doom-eternal 0.1.0

Rust CLI for the Xylex DOOM Eternal texture and install workflow
Documentation
use once_cell::sync::Lazy;
use regex::Regex;

static SET_TOKEN_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?i)set\d+").expect("valid set regex"));

pub fn normalize_set_name(value: impl AsRef<str>) -> Option<String> {
    let trimmed = value.as_ref().trim();
    if trimmed.is_empty() {
        return None;
    }

    if trimmed.to_ascii_lowercase().starts_with("set") {
        return Some(trimmed.to_ascii_lowercase());
    }

    Some(format!("set{}", trimmed.to_ascii_lowercase()))
}

pub fn parse_set_token(value: impl AsRef<str>) -> Option<String> {
    let normalized = normalize_set_name(value)?;
    let digits = normalized.strip_prefix("set")?;
    if !digits.is_empty() && digits.chars().all(|character| character.is_ascii_digit()) {
        Some(normalized)
    } else {
        None
    }
}

pub fn replace_set_tokens(value: impl AsRef<str>, replacement: &str) -> String {
    SET_TOKEN_RE
        .replace_all(value.as_ref(), replacement)
        .into_owned()
}

pub fn find_set_tokens(value: impl AsRef<str>) -> Vec<String> {
    SET_TOKEN_RE
        .find_iter(value.as_ref())
        .filter_map(|item| parse_set_token(item.as_str()))
        .collect()
}

#[cfg(test)]
mod tests {
    use super::{find_set_tokens, normalize_set_name, parse_set_token, replace_set_tokens};

    #[test]
    fn normalizes_and_parses_set_names() {
        assert_eq!(normalize_set_name("16").as_deref(), Some("set16"));
        assert_eq!(normalize_set_name("Set52").as_deref(), Some("set52"));
        assert_eq!(parse_set_token("set16").as_deref(), Some("set16"));
        assert_eq!(parse_set_token("helmet"), None);
    }

    #[test]
    fn replaces_embedded_set_tokens() {
        let replaced = replace_set_tokens(
            "models/customization/characters/doomslayer/set52/doomslayer_game_torso_set52.tga",
            "set16",
        );
        assert_eq!(
            replaced,
            "models/customization/characters/doomslayer/set16/doomslayer_game_torso_set16.tga"
        );
        assert_eq!(
            find_set_tokens("set52/doomslayer_game_torso_set52.tga"),
            vec!["set52".to_string(), "set52".to_string()]
        );
    }
}