use std::{ fmt::Display, ops::{Index, IndexMut}, str::FromStr, }; use crate::{parser, KdlDocument, KdlEntry, KdlError, KdlIdentifier, KdlValue}; /// Represents an individual KDL /// [`Node`](https://github.com/kdl-org/kdl/blob/main/SPEC.md#node) inside a /// KDL Document. #[derive(Debug, Clone, PartialEq)] pub struct KdlNode { pub(crate) leading: Option, pub(crate) ty: Option, pub(crate) name: KdlIdentifier, // TODO: consider using `hashlink` for this instead, later. pub(crate) entries: Vec, pub(crate) before_children: Option, pub(crate) children: Option, pub(crate) trailing: Option, } impl KdlNode { /// Creates a new KdlNode with a given name. pub fn new(name: impl Into) -> Self { Self { name: name.into(), leading: None, ty: None, entries: Vec::new(), before_children: None, children: None, trailing: None, } } /// Gets this node's name. pub fn name(&self) -> &KdlIdentifier { &self.name } /// Gets a mutable reference to this node's name. pub fn name_mut(&mut self) -> &mut KdlIdentifier { &mut self.name } /// Sets this node's name. pub fn set_name(&mut self, name: impl Into) { self.name = name.into(); } /// Gets the node's type identifier, if any. pub fn ty(&self) -> Option<&KdlIdentifier> { self.ty.as_ref() } /// Gets a mutable reference to the node's type identifier. pub fn ty_mut(&mut self) -> &mut Option { &mut self.ty } /// Sets the node's type identifier. pub fn set_ty(&mut self, ty: impl Into) { self.ty = Some(ty.into()); } /// Returns a reference to this node's entries (arguments and properties). pub fn entries(&self) -> &[KdlEntry] { &self.entries } /// Returns a mutable reference to this node's entries (arguments and /// properties). pub fn entries_mut(&mut self) -> &mut Vec { &mut self.entries } /// Gets leading text (whitespace, comments) for this node. pub fn leading(&self) -> Option<&str> { self.leading.as_deref() } /// Sets leading text (whitespace, comments) for this node. pub fn set_leading(&mut self, leading: impl Into) { self.leading = Some(leading.into()); } /// Gets text (whitespace, comments) right before the children block's starting `{`. pub fn before_children(&self) -> Option<&str> { self.before_children.as_deref() } /// Gets text (whitespace, comments) right before the children block's starting `{`. pub fn set_before_children(&mut self, before: impl Into) { self.before_children = Some(before.into()); } /// Gets trailing text (whitespace, comments) for this node. pub fn trailing(&self) -> Option<&str> { self.trailing.as_deref() } /// Sets trailing text (whitespace, comments) for this node. pub fn set_trailing(&mut self, trailing: impl Into) { self.trailing = Some(trailing.into()); } /// Clears leading and trailing text (whitespace, comments), as well as /// the space before the children block, if any. Individual entries and /// their formatting will be preserved. pub fn clear_fmt(&mut self) { self.leading = None; self.trailing = None; self.before_children = None; } /// Fetches an entry by key. Number keys will look up arguments, strings /// will look up properties. pub fn get(&self, key: impl Into) -> Option<&KdlEntry> { self.get_impl(key.into()) } fn get_impl(&self, key: NodeKey) -> Option<&KdlEntry> { match key { NodeKey::Key(key) => { for entry in &self.entries { if entry.name.is_some() && entry.name.as_ref().map(|i| i.value()) == Some(key.value()) { return Some(entry); } } None } NodeKey::Index(idx) => { let mut current_idx = 0; for entry in &self.entries { if entry.name.is_none() { if current_idx == idx { return Some(entry); } current_idx += 1; if current_idx > idx + 1 { return None; } } } None } } } /// Fetches a mutable referene to an entry by key. Number keys will look /// up arguments, strings will look up properties. pub fn get_mut(&mut self, key: impl Into) -> Option<&mut KdlEntry> { self.get_mut_impl(key.into()) } fn get_mut_impl(&mut self, key: NodeKey) -> Option<&mut KdlEntry> { match key { NodeKey::Key(key) => { for entry in &mut self.entries { if entry.name.is_some() && entry.name.as_ref().map(|i| i.value()) == Some(key.value()) { return Some(entry); } } None } NodeKey::Index(idx) => { let mut current_idx = 0; for entry in &mut self.entries { if entry.name.is_none() { if current_idx >= idx { return Some(entry); } current_idx += 1; if current_idx >= idx { return None; } } } None } } } /// Inserts an entry into this node. If an entry already exists with the /// same key, it will be replaced and the previous entry will be returned. /// /// Numerical keys will insert arguments, string keys will insert /// properties. pub fn insert( &mut self, key: impl Into, entry: impl Into, ) -> Option { self.insert_impl(key.into(), entry.into()) } fn insert_impl(&mut self, key: NodeKey, mut entry: KdlEntry) -> Option { match key { NodeKey::Key(ref key_val) => { if entry.name.is_none() { entry.name = Some(key_val.clone()); } if entry.name.as_ref().map(|i| i.value()) != Some(key_val.value()) { panic!("Property name mismatch"); } if let Some(existing) = self.get_mut(key) { std::mem::swap(existing, &mut entry); Some(entry) } else { self.entries.push(entry); None } } NodeKey::Index(idx) => { if entry.name.is_some() { panic!("Cannot insert property with name under a numerical key"); } if let Some(existing) = self.get_mut(key) { std::mem::swap(existing, &mut entry); Some(entry) } else { let mut current_idx = 0; for existing in &mut self.entries { if existing.name.is_none() { if current_idx == idx { std::mem::swap(existing, &mut entry); return Some(entry); } current_idx += 1; if current_idx >= idx { break; } } } if idx > current_idx { panic!( "Insertion index (is {}) should be <= len (is {})", idx, current_idx ); } else { self.entries.push(entry); None } } } } } /// Removes an entry from this node. If an entry already exists with the /// same key, it will be returned. /// /// Numerical keys will remove arguments, string keys will remove /// properties. pub fn remove(&mut self, key: impl Into) -> Option { self.remove_impl(key.into()) } fn remove_impl(&mut self, key: NodeKey) -> Option { match key { NodeKey::Key(key) => { for (idx, entry) in self.entries.iter_mut().enumerate() { if entry.name.is_some() && entry.name.as_ref() == Some(&key) { return Some(self.entries.remove(idx)); } } None } NodeKey::Index(idx) => { let mut current_idx = 0; for entry in &mut self.entries { if entry.name.is_none() { if current_idx == idx { return Some(self.entries.remove(idx)); } current_idx += 1; if current_idx >= idx { return None; } } } None } } } /// Shorthand for `self.entries_mut().push(entry)`. pub fn push(&mut self, entry: impl Into) { self.entries.push(entry.into()); } /// Shorthand for `self.entries_mut().clear()` pub fn clear_entries(&mut self) { self.entries.clear(); } /// Returns a reference to this node's children, if any. pub fn children(&self) -> Option<&KdlDocument> { self.children.as_ref() } /// Returns a mutable reference to this node's children, if any. pub fn children_mut(&mut self) -> &mut Option { &mut self.children } /// Sets the KdlDocument representing this node's children. pub fn set_children(&mut self, children: KdlDocument) { self.children = Some(children); } /// Removes this node's children completely. pub fn clear_children(&mut self) { self.children = None; } /// Returns a mutable reference to this node's children [`KdlDocument`], /// creating one first if one does not already exist. pub fn ensure_children(&mut self) -> &mut KdlDocument { if self.children.is_none() { self.children = Some(KdlDocument::new()); } self.children_mut().as_mut().unwrap() } /// Auto-formats this node and its contents. pub fn fmt(&mut self) { self.leading = None; self.trailing = None; for entry in &mut self.entries { entry.fmt(); } if let Some(children) = &mut self.children { children.fmt(); } } } /// Represents a [`KdlNode`]'s entry key. #[derive(Debug, Clone, PartialEq)] pub enum NodeKey { /// Key for a node property entry. Key(KdlIdentifier), /// Index for a node argument entry (positional value). Index(usize), } impl From<&str> for NodeKey { fn from(key: &str) -> Self { NodeKey::Key(key.into()) } } impl From for NodeKey { fn from(key: String) -> Self { NodeKey::Key(key.into()) } } impl From for NodeKey { fn from(key: usize) -> Self { NodeKey::Index(key) } } impl Index for KdlNode { type Output = KdlValue; fn index(&self, index: usize) -> &Self::Output { self.get(index).expect("Argument out of range.").value() } } impl IndexMut for KdlNode { fn index_mut(&mut self, index: usize) -> &mut Self::Output { self.get_mut(index) .expect("Argument out of range.") .value_mut() } } impl Index<&str> for KdlNode { type Output = KdlValue; fn index(&self, key: &str) -> &Self::Output { self.get(key).expect("No such property.").value() } } impl IndexMut<&str> for KdlNode { fn index_mut(&mut self, key: &str) -> &mut Self::Output { if self.get(key).is_none() { self.push((key, KdlValue::Null)); } self.get_mut(key) .expect("Something went wrong.") .value_mut() } } impl FromStr for KdlNode { type Err = KdlError; fn from_str(input: &str) -> Result { parser::parse(input, parser::node) } } impl Display for KdlNode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.stringify(f, 0) } } impl KdlNode { pub(crate) fn stringify( &self, f: &mut std::fmt::Formatter<'_>, indent: usize, ) -> std::fmt::Result { if let Some(leading) = &self.leading { write!(f, "{}", leading)?; } else { write!(f, "{:indent$}", "", indent = indent)?; } if let Some(ty) = &self.ty { write!(f, "({})", ty)?; } write!(f, "{}", self.name)?; let mut space_before_children = true; for entry in &self.entries { if entry.leading.is_none() { write!(f, " ")?; } write!(f, "{}", entry)?; space_before_children = entry.trailing.is_none(); } if let Some(children) = &self.children { if space_before_children { write!(f, " ")?; } write!(f, "{{")?; if children.leading.is_none() { writeln!(f)?; } children.stringify(f, indent + 4)?; write!(f, "}}")?; } if let Some(trailing) = &self.trailing { write!(f, "{}", trailing)?; } Ok(()) } } #[cfg(test)] mod test { use super::*; #[test] fn indexing() { let mut node = KdlNode::new("foo"); node.push("bar"); node["foo"] = 1.into(); assert_eq!(node[0], "bar".into()); assert_eq!(node["foo"], 1.into()); node[0] = false.into(); node["foo"] = KdlValue::Null; assert_eq!(node[0], false.into()); assert_eq!(node["foo"], KdlValue::Null); } }