1use std::iter::Peekable;
2
3use pest::iterators::Pair;
4use pest::Parser;
5
6use crate::error::RenderError;
7use crate::grammar::{HandlebarsParser, Rule};
8
9#[derive(PartialEq, Eq, Clone, Debug)]
10pub 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)]
20pub enum Path {
21 Relative((Vec<PathSeg>, String)),
22 Local((usize, String, String)),
23}
24
25impl 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
73fn 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
91pub(crate) fn parse_json_path_from_iter<'a, I>(it: &mut Peekable<I>, limit: usize) -> Vec<PathSeg>
92where
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
127pub(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