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:
@@ -131,10 +131,10 @@ Items validated by codebase investigation:
|
|||||||
- [x] Collect all unique tags across content items during build
|
- [x] Collect all unique tags across content items during build
|
||||||
- [x] Create `tags/default.html` template in `docs/templates/`
|
- [x] Create `tags/default.html` template in `docs/templates/`
|
||||||
- [x] Generate `/tags/<tag>.html` for each unique tag with list of tagged items
|
- [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: 404 page generation
|
||||||
- [x] Add tests: tag listing page generation (correct paths, correct items per tag)
|
- [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
|
## Verification
|
||||||
|
|
||||||
|
|||||||
31
src/main.rs
31
src/main.rs
@@ -200,12 +200,16 @@ fn run(config_path: &Path) -> Result<()> {
|
|||||||
generate_404(page_404, &output_dir, &config, &manifest.nav, &engine)?;
|
generate_404(page_404, &output_dir, &config, &manifest.nav, &engine)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Generate tag listing pages
|
// 6. Collect tags and generate tag listing pages
|
||||||
generate_tag_pages(&output_dir, &content_dir, &config, &manifest, &engine)?;
|
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)
|
// 7. Generate sitemap (if enabled)
|
||||||
|
let tag_names: Vec<String> = tags.keys().cloned().collect();
|
||||||
if config.sitemap.enabled {
|
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
|
// 8. Generate alias redirects
|
||||||
@@ -242,11 +246,12 @@ fn generate_sitemap_file(
|
|||||||
manifest: &content::SiteManifest,
|
manifest: &content::SiteManifest,
|
||||||
config: &config::SiteConfig,
|
config: &config::SiteConfig,
|
||||||
content_dir: &Path,
|
content_dir: &Path,
|
||||||
|
tag_names: &[String],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let out_path = output_dir.join("sitemap.xml");
|
let out_path = output_dir.join("sitemap.xml");
|
||||||
eprintln!("generating: {}", out_path.display());
|
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 {
|
fs::write(&out_path, sitemap_xml).map_err(|e| Error::WriteFile {
|
||||||
path: out_path.clone(),
|
path: out_path.clone(),
|
||||||
@@ -387,29 +392,23 @@ fn collect_tags(
|
|||||||
tags
|
tags
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate tag listing pages for all unique tags across content.
|
/// Write tag listing pages from pre-collected tag data.
|
||||||
fn generate_tag_pages(
|
fn write_tag_pages(
|
||||||
output_dir: &Path,
|
output_dir: &Path,
|
||||||
content_dir: &Path,
|
tags: &BTreeMap<String, Vec<ContentContext>>,
|
||||||
config: &config::SiteConfig,
|
config: &config::SiteConfig,
|
||||||
manifest: &content::SiteManifest,
|
nav: &[NavItem],
|
||||||
engine: &TemplateEngine,
|
engine: &TemplateEngine,
|
||||||
) -> Result<()> {
|
) -> 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");
|
let tags_dir = output_dir.join("tags");
|
||||||
fs::create_dir_all(&tags_dir).map_err(|e| Error::CreateDir {
|
fs::create_dir_all(&tags_dir).map_err(|e| Error::CreateDir {
|
||||||
path: tags_dir.clone(),
|
path: tags_dir.clone(),
|
||||||
source: e,
|
source: e,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
for (tag, items) in &tags {
|
for (tag, items) in tags {
|
||||||
let page_path = format!("/tags/{}.html", tag);
|
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));
|
let out_path = tags_dir.join(format!("{}.html", tag));
|
||||||
fs::write(&out_path, html).map_err(|e| Error::WriteFile {
|
fs::write(&out_path, html).map_err(|e| Error::WriteFile {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ pub fn generate_sitemap(
|
|||||||
manifest: &SiteManifest,
|
manifest: &SiteManifest,
|
||||||
config: &SiteConfig,
|
config: &SiteConfig,
|
||||||
content_root: &Path,
|
content_root: &Path,
|
||||||
|
tag_names: &[String],
|
||||||
) -> String {
|
) -> String {
|
||||||
let base_url = config.base_url.trim_end_matches('/');
|
let base_url = config.base_url.trim_end_matches('/');
|
||||||
let mut entries = Vec::new();
|
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)
|
build_sitemap_xml(&entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,4 +174,22 @@ mod tests {
|
|||||||
assert!(xml.contains("&"));
|
assert!(xml.contains("&"));
|
||||||
assert!(!xml.contains("?q=foo&bar")); // Raw & should not appear
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user