1use std::collections::HashMap;
2use std::fs;
3use std::path::Path;
4
5use anyhow::{Result, anyhow};
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Deserialize, Default, Clone)]
10pub struct ProjectLocalConfig {
11 #[serde(default)]
13 pub local: bool
14}
15
16#[derive(Debug, Deserialize, Clone, Default)]
18pub struct ShellSpec {
19 #[serde(default)]
21 pub env: PlatformOrEnvMap
22}
23
24#[derive(Debug, Deserialize, Clone)]
26pub struct RegistrySpec {
27 pub url: String,
29 pub revision: Option<String>,
31 #[serde(rename = "type")]
33 pub registry_type: Option<String>
34}
35
36#[derive(Debug, Serialize, Deserialize, Clone)]
38pub struct PackageSpec {
39 #[serde(rename = "type")]
41 pub package_type: Option<String>,
42 pub install_method: Option<String>,
44 pub sub_packages: Option<Vec<String>>,
46 pub version: Option<String>,
48 pub dependencies: Option<zoi_core::types::Dependencies>,
50 pub options: Option<Vec<String>>,
52 pub optionals: Option<Vec<String>>
54}
55
56#[derive(Debug, Deserialize, Clone)]
65#[allow(dead_code)]
66pub struct ProjectConfig {
67 pub name: String,
69 #[serde(default)]
71 pub registries: HashMap<String, RegistrySpec>,
72 #[serde(default)]
74 pub packages: Vec<PackageCheck>,
75 #[serde(default)]
77 pub pkgs: Vec<String>,
78 #[serde(default)]
80 pub pkgs_v2: HashMap<String, PackageSpec>,
81 #[serde(default)]
83 pub config: ProjectLocalConfig,
84 #[serde(default)]
86 pub commands: Vec<CommandSpec>,
87 #[serde(default)]
89 pub environments: Vec<EnvironmentSpec>,
90 #[serde(default)]
92 pub shell: Option<ShellSpec>
93}
94
95#[derive(Debug, Deserialize, Clone)]
97pub struct PackageCheck {
98 pub name: String,
100 pub check: String
102}
103
104#[derive(Debug, Deserialize, Clone)]
106#[serde(untagged)]
107pub enum PlatformOrString {
108 String(String),
110 Platform(HashMap<String, String>)
112}
113
114#[derive(Debug, Deserialize, Clone)]
117#[serde(untagged)]
118pub enum PlatformOrStringVec {
119 StringVec(Vec<String>),
121 Platform(HashMap<String, Vec<String>>)
123}
124
125#[derive(Debug, Deserialize, Clone)]
128#[serde(untagged)]
129pub enum PlatformOrEnvMap {
130 EnvMap(HashMap<String, String>),
132 Platform(HashMap<String, HashMap<String, String>>)
134}
135
136impl Default for PlatformOrEnvMap {
137 fn default() -> Self {
138 PlatformOrEnvMap::EnvMap(HashMap::new())
139 }
140}
141
142#[derive(Debug, Deserialize, Clone)]
144pub struct CommandSpec {
145 pub cmd: String,
147 pub run: PlatformOrString,
149 #[serde(default)]
151 pub env: PlatformOrEnvMap,
152 #[serde(default)]
154 pub depends_on: Option<Vec<String>>,
155 #[serde(default)]
157 pub cache_files: Option<Vec<String>>
158}
159
160#[derive(Debug, Deserialize, Clone)]
162pub struct EnvironmentSpec {
163 pub name: String,
165 pub cmd: String,
167 pub run: PlatformOrStringVec,
169 #[serde(default)]
171 pub env: PlatformOrEnvMap
172}
173
174pub fn load() -> Result<ProjectConfig> {
181 let env: HashMap<String, String> = std::env::vars().collect();
182 load_with_env(&env)
183}
184
185pub fn load_with_env<S: ::std::hash::BuildHasher>(
192 env: &HashMap<String, String, S>
193) -> Result<ProjectConfig> {
194 let lua_path = Path::new("zoi.lua");
195 if lua_path.exists() {
196 return crate::lua_config::load_zoi_lua(lua_path, env);
197 }
198
199 let config_path = Path::new("zoi.yaml");
200 if !config_path.exists() {
201 return Err(anyhow!(
202 "No 'zoi.lua' or 'zoi.yaml' file found in the current directory."
203 ));
204 }
205
206 let content = fs::read_to_string(config_path)?;
207 let config: ProjectConfig = serde_yaml::from_str(&content)?;
208 Ok(config)
209}
210
211pub fn add_packages_to_config(packages: &[String]) -> Result<()> {
218 if Path::new("zoi.lua").exists() {
219 return Err(anyhow!(
220 "Project uses zoi.lua. Automatic saving is not supported for Lua \
221 configurations."
222 ));
223 }
224 let config_path = Path::new("zoi.yaml");
225 if !config_path.exists() {
226 return Err(anyhow!(
227 "No 'zoi.yaml' file found in the current directory."
228 ));
229 }
230
231 let content = fs::read_to_string(config_path)?;
232 let mut yaml_value: serde_yaml::Value = serde_yaml::from_str(&content)?;
233
234 if let Some(mapping) = yaml_value.as_mapping_mut() {
235 let pkgs_key = serde_yaml::Value::String("pkgs".to_string());
236 let pkgs_list = mapping
237 .entry(pkgs_key)
238 .or_insert_with(|| serde_yaml::Value::Sequence(Vec::new()));
239
240 if let Some(sequence) = pkgs_list.as_sequence_mut() {
241 for package in packages {
242 let new_pkg_value = serde_yaml::Value::String(package.clone());
243 if !sequence.contains(&new_pkg_value) {
244 sequence.push(new_pkg_value);
245 }
246 }
247 }
248 }
249
250 let new_content = serde_yaml::to_string(&yaml_value)?;
251 fs::write(config_path, new_content)?;
252
253 Ok(())
254}
255
256pub fn remove_packages_from_config(
263 packages_to_remove: &[String]
264) -> Result<()> {
265 if Path::new("zoi.lua").exists() {
266 return Ok(());
267 }
268 let config_path = Path::new("zoi.yaml");
269 if !config_path.exists() {
270 return Ok(());
271 }
272
273 let content = fs::read_to_string(config_path)?;
274 let mut yaml_value: serde_yaml::Value = serde_yaml::from_str(&content)?;
275
276 if let Some(mapping) = yaml_value.as_mapping_mut()
277 && let Some(pkgs_list) = mapping.get_mut("pkgs")
278 && let Some(sequence) = pkgs_list.as_sequence_mut()
279 {
280 let packages_to_remove_names: Vec<_> = packages_to_remove
281 .iter()
282 .map(|p| {
283 zoi_resolver::resolve::parse_source_string(p)
284 .map_or_else(|_| p.clone(), |req| req.name)
285 })
286 .collect();
287
288 sequence.retain(|v| {
289 if let Some(s) = v.as_str() {
290 if let Ok(req) = zoi_resolver::resolve::parse_source_string(s) {
291 !packages_to_remove_names.contains(&req.name)
292 } else {
293 true
294 }
295 } else {
296 true
297 }
298 });
299 }
300
301 let new_content = serde_yaml::to_string(&yaml_value)?;
302 fs::write(config_path, new_content)?;
303
304 Ok(())
305}