use a3s_box_core::error::{BoxError, Result};
pub(super) fn parse_json_array(s: &str, line_num: usize) -> Result<Vec<String>> {
let parsed: Vec<String> = serde_json::from_str(s).map_err(|e| {
BoxError::BuildError(format!(
"Line {}: Invalid JSON array '{}': {}",
line_num, s, e
))
})?;
Ok(parsed)
}
pub(super) fn unquote(s: &str) -> String {
let s = s.trim();
if (s.starts_with('"') && s.ends_with('"')) || (s.starts_with('\'') && s.ends_with('\'')) {
s[1..s.len() - 1].to_string()
} else {
s.to_string()
}
}
pub(super) fn shell_split(s: &str) -> Vec<&str> {
s.split_whitespace().collect()
}
pub(super) fn parse_duration_secs(s: &str, line_num: usize) -> Result<u64> {
let s = s.trim();
if s.is_empty() {
return Ok(0);
}
if let Ok(n) = s.parse::<u64>() {
return Ok(n);
}
let invalid = || BoxError::BuildError(format!("Line {}: Invalid duration '{}'", line_num, s));
let mut total_secs = 0f64;
let mut num = String::new();
let mut saw_unit = false;
let mut chars = s.chars().peekable();
while let Some(&c) = chars.peek() {
if c.is_ascii_digit() || c == '.' {
num.push(c);
chars.next();
continue;
}
let mut unit = String::new();
while let Some(&u) = chars.peek() {
if u.is_ascii_alphabetic() || u == 'µ' {
unit.push(u);
chars.next();
} else {
break;
}
}
if num.is_empty() || unit.is_empty() {
return Err(invalid());
}
let value: f64 = num.parse().map_err(|_| invalid())?;
let unit_secs = match unit.as_str() {
"ns" => 1e-9,
"us" | "µs" => 1e-6,
"ms" => 1e-3,
"s" => 1.0,
"m" => 60.0,
"h" => 3600.0,
_ => return Err(invalid()),
};
total_secs += value * unit_secs;
num.clear();
saw_unit = true;
}
if !num.is_empty() || !saw_unit {
return Err(invalid());
}
Ok(total_secs.round() as u64)
}