1use std::collections::HashMap;
6
7#[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#[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#[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 pub fn enter(&mut self) {
40 self.level += 1;
41 }
42
43 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 pub fn level(&self) -> usize {
52 self.level
53 }
54
55 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 pub fn get(&self, name: &str) -> Option<&PrivateParam> {
78 self.params.get(name)
79 }
80
81 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 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 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 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 pub fn list_current(&self) -> Vec<&PrivateParam> {
122 self.params
123 .values()
124 .filter(|p| p.level == self.level)
125 .collect()
126 }
127
128 pub fn list_all(&self) -> Vec<&PrivateParam> {
130 self.params.values().collect()
131 }
132}
133
134pub 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 ¶m_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 ¶m_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}