1 //! Code to convert the Rust-styled field/variant (e.g. `my_field`, `MyType`) to the
2 //! case of the source (e.g. `my-field`, `MY_FIELD`).
3 
4 // See https://users.rust-lang.org/t/psa-dealing-with-warning-unused-import-std-ascii-asciiext-in-today-s-nightly/13726
5 #[allow(deprecated, unused_imports)]
6 use std::ascii::AsciiExt;
7 
8 use std::fmt::{self, Debug, Display};
9 
10 use self::RenameRule::*;
11 
12 /// The different possible ways to change case of fields in a struct, or variants in an enum.
13 #[derive(Copy, Clone, PartialEq)]
14 pub enum RenameRule {
15     /// Don't apply a default rename rule.
16     None,
17     /// Rename direct children to "lowercase" style.
18     LowerCase,
19     /// Rename direct children to "UPPERCASE" style.
20     UpperCase,
21     /// Rename direct children to "PascalCase" style, as typically used for
22     /// enum variants.
23     PascalCase,
24     /// Rename direct children to "camelCase" style.
25     CamelCase,
26     /// Rename direct children to "snake_case" style, as commonly used for
27     /// fields.
28     SnakeCase,
29     /// Rename direct children to "SCREAMING_SNAKE_CASE" style, as commonly
30     /// used for constants.
31     ScreamingSnakeCase,
32     /// Rename direct children to "kebab-case" style.
33     KebabCase,
34     /// Rename direct children to "SCREAMING-KEBAB-CASE" style.
35     ScreamingKebabCase,
36 }
37 
38 static RENAME_RULES: &[(&str, RenameRule)] = &[
39     ("lowercase", LowerCase),
40     ("UPPERCASE", UpperCase),
41     ("PascalCase", PascalCase),
42     ("camelCase", CamelCase),
43     ("snake_case", SnakeCase),
44     ("SCREAMING_SNAKE_CASE", ScreamingSnakeCase),
45     ("kebab-case", KebabCase),
46     ("SCREAMING-KEBAB-CASE", ScreamingKebabCase),
47 ];
48 
49 impl RenameRule {
from_str(rename_all_str: &str) -> Result<Self, ParseError>50     pub fn from_str(rename_all_str: &str) -> Result<Self, ParseError> {
51         for (name, rule) in RENAME_RULES {
52             if rename_all_str == *name {
53                 return Ok(*rule);
54             }
55         }
56         Err(ParseError {
57             unknown: rename_all_str,
58         })
59     }
60 
61     /// Apply a renaming rule to an enum variant, returning the version expected in the source.
apply_to_variant(&self, variant: &str) -> String62     pub fn apply_to_variant(&self, variant: &str) -> String {
63         match *self {
64             None | PascalCase => variant.to_owned(),
65             LowerCase => variant.to_ascii_lowercase(),
66             UpperCase => variant.to_ascii_uppercase(),
67             CamelCase => variant[..1].to_ascii_lowercase() + &variant[1..],
68             SnakeCase => {
69                 let mut snake = String::new();
70                 for (i, ch) in variant.char_indices() {
71                     if i > 0 && ch.is_uppercase() {
72                         snake.push('_');
73                     }
74                     snake.push(ch.to_ascii_lowercase());
75                 }
76                 snake
77             }
78             ScreamingSnakeCase => SnakeCase.apply_to_variant(variant).to_ascii_uppercase(),
79             KebabCase => SnakeCase.apply_to_variant(variant).replace('_', "-"),
80             ScreamingKebabCase => ScreamingSnakeCase
81                 .apply_to_variant(variant)
82                 .replace('_', "-"),
83         }
84     }
85 
86     /// Apply a renaming rule to a struct field, returning the version expected in the source.
apply_to_field(&self, field: &str) -> String87     pub fn apply_to_field(&self, field: &str) -> String {
88         match *self {
89             None | LowerCase | SnakeCase => field.to_owned(),
90             UpperCase => field.to_ascii_uppercase(),
91             PascalCase => {
92                 let mut pascal = String::new();
93                 let mut capitalize = true;
94                 for ch in field.chars() {
95                     if ch == '_' {
96                         capitalize = true;
97                     } else if capitalize {
98                         pascal.push(ch.to_ascii_uppercase());
99                         capitalize = false;
100                     } else {
101                         pascal.push(ch);
102                     }
103                 }
104                 pascal
105             }
106             CamelCase => {
107                 let pascal = PascalCase.apply_to_field(field);
108                 pascal[..1].to_ascii_lowercase() + &pascal[1..]
109             }
110             ScreamingSnakeCase => field.to_ascii_uppercase(),
111             KebabCase => field.replace('_', "-"),
112             ScreamingKebabCase => ScreamingSnakeCase.apply_to_field(field).replace('_', "-"),
113         }
114     }
115 }
116 
117 pub struct ParseError<'a> {
118     unknown: &'a str,
119 }
120 
121 impl<'a> Display for ParseError<'a> {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result122     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
123         f.write_str("unknown rename rule `rename_all = ")?;
124         Debug::fmt(self.unknown, f)?;
125         f.write_str("`, expected one of ")?;
126         for (i, (name, _rule)) in RENAME_RULES.iter().enumerate() {
127             if i > 0 {
128                 f.write_str(", ")?;
129             }
130             Debug::fmt(name, f)?;
131         }
132         Ok(())
133     }
134 }
135 
136 #[test]
rename_variants()137 fn rename_variants() {
138     for &(original, lower, upper, camel, snake, screaming, kebab, screaming_kebab) in &[
139         (
140             "Outcome", "outcome", "OUTCOME", "outcome", "outcome", "OUTCOME", "outcome", "OUTCOME",
141         ),
142         (
143             "VeryTasty",
144             "verytasty",
145             "VERYTASTY",
146             "veryTasty",
147             "very_tasty",
148             "VERY_TASTY",
149             "very-tasty",
150             "VERY-TASTY",
151         ),
152         ("A", "a", "A", "a", "a", "A", "a", "A"),
153         ("Z42", "z42", "Z42", "z42", "z42", "Z42", "z42", "Z42"),
154     ] {
155         assert_eq!(None.apply_to_variant(original), original);
156         assert_eq!(LowerCase.apply_to_variant(original), lower);
157         assert_eq!(UpperCase.apply_to_variant(original), upper);
158         assert_eq!(PascalCase.apply_to_variant(original), original);
159         assert_eq!(CamelCase.apply_to_variant(original), camel);
160         assert_eq!(SnakeCase.apply_to_variant(original), snake);
161         assert_eq!(ScreamingSnakeCase.apply_to_variant(original), screaming);
162         assert_eq!(KebabCase.apply_to_variant(original), kebab);
163         assert_eq!(
164             ScreamingKebabCase.apply_to_variant(original),
165             screaming_kebab
166         );
167     }
168 }
169 
170 #[test]
rename_fields()171 fn rename_fields() {
172     for &(original, upper, pascal, camel, screaming, kebab, screaming_kebab) in &[
173         (
174             "outcome", "OUTCOME", "Outcome", "outcome", "OUTCOME", "outcome", "OUTCOME",
175         ),
176         (
177             "very_tasty",
178             "VERY_TASTY",
179             "VeryTasty",
180             "veryTasty",
181             "VERY_TASTY",
182             "very-tasty",
183             "VERY-TASTY",
184         ),
185         ("a", "A", "A", "a", "A", "a", "A"),
186         ("z42", "Z42", "Z42", "z42", "Z42", "z42", "Z42"),
187     ] {
188         assert_eq!(None.apply_to_field(original), original);
189         assert_eq!(UpperCase.apply_to_field(original), upper);
190         assert_eq!(PascalCase.apply_to_field(original), pascal);
191         assert_eq!(CamelCase.apply_to_field(original), camel);
192         assert_eq!(SnakeCase.apply_to_field(original), original);
193         assert_eq!(ScreamingSnakeCase.apply_to_field(original), screaming);
194         assert_eq!(KebabCase.apply_to_field(original), kebab);
195         assert_eq!(ScreamingKebabCase.apply_to_field(original), screaming_kebab);
196     }
197 }
198