use crate::ported::lib::dict::REMOVE_THIS_KEY;
use serde_json::Value;
pub fn parse_value(s: &str) -> Value {
if s.is_empty() {
return REMOVE_THIS_KEY();
}
let first = s.chars().next().unwrap();
let starts_json = matches!(
first,
'"' | '{' | '[' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '-'
);
let is_special = s == "null" || s == "true" || s == "false";
if starts_json || is_special {
serde_json::from_str(s).unwrap_or_else(|_| Value::String(s.to_string()))
} else {
Value::String(s.to_string())
}
}
pub fn keyvaluesplit(s: &str) -> Result<(String, Value), String> {
if !s.contains('=') {
return Err("Option must look like option=json_value".to_string());
}
if s.starts_with('_') {
return Err("Option names must not start with `_'".to_string());
}
let idx = s.find('=').unwrap();
let o = s[..idx].to_string();
let val = parse_value(&s[idx + 1..]);
Ok((o, val))
}
pub fn parsedotval_str(s: &str) -> Result<(String, Value), String> {
let (o, val) = keyvaluesplit(s)?;
Ok(build_nested(&o, val))
}
pub fn parsedotval_tuple(o: &str, val: &str) -> (String, Value) {
let parsed_val = parse_value(val);
build_nested(o, parsed_val)
}
fn build_nested(o: &str, val: Value) -> (String, Value) {
let keys: Vec<&str> = o.split('.').collect();
if keys.len() > 1 {
let mut current = val;
for k in keys[1..].iter().rev() {
let mut m = serde_json::Map::new();
m.insert(k.to_string(), current);
current = Value::Object(m);
}
(keys[0].to_string(), current)
} else {
(o.to_string(), val)
}
}
pub enum ParseDotValInput<'a> {
Str(&'a str),
Tuple(&'a str, &'a str),
}
pub fn parsedotval(input: ParseDotValInput<'_>) -> Result<(String, Value), String> {
match input {
ParseDotValInput::Str(s) => parsedotval_str(s),
ParseDotValInput::Tuple(o, val) => Ok(parsedotval_tuple(o, val)),
}
}
pub fn parse_override_var(s: &str) -> Vec<(String, Value)> {
s.split(';')
.filter(|item| !item.is_empty())
.filter_map(|item| parsedotval_str(item).ok())
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn parse_value_empty_returns_remove_marker() {
let v = parse_value("");
assert_eq!(v, REMOVE_THIS_KEY());
}
#[test]
fn parse_value_number() {
assert_eq!(parse_value("42"), json!(42));
assert_eq!(parse_value("-3"), json!(-3));
assert_eq!(parse_value("2.5"), json!(2.5_f64));
}
#[test]
fn parse_value_string_in_quotes() {
assert_eq!(parse_value(r#""hello""#), json!("hello"));
}
#[test]
fn parse_value_specials() {
assert_eq!(parse_value("null"), json!(null));
assert_eq!(parse_value("true"), json!(true));
assert_eq!(parse_value("false"), json!(false));
}
#[test]
fn parse_value_array_and_object() {
assert_eq!(parse_value("[1,2,3]"), json!([1, 2, 3]));
assert_eq!(parse_value(r#"{"a":1}"#), json!({"a": 1}));
}
#[test]
fn parse_value_raw_string() {
assert_eq!(parse_value("hello"), json!("hello"));
assert_eq!(parse_value("TRUE"), json!("TRUE"));
}
#[test]
fn keyvaluesplit_basic() {
let (k, v) = keyvaluesplit("ext.tmux.theme=default").unwrap();
assert_eq!(k, "ext.tmux.theme");
assert_eq!(v, json!("default"));
}
#[test]
fn keyvaluesplit_no_equals_errors() {
assert!(keyvaluesplit("no-equals-here").is_err());
}
#[test]
fn keyvaluesplit_leading_underscore_errors() {
assert!(keyvaluesplit("_private=42").is_err());
}
#[test]
fn parsedotval_str_builds_nested() {
let (k, v) = parsedotval_str("ext.tmux.theme=default").unwrap();
assert_eq!(k, "ext");
assert_eq!(v, json!({"tmux": {"theme": "default"}}));
}
#[test]
fn parsedotval_str_no_dot_is_flat() {
let (k, v) = parsedotval_str("foo=42").unwrap();
assert_eq!(k, "foo");
assert_eq!(v, json!(42));
}
#[test]
fn parse_override_var_splits_on_semicolon() {
let items = parse_override_var("a=1;b=2;c.d=3");
assert_eq!(items.len(), 3);
assert_eq!(items[0], ("a".to_string(), json!(1)));
assert_eq!(items[1], ("b".to_string(), json!(2)));
assert_eq!(items[2], ("c".to_string(), json!({"d": 3})));
}
#[test]
fn parse_override_var_skips_empty() {
let items = parse_override_var("a=1;;b=2;");
assert_eq!(items.len(), 2);
}
#[test]
fn parsedotval_dispatches_str_to_keyvaluesplit() {
let (key, val) = parsedotval(ParseDotValInput::Str("foo.bar=42")).unwrap();
assert_eq!(key, "foo");
assert_eq!(val, json!({"bar": 42}));
}
#[test]
fn parsedotval_dispatches_tuple_to_parse_value() {
let (key, val) = parsedotval(ParseDotValInput::Tuple("foo.bar", "42")).unwrap();
assert_eq!(key, "foo");
assert_eq!(val, json!({"bar": 42}));
}
#[test]
fn parsedotval_str_no_dot_returns_flat_pair() {
let (key, val) = parsedotval(ParseDotValInput::Str("flag=true")).unwrap();
assert_eq!(key, "flag");
assert_eq!(val, json!(true));
}
}