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_from_file(
73 &mut self,
74 path: &std::path::Path,
75 ) -> Result<(), Box<dyn std::error::Error>> {
76 let mut config_parser = configparser::ini::Ini::new();
77 let map = config_parser.load(path)?;
78 self.load(map)
79 }
80
81 pub fn load_from_string(&mut self, ini_string: &str) -> Result<(), Box<dyn std::error::Error>> {
82 let mut config_parser = configparser::ini::Ini::new();
83 let map = config_parser.read(ini_string.into())?;
84 self.load(map)
85 }
86
87 fn load(
88 &mut self,
89 conf_map: HashMap<String, HashMap<String, Option<String>>>,
90 ) -> Result<(), Box<dyn std::error::Error>> {
91 for (section_name, properties) in conf_map {
92 for (key, value) in properties {
93 let Some(value) = value else {
94 continue;
95 };
96
97 let var_name = match section_name.as_str() {
98 "global" => VarName::new(Scope::Global, key.to_lowercase()),
99 "local" => VarName::new(Scope::Local, key.to_lowercase()),
100 _ => {
101 continue;
102 }
103 };
104
105 let parsed_value = if let Ok(bool_val) = value.parse::<bool>() {
107 Val::Bool(bool_val)
108 } else if let Ok(int_val) = value.parse::<i64>() {
109 Val::Int(int_val)
110 } else if let Ok(float_val) = value.parse::<f64>() {
111 Val::Float(float_val)
112 } else if value.is_empty() {
113 Val::Null
114 } else {
115 Val::String(value.clone().into())
116 };
117
118 if let Some(variable) = self.map.get(&var_name) {
120 if variable.prop == VarProp::ReadOnly {
121 log::warn!("Skipping read-only variable: {:?}", var_name);
122 continue;
123 }
124 }
125
126 self.map
127 .insert(var_name, Variable::new(VarProp::ReadWrite, parsed_value));
128 }
129 }
130 Ok(())
131 }
132
133 pub fn new() -> Variables {
156 let map = Self::const_variables();
157
158 Self {
159 map,
160 force_var_eval: false,
161 }
162 }
163
164 pub fn force_eval() -> Self {
199 let map = Self::const_variables();
200
201 Self {
202 map,
203 force_var_eval: true,
204 }
205 }
206
207 pub fn env() -> Variables {
230 let mut map = Self::const_variables();
231
232 for (key, value) in std::env::vars() {
234 map.insert(
237 VarName::new(Scope::Env, key.to_lowercase()),
238 Variable::new(VarProp::ReadWrite, Val::String(value.into())),
239 );
240 }
241
242 Self {
243 map,
244 force_var_eval: true,
245 }
246 }
247
248 pub fn from_ini_string(ini_string: &str) -> Result<Self, Box<dyn std::error::Error>> {
293 let mut variables = Self::new();
294 variables.load_from_string(ini_string)?;
295 Ok(variables)
296 }
297
298 pub fn from_ini_file(path: &std::path::Path) -> Result<Self, Box<dyn std::error::Error>> {
300 let mut variables = Self::new();
301 variables.load_from_file(path)?;
302 Ok(variables)
303 }
304
305 pub(crate) fn set(&mut self, var_name: &VarName, val: Val) -> VariableResult<()> {
317 if let Some(variable) = self.map.get_mut(var_name) {
318 if let VarProp::ReadOnly = variable.prop {
319 log::error!("You couldn't modify a read-only variable");
320 Err(VariableError::ReadOnly(var_name.name.to_string()))
321 } else {
322 variable.value = val;
323 Ok(())
324 }
325 } else {
326 self.map
327 .insert(var_name.clone(), Variable::new(VarProp::ReadWrite, val));
328 Ok(())
329 }
330 }
331
332 pub(crate) fn get(&self, var_name: &VarName) -> Option<Val> {
343 let mut var = self.map.get(var_name).map(|v| v.value.clone());
346 if self.force_var_eval && var.is_none() {
347 var = Some(Val::Null);
348 }
349
350 var
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 use super::Variables;
357 use crate::{PowerShellSession, PsValue};
358
359 #[test]
360 fn test_builtin_variables() {
361 let mut p = PowerShellSession::new();
362 assert_eq!(p.safe_eval(r#" $true "#).unwrap().as_str(), "True");
363 assert_eq!(p.safe_eval(r#" $false "#).unwrap().as_str(), "False");
364 assert_eq!(p.safe_eval(r#" $null "#).unwrap().as_str(), "");
365 }
366
367 #[test]
368 fn test_env_variables() {
369 let v = Variables::env();
370 let mut p = PowerShellSession::new().with_variables(v);
371 assert_eq!(
372 p.safe_eval(r#" $env:path "#).unwrap().as_str(),
373 std::env::var("PATH").unwrap()
374 );
375 assert_eq!(
376 p.safe_eval(r#" $env:username "#).unwrap().as_str(),
377 std::env::var("USERNAME").unwrap()
378 );
379 assert_eq!(
380 p.safe_eval(r#" $env:tEMp "#).unwrap().as_str(),
381 std::env::var("TEMP").unwrap()
382 );
383 assert_eq!(
384 p.safe_eval(r#" $env:tMp "#).unwrap().as_str(),
385 std::env::var("TMP").unwrap()
386 );
387 assert_eq!(
388 p.safe_eval(r#" $env:cOmputername "#).unwrap().as_str(),
389 std::env::var("COMPUTERNAME").unwrap()
390 );
391 assert_eq!(
392 p.safe_eval(r#" $env:programfiles "#).unwrap().as_str(),
393 std::env::var("PROGRAMFILES").unwrap()
394 );
395 assert_eq!(
396 p.safe_eval(r#" $env:temp "#).unwrap().as_str(),
397 std::env::var("TEMP").unwrap()
398 );
399 assert_eq!(
400 p.safe_eval(r#" ${Env:ProgramFiles(x86)} "#)
401 .unwrap()
402 .as_str(),
403 std::env::var("ProgramFiles(x86)").unwrap()
404 );
405
406 p.safe_eval(r#" $global:program = $env:programfiles + "\program" "#)
407 .unwrap();
408 assert_eq!(
409 p.safe_eval(r#" $global:program "#).unwrap().as_str(),
410 format!("{}\\program", std::env::var("PROGRAMFILES").unwrap())
411 );
412 assert_eq!(
413 p.safe_eval(r#" $program "#).unwrap().as_str(),
414 format!("{}\\program", std::env::var("PROGRAMFILES").unwrap())
415 );
416
417 p.safe_eval(r#" ${Env:ProgramFiles(x86):adsf} = 5 "#)
418 .unwrap();
419 assert_eq!(
420 p.safe_eval(r#" ${Env:ProgramFiles(x86):adsf} "#)
421 .unwrap()
422 .as_str(),
423 5.to_string()
424 );
425 assert_eq!(
426 p.safe_eval(r#" ${Env:ProgramFiles(x86)} "#)
427 .unwrap()
428 .as_str(),
429 std::env::var("ProgramFiles(x86)").unwrap()
430 );
431 }
432
433 #[test]
434 fn special_last_error() {
435 let input = r#"3+"01234 ?";$a=5;$a;$?"#;
436
437 let mut p = PowerShellSession::new();
438 assert_eq!(p.safe_eval(input).unwrap().as_str(), "True");
439
440 let input = r#"3+"01234 ?";$?"#;
441 assert_eq!(p.safe_eval(input).unwrap().as_str(), "False");
442 }
443
444 #[test]
445 fn test_from_ini() {
446 let input = r#"[global]
447name = radek
448age = 30
449is_admin = true
450height = 5.9
451empty_value =
452
453[local]
454local_var = "local_value"
455 "#;
456 let mut variables = Variables::new();
457 variables.load_from_string(input).unwrap();
458 let mut p = PowerShellSession::new().with_variables(variables);
459
460 assert_eq!(
461 p.parse_input(r#" $global:name "#).unwrap().result(),
462 PsValue::String("radek".into())
463 );
464 assert_eq!(
465 p.parse_input(r#" $global:age "#).unwrap().result(),
466 PsValue::Int(30)
467 );
468 assert_eq!(p.safe_eval(r#" $false "#).unwrap().as_str(), "False");
469 assert_eq!(p.safe_eval(r#" $null "#).unwrap().as_str(), "");
470 assert_eq!(
471 p.safe_eval(r#" $local:local_var "#).unwrap().as_str(),
472 "\"local_value\""
473 );
474 }
475
476 #[test]
477 fn test_from_ini_string() {
478 let input = r#"[global]
479name = radek
480age = 30
481is_admin = true
482height = 5.9
483empty_value =
484
485[local]
486local_var = "local_value"
487 "#;
488
489 let variables = Variables::from_ini_string(input).unwrap();
490 let mut p = PowerShellSession::new().with_variables(variables);
491
492 assert_eq!(
493 p.parse_input(r#" $global:name "#).unwrap().result(),
494 PsValue::String("radek".into())
495 );
496 assert_eq!(
497 p.parse_input(r#" $global:age "#).unwrap().result(),
498 PsValue::Int(30)
499 );
500 assert_eq!(p.safe_eval(r#" $false "#).unwrap().as_str(), "False");
501 assert_eq!(p.safe_eval(r#" $null "#).unwrap().as_str(), "");
502 assert_eq!(
503 p.safe_eval(r#" $local:local_var "#).unwrap().as_str(),
504 "\"local_value\""
505 );
506 }
507}