1use indexmap::IndexMap;
2use kdl::{KdlEntry, KdlEntryFormat, KdlNode, KdlValue};
3use miette::SourceSpan;
4use std::fmt::Debug;
5use std::ops::RangeBounds;
6
7use crate::error::UsageErr;
8use crate::spec::context::ParsingContext;
9
10fn raw_multiline_hash_count(value: &str) -> usize {
13 let mut max_count = 0;
14 for line in value.lines() {
15 for (idx, _) in line.match_indices("\"\"\"") {
16 let after = &line[idx + 3..];
17 let count = after.chars().take_while(|&c| c == '#').count();
18 max_count = max_count.max(count);
19 }
20 }
21 max_count + 1
22}
23
24fn escape_string(value: &str) -> String {
26 let mut out = String::with_capacity(value.len() + 2);
27 out.push('"');
28 for ch in value.chars() {
29 match ch {
30 '"' => out.push_str("\\\""),
31 '\\' => out.push_str("\\\\"),
32 '\n' => out.push_str("\\n"),
33 '\r' => out.push_str("\\r"),
34 '\t' => out.push_str("\\t"),
35 c if c.is_control() => out.push_str(&format!("\\u{{{:x}}}", c as u32)),
36 c => out.push(c),
37 }
38 }
39 out.push('"');
40 out
41}
42
43fn quoted_format(value_repr: &str) -> KdlEntryFormat {
45 KdlEntryFormat {
46 value_repr: value_repr.to_string(),
47 leading: " ".into(),
48 trailing: "".into(),
49 after_ty: "".into(),
50 before_ty_name: "".into(),
51 after_ty_name: "".into(),
52 after_key: "".into(),
53 after_eq: "".into(),
54 autoformat_keep: true,
55 }
56}
57
58pub(crate) fn string_entry(key: Option<&str>, value: &str) -> KdlEntry {
62 let mut entry = match key {
63 Some(k) => KdlEntry::new_prop(k, KdlValue::String(value.to_string())),
64 None => KdlEntry::new(KdlValue::String(value.to_string())),
65 };
66 let dashed_argument = key.is_none() && value.starts_with('-');
75 let has_control = value
79 .chars()
80 .any(|c| c.is_control() && c != '\n' && c != '\t');
81 if dashed_argument || has_control {
82 entry.set_format(quoted_format(&escape_string(value)));
83 return entry;
84 }
85 if value.contains('\n') {
86 let n = raw_multiline_hash_count(value);
87 let hashes = "#".repeat(n);
88 let repr = format!("{hashes}\"\"\"\n{value}\n\"\"\"{hashes}");
89 entry.set_format(KdlEntryFormat {
90 value_repr: repr,
91 leading: " ".into(),
92 trailing: "".into(),
93 after_ty: "".into(),
94 before_ty_name: "".into(),
95 after_ty_name: "".into(),
96 after_key: "".into(),
97 after_eq: "".into(),
98 autoformat_keep: true,
99 });
100 }
101 entry
102}
103
104#[derive(Debug)]
105pub struct NodeHelper<'a> {
106 pub(crate) node: &'a KdlNode,
107 pub(crate) ctx: &'a ParsingContext,
108}
109
110impl<'a> NodeHelper<'a> {
111 pub(crate) fn new(ctx: &'a ParsingContext, node: &'a KdlNode) -> Self {
112 Self { node, ctx }
113 }
114
115 pub(crate) fn name(&self) -> &str {
116 self.node.name().value()
117 }
118 pub(crate) fn span(&self) -> SourceSpan {
119 (self.node.span().offset(), self.node.span().len()).into()
120 }
121 pub(crate) fn ensure_arg_len<R>(&self, range: R) -> Result<&Self, UsageErr>
122 where
123 R: RangeBounds<usize> + Debug,
124 {
125 let count = self.args().count();
126 if !range.contains(&count) {
127 let ctx = self.ctx;
128 let span = self.span();
129 bail_parse!(ctx, span, "expected {range:?} arguments, got {count}",)
130 }
131 Ok(self)
132 }
133 pub(crate) fn get(&self, key: &str) -> Option<ParseEntry<'_>> {
134 self.node.entry(key).map(|e| ParseEntry::new(self.ctx, e))
135 }
136 pub(crate) fn arg(&self, i: usize) -> Result<ParseEntry<'_>, UsageErr> {
137 if let Some(entry) = self.args().nth(i) {
138 return Ok(entry);
139 }
140 bail_parse!(self.ctx, self.span(), "missing argument")
141 }
142 pub(crate) fn args(&self) -> impl Iterator<Item = ParseEntry<'_>> + '_ {
143 self.node
144 .entries()
145 .iter()
146 .filter(|e| e.name().is_none())
147 .map(|e| ParseEntry::new(self.ctx, e))
148 }
149 pub(crate) fn props(&self) -> IndexMap<&str, ParseEntry<'_>> {
150 self.node
151 .entries()
152 .iter()
153 .filter_map(|e| {
154 e.name()
155 .map(|key| (key.value(), ParseEntry::new(self.ctx, e)))
156 })
157 .collect()
158 }
159 pub(crate) fn children(&self) -> Vec<Self> {
160 self.node
161 .children()
162 .map(|c| {
163 c.nodes()
164 .iter()
165 .map(|n| NodeHelper::new(self.ctx, n))
166 .collect()
167 })
168 .unwrap_or_default()
169 }
170}
171
172#[derive(Debug)]
173pub(crate) struct ParseEntry<'a> {
174 pub(crate) ctx: &'a ParsingContext,
175 pub(crate) entry: &'a KdlEntry,
176 pub(crate) value: &'a KdlValue,
177}
178
179impl<'a> ParseEntry<'a> {
180 fn new(ctx: &'a ParsingContext, entry: &'a KdlEntry) -> Self {
181 Self {
182 ctx,
183 entry,
184 value: entry.value(),
185 }
186 }
187
188 fn span(&self) -> SourceSpan {
189 (self.entry.span().offset(), self.entry.span().len()).into()
190 }
191}
192
193impl ParseEntry<'_> {
194 pub fn ensure_usize(&self) -> Result<usize, UsageErr> {
195 match self.value.as_integer() {
196 Some(i) => Ok(i as usize),
197 None => bail_parse!(self.ctx, self.span(), "expected usize"),
198 }
199 }
200 #[allow(dead_code)]
201 pub fn ensure_f64(&self) -> Result<f64, UsageErr> {
202 match self.value.as_float() {
203 Some(f) => Ok(f),
204 None => bail_parse!(self.ctx, self.span(), "expected float"),
205 }
206 }
207 pub fn ensure_bool(&self) -> Result<bool, UsageErr> {
208 match self.value.as_bool() {
209 Some(b) => Ok(b),
210 None => bail_parse!(self.ctx, self.span(), "expected bool"),
211 }
212 }
213 pub fn ensure_string(&self) -> Result<String, UsageErr> {
214 match self.value.as_string() {
215 Some(s) => Ok(s.to_string()),
216 None => bail_parse!(self.ctx, self.span(), "expected string"),
217 }
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224 use kdl::KdlDocument;
225 use std::path::Path;
226
227 fn parse_node(input: &str) -> (ParsingContext, KdlDocument) {
228 let ctx = ParsingContext::new(Path::new("test.kdl"), input);
229 let doc: KdlDocument = input.parse().unwrap();
230 (ctx, doc)
231 }
232
233 #[test]
234 fn test_node_helper_name() {
235 let (ctx, doc) = parse_node("test_node \"arg1\"");
236 let node = doc.nodes().first().unwrap();
237 let helper = NodeHelper::new(&ctx, node);
238 assert_eq!(helper.name(), "test_node");
239 }
240
241 #[test]
242 fn test_node_helper_arg() {
243 let (ctx, doc) = parse_node("node \"first\" \"second\"");
244 let node = doc.nodes().first().unwrap();
245 let helper = NodeHelper::new(&ctx, node);
246
247 assert_eq!(helper.arg(0).unwrap().ensure_string().unwrap(), "first");
248 assert_eq!(helper.arg(1).unwrap().ensure_string().unwrap(), "second");
249 }
250
251 #[test]
252 fn test_node_helper_args_count() {
253 let (ctx, doc) = parse_node("node \"a\" \"b\" \"c\"");
254 let node = doc.nodes().first().unwrap();
255 let helper = NodeHelper::new(&ctx, node);
256
257 assert_eq!(helper.args().count(), 3);
258 }
259
260 #[test]
261 fn test_node_helper_props() {
262 let (ctx, doc) = parse_node("node key1=\"value1\" key2=\"value2\"");
263 let node = doc.nodes().first().unwrap();
264 let helper = NodeHelper::new(&ctx, node);
265
266 let props = helper.props();
267 assert_eq!(props.len(), 2);
268 assert_eq!(props["key1"].ensure_string().unwrap(), "value1");
269 assert_eq!(props["key2"].ensure_string().unwrap(), "value2");
270 }
271
272 #[test]
273 fn test_node_helper_get() {
274 let (ctx, doc) = parse_node("node name=\"test\"");
275 let node = doc.nodes().first().unwrap();
276 let helper = NodeHelper::new(&ctx, node);
277
278 assert!(helper.get("name").is_some());
279 assert!(helper.get("nonexistent").is_none());
280 }
281
282 #[test]
283 fn test_node_helper_children() {
284 let (ctx, doc) = parse_node("parent { child1; child2 }");
285 let node = doc.nodes().first().unwrap();
286 let helper = NodeHelper::new(&ctx, node);
287
288 let children = helper.children();
289 assert_eq!(children.len(), 2);
290 assert_eq!(children[0].name(), "child1");
291 assert_eq!(children[1].name(), "child2");
292 }
293
294 #[test]
295 fn test_node_helper_ensure_arg_len_valid() {
296 let (ctx, doc) = parse_node("node \"a\" \"b\"");
297 let node = doc.nodes().first().unwrap();
298 let helper = NodeHelper::new(&ctx, node);
299
300 assert!(helper.ensure_arg_len(2..=2).is_ok());
301 assert!(helper.ensure_arg_len(1..=3).is_ok());
302 assert!(helper.ensure_arg_len(0..).is_ok());
303 }
304
305 #[test]
306 fn test_node_helper_ensure_arg_len_invalid() {
307 let (ctx, doc) = parse_node("node \"a\"");
308 let node = doc.nodes().first().unwrap();
309 let helper = NodeHelper::new(&ctx, node);
310
311 assert!(helper.ensure_arg_len(2..=2).is_err());
312 }
313
314 #[test]
315 fn test_parse_entry_ensure_usize() {
316 let (ctx, doc) = parse_node("node 42");
317 let node = doc.nodes().first().unwrap();
318 let helper = NodeHelper::new(&ctx, node);
319
320 assert_eq!(helper.arg(0).unwrap().ensure_usize().unwrap(), 42);
321 }
322
323 #[test]
324 fn test_parse_entry_ensure_bool() {
325 let (ctx, doc) = parse_node("node #true");
326 let node = doc.nodes().first().unwrap();
327 let helper = NodeHelper::new(&ctx, node);
328
329 assert!(helper.arg(0).unwrap().ensure_bool().unwrap());
330 }
331
332 #[test]
333 fn test_parse_entry_ensure_string() {
334 let (ctx, doc) = parse_node("node \"hello\"");
335 let node = doc.nodes().first().unwrap();
336 let helper = NodeHelper::new(&ctx, node);
337
338 assert_eq!(helper.arg(0).unwrap().ensure_string().unwrap(), "hello");
339 }
340
341 #[test]
342 fn test_parse_entry_type_mismatch() {
343 let (ctx, doc) = parse_node("node \"not_a_number\"");
344 let node = doc.nodes().first().unwrap();
345 let helper = NodeHelper::new(&ctx, node);
346
347 assert!(helper.arg(0).unwrap().ensure_usize().is_err());
348 }
349}