kdl-4.6.0/.cargo_vcs_info.json 0000644 00000000136 00000000001 0011615 0 ustar {
"git": {
"sha1": "232d253cbf015741a9d35b02008ef53b5dc99f70"
},
"path_in_vcs": ""
} kdl-4.6.0/.editorconfig 0000644 0000000 0000000 00000000345 00726746425 0013114 0 ustar 0000000 0000000 # EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
kdl-4.6.0/.github/FUNDING.yml 0000644 0000000 0000000 00000000101 00726746425 0013602 0 ustar 0000000 0000000 # These are supported funding model platforms
github: [zkat]
kdl-4.6.0/.github/workflows/ci.yml 0000644 0000000 0000000 00000002047 00726746425 0015153 0 ustar 0000000 0000000 name: CI
on: [push, pull_request]
env:
RUSTFLAGS: -Dwarnings
jobs:
fmt_and_docs:
name: Check fmt & build docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
components: rustfmt
override: true
- name: rustfmt
run: cargo fmt --all -- --check
- name: docs
run: cargo doc --no-deps
build_and_test:
name: Build & Test
runs-on: ${{ matrix.os }}
strategy:
matrix:
rust: [1.56.0, stable]
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:
- uses: actions/checkout@v1
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
components: clippy
override: true
- name: Clippy
run: cargo clippy --all -- -D warnings
- name: Run tests
run: cargo test --all --verbose
kdl-4.6.0/.gitignore 0000644 0000000 0000000 00000000025 00726746425 0012422 0 ustar 0000000 0000000 /target
Cargo.lock
kdl-4.6.0/CHANGELOG.md 0000644 0000000 0000000 00000012760 00726746425 0012254 0 ustar 0000000 0000000 # `kdl` Release Changelog
## 4.6.0 (2022-10-09)
### Features
* **errors:** Add better diagnostics for errant plain identifiers in nodes (#59) ([3ddbfec8](https://github.com/kdl-org/kdl-rs/commit/3ddbfec80ec18bc97d9df4004ad262dcdcf79e9b))
## 4.5.0 (2022-08-31)
### Features
* **spans:** add spans to most elements (#57) ([b17ef8e2](https://github.com/kdl-org/kdl-rs/commit/b17ef8e2c61b67cdc632f1772e18f6c7521dcfd8))
## 4.4.0 (2022-08-18)
### Features
* **deps:** bump miette ([8d0f36ce](https://github.com/kdl-org/kdl-rs/commit/8d0f36ceb1c5c1243bae3247b6c86bfa45083f19))
### Bug Fixes
* **formatting:** Fix formatting when decoration is not present (#56) ([2e9c0447](https://github.com/kdl-org/kdl-rs/commit/2e9c0447f9420e37d5fe46d2a42ec7b9f0646d90))
## 4.3.0 (2022-06-11)
### Features
* **fmt:** Add clear_fmt_recursive method (#45) ([cd2d6e42](https://github.com/kdl-org/kdl-rs/commit/cd2d6e42b19b801a43e78256dca1d856367349f4))
## 4.2.0 (2022-05-11)
### Features
* **entry:** Add accessors to entry type. (#43) ([afccf012](https://github.com/kdl-org/kdl-rs/commit/afccf012168dcab1de89f3737014ee8ee037785b))
## 4.1.1 (2022-04-28)
### Bug Fixes
* **compliance:** pull in spec test suite and fix issues (#40) ([58a40fdf](https://github.com/kdl-org/kdl-rs/commit/58a40fdf487b303f7466c93d84a4cd8a5665aa24))
## 4.1.0 (2022-04-24)
### Features
* **fmt:** shiny new comment-preserving formatter! (#38) ([12d373a1](https://github.com/kdl-org/kdl-rs/commit/12d373a1e0de6533e7722e3ecc69e7ddc0e59db9))
## 4.0.0 (2022-04-23)
Hello again!
kdl-rs 4.0.0 is a _complete_ rewrite, featuring a full-fledged
"document-oriented" parser: that is, formatting, whitespace, comments, etc,
are all preserved and can be programmatically manipulated. KDL documents are
fully round-trippable, without losing any of that human-written content!
This crate will, for the time being, not include a serde-based parser, but
there's also crates like [`knuffel`](https://crates.io/crates/knuffel) and
[`kaydle`](https://crates.io/crates/kaydle) now that do probide serde (or
serde-like) functionality. You should definitely check those out if you're
looking for that kind of workflow!
Please give this version a whirl if you've been curious about using KDL for
your own projects, and let me know what can be improved, or even what you love
about it!
### Features
* **api:** complete rewrite into document-oriented parser (#29) ([364ea617](https://github.com/kdl-org/kdl-rs/commit/364ea6173c0bcfc2f5e4b21e19120179f6a5c5ed))
* **BREAKING CHANGE**: Completely new API and bumped MSRV to 1.56.0.
* **tests:** add test for kdl-schema.kdl (#30) ([ad34cfd9](https://github.com/kdl-org/kdl-rs/commit/ad34cfd93a9e6d8018b8086821a3463b764fb363))
* **types:** add type annotation support (#31) ([16c82f1e](https://github.com/kdl-org/kdl-rs/commit/16c82f1ec18c221b0d98dfcfb805ed3642354f5b))
* **errors:** improve parsing errors and fix some bugs (#33) ([8ed6a5cd](https://github.com/kdl-org/kdl-rs/commit/8ed6a5cd068e60de03a0e14493383f2515b98f81))
* **clear_fmt:** add methods to clear formatting and reset it to default ([892bf06e](https://github.com/kdl-org/kdl-rs/commit/892bf06e69c746ea9711fe33979f28f937329672))
* **errors:** overhauled error reporting a ton ([d63f336d](https://github.com/kdl-org/kdl-rs/commit/d63f336d188eb15a4bd8c870e7ee37617923270a))
* **len:** add APIs to calculate component lengths (#36) ([177c42ca](https://github.com/kdl-org/kdl-rs/commit/177c42cae75d8a0d9985c26ea28cb4f1cf7077de))
### Bug Fixes
* **parse:** small parser tweaks + more tests ([1a8eb351](https://github.com/kdl-org/kdl-rs/commit/1a8eb351685dc368c55d992d719e6bad34398df2))
* **api:** remove obsolete type ([40b04418](https://github.com/kdl-org/kdl-rs/commit/40b04418c9dc9a8363c000e19bc22e54c0dae7e9))
## 3.0.0 (2021-09-16)
### Features
* **spec:** update parser to handle KDL 1.0.0 ([f811c5c8](https://github.com/kdl-org/kdl-rs/commit/f811c5c89c18cb02cc3e7bdd8c872ea42308ae3e))
* **BREAKING CHANGE**: Various things have changed in the process of moving to KDL 1.0.0. Please test your stuff
## 2.0.0 (2021-09-16)
### Features
* **license:** change license to Apache-2.0 ([0dbf75c7](https://github.com/kdl-org/kdl-rs/commit/0dbf75c78eb918b6966aae27fb1d7591791f15de))
* **BREAKING CHANGE**: This is a significant licensing change. Please review.
## 1.1.0 (2021-05-08)
It's been a while! This release brings kdl-rs much closer in sync with the
actual spec.
#### Bug Fixes
* **deps:** Remove nom dependency on `bitvec` and `lexical` (#14) ([9bc5363b](https://github.com/kdl/kdl-rs/commit/9bc5363bb5b8e4ae39e250f2facbfcdf4557f11b))
* **numbers:** Fix parsing of non-integer and non-decimal numbers (#13) ([c1b7c25c](https://github.com/kdl/kdl-rs/commit/c1b7c25c0095ac2bd8acf06f6834c734a42b4470))
#### Features
* **display:** implemented Display for KdlNode (#6) ([b8c8b527](https://github.com/kdl/kdl-rs/commit/b8c8b52748747d80215ee0c3dea73e260e133af2))
* **docs:** Add documentation for the entire crate (#16) ([94190697](https://github.com/kdl/kdl-rs/commit/94190697d8ad676f9b879dcc90f8eb03266c3ef8))
* **identifier:** much larger character set for identifiers (not just alphanumeric), to match spec more closely (#7) ([95a1ee3e](https://github.com/kdl/kdl-rs/commit/95a1ee3e57156507c3bf8a8035017d4836e49a01))
## 1.0.0 (2020-12-19)
Initial Release! �
kdl-4.6.0/CODE_OF_CONDUCT.md 0000644 0000000 0000000 00000015461 00726746425 0013243 0 ustar 0000000 0000000 # Code of Conduct
## When Something Happens
If you see behavoir that makes you feel unsafe or unwelcome or otherwise uncomfortable, follow these steps:
1. Let the person know that what they did is not appropriate and ask them to stop and/or edit their message(s) or commits.
2. That person should immediately stop the behavior and correct the issue.
3. If this doesn’t happen, or if you're uncomfortable speaking up, [contact the maintainers](#contacting-maintainers).
4. As soon as available, a maintainer will look into the issue, and take [further action (see below)](#further-enforcement), starting with a warning, then temporary block, then long-term repo or organization ban.
**The maintainer team will prioritize the well-being and comfort of those affected over the comfort of the offending party.** See [some examples below](#enforcement-examples).
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers of this project pledge to making participation in our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, technical preferences, nationality, personal appearance, race, religion, or sexual identity and orientation.
This commitment means that inappropriate behavior can lead to intervention. This includes our intention to address issues with [missing stairs](https://en.wikipedia.org/wiki/Missing_stair) who may not have explicitly violated any written-down rules but might still be disrupting the community.
## Scope
This Code of Conduct applies both within spaces involving this project and in other spaces involving community members. This includes the repository, its Pull Requests and Issue tracker, its Twitter community, private email communications in the context of the project, and any events where members of the project are participating, as well as adjacent communities and venues affecting the project's members.
Depending on the violation, the maintainers may decide that violations of this code of conduct that have happened outside of the scope of the community may deem an individual unwelcome, and take appropriate action to maintain the comfort and safety of its members.
## Contacting Maintainers
- [Kat Marchán ](mailto:coc@zkat.tech)
## Further Enforcement
If you've already followed the [initial enforcement steps](#enforcement), these are the steps maintainers will take for further enforcement, as needed:
1. Repeat the request to stop.
2. If the person doubles down, they will have offending messages removed or edited by a maintainers given an official warning. The PR or Issue may be locked.
3. If the behavior continues or is repeated later, the person will be blocked from participating for 24 hours.
4. If the behavior continues or is repeated after the temporary block, a long-term (6-12mo) ban will be used.
On top of this, maintainers may remove any offending messages, images, contributions, etc, as they deem necessary.
Maintainers reserve full rights to skip any of these steps, at their discretion, if the violation is considered to be a serious and/or immediate threat to the health and well-being of members of the community. These include any threats, serious physical or verbal attacks, and other such behavior that would be completely unacceptable in any social setting that puts our members at risk.
Members expelled from events or venues with any sort of paid attendance will not be refunded.
## Who Watches the Watchers?
Maintainers and other leaders who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. These may include anything from removal from the maintainer team to a permanent ban from the community.
Additionally, as a project hosted on both GitHub, [their Community Guidielines may be applied to maintainers of this project](https://help.github.com/articles/github-community-guidelines/), externally of this project's procedures.
## Enforcement Examples
### The Best Case
The vast majority of situations work out like this. This interaction is common, and generally positive.
> Alex: "Yeah I used X and it was really crazy!"
> Patt (not a maintainer): "Hey, could you not use that word? What about 'ridiculous' instead?"
> Alex: "oh sorry, sure." -> edits old comment to say "it was really confusing!"
### The Maintainer Case
Sometimes, though, you need to get maintainers involved. Maintainers will do their best to resolve conflicts, but people who were harmed by something **will take priority**.
> Patt: "Honestly, sometimes I just really hate using $library and anyone who uses it probably sucks at their job."
> Alex: "Whoa there, could you dial it back a bit? There's a CoC thing about attacking folks' tech use like that."
> Patt: "I'm not attacking anyone, what's your problem?"
> Alex: "@maintainers hey uh. Can someone look at this issue? Patt is getting a bit aggro. I tried to nudge them about it, but nope."
> KeeperOfCommitBits: (on issue) "Hey Patt, maintainer here. Could you tone it down? This sort of attack is really not okay in this space."
> Patt: "Leave me alone I haven't said anything bad wtf is wrong with you."
> KeeperOfCommitBits: (deletes user's comment), "@patt I mean it. Please refer to the CoC over at (URL to this CoC) if you have questions, but you can consider this an actual warning. I'd appreciate it if you reworded your messages in this thread, since they made folks there uncomfortable. Let's try and be kind, yeah?"
> Patt: "@keeperofbits Okay sorry. I'm just frustrated and I'm kinda burnt out and I guess I got carried away. I'll DM Alex a note apologizing and edit my messages. Sorry for the trouble."
> KeeperOfCommitBits: "@patt Thanks for that. I hear you on the stress. Burnout sucks :/. Have a good one!"
### The Nope Case
> PepeTheFrog🐸: "Hi, I am a literal actual nazi and I think white supremacists are quite fashionable."
> Patt: "NOOOOPE. OH NOPE NOPE."
> Alex: "JFC NO. NOPE. @keeperofbits NOPE NOPE LOOK HERE"
> KeeperOfCommitBits: "👀 Nope. NOPE NOPE NOPE. 🔥"
> PepeTheFrog🐸 has been banned from all organization or user repositories belonging to KeeperOfCommitBits.
## Attribution
This Code of Conduct was generated using [WeAllJS Code of Conduct Generator](https://npm.im/weallbehave), which is based on the [WeAllJS Code of
Conduct](https://wealljs.org/code-of-conduct), which is itself based on
[Contributor Covenant](http://contributor-covenant.org), version 1.4, available
at
[http://contributor-covenant.org/version/1/4](http://contributor-covenant.org/version/1/4),
and the LGBTQ in Technology Slack [Code of
Conduct](http://lgbtq.technology/coc.html).
kdl-4.6.0/Cargo.lock 0000644 00000006062 00000000001 0007574 0 ustar # This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "kdl"
version = "4.6.0"
dependencies = [
"miette",
"nom",
"thiserror",
]
[[package]]
name = "memchr"
version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
[[package]]
name = "miette"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28d6092d7e94a90bb9ea8e6c26c99d5d112d49dda2afdb4f7ea8cf09e1a5a6d"
dependencies = [
"miette-derive",
"once_cell",
"thiserror",
"unicode-width",
]
[[package]]
name = "miette-derive"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f2485ed7d1fe80704928e3eb86387439609bd0c6bb96db8208daa364cfd1e09"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "once_cell"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
[[package]]
name = "proc-macro2"
version = "1.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "1.0.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "thiserror"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-width"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
kdl-4.6.0/Cargo.toml 0000644 00000002164 00000000001 0007616 0 ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
name = "kdl"
version = "4.6.0"
authors = [
"Kat Marchán ",
"KDL Community",
]
description = "Document-oriented KDL parser and API. Allows formatting/whitespace/comment-preserving parsing and modification of KDL text."
homepage = "https://kdl.dev"
readme = "README.md"
keywords = [
"kdl",
"document",
"serialization",
"config",
]
license = "Apache-2.0"
repository = "https://github.com/kdl-org/kdl-rs"
resolver = "2"
[dependencies.miette]
version = "5.3.0"
[dependencies.nom]
version = "7.1.1"
default-features = false
[dependencies.thiserror]
version = "1.0.30"
[features]
default = ["span"]
span = []
kdl-4.6.0/Cargo.toml.orig 0000644 00000001113 00000000001 0010546 0 ustar [package]
name = "kdl"
version = "4.6.0"
description = "Document-oriented KDL parser and API. Allows formatting/whitespace/comment-preserving parsing and modification of KDL text."
authors = ["Kat Marchán ", "KDL Community"]
license = "Apache-2.0"
readme = "README.md"
homepage = "https://kdl.dev"
repository = "https://github.com/kdl-org/kdl-rs"
keywords = ["kdl", "document", "serialization", "config"]
edition = "2021"
[features]
default = ["span"]
span = []
[dependencies]
miette = "5.3.0"
nom = { version = "7.1.1", default-features = false }
thiserror = "1.0.30"
kdl-4.6.0/Cargo.toml.orig 0000644 0000000 0000000 00000001113 00726746425 0013320 0 ustar 0000000 0000000 [package]
name = "kdl"
version = "4.6.0"
description = "Document-oriented KDL parser and API. Allows formatting/whitespace/comment-preserving parsing and modification of KDL text."
authors = ["Kat Marchán ", "KDL Community"]
license = "Apache-2.0"
readme = "README.md"
homepage = "https://kdl.dev"
repository = "https://github.com/kdl-org/kdl-rs"
keywords = ["kdl", "document", "serialization", "config"]
edition = "2021"
[features]
default = ["span"]
span = []
[dependencies]
miette = "5.3.0"
nom = { version = "7.1.1", default-features = false }
thiserror = "1.0.30"
kdl-4.6.0/LICENSE 0000644 0000000 0000000 00000024654 00726746425 0011455 0 ustar 0000000 0000000 Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/ TERMS
AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the
union of the acting entity and all other entities that control, are controlled
by, or are under common control with that entity. For the purposes of this
definition, "control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or otherwise, or (ii)
ownership of fifty percent (50%) or more of the outstanding shares, or (iii)
beneficial ownership of such entity.
"You" (or "Your") shall mean
an individual or Legal Entity exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation source, and
configuration files.
"Object" form shall mean any form resulting
from mechanical transformation or translation of a Source form, including but not
limited to compiled object code, generated documentation, and conversions to
other media types.
"Work" shall mean the work of authorship,
whether in Source or Object form, made available under the License, as indicated
by a copyright notice that is included in or attached to the work (an example is
provided in the Appendix below).
"Derivative Works" shall mean any
work, whether in Source or Object form, that is based on (or derived from) the
Work and for which the editorial revisions, annotations, elaborations, or other
modifications represent, as a whole, an original work of authorship. For the
purposes of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of, the Work
and Derivative Works thereof.
"Contribution" shall mean any work
of authorship, including the original version of the Work and any modifications
or additions to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner or by an
individual or Legal Entity authorized to submit on behalf of the copyright owner.
For the purposes of this definition, "submitted" means any form of electronic,
verbal, or written communication sent to the Licensor or its representatives,
including but not limited to communication on electronic mailing lists, source
code control systems, and issue tracking systems that are managed by, or on
behalf of, the Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise designated in
writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of
whom a Contribution has been received by Licensor and subsequently incorporated
within the Work.
2. Grant of Copyright License. Subject to the terms and
conditions of this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license
to reproduce, prepare Derivative Works of, publicly display, publicly perform,
sublicense, and distribute the Work and such Derivative Works in Source or Object
form.
3. Grant of Patent License. Subject to the terms and conditions of this
License, each Contributor hereby grants to You a perpetual, worldwide,
non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this
section) patent license to make, have made, use, offer to sell, sell, import, and
otherwise transfer the Work, where such license applies only to those patent
claims licensable by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s) with the Work to
which such Contribution(s) was submitted. If You institute patent litigation
against any entity (including a cross-claim or counterclaim in a lawsuit)
alleging that the Work or a Contribution incorporated within the Work constitutes
direct or contributory patent infringement, then any patent licenses granted to
You under this License for that Work shall terminate as of the date such
litigation is filed.
4. Redistribution. You may reproduce and distribute
copies of the Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You meet the following
conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any
modified files to carry prominent notices stating that You changed the files;
and
(c) You must retain, in the Source form of any Derivative Works that
You distribute, all copyright, patent, trademark, and attribution notices from
the Source form of the Work, excluding those notices that do not pertain to any
part of the Derivative Works; and
(d) If the Work includes a "NOTICE" text
file as part of its distribution, then any Derivative Works that You distribute
must include a readable copy of the attribution notices contained within such
NOTICE file, excluding those notices that do not pertain to any part of the
Derivative Works, in at least one of the following places: within a NOTICE text
file distributed as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or, within a display
generated by the Derivative Works, if and wherever such third-party notices
normally appear. The contents of the NOTICE file are for informational purposes
only and do not modify the License. You may add Your own attribution notices
within Derivative Works that You distribute, alongside or as an addendum to the
NOTICE text from the Work, provided that such additional attribution notices
cannot be construed as modifying the License.
You may add Your own
copyright statement to Your modifications and may provide additional or different
license terms and conditions for use, reproduction, or distribution of Your
modifications, or for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with the conditions
stated in this License.
5. Submission of Contributions. Unless You explicitly
state otherwise, any Contribution intentionally submitted for inclusion in the
Work by You to the Licensor shall be under the terms and conditions of this
License, without any additional terms or conditions. Notwithstanding the above,
nothing herein shall supersede or modify the terms of any separate license
agreement you may have executed with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade names,
trademarks, service marks, or product names of the Licensor, except as required
for reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless
required by applicable law or agreed to in writing, Licensor provides the Work
(and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including,
without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT,
MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible
for determining the appropriateness of using or redistributing the Work and
assume any risks associated with Your exercise of permissions under this
License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise, unless required
by applicable law (such as deliberate and grossly negligent acts) or agreed to in
writing, shall any Contributor be liable to You for damages, including any
direct, indirect, special, incidental, or consequential damages of any character
arising as a result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill, work stoppage,
computer failure or malfunction, or any and all other commercial damages or
losses), even if such Contributor has been advised of the possibility of such
damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer, and charge a fee
for, acceptance of support, warranty, indemnity, or other liability obligations
and/or rights consistent with this License. However, in accepting such
obligations, You may act only on Your own behalf and on Your sole responsibility,
not on behalf of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability incurred by, or
claims asserted against, such Contributor by reason of your accepting any such
warranty or additional liability. END OF TERMS AND CONDITIONS
APPENDIX: How to
apply the Apache License to your work.
To apply the Apache License to your work,
attach the following boilerplate notice, with the fields enclosed by brackets
"[]" replaced with your own identifying information. (Don't include the
brackets!) The text should be enclosed in the appropriate comment syntax for the
file format. We also recommend that a file or class name and description of
purpose be included on the same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] Kat
Marchán
Licensed under the Apache License, Version 2.0 (the "License");
you may
not use this file except in compliance with the License.
You may obtain a copy
of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by
applicable law or agreed to in writing, software
distributed under the License
is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.
See the License for the specific language
governing permissions and
limitations under the License. kdl-4.6.0/Makefile.toml 0000644 0000000 0000000 00000000575 00726746425 0013056 0 ustar 0000000 0000000 [tasks.changelog]
workspace=false
install_crate="git-cliff"
command = "git-cliff"
args = ["--prepend", "CHANGELOG.md", "-u", "--tag", "${@}"]
[tasks.release]
workspace=false
install_crate="cargo-release"
command = "cargo"
args = ["release", "--workspace", "${@}"]
[tasks.readme]
workspace=false
install_crate="cargo-readme"
command = "cargo"
args = ["readme", "-o", "README.md"]
kdl-4.6.0/README.md 0000644 0000000 0000000 00000007476 00726746425 0011732 0 ustar 0000000 0000000 # `kdl`
`kdl` is a "document-oriented" parser and API for the [KDL Document
Language](https://kdl.dev), a node-based, human-friendly configuration and
serialization format. Unlike serde-based implementations, this crate
preserves formatting when editing, as well as when inserting or changing
values with custom formatting. This is most useful when working with
human-maintained KDL files.
You can think of this crate as
[`toml_edit`](https://crates.io/crates/toml_edit), but for KDL.
If you don't care about formatting or programmatic manipulation, you might
check out [`knuffel`](https://crates.io/crates/knuffel) instead for serde
(or serde-like) parsing.
### Example
```rust
use kdl::KdlDocument;
let doc_str = r#"
hello 1 2 3
world prop="value" {
child 1
child 2
}
"#;
let doc: KdlDocument = doc_str.parse().expect("failed to parse KDL");
assert_eq!(
doc.get_args("hello"),
vec![&1.into(), &2.into(), &3.into()]
);
assert_eq!(
doc.get("world").map(|node| &node["prop"]),
Some(&"value".into())
);
// Documents fully roundtrip:
assert_eq!(doc.to_string(), doc_str);
```
### Controlling Formatting
By default, everything is created with default formatting. You can parse
items manually to provide custom representations, comments, etc:
```rust
let node_str = r#"
// indented comment
"formatted" 1 /* comment */ \
2;
"#;
let mut doc = kdl::KdlDocument::new();
doc.nodes_mut().push(node_str.parse().unwrap());
assert_eq!(&doc.to_string(), node_str);
```
[`KdlDocument`], [`KdlNode`], [`KdlEntry`], and [`KdlIdentifier`] can all
be parsed and managed this way.
### Error Reporting
[`KdlError`] implements [`miette::Diagnostic`] and can be used to display
detailed, pretty-printed diagnostic messages when using [`miette::Result`]
and the `"fancy"` feature flag for `miette`:
```toml
# Cargo.toml
[dependencies]
miette = { version = "x.y.z", features = ["fancy"] }
```
```rust
fn main() -> miette::Result<()> {
"foo 1.".parse::()?;
Ok(())
}
```
This will display a message like:
```
Error:
× Expected valid value.
╭────
1 │ foo 1.
· ─┬
· ╰── invalid float
╰────
help: Floating point numbers must be base 10, and have numbers after the decimal point.
```
### Quirks
#### Properties
Multiple properties with the same name are allowed, and all duplicated
**will be preserved**, meaning those documents will correctly round-trip.
When using `node.get()`/`node["key"]` & company, the _last_ property with
that name's value will be returned.
#### Numbers
KDL itself does not specify a particular representation for numbers and
accepts just about anything valid, no matter how large and how small. This
means a few things:
* Numbers without a decimal point are interpreted as u64.
* Numbers with a decimal point are interpreted as f64.
* Floating point numbers that evaluate to f64::INFINITY or
f64::NEG_INFINITY or NaN will be represented as such in the values,
instead of the original numbers.
* A similar restriction applies to overflowed u64 values.
* The original _representation_ of these numbers will be preserved, unless
you `doc.fmt()`, in which case the original representation will be
thrown away and the actual value will be used when serializing.
### License
The code in this repository is covered by [the Apache-2.0
License](LICENSE.md).
[`KdlDocument`]: https://docs.rs/kdl/latest/kdl/struct.KdlDocument.html
[`KdlNode`]: https://docs.rs/kdl/latest/kdl/struct.KdlNode.html
[`KdlEntry`]: https://docs.rs/kdl/latest/kdl/struct.KdlEntry.html
[`KdlIdentifier`]: https://docs.rs/kdl/latest/kdl/struct.KdlIdentifier.html
[`KdlError`]: https://docs.rs/kdl/latest/kdl/struct.KdlError.html
[`miette::Diagnostic`]: https://docs.rs/miette/latest/miette/trait.Diagnostic.html
[`miette::Result`]: https://docs.rs/miette/latest/miette/type.Result.html
kdl-4.6.0/README.tpl 0000644 0000000 0000000 00000001020 00726746425 0012104 0 ustar 0000000 0000000 # `{{crate}}`
{{readme}}
[`KdlDocument`]: https://docs.rs/kdl/latest/kdl/struct.KdlDocument.html
[`KdlNode`]: https://docs.rs/kdl/latest/kdl/struct.KdlNode.html
[`KdlEntry`]: https://docs.rs/kdl/latest/kdl/struct.KdlEntry.html
[`KdlIdentifier`]: https://docs.rs/kdl/latest/kdl/struct.KdlIdentifier.html
[`KdlError`]: https://docs.rs/kdl/latest/kdl/struct.KdlError.html
[`miette::Diagnostic`]: https://docs.rs/miette/latest/miette/trait.Diagnostic.html
[`miette::Result`]: https://docs.rs/miette/latest/miette/type.Result.html
kdl-4.6.0/cliff.toml 0000644 0000000 0000000 00000003552 00726746425 0012422 0 ustar 0000000 0000000 # configuration file for git-cliff (0.1.0)
[changelog]
# changelog header
header = """
# `kdl` Release Changelog
"""
# template for the changelog body
# https://tera.netlify.app/docs/#introduction
body = """
{% if version %}\
## {{ version | replace(from="v", to="") }} ({{ timestamp | date(format="%Y-%m-%d") }})
{% else %}\
## Unreleased
{% endif %}\
{% for group, commits in commits | filter(attribute="scope") | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
{% if commit.scope %}\
* **{{ commit.scope }}:** {{ commit.message }} ([{{ commit.id | truncate(length=8, end="") }}](https://github.com/kdl-org/kdl-rs/commit/{{ commit.id }}))
{%- if commit.breaking %}
* **BREAKING CHANGE**: {{ commit.breaking_description }}
{%- endif %}\
{% endif %}\
{% endfor %}
{% endfor %}
"""
# remove the leading and trailing whitespace from the template
trim = false
# changelog footer
# footer = """
#
# """
[git]
# allow only conventional commits
# https://www.conventionalcommits.org
conventional_commits = true
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat*", group = "Features"},
{ message = "^fix*", group = "Bug Fixes"},
{ message = "^doc*", group = "Documentation"},
{ message = "^perf*", group = "Performance"},
{ message = "^refactor*", group = "Refactor"},
{ message = "^style*", group = "Styling"},
{ message = "^test*", group = "Testing"},
{ message = "^chore\\(release\\): prepare for*", skip = true},
{ message = "^chore*", group = "Miscellaneous Tasks"},
{ body = ".*security", group = "Security"},
]
# filter out the commits that are not matched by commit parsers
filter_commits = true
# glob pattern for matching git tags
# tag_pattern = "v?[0-9]*"
# regex for skipping tags
# skip_tags = "v0.1.0-beta.1"
kdl-4.6.0/src/document.rs 0000644 0000000 0000000 00000051325 00726746425 0013416 0 ustar 0000000 0000000 #[cfg(feature = "span")]
use miette::SourceSpan;
use std::{fmt::Display, str::FromStr};
use crate::{parser, KdlError, KdlNode, KdlValue};
/// Represents a KDL
/// [`Document`](https://github.com/kdl-org/kdl/blob/main/SPEC.md#document).
///
/// This type is also used to manage a [`KdlNode`]'s [`Children
/// Block`](https://github.com/kdl-org/kdl/blob/main/SPEC.md#children-block),
/// when present.
///
/// # Examples
///
/// The easiest way to create a `KdlDocument` is to parse it:
/// ```rust
/// # use kdl::KdlDocument;
/// let kdl: KdlDocument = "foo 1 2 3\nbar 4 5 6".parse().expect("parse failed");
/// ```
#[derive(Debug, Clone)]
pub struct KdlDocument {
pub(crate) leading: Option,
pub(crate) nodes: Vec,
pub(crate) trailing: Option,
#[cfg(feature = "span")]
pub(crate) span: SourceSpan,
}
impl PartialEq for KdlDocument {
fn eq(&self, other: &Self) -> bool {
self.leading == other.leading
&& self.nodes == other.nodes
&& self.trailing == other.trailing
// Intentionally omitted: self.span == other.span
}
}
impl Default for KdlDocument {
fn default() -> Self {
Self {
leading: Default::default(),
nodes: Default::default(),
trailing: Default::default(),
#[cfg(feature = "span")]
span: SourceSpan::from(0..0),
}
}
}
impl KdlDocument {
/// Creates a new Document.
pub fn new() -> Self {
Default::default()
}
/// Gets this document's span.
///
/// This value will be properly initialized when created via [`KdlDocument::parse`]
/// but may become invalidated if the document is mutated. We do not currently
/// guarantee this to yield any particularly consistent results at that point.
#[cfg(feature = "span")]
pub fn span(&self) -> &SourceSpan {
&self.span
}
/// Gets a mutable reference to this document's span.
#[cfg(feature = "span")]
pub fn span_mut(&mut self) -> &mut SourceSpan {
&mut self.span
}
/// Sets this document's span.
#[cfg(feature = "span")]
pub fn set_span(&mut self, span: impl Into) {
self.span = span.into();
}
/// Gets the first child node with a matching name.
pub fn get(&self, name: &str) -> Option<&KdlNode> {
self.nodes.iter().find(move |n| n.name().value() == name)
}
/// Gets a reference to the first child node with a matching name.
pub fn get_mut(&mut self, name: &str) -> Option<&mut KdlNode> {
self.nodes
.iter_mut()
.find(move |n| n.name().value() == name)
}
/// Gets the first argument (value) of the first child node with a
/// matching name. This is a shorthand utility for cases where a document
/// is being used as a key/value store.
///
/// # Examples
///
/// Given a document like this:
/// ```kdl
/// foo 1
/// bar false
/// ```
///
/// You can fetch the value of `foo` in a single call like this:
/// ```rust
/// # use kdl::{KdlDocument, KdlValue};
/// # let doc: KdlDocument = "foo 1\nbar false".parse().unwrap();
/// assert_eq!(doc.get_arg("foo"), Some(&1.into()));
/// ```
pub fn get_arg(&self, name: &str) -> Option<&KdlValue> {
self.get(name)
.and_then(|node| node.get(0))
.map(|e| e.value())
}
/// Gets the all node arguments (value) of the first child node with a
/// matching name. This is a shorthand utility for cases where a document
/// is being used as a key/value store and the value is expected to be
/// array-ish.
///
/// If a node has no arguments, this will return an empty array.
///
/// # Examples
///
/// Given a document like this:
/// ```kdl
/// foo 1 2 3
/// bar false
/// ```
///
/// You can fetch the arguments for `foo` in a single call like this:
/// ```rust
/// # use kdl::{KdlDocument, KdlValue};
/// # let doc: KdlDocument = "foo 1 2 3\nbar false".parse().unwrap();
/// assert_eq!(doc.get_args("foo"), vec![&1.into(), &2.into(), &3.into()]);
/// ```
pub fn get_args(&self, name: &str) -> Vec<&KdlValue> {
self.get(name)
.map(|n| n.entries())
.unwrap_or_default()
.iter()
.filter(|e| e.name().is_none())
.map(|e| e.value())
.collect()
}
/// Gets a mutable reference to the first argument (value) of the first
/// child node with a matching name. This is a shorthand utility for cases
/// where a document is being used as a key/value store.
pub fn get_arg_mut(&mut self, name: &str) -> Option<&mut KdlValue> {
self.get_mut(name)
.and_then(|node| node.get_mut(0))
.map(|e| e.value_mut())
}
/// This utility makes it easy to interact with a KDL convention where
/// child nodes named `-` are treated as array-ish values.
///
/// # Examples
///
/// Given a document like this:
/// ```kdl
/// foo {
/// - 1
/// - 2
/// - false
/// }
/// ```
///
/// You can fetch the dashed child values of `foo` in a single call like this:
/// ```rust
/// # use kdl::{KdlDocument, KdlValue};
/// # let doc: KdlDocument = "foo {\n - 1\n - 2\n - false\n}".parse().unwrap();
/// assert_eq!(doc.get_dash_vals("foo"), vec![&1.into(), &2.into(), &false.into()]);
/// ```
pub fn get_dash_vals(&self, name: &str) -> Vec<&KdlValue> {
self.get(name)
.and_then(|n| n.children())
.map(|doc| doc.nodes())
.unwrap_or_default()
.iter()
.filter(|e| e.name().value() == "-")
.map(|e| e.get(0))
.filter(|v| v.is_some())
.map(|v| v.unwrap().value())
.collect()
}
/// Returns a reference to this document's child nodes.
pub fn nodes(&self) -> &[KdlNode] {
&self.nodes
}
/// Returns a mutable reference to this document's child nodes.
pub fn nodes_mut(&mut self) -> &mut Vec {
&mut self.nodes
}
/// Gets leading text (whitespace, comments) for this KdlDocument.
pub fn leading(&self) -> Option<&str> {
self.leading.as_deref()
}
/// Sets leading text (whitespace, comments) for this KdlDocument.
pub fn set_leading(&mut self, leading: impl Into) {
self.leading = Some(leading.into());
}
/// Gets trailing text (whitespace, comments) for this KdlDocument.
pub fn trailing(&self) -> Option<&str> {
self.trailing.as_deref()
}
/// Sets trailing text (whitespace, comments) for this KdlDocument.
pub fn set_trailing(&mut self, trailing: impl Into) {
self.trailing = Some(trailing.into());
}
/// Length of this document when rendered as a string.
pub fn len(&self) -> usize {
format!("{}", self).len()
}
/// Returns true if this document is completely empty (including whitespace)
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Clears leading and trailing text (whitespace, comments). `KdlNode`s in
/// this document will be unaffected.
///
/// If you need to clear the `KdlNode`s, use [`Self::clear_fmt_recursive`].
pub fn clear_fmt(&mut self) {
self.leading = None;
self.trailing = None;
}
/// Clears leading and trailing text (whitespace, comments), also clearing
/// all the `KdlNode`s in the document.
pub fn clear_fmt_recursive(&mut self) {
self.clear_fmt();
for node in self.nodes.iter_mut() {
node.clear_fmt_recursive();
}
}
/// Auto-formats this Document, making everything nice while preserving
/// comments.
pub fn fmt(&mut self) {
self.fmt_impl(0, false);
}
/// Formats the document and removes all comments from the document.
pub fn fmt_no_comments(&mut self) {
self.fmt_impl(0, true);
}
}
impl Display for KdlDocument {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.stringify(f, 0)
}
}
impl KdlDocument {
pub(crate) fn fmt_impl(&mut self, indent: usize, no_comments: bool) {
if let Some(s) = self.leading.as_mut() {
crate::fmt::fmt_leading(s, indent, no_comments);
}
let mut has_nodes = false;
for node in &mut self.nodes {
has_nodes = true;
node.fmt_impl(indent, no_comments);
}
if let Some(s) = self.trailing.as_mut() {
crate::fmt::fmt_trailing(s, no_comments);
if !has_nodes {
s.push('\n');
}
}
}
pub(crate) fn stringify(
&self,
f: &mut std::fmt::Formatter<'_>,
indent: usize,
) -> std::fmt::Result {
if let Some(leading) = &self.leading {
write!(f, "{}", leading)?;
}
for node in &self.nodes {
node.stringify(f, indent)?;
if node.trailing.is_none() {
writeln!(f)?;
}
}
if let Some(trailing) = &self.trailing {
write!(f, "{}", trailing)?;
}
Ok(())
}
}
impl IntoIterator for KdlDocument {
type Item = KdlNode;
type IntoIter = std::vec::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.nodes.into_iter()
}
}
impl FromStr for KdlDocument {
type Err = KdlError;
fn from_str(input: &str) -> Result {
let kdl_parser = parser::KdlParser::new(input);
kdl_parser.parse(parser::document(&kdl_parser))
}
}
#[cfg(test)]
mod test {
#[cfg(feature = "span")]
use crate::KdlIdentifier;
use crate::{KdlEntry, KdlValue};
use super::*;
#[test]
fn canonical_clear_fmt() -> miette::Result<()> {
let left_src = r#"
// There is a node here
first_node /*with cool comments, too */ param=1.03e2 /-"commented" "argument" {
// With nested nodes too
nested 1 2 3
nested_2 "hi" "world" // this one is cool
}
second_node param=153 { nested one=1 two=2; }"#;
let right_src = r#"
first_node param=103.0 "argument" {
// Different indentation, because
// Why not
nested 1 2 3
nested_2 "hi" /* actually, "hello" */ "world"
}
// There is a node here
second_node /* This time, the comment is here */ param=153 {
nested one=1 two=2
}"#;
let mut left_doc: KdlDocument = left_src.parse()?;
let mut right_doc: KdlDocument = right_src.parse()?;
assert_ne!(left_doc, right_doc);
left_doc.clear_fmt_recursive();
right_doc.clear_fmt_recursive();
assert_eq!(left_doc, right_doc);
Ok(())
}
#[test]
fn parsing() -> miette::Result<()> {
let src = "
// This is the first node
foo 1 2 \"three\" null true bar=\"baz\" {
- 1
- 2
- \"three\"
(mytype)something (\"name\")\"else\"\r
}
null_id null_prop=null
true_id true_prop=null
+false true
bar \"indented\" // trailing whitespace after this\t
/*
Some random comment
*/
a; b; c;
/-commented \"node\"
another /*foo*/ \"node\" /-1 /*bar*/ null;
final;";
let mut doc: KdlDocument = src.parse()?;
assert_eq!(doc.leading, Some("".into()));
assert_eq!(doc.get_arg("foo"), Some(&1.into()));
assert_eq!(
doc.get_dash_vals("foo"),
vec![&1.into(), &2.into(), &"three".into()]
);
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[2], &"three".into());
assert_eq!(&foo["bar"], &"baz".into());
assert_eq!(
foo.children().unwrap().get_arg("something"),
Some(&"else".into())
);
assert_eq!(doc.get_arg("another"), Some(&"node".into()));
let null = doc.get("null_id").expect("expected a null_id node");
assert_eq!(&null["null_prop"], &KdlValue::Null);
let tru = doc.get("true_id").expect("expected a true_id node");
assert_eq!(&tru["true_prop"], &KdlValue::Null);
let plusfalse = doc.get("+false").expect("expected a +false node");
assert_eq!(&plusfalse[0], &KdlValue::Bool(true));
let bar = doc.get("bar").expect("expected a bar node");
assert_eq!(
format!("{}", bar),
"\n bar \"indented\" // trailing whitespace after this\t\n"
);
let a = doc.get("a").expect("expected a node");
assert_eq!(
format!("{}", a),
"/*\nSome random comment\n */\n\na; ".to_string()
);
let b = doc.get("b").expect("expected a node");
assert_eq!(format!("{}", b), "b; ".to_string());
// Round-tripping works.
assert_eq!(format!("{}", doc), src);
// Programmatic manipulation works.
let mut node: KdlNode = "new\n".parse()?;
// Manual entry parsing preserves formatting/reprs.
node.push("\"blah\"=0xDEADbeef".parse::()?);
doc.nodes_mut().push(node);
assert_eq!(
format!("{}", doc),
format!("{}new \"blah\"=0xDEADbeef\n", src)
);
Ok(())
}
#[test]
fn construction() {
let mut doc = KdlDocument::new();
doc.nodes_mut().push(KdlNode::new("foo"));
let mut bar = KdlNode::new("bar");
bar.insert("prop", "value");
bar.push(1);
bar.push(2);
bar.push(false);
bar.push(KdlValue::Null);
let subdoc = bar.ensure_children();
subdoc.nodes_mut().push(KdlNode::new("barchild"));
doc.nodes_mut().push(bar);
doc.nodes_mut().push(KdlNode::new("baz"));
assert_eq!(
r#"foo
bar prop="value" 1 2 false null {
barchild
}
baz
"#,
format!("{}", doc)
);
}
#[test]
fn fmt() -> miette::Result<()> {
let mut doc: KdlDocument = r#"
/* x */ foo 1 "bar"=0xDEADbeef {
child1 1 ;
// child 2 comment
child2 2 // comment
child3 "
string\t" \
{
/*
multiline*/
inner1 \
r"value" \
;
inner2 \ //comment
{
inner3
}
}
}
// trailing comment here
"#
.parse()?;
KdlDocument::fmt(&mut doc);
print!("{}", doc);
assert_eq!(
doc.to_string(),
r#"/* x */
foo 1 bar=0xdeadbeef {
child1 1
// child 2 comment
child2 2 // comment
child3 "\n\n string\t" {
/*
multiline*/
inner1 r"value"
inner2 {
inner3
}
}
}
// trailing comment here"#
);
Ok(())
}
#[test]
fn simple_fmt() -> miette::Result<()> {
let mut doc: KdlDocument = "a { b { c { }; }; }".parse().unwrap();
KdlDocument::fmt(&mut doc);
print!("{}", doc);
assert_eq!(
doc.to_string(),
r#"a {
b {
c {
}
}
}
"#
);
Ok(())
}
#[cfg(feature = "span")]
fn check_spans_for_doc(doc: &KdlDocument, source: &impl miette::SourceCode) {
for node in doc.nodes() {
check_spans_for_node(node, source);
}
}
#[cfg(feature = "span")]
fn check_spans_for_node(node: &KdlNode, source: &impl miette::SourceCode) {
check_span_for_ident(node.name(), source);
if let Some(ty) = node.ty() {
check_span_for_ident(ty, source);
}
for entry in node.entries() {
if let Some(name) = entry.name() {
check_span_for_ident(name, source);
}
if let Some(ty) = entry.ty() {
check_span_for_ident(ty, source);
}
if let Some(repr) = entry.value_repr() {
if entry.name().is_none() && entry.ty().is_none() {
check_span(repr, entry.span(), source);
}
}
}
if let Some(children) = node.children() {
check_spans_for_doc(children, source);
}
}
#[cfg(feature = "span")]
#[track_caller]
fn check_span_for_ident(ident: &KdlIdentifier, source: &impl miette::SourceCode) {
if let Some(repr) = ident.repr() {
check_span(repr, ident.span(), source);
} else {
check_span(ident.value(), ident.span(), source);
}
}
#[cfg(feature = "span")]
#[track_caller]
fn check_span(expected: &str, span: &SourceSpan, source: &impl miette::SourceCode) {
let span = source.read_span(span, 0, 0).unwrap();
let span = std::str::from_utf8(span.data()).unwrap();
assert_eq!(span, expected);
}
#[cfg(feature = "span")]
#[test]
fn span_test() -> miette::Result<()> {
let input = r####"
this {
is (a)"cool" document="to" read=(int)5 10.1 (u32)0x45
and x="" {
"it" /*shh*/ "has"="💯" r##"the"##
Best🎊est
"syntax ever"
}
"yknow?" 0x10
}
// that's
nice
inline { time; to; live "our" "dreams"; "y;all"; }
"####;
let doc: KdlDocument = input.parse().unwrap();
// First check that all the identity-spans are correct
check_spans_for_doc(&doc, &input);
// Now check some more interesting concrete spans
// The whole document should presumably be "the input" again?
check_span(input, doc.span(), &input);
// This one-liner node should be the whole line without leading whitespace
let is_node = doc
.get("this")
.unwrap()
.children()
.unwrap()
.get("is")
.unwrap();
check_span(
r##"is (a)"cool" document="to" read=(int)5 10.1 (u32)0x45"##,
is_node.span(),
&input,
);
// Some simple with/without type hints
check_span(r#"(a)"cool""#, is_node.get(0).unwrap().span(), &input);
check_span(
r#"read=(int)5"#,
is_node.get("read").unwrap().span(),
&input,
);
check_span(r#"10.1"#, is_node.get(1).unwrap().span(), &input);
check_span(r#"(u32)0x45"#, is_node.get(2).unwrap().span(), &input);
// Now let's look at some messed up parts of that "and" node
let and_node = doc
.get("this")
.unwrap()
.children()
.unwrap()
.get("and")
.unwrap();
// The node is what you expect, the whole line and its two braces
check_span(
r####"and x="" {
"it" /*shh*/ "has"="💯" r##"the"##
Best🎊est
"syntax ever"
}"####,
and_node.span(),
&input,
);
// The child document is a little weird, it's the contents *inside* the braces
// with extra newlines on both ends.
check_span(
r####"
"it" /*shh*/ "has"="💯" r##"the"##
Best🎊est
"syntax ever"
"####,
and_node.children().unwrap().span(),
&input,
);
// Oh hey don't forget to check that "x" entry
check_span(r#"x="""#, and_node.get("x").unwrap().span(), &input);
// Now the "it" node, more straightforward
let it_node = and_node.children().unwrap().get("it").unwrap();
check_span(
r####""it" /*shh*/ "has"="💯" r##"the"##"####,
it_node.span(),
&input,
);
check_span(r#""has"="💯""#, it_node.get("has").unwrap().span(), &input);
check_span(
r####"r##"the"##"####,
it_node.get(0).unwrap().span(),
&input,
);
// Make sure inline nodes work ok
let inline_node = doc.get("inline").unwrap();
check_span(
r#"inline { time; to; live "our" "dreams"; "y;all"; }"#,
inline_node.span(),
&input,
);
let inline_children = inline_node.children().unwrap();
check_span(
r#" time; to; live "our" "dreams"; "y;all"; "#,
inline_children.span(),
&input,
);
let inline_nodes = inline_children.nodes();
check_span("time", inline_nodes[0].span(), &input);
check_span("to", inline_nodes[1].span(), &input);
check_span(r#"live "our" "dreams""#, inline_nodes[2].span(), &input);
check_span(r#""y;all""#, inline_nodes[3].span(), &input);
Ok(())
}
#[test]
fn parse_examples() -> miette::Result<()> {
include_str!("../examples/kdl-schema.kdl").parse::()?;
include_str!("../examples/Cargo.kdl").parse::()?;
include_str!("../examples/ci.kdl").parse::()?;
include_str!("../examples/nuget.kdl").parse::()?;
Ok(())
}
}
kdl-4.6.0/src/entry.rs 0000644 0000000 0000000 00000022507 00726746425 0012741 0 ustar 0000000 0000000 #[cfg(feature = "span")]
use miette::SourceSpan;
use std::{fmt::Display, str::FromStr};
use crate::{parser, KdlError, KdlIdentifier, KdlValue};
/// KDL Entries are the "arguments" to KDL nodes: either a (positional)
/// [`Argument`](https://github.com/kdl-org/kdl/blob/main/SPEC.md#argument) or
/// a (key/value)
/// [`Property`](https://github.com/kdl-org/kdl/blob/main/SPEC.md#property)
#[derive(Debug, Clone)]
pub struct KdlEntry {
pub(crate) leading: Option,
pub(crate) ty: Option,
pub(crate) value: KdlValue,
pub(crate) value_repr: Option,
pub(crate) name: Option,
pub(crate) trailing: Option,
#[cfg(feature = "span")]
pub(crate) span: SourceSpan,
}
impl PartialEq for KdlEntry {
fn eq(&self, other: &Self) -> bool {
self.leading == other.leading
&& self.ty == other.ty
&& self.value == other.value
&& self.value_repr == other.value_repr
&& self.name == other.name
&& self.trailing == other.trailing
// intentionally omitted: self.span == other.span
}
}
impl KdlEntry {
/// Creates a new Argument (positional) KdlEntry.
pub fn new(value: impl Into) -> Self {
KdlEntry {
leading: None,
ty: None,
value: value.into(),
value_repr: None,
name: None,
trailing: None,
#[cfg(feature = "span")]
span: SourceSpan::from(0..0),
}
}
/// Gets a reference to this entry's name, if it's a property entry.
pub fn name(&self) -> Option<&KdlIdentifier> {
self.name.as_ref()
}
/// Gets the entry's value.
pub fn value(&self) -> &KdlValue {
&self.value
}
/// Gets a mutable reference to this entry's value.
pub fn value_mut(&mut self) -> &mut KdlValue {
&mut self.value
}
/// Sets the entry's value.
pub fn set_value(&mut self, value: impl Into) {
self.value = value.into();
}
/// Gets this entry's span.
///
/// This value will be properly initialized when created via [`KdlDocument::parse`]
/// but may become invalidated if the document is mutated. We do not currently
/// guarantee this to yield any particularly consistent results at that point.
#[cfg(feature = "span")]
pub fn span(&self) -> &SourceSpan {
&self.span
}
/// Gets a mutable reference to this entry's span.
#[cfg(feature = "span")]
pub fn span_mut(&mut self) -> &mut SourceSpan {
&mut self.span
}
/// Sets this entry's span.
#[cfg(feature = "span")]
pub fn set_span(&mut self, span: impl Into) {
self.span = span.into();
}
/// Gets the entry's type.
pub fn ty(&self) -> Option<&KdlIdentifier> {
self.ty.as_ref()
}
/// Gets a mutable reference to this entry's type.
pub fn ty_mut(&mut self) -> Option<&mut KdlIdentifier> {
self.ty.as_mut()
}
/// Sets the entry's type.
pub fn set_ty(&mut self, ty: impl Into) {
self.ty = Some(ty.into());
}
/// Creates a new Property (key/value) KdlEntry.
pub fn new_prop(key: impl Into, value: impl Into) -> Self {
KdlEntry {
leading: None,
ty: None,
value: value.into(),
value_repr: None,
name: Some(key.into()),
trailing: None,
#[cfg(feature = "span")]
span: SourceSpan::from(0..0),
}
}
/// Gets leading text (whitespace, comments) for this KdlEntry.
pub fn leading(&self) -> Option<&str> {
self.leading.as_deref()
}
/// Sets leading text (whitespace, comments) for this KdlEntry.
pub fn set_leading(&mut self, leading: impl Into) {
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) {
self.trailing = Some(trailing.into());
}
/// Clears leading and trailing text (whitespace, comments), as well as
/// resetting this entry's value to its default representation.
pub fn clear_fmt(&mut self) {
self.leading = None;
self.trailing = None;
self.value_repr = None;
if let Some(ty) = &mut self.ty {
ty.clear_fmt();
}
if let Some(name) = &mut self.name {
name.clear_fmt();
}
}
/// 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) {
self.value_repr = Some(repr.into());
}
/// Length of this entry when rendered as a string.
pub fn len(&self) -> usize {
format!("{}", self).len()
}
/// Returns true if this entry is completely empty (including whitespace).
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Auto-formats this entry.
pub fn fmt(&mut self) {
self.leading = None;
self.trailing = None;
self.value_repr = None;
if let Some(name) = &mut self.name {
name.fmt();
}
}
}
impl Display for KdlEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(leading) = &self.leading {
write!(f, "{}", leading)?;
}
if let Some(name) = &self.name {
write!(f, "{}=", name)?;
}
if let Some(ty) = &self.ty {
write!(f, "({})", ty)?;
}
if let Some(repr) = &self.value_repr {
write!(f, "{}", repr)?;
} else {
write!(f, "{}", self.value)?;
}
if let Some(trailing) = &self.trailing {
write!(f, "{}", trailing)?;
}
Ok(())
}
}
impl From for KdlEntry
where
T: Into,
{
fn from(value: T) -> Self {
KdlEntry::new(value)
}
}
impl From<(K, V)> for KdlEntry
where
K: Into,
V: Into,
{
fn from((key, value): (K, V)) -> Self {
KdlEntry::new_prop(key, value)
}
}
impl FromStr for KdlEntry {
type Err = KdlError;
fn from_str(s: &str) -> Result {
let kdl_parser = parser::KdlParser::new(s);
kdl_parser.parse(parser::entry_with_trailing(&kdl_parser))
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn reset_value_repr() -> miette::Result<()> {
let mut left_entry: KdlEntry = " name=1.03e2".parse()?;
let mut right_entry: KdlEntry = " name=103.0".parse()?;
assert_ne!(left_entry, right_entry);
left_entry.clear_fmt();
right_entry.clear_fmt();
assert_eq!(left_entry, right_entry);
Ok(())
}
#[test]
fn new() {
let entry = KdlEntry::new(42);
assert_eq!(
entry,
KdlEntry {
leading: None,
ty: None,
value: KdlValue::Base10(42),
value_repr: None,
name: None,
trailing: None,
#[cfg(feature = "span")]
span: SourceSpan::from(0..0),
}
);
let entry = KdlEntry::new_prop("name", 42);
assert_eq!(
entry,
KdlEntry {
leading: None,
ty: None,
value: KdlValue::Base10(42),
value_repr: None,
name: Some("name".into()),
trailing: None,
#[cfg(feature = "span")]
span: SourceSpan::from(0..0),
}
);
}
#[test]
fn parsing() -> miette::Result<()> {
let entry: KdlEntry = " \\\n (\"m\\\"eh\")0xDEADbeef\t\\\n".parse()?;
assert_eq!(
entry,
KdlEntry {
leading: Some(" \\\n ".into()),
ty: Some("\"m\\\"eh\"".parse()?),
value: KdlValue::Base16(0xdeadbeef),
value_repr: Some("0xDEADbeef".into()),
name: None,
trailing: Some("\t\\\n".into()),
#[cfg(feature = "span")]
span: SourceSpan::from(0..0),
}
);
let entry: KdlEntry = " \\\n \"foo\"=(\"m\\\"eh\")0xDEADbeef\t\\\n".parse()?;
assert_eq!(
entry,
KdlEntry {
leading: Some(" \\\n ".into()),
ty: Some("\"m\\\"eh\"".parse()?),
value: KdlValue::Base16(0xdeadbeef),
value_repr: Some("0xDEADbeef".into()),
name: Some("\"foo\"".parse()?),
trailing: Some("\t\\\n".into()),
#[cfg(feature = "span")]
span: SourceSpan::from(0..0),
}
);
Ok(())
}
#[test]
fn display() {
let entry = KdlEntry::new(KdlValue::Base10(42));
assert_eq!(format!("{}", entry), "42");
let entry = KdlEntry::new_prop("name", KdlValue::Base10(42));
assert_eq!(format!("{}", entry), "name=42");
}
}
kdl-4.6.0/src/error.rs 0000644 0000000 0000000 00000010272 00726746425 0012725 0 ustar 0000000 0000000 use std::num::{ParseFloatError, ParseIntError};
use miette::{Diagnostic, SourceSpan};
use nom::error::{ContextError, ErrorKind, FromExternalError, ParseError};
use thiserror::Error;
#[cfg(doc)]
use {
crate::KdlNode,
std::convert::{TryFrom, TryInto},
};
/// An error that occurs when parsing a KDL document.
///
/// This error implements [`miette::Diagnostic`] and can be used to display
/// detailed, pretty-printed diagnostic messages when using [`miette::Result`]
/// and the `"fancy"` feature flag for `miette`:
///
/// ```no_run
/// fn main() -> miette::Result<()> {
/// "foo 1.".parse::()?;
/// Ok(())
/// }
/// ```
///
/// This will display a message like:
/// ```text
/// Error:
/// × Expected valid value.
/// ╭────
/// 1 │ foo 1.
/// · ─┬
/// · ╰── invalid float
/// ╰────
/// help: Floating point numbers must be base 10, and have numbers after the decimal point.
/// ```
#[derive(Debug, Diagnostic, Clone, Eq, PartialEq, Error)]
#[error("{kind}")]
pub struct KdlError {
/// Source string for the KDL document that failed to parse.
#[source_code]
pub input: String,
/// Offset in chars of the error.
#[label("{}", label.unwrap_or("here"))]
pub span: SourceSpan,
/// Label text for this span. Defaults to `"here"`.
pub label: Option<&'static str>,
/// Suggestion for fixing the parser error.
#[help]
pub help: Option<&'static str>,
/// Specific error kind for this parser error.
pub kind: KdlErrorKind,
}
/// A type reprenting additional information specific to the type of error being returned.
#[derive(Debug, Diagnostic, Clone, Eq, PartialEq, Error)]
pub enum KdlErrorKind {
/// An error occurred while parsing an integer.
#[error(transparent)]
#[diagnostic(code(kdl::parse_int))]
ParseIntError(ParseIntError),
/// An error occurred while parsing a floating point number.
#[error(transparent)]
#[diagnostic(code(kdl::parse_float))]
ParseFloatError(ParseFloatError),
/// Generic parsing error. The given context string denotes the component
/// that failed to parse.
#[error("Expected {0}.")]
#[diagnostic(code(kdl::parse_component))]
Context(&'static str),
/// Generic unspecified error. If this is returned, the call site should
/// be annotated with context, if possible.
#[error("An unspecified error occurred.")]
#[diagnostic(code(kdl::other))]
Other,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub(crate) struct KdlParseError {
pub(crate) input: I,
pub(crate) context: Option<&'static str>,
pub(crate) len: usize,
pub(crate) label: Option<&'static str>,
pub(crate) help: Option<&'static str>,
pub(crate) kind: Option,
pub(crate) touched: bool,
}
impl ParseError for KdlParseError {
fn from_error_kind(input: I, _kind: nom::error::ErrorKind) -> Self {
Self {
input,
len: 0,
label: None,
help: None,
context: None,
kind: None,
touched: false,
}
}
fn append(_input: I, _kind: nom::error::ErrorKind, other: Self) -> Self {
other
}
}
impl ContextError for KdlParseError {
fn add_context(_input: I, ctx: &'static str, mut other: Self) -> Self {
other.context = other.context.or(Some(ctx));
other
}
}
impl<'a> FromExternalError<&'a str, ParseIntError> for KdlParseError<&'a str> {
fn from_external_error(input: &'a str, _kind: ErrorKind, e: ParseIntError) -> Self {
KdlParseError {
input,
len: 0,
label: None,
help: None,
context: None,
kind: Some(KdlErrorKind::ParseIntError(e)),
touched: false,
}
}
}
impl<'a> FromExternalError<&'a str, ParseFloatError> for KdlParseError<&'a str> {
fn from_external_error(input: &'a str, _kind: ErrorKind, e: ParseFloatError) -> Self {
KdlParseError {
input,
len: 0,
label: None,
help: None,
context: None,
kind: Some(KdlErrorKind::ParseFloatError(e)),
touched: false,
}
}
}
kdl-4.6.0/src/fmt.rs 0000644 0000000 0000000 00000002520 00726746425 0012357 0 ustar 0000000 0000000 use std::fmt::Write as _;
pub(crate) fn fmt_leading(leading: &mut String, indent: usize, no_comments: bool) {
if leading.is_empty() {
return;
}
let mut result = String::new();
if !no_comments {
let input = leading.trim();
let kdl_parser = crate::parser::KdlParser { full_input: input };
let comments = kdl_parser
.parse(crate::parser::leading_comments(&kdl_parser))
.expect("invalid leading text");
for line in comments {
let trimmed = line.trim();
if !trimmed.is_empty() {
writeln!(result, "{:indent$}{}", "", trimmed, indent = indent).unwrap();
}
}
}
write!(result, "{:indent$}", "", indent = indent).unwrap();
*leading = result;
}
pub(crate) fn fmt_trailing(decor: &mut String, no_comments: bool) {
if decor.is_empty() {
return;
}
*decor = decor.trim().to_string();
let mut result = String::new();
if !no_comments {
let input = &*decor;
let kdl_parser = crate::parser::KdlParser { full_input: input };
let comments = kdl_parser
.parse(crate::parser::trailing_comments(&kdl_parser))
.expect("invalid trailing text");
for comment in comments {
result.push_str(comment);
}
}
*decor = result;
}
kdl-4.6.0/src/identifier.rs 0000644 0000000 0000000 00000016377 00726746425 0013732 0 ustar 0000000 0000000 #[cfg(feature = "span")]
use miette::SourceSpan;
use std::{fmt::Display, str::FromStr};
use crate::{parser, KdlError};
/// Represents a KDL
/// [Identifier](https://github.com/kdl-org/kdl/blob/main/SPEC.md#identifier).
#[derive(Debug, Clone, Eq)]
pub struct KdlIdentifier {
pub(crate) value: String,
pub(crate) repr: Option,
#[cfg(feature = "span")]
pub(crate) span: SourceSpan,
}
impl PartialEq for KdlIdentifier {
fn eq(&self, other: &Self) -> bool {
self.value == other.value && self.repr == other.repr
// intentionally omitted: self.span == other.span
}
}
impl std::hash::Hash for KdlIdentifier {
fn hash(&self, state: &mut H) {
self.value.hash(state);
self.repr.hash(state);
// Intentionally omitted: self.span.hash(state);
}
}
impl KdlIdentifier {
/// Gets the string value for this identifier.
pub fn value(&self) -> &str {
&self.value
}
/// Sets the string value for this identifier.
pub fn set_value(&mut self, value: impl Into) {
self.value = value.into();
}
/// Gets this identifier's span.
///
/// This value will be properly initialized when created via [`KdlDocument::parse`]
/// but may become invalidated if the document is mutated. We do not currently
/// guarantee this to yield any particularly consistent results at that point.
#[cfg(feature = "span")]
pub fn span(&self) -> &SourceSpan {
&self.span
}
/// Gets a mutable reference to this identifier's span.
#[cfg(feature = "span")]
pub fn span_mut(&mut self) -> &mut SourceSpan {
&mut self.span
}
/// Sets this identifier's span.
#[cfg(feature = "span")]
pub fn set_span(&mut self, span: impl Into) {
self.span = span.into();
}
/// Gets the custom string representation for this identifier, if any.
pub fn repr(&self) -> Option<&str> {
self.repr.as_deref()
}
/// Sets a custom string representation for this identifier.
pub fn set_repr(&mut self, repr: impl Into) {
self.repr = Some(repr.into());
}
/// Length of this identifier when rendered as a string.
pub fn len(&self) -> usize {
format!("{}", self).len()
}
/// Returns true if this identifier is completely empty.
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Resets this identifier to its default representation. It will attempt
/// to make it an unquoted identifier, and fall back to a string
/// representation if that would be invalid.
pub fn clear_fmt(&mut self) {
self.repr = None;
}
/// Auto-formats this identifier.
pub fn fmt(&mut self) {
self.repr = None;
}
}
impl Display for KdlIdentifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(repr) = &self.repr {
write!(f, "{}", repr)
} else if self.plain_value() {
write!(f, "{}", self.value)
} else {
write!(f, "{:?}", self.value)
}
}
}
impl KdlIdentifier {
pub(crate) fn is_identifier_char(c: char) -> bool {
!((c as u32) < 0x20
|| (c as u32) > 0x10ffff
|| matches!(
c,
'\\' | '/'
| '('
| ')'
| '{'
| '}'
| '<'
| '>'
| ';'
| '['
| ']'
| '='
| ','
| '"'
// Newlines
| '\r'
| '\n'
| '\u{0085}'
| '\u{000C}'
| '\u{2028}'
| '\u{2029}'
// Whitespace
| ' '
| '\t'
| '\u{FEFF}'
| '\u{00A0}'
| '\u{1680}'
| '\u{2000}'
| '\u{2001}'
| '\u{2002}'
| '\u{2003}'
| '\u{2004}'
| '\u{2005}'
| '\u{2006}'
| '\u{2007}'
| '\u{2008}'
| '\u{2009}'
| '\u{200A}'
| '\u{202F}'
| '\u{205F}'
| '\u{3000}'
))
}
pub(crate) fn is_initial_char(c: char) -> bool {
!c.is_numeric() && Self::is_identifier_char(c)
}
fn plain_value(&self) -> bool {
let mut iter = self.value.chars();
if let Some(c) = iter.next() {
if !Self::is_initial_char(c) {
return false;
}
} else {
return false;
}
for char in iter {
if !Self::is_identifier_char(char) {
return false;
}
}
true
}
}
impl From<&str> for KdlIdentifier {
fn from(value: &str) -> Self {
KdlIdentifier {
value: value.to_string(),
repr: None,
#[cfg(feature = "span")]
span: SourceSpan::from(0..0),
}
}
}
impl From for KdlIdentifier {
fn from(value: String) -> Self {
KdlIdentifier {
value,
repr: None,
#[cfg(feature = "span")]
span: SourceSpan::from(0..0),
}
}
}
impl From for String {
fn from(value: KdlIdentifier) -> Self {
value.value
}
}
impl FromStr for KdlIdentifier {
type Err = KdlError;
fn from_str(s: &str) -> Result {
let kdl_parser = crate::parser::KdlParser::new(s);
kdl_parser.parse(parser::identifier(&kdl_parser))
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn parsing() -> miette::Result<()> {
let plain = "foo";
assert_eq!(
plain.parse::()?,
KdlIdentifier {
value: plain.to_string(),
repr: Some(plain.to_string()),
#[cfg(feature = "span")]
span: SourceSpan::from(0..3),
}
);
let quoted = "\"foo\\\"bar\"";
assert_eq!(
quoted.parse::()?,
KdlIdentifier {
value: "foo\"bar".to_string(),
repr: Some(quoted.to_string()),
#[cfg(feature = "span")]
span: SourceSpan::from(0..0),
}
);
let invalid = "123";
assert!(invalid.parse::().is_err());
let invalid = " space ";
assert!(invalid.parse::().is_err());
let invalid = "\"x";
assert!(invalid.parse::().is_err());
Ok(())
}
#[test]
fn formatting() {
let plain = KdlIdentifier::from("foo");
assert_eq!(format!("{}", plain), "foo");
let quoted = KdlIdentifier::from("foo\"bar");
assert_eq!(format!("{}", quoted), r#""foo\"bar""#);
let mut custom_repr = KdlIdentifier::from("foo");
custom_repr.set_repr(r#""foo/bar""#.to_string());
assert_eq!(format!("{}", custom_repr), r#""foo/bar""#);
}
}
kdl-4.6.0/src/lib.rs 0000644 0000000 0000000 00000010537 00726746425 0012346 0 ustar 0000000 0000000 //! `kdl` is a "document-oriented" parser and API for the [KDL Document
//! Language](https://kdl.dev), a node-based, human-friendly configuration and
//! serialization format. Unlike serde-based implementations, this crate
//! preserves formatting when editing, as well as when inserting or changing
//! values with custom formatting. This is most useful when working with
//! human-maintained KDL files.
//!
//! You can think of this crate as
//! [`toml_edit`](https://crates.io/crates/toml_edit), but for KDL.
//!
//! If you don't care about formatting or programmatic manipulation, you might
//! check out [`knuffel`](https://crates.io/crates/knuffel) or
//! [`kaydle`](https://crates.io/crates/kaydle) instead for serde (or
//! serde-like) parsing.
//!
//! ## Example
//!
//! ```rust
//! use kdl::KdlDocument;
//!
//! let doc_str = r#"
//! hello 1 2 3
//!
//! world prop="value" {
//! child 1
//! child 2
//! }
//! "#;
//!
//! let doc: KdlDocument = doc_str.parse().expect("failed to parse KDL");
//!
//! assert_eq!(
//! doc.get_args("hello"),
//! vec![&1.into(), &2.into(), &3.into()]
//! );
//!
//! assert_eq!(
//! doc.get("world").map(|node| &node["prop"]),
//! Some(&"value".into())
//! );
//!
//! // Documents fully roundtrip:
//! assert_eq!(doc.to_string(), doc_str);
//! ```
//!
//! ## Controlling Formatting
//!
//! By default, everything is created with default formatting. You can parse
//! items manually to provide custom representations, comments, etc:
//!
//! ```rust
//! let node_str = r#"
//! // indented comment
//! "formatted" 1 /* comment */ \
//! 2;
//! "#;
//!
//! let mut doc = kdl::KdlDocument::new();
//! doc.nodes_mut().push(node_str.parse().unwrap());
//!
//! assert_eq!(&doc.to_string(), node_str);
//! ```
//!
//! [`KdlDocument`], [`KdlNode`], [`KdlEntry`], and [`KdlIdentifier`] can all
//! be parsed and managed this way.
//!
//! ## Error Reporting
//!
//! [`KdlError`] implements [`miette::Diagnostic`] and can be used to display
//! detailed, pretty-printed diagnostic messages when using [`miette::Result`]
//! and the `"fancy"` feature flag for `miette`:
//!
//! ```toml
//! # Cargo.toml
//! [dependencies]
//! miette = { version = "x.y.z", features = ["fancy"] }
//! ```
//!
//! ```no_run
//! fn main() -> miette::Result<()> {
//! "foo 1.".parse::()?;
//! Ok(())
//! }
//! ```
//!
//! This will display a message like:
//! ```text
//! Error:
//! × Expected valid value.
//! ╭────
//! 1 │ foo 1.
//! · ─┬
//! · ╰── invalid float
//! ╰────
//! help: Floating point numbers must be base 10, and have numbers after the decimal point.
//! ```
//!
//! ## Quirks
//!
//! ### Properties
//!
//! Multiple properties with the same name are allowed, and all duplicated
//! **will be preserved**, meaning those documents will correctly round-trip.
//! When using `node.get()`/`node["key"]` & company, the _last_ property with
//! that name's value will be returned.
//!
//! ### Numbers
//!
//! KDL itself does not specify a particular representation for numbers and
//! accepts just about anything valid, no matter how large and how small. This
//! means a few things:
//!
//! * Numbers without a decimal point are interpreted as [`u64`].
//! * Numbers with a decimal point are interpreted as [`f64`].
//! * Floating point numbers that evaluate to [`f64::INFINITY`] or
//! [`f64::NEG_INFINITY`] or NaN will be represented as such in the values,
//! instead of the original numbers.
//! * A similar restriction applies to overflowed [`u64`] values.
//! * The original _representation_ of these numbers will be preserved, unless
//! you [`KdlDocument::fmt`] in which case the original representation will be
//! thrown away and the actual value will be used when serializing.
//!
//! ## License
//!
//! The code in this repository is covered by [the Apache-2.0
//! License](LICENSE.md).
#![deny(missing_debug_implementations, nonstandard_style)]
#![warn(missing_docs, unreachable_pub, rust_2018_idioms, unreachable_pub)]
#![cfg_attr(test, deny(warnings))]
#![doc(html_favicon_url = "https://kdl.dev/favicon.ico")]
#![doc(html_logo_url = "https://kdl.dev/logo.svg")]
pub use document::*;
pub use entry::*;
pub use error::*;
pub use identifier::*;
pub use node::*;
pub use value::*;
mod document;
mod entry;
mod error;
mod fmt;
mod identifier;
mod node;
mod nom_compat;
mod parser;
mod value;
kdl-4.6.0/src/node.rs 0000644 0000000 0000000 00000046526 00726746425 0012534 0 ustar 0000000 0000000 use std::{
fmt::Display,
ops::{Index, IndexMut},
str::FromStr,
};
#[cfg(feature = "span")]
use miette::SourceSpan;
use crate::{parser, KdlDocument, KdlEntry, KdlError, KdlIdentifier, KdlValue};
/// Represents an individual KDL
/// [`Node`](https://github.com/kdl-org/kdl/blob/main/SPEC.md#node) inside a
/// KDL Document.
#[derive(Debug, Clone)]
pub struct KdlNode {
pub(crate) leading: Option,
pub(crate) ty: Option,
pub(crate) name: KdlIdentifier,
// TODO: consider using `hashlink` for this instead, later.
pub(crate) entries: Vec,
pub(crate) before_children: Option,
pub(crate) children: Option,
pub(crate) trailing: Option,
#[cfg(feature = "span")]
pub(crate) span: SourceSpan,
}
impl PartialEq for KdlNode {
fn eq(&self, other: &Self) -> bool {
self.leading == other.leading
&& self.ty == other.ty
&& self.name == other.name
&& self.entries == other.entries
&& self.before_children == other.before_children
&& self.children == other.children
&& self.trailing == other.trailing
// intentionally omitted: self.span == other.span
}
}
impl KdlNode {
/// Creates a new KdlNode with a given name.
pub fn new(name: impl Into) -> Self {
Self {
name: name.into(),
leading: None,
ty: None,
entries: Vec::new(),
before_children: None,
children: None,
trailing: None,
#[cfg(feature = "span")]
span: SourceSpan::from(0..0),
}
}
/// Gets this node's name.
pub fn name(&self) -> &KdlIdentifier {
&self.name
}
/// Gets a mutable reference to this node's name.
pub fn name_mut(&mut self) -> &mut KdlIdentifier {
&mut self.name
}
/// Sets this node's name.
pub fn set_name(&mut self, name: impl Into) {
self.name = name.into();
}
/// Gets this node's span.
///
/// This value will be properly initialized when created via [`KdlDocument::parse`]
/// but may become invalidated if the document is mutated. We do not currently
/// guarantee this to yield any particularly consistent results at that point.
#[cfg(feature = "span")]
pub fn span(&self) -> &SourceSpan {
&self.span
}
/// Gets a mutable reference to this node's span.
#[cfg(feature = "span")]
pub fn span_mut(&mut self) -> &mut SourceSpan {
&mut self.span
}
/// Sets this node's span.
#[cfg(feature = "span")]
pub fn set_span(&mut self, span: impl Into) {
self.span = span.into();
}
/// Gets the node's type identifier, if any.
pub fn ty(&self) -> Option<&KdlIdentifier> {
self.ty.as_ref()
}
/// Gets a mutable reference to the node's type identifier.
pub fn ty_mut(&mut self) -> &mut Option {
&mut self.ty
}
/// Sets the node's type identifier.
pub fn set_ty(&mut self, ty: impl Into) {
self.ty = Some(ty.into());
}
/// Returns a reference to this node's entries (arguments and properties).
pub fn entries(&self) -> &[KdlEntry] {
&self.entries
}
/// Returns a mutable reference to this node's entries (arguments and
/// properties).
pub fn entries_mut(&mut self) -> &mut Vec {
&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) {
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) {
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) {
self.trailing = Some(trailing.into());
}
/// Length of this node when rendered as a string.
pub fn len(&self) -> usize {
format!("{}", self).len()
}
/// Returns true if this node is completely empty (including whitespace).
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Clears leading and trailing text (whitespace, comments), as well as
/// the space before the children block, if any. Individual entries and
/// their formatting will be preserved.
///
/// If you want to clear formatting on all children and entries as well,
/// use [`Self::clear_fmt_recursive`].
pub fn clear_fmt(&mut self) {
self.leading = None;
self.trailing = None;
self.before_children = None;
}
/// Clears leading and trailing text (whitespace, comments), as well as
/// the space before the children block, if any. Individual entries and
/// children formatting will also be cleared.
pub fn clear_fmt_recursive(&mut self) {
self.clear_fmt();
self.name.clear_fmt();
if let Some(children) = &mut self.children {
children.clear_fmt_recursive();
}
for entry in self.entries.iter_mut() {
entry.clear_fmt();
}
}
/// Fetches an entry by key. Number keys will look up arguments, strings
/// will look up properties.
pub fn get(&self, key: impl Into) -> Option<&KdlEntry> {
self.get_impl(key.into())
}
fn get_impl(&self, key: NodeKey) -> Option<&KdlEntry> {
match key {
NodeKey::Key(key) => {
let mut current = None;
for entry in &self.entries {
if entry.name.is_some()
&& entry.name.as_ref().map(|i| i.value()) == Some(key.value())
{
current = Some(entry);
}
}
current
}
NodeKey::Index(idx) => {
let mut current_idx = 0;
for entry in &self.entries {
if entry.name.is_none() {
if current_idx == idx {
return Some(entry);
}
current_idx += 1;
if current_idx > idx + 1 {
return None;
}
}
}
None
}
}
}
/// Fetches a mutable referene to an entry by key. Number keys will look
/// up arguments, strings will look up properties.
pub fn get_mut(&mut self, key: impl Into) -> Option<&mut KdlEntry> {
self.get_mut_impl(key.into())
}
fn get_mut_impl(&mut self, key: NodeKey) -> Option<&mut KdlEntry> {
match key {
NodeKey::Key(key) => {
let mut current = None;
for entry in &mut self.entries {
if entry.name.is_some()
&& entry.name.as_ref().map(|i| i.value()) == Some(key.value())
{
current = Some(entry);
}
}
current
}
NodeKey::Index(idx) => {
let mut current_idx = 0;
for entry in &mut self.entries {
if entry.name.is_none() {
if current_idx >= idx {
return Some(entry);
}
current_idx += 1;
if current_idx >= idx {
return None;
}
}
}
None
}
}
}
/// Inserts an entry into this node. If an entry already exists with the
/// same key, it will be replaced and the previous entry will be returned.
///
/// Numerical keys will insert arguments, string keys will insert
/// properties.
pub fn insert(
&mut self,
key: impl Into,
entry: impl Into,
) -> Option {
self.insert_impl(key.into(), entry.into())
}
fn insert_impl(&mut self, key: NodeKey, mut entry: KdlEntry) -> Option {
match key {
NodeKey::Key(ref key_val) => {
if entry.name.is_none() {
entry.name = Some(key_val.clone());
}
if entry.name.as_ref().map(|i| i.value()) != Some(key_val.value()) {
panic!("Property name mismatch");
}
if let Some(existing) = self.get_mut(key) {
std::mem::swap(existing, &mut entry);
Some(entry)
} else {
self.entries.push(entry);
None
}
}
NodeKey::Index(idx) => {
if entry.name.is_some() {
panic!("Cannot insert property with name under a numerical key");
}
if let Some(existing) = self.get_mut(key) {
std::mem::swap(existing, &mut entry);
Some(entry)
} else {
let mut current_idx = 0;
for existing in &mut self.entries {
if existing.name.is_none() {
if current_idx == idx {
std::mem::swap(existing, &mut entry);
return Some(entry);
}
current_idx += 1;
if current_idx >= idx {
break;
}
}
}
if idx > current_idx {
panic!(
"Insertion index (is {}) should be <= len (is {})",
idx, current_idx
);
} else {
self.entries.push(entry);
None
}
}
}
}
}
/// Removes an entry from this node. If an entry already exists with the
/// same key, it will be returned.
///
/// Numerical keys will remove arguments, string keys will remove
/// properties.
pub fn remove(&mut self, key: impl Into) -> Option {
self.remove_impl(key.into())
}
fn remove_impl(&mut self, key: NodeKey) -> Option {
match key {
NodeKey::Key(key) => {
for (idx, entry) in self.entries.iter_mut().enumerate() {
if entry.name.is_some() && entry.name.as_ref() == Some(&key) {
return Some(self.entries.remove(idx));
}
}
None
}
NodeKey::Index(idx) => {
let mut current_idx = 0;
for entry in &mut self.entries {
if entry.name.is_none() {
if current_idx == idx {
return Some(self.entries.remove(idx));
}
current_idx += 1;
if current_idx >= idx {
return None;
}
}
}
None
}
}
}
/// Shorthand for `self.entries_mut().push(entry)`.
pub fn push(&mut self, entry: impl Into) {
self.entries.push(entry.into());
}
/// Shorthand for `self.entries_mut().clear()`
pub fn clear_entries(&mut self) {
self.entries.clear();
}
/// Returns a reference to this node's children, if any.
pub fn children(&self) -> Option<&KdlDocument> {
self.children.as_ref()
}
/// Returns a mutable reference to this node's children, if any.
pub fn children_mut(&mut self) -> &mut Option {
&mut self.children
}
/// Sets the KdlDocument representing this node's children.
pub fn set_children(&mut self, children: KdlDocument) {
self.children = Some(children);
}
/// Removes this node's children completely.
pub fn clear_children(&mut self) {
self.children = None;
}
/// Returns a mutable reference to this node's children [`KdlDocument`],
/// creating one first if one does not already exist.
pub fn ensure_children(&mut self) -> &mut KdlDocument {
if self.children.is_none() {
self.children = Some(KdlDocument::new());
}
self.children_mut().as_mut().unwrap()
}
/// Auto-formats this node and its contents.
pub fn fmt(&mut self) {
self.fmt_impl(0, false);
}
/// Auto-formats this node and its contents, stripping comments.
pub fn fmt_no_comments(&mut self) {
self.fmt_impl(0, true);
}
}
/// Represents a [`KdlNode`]'s entry key.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NodeKey {
/// Key for a node property entry.
Key(KdlIdentifier),
/// Index for a node argument entry (positional value).
Index(usize),
}
impl From<&str> for NodeKey {
fn from(key: &str) -> Self {
NodeKey::Key(key.into())
}
}
impl From for NodeKey {
fn from(key: String) -> Self {
NodeKey::Key(key.into())
}
}
impl From for NodeKey {
fn from(key: usize) -> Self {
NodeKey::Index(key)
}
}
impl Index for KdlNode {
type Output = KdlValue;
fn index(&self, index: usize) -> &Self::Output {
self.get(index).expect("Argument out of range.").value()
}
}
impl IndexMut for KdlNode {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
self.get_mut(index)
.expect("Argument out of range.")
.value_mut()
}
}
impl Index<&str> for KdlNode {
type Output = KdlValue;
fn index(&self, key: &str) -> &Self::Output {
self.get(key).expect("No such property.").value()
}
}
impl IndexMut<&str> for KdlNode {
fn index_mut(&mut self, key: &str) -> &mut Self::Output {
if self.get(key).is_none() {
self.push((key, KdlValue::Null));
}
self.get_mut(key)
.expect("Something went wrong.")
.value_mut()
}
}
impl FromStr for KdlNode {
type Err = KdlError;
fn from_str(input: &str) -> Result {
let kdl_parser = crate::parser::KdlParser::new(input);
kdl_parser.parse(parser::node(&kdl_parser))
}
}
impl Display for KdlNode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.stringify(f, 0)
}
}
impl KdlNode {
pub(crate) fn fmt_impl(&mut self, indent: usize, no_comments: bool) {
if let Some(s) = self.leading.as_mut() {
crate::fmt::fmt_leading(s, indent, no_comments);
}
if let Some(s) = self.trailing.as_mut() {
crate::fmt::fmt_trailing(s, no_comments);
if s.starts_with(';') {
s.remove(0);
}
if let Some(c) = s.chars().next() {
if !c.is_whitespace() {
s.insert(0, ' ');
}
}
s.push('\n');
}
self.before_children = None;
self.name.clear_fmt();
if let Some(ty) = self.ty.as_mut() {
ty.clear_fmt()
}
for entry in &mut self.entries {
entry.fmt();
}
if let Some(children) = self.children.as_mut() {
children.fmt_impl(indent + 4, no_comments);
if let Some(leading) = children.leading.as_mut() {
leading.push('\n');
}
if let Some(trailing) = children.trailing.as_mut() {
trailing.push_str(format!("{:indent$}", "", indent = indent).as_str());
}
}
}
pub(crate) fn stringify(
&self,
f: &mut std::fmt::Formatter<'_>,
indent: usize,
) -> std::fmt::Result {
if let Some(leading) = &self.leading {
write!(f, "{}", leading)?;
} else {
write!(f, "{:indent$}", "", indent = indent)?;
}
if let Some(ty) = &self.ty {
write!(f, "({})", ty)?;
}
write!(f, "{}", self.name)?;
let mut space_before_children = true;
for entry in &self.entries {
if entry.leading.is_none() {
write!(f, " ")?;
}
write!(f, "{}", entry)?;
space_before_children = entry.trailing.is_none();
}
if let Some(children) = &self.children {
if let Some(before) = self.before_children() {
write!(f, "{}", before)?;
} else if space_before_children {
write!(f, " ")?;
}
write!(f, "{{")?;
if children.leading.is_none() {
writeln!(f)?;
}
children.stringify(f, indent + 4)?;
if children.trailing.is_none() {
write!(f, "{:indent$}", "", indent = indent)?;
}
write!(f, "}}")?;
}
if let Some(trailing) = &self.trailing {
write!(f, "{}", trailing)?;
}
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn canonical_clear_fmt() -> miette::Result<()> {
let mut left_node: KdlNode = r#"node /-"commented" param_name=103.000 {
// This is a nested node
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);
left_node.clear_fmt_recursive();
right_node.clear_fmt_recursive();
assert_eq!(left_node, right_node);
Ok(())
}
#[test]
fn parsing() -> miette::Result<()> {
let node: KdlNode = "\n\t (\"ty\")\"node\" 0xDEADbeef;\n".parse()?;
assert_eq!(node.leading(), Some("\n\t "));
assert_eq!(node.trailing(), Some(";\n"));
assert_eq!(node.ty(), Some(&"\"ty\"".parse()?));
assert_eq!(node.name(), &"\"node\"".parse()?);
assert_eq!(node.get(0), Some(&"0xDEADbeef".parse()?));
r#"
node "test" {
link "blah" anything="self"
}"#
.parse::()?;
Ok(())
}
#[test]
fn indexing() {
let mut node = KdlNode::new("foo");
node.push("bar");
node["foo"] = 1.into();
assert_eq!(node[0], "bar".into());
assert_eq!(node["foo"], 1.into());
node[0] = false.into();
node["foo"] = KdlValue::Null;
assert_eq!(node[0], false.into());
assert_eq!(node["foo"], KdlValue::Null);
node.entries_mut().push(KdlEntry::new_prop("x", 1));
node.entries_mut().push(KdlEntry::new_prop("x", 2));
assert_eq!(&node["x"], &2.into())
}
}
kdl-4.6.0/src/nom_compat.rs 0000644 0000000 0000000 00000005713 00726746425 0013734 0 ustar 0000000 0000000 use nom::error::{ErrorKind, ParseError};
use nom::{Err, IResult, Parser};
pub(crate) fn many0(mut f: F) -> impl FnMut(I) -> IResult, E>
where
I: Clone + PartialEq,
F: Parser,
E: ParseError,
{
move |mut i: I| {
let mut acc = Vec::with_capacity(4);
loop {
match f.parse(i.clone()) {
Err(Err::Error(_)) => return Ok((i, acc)),
Err(e) => return Err(e),
Ok((i1, o)) => {
if i1 == i {
return Err(Err::Error(E::from_error_kind(i, ErrorKind::Many0)));
}
i = i1;
acc.push(o);
}
}
}
}
}
pub(crate) fn many1(mut f: F) -> impl FnMut(I) -> IResult, E>
where
I: Clone + PartialEq,
F: Parser,
E: ParseError,
{
move |mut i: I| match f.parse(i.clone()) {
Err(Err::Error(err)) => Err(Err::Error(E::append(i, ErrorKind::Many1, err))),
Err(e) => Err(e),
Ok((i1, o)) => {
let mut acc = Vec::with_capacity(4);
acc.push(o);
i = i1;
loop {
match f.parse(i.clone()) {
Err(Err::Error(_)) => return Ok((i, acc)),
Err(e) => return Err(e),
Ok((i1, o)) => {
if i1 == i {
return Err(Err::Error(E::from_error_kind(i, ErrorKind::Many1)));
}
i = i1;
acc.push(o);
}
}
}
}
}
}
pub(crate) fn many_till(
mut f: F,
mut g: G,
) -> impl FnMut(I) -> IResult, P), E>
where
I: Clone + PartialEq,
F: Parser,
G: Parser,
E: ParseError,
{
move |mut i: I| {
let mut res = Vec::new();
loop {
match g.parse(i.clone()) {
Ok((i1, o)) => return Ok((i1, (res, o))),
Err(Err::Error(_)) => {
match f.parse(i.clone()) {
Err(Err::Error(err)) => {
return Err(Err::Error(E::append(i, ErrorKind::ManyTill, err)))
}
Err(e) => return Err(e),
Ok((i1, o)) => {
// loop trip must always consume (otherwise infinite loops)
if i1 == i {
return Err(Err::Error(E::from_error_kind(
i1,
ErrorKind::ManyTill,
)));
}
res.push(o);
i = i1;
}
}
}
Err(e) => return Err(e),
}
}
}
}
kdl-4.6.0/src/parser.rs 0000644 0000000 0000000 00000114376 00726746425 0013102 0 ustar 0000000 0000000 // A bunch of random variables/functions become dead when you disable
// span support and rather than turning the code into complete cfg
// swiss-cheese, it's simpler to just hush the compiler about it
#![cfg_attr(not(feature = "span"), allow(dead_code, unused_variables))]
use std::ops::RangeTo;
use crate::nom_compat::{many0, many1, many_till};
use miette::SourceSpan;
use nom::branch::alt;
use nom::bytes::complete::{tag, take_until, take_while, take_while_m_n};
use nom::character::complete::{anychar, char, none_of, one_of};
use nom::combinator::{all_consuming, cut, eof, map, map_opt, map_res, opt, peek, recognize};
use nom::error::{context, ParseError};
use nom::sequence::{delimited, preceded, terminated, tuple};
use nom::{Finish, IResult, Offset, Parser, Slice};
use crate::{
KdlDocument, KdlEntry, KdlError, KdlErrorKind, KdlIdentifier, KdlNode, KdlParseError, KdlValue,
};
/// The parser for the entire input.
///
/// All of our parsing subroutines want to hold onto some global information
/// to generate things like spans, so instead of making them simple free
/// functions, we wrap their bodies in closures that take in a kdl_parser.
/// The free functions then becoming constructors that return those closures.
/// This is basically the same idea behind nom combinators like many0 which
/// take an input to configure the combinator and then return a function.
pub(crate) struct KdlParser<'a> {
pub(crate) full_input: &'a str,
}
impl<'a> KdlParser<'a> {
pub(crate) fn new(full_input: &'a str) -> Self {
Self { full_input }
}
pub(crate) fn parse(&self, parser: P) -> Result
where
P: Parser<&'a str, T, KdlParseError<&'a str>>,
{
all_consuming(parser)(self.full_input)
.finish()
.map(|(_, arg)| arg)
.map_err(|e| {
let span_substr = &e.input[..e.len];
KdlError {
input: self.full_input.into(),
span: self.span_from_substr(span_substr),
help: e.help,
label: e.label,
kind: if let Some(kind) = e.kind {
kind
} else if let Some(ctx) = e.context {
KdlErrorKind::Context(ctx)
} else {
KdlErrorKind::Other
},
}
})
}
/// Creates a span for an item using two substrings of self.full_input:
///
/// * before: the remainder of the input before parsing the item
/// * after: the remainder input after parsing the item
///
/// All we really care about are the addresses of the strings, the lengths don't matter
fn span_from_before_and_after(&self, before: &str, after: &str) -> SourceSpan {
let base_addr = self.full_input.as_ptr() as usize;
let before_addr = before.as_ptr() as usize;
let after_addr = after.as_ptr() as usize;
assert!(
before_addr >= base_addr,
"tried to get the span of a non-substring!"
);
assert!(
after_addr >= before_addr,
"subslices were in wrong order for spanning!"
);
let start = before_addr - base_addr;
let end = after_addr - base_addr;
SourceSpan::from(start..end)
}
/// Creates a span for an item using a substring of self.full_input
///
/// Note that substr must be a literal substring, as in it must be
/// a pointer into the same string!
fn span_from_substr(&self, substr: &str) -> SourceSpan {
let base_addr = self.full_input.as_ptr() as usize;
let substr_addr = substr.as_ptr() as usize;
assert!(
substr_addr >= base_addr,
"tried to get the span of a non-substring!"
);
let start = substr_addr - base_addr;
let end = start + substr.len();
SourceSpan::from(start..end)
}
}
fn set_details<'a>(
mut err: nom::Err>,
start: &'a str,
label: Option<&'static str>,
help: Option<&'static str>,
) -> nom::Err> {
match &mut err {
nom::Err::Error(e) | nom::Err::Failure(e) => {
if !e.touched {
e.len = start.offset(e.input);
e.input = start;
e.label = label;
e.help = help;
e.touched = true;
}
}
_ => {}
}
err
}
pub(crate) fn document<'a: 'b, 'b>(
kdl_parser: &'b KdlParser<'a>,
) -> impl Fn(&'a str) -> IResult<&'a str, KdlDocument, KdlParseError<&'a str>> + 'b {
move |input| {
let start = input;
let (input, nodes) = many0(node(kdl_parser))(input)?;
let (input, trailing) = all_whitespace(kdl_parser)(input)?;
let mut doc = KdlDocument::new();
doc.set_leading("");
doc.set_trailing(trailing);
*doc.nodes_mut() = nodes;
#[cfg(feature = "span")]
doc.set_span(kdl_parser.span_from_before_and_after(start, trailing));
Ok((input, doc))
}
}
pub(crate) fn node<'a: 'b, 'b>(
kdl_parser: &'b KdlParser<'a>,
) -> impl Fn(&'a str) -> IResult<&'a str, KdlNode, KdlParseError<&'a str>> + 'b {
|input| {
let (input, leading) = all_whitespace(kdl_parser)(input)?;
let start = input;
let (input, ty) = opt(context(
"valid node type annotation",
annotation(kdl_parser),
))(input)?;
let (input, name) = context("valid node name", identifier(kdl_parser))(input)?;
let (input, entries) = many0(context("valid node entry", entry(kdl_parser)))(input)?;
let (input, children) =
opt(context("valid node children block", children(kdl_parser)))(input)?;
let (input, trailing) = context(
"valid node terminator",
cut(recognize(preceded(
many0(node_space(kdl_parser)),
alt((
terminated(recognize(tag(";")), opt(alt((linespace, eof)))),
alt((newline, single_line_comment, eof)),
)),
))),
)(input)
.map_err(|e| {
set_details(
e,
start,
Some("parsed node"),
Some("Nodes can only be terminated by `;` or a valid line ending."),
)
})?;
let mut node = KdlNode::new(name);
node.set_leading(leading);
node.set_trailing(trailing);
#[cfg(feature = "span")]
node.set_span(kdl_parser.span_from_before_and_after(start, trailing));
node.ty = ty;
let ents = node.entries_mut();
*ents = entries;
if let Some((before, children)) = children {
let childs = node.children_mut();
*childs = Some(children);
node.set_before_children(before);
}
Ok((input, node))
}
}
pub(crate) fn identifier<'a: 'b, 'b>(
kdl_parser: &'b KdlParser<'a>,
) -> impl Fn(&'a str) -> IResult<&'a str, KdlIdentifier, KdlParseError<&'a str>> + 'b {
move |input| alt((quoted_identifier(kdl_parser), plain_identifier(kdl_parser)))(input)
}
pub(crate) fn leading_comments<'a: 'b, 'b>(
kdl_parser: &'b KdlParser<'a>,
) -> impl Fn(&'a str) -> IResult<&'a str, Vec<&'a str>, KdlParseError<&'a str>> + 'b {
move |input| {
terminated(
many0(preceded(
opt(many0(alt((newline, unicode_space)))),
comment(kdl_parser),
)),
opt(many0(alt((newline, unicode_space, eof)))),
)(input)
}
}
pub(crate) fn trailing_comments<'a: 'b, 'b>(
kdl_parser: &'b KdlParser<'a>,
) -> impl Fn(&'a str) -> IResult<&'a str, Vec<&'a str>, KdlParseError<&'a str>> + 'b {
move |mut input| {
let mut comments = vec![];
loop {
let (inp, _) = opt(many0(alt((newline, unicode_space, tag("\\")))))(input)?;
let (inp, comment) = opt(comment(kdl_parser))(inp)?;
if let Some(comment) = comment {
comments.push(comment);
}
let (inp, _) = opt(many0(alt((newline, unicode_space, tag("\\"), tag(";")))))(inp)?;
let (inp, end) = opt(eof)(inp)?;
if end.is_some() {
return Ok((inp, comments));
}
if input == inp {
panic!("invalid trailing text");
}
input = inp;
}
}
}
/// A "fake" parser to provide better diagnostics when a bare identifier
/// is used in places it shouldn't.
fn errant_plain_identifier<'a: 'b, 'b>(
kdl_parser: &'b KdlParser<'a>,
) -> impl Fn(&'a str) -> IResult<&'a str, KdlEntry, KdlParseError<&'a str>> + 'b {
move |input| {
let start = input;
let (_input, name) = plain_identifier(kdl_parser)(input)?;
Err(nom::Err::Failure(KdlParseError {
input: start,
context: Some("a valid node entry"),
len: name.len(),
label: Some("plain identifiers can't be used here"),
help: Some("If this was supposed to be a string, wrap it in quotes.\nIf this was supposed to be a new node, terminate the previous node with `;` or a newline."),
kind: None,
touched: false,
}))
}
}
fn plain_identifier<'a: 'b, 'b>(
kdl_parser: &'b KdlParser<'a>,
) -> impl Fn(&'a str) -> IResult<&'a str, KdlIdentifier, KdlParseError<&'a str>> + 'b {
move |input| {
let start = input;
let (input, name) = recognize(preceded(
take_while_m_n(1, 1, KdlIdentifier::is_initial_char),
cut(take_while(KdlIdentifier::is_identifier_char)),
))(input).map_err(|e| set_details(e, start, Some("invalid identifier character"), Some("See https://github.com/kdl-org/kdl/blob/main/SPEC.md#identifier for an explanation of valid KDL identifiers.")))?;
match name {
"false" | "true" | "null" => {
return Err(nom::Err::Error(KdlParseError {
input,
context: Some("non-keyword identifier"),
len: name.len(),
label: Some("reserved keyword"),
help: Some("Reserved keywords cannot be used as identifiers."),
kind: None,
touched: false,
}))
}
_ => {}
}
let mut ident = KdlIdentifier::from(name);
ident.set_repr(name);
#[cfg(feature = "span")]
ident.set_span(kdl_parser.span_from_before_and_after(start, input));
Ok((input, ident))
}
}
fn quoted_identifier<'a: 'b, 'b>(
kdl_parser: &'b KdlParser<'a>,
) -> impl Fn(&'a str) -> IResult<&str, KdlIdentifier, KdlParseError<&str>> + 'b {
move |input| {
let start = input;
let (input, (raw, val)) = alt((string, raw_string))(input)?;
let mut ident = KdlIdentifier::from(val.as_string().unwrap());
ident.set_repr(raw);
#[cfg(feature = "span")]
ident.set_span(kdl_parser.span_from_before_and_after(start, input));
Ok((input, ident))
}
}
pub(crate) fn entry_with_trailing<'a: 'b, 'b>(
kdl_parser: &'b KdlParser<'a>,
) -> impl Fn(&'a str) -> IResult<&'a str, KdlEntry, KdlParseError<&'a str>> + 'b {
move |input| {
let (input, mut leading) = recognize(many0(node_space(kdl_parser)))(input)?;
if leading.is_empty() {
leading = " ";
};
let (input, mut entry) = alt((
property(kdl_parser),
argument(kdl_parser),
errant_plain_identifier(kdl_parser),
))(input)?;
let (input, trailing) = recognize(many0(node_space(kdl_parser)))(input)?;
entry.set_leading(leading);
entry.set_trailing(trailing);
Ok((input, entry))
}
}
fn entry<'a: 'b, 'b>(
kdl_parser: &'b KdlParser<'a>,
) -> impl Fn(&'a str) -> IResult<&'a str, KdlEntry, KdlParseError<&'a str>> + 'b {
move |input| {
let (input, leading) = recognize(many1(node_space(kdl_parser)))(input)?;
let (input, mut entry) = alt((
property(kdl_parser),
argument(kdl_parser),
errant_plain_identifier(kdl_parser),
))(input)?;
entry.set_leading(leading);
Ok((input, entry))
}
}
fn entry_maybe_space<'a: 'b, 'b>(
kdl_parser: &'b KdlParser<'a>,
) -> impl Fn(&'a str) -> IResult<&'a str, KdlEntry, KdlParseError<&'a str>> + 'b {
move |input| {
let (input, leading) = recognize(many0(node_space(kdl_parser)))(input)?;
let (input, mut entry) = alt((
property(kdl_parser),
argument(kdl_parser),
errant_plain_identifier(kdl_parser),
))(input)?;
entry.set_leading(leading);
Ok((input, entry))
}
}
fn property<'a: 'b, 'b>(
kdl_parser: &'b KdlParser<'a>,
) -> impl Fn(&'a str) -> IResult<&'a str, KdlEntry, KdlParseError<&'a str>> + 'b {
move |input| {
let start = input;
let (input, name) = identifier(kdl_parser)(input)?;
let (input, _) = context("'=' after property name", tag("="))(input)?;
let (input, ty) = opt(annotation(kdl_parser))(input)?;
let (input, (raw, value)) = context("property value", cut(value))(input).map_err(|e| set_details(e, input, Some("invalid value"), Some("Please refer to https://github.com/kdl-org/kdl/blob/main/SPEC.md#value for valid KDL value syntaxes.")))?;
let mut entry = KdlEntry::new_prop(name, value);
entry.ty = ty;
entry.set_trailing("");
entry.set_value_repr(raw);
#[cfg(feature = "span")]
entry.set_span(kdl_parser.span_from_before_and_after(start, input));
Ok((input, entry))
}
}
fn argument<'a: 'b, 'b>(
kdl_parser: &'b KdlParser<'a>,
) -> impl Fn(&'a str) -> IResult<&'a str, KdlEntry, KdlParseError<&'a str>> + 'b {
move |input| {
let start = input;
let (input, ty) = opt(annotation(kdl_parser))(input)?;
let (input, (raw, value)) = if ty.is_some() {
context("valid value", cut(value))(input)
} else {
context("valid value", value)(input)
}?;
let mut entry = KdlEntry::new(value);
entry.ty = ty;
entry.set_trailing("");
entry.set_value_repr(raw);
#[cfg(feature = "span")]
entry.set_span(kdl_parser.span_from_before_and_after(start, input));
Ok((input, entry))
}
}
fn value(input: &str) -> IResult<&str, (String, KdlValue), KdlParseError<&str>> {
alt((
null,
boolean,
string,
raw_string,
hexadecimal,
octal,
binary,
float,
integer,
))(input)
}
fn children<'a: 'b, 'b>(
kdl_parser: &'b KdlParser<'a>,
) -> impl Fn(&'a str) -> IResult<&'a str, (&'a str, KdlDocument), KdlParseError<&'a str>> + 'b {
move |input| {
let (input, before) = recognize(many0(node_space(kdl_parser)))(input)?;
let start = input;
let (input, _) = tag("{")(input)?;
let (input, children) = document(kdl_parser)(input)?;
let (input, _) = cut(context("closing '}' in node children block", tag("}")))(input)
.map_err(|e| set_details(e, start, Some("children block body"), None))?;
Ok((input, (before, children)))
}
}
fn annotation<'a: 'b, 'b>(
kdl_parser: &'b KdlParser<'a>,
) -> impl Fn(&'a str) -> IResult<&'a str, KdlIdentifier, KdlParseError<&'a str>> + 'b {
move |input| {
let start = input;
let (input, _) = tag("(")(input)?;
let (input, ty) = cut(identifier(kdl_parser))(input)?;
let (input, _) = context("closing ')' for type annotation", cut(tag(")")))(input)
.map_err(|e| set_details(e, start, Some("annotation"), Some("annotations can only be KDL identifiers (including string identifiers), and can't have any space inside the parentheses.")))?;
Ok((input, ty))
}
}
fn all_whitespace<'a: 'b, 'b>(
kdl_parser: &'b KdlParser<'a>,
) -> impl Fn(&'a str) -> IResult<&'a str, &'a str, KdlParseError<&'a str>> + 'b {
move |input| recognize(many0(alt((comment(kdl_parser), unicode_space, newline))))(input)
}
fn whitespace(input: &str) -> IResult<&str, &str, KdlParseError<&str>> {
recognize(alt((unicode_space, multi_line_comment)))(input)
}
fn linespace(input: &str) -> IResult<&str, &str, KdlParseError<&str>> {
recognize(alt((unicode_space, newline, single_line_comment)))(input)
}
fn node_space<'a: 'b, 'b>(
kdl_parser: &'b KdlParser<'a>,
) -> impl Fn(&'a str) -> IResult<&'a str, &'a str, KdlParseError<&'a str>> + 'b {
move |input| {
context(
"node space",
recognize(alt((
delimited(many0(whitespace), escline, many0(whitespace)),
recognize(many1(whitespace)),
node_slashdash(kdl_parser),
))),
)(input)
}
}
fn escline(input: &str) -> IResult<&str, &str, KdlParseError<&str>> {
recognize(preceded(
tag("\\"),
context(
"newline after line escape",
cut(preceded(
many0(whitespace),
alt((single_line_comment, newline)),
)),
),
))(input).map_err(|e| set_details(e, input, Some("line escape starts here"), Some("line escapes can only be followed by whitespace plus a newline (or single-line comment).")))
}
fn unicode_space(input: &str) -> IResult<&str, &str, KdlParseError<&str>> {
alt((
tag(" "),
tag("\t"),
tag("\u{FEFF}"), // BOM
tag("\u{00A0}"),
tag("\u{1680}"),
tag("\u{2000}"),
tag("\u{2001}"),
tag("\u{2002}"),
tag("\u{2003}"),
tag("\u{2004}"),
tag("\u{2005}"),
tag("\u{2006}"),
tag("\u{2007}"),
tag("\u{2008}"),
tag("\u{2009}"),
tag("\u{200A}"),
tag("\u{202F}"),
tag("\u{205F}"),
tag("\u{3000}"),
))(input)
}
/// `newline := All line-break unicode white_space
pub(crate) fn newline(input: &str) -> IResult<&str, &str, KdlParseError<&str>> {
alt((
tag("\r\n"),
tag("\r"),
tag("\n"),
tag("\u{0085}"),
tag("\u{000C}"),
tag("\u{2028}"),
tag("\u{2029}"),
))(input)
}
fn comment<'a: 'b, 'b>(
kdl_parser: &'b KdlParser<'a>,
) -> impl Fn(&'a str) -> IResult<&'a str, &'a str, KdlParseError<&'a str>> + 'b {
move |input| {
alt((
single_line_comment,
multi_line_comment,
slashdash_comment(kdl_parser),
))(input)
}
}
/// `single-line-comment := '//' ('\r' [^\n] | [^\r\n])* (newline | eof)`
fn single_line_comment(input: &str) -> IResult<&str, &str, KdlParseError<&str>> {
recognize(preceded(
tag("//"),
cut(many_till(
anychar,
context("newline or eof after //", alt((newline, eof))),
)),
))(input)
.map_err(|e| set_details(e, input, Some("comment"), None))
}
/// `multi-line-comment := '/*' commented-block
fn multi_line_comment(input: &str) -> IResult<&str, &str, KdlParseError<&str>> {
recognize(preceded(
tag("/*"),
context("comment block body", cut(commented_block)),
))(input)
.map_err(|e| set_details(e, input, Some("comment"), Some("multi-line comments must start with /* and be terminated with a matching */. They may be nested, but their */ must match.")))
}
/// `commented-block := '*/' | (multi-line-comment | '*' | '/' | [^*/]+) commented-block`
fn commented_block(input: &str) -> IResult<&str, &str, KdlParseError<&str>> {
alt((
tag("*/"),
terminated(
alt((
multi_line_comment,
tag("*"),
tag("/"),
recognize(many_till(anychar, peek(alt((tag("*"), tag("/")))))),
)),
commented_block,
),
))(input)
}
fn node_slashdash<'a: 'b, 'b>(
kdl_parser: &'b KdlParser<'a>,
) -> impl Fn(&'a str) -> IResult<&'a str, &'a str, KdlParseError<&'a str>> + 'b {
move |input| {
recognize(preceded(
tag("/-"),
context(
"node following a slashdash",
cut(alt((
recognize(entry_maybe_space(kdl_parser)),
recognize(children(kdl_parser)),
))),
),
))(input)
.map_err(|e| set_details(e, input, Some("slashdash"), None))
}
}
fn slashdash_comment<'a: 'b, 'b>(
kdl_parser: &'b KdlParser<'a>,
) -> impl Fn(&'a str) -> IResult<&'a str, &'a str, KdlParseError<&'a str>> + 'b {
move |input| {
recognize(preceded(tag("/-"), cut(node(kdl_parser))))(input)
.map_err(|e| set_details(e, input, Some("slashdash"), None))
}
}
fn boolean(input: &str) -> IResult<&str, (String, KdlValue), KdlParseError<&str>> {
alt((
map(tag("true"), |s: &str| (s.into(), KdlValue::Bool(true))),
map(tag("false"), |s: &str| (s.into(), KdlValue::Bool(false))),
))(input)
}
fn null(input: &str) -> IResult<&str, (String, KdlValue), KdlParseError<&str>> {
map(tag("null"), |s: &str| (s.into(), KdlValue::Null))(input)
}
/// `escaped-string := '"' character* '"'`
fn string(input: &str) -> IResult<&str, (String, KdlValue), KdlParseError<&str>> {
let (input, _) = tag("\"")(input)?;
let mut original = String::new();
let mut value = String::new();
original.push('"');
let (input, chars) = many0(character)(input)?;
for (raw, processed) in chars {
original.push_str(raw);
value.push(processed);
}
let (input, _) =
cut(tag("\""))(input).map_err(|e| set_details(e, input, Some("string"), None))?;
original.push('"');
Ok((input, (original, KdlValue::String(value))))
}
/// `character := '\' escape | [^\"]`
fn character(input: &str) -> IResult<&str, (&str, char), KdlParseError<&str>> {
with_raw(alt((preceded(char('\\'), cut(escape)), none_of("\\\""))))(input)
}
/// This is like `recognize`, but _also_ returns the actual value.
fn with_raw>, O, E: ParseError, F>(
mut parser: F,
) -> impl FnMut(I) -> IResult
where
F: Parser,
{
move |input: I| {
let i = input.clone();
match parser.parse(i) {
Ok((i, x)) => {
let index = input.offset(&i);
Ok((i, (input.slice(..index), x)))
}
Err(e) => Err(e),
}
}
}
/// `escape := ["\\/bfnrt] | 'u{' hex-digit{1, 6} '}'`
fn escape(input: &str) -> IResult<&str, char, KdlParseError<&str>> {
alt((
delimited(tag("u{"), cut(unicode), char('}')),
map_opt(anychar, |c| match c {
'"' => Some('"'),
'\\' => Some('\\'),
'/' => Some('/'),
'b' => Some('\u{08}'),
'f' => Some('\u{0C}'),
'n' => Some('\n'),
'r' => Some('\r'),
't' => Some('\t'),
_ => None,
}),
))(input)
}
fn unicode(input: &str) -> IResult<&str, char, KdlParseError<&str>> {
// TODO: This should only accept up to 0x10FFFF.
map_opt(
map_res(
take_while_m_n(1, 6, |c: char| c.is_ascii_hexdigit()),
|hex| u32::from_str_radix(hex, 16),
),
std::char::from_u32,
)(input)
}
/// `raw-string := 'r' raw-string-hash`
/// `raw-string-hash := '#' raw-string-hash '#' | raw-string-quotes`
/// `raw-string-quotes := '"' .* '"'`
fn raw_string(input: &str) -> IResult<&str, (String, KdlValue), KdlParseError<&str>> {
let mut raw = String::new();
let (input, _) = char('r')(input)?;
raw.push('r');
let (input, hashes) = recognize(many0(char('#')))(input)?;
raw.push_str(hashes);
let (input, _) = char('"')(input)?;
raw.push('"');
let close = format!("\"{}", hashes);
let (input, value) = take_until(&close[..])(input)?;
raw.push_str(value);
let (input, _) = cut(tag(&close[..]))(input)?;
raw.push_str(&close);
Ok((input, (raw, KdlValue::RawString(value.into()))))
}
fn float(input: &str) -> IResult<&str, (String, KdlValue), KdlParseError<&str>> {
map_res(
with_raw(alt((
recognize(tuple((
integer,
opt(preceded(char('.'), cut(integer))),
one_of("eE"),
opt(one_of("+-")),
cut(integer),
))),
recognize(tuple((integer, char('.'), cut(integer)))),
))),
|(raw, x)| {
str::replace(x, "_", "")
.parse::()
.map(|x| (raw.into(), KdlValue::Base10Float(x)))
},
)(input)
.map_err(|e| {
set_details(
e,
input,
Some("invalid float"),
Some(
"Floating point numbers must be base 10, and have numbers after the decimal point.",
),
)
})
}
/// ```text
/// integer := sign? [1-9] [0-9_]*
/// sign := '+' | '-'
/// ```
fn integer(input: &str) -> IResult<&str, (String, KdlValue), KdlParseError<&str>> {
let mut raw = String::new();
let (input, (raw_sign, sign)) = with_raw(sign)(input)?;
raw.push_str(raw_sign);
map_res(
with_raw(recognize(many1(terminated(
one_of("0123456789"),
many0(char('_')),
)))),
move |(raw_int, out)| {
raw.push_str(raw_int);
str::replace(out, "_", "")
.parse::()
.map(move |x| x * sign)
.map(|x| (raw.clone(), KdlValue::Base10(x)))
},
)(input)
}
fn hexadecimal(input: &str) -> IResult<&str, (String, KdlValue), KdlParseError<&str>> {
let mut raw = String::new();
let (input, (raw_sign, sign)) = with_raw(sign)(input)?;
raw.push_str(raw_sign);
map_res(
with_raw(preceded(
alt((tag("0x"), tag("0X"))),
context(
"hexadecimal value",
cut(recognize(many1(terminated(
one_of("0123456789abcdefABCDEF"),
many0(char('_')),
)))),
),
)),
move |(raw_body, hex): (&str, &str)| {
raw.push_str(raw_body);
// TODO: Failure in case of int overflow!
i64::from_str_radix(&str::replace(hex, "_", ""), 16)
.map(|x| x * sign)
.map(|x| (raw.clone(), KdlValue::Base16(x)))
},
)(input)
.map_err(|e| set_details(e, input, Some("invalid hexadecimal"), Some("Hexadecimal values can only include the characters 0-9 and a-f (case-insensitive), with optional `_` separators.")))
}
/// `octal := sign? '0o' [0-7] [0-7_]*`
fn octal(input: &str) -> IResult<&str, (String, KdlValue), KdlParseError<&str>> {
let mut raw = String::new();
let (input, (raw_sign, sign)) = with_raw(sign)(input)?;
raw.push_str(raw_sign);
map_res(
with_raw(preceded(
alt((tag("0o"), tag("0O"))),
context(
"octal value",
cut(recognize(many1(terminated(
one_of("01234567"),
many0(char('_')),
)))),
),
)),
move |(raw_body, oct): (&str, &str)| {
raw.push_str(raw_body);
i64::from_str_radix(&str::replace(oct, "_", ""), 8)
.map(|x| x * sign)
.map(|x| (raw.clone(), KdlValue::Base8(x)))
},
)(input)
.map_err(|e| {
set_details(
e,
input,
Some("invalid octal"),
Some("octal values can only include the characters 0-7, with optional `_` separators."),
)
})
}
/// `binary := sign? '0b' ('0' | '1') ('0' | '1' | '_')*`
fn binary(input: &str) -> IResult<&str, (String, KdlValue), KdlParseError<&str>> {
let mut raw = String::new();
let (input, (raw_sign, sign)) = with_raw(sign)(input)?;
raw.push_str(raw_sign);
map_res(
with_raw(preceded(
alt((tag("0b"), tag("0B"))),
context(
"binary value",
cut(recognize(many1(terminated(one_of("01"), many0(char('_')))))),
),
)),
move |(raw_body, binary): (&str, &str)| {
raw.push_str(raw_body);
i64::from_str_radix(&str::replace(binary, "_", ""), 2)
.map(|x| x * sign)
.map(|x| (raw.clone(), KdlValue::Base2(x)))
},
)(input)
.map_err(|e| set_details(e, input, Some("invalid binary"), Some("Hexadecimal values can only include the characters 0 and 1, with optional `_` separators.")))
}
fn sign(input: &str) -> IResult<&str, i64, KdlParseError<&str>> {
let (input, sign) = opt(alt((char('+'), char('-'))))(input)?;
let mult = if let Some(sign) = sign {
if sign == '+' {
1
} else {
-1
}
} else {
1
};
Ok((input, mult))
}
#[cfg(test)]
mod node_tests {
use super::*;
#[test]
fn basic() {
let input = "foo 1 \"bar\"=false";
let kdl_parser = crate::parser::KdlParser::new(input);
match node(&kdl_parser)(input) {
Ok(("", parsed)) => {
let mut ident = KdlIdentifier::from("foo");
ident.set_repr("foo");
assert_eq!(parsed.name(), &ident);
let mut entries = parsed.entries().iter();
let mut one = KdlEntry::new(1);
one.set_leading(" ");
one.set_trailing("");
one.set_value_repr("1");
assert_eq!(entries.next(), Some(&one));
let mut ident = KdlIdentifier::from("bar");
ident.set_repr("\"bar\"");
let mut bar = KdlEntry::new_prop(ident, false);
bar.set_leading(" ");
bar.set_trailing("");
bar.set_value_repr("false");
assert_eq!(entries.next(), Some(&bar));
}
Ok(_) => panic!("unexpected success"),
Err(e) => {
panic!("failed to parse: {:?}", e);
}
};
}
#[test]
fn errant_ident1() {
let input = "struct Vec { }";
let kdl_parser = crate::parser::KdlParser::new(input);
let res = kdl_parser.parse(document(&kdl_parser));
let e = res.unwrap_err();
check_span("Vec", &e.span, &input);
assert_eq!(e.label, Some("plain identifiers can't be used here"));
}
#[test]
fn errant_ident2() {
let input = r##"
some_node
bad evil"##;
let kdl_parser = crate::parser::KdlParser::new(input);
let res = kdl_parser.parse(document(&kdl_parser));
let e = res.unwrap_err();
check_span("evil", &e.span, &input);
assert_eq!(e.label, Some("plain identifiers can't be used here"));
}
#[test]
fn errant_ident3() {
let input = r##"node "ok" wait "fine""##;
let kdl_parser = crate::parser::KdlParser::new(input);
let res = kdl_parser.parse(document(&kdl_parser));
let e = res.unwrap_err();
check_span("wait", &e.span, &input);
assert_eq!(e.label, Some("plain identifiers can't be used here"));
}
#[test]
fn errant_ident4() {
let input = r##"node x="ok" oof z="5"##;
let kdl_parser = crate::parser::KdlParser::new(input);
let res = kdl_parser.parse(document(&kdl_parser));
let e = res.unwrap_err();
check_span("oof", &e.span, &input);
assert_eq!(e.label, Some("plain identifiers can't be used here"));
}
#[test]
fn errant_ident5() {
// NOTE: this one is a different situation and doesn't provide as good help still!
// But at least it's clear that the value is bad, which is ok!
let input = r##"node x=bad"##;
let kdl_parser = crate::parser::KdlParser::new(input);
let res = kdl_parser.parse(document(&kdl_parser));
let e = res.unwrap_err();
check_span("", &e.span, &input);
assert_eq!(e.label, Some("invalid value"));
}
#[test]
fn errant_ident6() {
// NOTE: this one is a different situation and doesn't provide as good help still!
// But at least it's clear that the value is bad, which is ok!
let input = r##"node (int)bad"##;
let kdl_parser = crate::parser::KdlParser::new(input);
let res = kdl_parser.parse(document(&kdl_parser));
let e = res.unwrap_err();
check_span("", &e.span, &input);
assert_eq!(e.label, None);
}
#[cfg(feature = "span")]
#[track_caller]
fn check_span(expected: &str, span: &SourceSpan, source: &impl miette::SourceCode) {
let span = source.read_span(span, 0, 0).unwrap();
let span = std::str::from_utf8(span.data()).unwrap();
assert_eq!(span, expected);
}
}
#[cfg(test)]
mod whitespace_tests {
#[test]
fn basic() {
use super::all_whitespace;
let input = " \t\n\r";
let kdl_parser = crate::parser::KdlParser::new(input);
assert_eq!(all_whitespace(&kdl_parser)(input), Ok(("", " \t\n\r")));
}
}
#[cfg(test)]
mod comment_tests {
use super::*;
#[test]
fn single_line() {
let input = "// Hello world";
let kdl_parser = crate::parser::KdlParser::new(input);
assert_eq!(comment(&kdl_parser)(input), Ok(("", "// Hello world")));
}
#[test]
fn multi_line() {
let input = "/* Hello world */";
let kdl_parser = crate::parser::KdlParser::new(input);
assert_eq!(comment(&kdl_parser)(input), Ok(("", "/* Hello world */")));
let input = "/* Hello /* world */ blah */";
let kdl_parser = crate::parser::KdlParser::new(input);
assert_eq!(
comment(&kdl_parser)(input),
Ok(("", "/* Hello /* world */ blah */"))
);
}
#[test]
fn slashdash() {
let input = "/-foo 1 2";
let kdl_parser = crate::parser::KdlParser::new(input);
assert_eq!(comment(&kdl_parser)(input), Ok(("", "/-foo 1 2")));
}
#[test]
fn surrounding() {
// assert_eq!(trailing_comments("// foo"), Ok(("", vec!["// foo"])));
// assert_eq!(trailing_comments("/* foo */"), Ok(("", vec!["/* foo */"])));
// assert_eq!(trailing_comments("/* foo */ \n // bar"), Ok(("", vec!["/* foo */", "// bar"])));
}
}
#[cfg(test)]
mod value_tests {
use super::*;
#[test]
fn boolean_val() {
assert_eq!(
value("true"),
Ok(("", ("true".into(), KdlValue::Bool(true))))
);
assert_eq!(
value("false"),
Ok(("", ("false".into(), KdlValue::Bool(false))))
);
}
#[test]
fn null_val() {
assert_eq!(value("null"), Ok(("", ("null".into(), KdlValue::Null))));
}
#[test]
fn binary_val() {
assert_eq!(
value("0b0101"),
Ok(("", ("0b0101".into(), KdlValue::Base2(0b0101))))
);
assert_eq!(
value("0b0101_1111"),
Ok(("", ("0b0101_1111".into(), KdlValue::Base2(0b0101_1111))))
);
assert_eq!(
value("-0b0101"),
Ok(("", ("-0b0101".into(), KdlValue::Base2(-0b0101))))
);
assert_eq!(
value("+0b0101"),
Ok(("", ("+0b0101".into(), KdlValue::Base2(0b0101))))
);
}
#[test]
fn octal_val() {
assert_eq!(
value("0o01234567"),
Ok(("", ("0o01234567".into(), KdlValue::Base8(0o01234567))))
);
assert_eq!(
value("0o123_4567"),
Ok(("", ("0o123_4567".into(), KdlValue::Base8(0o1234567))))
);
assert_eq!(
value("-0o123"),
Ok(("", ("-0o123".into(), KdlValue::Base8(-0o123))))
);
assert_eq!(
value("+0o123"),
Ok(("", ("+0o123".into(), KdlValue::Base8(0o123))))
);
}
#[test]
fn hexadecimal_val() {
assert_eq!(
value("0x0123456789abcdef"),
Ok((
"",
(
"0x0123456789abcdef".into(),
KdlValue::Base16(0x0123456789abcdef)
)
))
);
let input = "node 0x0123_4567_89ab_cdef";
let kdl_parser = crate::parser::KdlParser::new(input);
let (_, n) = node(&kdl_parser)(input).expect("failed to parse node");
assert_eq!(&n[0], &KdlValue::Base16(0x0123456789abcdef));
assert_eq!(
value("0x123_4567"),
Ok(("", ("0x123_4567".into(), KdlValue::Base16(0x1234567))))
);
assert_eq!(
value("-0x123"),
Ok(("", ("-0x123".into(), KdlValue::Base16(-0x123))))
);
assert_eq!(
value("+0x123"),
Ok(("", ("+0x123".into(), KdlValue::Base16(0x123))))
);
}
#[test]
fn integer_val() {
assert_eq!(
value("123_456"),
Ok(("", ("123_456".into(), KdlValue::Base10(123456))))
);
assert_eq!(
value("-123"),
Ok(("", ("-123".into(), KdlValue::Base10(-123))))
);
assert_eq!(
value("+123"),
Ok(("", ("+123".into(), KdlValue::Base10(123))))
);
}
#[test]
fn float_val() {
assert_eq!(
value("123_456.789e-10"),
Ok((
"",
(
"123_456.789e-10".into(),
KdlValue::Base10Float(123_456.789e-10)
)
))
);
assert_eq!(
value("-123.456"),
Ok(("", ("-123.456".into(), KdlValue::Base10Float(-123.456))))
);
assert_eq!(
value("+123.456"),
Ok(("", ("+123.456".into(), KdlValue::Base10Float(123.456))))
);
}
#[test]
fn string_val() {
assert_eq!(
value(r#""Hello \n\u{2020}world""#),
Ok((
"",
(
r#""Hello \n\u{2020}world""#.into(),
KdlValue::String("Hello \n\u{2020}world".into())
)
))
);
}
#[test]
fn raw_string_val() {
assert_eq!(
value(r#"r"Hello \n\u{2020}world""#),
Ok((
"",
(
r#"r"Hello \n\u{2020}world""#.into(),
KdlValue::RawString(r"Hello \n\u{2020}world".into())
)
))
);
assert_eq!(
value(r###"r##"Hello \n\u{2020}world"##"###),
Ok((
"",
(
r###"r##"Hello \n\u{2020}world"##"###.into(),
KdlValue::RawString(r"Hello \n\u{2020}world".into())
)
))
);
}
}
kdl-4.6.0/src/value.rs 0000644 0000000 0000000 00000021227 00726746425 0012712 0 ustar 0000000 0000000 use std::fmt::Display;
/// A specific [KDL Value](https://github.com/kdl-org/kdl/blob/main/SPEC.md#value).
#[derive(Debug, Clone, PartialEq)]
pub enum KdlValue {
/// A [KDL Raw String](https://github.com/kdl-org/kdl/blob/main/SPEC.md#raw-string).
RawString(String),
/// A [KDL String](https://github.com/kdl-org/kdl/blob/main/SPEC.md#string).
String(String),
/// A [KDL
/// Number](https://github.com/kdl-org/kdl/blob/main/SPEC.md#number) in
/// binary form (e.g. `0b010101`).
Base2(i64),
/// A [KDL
/// Number](https://github.com/kdl-org/kdl/blob/main/SPEC.md#number) in
/// octal form (e.g. `0o12345670`).
Base8(i64),
/// A [KDL
/// Number](https://github.com/kdl-org/kdl/blob/main/SPEC.md#number) in
/// decimal form (e.g. `1234567890`).
Base10(i64),
/// A [KDL
/// Number](https://github.com/kdl-org/kdl/blob/main/SPEC.md#number) in
/// decimal form (e.g. `1234567890.123`), interpreted as a Rust f64.
Base10Float(f64),
/// A [KDL
/// Number](https://github.com/kdl-org/kdl/blob/main/SPEC.md#number) in
/// hexadecimal form (e.g. `1234567890abcdef`).
Base16(i64),
/// A [KDL Boolean](https://github.com/kdl-org/kdl/blob/main/SPEC.md#boolean).
Bool(bool),
/// The [KDL Null Value](https://github.com/kdl-org/kdl/blob/main/SPEC.md#null).
Null,
}
impl KdlValue {
/// Returns `true` if the value is a [`KdlValue::RawString`].
pub fn is_raw_string(&self) -> bool {
matches!(self, Self::RawString(..))
}
/// Returns `true` if the value is a [`KdlValue::String`].
pub fn is_string(&self) -> bool {
matches!(self, Self::String(..))
}
/// Returns `true` if the value is a [`KdlValue::String`] or [`KdlValue::RawString`].
pub fn is_string_value(&self) -> bool {
matches!(self, Self::String(..) | Self::RawString(..))
}
/// Returns `true` if the value is a [`KdlValue::Base2`].
pub fn is_base2(&self) -> bool {
matches!(self, Self::Base2(..))
}
/// Returns `true` if the value is a [`KdlValue::Base8`].
pub fn is_base8(&self) -> bool {
matches!(self, Self::Base8(..))
}
/// Returns `true` if the value is a [`KdlValue::Base10`].
pub fn is_base10(&self) -> bool {
matches!(self, Self::Base10(..))
}
/// Returns `true` if the value is a [`KdlValue::Base16`].
pub fn is_base16(&self) -> bool {
matches!(self, Self::Base16(..))
}
/// Returns `true` if the value is a [`KdlValue::Base2`],
/// [`KdlValue::Base8`], [`KdlValue::Base10`], or [`KdlValue::Base16`].
pub fn is_i64_value(&self) -> bool {
matches!(
self,
Self::Base2(..) | Self::Base8(..) | Self::Base10(..) | Self::Base16(..)
)
}
/// Returns `true` if the value is a [`KdlValue::Base10Float`].
pub fn is_base10_float(&self) -> bool {
matches!(self, Self::Base10Float(..))
}
/// Returns `true` if the value is a [`KdlValue::Base10Float`].
pub fn is_float_value(&self) -> bool {
matches!(self, Self::Base10Float(..))
}
/// Returns `true` if the value is a [`KdlValue::Bool`].
pub fn is_bool(&self) -> bool {
matches!(self, Self::Bool(..))
}
/// Returns `true` if the value is a [`KdlValue::Null`].
pub fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
/// Returns `Some(&str)` if the `KdlValue` is a [`KdlValue::RawString`] or a
/// [`KdlValue::String`], otherwise returns `None`.
pub fn as_string(&self) -> Option<&str> {
use KdlValue::*;
match self {
String(s) | RawString(s) => Some(s),
_ => None,
}
}
/// Returns `Some(i64)` if the `KdlValue` is a [`KdlValue::Base2`],
/// [`KdlValue::Base8`], [`KdlValue::Base10`], or [`KdlValue::Base16`],
/// otherwise returns `None`.
pub fn as_i64(&self) -> Option {
use KdlValue::*;
match self {
Base2(i) | Base8(i) | Base10(i) | Base16(i) => Some(*i),
_ => None,
}
}
/// Returns `Some(f64)` if the `KdlValue` is a [`KdlValue::Base10Float`],
/// otherwise returns `None`.
pub fn as_f64(&self) -> Option {
if let Self::Base10Float(v) = self {
Some(*v)
} else {
None
}
}
/// Returns `Some(bool)` if the `KdlValue` is a [`KdlValue::Bool`], otherwise returns `None`.
pub fn as_bool(&self) -> Option {
if let Self::Bool(v) = self {
Some(*v)
} else {
None
}
}
}
impl Display for KdlValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::RawString(_) => self.write_raw_string(f),
Self::String(_) => self.write_string(f),
Self::Base2(value) => write!(f, "0b{:b}", value),
Self::Base8(value) => write!(f, "0o{:o}", value),
Self::Base10(value) => write!(f, "{:?}", value),
Self::Base10Float(value) => write!(
f,
"{:?}",
if value == &f64::INFINITY {
f64::MAX
} else if value == &f64::NEG_INFINITY {
-f64::MAX
} else if value.is_nan() {
0.0
} else {
*value
}
),
Self::Base16(value) => write!(f, "0x{:x}", value),
Self::Bool(value) => write!(f, "{}", value),
Self::Null => write!(f, "null"),
}
}
}
impl KdlValue {
fn write_string(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let string = self.as_string().unwrap();
write!(f, "\"")?;
for char in string.chars() {
match char {
'\\' | '"' => write!(f, "\\{}", char)?,
'\n' => write!(f, "\\n")?,
'\r' => write!(f, "\\r")?,
'\t' => write!(f, "\\t")?,
'\u{08}' => write!(f, "\\b")?,
'\u{0C}' => write!(f, "\\f")?,
_ => write!(f, "{}", char)?,
}
}
write!(f, "\"")?;
Ok(())
}
fn write_raw_string(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let raw = self.as_string().unwrap();
let mut consecutive = 0usize;
let mut maxhash = 0usize;
for char in raw.chars() {
if char == '#' {
consecutive += 1;
} else if char == '"' {
maxhash = maxhash.max(consecutive + 1);
} else {
consecutive = 0;
}
}
write!(f, "r")?;
write!(f, "{}", "#".repeat(maxhash))?;
write!(f, "\"{}\"", raw)?;
write!(f, "{}", "#".repeat(maxhash))?;
Ok(())
}
}
impl From for KdlValue {
fn from(value: i64) -> Self {
KdlValue::Base10(value)
}
}
impl From for KdlValue {
fn from(value: f64) -> Self {
KdlValue::Base10Float(value)
}
}
impl From<&str> for KdlValue {
fn from(value: &str) -> Self {
KdlValue::String(value.to_string())
}
}
impl From for KdlValue {
fn from(value: String) -> Self {
KdlValue::String(value)
}
}
impl From for KdlValue {
fn from(value: bool) -> Self {
KdlValue::Bool(value)
}
}
impl From