yaml_subset/
path.rs

1use pest_consume::{match_nodes, Error, Parser};
2type PathResult<T> = std::result::Result<T, Error<Rule>>;
3type Node<'i> = pest_consume::Node<'i, Rule, ()>;
4
5#[derive(Parser)]
6#[grammar = "path.pest"]
7struct PathParser;
8
9#[pest_consume::parser]
10impl PathParser {
11    fn EOI(_input: Node) -> PathResult<()> {
12        Ok(())
13    }
14    fn pure_key(input: Node) -> PathResult<String> {
15        Ok(input.as_str().to_string())
16    }
17    fn field(input: Node) -> PathResult<String> {
18        Ok(input.as_str().to_string())
19    }
20    fn value(input: Node) -> PathResult<String> {
21        Ok(input.as_str().to_string())
22    }
23    fn condition(input: Node) -> PathResult<Condition> {
24        Ok(match_nodes!(input.into_children();
25            [field(f), value(v)] => Condition { field: f, value: v }
26        ))
27    }
28    fn key(input: Node) -> PathResult<YamlPath> {
29        Ok(match_nodes!(input.into_children();
30            [pure_key(key), condition(cs)..] => YamlPath::Key(key, cs.collect(), None)
31        ))
32    }
33
34    fn index_number(input: Node) -> PathResult<Vec<usize>> {
35        Ok(vec![input.as_str().parse().unwrap()]) // TODO: remove unwrap
36    }
37    fn index_all(input: Node) -> PathResult<()> {
38        Ok(())
39    }
40    fn part(input: Node) -> PathResult<YamlPath> {
41        Ok(match_nodes!(input.into_children();
42            [index_all(_), condition(cs)..] => YamlPath::AllIndexes(cs.collect(), None),
43            [index_number(indexes), condition(cs)..] => YamlPath::Indexes(indexes, cs.collect(), None),
44            [key(r)] => r,
45
46        ))
47    }
48    fn path(input: Node) -> PathResult<YamlPath> {
49        Ok(match_nodes!(input.into_children();
50            [key(s), part(vals)..] => {
51                let mut path = s;
52                for val in vals.into_iter() {
53                    path.insert(val);
54                }
55                path
56            }
57        ))
58    }
59    fn root_key(_input: Node) -> PathResult<()> {
60        Ok(())
61    }
62    fn root(input: Node) -> PathResult<YamlPath> {
63        Ok(match_nodes!(input.into_children();
64            [root_key(_s), condition(cs)..] => {
65            YamlPath::Root(cs.collect())
66            }
67        ))
68    }
69    fn final_path(input: Node) -> PathResult<YamlPath> {
70        Ok(match_nodes!(input.into_children();
71            [path(p), EOI(_)] => p,
72            [root(p), EOI(_)] => p
73        ))
74    }
75}
76
77fn parse(input_str: &str) -> PathResult<YamlPath> {
78    // Parse the input into `Nodes`
79    let inputs = PathParser::parse(Rule::final_path, input_str)?;
80    // There should be a single root node in the parsed tree
81    let input = inputs.single()?;
82    // Consume the `Node` recursively into the final value
83    PathParser::final_path(input)
84}
85
86#[derive(Debug, Clone, PartialEq)]
87pub struct Condition {
88    pub field: String,
89    pub value: String,
90}
91
92#[derive(Debug, Clone, PartialEq)]
93pub enum YamlPath {
94    Root(Vec<Condition>),
95    Key(String, Vec<Condition>, Option<Box<YamlPath>>),
96    AllIndexes(Vec<Condition>, Option<Box<YamlPath>>),
97    Indexes(Vec<usize>, Vec<Condition>, Option<Box<YamlPath>>),
98}
99
100impl std::str::FromStr for YamlPath {
101    type Err = pest_consume::Error<Rule>;
102
103    fn from_str(path_str: &str) -> PathResult<Self> {
104        parse(path_str)
105    }
106}
107
108impl YamlPath {
109    pub fn insert(&mut self, p: YamlPath) {
110        match self {
111            YamlPath::Root(_) => *self = p,
112            YamlPath::Key(_, _, Some(ref mut o)) => o.insert(p),
113            YamlPath::AllIndexes(_, Some(ref mut o)) => o.insert(p),
114            YamlPath::Indexes(_, _, Some(ref mut o)) => o.insert(p),
115            YamlPath::Key(_, _, ref mut o) => *o = Some(Box::new(p)),
116            YamlPath::AllIndexes(_, ref mut o) => *o = Some(Box::new(p)),
117            YamlPath::Indexes(_, _, ref mut o) => *o = Some(Box::new(p)),
118        }
119    }
120}
121#[cfg(test)]
122mod test {
123    use super::*;
124    #[test]
125    fn parse() {
126        assert_eq!("".parse(), Ok(YamlPath::Root(Vec::new())));
127        assert_eq!(
128            "item".parse(),
129            Ok(YamlPath::Key("item".to_string(), Vec::new(), None))
130        );
131        assert_eq!(
132            "item.item2".parse(),
133            Ok(YamlPath::Key(
134                "item".to_string(),
135                Vec::new(),
136                Some(Box::new(YamlPath::Key(
137                    "item2".to_string(),
138                    Vec::new(),
139                    None
140                )))
141            ))
142        );
143        assert_eq!(
144            "item.item2.item3".parse(),
145            Ok(YamlPath::Key(
146                "item".to_string(),
147                Vec::new(),
148                Some(Box::new(YamlPath::Key(
149                    "item2".to_string(),
150                    Vec::new(),
151                    Some(Box::new(YamlPath::Key(
152                        "item3".to_string(),
153                        Vec::new(),
154                        None
155                    )))
156                )))
157            ))
158        );
159    }
160
161    #[test]
162    fn parse_with_conditions() {
163        assert_eq!(
164            r#"item|field=test.item2"#.parse(),
165            Ok(YamlPath::Key(
166                "item".to_string(),
167                vec![Condition {
168                    field: "field".to_string(),
169                    value: "test".to_string()
170                }],
171                Some(Box::new(YamlPath::Key(
172                    "item2".to_string(),
173                    Vec::new(),
174                    None
175                )))
176            ))
177        );
178        assert_eq!(
179            r#"item|field=test|other=something.item2|yeah=oh"#.parse(),
180            Ok(YamlPath::Key(
181                "item".to_string(),
182                vec![
183                    Condition {
184                        field: "field".to_string(),
185                        value: "test".to_string()
186                    },
187                    Condition {
188                        field: "other".to_string(),
189                        value: "something".to_string()
190                    }
191                ],
192                Some(Box::new(YamlPath::Key(
193                    "item2".to_string(),
194                    vec![Condition {
195                        field: "yeah".to_string(),
196                        value: "oh".to_string()
197                    }],
198                    None
199                )))
200            ))
201        );
202        assert_eq!(
203            r#"item[*]|field=test|other=something.item2|yeah=oh"#.parse(),
204            Ok(YamlPath::Key(
205                "item".to_string(),
206                Vec::new(),
207                Some(Box::new(YamlPath::AllIndexes(
208                    vec![
209                        Condition {
210                            field: "field".to_string(),
211                            value: "test".to_string()
212                        },
213                        Condition {
214                            field: "other".to_string(),
215                            value: "something".to_string()
216                        }
217                    ],
218                    Some(Box::new(YamlPath::Key(
219                        "item2".to_string(),
220                        vec![Condition {
221                            field: "yeah".to_string(),
222                            value: "oh".to_string()
223                        }],
224                        None
225                    )))
226                )))
227            ))
228        );
229    }
230
231    #[test]
232    fn insert() {
233        let first: YamlPath = "minimum_marks".parse().unwrap();
234        let mut new: YamlPath = "parts[*].gaps|type=gapfill[*]".parse().unwrap();
235        new.insert(first);
236        assert_eq!(
237            new,
238            "parts[*].gaps|type=gapfill[*].minimum_marks"
239                .parse()
240                .unwrap()
241        );
242
243        let first: YamlPath = "minimum_marks".parse().unwrap();
244        let mut new: YamlPath = "parts[*]|type=gapfill.gaps[*]".parse().unwrap();
245        new.insert(first);
246        assert_eq!(
247            new,
248            "parts[*]|type=gapfill.gaps[*].minimum_marks"
249                .parse()
250                .unwrap()
251        );
252    }
253}