1use std::collections::HashMap;
6use std::path::{Path, PathBuf};
7
8use semver::Version;
9use serde::{de, Deserialize, Deserializer, Serialize};
10use serde_json::Value;
11use tracing::{debug, error};
12
13use crate::dependency::{Dependency, DependencyTag, Requirement};
14use crate::error::Result; #[derive(Debug, Clone, Serialize, PartialEq, Eq)]
19pub struct ResourceSpec {
20 pub name: String,
21 pub url: String,
22 pub sha256: String,
23 }
25
26#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
28pub struct BottleFileSpec {
29 pub url: String,
30 pub sha256: String,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
34pub struct BottleSpec {
35 pub stable: Option<BottleStableSpec>,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
39pub struct BottleStableSpec {
40 pub rebuild: u32,
41 #[serde(default)]
42 pub files: HashMap<String, BottleFileSpec>,
43}
44
45#[derive(Deserialize, Serialize, Debug, Clone, Default, PartialEq, Eq)]
47pub struct FormulaVersions {
48 pub stable: Option<String>,
49 pub head: Option<String>,
50 #[serde(default)]
51 pub bottle: bool,
52}
53
54#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
57pub struct Formula {
58 pub name: String,
59 pub stable_version_str: String,
60 #[serde(rename = "versions")]
61 pub version_semver: Version,
62 #[serde(default)]
63 pub revision: u32,
64 #[serde(default)]
65 pub desc: Option<String>,
66 #[serde(default)]
67 pub homepage: Option<String>,
68 #[serde(default)]
69 pub url: String,
70 #[serde(default)]
71 pub sha256: String,
72 #[serde(default)]
73 pub mirrors: Vec<String>,
74 #[serde(default)]
75 pub bottle: BottleSpec,
76 #[serde(skip_deserializing)]
77 pub dependencies: Vec<Dependency>,
78 #[serde(default, deserialize_with = "deserialize_requirements")]
79 pub requirements: Vec<Requirement>,
80 #[serde(skip_deserializing)] pub resources: Vec<ResourceSpec>, #[serde(skip)]
83 pub install_keg_path: Option<PathBuf>,
84}
85
86impl<'de> Deserialize<'de> for Formula {
88 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
89 where
90 D: Deserializer<'de>,
91 {
92 #[derive(Deserialize, Debug)]
95 struct RawFormulaData {
96 name: String,
97 #[serde(default)]
98 revision: u32,
99 desc: Option<String>,
100 homepage: Option<String>,
101 versions: FormulaVersions,
102 #[serde(default)]
103 url: String,
104 #[serde(default)]
105 sha256: String,
106 #[serde(default)]
107 mirrors: Vec<String>,
108 #[serde(default)]
109 bottle: BottleSpec,
110 #[serde(default)]
111 dependencies: Vec<String>,
112 #[serde(default)]
113 build_dependencies: Vec<String>,
114 #[serde(default)]
115 test_dependencies: Vec<String>,
116 #[serde(default)]
117 recommended_dependencies: Vec<String>,
118 #[serde(default)]
119 optional_dependencies: Vec<String>,
120 #[serde(default, deserialize_with = "deserialize_requirements")]
121 requirements: Vec<Requirement>,
122 #[serde(default)]
123 resources: Vec<Value>, #[serde(default)]
125 urls: Option<Value>,
126 }
127
128 let raw: RawFormulaData = RawFormulaData::deserialize(deserializer)?;
129
130 let stable_version_str = raw
132 .versions
133 .stable
134 .clone()
135 .ok_or_else(|| de::Error::missing_field("versions.stable"))?;
136 let version_semver = match crate::model::version::Version::parse(&stable_version_str) {
137 Ok(v) => v.into(),
138 Err(_) => {
139 let mut majors = 0u32;
140 let mut minors = 0u32;
141 let mut patches = 0u32;
142 let mut part_count = 0;
143 for (i, part) in stable_version_str.split('.').enumerate() {
144 let numeric_part = part
145 .chars()
146 .take_while(|c| c.is_ascii_digit())
147 .collect::<String>();
148 if numeric_part.is_empty() && i > 0 {
149 break;
150 }
151 if numeric_part.len() < part.len() && i > 0 {
152 if let Ok(num) = numeric_part.parse::<u32>() {
153 match i {
154 0 => majors = num,
155 1 => minors = num,
156 2 => patches = num,
157 _ => {}
158 }
159 part_count += 1;
160 }
161 break;
162 }
163 if let Ok(num) = numeric_part.parse::<u32>() {
164 match i {
165 0 => majors = num,
166 1 => minors = num,
167 2 => patches = num,
168 _ => {}
169 }
170 part_count += 1;
171 }
172 if i >= 2 {
173 break;
174 }
175 }
176 let version_str_padded = match part_count {
177 1 => format!("{majors}.0.0"),
178 2 => format!("{majors}.{minors}.0"),
179 _ => format!("{majors}.{minors}.{patches}"),
180 };
181 match Version::parse(&version_str_padded) {
182 Ok(v) => v,
183 Err(_) => {
184 error!(
185 "Warning: Could not parse version '{}' (sanitized to '{}') for formula '{}'. Using 0.0.0.",
186 stable_version_str, version_str_padded, raw.name
187 );
188 Version::new(0, 0, 0)
189 }
190 }
191 }
192 };
193
194 let mut final_url = raw.url;
196 let mut final_sha256 = raw.sha256;
197 if final_url.is_empty() {
198 if let Some(Value::Object(urls_map)) = raw.urls {
199 if let Some(Value::Object(stable_url_info)) = urls_map.get("stable") {
200 if let Some(Value::String(u)) = stable_url_info.get("url") {
201 final_url = u.clone();
202 }
203 if let Some(Value::String(s)) = stable_url_info
204 .get("checksum")
205 .or_else(|| stable_url_info.get("sha256"))
206 {
207 final_sha256 = s.clone();
208 }
209 }
210 }
211 }
212 if final_url.is_empty() && raw.versions.head.is_none() {
213 debug!("Warning: Formula '{}' has no stable URL defined.", raw.name);
214 }
215
216 let mut combined_dependencies: Vec<Dependency> = Vec::new();
218 let mut seen_deps: HashMap<String, DependencyTag> = HashMap::new();
219 let mut process_list = |deps: &[String], tag: DependencyTag| {
220 for name in deps {
221 *seen_deps
222 .entry(name.clone())
223 .or_insert(DependencyTag::empty()) |= tag;
224 }
225 };
226 process_list(&raw.dependencies, DependencyTag::RUNTIME);
227 process_list(&raw.build_dependencies, DependencyTag::BUILD);
228 process_list(&raw.test_dependencies, DependencyTag::TEST);
229 process_list(
230 &raw.recommended_dependencies,
231 DependencyTag::RECOMMENDED | DependencyTag::RUNTIME,
232 );
233 process_list(
234 &raw.optional_dependencies,
235 DependencyTag::OPTIONAL | DependencyTag::RUNTIME,
236 );
237 for (name, tags) in seen_deps {
238 combined_dependencies.push(Dependency::new_with_tags(name, tags));
239 }
240
241 let mut combined_resources: Vec<ResourceSpec> = Vec::new();
244 for res_val in raw.resources {
245 if let Value::Object(map) = res_val {
248 if let Some((res_name, res_spec_val)) = map.into_iter().next() {
250 match ResourceSpec::deserialize(res_spec_val.clone()) {
252 Ok(mut res_spec) => {
254 if res_spec.name.is_empty() {
256 res_spec.name = res_name;
257 } else if res_spec.name != res_name {
258 debug!(
259 "Resource name mismatch in formula '{}': key '{}' vs spec '{}'. Using key.",
260 raw.name, res_name, res_spec.name
261 );
262 res_spec.name = res_name; }
264 if res_spec.url.is_empty() || res_spec.sha256.is_empty() {
266 debug!(
267 "Resource '{}' for formula '{}' is missing URL or SHA256. Skipping.",
268 res_spec.name, raw.name
269 );
270 continue;
271 }
272 debug!(
273 "Parsed resource '{}' for formula '{}'",
274 res_spec.name, raw.name
275 );
276 combined_resources.push(res_spec);
277 }
278 Err(e) => {
279 debug!(
281 "Failed to parse resource spec value for key '{}' in formula '{}': {}. Value: {:?}",
282 res_name, raw.name, e, res_spec_val
283 );
284 }
285 }
286 } else {
287 debug!("Empty resource object found in formula '{}'.", raw.name);
288 }
289 } else {
290 debug!(
291 "Unexpected format for resource entry in formula '{}': expected object, got {:?}",
292 raw.name, res_val
293 );
294 }
295 }
296
297 Ok(Self {
298 name: raw.name,
299 stable_version_str,
300 version_semver,
301 revision: raw.revision,
302 desc: raw.desc,
303 homepage: raw.homepage,
304 url: final_url,
305 sha256: final_sha256,
306 mirrors: raw.mirrors,
307 bottle: raw.bottle,
308 dependencies: combined_dependencies,
309 requirements: raw.requirements,
310 resources: combined_resources, install_keg_path: None,
312 })
313 }
314}
315
316impl Formula {
318 pub fn dependencies(&self) -> Result<Vec<Dependency>> {
320 Ok(self.dependencies.clone())
321 }
322 pub fn requirements(&self) -> Result<Vec<Requirement>> {
323 Ok(self.requirements.clone())
324 }
325
326 pub fn resources(&self) -> Result<Vec<ResourceSpec>> {
328 Ok(self.resources.clone())
329 }
330
331 pub fn set_keg_path(&mut self, path: PathBuf) {
333 self.install_keg_path = Some(path);
334 }
335 pub fn version_str_full(&self) -> String {
336 if self.revision > 0 {
337 format!("{}_{}", self.stable_version_str, self.revision)
338 } else {
339 self.stable_version_str.clone()
340 }
341 }
342 pub fn name(&self) -> &str {
343 &self.name
344 }
345 pub fn version(&self) -> &Version {
346 &self.version_semver
347 }
348 pub fn source_url(&self) -> &str {
349 &self.url
350 }
351 pub fn source_sha256(&self) -> &str {
352 &self.sha256
353 }
354 pub fn get_bottle_spec(&self, bottle_tag: &str) -> Option<&BottleFileSpec> {
355 self.bottle.stable.as_ref()?.files.get(bottle_tag)
356 }
357}
358
359pub trait FormulaDependencies {
361 fn name(&self) -> &str;
362 fn install_prefix(&self, cellar_path: &Path) -> Result<PathBuf>;
363 fn resolved_runtime_dependency_paths(&self) -> Result<Vec<PathBuf>>;
364 fn resolved_build_dependency_paths(&self) -> Result<Vec<PathBuf>>;
365 fn all_resolved_dependency_paths(&self) -> Result<Vec<PathBuf>>;
366}
367impl FormulaDependencies for Formula {
368 fn name(&self) -> &str {
369 &self.name
370 }
371 fn install_prefix(&self, cellar_path: &Path) -> Result<PathBuf> {
372 let version_string = self.version_str_full();
373 Ok(cellar_path.join(self.name()).join(version_string))
374 }
375 fn resolved_runtime_dependency_paths(&self) -> Result<Vec<PathBuf>> {
376 Ok(Vec::new())
377 }
378 fn resolved_build_dependency_paths(&self) -> Result<Vec<PathBuf>> {
379 Ok(Vec::new())
380 }
381 fn all_resolved_dependency_paths(&self) -> Result<Vec<PathBuf>> {
382 Ok(Vec::new())
383 }
384}
385
386fn deserialize_requirements<'de, D>(
389 deserializer: D,
390) -> std::result::Result<Vec<Requirement>, D::Error>
391where
392 D: serde::Deserializer<'de>,
393{
394 #[derive(Deserialize, Debug)]
395 struct ReqWrapper {
396 #[serde(default)]
397 name: String,
398 #[serde(default)]
399 version: Option<String>,
400 #[serde(default)]
401 cask: Option<String>,
402 #[serde(default)]
403 download: Option<String>,
404 }
405 let raw_reqs: Vec<Value> = Deserialize::deserialize(deserializer)?;
406 let mut requirements = Vec::new();
407 for req_val in raw_reqs {
408 if let Ok(req_obj) = serde_json::from_value::<ReqWrapper>(req_val.clone()) {
409 match req_obj.name.as_str() {
410 "macos" => {
411 requirements.push(Requirement::MacOS(
412 req_obj.version.unwrap_or_else(|| "any".to_string()),
413 ));
414 }
415 "xcode" => {
416 requirements.push(Requirement::Xcode(
417 req_obj.version.unwrap_or_else(|| "any".to_string()),
418 ));
419 }
420 "cask" => {
421 requirements.push(Requirement::Other(format!(
422 "Cask Requirement: {}",
423 req_obj.cask.unwrap_or_else(|| "?".to_string())
424 )));
425 }
426 "download" => {
427 requirements.push(Requirement::Other(format!(
428 "Download Requirement: {}",
429 req_obj.download.unwrap_or_else(|| "?".to_string())
430 )));
431 }
432 _ => requirements.push(Requirement::Other(format!(
433 "Unknown requirement type: {req_obj:?}"
434 ))),
435 }
436 } else if let Value::String(req_str) = req_val {
437 match req_str.as_str() {
438 "macos" => requirements.push(Requirement::MacOS("latest".to_string())),
439 "xcode" => requirements.push(Requirement::Xcode("latest".to_string())),
440 _ => {
441 requirements.push(Requirement::Other(format!("Simple requirement: {req_str}")))
442 }
443 }
444 } else {
445 debug!("Warning: Could not parse requirement: {:?}", req_val);
446 requirements.push(Requirement::Other(format!(
447 "Unparsed requirement: {req_val:?}"
448 )));
449 }
450 }
451 Ok(requirements)
452}
453
454impl<'de> Deserialize<'de> for ResourceSpec {
456 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
457 where
458 D: Deserializer<'de>,
459 {
460 #[derive(Deserialize)]
461 struct Helper {
462 #[serde(default)]
463 name: String, url: String,
465 sha256: String,
466 }
467 let helper = Helper::deserialize(deserializer)?;
468 Ok(Self {
471 name: helper.name,
472 url: helper.url,
473 sha256: helper.sha256,
474 })
475 }
476}