ps_parser/parser/
variables.rs

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    //special variables
24    // status: bool, // $?
25    // first_token: Option<String>,
26    // last_token: Option<String>,
27    // current_pipeline: Option<String>,
28}
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 = config_parser.load_from_stream(reader)?;
75        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                // Try to parse the value as different types
92                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                // Insert the variable (overwrite if it exists and is not read-only)
105                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        // Load all environment variables
123        for (key, value) in std::env::vars() {
124            // Store environment variables with Env scope so they can be accessed via
125            // $env:variable_name
126            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    /// Create a new Variables instance with variables loaded from an INI file
157    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    /// Create a new Variables instance with variables loaded from an INI file
165    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        //todo: handle special variables and scopes
174
175        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    //#[test]
291    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    //#[test]
323    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}