1use indexmap::IndexMap;
2use kdl::{KdlEntry, KdlNode, KdlValue};
3use miette::SourceSpan;
4use std::fmt::Debug;
5use std::ops::RangeBounds;
6
7use crate::error::UsageErr;
8use crate::spec::context::ParsingContext;
9
10#[derive(Debug)]
11pub struct NodeHelper<'a> {
12 pub(crate) node: &'a KdlNode,
13 pub(crate) ctx: &'a ParsingContext,
14}
15
16impl<'a> NodeHelper<'a> {
17 pub(crate) fn new(ctx: &'a ParsingContext, node: &'a KdlNode) -> Self {
18 Self { node, ctx }
19 }
20
21 pub(crate) fn name(&self) -> &str {
22 self.node.name().value()
23 }
24 pub(crate) fn span(&self) -> SourceSpan {
25 (self.node.span().offset(), self.node.span().len()).into()
26 }
27 pub(crate) fn ensure_arg_len<R>(&self, range: R) -> Result<&Self, UsageErr>
28 where
29 R: RangeBounds<usize> + Debug,
30 {
31 let count = self.args().count();
32 if !range.contains(&count) {
33 let ctx = self.ctx;
34 let span = self.span();
35 bail_parse!(ctx, span, "expected {range:?} arguments, got {count}",)
36 }
37 Ok(self)
38 }
39 pub(crate) fn get(&self, key: &str) -> Option<ParseEntry<'_>> {
40 self.node.entry(key).map(|e| ParseEntry::new(self.ctx, e))
41 }
42 pub(crate) fn arg(&self, i: usize) -> Result<ParseEntry<'_>, UsageErr> {
43 if let Some(entry) = self.args().nth(i) {
44 return Ok(entry);
45 }
46 bail_parse!(self.ctx, self.span(), "missing argument")
47 }
48 pub(crate) fn args(&self) -> impl Iterator<Item = ParseEntry<'_>> + '_ {
49 self.node
50 .entries()
51 .iter()
52 .filter(|e| e.name().is_none())
53 .map(|e| ParseEntry::new(self.ctx, e))
54 }
55 pub(crate) fn props(&self) -> IndexMap<&str, ParseEntry<'_>> {
56 self.node
57 .entries()
58 .iter()
59 .filter_map(|e| {
60 e.name()
61 .map(|key| (key.value(), ParseEntry::new(self.ctx, e)))
62 })
63 .collect()
64 }
65 pub(crate) fn children(&self) -> Vec<Self> {
66 self.node
67 .children()
68 .map(|c| {
69 c.nodes()
70 .iter()
71 .map(|n| NodeHelper::new(self.ctx, n))
72 .collect()
73 })
74 .unwrap_or_default()
75 }
76}
77
78#[derive(Debug)]
79pub(crate) struct ParseEntry<'a> {
80 pub(crate) ctx: &'a ParsingContext,
81 pub(crate) entry: &'a KdlEntry,
82 pub(crate) value: &'a KdlValue,
83}
84
85impl<'a> ParseEntry<'a> {
86 fn new(ctx: &'a ParsingContext, entry: &'a KdlEntry) -> Self {
87 Self {
88 ctx,
89 entry,
90 value: entry.value(),
91 }
92 }
93
94 fn span(&self) -> SourceSpan {
95 (self.entry.span().offset(), self.entry.span().len()).into()
96 }
97}
98
99impl ParseEntry<'_> {
100 pub fn ensure_usize(&self) -> Result<usize, UsageErr> {
101 match self.value.as_integer() {
102 Some(i) => Ok(i as usize),
103 None => bail_parse!(self.ctx, self.span(), "expected usize"),
104 }
105 }
106 #[allow(dead_code)]
107 pub fn ensure_f64(&self) -> Result<f64, UsageErr> {
108 match self.value.as_float() {
109 Some(f) => Ok(f),
110 None => bail_parse!(self.ctx, self.span(), "expected float"),
111 }
112 }
113 pub fn ensure_bool(&self) -> Result<bool, UsageErr> {
114 match self.value.as_bool() {
115 Some(b) => Ok(b),
116 None => bail_parse!(self.ctx, self.span(), "expected bool"),
117 }
118 }
119 pub fn ensure_string(&self) -> Result<String, UsageErr> {
120 match self.value.as_string() {
121 Some(s) => Ok(s.to_string()),
122 None => bail_parse!(self.ctx, self.span(), "expected string"),
123 }
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use kdl::KdlDocument;
131 use std::path::Path;
132
133 fn parse_node(input: &str) -> (ParsingContext, KdlDocument) {
134 let ctx = ParsingContext::new(Path::new("test.kdl"), input);
135 let doc: KdlDocument = input.parse().unwrap();
136 (ctx, doc)
137 }
138
139 #[test]
140 fn test_node_helper_name() {
141 let (ctx, doc) = parse_node("test_node \"arg1\"");
142 let node = doc.nodes().first().unwrap();
143 let helper = NodeHelper::new(&ctx, node);
144 assert_eq!(helper.name(), "test_node");
145 }
146
147 #[test]
148 fn test_node_helper_arg() {
149 let (ctx, doc) = parse_node("node \"first\" \"second\"");
150 let node = doc.nodes().first().unwrap();
151 let helper = NodeHelper::new(&ctx, node);
152
153 assert_eq!(helper.arg(0).unwrap().ensure_string().unwrap(), "first");
154 assert_eq!(helper.arg(1).unwrap().ensure_string().unwrap(), "second");
155 }
156
157 #[test]
158 fn test_node_helper_args_count() {
159 let (ctx, doc) = parse_node("node \"a\" \"b\" \"c\"");
160 let node = doc.nodes().first().unwrap();
161 let helper = NodeHelper::new(&ctx, node);
162
163 assert_eq!(helper.args().count(), 3);
164 }
165
166 #[test]
167 fn test_node_helper_props() {
168 let (ctx, doc) = parse_node("node key1=\"value1\" key2=\"value2\"");
169 let node = doc.nodes().first().unwrap();
170 let helper = NodeHelper::new(&ctx, node);
171
172 let props = helper.props();
173 assert_eq!(props.len(), 2);
174 assert_eq!(props["key1"].ensure_string().unwrap(), "value1");
175 assert_eq!(props["key2"].ensure_string().unwrap(), "value2");
176 }
177
178 #[test]
179 fn test_node_helper_get() {
180 let (ctx, doc) = parse_node("node name=\"test\"");
181 let node = doc.nodes().first().unwrap();
182 let helper = NodeHelper::new(&ctx, node);
183
184 assert!(helper.get("name").is_some());
185 assert!(helper.get("nonexistent").is_none());
186 }
187
188 #[test]
189 fn test_node_helper_children() {
190 let (ctx, doc) = parse_node("parent { child1; child2 }");
191 let node = doc.nodes().first().unwrap();
192 let helper = NodeHelper::new(&ctx, node);
193
194 let children = helper.children();
195 assert_eq!(children.len(), 2);
196 assert_eq!(children[0].name(), "child1");
197 assert_eq!(children[1].name(), "child2");
198 }
199
200 #[test]
201 fn test_node_helper_ensure_arg_len_valid() {
202 let (ctx, doc) = parse_node("node \"a\" \"b\"");
203 let node = doc.nodes().first().unwrap();
204 let helper = NodeHelper::new(&ctx, node);
205
206 assert!(helper.ensure_arg_len(2..=2).is_ok());
207 assert!(helper.ensure_arg_len(1..=3).is_ok());
208 assert!(helper.ensure_arg_len(0..).is_ok());
209 }
210
211 #[test]
212 fn test_node_helper_ensure_arg_len_invalid() {
213 let (ctx, doc) = parse_node("node \"a\"");
214 let node = doc.nodes().first().unwrap();
215 let helper = NodeHelper::new(&ctx, node);
216
217 assert!(helper.ensure_arg_len(2..=2).is_err());
218 }
219
220 #[test]
221 fn test_parse_entry_ensure_usize() {
222 let (ctx, doc) = parse_node("node 42");
223 let node = doc.nodes().first().unwrap();
224 let helper = NodeHelper::new(&ctx, node);
225
226 assert_eq!(helper.arg(0).unwrap().ensure_usize().unwrap(), 42);
227 }
228
229 #[test]
230 fn test_parse_entry_ensure_bool() {
231 let (ctx, doc) = parse_node("node #true");
232 let node = doc.nodes().first().unwrap();
233 let helper = NodeHelper::new(&ctx, node);
234
235 assert!(helper.arg(0).unwrap().ensure_bool().unwrap());
236 }
237
238 #[test]
239 fn test_parse_entry_ensure_string() {
240 let (ctx, doc) = parse_node("node \"hello\"");
241 let node = doc.nodes().first().unwrap();
242 let helper = NodeHelper::new(&ctx, node);
243
244 assert_eq!(helper.arg(0).unwrap().ensure_string().unwrap(), "hello");
245 }
246
247 #[test]
248 fn test_parse_entry_type_mismatch() {
249 let (ctx, doc) = parse_node("node \"not_a_number\"");
250 let node = doc.nodes().first().unwrap();
251 let helper = NodeHelper::new(&ctx, node);
252
253 assert!(helper.arg(0).unwrap().ensure_usize().is_err());
254 }
255}