use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct PrivateParam {
pub name: String,
pub value: ParamValue,
pub level: usize,
pub readonly: bool,
}
#[derive(Debug, Clone)]
pub enum ParamValue {
Scalar(String),
Integer(i64),
Float(f64),
Array(Vec<String>),
Hash(HashMap<String, String>),
}
#[derive(Debug, Default)]
pub struct PrivateScope {
params: HashMap<String, PrivateParam>,
level: usize,
}
impl PrivateScope {
pub fn new() -> Self {
Self::default()
}
pub fn enter(&mut self) {
self.level += 1;
}
pub fn exit(&mut self) {
let level = self.level;
self.params.retain(|_, p| p.level < level);
self.level = self.level.saturating_sub(1);
}
pub fn level(&self) -> usize {
self.level
}
pub fn add(&mut self, name: &str, value: ParamValue, readonly: bool) -> bool {
if let Some(existing) = self.params.get(name) {
if existing.readonly {
return false;
}
}
self.params.insert(
name.to_string(),
PrivateParam {
name: name.to_string(),
value,
level: self.level,
readonly,
},
);
true
}
pub fn get(&self, name: &str) -> Option<&PrivateParam> {
self.params.get(name)
}
pub fn get_mut(&mut self, name: &str) -> Option<&mut PrivateParam> {
let param = self.params.get_mut(name)?;
if param.readonly {
return None;
}
Some(param)
}
pub fn is_private(&self, name: &str) -> bool {
self.params
.get(name)
.map(|p| p.level == self.level)
.unwrap_or(false)
}
pub fn set(&mut self, name: &str, value: ParamValue) -> bool {
if let Some(param) = self.params.get_mut(name) {
if param.readonly {
return false;
}
param.value = value;
return true;
}
false
}
pub fn remove(&mut self, name: &str) -> bool {
if let Some(param) = self.params.get(name) {
if param.readonly {
return false;
}
}
self.params.remove(name).is_some()
}
pub fn list_current(&self) -> Vec<&PrivateParam> {
self.params
.values()
.filter(|p| p.level == self.level)
.collect()
}
pub fn list_all(&self) -> Vec<&PrivateParam> {
self.params.values().collect()
}
}
pub fn builtin_private(args: &[&str], scope: &mut PrivateScope) -> (i32, String) {
if args.is_empty() {
let params = scope.list_current();
if params.is_empty() {
return (0, String::new());
}
let mut output = String::new();
for p in params {
let type_str = match &p.value {
ParamValue::Scalar(_) => "",
ParamValue::Integer(_) => "-i ",
ParamValue::Float(_) => "-F ",
ParamValue::Array(_) => "-a ",
ParamValue::Hash(_) => "-A ",
};
let readonly = if p.readonly { "-r " } else { "" };
output.push_str(&format!("private {}{}{}\n", type_str, readonly, p.name));
}
return (0, output);
}
let mut i = 0;
let mut param_type = ParamValue::Scalar(String::new());
let mut readonly = false;
while i < args.len() && args[i].starts_with('-') {
match args[i] {
"-i" => param_type = ParamValue::Integer(0),
"-F" => param_type = ParamValue::Float(0.0),
"-a" => param_type = ParamValue::Array(Vec::new()),
"-A" => param_type = ParamValue::Hash(HashMap::new()),
"-r" => readonly = true,
_ => {}
}
i += 1;
}
if i >= args.len() {
return (1, "private: parameter name required\n".to_string());
}
for arg in &args[i..] {
if let Some((name, value)) = arg.split_once('=') {
let val = match ¶m_type {
ParamValue::Scalar(_) => ParamValue::Scalar(value.to_string()),
ParamValue::Integer(_) => ParamValue::Integer(value.parse().unwrap_or(0)),
ParamValue::Float(_) => ParamValue::Float(value.parse().unwrap_or(0.0)),
ParamValue::Array(_) => {
ParamValue::Array(value.split_whitespace().map(|s| s.to_string()).collect())
}
ParamValue::Hash(_) => {
let mut map = HashMap::new();
for pair in value.split_whitespace() {
if let Some((k, v)) = pair.split_once('=') {
map.insert(k.to_string(), v.to_string());
}
}
ParamValue::Hash(map)
}
};
if !scope.add(name, val, readonly) {
return (1, format!("private: read-only variable: {}\n", name));
}
} else {
let val = match ¶m_type {
ParamValue::Scalar(_) => ParamValue::Scalar(String::new()),
ParamValue::Integer(_) => ParamValue::Integer(0),
ParamValue::Float(_) => ParamValue::Float(0.0),
ParamValue::Array(_) => ParamValue::Array(Vec::new()),
ParamValue::Hash(_) => ParamValue::Hash(HashMap::new()),
};
if !scope.add(arg, val, readonly) {
return (1, format!("private: read-only variable: {}\n", arg));
}
}
}
(0, String::new())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_private_scope_new() {
let scope = PrivateScope::new();
assert_eq!(scope.level(), 0);
}
#[test]
fn test_private_scope_enter_exit() {
let mut scope = PrivateScope::new();
scope.enter();
assert_eq!(scope.level(), 1);
scope.exit();
assert_eq!(scope.level(), 0);
}
#[test]
fn test_private_scope_add_get() {
let mut scope = PrivateScope::new();
scope.enter();
scope.add("foo", ParamValue::Scalar("bar".to_string()), false);
assert!(scope.get("foo").is_some());
}
#[test]
fn test_private_scope_readonly() {
let mut scope = PrivateScope::new();
scope.enter();
scope.add("foo", ParamValue::Scalar("bar".to_string()), true);
assert!(scope.get("foo").is_some());
assert!(scope.get_mut("foo").is_none());
}
#[test]
fn test_private_scope_exit_removes() {
let mut scope = PrivateScope::new();
scope.enter();
scope.add("foo", ParamValue::Scalar("bar".to_string()), false);
scope.exit();
assert!(scope.get("foo").is_none());
}
#[test]
fn test_builtin_private_no_args() {
let mut scope = PrivateScope::new();
scope.enter();
let (status, _) = builtin_private(&[], &mut scope);
assert_eq!(status, 0);
}
#[test]
fn test_builtin_private_scalar() {
let mut scope = PrivateScope::new();
scope.enter();
let (status, _) = builtin_private(&["foo=bar"], &mut scope);
assert_eq!(status, 0);
assert!(scope.get("foo").is_some());
}
#[test]
fn test_builtin_private_integer() {
let mut scope = PrivateScope::new();
scope.enter();
let (status, _) = builtin_private(&["-i", "foo=42"], &mut scope);
assert_eq!(status, 0);
if let Some(p) = scope.get("foo") {
assert!(matches!(p.value, ParamValue::Integer(42)));
}
}
}