Add trailing params.

This commit is contained in:
Sergio Benitez 2016-09-08 00:02:17 -07:00
parent a6967cb48f
commit b755e53f63
12 changed files with 255 additions and 138 deletions

View File

@ -2,19 +2,20 @@
#![plugin(rocket_macros)]
extern crate rocket;
use rocket::Rocket;
use std::fs::File;
use std::io::Error as IOError;
use std::path::{Path, PathBuf};
#[get("/")]
fn index() -> File {
File::open("static/index.html").unwrap()
}
#[get("/<file>")]
fn files(file: &str) -> Result<File, IOError> {
File::open(format!("static/{}", file))
#[get("/<file..>")]
fn files(file: PathBuf) -> Result<File, IOError> {
File::open(Path::new("static/").join(file))
}
fn main() {

View File

@ -36,7 +36,7 @@ pub use request::Request;
pub use method::Method;
pub use response::{Response, Responder};
pub use error::Error;
pub use param::FromParam;
pub use param::{FromParam, FromSegments};
pub use router::{Router, Route};
pub use catcher::Catcher;
pub use rocket::Rocket;

View File

@ -1,5 +1,8 @@
use std::str::FromStr;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, SocketAddr};
use std::path::PathBuf;
use router::Segments;
use url;
use error::Error;
@ -34,3 +37,19 @@ macro_rules! impl_with_fromstr {
impl_with_fromstr!(f32, f64, isize, i8, i16, i32, i64, usize, u8, u16, u32, u64,
bool, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6,
SocketAddr);
pub trait FromSegments<'a>: Sized {
fn from_segments(segments: Segments<'a>) -> Result<Self, Error>;
}
impl<'a> FromSegments<'a> for Segments<'a> {
fn from_segments(segments: Segments<'a>) -> Result<Segments<'a>, Error> {
Ok(segments)
}
}
impl<'a> FromSegments<'a> for PathBuf {
fn from_segments(segments: Segments<'a>) -> Result<PathBuf, Error> {
Ok(segments.collect())
}
}

View File

@ -6,7 +6,7 @@ use term_painter::Color::*;
use term_painter::ToStyle;
use error::Error;
use param::FromParam;
use param::{FromParam, FromSegments};
use method::Method;
use content_type::ContentType;
@ -28,15 +28,32 @@ pub struct Request<'a> {
}
impl<'a> Request<'a> {
// FIXME: Don't do the parsing here. I think. Not sure. Decide.
pub fn get_param<T: FromParam<'a>>(&self, n: usize) -> Result<T, Error> {
let params = self.params.borrow();
if params.is_none() || n >= params.as_ref().unwrap().len() {
debug!("{} is >= param count {}", n, params.as_ref().unwrap().len());
Err(Error::NoKey)
} else {
T::from_param(params.as_ref().unwrap()[n])
}
}
/// i is the index of the first segment to consider
pub fn get_segments<'r: 'a, T: FromSegments<'a>>(&'r self, i: usize)
-> Result<T, Error> {
if i >= self.uri().segment_count() {
debug!("{} is >= segment count {}", i, self.uri().segment_count());
Err(Error::NoKey)
} else {
// TODO: Really want to do self.uri.segments().skip(i).into_inner(),
// but the std lib doesn't implement it for Skip.
let mut segments = self.uri.segments();
for _ in segments.by_ref().take(i) { /* do nothing */ }
T::from_segments(segments)
}
}
pub fn mock(method: Method, uri: &str) -> Request {
Request {
params: RefCell::new(None),
@ -59,10 +76,24 @@ impl<'a> Request<'a> {
hyp_ct.map_or(ContentType::any(), |ct| ContentType::from(&ct.0))
}
/// Returns the first content-type accepted by this request.
pub fn accepts(&self) -> ContentType {
let accept = self.headers().get::<header::Accept>();
accept.map_or(ContentType::any(), |accept| {
let items = &accept.0;
if items.len() < 1 {
return ContentType::any();
} else {
return ContentType::from(items[0].item.clone())
}
})
}
pub fn uri(&'a self) -> URI<'a> {
self.uri.as_uri()
}
// FIXME: Don't need a refcell for this.
pub fn set_params(&'a self, route: &Route) {
*self.params.borrow_mut() = Some(route.get_params(self.uri.as_uri()))
}

View File

@ -44,7 +44,6 @@ impl Rocket {
info!("{}:", request);
let matches = self.router.route(&request);
trace_!("Found {} matches.", matches.len());
for route in matches {
// Retrieve and set the requests parameters.
info_!("Matched: {}", route);
@ -61,6 +60,7 @@ impl Rocket {
};
}
error_!("No matching routes.");
self.handle_not_found(&request, res);
}

View File

@ -1,3 +1,6 @@
/// The Collider trait is used to determine if two items that can be routed on
/// can match against a given request. That is, if two items `collide`, they
/// will both match against _some_ request.
pub trait Collider<T: ?Sized = Self> {
fn collides_with(&self, other: &T) -> bool;
}
@ -47,6 +50,7 @@ mod tests {
use {Request, Response};
use content_type::ContentType;
use std::str::FromStr;
use router::URI;
type SimpleRoute = (Method, &'static str);
@ -64,17 +68,8 @@ mod tests {
route_a.collides_with(&Route::ranked(0, Get, b.to_string(), dummy_handler))
}
fn s_r_collide(a: &'static str, b: &'static str) -> bool {
a.collides_with(&Route::new(Get, b.to_string(), dummy_handler))
}
fn r_s_collide(a: &'static str, b: &'static str) -> bool {
let route_a = Route::new(Get, a.to_string(), dummy_handler);
route_a.collides_with(b)
}
fn s_s_collide(a: &'static str, b: &'static str) -> bool {
a.collides_with(b)
URI::new(a).collides_with(&URI::new(b))
}
#[test]
@ -96,6 +91,14 @@ mod tests {
assert!(unranked_collide("/<name>/hi/there", "/dude/<name>/there"));
assert!(unranked_collide("/<name>/<a>/<b>", "/<a>/<b>/<c>"));
assert!(unranked_collide("/<name>/<a>/<b>/", "/<a>/<b>/<c>/"));
assert!(unranked_collide("/<a..>", "/hi"));
assert!(unranked_collide("/<a..>", "/hi/hey"));
assert!(unranked_collide("/<a..>", "/hi/hey/hayo"));
assert!(unranked_collide("/a/<a..>", "/a/hi/hey/hayo"));
assert!(unranked_collide("/a/<b>/<a..>", "/a/hi/hey/hayo"));
assert!(unranked_collide("/a/<b>/<c>/<a..>", "/a/hi/hey/hayo"));
assert!(unranked_collide("/<b>/<c>/<a..>", "/a/hi/hey/hayo"));
assert!(unranked_collide("/<b>/<c>/hey/hayo", "/a/hi/hey/hayo"));
}
#[test]
@ -114,6 +117,7 @@ mod tests {
assert!(unranked_collide("/<a>/<b>", "/a/b<c>"));
assert!(unranked_collide("/<a>/bc<b>", "/a/b<c>"));
assert!(unranked_collide("/<a>/bc<b>d", "/a/b<c>"));
assert!(unranked_collide("/<a..>", "///a///"));
}
#[test]
@ -130,6 +134,10 @@ mod tests {
assert!(!unranked_collide("/a<a>/<b>", "/b<b>/<a>"));
assert!(!unranked_collide("/a/<b>", "/b/<b>"));
assert!(!unranked_collide("/a<a>/<b>", "/b/<b>"));
assert!(!unranked_collide("/<a..>", "/"));
assert!(!unranked_collide("/hi/<a..>", "/hi"));
assert!(!unranked_collide("/hi/<a..>", "/hi/"));
assert!(!unranked_collide("/<a..>", "//////"));
}
#[test]
@ -144,34 +152,6 @@ mod tests {
#[test]
fn test_str_non_collisions() {
assert!(!s_r_collide("/a", "/b"));
assert!(!s_r_collide("/a/b", "/a"));
assert!(!s_r_collide("/a/b", "/a/c"));
assert!(!s_r_collide("/a/hello", "/a/c"));
assert!(!s_r_collide("/hello", "/a/c"));
assert!(!s_r_collide("/hello/there", "/hello/there/guy"));
assert!(!s_r_collide("/b<a>/there", "/hi/there"));
assert!(!s_r_collide("/<a>/<b>c", "/hi/person"));
assert!(!s_r_collide("/<a>/<b>cd", "/hi/<a>e"));
assert!(!s_r_collide("/a<a>/<b>", "/b<b>/<a>"));
assert!(!s_r_collide("/a/<b>", "/b/<b>"));
assert!(!s_r_collide("/a<a>/<b>", "/b/<b>"));
assert!(!r_s_collide("/a", "/b"));
assert!(!r_s_collide("/a/b", "/a"));
assert!(!r_s_collide("/a/b", "/a/c"));
assert!(!r_s_collide("/a/hello", "/a/c"));
assert!(!r_s_collide("/hello", "/a/c"));
assert!(!r_s_collide("/hello/there", "/hello/there/guy"));
assert!(!r_s_collide("/b<a>/there", "/hi/there"));
assert!(!r_s_collide("/<a>/<b>c", "/hi/person"));
assert!(!r_s_collide("/<a>/<b>cd", "/hi/<a>e"));
assert!(!r_s_collide("/a<a>/<b>", "/b<b>/<a>"));
assert!(!r_s_collide("/a/<b>", "/b/<b>"));
assert!(!r_s_collide("/a<a>/<b>", "/b/<b>"));
}
#[test]
fn test_str_collisions() {
assert!(!s_s_collide("/a", "/b"));
assert!(!s_s_collide("/a/b", "/a"));
assert!(!s_s_collide("/a/b", "/a/c"));
@ -184,6 +164,33 @@ mod tests {
assert!(!s_s_collide("/a<a>/<b>", "/b<b>/<a>"));
assert!(!s_s_collide("/a/<b>", "/b/<b>"));
assert!(!s_s_collide("/a<a>/<b>", "/b/<b>"));
assert!(!s_s_collide("/a", "/b"));
assert!(!s_s_collide("/a/b", "/a"));
assert!(!s_s_collide("/a/b", "/a/c"));
assert!(!s_s_collide("/a/hello", "/a/c"));
assert!(!s_s_collide("/hello", "/a/c"));
assert!(!s_s_collide("/hello/there", "/hello/there/guy"));
assert!(!s_s_collide("/b<a>/there", "/hi/there"));
assert!(!s_s_collide("/<a>/<b>c", "/hi/person"));
assert!(!s_s_collide("/<a>/<b>cd", "/hi/<a>e"));
assert!(!s_s_collide("/a<a>/<b>", "/b<b>/<a>"));
assert!(!s_s_collide("/a/<b>", "/b/<b>"));
assert!(!s_s_collide("/a<a>/<b>", "/b/<b>"));
assert!(!s_s_collide("/a", "/b"));
assert!(!s_s_collide("/a/b", "/a"));
assert!(!s_s_collide("/a/b", "/a/c"));
assert!(!s_s_collide("/a/hello", "/a/c"));
assert!(!s_s_collide("/hello", "/a/c"));
assert!(!s_s_collide("/hello/there", "/hello/there/guy"));
assert!(!s_s_collide("/b<a>/there", "/hi/there"));
assert!(!s_s_collide("/<a>/<b>c", "/hi/person"));
assert!(!s_s_collide("/<a>/<b>cd", "/hi/<a>e"));
assert!(!s_s_collide("/a<a>/<b>", "/b<b>/<a>"));
assert!(!s_s_collide("/a/<b>", "/b/<b>"));
assert!(!s_s_collide("/a<a>/<b>", "/b/<b>"));
assert!(!s_s_collide("/<a..>", "/"));
assert!(!s_s_collide("/hi/<a..>", "/hi/"));
assert!(!s_s_collide("/a/hi/<a..>", "/a/hi/"));
}
fn ct_route(m: Method, s: &str, ct: &str) -> Route {

View File

@ -3,14 +3,15 @@ mod route;
mod uri;
pub use self::collider::Collider;
pub use self::uri::{URI, URIBuf};
pub use self::uri::{URI, URIBuf, Segments};
pub use self::route::Route;
use std::collections::hash_map::HashMap;
use method::Method;
use request::Request;
type Selector = (Method, usize);
// type Selector = (Method, usize);
type Selector = Method;
#[derive(Default)]
pub struct Router {
@ -23,7 +24,8 @@ impl Router {
}
pub fn add(&mut self, route: Route) {
let selector = (route.method, route.path.segment_count());
// let selector = (route.method, route.path.segment_count());
let selector = route.method;
self.routes.entry(selector).or_insert_with(|| vec![]).push(route);
}
@ -33,9 +35,12 @@ impl Router {
// FIXME: Figure out a way to get more than one route, i.e., to correctly
// handle ranking.
pub fn route<'b>(&'b self, req: &Request) -> Vec<&'b Route> {
let num_segments = req.uri.segment_count();
self.routes.get(&(req.method, num_segments)).map_or(vec![], |routes| {
let mut matches: Vec<&'b Route> = routes.iter().filter(|r| {
trace_!("Trying to route: {}", req);
// let num_segments = req.uri.segment_count();
// self.routes.get(&(req.method, num_segments)).map_or(vec![], |routes| {
self.routes.get(&req.method).map_or(vec![], |routes| {
trace_!("All possible matches: {:?}", routes);
let mut matches: Vec<_> = routes.iter().filter(|r| {
r.collides_with(req)
}).collect();
@ -120,6 +125,18 @@ mod test {
assert!(unranked_route_collisions(&["/<a>", "/<b>"]));
assert!(unranked_route_collisions(&["/hello/bob", "/hello/<b>"]));
assert!(unranked_route_collisions(&["/a/b/<c>/d", "/<a>/<b>/c/d"]));
assert!(unranked_route_collisions(&["/a/b", "/<a..>"]));
assert!(unranked_route_collisions(&["/a/b/c", "/a/<a..>"]));
assert!(unranked_route_collisions(&["/<a>/b", "/a/<a..>"]));
assert!(unranked_route_collisions(&["/a/<b>", "/a/<a..>"]));
assert!(unranked_route_collisions(&["/a/b/<c>", "/a/<a..>"]));
}
#[test]
fn test_no_collisions() {
assert!(!unranked_route_collisions(&["/<a>", "/a/<a..>"]));
assert!(!unranked_route_collisions(&["/a/b", "/a/b/c"]));
assert!(!unranked_route_collisions(&["/a/b/c/d", "/a/b/c/<d>/e"]));
}
#[test]
@ -128,6 +145,8 @@ mod test {
assert!(!default_rank_route_collisions(&["/hello/bob", "/hello/<b>"]));
assert!(!default_rank_route_collisions(&["/a/b/c/d", "/<a>/<b>/c/d"]));
assert!(!default_rank_route_collisions(&["/hi", "/<hi>"]));
assert!(!default_rank_route_collisions(&["/hi", "/<hi>"]));
assert!(!default_rank_route_collisions(&["/a/b", "/a/b/<c..>"]));
}
fn route<'a>(router: &'a Router, method: Method, uri: &str) -> Option<&'a Route> {
@ -169,6 +188,13 @@ mod test {
assert!(route(&router, Put, "/hello").is_some());
assert!(route(&router, Post, "/hello").is_some());
assert!(route(&router, Delete, "/hello").is_some());
let router = router_with_routes(&["/<a..>"]);
assert!(route(&router, Get, "/hello/hi").is_some());
assert!(route(&router, Get, "/a/b/").is_some());
assert!(route(&router, Get, "/i/a").is_some());
assert!(route(&router, Get, "/a/b/c/d/e/f").is_some());
}
#[test]
@ -275,6 +301,18 @@ mod test {
with: [(0, "/<a>/<b>/edit"), (2, "/profile/<d>"), (5, "/<a>/<b>/<c>")],
expect: (0, "/<a>/<b>/edit"), (5, "/<a>/<b>/<c>")
);
assert_ranked_routing!(
to: "/a/b",
with: [(0, "/a/b"), (1, "/a/<b..>")],
expect: (0, "/a/b"), (1, "/a/<b..>")
);
assert_ranked_routing!(
to: "/a/b/c/d/e/f",
with: [(1, "/a/<b..>"), (2, "/a/b/<c..>")],
expect: (1, "/a/<b..>"), (2, "/a/b/<c..>")
);
}
fn match_params(router: &Router, path: &str, expected: &[&str]) -> bool {

View File

@ -53,12 +53,14 @@ impl Route {
// is, whether you can have: /a<a>b/ or even /<a>:<b>/
// TODO: Don't return a Vec...take in an &mut [&'a str] (no alloc!)
pub fn get_params<'a>(&self, uri: URI<'a>) -> Vec<&'a str> {
let route_components = self.path.segments();
let uri_components = uri.segments();
let route_segs = self.path.segments();
let uri_segs = uri.segments();
let mut result = Vec::with_capacity(self.path.segment_count());
for (route_seg, uri_seg) in route_components.zip(uri_components) {
if route_seg.starts_with('<') { // FIXME: Here.
for (route_seg, uri_seg) in route_segs.zip(uri_segs) {
if route_seg.ends_with("..>") { // FIXME: Here.
break;
} else if route_seg.ends_with('>') { // FIXME: Here.
result.push(uri_seg);
}
}
@ -83,6 +85,12 @@ impl fmt::Display for Route {
}
}
impl fmt::Debug for Route {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
<Route as fmt::Display>::fmt(self, f)
}
}
impl<'a> From<&'a StaticRouteInfo> for Route {
fn from(info: &'a StaticRouteInfo) -> Route {
let mut route = Route::new(info.method, info.path, info.handler);
@ -97,31 +105,17 @@ impl<'a> From<&'a StaticRouteInfo> for Route {
impl Collider for Route {
fn collides_with(&self, b: &Route) -> bool {
self.path.segment_count() == b.path.segment_count()
&& self.method == b.method
self.method == b.method
&& self.rank == b.rank
&& self.content_type.collides_with(&b.content_type)
&& self.path.collides_with(&b.path)
}
}
impl<'a> Collider<Route> for &'a str {
fn collides_with(&self, other: &Route) -> bool {
let path = URI::new(self);
path.collides_with(&other.path)
}
}
impl Collider<str> for Route {
fn collides_with(&self, other: &str) -> bool {
other.collides_with(self)
&& self.path.as_uri().collides_with(&b.path.as_uri())
}
}
impl<'r> Collider<Request<'r>> for Route {
fn collides_with(&self, req: &Request) -> bool {
self.method == req.method
&& req.uri.collides_with(&self.path)
&& req.content_type().collides_with(&self.content_type)
&& req.uri.as_uri().collides_with(&self.path.as_uri())
&& req.accepts().collides_with(&self.content_type)
}
}

View File

@ -130,38 +130,27 @@ impl<'a> From<&'a str> for URIBuf {
impl<'a> Collider for URI<'a> {
fn collides_with(&self, other: &URI) -> bool {
if self.segment_count() != other.segment_count() {
return false;
}
let mut trailing = false;
for (seg_a, seg_b) in self.segments().zip(other.segments()) {
if seg_a.ends_with("..>") || seg_b.ends_with("..>") {
trailing = true;
break;
}
if !seg_a.collides_with(seg_b) {
return false;
}
}
if !trailing && (self.segment_count() != other.segment_count()) {
return false;
}
true
}
}
impl Collider for URIBuf {
fn collides_with(&self, other: &URIBuf) -> bool {
self.as_uri().collides_with(&other.as_uri())
}
}
impl<'a> Collider<URI<'a>> for URIBuf {
fn collides_with(&self, other: &URI<'a>) -> bool {
self.as_uri().collides_with(other)
}
}
impl<'a> Collider<URIBuf> for URI<'a> {
fn collides_with(&self, other: &URIBuf) -> bool {
other.as_uri().collides_with(self)
}
}
#[derive(Clone, Debug)]
pub struct Segments<'a>(&'a str);
impl<'a> Iterator for Segments<'a> {

View File

@ -4,11 +4,11 @@ use std::fmt::Display;
use ::{ROUTE_STRUCT_PREFIX, ROUTE_FN_PREFIX, PARAM_PREFIX};
use utils::{emit_item, span, sep_by_tok, option_as_expr, strip_ty_lifetimes};
use utils::{SpanExt, IdentExt, ArgExt};
use parser::RouteParams;
use parser::{Param, RouteParams};
use syntax::codemap::{Span, Spanned};
use syntax::tokenstream::TokenTree;
use syntax::ast::{Name, Arg, Ident, Stmt, Expr, MetaItem, Path};
use syntax::ast::{Arg, Ident, Stmt, Expr, MetaItem, Path};
use syntax::ext::base::{Annotatable, ExtCtxt};
use syntax::ext::build::AstBuilder;
use syntax::parse::token::{self, str_to_ident};
@ -117,48 +117,49 @@ impl RouteGenerateExt for RouteParams {
// TODO: Add some kind of logging facility in Rocket to get be able to log
// an error/debug message if parsing a parameter fails.
fn generate_param_statements(&self, ecx: &ExtCtxt) -> Vec<Stmt> {
let params: Vec<_> = self.path_params(ecx).collect();
let mut fn_param_statements = vec![];
// Retrieve an iterator over the user's path parameters and ensure that
// each parameter appears in the function signature.
for param in &params {
if self.annotated_fn.find_input(&param.node.name).is_none() {
self.missing_declared_err(ecx, &param);
}
}
// Generate a statement for every declared paramter in the path.
let mut declared_set = HashSet::new();
for (i, param) in self.path_params(ecx).enumerate() {
declared_set.insert(param.ident().name.clone());
let ty = match self.annotated_fn.find_input(&param.ident().name) {
Some(arg) => strip_ty_lifetimes(arg.ty.clone()),
None => {
self.missing_declared_err(ecx, param.inner());
continue;
}
};
// Create a function thats checks if an argument was declared in `path`.
let set: HashSet<&Name> = params.iter().map(|p| &p.node.name).collect();
let declared = &|arg: &&Arg| set.contains(&*arg.name().unwrap());
let ident = param.ident().prepend(PARAM_PREFIX);
let expr = match param {
Param::Single(_) => quote_expr!(ecx, _req.get_param($i)),
Param::Many(_) => quote_expr!(ecx, _req.get_segments($i)),
};
// These are all of the arguments in the function signature.
let all = &self.annotated_fn.decl().inputs;
// Generate code for each user declared parameter.
for (i, arg) in all.iter().filter(declared).enumerate() {
let ident = arg.ident().unwrap().prepend(PARAM_PREFIX);
let ty = strip_ty_lifetimes(arg.ty.clone());
fn_param_statements.push(quote_stmt!(ecx,
let $ident: $ty = match _req.get_param($i) {
let $ident: $ty = match $expr {
Ok(v) => v,
Err(_) => return ::rocket::Response::forward()
};
).expect("declared param parsing statement"));
}
// A from_request parameter is one that isn't declared, `form`, or query.
// A from_request parameter is one that isn't declared, form, or query.
let from_request = |a: &&Arg| {
!declared(a) && self.form_param.as_ref().map_or(true, |p| {
!a.named(&p.value().name)
}) && self.query_param.as_ref().map_or(true, |p| {
!a.named(&p.node.name)
})
!declared_set.contains(&*a.name().unwrap())
&& self.form_param.as_ref().map_or(true, |p| {
!a.named(&p.value().name)
}) && self.query_param.as_ref().map_or(true, |p| {
!a.named(&p.node.name)
})
};
// Generate the code for `form_request` parameters.
let all = &self.annotated_fn.decl().inputs;
for arg in all.iter().filter(from_request) {
let (ident, ty) = (arg.ident().unwrap().prepend(PARAM_PREFIX), &arg.ty);
let ident = arg.ident().unwrap().prepend(PARAM_PREFIX);
let ty = strip_ty_lifetimes(arg.ty.clone());
fn_param_statements.push(quote_stmt!(ecx,
let $ident: $ty = match
<$ty as ::rocket::request::FromRequest>::from_request(&_req) {

View File

@ -7,5 +7,5 @@ mod function;
pub use self::keyvalue::KVSpanned;
pub use self::route::RouteParams;
pub use self::error::ErrorParams;
pub use self::param::ParamIter;
pub use self::param::{Param, ParamIter};
pub use self::function::Function;

View File

@ -3,12 +3,32 @@ use syntax::ext::base::ExtCtxt;
use syntax::codemap::{Span, Spanned, BytePos};
use syntax::parse::token::str_to_ident;
use utils::span;
use utils::{span, SpanExt};
#[derive(Debug)]
pub enum Param {
Single(Spanned<Ident>),
Many(Spanned<Ident>)
}
impl Param {
pub fn inner(&self) -> &Spanned<Ident> {
match *self {
Param::Single(ref ident) | Param::Many(ref ident) => ident
}
}
pub fn ident(&self) -> &Ident {
match *self {
Param::Single(ref ident) | Param::Many(ref ident) => &ident.node
}
}
}
pub struct ParamIter<'s, 'a, 'c: 'a> {
ctxt: &'a ExtCtxt<'c>,
span: Span,
string: &'s str
string: &'s str,
}
impl<'s, 'a, 'c: 'a> ParamIter<'s, 'a, 'c> {
@ -16,19 +36,19 @@ impl<'s, 'a, 'c: 'a> ParamIter<'s, 'a, 'c> {
ParamIter {
ctxt: c,
span: p,
string: s
string: s,
}
}
}
impl<'s, 'a, 'c> Iterator for ParamIter<'s, 'a, 'c> {
type Item = Spanned<Ident>;
type Item = Param;
fn next(&mut self) -> Option<Spanned<Ident>> {
fn next(&mut self) -> Option<Param> {
// Find the start and end indexes for the next parameter, if any.
let (start, end) = match (self.string.find('<'), self.string.find('>')) {
(Some(i), Some(j)) => (i, j),
_ => return None
_ => return None,
};
// Ensure we found a valid parameter.
@ -37,24 +57,41 @@ impl<'s, 'a, 'c> Iterator for ParamIter<'s, 'a, 'c> {
return None;
}
// Calculate the parameter and the span for the parameter.
let param = &self.string[(start + 1)..end];
// Calculate the parameter's ident.
let full_param = &self.string[(start + 1)..end];
let (is_many, param) = match full_param.ends_with("..") {
true => (true, &full_param[..(full_param.len() - 2)]),
false => (false, full_param)
};
let mut param_span = self.span;
param_span.lo = self.span.lo + BytePos(start as u32);
param_span.hi = self.span.lo + BytePos((end + 1) as u32);
// Check for nonemptiness and that the characters are correct.
// Advance the string and span.
self.string = &self.string[(end + 1)..];
self.span.lo = self.span.lo + BytePos((end + 1) as u32);
// Check for nonemptiness, that the characters are correct, and return.
if param.is_empty() {
self.ctxt.span_err(param_span, "parameter names cannot be empty");
None
} else if param.contains(|c: char| !c.is_alphanumeric()) {
self.ctxt.span_err(param_span, "parameters must be alphanumeric");
self.ctxt.span_err(param_span, "parameter names must be alphanumeric");
None
} else if is_many && !self.string.is_empty() {
let sp = self.span.shorten_to(self.string.len() as u32);
self.ctxt.struct_span_err(sp, "text after a trailing '..' param")
.span_note(param_span, "trailing param is here")
.emit();
None
} else {
self.string = &self.string[(end + 1)..];
self.span.lo = self.span.lo + BytePos((end + 1) as u32);
Some(span(str_to_ident(param), param_span))
let spanned_ident = span(str_to_ident(param), param_span);
match is_many {
true => Some(Param::Many(spanned_ident)),
false => Some(Param::Single(spanned_ident))
}
}
}
}