mirror of
https://github.com/Start9Labs/rpc-toolkit.git
synced 2026-03-30 12:21:58 +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::parse::*;
|
||||||
use super::*;
|
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();
|
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 })));
|
bounds.push(macro_try!(parse2(quote! { ::rpc_toolkit::Context })));
|
||||||
if let Options::Parent(ParentOptions { subcommands, .. }) = opt {
|
let mut rpc_bounds = bounds.clone();
|
||||||
bounds.push(macro_try!(parse2(quote! { 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 {
|
for subcmd in subcommands {
|
||||||
let mut path = subcmd.clone();
|
let mut path = subcmd.clone();
|
||||||
std::mem::take(&mut path.segments.last_mut().unwrap().arguments);
|
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 {}
|
let res = quote! {
|
||||||
impl<T> CommandContext for T where T: #bounds {}
|
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 {
|
fn metadata(full_options: &Options) -> TokenStream {
|
||||||
@@ -416,7 +466,7 @@ fn rpc_handler(
|
|||||||
let mut parent_data_ty = quote! { () };
|
let mut parent_data_ty = quote! { () };
|
||||||
let mut generics = fn_generics.clone();
|
let mut generics = fn_generics.clone();
|
||||||
generics.params.push(macro_try!(syn::parse2(
|
generics.params.push(macro_try!(syn::parse2(
|
||||||
quote! { GenericContext: CommandContext }
|
quote! { GenericContext: CommandContextRpc }
|
||||||
)));
|
)));
|
||||||
if generics.lt_token.is_none() {
|
if generics.lt_token.is_none() {
|
||||||
generics.lt_token = Some(Default::default());
|
generics.lt_token = Some(Default::default());
|
||||||
@@ -669,7 +719,7 @@ fn cli_handler(
|
|||||||
quote! { ParentParams: ::rpc_toolkit::command_helpers::prelude::Serialize }
|
quote! { ParentParams: ::rpc_toolkit::command_helpers::prelude::Serialize }
|
||||||
)));
|
)));
|
||||||
generics.params.push(macro_try!(syn::parse2(
|
generics.params.push(macro_try!(syn::parse2(
|
||||||
quote! { GenericContext: CommandContext }
|
quote! { GenericContext: CommandContextCli }
|
||||||
)));
|
)));
|
||||||
if generics.lt_token.is_none() {
|
if generics.lt_token.is_none() {
|
||||||
generics.lt_token = Some(Default::default());
|
generics.lt_token = Some(Default::default());
|
||||||
@@ -1073,11 +1123,11 @@ fn cli_handler(
|
|||||||
let self_impl_fn = &self_impl.path;
|
let self_impl_fn = &self_impl.path;
|
||||||
let self_impl = if self_impl.is_async {
|
let self_impl = if self_impl.is_async {
|
||||||
quote! {
|
quote! {
|
||||||
rt_ref.block_on(#self_impl_fn(Into::into(ctx), parent_data))
|
rt_ref.block_on(#self_impl_fn(unreachable!(), parent_data))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quote! {
|
quote! {
|
||||||
#self_impl_fn(Into::into(ctx), parent_data)
|
#self_impl_fn(unreachable!(), parent_data)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let create_rt = if common.is_async {
|
let create_rt = if common.is_async {
|
||||||
@@ -1093,9 +1143,6 @@ fn cli_handler(
|
|||||||
let return_ty = if true {
|
let return_ty = if true {
|
||||||
::rpc_toolkit::command_helpers::prelude::PhantomData
|
::rpc_toolkit::command_helpers::prelude::PhantomData
|
||||||
} else {
|
} 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?)
|
::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())
|
.map(|a| a.span())
|
||||||
.unwrap_or_else(Span::call_site),
|
.unwrap_or_else(Span::call_site),
|
||||||
);
|
);
|
||||||
let ctx_ty = params
|
let ctx_ty = params.iter().find_map(|a| {
|
||||||
.iter()
|
if let ParamType::Context(ty) = a {
|
||||||
.find_map(|a| {
|
Some(ty.clone())
|
||||||
if let ParamType::Context(ty) = a {
|
} else {
|
||||||
Some(ty.clone())
|
None
|
||||||
} else {
|
}
|
||||||
None
|
});
|
||||||
}
|
let ctx_trait = ctx_trait(ctx_ty, &mut opt);
|
||||||
})
|
|
||||||
.unwrap_or(macro_try!(syn::parse2(quote! { () })));
|
|
||||||
let ctx_trait = ctx_trait(ctx_ty, &opt);
|
|
||||||
let metadata = metadata(&mut opt);
|
let metadata = metadata(&mut opt);
|
||||||
let build_app = build_app(command_name_str.clone(), &mut opt, &mut params);
|
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 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
|
#cli_handler
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// panic!("{}", res);
|
if opt.common().macro_debug {
|
||||||
|
panic!("EXPANDED MACRO:\n{}", res);
|
||||||
|
}
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ pub enum ExecutionContext {
|
|||||||
CustomCli {
|
CustomCli {
|
||||||
custom: Path,
|
custom: Path,
|
||||||
cli: Path,
|
cli: Path,
|
||||||
|
context: Type,
|
||||||
is_async: bool,
|
is_async: bool,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -24,6 +25,7 @@ impl Default for ExecutionContext {
|
|||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct LeafOptions {
|
pub struct LeafOptions {
|
||||||
|
macro_debug: bool,
|
||||||
blocking: Option<Path>,
|
blocking: Option<Path>,
|
||||||
is_async: bool,
|
is_async: bool,
|
||||||
aliases: Vec<LitStr>,
|
aliases: Vec<LitStr>,
|
||||||
@@ -36,6 +38,7 @@ pub struct LeafOptions {
|
|||||||
|
|
||||||
pub struct SelfImplInfo {
|
pub struct SelfImplInfo {
|
||||||
path: Path,
|
path: Path,
|
||||||
|
context: Type,
|
||||||
is_async: bool,
|
is_async: bool,
|
||||||
blocking: bool,
|
blocking: bool,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ pub fn parse_command_attr(args: AttributeArgs) -> Result<Options> {
|
|||||||
let mut opt = Options::Leaf(Default::default());
|
let mut opt = Options::Leaf(Default::default());
|
||||||
for arg in args {
|
for arg in args {
|
||||||
match arg {
|
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") => {
|
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("subcommands") => {
|
||||||
let inner = opt.to_parent()?;
|
let inner = opt.to_parent()?;
|
||||||
if !inner.subcommands.is_empty() {
|
if !inner.subcommands.is_empty() {
|
||||||
@@ -31,32 +34,83 @@ pub fn parse_command_attr(args: AttributeArgs) -> Result<Options> {
|
|||||||
}
|
}
|
||||||
inner.self_impl = Some(SelfImplInfo {
|
inner.self_impl = Some(SelfImplInfo {
|
||||||
path: self_impl,
|
path: self_impl,
|
||||||
|
context: parse2(quote::quote! { () }).unwrap(),
|
||||||
is_async: false,
|
is_async: false,
|
||||||
blocking: false,
|
blocking: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
NestedMeta::Meta(Meta::List(l)) if l.nested.len() == 1 => {
|
NestedMeta::Meta(Meta::List(l)) => {
|
||||||
if inner.self_impl.is_some() {
|
if inner.self_impl.is_some() {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
self_impl.span(),
|
self_impl.span(),
|
||||||
"duplicate argument `self`",
|
"duplicate argument `self`",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let blocking = match l.nested.first().unwrap() {
|
let mut is_async = false;
|
||||||
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("blocking") => true,
|
let mut blocking = false;
|
||||||
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("async") => false,
|
let mut context: Type =
|
||||||
arg => return Err(Error::new(arg.span(), "unknown argument")),
|
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 {
|
inner.self_impl = Some(SelfImplInfo {
|
||||||
path: l.path,
|
path: l.path,
|
||||||
is_async: !blocking,
|
context,
|
||||||
|
is_async,
|
||||||
blocking,
|
blocking,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
a => {
|
a => {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
a.span(),
|
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)) => {
|
NestedMeta::Meta(Meta::Path(custom_cli_impl)) => {
|
||||||
opt.common().exec_ctx = ExecutionContext::CustomCli {
|
opt.common().exec_ctx = ExecutionContext::CustomCli {
|
||||||
custom: list.path,
|
custom: list.path,
|
||||||
|
context: parse2(quote::quote!{ () }).unwrap(),
|
||||||
cli: custom_cli_impl.clone(),
|
cli: custom_cli_impl.clone(),
|
||||||
is_async: false,
|
is_async: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NestedMeta::Meta(Meta::List(custom_cli_impl)) if custom_cli_impl.nested.len() == 1 => {
|
NestedMeta::Meta(Meta::List(custom_cli_impl)) => {
|
||||||
let is_async = match custom_cli_impl.nested.first().unwrap() {
|
let mut is_async = false;
|
||||||
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("async") => false,
|
let mut context: Type = parse2(quote::quote! { () }).unwrap();
|
||||||
arg => return Err(Error::new(arg.span(), "unknown argument")),
|
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 {
|
opt.common().exec_ctx = ExecutionContext::CustomCli {
|
||||||
custom: list.path,
|
custom: list.path,
|
||||||
|
context,
|
||||||
cli: custom_cli_impl.path.clone(),
|
cli: custom_cli_impl.path.clone(),
|
||||||
is_async,
|
is_async,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,41 +25,40 @@ impl Parse for MutApp {
|
|||||||
|
|
||||||
impl Parse for RunCliArgs {
|
impl Parse for RunCliArgs {
|
||||||
fn parse(input: ParseStream) -> Result<Self> {
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
let command = input.parse()?;
|
let args;
|
||||||
if !input.is_empty() {
|
braced!(args in input);
|
||||||
let _: token::Comma = input.parse()?;
|
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 {
|
Ok(RunCliArgs {
|
||||||
command,
|
command: command.expect("`command` is required"),
|
||||||
mut_app,
|
mut_app,
|
||||||
make_ctx,
|
make_ctx,
|
||||||
parent_data,
|
parent_data,
|
||||||
|
|||||||
@@ -195,16 +195,16 @@ fn cli_test() {
|
|||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
#[ignore]
|
||||||
fn cli_example() {
|
fn cli_example() {
|
||||||
run_cli!(
|
run_cli! ({
|
||||||
dothething::<String, _>,
|
command: dothething::<String, _>,
|
||||||
app => app
|
app: app => app
|
||||||
.arg(Arg::with_name("host").long("host").short("h").takes_value(true))
|
.arg(Arg::with_name("host").long("host").short("h").takes_value(true))
|
||||||
.arg(Arg::with_name("port").long("port").short("p").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(),
|
host: Host::parse(matches.value_of("host").unwrap_or("localhost")).unwrap(),
|
||||||
port: matches.value_of("port").unwrap_or("8000").parse().unwrap(),
|
port: matches.value_of("port").unwrap_or("8000").parse().unwrap(),
|
||||||
}))
|
}))
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////
|
////////////////////////////////////////////////
|
||||||
|
|||||||
Reference in New Issue
Block a user