use crate::ShellError;
pub fn nick_from_display_name(display_name: &str) -> Result<String, ShellError> {
let mut out = String::new();
let mut pending_dash = false;
for ch in display_name.chars() {
match latin_piece(ch) {
Some("") => {}
Some(piece) => {
if pending_dash && !out.is_empty() {
out.push('-');
}
pending_dash = false;
out.push_str(piece);
}
None => pending_dash = true,
}
}
let nick: String = out.chars().take(32).collect();
let nick = nick.trim_end_matches('-').to_string();
if !is_nick_token(&nick) {
return Err(ShellError::Nick);
}
Ok(nick)
}
pub fn routine_folder_id(name: &str) -> Option<String> {
let mut slug = String::new();
let mut pending_dash = false;
for ch in name.to_lowercase().chars() {
if ch.is_ascii_lowercase() || ch.is_ascii_digit() {
if pending_dash && !slug.is_empty() {
slug.push('-');
}
pending_dash = false;
slug.push(ch);
} else {
pending_dash = true;
}
}
let slug: String = slug.chars().take(48).collect();
if slug.is_empty() {
None
} else {
Some(slug)
}
}
pub fn lookup_nick(raw: &str) -> Result<String, ShellError> {
let trimmed = raw.trim();
if is_nick_token(trimmed) {
return Ok(trimmed.to_string());
}
nick_from_display_name(trimmed)
}
fn is_nick_token(nick: &str) -> bool {
!nick.is_empty()
&& nick.len() <= 32
&& nick
.chars()
.all(|ch| ch.is_ascii_alphanumeric() || ch == '_' || ch == '-')
}
fn latin_piece(ch: char) -> Option<&'static str> {
if ch.is_ascii_alphanumeric() {
return Some(ascii_lower(ch));
}
let lower = ch.to_lowercase().next()?;
Some(match lower {
'а' => "a",
'б' => "b",
'в' => "v",
'г' => "g",
'д' => "d",
'е' => "e",
'ё' => "yo",
'ж' => "zh",
'з' => "z",
'и' => "i",
'й' => "i",
'к' => "k",
'л' => "l",
'м' => "m",
'н' => "n",
'о' => "o",
'п' => "p",
'р' => "r",
'с' => "s",
'т' => "t",
'у' => "u",
'ф' => "f",
'х' => "kh",
'ц' => "ts",
'ч' => "ch",
'ш' => "sh",
'щ' => "shch",
'ъ' | 'ь' => "",
'ы' => "y",
'э' => "e",
'ю' => "yu",
'я' => "ya",
'є' => "ye",
'і' => "i",
'ї' => "yi",
'ґ' => "g",
'ў' => "u",
_ => return None,
})
}
fn ascii_lower(ch: char) -> &'static str {
match ch {
'A' | 'a' => "a",
'B' | 'b' => "b",
'C' | 'c' => "c",
'D' | 'd' => "d",
'E' | 'e' => "e",
'F' | 'f' => "f",
'G' | 'g' => "g",
'H' | 'h' => "h",
'I' | 'i' => "i",
'J' | 'j' => "j",
'K' | 'k' => "k",
'L' | 'l' => "l",
'M' | 'm' => "m",
'N' | 'n' => "n",
'O' | 'o' => "o",
'P' | 'p' => "p",
'Q' | 'q' => "q",
'R' | 'r' => "r",
'S' | 's' => "s",
'T' | 't' => "t",
'U' | 'u' => "u",
'V' | 'v' => "v",
'W' | 'w' => "w",
'X' | 'x' => "x",
'Y' | 'y' => "y",
'Z' | 'z' => "z",
'0' => "0",
'1' => "1",
'2' => "2",
'3' => "3",
'4' => "4",
'5' => "5",
'6' => "6",
'7' => "7",
'8' => "8",
'9' => "9",
_ => "",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_names_become_nicks() {
assert_eq!(nick_from_display_name("Alice").unwrap(), "alice");
assert_eq!(nick_from_display_name(" Alice ").unwrap(), "alice");
assert_eq!(
nick_from_display_name("Привет мир").unwrap(),
"privet-mir"
);
assert_eq!(
nick_from_display_name("Свой браузер").unwrap(),
"svoi-brauzer"
);
assert_eq!(
nick_from_display_name("foo+bar").unwrap(),
"foo-bar"
);
assert_eq!(nick_from_display_name("Sample Bot").unwrap(), "sample-bot");
assert_eq!(
nick_from_display_name("Объём, данные").unwrap(),
"obyom-dannye"
);
assert_eq!(
nick_from_display_name(&("x".repeat(31) + " y")).unwrap(),
"x".repeat(31)
);
assert_ne!(
nick_from_display_name("Привет мир").unwrap(),
"nachshtab"
);
assert_eq!(nick_from_display_name("Foo Bar!!").unwrap(), "foo-bar");
assert_eq!(nick_from_display_name("__a__b__").unwrap(), "a-b");
assert_eq!(
nick_from_display_name(&"A".repeat(40)).unwrap(),
"a".repeat(32)
);
assert!(nick_from_display_name("...").is_err());
assert_eq!(lookup_nick("Привет мир").unwrap(), "privet-mir");
assert_eq!(lookup_nick("chief_bob").unwrap(), "chief_bob");
assert_eq!(lookup_nick("Alice").unwrap(), "Alice");
}
#[test]
fn routine_folder_matches_the_host_slug() {
assert_eq!(routine_folder_id("alice").as_deref(), Some("alice"));
assert_eq!(
routine_folder_id("chief_bob").as_deref(),
Some("chief-bob")
);
assert_eq!(
routine_folder_id("sample_bot").as_deref(),
Some("sample-bot")
);
assert_eq!(
routine_folder_id("foobar").as_deref(),
Some("foobar")
);
assert_eq!(
routine_folder_id("foo+bar").as_deref(),
Some("foo-bar")
);
assert_eq!(routine_folder_id("Alice").as_deref(), Some("alice"));
assert_eq!(routine_folder_id("__a__b__").as_deref(), Some("a-b"));
assert_eq!(routine_folder_id("Привет"), None);
assert_eq!(routine_folder_id("___"), None);
assert_eq!(
routine_folder_id(&"x".repeat(60)).map(|s| s.len()),
Some(48)
);
assert_eq!(
routine_folder_id(&format!("{}_b", "a".repeat(47))).as_deref(),
Some(format!("{}-", "a".repeat(47)).as_str())
);
let chain = |name: &str| routine_folder_id(&nick_from_display_name(name).unwrap());
for name in [
"Alice",
"Привет мир",
"foo+bar",
"Sample Bot",
"carol",
] {
let nick = nick_from_display_name(name).unwrap();
assert_eq!(routine_folder_id(&nick).as_deref(), Some(nick.as_str()));
}
assert_eq!(
chain("Привет мир").as_deref(),
Some("privet-mir")
);
assert_eq!(chain("foo+bar").as_deref(), Some("foo-bar"));
assert_eq!(chain("Sample Bot").as_deref(), Some("sample-bot"));
assert_eq!(chain("carol").as_deref(), Some("carol"));
}
}