fix(autoformat): fix autoformatting of v1 -> v2

This commit is contained in:
Kat Marchán 2024-12-20 02:04:36 -08:00
parent 4cbc32246c
commit 37255b0bf6
No known key found for this signature in database
GPG Key ID: AEB529C08A3C7E9E
3 changed files with 89 additions and 62 deletions

View File

@ -161,57 +161,19 @@ impl KdlEntry {
self.len() == 0 self.len() == 0
} }
/// Keeps the general entry formatting, though v1 entries will still be
/// updated to v2 while preserving as much as possible.
pub fn keep_format(&mut self) {
if let Some(fmt) = self.format_mut() {
fmt.autoformat_keep = true;
}
}
/// Auto-formats this entry. /// Auto-formats this entry.
pub fn autoformat(&mut self) { pub fn autoformat(&mut self) {
// TODO once MSRV allows: // TODO once MSRV allows:
//self.format.take_if(|f| !f.autoformat_keep); //self.format.take_if(|f| !f.autoformat_keep);
let value_repr = self.format.as_ref().map(|x| { let old_fmt = self.format.clone();
match &self.value {
KdlValue::String(val) => {
// cleanup. I don't _think_ this should have any whitespace,
// but just in case.
let s = x.value_repr.trim();
// convert raw strings to new format
let s = s.strip_prefix('r').unwrap_or(s);
let s = if crate::value::is_plain_ident(val) {
val.to_string()
} else if s
.find(|c| v2_parser::NEWLINES.iter().any(|nl| nl.contains(c)))
.is_some()
{
// Multiline string. Need triple quotes if they're not there already.
if s.contains("\"\"\"") {
// We're probably good. This could be more precise, but close enough.
s.to_string()
} else {
// `"` -> `"""` but also extra newlines need to be
// added because v2 strips the first and last ones.
let s = s.replacen('\"', "\"\"\"\n", 1);
s.chars()
.rev()
.collect::<String>()
.replacen('\"', "\"\"\"\n", 1)
.chars()
.rev()
.collect::<String>()
}
} else if !s.starts_with('#') {
// `/` is no longer an escaped char in v2.
s.replace("\\/", "/")
} else {
// We're all good! Let's move on.
s.to_string()
};
s
}
// These have `#` prefixes now. The regular Display impl will
// take care of that.
KdlValue::Bool(_) | KdlValue::Null => format!("{}", self.value),
// These should be fine as-is?
KdlValue::Integer(_) | KdlValue::Float(_) => x.value_repr.clone(),
}
});
if !self if !self
.format .format
.as_ref() .as_ref()
@ -219,22 +181,69 @@ impl KdlEntry {
.unwrap_or(false) .unwrap_or(false)
{ {
self.format = None; self.format = None;
} } else {
let value_repr = old_fmt.map(|x| {
match &self.value {
KdlValue::String(val) => {
// cleanup. I don't _think_ this should have any whitespace,
// but just in case.
let s = x.value_repr.trim();
// convert raw strings to new format
let s = s.strip_prefix('r').unwrap_or(s);
let s = if crate::value::is_plain_ident(val) {
val.to_string()
} else if s
.find(|c| v2_parser::NEWLINES.iter().any(|nl| nl.contains(c)))
.is_some()
{
// Multiline string. Need triple quotes if they're not there already.
if s.contains("\"\"\"") {
// We're probably good. This could be more precise, but close enough.
s.to_string()
} else {
// `"` -> `"""` but also extra newlines need to be
// added because v2 strips the first and last ones.
let s = s.replacen('\"', "\"\"\"\n", 1);
s.chars()
.rev()
.collect::<String>()
.replacen('\"', "\"\"\"\n", 1)
.chars()
.rev()
.collect::<String>()
}
} else if !s.starts_with('#') {
// `/` is no longer an escaped char in v2.
s.replace("\\/", "/")
} else {
// We're all good! Let's move on.
s.to_string()
};
s
}
// These have `#` prefixes now. The regular Display impl will
// take care of that.
KdlValue::Bool(_) | KdlValue::Null => format!("{}", self.value),
// These should be fine as-is?
KdlValue::Integer(_) | KdlValue::Float(_) => x.value_repr.clone(),
}
});
if let Some(value_repr) = value_repr.as_ref() { if let Some(value_repr) = value_repr.as_ref() {
self.format = Some( self.format = Some(
self.format self.format
.clone() .clone()
.map(|mut x| { .map(|mut x| {
x.value_repr = value_repr.into(); x.value_repr = value_repr.into();
x x
}) })
.unwrap_or_else(|| KdlEntryFormat { .unwrap_or_else(|| KdlEntryFormat {
value_repr: value_repr.into(), value_repr: value_repr.into(),
leading: " ".into(), leading: " ".into(),
..Default::default() ..Default::default()
}), }),
) )
}
} }
if let Some(name) = &mut self.name { if let Some(name) = &mut self.name {
@ -523,42 +532,52 @@ mod test {
#[test] #[test]
fn v1_to_v2_format() -> miette::Result<()> { fn v1_to_v2_format() -> miette::Result<()> {
let mut entry = KdlEntry::parse_v1(r##"r#"hello, world!"#"##)?; let mut entry = KdlEntry::parse_v1(r##"r#"hello, world!"#"##)?;
entry.keep_format();
entry.autoformat(); entry.autoformat();
assert_eq!(format!("{}", entry), r##" #"hello, world!"#"##); assert_eq!(format!("{}", entry), r##" #"hello, world!"#"##);
let mut entry = KdlEntry::parse_v1(r#""hello, \" world!""#)?; let mut entry = KdlEntry::parse_v1(r#""hello, \" world!""#)?;
entry.keep_format();
entry.autoformat(); entry.autoformat();
assert_eq!(format!("{}", entry), r#" "hello, \" world!""#); assert_eq!(format!("{}", entry), r#" "hello, \" world!""#);
let mut entry = KdlEntry::parse_v1("\"foo!`~.,<>\"")?; let mut entry = KdlEntry::parse_v1("\"foo!`~.,<>\"")?;
entry.keep_format();
entry.autoformat(); entry.autoformat();
assert_eq!(format!("{}", entry), " foo!`~.,<>"); assert_eq!(format!("{}", entry), " foo!`~.,<>");
let mut entry = KdlEntry::parse_v1("\"\nhello, world!\"")?; let mut entry = KdlEntry::parse_v1("\"\nhello, world!\"")?;
entry.keep_format();
entry.autoformat(); entry.autoformat();
assert_eq!(format!("{}", entry), " \"\"\"\n\nhello, world!\n\"\"\""); assert_eq!(format!("{}", entry), " \"\"\"\n\nhello, world!\n\"\"\"");
let mut entry = KdlEntry::parse_v1("r#\"\nhello, world!\"#")?; let mut entry = KdlEntry::parse_v1("r#\"\nhello, world!\"#")?;
entry.keep_format();
entry.autoformat(); entry.autoformat();
assert_eq!(format!("{}", entry), " #\"\"\"\n\nhello, world!\n\"\"\"#"); assert_eq!(format!("{}", entry), " #\"\"\"\n\nhello, world!\n\"\"\"#");
let mut entry = KdlEntry::parse_v1("true")?; let mut entry = KdlEntry::parse_v1("true")?;
entry.keep_format();
entry.autoformat(); entry.autoformat();
assert_eq!(format!("{}", entry), " #true"); assert_eq!(format!("{}", entry), " #true");
let mut entry = KdlEntry::parse_v1("false")?; let mut entry = KdlEntry::parse_v1("false")?;
entry.keep_format();
entry.autoformat(); entry.autoformat();
assert_eq!(format!("{}", entry), " #false"); assert_eq!(format!("{}", entry), " #false");
let mut entry = KdlEntry::parse_v1("null")?; let mut entry = KdlEntry::parse_v1("null")?;
entry.keep_format();
entry.autoformat(); entry.autoformat();
assert_eq!(format!("{}", entry), " #null"); assert_eq!(format!("{}", entry), " #null");
let mut entry = KdlEntry::parse_v1("1_234_567")?; let mut entry = KdlEntry::parse_v1("1_234_567")?;
entry.keep_format();
entry.autoformat(); entry.autoformat();
assert_eq!(format!("{}", entry), " 1_234_567"); assert_eq!(format!("{}", entry), " 1_234_567");
let mut entry = KdlEntry::parse_v1("1_234_567E-10")?; let mut entry = KdlEntry::parse_v1("1_234_567E-10")?;
entry.keep_format();
entry.autoformat(); entry.autoformat();
assert_eq!(format!("{}", entry), " 1_234_567E-10"); assert_eq!(format!("{}", entry), " 1_234_567E-10");
Ok(()) Ok(())

View File

@ -15,6 +15,9 @@ pub struct FormatConfig<'a> {
/// Whether to remove comments. Defaults to `false`. /// Whether to remove comments. Defaults to `false`.
pub no_comments: bool, pub no_comments: bool,
/// Whether to keep individual entry formatting.
pub entry_autoformate_keep: bool,
} }
/// See field documentation for defaults. /// See field documentation for defaults.
@ -44,6 +47,7 @@ impl<'a> FormatConfigBuilder<'a> {
indent_level: 0, indent_level: 0,
indent: " ", indent: " ",
no_comments: false, no_comments: false,
entry_autoformate_keep: false,
}) })
} }
@ -163,6 +167,7 @@ mod test {
indent_level: 12, indent_level: 12,
indent: " \t", indent: " \t",
no_comments: true, no_comments: true,
entry_autoformate_keep: false,
} }
)); ));
Ok(()) Ok(())

View File

@ -309,6 +309,9 @@ impl KdlNode {
ty.clear_format() ty.clear_format()
} }
for entry in &mut self.entries { for entry in &mut self.entries {
if config.entry_autoformate_keep {
entry.keep_format();
}
entry.autoformat(); entry.autoformat();
} }
if let Some(children) = self.children.as_mut() { if let Some(children) = self.children.as_mut() {