poet 0.7.0

Static site generator with optional MCP server and LLM SEO optimizations
Documentation
use std::path::PathBuf;
use std::sync::Arc;

use anyhow::Result;
use anyhow::anyhow;

use crate::author::Author;
use crate::author_basename::AuthorBasename;
use crate::author_collection::AuthorCollection;
use crate::author_data::AuthorData;
use crate::document_error_collection::DocumentErrorCollection;
use crate::filesystem::Filesystem;
use crate::filesystem::storage::Storage;

pub async fn build_authors(source_filesystem: Arc<Storage>) -> Result<AuthorCollection> {
    let mut authors = AuthorCollection::default();
    let error_collection: DocumentErrorCollection = Default::default();

    for file in source_filesystem.read_project_files().await? {
        if file.kind.is_author() {
            let data: AuthorData = match toml::from_str(&file.contents) {
                Ok(data) => data,
                Err(err) => {
                    error_collection.register_error(
                        file.relative_path.display().to_string(),
                        anyhow!("Failed to parse author file: {err}"),
                    );
                    continue;
                }
            };

            let basename_path = file.get_stem_path_relative_to(&PathBuf::from("authors"));
            let basename: AuthorBasename = basename_path.into();

            authors.insert(basename.clone(), Author { basename, data });
        }
    }

    if error_collection.is_empty() {
        Ok(authors)
    } else {
        Err(anyhow!("{error_collection}"))
    }
}

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

    use super::*;

    #[tokio::test]
    async fn builds_author_collection_from_toml_files() -> Result<()> {
        let directory = tempdir()?;
        let storage = Storage {
            base_directory: directory.path().to_path_buf(),
        };

        storage
            .set_file_contents(&PathBuf::from("authors/alice.toml"), "name = \"Alice\"")
            .await?;

        let resolved = build_authors(Arc::new(storage))
            .await?
            .resolve(&["alice".to_string()]);

        assert_eq!(resolved.found_authors.len(), 1);
        assert_eq!(resolved.found_authors[0].data.name, "Alice");

        Ok(())
    }

    #[tokio::test]
    async fn errors_when_an_author_file_cannot_be_parsed() -> Result<()> {
        let directory = tempdir()?;
        let storage = Storage {
            base_directory: directory.path().to_path_buf(),
        };

        storage
            .set_file_contents(&PathBuf::from("authors/broken.toml"), "unexpected = true")
            .await?;

        assert!(build_authors(Arc::new(storage)).await.is_err());

        Ok(())
    }
}