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}