1#![warn(rust_2021_compatibility, rustdoc::all, missing_docs)]
15
16#[cfg(feature = "colored")]
17use ansi_term::{Color, Style};
18use proc_macro::TokenStream;
19use proc_macro2::TokenStream as TokenStream2;
20use quote::{format_ident, quote, ToTokens};
21use std::iter::once;
22use syn::{
23 braced,
24 parse::{self, Parse},
25 parse_macro_input, Attribute, Fields, Generics, Ident, LitInt, LitStr, Meta, Token, Variant,
26 Visibility,
27};
28
29#[derive(Clone, Copy, Debug)]
31struct Config<'i> {
32 category: &'i str,
33 nested: bool,
34 #[cfg(feature = "colored")]
35 style_prefix: Style,
36 #[cfg(feature = "colored")]
37 style_message: Style,
38}
39impl<'i> Config<'i> {
40 pub fn new(category: &'i str) -> Self {
41 let nested = false;
42 #[cfg(feature = "colored")]
43 {
44 let style_prefix = Style::default();
45 let style_message = Style::default();
46 #[cfg(feature = "colored")]
48 let style_prefix = style_prefix.fg(Color::Red);
49 Self {
50 category,
51 nested,
52 style_prefix,
53 style_message,
54 }
55 }
56 #[cfg(not(feature = "colored"))]
57 {
58 Self { category, nested }
59 }
60 }
61 #[cfg(feature = "colored")]
62 pub fn prefix(&self) -> ansi_term::Prefix {
63 self.style_prefix.prefix()
64 }
65 #[cfg(feature = "colored")]
66 pub fn infix(&self) -> ansi_term::Infix {
67 self.style_prefix.infix(self.style_message)
68 }
69 #[cfg(feature = "colored")]
70 pub fn suffix(&self) -> ansi_term::Suffix {
71 self.style_message.suffix()
72 }
73 pub fn on_category(&mut self, _category: &Ident) {}
74 pub fn on_attrs(&mut self, attrs: &Vec<Attribute>) {
75 for attr in attrs {
76 self.on_attr(attr)
77 }
78 }
79 #[cfg(feature = "colored")]
80 fn lit_str_to_color(str: &LitStr) -> Color {
81 let str = str.value();
82 match str.as_str() {
83 "black" => Color::Black,
84 "red" => Color::Red,
85 "green" => Color::Green,
86 "yellow" => Color::Yellow,
87 "blue" => Color::Blue,
88 "purple" => Color::Purple,
89 "cyan" => Color::Cyan,
90 "white" => Color::White,
91 _ => panic!("Unexpected color `{}`.", str),
92 }
93 }
94 #[cfg(feature = "colored")]
95 fn rgb_tuple_to_color(tuple: &syn::ExprTuple) -> Color {
96 assert!(
97 tuple.elems.len() == 3,
98 "RGB color should have 3 componenets."
99 );
100 let mut iter = tuple.elems.iter();
101 let mut get_component = || -> u8 {
102 let component = iter.next().unwrap();
103 if let syn::Expr::Lit(syn::ExprLit {
104 lit: syn::Lit::Int(int),
105 attrs: _attrs,
106 }) = component
107 {
108 int.base10_parse().expect("Invalid RGB code")
109 } else {
110 panic!("Unsupported expression in RGB code.");
111 }
112 };
113 Color::RGB(get_component(), get_component(), get_component())
114 }
115 fn on_attr(&mut self, attr: &Attribute) {
116 let res = self;
117 match &attr.meta {
118 Meta::List(_list) => {
119 unimplemented!("Attribute list.");
120 }
121 Meta::NameValue(name_value) => {
122 if let Some(_ident) = name_value.path.get_ident() {
123 #[cfg(feature = "colored")]
124 {
125 let ident = _ident;
126 if ident == "color" || ident == "foreground" || ident == "fg" {
127 match &name_value.value {
128 syn::Expr::Lit(literal) => {
129 if !literal.attrs.is_empty() {
130 eprintln!("Attributes in literal is ignored.");
131 }
132 match &literal.lit {
133 syn::Lit::Int(int) => {
134 res.style_prefix = res.style_prefix.fg(Color::Fixed(
135 int.base10_parse().expect("Invalid color."),
136 ));
137 }
138 syn::Lit::Str(str) => {
139 res.style_prefix =
140 res.style_prefix.fg(Self::lit_str_to_color(str))
141 }
142 _ => {
143 unimplemented!("Unsupported literal in MetaNameValue.")
144 }
145 }
146 }
147 syn::Expr::Tuple(tuple) => {
148 res.style_prefix =
149 res.style_prefix.fg(Self::rgb_tuple_to_color(tuple))
150 }
151 _ => unimplemented!("Unsupported expression in MetaNameValue."),
152 }
153 } else if ident == "background" || ident == "bg" {
154 match &name_value.value {
155 syn::Expr::Lit(literal) => {
156 if !literal.attrs.is_empty() {
157 eprintln!("Attributes in literal is ignored.");
158 }
159 match &literal.lit {
160 syn::Lit::Int(int) => {
161 res.style_prefix = res.style_prefix.on(Color::Fixed(
162 int.base10_parse().expect("Invalid color."),
163 ));
164 }
165 syn::Lit::Str(str) => {
166 res.style_prefix =
167 res.style_prefix.on(Self::lit_str_to_color(str));
168 }
169 _ => {
170 unimplemented!("Unsupported literal in MetaNameValue.")
171 }
172 }
173 }
174 syn::Expr::Tuple(tuple) => {
175 res.style_prefix =
176 res.style_prefix.on(Self::rgb_tuple_to_color(tuple))
177 }
178 _ => unimplemented!("Unsupported expression in MetaNameValue."),
179 }
180 }
181 }
182 #[cfg(not(feature = "colored"))]
183 unimplemented!("Path in MetaNameValue.");
184 } else {
185 unimplemented!("Path in MetaNameValue.");
186 }
187 }
188 Meta::Path(path) => {
189 if let Some(ident) = path.get_ident() {
190 if ident == "nested" {
191 res.nested = true;
192 } else {
193 #[cfg(feature = "colored")]
194 {
195 macro_rules! set_config {
196 ($ident:ident) => {
197 if ident == stringify!($ident) {
198 res.style_message = res.style_message.$ident();
199 }
200 };
201 }
202 set_config!(bold);
203 set_config!(dimmed);
204 set_config!(italic);
205 set_config!(underline);
206 set_config!(blink);
207 set_config!(reverse);
208 set_config!(hidden);
209 set_config!(strikethrough);
210 }
211 #[cfg(not(feature = "colored"))]
212 unimplemented!("Path in MetaNameValue.");
213 }
214 } else {
215 unimplemented!("Path in attribute.");
216 }
217 }
218 }
219 }
220}
221
222struct ErrorVariant {
223 variant: Variant,
224 msg: LitStr,
225}
226
227impl Parse for ErrorVariant {
228 fn parse(input: parse::ParseStream) -> syn::Result<Self> {
229 let variant = input.parse()?;
230 let msg = input.parse()?;
231 Ok(Self { variant, msg })
232 }
233}
234
235impl ErrorVariant {
236 fn to_tokens(&self, code: &str, tokens: &mut TokenStream2) {
237 let variant = &self.variant;
238 let msg = &self.msg;
239 let code = format!("{}: ", code);
240 tokens.extend(quote! {
241 #[doc = #code]
242 #[doc = #msg]
243 #variant,
244 })
245 }
246}
247
248impl ErrorVariant {
249 fn fmt_desc(&self) -> TokenStream2 {
250 let name = &self.variant.ident;
251 let msg = &self.msg;
252 match &self.variant.fields {
253 Fields::Named(fields) => {
254 let fields = fields
255 .named
256 .iter()
257 .map(|field| field.ident.as_ref().unwrap());
258 quote! {
259 Self::#name { #(#fields, )* } => {
260 ::core::write!{f, #msg}?;
261 ::core::result::Result::Ok(())
262 }
263 }
264 }
265 Fields::Unnamed(unnamed) => {
266 let elements = unnamed
267 .unnamed
268 .iter()
269 .enumerate()
270 .map(|(i, _)| format_ident!("_{i}"))
271 .collect::<Vec<_>>();
272 quote! {
273 Self::#name ( #(#elements, )* ) => {
274 ::core::write!{f, #msg, #(#elements, )*}?;
275 ::core::result::Result::Ok(())
276 }
277 }
278 }
279 Fields::Unit => {
280 quote! {
281 Self::#name => {
282 ::core::write!{f, #msg}?;
283 ::core::result::Result::Ok(())
284 }
285 }
286 }
287 }
288 }
289 fn get_desc(&self) -> TokenStream2 {
290 let name = &self.variant.ident;
291 let msg = &self.msg;
292 match &self.variant.fields {
293 Fields::Named(fields) => {
294 let fields = fields
295 .named
296 .iter()
297 .map(|field| field.ident.as_ref().unwrap());
298 quote! {
299 Self::#name { #(#fields, )* } => {
300 ::std::format!{#msg}
301 }
302 }
303 }
304 Fields::Unnamed(unnamed) => {
305 let elements = unnamed
306 .unnamed
307 .iter()
308 .enumerate()
309 .map(|(i, _)| format_ident!("_{i}"))
310 .collect::<Vec<_>>();
311 quote! {
312 Self::#name ( #(#elements, )* ) => {
313 ::std::format!{#msg, #(#elements, )*}
314 }
315 }
316 }
317 Fields::Unit => {
318 quote! {
319 Self::#name => {
320 ::std::format!{#msg}
321 }
322 }
323 }
324 }
325 }
326 fn get_category(&self, cfg: Config) -> TokenStream2 {
327 let name = &self.variant.ident;
328 if cfg.nested {
329 quote! {
330 Self::#name (nested) => nested.get_category(),
331 }
332 } else {
333 let category = cfg.category;
334 quote! {
335 Self::#name {..} => #category,
336 }
337 }
338 }
339 fn get_number(&self, cfg: Config, number: String) -> TokenStream2 {
340 let name = &self.variant.ident;
341 if cfg.nested {
342 quote! {
343 Self::#name (nested) => {
344 let number = nested.get_number();
345 ::std::borrow::Cow::Owned(::std::format!("{}{}", #number, number))
346 }
347 }
348 } else {
349 quote! {
350 Self::#name {..} => ::std::borrow::Cow::Borrowed(#number),
351 }
352 }
353 }
354 fn get_code(&self, cfg: Config, number: String) -> TokenStream2 {
355 let name = &self.variant.ident;
356 if cfg.nested {
357 quote! {
358 Self::#name (nested) => {
359 let number = nested.get_number();
360 let category = nested.get_category().chars().next().unwrap().to_uppercase();
361 ::std::borrow::Cow::Owned(::std::format!("{}{}{}", category, #number, number))
362 }
363 }
364 } else {
365 let category = cfg.category.chars().next().unwrap().to_uppercase();
366 let code = format!("{category}{number}");
367 quote! {
368 Self::#name {..} => ::std::borrow::Cow::Borrowed(#code),
369 }
370 }
371 }
372 fn get_prefix(&self, cfg: Config, number: String) -> TokenStream2 {
373 let name = &self.variant.ident;
374 if cfg.nested {
376 quote! {
377 Self::#name (nested) => {
378 let category = nested.get_category();
379 let short = category.chars().next().unwrap().to_uppercase();
380 let prefix = ::std::format!("{}[{}{}{}]", category, short, #number, nested.get_number());
381 ::std::borrow::Cow::Owned(prefix)
382 },
383 }
384 } else {
385 let category = cfg.category;
386 let short = category.chars().next().unwrap().to_uppercase();
387 let prefix = format!("{}[{}{}]", category, short, &number);
388 quote! {
389 Self::#name {..} => ::std::borrow::Cow::Borrowed(#prefix),
390 }
391 }
392 }
393 #[cfg(feature = "colored")]
394 fn write_str(s: impl std::fmt::Display) -> TokenStream2 {
395 let s = s.to_string();
396 quote! {f.write_str(#s)?;}
397 }
398 fn fmt(&self, number: String, cfg: Config) -> TokenStream2 {
399 let name = &self.variant.ident;
400 let msg = &self.msg;
401 #[cfg(feature = "colored")]
402 let prefix = Self::write_str(cfg.prefix());
403 #[cfg(feature = "colored")]
404 let infix = Self::write_str(cfg.infix());
405 #[cfg(feature = "colored")]
406 let suffix = Self::write_str(cfg.suffix());
407 #[cfg(not(feature = "colored"))]
408 let prefix = quote! {};
409 #[cfg(not(feature = "colored"))]
410 let infix = quote! {};
411 #[cfg(not(feature = "colored"))]
412 let suffix = quote! {};
413 let get_prefix = if cfg.nested {
414 quote! {&self.get_prefix()}
415 } else {
416 let prefix = format!(
417 "{}[{}{}]",
418 cfg.category,
419 cfg.category.chars().next().unwrap().to_uppercase(),
420 number,
421 );
422 quote! {#prefix}
423 };
424 match &self.variant.fields {
425 Fields::Named(fields) => {
426 assert!(!cfg.nested, "Named fields can't be nested error.");
427 let fields = fields
428 .named
429 .iter()
430 .map(|field| field.ident.as_ref().unwrap());
431 quote! {
432 Self::#name { #(#fields, )* } => {
433 #prefix
434 f.write_str(#get_prefix)?;
435 f.write_str(": ")?;
436 #infix
437 ::core::write!{f, #msg}?;
438 #suffix
439 ::core::result::Result::Ok(())
440 }
441 }
442 }
443 Fields::Unnamed(unnamed) => {
444 if cfg.nested {
445 assert_eq!(
446 unnamed.unnamed.len(),
447 1,
448 "Nested error can consists of one unnamed fields.",
449 );
450 quote! {
451 Self::#name ( nested ) => {
452 #prefix
453 f.write_str(#get_prefix)?;
454 f.write_str(": ")?;
455 #infix
456 ::core::write!{f, #msg, nested.get_desc()}?;
457 #suffix
458 ::core::result::Result::Ok(())
459 }
460 }
461 } else {
462 let elements = unnamed
463 .unnamed
464 .iter()
465 .enumerate()
466 .map(|(i, _)| format_ident!("_{i}"))
467 .collect::<Vec<_>>();
468 quote! {
469 Self::#name ( #(#elements, )* ) => {
470 #prefix
471 f.write_str(#get_prefix)?;
472 f.write_str(": ")?;
473 #infix
474 ::core::write!{f, #msg, #(#elements, )*}?;
475 #suffix
476 ::core::result::Result::Ok(())
477 }
478 }
479 }
480 }
481 Fields::Unit => {
482 assert!(!cfg.nested, "Unit can't be nested error.");
483 quote! {
484 Self::#name => {
485 #prefix
486 f.write_str(#get_prefix)?;
487 f.write_str(": ")?;
488 #infix
489 ::core::write!{f, #msg}?;
490 #suffix
491 ::core::result::Result::Ok(())
492 }
493 }
494 }
495 }
496 }
497}
498
499enum ErrorTree {
500 Prefix(Vec<Attribute>, LitInt, LitStr, Vec<ErrorTree>),
501 Variant(Vec<Attribute>, LitInt, ErrorVariant),
502}
503
504impl Parse for ErrorTree {
505 fn parse(input: parse::ParseStream) -> syn::Result<Self> {
506 if input.peek2(LitStr) {
507 let attrs = input.call(Attribute::parse_outer)?;
508 let code = input.parse()?;
509 let desc = input.parse()?;
510 let children;
511 braced!(children in input);
512 let mut nodes = Vec::new();
513 while !children.is_empty() {
514 let node = children.parse()?;
515 nodes.push(node);
516 }
517 Ok(ErrorTree::Prefix(attrs, code, desc, nodes))
518 } else {
519 let attrs = input.call(Attribute::parse_outer)?;
520 let code = input.parse()?;
521 let variant = input.parse()?;
522 let _comma: Token![,] = input.parse()?;
523 Ok(ErrorTree::Variant(attrs, code, variant))
524 }
525 }
526}
527
528impl ErrorTree {
529 fn get_variants<'s>(
533 &'s self,
534 config: Config<'s>,
535 prenumber: &'_ str,
536 ) -> impl Iterator<Item = (Config<'s>, String, &'s ErrorVariant)> {
537 match self {
538 Self::Prefix(attrs, postnumber, _desc, children) => children
539 .iter()
540 .flat_map(|node| {
541 let mut config = config.clone();
542 config.on_attrs(attrs);
543 node.get_variants(config, &format!("{prenumber}{postnumber}"))
544 })
545 .collect::<Vec<_>>()
546 .into_iter(),
547 Self::Variant(attrs, postnumber, var) => {
548 let code = format!("{prenumber}{postnumber}");
549 let mut config = config;
550 config.on_attrs(attrs);
551 vec![(config, code, var)].into_iter()
552 }
553 }
554 }
555 fn get_nodes<'s>(
560 &'s self,
561 config: Config<'s>,
562 prenumber: &str,
563 depth: usize,
564 ) -> impl Iterator<Item = (Config<'s>, usize, String, Option<String>, String)> {
565 match self {
566 Self::Prefix(_attrs, postnumber, desc, children) => {
567 let number = format!("{}{}", prenumber, postnumber);
568 once((config.clone(), depth, number.clone(), None, desc.value()))
569 .chain(
570 children
571 .iter()
572 .flat_map(|node| node.get_nodes(config.clone(), &number, depth + 1)),
573 )
574 .collect::<Vec<_>>()
575 .into_iter()
576 }
577 Self::Variant(_attrs, postnumber, var) => {
578 let number = format!("{}{}", prenumber, postnumber);
579 vec![(
580 config,
581 depth,
582 number,
583 Some(var.variant.ident.to_string()),
584 var.msg.value(),
585 )]
586 .into_iter()
587 }
588 }
589 }
590}
591
592struct ErrorEnum {
593 attrs: Vec<Attribute>,
594 vis: Visibility,
595 name: Ident,
596 generics: Generics,
597 variants: Vec<(Vec<Attribute>, Ident, String, LitStr, Vec<ErrorTree>)>,
598}
599
600impl ErrorEnum {
601 fn get_variants<'s>(&'s self) -> impl Iterator<Item = (Config, String, &'s ErrorVariant)> {
605 self.variants
606 .iter()
607 .flat_map(|(attrs, category, category_string, _, tree)| {
608 let mut config = Config::new(&category_string);
609 config.on_attrs(attrs);
610 config.on_category(category);
611 tree.iter()
612 .flat_map(move |node| node.get_variants(config, ""))
613 })
614 }
615 fn get_nodes<'s>(&'s self) -> Vec<(Config, usize, String, Option<String>, String)> {
621 self.variants
622 .iter()
623 .flat_map(|(attrs, category, category_string, msg, tree)| {
624 let mut config = Config::new(&category_string);
625 config.on_category(category);
626 config.on_attrs(attrs);
627 once((config, 0, String::new(), None, msg.value())).chain(
628 tree.iter()
629 .flat_map(|node| node.get_nodes(config, "", 1))
630 .collect::<Vec<_>>()
631 .into_iter(),
632 )
633 })
634 .collect()
635 }
636}
637
638impl Parse for ErrorEnum {
639 fn parse(input: parse::ParseStream) -> syn::Result<Self> {
640 let attrs = input.call(Attribute::parse_outer)?;
641 let vis = input.parse()?;
642 let name = input.parse()?;
643 let generics = input.parse()?;
644 let mut variants = Vec::new();
645 while !input.is_empty() {
646 let attrs = input.call(Attribute::parse_outer)?;
647 let category: syn::Ident = input.parse()?;
648 let category_string = category.to_string();
649 let msg = input.parse()?;
650 let inner;
651 braced!(inner in input);
652 let mut trees = Vec::new();
653 while !inner.is_empty() {
654 let tree = inner.parse()?;
655 trees.push(tree);
656 }
657 assert!(inner.is_empty());
658 variants.push((attrs, category, category_string, msg, trees));
659 }
660 Ok(Self {
661 attrs,
662 vis,
663 generics,
664 name,
665 variants,
666 })
667 }
668}
669
670impl ToTokens for ErrorEnum {
671 fn to_tokens(&self, tokens: &mut TokenStream2) {
672 let attrs = &self.attrs;
673 let vis = &self.vis;
674 let name = &self.name;
675 let generics = &self.generics;
676 let doc = self
677 .get_nodes()
678 .into_iter()
679 .map(|(_config, depth, code, name, desc)| {
680 let indent = " ".repeat(depth);
681 match name {
682 Some(name) => format!("{indent}- `{code}`(**{name}**): {desc}"),
683 None => format!("{indent}- `{code}`: {desc}"),
684 }
685 });
686 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
687
688 let error_variants = self.get_variants().collect::<Vec<_>>();
689
690 let variants = {
691 let mut tokens = TokenStream2::new();
692 error_variants.iter().for_each(|(cfg, number, var)| {
693 var.to_tokens(&format!("{}{}", cfg.category, number), &mut tokens)
694 });
695 tokens
696 };
697 tokens.extend(quote! {
698 #[doc = "List of error variants:"]
699 #(
700 #[doc = #doc]
701 )*
702 #(#attrs)*
703 #vis enum #name #generics {
704 #variants
705 }
706 });
707
708 let fmt = self
709 .get_variants()
710 .map(|(cfg, code, variant)| variant.fmt(code, cfg));
711 tokens.extend(quote! {
712 impl #impl_generics ::core::fmt::Display for #name #ty_generics #where_clause {
713 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
714 match self {
715 #(#fmt)*
716 }
717 }
718 }
719 });
720
721 let get_category = self
722 .get_variants()
723 .map(|(cfg, _number, variant)| variant.get_category(cfg));
724 let get_number = self
725 .get_variants()
726 .map(|(cfg, number, variant)| variant.get_number(cfg, number));
727 let get_code = self
728 .get_variants()
729 .map(|(cfg, number, variant)| variant.get_code(cfg, number));
730 let get_prefix = self
731 .get_variants()
732 .map(|(cfg, number, variant)| variant.get_prefix(cfg, number));
733 let fmt_desc = self
734 .get_variants()
735 .map(|(_cfg, _number, variant)| variant.fmt_desc());
736 let get_desc = self
737 .get_variants()
738 .map(|(_cfg, _number, variant)| variant.get_desc());
739 tokens.extend(quote! {
740 impl #impl_generics #name #ty_generics #where_clause {
741 pub fn get_category(&self) -> &'static ::core::primitive::str {
743 match self {
744 #(#get_category)*
745 }
746 }
747 pub fn get_number(&self) -> ::std::borrow::Cow<'static, str> {
749 match self {
750 #(#get_number)*
751 }
752 }
753 pub fn get_code(&self) -> ::std::borrow::Cow<'static, str> {
755 match self {
756 #(#get_code)*
757 }
758 }
759 pub fn get_prefix(&self) -> ::std::borrow::Cow<'static, str> {
761 match self {
762 #(#get_prefix)*
763 }
764 }
765 fn fmt_desc(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
766 match self {
767 #(#fmt_desc)*
768 }
769 }
770 pub fn get_desc(&self) -> String {
772 match self {
773 #(#get_desc)*
774 }
775 }
776 }
777 });
778 }
779}
780
781#[proc_macro]
815pub fn error_type(token: TokenStream) -> TokenStream {
816 let error = parse_macro_input!(token as ErrorEnum);
817 error.to_token_stream().into()
818}
819
820#[cfg(test)]
821mod tests {
822 use crate::ErrorEnum;
823 use quote::{quote, ToTokens};
824
825 #[test]
826 fn basic() {
827 let output: ErrorEnum = syn::parse2(quote! {
828 FileSystemError
829 E "错误" {
830 01 FileNotFound {path: std::path::Path}
831 "{path} not found.",
832 }
833 })
834 .unwrap();
835 let output = output.into_token_stream();
836 eprintln!("{:#}", output);
837 }
838
839 #[test]
840 #[cfg(feature = "colored")]
841 fn colored() {
842 let output: ErrorEnum = syn::parse2(quote! {
843 FileSystemError
844 #[fg = (0xaf, 0, 0)]
845 #[bg = (0xa8, 0xa8, 0xa8)]
846 E "错误" {
847 01 FileNotFound {path: std::path::Path}
848 "{path} not found.",
849 }
850 #[fg = 214]
851 #[bg = 025]
852 W "警告" {
853 01 FileTooLarge {path: std::path::Path}
854 "{path} is too large.",
855 }
856 #[color = "blue"]
857 H "提示" {
858 01 FileNameSuggestion (std::path::Path)
859 "{0} may be what you want.",
860 }
861 })
862 .unwrap();
863 let output = output.into_token_stream();
864 eprintln!("{:#}", output);
865 }
866
867 #[test]
868 #[cfg(feature = "colored")]
869 fn colorful() {
870 let output: ErrorEnum = syn::parse2(quote! {
871 ColoredError
872 #[bold]
873 #[dimmer]
874 E "错误" {
875 #[fg = "black"]
876 0 BlackError (u8)
877 "{0} is not black.",
878 #[bg = "red"]
879 1 RedError (u8, u8)
880 "{0} and {1} is red.",
881 #[fg = "green"]
882 #[bg = "yellow"]
883 2 GreenYellowError
884 "Code is green, while description is yellow.",
885 #[color = "blue"]
886 3 BlueError
887 "I'm blue.",
888 #[foreground = "purple"]
889 #[background = "cyan"]
890 4 PurpleCyanError
891 "Purpule and cyan.",
892 #[color = "white"]
893 5 WhiteError { white: String }
894 "All in white.",
895 }
896 })
897 .unwrap();
898 let output = output.into_token_stream();
899 eprintln!("{:#}", output);
900 }
901
902 #[test]
903 fn deep() {
904 let output: ErrorEnum = syn::parse2(quote! {
905 FileSystemError
906 E "错误" {
907 0 "文件错误" {
908 0 AccessDenied
909 "无权限。",
910 }
911 }
912 })
913 .unwrap();
914 let output = output.into_token_stream();
915 eprintln!("{:#}", output);
916 }
917
918 #[test]
919 fn nested() {
920 let output: ErrorEnum = syn::parse2(quote! {
921 FileSystemError
922 E "错误" {
923 #[nested]
924 01 FileError (FileError)
925 "{0}",
926 }
927 })
928 .unwrap();
929 let output = output.into_token_stream();
930 eprintln!("{:#}", output);
931 }
932
933 #[test]
934 fn check_config() {
935 let output: ErrorEnum = syn::parse2(quote! {
936 FileSystemError
937 Error "错误" {
938 #[nested]
939 01 FileError (FileError)
940 "{0}",
941 }
942 })
943 .unwrap();
944 for (config, number, _variant) in output.get_variants() {
945 assert_eq!(config.category, "Error");
946 #[cfg(feature = "colored")]
947 assert_eq!(
948 format!("{config:?}"),
949 r#"Config { category: "Error", nested: true, style_prefix: Style { fg(Red) }, style_message: Style {} }"#,
950 );
951 #[cfg(not(feature = "colored"))]
952 assert_eq!(
953 format!("{config:?}"),
954 "Config { category: 'E', nested: true }",
955 );
956 assert_eq!(number, "01");
957 }
958 }
959
960 #[test]
961 #[cfg(feature = "colored")]
962 #[should_panic]
963 fn rgb_2() {
964 let output: ErrorEnum = syn::parse2(quote! {
965 FileSystemError
966 #[color = (0, 0)]
967 E "错误" {
968 01 FileError (FileError)
969 "{0}",
970 }
971 })
972 .unwrap();
973 let output = output.into_token_stream();
974 eprintln!("{:#}", output);
975 }
976
977 #[test]
978 #[cfg(feature = "colored")]
979 #[should_panic]
980 fn rgb_wrong() {
981 let output: ErrorEnum = syn::parse2(quote! {
982 FileSystemError
983 #[color = (4usize, -3, 2*5)]
984 E "错误" {
985 01 FileError (FileError)
986 "{0}",
987 }
988 })
989 .unwrap();
990 let output = output.into_token_stream();
991 eprintln!("{:#}", output);
992 }
993
994 #[test]
995 #[cfg(feature = "colored")]
996 #[should_panic]
997 fn color_bool() {
998 let output: ErrorEnum = syn::parse2(quote! {
999 FileSystemError
1000 #[color = true]
1001 E "错误" {
1002 01 FileError (FileError)
1003 "{0}",
1004 }
1005 })
1006 .unwrap();
1007 let output = output.into_token_stream();
1008 eprintln!("{:#}", output);
1009 }
1010
1011 #[test]
1012 #[should_panic]
1013 fn attribute_list() {
1014 let output: ErrorEnum = syn::parse2(quote! {
1015 FileSystemError
1016 #[nested(true)]
1017 E "错误" {
1018 01 FileError (FileError)
1019 "{0}",
1020 }
1021 })
1022 .unwrap();
1023 let output = output.into_token_stream();
1024 eprintln!("{:#}", output);
1025 }
1026
1027 #[test]
1028 #[should_panic]
1029 fn path() {
1030 let output: ErrorEnum = syn::parse2(quote! {
1031 FileSystemError
1032 #[nest::ed]
1033 E "错误" {
1034 01 FileError (FileError)
1035 "{0}",
1036 }
1037 })
1038 .unwrap();
1039 let output = output.into_token_stream();
1040 eprintln!("{:#}", output);
1041 }
1042
1043 #[test]
1044 #[should_panic]
1045 fn unsupported_value() {
1046 let output: ErrorEnum = syn::parse2(quote! {
1047 FileSystemError
1048 #[fg = true || false]
1049 E "错误" {
1050 01 FileError (FileError)
1051 "{0}",
1052 }
1053 })
1054 .unwrap();
1055 let output = output.into_token_stream();
1056 eprintln!("{:#}", output);
1057 }
1058
1059 #[test]
1060 #[cfg(feature = "colored")]
1061 #[should_panic]
1062 fn wrong_color() {
1063 let output: ErrorEnum = syn::parse2(quote! {
1064 FileSystemError
1065 #[color = "blite"]
1066 E "错误" {
1067 01 FileError (FileError)
1068 "{0}",
1069 }
1070 })
1071 .unwrap();
1072 let output = output.into_token_stream();
1073 eprintln!("{:#}", output);
1074 }
1075}