1use anyhow::{Result, anyhow};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::fs;
5use std::path::Path;
6
7#[derive(Debug, Deserialize, Default, Clone)]
8pub struct ProjectLocalConfig {
9 #[serde(default)]
10 pub local: bool,
11}
12
13#[derive(Debug, Deserialize, Clone, Default)]
14pub struct ShellSpec {
15 #[serde(default)]
16 pub env: PlatformOrEnvMap,
17}
18
19#[derive(Debug, Deserialize, Clone)]
20pub struct RegistrySpec {
21 pub url: String,
22 pub revision: Option<String>,
23 #[serde(rename = "type")]
24 pub registry_type: Option<String>,
25}
26
27#[derive(Debug, Serialize, Deserialize, Clone)]
28pub struct PackageSpec {
29 #[serde(rename = "type")]
30 pub package_type: Option<String>,
31 pub install_method: Option<String>,
32 pub sub_packages: Option<Vec<String>>,
33 pub version: Option<String>,
34 pub dependencies: Option<zoi_core::types::Dependencies>,
35 pub options: Option<Vec<String>>,
36 pub optionals: Option<Vec<String>>,
37}
38
39#[derive(Debug, Deserialize, Clone)]
45#[allow(dead_code)]
46pub struct ProjectConfig {
47 pub name: String,
49 #[serde(default)]
51 pub registries: HashMap<String, RegistrySpec>,
52 #[serde(default)]
54 pub packages: Vec<PackageCheck>,
55 #[serde(default)]
57 pub pkgs: Vec<String>,
58 #[serde(default)]
60 pub pkgs_v2: HashMap<String, PackageSpec>,
61 #[serde(default)]
63 pub config: ProjectLocalConfig,
64 #[serde(default)]
66 pub commands: Vec<CommandSpec>,
67 #[serde(default)]
69 pub environments: Vec<EnvironmentSpec>,
70 #[serde(default)]
72 pub shell: Option<ShellSpec>,
73}
74
75#[derive(Debug, Deserialize, Clone)]
76pub struct PackageCheck {
77 pub name: String,
78 pub check: String,
79}
80
81#[derive(Debug, Deserialize, Clone)]
82#[serde(untagged)]
83pub enum PlatformOrString {
84 String(String),
85 Platform(HashMap<String, String>),
86}
87
88#[derive(Debug, Deserialize, Clone)]
89#[serde(untagged)]
90pub enum PlatformOrStringVec {
91 StringVec(Vec<String>),
92 Platform(HashMap<String, Vec<String>>),
93}
94
95#[derive(Debug, Deserialize, Clone)]
96#[serde(untagged)]
97pub enum PlatformOrEnvMap {
98 EnvMap(HashMap<String, String>),
99 Platform(HashMap<String, HashMap<String, String>>),
100}
101
102impl Default for PlatformOrEnvMap {
103 fn default() -> Self {
104 PlatformOrEnvMap::EnvMap(HashMap::new())
105 }
106}
107
108#[derive(Debug, Deserialize, Clone)]
109pub struct CommandSpec {
110 pub cmd: String,
111 pub run: PlatformOrString,
112 #[serde(default)]
113 pub env: PlatformOrEnvMap,
114 #[serde(default)]
115 pub depends_on: Option<Vec<String>>,
116 #[serde(default)]
117 pub cache_files: Option<Vec<String>>,
118}
119
120#[derive(Debug, Deserialize, Clone)]
121pub struct EnvironmentSpec {
122 pub name: String,
123 pub cmd: String,
124 pub run: PlatformOrStringVec,
125 #[serde(default)]
126 pub env: PlatformOrEnvMap,
127}
128
129pub fn load() -> Result<ProjectConfig> {
130 load_with_env(std::env::vars().collect())
131}
132
133pub fn load_with_env(env: HashMap<String, String>) -> Result<ProjectConfig> {
134 let lua_path = Path::new("zoi.lua");
135 if lua_path.exists() {
136 return crate::lua_config::load_zoi_lua(lua_path, env);
137 }
138
139 let config_path = Path::new("zoi.yaml");
140 if !config_path.exists() {
141 return Err(anyhow!(
142 "No 'zoi.lua' or 'zoi.yaml' file found in the current directory."
143 ));
144 }
145
146 let content = fs::read_to_string(config_path)?;
147 let config: ProjectConfig = serde_yaml::from_str(&content)?;
148 Ok(config)
149}
150
151pub fn add_packages_to_config(packages: &[String]) -> Result<()> {
152 if Path::new("zoi.lua").exists() {
153 return Err(anyhow!(
154 "Project uses zoi.lua. Automatic saving is not supported for Lua configurations."
155 ));
156 }
157 let config_path = Path::new("zoi.yaml");
158 if !config_path.exists() {
159 return Err(anyhow!(
160 "No 'zoi.yaml' file found in the current directory."
161 ));
162 }
163
164 let content = fs::read_to_string(config_path)?;
165 let mut yaml_value: serde_yaml::Value = serde_yaml::from_str(&content)?;
166
167 if let Some(mapping) = yaml_value.as_mapping_mut() {
168 let pkgs_key = serde_yaml::Value::String("pkgs".to_string());
169 let pkgs_list = mapping
170 .entry(pkgs_key)
171 .or_insert_with(|| serde_yaml::Value::Sequence(Vec::new()));
172
173 if let Some(sequence) = pkgs_list.as_sequence_mut() {
174 for package in packages {
175 let new_pkg_value = serde_yaml::Value::String(package.clone());
176 if !sequence.contains(&new_pkg_value) {
177 sequence.push(new_pkg_value);
178 }
179 }
180 }
181 }
182
183 let new_content = serde_yaml::to_string(&yaml_value)?;
184 fs::write(config_path, new_content)?;
185
186 Ok(())
187}
188
189pub fn remove_packages_from_config(packages_to_remove: &[String]) -> Result<()> {
190 if Path::new("zoi.lua").exists() {
191 return Ok(());
192 }
193 let config_path = Path::new("zoi.yaml");
194 if !config_path.exists() {
195 return Ok(());
196 }
197
198 let content = fs::read_to_string(config_path)?;
199 let mut yaml_value: serde_yaml::Value = serde_yaml::from_str(&content)?;
200
201 if let Some(mapping) = yaml_value.as_mapping_mut()
202 && let Some(pkgs_list) = mapping.get_mut("pkgs")
203 && let Some(sequence) = pkgs_list.as_sequence_mut()
204 {
205 let packages_to_remove_names: Vec<_> = packages_to_remove
206 .iter()
207 .map(|p| {
208 zoi_resolver::resolve::parse_source_string(p)
209 .map(|req| req.name)
210 .unwrap_or_else(|_| p.to_string())
211 })
212 .collect();
213
214 sequence.retain(|v| {
215 if let Some(s) = v.as_str() {
216 if let Ok(req) = zoi_resolver::resolve::parse_source_string(s) {
217 !packages_to_remove_names.contains(&req.name)
218 } else {
219 true
220 }
221 } else {
222 true
223 }
224 });
225 }
226
227 let new_content = serde_yaml::to_string(&yaml_value)?;
228 fs::write(config_path, new_content)?;
229
230 Ok(())
231}