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