1use crate::models::{Platform, OS};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(untagged)]
12pub enum PackageDefinition {
13 Simple(Vec<String>),
15 Complex(ComplexPackageDefinition),
17}
18
19impl PackageDefinition {
20 pub fn get_sources(&self) -> Vec<&str> {
22 match self {
23 PackageDefinition::Simple(sources) => sources.iter().map(|s| s.as_str()).collect(),
24 PackageDefinition::Complex(complex) => complex.get_sources(),
25 }
26 }
27
28 pub fn get_source_config(&self, source: &str) -> Option<&SourceSpecificConfig> {
30 match self {
31 PackageDefinition::Simple(_) => None,
32 PackageDefinition::Complex(complex) => complex.get_source_config(source),
33 }
34 }
35
36 pub fn is_available_in(&self, source: &str) -> bool {
38 self.get_sources().contains(&source)
39 }
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize, Default)]
43#[serde(default)]
44pub struct ComplexPackageDefinition {
45 #[serde(rename = "_sources", skip_serializing_if = "Option::is_none")]
47 pub sources: Option<Vec<String>>,
48
49 #[serde(rename = "_platforms", skip_serializing_if = "Option::is_none")]
51 pub platforms: Option<Vec<String>>,
52
53 #[serde(rename = "_aliases", skip_serializing_if = "Option::is_none")]
55 pub aliases: Option<Vec<String>>,
56
57 #[serde(flatten)]
59 pub source_configs: HashMap<String, SourceSpecificConfig>,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64#[serde(untagged)]
65pub enum SourceSpecificConfig {
66 Name(String),
68 Complex(SourceConfig),
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct SourceConfig {
74 pub name: Option<String>,
76 pub pre: Option<String>,
78 pub post: Option<String>,
80 pub prefix: Option<String>,
82 pub install_suffix: Option<String>,
84}
85
86pub type SourcesDefinition = HashMap<String, SourceDefinition>;
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct SourceDefinition {
91 pub emoji: String,
93 pub install: String,
95 pub check: String,
97 pub prefix: Option<String>,
99 #[serde(rename = "_overrides", skip_serializing_if = "Option::is_none")]
101 pub overrides: Option<HashMap<String, PlatformOverride>>,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct PlatformOverride {
106 pub install: Option<String>,
107 pub check: Option<String>,
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct ConfigDefinition {
113 pub sources: Vec<String>,
115 pub packages: Vec<String>,
117 #[serde(rename = "_settings", skip_serializing_if = "Option::is_none")]
119 pub settings: Option<ConfigSettings>,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct ConfigSettings {
124 #[serde(default)]
126 pub auto_update: bool,
127 #[serde(default = "default_parallel_installs")]
129 pub parallel_installs: u8,
130 #[serde(default = "default_true")]
132 pub confirm_before_install: bool,
133}
134
135fn default_parallel_installs() -> u8 {
136 3
137}
138fn default_true() -> bool {
139 true
140}
141
142impl ComplexPackageDefinition {
143 pub fn get_sources(&self) -> Vec<&str> {
145 let mut all_sources = Vec::new();
146
147 if let Some(sources) = &self.sources {
149 all_sources.extend(sources.iter().map(|s| s.as_str()));
150 }
151
152 all_sources.extend(self.source_configs.keys().map(|s| s.as_str()));
154
155 all_sources
156 }
157
158 pub fn get_source_config(&self, source: &str) -> Option<&SourceSpecificConfig> {
160 self.source_configs.get(source)
161 }
162
163 pub fn is_available_in(&self, source: &str) -> bool {
165 self.get_sources().contains(&source)
166 }
167}
168
169impl SourceDefinition {
170 pub fn get_install_command(&self, platform: &Platform) -> &str {
172 if let Some(overrides) = &self.overrides {
173 let platform_key = match platform.os {
174 OS::Windows => "windows",
175 OS::Linux => "linux",
176 OS::Macos => "macos",
177 };
178
179 if let Some(platform_override) = overrides.get(platform_key) {
180 if let Some(install) = &platform_override.install {
181 return install;
182 }
183 }
184 }
185 &self.install
186 }
187
188 pub fn get_check_command(&self, platform: &Platform) -> &str {
190 if let Some(overrides) = &self.overrides {
191 let platform_key = match platform.os {
192 OS::Windows => "windows",
193 OS::Linux => "linux",
194 OS::Macos => "macos",
195 };
196
197 if let Some(platform_override) = overrides.get(platform_key) {
198 if let Some(check) = &platform_override.check {
199 return check;
200 }
201 }
202 }
203 &self.check
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn test_package_definition_simple_format() {
213 let ccl = r#"
215bat =
216 = brew
217 = scoop
218 = pacman
219 = nix
220"#;
221 let packages: HashMap<String, PackageDefinition> = crate::parse_ccl_to(ccl).unwrap();
222 let def = packages.get("bat").unwrap();
223
224 assert!(def.is_available_in("brew"));
225 assert!(def.is_available_in("scoop"));
226 assert!(def.is_available_in("pacman"));
227 assert!(def.is_available_in("nix"));
228
229 assert!(def.get_source_config("brew").is_none());
231
232 let sources = def.get_sources();
234 assert_eq!(sources.len(), 4);
235 assert!(sources.contains(&"brew"));
236 assert!(sources.contains(&"scoop"));
237 assert!(sources.contains(&"pacman"));
238 assert!(sources.contains(&"nix"));
239 }
240
241 #[test]
242 fn test_package_definition_complex_format() {
243 let ccl = r#"
244ripgrep =
245 brew = gh
246 _sources =
247 = scoop
248 = apt
249 = pacman
250 = nix
251"#;
252 let packages: HashMap<String, PackageDefinition> = crate::parse_ccl_to(ccl).unwrap();
253 let def = packages.get("ripgrep").unwrap();
254
255 assert!(def.is_available_in("brew"));
256 assert!(def.is_available_in("scoop"));
257 assert!(def.get_source_config("brew").is_some());
258
259 let sources = def.get_sources();
261 assert!(sources.contains(&"scoop"));
262 assert!(sources.contains(&"apt"));
263 assert!(sources.contains(&"pacman"));
264 assert!(sources.contains(&"nix"));
265 assert!(sources.contains(&"brew"));
266 }
267
268 #[test]
269 fn test_source_definition() {
270 let ccl = r#"
271emoji = 🍺
272install = brew install {package}
273check = brew leaves --installed-on-request
274"#;
275 let def: SourceDefinition = sickle::from_str(ccl).unwrap();
276
277 assert_eq!(def.emoji, "🍺");
278 assert!(def.install.contains("{package}"));
279 }
280}