1use crate::error::LoadError;
4use sage_package::{parse_dependencies, DependencySpec};
5use serde::Deserialize;
6use std::collections::HashMap;
7use std::path::{Path, PathBuf};
8
9#[derive(Debug, Clone, Deserialize)]
11pub struct ProjectManifest {
12 pub project: ProjectConfig,
13 #[serde(default)]
14 pub dependencies: toml::Table,
15}
16
17#[derive(Debug, Clone, Deserialize)]
19pub struct ProjectConfig {
20 pub name: String,
21 #[serde(default = "default_version")]
22 pub version: String,
23 #[serde(default = "default_entry")]
24 pub entry: PathBuf,
25}
26
27fn default_version() -> String {
28 "0.1.0".to_string()
29}
30
31fn default_entry() -> PathBuf {
32 PathBuf::from("src/main.sg")
33}
34
35impl ProjectManifest {
36 pub fn load(path: &Path) -> Result<Self, LoadError> {
38 let contents = std::fs::read_to_string(path).map_err(|e| LoadError::IoError {
39 path: path.to_path_buf(),
40 source: e,
41 })?;
42
43 toml::from_str(&contents).map_err(|e| LoadError::InvalidManifest {
44 path: path.to_path_buf(),
45 source: e,
46 })
47 }
48
49 pub fn find(start_dir: &Path) -> Option<PathBuf> {
51 let mut current = start_dir.to_path_buf();
52 loop {
53 let manifest_path = current.join("sage.toml");
54 if manifest_path.exists() {
55 return Some(manifest_path);
56 }
57 if !current.pop() {
58 return None;
59 }
60 }
61 }
62
63 pub fn has_dependencies(&self) -> bool {
65 !self.dependencies.is_empty()
66 }
67
68 pub fn parse_dependencies(&self) -> Result<HashMap<String, DependencySpec>, LoadError> {
70 parse_dependencies(&self.dependencies).map_err(|e| LoadError::PackageError { source: e })
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn parse_minimal_manifest() {
80 let toml = r#"
81[project]
82name = "test"
83"#;
84 let manifest: ProjectManifest = toml::from_str(toml).unwrap();
85 assert_eq!(manifest.project.name, "test");
86 assert_eq!(manifest.project.version, "0.1.0");
87 assert_eq!(manifest.project.entry, PathBuf::from("src/main.sg"));
88 }
89
90 #[test]
91 fn parse_full_manifest() {
92 let toml = r#"
93[project]
94name = "research"
95version = "1.2.3"
96entry = "src/app.sg"
97
98[dependencies]
99"#;
100 let manifest: ProjectManifest = toml::from_str(toml).unwrap();
101 assert_eq!(manifest.project.name, "research");
102 assert_eq!(manifest.project.version, "1.2.3");
103 assert_eq!(manifest.project.entry, PathBuf::from("src/app.sg"));
104 }
105}