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 }