poet 0.7.0

Static site generator with optional MCP server and LLM SEO optimizations
Documentation
use std::fmt;

use dashmap::DashMap;
use itertools::Itertools as _;

use crate::document_error::DocumentError;

#[derive(Default)]
pub struct DocumentErrorCollection {
    errors: DashMap<String, Vec<DocumentError>>,
}

impl DocumentErrorCollection {
    pub fn is_empty(&self) -> bool {
        self.errors.is_empty()
    }

    pub fn register_error(&self, basename: String, err: anyhow::Error) {
        self.errors
            .entry(basename.clone())
            .or_default()
            .push(DocumentError { basename, err });
    }
}

impl fmt::Display for DocumentErrorCollection {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        writeln!(
            formatter,
            "Multiple errors occurred ({} total):",
            self.errors.len()
        )?;

        for errors in self
            .errors
            .iter()
            .sorted_by(|a, b| Ord::cmp(&a.key(), &b.key()))
        {
            for error in errors.value() {
                writeln!(formatter, "{error:#}")?;
            }
        }

        Ok(())
    }
}

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

    use super::*;

    #[test]
    fn is_empty_until_an_error_is_registered() {
        let collection = DocumentErrorCollection::default();

        assert!(collection.is_empty());

        collection.register_error("guide".to_string(), anyhow!("boom"));

        assert!(!collection.is_empty());
    }

    #[test]
    fn display_reports_count_and_sorts_errors_by_basename() {
        let collection = DocumentErrorCollection::default();

        collection.register_error("beta".to_string(), anyhow!("second"));
        collection.register_error("alpha".to_string(), anyhow!("first"));

        assert_eq!(
            format!("{collection}"),
            "Multiple errors occurred (2 total):\nalpha:\n- first\n\nbeta:\n- second\n\n"
        );
    }
}