Skip to main content

sage_loader/
manifest.rs

1//! Project manifest (sage.toml) parsing.
2
3use crate::error::LoadError;
4use serde::Deserialize;
5use std::path::{Path, PathBuf};
6
7/// A Sage project manifest (sage.toml).
8#[derive(Debug, Clone, Deserialize)]
9pub struct ProjectManifest {
10    pub project: ProjectConfig,
11    #[serde(default)]
12    pub dependencies: toml::Table,
13}
14
15/// The [project] section of sage.toml.
16#[derive(Debug, Clone, Deserialize)]
17pub struct ProjectConfig {
18    pub name: String,
19    #[serde(default = "default_version")]
20    pub version: String,
21    #[serde(default = "default_entry")]
22    pub entry: PathBuf,
23}
24
25fn default_version() -> String {
26    "0.1.0".to_string()
27}
28
29fn default_entry() -> PathBuf {
30    PathBuf::from("src/main.sg")
31}
32
33impl ProjectManifest {
34    /// Load a manifest from a sage.toml file.
35    pub fn load(path: &Path) -> Result<Self, LoadError> {
36        let contents = std::fs::read_to_string(path).map_err(|e| LoadError::IoError {
37            path: path.to_path_buf(),
38            source: e,
39        })?;
40
41        toml::from_str(&contents).map_err(|e| LoadError::InvalidManifest {
42            path: path.to_path_buf(),
43            source: e,
44        })
45    }
46
47    /// Find a sage.toml file by searching upward from the given directory.
48    pub fn find(start_dir: &Path) -> Option<PathBuf> {
49        let mut current = start_dir.to_path_buf();
50        loop {
51            let manifest_path = current.join("sage.toml");
52            if manifest_path.exists() {
53                return Some(manifest_path);
54            }
55            if !current.pop() {
56                return None;
57            }
58        }
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn parse_minimal_manifest() {
68        let toml = r#"
69[project]
70name = "test"
71"#;
72        let manifest: ProjectManifest = toml::from_str(toml).unwrap();
73        assert_eq!(manifest.project.name, "test");
74        assert_eq!(manifest.project.version, "0.1.0");
75        assert_eq!(manifest.project.entry, PathBuf::from("src/main.sg"));
76    }
77
78    #[test]
79    fn parse_full_manifest() {
80        let toml = r#"
81[project]
82name = "research"
83version = "1.2.3"
84entry = "src/app.sg"
85
86[dependencies]
87"#;
88        let manifest: ProjectManifest = toml::from_str(toml).unwrap();
89        assert_eq!(manifest.project.name, "research");
90        assert_eq!(manifest.project.version, "1.2.3");
91        assert_eq!(manifest.project.entry, PathBuf::from("src/app.sg"));
92    }
93}