nicknamer 0.1.0

A tool for generating alternate names.
Documentation
mod generated {
    include!(concat!(env!("OUT_DIR"), "/generated.rs"));
}

fn normalize(s: &str) -> String {
    s.trim().to_lowercase()
}

/// Return all alternate names (nicknames or canonical variants) related to the input name.
/// The comparison is case-insensitive; results are lowercase and do not include the input itself.
/// If the name is unknown, returns an empty vector.
pub fn nicknames(name: &str) -> Vec<String> {
    let key = normalize(name);
    if key.is_empty() { return Vec::new(); }
    let map = generated::nickname_map();
    match map.get(key.as_str()) {
        Some(list) => list.iter().map(|s| s.to_string()).collect(),
        None => Vec::new(),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn abigail_has_expected_nicknames() {
        let nicks = nicknames("Abigail");
        assert!(nicks.contains(&"abby".to_string()));
        assert!(nicks.contains(&"gail".to_string()));
        assert!(nicks.contains(&"abbey".to_string()));
        assert!(!nicks.contains(&"abigail".to_string()));
    }

    #[test]
    fn unknown_name_returns_empty() {
        let nicks = nicknames("thisnameisnotlikelypresent");
        assert!(nicks.is_empty());
    }

    #[test]
    fn case_insensitivity() {
        let lower = nicknames("abigail");
        let upper = nicknames("ABIGAIL");
        let mixed = nicknames("AbIgAiL");
        assert_eq!(lower, upper);
        assert_eq!(upper, mixed);
    }

    #[test]
    fn no_self_inclusion() {
        let nicks = nicknames("Abigail");
        assert!(!nicks.contains(&"abigail".to_string()));
    }

    #[test]
    fn name_with_no_alternates() {
        // Find a name that is only ever a nickname, never a canonical name
        // e.g. "abbey" is a nickname for several, but not a canonical name
        let nicks = nicknames("abbey");
        // Should be empty or only contain canonical forms, but not itself
        assert!(!nicks.contains(&"abbey".to_string()));
    }

    #[test]
    fn symmetry() {
        // "abby" is a nickname for "abigail", so "abigail" should be a nickname for "abby"
        let abigail_nicks = nicknames("abigail");
        let abby_nicks = nicknames("abby");
        assert!(abigail_nicks.contains(&"abby".to_string()));
        assert!(abby_nicks.contains(&"abigail".to_string()));
    }

    #[test]
    fn trims_whitespace() {
        let nicks = nicknames("  abigail  ");
        let expected = nicknames("abigail");
        assert_eq!(nicks, expected);
    }
}