feat(api): update the KdlNode and KdlDocument APIs to be more Vec-like (#101)

Fixes: https://github.com/kdl-org/kdl-rs/issues/81
This commit is contained in:
Kat Marchán 2024-12-19 20:08:53 -08:00 committed by GitHub
parent 4734b0601b
commit 683e87a142
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 329 additions and 174 deletions

View File

@ -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<&KdlValue>>(),
/// 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<Item = &KdlValue> {
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<&KdlValue>>(),
/// 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<Item = &KdlValue> {
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<&KdlValue>>(),
vec![&1.into(), &2.into(), &"three".into()]
);
assert_eq!(

View File

@ -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<&KdlValue>>(),
//! vec![&1.into(), &2.into(), &3.into()]
//! );
//!

View File

@ -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<NodeKey>) -> 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<NodeKey>) -> 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<NodeKey>) -> 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<NodeKey>) -> 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<NodeKey>,
entry: impl Into<KdlEntry>,
) -> Option<KdlEntry> {
self.insert_impl(key.into(), entry.into())
}
fn insert_impl(&mut self, key: NodeKey, mut entry: KdlEntry) -> Option<KdlEntry> {
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<NodeKey>) -> Option<KdlEntry> {
self.remove_impl(key.into())
}
fn remove_impl(&mut self, key: NodeKey) -> Option<KdlEntry> {
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<KdlEntry>) {
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<KdlQueryIterator<'_>, 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<KdlQueryIterator<'_>, 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<Option<&KdlNode>, KdlDiagnostic> {
// Ok(self.query_all(query)?.next())
// }
// /// Queries this Node according to the KQL query language,
// /// returning the first match, if any.
// pub fn query(&self, query: impl IntoKdlQuery) -> Result<Option<&KdlNode>, 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,
// /// 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<NodeKey>,
// ) -> Result<Option<&KdlValue>, 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<NodeKey>,
// ) -> Result<impl Iterator<Item = &KdlValue>, 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::Item> {
self.0.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::Item> {
self.0.next()
}
}
/// Iterator for child nodes for this node. Empty if there is no children block.
#[derive(Debug)]
pub struct ChildrenIter<'a>(Option<Iter<'a, KdlNode>>);
impl<'a> Iterator for ChildrenIter<'a> {
type Item = &'a KdlNode;
fn next(&mut self) -> Option<Self::Item> {
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<IterMut<'a, KdlNode>>);
impl<'a> Iterator for ChildrenIterMut<'a> {
type Item = &'a mut KdlNode;
fn next(&mut self) -> Option<Self::Item> {
self.0.as_mut().and_then(|x| x.next())
}
}
// 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<NodeKey>) -> 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<NodeKey>) -> 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<KdlEntry>) {
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<NodeKey>,
entry: impl Into<KdlEntry>,
) -> Option<KdlEntry> {
self.insert_impl(key.into(), entry.into())
}
fn insert_impl(&mut self, key: NodeKey, mut entry: KdlEntry) -> Option<KdlEntry> {
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<NodeKey>,
// ) -> Result<Option<&KdlValue>, KdlDiagnostic> {
// Ok(self.query(query)?.and_then(|node| node.get(key)))
// entry: impl Into<KdlEntry>,
// ) -> Option<KdlEntry> {
// 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<NodeKey>,
// ) -> Result<impl Iterator<Item = &KdlValue>, 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<KdlEntry> {
// 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<NodeKey>) -> Option<KdlEntry> {
self.remove_impl(key.into())
}
fn remove_impl(&mut self, key: NodeKey) -> Option<KdlEntry> {
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<F>(&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<F>(&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<K, F>(&mut self, f: F)
where
F: FnMut(&KdlEntry) -> K,
K: Ord,
{
self.entries.sort_by_key(f)
}
}
/// Represents a [`KdlNode`]'s entry key.