separate rpc and cli bounds

This commit is contained in:
Aiden McClelland
2021-08-31 22:53:25 -06:00
parent 3195ffab68
commit b806e08866
5 changed files with 197 additions and 79 deletions

View File

@@ -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<Type>, opt: &mut Options) -> TokenStream {
let mut bounds: Punctuated<TypeParamBound, Add> = 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<T> CommandContext for T where T: #bounds {}
}
let res = quote! {
pub trait CommandContextCli: #cli_bounds {}
impl<T> CommandContextCli for T where T: #cli_bounds {}
pub trait CommandContextRpc: #rpc_bounds {}
impl<T> 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, &params);
@@ -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
}

View File

@@ -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<Path>,
is_async: bool,
aliases: Vec<LitStr>,
@@ -36,6 +38,7 @@ pub struct LeafOptions {
pub struct SelfImplInfo {
path: Path,
context: Type,
is_async: bool,
blocking: bool,
}

View File

@@ -6,6 +6,9 @@ pub fn parse_command_attr(args: AttributeArgs) -> Result<Options> {
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<Options> {
}
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<Options> {
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,
};

View File

@@ -25,41 +25,40 @@ impl Parse for MutApp {
impl Parse for RunCliArgs {
fn parse(input: ParseStream) -> Result<Self> {
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,

View File

@@ -195,16 +195,16 @@ fn cli_test() {
#[test]
#[ignore]
fn cli_example() {
run_cli!(
dothething::<String, _>,
app => app
run_cli! ({
command: dothething::<String, _>,
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(),
}))
)
})
}
////////////////////////////////////////////////