From 4f984d7bff8bc822d26cc6c9b1956d22125e67c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Sun, 15 Dec 2024 19:22:34 -0800 Subject: [PATCH] feat(api): update the KdlNode and KdlDocument APIs to be more Vec-like Fixes: https://github.com/kdl-org/kdl-rs/issues/81 --- src/document.rs | 24 ++- src/lib.rs | 6 +- src/node.rs | 475 +++++++++++++++++++++++++++++++----------------- 3 files changed, 330 insertions(+), 175 deletions(-) diff --git a/src/document.rs b/src/document.rs index ceaa53d..a130ab1 100644 --- a/src/document.rs +++ b/src/document.rs @@ -108,7 +108,7 @@ impl KdlDocument { self.get(name).and_then(|node| node.get(0)) } - /// Gets the all node arguments (value) of the first child node with a + /// Returns an iterator of the all node arguments (value) of the first child node with a /// matching name. This is a shorthand utility for cases where a document /// is being used as a key/value store and the value is expected to be /// array-ish. @@ -127,16 +127,18 @@ impl KdlDocument { /// ```rust /// # use kdl::{KdlDocument, KdlValue}; /// # let doc: KdlDocument = "foo 1 2 3\nbar #false".parse().unwrap(); - /// assert_eq!(doc.get_args("foo"), vec![&1.into(), &2.into(), &3.into()]); + /// assert_eq!( + /// doc.iter_args("foo").collect::>(), + /// vec![&1.into(), &2.into(), &3.into()] + /// ); /// ``` - pub fn get_args(&self, name: &str) -> Vec<&KdlValue> { + pub fn iter_args(&self, name: &str) -> impl Iterator { self.get(name) .map(|n| n.entries()) .unwrap_or_default() .iter() .filter(|e| e.name().is_none()) .map(|e| e.value()) - .collect() } /// Gets a mutable reference to the first argument (value) of the first @@ -164,9 +166,12 @@ impl KdlDocument { /// ```rust /// # use kdl::{KdlDocument, KdlValue}; /// # let doc: KdlDocument = "foo {\n - 1\n - 2\n - #false\n}".parse().unwrap(); - /// assert_eq!(doc.get_dash_args("foo"), vec![&1.into(), &2.into(), &false.into()]); + /// assert_eq!( + /// doc.iter_dash_args("foo").collect::>(), + /// vec![&1.into(), &2.into(), &false.into()] + /// ); /// ``` - pub fn get_dash_args(&self, name: &str) -> Vec<&KdlValue> { + pub fn iter_dash_args(&self, name: &str) -> impl Iterator { self.get(name) .and_then(|n| n.children()) .map(|doc| doc.nodes()) @@ -174,7 +179,6 @@ impl KdlDocument { .iter() .filter(|e| e.name().value() == "-") .filter_map(|e| e.get(0)) - .collect() } /// Returns a reference to this document's child nodes. @@ -441,8 +445,8 @@ second_node /* This time, the comment is here */ param=153 { /* block comment */ inline /*comment*/ here another /-comment there - - + + after some whitespace trailing /* multiline */ trailing // single line @@ -480,7 +484,7 @@ final;"; assert_eq!(doc.get_arg("foo"), Some(&1.into())); assert_eq!( - doc.get_dash_args("foo"), + doc.iter_dash_args("foo").collect::>(), vec![&1.into(), &2.into(), &"three".into()] ); assert_eq!( diff --git a/src/lib.rs b/src/lib.rs index af026a1..6f76209 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ //! `kdl` is a "document-oriented" parser and API for the [KDL Document //! Language](https://kdl.dev), a node-based, human-friendly configuration and //! serialization format. -//! +//! //! Unlike serde-based implementations, this crate preserves formatting when //! editing, as well as when inserting or changing values with custom //! formatting. This is most useful when working with human-maintained KDL @@ -19,7 +19,7 @@ //! ## Example //! //! ```rust -//! use kdl::KdlDocument; +//! use kdl::{KdlDocument, KdlValue}; //! //! let doc_str = r#" //! hello 1 2 3 @@ -35,7 +35,7 @@ //! let doc: KdlDocument = doc_str.parse().expect("failed to parse KDL"); //! //! assert_eq!( -//! doc.get_args("hello"), +//! doc.iter_args("hello").collect::>(), //! vec![&1.into(), &2.into(), &3.into()] //! ); //! diff --git a/src/node.rs b/src/node.rs index b7eb33a..09bced1 100644 --- a/src/node.rs +++ b/src/node.rs @@ -1,6 +1,8 @@ use std::{ + cmp::Ordering, fmt::Display, ops::{Index, IndexMut}, + slice::{Iter, IterMut}, str::FromStr, }; @@ -123,16 +125,6 @@ impl KdlNode { &mut self.entries } - /// Length of this node when rendered as a string. - pub fn len(&self) -> usize { - format!("{}", self).len() - } - - /// Returns true if this node is completely empty (including whitespace). - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - /// 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. @@ -157,12 +149,6 @@ impl KdlNode { } } - /// Gets a value by key. Number keys will look up arguments, strings will - /// look up properties. - pub fn get(&self, key: impl Into) -> Option<&KdlValue> { - self.entry_impl(key.into()).map(|e| &e.value) - } - /// Fetches an entry by key. Number keys will look up arguments, strings /// will look up properties. pub fn entry(&self, key: impl Into) -> Option<&KdlEntry> { @@ -197,12 +183,6 @@ impl KdlNode { } } - /// Fetches a mutable referene to an value by key. Number keys will look - /// up arguments, strings will look up properties. - pub fn get_mut(&mut self, key: impl Into) -> Option<&mut KdlValue> { - self.entry_mut_impl(key.into()).map(|e| &mut e.value) - } - /// Fetches a mutable referene to an entry by key. Number keys will look /// up arguments, strings will look up properties. pub fn entry_mut(&mut self, key: impl Into) -> Option<&mut KdlEntry> { @@ -237,111 +217,6 @@ impl KdlNode { } } - /// Inserts an entry into this node. If an entry already exists with the - /// same string 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.entry_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"); - } - let mut current_idx = 0; - for (idx_existing, existing) in self.entries.iter().enumerate() { - if existing.name.is_none() { - if current_idx == idx { - self.entries.insert(idx_existing, entry); - return None; - } - current_idx += 1; - } - } - 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().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 (idx_entry, entry) in self.entries.iter().enumerate() { - if entry.name.is_none() { - if current_idx == idx { - return Some(self.entries.remove(idx_entry)); - } - current_idx += 1; - } - } - panic!( - "removal index (is {}) should be < number of index entries (is {})", - idx, current_idx - ); - } - } - } - - /// 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() @@ -450,48 +325,324 @@ impl KdlNode { } } } +} - // TODO(@zkat): These should all be moved into the query module, instead - // of being model methods. - // - // /// Queries this Node according to the KQL - // query language, /// returning an iterator over all matching nodes. pub - // fn query_all( &self, query: impl IntoKdlQuery, ) -> - // Result, KdlDiagnostic> { let q = - // query.into_query()?; Ok(KdlQueryIterator::new(Some(self), None, q)) - // } +// Query language +// impl KdlNode { +// /// Queries this Node according to the KQL +// query language, /// returning an iterator over all matching nodes. pub +// fn query_all( &self, query: impl IntoKdlQuery, ) -> +// Result, KdlDiagnostic> { let q = +// query.into_query()?; Ok(KdlQueryIterator::new(Some(self), None, q)) +// } + +// /// Queries this Node according to the KQL query language, +// /// returning the first match, if any. +// pub fn query(&self, query: impl IntoKdlQuery) -> Result, KdlDiagnostic> { +// Ok(self.query_all(query)?.next()) +// } + +// /// Queries this Node according to the KQL query language, +// /// picking the first match, and calling `.get(key)` on it, if the query +// /// succeeded. +// pub fn query_get( +// &self, +// query: impl IntoKdlQuery, +// key: impl Into, +// ) -> Result, KdlDiagnostic> { +// Ok(self.query(query)?.and_then(|node| node.get(key))) +// } + +// /// Queries this Node according to the KQL query language, +// /// returning an iterator over all matching nodes, returning the requested +// /// field from each of those nodes and filtering out nodes that don't have +// /// it. +// pub fn query_get_all( +// &self, +// query: impl IntoKdlQuery, +// key: impl Into, +// ) -> Result, KdlDiagnostic> { +// let key: NodeKey = key.into(); +// Ok(self +// .query_all(query)? +// .filter_map(move |node| node.get(key.clone()))) +// } +//} + +/// Iterator for entries in a node, including properties. +#[derive(Debug)] +pub struct EntryIter<'a>(Iter<'a, KdlEntry>); +impl<'a> Iterator for EntryIter<'a> { + type Item = &'a KdlEntry; + + fn next(&mut self) -> Option { + self.0.next() + } +} - // /// Queries this Node according to the KQL query language, - // /// returning the first match, if any. - // pub fn query(&self, query: impl IntoKdlQuery) -> Result, KdlDiagnostic> { - // Ok(self.query_all(query)?.next()) - // } +/// Mutable iterator for entries in a node, including properties. +#[derive(Debug)] +pub struct EntryIterMut<'a>(IterMut<'a, KdlEntry>); +impl<'a> Iterator for EntryIterMut<'a> { + type Item = &'a mut KdlEntry; + + fn next(&mut self) -> Option { + self.0.next() + } +} + +/// Iterator for child nodes for this node. Empty if there is no children block. +#[derive(Debug)] +pub struct ChildrenIter<'a>(Option>); +impl<'a> Iterator for ChildrenIter<'a> { + type Item = &'a KdlNode; + + fn next(&mut self) -> Option { + self.0.as_mut().and_then(|x| x.next()) + } +} + +/// Iterator for child nodes for this node. Empty if there is no children block. +#[derive(Debug)] +pub struct ChildrenIterMut<'a>(Option>); +impl<'a> Iterator for ChildrenIterMut<'a> { + type Item = &'a mut KdlNode; + + fn next(&mut self) -> Option { + self.0.as_mut().and_then(|x| x.next()) + } +} - // /// Queries this Node according to the KQL query language, - // /// picking the first match, and calling `.get(key)` on it, if the query - // /// succeeded. - // pub fn query_get( - // &self, - // query: impl IntoKdlQuery, +// Vec-style APIs +impl KdlNode { + /// Returns an iterator over the node's entries, including properties. + pub fn iter(&self) -> EntryIter<'_> { + EntryIter(self.entries.iter()) + } + + /// Returns a mutable iterator over the node's entries, including properties. + pub fn iter_mut(&mut self) -> EntryIterMut<'_> { + EntryIterMut(self.entries.iter_mut()) + } + + /// Returns an iterator over the node's children, if any. Nodes without + /// children will return an empty iterator. + pub fn iter_children(&self) -> ChildrenIter<'_> { + ChildrenIter(self.children.as_ref().map(|x| x.nodes.iter())) + } + + /// Returns a mutable iterator over the node's children, if any. Nodes + /// without children will return an empty iterator. + pub fn iter_children_mut(&mut self) -> ChildrenIterMut<'_> { + ChildrenIterMut(self.children.as_mut().map(|x| x.nodes.iter_mut())) + } + + /// Gets a value by key. Number keys will look up arguments, strings will + /// look up properties. + pub fn get(&self, key: impl Into) -> Option<&KdlValue> { + self.entry_impl(key.into()).map(|e| &e.value) + } + + /// Fetches a mutable referene to an value by key. Number keys will look + /// up arguments, strings will look up properties. + pub fn get_mut(&mut self, key: impl Into) -> Option<&mut KdlValue> { + self.entry_mut_impl(key.into()).map(|e| &mut e.value) + } + + /// Number of entries in this node. + pub fn len(&self) -> usize { + self.entries.len() + } + + /// Returns true if this node is completely empty (including whitespace). + pub fn is_empty(&self) -> bool { + self.entries.is_empty() + } + + /// Shorthand for `self.entries_mut().clear()` + pub fn clear(&mut self) { + self.entries.clear(); + } + + /// Shorthand for `self.entries_mut().push(entry)`. + pub fn push(&mut self, entry: impl Into) { + self.entries.push(entry.into()); + } + + /// Inserts an entry into this node. If an entry already exists with the + /// same string 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.entry_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"); + } + let mut current_idx = 0; + for (idx_existing, existing) in self.entries.iter().enumerate() { + if existing.name.is_none() { + if current_idx == idx { + self.entries.insert(idx_existing, entry); + return None; + } + current_idx += 1; + } + } + if idx > current_idx { + panic!( + "Insertion index (is {}) should be <= len (is {})", + idx, current_idx + ); + } else { + self.entries.push(entry); + None + } + } + } + } + + // pub fn replace( + // &mut self, // key: impl Into, - // ) -> Result, KdlDiagnostic> { - // Ok(self.query(query)?.and_then(|node| node.get(key))) + // entry: impl Into, + // ) -> Option { + // self.replace_impl(key.into(), entry.into()) // } - // /// Queries this Node according to the KQL query language, - // /// returning an iterator over all matching nodes, returning the requested - // /// field from each of those nodes and filtering out nodes that don't have - // /// it. - // pub fn query_get_all( - // &self, - // query: impl IntoKdlQuery, - // key: impl Into, - // ) -> Result, KdlDiagnostic> { - // let key: NodeKey = key.into(); - // Ok(self - // .query_all(query)? - // .filter_map(move |node| node.get(key.clone()))) + // fn replace_impl(&mut self, key: NodeKey, mut entry: KdlEntry) -> Option { + // todo!(); + // // 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.entry_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"); + // // } + // // let mut current_idx = 0; + // // for (idx_existing, existing) in self.entries.iter().enumerate() { + // // if existing.name.is_none() { + // // if current_idx == idx { + // // self.entries.replace(idx_existing, entry); + // // return None; + // // } + // // current_idx += 1; + // // } + // // } + // // 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().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 (idx_entry, entry) in self.entries.iter().enumerate() { + if entry.name.is_none() { + if current_idx == idx { + return Some(self.entries.remove(idx_entry)); + } + current_idx += 1; + } + } + panic!( + "removal index (is {}) should be < number of index entries (is {})", + idx, current_idx + ); + } + } + } + + /// Retains only the elements specified by the predicate. + pub fn retain(&mut self, keep: F) + where + F: FnMut(&KdlEntry) -> bool, + { + self.entries.retain(keep) + } + + /// Sorts the slice with a comparison function, preserving initial order of + /// equal elements. + pub fn sort_by(&mut self, compare: F) + where + F: FnMut(&KdlEntry, &KdlEntry) -> Ordering, + { + self.entries.sort_by(compare) + } + + /// Sorts the slice with a key extraction function, preserving initial order + /// of equal elements. + pub fn sort_by_key(&mut self, f: F) + where + F: FnMut(&KdlEntry) -> K, + K: Ord, + { + self.entries.sort_by_key(f) + } } /// Represents a [`KdlNode`]'s entry key.