mirror of https://github.com/kdl-org/kdl-rs.git
fix(compliance): pull in spec test suite and fix issues (#40)
This commit includes a whole bunch of fixes, some of which are significant changes to the parser and some related functionality. But I consider all changes to be bugfixes because they were compliance failures.
This commit is contained in:
parent
71df712c0c
commit
58a40fdf48
26
README.md
26
README.md
|
|
@ -96,6 +96,32 @@ Error:
|
||||||
╰────
|
╰────
|
||||||
help: Floating point numbers must be base 10, and have numbers after the decimal point.
|
help: Floating point numbers must be base 10, and have numbers after the decimal point.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Quirks
|
||||||
|
|
||||||
|
#### Properties
|
||||||
|
|
||||||
|
Multiple properties with the same name are allowed, and all duplicated
|
||||||
|
**will be preserved**, meaning those documents will correctly round-trip.
|
||||||
|
When using `node.get()`/`node["key"]` & company, the _last_ property with
|
||||||
|
that name's value will be returned.
|
||||||
|
|
||||||
|
#### Numbers
|
||||||
|
|
||||||
|
KDL itself does not specify a particular representation for numbers and
|
||||||
|
accepts just about anything valid, no matter how large and how small. This
|
||||||
|
means a few things:
|
||||||
|
|
||||||
|
* Numbers without a decimal point are interpreted as u64.
|
||||||
|
* Numbers with a decimal point are interpreted as f64.
|
||||||
|
* Floating point numbers that evaluate to f64::INFINITY or
|
||||||
|
f64::NEG_INFINITY or NaN will be represented as such in the values,
|
||||||
|
instead of the original numbers.
|
||||||
|
* A similar restriction applies to overflowed u64 values.
|
||||||
|
* The original _representation_ of these numbers will be preserved, unless
|
||||||
|
you `doc.fmt()`, in which case the original representation will be
|
||||||
|
thrown away and the actual value will be used when serializing.
|
||||||
|
|
||||||
### License
|
### License
|
||||||
|
|
||||||
The code in this repository is covered by [the Apache-2.0
|
The code in this repository is covered by [the Apache-2.0
|
||||||
|
|
|
||||||
|
|
@ -188,7 +188,12 @@ impl KdlDocument {
|
||||||
/// Auto-formats this Document, making everything nice while preserving
|
/// Auto-formats this Document, making everything nice while preserving
|
||||||
/// comments.
|
/// comments.
|
||||||
pub fn fmt(&mut self) {
|
pub fn fmt(&mut self) {
|
||||||
self.fmt_impl(0);
|
self.fmt_impl(0, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats the document and removes all comments from the document.
|
||||||
|
pub fn fmt_no_comments(&mut self) {
|
||||||
|
self.fmt_impl(0, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -199,15 +204,20 @@ impl Display for KdlDocument {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KdlDocument {
|
impl KdlDocument {
|
||||||
pub(crate) fn fmt_impl(&mut self, indent: usize) {
|
pub(crate) fn fmt_impl(&mut self, indent: usize, no_comments: bool) {
|
||||||
if let Some(s) = self.leading.as_mut() {
|
if let Some(s) = self.leading.as_mut() {
|
||||||
crate::fmt::fmt_leading(s, indent);
|
crate::fmt::fmt_leading(s, indent, no_comments);
|
||||||
|
}
|
||||||
|
let mut has_nodes = false;
|
||||||
|
for node in &mut self.nodes {
|
||||||
|
has_nodes = true;
|
||||||
|
node.fmt_impl(indent, no_comments);
|
||||||
}
|
}
|
||||||
if let Some(s) = self.trailing.as_mut() {
|
if let Some(s) = self.trailing.as_mut() {
|
||||||
crate::fmt::fmt_trailing(s);
|
crate::fmt::fmt_trailing(s, no_comments);
|
||||||
}
|
if !has_nodes {
|
||||||
for node in &mut self.nodes {
|
s.push('\n');
|
||||||
node.fmt_impl(indent);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
10
src/entry.rs
10
src/entry.rs
|
|
@ -124,12 +124,12 @@ impl Display for KdlEntry {
|
||||||
if let Some(leading) = &self.leading {
|
if let Some(leading) = &self.leading {
|
||||||
write!(f, "{}", leading)?;
|
write!(f, "{}", leading)?;
|
||||||
}
|
}
|
||||||
if let Some(ty) = &self.ty {
|
|
||||||
write!(f, "({})", ty)?;
|
|
||||||
}
|
|
||||||
if let Some(name) = &self.name {
|
if let Some(name) = &self.name {
|
||||||
write!(f, "{}=", name)?;
|
write!(f, "{}=", name)?;
|
||||||
}
|
}
|
||||||
|
if let Some(ty) = &self.ty {
|
||||||
|
write!(f, "({})", ty)?;
|
||||||
|
}
|
||||||
if let Some(repr) = &self.value_repr {
|
if let Some(repr) = &self.value_repr {
|
||||||
write!(f, "{}", repr)?;
|
write!(f, "{}", repr)?;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -165,7 +165,7 @@ impl FromStr for KdlEntry {
|
||||||
type Err = KdlError;
|
type Err = KdlError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
parser::parse(s, parser::entry_with_node_space)
|
parser::parse(s, parser::entry_with_trailing)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -217,7 +217,7 @@ mod test {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let entry: KdlEntry = " \\\n (\"m\\\"eh\")\"foo\"=0xDEADbeef\t\\\n".parse()?;
|
let entry: KdlEntry = " \\\n \"foo\"=(\"m\\\"eh\")0xDEADbeef\t\\\n".parse()?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
entry,
|
entry,
|
||||||
KdlEntry {
|
KdlEntry {
|
||||||
|
|
|
||||||
28
src/fmt.rs
28
src/fmt.rs
|
|
@ -1,30 +1,34 @@
|
||||||
pub(crate) fn fmt_leading(leading: &mut String, indent: usize) {
|
pub(crate) fn fmt_leading(leading: &mut String, indent: usize, no_comments: bool) {
|
||||||
if leading.is_empty() {
|
if leading.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let comments = crate::parser::parse(leading.trim(), crate::parser::leading_comments)
|
|
||||||
.expect("invalid leading text");
|
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
for line in comments {
|
if !no_comments {
|
||||||
let trimmed = line.trim();
|
let comments = crate::parser::parse(leading.trim(), crate::parser::leading_comments)
|
||||||
if !trimmed.is_empty() {
|
.expect("invalid leading text");
|
||||||
result.push_str(&format!("{:indent$}{}\n", "", trimmed, indent = indent));
|
for line in comments {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
if !trimmed.is_empty() {
|
||||||
|
result.push_str(&format!("{:indent$}{}\n", "", trimmed, indent = indent));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.push_str(&format!("{:indent$}", "", indent = indent));
|
result.push_str(&format!("{:indent$}", "", indent = indent));
|
||||||
*leading = result;
|
*leading = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn fmt_trailing(decor: &mut String) {
|
pub(crate) fn fmt_trailing(decor: &mut String, no_comments: bool) {
|
||||||
if decor.is_empty() {
|
if decor.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
*decor = decor.trim().to_string();
|
*decor = decor.trim().to_string();
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
let comments = crate::parser::parse(decor, crate::parser::trailing_comments)
|
if !no_comments {
|
||||||
.expect("invalid trailing text");
|
let comments = crate::parser::parse(decor, crate::parser::trailing_comments)
|
||||||
for comment in comments {
|
.expect("invalid trailing text");
|
||||||
result.push_str(comment);
|
for comment in comments {
|
||||||
|
result.push_str(comment);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
*decor = result;
|
*decor = result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use crate::{parser, KdlError};
|
||||||
|
|
||||||
/// Represents a KDL
|
/// Represents a KDL
|
||||||
/// [Identifier](https://github.com/kdl-org/kdl/blob/main/SPEC.md#identifier).
|
/// [Identifier](https://github.com/kdl-org/kdl/blob/main/SPEC.md#identifier).
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct KdlIdentifier {
|
pub struct KdlIdentifier {
|
||||||
pub(crate) value: String,
|
pub(crate) value: String,
|
||||||
pub(crate) repr: Option<String>,
|
pub(crate) repr: Option<String>,
|
||||||
|
|
@ -199,9 +199,6 @@ mod test {
|
||||||
let invalid = "\"x";
|
let invalid = "\"x";
|
||||||
assert!(invalid.parse::<KdlIdentifier>().is_err());
|
assert!(invalid.parse::<KdlIdentifier>().is_err());
|
||||||
|
|
||||||
let invalid = "r#\"foo\"#";
|
|
||||||
assert!(invalid.parse::<KdlIdentifier>().is_err());
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
27
src/lib.rs
27
src/lib.rs
|
|
@ -94,6 +94,32 @@
|
||||||
//! ╰────
|
//! ╰────
|
||||||
//! help: Floating point numbers must be base 10, and have numbers after the decimal point.
|
//! help: Floating point numbers must be base 10, and have numbers after the decimal point.
|
||||||
//! ```
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Quirks
|
||||||
|
//!
|
||||||
|
//! ### Properties
|
||||||
|
//!
|
||||||
|
//! Multiple properties with the same name are allowed, and all duplicated
|
||||||
|
//! **will be preserved**, meaning those documents will correctly round-trip.
|
||||||
|
//! When using `node.get()`/`node["key"]` & company, the _last_ property with
|
||||||
|
//! that name's value will be returned.
|
||||||
|
//!
|
||||||
|
//! ### Numbers
|
||||||
|
//!
|
||||||
|
//! KDL itself does not specify a particular representation for numbers and
|
||||||
|
//! accepts just about anything valid, no matter how large and how small. This
|
||||||
|
//! means a few things:
|
||||||
|
//!
|
||||||
|
//! * Numbers without a decimal point are interpreted as [`u64`].
|
||||||
|
//! * Numbers with a decimal point are interpreted as [`f64`].
|
||||||
|
//! * Floating point numbers that evaluate to [`f64::INFINITY`] or
|
||||||
|
//! [`f64::NEG_INFINITY`] or NaN will be represented as such in the values,
|
||||||
|
//! instead of the original numbers.
|
||||||
|
//! * A similar restriction applies to overflowed [`u64`] values.
|
||||||
|
//! * The original _representation_ of these numbers will be preserved, unless
|
||||||
|
//! you [`KdlDocument::fmt`] in which case the original representation will be
|
||||||
|
//! thrown away and the actual value will be used when serializing.
|
||||||
|
//!
|
||||||
//! ## License
|
//! ## License
|
||||||
//!
|
//!
|
||||||
//! The code in this repository is covered by [the Apache-2.0
|
//! The code in this repository is covered by [the Apache-2.0
|
||||||
|
|
@ -102,6 +128,7 @@
|
||||||
#![deny(missing_debug_implementations, nonstandard_style)]
|
#![deny(missing_debug_implementations, nonstandard_style)]
|
||||||
#![warn(missing_docs, unreachable_pub, rust_2018_idioms, unreachable_pub)]
|
#![warn(missing_docs, unreachable_pub, rust_2018_idioms, unreachable_pub)]
|
||||||
#![cfg_attr(test, deny(warnings))]
|
#![cfg_attr(test, deny(warnings))]
|
||||||
|
#![doc(html_favicon_url = "https://kdl.dev/favicon.ico")]
|
||||||
#![doc(html_logo_url = "https://kdl.dev/logo.svg")]
|
#![doc(html_logo_url = "https://kdl.dev/logo.svg")]
|
||||||
|
|
||||||
pub use document::*;
|
pub use document::*;
|
||||||
|
|
|
||||||
34
src/node.rs
34
src/node.rs
|
|
@ -134,14 +134,15 @@ impl KdlNode {
|
||||||
fn get_impl(&self, key: NodeKey) -> Option<&KdlEntry> {
|
fn get_impl(&self, key: NodeKey) -> Option<&KdlEntry> {
|
||||||
match key {
|
match key {
|
||||||
NodeKey::Key(key) => {
|
NodeKey::Key(key) => {
|
||||||
|
let mut current = None;
|
||||||
for entry in &self.entries {
|
for entry in &self.entries {
|
||||||
if entry.name.is_some()
|
if entry.name.is_some()
|
||||||
&& entry.name.as_ref().map(|i| i.value()) == Some(key.value())
|
&& entry.name.as_ref().map(|i| i.value()) == Some(key.value())
|
||||||
{
|
{
|
||||||
return Some(entry);
|
current = Some(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
current
|
||||||
}
|
}
|
||||||
NodeKey::Index(idx) => {
|
NodeKey::Index(idx) => {
|
||||||
let mut current_idx = 0;
|
let mut current_idx = 0;
|
||||||
|
|
@ -170,14 +171,15 @@ impl KdlNode {
|
||||||
fn get_mut_impl(&mut self, key: NodeKey) -> Option<&mut KdlEntry> {
|
fn get_mut_impl(&mut self, key: NodeKey) -> Option<&mut KdlEntry> {
|
||||||
match key {
|
match key {
|
||||||
NodeKey::Key(key) => {
|
NodeKey::Key(key) => {
|
||||||
|
let mut current = None;
|
||||||
for entry in &mut self.entries {
|
for entry in &mut self.entries {
|
||||||
if entry.name.is_some()
|
if entry.name.is_some()
|
||||||
&& entry.name.as_ref().map(|i| i.value()) == Some(key.value())
|
&& entry.name.as_ref().map(|i| i.value()) == Some(key.value())
|
||||||
{
|
{
|
||||||
return Some(entry);
|
current = Some(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
current
|
||||||
}
|
}
|
||||||
NodeKey::Index(idx) => {
|
NodeKey::Index(idx) => {
|
||||||
let mut current_idx = 0;
|
let mut current_idx = 0;
|
||||||
|
|
@ -340,7 +342,12 @@ impl KdlNode {
|
||||||
|
|
||||||
/// Auto-formats this node and its contents.
|
/// Auto-formats this node and its contents.
|
||||||
pub fn fmt(&mut self) {
|
pub fn fmt(&mut self) {
|
||||||
self.fmt_impl(0);
|
self.fmt_impl(0, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Auto-formats this node and its contents, stripping comments.
|
||||||
|
pub fn fmt_no_comments(&mut self) {
|
||||||
|
self.fmt_impl(0, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -421,12 +428,12 @@ impl Display for KdlNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KdlNode {
|
impl KdlNode {
|
||||||
pub(crate) fn fmt_impl(&mut self, indent: usize) {
|
pub(crate) fn fmt_impl(&mut self, indent: usize, no_comments: bool) {
|
||||||
if let Some(s) = self.leading.as_mut() {
|
if let Some(s) = self.leading.as_mut() {
|
||||||
crate::fmt::fmt_leading(s, indent);
|
crate::fmt::fmt_leading(s, indent, no_comments);
|
||||||
}
|
}
|
||||||
if let Some(s) = self.trailing.as_mut() {
|
if let Some(s) = self.trailing.as_mut() {
|
||||||
crate::fmt::fmt_trailing(s);
|
crate::fmt::fmt_trailing(s, no_comments);
|
||||||
if s.starts_with(';') {
|
if s.starts_with(';') {
|
||||||
s.remove(0);
|
s.remove(0);
|
||||||
}
|
}
|
||||||
|
|
@ -446,7 +453,7 @@ impl KdlNode {
|
||||||
entry.fmt();
|
entry.fmt();
|
||||||
}
|
}
|
||||||
if let Some(children) = self.children.as_mut() {
|
if let Some(children) = self.children.as_mut() {
|
||||||
children.fmt_impl(indent + 4);
|
children.fmt_impl(indent + 4, no_comments);
|
||||||
if let Some(leading) = children.leading.as_mut() {
|
if let Some(leading) = children.leading.as_mut() {
|
||||||
leading.push('\n');
|
leading.push('\n');
|
||||||
}
|
}
|
||||||
|
|
@ -511,6 +518,11 @@ mod test {
|
||||||
assert_eq!(node.name(), &"\"node\"".parse()?);
|
assert_eq!(node.name(), &"\"node\"".parse()?);
|
||||||
assert_eq!(node.get(0), Some(&"0xDEADbeef".parse()?));
|
assert_eq!(node.get(0), Some(&"0xDEADbeef".parse()?));
|
||||||
|
|
||||||
|
r#"
|
||||||
|
node "test" {
|
||||||
|
link "blah" anything="self"
|
||||||
|
}"#
|
||||||
|
.parse::<KdlNode>()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -528,5 +540,9 @@ mod test {
|
||||||
|
|
||||||
assert_eq!(node[0], false.into());
|
assert_eq!(node[0], false.into());
|
||||||
assert_eq!(node["foo"], KdlValue::Null);
|
assert_eq!(node["foo"], KdlValue::Null);
|
||||||
|
|
||||||
|
node.entries_mut().push(KdlEntry::new_prop("x", 1));
|
||||||
|
node.entries_mut().push(KdlEntry::new_prop("x", 2));
|
||||||
|
assert_eq!(&node["x"], &2.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ use std::ops::RangeTo;
|
||||||
|
|
||||||
use crate::nom_compat::{many0, many1, many_till};
|
use crate::nom_compat::{many0, many1, many_till};
|
||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
use nom::bytes::complete::{tag, take_until, take_until1, take_while, take_while_m_n};
|
use nom::bytes::complete::{tag, take_until, take_while, take_while_m_n};
|
||||||
use nom::character::complete::{anychar, char, none_of, one_of};
|
use nom::character::complete::{anychar, char, none_of, one_of};
|
||||||
use nom::combinator::{all_consuming, cut, eof, map, map_opt, map_res, opt, recognize};
|
use nom::combinator::{all_consuming, cut, eof, map, map_opt, map_res, opt, peek, recognize};
|
||||||
use nom::error::{context, ParseError};
|
use nom::error::{context, ParseError};
|
||||||
use nom::sequence::{delimited, preceded, terminated, tuple};
|
use nom::sequence::{delimited, preceded, terminated, tuple};
|
||||||
use nom::{Finish, IResult, Offset, Parser, Slice};
|
use nom::{Finish, IResult, Offset, Parser, Slice};
|
||||||
|
|
@ -81,7 +81,7 @@ pub(crate) fn node(input: &str) -> IResult<&str, KdlNode, KdlParseError<&str>> {
|
||||||
cut(recognize(preceded(
|
cut(recognize(preceded(
|
||||||
many0(node_space),
|
many0(node_space),
|
||||||
alt((
|
alt((
|
||||||
terminated(recognize(opt(tag(";"))), alt((linespace, eof))),
|
terminated(recognize(tag(";")), opt(alt((linespace, eof)))),
|
||||||
alt((newline, single_line_comment, eof)),
|
alt((newline, single_line_comment, eof)),
|
||||||
)),
|
)),
|
||||||
))),
|
))),
|
||||||
|
|
@ -109,7 +109,7 @@ pub(crate) fn node(input: &str) -> IResult<&str, KdlNode, KdlParseError<&str>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn identifier(input: &str) -> IResult<&str, KdlIdentifier, KdlParseError<&str>> {
|
pub(crate) fn identifier(input: &str) -> IResult<&str, KdlIdentifier, KdlParseError<&str>> {
|
||||||
alt((plain_identifier, quoted_identifier))(input)
|
alt((quoted_identifier, plain_identifier))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn leading_comments(input: &str) -> IResult<&str, Vec<&str>, KdlParseError<&str>> {
|
pub(crate) fn leading_comments(input: &str) -> IResult<&str, Vec<&str>, KdlParseError<&str>> {
|
||||||
|
|
@ -145,22 +145,38 @@ fn plain_identifier(input: &str) -> IResult<&str, KdlIdentifier, KdlParseError<&
|
||||||
take_while_m_n(1, 1, KdlIdentifier::is_initial_char),
|
take_while_m_n(1, 1, KdlIdentifier::is_initial_char),
|
||||||
cut(take_while(KdlIdentifier::is_identifier_char)),
|
cut(take_while(KdlIdentifier::is_identifier_char)),
|
||||||
))(input).map_err(|e| set_details(e, start, Some("invalid identifier character"), Some("See https://github.com/kdl-org/kdl/blob/main/SPEC.md#identifier for an explanation of valid KDL identifiers.")))?;
|
))(input).map_err(|e| set_details(e, start, Some("invalid identifier character"), Some("See https://github.com/kdl-org/kdl/blob/main/SPEC.md#identifier for an explanation of valid KDL identifiers.")))?;
|
||||||
|
match name {
|
||||||
|
"false" | "true" | "null" => {
|
||||||
|
return Err(nom::Err::Error(KdlParseError {
|
||||||
|
input,
|
||||||
|
context: Some("non-keyword identifier"),
|
||||||
|
len: name.len(),
|
||||||
|
label: Some("reserved keyword"),
|
||||||
|
help: Some("Reserved keywords cannot be used as identifiers."),
|
||||||
|
kind: None,
|
||||||
|
touched: false,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
let mut ident = KdlIdentifier::from(name);
|
let mut ident = KdlIdentifier::from(name);
|
||||||
ident.set_repr(name);
|
ident.set_repr(name);
|
||||||
Ok((input, ident))
|
Ok((input, ident))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn quoted_identifier(input: &str) -> IResult<&str, KdlIdentifier, KdlParseError<&str>> {
|
fn quoted_identifier(input: &str) -> IResult<&str, KdlIdentifier, KdlParseError<&str>> {
|
||||||
let (input, (raw, val)) = string(input)?;
|
let (input, (raw, val)) = alt((string, raw_string))(input)?;
|
||||||
let mut ident = KdlIdentifier::from(val.as_string().unwrap());
|
let mut ident = KdlIdentifier::from(val.as_string().unwrap());
|
||||||
ident.set_repr(raw);
|
ident.set_repr(raw);
|
||||||
Ok((input, ident))
|
Ok((input, ident))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn entry_with_node_space(input: &str) -> IResult<&str, KdlEntry, KdlParseError<&str>> {
|
pub(crate) fn entry_with_trailing(input: &str) -> IResult<&str, KdlEntry, KdlParseError<&str>> {
|
||||||
let (input, leading) = recognize(many0(node_space))(input)?;
|
let (input, mut leading) = recognize(many0(node_space))(input)?;
|
||||||
let leading = if leading.is_empty() { " " } else { leading };
|
if leading.is_empty() {
|
||||||
let (input, mut entry) = entry(input)?;
|
leading = " ";
|
||||||
|
};
|
||||||
|
let (input, mut entry) = alt((property, argument))(input)?;
|
||||||
let (input, trailing) = recognize(many0(node_space))(input)?;
|
let (input, trailing) = recognize(many0(node_space))(input)?;
|
||||||
entry.set_leading(leading);
|
entry.set_leading(leading);
|
||||||
entry.set_trailing(trailing);
|
entry.set_trailing(trailing);
|
||||||
|
|
@ -168,25 +184,32 @@ pub(crate) fn entry_with_node_space(input: &str) -> IResult<&str, KdlEntry, KdlP
|
||||||
}
|
}
|
||||||
|
|
||||||
fn entry(input: &str) -> IResult<&str, KdlEntry, KdlParseError<&str>> {
|
fn entry(input: &str) -> IResult<&str, KdlEntry, KdlParseError<&str>> {
|
||||||
alt((property, argument))(input)
|
let (input, leading) = recognize(many1(node_space))(input)?;
|
||||||
|
let (input, mut entry) = alt((property, argument))(input)?;
|
||||||
|
entry.set_leading(leading);
|
||||||
|
Ok((input, entry))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn entry_maybe_space(input: &str) -> IResult<&str, KdlEntry, KdlParseError<&str>> {
|
||||||
|
let (input, leading) = recognize(many0(node_space))(input)?;
|
||||||
|
let (input, mut entry) = alt((property, argument))(input)?;
|
||||||
|
entry.set_leading(leading);
|
||||||
|
Ok((input, entry))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn property(input: &str) -> IResult<&str, KdlEntry, KdlParseError<&str>> {
|
fn property(input: &str) -> IResult<&str, KdlEntry, KdlParseError<&str>> {
|
||||||
let (input, leading) = recognize(many0(node_space))(input)?;
|
|
||||||
let (input, ty) = opt(annotation)(input)?;
|
|
||||||
let (input, name) = identifier(input)?;
|
let (input, name) = identifier(input)?;
|
||||||
let (input, _) = context("'=' after property name", tag("="))(input)?;
|
let (input, _) = context("'=' after property name", tag("="))(input)?;
|
||||||
|
let (input, ty) = opt(annotation)(input)?;
|
||||||
let (input, (raw, value)) = context("property value", cut(value))(input).map_err(|e| set_details(e, input, Some("invalid value"), Some("Please refer to https://github.com/kdl-org/kdl/blob/main/SPEC.md#value for valid KDL value syntaxes.")))?;
|
let (input, (raw, value)) = context("property value", cut(value))(input).map_err(|e| set_details(e, input, Some("invalid value"), Some("Please refer to https://github.com/kdl-org/kdl/blob/main/SPEC.md#value for valid KDL value syntaxes.")))?;
|
||||||
let mut entry = KdlEntry::new_prop(name, value);
|
let mut entry = KdlEntry::new_prop(name, value);
|
||||||
entry.ty = ty;
|
entry.ty = ty;
|
||||||
entry.set_leading(leading);
|
|
||||||
entry.set_trailing("");
|
entry.set_trailing("");
|
||||||
entry.set_value_repr(raw);
|
entry.set_value_repr(raw);
|
||||||
Ok((input, entry))
|
Ok((input, entry))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn argument(input: &str) -> IResult<&str, KdlEntry, KdlParseError<&str>> {
|
fn argument(input: &str) -> IResult<&str, KdlEntry, KdlParseError<&str>> {
|
||||||
let (input, leading) = recognize(many0(node_space))(input)?;
|
|
||||||
let (input, ty) = opt(annotation)(input)?;
|
let (input, ty) = opt(annotation)(input)?;
|
||||||
let (input, (raw, value)) = if ty.is_some() {
|
let (input, (raw, value)) = if ty.is_some() {
|
||||||
context("valid value", cut(value))(input)
|
context("valid value", cut(value))(input)
|
||||||
|
|
@ -195,7 +218,6 @@ fn argument(input: &str) -> IResult<&str, KdlEntry, KdlParseError<&str>> {
|
||||||
}?;
|
}?;
|
||||||
let mut entry = KdlEntry::new(value);
|
let mut entry = KdlEntry::new(value);
|
||||||
entry.ty = ty;
|
entry.ty = ty;
|
||||||
entry.set_leading(leading);
|
|
||||||
entry.set_trailing("");
|
entry.set_trailing("");
|
||||||
entry.set_value_repr(raw);
|
entry.set_value_repr(raw);
|
||||||
Ok((input, entry))
|
Ok((input, entry))
|
||||||
|
|
@ -329,7 +351,7 @@ fn multi_line_comment(input: &str) -> IResult<&str, &str, KdlParseError<&str>> {
|
||||||
tag("/*"),
|
tag("/*"),
|
||||||
context("comment block body", cut(commented_block)),
|
context("comment block body", cut(commented_block)),
|
||||||
))(input)
|
))(input)
|
||||||
.map_err(|e| set_details(e, input, Some("comment"), None))
|
.map_err(|e| set_details(e, input, Some("comment"), Some("multi-line comments must start with /* and be terminated with a matching */. They may be nested, but their */ must match.")))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `commented-block := '*/' | (multi-line-comment | '*' | '/' | [^*/]+) commented-block`
|
/// `commented-block := '*/' | (multi-line-comment | '*' | '/' | [^*/]+) commented-block`
|
||||||
|
|
@ -337,7 +359,12 @@ fn commented_block(input: &str) -> IResult<&str, &str, KdlParseError<&str>> {
|
||||||
alt((
|
alt((
|
||||||
tag("*/"),
|
tag("*/"),
|
||||||
terminated(
|
terminated(
|
||||||
alt((multi_line_comment, take_until1("*/"), tag("*"), tag("/"))),
|
alt((
|
||||||
|
multi_line_comment,
|
||||||
|
tag("*"),
|
||||||
|
tag("/"),
|
||||||
|
recognize(many_till(anychar, peek(alt((tag("*"), tag("/")))))),
|
||||||
|
)),
|
||||||
commented_block,
|
commented_block,
|
||||||
),
|
),
|
||||||
))(input)
|
))(input)
|
||||||
|
|
@ -348,7 +375,7 @@ fn node_slashdash(input: &str) -> IResult<&str, &str, KdlParseError<&str>> {
|
||||||
tag("/-"),
|
tag("/-"),
|
||||||
context(
|
context(
|
||||||
"node following a slashdash",
|
"node following a slashdash",
|
||||||
cut(alt((recognize(entry), recognize(children)))),
|
cut(alt((recognize(entry_maybe_space), recognize(children)))),
|
||||||
),
|
),
|
||||||
))(input)
|
))(input)
|
||||||
.map_err(|e| set_details(e, input, Some("slashdash"), None))
|
.map_err(|e| set_details(e, input, Some("slashdash"), None))
|
||||||
|
|
@ -430,6 +457,7 @@ fn escape(input: &str) -> IResult<&str, char, KdlParseError<&str>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unicode(input: &str) -> IResult<&str, char, KdlParseError<&str>> {
|
fn unicode(input: &str) -> IResult<&str, char, KdlParseError<&str>> {
|
||||||
|
// TODO: This should only accept up to 0x10FFFF.
|
||||||
map_opt(
|
map_opt(
|
||||||
map_res(
|
map_res(
|
||||||
take_while_m_n(1, 6, |c: char| c.is_ascii_hexdigit()),
|
take_while_m_n(1, 6, |c: char| c.is_ascii_hexdigit()),
|
||||||
|
|
@ -448,7 +476,7 @@ fn raw_string(input: &str) -> IResult<&str, (String, KdlValue), KdlParseError<&s
|
||||||
raw.push('r');
|
raw.push('r');
|
||||||
let (input, hashes) = recognize(many0(char('#')))(input)?;
|
let (input, hashes) = recognize(many0(char('#')))(input)?;
|
||||||
raw.push_str(hashes);
|
raw.push_str(hashes);
|
||||||
let (input, _) = cut(char('"'))(input)?;
|
let (input, _) = char('"')(input)?;
|
||||||
raw.push('"');
|
raw.push('"');
|
||||||
let close = format!("\"{}", hashes);
|
let close = format!("\"{}", hashes);
|
||||||
let (input, value) = take_until(&close[..])(input)?;
|
let (input, value) = take_until(&close[..])(input)?;
|
||||||
|
|
@ -528,6 +556,7 @@ fn hexadecimal(input: &str) -> IResult<&str, (String, KdlValue), KdlParseError<&
|
||||||
)),
|
)),
|
||||||
move |(raw_body, hex): (&str, &str)| {
|
move |(raw_body, hex): (&str, &str)| {
|
||||||
raw.push_str(raw_body);
|
raw.push_str(raw_body);
|
||||||
|
// TODO: Failure in case of int overflow!
|
||||||
i64::from_str_radix(&str::replace(hex, "_", ""), 16)
|
i64::from_str_radix(&str::replace(hex, "_", ""), 16)
|
||||||
.map(|x| x * sign)
|
.map(|x| x * sign)
|
||||||
.map(|x| (raw.clone(), KdlValue::Base16(x)))
|
.map(|x| (raw.clone(), KdlValue::Base16(x)))
|
||||||
|
|
@ -664,6 +693,10 @@ mod comment_tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn multi_line() {
|
fn multi_line() {
|
||||||
assert_eq!(comment("/* Hello world */"), Ok(("", "/* Hello world */")));
|
assert_eq!(comment("/* Hello world */"), Ok(("", "/* Hello world */")));
|
||||||
|
assert_eq!(
|
||||||
|
comment("/* Hello /* world */ blah */"),
|
||||||
|
Ok(("", "/* Hello /* world */ blah */"))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -752,6 +785,8 @@ mod value_tests {
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
let (_, n) = node("node 0x0123_4567_89ab_cdef").expect("failed to parse node");
|
||||||
|
assert_eq!(&n[0], &KdlValue::Base16(0x0123456789abcdef));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
value("0x123_4567"),
|
value("0x123_4567"),
|
||||||
Ok(("", ("0x123_4567".into(), KdlValue::Base16(0x1234567))))
|
Ok(("", ("0x123_4567".into(), KdlValue::Base16(0x1234567))))
|
||||||
|
|
|
||||||
35
src/value.rs
35
src/value.rs
|
|
@ -151,11 +151,23 @@ impl Display for KdlValue {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::RawString(_) => self.write_raw_string(f),
|
Self::RawString(_) => self.write_raw_string(f),
|
||||||
Self::String(string) => write!(f, "{:?}", string),
|
Self::String(_) => self.write_string(f),
|
||||||
Self::Base2(value) => write!(f, "0b{:b}", value),
|
Self::Base2(value) => write!(f, "0b{:b}", value),
|
||||||
Self::Base8(value) => write!(f, "0o{:o}", value),
|
Self::Base8(value) => write!(f, "0o{:o}", value),
|
||||||
Self::Base10(value) => write!(f, "{}", value),
|
Self::Base10(value) => write!(f, "{:?}", value),
|
||||||
Self::Base10Float(value) => write!(f, "{}", value),
|
Self::Base10Float(value) => write!(
|
||||||
|
f,
|
||||||
|
"{:?}",
|
||||||
|
if value == &f64::INFINITY {
|
||||||
|
f64::MAX
|
||||||
|
} else if value == &f64::NEG_INFINITY {
|
||||||
|
-f64::MAX
|
||||||
|
} else if value.is_nan() {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
*value
|
||||||
|
}
|
||||||
|
),
|
||||||
Self::Base16(value) => write!(f, "0x{:x}", value),
|
Self::Base16(value) => write!(f, "0x{:x}", value),
|
||||||
Self::Bool(value) => write!(f, "{}", value),
|
Self::Bool(value) => write!(f, "{}", value),
|
||||||
Self::Null => write!(f, "null"),
|
Self::Null => write!(f, "null"),
|
||||||
|
|
@ -164,6 +176,23 @@ impl Display for KdlValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KdlValue {
|
impl KdlValue {
|
||||||
|
fn write_string(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let string = self.as_string().unwrap();
|
||||||
|
write!(f, "\"")?;
|
||||||
|
for char in string.chars() {
|
||||||
|
match char {
|
||||||
|
'\\' | '"' => write!(f, "\\{}", char)?,
|
||||||
|
'\n' => write!(f, "\\n")?,
|
||||||
|
'\r' => write!(f, "\\r")?,
|
||||||
|
'\t' => write!(f, "\\t")?,
|
||||||
|
'\u{08}' => write!(f, "\\b")?,
|
||||||
|
'\u{0C}' => write!(f, "\\f")?,
|
||||||
|
_ => write!(f, "{}", char)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(f, "\"")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
fn write_raw_string(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn write_raw_string(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let raw = self.as_string().unwrap();
|
let raw = self.as_string().unwrap();
|
||||||
let mut consecutive = 0usize;
|
let mut consecutive = 0usize;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
# Full Document Test Cases
|
||||||
|
|
||||||
|
The `input` folder contains test cases for KDL parsers. The `expected_kdl`
|
||||||
|
folder contains files with the same name as those in `input` with the expected
|
||||||
|
output after being run through the parser and printed out again. If there's no
|
||||||
|
file in `expected_kdl` with a name corresponding to one in `input` it
|
||||||
|
indicates that parsing for that case should fail.
|
||||||
|
|
||||||
|
## Translation Rules
|
||||||
|
|
||||||
|
By necessity, the files in `expected_kdl` are not identical to their
|
||||||
|
corresponding inputs. They are instead pretty-printed according to the
|
||||||
|
following rules:
|
||||||
|
|
||||||
|
* All comments removed
|
||||||
|
* Extra empty lines removed except for a newline after the last node
|
||||||
|
* All nodes should be reformatted without escaped newlines
|
||||||
|
* Node fields should be `identifier <values> <properties> <children only if non-empty>`
|
||||||
|
* All values and all children must be in the same order as they were defined.
|
||||||
|
* Properties must be in _alphabetical order_ and separated by a single space.
|
||||||
|
* All strings must be represented as regular strings, with appropriate escapes
|
||||||
|
for invalid bare characters. That means that raw strings must be converted
|
||||||
|
to plain strings, and escaped.
|
||||||
|
* Any literal newlines or other ascii escape characters in escaped strings
|
||||||
|
replaced with their escape sequences.
|
||||||
|
* All identifiers must be unquoted unless they _must_ be quoted. That means
|
||||||
|
`"foo"` becomes `foo`, and `"foo bar"` stays that way.
|
||||||
|
* Any duplicate properties must be removed, with only the rightmost one
|
||||||
|
remaining. This also means duplicate properties must be allowed.
|
||||||
|
* 4 space indents
|
||||||
|
* All numbers must be converted to their simplest decimal representation. That
|
||||||
|
means that hex, octal, and binary must all be converted to decimals. All
|
||||||
|
floats must be represented using `E` notation, with a single digit left of
|
||||||
|
the decimal point if the float is less than 1. While parsers are required to
|
||||||
|
_consume_ different number syntaxes, they are under no obligation to
|
||||||
|
represent numbers in any particular way.
|
||||||
|
|
||||||
|
Data may be manipulated as you wish in order to output the expected KDL. This
|
||||||
|
test suite verifies the ability to **parse**, not specific quirks about
|
||||||
|
internal representations.
|
||||||
|
|
||||||
|
## What to do if a test fails for you
|
||||||
|
|
||||||
|
This test suite was originally designed for a pre-1.0 version of the KDL
|
||||||
|
specification. If you encounter a failure, it's likely that the test suite
|
||||||
|
will need to be updated, rather than your parser itself. This test suite is
|
||||||
|
NOT AUTHORITATIVE. If this test suite disagrees with the KDL spec in any way,
|
||||||
|
the most desirable resolution is to send a PR to this repository to fix the
|
||||||
|
test itself. Likewise, if you think a test succeeded but should not have,
|
||||||
|
please send a PR.
|
||||||
|
|
||||||
|
If you think the disagreement is due to a genuine error or oversight in the
|
||||||
|
KDL specification, please open an issue explaining the matter and the change
|
||||||
|
will be considered for the next version of the KDL spec.
|
||||||
|
|
||||||
|
## Credit
|
||||||
|
|
||||||
|
This test suite was extracted from
|
||||||
|
[`kdl4j`](https://github.com/hkolbeck/kdl4j), the original Java
|
||||||
|
implementation of KDL, with huge thanks to
|
||||||
|
[@hkolbeck](https://github.com/hkolbeck) for authoring them!
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
fs,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use kdl::{KdlDocument, KdlError, KdlIdentifier, KdlValue};
|
||||||
|
use miette::IntoDiagnostic;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn spec_compliance() -> miette::Result<()> {
|
||||||
|
let input = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.join("tests")
|
||||||
|
.join("test_cases")
|
||||||
|
.join("input");
|
||||||
|
for test_name in fs::read_dir(&input).into_diagnostic()? {
|
||||||
|
let test_path = test_name.into_diagnostic()?.path();
|
||||||
|
println!(
|
||||||
|
"parsing {}:",
|
||||||
|
PathBuf::from(test_path.file_name().unwrap()).display()
|
||||||
|
);
|
||||||
|
let src = normalize_line_endings(fs::read_to_string(&test_path).into_diagnostic()?);
|
||||||
|
println!("src: {}", src);
|
||||||
|
let res: Result<KdlDocument, KdlError> = src.parse();
|
||||||
|
validate_res(res, &test_path)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_res(res: Result<KdlDocument, KdlError>, path: &Path) -> miette::Result<()> {
|
||||||
|
let file_name = path.file_name().unwrap();
|
||||||
|
let expected_dir = path
|
||||||
|
.parent()
|
||||||
|
.unwrap()
|
||||||
|
.parent()
|
||||||
|
.unwrap()
|
||||||
|
.join("expected_kdl");
|
||||||
|
let expected_path = expected_dir.join(file_name);
|
||||||
|
let underscored = expected_dir.join(&format!("_{}", PathBuf::from(file_name).display()));
|
||||||
|
if expected_path.exists() {
|
||||||
|
let doc = res?;
|
||||||
|
let expected =
|
||||||
|
normalize_line_endings(fs::read_to_string(&expected_path).into_diagnostic()?);
|
||||||
|
println!("expected: {}", expected);
|
||||||
|
let stringified = stringify_to_expected(doc);
|
||||||
|
println!("stringified: {}", stringified);
|
||||||
|
assert_eq!(stringified, expected);
|
||||||
|
} else if underscored.exists() {
|
||||||
|
println!(
|
||||||
|
"skipped reserialization for {}",
|
||||||
|
PathBuf::from(file_name).display()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
assert!(res.is_err(), "parse should not have succeeded");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_line_endings(src: String) -> String {
|
||||||
|
src.replace("\r\n", "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stringify_to_expected(mut doc: KdlDocument) -> String {
|
||||||
|
doc.fmt_no_comments();
|
||||||
|
normalize_numbers(&mut doc);
|
||||||
|
normalize_strings(&mut doc);
|
||||||
|
dedupe_props(&mut doc);
|
||||||
|
remove_empty_children(&mut doc);
|
||||||
|
doc.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_numbers(doc: &mut KdlDocument) {
|
||||||
|
for node in doc.nodes_mut() {
|
||||||
|
for entry in node.entries_mut() {
|
||||||
|
if let Some(value) = entry.value().as_i64() {
|
||||||
|
*entry.value_mut() = KdlValue::Base10(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(children) = node.children_mut() {
|
||||||
|
normalize_numbers(children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_strings(doc: &mut KdlDocument) {
|
||||||
|
for node in doc.nodes_mut() {
|
||||||
|
for entry in node.entries_mut() {
|
||||||
|
if let Some(value) = entry.value().as_string() {
|
||||||
|
*entry.value_mut() = KdlValue::String(value.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(children) = node.children_mut() {
|
||||||
|
normalize_strings(children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dedupe_props(doc: &mut KdlDocument) {
|
||||||
|
for node in doc.nodes_mut() {
|
||||||
|
let mut props = HashMap::<KdlIdentifier, Vec<usize>>::new();
|
||||||
|
for (idx, entry) in node.entries_mut().iter_mut().enumerate() {
|
||||||
|
if let Some(name) = entry.name() {
|
||||||
|
if !props.contains_key(name) {
|
||||||
|
props.insert(name.clone(), Vec::new());
|
||||||
|
}
|
||||||
|
if let Some(indices) = props.get_mut(name) {
|
||||||
|
indices.push(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let new_entries = node
|
||||||
|
.entries()
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(idx, entry)| {
|
||||||
|
if let Some(name) = entry.name() {
|
||||||
|
if let Some(indices) = props.get(name) {
|
||||||
|
if &idx == indices.last().unwrap() {
|
||||||
|
return Some(entry.clone());
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(entry.clone())
|
||||||
|
});
|
||||||
|
*node.entries_mut() = new_entries.collect();
|
||||||
|
if let Some(children) = node.children_mut() {
|
||||||
|
dedupe_props(children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_empty_children(doc: &mut KdlDocument) {
|
||||||
|
for node in doc.nodes_mut() {
|
||||||
|
let maybe_children = node.children_mut();
|
||||||
|
if maybe_children.is_some() && maybe_children.as_ref().unwrap().nodes().is_empty() {
|
||||||
|
*maybe_children = None;
|
||||||
|
}
|
||||||
|
if let Some(children) = maybe_children {
|
||||||
|
remove_empty_children(children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node 1e-10
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node 1 1.0 10000000000.0 1e-10 1 7 2 "arg" "arg\\\\" true false null
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node 10000000000.0
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node prop=1.23E+1000
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node prop=1.23E-1000
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node 1e-100
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node "\"\\/\b\f\n\r\t"
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
node "arg" prop="val" {
|
||||||
|
inner_node
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node "arg" arg="val"
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node (type)false
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node (type)2.5
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node (type)16
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node (type)null
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node (type)"str"
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node (type)"str"
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node (type)true
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node (type)"arg"
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node (type)0
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
😁 "happy!"
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node 2
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node 2
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node 2
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node ("")10
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
("")node
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node key=("")true
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node "arg"
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node "arg"
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node false true
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node prop1=true prop2=false
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node "arg2"
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node "arg"
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node_2
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node_2
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node "arg"
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
node1
|
||||||
|
node2
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node "😀"
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
"" "arg"
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node ""="empty"
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node ""
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node "hello\nworld"
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node "hello\nworld"
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node "arg"
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node "arg" "arg2\n"
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
node1
|
||||||
|
node2
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
false_id
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node false_id=1
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node 1311768467294899695
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node 1311768467294899695
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node 737894400291
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node 1
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node 1234
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
node {
|
||||||
|
inner_node
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node 1
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node 11
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node 1
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node "arg"
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node "arg1" "arg2"
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node " hey\neveryone\nhow goes?\n"
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node -1.0 key=-10.0
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node -10 prop=-15
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node "arg"
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
node1 {
|
||||||
|
node2 {
|
||||||
|
node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node "arg"
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node "arg"
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
node1
|
||||||
|
node2
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node "arg"
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node 10000000000.0
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node false
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node true
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
(type)node
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node null
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
null_id
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node null_id=1
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node prop=null
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
node 15.7
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue