poet 0.7.0

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

use anyhow::Result;
use anyhow::anyhow;
use dashmap::DashMap;

use crate::filesystem::Filesystem;
use crate::filesystem::file_entry::FileEntry;

#[derive(Default)]
pub struct FilesystemHttpRouteIndex {
    routes: DashMap<String, FileEntry>,
}

impl FilesystemHttpRouteIndex {
    pub fn get_file_entry_for_path(&self, path: &str) -> Option<FileEntry> {
        self.routes.get(path).map(|entry| entry.value().clone())
    }

    pub async fn from_filesystem<TFilesystem: Filesystem>(
        filesystem: Arc<TFilesystem>,
    ) -> Result<Self> {
        let this: Self = Default::default();

        for file in filesystem.read_project_files().await? {
            this.register_file(file)?;
        }

        Ok(this)
    }

    fn register_file(&self, file: FileEntry) -> Result<()> {
        let filename = file.relative_path.to_string_lossy().to_string();

        if filename.ends_with("/index.html") {
            let filename_stripped: String = filename
                .strip_suffix("index.html")
                .ok_or_else(|| anyhow!("Unable to strip '/index.html' suffix from: '{filename}'"))?
                .to_string();

            self.routes.insert(filename, file.clone());
            self.routes.insert(filename_stripped, file.clone());
        } else if filename == "index.html" {
            self.routes.insert("".to_string(), file.clone());
            self.routes.insert("index.html".to_string(), file.clone());
        } else if filename == "sitemap.xml" {
            self.routes.insert(filename, file.clone());
        } else {
            return Err(anyhow!("Unexpected filename: '{filename}'"));
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use std::path::PathBuf;

    use super::*;
    use crate::filesystem::file_entry_stub::FileEntryStub;

    fn file_entry(relative_path: &str) -> Result<FileEntry> {
        FileEntryStub {
            contents: String::new(),
            relative_path: PathBuf::from(relative_path),
        }
        .try_into()
    }

    #[test]
    fn registers_root_index_under_empty_and_named_routes() -> Result<()> {
        let index = FilesystemHttpRouteIndex::default();

        index.register_file(file_entry("index.html")?)?;

        assert_eq!(
            index
                .get_file_entry_for_path("")
                .map(|entry| entry.relative_path),
            Some(PathBuf::from("index.html"))
        );
        assert!(index.get_file_entry_for_path("index.html").is_some());

        Ok(())
    }

    #[test]
    fn registers_nested_index_under_directory_and_full_path() -> Result<()> {
        let index = FilesystemHttpRouteIndex::default();

        index.register_file(file_entry("docs/index.html")?)?;

        assert!(index.get_file_entry_for_path("docs/index.html").is_some());
        assert!(index.get_file_entry_for_path("docs/").is_some());

        Ok(())
    }

    #[test]
    fn registers_sitemap_under_its_own_path() -> Result<()> {
        let index = FilesystemHttpRouteIndex::default();

        index.register_file(file_entry("sitemap.xml")?)?;

        assert!(index.get_file_entry_for_path("sitemap.xml").is_some());

        Ok(())
    }

    #[test]
    fn rejects_unexpected_filename() -> Result<()> {
        let index = FilesystemHttpRouteIndex::default();

        assert!(index.register_file(file_entry("page.html")?).is_err());

        Ok(())
    }

    #[test]
    fn returns_none_for_unregistered_path() {
        let index = FilesystemHttpRouteIndex::default();

        assert!(index.get_file_entry_for_path("missing").is_none());
    }
}