@@ -8,6 +8,9 @@ pub(crate) mod debug;
8
8
pub ( crate ) mod display;
9
9
mod parsing;
10
10
11
+ use std:: fmt:: Display ;
12
+
13
+ use convert_case:: { Case , Casing as _} ;
11
14
use proc_macro2:: TokenStream ;
12
15
use quote:: { format_ident, quote, ToTokens } ;
13
16
use syn:: {
@@ -16,7 +19,7 @@ use syn::{
16
19
parse_quote,
17
20
punctuated:: Punctuated ,
18
21
spanned:: Spanned as _,
19
- token,
22
+ token, LitStr , Token ,
20
23
} ;
21
24
22
25
use crate :: {
@@ -88,6 +91,62 @@ impl BoundsAttribute {
88
91
}
89
92
}
90
93
94
+ /// Representation of a `rename_all` macro attribute.
95
+ ///
96
+ /// ```rust,ignore
97
+ /// #[<attribute>(rename_all = "...")]
98
+ /// ```
99
+ ///
100
+ /// Possible Cases:
101
+ /// - `lowercase`
102
+ /// - `UPPERCASE`
103
+ /// - `PascalCase`
104
+ /// - `camelCase`
105
+ /// - `snake_case`
106
+ /// - `SCREAMING_SNAKE_CASE`
107
+ /// - `kebab-case`
108
+ /// - `SCREAMING-KEBAB-CASE`
109
+ #[ derive( Debug , Clone , Copy ) ]
110
+ struct RenameAllAttribute ( Case ) ;
111
+
112
+ impl RenameAllAttribute {
113
+ fn convert_case ( & self , ident : & syn:: Ident ) -> String {
114
+ ident. unraw ( ) . to_string ( ) . to_case ( self . 0 )
115
+ }
116
+ }
117
+
118
+ impl Parse for RenameAllAttribute {
119
+ fn parse ( input : ParseStream < ' _ > ) -> syn:: Result < Self > {
120
+ let _ = input. parse :: < syn:: Path > ( ) . and_then ( |p| {
121
+ if p. is_ident ( "rename_all" ) {
122
+ Ok ( p)
123
+ } else {
124
+ Err ( syn:: Error :: new (
125
+ p. span ( ) ,
126
+ "unknown attribute argument, expected `rename_all = \" ...\" `" ,
127
+ ) )
128
+ }
129
+ } ) ?;
130
+
131
+ input. parse :: < Token ! [ =] > ( ) ?;
132
+
133
+ let value: LitStr = input. parse ( ) ?;
134
+
135
+ // TODO should we really do a case insensitive comparision here?
136
+ Ok ( Self ( match value. value ( ) . replace ( [ '-' , '_' ] , "" ) . to_lowercase ( ) . as_str ( ) {
137
+ "lowercase" => Case :: Flat ,
138
+ "uppercase" => Case :: UpperFlat ,
139
+ "pascalcase" => Case :: Pascal ,
140
+ "camelcase" => Case :: Camel ,
141
+ "snakecase" => Case :: Snake ,
142
+ "screamingsnakecase" => Case :: UpperSnake ,
143
+ "kebabcase" => Case :: Kebab ,
144
+ "screamingkebabcase" => Case :: UpperKebab ,
145
+ _ => return Err ( syn:: Error :: new_spanned ( value, "unexpected casing expected one of: \" lowercase\" , \" UPPERCASE\" , \" PascalCase\" , \" camelCase\" , \" snake_case\" , \" SCREAMING_SNAKE_CASE\" , \" kebab-case\" , or \" SCREAMING-KEBAB-CASE\" " ) )
146
+ } ) )
147
+ }
148
+ }
149
+
91
150
/// Representation of a [`fmt`]-like attribute.
92
151
///
93
152
/// ```rust,ignore
@@ -516,19 +575,81 @@ struct ContainerAttributes {
516
575
517
576
/// Addition trait bounds.
518
577
bounds : BoundsAttribute ,
578
+
579
+ /// Rename unit enum variants following a similar behavior as [`serde`](https://serde.rs/container-attrs.html#rename_all).
580
+ rename_all : Option < RenameAllAttribute > ,
581
+ }
582
+
583
+ impl Spanning < ContainerAttributes > {
584
+ fn validate_for_struct ( & self , attr_name : impl Display ) -> syn:: Result < ( ) > {
585
+ if self . rename_all . is_some ( ) {
586
+ Err ( syn:: Error :: new (
587
+ self . span ,
588
+ format_args ! ( "`#[{attr_name}(rename_all=\" ...\" )]` can not be specified on structs or variants" ) ,
589
+ ) )
590
+ } else {
591
+ Ok ( ( ) )
592
+ }
593
+ }
594
+ }
595
+
596
+ mod kw {
597
+ use syn:: custom_keyword;
598
+
599
+ custom_keyword ! ( rename_all) ;
600
+ custom_keyword ! ( bounds) ;
601
+ custom_keyword ! ( bound) ;
519
602
}
520
603
521
604
impl Parse for ContainerAttributes {
522
605
fn parse ( input : ParseStream < ' _ > ) -> syn:: Result < Self > {
523
606
// We do check `FmtAttribute::check_legacy_fmt` eagerly here, because `Either` will swallow
524
607
// any error of the `Either::Left` if the `Either::Right` succeeds.
525
608
FmtAttribute :: check_legacy_fmt ( input) ?;
526
- <Either < FmtAttribute , BoundsAttribute > >:: parse ( input) . map ( |v| match v {
527
- Either :: Left ( fmt) => Self {
609
+ let lookahead = input. lookahead1 ( ) ;
610
+ Ok ( if lookahead. peek ( LitStr ) {
611
+ Self {
612
+ fmt : Some ( input. parse ( ) ?) ,
528
613
bounds : BoundsAttribute :: default ( ) ,
529
- fmt : Some ( fmt) ,
530
- } ,
531
- Either :: Right ( bounds) => Self { bounds, fmt : None } ,
614
+ rename_all : None ,
615
+ }
616
+ } else if lookahead. peek ( kw:: rename_all)
617
+ || lookahead. peek ( kw:: bounds)
618
+ || lookahead. peek ( kw:: bound)
619
+ || lookahead. peek ( Token ! [ where ] )
620
+ {
621
+ let mut bounds = BoundsAttribute :: default ( ) ;
622
+ let mut rename_all = None ;
623
+
624
+ while !input. is_empty ( ) {
625
+ let lookahead = input. lookahead1 ( ) ;
626
+ if lookahead. peek ( kw:: rename_all) {
627
+ if rename_all. is_some ( ) {
628
+ return Err (
629
+ input. error ( "`rename_all` can only be specified once" )
630
+ ) ;
631
+ } else {
632
+ rename_all = Some ( input. parse ( ) ?) ;
633
+ }
634
+ } else if lookahead. peek ( kw:: bounds)
635
+ || lookahead. peek ( kw:: bound)
636
+ || lookahead. peek ( Token ! [ where ] )
637
+ {
638
+ bounds. 0 . extend ( input. parse :: < BoundsAttribute > ( ) ?. 0 )
639
+ } else {
640
+ return Err ( lookahead. error ( ) ) ;
641
+ }
642
+ if !input. is_empty ( ) {
643
+ input. parse :: < Token ! [ , ] > ( ) ?;
644
+ }
645
+ }
646
+ Self {
647
+ fmt : None ,
648
+ bounds,
649
+ rename_all,
650
+ }
651
+ } else {
652
+ return Err ( lookahead. error ( ) ) ;
532
653
} )
533
654
}
534
655
}
@@ -554,6 +675,16 @@ impl attr::ParseMultiple for ContainerAttributes {
554
675
format ! ( "multiple `#[{name}(\" ...\" , ...)]` attributes aren't allowed" ) ,
555
676
) ) ;
556
677
}
678
+ if new
679
+ . rename_all
680
+ . and_then ( |n| prev. rename_all . replace ( n) )
681
+ . is_some ( )
682
+ {
683
+ return Err ( syn:: Error :: new (
684
+ new_span,
685
+ format ! ( "multiple `#[{name}(rename_all=\" ...\" )]` attributes aren't allowed" ) ,
686
+ ) ) ;
687
+ }
557
688
prev. bounds . 0 . extend ( new. bounds . 0 ) ;
558
689
559
690
Ok ( Spanning :: new (
@@ -582,7 +713,7 @@ where
582
713
}
583
714
}
584
715
585
- /// Extension of a [`syn::Type`] and a [`syn::Path`] allowing to travers its type parameters.
716
+ /// Extension of a [`syn::Type`] and a [`syn::Path`] allowing to traverse its type parameters.
586
717
trait ContainsGenericsExt {
587
718
/// Checks whether this definition contains any of the provided `type_params`.
588
719
fn contains_generics ( & self , type_params : & [ & syn:: Ident ] ) -> bool ;
0 commit comments