pkgs/config/
named_package.rs

1use std::path::Path;
2
3use super::{Config, PkgsParseError, VarMap};
4use crate::config::{Package, PackageType};
5
6impl Config {
7    pub fn get(&self, name: &str) -> Result<NamedPackage, PkgsParseError> {
8        NamedPackage::try_new(
9            name,
10            self.packages[name].clone(),
11            VarMap::try_new(&self.vars)?, // PERF: varmap will be built multiple times here
12        )
13    }
14}
15
16#[derive(Debug)]
17pub struct NamedPackage {
18    name: String,
19    kind: PackageType,
20    maps: Vec<(String, String)>,
21}
22
23impl NamedPackage {
24    pub fn try_new(name: &str, package: Package, mut vars: VarMap) -> Result<Self, PkgsParseError> {
25        vars.extends(&package.vars)?;
26
27        let maps = package
28            .maps
29            .into_iter()
30            .map(|(k, v)| {
31                let mut v = vars.parse(&v)?;
32
33                let k_path = Path::new(&k);
34                if v.ends_with('/') {
35                    v.push_str(
36                        k_path
37                            .file_name()
38                            .ok_or_else(|| PkgsParseError::NoneFilename(k.clone()))?
39                            .to_string_lossy()
40                            .as_ref(),
41                    );
42                }
43
44                Ok((k, v))
45            })
46            .collect::<Result<Vec<_>, PkgsParseError>>()?;
47
48        Ok(Self {
49            name: name.to_string(),
50            kind: package.kind,
51            maps,
52        })
53    }
54
55    pub fn get_directory(&self) -> String {
56        match self.kind() {
57            PackageType::Local => self.name.to_string(),
58        }
59    }
60
61    pub fn name(&self) -> &str {
62        &self.name
63    }
64
65    pub fn kind(&self) -> PackageType {
66        self.kind
67    }
68
69    pub fn maps(&self) -> &[(String, String)] {
70        &self.maps
71    }
72
73    #[cfg(test)]
74    pub fn insert_map(&mut self, key: impl AsRef<str>, value: impl AsRef<str>) {
75        self.maps
76            .push((key.as_ref().to_string(), value.as_ref().to_string()));
77    }
78
79    #[cfg(test)]
80    pub fn remove_map(&mut self, key: impl AsRef<str>) {
81        self.maps.retain(|(k, _)| k.as_str() != key.as_ref());
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use std::collections::BTreeMap;
88
89    use super::*;
90    use crate::{fs::home_dir, test_utils::prelude::*};
91
92    fn setup() -> Config {
93        let vars = vec![
94            ("APP_DIR".to_string(), "${HOME}/myapp".to_string()),
95            ("MY_VAR1".to_string(), "hello".to_string()),
96            ("MY_VAR2".to_string(), "${MY_VAR1}_world".to_string()),
97        ];
98
99        let packages = BTreeMap::from_iter([(
100            "test_pkg".to_string(),
101            Package {
102                kind: PackageType::Local,
103                vars: vec![],
104                maps: vec![
105                    ("app_dir".to_string(), "${APP_DIR}".to_string()),
106                    ("path".to_string(), "/usr/local/${MY_VAR2}".to_string()),
107                    ("config".to_string(), "${MY_VAR1}_config".to_string()),
108                ],
109            },
110        )]);
111
112        Config { vars, packages }
113    }
114
115    #[gtest]
116    fn it_works() -> Result<()> {
117        let config = setup();
118
119        let pkg = config.get("test_pkg")?;
120        expect_eq!(pkg.name(), "test_pkg");
121        expect_eq!(pkg.kind(), PackageType::Local);
122        expect_eq!(pkg.get_directory(), "test_pkg");
123
124        expect_eq!(
125            *pkg.maps(),
126            [
127                (
128                    "app_dir".into(),
129                    home_dir().join("myapp").to_str().unwrap().into()
130                ),
131                ("path".into(), "/usr/local/hello_world".into()),
132                ("config".into(), "hello_config".into())
133            ]
134        );
135
136        Ok(())
137    }
138
139    #[gtest]
140    fn local_vars() -> Result<()> {
141        let mut config = setup();
142        config
143            .packages
144            .get_mut("test_pkg")
145            .unwrap()
146            .vars
147            .push(("MY_VAR1".to_string(), "hi".to_string()));
148
149        let pkg = config.get("test_pkg")?;
150        expect_eq!(
151            *pkg.maps(),
152            [
153                (
154                    "app_dir".into(),
155                    home_dir().join("myapp").to_str().unwrap().into()
156                ),
157                ("path".into(), "/usr/local/hello_world".into()),
158                ("config".into(), "hi_config".into())
159            ]
160        );
161
162        Ok(())
163    }
164
165    #[gtest]
166    fn unknown_var_when_build() -> Result<()> {
167        let mut config = setup();
168        config
169            .vars
170            .push(("MY_VAR3".to_string(), "${UNKNOWN}".to_string()));
171
172        let err = config.get("test_pkg").unwrap_err();
173        expect_that!(err, pat!(PkgsParseError::VarsBuild(_)));
174
175        Ok(())
176    }
177
178    #[gtest]
179    fn unknown_var_when_parse() -> Result<()> {
180        let mut config = setup();
181        config
182            .packages
183            .get_mut("test_pkg")
184            .unwrap()
185            .maps
186            .push(("bad".to_string(), "${UNKNOWN}".to_string()));
187
188        let err = config.get("test_pkg").unwrap_err();
189        expect_that!(err, pat!(PkgsParseError::VarsParse(_)));
190
191        Ok(())
192    }
193
194    mod trailing_slash {
195        use super::*;
196
197        fn setup(src: &str, dst: &str) -> Config {
198            let vars = vec![
199                ("APP_DIR".to_string(), "${HOME}/myapp".to_string()),
200                ("MY_VAR1".to_string(), "hello/".to_string()),
201            ];
202
203            let packages = BTreeMap::from_iter([(
204                "test_pkg".to_string(),
205                Package {
206                    kind: PackageType::Local,
207                    vars: vec![],
208                    maps: vec![(src.to_string(), dst.to_string())],
209                },
210            )]);
211
212            Config { vars, packages }
213        }
214
215        #[gtest]
216        fn common_trailing_slash() -> Result<()> {
217            let src = "path/to/trailing_slash";
218            let config = setup(src, "/usr/bin/");
219            let pkg = config.get("test_pkg")?;
220
221            expect_eq!(
222                *pkg.maps(),
223                [(src.into(), "/usr/bin/trailing_slash".into())]
224            );
225
226            Ok(())
227        }
228
229        #[gtest]
230        fn no_trailing_slash() -> Result<()> {
231            let src = "path/to/no_trailing_slash";
232            let config = setup(src, "/usr/local/bin");
233            let pkg = config.get("test_pkg")?;
234
235            expect_eq!(*pkg.maps(), [(src.into(), "/usr/local/bin".into())]);
236
237            Ok(())
238        }
239
240        #[gtest]
241        fn trailing_slash_in_var() -> Result<()> {
242            let src = "path/to/trailing_var";
243            let config = setup(src, "${MY_VAR1}");
244            let pkg = config.get("test_pkg")?;
245
246            expect_eq!(*pkg.maps(), [(src.into(), "hello/trailing_var".into())]);
247
248            Ok(())
249        }
250
251        #[gtest]
252        fn no_filename() -> Result<()> {
253            let src = "no_filename/..";
254            let config = setup(src, "/usr/bin/");
255            let err = config.get("test_pkg").unwrap_err();
256
257            expect_that!(err, pat!(PkgsParseError::NoneFilename(src)));
258
259            Ok(())
260        }
261    }
262
263    mod local_vars {}
264}