Skip to main content

just_kdl/
dom.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! Document tree structures.
3//!
4//! You probably want to start at [`Document`].
5//!
6//! [`Display`] implementations are equivalent to serialization.
7//!
8//! [`Display`]: fmt::Display
9
10use alloc::string::String;
11use alloc::vec::Vec;
12use core::convert::Infallible;
13use core::fmt;
14use core::ops::{Index, IndexMut};
15use core::ptr::eq as ptr_eq;
16
17use smol_str::SmolStr;
18
19use crate::IdentDisplay;
20use crate::writer::Writer;
21
22pub mod iter;
23pub mod number;
24
25/// debug an `Option<T>` as just `T` or `None`
26fn option_debug<T: fmt::Debug>(value: Option<&T>) -> &dyn fmt::Debug {
27	match value {
28		Some(value) => value,
29		None => &None::<Infallible>,
30	}
31}
32
33/// A `document` or `nodes` element, an ordered collection of nodes.
34#[derive(Default, Clone, PartialEq, Eq, Hash)]
35pub struct Document {
36	/// The ordered nodes in this document.
37	pub nodes: Vec<Node>,
38}
39
40impl Document {
41	/// Create a document with no children.
42	pub const fn new() -> Self { Self { nodes: Vec::new() } }
43	/// Iterator over every node with a particular name.
44	pub fn get(&self, name: &str) -> impl Iterator<Item = &Node> {
45		self.nodes.iter().filter(move |node| node.name() == name)
46	}
47	/// Mutable iterator over every node with a particular name.
48	pub fn get_mut(&mut self, name: &str) -> impl Iterator<Item = &mut Node> {
49		self.nodes
50			.iter_mut()
51			.filter(move |node| node.name() == name)
52	}
53	/// Iterate over the [`Event`]s of this document.
54	pub fn iter(&self) -> iter::Iter<'_> { self.into_iter() }
55	/// Normalize document to kdl spec by [`normalize`]-ing child nodes.
56	///
57	/// [`normalize`]: Node::normalize
58	#[cfg(feature = "std")]
59	pub fn normalize(&mut self) {
60		for node in &mut self.nodes {
61			node.normalize();
62		}
63	}
64}
65
66impl fmt::Debug for Document {
67	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
68		f.write_str("Document ")?;
69		f.debug_list().entries(&self.nodes).finish()
70	}
71}
72impl fmt::Display for Document {
73	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
74		let mut writer = Writer::new(f);
75		for event in self {
76			writer.push(&event)?;
77		}
78		Ok(())
79	}
80}
81
82impl From<Vec<Node>> for Document {
83	fn from(nodes: Vec<Node>) -> Self { Self { nodes } }
84}
85impl From<Document> for Vec<Node> {
86	fn from(value: Document) -> Self { value.nodes }
87}
88
89/// A `node` element, a collection of entries with optional children.
90#[derive(Clone, PartialEq, Eq, Hash)]
91pub struct Node {
92	/// The node's type hint.
93	pub r#type: Option<SmolStr>,
94	/// The node's name.
95	pub name: SmolStr,
96	// TODO: consider splitting args / props (makes args indexing constant)
97	// official kdl doesn't do this, and it breaks Events → Document → Events parity
98	/// The node's entries in order.
99	pub entries: Vec<Entry>,
100	/// The node's child document.
101	pub children: Option<Document>,
102}
103
104/// The default node is `-`
105impl Default for Node {
106	fn default() -> Self {
107		Self {
108			r#type: None,
109			name: SmolStr::new_inline("-"),
110			entries: Vec::new(),
111			children: None,
112		}
113	}
114}
115
116impl Node {
117	/// Create a new node with a name.
118	pub fn new<I: Into<SmolStr>>(name: I) -> Self {
119		Self {
120			r#type: None,
121			name: name.into(),
122			entries: Vec::new(),
123			children: None,
124		}
125	}
126	/// Get the node's name.
127	pub fn name(&self) -> &str { &self.name }
128	/// Set the node's name.
129	pub fn set_name<T: Into<SmolStr>>(&mut self, name: T) { self.name = name.into(); }
130	/// Get the node's type hint.
131	pub fn type_hint(&self) -> Option<&str> { self.r#type.as_deref() }
132	/// Set the node's type hint.
133	pub fn set_type_hint<T: Into<SmolStr>>(&mut self, r#type: Option<T>) {
134		self.r#type = r#type.map(Into::into);
135	}
136	/// Get a specific entry.
137	pub fn entry<'key, T: Into<EntryKey<'key>>>(&self, key: T) -> Option<&Entry> {
138		key.into()
139			.seek(self.entries.iter(), |ent| ent.name.as_deref())
140	}
141	/// Mutably get a specific entry.
142	pub fn entry_mut<'key, T: Into<EntryKey<'key>>>(&mut self, key: T) -> Option<&mut Entry> {
143		key.into()
144			.seek(self.entries.iter_mut(), |ent| ent.name.as_deref())
145	}
146	/// Iterate over the [`Event`]s of this node.
147	pub fn iter(&self) -> iter::Iter<'_> { self.into_iter() }
148	/// Normalize node to kdl spec:
149	/// - Empty children block gets removed
150	/// - Normalize child document
151	/// - Duplicate properties are removed
152	#[cfg(feature = "std")]
153	pub fn normalize(&mut self) {
154		use std::collections::HashSet;
155		if let Some(children) = &mut self.children {
156			if children.nodes.is_empty() {
157				self.children = None;
158			} else {
159				children.normalize();
160			}
161		}
162		// TODO: this is simply an unlikely string-pointer
163		// consider a real way to get a fake/random string pointer
164		// or otherwise mark indexes as used with few allocatioons
165		let marker = SmolStr::new_static(&"\0temp"[5..]);
166		// replace duplicate props with marker (in reverse order)
167		let mut seen = HashSet::new();
168		for entry in self.entries.iter_mut().rev() {
169			if let Some(name) = &mut entry.name {
170				if seen.contains(&**name) {
171					*name = marker.clone();
172				} else {
173					seen.insert(&**name);
174				}
175			}
176		}
177		drop(seen);
178		// delete marked props
179		self.entries.retain(|ent| {
180			!ent.name
181				.as_ref()
182				.is_some_and(|name| ptr_eq(name.as_ptr(), marker.as_ptr()) && name.is_empty())
183		});
184	}
185}
186
187impl fmt::Debug for Node {
188	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
189		f.debug_struct("Node")
190			.field("type", option_debug(self.type_hint().as_ref()))
191			.field("name", &self.name)
192			.field("entries", &self.entries)
193			.field("children", option_debug(self.children.as_ref()))
194			.finish()
195	}
196}
197impl fmt::Display for Node {
198	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
199		let mut writer = Writer::new(f);
200		for event in self {
201			writer.push(&event)?;
202		}
203		Ok(())
204	}
205}
206impl<'key, T: Into<EntryKey<'key>>> Index<T> for Node {
207	type Output = Entry;
208	fn index(&self, index: T) -> &Self::Output {
209		let key = index.into();
210		self.entry(key)
211			.unwrap_or_else(|| panic!("Key {key:?} does not exist in node"))
212	}
213}
214impl<'key, T: Into<EntryKey<'key>>> IndexMut<T> for Node {
215	fn index_mut(&mut self, index: T) -> &mut Self::Output {
216		let key = index.into();
217		self.entry_mut(key)
218			.unwrap_or_else(|| panic!("Key {key:?} does not exist in node"))
219	}
220}
221
222/// A `prop` or `value` element, a piece of labelled information.
223#[derive(Clone, PartialEq, Eq, Hash)]
224pub struct Entry {
225	/// The name ("key"), if this property is an entry.
226	pub name: Option<SmolStr>,
227	/// The entry's type hint.
228	pub r#type: Option<SmolStr>,
229	/// The entry's value.
230	pub value: Value,
231}
232
233impl Entry {
234	/// Create an entry that represents a plain value.
235	pub fn new_value(value: Value) -> Self {
236		Self {
237			name: None,
238			r#type: None,
239			value,
240		}
241	}
242	/// Create an entry that represents a named property.
243	pub fn new_prop<T: Into<SmolStr>>(name: T, value: Value) -> Self {
244		Self {
245			name: Some(name.into()),
246			r#type: None,
247			value,
248		}
249	}
250	/// Get the entry's name, if it has one.
251	pub fn name(&self) -> Option<&str> { self.name.as_deref() }
252	/// Set or clear the entry's name.
253	pub fn set_name<T: Into<SmolStr>>(&mut self, name: Option<T>) {
254		self.name = name.map(Into::into);
255	}
256	/// Get the entry's type hint.
257	pub fn type_hint(&self) -> Option<&str> { self.r#type.as_deref() }
258	/// Set the entry's type hint.
259	pub fn set_type_hint<T: Into<SmolStr>>(&mut self, r#type: Option<T>) {
260		self.r#type = r#type.map(Into::into);
261	}
262}
263
264impl fmt::Debug for Entry {
265	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
266		f.debug_struct("Entry")
267			.field("name", &self.name)
268			.field("type", option_debug(self.type_hint().as_ref()))
269			.field("value", &self.value)
270			.finish()
271	}
272}
273impl fmt::Display for Entry {
274	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
275		if let Some(name) = &self.name {
276			write!(f, "{}=", IdentDisplay(name))?;
277		}
278		if let Some(r#type) = &self.r#type {
279			write!(f, "({})", IdentDisplay(r#type))?;
280		}
281		fmt::Display::fmt(&self.value, f)
282	}
283}
284impl<K: Into<SmolStr>, V: Into<Value>> From<(K, V)> for Entry {
285	fn from((name, value): (K, V)) -> Self { Self::new_prop(name.into(), value.into()) }
286}
287impl<V: Into<Value>> From<V> for Entry {
288	fn from(value: V) -> Self { Self::new_value(value.into()) }
289}
290
291/// A numeric or textual key to index an [`Entry`] in a [`Node`].
292#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
293pub enum EntryKey<'key> {
294	/// Index the nth value
295	Value(usize),
296	/// Index the right-most property with this name
297	Property(&'key str),
298}
299impl EntryKey<'_> {
300	fn seek<T>(
301		self,
302		mut iter: impl DoubleEndedIterator<Item = T>,
303		name: impl Fn(&T) -> Option<&str>,
304	) -> Option<T> {
305		match self {
306			EntryKey::Value(key) => iter.filter(|ent| name(ent).is_none()).nth(key),
307			// right-most property overrides value
308			EntryKey::Property(key) => iter.rfind(|ent| name(ent) == Some(key)),
309		}
310	}
311}
312impl From<usize> for EntryKey<'_> {
313	fn from(value: usize) -> Self { Self::Value(value) }
314}
315impl<'key> From<&'key str> for EntryKey<'key> {
316	fn from(value: &'key str) -> Self { Self::Property(value) }
317}
318
319/// A `value`, a piece of information.
320#[derive(Default, Clone, PartialEq, Eq, Hash)]
321pub enum Value {
322	/// A textual value, quoted or unquoted.
323	String(SmolStr),
324	/// A numeric value, including `#nan`, `#inf`, and `#-inf`.
325	Number(Number),
326	/// A boolean value, `#true` or `#false`.
327	Bool(bool),
328	/// The `#null` value.
329	#[default]
330	Null,
331}
332
333/// value util
334macro_rules! valutil {
335	(into $self:ident $ty:ident) => {
336		match $self {
337			Value::$ty(value) => Ok(value),
338			this => Err(this),
339		}
340	};
341	(to $self:ident $ty:ident) => {
342		match $self {
343			Value::$ty(value) => Some(value),
344			_ => None,
345		}
346	};
347}
348
349impl Value {
350	/// Extract the value of a string, or return self on fallback.
351	#[expect(clippy::missing_errors_doc, reason = "not an error")]
352	pub fn into_string(self) -> Result<SmolStr, Self> { valutil!(into self String) }
353	/// Extract the value of a number, or return self on fallback.
354	#[expect(clippy::missing_errors_doc, reason = "not an error")]
355	pub fn into_number(self) -> Result<Number, Self> { valutil!(into self Number) }
356	/// Reference the value of a string, or `None`.
357	pub fn to_string(&self) -> Option<&SmolStr> { valutil!(to self String) }
358	/// Reference the value of a number, or `None`.
359	pub fn to_number(&self) -> Option<&Number> { valutil!(to self Number) }
360	/// Reference the value of a bool, or `None`.
361	pub fn to_bool(&self) -> Option<bool> { valutil!(to self Bool).copied() }
362	/// Check if the value is a string.
363	pub fn is_string(&self) -> bool { matches!(self, Self::String(_)) }
364	/// Check if the value is a number.
365	pub fn is_number(&self) -> bool { matches!(self, Self::Number(_)) }
366	/// Check if the value is a bool.
367	pub fn is_bool(&self) -> bool { matches!(self, Self::Bool(_)) }
368	/// Check if the value is null.
369	pub fn is_null(&self) -> bool { matches!(self, Self::Null) }
370}
371
372impl fmt::Debug for Value {
373	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
374		match self {
375			Self::String(value) => fmt::Debug::fmt(&**value, f),
376			Self::Number(value) => fmt::Debug::fmt(value, f),
377			Self::Bool(true) => f.write_str("#true"),
378			Self::Bool(false) => f.write_str("#false"),
379			Self::Null => f.write_str("#null"),
380		}
381	}
382}
383impl fmt::Display for Value {
384	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
385		match self {
386			Self::String(value) => fmt::Display::fmt(&IdentDisplay(value), f),
387			Self::Number(value) => fmt::Display::fmt(value, f),
388			Self::Bool(true) => f.write_str("#true"),
389			Self::Bool(false) => f.write_str("#false"),
390			Self::Null => f.write_str("#null"),
391		}
392	}
393}
394impl<'text> From<&'text str> for Value {
395	fn from(value: &'text str) -> Self { Self::String(value.into()) }
396}
397impl From<SmolStr> for Value {
398	fn from(value: SmolStr) -> Self { Self::String(value) }
399}
400impl From<String> for Value {
401	fn from(value: String) -> Self { Self::String(value.into()) }
402}
403impl<T: Into<Number>> From<T> for Value {
404	fn from(value: T) -> Self { Self::Number(value.into()) }
405}
406impl From<bool> for Value {
407	fn from(value: bool) -> Self { Self::Bool(value) }
408}
409impl From<()> for Value {
410	fn from((): ()) -> Self { Self::Null }
411}
412impl<T: Into<Value>> From<Option<T>> for Value {
413	fn from(value: Option<T>) -> Self {
414		match value {
415			Some(v) => v.into(),
416			_ => Self::Null,
417		}
418	}
419}
420
421/// An arbitrary-size document-formatted number,
422/// can convert to/from standard number types as needed.
423// implementations in `number` module
424#[derive(Clone, Debug, PartialEq, Eq, Hash)]
425pub struct Number(SmolStr);
426
427/// A document-stream event.
428#[derive(Clone, PartialEq, Eq, Hash)]
429pub enum Event {
430	/// Beginning of a node, terminated by a matching `End` event.
431	Node {
432		/// Optional node type hint.
433		r#type: Option<SmolStr>,
434		/// Node name.
435		name: SmolStr,
436	},
437	/// A property or value on the `Node`.
438	Entry(Entry),
439	/// The beginning of the `Node`s children.
440	Children,
441	/// The end of the `Node` and its children block.
442	End,
443}
444
445impl fmt::Debug for Event {
446	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
447		match self {
448			Self::Node { r#type, name } => f
449				.debug_struct("Node")
450				.field("type", option_debug(r#type.as_ref()))
451				.field("name", name)
452				.finish(),
453			Self::Entry(entry) => fmt::Debug::fmt(entry, f),
454			Self::Children => f.write_str("Children"),
455			Self::End => f.write_str("End"),
456		}
457	}
458}