use std::collections::HashSet; use proc_macro2::*; use quote::*; use syn::fold::Fold; use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::token::{Add, Comma, Where}; use super::parse::*; use super::*; fn ctx_trait(ctx_ty: Type, opt: &Options) -> TokenStream { let mut bounds: Punctuated = Punctuated::new(); bounds.push(macro_try!(parse2(quote! { Into<#ctx_ty> }))); bounds.push(macro_try!(parse2(quote! { ::rpc_toolkit::Context }))); if let Options::Parent(ParentOptions { subcommands, .. }) = opt { bounds.push(macro_try!(parse2(quote! { Clone }))); for subcmd in subcommands { let mut path = subcmd.clone(); std::mem::take(&mut path.segments.last_mut().unwrap().arguments); bounds.push(macro_try!(parse2(quote! { #path::CommandContext }))); } } quote! { pub trait CommandContext: #bounds {} impl CommandContext for T where T: #bounds {} } } fn metadata(full_options: &Options) -> TokenStream { let options = match full_options { Options::Leaf(a) => a, Options::Parent(ParentOptions { common, .. }) => common, }; let fallthrough = |ty: &str| { let getter_name = Ident::new(&format!("get_{}", ty), Span::call_site()); match &*full_options { Options::Parent(ParentOptions { subcommands, .. }) => { let subcmd_handler = subcommands.iter().map(|subcmd| { let mut subcmd = subcmd.clone(); subcmd.segments.last_mut().unwrap().arguments = PathArguments::None; quote_spanned!{ subcmd.span() => [#subcmd::NAME, rest] => if let Some(val) = #subcmd::Metadata.#getter_name(rest, key) { return Some(val); }, } }); quote! { if !command.is_empty() { match command.splitn(2, ".").chain(std::iter::repeat("")).take(2).collect::>().as_slice() { #( #subcmd_handler )* _ => () } } } } _ => quote! {}, } }; fn impl_getter>( ty: &str, metadata: I, fallthrough: TokenStream, ) -> TokenStream { let getter_name = Ident::new(&format!("get_{}", ty), Span::call_site()); let ty: Type = syn::parse_str(ty).unwrap(); quote! { fn #getter_name(&self, command: &str, key: &str) -> Option<#ty> { #fallthrough match key { #(#metadata)* _ => None, } } } } let bool_metadata = options .metadata .iter() .filter(|(_, lit)| matches!(lit, Lit::Bool(_))) .map(|(name, value)| { let name = LitStr::new(&name.to_string(), name.span()); quote! { #name => Some(#value), } }); let number_metadata = |ty: &str| { let ty: Type = syn::parse_str(ty).unwrap(); options .metadata .iter() .filter(|(_, lit)| matches!(lit, Lit::Int(_) | Lit::Float(_) | Lit::Byte(_))) .map(move |(name, value)| { let name = LitStr::new(&name.to_string(), name.span()); quote! { #name => Some(#value as #ty), } }) }; let char_metadata = options .metadata .iter() .filter(|(_, lit)| matches!(lit, Lit::Char(_))) .map(|(name, value)| { let name = LitStr::new(&name.to_string(), name.span()); quote! { #name => Some(#value), } }); let str_metadata = options .metadata .iter() .filter(|(_, lit)| matches!(lit, Lit::Str(_))) .map(|(name, value)| { let name = LitStr::new(&name.to_string(), name.span()); quote! { #name => Some(#value), } }); let bstr_metadata = options .metadata .iter() .filter(|(_, lit)| matches!(lit, Lit::ByteStr(_))) .map(|(name, value)| { let name = LitStr::new(&name.to_string(), name.span()); quote! { #name => Some(#value), } }); let bool_getter = impl_getter("bool", bool_metadata, fallthrough("bool")); let u8_getter = impl_getter("u8", number_metadata("u8"), fallthrough("u8")); let u16_getter = impl_getter("u16", number_metadata("u16"), fallthrough("u16")); let u32_getter = impl_getter("u32", number_metadata("u32"), fallthrough("u32")); let u64_getter = impl_getter("u64", number_metadata("u64"), fallthrough("u64")); let usize_getter = impl_getter("usize", number_metadata("usize"), fallthrough("usize")); let i8_getter = impl_getter("i8", number_metadata("i8"), fallthrough("i8")); let i16_getter = impl_getter("i16", number_metadata("i16"), fallthrough("i16")); let i32_getter = impl_getter("i32", number_metadata("i32"), fallthrough("i32")); let i64_getter = impl_getter("i64", number_metadata("i64"), fallthrough("i64")); let isize_getter = impl_getter("isize", number_metadata("isize"), fallthrough("isize")); let f32_getter = impl_getter("f32", number_metadata("f32"), fallthrough("f32")); let f64_getter = impl_getter("f64", number_metadata("f64"), fallthrough("f64")); let char_getter = impl_getter("char", char_metadata, fallthrough("char")); let str_fallthrough = fallthrough("str"); let str_getter = quote! { fn get_str(&self, command: &str, key: &str) -> Option<&'static str> { #str_fallthrough match key { #(#str_metadata)* _ => None, } } }; let bstr_fallthrough = fallthrough("bstr"); let bstr_getter = quote! { fn get_bstr(&self, command: &str, key: &str) -> Option<&'static [u8]> { #bstr_fallthrough match key { #(#bstr_metadata)* _ => None, } } }; let res = quote! { #[derive(Clone, Copy, Default)] pub struct Metadata; #[allow(overflowing_literals)] impl ::rpc_toolkit::Metadata for Metadata { #bool_getter #u8_getter #u16_getter #u32_getter #u64_getter #usize_getter #i8_getter #i16_getter #i32_getter #i64_getter #isize_getter #f32_getter #f64_getter #char_getter #str_getter #bstr_getter } }; // panic!("{}", res); res } fn build_app(name: LitStr, opt: &mut Options, params: &mut [ParamType]) -> TokenStream { let about = opt.common().about.clone().into_iter(); let (subcommand, subcommand_required) = if let Options::Parent(opt) = opt { ( opt.subcommands .iter() .map(|subcmd| { let mut path = subcmd.clone(); path.segments.last_mut().unwrap().arguments = PathArguments::None; path }) .collect(), opt.self_impl.is_none(), ) } else { (Vec::new(), false) }; let arg = params .iter_mut() .filter_map(|param| { if let ParamType::Arg(arg) = param { if arg.stdin.is_some() { return None; } let name = arg.name.clone().unwrap(); let name_str = arg.rename.clone().unwrap_or_else(|| LitStr::new(&name.to_string(), name.span())); let help = arg.help.clone().into_iter(); let short = arg.short.clone().into_iter(); let long = arg.long.clone().into_iter(); let mut modifications = TokenStream::default(); let ty_span = arg.ty.span(); if let Type::Path(p) = &mut arg.ty { if p.path.is_ident("bool") && arg.parse.is_none() && (arg.short.is_some() || arg.long.is_some()) { arg.check_is_present = true; modifications.extend(quote_spanned! { ty_span => arg = arg.takes_value(false); }); } else if arg.count.is_some() { modifications.extend(quote_spanned! { ty_span => arg = arg.takes_value(false); arg = arg.multiple(true); }); } else { modifications.extend(quote_spanned! { ty_span => arg = arg.takes_value(true); }); if let Some(default) = &arg.default { modifications.extend(quote_spanned! { ty_span => arg = arg.default_value(#default); }); } else if p.path.segments.last().unwrap().ident == "Option" { arg.optional = true; modifications.extend(quote_spanned! { ty_span => arg = arg.required(false); }); } else if arg.multiple.is_some() { modifications.extend(quote_spanned! { ty_span => arg = arg.multiple(true); }); } else { modifications.extend(quote_spanned! { ty_span => arg = arg.required(true); }); } } }; Some(quote! { { let mut arg = ::rpc_toolkit::command_helpers::prelude::Arg::with_name(#name_str); #( arg = arg.help(#help); )* #( arg = arg.short(#short); )* #( arg = arg.long(#long); )* #modifications arg } }) } else { None } }) .collect::>(); let required = LitBool::new(subcommand_required, Span::call_site()); let alias = &opt.common().aliases; quote! { pub fn build_app() -> ::rpc_toolkit::command_helpers::prelude::App<'static, 'static> { let mut app = ::rpc_toolkit::command_helpers::prelude::App::new(#name); #( app = app.about(#about); )* #( app = app.alias(#alias); )* #( app = app.arg(#arg); )* #( app = app.subcommand(#subcommand::build_app()); )* if #required { app = app.setting(::rpc_toolkit::command_helpers::prelude::AppSettings::SubcommandRequired); } app } } } struct GenericFilter<'a> { src: &'a Generics, lifetimes: HashSet, types: HashSet, } impl<'a> GenericFilter<'a> { fn new(src: &'a Generics) -> Self { GenericFilter { src, lifetimes: HashSet::new(), types: HashSet::new(), } } fn finish(self) -> Generics { let mut params: Punctuated = Default::default(); let mut where_clause = self .src .where_clause .as_ref() .map(|wc| WhereClause { where_token: wc.where_token, predicates: Default::default(), }) .unwrap_or_else(|| WhereClause { where_token: Where(Span::call_site()), predicates: Default::default(), }); for src_param in &self.src.params { match src_param { GenericParam::Lifetime(l) if self.lifetimes.contains(&l.lifetime) => { params.push(src_param.clone()) } GenericParam::Type(t) if self.types.contains(&t.ident) => { params.push(src_param.clone()) } _ => (), } } for src_predicate in self.src.where_clause.iter().flat_map(|wc| &wc.predicates) { match src_predicate { WherePredicate::Lifetime(l) if self.lifetimes.contains(&l.lifetime) => { where_clause.predicates.push(src_predicate.clone()) } WherePredicate::Type(PredicateType { bounded_ty: Type::Path(t), .. }) if self.types.contains(&t.path.segments.last().unwrap().ident) => { where_clause.predicates.push(src_predicate.clone()) } _ => (), } } Generics { lt_token: if params.is_empty() { None } else { self.src.lt_token.clone() }, gt_token: if params.is_empty() { None } else { self.src.gt_token.clone() }, params, where_clause: if where_clause.predicates.is_empty() { None } else { Some(where_clause) }, } } } impl<'a> Fold for GenericFilter<'a> { fn fold_lifetime(&mut self, i: Lifetime) -> Lifetime { self.lifetimes .extend(self.src.params.iter().filter_map(|param| match param { GenericParam::Lifetime(l) if l.lifetime == i => Some(l.lifetime.clone()), _ => None, })); i } fn fold_type(&mut self, i: Type) -> Type { self.types.extend( self.src .params .iter() .filter_map(|param| match (param, &i) { (GenericParam::Type(t), Type::Path(i)) if i.path.is_ident(&t.ident) => { Some(t.ident.clone()) } _ => None, }), ); i } } fn rpc_handler( fn_name: &Ident, fn_generics: &Generics, opt: &Options, params: &[ParamType], ) -> TokenStream { let mut parent_data_ty = quote! { () }; let mut generics = fn_generics.clone(); generics.params.push(macro_try!(syn::parse2( quote! { GenericContext: CommandContext } ))); if generics.lt_token.is_none() { generics.lt_token = Some(Default::default()); } if generics.gt_token.is_none() { generics.gt_token = Some(Default::default()); } let mut param_def = Vec::new(); for param in params { match param { ParamType::Arg(arg) => { let name = arg.name.clone().unwrap(); let rename = arg .rename .clone() .unwrap_or_else(|| LitStr::new(&name.to_string(), name.span())); let field_name = Ident::new(&format!("arg_{}", name), name.span()); let ty = arg.ty.clone(); param_def.push(quote! { #[serde(rename = #rename)] #field_name: #ty, }) } ParamType::ParentData(ty) => parent_data_ty = quote! { #ty }, _ => (), } } let (_, fn_type_generics, _) = fn_generics.split_for_impl(); let fn_turbofish = fn_type_generics.as_turbofish(); let fn_path: Path = macro_try!(syn::parse2(quote! { super::#fn_name#fn_turbofish })); let mut param_generics_filter = GenericFilter::new(fn_generics); for param in params { if let ParamType::Arg(a) = param { param_generics_filter.fold_type(a.ty.clone()); } } let param_generics = param_generics_filter.finish(); let (_, param_ty_generics, _) = param_generics.split_for_impl(); let param_struct_def = quote! { #[allow(dead_code)] #[derive(::rpc_toolkit::command_helpers::prelude::Deserialize)] pub struct Params#param_ty_generics { #( #param_def )* #[serde(flatten)] #[serde(default)] rest: ::rpc_toolkit::command_helpers::prelude::Value, } }; let param = params.iter().map(|param| match param { ParamType::Arg(arg) => { let name = arg.name.clone().unwrap(); let field_name = Ident::new(&format!("arg_{}", name), name.span()); quote! { args.#field_name } } ParamType::Context(ty) => { if matches!(opt, Options::Parent { .. }) { quote! { >::into(ctx.clone()) } } else { quote! { >::into(ctx) } } } ParamType::ParentData(_) => { quote! { parent_data } } ParamType::Request => quote! { request }, ParamType::Response => quote! { response }, ParamType::None => unreachable!(), }); match opt { Options::Leaf(opt) if matches!(opt.exec_ctx, ExecutionContext::CliOnly(_)) => quote! { #param_struct_def pub async fn rpc_handler#generics( _ctx: GenericContext, _parent_data: #parent_data_ty, _request: &::rpc_toolkit::command_helpers::prelude::RequestParts, _response: &mut ::rpc_toolkit::command_helpers::prelude::ResponseParts, method: &str, _args: Params#param_ty_generics, ) -> Result<::rpc_toolkit::command_helpers::prelude::Value, ::rpc_toolkit::command_helpers::prelude::RpcError> { Err(::rpc_toolkit::command_helpers::prelude::RpcError { data: Some(method.into()), ..::rpc_toolkit::command_helpers::prelude::yajrc::METHOD_NOT_FOUND_ERROR }) } }, Options::Leaf(opt) => { let invocation = if opt.is_async { quote! { #fn_path(#(#param),*).await? } } else if opt.blocking.is_some() { quote! { ::rpc_toolkit::command_helpers::prelude::spawn_blocking(move || #fn_path(#(#param),*)).await? } } else { quote! { #fn_path(#(#param),*)? } }; quote! { #param_struct_def pub async fn rpc_handler#generics( ctx: GenericContext, parent_data: #parent_data_ty, request: &::rpc_toolkit::command_helpers::prelude::RequestParts, response: &mut ::rpc_toolkit::command_helpers::prelude::ResponseParts, method: &str, args: Params#param_ty_generics, ) -> Result<::rpc_toolkit::command_helpers::prelude::Value, ::rpc_toolkit::command_helpers::prelude::RpcError> { if method.is_empty() { Ok(::rpc_toolkit::command_helpers::prelude::to_value(#invocation)?) } else { Err(::rpc_toolkit::command_helpers::prelude::RpcError { data: Some(method.into()), ..::rpc_toolkit::command_helpers::prelude::yajrc::METHOD_NOT_FOUND_ERROR }) } } } } Options::Parent(ParentOptions { common, subcommands, self_impl, }) => { let cmd_preprocess = if common.is_async { quote! { let parent_data = #fn_path(#(#param),*).await?; } } else if common.blocking.is_some() { quote! { let parent_data = ::rpc_toolkit::command_helpers::prelude::spawn_blocking(move || #fn_path(#(#param),*)).await?; } } else { quote! { let parent_data = #fn_path(#(#param),*)?; } }; let subcmd_impl = subcommands.iter().map(|subcommand| { let mut subcommand = subcommand.clone(); let mut rpc_handler = PathSegment { ident: Ident::new("rpc_handler", Span::call_site()), arguments: std::mem::replace( &mut subcommand.segments.last_mut().unwrap().arguments, PathArguments::None, ), }; rpc_handler.arguments = match rpc_handler.arguments { PathArguments::None => PathArguments::AngleBracketed( syn::parse2(quote! { :: }) .unwrap(), ), PathArguments::AngleBracketed(mut a) => { a.args.push(syn::parse2(quote! { GenericContext }).unwrap()); PathArguments::AngleBracketed(a) } _ => unreachable!(), }; quote_spanned!{ subcommand.span() => [#subcommand::NAME, rest] => #subcommand::#rpc_handler(ctx, parent_data, request, response, rest, ::rpc_toolkit::command_helpers::prelude::from_value(args.rest)?).await } }); let subcmd_impl = quote! { match method.splitn(2, ".").chain(std::iter::repeat("")).take(2).collect::>().as_slice() { #( #subcmd_impl, )* _ => Err(::rpc_toolkit::command_helpers::prelude::RpcError { data: Some(method.into()), ..::rpc_toolkit::command_helpers::prelude::yajrc::METHOD_NOT_FOUND_ERROR }) } }; match self_impl { Some(self_impl) if !matches!(common.exec_ctx, ExecutionContext::CliOnly(_)) => { let self_impl_fn = &self_impl.path; let self_impl = if self_impl.is_async { quote_spanned! { self_impl_fn.span() => #self_impl_fn(Into::into(ctx), parent_data).await? } } else if self_impl.blocking { quote_spanned! { self_impl_fn.span() => { let ctx = Into::into(ctx); ::rpc_toolkit::command_helpers::prelude::spawn_blocking(move || #self_impl_fn(ctx, parent_data)).await? } } } else { quote_spanned! { self_impl_fn.span() => #self_impl_fn(Into::into(ctx), parent_data)? } }; quote! { #param_struct_def pub async fn rpc_handler#generics( ctx: GenericContext, parent_data: #parent_data_ty, request: &::rpc_toolkit::command_helpers::prelude::RequestParts, response: &mut ::rpc_toolkit::command_helpers::prelude::ResponseParts, method: &str, args: Params#param_ty_generics, ) -> Result<::rpc_toolkit::command_helpers::prelude::Value, ::rpc_toolkit::command_helpers::prelude::RpcError> { #cmd_preprocess if method.is_empty() { Ok(::rpc_toolkit::command_helpers::prelude::to_value(&#self_impl)?) } else { #subcmd_impl } } } } _ => { quote! { #param_struct_def pub async fn rpc_handler#generics( ctx: GenericContext, parent_data: #parent_data_ty, request: &::rpc_toolkit::command_helpers::prelude::RequestParts, response: &mut ::rpc_toolkit::command_helpers::prelude::ResponseParts, method: &str, args: Params#param_ty_generics, ) -> Result<::rpc_toolkit::command_helpers::prelude::Value, ::rpc_toolkit::command_helpers::prelude::RpcError> { #cmd_preprocess #subcmd_impl } } } } } } } fn cli_handler( fn_name: &Ident, fn_generics: &Generics, opt: &mut Options, params: &[ParamType], ) -> TokenStream { let mut parent_data_ty = quote! { () }; let mut generics = fn_generics.clone(); generics.params.push(macro_try!(syn::parse2( quote! { ParentParams: ::rpc_toolkit::command_helpers::prelude::Serialize } ))); generics.params.push(macro_try!(syn::parse2( quote! { GenericContext: CommandContext } ))); if generics.lt_token.is_none() { generics.lt_token = Some(Default::default()); } if generics.gt_token.is_none() { generics.gt_token = Some(Default::default()); } let (_, fn_type_generics, _) = fn_generics.split_for_impl(); let fn_turbofish = fn_type_generics.as_turbofish(); let fn_path: Path = macro_try!(syn::parse2(quote! { super::#fn_name#fn_turbofish })); let is_parent = matches!(opt, Options::Parent { .. }); let param = params.iter().map(|param| match param { ParamType::Arg(arg) => { let name = arg.name.clone().unwrap(); let field_name = Ident::new(&format!("arg_{}", name), name.span()); quote! { params.#field_name.clone() } } ParamType::Context(ty) => { if is_parent { quote! { >::into(ctx.clone()) } } else { quote! { >::into(ctx) } } } ParamType::ParentData(ty) => { parent_data_ty = quote! { #ty }; quote! { parent_data } } ParamType::Request => quote! { request }, ParamType::Response => quote! { response }, ParamType::None => unreachable!(), }); let mut param_generics_filter = GenericFilter::new(fn_generics); for param in params { if let ParamType::Arg(a) = param { param_generics_filter.fold_type(a.ty.clone()); } } let mut param_generics = param_generics_filter.finish(); param_generics.params.push(macro_try!(syn::parse2(quote! { ParentParams: ::rpc_toolkit::command_helpers::prelude::Serialize }))); if param_generics.lt_token.is_none() { param_generics.lt_token = Some(Default::default()); } if param_generics.gt_token.is_none() { param_generics.gt_token = Some(Default::default()); } let (_, param_ty_generics, _) = param_generics.split_for_impl(); let mut arg_def = Vec::new(); for param in params { match param { ParamType::Arg(arg) => { let name = arg.name.clone().unwrap(); let rename = arg .rename .clone() .unwrap_or_else(|| LitStr::new(&name.to_string(), name.span())); let field_name = Ident::new(&format!("arg_{}", name), name.span()); let ty = arg.ty.clone(); arg_def.push(quote! { #[serde(rename = #rename)] #field_name: #ty, }) } _ => (), } } let arg = params .iter() .filter_map(|param| { if let ParamType::Arg(a) = param { Some(a) } else { None } }) .map(|arg| { let name = arg.name.clone().unwrap(); let arg_name = arg.rename.clone().unwrap_or_else(|| LitStr::new(&name.to_string(), name.span())); let field_name = Ident::new(&format!("arg_{}", name), name.span()); if arg.stdin.is_some() { if let Some(parse) = &arg.parse { quote! { #field_name: #parse(&mut std::io::stdin(), matches)?, } } else { quote! { #field_name: ::rpc_toolkit::command_helpers::prelude::default_stdin_parser(&mut std::io::stdin(), matches)?, } } } else if arg.check_is_present { quote! { #field_name: matches.is_present(#arg_name), } } else if arg.count.is_some() { quote! { #field_name: matches.occurrences_of(#arg_name), } } else { let parse_val = if let Some(parse) = &arg.parse { quote! { #parse(arg_val, matches) } } else { quote! { ::rpc_toolkit::command_helpers::prelude::default_arg_parser(arg_val, matches) } }; if arg.optional { quote! { #field_name: if let Some(arg_val) = matches.value_of(#arg_name) { Some(#parse_val?) } else { None }, } } else if arg.multiple.is_some() { quote! { #field_name: matches.values_of(#arg_name).iter().flatten().map(|arg_val| #parse_val).collect::>()?, } } else { quote! { #field_name: { let arg_val = matches.value_of(#arg_name).unwrap(); #parse_val? }, } } } }); let param_struct_def = quote! { #[derive(::rpc_toolkit::command_helpers::prelude::Serialize)] struct Params#param_ty_generics { #( #arg_def )* #[serde(flatten)] rest: ParentParams, } let params: Params#param_ty_generics = Params { #( #arg )* rest: parent_params, }; }; let create_rt = quote! { let rt_ref = if let Some(rt) = rt.as_mut() { &*rt } else { rt = Some(::rpc_toolkit::command_helpers::prelude::Runtime::new().map_err(|e| ::rpc_toolkit::command_helpers::prelude::RpcError { data: Some(format!("{}", e).into()), ..::rpc_toolkit::command_helpers::prelude::yajrc::INTERNAL_ERROR })?); rt.as_ref().unwrap() }; }; let display = if let Some(display) = &opt.common().display { quote! { #display } } else { quote! { ::rpc_toolkit::command_helpers::prelude::default_display } }; match opt { Options::Leaf(opt) if matches!(opt.exec_ctx, ExecutionContext::RpcOnly(_)) => quote! { pub fn cli_handler#generics( _ctx: (), _parent_data: #parent_data_ty, _rt: Option<::rpc_toolkit::command_helpers::prelude::Runtime>, _matches: &::rpc_toolkit::command_helpers::prelude::ArgMatches<'_>, method: ::rpc_toolkit::command_helpers::prelude::Cow<'_, str>, _parent_params: ParentParams, ) -> Result<(), ::rpc_toolkit::command_helpers::prelude::RpcError> { Err(::rpc_toolkit::command_helpers::prelude::RpcError { data: Some(method.into()), ..::rpc_toolkit::command_helpers::prelude::yajrc::METHOD_NOT_FOUND_ERROR }) } }, Options::Leaf(opt) if matches!(opt.exec_ctx, ExecutionContext::Standard) => { let param = param.map(|_| quote! { unreachable!() }); let invocation = if opt.is_async { quote! { rt_ref.block_on(#fn_path(#(#param),*))? } } else { quote! { #fn_path(#(#param),*)? } }; quote! { pub fn cli_handler#generics( ctx: GenericContext, parent_data: #parent_data_ty, mut rt: Option<::rpc_toolkit::command_helpers::prelude::Runtime>, matches: &::rpc_toolkit::command_helpers::prelude::ArgMatches<'_>, method: ::rpc_toolkit::command_helpers::prelude::Cow<'_, str>, parent_params: ParentParams, ) -> Result<(), ::rpc_toolkit::command_helpers::prelude::RpcError> { #param_struct_def #create_rt #[allow(unreachable_code)] let return_ty = if true { ::rpc_toolkit::command_helpers::prelude::PhantomData } else { let ctx_new = unreachable!(); ::rpc_toolkit::command_helpers::prelude::match_types(&ctx, &ctx_new); let ctx = ctx_new; ::rpc_toolkit::command_helpers::prelude::make_phantom(#invocation) }; let res = rt_ref.block_on(::rpc_toolkit::command_helpers::prelude::call_remote(ctx, method.as_ref(), params, return_ty))?; Ok(#display(res.result?, matches)) } } } Options::Leaf(opt) => { if let ExecutionContext::CustomCli { ref cli, is_async, .. } = opt.exec_ctx { let fn_path = cli; let cli_param = params.iter().filter_map(|param| match param { ParamType::Arg(arg) => { let name = arg.name.clone().unwrap(); let field_name = Ident::new(&format!("arg_{}", name), name.span()); Some(quote! { params.#field_name.clone() }) } ParamType::Context(_) => Some(quote! { Into::into(ctx) }), ParamType::ParentData(_) => Some(quote! { parent_data }), ParamType::Request => None, ParamType::Response => None, ParamType::None => unreachable!(), }); let invocation = if is_async { quote! { rt_ref.block_on(#fn_path(#(#cli_param),*))? } } else { quote! { #fn_path(#(#cli_param),*)? } }; let display_res = if let Some(display_fn) = &opt.display { quote! { #display_fn(#invocation, matches) } } else { quote! { ::rpc_toolkit::command_helpers::prelude::default_display(#invocation, matches) } }; let rt_action = if is_async { create_rt } else { quote! { drop(rt); } }; quote! { pub fn cli_handler#generics( ctx: GenericContext, parent_data: #parent_data_ty, mut rt: Option<::rpc_toolkit::command_helpers::prelude::Runtime>, matches: &::rpc_toolkit::command_helpers::prelude::ArgMatches<'_>, _method: ::rpc_toolkit::command_helpers::prelude::Cow<'_, str>, parent_params: ParentParams ) -> Result<(), ::rpc_toolkit::command_helpers::prelude::RpcError> { #param_struct_def #rt_action Ok(#display_res) } } } else { let invocation = if opt.is_async { quote! { rt_ref.block_on(#fn_path(#(#param),*))? } } else { quote! { #fn_path(#(#param),*)? } }; let display_res = if let Some(display_fn) = &opt.display { quote! { #display_fn(#invocation, matches) } } else { quote! { ::rpc_toolkit::command_helpers::prelude::default_display(#invocation, matches) } }; let rt_action = if opt.is_async { create_rt } else { quote! { drop(rt); } }; quote! { pub fn cli_handler#generics( ctx: GenericContext, parent_data: #parent_data_ty, mut rt: Option<::rpc_toolkit::command_helpers::prelude::Runtime>, matches: &::rpc_toolkit::command_helpers::prelude::ArgMatches<'_>, _method: ::rpc_toolkit::command_helpers::prelude::Cow<'_, str>, parent_params: ParentParams ) -> Result<(), ::rpc_toolkit::command_helpers::prelude::RpcError> { #param_struct_def #rt_action Ok(#display_res) } } } } Options::Parent(ParentOptions { common, subcommands, self_impl, }) => { let cmd_preprocess = if common.is_async { quote! { #create_rt let parent_data = rt_ref.block_on(#fn_path(#(#param),*))?; } } else { quote! { let parent_data = #fn_path(#(#param),*)?; } }; let subcmd_impl = subcommands.iter().map(|subcommand| { let mut subcommand = subcommand.clone(); let mut cli_handler = PathSegment { ident: Ident::new("cli_handler", Span::call_site()), arguments: std::mem::replace( &mut subcommand.segments.last_mut().unwrap().arguments, PathArguments::None, ), }; cli_handler.arguments = match cli_handler.arguments { PathArguments::None => PathArguments::AngleBracketed( syn::parse2(quote! { :: }) .unwrap(), ), PathArguments::AngleBracketed(mut a) => { a.args .push(syn::parse2(quote! { Params#param_ty_generics }).unwrap()); a.args.push(syn::parse2(quote! { GenericContext }).unwrap()); PathArguments::AngleBracketed(a) } _ => unreachable!(), }; quote_spanned! { subcommand.span() => (#subcommand::NAME, Some(sub_m)) => { let method = if method.is_empty() { #subcommand::NAME.into() } else { method + "." + #subcommand::NAME }; #subcommand::#cli_handler(ctx, parent_data, rt, sub_m, method, params) }, } }); let self_impl = match (self_impl, &common.exec_ctx) { (Some(self_impl), ExecutionContext::CliOnly(_)) | (Some(self_impl), ExecutionContext::Local(_)) | (Some(self_impl), ExecutionContext::CustomCli { .. }) => { let (self_impl_fn, is_async) = if let ExecutionContext::CustomCli { cli, is_async, .. } = &common.exec_ctx { (cli, *is_async) } else { (&self_impl.path, self_impl.is_async) }; let create_rt = if common.is_async { None } else { Some(create_rt) }; let self_impl = if is_async { quote_spanned! { self_impl_fn.span() => #create_rt rt_ref.block_on(#self_impl_fn(Into::into(ctx), parent_data))? } } else { quote_spanned! { self_impl_fn.span() => #self_impl_fn(Into::into(ctx), parent_data)? } }; quote! { Ok(#display(#self_impl, matches)), } } (Some(self_impl), ExecutionContext::Standard) => { let self_impl_fn = &self_impl.path; let self_impl = if self_impl.is_async { quote! { rt_ref.block_on(#self_impl_fn(Into::into(ctx), parent_data)) } } else { quote! { #self_impl_fn(Into::into(ctx), parent_data) } }; let create_rt = if common.is_async { None } else { Some(create_rt) }; quote! { { #create_rt #[allow(unreachable_code)] let return_ty = if true { ::rpc_toolkit::command_helpers::prelude::PhantomData } else { let ctx_new = unreachable!(); ::rpc_toolkit::command_helpers::prelude::match_types(&ctx, &ctx_new); let ctx = ctx_new; ::rpc_toolkit::command_helpers::prelude::make_phantom(#self_impl?) }; let res = rt_ref.block_on(::rpc_toolkit::command_helpers::prelude::call_remote(ctx, method.as_ref(), params, return_ty))?; Ok(#display(res.result?, matches)) } } } (None, _) | (Some(_), ExecutionContext::RpcOnly(_)) => quote! { Err(::rpc_toolkit::command_helpers::prelude::RpcError { data: Some(method.into()), ..::rpc_toolkit::command_helpers::prelude::yajrc::METHOD_NOT_FOUND_ERROR }), }, }; quote! { pub fn cli_handler#generics( ctx: GenericContext, parent_data: #parent_data_ty, mut rt: Option<::rpc_toolkit::command_helpers::prelude::Runtime>, matches: &::rpc_toolkit::command_helpers::prelude::ArgMatches<'_>, method: ::rpc_toolkit::command_helpers::prelude::Cow<'_, str>, parent_params: ParentParams, ) -> Result<(), ::rpc_toolkit::command_helpers::prelude::RpcError> { #param_struct_def #cmd_preprocess match matches.subcommand() { #( #subcmd_impl )* _ => #self_impl } } } } } } pub fn build(args: AttributeArgs, mut item: ItemFn) -> TokenStream { let mut params = macro_try!(parse_param_attrs(&mut item)); let mut opt = macro_try!(parse_command_attr(args)); if let Some(a) = &opt.common().blocking { if item.sig.asyncness.is_some() { return Error::new(a.span(), "cannot use `blocking` on an async fn").to_compile_error(); } } opt.common().is_async = item.sig.asyncness.is_some(); let fn_vis = &item.vis; let fn_name = &item.sig.ident; let fn_generics = &item.sig.generics; let command_name_str = opt .common() .rename .clone() .unwrap_or_else(|| LitStr::new(&fn_name.to_string(), fn_name.span())); let is_async = LitBool::new( opt.common().is_async, item.sig .asyncness .map(|a| a.span()) .unwrap_or_else(Span::call_site), ); let ctx_ty = params .iter() .find_map(|a| { if let ParamType::Context(ty) = a { Some(ty.clone()) } else { None } }) .unwrap_or(macro_try!(syn::parse2(quote! { () }))); let ctx_trait = ctx_trait(ctx_ty, &opt); let metadata = metadata(&mut opt); let build_app = build_app(command_name_str.clone(), &mut opt, &mut params); let rpc_handler = rpc_handler(fn_name, fn_generics, &opt, ¶ms); let cli_handler = cli_handler(fn_name, fn_generics, &mut opt, ¶ms); let res = quote! { #item #fn_vis mod #fn_name { use super::*; pub const NAME: &'static str = #command_name_str; pub const ASYNC: bool = #is_async; #ctx_trait #metadata #build_app #rpc_handler #cli_handler } }; // panic!("{}", res); res }