#[must_use]
pub fn codenexus_tokenize(name: &str) -> Vec<String> {
let mut tokens = Vec::new();
let mut current = String::new();
for ch in name.chars() {
if ch.is_uppercase() {
if current.ends_with(|c: char| c.is_lowercase() || crate::model::i18n::is_cjk(c)) {
flush(&mut current, &mut tokens);
}
current.push(ch);
} else if ch.is_lowercase() {
if current.ends_with(|c: char| crate::model::i18n::is_cjk(c)) {
flush(&mut current, &mut tokens);
}
if current.len() > 1 && current.chars().all(|c| c.is_uppercase()) {
let last = current.pop().expect("current has len > 1");
flush(&mut current, &mut tokens);
current.push(last);
} else if current.ends_with(|c: char| c.is_ascii_digit()) {
flush(&mut current, &mut tokens);
}
current.push(ch);
} else if ch.is_ascii_digit() {
if current.ends_with(|c: char| c.is_alphabetic()) {
flush(&mut current, &mut tokens);
}
current.push(ch);
} else if crate::model::i18n::is_cjk(ch) {
if !current.is_empty()
&& !current
.chars()
.last()
.is_some_and(crate::model::i18n::is_cjk)
{
flush(&mut current, &mut tokens);
}
current.push(ch);
} else {
flush(&mut current, &mut tokens);
}
}
flush(&mut current, &mut tokens);
tokens
}
fn flush(current: &mut String, tokens: &mut Vec<String>) {
if !current.is_empty() {
tokens.push(crate::model::i18n::fold_case(current));
current.clear();
}
}
#[must_use]
pub fn codenexus_tokenize_join(name: &str) -> String {
codenexus_tokenize(name).join(" ")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tokenize_camel_case() {
assert_eq!(codenexus_tokenize("parseFile"), vec!["parse", "file"]);
}
#[test]
fn tokenize_pascal_case() {
assert_eq!(codenexus_tokenize("ParseFile"), vec!["parse", "file"]);
}
#[test]
fn tokenize_snake_case() {
assert_eq!(codenexus_tokenize("parse_file"), vec!["parse", "file"]);
}
#[test]
fn tokenize_screaming_snake() {
assert_eq!(
codenexus_tokenize("SCREAMING_SNAKE"),
vec!["screaming", "snake"]
);
}
#[test]
fn tokenize_acronym_prefix() {
assert_eq!(codenexus_tokenize("HTTPClient"), vec!["http", "client"]);
}
#[test]
fn tokenize_single_word() {
assert_eq!(codenexus_tokenize("parse"), vec!["parse"]);
}
#[test]
fn tokenize_single_uppercase_word() {
assert_eq!(codenexus_tokenize("PARSE"), vec!["parse"]);
}
#[test]
fn tokenize_empty_string() {
let result = codenexus_tokenize("");
assert!(result.is_empty());
}
#[test]
fn tokenize_with_digits_letter_to_digit() {
assert_eq!(codenexus_tokenize("file2"), vec!["file", "2"]);
}
#[test]
fn tokenize_with_digits_digit_to_letter() {
assert_eq!(codenexus_tokenize("2nd"), vec!["2", "nd"]);
}
#[test]
fn tokenize_mixed_case_with_digits_and_underscore() {
assert_eq!(
codenexus_tokenize("parseFile_v2"),
vec!["parse", "file", "v", "2"]
);
}
#[test]
fn tokenize_multiple_separators() {
assert_eq!(codenexus_tokenize("parse__file"), vec!["parse", "file"]);
}
#[test]
fn tokenize_hyphen_separator() {
assert_eq!(codenexus_tokenize("parse-file"), vec!["parse", "file"]);
}
#[test]
fn tokenize_dot_separator() {
assert_eq!(codenexus_tokenize("parse.file"), vec!["parse", "file"]);
}
#[test]
fn tokenize_join_produces_space_separated() {
assert_eq!(codenexus_tokenize_join("parseFile"), "parse file");
}
#[test]
fn tokenize_join_empty_string() {
assert_eq!(codenexus_tokenize_join(""), "");
}
#[test]
fn tokenize_all_uppercase_acronym() {
assert_eq!(codenexus_tokenize("HTTP"), vec!["http"]);
}
#[test]
fn tokenize_mixed_acronyms() {
assert_eq!(codenexus_tokenize("XMLParser"), vec!["xml", "parser"]);
}
#[test]
fn tokenize_preserves_no_case_in_output() {
let tokens = codenexus_tokenize("ParseFile");
assert!(tokens.iter().all(|t| t == &t.to_ascii_lowercase()));
}
#[cfg(feature = "i18n")]
#[test]
fn tokenize_unicode_case_folding() {
assert_eq!(codenexus_tokenize("ParseStraße"), vec!["parse", "strasse"]);
}
#[test]
fn tokenize_cjk_identifier_no_split() {
assert_eq!(codenexus_tokenize("你好World"), vec!["你好", "world"]);
}
#[test]
fn tokenize_mixed_script_preserves_cjk() {
assert_eq!(
codenexus_tokenize("parse你好File"),
vec!["parse", "你好", "file"]
);
}
}