Skip to main content

soar_db/models/
types.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Deserialize, Serialize)]
4pub enum ProvideStrategy {
5    KeepTargetOnly,
6    KeepBoth,
7    Alias,
8}
9
10impl std::fmt::Display for ProvideStrategy {
11    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
12        let msg = match self {
13            ProvideStrategy::KeepTargetOnly => "=>",
14            ProvideStrategy::KeepBoth => "==",
15            ProvideStrategy::Alias => ":",
16        };
17        write!(f, "{msg}")
18    }
19}
20
21#[derive(Debug, Default, Deserialize, Serialize, Clone)]
22pub struct PackageProvide {
23    pub name: String,
24    pub target: Option<String>,
25    pub strategy: Option<ProvideStrategy>,
26    pub symlink_to_bin: bool,
27}
28
29impl PackageProvide {
30    /// Returns the symlink names this provide creates in the bin directory,
31    /// mirroring the install logic in `setup_provide_symlinks`.
32    pub fn bin_symlink_names(&self) -> Vec<&str> {
33        if self.symlink_to_bin {
34            // @name -> bin/name
35            return vec![&self.name];
36        }
37        match (&self.target, &self.strategy) {
38            (Some(target), Some(ProvideStrategy::KeepBoth)) => vec![&self.name, target],
39            (Some(target), Some(ProvideStrategy::KeepTargetOnly | ProvideStrategy::Alias)) => {
40                vec![target]
41            }
42            _ => vec![&self.name],
43        }
44    }
45
46    pub fn from_string(provide: &str) -> Self {
47        let (symlink_to_bin, provide) = if let Some(stripped) = provide.strip_prefix('@') {
48            (true, stripped)
49        } else {
50            (false, provide)
51        };
52
53        if let Some((name, target_name)) = provide.split_once("==") {
54            Self {
55                name: name.to_string(),
56                target: Some(target_name.to_string()),
57                strategy: Some(ProvideStrategy::KeepBoth),
58                symlink_to_bin,
59            }
60        } else if let Some((name, target_name)) = provide.split_once("=>") {
61            Self {
62                name: name.to_string(),
63                target: Some(target_name.to_string()),
64                strategy: Some(ProvideStrategy::KeepTargetOnly),
65                symlink_to_bin,
66            }
67        } else if let Some((name, target_name)) = provide.split_once(":") {
68            Self {
69                name: name.to_string(),
70                target: Some(target_name.to_string()),
71                strategy: Some(ProvideStrategy::Alias),
72                symlink_to_bin,
73            }
74        } else {
75            Self {
76                name: provide.to_string(),
77                target: None,
78                strategy: None,
79                symlink_to_bin,
80            }
81        }
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn test_bin_symlink_names_plain() {
91        let p = PackageProvide::from_string("clipcatd");
92        assert_eq!(p.bin_symlink_names(), vec!["clipcatd"]);
93    }
94
95    #[test]
96    fn test_bin_symlink_names_at_prefix() {
97        let p = PackageProvide::from_string("@clipcat-menu");
98        assert!(p.symlink_to_bin);
99        assert_eq!(p.bin_symlink_names(), vec!["clipcat-menu"]);
100    }
101
102    #[test]
103    fn test_bin_symlink_names_keep_both() {
104        let p = PackageProvide::from_string("clipcatd==clipcat");
105        assert_eq!(p.bin_symlink_names(), vec!["clipcatd", "clipcat"]);
106    }
107
108    #[test]
109    fn test_bin_symlink_names_keep_target_only() {
110        let p = PackageProvide::from_string("clipcatd=>clipcat");
111        assert_eq!(p.bin_symlink_names(), vec!["clipcat"]);
112    }
113
114    #[test]
115    fn test_bin_symlink_names_alias() {
116        let p = PackageProvide::from_string("clipcatd:clipcat");
117        assert_eq!(p.bin_symlink_names(), vec!["clipcat"]);
118    }
119}