feat: integrate tag pages into sitemap

Thread tag names from collect_tags() through
generate_sitemap_file to generate_sitemap, appending
/tags/<tag>.html entries to sitemap.xml output.

Refactor: hoist collect_tags into run(), rename
generate_tag_pages to write_tag_pages.

Test suite: 83 → 84, phase 3 complete.
This commit is contained in:
Timothy DeHerrera
2026-02-15 09:15:31 -07:00
parent e7809e1ce2
commit 7b3373295b
3 changed files with 44 additions and 18 deletions

View File

@@ -131,10 +131,10 @@ Items validated by codebase investigation:
- [x] Collect all unique tags across content items during build
- [x] Create `tags/default.html` template in `docs/templates/`
- [x] Generate `/tags/<tag>.html` for each unique tag with list of tagged items
- [ ] Add tag listing page entries to sitemap (if enabled)
- [x] Add tag listing page entries to sitemap (if enabled)
- [x] Add tests: 404 page generation
- [x] Add tests: tag listing page generation (correct paths, correct items per tag)
- [ ] End-to-end: build `docs/` site and verify all outputs
- [x] End-to-end: build `docs/` site and verify all outputs
## Verification

View File

@@ -200,12 +200,16 @@ fn run(config_path: &Path) -> Result<()> {
generate_404(page_404, &output_dir, &config, &manifest.nav, &engine)?;
}
// 6. Generate tag listing pages
generate_tag_pages(&output_dir, &content_dir, &config, &manifest, &engine)?;
// 6. Collect tags and generate tag listing pages
let tags = collect_tags(&manifest.sections, &manifest.pages, &content_dir, &config);
if !tags.is_empty() {
write_tag_pages(&output_dir, &tags, &config, &manifest.nav, &engine)?;
}
// 7. Generate sitemap (if enabled)
let tag_names: Vec<String> = tags.keys().cloned().collect();
if config.sitemap.enabled {
generate_sitemap_file(&output_dir, &manifest, &config, &content_dir)?;
generate_sitemap_file(&output_dir, &manifest, &config, &content_dir, &tag_names)?;
}
// 8. Generate alias redirects
@@ -242,11 +246,12 @@ fn generate_sitemap_file(
manifest: &content::SiteManifest,
config: &config::SiteConfig,
content_dir: &Path,
tag_names: &[String],
) -> Result<()> {
let out_path = output_dir.join("sitemap.xml");
eprintln!("generating: {}", out_path.display());
let sitemap_xml = sitemap::generate_sitemap(manifest, config, content_dir);
let sitemap_xml = sitemap::generate_sitemap(manifest, config, content_dir, tag_names);
fs::write(&out_path, sitemap_xml).map_err(|e| Error::WriteFile {
path: out_path.clone(),
@@ -387,29 +392,23 @@ fn collect_tags(
tags
}
/// Generate tag listing pages for all unique tags across content.
fn generate_tag_pages(
/// Write tag listing pages from pre-collected tag data.
fn write_tag_pages(
output_dir: &Path,
content_dir: &Path,
tags: &BTreeMap<String, Vec<ContentContext>>,
config: &config::SiteConfig,
manifest: &content::SiteManifest,
nav: &[NavItem],
engine: &TemplateEngine,
) -> Result<()> {
let tags = collect_tags(&manifest.sections, &manifest.pages, content_dir, config);
if tags.is_empty() {
return Ok(());
}
let tags_dir = output_dir.join("tags");
fs::create_dir_all(&tags_dir).map_err(|e| Error::CreateDir {
path: tags_dir.clone(),
source: e,
})?;
for (tag, items) in &tags {
for (tag, items) in tags {
let page_path = format!("/tags/{}.html", tag);
let html = engine.render_tag_page(tag, items, &page_path, config, &manifest.nav)?;
let html = engine.render_tag_page(tag, items, &page_path, config, nav)?;
let out_path = tags_dir.join(format!("{}.html", tag));
fs::write(&out_path, html).map_err(|e| Error::WriteFile {

View File

@@ -24,6 +24,7 @@ pub fn generate_sitemap(
manifest: &SiteManifest,
config: &SiteConfig,
content_root: &Path,
tag_names: &[String],
) -> String {
let base_url = config.base_url.trim_end_matches('/');
let mut entries = Vec::new();
@@ -63,6 +64,14 @@ pub fn generate_sitemap(
});
}
// Tag listing pages
for tag in tag_names {
entries.push(SitemapEntry {
loc: format!("{}/tags/{}.html", base_url, tag),
lastmod: None,
});
}
build_sitemap_xml(&entries)
}
@@ -165,4 +174,22 @@ mod tests {
assert!(xml.contains("&amp;"));
assert!(!xml.contains("?q=foo&bar")); // Raw & should not appear
}
#[test]
fn test_sitemap_includes_tag_pages() {
let tag_names = vec!["rust".to_string(), "web".to_string()];
let entries: Vec<SitemapEntry> = tag_names
.iter()
.map(|tag| SitemapEntry {
loc: format!("https://example.com/tags/{}.html", tag),
lastmod: None,
})
.collect();
let xml = build_sitemap_xml(&entries);
assert!(xml.contains("https://example.com/tags/rust.html"));
assert!(xml.contains("https://example.com/tags/web.html"));
assert_eq!(xml.matches("<url>").count(), 2);
}
}