mirror of https://github.com/kdl-org/kdl-rs.git
feat(fmt): shiny new comment-preserving formatter! (#38)
Fixes: https://github.com/kdl-org/kdl-rs/issues/34
This commit is contained in:
parent
2d65b61532
commit
12d373a1e0
|
|
@ -185,15 +185,10 @@ impl KdlDocument {
|
||||||
self.trailing = None;
|
self.trailing = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Auto-formats this Document.
|
/// Auto-formats this Document, making everything nice while preserving
|
||||||
///
|
/// comments.
|
||||||
/// Note: This currently removes comments as well.
|
|
||||||
pub fn fmt(&mut self) {
|
pub fn fmt(&mut self) {
|
||||||
self.leading = None;
|
self.fmt_impl(0);
|
||||||
self.trailing = None;
|
|
||||||
for node in &mut self.nodes {
|
|
||||||
node.fmt();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -204,6 +199,18 @@ impl Display for KdlDocument {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KdlDocument {
|
impl KdlDocument {
|
||||||
|
pub(crate) fn fmt_impl(&mut self, indent: usize) {
|
||||||
|
if let Some(s) = self.leading.as_mut() {
|
||||||
|
crate::fmt::fmt_leading(s, indent);
|
||||||
|
}
|
||||||
|
if let Some(s) = self.trailing.as_mut() {
|
||||||
|
crate::fmt::fmt_trailing(s);
|
||||||
|
}
|
||||||
|
for node in &mut self.nodes {
|
||||||
|
node.fmt_impl(indent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn stringify(
|
pub(crate) fn stringify(
|
||||||
&self,
|
&self,
|
||||||
f: &mut std::fmt::Formatter<'_>,
|
f: &mut std::fmt::Formatter<'_>,
|
||||||
|
|
@ -361,6 +368,67 @@ baz
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fmt() -> miette::Result<()> {
|
||||||
|
let mut doc: KdlDocument = r#"
|
||||||
|
|
||||||
|
/* x */ foo 1 "bar"=0xDEADbeef {
|
||||||
|
child1 1 ;
|
||||||
|
|
||||||
|
// child 2 comment
|
||||||
|
|
||||||
|
child2 2 // comment
|
||||||
|
|
||||||
|
child3 "
|
||||||
|
|
||||||
|
string\t" \
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
|
||||||
|
|
||||||
|
multiline*/
|
||||||
|
inner1 \
|
||||||
|
r"value" \
|
||||||
|
;
|
||||||
|
|
||||||
|
inner2 \ //comment
|
||||||
|
{
|
||||||
|
inner3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// trailing comment here
|
||||||
|
|
||||||
|
"#
|
||||||
|
.parse()?;
|
||||||
|
|
||||||
|
KdlDocument::fmt(&mut doc);
|
||||||
|
|
||||||
|
print!("{}", doc);
|
||||||
|
assert_eq!(
|
||||||
|
doc.to_string(),
|
||||||
|
r#"/* x */
|
||||||
|
foo 1 bar=0xdeadbeef {
|
||||||
|
child1 1
|
||||||
|
// child 2 comment
|
||||||
|
child2 2 // comment
|
||||||
|
child3 "\n\n string\t" {
|
||||||
|
/*
|
||||||
|
|
||||||
|
|
||||||
|
multiline*/
|
||||||
|
inner1 r"value"
|
||||||
|
inner2 {
|
||||||
|
inner3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// trailing comment here"#
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_examples() -> miette::Result<()> {
|
fn parse_examples() -> miette::Result<()> {
|
||||||
include_str!("../examples/kdl-schema.kdl").parse::<KdlDocument>()?;
|
include_str!("../examples/kdl-schema.kdl").parse::<KdlDocument>()?;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
pub(crate) fn fmt_leading(leading: &mut String, indent: usize) {
|
||||||
|
if leading.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let comments = crate::parser::parse(leading.trim(), crate::parser::leading_comments)
|
||||||
|
.expect("invalid leading text");
|
||||||
|
let mut result = String::new();
|
||||||
|
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));
|
||||||
|
*leading = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn fmt_trailing(decor: &mut String) {
|
||||||
|
if decor.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*decor = decor.trim().to_string();
|
||||||
|
let mut result = String::new();
|
||||||
|
let comments = crate::parser::parse(decor, crate::parser::trailing_comments)
|
||||||
|
.expect("invalid trailing text");
|
||||||
|
for comment in comments {
|
||||||
|
result.push_str(comment);
|
||||||
|
}
|
||||||
|
*decor = result;
|
||||||
|
}
|
||||||
|
|
@ -114,6 +114,7 @@ pub use value::*;
|
||||||
mod document;
|
mod document;
|
||||||
mod entry;
|
mod entry;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod fmt;
|
||||||
mod identifier;
|
mod identifier;
|
||||||
mod node;
|
mod node;
|
||||||
mod nom_compat;
|
mod nom_compat;
|
||||||
|
|
|
||||||
44
src/node.rs
44
src/node.rs
|
|
@ -340,14 +340,7 @@ 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.leading = None;
|
self.fmt_impl(0);
|
||||||
self.trailing = None;
|
|
||||||
for entry in &mut self.entries {
|
|
||||||
entry.fmt();
|
|
||||||
}
|
|
||||||
if let Some(children) = &mut self.children {
|
|
||||||
children.fmt();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -428,6 +421,41 @@ impl Display for KdlNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KdlNode {
|
impl KdlNode {
|
||||||
|
pub(crate) fn fmt_impl(&mut self, indent: usize) {
|
||||||
|
if let Some(s) = self.leading.as_mut() {
|
||||||
|
crate::fmt::fmt_leading(s, indent);
|
||||||
|
}
|
||||||
|
if let Some(s) = self.trailing.as_mut() {
|
||||||
|
crate::fmt::fmt_trailing(s);
|
||||||
|
if s.starts_with(';') {
|
||||||
|
s.remove(0);
|
||||||
|
}
|
||||||
|
if let Some(c) = s.chars().next() {
|
||||||
|
if !c.is_whitespace() {
|
||||||
|
s.insert(0, ' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.push('\n');
|
||||||
|
}
|
||||||
|
self.before_children = None;
|
||||||
|
self.name.clear_fmt();
|
||||||
|
if let Some(ty) = self.ty.as_mut() {
|
||||||
|
ty.clear_fmt()
|
||||||
|
}
|
||||||
|
for entry in &mut self.entries {
|
||||||
|
entry.fmt();
|
||||||
|
}
|
||||||
|
if let Some(children) = self.children.as_mut() {
|
||||||
|
children.fmt_impl(indent + 4);
|
||||||
|
if let Some(leading) = children.leading.as_mut() {
|
||||||
|
leading.push('\n');
|
||||||
|
}
|
||||||
|
if let Some(trailing) = children.trailing.as_mut() {
|
||||||
|
trailing.push_str(format!("{:indent$}", "", indent = indent).as_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn stringify(
|
pub(crate) fn stringify(
|
||||||
&self,
|
&self,
|
||||||
f: &mut std::fmt::Formatter<'_>,
|
f: &mut std::fmt::Formatter<'_>,
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,33 @@ pub(crate) fn identifier(input: &str) -> IResult<&str, KdlIdentifier, KdlParseEr
|
||||||
alt((plain_identifier, quoted_identifier))(input)
|
alt((plain_identifier, quoted_identifier))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn leading_comments(input: &str) -> IResult<&str, Vec<&str>, KdlParseError<&str>> {
|
||||||
|
terminated(
|
||||||
|
many0(preceded(opt(many0(alt((newline, unicode_space)))), comment)),
|
||||||
|
opt(many0(alt((newline, unicode_space, eof)))),
|
||||||
|
)(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn trailing_comments(mut input: &str) -> IResult<&str, Vec<&str>, KdlParseError<&str>> {
|
||||||
|
let mut comments = vec![];
|
||||||
|
loop {
|
||||||
|
let (inp, _) = opt(many0(alt((newline, unicode_space, tag("\\")))))(input)?;
|
||||||
|
let (inp, comment) = opt(comment)(inp)?;
|
||||||
|
if let Some(comment) = comment {
|
||||||
|
comments.push(comment);
|
||||||
|
}
|
||||||
|
let (inp, _) = opt(many0(alt((newline, unicode_space, tag("\\"), tag(";")))))(inp)?;
|
||||||
|
let (inp, end) = opt(eof)(inp)?;
|
||||||
|
if end.is_some() {
|
||||||
|
return Ok((inp, comments));
|
||||||
|
}
|
||||||
|
if input == inp {
|
||||||
|
panic!("invalid trailing text");
|
||||||
|
}
|
||||||
|
input = inp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn plain_identifier(input: &str) -> IResult<&str, KdlIdentifier, KdlParseError<&str>> {
|
fn plain_identifier(input: &str) -> IResult<&str, KdlIdentifier, KdlParseError<&str>> {
|
||||||
let start = input;
|
let start = input;
|
||||||
let (input, name) = recognize(preceded(
|
let (input, name) = recognize(preceded(
|
||||||
|
|
@ -643,6 +670,13 @@ mod comment_tests {
|
||||||
fn slashdash() {
|
fn slashdash() {
|
||||||
assert_eq!(comment("/-foo 1 2"), Ok(("", "/-foo 1 2")));
|
assert_eq!(comment("/-foo 1 2"), Ok(("", "/-foo 1 2")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn surrounding() {
|
||||||
|
// assert_eq!(trailing_comments("// foo"), Ok(("", vec!["// foo"])));
|
||||||
|
// assert_eq!(trailing_comments("/* foo */"), Ok(("", vec!["/* foo */"])));
|
||||||
|
// assert_eq!(trailing_comments("/* foo */ \n // bar"), Ok(("", vec!["/* foo */", "// bar"])));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,6 @@ impl Display for KdlValue {
|
||||||
|
|
||||||
impl KdlValue {
|
impl KdlValue {
|
||||||
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 {
|
||||||
write!(f, "r")?;
|
|
||||||
let raw = self.as_string().unwrap();
|
let raw = self.as_string().unwrap();
|
||||||
let mut consecutive = 0usize;
|
let mut consecutive = 0usize;
|
||||||
let mut maxhash = 0usize;
|
let mut maxhash = 0usize;
|
||||||
|
|
@ -173,14 +172,15 @@ impl KdlValue {
|
||||||
if char == '#' {
|
if char == '#' {
|
||||||
consecutive += 1;
|
consecutive += 1;
|
||||||
} else if char == '"' {
|
} else if char == '"' {
|
||||||
maxhash = maxhash.max(consecutive);
|
maxhash = maxhash.max(consecutive + 1);
|
||||||
} else {
|
} else {
|
||||||
consecutive = 0;
|
consecutive = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
write!(f, "{}", "#".repeat(maxhash + 1))?;
|
write!(f, "r")?;
|
||||||
|
write!(f, "{}", "#".repeat(maxhash))?;
|
||||||
write!(f, "\"{}\"", raw)?;
|
write!(f, "\"{}\"", raw)?;
|
||||||
write!(f, "{}", "#".repeat(maxhash + 1))?;
|
write!(f, "{}", "#".repeat(maxhash))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue