1 | use std::iter::Peekable; |
2 | |
3 | use pest::iterators::Pair; |
4 | use pest::Parser; |
5 | |
6 | use crate::error::RenderError; |
7 | use crate::grammar::{HandlebarsParser, Rule}; |
8 | |
9 | #[derive (PartialEq, Eq, Clone, Debug)] |
10 | pub enum PathSeg { |
11 | Named(String), |
12 | Ruled(Rule), |
13 | } |
14 | |
15 | /// Represents the Json path in templates. |
16 | /// |
17 | /// It can be either a local variable like `@first`, `../@index`, |
18 | /// or a normal relative path like `a/b/c`. |
19 | #[derive (PartialEq, Eq, Clone, Debug)] |
20 | pub enum Path { |
21 | Relative((Vec<PathSeg>, String)), |
22 | Local((usize, String, String)), |
23 | } |
24 | |
25 | impl Path { |
26 | pub(crate) fn new(raw: &str, segs: Vec<PathSeg>) -> Path { |
27 | if let Some((level, name)) = get_local_path_and_level(&segs) { |
28 | Path::Local((level, name, raw.to_owned())) |
29 | } else { |
30 | Path::Relative((segs, raw.to_owned())) |
31 | } |
32 | } |
33 | |
34 | pub fn parse(raw: &str) -> Result<Path, RenderError> { |
35 | HandlebarsParser::parse(Rule::path, raw) |
36 | .map(|p| { |
37 | let parsed = p.flatten(); |
38 | let segs = parse_json_path_from_iter(&mut parsed.peekable(), raw.len()); |
39 | Ok(Path::new(raw, segs)) |
40 | }) |
41 | .map_err(|_| RenderError::new("Invalid JSON path" ))? |
42 | } |
43 | |
44 | pub(crate) fn raw(&self) -> &str { |
45 | match self { |
46 | Path::Relative((_, ref raw)) => raw, |
47 | Path::Local((_, _, ref raw)) => raw, |
48 | } |
49 | } |
50 | |
51 | pub(crate) fn current() -> Path { |
52 | Path::Relative((Vec::with_capacity(0), "" .to_owned())) |
53 | } |
54 | |
55 | // for test only |
56 | pub(crate) fn with_named_paths(name_segs: &[&str]) -> Path { |
57 | let segs = name_segs |
58 | .iter() |
59 | .map(|n| PathSeg::Named((*n).to_string())) |
60 | .collect(); |
61 | Path::Relative((segs, name_segs.join("/" ))) |
62 | } |
63 | |
64 | // for test only |
65 | pub(crate) fn segs(&self) -> Option<&[PathSeg]> { |
66 | match self { |
67 | Path::Relative((segs, _)) => Some(segs), |
68 | _ => None, |
69 | } |
70 | } |
71 | } |
72 | |
73 | fn get_local_path_and_level(paths: &[PathSeg]) -> Option<(usize, String)> { |
74 | paths.get(index:0).and_then(|seg: &PathSeg| { |
75 | if seg == &PathSeg::Ruled(Rule::path_local) { |
76 | let mut level: usize = 0; |
77 | while paths.get(index:level + 1)? == &PathSeg::Ruled(Rule::path_up) { |
78 | level += 1; |
79 | } |
80 | if let Some(PathSeg::Named(name: &String)) = paths.get(index:level + 1) { |
81 | Some((level, name.clone())) |
82 | } else { |
83 | None |
84 | } |
85 | } else { |
86 | None |
87 | } |
88 | }) |
89 | } |
90 | |
91 | pub(crate) fn parse_json_path_from_iter<'a, I>(it: &mut Peekable<I>, limit: usize) -> Vec<PathSeg> |
92 | where |
93 | I: Iterator<Item = Pair<'a, Rule>>, |
94 | { |
95 | let mut path_stack = Vec::with_capacity(5); |
96 | while let Some(n) = it.peek() { |
97 | let span = n.as_span(); |
98 | if span.end() > limit { |
99 | break; |
100 | } |
101 | |
102 | match n.as_rule() { |
103 | Rule::path_root => { |
104 | path_stack.push(PathSeg::Ruled(Rule::path_root)); |
105 | } |
106 | Rule::path_local => { |
107 | path_stack.push(PathSeg::Ruled(Rule::path_local)); |
108 | } |
109 | Rule::path_up => { |
110 | path_stack.push(PathSeg::Ruled(Rule::path_up)); |
111 | } |
112 | Rule::path_id | Rule::path_raw_id => { |
113 | let name = n.as_str(); |
114 | if name != "this" { |
115 | path_stack.push(PathSeg::Named(name.to_string())); |
116 | } |
117 | } |
118 | _ => {} |
119 | } |
120 | |
121 | it.next(); |
122 | } |
123 | |
124 | path_stack |
125 | } |
126 | |
127 | pub(crate) fn merge_json_path(path_stack: &mut Vec<String>, relative_path: &[PathSeg]) { |
128 | for seg: &PathSeg in relative_path { |
129 | match seg { |
130 | PathSeg::Named(ref s: &String) => { |
131 | path_stack.push(s.to_owned()); |
132 | } |
133 | PathSeg::Ruled(Rule::path_root) => {} |
134 | PathSeg::Ruled(Rule::path_up) => {} |
135 | _ => {} |
136 | } |
137 | } |
138 | } |
139 | |