1use crate::kdl::{KdlEntry, KdlEntryFormat, KdlNode, KdlValue};
2use crate::miette::SourceSpan;
3use indexmap::IndexMap;
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 {
17 let mut max_count = 0;
18 let bytes = value.as_bytes();
19 for i in 0..bytes.len().saturating_sub(2) {
20 if bytes[i] == b'"' && bytes[i + 1] == b'"' && bytes[i + 2] == b'"' {
21 let count = value[i + 3..].chars().take_while(|&c| c == '#').count();
22 max_count = max_count.max(count);
23 }
24 }
25 max_count + 1
26}
27
28fn escape_string(value: &str) -> String {
30 let mut out = String::with_capacity(value.len() + 2);
31 out.push('"');
32 for ch in value.chars() {
33 match ch {
34 '"' => out.push_str("\\\""),
35 '\\' => out.push_str("\\\\"),
36 '\n' => out.push_str("\\n"),
37 '\r' => out.push_str("\\r"),
38 '\t' => out.push_str("\\t"),
39 c if c.is_control() => out.push_str(&format!("\\u{{{:x}}}", c as u32)),
40 c => out.push(c),
41 }
42 }
43 out.push('"');
44 out
45}
46
47fn quoted_format(value_repr: &str) -> KdlEntryFormat {
49 KdlEntryFormat {
50 value_repr: value_repr.to_string(),
51 leading: " ".into(),
52 trailing: "".into(),
53 after_ty: "".into(),
54 before_ty_name: "".into(),
55 after_ty_name: "".into(),
56 after_key: "".into(),
57 after_eq: "".into(),
58 autoformat_keep: true,
59 }
60}
61
62pub(crate) fn string_entry(key: Option<&str>, value: &str) -> KdlEntry {
66 let mut entry = match key {
67 Some(k) => KdlEntry::new_prop(k, KdlValue::String(value.to_string())),
68 None => KdlEntry::new(KdlValue::String(value.to_string())),
69 };
70 let dashed_argument = key.is_none() && value.starts_with('-');
79 let has_control = value
83 .chars()
84 .any(|c| c.is_control() && c != '\n' && c != '\t');
85 if dashed_argument || has_control {
86 entry.set_format(quoted_format(&escape_string(value)));
87 return entry;
88 }
89 if value.contains('\n') {
90 let n = raw_multiline_hash_count(value);
91 let hashes = "#".repeat(n);
92 let repr = format!("{hashes}\"\"\"\n{value}\n\"\"\"{hashes}");
93 entry.set_format(KdlEntryFormat {
94 value_repr: repr,
95 leading: " ".into(),
96 trailing: "".into(),
97 after_ty: "".into(),
98 before_ty_name: "".into(),
99 after_ty_name: "".into(),
100 after_key: "".into(),
101 after_eq: "".into(),
102 autoformat_keep: true,
103 });
104 }
105 entry
106}
107
108pub(crate) fn fits_selector_property(selector: &str) -> bool {
113 selector.split_whitespace().nth(1).is_none()
114 || !selector
115 .split_whitespace()
116 .any(|word| word.starts_with('-'))
117}
118
119#[derive(Debug)]
120pub struct NodeHelper<'a> {
121 pub(crate) node: &'a KdlNode,
122 pub(crate) ctx: &'a ParsingContext,
123}
124
125impl<'a> NodeHelper<'a> {
126 pub(crate) fn new(ctx: &'a ParsingContext, node: &'a KdlNode) -> Self {
127 Self { node, ctx }
128 }
129
130 pub(crate) fn name(&self) -> &str {
131 self.node.name().value()
132 }
133 pub(crate) fn span(&self) -> SourceSpan {
134 (self.node.span().offset(), self.node.span().len()).into()
135 }
136 pub(crate) fn ensure_arg_len<R>(&self, range: R) -> Result<&Self, UsageErr>
137 where
138 R: RangeBounds<usize> + Debug,
139 {
140 let count = self.args().count();
141 if !range.contains(&count) {
142 let ctx = self.ctx;
143 let span = self.span();
144 bail_parse!(ctx, span, "expected {range:?} arguments, got {count}",)
145 }
146 Ok(self)
147 }
148 pub(crate) fn ensure_props_unrepeated(&self, keys: &[&str]) -> Result<(), UsageErr> {
150 for key in keys {
151 let mut written = self
152 .node
153 .entries()
154 .iter()
155 .filter(|e| e.name().is_some_and(|name| name.value() == *key));
156 if let (Some(_), Some(second)) = (written.next(), written.next()) {
157 bail_parse!(
158 self.ctx,
159 second.span(),
160 "{key} is given more than once; the second would replace the first, so write several as a child node instead: {key} \"--a\" \"--b\""
161 );
162 }
163 }
164 Ok(())
165 }
166 pub(crate) fn get(&self, key: &str) -> Option<ParseEntry<'_>> {
167 self.node.entry(key).map(|e| ParseEntry::new(self.ctx, e))
168 }
169 pub(crate) fn arg(&self, i: usize) -> Result<ParseEntry<'_>, UsageErr> {
170 if let Some(entry) = self.args().nth(i) {
171 return Ok(entry);
172 }
173 bail_parse!(self.ctx, self.span(), "missing argument")
174 }
175 pub(crate) fn args(&self) -> impl Iterator<Item = ParseEntry<'_>> + '_ {
176 self.node
177 .entries()
178 .iter()
179 .filter(|e| e.name().is_none())
180 .map(|e| ParseEntry::new(self.ctx, e))
181 }
182 pub(crate) fn props(&self) -> IndexMap<&str, ParseEntry<'_>> {
183 self.node
184 .entries()
185 .iter()
186 .filter_map(|e| {
187 e.name()
188 .map(|key| (key.value(), ParseEntry::new(self.ctx, e)))
189 })
190 .collect()
191 }
192 pub(crate) fn children(&self) -> Vec<Self> {
193 self.node
194 .children()
195 .map(|c| {
196 c.nodes()
197 .iter()
198 .map(|n| NodeHelper::new(self.ctx, n))
199 .collect()
200 })
201 .unwrap_or_default()
202 }
203}
204
205#[derive(Debug)]
206pub(crate) struct ParseEntry<'a> {
207 pub(crate) ctx: &'a ParsingContext,
208 pub(crate) entry: &'a KdlEntry,
209 pub(crate) value: &'a KdlValue,
210}
211
212impl<'a> ParseEntry<'a> {
213 fn new(ctx: &'a ParsingContext, entry: &'a KdlEntry) -> Self {
214 Self {
215 ctx,
216 entry,
217 value: entry.value(),
218 }
219 }
220
221 fn span(&self) -> SourceSpan {
222 (self.entry.span().offset(), self.entry.span().len()).into()
223 }
224}
225
226impl ParseEntry<'_> {
227 pub fn ensure_usize(&self) -> Result<usize, UsageErr> {
228 match self.value.as_integer() {
229 Some(i) => Ok(i as usize),
230 None => bail_parse!(self.ctx, self.span(), "expected usize"),
231 }
232 }
233 #[allow(dead_code)]
234 pub fn ensure_f64(&self) -> Result<f64, UsageErr> {
235 match self.value.as_float() {
236 Some(f) => Ok(f),
237 None => bail_parse!(self.ctx, self.span(), "expected float"),
238 }
239 }
240 pub fn ensure_bool(&self) -> Result<bool, UsageErr> {
241 match self.value.as_bool() {
242 Some(b) => Ok(b),
243 None => bail_parse!(self.ctx, self.span(), "expected bool"),
244 }
245 }
246 pub fn ensure_string(&self) -> Result<String, UsageErr> {
247 match self.value.as_string() {
248 Some(s) => Ok(s.to_string()),
249 None => bail_parse!(self.ctx, self.span(), "expected string"),
250 }
251 }
252
253 pub fn set_selector(&self, key: &str, list: &mut Vec<String>) -> Result<(), UsageErr> {
263 let selector = self.ensure_string()?;
264 if !fits_selector_property(&selector) {
265 bail_parse!(
266 self.ctx,
267 self.span(),
268 "{key} takes one flag or argument per property, but {selector:?} has several; write them as a child node instead: {key} \"--a\" \"--b\""
269 );
270 }
271 list.push(selector);
272 Ok(())
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279 use crate::kdl::KdlDocument;
280 use std::path::Path;
281
282 fn parse_node(input: &str) -> (ParsingContext, KdlDocument) {
283 let ctx = ParsingContext::new(Path::new("test.kdl"), input);
284 let doc: KdlDocument = input.parse().unwrap();
285 (ctx, doc)
286 }
287
288 #[test]
289 fn test_node_helper_name() {
290 let (ctx, doc) = parse_node("test_node \"arg1\"");
291 let node = doc.nodes().first().unwrap();
292 let helper = NodeHelper::new(&ctx, node);
293 assert_eq!(helper.name(), "test_node");
294 }
295
296 #[test]
297 fn test_node_helper_arg() {
298 let (ctx, doc) = parse_node("node \"first\" \"second\"");
299 let node = doc.nodes().first().unwrap();
300 let helper = NodeHelper::new(&ctx, node);
301
302 assert_eq!(helper.arg(0).unwrap().ensure_string().unwrap(), "first");
303 assert_eq!(helper.arg(1).unwrap().ensure_string().unwrap(), "second");
304 }
305
306 #[test]
307 fn test_node_helper_args_count() {
308 let (ctx, doc) = parse_node("node \"a\" \"b\" \"c\"");
309 let node = doc.nodes().first().unwrap();
310 let helper = NodeHelper::new(&ctx, node);
311
312 assert_eq!(helper.args().count(), 3);
313 }
314
315 #[test]
316 fn test_node_helper_props() {
317 let (ctx, doc) = parse_node("node key1=\"value1\" key2=\"value2\"");
318 let node = doc.nodes().first().unwrap();
319 let helper = NodeHelper::new(&ctx, node);
320
321 let props = helper.props();
322 assert_eq!(props.len(), 2);
323 assert_eq!(props["key1"].ensure_string().unwrap(), "value1");
324 assert_eq!(props["key2"].ensure_string().unwrap(), "value2");
325 }
326
327 #[test]
328 fn test_node_helper_get() {
329 let (ctx, doc) = parse_node("node name=\"test\"");
330 let node = doc.nodes().first().unwrap();
331 let helper = NodeHelper::new(&ctx, node);
332
333 assert!(helper.get("name").is_some());
334 assert!(helper.get("nonexistent").is_none());
335 }
336
337 #[test]
338 fn test_node_helper_children() {
339 let (ctx, doc) = parse_node("parent { child1; child2 }");
340 let node = doc.nodes().first().unwrap();
341 let helper = NodeHelper::new(&ctx, node);
342
343 let children = helper.children();
344 assert_eq!(children.len(), 2);
345 assert_eq!(children[0].name(), "child1");
346 assert_eq!(children[1].name(), "child2");
347 }
348
349 #[test]
350 fn test_node_helper_ensure_arg_len_valid() {
351 let (ctx, doc) = parse_node("node \"a\" \"b\"");
352 let node = doc.nodes().first().unwrap();
353 let helper = NodeHelper::new(&ctx, node);
354
355 assert!(helper.ensure_arg_len(2..=2).is_ok());
356 assert!(helper.ensure_arg_len(1..=3).is_ok());
357 assert!(helper.ensure_arg_len(0..).is_ok());
358 }
359
360 #[test]
361 fn test_node_helper_ensure_arg_len_invalid() {
362 let (ctx, doc) = parse_node("node \"a\"");
363 let node = doc.nodes().first().unwrap();
364 let helper = NodeHelper::new(&ctx, node);
365
366 assert!(helper.ensure_arg_len(2..=2).is_err());
367 }
368
369 #[test]
370 fn test_parse_entry_ensure_usize() {
371 let (ctx, doc) = parse_node("node 42");
372 let node = doc.nodes().first().unwrap();
373 let helper = NodeHelper::new(&ctx, node);
374
375 assert_eq!(helper.arg(0).unwrap().ensure_usize().unwrap(), 42);
376 }
377
378 #[test]
379 fn test_parse_entry_ensure_bool() {
380 let (ctx, doc) = parse_node("node #true");
381 let node = doc.nodes().first().unwrap();
382 let helper = NodeHelper::new(&ctx, node);
383
384 assert!(helper.arg(0).unwrap().ensure_bool().unwrap());
385 }
386
387 #[test]
388 fn test_parse_entry_ensure_string() {
389 let (ctx, doc) = parse_node("node \"hello\"");
390 let node = doc.nodes().first().unwrap();
391 let helper = NodeHelper::new(&ctx, node);
392
393 assert_eq!(helper.arg(0).unwrap().ensure_string().unwrap(), "hello");
394 }
395
396 #[test]
397 fn test_parse_entry_type_mismatch() {
398 let (ctx, doc) = parse_node("node \"not_a_number\"");
399 let node = doc.nodes().first().unwrap();
400 let helper = NodeHelper::new(&ctx, node);
401
402 assert!(helper.arg(0).unwrap().ensure_usize().is_err());
403 }
404
405 #[test]
406 fn overlapping_quotes_raise_the_raw_multiline_hash_count() {
407 assert_eq!(raw_multiline_hash_count("\"\"\"#"), 2);
408 assert_eq!(raw_multiline_hash_count("\"\"\"\"#"), 2);
409 assert_eq!(raw_multiline_hash_count("\"\"\"\"##"), 3);
410 assert_eq!(raw_multiline_hash_count("plain"), 1);
411 }
412
413 #[test]
414 fn overlapping_quotes_round_trip_through_parse() {
415 for value in [
416 "before\n\"\"\"\"#\nafter",
417 "before\n\"\"\"\"##\nafter",
418 "line\n\"\"\"#\nstill",
419 ] {
420 let mut node = KdlNode::new("about");
421 node.push(string_entry(None, value));
422 let kdl = format!("name ex\nbin ex\n{node}\n");
423 let spec: crate::Spec = kdl
424 .parse()
425 .unwrap_or_else(|e| panic!("emitted spec must parse: {e}\n{kdl}"));
426 assert_eq!(spec.about.as_deref(), Some(value), "{kdl}");
427 }
428 }
429}