| 1 | #[cfg (test)] |
| 2 | use strum::EnumIter; |
| 3 | |
| 4 | use crate::boundary::Boundary; |
| 5 | use crate::pattern::Pattern; |
| 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 | /// Sentence case strings are delimited by spaces. Only the leading character of |
| 66 | /// the first word is uppercase. |
| 67 | /// * Boundaries: [Space](`Boundary::SPACE`) |
| 68 | /// * Pattern: [Capital](`Pattern::Sentence`) |
| 69 | /// * Delimeter: Space |
| 70 | /// |
| 71 | /// ``` |
| 72 | /// use convert_case::{Case, Casing}; |
| 73 | /// assert_eq!("My variable name" , "My variable NAME" .to_case(Case::Sentence)) |
| 74 | /// ``` |
| 75 | Sentence, |
| 76 | |
| 77 | /// Toggle case strings are delimited by spaces. All characters are uppercase except |
| 78 | /// for the leading character of each word, which is lowercase. |
| 79 | /// * Boundaries: [Space](`Boundary::SPACE`) |
| 80 | /// * Pattern: [Toggle](`Pattern::Toggle`) |
| 81 | /// * Delimeter: Space |
| 82 | /// |
| 83 | /// ``` |
| 84 | /// use convert_case::{Case, Casing}; |
| 85 | /// assert_eq!("mY vARIABLE nAME" , "My variable NAME" .to_case(Case::Toggle)) |
| 86 | /// ``` |
| 87 | Toggle, |
| 88 | |
| 89 | /// Camel case strings are lowercase, but for every word _except the first_ the |
| 90 | /// first letter is capitalized. |
| 91 | /// * Boundaries: [LowerUpper](Boundary::LOWER_UPPER), [DigitUpper](Boundary::DIGIT_UPPER), |
| 92 | /// [UpperDigit](Boundary::UPPER_DIGIT), [DigitLower](Boundary::DIGIT_LOWER), |
| 93 | /// [LowerDigit](Boundary::LOWER_DIGIT), [Acronym](Boundary::ACRONYM) |
| 94 | /// * Pattern: [Camel](`Pattern::Camel`) |
| 95 | /// * Delimeter: No delimeter |
| 96 | /// |
| 97 | /// ``` |
| 98 | /// use convert_case::{Case, Casing}; |
| 99 | /// assert_eq!("myVariableName" , "My variable NAME" .to_case(Case::Camel)) |
| 100 | /// ``` |
| 101 | Camel, |
| 102 | |
| 103 | /// Pascal case strings are lowercase, but for every word the |
| 104 | /// first letter is capitalized. |
| 105 | /// * Boundaries: [LowerUpper](Boundary::LOWER_UPPER), [DigitUpper](Boundary::DIGIT_UPPER), |
| 106 | /// [UpperDigit](Boundary::UPPER_DIGIT), [DigitLower](Boundary::DIGIT_LOWER), |
| 107 | /// [LowerDigit](Boundary::LOWER_DIGIT), [Acronym](Boundary::ACRONYM) |
| 108 | /// * Pattern: [Capital](`Pattern::Capital`) |
| 109 | /// * Delimeter: No delimeter |
| 110 | /// |
| 111 | /// ``` |
| 112 | /// use convert_case::{Case, Casing}; |
| 113 | /// assert_eq!("MyVariableName" , "My variable NAME" .to_case(Case::Pascal)) |
| 114 | /// ``` |
| 115 | Pascal, |
| 116 | |
| 117 | /// Upper camel case is an alternative name for [Pascal case](Case::Pascal). |
| 118 | UpperCamel, |
| 119 | |
| 120 | /// Snake case strings are delimited by underscores `_` and are all lowercase. |
| 121 | /// * Boundaries: [Underscore](Boundary::UNDERSCORE) |
| 122 | /// * Pattern: [Lowercase](Pattern::Lowercase) |
| 123 | /// * Delimeter: Underscore `_` |
| 124 | /// |
| 125 | /// ``` |
| 126 | /// use convert_case::{Case, Casing}; |
| 127 | /// assert_eq!("my_variable_name" , "My variable NAME" .to_case(Case::Snake)) |
| 128 | /// ``` |
| 129 | Snake, |
| 130 | |
| 131 | /// Constant case strings are delimited by underscores `_` and are all uppercase. |
| 132 | /// * Boundaries: [Underscore](Boundary::UNDERSCORE) |
| 133 | /// * Pattern: [Uppercase](Pattern::Uppercase) |
| 134 | /// * Delimeter: Underscore `_` |
| 135 | /// |
| 136 | /// ``` |
| 137 | /// use convert_case::{Case, Casing}; |
| 138 | /// assert_eq!("MY_VARIABLE_NAME" , "My variable NAME" .to_case(Case::Constant)) |
| 139 | /// ``` |
| 140 | Constant, |
| 141 | |
| 142 | /// Upper snake case is an alternative name for [constant case](Case::Constant). |
| 143 | UpperSnake, |
| 144 | |
| 145 | /// Kebab case strings are delimited by hyphens `-` and are all lowercase. |
| 146 | /// * Boundaries: [Hyphen](Boundary::HYPHEN) |
| 147 | /// * Pattern: [Lowercase](Pattern::Lowercase) |
| 148 | /// * Delimeter: Hyphen `-` |
| 149 | /// |
| 150 | /// ``` |
| 151 | /// use convert_case::{Case, Casing}; |
| 152 | /// assert_eq!("my-variable-name" , "My variable NAME" .to_case(Case::Kebab)) |
| 153 | /// ``` |
| 154 | Kebab, |
| 155 | |
| 156 | /// Cobol case strings are delimited by hyphens `-` and are all uppercase. |
| 157 | /// * Boundaries: [Hyphen](Boundary::HYPHEN) |
| 158 | /// * Pattern: [Uppercase](Pattern::Uppercase) |
| 159 | /// * Delimeter: Hyphen `-` |
| 160 | /// |
| 161 | /// ``` |
| 162 | /// use convert_case::{Case, Casing}; |
| 163 | /// assert_eq!("MY-VARIABLE-NAME" , "My variable NAME" .to_case(Case::Cobol)) |
| 164 | /// ``` |
| 165 | Cobol, |
| 166 | |
| 167 | /// Upper kebab case is an alternative name for [Cobol case](Case::Cobol). |
| 168 | UpperKebab, |
| 169 | |
| 170 | /// Train case strings are delimited by hyphens `-`. All characters are lowercase |
| 171 | /// except for the leading character of each word. |
| 172 | /// * Boundaries: [Hyphen](Boundary::HYPHEN) |
| 173 | /// * Pattern: [Capital](Pattern::Capital) |
| 174 | /// * Delimeter: Hyphen `-` |
| 175 | /// |
| 176 | /// ``` |
| 177 | /// use convert_case::{Case, Casing}; |
| 178 | /// assert_eq!("My-Variable-Name" , "My variable NAME" .to_case(Case::Train)) |
| 179 | /// ``` |
| 180 | Train, |
| 181 | |
| 182 | /// Flat case strings are all lowercase, with no delimiter. Note that word boundaries are lost. |
| 183 | /// * Boundaries: No boundaries |
| 184 | /// * Pattern: [Lowercase](Pattern::Lowercase) |
| 185 | /// * Delimeter: No delimeter |
| 186 | /// |
| 187 | /// ``` |
| 188 | /// use convert_case::{Case, Casing}; |
| 189 | /// assert_eq!("myvariablename" , "My variable NAME" .to_case(Case::Flat)) |
| 190 | /// ``` |
| 191 | Flat, |
| 192 | |
| 193 | /// Upper flat case strings are all uppercase, with no delimiter. Note that word boundaries are lost. |
| 194 | /// * Boundaries: No boundaries |
| 195 | /// * Pattern: [Uppercase](Pattern::Uppercase) |
| 196 | /// * Delimeter: No delimeter |
| 197 | /// |
| 198 | /// ``` |
| 199 | /// use convert_case::{Case, Casing}; |
| 200 | /// assert_eq!("MYVARIABLENAME" , "My variable NAME" .to_case(Case::UpperFlat)) |
| 201 | /// ``` |
| 202 | UpperFlat, |
| 203 | |
| 204 | /// Alternating case strings are delimited by spaces. Characters alternate between uppercase |
| 205 | /// and lowercase. |
| 206 | /// * Boundaries: [Space](Boundary::SPACE) |
| 207 | /// * Pattern: [Alternating](Pattern::Alternating) |
| 208 | /// * Delimeter: Space |
| 209 | /// |
| 210 | /// ``` |
| 211 | /// use convert_case::{Case, Casing}; |
| 212 | /// assert_eq!("mY vArIaBlE nAmE" , "My variable NAME" .to_case(Case::Alternating)); |
| 213 | /// ``` |
| 214 | Alternating, |
| 215 | |
| 216 | /// Random case strings are delimited by spaces and characters are |
| 217 | /// randomly upper case or lower case. This uses the `rand` crate |
| 218 | /// and is only available with the "random" feature. |
| 219 | /// * Boundaries: [Space](Boundary::SPACE) |
| 220 | /// * Pattern: [Random](Pattern::Random) |
| 221 | /// * Delimeter: Space |
| 222 | /// |
| 223 | /// ``` |
| 224 | /// use convert_case::{Case, Casing}; |
| 225 | /// # #[cfg(any(doc, feature = "random"))] |
| 226 | /// let new = "My variable NAME".to_case(Case::Random); |
| 227 | /// ``` |
| 228 | /// String `new` could be "My vaRIAbLE nAme" for example. |
| 229 | #[cfg (any(doc, feature = "random" ))] |
| 230 | #[cfg (feature = "random" )] |
| 231 | Random, |
| 232 | |
| 233 | /// Pseudo-random case strings are delimited by spaces and characters are randomly |
| 234 | /// upper case or lower case, but there will never more than two consecutive lower |
| 235 | /// case or upper case letters in a row. This uses the `rand` crate and is |
| 236 | /// only available with the "random" feature. |
| 237 | /// * Boundaries: [Space](Boundary::SPACE) |
| 238 | /// * Pattern: [PseudoRandom](Pattern::PseudoRandom) |
| 239 | /// * Delimeter: Space |
| 240 | /// |
| 241 | /// ``` |
| 242 | /// use convert_case::{Case, Casing}; |
| 243 | /// # #[cfg(any(doc, feature = "random"))] |
| 244 | /// let new = "My variable NAME".to_case(Case::Random); |
| 245 | /// ``` |
| 246 | /// String `new` could be "mY vArIAblE NamE" for example. |
| 247 | #[cfg (any(doc, feature = "random" ))] |
| 248 | #[cfg (feature = "random" )] |
| 249 | PseudoRandom, |
| 250 | } |
| 251 | |
| 252 | impl Case { |
| 253 | /// Returns the delimiter used in the corresponding case. The following |
| 254 | /// table outlines which cases use which delimeter. |
| 255 | /// |
| 256 | /// | Cases | Delimeter | |
| 257 | /// | --- | --- | |
| 258 | /// | Upper, Lower, Title, Toggle, Alternating, Random, PseudoRandom | Space | |
| 259 | /// | Snake, Constant, UpperSnake | Underscore `_` | |
| 260 | /// | Kebab, Cobol, UpperKebab, Train | Hyphen `-` | |
| 261 | /// | UpperFlat, Flat, Camel, UpperCamel, Pascal | Empty string, no delimeter | |
| 262 | pub const fn delim(&self) -> &'static str { |
| 263 | use Case::*; |
| 264 | match self { |
| 265 | Upper | Lower | Title | Sentence | Toggle | Alternating => " " , |
| 266 | Snake | Constant | UpperSnake => "_" , |
| 267 | Kebab | Cobol | UpperKebab | Train => "-" , |
| 268 | |
| 269 | #[cfg (feature = "random" )] |
| 270 | Random | PseudoRandom => " " , |
| 271 | |
| 272 | UpperFlat | Flat | Camel | UpperCamel | Pascal => "" , |
| 273 | } |
| 274 | } |
| 275 | |
| 276 | /// Returns the pattern used in the corresponding case. The following |
| 277 | /// table outlines which cases use which pattern. |
| 278 | /// |
| 279 | /// | Cases | Pattern | |
| 280 | /// | --- | --- | |
| 281 | /// | Upper, Constant, UpperSnake, UpperFlat, Cobol, UpperKebab | Uppercase | |
| 282 | /// | Lower, Snake, Kebab, Flat | Lowercase | |
| 283 | /// | Title, Pascal, UpperCamel, Train | Capital | |
| 284 | /// | Camel | Camel | |
| 285 | /// | Alternating | Alternating | |
| 286 | /// | Random | Random | |
| 287 | /// | PseudoRandom | PseudoRandom | |
| 288 | pub const fn pattern(&self) -> Pattern { |
| 289 | use Case::*; |
| 290 | match self { |
| 291 | Upper | Constant | UpperSnake | UpperFlat | Cobol | UpperKebab => Pattern::Uppercase, |
| 292 | Lower | Snake | Kebab | Flat => Pattern::Lowercase, |
| 293 | Title | Pascal | UpperCamel | Train => Pattern::Capital, |
| 294 | Camel => Pattern::Camel, |
| 295 | Toggle => Pattern::Toggle, |
| 296 | Alternating => Pattern::Alternating, |
| 297 | Sentence => Pattern::Sentence, |
| 298 | |
| 299 | #[cfg (feature = "random" )] |
| 300 | Random => Pattern::Random, |
| 301 | #[cfg (feature = "random" )] |
| 302 | PseudoRandom => Pattern::PseudoRandom, |
| 303 | } |
| 304 | } |
| 305 | |
| 306 | /// Returns the boundaries used in the corresponding case. That is, where can word boundaries |
| 307 | /// be distinguished in a string of the given case. The table outlines which cases use which |
| 308 | /// set of boundaries. |
| 309 | /// |
| 310 | /// | Cases | Boundaries | |
| 311 | /// | --- | --- | |
| 312 | /// | Upper, Lower, Title, Toggle, Alternating, Random, PseudoRandom | Space | |
| 313 | /// | Snake, Constant, UpperSnake | Underscore `_` | |
| 314 | /// | Kebab, Cobol, UpperKebab, Train | Hyphen `-` | |
| 315 | /// | Camel, UpperCamel, Pascal | LowerUpper, LowerDigit, UpperDigit, DigitLower, DigitUpper, Acronym | |
| 316 | /// | UpperFlat, Flat | No boundaries | |
| 317 | pub fn boundaries(&self) -> Vec<Boundary> { |
| 318 | use Case::*; |
| 319 | match self { |
| 320 | Upper | Lower | Title | Sentence | Toggle | Alternating => vec![Boundary::SPACE], |
| 321 | Snake | Constant | UpperSnake => vec![Boundary::UNDERSCORE], |
| 322 | Kebab | Cobol | UpperKebab | Train => vec![Boundary::HYPHEN], |
| 323 | |
| 324 | #[cfg (feature = "random" )] |
| 325 | Random | PseudoRandom => vec![Boundary::SPACE], |
| 326 | |
| 327 | UpperFlat | Flat => vec![], |
| 328 | Camel | UpperCamel | Pascal => vec![ |
| 329 | Boundary::LOWER_UPPER, |
| 330 | Boundary::ACRONYM, |
| 331 | Boundary::LOWER_DIGIT, |
| 332 | Boundary::UPPER_DIGIT, |
| 333 | Boundary::DIGIT_LOWER, |
| 334 | Boundary::DIGIT_UPPER, |
| 335 | ], |
| 336 | } |
| 337 | } |
| 338 | |
| 339 | // Created to avoid using the EnumIter trait from strum in |
| 340 | // final library. A test confirms that all cases are listed here. |
| 341 | // Why is this needed? If it's only for ccase then I don't see why it's here. |
| 342 | /// Returns a vector with all case enum variants in no particular order. |
| 343 | pub fn all_cases() -> Vec<Case> { |
| 344 | use Case::*; |
| 345 | vec![ |
| 346 | Upper, |
| 347 | Lower, |
| 348 | Title, |
| 349 | Sentence, |
| 350 | Toggle, |
| 351 | Camel, |
| 352 | Pascal, |
| 353 | UpperCamel, |
| 354 | Snake, |
| 355 | Constant, |
| 356 | UpperSnake, |
| 357 | Kebab, |
| 358 | Cobol, |
| 359 | UpperKebab, |
| 360 | Train, |
| 361 | Flat, |
| 362 | UpperFlat, |
| 363 | Alternating, |
| 364 | #[cfg (feature = "random" )] |
| 365 | Random, |
| 366 | #[cfg (feature = "random" )] |
| 367 | PseudoRandom, |
| 368 | ] |
| 369 | } |
| 370 | |
| 371 | /// Returns a vector with the two "random" feature cases `Random` and `PseudoRandom`. Only |
| 372 | /// defined in the "random" feature. |
| 373 | #[cfg (feature = "random" )] |
| 374 | pub fn random_cases() -> Vec<Case> { |
| 375 | use Case::*; |
| 376 | vec![Random, PseudoRandom] |
| 377 | } |
| 378 | |
| 379 | /// Returns a vector with all the cases that do not depend on randomness. This is all |
| 380 | /// the cases not in the "random" feature. |
| 381 | pub fn deterministic_cases() -> Vec<Case> { |
| 382 | use Case::*; |
| 383 | vec![ |
| 384 | Upper, |
| 385 | Lower, |
| 386 | Title, |
| 387 | Sentence, |
| 388 | Toggle, |
| 389 | Camel, |
| 390 | Pascal, |
| 391 | UpperCamel, |
| 392 | Snake, |
| 393 | Constant, |
| 394 | UpperSnake, |
| 395 | Kebab, |
| 396 | Cobol, |
| 397 | UpperKebab, |
| 398 | Train, |
| 399 | Flat, |
| 400 | UpperFlat, |
| 401 | Alternating, |
| 402 | ] |
| 403 | } |
| 404 | } |
| 405 | |
| 406 | #[cfg (test)] |
| 407 | mod test { |
| 408 | |
| 409 | use super::*; |
| 410 | use strum::IntoEnumIterator; |
| 411 | |
| 412 | #[test ] |
| 413 | fn all_cases_in_iter() { |
| 414 | let all = Case::all_cases(); |
| 415 | for case in Case::iter() { |
| 416 | assert!(all.contains(&case)); |
| 417 | } |
| 418 | } |
| 419 | } |
| 420 | |