Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
/// Skipped, not counted, in the middle of a name: "Ludwig van Beethoven" is "LB".
const PARTICLES: [&str; 20] = [
"van", "von", "de", "da", "di", "del", "della", "der", "den", "des", "du",
"la", "le", "ter", "ten", "dos", "das", "do", "bin", "ibn",
];
/// The Unicode White_Space property, spelled out so all three languages agree.
fn is_whitespace(ch: char) -> bool {
let cp = ch as u32;
(0x09..=0x0d).contains(&cp)
|| cp == 0x20
|| cp == 0x85
|| cp == 0xa0
|| cp == 0x1680
|| (0x2000..=0x200a).contains(&cp)
|| cp == 0x2028
|| cp == 0x2029
|| cp == 0x202f
|| cp == 0x205f
|| cp == 0x3000
}
/// ASCII punctuation and typographic quotes are skipped at the start of a part.
fn is_skippable(ch: char) -> bool {
if ch.is_ascii() {
return !ch.is_ascii_alphanumeric();
}
matches!(ch as u32, 0x2018 | 0x2019 | 0x201c | 0x201d)
}
/// One-to-one case mappings only, so 'ß' stays 'ß' in all three languages.
fn upper_one(ch: char) -> char {
let mapped: Vec<char> = ch.to_uppercase().collect();
if mapped.len() == 1 {
mapped[0]
} else {
ch
}
}
fn lower_one(ch: char) -> char {
let mapped: Vec<char> = ch.to_lowercase().collect();
if mapped.len() == 1 {
mapped[0]
} else {
ch
}
}
/// Initials from a personal name: "Jean-Paul Sartre" is "JPS".
pub fn initials(name: &str) -> String {
let mut words: Vec<Vec<char>> = Vec::new();
let mut current: Vec<char> = Vec::new();
for ch in name.chars() {
if is_whitespace(ch) {
if !current.is_empty() {
words.push(std::mem::take(&mut current));
}
} else {
current.push(ch);
}
}
if !current.is_empty() {
words.push(current);
}
let mut out = String::new();
let count = words.len();
for (i, word) in words.iter().enumerate() {
let lower: String = word.iter().map(|&c| lower_one(c)).collect();
if i > 0 && i + 1 < count && PARTICLES.contains(&lower.as_str()) {
continue;
}
// Hyphens and full stops split a word into parts that each give a letter.
let mut at_part_start = true;
for &ch in word {
if ch == '-' || ch == '.' {
at_part_start = true;
} else if at_part_start && !is_skippable(ch) {
out.push(upper_one(ch));
at_part_start = false;
}
}
}
out
}
pub fn fune_vector(args: &[Value]) -> Value {
// Refuse what the typed signature cannot hold, with the wording TypeScript
// and Python use, rather than let the conversion below quietly change it.
if !matches!(args[0], Value::Str(_)) {
panic!("initials needs a string, received {:?}", args[0]);
}
Value::Str(initials(args[0].as_str()))
}