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 }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
rust: [1.56.0, stable]
|
rust: [1.70.0, stable]
|
||||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,11 @@ default = ["span"]
|
||||||
span = []
|
span = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
miette = "5.7.0"
|
miette = "7.2.0"
|
||||||
nom = "7.1.1"
|
nom = "7.1.1"
|
||||||
thiserror = "1.0.40"
|
thiserror = "1.0.40"
|
||||||
|
winnow = { version = "0.6.20", features = ["alloc", "unstable-recover"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
miette = { version = "5.7.0", features = ["fancy"] }
|
miette = { version = "7.2.0", features = ["fancy"] }
|
||||||
pretty_assertions = "1.3.0"
|
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
|
[`KdlDocument`], [`KdlNode`], [`KdlEntry`], and [`KdlIdentifier`] can all
|
||||||
be parsed and managed this way.
|
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
|
### Error Reporting
|
||||||
|
|
||||||
[`KdlError`] implements [`miette::Diagnostic`] and can be used to display
|
[`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
|
you [`KdlDocument::fmt`] in which case the original representation will be
|
||||||
thrown away and the actual value will be used when serializing.
|
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
|
### License
|
||||||
|
|
||||||
The code in this repository is covered by [the Apache-2.0
|
The code in this repository is covered by [the Apache-2.0
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
package {
|
package {
|
||||||
name "kdl"
|
name kdl
|
||||||
version "0.0.0"
|
version "0.0.0"
|
||||||
description "kat's document language"
|
description "The kdl document language"
|
||||||
authors "Kat Marchán <kzm@zkat.tech>"
|
authors "Kat Marchán <kzm@zkat.tech>"
|
||||||
license-file "LICENSE.md"
|
license-file LICENSE.md
|
||||||
edition "2018"
|
edition "2018"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,47 +1,52 @@
|
||||||
// This example is a GitHub Action if it used KDL syntax.
|
// This example is a GitHub Action if it used KDL syntax.
|
||||||
// See .github/workflows/ci.yml for the file this was based on.
|
// 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 {
|
env {
|
||||||
RUSTFLAGS "-Dwarnings"
|
RUSTFLAGS -Dwarnings
|
||||||
}
|
}
|
||||||
|
|
||||||
jobs {
|
jobs {
|
||||||
fmt_and_docs "Check fmt & build docs" {
|
fmt_and_docs "Check fmt & build docs" {
|
||||||
runs-on "ubuntu-latest"
|
runs-on ubuntu-latest
|
||||||
steps {
|
steps {
|
||||||
step uses="actions/checkout@v1"
|
step uses="actions/checkout@v1"
|
||||||
step "Install Rust" uses="actions-rs/toolchain@v1" {
|
step "Install Rust" uses="actions-rs/toolchain@v1" {
|
||||||
profile "minimal"
|
profile minimal
|
||||||
toolchain "stable"
|
toolchain stable
|
||||||
components "rustfmt"
|
components rustfmt
|
||||||
override true
|
override #true
|
||||||
}
|
}
|
||||||
step "rustfmt" run="cargo fmt --all -- --check"
|
step rustfmt { run cargo fmt --all -- --check }
|
||||||
step "docs" run="cargo doc --no-deps"
|
step docs { run cargo doc --no-deps }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
build_and_test "Build & Test" {
|
build_and_test "Build & Test" {
|
||||||
runs-on "${{ matrix.os }}"
|
runs-on "${{ matrix.os }}"
|
||||||
strategy {
|
strategy {
|
||||||
matrix {
|
matrix {
|
||||||
rust "1.46.0" "stable"
|
rust "1.46.0" stable
|
||||||
os "ubuntu-latest" "macOS-latest" "windows-latest"
|
os ubuntu-latest macOS-latest windows-latest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
steps {
|
steps {
|
||||||
step uses="actions/checkout@v1"
|
step uses="actions/checkout@v1"
|
||||||
step "Install Rust" uses="actions-rs/toolchain@v1" {
|
step "Install Rust" uses="actions-rs/toolchain@v1" {
|
||||||
profile "minimal"
|
profile minimal
|
||||||
toolchain "${{ matrix.rust }}"
|
toolchain "${{ matrix.rust }}"
|
||||||
components "clippy"
|
components clippy
|
||||||
override true
|
override #true
|
||||||
}
|
}
|
||||||
step "Clippy" run="cargo clippy --all -- -D warnings"
|
step Clippy { run cargo clippy --all -- -D warnings }
|
||||||
step "Run tests" run="cargo test --all --verbose"
|
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
|
// TODO(@zkat): Error stuff has changed, so this needs updating.
|
||||||
/// `fancy` feature or having `main()` return `miette::Result`
|
|
||||||
use kdl::KdlDocument;
|
|
||||||
use miette::Diagnostic;
|
|
||||||
use miette::SourceSpan;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
// /// Show how to build your own diagnostics, without having to use the
|
||||||
pub struct MyError {
|
// /// `fancy` feature or having `main()` return `miette::Result`
|
||||||
pub message: String,
|
// ///
|
||||||
}
|
// use kdl::KdlDocument;
|
||||||
|
// use miette::Diagnostic;
|
||||||
|
// use miette::SourceSpan;
|
||||||
|
|
||||||
fn parse(input: &str) -> Result<KdlDocument, MyError> {
|
// #[derive(Debug)]
|
||||||
let doc = input.parse::<KdlDocument>();
|
// pub struct MyError {
|
||||||
doc.map_err(|error| {
|
// pub message: String,
|
||||||
let source = error
|
// }
|
||||||
.source_code()
|
|
||||||
.expect("parse errors should have source code");
|
// fn parse(input: &str) -> Result<KdlDocument, MyError> {
|
||||||
let help = error.help.unwrap_or_default();
|
// let doc = input.parse::<KdlDocument>();
|
||||||
let span: SourceSpan = error.span;
|
// doc.map_err(|error| {
|
||||||
let contents = source
|
// let source = error
|
||||||
.read_span(&span, 0, 0)
|
// .source_code()
|
||||||
.expect("source should have span contents");
|
// .expect("parse errors should have source code");
|
||||||
// miette uses 0 based indexes, but humans prefer 1-based
|
// let help = error.help.unwrap_or_default();
|
||||||
let line = contents.line() + 1;
|
// let span: SourceSpan = error.span;
|
||||||
let column = contents.column() + 1;
|
// let contents = source
|
||||||
let message = format!(
|
// .read_span(&span, 0, 0)
|
||||||
"line {}, column {}: {}\n help: {}",
|
// .expect("source should have span contents");
|
||||||
line, column, error, help
|
// // miette uses 0 based indexes, but humans prefer 1-based
|
||||||
);
|
// let line = contents.line() + 1;
|
||||||
MyError { message }
|
// let column = contents.column() + 1;
|
||||||
})
|
// let message = format!(
|
||||||
}
|
// "line {}, column {}: {}\n help: {}",
|
||||||
|
// line, column, error, help
|
||||||
|
// );
|
||||||
|
// MyError { message }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let input = r#"
|
// let input = r#"
|
||||||
foo {
|
// foo {
|
||||||
bar {
|
// bar {
|
||||||
baz 1.
|
// baz 1.
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
"#;
|
// "#;
|
||||||
let err = parse(input).unwrap_err();
|
// let err = parse(input).unwrap_err();
|
||||||
eprintln!("{}", err.message);
|
// eprintln!("{}", err.message);
|
||||||
// Output:
|
// // Output:
|
||||||
// line 4, column 14: Expected valid value.
|
// // line 4, column 14: Expected valid value.
|
||||||
// help: Floating point numbers must be base 10, and have numbers after the decimal point.
|
// // 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);
|
let word_node = KdlNode::new(identifier);
|
||||||
word_nodes.push(word_node);
|
word_nodes.push(word_node);
|
||||||
word_nodes.sort_by(sort_by_name);
|
word_nodes.sort_by(sort_by_name);
|
||||||
words_section.fmt();
|
words_section.autoformat();
|
||||||
|
|
||||||
println!("{}", doc);
|
println!("{}", doc);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,371 +1,374 @@
|
||||||
document {
|
document {
|
||||||
info {
|
info {
|
||||||
title "KDL Schema" lang="en"
|
title "KDL Schema" lang=en
|
||||||
description "KDL Schema KDL schema in KDL" lang="en"
|
description "KDL Schema KDL schema in KDL" lang=en
|
||||||
author "Kat Marchán" {
|
author "Kat Marchán" {
|
||||||
link "https://github.com/zkat" rel="self"
|
link "https://github.com/zkat" rel=self
|
||||||
}
|
}
|
||||||
contributor "Lars Willighagen" {
|
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"
|
link "https://github.com/zkat/kdl" rel=documentation
|
||||||
license "Creative Commons Attribution-ShareAlike 4.0 International License" spdx="CC-BY-SA-4.0" {
|
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://creativecommons.org/licenses/by-sa/4.0/" lang=en
|
||||||
}
|
}
|
||||||
published "2021-08-31"
|
published "2021-08-31"
|
||||||
modified "2021-09-01"
|
modified "2021-09-01"
|
||||||
}
|
}
|
||||||
node "document" {
|
node document {
|
||||||
min 1
|
min 1
|
||||||
max 1
|
max 1
|
||||||
children id="node-children" {
|
children id=node-children {
|
||||||
node "node-names" id="node-names-node" description="Validations to apply specifically to arbitrary node names" {
|
node node-names id=node-names-node description="Validations to apply specifically to arbitrary node names" {
|
||||||
children ref=r#"[id="validations"]"#
|
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
|
max 1
|
||||||
value {
|
value {
|
||||||
min 1
|
min 1
|
||||||
max 1
|
max 1
|
||||||
type "boolean"
|
type boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node "tag-names" description="Validations to apply specifically to arbitrary type tag names" {
|
node tag-names description="Validations to apply specifically to arbitrary type tag names" {
|
||||||
children ref=r#"[id="validations"]"#
|
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
|
max 1
|
||||||
value {
|
value {
|
||||||
min 1
|
min 1
|
||||||
max 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 {
|
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" {
|
value description="The title text" {
|
||||||
type "string"
|
type string
|
||||||
min 1
|
min 1
|
||||||
max 1
|
max 1
|
||||||
}
|
}
|
||||||
prop "lang" id="info-lang" description="The language of the text" {
|
prop lang id=info-lang description="The language of the text" {
|
||||||
type "string"
|
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" {
|
value description="The description text" {
|
||||||
type "string"
|
type string
|
||||||
min 1
|
min 1
|
||||||
max 1
|
max 1
|
||||||
}
|
}
|
||||||
prop ref=r#"[id="info-lang"]"#
|
prop ref=#"[id="info-lang"]"#
|
||||||
}
|
}
|
||||||
node "author" description="Author of the schema" {
|
node author description="Author of the schema" {
|
||||||
value id="info-person-name" description="Person name" {
|
value id=info-person-name description="Person name" {
|
||||||
type "string"
|
type string
|
||||||
min 1
|
min 1
|
||||||
max 1
|
max 1
|
||||||
}
|
}
|
||||||
prop "orcid" id="info-orcid" description="The ORCID of the person" {
|
prop orcid id=info-orcid description="The ORCID of the person" {
|
||||||
type "string"
|
type string
|
||||||
pattern r"\d{4}-\d{4}-\d{4}-\d{4}"
|
pattern #"\d{4}-\d{4}-\d{4}-\d{4}"#
|
||||||
}
|
}
|
||||||
children {
|
children {
|
||||||
node ref=r#"[id="info-link"]"#
|
node ref=#"[id="info-link"]"#
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node "contributor" description="Contributor to the schema" {
|
node contributor description="Contributor to the schema" {
|
||||||
value ref=r#"[id="info-person-name"]"#
|
value ref=#"[id="info-person-name"]"#
|
||||||
prop ref=r#"[id="info-orcid"]"#
|
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" {
|
value description="A URL that the link points to" {
|
||||||
type "string"
|
type string
|
||||||
format "url" "irl"
|
format url irl
|
||||||
min 1
|
min 1
|
||||||
max 1
|
max 1
|
||||||
}
|
}
|
||||||
prop "rel" description="The relation between the current entity and the URL" {
|
prop rel description="The relation between the current entity and the URL" {
|
||||||
type "string"
|
type string
|
||||||
enum "self" "documentation"
|
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" {
|
value description="Name of the used license" {
|
||||||
type "string"
|
type string
|
||||||
min 1
|
min 1
|
||||||
max 1
|
max 1
|
||||||
}
|
}
|
||||||
prop "spdx" description="An SPDX license identifier" {
|
prop spdx description="An SPDX license identifier" {
|
||||||
type "string"
|
type string
|
||||||
}
|
}
|
||||||
children {
|
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" {
|
value description="Publication date" {
|
||||||
type "string"
|
type string
|
||||||
format "date"
|
format date
|
||||||
min 1
|
min 1
|
||||||
max 1
|
max 1
|
||||||
}
|
}
|
||||||
prop "time" id="info-time" description="A time to accompany the date" {
|
prop time id=info-time description="A time to accompany the date" {
|
||||||
type "string"
|
type string
|
||||||
format "time"
|
format time
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node "modified" description="When the schema was last modified" {
|
node modified description="When the schema was last modified" {
|
||||||
value description="Modification date" {
|
value description="Modification date" {
|
||||||
type "string"
|
type string
|
||||||
format "date"
|
format date
|
||||||
min 1
|
min 1
|
||||||
max 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" {
|
value description="Semver version number" {
|
||||||
type "string"
|
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-]+)*))?$"
|
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
|
min 1
|
||||||
max 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." {
|
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
|
max 1
|
||||||
}
|
}
|
||||||
prop "description" description="A description of this node's purpose." {
|
prop description description="A description of this node's purpose." {
|
||||||
type "string"
|
type string
|
||||||
}
|
}
|
||||||
prop "id" description="A globally-unique ID for this node." {
|
prop id description="A globally-unique ID for this node." {
|
||||||
type "string"
|
type string
|
||||||
}
|
}
|
||||||
prop "ref" description="A globally unique reference to another node." {
|
prop ref description="A globally unique reference to another node." {
|
||||||
type "string"
|
type string
|
||||||
format "kdl-query"
|
format kdl-query
|
||||||
}
|
}
|
||||||
children {
|
children {
|
||||||
node ref=r#"[id="node-names-node"]"#
|
node ref=#"[id="node-names-node"]"#
|
||||||
node ref=r#"[id="other-nodes-allowed-node"]"#
|
node ref=#"[id="other-nodes-allowed-node"]"#
|
||||||
node ref=r#"[id="node-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." {
|
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
|
max 1
|
||||||
}
|
}
|
||||||
prop "description" description="A description of this node's purpose." {
|
prop description description="A description of this node's purpose." {
|
||||||
type "string"
|
type string
|
||||||
}
|
}
|
||||||
prop "id" description="A globally-unique ID for this node." {
|
prop id description="A globally-unique ID for this node." {
|
||||||
type "string"
|
type string
|
||||||
}
|
}
|
||||||
prop "ref" description="A globally unique reference to another node." {
|
prop ref description="A globally unique reference to another node." {
|
||||||
type "string"
|
type string
|
||||||
format "kdl-query"
|
format kdl-query
|
||||||
}
|
}
|
||||||
children {
|
children {
|
||||||
node "prop-names" description="Validations to apply specifically to arbitrary property names" {
|
node prop-names description="Validations to apply specifically to arbitrary property names" {
|
||||||
children ref=r#"[id="validations"]"#
|
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
|
max 1
|
||||||
value {
|
value {
|
||||||
min 1
|
min 1
|
||||||
max 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
|
max 1
|
||||||
value {
|
value {
|
||||||
min 1
|
min 1
|
||||||
max 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
|
max 1
|
||||||
value {
|
value {
|
||||||
min 1
|
min 1
|
||||||
max 1
|
max 1
|
||||||
type "number"
|
type number
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node ref=r#"[id="value-tag-node"]"#
|
node ref=#"[id="value-tag-node"]"#
|
||||||
node "prop" id="prop-node" description="A node property key/value pair." {
|
node prop id="prop-node" description="A node property key/value pair." {
|
||||||
value description="The property key." {
|
value description="The property key." {
|
||||||
type "string"
|
type string
|
||||||
}
|
}
|
||||||
prop "id" description="A globally-unique ID of this property." {
|
prop id description="A globally-unique ID of this property." {
|
||||||
type "string"
|
type string
|
||||||
}
|
}
|
||||||
prop "ref" description="A globally unique reference to another property node." {
|
prop ref description="A globally unique reference to another property node." {
|
||||||
type "string"
|
type string
|
||||||
format "kdl-query"
|
format kdl-query
|
||||||
}
|
}
|
||||||
prop "description" description="A description of this property's purpose." {
|
prop description description="A description of this property's purpose." {
|
||||||
type "string"
|
type string
|
||||||
}
|
}
|
||||||
children description="Property-specific validations." {
|
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
|
max 1
|
||||||
value {
|
value {
|
||||||
min 1
|
min 1
|
||||||
max 1
|
max 1
|
||||||
type "boolean"
|
type boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
children id="validations" description="General value validations." {
|
children id=validations description="General value validations." {
|
||||||
node "tag" id="value-tag-node" description="The tags associated with this value" {
|
node tag id=value-tag-node description="The tags associated with this value" {
|
||||||
max 1
|
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
|
max 1
|
||||||
value {
|
value {
|
||||||
min 1
|
min 1
|
||||||
type "string"
|
type string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node "enum" description="An enumeration of possible values" {
|
node enum description="An enumeration of possible values" {
|
||||||
max 1
|
max 1
|
||||||
value description="Enumeration choices" {
|
value description="Enumeration choices" {
|
||||||
min 1
|
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 {
|
value {
|
||||||
min 1
|
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
|
max 1
|
||||||
value {
|
value {
|
||||||
min 1
|
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
|
max 1
|
||||||
value {
|
value {
|
||||||
min 1
|
min 1
|
||||||
type "number"
|
type number
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node "format" description="Intended data format." {
|
node format description="Intended data format." {
|
||||||
max 1
|
max 1
|
||||||
value {
|
value {
|
||||||
min 1
|
min 1
|
||||||
type "string"
|
type string
|
||||||
// https://json-schema.org/understanding-json-schema/reference/string.html#format
|
// 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
|
max 1
|
||||||
value {
|
value {
|
||||||
min 1
|
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
|
max 1
|
||||||
value {
|
value {
|
||||||
min 1
|
min 1
|
||||||
max 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
|
max 1
|
||||||
value {
|
value {
|
||||||
min 1
|
min 1
|
||||||
max 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
|
max 1
|
||||||
value {
|
value {
|
||||||
min 1
|
min 1
|
||||||
max 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
|
max 1
|
||||||
value {
|
value {
|
||||||
min 1
|
min 1
|
||||||
max 1
|
max 1
|
||||||
type "number"
|
type number
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node "value" id="value-node" description="one or more direct node values" {
|
node value id=value-node description="one or more direct node values" {
|
||||||
prop "id" description="A globally-unique ID of this value." {
|
prop id description="A globally-unique ID of this value." {
|
||||||
type "string"
|
type string
|
||||||
}
|
}
|
||||||
prop "ref" description="A globally unique reference to another value node." {
|
prop ref description="A globally unique reference to another value node." {
|
||||||
type "string"
|
type string
|
||||||
format "kdl-query"
|
format kdl-query
|
||||||
}
|
}
|
||||||
prop "description" description="A description of this property's purpose." {
|
prop description description="A description of this property's purpose." {
|
||||||
type "string"
|
type string
|
||||||
}
|
}
|
||||||
children ref=r#"[id="validations"]"#
|
children ref=#"[id="validations"]"#
|
||||||
children description="Node value-specific 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
|
max 1
|
||||||
value {
|
value {
|
||||||
min 1
|
min 1
|
||||||
max 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
|
max 1
|
||||||
value {
|
value {
|
||||||
min 1
|
min 1
|
||||||
max 1
|
max 1
|
||||||
type "number"
|
type number
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node "children" id="children-node" {
|
node children id=children-node {
|
||||||
prop "id" description="A globally-unique ID of this children node." {
|
prop id description="A globally-unique ID of this children node." {
|
||||||
type "string"
|
type string
|
||||||
}
|
}
|
||||||
prop "ref" description="A globally unique reference to another children node." {
|
prop ref description="A globally unique reference to another children node." {
|
||||||
type "string"
|
type string
|
||||||
format "kdl-query"
|
format kdl-query
|
||||||
}
|
}
|
||||||
prop "description" description="A description of this these children's purpose." {
|
prop description description="A description of this these children's purpose." {
|
||||||
type "string"
|
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 {
|
children {
|
||||||
node ref=r#"[id="node-node"]"#
|
node ref=#"[id="node-node"]"#
|
||||||
node ref=r#"[id="value-node"]"#
|
node ref=#"[id="value-node"]"#
|
||||||
node ref=r#"[id="prop-node"]"#
|
node ref=#"[id="prop-node"]"#
|
||||||
node ref=r#"[id="children-node"]"#
|
node ref=#"[id="children-node"]"#
|
||||||
node ref=r#"[id="tag-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
|
// Based on https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Clients/NuGet.CommandLine/NuGet.CommandLine.csproj
|
||||||
Project {
|
Project {
|
||||||
PropertyGroup {
|
PropertyGroup {
|
||||||
IsCommandLinePackage true
|
IsCommandLinePackage #true
|
||||||
}
|
}
|
||||||
|
|
||||||
Import Project=r"$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), 'README.md'))\build\common.props"
|
Import Project=#"$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), 'README.md'))\build\common.props"#
|
||||||
Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk"
|
Import Project=Sdk.props Sdk=Microsoft.NET.Sdk
|
||||||
Import Project="ilmerge.props"
|
Import Project=ilmerge.props
|
||||||
|
|
||||||
PropertyGroup {
|
PropertyGroup {
|
||||||
RootNamespace "NuGet.CommandLine"
|
RootNamespace NuGet.CommandLine
|
||||||
AssemblyName "NuGet"
|
AssemblyName NuGet
|
||||||
AssemblyTitle "NuGet Command Line"
|
AssemblyTitle "NuGet Command Line"
|
||||||
PackageId "NuGet.CommandLine"
|
PackageId NuGet.CommandLine
|
||||||
TargetFramework "$(NETFXTargetFramework)"
|
TargetFramework "$(NETFXTargetFramework)"
|
||||||
GenerateDocumentationFile false
|
GenerateDocumentationFile #false
|
||||||
Description "NuGet Command Line Interface."
|
Description "NuGet Command Line Interface."
|
||||||
ApplicationManifest "app.manifest"
|
ApplicationManifest app.manifest
|
||||||
Shipping true
|
Shipping #true
|
||||||
OutputType "Exe"
|
OutputType Exe
|
||||||
ComVisible false
|
ComVisible #false
|
||||||
// Pack properties
|
// Pack properties
|
||||||
PackProject true
|
PackProject #true
|
||||||
IncludeBuildOutput false
|
IncludeBuildOutput #false
|
||||||
TargetsForTfmSpecificContentInPackage "$(TargetsForTfmSpecificContentInPackage)" "CreateCommandlineNupkg"
|
TargetsForTfmSpecificContentInPackage "$(TargetsForTfmSpecificContentInPackage)" "CreateCommandlineNupkg"
|
||||||
SuppressDependenciesWhenPacking true
|
SuppressDependenciesWhenPacking #true
|
||||||
DevelopmentDependency true
|
DevelopmentDependency #true
|
||||||
PackageRequireLicenseAcceptance false
|
PackageRequireLicenseAcceptance #false
|
||||||
UsePublicApiAnalyzer false
|
UsePublicApiAnalyzer #false
|
||||||
}
|
}
|
||||||
|
|
||||||
Target Name="CreateCommandlineNupkg" {
|
Target Name=CreateCommandlineNupkg {
|
||||||
ItemGroup {
|
ItemGroup {
|
||||||
TfmSpecificPackageFile Include=r"$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.exe" {
|
TfmSpecificPackageFile Include=#"$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.exe"# {
|
||||||
PackagePath "tools/"
|
PackagePath "tools/"
|
||||||
}
|
}
|
||||||
TfmSpecificPackageFile Include=r"$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.pdb" {
|
TfmSpecificPackageFile Include=#"$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.pdb"# {
|
||||||
PackagePath "tools/"
|
PackagePath "tools/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemGroup Condition="$(DefineConstants.Contains(SIGNED_BUILD))" {
|
ItemGroup Condition="$(DefineConstants.Contains(SIGNED_BUILD))" {
|
||||||
AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo" {
|
AssemblyAttribute Include=System.Runtime.CompilerServices.InternalsVisibleTo {
|
||||||
_Parameter1 "NuGet.CommandLine.FuncTest, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293"
|
_Parameter1 "NuGet.CommandLine.FuncTest, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293"
|
||||||
}
|
}
|
||||||
AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo" {
|
AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo" {
|
||||||
|
|
@ -51,81 +51,81 @@ Project {
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemGroup Condition="!$(DefineConstants.Contains(SIGNED_BUILD))" {
|
ItemGroup Condition="!$(DefineConstants.Contains(SIGNED_BUILD))" {
|
||||||
AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo" {
|
AssemblyAttribute Include=System.Runtime.CompilerServices.InternalsVisibleTo {
|
||||||
_Parameter1 "NuGet.CommandLine.FuncTest"
|
_Parameter1 NuGet.CommandLine.FuncTest
|
||||||
}
|
}
|
||||||
AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo" {
|
AssemblyAttribute Include=System.Runtime.CompilerServices.InternalsVisibleTo {
|
||||||
_Parameter1 "NuGet.CommandLine.Test"
|
_Parameter1 NuGet.CommandLine.Test
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemGroup Condition="$(DefineConstants.Contains(SIGNED_BUILD))" {
|
ItemGroup Condition="$(DefineConstants.Contains(SIGNED_BUILD))" {
|
||||||
AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo" {
|
AssemblyAttribute Include=System.Runtime.CompilerServices.InternalsVisibleTo {
|
||||||
_Parameter1 "NuGet.CommandLine.Test, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293"
|
_Parameter1 "NuGet.CommandLine.Test, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemGroup Condition="!$(DefineConstants.Contains(SIGNED_BUILD))" {
|
ItemGroup Condition="!$(DefineConstants.Contains(SIGNED_BUILD))" {
|
||||||
AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo" {
|
AssemblyAttribute Include=System.Runtime.CompilerServices.InternalsVisibleTo {
|
||||||
_Parameter1 "NuGet.CommandLine.Test"
|
_Parameter1 NuGet.CommandLine.Test
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemGroup {
|
ItemGroup {
|
||||||
Reference Include="Microsoft.Build.Utilities.v4.0"
|
Reference Include=Microsoft.Build.Utilities.v4.0
|
||||||
Reference Include="Microsoft.CSharp"
|
Reference Include=Microsoft.CSharp
|
||||||
Reference Include="System"
|
Reference Include=System
|
||||||
Reference Include="System.ComponentModel.Composition"
|
Reference Include=System.ComponentModel.Composition
|
||||||
Reference Include="System.ComponentModel.Composition.Registration"
|
Reference Include=System.ComponentModel.Composition.Registration
|
||||||
Reference Include="System.ComponentModel.DataAnnotations"
|
Reference Include=System.ComponentModel.DataAnnotations
|
||||||
Reference Include="System.IO.Compression"
|
Reference Include=System.IO.Compression
|
||||||
Reference Include="System.Net.Http"
|
Reference Include=System.Net.Http
|
||||||
Reference Include="System.Xml"
|
Reference Include=System.Xml
|
||||||
Reference Include="System.Xml.Linq"
|
Reference Include=System.Xml.Linq
|
||||||
Reference Include="NuGet.Core" {
|
Reference Include=NuGet.Core {
|
||||||
HintPath r"$(SolutionPackagesFolder)nuget.core\2.14.0-rtm-832\lib\net40-Client\NuGet.Core.dll"
|
HintPath #"$(SolutionPackagesFolder)nuget.core\2.14.0-rtm-832\lib\net40-Client\NuGet.Core.dll"#
|
||||||
Aliases "CoreV2"
|
Aliases CoreV2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ItemGroup {
|
ItemGroup {
|
||||||
PackageReference Include="Microsoft.VisualStudio.Setup.Configuration.Interop"
|
PackageReference Include=Microsoft.VisualStudio.Setup.Configuration.Interop
|
||||||
ProjectReference Include=r"$(NuGetCoreSrcDirectory)NuGet.PackageManagement\NuGet.PackageManagement.csproj"
|
ProjectReference Include=#"$(NuGetCoreSrcDirectory)NuGet.PackageManagement\NuGet.PackageManagement.csproj"#
|
||||||
ProjectReference Include=r"$(NuGetCoreSrcDirectory)NuGet.Build.Tasks\NuGet.Build.Tasks.csproj"
|
ProjectReference Include=#"$(NuGetCoreSrcDirectory)NuGet.Build.Tasks\NuGet.Build.Tasks.csproj"#
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemGroup {
|
ItemGroup {
|
||||||
EmbeddedResource Update="NuGetCommand.resx" {
|
EmbeddedResource Update=NuGetCommand.resx {
|
||||||
Generator "ResXFileCodeGenerator"
|
Generator ResXFileCodeGenerator
|
||||||
LastGenOutput "NuGetCommand.Designer.cs"
|
LastGenOutput NuGetCommand.Designer.cs
|
||||||
}
|
}
|
||||||
Compile Update="NuGetCommand.Designer.cs" {
|
Compile Update=NuGetCommand.Designer.cs {
|
||||||
DesignTime true
|
DesignTime #true
|
||||||
AutoGen true
|
AutoGen #true
|
||||||
DependentUpon "NuGetCommand.resx"
|
DependentUpon NuGetCommand.resx
|
||||||
}
|
}
|
||||||
EmbeddedResource Update="NuGetResources.resx" {
|
EmbeddedResource Update=NuGetResources.resx {
|
||||||
// Strings are shared by other projects, use public strings.
|
// Strings are shared by other projects, use public strings.
|
||||||
Generator "PublicResXFileCodeGenerator"
|
Generator PublicResXFileCodeGenerator
|
||||||
LastGenOutput "NuGetResources.Designer.cs"
|
LastGenOutput NuGetResources.Designer.cs
|
||||||
}
|
}
|
||||||
Compile Update="NuGetResources.Designer.cs" {
|
Compile Update=NuGetResources.Designer.cs {
|
||||||
DesignTime true
|
DesignTime #true
|
||||||
AutoGen true
|
AutoGen #true
|
||||||
DependentUpon "NuGetResources.resx"
|
DependentUpon NuGetResources.resx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemGroup {
|
ItemGroup {
|
||||||
EmbeddedResource Include=r"$(NuGetCoreSrcDirectory)NuGet.Build.Tasks\NuGet.targets" {
|
EmbeddedResource Include=#"$(NuGetCoreSrcDirectory)NuGet.Build.Tasks\NuGet.targets"# {
|
||||||
Link "NuGet.targets"
|
Link NuGet.targets
|
||||||
SubType "Designer"
|
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
|
// 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
|
// This will also be called from CI build, after assemblies are localized, since our test infra takes nuget.exe before Localization
|
||||||
Target Name="ILMergeNuGetExe" \
|
Target Name=ILMergeNuGetExe \
|
||||||
AfterTargets="Build" \
|
AfterTargets=Build \
|
||||||
Condition="'$(BuildingInsideVisualStudio)' != 'true' and '$(SkipILMergeOfNuGetExe)' != 'true'" \
|
Condition="'$(BuildingInsideVisualStudio)' != 'true' and '$(SkipILMergeOfNuGetExe)' != 'true'" \
|
||||||
{
|
{
|
||||||
PropertyGroup {
|
PropertyGroup {
|
||||||
|
|
@ -133,9 +133,9 @@ Project {
|
||||||
ExpectedLocalizedArtifactCount 0 Condition="'$(ExpectedLocalizedArtifactCount)' == ''"
|
ExpectedLocalizedArtifactCount 0 Condition="'$(ExpectedLocalizedArtifactCount)' == ''"
|
||||||
}
|
}
|
||||||
ItemGroup {
|
ItemGroup {
|
||||||
BuildArtifacts Include=r"$(OutputPath)\*.dll" Exclude="@(MergeExclude)"
|
BuildArtifacts Include=#"$(OutputPath)\*.dll"# Exclude="@(MergeExclude)"
|
||||||
// NuGet.exe needs all NuGet.Commands.resources.dll merged in
|
// 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" \
|
Error Text="Build dependencies are inconsistent with mergeinclude specified in ilmerge.props" \
|
||||||
Condition="'@(BuildArtifacts->Count())' != '@(MergeInclude->Count())'"
|
Condition="'@(BuildArtifacts->Count())' != '@(MergeInclude->Count())'"
|
||||||
|
|
@ -143,36 +143,36 @@ Project {
|
||||||
Condition="'@(LocalizedArtifacts->Count())' != '$(ExpectedLocalizedArtifactCount)'"
|
Condition="'@(LocalizedArtifacts->Count())' != '$(ExpectedLocalizedArtifactCount)'"
|
||||||
PropertyGroup {
|
PropertyGroup {
|
||||||
PathToBuiltNuGetExe "$(OutputPath)NuGet.exe"
|
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)"
|
IlmergeCommand Condition="Exists($(MS_PFX_PATH))" "$(IlmergeCommand) /delaysign /keyfile:$(MS_PFX_PATH)"
|
||||||
// LocalizedArtifacts need fullpath, since there will be duplicate file names
|
// LocalizedArtifacts need fullpath, since there will be duplicate file names
|
||||||
IlmergeCommand "$(IlmergeCommand) $(PathToBuiltNuGetExe) @(BuildArtifacts->'%(filename)%(extension)', ' ') @(LocalizedArtifacts->'%(fullpath)', ' ')"
|
IlmergeCommand "$(IlmergeCommand) $(PathToBuiltNuGetExe) @(BuildArtifacts->'%(filename)%(extension)', ' ') @(LocalizedArtifacts->'%(fullpath)', ' ')"
|
||||||
}
|
}
|
||||||
MakeDir Directories="$(ArtifactsDirectory)$(VsixOutputDirName)"
|
MakeDir Directories="$(ArtifactsDirectory)$(VsixOutputDirName)"
|
||||||
Exec Command="$(IlmergeCommand)" ContinueOnError="false"
|
Exec Command="$(IlmergeCommand)" ContinueOnError=#false
|
||||||
}
|
}
|
||||||
|
|
||||||
Import Project="$(BuildCommonDirectory)common.targets"
|
Import Project="$(BuildCommonDirectory)common.targets"
|
||||||
Import Project="$(BuildCommonDirectory)embedinterop.targets"
|
Import Project="$(BuildCommonDirectory)embedinterop.targets"
|
||||||
|
|
||||||
// Do nothing. This basically strips away the framework assemblies from the resulting nuspec.
|
// 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 {
|
ItemGroup {
|
||||||
DllsToSign Include=r"$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.exe" {
|
DllsToSign Include=#"$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.exe"# {
|
||||||
StrongName "MsSharedLib72"
|
StrongName MsSharedLib72
|
||||||
Authenticode "Microsoft400"
|
Authenticode Microsoft400
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Target Name="GetSymbolsToIndex" Returns="@(SymbolsToIndex)" {
|
Target Name=GetSymbolsToIndex Returns="@(SymbolsToIndex)" {
|
||||||
ItemGroup {
|
ItemGroup {
|
||||||
SymbolsToIndex Include=r"$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.exe"
|
SymbolsToIndex Include=#"$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.exe"#
|
||||||
SymbolsToIndex Include=r"$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.pdb"
|
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")]
|
#[cfg(feature = "span")]
|
||||||
use miette::SourceSpan;
|
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
|
/// Represents a KDL
|
||||||
/// [`Document`](https://github.com/kdl-org/kdl/blob/main/SPEC.md#document).
|
/// [`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)]
|
#[derive(Debug, Clone, Eq)]
|
||||||
pub struct KdlDocument {
|
pub struct KdlDocument {
|
||||||
pub(crate) leading: Option<String>,
|
|
||||||
pub(crate) nodes: Vec<KdlNode>,
|
pub(crate) nodes: Vec<KdlNode>,
|
||||||
pub(crate) trailing: Option<String>,
|
pub(crate) format: Option<KdlDocumentFormat>,
|
||||||
#[cfg(feature = "span")]
|
#[cfg(feature = "span")]
|
||||||
pub(crate) span: SourceSpan,
|
pub(crate) span: SourceSpan,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for KdlDocument {
|
impl PartialEq for KdlDocument {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.leading == other.leading
|
self.nodes == other.nodes && self.format == other.format
|
||||||
&& self.nodes == other.nodes
|
|
||||||
&& self.trailing == other.trailing
|
|
||||||
// Intentionally omitted: self.span == other.span
|
// Intentionally omitted: self.span == other.span
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::hash::Hash for KdlDocument {
|
impl std::hash::Hash for KdlDocument {
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
self.leading.hash(state);
|
|
||||||
self.nodes.hash(state);
|
self.nodes.hash(state);
|
||||||
self.trailing.hash(state);
|
self.format.hash(state);
|
||||||
// Intentionally omitted: self.span.hash(state)
|
// Intentionally omitted: self.span.hash(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -48,9 +44,8 @@ impl std::hash::Hash for KdlDocument {
|
||||||
impl Default for KdlDocument {
|
impl Default for KdlDocument {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
leading: Default::default(),
|
|
||||||
nodes: Default::default(),
|
nodes: Default::default(),
|
||||||
trailing: Default::default(),
|
format: Default::default(),
|
||||||
#[cfg(feature = "span")]
|
#[cfg(feature = "span")]
|
||||||
span: SourceSpan::from(0..0),
|
span: SourceSpan::from(0..0),
|
||||||
}
|
}
|
||||||
|
|
@ -106,7 +101,7 @@ impl KdlDocument {
|
||||||
/// You can fetch the value of `foo` in a single call like this:
|
/// You can fetch the value of `foo` in a single call like this:
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use kdl::{KdlDocument, KdlValue};
|
/// # 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()));
|
/// assert_eq!(doc.get_arg("foo"), Some(&1.into()));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn get_arg(&self, name: &str) -> Option<&KdlValue> {
|
pub fn get_arg(&self, name: &str) -> Option<&KdlValue> {
|
||||||
|
|
@ -125,13 +120,13 @@ impl KdlDocument {
|
||||||
/// Given a document like this:
|
/// Given a document like this:
|
||||||
/// ```kdl
|
/// ```kdl
|
||||||
/// foo 1 2 3
|
/// foo 1 2 3
|
||||||
/// bar false
|
/// bar #false
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// You can fetch the arguments for `foo` in a single call like this:
|
/// You can fetch the arguments for `foo` in a single call like this:
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use kdl::{KdlDocument, KdlValue};
|
/// # 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()]);
|
/// assert_eq!(doc.get_args("foo"), vec![&1.into(), &2.into(), &3.into()]);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn get_args(&self, name: &str) -> Vec<&KdlValue> {
|
pub fn get_args(&self, name: &str) -> Vec<&KdlValue> {
|
||||||
|
|
@ -161,14 +156,14 @@ impl KdlDocument {
|
||||||
/// foo {
|
/// foo {
|
||||||
/// - 1
|
/// - 1
|
||||||
/// - 2
|
/// - 2
|
||||||
/// - false
|
/// - #false
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// You can fetch the dashed child values of `foo` in a single call like this:
|
/// You can fetch the dashed child values of `foo` in a single call like this:
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use kdl::{KdlDocument, KdlValue};
|
/// # 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()]);
|
/// assert_eq!(doc.get_dash_args("foo"), vec![&1.into(), &2.into(), &false.into()]);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn get_dash_args(&self, name: &str) -> Vec<&KdlValue> {
|
pub fn get_dash_args(&self, name: &str) -> Vec<&KdlValue> {
|
||||||
|
|
@ -192,24 +187,19 @@ impl KdlDocument {
|
||||||
&mut self.nodes
|
&mut self.nodes
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets leading text (whitespace, comments) for this KdlDocument.
|
/// Gets the formatting details for this entry.
|
||||||
pub fn leading(&self) -> Option<&str> {
|
pub fn format(&self) -> Option<&KdlDocumentFormat> {
|
||||||
self.leading.as_deref()
|
self.format.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets leading text (whitespace, comments) for this KdlDocument.
|
/// Gets a mutable reference to this entry's formatting details.
|
||||||
pub fn set_leading(&mut self, leading: impl Into<String>) {
|
pub fn format_mut(&mut self) -> Option<&mut KdlDocumentFormat> {
|
||||||
self.leading = Some(leading.into());
|
self.format.as_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets trailing text (whitespace, comments) for this KdlDocument.
|
/// Sets the formatting details for this entry.
|
||||||
pub fn trailing(&self) -> Option<&str> {
|
pub fn set_format(&mut self, format: KdlDocumentFormat) {
|
||||||
self.trailing.as_deref()
|
self.format = Some(format);
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets trailing text (whitespace, comments) for this KdlDocument.
|
|
||||||
pub fn set_trailing(&mut self, trailing: impl Into<String>) {
|
|
||||||
self.trailing = Some(trailing.into());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Length of this document when rendered as a string.
|
/// Length of this document when rendered as a string.
|
||||||
|
|
@ -226,94 +216,99 @@ impl KdlDocument {
|
||||||
/// this document will be unaffected.
|
/// this document will be unaffected.
|
||||||
///
|
///
|
||||||
/// If you need to clear the `KdlNode`s, use [`Self::clear_fmt_recursive`].
|
/// If you need to clear the `KdlNode`s, use [`Self::clear_fmt_recursive`].
|
||||||
pub fn clear_fmt(&mut self) {
|
pub fn clear_format(&mut self) {
|
||||||
self.leading = None;
|
self.format = None;
|
||||||
self.trailing = None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears leading and trailing text (whitespace, comments), also clearing
|
/// Clears leading and trailing text (whitespace, comments), also clearing
|
||||||
/// all the `KdlNode`s in the document.
|
/// all the `KdlNode`s in the document.
|
||||||
pub fn clear_fmt_recursive(&mut self) {
|
pub fn clear_format_recursive(&mut self) {
|
||||||
self.clear_fmt();
|
self.clear_format();
|
||||||
for node in self.nodes.iter_mut() {
|
for node in self.nodes.iter_mut() {
|
||||||
node.clear_fmt_recursive();
|
node.clear_format_recursive();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Auto-formats this Document, making everything nice while preserving
|
/// Auto-formats this Document, making everything nice while preserving
|
||||||
/// comments.
|
/// comments.
|
||||||
pub fn fmt(&mut self) {
|
pub fn autoformat(&mut self) {
|
||||||
self.fmt_impl(0, false);
|
self.autoformat_impl(0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Formats the document and removes all comments from the document.
|
/// Formats the document and removes all comments from the document.
|
||||||
pub fn fmt_no_comments(&mut self) {
|
pub fn autoformat_no_comments(&mut self) {
|
||||||
self.fmt_impl(0, true);
|
self.autoformat_impl(0, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queries this Document's children according to the KQL query language,
|
// TODO(@zkat): These should all be moved into the query module itself,
|
||||||
/// returning an iterator over all matching nodes.
|
// instead of being methods on the models
|
||||||
///
|
//
|
||||||
/// # NOTE
|
// /// Queries this Document's children according to the KQL query language,
|
||||||
///
|
// /// returning an iterator over all matching nodes.
|
||||||
/// Any query selectors that try to select the toplevel `scope()` will
|
// ///
|
||||||
/// fail to match when using this method, since there's no [`KdlNode`] to
|
// /// # NOTE
|
||||||
/// return in this case.
|
// ///
|
||||||
pub fn query_all(&self, query: impl IntoKdlQuery) -> Result<KdlQueryIterator<'_>, KdlError> {
|
// /// Any query selectors that try to select the toplevel `scope()` will
|
||||||
let parsed = query.into_query()?;
|
// /// fail to match when using this method, since there's no [`KdlNode`] to
|
||||||
Ok(KdlQueryIterator::new(None, Some(self), parsed))
|
// /// 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,
|
// /// Queries this Document's children according to the KQL query language,
|
||||||
/// returning the first match, if any.
|
// /// returning the first match, if any.
|
||||||
///
|
// ///
|
||||||
/// # NOTE
|
// /// # NOTE
|
||||||
///
|
// ///
|
||||||
/// Any query selectors that try to select the toplevel `scope()` will
|
// /// Any query selectors that try to select the toplevel `scope()` will
|
||||||
/// fail to match when using this method, since there's no [`KdlNode`] to
|
// /// fail to match when using this method, since there's no [`KdlNode`] to
|
||||||
/// return in this case.
|
// /// return in this case.
|
||||||
pub fn query(&self, query: impl IntoKdlQuery) -> Result<Option<&KdlNode>, KdlError> {
|
// pub fn query(&self, query: impl IntoKdlQuery) -> Result<Option<&KdlNode>, KdlDiagnostic> {
|
||||||
let mut iter = self.query_all(query)?;
|
// let mut iter = self.query_all(query)?;
|
||||||
Ok(iter.next())
|
// Ok(iter.next())
|
||||||
}
|
// }
|
||||||
|
|
||||||
/// Queries this Document's children according to the KQL query language,
|
// /// Queries this Document's children according to the KQL query language,
|
||||||
/// picking the first match, and calling `.get(key)` on it, if the query
|
// /// picking the first match, and calling `.get(key)` on it, if the query
|
||||||
/// succeeded.
|
// /// succeeded.
|
||||||
///
|
// ///
|
||||||
/// # NOTE
|
// /// # NOTE
|
||||||
///
|
// ///
|
||||||
/// Any query selectors that try to select the toplevel `scope()` will
|
// /// Any query selectors that try to select the toplevel `scope()` will
|
||||||
/// fail to match when using this method, since there's no [`KdlNode`] to
|
// /// fail to match when using this method, since there's no [`KdlNode`] to
|
||||||
/// return in this case.
|
// /// return in this case.
|
||||||
pub fn query_get(
|
// pub fn query_get(
|
||||||
&self,
|
// &self,
|
||||||
query: impl IntoKdlQuery,
|
// query: impl IntoKdlQuery,
|
||||||
key: impl Into<NodeKey>,
|
// key: impl Into<NodeKey>,
|
||||||
) -> Result<Option<&KdlValue>, KdlError> {
|
// ) -> Result<Option<&KdlValue>, KdlDiagnostic> {
|
||||||
Ok(self.query(query)?.and_then(|node| node.get(key)))
|
// Ok(self.query(query)?.and_then(|node| node.get(key)))
|
||||||
}
|
// }
|
||||||
|
|
||||||
/// Queries this Document's children according to the KQL query language,
|
// /// Queries this Document's children according to the KQL query language,
|
||||||
/// returning an iterator over all matching nodes, returning the requested
|
// /// returning an iterator over all matching nodes, returning the requested
|
||||||
/// field from each of those nodes and filtering out nodes that don't have
|
// /// field from each of those nodes and filtering out nodes that don't have
|
||||||
/// it.
|
// /// it.
|
||||||
///
|
// ///
|
||||||
/// # NOTE
|
// /// # NOTE
|
||||||
///
|
// ///
|
||||||
/// Any query selectors that try to select the toplevel `scope()` will
|
// /// Any query selectors that try to select the toplevel `scope()` will
|
||||||
/// fail to match when using this method, since there's no [`KdlNode`] to
|
// /// fail to match when using this method, since there's no [`KdlNode`] to
|
||||||
/// return in this case.
|
// /// return in this case.
|
||||||
pub fn query_get_all(
|
// pub fn query_get_all(
|
||||||
&self,
|
// &self,
|
||||||
query: impl IntoKdlQuery,
|
// query: impl IntoKdlQuery,
|
||||||
key: impl Into<NodeKey>,
|
// key: impl Into<NodeKey>,
|
||||||
) -> Result<impl Iterator<Item = &KdlValue>, KdlError> {
|
// ) -> Result<impl Iterator<Item = &KdlValue>, KdlDiagnostic> {
|
||||||
let key: NodeKey = key.into();
|
// let key: NodeKey = key.into();
|
||||||
Ok(self
|
// Ok(self
|
||||||
.query_all(query)?
|
// .query_all(query)?
|
||||||
.filter_map(move |node| node.get(key.clone())))
|
// .filter_map(move |node| node.get(key.clone())))
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for KdlDocument {
|
impl Display for KdlDocument {
|
||||||
|
|
@ -323,19 +318,19 @@ impl Display for KdlDocument {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KdlDocument {
|
impl KdlDocument {
|
||||||
pub(crate) fn fmt_impl(&mut self, indent: usize, no_comments: bool) {
|
pub(crate) fn autoformat_impl(&mut self, indent: usize, no_comments: bool) {
|
||||||
if let Some(s) = self.leading.as_mut() {
|
if let Some(KdlDocumentFormat { leading, .. }) = self.format_mut() {
|
||||||
crate::fmt::fmt_leading(s, indent, no_comments);
|
crate::fmt::autoformat_leading(leading, indent, no_comments);
|
||||||
}
|
}
|
||||||
let mut has_nodes = false;
|
let mut has_nodes = false;
|
||||||
for node in &mut self.nodes {
|
for node in &mut self.nodes {
|
||||||
has_nodes = true;
|
has_nodes = true;
|
||||||
node.fmt_impl(indent, no_comments);
|
node.autoformat_impl(indent, no_comments);
|
||||||
}
|
}
|
||||||
if let Some(s) = self.trailing.as_mut() {
|
if let Some(KdlDocumentFormat { trailing, .. }) = self.format_mut() {
|
||||||
crate::fmt::fmt_trailing(s, no_comments);
|
crate::fmt::autoformat_trailing(trailing, no_comments);
|
||||||
if !has_nodes {
|
if !has_nodes {
|
||||||
s.push('\n');
|
trailing.push('\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -345,16 +340,13 @@ impl KdlDocument {
|
||||||
f: &mut std::fmt::Formatter<'_>,
|
f: &mut std::fmt::Formatter<'_>,
|
||||||
indent: usize,
|
indent: usize,
|
||||||
) -> std::fmt::Result {
|
) -> std::fmt::Result {
|
||||||
if let Some(leading) = &self.leading {
|
if let Some(KdlDocumentFormat { leading, .. }) = self.format() {
|
||||||
write!(f, "{}", leading)?;
|
write!(f, "{}", leading)?;
|
||||||
}
|
}
|
||||||
for node in &self.nodes {
|
for node in &self.nodes {
|
||||||
node.stringify(f, indent)?;
|
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)?;
|
write!(f, "{}", trailing)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -370,13 +362,13 @@ impl IntoIterator for KdlDocument {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for KdlDocument {
|
/// Formatting details for [`KdlDocument`]s.
|
||||||
type Err = KdlError;
|
#[derive(Debug, Clone, Default, Hash, Eq, PartialEq)]
|
||||||
|
pub struct KdlDocumentFormat {
|
||||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
/// Whitespace and comments preceding the document's first node.
|
||||||
let kdl_parser = parser::KdlParser::new(input);
|
pub leading: String,
|
||||||
kdl_parser.parse(parser::document(&kdl_parser))
|
/// Whitespace and comments following the document's last node.
|
||||||
}
|
pub trailing: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[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 left_doc: KdlDocument = left_src.parse()?;
|
||||||
let mut right_doc: KdlDocument = right_src.parse()?;
|
let mut right_doc: KdlDocument = right_src.parse()?;
|
||||||
assert_ne!(left_doc, right_doc);
|
assert_ne!(left_doc, right_doc);
|
||||||
left_doc.clear_fmt_recursive();
|
left_doc.clear_format_recursive();
|
||||||
right_doc.clear_fmt_recursive();
|
right_doc.clear_format_recursive();
|
||||||
assert_eq!(left_doc, right_doc);
|
assert_eq!(left_doc, right_doc);
|
||||||
Ok(())
|
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]
|
#[test]
|
||||||
fn parsing() -> miette::Result<()> {
|
fn parsing() -> miette::Result<()> {
|
||||||
let src = "
|
let src = "
|
||||||
// This is the first node
|
// This is the first node
|
||||||
foo 1 2 \"three\" null true bar=\"baz\" {
|
foo 1 2 three #null #true bar=\"baz\" {
|
||||||
- 1
|
- 1
|
||||||
- 2
|
- 2
|
||||||
- \"three\"
|
- three
|
||||||
(mytype)something (\"name\")\"else\"\r
|
(mytype)something (\"name\")else\r
|
||||||
}
|
}
|
||||||
|
|
||||||
null_id null_prop=null
|
null_id null_prop=#null
|
||||||
true_id true_prop=null
|
true_id true_prop=#null
|
||||||
+false true
|
+false #true
|
||||||
|
|
||||||
bar \"indented\" // trailing whitespace after this\t
|
bar \"indented\" // trailing whitespace after this\t
|
||||||
/*
|
/*
|
||||||
|
|
@ -440,19 +462,22 @@ Some random comment
|
||||||
a; b; c;
|
a; b; c;
|
||||||
/-commented \"node\"
|
/-commented \"node\"
|
||||||
|
|
||||||
another /*foo*/ \"node\" /-1 /*bar*/ null;
|
another /*foo*/ \"node\" /-1 /*bar*/ #null;
|
||||||
final;";
|
final;";
|
||||||
let mut doc: KdlDocument = src.parse()?;
|
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_arg("foo"), Some(&1.into()));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
doc.get_dash_args("foo"),
|
doc.get_dash_args("foo"),
|
||||||
vec![&1.into(), &2.into(), &"three".into()]
|
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");
|
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[2], &"three".into());
|
||||||
assert_eq!(&foo["bar"], &"baz".into());
|
assert_eq!(&foo["bar"], &"baz".into());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -479,19 +504,21 @@ final;";
|
||||||
let a = doc.get("a").expect("expected a node");
|
let a = doc.get("a").expect("expected a node");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
format!("{}", a),
|
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");
|
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.
|
// Round-tripping works.
|
||||||
assert_eq!(format!("{}", doc), src);
|
assert_eq!(format!("{}", doc), src);
|
||||||
|
|
||||||
// Programmatic manipulation works.
|
// Programmatic manipulation works.
|
||||||
let mut node: KdlNode = "new\n".parse()?;
|
let mut node: KdlNode = "new\n".parse()?;
|
||||||
// Manual entry parsing preserves formatting/reprs.
|
// Manual entry parsing preserves formatting/reprs. Note that
|
||||||
node.push("\"blah\"=0xDEADbeef".parse::<KdlEntry>()?);
|
// 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);
|
doc.nodes_mut().push(node);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -519,9 +546,11 @@ final;";
|
||||||
doc.nodes_mut().push(bar);
|
doc.nodes_mut().push(bar);
|
||||||
doc.nodes_mut().push(KdlNode::new("baz"));
|
doc.nodes_mut().push(KdlNode::new("baz"));
|
||||||
|
|
||||||
|
doc.autoformat();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
r#"foo
|
r#"foo
|
||||||
bar prop="value" 1 2 false null {
|
bar prop=value 1 2 #false #null {
|
||||||
barchild
|
barchild
|
||||||
}
|
}
|
||||||
baz
|
baz
|
||||||
|
|
@ -530,9 +559,10 @@ baz
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[ignore = "There's still issues around formatting comments and esclines."]
|
||||||
#[test]
|
#[test]
|
||||||
fn fmt() -> miette::Result<()> {
|
fn autoformat() -> miette::Result<()> {
|
||||||
let mut doc: KdlDocument = r#"
|
let mut doc: KdlDocument = r##"
|
||||||
|
|
||||||
/* x */ foo 1 "bar"=0xDEADbeef {
|
/* x */ foo 1 "bar"=0xDEADbeef {
|
||||||
child1 1 ;
|
child1 1 ;
|
||||||
|
|
@ -543,14 +573,15 @@ baz
|
||||||
|
|
||||||
child3 "
|
child3 "
|
||||||
|
|
||||||
string\t" \
|
string\t
|
||||||
|
" \
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|
|
||||||
|
|
||||||
multiline*/
|
multiline*/
|
||||||
inner1 \
|
inner1 \
|
||||||
r"value" \
|
#"value"# \
|
||||||
;
|
;
|
||||||
|
|
||||||
inner2 \ //comment
|
inner2 \ //comment
|
||||||
|
|
@ -562,12 +593,11 @@ baz
|
||||||
|
|
||||||
// trailing comment here
|
// trailing comment here
|
||||||
|
|
||||||
"#
|
"##
|
||||||
.parse()?;
|
.parse()?;
|
||||||
|
|
||||||
KdlDocument::fmt(&mut doc);
|
KdlDocument::autoformat(&mut doc);
|
||||||
|
|
||||||
print!("{}", doc);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
doc.to_string(),
|
doc.to_string(),
|
||||||
r#"/* x */
|
r#"/* x */
|
||||||
|
|
@ -575,12 +605,12 @@ foo 1 bar=0xdeadbeef {
|
||||||
child1 1
|
child1 1
|
||||||
// child 2 comment
|
// child 2 comment
|
||||||
child2 2 // comment
|
child2 2 // comment
|
||||||
child3 "\n\n string\t" {
|
child3 "\nstring\t" {
|
||||||
/*
|
/*
|
||||||
|
|
||||||
|
|
||||||
multiline*/
|
multiline*/
|
||||||
inner1 r"value"
|
inner1 value
|
||||||
inner2 {
|
inner2 {
|
||||||
inner3
|
inner3
|
||||||
}
|
}
|
||||||
|
|
@ -592,10 +622,9 @@ foo 1 bar=0xdeadbeef {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn simple_fmt() -> miette::Result<()> {
|
fn simple_autoformat() -> miette::Result<()> {
|
||||||
let mut doc: KdlDocument = "a { b { c { }; }; }".parse().unwrap();
|
let mut doc: KdlDocument = "a { b { c { }; }; }".parse().unwrap();
|
||||||
KdlDocument::fmt(&mut doc);
|
KdlDocument::autoformat(&mut doc);
|
||||||
print!("{}", doc);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
doc.to_string(),
|
doc.to_string(),
|
||||||
r#"a {
|
r#"a {
|
||||||
|
|
@ -619,6 +648,8 @@ foo 1 bar=0xdeadbeef {
|
||||||
|
|
||||||
#[cfg(feature = "span")]
|
#[cfg(feature = "span")]
|
||||||
fn check_spans_for_node(node: &KdlNode, source: &impl miette::SourceCode) {
|
fn check_spans_for_node(node: &KdlNode, source: &impl miette::SourceCode) {
|
||||||
|
use crate::KdlEntryFormat;
|
||||||
|
|
||||||
check_span_for_ident(node.name(), source);
|
check_span_for_ident(node.name(), source);
|
||||||
if let Some(ty) = node.ty() {
|
if let Some(ty) = node.ty() {
|
||||||
check_span_for_ident(ty, source);
|
check_span_for_ident(ty, source);
|
||||||
|
|
@ -631,9 +662,9 @@ foo 1 bar=0xdeadbeef {
|
||||||
if let Some(ty) = entry.ty() {
|
if let Some(ty) = entry.ty() {
|
||||||
check_span_for_ident(ty, source);
|
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() {
|
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 {
|
this {
|
||||||
is (a)"cool" document="to" read=(int)5 10.1 (u32)0x45
|
is (a)"cool" document="to" read=(int)5 10.1 (u32)0x45
|
||||||
and x="" {
|
and x="" {
|
||||||
"it" /*shh*/ "has"="💯" r##"the"##
|
"it" /*shh*/ "has"="💯" ##"the"##
|
||||||
Best🎊est
|
Best🎊est
|
||||||
"syntax ever"
|
"syntax ever"
|
||||||
}
|
}
|
||||||
|
|
@ -678,7 +709,7 @@ nice
|
||||||
inline { time; to; live "our" "dreams"; "y;all"; }
|
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
|
// First check that all the identity-spans are correct
|
||||||
check_spans_for_doc(&doc, &input);
|
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
|
// The node is what you expect, the whole line and its two braces
|
||||||
check_span(
|
check_span(
|
||||||
r####"and x="" {
|
r####"and x="" {
|
||||||
"it" /*shh*/ "has"="💯" r##"the"##
|
"it" /*shh*/ "has"="💯" ##"the"##
|
||||||
Best🎊est
|
Best🎊est
|
||||||
"syntax ever"
|
"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
|
// The child document is a little weird, it's the contents *inside* the braces
|
||||||
// with extra newlines on both ends.
|
// with extra newlines on both ends.
|
||||||
check_span(
|
check_span(
|
||||||
r####"
|
r####"{
|
||||||
"it" /*shh*/ "has"="💯" r##"the"##
|
"it" /*shh*/ "has"="💯" ##"the"##
|
||||||
Best🎊est
|
Best🎊est
|
||||||
"syntax ever"
|
"syntax ever"
|
||||||
"####,
|
}"####,
|
||||||
and_node.children().unwrap().span(),
|
and_node.children().unwrap().span(),
|
||||||
&input,
|
&input,
|
||||||
);
|
);
|
||||||
|
|
@ -750,7 +781,7 @@ inline { time; to; live "our" "dreams"; "y;all"; }
|
||||||
// Now the "it" node, more straightforward
|
// Now the "it" node, more straightforward
|
||||||
let it_node = and_node.children().unwrap().get("it").unwrap();
|
let it_node = and_node.children().unwrap().get("it").unwrap();
|
||||||
check_span(
|
check_span(
|
||||||
r####""it" /*shh*/ "has"="💯" r##"the"##"####,
|
r####""it" /*shh*/ "has"="💯" ##"the"##"####,
|
||||||
it_node.span(),
|
it_node.span(),
|
||||||
&input,
|
&input,
|
||||||
);
|
);
|
||||||
|
|
@ -760,7 +791,7 @@ inline { time; to; live "our" "dreams"; "y;all"; }
|
||||||
&input,
|
&input,
|
||||||
);
|
);
|
||||||
check_span(
|
check_span(
|
||||||
r####"r##"the"##"####,
|
r####"##"the"##"####,
|
||||||
it_node.entry(0).unwrap().span(),
|
it_node.entry(0).unwrap().span(),
|
||||||
&input,
|
&input,
|
||||||
);
|
);
|
||||||
|
|
@ -775,7 +806,7 @@ inline { time; to; live "our" "dreams"; "y;all"; }
|
||||||
|
|
||||||
let inline_children = inline_node.children().unwrap();
|
let inline_children = inline_node.children().unwrap();
|
||||||
check_span(
|
check_span(
|
||||||
r#" time; to; live "our" "dreams"; "y;all"; "#,
|
r#"{ time; to; live "our" "dreams"; "y;all"; }"#,
|
||||||
inline_children.span(),
|
inline_children.span(),
|
||||||
&input,
|
&input,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
254
src/entry.rs
254
src/entry.rs
|
|
@ -2,7 +2,7 @@
|
||||||
use miette::SourceSpan;
|
use miette::SourceSpan;
|
||||||
use std::{fmt::Display, str::FromStr};
|
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)
|
/// KDL Entries are the "arguments" to KDL nodes: either a (positional)
|
||||||
/// [`Argument`](https://github.com/kdl-org/kdl/blob/main/SPEC.md#argument) or
|
/// [`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)
|
/// [`Property`](https://github.com/kdl-org/kdl/blob/main/SPEC.md#property)
|
||||||
#[derive(Debug, Clone, Eq)]
|
#[derive(Debug, Clone, Eq)]
|
||||||
pub struct KdlEntry {
|
pub struct KdlEntry {
|
||||||
pub(crate) leading: Option<String>,
|
|
||||||
pub(crate) ty: Option<KdlIdentifier>,
|
pub(crate) ty: Option<KdlIdentifier>,
|
||||||
pub(crate) value: KdlValue,
|
pub(crate) value: KdlValue,
|
||||||
pub(crate) value_repr: Option<String>,
|
|
||||||
pub(crate) name: Option<KdlIdentifier>,
|
pub(crate) name: Option<KdlIdentifier>,
|
||||||
pub(crate) trailing: Option<String>,
|
pub(crate) format: Option<KdlEntryFormat>,
|
||||||
#[cfg(feature = "span")]
|
#[cfg(feature = "span")]
|
||||||
pub(crate) span: SourceSpan,
|
pub(crate) span: SourceSpan,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for KdlEntry {
|
impl PartialEq for KdlEntry {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.leading == other.leading
|
self.ty == other.ty
|
||||||
&& self.ty == other.ty
|
|
||||||
&& self.value == other.value
|
&& self.value == other.value
|
||||||
&& self.value_repr == other.value_repr
|
|
||||||
&& self.name == other.name
|
&& self.name == other.name
|
||||||
&& self.trailing == other.trailing
|
&& self.format == other.format
|
||||||
// intentionally omitted: self.span == other.span
|
// intentionally omitted: self.span == other.span
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::hash::Hash for KdlEntry {
|
impl std::hash::Hash for KdlEntry {
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
self.leading.hash(state);
|
|
||||||
self.ty.hash(state);
|
self.ty.hash(state);
|
||||||
self.value.hash(state);
|
self.value.hash(state);
|
||||||
self.value_repr.hash(state);
|
|
||||||
self.name.hash(state);
|
self.name.hash(state);
|
||||||
self.trailing.hash(state);
|
self.format.hash(state);
|
||||||
// intentionally omitted: self.span.hash(state)
|
// intentionally omitted: self.span.hash(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -48,14 +42,12 @@ impl KdlEntry {
|
||||||
/// Creates a new Argument (positional) KdlEntry.
|
/// Creates a new Argument (positional) KdlEntry.
|
||||||
pub fn new(value: impl Into<KdlValue>) -> Self {
|
pub fn new(value: impl Into<KdlValue>) -> Self {
|
||||||
KdlEntry {
|
KdlEntry {
|
||||||
leading: None,
|
|
||||||
ty: None,
|
ty: None,
|
||||||
value: value.into(),
|
value: value.into(),
|
||||||
value_repr: None,
|
|
||||||
name: None,
|
name: None,
|
||||||
trailing: None,
|
format: None,
|
||||||
#[cfg(feature = "span")]
|
#[cfg(feature = "span")]
|
||||||
span: SourceSpan::from(0..0),
|
span: (0..0).into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,6 +56,16 @@ impl KdlEntry {
|
||||||
self.name.as_ref()
|
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.
|
/// Gets the entry's value.
|
||||||
pub fn value(&self) -> &KdlValue {
|
pub fn value(&self) -> &KdlValue {
|
||||||
&self.value
|
&self.value
|
||||||
|
|
@ -110,64 +112,45 @@ impl KdlEntry {
|
||||||
self.ty = Some(ty.into());
|
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.
|
/// Creates a new Property (key/value) KdlEntry.
|
||||||
pub fn new_prop(key: impl Into<KdlIdentifier>, value: impl Into<KdlValue>) -> Self {
|
pub fn new_prop(key: impl Into<KdlIdentifier>, value: impl Into<KdlValue>) -> Self {
|
||||||
KdlEntry {
|
KdlEntry {
|
||||||
leading: None,
|
|
||||||
ty: None,
|
ty: None,
|
||||||
value: value.into(),
|
value: value.into(),
|
||||||
value_repr: None,
|
|
||||||
name: Some(key.into()),
|
name: Some(key.into()),
|
||||||
trailing: None,
|
format: None,
|
||||||
#[cfg(feature = "span")]
|
#[cfg(feature = "span")]
|
||||||
span: SourceSpan::from(0..0),
|
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
|
/// Clears leading and trailing text (whitespace, comments), as well as
|
||||||
/// resetting this entry's value to its default representation.
|
/// resetting this entry's value to its default representation.
|
||||||
pub fn clear_fmt(&mut self) {
|
pub fn clear_format(&mut self) {
|
||||||
self.leading = None;
|
self.format = None;
|
||||||
self.trailing = None;
|
|
||||||
self.value_repr = None;
|
|
||||||
if let Some(ty) = &mut self.ty {
|
if let Some(ty) = &mut self.ty {
|
||||||
ty.clear_fmt();
|
ty.clear_format();
|
||||||
}
|
}
|
||||||
if let Some(name) = &mut self.name {
|
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.
|
/// Length of this entry when rendered as a string.
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
format!("{}", self).len()
|
format!("{}", self).len()
|
||||||
|
|
@ -179,33 +162,54 @@ impl KdlEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Auto-formats this entry.
|
/// Auto-formats this entry.
|
||||||
pub fn fmt(&mut self) {
|
pub fn autoformat(&mut self) {
|
||||||
self.leading = None;
|
self.format = None;
|
||||||
self.trailing = None;
|
|
||||||
self.value_repr = None;
|
|
||||||
if let Some(name) = &mut self.name {
|
if let Some(name) = &mut self.name {
|
||||||
name.fmt();
|
name.autoformat();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for KdlEntry {
|
impl Display for KdlEntry {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
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)?;
|
write!(f, "{}", leading)?;
|
||||||
}
|
}
|
||||||
if let Some(name) = &self.name {
|
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 {
|
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 {
|
if let Some(KdlEntryFormat {
|
||||||
write!(f, "{}", repr)?;
|
after_ty,
|
||||||
|
value_repr,
|
||||||
|
..
|
||||||
|
}) = &self.format
|
||||||
|
{
|
||||||
|
write!(f, "{}{}", after_ty, value_repr)?;
|
||||||
} else {
|
} else {
|
||||||
write!(f, "{}", self.value)?;
|
write!(f, "{}", self.value)?;
|
||||||
}
|
}
|
||||||
if let Some(trailing) = &self.trailing {
|
if let Some(KdlEntryFormat { trailing, .. }) = &self.format {
|
||||||
write!(f, "{}", trailing)?;
|
write!(f, "{}", trailing)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -232,14 +236,42 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for KdlEntry {
|
impl FromStr for KdlEntry {
|
||||||
type Err = KdlError;
|
type Err = KdlParseFailure;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let kdl_parser = parser::KdlParser::new(s);
|
let (maybe_val, errs) = v2_parser::try_parse(v2_parser::padded_node_entry, s);
|
||||||
kdl_parser.parse(parser::entry_with_trailing(&kdl_parser))
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
@ -249,8 +281,8 @@ mod test {
|
||||||
let mut left_entry: KdlEntry = " name=1.03e2".parse()?;
|
let mut left_entry: KdlEntry = " name=1.03e2".parse()?;
|
||||||
let mut right_entry: KdlEntry = " name=103.0".parse()?;
|
let mut right_entry: KdlEntry = " name=103.0".parse()?;
|
||||||
assert_ne!(left_entry, right_entry);
|
assert_ne!(left_entry, right_entry);
|
||||||
left_entry.clear_fmt();
|
left_entry.clear_format();
|
||||||
right_entry.clear_fmt();
|
right_entry.clear_format();
|
||||||
assert_eq!(left_entry, right_entry);
|
assert_eq!(left_entry, right_entry);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -261,12 +293,10 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
entry,
|
entry,
|
||||||
KdlEntry {
|
KdlEntry {
|
||||||
leading: None,
|
|
||||||
ty: None,
|
ty: None,
|
||||||
value: KdlValue::Base10(42),
|
value: KdlValue::Integer(42),
|
||||||
value_repr: None,
|
|
||||||
name: None,
|
name: None,
|
||||||
trailing: None,
|
format: None,
|
||||||
#[cfg(feature = "span")]
|
#[cfg(feature = "span")]
|
||||||
span: SourceSpan::from(0..0),
|
span: SourceSpan::from(0..0),
|
||||||
}
|
}
|
||||||
|
|
@ -276,12 +306,10 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
entry,
|
entry,
|
||||||
KdlEntry {
|
KdlEntry {
|
||||||
leading: None,
|
|
||||||
ty: None,
|
ty: None,
|
||||||
value: KdlValue::Base10(42),
|
value: KdlValue::Integer(42),
|
||||||
value_repr: None,
|
|
||||||
name: Some("name".into()),
|
name: Some("name".into()),
|
||||||
trailing: None,
|
format: None,
|
||||||
#[cfg(feature = "span")]
|
#[cfg(feature = "span")]
|
||||||
span: SourceSpan::from(0..0),
|
span: SourceSpan::from(0..0),
|
||||||
}
|
}
|
||||||
|
|
@ -290,18 +318,55 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parsing() -> miette::Result<()> {
|
fn parsing() -> miette::Result<()> {
|
||||||
let entry: KdlEntry = " \\\n (\"m\\\"eh\")0xDEADbeef\t\\\n".parse()?;
|
let entry: KdlEntry = "foo".parse()?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
entry,
|
entry,
|
||||||
KdlEntry {
|
KdlEntry {
|
||||||
leading: Some(" \\\n ".into()),
|
ty: None,
|
||||||
ty: Some("\"m\\\"eh\"".parse()?),
|
value: KdlValue::from("foo"),
|
||||||
value: KdlValue::Base16(0xdeadbeef),
|
|
||||||
value_repr: Some("0xDEADbeef".into()),
|
|
||||||
name: None,
|
name: None,
|
||||||
trailing: Some("\t\\\n".into()),
|
format: Some(KdlEntryFormat {
|
||||||
|
value_repr: "foo".into(),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
#[cfg(feature = "span")]
|
#[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!(
|
assert_eq!(
|
||||||
entry,
|
entry,
|
||||||
KdlEntry {
|
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()?),
|
ty: Some("\"m\\\"eh\"".parse()?),
|
||||||
value: KdlValue::Base16(0xdeadbeef),
|
value: KdlValue::Integer(0xdeadbeef),
|
||||||
value_repr: Some("0xDEADbeef".into()),
|
|
||||||
name: Some("\"foo\"".parse()?),
|
name: Some("\"foo\"".parse()?),
|
||||||
trailing: Some("\t\\\n".into()),
|
|
||||||
#[cfg(feature = "span")]
|
#[cfg(feature = "span")]
|
||||||
span: SourceSpan::from(0..0),
|
span: SourceSpan::from(0..0),
|
||||||
}
|
}
|
||||||
|
|
@ -325,10 +397,10 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn display() {
|
fn display() {
|
||||||
let entry = KdlEntry::new(KdlValue::Base10(42));
|
let entry = KdlEntry::new(KdlValue::Integer(42));
|
||||||
assert_eq!(format!("{}", entry), "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");
|
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 miette::{Diagnostic, SourceSpan};
|
||||||
use nom::error::{ContextError, ErrorKind, FromExternalError, ParseError};
|
use nom::error::{ContextError, ErrorKind, FromExternalError, ParseError};
|
||||||
|
|
@ -10,11 +13,12 @@ use {
|
||||||
std::convert::{TryFrom, TryInto},
|
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
|
/// This diagnostic implements [`miette::Diagnostic`] and can be used to
|
||||||
/// detailed, pretty-printed diagnostic messages when using [`miette::Result`]
|
/// display detailed, pretty-printed diagnostic messages when using
|
||||||
/// and the `"fancy"` feature flag for `miette`:
|
/// [`miette::Result`] and the `"fancy"` feature flag for `miette`:
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// fn main() -> miette::Result<()> {
|
/// fn main() -> miette::Result<()> {
|
||||||
|
|
@ -35,11 +39,26 @@ use {
|
||||||
/// help: Floating point numbers must be base 10, and have numbers after the decimal point.
|
/// help: Floating point numbers must be base 10, and have numbers after the decimal point.
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, Diagnostic, Clone, Eq, PartialEq, Error)]
|
#[derive(Debug, Diagnostic, Clone, Eq, PartialEq, Error)]
|
||||||
#[error("{kind}")]
|
#[error("Failed to parse KDL.")]
|
||||||
pub struct KdlError {
|
pub struct KdlParseFailure {
|
||||||
/// Source string for the KDL document that failed to parse.
|
/// Original input that this failure came from.
|
||||||
#[source_code]
|
#[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.
|
/// Offset in chars of the error.
|
||||||
#[label("{}", label.unwrap_or("here"))]
|
#[label("{}", label.unwrap_or("here"))]
|
||||||
|
|
@ -52,6 +71,10 @@ pub struct KdlError {
|
||||||
#[help]
|
#[help]
|
||||||
pub help: Option<&'static str>,
|
pub help: Option<&'static str>,
|
||||||
|
|
||||||
|
/// Severity level for the Diagnostic.
|
||||||
|
#[diagnostic(severity)]
|
||||||
|
pub severity: miette::Severity,
|
||||||
|
|
||||||
/// Specific error kind for this parser error.
|
/// Specific error kind for this parser error.
|
||||||
pub kind: KdlErrorKind,
|
pub kind: KdlErrorKind,
|
||||||
}
|
}
|
||||||
|
|
@ -77,7 +100,7 @@ pub enum KdlErrorKind {
|
||||||
|
|
||||||
/// Generic unspecified error. If this is returned, the call site should
|
/// Generic unspecified error. If this is returned, the call site should
|
||||||
/// be annotated with context, if possible.
|
/// be annotated with context, if possible.
|
||||||
#[error("An unspecified error occurred.")]
|
#[error("An unspecified parse error occurred.")]
|
||||||
#[diagnostic(code(kdl::other))]
|
#[diagnostic(code(kdl::other))]
|
||||||
Other,
|
Other,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
35
src/fmt.rs
35
src/fmt.rs
|
|
@ -1,20 +1,15 @@
|
||||||
use std::fmt::Write as _;
|
use std::fmt::Write as _;
|
||||||
|
|
||||||
pub(crate) fn fmt_leading(leading: &mut String, indent: usize, no_comments: bool) {
|
pub(crate) fn autoformat_leading(leading: &mut String, indent: usize, no_comments: bool) {
|
||||||
if leading.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
if !no_comments {
|
if !no_comments {
|
||||||
let input = leading.trim();
|
let input = leading.trim();
|
||||||
let kdl_parser = crate::parser::KdlParser { full_input: input };
|
if !input.is_empty() {
|
||||||
let comments = kdl_parser
|
for line in input.lines() {
|
||||||
.parse(crate::parser::leading_comments(&kdl_parser))
|
let trimmed = line.trim();
|
||||||
.expect("invalid leading text");
|
if !trimmed.is_empty() {
|
||||||
for line in comments {
|
writeln!(result, "{:indent$}{}", "", trimmed, indent = indent).unwrap();
|
||||||
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;
|
*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() {
|
if decor.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
*decor = decor.trim().to_string();
|
*decor = decor.trim().to_string();
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
if !no_comments {
|
if !decor.is_empty() && !no_comments {
|
||||||
let input = &*decor;
|
if decor.trim_start() == &decor[..] {
|
||||||
let kdl_parser = crate::parser::KdlParser { full_input: input };
|
write!(result, " ").unwrap();
|
||||||
let comments = kdl_parser
|
}
|
||||||
.parse(crate::parser::trailing_comments(&kdl_parser))
|
for comment in decor.lines() {
|
||||||
.expect("invalid trailing text");
|
writeln!(result, "{comment}").unwrap();
|
||||||
for comment in comments {
|
|
||||||
result.push_str(comment);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*decor = result;
|
*decor = result;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
use miette::SourceSpan;
|
use miette::SourceSpan;
|
||||||
use std::{fmt::Display, str::FromStr};
|
use std::{fmt::Display, str::FromStr};
|
||||||
|
|
||||||
use crate::{parser, KdlError};
|
use crate::{v2_parser, KdlParseFailure, KdlValue};
|
||||||
|
|
||||||
/// Represents a KDL
|
/// Represents a KDL
|
||||||
/// [Identifier](https://github.com/kdl-org/kdl/blob/main/SPEC.md#identifier).
|
/// [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
|
/// Resets this identifier to its default representation. It will attempt
|
||||||
/// to make it an unquoted identifier, and fall back to a string
|
/// to make it an unquoted identifier, and fall back to a string
|
||||||
/// representation if that would be invalid.
|
/// representation if that would be invalid.
|
||||||
pub fn clear_fmt(&mut self) {
|
pub fn clear_format(&mut self) {
|
||||||
self.repr = None;
|
self.repr = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Auto-formats this identifier.
|
/// Auto-formats this identifier.
|
||||||
pub fn fmt(&mut self) {
|
pub fn autoformat(&mut self) {
|
||||||
self.repr = None;
|
self.repr = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -93,85 +93,12 @@ impl Display for KdlIdentifier {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
if let Some(repr) = &self.repr {
|
if let Some(repr) = &self.repr {
|
||||||
write!(f, "{}", repr)
|
write!(f, "{}", repr)
|
||||||
} else if self.plain_value() {
|
|
||||||
write!(f, "{}", self.value)
|
|
||||||
} else {
|
} 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 {
|
impl From<&str> for KdlIdentifier {
|
||||||
fn from(value: &str) -> Self {
|
fn from(value: &str) -> Self {
|
||||||
KdlIdentifier {
|
KdlIdentifier {
|
||||||
|
|
@ -201,11 +128,15 @@ impl From<KdlIdentifier> for String {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for KdlIdentifier {
|
impl FromStr for KdlIdentifier {
|
||||||
type Err = KdlError;
|
type Err = KdlParseFailure;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let kdl_parser = crate::parser::KdlParser::new(s);
|
let (maybe_val, errs) = v2_parser::try_parse(v2_parser::identifier, s);
|
||||||
kdl_parser.parse(parser::identifier(&kdl_parser))
|
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!(
|
assert_eq!(
|
||||||
quoted.parse::<KdlIdentifier>()?,
|
quoted.parse::<KdlIdentifier>()?,
|
||||||
KdlIdentifier {
|
KdlIdentifier {
|
||||||
|
|
|
||||||
85
src/lib.rs
85
src/lib.rs
|
|
@ -64,38 +64,6 @@
|
||||||
//! [`KdlDocument`], [`KdlNode`], [`KdlEntry`], and [`KdlIdentifier`] can all
|
//! [`KdlDocument`], [`KdlNode`], [`KdlEntry`], and [`KdlIdentifier`] can all
|
||||||
//! be parsed and managed this way.
|
//! 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
|
//! ## Error Reporting
|
||||||
//!
|
//!
|
||||||
//! [`KdlError`] implements [`miette::Diagnostic`] and can be used to display
|
//! [`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
|
//! you [`KdlDocument::fmt`] in which case the original representation will be
|
||||||
//! thrown away and the actual value will be used when serializing.
|
//! 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
|
//! ## License
|
||||||
//!
|
//!
|
||||||
//! The code in this repository is covered by [the Apache-2.0
|
//! The code in this repository is covered by [the Apache-2.0
|
||||||
//! License](LICENSE.md).
|
//! 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)]
|
#![deny(missing_debug_implementations, nonstandard_style)]
|
||||||
#![warn(missing_docs, unreachable_pub, rust_2018_idioms, unreachable_pub)]
|
#![warn(missing_docs, rust_2018_idioms, unreachable_pub)]
|
||||||
#![cfg_attr(test, deny(warnings))]
|
// #![cfg_attr(test, deny(warnings))]
|
||||||
#![doc(html_favicon_url = "https://kdl.dev/favicon.ico")]
|
#![doc(html_favicon_url = "https://kdl.dev/favicon.ico")]
|
||||||
#![doc(html_logo_url = "https://kdl.dev/logo.svg")]
|
#![doc(html_logo_url = "https://kdl.dev/logo.svg")]
|
||||||
|
|
||||||
|
|
@ -168,7 +173,7 @@ pub use entry::*;
|
||||||
pub use error::*;
|
pub use error::*;
|
||||||
pub use identifier::*;
|
pub use identifier::*;
|
||||||
pub use node::*;
|
pub use node::*;
|
||||||
pub use query::*;
|
// pub use query::*;
|
||||||
pub use value::*;
|
pub use value::*;
|
||||||
|
|
||||||
mod document;
|
mod document;
|
||||||
|
|
@ -177,8 +182,10 @@ mod error;
|
||||||
mod fmt;
|
mod fmt;
|
||||||
mod identifier;
|
mod identifier;
|
||||||
mod node;
|
mod node;
|
||||||
mod nom_compat;
|
// mod nom_compat;
|
||||||
mod parser;
|
// mod query;
|
||||||
mod query;
|
// mod query_parser;
|
||||||
mod query_parser;
|
// mod v1_parser;
|
||||||
mod value;
|
mod value;
|
||||||
|
|
||||||
|
mod v2_parser;
|
||||||
|
|
|
||||||
294
src/node.rs
294
src/node.rs
|
|
@ -8,49 +8,44 @@ use std::{
|
||||||
use miette::SourceSpan;
|
use miette::SourceSpan;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
parser, IntoKdlQuery, KdlDocument, KdlEntry, KdlError, KdlIdentifier, KdlQueryIterator,
|
v2_parser, KdlDocument, KdlDocumentFormat, KdlEntry, KdlIdentifier, KdlParseFailure, KdlValue,
|
||||||
KdlValue,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static INDENT: usize = 4;
|
||||||
|
|
||||||
/// Represents an individual KDL
|
/// Represents an individual KDL
|
||||||
/// [`Node`](https://github.com/kdl-org/kdl/blob/main/SPEC.md#node) inside a
|
/// [`Node`](https://github.com/kdl-org/kdl/blob/main/SPEC.md#node) inside a
|
||||||
/// KDL Document.
|
/// KDL Document.
|
||||||
#[derive(Debug, Clone, Eq)]
|
#[derive(Debug, Clone, Eq)]
|
||||||
pub struct KdlNode {
|
pub struct KdlNode {
|
||||||
pub(crate) leading: Option<String>,
|
|
||||||
pub(crate) ty: Option<KdlIdentifier>,
|
pub(crate) ty: Option<KdlIdentifier>,
|
||||||
pub(crate) name: KdlIdentifier,
|
pub(crate) name: KdlIdentifier,
|
||||||
// TODO: consider using `hashlink` for this instead, later.
|
// TODO: consider using `hashlink` for this instead, later.
|
||||||
pub(crate) entries: Vec<KdlEntry>,
|
pub(crate) entries: Vec<KdlEntry>,
|
||||||
pub(crate) before_children: Option<String>,
|
|
||||||
pub(crate) children: Option<KdlDocument>,
|
pub(crate) children: Option<KdlDocument>,
|
||||||
pub(crate) trailing: Option<String>,
|
pub(crate) format: Option<KdlNodeFormat>,
|
||||||
#[cfg(feature = "span")]
|
#[cfg(feature = "span")]
|
||||||
pub(crate) span: SourceSpan,
|
pub(crate) span: SourceSpan,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for KdlNode {
|
impl PartialEq for KdlNode {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.leading == other.leading
|
self.ty == other.ty
|
||||||
&& self.ty == other.ty
|
|
||||||
&& self.name == other.name
|
&& self.name == other.name
|
||||||
&& self.entries == other.entries
|
&& self.entries == other.entries
|
||||||
&& self.before_children == other.before_children
|
|
||||||
&& self.children == other.children
|
&& self.children == other.children
|
||||||
&& self.trailing == other.trailing
|
&& self.format == other.format
|
||||||
// intentionally omitted: self.span == other.span
|
// intentionally omitted: self.span == other.span
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::hash::Hash for KdlNode {
|
impl std::hash::Hash for KdlNode {
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
self.leading.hash(state);
|
|
||||||
self.ty.hash(state);
|
self.ty.hash(state);
|
||||||
self.name.hash(state);
|
self.name.hash(state);
|
||||||
self.entries.hash(state);
|
self.entries.hash(state);
|
||||||
self.before_children.hash(state);
|
|
||||||
self.children.hash(state);
|
self.children.hash(state);
|
||||||
self.trailing.hash(state);
|
self.format.hash(state);
|
||||||
// Intentionally omitted: self.span.hash(state);
|
// Intentionally omitted: self.span.hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -60,12 +55,13 @@ impl KdlNode {
|
||||||
pub fn new(name: impl Into<KdlIdentifier>) -> Self {
|
pub fn new(name: impl Into<KdlIdentifier>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
leading: None,
|
|
||||||
ty: None,
|
ty: None,
|
||||||
entries: Vec::new(),
|
entries: Vec::new(),
|
||||||
before_children: None,
|
|
||||||
children: None,
|
children: None,
|
||||||
trailing: None,
|
format: Some(KdlNodeFormat {
|
||||||
|
trailing: "\n".into(),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
#[cfg(feature = "span")]
|
#[cfg(feature = "span")]
|
||||||
span: SourceSpan::from(0..0),
|
span: SourceSpan::from(0..0),
|
||||||
}
|
}
|
||||||
|
|
@ -128,36 +124,6 @@ impl KdlNode {
|
||||||
&mut self.entries
|
&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.
|
/// Length of this node when rendered as a string.
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
format!("{}", self).len()
|
format!("{}", self).len()
|
||||||
|
|
@ -174,23 +140,21 @@ impl KdlNode {
|
||||||
///
|
///
|
||||||
/// If you want to clear formatting on all children and entries as well,
|
/// If you want to clear formatting on all children and entries as well,
|
||||||
/// use [`Self::clear_fmt_recursive`].
|
/// use [`Self::clear_fmt_recursive`].
|
||||||
pub fn clear_fmt(&mut self) {
|
pub fn clear_format(&mut self) {
|
||||||
self.leading = None;
|
self.format = None;
|
||||||
self.trailing = None;
|
|
||||||
self.before_children = None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears leading and trailing text (whitespace, comments), as well as
|
/// Clears leading and trailing text (whitespace, comments), as well as
|
||||||
/// the space before the children block, if any. Individual entries and
|
/// the space before the children block, if any. Individual entries and
|
||||||
/// children formatting will also be cleared.
|
/// children formatting will also be cleared.
|
||||||
pub fn clear_fmt_recursive(&mut self) {
|
pub fn clear_format_recursive(&mut self) {
|
||||||
self.clear_fmt();
|
self.clear_format();
|
||||||
self.name.clear_fmt();
|
self.name.clear_format();
|
||||||
if let Some(children) = &mut self.children {
|
if let Some(children) = &mut self.children {
|
||||||
children.clear_fmt_recursive();
|
children.clear_format_recursive();
|
||||||
}
|
}
|
||||||
for entry in self.entries.iter_mut() {
|
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()
|
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.
|
/// Auto-formats this node and its contents.
|
||||||
pub fn fmt(&mut self) {
|
pub fn autoformat(&mut self) {
|
||||||
self.fmt_impl(0, false);
|
self.autoformat_impl(0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Auto-formats this node and its contents, stripping comments.
|
/// Auto-formats this node and its contents, stripping comments.
|
||||||
pub fn fmt_no_comments(&mut self) {
|
pub fn autoformat_no_comments(&mut self) {
|
||||||
self.fmt_impl(0, true);
|
self.autoformat_impl(0, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queries this Node according to the KQL query language,
|
// TODO(@zkat): These should all be moved into the query module, instead
|
||||||
/// returning an iterator over all matching nodes.
|
// of being model methods.
|
||||||
pub fn query_all(&self, query: impl IntoKdlQuery) -> Result<KdlQueryIterator<'_>, KdlError> {
|
//
|
||||||
let q = query.into_query()?;
|
// /// Queries this Node according to the KQL
|
||||||
Ok(KdlQueryIterator::new(Some(self), None, q))
|
// 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,
|
// /// Queries this Node according to the KQL query language,
|
||||||
/// returning the first match, if any.
|
// /// returning the first match, if any.
|
||||||
pub fn query(&self, query: impl IntoKdlQuery) -> Result<Option<&KdlNode>, KdlError> {
|
// pub fn query(&self, query: impl IntoKdlQuery) -> Result<Option<&KdlNode>, KdlDiagnostic> {
|
||||||
Ok(self.query_all(query)?.next())
|
// Ok(self.query_all(query)?.next())
|
||||||
}
|
// }
|
||||||
|
|
||||||
/// Queries this Node according to the KQL query language,
|
// /// Queries this Node according to the KQL query language,
|
||||||
/// picking the first match, and calling `.get(key)` on it, if the query
|
// /// picking the first match, and calling `.get(key)` on it, if the query
|
||||||
/// succeeded.
|
// /// succeeded.
|
||||||
pub fn query_get(
|
// pub fn query_get(
|
||||||
&self,
|
// &self,
|
||||||
query: impl IntoKdlQuery,
|
// query: impl IntoKdlQuery,
|
||||||
key: impl Into<NodeKey>,
|
// key: impl Into<NodeKey>,
|
||||||
) -> Result<Option<&KdlValue>, KdlError> {
|
// ) -> Result<Option<&KdlValue>, KdlDiagnostic> {
|
||||||
Ok(self.query(query)?.and_then(|node| node.get(key)))
|
// Ok(self.query(query)?.and_then(|node| node.get(key)))
|
||||||
}
|
// }
|
||||||
|
|
||||||
/// Queries this Node according to the KQL query language,
|
// /// Queries this Node according to the KQL query language,
|
||||||
/// returning an iterator over all matching nodes, returning the requested
|
// /// returning an iterator over all matching nodes, returning the requested
|
||||||
/// field from each of those nodes and filtering out nodes that don't have
|
// /// field from each of those nodes and filtering out nodes that don't have
|
||||||
/// it.
|
// /// it.
|
||||||
pub fn query_get_all(
|
// pub fn query_get_all(
|
||||||
&self,
|
// &self,
|
||||||
query: impl IntoKdlQuery,
|
// query: impl IntoKdlQuery,
|
||||||
key: impl Into<NodeKey>,
|
// key: impl Into<NodeKey>,
|
||||||
) -> Result<impl Iterator<Item = &KdlValue>, KdlError> {
|
// ) -> Result<impl Iterator<Item = &KdlValue>, KdlDiagnostic> {
|
||||||
let key: NodeKey = key.into();
|
// let key: NodeKey = key.into();
|
||||||
Ok(self
|
// Ok(self
|
||||||
.query_all(query)?
|
// .query_all(query)?
|
||||||
.filter_map(move |node| node.get(key.clone())))
|
// .filter_map(move |node| node.get(key.clone())))
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a [`KdlNode`]'s entry key.
|
/// Represents a [`KdlNode`]'s entry key.
|
||||||
|
|
@ -517,11 +498,15 @@ impl IndexMut<&str> for KdlNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for KdlNode {
|
impl FromStr for KdlNode {
|
||||||
type Err = KdlError;
|
type Err = KdlParseFailure;
|
||||||
|
|
||||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
let kdl_parser = crate::parser::KdlParser::new(input);
|
let (maybe_val, errs) = v2_parser::try_parse(v2_parser::padded_node, input);
|
||||||
kdl_parser.parse(parser::node(&kdl_parser))
|
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 {
|
impl KdlNode {
|
||||||
pub(crate) fn fmt_impl(&mut self, indent: usize, no_comments: bool) {
|
pub(crate) fn autoformat_impl(&mut self, indent: usize, no_comments: bool) {
|
||||||
if let Some(s) = self.leading.as_mut() {
|
if let Some(KdlNodeFormat {
|
||||||
crate::fmt::fmt_leading(s, indent, no_comments);
|
leading,
|
||||||
}
|
trailing,
|
||||||
if let Some(s) = self.trailing.as_mut() {
|
before_children,
|
||||||
crate::fmt::fmt_trailing(s, no_comments);
|
..
|
||||||
if s.starts_with(';') {
|
}) = self.format_mut()
|
||||||
s.remove(0);
|
{
|
||||||
|
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() {
|
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_format();
|
||||||
self.name.clear_fmt();
|
|
||||||
if let Some(ty) = self.ty.as_mut() {
|
if let Some(ty) = self.ty.as_mut() {
|
||||||
ty.clear_fmt()
|
ty.clear_format()
|
||||||
}
|
}
|
||||||
for entry in &mut self.entries {
|
for entry in &mut self.entries {
|
||||||
entry.fmt();
|
entry.autoformat();
|
||||||
}
|
}
|
||||||
if let Some(children) = self.children.as_mut() {
|
if let Some(children) = self.children.as_mut() {
|
||||||
children.fmt_impl(indent + 4, no_comments);
|
children.autoformat_impl(indent + INDENT, no_comments);
|
||||||
if let Some(leading) = children.leading.as_mut() {
|
if let Some(KdlDocumentFormat { leading, trailing }) = children.format_mut() {
|
||||||
|
*leading = leading.trim().into();
|
||||||
leading.push('\n');
|
leading.push('\n');
|
||||||
}
|
|
||||||
if let Some(trailing) = children.trailing.as_mut() {
|
|
||||||
trailing.push_str(format!("{:indent$}", "", indent = indent).as_str());
|
trailing.push_str(format!("{:indent$}", "", indent = indent).as_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -572,7 +567,7 @@ impl KdlNode {
|
||||||
f: &mut std::fmt::Formatter<'_>,
|
f: &mut std::fmt::Formatter<'_>,
|
||||||
indent: usize,
|
indent: usize,
|
||||||
) -> std::fmt::Result {
|
) -> std::fmt::Result {
|
||||||
if let Some(leading) = &self.leading {
|
if let Some(KdlNodeFormat { leading, .. }) = self.format() {
|
||||||
write!(f, "{}", leading)?;
|
write!(f, "{}", leading)?;
|
||||||
} else {
|
} else {
|
||||||
write!(f, "{:indent$}", "", indent = indent)?;
|
write!(f, "{:indent$}", "", indent = indent)?;
|
||||||
|
|
@ -583,35 +578,56 @@ impl KdlNode {
|
||||||
write!(f, "{}", self.name)?;
|
write!(f, "{}", self.name)?;
|
||||||
let mut space_before_children = true;
|
let mut space_before_children = true;
|
||||||
for entry in &self.entries {
|
for entry in &self.entries {
|
||||||
if entry.leading.is_none() {
|
if entry.format().is_none() {
|
||||||
write!(f, " ")?;
|
write!(f, " ")?;
|
||||||
}
|
}
|
||||||
write!(f, "{}", entry)?;
|
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(children) = &self.children {
|
||||||
if let Some(before) = self.before_children() {
|
if let Some(KdlNodeFormat {
|
||||||
write!(f, "{}", before)?;
|
before_children, ..
|
||||||
|
}) = self.format()
|
||||||
|
{
|
||||||
|
write!(f, "{before_children}")?;
|
||||||
} else if space_before_children {
|
} else if space_before_children {
|
||||||
write!(f, " ")?;
|
write!(f, " ")?;
|
||||||
}
|
}
|
||||||
write!(f, "{{")?;
|
write!(f, "{{")?;
|
||||||
if children.leading.is_none() {
|
if children.format().is_none() {
|
||||||
writeln!(f)?;
|
writeln!(f)?;
|
||||||
}
|
}
|
||||||
children.stringify(f, indent + 4)?;
|
children.stringify(f, indent + 4)?;
|
||||||
if children.trailing.is_none() {
|
if children.format().is_none() {
|
||||||
write!(f, "{:indent$}", "", indent = indent)?;
|
write!(f, "{:indent$}", "", indent = indent)?;
|
||||||
}
|
}
|
||||||
write!(f, "}}")?;
|
write!(f, "}}")?;
|
||||||
}
|
}
|
||||||
if let Some(trailing) = &self.trailing {
|
if let Some(KdlNodeFormat { trailing, .. }) = self.format() {
|
||||||
write!(f, "{}", trailing)?;
|
write!(f, "{}", trailing)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
@ -625,26 +641,38 @@ mod test {
|
||||||
.parse()?;
|
.parse()?;
|
||||||
let mut right_node: KdlNode = "node param_name=103.0 { nested 1 2 3; }".parse()?;
|
let mut right_node: KdlNode = "node param_name=103.0 { nested 1 2 3; }".parse()?;
|
||||||
assert_ne!(left_node, right_node);
|
assert_ne!(left_node, right_node);
|
||||||
left_node.clear_fmt_recursive();
|
left_node.clear_format_recursive();
|
||||||
right_node.clear_fmt_recursive();
|
right_node.clear_format_recursive();
|
||||||
assert_eq!(left_node, right_node);
|
assert_eq!(left_node.to_string(), right_node.to_string());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parsing() -> miette::Result<()> {
|
fn parsing() -> miette::Result<()> {
|
||||||
let node: KdlNode = "\n\t (\"ty\")\"node\" 0xDEADbeef;\n".parse()?;
|
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.ty(), Some(&"\"ty\"".parse()?));
|
||||||
assert_eq!(node.name(), &"\"node\"".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#"
|
let node: KdlNode = r#"node test {
|
||||||
node "test" {
|
link "blah" anything=self
|
||||||
link "blah" anything="self"
|
}
|
||||||
}"#
|
"#
|
||||||
.parse::<KdlNode>()?;
|
.parse::<KdlNode>()?;
|
||||||
|
assert_eq!(node.entry(0), Some(&" test".parse()?));
|
||||||
|
assert_eq!(node.children().unwrap().nodes().len(), 1);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
14
src/query.rs
14
src/query.rs
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{collections::VecDeque, str::FromStr, sync::Arc};
|
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
|
/// A parsed KQL query. For details on the syntax, see the [KQL
|
||||||
/// spec](https://github.com/kdl-org/kdl/blob/main/QUERY-SPEC.md).
|
/// 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>);
|
pub struct KdlQuery(pub(crate) Vec<KdlQuerySelector>);
|
||||||
|
|
||||||
impl FromStr for KdlQuery {
|
impl FromStr for KdlQuery {
|
||||||
type Err = KdlError;
|
type Err = KdlDiagnostic;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let parser = KdlQueryParser::new(s);
|
let parser = KdlQueryParser::new(s);
|
||||||
|
|
@ -26,29 +26,29 @@ impl<'a> IntoKdlQuery for &'a String {}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub trait IntoQuerySealed {
|
pub trait IntoQuerySealed {
|
||||||
fn into_query(self) -> Result<KdlQuery, KdlError>;
|
fn into_query(self) -> Result<KdlQuery, KdlDiagnostic>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoQuerySealed for KdlQuery {
|
impl IntoQuerySealed for KdlQuery {
|
||||||
fn into_query(self) -> Result<KdlQuery, KdlError> {
|
fn into_query(self) -> Result<KdlQuery, KdlDiagnostic> {
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoQuerySealed for &str {
|
impl IntoQuerySealed for &str {
|
||||||
fn into_query(self) -> Result<KdlQuery, KdlError> {
|
fn into_query(self) -> Result<KdlQuery, KdlDiagnostic> {
|
||||||
self.parse()
|
self.parse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoQuerySealed for String {
|
impl IntoQuerySealed for String {
|
||||||
fn into_query(self) -> Result<KdlQuery, KdlError> {
|
fn into_query(self) -> Result<KdlQuery, KdlDiagnostic> {
|
||||||
self.parse()
|
self.parse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoQuerySealed for &String {
|
impl IntoQuerySealed for &String {
|
||||||
fn into_query(self) -> Result<KdlQuery, KdlError> {
|
fn into_query(self) -> Result<KdlQuery, KdlDiagnostic> {
|
||||||
self.parse()
|
self.parse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
use crate::nom_compat::many0;
|
use crate::nom_compat::many0;
|
||||||
use crate::parser::{value, KdlParser};
|
|
||||||
use crate::query::{
|
use crate::query::{
|
||||||
KdlQuery, KdlQueryAttributeOp, KdlQueryMatcher, KdlQueryMatcherAccessor,
|
KdlQuery, KdlQueryAttributeOp, KdlQueryMatcher, KdlQueryMatcherAccessor,
|
||||||
KdlQueryMatcherDetails, KdlQuerySelector, KdlQuerySelectorSegment, KdlSegmentCombinator,
|
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::branch::alt;
|
||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
use nom::combinator::{all_consuming, cut, map, opt, recognize};
|
use nom::combinator::{all_consuming, cut, map, opt, recognize};
|
||||||
|
|
@ -20,7 +21,7 @@ impl<'a> KdlQueryParser<'a> {
|
||||||
Self(KdlParser::new(full_input))
|
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
|
where
|
||||||
P: Parser<&'a str, T, KdlParseError<&'a str>>,
|
P: Parser<&'a str, T, KdlParseError<&'a str>>,
|
||||||
{
|
{
|
||||||
|
|
@ -29,7 +30,7 @@ impl<'a> KdlQueryParser<'a> {
|
||||||
.map(|(_, arg)| arg)
|
.map(|(_, arg)| arg)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
let span_substr = &e.input[..e.len];
|
let span_substr = &e.input[..e.len];
|
||||||
KdlError {
|
KdlDiagnostic {
|
||||||
input: self.0.full_input.into(),
|
input: self.0.full_input.into(),
|
||||||
span: self.0.span_from_substr(span_substr),
|
span: self.0.span_from_substr(span_substr),
|
||||||
help: if let Some(help) = e.help {
|
help: if let Some(help) = e.help {
|
||||||
|
|
@ -47,6 +48,7 @@ impl<'a> KdlQueryParser<'a> {
|
||||||
} else {
|
} else {
|
||||||
KdlErrorKind::Context("a valid KQL query")
|
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 {
|
if let Some(node) = node {
|
||||||
matchers.push(KdlQueryMatcherDetails {
|
matchers.push(KdlQueryMatcherDetails {
|
||||||
op: KdlQueryAttributeOp::Equal,
|
op: KdlQueryAttributeOp::Equal,
|
||||||
|
|
@ -230,7 +232,7 @@ fn node_matchers<'a: 'b, 'b>(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for trailing node name matcher.
|
// 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() {
|
if ident.is_some() {
|
||||||
return Err(nom::Err::Error(KdlParseError {
|
return Err(nom::Err::Error(KdlParseError {
|
||||||
input: start,
|
input: start,
|
||||||
|
|
@ -275,7 +277,7 @@ fn attribute_matcher_inner<'a: 'b, 'b>(
|
||||||
let (input, _) = whitespace(input)?;
|
let (input, _) = whitespace(input)?;
|
||||||
if let Some(op) = op {
|
if let Some(op) = op {
|
||||||
let prev = input;
|
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
|
// Make sure it's a syntax error to try and use string
|
||||||
// operators with non-string arguments.
|
// operators with non-string arguments.
|
||||||
if let Some((_, value)) = val {
|
if let Some((_, value)) = val {
|
||||||
|
|
@ -370,7 +372,7 @@ fn annotation_matcher<'a: 'b, 'b>(
|
||||||
let start = input;
|
let start = input;
|
||||||
let (input, _) = tag("(")(input)?;
|
let (input, _) = tag("(")(input)?;
|
||||||
let (input, _) = whitespace(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)
|
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.")))?;
|
.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((
|
Ok((
|
||||||
|
|
@ -464,7 +466,7 @@ fn prop_name_accessor<'a: 'b, 'b>(
|
||||||
) -> impl Fn(&'a str) -> IResult<&'a str, KdlQueryMatcherAccessor, KdlParseError<&'a str>> + 'b {
|
) -> impl Fn(&'a str) -> IResult<&'a str, KdlQueryMatcherAccessor, KdlParseError<&'a str>> + 'b {
|
||||||
move |input| {
|
move |input| {
|
||||||
let start = 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)?;
|
let (_, paren) = opt(preceded(whitespace, tag("(")))(input)?;
|
||||||
if paren.is_some() {
|
if paren.is_some() {
|
||||||
Err(nom::Err::Error(KdlParseError {
|
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 {
|
) -> impl Fn(&'a str) -> IResult<&'a str, String, KdlParseError<&'a str>> + 'b {
|
||||||
move |input| {
|
move |input| {
|
||||||
let (input, _) = tag("(")(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)?;
|
let (input, _) = tag(")")(input)?;
|
||||||
Ok((input, prop.value().to_owned()))
|
Ok((input, prop.value().to_owned()))
|
||||||
}
|
}
|
||||||
|
|
@ -547,7 +549,7 @@ fn bad_accessor<'a: 'b, 'b>(
|
||||||
}
|
}
|
||||||
|
|
||||||
let (input, ident) = opt(terminated(
|
let (input, ident) = opt(terminated(
|
||||||
crate::parser::identifier(&kdl_parser.0),
|
crate::v1_parser::identifier(&kdl_parser.0),
|
||||||
preceded(
|
preceded(
|
||||||
whitespace,
|
whitespace,
|
||||||
terminated(tag("("), opt(preceded(whitespace, tag(")")))),
|
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>> {
|
fn whitespace(input: &str) -> IResult<&str, &str, KdlParseError<&str>> {
|
||||||
recognize(many0(alt((
|
recognize(many0(alt((
|
||||||
crate::parser::unicode_space,
|
crate::v1_parser::unicode_space,
|
||||||
crate::parser::newline,
|
crate::v1_parser::newline,
|
||||||
))))(input)
|
))))(input)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
use std::ops::RangeTo;
|
use std::ops::RangeTo;
|
||||||
|
|
||||||
use crate::nom_compat::{many0, many1, many_till};
|
use crate::nom_compat::{many0, many1, many_till};
|
||||||
use miette::SourceSpan;
|
use miette::{Severity, SourceSpan};
|
||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
use nom::bytes::complete::{tag, take_until, take_while, take_while_m_n};
|
use nom::bytes::complete::{tag, take_until, take_while, take_while_m_n};
|
||||||
use nom::character::complete::{anychar, char, none_of, one_of};
|
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 nom::{Finish, IResult, Offset, Parser, Slice};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
KdlDocument, KdlEntry, KdlError, KdlErrorKind, KdlIdentifier, KdlNode, KdlParseError, KdlValue,
|
KdlDiagnostic, KdlDocument, KdlEntry, KdlErrorKind, KdlIdentifier, KdlNode, KdlParseError,
|
||||||
|
KdlValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The parser for the entire input.
|
/// The parser for the entire input.
|
||||||
|
|
@ -36,7 +37,7 @@ impl<'a> KdlParser<'a> {
|
||||||
Self { full_input }
|
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
|
where
|
||||||
P: Parser<&'a str, T, KdlParseError<&'a str>>,
|
P: Parser<&'a str, T, KdlParseError<&'a str>>,
|
||||||
{
|
{
|
||||||
|
|
@ -45,7 +46,7 @@ impl<'a> KdlParser<'a> {
|
||||||
.map(|(_, arg)| arg)
|
.map(|(_, arg)| arg)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
let span_substr = &e.input[..e.len];
|
let span_substr = &e.input[..e.len];
|
||||||
KdlError {
|
KdlDiagnostic {
|
||||||
input: self.full_input.into(),
|
input: self.full_input.into(),
|
||||||
span: self.span_from_substr(span_substr),
|
span: self.span_from_substr(span_substr),
|
||||||
help: e.help,
|
help: e.help,
|
||||||
|
|
@ -57,6 +58,7 @@ impl<'a> KdlParser<'a> {
|
||||||
} else {
|
} else {
|
||||||
KdlErrorKind::Other
|
KdlErrorKind::Other
|
||||||
},
|
},
|
||||||
|
severity: Severity::Error,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -840,7 +842,7 @@ mod node_tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn basic() {
|
fn basic() {
|
||||||
let input = "foo 1 \"bar\"=false";
|
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) {
|
match node(&kdl_parser)(input) {
|
||||||
Ok(("", parsed)) => {
|
Ok(("", parsed)) => {
|
||||||
let mut ident = KdlIdentifier::from("foo");
|
let mut ident = KdlIdentifier::from("foo");
|
||||||
|
|
@ -873,7 +875,7 @@ mod node_tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn errant_ident1() {
|
fn errant_ident1() {
|
||||||
let input = "struct Vec { }";
|
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 res = kdl_parser.parse(document(&kdl_parser));
|
||||||
let e = res.unwrap_err();
|
let e = res.unwrap_err();
|
||||||
check_span("Vec", &e.span, &input);
|
check_span("Vec", &e.span, &input);
|
||||||
|
|
@ -885,7 +887,7 @@ mod node_tests {
|
||||||
let input = r##"
|
let input = r##"
|
||||||
some_node
|
some_node
|
||||||
bad evil"##;
|
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 res = kdl_parser.parse(document(&kdl_parser));
|
||||||
let e = res.unwrap_err();
|
let e = res.unwrap_err();
|
||||||
check_span("evil", &e.span, &input);
|
check_span("evil", &e.span, &input);
|
||||||
|
|
@ -895,7 +897,7 @@ mod node_tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn errant_ident3() {
|
fn errant_ident3() {
|
||||||
let input = r##"node "ok" wait "fine""##;
|
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 res = kdl_parser.parse(document(&kdl_parser));
|
||||||
let e = res.unwrap_err();
|
let e = res.unwrap_err();
|
||||||
check_span("wait", &e.span, &input);
|
check_span("wait", &e.span, &input);
|
||||||
|
|
@ -905,7 +907,7 @@ mod node_tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn errant_ident4() {
|
fn errant_ident4() {
|
||||||
let input = r##"node x="ok" oof z="5"##;
|
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 res = kdl_parser.parse(document(&kdl_parser));
|
||||||
let e = res.unwrap_err();
|
let e = res.unwrap_err();
|
||||||
check_span("oof", &e.span, &input);
|
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!
|
// 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!
|
// But at least it's clear that the value is bad, which is ok!
|
||||||
let input = r##"node x=bad"##;
|
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 res = kdl_parser.parse(document(&kdl_parser));
|
||||||
let e = res.unwrap_err();
|
let e = res.unwrap_err();
|
||||||
check_span("", &e.span, &input);
|
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!
|
// 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!
|
// But at least it's clear that the value is bad, which is ok!
|
||||||
let input = r##"node (int)bad"##;
|
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 res = kdl_parser.parse(document(&kdl_parser));
|
||||||
let e = res.unwrap_err();
|
let e = res.unwrap_err();
|
||||||
check_span("", &e.span, &input);
|
check_span("", &e.span, &input);
|
||||||
|
|
@ -952,7 +954,7 @@ mod whitespace_tests {
|
||||||
use super::all_whitespace;
|
use super::all_whitespace;
|
||||||
|
|
||||||
let input = " \t\n\r";
|
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")));
|
assert_eq!(all_whitespace(&kdl_parser)(input), Ok(("", " \t\n\r")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -964,18 +966,18 @@ mod comment_tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn single_line() {
|
fn single_line() {
|
||||||
let input = "// Hello world";
|
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")));
|
assert_eq!(comment(&kdl_parser)(input), Ok(("", "// Hello world")));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn multi_line() {
|
fn multi_line() {
|
||||||
let input = "/* Hello world */";
|
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 */")));
|
assert_eq!(comment(&kdl_parser)(input), Ok(("", "/* Hello world */")));
|
||||||
|
|
||||||
let input = "/* Hello /* world */ blah */";
|
let input = "/* Hello /* world */ blah */";
|
||||||
let kdl_parser = crate::parser::KdlParser::new(input);
|
let kdl_parser = crate::v1_parser::KdlParser::new(input);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
comment(&kdl_parser)(input),
|
comment(&kdl_parser)(input),
|
||||||
Ok(("", "/* Hello /* world */ blah */"))
|
Ok(("", "/* Hello /* world */ blah */"))
|
||||||
|
|
@ -985,7 +987,7 @@ mod comment_tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn slashdash() {
|
fn slashdash() {
|
||||||
let input = "/-foo 1 2";
|
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")));
|
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 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");
|
let (_, n) = node(&kdl_parser)(input).expect("failed to parse node");
|
||||||
assert_eq!(&n[0], &KdlValue::Base16(0x0123456789abcdef));
|
assert_eq!(&n[0], &KdlValue::Base16(0x0123456789abcdef));
|
||||||
assert_eq!(
|
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).
|
/// A specific [KDL Value](https://github.com/kdl-org/kdl/blob/main/SPEC.md#value).
|
||||||
#[derive(Debug, Clone, PartialOrd)]
|
#[derive(Debug, Clone, PartialOrd)]
|
||||||
pub enum KdlValue {
|
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).
|
/// A [KDL String](https://github.com/kdl-org/kdl/blob/main/SPEC.md#string).
|
||||||
String(String),
|
String(String),
|
||||||
|
|
||||||
/// A [KDL
|
/// A non-float [KDL
|
||||||
/// Number](https://github.com/kdl-org/kdl/blob/main/SPEC.md#number) in
|
/// Number](https://github.com/kdl-org/kdl/blob/main/SPEC.md#number)
|
||||||
/// binary form (e.g. `0b010101`).
|
Integer(i64),
|
||||||
Base2(i64),
|
|
||||||
|
|
||||||
/// A [KDL
|
/// A floating point [KDL
|
||||||
/// Number](https://github.com/kdl-org/kdl/blob/main/SPEC.md#number) in
|
/// Number](https://github.com/kdl-org/kdl/blob/main/SPEC.md#number)
|
||||||
/// octal form (e.g. `0o12345670`).
|
Float(f64),
|
||||||
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 [KDL Boolean](https://github.com/kdl-org/kdl/blob/main/SPEC.md#boolean).
|
/// A [KDL Boolean](https://github.com/kdl-org/kdl/blob/main/SPEC.md#boolean).
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
|
|
@ -46,12 +26,9 @@ impl Eq for KdlValue {}
|
||||||
impl PartialEq for KdlValue {
|
impl PartialEq for KdlValue {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(Self::RawString(l0), Self::RawString(r0)) => l0 == r0,
|
|
||||||
(Self::String(l0), Self::String(r0)) => l0 == r0,
|
(Self::String(l0), Self::String(r0)) => l0 == r0,
|
||||||
(Self::Base2(l0), Self::Base2(r0)) => l0 == r0,
|
(Self::Integer(l0), Self::Integer(r0)) => l0 == r0,
|
||||||
(Self::Base8(l0), Self::Base8(r0)) => l0 == r0,
|
(Self::Float(l0), Self::Float(r0)) => {
|
||||||
(Self::Base10(l0), Self::Base10(r0)) => l0 == r0,
|
|
||||||
(Self::Base10Float(l0), Self::Base10Float(r0)) => {
|
|
||||||
let l0 = if l0 == &f64::INFINITY {
|
let l0 = if l0 == &f64::INFINITY {
|
||||||
f64::MAX
|
f64::MAX
|
||||||
} else if l0 == &f64::NEG_INFINITY {
|
} else if l0 == &f64::NEG_INFINITY {
|
||||||
|
|
@ -74,7 +51,6 @@ impl PartialEq for KdlValue {
|
||||||
};
|
};
|
||||||
l0 == r0
|
l0 == r0
|
||||||
}
|
}
|
||||||
(Self::Base16(l0), Self::Base16(r0)) => l0 == r0,
|
|
||||||
(Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
|
(Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
|
||||||
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
|
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
|
||||||
}
|
}
|
||||||
|
|
@ -86,12 +62,9 @@ impl PartialEq for KdlValue {
|
||||||
impl std::hash::Hash for KdlValue {
|
impl std::hash::Hash for KdlValue {
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
match self {
|
match self {
|
||||||
KdlValue::RawString(val) => val.hash(state),
|
|
||||||
KdlValue::String(val) => val.hash(state),
|
KdlValue::String(val) => val.hash(state),
|
||||||
KdlValue::Base2(val) => val.hash(state),
|
KdlValue::Integer(val) => val.hash(state),
|
||||||
KdlValue::Base8(val) => val.hash(state),
|
KdlValue::Float(val) => {
|
||||||
KdlValue::Base10(val) => val.hash(state),
|
|
||||||
KdlValue::Base10Float(val) => {
|
|
||||||
let val = if val == &f64::INFINITY {
|
let val = if val == &f64::INFINITY {
|
||||||
f64::MAX
|
f64::MAX
|
||||||
} else if val == &f64::NEG_INFINITY {
|
} else if val == &f64::NEG_INFINITY {
|
||||||
|
|
@ -106,7 +79,6 @@ impl std::hash::Hash for KdlValue {
|
||||||
(val.trunc() as i64).hash(state);
|
(val.trunc() as i64).hash(state);
|
||||||
(val.fract() as i64).hash(state);
|
(val.fract() as i64).hash(state);
|
||||||
}
|
}
|
||||||
KdlValue::Base16(val) => val.hash(state),
|
|
||||||
KdlValue::Bool(val) => val.hash(state),
|
KdlValue::Bool(val) => val.hash(state),
|
||||||
KdlValue::Null => core::mem::discriminant(self).hash(state),
|
KdlValue::Null => core::mem::discriminant(self).hash(state),
|
||||||
}
|
}
|
||||||
|
|
@ -114,58 +86,19 @@ impl std::hash::Hash for KdlValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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`].
|
/// Returns `true` if the value is a [`KdlValue::String`].
|
||||||
pub fn is_string(&self) -> bool {
|
pub fn is_string(&self) -> bool {
|
||||||
matches!(self, Self::String(..))
|
matches!(self, Self::String(..))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the value is a [`KdlValue::String`] or [`KdlValue::RawString`].
|
/// Returns `true` if the value is a [`KdlValue::Integer`].
|
||||||
pub fn is_string_value(&self) -> bool {
|
pub fn is_integer(&self) -> bool {
|
||||||
matches!(self, Self::String(..) | Self::RawString(..))
|
matches!(self, Self::Integer(..))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the value is a [`KdlValue::Base2`].
|
/// Returns `true` if the value is a [`KdlValue::Float`].
|
||||||
pub fn is_base2(&self) -> bool {
|
pub fn is_float(&self) -> bool {
|
||||||
matches!(self, Self::Base2(..))
|
matches!(self, Self::Float(..))
|
||||||
}
|
|
||||||
|
|
||||||
/// 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::Bool`].
|
/// Returns `true` if the value is a [`KdlValue::Bool`].
|
||||||
|
|
@ -178,34 +111,32 @@ impl KdlValue {
|
||||||
matches!(self, Self::Null)
|
matches!(self, Self::Null)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `Some(&str)` if the `KdlValue` is a [`KdlValue::RawString`] or a
|
/// Returns `Some(&str)` if the `KdlValue` is a [`KdlValue::String`],
|
||||||
/// [`KdlValue::String`], otherwise returns `None`.
|
/// otherwise returns `None`.
|
||||||
pub fn as_string(&self) -> Option<&str> {
|
pub fn as_string(&self) -> Option<&str> {
|
||||||
use KdlValue::*;
|
use KdlValue::*;
|
||||||
match self {
|
match self {
|
||||||
String(s) | RawString(s) => Some(s),
|
String(s) => Some(s),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `Some(i64)` if the `KdlValue` is a [`KdlValue::Base2`],
|
/// Returns `Some(i64)` if the `KdlValue` is a [`KdlValue::Integer`],
|
||||||
/// [`KdlValue::Base8`], [`KdlValue::Base10`], or [`KdlValue::Base16`],
|
|
||||||
/// otherwise returns `None`.
|
/// otherwise returns `None`.
|
||||||
pub fn as_i64(&self) -> Option<i64> {
|
pub fn as_integer(&self) -> Option<i64> {
|
||||||
use KdlValue::*;
|
use KdlValue::*;
|
||||||
match self {
|
match self {
|
||||||
Base2(i) | Base8(i) | Base10(i) | Base16(i) => Some(*i),
|
Integer(i) => Some(*i),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `Some(f64)` if the `KdlValue` is a [`KdlValue::Base10Float`],
|
/// Returns `Some(f64)` if the `KdlValue` is a [`KdlValue::Float`],
|
||||||
/// otherwise returns `None`.
|
/// otherwise returns `None`.
|
||||||
pub fn as_f64(&self) -> Option<f64> {
|
pub fn as_float(&self) -> Option<f64> {
|
||||||
if let Self::Base10Float(v) = self {
|
match self {
|
||||||
Some(*v)
|
Self::Float(i) => Some(*i),
|
||||||
} else {
|
_ => None,
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -222,79 +153,81 @@ impl KdlValue {
|
||||||
impl Display for KdlValue {
|
impl Display for KdlValue {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::RawString(_) => self.write_raw_string(f),
|
|
||||||
Self::String(_) => self.write_string(f),
|
Self::String(_) => self.write_string(f),
|
||||||
Self::Base2(value) => write!(f, "0b{:b}", value),
|
Self::Integer(value) => write!(f, "{:?}", value),
|
||||||
Self::Base8(value) => write!(f, "0o{:o}", value),
|
Self::Float(value) => write!(
|
||||||
Self::Base10(value) => write!(f, "{:?}", value),
|
|
||||||
Self::Base10Float(value) => write!(
|
|
||||||
f,
|
f,
|
||||||
"{:?}",
|
"{}",
|
||||||
if value == &f64::INFINITY {
|
if value == &f64::INFINITY {
|
||||||
f64::MAX
|
"#inf".into()
|
||||||
} else if value == &f64::NEG_INFINITY {
|
} else if value == &f64::NEG_INFINITY {
|
||||||
-f64::MAX
|
"#-inf".into()
|
||||||
} else if value.is_nan() {
|
} else if value.is_nan() {
|
||||||
0.0
|
"#nan".into()
|
||||||
} else {
|
} else {
|
||||||
*value
|
format!("{:?}", *value)
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
Self::Base16(value) => write!(f, "0x{:x}", value),
|
Self::Bool(value) => write!(f, "#{}", value),
|
||||||
Self::Bool(value) => write!(f, "{}", value),
|
Self::Null => write!(f, "#null"),
|
||||||
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 {
|
impl KdlValue {
|
||||||
fn write_string(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn write_string(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let string = self.as_string().unwrap();
|
let string = self.as_string().unwrap();
|
||||||
write!(f, "\"")?;
|
if !string.is_empty() && is_plain_ident(string) {
|
||||||
for char in string.chars() {
|
write!(f, "{string}")?;
|
||||||
match char {
|
} else {
|
||||||
'\\' | '"' => write!(f, "\\{}", char)?,
|
write!(f, "\"")?;
|
||||||
'\n' => write!(f, "\\n")?,
|
for char in string.chars() {
|
||||||
'\r' => write!(f, "\\r")?,
|
match char {
|
||||||
'\t' => write!(f, "\\t")?,
|
'\\' | '"' => write!(f, "\\{}", char)?,
|
||||||
'\u{08}' => write!(f, "\\b")?,
|
'\n' => write!(f, "\\n")?,
|
||||||
'\u{0C}' => write!(f, "\\f")?,
|
'\r' => write!(f, "\\r")?,
|
||||||
_ => write!(f, "{}", char)?,
|
'\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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<i64> for KdlValue {
|
impl From<i64> for KdlValue {
|
||||||
fn from(value: i64) -> Self {
|
fn from(value: i64) -> Self {
|
||||||
KdlValue::Base10(value)
|
KdlValue::Integer(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<f64> for KdlValue {
|
impl From<f64> for KdlValue {
|
||||||
fn from(value: f64) -> Self {
|
fn from(value: f64) -> Self {
|
||||||
KdlValue::Base10Float(value)
|
KdlValue::Float(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -334,34 +267,19 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn formatting() {
|
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());
|
let string = KdlValue::String("foo\n".into());
|
||||||
assert_eq!(format!("{}", string), r#""foo\n""#);
|
assert_eq!(format!("{}", string), r#""foo\n""#);
|
||||||
|
|
||||||
let base2 = KdlValue::Base2(0b1010_1010);
|
let integer = KdlValue::Integer(1234567890);
|
||||||
assert_eq!(format!("{}", base2), "0b10101010");
|
assert_eq!(format!("{}", integer), "1234567890");
|
||||||
|
|
||||||
let base8 = KdlValue::Base8(0o12345670);
|
let float = KdlValue::Float(1234567890.12345);
|
||||||
assert_eq!(format!("{}", base8), "0o12345670");
|
assert_eq!(format!("{}", float), "1234567890.12345");
|
||||||
|
|
||||||
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 boolean = KdlValue::Bool(true);
|
let boolean = KdlValue::Bool(true);
|
||||||
assert_eq!(format!("{}", boolean), "true");
|
assert_eq!(format!("{}", boolean), "#true");
|
||||||
|
|
||||||
let null = KdlValue::Null;
|
let null = KdlValue::Null;
|
||||||
assert_eq!(format!("{}", null), "null");
|
assert_eq!(format!("{}", null), "#null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,42 @@ use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use kdl::{KdlDocument, KdlError, KdlIdentifier, KdlValue};
|
use kdl::{KdlDocument, KdlIdentifier, KdlParseFailure, KdlValue};
|
||||||
use miette::IntoDiagnostic;
|
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]
|
#[test]
|
||||||
fn spec_compliance() -> miette::Result<()> {
|
fn spec_compliance() -> miette::Result<()> {
|
||||||
|
|
@ -13,21 +47,37 @@ fn spec_compliance() -> miette::Result<()> {
|
||||||
.join("tests")
|
.join("tests")
|
||||||
.join("test_cases")
|
.join("test_cases")
|
||||||
.join("input");
|
.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();
|
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()?);
|
let src = normalize_line_endings(fs::read_to_string(&test_path).into_diagnostic()?);
|
||||||
println!("src: {}", src);
|
let res = src.parse();
|
||||||
let res: Result<KdlDocument, KdlError> = src.parse();
|
if let Err(e) = validate_res(res, &test_path, &src) {
|
||||||
validate_res(res, &test_path)?;
|
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 file_name = path.file_name().unwrap();
|
||||||
let expected_dir = path
|
let expected_dir = path
|
||||||
.parent()
|
.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 expected_path = expected_dir.join(file_name);
|
||||||
let underscored = expected_dir.join(format!("_{}", PathBuf::from(file_name).display()));
|
let underscored = expected_dir.join(format!("_{}", PathBuf::from(file_name).display()));
|
||||||
if expected_path.exists() {
|
if expected_path.exists() {
|
||||||
let doc = res?;
|
let doc = res.map_err(|e| ComplianceDiagnostic::KdlParseFailure(path.into(), e))?;
|
||||||
let expected =
|
let expected = normalize_line_endings(fs::read_to_string(&expected_path)?);
|
||||||
normalize_line_endings(fs::read_to_string(&expected_path).into_diagnostic()?);
|
let actual = stringify_to_expected(doc);
|
||||||
println!("expected: {}", expected);
|
if actual != expected {
|
||||||
let stringified = stringify_to_expected(doc);
|
return Err(ComplianceDiagnostic::ExpectationMismatch {
|
||||||
println!("stringified: {}", stringified);
|
file: path.into(),
|
||||||
assert_eq!(stringified, expected);
|
original: src.clone(),
|
||||||
|
expected,
|
||||||
|
actual,
|
||||||
|
});
|
||||||
|
}
|
||||||
} else if underscored.exists() {
|
} else if underscored.exists() {
|
||||||
println!(
|
eprintln!(
|
||||||
"skipped reserialization for {}",
|
"skipped reserialization for {}",
|
||||||
PathBuf::from(file_name).display()
|
PathBuf::from(file_name).display()
|
||||||
);
|
);
|
||||||
} else {
|
// } else {
|
||||||
assert!(res.is_err(), "parse should not have succeeded");
|
// res.map_err(|e| ComplianceDiagnostic::KdlParseFailure(path.into(), e))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -61,27 +115,14 @@ fn normalize_line_endings(src: String) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stringify_to_expected(mut doc: KdlDocument) -> String {
|
fn stringify_to_expected(mut doc: KdlDocument) -> String {
|
||||||
doc.fmt_no_comments();
|
doc.autoformat_no_comments();
|
||||||
normalize_numbers(&mut doc);
|
|
||||||
normalize_strings(&mut doc);
|
normalize_strings(&mut doc);
|
||||||
|
normalize_identifiers(&mut doc);
|
||||||
dedupe_props(&mut doc);
|
dedupe_props(&mut doc);
|
||||||
remove_empty_children(&mut doc);
|
remove_empty_children(&mut doc);
|
||||||
doc.to_string()
|
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) {
|
fn normalize_strings(doc: &mut KdlDocument) {
|
||||||
for node in doc.nodes_mut() {
|
for node in doc.nodes_mut() {
|
||||||
for entry in node.entries_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) {
|
fn dedupe_props(doc: &mut KdlDocument) {
|
||||||
for node in doc.nodes_mut() {
|
for node in doc.nodes_mut() {
|
||||||
let mut props = HashMap::<KdlIdentifier, Vec<usize>>::new();
|
let mut props = HashMap::<KdlIdentifier, Vec<usize>>::new();
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ fn build_and_format() {
|
||||||
|
|
||||||
let mut doc = KdlDocument::new();
|
let mut doc = KdlDocument::new();
|
||||||
doc.nodes_mut().push(a);
|
doc.nodes_mut().push(a);
|
||||||
doc.fmt();
|
doc.autoformat();
|
||||||
let fmt = doc.to_string();
|
let fmt = doc.to_string();
|
||||||
println!("{}", fmt);
|
println!("{}", fmt);
|
||||||
assert_eq!(
|
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
|
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