Skip to main content

oxios_kernel/skill/
requirements.rs

1#![allow(missing_docs)]
2//! Requirements evaluation.
3
4use super::types::*;
5
6fn has_bin(bin: &str) -> bool {
7    std::process::Command::new("which")
8        .arg(bin)
9        .output()
10        .map(|o| o.status.success())
11        .unwrap_or(false)
12}
13
14pub fn current_platform() -> &'static str {
15    if cfg!(target_os = "macos") {
16        "darwin"
17    } else if cfg!(target_os = "windows") {
18        "windows"
19    } else {
20        "linux"
21    }
22}
23
24pub fn check_requirements(metadata: &SkillMetadata) -> RequirementsCheck {
25    let platform = current_platform();
26    let missing_bins: Vec<String> = metadata
27        .requires
28        .bins
29        .iter()
30        .filter(|b| !has_bin(b))
31        .cloned()
32        .collect();
33    let missing_any_bins = if metadata.requires.any_bins.is_empty()
34        || metadata.requires.any_bins.iter().any(|b| has_bin(b))
35    {
36        Vec::new()
37    } else {
38        metadata.requires.any_bins.clone()
39    };
40    let missing_env: Vec<String> = metadata
41        .requires
42        .env
43        .iter()
44        .filter(|e| std::env::var(e).is_err())
45        .cloned()
46        .collect();
47    let config_checks: Vec<ConfigCheck> = metadata
48        .requires
49        .config
50        .iter()
51        .map(|path| ConfigCheck {
52            path: path.clone(),
53            satisfied: true,
54        })
55        .collect();
56    let missing_config: Vec<String> = config_checks
57        .iter()
58        .filter(|c| !c.satisfied)
59        .map(|c| c.path.clone())
60        .collect();
61    let missing_os = if metadata.os.is_empty() || metadata.os.iter().any(|o| o == platform) {
62        Vec::new()
63    } else {
64        metadata.os.clone()
65    };
66    let eligible = metadata.always
67        || (missing_bins.is_empty()
68            && missing_any_bins.is_empty()
69            && missing_env.is_empty()
70            && missing_config.is_empty()
71            && missing_os.is_empty());
72    RequirementsCheck {
73        missing_bins,
74        missing_any_bins,
75        missing_env,
76        missing_config,
77        missing_os,
78        eligible,
79        config_checks,
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    #[test]
87    fn test_no_reqs() {
88        assert!(check_requirements(&SkillMetadata::default()).eligible);
89    }
90    #[test]
91    fn test_always() {
92        let mut m = SkillMetadata::default();
93        m.always = true;
94        m.requires.bins = vec!["nonexistent-xyz".into()];
95        assert!(check_requirements(&m).eligible);
96    }
97    #[test]
98    fn test_existing_bin() {
99        let mut m = SkillMetadata::default();
100        m.requires.bins = vec!["echo".into()];
101        assert!(check_requirements(&m).eligible);
102    }
103    #[test]
104    fn test_missing_bin() {
105        let mut m = SkillMetadata::default();
106        m.requires.bins = vec!["nonexistent-xyz".into()];
107        assert!(!check_requirements(&m).eligible);
108    }
109    #[test]
110    fn test_missing_env() {
111        let mut m = SkillMetadata::default();
112        m.requires.env = vec!["OXIOS_TEST_MISSING".into()];
113        assert!(!check_requirements(&m).eligible);
114    }
115}