use std::path::Path;
use zenops_expand::{ExpandLookup, ExpandStr};
use super::error::Error;
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) -> Result<bool, Error> {
if !self.os.is_empty() && !self.os.contains(&Os::current()?) {
return Ok(false);
}
self.kind.check(home, lookup)
}
}
impl DetectKind {
pub fn check(&self, home: &Path, lookup: &impl ExpandLookup) -> Result<bool, Error> {
match self {
Self::File { path } => {
let Ok(expanded) = path.expand_to_string(lookup) else {
return Ok(false);
};
let resolved = expanded.replacen('~', &home.to_string_lossy(), 1);
Path::new(&resolved)
.try_exists()
.map_err(|e| Error::ExistsFailed(resolved, e))
}
Self::Which { binary } => Ok(crate::utils::which::expand_and_exists(binary, lookup)?),
Self::Any { of } => {
for s in of {
if s.check(home, lookup)? {
return Ok(true);
}
}
Ok(false)
}
Self::All { of } => {
for s in of {
if !s.check(home, lookup)? {
return Ok(false);
}
}
Ok(true)
}
}
}
}
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, ")")
}