tiger-lib 1.17.0

Library used by the tools ck3-tiger, vic3-tiger, and imperator-tiger. This library holds the bulk of the code for them. It can be built either for ck3-tiger with the feature ck3, or for vic3-tiger with the feature vic3, or for imperator-tiger with the feature imperator, but not both at the same time.
Documentation
use std::sync::LazyLock;

use lalrpop_util::lalrpop_mod;

use crate::report::ErrorKey;

lalrpop_mod! {
    #[allow(clippy::pedantic)]
    #[allow(clippy::if_then_some_else_none)]
    parser, "/parse/ignore/parser.rs"
}
static COMMENT_PARSER: LazyLock<parser::CommentParser> = LazyLock::new(parser::CommentParser::new);

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum IgnoreSize {
    #[default]
    Line,
    Block,
    File,
    Begin,
    End,
}

#[derive(Debug, Clone, Default)]
pub struct IgnoreFilter {
    key: Option<ErrorKey>,
    text: Option<String>,
}

#[derive(Debug, Clone, Default)]
pub struct IgnoreSpec {
    pub size: IgnoreSize,
    pub filter: IgnoreFilter,
}

pub fn parse_comment(comment: &str) -> Option<IgnoreSpec> {
    // Shortcut for performance
    if !comment.trim_start().starts_with("tiger-ignore") {
        return None;
    }
    COMMENT_PARSER.parse(comment).ok()
}

impl IgnoreSpec {
    fn set_key(mut self, key: ErrorKey) -> Self {
        self.filter.key = Some(key);
        self
    }

    fn set_text(mut self, text: String) -> Self {
        self.filter.text = Some(text);
        self
    }

    fn merge(mut self, other: Self) -> Self {
        if other.size != IgnoreSize::Line {
            self.size = other.size;
        }
        if other.filter.key.is_some() {
            self.filter.key = other.filter.key;
        }
        if other.filter.text.is_some() {
            self.filter.text = other.filter.text;
        }
        self
    }
}

impl IgnoreFilter {
    pub fn matches(&self, other_key: ErrorKey, other_text: &str) -> bool {
        if let Some(key) = self.key {
            if key != other_key {
                return false;
            }
        }
        if let Some(text) = &self.text {
            if !other_text.contains(text) {
                return false;
            }
        }
        true
    }
}

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

    #[test]
    fn test_bare() {
        let result = parse_comment("tiger-ignore");
        assert!(result.is_some());
        if let Some(spec) = result {
            assert_eq!(spec.size, IgnoreSize::Line);
            assert!(spec.filter.key.is_none());
            assert!(spec.filter.text.is_none());
        }
    }

    #[test]
    fn test_non_ignore() {
        let result = parse_comment("a random comment");
        assert!(result.is_none());
    }

    #[test]
    fn test_bare_trailing() {
        let result = parse_comment("tiger-ignore with explanation");
        assert!(result.is_some());
        if let Some(spec) = result {
            assert_eq!(spec.size, IgnoreSize::Line);
            assert!(spec.filter.key.is_none());
            assert!(spec.filter.text.is_none());
        }
    }

    #[test]
    fn test_block() {
        let result = parse_comment("tiger-ignore(block)");
        assert!(result.is_some());
        if let Some(spec) = result {
            assert_eq!(spec.size, IgnoreSize::Block);
            assert!(spec.filter.key.is_none());
            assert!(spec.filter.text.is_none());
        }
    }

    #[test]
    fn test_file() {
        let result = parse_comment("tiger-ignore(file)");
        assert!(result.is_some());
        if let Some(spec) = result {
            assert_eq!(spec.size, IgnoreSize::File);
            assert!(spec.filter.key.is_none());
            assert!(spec.filter.text.is_none());
        }
    }

    #[test]
    fn test_begin() {
        let result = parse_comment("tiger-ignore(begin)");
        assert!(result.is_some());
        if let Some(spec) = result {
            assert_eq!(spec.size, IgnoreSize::Begin);
            assert!(spec.filter.key.is_none());
            assert!(spec.filter.text.is_none());
        }
    }

    #[test]
    fn test_end() {
        let result = parse_comment("tiger-ignore(end)");
        assert!(result.is_some());
        if let Some(spec) = result {
            assert_eq!(spec.size, IgnoreSize::End);
            assert!(spec.filter.key.is_none());
            assert!(spec.filter.text.is_none());
        }
    }

    #[test]
    fn test_block_with_key() {
        let result = parse_comment("tiger-ignore(block, key=missing-item)");
        assert!(result.is_some());
        if let Some(spec) = result {
            assert_eq!(spec.size, IgnoreSize::Block);
            assert_eq!(spec.filter.key, Some(ErrorKey::MissingItem));
            assert!(spec.filter.text.is_none());
        }
    }

    #[test]
    fn test_block_with_quoted_text() {
        let result = parse_comment("tiger-ignore(block, text=\"missing english\")");
        assert!(result.is_some());
        if let Some(spec) = result {
            assert_eq!(spec.size, IgnoreSize::Block);
            assert!(spec.filter.key.is_none());
            assert_eq!(spec.filter.text, Some("missing english".to_owned()));
        }
    }
}