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)?, )
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}