mirror of https://github.com/kdl-org/kdl-rs.git
feat(parser): v2 parser (#70)
This is a major rewrite of kdl-rs to comply with the KDL v2 spec.
This commit is contained in:
parent
6044ef9776
commit
c9898cdbd2
|
|
@ -28,7 +28,7 @@ jobs:
|
|||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
rust: [1.56.0, stable]
|
||||
rust: [1.70.0, stable]
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
|
||||
steps:
|
||||
|
|
|
|||
|
|
@ -16,10 +16,11 @@ default = ["span"]
|
|||
span = []
|
||||
|
||||
[dependencies]
|
||||
miette = "5.7.0"
|
||||
miette = "7.2.0"
|
||||
nom = "7.1.1"
|
||||
thiserror = "1.0.40"
|
||||
winnow = { version = "0.6.20", features = ["alloc", "unstable-recover"] }
|
||||
|
||||
[dev-dependencies]
|
||||
miette = { version = "5.7.0", features = ["fancy"] }
|
||||
miette = { version = "7.2.0", features = ["fancy"] }
|
||||
pretty_assertions = "1.3.0"
|
||||
|
|
|
|||
36
README.md
36
README.md
|
|
@ -66,38 +66,6 @@ assert_eq!(&doc.to_string(), node_str);
|
|||
[`KdlDocument`], [`KdlNode`], [`KdlEntry`], and [`KdlIdentifier`] can all
|
||||
be parsed and managed this way.
|
||||
|
||||
#### Query Engine
|
||||
|
||||
`kdl` includes a query engine for
|
||||
[KQL](https://github.com/kdl-org/kdl/blob/main/QUERY-SPEC.md), which lets you
|
||||
pick out nodes from a document using a CSS Selectors-style syntax.
|
||||
|
||||
Queries can be done from either a [`KdlDocument`] or a [`KdlNode`], with
|
||||
mostly the same semantics.
|
||||
|
||||
```rust
|
||||
use kdl::KdlDocument;
|
||||
|
||||
let doc = r#"
|
||||
a {
|
||||
b 1
|
||||
c 2
|
||||
d 3 {
|
||||
e prop="hello"
|
||||
}
|
||||
}
|
||||
"#.parse::<KdlDocument>().expect("failed to parse KDL");
|
||||
|
||||
let results = doc.query("a > b").expect("failed to parse query");
|
||||
assert_eq!(results, Some(&doc.nodes()[0].children().unwrap().nodes()[0]));
|
||||
|
||||
let results = doc.query_get("e", "prop").expect("failed to parse query");
|
||||
assert_eq!(results, Some(&"hello".into()));
|
||||
|
||||
let results = doc.query_get_all("a > []", 0).expect("failed to parse query").collect::<Vec<_>>();
|
||||
assert_eq!(results, vec![&1.into(), &2.into(), &3.into()]);
|
||||
```
|
||||
|
||||
### Error Reporting
|
||||
|
||||
[`KdlError`] implements [`miette::Diagnostic`] and can be used to display
|
||||
|
|
@ -154,6 +122,10 @@ means a few things:
|
|||
you [`KdlDocument::fmt`] in which case the original representation will be
|
||||
thrown away and the actual value will be used when serializing.
|
||||
|
||||
### Minimum Supported Rust Version
|
||||
|
||||
You must be at least `1.70.0` tall to get on this ride.
|
||||
|
||||
### License
|
||||
|
||||
The code in this repository is covered by [the Apache-2.0
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
package {
|
||||
name "kdl"
|
||||
name kdl
|
||||
version "0.0.0"
|
||||
description "kat's document language"
|
||||
description "The kdl document language"
|
||||
authors "Kat Marchán <kzm@zkat.tech>"
|
||||
license-file "LICENSE.md"
|
||||
license-file LICENSE.md
|
||||
edition "2018"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,47 +1,52 @@
|
|||
// This example is a GitHub Action if it used KDL syntax.
|
||||
// See .github/workflows/ci.yml for the file this was based on.
|
||||
name "CI"
|
||||
name CI
|
||||
|
||||
on "push" "pull_request"
|
||||
on push pull_request
|
||||
|
||||
env {
|
||||
RUSTFLAGS "-Dwarnings"
|
||||
RUSTFLAGS -Dwarnings
|
||||
}
|
||||
|
||||
jobs {
|
||||
fmt_and_docs "Check fmt & build docs" {
|
||||
runs-on "ubuntu-latest"
|
||||
runs-on ubuntu-latest
|
||||
steps {
|
||||
step uses="actions/checkout@v1"
|
||||
step "Install Rust" uses="actions-rs/toolchain@v1" {
|
||||
profile "minimal"
|
||||
toolchain "stable"
|
||||
components "rustfmt"
|
||||
override true
|
||||
profile minimal
|
||||
toolchain stable
|
||||
components rustfmt
|
||||
override #true
|
||||
}
|
||||
step "rustfmt" run="cargo fmt --all -- --check"
|
||||
step "docs" run="cargo doc --no-deps"
|
||||
step rustfmt { run cargo fmt --all -- --check }
|
||||
step docs { run cargo doc --no-deps }
|
||||
}
|
||||
}
|
||||
build_and_test "Build & Test" {
|
||||
runs-on "${{ matrix.os }}"
|
||||
strategy {
|
||||
matrix {
|
||||
rust "1.46.0" "stable"
|
||||
os "ubuntu-latest" "macOS-latest" "windows-latest"
|
||||
rust "1.46.0" stable
|
||||
os ubuntu-latest macOS-latest windows-latest
|
||||
}
|
||||
}
|
||||
|
||||
steps {
|
||||
step uses="actions/checkout@v1"
|
||||
step "Install Rust" uses="actions-rs/toolchain@v1" {
|
||||
profile "minimal"
|
||||
profile minimal
|
||||
toolchain "${{ matrix.rust }}"
|
||||
components "clippy"
|
||||
override true
|
||||
components clippy
|
||||
override #true
|
||||
}
|
||||
step "Clippy" run="cargo clippy --all -- -D warnings"
|
||||
step "Run tests" run="cargo test --all --verbose"
|
||||
step Clippy { run cargo clippy --all -- -D warnings }
|
||||
step "Run tests" { run cargo test --all --verbose }
|
||||
step "Other Stuff" run="
|
||||
echo foo
|
||||
echo bar
|
||||
echo baz
|
||||
"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,47 +1,50 @@
|
|||
/// Show how to build your own diagnostics, without having to use the
|
||||
/// `fancy` feature or having `main()` return `miette::Result`
|
||||
use kdl::KdlDocument;
|
||||
use miette::Diagnostic;
|
||||
use miette::SourceSpan;
|
||||
// TODO(@zkat): Error stuff has changed, so this needs updating.
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MyError {
|
||||
pub message: String,
|
||||
}
|
||||
// /// Show how to build your own diagnostics, without having to use the
|
||||
// /// `fancy` feature or having `main()` return `miette::Result`
|
||||
// ///
|
||||
// use kdl::KdlDocument;
|
||||
// use miette::Diagnostic;
|
||||
// use miette::SourceSpan;
|
||||
|
||||
fn parse(input: &str) -> Result<KdlDocument, MyError> {
|
||||
let doc = input.parse::<KdlDocument>();
|
||||
doc.map_err(|error| {
|
||||
let source = error
|
||||
.source_code()
|
||||
.expect("parse errors should have source code");
|
||||
let help = error.help.unwrap_or_default();
|
||||
let span: SourceSpan = error.span;
|
||||
let contents = source
|
||||
.read_span(&span, 0, 0)
|
||||
.expect("source should have span contents");
|
||||
// miette uses 0 based indexes, but humans prefer 1-based
|
||||
let line = contents.line() + 1;
|
||||
let column = contents.column() + 1;
|
||||
let message = format!(
|
||||
"line {}, column {}: {}\n help: {}",
|
||||
line, column, error, help
|
||||
);
|
||||
MyError { message }
|
||||
})
|
||||
}
|
||||
// #[derive(Debug)]
|
||||
// pub struct MyError {
|
||||
// pub message: String,
|
||||
// }
|
||||
|
||||
// fn parse(input: &str) -> Result<KdlDocument, MyError> {
|
||||
// let doc = input.parse::<KdlDocument>();
|
||||
// doc.map_err(|error| {
|
||||
// let source = error
|
||||
// .source_code()
|
||||
// .expect("parse errors should have source code");
|
||||
// let help = error.help.unwrap_or_default();
|
||||
// let span: SourceSpan = error.span;
|
||||
// let contents = source
|
||||
// .read_span(&span, 0, 0)
|
||||
// .expect("source should have span contents");
|
||||
// // miette uses 0 based indexes, but humans prefer 1-based
|
||||
// let line = contents.line() + 1;
|
||||
// let column = contents.column() + 1;
|
||||
// let message = format!(
|
||||
// "line {}, column {}: {}\n help: {}",
|
||||
// line, column, error, help
|
||||
// );
|
||||
// MyError { message }
|
||||
// })
|
||||
// }
|
||||
|
||||
fn main() {
|
||||
let input = r#"
|
||||
foo {
|
||||
bar {
|
||||
baz 1.
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let err = parse(input).unwrap_err();
|
||||
eprintln!("{}", err.message);
|
||||
// Output:
|
||||
// line 4, column 14: Expected valid value.
|
||||
// help: Floating point numbers must be base 10, and have numbers after the decimal point.
|
||||
// let input = r#"
|
||||
// foo {
|
||||
// bar {
|
||||
// baz 1.
|
||||
// }
|
||||
// }
|
||||
// "#;
|
||||
// let err = parse(input).unwrap_err();
|
||||
// eprintln!("{}", err.message);
|
||||
// // Output:
|
||||
// // line 4, column 14: Expected valid value.
|
||||
// // help: Floating point numbers must be base 10, and have numbers after the decimal point.
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ words {
|
|||
let word_node = KdlNode::new(identifier);
|
||||
word_nodes.push(word_node);
|
||||
word_nodes.sort_by(sort_by_name);
|
||||
words_section.fmt();
|
||||
words_section.autoformat();
|
||||
|
||||
println!("{}", doc);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,371 +1,374 @@
|
|||
document {
|
||||
info {
|
||||
title "KDL Schema" lang="en"
|
||||
description "KDL Schema KDL schema in KDL" lang="en"
|
||||
title "KDL Schema" lang=en
|
||||
description "KDL Schema KDL schema in KDL" lang=en
|
||||
author "Kat Marchán" {
|
||||
link "https://github.com/zkat" rel="self"
|
||||
link "https://github.com/zkat" rel=self
|
||||
}
|
||||
contributor "Lars Willighagen" {
|
||||
link "https://github.com/larsgw" rel="self"
|
||||
link "https://github.com/larsgw" rel=self
|
||||
}
|
||||
link "https://github.com/zkat/kdl" rel="documentation"
|
||||
license "Creative Commons Attribution-ShareAlike 4.0 International License" spdx="CC-BY-SA-4.0" {
|
||||
link "https://creativecommons.org/licenses/by-sa/4.0/" lang="en"
|
||||
link "https://github.com/zkat/kdl" rel=documentation
|
||||
license "Creative Commons Attribution-ShareAlike 4.0 International License" spdx=CC-BY-SA-4.0 {
|
||||
link "https://creativecommons.org/licenses/by-sa/4.0/" lang=en
|
||||
}
|
||||
published "2021-08-31"
|
||||
modified "2021-09-01"
|
||||
}
|
||||
node "document" {
|
||||
node document {
|
||||
min 1
|
||||
max 1
|
||||
children id="node-children" {
|
||||
node "node-names" id="node-names-node" description="Validations to apply specifically to arbitrary node names" {
|
||||
children ref=r#"[id="validations"]"#
|
||||
children id=node-children {
|
||||
node node-names id=node-names-node description="Validations to apply specifically to arbitrary node names" {
|
||||
children ref=#"[id="validations"]"#
|
||||
}
|
||||
node "other-nodes-allowed" id="other-nodes-allowed-node" description="Whether to allow child nodes other than the ones explicitly listed. Defaults to 'false'." {
|
||||
node other-nodes-allowed id=other-nodes-allowed-node description="Whether to allow child nodes other than the ones explicitly listed. Defaults to '#false'." {
|
||||
max 1
|
||||
value {
|
||||
min 1
|
||||
max 1
|
||||
type "boolean"
|
||||
type boolean
|
||||
}
|
||||
}
|
||||
node "tag-names" description="Validations to apply specifically to arbitrary type tag names" {
|
||||
children ref=r#"[id="validations"]"#
|
||||
node tag-names description="Validations to apply specifically to arbitrary type tag names" {
|
||||
children ref=#"[id="validations"]"#
|
||||
}
|
||||
node "other-tags-allowed" description="Whether to allow child node tags other than the ones explicitly listed. Defaults to 'false'." {
|
||||
node other-tags-allowed description="Whether to allow child node tags other than the ones explicitly listed. Defaults to '#false'." {
|
||||
max 1
|
||||
value {
|
||||
min 1
|
||||
max 1
|
||||
type "boolean"
|
||||
type boolean
|
||||
}
|
||||
}
|
||||
node "info" description="A child node that describes the schema itself." {
|
||||
node info description="A child node that describes the schema itself." {
|
||||
children {
|
||||
node "title" description="The title of the schema or the format it describes" {
|
||||
node title description="The title of the schema or the format it describes" {
|
||||
value description="The title text" {
|
||||
type "string"
|
||||
type string
|
||||
min 1
|
||||
max 1
|
||||
}
|
||||
prop "lang" id="info-lang" description="The language of the text" {
|
||||
type "string"
|
||||
prop lang id=info-lang description="The language of the text" {
|
||||
type string
|
||||
}
|
||||
}
|
||||
node "description" description="A description of the schema or the format it describes" {
|
||||
node description description="A description of the schema or the format it describes" {
|
||||
value description="The description text" {
|
||||
type "string"
|
||||
type string
|
||||
min 1
|
||||
max 1
|
||||
}
|
||||
prop ref=r#"[id="info-lang"]"#
|
||||
prop ref=#"[id="info-lang"]"#
|
||||
}
|
||||
node "author" description="Author of the schema" {
|
||||
value id="info-person-name" description="Person name" {
|
||||
type "string"
|
||||
node author description="Author of the schema" {
|
||||
value id=info-person-name description="Person name" {
|
||||
type string
|
||||
min 1
|
||||
max 1
|
||||
}
|
||||
prop "orcid" id="info-orcid" description="The ORCID of the person" {
|
||||
type "string"
|
||||
pattern r"\d{4}-\d{4}-\d{4}-\d{4}"
|
||||
prop orcid id=info-orcid description="The ORCID of the person" {
|
||||
type string
|
||||
pattern #"\d{4}-\d{4}-\d{4}-\d{4}"#
|
||||
}
|
||||
children {
|
||||
node ref=r#"[id="info-link"]"#
|
||||
node ref=#"[id="info-link"]"#
|
||||
}
|
||||
}
|
||||
node "contributor" description="Contributor to the schema" {
|
||||
value ref=r#"[id="info-person-name"]"#
|
||||
prop ref=r#"[id="info-orcid"]"#
|
||||
node contributor description="Contributor to the schema" {
|
||||
value ref=#"[id="info-person-name"]"#
|
||||
prop ref=#"[id="info-orcid"]"#
|
||||
children {
|
||||
node ref=#"[id="info-link"]"#
|
||||
}
|
||||
}
|
||||
node "link" id="info-link" description="Links to itself, and to sources describing it" {
|
||||
node link id=info-link description="Links to itself, and to sources describing it" {
|
||||
value description="A URL that the link points to" {
|
||||
type "string"
|
||||
format "url" "irl"
|
||||
type string
|
||||
format url irl
|
||||
min 1
|
||||
max 1
|
||||
}
|
||||
prop "rel" description="The relation between the current entity and the URL" {
|
||||
type "string"
|
||||
enum "self" "documentation"
|
||||
prop rel description="The relation between the current entity and the URL" {
|
||||
type string
|
||||
enum self documentation
|
||||
}
|
||||
prop ref=r#"[id="info-lang"]"#
|
||||
prop ref=#"[id="info-lang"]"#
|
||||
}
|
||||
node "license" description="The license(s) that the schema is licensed under" {
|
||||
node license description="The license(s) that the schema is licensed under" {
|
||||
value description="Name of the used license" {
|
||||
type "string"
|
||||
type string
|
||||
min 1
|
||||
max 1
|
||||
}
|
||||
prop "spdx" description="An SPDX license identifier" {
|
||||
type "string"
|
||||
prop spdx description="An SPDX license identifier" {
|
||||
type string
|
||||
}
|
||||
children {
|
||||
node ref=r#"[id="info-link"]"#
|
||||
node ref=#"[id="info-link"]"#
|
||||
}
|
||||
}
|
||||
node "published" description="When the schema was published" {
|
||||
node published description="When the schema was published" {
|
||||
value description="Publication date" {
|
||||
type "string"
|
||||
format "date"
|
||||
type string
|
||||
format date
|
||||
min 1
|
||||
max 1
|
||||
}
|
||||
prop "time" id="info-time" description="A time to accompany the date" {
|
||||
type "string"
|
||||
format "time"
|
||||
prop time id=info-time description="A time to accompany the date" {
|
||||
type string
|
||||
format time
|
||||
}
|
||||
}
|
||||
node "modified" description="When the schema was last modified" {
|
||||
node modified description="When the schema was last modified" {
|
||||
value description="Modification date" {
|
||||
type "string"
|
||||
format "date"
|
||||
type string
|
||||
format date
|
||||
min 1
|
||||
max 1
|
||||
}
|
||||
prop ref=r#"[id="info-time"]"#
|
||||
prop ref=#"[id="info-time"]"#
|
||||
}
|
||||
node "version" description="The version number of this version of the schema" {
|
||||
node version description="The version number of this version of the schema" {
|
||||
value description="Semver version number" {
|
||||
type "string"
|
||||
pattern r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
|
||||
type string
|
||||
pattern #"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"#
|
||||
min 1
|
||||
max 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
node "tag" id="tag-node" description="A tag belonging to a child node of `document` or another node." {
|
||||
node tag id=tag-node description="A tag belonging to a child node of `document` or another node." {
|
||||
value description="The name of the tag. If a tag name is not supplied, the node rules apply to _all_ nodes belonging to the parent." {
|
||||
type "string"
|
||||
type string
|
||||
max 1
|
||||
}
|
||||
prop "description" description="A description of this node's purpose." {
|
||||
type "string"
|
||||
prop description description="A description of this node's purpose." {
|
||||
type string
|
||||
}
|
||||
prop "id" description="A globally-unique ID for this node." {
|
||||
type "string"
|
||||
prop id description="A globally-unique ID for this node." {
|
||||
type string
|
||||
}
|
||||
prop "ref" description="A globally unique reference to another node." {
|
||||
type "string"
|
||||
format "kdl-query"
|
||||
prop ref description="A globally unique reference to another node." {
|
||||
type string
|
||||
format kdl-query
|
||||
}
|
||||
children {
|
||||
node ref=r#"[id="node-names-node"]"#
|
||||
node ref=r#"[id="other-nodes-allowed-node"]"#
|
||||
node ref=r#"[id="node-node"]"#
|
||||
node ref=#"[id="node-names-node"]"#
|
||||
node ref=#"[id="other-nodes-allowed-node"]"#
|
||||
node ref=#"[id="node-node"]"#
|
||||
}
|
||||
}
|
||||
node "node" id="node-node" description="A child node belonging either to `document` or to another `node`. Nodes may be anonymous." {
|
||||
node node id=node-node description="A child node belonging either to `document` or to another `node`. Nodes may be anonymous." {
|
||||
value description="The name of the node. If a node name is not supplied, the node rules apply to _all_ nodes belonging to the parent." {
|
||||
type "string"
|
||||
type string
|
||||
max 1
|
||||
}
|
||||
prop "description" description="A description of this node's purpose." {
|
||||
type "string"
|
||||
prop description description="A description of this node's purpose." {
|
||||
type string
|
||||
}
|
||||
prop "id" description="A globally-unique ID for this node." {
|
||||
type "string"
|
||||
prop id description="A globally-unique ID for this node." {
|
||||
type string
|
||||
}
|
||||
prop "ref" description="A globally unique reference to another node." {
|
||||
type "string"
|
||||
format "kdl-query"
|
||||
prop ref description="A globally unique reference to another node." {
|
||||
type string
|
||||
format kdl-query
|
||||
}
|
||||
children {
|
||||
node "prop-names" description="Validations to apply specifically to arbitrary property names" {
|
||||
children ref=r#"[id="validations"]"#
|
||||
node prop-names description="Validations to apply specifically to arbitrary property names" {
|
||||
children ref=#"[id="validations"]"#
|
||||
}
|
||||
node "other-props-allowed" description="Whether to allow properties other than the ones explicitly listed. Defaults to 'false'." {
|
||||
node other-props-allowed description="Whether to allow properties other than the ones explicitly listed. Defaults to '#false'." {
|
||||
max 1
|
||||
value {
|
||||
min 1
|
||||
max 1
|
||||
type "boolean"
|
||||
type boolean
|
||||
}
|
||||
}
|
||||
node "min" description="minimum number of instances of this node in its parent's children." {
|
||||
node min description="minimum number of instances of this node in its parent's children." {
|
||||
max 1
|
||||
value {
|
||||
min 1
|
||||
max 1
|
||||
type "number"
|
||||
type number
|
||||
}
|
||||
}
|
||||
node "max" description="maximum number of instances of this node in its parent's children." {
|
||||
node max description="maximum number of instances of this node in its parent's children." {
|
||||
max 1
|
||||
value {
|
||||
min 1
|
||||
max 1
|
||||
type "number"
|
||||
type number
|
||||
}
|
||||
}
|
||||
node ref=r#"[id="value-tag-node"]"#
|
||||
node "prop" id="prop-node" description="A node property key/value pair." {
|
||||
node ref=#"[id="value-tag-node"]"#
|
||||
node prop id="prop-node" description="A node property key/value pair." {
|
||||
value description="The property key." {
|
||||
type "string"
|
||||
type string
|
||||
}
|
||||
prop "id" description="A globally-unique ID of this property." {
|
||||
type "string"
|
||||
prop id description="A globally-unique ID of this property." {
|
||||
type string
|
||||
}
|
||||
prop "ref" description="A globally unique reference to another property node." {
|
||||
type "string"
|
||||
format "kdl-query"
|
||||
prop ref description="A globally unique reference to another property node." {
|
||||
type string
|
||||
format kdl-query
|
||||
}
|
||||
prop "description" description="A description of this property's purpose." {
|
||||
type "string"
|
||||
prop description description="A description of this property's purpose." {
|
||||
type string
|
||||
}
|
||||
children description="Property-specific validations." {
|
||||
node "required" description="Whether this property is required if its parent is present." {
|
||||
node required description="Whether this property is required if its parent is present." {
|
||||
max 1
|
||||
value {
|
||||
min 1
|
||||
max 1
|
||||
type "boolean"
|
||||
type boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
children id="validations" description="General value validations." {
|
||||
node "tag" id="value-tag-node" description="The tags associated with this value" {
|
||||
children id=validations description="General value validations." {
|
||||
node tag id=value-tag-node description="The tags associated with this value" {
|
||||
max 1
|
||||
children ref=r#"[id="validations"]"#
|
||||
children ref=#"[id="validations"]"#
|
||||
}
|
||||
node "type" description="The type for this prop's value." {
|
||||
node type description="The type for this prop's value." {
|
||||
max 1
|
||||
value {
|
||||
min 1
|
||||
type "string"
|
||||
type string
|
||||
}
|
||||
}
|
||||
node "enum" description="An enumeration of possible values" {
|
||||
node enum description="An enumeration of possible values" {
|
||||
max 1
|
||||
value description="Enumeration choices" {
|
||||
min 1
|
||||
}
|
||||
}
|
||||
node "pattern" description="PCRE (Regex) pattern or patterns to test prop values against." {
|
||||
node pattern description="PCRE (Regex) pattern or patterns to test prop values against." {
|
||||
value {
|
||||
min 1
|
||||
type "string"
|
||||
type string
|
||||
}
|
||||
}
|
||||
node "min-length" description="Minimum length of prop value, if it's a string." {
|
||||
node min-length description="Minimum length of prop value, if it's a string." {
|
||||
max 1
|
||||
value {
|
||||
min 1
|
||||
type "number"
|
||||
type number
|
||||
}
|
||||
}
|
||||
node "max-length" description="Maximum length of prop value, if it's a string." {
|
||||
node max-length description="Maximum length of prop value, if it's a string." {
|
||||
max 1
|
||||
value {
|
||||
min 1
|
||||
type "number"
|
||||
type number
|
||||
}
|
||||
}
|
||||
node "format" description="Intended data format." {
|
||||
node format description="Intended data format." {
|
||||
max 1
|
||||
value {
|
||||
min 1
|
||||
type "string"
|
||||
type string
|
||||
// https://json-schema.org/understanding-json-schema/reference/string.html#format
|
||||
enum "date-time" "date" "time" "duration" "decimal" "currency" "country-2" "country-3" "country-subdivision" "email" "idn-email" "hostname" "idn-hostname" "ipv4" "ipv6" "url" "url-reference" "irl" "irl-reference" "url-template" "regex" "uuid" "kdl-query" "i8" "i16" "i32" "i64" "u8" "u16" "u32" "u64" "isize" "usize" "f32" "f64" "decimal64" "decimal128"
|
||||
enum date-time date time duration decimal currency country-2 country-3 country-subdivision email idn-email hostname idn-hostname ipv4 ipv6 url url-reference irl irl-reference url-template regex uuid kdl-query i8 i16 i32 i64 u8 u16 u32 u64 isize usize f32 f64 decimal64 decimal128
|
||||
}
|
||||
}
|
||||
node "%" description="Only used for numeric values. Constrains them to be multiples of the given number(s)" {
|
||||
node % description="Only used for numeric values. Constrains them to be multiples of the given number(s)" {
|
||||
max 1
|
||||
value {
|
||||
min 1
|
||||
type "number"
|
||||
type number
|
||||
}
|
||||
}
|
||||
node ">" description="Only used for numeric values. Constrains them to be greater than the given number(s)" {
|
||||
node gt description="Only used for numeric values. Constrains them to be greater than the given number(s)" {
|
||||
max 1
|
||||
value {
|
||||
min 1
|
||||
max 1
|
||||
type "number"
|
||||
type number
|
||||
}
|
||||
}
|
||||
node ">=" description="Only used for numeric values. Constrains them to be greater than or equal to the given number(s)" {
|
||||
node gte description="Only used for numeric values. Constrains them to be greater than or equal to the given number(s)" {
|
||||
max 1
|
||||
value {
|
||||
min 1
|
||||
max 1
|
||||
type "number"
|
||||
type number
|
||||
}
|
||||
}
|
||||
node "<" description="Only used for numeric values. Constrains them to be less than the given number(s)" {
|
||||
node lt description="Only used for numeric values. Constrains them to be less than the given number(s)" {
|
||||
max 1
|
||||
value {
|
||||
min 1
|
||||
max 1
|
||||
type "number"
|
||||
type number
|
||||
}
|
||||
}
|
||||
node "<=" description="Only used for numeric values. Constrains them to be less than or equal to the given number(s)" {
|
||||
node lte description="Only used for numeric values. Constrains them to be less than or equal to the given number(s)" {
|
||||
max 1
|
||||
value {
|
||||
min 1
|
||||
max 1
|
||||
type "number"
|
||||
type number
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
node "value" id="value-node" description="one or more direct node values" {
|
||||
prop "id" description="A globally-unique ID of this value." {
|
||||
type "string"
|
||||
node value id=value-node description="one or more direct node values" {
|
||||
prop id description="A globally-unique ID of this value." {
|
||||
type string
|
||||
}
|
||||
prop "ref" description="A globally unique reference to another value node." {
|
||||
type "string"
|
||||
format "kdl-query"
|
||||
prop ref description="A globally unique reference to another value node." {
|
||||
type string
|
||||
format kdl-query
|
||||
}
|
||||
prop "description" description="A description of this property's purpose." {
|
||||
type "string"
|
||||
prop description description="A description of this property's purpose." {
|
||||
type string
|
||||
}
|
||||
children ref=r#"[id="validations"]"#
|
||||
children ref=#"[id="validations"]"#
|
||||
children description="Node value-specific validations" {
|
||||
node "min" description="minimum number of values for this node." {
|
||||
node min description="minimum number of values for this node." {
|
||||
max 1
|
||||
value {
|
||||
min 1
|
||||
max 1
|
||||
type "number"
|
||||
type number
|
||||
}
|
||||
}
|
||||
node "max" description="maximum number of values for this node." {
|
||||
node max description="maximum number of values for this node." {
|
||||
max 1
|
||||
value {
|
||||
min 1
|
||||
max 1
|
||||
type "number"
|
||||
type number
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
node "children" id="children-node" {
|
||||
prop "id" description="A globally-unique ID of this children node." {
|
||||
type "string"
|
||||
node children id=children-node {
|
||||
prop id description="A globally-unique ID of this children node." {
|
||||
type string
|
||||
}
|
||||
prop "ref" description="A globally unique reference to another children node." {
|
||||
type "string"
|
||||
format "kdl-query"
|
||||
prop ref description="A globally unique reference to another children node." {
|
||||
type string
|
||||
format kdl-query
|
||||
}
|
||||
prop "description" description="A description of this these children's purpose." {
|
||||
type "string"
|
||||
prop description description="A description of this these children's purpose." {
|
||||
type string
|
||||
}
|
||||
children ref=r#"[id="node-children"]"#
|
||||
children ref=#"[id="node-children"]"#
|
||||
}
|
||||
}
|
||||
}
|
||||
node "definitions" description="Definitions to reference in parts of the top-level nodes" {
|
||||
node definitions description="Definitions to reference in parts of the top-level nodes" {
|
||||
children {
|
||||
node ref=r#"[id="node-node"]"#
|
||||
node ref=r#"[id="value-node"]"#
|
||||
node ref=r#"[id="prop-node"]"#
|
||||
node ref=r#"[id="children-node"]"#
|
||||
node ref=r#"[id="tag-node"]"#
|
||||
node ref=#"[id="node-node"]"#
|
||||
node ref=#"[id="value-node"]"#
|
||||
node ref=#"[id="prop-node"]"#
|
||||
node ref=#"[id="children-node"]"#
|
||||
node ref=#"[id="tag-node"]"#
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,48 +1,48 @@
|
|||
// Based on https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Clients/NuGet.CommandLine/NuGet.CommandLine.csproj
|
||||
Project {
|
||||
PropertyGroup {
|
||||
IsCommandLinePackage true
|
||||
IsCommandLinePackage #true
|
||||
}
|
||||
|
||||
Import Project=r"$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), 'README.md'))\build\common.props"
|
||||
Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk"
|
||||
Import Project="ilmerge.props"
|
||||
Import Project=#"$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), 'README.md'))\build\common.props"#
|
||||
Import Project=Sdk.props Sdk=Microsoft.NET.Sdk
|
||||
Import Project=ilmerge.props
|
||||
|
||||
PropertyGroup {
|
||||
RootNamespace "NuGet.CommandLine"
|
||||
AssemblyName "NuGet"
|
||||
RootNamespace NuGet.CommandLine
|
||||
AssemblyName NuGet
|
||||
AssemblyTitle "NuGet Command Line"
|
||||
PackageId "NuGet.CommandLine"
|
||||
PackageId NuGet.CommandLine
|
||||
TargetFramework "$(NETFXTargetFramework)"
|
||||
GenerateDocumentationFile false
|
||||
GenerateDocumentationFile #false
|
||||
Description "NuGet Command Line Interface."
|
||||
ApplicationManifest "app.manifest"
|
||||
Shipping true
|
||||
OutputType "Exe"
|
||||
ComVisible false
|
||||
ApplicationManifest app.manifest
|
||||
Shipping #true
|
||||
OutputType Exe
|
||||
ComVisible #false
|
||||
// Pack properties
|
||||
PackProject true
|
||||
IncludeBuildOutput false
|
||||
PackProject #true
|
||||
IncludeBuildOutput #false
|
||||
TargetsForTfmSpecificContentInPackage "$(TargetsForTfmSpecificContentInPackage)" "CreateCommandlineNupkg"
|
||||
SuppressDependenciesWhenPacking true
|
||||
DevelopmentDependency true
|
||||
PackageRequireLicenseAcceptance false
|
||||
UsePublicApiAnalyzer false
|
||||
SuppressDependenciesWhenPacking #true
|
||||
DevelopmentDependency #true
|
||||
PackageRequireLicenseAcceptance #false
|
||||
UsePublicApiAnalyzer #false
|
||||
}
|
||||
|
||||
Target Name="CreateCommandlineNupkg" {
|
||||
Target Name=CreateCommandlineNupkg {
|
||||
ItemGroup {
|
||||
TfmSpecificPackageFile Include=r"$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.exe" {
|
||||
TfmSpecificPackageFile Include=#"$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.exe"# {
|
||||
PackagePath "tools/"
|
||||
}
|
||||
TfmSpecificPackageFile Include=r"$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.pdb" {
|
||||
TfmSpecificPackageFile Include=#"$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.pdb"# {
|
||||
PackagePath "tools/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ItemGroup Condition="$(DefineConstants.Contains(SIGNED_BUILD))" {
|
||||
AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo" {
|
||||
AssemblyAttribute Include=System.Runtime.CompilerServices.InternalsVisibleTo {
|
||||
_Parameter1 "NuGet.CommandLine.FuncTest, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293"
|
||||
}
|
||||
AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo" {
|
||||
|
|
@ -51,81 +51,81 @@ Project {
|
|||
}
|
||||
|
||||
ItemGroup Condition="!$(DefineConstants.Contains(SIGNED_BUILD))" {
|
||||
AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo" {
|
||||
_Parameter1 "NuGet.CommandLine.FuncTest"
|
||||
AssemblyAttribute Include=System.Runtime.CompilerServices.InternalsVisibleTo {
|
||||
_Parameter1 NuGet.CommandLine.FuncTest
|
||||
}
|
||||
AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo" {
|
||||
_Parameter1 "NuGet.CommandLine.Test"
|
||||
AssemblyAttribute Include=System.Runtime.CompilerServices.InternalsVisibleTo {
|
||||
_Parameter1 NuGet.CommandLine.Test
|
||||
}
|
||||
}
|
||||
|
||||
ItemGroup Condition="$(DefineConstants.Contains(SIGNED_BUILD))" {
|
||||
AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo" {
|
||||
AssemblyAttribute Include=System.Runtime.CompilerServices.InternalsVisibleTo {
|
||||
_Parameter1 "NuGet.CommandLine.Test, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293"
|
||||
}
|
||||
}
|
||||
|
||||
ItemGroup Condition="!$(DefineConstants.Contains(SIGNED_BUILD))" {
|
||||
AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo" {
|
||||
_Parameter1 "NuGet.CommandLine.Test"
|
||||
AssemblyAttribute Include=System.Runtime.CompilerServices.InternalsVisibleTo {
|
||||
_Parameter1 NuGet.CommandLine.Test
|
||||
}
|
||||
}
|
||||
|
||||
ItemGroup {
|
||||
Reference Include="Microsoft.Build.Utilities.v4.0"
|
||||
Reference Include="Microsoft.CSharp"
|
||||
Reference Include="System"
|
||||
Reference Include="System.ComponentModel.Composition"
|
||||
Reference Include="System.ComponentModel.Composition.Registration"
|
||||
Reference Include="System.ComponentModel.DataAnnotations"
|
||||
Reference Include="System.IO.Compression"
|
||||
Reference Include="System.Net.Http"
|
||||
Reference Include="System.Xml"
|
||||
Reference Include="System.Xml.Linq"
|
||||
Reference Include="NuGet.Core" {
|
||||
HintPath r"$(SolutionPackagesFolder)nuget.core\2.14.0-rtm-832\lib\net40-Client\NuGet.Core.dll"
|
||||
Aliases "CoreV2"
|
||||
Reference Include=Microsoft.Build.Utilities.v4.0
|
||||
Reference Include=Microsoft.CSharp
|
||||
Reference Include=System
|
||||
Reference Include=System.ComponentModel.Composition
|
||||
Reference Include=System.ComponentModel.Composition.Registration
|
||||
Reference Include=System.ComponentModel.DataAnnotations
|
||||
Reference Include=System.IO.Compression
|
||||
Reference Include=System.Net.Http
|
||||
Reference Include=System.Xml
|
||||
Reference Include=System.Xml.Linq
|
||||
Reference Include=NuGet.Core {
|
||||
HintPath #"$(SolutionPackagesFolder)nuget.core\2.14.0-rtm-832\lib\net40-Client\NuGet.Core.dll"#
|
||||
Aliases CoreV2
|
||||
}
|
||||
}
|
||||
ItemGroup {
|
||||
PackageReference Include="Microsoft.VisualStudio.Setup.Configuration.Interop"
|
||||
ProjectReference Include=r"$(NuGetCoreSrcDirectory)NuGet.PackageManagement\NuGet.PackageManagement.csproj"
|
||||
ProjectReference Include=r"$(NuGetCoreSrcDirectory)NuGet.Build.Tasks\NuGet.Build.Tasks.csproj"
|
||||
PackageReference Include=Microsoft.VisualStudio.Setup.Configuration.Interop
|
||||
ProjectReference Include=#"$(NuGetCoreSrcDirectory)NuGet.PackageManagement\NuGet.PackageManagement.csproj"#
|
||||
ProjectReference Include=#"$(NuGetCoreSrcDirectory)NuGet.Build.Tasks\NuGet.Build.Tasks.csproj"#
|
||||
}
|
||||
|
||||
ItemGroup {
|
||||
EmbeddedResource Update="NuGetCommand.resx" {
|
||||
Generator "ResXFileCodeGenerator"
|
||||
LastGenOutput "NuGetCommand.Designer.cs"
|
||||
EmbeddedResource Update=NuGetCommand.resx {
|
||||
Generator ResXFileCodeGenerator
|
||||
LastGenOutput NuGetCommand.Designer.cs
|
||||
}
|
||||
Compile Update="NuGetCommand.Designer.cs" {
|
||||
DesignTime true
|
||||
AutoGen true
|
||||
DependentUpon "NuGetCommand.resx"
|
||||
Compile Update=NuGetCommand.Designer.cs {
|
||||
DesignTime #true
|
||||
AutoGen #true
|
||||
DependentUpon NuGetCommand.resx
|
||||
}
|
||||
EmbeddedResource Update="NuGetResources.resx" {
|
||||
EmbeddedResource Update=NuGetResources.resx {
|
||||
// Strings are shared by other projects, use public strings.
|
||||
Generator "PublicResXFileCodeGenerator"
|
||||
LastGenOutput "NuGetResources.Designer.cs"
|
||||
Generator PublicResXFileCodeGenerator
|
||||
LastGenOutput NuGetResources.Designer.cs
|
||||
}
|
||||
Compile Update="NuGetResources.Designer.cs" {
|
||||
DesignTime true
|
||||
AutoGen true
|
||||
DependentUpon "NuGetResources.resx"
|
||||
Compile Update=NuGetResources.Designer.cs {
|
||||
DesignTime #true
|
||||
AutoGen #true
|
||||
DependentUpon NuGetResources.resx
|
||||
}
|
||||
}
|
||||
|
||||
ItemGroup {
|
||||
EmbeddedResource Include=r"$(NuGetCoreSrcDirectory)NuGet.Build.Tasks\NuGet.targets" {
|
||||
Link "NuGet.targets"
|
||||
SubType "Designer"
|
||||
EmbeddedResource Include=#"$(NuGetCoreSrcDirectory)NuGet.Build.Tasks\NuGet.targets"# {
|
||||
Link NuGet.targets
|
||||
SubType Designer
|
||||
}
|
||||
}
|
||||
|
||||
// Since we are moving some code and strings from NuGet.CommandLine to NuGet.Commands, we opted to go through normal localization process (build .resources.dll) and then add them to the ILMerged nuget.exe
|
||||
// This will also be called from CI build, after assemblies are localized, since our test infra takes nuget.exe before Localization
|
||||
Target Name="ILMergeNuGetExe" \
|
||||
AfterTargets="Build" \
|
||||
Target Name=ILMergeNuGetExe \
|
||||
AfterTargets=Build \
|
||||
Condition="'$(BuildingInsideVisualStudio)' != 'true' and '$(SkipILMergeOfNuGetExe)' != 'true'" \
|
||||
{
|
||||
PropertyGroup {
|
||||
|
|
@ -133,9 +133,9 @@ Project {
|
|||
ExpectedLocalizedArtifactCount 0 Condition="'$(ExpectedLocalizedArtifactCount)' == ''"
|
||||
}
|
||||
ItemGroup {
|
||||
BuildArtifacts Include=r"$(OutputPath)\*.dll" Exclude="@(MergeExclude)"
|
||||
BuildArtifacts Include=#"$(OutputPath)\*.dll"# Exclude="@(MergeExclude)"
|
||||
// NuGet.exe needs all NuGet.Commands.resources.dll merged in
|
||||
LocalizedArtifacts Include=r"$(ArtifactsDirectory)\NuGet.Commands\**\$(NETFXTargetFramework)\**\*.resources.dll"
|
||||
LocalizedArtifacts Include=#"$(ArtifactsDirectory)\NuGet.Commands\**\$(NETFXTargetFramework)\**\*.resources.dll"#
|
||||
}
|
||||
Error Text="Build dependencies are inconsistent with mergeinclude specified in ilmerge.props" \
|
||||
Condition="'@(BuildArtifacts->Count())' != '@(MergeInclude->Count())'"
|
||||
|
|
@ -143,36 +143,36 @@ Project {
|
|||
Condition="'@(LocalizedArtifacts->Count())' != '$(ExpectedLocalizedArtifactCount)'"
|
||||
PropertyGroup {
|
||||
PathToBuiltNuGetExe "$(OutputPath)NuGet.exe"
|
||||
IlmergeCommand r"$(ILMergeExePath) /lib:$(OutputPath) /out:$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.exe @(MergeAllowDup -> '/allowdup:%(Identity)', ' ') /log:$(OutputPath)IlMergeLog.txt"
|
||||
IlmergeCommand #"$(ILMergeExePath) /lib:$(OutputPath) /out:$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.exe @(MergeAllowDup -> '/allowdup:%(Identity)', ' ') /log:$(OutputPath)IlMergeLog.txt"#
|
||||
IlmergeCommand Condition="Exists($(MS_PFX_PATH))" "$(IlmergeCommand) /delaysign /keyfile:$(MS_PFX_PATH)"
|
||||
// LocalizedArtifacts need fullpath, since there will be duplicate file names
|
||||
IlmergeCommand "$(IlmergeCommand) $(PathToBuiltNuGetExe) @(BuildArtifacts->'%(filename)%(extension)', ' ') @(LocalizedArtifacts->'%(fullpath)', ' ')"
|
||||
}
|
||||
MakeDir Directories="$(ArtifactsDirectory)$(VsixOutputDirName)"
|
||||
Exec Command="$(IlmergeCommand)" ContinueOnError="false"
|
||||
Exec Command="$(IlmergeCommand)" ContinueOnError=#false
|
||||
}
|
||||
|
||||
Import Project="$(BuildCommonDirectory)common.targets"
|
||||
Import Project="$(BuildCommonDirectory)embedinterop.targets"
|
||||
|
||||
// Do nothing. This basically strips away the framework assemblies from the resulting nuspec.
|
||||
Target Name="_GetFrameworkAssemblyReferences" DependsOnTargets="ResolveReferences"
|
||||
Target Name=_GetFrameworkAssemblyReferences DependsOnTargets=ResolveReferences
|
||||
|
||||
Target Name="GetSigningInputs" Returns="@(DllsToSign)" {
|
||||
Target Name=GetSigningInputs Returns="@(DllsToSign)" {
|
||||
ItemGroup {
|
||||
DllsToSign Include=r"$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.exe" {
|
||||
StrongName "MsSharedLib72"
|
||||
Authenticode "Microsoft400"
|
||||
DllsToSign Include=#"$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.exe"# {
|
||||
StrongName MsSharedLib72
|
||||
Authenticode Microsoft400
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Target Name="GetSymbolsToIndex" Returns="@(SymbolsToIndex)" {
|
||||
Target Name=GetSymbolsToIndex Returns="@(SymbolsToIndex)" {
|
||||
ItemGroup {
|
||||
SymbolsToIndex Include=r"$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.exe"
|
||||
SymbolsToIndex Include=r"$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.pdb"
|
||||
SymbolsToIndex Include=#"$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.exe"#
|
||||
SymbolsToIndex Include=#"$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.pdb"#
|
||||
}
|
||||
}
|
||||
|
||||
Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk"
|
||||
Import Project=Sdk.targets Sdk=Microsoft.NET.Sdk
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
!doctype html
|
||||
html lang=en {
|
||||
head {
|
||||
meta charset=utf-8
|
||||
meta name=viewport content="width=device-width, initial-scale=1.0"
|
||||
meta \
|
||||
name=description \
|
||||
content="kdl is a document language, mostly based on SDLang, with xml-like semantics that looks like you're invoking a bunch of CLI commands!"
|
||||
title "kdl - The KDL Document Language"
|
||||
link rel=stylesheet href="/styles/global.css"
|
||||
}
|
||||
body {
|
||||
main {
|
||||
header class="py-10 bg-gray-300" {
|
||||
h1 class="text-4xl text-center" "kdl - The KDL Document Language"
|
||||
}
|
||||
section class=kdl-section id=description {
|
||||
p {
|
||||
- "kdl is a document language, mostly based on "
|
||||
a href="https://sdlang.org" "SDLang"
|
||||
- " with xml-like semantics that looks like you're invoking a bunch of CLI commands"
|
||||
}
|
||||
p "It's meant to be used both as a serialization format and a configuration language, and is relatively light on syntax compared to XML."
|
||||
}
|
||||
section class=kdl-section id=design-and-discussion {
|
||||
h2 "Design and Discussion"
|
||||
p {
|
||||
- "kdl is still extremely new, and discussion about the format should happen over on the "
|
||||
a href="https://github.com/kdoclang/kdl/discussions" {
|
||||
- "discussions"
|
||||
}
|
||||
- " page in the Github repo. Feel free to jump in and give us your 2 cents!"
|
||||
}
|
||||
}
|
||||
section class=kdl-section id=design-principles {
|
||||
h2 "Design Principles"
|
||||
ol {
|
||||
li Maintainability
|
||||
li Flexibility
|
||||
li "Cognitive simplicity and Learnability"
|
||||
li "Ease of de/serialization"
|
||||
li "Ease of implementation"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
347
src/document.rs
347
src/document.rs
|
|
@ -1,8 +1,8 @@
|
|||
#[cfg(feature = "span")]
|
||||
use miette::SourceSpan;
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::{parser, IntoKdlQuery, KdlError, KdlNode, KdlQueryIterator, KdlValue, NodeKey};
|
||||
use crate::{KdlNode, KdlValue};
|
||||
|
||||
/// Represents a KDL
|
||||
/// [`Document`](https://github.com/kdl-org/kdl/blob/main/SPEC.md#document).
|
||||
|
|
@ -20,27 +20,23 @@ use crate::{parser, IntoKdlQuery, KdlError, KdlNode, KdlQueryIterator, KdlValue,
|
|||
/// ```
|
||||
#[derive(Debug, Clone, Eq)]
|
||||
pub struct KdlDocument {
|
||||
pub(crate) leading: Option<String>,
|
||||
pub(crate) nodes: Vec<KdlNode>,
|
||||
pub(crate) trailing: Option<String>,
|
||||
pub(crate) format: Option<KdlDocumentFormat>,
|
||||
#[cfg(feature = "span")]
|
||||
pub(crate) span: SourceSpan,
|
||||
}
|
||||
|
||||
impl PartialEq for KdlDocument {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.leading == other.leading
|
||||
&& self.nodes == other.nodes
|
||||
&& self.trailing == other.trailing
|
||||
self.nodes == other.nodes && self.format == other.format
|
||||
// Intentionally omitted: self.span == other.span
|
||||
}
|
||||
}
|
||||
|
||||
impl std::hash::Hash for KdlDocument {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.leading.hash(state);
|
||||
self.nodes.hash(state);
|
||||
self.trailing.hash(state);
|
||||
self.format.hash(state);
|
||||
// Intentionally omitted: self.span.hash(state)
|
||||
}
|
||||
}
|
||||
|
|
@ -48,9 +44,8 @@ impl std::hash::Hash for KdlDocument {
|
|||
impl Default for KdlDocument {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
leading: Default::default(),
|
||||
nodes: Default::default(),
|
||||
trailing: Default::default(),
|
||||
format: Default::default(),
|
||||
#[cfg(feature = "span")]
|
||||
span: SourceSpan::from(0..0),
|
||||
}
|
||||
|
|
@ -106,7 +101,7 @@ impl KdlDocument {
|
|||
/// You can fetch the value of `foo` in a single call like this:
|
||||
/// ```rust
|
||||
/// # use kdl::{KdlDocument, KdlValue};
|
||||
/// # let doc: KdlDocument = "foo 1\nbar false".parse().unwrap();
|
||||
/// # let doc: KdlDocument = "foo 1\nbar #false".parse().unwrap();
|
||||
/// assert_eq!(doc.get_arg("foo"), Some(&1.into()));
|
||||
/// ```
|
||||
pub fn get_arg(&self, name: &str) -> Option<&KdlValue> {
|
||||
|
|
@ -125,13 +120,13 @@ impl KdlDocument {
|
|||
/// Given a document like this:
|
||||
/// ```kdl
|
||||
/// foo 1 2 3
|
||||
/// bar false
|
||||
/// bar #false
|
||||
/// ```
|
||||
///
|
||||
/// You can fetch the arguments for `foo` in a single call like this:
|
||||
/// ```rust
|
||||
/// # use kdl::{KdlDocument, KdlValue};
|
||||
/// # let doc: KdlDocument = "foo 1 2 3\nbar false".parse().unwrap();
|
||||
/// # let doc: KdlDocument = "foo 1 2 3\nbar #false".parse().unwrap();
|
||||
/// assert_eq!(doc.get_args("foo"), vec![&1.into(), &2.into(), &3.into()]);
|
||||
/// ```
|
||||
pub fn get_args(&self, name: &str) -> Vec<&KdlValue> {
|
||||
|
|
@ -161,14 +156,14 @@ impl KdlDocument {
|
|||
/// foo {
|
||||
/// - 1
|
||||
/// - 2
|
||||
/// - false
|
||||
/// - #false
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// You can fetch the dashed child values of `foo` in a single call like this:
|
||||
/// ```rust
|
||||
/// # use kdl::{KdlDocument, KdlValue};
|
||||
/// # let doc: KdlDocument = "foo {\n - 1\n - 2\n - false\n}".parse().unwrap();
|
||||
/// # let doc: KdlDocument = "foo {\n - 1\n - 2\n - #false\n}".parse().unwrap();
|
||||
/// assert_eq!(doc.get_dash_args("foo"), vec![&1.into(), &2.into(), &false.into()]);
|
||||
/// ```
|
||||
pub fn get_dash_args(&self, name: &str) -> Vec<&KdlValue> {
|
||||
|
|
@ -192,24 +187,19 @@ impl KdlDocument {
|
|||
&mut self.nodes
|
||||
}
|
||||
|
||||
/// Gets leading text (whitespace, comments) for this KdlDocument.
|
||||
pub fn leading(&self) -> Option<&str> {
|
||||
self.leading.as_deref()
|
||||
/// Gets the formatting details for this entry.
|
||||
pub fn format(&self) -> Option<&KdlDocumentFormat> {
|
||||
self.format.as_ref()
|
||||
}
|
||||
|
||||
/// Sets leading text (whitespace, comments) for this KdlDocument.
|
||||
pub fn set_leading(&mut self, leading: impl Into<String>) {
|
||||
self.leading = Some(leading.into());
|
||||
/// Gets a mutable reference to this entry's formatting details.
|
||||
pub fn format_mut(&mut self) -> Option<&mut KdlDocumentFormat> {
|
||||
self.format.as_mut()
|
||||
}
|
||||
|
||||
/// Gets trailing text (whitespace, comments) for this KdlDocument.
|
||||
pub fn trailing(&self) -> Option<&str> {
|
||||
self.trailing.as_deref()
|
||||
}
|
||||
|
||||
/// Sets trailing text (whitespace, comments) for this KdlDocument.
|
||||
pub fn set_trailing(&mut self, trailing: impl Into<String>) {
|
||||
self.trailing = Some(trailing.into());
|
||||
/// Sets the formatting details for this entry.
|
||||
pub fn set_format(&mut self, format: KdlDocumentFormat) {
|
||||
self.format = Some(format);
|
||||
}
|
||||
|
||||
/// Length of this document when rendered as a string.
|
||||
|
|
@ -226,94 +216,99 @@ impl KdlDocument {
|
|||
/// this document will be unaffected.
|
||||
///
|
||||
/// If you need to clear the `KdlNode`s, use [`Self::clear_fmt_recursive`].
|
||||
pub fn clear_fmt(&mut self) {
|
||||
self.leading = None;
|
||||
self.trailing = None;
|
||||
pub fn clear_format(&mut self) {
|
||||
self.format = None;
|
||||
}
|
||||
|
||||
/// Clears leading and trailing text (whitespace, comments), also clearing
|
||||
/// all the `KdlNode`s in the document.
|
||||
pub fn clear_fmt_recursive(&mut self) {
|
||||
self.clear_fmt();
|
||||
pub fn clear_format_recursive(&mut self) {
|
||||
self.clear_format();
|
||||
for node in self.nodes.iter_mut() {
|
||||
node.clear_fmt_recursive();
|
||||
node.clear_format_recursive();
|
||||
}
|
||||
}
|
||||
|
||||
/// Auto-formats this Document, making everything nice while preserving
|
||||
/// comments.
|
||||
pub fn fmt(&mut self) {
|
||||
self.fmt_impl(0, false);
|
||||
pub fn autoformat(&mut self) {
|
||||
self.autoformat_impl(0, false);
|
||||
}
|
||||
|
||||
/// Formats the document and removes all comments from the document.
|
||||
pub fn fmt_no_comments(&mut self) {
|
||||
self.fmt_impl(0, true);
|
||||
pub fn autoformat_no_comments(&mut self) {
|
||||
self.autoformat_impl(0, true);
|
||||
}
|
||||
|
||||
/// Queries this Document's children according to the KQL query language,
|
||||
/// returning an iterator over all matching nodes.
|
||||
///
|
||||
/// # NOTE
|
||||
///
|
||||
/// Any query selectors that try to select the toplevel `scope()` will
|
||||
/// fail to match when using this method, since there's no [`KdlNode`] to
|
||||
/// return in this case.
|
||||
pub fn query_all(&self, query: impl IntoKdlQuery) -> Result<KdlQueryIterator<'_>, KdlError> {
|
||||
let parsed = query.into_query()?;
|
||||
Ok(KdlQueryIterator::new(None, Some(self), parsed))
|
||||
}
|
||||
// TODO(@zkat): These should all be moved into the query module itself,
|
||||
// instead of being methods on the models
|
||||
//
|
||||
// /// Queries this Document's children according to the KQL query language,
|
||||
// /// returning an iterator over all matching nodes.
|
||||
// ///
|
||||
// /// # NOTE
|
||||
// ///
|
||||
// /// Any query selectors that try to select the toplevel `scope()` will
|
||||
// /// fail to match when using this method, since there's no [`KdlNode`] to
|
||||
// /// return in this case.
|
||||
// pub fn query_all(
|
||||
// &self,
|
||||
// query: impl IntoKdlQuery,
|
||||
// ) -> Result<KdlQueryIterator<'_>, KdlDiagnostic> {
|
||||
// let parsed = query.into_query()?;
|
||||
// Ok(KdlQueryIterator::new(None, Some(self), parsed))
|
||||
// }
|
||||
|
||||
/// Queries this Document's children according to the KQL query language,
|
||||
/// returning the first match, if any.
|
||||
///
|
||||
/// # NOTE
|
||||
///
|
||||
/// Any query selectors that try to select the toplevel `scope()` will
|
||||
/// fail to match when using this method, since there's no [`KdlNode`] to
|
||||
/// return in this case.
|
||||
pub fn query(&self, query: impl IntoKdlQuery) -> Result<Option<&KdlNode>, KdlError> {
|
||||
let mut iter = self.query_all(query)?;
|
||||
Ok(iter.next())
|
||||
}
|
||||
// /// Queries this Document's children according to the KQL query language,
|
||||
// /// returning the first match, if any.
|
||||
// ///
|
||||
// /// # NOTE
|
||||
// ///
|
||||
// /// Any query selectors that try to select the toplevel `scope()` will
|
||||
// /// fail to match when using this method, since there's no [`KdlNode`] to
|
||||
// /// return in this case.
|
||||
// pub fn query(&self, query: impl IntoKdlQuery) -> Result<Option<&KdlNode>, KdlDiagnostic> {
|
||||
// let mut iter = self.query_all(query)?;
|
||||
// Ok(iter.next())
|
||||
// }
|
||||
|
||||
/// Queries this Document's children according to the KQL query language,
|
||||
/// picking the first match, and calling `.get(key)` on it, if the query
|
||||
/// succeeded.
|
||||
///
|
||||
/// # NOTE
|
||||
///
|
||||
/// Any query selectors that try to select the toplevel `scope()` will
|
||||
/// fail to match when using this method, since there's no [`KdlNode`] to
|
||||
/// return in this case.
|
||||
pub fn query_get(
|
||||
&self,
|
||||
query: impl IntoKdlQuery,
|
||||
key: impl Into<NodeKey>,
|
||||
) -> Result<Option<&KdlValue>, KdlError> {
|
||||
Ok(self.query(query)?.and_then(|node| node.get(key)))
|
||||
}
|
||||
// /// Queries this Document's children according to the KQL query language,
|
||||
// /// picking the first match, and calling `.get(key)` on it, if the query
|
||||
// /// succeeded.
|
||||
// ///
|
||||
// /// # NOTE
|
||||
// ///
|
||||
// /// Any query selectors that try to select the toplevel `scope()` will
|
||||
// /// fail to match when using this method, since there's no [`KdlNode`] to
|
||||
// /// return in this case.
|
||||
// pub fn query_get(
|
||||
// &self,
|
||||
// query: impl IntoKdlQuery,
|
||||
// key: impl Into<NodeKey>,
|
||||
// ) -> Result<Option<&KdlValue>, KdlDiagnostic> {
|
||||
// Ok(self.query(query)?.and_then(|node| node.get(key)))
|
||||
// }
|
||||
|
||||
/// Queries this Document's children according to the KQL query language,
|
||||
/// returning an iterator over all matching nodes, returning the requested
|
||||
/// field from each of those nodes and filtering out nodes that don't have
|
||||
/// it.
|
||||
///
|
||||
/// # NOTE
|
||||
///
|
||||
/// Any query selectors that try to select the toplevel `scope()` will
|
||||
/// fail to match when using this method, since there's no [`KdlNode`] to
|
||||
/// return in this case.
|
||||
pub fn query_get_all(
|
||||
&self,
|
||||
query: impl IntoKdlQuery,
|
||||
key: impl Into<NodeKey>,
|
||||
) -> Result<impl Iterator<Item = &KdlValue>, KdlError> {
|
||||
let key: NodeKey = key.into();
|
||||
Ok(self
|
||||
.query_all(query)?
|
||||
.filter_map(move |node| node.get(key.clone())))
|
||||
}
|
||||
// /// Queries this Document's children according to the KQL query language,
|
||||
// /// returning an iterator over all matching nodes, returning the requested
|
||||
// /// field from each of those nodes and filtering out nodes that don't have
|
||||
// /// it.
|
||||
// ///
|
||||
// /// # NOTE
|
||||
// ///
|
||||
// /// Any query selectors that try to select the toplevel `scope()` will
|
||||
// /// fail to match when using this method, since there's no [`KdlNode`] to
|
||||
// /// return in this case.
|
||||
// pub fn query_get_all(
|
||||
// &self,
|
||||
// query: impl IntoKdlQuery,
|
||||
// key: impl Into<NodeKey>,
|
||||
// ) -> Result<impl Iterator<Item = &KdlValue>, KdlDiagnostic> {
|
||||
// let key: NodeKey = key.into();
|
||||
// Ok(self
|
||||
// .query_all(query)?
|
||||
// .filter_map(move |node| node.get(key.clone())))
|
||||
// }
|
||||
}
|
||||
|
||||
impl Display for KdlDocument {
|
||||
|
|
@ -323,19 +318,19 @@ impl Display for KdlDocument {
|
|||
}
|
||||
|
||||
impl KdlDocument {
|
||||
pub(crate) fn fmt_impl(&mut self, indent: usize, no_comments: bool) {
|
||||
if let Some(s) = self.leading.as_mut() {
|
||||
crate::fmt::fmt_leading(s, indent, no_comments);
|
||||
pub(crate) fn autoformat_impl(&mut self, indent: usize, no_comments: bool) {
|
||||
if let Some(KdlDocumentFormat { leading, .. }) = self.format_mut() {
|
||||
crate::fmt::autoformat_leading(leading, indent, no_comments);
|
||||
}
|
||||
let mut has_nodes = false;
|
||||
for node in &mut self.nodes {
|
||||
has_nodes = true;
|
||||
node.fmt_impl(indent, no_comments);
|
||||
node.autoformat_impl(indent, no_comments);
|
||||
}
|
||||
if let Some(s) = self.trailing.as_mut() {
|
||||
crate::fmt::fmt_trailing(s, no_comments);
|
||||
if let Some(KdlDocumentFormat { trailing, .. }) = self.format_mut() {
|
||||
crate::fmt::autoformat_trailing(trailing, no_comments);
|
||||
if !has_nodes {
|
||||
s.push('\n');
|
||||
trailing.push('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -345,16 +340,13 @@ impl KdlDocument {
|
|||
f: &mut std::fmt::Formatter<'_>,
|
||||
indent: usize,
|
||||
) -> std::fmt::Result {
|
||||
if let Some(leading) = &self.leading {
|
||||
if let Some(KdlDocumentFormat { leading, .. }) = self.format() {
|
||||
write!(f, "{}", leading)?;
|
||||
}
|
||||
for node in &self.nodes {
|
||||
node.stringify(f, indent)?;
|
||||
if node.trailing.is_none() {
|
||||
writeln!(f)?;
|
||||
}
|
||||
}
|
||||
if let Some(trailing) = &self.trailing {
|
||||
if let Some(KdlDocumentFormat { trailing, .. }) = self.format() {
|
||||
write!(f, "{}", trailing)?;
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -370,13 +362,13 @@ impl IntoIterator for KdlDocument {
|
|||
}
|
||||
}
|
||||
|
||||
impl FromStr for KdlDocument {
|
||||
type Err = KdlError;
|
||||
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
let kdl_parser = parser::KdlParser::new(input);
|
||||
kdl_parser.parse(parser::document(&kdl_parser))
|
||||
}
|
||||
/// Formatting details for [`KdlDocument`]s.
|
||||
#[derive(Debug, Clone, Default, Hash, Eq, PartialEq)]
|
||||
pub struct KdlDocumentFormat {
|
||||
/// Whitespace and comments preceding the document's first node.
|
||||
pub leading: String,
|
||||
/// Whitespace and comments following the document's last node.
|
||||
pub trailing: String,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -411,26 +403,56 @@ second_node /* This time, the comment is here */ param=153 {
|
|||
let mut left_doc: KdlDocument = left_src.parse()?;
|
||||
let mut right_doc: KdlDocument = right_src.parse()?;
|
||||
assert_ne!(left_doc, right_doc);
|
||||
left_doc.clear_fmt_recursive();
|
||||
right_doc.clear_fmt_recursive();
|
||||
left_doc.clear_format_recursive();
|
||||
right_doc.clear_format_recursive();
|
||||
assert_eq!(left_doc, right_doc);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_parsing() -> miette::Result<()> {
|
||||
let src = r#"
|
||||
// Hello, world!
|
||||
node 1
|
||||
node two
|
||||
node item="three";
|
||||
node {
|
||||
nested 1 2 3
|
||||
nested_2 hi "world"
|
||||
}
|
||||
(type)node ("type")what?
|
||||
+false #true
|
||||
null_id null_prop=#null
|
||||
foo indented
|
||||
// normal comment?
|
||||
/- comment
|
||||
/* block comment */
|
||||
inline /*comment*/ here
|
||||
another /-commend there
|
||||
|
||||
|
||||
after some whitespace
|
||||
trailing /* multiline */
|
||||
trailing // single line
|
||||
"#;
|
||||
let _doc: KdlDocument = src.parse()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing() -> miette::Result<()> {
|
||||
let src = "
|
||||
// This is the first node
|
||||
foo 1 2 \"three\" null true bar=\"baz\" {
|
||||
foo 1 2 three #null #true bar=\"baz\" {
|
||||
- 1
|
||||
- 2
|
||||
- \"three\"
|
||||
(mytype)something (\"name\")\"else\"\r
|
||||
- three
|
||||
(mytype)something (\"name\")else\r
|
||||
}
|
||||
|
||||
null_id null_prop=null
|
||||
true_id true_prop=null
|
||||
+false true
|
||||
null_id null_prop=#null
|
||||
true_id true_prop=#null
|
||||
+false #true
|
||||
|
||||
bar \"indented\" // trailing whitespace after this\t
|
||||
/*
|
||||
|
|
@ -440,19 +462,22 @@ Some random comment
|
|||
a; b; c;
|
||||
/-commented \"node\"
|
||||
|
||||
another /*foo*/ \"node\" /-1 /*bar*/ null;
|
||||
another /*foo*/ \"node\" /-1 /*bar*/ #null;
|
||||
final;";
|
||||
let mut doc: KdlDocument = src.parse()?;
|
||||
|
||||
assert_eq!(doc.leading, Some("".into()));
|
||||
assert_eq!(doc.get_arg("foo"), Some(&1.into()));
|
||||
assert_eq!(
|
||||
doc.get_dash_args("foo"),
|
||||
vec![&1.into(), &2.into(), &"three".into()]
|
||||
);
|
||||
assert_eq!(
|
||||
doc.format().map(|f| &f.leading[..]),
|
||||
Some("\n// This is the first node\n")
|
||||
);
|
||||
|
||||
let foo = doc.get("foo").expect("expected a foo node");
|
||||
assert_eq!(foo.leading, Some("\n// This is the first node\n".into()));
|
||||
assert_eq!(foo.format().map(|f| &f.trailing[..]), Some("\n"));
|
||||
assert_eq!(&foo[2], &"three".into());
|
||||
assert_eq!(&foo["bar"], &"baz".into());
|
||||
assert_eq!(
|
||||
|
|
@ -479,19 +504,21 @@ final;";
|
|||
let a = doc.get("a").expect("expected a node");
|
||||
assert_eq!(
|
||||
format!("{}", a),
|
||||
"/*\nSome random comment\n */\n\na; ".to_string()
|
||||
"/*\nSome random comment\n */\n\na;".to_string()
|
||||
);
|
||||
|
||||
let b = doc.get("b").expect("expected a node");
|
||||
assert_eq!(format!("{}", b), "b; ".to_string());
|
||||
assert_eq!(format!("{}", b), " b;".to_string());
|
||||
|
||||
// Round-tripping works.
|
||||
assert_eq!(format!("{}", doc), src);
|
||||
|
||||
// Programmatic manipulation works.
|
||||
let mut node: KdlNode = "new\n".parse()?;
|
||||
// Manual entry parsing preserves formatting/reprs.
|
||||
node.push("\"blah\"=0xDEADbeef".parse::<KdlEntry>()?);
|
||||
// Manual entry parsing preserves formatting/reprs. Note that
|
||||
// if you're making KdlEntries this way, you need to inject
|
||||
// your own whitespace (or format the node)
|
||||
node.push(" \"blah\"=0xDEADbeef".parse::<KdlEntry>()?);
|
||||
doc.nodes_mut().push(node);
|
||||
|
||||
assert_eq!(
|
||||
|
|
@ -519,9 +546,11 @@ final;";
|
|||
doc.nodes_mut().push(bar);
|
||||
doc.nodes_mut().push(KdlNode::new("baz"));
|
||||
|
||||
doc.autoformat();
|
||||
|
||||
assert_eq!(
|
||||
r#"foo
|
||||
bar prop="value" 1 2 false null {
|
||||
bar prop=value 1 2 #false #null {
|
||||
barchild
|
||||
}
|
||||
baz
|
||||
|
|
@ -530,9 +559,10 @@ baz
|
|||
);
|
||||
}
|
||||
|
||||
#[ignore = "There's still issues around formatting comments and esclines."]
|
||||
#[test]
|
||||
fn fmt() -> miette::Result<()> {
|
||||
let mut doc: KdlDocument = r#"
|
||||
fn autoformat() -> miette::Result<()> {
|
||||
let mut doc: KdlDocument = r##"
|
||||
|
||||
/* x */ foo 1 "bar"=0xDEADbeef {
|
||||
child1 1 ;
|
||||
|
|
@ -543,14 +573,15 @@ baz
|
|||
|
||||
child3 "
|
||||
|
||||
string\t" \
|
||||
string\t
|
||||
" \
|
||||
{
|
||||
/*
|
||||
|
||||
|
||||
multiline*/
|
||||
inner1 \
|
||||
r"value" \
|
||||
#"value"# \
|
||||
;
|
||||
|
||||
inner2 \ //comment
|
||||
|
|
@ -562,12 +593,11 @@ baz
|
|||
|
||||
// trailing comment here
|
||||
|
||||
"#
|
||||
"##
|
||||
.parse()?;
|
||||
|
||||
KdlDocument::fmt(&mut doc);
|
||||
KdlDocument::autoformat(&mut doc);
|
||||
|
||||
print!("{}", doc);
|
||||
assert_eq!(
|
||||
doc.to_string(),
|
||||
r#"/* x */
|
||||
|
|
@ -575,12 +605,12 @@ foo 1 bar=0xdeadbeef {
|
|||
child1 1
|
||||
// child 2 comment
|
||||
child2 2 // comment
|
||||
child3 "\n\n string\t" {
|
||||
child3 "\nstring\t" {
|
||||
/*
|
||||
|
||||
|
||||
multiline*/
|
||||
inner1 r"value"
|
||||
inner1 value
|
||||
inner2 {
|
||||
inner3
|
||||
}
|
||||
|
|
@ -592,10 +622,9 @@ foo 1 bar=0xdeadbeef {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn simple_fmt() -> miette::Result<()> {
|
||||
fn simple_autoformat() -> miette::Result<()> {
|
||||
let mut doc: KdlDocument = "a { b { c { }; }; }".parse().unwrap();
|
||||
KdlDocument::fmt(&mut doc);
|
||||
print!("{}", doc);
|
||||
KdlDocument::autoformat(&mut doc);
|
||||
assert_eq!(
|
||||
doc.to_string(),
|
||||
r#"a {
|
||||
|
|
@ -619,6 +648,8 @@ foo 1 bar=0xdeadbeef {
|
|||
|
||||
#[cfg(feature = "span")]
|
||||
fn check_spans_for_node(node: &KdlNode, source: &impl miette::SourceCode) {
|
||||
use crate::KdlEntryFormat;
|
||||
|
||||
check_span_for_ident(node.name(), source);
|
||||
if let Some(ty) = node.ty() {
|
||||
check_span_for_ident(ty, source);
|
||||
|
|
@ -631,9 +662,9 @@ foo 1 bar=0xdeadbeef {
|
|||
if let Some(ty) = entry.ty() {
|
||||
check_span_for_ident(ty, source);
|
||||
}
|
||||
if let Some(repr) = entry.value_repr() {
|
||||
if let Some(KdlEntryFormat { value_repr, .. }) = entry.format() {
|
||||
if entry.name().is_none() && entry.ty().is_none() {
|
||||
check_span(repr, entry.span(), source);
|
||||
check_span(value_repr, entry.span(), source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -667,7 +698,7 @@ foo 1 bar=0xdeadbeef {
|
|||
this {
|
||||
is (a)"cool" document="to" read=(int)5 10.1 (u32)0x45
|
||||
and x="" {
|
||||
"it" /*shh*/ "has"="💯" r##"the"##
|
||||
"it" /*shh*/ "has"="💯" ##"the"##
|
||||
Best🎊est
|
||||
"syntax ever"
|
||||
}
|
||||
|
|
@ -678,7 +709,7 @@ nice
|
|||
inline { time; to; live "our" "dreams"; "y;all"; }
|
||||
"####;
|
||||
|
||||
let doc: KdlDocument = input.parse().unwrap();
|
||||
let doc: KdlDocument = input.parse()?;
|
||||
|
||||
// First check that all the identity-spans are correct
|
||||
check_spans_for_doc(&doc, &input);
|
||||
|
|
@ -724,7 +755,7 @@ inline { time; to; live "our" "dreams"; "y;all"; }
|
|||
// The node is what you expect, the whole line and its two braces
|
||||
check_span(
|
||||
r####"and x="" {
|
||||
"it" /*shh*/ "has"="💯" r##"the"##
|
||||
"it" /*shh*/ "has"="💯" ##"the"##
|
||||
Best🎊est
|
||||
"syntax ever"
|
||||
}"####,
|
||||
|
|
@ -735,11 +766,11 @@ inline { time; to; live "our" "dreams"; "y;all"; }
|
|||
// The child document is a little weird, it's the contents *inside* the braces
|
||||
// with extra newlines on both ends.
|
||||
check_span(
|
||||
r####"
|
||||
"it" /*shh*/ "has"="💯" r##"the"##
|
||||
r####"{
|
||||
"it" /*shh*/ "has"="💯" ##"the"##
|
||||
Best🎊est
|
||||
"syntax ever"
|
||||
"####,
|
||||
}"####,
|
||||
and_node.children().unwrap().span(),
|
||||
&input,
|
||||
);
|
||||
|
|
@ -750,7 +781,7 @@ inline { time; to; live "our" "dreams"; "y;all"; }
|
|||
// Now the "it" node, more straightforward
|
||||
let it_node = and_node.children().unwrap().get("it").unwrap();
|
||||
check_span(
|
||||
r####""it" /*shh*/ "has"="💯" r##"the"##"####,
|
||||
r####""it" /*shh*/ "has"="💯" ##"the"##"####,
|
||||
it_node.span(),
|
||||
&input,
|
||||
);
|
||||
|
|
@ -760,7 +791,7 @@ inline { time; to; live "our" "dreams"; "y;all"; }
|
|||
&input,
|
||||
);
|
||||
check_span(
|
||||
r####"r##"the"##"####,
|
||||
r####"##"the"##"####,
|
||||
it_node.entry(0).unwrap().span(),
|
||||
&input,
|
||||
);
|
||||
|
|
@ -775,7 +806,7 @@ inline { time; to; live "our" "dreams"; "y;all"; }
|
|||
|
||||
let inline_children = inline_node.children().unwrap();
|
||||
check_span(
|
||||
r#" time; to; live "our" "dreams"; "y;all"; "#,
|
||||
r#"{ time; to; live "our" "dreams"; "y;all"; }"#,
|
||||
inline_children.span(),
|
||||
&input,
|
||||
);
|
||||
|
|
|
|||
254
src/entry.rs
254
src/entry.rs
|
|
@ -2,7 +2,7 @@
|
|||
use miette::SourceSpan;
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
use crate::{parser, KdlError, KdlIdentifier, KdlValue};
|
||||
use crate::{v2_parser, KdlIdentifier, KdlParseFailure, KdlValue};
|
||||
|
||||
/// KDL Entries are the "arguments" to KDL nodes: either a (positional)
|
||||
/// [`Argument`](https://github.com/kdl-org/kdl/blob/main/SPEC.md#argument) or
|
||||
|
|
@ -10,36 +10,30 @@ use crate::{parser, KdlError, KdlIdentifier, KdlValue};
|
|||
/// [`Property`](https://github.com/kdl-org/kdl/blob/main/SPEC.md#property)
|
||||
#[derive(Debug, Clone, Eq)]
|
||||
pub struct KdlEntry {
|
||||
pub(crate) leading: Option<String>,
|
||||
pub(crate) ty: Option<KdlIdentifier>,
|
||||
pub(crate) value: KdlValue,
|
||||
pub(crate) value_repr: Option<String>,
|
||||
pub(crate) name: Option<KdlIdentifier>,
|
||||
pub(crate) trailing: Option<String>,
|
||||
pub(crate) format: Option<KdlEntryFormat>,
|
||||
#[cfg(feature = "span")]
|
||||
pub(crate) span: SourceSpan,
|
||||
}
|
||||
|
||||
impl PartialEq for KdlEntry {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.leading == other.leading
|
||||
&& self.ty == other.ty
|
||||
self.ty == other.ty
|
||||
&& self.value == other.value
|
||||
&& self.value_repr == other.value_repr
|
||||
&& self.name == other.name
|
||||
&& self.trailing == other.trailing
|
||||
&& self.format == other.format
|
||||
// intentionally omitted: self.span == other.span
|
||||
}
|
||||
}
|
||||
|
||||
impl std::hash::Hash for KdlEntry {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.leading.hash(state);
|
||||
self.ty.hash(state);
|
||||
self.value.hash(state);
|
||||
self.value_repr.hash(state);
|
||||
self.name.hash(state);
|
||||
self.trailing.hash(state);
|
||||
self.format.hash(state);
|
||||
// intentionally omitted: self.span.hash(state)
|
||||
}
|
||||
}
|
||||
|
|
@ -48,14 +42,12 @@ impl KdlEntry {
|
|||
/// Creates a new Argument (positional) KdlEntry.
|
||||
pub fn new(value: impl Into<KdlValue>) -> Self {
|
||||
KdlEntry {
|
||||
leading: None,
|
||||
ty: None,
|
||||
value: value.into(),
|
||||
value_repr: None,
|
||||
name: None,
|
||||
trailing: None,
|
||||
format: None,
|
||||
#[cfg(feature = "span")]
|
||||
span: SourceSpan::from(0..0),
|
||||
span: (0..0).into(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -64,6 +56,16 @@ impl KdlEntry {
|
|||
self.name.as_ref()
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to this node's name.
|
||||
pub fn name_mut(&mut self) -> Option<&mut KdlIdentifier> {
|
||||
self.name.as_mut()
|
||||
}
|
||||
|
||||
/// Sets this node's name.
|
||||
pub fn set_name(&mut self, name: Option<impl Into<KdlIdentifier>>) {
|
||||
self.name = name.map(|x| x.into());
|
||||
}
|
||||
|
||||
/// Gets the entry's value.
|
||||
pub fn value(&self) -> &KdlValue {
|
||||
&self.value
|
||||
|
|
@ -110,64 +112,45 @@ impl KdlEntry {
|
|||
self.ty = Some(ty.into());
|
||||
}
|
||||
|
||||
/// Gets the formatting details for this entry.
|
||||
pub fn format(&self) -> Option<&KdlEntryFormat> {
|
||||
self.format.as_ref()
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to this entry's formatting details.
|
||||
pub fn format_mut(&mut self) -> Option<&mut KdlEntryFormat> {
|
||||
self.format.as_mut()
|
||||
}
|
||||
|
||||
/// Sets the formatting details for this entry.
|
||||
pub fn set_format(&mut self, format: KdlEntryFormat) {
|
||||
self.format = Some(format);
|
||||
}
|
||||
|
||||
/// Creates a new Property (key/value) KdlEntry.
|
||||
pub fn new_prop(key: impl Into<KdlIdentifier>, value: impl Into<KdlValue>) -> Self {
|
||||
KdlEntry {
|
||||
leading: None,
|
||||
ty: None,
|
||||
value: value.into(),
|
||||
value_repr: None,
|
||||
name: Some(key.into()),
|
||||
trailing: None,
|
||||
format: None,
|
||||
#[cfg(feature = "span")]
|
||||
span: SourceSpan::from(0..0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets leading text (whitespace, comments) for this KdlEntry.
|
||||
pub fn leading(&self) -> Option<&str> {
|
||||
self.leading.as_deref()
|
||||
}
|
||||
|
||||
/// Sets leading text (whitespace, comments) for this KdlEntry.
|
||||
pub fn set_leading(&mut self, leading: impl Into<String>) {
|
||||
self.leading = Some(leading.into());
|
||||
}
|
||||
|
||||
/// Gets trailing text (whitespace, comments) for this KdlEntry.
|
||||
pub fn trailing(&self) -> Option<&str> {
|
||||
self.trailing.as_deref()
|
||||
}
|
||||
|
||||
/// Sets trailing text (whitespace, comments) for this KdlEntry.
|
||||
pub fn set_trailing(&mut self, trailing: impl Into<String>) {
|
||||
self.trailing = Some(trailing.into());
|
||||
}
|
||||
|
||||
/// Clears leading and trailing text (whitespace, comments), as well as
|
||||
/// resetting this entry's value to its default representation.
|
||||
pub fn clear_fmt(&mut self) {
|
||||
self.leading = None;
|
||||
self.trailing = None;
|
||||
self.value_repr = None;
|
||||
pub fn clear_format(&mut self) {
|
||||
self.format = None;
|
||||
if let Some(ty) = &mut self.ty {
|
||||
ty.clear_fmt();
|
||||
ty.clear_format();
|
||||
}
|
||||
if let Some(name) = &mut self.name {
|
||||
name.clear_fmt();
|
||||
name.clear_format();
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the custom string representation for this KdlEntry's [`KdlValue`].
|
||||
pub fn value_repr(&self) -> Option<&str> {
|
||||
self.value_repr.as_deref()
|
||||
}
|
||||
|
||||
/// Sets a custom string representation for this KdlEntry's [`KdlValue`].
|
||||
pub fn set_value_repr(&mut self, repr: impl Into<String>) {
|
||||
self.value_repr = Some(repr.into());
|
||||
}
|
||||
|
||||
/// Length of this entry when rendered as a string.
|
||||
pub fn len(&self) -> usize {
|
||||
format!("{}", self).len()
|
||||
|
|
@ -179,33 +162,54 @@ impl KdlEntry {
|
|||
}
|
||||
|
||||
/// Auto-formats this entry.
|
||||
pub fn fmt(&mut self) {
|
||||
self.leading = None;
|
||||
self.trailing = None;
|
||||
self.value_repr = None;
|
||||
pub fn autoformat(&mut self) {
|
||||
self.format = None;
|
||||
if let Some(name) = &mut self.name {
|
||||
name.fmt();
|
||||
name.autoformat();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for KdlEntry {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(leading) = &self.leading {
|
||||
if let Some(KdlEntryFormat { leading, .. }) = &self.format {
|
||||
write!(f, "{}", leading)?;
|
||||
}
|
||||
if let Some(name) = &self.name {
|
||||
write!(f, "{}=", name)?;
|
||||
write!(f, "{}", name)?;
|
||||
if let Some(KdlEntryFormat {
|
||||
after_key,
|
||||
after_eq,
|
||||
..
|
||||
}) = &self.format
|
||||
{
|
||||
write!(f, "{}={}", after_key, after_eq)?;
|
||||
} else {
|
||||
write!(f, "=")?;
|
||||
}
|
||||
}
|
||||
if let Some(ty) = &self.ty {
|
||||
write!(f, "({})", ty)?;
|
||||
write!(f, "(")?;
|
||||
if let Some(KdlEntryFormat { before_ty_name, .. }) = &self.format {
|
||||
write!(f, "{}", before_ty_name)?;
|
||||
}
|
||||
write!(f, "{}", ty)?;
|
||||
if let Some(KdlEntryFormat { after_ty_name, .. }) = &self.format {
|
||||
write!(f, "{}", after_ty_name)?;
|
||||
}
|
||||
write!(f, ")")?;
|
||||
}
|
||||
if let Some(repr) = &self.value_repr {
|
||||
write!(f, "{}", repr)?;
|
||||
if let Some(KdlEntryFormat {
|
||||
after_ty,
|
||||
value_repr,
|
||||
..
|
||||
}) = &self.format
|
||||
{
|
||||
write!(f, "{}{}", after_ty, value_repr)?;
|
||||
} else {
|
||||
write!(f, "{}", self.value)?;
|
||||
}
|
||||
if let Some(trailing) = &self.trailing {
|
||||
if let Some(KdlEntryFormat { trailing, .. }) = &self.format {
|
||||
write!(f, "{}", trailing)?;
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -232,14 +236,42 @@ where
|
|||
}
|
||||
|
||||
impl FromStr for KdlEntry {
|
||||
type Err = KdlError;
|
||||
type Err = KdlParseFailure;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let kdl_parser = parser::KdlParser::new(s);
|
||||
kdl_parser.parse(parser::entry_with_trailing(&kdl_parser))
|
||||
let (maybe_val, errs) = v2_parser::try_parse(v2_parser::padded_node_entry, s);
|
||||
if let (Some(Some(v)), true) = (maybe_val, errs.is_empty()) {
|
||||
Ok(v)
|
||||
} else {
|
||||
Err(v2_parser::failure_from_errs(errs, s))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Formatting details for [`KdlEntry`]s.
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct KdlEntryFormat {
|
||||
/// The actual text representation of the entry's value.
|
||||
pub value_repr: String,
|
||||
/// Whitespace and comments preceding the entry itself.
|
||||
pub leading: String,
|
||||
/// Whitespace and comments following the entry itself.
|
||||
pub trailing: String,
|
||||
/// Whitespace and comments after the entry's type annotation's closing
|
||||
/// `)`, before its value.
|
||||
pub after_ty: String,
|
||||
/// Whitespace and comments between the opening `(` of an entry's type
|
||||
/// annotation and its actual type name.
|
||||
pub before_ty_name: String,
|
||||
/// Whitespace and comments between the actual type name and the closing
|
||||
/// `)` in an entry's type annotation.
|
||||
pub after_ty_name: String,
|
||||
/// Whitespace and comments between an entry's key name and its equals sign.
|
||||
pub after_key: String,
|
||||
/// Whitespace and comments between an entry's equals sign and its value.
|
||||
pub after_eq: String,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
|
@ -249,8 +281,8 @@ mod test {
|
|||
let mut left_entry: KdlEntry = " name=1.03e2".parse()?;
|
||||
let mut right_entry: KdlEntry = " name=103.0".parse()?;
|
||||
assert_ne!(left_entry, right_entry);
|
||||
left_entry.clear_fmt();
|
||||
right_entry.clear_fmt();
|
||||
left_entry.clear_format();
|
||||
right_entry.clear_format();
|
||||
assert_eq!(left_entry, right_entry);
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -261,12 +293,10 @@ mod test {
|
|||
assert_eq!(
|
||||
entry,
|
||||
KdlEntry {
|
||||
leading: None,
|
||||
ty: None,
|
||||
value: KdlValue::Base10(42),
|
||||
value_repr: None,
|
||||
value: KdlValue::Integer(42),
|
||||
name: None,
|
||||
trailing: None,
|
||||
format: None,
|
||||
#[cfg(feature = "span")]
|
||||
span: SourceSpan::from(0..0),
|
||||
}
|
||||
|
|
@ -276,12 +306,10 @@ mod test {
|
|||
assert_eq!(
|
||||
entry,
|
||||
KdlEntry {
|
||||
leading: None,
|
||||
ty: None,
|
||||
value: KdlValue::Base10(42),
|
||||
value_repr: None,
|
||||
value: KdlValue::Integer(42),
|
||||
name: Some("name".into()),
|
||||
trailing: None,
|
||||
format: None,
|
||||
#[cfg(feature = "span")]
|
||||
span: SourceSpan::from(0..0),
|
||||
}
|
||||
|
|
@ -290,18 +318,55 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn parsing() -> miette::Result<()> {
|
||||
let entry: KdlEntry = " \\\n (\"m\\\"eh\")0xDEADbeef\t\\\n".parse()?;
|
||||
let entry: KdlEntry = "foo".parse()?;
|
||||
assert_eq!(
|
||||
entry,
|
||||
KdlEntry {
|
||||
leading: Some(" \\\n ".into()),
|
||||
ty: Some("\"m\\\"eh\"".parse()?),
|
||||
value: KdlValue::Base16(0xdeadbeef),
|
||||
value_repr: Some("0xDEADbeef".into()),
|
||||
ty: None,
|
||||
value: KdlValue::from("foo"),
|
||||
name: None,
|
||||
trailing: Some("\t\\\n".into()),
|
||||
format: Some(KdlEntryFormat {
|
||||
value_repr: "foo".into(),
|
||||
..Default::default()
|
||||
}),
|
||||
#[cfg(feature = "span")]
|
||||
span: SourceSpan::from(0..0),
|
||||
span: SourceSpan::from(0..3),
|
||||
}
|
||||
);
|
||||
|
||||
let entry: KdlEntry = "foo=bar".parse()?;
|
||||
assert_eq!(
|
||||
entry,
|
||||
KdlEntry {
|
||||
ty: None,
|
||||
value: KdlValue::from("bar"),
|
||||
name: Some("foo".parse()?),
|
||||
format: Some(KdlEntryFormat {
|
||||
value_repr: "bar".into(),
|
||||
..Default::default()
|
||||
}),
|
||||
#[cfg(feature = "span")]
|
||||
span: SourceSpan::from(0..7),
|
||||
}
|
||||
);
|
||||
|
||||
let entry: KdlEntry = " \\\n (\"m\\\"eh\")0xDEADbeef\t\\\n".parse()?;
|
||||
let mut ty: KdlIdentifier = "\"m\\\"eh\"".parse()?;
|
||||
ty.span = (5..12).into();
|
||||
assert_eq!(
|
||||
entry,
|
||||
KdlEntry {
|
||||
ty: Some(ty),
|
||||
value: KdlValue::Integer(0xdeadbeef),
|
||||
name: None,
|
||||
format: Some(KdlEntryFormat {
|
||||
leading: " \\\n ".into(),
|
||||
trailing: "\t\\\n".into(),
|
||||
value_repr: "0xDEADbeef".into(),
|
||||
..Default::default()
|
||||
}),
|
||||
#[cfg(feature = "span")]
|
||||
span: SourceSpan::from(0..26),
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -309,12 +374,19 @@ mod test {
|
|||
assert_eq!(
|
||||
entry,
|
||||
KdlEntry {
|
||||
leading: Some(" \\\n ".into()),
|
||||
format: Some(KdlEntryFormat {
|
||||
leading: " \\\n ".into(),
|
||||
trailing: "\t\\\n".into(),
|
||||
value_repr: "0xDEADbeef".into(),
|
||||
before_ty_name: "".into(),
|
||||
after_ty_name: "".into(),
|
||||
after_ty: "".into(),
|
||||
after_key: "".into(),
|
||||
after_eq: "".into(),
|
||||
}),
|
||||
ty: Some("\"m\\\"eh\"".parse()?),
|
||||
value: KdlValue::Base16(0xdeadbeef),
|
||||
value_repr: Some("0xDEADbeef".into()),
|
||||
value: KdlValue::Integer(0xdeadbeef),
|
||||
name: Some("\"foo\"".parse()?),
|
||||
trailing: Some("\t\\\n".into()),
|
||||
#[cfg(feature = "span")]
|
||||
span: SourceSpan::from(0..0),
|
||||
}
|
||||
|
|
@ -325,10 +397,10 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn display() {
|
||||
let entry = KdlEntry::new(KdlValue::Base10(42));
|
||||
let entry = KdlEntry::new(KdlValue::Integer(42));
|
||||
assert_eq!(format!("{}", entry), "42");
|
||||
|
||||
let entry = KdlEntry::new_prop("name", KdlValue::Base10(42));
|
||||
let entry = KdlEntry::new_prop("name", KdlValue::Integer(42));
|
||||
assert_eq!(format!("{}", entry), "name=42");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
43
src/error.rs
43
src/error.rs
|
|
@ -1,4 +1,7 @@
|
|||
use std::num::{ParseFloatError, ParseIntError};
|
||||
use std::{
|
||||
num::{ParseFloatError, ParseIntError},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use miette::{Diagnostic, SourceSpan};
|
||||
use nom::error::{ContextError, ErrorKind, FromExternalError, ParseError};
|
||||
|
|
@ -10,11 +13,12 @@ use {
|
|||
std::convert::{TryFrom, TryInto},
|
||||
};
|
||||
|
||||
/// An error that occurs when parsing a KDL document.
|
||||
/// The toplevel `Error` type for KDL: this is returned when a KDL document
|
||||
/// failed to parse entirely.
|
||||
///
|
||||
/// This error implements [`miette::Diagnostic`] and can be used to display
|
||||
/// detailed, pretty-printed diagnostic messages when using [`miette::Result`]
|
||||
/// and the `"fancy"` feature flag for `miette`:
|
||||
/// This diagnostic implements [`miette::Diagnostic`] and can be used to
|
||||
/// display detailed, pretty-printed diagnostic messages when using
|
||||
/// [`miette::Result`] and the `"fancy"` feature flag for `miette`:
|
||||
///
|
||||
/// ```no_run
|
||||
/// fn main() -> miette::Result<()> {
|
||||
|
|
@ -35,11 +39,26 @@ use {
|
|||
/// help: Floating point numbers must be base 10, and have numbers after the decimal point.
|
||||
/// ```
|
||||
#[derive(Debug, Diagnostic, Clone, Eq, PartialEq, Error)]
|
||||
#[error("{kind}")]
|
||||
pub struct KdlError {
|
||||
/// Source string for the KDL document that failed to parse.
|
||||
#[error("Failed to parse KDL.")]
|
||||
pub struct KdlParseFailure {
|
||||
/// Original input that this failure came from.
|
||||
#[source_code]
|
||||
pub input: String,
|
||||
pub input: Arc<String>,
|
||||
|
||||
/// Sub-diagnostics for this failure.
|
||||
#[related]
|
||||
pub diagnostics: Vec<KdlDiagnostic>,
|
||||
}
|
||||
|
||||
/// An individual diagnostic message for a KDL parsing issue.
|
||||
///
|
||||
/// While generally signifying errors, they can also be treated as warnings.
|
||||
#[derive(Debug, Diagnostic, Clone, Eq, PartialEq, Error)]
|
||||
#[error("{kind}")]
|
||||
pub struct KdlDiagnostic {
|
||||
/// Shared source for the diagnostic.
|
||||
#[source_code]
|
||||
pub input: Arc<String>,
|
||||
|
||||
/// Offset in chars of the error.
|
||||
#[label("{}", label.unwrap_or("here"))]
|
||||
|
|
@ -52,6 +71,10 @@ pub struct KdlError {
|
|||
#[help]
|
||||
pub help: Option<&'static str>,
|
||||
|
||||
/// Severity level for the Diagnostic.
|
||||
#[diagnostic(severity)]
|
||||
pub severity: miette::Severity,
|
||||
|
||||
/// Specific error kind for this parser error.
|
||||
pub kind: KdlErrorKind,
|
||||
}
|
||||
|
|
@ -77,7 +100,7 @@ pub enum KdlErrorKind {
|
|||
|
||||
/// Generic unspecified error. If this is returned, the call site should
|
||||
/// be annotated with context, if possible.
|
||||
#[error("An unspecified error occurred.")]
|
||||
#[error("An unspecified parse error occurred.")]
|
||||
#[diagnostic(code(kdl::other))]
|
||||
Other,
|
||||
}
|
||||
|
|
|
|||
35
src/fmt.rs
35
src/fmt.rs
|
|
@ -1,20 +1,15 @@
|
|||
use std::fmt::Write as _;
|
||||
|
||||
pub(crate) fn fmt_leading(leading: &mut String, indent: usize, no_comments: bool) {
|
||||
if leading.is_empty() {
|
||||
return;
|
||||
}
|
||||
pub(crate) fn autoformat_leading(leading: &mut String, indent: usize, no_comments: bool) {
|
||||
let mut result = String::new();
|
||||
if !no_comments {
|
||||
let input = leading.trim();
|
||||
let kdl_parser = crate::parser::KdlParser { full_input: input };
|
||||
let comments = kdl_parser
|
||||
.parse(crate::parser::leading_comments(&kdl_parser))
|
||||
.expect("invalid leading text");
|
||||
for line in comments {
|
||||
let trimmed = line.trim();
|
||||
if !trimmed.is_empty() {
|
||||
writeln!(result, "{:indent$}{}", "", trimmed, indent = indent).unwrap();
|
||||
if !input.is_empty() {
|
||||
for line in input.lines() {
|
||||
let trimmed = line.trim();
|
||||
if !trimmed.is_empty() {
|
||||
writeln!(result, "{:indent$}{}", "", trimmed, indent = indent).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,20 +17,18 @@ pub(crate) fn fmt_leading(leading: &mut String, indent: usize, no_comments: bool
|
|||
*leading = result;
|
||||
}
|
||||
|
||||
pub(crate) fn fmt_trailing(decor: &mut String, no_comments: bool) {
|
||||
pub(crate) fn autoformat_trailing(decor: &mut String, no_comments: bool) {
|
||||
if decor.is_empty() {
|
||||
return;
|
||||
}
|
||||
*decor = decor.trim().to_string();
|
||||
let mut result = String::new();
|
||||
if !no_comments {
|
||||
let input = &*decor;
|
||||
let kdl_parser = crate::parser::KdlParser { full_input: input };
|
||||
let comments = kdl_parser
|
||||
.parse(crate::parser::trailing_comments(&kdl_parser))
|
||||
.expect("invalid trailing text");
|
||||
for comment in comments {
|
||||
result.push_str(comment);
|
||||
if !decor.is_empty() && !no_comments {
|
||||
if decor.trim_start() == &decor[..] {
|
||||
write!(result, " ").unwrap();
|
||||
}
|
||||
for comment in decor.lines() {
|
||||
writeln!(result, "{comment}").unwrap();
|
||||
}
|
||||
}
|
||||
*decor = result;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
use miette::SourceSpan;
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
use crate::{parser, KdlError};
|
||||
use crate::{v2_parser, KdlParseFailure, KdlValue};
|
||||
|
||||
/// Represents a KDL
|
||||
/// [Identifier](https://github.com/kdl-org/kdl/blob/main/SPEC.md#identifier).
|
||||
|
|
@ -79,12 +79,12 @@ impl KdlIdentifier {
|
|||
/// Resets this identifier to its default representation. It will attempt
|
||||
/// to make it an unquoted identifier, and fall back to a string
|
||||
/// representation if that would be invalid.
|
||||
pub fn clear_fmt(&mut self) {
|
||||
pub fn clear_format(&mut self) {
|
||||
self.repr = None;
|
||||
}
|
||||
|
||||
/// Auto-formats this identifier.
|
||||
pub fn fmt(&mut self) {
|
||||
pub fn autoformat(&mut self) {
|
||||
self.repr = None;
|
||||
}
|
||||
}
|
||||
|
|
@ -93,85 +93,12 @@ impl Display for KdlIdentifier {
|
|||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(repr) = &self.repr {
|
||||
write!(f, "{}", repr)
|
||||
} else if self.plain_value() {
|
||||
write!(f, "{}", self.value)
|
||||
} else {
|
||||
write!(f, "{:?}", self.value)
|
||||
write!(f, "{}", KdlValue::String(self.value().into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KdlIdentifier {
|
||||
pub(crate) fn is_identifier_char(c: char) -> bool {
|
||||
!((c as u32) < 0x20
|
||||
|| (c as u32) > 0x10ffff
|
||||
|| matches!(
|
||||
c,
|
||||
'\\' | '/'
|
||||
| '('
|
||||
| ')'
|
||||
| '{'
|
||||
| '}'
|
||||
| '<'
|
||||
| '>'
|
||||
| ';'
|
||||
| '['
|
||||
| ']'
|
||||
| '='
|
||||
| ','
|
||||
| '"'
|
||||
// Newlines
|
||||
| '\r'
|
||||
| '\n'
|
||||
| '\u{0085}'
|
||||
| '\u{000C}'
|
||||
| '\u{2028}'
|
||||
| '\u{2029}'
|
||||
// Whitespace
|
||||
| ' '
|
||||
| '\t'
|
||||
| '\u{FEFF}'
|
||||
| '\u{00A0}'
|
||||
| '\u{1680}'
|
||||
| '\u{2000}'
|
||||
| '\u{2001}'
|
||||
| '\u{2002}'
|
||||
| '\u{2003}'
|
||||
| '\u{2004}'
|
||||
| '\u{2005}'
|
||||
| '\u{2006}'
|
||||
| '\u{2007}'
|
||||
| '\u{2008}'
|
||||
| '\u{2009}'
|
||||
| '\u{200A}'
|
||||
| '\u{202F}'
|
||||
| '\u{205F}'
|
||||
| '\u{3000}'
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn is_initial_char(c: char) -> bool {
|
||||
!c.is_numeric() && Self::is_identifier_char(c)
|
||||
}
|
||||
|
||||
fn plain_value(&self) -> bool {
|
||||
let mut iter = self.value.chars();
|
||||
if let Some(c) = iter.next() {
|
||||
if !Self::is_initial_char(c) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
for char in iter {
|
||||
if !Self::is_identifier_char(char) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for KdlIdentifier {
|
||||
fn from(value: &str) -> Self {
|
||||
KdlIdentifier {
|
||||
|
|
@ -201,11 +128,15 @@ impl From<KdlIdentifier> for String {
|
|||
}
|
||||
|
||||
impl FromStr for KdlIdentifier {
|
||||
type Err = KdlError;
|
||||
type Err = KdlParseFailure;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let kdl_parser = crate::parser::KdlParser::new(s);
|
||||
kdl_parser.parse(parser::identifier(&kdl_parser))
|
||||
let (maybe_val, errs) = v2_parser::try_parse(v2_parser::identifier, s);
|
||||
if let Some(v) = maybe_val {
|
||||
Ok(v)
|
||||
} else {
|
||||
Err(v2_parser::failure_from_errs(errs, s))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -226,7 +157,7 @@ mod test {
|
|||
}
|
||||
);
|
||||
|
||||
let quoted = "\"foo\\\"bar\"";
|
||||
let quoted = r#""foo\"bar""#;
|
||||
assert_eq!(
|
||||
quoted.parse::<KdlIdentifier>()?,
|
||||
KdlIdentifier {
|
||||
|
|
|
|||
85
src/lib.rs
85
src/lib.rs
|
|
@ -64,38 +64,6 @@
|
|||
//! [`KdlDocument`], [`KdlNode`], [`KdlEntry`], and [`KdlIdentifier`] can all
|
||||
//! be parsed and managed this way.
|
||||
//!
|
||||
//! ### Query Engine
|
||||
//!
|
||||
//! `kdl` includes a query engine for
|
||||
//! [KQL](https://github.com/kdl-org/kdl/blob/main/QUERY-SPEC.md), which lets you
|
||||
//! pick out nodes from a document using a CSS Selectors-style syntax.
|
||||
//!
|
||||
//! Queries can be done from either a [`KdlDocument`] or a [`KdlNode`], with
|
||||
//! mostly the same semantics.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use kdl::KdlDocument;
|
||||
//!
|
||||
//! let doc = r#"
|
||||
//! a {
|
||||
//! b 1
|
||||
//! c 2
|
||||
//! d 3 {
|
||||
//! e prop="hello"
|
||||
//! }
|
||||
//! }
|
||||
//! "#.parse::<KdlDocument>().expect("failed to parse KDL");
|
||||
//!
|
||||
//! let results = doc.query("a > b").expect("failed to parse query");
|
||||
//! assert_eq!(results, Some(&doc.nodes()[0].children().unwrap().nodes()[0]));
|
||||
//!
|
||||
//! let results = doc.query_get("e", "prop").expect("failed to parse query");
|
||||
//! assert_eq!(results, Some(&"hello".into()));
|
||||
//!
|
||||
//! let results = doc.query_get_all("a > []", 0).expect("failed to parse query").collect::<Vec<_>>();
|
||||
//! assert_eq!(results, vec![&1.into(), &2.into(), &3.into()]);
|
||||
//! ```
|
||||
//!
|
||||
//! ## Error Reporting
|
||||
//!
|
||||
//! [`KdlError`] implements [`miette::Diagnostic`] and can be used to display
|
||||
|
|
@ -152,14 +120,51 @@
|
|||
//! you [`KdlDocument::fmt`] in which case the original representation will be
|
||||
//! thrown away and the actual value will be used when serializing.
|
||||
//!
|
||||
//! ## Minimum Supported Rust Version
|
||||
//!
|
||||
//! You must be at least `1.70.0` tall to get on this ride.
|
||||
//!
|
||||
//! ## License
|
||||
//!
|
||||
//! The code in this repository is covered by [the Apache-2.0
|
||||
//! License](LICENSE.md).
|
||||
|
||||
// TODO(@zkat): bring this back later.
|
||||
// ### Query Engine
|
||||
|
||||
// `kdl` includes a query engine for
|
||||
// [KQL](https://github.com/kdl-org/kdl/blob/main/QUERY-SPEC.md), which lets you
|
||||
// pick out nodes from a document using a CSS Selectors-style syntax.
|
||||
|
||||
// Queries can be done from either a [`KdlDocument`] or a [`KdlNode`], with
|
||||
// mostly the same semantics.
|
||||
|
||||
// ```rust
|
||||
// use kdl::KdlDocument;
|
||||
|
||||
// let doc = r#"
|
||||
// a {
|
||||
// b 1
|
||||
// c 2
|
||||
// d 3 {
|
||||
// e prop="hello"
|
||||
// }
|
||||
// }
|
||||
// "#.parse::<KdlDocument>().expect("failed to parse KDL");
|
||||
|
||||
// let results = doc.query("a > b").expect("failed to parse query");
|
||||
// assert_eq!(results, Some(&doc.nodes()[0].children().unwrap().nodes()[0]));
|
||||
|
||||
// let results = doc.query_get("e", "prop").expect("failed to parse query");
|
||||
// assert_eq!(results, Some(&"hello".into()));
|
||||
|
||||
// let results = doc.query_get_all("a > []", 0).expect("failed to parse query").collect::<Vec<_>>();
|
||||
// assert_eq!(results, vec![&1.into(), &2.into(), &3.into()]);
|
||||
// ```
|
||||
|
||||
#![deny(missing_debug_implementations, nonstandard_style)]
|
||||
#![warn(missing_docs, unreachable_pub, rust_2018_idioms, unreachable_pub)]
|
||||
#![cfg_attr(test, deny(warnings))]
|
||||
#![warn(missing_docs, rust_2018_idioms, unreachable_pub)]
|
||||
// #![cfg_attr(test, deny(warnings))]
|
||||
#![doc(html_favicon_url = "https://kdl.dev/favicon.ico")]
|
||||
#![doc(html_logo_url = "https://kdl.dev/logo.svg")]
|
||||
|
||||
|
|
@ -168,7 +173,7 @@ pub use entry::*;
|
|||
pub use error::*;
|
||||
pub use identifier::*;
|
||||
pub use node::*;
|
||||
pub use query::*;
|
||||
// pub use query::*;
|
||||
pub use value::*;
|
||||
|
||||
mod document;
|
||||
|
|
@ -177,8 +182,10 @@ mod error;
|
|||
mod fmt;
|
||||
mod identifier;
|
||||
mod node;
|
||||
mod nom_compat;
|
||||
mod parser;
|
||||
mod query;
|
||||
mod query_parser;
|
||||
// mod nom_compat;
|
||||
// mod query;
|
||||
// mod query_parser;
|
||||
// mod v1_parser;
|
||||
mod value;
|
||||
|
||||
mod v2_parser;
|
||||
|
|
|
|||
294
src/node.rs
294
src/node.rs
|
|
@ -8,49 +8,44 @@ use std::{
|
|||
use miette::SourceSpan;
|
||||
|
||||
use crate::{
|
||||
parser, IntoKdlQuery, KdlDocument, KdlEntry, KdlError, KdlIdentifier, KdlQueryIterator,
|
||||
KdlValue,
|
||||
v2_parser, KdlDocument, KdlDocumentFormat, KdlEntry, KdlIdentifier, KdlParseFailure, KdlValue,
|
||||
};
|
||||
|
||||
static INDENT: usize = 4;
|
||||
|
||||
/// Represents an individual KDL
|
||||
/// [`Node`](https://github.com/kdl-org/kdl/blob/main/SPEC.md#node) inside a
|
||||
/// KDL Document.
|
||||
#[derive(Debug, Clone, Eq)]
|
||||
pub struct KdlNode {
|
||||
pub(crate) leading: Option<String>,
|
||||
pub(crate) ty: Option<KdlIdentifier>,
|
||||
pub(crate) name: KdlIdentifier,
|
||||
// TODO: consider using `hashlink` for this instead, later.
|
||||
pub(crate) entries: Vec<KdlEntry>,
|
||||
pub(crate) before_children: Option<String>,
|
||||
pub(crate) children: Option<KdlDocument>,
|
||||
pub(crate) trailing: Option<String>,
|
||||
pub(crate) format: Option<KdlNodeFormat>,
|
||||
#[cfg(feature = "span")]
|
||||
pub(crate) span: SourceSpan,
|
||||
}
|
||||
|
||||
impl PartialEq for KdlNode {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.leading == other.leading
|
||||
&& self.ty == other.ty
|
||||
self.ty == other.ty
|
||||
&& self.name == other.name
|
||||
&& self.entries == other.entries
|
||||
&& self.before_children == other.before_children
|
||||
&& self.children == other.children
|
||||
&& self.trailing == other.trailing
|
||||
&& self.format == other.format
|
||||
// intentionally omitted: self.span == other.span
|
||||
}
|
||||
}
|
||||
|
||||
impl std::hash::Hash for KdlNode {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.leading.hash(state);
|
||||
self.ty.hash(state);
|
||||
self.name.hash(state);
|
||||
self.entries.hash(state);
|
||||
self.before_children.hash(state);
|
||||
self.children.hash(state);
|
||||
self.trailing.hash(state);
|
||||
self.format.hash(state);
|
||||
// Intentionally omitted: self.span.hash(state);
|
||||
}
|
||||
}
|
||||
|
|
@ -60,12 +55,13 @@ impl KdlNode {
|
|||
pub fn new(name: impl Into<KdlIdentifier>) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
leading: None,
|
||||
ty: None,
|
||||
entries: Vec::new(),
|
||||
before_children: None,
|
||||
children: None,
|
||||
trailing: None,
|
||||
format: Some(KdlNodeFormat {
|
||||
trailing: "\n".into(),
|
||||
..Default::default()
|
||||
}),
|
||||
#[cfg(feature = "span")]
|
||||
span: SourceSpan::from(0..0),
|
||||
}
|
||||
|
|
@ -128,36 +124,6 @@ impl KdlNode {
|
|||
&mut self.entries
|
||||
}
|
||||
|
||||
/// Gets leading text (whitespace, comments) for this node.
|
||||
pub fn leading(&self) -> Option<&str> {
|
||||
self.leading.as_deref()
|
||||
}
|
||||
|
||||
/// Sets leading text (whitespace, comments) for this node.
|
||||
pub fn set_leading(&mut self, leading: impl Into<String>) {
|
||||
self.leading = Some(leading.into());
|
||||
}
|
||||
|
||||
/// Gets text (whitespace, comments) right before the children block's starting `{`.
|
||||
pub fn before_children(&self) -> Option<&str> {
|
||||
self.before_children.as_deref()
|
||||
}
|
||||
|
||||
/// Gets text (whitespace, comments) right before the children block's starting `{`.
|
||||
pub fn set_before_children(&mut self, before: impl Into<String>) {
|
||||
self.before_children = Some(before.into());
|
||||
}
|
||||
|
||||
/// Gets trailing text (whitespace, comments) for this node.
|
||||
pub fn trailing(&self) -> Option<&str> {
|
||||
self.trailing.as_deref()
|
||||
}
|
||||
|
||||
/// Sets trailing text (whitespace, comments) for this node.
|
||||
pub fn set_trailing(&mut self, trailing: impl Into<String>) {
|
||||
self.trailing = Some(trailing.into());
|
||||
}
|
||||
|
||||
/// Length of this node when rendered as a string.
|
||||
pub fn len(&self) -> usize {
|
||||
format!("{}", self).len()
|
||||
|
|
@ -174,23 +140,21 @@ impl KdlNode {
|
|||
///
|
||||
/// If you want to clear formatting on all children and entries as well,
|
||||
/// use [`Self::clear_fmt_recursive`].
|
||||
pub fn clear_fmt(&mut self) {
|
||||
self.leading = None;
|
||||
self.trailing = None;
|
||||
self.before_children = None;
|
||||
pub fn clear_format(&mut self) {
|
||||
self.format = None;
|
||||
}
|
||||
|
||||
/// Clears leading and trailing text (whitespace, comments), as well as
|
||||
/// the space before the children block, if any. Individual entries and
|
||||
/// children formatting will also be cleared.
|
||||
pub fn clear_fmt_recursive(&mut self) {
|
||||
self.clear_fmt();
|
||||
self.name.clear_fmt();
|
||||
pub fn clear_format_recursive(&mut self) {
|
||||
self.clear_format();
|
||||
self.name.clear_format();
|
||||
if let Some(children) = &mut self.children {
|
||||
children.clear_fmt_recursive();
|
||||
children.clear_format_recursive();
|
||||
}
|
||||
for entry in self.entries.iter_mut() {
|
||||
entry.clear_fmt();
|
||||
entry.clear_format();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -408,54 +372,71 @@ impl KdlNode {
|
|||
self.children_mut().as_mut().unwrap()
|
||||
}
|
||||
|
||||
/// Gets the formatting details for this node.
|
||||
pub fn format(&self) -> Option<&KdlNodeFormat> {
|
||||
self.format.as_ref()
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to this node's formatting details.
|
||||
pub fn format_mut(&mut self) -> Option<&mut KdlNodeFormat> {
|
||||
self.format.as_mut()
|
||||
}
|
||||
|
||||
/// Sets the formatting details for this node.
|
||||
pub fn set_format(&mut self, format: KdlNodeFormat) {
|
||||
self.format = Some(format);
|
||||
}
|
||||
/// Auto-formats this node and its contents.
|
||||
pub fn fmt(&mut self) {
|
||||
self.fmt_impl(0, false);
|
||||
pub fn autoformat(&mut self) {
|
||||
self.autoformat_impl(0, false);
|
||||
}
|
||||
|
||||
/// Auto-formats this node and its contents, stripping comments.
|
||||
pub fn fmt_no_comments(&mut self) {
|
||||
self.fmt_impl(0, true);
|
||||
pub fn autoformat_no_comments(&mut self) {
|
||||
self.autoformat_impl(0, true);
|
||||
}
|
||||
|
||||
/// Queries this Node according to the KQL query language,
|
||||
/// returning an iterator over all matching nodes.
|
||||
pub fn query_all(&self, query: impl IntoKdlQuery) -> Result<KdlQueryIterator<'_>, KdlError> {
|
||||
let q = query.into_query()?;
|
||||
Ok(KdlQueryIterator::new(Some(self), None, q))
|
||||
}
|
||||
// TODO(@zkat): These should all be moved into the query module, instead
|
||||
// of being model methods.
|
||||
//
|
||||
// /// Queries this Node according to the KQL
|
||||
// query language, /// returning an iterator over all matching nodes. pub
|
||||
// fn query_all( &self, query: impl IntoKdlQuery, ) ->
|
||||
// Result<KdlQueryIterator<'_>, KdlDiagnostic> { let q =
|
||||
// query.into_query()?; Ok(KdlQueryIterator::new(Some(self), None, q))
|
||||
// }
|
||||
|
||||
/// Queries this Node according to the KQL query language,
|
||||
/// returning the first match, if any.
|
||||
pub fn query(&self, query: impl IntoKdlQuery) -> Result<Option<&KdlNode>, KdlError> {
|
||||
Ok(self.query_all(query)?.next())
|
||||
}
|
||||
// /// Queries this Node according to the KQL query language,
|
||||
// /// returning the first match, if any.
|
||||
// pub fn query(&self, query: impl IntoKdlQuery) -> Result<Option<&KdlNode>, KdlDiagnostic> {
|
||||
// Ok(self.query_all(query)?.next())
|
||||
// }
|
||||
|
||||
/// Queries this Node according to the KQL query language,
|
||||
/// picking the first match, and calling `.get(key)` on it, if the query
|
||||
/// succeeded.
|
||||
pub fn query_get(
|
||||
&self,
|
||||
query: impl IntoKdlQuery,
|
||||
key: impl Into<NodeKey>,
|
||||
) -> Result<Option<&KdlValue>, KdlError> {
|
||||
Ok(self.query(query)?.and_then(|node| node.get(key)))
|
||||
}
|
||||
// /// Queries this Node according to the KQL query language,
|
||||
// /// picking the first match, and calling `.get(key)` on it, if the query
|
||||
// /// succeeded.
|
||||
// pub fn query_get(
|
||||
// &self,
|
||||
// query: impl IntoKdlQuery,
|
||||
// key: impl Into<NodeKey>,
|
||||
// ) -> Result<Option<&KdlValue>, KdlDiagnostic> {
|
||||
// Ok(self.query(query)?.and_then(|node| node.get(key)))
|
||||
// }
|
||||
|
||||
/// Queries this Node according to the KQL query language,
|
||||
/// returning an iterator over all matching nodes, returning the requested
|
||||
/// field from each of those nodes and filtering out nodes that don't have
|
||||
/// it.
|
||||
pub fn query_get_all(
|
||||
&self,
|
||||
query: impl IntoKdlQuery,
|
||||
key: impl Into<NodeKey>,
|
||||
) -> Result<impl Iterator<Item = &KdlValue>, KdlError> {
|
||||
let key: NodeKey = key.into();
|
||||
Ok(self
|
||||
.query_all(query)?
|
||||
.filter_map(move |node| node.get(key.clone())))
|
||||
}
|
||||
// /// Queries this Node according to the KQL query language,
|
||||
// /// returning an iterator over all matching nodes, returning the requested
|
||||
// /// field from each of those nodes and filtering out nodes that don't have
|
||||
// /// it.
|
||||
// pub fn query_get_all(
|
||||
// &self,
|
||||
// query: impl IntoKdlQuery,
|
||||
// key: impl Into<NodeKey>,
|
||||
// ) -> Result<impl Iterator<Item = &KdlValue>, KdlDiagnostic> {
|
||||
// let key: NodeKey = key.into();
|
||||
// Ok(self
|
||||
// .query_all(query)?
|
||||
// .filter_map(move |node| node.get(key.clone())))
|
||||
// }
|
||||
}
|
||||
|
||||
/// Represents a [`KdlNode`]'s entry key.
|
||||
|
|
@ -517,11 +498,15 @@ impl IndexMut<&str> for KdlNode {
|
|||
}
|
||||
|
||||
impl FromStr for KdlNode {
|
||||
type Err = KdlError;
|
||||
type Err = KdlParseFailure;
|
||||
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
let kdl_parser = crate::parser::KdlParser::new(input);
|
||||
kdl_parser.parse(parser::node(&kdl_parser))
|
||||
let (maybe_val, errs) = v2_parser::try_parse(v2_parser::padded_node, input);
|
||||
if let (Some(v), true) = (maybe_val, errs.is_empty()) {
|
||||
Ok(v)
|
||||
} else {
|
||||
Err(v2_parser::failure_from_errs(errs, input))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -532,36 +517,46 @@ impl Display for KdlNode {
|
|||
}
|
||||
|
||||
impl KdlNode {
|
||||
pub(crate) fn fmt_impl(&mut self, indent: usize, no_comments: bool) {
|
||||
if let Some(s) = self.leading.as_mut() {
|
||||
crate::fmt::fmt_leading(s, indent, no_comments);
|
||||
}
|
||||
if let Some(s) = self.trailing.as_mut() {
|
||||
crate::fmt::fmt_trailing(s, no_comments);
|
||||
if s.starts_with(';') {
|
||||
s.remove(0);
|
||||
pub(crate) fn autoformat_impl(&mut self, indent: usize, no_comments: bool) {
|
||||
if let Some(KdlNodeFormat {
|
||||
leading,
|
||||
trailing,
|
||||
before_children,
|
||||
..
|
||||
}) = self.format_mut()
|
||||
{
|
||||
crate::fmt::autoformat_leading(leading, indent, no_comments);
|
||||
crate::fmt::autoformat_trailing(trailing, no_comments);
|
||||
*trailing = trailing.trim().into();
|
||||
if trailing.starts_with(';') {
|
||||
trailing.remove(0);
|
||||
}
|
||||
if let Some(c) = s.chars().next() {
|
||||
if let Some(c) = trailing.chars().next() {
|
||||
if !c.is_whitespace() {
|
||||
s.insert(0, ' ');
|
||||
trailing.insert(0, ' ');
|
||||
}
|
||||
}
|
||||
s.push('\n');
|
||||
trailing.push('\n');
|
||||
|
||||
*before_children = " ".into();
|
||||
} else {
|
||||
self.set_format(KdlNodeFormat {
|
||||
trailing: "\n".into(),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
self.before_children = None;
|
||||
self.name.clear_fmt();
|
||||
self.name.clear_format();
|
||||
if let Some(ty) = self.ty.as_mut() {
|
||||
ty.clear_fmt()
|
||||
ty.clear_format()
|
||||
}
|
||||
for entry in &mut self.entries {
|
||||
entry.fmt();
|
||||
entry.autoformat();
|
||||
}
|
||||
if let Some(children) = self.children.as_mut() {
|
||||
children.fmt_impl(indent + 4, no_comments);
|
||||
if let Some(leading) = children.leading.as_mut() {
|
||||
children.autoformat_impl(indent + INDENT, no_comments);
|
||||
if let Some(KdlDocumentFormat { leading, trailing }) = children.format_mut() {
|
||||
*leading = leading.trim().into();
|
||||
leading.push('\n');
|
||||
}
|
||||
if let Some(trailing) = children.trailing.as_mut() {
|
||||
trailing.push_str(format!("{:indent$}", "", indent = indent).as_str());
|
||||
}
|
||||
}
|
||||
|
|
@ -572,7 +567,7 @@ impl KdlNode {
|
|||
f: &mut std::fmt::Formatter<'_>,
|
||||
indent: usize,
|
||||
) -> std::fmt::Result {
|
||||
if let Some(leading) = &self.leading {
|
||||
if let Some(KdlNodeFormat { leading, .. }) = self.format() {
|
||||
write!(f, "{}", leading)?;
|
||||
} else {
|
||||
write!(f, "{:indent$}", "", indent = indent)?;
|
||||
|
|
@ -583,35 +578,56 @@ impl KdlNode {
|
|||
write!(f, "{}", self.name)?;
|
||||
let mut space_before_children = true;
|
||||
for entry in &self.entries {
|
||||
if entry.leading.is_none() {
|
||||
if entry.format().is_none() {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
write!(f, "{}", entry)?;
|
||||
space_before_children = entry.trailing.is_none();
|
||||
space_before_children = entry.format().is_none();
|
||||
}
|
||||
if let Some(children) = &self.children {
|
||||
if let Some(before) = self.before_children() {
|
||||
write!(f, "{}", before)?;
|
||||
if let Some(KdlNodeFormat {
|
||||
before_children, ..
|
||||
}) = self.format()
|
||||
{
|
||||
write!(f, "{before_children}")?;
|
||||
} else if space_before_children {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
write!(f, "{{")?;
|
||||
if children.leading.is_none() {
|
||||
if children.format().is_none() {
|
||||
writeln!(f)?;
|
||||
}
|
||||
children.stringify(f, indent + 4)?;
|
||||
if children.trailing.is_none() {
|
||||
if children.format().is_none() {
|
||||
write!(f, "{:indent$}", "", indent = indent)?;
|
||||
}
|
||||
write!(f, "}}")?;
|
||||
}
|
||||
if let Some(trailing) = &self.trailing {
|
||||
if let Some(KdlNodeFormat { trailing, .. }) = self.format() {
|
||||
write!(f, "{}", trailing)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Formatting details for [`KdlNode`].
|
||||
#[derive(Debug, Clone, Default, Hash, Eq, PartialEq)]
|
||||
pub struct KdlNodeFormat {
|
||||
/// Whitespace and comments preceding the node itself.
|
||||
pub leading: String,
|
||||
/// Whitespace and comments between the opening `(` of a type annotation and the actual annotation name.
|
||||
pub before_ty_name: String,
|
||||
/// Whitespace and comments between the annotation name and the closing `)`.
|
||||
pub after_ty_name: String,
|
||||
/// Whitespace and comments after a node's type annotation.
|
||||
pub after_ty: String,
|
||||
/// Whitespace and comments preceding the node's children block.
|
||||
pub before_children: String,
|
||||
/// Whitespace and comments following the node itself, including the
|
||||
/// optional semicolon.
|
||||
pub trailing: String,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
|
@ -625,26 +641,38 @@ mod test {
|
|||
.parse()?;
|
||||
let mut right_node: KdlNode = "node param_name=103.0 { nested 1 2 3; }".parse()?;
|
||||
assert_ne!(left_node, right_node);
|
||||
left_node.clear_fmt_recursive();
|
||||
right_node.clear_fmt_recursive();
|
||||
assert_eq!(left_node, right_node);
|
||||
left_node.clear_format_recursive();
|
||||
right_node.clear_format_recursive();
|
||||
assert_eq!(left_node.to_string(), right_node.to_string());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing() -> miette::Result<()> {
|
||||
let node: KdlNode = "\n\t (\"ty\")\"node\" 0xDEADbeef;\n".parse()?;
|
||||
assert_eq!(node.leading(), Some("\n\t "));
|
||||
assert_eq!(node.trailing(), Some(";\n"));
|
||||
assert_eq!(node.ty(), Some(&"\"ty\"".parse()?));
|
||||
assert_eq!(node.name(), &"\"node\"".parse()?);
|
||||
assert_eq!(node.entry(0), Some(&"0xDEADbeef".parse()?));
|
||||
assert_eq!(node.entry(0), Some(&" 0xDEADbeef".parse()?));
|
||||
assert_eq!(
|
||||
node.format(),
|
||||
Some(&KdlNodeFormat {
|
||||
leading: "\n\t ".into(),
|
||||
trailing: ";\n".into(),
|
||||
before_ty_name: "".into(),
|
||||
after_ty_name: "".into(),
|
||||
after_ty: "".into(),
|
||||
before_children: "".into(),
|
||||
})
|
||||
);
|
||||
|
||||
r#"
|
||||
node "test" {
|
||||
link "blah" anything="self"
|
||||
}"#
|
||||
let node: KdlNode = r#"node test {
|
||||
link "blah" anything=self
|
||||
}
|
||||
"#
|
||||
.parse::<KdlNode>()?;
|
||||
assert_eq!(node.entry(0), Some(&" test".parse()?));
|
||||
assert_eq!(node.children().unwrap().nodes().len(), 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
14
src/query.rs
14
src/query.rs
|
|
@ -1,6 +1,6 @@
|
|||
use std::{collections::VecDeque, str::FromStr, sync::Arc};
|
||||
|
||||
use crate::{query_parser::KdlQueryParser, KdlDocument, KdlError, KdlNode, KdlValue};
|
||||
use crate::{query_parser::KdlQueryParser, KdlDiagnostic, KdlDocument, KdlNode, KdlValue};
|
||||
|
||||
/// A parsed KQL query. For details on the syntax, see the [KQL
|
||||
/// spec](https://github.com/kdl-org/kdl/blob/main/QUERY-SPEC.md).
|
||||
|
|
@ -8,7 +8,7 @@ use crate::{query_parser::KdlQueryParser, KdlDocument, KdlError, KdlNode, KdlVal
|
|||
pub struct KdlQuery(pub(crate) Vec<KdlQuerySelector>);
|
||||
|
||||
impl FromStr for KdlQuery {
|
||||
type Err = KdlError;
|
||||
type Err = KdlDiagnostic;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let parser = KdlQueryParser::new(s);
|
||||
|
|
@ -26,29 +26,29 @@ impl<'a> IntoKdlQuery for &'a String {}
|
|||
|
||||
#[doc(hidden)]
|
||||
pub trait IntoQuerySealed {
|
||||
fn into_query(self) -> Result<KdlQuery, KdlError>;
|
||||
fn into_query(self) -> Result<KdlQuery, KdlDiagnostic>;
|
||||
}
|
||||
|
||||
impl IntoQuerySealed for KdlQuery {
|
||||
fn into_query(self) -> Result<KdlQuery, KdlError> {
|
||||
fn into_query(self) -> Result<KdlQuery, KdlDiagnostic> {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoQuerySealed for &str {
|
||||
fn into_query(self) -> Result<KdlQuery, KdlError> {
|
||||
fn into_query(self) -> Result<KdlQuery, KdlDiagnostic> {
|
||||
self.parse()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoQuerySealed for String {
|
||||
fn into_query(self) -> Result<KdlQuery, KdlError> {
|
||||
fn into_query(self) -> Result<KdlQuery, KdlDiagnostic> {
|
||||
self.parse()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoQuerySealed for &String {
|
||||
fn into_query(self) -> Result<KdlQuery, KdlError> {
|
||||
fn into_query(self) -> Result<KdlQuery, KdlDiagnostic> {
|
||||
self.parse()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
use crate::nom_compat::many0;
|
||||
use crate::parser::{value, KdlParser};
|
||||
use crate::query::{
|
||||
KdlQuery, KdlQueryAttributeOp, KdlQueryMatcher, KdlQueryMatcherAccessor,
|
||||
KdlQueryMatcherDetails, KdlQuerySelector, KdlQuerySelectorSegment, KdlSegmentCombinator,
|
||||
};
|
||||
use crate::{KdlError, KdlErrorKind, KdlParseError, KdlValue};
|
||||
use crate::v1_parser::{value, KdlParser};
|
||||
use crate::{KdlDiagnostic, KdlErrorKind, KdlParseError, KdlValue};
|
||||
use miette::Severity;
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::combinator::{all_consuming, cut, map, opt, recognize};
|
||||
|
|
@ -20,7 +21,7 @@ impl<'a> KdlQueryParser<'a> {
|
|||
Self(KdlParser::new(full_input))
|
||||
}
|
||||
|
||||
pub(crate) fn parse<T, P>(&self, parser: P) -> Result<T, KdlError>
|
||||
pub(crate) fn parse<T, P>(&self, parser: P) -> Result<T, KdlDiagnostic>
|
||||
where
|
||||
P: Parser<&'a str, T, KdlParseError<&'a str>>,
|
||||
{
|
||||
|
|
@ -29,7 +30,7 @@ impl<'a> KdlQueryParser<'a> {
|
|||
.map(|(_, arg)| arg)
|
||||
.map_err(|e| {
|
||||
let span_substr = &e.input[..e.len];
|
||||
KdlError {
|
||||
KdlDiagnostic {
|
||||
input: self.0.full_input.into(),
|
||||
span: self.0.span_from_substr(span_substr),
|
||||
help: if let Some(help) = e.help {
|
||||
|
|
@ -47,6 +48,7 @@ impl<'a> KdlQueryParser<'a> {
|
|||
} else {
|
||||
KdlErrorKind::Context("a valid KQL query")
|
||||
},
|
||||
severity: Severity::Error,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -176,7 +178,7 @@ fn node_matchers<'a: 'b, 'b>(
|
|||
}
|
||||
}
|
||||
|
||||
let (input, node) = opt(crate::parser::identifier(&kdl_parser.0))(input)?;
|
||||
let (input, node) = opt(crate::v1_parser::identifier(&kdl_parser.0))(input)?;
|
||||
if let Some(node) = node {
|
||||
matchers.push(KdlQueryMatcherDetails {
|
||||
op: KdlQueryAttributeOp::Equal,
|
||||
|
|
@ -230,7 +232,7 @@ fn node_matchers<'a: 'b, 'b>(
|
|||
}
|
||||
|
||||
// Check for trailing node name matcher.
|
||||
let (end, ident) = opt(crate::parser::identifier(&kdl_parser.0))(input)?;
|
||||
let (end, ident) = opt(crate::v1_parser::identifier(&kdl_parser.0))(input)?;
|
||||
if ident.is_some() {
|
||||
return Err(nom::Err::Error(KdlParseError {
|
||||
input: start,
|
||||
|
|
@ -275,7 +277,7 @@ fn attribute_matcher_inner<'a: 'b, 'b>(
|
|||
let (input, _) = whitespace(input)?;
|
||||
if let Some(op) = op {
|
||||
let prev = input;
|
||||
let (input, val) = opt(crate::parser::value)(input)?;
|
||||
let (input, val) = opt(crate::v1_parser::value)(input)?;
|
||||
// Make sure it's a syntax error to try and use string
|
||||
// operators with non-string arguments.
|
||||
if let Some((_, value)) = val {
|
||||
|
|
@ -370,7 +372,7 @@ fn annotation_matcher<'a: 'b, 'b>(
|
|||
let start = input;
|
||||
let (input, _) = tag("(")(input)?;
|
||||
let (input, _) = whitespace(input)?;
|
||||
let (input, ty) = opt(crate::parser::identifier(&kdl_parser.0))(input)?;
|
||||
let (input, ty) = opt(crate::v1_parser::identifier(&kdl_parser.0))(input)?;
|
||||
let (input, _) = context("closing ')' for type annotation", cut(tag(")")))(input)
|
||||
.map_err(|e| set_details(e, start, Some("annotation"), Some("annotations can only be KDL identifiers (including string identifiers), and can't have any space inside the parentheses.")))?;
|
||||
Ok((
|
||||
|
|
@ -464,7 +466,7 @@ fn prop_name_accessor<'a: 'b, 'b>(
|
|||
) -> impl Fn(&'a str) -> IResult<&'a str, KdlQueryMatcherAccessor, KdlParseError<&'a str>> + 'b {
|
||||
move |input| {
|
||||
let start = input;
|
||||
let (input, prop_name) = crate::parser::identifier(&kdl_parser.0)(input)?;
|
||||
let (input, prop_name) = crate::v1_parser::identifier(&kdl_parser.0)(input)?;
|
||||
let (_, paren) = opt(preceded(whitespace, tag("(")))(input)?;
|
||||
if paren.is_some() {
|
||||
Err(nom::Err::Error(KdlParseError {
|
||||
|
|
@ -512,7 +514,7 @@ fn parenthesized_prop<'a: 'b, 'b>(
|
|||
) -> impl Fn(&'a str) -> IResult<&'a str, String, KdlParseError<&'a str>> + 'b {
|
||||
move |input| {
|
||||
let (input, _) = tag("(")(input)?;
|
||||
let (input, prop) = crate::parser::identifier(&kdl_parser.0)(input)?;
|
||||
let (input, prop) = crate::v1_parser::identifier(&kdl_parser.0)(input)?;
|
||||
let (input, _) = tag(")")(input)?;
|
||||
Ok((input, prop.value().to_owned()))
|
||||
}
|
||||
|
|
@ -547,7 +549,7 @@ fn bad_accessor<'a: 'b, 'b>(
|
|||
}
|
||||
|
||||
let (input, ident) = opt(terminated(
|
||||
crate::parser::identifier(&kdl_parser.0),
|
||||
crate::v1_parser::identifier(&kdl_parser.0),
|
||||
preceded(
|
||||
whitespace,
|
||||
terminated(tag("("), opt(preceded(whitespace, tag(")")))),
|
||||
|
|
@ -585,7 +587,7 @@ fn bad_accessor<'a: 'b, 'b>(
|
|||
|
||||
fn whitespace(input: &str) -> IResult<&str, &str, KdlParseError<&str>> {
|
||||
recognize(many0(alt((
|
||||
crate::parser::unicode_space,
|
||||
crate::parser::newline,
|
||||
crate::v1_parser::unicode_space,
|
||||
crate::v1_parser::newline,
|
||||
))))(input)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
use std::ops::RangeTo;
|
||||
|
||||
use crate::nom_compat::{many0, many1, many_till};
|
||||
use miette::SourceSpan;
|
||||
use miette::{Severity, SourceSpan};
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::{tag, take_until, take_while, take_while_m_n};
|
||||
use nom::character::complete::{anychar, char, none_of, one_of};
|
||||
|
|
@ -16,7 +16,8 @@ use nom::sequence::{delimited, preceded, terminated, tuple};
|
|||
use nom::{Finish, IResult, Offset, Parser, Slice};
|
||||
|
||||
use crate::{
|
||||
KdlDocument, KdlEntry, KdlError, KdlErrorKind, KdlIdentifier, KdlNode, KdlParseError, KdlValue,
|
||||
KdlDiagnostic, KdlDocument, KdlEntry, KdlErrorKind, KdlIdentifier, KdlNode, KdlParseError,
|
||||
KdlValue,
|
||||
};
|
||||
|
||||
/// The parser for the entire input.
|
||||
|
|
@ -36,7 +37,7 @@ impl<'a> KdlParser<'a> {
|
|||
Self { full_input }
|
||||
}
|
||||
|
||||
pub(crate) fn parse<T, P>(&self, parser: P) -> Result<T, KdlError>
|
||||
pub(crate) fn parse<T, P>(&self, parser: P) -> Result<T, KdlDiagnostic>
|
||||
where
|
||||
P: Parser<&'a str, T, KdlParseError<&'a str>>,
|
||||
{
|
||||
|
|
@ -45,7 +46,7 @@ impl<'a> KdlParser<'a> {
|
|||
.map(|(_, arg)| arg)
|
||||
.map_err(|e| {
|
||||
let span_substr = &e.input[..e.len];
|
||||
KdlError {
|
||||
KdlDiagnostic {
|
||||
input: self.full_input.into(),
|
||||
span: self.span_from_substr(span_substr),
|
||||
help: e.help,
|
||||
|
|
@ -57,6 +58,7 @@ impl<'a> KdlParser<'a> {
|
|||
} else {
|
||||
KdlErrorKind::Other
|
||||
},
|
||||
severity: Severity::Error,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -840,7 +842,7 @@ mod node_tests {
|
|||
#[test]
|
||||
fn basic() {
|
||||
let input = "foo 1 \"bar\"=false";
|
||||
let kdl_parser = crate::parser::KdlParser::new(input);
|
||||
let kdl_parser = crate::v1_parser::KdlParser::new(input);
|
||||
match node(&kdl_parser)(input) {
|
||||
Ok(("", parsed)) => {
|
||||
let mut ident = KdlIdentifier::from("foo");
|
||||
|
|
@ -873,7 +875,7 @@ mod node_tests {
|
|||
#[test]
|
||||
fn errant_ident1() {
|
||||
let input = "struct Vec { }";
|
||||
let kdl_parser = crate::parser::KdlParser::new(input);
|
||||
let kdl_parser = crate::v1_parser::KdlParser::new(input);
|
||||
let res = kdl_parser.parse(document(&kdl_parser));
|
||||
let e = res.unwrap_err();
|
||||
check_span("Vec", &e.span, &input);
|
||||
|
|
@ -885,7 +887,7 @@ mod node_tests {
|
|||
let input = r##"
|
||||
some_node
|
||||
bad evil"##;
|
||||
let kdl_parser = crate::parser::KdlParser::new(input);
|
||||
let kdl_parser = crate::v1_parser::KdlParser::new(input);
|
||||
let res = kdl_parser.parse(document(&kdl_parser));
|
||||
let e = res.unwrap_err();
|
||||
check_span("evil", &e.span, &input);
|
||||
|
|
@ -895,7 +897,7 @@ mod node_tests {
|
|||
#[test]
|
||||
fn errant_ident3() {
|
||||
let input = r##"node "ok" wait "fine""##;
|
||||
let kdl_parser = crate::parser::KdlParser::new(input);
|
||||
let kdl_parser = crate::v1_parser::KdlParser::new(input);
|
||||
let res = kdl_parser.parse(document(&kdl_parser));
|
||||
let e = res.unwrap_err();
|
||||
check_span("wait", &e.span, &input);
|
||||
|
|
@ -905,7 +907,7 @@ mod node_tests {
|
|||
#[test]
|
||||
fn errant_ident4() {
|
||||
let input = r##"node x="ok" oof z="5"##;
|
||||
let kdl_parser = crate::parser::KdlParser::new(input);
|
||||
let kdl_parser = crate::v1_parser::KdlParser::new(input);
|
||||
let res = kdl_parser.parse(document(&kdl_parser));
|
||||
let e = res.unwrap_err();
|
||||
check_span("oof", &e.span, &input);
|
||||
|
|
@ -917,7 +919,7 @@ mod node_tests {
|
|||
// NOTE: this one is a different situation and doesn't provide as good help still!
|
||||
// But at least it's clear that the value is bad, which is ok!
|
||||
let input = r##"node x=bad"##;
|
||||
let kdl_parser = crate::parser::KdlParser::new(input);
|
||||
let kdl_parser = crate::v1_parser::KdlParser::new(input);
|
||||
let res = kdl_parser.parse(document(&kdl_parser));
|
||||
let e = res.unwrap_err();
|
||||
check_span("", &e.span, &input);
|
||||
|
|
@ -929,7 +931,7 @@ mod node_tests {
|
|||
// NOTE: this one is a different situation and doesn't provide as good help still!
|
||||
// But at least it's clear that the value is bad, which is ok!
|
||||
let input = r##"node (int)bad"##;
|
||||
let kdl_parser = crate::parser::KdlParser::new(input);
|
||||
let kdl_parser = crate::v1_parser::KdlParser::new(input);
|
||||
let res = kdl_parser.parse(document(&kdl_parser));
|
||||
let e = res.unwrap_err();
|
||||
check_span("", &e.span, &input);
|
||||
|
|
@ -952,7 +954,7 @@ mod whitespace_tests {
|
|||
use super::all_whitespace;
|
||||
|
||||
let input = " \t\n\r";
|
||||
let kdl_parser = crate::parser::KdlParser::new(input);
|
||||
let kdl_parser = crate::v1_parser::KdlParser::new(input);
|
||||
assert_eq!(all_whitespace(&kdl_parser)(input), Ok(("", " \t\n\r")));
|
||||
}
|
||||
}
|
||||
|
|
@ -964,18 +966,18 @@ mod comment_tests {
|
|||
#[test]
|
||||
fn single_line() {
|
||||
let input = "// Hello world";
|
||||
let kdl_parser = crate::parser::KdlParser::new(input);
|
||||
let kdl_parser = crate::v1_parser::KdlParser::new(input);
|
||||
assert_eq!(comment(&kdl_parser)(input), Ok(("", "// Hello world")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_line() {
|
||||
let input = "/* Hello world */";
|
||||
let kdl_parser = crate::parser::KdlParser::new(input);
|
||||
let kdl_parser = crate::v1_parser::KdlParser::new(input);
|
||||
assert_eq!(comment(&kdl_parser)(input), Ok(("", "/* Hello world */")));
|
||||
|
||||
let input = "/* Hello /* world */ blah */";
|
||||
let kdl_parser = crate::parser::KdlParser::new(input);
|
||||
let kdl_parser = crate::v1_parser::KdlParser::new(input);
|
||||
assert_eq!(
|
||||
comment(&kdl_parser)(input),
|
||||
Ok(("", "/* Hello /* world */ blah */"))
|
||||
|
|
@ -985,7 +987,7 @@ mod comment_tests {
|
|||
#[test]
|
||||
fn slashdash() {
|
||||
let input = "/-foo 1 2";
|
||||
let kdl_parser = crate::parser::KdlParser::new(input);
|
||||
let kdl_parser = crate::v1_parser::KdlParser::new(input);
|
||||
assert_eq!(comment(&kdl_parser)(input), Ok(("", "/-foo 1 2")));
|
||||
}
|
||||
|
||||
|
|
@ -1071,7 +1073,7 @@ mod value_tests {
|
|||
))
|
||||
);
|
||||
let input = "node 0x0123_4567_89ab_cdef";
|
||||
let kdl_parser = crate::parser::KdlParser::new(input);
|
||||
let kdl_parser = crate::v1_parser::KdlParser::new(input);
|
||||
let (_, n) = node(&kdl_parser)(input).expect("failed to parse node");
|
||||
assert_eq!(&n[0], &KdlValue::Base16(0x0123456789abcdef));
|
||||
assert_eq!(
|
||||
File diff suppressed because it is too large
Load Diff
242
src/value.rs
242
src/value.rs
|
|
@ -3,36 +3,16 @@ use std::fmt::Display;
|
|||
/// A specific [KDL Value](https://github.com/kdl-org/kdl/blob/main/SPEC.md#value).
|
||||
#[derive(Debug, Clone, PartialOrd)]
|
||||
pub enum KdlValue {
|
||||
/// A [KDL Raw String](https://github.com/kdl-org/kdl/blob/main/SPEC.md#raw-string).
|
||||
RawString(String),
|
||||
|
||||
/// A [KDL String](https://github.com/kdl-org/kdl/blob/main/SPEC.md#string).
|
||||
String(String),
|
||||
|
||||
/// A [KDL
|
||||
/// Number](https://github.com/kdl-org/kdl/blob/main/SPEC.md#number) in
|
||||
/// binary form (e.g. `0b010101`).
|
||||
Base2(i64),
|
||||
/// A non-float [KDL
|
||||
/// Number](https://github.com/kdl-org/kdl/blob/main/SPEC.md#number)
|
||||
Integer(i64),
|
||||
|
||||
/// A [KDL
|
||||
/// Number](https://github.com/kdl-org/kdl/blob/main/SPEC.md#number) in
|
||||
/// octal form (e.g. `0o12345670`).
|
||||
Base8(i64),
|
||||
|
||||
/// A [KDL
|
||||
/// Number](https://github.com/kdl-org/kdl/blob/main/SPEC.md#number) in
|
||||
/// decimal form (e.g. `1234567890`).
|
||||
Base10(i64),
|
||||
|
||||
/// A [KDL
|
||||
/// Number](https://github.com/kdl-org/kdl/blob/main/SPEC.md#number) in
|
||||
/// decimal form (e.g. `1234567890.123`), interpreted as a Rust f64.
|
||||
Base10Float(f64),
|
||||
|
||||
/// A [KDL
|
||||
/// Number](https://github.com/kdl-org/kdl/blob/main/SPEC.md#number) in
|
||||
/// hexadecimal form (e.g. `1234567890abcdef`).
|
||||
Base16(i64),
|
||||
/// A floating point [KDL
|
||||
/// Number](https://github.com/kdl-org/kdl/blob/main/SPEC.md#number)
|
||||
Float(f64),
|
||||
|
||||
/// A [KDL Boolean](https://github.com/kdl-org/kdl/blob/main/SPEC.md#boolean).
|
||||
Bool(bool),
|
||||
|
|
@ -46,12 +26,9 @@ impl Eq for KdlValue {}
|
|||
impl PartialEq for KdlValue {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::RawString(l0), Self::RawString(r0)) => l0 == r0,
|
||||
(Self::String(l0), Self::String(r0)) => l0 == r0,
|
||||
(Self::Base2(l0), Self::Base2(r0)) => l0 == r0,
|
||||
(Self::Base8(l0), Self::Base8(r0)) => l0 == r0,
|
||||
(Self::Base10(l0), Self::Base10(r0)) => l0 == r0,
|
||||
(Self::Base10Float(l0), Self::Base10Float(r0)) => {
|
||||
(Self::Integer(l0), Self::Integer(r0)) => l0 == r0,
|
||||
(Self::Float(l0), Self::Float(r0)) => {
|
||||
let l0 = if l0 == &f64::INFINITY {
|
||||
f64::MAX
|
||||
} else if l0 == &f64::NEG_INFINITY {
|
||||
|
|
@ -74,7 +51,6 @@ impl PartialEq for KdlValue {
|
|||
};
|
||||
l0 == r0
|
||||
}
|
||||
(Self::Base16(l0), Self::Base16(r0)) => l0 == r0,
|
||||
(Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
|
||||
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
|
||||
}
|
||||
|
|
@ -86,12 +62,9 @@ impl PartialEq for KdlValue {
|
|||
impl std::hash::Hash for KdlValue {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
match self {
|
||||
KdlValue::RawString(val) => val.hash(state),
|
||||
KdlValue::String(val) => val.hash(state),
|
||||
KdlValue::Base2(val) => val.hash(state),
|
||||
KdlValue::Base8(val) => val.hash(state),
|
||||
KdlValue::Base10(val) => val.hash(state),
|
||||
KdlValue::Base10Float(val) => {
|
||||
KdlValue::Integer(val) => val.hash(state),
|
||||
KdlValue::Float(val) => {
|
||||
let val = if val == &f64::INFINITY {
|
||||
f64::MAX
|
||||
} else if val == &f64::NEG_INFINITY {
|
||||
|
|
@ -106,7 +79,6 @@ impl std::hash::Hash for KdlValue {
|
|||
(val.trunc() as i64).hash(state);
|
||||
(val.fract() as i64).hash(state);
|
||||
}
|
||||
KdlValue::Base16(val) => val.hash(state),
|
||||
KdlValue::Bool(val) => val.hash(state),
|
||||
KdlValue::Null => core::mem::discriminant(self).hash(state),
|
||||
}
|
||||
|
|
@ -114,58 +86,19 @@ impl std::hash::Hash for KdlValue {
|
|||
}
|
||||
|
||||
impl KdlValue {
|
||||
/// Returns `true` if the value is a [`KdlValue::RawString`].
|
||||
pub fn is_raw_string(&self) -> bool {
|
||||
matches!(self, Self::RawString(..))
|
||||
}
|
||||
|
||||
/// Returns `true` if the value is a [`KdlValue::String`].
|
||||
pub fn is_string(&self) -> bool {
|
||||
matches!(self, Self::String(..))
|
||||
}
|
||||
|
||||
/// Returns `true` if the value is a [`KdlValue::String`] or [`KdlValue::RawString`].
|
||||
pub fn is_string_value(&self) -> bool {
|
||||
matches!(self, Self::String(..) | Self::RawString(..))
|
||||
/// Returns `true` if the value is a [`KdlValue::Integer`].
|
||||
pub fn is_integer(&self) -> bool {
|
||||
matches!(self, Self::Integer(..))
|
||||
}
|
||||
|
||||
/// Returns `true` if the value is a [`KdlValue::Base2`].
|
||||
pub fn is_base2(&self) -> bool {
|
||||
matches!(self, Self::Base2(..))
|
||||
}
|
||||
|
||||
/// Returns `true` if the value is a [`KdlValue::Base8`].
|
||||
pub fn is_base8(&self) -> bool {
|
||||
matches!(self, Self::Base8(..))
|
||||
}
|
||||
|
||||
/// Returns `true` if the value is a [`KdlValue::Base10`].
|
||||
pub fn is_base10(&self) -> bool {
|
||||
matches!(self, Self::Base10(..))
|
||||
}
|
||||
|
||||
/// Returns `true` if the value is a [`KdlValue::Base16`].
|
||||
pub fn is_base16(&self) -> bool {
|
||||
matches!(self, Self::Base16(..))
|
||||
}
|
||||
|
||||
/// Returns `true` if the value is a [`KdlValue::Base2`],
|
||||
/// [`KdlValue::Base8`], [`KdlValue::Base10`], or [`KdlValue::Base16`].
|
||||
pub fn is_i64_value(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::Base2(..) | Self::Base8(..) | Self::Base10(..) | Self::Base16(..)
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns `true` if the value is a [`KdlValue::Base10Float`].
|
||||
pub fn is_base10_float(&self) -> bool {
|
||||
matches!(self, Self::Base10Float(..))
|
||||
}
|
||||
|
||||
/// Returns `true` if the value is a [`KdlValue::Base10Float`].
|
||||
pub fn is_float_value(&self) -> bool {
|
||||
matches!(self, Self::Base10Float(..))
|
||||
/// Returns `true` if the value is a [`KdlValue::Float`].
|
||||
pub fn is_float(&self) -> bool {
|
||||
matches!(self, Self::Float(..))
|
||||
}
|
||||
|
||||
/// Returns `true` if the value is a [`KdlValue::Bool`].
|
||||
|
|
@ -178,34 +111,32 @@ impl KdlValue {
|
|||
matches!(self, Self::Null)
|
||||
}
|
||||
|
||||
/// Returns `Some(&str)` if the `KdlValue` is a [`KdlValue::RawString`] or a
|
||||
/// [`KdlValue::String`], otherwise returns `None`.
|
||||
/// Returns `Some(&str)` if the `KdlValue` is a [`KdlValue::String`],
|
||||
/// otherwise returns `None`.
|
||||
pub fn as_string(&self) -> Option<&str> {
|
||||
use KdlValue::*;
|
||||
match self {
|
||||
String(s) | RawString(s) => Some(s),
|
||||
String(s) => Some(s),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Some(i64)` if the `KdlValue` is a [`KdlValue::Base2`],
|
||||
/// [`KdlValue::Base8`], [`KdlValue::Base10`], or [`KdlValue::Base16`],
|
||||
/// Returns `Some(i64)` if the `KdlValue` is a [`KdlValue::Integer`],
|
||||
/// otherwise returns `None`.
|
||||
pub fn as_i64(&self) -> Option<i64> {
|
||||
pub fn as_integer(&self) -> Option<i64> {
|
||||
use KdlValue::*;
|
||||
match self {
|
||||
Base2(i) | Base8(i) | Base10(i) | Base16(i) => Some(*i),
|
||||
Integer(i) => Some(*i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Some(f64)` if the `KdlValue` is a [`KdlValue::Base10Float`],
|
||||
/// Returns `Some(f64)` if the `KdlValue` is a [`KdlValue::Float`],
|
||||
/// otherwise returns `None`.
|
||||
pub fn as_f64(&self) -> Option<f64> {
|
||||
if let Self::Base10Float(v) = self {
|
||||
Some(*v)
|
||||
} else {
|
||||
None
|
||||
pub fn as_float(&self) -> Option<f64> {
|
||||
match self {
|
||||
Self::Float(i) => Some(*i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -222,79 +153,81 @@ impl KdlValue {
|
|||
impl Display for KdlValue {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::RawString(_) => self.write_raw_string(f),
|
||||
Self::String(_) => self.write_string(f),
|
||||
Self::Base2(value) => write!(f, "0b{:b}", value),
|
||||
Self::Base8(value) => write!(f, "0o{:o}", value),
|
||||
Self::Base10(value) => write!(f, "{:?}", value),
|
||||
Self::Base10Float(value) => write!(
|
||||
Self::Integer(value) => write!(f, "{:?}", value),
|
||||
Self::Float(value) => write!(
|
||||
f,
|
||||
"{:?}",
|
||||
"{}",
|
||||
if value == &f64::INFINITY {
|
||||
f64::MAX
|
||||
"#inf".into()
|
||||
} else if value == &f64::NEG_INFINITY {
|
||||
-f64::MAX
|
||||
"#-inf".into()
|
||||
} else if value.is_nan() {
|
||||
0.0
|
||||
"#nan".into()
|
||||
} else {
|
||||
*value
|
||||
format!("{:?}", *value)
|
||||
}
|
||||
),
|
||||
Self::Base16(value) => write!(f, "0x{:x}", value),
|
||||
Self::Bool(value) => write!(f, "{}", value),
|
||||
Self::Null => write!(f, "null"),
|
||||
Self::Bool(value) => write!(f, "#{}", value),
|
||||
Self::Null => write!(f, "#null"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_plain_ident(ident: &str) -> bool {
|
||||
let ident_bytes = ident.as_bytes();
|
||||
ident
|
||||
.find(crate::v2_parser::is_disallowed_ident_char)
|
||||
.is_none()
|
||||
&& ident_bytes.first().map(|c| c.is_ascii_digit()) != Some(true)
|
||||
&& !(ident
|
||||
.chars()
|
||||
.next()
|
||||
.map(|c| c == '.' || c == '-' || c == '+')
|
||||
== Some(true)
|
||||
&& ident_bytes.get(1).map(|c| c.is_ascii_digit()) == Some(true))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn plain_ident_test() {
|
||||
assert!(is_plain_ident("foo123,bar"));
|
||||
assert!(is_plain_ident("foo123~!@$%^&*.:'|?+<>,"));
|
||||
}
|
||||
|
||||
impl KdlValue {
|
||||
fn write_string(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let string = self.as_string().unwrap();
|
||||
write!(f, "\"")?;
|
||||
for char in string.chars() {
|
||||
match char {
|
||||
'\\' | '"' => write!(f, "\\{}", char)?,
|
||||
'\n' => write!(f, "\\n")?,
|
||||
'\r' => write!(f, "\\r")?,
|
||||
'\t' => write!(f, "\\t")?,
|
||||
'\u{08}' => write!(f, "\\b")?,
|
||||
'\u{0C}' => write!(f, "\\f")?,
|
||||
_ => write!(f, "{}", char)?,
|
||||
if !string.is_empty() && is_plain_ident(string) {
|
||||
write!(f, "{string}")?;
|
||||
} else {
|
||||
write!(f, "\"")?;
|
||||
for char in string.chars() {
|
||||
match char {
|
||||
'\\' | '"' => write!(f, "\\{}", char)?,
|
||||
'\n' => write!(f, "\\n")?,
|
||||
'\r' => write!(f, "\\r")?,
|
||||
'\t' => write!(f, "\\t")?,
|
||||
'\u{08}' => write!(f, "\\b")?,
|
||||
'\u{0C}' => write!(f, "\\f")?,
|
||||
_ => write!(f, "{}", char)?,
|
||||
}
|
||||
}
|
||||
write!(f, "\"")?;
|
||||
}
|
||||
write!(f, "\"")?;
|
||||
Ok(())
|
||||
}
|
||||
fn write_raw_string(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let raw = self.as_string().unwrap();
|
||||
let mut consecutive = 0usize;
|
||||
let mut maxhash = 0usize;
|
||||
for char in raw.chars() {
|
||||
if char == '#' {
|
||||
consecutive += 1;
|
||||
} else if char == '"' {
|
||||
maxhash = maxhash.max(consecutive + 1);
|
||||
} else {
|
||||
consecutive = 0;
|
||||
}
|
||||
}
|
||||
write!(f, "r")?;
|
||||
write!(f, "{}", "#".repeat(maxhash))?;
|
||||
write!(f, "\"{}\"", raw)?;
|
||||
write!(f, "{}", "#".repeat(maxhash))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for KdlValue {
|
||||
fn from(value: i64) -> Self {
|
||||
KdlValue::Base10(value)
|
||||
KdlValue::Integer(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for KdlValue {
|
||||
fn from(value: f64) -> Self {
|
||||
KdlValue::Base10Float(value)
|
||||
KdlValue::Float(value)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -334,34 +267,19 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn formatting() {
|
||||
let raw = KdlValue::RawString(r###"r##"foor#"bar"#baz"##"###.into());
|
||||
assert_eq!(
|
||||
format!("{}", raw),
|
||||
r####"r###"r##"foor#"bar"#baz"##"###"####
|
||||
);
|
||||
|
||||
let string = KdlValue::String("foo\n".into());
|
||||
assert_eq!(format!("{}", string), r#""foo\n""#);
|
||||
|
||||
let base2 = KdlValue::Base2(0b1010_1010);
|
||||
assert_eq!(format!("{}", base2), "0b10101010");
|
||||
let integer = KdlValue::Integer(1234567890);
|
||||
assert_eq!(format!("{}", integer), "1234567890");
|
||||
|
||||
let base8 = KdlValue::Base8(0o12345670);
|
||||
assert_eq!(format!("{}", base8), "0o12345670");
|
||||
|
||||
let base10 = KdlValue::Base10(1234567890);
|
||||
assert_eq!(format!("{}", base10), "1234567890");
|
||||
|
||||
let base10float = KdlValue::Base10Float(1234567890.12345);
|
||||
assert_eq!(format!("{}", base10float), "1234567890.12345");
|
||||
|
||||
let base16 = KdlValue::Base16(0x1234567890ABCDEF);
|
||||
assert_eq!(format!("{}", base16), "0x1234567890abcdef");
|
||||
let float = KdlValue::Float(1234567890.12345);
|
||||
assert_eq!(format!("{}", float), "1234567890.12345");
|
||||
|
||||
let boolean = KdlValue::Bool(true);
|
||||
assert_eq!(format!("{}", boolean), "true");
|
||||
assert_eq!(format!("{}", boolean), "#true");
|
||||
|
||||
let null = KdlValue::Null;
|
||||
assert_eq!(format!("{}", null), "null");
|
||||
assert_eq!(format!("{}", null), "#null");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,42 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use kdl::{KdlDocument, KdlError, KdlIdentifier, KdlValue};
|
||||
use miette::IntoDiagnostic;
|
||||
use kdl::{KdlDocument, KdlIdentifier, KdlParseFailure, KdlValue};
|
||||
use miette::{Diagnostic, IntoDiagnostic};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
#[error("Compliance test suite failed {} of {total_checks}.", diagnostics.len())]
|
||||
struct ComplianceSuiteFailure {
|
||||
total_checks: usize,
|
||||
#[related]
|
||||
diagnostics: Vec<ComplianceDiagnostic>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
enum ComplianceDiagnostic {
|
||||
#[error("{}", PathBuf::from(.0.file_name().unwrap()).display())]
|
||||
#[diagnostic(code(kdl::compliance::parse_failure))]
|
||||
KdlParseFailure(
|
||||
PathBuf,
|
||||
#[source]
|
||||
#[diagnostic_source]
|
||||
KdlParseFailure,
|
||||
),
|
||||
|
||||
#[error("{}:\nExpected:\n{expected}\nActual:\n{actual}", PathBuf::from(file.file_name().unwrap()).display())]
|
||||
#[diagnostic(code(kdl::compliance::expectation_mismatch))]
|
||||
ExpectationMismatch {
|
||||
file: PathBuf,
|
||||
original: String,
|
||||
expected: String,
|
||||
actual: String,
|
||||
},
|
||||
|
||||
#[error(transparent)]
|
||||
#[diagnostic(code(kdl::compliance::io_error))]
|
||||
IoError(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spec_compliance() -> miette::Result<()> {
|
||||
|
|
@ -13,21 +47,37 @@ fn spec_compliance() -> miette::Result<()> {
|
|||
.join("tests")
|
||||
.join("test_cases")
|
||||
.join("input");
|
||||
for test_name in fs::read_dir(&input).into_diagnostic()? {
|
||||
let mut failures = Vec::new();
|
||||
let mut count = 0usize;
|
||||
for test_name in fs::read_dir(input).into_diagnostic()? {
|
||||
let test_path = test_name.into_diagnostic()?.path();
|
||||
println!(
|
||||
"parsing {}:",
|
||||
PathBuf::from(test_path.file_name().unwrap()).display()
|
||||
);
|
||||
let src = normalize_line_endings(fs::read_to_string(&test_path).into_diagnostic()?);
|
||||
println!("src: {}", src);
|
||||
let res: Result<KdlDocument, KdlError> = src.parse();
|
||||
validate_res(res, &test_path)?;
|
||||
let res = src.parse();
|
||||
if let Err(e) = validate_res(res, &test_path, &src) {
|
||||
failures.push(e);
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
if failures.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
let mut output = String::new();
|
||||
for failure in &failures {
|
||||
output.push_str(format!("\n{failure}").as_str());
|
||||
}
|
||||
Err(ComplianceSuiteFailure {
|
||||
total_checks: count,
|
||||
diagnostics: failures,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_res(res: Result<KdlDocument, KdlError>, path: &Path) -> miette::Result<()> {
|
||||
fn validate_res(
|
||||
res: Result<KdlDocument, KdlParseFailure>,
|
||||
path: &Path,
|
||||
src: &String,
|
||||
) -> Result<(), ComplianceDiagnostic> {
|
||||
let file_name = path.file_name().unwrap();
|
||||
let expected_dir = path
|
||||
.parent()
|
||||
|
|
@ -38,20 +88,24 @@ fn validate_res(res: Result<KdlDocument, KdlError>, path: &Path) -> miette::Resu
|
|||
let expected_path = expected_dir.join(file_name);
|
||||
let underscored = expected_dir.join(format!("_{}", PathBuf::from(file_name).display()));
|
||||
if expected_path.exists() {
|
||||
let doc = res?;
|
||||
let expected =
|
||||
normalize_line_endings(fs::read_to_string(&expected_path).into_diagnostic()?);
|
||||
println!("expected: {}", expected);
|
||||
let stringified = stringify_to_expected(doc);
|
||||
println!("stringified: {}", stringified);
|
||||
assert_eq!(stringified, expected);
|
||||
let doc = res.map_err(|e| ComplianceDiagnostic::KdlParseFailure(path.into(), e))?;
|
||||
let expected = normalize_line_endings(fs::read_to_string(&expected_path)?);
|
||||
let actual = stringify_to_expected(doc);
|
||||
if actual != expected {
|
||||
return Err(ComplianceDiagnostic::ExpectationMismatch {
|
||||
file: path.into(),
|
||||
original: src.clone(),
|
||||
expected,
|
||||
actual,
|
||||
});
|
||||
}
|
||||
} else if underscored.exists() {
|
||||
println!(
|
||||
eprintln!(
|
||||
"skipped reserialization for {}",
|
||||
PathBuf::from(file_name).display()
|
||||
);
|
||||
} else {
|
||||
assert!(res.is_err(), "parse should not have succeeded");
|
||||
// } else {
|
||||
// res.map_err(|e| ComplianceDiagnostic::KdlParseFailure(path.into(), e))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -61,27 +115,14 @@ fn normalize_line_endings(src: String) -> String {
|
|||
}
|
||||
|
||||
fn stringify_to_expected(mut doc: KdlDocument) -> String {
|
||||
doc.fmt_no_comments();
|
||||
normalize_numbers(&mut doc);
|
||||
doc.autoformat_no_comments();
|
||||
normalize_strings(&mut doc);
|
||||
normalize_identifiers(&mut doc);
|
||||
dedupe_props(&mut doc);
|
||||
remove_empty_children(&mut doc);
|
||||
doc.to_string()
|
||||
}
|
||||
|
||||
fn normalize_numbers(doc: &mut KdlDocument) {
|
||||
for node in doc.nodes_mut() {
|
||||
for entry in node.entries_mut() {
|
||||
if let Some(value) = entry.value().as_i64() {
|
||||
*entry.value_mut() = KdlValue::Base10(value);
|
||||
}
|
||||
}
|
||||
if let Some(children) = node.children_mut() {
|
||||
normalize_numbers(children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_strings(doc: &mut KdlDocument) {
|
||||
for node in doc.nodes_mut() {
|
||||
for entry in node.entries_mut() {
|
||||
|
|
@ -95,6 +136,22 @@ fn normalize_strings(doc: &mut KdlDocument) {
|
|||
}
|
||||
}
|
||||
|
||||
fn normalize_identifiers(doc: &mut KdlDocument) {
|
||||
for node in doc.nodes_mut() {
|
||||
node.name_mut().clear_format();
|
||||
for entry in node.entries_mut() {
|
||||
if entry.name().is_some() {
|
||||
if let Some(x) = entry.name_mut() {
|
||||
x.clear_format()
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(children) = node.children_mut() {
|
||||
normalize_identifiers(children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dedupe_props(doc: &mut KdlDocument) {
|
||||
for node in doc.nodes_mut() {
|
||||
let mut props = HashMap::<KdlIdentifier, Vec<usize>>::new();
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ fn build_and_format() {
|
|||
|
||||
let mut doc = KdlDocument::new();
|
||||
doc.nodes_mut().push(a);
|
||||
doc.fmt();
|
||||
doc.autoformat();
|
||||
let fmt = doc.to_string();
|
||||
println!("{}", fmt);
|
||||
assert_eq!(
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
node 1 1.0 10000000000.0 1e-10 1 7 2 "arg" "arg\\\\" true false null
|
||||
|
|
@ -1 +0,0 @@
|
|||
node prop=1.23E+1000
|
||||
|
|
@ -1 +0,0 @@
|
|||
node prop=1.23E-1000
|
||||
|
|
@ -1 +1 @@
|
|||
node "\"\\/\b\f\n\r\t"
|
||||
node "\"\\\b\f\n\r\t "
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
node "arg" prop="val" {
|
||||
node arg prop=val {
|
||||
inner_node
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
node "arg" arg="val"
|
||||
node arg arg=val
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
node a
|
||||
|
|
@ -1 +1 @@
|
|||
node (type)false
|
||||
node (type)#false
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
node (type)null
|
||||
node (type)#null
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
node (type)"str"
|
||||
node (type)str
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
node (type)"str"
|
||||
node (type)str
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
node (type)true
|
||||
node (type)#true
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
node (type)"arg"
|
||||
node (type)arg
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
😁 "happy!"
|
||||
😁 happy!
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
node .
|
||||
|
|
@ -0,0 +1 @@
|
|||
node +
|
||||
|
|
@ -0,0 +1 @@
|
|||
node +.
|
||||
|
|
@ -1 +1 @@
|
|||
node key=("")true
|
||||
node key=("")#true
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
node "arg"
|
||||
node arg
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
node "arg"
|
||||
node arg
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
node arg
|
||||
|
|
@ -1 +1 @@
|
|||
node false true
|
||||
node #false #true
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
node prop1=true prop2=false
|
||||
node prop1=#true prop2=#false
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
foo123<bar>foo weeee
|
||||
|
|
@ -0,0 +1 @@
|
|||
foo123,bar weeee
|
||||
|
|
@ -0,0 +1 @@
|
|||
node (type)10
|
||||
|
|
@ -0,0 +1 @@
|
|||
(type)node
|
||||
|
|
@ -0,0 +1 @@
|
|||
node key=(type)10
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
node1
|
||||
node2
|
||||
|
|
@ -0,0 +1 @@
|
|||
node (type)10
|
||||
|
|
@ -0,0 +1 @@
|
|||
(type)node
|
||||
|
|
@ -0,0 +1 @@
|
|||
node key=(type)10
|
||||
|
|
@ -1 +1 @@
|
|||
node "arg2"
|
||||
node arg2
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
node "arg"
|
||||
node arg
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
node "arg"
|
||||
node arg
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
node --
|
||||
|
|
@ -1 +1 @@
|
|||
node "😀"
|
||||
node 😀
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
node
|
||||
|
|
@ -1 +1 @@
|
|||
"" "arg"
|
||||
"" arg
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
node ""="empty"
|
||||
node ""=empty
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
node
|
||||
|
|
@ -0,0 +1 @@
|
|||
node "Hello\n\tWorld" "Hello\n\tWorld" "Hello\n\tWorld" "Hello\n\tWorld" "Hello\n\tWorld"
|
||||
|
|
@ -1 +1 @@
|
|||
node "arg"
|
||||
node arg
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
node "arg" "arg2\n"
|
||||
node arg arg2
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
floats #inf #-inf #nan
|
||||
|
|
@ -1 +0,0 @@
|
|||
node 1311768467294899695
|
||||
|
|
@ -1 +1 @@
|
|||
node 1311768467294899695
|
||||
node 188900966474565
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
another-node
|
||||
|
|
@ -1 +1 @@
|
|||
node "arg"
|
||||
node arg
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
node "arg1" "arg2"
|
||||
node arg1 arg2
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
node "hey\neveryone\nhow goes?"
|
||||
|
|
@ -0,0 +1 @@
|
|||
node " hey\n everyone\n how goes?"
|
||||
|
|
@ -1 +1 @@
|
|||
node " hey\neveryone\nhow goes?\n"
|
||||
node "hey\neveryone\nhow goes?"
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
node " hey\n everyone\n how goes?"
|
||||
|
|
@ -1 +1 @@
|
|||
node "arg"
|
||||
node arg
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
node "arg"
|
||||
node arg
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
node "arg"
|
||||
node arg
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
node "arg"
|
||||
node arg
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
node false
|
||||
node #false
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
node true
|
||||
node #true
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
node null
|
||||
node #null
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
node prop=null
|
||||
node prop=#null
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
node {
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
node 1 1.0 10000000000.0 1e-10 1 7 2 arg arg "arg\\" #true #false #null
|
||||
|
|
@ -1 +1 @@
|
|||
node key=(type)false
|
||||
node key=(type)#false
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
node key=(type)str
|
||||
|
|
@ -1 +1 @@
|
|||
node key=(type)null
|
||||
node key=(type)#null
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
node key=(type)"str"
|
||||
node key=(type)str
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
node key=(type)"str"
|
||||
node key=(type)str
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
node key=(type)true
|
||||
node key=(type)#true
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue