| 1 | #[cfg (test)] |
| 2 | use strum_macros::EnumIter; |
| 3 | |
| 4 | use crate::pattern::Pattern; |
| 5 | use crate::Boundary; |
| 6 | |
| 7 | /// Defines the type of casing a string can be. |
| 8 | /// |
| 9 | /// ``` |
| 10 | /// use convert_case::{Case, Casing}; |
| 11 | /// |
| 12 | /// let super_mario_title: String = "super_mario_64" .to_case(Case::Title); |
| 13 | /// assert_eq!("Super Mario 64" , super_mario_title); |
| 14 | /// ``` |
| 15 | /// |
| 16 | /// A case is the pair of a [pattern](enum.Pattern.html) and a delimeter (a string). Given |
| 17 | /// a list of words, a pattern describes how to mutate the words and a delimeter is how the mutated |
| 18 | /// words are joined together. These inherantly are the properties of what makes a "multiword |
| 19 | /// identifier case", or simply "case". |
| 20 | /// |
| 21 | /// This crate provides the ability to convert "from" a case. This introduces a different feature |
| 22 | /// of cases which are the [word boundaries](Boundary) that segment the identifier into words. For example, a |
| 23 | /// snake case identifier `my_var_name` can be split on underscores `_` to segment into words. A |
| 24 | /// camel case identifier `myVarName` is split where a lowercase letter is followed by an |
| 25 | /// uppercase letter. Each case is also associated with a list of boundaries that are used when |
| 26 | /// converting "from" a particular case. |
| 27 | #[cfg_attr (test, derive(EnumIter))] |
| 28 | #[derive (Eq, PartialEq, Hash, Clone, Copy, Debug)] |
| 29 | pub enum Case { |
| 30 | /// Uppercase strings are delimited by spaces and all characters are uppercase. |
| 31 | /// * Boundaries: [Space](`Boundary::Space`) |
| 32 | /// * Pattern: [Uppercase](`Pattern::Uppercase`) |
| 33 | /// * Delimeter: Space |
| 34 | /// |
| 35 | /// ``` |
| 36 | /// use convert_case::{Case, Casing}; |
| 37 | /// assert_eq!("MY VARIABLE NAME" , "My variable NAME" .to_case(Case::Upper)) |
| 38 | /// ``` |
| 39 | Upper, |
| 40 | |
| 41 | /// Lowercase strings are delimited by spaces and all characters are lowercase. |
| 42 | /// * Boundaries: [Space](`Boundary::Space`) |
| 43 | /// * Pattern: [Lowercase](`Pattern::Lowercase`) |
| 44 | /// * Delimeter: Space |
| 45 | /// |
| 46 | /// ``` |
| 47 | /// use convert_case::{Case, Casing}; |
| 48 | /// assert_eq!("my variable name" , "My variable NAME" .to_case(Case::Lower)) |
| 49 | /// ``` |
| 50 | Lower, |
| 51 | |
| 52 | /// Title case strings are delimited by spaces. Only the leading character of |
| 53 | /// each word is uppercase. No inferences are made about language, so words |
| 54 | /// like "as", "to", and "for" will still be capitalized. |
| 55 | /// * Boundaries: [Space](`Boundary::Space`) |
| 56 | /// * Pattern: [Capital](`Pattern::Capital`) |
| 57 | /// * Delimeter: Space |
| 58 | /// |
| 59 | /// ``` |
| 60 | /// use convert_case::{Case, Casing}; |
| 61 | /// assert_eq!("My Variable Name" , "My variable NAME" .to_case(Case::Title)) |
| 62 | /// ``` |
| 63 | Title, |
| 64 | |
| 65 | /// Toggle case strings are delimited by spaces. All characters are uppercase except |
| 66 | /// for the leading character of each word, which is lowercase. |
| 67 | /// * Boundaries: [Space](`Boundary::Space`) |
| 68 | /// * Pattern: [Toggle](`Pattern::Toggle`) |
| 69 | /// * Delimeter: Space |
| 70 | /// |
| 71 | /// ``` |
| 72 | /// use convert_case::{Case, Casing}; |
| 73 | /// assert_eq!("mY vARIABLE nAME" , "My variable NAME" .to_case(Case::Toggle)) |
| 74 | /// ``` |
| 75 | Toggle, |
| 76 | |
| 77 | /// Camel case strings are lowercase, but for every word _except the first_ the |
| 78 | /// first letter is capitalized. |
| 79 | /// * Boundaries: [LowerUpper](Boundary::LowerUpper), [DigitUpper](Boundary::DigitUpper), |
| 80 | /// [UpperDigit](Boundary::UpperDigit), [DigitLower](Boundary::DigitLower), |
| 81 | /// [LowerDigit](Boundary::LowerDigit), [Acronym](Boundary::Acronym) |
| 82 | /// * Pattern: [Camel](`Pattern::Camel`) |
| 83 | /// * Delimeter: No delimeter |
| 84 | /// |
| 85 | /// ``` |
| 86 | /// use convert_case::{Case, Casing}; |
| 87 | /// assert_eq!("myVariableName" , "My variable NAME" .to_case(Case::Camel)) |
| 88 | /// ``` |
| 89 | Camel, |
| 90 | |
| 91 | /// Pascal case strings are lowercase, but for every word the |
| 92 | /// first letter is capitalized. |
| 93 | /// * Boundaries: [LowerUpper](Boundary::LowerUpper), [DigitUpper](Boundary::DigitUpper), |
| 94 | /// [UpperDigit](Boundary::UpperDigit), [DigitLower](Boundary::DigitLower), |
| 95 | /// [LowerDigit](Boundary::LowerDigit), [Acronym](Boundary::Acronym) |
| 96 | /// * Pattern: [Capital](`Pattern::Capital`) |
| 97 | /// * Delimeter: No delimeter |
| 98 | /// |
| 99 | /// ``` |
| 100 | /// use convert_case::{Case, Casing}; |
| 101 | /// assert_eq!("MyVariableName" , "My variable NAME" .to_case(Case::Pascal)) |
| 102 | /// ``` |
| 103 | Pascal, |
| 104 | |
| 105 | /// Upper camel case is an alternative name for [Pascal case](Case::Pascal). |
| 106 | UpperCamel, |
| 107 | |
| 108 | /// Snake case strings are delimited by underscores `_` and are all lowercase. |
| 109 | /// * Boundaries: [Underscore](Boundary::Underscore) |
| 110 | /// * Pattern: [Lowercase](Pattern::Lowercase) |
| 111 | /// * Delimeter: Underscore `_` |
| 112 | /// |
| 113 | /// ``` |
| 114 | /// use convert_case::{Case, Casing}; |
| 115 | /// assert_eq!("my_variable_name" , "My variable NAME" .to_case(Case::Snake)) |
| 116 | /// ``` |
| 117 | Snake, |
| 118 | |
| 119 | /// Upper snake case strings are delimited by underscores `_` and are all uppercase. |
| 120 | /// * Boundaries: [Underscore](Boundary::Underscore) |
| 121 | /// * Pattern: [Uppercase](Pattern::Uppercase) |
| 122 | /// * Delimeter: Underscore `_` |
| 123 | /// |
| 124 | /// ``` |
| 125 | /// use convert_case::{Case, Casing}; |
| 126 | /// assert_eq!("MY_VARIABLE_NAME" , "My variable NAME" .to_case(Case::UpperSnake)) |
| 127 | /// ``` |
| 128 | UpperSnake, |
| 129 | |
| 130 | /// Screaming snake case is an alternative name for [upper snake case](Case::UpperSnake). |
| 131 | ScreamingSnake, |
| 132 | |
| 133 | /// Kebab case strings are delimited by hyphens `-` and are all lowercase. |
| 134 | /// * Boundaries: [Hyphen](Boundary::Hyphen) |
| 135 | /// * Pattern: [Lowercase](Pattern::Lowercase) |
| 136 | /// * Delimeter: Hyphen `-` |
| 137 | /// |
| 138 | /// ``` |
| 139 | /// use convert_case::{Case, Casing}; |
| 140 | /// assert_eq!("my-variable-name" , "My variable NAME" .to_case(Case::Kebab)) |
| 141 | /// ``` |
| 142 | Kebab, |
| 143 | |
| 144 | /// Cobol case strings are delimited by hyphens `-` and are all uppercase. |
| 145 | /// * Boundaries: [Hyphen](Boundary::Hyphen) |
| 146 | /// * Pattern: [Uppercase](Pattern::Uppercase) |
| 147 | /// * Delimeter: Hyphen `-` |
| 148 | /// |
| 149 | /// ``` |
| 150 | /// use convert_case::{Case, Casing}; |
| 151 | /// assert_eq!("MY-VARIABLE-NAME" , "My variable NAME" .to_case(Case::Cobol)) |
| 152 | /// ``` |
| 153 | Cobol, |
| 154 | |
| 155 | /// Upper kebab case is an alternative name for [Cobol case](Case::Cobol). |
| 156 | UpperKebab, |
| 157 | |
| 158 | /// Train case strings are delimited by hyphens `-`. All characters are lowercase |
| 159 | /// except for the leading character of each word. |
| 160 | /// * Boundaries: [Hyphen](Boundary::Hyphen) |
| 161 | /// * Pattern: [Capital](Pattern::Capital) |
| 162 | /// * Delimeter: Hyphen `-` |
| 163 | /// |
| 164 | /// ``` |
| 165 | /// use convert_case::{Case, Casing}; |
| 166 | /// assert_eq!("My-Variable-Name" , "My variable NAME" .to_case(Case::Train)) |
| 167 | /// ``` |
| 168 | Train, |
| 169 | |
| 170 | /// Flat case strings are all lowercase, with no delimiter. Note that word boundaries are lost. |
| 171 | /// * Boundaries: No boundaries |
| 172 | /// * Pattern: [Lowercase](Pattern::Lowercase) |
| 173 | /// * Delimeter: No delimeter |
| 174 | /// |
| 175 | /// ``` |
| 176 | /// use convert_case::{Case, Casing}; |
| 177 | /// assert_eq!("myvariablename" , "My variable NAME" .to_case(Case::Flat)) |
| 178 | /// ``` |
| 179 | Flat, |
| 180 | |
| 181 | /// Upper flat case strings are all uppercase, with no delimiter. Note that word boundaries are lost. |
| 182 | /// * Boundaries: No boundaries |
| 183 | /// * Pattern: [Uppercase](Pattern::Uppercase) |
| 184 | /// * Delimeter: No delimeter |
| 185 | /// |
| 186 | /// ``` |
| 187 | /// use convert_case::{Case, Casing}; |
| 188 | /// assert_eq!("MYVARIABLENAME" , "My variable NAME" .to_case(Case::UpperFlat)) |
| 189 | /// ``` |
| 190 | UpperFlat, |
| 191 | |
| 192 | /// Alternating case strings are delimited by spaces. Characters alternate between uppercase |
| 193 | /// and lowercase. |
| 194 | /// * Boundaries: [Space](Boundary::Space) |
| 195 | /// * Pattern: [Alternating](Pattern::Alternating) |
| 196 | /// * Delimeter: Space |
| 197 | /// |
| 198 | /// ``` |
| 199 | /// use convert_case::{Case, Casing}; |
| 200 | /// assert_eq!("mY vArIaBlE nAmE" , "My variable NAME" .to_case(Case::Alternating)); |
| 201 | /// ``` |
| 202 | Alternating, |
| 203 | |
| 204 | /// Random case strings are delimited by spaces and characters are |
| 205 | /// randomly upper case or lower case. This uses the `rand` crate |
| 206 | /// and is only available with the "random" feature. |
| 207 | /// * Boundaries: [Space](Boundary::Space) |
| 208 | /// * Pattern: [Random](Pattern::Random) |
| 209 | /// * Delimeter: Space |
| 210 | /// |
| 211 | /// ``` |
| 212 | /// use convert_case::{Case, Casing}; |
| 213 | /// let new = "My variable NAME".to_case(Case::Random); |
| 214 | /// ``` |
| 215 | /// String `new` could be "My vaRIAbLE nAme" for example. |
| 216 | #[cfg (any(doc, feature = "random" ))] |
| 217 | Random, |
| 218 | |
| 219 | /// Pseudo-random case strings are delimited by spaces and characters are randomly |
| 220 | /// upper case or lower case, but there will never more than two consecutive lower |
| 221 | /// case or upper case letters in a row. This uses the `rand` crate and is |
| 222 | /// only available with the "random" feature. |
| 223 | /// * Boundaries: [Space](Boundary::Space) |
| 224 | /// * Pattern: [PseudoRandom](Pattern::PseudoRandom) |
| 225 | /// * Delimeter: Space |
| 226 | /// |
| 227 | /// ``` |
| 228 | /// use convert_case::{Case, Casing}; |
| 229 | /// let new = "My variable NAME".to_case(Case::Random); |
| 230 | /// ``` |
| 231 | /// String `new` could be "mY vArIAblE NamE" for example. |
| 232 | #[cfg (any(doc, feature = "random" ))] |
| 233 | PseudoRandom, |
| 234 | } |
| 235 | |
| 236 | impl Case { |
| 237 | /// Returns the delimiter used in the corresponding case. The following |
| 238 | /// table outlines which cases use which delimeter. |
| 239 | /// |
| 240 | /// | Cases | Delimeter | |
| 241 | /// | --- | --- | |
| 242 | /// | Upper, Lower, Title, Toggle, Alternating, Random, PseudoRandom | Space | |
| 243 | /// | Snake, UpperSnake, ScreamingSnake | Underscore `_` | |
| 244 | /// | Kebab, Cobol, UpperKebab, Train | Hyphen `-` | |
| 245 | /// | UpperFlat, Flat, Camel, UpperCamel, Pascal | Empty string, no delimeter | |
| 246 | pub const fn delim(&self) -> &'static str { |
| 247 | use Case::*; |
| 248 | match self { |
| 249 | Upper | Lower | Title | Toggle | Alternating => " " , |
| 250 | Snake | UpperSnake | ScreamingSnake => "_" , |
| 251 | Kebab | Cobol | UpperKebab | Train => "-" , |
| 252 | |
| 253 | #[cfg (feature = "random" )] |
| 254 | Random | PseudoRandom => " " , |
| 255 | |
| 256 | UpperFlat | Flat | Camel | UpperCamel | Pascal => "" , |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | /// Returns the pattern used in the corresponding case. The following |
| 261 | /// table outlines which cases use which pattern. |
| 262 | /// |
| 263 | /// | Cases | Pattern | |
| 264 | /// | --- | --- | |
| 265 | /// | Upper, UpperSnake, ScreamingSnake, UpperFlat, Cobol, UpperKebab | Uppercase | |
| 266 | /// | Lower, Snake, Kebab, Flat | Lowercase | |
| 267 | /// | Title, Pascal, UpperCamel, Train | Capital | |
| 268 | /// | Camel | Camel | |
| 269 | /// | Alternating | Alternating | |
| 270 | /// | Random | Random | |
| 271 | /// | PseudoRandom | PseudoRandom | |
| 272 | pub const fn pattern(&self) -> Pattern { |
| 273 | use Case::*; |
| 274 | match self { |
| 275 | Upper | UpperSnake | ScreamingSnake | UpperFlat | Cobol | UpperKebab => { |
| 276 | Pattern::Uppercase |
| 277 | } |
| 278 | Lower | Snake | Kebab | Flat => Pattern::Lowercase, |
| 279 | Title | Pascal | UpperCamel | Train => Pattern::Capital, |
| 280 | Camel => Pattern::Camel, |
| 281 | Toggle => Pattern::Toggle, |
| 282 | Alternating => Pattern::Alternating, |
| 283 | |
| 284 | #[cfg (feature = "random" )] |
| 285 | Random => Pattern::Random, |
| 286 | #[cfg (feature = "random" )] |
| 287 | PseudoRandom => Pattern::PseudoRandom, |
| 288 | } |
| 289 | } |
| 290 | |
| 291 | /// Returns the boundaries used in the corresponding case. That is, where can word boundaries |
| 292 | /// be distinguished in a string of the given case. The table outlines which cases use which |
| 293 | /// set of boundaries. |
| 294 | /// |
| 295 | /// | Cases | Boundaries | |
| 296 | /// | --- | --- | |
| 297 | /// | Upper, Lower, Title, Toggle, Alternating, Random, PseudoRandom | Space | |
| 298 | /// | Snake, UpperSnake, ScreamingSnake | Underscore `_` | |
| 299 | /// | Kebab, Cobol, UpperKebab, Train | Hyphen `-` | |
| 300 | /// | Camel, UpperCamel, Pascal | LowerUpper, LowerDigit, UpperDigit, DigitLower, DigitUpper, Acronym | |
| 301 | /// | UpperFlat, Flat | No boundaries | |
| 302 | pub fn boundaries(&self) -> Vec<Boundary> { |
| 303 | use Boundary::*; |
| 304 | use Case::*; |
| 305 | match self { |
| 306 | Upper | Lower | Title | Toggle | Alternating => vec![Space], |
| 307 | Snake | UpperSnake | ScreamingSnake => vec![Underscore], |
| 308 | Kebab | Cobol | UpperKebab | Train => vec![Hyphen], |
| 309 | |
| 310 | #[cfg (feature = "random" )] |
| 311 | Random | PseudoRandom => vec![Space], |
| 312 | |
| 313 | UpperFlat | Flat => vec![], |
| 314 | Camel | UpperCamel | Pascal => vec![ |
| 315 | LowerUpper, Acronym, LowerDigit, UpperDigit, DigitLower, DigitUpper, |
| 316 | ], |
| 317 | } |
| 318 | } |
| 319 | |
| 320 | // Created to avoid using the EnumIter trait from strum in |
| 321 | // final library. A test confirms that all cases are listed here. |
| 322 | /// Returns a vector with all case enum variants in no particular order. |
| 323 | pub fn all_cases() -> Vec<Case> { |
| 324 | use Case::*; |
| 325 | vec![ |
| 326 | Upper, |
| 327 | Lower, |
| 328 | Title, |
| 329 | Toggle, |
| 330 | Camel, |
| 331 | Pascal, |
| 332 | UpperCamel, |
| 333 | Snake, |
| 334 | UpperSnake, |
| 335 | ScreamingSnake, |
| 336 | Kebab, |
| 337 | Cobol, |
| 338 | UpperKebab, |
| 339 | Train, |
| 340 | Flat, |
| 341 | UpperFlat, |
| 342 | Alternating, |
| 343 | #[cfg (feature = "random" )] |
| 344 | Random, |
| 345 | #[cfg (feature = "random" )] |
| 346 | PseudoRandom, |
| 347 | ] |
| 348 | } |
| 349 | |
| 350 | /// Returns a vector with the two "random" feature cases `Random` and `PseudoRandom`. Only |
| 351 | /// defined in the "random" feature. |
| 352 | #[cfg (feature = "random" )] |
| 353 | pub fn random_cases() -> Vec<Case> { |
| 354 | use Case::*; |
| 355 | vec![Random, PseudoRandom] |
| 356 | } |
| 357 | |
| 358 | /// Returns a vector with all the cases that do not depend on randomness. This is all |
| 359 | /// the cases not in the "random" feature. |
| 360 | pub fn deterministic_cases() -> Vec<Case> { |
| 361 | use Case::*; |
| 362 | vec![ |
| 363 | Upper, |
| 364 | Lower, |
| 365 | Title, |
| 366 | Toggle, |
| 367 | Camel, |
| 368 | Pascal, |
| 369 | UpperCamel, |
| 370 | Snake, |
| 371 | UpperSnake, |
| 372 | ScreamingSnake, |
| 373 | Kebab, |
| 374 | Cobol, |
| 375 | UpperKebab, |
| 376 | Train, |
| 377 | Flat, |
| 378 | UpperFlat, |
| 379 | Alternating, |
| 380 | ] |
| 381 | } |
| 382 | } |
| 383 | |
| 384 | #[cfg (test)] |
| 385 | mod test { |
| 386 | |
| 387 | use super::*; |
| 388 | use strum::IntoEnumIterator; |
| 389 | |
| 390 | #[test ] |
| 391 | fn all_cases_in_iter() { |
| 392 | let all = Case::all_cases(); |
| 393 | for case in Case::iter() { |
| 394 | assert!(all.contains(&case)); |
| 395 | } |
| 396 | } |
| 397 | } |
| 398 | |