1#[derive(Clone, Debug, PartialEq, Eq)]
17pub enum Inflection {
18 Lower,
20 Upper,
22 Camel,
24 Snake,
26 Pascal,
28 ScreamingSnake,
30 Kebab,
32 ScreamingKebab,
34}
35
36impl Inflection {
37 pub fn parse(s: &str) -> Option<Self> {
41 match s {
42 "lowercase" => Some(Self::Lower),
43 "UPPERCASE" => Some(Self::Upper),
44 "camelCase" => Some(Self::Camel),
45 "snake_case" => Some(Self::Snake),
46 "PascalCase" => Some(Self::Pascal),
47 "SCREAMING_SNAKE_CASE" => Some(Self::ScreamingSnake),
48 "kebab-case" => Some(Self::Kebab),
49 "SCREAMING-KEBAB-CASE" => Some(Self::ScreamingKebab),
50 _ => None,
51 }
52 }
53
54 pub const VALID_VALUES: &[&str] = &[
56 "lowercase",
57 "UPPERCASE",
58 "camelCase",
59 "snake_case",
60 "PascalCase",
61 "SCREAMING_SNAKE_CASE",
62 "kebab-case",
63 "SCREAMING-KEBAB-CASE",
64 ];
65
66 pub fn apply(&self, s: &str) -> String {
68 match self {
69 Self::Lower => s.to_lowercase(),
70 Self::Upper => s.to_uppercase(),
71 Self::Snake => to_snake_case(s),
72 Self::ScreamingSnake => to_snake_case(s).to_uppercase(),
73 Self::Camel => to_camel_case(s),
74 Self::Pascal => to_pascal_case(s),
75 Self::Kebab => to_snake_case(s).replace('_', "-"),
76 Self::ScreamingKebab => to_snake_case(s).to_uppercase().replace('_', "-"),
77 }
78 }
79}
80
81fn to_snake_case(s: &str) -> String {
82 let mut result = String::new();
83 for (i, ch) in s.chars().enumerate() {
84 if ch.is_uppercase() {
85 if i > 0 {
86 result.push('_');
87 }
88 result.push(ch.to_lowercase().next().unwrap());
89 } else {
90 result.push(ch);
91 }
92 }
93 result
94}
95
96fn to_camel_case(s: &str) -> String {
97 let pascal = to_pascal_case(s);
98 let mut chars = pascal.chars();
99 match chars.next() {
100 None => String::new(),
101 Some(first) => first.to_lowercase().to_string() + chars.as_str(),
102 }
103}
104
105fn to_pascal_case(s: &str) -> String {
106 s.split('_')
107 .map(|word| {
108 let mut chars = word.chars();
109 match chars.next() {
110 None => String::new(),
111 Some(first) => first.to_uppercase().to_string() + chars.as_str(),
112 }
113 })
114 .collect()
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn parse_all_values() {
123 for val in Inflection::VALID_VALUES {
124 assert!(Inflection::parse(val).is_some(), "should parse: {val}");
125 }
126 assert!(Inflection::parse("invalid").is_none());
127 }
128
129 #[test]
130 fn camel_case() {
131 assert_eq!(Inflection::Camel.apply("first_name"), "firstName");
132 assert_eq!(Inflection::Camel.apply("FirstName"), "firstName");
133 }
134
135 #[test]
136 fn snake_case() {
137 assert_eq!(Inflection::Snake.apply("firstName"), "first_name");
138 assert_eq!(Inflection::Snake.apply("FirstName"), "first_name");
139 }
140
141 #[test]
142 fn pascal_case() {
143 assert_eq!(Inflection::Pascal.apply("first_name"), "FirstName");
144 }
145
146 #[test]
147 fn kebab_case() {
148 assert_eq!(Inflection::Kebab.apply("first_name"), "first-name");
149 assert_eq!(Inflection::Kebab.apply("FirstName"), "first-name");
150 }
151
152 #[test]
153 fn screaming_snake() {
154 assert_eq!(Inflection::ScreamingSnake.apply("firstName"), "FIRST_NAME");
155 }
156
157 #[test]
158 fn screaming_kebab() {
159 assert_eq!(Inflection::ScreamingKebab.apply("firstName"), "FIRST-NAME");
160 }
161
162 #[test]
163 fn lower_upper() {
164 assert_eq!(Inflection::Lower.apply("FooBar"), "foobar");
165 assert_eq!(Inflection::Upper.apply("FooBar"), "FOOBAR");
166 }
167}