1 //! Crate for changing case of Rust identifiers. 2 //! 3 //! # Features 4 //! * Supports `snake_case`, `lowercase`, `camelCase`, 5 //! `PascalCase`, `SCREAMING_SNAKE_CASE`, and `kebab-case` 6 //! * Rename variants, and fields 7 //! 8 //! # Examples 9 //! ```rust 10 //! use ident_case::RenameRule; 11 //! 12 //! assert_eq!("helloWorld", RenameRule::CamelCase.apply_to_field("hello_world")); 13 //! 14 //! assert_eq!("i_love_serde", RenameRule::SnakeCase.apply_to_variant("ILoveSerde")); 15 //! ``` 16 17 // Copyright 2017 Serde Developers 18 // 19 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or 20 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license 21 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your 22 // option. This file may not be copied, modified, or distributed 23 // except according to those terms. 24 25 use std::ascii::AsciiExt; 26 use std::str::FromStr; 27 28 use self::RenameRule::*; 29 30 /// A casing rule for renaming Rust identifiers. 31 #[derive(Debug, PartialEq, Eq, Clone, Copy)] 32 pub enum RenameRule { 33 /// No-op rename rule. 34 None, 35 /// Rename direct children to "lowercase" style. 36 LowerCase, 37 /// Rename direct children to "PascalCase" style, as typically used for enum variants. 38 PascalCase, 39 /// Rename direct children to "camelCase" style. 40 CamelCase, 41 /// Rename direct children to "snake_case" style, as commonly used for fields. 42 SnakeCase, 43 /// Rename direct children to "SCREAMING_SNAKE_CASE" style, as commonly used for constants. 44 ScreamingSnakeCase, 45 /// Rename direct children to "kebab-case" style. 46 KebabCase, 47 } 48 49 impl RenameRule { 50 /// Change case of a `PascalCase` variant. apply_to_variant<S: AsRef<str>>(&self, variant: S) -> String51 pub fn apply_to_variant<S: AsRef<str>>(&self, variant: S) -> String { 52 53 let variant = variant.as_ref(); 54 match *self { 55 None | PascalCase => variant.to_owned(), 56 LowerCase => variant.to_ascii_lowercase(), 57 CamelCase => variant[..1].to_ascii_lowercase() + &variant[1..], 58 SnakeCase => { 59 let mut snake = String::new(); 60 for (i, ch) in variant.char_indices() { 61 if i > 0 && ch.is_uppercase() { 62 snake.push('_'); 63 } 64 snake.push(ch.to_ascii_lowercase()); 65 } 66 snake 67 } 68 ScreamingSnakeCase => SnakeCase.apply_to_variant(variant).to_ascii_uppercase(), 69 KebabCase => SnakeCase.apply_to_variant(variant).replace('_', "-"), 70 } 71 } 72 73 /// Change case of a `snake_case` field. apply_to_field<S: AsRef<str>>(&self, field: S) -> String74 pub fn apply_to_field<S: AsRef<str>>(&self, field: S) -> String { 75 76 let field = field.as_ref(); 77 match *self { 78 None | LowerCase | SnakeCase => field.to_owned(), 79 PascalCase => { 80 let mut pascal = String::new(); 81 let mut capitalize = true; 82 for ch in field.chars() { 83 if ch == '_' { 84 capitalize = true; 85 } else if capitalize { 86 pascal.push(ch.to_ascii_uppercase()); 87 capitalize = false; 88 } else { 89 pascal.push(ch); 90 } 91 } 92 pascal 93 } 94 CamelCase => { 95 let pascal = PascalCase.apply_to_field(field); 96 pascal[..1].to_ascii_lowercase() + &pascal[1..] 97 } 98 ScreamingSnakeCase => field.to_ascii_uppercase(), 99 KebabCase => field.replace('_', "-"), 100 } 101 } 102 } 103 104 impl FromStr for RenameRule { 105 type Err = (); 106 from_str(rename_all_str: &str) -> Result<Self, Self::Err>107 fn from_str(rename_all_str: &str) -> Result<Self, Self::Err> { 108 match rename_all_str { 109 "lowercase" => Ok(LowerCase), 110 "PascalCase" => Ok(PascalCase), 111 "camelCase" => Ok(CamelCase), 112 "snake_case" => Ok(SnakeCase), 113 "SCREAMING_SNAKE_CASE" => Ok(ScreamingSnakeCase), 114 "kebab-case" => Ok(KebabCase), 115 _ => Err(()), 116 } 117 } 118 } 119 120 impl Default for RenameRule { default() -> Self121 fn default() -> Self { 122 RenameRule::None 123 } 124 } 125 126 #[cfg(test)] 127 mod tests { 128 use super::RenameRule::*; 129 130 #[test] rename_variants()131 fn rename_variants() { 132 for &(original, lower, camel, snake, screaming, kebab) in 133 &[ 134 ("Outcome", "outcome", "outcome", "outcome", "OUTCOME", "outcome"), 135 ("VeryTasty", "verytasty", "veryTasty", "very_tasty", "VERY_TASTY", "very-tasty"), 136 ("A", "a", "a", "a", "A", "a"), 137 ("Z42", "z42", "z42", "z42", "Z42", "z42"), 138 ] { 139 assert_eq!(None.apply_to_variant(original), original); 140 assert_eq!(LowerCase.apply_to_variant(original), lower); 141 assert_eq!(PascalCase.apply_to_variant(original), original); 142 assert_eq!(CamelCase.apply_to_variant(original), camel); 143 assert_eq!(SnakeCase.apply_to_variant(original), snake); 144 assert_eq!(ScreamingSnakeCase.apply_to_variant(original), screaming); 145 assert_eq!(KebabCase.apply_to_variant(original), kebab); 146 } 147 } 148 149 #[test] rename_fields()150 fn rename_fields() { 151 for &(original, pascal, camel, screaming, kebab) in 152 &[ 153 ("outcome", "Outcome", "outcome", "OUTCOME", "outcome"), 154 ("very_tasty", "VeryTasty", "veryTasty", "VERY_TASTY", "very-tasty"), 155 ("_leading_under", "LeadingUnder", "leadingUnder", "_LEADING_UNDER", "-leading-under"), 156 ("double__under", "DoubleUnder", "doubleUnder", "DOUBLE__UNDER", "double--under"), 157 ("a", "A", "a", "A", "a"), 158 ("z42", "Z42", "z42", "Z42", "z42"), 159 ] { 160 assert_eq!(None.apply_to_field(original), original); 161 assert_eq!(PascalCase.apply_to_field(original), pascal); 162 assert_eq!(CamelCase.apply_to_field(original), camel); 163 assert_eq!(SnakeCase.apply_to_field(original), original); 164 assert_eq!(ScreamingSnakeCase.apply_to_field(original), screaming); 165 assert_eq!(KebabCase.apply_to_field(original), kebab); 166 } 167 } 168 }