From 6a7248c40516dc8e25fab81179b5677b3ab4823b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Thu, 19 Dec 2024 23:15:24 -0800 Subject: [PATCH] feat(v1): add rudimentary, optional, KDL v1 parsing (#104) This is kinda rough right now, since it involves actually pulling in the old kdl v1. Not ideal, but workable as a stepping stone while a real v1 parser gets written. --- Cargo.toml | 7 +++++-- clippy.toml | 2 +- src/document.rs | 42 +++++++++++++++++++++++++++++++++++++++- src/entry.rs | 46 +++++++++++++++++++++++++++++++++++++++++++- src/error.rs | 18 +++++++++++++++++ src/identifier.rs | 39 ++++++++++++++++++++++++++++++++++++- src/node.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++ src/value.rs | 17 ++++++++++++++++ 8 files changed, 214 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 766a5c2..9e19ab9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,18 +8,21 @@ readme = "README.md" homepage = "https://kdl.dev" repository = "https://github.com/kdl-org/kdl-rs" keywords = ["kdl", "document", "serialization", "config"] -rust-version = "1.56.0" +rust-version = "1.70.0" edition = "2021" [features] -default = ["span"] +default = ["span", "v1"] span = [] +v1-fallback = ["v1"] +v1 = ["kdlv1"] [dependencies] miette = "7.2.0" num = "0.4.2" thiserror = "1.0.40" winnow = { version = "0.6.20", features = ["alloc", "unstable-recover"] } +kdlv1 = { package = "kdl", version = "4.7.0", optional = true } [dev-dependencies] miette = { version = "7.2.0", features = ["fancy"] } diff --git a/clippy.toml b/clippy.toml index 0d369b5..1645c19 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1 @@ -msrv = "1.56.0" +msrv = "1.70.0" diff --git a/src/document.rs b/src/document.rs index a130ab1..0e2456b 100644 --- a/src/document.rs +++ b/src/document.rs @@ -334,13 +334,53 @@ impl KdlDocument { // .query_all(query)? // .filter_map(move |node| node.get(key.clone()))) // } + + /// Parses a string into a document. + /// + /// If the `v1-fallback` feature is enabled, this method will first try to + /// parse the string as a KDL v2 document, and, if that fails, it will try + /// to parse again as a KDL v1 document. If both fail, only the v2 parse + /// errors will be returned. + pub fn parse(s: &str) -> Result { + #[cfg(not(feature = "v1-fallback"))] + { + crate::v2_parser::try_parse(crate::v2_parser::document, s) + } + #[cfg(feature = "v1-fallback")] + { + crate::v2_parser::try_parse(crate::v2_parser::document, s) + .or_else(|e| KdlDocument::parse_v1(s).map_err(|_| e)) + } + } + + /// Parses a KDL v1 string into a document. + #[cfg(feature = "v1")] + pub fn parse_v1(s: &str) -> Result { + let ret: Result = s.parse(); + ret.map(|x| x.into()).map_err(|e| e.into()) + } +} + +#[cfg(feature = "v1")] +impl From for KdlDocument { + fn from(value: kdlv1::KdlDocument) -> Self { + KdlDocument { + nodes: value.nodes().iter().map(|x| x.clone().into()).collect(), + format: Some(KdlDocumentFormat { + leading: value.leading().unwrap_or("").into(), + trailing: value.trailing().unwrap_or("").into(), + }), + #[cfg(feature = "span")] + span: SourceSpan::new(value.span().offset().into(), value.span().len()), + } + } } impl std::str::FromStr for KdlDocument { type Err = KdlParseFailure; fn from_str(s: &str) -> Result { - crate::v2_parser::try_parse(crate::v2_parser::document, s) + KdlDocument::parse(s) } } diff --git a/src/entry.rs b/src/entry.rs index f941c83..7bd02f4 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -178,6 +178,50 @@ impl KdlEntry { name.autoformat(); } } + + /// Parses a string into a entry. + /// + /// If the `v1-fallback` feature is enabled, this method will first try to + /// parse the string as a KDL v2 entry, and, if that fails, it will try + /// to parse again as a KDL v1 entry. If both fail, only the v2 parse + /// errors will be returned. + pub fn parse(s: &str) -> Result { + #[cfg(not(feature = "v1-fallback"))] + { + v2_parser::try_parse(v2_parser::padded_node_entry, s) + } + #[cfg(feature = "v1-fallback")] + { + v2_parser::try_parse(v2_parser::padded_node_entry, s) + .or_else(|e| KdlEntry::parse_v1(s).map_err(|_| e)) + } + } + + /// Parses a KDL v1 string into an entry. + #[cfg(feature = "v1")] + pub fn parse_v1(s: &str) -> Result { + let ret: Result = s.parse(); + ret.map(|x| x.into()).map_err(|e| e.into()) + } +} + +#[cfg(feature = "v1")] +impl From for KdlEntry { + fn from(value: kdlv1::KdlEntry) -> Self { + KdlEntry { + ty: value.ty().map(|x| x.clone().into()), + value: value.value().clone().into(), + name: value.name().map(|x| x.clone().into()), + format: Some(KdlEntryFormat { + value_repr: value.value_repr().unwrap_or("").into(), + leading: value.leading().unwrap_or("").into(), + trailing: value.trailing().unwrap_or("").into(), + ..Default::default() + }), + #[cfg(feature = "span")] + span: SourceSpan::new(value.span().offset().into(), value.span().len()), + } + } } impl Display for KdlEntry { @@ -249,7 +293,7 @@ impl FromStr for KdlEntry { type Err = KdlParseFailure; fn from_str(s: &str) -> Result { - v2_parser::try_parse(v2_parser::padded_node_entry, s) + KdlEntry::parse(s) } } diff --git a/src/error.rs b/src/error.rs index 533b6b6..16aaac8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -74,3 +74,21 @@ pub struct KdlDiagnostic { #[diagnostic(severity)] pub severity: miette::Severity, } + +#[cfg(feature = "v1")] +impl From for KdlParseFailure { + fn from(value: kdlv1::KdlError) -> Self { + let input = Arc::new(value.input); + KdlParseFailure { + input: input.clone(), + diagnostics: vec![KdlDiagnostic { + input, + span: SourceSpan::new(value.span.offset().into(), value.span.len()), + message: Some(format!("{}", value.kind)), + label: value.label.map(|x| x.into()), + help: value.help.map(|x| x.into()), + severity: miette::Severity::Error, + }], + } + } +} diff --git a/src/identifier.rs b/src/identifier.rs index 4395ee6..57d4134 100644 --- a/src/identifier.rs +++ b/src/identifier.rs @@ -87,6 +87,43 @@ impl KdlIdentifier { pub fn autoformat(&mut self) { self.repr = None; } + + /// Parses a string into a entry. + /// + /// If the `v1-fallback` feature is enabled, this method will first try to + /// parse the string as a KDL v2 entry, and, if that fails, it will try + /// to parse again as a KDL v1 entry. If both fail, only the v2 parse + /// errors will be returned. + pub fn parse(s: &str) -> Result { + #[cfg(not(feature = "v1-fallback"))] + { + v2_parser::try_parse(v2_parser::identifier, s) + } + #[cfg(feature = "v1-fallback")] + { + v2_parser::try_parse(v2_parser::identifier, s) + .or_else(|e| KdlIdentifier::parse_v1(s).map_err(|_| e)) + } + } + + /// Parses a KDL v1 string into an entry. + #[cfg(feature = "v1")] + pub fn parse_v1(s: &str) -> Result { + let ret: Result = s.parse(); + ret.map(|x| x.into()).map_err(|e| e.into()) + } +} + +#[cfg(feature = "v1")] +impl From for KdlIdentifier { + fn from(value: kdlv1::KdlIdentifier) -> Self { + KdlIdentifier { + value: value.value().into(), + repr: value.repr().map(|x| x.into()), + #[cfg(feature = "span")] + span: SourceSpan::new(value.span().offset().into(), value.span().len()), + } + } } impl Display for KdlIdentifier { @@ -131,7 +168,7 @@ impl FromStr for KdlIdentifier { type Err = KdlParseFailure; fn from_str(s: &str) -> Result { - v2_parser::try_parse(v2_parser::identifier, s) + KdlIdentifier::parse(s) } } diff --git a/src/node.rs b/src/node.rs index 09bced1..ed8f331 100644 --- a/src/node.rs +++ b/src/node.rs @@ -325,6 +325,55 @@ impl KdlNode { } } } + + /// Parses a string into a node. + /// + /// If the `v1-fallback` feature is enabled, this method will first try to + /// parse the string as a KDL v2 node, and, if that fails, it will try + /// to parse again as a KDL v1 node. If both fail, only the v2 parse + /// errors will be returned. + pub fn parse(s: &str) -> Result { + #[cfg(not(feature = "v1-fallback"))] + { + v2_parser::try_parse(v2_parser::padded_node, s) + } + #[cfg(feature = "v1-fallback")] + { + v2_parser::try_parse(v2_parser::padded_node, s) + .or_else(|e| KdlNode::parse_v1(s).map_err(|_| e)) + } + } + + /// Parses a KDL v1 string into a document. + #[cfg(feature = "v1")] + pub fn parse_v1(s: &str) -> Result { + let ret: Result = s.parse(); + ret.map(|x| x.into()).map_err(|e| e.into()) + } +} + +#[cfg(feature = "v1")] +impl From for KdlNode { + fn from(value: kdlv1::KdlNode) -> Self { + KdlNode { + ty: value.ty().map(|x| x.clone().into()), + name: value.name().clone().into(), + entries: value.entries().iter().map(|x| x.clone().into()).collect(), + children: value.children().map(|x| x.clone().into()), + format: Some(KdlNodeFormat { + leading: value.leading().unwrap_or("").into(), + before_ty_name: "".into(), + after_ty_name: "".into(), + after_ty: "".into(), + before_children: value.before_children().unwrap_or("").into(), + before_terminator: "".into(), + terminator: "".into(), + trailing: value.trailing().unwrap_or("").into(), + }), + #[cfg(feature = "span")] + span: SourceSpan::new(value.span().offset().into(), value.span().len()), + } + } } // Query language diff --git a/src/value.rs b/src/value.rs index 733a3ed..8fe4722 100644 --- a/src/value.rs +++ b/src/value.rs @@ -261,6 +261,23 @@ where } } +#[cfg(feature = "v1")] +impl From for KdlValue { + fn from(value: kdlv1::KdlValue) -> Self { + match value { + kdlv1::KdlValue::RawString(s) => KdlValue::String(s), + kdlv1::KdlValue::String(s) => KdlValue::String(s), + kdlv1::KdlValue::Base2(i) => KdlValue::Integer(i.into()), + kdlv1::KdlValue::Base8(i) => KdlValue::Integer(i.into()), + kdlv1::KdlValue::Base10(i) => KdlValue::Integer(i.into()), + kdlv1::KdlValue::Base10Float(f) => KdlValue::Float(f), + kdlv1::KdlValue::Base16(i) => KdlValue::Integer(i.into()), + kdlv1::KdlValue::Bool(b) => KdlValue::Bool(b), + kdlv1::KdlValue::Null => KdlValue::Null, + } + } +} + #[cfg(test)] mod test { use super::*;