Skip to content

Commit b332eed

Browse files
committed
feat(v1): add v2 -> v1 translation and fix translations to not autoformat
1 parent ec73cdf commit b332eed

File tree

6 files changed

+395
-75
lines changed

6 files changed

+395
-75
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,4 @@ jobs:
4343
- name: Clippy
4444
run: cargo clippy --all -- -D warnings
4545
- name: Run tests
46-
run: cargo test --all --verbose
46+
run: cargo test --features span --features --v1 --all --verbose

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ rust-version = "1.70.0"
1212
edition = "2021"
1313

1414
[features]
15-
default = ["span"]
15+
default = ["span", "v1"]
1616
span = []
1717
v1-fallback = ["v1"]
1818
v1 = ["kdlv1"]

src/document.rs

Lines changed: 162 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
use miette::SourceSpan;
33
use std::fmt::Display;
44

5-
use crate::{FormatConfig, KdlError, KdlNode, KdlValue};
5+
use crate::{FormatConfig, KdlError, KdlNode, KdlNodeFormat, KdlValue};
66

77
/// Represents a KDL
88
/// [`Document`](https://github.com/kdl-org/kdl/blob/main/SPEC.md#document).
@@ -344,15 +344,20 @@ impl KdlDocument {
344344
pub fn parse(s: &str) -> Result<Self, KdlError> {
345345
#[cfg(not(feature = "v1-fallback"))]
346346
{
347-
crate::v2_parser::try_parse(crate::v2_parser::document, s)
347+
KdlDocument::parse_v2(s)
348348
}
349349
#[cfg(feature = "v1-fallback")]
350350
{
351-
crate::v2_parser::try_parse(crate::v2_parser::document, s)
352-
.or_else(|e| KdlDocument::parse_v1(s).map_err(|_| e))
351+
KdlDocument::parse_v2(s).or_else(|e| KdlDocument::parse_v1(s).map_err(|_| e))
353352
}
354353
}
355354

355+
/// Parses a KDL v2 string into a document.
356+
#[cfg(feature = "v1")]
357+
pub fn parse_v2(s: &str) -> Result<Self, KdlError> {
358+
crate::v2_parser::try_parse(crate::v2_parser::document, s)
359+
}
360+
356361
/// Parses a KDL v1 string into a document.
357362
#[cfg(feature = "v1")]
358363
pub fn parse_v1(s: &str) -> Result<Self, KdlError> {
@@ -365,9 +370,74 @@ impl KdlDocument {
365370
#[cfg(feature = "v1")]
366371
pub fn v1_to_v2(s: &str) -> Result<String, KdlError> {
367372
let mut doc = KdlDocument::parse_v1(s)?;
368-
doc.autoformat();
373+
doc.ensure_v2();
369374
Ok(doc.to_string())
370375
}
376+
377+
/// Takes a KDL v2 document string and returns the same document, but
378+
/// autoformatted into valid KDL v2 syntax.
379+
#[cfg(feature = "v1")]
380+
pub fn v2_to_v1(s: &str) -> Result<String, KdlError> {
381+
let mut doc = KdlDocument::parse_v2(s)?;
382+
doc.ensure_v1();
383+
Ok(doc.to_string())
384+
}
385+
386+
/// Makes sure this document is in v2 format.
387+
#[cfg(feature = "v1")]
388+
pub fn ensure_v2(&mut self) {
389+
// No need to touch KdlDocumentFormat, probably. In the longer term,
390+
// we'll want to make sure to parse out whitespace and comments and make
391+
// sure they're actually compliant, but this is good enough for now.
392+
for node in self.nodes_mut().iter_mut() {
393+
node.ensure_v2();
394+
}
395+
}
396+
397+
/// Makes sure this document is in v1 format.
398+
#[cfg(feature = "v1")]
399+
pub fn ensure_v1(&mut self) {
400+
// No need to touch KdlDocumentFormat, probably. In the longer term,
401+
// we'll want to make sure to parse out whitespace and comments and make
402+
// sure they're actually compliant, but this is good enough for now.
403+
404+
// the last node in v1 docs/children has to have a semicolon.
405+
let mut iter = self.nodes_mut().iter_mut().rev();
406+
let last = iter.next();
407+
let penult = iter.next();
408+
if let Some(last) = last {
409+
if let Some(fmt) = last.format_mut() {
410+
if !fmt.trailing.contains(";")
411+
&& fmt
412+
.trailing
413+
.chars()
414+
.any(|c| crate::v2_parser::NEWLINES.iter().any(|nl| nl.contains(c)))
415+
{
416+
fmt.terminator = ";".into();
417+
}
418+
} else {
419+
let maybe_indent = {
420+
if let Some(penult) = penult {
421+
if let Some(fmt) = penult.format() {
422+
fmt.leading.clone()
423+
} else {
424+
"".into()
425+
}
426+
} else {
427+
"".into()
428+
}
429+
};
430+
last.format = Some(KdlNodeFormat {
431+
leading: maybe_indent,
432+
terminator: "\n".into(),
433+
..Default::default()
434+
})
435+
}
436+
}
437+
for node in self.nodes_mut().iter_mut() {
438+
node.ensure_v1();
439+
}
440+
}
371441
}
372442

373443
#[cfg(feature = "v1")]
@@ -956,10 +1026,95 @@ inline { time; to; live "our" "dreams"; "y;all" }
9561026
Ok(())
9571027
}
9581028

959-
#[ignore = "Formatting is still seriously broken, and this is gonna need some extra love."]
9601029
#[cfg(feature = "v1")]
9611030
#[test]
962-
fn v1_to_v2() -> miette::Result<()> {
1031+
fn v1_v2_conversions() -> miette::Result<()> {
1032+
let v1 = r##"
1033+
// If you'd like to override the default keybindings completely, be sure to change "keybinds" to "keybinds clear-defaults=true"
1034+
keybinds {
1035+
normal {
1036+
// uncomment this and adjust key if using copy_on_select=false
1037+
// bind "Alt c" { Copy; }
1038+
}
1039+
locked {
1040+
bind "Ctrl g" { SwitchToMode "Normal"; }
1041+
}
1042+
resize {
1043+
bind "Ctrl n" { SwitchToMode "Normal"; }
1044+
bind "h" "Left" { Resize "Increase Left"; }
1045+
bind "j" "Down" { Resize "Increase Down"; }
1046+
bind "k" "Up" { Resize "Increase Up"; }
1047+
bind "l" "Right" { Resize "Increase Right"; }
1048+
bind "H" { Resize "Decrease Left"; }
1049+
bind "J" { Resize "Decrease Down"; }
1050+
bind "K" { Resize "Decrease Up"; }
1051+
bind "L" { Resize "Decrease Right"; }
1052+
bind "=" "+" { Resize "Increase"; }
1053+
bind "-" { Resize "Decrease"; }
1054+
}
1055+
}
1056+
// Plugin aliases - can be used to change the implementation of Zellij
1057+
// changing these requires a restart to take effect
1058+
plugins {
1059+
tab-bar location="zellij:tab-bar"
1060+
status-bar location="zellij:status-bar"
1061+
welcome-screen location="zellij:session-manager" {
1062+
welcome_screen true
1063+
}
1064+
filepicker location="zellij:strider" {
1065+
cwd "\/"
1066+
}
1067+
}
1068+
mouse_mode false
1069+
mirror_session true
1070+
"##;
1071+
let v2 = r##"
1072+
// If you'd like to override the default keybindings completely, be sure to change "keybinds" to "keybinds clear-defaults=true"
1073+
keybinds {
1074+
normal {
1075+
// uncomment this and adjust key if using copy_on_select=false
1076+
// bind "Alt c" { Copy; }
1077+
}
1078+
locked {
1079+
bind "Ctrl g" { SwitchToMode Normal; }
1080+
}
1081+
resize {
1082+
bind "Ctrl n" { SwitchToMode Normal; }
1083+
bind h Left { Resize "Increase Left"; }
1084+
bind j Down { Resize "Increase Down"; }
1085+
bind k Up { Resize "Increase Up"; }
1086+
bind l Right { Resize "Increase Right"; }
1087+
bind H { Resize "Decrease Left"; }
1088+
bind J { Resize "Decrease Down"; }
1089+
bind K { Resize "Decrease Up"; }
1090+
bind L { Resize "Decrease Right"; }
1091+
bind "=" + { Resize Increase; }
1092+
bind - { Resize Decrease; }
1093+
}
1094+
}
1095+
// Plugin aliases - can be used to change the implementation of Zellij
1096+
// changing these requires a restart to take effect
1097+
plugins {
1098+
tab-bar location=zellij:tab-bar
1099+
status-bar location=zellij:status-bar
1100+
welcome-screen location=zellij:session-manager {
1101+
welcome_screen #true
1102+
}
1103+
filepicker location=zellij:strider {
1104+
cwd "/"
1105+
}
1106+
}
1107+
mouse_mode #false
1108+
mirror_session #true
1109+
"##;
1110+
pretty_assertions::assert_eq!(KdlDocument::v1_to_v2(v1)?, v2, "Converting a v1 doc to v2");
1111+
pretty_assertions::assert_eq!(KdlDocument::v2_to_v1(v2)?, v1, "Converting a v2 doc to v1");
1112+
Ok(())
1113+
}
1114+
1115+
#[cfg(feature = "v1")]
1116+
#[test]
1117+
fn v2_to_v1() -> miette::Result<()> {
9631118
let original = r##"
9641119
// If you'd like to override the default keybindings completely, be sure to change "keybinds" to "keybinds clear-defaults=true"
9651120
keybinds {

0 commit comments

Comments
 (0)