feat(display): implemented Display for KdlNode (#6)

This commit is contained in:
hclarke 2020-12-24 02:41:11 -04:00 committed by Kat Marchán
parent b9d7ac4ca0
commit b8c8b52748
No known key found for this signature in database
GPG Key ID: AEB529C08A3C7E9E
3 changed files with 176 additions and 20 deletions

View File

@ -12,4 +12,5 @@ edition = "2018"
[dependencies]
nom = "6.0.1"
phf = { version = "0.8.0", features = ["macros"] }
thiserror = "1.0.22"

View File

@ -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));

View File

@ -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)
}