1use toml::Value;
2
3pub type ExtractResult<T> = Result<T, String>;
4
5pub fn extract(pattern: &str, value: &Value) -> ExtractResult<String> {
6 let parts: Vec<&str> = pattern.split('.').collect();
7 handle(pattern, &parts, value)
8}
9
10fn handle(pattern: &str, parts: &[&str], value: &Value) -> ExtractResult<String> {
11 match value {
12 Value::String(value) => check_primitive(pattern, parts, value.to_string()),
15 value @ (Value::Integer(_) | Value::Float(_) | Value::Boolean(_) | Value::Datetime(_)) => {
16 check_primitive(pattern, parts, value.to_string())
17 }
18 Value::Array(value) => handle_array(pattern, parts, value),
19 Value::Table(value) => handle_table(pattern, parts, value),
20 }
21}
22
23fn handle_array(pattern: &str, parts: &[&str], value: &[Value]) -> ExtractResult<String> {
24 match parts.split_first() {
25 Some((first, rest)) if first.parse::<usize>().is_ok() => value
26 .get(first.parse::<usize>().unwrap())
27 .map(|v| handle(pattern, rest, v))
28 .unwrap_or_else(|| {
29 Err(construct_error(
30 pattern,
31 first,
32 &format!("Array index out of bounds [{first}]"),
33 ))
34 }),
35 Some((first, _)) => Err(construct_error(
36 pattern,
37 first,
38 &format!("Not an array index [{first}]"),
39 )),
40 None => value
41 .iter()
42 .map(|v| handle(pattern, &[], v))
43 .collect::<ExtractResult<Vec<_>>>()
44 .map(|v| v.join("\n")),
45 }
46}
47
48fn handle_table(
49 pattern: &str,
50 parts: &[&str],
51 value: &toml::map::Map<String, Value>,
52) -> ExtractResult<String> {
53 match parts.split_first() {
54 Some((first, rest)) => value
55 .get(*first)
56 .map(|v| handle(pattern, rest, v))
57 .unwrap_or_else(|| {
58 Err(construct_error(
59 pattern,
60 first,
61 &format!("No such property [{first}]"),
62 ))
63 }),
64 None => value
65 .iter()
66 .map(|(k, v)| handle(pattern, &[], v).map(|val| format!("{k} = {val}")))
67 .collect::<ExtractResult<Vec<_>>>()
68 .map(|v| v.join("\n")),
69 }
70}
71
72fn construct_error(pattern: &str, part: &str, msg: &str) -> String {
73 let index = pattern.find(part).unwrap_or(0);
74 let offset = " ".repeat(index);
75 format!("{pattern}\n{offset}^ {msg}")
76}
77
78fn check_primitive(pattern: &str, parts: &[&str], value: String) -> ExtractResult<String> {
79 if parts.is_empty() {
80 Ok(value)
81 } else {
82 Err(construct_error(
83 pattern,
84 parts[0],
85 &format!("No such property [{}]", parts[0]),
86 ))
87 }
88}