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