1mod variable;
2use std::collections::HashMap;
3
4use thiserror_no_std::Error;
5pub(super) use variable::{Scope, VarName, VarProp, Variable};
6
7use crate::parser::Val;
8
9#[derive(Error, Debug, PartialEq, Clone)]
10pub enum VariableError {
11 #[error("Variable \"{0}\" is not defined")]
12 NotDefined(String),
13 #[error("Cannot overwrite variable \"{0}\" because it is read-only or constant.")]
14 ReadOnly(String),
15}
16
17pub type VariableResult<T> = core::result::Result<T, VariableError>;
18
19#[derive(Default, Clone)]
20pub struct Variables {
21 map: HashMap<VarName, Variable>,
22 force_var_eval: bool,
23 }
29
30impl Variables {
31 fn const_variables() -> HashMap<VarName, Variable> {
32 HashMap::from([
33 (
34 VarName::new(Scope::Global, "true".to_ascii_lowercase()),
35 Variable::new(VarProp::ReadOnly, Val::Bool(true)),
36 ),
37 (
38 VarName::new(Scope::Global, "false".to_ascii_lowercase()),
39 Variable::new(VarProp::ReadOnly, Val::Bool(false)),
40 ),
41 (
42 VarName::new(Scope::Global, "null".to_ascii_lowercase()),
43 Variable::new(VarProp::ReadOnly, Val::Null),
44 ),
45 ])
46 }
47
48 pub(crate) fn set_ps_item(&mut self, ps_item: Val) {
49 let _ = self.set(
50 &VarName::new(Scope::Special, "$PSItem".into()),
51 ps_item.clone(),
52 );
53 let _ = self.set(&VarName::new(Scope::Special, "$_".into()), ps_item);
54 }
55
56 pub(crate) fn reset_ps_item(&mut self) {
57 let _ = self.set(&VarName::new(Scope::Special, "$PSItem".into()), Val::Null);
58 let _ = self.set(&VarName::new(Scope::Special, "$_".into()), Val::Null);
59 }
60
61 pub fn set_status(&mut self, b: bool) {
62 let _ = self.set(&VarName::new(Scope::Special, "$?".into()), Val::Bool(b));
63 }
64
65 pub fn status(&mut self) -> bool {
66 let Some(Val::Bool(b)) = self.get(&VarName::new(Scope::Special, "$?".into())) else {
67 return false;
68 };
69 b
70 }
71
72 pub fn load<R: std::io::Read>(&mut self, _reader: R) -> Result<(), Box<dyn std::error::Error>> {
73 let mut _config_parser = configparser::ini::Ini::new();
74 let conf: HashMap<String, HashMap<String, Option<String>>> = HashMap::new();
76
77 for (section_name, properties) in &conf {
78 for (key, value) in properties {
79 let Some(value) = value else {
80 continue;
81 };
82
83 let var_name = match section_name.as_str() {
84 "global" => VarName::new(Scope::Global, key.to_lowercase()),
85 "local" => VarName::new(Scope::Local, key.to_lowercase()),
86 _ => {
87 continue;
88 }
89 };
90
91 let parsed_value = if let Ok(bool_val) = value.parse::<bool>() {
93 Val::Bool(bool_val)
94 } else if let Ok(int_val) = value.parse::<i64>() {
95 Val::Int(int_val)
96 } else if let Ok(float_val) = value.parse::<f64>() {
97 Val::Float(float_val)
98 } else if value.is_empty() {
99 Val::Null
100 } else {
101 Val::String(value.clone().into())
102 };
103
104 if let Some(variable) = self.map.get(&var_name) {
106 if variable.prop == VarProp::ReadOnly {
107 log::warn!("Skipping read-only variable: {:?}", var_name);
108 continue;
109 }
110 }
111
112 self.map
113 .insert(var_name, Variable::new(VarProp::ReadWrite, parsed_value));
114 }
115 }
116 Ok(())
117 }
118
119 pub fn env() -> Self {
120 let mut map = Self::const_variables();
121
122 for (key, value) in std::env::vars() {
124 map.insert(
127 VarName::new(Scope::Env, key.to_lowercase()),
128 Variable::new(VarProp::ReadWrite, Val::String(value.into())),
129 );
130 }
131
132 Self {
133 map,
134 force_var_eval: true,
135 }
136 }
137
138 pub fn new() -> Self {
139 let map = Self::const_variables();
140
141 Self {
142 map,
143 force_var_eval: false,
144 }
145 }
146
147 pub fn force_eval() -> Self {
148 let map = Self::const_variables();
149
150 Self {
151 map,
152 force_var_eval: true,
153 }
154 }
155
156 pub fn from_ini_file(path: &std::path::Path) -> Result<Self, Box<dyn std::error::Error>> {
158 let mut variables = Self::new();
159 let mut file = std::fs::File::open(path)?;
160 variables.load(&mut file)?;
161 Ok(variables)
162 }
163
164 pub fn from_ini_string(ini_string: &str) -> Result<Self, Box<dyn std::error::Error>> {
166 let mut variables = Self::new();
167 let mut reader = std::io::Cursor::new(ini_string);
168 variables.load(&mut reader)?;
169 Ok(variables)
170 }
171
172 pub(crate) fn get(&self, var_name: &VarName) -> Option<Val> {
173 let mut var = self.map.get(var_name).map(|v| v.value.clone());
176 if self.force_var_eval && var.is_none() {
177 var = Some(Val::Null);
178 }
179
180 var
181 }
182
183 pub(crate) fn set(&mut self, var_name: &VarName, val: Val) -> VariableResult<()> {
184 if let Some(variable) = self.map.get_mut(var_name) {
185 if let VarProp::ReadOnly = variable.prop {
186 log::error!("You couldn't modify a read-only variable");
187 Err(VariableError::ReadOnly(var_name.name.to_string()))
188 } else {
189 variable.value = val;
190 Ok(())
191 }
192 } else {
193 self.map
194 .insert(var_name.clone(), Variable::new(VarProp::ReadWrite, val));
195 Ok(())
196 }
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::Variables;
203 use crate::{PowerShellSession, PsValue};
204
205 #[test]
206 fn test_builtin_variables() {
207 let mut p = PowerShellSession::new();
208 assert_eq!(p.safe_eval(r#" $true "#).unwrap().as_str(), "True");
209 assert_eq!(p.safe_eval(r#" $false "#).unwrap().as_str(), "False");
210 assert_eq!(p.safe_eval(r#" $null "#).unwrap().as_str(), "");
211 }
212
213 #[test]
214 fn test_env_variables() {
215 let v = Variables::env();
216 let mut p = PowerShellSession::new().with_variables(v);
217 assert_eq!(
218 p.safe_eval(r#" $env:path "#).unwrap().as_str(),
219 std::env::var("PATH").unwrap()
220 );
221 assert_eq!(
222 p.safe_eval(r#" $env:username "#).unwrap().as_str(),
223 std::env::var("USERNAME").unwrap()
224 );
225 assert_eq!(
226 p.safe_eval(r#" $env:tEMp "#).unwrap().as_str(),
227 std::env::var("TEMP").unwrap()
228 );
229 assert_eq!(
230 p.safe_eval(r#" $env:tMp "#).unwrap().as_str(),
231 std::env::var("TMP").unwrap()
232 );
233 assert_eq!(
234 p.safe_eval(r#" $env:cOmputername "#).unwrap().as_str(),
235 std::env::var("COMPUTERNAME").unwrap()
236 );
237 assert_eq!(
238 p.safe_eval(r#" $env:programfiles "#).unwrap().as_str(),
239 std::env::var("PROGRAMFILES").unwrap()
240 );
241 assert_eq!(
242 p.safe_eval(r#" $env:temp "#).unwrap().as_str(),
243 std::env::var("TEMP").unwrap()
244 );
245 assert_eq!(
246 p.safe_eval(r#" ${Env:ProgramFiles(x86)} "#)
247 .unwrap()
248 .as_str(),
249 std::env::var("ProgramFiles(x86)").unwrap()
250 );
251
252 p.safe_eval(r#" $global:program = $env:programfiles + "\program" "#)
253 .unwrap();
254 assert_eq!(
255 p.safe_eval(r#" $global:program "#).unwrap().as_str(),
256 format!("{}\\program", std::env::var("PROGRAMFILES").unwrap())
257 );
258 assert_eq!(
259 p.safe_eval(r#" $program "#).unwrap().as_str(),
260 format!("{}\\program", std::env::var("PROGRAMFILES").unwrap())
261 );
262
263 p.safe_eval(r#" ${Env:ProgramFiles(x86):adsf} = 5 "#)
264 .unwrap();
265 assert_eq!(
266 p.safe_eval(r#" ${Env:ProgramFiles(x86):adsf} "#)
267 .unwrap()
268 .as_str(),
269 5.to_string()
270 );
271 assert_eq!(
272 p.safe_eval(r#" ${Env:ProgramFiles(x86)} "#)
273 .unwrap()
274 .as_str(),
275 std::env::var("ProgramFiles(x86)").unwrap()
276 );
277 }
278
279 #[test]
280 fn special_last_error() {
281 let input = r#"3+"01234 ?";$a=5;$a;$?"#;
282
283 let mut p = PowerShellSession::new();
284 assert_eq!(p.safe_eval(input).unwrap().as_str(), "True");
285
286 let input = r#"3+"01234 ?";$?"#;
287 assert_eq!(p.safe_eval(input).unwrap().as_str(), "False");
288 }
289
290 fn test_from_ini() {
292 let input = r#"[global]
293name = radek
294age = 30
295is_admin = true
296height = 5.9
297empty_value =
298
299[local]
300local_var = "local_value"
301 "#;
302 let mut variables = Variables::new();
303 variables.load(input.as_bytes()).unwrap();
304 let mut p = PowerShellSession::new().with_variables(variables);
305
306 assert_eq!(
307 p.parse_input(r#" $global:name "#).unwrap().result(),
308 PsValue::String("radek".into())
309 );
310 assert_eq!(
311 p.parse_input(r#" $global:age "#).unwrap().result(),
312 PsValue::Int(30)
313 );
314 assert_eq!(p.safe_eval(r#" $false "#).unwrap().as_str(), "False");
315 assert_eq!(p.safe_eval(r#" $null "#).unwrap().as_str(), "");
316 assert_eq!(
317 p.safe_eval(r#" $local:local_var "#).unwrap().as_str(),
318 "\"local_value\""
319 );
320 }
321
322 fn test_from_ini_string() {
324 let input = r#"[global]
325name = radek
326age = 30
327is_admin = true
328height = 5.9
329empty_value =
330
331[local]
332local_var = "local_value"
333 "#;
334
335 let variables = Variables::from_ini_string(input).unwrap();
336 let mut p = PowerShellSession::new().with_variables(variables);
337
338 assert_eq!(
339 p.parse_input(r#" $global:name "#).unwrap().result(),
340 PsValue::String("radek".into())
341 );
342 assert_eq!(
343 p.parse_input(r#" $global:age "#).unwrap().result(),
344 PsValue::Int(30)
345 );
346 assert_eq!(p.safe_eval(r#" $false "#).unwrap().as_str(), "False");
347 assert_eq!(p.safe_eval(r#" $null "#).unwrap().as_str(), "");
348 assert_eq!(
349 p.safe_eval(r#" $local:local_var "#).unwrap().as_str(),
350 "\"local_value\""
351 );
352 }
353}