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