From b806e08866be39e3af586fd35a003d6d1e4e3001 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Tue, 31 Aug 2021 22:53:25 -0600 Subject: [PATCH] separate rpc and cli bounds --- .../src/command/build.rs | 102 +++++++++++++----- .../src/command/mod.rs | 3 + .../src/command/parse.rs | 96 ++++++++++++++--- .../src/run_cli/parse.rs | 65 ++++++----- rpc-toolkit/tests/test.rs | 10 +- 5 files changed, 197 insertions(+), 79 deletions(-) diff --git a/rpc-toolkit-macro-internals/src/command/build.rs b/rpc-toolkit-macro-internals/src/command/build.rs index 46a87fe..1d1eb88 100644 --- a/rpc-toolkit-macro-internals/src/command/build.rs +++ b/rpc-toolkit-macro-internals/src/command/build.rs @@ -10,22 +10,72 @@ use syn::token::{Add, Comma, Where}; use super::parse::*; use super::*; -fn ctx_trait(ctx_ty: Type, opt: &Options) -> TokenStream { +fn ctx_trait(ctx_ty: Option, opt: &mut 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 }))); + let mut rpc_bounds = bounds.clone(); + let mut cli_bounds = bounds; + + let (use_cli, use_rpc) = match &opt.common().exec_ctx { + ExecutionContext::CliOnly(_) => (Some(None), false), + ExecutionContext::RpcOnly(_) | ExecutionContext::Standard => (None, true), + ExecutionContext::Local(_) => (Some(None), true), + ExecutionContext::CustomCli { context, .. } => (Some(Some(context.clone())), true), + }; + + if let Options::Parent(ParentOptions { + subcommands, + self_impl, + .. + }) = opt + { + if let Some(ctx_ty) = ctx_ty { + cli_bounds.push(macro_try!(parse2(quote! { Into<#ctx_ty> }))); + cli_bounds.push(macro_try!(parse2(quote! { Clone }))); + rpc_bounds.push(macro_try!(parse2(quote! { Into<#ctx_ty> }))); + rpc_bounds.push(macro_try!(parse2(quote! { Clone }))); + } + if let Some(SelfImplInfo { context, .. }) = self_impl { + if let Some(cli_ty) = use_cli.as_ref() { + if let Some(cli_ty) = cli_ty { + cli_bounds.push(macro_try!(parse2(quote! { Into<#cli_ty> }))); + } else { + cli_bounds.push(macro_try!(parse2(quote! { Into<#context> }))); + } + } + if use_rpc { + rpc_bounds.push(macro_try!(parse2(quote! { Into<#context> }))); + } + } 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 }))); + cli_bounds.push(macro_try!(parse2(quote! { #path::CommandContextCli }))); + rpc_bounds.push(macro_try!(parse2(quote! { #path::CommandContextRpc }))); + } + } else { + if let Some(cli_ty) = use_cli.as_ref() { + if let Some(cli_ty) = cli_ty { + cli_bounds.push(macro_try!(parse2(quote! { Into<#cli_ty> }))); + } else if let Some(ctx_ty) = &ctx_ty { + cli_bounds.push(macro_try!(parse2(quote! { Into<#ctx_ty> }))); + } + } + if use_rpc { + if let Some(ctx_ty) = &ctx_ty { + rpc_bounds.push(macro_try!(parse2(quote! { Into<#ctx_ty> }))); + } } } - quote! { - pub trait CommandContext: #bounds {} - impl CommandContext for T where T: #bounds {} - } + + let res = quote! { + pub trait CommandContextCli: #cli_bounds {} + impl CommandContextCli for T where T: #cli_bounds {} + + pub trait CommandContextRpc: #rpc_bounds {} + impl CommandContextRpc for T where T: #rpc_bounds {} + }; + res } fn metadata(full_options: &Options) -> TokenStream { @@ -416,7 +466,7 @@ fn rpc_handler( let mut parent_data_ty = quote! { () }; let mut generics = fn_generics.clone(); generics.params.push(macro_try!(syn::parse2( - quote! { GenericContext: CommandContext } + quote! { GenericContext: CommandContextRpc } ))); if generics.lt_token.is_none() { generics.lt_token = Some(Default::default()); @@ -669,7 +719,7 @@ fn cli_handler( quote! { ParentParams: ::rpc_toolkit::command_helpers::prelude::Serialize } ))); generics.params.push(macro_try!(syn::parse2( - quote! { GenericContext: CommandContext } + quote! { GenericContext: CommandContextCli } ))); if generics.lt_token.is_none() { generics.lt_token = Some(Default::default()); @@ -1073,11 +1123,11 @@ fn cli_handler( 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)) + rt_ref.block_on(#self_impl_fn(unreachable!(), parent_data)) } } else { quote! { - #self_impl_fn(Into::into(ctx), parent_data) + #self_impl_fn(unreachable!(), parent_data) } }; let create_rt = if common.is_async { @@ -1093,9 +1143,6 @@ fn cli_handler( 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?) }; @@ -1160,17 +1207,14 @@ pub fn build(args: AttributeArgs, mut item: ItemFn) -> TokenStream { .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 ctx_ty = params.iter().find_map(|a| { + if let ParamType::Context(ty) = a { + Some(ty.clone()) + } else { + None + } + }); + let ctx_trait = ctx_trait(ctx_ty, &mut 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); @@ -1195,6 +1239,8 @@ pub fn build(args: AttributeArgs, mut item: ItemFn) -> TokenStream { #cli_handler } }; - // panic!("{}", res); + if opt.common().macro_debug { + panic!("EXPANDED MACRO:\n{}", res); + } res } diff --git a/rpc-toolkit-macro-internals/src/command/mod.rs b/rpc-toolkit-macro-internals/src/command/mod.rs index 0b77805..71f6840 100644 --- a/rpc-toolkit-macro-internals/src/command/mod.rs +++ b/rpc-toolkit-macro-internals/src/command/mod.rs @@ -13,6 +13,7 @@ pub enum ExecutionContext { CustomCli { custom: Path, cli: Path, + context: Type, is_async: bool, }, } @@ -24,6 +25,7 @@ impl Default for ExecutionContext { #[derive(Default)] pub struct LeafOptions { + macro_debug: bool, blocking: Option, is_async: bool, aliases: Vec, @@ -36,6 +38,7 @@ pub struct LeafOptions { pub struct SelfImplInfo { path: Path, + context: Type, is_async: bool, blocking: bool, } diff --git a/rpc-toolkit-macro-internals/src/command/parse.rs b/rpc-toolkit-macro-internals/src/command/parse.rs index fdd5796..155041a 100644 --- a/rpc-toolkit-macro-internals/src/command/parse.rs +++ b/rpc-toolkit-macro-internals/src/command/parse.rs @@ -6,6 +6,9 @@ pub fn parse_command_attr(args: AttributeArgs) -> Result { let mut opt = Options::Leaf(Default::default()); for arg in args { match arg { + NestedMeta::Meta(Meta::Path(p)) if p.is_ident("macro_debug") => { + opt.common().macro_debug = true; + } NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("subcommands") => { let inner = opt.to_parent()?; if !inner.subcommands.is_empty() { @@ -31,32 +34,83 @@ pub fn parse_command_attr(args: AttributeArgs) -> Result { } inner.self_impl = Some(SelfImplInfo { path: self_impl, + context: parse2(quote::quote! { () }).unwrap(), is_async: false, blocking: false, }) } - NestedMeta::Meta(Meta::List(l)) if l.nested.len() == 1 => { + NestedMeta::Meta(Meta::List(l)) => { if inner.self_impl.is_some() { return Err(Error::new( self_impl.span(), "duplicate argument `self`", )); } - let blocking = match l.nested.first().unwrap() { - NestedMeta::Meta(Meta::Path(p)) if p.is_ident("blocking") => true, - NestedMeta::Meta(Meta::Path(p)) if p.is_ident("async") => false, - arg => return Err(Error::new(arg.span(), "unknown argument")), - }; + let mut is_async = false; + let mut blocking = false; + let mut context: Type = + parse2(quote::quote! { () }).unwrap(); + for meta in &l.nested { + match meta { + NestedMeta::Meta(Meta::Path(p)) + if p.is_ident("async") => + { + is_async = true; + if blocking { + return Err(Error::new( + p.span(), + "`async` and `blocking` are mutually exclusive", + )); + } + } + NestedMeta::Meta(Meta::Path(p)) + if p.is_ident("blocking") => + { + blocking = true; + if is_async { + return Err(Error::new( + p.span(), + "`async` and `blocking` are mutually exclusive", + )); + } + } + NestedMeta::Meta(Meta::List(p)) + if p.path.is_ident("context") => + { + context = if let Some(NestedMeta::Meta( + Meta::Path(context), + )) = p.nested.first() + { + Type::Path(TypePath { + path: context.clone(), + qself: None, + }) + } else { + return Err(Error::new( + p.span(), + "`context` requires a path argument", + )); + } + } + arg => { + return Err(Error::new( + arg.span(), + "unknown argument", + )) + } + } + } inner.self_impl = Some(SelfImplInfo { path: l.path, - is_async: !blocking, + context, + is_async, blocking, }) } a => { return Err(Error::new( a.span(), - "`self` implementation must be a path, or a list with 1 argument", + "`self` implementation must be a path, or a list", )) } } @@ -133,17 +187,33 @@ pub fn parse_command_attr(args: AttributeArgs) -> Result { NestedMeta::Meta(Meta::Path(custom_cli_impl)) => { opt.common().exec_ctx = ExecutionContext::CustomCli { custom: list.path, + context: parse2(quote::quote!{ () }).unwrap(), cli: custom_cli_impl.clone(), is_async: false, } } - NestedMeta::Meta(Meta::List(custom_cli_impl)) if custom_cli_impl.nested.len() == 1 => { - let is_async = match custom_cli_impl.nested.first().unwrap() { - NestedMeta::Meta(Meta::Path(p)) if p.is_ident("async") => false, - arg => return Err(Error::new(arg.span(), "unknown argument")), - }; + NestedMeta::Meta(Meta::List(custom_cli_impl)) => { + let mut is_async = false; + let mut context: Type = parse2(quote::quote! { () }).unwrap(); + for meta in &custom_cli_impl.nested { + match meta { + NestedMeta::Meta(Meta::Path(p)) if p.is_ident("async") => is_async = true, + NestedMeta::Meta(Meta::List(p)) if p.path.is_ident("context") => { + context = if let Some(NestedMeta::Meta(Meta::Path(context))) = p.nested.first() { + Type::Path(TypePath { + path: context.clone(), + qself: None, + }) + } else { + return Err(Error::new(p.span(), "`context` requires a path argument")); + } + } + arg => return Err(Error::new(arg.span(), "unknown argument")), + } + } opt.common().exec_ctx = ExecutionContext::CustomCli { custom: list.path, + context, cli: custom_cli_impl.path.clone(), is_async, }; diff --git a/rpc-toolkit-macro-internals/src/run_cli/parse.rs b/rpc-toolkit-macro-internals/src/run_cli/parse.rs index 0f44c05..2ca05b1 100644 --- a/rpc-toolkit-macro-internals/src/run_cli/parse.rs +++ b/rpc-toolkit-macro-internals/src/run_cli/parse.rs @@ -25,41 +25,40 @@ impl Parse for MutApp { impl Parse for RunCliArgs { fn parse(input: ParseStream) -> Result { - let command = input.parse()?; - if !input.is_empty() { - let _: token::Comma = input.parse()?; + let args; + braced!(args in input); + let mut command = None; + let mut mut_app = None; + let mut make_ctx = None; + let mut parent_data = None; + let mut exit_fn = None; + while !args.is_empty() { + let arg_name: syn::Ident = args.parse()?; + let _: token::Colon = args.parse()?; + match arg_name.to_string().as_str() { + "command" => { + command = Some(args.parse()?); + } + "app" => { + mut_app = Some(args.parse()?); + } + "context" => { + make_ctx = Some(args.parse()?); + } + "parent_data" => { + parent_data = Some(args.parse()?); + } + "exit" => { + exit_fn = Some(args.parse()?); + } + _ => return Err(Error::new(arg_name.span(), "unknown argument")), + } + if !args.is_empty() { + let _: token::Comma = args.parse()?; + } } - let mut_app = if !input.is_empty() { - Some(input.parse()?) - } else { - None - }; - if !input.is_empty() { - let _: token::Comma = input.parse()?; - } - let make_ctx = if !input.is_empty() { - Some(input.parse()?) - } else { - None - }; - if !input.is_empty() { - let _: token::Comma = input.parse()?; - } - if !input.is_empty() { - let _: token::Comma = input.parse()?; - } - let parent_data = if !input.is_empty() { - Some(input.parse()?) - } else { - None - }; - let exit_fn = if !input.is_empty() { - Some(input.parse()?) - } else { - None - }; Ok(RunCliArgs { - command, + command: command.expect("`command` is required"), mut_app, make_ctx, parent_data, diff --git a/rpc-toolkit/tests/test.rs b/rpc-toolkit/tests/test.rs index 4ac992b..850377f 100644 --- a/rpc-toolkit/tests/test.rs +++ b/rpc-toolkit/tests/test.rs @@ -195,16 +195,16 @@ fn cli_test() { #[test] #[ignore] fn cli_example() { - run_cli!( - dothething::, - app => app + run_cli! ({ + command: dothething::, + app: app => app .arg(Arg::with_name("host").long("host").short("h").takes_value(true)) .arg(Arg::with_name("port").long("port").short("p").takes_value(true)), - matches => AppState(Arc::new(ConfigSeed { + context: matches => AppState(Arc::new(ConfigSeed { host: Host::parse(matches.value_of("host").unwrap_or("localhost")).unwrap(), port: matches.value_of("port").unwrap_or("8000").parse().unwrap(), })) - ) + }) } ////////////////////////////////////////////////