use 'quote' in router macro

get rid of a lot of unreadable TokenTree entries

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2019-06-08 10:52:11 +02:00
parent 7155689724
commit f3e2e672be
2 changed files with 47 additions and 65 deletions

View File

@ -108,12 +108,12 @@ impl From<Ident> for Name {
} }
} }
pub fn need_hyphenated_name(tokens: &mut TokenIter) -> Result<Name, Error> { pub fn need_hyphenated_name(tokens: &mut TokenIter) -> Result<syn::LitStr, Error> {
let start = need_ident(&mut *tokens)?; let start = need_ident(&mut *tokens)?;
finish_hyphenated_name(&mut *tokens, start) finish_hyphenated_name(&mut *tokens, start)
} }
pub fn finish_hyphenated_name(tokens: &mut TokenIter, name: Ident) -> Result<Name, Error> { pub fn finish_hyphenated_name(tokens: &mut TokenIter, name: Ident) -> Result<syn::LitStr, Error> {
let span = name.span(); let span = name.span();
let mut name = name.to_string(); let mut name = name.to_string();
@ -137,7 +137,7 @@ pub fn finish_hyphenated_name(tokens: &mut TokenIter, name: Ident) -> Result<Nam
} }
} }
Ok(Name(name, span)) Ok(syn::LitStr::new(&name, span))
} }
// parse an object notation: // parse an object notation:

View File

@ -1,9 +1,10 @@
use std::collections::HashMap; use std::collections::HashMap;
use proc_macro2::{Delimiter, Ident, TokenStream, TokenTree}; use proc_macro2::{Delimiter, Ident, Span, TokenStream, TokenTree};
use failure::{bail, Error}; use failure::{bail, Error};
use quote::quote; use quote::quote;
use syn::LitStr;
use super::parsing::*; use super::parsing::*;
@ -38,8 +39,8 @@ pub fn router_macro(input: TokenStream) -> Result<TokenStream, Error> {
/// This can either be a fixed set of directories, or a parameter name, in which case it matches /// This can either be a fixed set of directories, or a parameter name, in which case it matches
/// all directory names into the parameter of the specified name. /// all directory names into the parameter of the specified name.
pub enum SubRoute { pub enum SubRoute {
Directories(HashMap<String, Router>), Directories(HashMap<LitStr, Router>),
Parameter(String, Box<Router>), Parameter(LitStr, Box<Router>),
} }
impl SubRoute { impl SubRoute {
@ -49,7 +50,7 @@ impl SubRoute {
} }
/// Create a parameter entry with an empty default router. /// Create a parameter entry with an empty default router.
fn parameter(name: String) -> Self { fn parameter(name: LitStr) -> Self {
SubRoute::Parameter(name, Box::new(Router::default())) SubRoute::Parameter(name, Box::new(Router::default()))
} }
} }
@ -78,9 +79,9 @@ enum Entry {
/// The components making up a path. /// The components making up a path.
enum Component { enum Component {
/// This component is a fixed sub directory name. Eg. `foo` or `baz` in `/foo/{bar}/baz`. /// This component is a fixed sub directory name. Eg. `foo` or `baz` in `/foo/{bar}/baz`.
Name(String), Name(LitStr),
/// This component matches everything into a parameter. Eg. `bar` in `/foo/{bar}/baz`. /// This component matches everything into a parameter. Eg. `bar` in `/foo/{bar}/baz`.
Match(String), Match(LitStr),
} }
/// A path is just a list of components. /// A path is just a list of components.
@ -106,7 +107,7 @@ impl Router {
}); });
} }
SubRoute::Parameter(_param, _router) => { SubRoute::Parameter(_param, _router) => {
bail!("subdirectory '{}' clashes with parameter matcher", name); bail!("subdir '{}' clashes with matched parameter", name.value());
} }
} }
} }
@ -119,15 +120,15 @@ impl Router {
SubRoute::Directories(_) => { SubRoute::Directories(_) => {
bail!( bail!(
"parameter matcher '{}' clashes with existing directory", "parameter matcher '{}' clashes with existing directory",
name name.value()
); );
} }
SubRoute::Parameter(existing_name, router) => { SubRoute::Parameter(existing_name, router) => {
if name != *existing_name { if name != *existing_name {
bail!( bail!(
"paramter matcher '{}' clashes with existing name '{}'", "paramter matcher '{}' clashes with existing name '{}'",
name, name.value(),
existing_name, existing_name.value(),
); );
} }
at = router.as_mut(); at = router.as_mut();
@ -147,23 +148,17 @@ impl Router {
fn into_token_stream(self, name: Option<Ident>) -> TokenStream { fn into_token_stream(self, name: Option<Ident>) -> TokenStream {
use std::iter::FromIterator; use std::iter::FromIterator;
use proc_macro2::{Group, Literal, Punct, Spacing, Span}; use proc_macro2::{Group, Literal, Punct, Spacing};
let mut out = vec![ let mut out = quote! {
TokenTree::Ident(Ident::new("Router", Span::call_site())), ::proxmox::api::Router::new()
TokenTree::Punct(Punct::new(':', Spacing::Joint)), };
TokenTree::Punct(Punct::new(':', Spacing::Alone)),
TokenTree::Ident(Ident::new("new", Span::call_site())),
TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::new())),
];
fn add_method(out: &mut Vec<TokenTree>, name: &str, func_name: Ident) { fn add_method(out: &mut TokenStream, name: &'static str, func_name: Ident) {
out.push(TokenTree::Punct(Punct::new('.', Spacing::Alone))); let name = Ident::new(name, func_name.span());
out.push(TokenTree::Ident(Ident::new(name, Span::call_site()))); out.extend(quote! {
out.push(TokenTree::Group(Group::new( .#name(#func_name)
Delimiter::Parenthesis, });
TokenStream::from_iter(vec![TokenTree::Ident(func_name)]),
)));
} }
if let Some(method) = self.get { if let Some(method) = self.get {
@ -182,34 +177,17 @@ impl Router {
match self.subroute { match self.subroute {
None => (), None => (),
Some(SubRoute::Parameter(name, router)) => { Some(SubRoute::Parameter(name, router)) => {
out.push(TokenTree::Punct(Punct::new('.', Spacing::Alone))); let router = router.into_token_stream(None);
out.push(TokenTree::Ident(Ident::new( out.extend(quote! {
"parameter_subdir", .parameter_subdir(#name, #router)
Span::call_site(), });
)));
let mut sub_route = TokenStream::from_iter(vec![
TokenTree::Literal(Literal::string(&name)),
TokenTree::Punct(Punct::new(',', Spacing::Alone)),
]);
sub_route.extend(router.into_token_stream(None));
out.push(TokenTree::Group(Group::new(
Delimiter::Parenthesis,
sub_route,
)));
} }
Some(SubRoute::Directories(hash)) => { Some(SubRoute::Directories(hash)) => {
for (name, router) in hash { for (name, router) in hash {
out.push(TokenTree::Punct(Punct::new('.', Spacing::Alone))); let router = router.into_token_stream(None);
out.push(TokenTree::Ident(Ident::new("subdir", Span::call_site()))); out.extend(quote! {
let mut sub_route = TokenStream::from_iter(vec![ .subdir(#name, #router)
TokenTree::Literal(Literal::string(&name)), });
TokenTree::Punct(Punct::new(',', Spacing::Alone)),
]);
sub_route.extend(router.into_token_stream(None));
out.push(TokenTree::Group(Group::new(
Delimiter::Parenthesis,
sub_route,
)));
} }
} }
} }
@ -295,6 +273,15 @@ fn parse_entry_key(tokens: &mut TokenIter) -> Result<Option<Entry>, Error> {
fn parse_path_name(tokens: &mut TokenIter) -> Result<Path, Error> { fn parse_path_name(tokens: &mut TokenIter) -> Result<Path, Error> {
let mut path = Path::new(); let mut path = Path::new();
let mut component = String::new(); let mut component = String::new();
let mut span = None;
fn push_component(path: &mut Path, component: &mut String, span: &mut Option<Span>) {
if !component.is_empty() {
path.push(Component::Name(LitStr::new(&component, span.take().unwrap())));
component.clear();
}
};
loop { loop {
match tokens.next() { match tokens.next() {
None => bail!("expected path component"), None => bail!("expected path component"),
@ -303,11 +290,8 @@ fn parse_path_name(tokens: &mut TokenIter) -> Result<Path, Error> {
bail!("invalid path component: {:?}", group); bail!("invalid path component: {:?}", group);
} }
let name = need_hyphenated_name(&mut group.stream().into_iter().peekable())?; let name = need_hyphenated_name(&mut group.stream().into_iter().peekable())?;
if !component.is_empty() { push_component(&mut path, &mut component, &mut span);
path.push(Component::Name(component)); path.push(Component::Match(name));
component = String::new();
}
path.push(Component::Match(name.into_string()));
// Now: // Now:
// `component` is empty // `component` is empty
@ -324,6 +308,9 @@ fn parse_path_name(tokens: &mut TokenIter) -> Result<Path, Error> {
} }
Some(TokenTree::Ident(ident)) => { Some(TokenTree::Ident(ident)) => {
component.push_str(&ident.to_string()); component.push_str(&ident.to_string());
if span.is_none() {
span = Some(ident.span());
}
// Now: // Now:
// `component` is partially or fully filled // `component` is partially or fully filled
@ -348,10 +335,7 @@ fn parse_path_name(tokens: &mut TokenIter) -> Result<Path, Error> {
// `component` is partially filled, we need more // `component` is partially filled, we need more
} }
'/' => { '/' => {
if !component.is_empty() { push_component(&mut path, &mut component, &mut span);
path.push(Component::Name(component));
component = String::new();
}
// `component` is cleared, we start the next one // `component` is cleared, we start the next one
} }
other => bail!("invalid punctuation in path: {:?}", other), other => bail!("invalid punctuation in path: {:?}", other),
@ -363,9 +347,7 @@ fn parse_path_name(tokens: &mut TokenIter) -> Result<Path, Error> {
} }
} }
if !component.is_empty() { push_component(&mut path, &mut component, &mut span);
path.push(Component::Name(component));
}
Ok(path) Ok(path)
} }