toml-span-0.3.0/.cargo_vcs_info.json0000644000000001470000000000100127500ustar { "git": { "sha1": "f206445808167fa370c224bb70689fa89e918024" }, "path_in_vcs": "toml-span" }toml-span-0.3.0/CHANGELOG.md000064400000000000000000000035531046102023000133550ustar 00000000000000 # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] - ReleaseDate ## [0.3.0] - 2024-06-26 ### Changed - [PR#7](https://github.com/EmbarkStudios/toml-span/pull/7) implemented `Borrow` for `Key`, making the API much more ergonomic. ## [0.2.1] - 2024-06-13 ### Changed - [PR#6](https://github.com/EmbarkStudios/toml-span/pull/6) updates crates and fixed a lint. ### Fixed - [PR#4](https://github.com/EmbarkStudios/toml-span/pull/4) added the missing `repository` field in the `toml-span` package manifest. - [PR#5](https://github.com/EmbarkStudios/toml-span/pull/5) fixed the crate package missing the LICENSE-* files and CHANGELOG.md. ## [0.2.0] - 2024-02-22 ### Added - [PR#3](https://github.com/EmbarkStudios/toml-span/pull/3) actually added some documentation. - [PR#3](https://github.com/EmbarkStudios/toml-span/pull/3) added `DeserError::merge` ### Fixed - [PR#3](https://github.com/EmbarkStudios/toml-span/pull/3) `TableHelper::take` now appends the key to the `expected` array ### Changed - [PR#3](https://github.com/EmbarkStudios/toml-span/pull/3) removed `TableHelper::with_default/parse/parse_opt` ## [0.1.0] - 2024-02-20 ### Added - Initial implementation [Unreleased]: https://github.com/EmbarkStudios/toml-span/compare/0.3.0...HEAD [0.3.0]: https://github.com/EmbarkStudios/toml-span/compare/0.2.1...0.3.0 [0.2.1]: https://github.com/EmbarkStudios/toml-span/compare/0.2.0...0.2.1 [0.2.0]: https://github.com/EmbarkStudios/toml-span/compare/0.1.0...0.2.0 [0.1.0]: https://github.com/EmbarkStudios/toml-span/releases/tag/0.1.0 toml-span-0.3.0/Cargo.toml0000644000000021770000000000100107530ustar # 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 = "toml-span" version = "0.3.0" description = "Toml parser and deserializer that preserves span information" homepage = "https://github.com/EmbarkStudios/toml-span" documentation = "https://docs.rs/toml-span" readme = "README.md" categories = [ "parser-implementations", "config", ] license = "MIT OR Apache-2.0" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", ] [dependencies.codespan-reporting] version = "0.11" optional = true [dependencies.serde] version = "1.0" optional = true [dependencies.smallvec] version = "1.13" [features] reporting = ["dep:codespan-reporting"] serde = ["dep:serde"] toml-span-0.3.0/Cargo.toml.orig000064400000000000000000000011411046102023000144220ustar 00000000000000[package] name = "toml-span" version = "0.3.0" description = "Toml parser and deserializer that preserves span information" license.workspace = true edition.workspace = true homepage.workspace = true documentation = "https://docs.rs/toml-span" readme = "README.md" categories = ["parser-implementations", "config"] [features] serde = ["dep:serde"] reporting = ["dep:codespan-reporting"] [dependencies] codespan-reporting = { version = "0.11", optional = true } serde = { version = "1.0", optional = true } smallvec = "1.13" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] toml-span-0.3.0/LICENSE-APACHE000064400000000000000000000251421046102023000134660ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. toml-span-0.3.0/LICENSE-MIT000064400000000000000000000020421046102023000131700ustar 00000000000000Copyright (c) 2019 Embark Studios 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. toml-span-0.3.0/README.md000064400000000000000000000443071046102023000130250ustar 00000000000000
# `↔️ toml-span` **Span-preserving toml deserializer** [![Embark](https://img.shields.io/badge/embark-open%20source-blueviolet.svg)](https://embark.dev) [![Embark](https://img.shields.io/badge/discord-ark-%237289da.svg?logo=discord)](https://discord.gg/dAuKfZS) [![Crates.io](https://img.shields.io/crates/v/rust-gpu.svg)](https://crates.io/crates/toml-span) [![Docs](https://docs.rs/toml-span/badge.svg)](https://docs.rs/toml-span) [![dependency status](https://deps.rs/repo/github/EmbarkStudios/toml-span/status.svg)](https://deps.rs/repo/github/EmbarkStudios/toml-span) [![Build status](https://github.com/EmbarkStudios/toml-span/workflows/CI/badge.svg)](https://github.com/EmbarkStudios/toml-span/actions)
## Differences from `toml` First off I just want to be up front and clear about the differences/limitations of this crate versus `toml` 1. No `serde` support for deserialization, there is a `serde` feature, but that only enables serialization of the `Value` and `Spanned` types. 1. No toml serialization. This crate is only intended to be a span preserving deserializer, there is no intention to provide serialization to toml, especially the advanced format preserving kind provided by `toml-edit`. 1. No datetime deserialization. It would be trivial to add support for this (behind an optional feature), I just have no use for it at the moment. PRs welcome. ## Why does this crate exist? ### The problem This crate was specifically made to suit the needs of [cargo-deny], namely, that it can always retrieve the span of any toml item that it wants to. While the [toml](https://docs.rs/toml/latest/toml/) crate can also produce span information via [toml::Spanned](https://docs.rs/toml/latest/toml/struct.Spanned.html) there is one rather significant limitation, namely, that it must pass through [serde](https://docs.rs/serde/latest/serde/). While in simple cases the `Spanned` type works quite well, eg. ```rust,ignore #[derive(serde::Deserialize)] struct Simple { /// This works just fine simple_string: toml::Spanned, } ``` As soon as you have a [more complicated scenario](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=aeb611bbe387538d2ebb6780055b3167), the mechanism that `toml` uses to get the span information breaks down. ```rust,ignore #[derive(serde::Deserialize)] #[serde(untagged)] enum Ohno { Integer(u32), SpannedString(toml::Spanned), } #[derive(serde::Deserialize)] struct Root { integer: Ohno, string: Ohno } fn main() { let toml = r#" integer = 42 string = "we want this to be spanned" "#; let parsed: Root = toml::from_str(toml).expect("failed to deserialize toml"); } ``` ```text thread 'main' panicked at src/main.rs:20:45: failed to deserialize toml: Error { inner: Error { inner: TomlError { message: "data did not match any variant of untagged enum Ohno", original: Some("\ninteger = 42\nstring = \"we want this to be spanned\"\n"), keys: ["string"], span: Some(23..51) } } } ``` To understand why this fails we can look at what `#[derive(serde::Deserialize)]` expand to for `Ohno` in HIR. ```rust,ignore #[allow(unused_extern_crates, clippy :: useless_attribute)] extern crate serde as _serde; #[automatically_derived] impl <'de> _serde::Deserialize<'de> for Ohno { fn deserialize<__D>(__deserializer: __D) -> _serde::__private::Result where __D: _serde::Deserializer<'de> { let __content = match #[lang = "branch"](<_serde::__private::de::Content as _serde::Deserialize>::deserialize(__deserializer)) { #[lang = "Break"] { 0: residual } => #[allow(unreachable_code)] return #[lang = "from_residual"](residual), #[lang = "Continue"] { 0: val } => #[allow(unreachable_code)] val, }; let __deserializer = _serde::__private::de::ContentRefDeserializer<, , __D::Error>::new(&__content); if let _serde::__private::Ok(__ok) = _serde::__private::Result::map(::deserialize(__deserializer), Ohno::Integer) { return _serde::__private::Ok(__ok); } if let _serde::__private::Ok(__ok) = _serde::__private::Result::map( as _serde::Deserialize>::deserialize(__deserializer), Ohno::SpannedString) { return _serde::__private::Ok(__ok); } _serde::__private::Err(_serde::de::Error::custom("data did not match any variant of untagged enum Ohno")) } } ``` What serde does in the untagged case is first deserialize into `_serde::__private::de::Content`, an internal API container that is easiest to think of as something like `serde_json::Value`. This is because serde speculatively parses each enum variant until one succeeds by passing a `ContentRefDeserializer` that just borrows the deserialized `Content` from earlier to satisfy the serde deserialize API consuming the `Deserializer`. The problem comes because of how [`toml::Spanned`](https://docs.rs/serde_spanned/0.6.5/src/serde_spanned/spanned.rs.html#161-212) works, namely that it uses a hack to workaround the limitations of the serde API in order to "deserialize" the item as well as its span information, by the `Spanned` object specifically requesting a set of keys from the `toml::Deserializer` impl so that it can [encode](https://github.com/toml-rs/toml/blob/c4b62fda23343037ebe5ea93db9393cb25fcf233/crates/toml_edit/src/de/spanned.rs#L27-L70) the span information as if it was a struct to satisfy serde. But serde doesn't know that when it deserializes the `Content` object, it just knows that the Deserializer reports it has a string, int or what have you, and deserializes that, "losing" the span information. This problem also affects things like `#[serde(flatten)]` for slightly different reasons, but they all basically come down to the serde API not truly supporting span information, nor [any plans](https://github.com/serde-rs/serde/issues/1811) to. ### How `toml-span` is different This crate works by just...not using `serde`. The core of the crate is based off of [basic-toml](https://github.com/dtolnay/basic-toml) which itself a fork of `toml v0.5` before it added a ton of features an complexity that...well, is not needed by [cargo-deny] or many other crates that only need deserialization. Removing `serde` support means that while deserialization must be manually written, which can be tedious in some cases, while doing the porting of [cargo-deny] I actually came to appreciate it more and more due to a couple of things. 1. Maximal control. `toml-span` does an initial deserialization pass into `toml_span::value::Value` which keeps span information for both keys and values, and provides helpers (namely `TableHelper`), but other than satisfying the `toml_span::Deserialize` trait doesn't restrict you in how you want to deserialize your values, and you don't even have to use that if you don't want to. 2. While it's slower to manually write deserialization code rather than just putting on a few serde attributes, the truth is that that initial convenience carries a compile time cost in terms of `serde_derive` and all of its dependencies, as well as all of the code that is generated, for...ever. This is fine when you are prototyping, but becomes quite wasteful once you have (mostly/somewhat) stabilized your data format. 3. (optional) Span-based errors. `toml-span` provides the `reporting` feature that can be enabled to have `toml_span::Error` be able to be converted into a [Diagnostic](https://docs.rs/codespan-reporting/latest/codespan_reporting/diagnostic/struct.Diagnostic.html) which can provide nice error output if you use the `codespan-reporting` crate. ## Usage ### Simple The most simple use case for `toml-span` is just as slimmer version of `toml` that also has a pointer API similar to [serde_json](https://docs.rs/serde_json/latest/serde_json/enum.Value.html#method.pointer) allowing easy piecemeal deserialization of a toml document. #### `toml` version ```rust,ignore fn is_crates_io_sparse(config: &toml::Value) -> Option { config .get("registries") .and_then(|v| v.get("crates-io")) .and_then(|v| v.get("protocol")) .and_then(|v| v.as_str()) .and_then(|v| match v { "sparse" => Some(true), "git" => Some(false), _ => None, }) } ``` #### `toml-span` version ```rust fn is_crates_io_sparse(config: &toml_span::Value) -> Option { match config.pointer("/registries/crates-io/protocol").and_then(|p| p.as_str())? { "sparse" => Some(true), "git" => Some(false), _ => None } } ``` ### Common Of course the most common case is deserializing toml into Rust containers. #### `toml` version ```rust,ignore #[derive(Deserialize, Clone)] #[cfg_attr(test, derive(Debug, PartialEq, Eq))] #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct CrateBan { pub name: Spanned, pub version: Option, /// One or more crates that will allow this crate to be used if it is a /// direct dependency pub wrappers: Option>>>, /// Setting this to true will only emit an error if multiple // versions of the crate are found pub deny_multiple_versions: Option>, } ``` #### `toml-span` version The following code is much more verbose (before proc macros run at least), but show cases something that moving [cargo-deny] to `toml-span` allowed, namely, `PackageSpec`. Before `toml-span`, all cases where a user specifies a crate spec, (ie, name + optional version requirement) was done via two separate fields, `name` and `version`. This was quite verbose, as in many cases not only is `version` not specified, but also could be just a string if the user doesn't need/want to provide other fields. Normally one would use the [string or struct](https://serde.rs/string-or-struct.html) idiom but this was impossible due to how I wanted to reorganize the data to have the package spec as either a string or struct, _as well as_ optional data that is flattened to the same level as the package spec. But since `toml-span` changes how deserialization is done, this change was quite trivial after the initial work of getting the crate stood up was done. ```rust,ignore pub type CrateBan = PackageSpecOrExtended; #[cfg_attr(test, derive(Debug, PartialEq, Eq))] pub struct CrateBanExtended { /// One or more crates that will allow this crate to be used if it is a /// direct dependency pub wrappers: Option>>>, /// Setting this to true will only emit an error if multiple versions of the /// crate are found pub deny_multiple_versions: Option>, /// The reason for banning the crate pub reason: Option, /// The crate to use instead of the banned crate, could be just the crate name /// or a URL pub use_instead: Option>, } impl<'de> Deserialize<'de> for CrateBanExtended { fn deserialize(value: &mut Value<'de>) -> Result { // The table helper provides convenience wrappers around a Value::Table, which // is just a BTreeMap let mut th = TableHelper::new(value)?; // Since we specify the keys manually there is no need for serde(rename/rename_all) let wrappers = th.optional("wrappers"); let deny_multiple_versions = th.optional("deny-multiple-versions"); let reason = th.optional_s("reason"); let use_instead = th.optional("use-instead"); // Specifying None means that any keys that still exist in the table are // unknown, producing an error the same as with serde(deny_unknown_fields) th.finalize(None)?; Ok(Self { wrappers, deny_multiple_versions, reason: reason.map(Reason::from), use_instead, }) } } #[derive(Clone, PartialEq, Eq)] pub struct PackageSpec { pub name: Spanned, pub version_req: Option, } impl<'de> Deserialize<'de> for PackageSpec { fn deserialize(value: &mut Value<'de>) -> Result { use std::borrow::Cow; struct Ctx<'de> { inner: Cow<'de, str>, split: Option<(usize, bool)>, span: Span, } impl<'de> Ctx<'de> { fn from_str(bs: Cow<'de, str>, span: Span) -> Self { let split = bs .find('@') .map(|i| (i, true)) .or_else(|| bs.find(':').map(|i| (i, false))); Self { inner: bs, split, span, } } } let ctx = match value.take() { ValueInner::String(s) => Ctx::from_str(s, value.span), ValueInner::Table(tab) => { let mut th = TableHelper::from((tab, value.span)); if let Some(mut val) = th.table.remove(&"crate".into()) { let s = val.take_string(Some("a crate spec"))?; th.finalize(Some(value))?; Ctx::from_str(s, val.span) } else { // Encourage user to use the 'crate' spec instead let name = th.required("name").map_err(|e| { if matches!(e.kind, toml_span::ErrorKind::MissingField(_)) { (toml_span::ErrorKind::MissingField("crate"), e.span).into() } else { e } })?; let version = th.optional::>>("version"); // We return all the keys we haven't deserialized back to the value, // so that further deserializers can use them as this spec is // always embedded in a larger structure th.finalize(Some(value))?; let version_req = if let Some(vr) = version { Some(vr.value.parse().map_err(|e: semver::Error| { toml_span::Error::from(( toml_span::ErrorKind::Custom(e.to_string()), vr.span, )) })?) } else { None }; return Ok(Self { name, version_req }); } } other => return Err(expected("a string or table", other, value.span).into()), }; let (name, version_req) = if let Some((i, make_exact)) = ctx.split { let mut v: VersionReq = ctx.inner[i + 1..].parse().map_err(|e: semver::Error| { toml_span::Error::from(( toml_span::ErrorKind::Custom(e.to_string()), Span::new(ctx.span.start + i + 1, ctx.span.end), )) })?; if make_exact { if let Some(comp) = v.comparators.get_mut(0) { comp.op = semver::Op::Exact; } } ( Spanned::with_span( ctx.inner[..i].into(), Span::new(ctx.span.start, ctx.span.start + i), ), Some(v), ) } else { (Spanned::with_span(ctx.inner.into(), ctx.span), None) }; Ok(Self { name, version_req }) } } pub struct PackageSpecOrExtended { pub spec: PackageSpec, pub inner: Option, } impl PackageSpecOrExtended { pub fn try_convert(self) -> Result, E> where V: TryFrom, { let inner = if let Some(i) = self.inner { Some(V::try_from(i)?) } else { None }; Ok(PackageSpecOrExtended { spec: self.spec, inner, }) } pub fn convert(self) -> PackageSpecOrExtended where V: From, { PackageSpecOrExtended { spec: self.spec, inner: self.inner.map(V::from), } } } impl<'de, T> toml_span::Deserialize<'de> for PackageSpecOrExtended where T: toml_span::Deserialize<'de>, { fn deserialize(value: &mut Value<'de>) -> Result { let spec = PackageSpec::deserialize(value)?; // If more keys exist in the table (or string) then try to deserialize // the rest as the "extended" portion let inner = if value.has_keys() { Some(T::deserialize(value)?) } else { None }; Ok(Self { spec, inner }) } } ``` ## Contributing [![Contributor Covenant](https://img.shields.io/badge/contributor%20covenant-v1.4-ff69b4.svg)](CODE_OF_CONDUCT.md) We welcome community contributions to this project. Please read our [Contributor Guide](CONTRIBUTING.md) for more information on how to get started. Please also read our [Contributor Terms](CONTRIBUTING.md#contributor-terms) before you make any contributions. Any contribution intentionally submitted for inclusion in an Embark Studios project, shall comply with the Rust standard licensing model (MIT OR Apache 2.0) and therefore be dual licensed as described below, without any additional terms or conditions: ### License This contribution is dual licensed under EITHER OF - Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) - MIT license ([LICENSE-MIT](LICENSE-MIT) or ) at your option. For clarity, "your" refers to Embark or any other licensee/user of the contribution. [cargo-deny]: https://github.com/EmbarkStudios/cargo-deny toml-span-0.3.0/release.toml000064400000000000000000000012521046102023000140530ustar 00000000000000pre-release-commit-message = "Release {{version}}" tag-message = "Release {{version}}" tag-name = "{{version}}" pre-release-replacements = [ { file = "CHANGELOG.md", search = "Unreleased", replace = "{{version}}" }, { file = "CHANGELOG.md", search = "\\.\\.\\.HEAD", replace = "...{{tag_name}}" }, { file = "CHANGELOG.md", search = "ReleaseDate", replace = "{{date}}" }, { file = "CHANGELOG.md", search = "", replace = "\n## [Unreleased] - ReleaseDate" }, { file = "CHANGELOG.md", search = "", replace = "\n[Unreleased]: https://github.com/EmbarkStudios/toml-span/compare/{{tag_name}}...HEAD" }, ] toml-span-0.3.0/src/de.rs000064400000000000000000001063121046102023000132660ustar 00000000000000//! Core deserialization logic that deserializes toml content to [`Value`] use crate::{ error::{Error, ErrorKind}, tokens::{Error as TokenError, Token, Tokenizer}, value::{self, Key, Value, ValueInner}, Span, }; use smallvec::SmallVec; use std::{ borrow::Cow, collections::{btree_map::Entry, BTreeMap}, }; type DeStr<'de> = Cow<'de, str>; type TablePair<'de> = (Key<'de>, Val<'de>); type InlineVec = SmallVec<[T; 5]>; /// Parses a toml string into a [`ValueInner::Table`] pub fn parse(s: &str) -> Result, Error> { let mut de = Deserializer::new(s); let mut tables = de.tables()?; let table_indices = build_table_indices(&tables); let table_pindices = build_table_pindices(&tables); let root_ctx = Ctx { depth: 0, cur: 0, cur_parent: 0, table_indices: &table_indices, table_pindices: &table_pindices, de: &de, values: None, max: tables.len(), }; let mut root = value::Table::new(); deserialize_table(root_ctx, &mut tables, &mut root)?; Ok(Value::with_span( ValueInner::Table(root), Span::new(0, s.len()), )) } struct Deserializer<'a> { input: &'a str, tokens: Tokenizer<'a>, } struct Ctx<'de, 'b> { depth: usize, cur: usize, cur_parent: usize, max: usize, table_indices: &'b BTreeMap>, Vec>, table_pindices: &'b BTreeMap>, Vec>, de: &'b Deserializer<'de>, values: Option>>, //array: bool, } impl<'de, 'b> Ctx<'de, 'b> { #[inline] fn error(&self, start: usize, end: Option, kind: ErrorKind) -> Error { self.de.error(start, end, kind) } } fn deserialize_table<'de, 'b>( mut ctx: Ctx<'de, 'b>, tables: &'b mut [Table<'de>], table: &mut value::Table<'de>, ) -> Result { while ctx.cur_parent < ctx.max && ctx.cur < ctx.max { if let Some(values) = ctx.values.take() { for (key, val) in values { table_insert(table, key, val, ctx.de)?; } } let next_table = { let prefix_stripped: InlineVec<_> = tables[ctx.cur_parent].header[..ctx.depth] .iter() .map(|v| v.name.clone()) .collect::>(); ctx.table_pindices .get(&prefix_stripped) .and_then(|entries| { let start = entries.binary_search(&ctx.cur).unwrap_or_else(|v| v); if start == entries.len() || entries[start] < ctx.cur { return None; } entries[start..].iter().find_map(|i| { let i = *i; (i < ctx.max && tables[i].values.is_some()).then_some(i) }) }) }; let Some(pos) = next_table else { break; }; ctx.cur = pos; // Test to see if we're duplicating our parent's table, and if so // then this is an error in the toml format if ctx.cur_parent != pos { let cur = &tables[pos]; let parent = &tables[ctx.cur_parent]; if parent.header == cur.header { let name = cur.header.iter().fold(String::new(), |mut s, k| { if !s.is_empty() { s.push('.'); } s.push_str(&k.name); s }); let first = Span::new(parent.at, parent.end); return Err(ctx.error( cur.at, Some(cur.end), ErrorKind::DuplicateTable { name, first }, )); } // If we're here we know we should share the same prefix, and if // the longer table was defined first then we want to narrow // down our parent's length if possible to ensure that we catch // duplicate tables defined afterwards. let parent_len = parent.header.len(); let cur_len = cur.header.len(); if cur_len < parent_len { ctx.cur_parent = pos; } } let ttable = &mut tables[pos]; // If we're not yet at the appropriate depth for this table then we // just next the next portion of its header and then continue // decoding. if ctx.depth != ttable.header.len() { let key = ttable.header[ctx.depth].clone(); if let Some((k, _)) = table.get_key_value(&key) { return Err(ctx.error( key.span.start, Some(key.span.end), ErrorKind::DuplicateKey { key: key.name.to_string(), first: k.span, }, )); } let array = ttable.array && ctx.depth == ttable.header.len() - 1; ctx.cur += 1; let cctx = Ctx { depth: ctx.depth + if array { 0 } else { 1 }, max: ctx.max, cur: 0, cur_parent: pos, table_indices: ctx.table_indices, table_pindices: ctx.table_pindices, de: ctx.de, values: None, //array.then(|| ttable.values.take().unwrap()), }; let value = if array { let mut arr = Vec::new(); deserialize_array(cctx, tables, &mut arr)?; ValueInner::Array(arr) } else { let mut tab = value::Table::new(); deserialize_table(cctx, tables, &mut tab)?; ValueInner::Table(tab) }; table.insert(key, Value::new(value)); continue; } // Rule out cases like: // // [[foo.bar]] // [[foo]] if ttable.array { return Err(ctx.error(ttable.at, Some(ttable.end), ErrorKind::RedefineAsArray)); } ctx.values = ttable.values.take(); } Ok(ctx.cur_parent) } fn to_value<'de>(val: Val<'de>, de: &Deserializer<'de>) -> Result, Error> { let value = match val.e { E::String(s) => ValueInner::String(s), E::Boolean(b) => ValueInner::Boolean(b), E::Integer(i) => ValueInner::Integer(i), E::Float(f) => ValueInner::Float(f), E::Array(arr) => { let mut varr = Vec::new(); for val in arr { varr.push(to_value(val, de)?); } ValueInner::Array(varr) } E::DottedTable(tab) | E::InlineTable(tab) => { let mut ntable = value::Table::new(); for (k, v) in tab { table_insert(&mut ntable, k, v, de)?; } ValueInner::Table(ntable) } }; Ok(Value::with_span(value, Span::new(val.start, val.end))) } fn table_insert<'de>( table: &mut value::Table<'de>, key: Key<'de>, val: Val<'de>, de: &Deserializer<'de>, ) -> Result<(), Error> { match table.entry(key.clone()) { Entry::Occupied(occ) => Err(de.error( key.span.start, Some(key.span.end), ErrorKind::DuplicateKey { key: key.name.to_string(), first: occ.key().span, }, )), Entry::Vacant(vac) => { vac.insert(to_value(val, de)?); Ok(()) } } } fn deserialize_array<'de, 'b>( mut ctx: Ctx<'de, 'b>, tables: &'b mut [Table<'de>], arr: &mut Vec>, ) -> Result { // if let Some(values) = ctx.values.take() { // for (key, val) in values { // //printc!(&ctx, "{} => {val:?}", key.name); // arr.push(to_value(val, ctx.de)?); // } // } while ctx.cur_parent < ctx.max { let header_stripped = tables[ctx.cur_parent] .header .iter() .map(|v| v.name.clone()) .collect::>(); let start_idx = ctx.cur_parent + 1; let next = ctx .table_indices .get(&header_stripped) .and_then(|entries| { let start = entries.binary_search(&start_idx).unwrap_or_else(|v| v); if start == entries.len() || entries[start] < start_idx { return None; } entries[start..] .iter() .filter_map(|i| if *i < ctx.max { Some(*i) } else { None }) .map(|i| (i, &tables[i])) .find(|(_, table)| table.array) .map(|p| p.0) }) .unwrap_or(ctx.max); let actx = Ctx { values: Some( tables[ctx.cur_parent] .values .take() .expect("no array values"), ), max: next, depth: ctx.depth + 1, cur: 0, cur_parent: ctx.cur_parent, table_indices: ctx.table_indices, table_pindices: ctx.table_pindices, de: ctx.de, }; let mut table = value::Table::new(); deserialize_table(actx, tables, &mut table)?; arr.push(Value::new(ValueInner::Table(table))); ctx.cur_parent = next; } Ok(ctx.cur_parent) } // Builds a datastructure that allows for efficient sublinear lookups. The // returned BTreeMap contains a mapping from table header (like [a.b.c]) to list // of tables with that precise name. The tables are being identified by their // index in the passed slice. We use a list as the implementation uses this data // structure for arrays as well as tables, so if any top level [[name]] array // contains multiple entries, there are multiple entries in the list. The lookup // is performed in the `SeqAccess` implementation of `MapVisitor`. The lists are // ordered, which we exploit in the search code by using bisection. fn build_table_indices<'de>(tables: &[Table<'de>]) -> BTreeMap>, Vec> { let mut res = BTreeMap::new(); for (i, table) in tables.iter().enumerate() { let header = table .header .iter() .map(|v| v.name.clone()) .collect::>(); res.entry(header).or_insert_with(Vec::new).push(i); } res } // Builds a datastructure that allows for efficient sublinear lookups. The // returned BTreeMap contains a mapping from table header (like [a.b.c]) to list // of tables whose name at least starts with the specified name. So searching // for [a.b] would give both [a.b.c.d] as well as [a.b.e]. The tables are being // identified by their index in the passed slice. // // A list is used for two reasons: First, the implementation also stores arrays // in the same data structure and any top level array of size 2 or greater // creates multiple entries in the list with the same shared name. Second, there // can be multiple tables sharing the same prefix. // // The lookup is performed in the `MapAccess` implementation of `MapVisitor`. // The lists are ordered, which we exploit in the search code by using // bisection. fn build_table_pindices<'de>(tables: &[Table<'de>]) -> BTreeMap>, Vec> { let mut res = BTreeMap::new(); for (i, table) in tables.iter().enumerate() { let header = table .header .iter() .map(|v| v.name.clone()) .collect::>(); for len in 0..=header.len() { res.entry(header[..len].into()) .or_insert_with(Vec::new) .push(i); } } res } // fn headers_equal(hdr_a: &[Key<'_>], hdr_b: &[Key<'_>]) -> bool { // if hdr_a.len() != hdr_b.len() { // return false; // } // hdr_a.iter().zip(hdr_b.iter()).all(|(h1, h2)| h1.1 == h2.1) // } #[derive(Debug)] struct Table<'de> { at: usize, end: usize, header: InlineVec>, values: Option>>, array: bool, } impl<'a> Deserializer<'a> { fn new(input: &'a str) -> Deserializer<'a> { Deserializer { tokens: Tokenizer::new(input), input, } } fn tables(&mut self) -> Result>, Error> { let mut tables = Vec::new(); let mut cur_table = Table { at: 0, end: 0, header: InlineVec::new(), values: None, array: false, }; while let Some(line) = self.line()? { match line { Line::Table { at, end, mut header, array, } => { if !cur_table.header.is_empty() || cur_table.values.is_some() { tables.push(cur_table); } cur_table = Table { at, end, header: InlineVec::new(), values: Some(Vec::new()), array, }; while let Some(part) = header.next().map_err(|e| self.token_error(e))? { cur_table.header.push(part); } } Line::KeyValue(key, value) => { if cur_table.values.is_none() { cur_table.values = Some(Vec::new()); } self.add_dotted_key(key, value, cur_table.values.as_mut().unwrap())?; } } } if !cur_table.header.is_empty() || cur_table.values.is_some() { tables.push(cur_table); } Ok(tables) } fn line(&mut self) -> Result>, Error> { loop { self.eat_whitespace(); if self.eat_comment()? { continue; } if self.eat(Token::Newline)? { continue; } break; } match self.peek()? { Some((_, Token::LeftBracket)) => self.table_header().map(Some), Some(_) => self.key_value().map(Some), None => Ok(None), } } fn table_header(&mut self) -> Result, Error> { let start = self.tokens.current(); self.expect(Token::LeftBracket)?; let array = self.eat(Token::LeftBracket)?; let ret = Header::new(self.tokens.clone(), array); self.tokens.skip_to_newline(); let end = self.tokens.current(); Ok(Line::Table { at: start, end, header: ret, array, }) } fn key_value(&mut self) -> Result, Error> { let key = self.dotted_key()?; self.eat_whitespace(); self.expect(Token::Equals)?; self.eat_whitespace(); let value = self.value()?; self.eat_whitespace(); if !self.eat_comment()? { self.eat_newline_or_eof()?; } Ok(Line::KeyValue(key, value)) } fn value(&mut self) -> Result, Error> { let at = self.tokens.current(); let value = match self.next()? { Some((Span { start, end }, Token::String { val, .. })) => Val { e: E::String(val), start, end, }, Some((Span { start, end }, Token::Keylike("true"))) => Val { e: E::Boolean(true), start, end, }, Some((Span { start, end }, Token::Keylike("false"))) => Val { e: E::Boolean(false), start, end, }, Some((span, Token::Keylike(key))) => self.parse_keylike(at, span, key)?, Some((span, Token::Plus)) => self.number_leading_plus(span)?, Some((Span { start, .. }, Token::LeftBrace)) => { self.inline_table().map(|(Span { end, .. }, table)| Val { e: E::InlineTable(table), start, end, })? } Some((Span { start, .. }, Token::LeftBracket)) => { self.array().map(|(Span { end, .. }, array)| Val { e: E::Array(array), start, end, })? } Some(token) => { return Err(self.error( at, Some(token.0.end), ErrorKind::Wanted { expected: "a value", found: token.1.describe(), }, )); } None => return Err(self.eof()), }; Ok(value) } fn parse_keylike(&mut self, at: usize, span: Span, key: &'a str) -> Result, Error> { if key == "inf" || key == "nan" { return self.number(span, key); } let first_char = key.chars().next().expect("key should not be empty here"); match first_char { '-' | '0'..='9' => self.number(span, key), _ => Err(self.error(at, Some(span.end), ErrorKind::UnquotedString)), } } fn number(&mut self, Span { start, end }: Span, s: &'a str) -> Result, Error> { let to_integer = |f| Val { e: E::Integer(f), start, end, }; if let Some(s) = s.strip_prefix("0x") { self.integer(s, 16).map(to_integer) } else if let Some(s) = s.strip_prefix("0o") { self.integer(s, 8).map(to_integer) } else if let Some(s) = s.strip_prefix("0b") { self.integer(s, 2).map(to_integer) } else if s.contains('e') || s.contains('E') { self.float(s, None).map(|f| Val { e: E::Float(f), start, end: self.tokens.current(), }) } else if self.eat(Token::Period)? { let at = self.tokens.current(); match self.next()? { Some((Span { .. }, Token::Keylike(after))) => { self.float(s, Some(after)).map(|f| Val { e: E::Float(f), start, end: self.tokens.current(), }) } _ => Err(self.error(at, Some(end), ErrorKind::InvalidNumber)), } } else if s == "inf" { Ok(Val { e: E::Float(f64::INFINITY), start, end, }) } else if s == "-inf" { Ok(Val { e: E::Float(f64::NEG_INFINITY), start, end, }) } else if s == "nan" { Ok(Val { e: E::Float(f64::NAN.copysign(1.0)), start, end, }) } else if s == "-nan" { Ok(Val { e: E::Float(f64::NAN.copysign(-1.0)), start, end, }) } else { self.integer(s, 10).map(to_integer) } } fn number_leading_plus(&mut self, Span { start, end }: Span) -> Result, Error> { let start_token = self.tokens.current(); match self.next()? { Some((Span { end, .. }, Token::Keylike(s))) => self.number(Span { start, end }, s), _ => Err(self.error(start_token, Some(end), ErrorKind::InvalidNumber)), } } fn integer(&self, s: &'a str, radix: u32) -> Result { let allow_sign = radix == 10; let allow_leading_zeros = radix != 10; let (prefix, suffix) = self.parse_integer(s, allow_sign, allow_leading_zeros, radix)?; let start = self.tokens.substr_offset(s); if !suffix.is_empty() { return Err(self.error(start, Some(start + s.len()), ErrorKind::InvalidNumber)); } i64::from_str_radix(prefix.replace('_', "").trim_start_matches('+'), radix) .map_err(|_e| self.error(start, Some(start + s.len()), ErrorKind::InvalidNumber)) } fn parse_integer( &self, s: &'a str, allow_sign: bool, allow_leading_zeros: bool, radix: u32, ) -> Result<(&'a str, &'a str), Error> { let start = self.tokens.substr_offset(s); let mut first = true; let mut first_zero = false; let mut underscore = false; let mut end = s.len(); let send = start + s.len(); for (i, c) in s.char_indices() { let at = i + start; if i == 0 && (c == '+' || c == '-') && allow_sign { continue; } if c == '0' && first { first_zero = true; } else if c.is_digit(radix) { if !first && first_zero && !allow_leading_zeros { return Err(self.error(at, Some(send), ErrorKind::InvalidNumber)); } underscore = false; } else if c == '_' && first { return Err(self.error(at, Some(send), ErrorKind::InvalidNumber)); } else if c == '_' && !underscore { underscore = true; } else { end = i; break; } first = false; } if first || underscore { return Err(self.error(start, Some(send), ErrorKind::InvalidNumber)); } Ok((&s[..end], &s[end..])) } fn float(&mut self, s: &'a str, after_decimal: Option<&'a str>) -> Result { let (integral, mut suffix) = self.parse_integer(s, true, false, 10)?; let start = self.tokens.substr_offset(integral); let mut fraction = None; if let Some(after) = after_decimal { if !suffix.is_empty() { return Err(self.error(start, Some(start + s.len()), ErrorKind::InvalidNumber)); } let (a, b) = self.parse_integer(after, false, true, 10)?; fraction = Some(a); suffix = b; } let mut exponent = None; if suffix.starts_with('e') || suffix.starts_with('E') { let (a, b) = if suffix.len() == 1 { self.eat(Token::Plus)?; match self.next()? { Some((_, Token::Keylike(s))) => self.parse_integer(s, false, true, 10)?, _ => { return Err(self.error( start, Some(start + s.len()), ErrorKind::InvalidNumber, )) } } } else { self.parse_integer(&suffix[1..], true, true, 10)? }; if !b.is_empty() { return Err(self.error(start, Some(start + s.len()), ErrorKind::InvalidNumber)); } exponent = Some(a); } else if !suffix.is_empty() { return Err(self.error(start, Some(start + s.len()), ErrorKind::InvalidNumber)); } let mut number = integral .trim_start_matches('+') .chars() .filter(|c| *c != '_') .collect::(); if let Some(fraction) = fraction { number.push('.'); number.extend(fraction.chars().filter(|c| *c != '_')); } if let Some(exponent) = exponent { number.push('E'); number.extend(exponent.chars().filter(|c| *c != '_')); } number .parse() .map_err(|_e| self.error(start, Some(start + s.len()), ErrorKind::InvalidNumber)) .and_then(|n: f64| { if n.is_finite() { Ok(n) } else { Err(self.error(start, Some(start + s.len()), ErrorKind::InvalidNumber)) } }) } // TODO(#140): shouldn't buffer up this entire table in memory, it'd be // great to defer parsing everything until later. fn inline_table(&mut self) -> Result<(Span, Vec>), Error> { let mut ret = Vec::new(); self.eat_whitespace(); if let Some(span) = self.eat_spanned(Token::RightBrace)? { return Ok((span, ret)); } loop { let key = self.dotted_key()?; self.eat_whitespace(); self.expect(Token::Equals)?; self.eat_whitespace(); let value = self.value()?; self.add_dotted_key(key, value, &mut ret)?; self.eat_whitespace(); if let Some(span) = self.eat_spanned(Token::RightBrace)? { return Ok((span, ret)); } self.expect(Token::Comma)?; self.eat_whitespace(); } } // TODO(#140): shouldn't buffer up this entire array in memory, it'd be // great to defer parsing everything until later. fn array(&mut self) -> Result<(Span, Vec>), Error> { let mut ret = Vec::new(); let intermediate = |me: &mut Deserializer<'_>| -> Result<(), Error> { loop { me.eat_whitespace(); if !me.eat(Token::Newline)? && !me.eat_comment()? { break; } } Ok(()) }; loop { intermediate(self)?; if let Some(span) = self.eat_spanned(Token::RightBracket)? { return Ok((span, ret)); } let value = self.value()?; ret.push(value); intermediate(self)?; if !self.eat(Token::Comma)? { break; } } intermediate(self)?; let span = self.expect_spanned(Token::RightBracket)?; Ok((span, ret)) } fn table_key(&mut self) -> Result, Error> { self.tokens.table_key().map_err(|e| self.token_error(e)) } fn dotted_key(&mut self) -> Result>, Error> { let mut result = Vec::new(); result.push(self.table_key()?); self.eat_whitespace(); while self.eat(Token::Period)? { self.eat_whitespace(); result.push(self.table_key()?); self.eat_whitespace(); } Ok(result) } /// Stores a value in the appropriate hierarchical structure positioned based on the dotted key. /// /// Given the following definition: `multi.part.key = "value"`, `multi` and `part` are /// intermediate parts which are mapped to the relevant fields in the deserialized type's data /// hierarchy. /// /// # Parameters /// /// * `key_parts`: Each segment of the dotted key, e.g. `part.one` maps to /// `vec![Cow::Borrowed("part"), Cow::Borrowed("one")].` /// * `value`: The parsed value. /// * `values`: The `Vec` to store the value in. fn add_dotted_key( &self, mut key_parts: Vec>, value: Val<'a>, values: &mut Vec>, ) -> Result<(), Error> { let key = key_parts.remove(0); if key_parts.is_empty() { values.push((key, value)); return Ok(()); } match values .iter_mut() .find(|&&mut (ref k, _)| k.name == key.name) { Some(&mut ( _, Val { e: E::DottedTable(ref mut v), .. }, )) => { return self.add_dotted_key(key_parts, value, v); } Some(&mut (ref first, _)) => { return Err(self.error( key.span.start, Some(value.end), ErrorKind::DottedKeyInvalidType { first: first.span }, )); } None => {} } // The start/end value is somewhat misleading here. let table_values = Val { e: E::DottedTable(Vec::new()), start: value.start, end: value.end, }; values.push((key, table_values)); let last_i = values.len() - 1; if let ( _, Val { e: E::DottedTable(ref mut v), .. }, ) = values[last_i] { self.add_dotted_key(key_parts, value, v)?; } Ok(()) } fn eat_whitespace(&mut self) { self.tokens.eat_whitespace(); } fn eat_comment(&mut self) -> Result { self.tokens.eat_comment().map_err(|e| self.token_error(e)) } fn eat_newline_or_eof(&mut self) -> Result<(), Error> { self.tokens .eat_newline_or_eof() .map_err(|e| self.token_error(e)) } fn eat(&mut self, expected: Token<'a>) -> Result { self.tokens.eat(expected).map_err(|e| self.token_error(e)) } fn eat_spanned(&mut self, expected: Token<'a>) -> Result, Error> { self.tokens .eat_spanned(expected) .map_err(|e| self.token_error(e)) } fn expect(&mut self, expected: Token<'a>) -> Result<(), Error> { self.tokens .expect(expected) .map_err(|e| self.token_error(e)) } fn expect_spanned(&mut self, expected: Token<'a>) -> Result { self.tokens .expect_spanned(expected) .map_err(|e| self.token_error(e)) } fn next(&mut self) -> Result)>, Error> { self.tokens.step().map_err(|e| self.token_error(e)) } fn peek(&mut self) -> Result)>, Error> { self.tokens.peek().map_err(|e| self.token_error(e)) } fn eof(&self) -> Error { self.error(self.input.len(), None, ErrorKind::UnexpectedEof) } fn token_error(&self, error: TokenError) -> Error { match error { TokenError::InvalidCharInString(at, ch) => { self.error(at, None, ErrorKind::InvalidCharInString(ch)) } TokenError::InvalidEscape(at, ch) => self.error(at, None, ErrorKind::InvalidEscape(ch)), TokenError::InvalidEscapeValue(at, len, v) => { self.error(at, Some(at + len), ErrorKind::InvalidEscapeValue(v)) } TokenError::InvalidHexEscape(at, ch) => { self.error(at, None, ErrorKind::InvalidHexEscape(ch)) } TokenError::NewlineInString(at) => { self.error(at, None, ErrorKind::InvalidCharInString('\n')) } TokenError::Unexpected(at, ch) => self.error(at, None, ErrorKind::Unexpected(ch)), TokenError::UnterminatedString(at) => { self.error(at, None, ErrorKind::UnterminatedString) } TokenError::Wanted { at, expected, found, } => self.error( at, Some(at + found.len()), ErrorKind::Wanted { expected, found }, ), TokenError::MultilineStringKey(at, end) => { self.error(at, Some(end), ErrorKind::MultilineStringKey) } } } fn error(&self, start: usize, end: Option, kind: ErrorKind) -> Error { let span = Span::new(start, end.unwrap_or(start + 1)); let line_info = Some(self.to_linecol(start)); Error { span, kind, line_info, } } /// Converts a byte offset from an error message to a (line, column) pair /// /// All indexes are 0-based. fn to_linecol(&self, offset: usize) -> (usize, usize) { let mut cur = 0; // Use split_terminator instead of lines so that if there is a `\r`, it // is included in the offset calculation. The `+1` values below account // for the `\n`. for (i, line) in self.input.split_terminator('\n').enumerate() { if cur + line.len() + 1 > offset { return (i, offset - cur); } cur += line.len() + 1; } (self.input.lines().count(), 0) } } // impl Error { // pub(crate) fn line_col(&self) -> Option<(usize, usize)> { // self.line.map(|line| (line, self.col)) // } // fn from_kind(at: Option, kind: ErrorKind) -> Self { // Error { // kind, // line: None, // col: 0, // at, // message: String::new(), // key: Vec::new(), // } // } // fn custom(at: Option, s: String) -> Self { // Error { // kind: ErrorKind::Custom, // line: None, // col: 0, // at, // message: s, // key: Vec::new(), // } // } // pub(crate) fn add_key_context(&mut self, key: &str) { // self.key.insert(0, key.to_string()); // } // fn fix_offset(&mut self, f: F) // where // F: FnOnce() -> Option, // { // // An existing offset is always better positioned than anything we might // // want to add later. // if self.at.is_none() { // self.at = f(); // } // } // fn fix_linecol(&mut self, f: F) // where // F: FnOnce(usize) -> (usize, usize), // { // if let Some(at) = self.at { // let (line, col) = f(at); // self.line = Some(line); // self.col = col; // } // } // } impl std::convert::From for std::io::Error { fn from(e: Error) -> Self { std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()) } } enum Line<'a> { Table { at: usize, end: usize, header: Header<'a>, array: bool, }, KeyValue(Vec>, Val<'a>), } struct Header<'a> { first: bool, array: bool, tokens: Tokenizer<'a>, } impl<'a> Header<'a> { fn new(tokens: Tokenizer<'a>, array: bool) -> Header<'a> { Header { first: true, array, tokens, } } fn next(&mut self) -> Result>, TokenError> { self.tokens.eat_whitespace(); if self.first || self.tokens.eat(Token::Period)? { self.first = false; self.tokens.eat_whitespace(); self.tokens.table_key().map(Some) } else { self.tokens.expect(Token::RightBracket)?; if self.array { self.tokens.expect(Token::RightBracket)?; } self.tokens.eat_whitespace(); if !self.tokens.eat_comment()? { self.tokens.eat_newline_or_eof()?; } Ok(None) } } } #[derive(Debug)] struct Val<'a> { e: E<'a>, start: usize, end: usize, } #[derive(Debug)] enum E<'a> { Integer(i64), Float(f64), Boolean(bool), String(DeStr<'a>), Array(Vec>), InlineTable(Vec>), DottedTable(Vec>), } impl<'a> E<'a> { #[allow(dead_code)] fn type_name(&self) -> &'static str { match *self { E::String(..) => "string", E::Integer(..) => "integer", E::Float(..) => "float", E::Boolean(..) => "boolean", E::Array(..) => "array", E::InlineTable(..) => "inline table", E::DottedTable(..) => "dotted table", } } } toml-span-0.3.0/src/de_helpers.rs000064400000000000000000000227411046102023000150130ustar 00000000000000//! Provides helpers for deserializing [`Value`]/[`ValueInner`] into Rust types use crate::{ span::Spanned, value::{self, Table, Value, ValueInner}, DeserError, Deserialize, Error, ErrorKind, Span, }; use std::{fmt::Display, str::FromStr}; /// Helper for construction an [`ErrorKind::Wanted`] #[inline] pub fn expected(expected: &'static str, found: ValueInner<'_>, span: Span) -> Error { Error { kind: ErrorKind::Wanted { expected, found: found.type_str(), }, span, line_info: None, } } /// Attempts to acquire a [`ValueInner::String`] and parse it, returning an error /// if the value is not a string, or the parse implementation fails #[inline] pub fn parse(value: &mut Value<'_>) -> Result where T: FromStr, E: Display, { let s = value.take_string(None)?; match s.parse() { Ok(v) => Ok(v), Err(err) => Err(Error { kind: ErrorKind::Custom(format!("failed to parse string: {err}").into()), span: value.span, line_info: None, }), } } /// A helper for dealing with [`ValueInner::Table`] pub struct TableHelper<'de> { /// The table the helper is operating upon pub table: Table<'de>, /// The errors accumulated while deserializing pub errors: Vec, /// The list of keys that have been requested by the user, this is used to /// show a list of keys that _could_ be used in the case the finalize method /// fails due to keys still being present in the map expected: Vec<&'static str>, /// The span for the table location span: Span, } impl<'de> From<(Table<'de>, Span)> for TableHelper<'de> { fn from((table, span): (Table<'de>, Span)) -> Self { Self { table, span, expected: Vec::new(), errors: Vec::new(), } } } impl<'de> TableHelper<'de> { /// Creates a helper for the value, failing if it is not a table pub fn new(value: &mut Value<'de>) -> Result { let table = match value.take() { ValueInner::Table(table) => table, other => return Err(expected("a table", other, value.span).into()), }; Ok(Self { errors: Vec::new(), table, expected: Vec::new(), span: value.span, }) } /// Returns true if the table contains the specified key #[inline] pub fn contains(&self, name: &str) -> bool { self.table.contains_key(name) } /// Takes the specified key and its value if it exists #[inline] pub fn take(&mut self, name: &'static str) -> Option<(value::Key<'de>, Value<'de>)> { self.expected.push(name); self.table.remove_entry(name) } /// Attempts to deserialize the specified key /// /// Errors that occur when calling this method are automatically added to /// the set of errors that are reported from [`Self::finalize`], so not early /// returning if this method fails will still report the error by default /// /// # Errors /// - The key does not exist /// - The [`Deserialize`] implementation for the type returns an error #[inline] pub fn required>(&mut self, name: &'static str) -> Result { Ok(self.required_s(name)?.value) } /// The same as [`Self::required`], except it returns a [`Spanned`] pub fn required_s>( &mut self, name: &'static str, ) -> Result, Error> { self.expected.push(name); let Some(mut val) = self.table.remove(name) else { let missing = Error { kind: ErrorKind::MissingField(name), span: self.span, line_info: None, }; self.errors.push(missing.clone()); return Err(missing); }; Spanned::::deserialize(&mut val).map_err(|mut errs| { let err = errs.errors.last().unwrap().clone(); self.errors.append(&mut errs.errors); err }) } /// Attempts to deserialize the specified key, if it exists /// /// Note that if the key exists but deserialization fails, an error will be /// appended and if [`Self::finalize`] is called it will return that error /// along with any others that occurred pub fn optional>(&mut self, name: &'static str) -> Option { self.optional_s(name).map(|v| v.value) } /// The same as [`Self::optional`], except it returns a [`Spanned`] pub fn optional_s>(&mut self, name: &'static str) -> Option> { self.expected.push(name); let mut val = self.table.remove(name)?; match Spanned::::deserialize(&mut val) { Ok(v) => Some(v), Err(mut err) => { self.errors.append(&mut err.errors); None } } } /// Called when you are finished with this [`TableHelper`] /// /// If errors have been accumulated when using this [`TableHelper`], this will /// return an error with all of those errors. /// /// Additionally, if [`Option::None`] is passed, any keys that still exist /// in the table will be added to an [`ErrorKind::UnexpectedKeys`] error, /// which can be considered equivalent to [`#[serde(deny_unknown_fields)]`](https://serde.rs/container-attrs.html#deny_unknown_fields) /// /// If you want simulate [`#[serde(flatten)]`](https://serde.rs/field-attrs.html#flatten) /// you can instead put that table back in its original value during this step pub fn finalize(mut self, original: Option<&mut Value<'de>>) -> Result<(), DeserError> { if let Some(original) = original { original.set(ValueInner::Table(self.table)); } else if !self.table.is_empty() { let keys = self .table .into_keys() .map(|key| (key.name.into(), key.span)) .collect(); self.errors.push( ( ErrorKind::UnexpectedKeys { keys, expected: self.expected.into_iter().map(String::from).collect(), }, self.span, ) .into(), ); } if self.errors.is_empty() { Ok(()) } else { Err(DeserError { errors: self.errors, }) } } } impl<'de> Deserialize<'de> for String { fn deserialize(value: &mut Value<'de>) -> Result { value .take_string(None) .map(|s| s.into()) .map_err(DeserError::from) } } impl<'de> Deserialize<'de> for std::borrow::Cow<'de, str> { fn deserialize(value: &mut Value<'de>) -> Result { value.take_string(None).map_err(DeserError::from) } } impl<'de> Deserialize<'de> for bool { fn deserialize(value: &mut Value<'de>) -> Result { match value.take() { ValueInner::Boolean(b) => Ok(b), other => Err(expected("a bool", other, value.span).into()), } } } macro_rules! integer { ($num:ty) => { impl<'de> Deserialize<'de> for $num { fn deserialize(value: &mut Value<'de>) -> Result { match value.take() { ValueInner::Integer(i) => { let i = i.try_into().map_err(|_| { DeserError::from(Error { kind: ErrorKind::OutOfRange(stringify!($num)), span: value.span, line_info: None, }) })?; Ok(i) } other => Err(expected(stringify!($num), other, value.span).into()), } } } }; } integer!(u8); integer!(u16); integer!(u32); integer!(u64); integer!(i8); integer!(i16); integer!(i32); integer!(i64); integer!(usize); integer!(isize); impl<'de> Deserialize<'de> for f32 { fn deserialize(value: &mut Value<'de>) -> Result { match value.take() { ValueInner::Float(f) => Ok(f as f32), other => Err(expected("a float", other, value.span).into()), } } } impl<'de> Deserialize<'de> for f64 { fn deserialize(value: &mut Value<'de>) -> Result { match value.take() { ValueInner::Float(f) => Ok(f), other => Err(expected("a float", other, value.span).into()), } } } impl<'de, T> Deserialize<'de> for Vec where T: Deserialize<'de>, { fn deserialize(value: &mut value::Value<'de>) -> Result { match value.take() { ValueInner::Array(arr) => { let mut errors = Vec::new(); let mut s = Vec::new(); for mut v in arr { match T::deserialize(&mut v) { Ok(v) => s.push(v), Err(mut err) => errors.append(&mut err.errors), } } if errors.is_empty() { Ok(s) } else { Err(DeserError { errors }) } } other => Err(expected("an array", other, value.span).into()), } } } toml-span-0.3.0/src/error.rs000064400000000000000000000352101046102023000140250ustar 00000000000000use crate::Span; use std::fmt::{self, Debug, Display}; /// Error that can occur when deserializing TOML. #[derive(Debug, Clone)] pub struct Error { /// The error kind pub kind: ErrorKind, /// The span where the error occurs. /// /// Note some [`ErrorKind`] contain additional span information pub span: Span, /// Line and column information, only available for errors coming from the parser pub line_info: Option<(usize, usize)>, } impl std::error::Error for Error {} impl From<(ErrorKind, Span)> for Error { fn from((kind, span): (ErrorKind, Span)) -> Self { Self { kind, span, line_info: None, } } } /// Errors that can occur when deserializing a type. #[derive(Debug, Clone)] pub enum ErrorKind { /// EOF was reached when looking for a value. UnexpectedEof, /// An invalid character not allowed in a string was found. InvalidCharInString(char), /// An invalid character was found as an escape. InvalidEscape(char), /// An invalid character was found in a hex escape. InvalidHexEscape(char), /// An invalid escape value was specified in a hex escape in a string. /// /// Valid values are in the plane of unicode codepoints. InvalidEscapeValue(u32), /// An unexpected character was encountered, typically when looking for a /// value. Unexpected(char), /// An unterminated string was found where EOF was found before the ending /// EOF mark. UnterminatedString, /// A number failed to parse. InvalidNumber, /// The number in the toml file cannot be losslessly converted to the specified /// number type OutOfRange(&'static str), /// Wanted one sort of token, but found another. Wanted { /// Expected token type. expected: &'static str, /// Actually found token type. found: &'static str, }, /// A duplicate table definition was found. DuplicateTable { /// The name of the duplicate table name: String, /// The span where the table was first defined first: Span, }, /// Duplicate key in table. DuplicateKey { /// The duplicate key key: String, /// The span where the first key is located first: Span, }, /// A previously defined table was redefined as an array. RedefineAsArray, /// Multiline strings are not allowed for key. MultilineStringKey, /// A custom error which could be generated when deserializing a particular /// type. Custom(std::borrow::Cow<'static, str>), /// Dotted key attempted to extend something that is not a table. DottedKeyInvalidType { /// The span where the non-table value was defined first: Span, }, /// An unexpected key was encountered. /// /// Used when deserializing a struct with a limited set of fields. UnexpectedKeys { /// The unexpected keys. keys: Vec<(String, Span)>, /// The list of keys that were expected for the table expected: Vec, }, /// Unquoted string was found when quoted one was expected. UnquotedString, /// A required field is missing from a table MissingField(&'static str), /// A field in the table is deprecated and the new key should be used instead Deprecated { /// The deprecated key name old: &'static str, /// The key name that should be used instead new: &'static str, }, /// An unexpected value was encountered UnexpectedValue { /// The list of values that could have been used, eg. typically enum variants expected: &'static [&'static str], }, } impl Display for ErrorKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::UnexpectedEof => f.write_str("unexpected-eof"), Self::Custom(..) => f.write_str("custom"), Self::DottedKeyInvalidType { .. } => f.write_str("dotted-key-invalid-type"), Self::DuplicateKey { .. } => f.write_str("duplicate-key"), Self::DuplicateTable { .. } => f.write_str("duplicate-table"), Self::UnexpectedKeys { .. } => f.write_str("unexpected-keys"), Self::UnquotedString => f.write_str("unquoted-string"), Self::MultilineStringKey => f.write_str("multiline-string-key"), Self::RedefineAsArray => f.write_str("redefine-as-array"), Self::InvalidCharInString(..) => f.write_str("invalid-char-in-string"), Self::InvalidEscape(..) => f.write_str("invalid-escape"), Self::InvalidEscapeValue(..) => f.write_str("invalid-escape-value"), Self::InvalidHexEscape(..) => f.write_str("invalid-hex-escape"), Self::Unexpected(..) => f.write_str("unexpected"), Self::UnterminatedString => f.write_str("unterminated-string"), Self::InvalidNumber => f.write_str("invalid-number"), Self::OutOfRange(_) => f.write_str("out-of-range"), Self::Wanted { .. } => f.write_str("wanted"), Self::MissingField(..) => f.write_str("missing-field"), Self::Deprecated { .. } => f.write_str("deprecated"), Self::UnexpectedValue { .. } => f.write_str("unexpected-value"), } } } struct Escape(char); impl fmt::Display for Escape { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use std::fmt::Write as _; if self.0.is_whitespace() { for esc in self.0.escape_default() { f.write_char(esc)?; } Ok(()) } else { f.write_char(self.0) } } } impl Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.kind { ErrorKind::UnexpectedEof => f.write_str("unexpected eof encountered")?, ErrorKind::InvalidCharInString(c) => { write!(f, "invalid character in string: `{}`", Escape(*c))?; } ErrorKind::InvalidEscape(c) => { write!(f, "invalid escape character in string: `{}`", Escape(*c))?; } ErrorKind::InvalidHexEscape(c) => write!( f, "invalid hex escape character in string: `{}`", Escape(*c) )?, ErrorKind::InvalidEscapeValue(c) => write!(f, "invalid escape value: `{c}`")?, ErrorKind::Unexpected(c) => write!(f, "unexpected character found: `{}`", Escape(*c))?, ErrorKind::UnterminatedString => f.write_str("unterminated string")?, ErrorKind::Wanted { expected, found } => { write!(f, "expected {expected}, found {found}")?; } ErrorKind::InvalidNumber => f.write_str("invalid number")?, ErrorKind::OutOfRange(kind) => write!(f, "out of range of '{kind}'")?, ErrorKind::DuplicateTable { name, .. } => { write!(f, "redefinition of table `{name}`")?; } ErrorKind::DuplicateKey { key, .. } => { write!(f, "duplicate key: `{key}`")?; } ErrorKind::RedefineAsArray => f.write_str("table redefined as array")?, ErrorKind::MultilineStringKey => { f.write_str("multiline strings are not allowed for key")?; } ErrorKind::Custom(message) => f.write_str(message)?, ErrorKind::DottedKeyInvalidType { .. } => { f.write_str("dotted key attempted to extend non-table type")?; } ErrorKind::UnexpectedKeys { keys, expected } => write!( f, "unexpected keys in table: `{keys:?}`\nexpected: {expected:?}" )?, ErrorKind::UnquotedString => { f.write_str("invalid TOML value, did you mean to use a quoted string?")?; } ErrorKind::MissingField(field) => write!(f, "missing field '{field}' in table")?, ErrorKind::Deprecated { old, new } => { write!(f, "field '{old}' is deprecated, '{new}' has replaced it")?; } ErrorKind::UnexpectedValue { expected } => write!(f, "expected '{expected:?}'")?, } // if !self.key.is_empty() { // write!(f, " for key `")?; // for (i, k) in self.key.iter().enumerate() { // if i > 0 { // write!(f, ".")?; // } // write!(f, "{}", k)?; // } // write!(f, "`")?; // } // if let Some(line) = self.line { // write!(f, " at line {} column {}", line + 1, self.col + 1)?; // } Ok(()) } } #[cfg(feature = "reporting")] #[cfg_attr(docsrs, doc(cfg(feature = "reporting")))] impl Error { /// Converts this [`Error`] into a [`codespan_reporting::diagnostic::Diagnostic`] pub fn to_diagnostic( &self, fid: FileId, ) -> codespan_reporting::diagnostic::Diagnostic { let diag = codespan_reporting::diagnostic::Diagnostic::error().with_code(self.kind.to_string()); use codespan_reporting::diagnostic::Label; let diag = match &self.kind { ErrorKind::DuplicateKey { first, .. } => diag.with_labels(vec![ Label::secondary(fid, *first).with_message("first key instance"), Label::primary(fid, self.span).with_message("duplicate key"), ]), ErrorKind::Unexpected(c) => diag.with_labels(vec![Label::primary(fid, self.span) .with_message(format!("unexpected character '{}'", Escape(*c)))]), ErrorKind::InvalidCharInString(c) => { diag.with_labels(vec![Label::primary(fid, self.span) .with_message(format!("invalid character '{}' in string", Escape(*c)))]) } ErrorKind::InvalidEscape(c) => diag.with_labels(vec![Label::primary(fid, self.span) .with_message(format!( "invalid escape character '{}' in string", Escape(*c) ))]), ErrorKind::InvalidEscapeValue(_) => diag .with_labels(vec![ Label::primary(fid, self.span).with_message("invalid escape value") ]), ErrorKind::InvalidNumber => diag.with_labels(vec![ Label::primary(fid, self.span).with_message("unable to parse number") ]), ErrorKind::OutOfRange(kind) => diag .with_message(format!("number is out of range of '{kind}'")) .with_labels(vec![Label::primary(fid, self.span)]), ErrorKind::Wanted { expected, .. } => diag .with_labels(vec![ Label::primary(fid, self.span).with_message(format!("expected {expected}")) ]), ErrorKind::MultilineStringKey => diag.with_labels(vec![ Label::primary(fid, self.span).with_message("multiline keys are not allowed") ]), ErrorKind::UnterminatedString => diag .with_labels(vec![Label::primary(fid, self.span) .with_message("eof reached before string terminator")]), ErrorKind::DuplicateTable { first, .. } => diag.with_labels(vec![ Label::secondary(fid, *first).with_message("first table instance"), Label::primary(fid, self.span).with_message("duplicate table"), ]), ErrorKind::InvalidHexEscape(c) => diag .with_labels(vec![Label::primary(fid, self.span) .with_message(format!("invalid hex escape '{}'", Escape(*c)))]), ErrorKind::UnquotedString => diag.with_labels(vec![ Label::primary(fid, self.span).with_message("string is not quoted") ]), ErrorKind::UnexpectedKeys { keys, expected } => diag .with_message(format!( "found {} unexpected keys, expected: {expected:?}", keys.len() )) .with_labels( keys.iter() .map(|(_name, span)| Label::secondary(fid, *span)) .collect(), ), ErrorKind::MissingField(field) => diag .with_message(format!("missing field '{field}'")) .with_labels(vec![ Label::primary(fid, self.span).with_message("table with missing field") ]), ErrorKind::Deprecated { new, .. } => diag .with_message(format!( "deprecated field enountered, '{new}' should be used instead" )) .with_labels(vec![ Label::primary(fid, self.span).with_message("deprecated field") ]), ErrorKind::UnexpectedValue { expected } => diag .with_message(format!("expected '{expected:?}'")) .with_labels(vec![ Label::primary(fid, self.span).with_message("unexpected value") ]), ErrorKind::UnexpectedEof => diag .with_message("unexpected end of file") .with_labels(vec![Label::primary(fid, self.span)]), ErrorKind::DottedKeyInvalidType { first } => { diag.with_message(self.to_string()).with_labels(vec![ Label::primary(fid, self.span).with_message("attempted to extend table here"), Label::secondary(fid, *first).with_message("non-table"), ]) } ErrorKind::RedefineAsArray => diag .with_message(self.to_string()) .with_labels(vec![Label::primary(fid, self.span)]), ErrorKind::Custom(msg) => diag .with_message(msg.to_string()) .with_labels(vec![Label::primary(fid, self.span)]), }; diag } } /// When deserializing, it's possible to collect multiple errors instead of earlying /// out at the first error #[derive(Debug)] pub struct DeserError { /// The set of errors that occurred during deserialization pub errors: Vec, } impl DeserError { /// Merges errors from another [`Self`] #[inline] pub fn merge(&mut self, mut other: Self) { self.errors.append(&mut other.errors); } } impl std::error::Error for DeserError {} impl From for DeserError { fn from(value: Error) -> Self { Self { errors: vec![value], } } } impl fmt::Display for DeserError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for err in &self.errors { writeln!(f, "{err}")?; } Ok(()) } } toml-span-0.3.0/src/impl_serde.rs000064400000000000000000000026051046102023000150210ustar 00000000000000#![cfg_attr(docsrs, doc(cfg(feature = "serde")))] //! Provides [`serde::Serialize`] support for [`Value`] and [`Spanned`] use crate::{ value::{Value, ValueInner}, Spanned, }; use serde::ser::{SerializeMap, SerializeSeq}; impl<'de> serde::Serialize for Value<'de> { fn serialize(&self, ser: S) -> Result where S: serde::Serializer, { match self.as_ref() { ValueInner::String(s) => ser.serialize_str(s), ValueInner::Integer(i) => ser.serialize_i64(*i), ValueInner::Float(f) => ser.serialize_f64(*f), ValueInner::Boolean(b) => ser.serialize_bool(*b), ValueInner::Array(arr) => { let mut seq = ser.serialize_seq(Some(arr.len()))?; for ele in arr { seq.serialize_element(ele)?; } seq.end() } ValueInner::Table(tab) => { let mut map = ser.serialize_map(Some(tab.len()))?; for (k, v) in tab { map.serialize_entry(&k.name, v)?; } map.end() } } } } impl serde::Serialize for Spanned where T: serde::Serialize, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { self.value.serialize(serializer) } } toml-span-0.3.0/src/lib.rs000064400000000000000000000017331046102023000134450ustar 00000000000000#![cfg_attr(docsrs, feature(doc_cfg))] #![doc = include_str!("../README.md")] pub mod de; pub mod de_helpers; mod error; pub mod span; pub mod tokens; pub mod value; pub use de::parse; pub use error::{DeserError, Error, ErrorKind}; pub use span::{Span, Spanned}; pub use value::Value; #[cfg(feature = "serde")] pub mod impl_serde; /// This crate's equivalent to [`serde::Deserialize`](https://docs.rs/serde/latest/serde/de/trait.Deserialize.html) pub trait Deserialize<'de>: Sized { /// Given a mutable [`Value`], allows you to deserialize the type from it, /// or accumulate 1 or more errors fn deserialize(value: &mut Value<'de>) -> Result; } /// This crate's equivalent to [`serde::DeserializeOwned`](https://docs.rs/serde/latest/serde/de/trait.DeserializeOwned.html) /// /// This is useful if you want to use trait bounds pub trait DeserializeOwned: for<'de> Deserialize<'de> {} impl DeserializeOwned for T where T: for<'de> Deserialize<'de> {} toml-span-0.3.0/src/span.rs000064400000000000000000000070551046102023000136430ustar 00000000000000//! Provides span helpers /// A start and end location within a toml document #[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] pub struct Span { /// The start byte index pub start: usize, /// The end (exclusive) byte index pub end: usize, } impl Span { /// Creates a new [`Span`] #[inline] pub fn new(start: usize, end: usize) -> Self { Self { start, end } } /// Checks if the start and end are the same, and thus the span is empty #[inline] pub fn is_empty(&self) -> bool { self.start == 0 && self.end == 0 } } impl From for (usize, usize) { fn from(Span { start, end }: Span) -> (usize, usize) { (start, end) } } impl From> for Span { fn from(s: std::ops::Range) -> Self { Self { start: s.start, end: s.end, } } } impl From for std::ops::Range { fn from(s: Span) -> Self { Self { start: s.start, end: s.end, } } } /// An arbitrary `T` with additional span information pub struct Spanned { /// The value pub value: T, /// The span information for the value pub span: Span, } impl Spanned { /// Creates a [`Spanned`] with just the value and an empty [`Span`] #[inline] pub const fn new(value: T) -> Self { Self { value, span: Span { start: 0, end: 0 }, } } /// Creates a [`Spanned`] from both a value and a [`Span`] #[inline] pub const fn with_span(value: T, span: Span) -> Self { Self { value, span } } /// Converts [`Self`] into its inner value #[inline] pub fn take(self) -> T { self.value } /// Helper to convert the value inside the Spanned #[inline] pub fn map(self) -> Spanned where V: From, { Spanned { value: self.value.into(), span: self.span, } } } impl Default for Spanned where T: Default, { fn default() -> Self { Self { value: Default::default(), span: Span::default(), } } } impl AsRef for Spanned { fn as_ref(&self) -> &T { &self.value } } impl std::fmt::Debug for Spanned where T: std::fmt::Debug, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self.value) } } impl Clone for Spanned where T: Clone, { fn clone(&self) -> Self { Self { value: self.value.clone(), span: self.span, } } } impl PartialOrd for Spanned where T: PartialOrd, { fn partial_cmp(&self, o: &Spanned) -> Option { self.value.partial_cmp(&o.value) } } impl Ord for Spanned where T: Ord, { fn cmp(&self, o: &Spanned) -> std::cmp::Ordering { self.value.cmp(&o.value) } } impl PartialEq for Spanned where T: PartialEq, { fn eq(&self, o: &Spanned) -> bool { self.value == o.value } } impl Eq for Spanned where T: Eq {} impl PartialEq for Spanned where T: PartialEq, { fn eq(&self, o: &T) -> bool { &self.value == o } } impl<'de, T> crate::Deserialize<'de> for Spanned where T: crate::Deserialize<'de>, { #[inline] fn deserialize(value: &mut crate::value::Value<'de>) -> Result { let span = value.span; let value = T::deserialize(value)?; Ok(Self { span, value }) } } toml-span-0.3.0/src/tokens.rs000064400000000000000000000434761046102023000142140ustar 00000000000000#![allow(missing_docs)] //! The tokenizer is publicly exposed if you wish to use it instead use crate::{value::Key, Span}; use std::{borrow::Cow, char, str}; #[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, usize, u32), NewlineInString(usize), Unexpected(usize, char), UnterminatedString(usize), MultilineStringKey(usize, 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 step(&mut self) -> Result)>, 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(|(s, t)| Some((s, t))), Some((start, '"')) => return self.basic_string(start).map(|(s, t)| Some((s, 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)>, Error> { self.clone().step() } pub fn eat(&mut self, expected: Token<'a>) -> Result { 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, Error> { let span = match self.peek()? { Some((span, ref found)) if expected == *found => span, Some(_) | None => return Ok(None), }; drop(self.step()); 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 { let current = self.current(); match self.step()? { 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, Error> { let current = self.current(); match self.step()? { Some((span, Token::Keylike(k))) => Ok(Key { span, name: k.into(), }), Some(( span, Token::String { src, val, multiline, .. }, )) => { let offset = self.substr_offset(src); if multiline { return Err(Error::MultilineStringKey(offset, offset + val.len())); } match src.find('\n') { None => Ok(Key { span, name: val }), // This is not reachable Some(i) => Err(Error::InvalidCharInString(i, '\n')), } } 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 { 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.step()? { 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' && !('\u{20}'..='\u{10ffff}').contains(&ch) { break; } self.one(); } Token::Comment(&self.input[start..self.current()]) } /// String spans are treated slightly differently, as we only want the /// characters in the string, not the quotes, as once the user gets the /// string and its span they won't know the actual begin/end which can /// be needed for doing substring indices (eg reporting error messages /// when parsing a string) #[allow(clippy::type_complexity)] fn read_string( &mut self, delim: char, start: usize, new_ch: &mut dyn FnMut( &mut Tokenizer<'_>, &mut MaybeString, bool, usize, char, ) -> Result<(), Error>, ) -> Result<(Span, Token<'a>), Error> { let mut multiline = false; if self.eatc(delim) { if self.eatc(delim) { multiline = true; } else { return Ok(( // Point the caret at the beginning of the quote, that looks // better than the end quote (start..start + 1).into(), 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 => { let span = 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; } // Also skip the first newline after the opening delimiters let maybe_nl = self.input.as_bytes()[start + 3]; let start_off = if maybe_nl == b'\n' { 4 } else if maybe_nl == b'\r' { 5 } else { 3 }; start + start_off..self.current() - 3 } else { start + 1..self.current() - 1 } .into(); return Ok(( span, 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<(Span, Token<'a>), Error> { self.read_string('\'', start, &mut |_me, val, _multi, i, ch| { if ch == '\u{09}' || (ch != '\u{7f}' && ('\u{20}'..='\u{10ffff}').contains(&ch)) { val.push(ch); Ok(()) } else { Err(Error::InvalidCharInString(i, ch)) } }) } fn basic_string(&mut self, start: usize) -> Result<(Span, 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 c = if c == 'u' { me.hex::<4>(start, i) } else { me.hex::<8>(start, i) }; val.push(c?); } Some((i, c @ (' ' | '\t' | '\n'))) if multi => { if c != '\n' { while let Some((_, ch)) = me.chars.clone().next() { match ch { ' ' | '\t' => { me.chars.next(); continue; } '\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}' || (ch != '\u{7f}' && ('\u{20}'..='\u{10ffff}').contains(&ch)) => { val.push(ch); Ok(()) } _ => Err(Error::InvalidCharInString(i, ch)), }) } fn hex(&mut self, start: usize, i: usize) -> Result { let mut buf = [0; N]; for b in buf.iter_mut() { match self.one() { Some((_, ch)) if ch as u32 <= 0x7F && ch.is_ascii_hexdigit() => *b = ch as u8, Some((i, ch)) => return Err(Error::InvalidHexEscape(i, ch)), None => return Err(Error::UnterminatedString(start)), } } let val = u32::from_str_radix(std::str::from_utf8(&buf).unwrap(), 16).unwrap(); match char::from_u32(val) { Some(ch) => Ok(ch), None => Err(Error::InvalidEscapeValue(i, N, 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), } } } #[inline] fn is_keylike(ch: char) -> bool { ch.is_ascii_alphanumeric() || 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", } } } toml-span-0.3.0/src/value.rs000064400000000000000000000247611046102023000140210ustar 00000000000000//! Contains the [`Value`] and [`ValueInner`] containers into which all toml //! contents can be deserialized into and either used directly or fed into //! [`crate::Deserialize`] or your own constructs to deserialize into your own //! types use crate::{Error, ErrorKind, Span}; use std::{borrow::Cow, fmt}; /// A deserialized [`ValueInner`] with accompanying [`Span`] information for where /// it was located in the toml document pub struct Value<'de> { value: Option>, /// The location of the value in the toml document pub span: Span, } impl<'de> Value<'de> { /// Creates a new [`Value`] with an empty [`Span`] #[inline] pub fn new(value: ValueInner<'de>) -> Self { Self::with_span(value, Span::default()) } /// Creates a new [`Value`] with the specified [`Span`] #[inline] pub fn with_span(value: ValueInner<'de>, span: Span) -> Self { Self { value: Some(value), span, } } /// Takes the inner [`ValueInner`] /// /// This panics if the inner value has already been taken. /// /// Typically paired with [`Self::set`] #[inline] pub fn take(&mut self) -> ValueInner<'de> { self.value.take().expect("the value has already been taken") } /// Sets the inner [`ValueInner`] /// /// This is typically done when the value is taken with [`Self::take`], /// processed, and returned #[inline] pub fn set(&mut self, value: ValueInner<'de>) { self.value = Some(value); } /// Returns true if the value is a table and is non-empty #[inline] pub fn has_keys(&self) -> bool { self.value.as_ref().map_or(false, |val| { if let ValueInner::Table(table) = val { !table.is_empty() } else { false } }) } /// Returns true if the value is a table and has the specified key #[inline] pub fn has_key(&self, key: &str) -> bool { self.value.as_ref().map_or(false, |val| { if let ValueInner::Table(table) = val { table.contains_key(key) } else { false } }) } /// Takes the value as a string, returning an error with either a default /// or user supplied message #[inline] pub fn take_string(&mut self, msg: Option<&'static str>) -> Result, Error> { match self.take() { ValueInner::String(s) => Ok(s), other => Err(Error { kind: ErrorKind::Wanted { expected: msg.unwrap_or("a string"), found: other.type_str(), }, span: self.span, line_info: None, }), } } /// Returns a borrowed string if this is a [`ValueInner::String`] #[inline] pub fn as_str(&self) -> Option<&str> { self.value.as_ref().and_then(|v| v.as_str()) } /// Returns a borrowed table if this is a [`ValueInner::Table`] #[inline] pub fn as_table(&self) -> Option<&Table<'de>> { self.value.as_ref().and_then(|v| v.as_table()) } /// Returns a borrowed array if this is a [`ValueInner::Array`] #[inline] pub fn as_array(&self) -> Option<&Array<'de>> { self.value.as_ref().and_then(|v| v.as_array()) } /// Returns an `i64` if this is a [`ValueInner::Integer`] #[inline] pub fn as_integer(&self) -> Option { self.value.as_ref().and_then(|v| v.as_integer()) } /// Returns an `f64` if this is a [`ValueInner::Float`] #[inline] pub fn as_float(&self) -> Option { self.value.as_ref().and_then(|v| v.as_float()) } /// Returns a `bool` if this is a [`ValueInner::Boolean`] #[inline] pub fn as_bool(&self) -> Option { self.value.as_ref().and_then(|v| v.as_bool()) } /// Uses JSON pointer-like syntax to lookup a specific [`Value`] /// /// The basic format is: /// /// - The path starts with `/` /// - Each segment is separated by a `/` /// - Each segment is either a key name, or an integer array index /// /// ```rust /// let data = "[x]\ny = ['z', 'zz']"; /// let value = toml_span::parse(data).unwrap(); /// assert_eq!(value.pointer("/x/y/1").unwrap().as_str().unwrap(), "zz"); /// assert!(value.pointer("/a/b/c").is_none()); /// ``` /// /// Note that this is JSON pointer**-like** because `/` is not supported in /// key names because I don't see the point. If you want this it is easy to /// implement. pub fn pointer(&self, pointer: &str) -> Option<&Self> { if pointer.is_empty() { return Some(self); } else if !pointer.starts_with('/') { return None; } pointer .split('/') .skip(1) // Don't support / or ~ in key names unless someone actually opens // an issue about it //.map(|x| x.replace("~1", "/").replace("~0", "~")) .try_fold(self, move |target, token| { (match &target.value { Some(ValueInner::Table(tab)) => tab.get(token), Some(ValueInner::Array(list)) => parse_index(token).and_then(|x| list.get(x)), _ => None, }) .filter(|v| v.value.is_some()) }) } /// The `mut` version of [`Self::pointer`] pub fn pointer_mut(&mut self, pointer: &'de str) -> Option<&mut Self> { if pointer.is_empty() { return Some(self); } else if !pointer.starts_with('/') { return None; } pointer .split('/') .skip(1) // Don't support / or ~ in key names unless someone actually opens // an issue about it //.map(|x| x.replace("~1", "/").replace("~0", "~")) .try_fold(self, |target, token| { (match &mut target.value { Some(ValueInner::Table(tab)) => tab.get_mut(token), Some(ValueInner::Array(list)) => { parse_index(token).and_then(|x| list.get_mut(x)) } _ => None, }) .filter(|v| v.value.is_some()) }) } } fn parse_index(s: &str) -> Option { if s.starts_with('+') || (s.starts_with('0') && s.len() != 1) { return None; } s.parse().ok() } impl<'de> AsRef> for Value<'de> { fn as_ref(&self) -> &ValueInner<'de> { self.value .as_ref() .expect("the value has already been taken") } } impl<'de> fmt::Debug for Value<'de> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self.value) } } /// A toml table key #[derive(Clone)] pub struct Key<'de> { /// The key itself, in most cases it will be borrowed, but may be owned /// if escape characters are present in the original source pub name: Cow<'de, str>, /// The span for the key in the original document pub span: Span, } impl<'de> std::borrow::Borrow for Key<'de> { fn borrow(&self) -> &str { self.name.as_ref() } } impl<'de> fmt::Debug for Key<'de> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.name) } } impl<'de> fmt::Display for Key<'de> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.name) } } impl<'de> Ord for Key<'de> { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.name.cmp(&other.name) } } impl<'de> PartialOrd for Key<'de> { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl<'de> PartialEq for Key<'de> { fn eq(&self, other: &Self) -> bool { self.name.eq(&other.name) } } impl<'de> Eq for Key<'de> {} /// A toml table, always represented as a sorted map. /// /// The original key ordering can be obtained by ordering the keys by their span pub type Table<'de> = std::collections::BTreeMap, Value<'de>>; /// A toml array pub type Array<'de> = Vec>; /// The core value types that toml can deserialize to /// /// Note that this library does not support datetime values that are part of the /// toml spec since I have no need of them, but could be added #[derive(Debug)] pub enum ValueInner<'de> { /// A string. /// /// This will be borrowed from the original toml source unless it contains /// escape characters String(Cow<'de, str>), /// An integer Integer(i64), /// A float Float(f64), /// A boolean Boolean(bool), /// An array Array(Array<'de>), /// A table Table(Table<'de>), } impl<'de> ValueInner<'de> { /// Gets the type of the value as a string pub fn type_str(&self) -> &'static str { match self { Self::String(..) => "string", Self::Integer(..) => "integer", Self::Float(..) => "float", Self::Boolean(..) => "boolean", Self::Array(..) => "array", Self::Table(..) => "table", } } /// Returns a borrowed string if this is a [`Self::String`] #[inline] pub fn as_str(&self) -> Option<&str> { if let Self::String(s) = self { Some(s.as_ref()) } else { None } } /// Returns a borrowed table if this is a [`Self::Table`] #[inline] pub fn as_table(&self) -> Option<&Table<'de>> { if let ValueInner::Table(t) = self { Some(t) } else { None } } /// Returns a borrowed array if this is a [`Self::Array`] #[inline] pub fn as_array(&self) -> Option<&Array<'de>> { if let ValueInner::Array(a) = self { Some(a) } else { None } } /// Returns an `i64` if this is a [`Self::Integer`] #[inline] pub fn as_integer(&self) -> Option { if let ValueInner::Integer(i) = self { Some(*i) } else { None } } /// Returns an `f64` if this is a [`Self::Float`] #[inline] pub fn as_float(&self) -> Option { if let ValueInner::Float(f) = self { Some(*f) } else { None } } /// Returns a `bool` if this is a [`Self::Boolean`] #[inline] pub fn as_bool(&self) -> Option { if let ValueInner::Boolean(b) = self { Some(*b) } else { None } } }