Skip to main content

zsh/
param_private.rs

1//! Private parameters module - port of Modules/param_private.c
2//!
3//! Provides private parameter scoping for shell functions.
4
5use std::collections::HashMap;
6
7/// Private parameter state
8#[derive(Debug, Clone)]
9pub struct PrivateParam {
10    pub name: String,
11    pub value: ParamValue,
12    pub level: usize,
13    pub readonly: bool,
14}
15
16/// Parameter value types
17#[derive(Debug, Clone)]
18pub enum ParamValue {
19    Scalar(String),
20    Integer(i64),
21    Float(f64),
22    Array(Vec<String>),
23    Hash(HashMap<String, String>),
24}
25
26/// Private scope manager
27#[derive(Debug, Default)]
28pub struct PrivateScope {
29    params: HashMap<String, PrivateParam>,
30    level: usize,
31}
32
33impl PrivateScope {
34    pub fn new() -> Self {
35        Self::default()
36    }
37
38    /// Enter a new scope level
39    pub fn enter(&mut self) {
40        self.level += 1;
41    }
42
43    /// Exit current scope level
44    pub fn exit(&mut self) {
45        let level = self.level;
46        self.params.retain(|_, p| p.level < level);
47        self.level = self.level.saturating_sub(1);
48    }
49
50    /// Current scope level
51    pub fn level(&self) -> usize {
52        self.level
53    }
54
55    /// Add a private parameter
56    pub fn add(&mut self, name: &str, value: ParamValue, readonly: bool) -> bool {
57        if let Some(existing) = self.params.get(name) {
58            if existing.readonly {
59                return false;
60            }
61        }
62
63        self.params.insert(
64            name.to_string(),
65            PrivateParam {
66                name: name.to_string(),
67                value,
68                level: self.level,
69                readonly,
70            },
71        );
72
73        true
74    }
75
76    /// Get a private parameter
77    pub fn get(&self, name: &str) -> Option<&PrivateParam> {
78        self.params.get(name)
79    }
80
81    /// Get a private parameter mutably
82    pub fn get_mut(&mut self, name: &str) -> Option<&mut PrivateParam> {
83        let param = self.params.get_mut(name)?;
84        if param.readonly {
85            return None;
86        }
87        Some(param)
88    }
89
90    /// Check if a parameter is private at current scope
91    pub fn is_private(&self, name: &str) -> bool {
92        self.params
93            .get(name)
94            .map(|p| p.level == self.level)
95            .unwrap_or(false)
96    }
97
98    /// Set parameter value if not readonly
99    pub fn set(&mut self, name: &str, value: ParamValue) -> bool {
100        if let Some(param) = self.params.get_mut(name) {
101            if param.readonly {
102                return false;
103            }
104            param.value = value;
105            return true;
106        }
107        false
108    }
109
110    /// Remove a private parameter
111    pub fn remove(&mut self, name: &str) -> bool {
112        if let Some(param) = self.params.get(name) {
113            if param.readonly {
114                return false;
115            }
116        }
117        self.params.remove(name).is_some()
118    }
119
120    /// List all private parameters at current level
121    pub fn list_current(&self) -> Vec<&PrivateParam> {
122        self.params
123            .values()
124            .filter(|p| p.level == self.level)
125            .collect()
126    }
127
128    /// List all private parameters
129    pub fn list_all(&self) -> Vec<&PrivateParam> {
130        self.params.values().collect()
131    }
132}
133
134/// Execute private builtin
135pub fn builtin_private(args: &[&str], scope: &mut PrivateScope) -> (i32, String) {
136    if args.is_empty() {
137        let params = scope.list_current();
138        if params.is_empty() {
139            return (0, String::new());
140        }
141
142        let mut output = String::new();
143        for p in params {
144            let type_str = match &p.value {
145                ParamValue::Scalar(_) => "",
146                ParamValue::Integer(_) => "-i ",
147                ParamValue::Float(_) => "-F ",
148                ParamValue::Array(_) => "-a ",
149                ParamValue::Hash(_) => "-A ",
150            };
151            let readonly = if p.readonly { "-r " } else { "" };
152            output.push_str(&format!("private {}{}{}\n", type_str, readonly, p.name));
153        }
154
155        return (0, output);
156    }
157
158    let mut i = 0;
159    let mut param_type = ParamValue::Scalar(String::new());
160    let mut readonly = false;
161
162    while i < args.len() && args[i].starts_with('-') {
163        match args[i] {
164            "-i" => param_type = ParamValue::Integer(0),
165            "-F" => param_type = ParamValue::Float(0.0),
166            "-a" => param_type = ParamValue::Array(Vec::new()),
167            "-A" => param_type = ParamValue::Hash(HashMap::new()),
168            "-r" => readonly = true,
169            _ => {}
170        }
171        i += 1;
172    }
173
174    if i >= args.len() {
175        return (1, "private: parameter name required\n".to_string());
176    }
177
178    for arg in &args[i..] {
179        if let Some((name, value)) = arg.split_once('=') {
180            let val = match &param_type {
181                ParamValue::Scalar(_) => ParamValue::Scalar(value.to_string()),
182                ParamValue::Integer(_) => ParamValue::Integer(value.parse().unwrap_or(0)),
183                ParamValue::Float(_) => ParamValue::Float(value.parse().unwrap_or(0.0)),
184                ParamValue::Array(_) => {
185                    ParamValue::Array(value.split_whitespace().map(|s| s.to_string()).collect())
186                }
187                ParamValue::Hash(_) => {
188                    let mut map = HashMap::new();
189                    for pair in value.split_whitespace() {
190                        if let Some((k, v)) = pair.split_once('=') {
191                            map.insert(k.to_string(), v.to_string());
192                        }
193                    }
194                    ParamValue::Hash(map)
195                }
196            };
197
198            if !scope.add(name, val, readonly) {
199                return (1, format!("private: read-only variable: {}\n", name));
200            }
201        } else {
202            let val = match &param_type {
203                ParamValue::Scalar(_) => ParamValue::Scalar(String::new()),
204                ParamValue::Integer(_) => ParamValue::Integer(0),
205                ParamValue::Float(_) => ParamValue::Float(0.0),
206                ParamValue::Array(_) => ParamValue::Array(Vec::new()),
207                ParamValue::Hash(_) => ParamValue::Hash(HashMap::new()),
208            };
209
210            if !scope.add(arg, val, readonly) {
211                return (1, format!("private: read-only variable: {}\n", arg));
212            }
213        }
214    }
215
216    (0, String::new())
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222
223    #[test]
224    fn test_private_scope_new() {
225        let scope = PrivateScope::new();
226        assert_eq!(scope.level(), 0);
227    }
228
229    #[test]
230    fn test_private_scope_enter_exit() {
231        let mut scope = PrivateScope::new();
232        scope.enter();
233        assert_eq!(scope.level(), 1);
234        scope.exit();
235        assert_eq!(scope.level(), 0);
236    }
237
238    #[test]
239    fn test_private_scope_add_get() {
240        let mut scope = PrivateScope::new();
241        scope.enter();
242        scope.add("foo", ParamValue::Scalar("bar".to_string()), false);
243        assert!(scope.get("foo").is_some());
244    }
245
246    #[test]
247    fn test_private_scope_readonly() {
248        let mut scope = PrivateScope::new();
249        scope.enter();
250        scope.add("foo", ParamValue::Scalar("bar".to_string()), true);
251        assert!(scope.get("foo").is_some());
252        assert!(scope.get_mut("foo").is_none());
253    }
254
255    #[test]
256    fn test_private_scope_exit_removes() {
257        let mut scope = PrivateScope::new();
258        scope.enter();
259        scope.add("foo", ParamValue::Scalar("bar".to_string()), false);
260        scope.exit();
261        assert!(scope.get("foo").is_none());
262    }
263
264    #[test]
265    fn test_builtin_private_no_args() {
266        let mut scope = PrivateScope::new();
267        scope.enter();
268        let (status, _) = builtin_private(&[], &mut scope);
269        assert_eq!(status, 0);
270    }
271
272    #[test]
273    fn test_builtin_private_scalar() {
274        let mut scope = PrivateScope::new();
275        scope.enter();
276        let (status, _) = builtin_private(&["foo=bar"], &mut scope);
277        assert_eq!(status, 0);
278        assert!(scope.get("foo").is_some());
279    }
280
281    #[test]
282    fn test_builtin_private_integer() {
283        let mut scope = PrivateScope::new();
284        scope.enter();
285        let (status, _) = builtin_private(&["-i", "foo=42"], &mut scope);
286        assert_eq!(status, 0);
287        if let Some(p) = scope.get("foo") {
288            assert!(matches!(p.value, ParamValue::Integer(42)));
289        }
290    }
291}