1#[cfg(feature = "span")]
2use miette::SourceSpan;
3use std::fmt::Display;
4
5#[cfg(feature = "v1")]
6use crate::KdlNodeFormat;
7use crate::{FormatConfig, KdlError, KdlNode, KdlValue};
8
9#[derive(Debug, Clone, Eq)]
24pub struct KdlDocument {
25 pub(crate) nodes: Vec<KdlNode>,
26 pub(crate) format: Option<KdlDocumentFormat>,
27 #[cfg(feature = "span")]
28 pub(crate) span: SourceSpan,
29}
30
31impl PartialEq for KdlDocument {
32 fn eq(&self, other: &Self) -> bool {
33 self.nodes == other.nodes && self.format == other.format
34 }
36}
37
38impl std::hash::Hash for KdlDocument {
39 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
40 self.nodes.hash(state);
41 self.format.hash(state);
42 }
44}
45
46impl Default for KdlDocument {
47 fn default() -> Self {
48 Self {
49 nodes: Default::default(),
50 format: Default::default(),
51 #[cfg(feature = "span")]
52 span: SourceSpan::from(0..0),
53 }
54 }
55}
56
57impl KdlDocument {
58 pub fn new() -> Self {
60 Default::default()
61 }
62
63 #[cfg(feature = "span")]
69 pub fn span(&self) -> SourceSpan {
70 self.span
71 }
72
73 #[cfg(feature = "span")]
75 pub fn set_span(&mut self, span: impl Into<SourceSpan>) {
76 self.span = span.into();
77 }
78
79 pub fn get(&self, name: &str) -> Option<&KdlNode> {
81 self.nodes.iter().find(move |n| n.name().value() == name)
82 }
83
84 pub fn get_mut(&mut self, name: &str) -> Option<&mut KdlNode> {
86 self.nodes
87 .iter_mut()
88 .find(move |n| n.name().value() == name)
89 }
90
91 pub fn get_arg(&self, name: &str) -> Option<&KdlValue> {
110 self.get(name).and_then(|node| node.get(0))
111 }
112
113 pub fn iter_args(&self, name: &str) -> impl Iterator<Item = &KdlValue> {
138 self.get(name)
139 .map(|n| n.entries())
140 .unwrap_or_default()
141 .iter()
142 .filter(|e| e.name().is_none())
143 .map(|e| e.value())
144 }
145
146 pub fn get_arg_mut(&mut self, name: &str) -> Option<&mut KdlValue> {
150 self.get_mut(name).and_then(|node| node.get_mut(0))
151 }
152
153 pub fn iter_dash_args(&self, name: &str) -> impl Iterator<Item = &KdlValue> {
177 self.get(name)
178 .and_then(|n| n.children())
179 .map(|doc| doc.nodes())
180 .unwrap_or_default()
181 .iter()
182 .filter(|e| e.name().value() == "-")
183 .filter_map(|e| e.get(0))
184 }
185
186 pub fn nodes(&self) -> &[KdlNode] {
188 &self.nodes
189 }
190
191 pub fn nodes_mut(&mut self) -> &mut Vec<KdlNode> {
193 &mut self.nodes
194 }
195
196 pub fn format(&self) -> Option<&KdlDocumentFormat> {
198 self.format.as_ref()
199 }
200
201 pub fn format_mut(&mut self) -> Option<&mut KdlDocumentFormat> {
203 self.format.as_mut()
204 }
205
206 pub fn set_format(&mut self, format: KdlDocumentFormat) {
208 self.format = Some(format);
209 }
210
211 pub fn len(&self) -> usize {
213 format!("{self}").len()
214 }
215
216 pub fn is_empty(&self) -> bool {
218 self.len() == 0
219 }
220
221 pub fn clear_format(&mut self) {
226 self.format = None;
227 }
228
229 pub fn clear_format_recursive(&mut self) {
232 self.clear_format();
233 for node in self.nodes.iter_mut() {
234 node.clear_format_recursive();
235 }
236 }
237
238 pub fn autoformat(&mut self) {
241 self.autoformat_config(&FormatConfig::default());
242 }
243
244 pub fn autoformat_no_comments(&mut self) {
246 self.autoformat_config(&FormatConfig {
247 no_comments: true,
248 ..Default::default()
249 });
250 }
251
252 pub fn autoformat_config(&mut self, config: &FormatConfig<'_>) {
254 if let Some(KdlDocumentFormat { leading, .. }) = (*self).format_mut() {
255 crate::fmt::autoformat_leading(leading, config);
256 }
257 let mut has_nodes = false;
258 for node in &mut self.nodes {
259 has_nodes = true;
260 node.autoformat_config(config);
261 }
262 if let Some(KdlDocumentFormat { trailing, .. }) = (*self).format_mut() {
263 crate::fmt::autoformat_trailing(trailing, config.no_comments);
264 if !has_nodes {
265 trailing.push('\n');
266 }
267 };
268 }
269
270 pub fn parse(s: &str) -> Result<Self, KdlError> {
348 #[cfg(not(feature = "v1-fallback"))]
349 {
350 Self::parse_v2(s)
351 }
352 #[cfg(feature = "v1-fallback")]
353 {
354 let v2_res = KdlDocument::parse_v2(s);
355 if v2_res.is_err() {
356 let v1_res = KdlDocument::parse_v1(s);
357 if v1_res.is_ok() || detect_v1(s) {
358 v1_res
359 } else {
360 v2_res
364 }
365 } else {
366 v2_res
367 }
368 }
369 }
370
371 pub fn parse_v2(s: &str) -> Result<Self, KdlError> {
373 crate::v2_parser::try_parse(crate::v2_parser::document, s)
374 }
375
376 #[cfg(feature = "v1")]
378 pub fn parse_v1(s: &str) -> Result<Self, KdlError> {
379 let ret: Result<kdlv1::KdlDocument, kdlv1::KdlError> = s.parse();
380 ret.map(|x| x.into()).map_err(|e| e.into())
381 }
382
383 #[cfg(feature = "v1")]
386 pub fn v1_to_v2(s: &str) -> Result<String, KdlError> {
387 let mut doc = KdlDocument::parse_v1(s)?;
388 doc.ensure_v2();
389 Ok(doc.to_string())
390 }
391
392 #[cfg(feature = "v1")]
395 pub fn v2_to_v1(s: &str) -> Result<String, KdlError> {
396 let mut doc = KdlDocument::parse_v2(s)?;
397 doc.ensure_v1();
398 Ok(doc.to_string())
399 }
400
401 pub fn ensure_v2(&mut self) {
403 for node in self.nodes_mut().iter_mut() {
407 node.ensure_v2();
408 }
409 }
410
411 #[cfg(feature = "v1")]
413 pub fn ensure_v1(&mut self) {
414 let mut iter = self.nodes_mut().iter_mut().rev();
420 let last = iter.next();
421 let penult = iter.next();
422 if let Some(last) = last {
423 if let Some(fmt) = last.format_mut() {
424 if !fmt.trailing.contains(';')
425 && fmt
426 .trailing
427 .chars()
428 .any(|c| crate::v2_parser::NEWLINES.iter().any(|nl| nl.contains(c)))
429 {
430 fmt.terminator = ";".into();
431 }
432 } else {
433 let maybe_indent = {
434 if let Some(penult) = penult {
435 if let Some(fmt) = penult.format() {
436 fmt.leading.clone()
437 } else {
438 "".into()
439 }
440 } else {
441 "".into()
442 }
443 };
444 last.format = Some(KdlNodeFormat {
445 leading: maybe_indent,
446 terminator: "\n".into(),
447 ..Default::default()
448 })
449 }
450 }
451 for node in self.nodes_mut().iter_mut() {
452 node.ensure_v1();
453 }
454 }
455}
456
457#[cfg(feature = "v1")]
458impl From<kdlv1::KdlDocument> for KdlDocument {
459 fn from(value: kdlv1::KdlDocument) -> Self {
460 Self {
461 nodes: value.nodes().iter().map(|x| x.clone().into()).collect(),
462 format: Some(KdlDocumentFormat {
463 leading: value.leading().unwrap_or("").into(),
464 trailing: value.trailing().unwrap_or("").into(),
465 }),
466 #[cfg(feature = "span")]
467 span: SourceSpan::new(value.span().offset().into(), value.span().len()),
468 }
469 }
470}
471
472#[allow(unused)]
475pub(crate) fn detect_v2(input: &str) -> bool {
476 for line in input.lines() {
477 if line.contains("kdl-version 2")
478 || line.contains("#true")
479 || line.contains("#false")
480 || line.contains("#null")
481 || line.contains("#inf")
482 || line.contains("#-inf")
483 || line.contains("#nan")
484 || line.contains(" #\"")
485 || line.contains("\"\"\"")
486 || (!line.contains('"') && line
489 .split_whitespace()
490 .skip(1)
491 .any(|x| {
492 x.chars()
493 .next()
494 .map(|d| !d.is_ascii_digit() && d != '-' && d != '+')
495 .unwrap_or_default()
496 }))
497 {
498 return true;
499 }
500 }
501 false
502}
503
504#[allow(unused)]
507pub(crate) fn detect_v1(input: &str) -> bool {
508 input
509 .lines()
510 .next()
511 .map(|l| l.contains("kdl-version 1"))
512 .unwrap_or(false)
513 || input.contains(" true")
514 || input.contains(" false")
515 || input.contains(" null")
516 || input.contains("r#\"")
517 || input.contains(" \"\n")
518 || input.contains(" \"\r\n")
519}
520
521impl std::str::FromStr for KdlDocument {
522 type Err = KdlError;
523
524 fn from_str(s: &str) -> Result<Self, Self::Err> {
525 Self::parse(s)
526 }
527}
528
529impl Display for KdlDocument {
530 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
531 self.stringify(f, 0)
532 }
533}
534
535impl KdlDocument {
536 pub(crate) fn stringify(
537 &self,
538 f: &mut std::fmt::Formatter<'_>,
539 indent: usize,
540 ) -> std::fmt::Result {
541 if let Some(KdlDocumentFormat { leading, .. }) = self.format() {
542 write!(f, "{leading}")?;
543 }
544 for node in &self.nodes {
545 node.stringify(f, indent)?;
546 }
547 if let Some(KdlDocumentFormat { trailing, .. }) = self.format() {
548 write!(f, "{trailing}")?;
549 }
550 Ok(())
551 }
552}
553
554impl IntoIterator for KdlDocument {
555 type Item = KdlNode;
556 type IntoIter = std::vec::IntoIter<Self::Item>;
557
558 fn into_iter(self) -> Self::IntoIter {
559 self.nodes.into_iter()
560 }
561}
562
563#[derive(Debug, Clone, Default, Hash, Eq, PartialEq)]
565pub struct KdlDocumentFormat {
566 pub leading: String,
568 pub trailing: String,
570}
571
572#[cfg(test)]
573mod test {
574 #[cfg(feature = "span")]
575 use crate::KdlIdentifier;
576 use crate::{KdlEntry, KdlValue};
577
578 use super::*;
579
580 #[test]
581 fn canonical_clear_fmt() -> miette::Result<()> {
582 let left_src = r#"
583// There is a node here
584first_node /*with cool comments, too */ param=1.03e2 /-"commented" "argument" {
585 // With nested nodes too
586 nested 1 2 3
587 nested_2 "hi" "world" // this one is cool
588}
589second_node param=153 { nested one=1 two=2; }"#;
590 let right_src = r#"
591first_node param=103.0 "argument" {
592 // Different indentation, because
593 // Why not
594 nested 1 2 3
595 nested_2 "hi" /* actually, "hello" */ "world"
596}
597// There is a node here
598second_node /* This time, the comment is here */ param=153 {
599 nested one=1 two=2
600}"#;
601 let mut left_doc: KdlDocument = left_src.parse()?;
602 let mut right_doc: KdlDocument = right_src.parse()?;
603 assert_ne!(left_doc, right_doc);
604 left_doc.clear_format_recursive();
605 right_doc.clear_format_recursive();
606 assert_eq!(left_doc, right_doc);
607 Ok(())
608 }
609
610 #[test]
611 fn basic_parsing() -> miette::Result<()> {
612 let src = r#"
613 // Hello, world!
614 node 1
615 node two
616 node item="three";
617 node {
618 nested 1 2 3
619 nested_2 hi "world"
620 }
621 (type)node ("type")what?
622 +false #true
623 null_id null_prop=#null
624 foo indented
625 // normal comment?
626 /- comment
627 /* block comment */
628 inline /*comment*/ here
629 another /-comment there
630
631
632 after some whitespace
633 trailing /* multiline */
634 trailing // single line
635 "#;
636 let _doc: KdlDocument = src.parse()?;
637 Ok(())
638 }
639
640 #[test]
641 fn parsing() -> miette::Result<()> {
642 let src = "
643// This is the first node
644foo 1 2 three #null #true bar=\"baz\" {
645 - 1
646 - 2
647 - three
648 (mytype)something (\"name\")else\r
649}
650
651null_id null_prop=#null
652true_id true_prop=#null
653+false #true
654
655 bar \"indented\" // trailing whitespace after this\t
656/*
657Some random comment
658 */
659
660a; b; c;
661/-commented \"node\"
662
663another /*foo*/ \"node\" /-1 /*bar*/ #null;
664final;";
665 let mut doc: KdlDocument = src.parse()?;
666
667 assert_eq!(doc.get_arg("foo"), Some(&1.into()));
668 assert_eq!(
669 doc.iter_dash_args("foo").collect::<Vec<&KdlValue>>(),
670 vec![&1.into(), &2.into(), &"three".into()]
671 );
672 assert_eq!(doc.format().map(|f| &f.leading[..]), Some(""));
673
674 let foo = doc.get("foo").expect("expected a foo node");
675 assert_eq!(
676 foo.format().map(|f| &f.leading[..]),
677 Some("\n// This is the first node\n")
678 );
679 assert_eq!(foo.format().map(|f| &f.terminator[..]), Some("\n"));
680 assert_eq!(&foo[2], &"three".into());
681 assert_eq!(&foo["bar"], &"baz".into());
682 assert_eq!(
683 foo.children().unwrap().get_arg("something"),
684 Some(&"else".into())
685 );
686 assert_eq!(doc.get_arg("another"), Some(&"node".into()));
687
688 let null = doc.get("null_id").expect("expected a null_id node");
689 assert_eq!(&null["null_prop"], &KdlValue::Null);
690
691 let tru = doc.get("true_id").expect("expected a true_id node");
692 assert_eq!(&tru["true_prop"], &KdlValue::Null);
693
694 let plusfalse = doc.get("+false").expect("expected a +false node");
695 assert_eq!(&plusfalse[0], &KdlValue::Bool(true));
696
697 let bar = doc.get("bar").expect("expected a bar node");
698 assert_eq!(
699 format!("{bar}"),
700 "\n bar \"indented\" // trailing whitespace after this\t\n"
701 );
702
703 let a = doc.get("a").expect("expected a node");
704 assert_eq!(
705 format!("{a}"),
706 "/*\nSome random comment\n */\n\na;".to_string()
707 );
708
709 let b = doc.get("b").expect("expected a node");
710 assert_eq!(format!("{b}"), " b;".to_string());
711
712 assert_eq!(format!("{doc}"), src);
714
715 let mut node: KdlNode = "new\n".parse()?;
717 node.push(" \"blah\"=0xDEADbeef".parse::<KdlEntry>()?);
721 doc.nodes_mut().push(node);
722
723 assert_eq!(
724 format!("{doc}"),
725 format!("{}new \"blah\"=0xDEADbeef\n", src)
726 );
727
728 Ok(())
729 }
730
731 #[test]
732 fn construction() {
733 let mut doc = KdlDocument::new();
734 doc.nodes_mut().push(KdlNode::new("foo"));
735
736 let mut bar = KdlNode::new("bar");
737 bar.insert("prop", "value");
738 bar.push(1);
739 bar.push(2);
740 bar.push(false);
741 bar.push(KdlValue::Null);
742
743 let subdoc = bar.ensure_children();
744 subdoc.nodes_mut().push(KdlNode::new("barchild"));
745 doc.nodes_mut().push(bar);
746 doc.nodes_mut().push(KdlNode::new("baz"));
747
748 doc.autoformat();
749
750 assert_eq!(
751 r#"foo
752bar prop=value 1 2 #false #null {
753 barchild
754}
755baz
756"#,
757 format!("{doc}")
758 );
759 }
760
761 #[ignore = "There's still issues around formatting comments and esclines."]
762 #[test]
763 fn autoformat() -> miette::Result<()> {
764 let mut doc: KdlDocument = r##"
765
766 /* x */ foo 1 "bar"=0xDEADbeef {
767 child1 1 ;
768
769 // child 2 comment
770
771 child2 2 /-3 // comment
772
773 child3 "
774
775 string\t
776 " \
777{
778 /*
779
780
781 multiline*/
782 inner1 \
783 #"value"# \
784 ;
785
786 inner2 \ //comment
787 {
788 inner3
789 }
790 }
791 }
792
793 // trailing comment here
794
795 "##
796 .parse()?;
797
798 KdlDocument::autoformat(&mut doc);
799
800 assert_eq!(
801 doc.to_string(),
802 r#"/* x */
803foo 1 bar=0xdeadbeef {
804 child1 1
805 // child 2 comment
806 child2 2 /-3 // comment
807 child3 "\nstring\t" {
808 /*
809
810
811 multiline*/
812 inner1 value
813 inner2 {
814 inner3
815 }
816 }
817}
818// trailing comment here"#
819 );
820 Ok(())
821 }
822
823 #[test]
824 fn simple_autoformat() -> miette::Result<()> {
825 let mut doc: KdlDocument = "a { b { c { }; }; }".parse().unwrap();
826 KdlDocument::autoformat(&mut doc);
827 assert_eq!(
828 doc.to_string(),
829 r#"a {
830 b {
831 c {
832
833 }
834 }
835}
836"#
837 );
838 Ok(())
839 }
840
841 #[test]
842 fn simple_autoformat_two_spaces() -> miette::Result<()> {
843 let mut doc: KdlDocument = "a { b { c { }; }; }".parse().unwrap();
844 KdlDocument::autoformat_config(
845 &mut doc,
846 &FormatConfig {
847 indent: " ",
848 ..Default::default()
849 },
850 );
851 assert_eq!(
852 doc.to_string(),
853 r#"a {
854 b {
855 c {
856
857 }
858 }
859}
860"#
861 );
862 Ok(())
863 }
864
865 #[test]
866 fn simple_autoformat_single_tabs() -> miette::Result<()> {
867 let mut doc: KdlDocument = "a { b { c { }; }; }".parse().unwrap();
868 KdlDocument::autoformat_config(
869 &mut doc,
870 &FormatConfig {
871 indent: "\t",
872 ..Default::default()
873 },
874 );
875 assert_eq!(doc.to_string(), "a {\n\tb {\n\t\tc {\n\n\t\t}\n\t}\n}\n");
876 Ok(())
877 }
878
879 #[test]
880 fn simple_autoformat_no_comments() -> miette::Result<()> {
881 let mut doc: KdlDocument =
882 "// a comment\na {\n// another comment\n b { c { // another comment\n }; }; }"
883 .parse()
884 .unwrap();
885 KdlDocument::autoformat_no_comments(&mut doc);
886 assert_eq!(
887 doc.to_string(),
888 r#"a {
889 b {
890 c {
891
892 }
893 }
894}
895"#
896 );
897 Ok(())
898 }
899
900 #[cfg(feature = "span")]
901 fn check_spans_for_doc(doc: &KdlDocument, source: &impl miette::SourceCode) {
902 for node in doc.nodes() {
903 check_spans_for_node(node, source);
904 }
905 }
906
907 #[cfg(feature = "span")]
908 fn check_spans_for_node(node: &KdlNode, source: &impl miette::SourceCode) {
909 use crate::KdlEntryFormat;
910
911 check_span_for_ident(node.name(), source);
912 if let Some(ty) = node.ty() {
913 check_span_for_ident(ty, source);
914 }
915
916 for entry in node.entries() {
917 if let Some(name) = entry.name() {
918 check_span_for_ident(name, source);
919 }
920 if let Some(ty) = entry.ty() {
921 check_span_for_ident(ty, source);
922 }
923 if let Some(KdlEntryFormat { value_repr, .. }) = entry.format() {
924 if entry.name().is_none() && entry.ty().is_none() {
925 check_span(value_repr, entry.span(), source);
926 }
927 }
928 }
929 if let Some(children) = node.children() {
930 check_spans_for_doc(children, source);
931 }
932 }
933
934 #[cfg(feature = "span")]
935 #[track_caller]
936 fn check_span_for_ident(ident: &KdlIdentifier, source: &impl miette::SourceCode) {
937 if let Some(repr) = ident.repr() {
938 check_span(repr, ident.span(), source);
939 } else {
940 check_span(ident.value(), ident.span(), source);
941 }
942 }
943
944 #[cfg(feature = "span")]
945 #[track_caller]
946 fn check_span(expected: &str, span: SourceSpan, source: &impl miette::SourceCode) {
947 let span = source.read_span(&span, 0, 0).unwrap();
948 let span = std::str::from_utf8(span.data()).unwrap();
949 assert_eq!(span, expected);
950 }
951
952 #[cfg(feature = "span")]
953 #[test]
954 fn span_test() -> miette::Result<()> {
955 let input = r####"
956this {
957 is (a)"cool" document="to" read=(int)5 10.1 (u32)0x45
958 and x="" {
959 "it" /*shh*/ "has"="💯" ##"the"##
960 Best🎊est
961 "syntax ever"
962 }
963 "yknow?" 0x10
964}
965// that's
966nice
967inline { time; to; live "our" "dreams"; "y;all" }
968
969"####;
970
971 let doc: KdlDocument = input.parse()?;
972
973 check_spans_for_doc(&doc, &input);
975
976 check_span(&input[1..(input.len() - 2)], doc.span(), &input);
981
982 let is_node = doc
984 .get("this")
985 .unwrap()
986 .children()
987 .unwrap()
988 .get("is")
989 .unwrap();
990 check_span(
991 r##"is (a)"cool" document="to" read=(int)5 10.1 (u32)0x45"##,
992 is_node.span(),
993 &input,
994 );
995
996 check_span(r#"(a)"cool""#, is_node.entry(0).unwrap().span(), &input);
998 check_span(
999 r#"read=(int)5"#,
1000 is_node.entry("read").unwrap().span(),
1001 &input,
1002 );
1003 check_span(r#"10.1"#, is_node.entry(1).unwrap().span(), &input);
1004 check_span(r#"(u32)0x45"#, is_node.entry(2).unwrap().span(), &input);
1005
1006 let and_node = doc
1008 .get("this")
1009 .unwrap()
1010 .children()
1011 .unwrap()
1012 .get("and")
1013 .unwrap();
1014
1015 check_span(
1017 r####"and x="" {
1018 "it" /*shh*/ "has"="💯" ##"the"##
1019 Best🎊est
1020 "syntax ever"
1021 }"####,
1022 and_node.span(),
1023 &input,
1024 );
1025
1026 check_span(
1029 r####""it" /*shh*/ "has"="💯" ##"the"##
1030 Best🎊est
1031 "syntax ever""####,
1032 and_node.children().unwrap().span(),
1033 &input,
1034 );
1035
1036 check_span(r#"x="""#, and_node.entry("x").unwrap().span(), &input);
1038
1039 let it_node = and_node.children().unwrap().get("it").unwrap();
1041 check_span(
1042 r####""it" /*shh*/ "has"="💯" ##"the"##"####,
1043 it_node.span(),
1044 &input,
1045 );
1046 check_span(
1047 r#""has"="💯""#,
1048 it_node.entry("has").unwrap().span(),
1049 &input,
1050 );
1051 check_span(
1052 r####"##"the"##"####,
1053 it_node.entry(0).unwrap().span(),
1054 &input,
1055 );
1056
1057 let inline_node = doc.get("inline").unwrap();
1059 check_span(
1060 r#"inline { time; to; live "our" "dreams"; "y;all" }"#,
1061 inline_node.span(),
1062 &input,
1063 );
1064
1065 let inline_children = inline_node.children().unwrap();
1066 check_span(
1067 r#"time; to; live "our" "dreams"; "y;all" "#,
1068 inline_children.span(),
1069 &input,
1070 );
1071
1072 let inline_nodes = inline_children.nodes();
1073 check_span("time", inline_nodes[0].span(), &input);
1074 check_span("to", inline_nodes[1].span(), &input);
1075 check_span(r#"live "our" "dreams""#, inline_nodes[2].span(), &input);
1076 check_span(r#""y;all" "#, inline_nodes[3].span(), &input);
1077
1078 Ok(())
1079 }
1080
1081 #[test]
1082 fn parse_examples() -> miette::Result<()> {
1083 include_str!("../examples/kdl-schema.kdl").parse::<KdlDocument>()?;
1084 include_str!("../examples/Cargo.kdl").parse::<KdlDocument>()?;
1085 include_str!("../examples/ci.kdl").parse::<KdlDocument>()?;
1086 include_str!("../examples/nuget.kdl").parse::<KdlDocument>()?;
1087 include_str!("../examples/website.kdl").parse::<KdlDocument>()?;
1088 include_str!("../examples/zellij.kdl").parse::<KdlDocument>()?;
1089 include_str!("../examples/zellij-unquoted-bindings.kdl").parse::<KdlDocument>()?;
1090 Ok(())
1091 }
1092
1093 #[cfg(feature = "v1")]
1094 #[test]
1095 fn v1_v2_conversions() -> miette::Result<()> {
1096 let v1 = r##"
1097// If you'd like to override the default keybindings completely, be sure to change "keybinds" to "keybinds clear-defaults=true"
1098keybinds {
1099 normal {
1100 // uncomment this and adjust key if using copy_on_select=false
1101 // bind "Alt c" { Copy; }
1102 }
1103 locked {
1104 bind "Ctrl g" { SwitchToMode "Normal"; }
1105 }
1106 resize {
1107 bind "Ctrl n" { SwitchToMode "Normal"; }
1108 bind "h" "Left" { Resize "Increase Left"; }
1109 bind "j" "Down" { Resize "Increase Down"; }
1110 bind "k" "Up" { Resize "Increase Up"; }
1111 bind "l" "Right" { Resize "Increase Right"; }
1112 bind "H" { Resize "Decrease Left"; }
1113 bind "J" { Resize "Decrease Down"; }
1114 bind "K" { Resize "Decrease Up"; }
1115 bind "L" { Resize "Decrease Right"; }
1116 bind "=" "+" { Resize "Increase"; }
1117 bind "-" { Resize "Decrease"; }
1118 }
1119}
1120// Plugin aliases - can be used to change the implementation of Zellij
1121// changing these requires a restart to take effect
1122plugins {
1123 tab-bar location="zellij:tab-bar"
1124 status-bar location="zellij:status-bar"
1125 welcome-screen location="zellij:session-manager" {
1126 welcome_screen true
1127 }
1128 filepicker location="zellij:strider" {
1129 cwd "\/"
1130 }
1131}
1132mouse_mode false
1133mirror_session true
1134"##;
1135 let v2 = r##"
1136// If you'd like to override the default keybindings completely, be sure to change "keybinds" to "keybinds clear-defaults=true"
1137keybinds {
1138 normal {
1139 // uncomment this and adjust key if using copy_on_select=false
1140 // bind "Alt c" { Copy; }
1141 }
1142 locked {
1143 bind "Ctrl g" { SwitchToMode Normal; }
1144 }
1145 resize {
1146 bind "Ctrl n" { SwitchToMode Normal; }
1147 bind h Left { Resize "Increase Left"; }
1148 bind j Down { Resize "Increase Down"; }
1149 bind k Up { Resize "Increase Up"; }
1150 bind l Right { Resize "Increase Right"; }
1151 bind H { Resize "Decrease Left"; }
1152 bind J { Resize "Decrease Down"; }
1153 bind K { Resize "Decrease Up"; }
1154 bind L { Resize "Decrease Right"; }
1155 bind "=" + { Resize Increase; }
1156 bind - { Resize Decrease; }
1157 }
1158}
1159// Plugin aliases - can be used to change the implementation of Zellij
1160// changing these requires a restart to take effect
1161plugins {
1162 tab-bar location=zellij:tab-bar
1163 status-bar location=zellij:status-bar
1164 welcome-screen location=zellij:session-manager {
1165 welcome_screen #true
1166 }
1167 filepicker location=zellij:strider {
1168 cwd "/"
1169 }
1170}
1171mouse_mode #false
1172mirror_session #true
1173"##;
1174 pretty_assertions::assert_eq!(KdlDocument::v1_to_v2(v1)?, v2, "Converting a v1 doc to v2");
1175 pretty_assertions::assert_eq!(KdlDocument::v2_to_v1(v2)?, v1, "Converting a v2 doc to v1");
1176 assert!(super::detect_v1(v1));
1177 assert!(super::detect_v2(v2));
1178 Ok(())
1179 }
1180}