Skip to content

Commit 7f11562

Browse files
committed
feat(fallback): apply heuristics when doing fallbacks
1 parent 4fee6b8 commit 7f11562

File tree

2 files changed

+71
-90
lines changed

2 files changed

+71
-90
lines changed

src/document.rs

Lines changed: 68 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -341,15 +341,30 @@ impl KdlDocument {
341341
///
342342
/// If the `v1-fallback` feature is enabled, this method will first try to
343343
/// parse the string as a KDL v2 document, and, if that fails, it will try
344-
/// to parse again as a KDL v1 document. If both fail, only the v2 parse
345-
/// errors will be returned.
344+
/// to parse again as a KDL v1 document. If both fail, a heuristic will be
345+
/// applied to try and detect the "intended" KDL version, and that version's
346+
/// error(s) will be returned.
346347
pub fn parse(s: &str) -> Result<Self, KdlError> {
347348
#[cfg(not(feature = "v1-fallback"))]
348349
{
349350
KdlDocument::parse_v2(s)
350351
}
351352
#[cfg(feature = "v1-fallback")]
352353
{
354+
let v2_res = KdlDocument::parse_v2(s);
355+
if let Err(err) = v2_res {
356+
let v1_res = KdlDocument::parse_v2(s);
357+
if v1_res.is_err() && detect_v2(s) {
358+
v2_res
359+
} else if detect_v1(s) {
360+
v1_res
361+
} else {
362+
// This does matter, because detection short-circuits.
363+
v2_res
364+
}
365+
} else {
366+
v2_res
367+
}
353368
KdlDocument::parse_v2(s).or_else(|e| KdlDocument::parse_v1(s).map_err(|_| e))
354369
}
355370
}
@@ -455,6 +470,55 @@ impl From<kdlv1::KdlDocument> for KdlDocument {
455470
}
456471
}
457472

473+
/// Applies heuristics to get an idea of whether the string might be intended to
474+
/// be v2.
475+
#[allow(unused)]
476+
pub(crate) fn detect_v2(input: &str) -> bool {
477+
for line in input.lines() {
478+
if line.contains("kdl-version 2")
479+
|| line.contains("#true")
480+
|| line.contains("#false")
481+
|| line.contains("#null")
482+
|| line.contains("#inf")
483+
|| line.contains("#-inf")
484+
|| line.contains("#nan")
485+
|| line.contains(" #\"")
486+
|| line.contains("\"\"\"")
487+
// Very very rough attempt at finding unquoted strings. We give up
488+
// the first time we see a quoted one on a line.
489+
|| (!line.contains('"') && line
490+
.split_whitespace()
491+
.skip(1)
492+
.any(|x| {
493+
x.chars()
494+
.next()
495+
.map(|d| !d.is_ascii_digit() && d != '-' && d != '+')
496+
.unwrap_or_default()
497+
}))
498+
{
499+
return true;
500+
}
501+
}
502+
false
503+
}
504+
505+
/// Applies heuristics to get an idea of whether the string might be intended to
506+
/// be v2.
507+
#[allow(unused)]
508+
pub(crate) fn detect_v1(input: &str) -> bool {
509+
input
510+
.lines()
511+
.next()
512+
.map(|l| l.contains("kdl-version 1"))
513+
.unwrap_or(false)
514+
|| input.contains(" true")
515+
|| input.contains(" false")
516+
|| input.contains(" null")
517+
|| input.contains("r#\"")
518+
|| input.contains(" \"\n")
519+
|| input.contains(" \"\r\n")
520+
}
521+
458522
impl std::str::FromStr for KdlDocument {
459523
type Err = KdlError;
460524

@@ -1109,91 +1173,8 @@ mirror_session #true
11091173
"##;
11101174
pretty_assertions::assert_eq!(KdlDocument::v1_to_v2(v1)?, v2, "Converting a v1 doc to v2");
11111175
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<()> {
1118-
let original = r##"
1119-
// If you'd like to override the default keybindings completely, be sure to change "keybinds" to "keybinds clear-defaults=true"
1120-
keybinds {
1121-
normal {
1122-
// uncomment this and adjust key if using copy_on_select=false
1123-
// bind "Alt c" { Copy; }
1124-
}
1125-
locked {
1126-
bind "Ctrl g" { SwitchToMode "Normal"; }
1127-
}
1128-
resize {
1129-
bind "Ctrl n" { SwitchToMode "Normal"; }
1130-
bind "h" "Left" { Resize "Increase Left"; }
1131-
bind "j" "Down" { Resize "Increase Down"; }
1132-
bind "k" "Up" { Resize "Increase Up"; }
1133-
bind "l" "Right" { Resize "Increase Right"; }
1134-
bind "H" { Resize "Decrease Left"; }
1135-
bind "J" { Resize "Decrease Down"; }
1136-
bind "K" { Resize "Decrease Up"; }
1137-
bind "L" { Resize "Decrease Right"; }
1138-
bind "=" "+" { Resize "Increase"; }
1139-
bind "-" { Resize "Decrease"; }
1140-
}
1141-
}
1142-
// Plugin aliases - can be used to change the implementation of Zellij
1143-
// changing these requires a restart to take effect
1144-
plugins {
1145-
tab-bar location="zellij:tab-bar"
1146-
status-bar location="zellij:status-bar"
1147-
welcome-screen location="zellij:session-manager" {
1148-
welcome_screen true
1149-
}
1150-
filepicker location="zellij:strider" {
1151-
cwd "/"
1152-
}
1153-
}
1154-
mouse_mode false
1155-
mirror_session true
1156-
"##;
1157-
let expected = r##"
1158-
// If you'd like to override the default keybindings completely, be sure to change "keybinds" to "keybinds clear-defaults=true"
1159-
keybinds {
1160-
normal {
1161-
// uncomment this and adjust key if using copy_on_select=false
1162-
// bind "Alt c" { Copy; }
1163-
}
1164-
locked {
1165-
bind "Ctrl g" { SwitchToMode Normal; }
1166-
}
1167-
resize {
1168-
bind "Ctrl n" { SwitchToMode Normal; }
1169-
bind h Left { Resize "Increase Left"; }
1170-
bind j Down { Resize "Increase Down"; }
1171-
bind k Up { Resize "Increase Up"; }
1172-
bind l Right { Resize "Increase Right"; }
1173-
bind H { Resize "Decrease Left"; }
1174-
bind J { Resize "Decrease Down"; }
1175-
bind K { Resize "Decrease Up"; }
1176-
bind L { Resize "Decrease Right"; }
1177-
bind "=" + { Resize Increase; }
1178-
bind - { Resize Decrease; }
1179-
}
1180-
}
1181-
// Plugin aliases - can be used to change the implementation of Zellij
1182-
// changing these requires a restart to take effect
1183-
plugins {
1184-
tab-bar location=zellij:tab-bar
1185-
status-bar location=zellij:status-bar
1186-
welcome-screen location=zellij:session-manager {
1187-
welcome_screen #true
1188-
}
1189-
filepicker location=zellij:strider {
1190-
cwd "/"
1191-
}
1192-
}
1193-
mouse_mode #false
1194-
mirror_session #true
1195-
"##;
1196-
pretty_assertions::assert_eq!(KdlDocument::v1_to_v2(original)?, expected);
1176+
assert!(super::detect_v1(v1));
1177+
assert!(super::detect_v2(v2));
11971178
Ok(())
11981179
}
11991180
}

src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,9 @@
110110
//! version of `kdl-rs`, and so may be fairly heavy.
111111
//! * `v1-fallback` - Implies `v1`. Makes it so the various `*::parse()` and
112112
//! `FromStr` implementations try to parse their inputs as `v2`, and, if that
113-
//! fails, try again with `v1`. Errors will only be reported as if the input was
114-
//! `v2`. To manage this more precisely, you can use the `*::parse_v2` and
115-
//! `*::parse_v1` methods.
113+
//! fails, try again with `v1`. For `KdlDocument`, a heuristic will be applied
114+
//! if both `v1` and `v2` parsers fail, to pick which error(s) to return. For
115+
//! other types, only the `v2` parser's errors will be returned.
116116
//!
117117
//! ## Quirks
118118
//!

0 commit comments

Comments
 (0)