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.
This commit is contained in:
Kat Marchán 2024-12-19 23:15:24 -08:00 committed by GitHub
parent 683e87a142
commit 6a7248c405
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 214 additions and 6 deletions

View File

@ -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"] }

View File

@ -1 +1 @@
msrv = "1.56.0"
msrv = "1.70.0"

View File

@ -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<Self, KdlParseFailure> {
#[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<Self, KdlParseFailure> {
let ret: Result<kdlv1::KdlDocument, kdlv1::KdlError> = s.parse();
ret.map(|x| x.into()).map_err(|e| e.into())
}
}
#[cfg(feature = "v1")]
impl From<kdlv1::KdlDocument> 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<Self, Self::Err> {
crate::v2_parser::try_parse(crate::v2_parser::document, s)
KdlDocument::parse(s)
}
}

View File

@ -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<Self, KdlParseFailure> {
#[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<Self, KdlParseFailure> {
let ret: Result<kdlv1::KdlEntry, kdlv1::KdlError> = s.parse();
ret.map(|x| x.into()).map_err(|e| e.into())
}
}
#[cfg(feature = "v1")]
impl From<kdlv1::KdlEntry> 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<Self, Self::Err> {
v2_parser::try_parse(v2_parser::padded_node_entry, s)
KdlEntry::parse(s)
}
}

View File

@ -74,3 +74,21 @@ pub struct KdlDiagnostic {
#[diagnostic(severity)]
pub severity: miette::Severity,
}
#[cfg(feature = "v1")]
impl From<kdlv1::KdlError> 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,
}],
}
}
}

View File

@ -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<Self, KdlParseFailure> {
#[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<Self, KdlParseFailure> {
let ret: Result<kdlv1::KdlIdentifier, kdlv1::KdlError> = s.parse();
ret.map(|x| x.into()).map_err(|e| e.into())
}
}
#[cfg(feature = "v1")]
impl From<kdlv1::KdlIdentifier> 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<Self, Self::Err> {
v2_parser::try_parse(v2_parser::identifier, s)
KdlIdentifier::parse(s)
}
}

View File

@ -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<Self, KdlParseFailure> {
#[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<Self, KdlParseFailure> {
let ret: Result<kdlv1::KdlNode, kdlv1::KdlError> = s.parse();
ret.map(|x| x.into()).map_err(|e| e.into())
}
}
#[cfg(feature = "v1")]
impl From<kdlv1::KdlNode> 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

View File

@ -261,6 +261,23 @@ where
}
}
#[cfg(feature = "v1")]
impl From<kdlv1::KdlValue> 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::*;