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]
|
||||
nom = "6.0.1"
|
||||
phf = { version = "0.8.0", features = ["macros"] }
|
||||
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;
|
||||
|
||||
|
|
@ -19,6 +19,73 @@ pub enum KdlValue {
|
|||
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
|
||||
|
||||
impl From<i64> for KdlValue {
|
||||
|
|
@ -120,6 +187,83 @@ impl_try_from!(&, bool, KdlValue::Boolean(v) => *v; Int, Float, String);
|
|||
mod tests {
|
||||
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]
|
||||
fn from() {
|
||||
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`
|
||||
fn identifier(input: &str) -> IResult<&str, String, KdlParseError<&str>> {
|
||||
alt((
|
||||
map(
|
||||
recognize(pair(
|
||||
alt((alpha1, tag("_"))),
|
||||
many0(alt((alphanumeric1, recognize(one_of("~!@$%^&*-_+./:<>?"))))),
|
||||
)),
|
||||
String::from,
|
||||
),
|
||||
string,
|
||||
/// `bare_identifier := [a-zA-Z_] [a-zA-Z0-9!$%&'*+\-./:<>?@\^_|~]*`
|
||||
pub(crate) fn bare_identifier(input: &str) -> IResult<&str, &str, KdlParseError<&str>> {
|
||||
recognize(pair(
|
||||
alt((alpha1, tag("_"))),
|
||||
many0(alt((alphanumeric1, recognize(one_of("~!@$%^&*-_+./:<>?"))))),
|
||||
))(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)`
|
||||
fn node_prop_or_arg(input: &str) -> IResult<&str, Option<NodeArg>, KdlParseError<&str>> {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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} '}'`
|
||||
fn escape(input: &str) -> IResult<&str, char, KdlParseError<&str>> {
|
||||
alt((
|
||||
delimited(tag("u{"), unicode, char('}')),
|
||||
value('"', char('"')),
|
||||
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')),
|
||||
map_opt(anychar, |c| ESCAPE_CHARS.0.get(&c).copied()),
|
||||
))(input)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue