bim 0.1.0

A content-first static site generator for blogs, digital gardens, and knowledge bases
Documentation
use crate::content::Site;
use anyhow::Result;
use std::io::Write;
use std::path::Path;

pub fn generate_sitemap(site: &Site, output_dir: &Path, base_url: &str) -> Result<()> {
    let sitemap_path = output_dir.join("sitemap.xml");
    let mut file = std::fs::File::create(&sitemap_path)?;

    write!(file, r#"<?xml version="1.0" encoding="UTF-8"?>"#)?;
    write!(
        file,
        r#"<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">"#
    )?;

    // Add all pages
    for page in &site.pages {
        if page.is_draft {
            continue;
        }

        write!(file, "<url>")?;
        write!(
            file,
            "<loc>{}{}</loc>",
            xml_escape(base_url),
            xml_escape(&page.url)
        )?;

        if let Some(date) = page.date {
            write!(file, "<lastmod>{}</lastmod>", date.format("%Y-%m-%d"))?;
        }

        // Set priority based on content type
        let priority = match page.url.as_str() {
            "/" => "1.0",
            url if url.starts_with("/blog/") => "0.8",
            url if url.starts_with("/notes/") => "0.7",
            url if url.starts_with("/tags/") || url.starts_with("/contexts/") => "0.5",
            _ => "0.6",
        };
        write!(file, "<priority>{}</priority>", priority)?;

        write!(file, "</url>")?;
    }

    // Add all snippets
    for snippet in &site.snippets {
        write!(file, "<url>")?;
        write!(
            file,
            "<loc>{}{}</loc>",
            xml_escape(base_url),
            xml_escape(&snippet.url)
        )?;

        if let Some(date) = snippet.date {
            write!(file, "<lastmod>{}</lastmod>", date.format("%Y-%m-%d"))?;
        }

        write!(file, "<priority>0.6</priority>")?;
        write!(file, "</url>")?;
    }

    write!(file, "</urlset>")?;

    tracing::info!("Generated sitemap: {:?}", sitemap_path);
    Ok(())
}

fn xml_escape(s: &str) -> String {
    s.replace('&', "&amp;")
        .replace('<', "&lt;")
        .replace('>', "&gt;")
        .replace('"', "&quot;")
        .replace('\'', "&apos;")
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::collections::Collection;
    use crate::content::Page;
    use tempfile::TempDir;

    #[test]
    fn test_generate_sitemap() {
        let temp = TempDir::new().unwrap();
        let mut site = Site::new();

        let mut page = Page::new(std::path::PathBuf::from("test.md"), Collection::Blog);
        page.url = "/blog/test/".to_string();
        page.title = "Test".to_string();
        site.pages.push(page);

        let result = generate_sitemap(&site, temp.path(), "https://example.com");
        assert!(result.is_ok());

        let content = std::fs::read_to_string(temp.path().join("sitemap.xml")).unwrap();
        assert!(content.contains("https://example.com/blog/test/"));
    }
}