1use std::collections::HashMap;
2
3use super::{VarsBuildError, VarsParseError};
4use crate::fs::home_dir;
5
6#[derive(Debug)]
7pub struct VarMap {
8 map: HashMap<String, String>,
9}
10
11impl VarMap {
12 fn default_vars() -> HashMap<String, String> {
13 HashMap::from([("HOME".into(), home_dir().to_string_lossy().into())])
14 }
15
16 pub fn try_new(vars: &[(String, String)]) -> Result<Self, VarsBuildError> {
17 let mut ret = Self::default();
18 for (var, value) in vars {
19 ret.add_var(var, value)?;
20 }
21 Ok(ret)
22 }
23
24 pub fn extends(&mut self, vars: &[(String, String)]) -> Result<(), VarsBuildError> {
25 for (var, value) in vars {
26 self.add_var(var, value)?;
27 }
28 Ok(())
29 }
30
31 fn add_var(&mut self, var: &str, value: &str) -> Result<(), VarsBuildError> {
32 let value = self.parse(value).map_err(|kind| VarsBuildError {
33 var: var.to_string(),
34 kind,
35 })?;
36 self.map.insert(var.to_string(), value);
37 Ok(())
38 }
39
40 pub fn map(&self) -> &HashMap<String, String> {
41 &self.map
42 }
43
44 pub fn parse(&self, input: &str) -> Result<String, VarsParseError> {
45 let mut result = String::with_capacity(input.len());
46 let mut rest = input;
47 let mut cursor = 0;
48
49 while let Some(i) = rest.find("${") {
50 result.push_str(&rest[..i]);
51
52 let after = i + 2;
53 let Some(end_rel) = rest[after..].find('}') else {
54 return Err(VarsParseError::UnclosedBrace(cursor + i));
55 };
56
57 let name = &rest[after..after + end_rel];
58 if name.is_empty() {
59 return Err(VarsParseError::EmptyVarName(cursor + i));
60 }
61
62 match self.map().get(name) {
63 Some(value) => result.push_str(value),
64 None => return Err(VarsParseError::UnknowndVar(name.to_string(), cursor + i)),
65 }
66
67 rest = &rest[after + end_rel + 1..];
68 cursor += after + end_rel + 1;
69 }
70 result.push_str(rest);
71
72 Ok(result)
73 }
74}
75
76impl Default for VarMap {
77 fn default() -> Self {
78 Self {
79 map: Self::default_vars(),
80 }
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87 use crate::test_utils::prelude::*;
88
89 fn setup() -> Result<VarMap> {
90 let custom = vec![
91 ("CONFIG_DIR".into(), "${HOME}/.config".into()),
92 ("MY_VAR1".into(), "hello".into()),
93 ("MY_VAR2".into(), "${MY_VAR1}_world".into()),
94 ];
95 Ok(VarMap::try_new(&custom)?)
96 }
97
98 #[gtest]
99 fn default_vars() -> Result<()> {
100 let var_map = VarMap::try_new(&[])?;
101 expect_eq!(var_map.map()["HOME"], home_dir().to_str().unwrap());
102 Ok(())
103 }
104
105 mod extend_vars {
106 use super::*;
107
108 #[gtest]
109 fn add_new() -> Result<()> {
110 let mut var_map = setup()?;
111 let len = var_map.map().len();
112
113 var_map.extends(&[("NEW_VAR".into(), "new_value".into())])?;
114 expect_eq!(var_map.map().len(), len + 1);
115 expect_eq!(var_map.map()["NEW_VAR"], "new_value");
116
117 Ok(())
118 }
119
120 #[gtest]
121 fn override_existing() -> Result<()> {
122 let mut var_map = setup()?;
123 let len = var_map.map().len();
124
125 var_map.extends(&[("MY_VAR1".into(), "${MY_VAR1}_new".into())])?;
126 expect_eq!(var_map.map().len(), len);
127 expect_eq!(var_map.map()["MY_VAR1"], "hello_new");
128 expect_eq!(var_map.map()["MY_VAR2"], "hello_world");
129
130 Ok(())
131 }
132 }
133
134 #[gtest]
135 fn custom_vars() -> Result<()> {
136 let var_map = setup()?;
137 expect_eq!(var_map.map()["HOME"], home_dir().to_str().unwrap());
138 expect_eq!(
139 var_map.map()["CONFIG_DIR"],
140 home_dir().join(".config").to_str().unwrap()
141 );
142 expect_eq!(var_map.map()["MY_VAR1"], "hello");
143 expect_eq!(var_map.map()["MY_VAR2"], "hello_world");
144
145 Ok(())
146 }
147
148 #[gtest]
149 fn custom_vars_with_wrong_order() -> Result<()> {
150 let custom = vec![
151 ("MY_VAR2".into(), "${MY_VAR1}_world".into()),
152 ("MY_VAR1".into(), "hello".into()),
153 ];
154
155 let err = VarMap::try_new(&custom).unwrap_err();
156 expect_that!(
157 err.kind,
158 pat!(VarsParseError::UnknowndVar("MY_VAR1", &0_usize))
159 );
160 expect_eq!(err.var, "MY_VAR2");
161
162 Ok(())
163 }
164
165 mod parse {
166 use super::*;
167
168 #[gtest]
169 fn common_parse() -> Result<()> {
170 let var_map = setup()?;
171 expect_eq!(
172 var_map.parse("${CONFIG_DIR}/test")?,
173 home_dir().join(".config/test").to_str().unwrap()
174 );
175 expect_eq!(
176 var_map.parse("/tmp/${MY_VAR1}/${MY_VAR2}/${MY_VAR1}")?,
177 "/tmp/hello/hello_world/hello"
178 );
179
180 Ok(())
181 }
182
183 #[gtest]
184 fn unclosed_brace() -> Result<()> {
185 let var_map = setup()?;
186 let err = var_map
187 .parse("/tmp/${MY_VAR1}/${MY_VAR2/hello")
188 .unwrap_err();
189 expect_that!(err, pat!(VarsParseError::UnclosedBrace(&16_usize)));
190 Ok(())
191 }
192
193 #[gtest]
194 fn empty_var() -> Result<()> {
195 let var_map = setup()?;
196 let err = var_map.parse("/tmp/${MY_VAR1}/${MY_VAR2}/${}").unwrap_err();
197 expect_that!(err, pat!(VarsParseError::EmptyVarName(&27_usize)));
198 Ok(())
199 }
200
201 #[gtest]
202 fn unknowd_var() -> Result<()> {
203 let var_map = setup()?;
204 let err = var_map
205 .parse("/tmp/${MY_VAR1}/${MY_VAR2}/${MY_VAR3}")
206 .unwrap_err();
207 expect_that!(err, pat!(VarsParseError::UnknowndVar("MY_VAR3", &27_usize)));
208 Ok(())
209 }
210 }
211}