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
4use self::RenameRule::*;
5use 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)]
9pub 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
33static 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
44impl 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
120pub struct ParseError<'a> {
121 unknown: &'a str,
122}
123
124impl<'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]
140fn 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]
174fn 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