diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:18:25 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:18:25 +0000 |
commit | 5363f350887b1e5b5dd21a86f88c8af9d7fea6da (patch) | |
tree | 35ca005eb6e0e9a1ba3bb5dbc033209ad445dc17 /vendor/convert_case/src/words.rs | |
parent | Adding debian version 1.66.0+dfsg1-1. (diff) | |
download | rustc-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/words.rs')
-rw-r--r-- | vendor/convert_case/src/words.rs | 377 |
1 files changed, 377 insertions, 0 deletions
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')); + } +} |