feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
+1
@@ -0,0 +1 @@
|
||||
{"files":{".cargo_vcs_info.json":"72692ac658d7a52aa23c651f3b480ef4e0e94ab83a6aec620ea36ea40a972253",".github/workflows/ci.yml":"5a0da1f9728e84c8353b3099bf952f06177024877d6a00b088fa05b53e5a7065",".taplo.toml":"378370a906f719c8dc33bee3083b8ca62b6b11b42209f31ab354f246d9d59dd3","Cargo.lock":"113516ab8136995c417856edd53364ed8df08f565c06d900512e61e97ae79486","Cargo.toml":"b3308bdf41b05098e30cd18a45acacbbe4a92e0c6e1405898615978a1c61e5f7","Cargo.toml.orig":"a664c4dca28108ea2e8977735b91140e7281eb1cabdfffe750c29a6f2d646c26","LICENSE-APACHE":"62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a","LICENSE-MIT":"23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3","README.md":"5619ae83205466278a91e52d0d0ff050440754a847acef802edfd8ed9ab73e0a","examples/decode.rs":"c6b00634e2d59589f47777b9012672856e8a257be84b081fb88f2ec4e4e41cc8","src/de.rs":"95385155e7f1425d086b5fef2941b4df28038fc21743d03871a29c2f2a9d6938","src/error.rs":"59798d362c2794420801a9dd951916f58163312ec133ab48a2c1ecd4c445b5d7","src/lib.rs":"453e8dc2a7f17829f68009ddcec79c49fefc921bfcac4f2ec48805b217a7da5a","src/ser.rs":"5d2b9cc71dcf7b072095a9c63d923384f351bb1b1f869be4afbe03ac65666013","src/tokens.rs":"700d844e20e678d9ead1ae9ddcf427f7249c66d803730deebc39583098dbd22c","tests/README.md":"3e7d07d64c34e117c3d862ee12e4947af8c133fb0b53f6f0de43cc3bfa5057b8","tests/datetime.rs":"b2effdea59ad0b1f856bb67ad3b855e34fea2ef3ddd2453d48d6d30f2b62104a","tests/de-errors.rs":"3ad1abe2153645facfb504dfa1cbfcca2db8cbd9cb0261b98aa20f99c5a5d366","tests/display-tricky.rs":"240f050d9bdf4693d2dec5b64c8ee616c2fb1acff3addaad4da582bf9ea03295","tests/enum_external_deserialize.rs":"1ea8bd4585d76c03ca15136d7ffa44348e235a57ca846180cf20466e5c8edf22","tests/float.rs":"7fd288dbf32764b0456038ca802386b2830bb27687a089c88d51b8861776594b","tests/formatting.rs":"d6904ef6d7689beee0751da6d4d6b74fec4d4ce469234894f4a06b345b7ae620","tests/invalid-encoder/array-mixed-types-ints-and-floats.json":"206d2a353153efbcee74e7ee7b0f852e7a948cfb88417f74607b3ad6f020e373","tests/invalid-misc.rs":"49b5d2664647a6dc31d662565d5972da3d9253a1fb5146584ea79b9c0b6379a6","tests/invalid.rs":"17ada3847954c45a56907d125aafe381bf2fb8cb3d6586fc7a10055b3857b2df","tests/invalid/datetime-malformed-no-leads.toml":"1fa6bf72ea0b96be835ac031408f7acfd0b8d18d5f54b4c0fce8136aad27d22e","tests/invalid/datetime-malformed-no-secs.toml":"b9c30b7fa1847c80e381d68cc79834eefbb8813cd0eff465fb7cbe0733df429b","tests/invalid/datetime-malformed-no-t.toml":"490a46d2e707ef4f87987ec12a22afa5a71c97c5eaa0f4201a174c47846c6a4a","tests/invalid/datetime-malformed-with-milli.toml":"62bfd0a6645bcb3f78211523e5673a3d1fa726d9e942c1643df243d6fba474c8","tests/invalid/duplicate-key-table.toml":"a896ea2180d16fcdd4f6c30aa27529b5b29e7e195a4e7a9944343028783602e9","tests/invalid/duplicate-keys.toml":"4bb0a65c22e7704893a2a51334eb2518af702850ef59aeecd5226e7b26bc07ec","tests/invalid/duplicate-table.toml":"449a36439d737320604c96b2fb86e796195f4801446dfd294c32dcb6ab3a7add","tests/invalid/duplicate-tables.toml":"23b16ce26e1155ee6bf1fff559701dce86475c6a2878536b61f6b7e68be340de","tests/invalid/empty-implicit-table.toml":"d6026110dc0dee7babd69592218819328caa425f48cc879e895b29e48550c06c","tests/invalid/empty-table.toml":"37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570","tests/invalid/float-no-leading-zero.toml":"159654461094c938574ba2d2d09baa3d3c387dd6ed024fd411530c0573a1ec42","tests/invalid/float-no-suffix.toml":"4d6ce54a312e2da26b89c47e19e2c9002ce5fcfe59ec1201f17e240369429d46","tests/invalid/float-no-trailing-digits.toml":"64e4f336186cd096be2804532dbd694dd50ea551d292a9cdbf0bef2abf227101","tests/invalid/key-after-array.toml":"314af33770170b53bf2ec3be43ea1609d981c81d62c968052499b85ed54ccce8","tests/invalid/key-after-table.toml":"ed0dcf38f003d184dd18d1518702da0115cbfb05a5a28cbcf42de2f9bdee05fa","tests/invalid/key-empty.toml":"4303477abf4c0b920b42e96edd61caecf9c1f2d5d97f56af876854cd725aff3c","tests/invalid/key-hash.toml":"cd2a2eba6032d32c829633d3cd2778aeba81f5ff554e69f62af6557d1dc712f6","tests/invalid/key-newline.toml":"06a313a6bd70c2db6a1f5bda479d854d8f87e037e3cabf18fb5db822466ffcac","tests/invalid/key-open-bracket.toml":"52dea939022949290e3a19f1291d15605429344dce3cd1aa1f1568ecad8ca365","tests/invalid/key-single-open-bracket.toml":"245843abef9e72e7efac30138a994bf6301e7e1d7d7042a33d42e863d2638811","tests/invalid/key-space.toml":"b4e336d07c27fb3d0f0a6e50b733e1546202dfd58aaf2f7956f56fd6f075b0a1","tests/invalid/key-start-bracket.toml":"3bd3748a9df1d25ab2661330a3da187bd4da3958292bbf0e8b59d7963634dd87","tests/invalid/key-two-equals.toml":"3ac0c4e339d47c86e57564e43147b772ae83933b78083dc78d0ef77e231df3f1","tests/invalid/string-bad-byte-escape.toml":"c665dcec7c02f442c4fdc80423698eed2376ce65779cf855371772293bec2927","tests/invalid/string-bad-escape.toml":"eeca691fbba3d270f58ae2953d2d1369a773e619e39d8c11f38d6bf6f8472e82","tests/invalid/string-bad-line-ending-escape.toml":"ffa1673378cf4c4153d7f139ea8e4628c1f0aaa721c94b8766a1a3822fb080e4","tests/invalid/string-byte-Line truncated
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"git": {
|
||||
"sha1": "c54c9bd3ade1a609f5da6e31cf6ee72d6c7552fb"
|
||||
},
|
||||
"path_in_vcs": ""
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
schedule: [cron: "40 1 * * *"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
|
||||
jobs:
|
||||
pre_ci:
|
||||
uses: dtolnay/.github/.github/workflows/pre_ci.yml@master
|
||||
|
||||
test:
|
||||
name: Rust ${{matrix.rust}}
|
||||
needs: pre_ci
|
||||
if: needs.pre_ci.outputs.continue
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
rust: [nightly, beta, stable, 1.56.0]
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{matrix.rust}}
|
||||
- name: Enable type layout randomization
|
||||
run: echo RUSTFLAGS=${RUSTFLAGS}\ -Zrandomize-layout >> $GITHUB_ENV
|
||||
if: matrix.rust == 'nightly'
|
||||
- run: cargo test
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: matrix.rust == 'nightly' && always()
|
||||
with:
|
||||
name: Cargo.lock
|
||||
path: Cargo.lock
|
||||
continue-on-error: true
|
||||
|
||||
minimal:
|
||||
name: Minimal versions
|
||||
needs: pre_ci
|
||||
if: needs.pre_ci.outputs.continue
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- run: cargo generate-lockfile -Z minimal-versions
|
||||
- run: cargo check --locked
|
||||
|
||||
doc:
|
||||
name: Documentation
|
||||
needs: pre_ci
|
||||
if: needs.pre_ci.outputs.continue
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
env:
|
||||
RUSTDOCFLAGS: -Dwarnings
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- uses: dtolnay/install@cargo-docs-rs
|
||||
- run: cargo docs-rs
|
||||
|
||||
clippy:
|
||||
name: Clippy
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request'
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@clippy
|
||||
- run: cargo clippy --tests -- -Dclippy::all -Dclippy::pedantic
|
||||
|
||||
miri:
|
||||
name: Miri
|
||||
needs: pre_ci
|
||||
if: needs.pre_ci.outputs.continue
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@miri
|
||||
- run: cargo miri setup
|
||||
- run: cargo miri test
|
||||
env:
|
||||
MIRIFLAGS: -Zmiri-strict-provenance
|
||||
|
||||
outdated:
|
||||
name: Outdated
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request'
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: dtolnay/install@cargo-outdated
|
||||
- run: cargo outdated --workspace --exit-code 1
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
exclude = ["tests/**"]
|
||||
|
||||
[formatting]
|
||||
array_auto_expand = false
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "basic-toml"
|
||||
version = "0.1.10"
|
||||
dependencies = [
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.138"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
|
||||
Vendored
+117
@@ -0,0 +1,117 @@
|
||||
# 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 = "basic-toml"
|
||||
version = "0.1.10"
|
||||
authors = [
|
||||
"Alex Crichton <alex@alexcrichton.com>",
|
||||
"David Tolnay <dtolnay@gmail.com>",
|
||||
]
|
||||
build = false
|
||||
autolib = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "Minimal TOML library with few dependencies"
|
||||
documentation = "https://docs.rs/basic-toml"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"toml",
|
||||
"serde",
|
||||
]
|
||||
categories = [
|
||||
"config",
|
||||
"encoding",
|
||||
"parser-implementations",
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/dtolnay/basic-toml"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = [
|
||||
"--generate-link-to-definition",
|
||||
"--extern-html-root-url=core=https://doc.rust-lang.org",
|
||||
"--extern-html-root-url=alloc=https://doc.rust-lang.org",
|
||||
"--extern-html-root-url=std=https://doc.rust-lang.org",
|
||||
]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[lib]
|
||||
name = "basic_toml"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[example]]
|
||||
name = "decode"
|
||||
path = "examples/decode.rs"
|
||||
|
||||
[[test]]
|
||||
name = "datetime"
|
||||
path = "tests/datetime.rs"
|
||||
|
||||
[[test]]
|
||||
name = "de-errors"
|
||||
path = "tests/de-errors.rs"
|
||||
|
||||
[[test]]
|
||||
name = "display-tricky"
|
||||
path = "tests/display-tricky.rs"
|
||||
|
||||
[[test]]
|
||||
name = "enum_external_deserialize"
|
||||
path = "tests/enum_external_deserialize.rs"
|
||||
|
||||
[[test]]
|
||||
name = "float"
|
||||
path = "tests/float.rs"
|
||||
|
||||
[[test]]
|
||||
name = "formatting"
|
||||
path = "tests/formatting.rs"
|
||||
|
||||
[[test]]
|
||||
name = "invalid"
|
||||
path = "tests/invalid.rs"
|
||||
|
||||
[[test]]
|
||||
name = "invalid-misc"
|
||||
path = "tests/invalid-misc.rs"
|
||||
|
||||
[[test]]
|
||||
name = "parser"
|
||||
path = "tests/parser.rs"
|
||||
|
||||
[[test]]
|
||||
name = "tokens"
|
||||
path = "tests/tokens.rs"
|
||||
|
||||
[[test]]
|
||||
name = "valid"
|
||||
path = "tests/valid.rs"
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0.194"
|
||||
|
||||
[dev-dependencies.semver]
|
||||
version = "1.0.17"
|
||||
features = ["serde"]
|
||||
|
||||
[dev-dependencies.serde]
|
||||
version = "1.0.194"
|
||||
features = ["derive"]
|
||||
|
||||
[dev-dependencies.serde_derive]
|
||||
version = "1.0.194"
|
||||
|
||||
[dev-dependencies.serde_json]
|
||||
version = "1.0.99"
|
||||
Vendored
+176
@@ -0,0 +1,176 @@
|
||||
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
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
Vendored
+36
@@ -0,0 +1,36 @@
|
||||
# basic-toml
|
||||
|
||||
[<img alt="github" src="https://img.shields.io/badge/github-dtolnay/basic--toml-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="20">](https://github.com/dtolnay/basic-toml)
|
||||
[<img alt="crates.io" src="https://img.shields.io/crates/v/basic-toml.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/basic-toml)
|
||||
[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-basic--toml-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/basic-toml)
|
||||
[<img alt="build status" src="https://img.shields.io/github/actions/workflow/status/dtolnay/basic-toml/ci.yml?branch=master&style=for-the-badge" height="20">](https://github.com/dtolnay/basic-toml/actions?query=branch%3Amaster)
|
||||
|
||||
A library for parsing and producing data in [TOML] format using [Serde].
|
||||
|
||||
This crate is a stripped down fork of version 0.5 of the `toml` crate (from
|
||||
before the `toml_edit` rewrite).
|
||||
|
||||
[TOML]: https://toml.io
|
||||
[Serde]: https://serde.rs
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
basic-toml = "0.1"
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
#### License
|
||||
|
||||
<sup>
|
||||
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
|
||||
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
|
||||
</sup>
|
||||
|
||||
<br>
|
||||
|
||||
<sub>
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
|
||||
be dual licensed as above, without any additional terms or conditions.
|
||||
</sub>
|
||||
Vendored
+54
@@ -0,0 +1,54 @@
|
||||
//! An example showing off the usage of `Deserialize` to automatically decode
|
||||
//! TOML into a Rust `struct`
|
||||
|
||||
#![deny(warnings)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
/// This is what we're going to decode into. Each field is optional, meaning
|
||||
/// that it doesn't have to be present in TOML.
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Config {
|
||||
global_string: Option<String>,
|
||||
global_integer: Option<u64>,
|
||||
server: Option<ServerConfig>,
|
||||
peers: Option<Vec<PeerConfig>>,
|
||||
}
|
||||
|
||||
/// Sub-structs are decoded from tables, so this will decode from the `[server]`
|
||||
/// table.
|
||||
///
|
||||
/// Again, each field is optional, meaning they don't have to be present.
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ServerConfig {
|
||||
ip: Option<String>,
|
||||
port: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct PeerConfig {
|
||||
ip: Option<String>,
|
||||
port: Option<u64>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let toml_str = r#"
|
||||
global_string = "test"
|
||||
global_integer = 5
|
||||
|
||||
[server]
|
||||
ip = "127.0.0.1"
|
||||
port = 80
|
||||
|
||||
[[peers]]
|
||||
ip = "127.0.0.1"
|
||||
port = 8080
|
||||
|
||||
[[peers]]
|
||||
ip = "127.0.0.1"
|
||||
"#;
|
||||
|
||||
let decoded: Config = basic_toml::from_str(toml_str).unwrap();
|
||||
println!("{:#?}", decoded);
|
||||
}
|
||||
Vendored
+1694
File diff suppressed because it is too large.
Load diff
Vendored
+54
@@ -0,0 +1,54 @@
|
||||
use std::fmt::{self, Debug, Display};
|
||||
|
||||
/// Errors that can occur when serializing or deserializing TOML.
|
||||
pub struct Error(Box<ErrorInner>);
|
||||
|
||||
pub(crate) enum ErrorInner {
|
||||
Ser(crate::ser::Error),
|
||||
De(crate::de::Error),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Produces a (line, column) pair of the position of the error if
|
||||
/// available.
|
||||
///
|
||||
/// All indexes are 0-based.
|
||||
pub fn line_col(&self) -> Option<(usize, usize)> {
|
||||
match &*self.0 {
|
||||
ErrorInner::Ser(_) => None,
|
||||
ErrorInner::De(error) => error.line_col(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::ser::Error> for Error {
|
||||
fn from(error: crate::ser::Error) -> Self {
|
||||
Error(Box::new(ErrorInner::Ser(error)))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::de::Error> for Error {
|
||||
fn from(error: crate::de::Error) -> Self {
|
||||
Error(Box::new(ErrorInner::De(error)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
match &*self.0 {
|
||||
ErrorInner::Ser(error) => Display::fmt(error, formatter),
|
||||
ErrorInner::De(error) => Display::fmt(error, formatter),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Error {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
match &*self.0 {
|
||||
ErrorInner::Ser(error) => Debug::fmt(error, formatter),
|
||||
ErrorInner::De(error) => Debug::fmt(error, formatter),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
Vendored
+144
@@ -0,0 +1,144 @@
|
||||
//! [![github]](https://github.com/dtolnay/basic-toml) [![crates-io]](https://crates.io/crates/basic-toml) [![docs-rs]](https://docs.rs/basic-toml)
|
||||
//!
|
||||
//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
|
||||
//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
|
||||
//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
|
||||
//!
|
||||
//! <br>
|
||||
//!
|
||||
//! A library for parsing and producing data in [TOML] format using [Serde].
|
||||
//!
|
||||
//! TOML is designed to be "a config file format for humans": minimal and easy
|
||||
//! to read due to obvious semantics.
|
||||
//!
|
||||
//! ```toml
|
||||
//! [package]
|
||||
//! name = "basic-toml"
|
||||
#![doc = concat!("version = \"", env!("CARGO_PKG_VERSION_MAJOR"), ".", env!("CARGO_PKG_VERSION_MINOR"), ".", env!("CARGO_PKG_VERSION_PATCH"), "\"")]
|
||||
//! authors = ["Alex Crichton <alex@alexcrichton.com>"]
|
||||
//!
|
||||
//! [dependencies]
|
||||
//! serde = "1.0"
|
||||
//! ```
|
||||
//!
|
||||
//! The TOML format is widely used throughout the Rust community for
|
||||
//! configuration, notably being used by [Cargo], Rust's package manager.
|
||||
//!
|
||||
//! [TOML]: https://toml.io
|
||||
//! [Serde]: https://serde.rs
|
||||
//! [Cargo]: https://crates.io
|
||||
//!
|
||||
//! # Deserialization
|
||||
//!
|
||||
//! ```
|
||||
//! use semver::{Version, VersionReq};
|
||||
//! use serde_derive::Deserialize;
|
||||
//! use std::collections::BTreeMap as Map;
|
||||
//!
|
||||
//! #[derive(Deserialize)]
|
||||
//! struct Manifest {
|
||||
//! package: Package,
|
||||
//! #[serde(default)]
|
||||
//! dependencies: Map<String, VersionReq>,
|
||||
//! }
|
||||
//!
|
||||
//! #[derive(Deserialize)]
|
||||
//! struct Package {
|
||||
//! name: String,
|
||||
//! version: Version,
|
||||
//! #[serde(default)]
|
||||
//! authors: Vec<String>,
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let manifest: Manifest = basic_toml::from_str(r#"
|
||||
//! [package]
|
||||
//! name = "basic-toml"
|
||||
#![doc = concat!(" version = \"", env!("CARGO_PKG_VERSION_MAJOR"), ".", env!("CARGO_PKG_VERSION_MINOR"), ".", env!("CARGO_PKG_VERSION_PATCH"), "\"")]
|
||||
//! authors = ["Alex Crichton <alex@alexcrichton.com>"]
|
||||
//!
|
||||
//! [dependencies]
|
||||
//! serde = "^1.0"
|
||||
//! "#).unwrap();
|
||||
//!
|
||||
//! assert_eq!(manifest.package.name, "basic-toml");
|
||||
#![doc = concat!(" assert_eq!(manifest.package.version, Version::new(", env!("CARGO_PKG_VERSION_MAJOR"), ", ", env!("CARGO_PKG_VERSION_MINOR"), ", ", env!("CARGO_PKG_VERSION_PATCH"), "));")]
|
||||
//! assert_eq!(manifest.package.authors, ["Alex Crichton <alex@alexcrichton.com>"]);
|
||||
//! assert_eq!(manifest.dependencies["serde"].to_string(), "^1.0");
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! # Serialization
|
||||
//!
|
||||
//! ```
|
||||
//! use semver::{Version, VersionReq};
|
||||
//! use serde_derive::Serialize;
|
||||
//! use std::collections::BTreeMap as Map;
|
||||
//!
|
||||
//! #[derive(Serialize)]
|
||||
//! struct Manifest {
|
||||
//! package: Package,
|
||||
//! dependencies: Map<String, VersionReq>,
|
||||
//! }
|
||||
//!
|
||||
//! #[derive(Serialize)]
|
||||
//! struct Package {
|
||||
//! name: String,
|
||||
//! version: Version,
|
||||
//! authors: Vec<String>,
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let manifest = Manifest {
|
||||
//! package: Package {
|
||||
//! name: "basic-toml".to_owned(),
|
||||
#![doc = concat!(" version: Version::new(", env!("CARGO_PKG_VERSION_MAJOR"), ", ", env!("CARGO_PKG_VERSION_MINOR"), ", ", env!("CARGO_PKG_VERSION_PATCH"), "),")]
|
||||
//! authors: vec!["Alex Crichton <alex@alexcrichton.com>".to_owned()],
|
||||
//! },
|
||||
//! dependencies: {
|
||||
//! let mut dependencies = Map::new();
|
||||
//! dependencies.insert("serde".to_owned(), "^1.0".parse().unwrap());
|
||||
//! dependencies
|
||||
//! },
|
||||
//! };
|
||||
//!
|
||||
//! let toml = basic_toml::to_string(&manifest).unwrap();
|
||||
//! print!("{}", toml);
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! # Spec compatibility
|
||||
//!
|
||||
//! TOML v0.5.0.
|
||||
//!
|
||||
//! TOML's date and time syntax are not supported.
|
||||
|
||||
#![doc(html_root_url = "https://docs.rs/basic-toml/0.1.10")]
|
||||
#![deny(missing_docs)]
|
||||
#![allow(
|
||||
clippy::bool_to_int_with_if,
|
||||
clippy::elidable_lifetime_names,
|
||||
clippy::let_underscore_untyped,
|
||||
clippy::manual_let_else,
|
||||
clippy::manual_range_contains,
|
||||
clippy::match_like_matches_macro,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::must_use_candidate,
|
||||
clippy::needless_doctest_main,
|
||||
clippy::needless_lifetimes,
|
||||
clippy::needless_pass_by_value,
|
||||
clippy::similar_names,
|
||||
clippy::type_complexity,
|
||||
clippy::uninlined_format_args,
|
||||
clippy::unnecessary_box_returns,
|
||||
clippy::unwrap_or_default
|
||||
)]
|
||||
|
||||
mod de;
|
||||
mod error;
|
||||
mod ser;
|
||||
mod tokens;
|
||||
|
||||
pub use crate::de::{from_slice, from_str};
|
||||
pub use crate::error::Error;
|
||||
pub use crate::ser::to_string;
|
||||
Vendored
+833
@@ -0,0 +1,833 @@
|
||||
use serde::ser::{self, Serialize};
|
||||
use std::cell::Cell;
|
||||
use std::error;
|
||||
use std::fmt::{self, Display, Write};
|
||||
|
||||
/// Serialize the given data structure as a String of TOML.
|
||||
///
|
||||
/// Serialization can fail if `T`'s implementation of `Serialize` decides to
|
||||
/// fail, if `T` contains a map with non-string keys, or if `T` attempts to
|
||||
/// serialize an unsupported datatype such as an enum, tuple, or tuple struct.
|
||||
pub fn to_string<T>(value: &T) -> Result<String, crate::Error>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
let mut dst = String::with_capacity(128);
|
||||
value.serialize(&mut Serializer::new(&mut dst))?;
|
||||
Ok(dst)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Error {
|
||||
/// Indicates that a Rust type was requested to be serialized but it was not
|
||||
/// supported.
|
||||
///
|
||||
/// Currently the TOML format does not support serializing types such as
|
||||
/// enums, tuples and tuple structs.
|
||||
UnsupportedType,
|
||||
|
||||
/// The key of all TOML maps must be strings, but serialization was
|
||||
/// attempted where the key of a map was not a string.
|
||||
KeyNotString,
|
||||
|
||||
/// All values in a TOML table must be emitted before further tables are
|
||||
/// emitted. If a value is emitted *after* a table then this error is
|
||||
/// generated.
|
||||
ValueAfterTable,
|
||||
|
||||
/// None was attempted to be serialized, but it's not supported.
|
||||
UnsupportedNone,
|
||||
|
||||
/// A custom error which could be generated when serializing a particular
|
||||
/// type.
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
struct Serializer<'a> {
|
||||
dst: &'a mut String,
|
||||
state: State<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum ArrayState {
|
||||
Started,
|
||||
StartedAsATable,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum State<'a> {
|
||||
Table {
|
||||
key: &'a str,
|
||||
parent: &'a State<'a>,
|
||||
first: &'a Cell<bool>,
|
||||
table_emitted: &'a Cell<bool>,
|
||||
},
|
||||
Array {
|
||||
parent: &'a State<'a>,
|
||||
first: &'a Cell<bool>,
|
||||
type_: &'a Cell<Option<ArrayState>>,
|
||||
len: Option<usize>,
|
||||
},
|
||||
End,
|
||||
}
|
||||
|
||||
struct SerializeSeq<'a, 'b> {
|
||||
ser: &'b mut Serializer<'a>,
|
||||
first: Cell<bool>,
|
||||
type_: Cell<Option<ArrayState>>,
|
||||
len: Option<usize>,
|
||||
}
|
||||
|
||||
struct SerializeTable<'a, 'b> {
|
||||
ser: &'b mut Serializer<'a>,
|
||||
key: String,
|
||||
first: Cell<bool>,
|
||||
table_emitted: Cell<bool>,
|
||||
}
|
||||
|
||||
impl<'a> Serializer<'a> {
|
||||
fn new(dst: &'a mut String) -> Serializer<'a> {
|
||||
Serializer {
|
||||
dst,
|
||||
state: State::End,
|
||||
}
|
||||
}
|
||||
|
||||
fn display<T: Display>(&mut self, t: T, type_: ArrayState) -> Result<(), Error> {
|
||||
self.emit_key(type_)?;
|
||||
write!(self.dst, "{}", t).map_err(ser::Error::custom)?;
|
||||
if let State::Table { .. } = self.state {
|
||||
self.dst.push('\n');
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_key(&mut self, type_: ArrayState) -> Result<(), Error> {
|
||||
self.array_type(type_);
|
||||
let state = self.state.clone();
|
||||
self.do_emit_key(&state)
|
||||
}
|
||||
|
||||
// recursive implementation of `emit_key` above
|
||||
fn do_emit_key(&mut self, state: &State) -> Result<(), Error> {
|
||||
match *state {
|
||||
State::End => Ok(()),
|
||||
State::Array {
|
||||
parent,
|
||||
first,
|
||||
type_,
|
||||
len,
|
||||
} => {
|
||||
assert!(type_.get().is_some());
|
||||
if first.get() {
|
||||
self.do_emit_key(parent)?;
|
||||
}
|
||||
self.emit_array(first, len);
|
||||
Ok(())
|
||||
}
|
||||
State::Table {
|
||||
parent,
|
||||
first,
|
||||
table_emitted,
|
||||
key,
|
||||
} => {
|
||||
if table_emitted.get() {
|
||||
return Err(Error::ValueAfterTable);
|
||||
}
|
||||
if first.get() {
|
||||
self.emit_table_header(parent)?;
|
||||
first.set(false);
|
||||
}
|
||||
self.escape_key(key)?;
|
||||
self.dst.push_str(" = ");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_array(&mut self, first: &Cell<bool>, _len: Option<usize>) {
|
||||
if first.get() {
|
||||
self.dst.push('[');
|
||||
} else {
|
||||
self.dst.push_str(", ");
|
||||
}
|
||||
}
|
||||
|
||||
fn array_type(&mut self, type_: ArrayState) {
|
||||
let prev = match self.state {
|
||||
State::Array { type_, .. } => type_,
|
||||
_ => return,
|
||||
};
|
||||
if prev.get().is_none() {
|
||||
prev.set(Some(type_));
|
||||
}
|
||||
}
|
||||
|
||||
fn escape_key(&mut self, key: &str) -> Result<(), Error> {
|
||||
let ok = !key.is_empty()
|
||||
&& key.chars().all(|c| match c {
|
||||
'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' => true,
|
||||
_ => false,
|
||||
});
|
||||
if ok {
|
||||
write!(self.dst, "{}", key).map_err(ser::Error::custom)?;
|
||||
} else {
|
||||
self.emit_str(key)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_str(&mut self, value: &str) -> Result<(), Error> {
|
||||
self.dst.push('"');
|
||||
for ch in value.chars() {
|
||||
match ch {
|
||||
'\u{8}' => self.dst.push_str("\\b"),
|
||||
'\u{9}' => self.dst.push_str("\\t"),
|
||||
'\u{a}' => self.dst.push_str("\\n"),
|
||||
'\u{c}' => self.dst.push_str("\\f"),
|
||||
'\u{d}' => self.dst.push_str("\\r"),
|
||||
'\u{22}' => self.dst.push_str("\\\""),
|
||||
'\u{5c}' => self.dst.push_str("\\\\"),
|
||||
c if c <= '\u{1f}' || c == '\u{7f}' => {
|
||||
write!(self.dst, "\\u{:04X}", ch as u32).map_err(ser::Error::custom)?;
|
||||
}
|
||||
ch => self.dst.push(ch),
|
||||
}
|
||||
}
|
||||
self.dst.push('"');
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_table_header(&mut self, state: &State) -> Result<(), Error> {
|
||||
let array_of_tables = match *state {
|
||||
State::End => return Ok(()),
|
||||
State::Array { .. } => true,
|
||||
State::Table { .. } => false,
|
||||
};
|
||||
|
||||
// Unlike [..]s, we can't omit [[..]] ancestors, so be sure to emit
|
||||
// table headers for them.
|
||||
let mut p = state;
|
||||
if let State::Array { first, parent, .. } = *state {
|
||||
if first.get() {
|
||||
p = parent;
|
||||
}
|
||||
}
|
||||
while let State::Table { first, parent, .. } = *p {
|
||||
p = parent;
|
||||
if !first.get() {
|
||||
break;
|
||||
}
|
||||
if let State::Array {
|
||||
parent: &State::Table { .. },
|
||||
..
|
||||
} = *parent
|
||||
{
|
||||
self.emit_table_header(parent)?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
match *state {
|
||||
State::Table { first, .. } => {
|
||||
if !first.get() {
|
||||
// Newline if we are a table that is not the first table in
|
||||
// the document.
|
||||
self.dst.push('\n');
|
||||
}
|
||||
}
|
||||
State::Array { parent, first, .. } => {
|
||||
if !first.get() {
|
||||
// Always newline if we are not the first item in the
|
||||
// table-array
|
||||
self.dst.push('\n');
|
||||
} else if let State::Table { first, .. } = *parent {
|
||||
if !first.get() {
|
||||
// Newline if we are not the first item in the document
|
||||
self.dst.push('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
State::End => {}
|
||||
}
|
||||
self.dst.push('[');
|
||||
if array_of_tables {
|
||||
self.dst.push('[');
|
||||
}
|
||||
self.emit_key_part(state)?;
|
||||
if array_of_tables {
|
||||
self.dst.push(']');
|
||||
}
|
||||
self.dst.push_str("]\n");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_key_part(&mut self, key: &State) -> Result<bool, Error> {
|
||||
match *key {
|
||||
State::Array { parent, .. } => self.emit_key_part(parent),
|
||||
State::End => Ok(true),
|
||||
State::Table {
|
||||
key,
|
||||
parent,
|
||||
table_emitted,
|
||||
..
|
||||
} => {
|
||||
table_emitted.set(true);
|
||||
let first = self.emit_key_part(parent)?;
|
||||
if !first {
|
||||
self.dst.push('.');
|
||||
}
|
||||
self.escape_key(key)?;
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! serialize_float {
|
||||
($this:expr, $v:expr) => {{
|
||||
$this.emit_key(ArrayState::Started)?;
|
||||
match ($v.is_sign_negative(), $v.is_nan(), $v == 0.0) {
|
||||
(_, true, _) => write!($this.dst, "nan"),
|
||||
(true, false, true) => write!($this.dst, "-0.0"),
|
||||
(false, false, true) => write!($this.dst, "0.0"),
|
||||
(_, false, false) => write!($this.dst, "{}", $v).and_then(|()| {
|
||||
if $v % 1.0 == 0.0 {
|
||||
write!($this.dst, ".0")
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}),
|
||||
}
|
||||
.map_err(ser::Error::custom)?;
|
||||
|
||||
if let State::Table { .. } = $this.state {
|
||||
$this.dst.push_str("\n");
|
||||
}
|
||||
return Ok(());
|
||||
}};
|
||||
}
|
||||
|
||||
impl<'a, 'b> ser::Serializer for &'b mut Serializer<'a> {
|
||||
type Ok = ();
|
||||
type Error = Error;
|
||||
type SerializeSeq = SerializeSeq<'a, 'b>;
|
||||
type SerializeTuple = SerializeSeq<'a, 'b>;
|
||||
type SerializeTupleStruct = SerializeSeq<'a, 'b>;
|
||||
type SerializeTupleVariant = ser::Impossible<(), Error>;
|
||||
type SerializeMap = SerializeTable<'a, 'b>;
|
||||
type SerializeStruct = SerializeTable<'a, 'b>;
|
||||
type SerializeStructVariant = ser::Impossible<(), Error>;
|
||||
|
||||
fn serialize_bool(self, v: bool) -> Result<(), Self::Error> {
|
||||
self.display(v, ArrayState::Started)
|
||||
}
|
||||
|
||||
fn serialize_i8(self, v: i8) -> Result<(), Self::Error> {
|
||||
self.display(v, ArrayState::Started)
|
||||
}
|
||||
|
||||
fn serialize_i16(self, v: i16) -> Result<(), Self::Error> {
|
||||
self.display(v, ArrayState::Started)
|
||||
}
|
||||
|
||||
fn serialize_i32(self, v: i32) -> Result<(), Self::Error> {
|
||||
self.display(v, ArrayState::Started)
|
||||
}
|
||||
|
||||
fn serialize_i64(self, v: i64) -> Result<(), Self::Error> {
|
||||
self.display(v, ArrayState::Started)
|
||||
}
|
||||
|
||||
fn serialize_u8(self, v: u8) -> Result<(), Self::Error> {
|
||||
self.display(v, ArrayState::Started)
|
||||
}
|
||||
|
||||
fn serialize_u16(self, v: u16) -> Result<(), Self::Error> {
|
||||
self.display(v, ArrayState::Started)
|
||||
}
|
||||
|
||||
fn serialize_u32(self, v: u32) -> Result<(), Self::Error> {
|
||||
self.display(v, ArrayState::Started)
|
||||
}
|
||||
|
||||
fn serialize_u64(self, v: u64) -> Result<(), Self::Error> {
|
||||
self.display(v, ArrayState::Started)
|
||||
}
|
||||
|
||||
fn serialize_f32(self, v: f32) -> Result<(), Self::Error> {
|
||||
serialize_float!(self, v)
|
||||
}
|
||||
|
||||
fn serialize_f64(self, v: f64) -> Result<(), Self::Error> {
|
||||
serialize_float!(self, v)
|
||||
}
|
||||
|
||||
fn serialize_char(self, v: char) -> Result<(), Self::Error> {
|
||||
let mut buf = [0; 4];
|
||||
self.serialize_str(v.encode_utf8(&mut buf))
|
||||
}
|
||||
|
||||
fn serialize_str(self, value: &str) -> Result<(), Self::Error> {
|
||||
self.emit_key(ArrayState::Started)?;
|
||||
self.emit_str(value)?;
|
||||
if let State::Table { .. } = self.state {
|
||||
self.dst.push('\n');
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_bytes(self, value: &[u8]) -> Result<(), Self::Error> {
|
||||
value.serialize(self)
|
||||
}
|
||||
|
||||
fn serialize_none(self) -> Result<(), Self::Error> {
|
||||
Err(Error::UnsupportedNone)
|
||||
}
|
||||
|
||||
fn serialize_some<T>(self, value: &T) -> Result<(), Self::Error>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
value.serialize(self)
|
||||
}
|
||||
|
||||
fn serialize_unit(self) -> Result<(), Self::Error> {
|
||||
Err(Error::UnsupportedType)
|
||||
}
|
||||
|
||||
fn serialize_unit_struct(self, _name: &'static str) -> Result<(), Self::Error> {
|
||||
Err(Error::UnsupportedType)
|
||||
}
|
||||
|
||||
fn serialize_unit_variant(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
variant: &'static str,
|
||||
) -> Result<(), Self::Error> {
|
||||
self.serialize_str(variant)
|
||||
}
|
||||
|
||||
fn serialize_newtype_struct<T>(self, _name: &'static str, value: &T) -> Result<(), Self::Error>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
value.serialize(self)
|
||||
}
|
||||
|
||||
fn serialize_newtype_variant<T>(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
_variant: &'static str,
|
||||
_value: &T,
|
||||
) -> Result<(), Self::Error>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
Err(Error::UnsupportedType)
|
||||
}
|
||||
|
||||
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
|
||||
self.array_type(ArrayState::Started);
|
||||
Ok(SerializeSeq {
|
||||
ser: self,
|
||||
first: Cell::new(true),
|
||||
type_: Cell::new(None),
|
||||
len,
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, Self::Error> {
|
||||
self.serialize_seq(Some(len))
|
||||
}
|
||||
|
||||
fn serialize_tuple_struct(
|
||||
self,
|
||||
_name: &'static str,
|
||||
len: usize,
|
||||
) -> Result<Self::SerializeTupleStruct, Self::Error> {
|
||||
self.serialize_seq(Some(len))
|
||||
}
|
||||
|
||||
fn serialize_tuple_variant(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
_variant: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeTupleVariant, Self::Error> {
|
||||
Err(Error::UnsupportedType)
|
||||
}
|
||||
|
||||
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
|
||||
self.array_type(ArrayState::StartedAsATable);
|
||||
Ok(SerializeTable {
|
||||
ser: self,
|
||||
key: String::new(),
|
||||
first: Cell::new(true),
|
||||
table_emitted: Cell::new(false),
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize_struct(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeStruct, Self::Error> {
|
||||
self.array_type(ArrayState::StartedAsATable);
|
||||
Ok(SerializeTable {
|
||||
ser: self,
|
||||
key: String::new(),
|
||||
first: Cell::new(true),
|
||||
table_emitted: Cell::new(false),
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize_struct_variant(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
_variant: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeStructVariant, Self::Error> {
|
||||
Err(Error::UnsupportedType)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> ser::SerializeSeq for SerializeSeq<'a, 'b> {
|
||||
type Ok = ();
|
||||
type Error = Error;
|
||||
|
||||
fn serialize_element<T>(&mut self, value: &T) -> Result<(), Error>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
value.serialize(&mut Serializer {
|
||||
dst: &mut *self.ser.dst,
|
||||
state: State::Array {
|
||||
parent: &self.ser.state,
|
||||
first: &self.first,
|
||||
type_: &self.type_,
|
||||
len: self.len,
|
||||
},
|
||||
})?;
|
||||
self.first.set(false);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<(), Error> {
|
||||
match self.type_.get() {
|
||||
Some(ArrayState::StartedAsATable) => return Ok(()),
|
||||
Some(ArrayState::Started) => self.ser.dst.push(']'),
|
||||
None => {
|
||||
assert!(self.first.get());
|
||||
self.ser.emit_key(ArrayState::Started)?;
|
||||
self.ser.dst.push_str("[]");
|
||||
}
|
||||
}
|
||||
if let State::Table { .. } = self.ser.state {
|
||||
self.ser.dst.push('\n');
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> ser::SerializeTuple for SerializeSeq<'a, 'b> {
|
||||
type Ok = ();
|
||||
type Error = Error;
|
||||
|
||||
fn serialize_element<T>(&mut self, value: &T) -> Result<(), Error>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
ser::SerializeSeq::serialize_element(self, value)
|
||||
}
|
||||
|
||||
fn end(self) -> Result<(), Error> {
|
||||
ser::SerializeSeq::end(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> ser::SerializeTupleStruct for SerializeSeq<'a, 'b> {
|
||||
type Ok = ();
|
||||
type Error = Error;
|
||||
|
||||
fn serialize_field<T>(&mut self, value: &T) -> Result<(), Error>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
ser::SerializeSeq::serialize_element(self, value)
|
||||
}
|
||||
|
||||
fn end(self) -> Result<(), Error> {
|
||||
ser::SerializeSeq::end(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> ser::SerializeMap for SerializeTable<'a, 'b> {
|
||||
type Ok = ();
|
||||
type Error = Error;
|
||||
|
||||
fn serialize_key<T>(&mut self, input: &T) -> Result<(), Error>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
self.key = input.serialize(StringExtractor)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_value<T>(&mut self, value: &T) -> Result<(), Error>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
let res = value.serialize(&mut Serializer {
|
||||
dst: &mut *self.ser.dst,
|
||||
state: State::Table {
|
||||
key: &self.key,
|
||||
parent: &self.ser.state,
|
||||
first: &self.first,
|
||||
table_emitted: &self.table_emitted,
|
||||
},
|
||||
});
|
||||
match res {
|
||||
Ok(()) => self.first.set(false),
|
||||
Err(Error::UnsupportedNone) => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<(), Error> {
|
||||
if self.first.get() {
|
||||
let state = self.ser.state.clone();
|
||||
self.ser.emit_table_header(&state)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> ser::SerializeStruct for SerializeTable<'a, 'b> {
|
||||
type Ok = ();
|
||||
type Error = Error;
|
||||
|
||||
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<(), Error>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
let res = value.serialize(&mut Serializer {
|
||||
dst: &mut *self.ser.dst,
|
||||
state: State::Table {
|
||||
key,
|
||||
parent: &self.ser.state,
|
||||
first: &self.first,
|
||||
table_emitted: &self.table_emitted,
|
||||
},
|
||||
});
|
||||
match res {
|
||||
Ok(()) => self.first.set(false),
|
||||
Err(Error::UnsupportedNone) => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<(), Error> {
|
||||
if self.first.get() {
|
||||
let state = self.ser.state.clone();
|
||||
self.ser.emit_table_header(&state)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct StringExtractor;
|
||||
|
||||
impl ser::Serializer for StringExtractor {
|
||||
type Ok = String;
|
||||
type Error = Error;
|
||||
type SerializeSeq = ser::Impossible<String, Error>;
|
||||
type SerializeTuple = ser::Impossible<String, Error>;
|
||||
type SerializeTupleStruct = ser::Impossible<String, Error>;
|
||||
type SerializeTupleVariant = ser::Impossible<String, Error>;
|
||||
type SerializeMap = ser::Impossible<String, Error>;
|
||||
type SerializeStruct = ser::Impossible<String, Error>;
|
||||
type SerializeStructVariant = ser::Impossible<String, Error>;
|
||||
|
||||
fn serialize_bool(self, _v: bool) -> Result<String, Self::Error> {
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
|
||||
fn serialize_i8(self, _v: i8) -> Result<String, Self::Error> {
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
|
||||
fn serialize_i16(self, _v: i16) -> Result<String, Self::Error> {
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
|
||||
fn serialize_i32(self, _v: i32) -> Result<String, Self::Error> {
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
|
||||
fn serialize_i64(self, _v: i64) -> Result<String, Self::Error> {
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
|
||||
fn serialize_u8(self, _v: u8) -> Result<String, Self::Error> {
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
|
||||
fn serialize_u16(self, _v: u16) -> Result<String, Self::Error> {
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
|
||||
fn serialize_u32(self, _v: u32) -> Result<String, Self::Error> {
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
|
||||
fn serialize_u64(self, _v: u64) -> Result<String, Self::Error> {
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
|
||||
fn serialize_f32(self, _v: f32) -> Result<String, Self::Error> {
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
|
||||
fn serialize_f64(self, _v: f64) -> Result<String, Self::Error> {
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
|
||||
fn serialize_char(self, _v: char) -> Result<String, Self::Error> {
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
|
||||
fn serialize_str(self, value: &str) -> Result<String, Self::Error> {
|
||||
Ok(value.to_string())
|
||||
}
|
||||
|
||||
fn serialize_bytes(self, _value: &[u8]) -> Result<String, Self::Error> {
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
|
||||
fn serialize_none(self) -> Result<String, Self::Error> {
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
|
||||
fn serialize_some<T>(self, _value: &T) -> Result<String, Self::Error>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
|
||||
fn serialize_unit(self) -> Result<String, Self::Error> {
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
|
||||
fn serialize_unit_struct(self, _name: &'static str) -> Result<String, Self::Error> {
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
|
||||
fn serialize_unit_variant(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
_variant: &'static str,
|
||||
) -> Result<String, Self::Error> {
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
|
||||
fn serialize_newtype_struct<T>(
|
||||
self,
|
||||
_name: &'static str,
|
||||
value: &T,
|
||||
) -> Result<String, Self::Error>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
value.serialize(self)
|
||||
}
|
||||
|
||||
fn serialize_newtype_variant<T>(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
_variant: &'static str,
|
||||
_value: &T,
|
||||
) -> Result<String, Self::Error>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
|
||||
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
|
||||
fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, Self::Error> {
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
|
||||
fn serialize_tuple_struct(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeTupleStruct, Self::Error> {
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
|
||||
fn serialize_tuple_variant(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
_variant: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeTupleVariant, Self::Error> {
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
|
||||
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
|
||||
fn serialize_struct(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeStruct, Self::Error> {
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
|
||||
fn serialize_struct_variant(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
_variant: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeStructVariant, Self::Error> {
|
||||
Err(Error::KeyNotString)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Error::UnsupportedType => "unsupported Rust type".fmt(f),
|
||||
Error::KeyNotString => "map key was not a string".fmt(f),
|
||||
Error::ValueAfterTable => "values must be emitted before tables".fmt(f),
|
||||
Error::UnsupportedNone => "unsupported None value".fmt(f),
|
||||
Error::Custom(ref s) => s.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {}
|
||||
|
||||
impl ser::Error for Error {
|
||||
fn custom<T: Display>(msg: T) -> Error {
|
||||
Error::Custom(msg.to_string())
|
||||
}
|
||||
}
|
||||
Vendored
+545
@@ -0,0 +1,545 @@
|
||||
use std::borrow::Cow;
|
||||
use std::char;
|
||||
use std::str;
|
||||
|
||||
/// A span, designating a range of bytes where a token is located.
|
||||
#[derive(Eq, PartialEq, Debug, Clone, Copy)]
|
||||
pub struct Span {
|
||||
/// The start of the range.
|
||||
pub start: usize,
|
||||
/// The end of the range (exclusive).
|
||||
pub end: usize,
|
||||
}
|
||||
|
||||
impl From<Span> for (usize, usize) {
|
||||
fn from(Span { start, end }: Span) -> (usize, usize) {
|
||||
(start, end)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug)]
|
||||
pub enum Token<'a> {
|
||||
Whitespace(&'a str),
|
||||
Newline,
|
||||
Comment(&'a str),
|
||||
|
||||
Equals,
|
||||
Period,
|
||||
Comma,
|
||||
Colon,
|
||||
Plus,
|
||||
LeftBrace,
|
||||
RightBrace,
|
||||
LeftBracket,
|
||||
RightBracket,
|
||||
|
||||
Keylike(&'a str),
|
||||
String {
|
||||
src: &'a str,
|
||||
val: Cow<'a, str>,
|
||||
multiline: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug)]
|
||||
pub enum Error {
|
||||
InvalidCharInString(usize, char),
|
||||
InvalidEscape(usize, char),
|
||||
InvalidHexEscape(usize, char),
|
||||
InvalidEscapeValue(usize, u32),
|
||||
NewlineInString(usize),
|
||||
Unexpected(usize, char),
|
||||
UnterminatedString(usize),
|
||||
NewlineInTableKey(usize),
|
||||
MultilineStringKey(usize),
|
||||
Wanted {
|
||||
at: usize,
|
||||
expected: &'static str,
|
||||
found: &'static str,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Tokenizer<'a> {
|
||||
input: &'a str,
|
||||
chars: CrlfFold<'a>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct CrlfFold<'a> {
|
||||
chars: str::CharIndices<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum MaybeString {
|
||||
NotEscaped(usize),
|
||||
Owned(String),
|
||||
}
|
||||
|
||||
impl<'a> Tokenizer<'a> {
|
||||
pub fn new(input: &'a str) -> Tokenizer<'a> {
|
||||
let mut t = Tokenizer {
|
||||
input,
|
||||
chars: CrlfFold {
|
||||
chars: input.char_indices(),
|
||||
},
|
||||
};
|
||||
// Eat utf-8 BOM
|
||||
t.eatc('\u{feff}');
|
||||
t
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> Result<Option<(Span, Token<'a>)>, Error> {
|
||||
let (start, token) = match self.one() {
|
||||
Some((start, '\n')) => (start, Token::Newline),
|
||||
Some((start, ' ' | '\t')) => (start, self.whitespace_token(start)),
|
||||
Some((start, '#')) => (start, self.comment_token(start)),
|
||||
Some((start, '=')) => (start, Token::Equals),
|
||||
Some((start, '.')) => (start, Token::Period),
|
||||
Some((start, ',')) => (start, Token::Comma),
|
||||
Some((start, ':')) => (start, Token::Colon),
|
||||
Some((start, '+')) => (start, Token::Plus),
|
||||
Some((start, '{')) => (start, Token::LeftBrace),
|
||||
Some((start, '}')) => (start, Token::RightBrace),
|
||||
Some((start, '[')) => (start, Token::LeftBracket),
|
||||
Some((start, ']')) => (start, Token::RightBracket),
|
||||
Some((start, '\'')) => {
|
||||
return self
|
||||
.literal_string(start)
|
||||
.map(|t| Some((self.step_span(start), t)))
|
||||
}
|
||||
Some((start, '"')) => {
|
||||
return self
|
||||
.basic_string(start)
|
||||
.map(|t| Some((self.step_span(start), t)))
|
||||
}
|
||||
Some((start, ch)) if is_keylike(ch) => (start, self.keylike(start)),
|
||||
|
||||
Some((start, ch)) => return Err(Error::Unexpected(start, ch)),
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let span = self.step_span(start);
|
||||
Ok(Some((span, token)))
|
||||
}
|
||||
|
||||
pub fn peek(&mut self) -> Result<Option<(Span, Token<'a>)>, Error> {
|
||||
self.clone().next()
|
||||
}
|
||||
|
||||
pub fn eat(&mut self, expected: Token<'a>) -> Result<bool, Error> {
|
||||
self.eat_spanned(expected).map(|s| s.is_some())
|
||||
}
|
||||
|
||||
/// Eat a value, returning it's span if it was consumed.
|
||||
pub fn eat_spanned(&mut self, expected: Token<'a>) -> Result<Option<Span>, Error> {
|
||||
let span = match self.peek()? {
|
||||
Some((span, ref found)) if expected == *found => span,
|
||||
Some(_) | None => return Ok(None),
|
||||
};
|
||||
|
||||
drop(self.next());
|
||||
Ok(Some(span))
|
||||
}
|
||||
|
||||
pub fn expect(&mut self, expected: Token<'a>) -> Result<(), Error> {
|
||||
// ignore span
|
||||
let _ = self.expect_spanned(expected)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Expect the given token returning its span.
|
||||
pub fn expect_spanned(&mut self, expected: Token<'a>) -> Result<Span, Error> {
|
||||
let current = self.current();
|
||||
match self.next()? {
|
||||
Some((span, found)) => {
|
||||
if expected == found {
|
||||
Ok(span)
|
||||
} else {
|
||||
Err(Error::Wanted {
|
||||
at: current,
|
||||
expected: expected.describe(),
|
||||
found: found.describe(),
|
||||
})
|
||||
}
|
||||
}
|
||||
None => Err(Error::Wanted {
|
||||
at: self.input.len(),
|
||||
expected: expected.describe(),
|
||||
found: "eof",
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn table_key(&mut self) -> Result<(Span, Cow<'a, str>), Error> {
|
||||
let current = self.current();
|
||||
match self.next()? {
|
||||
Some((span, Token::Keylike(k))) => Ok((span, k.into())),
|
||||
Some((
|
||||
span,
|
||||
Token::String {
|
||||
src,
|
||||
val,
|
||||
multiline,
|
||||
},
|
||||
)) => {
|
||||
let offset = self.substr_offset(src);
|
||||
if multiline {
|
||||
return Err(Error::MultilineStringKey(offset));
|
||||
}
|
||||
match src.find('\n') {
|
||||
None => Ok((span, val)),
|
||||
Some(i) => Err(Error::NewlineInTableKey(offset + i)),
|
||||
}
|
||||
}
|
||||
Some((_, other)) => Err(Error::Wanted {
|
||||
at: current,
|
||||
expected: "a table key",
|
||||
found: other.describe(),
|
||||
}),
|
||||
None => Err(Error::Wanted {
|
||||
at: self.input.len(),
|
||||
expected: "a table key",
|
||||
found: "eof",
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eat_whitespace(&mut self) {
|
||||
while self.eatc(' ') || self.eatc('\t') {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eat_comment(&mut self) -> Result<bool, Error> {
|
||||
if !self.eatc('#') {
|
||||
return Ok(false);
|
||||
}
|
||||
drop(self.comment_token(0));
|
||||
self.eat_newline_or_eof().map(|()| true)
|
||||
}
|
||||
|
||||
pub fn eat_newline_or_eof(&mut self) -> Result<(), Error> {
|
||||
let current = self.current();
|
||||
match self.next()? {
|
||||
None | Some((_, Token::Newline)) => Ok(()),
|
||||
Some((_, other)) => Err(Error::Wanted {
|
||||
at: current,
|
||||
expected: "newline",
|
||||
found: other.describe(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn skip_to_newline(&mut self) {
|
||||
loop {
|
||||
match self.one() {
|
||||
Some((_, '\n')) | None => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn eatc(&mut self, ch: char) -> bool {
|
||||
match self.chars.clone().next() {
|
||||
Some((_, ch2)) if ch == ch2 => {
|
||||
self.one();
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current(&mut self) -> usize {
|
||||
match self.chars.clone().next() {
|
||||
Some(i) => i.0,
|
||||
None => self.input.len(),
|
||||
}
|
||||
}
|
||||
|
||||
fn whitespace_token(&mut self, start: usize) -> Token<'a> {
|
||||
while self.eatc(' ') || self.eatc('\t') {
|
||||
// ...
|
||||
}
|
||||
Token::Whitespace(&self.input[start..self.current()])
|
||||
}
|
||||
|
||||
fn comment_token(&mut self, start: usize) -> Token<'a> {
|
||||
while let Some((_, ch)) = self.chars.clone().next() {
|
||||
if ch != '\t' && (ch < '\u{20}' || ch > '\u{10ffff}') {
|
||||
break;
|
||||
}
|
||||
self.one();
|
||||
}
|
||||
Token::Comment(&self.input[start..self.current()])
|
||||
}
|
||||
|
||||
fn read_string(
|
||||
&mut self,
|
||||
delim: char,
|
||||
start: usize,
|
||||
new_ch: &mut dyn FnMut(
|
||||
&mut Tokenizer,
|
||||
&mut MaybeString,
|
||||
bool,
|
||||
usize,
|
||||
char,
|
||||
) -> Result<(), Error>,
|
||||
) -> Result<Token<'a>, Error> {
|
||||
let mut multiline = false;
|
||||
if self.eatc(delim) {
|
||||
if self.eatc(delim) {
|
||||
multiline = true;
|
||||
} else {
|
||||
return Ok(Token::String {
|
||||
src: &self.input[start..start + 2],
|
||||
val: Cow::Borrowed(""),
|
||||
multiline: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
let mut val = MaybeString::NotEscaped(self.current());
|
||||
let mut n = 0;
|
||||
loop {
|
||||
n += 1;
|
||||
match self.one() {
|
||||
Some((i, '\n')) => {
|
||||
if multiline {
|
||||
if self.input.as_bytes()[i] == b'\r' {
|
||||
val.make_owned(&self.input[..i]);
|
||||
}
|
||||
if n == 1 {
|
||||
val = MaybeString::NotEscaped(self.current());
|
||||
} else {
|
||||
val.push('\n');
|
||||
}
|
||||
} else {
|
||||
return Err(Error::NewlineInString(i));
|
||||
}
|
||||
}
|
||||
Some((mut i, ch)) if ch == delim => {
|
||||
if multiline {
|
||||
if !self.eatc(delim) {
|
||||
val.push(delim);
|
||||
continue;
|
||||
}
|
||||
if !self.eatc(delim) {
|
||||
val.push(delim);
|
||||
val.push(delim);
|
||||
continue;
|
||||
}
|
||||
if self.eatc(delim) {
|
||||
val.push(delim);
|
||||
i += 1;
|
||||
}
|
||||
if self.eatc(delim) {
|
||||
val.push(delim);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
return Ok(Token::String {
|
||||
src: &self.input[start..self.current()],
|
||||
val: val.into_cow(&self.input[..i]),
|
||||
multiline,
|
||||
});
|
||||
}
|
||||
Some((i, c)) => new_ch(self, &mut val, multiline, i, c)?,
|
||||
None => return Err(Error::UnterminatedString(start)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn literal_string(&mut self, start: usize) -> Result<Token<'a>, Error> {
|
||||
self.read_string('\'', start, &mut |_me, val, _multi, i, ch| {
|
||||
if ch == '\u{09}' || ('\u{20}' <= ch && ch <= '\u{10ffff}' && ch != '\u{7f}') {
|
||||
val.push(ch);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::InvalidCharInString(i, ch))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn basic_string(&mut self, start: usize) -> Result<Token<'a>, Error> {
|
||||
self.read_string('"', start, &mut |me, val, multi, i, ch| match ch {
|
||||
'\\' => {
|
||||
val.make_owned(&me.input[..i]);
|
||||
match me.chars.next() {
|
||||
Some((_, '"')) => val.push('"'),
|
||||
Some((_, '\\')) => val.push('\\'),
|
||||
Some((_, 'b')) => val.push('\u{8}'),
|
||||
Some((_, 'f')) => val.push('\u{c}'),
|
||||
Some((_, 'n')) => val.push('\n'),
|
||||
Some((_, 'r')) => val.push('\r'),
|
||||
Some((_, 't')) => val.push('\t'),
|
||||
Some((i, c @ ('u' | 'U'))) => {
|
||||
let len = if c == 'u' { 4 } else { 8 };
|
||||
val.push(me.hex(start, i, len)?);
|
||||
}
|
||||
Some((i, c @ (' ' | '\t' | '\n'))) if multi => {
|
||||
if c != '\n' {
|
||||
while let Some((_, ch)) = me.chars.clone().next() {
|
||||
match ch {
|
||||
' ' | '\t' => {
|
||||
me.chars.next();
|
||||
}
|
||||
'\n' => {
|
||||
me.chars.next();
|
||||
break;
|
||||
}
|
||||
_ => return Err(Error::InvalidEscape(i, c)),
|
||||
}
|
||||
}
|
||||
}
|
||||
while let Some((_, ch)) = me.chars.clone().next() {
|
||||
match ch {
|
||||
' ' | '\t' | '\n' => {
|
||||
me.chars.next();
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
Some((i, c)) => return Err(Error::InvalidEscape(i, c)),
|
||||
None => return Err(Error::UnterminatedString(start)),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ch if ch == '\u{09}' || ('\u{20}' <= ch && ch <= '\u{10ffff}' && ch != '\u{7f}') => {
|
||||
val.push(ch);
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(Error::InvalidCharInString(i, ch)),
|
||||
})
|
||||
}
|
||||
|
||||
fn hex(&mut self, start: usize, i: usize, len: usize) -> Result<char, Error> {
|
||||
let mut buf = String::with_capacity(len);
|
||||
for _ in 0..len {
|
||||
match self.one() {
|
||||
Some((_, ch)) if ch as u32 <= 0x7F && ch.is_ascii_hexdigit() => buf.push(ch),
|
||||
Some((i, ch)) => return Err(Error::InvalidHexEscape(i, ch)),
|
||||
None => return Err(Error::UnterminatedString(start)),
|
||||
}
|
||||
}
|
||||
let val = u32::from_str_radix(&buf, 16).unwrap();
|
||||
match char::from_u32(val) {
|
||||
Some(ch) => Ok(ch),
|
||||
None => Err(Error::InvalidEscapeValue(i, val)),
|
||||
}
|
||||
}
|
||||
|
||||
fn keylike(&mut self, start: usize) -> Token<'a> {
|
||||
while let Some((_, ch)) = self.peek_one() {
|
||||
if !is_keylike(ch) {
|
||||
break;
|
||||
}
|
||||
self.one();
|
||||
}
|
||||
Token::Keylike(&self.input[start..self.current()])
|
||||
}
|
||||
|
||||
pub fn substr_offset(&self, s: &'a str) -> usize {
|
||||
assert!(s.len() <= self.input.len());
|
||||
let a = self.input.as_ptr() as usize;
|
||||
let b = s.as_ptr() as usize;
|
||||
assert!(a <= b);
|
||||
b - a
|
||||
}
|
||||
|
||||
/// Calculate the span of a single character.
|
||||
fn step_span(&mut self, start: usize) -> Span {
|
||||
let end = match self.peek_one() {
|
||||
Some(t) => t.0,
|
||||
None => self.input.len(),
|
||||
};
|
||||
Span { start, end }
|
||||
}
|
||||
|
||||
/// Peek one char without consuming it.
|
||||
fn peek_one(&mut self) -> Option<(usize, char)> {
|
||||
self.chars.clone().next()
|
||||
}
|
||||
|
||||
/// Take one char.
|
||||
pub fn one(&mut self) -> Option<(usize, char)> {
|
||||
self.chars.next()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for CrlfFold<'a> {
|
||||
type Item = (usize, char);
|
||||
|
||||
fn next(&mut self) -> Option<(usize, char)> {
|
||||
self.chars.next().map(|(i, c)| {
|
||||
if c == '\r' {
|
||||
let mut attempt = self.chars.clone();
|
||||
if let Some((_, '\n')) = attempt.next() {
|
||||
self.chars = attempt;
|
||||
return (i, '\n');
|
||||
}
|
||||
}
|
||||
(i, c)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl MaybeString {
|
||||
fn push(&mut self, ch: char) {
|
||||
match *self {
|
||||
MaybeString::NotEscaped(..) => {}
|
||||
MaybeString::Owned(ref mut s) => s.push(ch),
|
||||
}
|
||||
}
|
||||
|
||||
fn make_owned(&mut self, input: &str) {
|
||||
match *self {
|
||||
MaybeString::NotEscaped(start) => {
|
||||
*self = MaybeString::Owned(input[start..].to_owned());
|
||||
}
|
||||
MaybeString::Owned(..) => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn into_cow(self, input: &str) -> Cow<str> {
|
||||
match self {
|
||||
MaybeString::NotEscaped(start) => Cow::Borrowed(&input[start..]),
|
||||
MaybeString::Owned(s) => Cow::Owned(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_keylike(ch: char) -> bool {
|
||||
('A' <= ch && ch <= 'Z')
|
||||
|| ('a' <= ch && ch <= 'z')
|
||||
|| ('0' <= ch && ch <= '9')
|
||||
|| ch == '-'
|
||||
|| ch == '_'
|
||||
}
|
||||
|
||||
impl<'a> Token<'a> {
|
||||
pub fn describe(&self) -> &'static str {
|
||||
match *self {
|
||||
Token::Keylike(_) => "an identifier",
|
||||
Token::Equals => "an equals",
|
||||
Token::Period => "a period",
|
||||
Token::Comment(_) => "a comment",
|
||||
Token::Newline => "a newline",
|
||||
Token::Whitespace(_) => "whitespace",
|
||||
Token::Comma => "a comma",
|
||||
Token::RightBrace => "a right brace",
|
||||
Token::LeftBrace => "a left brace",
|
||||
Token::RightBracket => "a right bracket",
|
||||
Token::LeftBracket => "a left bracket",
|
||||
Token::String { multiline, .. } => {
|
||||
if multiline {
|
||||
"a multiline string"
|
||||
} else {
|
||||
"a string"
|
||||
}
|
||||
}
|
||||
Token::Colon => "a colon",
|
||||
Token::Plus => "a plus",
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
Tests are from https://github.com/BurntSushi/toml-test
|
||||
Vendored
+142
@@ -0,0 +1,142 @@
|
||||
use serde_json::Value;
|
||||
|
||||
macro_rules! bad {
|
||||
($toml:expr, $msg:expr) => {
|
||||
match basic_toml::from_str::<Value>($toml) {
|
||||
Ok(s) => panic!("parsed to: {:#?}", s),
|
||||
Err(e) => assert_eq!(e.to_string(), $msg),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn times() {
|
||||
fn multi_bad(s: &str, msg: &str) {
|
||||
bad!(s, msg);
|
||||
bad!(&s.replace('T', " "), msg);
|
||||
bad!(&s.replace('T', "t"), msg);
|
||||
bad!(&s.replace('Z', "z"), msg);
|
||||
}
|
||||
|
||||
multi_bad(
|
||||
"foo = 1997-09-09T09:09:09Z",
|
||||
"invalid number at line 1 column 7",
|
||||
);
|
||||
multi_bad(
|
||||
"foo = 1997-09-09T09:09:09+09:09",
|
||||
"invalid number at line 1 column 7",
|
||||
);
|
||||
multi_bad(
|
||||
"foo = 1997-09-09T09:09:09-09:09",
|
||||
"invalid number at line 1 column 7",
|
||||
);
|
||||
multi_bad(
|
||||
"foo = 1997-09-09T09:09:09",
|
||||
"invalid number at line 1 column 7",
|
||||
);
|
||||
multi_bad("foo = 1997-09-09", "invalid number at line 1 column 7");
|
||||
bad!("foo = 1997-09-09 ", "invalid number at line 1 column 7");
|
||||
bad!(
|
||||
"foo = 1997-09-09 # comment",
|
||||
"invalid number at line 1 column 7"
|
||||
);
|
||||
multi_bad("foo = 09:09:09", "invalid number at line 1 column 8");
|
||||
multi_bad(
|
||||
"foo = 1997-09-09T09:09:09.09Z",
|
||||
"invalid number at line 1 column 7",
|
||||
);
|
||||
multi_bad(
|
||||
"foo = 1997-09-09T09:09:09.09+09:09",
|
||||
"invalid number at line 1 column 7",
|
||||
);
|
||||
multi_bad(
|
||||
"foo = 1997-09-09T09:09:09.09-09:09",
|
||||
"invalid number at line 1 column 7",
|
||||
);
|
||||
multi_bad(
|
||||
"foo = 1997-09-09T09:09:09.09",
|
||||
"invalid number at line 1 column 7",
|
||||
);
|
||||
multi_bad("foo = 09:09:09.09", "invalid number at line 1 column 8");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_times() {
|
||||
bad!("foo = 199-09-09", "invalid number at line 1 column 7");
|
||||
bad!("foo = 199709-09", "invalid number at line 1 column 7");
|
||||
bad!("foo = 1997-9-09", "invalid number at line 1 column 7");
|
||||
bad!("foo = 1997-09-9", "invalid number at line 1 column 7");
|
||||
bad!(
|
||||
"foo = 1997-09-0909:09:09",
|
||||
"invalid number at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = 1997-09-09T09:09:09.",
|
||||
"invalid number at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = T",
|
||||
"invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = T.",
|
||||
"invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = TZ",
|
||||
"invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = 1997-09-09T09:09:09.09+",
|
||||
"invalid number at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = 1997-09-09T09:09:09.09+09",
|
||||
"invalid number at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = 1997-09-09T09:09:09.09+09:9",
|
||||
"invalid number at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = 1997-09-09T09:09:09.09+0909",
|
||||
"invalid number at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = 1997-09-09T09:09:09.09-",
|
||||
"invalid number at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = 1997-09-09T09:09:09.09-09",
|
||||
"invalid number at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = 1997-09-09T09:09:09.09-09:9",
|
||||
"invalid number at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = 1997-09-09T09:09:09.09-0909",
|
||||
"invalid number at line 1 column 7"
|
||||
);
|
||||
|
||||
bad!(
|
||||
"foo = 1997-00-09T09:09:09.09Z",
|
||||
"invalid number at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = 1997-09-00T09:09:09.09Z",
|
||||
"invalid number at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = 1997-09-09T30:09:09.09Z",
|
||||
"invalid number at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = 1997-09-09T12:69:09.09Z",
|
||||
"invalid number at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = 1997-09-09T12:09:69.09Z",
|
||||
"invalid number at line 1 column 7"
|
||||
);
|
||||
}
|
||||
Vendored
+354
@@ -0,0 +1,354 @@
|
||||
#![allow(
|
||||
clippy::elidable_lifetime_names,
|
||||
clippy::needless_lifetimes,
|
||||
clippy::too_many_lines
|
||||
)]
|
||||
|
||||
use serde::{de, Deserialize};
|
||||
use std::fmt;
|
||||
|
||||
macro_rules! bad {
|
||||
($toml:expr, $ty:ty, $msg:expr) => {
|
||||
match basic_toml::from_str::<$ty>($toml) {
|
||||
Ok(s) => panic!("parsed to: {:#?}", s),
|
||||
Err(e) => assert_eq!(e.to_string(), $msg),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
struct Parent<T> {
|
||||
p_a: T,
|
||||
p_b: Vec<Child<T>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct Child<T> {
|
||||
c_a: T,
|
||||
c_b: T,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum CasedString {
|
||||
Lowercase(String),
|
||||
Uppercase(String),
|
||||
}
|
||||
|
||||
impl<'de> de::Deserialize<'de> for CasedString {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: de::Deserializer<'de>,
|
||||
{
|
||||
struct CasedStringVisitor;
|
||||
|
||||
impl<'de> de::Visitor<'de> for CasedStringVisitor {
|
||||
type Value = CasedString;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a string")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
if s.is_empty() {
|
||||
Err(de::Error::invalid_length(0, &"a non-empty string"))
|
||||
} else if s.chars().all(|x| x.is_ascii_lowercase()) {
|
||||
Ok(CasedString::Lowercase(s.to_string()))
|
||||
} else if s.chars().all(|x| x.is_ascii_uppercase()) {
|
||||
Ok(CasedString::Uppercase(s.to_string()))
|
||||
} else {
|
||||
Err(de::Error::invalid_value(
|
||||
de::Unexpected::Str(s),
|
||||
&"all lowercase or all uppercase",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(CasedStringVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_errors() {
|
||||
basic_toml::from_str::<Parent<CasedString>>(
|
||||
"
|
||||
p_a = 'a'
|
||||
p_b = [{c_a = 'a', c_b = 'c'}]
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Custom error at p_b value.
|
||||
bad!(
|
||||
"
|
||||
p_a = ''
|
||||
# ^
|
||||
",
|
||||
Parent<CasedString>,
|
||||
"invalid length 0, expected a non-empty string for key `p_a` at line 2 column 19"
|
||||
);
|
||||
|
||||
// Missing field in table.
|
||||
bad!(
|
||||
"
|
||||
p_a = 'a'
|
||||
# ^
|
||||
",
|
||||
Parent<CasedString>,
|
||||
"missing field `p_b` at line 1 column 1"
|
||||
);
|
||||
|
||||
// Invalid type in p_b.
|
||||
bad!(
|
||||
"
|
||||
p_a = 'a'
|
||||
p_b = 1
|
||||
# ^
|
||||
",
|
||||
Parent<CasedString>,
|
||||
"invalid type: integer `1`, expected a sequence for key `p_b` at line 3 column 19"
|
||||
);
|
||||
|
||||
// Sub-table in Vec is missing a field.
|
||||
bad!(
|
||||
"
|
||||
p_a = 'a'
|
||||
p_b = [
|
||||
{c_a = 'a'}
|
||||
# ^
|
||||
]
|
||||
",
|
||||
Parent<CasedString>,
|
||||
"missing field `c_b` for key `p_b` at line 4 column 17"
|
||||
);
|
||||
|
||||
// Sub-table in Vec has a field with a bad value.
|
||||
bad!(
|
||||
"
|
||||
p_a = 'a'
|
||||
p_b = [
|
||||
{c_a = 'a', c_b = '*'}
|
||||
# ^
|
||||
]
|
||||
",
|
||||
Parent<CasedString>,
|
||||
"invalid value: string \"*\", expected all lowercase or all uppercase for key `p_b` at line 4 column 35"
|
||||
);
|
||||
|
||||
// Sub-table in Vec is missing a field.
|
||||
bad!(
|
||||
"
|
||||
p_a = 'a'
|
||||
p_b = [
|
||||
{c_a = 'a', c_b = 'b'},
|
||||
{c_a = 'aa'}
|
||||
# ^
|
||||
]
|
||||
",
|
||||
Parent<CasedString>,
|
||||
"missing field `c_b` for key `p_b` at line 5 column 17"
|
||||
);
|
||||
|
||||
// Sub-table in the middle of a Vec is missing a field.
|
||||
bad!(
|
||||
"
|
||||
p_a = 'a'
|
||||
p_b = [
|
||||
{c_a = 'a', c_b = 'b'},
|
||||
{c_a = 'aa'},
|
||||
# ^
|
||||
{c_a = 'aaa', c_b = 'bbb'},
|
||||
]
|
||||
",
|
||||
Parent<CasedString>,
|
||||
"missing field `c_b` for key `p_b` at line 5 column 17"
|
||||
);
|
||||
|
||||
// Sub-table in the middle of a Vec has a field with a bad value.
|
||||
bad!(
|
||||
"
|
||||
p_a = 'a'
|
||||
p_b = [
|
||||
{c_a = 'a', c_b = 'b'},
|
||||
{c_a = 'aa', c_b = 1},
|
||||
# ^
|
||||
{c_a = 'aaa', c_b = 'bbb'},
|
||||
]
|
||||
",
|
||||
Parent<CasedString>,
|
||||
"invalid type: integer `1`, expected a string for key `p_b` at line 5 column 36"
|
||||
);
|
||||
|
||||
// Sub-table in the middle of a Vec has an extra field.
|
||||
// FIXME: This location could be better.
|
||||
bad!(
|
||||
"
|
||||
p_a = 'a'
|
||||
p_b = [
|
||||
{c_a = 'a', c_b = 'b'},
|
||||
{c_a = 'aa', c_b = 'bb', c_d = 'd'},
|
||||
# ^
|
||||
{c_a = 'aaa', c_b = 'bbb'},
|
||||
{c_a = 'aaaa', c_b = 'bbbb'},
|
||||
]
|
||||
",
|
||||
Parent<CasedString>,
|
||||
"unknown field `c_d`, expected `c_a` or `c_b` for key `p_b` at line 5 column 17"
|
||||
);
|
||||
|
||||
// Sub-table in the middle of a Vec is missing a field.
|
||||
// FIXME: This location is pretty off.
|
||||
bad!(
|
||||
"
|
||||
p_a = 'a'
|
||||
[[p_b]]
|
||||
c_a = 'a'
|
||||
c_b = 'b'
|
||||
[[p_b]]
|
||||
c_a = 'aa'
|
||||
# c_b = 'bb' # <- missing field
|
||||
[[p_b]]
|
||||
c_a = 'aaa'
|
||||
c_b = 'bbb'
|
||||
[[p_b]]
|
||||
# ^
|
||||
c_a = 'aaaa'
|
||||
c_b = 'bbbb'
|
||||
",
|
||||
Parent<CasedString>,
|
||||
"missing field `c_b` for key `p_b` at line 12 column 13"
|
||||
);
|
||||
|
||||
// Sub-table in the middle of a Vec has a field with a bad value.
|
||||
bad!(
|
||||
"
|
||||
p_a = 'a'
|
||||
[[p_b]]
|
||||
c_a = 'a'
|
||||
c_b = 'b'
|
||||
[[p_b]]
|
||||
c_a = 'aa'
|
||||
c_b = '*'
|
||||
# ^
|
||||
[[p_b]]
|
||||
c_a = 'aaa'
|
||||
c_b = 'bbb'
|
||||
",
|
||||
Parent<CasedString>,
|
||||
"invalid value: string \"*\", expected all lowercase or all uppercase for key `p_b.c_b` at line 8 column 19"
|
||||
);
|
||||
|
||||
// Sub-table in the middle of a Vec has an extra field.
|
||||
// FIXME: This location is pretty off.
|
||||
bad!(
|
||||
"
|
||||
p_a = 'a'
|
||||
[[p_b]]
|
||||
c_a = 'a'
|
||||
c_b = 'b'
|
||||
[[p_b]]
|
||||
c_a = 'aa'
|
||||
c_d = 'dd' # unknown field
|
||||
[[p_b]]
|
||||
c_a = 'aaa'
|
||||
c_b = 'bbb'
|
||||
[[p_b]]
|
||||
# ^
|
||||
c_a = 'aaaa'
|
||||
c_b = 'bbbb'
|
||||
",
|
||||
Parent<CasedString>,
|
||||
"unknown field `c_d`, expected `c_a` or `c_b` for key `p_b` at line 12 column 13"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_derive_deserialize_errors() {
|
||||
bad!(
|
||||
"
|
||||
p_a = ''
|
||||
# ^
|
||||
",
|
||||
Parent<String>,
|
||||
"missing field `p_b` at line 1 column 1"
|
||||
);
|
||||
|
||||
bad!(
|
||||
"
|
||||
p_a = ''
|
||||
p_b = [
|
||||
{c_a = ''}
|
||||
# ^
|
||||
]
|
||||
",
|
||||
Parent<String>,
|
||||
"missing field `c_b` for key `p_b` at line 4 column 17"
|
||||
);
|
||||
|
||||
bad!(
|
||||
"
|
||||
p_a = ''
|
||||
p_b = [
|
||||
{c_a = '', c_b = 1}
|
||||
# ^
|
||||
]
|
||||
",
|
||||
Parent<String>,
|
||||
"invalid type: integer `1`, expected a string for key `p_b` at line 4 column 34"
|
||||
);
|
||||
|
||||
// FIXME: This location could be better.
|
||||
bad!(
|
||||
"
|
||||
p_a = ''
|
||||
p_b = [
|
||||
{c_a = '', c_b = '', c_d = ''},
|
||||
# ^
|
||||
]
|
||||
",
|
||||
Parent<String>,
|
||||
"unknown field `c_d`, expected `c_a` or `c_b` for key `p_b` at line 4 column 17"
|
||||
);
|
||||
|
||||
bad!(
|
||||
"
|
||||
p_a = 'a'
|
||||
p_b = [
|
||||
{c_a = '', c_b = 1, c_d = ''},
|
||||
# ^
|
||||
]
|
||||
",
|
||||
Parent<String>,
|
||||
"invalid type: integer `1`, expected a string for key `p_b` at line 4 column 34"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_handles_crlf() {
|
||||
bad!(
|
||||
"\r\n\
|
||||
[t1]\r\n\
|
||||
[t2]\r\n\
|
||||
a = 1\r\n\
|
||||
. = 2\r\n\
|
||||
",
|
||||
serde_json::Value,
|
||||
"expected a table key, found a period at line 5 column 1"
|
||||
);
|
||||
|
||||
// Should be the same as above.
|
||||
bad!(
|
||||
"\n\
|
||||
[t1]\n\
|
||||
[t2]\n\
|
||||
a = 1\n\
|
||||
. = 2\n\
|
||||
",
|
||||
serde_json::Value,
|
||||
"expected a table key, found a period at line 5 column 1"
|
||||
);
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Recipe {
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
#[serde(default)]
|
||||
pub modules: Vec<Modules>,
|
||||
#[serde(default)]
|
||||
pub packages: Vec<Packages>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Modules {
|
||||
pub name: String,
|
||||
pub version: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Packages {
|
||||
pub name: String,
|
||||
pub version: Option<String>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn both_ends() {
|
||||
let recipe_works = basic_toml::from_str::<Recipe>(
|
||||
r#"
|
||||
name = "testing"
|
||||
description = "example"
|
||||
modules = []
|
||||
|
||||
[[packages]]
|
||||
name = "base"
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
basic_toml::to_string(&recipe_works).unwrap();
|
||||
|
||||
let recipe_fails = basic_toml::from_str::<Recipe>(
|
||||
r#"
|
||||
name = "testing"
|
||||
description = "example"
|
||||
packages = []
|
||||
|
||||
[[modules]]
|
||||
name = "base"
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let err = basic_toml::to_string(&recipe_fails).unwrap_err();
|
||||
assert_eq!(err.to_string(), "values must be emitted before tables");
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#![allow(clippy::wildcard_imports)]
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
struct Struct {
|
||||
value: Enum,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
enum Enum {
|
||||
Variant,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_variant() {
|
||||
let error = basic_toml::from_str::<Struct>("value = \"NonExistent\"").unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
"unknown variant `NonExistent`, expected `Variant` for key `value` at line 1 column 1"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_str() {
|
||||
let s = basic_toml::from_str::<Struct>("value = \"Variant\"").unwrap();
|
||||
|
||||
assert_eq!(Enum::Variant, s.value);
|
||||
}
|
||||
Vendored
+81
@@ -0,0 +1,81 @@
|
||||
#![allow(clippy::float_cmp)]
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
#[rustfmt::skip] // appears to be a bug in rustfmt to make this converge...
|
||||
macro_rules! float_inf_tests {
|
||||
($ty:ty) => {{
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct S {
|
||||
sf1: $ty,
|
||||
sf2: $ty,
|
||||
sf3: $ty,
|
||||
sf4: $ty,
|
||||
sf5: $ty,
|
||||
sf6: $ty,
|
||||
sf7: $ty,
|
||||
sf8: $ty,
|
||||
}
|
||||
let inf: S = basic_toml::from_str(
|
||||
"
|
||||
# infinity
|
||||
sf1 = inf # positive infinity
|
||||
sf2 = +inf # positive infinity
|
||||
sf3 = -inf # negative infinity
|
||||
|
||||
# not a number
|
||||
sf4 = nan # actual sNaN/qNaN encoding is implementation specific
|
||||
sf5 = +nan # same as `nan`
|
||||
sf6 = -nan # valid, actual encoding is implementation specific
|
||||
|
||||
# zero
|
||||
sf7 = +0.0
|
||||
sf8 = -0.0
|
||||
",
|
||||
)
|
||||
.expect("Parse infinities.");
|
||||
|
||||
assert!(inf.sf1.is_infinite());
|
||||
assert!(inf.sf1.is_sign_positive());
|
||||
assert!(inf.sf2.is_infinite());
|
||||
assert!(inf.sf2.is_sign_positive());
|
||||
assert!(inf.sf3.is_infinite());
|
||||
assert!(inf.sf3.is_sign_negative());
|
||||
|
||||
assert!(inf.sf4.is_nan());
|
||||
assert!(inf.sf4.is_sign_positive());
|
||||
assert!(inf.sf5.is_nan());
|
||||
assert!(inf.sf5.is_sign_positive());
|
||||
assert!(inf.sf6.is_nan());
|
||||
assert!(inf.sf6.is_sign_negative()); // NOTE: but serializes to just `nan`
|
||||
|
||||
assert_eq!(inf.sf7, 0.0);
|
||||
assert!(inf.sf7.is_sign_positive());
|
||||
assert_eq!(inf.sf8, 0.0);
|
||||
assert!(inf.sf8.is_sign_negative());
|
||||
|
||||
let s = basic_toml::to_string(&inf).unwrap();
|
||||
assert_eq!(
|
||||
s,
|
||||
"\
|
||||
sf1 = inf
|
||||
sf2 = inf
|
||||
sf3 = -inf
|
||||
sf4 = nan
|
||||
sf5 = nan
|
||||
sf6 = nan
|
||||
sf7 = 0.0
|
||||
sf8 = -0.0
|
||||
"
|
||||
);
|
||||
|
||||
basic_toml::from_str::<Value>(&s).expect("roundtrip");
|
||||
}};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_inf() {
|
||||
float_inf_tests!(f32);
|
||||
float_inf_tests!(f64);
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
use basic_toml::to_string;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
struct User {
|
||||
pub name: String,
|
||||
pub surname: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
struct Users {
|
||||
pub user: Vec<User>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
struct TwoUsers {
|
||||
pub user0: User,
|
||||
pub user1: User,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_unnecessary_newlines_array() {
|
||||
assert!(!to_string(&Users {
|
||||
user: vec![
|
||||
User {
|
||||
name: "John".to_string(),
|
||||
surname: "Doe".to_string(),
|
||||
},
|
||||
User {
|
||||
name: "Jane".to_string(),
|
||||
surname: "Dough".to_string(),
|
||||
},
|
||||
],
|
||||
})
|
||||
.unwrap()
|
||||
.starts_with('\n'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_unnecessary_newlines_table() {
|
||||
assert!(!to_string(&TwoUsers {
|
||||
user0: User {
|
||||
name: "John".to_string(),
|
||||
surname: "Doe".to_string(),
|
||||
},
|
||||
user1: User {
|
||||
name: "Jane".to_string(),
|
||||
surname: "Dough".to_string(),
|
||||
},
|
||||
})
|
||||
.unwrap()
|
||||
.starts_with('\n'));
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"ints-and-floats": {
|
||||
"type": "array",
|
||||
"value": [
|
||||
{
|
||||
"type": "integer",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"type": "float",
|
||||
"value": "1.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
use serde_json::Value;
|
||||
|
||||
macro_rules! bad {
|
||||
($toml:expr, $msg:expr) => {
|
||||
match basic_toml::from_str::<Value>($toml) {
|
||||
Ok(s) => panic!("parsed to: {:#?}", s),
|
||||
Err(e) => assert_eq!(e.to_string(), $msg),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad() {
|
||||
bad!("a = 01", "invalid number at line 1 column 6");
|
||||
bad!("a = 1__1", "invalid number at line 1 column 5");
|
||||
bad!("a = 1_", "invalid number at line 1 column 5");
|
||||
bad!("''", "expected an equals, found eof at line 1 column 3");
|
||||
bad!("a = 9e99999", "invalid number at line 1 column 5");
|
||||
|
||||
bad!(
|
||||
"a = \"\u{7f}\"",
|
||||
"invalid character in string: `\\u{7f}` at line 1 column 6"
|
||||
);
|
||||
bad!(
|
||||
"a = '\u{7f}'",
|
||||
"invalid character in string: `\\u{7f}` at line 1 column 6"
|
||||
);
|
||||
|
||||
bad!("a = -0x1", "invalid number at line 1 column 5");
|
||||
bad!("a = 0x-1", "invalid number at line 1 column 7");
|
||||
|
||||
// Dotted keys.
|
||||
bad!(
|
||||
"a.b.c = 1
|
||||
a.b = 2
|
||||
",
|
||||
"duplicate key: `b` for key `a` at line 2 column 12"
|
||||
);
|
||||
bad!(
|
||||
"a = 1
|
||||
a.b = 2",
|
||||
"dotted key attempted to extend non-table type at line 1 column 5"
|
||||
);
|
||||
bad!(
|
||||
"a = {k1 = 1, k1.name = \"joe\"}",
|
||||
"dotted key attempted to extend non-table type at line 1 column 11"
|
||||
);
|
||||
}
|
||||
Vendored
+226
@@ -0,0 +1,226 @@
|
||||
use serde_json::Value;
|
||||
|
||||
macro_rules! bad {
|
||||
($toml:expr, $msg:expr) => {
|
||||
match basic_toml::from_str::<Value>($toml) {
|
||||
Ok(s) => panic!("parsed to: {:#?}", s),
|
||||
Err(e) => assert_eq!(e.to_string(), $msg),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! test( ($name:ident, $s:expr, $msg:expr) => (
|
||||
#[test]
|
||||
fn $name() { bad!($s, $msg); }
|
||||
) );
|
||||
|
||||
test!(
|
||||
datetime_malformed_no_leads,
|
||||
include_str!("invalid/datetime-malformed-no-leads.toml"),
|
||||
"invalid number at line 1 column 12"
|
||||
);
|
||||
test!(
|
||||
datetime_malformed_no_secs,
|
||||
include_str!("invalid/datetime-malformed-no-secs.toml"),
|
||||
"invalid number at line 1 column 11"
|
||||
);
|
||||
test!(
|
||||
datetime_malformed_no_t,
|
||||
include_str!("invalid/datetime-malformed-no-t.toml"),
|
||||
"invalid number at line 1 column 8"
|
||||
);
|
||||
test!(
|
||||
datetime_malformed_with_milli,
|
||||
include_str!("invalid/datetime-malformed-with-milli.toml"),
|
||||
"invalid number at line 1 column 14"
|
||||
);
|
||||
test!(
|
||||
duplicate_key_table,
|
||||
include_str!("invalid/duplicate-key-table.toml"),
|
||||
"duplicate key: `type` for key `fruit` at line 4 column 8"
|
||||
);
|
||||
test!(
|
||||
duplicate_keys,
|
||||
include_str!("invalid/duplicate-keys.toml"),
|
||||
"duplicate key: `dupe` at line 2 column 1"
|
||||
);
|
||||
test!(
|
||||
duplicate_table,
|
||||
include_str!("invalid/duplicate-table.toml"),
|
||||
"redefinition of table `dependencies` for key `dependencies` at line 7 column 1"
|
||||
);
|
||||
test!(
|
||||
duplicate_tables,
|
||||
include_str!("invalid/duplicate-tables.toml"),
|
||||
"redefinition of table `a` for key `a` at line 2 column 1"
|
||||
);
|
||||
test!(
|
||||
empty_implicit_table,
|
||||
include_str!("invalid/empty-implicit-table.toml"),
|
||||
"expected a table key, found a period at line 1 column 10"
|
||||
);
|
||||
test!(
|
||||
empty_table,
|
||||
include_str!("invalid/empty-table.toml"),
|
||||
"expected a table key, found a right bracket at line 1 column 2"
|
||||
);
|
||||
test!(
|
||||
float_no_leading_zero,
|
||||
include_str!("invalid/float-no-leading-zero.toml"),
|
||||
"expected a value, found a period at line 1 column 10"
|
||||
);
|
||||
test!(
|
||||
float_no_suffix,
|
||||
include_str!("invalid/float-no-suffix.toml"),
|
||||
"invalid number at line 1 column 5"
|
||||
);
|
||||
test!(
|
||||
float_no_trailing_digits,
|
||||
include_str!("invalid/float-no-trailing-digits.toml"),
|
||||
"invalid number at line 1 column 12"
|
||||
);
|
||||
test!(
|
||||
key_after_array,
|
||||
include_str!("invalid/key-after-array.toml"),
|
||||
"expected newline, found an identifier at line 1 column 14"
|
||||
);
|
||||
test!(
|
||||
key_after_table,
|
||||
include_str!("invalid/key-after-table.toml"),
|
||||
"expected newline, found an identifier at line 1 column 11"
|
||||
);
|
||||
test!(
|
||||
key_empty,
|
||||
include_str!("invalid/key-empty.toml"),
|
||||
"expected a table key, found an equals at line 1 column 2"
|
||||
);
|
||||
test!(
|
||||
key_hash,
|
||||
include_str!("invalid/key-hash.toml"),
|
||||
"expected an equals, found a comment at line 1 column 2"
|
||||
);
|
||||
test!(
|
||||
key_newline,
|
||||
include_str!("invalid/key-newline.toml"),
|
||||
"expected an equals, found a newline at line 1 column 2"
|
||||
);
|
||||
test!(
|
||||
key_open_bracket,
|
||||
include_str!("invalid/key-open-bracket.toml"),
|
||||
"expected a right bracket, found an equals at line 1 column 6"
|
||||
);
|
||||
test!(
|
||||
key_single_open_bracket,
|
||||
include_str!("invalid/key-single-open-bracket.toml"),
|
||||
"expected a table key, found eof at line 1 column 2"
|
||||
);
|
||||
test!(
|
||||
key_space,
|
||||
include_str!("invalid/key-space.toml"),
|
||||
"expected an equals, found an identifier at line 1 column 3"
|
||||
);
|
||||
test!(
|
||||
key_start_bracket,
|
||||
include_str!("invalid/key-start-bracket.toml"),
|
||||
"expected a right bracket, found an equals at line 2 column 6"
|
||||
);
|
||||
test!(
|
||||
key_two_equals,
|
||||
include_str!("invalid/key-two-equals.toml"),
|
||||
"expected a value, found an equals at line 1 column 6"
|
||||
);
|
||||
test!(
|
||||
string_bad_byte_escape,
|
||||
include_str!("invalid/string-bad-byte-escape.toml"),
|
||||
"invalid escape character in string: `x` at line 1 column 13"
|
||||
);
|
||||
test!(
|
||||
string_bad_escape,
|
||||
include_str!("invalid/string-bad-escape.toml"),
|
||||
"invalid escape character in string: `a` at line 1 column 42"
|
||||
);
|
||||
test!(
|
||||
string_bad_line_ending_escape,
|
||||
include_str!("invalid/string-bad-line-ending-escape.toml"),
|
||||
"invalid escape character in string: ` ` at line 2 column 79"
|
||||
);
|
||||
test!(
|
||||
string_byte_escapes,
|
||||
include_str!("invalid/string-byte-escapes.toml"),
|
||||
"invalid escape character in string: `x` at line 1 column 12"
|
||||
);
|
||||
test!(
|
||||
string_no_close,
|
||||
include_str!("invalid/string-no-close.toml"),
|
||||
"newline in string found at line 1 column 42"
|
||||
);
|
||||
test!(
|
||||
table_array_implicit,
|
||||
include_str!("invalid/table-array-implicit.toml"),
|
||||
"table redefined as array for key `albums` at line 13 column 1"
|
||||
);
|
||||
test!(
|
||||
table_array_malformed_bracket,
|
||||
include_str!("invalid/table-array-malformed-bracket.toml"),
|
||||
"expected a right bracket, found a newline at line 1 column 10"
|
||||
);
|
||||
test!(
|
||||
table_array_malformed_empty,
|
||||
include_str!("invalid/table-array-malformed-empty.toml"),
|
||||
"expected a table key, found a right bracket at line 1 column 3"
|
||||
);
|
||||
test!(
|
||||
table_empty,
|
||||
include_str!("invalid/table-empty.toml"),
|
||||
"expected a table key, found a right bracket at line 1 column 2"
|
||||
);
|
||||
test!(
|
||||
table_nested_brackets_close,
|
||||
include_str!("invalid/table-nested-brackets-close.toml"),
|
||||
"expected newline, found an identifier at line 1 column 4"
|
||||
);
|
||||
test!(
|
||||
table_nested_brackets_open,
|
||||
include_str!("invalid/table-nested-brackets-open.toml"),
|
||||
"expected a right bracket, found a left bracket at line 1 column 3"
|
||||
);
|
||||
test!(
|
||||
table_whitespace,
|
||||
include_str!("invalid/table-whitespace.toml"),
|
||||
"expected a right bracket, found an identifier at line 1 column 10"
|
||||
);
|
||||
test!(
|
||||
table_with_pound,
|
||||
include_str!("invalid/table-with-pound.toml"),
|
||||
"expected a right bracket, found a comment at line 1 column 5"
|
||||
);
|
||||
test!(
|
||||
text_after_array_entries,
|
||||
include_str!("invalid/text-after-array-entries.toml"),
|
||||
"invalid TOML value, did you mean to use a quoted string? at line 2 column 46"
|
||||
);
|
||||
test!(
|
||||
text_after_integer,
|
||||
include_str!("invalid/text-after-integer.toml"),
|
||||
"expected newline, found an identifier at line 1 column 13"
|
||||
);
|
||||
test!(
|
||||
text_after_string,
|
||||
include_str!("invalid/text-after-string.toml"),
|
||||
"expected newline, found an identifier at line 1 column 41"
|
||||
);
|
||||
test!(
|
||||
text_after_table,
|
||||
include_str!("invalid/text-after-table.toml"),
|
||||
"expected newline, found an identifier at line 1 column 9"
|
||||
);
|
||||
test!(
|
||||
text_before_array_separator,
|
||||
include_str!("invalid/text-before-array-separator.toml"),
|
||||
"expected a right bracket, found an identifier at line 2 column 46"
|
||||
);
|
||||
test!(
|
||||
text_in_array,
|
||||
include_str!("invalid/text-in-array.toml"),
|
||||
"invalid TOML value, did you mean to use a quoted string? at line 3 column 3"
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
no-leads = 1987-7-05T17:45:00Z
|
||||
@@ -0,0 +1 @@
|
||||
no-secs = 1987-07-05T17:45Z
|
||||
@@ -0,0 +1 @@
|
||||
no-t = 1987-07-0517:45:00Z
|
||||
@@ -0,0 +1 @@
|
||||
with-milli = 1987-07-5T17:45:00.12Z
|
||||
@@ -0,0 +1,5 @@
|
||||
[fruit]
|
||||
type = "apple"
|
||||
|
||||
[fruit.type]
|
||||
apple = "yes"
|
||||
@@ -0,0 +1,2 @@
|
||||
dupe = false
|
||||
dupe = true
|
||||
@@ -0,0 +1,8 @@
|
||||
[dependencies.openssl-sys]
|
||||
version = "0.5.2"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.1"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "0.1.1"
|
||||
@@ -0,0 +1,2 @@
|
||||
[a]
|
||||
[a]
|
||||
@@ -0,0 +1 @@
|
||||
[naughty..naughty]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,2 @@
|
||||
answer = .12345
|
||||
neganswer = -.12345
|
||||
@@ -0,0 +1 @@
|
||||
a = 1.2f
|
||||
@@ -0,0 +1,2 @@
|
||||
answer = 1.
|
||||
neganswer = -1.
|
||||
@@ -0,0 +1 @@
|
||||
[[agencies]] owner = "S Cjelli"
|
||||
@@ -0,0 +1 @@
|
||||
[history] guard = "sleeping"
|
||||
@@ -0,0 +1 @@
|
||||
= 1
|
||||
@@ -0,0 +1 @@
|
||||
a# = 1
|
||||
@@ -0,0 +1,2 @@
|
||||
a
|
||||
= 1
|
||||
@@ -0,0 +1 @@
|
||||
[abc = 1
|
||||
@@ -0,0 +1 @@
|
||||
[
|
||||
@@ -0,0 +1 @@
|
||||
a b = 1
|
||||
@@ -0,0 +1,3 @@
|
||||
[a]
|
||||
[xyz = 5
|
||||
[b]
|
||||
@@ -0,0 +1 @@
|
||||
key= = 1
|
||||
@@ -0,0 +1 @@
|
||||
naughty = "\xAg"
|
||||
@@ -0,0 +1 @@
|
||||
invalid-escape = "This string has a bad \a escape character."
|
||||
@@ -0,0 +1,3 @@
|
||||
invalid-escape = """\
|
||||
This string has a non whitespace-character after the line ending escape. \ a
|
||||
"""
|
||||
@@ -0,0 +1 @@
|
||||
answer = "\x33"
|
||||
@@ -0,0 +1 @@
|
||||
no-ending-quote = "One time, at band camp
|
||||
@@ -0,0 +1,14 @@
|
||||
# This test is a bit tricky. It should fail because the first use of
|
||||
# `[[albums.songs]]` without first declaring `albums` implies that `albums`
|
||||
# must be a table. The alternative would be quite weird. Namely, it wouldn't
|
||||
# comply with the TOML spec: "Each double-bracketed sub-table will belong to
|
||||
# the most *recently* defined table element *above* it."
|
||||
#
|
||||
# This is in contrast to the *valid* test, table-array-implicit where
|
||||
# `[[albums.songs]]` works by itself, so long as `[[albums]]` isn't declared
|
||||
# later. (Although, `[albums]` could be.)
|
||||
[[albums.songs]]
|
||||
name = "Glory Days"
|
||||
|
||||
[[albums]]
|
||||
name = "Born in the USA"
|
||||
@@ -0,0 +1,2 @@
|
||||
[[albums]
|
||||
name = "Born to Run"
|
||||
@@ -0,0 +1,2 @@
|
||||
[[]]
|
||||
name = "Born to Run"
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,2 @@
|
||||
[a]b]
|
||||
zyx = 42
|
||||
@@ -0,0 +1,2 @@
|
||||
[a[b]
|
||||
zyx = 42
|
||||
@@ -0,0 +1 @@
|
||||
[invalid key]
|
||||
@@ -0,0 +1,2 @@
|
||||
[key#group]
|
||||
answer = 42
|
||||
@@ -0,0 +1,4 @@
|
||||
array = [
|
||||
"Is there life after an array separator?", No
|
||||
"Entry"
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
answer = 42 the ultimate answer?
|
||||
@@ -0,0 +1 @@
|
||||
string = "Is there life after strings?" No.
|
||||
@@ -0,0 +1 @@
|
||||
[error] this shouldn't be here
|
||||
@@ -0,0 +1,4 @@
|
||||
array = [
|
||||
"Is there life before an array separator?" No,
|
||||
"Entry"
|
||||
]
|
||||
@@ -0,0 +1,5 @@
|
||||
array = [
|
||||
"Entry 1",
|
||||
I don't belong,
|
||||
"Entry 2",
|
||||
]
|
||||
Vendored
+687
@@ -0,0 +1,687 @@
|
||||
#![allow(clippy::let_underscore_untyped, clippy::uninlined_format_args)]
|
||||
|
||||
use serde_json::{json, Value};
|
||||
|
||||
macro_rules! bad {
|
||||
($toml:expr, $msg:expr) => {
|
||||
match basic_toml::from_str::<Value>($toml) {
|
||||
Ok(s) => panic!("parsed to: {:#?}", s),
|
||||
Err(e) => assert_eq!(e.to_string(), $msg),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crlf() {
|
||||
let toml = "\
|
||||
[project]\r\n\
|
||||
\r\n\
|
||||
name = \"splay\"\r\n\
|
||||
version = \"0.1.0\"\r\n\
|
||||
authors = [\"alex@crichton.co\"]\r\n\
|
||||
\r\n\
|
||||
[[lib]]\r\n\
|
||||
\r\n\
|
||||
path = \"lib.rs\"\r\n\
|
||||
name = \"splay\"\r\n\
|
||||
description = \"\"\"\
|
||||
A Rust implementation of a TAR file reader and writer. This library does not\r\n\
|
||||
currently handle compression, but it is abstract over all I/O readers and\r\n\
|
||||
writers. Additionally, great lengths are taken to ensure that the entire\r\n\
|
||||
contents are never required to be entirely resident in memory all at once.\r\n\
|
||||
\"\"\"\
|
||||
";
|
||||
basic_toml::from_str::<Value>(toml).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fun_with_strings() {
|
||||
let toml = r#"
|
||||
bar = "\U00000000"
|
||||
key1 = "One\nTwo"
|
||||
key2 = """One\nTwo"""
|
||||
key3 = """
|
||||
One
|
||||
Two"""
|
||||
|
||||
key4 = "The quick brown fox jumps over the lazy dog."
|
||||
key5 = """
|
||||
The quick brown \
|
||||
|
||||
|
||||
fox jumps over \
|
||||
the lazy dog."""
|
||||
key6 = """\
|
||||
The quick brown \
|
||||
fox jumps over \
|
||||
the lazy dog.\
|
||||
"""
|
||||
# What you see is what you get.
|
||||
winpath = 'C:\Users\nodejs\templates'
|
||||
winpath2 = '\\ServerX\admin$\system32\'
|
||||
quoted = 'Tom "Dubs" Preston-Werner'
|
||||
regex = '<\i\c*\s*>'
|
||||
|
||||
regex2 = '''I [dw]on't need \d{2} apples'''
|
||||
lines = '''
|
||||
The first newline is
|
||||
trimmed in raw strings.
|
||||
All other whitespace
|
||||
is preserved.
|
||||
'''
|
||||
"#;
|
||||
let table: Value = basic_toml::from_str(toml).unwrap();
|
||||
assert_eq!(table["bar"], json!("\0"));
|
||||
assert_eq!(table["key1"], json!("One\nTwo"));
|
||||
assert_eq!(table["key2"], json!("One\nTwo"));
|
||||
assert_eq!(table["key3"], json!("One\nTwo"));
|
||||
|
||||
let msg = "The quick brown fox jumps over the lazy dog.";
|
||||
assert_eq!(table["key4"], json!(msg));
|
||||
assert_eq!(table["key5"], json!(msg));
|
||||
assert_eq!(table["key6"], json!(msg));
|
||||
|
||||
assert_eq!(table["winpath"], json!(r"C:\Users\nodejs\templates"));
|
||||
assert_eq!(table["winpath2"], json!(r"\\ServerX\admin$\system32\"));
|
||||
assert_eq!(table["quoted"], json!(r#"Tom "Dubs" Preston-Werner"#));
|
||||
assert_eq!(table["regex"], json!(r"<\i\c*\s*>"));
|
||||
assert_eq!(table["regex2"], json!(r"I [dw]on't need \d{2} apples"));
|
||||
assert_eq!(
|
||||
table["lines"],
|
||||
json!(
|
||||
"The first newline is\n\
|
||||
trimmed in raw strings.\n\
|
||||
All other whitespace\n\
|
||||
is preserved.\n"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tables_in_arrays() {
|
||||
let toml = "
|
||||
[[foo]]
|
||||
#…
|
||||
[foo.bar]
|
||||
#…
|
||||
|
||||
[[foo]] # ...
|
||||
#…
|
||||
[foo.bar]
|
||||
#...
|
||||
";
|
||||
let table: Value = basic_toml::from_str(toml).unwrap();
|
||||
table["foo"][0]["bar"].as_object().unwrap();
|
||||
table["foo"][1]["bar"].as_object().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_table() {
|
||||
let toml = "
|
||||
[foo]";
|
||||
let table: Value = basic_toml::from_str(toml).unwrap();
|
||||
table["foo"].as_object().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fruit() {
|
||||
let toml = r#"
|
||||
[[fruit]]
|
||||
name = "apple"
|
||||
|
||||
[fruit.physical]
|
||||
color = "red"
|
||||
shape = "round"
|
||||
|
||||
[[fruit.variety]]
|
||||
name = "red delicious"
|
||||
|
||||
[[fruit.variety]]
|
||||
name = "granny smith"
|
||||
|
||||
[[fruit]]
|
||||
name = "banana"
|
||||
|
||||
[[fruit.variety]]
|
||||
name = "plantain"
|
||||
"#;
|
||||
let table: Value = basic_toml::from_str(toml).unwrap();
|
||||
assert_eq!(table["fruit"][0]["name"], json!("apple"));
|
||||
assert_eq!(table["fruit"][0]["physical"]["color"], json!("red"));
|
||||
assert_eq!(table["fruit"][0]["physical"]["shape"], json!("round"));
|
||||
assert_eq!(
|
||||
table["fruit"][0]["variety"][0]["name"],
|
||||
json!("red delicious")
|
||||
);
|
||||
assert_eq!(
|
||||
table["fruit"][0]["variety"][1]["name"],
|
||||
json!("granny smith")
|
||||
);
|
||||
assert_eq!(table["fruit"][1]["name"], json!("banana"));
|
||||
assert_eq!(table["fruit"][1]["variety"][0]["name"], json!("plantain"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stray_cr() {
|
||||
bad!("\r", "unexpected character found: `\\r` at line 1 column 1");
|
||||
bad!(
|
||||
"a = [ \r ]",
|
||||
"unexpected character found: `\\r` at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"a = \"\"\"\r\"\"\"",
|
||||
"invalid character in string: `\\r` at line 1 column 8"
|
||||
);
|
||||
bad!(
|
||||
"a = \"\"\"\\ \r \"\"\"",
|
||||
"invalid escape character in string: ` ` at line 1 column 9"
|
||||
);
|
||||
bad!(
|
||||
"a = '''\r'''",
|
||||
"invalid character in string: `\\r` at line 1 column 8"
|
||||
);
|
||||
bad!(
|
||||
"a = '\r'",
|
||||
"invalid character in string: `\\r` at line 1 column 6"
|
||||
);
|
||||
bad!(
|
||||
"a = \"\r\"",
|
||||
"invalid character in string: `\\r` at line 1 column 6"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blank_literal_string() {
|
||||
let table: Value = basic_toml::from_str("foo = ''").unwrap();
|
||||
assert_eq!(table["foo"], json!(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn many_blank() {
|
||||
let table: Value = basic_toml::from_str("foo = \"\"\"\n\n\n\"\"\"").unwrap();
|
||||
assert_eq!(table["foo"], json!("\n\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_eats_crlf() {
|
||||
let toml = "
|
||||
foo = \"\"\"\\\r\n\"\"\"
|
||||
bar = \"\"\"\\\r\n \r\n \r\n a\"\"\"
|
||||
";
|
||||
let table: Value = basic_toml::from_str(toml).unwrap();
|
||||
assert_eq!(table["foo"], json!(""));
|
||||
assert_eq!(table["bar"], json!("a"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_no_newline() {
|
||||
bad!("a = \"\n\"", "newline in string found at line 1 column 6");
|
||||
bad!("a = '\n'", "newline in string found at line 1 column 6");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_leading_zeros() {
|
||||
bad!("a = 00", "invalid number at line 1 column 6");
|
||||
bad!("a = -00", "invalid number at line 1 column 7");
|
||||
bad!("a = +00", "invalid number at line 1 column 7");
|
||||
bad!("a = 00.0", "invalid number at line 1 column 6");
|
||||
bad!("a = -00.0", "invalid number at line 1 column 7");
|
||||
bad!("a = +00.0", "invalid number at line 1 column 7");
|
||||
bad!(
|
||||
"a = 9223372036854775808",
|
||||
"invalid number at line 1 column 5"
|
||||
);
|
||||
bad!(
|
||||
"a = -9223372036854775809",
|
||||
"invalid number at line 1 column 5"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_floats() {
|
||||
bad!("a = 0.", "invalid number at line 1 column 7");
|
||||
bad!("a = 0.e", "invalid number at line 1 column 7");
|
||||
bad!("a = 0.E", "invalid number at line 1 column 7");
|
||||
bad!("a = 0.0E", "invalid number at line 1 column 5");
|
||||
bad!("a = 0.0e", "invalid number at line 1 column 5");
|
||||
bad!("a = 0.0e-", "invalid number at line 1 column 9");
|
||||
bad!("a = 0.0e+", "invalid number at line 1 column 5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn floats() {
|
||||
macro_rules! t {
|
||||
($actual:expr, $expected:expr) => {{
|
||||
let f = format!("foo = {}", $actual);
|
||||
println!("{}", f);
|
||||
let a: Value = basic_toml::from_str(&f).unwrap();
|
||||
assert_eq!(a["foo"], json!($expected));
|
||||
}};
|
||||
}
|
||||
|
||||
t!("1.0", 1.0);
|
||||
t!("1.0e0", 1.0);
|
||||
t!("1.0e+0", 1.0);
|
||||
t!("1.0e-0", 1.0);
|
||||
t!("1E-0", 1.0);
|
||||
t!("1.001e-0", 1.001);
|
||||
t!("2e10", 2e10);
|
||||
t!("2e+10", 2e10);
|
||||
t!("2e-10", 2e-10);
|
||||
t!("2_0.0", 20.0);
|
||||
t!("2_0.0_0e1_0", 20.0e10);
|
||||
t!("2_0.1_0e1_0", 20.1e10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bare_key_names() {
|
||||
let toml = "
|
||||
foo = 3
|
||||
foo_3 = 3
|
||||
foo_-2--3--r23f--4-f2-4 = 3
|
||||
_ = 3
|
||||
- = 3
|
||||
8 = 8
|
||||
\"a\" = 3
|
||||
\"!\" = 3
|
||||
\"a^b\" = 3
|
||||
\"\\\"\" = 3
|
||||
\"character encoding\" = \"value\"
|
||||
'ʎǝʞ' = \"value\"
|
||||
";
|
||||
let a: Value = basic_toml::from_str(toml).unwrap();
|
||||
let _ = &a["foo"];
|
||||
let _ = &a["-"];
|
||||
let _ = &a["_"];
|
||||
let _ = &a["8"];
|
||||
let _ = &a["foo_3"];
|
||||
let _ = &a["foo_-2--3--r23f--4-f2-4"];
|
||||
let _ = &a["a"];
|
||||
let _ = &a["!"];
|
||||
let _ = &a["\""];
|
||||
let _ = &a["character encoding"];
|
||||
let _ = &a["ʎǝʞ"];
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_keys() {
|
||||
bad!(
|
||||
"key\n=3",
|
||||
"expected an equals, found a newline at line 1 column 4"
|
||||
);
|
||||
bad!(
|
||||
"key=\n3",
|
||||
"expected a value, found a newline at line 1 column 5"
|
||||
);
|
||||
bad!(
|
||||
"key|=3",
|
||||
"unexpected character found: `|` at line 1 column 4"
|
||||
);
|
||||
bad!(
|
||||
"=3",
|
||||
"expected a table key, found an equals at line 1 column 1"
|
||||
);
|
||||
bad!(
|
||||
"\"\"|=3",
|
||||
"unexpected character found: `|` at line 1 column 3"
|
||||
);
|
||||
bad!("\"\n\"|=3", "newline in string found at line 1 column 2");
|
||||
bad!(
|
||||
"\"\r\"|=3",
|
||||
"invalid character in string: `\\r` at line 1 column 2"
|
||||
);
|
||||
bad!(
|
||||
"''''''=3",
|
||||
"multiline strings are not allowed for key at line 1 column 1"
|
||||
);
|
||||
bad!(
|
||||
"\"\"\"\"\"\"=3",
|
||||
"multiline strings are not allowed for key at line 1 column 1"
|
||||
);
|
||||
bad!(
|
||||
"'''key'''=3",
|
||||
"multiline strings are not allowed for key at line 1 column 1"
|
||||
);
|
||||
bad!(
|
||||
"\"\"\"key\"\"\"=3",
|
||||
"multiline strings are not allowed for key at line 1 column 1"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_table_names() {
|
||||
bad!(
|
||||
"[]",
|
||||
"expected a table key, found a right bracket at line 1 column 2"
|
||||
);
|
||||
bad!(
|
||||
"[.]",
|
||||
"expected a table key, found a period at line 1 column 2"
|
||||
);
|
||||
bad!(
|
||||
"[a.]",
|
||||
"expected a table key, found a right bracket at line 1 column 4"
|
||||
);
|
||||
bad!("[!]", "unexpected character found: `!` at line 1 column 2");
|
||||
bad!("[\"\n\"]", "newline in string found at line 1 column 3");
|
||||
bad!(
|
||||
"[a.b]\n[a.\"b\"]",
|
||||
"redefinition of table `a.b` for key `a.b` at line 2 column 1"
|
||||
);
|
||||
bad!("[']", "unterminated string at line 1 column 2");
|
||||
bad!("[''']", "unterminated string at line 1 column 2");
|
||||
bad!(
|
||||
"['''''']",
|
||||
"multiline strings are not allowed for key at line 1 column 2"
|
||||
);
|
||||
bad!(
|
||||
"['''foo''']",
|
||||
"multiline strings are not allowed for key at line 1 column 2"
|
||||
);
|
||||
bad!(
|
||||
"[\"\"\"bar\"\"\"]",
|
||||
"multiline strings are not allowed for key at line 1 column 2"
|
||||
);
|
||||
bad!("['\n']", "newline in string found at line 1 column 3");
|
||||
bad!("['\r\n']", "newline in string found at line 1 column 3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_names() {
|
||||
let toml = "
|
||||
[a.\"b\"]
|
||||
[\"f f\"]
|
||||
[\"f.f\"]
|
||||
[\"\\\"\"]
|
||||
['a.a']
|
||||
['\"\"']
|
||||
";
|
||||
let a: Value = basic_toml::from_str(toml).unwrap();
|
||||
println!("{:?}", a);
|
||||
let _ = &a["a"]["b"];
|
||||
let _ = &a["f f"];
|
||||
let _ = &a["f.f"];
|
||||
let _ = &a["\""];
|
||||
let _ = &a["\"\""];
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_bare_numeral() {
|
||||
bad!("4", "expected an equals, found eof at line 1 column 2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inline_tables() {
|
||||
basic_toml::from_str::<Value>("a = {}").unwrap();
|
||||
basic_toml::from_str::<Value>("a = {b=1}").unwrap();
|
||||
basic_toml::from_str::<Value>("a = { b = 1 }").unwrap();
|
||||
basic_toml::from_str::<Value>("a = {a=1,b=2}").unwrap();
|
||||
basic_toml::from_str::<Value>("a = {a=1,b=2,c={}}").unwrap();
|
||||
|
||||
bad!(
|
||||
"a = {a=1,}",
|
||||
"expected a table key, found a right brace at line 1 column 10"
|
||||
);
|
||||
bad!(
|
||||
"a = {,}",
|
||||
"expected a table key, found a comma at line 1 column 6"
|
||||
);
|
||||
bad!(
|
||||
"a = {a=1,a=1}",
|
||||
"duplicate key: `a` for key `a` at line 1 column 10"
|
||||
);
|
||||
bad!(
|
||||
"a = {\n}",
|
||||
"expected a table key, found a newline at line 1 column 6"
|
||||
);
|
||||
bad!(
|
||||
"a = {",
|
||||
"expected a table key, found eof at line 1 column 6"
|
||||
);
|
||||
|
||||
basic_toml::from_str::<Value>("a = {a=[\n]}").unwrap();
|
||||
basic_toml::from_str::<Value>("a = {\"a\"=[\n]}").unwrap();
|
||||
basic_toml::from_str::<Value>("a = [\n{},\n{},\n]").unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn number_underscores() {
|
||||
macro_rules! t {
|
||||
($actual:expr, $expected:expr) => {{
|
||||
let f = format!("foo = {}", $actual);
|
||||
let table: Value = basic_toml::from_str(&f).unwrap();
|
||||
assert_eq!(table["foo"], json!($expected));
|
||||
}};
|
||||
}
|
||||
|
||||
t!("1_0", 10);
|
||||
t!("1_0_0", 100);
|
||||
t!("1_000", 1000);
|
||||
t!("+1_000", 1000);
|
||||
t!("-1_000", -1000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_underscores() {
|
||||
bad!("foo = 0_", "invalid number at line 1 column 7");
|
||||
bad!("foo = 0__0", "invalid number at line 1 column 7");
|
||||
bad!(
|
||||
"foo = __0",
|
||||
"invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
|
||||
);
|
||||
bad!("foo = 1_0_", "invalid number at line 1 column 7");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_unicode_codepoint() {
|
||||
bad!(
|
||||
"foo = \"\\uD800\"",
|
||||
"invalid escape value: `55296` at line 1 column 9"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_strings() {
|
||||
bad!(
|
||||
"foo = \"\\uxx\"",
|
||||
"invalid hex escape character in string: `x` at line 1 column 10"
|
||||
);
|
||||
bad!(
|
||||
"foo = \"\\u\"",
|
||||
"invalid hex escape character in string: `\\\"` at line 1 column 10"
|
||||
);
|
||||
bad!("foo = \"\\", "unterminated string at line 1 column 7");
|
||||
bad!("foo = '", "unterminated string at line 1 column 7");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_string() {
|
||||
let table: Value = basic_toml::from_str::<Value>("foo = \"\"").unwrap();
|
||||
assert_eq!(table["foo"], json!(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn booleans() {
|
||||
let table: Value = basic_toml::from_str("foo = true").unwrap();
|
||||
assert_eq!(table["foo"], json!(true));
|
||||
|
||||
let table: Value = basic_toml::from_str("foo = false").unwrap();
|
||||
assert_eq!(table["foo"], json!(false));
|
||||
|
||||
bad!(
|
||||
"foo = true2",
|
||||
"invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = false2",
|
||||
"invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = t1",
|
||||
"invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = f2",
|
||||
"invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_nesting() {
|
||||
bad!(
|
||||
"
|
||||
a = [2]
|
||||
[[a]]
|
||||
b = 5
|
||||
",
|
||||
"duplicate key: `a` at line 3 column 11"
|
||||
);
|
||||
bad!(
|
||||
"
|
||||
a = 1
|
||||
[a.b]
|
||||
",
|
||||
"duplicate key: `a` at line 3 column 10"
|
||||
);
|
||||
bad!(
|
||||
"
|
||||
a = []
|
||||
[a.b]
|
||||
",
|
||||
"duplicate key: `a` at line 3 column 10"
|
||||
);
|
||||
bad!(
|
||||
"
|
||||
a = []
|
||||
[[a.b]]
|
||||
",
|
||||
"duplicate key: `a` at line 3 column 11"
|
||||
);
|
||||
bad!(
|
||||
"
|
||||
[a]
|
||||
b = { c = 2, d = {} }
|
||||
[a.b]
|
||||
c = 2
|
||||
",
|
||||
"duplicate key: `b` for key `a` at line 4 column 12"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_table_redefine() {
|
||||
bad!(
|
||||
"
|
||||
[a]
|
||||
foo=\"bar\"
|
||||
[a.b]
|
||||
foo=\"bar\"
|
||||
[a]
|
||||
",
|
||||
"redefinition of table `a` for key `a` at line 6 column 9"
|
||||
);
|
||||
bad!(
|
||||
"
|
||||
[a]
|
||||
foo=\"bar\"
|
||||
b = { foo = \"bar\" }
|
||||
[a]
|
||||
",
|
||||
"redefinition of table `a` for key `a` at line 5 column 9"
|
||||
);
|
||||
bad!(
|
||||
"
|
||||
[a]
|
||||
b = {}
|
||||
[a.b]
|
||||
",
|
||||
"duplicate key: `b` for key `a` at line 4 column 12"
|
||||
);
|
||||
|
||||
bad!(
|
||||
"
|
||||
[a]
|
||||
b = {}
|
||||
[a]
|
||||
",
|
||||
"redefinition of table `a` for key `a` at line 4 column 9"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn datetimes() {
|
||||
bad!(
|
||||
"foo = 2016-09-09T09:09:09Z",
|
||||
"invalid number at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = 2016-09-09T09:09:09.1Z",
|
||||
"invalid number at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = 2016-09-09T09:09:09.2+10:00",
|
||||
"invalid number at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = 2016-09-09T09:09:09.123456789-02:00",
|
||||
"invalid number at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = 2016-09-09T09:09:09.Z",
|
||||
"invalid number at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = 2016-9-09T09:09:09Z",
|
||||
"invalid number at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = 2016-09-09T09:09:09+2:00",
|
||||
"invalid number at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = 2016-09-09T09:09:09-2:00",
|
||||
"invalid number at line 1 column 7"
|
||||
);
|
||||
bad!(
|
||||
"foo = 2016-09-09T09:09:09Z-2:00",
|
||||
"invalid number at line 1 column 7"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn require_newline_after_value() {
|
||||
bad!("0=0r=false", "invalid number at line 1 column 3");
|
||||
bad!(
|
||||
r#"
|
||||
0=""o=""m=""r=""00="0"q="""0"""e="""0"""
|
||||
"#,
|
||||
"expected newline, found an identifier at line 2 column 5"
|
||||
);
|
||||
bad!(
|
||||
r#"
|
||||
[[0000l0]]
|
||||
0="0"[[0000l0]]
|
||||
0="0"[[0000l0]]
|
||||
0="0"l="0"
|
||||
"#,
|
||||
"expected newline, found a left bracket at line 3 column 6"
|
||||
);
|
||||
bad!(
|
||||
r#"
|
||||
0=[0]00=[0,0,0]t=["0","0","0"]s=[1000-00-00T00:00:00Z,2000-00-00T00:00:00Z]
|
||||
"#,
|
||||
"expected newline, found an identifier at line 2 column 6"
|
||||
);
|
||||
bad!(
|
||||
"
|
||||
0=0r0=0r=false
|
||||
",
|
||||
"invalid number at line 2 column 3"
|
||||
);
|
||||
bad!(
|
||||
"
|
||||
0=0r0=0r=falsefal=false
|
||||
",
|
||||
"invalid number at line 2 column 3"
|
||||
);
|
||||
}
|
||||
Vendored
+190
@@ -0,0 +1,190 @@
|
||||
#![allow(
|
||||
clippy::elidable_lifetime_names,
|
||||
clippy::let_underscore_untyped,
|
||||
clippy::manual_range_contains,
|
||||
clippy::needless_lifetimes,
|
||||
clippy::needless_pass_by_value,
|
||||
clippy::type_complexity
|
||||
)]
|
||||
|
||||
#[path = "../src/tokens.rs"]
|
||||
#[allow(dead_code)]
|
||||
mod tokens;
|
||||
|
||||
use crate::tokens::{Error, Token, Tokenizer};
|
||||
use std::borrow::Cow;
|
||||
|
||||
fn err(input: &str, err: Error) {
|
||||
let mut t = Tokenizer::new(input);
|
||||
let token = t.next().unwrap_err();
|
||||
assert_eq!(token, err);
|
||||
assert!(t.next().unwrap().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_strings() {
|
||||
fn t(input: &str, val: &str, multiline: bool) {
|
||||
let mut t = Tokenizer::new(input);
|
||||
let (_, token) = t.next().unwrap().unwrap();
|
||||
assert_eq!(
|
||||
token,
|
||||
Token::String {
|
||||
src: input,
|
||||
val: Cow::Borrowed(val),
|
||||
multiline,
|
||||
}
|
||||
);
|
||||
assert!(t.next().unwrap().is_none());
|
||||
}
|
||||
|
||||
t("''", "", false);
|
||||
t("''''''", "", true);
|
||||
t("'''\n'''", "", true);
|
||||
t("'a'", "a", false);
|
||||
t("'\"a'", "\"a", false);
|
||||
t("''''a'''", "'a", true);
|
||||
t("'''\n'a\n'''", "'a\n", true);
|
||||
t("'''a\n'a\r\n'''", "a\n'a\n", true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_strings() {
|
||||
fn t(input: &str, val: &str, multiline: bool) {
|
||||
let mut t = Tokenizer::new(input);
|
||||
let (_, token) = t.next().unwrap().unwrap();
|
||||
assert_eq!(
|
||||
token,
|
||||
Token::String {
|
||||
src: input,
|
||||
val: Cow::Borrowed(val),
|
||||
multiline,
|
||||
}
|
||||
);
|
||||
assert!(t.next().unwrap().is_none());
|
||||
}
|
||||
|
||||
t(r#""""#, "", false);
|
||||
t(r#""""""""#, "", true);
|
||||
t(r#""a""#, "a", false);
|
||||
t(r#""""a""""#, "a", true);
|
||||
t(r#""\t""#, "\t", false);
|
||||
t(r#""\u0000""#, "\0", false);
|
||||
t(r#""\U00000000""#, "\0", false);
|
||||
t(r#""\U000A0000""#, "\u{A0000}", false);
|
||||
t(r#""\\t""#, "\\t", false);
|
||||
t("\"\t\"", "\t", false);
|
||||
t("\"\"\"\n\t\"\"\"", "\t", true);
|
||||
t("\"\"\"\\\n\"\"\"", "", true);
|
||||
t(
|
||||
"\"\"\"\\\n \t \t \\\r\n \t \n \t \r\n\"\"\"",
|
||||
"",
|
||||
true,
|
||||
);
|
||||
t(r#""\r""#, "\r", false);
|
||||
t(r#""\n""#, "\n", false);
|
||||
t(r#""\b""#, "\u{8}", false);
|
||||
t(r#""a\fa""#, "a\u{c}a", false);
|
||||
t(r#""\"a""#, "\"a", false);
|
||||
t("\"\"\"\na\"\"\"", "a", true);
|
||||
t("\"\"\"\n\"\"\"", "", true);
|
||||
t(r#""""a\"""b""""#, "a\"\"\"b", true);
|
||||
err(r#""\a"#, Error::InvalidEscape(2, 'a'));
|
||||
err("\"\\\n", Error::InvalidEscape(2, '\n'));
|
||||
err("\"\\\r\n", Error::InvalidEscape(2, '\n'));
|
||||
err("\"\\", Error::UnterminatedString(0));
|
||||
err("\"\u{0}", Error::InvalidCharInString(1, '\u{0}'));
|
||||
err(r#""\U00""#, Error::InvalidHexEscape(5, '"'));
|
||||
err(r#""\U00"#, Error::UnterminatedString(0));
|
||||
err(r#""\uD800"#, Error::InvalidEscapeValue(2, 0xd800));
|
||||
err(r#""\UFFFFFFFF"#, Error::InvalidEscapeValue(2, 0xffff_ffff));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keylike() {
|
||||
fn t(input: &str) {
|
||||
let mut t = Tokenizer::new(input);
|
||||
let (_, token) = t.next().unwrap().unwrap();
|
||||
assert_eq!(token, Token::Keylike(input));
|
||||
assert!(t.next().unwrap().is_none());
|
||||
}
|
||||
t("foo");
|
||||
t("0bar");
|
||||
t("bar0");
|
||||
t("1234");
|
||||
t("a-b");
|
||||
t("a_B");
|
||||
t("-_-");
|
||||
t("___");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all() {
|
||||
fn t(input: &str, expected: &[((usize, usize), Token, &str)]) {
|
||||
let mut tokens = Tokenizer::new(input);
|
||||
let mut actual: Vec<((usize, usize), Token, &str)> = Vec::new();
|
||||
while let Some((span, token)) = tokens.next().unwrap() {
|
||||
actual.push((span.into(), token, &input[span.start..span.end]));
|
||||
}
|
||||
for (a, b) in actual.iter().zip(expected) {
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
}
|
||||
|
||||
t(
|
||||
" a ",
|
||||
&[
|
||||
((0, 1), Token::Whitespace(" "), " "),
|
||||
((1, 2), Token::Keylike("a"), "a"),
|
||||
((2, 3), Token::Whitespace(" "), " "),
|
||||
],
|
||||
);
|
||||
|
||||
t(
|
||||
" a\t [[]] \t [] {} , . =\n# foo \r\n#foo \n ",
|
||||
&[
|
||||
((0, 1), Token::Whitespace(" "), " "),
|
||||
((1, 2), Token::Keylike("a"), "a"),
|
||||
((2, 4), Token::Whitespace("\t "), "\t "),
|
||||
((4, 5), Token::LeftBracket, "["),
|
||||
((5, 6), Token::LeftBracket, "["),
|
||||
((6, 7), Token::RightBracket, "]"),
|
||||
((7, 8), Token::RightBracket, "]"),
|
||||
((8, 11), Token::Whitespace(" \t "), " \t "),
|
||||
((11, 12), Token::LeftBracket, "["),
|
||||
((12, 13), Token::RightBracket, "]"),
|
||||
((13, 14), Token::Whitespace(" "), " "),
|
||||
((14, 15), Token::LeftBrace, "{"),
|
||||
((15, 16), Token::RightBrace, "}"),
|
||||
((16, 17), Token::Whitespace(" "), " "),
|
||||
((17, 18), Token::Comma, ","),
|
||||
((18, 19), Token::Whitespace(" "), " "),
|
||||
((19, 20), Token::Period, "."),
|
||||
((20, 21), Token::Whitespace(" "), " "),
|
||||
((21, 22), Token::Equals, "="),
|
||||
((22, 23), Token::Newline, "\n"),
|
||||
((23, 29), Token::Comment("# foo "), "# foo "),
|
||||
((29, 31), Token::Newline, "\r\n"),
|
||||
((31, 36), Token::Comment("#foo "), "#foo "),
|
||||
((36, 37), Token::Newline, "\n"),
|
||||
((37, 38), Token::Whitespace(" "), " "),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bare_cr_bad() {
|
||||
err("\r", Error::Unexpected(0, '\r'));
|
||||
err("'\n", Error::NewlineInString(1));
|
||||
err("'\u{0}", Error::InvalidCharInString(1, '\u{0}'));
|
||||
err("'", Error::UnterminatedString(0));
|
||||
err("\u{0}", Error::Unexpected(0, '\u{0}'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_comment() {
|
||||
let mut t = Tokenizer::new("#\u{0}");
|
||||
t.next().unwrap().unwrap();
|
||||
assert_eq!(t.next(), Err(Error::Unexpected(1, '\u{0}')));
|
||||
assert!(t.next().unwrap().is_none());
|
||||
}
|
||||
Vendored
+368
@@ -0,0 +1,368 @@
|
||||
#![allow(
|
||||
clippy::match_like_matches_macro,
|
||||
clippy::needless_pass_by_value,
|
||||
clippy::uninlined_format_args
|
||||
)]
|
||||
|
||||
use serde_json::{json, Value};
|
||||
|
||||
fn to_json(toml: Value) -> Value {
|
||||
fn doit(s: &str, json: Value) -> Value {
|
||||
json!({ "type": s, "value": json })
|
||||
}
|
||||
|
||||
match toml {
|
||||
Value::Null => unreachable!(),
|
||||
Value::String(s) => doit("string", Value::String(s)),
|
||||
Value::Number(n) => {
|
||||
let repr = n.to_string();
|
||||
if repr.contains('.') {
|
||||
let float: f64 = repr.parse().unwrap();
|
||||
let mut repr = format!("{:.15}", float);
|
||||
repr.truncate(repr.trim_end_matches('0').len());
|
||||
if repr.ends_with('.') {
|
||||
repr.push('0');
|
||||
}
|
||||
doit("float", Value::String(repr))
|
||||
} else {
|
||||
doit("integer", Value::String(repr))
|
||||
}
|
||||
}
|
||||
Value::Bool(b) => doit("bool", Value::String(format!("{}", b))),
|
||||
Value::Array(arr) => {
|
||||
let is_table = match arr.first() {
|
||||
Some(&Value::Object(_)) => true,
|
||||
_ => false,
|
||||
};
|
||||
let json = Value::Array(arr.into_iter().map(to_json).collect());
|
||||
if is_table {
|
||||
json
|
||||
} else {
|
||||
doit("array", json)
|
||||
}
|
||||
}
|
||||
Value::Object(table) => {
|
||||
let mut map = serde_json::Map::new();
|
||||
for (k, v) in table {
|
||||
map.insert(k, to_json(v));
|
||||
}
|
||||
Value::Object(map)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run(toml_raw: &str, json_raw: &str) {
|
||||
println!("parsing:\n{}", toml_raw);
|
||||
let toml: Value = basic_toml::from_str(toml_raw).unwrap();
|
||||
let json: Value = serde_json::from_str(json_raw).unwrap();
|
||||
|
||||
// Assert toml == json
|
||||
let toml_json = to_json(toml.clone());
|
||||
assert!(
|
||||
json == toml_json,
|
||||
"expected\n{}\ngot\n{}\n",
|
||||
serde_json::to_string_pretty(&json).unwrap(),
|
||||
serde_json::to_string_pretty(&toml_json).unwrap()
|
||||
);
|
||||
|
||||
// Assert round trip
|
||||
println!("round trip parse: {}", toml);
|
||||
let toml2: Value = basic_toml::from_str(&basic_toml::to_string(&toml).unwrap()).unwrap();
|
||||
assert_eq!(toml, toml2);
|
||||
}
|
||||
|
||||
macro_rules! test( ($name:ident, $toml:expr, $json:expr) => (
|
||||
#[test]
|
||||
fn $name() { run($toml, $json); }
|
||||
) );
|
||||
|
||||
test!(
|
||||
array_empty,
|
||||
include_str!("valid/array-empty.toml"),
|
||||
include_str!("valid/array-empty.json")
|
||||
);
|
||||
test!(
|
||||
array_nospaces,
|
||||
include_str!("valid/array-nospaces.toml"),
|
||||
include_str!("valid/array-nospaces.json")
|
||||
);
|
||||
test!(
|
||||
arrays_hetergeneous,
|
||||
include_str!("valid/arrays-hetergeneous.toml"),
|
||||
include_str!("valid/arrays-hetergeneous.json")
|
||||
);
|
||||
#[cfg(any())]
|
||||
test!(
|
||||
arrays,
|
||||
include_str!("valid/arrays.toml"),
|
||||
include_str!("valid/arrays.json")
|
||||
);
|
||||
test!(
|
||||
arrays_nested,
|
||||
include_str!("valid/arrays-nested.toml"),
|
||||
include_str!("valid/arrays-nested.json")
|
||||
);
|
||||
test!(
|
||||
array_mixed_types_ints_and_floats,
|
||||
include_str!("valid/array-mixed-types-ints-and-floats.toml"),
|
||||
include_str!("valid/array-mixed-types-ints-and-floats.json")
|
||||
);
|
||||
test!(
|
||||
array_mixed_types_arrays_and_ints,
|
||||
include_str!("valid/array-mixed-types-arrays-and-ints.toml"),
|
||||
include_str!("valid/array-mixed-types-arrays-and-ints.json")
|
||||
);
|
||||
test!(
|
||||
array_mixed_types_strings_and_ints,
|
||||
include_str!("valid/array-mixed-types-strings-and-ints.toml"),
|
||||
include_str!("valid/array-mixed-types-strings-and-ints.json")
|
||||
);
|
||||
test!(
|
||||
empty,
|
||||
include_str!("valid/empty.toml"),
|
||||
include_str!("valid/empty.json")
|
||||
);
|
||||
test!(
|
||||
bool,
|
||||
include_str!("valid/bool.toml"),
|
||||
include_str!("valid/bool.json")
|
||||
);
|
||||
test!(
|
||||
comments_everywhere,
|
||||
include_str!("valid/comments-everywhere.toml"),
|
||||
include_str!("valid/comments-everywhere.json")
|
||||
);
|
||||
#[cfg(any())]
|
||||
test!(
|
||||
datetime,
|
||||
include_str!("valid/datetime.toml"),
|
||||
include_str!("valid/datetime.json")
|
||||
);
|
||||
#[cfg(any())]
|
||||
test!(
|
||||
example,
|
||||
include_str!("valid/example.toml"),
|
||||
include_str!("valid/example.json")
|
||||
);
|
||||
test!(
|
||||
float,
|
||||
include_str!("valid/float.toml"),
|
||||
include_str!("valid/float.json")
|
||||
);
|
||||
#[cfg(any())]
|
||||
test!(
|
||||
implicit_and_explicit_after,
|
||||
include_str!("valid/implicit-and-explicit-after.toml"),
|
||||
include_str!("valid/implicit-and-explicit-after.json")
|
||||
);
|
||||
#[cfg(any())]
|
||||
test!(
|
||||
implicit_and_explicit_before,
|
||||
include_str!("valid/implicit-and-explicit-before.toml"),
|
||||
include_str!("valid/implicit-and-explicit-before.json")
|
||||
);
|
||||
test!(
|
||||
implicit_groups,
|
||||
include_str!("valid/implicit-groups.toml"),
|
||||
include_str!("valid/implicit-groups.json")
|
||||
);
|
||||
test!(
|
||||
integer,
|
||||
include_str!("valid/integer.toml"),
|
||||
include_str!("valid/integer.json")
|
||||
);
|
||||
test!(
|
||||
key_equals_nospace,
|
||||
include_str!("valid/key-equals-nospace.toml"),
|
||||
include_str!("valid/key-equals-nospace.json")
|
||||
);
|
||||
test!(
|
||||
key_space,
|
||||
include_str!("valid/key-space.toml"),
|
||||
include_str!("valid/key-space.json")
|
||||
);
|
||||
test!(
|
||||
key_special_chars,
|
||||
include_str!("valid/key-special-chars.toml"),
|
||||
include_str!("valid/key-special-chars.json")
|
||||
);
|
||||
test!(
|
||||
key_with_pound,
|
||||
include_str!("valid/key-with-pound.toml"),
|
||||
include_str!("valid/key-with-pound.json")
|
||||
);
|
||||
test!(
|
||||
key_empty,
|
||||
include_str!("valid/key-empty.toml"),
|
||||
include_str!("valid/key-empty.json")
|
||||
);
|
||||
test!(
|
||||
long_float,
|
||||
include_str!("valid/long-float.toml"),
|
||||
include_str!("valid/long-float.json")
|
||||
);
|
||||
test!(
|
||||
long_integer,
|
||||
include_str!("valid/long-integer.toml"),
|
||||
include_str!("valid/long-integer.json")
|
||||
);
|
||||
test!(
|
||||
multiline_string,
|
||||
include_str!("valid/multiline-string.toml"),
|
||||
include_str!("valid/multiline-string.json")
|
||||
);
|
||||
test!(
|
||||
raw_multiline_string,
|
||||
include_str!("valid/raw-multiline-string.toml"),
|
||||
include_str!("valid/raw-multiline-string.json")
|
||||
);
|
||||
test!(
|
||||
raw_string,
|
||||
include_str!("valid/raw-string.toml"),
|
||||
include_str!("valid/raw-string.json")
|
||||
);
|
||||
test!(
|
||||
string_empty,
|
||||
include_str!("valid/string-empty.toml"),
|
||||
include_str!("valid/string-empty.json")
|
||||
);
|
||||
test!(
|
||||
string_escapes,
|
||||
include_str!("valid/string-escapes.toml"),
|
||||
include_str!("valid/string-escapes.json")
|
||||
);
|
||||
test!(
|
||||
string_simple,
|
||||
include_str!("valid/string-simple.toml"),
|
||||
include_str!("valid/string-simple.json")
|
||||
);
|
||||
test!(
|
||||
string_with_pound,
|
||||
include_str!("valid/string-with-pound.toml"),
|
||||
include_str!("valid/string-with-pound.json")
|
||||
);
|
||||
test!(
|
||||
table_array_implicit,
|
||||
include_str!("valid/table-array-implicit.toml"),
|
||||
include_str!("valid/table-array-implicit.json")
|
||||
);
|
||||
test!(
|
||||
table_array_many,
|
||||
include_str!("valid/table-array-many.toml"),
|
||||
include_str!("valid/table-array-many.json")
|
||||
);
|
||||
test!(
|
||||
table_array_nest,
|
||||
include_str!("valid/table-array-nest.toml"),
|
||||
include_str!("valid/table-array-nest.json")
|
||||
);
|
||||
test!(
|
||||
table_array_one,
|
||||
include_str!("valid/table-array-one.toml"),
|
||||
include_str!("valid/table-array-one.json")
|
||||
);
|
||||
test!(
|
||||
table_empty,
|
||||
include_str!("valid/table-empty.toml"),
|
||||
include_str!("valid/table-empty.json")
|
||||
);
|
||||
test!(
|
||||
table_sub_empty,
|
||||
include_str!("valid/table-sub-empty.toml"),
|
||||
include_str!("valid/table-sub-empty.json")
|
||||
);
|
||||
test!(
|
||||
table_multi_empty,
|
||||
include_str!("valid/table-multi-empty.toml"),
|
||||
include_str!("valid/table-multi-empty.json")
|
||||
);
|
||||
test!(
|
||||
table_whitespace,
|
||||
include_str!("valid/table-whitespace.toml"),
|
||||
include_str!("valid/table-whitespace.json")
|
||||
);
|
||||
test!(
|
||||
table_with_pound,
|
||||
include_str!("valid/table-with-pound.toml"),
|
||||
include_str!("valid/table-with-pound.json")
|
||||
);
|
||||
test!(
|
||||
unicode_escape,
|
||||
include_str!("valid/unicode-escape.toml"),
|
||||
include_str!("valid/unicode-escape.json")
|
||||
);
|
||||
test!(
|
||||
unicode_literal,
|
||||
include_str!("valid/unicode-literal.toml"),
|
||||
include_str!("valid/unicode-literal.json")
|
||||
);
|
||||
#[cfg(any())]
|
||||
test!(
|
||||
hard_example,
|
||||
include_str!("valid/hard_example.toml"),
|
||||
include_str!("valid/hard_example.json")
|
||||
);
|
||||
#[cfg(any())]
|
||||
test!(
|
||||
example2,
|
||||
include_str!("valid/example2.toml"),
|
||||
include_str!("valid/example2.json")
|
||||
);
|
||||
#[cfg(any())]
|
||||
test!(
|
||||
example3,
|
||||
include_str!("valid/example-v0.3.0.toml"),
|
||||
include_str!("valid/example-v0.3.0.json")
|
||||
);
|
||||
#[cfg(any())]
|
||||
test!(
|
||||
example4,
|
||||
include_str!("valid/example-v0.4.0.toml"),
|
||||
include_str!("valid/example-v0.4.0.json")
|
||||
);
|
||||
#[cfg(any())]
|
||||
test!(
|
||||
example_bom,
|
||||
include_str!("valid/example-bom.toml"),
|
||||
include_str!("valid/example.json")
|
||||
);
|
||||
|
||||
#[cfg(any())]
|
||||
test!(
|
||||
datetime_truncate,
|
||||
include_str!("valid/datetime-truncate.toml"),
|
||||
include_str!("valid/datetime-truncate.json")
|
||||
);
|
||||
test!(
|
||||
key_quote_newline,
|
||||
include_str!("valid/key-quote-newline.toml"),
|
||||
include_str!("valid/key-quote-newline.json")
|
||||
);
|
||||
test!(
|
||||
table_array_nest_no_keys,
|
||||
include_str!("valid/table-array-nest-no-keys.toml"),
|
||||
include_str!("valid/table-array-nest-no-keys.json")
|
||||
);
|
||||
test!(
|
||||
dotted_keys,
|
||||
include_str!("valid/dotted-keys.toml"),
|
||||
include_str!("valid/dotted-keys.json")
|
||||
);
|
||||
|
||||
test!(
|
||||
quote_surrounded_value,
|
||||
include_str!("valid/quote-surrounded-value.toml"),
|
||||
include_str!("valid/quote-surrounded-value.json")
|
||||
);
|
||||
|
||||
test!(
|
||||
float_exponent,
|
||||
include_str!("valid/float-exponent.toml"),
|
||||
include_str!("valid/float-exponent.json")
|
||||
);
|
||||
|
||||
test!(
|
||||
string_delim_end,
|
||||
include_str!("valid/string-delim-end.toml"),
|
||||
include_str!("valid/string-delim-end.json")
|
||||
);
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"thevoid": { "type": "array", "value": [
|
||||
{"type": "array", "value": [
|
||||
{"type": "array", "value": [
|
||||
{"type": "array", "value": [
|
||||
{"type": "array", "value": []}
|
||||
]}
|
||||
]}
|
||||
]}
|
||||
]}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
thevoid = [[[[[]]]]]
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"arrays-and-ints": {
|
||||
"type": "array",
|
||||
"value": [
|
||||
{"type": "integer", "value": "1"},
|
||||
{"type": "array", "value": [
|
||||
{ "type": "string", "value":"Arrays are not integers."}
|
||||
]}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
arrays-and-ints = [1, ["Arrays are not integers."]]
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ints-and-floats": {
|
||||
"type": "array",
|
||||
"value": [
|
||||
{"type": "integer", "value": "1"},
|
||||
{"type": "float", "value": "1.1"}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
ints-and-floats = [1, 1.1]
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"strings-and-ints": {
|
||||
"type": "array",
|
||||
"value": [
|
||||
{"type": "string", "value": "hi"},
|
||||
{"type": "integer", "value": "42"}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
strings-and-ints = ["hi", 42]
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"ints": {
|
||||
"type": "array",
|
||||
"value": [
|
||||
{"type": "integer", "value": "1"},
|
||||
{"type": "integer", "value": "2"},
|
||||
{"type": "integer", "value": "3"}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
ints = [1,2,3]
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"mixed": {
|
||||
"type": "array",
|
||||
"value": [
|
||||
{"type": "array", "value": [
|
||||
{"type": "integer", "value": "1"},
|
||||
{"type": "integer", "value": "2"}
|
||||
]},
|
||||
{"type": "array", "value": [
|
||||
{"type": "string", "value": "a"},
|
||||
{"type": "string", "value": "b"}
|
||||
]},
|
||||
{"type": "array", "value": [
|
||||
{"type": "float", "value": "1.1"},
|
||||
{"type": "float", "value": "2.1"}
|
||||
]}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
mixed = [[1, 2], ["a", "b"], [1.1, 2.1]]
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"nest": {
|
||||
"type": "array",
|
||||
"value": [
|
||||
{"type": "array", "value": [
|
||||
{"type": "string", "value": "a"}
|
||||
]},
|
||||
{"type": "array", "value": [
|
||||
{"type": "string", "value": "b"}
|
||||
]}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
nest = [["a"], ["b"]]
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"ints": {
|
||||
"type": "array",
|
||||
"value": [
|
||||
{"type": "integer", "value": "1"},
|
||||
{"type": "integer", "value": "2"},
|
||||
{"type": "integer", "value": "3"}
|
||||
]
|
||||
},
|
||||
"floats": {
|
||||
"type": "array",
|
||||
"value": [
|
||||
{"type": "float", "value": "1.1"},
|
||||
{"type": "float", "value": "2.1"},
|
||||
{"type": "float", "value": "3.1"}
|
||||
]
|
||||
},
|
||||
"strings": {
|
||||
"type": "array",
|
||||
"value": [
|
||||
{"type": "string", "value": "a"},
|
||||
{"type": "string", "value": "b"},
|
||||
{"type": "string", "value": "c"}
|
||||
]
|
||||
},
|
||||
"dates": {
|
||||
"type": "array",
|
||||
"value": [
|
||||
{"type": "datetime", "value": "1987-07-05T17:45:00Z"},
|
||||
{"type": "datetime", "value": "1979-05-27T07:32:00Z"},
|
||||
{"type": "datetime", "value": "2006-06-01T11:00:00Z"}
|
||||
]
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
ints = [1, 2, 3]
|
||||
floats = [1.1, 2.1, 3.1]
|
||||
strings = ["a", "b", "c"]
|
||||
dates = [
|
||||
1987-07-05T17:45:00Z,
|
||||
1979-05-27T07:32:00Z,
|
||||
2006-06-01T11:00:00Z,
|
||||
]
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"f": {"type": "bool", "value": "false"},
|
||||
"t": {"type": "bool", "value": "true"}
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
t = true
|
||||
f = false
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"group": {
|
||||
"answer": {"type": "integer", "value": "42"},
|
||||
"more": {
|
||||
"type": "array",
|
||||
"value": [
|
||||
{"type": "integer", "value": "42"},
|
||||
{"type": "integer", "value": "42"}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# Top comment.
|
||||
# Top comment.
|
||||
# Top comment.
|
||||
|
||||
# [no-extraneous-groups-please]
|
||||
|
||||
[group] # Comment
|
||||
answer = 42 # Comment
|
||||
# no-extraneous-keys-please = 999
|
||||
# In between comment.
|
||||
more = [ # Comment
|
||||
# What about multiple # comments?
|
||||
# Can you handle it?
|
||||
#
|
||||
# Evil.
|
||||
# Evil.
|
||||
42, 42, # Comments within arrays are fun.
|
||||
# What about multiple # comments?
|
||||
# Can you handle it?
|
||||
#
|
||||
# Evil.
|
||||
# Evil.
|
||||
# ] Did I fool you?
|
||||
] # Hopefully not.
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"bestdayever": {
|
||||
"type": "datetime",
|
||||
"value": "1987-07-05T17:45:00.123456789Z"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
bestdayever = 1987-07-05T17:45:00.123456789012345Z
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"bestdayever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
bestdayever = 1987-07-05T17:45:00Z
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"a": {
|
||||
"b": {
|
||||
"type": "integer",
|
||||
"value": "123"
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"a": {
|
||||
"b": {
|
||||
"c": {
|
||||
"type": "integer",
|
||||
"value": "1"
|
||||
},
|
||||
"d": {
|
||||
"type": "integer",
|
||||
"value": "2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"in": {
|
||||
"type": {
|
||||
"color": {
|
||||
"type": "string",
|
||||
"value": "blue"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"value": "cat"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
a.b = 123
|
||||
|
||||
[table]
|
||||
a.b.c = 1
|
||||
a . b . d = 2
|
||||
|
||||
in = { type.name = "cat", type.color = "blue" }
|
||||
+1
@@ -0,0 +1 @@
|
||||
{}
|
||||
Whitespace-only changes.
@@ -0,0 +1,5 @@
|
||||
best-day-ever = 1987-07-05T17:45:00Z
|
||||
|
||||
[numtheory]
|
||||
boring = false
|
||||
perfection = [6, 28, 496]
|
||||
@@ -0,0 +1 @@
|
||||
{"Array":{"key1":{"type":"array","value":[{"type":"integer","value":"1"},{"type":"integer","value":"2"},{"type":"integer","value":"3"}]},"key2":{"type":"array","value":[{"type":"string","value":"red"},{"type":"string","value":"yellow"},{"type":"string","value":"green"}]},"key3":{"type":"array","value":[{"type":"array","value":[{"type":"integer","value":"1"},{"type":"integer","value":"2"}]},{"type":"array","value":[{"type":"integer","value":"3"},{"type":"integer","value":"4"},{"type":"integer","value":"5"}]}]},"key4":{"type":"array","value":[{"type":"array","value":[{"type":"integer","value":"1"},{"type":"integer","value":"2"}]},{"type":"array","value":[{"type":"string","value":"a"},{"type":"string","value":"b"},{"type":"string","value":"c"}]}]},"key5":{"type":"array","value":[{"type":"integer","value":"1"},{"type":"integer","value":"2"},{"type":"integer","value":"3"}]},"key6":{"type":"array","value":[{"type":"integer","value":"1"},{"type":"integer","value":"2"}]}},"Booleans":{"False":{"type":"bool","value":"false"},"True":{"type":"bool","value":"true"}},"Datetime":{"key1":{"type":"datetime","value":"1979-05-27T07:32:00Z"}},"Float":{"both":{},"exponent":{},"fractional":{"key1":{"type":"float","value":"1.0"},"key2":{"type":"float","value":"3.1415"},"key3":{"type":"float","value":"-0.01"}}},"Integer":{"key1":{"type":"integer","value":"99"},"key2":{"type":"integer","value":"42"},"key3":{"type":"integer","value":"0"},"key4":{"type":"integer","value":"-17"}},"String":{"Literal":{"Multiline":{"lines":{"type":"string","value":"The first newline is\ntrimmed in raw strings.\n All other whitespace\n is preserved.\n"},"regex2":{"type":"string","value":"I [dw]on't need \\d{2} apples"}},"quoted":{"type":"string","value":"Tom \"Dubs\" Preston-Werner"},"regex":{"type":"string","value":"\u003c\\i\\c*\\s*\u003e"},"winpath":{"type":"string","value":"C:\\Users\\nodejs\\templates"},"winpath2":{"type":"string","value":"\\\\ServerX\\admin$\\system32\\"}},"Multiline":{"key1":{"type":"string","value":"One\nTwo"},"key2":{"type":"string","value":"One\nTwo"},"key3":{"type":"string","value":"One\nTwo"}},"Multilined":{"Singleline":{"key1":{"type":"string","value":"The quick brown fox jumps over the lazy dog."},"key2":{"type":"string","value":"The quick brown fox jumps over the lazy dog."},"key3":{"type":"string","value":"The quick brown fox jumps over the lazy dog."}}},"basic":{"type":"string","value":"I'm a string. \"You can quote me\". Name\u0009José\nLocation\u0009SF."}},"Table":{"key":{"type":"string","value":"value"}},"dog":{"tater":{"type":{"type":"string","value":"pug"}}},"fruit":[{"name":{"type":"string","value":"apple"},"physical":{"color":{"type":"string","value":"red"},"shape":{"type":"string","value":"round"}},"variety":[{"name":{"type":"string","value":"red delicious"}},{"name":{"type":"string","value":"granny smith"}}]},{"name":{"type":"string","value":"banana"},"variety":[{"name":{"type":"string","value":"plantain"}}]}],"products":[{"name":{"type":"string","value":"Hammer"},"sku":{"type":"integer","value":"738594937"}},{},{"color":{"type":"string","value":"gray"},"name":{"type":"string","value":"Nail"},"sku":{"type":"integer","value":"284758393"}}],"x":{"y":{"z":{"w":{}}}}}
|
||||
Loaded 100 of 17624 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user