use std::path::Path;
use zenops_expand::{ExpandLookup, ExpandStr};
use super::Os;
#[derive(serde::Deserialize, schemars::JsonSchema, Debug, Clone, PartialEq)]
pub struct DetectStrategy {
#[serde(default)]
pub os: Vec<Os>,
#[serde(flatten)]
pub kind: DetectKind,
}
#[derive(serde::Deserialize, schemars::JsonSchema, Debug, Clone, PartialEq)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum DetectKind {
File {
path: ExpandStr,
},
Which {
binary: ExpandStr,
},
Any {
of: Vec<DetectStrategy>,
},
All {
of: Vec<DetectStrategy>,
},
}
impl DetectStrategy {
pub fn check(&self, home: &Path, lookup: &impl ExpandLookup) -> bool {
if !self.os.is_empty() {
let Some(cur) = Os::current() else {
return false;
};
if !self.os.contains(&cur) {
return false;
}
}
self.kind.check(home, lookup)
}
}
impl DetectKind {
pub fn check(&self, home: &Path, lookup: &impl ExpandLookup) -> bool {
match self {
Self::File { path } => {
let Ok(expanded) = path.expand_to_string(lookup) else {
return false;
};
let resolved = expanded.replacen('~', &home.to_string_lossy(), 1);
Path::new(&resolved).exists()
}
Self::Which { binary } => match binary.expand_to_string(lookup) {
Ok(b) => which_on_path(&b),
Err(_) => false,
},
Self::Any { of } => of.iter().any(|s| s.check(home, lookup)),
Self::All { of } => of.iter().all(|s| s.check(home, lookup)),
}
}
}
impl std::fmt::Display for DetectStrategy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if !self.os.is_empty() {
let names: Vec<&'static str> = self
.os
.iter()
.map(|o| match o {
Os::Linux => "linux",
Os::Macos => "macos",
})
.collect();
write!(f, "[os={}] ", names.join(","))?;
}
write!(f, "{}", self.kind)
}
}
impl std::fmt::Display for DetectKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::File { path } => write!(f, "{}", path.as_template()),
Self::Which { binary } => write!(f, "which {}", binary.as_template()),
Self::Any { of } => write_combinator(f, "any", of),
Self::All { of } => write_combinator(f, "all", of),
}
}
}
fn write_combinator(
f: &mut std::fmt::Formatter<'_>,
name: &str,
of: &[DetectStrategy],
) -> std::fmt::Result {
write!(f, "{name}(")?;
for (i, s) in of.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{s}")?;
}
write!(f, ")")
}
pub(crate) fn which_on_path(binary: &str) -> bool {
std::env::var("PATH")
.unwrap_or_default()
.split(':')
.any(|dir| Path::new(dir).join(binary).is_file())
}