Skip to content

Commit 12d373a

Browse files
authored
feat(fmt): shiny new comment-preserving formatter! (#38)
Fixes: #34
1 parent 2d65b61 commit 12d373a

File tree

6 files changed

+181
-20
lines changed

6 files changed

+181
-20
lines changed

src/document.rs

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -185,15 +185,10 @@ impl KdlDocument {
185185
self.trailing = None;
186186
}
187187

188-
/// Auto-formats this Document.
189-
///
190-
/// Note: This currently removes comments as well.
188+
/// Auto-formats this Document, making everything nice while preserving
189+
/// comments.
191190
pub fn fmt(&mut self) {
192-
self.leading = None;
193-
self.trailing = None;
194-
for node in &mut self.nodes {
195-
node.fmt();
196-
}
191+
self.fmt_impl(0);
197192
}
198193
}
199194

@@ -204,6 +199,18 @@ impl Display for KdlDocument {
204199
}
205200

206201
impl KdlDocument {
202+
pub(crate) fn fmt_impl(&mut self, indent: usize) {
203+
if let Some(s) = self.leading.as_mut() {
204+
crate::fmt::fmt_leading(s, indent);
205+
}
206+
if let Some(s) = self.trailing.as_mut() {
207+
crate::fmt::fmt_trailing(s);
208+
}
209+
for node in &mut self.nodes {
210+
node.fmt_impl(indent);
211+
}
212+
}
213+
207214
pub(crate) fn stringify(
208215
&self,
209216
f: &mut std::fmt::Formatter<'_>,
@@ -361,6 +368,67 @@ baz
361368
);
362369
}
363370

371+
#[test]
372+
fn fmt() -> miette::Result<()> {
373+
let mut doc: KdlDocument = r#"
374+
375+
/* x */ foo 1 "bar"=0xDEADbeef {
376+
child1 1 ;
377+
378+
// child 2 comment
379+
380+
child2 2 // comment
381+
382+
child3 "
383+
384+
string\t" \
385+
{
386+
/*
387+
388+
389+
multiline*/
390+
inner1 \
391+
r"value" \
392+
;
393+
394+
inner2 \ //comment
395+
{
396+
inner3
397+
}
398+
}
399+
}
400+
401+
// trailing comment here
402+
403+
"#
404+
.parse()?;
405+
406+
KdlDocument::fmt(&mut doc);
407+
408+
print!("{}", doc);
409+
assert_eq!(
410+
doc.to_string(),
411+
r#"/* x */
412+
foo 1 bar=0xdeadbeef {
413+
child1 1
414+
// child 2 comment
415+
child2 2 // comment
416+
child3 "\n\n string\t" {
417+
/*
418+
419+
420+
multiline*/
421+
inner1 r"value"
422+
inner2 {
423+
inner3
424+
}
425+
}
426+
}
427+
// trailing comment here"#
428+
);
429+
Ok(())
430+
}
431+
364432
#[test]
365433
fn parse_examples() -> miette::Result<()> {
366434
include_str!("../examples/kdl-schema.kdl").parse::<KdlDocument>()?;

src/fmt.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
pub(crate) fn fmt_leading(leading: &mut String, indent: usize) {
2+
if leading.is_empty() {
3+
return;
4+
}
5+
let comments = crate::parser::parse(leading.trim(), crate::parser::leading_comments)
6+
.expect("invalid leading text");
7+
let mut result = String::new();
8+
for line in comments {
9+
let trimmed = line.trim();
10+
if !trimmed.is_empty() {
11+
result.push_str(&format!("{:indent$}{}\n", "", trimmed, indent = indent));
12+
}
13+
}
14+
result.push_str(&format!("{:indent$}", "", indent = indent));
15+
*leading = result;
16+
}
17+
18+
pub(crate) fn fmt_trailing(decor: &mut String) {
19+
if decor.is_empty() {
20+
return;
21+
}
22+
*decor = decor.trim().to_string();
23+
let mut result = String::new();
24+
let comments = crate::parser::parse(decor, crate::parser::trailing_comments)
25+
.expect("invalid trailing text");
26+
for comment in comments {
27+
result.push_str(comment);
28+
}
29+
*decor = result;
30+
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ pub use value::*;
114114
mod document;
115115
mod entry;
116116
mod error;
117+
mod fmt;
117118
mod identifier;
118119
mod node;
119120
mod nom_compat;

src/node.rs

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -340,14 +340,7 @@ impl KdlNode {
340340

341341
/// Auto-formats this node and its contents.
342342
pub fn fmt(&mut self) {
343-
self.leading = None;
344-
self.trailing = None;
345-
for entry in &mut self.entries {
346-
entry.fmt();
347-
}
348-
if let Some(children) = &mut self.children {
349-
children.fmt();
350-
}
343+
self.fmt_impl(0);
351344
}
352345
}
353346

@@ -428,6 +421,41 @@ impl Display for KdlNode {
428421
}
429422

430423
impl KdlNode {
424+
pub(crate) fn fmt_impl(&mut self, indent: usize) {
425+
if let Some(s) = self.leading.as_mut() {
426+
crate::fmt::fmt_leading(s, indent);
427+
}
428+
if let Some(s) = self.trailing.as_mut() {
429+
crate::fmt::fmt_trailing(s);
430+
if s.starts_with(';') {
431+
s.remove(0);
432+
}
433+
if let Some(c) = s.chars().next() {
434+
if !c.is_whitespace() {
435+
s.insert(0, ' ');
436+
}
437+
}
438+
s.push('\n');
439+
}
440+
self.before_children = None;
441+
self.name.clear_fmt();
442+
if let Some(ty) = self.ty.as_mut() {
443+
ty.clear_fmt()
444+
}
445+
for entry in &mut self.entries {
446+
entry.fmt();
447+
}
448+
if let Some(children) = self.children.as_mut() {
449+
children.fmt_impl(indent + 4);
450+
if let Some(leading) = children.leading.as_mut() {
451+
leading.push('\n');
452+
}
453+
if let Some(trailing) = children.trailing.as_mut() {
454+
trailing.push_str(format!("{:indent$}", "", indent = indent).as_str());
455+
}
456+
}
457+
}
458+
431459
pub(crate) fn stringify(
432460
&self,
433461
f: &mut std::fmt::Formatter<'_>,

src/parser.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,33 @@ pub(crate) fn identifier(input: &str) -> IResult<&str, KdlIdentifier, KdlParseEr
112112
alt((plain_identifier, quoted_identifier))(input)
113113
}
114114

115+
pub(crate) fn leading_comments(input: &str) -> IResult<&str, Vec<&str>, KdlParseError<&str>> {
116+
terminated(
117+
many0(preceded(opt(many0(alt((newline, unicode_space)))), comment)),
118+
opt(many0(alt((newline, unicode_space, eof)))),
119+
)(input)
120+
}
121+
122+
pub(crate) fn trailing_comments(mut input: &str) -> IResult<&str, Vec<&str>, KdlParseError<&str>> {
123+
let mut comments = vec![];
124+
loop {
125+
let (inp, _) = opt(many0(alt((newline, unicode_space, tag("\\")))))(input)?;
126+
let (inp, comment) = opt(comment)(inp)?;
127+
if let Some(comment) = comment {
128+
comments.push(comment);
129+
}
130+
let (inp, _) = opt(many0(alt((newline, unicode_space, tag("\\"), tag(";")))))(inp)?;
131+
let (inp, end) = opt(eof)(inp)?;
132+
if end.is_some() {
133+
return Ok((inp, comments));
134+
}
135+
if input == inp {
136+
panic!("invalid trailing text");
137+
}
138+
input = inp;
139+
}
140+
}
141+
115142
fn plain_identifier(input: &str) -> IResult<&str, KdlIdentifier, KdlParseError<&str>> {
116143
let start = input;
117144
let (input, name) = recognize(preceded(
@@ -643,6 +670,13 @@ mod comment_tests {
643670
fn slashdash() {
644671
assert_eq!(comment("/-foo 1 2"), Ok(("", "/-foo 1 2")));
645672
}
673+
674+
#[test]
675+
fn surrounding() {
676+
// assert_eq!(trailing_comments("// foo"), Ok(("", vec!["// foo"])));
677+
// assert_eq!(trailing_comments("/* foo */"), Ok(("", vec!["/* foo */"])));
678+
// assert_eq!(trailing_comments("/* foo */ \n // bar"), Ok(("", vec!["/* foo */", "// bar"])));
679+
}
646680
}
647681

648682
#[cfg(test)]

src/value.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,22 +165,22 @@ impl Display for KdlValue {
165165

166166
impl KdlValue {
167167
fn write_raw_string(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168-
write!(f, "r")?;
169168
let raw = self.as_string().unwrap();
170169
let mut consecutive = 0usize;
171170
let mut maxhash = 0usize;
172171
for char in raw.chars() {
173172
if char == '#' {
174173
consecutive += 1;
175174
} else if char == '"' {
176-
maxhash = maxhash.max(consecutive);
175+
maxhash = maxhash.max(consecutive + 1);
177176
} else {
178177
consecutive = 0;
179178
}
180179
}
181-
write!(f, "{}", "#".repeat(maxhash + 1))?;
180+
write!(f, "r")?;
181+
write!(f, "{}", "#".repeat(maxhash))?;
182182
write!(f, "\"{}\"", raw)?;
183-
write!(f, "{}", "#".repeat(maxhash + 1))?;
183+
write!(f, "{}", "#".repeat(maxhash))?;
184184
Ok(())
185185
}
186186
}

0 commit comments

Comments
 (0)