mirror of https://github.com/kdl-org/kdl-rs.git
feat(display): implemented Display for KdlNode (#6)
This commit is contained in:
parent
b9d7ac4ca0
commit
b8c8b52748
|
|
@ -12,4 +12,5 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nom = "6.0.1"
|
nom = "6.0.1"
|
||||||
|
phf = { version = "0.8.0", features = ["macros"] }
|
||||||
thiserror = "1.0.22"
|
thiserror = "1.0.22"
|
||||||
|
|
|
||||||
146
src/node.rs
146
src/node.rs
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{collections::HashMap, convert::TryFrom};
|
use std::{collections::HashMap, convert::TryFrom, fmt};
|
||||||
|
|
||||||
use crate::TryFromKdlNodeValueError;
|
use crate::TryFromKdlNodeValueError;
|
||||||
|
|
||||||
|
|
@ -19,6 +19,73 @@ pub enum KdlValue {
|
||||||
Null,
|
Null,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for KdlNode {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
self.write(f, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KdlNode {
|
||||||
|
fn write(&self, f: &mut fmt::Formatter<'_>, indent: usize) -> fmt::Result {
|
||||||
|
write!(f, "{:indent$}", "", indent = indent)?;
|
||||||
|
|
||||||
|
display_identifier(f, &self.name)?;
|
||||||
|
for arg in &self.values {
|
||||||
|
write!(f, " {}", arg)?;
|
||||||
|
}
|
||||||
|
for (prop, value) in &self.properties {
|
||||||
|
write!(f, " ")?;
|
||||||
|
display_identifier(f, prop)?;
|
||||||
|
write!(f, "={}", value)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.children.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
writeln!(f, " {{")?;
|
||||||
|
for child in &self.children {
|
||||||
|
child.write(f, indent + 4)?;
|
||||||
|
writeln!(f)?;
|
||||||
|
}
|
||||||
|
write!(f, "{:indent$}}}", "", indent = indent)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl fmt::Display for KdlValue {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
use KdlValue::*;
|
||||||
|
match self {
|
||||||
|
Int(x) => write!(f, "{}", x),
|
||||||
|
Float(x) => write!(f, "{}", x),
|
||||||
|
String(x) => display_string(f, x),
|
||||||
|
Boolean(x) => write!(f, "{}", x),
|
||||||
|
Null => write!(f, "null"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_identifier(f: &mut fmt::Formatter<'_>, s: &str) -> fmt::Result {
|
||||||
|
if let Ok(("", identifier)) = crate::parser::bare_identifier(s) {
|
||||||
|
write!(f, "{}", identifier)
|
||||||
|
} else {
|
||||||
|
display_string(f, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_string(f: &mut fmt::Formatter<'_>, s: &str) -> fmt::Result {
|
||||||
|
write!(f, "\"")?;
|
||||||
|
for c in s.chars() {
|
||||||
|
match crate::parser::ESCAPE_CHARS.1.get(&c) {
|
||||||
|
None => write!(f, "{}", c)?,
|
||||||
|
Some(c) => write!(f, "\\{}", c)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(f, "\"")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// Support conversions from base types into KdlNodeValue
|
// Support conversions from base types into KdlNodeValue
|
||||||
|
|
||||||
impl From<i64> for KdlValue {
|
impl From<i64> for KdlValue {
|
||||||
|
|
@ -120,6 +187,83 @@ impl_try_from!(&, bool, KdlValue::Boolean(v) => *v; Int, Float, String);
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn display_value() {
|
||||||
|
assert_eq!("1", format!("{}", KdlValue::Int(1)));
|
||||||
|
assert_eq!("1.5", format!("{}", KdlValue::Float(1.5)));
|
||||||
|
assert_eq!("true", format!("{}", KdlValue::Boolean(true)));
|
||||||
|
assert_eq!("false", format!("{}", KdlValue::Boolean(false)));
|
||||||
|
assert_eq!("null", format!("{}", KdlValue::Null));
|
||||||
|
assert_eq!(
|
||||||
|
r#""foo""#,
|
||||||
|
format!("{}", KdlValue::String("foo".to_owned()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
r#""foo \"bar\" baz""#,
|
||||||
|
format!("{}", KdlValue::String(r#"foo "bar" baz"#.to_owned()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn display_node() {
|
||||||
|
let mut value = KdlNode {
|
||||||
|
name: "foo".into(),
|
||||||
|
values: vec![1.into(), "two".into()],
|
||||||
|
properties: HashMap::new(),
|
||||||
|
children: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
value.properties.insert("three".to_owned(), 3.into());
|
||||||
|
|
||||||
|
assert_eq!(r#"foo 1 "two" three=3"#, format!("{}", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn display_nested_node() {
|
||||||
|
let value = KdlNode {
|
||||||
|
name: "a1".into(),
|
||||||
|
values: vec!["a".into(), 1.into()],
|
||||||
|
properties: HashMap::new(),
|
||||||
|
children: vec![
|
||||||
|
KdlNode {
|
||||||
|
name: "b1".into(),
|
||||||
|
values: vec!["b".into(), 1.into()],
|
||||||
|
properties: HashMap::new(),
|
||||||
|
children: vec![KdlNode {
|
||||||
|
name: "c1".into(),
|
||||||
|
values: vec!["c".into(), 1.into()],
|
||||||
|
properties: HashMap::new(),
|
||||||
|
children: vec![],
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
KdlNode {
|
||||||
|
name: "b2".into(),
|
||||||
|
values: vec!["b".into(), 2.into()],
|
||||||
|
properties: HashMap::new(),
|
||||||
|
children: vec![KdlNode {
|
||||||
|
name: "c2".into(),
|
||||||
|
values: vec!["c".into(), 2.into()],
|
||||||
|
properties: HashMap::new(),
|
||||||
|
children: vec![],
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
r#"
|
||||||
|
a1 "a" 1 {
|
||||||
|
b1 "b" 1 {
|
||||||
|
c1 "c" 1
|
||||||
|
}
|
||||||
|
b2 "b" 2 {
|
||||||
|
c2 "c" 2
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
format!("\n{}", value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn from() {
|
fn from() {
|
||||||
assert_eq!(KdlValue::from(1), KdlValue::Int(1));
|
assert_eq!(KdlValue::from(1), KdlValue::Int(1));
|
||||||
|
|
|
||||||
|
|
@ -112,20 +112,19 @@ pub(crate) fn node(input: &str) -> IResult<&str, Option<KdlNode>, KdlParseError<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `identifier := [a-zA-Z_] [a-zA-Z0-9!$%&'*+\-./:<>?@\^_|~]* | string`
|
/// `bare_identifier := [a-zA-Z_] [a-zA-Z0-9!$%&'*+\-./:<>?@\^_|~]*`
|
||||||
fn identifier(input: &str) -> IResult<&str, String, KdlParseError<&str>> {
|
pub(crate) fn bare_identifier(input: &str) -> IResult<&str, &str, KdlParseError<&str>> {
|
||||||
alt((
|
recognize(pair(
|
||||||
map(
|
alt((alpha1, tag("_"))),
|
||||||
recognize(pair(
|
many0(alt((alphanumeric1, recognize(one_of("~!@$%^&*-_+./:<>?"))))),
|
||||||
alt((alpha1, tag("_"))),
|
|
||||||
many0(alt((alphanumeric1, recognize(one_of("~!@$%^&*-_+./:<>?"))))),
|
|
||||||
)),
|
|
||||||
String::from,
|
|
||||||
),
|
|
||||||
string,
|
|
||||||
))(input)
|
))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `identifier := bare_identifier | string`
|
||||||
|
fn identifier(input: &str) -> IResult<&str, String, KdlParseError<&str>> {
|
||||||
|
alt((map(bare_identifier, String::from), string))(input)
|
||||||
|
}
|
||||||
|
|
||||||
/// `node-props-and-args := ('/-' ws*)? (prop | value)`
|
/// `node-props-and-args := ('/-' ws*)? (prop | value)`
|
||||||
fn node_prop_or_arg(input: &str) -> IResult<&str, Option<NodeArg>, KdlParseError<&str>> {
|
fn node_prop_or_arg(input: &str) -> IResult<&str, Option<NodeArg>, KdlParseError<&str>> {
|
||||||
let (input, comment) = opt(terminated(tag("/-"), many0(whitespace)))(input)?;
|
let (input, comment) = opt(terminated(tag("/-"), many0(whitespace)))(input)?;
|
||||||
|
|
@ -197,18 +196,30 @@ fn character(input: &str) -> IResult<&str, char, KdlParseError<&str>> {
|
||||||
alt((preceded(char('\\'), escape), none_of("\\\"")))(input)
|
alt((preceded(char('\\'), escape), none_of("\\\"")))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// creates a (map, inverse map) tuple
|
||||||
|
macro_rules! bimap {
|
||||||
|
($($x:expr => $y:expr),+) => {
|
||||||
|
(phf::phf_map!($($x => $y),+), phf::phf_map!($($y => $x),+))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// a map and its inverse of escape-sequence<->char
|
||||||
|
pub(crate) static ESCAPE_CHARS: (phf::Map<char, char>, phf::Map<char, char>) = bimap! {
|
||||||
|
'"' => '"',
|
||||||
|
'\\' => '\\',
|
||||||
|
'/' => '/',
|
||||||
|
'b' => '\u{08}',
|
||||||
|
'f' => '\u{0C}',
|
||||||
|
'n' => '\n',
|
||||||
|
'r' => '\r',
|
||||||
|
't' => '\t'
|
||||||
|
};
|
||||||
|
|
||||||
/// `escape := ["\\/bfnrt] | 'u{' hex-digit{1, 6} '}'`
|
/// `escape := ["\\/bfnrt] | 'u{' hex-digit{1, 6} '}'`
|
||||||
fn escape(input: &str) -> IResult<&str, char, KdlParseError<&str>> {
|
fn escape(input: &str) -> IResult<&str, char, KdlParseError<&str>> {
|
||||||
alt((
|
alt((
|
||||||
delimited(tag("u{"), unicode, char('}')),
|
delimited(tag("u{"), unicode, char('}')),
|
||||||
value('"', char('"')),
|
map_opt(anychar, |c| ESCAPE_CHARS.0.get(&c).copied()),
|
||||||
value('\\', char('\\')),
|
|
||||||
value('/', char('/')),
|
|
||||||
value('\u{08}', char('b')),
|
|
||||||
value('\u{0C}', char('f')),
|
|
||||||
value('\n', char('n')),
|
|
||||||
value('\r', char('r')),
|
|
||||||
value('\t', char('t')),
|
|
||||||
))(input)
|
))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue