logicaffeine_cli/project/
manifest.rs1use serde::{Deserialize, Serialize};
24use std::collections::HashMap;
25use std::fs;
26use std::path::Path;
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct Manifest {
34 pub package: Package,
36 #[serde(default)]
38 pub dependencies: HashMap<String, DependencySpec>,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct Package {
47 pub name: String,
49 #[serde(default = "default_version")]
51 pub version: String,
52 #[serde(default)]
54 pub description: Option<String>,
55 #[serde(default)]
57 pub authors: Vec<String>,
58 #[serde(default = "default_entry")]
60 pub entry: String,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
69#[serde(untagged)]
70pub enum DependencySpec {
71 Simple(String),
73 Detailed(DependencyDetail),
75}
76
77impl std::fmt::Display for DependencySpec {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 match self {
80 DependencySpec::Simple(s) => write!(f, "{}", s),
81 DependencySpec::Detailed(d) => {
82 if let Some(v) = &d.version {
83 write!(f, "{}", v)
84 } else if let Some(p) = &d.path {
85 write!(f, "path:{}", p)
86 } else if let Some(g) = &d.git {
87 write!(f, "git:{}", g)
88 } else {
89 write!(f, "*")
90 }
91 }
92 }
93 }
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct DependencyDetail {
102 #[serde(default)]
104 pub version: Option<String>,
105 #[serde(default)]
107 pub path: Option<String>,
108 #[serde(default)]
110 pub git: Option<String>,
111}
112
113fn default_version() -> String {
114 "0.1.0".to_string()
115}
116
117fn default_entry() -> String {
118 "src/main.lg".to_string()
119}
120
121#[derive(Debug)]
123pub enum ManifestError {
124 Io(std::path::PathBuf, String),
125 Parse(std::path::PathBuf, String),
126 Serialize(String),
127}
128
129impl std::fmt::Display for ManifestError {
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 match self {
132 ManifestError::Io(path, e) => write!(f, "Failed to read {}: {}", path.display(), e),
133 ManifestError::Parse(path, e) => write!(f, "Failed to parse {}: {}", path.display(), e),
134 ManifestError::Serialize(e) => write!(f, "Failed to serialize manifest: {}", e),
135 }
136 }
137}
138
139impl std::error::Error for ManifestError {}
140
141impl Manifest {
142 pub fn load(dir: &Path) -> Result<Self, ManifestError> {
144 let path = dir.join("Largo.toml");
145 let content = fs::read_to_string(&path)
146 .map_err(|e| ManifestError::Io(path.clone(), e.to_string()))?;
147 toml::from_str(&content).map_err(|e| ManifestError::Parse(path, e.to_string()))
148 }
149
150 pub fn new(name: &str) -> Self {
152 Manifest {
153 package: Package {
154 name: name.to_string(),
155 version: default_version(),
156 description: None,
157 authors: Vec::new(),
158 entry: default_entry(),
159 },
160 dependencies: HashMap::new(),
161 }
162 }
163
164 pub fn to_toml(&self) -> Result<String, ManifestError> {
166 toml::to_string_pretty(self).map_err(|e| ManifestError::Serialize(e.to_string()))
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 #[test]
175 fn parse_minimal_manifest() {
176 let toml = r#"
177[package]
178name = "myproject"
179"#;
180 let manifest: Manifest = toml::from_str(toml).expect("Should parse minimal manifest");
181 assert_eq!(manifest.package.name, "myproject");
182 assert_eq!(manifest.package.version, "0.1.0"); assert_eq!(manifest.package.entry, "src/main.lg"); }
185
186 #[test]
187 fn parse_full_manifest() {
188 let toml = r#"
189[package]
190name = "myproject"
191version = "1.0.0"
192description = "A test project"
193entry = "src/app.lg"
194authors = ["Test Author"]
195
196[dependencies]
197std = "logos:std"
198"#;
199 let manifest: Manifest = toml::from_str(toml).expect("Should parse full manifest");
200 assert_eq!(manifest.package.name, "myproject");
201 assert_eq!(manifest.package.version, "1.0.0");
202 assert_eq!(manifest.package.entry, "src/app.lg");
203 assert!(manifest.package.description.is_some());
204 assert_eq!(manifest.package.authors.len(), 1);
205 }
206
207 #[test]
208 fn create_new_manifest() {
209 let manifest = Manifest::new("testproject");
210 assert_eq!(manifest.package.name, "testproject");
211 let toml = manifest.to_toml().expect("Should serialize");
212 assert!(toml.contains("name = \"testproject\""));
213 }
214
215 #[test]
216 fn parse_path_dependency() {
217 let toml = r#"
218[package]
219name = "with_deps"
220
221[dependencies]
222math = { path = "./math" }
223"#;
224 let manifest: Manifest = toml::from_str(toml).expect("Should parse path deps");
225 assert!(!manifest.dependencies.is_empty());
226 match &manifest.dependencies["math"] {
227 DependencySpec::Detailed(d) => {
228 assert_eq!(d.path.as_deref(), Some("./math"));
229 }
230 _ => panic!("Expected detailed dependency"),
231 }
232 }
233}