cargo_extract/
lib.rs

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        // If included in the below "v @ "-binding pattern
13        // it produces strings with extra quotes
14        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}