knave-expand 0.14.22

The reading, scoping and expansion stages of knave
Documentation
pub struct Keyword;

impl Keyword {
    pub const MARKER: &'static str = ":";

    pub fn field_name(symbol_name: &str) -> Option<&str> {
        symbol_name
            .strip_prefix(Self::MARKER)
            .filter(|field_name| !field_name.is_empty())
    }

    pub fn of_field(field_name: &str) -> Option<String> {
        (!field_name.starts_with(Self::MARKER)).then(|| format!("{}{field_name}", Self::MARKER))
    }

    pub fn trim_end(symbol_name: &str) -> Option<String> {
        let field_name = Self::field_name(symbol_name)?;
        let trimmed = field_name.trim_end_matches(Self::MARKER);
        Some(format!("{}{trimmed}", Self::MARKER))
    }
}

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

    #[test]
    fn trim_end_drops_every_trailing_marker() {
        assert_eq!(Keyword::trim_end(":email::").as_deref(), Some(":email"));
    }

    #[test]
    fn trim_end_keeps_a_keyword_without_a_trailing_marker() {
        assert_eq!(Keyword::trim_end(":email").as_deref(), Some(":email"));
    }

    #[test]
    fn trim_end_leaves_only_the_marker_of_a_keyword_made_of_markers() {
        assert_eq!(Keyword::trim_end(":::").as_deref(), Some(":"));
    }

    #[test]
    fn trim_end_rejects_a_plain_symbol() {
        assert_eq!(Keyword::trim_end("email:"), None);
    }
}