1extern crate proc_macro;
2
3use std::collections::HashMap;
4
5use convert_case::{Case, Casing};
6use proc_macro::{TokenStream, TokenTree};
7use proc_macro_error::{abort, proc_macro_error};
8use quote::quote;
9use syn::{FnArg, parse_macro_input, spanned::Spanned};
10
11macro_rules! emit {
14 ($tokens:expr) => {{
15 use proc_macro2_diagnostics::SpanDiagnosticExt;
16 let mut tokens = $tokens;
17 if std::env::var_os("RUNBOT_CODEGEN_DEBUG").is_some() {
18 let debug_tokens = proc_macro2::Span::call_site()
19 .note("emitting RUNBOT_CODEGEN_DEBUG code generation debug output")
20 .note(tokens.to_string())
21 .emit_as_item_tokens();
22 tokens.extend(debug_tokens);
23 }
24 tokens.into()
25 }};
26}
27
28#[derive(Default, Debug)]
29struct ProcessorAttributes {
30 command: Option<syn::LitStr>,
31}
32
33impl ProcessorAttributes {
34 fn parse(&mut self, meta: syn::meta::ParseNestedMeta) -> syn::Result<()> {
35 if meta.path.is_ident("command") {
36 self.command = Some(meta.value()?.parse()?);
37 Ok(())
38 } else {
39 Ok(())
40 }
41 }
42}
43
44#[proc_macro_error]
45#[proc_macro_attribute]
46pub fn processor(args: TokenStream, input: TokenStream) -> TokenStream {
47 let mut attrs = ProcessorAttributes::default();
48 let command_parser = syn::meta::parser(|meta| attrs.parse(meta));
49 parse_macro_input!(args with command_parser);
50 if attrs.command.is_some() {
51 command_processor(attrs.command.unwrap(), input)
52 } else {
53 normal_processor(input)
54 }
55}
56
57fn normal_processor(input: TokenStream) -> TokenStream {
58 let method = parse_macro_input!(input as syn::ItemFn);
59 let method_clone = method.clone();
60 if method.sig.asyncness.is_none() {
61 abort!(&method.sig.span(), "method must be async");
62 }
63
64 let sig_params = &method.sig.inputs;
68 if sig_params.len() != 2 {
69 abort!(&method.sig.span(), "method must have 2 parameters");
70 }
71
72 let first_param = &sig_params[0];
74 let t = match first_param {
75 FnArg::Receiver(_) => {
76 abort!(&first_param.span(), "first parameter must be a parameter");
77 }
78 FnArg::Typed(t) => t,
79 };
80 let first_param_type = &t.ty;
81 if first_param_type != &syn::parse_quote!(Arc<BotContext>) {
82 abort!(
83 &first_param.span(),
84 "first parameter must be Arc<BotContext>"
85 );
86 }
87
88 let second_param = &sig_params[1];
90 let second_param_type = match second_param {
91 FnArg::Receiver(_) => {
92 abort!(&second_param.span(), "second parameter must be a parameter");
93 }
94 FnArg::Typed(t) => t,
95 };
96 let second_param_type = &second_param_type.ty;
97
98 let (trait_name, trait_fn_name, processor_type) =
99 if second_param_type == &syn::parse_quote!(&Message) {
100 (
101 quote! {MessageProcessor},
102 quote! {process_message},
103 quote! {Message},
104 )
105 } else if second_param_type == &syn::parse_quote!(&Notice) {
106 (
107 quote! {NoticeProcessor},
108 quote! {process_notice},
109 quote! {Notice},
110 )
111 } else if second_param_type == &syn::parse_quote!(&Request) {
112 (
113 quote! {RequestProcessor},
114 quote! {process_request},
115 quote! {Request},
116 )
117 } else if second_param_type == &syn::parse_quote!(&Post) {
118 (quote! {PostProcessor}, quote! {process_post}, quote! {Post})
119 } else {
120 abort!(
121 &second_param.span(),
122 "second parameter must be &Message or &Notice or &Request or &Post"
123 );
124 };
125
126 let vis = method.vis;
127 let asyncness = method.sig.asyncness;
128 let fn_name = method.sig.ident.clone();
129 let return_type = &method.sig.output;
130 let struct_name = &fn_name.to_string().to_case(Case::UpperCamel);
131 let struct_name = proc_macro2::Ident::new(&struct_name, proc_macro2::Span::call_site());
132 let static_name = &fn_name.to_string().to_case(Case::UpperSnake);
133 let static_name = proc_macro2::Ident::new(&static_name, proc_macro2::Span::call_site());
134 let first_param_ident = match first_param {
135 FnArg::Typed(t) => match &*t.pat {
136 syn::Pat::Ident(ident) => ident.ident.clone(),
137 _ => abort!(&t.pat, "first parameter must be a parameter"),
138 },
139 _ => abort!(&first_param.span(), "first parameter must be a parameter"),
140 };
141 let second_param_ident = match second_param {
142 FnArg::Typed(t) => match &*t.pat {
143 syn::Pat::Ident(ident) => ident.ident.clone(),
144 _ => abort!(&t.pat, "second parameter must be a parameter"),
145 },
146 _ => abort!(&second_param.span(), "second parameter must be a parameter"),
147 };
148 emit!(quote::quote! {
149 #[derive(Copy, Clone, Default, Debug)]
150 #vis struct #struct_name;
151
152 #[::runbot::re_export::async_trait::async_trait]
153 impl #trait_name for #struct_name {
154 fn id(&self) -> &'static str {
155 concat!(
156 env!("CARGO_PKG_NAME"),
157 "::",
158 module_path!(),
159 "::",
160 stringify!(#fn_name)
161 )
162 }
163
164 #asyncness fn #trait_fn_name(&self, #first_param, #second_param) #return_type {
165 #fn_name(#first_param_ident, #second_param_ident).await
166 }
167 }
168
169 #vis static #static_name: #struct_name = #struct_name;
170
171 #method_clone
172
173 impl Into<Processor> for #struct_name {
174 fn into(self) -> Processor {
175 Processor::#processor_type(Box::new(self))
176 }
177 }
178 })
179}
180
181#[proc_macro_derive(ParseJson)]
182pub fn parse_json_derive(input: TokenStream) -> TokenStream {
183 let input = parse_macro_input!(input as syn::DeriveInput);
184 let name = &input.ident;
185
186 let expanded = quote! {
187 impl #name {
188 pub fn parse(json: &serde_json::Value) -> Result<Self> {
189 Ok(serde_json::from_value(json.clone())?)
190 }
191 }
192 };
193
194 TokenStream::from(expanded)
195}
196
197#[proc_macro_derive(UnknownTypeSerde)]
198pub fn notice_type_serde(input: TokenStream) -> TokenStream {
199 let input = parse_macro_input!(input as syn::DeriveInput);
200 let name = &input.ident;
201
202 let variants = match input.data {
203 syn::Data::Enum(syn::DataEnum { ref variants, .. }) => variants,
204 _ => panic!("NoticeTypeSerde can only be used with enums"),
205 };
206
207 let match_arms_ser = variants.iter().map(|v| {
208 let ident = &v.ident;
209 let ser_string = ident.to_string().to_case(Case::Snake);
210 if ident == "Unknown" {
211 quote! { #name::Unknown(s) => serializer.serialize_str(s), }
212 } else {
213 quote! { #name::#ident => serializer.serialize_str(#ser_string), }
214 }
215 });
216
217 let match_arms_de = variants.iter().map(|v| {
218 let ident = &v.ident;
219 let de_string = ident.to_string().to_case(Case::Snake);
220 if ident == "Unknown" {
221 quote! { other => Ok(#name::Unknown(other.to_string())), }
222 } else {
223 quote! { #de_string => Ok(#name::#ident), }
224 }
225 });
226
227 let r#gen = quote! {
228 impl serde::Serialize for #name {
229 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
230 where S: serde::Serializer {
231 match self {
232 #(#match_arms_ser)*
233 }
234 }
235 }
236 impl<'de> serde::Deserialize<'de> for #name {
237 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
238 where D: serde::Deserializer<'de> {
239 let s = String::deserialize(deserializer)?;
240 match s.as_str() {
241 #(#match_arms_de)*
242 }
243 }
244 }
245 };
246 emit!(r#gen)
247}
248
249#[proc_macro_derive(UnknownEnumSerdeAndParse, attributes(enum_field))]
250pub fn unknown_enum_serde_and_parse(input: TokenStream) -> TokenStream {
251 let input = parse_macro_input!(input as syn::DeriveInput);
253 let enum_name = &input.ident;
254
255 let mut field_name = Option::<String>::None;
257
258 for attr in &input.attrs {
260 if attr.path().is_ident("enum_field") {
261 if let Ok(nested) = attr.parse_args_with(|input: syn::parse::ParseStream| {
262 syn::punctuated::Punctuated::<darling::ast::NestedMeta, syn::Token![,]>::parse_terminated(input)
263 }) {
264 for meta in &nested {
265 if let darling::ast::NestedMeta::Meta(syn::Meta::NameValue(nv)) = meta {
266 if nv.path.is_ident("name") {
267 if let syn::Expr::Lit(expr_lit) = &nv.value {
268 if let syn::Lit::Str(ref s) = expr_lit.lit {
269 field_name = Some(s.value());
270 }
271 }
272 }
273 }
274 }
275 }
276 }
277 }
278
279 let field_name = if let Some(field_name) = field_name {
280 field_name
281 } else {
282 abort!(&input.span(), "enum_field not define");
283 };
284
285 let variants = match &input.data {
287 syn::Data::Enum(syn::DataEnum { variants, .. }) => variants,
288 _ => panic!("ParseJson only supports enums"),
289 };
290
291 let mut arms = Vec::new();
292 let mut unknown_arm = None;
293
294 for variant in variants {
295 let ident = &variant.ident;
296 match &variant.fields {
297 syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
298 let ty = &fields.unnamed.first().unwrap().ty;
299 let is_unknown =
301 ident == "Unknown" && quote!(#ty).to_string().contains("serde_json :: Value");
302 if is_unknown {
303 unknown_arm = Some(quote! {
304 _ => Ok(#enum_name::Unknown(value.clone()))
305 });
306 } else {
307 let case_name = &ident.to_string().to_case(Case::Snake);
308 arms.push(quote! {
309 #case_name => Ok(#enum_name::#ident(<#ty>::parse(value)?))
310 });
311 }
312 }
313 _ => {}
314 }
315 }
316
317 let unknown_arm = unknown_arm.unwrap_or(quote! {
318 _ => Err(Error::FieldError("Unknown type".to_string()))
319 });
320
321 let r#gen = quote! {
322 impl #enum_name {
323 pub fn parse(value: &serde_json::Value) -> Result<Self> {
324 let request_type = value.get(#field_name)
325 .ok_or(Error::FieldError(format!("{} not found", #field_name)))?;
326 let request_type = request_type.as_str()
327 .ok_or(Error::FieldError(format!("{} not is str", #field_name)))?;
328 match request_type {
329 #(#arms,)*
330 #unknown_arm,
331 }
332 }
333 }
334 };
335 emit!(r#gen)
336}
337
338fn command_processor(bot_command_pattern_str: syn::LitStr, input: TokenStream) -> TokenStream {
339 let method = parse_macro_input!(input as syn::ItemFn);
341 let span = method.span();
342 let method_clone = method.clone();
343 let bot_command_pattern_str = bot_command_pattern_str.value();
345 if bot_command_pattern_str.is_empty() {
346 abort!(&span, "command pattern is empty");
347 }
348 let plain_text_regex =
349 regex::Regex::new(r#"^[A-Za-z0-9_/\p{Han}\p{Hiragana}\p{Katakana}]+$"#).unwrap();
350 let mut bot_command_items = vec![];
351 for fragment in bot_command_pattern_str.split_ascii_whitespace() {
352 if plain_text_regex.is_match(fragment) {
353 bot_command_items.push(BotCommandItem::PlainText(
354 false,
355 false,
356 false,
357 fragment.to_owned(),
358 ));
359 continue;
360 }
361 for item in parse_template(fragment) {
362 bot_command_items.push(item);
363 }
364 }
365 if !is_text_to_end_at_most_one_and_last(&bot_command_items) {
366 abort!(&method.span(), "text to end must be at most one and last");
367 }
368
369 if method.sig.asyncness.is_none() {
371 abort!(&method.sig.span(), "method must be async");
372 }
373 let sig_params = &method.sig.inputs;
374 if sig_params.len() < 2 {
375 abort!(
376 &method.sig.span(),
377 "method must have 2 parameters Arc<BotContext>,&Message"
378 );
379 }
380 let first_param = &sig_params[0];
381 let first_param_type = match first_param {
382 FnArg::Receiver(_) => {
383 abort!(&first_param.span(), "first parameter must be a parameter");
384 }
385 FnArg::Typed(t) => t,
386 };
387 let first_param_type = &first_param_type.ty;
388 if first_param_type != &syn::parse_quote!(Arc<BotContext>) {
389 abort!(
390 &first_param.span(),
391 "first parameter must be Arc<BotContext>"
392 );
393 }
394 let second_param = &sig_params[1];
395 let second_param_type = match second_param {
396 FnArg::Receiver(_) => {
397 abort!(&second_param.span(), "second parameter must be a parameter");
398 }
399 FnArg::Typed(t) => t,
400 };
401 let second_param_type = &second_param_type.ty;
402 if second_param_type != &syn::parse_quote!(&Message) {
403 abort!(&second_param.span(), "second parameter must be &Message");
404 }
405
406 let paramed_bot_command_items = bot_command_items
407 .iter()
408 .filter(|item| match item {
409 BotCommandItem::NumberParam(_, _, _, _) => true,
410 BotCommandItem::TextToSpaceParam(_, _, _, _) => true,
411 BotCommandItem::EnumParam(_, _, _, _, _) => true,
412 BotCommandItem::TextToEnd(_, _, _, _) => true,
413 _ => false,
414 })
415 .collect::<Vec<_>>();
416 if sig_params.len() != paramed_bot_command_items.len() + 2 {
417 abort!(
418 &method.sig.span(),
419 "method must have {} parameters, but got {}",
420 paramed_bot_command_items.len() + 2,
421 sig_params.len()
422 );
423 }
424
425 for i in 0..paramed_bot_command_items.len() {
426 let param_name_command_item = match paramed_bot_command_items[i] {
427 BotCommandItem::NumberParam(_, _, _, name) => name,
428 BotCommandItem::TextToSpaceParam(_, _, _, name) => name,
429 BotCommandItem::EnumParam(_, _, _, name, _) => name,
430 BotCommandItem::TextToEnd(_, _, _, name) => name,
431 _ => continue,
432 };
433 let param_name_sig_param = match &sig_params[i + 2] {
434 FnArg::Typed(t) => match &*t.pat {
435 syn::Pat::Ident(ident) => ident.ident.to_string(),
436 _ => continue,
437 },
438 _ => continue,
439 };
440 if param_name_command_item != ¶m_name_sig_param {
441 abort!(
442 &method.sig.span(),
443 "param name mismatch, command item: {}, sig param: {}",
444 param_name_command_item,
445 param_name_sig_param
446 );
447 }
448
449 let type_is_option_command_item = match paramed_bot_command_items[i] {
450 BotCommandItem::NumberParam(optional, _, _, _) => optional,
451 BotCommandItem::TextToSpaceParam(optional, _, _, _) => optional,
452 BotCommandItem::EnumParam(optional, _, _, _, _) => optional,
453 BotCommandItem::TextToEnd(optional, _, _, _) => optional,
454 _ => continue,
455 };
456 let type_is_option_sig_param = match &sig_params[i + 2] {
457 FnArg::Typed(t) => {
458 if let syn::Type::Path(type_path) = &*t.ty {
460 if let Some(seg) = type_path.path.segments.last() {
461 seg.ident == "Option"
462 } else {
463 false
464 }
465 } else {
466 false
467 }
468 }
469 _ => continue,
470 };
471 if type_is_option_command_item != &type_is_option_sig_param {
472 abort!(
473 &method.sig.span(),
474 "param type option mismatch, command item: {}, sig param: {}",
475 param_name_command_item,
476 param_name_sig_param
477 );
478 }
479
480 let vec_type_command_item = match paramed_bot_command_items[i] {
481 BotCommandItem::NumberParam(_, a, b, _) => *a || *b,
482 BotCommandItem::Number(_, a, b) => *a || *b,
483 BotCommandItem::TextToSpace(_, a, b) => *a || *b,
484 BotCommandItem::TextToSpaceParam(_, a, b, _) => *a || *b,
485 BotCommandItem::PlainText(_, a, b, _) => *a || *b,
486 BotCommandItem::Enum(_, a, b, _) => *a || *b,
487 BotCommandItem::EnumParam(_, a, b, _, _) => *a || *b,
488 BotCommandItem::TextToEnd(_, a, b, _) => *a || *b,
489 };
490 let vec_type_sig_param = match &sig_params[i + 2] {
491 FnArg::Typed(t) => {
492 if let syn::Type::Path(type_path) = &*t.ty {
493 if let Some(seg) = type_path.path.segments.last() {
494 seg.ident == "Vec"
495 } else {
496 false
497 }
498 } else {
499 false
500 }
501 }
502 _ => continue,
503 };
504 if vec_type_command_item != vec_type_sig_param {
505 abort!(
506 &method.sig.span(),
507 "param type vec mismatch, command item: {}, sig param: {}",
508 param_name_command_item,
509 param_name_sig_param
510 );
511 }
512 }
513
514 let vis = method.vis;
515 let asyncness = method.sig.asyncness;
516 let fn_name = method.sig.ident.clone();
517 let return_type = &method.sig.output;
518 let struct_name = &fn_name.to_string().to_case(Case::UpperCamel);
519 let struct_name = proc_macro2::Ident::new(&struct_name, proc_macro2::Span::call_site());
520 let static_name = &fn_name.to_string().to_case(Case::UpperSnake);
521 let static_name = proc_macro2::Ident::new(&static_name, proc_macro2::Span::call_site());
522 let first_param_ident = match first_param {
523 FnArg::Typed(t) => match &*t.pat {
524 syn::Pat::Ident(ident) => ident.ident.clone(),
525 _ => abort!(&t.pat, "first parameter must be a parameter"),
526 },
527 _ => abort!(&first_param.span(), "first parameter must be a parameter"),
528 };
529 let second_param_ident = match second_param {
530 FnArg::Typed(t) => match &*t.pat {
531 syn::Pat::Ident(ident) => ident.ident.clone(),
532 _ => abort!(&t.pat, "second parameter must be a parameter"),
533 },
534 _ => abort!(&second_param.span(), "second parameter must be a parameter"),
535 };
536
537 let mut command_item_ident_stream = quote! {};
538 for item in paramed_bot_command_items {
539 let command_item_ident = match item {
540 BotCommandItem::NumberParam(_, _, _, name) => name,
541 BotCommandItem::TextToSpaceParam(_, _, _, name) => name,
542 BotCommandItem::EnumParam(_, _, _, name, _) => name,
543 BotCommandItem::TextToEnd(_, _, _, name) => name,
544 BotCommandItem::Number(_, _, _) => abort!(&span, "number param not support"),
545 BotCommandItem::PlainText(_, _, _, _) => abort!(&span, "plain text param not support"),
546 BotCommandItem::TextToSpace(_, _, _) => {
547 abort!(&span, "text to space param not support")
548 }
549 BotCommandItem::Enum(_, _, _, _) => abort!(&span, "enum param not support"),
550 };
551 let command_item_ident =
552 proc_macro2::Ident::new(&command_item_ident, proc_macro2::Span::call_site());
553
554 command_item_ident_stream.extend(quote::quote! {
555 , #command_item_ident
556 });
557 }
558
559 let define_command_lopper = quote::quote! {
560 let mut runbot_command_string_buffer = String::new();
561 for message_data in &message.message {
562 match message_data {
564 MessageData::Text(MessageText { text }) => {
565 if text.is_empty() {
566 continue;
567 }
568 if runbot_command_string_buffer.is_empty() {
569 runbot_command_string_buffer.push_str(text);
570 } else {
571 runbot_command_string_buffer.push(' ');
572 runbot_command_string_buffer.push_str(text);
573 }
574 }
575 MessageData::At(MessageAt { qq, .. }) => {
576 if runbot_command_string_buffer.is_empty() {
577 runbot_command_string_buffer.push_str(qq);
578 } else {
579 runbot_command_string_buffer.push(' ');
580 runbot_command_string_buffer.push_str(qq);
581 }
582 }
583 _ => return Ok(false),
584 }
585 }
586 let mut runbot_command_looper = ::runbot::command::CommandLopper::new(runbot_command_string_buffer.trim().split_ascii_whitespace().collect::<Vec<&str>>());
587 };
588
589 let mut define_lopper_value = quote::quote! {};
590
591 if std::env::var_os("RUNBOT_CODEGEN_DEBUG").is_some() {
592 eprintln!("bot_command_items : {:?}", bot_command_items)
593 }
594
595 for item in bot_command_items {
596 match item {
597 BotCommandItem::Number(optional, repat_less_one, repat_zero_or_more) => {
598 if optional {
599 define_lopper_value.extend(quote::quote! {
600 runbot_command_looper.next_number();
601 });
602 } else if repat_less_one {
603 define_lopper_value.extend(quote::quote! {
604 let runbot_command_number = runbot_command_looper.next_number();
605 if runbot_command_number.is_none() {
606 return Ok(false);
607 }
608 while let Some(_) = runbot_command_looper.next_number() {
609 continue;
610 }
611 });
612 } else if repat_zero_or_more {
613 define_lopper_value.extend(quote::quote! {
614 while let Some(_) = runbot_command_looper.next_number() {
615 continue;
616 }
617 });
618 } else {
619 define_lopper_value.extend(quote::quote! {
620 let runbot_command_number = runbot_command_looper.next_number();
621 if runbot_command_number.is_none() {
622 return Ok(false);
623 }
624 });
625 }
626 }
627 BotCommandItem::NumberParam(optional, repat_less_one, repat_zero_or_more, name) => {
628 let ident = proc_macro2::Ident::new(&name, proc_macro2::Span::call_site());
629 if optional {
630 define_lopper_value.extend(quote::quote! {
631 let runbot_command_number = runbot_command_looper.next_number();
632 let #ident = if let Some(number) = runbot_command_number {
633 if let Ok(p) = std::str::FromStr::from_str(number.as_str()) {
634 Some(p)
635 } else {
636 return Ok(false);
637 }
638 } else {
639 None
640 };
641 });
642 } else if repat_less_one {
643 define_lopper_value.extend(quote::quote! {
644 let mut runbot_command_numbers = Vec::new();
645 while let Some(number) = runbot_command_looper.next_number() {
646 if let Ok(p) = std::str::FromStr::from_str(number.as_str()) {
647 runbot_command_numbers.push(p);
648 } else {
649 return Ok(false);
650 }
651 }
652 if runbot_command_numbers.is_empty() {
653 return Ok(false);
654 }
655 let #ident = runbot_command_numbers;
656 })
657 } else if repat_zero_or_more {
658 define_lopper_value.extend(quote::quote! {
659 let mut runbot_command_numbers = Vec::new();
660 while let Some(number) = runbot_command_looper.next_number() {
661 if let Ok(p) = std::str::FromStr::from_str(number.as_str()) {
662 runbot_command_numbers.push(p);
663 } else {
664 return Ok(false);
665 }
666 }
667 let #ident = runbot_command_numbers;
668 });
669 } else {
670 define_lopper_value.extend(quote::quote! {
671 let runbot_command_number = runbot_command_looper.next_number();
672 if runbot_command_number.is_none() {
673 return Ok(false);
674 }
675 let #ident = if let Ok(p) = std::str::FromStr::from_str(runbot_command_number.unwrap().as_str()) {
676 p
677 } else {
678 return Ok(false);
679 };
680 });
681 }
682 }
683 BotCommandItem::PlainText(optional, repat_less_one, repat_zero_or_more, text) => {
684 if optional {
685 define_lopper_value.extend(quote::quote! {
686 runbot_command_looper.cut_plain_text(#text);
687 });
688 } else if repat_less_one {
689 define_lopper_value.extend(quote::quote! {
690 if !runbot_command_looper.cut_plain_text(#text) {
691 return Ok(false);
692 }
693 while let Some(_) = runbot_command_looper.cut_plain_text(#text) {
694 continue;
695 }
696 });
697 } else if repat_zero_or_more {
698 define_lopper_value.extend(quote::quote! {
699 while let Some(_) = runbot_command_looper.cut_plain_text(#text) {
700 continue;
701 }
702 });
703 } else {
704 define_lopper_value.extend(quote::quote! {
705 if !runbot_command_looper.cut_plain_text(#text) {
706 return Ok(false);
707 }
708 });
709 }
710 }
711 BotCommandItem::TextToSpace(optional, repat_less_one, repat_zero_or_more) => {
712 if optional {
713 define_lopper_value.extend(quote::quote! {
714 runbot_command_looper.cut_text_to_space();
715 });
716 } else if repat_less_one {
717 define_lopper_value.extend(quote::quote! {
718 if !runbot_command_looper.cut_text_to_space() {
719 return Ok(false);
720 }
721 while let Some(_) = runbot_command_looper.cut_text_to_space() {
722 continue;
723 }
724 });
725 } else if repat_zero_or_more {
726 define_lopper_value.extend(quote::quote! {
727 while let Some(_) = runbot_command_looper.cut_text_to_space() {
728 continue;
729 }
730 });
731 } else {
732 define_lopper_value.extend(quote::quote! {
733 if !runbot_command_looper.cut_text_to_space() {
734 return Ok(false);
735 }
736 });
737 }
738 }
739 BotCommandItem::TextToSpaceParam(
740 optional,
741 repat_less_one,
742 repat_zero_or_more,
743 name,
744 ) => {
745 let ident = proc_macro2::Ident::new(&name, proc_macro2::Span::call_site());
746 if optional {
747 define_lopper_value.extend(quote::quote! {
748 let runbot_command_text = runbot_command_looper.cut_text_to_space();
749 let #ident = if let Some(text) = runbot_command_text {
750 if let Ok(p) = std::str::FromStr::from_str(text.as_str()) {
751 Some(p)
752 } else {
753 return Ok(false);
754 }
755 } else {
756 None
757 };
758 });
759 } else if repat_less_one {
760 define_lopper_value.extend(quote::quote! {
761 let mut runbot_command_texts = vec![];
762 runbot_command_texts.push(if let Some(text) = runbot_command_looper.cut_text_to_space() {
763 if let Ok(p) = std::str::FromStr::from_str(text.as_str()) {
764 p
765 } else {
766 return Ok(false);
767 }
768 } else {
769 return Ok(false);
770 });
771 while let Some(text) = runbot_command_looper.cut_text_to_space() {
772 runbot_command_texts.push(if let Ok(p) = std::str::FromStr::from_str(text.as_str()) {
773 p
774 } else {
775 return Ok(false);
776 });
777 }
778 let #ident = runbot_command_texts;
779 });
780 } else if repat_zero_or_more {
781 define_lopper_value.extend(quote::quote! {
782 let mut runbot_command_texts = vec![];
783 while let Some(text) = runbot_command_looper.cut_text_to_space() {
784 runbot_command_texts.push(if let Ok(p) = std::str::FromStr::from_str(text.as_str()) {
785 p
786 } else {
787 return Ok(false);
788 });
789 }
790 let #ident = runbot_command_texts;
791 });
792 } else {
793 define_lopper_value.extend(quote::quote! {
794 if !runbot_command_looper.cut_text_to_space() {
795 return Ok(false);
796 }
797 });
798 }
799 }
800 BotCommandItem::Enum(optional, repat_less_one, repat_zero_or_more, options) => {
801 if optional {
802 define_lopper_value.extend(quote::quote! {
803 runbot_command_looper.next_enum(&[#(#options),*]);
804 });
805 } else if repat_less_one {
806 define_lopper_value.extend(quote::quote! {
807 let runbot_command_option = runbot_command_looper.next_enum(&[#(#options),*]);
808 if runbot_command_option.is_none() {
809 return Ok(false);
810 }
811 while let Some(_) = runbot_command_looper.next_enum(&[#(#options),*]) {
812 continue;
813 }
814 });
815 } else if repat_zero_or_more {
816 define_lopper_value.extend(quote::quote! {
817 while let Some(_) = runbot_command_looper.next_enum(&[#(#options),*]) {
818 continue;
819 }
820 });
821 } else {
822 define_lopper_value.extend(quote::quote! {
823 if runbot_command_looper.next_enum(&[#(#options),*]).is_none() {
824 return Ok(false);
825 }
826 });
827 }
828 }
829 BotCommandItem::EnumParam(
830 optional,
831 repat_less_one,
832 repat_zero_or_more,
833 name,
834 options,
835 ) => {
836 let ident = proc_macro2::Ident::new(&name, proc_macro2::Span::call_site());
837 if optional {
838 define_lopper_value.extend(quote::quote! {
839 let #ident = runbot_command_looper.next_enum(&[#(#options),*]);
840 });
841 } else if repat_less_one {
842 define_lopper_value.extend(quote::quote! {
843 let mut runbot_command_option = vec![];
844 runbot_command_option.push(if let Some(text) = runbot_command_looper.next_enum(&[#(#options),*]) {
845 if let Ok(text) = std::str::FromStr::from_str(text.as_str()) {
846 text
847 } else {
848 return Ok(false);
849 }
850 } else {
851 return Ok(false);
852 });
853 while let Some(text) = runbot_command_looper.next_enum(&[#(#options),*]) {
854 runbot_command_option.push(if let Ok(text) = std::str::FromStr::from_str(text.as_str()) {
855 text
856 } else {
857 return Ok(false);
858 });
859 }
860 let #ident = runbot_command_option;
861 });
862 } else if repat_zero_or_more {
863 define_lopper_value.extend(quote::quote! {
864 let mut runbot_command_option = vec![];
865 while let Some(text) = runbot_command_looper.next_enum(&[#(#options),*]) {
866 runbot_command_option.push(if let Ok(text) = std::str::FromStr::from_str(text.as_str()) {
867 text
868 } else {
869 return Ok(false);
870 });
871 }
872 let #ident = runbot_command_option;
873 });
874 } else {
875 define_lopper_value.extend(quote::quote! {
876 let runbot_command_option = if let Some(text) = runbot_command_looper.next_enum(&[#(#options),*]) {
877 if let Ok(text) = std::str::FromStr::from_str(text.as_str()) {
878 text
879 } else {
880 return Ok(false);
881 }
882 } else {
883 return Ok(false);
884 };
885 let #ident = runbot_command_option;
886 });
887 }
888 }
889 BotCommandItem::TextToEnd(optional, repat_less_one, repat_zero_or_more, name) => {
890 let ident = proc_macro2::Ident::new(&name, proc_macro2::Span::call_site());
891 if optional {
892 define_lopper_value.extend(quote::quote! {
893 let #ident = if let Some(text) = runbot_command_looper.cut_text_to_end() {
894 if let Ok(text) = std::str::FromStr::from_str(text.as_str()) {
895 Some(text)
896 } else {
897 return Ok(false);
898 }
899 };
900 });
901 } else if repat_less_one {
902 define_lopper_value.extend(quote::quote! {
903 let mut runbot_command_texts = vec![];
904 runbot_command_texts.push(if let Some(text) = runbot_command_looper.cut_text_to_end() {
905 if let Ok(text) = std::str::FromStr::from_str(text.as_str()) {
906 text
907 } else {
908 return Ok(false);
909 }
910 } else {
911 return Ok(false);
912 });
913 });
914 } else if repat_zero_or_more {
915 define_lopper_value.extend(quote::quote! {
916 let mut runbot_command_texts = vec![];
917 while let Some(text) = runbot_command_looper.cut_text_to_end() {
918 runbot_command_texts.push(if let Ok(text) = std::str::FromStr::from_str(text.as_str()) {
919 text
920 } else {
921 return Ok(false);
922 });
923 }
924 let #ident = runbot_command_texts;
925 });
926 } else {
927 define_lopper_value.extend(quote::quote! {
928 let #ident = if let Some(text) = runbot_command_looper.cut_text_to_end() {
929 if let Ok(text) = std::str::FromStr::from_str(text.as_str()) {
930 text
931 } else {
932 return Ok(false);
933 }
934 } else {
935 return Ok(false);
936 };
937 });
938 }
939 }
940 }
941 }
942
943 emit!(quote::quote! {
944 #[derive(Copy, Clone, Default, Debug)]
945 #vis struct #struct_name;
946
947 #[::runbot::re_export::async_trait::async_trait]
948 impl MessageProcessor for #struct_name {
949 fn id(&self) -> &'static str {
950 concat!(
951 env!("CARGO_PKG_NAME"),
952 "::",
953 module_path!(),
954 "::",
955 stringify!(#fn_name)
956 )
957 }
958 #asyncness fn process_message(&self, #first_param, #second_param) #return_type {
959 #define_command_lopper
960 #define_lopper_value
961 #fn_name(#first_param_ident, #second_param_ident #command_item_ident_stream).await
962 }
963 }
964
965 #vis static #static_name: #struct_name = #struct_name;
966
967 #method_clone
968
969 impl Into<Processor> for #struct_name {
970 fn into(self) -> Processor {
971 Processor::Message(Box::new(self))
972 }
973 }
974 })
975}
976
977fn is_text_to_end_at_most_one_and_last(items: &[BotCommandItem]) -> bool {
978 let count = items
979 .iter()
980 .filter(|x| matches!(x, BotCommandItem::TextToEnd(_, _, _, _)))
981 .count();
982 match count {
983 0 => true, 1 => matches!(items.last(), Some(BotCommandItem::TextToEnd(_, _, _, _))), _ => false, }
987}
988
989#[derive(Debug, Clone, PartialEq)]
992enum BotCommandItem {
993 Number(bool, bool, bool), NumberParam(bool, bool, bool, String), PlainText(bool, bool, bool, String), TextToSpace(bool, bool, bool), TextToSpaceParam(bool, bool, bool, String), Enum(bool, bool, bool, Vec<String>), EnumParam(bool, bool, bool, String, Vec<String>), TextToEnd(bool, bool, bool, String), }
1002
1003fn get_repeat_flags_from_char(ch: char) -> Option<(bool, bool, bool)> {
1004 match ch {
1005 '?' => Some((true, false, false)),
1006 '+' => Some((false, true, false)),
1007 '*' => Some((false, false, true)),
1008 _ => None,
1009 }
1010}
1011
1012fn parse_template(template: &str) -> Vec<BotCommandItem> {
1013 let mut result = Vec::new();
1014 let mut i = 0;
1015 let template_len = template.len();
1016
1017 while i < template_len {
1018 let rest = &template[i..];
1019
1020 if rest.starts_with('{') {
1022 if let Some(j) = rest.find('}') {
1023 let body = &rest[1..j];
1024
1025 let mut next_index = i + j + 1;
1027 let mut flags = (false, false, false);
1028 if let Some(ch) = rest[j + 1..].chars().next() {
1029 if let Some(f) = get_repeat_flags_from_char(ch) {
1030 flags = f;
1031 next_index += ch.len_utf8();
1032 }
1033 }
1034
1035 if body == ":n" {
1036 result.push(BotCommandItem::Number(flags.0, flags.1, flags.2));
1037 i = next_index;
1038 continue;
1039 } else if body == ":s" {
1040 result.push(BotCommandItem::TextToSpace(flags.0, flags.1, flags.2));
1041 i = next_index;
1042 continue;
1043 } else if let Some(colon) = body.find(':') {
1044 let name = &body[..colon];
1045 let typ = &body[colon + 1..];
1046 match typ {
1047 "n" => result.push(BotCommandItem::NumberParam(
1048 flags.0,
1049 flags.1,
1050 flags.2,
1051 name.to_string(),
1052 )),
1053 "s" => result.push(BotCommandItem::TextToSpaceParam(
1054 flags.0,
1055 flags.1,
1056 flags.2,
1057 name.to_string(),
1058 )),
1059 "e" => result.push(BotCommandItem::TextToEnd(
1060 flags.0,
1061 flags.1,
1062 flags.2,
1063 name.to_string(),
1064 )),
1065 _ => {}
1066 }
1067 i = next_index;
1068 continue;
1069 }
1070 }
1072 }
1073
1074 if rest.starts_with('[') {
1076 if let Some(j) = rest.find(']') {
1077 let body = &rest[1..j];
1078
1079 let mut next_index = i + j + 1;
1081 let mut flags = (false, false, false);
1082 if let Some(ch) = rest[j + 1..].chars().next() {
1083 if let Some(f) = get_repeat_flags_from_char(ch) {
1084 flags = f;
1085 next_index += ch.len_utf8();
1086 }
1087 }
1088
1089 if let Some(colon) = body.find(':') {
1090 let name = &body[..colon];
1091 let options: Vec<String> = body[colon + 1..]
1092 .split('|')
1093 .map(|s| s.to_string())
1094 .collect();
1095 result.push(BotCommandItem::EnumParam(
1096 flags.0,
1097 flags.1,
1098 flags.2,
1099 name.to_string(),
1100 options,
1101 ));
1102 } else {
1103 let options: Vec<String> = body.split('|').map(|s| s.to_string()).collect();
1104 result.push(BotCommandItem::Enum(flags.0, flags.1, flags.2, options));
1105 }
1106 i = next_index;
1107 continue;
1108 }
1109 }
1110
1111 let mut j = 0;
1113 while i + j < template_len {
1114 let c = template[i + j..].chars().next().unwrap();
1115 if c == '{' || c == '[' {
1116 break;
1117 }
1118 j += c.len_utf8();
1119 }
1120 if j > 0 {
1121 let text = &template[i..i + j];
1122 let mut last_idx = 0;
1123 let mut chars_iter = text.char_indices().peekable();
1124
1125 while let Some((idx, ch)) = chars_iter.next() {
1126 if let Some((opt, rep0, rep1)) = get_repeat_flags_from_char(ch) {
1127 let part = &text[last_idx..idx];
1128 if !part.is_empty() {
1130 let trimmed = part.trim();
1131 if trimmed.is_empty() {
1132 result.push(BotCommandItem::PlainText(
1134 false,
1135 false,
1136 false,
1137 "".to_string(),
1138 ));
1139 } else {
1140 result.push(BotCommandItem::PlainText(
1141 opt,
1142 rep0,
1143 rep1,
1144 trimmed.to_string(),
1145 ));
1146 }
1147 }
1148 last_idx = idx + ch.len_utf8();
1149 }
1150 }
1151 if last_idx < text.len() {
1153 let part = &text[last_idx..];
1154 let trimmed = part.trim();
1155 if trimmed.is_empty() {
1156 result.push(BotCommandItem::PlainText(
1158 false,
1159 false,
1160 false,
1161 "".to_string(),
1162 ));
1163 } else {
1164 result.push(BotCommandItem::PlainText(
1165 false,
1166 false,
1167 false,
1168 trimmed.to_string(),
1169 ));
1170 }
1171 }
1172
1173 i += j;
1174 } else {
1175 if let Some(ch) = template[i..].chars().next() {
1177 i += ch.len_utf8();
1178 } else {
1179 break;
1180 }
1181 }
1182 }
1183
1184 result
1185}
1186
1187#[derive(Default, Debug)]
1188struct ModuleAttributes {
1189 name: Option<syn::LitStr>,
1190 help: Option<syn::LitStr>,
1191 processors: Option<syn::LitStr>,
1192}
1193
1194impl ModuleAttributes {
1195 fn parse(&mut self, ts: &TokenStream) -> syn::Result<()> {
1196 if ts.is_empty() {
1197 return Ok(());
1198 }
1199 let error_msg = "All attributes should be `ident = \"value\"` joined by ,";
1200 let tree_vec = ts.clone().into_iter().collect::<Vec<_>>();
1201 let mut idx: usize = 0;
1202 loop {
1203 if idx >= tree_vec.len() {
1204 break;
1205 }
1206 let ident = &tree_vec[idx];
1207 idx += 1;
1208 let ident = match ident {
1209 TokenTree::Ident(ident) => ident,
1210 _ => {
1211 return Err(syn::Error::new(ident.span().into(), error_msg));
1212 }
1213 };
1214 if idx >= tree_vec.len() {
1215 return Err(syn::Error::new(ident.span().into(), error_msg));
1216 }
1217 let punct = &tree_vec[idx];
1218 idx += 1;
1219 match punct {
1220 TokenTree::Punct(punct) => {
1221 let equals_char: char = '=';
1222 if equals_char != punct.as_char() {
1223 return Err(syn::Error::new(punct.span().into(), error_msg));
1224 }
1225 }
1226 _ => return Err(syn::Error::new(punct.span().into(), error_msg)),
1227 }
1228 if idx >= tree_vec.len() {
1229 return Err(syn::Error::new(punct.span().into(), error_msg));
1230 }
1231 let literal = &tree_vec[idx];
1232 idx += 1;
1233 let literal = match literal {
1234 TokenTree::Literal(literal) => {
1235 let literal_str = literal.to_string();
1236 syn::parse_str::<syn::LitStr>(&literal_str)?
1237 }
1238 _ => return Err(syn::Error::new(literal.span().into(), error_msg)),
1239 };
1240 match ident.to_string().as_str() {
1241 "name" => {
1242 if self.name.is_some() {
1243 return Err(syn::Error::new(ident.span().into(), "duplicate 'name'"));
1244 }
1245 self.name = Some(literal);
1246 }
1247 "help" => {
1248 if self.help.is_some() {
1249 return Err(syn::Error::new(ident.span().into(), "duplicate 'help'"));
1250 }
1251 self.help = Some(literal);
1252 }
1253 "processors" => {
1254 if self.processors.is_some() {
1255 return Err(syn::Error::new(
1256 ident.span().into(),
1257 "duplicate 'processors'",
1258 ));
1259 }
1260 self.processors = Some(literal);
1261 }
1262 _ => {
1263 return Err(syn::Error::new(
1264 ident.span().into(),
1265 format!("not allowed idnet '{}'", ident.to_string()),
1266 ));
1267 }
1268 }
1269 if idx >= tree_vec.len() {
1270 break;
1271 }
1272 let punct = &tree_vec[idx];
1273 idx += 1;
1274 match punct {
1275 TokenTree::Punct(punct) => {
1276 let equals_char: char = ',';
1277 if equals_char != punct.as_char() {
1278 return Err(syn::Error::new(punct.span().into(), error_msg));
1279 }
1280 }
1281 _ => return Err(syn::Error::new(punct.span().into(), error_msg)),
1282 }
1283 }
1284 Ok(())
1285 }
1286}
1287
1288#[proc_macro_error]
1289#[proc_macro_attribute]
1290pub fn module(args: TokenStream, input: TokenStream) -> TokenStream {
1291 let mut attrs = ModuleAttributes::default();
1295 match attrs.parse(&args) {
1296 Ok(_) => {}
1297 Err(err) => {
1298 abort!(&args.into_iter().take(1).last().unwrap().span(), err);
1299 }
1300 };
1301 let module_impl = parse_macro_input!(input as syn::ItemImpl);
1303 let module_impl_span = module_impl.span();
1304 let struct_ident = match *module_impl.self_ty {
1305 syn::Type::Path(ref type_path) => &type_path.path.segments.last().unwrap().ident,
1306 _ => abort!(&module_impl_span, "Expected a struct type for impl block"),
1307 };
1308 let struct_name = struct_ident.to_string();
1309 let functions = module_impl
1310 .items
1311 .iter()
1312 .filter_map(|item| match item {
1313 syn::ImplItem::Fn(m) => Some(m),
1314 _ => None,
1315 })
1316 .collect::<Vec<&syn::ImplItemFn>>();
1317 let mut function_map = functions
1319 .iter()
1320 .map(|f| (f.sig.ident.to_string(), *f))
1321 .collect::<HashMap<String, &syn::ImplItemFn>>();
1322 let function_names = functions
1323 .iter()
1324 .map(|f| f.sig.ident.to_string())
1325 .collect::<Vec<String>>();
1326 let id_function_defined = function_names.contains(&"id".to_owned());
1328 let id_tokens = if id_function_defined {
1329 let id_function = function_map.remove(&"id".to_owned()).unwrap();
1330 quote! {
1331 #id_function
1332 }
1333 } else {
1334 quote! {
1335 fn id() -> &'static str {
1336 concat!(env!("CARGO_PKG_NAME"), "::", module_path!(), "::", #struct_name)
1337 }
1338 }
1339 };
1340 let name_function_defined = function_names.contains(&"name".to_owned());
1342 let name_tokens = if let Some(name) = attrs.name {
1343 if name_function_defined {
1344 abort!(
1345 &module_impl_span,
1346 "both define `attribute name` and `function name`, only one can be defined"
1347 );
1348 }
1349 let lit_str = name.value();
1350 if lit_str.ends_with(")") {
1351 let expr: syn::Expr = match syn::parse_str(lit_str.as_str()) {
1352 Ok(expr) => expr,
1353 Err(error) => abort!(&module_impl_span, error),
1354 };
1355 quote! {
1356 fn name() -> &'static str {
1357 #expr
1358 }
1359 }
1360 } else {
1361 quote! {
1362 fn name() -> &'static str {
1363 #name
1364 }
1365 }
1366 }
1367 } else {
1368 if !name_function_defined {
1369 quote! {
1370 fn name() -> &'static str {
1371 #struct_name
1372 }
1373 }
1374 } else {
1375 let name_function = function_map.remove(&"name".to_owned()).unwrap();
1376 quote! {
1377 #name_function
1378 }
1379 }
1380 };
1381 let help_function_defined = function_names.contains(&"help".to_owned());
1383 let help_tokens = if let Some(help) = attrs.help {
1384 if help_function_defined {
1385 abort!(
1386 &module_impl_span,
1387 "both define `attribute help` and `function help`, only one can be defined"
1388 );
1389 }
1390 let lit_str = help.value();
1391 if lit_str.ends_with(")") {
1392 let expr: syn::Expr = match syn::parse_str(lit_str.as_str()) {
1393 Ok(expr) => expr,
1394 Err(error) => abort!(&module_impl_span, error),
1395 };
1396 quote! {
1397 fn help() -> &'static str {
1398 #expr
1399 }
1400 }
1401 } else {
1402 quote! {
1403 fn help() -> &'static str {
1404 #help
1405 }
1406 }
1407 }
1408 } else {
1409 if !help_function_defined {
1410 quote! {
1411 fn help() -> &'static str {
1412 #struct_name
1413 }
1414 }
1415 } else {
1416 let help_function = function_map.remove(&"help".to_owned()).unwrap();
1417 quote! {
1418 #help_function
1419 }
1420 }
1421 };
1422 let processors_function_defined = function_names.contains(&"processors".to_owned());
1424 let processors_tokens = if let Some(processors) = attrs.processors {
1425 if processors_function_defined {
1426 abort!(
1427 &module_impl_span,
1428 "both define `attribute processors` and `function processors`, only one can be defined"
1429 );
1430 }
1431 let lit_str = processors.value();
1432 let lit_str_array = lit_str
1433 .split("+")
1434 .into_iter()
1435 .filter_map(|s| {
1436 let s = s.trim();
1437 if s.is_empty() { None } else { Some(s) }
1438 })
1439 .map(|sn| {
1440 if sn.ends_with(")") {
1441 let expr: syn::Expr = match syn::parse_str(sn) {
1442 Ok(expr) => expr,
1443 Err(error) => abort!(&module_impl_span, error),
1444 };
1445 quote! {
1446 #expr
1447 }
1448 } else {
1449 let ident = proc_macro2::Ident::new(
1450 &sn.to_case(Case::UpperSnake),
1451 proc_macro2::Span::call_site(),
1452 );
1453 quote! {
1454 #ident.into()
1455 }
1456 }
1457 })
1458 .reduce(|a, b| {
1459 let mut t = proc_macro2::TokenStream::from(a);
1460 let com = quote! {,};
1461 t.extend(com);
1462 t.extend(b);
1463 t
1464 });
1465 let lit_str_array = if let Some(lit_str_array) = lit_str_array {
1466 lit_str_array
1467 } else {
1468 quote! {}
1469 };
1470 quote! {
1471 fn processors() -> Vec<Processor> {
1472 vec![#lit_str_array]
1473 }
1474 }
1475 } else {
1476 if !processors_function_defined {
1477 quote! {
1478 fn processors() -> Vec<Processor> {
1479 vec![]
1480 }
1481 }
1482 } else {
1483 let processors_function = function_map.remove(&"processors".to_owned()).unwrap();
1484 quote! {
1485 #processors_function
1486 }
1487 }
1488 };
1489 let mut surplus_functions_tokens = quote! {};
1491 for (_, function) in function_map {
1492 surplus_functions_tokens.extend(quote! {
1493 #function
1494 });
1495 }
1496 let struct_define = quote! {
1498 #[derive(Debug, Copy, Clone)]
1499 pub struct #struct_ident();
1500 };
1501 let into_process = quote! {
1502 impl Into<Processor> for #struct_ident {
1503 fn into(self) -> Processor {
1504 Processor::Module(Box::new(ProcessModule {
1505 id: Self::id(),
1506 name: Self::name(),
1507 help: Self::help(),
1508 processors: Self::processors().into(),
1509 }))
1510 }
1511 }
1512 };
1513 let static_name = proc_macro2::Ident::new(
1515 &struct_ident.to_string().to_case(Case::UpperSnake),
1516 proc_macro2::Span::call_site(),
1517 );
1518 let static_instance = quote! {
1519 pub static #static_name: #struct_ident = #struct_ident();
1520 };
1521 let output = quote! {
1523 #struct_define
1524 #[::runbot::re_export::async_trait::async_trait]
1525 impl Module for #struct_ident {
1526 #id_tokens
1527 #name_tokens
1528 #help_tokens
1529 #processors_tokens
1530 #surplus_functions_tokens
1531 }
1532 #into_process
1533 #static_instance
1534 };
1535 emit!(output)
1536}
1537
1538#[cfg(test)]
1539mod tests {
1540 use super::*;
1541
1542 #[test]
1543 fn test_parse_template_repeat() {
1544 let template = "{:n}{time:n}?{:n}*{time:n}+";
1545 let items = parse_template(template);
1546 assert_eq!(
1547 items,
1548 vec![
1549 BotCommandItem::Number(false, false, false),
1550 BotCommandItem::NumberParam(true, false, false, "time".to_string()),
1551 BotCommandItem::Number(false, false, true),
1552 BotCommandItem::NumberParam(false, true, false, "time".to_string()),
1553 ]
1554 );
1555 }
1556
1557 #[test]
1558 fn test_plain_text_repeat() {
1559 let template = "hi?hello*world+";
1560 let items = parse_template(template);
1561 assert_eq!(
1562 items,
1563 vec![
1564 BotCommandItem::PlainText(true, false, false, "hi".to_string()),
1565 BotCommandItem::PlainText(false, false, true, "hello".to_string()),
1566 BotCommandItem::PlainText(false, true, false, "world".to_string()),
1567 ]
1568 );
1569 }
1570
1571 #[test]
1572 fn test_plain_text_chinese_optional() {
1573 let template = "我是Rust软件工程师?你好吗";
1574 let items = parse_template(template);
1575 assert_eq!(
1576 items,
1577 vec![
1578 BotCommandItem::PlainText(true, false, false, "我是Rust软件工程师".to_string()),
1579 BotCommandItem::PlainText(false, false, false, "你好吗".to_string()),
1580 ]
1581 );
1582 }
1583
1584 #[test]
1585 fn test_plain_text_chinese_repeat() {
1586 let template = "你好*世界+";
1587 let items = parse_template(template);
1588 assert_eq!(
1589 items,
1590 vec![
1591 BotCommandItem::PlainText(false, false, true, "你好".to_string()),
1592 BotCommandItem::PlainText(false, true, false, "世界".to_string()),
1593 ]
1594 );
1595 }
1596
1597 #[test]
1598 fn test_plain_text_mix() {
1599 let template = "a?b*c+d";
1600 let items = parse_template(template);
1601 assert_eq!(
1602 items,
1603 vec![
1604 BotCommandItem::PlainText(true, false, false, "a".to_string()),
1605 BotCommandItem::PlainText(false, false, true, "b".to_string()),
1606 BotCommandItem::PlainText(false, true, false, "c".to_string()),
1607 BotCommandItem::PlainText(false, false, false, "d".to_string()),
1608 ]
1609 );
1610 }
1611
1612 #[test]
1613 fn test_enum_and_param_repeat() {
1614 let template = "[a|b][enum_name:a|b][a|b]? [enum_name:a|b]* [enum_name:a|b]+";
1615 let items = parse_template(template);
1616 assert_eq!(
1617 items,
1618 vec![
1619 BotCommandItem::Enum(false, false, false, vec!["a".to_string(), "b".to_string()]),
1620 BotCommandItem::EnumParam(
1621 false,
1622 false,
1623 false,
1624 "enum_name".to_string(),
1625 vec!["a".to_string(), "b".to_string()]
1626 ),
1627 BotCommandItem::Enum(true, false, false, vec!["a".to_string(), "b".to_string()]),
1628 BotCommandItem::PlainText(false, false, false, "".to_string()),
1629 BotCommandItem::EnumParam(
1630 false,
1631 false,
1632 true,
1633 "enum_name".to_string(),
1634 vec!["a".to_string(), "b".to_string()]
1635 ),
1636 BotCommandItem::PlainText(false, false, false, "".to_string()),
1637 BotCommandItem::EnumParam(
1638 false,
1639 true,
1640 false,
1641 "enum_name".to_string(),
1642 vec!["a".to_string(), "b".to_string()]
1643 ),
1644 ]
1645 );
1646 }
1647
1648 #[test]
1649 fn test_text_to_end_param_repeat() {
1650 let template = "{text:e}{text:e}?{text:e}*{text:e}+";
1651 let items = parse_template(template);
1652 assert_eq!(
1653 items,
1654 vec![
1655 BotCommandItem::TextToEnd(false, false, false, "text".to_string()),
1656 BotCommandItem::TextToEnd(true, false, false, "text".to_string()),
1657 BotCommandItem::TextToEnd(false, false, true, "text".to_string()),
1658 BotCommandItem::TextToEnd(false, true, false, "text".to_string()),
1659 ]
1660 );
1661 }
1662
1663 #[test]
1664 fn test_mix_all() {
1665 let template = "提醒我{time:n}[单位:秒|分|时]?之后[通知|告诉]+{text:e}";
1666 let items = parse_template(template);
1667 assert_eq!(
1668 items,
1669 vec![
1670 BotCommandItem::PlainText(false, false, false, "提醒我".to_string()),
1671 BotCommandItem::NumberParam(false, false, false, "time".to_string()),
1672 BotCommandItem::EnumParam(
1673 true,
1674 false,
1675 false,
1676 "单位".to_string(),
1677 vec!["秒".to_string(), "分".to_string(), "时".to_string()]
1678 ),
1679 BotCommandItem::PlainText(false, false, false, "之后".to_string()),
1680 BotCommandItem::Enum(
1681 false,
1682 true,
1683 false,
1684 vec!["通知".to_string(), "告诉".to_string()]
1685 ),
1686 BotCommandItem::TextToEnd(false, false, false, "text".to_string()),
1687 ]
1688 );
1689 }
1690
1691 #[test]
1692 fn test_plain_text_with_no_special() {
1693 let template = "纯文本无特殊符号";
1694 let items = parse_template(template);
1695 assert_eq!(
1696 items,
1697 vec![BotCommandItem::PlainText(
1698 false,
1699 false,
1700 false,
1701 "纯文本无特殊符号".to_string()
1702 ),]
1703 );
1704 }
1705}