mirror of https://github.com/rwf2/Rocket.git
Complete rewrite of macro parsing and item generation.
This commit is contained in:
parent
99074a913d
commit
a42d7f8668
|
@ -4,12 +4,12 @@
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
use rocket::{Rocket, Error, Request};
|
use rocket::{Rocket, Error, Request};
|
||||||
|
|
||||||
#[route(GET, path = "/hello/<name>/<age>")]
|
#[get("/hello/<name>/<age>")]
|
||||||
fn hello(name: &str, age: i8) -> String {
|
fn hello(name: &str, age: i8) -> String {
|
||||||
format!("Hello, {} year old named {}!", age, name)
|
format!("Hello, {} year old named {}!", age, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[error(code = "404")]
|
#[error(404)]
|
||||||
fn not_found<'r>(_error: Error, request: &'r Request<'r>) -> String {
|
fn not_found<'r>(_error: Error, request: &'r Request<'r>) -> String {
|
||||||
format!("<p>Sorry, but '{}' is not a valid path!</p>
|
format!("<p>Sorry, but '{}' is not a valid path!</p>
|
||||||
<p>Try visiting /hello/<name>/<age> instead.</p>",
|
<p>Try visiting /hello/<name>/<age> instead.</p>",
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
use rocket::Rocket;
|
use rocket::Rocket;
|
||||||
|
|
||||||
#[GET(path = "/hello/<name>/<age>")]
|
#[get("/hello/<name>/<age>")]
|
||||||
fn hello(name: &str, age: i8) -> String {
|
fn hello(name: &str, age: i8) -> String {
|
||||||
format!("Hello, {} year old named {}!", age, name)
|
format!("Hello, {} year old named {}!", age, name)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
use rocket::Rocket;
|
use rocket::Rocket;
|
||||||
|
|
||||||
#[GET(path = "/")]
|
#[get("/")]
|
||||||
fn root() -> &'static str {
|
fn root() -> &'static str {
|
||||||
"Hello, world!"
|
"Hello, world!"
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use content_type::ContentType;
|
||||||
pub struct StaticRouteInfo {
|
pub struct StaticRouteInfo {
|
||||||
pub method: Method,
|
pub method: Method,
|
||||||
pub path: &'static str,
|
pub path: &'static str,
|
||||||
pub content_type: ContentType,
|
pub accept: Option<ContentType>,
|
||||||
pub handler: Handler,
|
pub handler: Handler,
|
||||||
pub rank: Option<isize>,
|
pub rank: Option<isize>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
pub use response::mime::{Mime, TopLevel, SubLevel};
|
pub use response::mime::{Mime, TopLevel, SubLevel};
|
||||||
use response::mime::{Param};
|
use response::mime::{Param};
|
||||||
|
use std::default::Default;
|
||||||
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
|
@ -50,6 +51,13 @@ impl ContentType {
|
||||||
is_some!(is_html: Application/Html);
|
is_some!(is_html: Application/Html);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ContentType {
|
||||||
|
#[inline(always)]
|
||||||
|
fn default() -> ContentType {
|
||||||
|
ContentType::any()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Into<Mime> for ContentType {
|
impl Into<Mime> for ContentType {
|
||||||
fn into(self) -> Mime {
|
fn into(self) -> Mime {
|
||||||
Mime(self.0, self.1, self.2.unwrap_or_default())
|
Mime(self.0, self.1, self.2.unwrap_or_default())
|
||||||
|
|
|
@ -86,7 +86,7 @@ impl fmt::Display for Route {
|
||||||
impl<'a> From<&'a StaticRouteInfo> for Route {
|
impl<'a> From<&'a StaticRouteInfo> for Route {
|
||||||
fn from(info: &'a StaticRouteInfo) -> Route {
|
fn from(info: &'a StaticRouteInfo) -> Route {
|
||||||
let mut route = Route::new(info.method, info.path, info.handler);
|
let mut route = Route::new(info.method, info.path, info.handler);
|
||||||
route.content_type = info.content_type.clone();
|
route.content_type = info.accept.clone().unwrap_or(ContentType::any());
|
||||||
if let Some(rank) = info.rank {
|
if let Some(rank) = info.rank {
|
||||||
route.rank = rank;
|
route.rank = rank;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
use utils::*;
|
||||||
|
use ::{CATCH_STRUCT_PREFIX, CATCH_FN_PREFIX};
|
||||||
|
|
||||||
|
use syntax::codemap::{Span};
|
||||||
|
use syntax::ast::{MetaItem};
|
||||||
|
use syntax::ext::base::{Annotatable, ExtCtxt};
|
||||||
|
use parser::ErrorParams;
|
||||||
|
|
||||||
|
pub fn error_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
|
||||||
|
annotated: &Annotatable, push: &mut FnMut(Annotatable)) {
|
||||||
|
let error = ErrorParams::from(ecx, sp, meta_item, annotated);
|
||||||
|
|
||||||
|
let user_fn_name = error.annotated_fn.ident();
|
||||||
|
let catch_fn_name = user_fn_name.prepend(CATCH_FN_PREFIX);
|
||||||
|
let code = error.code.node;
|
||||||
|
emit_item(push, quote_item!(ecx,
|
||||||
|
fn $catch_fn_name<'rocket>(err: ::rocket::Error,
|
||||||
|
req: &'rocket ::rocket::Request<'rocket>)
|
||||||
|
-> ::rocket::Response<'rocket> {
|
||||||
|
rocket::Response::with_raw_status($code, $user_fn_name(err, req))
|
||||||
|
}
|
||||||
|
).expect("catch function"));
|
||||||
|
|
||||||
|
let struct_name = user_fn_name.prepend(CATCH_STRUCT_PREFIX);
|
||||||
|
emit_item(push, quote_item!(ecx,
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
pub static $struct_name: rocket::StaticCatchInfo = rocket::StaticCatchInfo {
|
||||||
|
code: $code,
|
||||||
|
handler: $catch_fn_name
|
||||||
|
};
|
||||||
|
).expect("catch info struct"));
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
mod route;
|
||||||
|
mod error;
|
||||||
|
|
||||||
|
pub use self::route::*;
|
||||||
|
pub use self::error::*;
|
||||||
|
|
|
@ -0,0 +1,199 @@
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use ::{ROUTE_STRUCT_PREFIX, ROUTE_FN_PREFIX};
|
||||||
|
use utils::{emit_item, span, sep_by_tok, SpanExt, IdentExt, ArgExt, option_as_expr};
|
||||||
|
use parser::RouteParams;
|
||||||
|
|
||||||
|
use syntax::codemap::{Span, Spanned};
|
||||||
|
use syntax::tokenstream::TokenTree;
|
||||||
|
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};
|
||||||
|
use syntax::ptr::P;
|
||||||
|
|
||||||
|
use rocket::{Method, ContentType};
|
||||||
|
use rocket::content_type::{TopLevel, SubLevel};
|
||||||
|
|
||||||
|
fn method_variant_to_expr(ecx: &ExtCtxt, method: Method) -> Path {
|
||||||
|
quote_enum!(ecx, method => ::rocket::Method {
|
||||||
|
Options, Get, Post, Put, Delete, Head, Trace, Connect, Patch;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn top_level_to_expr(ecx: &ExtCtxt, level: &TopLevel) -> Path {
|
||||||
|
quote_enum!(ecx, *level => ::rocket::content_type::TopLevel {
|
||||||
|
Star, Text, Image, Audio, Video, Application, Multipart, Model, Message;
|
||||||
|
Ext(ref s) => quote_path!(ecx, ::rocket::content_type::TopLevel::Ext($s))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sub_level_to_expr(ecx: &ExtCtxt, level: &SubLevel) -> Path {
|
||||||
|
quote_enum!(ecx, *level => ::rocket::content_type::SubLevel {
|
||||||
|
Star, Plain, Html, Xml, Javascript, Css, EventStream, Json,
|
||||||
|
WwwFormUrlEncoded, Msgpack, OctetStream, FormData, Png, Gif, Bmp, Jpeg;
|
||||||
|
Ext(ref s) => quote_path!(ecx, ::rocket::content_type::SubLevel::Ext($s))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accept_to_path(ecx: &ExtCtxt, accept: Option<ContentType>) -> Option<Path> {
|
||||||
|
accept.map(|ct| {
|
||||||
|
let top_level = top_level_to_expr(ecx, &ct.0);
|
||||||
|
let sub_level = sub_level_to_expr(ecx, &ct.1);
|
||||||
|
quote_path!(ecx, ::rocket::ContentType($top_level, $sub_level, None))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
trait RouteGenerateExt {
|
||||||
|
fn generate_form_statement(&self, ecx: &ExtCtxt) -> Option<Stmt>;
|
||||||
|
fn generate_param_statements(&self, ecx: &ExtCtxt) -> Vec<Stmt>;
|
||||||
|
fn generate_fn_arguments(&self, ecx: &ExtCtxt) -> Vec<TokenTree>;
|
||||||
|
fn explode(&self, ecx: &ExtCtxt) -> (&String, Path, P<Expr>, P<Expr>);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RouteGenerateExt for RouteParams {
|
||||||
|
fn generate_form_statement(&self, ecx: &ExtCtxt) -> Option<Stmt> {
|
||||||
|
let name = self.form_param.as_ref().map(|p| p.value());
|
||||||
|
let arg: &Arg = match name.and_then(|p| self.annotated_fn.find_input(p)) {
|
||||||
|
Some(arg) => arg,
|
||||||
|
None => return None
|
||||||
|
};
|
||||||
|
|
||||||
|
let (name, ty) = (arg.ident().unwrap(), &arg.ty);
|
||||||
|
Some(quote_stmt!(ecx,
|
||||||
|
let $name: $ty =
|
||||||
|
if let Ok(s) = ::std::str::from_utf8(_req.data.as_slice()) {
|
||||||
|
if let Ok(v) = ::rocket::form::FromForm::from_form_string(s) {
|
||||||
|
v
|
||||||
|
} else {
|
||||||
|
return ::rocket::Response::not_found();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return ::rocket::Response::server_error();
|
||||||
|
};
|
||||||
|
).expect("form statement"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 path_params = self.path_params(ecx);
|
||||||
|
let all = &self.annotated_fn.decl().inputs;
|
||||||
|
let declared: HashSet<&str> = path_params.map(|p| p.node).collect();
|
||||||
|
let arg_declared = |arg: &&Arg| declared.contains(&*arg.name().unwrap());
|
||||||
|
|
||||||
|
let mut fn_param_statements = vec![];
|
||||||
|
|
||||||
|
for (i, arg) in all.iter().filter(&arg_declared).enumerate() {
|
||||||
|
let (ident, ty) = (arg.ident().unwrap(), &arg.ty);
|
||||||
|
fn_param_statements.push(quote_stmt!(ecx,
|
||||||
|
let $ident: $ty = match _req.get_param($i) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => return ::rocket::Response::forward()
|
||||||
|
};
|
||||||
|
).expect("declared param parsing statement"));
|
||||||
|
}
|
||||||
|
|
||||||
|
for arg in all.iter().filter(|p| !arg_declared(p)) {
|
||||||
|
let (ident, ty) = (arg.ident().unwrap(), &arg.ty);
|
||||||
|
fn_param_statements.push(quote_stmt!(ecx,
|
||||||
|
let $ident: $ty = match
|
||||||
|
<$ty as ::rocket::request::FromRequest>::from_request(&_req) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_e) => return ::rocket::Response::forward()
|
||||||
|
};
|
||||||
|
).expect("undeclared param parsing statement"));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn_param_statements
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_fn_arguments(&self, ecx: &ExtCtxt) -> Vec<TokenTree> {
|
||||||
|
let args = self.annotated_fn.decl().inputs.iter().map(|a| {
|
||||||
|
a.ident().expect("function decl pat -> ident").clone()
|
||||||
|
}).collect::<Vec<Ident>>();
|
||||||
|
|
||||||
|
sep_by_tok(ecx, &args, token::Comma)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn explode(&self, ecx: &ExtCtxt) -> (&String, Path, P<Expr>, P<Expr>) {
|
||||||
|
let path = &self.path.node;
|
||||||
|
let method = method_variant_to_expr(ecx, self.method.node);
|
||||||
|
let accept = self.accept.as_ref().map(|kv| kv.value().clone());
|
||||||
|
let content_type = option_as_expr(ecx, &accept_to_path(ecx, accept));
|
||||||
|
let rank = option_as_expr(ecx, &self.rank);
|
||||||
|
|
||||||
|
(path, method, content_type, rank)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Compilation fails when parameters have the same name as the function!
|
||||||
|
fn generic_route_decorator(known_method: Option<Spanned<Method>>,
|
||||||
|
ecx: &mut ExtCtxt,
|
||||||
|
sp: Span,
|
||||||
|
meta_item: &MetaItem,
|
||||||
|
annotated: &Annotatable,
|
||||||
|
push: &mut FnMut(Annotatable)) {
|
||||||
|
// Initialize the logger.
|
||||||
|
::rocket::logger::init(::rocket::LoggingLevel::Debug);
|
||||||
|
|
||||||
|
// Parse the route and generate the code to create the form and param vars.
|
||||||
|
let route = RouteParams::from(ecx, sp, known_method, meta_item, annotated);
|
||||||
|
let form_statement = route.generate_form_statement(ecx);
|
||||||
|
let param_statements = route.generate_param_statements(ecx);
|
||||||
|
let fn_arguments = route.generate_fn_arguments(ecx);
|
||||||
|
|
||||||
|
// Generate and emit the wrapping function with the Rocket handler signature.
|
||||||
|
let user_fn_name = route.annotated_fn.ident();
|
||||||
|
let route_fn_name = user_fn_name.prepend(ROUTE_FN_PREFIX);
|
||||||
|
emit_item(push, quote_item!(ecx,
|
||||||
|
fn $route_fn_name<'rocket>(_req: &'rocket ::rocket::Request<'rocket>)
|
||||||
|
-> ::rocket::Response<'rocket> {
|
||||||
|
$form_statement
|
||||||
|
$param_statements
|
||||||
|
let result = $user_fn_name($fn_arguments);
|
||||||
|
::rocket::Response::new(result)
|
||||||
|
}
|
||||||
|
).unwrap());
|
||||||
|
|
||||||
|
// Generate and emit the static route info that uses the just generated
|
||||||
|
// function as its handler. A proper Rocket route will be created from this.
|
||||||
|
let struct_name = user_fn_name.prepend(ROUTE_STRUCT_PREFIX);
|
||||||
|
let (path, method, content_type, rank) = route.explode(ecx);
|
||||||
|
emit_item(push, quote_item!(ecx,
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
pub static $struct_name: ::rocket::StaticRouteInfo =
|
||||||
|
::rocket::StaticRouteInfo {
|
||||||
|
method: $method,
|
||||||
|
path: $path,
|
||||||
|
handler: $route_fn_name,
|
||||||
|
accept: $content_type,
|
||||||
|
rank: $rank,
|
||||||
|
};
|
||||||
|
).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn route_decorator(ecx: &mut ExtCtxt,
|
||||||
|
sp: Span,
|
||||||
|
meta_item: &MetaItem,
|
||||||
|
annotated: &Annotatable,
|
||||||
|
push: &mut FnMut(Annotatable)) {
|
||||||
|
generic_route_decorator(None, ecx, sp, meta_item, annotated, push);
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! method_decorator {
|
||||||
|
($name:ident, $method:ident) => (
|
||||||
|
pub fn $name(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
|
||||||
|
annotated: &Annotatable, push: &mut FnMut(Annotatable)) {
|
||||||
|
let i_sp = meta_item.span.shorten_to(meta_item.name().len() as u32);
|
||||||
|
let method = Some(span(Method::$method, i_sp));
|
||||||
|
generic_route_decorator(method, ecx, sp, meta_item, annotated, push);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
method_decorator!(get_decorator, Get);
|
||||||
|
method_decorator!(put_decorator, Put);
|
||||||
|
method_decorator!(post_decorator, Post);
|
||||||
|
method_decorator!(delete_decorator, Delete);
|
||||||
|
method_decorator!(patch_decorator, Patch);
|
|
@ -1,79 +0,0 @@
|
||||||
use utils::*;
|
|
||||||
use meta_item_parser::MetaItemParser;
|
|
||||||
use super::{CATCH_STRUCT_PREFIX, CATCH_FN_PREFIX};
|
|
||||||
|
|
||||||
use syntax::codemap::{Span};
|
|
||||||
use syntax::ast::{MetaItem};
|
|
||||||
use syntax::ext::base::{Annotatable, ExtCtxt};
|
|
||||||
use syntax::print::pprust::{item_to_string};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Params {
|
|
||||||
code: KVSpanned<u16>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_error_params(ecx: &ExtCtxt, meta_item: &MetaItem) -> Params {
|
|
||||||
// Ensure we've been supplied with a k = v meta item. Error out if not.
|
|
||||||
let params = meta_item.expect_list(ecx, "Bad use. Expected: #[error(...)]");
|
|
||||||
|
|
||||||
// Now grab all of the required and optional parameters.
|
|
||||||
let req: [&'static str; 1] = ["code"];
|
|
||||||
let kv_pairs = get_key_values(ecx, meta_item.span, &req, &[], &*params);
|
|
||||||
|
|
||||||
// Ensure we have a code, just to keep parsing and generating errors.
|
|
||||||
let code = kv_pairs.get("code").map_or(KVSpanned::dummy(404), |c| {
|
|
||||||
let numeric_code = match c.node.parse() {
|
|
||||||
Ok(n) => n,
|
|
||||||
Err(_) => {
|
|
||||||
let msg = "Error codes must be integer strings. (e.g. \"404\")";
|
|
||||||
ecx.span_err(c.v_span, msg);
|
|
||||||
404
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if numeric_code < 400 || numeric_code > 599 {
|
|
||||||
ecx.span_err(c.v_span, "Error codes must be >= 400 and <= 599.");
|
|
||||||
}
|
|
||||||
|
|
||||||
c.clone().map(|_| { numeric_code })
|
|
||||||
});
|
|
||||||
|
|
||||||
Params {
|
|
||||||
code: code,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn error_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
|
|
||||||
annotated: &Annotatable, push: &mut FnMut(Annotatable)) {
|
|
||||||
let parser = MetaItemParser::new(ecx, meta_item, annotated, &sp);
|
|
||||||
let item = parser.expect_item();
|
|
||||||
|
|
||||||
let error_params = get_error_params(ecx, meta_item);
|
|
||||||
debug!("Error parameters are: {:?}", error_params);
|
|
||||||
|
|
||||||
let fn_name = item.ident;
|
|
||||||
let catch_fn_name = prepend_ident(CATCH_FN_PREFIX, &item.ident);
|
|
||||||
let catch_code = error_params.code.node;
|
|
||||||
let catch_fn_item = quote_item!(ecx,
|
|
||||||
fn $catch_fn_name<'rocket>(err: ::rocket::Error,
|
|
||||||
req: &'rocket ::rocket::Request<'rocket>)
|
|
||||||
-> ::rocket::Response<'rocket> {
|
|
||||||
// TODO: Figure out what type signature of catcher should be.
|
|
||||||
let result = $fn_name(err, req);
|
|
||||||
rocket::Response::with_raw_status($catch_code, result)
|
|
||||||
}
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
debug!("{}", item_to_string(&catch_fn_item));
|
|
||||||
push(Annotatable::Item(catch_fn_item));
|
|
||||||
|
|
||||||
let struct_name = prepend_ident(CATCH_STRUCT_PREFIX, &item.ident);
|
|
||||||
push(Annotatable::Item(quote_item!(ecx,
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
pub static $struct_name: rocket::StaticCatchInfo = rocket::StaticCatchInfo {
|
|
||||||
code: $catch_code,
|
|
||||||
handler: $catch_fn_name
|
|
||||||
};
|
|
||||||
).unwrap()));
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
use super::{CATCH_STRUCT_PREFIX};
|
|
||||||
use utils::*;
|
|
||||||
use syntax::codemap::Span;
|
|
||||||
use syntax::tokenstream::TokenTree;
|
|
||||||
use syntax::ast::Expr;
|
|
||||||
use syntax::ext::base::{ExtCtxt, MacResult, MacEager};
|
|
||||||
use syntax::parse::token::Token;
|
|
||||||
use syntax::ptr::P;
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
const DEBUG: bool = false;
|
|
||||||
|
|
||||||
pub fn errors_macro(ecx: &mut ExtCtxt, _sp: Span, args: &[TokenTree])
|
|
||||||
-> Box<MacResult + 'static> {
|
|
||||||
let mut parser = ecx.new_parser_from_tts(args);
|
|
||||||
let mut paths = parse_paths(&mut parser).unwrap_or_else(|mut e| {
|
|
||||||
e.emit();
|
|
||||||
vec![]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Prefix each path terminator
|
|
||||||
prefix_paths(CATCH_STRUCT_PREFIX, &mut paths);
|
|
||||||
|
|
||||||
// Build up the P<Expr> for each path.
|
|
||||||
let path_exprs: Vec<P<Expr>> = paths.iter().map(|p| {
|
|
||||||
quote_expr!(ecx, rocket::Catcher::from(&$p))
|
|
||||||
}).collect();
|
|
||||||
|
|
||||||
// Now put them all in one vector and return the thing.
|
|
||||||
let path_list = token_separate(ecx, &path_exprs, Token::Comma);
|
|
||||||
let output = quote_expr!(ecx, vec![$path_list]).unwrap();
|
|
||||||
MacEager::expr(P(output))
|
|
||||||
}
|
|
|
@ -1,5 +1,8 @@
|
||||||
#![crate_type = "dylib"]
|
#![crate_type = "dylib"]
|
||||||
#![feature(quote, concat_idents, plugin_registrar, rustc_private)]
|
#![feature(quote, concat_idents, plugin_registrar, rustc_private)]
|
||||||
|
#![feature(custom_attribute)]
|
||||||
|
#![feature(dotdot_in_tuple_patterns)]
|
||||||
|
#![allow(unused_attributes)]
|
||||||
|
|
||||||
#[macro_use] extern crate syntax;
|
#[macro_use] extern crate syntax;
|
||||||
#[macro_use] extern crate log;
|
#[macro_use] extern crate log;
|
||||||
|
@ -10,54 +13,41 @@ extern crate rocket;
|
||||||
extern crate env_logger;
|
extern crate env_logger;
|
||||||
|
|
||||||
#[macro_use] mod utils;
|
#[macro_use] mod utils;
|
||||||
mod routes_macro;
|
mod parser;
|
||||||
mod errors_macro;
|
mod macros;
|
||||||
mod route_decorator;
|
mod decorators;
|
||||||
mod error_decorator;
|
|
||||||
mod derive_form;
|
|
||||||
mod meta_item_parser;
|
|
||||||
|
|
||||||
use rustc_plugin::Registry;
|
use rustc_plugin::Registry;
|
||||||
use syntax::ext::base::SyntaxExtension;
|
use syntax::ext::base::SyntaxExtension;
|
||||||
use syntax::parse::token::intern;
|
use syntax::parse::token::intern;
|
||||||
|
|
||||||
use routes_macro::routes_macro;
|
|
||||||
use errors_macro::errors_macro;
|
|
||||||
use route_decorator::*;
|
|
||||||
use error_decorator::error_decorator;
|
|
||||||
use derive_form::from_form_derive;
|
|
||||||
|
|
||||||
const ROUTE_STRUCT_PREFIX: &'static str = "static_rocket_route_info_for_";
|
const ROUTE_STRUCT_PREFIX: &'static str = "static_rocket_route_info_for_";
|
||||||
const CATCH_STRUCT_PREFIX: &'static str = "static_rocket_catch_info_for_";
|
const CATCH_STRUCT_PREFIX: &'static str = "static_rocket_catch_info_for_";
|
||||||
const ROUTE_FN_PREFIX: &'static str = "rocket_route_fn_";
|
const ROUTE_FN_PREFIX: &'static str = "rocket_route_fn_";
|
||||||
const CATCH_FN_PREFIX: &'static str = "rocket_catch_fn_";
|
const CATCH_FN_PREFIX: &'static str = "rocket_catch_fn_";
|
||||||
|
|
||||||
macro_rules! register_decorators {
|
macro_rules! register_decorators {
|
||||||
($registry:expr, $($name:expr => $func:expr),+) => (
|
($registry:expr, $($name:expr => $func:ident),+) => (
|
||||||
$($registry.register_syntax_extension(intern($name),
|
$($registry.register_syntax_extension(intern($name),
|
||||||
SyntaxExtension::MultiDecorator(Box::new($func)));
|
SyntaxExtension::MultiDecorator(Box::new(decorators::$func)));
|
||||||
)+
|
)+
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[plugin_registrar]
|
#[plugin_registrar]
|
||||||
pub fn plugin_registrar(reg: &mut Registry) {
|
pub fn plugin_registrar(reg: &mut Registry) {
|
||||||
reg.register_macro("routes", routes_macro);
|
reg.register_macro("routes", macros::routes);
|
||||||
reg.register_macro("errors", errors_macro);
|
reg.register_macro("errors", macros::errors);
|
||||||
|
|
||||||
register_decorators!(reg,
|
register_decorators!(reg,
|
||||||
"derive_FromForm" => from_form_derive,
|
// "derive_FromForm" => from_form_derive
|
||||||
"route" => generic_route_decorator,
|
|
||||||
"error" => error_decorator,
|
"error" => error_decorator,
|
||||||
|
|
||||||
"GET" => get_decorator,
|
"route" => route_decorator,
|
||||||
"PUT" => put_decorator,
|
"get" => get_decorator,
|
||||||
"POST" => post_decorator,
|
"put" => put_decorator,
|
||||||
"DELETE" => delete_decorator,
|
"post" => post_decorator,
|
||||||
"OPTIONS" => options_decorator,
|
"delete" => delete_decorator,
|
||||||
"HEAD" => head_decorator,
|
"patch" => patch_decorator
|
||||||
"TRACE" => trace_decorator,
|
|
||||||
"CONNECT" => connect_decorator,
|
|
||||||
"PATCH" => patch_decorator
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
use {ROUTE_STRUCT_PREFIX, CATCH_STRUCT_PREFIX};
|
||||||
|
use utils::{sep_by_tok, ParserExt, IdentExt};
|
||||||
|
|
||||||
|
use syntax::codemap::Span;
|
||||||
|
use syntax::tokenstream::TokenTree;
|
||||||
|
use syntax::ast::{Path, Expr};
|
||||||
|
use syntax::ext::base::{DummyResult, ExtCtxt, MacResult, MacEager};
|
||||||
|
use syntax::parse::token::Token;
|
||||||
|
use syntax::ptr::P;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn prefix_paths(prefix: &str, paths: &mut Vec<Path>) {
|
||||||
|
for p in paths {
|
||||||
|
let last = p.segments.len() - 1;
|
||||||
|
let last_seg = &mut p.segments[last];
|
||||||
|
last_seg.identifier = last_seg.identifier.prepend(prefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prefixing_vec_macro<F>(prefix: &str,
|
||||||
|
mut to_expr: F,
|
||||||
|
ecx: &mut ExtCtxt,
|
||||||
|
sp: Span,
|
||||||
|
args: &[TokenTree])
|
||||||
|
-> Box<MacResult + 'static>
|
||||||
|
where F: FnMut(&ExtCtxt, Path) -> P<Expr>
|
||||||
|
{
|
||||||
|
let mut parser = ecx.new_parser_from_tts(args);
|
||||||
|
let paths = parser.parse_paths();
|
||||||
|
if let Ok(mut paths) = paths {
|
||||||
|
// Prefix each path terminator and build up the P<Expr> for each path.
|
||||||
|
prefix_paths(prefix, &mut paths);
|
||||||
|
let path_exprs: Vec<P<Expr>> = paths.into_iter()
|
||||||
|
.map(|path| to_expr(ecx, path))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Now put them all in one vector and return the thing.
|
||||||
|
let path_list = sep_by_tok(ecx, &path_exprs, Token::Comma);
|
||||||
|
let output = quote_expr!(ecx, vec![$path_list]).unwrap();
|
||||||
|
MacEager::expr(P(output))
|
||||||
|
} else {
|
||||||
|
paths.unwrap_err().emit();
|
||||||
|
DummyResult::expr(sp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustfmt_skip]
|
||||||
|
pub fn routes(ecx: &mut ExtCtxt, sp: Span, args: &[TokenTree])
|
||||||
|
-> Box<MacResult + 'static> {
|
||||||
|
prefixing_vec_macro(ROUTE_STRUCT_PREFIX, |ecx, path| {
|
||||||
|
quote_expr!(ecx, ::rocket::Route::from(&$path))
|
||||||
|
}, ecx, sp, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustfmt_skip]
|
||||||
|
pub fn errors(ecx: &mut ExtCtxt, sp: Span, args: &[TokenTree])
|
||||||
|
-> Box<MacResult + 'static> {
|
||||||
|
prefixing_vec_macro(CATCH_STRUCT_PREFIX, |ecx, path| {
|
||||||
|
quote_expr!(ecx, rocket::Catcher::from(&$path))
|
||||||
|
}, ecx, sp, args)
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
use syntax::ast::*;
|
||||||
|
use syntax::ext::base::{ExtCtxt, Annotatable};
|
||||||
|
use syntax::codemap::{Span, Spanned, dummy_spanned};
|
||||||
|
|
||||||
|
use utils::{span, MetaItemExt};
|
||||||
|
use super::Function;
|
||||||
|
|
||||||
|
/// This structure represents the parsed `error` attribute.
|
||||||
|
pub struct ErrorParams {
|
||||||
|
pub annotated_fn: Function,
|
||||||
|
pub code: Spanned<u16>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErrorParams {
|
||||||
|
/// Parses the route attribute from the given decorator context. If the
|
||||||
|
/// parse is not successful, this function exits early with the appropriate
|
||||||
|
/// error message to the user.
|
||||||
|
pub fn from(ecx: &mut ExtCtxt,
|
||||||
|
sp: Span,
|
||||||
|
meta_item: &MetaItem,
|
||||||
|
annotated: &Annotatable)
|
||||||
|
-> ErrorParams {
|
||||||
|
let function = Function::from(annotated).unwrap_or_else(|item_sp| {
|
||||||
|
ecx.span_err(sp, "this attribute can only be used on functions...");
|
||||||
|
ecx.span_fatal(item_sp, "...but was applied to the item above.");
|
||||||
|
});
|
||||||
|
|
||||||
|
let meta_items = meta_item.meta_item_list().unwrap_or_else(|| {
|
||||||
|
ecx.struct_span_fatal(sp, "incorrect use of attribute")
|
||||||
|
.help("attributes in Rocket must have the form: #[name(...)]")
|
||||||
|
.emit();
|
||||||
|
unreachable!()
|
||||||
|
});
|
||||||
|
|
||||||
|
if meta_items.len() < 1 {
|
||||||
|
ecx.span_fatal(sp, "attribute requires the `code` parameter");
|
||||||
|
} else if meta_items.len() > 1 {
|
||||||
|
ecx.span_fatal(sp, "attribute can only have one `code` parameter");
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorParams {
|
||||||
|
annotated_fn: function,
|
||||||
|
code: parse_code(ecx, &meta_items[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_code(ecx: &ExtCtxt, meta_item: &NestedMetaItem) -> Spanned<u16> {
|
||||||
|
let code_from_u64 = |n: Spanned<u64>| {
|
||||||
|
if n.node < 400 || n.node > 599 {
|
||||||
|
ecx.span_err(n.span, "code must be >= 400 and <= 599.");
|
||||||
|
span(0, n.span)
|
||||||
|
} else {
|
||||||
|
span(n.node as u16, n.span)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let sp = meta_item.span();
|
||||||
|
if let Some((name, lit)) = meta_item.name_value() {
|
||||||
|
if name != "code" {
|
||||||
|
ecx.span_err(sp, "the first key, if any, must be 'code'");
|
||||||
|
} else if let LitKind::Int(n, _) = lit.node {
|
||||||
|
return code_from_u64(span(n, lit.span))
|
||||||
|
} else {
|
||||||
|
ecx.span_err(lit.span, "`code` value must be an integer")
|
||||||
|
}
|
||||||
|
} else if let Some(n) = meta_item.int_lit() {
|
||||||
|
return code_from_u64(span(n, sp))
|
||||||
|
} else {
|
||||||
|
ecx.struct_span_err(sp, r#"expected `code = int` or an integer literal"#)
|
||||||
|
.help(r#"you can specify the code directly as an integer,
|
||||||
|
e.g: #[error(404)], or as a key-value pair,
|
||||||
|
e.g: $[error(code = 404)]"#)
|
||||||
|
.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
dummy_spanned(0)
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
use syntax::ast::*;
|
||||||
|
use syntax::codemap::{Span, Spanned};
|
||||||
|
use syntax::ext::base::Annotatable;
|
||||||
|
use utils::{ArgExt, span};
|
||||||
|
|
||||||
|
pub struct Function(Spanned<(Ident, FnDecl)>);
|
||||||
|
|
||||||
|
impl Function {
|
||||||
|
pub fn from(annotated: &Annotatable) -> Result<Function, Span> {
|
||||||
|
let inner = match *annotated {
|
||||||
|
Annotatable::Item(ref item) => match item.node {
|
||||||
|
ItemKind::Fn(ref decl, ..) => {
|
||||||
|
span((item.ident, decl.clone().unwrap()), item.span)
|
||||||
|
}
|
||||||
|
_ => return Err(item.span)
|
||||||
|
},
|
||||||
|
Annotatable::TraitItem(ref item) => return Err(item.span),
|
||||||
|
Annotatable::ImplItem(ref item) => return Err(item.span),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Function(inner))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ident(&self) -> &Ident {
|
||||||
|
&self.0.node.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decl(&self) -> &FnDecl {
|
||||||
|
&self.0.node.1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_input<'a>(&'a self, name: &str) -> Option<&'a Arg> {
|
||||||
|
self.decl().inputs.iter().filter(|arg| arg.named(name)).next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
use syntax::codemap::{Spanned, Span, dummy_spanned, DUMMY_SP};
|
||||||
|
use syntax::ext::base::ExtCtxt;
|
||||||
|
use syntax::tokenstream::TokenTree;
|
||||||
|
use syntax::ext::quote::rt::ToTokens;
|
||||||
|
use utils::span;
|
||||||
|
|
||||||
|
/// A spanned key-value pair in an attribute.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct KVSpanned<V> {
|
||||||
|
/// Span for the full key/value pair.
|
||||||
|
pub span: Span,
|
||||||
|
/// The spanned key.
|
||||||
|
pub key: Spanned<String>,
|
||||||
|
/// The spanned value.
|
||||||
|
pub value: Spanned<V>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V> KVSpanned<V> {
|
||||||
|
/// Maps the inner value of this span using `f`. The key, value, and full
|
||||||
|
/// spans will remain the same in the new KVSpan.
|
||||||
|
pub fn map<U, F: FnOnce(V) -> U>(self, f: F) -> KVSpanned<U> {
|
||||||
|
KVSpanned {
|
||||||
|
span: self.span,
|
||||||
|
key: self.key,
|
||||||
|
value: span(f(self.value.node), self.value.span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maps a reference of the inner value of this span using `f`. The key,
|
||||||
|
/// value, and full spans will remain the same in the new KVSpan.
|
||||||
|
pub fn map_ref<U, F: FnOnce(&V) -> U>(&self, f: F) -> KVSpanned<U> {
|
||||||
|
KVSpanned {
|
||||||
|
span: self.span,
|
||||||
|
key: self.key.clone(),
|
||||||
|
value: span(f(&self.value.node), self.value.span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the unspanned key. Purely for convenience.
|
||||||
|
pub fn key(&self) -> &String {
|
||||||
|
&self.key.node
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the unspanned value. Purely for convenience.
|
||||||
|
pub fn value(&self) -> &V {
|
||||||
|
&self.value.node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Default> Default for KVSpanned<T> {
|
||||||
|
/// Returns a dummy KVSpan with an empty key for the default value of T.
|
||||||
|
fn default() -> KVSpanned<T> {
|
||||||
|
dummy_kvspanned("", T::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ToTokens> ToTokens for KVSpanned<T> {
|
||||||
|
fn to_tokens(&self, cx: &ExtCtxt) -> Vec<TokenTree> {
|
||||||
|
self.value().to_tokens(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a KVSpanned value with dummy (meaningless) spans.
|
||||||
|
pub fn dummy_kvspanned<T, S: ToString>(key: S, value: T) -> KVSpanned<T> {
|
||||||
|
KVSpanned {
|
||||||
|
span: DUMMY_SP,
|
||||||
|
key: dummy_spanned(key.to_string()),
|
||||||
|
value: dummy_spanned(value),
|
||||||
|
}
|
||||||
|
}
|
|
@ -131,8 +131,8 @@ pub trait RouteDecoratorExt {
|
||||||
|
|
||||||
impl<'a, 'c> RouteDecoratorExt for MetaItemParser<'a, 'c> {
|
impl<'a, 'c> RouteDecoratorExt for MetaItemParser<'a, 'c> {
|
||||||
fn bad_method(&self, sp: Span, message: &str) {
|
fn bad_method(&self, sp: Span, message: &str) {
|
||||||
let message = format!("{} Valid methods are: [GET, PUT, POST, DELETE, \
|
let message = format!("{} {}", message,
|
||||||
OPTIONS, HEAD, TRACE, CONNECT, PATCH]", message);
|
"Valid methods are: [GET, PUT, POST, DELETE, PATCH]");
|
||||||
self.ctxt.span_err(sp, message.as_str());
|
self.ctxt.span_err(sp, message.as_str());
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
mod keyvalue;
|
||||||
|
mod route;
|
||||||
|
mod error;
|
||||||
|
mod param;
|
||||||
|
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::function::Function;
|
|
@ -0,0 +1,58 @@
|
||||||
|
use syntax::ext::base::ExtCtxt;
|
||||||
|
use syntax::codemap::{Span, Spanned, BytePos};
|
||||||
|
|
||||||
|
use utils::span;
|
||||||
|
|
||||||
|
pub struct ParamIter<'s, 'a, 'c: 'a> {
|
||||||
|
ctxt: &'a ExtCtxt<'c>,
|
||||||
|
span: Span,
|
||||||
|
string: &'s str
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s, 'a, 'c: 'a> ParamIter<'s, 'a, 'c> {
|
||||||
|
pub fn new(c: &'a ExtCtxt<'c>, s: &'s str, p: Span) -> ParamIter<'s, 'a, 'c> {
|
||||||
|
ParamIter {
|
||||||
|
ctxt: c,
|
||||||
|
span: p,
|
||||||
|
string: s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s, 'a, 'c> Iterator for ParamIter<'s, 'a, 'c> {
|
||||||
|
type Item = Spanned<&'s str>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Spanned<&'s str>> {
|
||||||
|
// 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
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ensure we found a valid parameter.
|
||||||
|
if end <= start {
|
||||||
|
self.ctxt.span_err(self.span, "Parameter list is malformed.");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the parameter and the span for the parameter.
|
||||||
|
let param = &self.string[(start + 1)..end];
|
||||||
|
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.
|
||||||
|
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.");
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
self.string = &self.string[(end + 1)..];
|
||||||
|
self.span.lo = self.span.lo + BytePos((end + 1) as u32);
|
||||||
|
Some(span(param, param_span))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,280 @@
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use syntax::ast::*;
|
||||||
|
use syntax::ext::base::{ExtCtxt, Annotatable};
|
||||||
|
use syntax::codemap::{Span, Spanned, dummy_spanned};
|
||||||
|
|
||||||
|
use utils::{span, MetaItemExt, SpanExt, ArgExt};
|
||||||
|
use super::ParamIter;
|
||||||
|
use super::keyvalue::KVSpanned;
|
||||||
|
use rocket::{Method, ContentType};
|
||||||
|
|
||||||
|
pub struct Function(Spanned<(Ident, FnDecl)>);
|
||||||
|
|
||||||
|
impl Function {
|
||||||
|
fn from(annotated: &Annotatable) -> Result<Function, Span> {
|
||||||
|
let inner = match *annotated {
|
||||||
|
Annotatable::Item(ref item) => match item.node {
|
||||||
|
ItemKind::Fn(ref decl, ..) => {
|
||||||
|
span((item.ident, decl.clone().unwrap()), item.span)
|
||||||
|
}
|
||||||
|
_ => return Err(item.span)
|
||||||
|
},
|
||||||
|
Annotatable::TraitItem(ref item) => return Err(item.span),
|
||||||
|
Annotatable::ImplItem(ref item) => return Err(item.span),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Function(inner))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ident(&self) -> &Ident {
|
||||||
|
&self.0.node.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decl(&self) -> &FnDecl {
|
||||||
|
&self.0.node.1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_input<'a>(&'a self, name: &str) -> Option<&'a Arg> {
|
||||||
|
self.decl().inputs.iter().filter(|arg| arg.named(name)).next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This structure represents the parsed `route` attribute.
|
||||||
|
///
|
||||||
|
/// It contains all of the information supplied by the user and the span where
|
||||||
|
/// the user supplied the information. This structure can only be obtained by
|
||||||
|
/// calling the `RouteParams::from` function and passing in the entire decorator
|
||||||
|
/// environment.
|
||||||
|
pub struct RouteParams {
|
||||||
|
pub annotated_fn: Function,
|
||||||
|
pub method: Spanned<Method>,
|
||||||
|
pub path: Spanned<String>,
|
||||||
|
pub form_param: Option<KVSpanned<String>>,
|
||||||
|
pub accept: Option<KVSpanned<ContentType>>,
|
||||||
|
pub rank: Option<KVSpanned<isize>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RouteParams {
|
||||||
|
/// Parses the route attribute from the given decorator context. If the
|
||||||
|
/// parse is not successful, this function exits early with the appropriate
|
||||||
|
/// error message to the user.
|
||||||
|
pub fn from(ecx: &mut ExtCtxt,
|
||||||
|
sp: Span,
|
||||||
|
known_method: Option<Spanned<Method>>,
|
||||||
|
meta_item: &MetaItem,
|
||||||
|
annotated: &Annotatable)
|
||||||
|
-> RouteParams {
|
||||||
|
let function = Function::from(annotated).unwrap_or_else(|item_sp| {
|
||||||
|
ecx.span_err(sp, "this attribute can only be used on functions...");
|
||||||
|
ecx.span_fatal(item_sp, "...but was applied to the item above.");
|
||||||
|
});
|
||||||
|
|
||||||
|
let meta_items = meta_item.meta_item_list().unwrap_or_else(|| {
|
||||||
|
ecx.struct_span_fatal(sp, "incorrect use of attribute")
|
||||||
|
.help("attributes in Rocket must have the form: #[name(...)]")
|
||||||
|
.emit();
|
||||||
|
unreachable!()
|
||||||
|
});
|
||||||
|
|
||||||
|
if meta_items.len() < 1 {
|
||||||
|
ecx.span_fatal(sp, "attribute requires at least 1 parameter");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Figure out the method. If it is known (i.e, because we're parsing a
|
||||||
|
// helper attribute), use that method directly. Otherwise, try to parse
|
||||||
|
// it from the list of meta items.
|
||||||
|
let (method, attr_params) = match known_method {
|
||||||
|
Some(method) => (method, meta_items),
|
||||||
|
None => (parse_method(ecx, &meta_items[0]), &meta_items[1..])
|
||||||
|
};
|
||||||
|
|
||||||
|
if attr_params.len() < 1 {
|
||||||
|
ecx.struct_span_fatal(sp, "attribute requires at least a path")
|
||||||
|
.help(r#"example: #[get("/my/path")] or #[get(path = "/hi")]"#)
|
||||||
|
.emit();
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = parse_path(ecx, &attr_params[0]);
|
||||||
|
|
||||||
|
// Parse all of the optional parameters.
|
||||||
|
// TODO: Factor this out for use in Error.
|
||||||
|
let mut seen_keys = HashSet::new();
|
||||||
|
let (mut rank, mut form, mut accept) = Default::default();
|
||||||
|
for param in &attr_params[1..] {
|
||||||
|
let kv_opt = kv_from_nested(¶m);
|
||||||
|
if kv_opt.is_none() {
|
||||||
|
ecx.span_err(param.span(), "expected key = value");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let kv = kv_opt.unwrap();
|
||||||
|
match kv.key().as_str() {
|
||||||
|
"rank" => rank = parse_opt(ecx, &kv, parse_rank),
|
||||||
|
"form" => form = parse_opt(ecx, &kv, parse_form),
|
||||||
|
"accept" => accept = parse_opt(ecx, &kv, parse_accept),
|
||||||
|
_ => {
|
||||||
|
let msg = format!("{} is not a known parameter", kv.key());
|
||||||
|
ecx.span_err(kv.span, &msg);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if seen_keys.contains(kv.key()) {
|
||||||
|
let msg = format!("{} was already defined", kv.key());
|
||||||
|
ecx.struct_span_warn(param.span, &msg)
|
||||||
|
.note("the last declared value will be used")
|
||||||
|
.emit();
|
||||||
|
} else {
|
||||||
|
seen_keys.insert(kv.key().clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RouteParams {
|
||||||
|
method: method,
|
||||||
|
path: path,
|
||||||
|
form_param: form,
|
||||||
|
accept: accept,
|
||||||
|
rank: rank,
|
||||||
|
annotated_fn: function,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path_params<'s, 'a, 'c: 'a>(&'s self,
|
||||||
|
ecx: &'a ExtCtxt<'c>)
|
||||||
|
-> ParamIter<'s, 'a, 'c> {
|
||||||
|
ParamIter::new(ecx, self.path.node.as_str(), self.path.span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_method(method: Method) -> bool {
|
||||||
|
use rocket::Method::*;
|
||||||
|
match method {
|
||||||
|
Get | Put | Post | Delete | Patch => true,
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kv_from_nested(item: &NestedMetaItem) -> Option<KVSpanned<LitKind>> {
|
||||||
|
item.name_value().map(|(name, value)| {
|
||||||
|
let k_span = item.span().shorten_to(name.len() as u32);
|
||||||
|
KVSpanned {
|
||||||
|
key: span(name.to_string(), k_span),
|
||||||
|
value: value.clone(),
|
||||||
|
span: item.span(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_method(ecx: &ExtCtxt, meta_item: &NestedMetaItem) -> Spanned<Method> {
|
||||||
|
if let Some(word) = meta_item.word() {
|
||||||
|
if let Ok(method) = Method::from_str(&*word.name()) {
|
||||||
|
if is_valid_method(method) {
|
||||||
|
return span(method, word.span());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let msg = format!("{} is not a valid method.", word.name());
|
||||||
|
ecx.span_err(word.span(), &msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallthrough. Return default method.
|
||||||
|
ecx.struct_span_err(meta_item.span, "expected a valid HTTP method")
|
||||||
|
.help("valid methods are: GET, PUT, POST, DELETE, PATCH")
|
||||||
|
.emit();
|
||||||
|
|
||||||
|
return dummy_spanned(Method::Get);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_path(ecx: &ExtCtxt, meta_item: &NestedMetaItem) -> Spanned<String> {
|
||||||
|
let sp = meta_item.span();
|
||||||
|
if let Some((name, lit)) = meta_item.name_value() {
|
||||||
|
if name != "path" {
|
||||||
|
ecx.span_err(sp, "the first key, if any, must be 'path'");
|
||||||
|
} else if let LitKind::Str(ref s, _) = lit.node {
|
||||||
|
return span(s.to_string(), lit.span);
|
||||||
|
} else {
|
||||||
|
ecx.span_err(lit.span, "`path` value must be a string")
|
||||||
|
}
|
||||||
|
} else if let Some(s) = meta_item.str_lit() {
|
||||||
|
return span(s.to_string(), sp);
|
||||||
|
} else {
|
||||||
|
ecx.struct_span_err(sp, r#"expected `path = string` or a path string"#)
|
||||||
|
.help(r#"you can specify the path directly as a string, \
|
||||||
|
e.g: "/hello/world", or as a key-value pair, \
|
||||||
|
e.g: path = "/hello/world" "#)
|
||||||
|
.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
dummy_spanned("".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_opt<O, T, F>(ecx: &ExtCtxt, kv: &KVSpanned<T>, f: F) -> Option<KVSpanned<O>>
|
||||||
|
where F: Fn(&ExtCtxt, &KVSpanned<T>) -> O
|
||||||
|
{
|
||||||
|
Some(kv.map_ref(|_| f(ecx, kv)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_form(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> String {
|
||||||
|
if let LitKind::Str(ref s, _) = *kv.value() {
|
||||||
|
if s.starts_with('<') && s.ends_with('>') {
|
||||||
|
let form_param = s[1..(s.len() - 1)].to_string();
|
||||||
|
if form_param.chars().all(char::is_alphanumeric) {
|
||||||
|
return form_param;
|
||||||
|
}
|
||||||
|
|
||||||
|
ecx.span_err(kv.value.span, "parameter name must be alphanumeric");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ecx.struct_span_err(kv.span, r#"expected `form = "<name>"`"#)
|
||||||
|
.help(r#"form, if specified, must be a key-value pair where \
|
||||||
|
the key is `form` and the value is a string with a single \
|
||||||
|
parameter inside '<' '>'. e.g: form = "<login>""#)
|
||||||
|
.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
"".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_rank(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> isize {
|
||||||
|
if let LitKind::Int(n, _) = *kv.value() {
|
||||||
|
let max = isize::max_value();
|
||||||
|
if n <= max as u64 {
|
||||||
|
return n as isize;
|
||||||
|
} else {
|
||||||
|
let msg = format!("rank must be less than or equal to {}", max);
|
||||||
|
ecx.span_err(kv.value.span, msg.as_str());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ecx.struct_span_err(kv.span, r#"expected `rank = int`"#)
|
||||||
|
.help(r#"the rank, if specified, must be a key-value pair where
|
||||||
|
the key is `rank` and the value is an integer.
|
||||||
|
e.g: rank = 1, or e.g: rank = 10"#)
|
||||||
|
.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_accept(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> ContentType {
|
||||||
|
if let LitKind::Str(ref s, _) = *kv.value() {
|
||||||
|
if let Ok(ct) = ContentType::from_str(s) {
|
||||||
|
if ct.is_ext() {
|
||||||
|
let msg = format!("'{}' is not a known content-type", s);
|
||||||
|
ecx.span_warn(kv.value.span, &msg);
|
||||||
|
} else {
|
||||||
|
return ct;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ecx.struct_span_err(kv.span, r#"expected `accept = "content/type"`"#)
|
||||||
|
.help(r#"accept, if specified, must be a key-value pair where
|
||||||
|
the key is `accept` and the value is a string representing the
|
||||||
|
content-type accepted. e.g: accept = "application/json""#)
|
||||||
|
.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentType::any()
|
||||||
|
}
|
|
@ -1,314 +0,0 @@
|
||||||
use super::{ROUTE_STRUCT_PREFIX, ROUTE_FN_PREFIX};
|
|
||||||
use utils::*;
|
|
||||||
use meta_item_parser::{MetaItemParser, RouteDecoratorExt};
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use syntax::codemap::{Span, BytePos, /* DUMMY_SP, */ Spanned};
|
|
||||||
use syntax::ast::{Stmt, Expr, MetaItem, FnDecl};
|
|
||||||
use syntax::ext::base::{Annotatable, ExtCtxt};
|
|
||||||
use syntax::ptr::P;
|
|
||||||
use syntax::print::pprust::{item_to_string, stmt_to_string};
|
|
||||||
use syntax::parse::token::{self, str_to_ident};
|
|
||||||
|
|
||||||
use rocket::{Method, ContentType};
|
|
||||||
use rocket::content_type::{TopLevel, SubLevel};
|
|
||||||
|
|
||||||
pub fn extract_params_from_kv<'a>(parser: &MetaItemParser,
|
|
||||||
params: &'a KVSpanned<String>) -> Vec<Spanned<&'a str>> {
|
|
||||||
let mut param_span = params.v_span;
|
|
||||||
param_span.lo = params.v_span.lo + BytePos(1);
|
|
||||||
let spanned = span(&*params.node, param_span);
|
|
||||||
parser.iter_params(&spanned).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Analyzes the declared parameters against the function declaration. Returns
|
|
||||||
// a vector of all of the parameters in the order the user wants them.
|
|
||||||
fn get_fn_params<'a, T: Iterator<Item=&'a Spanned<&'a str>>>(ecx: &ExtCtxt,
|
|
||||||
declared_params: T, fn_decl: &Spanned<&FnDecl>)
|
|
||||||
-> Vec<UserParam> {
|
|
||||||
debug!("FUNCTION: {:?}", fn_decl);
|
|
||||||
|
|
||||||
// First, check that all of the parameters are unique.
|
|
||||||
let mut seen: HashMap<&str, &Spanned<&str>> = HashMap::new();
|
|
||||||
for item in declared_params {
|
|
||||||
if seen.contains_key(item.node) {
|
|
||||||
let msg = format!(
|
|
||||||
"\"{}\" was declared as a parameter more than once.", item.node);
|
|
||||||
ecx.span_err(item.span, msg.as_str());
|
|
||||||
} else {
|
|
||||||
seen.insert(item.node, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut user_params = vec![];
|
|
||||||
|
|
||||||
// Ensure every param in the function declaration was declared by the user.
|
|
||||||
for arg in &fn_decl.node.inputs {
|
|
||||||
let name = arg.pat.expect_ident(ecx, "Expected identifier.");
|
|
||||||
let arg = SimpleArg::new(name, arg.ty.clone(), arg.pat.span);
|
|
||||||
if seen.remove(&*name.to_string()).is_some() {
|
|
||||||
user_params.push(UserParam::new(arg, true));
|
|
||||||
} else {
|
|
||||||
user_params.push(UserParam::new(arg, false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emit an error on every attribute param that didn't match in fn params.
|
|
||||||
for item in seen.values() {
|
|
||||||
let msg = format!("'{}' was declared in the attribute...", item.node);
|
|
||||||
ecx.span_err(item.span, msg.as_str());
|
|
||||||
ecx.span_err(fn_decl.span, "...but does not appear in the function \
|
|
||||||
declaration.");
|
|
||||||
}
|
|
||||||
|
|
||||||
user_params
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_form_stmt(ecx: &ExtCtxt, fn_args: &mut Vec<UserParam>,
|
|
||||||
form_params: &[Spanned<&str>]) -> Option<Stmt> {
|
|
||||||
if form_params.len() < 1 {
|
|
||||||
return None;
|
|
||||||
} else if form_params.len() > 1 {
|
|
||||||
panic!("Allowed more than 1 form parameter!");
|
|
||||||
}
|
|
||||||
|
|
||||||
let param_name = &form_params[0].node;
|
|
||||||
let (param_ty, param_ident) = {
|
|
||||||
// Get the first item in the hashset, i.e., the form params variable name.
|
|
||||||
let fn_arg = fn_args.iter().filter(|a| &&*a.name == param_name).next();
|
|
||||||
if fn_arg.is_none() {
|
|
||||||
// This happens when a form parameter doesn't appear in the function.
|
|
||||||
// We should have already caught this, so just return None.
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
(fn_arg.unwrap().ty.clone(), str_to_ident(param_name))
|
|
||||||
};
|
|
||||||
|
|
||||||
// Remove the paramter from the function arguments.
|
|
||||||
debug!("Form parameter variable: {}: {:?}", param_name, param_ty);
|
|
||||||
let fn_arg_index = fn_args.iter().position(|a| &&*a.name == param_name).unwrap();
|
|
||||||
fn_args.remove(fn_arg_index);
|
|
||||||
|
|
||||||
// The actual code we'll be inserting.
|
|
||||||
quote_stmt!(ecx,
|
|
||||||
let $param_ident: $param_ty =
|
|
||||||
if let Ok(form_string) = ::std::str::from_utf8(_req.data.as_slice()) {
|
|
||||||
match ::rocket::form::FromForm::from_form_string(form_string) {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(_) => {
|
|
||||||
// TODO:
|
|
||||||
// debug!("\t=> Form failed to parse.");
|
|
||||||
return ::rocket::Response::not_found();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return ::rocket::Response::server_error();
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Is there a better way to do this? I need something with ToTokens for
|
|
||||||
// the quote_expr macro that builds the route struct. I tried using
|
|
||||||
// str_to_ident("::rocket::Method::Options"), but this seems to miss the context,
|
|
||||||
// and you get an 'ident not found' on compile. I also tried using the path expr
|
|
||||||
// builder from ASTBuilder: same thing.
|
|
||||||
fn method_variant_to_expr(ecx: &ExtCtxt, method: Method) -> P<Expr> {
|
|
||||||
match method {
|
|
||||||
Method::Options => quote_expr!(ecx, ::rocket::Method::Options),
|
|
||||||
Method::Get => quote_expr!(ecx, ::rocket::Method::Get),
|
|
||||||
Method::Post => quote_expr!(ecx, ::rocket::Method::Post),
|
|
||||||
Method::Put => quote_expr!(ecx, ::rocket::Method::Put),
|
|
||||||
Method::Delete => quote_expr!(ecx, ::rocket::Method::Delete),
|
|
||||||
Method::Head => quote_expr!(ecx, ::rocket::Method::Head),
|
|
||||||
Method::Trace => quote_expr!(ecx, ::rocket::Method::Trace),
|
|
||||||
Method::Connect => quote_expr!(ecx, ::rocket::Method::Connect),
|
|
||||||
Method::Patch => quote_expr!(ecx, ::rocket::Method::Patch),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Same here.
|
|
||||||
fn top_level_to_expr(ecx: &ExtCtxt, level: &TopLevel) -> P<Expr> {
|
|
||||||
use rocket::content_type::TopLevel::*;
|
|
||||||
match *level {
|
|
||||||
Star => quote_expr!(ecx, ::rocket::content_type::TopLevel::Star),
|
|
||||||
Text => quote_expr!(ecx, ::rocket::content_type::TopLevel::Text),
|
|
||||||
Image => quote_expr!(ecx, ::rocket::content_type::TopLevel::Image),
|
|
||||||
Audio => quote_expr!(ecx, ::rocket::content_type::TopLevel::Audio),
|
|
||||||
Video => quote_expr!(ecx, ::rocket::content_type::TopLevel::Video),
|
|
||||||
Application => quote_expr!(ecx, ::rocket::content_type::TopLevel::Application),
|
|
||||||
Multipart => quote_expr!(ecx, ::rocket::content_type::TopLevel::Multipart),
|
|
||||||
Message => quote_expr!(ecx, ::rocket::content_type::TopLevel::Message),
|
|
||||||
Model => quote_expr!(ecx, ::rocket::content_type::TopLevel::Model),
|
|
||||||
Ext(ref s) => quote_expr!(ecx, ::rocket::content_type::TopLevel::Ext($s)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Same here.
|
|
||||||
fn sub_level_to_expr(ecx: &ExtCtxt, level: &SubLevel) -> P<Expr> {
|
|
||||||
use rocket::content_type::SubLevel::*;
|
|
||||||
match *level {
|
|
||||||
Star => quote_expr!(ecx, ::rocket::content_type::SubLevel::Star),
|
|
||||||
Plain => quote_expr!(ecx, ::rocket::content_type::SubLevel::Plain),
|
|
||||||
Html => quote_expr!(ecx, ::rocket::content_type::SubLevel::Html),
|
|
||||||
Xml => quote_expr!(ecx, ::rocket::content_type::SubLevel::Xml),
|
|
||||||
Javascript => quote_expr!(ecx, ::rocket::content_type::SubLevel::Javascript),
|
|
||||||
Css => quote_expr!(ecx, ::rocket::content_type::SubLevel::Css),
|
|
||||||
EventStream => quote_expr!(ecx, ::rocket::content_type::SubLevel::EventStream),
|
|
||||||
Json => quote_expr!(ecx, ::rocket::content_type::SubLevel::Json),
|
|
||||||
WwwFormUrlEncoded =>
|
|
||||||
quote_expr!(ecx, ::rocket::content_type::SubLevel::WwwFormUrlEncoded),
|
|
||||||
Msgpack => quote_expr!(ecx, ::rocket::content_type::SubLevel::Msgpack),
|
|
||||||
OctetStream =>
|
|
||||||
quote_expr!(ecx, ::rocket::content_type::SubLevel::OctetStream),
|
|
||||||
FormData => quote_expr!(ecx, ::rocket::content_type::SubLevel::FormData),
|
|
||||||
Png => quote_expr!(ecx, ::rocket::content_type::SubLevel::Png),
|
|
||||||
Gif => quote_expr!(ecx, ::rocket::content_type::SubLevel::Gif),
|
|
||||||
Bmp => quote_expr!(ecx, ::rocket::content_type::SubLevel::Bmp),
|
|
||||||
Jpeg => quote_expr!(ecx, ::rocket::content_type::SubLevel::Jpeg),
|
|
||||||
Ext(ref s) => quote_expr!(ecx, ::rocket::content_type::SubLevel::Ext($s)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn content_type_to_expr(ecx: &ExtCtxt, content_type: &ContentType) -> P<Expr> {
|
|
||||||
let top_level = top_level_to_expr(ecx, &content_type.0);
|
|
||||||
let sub_level = sub_level_to_expr(ecx, &content_type.1);
|
|
||||||
quote_expr!(ecx, ::rocket::ContentType($top_level, $sub_level, None))
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: Compilation fails when parameters have the same name as the function!
|
|
||||||
pub fn route_decorator(known_method: Option<Spanned<Method>>, ecx: &mut ExtCtxt,
|
|
||||||
sp: Span, meta_item: &MetaItem, annotated: &Annotatable,
|
|
||||||
push: &mut FnMut(Annotatable)) {
|
|
||||||
::rocket::logger::init(::rocket::LoggingLevel::Debug);
|
|
||||||
|
|
||||||
// Get the encompassing item and function declaration for the annotated func.
|
|
||||||
let parser = MetaItemParser::new(ecx, meta_item, annotated, &sp);
|
|
||||||
let (item, fn_decl) = (parser.expect_item(), parser.expect_fn_decl());
|
|
||||||
|
|
||||||
// Parse and retrieve all of the parameters of the route.
|
|
||||||
let route = parser.parse_route(known_method);
|
|
||||||
|
|
||||||
// Get a list of the user declared parameters in `path` and `form`.
|
|
||||||
let path_params = extract_params_from_kv(&parser, &route.path);
|
|
||||||
let form_thing = route.form.unwrap_or_default(); // Default is empty string.
|
|
||||||
let form_params = extract_params_from_kv(&parser, &form_thing);
|
|
||||||
|
|
||||||
// Ensure the params match the function declaration and return the params.
|
|
||||||
let all_params = path_params.iter().chain(form_params.iter());
|
|
||||||
let mut user_params = get_fn_params(ecx, all_params, &fn_decl);
|
|
||||||
|
|
||||||
// Create a comma seperated list (token tree) of the function parameters
|
|
||||||
// We pass this in to the user's function that we're wrapping.
|
|
||||||
let fn_param_idents = token_separate(ecx, &user_params, token::Comma);
|
|
||||||
|
|
||||||
// Generate the statements that will attempt to parse forms during run-time.
|
|
||||||
// Calling this function also remove the form parameter from fn_params.
|
|
||||||
let form_stmt = get_form_stmt(ecx, &mut user_params, &form_params);
|
|
||||||
form_stmt.as_ref().map(|s| debug!("Form stmt: {:?}", stmt_to_string(s)));
|
|
||||||
|
|
||||||
// Generate the statements that will parse paramaters during run-time.
|
|
||||||
let mut fn_param_exprs = vec![];
|
|
||||||
|
|
||||||
// Push all of the declared parameters.
|
|
||||||
for (i, param) in user_params.iter().filter(|p| p.declared).enumerate() {
|
|
||||||
let (ident, ty) = (str_to_ident(param.as_str()), ¶m.ty);
|
|
||||||
let fn_item = quote_stmt!(ecx,
|
|
||||||
let $ident: $ty = match _req.get_param($i) {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(_) => return ::rocket::Response::forward()
|
|
||||||
};
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
debug!("Declared FN: {}", stmt_to_string(&fn_item));
|
|
||||||
fn_param_exprs.push(fn_item);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push all of the undeclared (FromRequest) parameters.
|
|
||||||
for param in user_params.iter().filter(|p| !p.declared) {
|
|
||||||
let (ident, ty) = (str_to_ident(param.as_str()), ¶m.ty);
|
|
||||||
let fn_item = quote_stmt!(ecx,
|
|
||||||
let $ident: $ty = match
|
|
||||||
<$ty as ::rocket::request::FromRequest>::from_request(&_req) {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(_e) => {
|
|
||||||
// TODO: Add $ident and $ty to the string.
|
|
||||||
// TODO: Add some kind of loggin facility in Rocket
|
|
||||||
// to get the formatting right (IE, so it idents
|
|
||||||
// correctly).
|
|
||||||
// debug!("Failed to parse: {:?}", e);
|
|
||||||
return ::rocket::Response::forward();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
debug!("Param FN: {}", stmt_to_string(&fn_item));
|
|
||||||
fn_param_exprs.push(fn_item);
|
|
||||||
}
|
|
||||||
|
|
||||||
let route_fn_name = prepend_ident(ROUTE_FN_PREFIX, &item.ident);
|
|
||||||
let fn_name = item.ident;
|
|
||||||
let route_fn_item = quote_item!(ecx,
|
|
||||||
fn $route_fn_name<'rocket>(_req: &'rocket ::rocket::Request<'rocket>)
|
|
||||||
-> ::rocket::Response<'rocket> {
|
|
||||||
$form_stmt
|
|
||||||
$fn_param_exprs
|
|
||||||
let result = $fn_name($fn_param_idents);
|
|
||||||
::rocket::Response::new(result)
|
|
||||||
}
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
debug!("{}", item_to_string(&route_fn_item));
|
|
||||||
push(Annotatable::Item(route_fn_item));
|
|
||||||
|
|
||||||
let struct_name = prepend_ident(ROUTE_STRUCT_PREFIX, &item.ident);
|
|
||||||
let path = &route.path.node;
|
|
||||||
let method = method_variant_to_expr(ecx, route.method.node);
|
|
||||||
let content_type = content_type_to_expr(ecx, &route.content_type.node);
|
|
||||||
let rank = option_as_expr(ecx, &route.rank);
|
|
||||||
|
|
||||||
let static_item = quote_item!(ecx,
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
pub static $struct_name: ::rocket::StaticRouteInfo =
|
|
||||||
::rocket::StaticRouteInfo {
|
|
||||||
method: $method,
|
|
||||||
path: $path,
|
|
||||||
handler: $route_fn_name,
|
|
||||||
content_type: $content_type,
|
|
||||||
rank: $rank,
|
|
||||||
};
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
debug!("Emitting static: {}", item_to_string(&static_item));
|
|
||||||
push(Annotatable::Item(static_item));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn generic_route_decorator(ecx: &mut ExtCtxt,
|
|
||||||
sp: Span, meta_item: &MetaItem, annotated: &Annotatable,
|
|
||||||
push: &mut FnMut(Annotatable)) {
|
|
||||||
route_decorator(None, ecx, sp, meta_item, annotated, push);
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! method_decorator {
|
|
||||||
($name:ident, $method:ident) => (
|
|
||||||
pub fn $name(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
|
|
||||||
annotated: &Annotatable, push: &mut FnMut(Annotatable)) {
|
|
||||||
let mut i_sp = meta_item.span;
|
|
||||||
i_sp.hi = i_sp.lo + BytePos(meta_item.name().len() as u32);
|
|
||||||
let method = Some(span(Method::$method, i_sp));
|
|
||||||
route_decorator(method, ecx, sp, meta_item, annotated, push);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
method_decorator!(get_decorator, Get);
|
|
||||||
method_decorator!(put_decorator, Put);
|
|
||||||
method_decorator!(post_decorator, Post);
|
|
||||||
method_decorator!(delete_decorator, Delete);
|
|
||||||
method_decorator!(options_decorator, Options);
|
|
||||||
method_decorator!(head_decorator, Head);
|
|
||||||
method_decorator!(trace_decorator, Trace);
|
|
||||||
method_decorator!(connect_decorator, Connect);
|
|
||||||
method_decorator!(patch_decorator, Patch);
|
|
|
@ -1,33 +0,0 @@
|
||||||
use super::{ROUTE_STRUCT_PREFIX};
|
|
||||||
use utils::*;
|
|
||||||
use syntax::codemap::Span;
|
|
||||||
use syntax::tokenstream::TokenTree;
|
|
||||||
use syntax::ast::Expr;
|
|
||||||
use syntax::ext::base::{ExtCtxt, MacResult, MacEager};
|
|
||||||
use syntax::parse::token::Token;
|
|
||||||
use syntax::ptr::P;
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
const DEBUG: bool = false;
|
|
||||||
|
|
||||||
pub fn routes_macro(ecx: &mut ExtCtxt, _sp: Span, args: &[TokenTree])
|
|
||||||
-> Box<MacResult + 'static> {
|
|
||||||
let mut parser = ecx.new_parser_from_tts(args);
|
|
||||||
let mut paths = parse_paths(&mut parser).unwrap_or_else(|mut e| {
|
|
||||||
e.emit();
|
|
||||||
vec![]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Prefix each path terminator.
|
|
||||||
prefix_paths(ROUTE_STRUCT_PREFIX, &mut paths);
|
|
||||||
|
|
||||||
// Build up the P<Expr> for each path.
|
|
||||||
let path_exprs: Vec<P<Expr>> = paths.iter().map(|p| {
|
|
||||||
quote_expr!(ecx, ::rocket::Route::from(&$p))
|
|
||||||
}).collect();
|
|
||||||
|
|
||||||
// Now put them all in one vector and return the thing.
|
|
||||||
let path_list = token_separate(ecx, &path_exprs, Token::Comma);
|
|
||||||
let output = quote_expr!(ecx, vec![$path_list]).unwrap();
|
|
||||||
MacEager::expr(P(output))
|
|
||||||
}
|
|
|
@ -1,287 +0,0 @@
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
use syntax::parse::{token};
|
|
||||||
use syntax::parse::token::Token;
|
|
||||||
use syntax::tokenstream::TokenTree;
|
|
||||||
use syntax::ast::{Path, Expr, Ident, MetaItem, MetaItemKind, LitKind, Ty, self};
|
|
||||||
use syntax::ext::base::{ExtCtxt};
|
|
||||||
use syntax::codemap::{Span, Spanned, BytePos, DUMMY_SP};
|
|
||||||
use syntax::ext::quote::rt::ToTokens;
|
|
||||||
use syntax::parse::PResult;
|
|
||||||
use syntax::parse::parser::{PathStyle, Parser};
|
|
||||||
use syntax::ptr::P;
|
|
||||||
|
|
||||||
use std::collections::{HashSet, HashMap};
|
|
||||||
|
|
||||||
pub fn prepend_ident<T: ToString>(other: T, ident: &Ident) -> Ident {
|
|
||||||
let mut new_ident = other.to_string();
|
|
||||||
new_ident.push_str(ident.name.to_string().as_str());
|
|
||||||
token::str_to_ident(new_ident.as_str())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn append_ident<T: ToString>(ident: &Ident, other: T) -> Ident {
|
|
||||||
let mut new_ident = ident.name.to_string();
|
|
||||||
new_ident.push_str(other.to_string().as_str());
|
|
||||||
token::str_to_ident(new_ident.as_str())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn span<T>(t: T, span: Span) -> Spanned<T> {
|
|
||||||
Spanned {
|
|
||||||
span: span,
|
|
||||||
node: t,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn dummy_span<T>(t: T) -> Spanned<T> {
|
|
||||||
Spanned {
|
|
||||||
span: DUMMY_SP,
|
|
||||||
node: t,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct KVSpanned<T> {
|
|
||||||
pub k_span: Span, // Span for the key.
|
|
||||||
pub v_span: Span, // Span for the value.
|
|
||||||
pub p_span: Span, // Span for the full parameter.
|
|
||||||
pub node: T // The value.
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> KVSpanned<T> {
|
|
||||||
#[inline]
|
|
||||||
pub fn dummy(t: T) -> KVSpanned<T> {
|
|
||||||
KVSpanned {
|
|
||||||
k_span: DUMMY_SP,
|
|
||||||
v_span: DUMMY_SP,
|
|
||||||
p_span: DUMMY_SP,
|
|
||||||
node: t,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Default> Default for KVSpanned<T> {
|
|
||||||
fn default() -> KVSpanned<T> {
|
|
||||||
KVSpanned::dummy(T::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ToTokens> ToTokens for KVSpanned<T> {
|
|
||||||
fn to_tokens(&self, cx: &ExtCtxt) -> Vec<TokenTree> {
|
|
||||||
self.node.to_tokens(cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> KVSpanned<T> {
|
|
||||||
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> KVSpanned<U> {
|
|
||||||
KVSpanned {
|
|
||||||
k_span: self.k_span,
|
|
||||||
v_span: self.v_span,
|
|
||||||
p_span: self.p_span,
|
|
||||||
node: f(self.node),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_key_values<'b>(ecx: &ExtCtxt, sp: Span, required: &[&str],
|
|
||||||
optional: &[&str], kv_params: &'b [P<MetaItem>])
|
|
||||||
-> HashMap<&'b str, KVSpanned<&'b str>> {
|
|
||||||
let mut seen = HashSet::new();
|
|
||||||
let mut kv_pairs = HashMap::new();
|
|
||||||
|
|
||||||
// Collect all the kv pairs, keeping track of what we've seen.
|
|
||||||
for param in kv_params {
|
|
||||||
if let MetaItemKind::NameValue(ref name, ref value) = param.node {
|
|
||||||
if required.contains(&&**name) || optional.contains(&&**name) {
|
|
||||||
if seen.contains(&**name) {
|
|
||||||
let msg = format!("'{}' parameter appears twice.", &**name);
|
|
||||||
ecx.span_err(param.span, &msg);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
seen.insert(&**name);
|
|
||||||
if let LitKind::Str(ref string, _) = value.node {
|
|
||||||
let mut k_span = param.span;
|
|
||||||
k_span.hi = k_span.lo + BytePos(name.len() as u32);
|
|
||||||
kv_pairs.insert(&**name, KVSpanned {
|
|
||||||
node: &**string,
|
|
||||||
k_span: k_span,
|
|
||||||
p_span: param.span,
|
|
||||||
v_span: value.span,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
ecx.span_err(value.span, "Value must be a string.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let msg = format!("'{}' is not a valid parameter.", &**name);
|
|
||||||
ecx.span_err(param.span, &msg);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ecx.span_err(param.span, "Expected 'key = value', found:");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now, trigger an error for missing `required` params.
|
|
||||||
for req in required {
|
|
||||||
if !seen.contains(req) {
|
|
||||||
let m = format!("'{}' parameter is required but is missing.", req);
|
|
||||||
ecx.span_err(sp, &m);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kv_pairs
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn token_separate<T: ToTokens>(ecx: &ExtCtxt, things: &[T],
|
|
||||||
token: Token) -> Vec<TokenTree> {
|
|
||||||
let mut output: Vec<TokenTree> = vec![];
|
|
||||||
for (i, thing) in things.iter().enumerate() {
|
|
||||||
output.extend(thing.to_tokens(ecx));
|
|
||||||
if i < things.len() - 1 {
|
|
||||||
output.push(TokenTree::Token(DUMMY_SP, token.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait MetaItemExt {
|
|
||||||
fn expect_list<'a>(&'a self, ecx: &ExtCtxt, msg: &str) -> &'a Vec<P<MetaItem>>;
|
|
||||||
fn expect_word<'a>(&'a self, ecx: &ExtCtxt, msg: &str) -> &'a str;
|
|
||||||
fn is_word(&self) -> bool;
|
|
||||||
fn name(&self) -> &str;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MetaItemExt for MetaItem {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
let interned_name = match self.node {
|
|
||||||
MetaItemKind::Word(ref s) | MetaItemKind::List(ref s, _)
|
|
||||||
| MetaItemKind::NameValue(ref s, _) => s,
|
|
||||||
};
|
|
||||||
|
|
||||||
&*interned_name
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expect_list<'a>(&'a self, ecx: &ExtCtxt, msg: &str) -> &'a Vec<P<MetaItem>> {
|
|
||||||
match self.node {
|
|
||||||
MetaItemKind::List(_, ref params) => params,
|
|
||||||
_ => ecx.span_fatal(self.span, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expect_word<'a>(&'a self, ecx: &ExtCtxt, msg: &str) -> &'a str {
|
|
||||||
match self.node {
|
|
||||||
MetaItemKind::Word(ref s) => &*s,
|
|
||||||
_ => ecx.span_fatal(self.span, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_word(&self) -> bool {
|
|
||||||
match self.node {
|
|
||||||
MetaItemKind::Word(_) => true,
|
|
||||||
_ => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait PatExt {
|
|
||||||
fn expect_ident<'a>(&'a self, ecx: &ExtCtxt, msg: &str) -> &'a Ident;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PatExt for ast::Pat {
|
|
||||||
fn expect_ident<'a>(&'a self, ecx: &ExtCtxt, msg: &str) -> &'a Ident {
|
|
||||||
match self.node {
|
|
||||||
ast::PatKind::Ident(_, ref ident, _) => &ident.node,
|
|
||||||
_ => {
|
|
||||||
ecx.span_fatal(self.span, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_paths<'a>(parser: &mut Parser<'a>) -> PResult<'a, Vec<Path>> {
|
|
||||||
if parser.eat(&Token::Eof) {
|
|
||||||
return Ok(vec![]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut results = Vec::new();
|
|
||||||
loop {
|
|
||||||
results.push(try!(parser.parse_path(PathStyle::Mod)));
|
|
||||||
if !parser.eat(&Token::Comma) {
|
|
||||||
try!(parser.expect(&Token::Eof));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(results)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prefix_paths(prefix: &str, paths: &mut Vec<Path>) {
|
|
||||||
for p in paths {
|
|
||||||
let last = p.segments.len() - 1;
|
|
||||||
let last_seg = &mut p.segments[last];
|
|
||||||
let new_ident = prepend_ident(prefix, &last_seg.identifier);
|
|
||||||
last_seg.identifier = new_ident;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn option_as_expr<T: ToTokens>(ecx: &ExtCtxt, opt: &Option<T>) -> P<Expr> {
|
|
||||||
match *opt {
|
|
||||||
Some(ref item) => quote_expr!(ecx, Some($item)),
|
|
||||||
None => quote_expr!(ecx, None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct SimpleArg {
|
|
||||||
pub name: String,
|
|
||||||
pub ty: P<Ty>,
|
|
||||||
pub span: Span
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SimpleArg {
|
|
||||||
pub fn new<T: ToString>(name: T, ty: P<Ty>, sp: Span) -> SimpleArg {
|
|
||||||
SimpleArg { name: name.to_string(), ty: ty, span: sp }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_str(&self) -> &str {
|
|
||||||
self.name.as_str()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToTokens for SimpleArg {
|
|
||||||
fn to_tokens(&self, cx: &ExtCtxt) -> Vec<TokenTree> {
|
|
||||||
token::str_to_ident(self.as_str()).to_tokens(cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct UserParam {
|
|
||||||
pub arg: SimpleArg,
|
|
||||||
pub declared: bool
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UserParam {
|
|
||||||
pub fn new(arg: SimpleArg, declared: bool) -> UserParam {
|
|
||||||
UserParam {
|
|
||||||
arg: arg,
|
|
||||||
declared: declared
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for UserParam {
|
|
||||||
type Target = SimpleArg;
|
|
||||||
|
|
||||||
fn deref(&self) -> &SimpleArg {
|
|
||||||
&self.arg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToTokens for UserParam {
|
|
||||||
fn to_tokens(&self, cx: &ExtCtxt) -> Vec<TokenTree> {
|
|
||||||
self.arg.to_tokens(cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
use syntax::ast::{Arg, PatKind, Ident};
|
||||||
|
|
||||||
|
pub trait ArgExt {
|
||||||
|
fn ident(&self) -> Option<&Ident>;
|
||||||
|
|
||||||
|
fn name(&self) -> Option<String> {
|
||||||
|
self.ident().map(|ident| {
|
||||||
|
ident.name.to_string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn named(&self, name: &str) -> bool {
|
||||||
|
self.name().map_or(false, |a| a == name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArgExt for Arg {
|
||||||
|
fn ident(&self) -> Option<&Ident> {
|
||||||
|
match self.pat.node {
|
||||||
|
PatKind::Ident(_, ref ident, _) => Some(&ident.node),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
use std::fmt::Display;
|
||||||
|
use syntax::parse::token::str_to_ident;
|
||||||
|
use syntax::ast::Ident;
|
||||||
|
|
||||||
|
pub trait IdentExt {
|
||||||
|
fn prepend<T: Display>(&self, other: T) -> Ident;
|
||||||
|
fn append<T: Display>(&self, other: T) -> Ident;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IdentExt for Ident {
|
||||||
|
fn prepend<T: Display>(&self, other: T) -> Ident {
|
||||||
|
let new_ident = format!("{}{}", other, self.name);
|
||||||
|
str_to_ident(new_ident.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append<T: Display>(&self, other: T) -> Ident {
|
||||||
|
let new_ident = format!("{}{}", self.name, other);
|
||||||
|
str_to_ident(new_ident.as_str())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
use syntax::ast::{LitKind, NestedMetaItem, MetaItemKind, Lit};
|
||||||
|
use syntax::parse::token::InternedString;
|
||||||
|
|
||||||
|
pub trait MetaItemExt {
|
||||||
|
fn name_value(&self) -> Option<(&InternedString, &Lit)>;
|
||||||
|
fn str_lit(&self) -> Option<&InternedString>;
|
||||||
|
fn int_lit(&self) -> Option<u64>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MetaItemExt for NestedMetaItem {
|
||||||
|
fn name_value(&self) -> Option<(&InternedString, &Lit)> {
|
||||||
|
self.meta_item().and_then(|mi| match mi.node {
|
||||||
|
MetaItemKind::NameValue(ref s, ref l) => Some((s, l)),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn str_lit(&self) -> Option<&InternedString> {
|
||||||
|
self.literal().and_then(|lit| match lit.node {
|
||||||
|
LitKind::Str(ref s, _) => Some(s),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn int_lit(&self) -> Option<u64> {
|
||||||
|
self.literal().and_then(|lit| match lit.node {
|
||||||
|
LitKind::Int(n, _) => Some(n),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
mod meta_item_ext;
|
||||||
|
mod arg_ext;
|
||||||
|
mod parser_ext;
|
||||||
|
mod ident_ext;
|
||||||
|
mod span_ext;
|
||||||
|
|
||||||
|
pub use self::arg_ext::ArgExt;
|
||||||
|
pub use self::meta_item_ext::MetaItemExt;
|
||||||
|
pub use self::parser_ext::ParserExt;
|
||||||
|
pub use self::ident_ext::IdentExt;
|
||||||
|
pub use self::span_ext::SpanExt;
|
||||||
|
|
||||||
|
use syntax::parse::token::Token;
|
||||||
|
use syntax::tokenstream::TokenTree;
|
||||||
|
use syntax::ast::{Item, Expr};
|
||||||
|
use syntax::ext::base::{Annotatable, ExtCtxt};
|
||||||
|
use syntax::codemap::{spanned, Span, Spanned, DUMMY_SP};
|
||||||
|
use syntax::ext::quote::rt::ToTokens;
|
||||||
|
use syntax::print::pprust::item_to_string;
|
||||||
|
use syntax::ptr::P;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn span<T>(t: T, span: Span) -> Spanned<T> {
|
||||||
|
spanned(span.lo, span.hi, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn sep_by_tok<T>(ecx: &ExtCtxt, things: &[T], token: Token) -> Vec<TokenTree>
|
||||||
|
where T: ToTokens
|
||||||
|
{
|
||||||
|
let mut output: Vec<TokenTree> = vec![];
|
||||||
|
for (i, thing) in things.iter().enumerate() {
|
||||||
|
output.extend(thing.to_tokens(ecx));
|
||||||
|
if i < things.len() - 1 {
|
||||||
|
output.push(TokenTree::Token(DUMMY_SP, token.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn option_as_expr<T: ToTokens>(ecx: &ExtCtxt, opt: &Option<T>) -> P<Expr> {
|
||||||
|
match *opt {
|
||||||
|
Some(ref item) => quote_expr!(ecx, Some($item)),
|
||||||
|
None => quote_expr!(ecx, None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn emit_item(push: &mut FnMut(Annotatable), item: P<Item>) {
|
||||||
|
debug!("Emitting item: {}", item_to_string(&item));
|
||||||
|
push(Annotatable::Item(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! quote_enum {
|
||||||
|
($ecx:expr, $var:expr => $(::$root:ident)+
|
||||||
|
{ $($variant:ident),+ ; $($extra:pat => $result:expr),* }) => ({
|
||||||
|
use syntax::codemap::DUMMY_SP;
|
||||||
|
use $(::$root)+::*;
|
||||||
|
let root_idents = vec![$(str_to_ident(stringify!($root))),+];
|
||||||
|
match $var {
|
||||||
|
$($variant => {
|
||||||
|
let variant = str_to_ident(stringify!($variant));
|
||||||
|
let mut idents = root_idents.clone();
|
||||||
|
idents.push(variant);
|
||||||
|
$ecx.path_global(DUMMY_SP, idents)
|
||||||
|
})+
|
||||||
|
$($extra => $result)*
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
use syntax::parse::parser::{PathStyle, Parser};
|
||||||
|
use syntax::parse::PResult;
|
||||||
|
use syntax::ast::Path;
|
||||||
|
use syntax::parse::token::Token::{Eof, Comma};
|
||||||
|
use syntax::parse::common::SeqSep;
|
||||||
|
|
||||||
|
pub trait ParserExt<'a> {
|
||||||
|
fn parse_paths(&mut self) -> PResult<'a, Vec<Path>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ParserExt<'a> for Parser<'a> {
|
||||||
|
fn parse_paths(&mut self) -> PResult<'a, Vec<Path>> {
|
||||||
|
self.parse_seq_to_end(&Eof,
|
||||||
|
SeqSep::trailing_allowed(Comma),
|
||||||
|
|p| p.parse_path(PathStyle::Mod))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
use syntax::ast::{Pat, PatKind, Ident};
|
||||||
|
use syntax::parse::token;
|
||||||
|
use syntax::codemap::DUMMY_SP;
|
||||||
|
use syntax::tokenstream::TokenTree;
|
||||||
|
use syntax::ext::quote::rt::ToTokens;
|
||||||
|
use syntax::ext::base::ExtCtxt;
|
||||||
|
use syntax::ptr::P;
|
||||||
|
|
||||||
|
pub trait PatExt {
|
||||||
|
fn named(&self, name: &str) -> bool;
|
||||||
|
fn ident(&self) -> Option<&Ident>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PatExt for Pat {
|
||||||
|
fn named(&self, name: &str) -> bool {
|
||||||
|
match self.node {
|
||||||
|
PatKind::Ident(_, ref ident, _) => ident.node.name.as_str() == name,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ident(&self) -> Option<&Ident> {
|
||||||
|
match self.node {
|
||||||
|
PatKind::Ident(_, ref ident, _) => Some(&ident.node),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
use syntax::codemap::{Span, BytePos};
|
||||||
|
|
||||||
|
pub trait SpanExt {
|
||||||
|
fn shorten_to(self, to_length: u32) -> Span;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpanExt for Span {
|
||||||
|
fn shorten_to(mut self, to_length: u32) -> Span {
|
||||||
|
self.hi = self.lo + BytePos(to_length);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
enum_trailing_comma = false
|
||||||
|
max_width = 85
|
||||||
|
fn_call_width = 80
|
Loading…
Reference in New Issue