Skip to main content

orbit_md/
config.rs

1//! Site-wide configuration loaded from YAML at startup.
2
3use std::path::{Path, PathBuf};
4
5use serde::Deserialize;
6
7use crate::error::OrbitError;
8
9/// Default config filename for Orbit projects.
10pub const DEFAULT_CONFIG_FILE: &str = "orbit.yaml";
11
12/// Global site configuration.
13///
14/// Values may be supplied via [`DEFAULT_CONFIG_FILE`] in the project root. Any
15/// omitted field falls back to a sensible default so local development requires
16/// minimal setup.
17#[derive(Debug, Clone, Deserialize)]
18pub struct Config {
19    /// Human-readable site title injected into layout templates.
20    #[serde(default = "default_title")]
21    pub title: String,
22
23    /// Root directory containing Markdown source files.
24    #[serde(default = "default_source_dir")]
25    pub source_dir: PathBuf,
26
27    /// Output directory for generated HTML (typically `.orbit/`).
28    #[serde(default = "default_output_dir")]
29    pub output_dir: PathBuf,
30
31    /// Directory holding Handlebars layout templates.
32    #[serde(default = "default_template_dir")]
33    pub template_dir: PathBuf,
34
35    /// Base layout template filename inside [`Config::template_dir`].
36    #[serde(default = "default_layout")]
37    pub layout: String,
38
39    /// Directory holding reusable Markdown components (`*.hbs`).
40    #[serde(default = "default_components_dir")]
41    pub components_dir: PathBuf,
42}
43
44impl Default for Config {
45    fn default() -> Self {
46        Self {
47            title: default_title(),
48            source_dir: default_source_dir(),
49            output_dir: default_output_dir(),
50            template_dir: default_template_dir(),
51            layout: default_layout(),
52            components_dir: default_components_dir(),
53        }
54    }
55}
56
57impl Config {
58    /// Loads configuration from `path`, falling back to defaults when the file
59    /// is absent.
60    ///
61    /// # Errors
62    ///
63    /// Returns [`OrbitError::Config`] when the file exists but cannot be parsed.
64    pub fn load(path: impl AsRef<Path>) -> Result<Self, OrbitError> {
65        let path = path.as_ref();
66        if !path.exists() {
67            return Ok(Self::default());
68        }
69
70        let contents = std::fs::read_to_string(path).map_err(|source| OrbitError::Io {
71            path: path.to_path_buf(),
72            source,
73        })?;
74
75        serde_yaml::from_str(&contents)
76            .map_err(|err| OrbitError::Config(format!("failed to parse {}: {err}", path.display())))
77    }
78}
79
80fn default_title() -> String {
81    "My Site".to_owned()
82}
83
84fn default_source_dir() -> PathBuf {
85    PathBuf::from("content")
86}
87
88fn default_output_dir() -> PathBuf {
89    PathBuf::from(".orbit")
90}
91
92fn default_template_dir() -> PathBuf {
93    PathBuf::from("templates")
94}
95
96fn default_layout() -> String {
97    "base.hbs".to_owned()
98}
99
100fn default_components_dir() -> PathBuf {
101    PathBuf::from("components")
102}