mirror of
https://github.com/Start9Labs/rpc-toolkit.git
synced 2026-03-26 02:11:56 +00:00
separate rpc and cli bounds
This commit is contained in:
@@ -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, ¶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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
}))
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////
|
||||
|
||||
Reference in New Issue
Block a user