Skip to main content

spec_driven_docs/gates/
comparison_legend.rs

1//! Gate: a comparison document carrying tables carries a legend.
2//!
3//! The verdict symbols mean nothing without the line that defines them.
4//! Only presence is judged; which symbols the legend defines is the verdict
5//! gate's business.
6
7use crate::domain::finding::Finding;
8use crate::domain::rule_id::RuleId;
9use crate::gates::comparison_dated_tables::has_table;
10use crate::gates::{GateCtx, GateResult, Violation, read_text};
11
12/// The rules this gate can cite.
13pub const CITES: &[RuleId] = &[RuleId::ComparisonCarriesALegend];
14
15/// Judge every file pre-commit passed.
16///
17/// # Errors
18///
19/// [`crate::gates::GateError::Io`] when a file cannot be read.
20pub fn run(ctx: &GateCtx, files: &[String]) -> GateResult {
21    let mut violations = Vec::new();
22    for file in files {
23        let text = read_text(ctx, file)?;
24        if has_table(&text) && !text.lines().any(|line| line.starts_with("Legend: ")) {
25            violations.push(Violation::Finding(Finding::on_file(
26                RuleId::ComparisonCarriesALegend,
27                file,
28                "",
29            )));
30        }
31    }
32    Ok(violations)
33}
34
35#[cfg(test)]
36mod tests {
37    use super::*;
38
39    fn run_on(text: &str) -> Vec<String> {
40        let dir = tempfile::tempdir().unwrap();
41        std::fs::write(dir.path().join("comparison.md"), text).unwrap();
42        let ctx = GateCtx::new(dir.path().to_str().unwrap());
43        run(&ctx, &["comparison.md".to_string()])
44            .unwrap()
45            .iter()
46            .map(ToString::to_string)
47            .collect()
48    }
49
50    #[test]
51    fn accepts_a_table_with_a_legend() {
52        assert!(run_on("Legend: ✅ yes.\n\n| a |\n").is_empty());
53    }
54
55    #[test]
56    fn rejects_a_table_without_a_legend() {
57        let out = run_on("| a |\n");
58        assert_eq!(out.len(), 1);
59        assert!(out[0].contains("comparison-docs:a-comparison-carries-a-legend"));
60    }
61
62    #[test]
63    fn a_tableless_document_needs_no_legend() {
64        assert!(run_on("# Notes\n").is_empty());
65    }
66}