ident_case/
lib.rs

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
25use std::ascii::AsciiExt;
26use std::str::FromStr;
27
28use self::RenameRule::*;
29
30/// A casing rule for renaming Rust identifiers.
31#[derive(Debug, PartialEq, Eq, Clone, Copy)]
32pub 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
49impl RenameRule {
50    /// Change case of a `PascalCase` variant.
51    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.
74    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
104impl FromStr for RenameRule {
105    type Err = ();
106
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
120impl Default for RenameRule {
121    fn default() -> Self {
122        RenameRule::None
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::RenameRule::*;
129
130    #[test]
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]
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}