summaryrefslogtreecommitdiffstats
path: root/vendor/convert_case/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:18:25 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:18:25 +0000
commit5363f350887b1e5b5dd21a86f88c8af9d7fea6da (patch)
tree35ca005eb6e0e9a1ba3bb5dbc033209ad445dc17 /vendor/convert_case/src
parentAdding debian version 1.66.0+dfsg1-1. (diff)
downloadrustc-5363f350887b1e5b5dd21a86f88c8af9d7fea6da.tar.xz
rustc-5363f350887b1e5b5dd21a86f88c8af9d7fea6da.zip
Merging upstream version 1.67.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/convert_case/src')
-rw-r--r--vendor/convert_case/src/case.rs212
-rw-r--r--vendor/convert_case/src/lib.rs337
-rw-r--r--vendor/convert_case/src/words.rs377
3 files changed, 926 insertions, 0 deletions
diff --git a/vendor/convert_case/src/case.rs b/vendor/convert_case/src/case.rs
new file mode 100644
index 000000000..f7a9c8c9c
--- /dev/null
+++ b/vendor/convert_case/src/case.rs
@@ -0,0 +1,212 @@
+#[cfg(test)]
+use strum_macros::EnumIter;
+
+/// Defines the type of casing a string can be.
+///
+/// ```
+/// use convert_case::{Case, Casing};
+///
+/// let super_mario_title: String = "super_mario_64".to_case(Case::Title);
+/// assert_eq!("Super Mario 64", super_mario_title);
+/// ```
+#[cfg_attr(test, derive(EnumIter))]
+#[derive(Eq, PartialEq, Clone, Copy, Debug)]
+pub enum Case {
+
+ /// Uppercase strings are delimited by spaces and all characters are uppercase.
+ ///
+ /// ```
+ /// use convert_case::{Case, Casing};
+ /// assert_eq!("MY VARIABLE NAME", "My variable NAME".to_case(Case::Upper))
+ /// ```
+ Upper,
+
+ /// Lowercase strings are delimited by spaces and all characters are lowercase.
+ ///
+ /// ```
+ /// use convert_case::{Case, Casing};
+ /// assert_eq!("my variable name", "My variable NAME".to_case(Case::Lower))
+ /// ```
+ Lower,
+
+ /// Title case strings are delimited by spaces. Only the leading character of
+ /// each word is uppercase. No inferences are made about language, so words
+ /// like "as", "to", and "for" will still be capitalized.
+ ///
+ /// ```
+ /// use convert_case::{Case, Casing};
+ /// assert_eq!("My Variable Name", "My variable NAME".to_case(Case::Title))
+ /// ```
+ Title,
+
+ /// Toggle case strings are delimited by spaces. All characters are uppercase except
+ /// for the leading character of each word, which is lowercase.
+ ///
+ /// ```
+ /// use convert_case::{Case, Casing};
+ /// assert_eq!("mY vARIABLE nAME", "My variable NAME".to_case(Case::Toggle))
+ /// ```
+ Toggle,
+
+ /// Camel case strings are lowercase, but for every word _except the first_ the
+ /// first letter is capitalized.
+ ///
+ /// ```
+ /// use convert_case::{Case, Casing};
+ /// assert_eq!("myVariableName", "My variable NAME".to_case(Case::Camel))
+ /// ```
+ Camel,
+
+ /// Pascal case strings are lowercase, but for every word the
+ /// first letter is capitalized.
+ ///
+ /// ```
+ /// use convert_case::{Case, Casing};
+ /// assert_eq!("MyVariableName", "My variable NAME".to_case(Case::Pascal))
+ /// ```
+ Pascal,
+
+ /// Upper camel case is an alternative name for Pascal case.
+ UpperCamel,
+
+ /// Snake case strings are delimited by underscores `_` and are all lowercase.
+ ///
+ /// ```
+ /// use convert_case::{Case, Casing};
+ /// assert_eq!("my_variable_name", "My variable NAME".to_case(Case::Snake))
+ /// ```
+ Snake,
+
+ /// Upper snake case strings are delimited by underscores `_` and are all uppercase.
+ ///
+ /// ```
+ /// use convert_case::{Case, Casing};
+ /// assert_eq!("MY_VARIABLE_NAME", "My variable NAME".to_case(Case::UpperSnake))
+ /// ```
+ UpperSnake,
+
+ /// Screaming snake case is an alternative name for upper snake case.
+ ScreamingSnake,
+
+ /// Kebab case strings are delimited by hyphens `-` and are all lowercase.
+ ///
+ /// ```
+ /// use convert_case::{Case, Casing};
+ /// assert_eq!("my-variable-name", "My variable NAME".to_case(Case::Kebab))
+ /// ```
+ Kebab,
+
+ /// Cobol case strings are delimited by hyphens `-` and are all uppercase.
+ ///
+ /// ```
+ /// use convert_case::{Case, Casing};
+ /// assert_eq!("MY-VARIABLE-NAME", "My variable NAME".to_case(Case::Cobol))
+ /// ```
+ Cobol,
+
+ /// Train case strings are delimited by hyphens `-`. All characters are lowercase
+ /// except for the leading character of each word.
+ ///
+ /// ```
+ /// use convert_case::{Case, Casing};
+ /// assert_eq!("My-Variable-Name", "My variable NAME".to_case(Case::Train))
+ /// ```
+ Train,
+
+ /// Flat case strings are all lowercase, with no delimiter. Converting to
+ /// this case is **lossy**. That is, word boundaries are lost.
+ ///
+ /// ```
+ /// use convert_case::{Case, Casing};
+ /// assert_eq!("myvariablename", "My variable NAME".to_case(Case::Flat))
+ /// ```
+ Flat,
+
+ /// Upper flat case strings are all uppercase, with no delimiter. Converting to
+ /// this case is **lossy**. That is, word boundaries are lost.
+ ///
+ /// ```
+ /// use convert_case::{Case, Casing};
+ /// assert_eq!("MYVARIABLENAME", "My variable NAME".to_case(Case::UpperFlat))
+ /// ```
+ UpperFlat,
+
+ /// Alternating case strings are delimited by spaces. Characters alternate between uppercase
+ /// and lowercase.
+ /// ```
+ /// use convert_case::{Case, Casing};
+ /// assert_eq!("mY vArIaBlE nAmE", "My variable NAME".to_case(Case::Alternating));
+ /// ```
+ Alternating,
+
+ /// Random case strings are delimited by spaces and characters are
+ /// randomly upper case or lower case. This uses the `rand` crate
+ /// and is only available with the "random" feature.
+ /// ```
+ /// use convert_case::{Case, Casing};
+ /// let new = "My variable NAME".to_case(Case::Random);
+ /// ```
+ /// `new` could be "My vaRIAbLE nAme" for example.
+ #[cfg(feature = "random")]
+ Random,
+
+ /// Pseudo-random case strings are delimited by spaces and characters are randomly
+ /// upper case or lower case, but there will never more than two consecutive lower
+ /// case or upper case letters in a row. This uses the `rand` crate and is
+ /// only available with the "random" feature.
+ /// ```
+ /// use convert_case::{Case, Casing};
+ /// let new = "My variable NAME".to_case(Case::Random);
+ /// ```
+ /// `new` could be "mY vArIAblE NamE" for example.
+ #[cfg(feature = "random")]
+ PseudoRandom,
+}
+
+impl Case {
+ // Created to avoid using the EnumIter trait from strum in
+ // final library. A test confirms that all cases are listed here.
+ /// Returns a vector with all case enum variants. This was
+ /// created for use in the `ccase` binary.
+ pub fn all_cases() -> Vec<Case> {
+ use Case::*;
+ vec![
+ Upper,
+ Lower,
+ Title,
+ Toggle,
+ Camel,
+ Pascal,
+ UpperCamel,
+ Snake,
+ UpperSnake,
+ ScreamingSnake,
+ Kebab,
+ Cobol,
+ Train,
+ Flat,
+ UpperFlat,
+ Alternating,
+
+ #[cfg(feature = "random")]
+ Random,
+ #[cfg(feature = "random")]
+ PseudoRandom,
+ ]
+ }
+}
+
+#[cfg(test)]
+mod test {
+
+ use super::*;
+ use strum::IntoEnumIterator;
+
+ #[test]
+ fn all_cases_in_iter() {
+ let all = Case::all_cases();
+ for case in Case::iter() {
+ assert!(all.contains(&case));
+ }
+ }
+}
diff --git a/vendor/convert_case/src/lib.rs b/vendor/convert_case/src/lib.rs
new file mode 100644
index 000000000..29711655f
--- /dev/null
+++ b/vendor/convert_case/src/lib.rs
@@ -0,0 +1,337 @@
+//! Converts to and from various cases.
+//!
+//! # Command Line Utility `ccase`
+//!
+//! Since version "0.3.0" this crate is just a case conversion _library_. The command line utility
+//! that uses the tools in this library has been moved to the `ccase` crate. You can read about it
+//! at the [github repository](https://github.com/rutrum/convert-case/tree/master/ccase).
+//!
+//! # Rust Library
+//!
+//! Provides a [`Case`](enum.Case.html) enum which defines a variety of cases to convert into.
+//! A `Case` can be used with an item that implements the [`Casing`](trait.Casing.html) trait,
+//! which allows the item to be converted to a given case.
+//!
+//! You can convert a string or string slice into a case using the `to_case` method.
+//! ```
+//! use convert_case::{Case, Casing};
+//!
+//! assert_eq!("Ronnie James Dio", "ronnie james dio".to_case(Case::Title));
+//! assert_eq!("ronnieJamesDio", "Ronnie_James_dio".to_case(Case::Camel));
+//! assert_eq!("Ronnie-James-Dio", "RONNIE_JAMES_DIO".to_case(Case::Train));
+//! ```
+//!
+//! By default, `to_case` will split along all word boundaries, that is
+//! * space characters ` `,
+//! * underscores `_`,
+//! * hyphens `-`,
+//! * and changes in capitalization `aA`.
+//!
+//! For more accuracy, the `from_case` method splits based on the word boundaries
+//! of a particular case. For example, splitting from snake case will only treat
+//! underscores as word boundaries.
+//! ```
+//! use convert_case::{Case, Casing};
+//!
+//! assert_eq!(
+//! "2020 04 16 My Cat Cali",
+//! "2020-04-16_my_cat_cali".to_case(Case::Title)
+//! );
+//! assert_eq!(
+//! "2020-04-16 My Cat Cali",
+//! "2020-04-16_my_cat_cali".from_case(Case::Snake).to_case(Case::Title)
+//! );
+//! ```
+//!
+//! By default (and when converting from camel case or similar cases) `convert_case`
+//! will detect acronyms. It also ignores any leading, trailing, or deplicate delimeters.
+//! ```
+//! use convert_case::{Case, Casing};
+//!
+//! assert_eq!("io_stream", "IOStream".to_case(Case::Snake));
+//! assert_eq!("my_json_parser", "myJSONParser".to_case(Case::Snake));
+//!
+//! assert_eq!("weird_var_name", "__weird--var _name-".to_case(Case::Snake));
+//! ```
+//!
+//! It also works non-ascii characters. However, no inferences on the language itself is made.
+//! For instance, the diagraph `ij` in dutch will not be capitalized, because it is represented
+//! as two distinct unicode characters. However, `æ` would be capitalized.
+//! ```
+//! use convert_case::{Case, Casing};
+//!
+//! assert_eq!("granat-äpfel", "GranatÄpfel".to_case(Case::Kebab));
+//!
+//! // The example from str::to_lowercase documentation
+//! let odysseus = "ὈΔΥΣΣΕΎΣ";
+//! assert_eq!("ὀδυσσεύς", odysseus.to_case(Case::Lower));
+//! ```
+//!
+//! For the purposes of case conversion, characters followed by numerics and vice-versa are
+//! considered word boundaries. In addition, any special ascii characters (besides `_` and `-`)
+//! are ignored.
+//! ```
+//! use convert_case::{Case, Casing};
+//!
+//! assert_eq!("e_5150", "E5150".to_case(Case::Snake));
+//! assert_eq!("10,000_days", "10,000Days".to_case(Case::Snake));
+//! assert_eq!("HELLO, WORLD!", "Hello, world!".to_case(Case::Upper));
+//! assert_eq!("One\ntwo\nthree", "ONE\nTWO\nTHREE".to_case(Case::Title));
+//! ```
+//!
+//! # Note on Accuracy
+//!
+//! The `Casing` methods `from_case` and `to_case` do not fail. Conversion to a case will always
+//! succeed. However, the results can still be unexpected. Failure to detect any word boundaries
+//! for a particular case means the entire string will be considered a single word.
+//! ```
+//! use convert_case::{Case, Casing};
+//!
+//! // Mistakenly parsing using Case::Snake
+//! assert_eq!("My-kebab-var", "my-kebab-var".from_case(Case::Snake).to_case(Case::Title));
+//!
+//! // Converts using an unexpected method
+//! assert_eq!("my_kebab_like_variable", "myKebab-like-variable".to_case(Case::Snake));
+//! ```
+//!
+//! # Random Feature
+//!
+//! To ensure this library had zero dependencies, randomness was moved to the _random_ feature,
+//! which requires the `rand` crate. You can enable this feature by including the
+//! following in your `Cargo.toml`.
+//! ```{toml}
+//! [dependencies]
+//! convert_case = { version = "^0.3, features = ["random"] }
+//! ```
+//! This will add two additional cases: Random and PseudoRandom. You can read about their
+//! construction in the [Case enum](enum.Case.html).
+
+mod case;
+mod words;
+pub use case::Case;
+use words::Words;
+
+/// Describes items that can be converted into a case.
+///
+/// Implemented for string slices `&str` and owned strings `String`.
+pub trait Casing {
+ /// References `self` and converts to the given case.
+ fn to_case(&self, case: Case) -> String;
+
+ /// Creates a `FromCasing` struct, which saves information about
+ /// how to parse `self` before converting to a case.
+ fn from_case(&self, case: Case) -> FromCasing;
+}
+
+impl Casing for str {
+ fn to_case(&self, case: Case) -> String {
+ Words::new(self).into_case(case)
+ }
+
+ fn from_case(&self, case: Case) -> FromCasing {
+ FromCasing::new(self.to_string(), case)
+ }
+}
+
+impl Casing for String {
+ fn to_case(&self, case: Case) -> String {
+ Words::new(self).into_case(case)
+ }
+
+ fn from_case(&self, case: Case) -> FromCasing {
+ FromCasing::new(self.to_string(), case)
+ }
+}
+
+/// Holds information about parsing before converting into a case.
+///
+/// This struct is used when invoking the `from_case` method on
+/// `Casing`. `FromCasing` also implements `Casing`.
+/// ```
+/// use convert_case::{Case, Casing};
+///
+/// let title = "ninety-nine_problems".from_case(Case::Snake).to_case(Case::Title);
+/// assert_eq!("Ninety-nine Problems", title);
+/// ```
+pub struct FromCasing {
+ name: String,
+ case: Case,
+}
+
+impl FromCasing {
+ const fn new(name: String, case: Case) -> Self {
+ Self { name, case }
+ }
+}
+
+impl Casing for FromCasing {
+ fn to_case(&self, case: Case) -> String {
+ Words::from_casing(&self.name, self.case).into_case(case)
+ }
+
+ fn from_case(&self, case: Case) -> Self {
+ Self::new(self.name.to_string(), case)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use strum::IntoEnumIterator;
+
+ #[test]
+ fn lossless_against_lossless() {
+ let examples = vec![
+ (Case::Lower, "my variable 22 name"),
+ (Case::Upper, "MY VARIABLE 22 NAME"),
+ (Case::Title, "My Variable 22 Name"),
+ (Case::Camel, "myVariable22Name"),
+ (Case::Pascal, "MyVariable22Name"),
+ (Case::Snake, "my_variable_22_name"),
+ (Case::ScreamingSnake, "MY_VARIABLE_22_NAME"),
+ (Case::Kebab, "my-variable-22-name"),
+ (Case::Cobol, "MY-VARIABLE-22-NAME"),
+ (Case::Toggle, "mY vARIABLE 22 nAME"),
+ (Case::Train, "My-Variable-22-Name"),
+ (Case::Alternating, "mY vArIaBlE 22 nAmE"),
+ ];
+
+ for (case_a, str_a) in examples.iter() {
+ for (case_b, str_b) in examples.iter() {
+ assert_eq!(*str_a, str_b.from_case(*case_b).to_case(*case_a))
+ }
+ }
+ }
+
+ #[test]
+ fn obvious_default_parsing() {
+ let examples = vec![
+ "SuperMario64Game",
+ "super-mario64-game",
+ "superMario64 game",
+ "Super Mario 64_game",
+ "SUPERMario 64-game",
+ "super_mario-64 game",
+ ];
+
+ for example in examples {
+ assert_eq!("super_mario_64_game", example.to_case(Case::Snake));
+ }
+ }
+
+ #[test]
+ fn multiline_strings() {
+ assert_eq!(
+ "One\ntwo\nthree",
+ "one\ntwo\nthree".to_case(Case::Title)
+ );
+ }
+
+ #[test]
+ fn camel_case_acroynms() {
+ assert_eq!(
+ "xml_http_request",
+ "XMLHttpRequest".from_case(Case::Camel).to_case(Case::Snake)
+ );
+ assert_eq!(
+ "xml_http_request",
+ "XMLHttpRequest"
+ .from_case(Case::UpperCamel)
+ .to_case(Case::Snake)
+ );
+ assert_eq!(
+ "xml_http_request",
+ "XMLHttpRequest"
+ .from_case(Case::Pascal)
+ .to_case(Case::Snake)
+ );
+ }
+
+ #[test]
+ fn leading_tailing_delimeters() {
+ assert_eq!(
+ "leading_underscore",
+ "_leading_underscore"
+ .from_case(Case::Snake)
+ .to_case(Case::Snake)
+ );
+ assert_eq!(
+ "tailing_underscore",
+ "tailing_underscore_"
+ .from_case(Case::Snake)
+ .to_case(Case::Snake)
+ );
+ assert_eq!(
+ "leading_hyphen",
+ "-leading-hyphen"
+ .from_case(Case::Kebab)
+ .to_case(Case::Snake)
+ );
+ assert_eq!(
+ "tailing_hyphen",
+ "tailing-hyphen-"
+ .from_case(Case::Kebab)
+ .to_case(Case::Snake)
+ );
+ }
+
+ #[test]
+ fn double_delimeters() {
+ assert_eq!(
+ "many_underscores",
+ "many___underscores"
+ .from_case(Case::Snake)
+ .to_case(Case::Snake)
+ );
+ assert_eq!(
+ "many-underscores",
+ "many---underscores"
+ .from_case(Case::Kebab)
+ .to_case(Case::Kebab)
+ );
+ }
+
+ #[test]
+ fn early_word_boundaries() {
+ assert_eq!(
+ "a_bagel",
+ "aBagel".from_case(Case::Camel).to_case(Case::Snake)
+ );
+ }
+
+ #[test]
+ fn late_word_boundaries() {
+ assert_eq!(
+ "team_a",
+ "teamA".from_case(Case::Camel).to_case(Case::Snake)
+ );
+ }
+
+ #[test]
+ fn empty_string() {
+ for (case_a, case_b) in Case::iter().zip(Case::iter()) {
+ assert_eq!("", "".from_case(case_a).to_case(case_b));
+ }
+ }
+
+ #[test]
+ fn owned_string() {
+ assert_eq!(
+ "test_variable",
+ String::from("TestVariable").to_case(Case::Snake)
+ )
+ }
+
+ #[test]
+ fn default_all_boundaries() {
+ assert_eq!(
+ "abc_abc_abc_abc_abc_abc",
+ "ABC-abc_abcAbc ABCAbc".to_case(Case::Snake)
+ );
+ }
+
+ #[test]
+ fn alternating_ignore_symbols() {
+ assert_eq!("tHaT's", "that's".to_case(Case::Alternating));
+ }
+}
diff --git a/vendor/convert_case/src/words.rs b/vendor/convert_case/src/words.rs
new file mode 100644
index 000000000..e7640107a
--- /dev/null
+++ b/vendor/convert_case/src/words.rs
@@ -0,0 +1,377 @@
+use crate::Case;
+
+#[cfg(feature = "random")]
+use rand::prelude::*;
+
+pub(super) struct Words {
+ words: Vec<String>,
+}
+
+impl Words {
+ pub fn new(name: &str) -> Self {
+ let words = name
+ .split(|c| "-_ ".contains(c))
+ .flat_map(Self::split_camel)
+ .filter(|s| !s.is_empty())
+ .collect();
+ Words { words }
+ }
+
+ pub fn from_casing(name: &str, case: Case) -> Self {
+ use Case::*;
+ let words = match case {
+ Title | Upper | Lower | Toggle | Alternating => name
+ .split_ascii_whitespace()
+ .map(ToString::to_string)
+ .collect(),
+ Kebab | Cobol | Train => name
+ .split('-')
+ .map(ToString::to_string)
+ .filter(|s| !s.is_empty())
+ .collect(),
+ Snake | UpperSnake | ScreamingSnake => name
+ .split('_')
+ .map(ToString::to_string)
+ .filter(|s| !s.is_empty())
+ .collect(),
+ Pascal | Camel | UpperCamel => Self::split_camel(name),
+ Flat | UpperFlat => vec![name.to_string()],
+
+ // Same behavior as title, upper, etc.
+ #[cfg(feature = "random")]
+ Random | PseudoRandom => name
+ .split_ascii_whitespace()
+ .map(ToString::to_string)
+ .collect(),
+ };
+ Self { words }
+ }
+
+ fn split_camel(name: &str) -> Vec<String> {
+ let left_iter = name.chars();
+ let mid_iter = name.chars().skip(1);
+ let right_iter = name.chars().skip(2);
+
+ let mut split_indices = left_iter
+ .zip(mid_iter)
+ .zip(right_iter)
+ .enumerate()
+ .filter(|(_, ((f, s), t))| Self::three_char_is_boundary(*f, *s, *t))
+ .map(|(i, _)| i + 1)
+ .collect::<Vec<usize>>();
+
+ // Check for boundary in the last two characters
+ // Can be rewritten nicer (use fold)
+ let mut backwards_seek = name.chars().rev();
+ let last = backwards_seek.next();
+ let second_last = backwards_seek.next();
+ if let (Some(a), Some(b)) = (second_last, last) {
+ if Self::two_char_is_boundary(a, b) {
+ split_indices.push(name.len() - 1);
+ }
+ }
+
+ Self::split_at_indices(name, split_indices)
+ }
+
+ /// Allowed boundaries are (lower upper) (digit (!digit and !punc)) ((!digit and !punc) digit).
+ fn two_char_is_boundary(f: char, s: char) -> bool {
+ (f.is_lowercase() && s.is_uppercase())
+ || (f.is_ascii_digit() && !(s.is_ascii_digit() || s.is_ascii_punctuation()))
+ || (!(f.is_ascii_digit() || f.is_ascii_punctuation()) && s.is_ascii_digit())
+ }
+
+ /// Checks if three characters are the end of an acronym, otherwise
+ /// calls `two_char_is_boundary`.
+ fn three_char_is_boundary(f: char, s: char, t: char) -> bool {
+ (f.is_uppercase() && s.is_uppercase() && t.is_lowercase())
+ || Self::two_char_is_boundary(f, s)
+ }
+
+ fn split_at_indices(name: &str, indices: Vec<usize>) -> Vec<String> {
+ let mut words = Vec::new();
+
+ let mut first = name;
+ let mut second;
+ for &x in indices.iter().rev() {
+ let pair = first.split_at(x);
+ first = pair.0;
+ second = pair.1;
+ words.push(second);
+ }
+ words.push(first);
+
+ words.iter().rev().map(ToString::to_string).collect()
+ }
+
+ pub fn into_case(mut self, case: Case) -> String {
+ use Case::*;
+ match case {
+ Camel => {
+ self.make_camel_case();
+ self.join("")
+ }
+ Title => {
+ self.capitalize_first_letter();
+ self.join(" ")
+ }
+ Pascal | UpperCamel => {
+ self.capitalize_first_letter();
+ self.join("")
+ }
+ Toggle => {
+ self.lower_first_letter();
+ self.join(" ")
+ }
+ Snake => {
+ self.make_lowercase();
+ self.join("_")
+ }
+ Cobol => {
+ self.make_uppercase();
+ self.join("-")
+ }
+ Kebab => {
+ self.make_lowercase();
+ self.join("-")
+ }
+ UpperSnake | ScreamingSnake => {
+ self.make_uppercase();
+ self.join("_")
+ }
+ Lower => {
+ self.make_lowercase();
+ self.join(" ")
+ }
+ Upper => {
+ self.make_uppercase();
+ self.join(" ")
+ }
+ Flat => {
+ self.make_lowercase();
+ self.join("")
+ }
+ Train => {
+ self.capitalize_first_letter();
+ self.join("-")
+ }
+ UpperFlat => {
+ self.make_uppercase();
+ self.join("")
+ }
+ Alternating => {
+ self.make_alternating();
+ self.join(" ")
+ }
+ #[cfg(feature = "random")]
+ Random => {
+ self.randomize();
+ self.join(" ")
+ }
+ #[cfg(feature = "random")]
+ PseudoRandom => {
+ self.pseudo_randomize();
+ self.join(" ")
+ }
+ }
+ }
+
+ // Randomly picks whether to be upper case or lower case
+ #[cfg(feature = "random")]
+ fn randomize(&mut self) {
+ let mut rng = rand::thread_rng();
+ self.words = self
+ .words
+ .iter()
+ .map(|word| {
+ word.chars()
+ .map(|letter| {
+ if rng.gen::<f32>() > 0.5 {
+ letter.to_uppercase().to_string()
+ } else {
+ letter.to_lowercase().to_string()
+ }
+ })
+ .collect()
+ })
+ .collect();
+ }
+
+ // Randomly selects patterns: [upper, lower] or [lower, upper]
+ // for a more random feeling pattern.
+ #[cfg(feature = "random")]
+ fn pseudo_randomize(&mut self) {
+ let mut rng = rand::thread_rng();
+
+ // Keeps track of when to alternate
+ let mut alt: Option<bool> = None;
+ self.words = self
+ .words
+ .iter()
+ .map(|word| {
+ word.chars()
+ .map(|letter| {
+ match alt {
+ // No existing pattern, start one
+ None => {
+ if rng.gen::<f32>() > 0.5 {
+ alt = Some(false); // Make the next char lower
+ letter.to_uppercase().to_string()
+ } else {
+ alt = Some(true); // Make the next char upper
+ letter.to_lowercase().to_string()
+ }
+ }
+ // Existing pattern, do what it says
+ Some(upper) => {
+ alt = None;
+ if upper {
+ letter.to_uppercase().to_string()
+ } else {
+ letter.to_lowercase().to_string()
+ }
+ }
+ }
+ })
+ .collect()
+ })
+ .collect();
+ }
+
+ fn make_camel_case(&mut self) {
+ self.words = self
+ .words
+ .iter()
+ .enumerate()
+ .map(|(i, word)| {
+ if i != 0 {
+ let mut chars = word.chars();
+ if let Some(a) = chars.next() {
+ a.to_uppercase()
+ .chain(chars.as_str().to_lowercase().chars())
+ .collect()
+ } else {
+ String::new()
+ }
+ } else {
+ word.to_lowercase()
+ }
+ })
+ .collect();
+ }
+
+ fn make_alternating(&mut self) {
+ let mut upper = false;
+ self.words = self
+ .words
+ .iter()
+ .map(|word| {
+ word.chars()
+ .map(|letter| {
+ if letter.is_uppercase() || letter.is_lowercase() {
+ if upper {
+ upper = false;
+ letter.to_uppercase().to_string()
+ } else {
+ upper = true;
+ letter.to_lowercase().to_string()
+ }
+ } else {
+ letter.to_string()
+ }
+ })
+ .collect()
+ })
+ .collect();
+ }
+
+ fn make_uppercase(&mut self) {
+ self.words = self.words.iter().map(|word| word.to_uppercase()).collect();
+ }
+
+ fn make_lowercase(&mut self) {
+ self.words = self.words.iter().map(|word| word.to_lowercase()).collect();
+ }
+
+ fn capitalize_first_letter(&mut self) {
+ self.words = self
+ .words
+ .iter()
+ .map(|word| {
+ let mut chars = word.chars();
+ if let Some(a) = chars.next() {
+ a.to_uppercase()
+ .chain(chars.as_str().to_lowercase().chars())
+ .collect()
+ } else {
+ String::new()
+ }
+ })
+ .collect();
+ }
+
+ fn lower_first_letter(&mut self) {
+ self.words = self
+ .words
+ .iter()
+ .map(|word| {
+ let mut chars = word.chars();
+ if let Some(a) = chars.next() {
+ a.to_lowercase()
+ .chain(chars.as_str().to_uppercase().chars())
+ .collect()
+ } else {
+ String::new()
+ }
+ })
+ .collect();
+ }
+
+ // Alternative: construct [my, -, variable, -, name] then collect
+ fn join(self, delim: &str) -> String {
+ self.words
+ .iter()
+ .enumerate()
+ .map(|(i, val)| {
+ if i == 0 {
+ val.to_owned()
+ } else {
+ delim.to_owned() + val
+ }
+ })
+ .collect()
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn correct_two_char_boundaries() {
+ assert!(!Words::two_char_is_boundary('a', 'a'));
+ assert!(Words::two_char_is_boundary('a', 'A'));
+ assert!(Words::two_char_is_boundary('a', '5'));
+ assert!(!Words::two_char_is_boundary('a', ','));
+ assert!(!Words::two_char_is_boundary('A', 'A'));
+ assert!(!Words::two_char_is_boundary('A', 'a'));
+ assert!(Words::two_char_is_boundary('A', '5'));
+ assert!(!Words::two_char_is_boundary('A', ','));
+ assert!(Words::two_char_is_boundary('5', 'a'));
+ assert!(Words::two_char_is_boundary('5', 'A'));
+ assert!(!Words::two_char_is_boundary('5', '5'));
+ assert!(!Words::two_char_is_boundary('5', ','));
+ assert!(!Words::two_char_is_boundary(',', 'a'));
+ assert!(!Words::two_char_is_boundary(',', 'A'));
+ assert!(!Words::two_char_is_boundary(',', '5'));
+ assert!(!Words::two_char_is_boundary(',', ','));
+ }
+
+ #[test]
+ fn correct_three_char_boundaries() {
+ assert!(Words::three_char_is_boundary('A', 'A', 'a'));
+ assert!(!Words::three_char_is_boundary('A', 'a', 'a'));
+ assert!(!Words::three_char_is_boundary('A', 'a', 'A'));
+ assert!(!Words::three_char_is_boundary('A', 'A', '3'));
+ }
+}