use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
pub mod detect;
pub mod registry;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
pub struct ProgramDep {
pub name: String,
pub detect: DetectMethod,
pub reason: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hint: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub registry: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub recipe: Option<ProgramRecipe>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(untagged)]
pub enum DetectMethod {
File { file: String },
Command { command: String },
Version { version: VersionCheck },
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
pub struct VersionCheck {
pub command: String,
pub min: String,
}
#[derive(Debug, Clone, PartialEq)]
pub enum DepStatus {
Present,
Missing,
PresentWrongVersion { found: String },
}
pub fn current_platform() -> String {
format!("{}-{}", std::env::consts::ARCH, std::env::consts::OS)
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
pub struct ProgramRecipe {
pub platforms: BTreeMap<String, PlatformRecipe>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
pub struct PlatformRecipe {
pub url: String,
pub sha256: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub install_to: Option<String>,
#[serde(default)]
pub executable: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub archive: Option<registry::ArchiveSpec>,
}
impl ProgramRecipe {
pub fn for_platform(&self, platform: &str) -> Option<registry::CuratedRecipe> {
let p = self.platforms.get(platform)?;
Some(registry::CuratedRecipe {
description: "author-declared recipe".to_string(),
url: p.url.clone(),
sha256: p.sha256.clone(),
install_to: p.install_to.clone(),
executable: p.executable,
archive: p.archive.clone(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn program_dep_parses_all_detect_methods() {
let y = r#"
- name: lightpanda
detect: { file: "~/.mur/aura/lightpanda" }
reason: "render tier"
hint: "https://lightpanda.io/download"
registry: lightpanda
- name: gh
detect: { command: "gh" }
reason: "github ops"
- name: node
detect: { version: { command: "node --version", min: "18.0.0" } }
reason: "js runtime"
"#;
let deps: Vec<ProgramDep> = serde_yaml::from_str(y).unwrap();
assert_eq!(deps.len(), 3);
assert_eq!(deps[0].name, "lightpanda");
assert!(
matches!(&deps[0].detect, DetectMethod::File { file } if file == "~/.mur/aura/lightpanda")
);
assert_eq!(deps[0].registry.as_deref(), Some("lightpanda"));
assert!(matches!(&deps[1].detect, DetectMethod::Command { command } if command == "gh"));
assert!(deps[1].hint.is_none());
assert!(
matches!(&deps[2].detect, DetectMethod::Version { version } if version.min == "18.0.0")
);
}
#[test]
fn current_platform_is_arch_dash_os() {
let p = current_platform();
assert!(p.contains('-'));
let (arch, os) = p.split_once('-').unwrap();
assert!(!arch.is_empty() && !os.is_empty());
}
#[test]
fn program_dep_parses_optional_recipe_and_defaults_none() {
let with = r#"
name: some-tool
detect: { command: some-tool }
reason: "x"
recipe:
platforms:
aarch64-macos: { url: "u", sha256: "s", install_to: "aura/some-tool", executable: true }
"#;
let d: ProgramDep = serde_yaml::from_str(with).unwrap();
assert!(d.recipe.is_some());
assert!(d.recipe.unwrap().for_platform("aarch64-macos").is_some());
let without = "name: x\ndetect: { command: x }\nreason: r\n";
let d2: ProgramDep = serde_yaml::from_str(without).unwrap();
assert!(d2.recipe.is_none());
}
}
#[cfg(test)]
mod recipe_tests {
use super::*;
#[test]
fn program_recipe_for_platform_converts_to_curated() {
let y = r#"
platforms:
aarch64-macos: { url: "https://x/tool", sha256: "abc123", install_to: "aura/tool", executable: true }
"#;
let r: ProgramRecipe = serde_yaml::from_str(y).unwrap();
let cur = r.for_platform("aarch64-macos").expect("platform present");
assert_eq!(cur.url, "https://x/tool");
assert_eq!(cur.sha256, "abc123");
assert_eq!(cur.install_to.as_deref(), Some("aura/tool"));
assert!(cur.executable);
assert!(cur.archive.is_none());
assert!(r.for_platform("sparc-solaris").is_none());
}
}