1use std::collections::HashSet;
2
3use proc_macro2::*;
4use quote::*;
5use syn::fold::Fold;
6use syn::punctuated::Punctuated;
7use syn::spanned::Spanned;
8use syn::token::{Add, Comma, Where};
9
10use super::parse::*;
11use super::*;
12
13fn ctx_trait(ctx_ty: Option<Type>, opt: &mut Options) -> TokenStream {
14 let mut bounds: Punctuated<TypeParamBound, Add> = Punctuated::new();
15 bounds.push(macro_try!(parse2(quote! { ::rpc_toolkit::Context })));
16 let mut rpc_bounds = bounds.clone();
17 let mut cli_bounds = bounds;
18
19 let (use_cli, use_rpc) = match &opt.common().exec_ctx {
20 ExecutionContext::CliOnly(_) => (Some(None), false),
21 ExecutionContext::RpcOnly(_) | ExecutionContext::Standard => (None, true),
22 ExecutionContext::Local(_) => (Some(None), true),
23 ExecutionContext::CustomCli { context, .. } => (Some(Some(context.clone())), true),
24 };
25
26 if let Options::Parent(ParentOptions {
27 subcommands,
28 self_impl,
29 ..
30 }) = opt
31 {
32 if let Some(ctx_ty) = ctx_ty {
33 cli_bounds.push(macro_try!(parse2(quote! { Into<#ctx_ty> })));
34 cli_bounds.push(macro_try!(parse2(quote! { Clone })));
35 rpc_bounds.push(macro_try!(parse2(quote! { Into<#ctx_ty> })));
36 rpc_bounds.push(macro_try!(parse2(quote! { Clone })));
37 }
38 if let Some(SelfImplInfo { context, .. }) = self_impl {
39 if let Some(cli_ty) = use_cli.as_ref() {
40 if let Some(cli_ty) = cli_ty {
41 cli_bounds.push(macro_try!(parse2(quote! { Into<#cli_ty> })));
42 } else {
43 cli_bounds.push(macro_try!(parse2(quote! { Into<#context> })));
44 }
45 }
46 if use_rpc {
47 rpc_bounds.push(macro_try!(parse2(quote! { Into<#context> })));
48 }
49 }
50 for subcmd in subcommands {
51 let mut path = subcmd.clone();
52 std::mem::take(&mut path.segments.last_mut().unwrap().arguments);
53 cli_bounds.push(macro_try!(parse2(quote! { #path::CommandContextCli })));
54 rpc_bounds.push(macro_try!(parse2(quote! { #path::CommandContextRpc })));
55 }
56 } else {
57 if let Some(cli_ty) = use_cli.as_ref() {
58 if let Some(cli_ty) = cli_ty {
59 cli_bounds.push(macro_try!(parse2(quote! { Into<#cli_ty> })));
60 } else if let Some(ctx_ty) = &ctx_ty {
61 cli_bounds.push(macro_try!(parse2(quote! { Into<#ctx_ty> })));
62 }
63 }
64 if use_rpc {
65 if let Some(ctx_ty) = &ctx_ty {
66 rpc_bounds.push(macro_try!(parse2(quote! { Into<#ctx_ty> })));
67 }
68 }
69 }
70
71 let res = quote! {
72 pub trait CommandContextCli: #cli_bounds {}
73 impl<T> CommandContextCli for T where T: #cli_bounds {}
74
75 pub trait CommandContextRpc: #rpc_bounds {}
76 impl<T> CommandContextRpc for T where T: #rpc_bounds {}
77 };
78 res
79}
80
81fn metadata(full_options: &Options) -> TokenStream {
82 let options = match full_options {
83 Options::Leaf(a) => a,
84 Options::Parent(ParentOptions { common, .. }) => common,
85 };
86 let fallthrough = |ty: &str| {
87 let getter_name = Ident::new(&format!("get_{}", ty), Span::call_site());
88 match &*full_options {
89 Options::Parent(ParentOptions { subcommands, .. }) => {
90 let subcmd_handler = subcommands.iter().map(|subcmd| {
91 let mut subcmd = subcmd.clone();
92 subcmd.segments.last_mut().unwrap().arguments = PathArguments::None;
93 quote_spanned!{ subcmd.span() =>
94 [#subcmd::NAME, rest] => if let Some(val) = #subcmd::Metadata.#getter_name(rest, key) {
95 return Some(val);
96 },
97 }
98 });
99 quote! {
100 if !command.is_empty() {
101 match command.splitn(2, ".").chain(std::iter::repeat("")).take(2).collect::<Vec<_>>().as_slice() {
102 #(
103 #subcmd_handler
104 )*
105 _ => ()
106 }
107 }
108 }
109 }
110 _ => quote! {},
111 }
112 };
113 fn impl_getter<I: Iterator<Item = TokenStream>>(
114 ty: &str,
115 metadata: I,
116 fallthrough: TokenStream,
117 ) -> TokenStream {
118 let getter_name = Ident::new(&format!("get_{}", ty), Span::call_site());
119 let ty: Type = syn::parse_str(ty).unwrap();
120 quote! {
121 fn #getter_name(&self, command: &str, key: &str) -> Option<#ty> {
122 #fallthrough
123 match key {
124 #(#metadata)*
125 _ => None,
126 }
127 }
128 }
129 }
130 let bool_metadata = options
131 .metadata
132 .iter()
133 .filter(|(_, lit)| matches!(lit, Lit::Bool(_)))
134 .map(|(name, value)| {
135 let name = LitStr::new(&name.to_string(), name.span());
136 quote! {
137 #name => Some(#value),
138 }
139 });
140 let number_metadata = |ty: &str| {
141 let ty: Type = syn::parse_str(ty).unwrap();
142 options
143 .metadata
144 .iter()
145 .filter(|(_, lit)| matches!(lit, Lit::Int(_) | Lit::Float(_) | Lit::Byte(_)))
146 .map(move |(name, value)| {
147 let name = LitStr::new(&name.to_string(), name.span());
148 quote! {
149 #name => Some(#value as #ty),
150 }
151 })
152 };
153 let char_metadata = options
154 .metadata
155 .iter()
156 .filter(|(_, lit)| matches!(lit, Lit::Char(_)))
157 .map(|(name, value)| {
158 let name = LitStr::new(&name.to_string(), name.span());
159 quote! {
160 #name => Some(#value),
161 }
162 });
163 let str_metadata = options
164 .metadata
165 .iter()
166 .filter(|(_, lit)| matches!(lit, Lit::Str(_)))
167 .map(|(name, value)| {
168 let name = LitStr::new(&name.to_string(), name.span());
169 quote! {
170 #name => Some(#value),
171 }
172 });
173 let bstr_metadata = options
174 .metadata
175 .iter()
176 .filter(|(_, lit)| matches!(lit, Lit::ByteStr(_)))
177 .map(|(name, value)| {
178 let name = LitStr::new(&name.to_string(), name.span());
179 quote! {
180 #name => Some(#value),
181 }
182 });
183
184 let bool_getter = impl_getter("bool", bool_metadata, fallthrough("bool"));
185 let u8_getter = impl_getter("u8", number_metadata("u8"), fallthrough("u8"));
186 let u16_getter = impl_getter("u16", number_metadata("u16"), fallthrough("u16"));
187 let u32_getter = impl_getter("u32", number_metadata("u32"), fallthrough("u32"));
188 let u64_getter = impl_getter("u64", number_metadata("u64"), fallthrough("u64"));
189 let usize_getter = impl_getter("usize", number_metadata("usize"), fallthrough("usize"));
190 let i8_getter = impl_getter("i8", number_metadata("i8"), fallthrough("i8"));
191 let i16_getter = impl_getter("i16", number_metadata("i16"), fallthrough("i16"));
192 let i32_getter = impl_getter("i32", number_metadata("i32"), fallthrough("i32"));
193 let i64_getter = impl_getter("i64", number_metadata("i64"), fallthrough("i64"));
194 let isize_getter = impl_getter("isize", number_metadata("isize"), fallthrough("isize"));
195 let f32_getter = impl_getter("f32", number_metadata("f32"), fallthrough("f32"));
196 let f64_getter = impl_getter("f64", number_metadata("f64"), fallthrough("f64"));
197 let char_getter = impl_getter("char", char_metadata, fallthrough("char"));
198 let str_fallthrough = fallthrough("str");
199 let str_getter = quote! {
200 fn get_str(&self, command: &str, key: &str) -> Option<&'static str> {
201 #str_fallthrough
202 match key {
203 #(#str_metadata)*
204 _ => None,
205 }
206 }
207 };
208 let bstr_fallthrough = fallthrough("bstr");
209 let bstr_getter = quote! {
210 fn get_bstr(&self, command: &str, key: &str) -> Option<&'static [u8]> {
211 #bstr_fallthrough
212 match key {
213 #(#bstr_metadata)*
214 _ => None,
215 }
216 }
217 };
218
219 let res = quote! {
220 #[derive(Clone, Copy, Default)]
221 pub struct Metadata;
222
223 #[allow(overflowing_literals)]
224 impl ::rpc_toolkit::Metadata for Metadata {
225 #bool_getter
226 #u8_getter
227 #u16_getter
228 #u32_getter
229 #u64_getter
230 #usize_getter
231 #i8_getter
232 #i16_getter
233 #i32_getter
234 #i64_getter
235 #isize_getter
236 #f32_getter
237 #f64_getter
238 #char_getter
239 #str_getter
240 #bstr_getter
241 }
242 };
243 res
245}
246
247fn build_app(name: LitStr, opt: &mut Options, params: &mut [ParamType]) -> TokenStream {
248 let about = opt.common().about.clone().into_iter();
249 let (subcommand, subcommand_required) = if let Options::Parent(opt) = opt {
250 (
251 opt.subcommands
252 .iter()
253 .map(|subcmd| {
254 let mut path = subcmd.clone();
255 path.segments.last_mut().unwrap().arguments = PathArguments::None;
256 path
257 })
258 .collect(),
259 opt.self_impl.is_none(),
260 )
261 } else {
262 (Vec::new(), false)
263 };
264 let arg = params
265 .iter_mut()
266 .filter_map(|param| {
267 if let ParamType::Arg(arg) = param {
268 if arg.stdin.is_some() {
269 return None;
270 }
271 let name = arg.name.clone().unwrap();
272 let name_str = arg.rename.clone().unwrap_or_else(|| LitStr::new(&name.to_string(), name.span()));
273 let help = arg.help.clone().into_iter();
274 let short = arg.short.clone().into_iter();
275 let long = arg.long.clone().into_iter();
276 let mut modifications = TokenStream::default();
277 let ty_span = arg.ty.span();
278 if let Type::Path(p) = &mut arg.ty {
279 if p.path.is_ident("bool")
280 && arg.parse.is_none()
281 && (arg.short.is_some() || arg.long.is_some())
282 {
283 arg.check_is_present = true;
284 modifications.extend(quote_spanned! { ty_span =>
285 arg = arg.takes_value(false);
286 });
287 } else if arg.count.is_some() {
288 modifications.extend(quote_spanned! { ty_span =>
289 arg = arg.takes_value(false);
290 arg = arg.multiple(true);
291 });
292 } else {
293 modifications.extend(quote_spanned! { ty_span =>
294 arg = arg.takes_value(true);
295 });
296 if let Some(_) = &arg.default {
297 modifications.extend(quote_spanned! { ty_span =>
298 arg = arg.required(false);
299 });
300 } else if p.path.segments.last().unwrap().ident == "Option" {
301 arg.optional = true;
302 modifications.extend(quote_spanned! { ty_span =>
303 arg = arg.required(false);
304 });
305 } else if arg.multiple.is_some() {
306 modifications.extend(quote_spanned! { ty_span =>
307 arg = arg.multiple(true);
308 });
309 } else {
310 modifications.extend(quote_spanned! { ty_span =>
311 arg = arg.required(true);
312 });
313 }
314 }
315 };
316 Some(quote! {
317 {
318 let mut arg = ::rpc_toolkit::command_helpers::prelude::Arg::with_name(#name_str);
319 #(
320 arg = arg.help(#help);
321 )*
322 #(
323 arg = arg.short(#short);
324 )*
325 #(
326 arg = arg.long(#long);
327 )*
328 #modifications
329
330 arg
331 }
332 })
333 } else {
334 None
335 }
336 })
337 .collect::<Vec<_>>();
338 let required = LitBool::new(subcommand_required, Span::call_site());
339 let alias = &opt.common().aliases;
340 quote! {
341 pub fn build_app() -> ::rpc_toolkit::command_helpers::prelude::App<'static> {
342 let mut app = ::rpc_toolkit::command_helpers::prelude::App::new(#name);
343 #(
344 app = app.about(#about);
345 )*
346 #(
347 app = app.alias(#alias);
348 )*
349 #(
350 app = app.arg(#arg);
351 )*
352 #(
353 app = app.subcommand(#subcommand::build_app());
354 )*
355 if #required {
356 app = app.setting(::rpc_toolkit::command_helpers::prelude::AppSettings::SubcommandRequired);
357 }
358 app
359 }
360 }
361}
362
363struct GenericFilter<'a> {
364 src: &'a Generics,
365 lifetimes: HashSet<Lifetime>,
366 types: HashSet<Ident>,
367}
368impl<'a> GenericFilter<'a> {
369 fn new(src: &'a Generics) -> Self {
370 GenericFilter {
371 src,
372 lifetimes: HashSet::new(),
373 types: HashSet::new(),
374 }
375 }
376 fn finish(self) -> Generics {
377 let mut params: Punctuated<GenericParam, Comma> = Default::default();
378 let mut where_clause = self
379 .src
380 .where_clause
381 .as_ref()
382 .map(|wc| WhereClause {
383 where_token: wc.where_token,
384 predicates: Default::default(),
385 })
386 .unwrap_or_else(|| WhereClause {
387 where_token: Where(Span::call_site()),
388 predicates: Default::default(),
389 });
390 for src_param in &self.src.params {
391 match src_param {
392 GenericParam::Lifetime(l) if self.lifetimes.contains(&l.lifetime) => {
393 params.push(src_param.clone())
394 }
395 GenericParam::Type(t) if self.types.contains(&t.ident) => {
396 params.push(src_param.clone())
397 }
398 _ => (),
399 }
400 }
401 for src_predicate in self.src.where_clause.iter().flat_map(|wc| &wc.predicates) {
402 match src_predicate {
403 WherePredicate::Lifetime(l) if self.lifetimes.contains(&l.lifetime) => {
404 where_clause.predicates.push(src_predicate.clone())
405 }
406 WherePredicate::Type(PredicateType {
407 bounded_ty: Type::Path(t),
408 ..
409 }) if self.types.contains(&t.path.segments.last().unwrap().ident) => {
410 where_clause.predicates.push(src_predicate.clone())
411 }
412 _ => (),
413 }
414 }
415 Generics {
416 lt_token: if params.is_empty() {
417 None
418 } else {
419 self.src.lt_token.clone()
420 },
421 gt_token: if params.is_empty() {
422 None
423 } else {
424 self.src.gt_token.clone()
425 },
426 params,
427 where_clause: if where_clause.predicates.is_empty() {
428 None
429 } else {
430 Some(where_clause)
431 },
432 }
433 }
434}
435impl<'a> Fold for GenericFilter<'a> {
436 fn fold_lifetime(&mut self, i: Lifetime) -> Lifetime {
437 self.lifetimes
438 .extend(self.src.params.iter().filter_map(|param| match param {
439 GenericParam::Lifetime(l) if l.lifetime == i => Some(l.lifetime.clone()),
440 _ => None,
441 }));
442 i
443 }
444 fn fold_type(&mut self, i: Type) -> Type {
445 self.types.extend(
446 self.src
447 .params
448 .iter()
449 .filter_map(|param| match (param, &i) {
450 (GenericParam::Type(t), Type::Path(i)) if i.path.is_ident(&t.ident) => {
451 Some(t.ident.clone())
452 }
453 _ => None,
454 }),
455 );
456 i
457 }
458}
459
460fn rpc_handler(
461 fn_name: &Ident,
462 fn_generics: &Generics,
463 opt: &Options,
464 params: &[ParamType],
465) -> TokenStream {
466 let mut parent_data_ty = quote! { () };
467 let mut generics = fn_generics.clone();
468 generics.params.push(macro_try!(syn::parse2(
469 quote! { GenericContext: CommandContextRpc }
470 )));
471 if generics.lt_token.is_none() {
472 generics.lt_token = Some(Default::default());
473 }
474 if generics.gt_token.is_none() {
475 generics.gt_token = Some(Default::default());
476 }
477 let mut param_def = Vec::new();
478 for param in params {
479 match param {
480 ParamType::Arg(arg) => {
481 let name = arg.name.clone().unwrap();
482 let rename = arg
483 .rename
484 .clone()
485 .unwrap_or_else(|| LitStr::new(&name.to_string(), name.span()));
486 let field_name = Ident::new(&format!("arg_{}", name), name.span());
487 let ty = arg.ty.clone();
488 let def = quote! {
489 #[serde(rename = #rename)]
490 #field_name: #ty,
491 };
492 let def = match &arg.default {
493 Some(Some(default)) => {
494 quote! {
495 #[serde(default = #default)]
496 #def
497 }
498 }
499 Some(None) => {
500 quote! {
501 #[serde(default)]
502 #def
503 }
504 }
505 None => def,
506 };
507 param_def.push(def);
508 }
509 ParamType::ParentData(ty) => parent_data_ty = quote! { #ty },
510 _ => (),
511 }
512 }
513 let (_, fn_type_generics, _) = fn_generics.split_for_impl();
514 let fn_turbofish = fn_type_generics.as_turbofish();
515 let fn_path: Path = macro_try!(syn::parse2(quote! { super::#fn_name#fn_turbofish }));
516 let mut param_generics_filter = GenericFilter::new(fn_generics);
517 for param in params {
518 if let ParamType::Arg(a) = param {
519 param_generics_filter.fold_type(a.ty.clone());
520 }
521 }
522 let param_generics = param_generics_filter.finish();
523 let (_, param_ty_generics, _) = param_generics.split_for_impl();
524 let param_struct_def = quote! {
525 #[allow(dead_code)]
526 #[derive(::rpc_toolkit::command_helpers::prelude::Deserialize)]
527 pub struct Params#param_ty_generics {
528 #(
529 #param_def
530 )*
531 #[serde(flatten)]
532 #[serde(default)]
533 rest: ::rpc_toolkit::command_helpers::prelude::Value,
534 }
535 };
536 let param = params.iter().map(|param| match param {
537 ParamType::Arg(arg) => {
538 let name = arg.name.clone().unwrap();
539 let field_name = Ident::new(&format!("arg_{}", name), name.span());
540 quote! { args.#field_name }
541 }
542 ParamType::Context(ty) => {
543 if matches!(opt, Options::Parent { .. }) {
544 quote! { <GenericContext as Into<#ty>>::into(ctx.clone()) }
545 } else {
546 quote! { <GenericContext as Into<#ty>>::into(ctx) }
547 }
548 }
549 ParamType::ParentData(_) => {
550 quote! { parent_data }
551 }
552 ParamType::Request => quote! { request },
553 ParamType::Response => quote! { response },
554 ParamType::None => unreachable!(),
555 });
556 match opt {
557 Options::Leaf(opt) if matches!(opt.exec_ctx, ExecutionContext::CliOnly(_)) => quote! {
558 #param_struct_def
559
560 pub async fn rpc_handler#generics(
561 _ctx: GenericContext,
562 _parent_data: #parent_data_ty,
563 _request: &::rpc_toolkit::command_helpers::prelude::RequestParts,
564 _response: &mut ::rpc_toolkit::command_helpers::prelude::ResponseParts,
565 method: &str,
566 _args: Params#param_ty_generics,
567 ) -> Result<::rpc_toolkit::command_helpers::prelude::Value, ::rpc_toolkit::command_helpers::prelude::RpcError> {
568 Err(::rpc_toolkit::command_helpers::prelude::RpcError {
569 data: Some(method.into()),
570 ..::rpc_toolkit::command_helpers::prelude::yajrc::METHOD_NOT_FOUND_ERROR
571 })
572 }
573 },
574 Options::Leaf(opt) => {
575 let invocation = if opt.is_async {
576 quote! {
577 #fn_path(#(#param),*).await?
578 }
579 } else if opt.blocking.is_some() {
580 quote! {
581 ::rpc_toolkit::command_helpers::prelude::spawn_blocking(move || #fn_path(#(#param),*)).await?
582 }
583 } else {
584 quote! {
585 #fn_path(#(#param),*)?
586 }
587 };
588 quote! {
589 #param_struct_def
590
591 pub async fn rpc_handler#generics(
592 ctx: GenericContext,
593 parent_data: #parent_data_ty,
594 request: &::rpc_toolkit::command_helpers::prelude::RequestParts,
595 response: &mut ::rpc_toolkit::command_helpers::prelude::ResponseParts,
596 method: &str,
597 args: Params#param_ty_generics,
598 ) -> Result<::rpc_toolkit::command_helpers::prelude::Value, ::rpc_toolkit::command_helpers::prelude::RpcError> {
599 if method.is_empty() {
600 Ok(::rpc_toolkit::command_helpers::prelude::to_value(#invocation)?)
601 } else {
602 Err(::rpc_toolkit::command_helpers::prelude::RpcError {
603 data: Some(method.into()),
604 ..::rpc_toolkit::command_helpers::prelude::yajrc::METHOD_NOT_FOUND_ERROR
605 })
606 }
607 }
608 }
609 }
610 Options::Parent(ParentOptions {
611 common,
612 subcommands,
613 self_impl,
614 }) => {
615 let cmd_preprocess = if common.is_async {
616 quote! {
617 let parent_data = #fn_path(#(#param),*).await?;
618 }
619 } else if common.blocking.is_some() {
620 quote! {
621 let parent_data = ::rpc_toolkit::command_helpers::prelude::spawn_blocking(move || #fn_path(#(#param),*)).await?;
622 }
623 } else {
624 quote! {
625 let parent_data = #fn_path(#(#param),*)?;
626 }
627 };
628 let subcmd_impl = subcommands.iter().map(|subcommand| {
629 let mut subcommand = subcommand.clone();
630 let mut rpc_handler = PathSegment {
631 ident: Ident::new("rpc_handler", Span::call_site()),
632 arguments: std::mem::replace(
633 &mut subcommand.segments.last_mut().unwrap().arguments,
634 PathArguments::None,
635 ),
636 };
637 rpc_handler.arguments = match rpc_handler.arguments {
638 PathArguments::None => PathArguments::AngleBracketed(
639 syn::parse2(quote! { ::<GenericContext> })
640 .unwrap(),
641 ),
642 PathArguments::AngleBracketed(mut a) => {
643 a.args.push(syn::parse2(quote! { GenericContext }).unwrap());
644 PathArguments::AngleBracketed(a)
645 }
646 _ => unreachable!(),
647 };
648 quote_spanned!{ subcommand.span() =>
649 [#subcommand::NAME, rest] => #subcommand::#rpc_handler(ctx, parent_data, request, response, rest, ::rpc_toolkit::command_helpers::prelude::from_value(args.rest)?).await
650 }
651 });
652 let subcmd_impl = quote! {
653 match method.splitn(2, ".").chain(std::iter::repeat("")).take(2).collect::<Vec<_>>().as_slice() {
654 #(
655 #subcmd_impl,
656 )*
657 _ => Err(::rpc_toolkit::command_helpers::prelude::RpcError {
658 data: Some(method.into()),
659 ..::rpc_toolkit::command_helpers::prelude::yajrc::METHOD_NOT_FOUND_ERROR
660 })
661 }
662 };
663 match self_impl {
664 Some(self_impl) if !matches!(common.exec_ctx, ExecutionContext::CliOnly(_)) => {
665 let self_impl_fn = &self_impl.path;
666 let self_impl = if self_impl.is_async {
667 quote_spanned! { self_impl_fn.span() =>
668 #self_impl_fn(Into::into(ctx), parent_data).await?
669 }
670 } else if self_impl.blocking {
671 quote_spanned! { self_impl_fn.span() =>
672 {
673 let ctx = Into::into(ctx);
674 ::rpc_toolkit::command_helpers::prelude::spawn_blocking(move || #self_impl_fn(ctx, parent_data)).await?
675 }
676 }
677 } else {
678 quote_spanned! { self_impl_fn.span() =>
679 #self_impl_fn(Into::into(ctx), parent_data)?
680 }
681 };
682 quote! {
683 #param_struct_def
684
685 pub async fn rpc_handler#generics(
686 ctx: GenericContext,
687 parent_data: #parent_data_ty,
688 request: &::rpc_toolkit::command_helpers::prelude::RequestParts,
689 response: &mut ::rpc_toolkit::command_helpers::prelude::ResponseParts,
690 method: &str,
691 args: Params#param_ty_generics,
692 ) -> Result<::rpc_toolkit::command_helpers::prelude::Value, ::rpc_toolkit::command_helpers::prelude::RpcError> {
693 #cmd_preprocess
694
695 if method.is_empty() {
696 Ok(::rpc_toolkit::command_helpers::prelude::to_value(&#self_impl)?)
697 } else {
698 #subcmd_impl
699 }
700 }
701 }
702 }
703 _ => {
704 quote! {
705 #param_struct_def
706
707 pub async fn rpc_handler#generics(
708 ctx: GenericContext,
709 parent_data: #parent_data_ty,
710 request: &::rpc_toolkit::command_helpers::prelude::RequestParts,
711 response: &mut ::rpc_toolkit::command_helpers::prelude::ResponseParts,
712 method: &str,
713 args: Params#param_ty_generics,
714 ) -> Result<::rpc_toolkit::command_helpers::prelude::Value, ::rpc_toolkit::command_helpers::prelude::RpcError> {
715 #cmd_preprocess
716
717 #subcmd_impl
718 }
719 }
720 }
721 }
722 }
723 }
724}
725
726fn cli_handler(
727 fn_name: &Ident,
728 fn_generics: &Generics,
729 opt: &mut Options,
730 params: &[ParamType],
731) -> TokenStream {
732 let mut parent_data_ty = quote! { () };
733 let mut generics = fn_generics.clone();
734 generics.params.push(macro_try!(syn::parse2(
735 quote! { ParentParams: ::rpc_toolkit::command_helpers::prelude::Serialize }
736 )));
737 generics.params.push(macro_try!(syn::parse2(
738 quote! { GenericContext: CommandContextCli }
739 )));
740 if generics.lt_token.is_none() {
741 generics.lt_token = Some(Default::default());
742 }
743 if generics.gt_token.is_none() {
744 generics.gt_token = Some(Default::default());
745 }
746 let (_, fn_type_generics, _) = fn_generics.split_for_impl();
747 let fn_turbofish = fn_type_generics.as_turbofish();
748 let fn_path: Path = macro_try!(syn::parse2(quote! { super::#fn_name#fn_turbofish }));
749 let is_parent = matches!(opt, Options::Parent { .. });
750 let param: Vec<_> = params
751 .iter()
752 .map(|param| match param {
753 ParamType::Arg(arg) => {
754 let name = arg.name.clone().unwrap();
755 let field_name = Ident::new(&format!("arg_{}", name), name.span());
756 quote! { params.#field_name.clone() }
757 }
758 ParamType::Context(ty) => {
759 if is_parent {
760 quote! { <GenericContext as Into<#ty>>::into(ctx.clone()) }
761 } else {
762 quote! { <GenericContext as Into<#ty>>::into(ctx) }
763 }
764 }
765 ParamType::ParentData(ty) => {
766 parent_data_ty = quote! { #ty };
767 quote! { parent_data }
768 }
769 ParamType::Request => quote! { request },
770 ParamType::Response => quote! { response },
771 ParamType::None => unreachable!(),
772 })
773 .collect();
774 let mut param_generics_filter = GenericFilter::new(fn_generics);
775 for param in params {
776 if let ParamType::Arg(a) = param {
777 param_generics_filter.fold_type(a.ty.clone());
778 }
779 }
780 let mut param_generics = param_generics_filter.finish();
781 param_generics.params.push(macro_try!(syn::parse2(quote! {
782 ParentParams: ::rpc_toolkit::command_helpers::prelude::Serialize
783 })));
784 if param_generics.lt_token.is_none() {
785 param_generics.lt_token = Some(Default::default());
786 }
787 if param_generics.gt_token.is_none() {
788 param_generics.gt_token = Some(Default::default());
789 }
790 let (_, param_ty_generics, _) = param_generics.split_for_impl();
791 let mut arg_def = Vec::new();
792 for param in params {
793 match param {
794 ParamType::Arg(arg) => {
795 let name = arg.name.clone().unwrap();
796 let rename = arg
797 .rename
798 .clone()
799 .unwrap_or_else(|| LitStr::new(&name.to_string(), name.span()));
800 let field_name = Ident::new(&format!("arg_{}", name), name.span());
801 let ty = arg.ty.clone();
802 arg_def.push(quote! {
803 #[serde(rename = #rename)]
804 #field_name: #ty,
805 })
806 }
807 _ => (),
808 }
809 }
810 let arg = params
811 .iter()
812 .filter_map(|param| {
813 if let ParamType::Arg(a) = param {
814 Some(a)
815 } else {
816 None
817 }
818 })
819 .map(|arg| {
820 let name = arg.name.clone().unwrap();
821 let arg_name = arg.rename.clone().unwrap_or_else(|| LitStr::new(&name.to_string(), name.span()));
822 let field_name = Ident::new(&format!("arg_{}", name), name.span());
823 if arg.stdin.is_some() {
824 if let Some(parse) = &arg.parse {
825 quote! {
826 #field_name: #parse(&mut std::io::stdin(), matches)?,
827 }
828 } else {
829 quote! {
830 #field_name: ::rpc_toolkit::command_helpers::prelude::default_stdin_parser(&mut std::io::stdin(), matches)?,
831 }
832 }
833 } else if arg.check_is_present {
834 quote! {
835 #field_name: matches.is_present(#arg_name),
836 }
837 } else if arg.count.is_some() {
838 quote! {
839 #field_name: matches.occurrences_of(#arg_name),
840 }
841 } else {
842 let parse_val = if let Some(parse) = &arg.parse {
843 quote! {
844 #parse(arg_val, matches)
845 }
846 } else {
847 quote! {
848 ::rpc_toolkit::command_helpers::prelude::default_arg_parser(arg_val, matches)
849 }
850 };
851 if arg.optional {
852 quote! {
853 #field_name: if let Some(arg_val) = matches.value_of(#arg_name) {
854 Some(#parse_val?)
855 } else {
856 None
857 },
858 }
859 } else if let Some(default) = &arg.default {
860 if let Some(default) = default {
861 let path: Path = match syn::parse_str(&default.value()) {
862 Ok(a) => a,
863 Err(e) => return e.into_compile_error(),
864 };
865 quote! {
866 #field_name: if let Some(arg_val) = matches.value_of(#arg_name) {
867 #parse_val?
868 } else {
869 #path()
870 },
871 }
872 } else {
873 quote! {
874 #field_name: if let Some(arg_val) = matches.value_of(#arg_name) {
875 #parse_val?
876 } else {
877 Default::default()
878 },
879 }
880 }
881 } else if arg.multiple.is_some() {
882 quote! {
883 #field_name: matches.values_of(#arg_name).iter().flatten().map(|arg_val| #parse_val).collect::<Result<_, _>>()?,
884 }
885 } else {
886 quote! {
887 #field_name: {
888 let arg_val = matches.value_of(#arg_name).unwrap();
889 #parse_val?
890 },
891 }
892 }
893 }
894 });
895 let param_struct_def = quote! {
896 #[derive(::rpc_toolkit::command_helpers::prelude::Serialize)]
897 struct Params#param_ty_generics {
898 #(
899 #arg_def
900 )*
901 #[serde(flatten)]
902 rest: ParentParams,
903 }
904 let params: Params#param_ty_generics = Params {
905 #(
906 #arg
907 )*
908 rest: parent_params,
909 };
910 };
911 let create_rt = quote! {
912 let rt_ref = if let Some(rt) = rt.as_mut() {
913 &*rt
914 } else {
915 rt = Some(::rpc_toolkit::command_helpers::prelude::Runtime::new().map_err(|e| ::rpc_toolkit::command_helpers::prelude::RpcError {
916 data: Some(format!("{}", e).into()),
917 ..::rpc_toolkit::command_helpers::prelude::yajrc::INTERNAL_ERROR
918 })?);
919 rt.as_ref().unwrap()
920 };
921 };
922 let display = if let Some(display) = &opt.common().display {
923 quote! { #display }
924 } else {
925 quote! { ::rpc_toolkit::command_helpers::prelude::default_display }
926 };
927 match opt {
928 Options::Leaf(opt) if matches!(opt.exec_ctx, ExecutionContext::RpcOnly(_)) => quote! {
929 pub fn cli_handler#generics(
930 _ctx: GenericContext,
931 _parent_data: #parent_data_ty,
932 _rt: Option<::rpc_toolkit::command_helpers::prelude::Runtime>,
933 _matches: &::rpc_toolkit::command_helpers::prelude::ArgMatches,
934 method: ::rpc_toolkit::command_helpers::prelude::Cow<'_, str>,
935 _parent_params: ParentParams,
936 ) -> Result<(), ::rpc_toolkit::command_helpers::prelude::RpcError> {
937 Err(::rpc_toolkit::command_helpers::prelude::RpcError {
938 data: Some(method.into()),
939 ..::rpc_toolkit::command_helpers::prelude::yajrc::METHOD_NOT_FOUND_ERROR
940 })
941 }
942 },
943 Options::Leaf(opt) if matches!(opt.exec_ctx, ExecutionContext::Standard) => {
944 let param = param.into_iter().map(|_| quote! { unreachable!() });
945 let invocation = if opt.is_async {
946 quote! {
947 rt_ref.block_on(#fn_path(#(#param),*))?
948 }
949 } else {
950 quote! {
951 #fn_path(#(#param),*)?
952 }
953 };
954 quote! {
955 pub fn cli_handler#generics(
956 ctx: GenericContext,
957 parent_data: #parent_data_ty,
958 mut rt: Option<::rpc_toolkit::command_helpers::prelude::Runtime>,
959 matches: &::rpc_toolkit::command_helpers::prelude::ArgMatches,
960 method: ::rpc_toolkit::command_helpers::prelude::Cow<'_, str>,
961 parent_params: ParentParams,
962 ) -> Result<(), ::rpc_toolkit::command_helpers::prelude::RpcError> {
963 #param_struct_def
964
965 #create_rt
966
967 #[allow(unreachable_code)]
968 let return_ty = if true {
969 ::rpc_toolkit::command_helpers::prelude::PhantomData
970 } else {
971 let ctx_new = unreachable!();
972 ::rpc_toolkit::command_helpers::prelude::match_types(&ctx, &ctx_new);
973 let ctx = ctx_new;
974 ::rpc_toolkit::command_helpers::prelude::make_phantom(#invocation)
975 };
976
977 let res = rt_ref.block_on(::rpc_toolkit::command_helpers::prelude::call_remote(ctx, method.as_ref(), params, return_ty))?;
978 Ok(#display(res.result?, matches))
979 }
980 }
981 }
982 Options::Leaf(opt) => {
983 if let ExecutionContext::CustomCli {
984 ref cli, is_async, ..
985 } = opt.exec_ctx
986 {
987 let fn_path = cli;
988 let cli_param = params.iter().filter_map(|param| match param {
989 ParamType::Arg(arg) => {
990 let name = arg.name.clone().unwrap();
991 let field_name = Ident::new(&format!("arg_{}", name), name.span());
992 Some(quote! { params.#field_name.clone() })
993 }
994 ParamType::Context(_) => Some(quote! { Into::into(ctx) }),
995 ParamType::ParentData(_) => Some(quote! { parent_data }),
996 ParamType::Request => None,
997 ParamType::Response => None,
998 ParamType::None => unreachable!(),
999 });
1000 let invocation = if is_async {
1001 quote! {
1002 rt_ref.block_on(#fn_path(#(#cli_param),*))?
1003 }
1004 } else {
1005 quote! {
1006 #fn_path(#(#cli_param),*)?
1007 }
1008 };
1009 let display_res = if let Some(display_fn) = &opt.display {
1010 quote! {
1011 #display_fn(#invocation, matches)
1012 }
1013 } else {
1014 quote! {
1015 ::rpc_toolkit::command_helpers::prelude::default_display(#invocation, matches)
1016 }
1017 };
1018 let rt_action = if is_async {
1019 create_rt
1020 } else {
1021 quote! {
1022 drop(rt);
1023 }
1024 };
1025 quote! {
1026 pub fn cli_handler#generics(
1027 ctx: GenericContext,
1028 parent_data: #parent_data_ty,
1029 mut rt: Option<::rpc_toolkit::command_helpers::prelude::Runtime>,
1030 matches: &::rpc_toolkit::command_helpers::prelude::ArgMatches,
1031 _method: ::rpc_toolkit::command_helpers::prelude::Cow<'_, str>,
1032 parent_params: ParentParams
1033 ) -> Result<(), ::rpc_toolkit::command_helpers::prelude::RpcError> {
1034 #param_struct_def
1035
1036 #rt_action
1037
1038 Ok(#display_res)
1039 }
1040 }
1041 } else {
1042 let invocation = if opt.is_async {
1043 quote! {
1044 rt_ref.block_on(#fn_path(#(#param),*))?
1045 }
1046 } else {
1047 quote! {
1048 #fn_path(#(#param),*)?
1049 }
1050 };
1051 let display_res = if let Some(display_fn) = &opt.display {
1052 quote! {
1053 #display_fn(#invocation, matches)
1054 }
1055 } else {
1056 quote! {
1057 ::rpc_toolkit::command_helpers::prelude::default_display(#invocation, matches)
1058 }
1059 };
1060 let rt_action = if opt.is_async {
1061 create_rt
1062 } else {
1063 quote! {
1064 drop(rt);
1065 }
1066 };
1067 quote! {
1068 pub fn cli_handler#generics(
1069 ctx: GenericContext,
1070 parent_data: #parent_data_ty,
1071 mut rt: Option<::rpc_toolkit::command_helpers::prelude::Runtime>,
1072 matches: &::rpc_toolkit::command_helpers::prelude::ArgMatches,
1073 _method: ::rpc_toolkit::command_helpers::prelude::Cow<'_, str>,
1074 parent_params: ParentParams
1075 ) -> Result<(), ::rpc_toolkit::command_helpers::prelude::RpcError> {
1076 #param_struct_def
1077
1078 #rt_action
1079
1080 Ok(#display_res)
1081 }
1082 }
1083 }
1084 }
1085 Options::Parent(ParentOptions {
1086 common,
1087 subcommands,
1088 self_impl,
1089 }) => {
1090 let cmd_preprocess = if common.is_async {
1091 quote! {
1092 #create_rt
1093 let parent_data = rt_ref.block_on(#fn_path(#(#param),*))?;
1094 }
1095 } else {
1096 quote! {
1097 let parent_data = #fn_path(#(#param),*)?;
1098 }
1099 };
1100 let subcmd_impl = subcommands.iter().map(|subcommand| {
1101 let mut subcommand = subcommand.clone();
1102 let mut cli_handler = PathSegment {
1103 ident: Ident::new("cli_handler", Span::call_site()),
1104 arguments: std::mem::replace(
1105 &mut subcommand.segments.last_mut().unwrap().arguments,
1106 PathArguments::None,
1107 ),
1108 };
1109 cli_handler.arguments = match cli_handler.arguments {
1110 PathArguments::None => PathArguments::AngleBracketed(
1111 syn::parse2(quote! { ::<Params#param_ty_generics, GenericContext> })
1112 .unwrap(),
1113 ),
1114 PathArguments::AngleBracketed(mut a) => {
1115 a.args
1116 .push(syn::parse2(quote! { Params#param_ty_generics }).unwrap());
1117 a.args.push(syn::parse2(quote! { GenericContext }).unwrap());
1118 PathArguments::AngleBracketed(a)
1119 }
1120 _ => unreachable!(),
1121 };
1122 quote_spanned! { subcommand.span() =>
1123 Some((#subcommand::NAME, sub_m)) => {
1124 let method = if method.is_empty() {
1125 #subcommand::NAME.into()
1126 } else {
1127 method + "." + #subcommand::NAME
1128 };
1129 #subcommand::#cli_handler(ctx, parent_data, rt, sub_m, method, params)
1130 },
1131 }
1132 });
1133 let self_impl = match (self_impl, &common.exec_ctx) {
1134 (Some(self_impl), ExecutionContext::CliOnly(_))
1135 | (Some(self_impl), ExecutionContext::Local(_))
1136 | (Some(self_impl), ExecutionContext::CustomCli { .. }) => {
1137 let (self_impl_fn, is_async) =
1138 if let ExecutionContext::CustomCli { cli, is_async, .. } = &common.exec_ctx
1139 {
1140 (cli, *is_async)
1141 } else {
1142 (&self_impl.path, self_impl.is_async)
1143 };
1144 let create_rt = if common.is_async {
1145 None
1146 } else {
1147 Some(create_rt)
1148 };
1149 let self_impl = if is_async {
1150 quote_spanned! { self_impl_fn.span() =>
1151 #create_rt
1152 rt_ref.block_on(#self_impl_fn(Into::into(ctx), parent_data))?
1153 }
1154 } else {
1155 quote_spanned! { self_impl_fn.span() =>
1156 #self_impl_fn(Into::into(ctx), parent_data)?
1157 }
1158 };
1159 quote! {
1160 Ok(#display(#self_impl, matches)),
1161 }
1162 }
1163 (Some(self_impl), ExecutionContext::Standard) => {
1164 let self_impl_fn = &self_impl.path;
1165 let self_impl = if self_impl.is_async {
1166 quote! {
1167 rt_ref.block_on(#self_impl_fn(unreachable!(), parent_data))
1168 }
1169 } else {
1170 quote! {
1171 #self_impl_fn(unreachable!(), parent_data)
1172 }
1173 };
1174 let create_rt = if common.is_async {
1175 None
1176 } else {
1177 Some(create_rt)
1178 };
1179 quote! {
1180 {
1181 #create_rt
1182
1183 #[allow(unreachable_code)]
1184 let return_ty = if true {
1185 ::rpc_toolkit::command_helpers::prelude::PhantomData
1186 } else {
1187 ::rpc_toolkit::command_helpers::prelude::make_phantom(#self_impl?)
1188 };
1189
1190 let res = rt_ref.block_on(::rpc_toolkit::command_helpers::prelude::call_remote(ctx, method.as_ref(), params, return_ty))?;
1191 Ok(#display(res.result?, matches))
1192 }
1193 }
1194 }
1195 (None, _) | (Some(_), ExecutionContext::RpcOnly(_)) => quote! {
1196 Err(::rpc_toolkit::command_helpers::prelude::RpcError {
1197 data: Some(method.into()),
1198 ..::rpc_toolkit::command_helpers::prelude::yajrc::METHOD_NOT_FOUND_ERROR
1199 }),
1200 },
1201 };
1202 quote! {
1203 pub fn cli_handler#generics(
1204 ctx: GenericContext,
1205 parent_data: #parent_data_ty,
1206 mut rt: Option<::rpc_toolkit::command_helpers::prelude::Runtime>,
1207 matches: &::rpc_toolkit::command_helpers::prelude::ArgMatches,
1208 method: ::rpc_toolkit::command_helpers::prelude::Cow<'_, str>,
1209 parent_params: ParentParams,
1210 ) -> Result<(), ::rpc_toolkit::command_helpers::prelude::RpcError> {
1211 #param_struct_def
1212
1213 #cmd_preprocess
1214
1215 match matches.subcommand() {
1216 #(
1217 #subcmd_impl
1218 )*
1219 _ => #self_impl
1220 }
1221 }
1222 }
1223 }
1224 }
1225}
1226
1227pub fn build(args: AttributeArgs, mut item: ItemFn) -> TokenStream {
1228 let mut params = macro_try!(parse_param_attrs(&mut item));
1229 let mut opt = macro_try!(parse_command_attr(args));
1230 if let Some(a) = &opt.common().blocking {
1231 if item.sig.asyncness.is_some() {
1232 return Error::new(a.span(), "cannot use `blocking` on an async fn").to_compile_error();
1233 }
1234 }
1235 opt.common().is_async = item.sig.asyncness.is_some();
1236 let fn_vis = &item.vis;
1237 let fn_name = &item.sig.ident;
1238 let fn_generics = &item.sig.generics;
1239 let command_name_str = opt
1240 .common()
1241 .rename
1242 .clone()
1243 .unwrap_or_else(|| LitStr::new(&fn_name.to_string(), fn_name.span()));
1244 let is_async = LitBool::new(
1245 opt.common().is_async,
1246 item.sig
1247 .asyncness
1248 .map(|a| a.span())
1249 .unwrap_or_else(Span::call_site),
1250 );
1251 let ctx_ty = params.iter().find_map(|a| {
1252 if let ParamType::Context(ty) = a {
1253 Some(ty.clone())
1254 } else {
1255 None
1256 }
1257 });
1258 let ctx_trait = ctx_trait(ctx_ty, &mut opt);
1259 let metadata = metadata(&mut opt);
1260 let build_app = build_app(command_name_str.clone(), &mut opt, &mut params);
1261 let rpc_handler = rpc_handler(fn_name, fn_generics, &opt, ¶ms);
1262 let cli_handler = cli_handler(fn_name, fn_generics, &mut opt, ¶ms);
1263
1264 let res = quote! {
1265 #item
1266 #fn_vis mod #fn_name {
1267 use super::*;
1268
1269 pub const NAME: &'static str = #command_name_str;
1270 pub const ASYNC: bool = #is_async;
1271
1272 #ctx_trait
1273
1274 #metadata
1275
1276 #build_app
1277
1278 #rpc_handler
1279
1280 #cli_handler
1281 }
1282 };
1283 if opt.common().macro_debug {
1284 panic!("EXPANDED MACRO:\n{}", res);
1285 }
1286 res
1287}