feat: normalize config context and template API

Add FeedConfig and SitemapConfig structs to config.rs
with enabled: bool (default true) for opt-out control.

Refactor ConfigContext: flat nested_nav bool replaced
with nested nav: NavContext { nested, toc } to mirror
site.toml [nav] table structure.

Remove standalone base_url template variable; use
config.base_url as single source of truth with
trailing-slash trimming in ConfigContext::from().

Add section template fallback: try section/<type>.html,
fall back to section/default.html for unknown types.

Delete section/features.html (duplicate of default.html)
and homepage.html (dead code, never referenced).

Update base.html for new variable names.
This commit is contained in:
Timothy DeHerrera
2026-02-14 07:09:05 -07:00
parent 46c00c7729
commit 45448cc443
6 changed files with 72 additions and 43 deletions

View File

@@ -20,6 +20,40 @@ pub struct SiteConfig {
/// Navigation configuration.
#[serde(default)]
pub nav: NavConfig,
/// Feed (Atom) configuration.
#[serde(default)]
pub feed: FeedConfig,
/// Sitemap configuration.
#[serde(default)]
pub sitemap: SitemapConfig,
}
/// Feed (Atom) generation configuration.
#[derive(Debug, Deserialize)]
#[serde(default)]
pub struct FeedConfig {
/// Whether to generate an Atom feed (default: true).
pub enabled: bool,
}
impl Default for FeedConfig {
fn default() -> Self {
Self { enabled: true }
}
}
/// Sitemap generation configuration.
#[derive(Debug, Deserialize)]
#[serde(default)]
pub struct SitemapConfig {
/// Whether to generate a sitemap.xml (default: true).
pub enabled: bool,
}
impl Default for SitemapConfig {
fn default() -> Self {
Self { enabled: true }
}
}
/// Navigation configuration.

View File

@@ -86,6 +86,9 @@ impl TemplateEngine {
}
/// Render a section index page (blog index, projects index).
///
/// Tries `section/<type>.html` first, falls back to `section/default.html`
/// if no type-specific template exists.
pub fn render_section(
&self,
section: &Content,
@@ -95,7 +98,12 @@ impl TemplateEngine {
config: &SiteConfig,
nav: &[NavItem],
) -> Result<String> {
let template = format!("section/{}.html", section_type);
let preferred = format!("section/{}.html", section_type);
let template = if self.tera.get_template_names().any(|n| n == preferred) {
preferred
} else {
"section/default.html".to_string()
};
let mut ctx = self.base_context(page_path, config, nav);
ctx.insert("title", &section.frontmatter.title);
@@ -114,8 +122,6 @@ impl TemplateEngine {
ctx.insert("nav", nav);
ctx.insert("page_path", page_path);
ctx.insert("prefix", &relative_prefix(page_path));
// Trimmed base_url for canonical links
ctx.insert("base_url", config.base_url.trim_end_matches('/'));
ctx
}
}
@@ -140,8 +146,17 @@ pub struct ConfigContext {
pub title: String,
pub author: String,
pub base_url: String,
/// Whether to display nested navigation
pub nested_nav: bool,
/// Navigation settings for templates.
pub nav: NavContext,
}
/// Navigation context for templates.
#[derive(Serialize)]
pub struct NavContext {
/// Whether to display nested navigation.
pub nested: bool,
/// Whether table of contents is globally enabled.
pub toc: bool,
}
impl From<&SiteConfig> for ConfigContext {
@@ -149,8 +164,11 @@ impl From<&SiteConfig> for ConfigContext {
Self {
title: config.title.clone(),
author: config.author.clone(),
base_url: config.base_url.clone(),
nested_nav: config.nav.nested,
base_url: config.base_url.trim_end_matches('/').to_string(),
nav: NavContext {
nested: config.nav.nested,
toc: config.nav.toc,
},
}
}
}
@@ -259,6 +277,8 @@ mod tests {
nested: false,
toc: true,
},
feed: crate::config::FeedConfig::default(),
sitemap: crate::config::SitemapConfig::default(),
};
let config_toc_false = SiteConfig {
@@ -270,6 +290,8 @@ mod tests {
nested: false,
toc: false,
},
feed: crate::config::FeedConfig::default(),
sitemap: crate::config::SitemapConfig::default(),
};
// Frontmatter with explicit toc: true