Files
sukr/src/config.rs
Timothy DeHerrera 0c9ecbfad6 feat: gate feed/sitemap generation on config
Wire config.feed.enabled and config.sitemap.enabled
into main.rs run() function. Both default to true,
so existing behavior is preserved.

Add 5 new tests: feed/sitemap config defaults,
independent disable, and ConfigContext nav structure
with base_url trailing-slash normalization.

Test suite: 69 → 73 tests, all passing.
Phase 1 complete — all plan items checked off.
2026-02-14 07:12:56 -07:00

213 lines
5.7 KiB
Rust

//! Site configuration loading.
use crate::error::{Error, Result};
use serde::Deserialize;
use std::fs;
use std::path::{Path, PathBuf};
/// Site-wide configuration loaded from site.toml.
#[derive(Debug, Deserialize)]
pub struct SiteConfig {
/// Site title (used in page titles and nav).
pub title: String,
/// Site author name.
pub author: String,
/// Base URL for the site (used for feeds, canonical links).
pub base_url: String,
/// Path configuration (all optional with defaults).
#[serde(default)]
pub paths: PathsConfig,
/// 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.
#[derive(Debug, Deserialize, Default)]
#[serde(default)]
pub struct NavConfig {
/// Whether to display nested navigation (default: false).
pub nested: bool,
/// Enable table of contents (anchor nav) globally (default: false).
/// Can be overridden per-page via frontmatter toc field.
pub toc: bool,
}
/// Path configuration with sensible defaults.
#[derive(Debug, Deserialize)]
#[serde(default)]
pub struct PathsConfig {
/// Content directory (default: "content")
pub content: PathBuf,
/// Output directory (default: "public")
pub output: PathBuf,
/// Static assets directory (default: "static")
#[serde(rename = "static")]
pub static_dir: PathBuf,
/// Templates directory (default: "templates")
pub templates: PathBuf,
}
impl Default for PathsConfig {
fn default() -> Self {
Self {
content: PathBuf::from("content"),
output: PathBuf::from("public"),
static_dir: PathBuf::from("static"),
templates: PathBuf::from("templates"),
}
}
}
impl SiteConfig {
/// Load configuration from a TOML file.
pub fn load(path: &Path) -> Result<Self> {
let content = fs::read_to_string(path).map_err(|e| Error::ReadFile {
path: path.to_path_buf(),
source: e,
})?;
toml::from_str(&content).map_err(|e| Error::Config {
path: path.to_path_buf(),
message: e.to_string(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_config() {
let toml = r#"
title = "Test Site"
author = "Test Author"
base_url = "https://example.com/"
"#;
let config: SiteConfig = toml::from_str(toml).unwrap();
assert_eq!(config.title, "Test Site");
assert_eq!(config.author, "Test Author");
assert_eq!(config.base_url, "https://example.com/");
}
#[test]
fn test_paths_config_defaults() {
let toml = r#"
title = "Test"
author = "Author"
base_url = "https://example.com"
"#;
let config: SiteConfig = toml::from_str(toml).unwrap();
assert_eq!(config.paths.content, PathBuf::from("content"));
assert_eq!(config.paths.output, PathBuf::from("public"));
assert_eq!(config.paths.static_dir, PathBuf::from("static"));
assert_eq!(config.paths.templates, PathBuf::from("templates"));
}
#[test]
fn test_paths_config_custom() {
let toml = r#"
title = "Test"
author = "Author"
base_url = "https://example.com"
[paths]
content = "src/content"
output = "dist"
static = "assets"
templates = "theme"
"#;
let config: SiteConfig = toml::from_str(toml).unwrap();
assert_eq!(config.paths.content, PathBuf::from("src/content"));
assert_eq!(config.paths.output, PathBuf::from("dist"));
assert_eq!(config.paths.static_dir, PathBuf::from("assets"));
assert_eq!(config.paths.templates, PathBuf::from("theme"));
}
#[test]
fn test_feed_sitemap_defaults() {
let toml = r#"
title = "Test"
author = "Author"
base_url = "https://example.com"
"#;
let config: SiteConfig = toml::from_str(toml).unwrap();
assert!(config.feed.enabled, "feed should be enabled by default");
assert!(
config.sitemap.enabled,
"sitemap should be enabled by default"
);
}
#[test]
fn test_feed_disabled() {
let toml = r#"
title = "Test"
author = "Author"
base_url = "https://example.com"
[feed]
enabled = false
"#;
let config: SiteConfig = toml::from_str(toml).unwrap();
assert!(!config.feed.enabled);
assert!(config.sitemap.enabled, "sitemap unaffected");
}
#[test]
fn test_sitemap_disabled() {
let toml = r#"
title = "Test"
author = "Author"
base_url = "https://example.com"
[sitemap]
enabled = false
"#;
let config: SiteConfig = toml::from_str(toml).unwrap();
assert!(config.feed.enabled, "feed unaffected");
assert!(!config.sitemap.enabled);
}
}