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 { |
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. |
57 | 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. |
82 | 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. |
112 | 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> { |
125 | 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] |
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] |
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 | |