yaml-rust2-0.10.1/.cargo/config.toml000064400000000000000000000004121046102023000152320ustar 00000000000000[alias] gen_large_yaml = "run --profile=release-lto --package gen_large_yaml --bin gen_large_yaml --manifest-path tools/gen_large_yaml/Cargo.toml --" bench_compare = "run --package bench_compare --bin bench_compare --manifest-path tools/bench_compare/Cargo.toml --" yaml-rust2-0.10.1/.cargo_vcs_info.json0000644000000001360000000000100131320ustar { "git": { "sha1": "9e748129ee35831c52bedeb03d5c0d7e11b1bb00" }, "path_in_vcs": "" }yaml-rust2-0.10.1/.gitattributes000064400000000000000000000000711046102023000146130ustar 00000000000000tests/*.rs.inc linguist-language=Rust linguist-generated yaml-rust2-0.10.1/.github/workflows/ci.yml000064400000000000000000000020551046102023000164370ustar 00000000000000name: CI on: pull_request: push: branches: - master jobs: check: name: Lints and checks runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - run: rustup toolchain install stable --profile minimal --component rustfmt --component clippy --no-self-update - uses: Swatinem/rust-cache@v2 - name: Run clippy checks run: cargo clippy --all-targets -- -D warnings - name: Run format checks run: cargo fmt --check test: name: Test using Rust ${{ matrix.rust }} on ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest] rust: [stable] runs-on: ${{ matrix.os }} steps: - name: Checkout uses: actions/checkout@v3 - run: git submodule update --init - run: rustup toolchain install ${{ matrix.rust }} --profile minimal --no-self-update - uses: Swatinem/rust-cache@v2 - name: Run build run: cargo build - name: Run tests run: cargo test -v yaml-rust2-0.10.1/.gitignore000064400000000000000000000000551046102023000137120ustar 00000000000000target Cargo.lock *.swp /perf.* /coverage.sh yaml-rust2-0.10.1/.gitmodules000064400000000000000000000001621046102023000140760ustar 00000000000000[submodule "tests/yaml-test-suite"] path = tests/yaml-test-suite url = https://github.com/yaml/yaml-test-suite/ yaml-rust2-0.10.1/.licenses/ChenYuheng-Apache000064400000000000000000000240461046102023000167520ustar 00000000000000Copyright (c) 2015 Chen Yuheng 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. 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 yaml-rust2-0.10.1/.licenses/ChenYuheng-MIT000064400000000000000000000020661046102023000162200ustar 00000000000000The MIT License (MIT) Copyright (c) 2015 Chen Yuheng 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. yaml-rust2-0.10.1/.licenses/Ethiraric-Apache000064400000000000000000000240441046102023000166250ustar 00000000000000Copyright (c) 2023 Ethiraric 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. 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 yaml-rust2-0.10.1/.licenses/Ethiraric-MIT000064400000000000000000000020641046102023000160730ustar 00000000000000The MIT License (MIT) Copyright (c) 2023 Ethiraric 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. yaml-rust2-0.10.1/CHANGELOG.md000064400000000000000000000130631046102023000135360ustar 00000000000000# Changelog ## Upcoming ## v0.10.1 **Bug fixes** - Parse `.NaN` as float instead of `NaN`. ## v0.10.0 **Breaking Changes** - Update dependencies. `hashlink` had a bogus requirement of `>= 0.8, < 0.10`, sorry. As mentioned [here](https://github.com/Ethiraric/yaml-rust2/issues/33#issuecomment-2227455469), range requirements shouldn't be used and I haven't been vigilant enough when reviewing. The requirement is now set to `0.10`. **Changes** - Force quotes on `y` and `n` to appease the YAML 1.1 lords. ## v0.9.0 **Breaking Changes** - Update dependencies. Since `hashlink` is exposed (through `Yaml::Hash`) and has been updated from 0.8.4 to 0.9.1, the new version of `yaml-rust2` will not link properly if you explicitly rely on `hashlink v0.8`. Existing code with v0.8.4 should still compile fine in v0.9.1 (see [hashlink's v0.9.0 changelog](https://github.com/kyren/hashlink/releases/tag/v0.9.0)). **Bug fixes** - ([#37](https://github.com/Ethiraric/yaml-rust2/pull/37)) Parse empty scalars as `""` instead of `"~"`. **Features** - Add `Yaml::is_hash`. - Add better doccomments to the `Index` and `IntoIterator` implementations for `Yaml` to better explain their quirks and design decisions. ## v0.8.1 **Bug fixes** - ([#29](https://github.com/Ethiraric/yaml-rust2/issues/29)) Fix parsing failing for deeply indented scalar blocks. - ([#21-comment](https://github.com/Ethiraric/yaml-rust2/issues/21#issuecomment-2053513507)) Fix parsing failing with comments immediately following a YAML tag. **Features** - ([#19](https://github.com/Ethiraric/yaml-rust2/pull/19)) `Yaml` now implements `IndexMut` and `IndexMut<&'a str>`. These functions may not return a mutable reference to a `BAD_VALUE`. Instead, `index_mut()` will panic if either: * The index is out of range, as per `IndexMut`'s requirements * The inner `Yaml` variant doesn't match `Yaml::Array` for `usize` or `Yaml::Hash` for `&'a str` - Use cargo features This allows for more fine-grained control over MSRV and to completely remove debug code from the library when it is consumed. The `encoding` feature, governing the `YamlDecoder`, has been enabled by default. Users of `@davvid`'s fork of `yaml-rust` or of `yaml-rust2` might already use this. Users of the original `yaml-rust` crate may freely disable this feature (`cargo <...> --no-default-features`) and lower MSRV to 1.65.0. - Duplicate keys no longer allowed Instead of silently choosing one of two values sharing the same key in a mapping, we now issue an error. This behavior is part of the YAML specification, but not tested by the `yaml-test-suite` (the parser needs to emit events for both key-values). Additionally, there is no standard way of defining which value should be chosen in case of a duplicate. ## v0.8.0 **Breaking Changes**: - The `encoding` library has been replaced with `encoding_rs`. If you use the `trap` of `YamlDecoder`, this change will make your code not compile. An additional enum `YamlDecoderTrap` has been added to abstract the underlying library and avoid breaking changes in the future. This additionally lifts the `encoding` dependency on _your_ project if you were using that feature. - The signature of the function for `YamlDecoderTrap::Call` has changed: - The `encoding::types::DecoderTrap` has been replaced with `YamlDecoderTrap`. ```rust // Before, with `encoding::types::DecoderTrap::Call` fn(_: &mut encoding::RawDecoder, _: &[u8], _: &mut encoding::StringWriter) -> bool; // Now, with `YamlDecoderTrap::Call` fn(_: u8, _: u8, _: &[u8], _: &mut String) -> ControlFlow>; ``` Please refer to the `YamlDecoderTrapFn` documentation for more details. **Features**: - Tags can now be retained across documents by calling `keep_tags(true)` on a `Parser` before loading documents. ([#10](https://github.com/Ethiraric/yaml-rust2/issues/10) ([#12](https://github.com/Ethiraric/yaml-rust2/pull/12)) - `YamlLoader` structs now have a `documents()` method that returns the parsed documents associated with a loader. - `Parser::new_from_str(&str)` and `YamlLoader::load_from_parser(&Parser)` were added. **Development**: - Linguist attributes were added for the `tests/*.rs.inc` files to prevent github from classifying them as C++ files. ## v0.7.0 **Features**: - Multi-line strings are now [emitted using block scalars](https://github.com/chyh1990/yaml-rust/pull/136). - Error messages now contain a byte offset to aid debugging. ([#176](https://github.com/chyh1990/yaml-rust/pull/176)) - Yaml now has `or` and `borrowed_or` methods. ([#179](https://github.com/chyh1990/yaml-rust/pull/179)) - `Yaml::load_from_bytes()` is now available. ([#156](https://github.com/chyh1990/yaml-rust/pull/156)) - The parser and scanner now return Err() instead of calling panic. **Development**: - The documentation was updated to include a security note mentioning that yaml-rust is safe because it does not interpret types. ([#195](https://github.com/chyh1990/yaml-rust/pull/195)) - Updated to quickcheck 1.0. ([#188](https://github.com/chyh1990/yaml-rust/pull/188)) - `hashlink` is [now used](https://github.com/chyh1990/yaml-rust/pull/157) instead of `linked_hash_map`. ## v0.6.0 **Development**: - `is_xxx` functions were moved into the private `char_traits` module. - Benchmarking tools were added. - Performance was improved. ## v0.5.0 - The parser now supports tag directives. ([#35](https://github.com/chyh1990/yaml-rust/issues/35) - The `info` field has been exposed via a new `Yaml::info()` API method. ([#190](https://github.com/chyh1990/yaml-rust/pull/190)) yaml-rust2-0.10.1/Cargo.lock0000644000000342320000000000100111110ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "arraydeque" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", "winapi", ] [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "atty", "bitflags", "clap_derive", "clap_lex", "indexmap", "once_cell", "strsim", "termcolor", "textwrap", ] [[package]] name = "clap_derive" version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" dependencies = [ "heck", "proc-macro-error", "proc-macro2", "quote", "syn", ] [[package]] name = "clap_lex" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" dependencies = [ "os_str_bytes", ] [[package]] name = "crossbeam-channel" version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encoding_rs" version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] [[package]] name = "env_logger" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" dependencies = [ "log", "regex", ] [[package]] name = "foldhash" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ "foldhash", ] [[package]] name = "hashlink" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ "hashbrown 0.15.2", ] [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "indexmap" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", ] [[package]] name = "libc" version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libtest-mimic" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc195aab5b803465bf614a5a4765741abce6c8d64e7d8ca57acd2923661fba9f" dependencies = [ "clap", "crossbeam-channel", "rayon", "termcolor", ] [[package]] name = "log" version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "once_cell" version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "os_str_bytes" version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" [[package]] name = "proc-macro-error" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", "syn", "version_check", ] [[package]] name = "proc-macro-error-attr" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2", "quote", "version_check", ] [[package]] name = "proc-macro2" version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "quickcheck" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" dependencies = [ "env_logger", "log", "rand", ] [[package]] name = "quote" version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "rayon" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "termcolor" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] [[package]] name = "textwrap" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" [[package]] name = "unicode-ident" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "yaml-rust2" version = "0.10.1" dependencies = [ "arraydeque", "encoding_rs", "hashlink", "libtest-mimic", "quickcheck", ] yaml-rust2-0.10.1/Cargo.toml0000644000000040710000000000100111320ustar # 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" rust-version = "1.70.0" name = "yaml-rust2" version = "0.10.1" authors = [ "Yuheng Chen ", "Ethiraric ", "David Aguilar ", ] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "A fully YAML 1.2 compliant YAML parser" documentation = "https://docs.rs/yaml-rust2" readme = "README.md" license = "MIT OR Apache-2.0" repository = "https://github.com/Ethiraric/yaml-rust2" [profile.release-lto] lto = true inherits = "release" [lib] name = "yaml_rust2" path = "src/lib.rs" [[bin]] name = "dump_events" path = "tools/dump_events.rs" [[bin]] name = "run_bench" path = "tools/run_bench.rs" [[bin]] name = "time_parse" path = "tools/time_parse.rs" [[example]] name = "dump_yaml" path = "examples/dump_yaml.rs" [[test]] name = "basic" path = "tests/basic.rs" [[test]] name = "emitter" path = "tests/emitter.rs" [[test]] name = "quickcheck" path = "tests/quickcheck.rs" [[test]] name = "scanner" path = "tests/scanner.rs" [[test]] name = "spec_test" path = "tests/spec_test.rs" [[test]] name = "test_round_trip" path = "tests/test_round_trip.rs" [[test]] name = "yaml-test-suite" path = "tests/yaml-test-suite.rs" harness = false [dependencies.arraydeque] version = "0.5.1" [dependencies.encoding_rs] version = "0.8.33" optional = true [dependencies.hashlink] version = "0.10" [dev-dependencies.libtest-mimic] version = "0.4.0" [dev-dependencies.quickcheck] version = "1.0" [features] debug_prints = [] default = ["encoding"] encoding = ["dep:encoding_rs"] yaml-rust2-0.10.1/Cargo.toml.orig0000644000000017100000000000100120660ustar [package] name = "yaml-rust2" version = "0.10.1" authors = [ "Yuheng Chen ", "Ethiraric ", "David Aguilar " ] documentation = "https://docs.rs/yaml-rust2" license = "MIT OR Apache-2.0" description = "A fully YAML 1.2 compliant YAML parser" repository = "https://github.com/Ethiraric/yaml-rust2" readme = "README.md" edition = "2021" rust-version = "1.70.0" [features] default = [ "encoding" ] debug_prints = [] encoding = [ "dep:encoding_rs" ] [dependencies] arraydeque = "0.5.1" encoding_rs = { version = "0.8.33", optional = true } hashlink = "0.10" [dev-dependencies] libtest-mimic = "0.4.0" quickcheck = "1.0" [profile.release-lto] inherits = "release" lto = true [[test]] name = "yaml-test-suite" harness = false [[bin]] name = "dump_events" path = "tools/dump_events.rs" [[bin]] name = "time_parse" path = "tools/time_parse.rs" [[bin]] name = "run_bench" path = "tools/run_bench.rs" yaml-rust2-0.10.1/Cargo.toml.orig000064400000000000000000000017101046102023000146100ustar 00000000000000[package] name = "yaml-rust2" version = "0.10.1" authors = [ "Yuheng Chen ", "Ethiraric ", "David Aguilar " ] documentation = "https://docs.rs/yaml-rust2" license = "MIT OR Apache-2.0" description = "A fully YAML 1.2 compliant YAML parser" repository = "https://github.com/Ethiraric/yaml-rust2" readme = "README.md" edition = "2021" rust-version = "1.70.0" [features] default = [ "encoding" ] debug_prints = [] encoding = [ "dep:encoding_rs" ] [dependencies] arraydeque = "0.5.1" encoding_rs = { version = "0.8.33", optional = true } hashlink = "0.10" [dev-dependencies] libtest-mimic = "0.4.0" quickcheck = "1.0" [profile.release-lto] inherits = "release" lto = true [[test]] name = "yaml-test-suite" harness = false [[bin]] name = "dump_events" path = "tools/dump_events.rs" [[bin]] name = "time_parse" path = "tools/time_parse.rs" [[bin]] name = "run_bench" path = "tools/run_bench.rs" yaml-rust2-0.10.1/LICENSE000064400000000000000000000013221046102023000127250ustar 00000000000000Code up to and including commit `da52a68615f2ecdd6b7e4567019f280c433c1521` is licensed by Chen Yuheng under either of: - [Apache License, Version 2.0](.licenses/ChenYuheng-Apache) (http://www.apache.org/licenses/LICENSE-2.0) - [MIT License](./licenses/ChenYuheng-MIT) (http://opensource.org/licenses/MIT) Code modifications starting with commit `1d71a23b151dcc12b289d0f06d8207dd9c764216` (included) are licenced by Ethiraric under either of: - [Apache License, Version 2.0](.licenses/Ethiraric-Apache) (http://www.apache.org/licenses/LICENSE-2.0) - [MIT License](./licenses/Ethiraric-MIT) (http://opensource.org/licenses/MIT) Redistributions of this Work must include licenses of both Chen Yuheng and Ethiraric. yaml-rust2-0.10.1/README.md000064400000000000000000000110011046102023000131720ustar 00000000000000# yaml-rust2 > [!IMPORTANT] > > This crate will receive only basic maintenance and keep a stable API. [`saphyr`](https://github.com/saphyr-rs/saphyr) will accept new features, at the cost of a less stable API. > > Please refer to [#26](https://github.com/Ethiraric/yaml-rust2/issues/26) for more details. [yaml-rust2](https://github.com/Ethiraric/yaml-rust2) is a fully compliant YAML 1.2 implementation written in pure Rust. This work is based on [`yaml-rust`](https://github.com/chyh1990/yaml-rust) with fixes towards being compliant to the [YAML test suite](https://github.com/yaml/yaml-test-suite/). `yaml-rust`'s parser is heavily influenced by `libyaml` and `yaml-cpp`. `yaml-rust2` is a pure Rust YAML 1.2 implementation that benefits from the memory safety and other benefits from the Rust language. ## Quick Start Add the following to the Cargo.toml of your project: ```toml [dependencies] yaml-rust2 = "0.9" ``` Use `yaml_rust2::YamlLoader` to load YAML documents and access them as `Yaml` objects: ```rust use yaml_rust2::{YamlLoader, YamlEmitter}; fn main() { let s = " foo: - list1 - list2 bar: - 1 - 2.0 "; let docs = YamlLoader::load_from_str(s).unwrap(); // Multi document support, doc is a yaml::Yaml let doc = &docs[0]; // Debug support println!("{:?}", doc); // Index access for map & array assert_eq!(doc["foo"][0].as_str().unwrap(), "list1"); assert_eq!(doc["bar"][1].as_f64().unwrap(), 2.0); // Array/map-like accesses are checked and won't panic. // They will return `BadValue` if the access is invalid. assert!(doc["INVALID_KEY"][100].is_badvalue()); // Dump the YAML object let mut out_str = String::new(); { let mut emitter = YamlEmitter::new(&mut out_str); emitter.dump(doc).unwrap(); // dump the YAML object to a String } println!("{}", out_str); } ``` Note that `yaml_rust2::Yaml` implements `Index<&'a str>` and `Index`: * `Index` assumes the container is an array * `Index<&'a str>` assumes the container is a string to value map * otherwise, `Yaml::BadValue` is returned If your document does not conform to this convention (e.g. map with complex type key), you can use the `Yaml::as_XXX` family API of functions to access your objects. ## Features * Pure Rust * `Vec`/`HashMap` access API * Low-level YAML events emission ## Security This library does not try to interpret any type specifiers in a YAML document, so there is no risk of, say, instantiating a socket with fields and communicating with the outside world just by parsing a YAML document. ## Specification Compliance This implementation is fully compatible with the YAML 1.2 specification. In order to help with compliance, `yaml-rust2` tests against (and passes) the [YAML test suite](https://github.com/yaml/yaml-test-suite/). ## Upgrading from yaml-rust You can use `yaml-rust2` as a drop-in replacement for the original `yaml-rust` crate. ```toml [dependencies] yaml-rust = { version = "#.#", package = "yaml-rust2" } ``` This `Cargo.toml` declaration allows you to refer to this crate as `yaml_rust` in your code. ```rust use yaml_rust::{YamlLoader, YamlEmitter}; ``` ## License Licensed under either of * Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) * MIT license (http://opensource.org/licenses/MIT) at your option. Since this repository was originally maintained by [chyh1990](https://github.com/chyh1990), there are 2 sets of licenses. A license of each set must be included in redistributions. See the [LICENSE](LICENSE) file for more details. You can find licences in the [`.licenses`](.licenses) subfolder. ## Contribution [Fork this repository](https://github.com/Ethiraric/yaml-rust2/fork) and [Create a Pull Request on Github](https://github.com/Ethiraric/yaml-rust2/compare/master...Ethiraric:yaml-rust2:master). You may need to click on "compare across forks" and select your fork's branch. Make sure that `Ethiraric` is selected as the base repository, not `chyh1990`. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. ## Links * [yaml-rust2 source code repository](https://github.com/Ethiraric/yaml-rust2) * [yaml-rust2 releases on crates.io](https://crates.io/crates/yaml-rust2) * [yaml-rust2 documentation on docs.rs](https://docs.rs/yaml-rust2/latest/yaml_rust2/) * [yaml-test-suite](https://github.com/yaml/yaml-test-suite) yaml-rust2-0.10.1/appveyor.yml000064400000000000000000000037221046102023000143160ustar 00000000000000clone_depth: 1 branches: only: - master environment: LLVM_VERSION: 9.0.1 PLATFORM: x64 matrix: - channel: stable target: i686-pc-windows-msvc type: msvc - channel: stable target: x86_64-pc-windows-msvc type: msvc - channel: stable target: i686-pc-windows-gnu type: gnu - channel: stable target: x86_64-pc-windows-gnu type: gnu - channel: nightly target: i686-pc-windows-msvc type: msvc - channel: nightly target: x86_64-pc-windows-msvc type: msvc - channel: nightly target: i686-pc-windows-gnu type: gnu - channel: nightly target: x86_64-pc-windows-gnu type: gnu install: - if %PLATFORM% == x86 (set RUST_PLATFORM=i686&set MINGW_BITS=32) else (set RUST_PLATFORM=x86_64&set MINGW_BITS=64) - ps: >- If ($env:target -eq 'x86_64-pc-windows-gnu') { $env:PATH += ';C:\msys64\mingw64\bin' } ElseIf ($env:target -eq 'i686-pc-windows-gnu') { $env:PATH += ';C:\msys64\mingw32\bin' } - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe - rustup-init -yv --default-toolchain %channel% --default-host %target% - set PATH=%PATH%;%USERPROFILE%\.cargo\bin - rustc -vV - cargo -vV # Install LLVM for GNU - if %type%==gnu set PATH=C:\msys64\mingw%MINGW_BITS%\bin;C:\msys64\usr\bin;%PATH% - if %type%==gnu set "MINGW_URL=http://repo.msys2.org/mingw/%RUST_PLATFORM%/mingw-w64-%RUST_PLATFORM%" - if %type%==gnu set "URL_VER=%LLVM_VERSION%-1-any.pkg.tar.xz" - if %type%==gnu bash -lc "pacman -U --noconfirm $MINGW_URL-clang-$URL_VER $MINGW_URL-llvm-$URL_VER" - if %type%==gnu bash -lc "clang --version" # Use preinstalled LLVM for MSVC - if %type%==msvc set PATH=%PATH%;C:\Program Files\LLVM\bin - if %type%==msvc where clang - if %type%==msvc clang --version build_script: - cargo build -vv test_script: - cargo test -vv deploy: off yaml-rust2-0.10.1/documents/2024-03-15-FirstRelease.md000064400000000000000000000236471046102023000177010ustar 00000000000000# `yaml-rust2`'s first real release If you are not interested in how this crate was born and just want to know what differs from `yaml-rust`, scroll down to ["This release" or click here](#this-release). ## The why Sometime in August 2023, an ordinary developer (that's me) felt the urge to start scribbling about an OpenAPI linter. I had worked with the OpenAPI format and tried different linters, but none of them felt right. And me needing 3 different linters to lint my OpenAPI was a pain to me. Like any sane person would do, I would write my own (author's note: you are not not sane if you wouldn't). In order to get things started, I needed a YAML parser. On August 14th 2023, I forked `yaml-rust` and started working on it. The crate stated that some YAML features were not yet available and I felt that was an issue I could tackle. I started by getting to know the code, understanding it, adding warnings, refactoring, tinkering, documenting, ... . Anything I could do that made me feel that codebase was better, I would do it. I wanted this crate to be as clean as it could be. ## Fixing YAML compliance In my quest to understand YAML better, I found [the YAML test suite](https://github.com/yaml/yaml-test-suite/): a compilation of corner cases and intricate YAML examples with their expected output / behavior. Interestingly enough, there was an [open pull request on yaml-rust](https://github.com/chyh1990/yaml-rust/pull/187) by [tanriol](https://github.com/tanriol) which integrated the YAML test suite as part of the crate tests. Comments mention that the maintainer wasn't around anymore and that new contributions would probably never be accepted. That, however, was a problem for future-past-me, as I was determined (somehow) to have `yaml-rust` pass every single test of the YAML test suite. Slowly, over the course of multiple months (from August 2023 to January 2024), I would sometimes pick a test from the test suite, fix it, commit and start again. On the 23rd of January, the last commit fixing a test was created. According to the [YAML test matrix](https://matrix.yaml.info/), there is to this day only 1 library that is fully compliant (aside from the Perl parser generated by the reference). This would make `yaml-rust2` the second library to be fully YAML-compliant. You really wouldn't believe how much you have to stretch YAML so that it's not valid YAML anymore. ## Performance With so many improvements, the crate was now perfect!.. Except for performance. Adding conditions for every little bit of compliance has lead the code to be much more complex and branch-y, which CPUs hate. I was around 20% slower than the code was when I started. For a bit over 3 weeks, I stared at flamegraphs and made my CPU repeat the same instructions until it could do it faster. There have been a bunch of improvements for performance since `yaml-rust`'s last commit. Here are a few of them: * Avoid putting characters in a `VecDeque` buffer when we can push them directly into a `String`. * Be a bit smarter about reallocating temporaries: it's best if we know the size in advance, but when we don't we can sometimes avoid pushing characters 1 at a time. * The scanner skips over characters one at a time. When skipping them, it needs to check whether they're a linebreak to update the location. Sometimes, we know we skip over a letter (which is not a linebreak). Several "skip" functions have been added for specific uses. And the big winner, for an around 15% decrease in runtime was: use a statically-sized buffer instead of a dynamically allocated one. (Almost) Every character goes from the input stream into the buffer and then gets read from the buffer. This means that `VecDeque::push` and `VecDeque::pop` were called very frequently. The former always has to check for capacity. Using an `ArrayDeque` removed the need for constant capacity checks, at the cost of a minor decrease in performance if a line is deeply indented. Hopefully, nobody has 42 nested YAML objects. Here is in the end the performance breakdown: ![Comparison of the performance between `yaml-rust`, `yaml-rust2` and the C `libfyaml`. `yaml-rust2` is faster in every test than `yaml-rust`, but `libfyaml` remains faster overall.](./img/benchmarks-v0.6.svg) Here is a short description of what the files contain: * `big`: A large array of records with few fields. One of the fields is a description, a large text block scalar spanning multiple lines. Most of the scanning happens in block scalars. * `nested`: Very short key-value pairs that nest deeply. * `small_objects`: A large array of 2 key-value mappings. * `strings_array`: A large array of lipsum one-liners (~150-175 characters in length). As you can see, `yaml-rust2` performs better than `yaml-rust` on every benchmark. However, when compared against the C [`libfyaml`](https://github.com/pantoniou/libfyaml), we can see that there is still much room for improvement. I'd like to end this section with a small disclaimer: I am not a benchmark expert. I tried to have an heterogenous set of files that would highlight how the parser performs when stressed different ways. I invite you to take a look at [the code generating the YAML files](https://github.com/Ethiraric/yaml-rust2/tree/master/tools/gen_large_yaml) and, if you are more knowledgeable than I am, improve upon them. `yaml-rust2` performs better with these files because those are the ones I could work with. If you find a file with which `yaml-rust2` is slower than `yaml-rust`, do file an issue! ## This release ### Improvements from `yaml-rust` This release should improve over `yaml-rust` over 3 major points: * Performance: We all love fast software. I want to help you achieve it. I haven't managed to make this crate twice as fast, but you should notice a 15-20% improvement in performance. * Compliance: You may not notice it, since I didn't know most of the bugs I fixed were bugs to begin with, but this crate should now be fully YAML-compliant. * Documentation: The documentation of `yaml-rust` is unfortunately incomplete. Documentation here is not exhaustive, but most items are documented. Notably, private items are documented, making it much easier to understand where something happens. There are also in-code comments that help figure out what is going on under the hood. Also, last but not least, I do plan on keeping this crate alive as long as I can. Nobody can make promises on that regard, of course, but I have poured hours of work into this, and I would hate to see this go to waste. ### Switching to `yaml-rust2` This release is `v0.6.0`, chosen to explicitly differ in minor from `yaml-rust`. `v0.4.x` does not exist in this crate to avoid any confusion between the 2 crates. Switching to `yaml-rust2` should be a very simple process. Change your `Cargo.toml` to use `yaml-rust2` instead of `yaml-rust`: ```diff -yaml-rust = "0.4.4" +yaml-rust2 = "0.8.0" ``` As for your code, you have one of two solutions: * Changing your imports from `use yaml_rust::Yaml` to `use yaml_rust2::Yaml` if you import items directly, or change occurrences of `yaml_rust` to `yaml_rust2` if you use fully qualified paths. * Alternatively, you can alias `yaml_rust2` with `use yaml_rust2 as yaml_rust`. This would keep your code working if you use fully qualified paths. Whichever you decide is up to you. [Courtesy of davvid](https://github.com/chyh1990/yaml-rust/issues/160#issuecomment-2008931473), there is another solution. You can combine both approaches and tell `Cargo.toml` to add `yaml-rust2` and to create a `yaml_rust` alias for your code with the following: ```diff -yaml-rust = "0.4.4" +yaml-rust = { version = "0.6", package = "yaml-rust2" } ``` This allows you to switch to `yaml-rust2` while continuing to refer to `yaml_rust` in your code (e.g. use `yaml_rust::YamlLoader;` will continue to work so that no Rust code changes are required). #### What about API breakage? Most of what I have changed is in the implementation details. You might notice more documentation appearing on your LSP, but documentation isn't bound by the API. There is only one change I made that could lead to compile errors. It is unlikely you used that feature, but I'd hate to leave this undocumented. If you use the low-level event parsing API (`Parser`, `EventReceiver` / `MarkedEventReceiver`) and namely the `yaml_rust::Event` enumeration, there is one change that might break your code. This was needed for tests in the YAML test suite. In `yaml-rust`, YAML tags are not forwarded from the lower-level `Scanner` API to the low-level `Parser` API. Here is the change that was made in the library: ```diff pub enum Event { // ... -SequenceStart(usize), -MappingStart(usize), +SequenceStart(usize, Option), +MappingStart(usize, Option), // ... } ``` This means that you may now see YAML tags appearing in your code. ## Closing words YAML is hard. Much more than I had anticipated. If you are exploring dark corners of YAML that `yaml-rust2` supports but `yaml-rust` doesn't, I'm curious to know what it is. Work on this crate is far from over. I will try and match `libfyaml`'s performance. Today is the first time I benched against it, and I wouldn't have guessed it to outperform `yaml-rust2` that much. If you're interested in upgrading your `yaml-rust` crate, please do take a look at [davvid](https://github.com/davvid)'s [fork of `yaml-rust`](https://github.com/davvid/yaml-rust). Very recent developments on this crate sparked from an [issue on advisory-db](https://github.com/rustsec/advisory-db/issues/1921) about the unmaintained state of `yaml-rust`. I hope it will be that YAML in Rust will improve following this issue. Thank you for reading through this. If you happen to have issues with `yaml-rust2` or suggestions, do [drop an issue](https://github.com/Ethiraric/yaml-rust2/issues)! If however you wanted an OpenAPI linter, I'm afraid you're out of luck. Just as much as I'm out of time ;) -Ethiraric EDIT(20-03-2024): Add davvid's method of switching to `yaml-rust2` by creating a Cargo alias. yaml-rust2-0.10.1/documents/img/2024-03-15-benchmarks.csv000064400000000000000000000003351046102023000203620ustar 00000000000000,yaml-rust2,yaml-rust,libfyaml big.yaml,1644933464,2097747837,1642761913 nested.yaml,1186706803,1461738560,1104480120 small_objects.yaml,5459915062,5686715239,4402878726 strings_array.yaml,1698194153,2044921291,924246153 yaml-rust2-0.10.1/documents/img/benchmarks-v0.6.svg000064400000000000000000000232511046102023000200270ustar 00000000000000 big nested small_objects strings_array 0 1000000 2000000 3000000 4000000 5000000 6000000 yaml-rust yaml-rust2 libfyaml Time in ms (less is better) yaml-rust2-0.10.1/examples/dump_yaml.rs000064400000000000000000000017421046102023000161010ustar 00000000000000use std::env; use std::fs::File; use std::io::prelude::*; use yaml_rust2::yaml; fn print_indent(indent: usize) { for _ in 0..indent { print!(" "); } } fn dump_node(doc: &yaml::Yaml, indent: usize) { match *doc { yaml::Yaml::Array(ref v) => { for x in v { dump_node(x, indent + 1); } } yaml::Yaml::Hash(ref h) => { for (k, v) in h { print_indent(indent); println!("{k:?}:"); dump_node(v, indent + 1); } } _ => { print_indent(indent); println!("{doc:?}"); } } } fn main() { let args: Vec<_> = env::args().collect(); let mut f = File::open(&args[1]).unwrap(); let mut s = String::new(); f.read_to_string(&mut s).unwrap(); let docs = yaml::YamlLoader::load_from_str(&s).unwrap(); for doc in &docs { println!("---"); dump_node(doc, 0); } } yaml-rust2-0.10.1/garden.yaml000064400000000000000000000041611046102023000140500ustar 00000000000000# Use "cargo install garden-tools" to install garden https://gitlab.com/garden-rs/garden # # usage: # garden build # garden test # garden check # garden fmt # garden fix commands: bench: cargo bench "$@" build: cargo build "$@" check>: - check/clippy - check/fmt - build - test - check/profile check/clippy: cargo clippy --all-targets "$@" -- -D warnings check/fmt: cargo fmt --check check/profile: | cargo build \ --profile=release-lto \ --package gen_large_yaml \ --bin gen_large_yaml \ --manifest-path tools/gen_large_yaml/Cargo.toml clean: cargo clean "$@" coverage: cargo kcov "$@" doc: cargo doc --no-deps --package yaml-rust2 "$@" ethi/bench: | cargo build --release --all-targets cd ../Yaml-rust && cargo build --release --all-targets cd ../libfyaml/build && ninja cargo bench_compare run_bench fix: cargo clippy --all-targets --fix "$@" -- -D warnings fmt: cargo fmt "$@" test: cargo test "$@" update: cargo update "$@" watch: cargo watch --shell "garden check" trees: yaml-rust2: description: A pure Rust YAML implementation path: ${GARDEN_CONFIG_DIR} url: "git@github.com:Ethiraric/yaml-rust2.git" remotes: davvid: "git@github.com:davvid/yaml-rust2.git" yaml-rust: "git@github.com:chyh1990/yaml-rust.git" gitconfig: # Access yaml-rust2 pull requests as yaml-rust2/pull/* remote.yaml-rust2.url: "git@github.com:Ethiraric/yaml-rust2.git" remote.yaml-rust2.fetch: - "+refs/pull/*/head:refs/remotes/yaml-rust2/pull/*" # Access yaml-rust pull requests as yaml-rust/pull/* remote.yaml-rust.fetch: - "+refs/heads/*:refs/remotes/yaml-rust/*" - "+refs/pull/*/head:refs/remotes/yaml-rust/pull/*" yaml-test-suite: description: Comprehensive, language independent Test Suite for YAML path: tests/yaml-test-suite url: https://github.com/yaml/yaml-test-suite yaml-rust2-0.10.1/justfile000064400000000000000000000012351046102023000134730ustar 00000000000000before_commit: cargo fmt --check cargo clippy --release --all-targets -- -D warnings cargo clippy --all-targets -- -D warnings cargo build --release --all-targets cargo build --all-targets cargo test cargo test --release cargo test --doc cargo build --profile=release-lto --package gen_large_yaml --bin gen_large_yaml --manifest-path tools/gen_large_yaml/Cargo.toml RUSTDOCFLAGS="-D warnings" cargo doc --all-features ethi_bench: cargo build --release --all-targets cd ../Yaml-rust && cargo build --release --all-targets cd ../serde-yaml/ && cargo build --release --all-targets cd ../libfyaml/build && ninja cargo bench_compare run_bench yaml-rust2-0.10.1/src/char_traits.rs000064400000000000000000000070321046102023000153640ustar 00000000000000//! Holds functions to determine if a character belongs to a specific character set. /// Check whether the character is nil (`\0`). #[inline] pub(crate) fn is_z(c: char) -> bool { c == '\0' } /// Check whether the character is a line break (`\r` or `\n`). #[inline] pub(crate) fn is_break(c: char) -> bool { c == '\n' || c == '\r' } /// Check whether the character is nil or a line break (`\0`, `\r`, `\n`). #[inline] pub(crate) fn is_breakz(c: char) -> bool { is_break(c) || is_z(c) } /// Check whether the character is a whitespace (` ` or `\t`). #[inline] pub(crate) fn is_blank(c: char) -> bool { c == ' ' || c == '\t' } /// Check whether the character is nil, a linebreak or a whitespace. /// /// `\0`, ` `, `\t`, `\n`, `\r` #[inline] pub(crate) fn is_blank_or_breakz(c: char) -> bool { is_blank(c) || is_breakz(c) } /// Check whether the character is an ascii digit. #[inline] pub(crate) fn is_digit(c: char) -> bool { c.is_ascii_digit() } /// Check whether the character is a digit, letter, `_` or `-`. #[inline] pub(crate) fn is_alpha(c: char) -> bool { matches!(c, '0'..='9' | 'a'..='z' | 'A'..='Z' | '_' | '-') } /// Check whether the character is a hexadecimal character (case insensitive). #[inline] pub(crate) fn is_hex(c: char) -> bool { c.is_ascii_digit() || ('a'..='f').contains(&c) || ('A'..='F').contains(&c) } /// Convert the hexadecimal digit to an integer. #[inline] pub(crate) fn as_hex(c: char) -> u32 { match c { '0'..='9' => (c as u32) - ('0' as u32), 'a'..='f' => (c as u32) - ('a' as u32) + 10, 'A'..='F' => (c as u32) - ('A' as u32) + 10, _ => unreachable!(), } } /// Check whether the character is a YAML flow character (one of `,[]{}`). #[inline] pub(crate) fn is_flow(c: char) -> bool { matches!(c, ',' | '[' | ']' | '{' | '}') } /// Check whether the character is the BOM character. #[inline] pub(crate) fn is_bom(c: char) -> bool { c == '\u{FEFF}' } /// Check whether the character is a YAML non-breaking character. #[inline] pub(crate) fn is_yaml_non_break(c: char) -> bool { // TODO(ethiraric, 28/12/2023): is_printable !is_break(c) && !is_bom(c) } /// Check whether the character is NOT a YAML whitespace (` ` / `\t`). #[inline] pub(crate) fn is_yaml_non_space(c: char) -> bool { is_yaml_non_break(c) && !is_blank(c) } /// Check whether the character is a valid YAML anchor name character. #[inline] pub(crate) fn is_anchor_char(c: char) -> bool { is_yaml_non_space(c) && !is_flow(c) && !is_z(c) } /// Check whether the character is a valid word character. #[inline] pub(crate) fn is_word_char(c: char) -> bool { is_alpha(c) && c != '_' } /// Check whether the character is a valid URI character. #[inline] pub(crate) fn is_uri_char(c: char) -> bool { is_word_char(c) || "#;/?:@&=+$,_.!~*\'()[]%".contains(c) } /// Check whether the character is a valid tag character. #[inline] pub(crate) fn is_tag_char(c: char) -> bool { is_uri_char(c) && !is_flow(c) && c != '!' } /// Check if the string can be expressed a valid literal block scalar. /// The YAML spec supports all of the following in block literals except `#xFEFF`: /// ```no_compile /// #x9 | #xA | [#x20-#x7E] /* 8 bit */ /// | #x85 | [#xA0-#xD7FF] | [#xE000-#xFFFD] /* 16 bit */ /// | [#x10000-#x10FFFF] /* 32 bit */ /// ``` #[inline] pub(crate) fn is_valid_literal_block_scalar(string: &str) -> bool { string.chars().all(|character: char| matches!(character, '\t' | '\n' | '\x20'..='\x7e' | '\u{0085}' | '\u{00a0}'..='\u{d7fff}')) } yaml-rust2-0.10.1/src/debug.rs000064400000000000000000000025621046102023000141520ustar 00000000000000//! Debugging helpers. //! //! Debugging is governed by two conditions: //! 1. The build mode. Debugging code is not emitted in release builds and thus not available. //! 2. The `YAMLALL_DEBUG` environment variable. If built in debug mode, the program must be fed //! the `YAMLALL_DEBUG` variable in its environment. While debugging code is present in debug //! build, debug helpers will only trigger if that variable is set when running the program. // If a debug build, use stuff in the debug submodule. #[cfg(feature = "debug_prints")] pub use debug::enabled; // Otherwise, just export dummies for publicly visible functions. /// Evaluates to nothing. #[cfg(not(feature = "debug_prints"))] macro_rules! debug_print { ($($arg:tt)*) => {{}}; } #[cfg(feature = "debug_prints")] #[macro_use] #[allow(clippy::module_inception)] mod debug { use std::sync::OnceLock; /// If debugging is [`enabled`], print the format string on the error output. macro_rules! debug_print { ($($arg:tt)*) => {{ if $crate::debug::enabled() { eprintln!($($arg)*) } }}; } /// Return whether debugging features are enabled in this execution. #[cfg(debug_assertions)] pub fn enabled() -> bool { static ENABLED: OnceLock = OnceLock::new(); *ENABLED.get_or_init(|| std::env::var("YAMLRUST2_DEBUG").is_ok()) } } yaml-rust2-0.10.1/src/emitter.rs000064400000000000000000000325621046102023000145400ustar 00000000000000//! YAML serialization helpers. use crate::char_traits; use crate::yaml::{Hash, Yaml}; use std::convert::From; use std::error::Error; use std::fmt::{self, Display}; /// An error when emitting YAML. #[derive(Copy, Clone, Debug)] pub enum EmitError { /// A formatting error. FmtError(fmt::Error), } impl Error for EmitError { fn cause(&self) -> Option<&dyn Error> { None } } impl Display for EmitError { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { match *self { EmitError::FmtError(ref err) => Display::fmt(err, formatter), } } } impl From for EmitError { fn from(f: fmt::Error) -> Self { EmitError::FmtError(f) } } /// The YAML serializer. /// /// ``` /// # use yaml_rust2::{YamlLoader, YamlEmitter}; /// let input_string = "a: b\nc: d"; /// let yaml = YamlLoader::load_from_str(input_string).unwrap(); /// /// let mut output = String::new(); /// YamlEmitter::new(&mut output).dump(&yaml[0]).unwrap(); /// /// assert_eq!(output, r#"--- /// a: b /// c: d"#); /// ``` #[allow(clippy::module_name_repetitions)] pub struct YamlEmitter<'a> { writer: &'a mut dyn fmt::Write, best_indent: usize, compact: bool, level: isize, multiline_strings: bool, } /// A convenience alias for emitter functions that may fail without returning a value. pub type EmitResult = Result<(), EmitError>; // from serialize::json fn escape_str(wr: &mut dyn fmt::Write, v: &str) -> Result<(), fmt::Error> { wr.write_str("\"")?; let mut start = 0; for (i, byte) in v.bytes().enumerate() { let escaped = match byte { b'"' => "\\\"", b'\\' => "\\\\", b'\x00' => "\\u0000", b'\x01' => "\\u0001", b'\x02' => "\\u0002", b'\x03' => "\\u0003", b'\x04' => "\\u0004", b'\x05' => "\\u0005", b'\x06' => "\\u0006", b'\x07' => "\\u0007", b'\x08' => "\\b", b'\t' => "\\t", b'\n' => "\\n", b'\x0b' => "\\u000b", b'\x0c' => "\\f", b'\r' => "\\r", b'\x0e' => "\\u000e", b'\x0f' => "\\u000f", b'\x10' => "\\u0010", b'\x11' => "\\u0011", b'\x12' => "\\u0012", b'\x13' => "\\u0013", b'\x14' => "\\u0014", b'\x15' => "\\u0015", b'\x16' => "\\u0016", b'\x17' => "\\u0017", b'\x18' => "\\u0018", b'\x19' => "\\u0019", b'\x1a' => "\\u001a", b'\x1b' => "\\u001b", b'\x1c' => "\\u001c", b'\x1d' => "\\u001d", b'\x1e' => "\\u001e", b'\x1f' => "\\u001f", b'\x7f' => "\\u007f", _ => continue, }; if start < i { wr.write_str(&v[start..i])?; } wr.write_str(escaped)?; start = i + 1; } if start != v.len() { wr.write_str(&v[start..])?; } wr.write_str("\"")?; Ok(()) } impl<'a> YamlEmitter<'a> { /// Create a new emitter serializing into `writer`. pub fn new(writer: &'a mut dyn fmt::Write) -> YamlEmitter<'a> { YamlEmitter { writer, best_indent: 2, compact: true, level: -1, multiline_strings: false, } } /// Set 'compact inline notation' on or off, as described for block /// [sequences](http://www.yaml.org/spec/1.2/spec.html#id2797382) /// and /// [mappings](http://www.yaml.org/spec/1.2/spec.html#id2798057). /// /// In this form, blocks cannot have any properties (such as anchors /// or tags), which should be OK, because this emitter doesn't /// (currently) emit those anyways. pub fn compact(&mut self, compact: bool) { self.compact = compact; } /// Determine if this emitter is using 'compact inline notation'. #[must_use] pub fn is_compact(&self) -> bool { self.compact } /// Render strings containing multiple lines in [literal style]. /// /// # Examples /// /// ```rust /// use yaml_rust2::{Yaml, YamlEmitter, YamlLoader}; /// /// let input = r#"{foo: "bar!\nbar!", baz: 42}"#; /// let parsed = YamlLoader::load_from_str(input).unwrap(); /// eprintln!("{:?}", parsed); /// /// let mut output = String::new(); /// let mut emitter = YamlEmitter::new(&mut output); /// emitter.multiline_strings(true); /// emitter.dump(&parsed[0]).unwrap(); /// assert_eq!(output.as_str(), "\ /// --- /// foo: |- /// bar! /// bar! /// baz: 42"); /// ``` /// /// [literal style]: https://yaml.org/spec/1.2/spec.html#id2795688 pub fn multiline_strings(&mut self, multiline_strings: bool) { self.multiline_strings = multiline_strings; } /// Determine if this emitter will emit multiline strings when appropriate. #[must_use] pub fn is_multiline_strings(&self) -> bool { self.multiline_strings } /// Dump Yaml to an output stream. /// # Errors /// Returns `EmitError` when an error occurs. pub fn dump(&mut self, doc: &Yaml) -> EmitResult { // write DocumentStart writeln!(self.writer, "---")?; self.level = -1; self.emit_node(doc) } fn write_indent(&mut self) -> EmitResult { if self.level <= 0 { return Ok(()); } for _ in 0..self.level { for _ in 0..self.best_indent { write!(self.writer, " ")?; } } Ok(()) } fn emit_node(&mut self, node: &Yaml) -> EmitResult { match *node { Yaml::Array(ref v) => self.emit_array(v), Yaml::Hash(ref h) => self.emit_hash(h), Yaml::String(ref v) => { if self.multiline_strings && v.contains('\n') && char_traits::is_valid_literal_block_scalar(v) { self.emit_literal_block(v)?; } else if need_quotes(v) { escape_str(self.writer, v)?; } else { write!(self.writer, "{v}")?; } Ok(()) } Yaml::Boolean(v) => { if v { self.writer.write_str("true")?; } else { self.writer.write_str("false")?; } Ok(()) } Yaml::Integer(v) => { write!(self.writer, "{v}")?; Ok(()) } Yaml::Real(ref v) => { write!(self.writer, "{v}")?; Ok(()) } Yaml::Null | Yaml::BadValue => { write!(self.writer, "~")?; Ok(()) } // XXX(chenyh) Alias Yaml::Alias(_) => Ok(()), } } fn emit_literal_block(&mut self, v: &str) -> EmitResult { let ends_with_newline = v.ends_with('\n'); if ends_with_newline { self.writer.write_str("|")?; } else { self.writer.write_str("|-")?; } self.level += 1; // lines() will omit the last line if it is empty. for line in v.lines() { writeln!(self.writer)?; self.write_indent()?; // It's literal text, so don't escape special chars. self.writer.write_str(line)?; } self.level -= 1; Ok(()) } fn emit_array(&mut self, v: &[Yaml]) -> EmitResult { if v.is_empty() { write!(self.writer, "[]")?; } else { self.level += 1; for (cnt, x) in v.iter().enumerate() { if cnt > 0 { writeln!(self.writer)?; self.write_indent()?; } write!(self.writer, "-")?; self.emit_val(true, x)?; } self.level -= 1; } Ok(()) } fn emit_hash(&mut self, h: &Hash) -> EmitResult { if h.is_empty() { self.writer.write_str("{}")?; } else { self.level += 1; for (cnt, (k, v)) in h.iter().enumerate() { let complex_key = matches!(*k, Yaml::Hash(_) | Yaml::Array(_)); if cnt > 0 { writeln!(self.writer)?; self.write_indent()?; } if complex_key { write!(self.writer, "?")?; self.emit_val(true, k)?; writeln!(self.writer)?; self.write_indent()?; write!(self.writer, ":")?; self.emit_val(true, v)?; } else { self.emit_node(k)?; write!(self.writer, ":")?; self.emit_val(false, v)?; } } self.level -= 1; } Ok(()) } /// Emit a yaml as a hash or array value: i.e., which should appear /// following a ":" or "-", either after a space, or on a new line. /// If `inline` is true, then the preceding characters are distinct /// and short enough to respect the compact flag. fn emit_val(&mut self, inline: bool, val: &Yaml) -> EmitResult { match *val { Yaml::Array(ref v) => { if (inline && self.compact) || v.is_empty() { write!(self.writer, " ")?; } else { writeln!(self.writer)?; self.level += 1; self.write_indent()?; self.level -= 1; } self.emit_array(v) } Yaml::Hash(ref h) => { if (inline && self.compact) || h.is_empty() { write!(self.writer, " ")?; } else { writeln!(self.writer)?; self.level += 1; self.write_indent()?; self.level -= 1; } self.emit_hash(h) } _ => { write!(self.writer, " ")?; self.emit_node(val) } } } } /// Check if the string requires quoting. /// Strings starting with any of the following characters must be quoted. /// :, &, *, ?, |, -, <, >, =, !, %, @ /// Strings containing any of the following characters must be quoted. /// {, }, \[, t \], ,, #, ` /// /// If the string contains any of the following control characters, it must be escaped with double quotes: /// \0, \x01, \x02, \x03, \x04, \x05, \x06, \a, \b, \t, \n, \v, \f, \r, \x0e, \x0f, \x10, \x11, \x12, \x13, \x14, \x15, \x16, \x17, \x18, \x19, \x1a, \e, \x1c, \x1d, \x1e, \x1f, \N, \_, \L, \P /// /// Finally, there are other cases when the strings must be quoted, no matter if you're using single or double quotes: /// * When the string is true or false (otherwise, it would be treated as a boolean value); /// * When the string is null or ~ (otherwise, it would be considered as a null value); /// * When the string looks like a number, such as integers (e.g. 2, 14, etc.), floats (e.g. 2.6, 14.9) and exponential numbers (e.g. 12e7, etc.) (otherwise, it would be treated as a numeric value); /// * When the string looks like a date (e.g. 2014-12-31) (otherwise it would be automatically converted into a Unix timestamp). #[allow(clippy::doc_markdown)] fn need_quotes(string: &str) -> bool { fn need_quotes_spaces(string: &str) -> bool { string.starts_with(' ') || string.ends_with(' ') } string.is_empty() || need_quotes_spaces(string) || string.starts_with(|character: char| { matches!( character, '&' | '*' | '?' | '|' | '-' | '<' | '>' | '=' | '!' | '%' | '@' ) }) || string.contains(|character: char| { matches!(character, ':' | '{' | '}' | '[' | ']' | ',' | '#' | '`' | '\"' | '\'' | '\\' | '\0'..='\x06' | '\t' | '\n' | '\r' | '\x0e'..='\x1a' | '\x1c'..='\x1f') }) || [ // Canonical forms of the boolean values in the Core schema. "true", "false", "True", "False", "TRUE", "FALSE", // Canonical forms of the null value in the Core schema. "null", "Null", "NULL", "~", // These can be quoted when emitting so that YAML 1.1 parsers do not parse them as // booleans. This doesn't cause any issue with YAML 1.2 parsers. "y", "Y", "n", "N", "yes", "Yes", "YES", "no", "No", "NO", "True", "TRUE", "False", "FALSE", "on", "On", "ON", "off", "Off", "OFF", ] .contains(&string) || string.starts_with('.') || string.starts_with("0x") || string.parse::().is_ok() || string.parse::().is_ok() } #[cfg(test)] mod test { use super::YamlEmitter; use crate::YamlLoader; #[test] fn test_multiline_string() { let input = r#"{foo: "bar!\nbar!", baz: 42}"#; let parsed = YamlLoader::load_from_str(input).unwrap(); let mut output = String::new(); let mut emitter = YamlEmitter::new(&mut output); emitter.multiline_strings(true); emitter.dump(&parsed[0]).unwrap(); } } yaml-rust2-0.10.1/src/lib.rs000064400000000000000000000035101046102023000136240ustar 00000000000000// Copyright 2015, Yuheng Chen. // Copyright 2023, Ethiraric. // See the LICENSE file at the top-level directory of this distribution. //! YAML 1.2 implementation in pure Rust. //! //! # Usage //! //! This crate is [on github](https://github.com/Ethiraric/yaml-rust2) and can be used by adding //! `yaml-rust2` to the dependencies in your project's `Cargo.toml`. //! //! ```toml //! [dependencies] //! yaml-rust2 = "0.10.1" //! ``` //! //! # Examples //! Parse a string into `Vec` and then serialize it as a YAML string. //! //! ``` //! use yaml_rust2::{YamlLoader, YamlEmitter}; //! //! let docs = YamlLoader::load_from_str("[1, 2, 3]").unwrap(); //! let doc = &docs[0]; // select the first YAML document //! assert_eq!(doc[0].as_i64().unwrap(), 1); // access elements by index //! //! let mut out_str = String::new(); //! let mut emitter = YamlEmitter::new(&mut out_str); //! emitter.dump(doc).unwrap(); // dump the YAML object to a String //! //! ``` //! //! # Features //! **Note:** With all features disabled, this crate's MSRV is `1.65.0`. //! //! #### `encoding` (_enabled by default_) //! Enables encoding-aware decoding of Yaml documents. //! //! The MSRV for this feature is `1.70.0`. //! //! #### `debug_prints` //! Enables the `debug` module and usage of debug prints in the scanner and the parser. Do not //! enable if you are consuming the crate rather than working on it as this can significantly //! decrease performance. //! //! The MSRV for this feature is `1.70.0`. #![warn(missing_docs, clippy::pedantic)] extern crate hashlink; pub(crate) mod char_traits; #[macro_use] pub(crate) mod debug; pub mod emitter; pub mod parser; pub mod scanner; pub mod yaml; // reexport key APIs pub use crate::emitter::{EmitError, YamlEmitter}; pub use crate::parser::Event; pub use crate::scanner::ScanError; pub use crate::yaml::{Yaml, YamlLoader}; yaml-rust2-0.10.1/src/parser.rs000064400000000000000000001165661046102023000143720ustar 00000000000000//! Home to the YAML Parser. //! //! The parser takes input from the [`crate::scanner::Scanner`], performs final checks for YAML //! compliance, and emits a stream of tokens that can be used by the [`crate::YamlLoader`] to //! construct the [`crate::Yaml`] object. use crate::scanner::{Marker, ScanError, Scanner, TScalarStyle, Token, TokenType}; use std::collections::HashMap; #[derive(Clone, Copy, PartialEq, Debug, Eq)] enum State { /// We await the start of the stream. StreamStart, ImplicitDocumentStart, DocumentStart, DocumentContent, DocumentEnd, BlockNode, // BlockNodeOrIndentlessSequence, // FlowNode, BlockSequenceFirstEntry, BlockSequenceEntry, IndentlessSequenceEntry, BlockMappingFirstKey, BlockMappingKey, BlockMappingValue, FlowSequenceFirstEntry, FlowSequenceEntry, FlowSequenceEntryMappingKey, FlowSequenceEntryMappingValue, FlowSequenceEntryMappingEnd, FlowMappingFirstKey, FlowMappingKey, FlowMappingValue, FlowMappingEmptyValue, End, } /// An event generated by the YAML parser. /// /// Events are used in the low-level event-based API (push parser). The API entrypoint is the /// [`EventReceiver`] trait. #[derive(Clone, PartialEq, Debug, Eq)] pub enum Event { /// Reserved for internal use. Nothing, /// Event generated at the very beginning of parsing. StreamStart, /// Last event that will be generated by the parser. Signals EOF. StreamEnd, /// The YAML start document directive (`---`). DocumentStart, /// The YAML end document directive (`...`). DocumentEnd, /// A YAML Alias. Alias( /// The anchor ID the alias refers to. usize, ), /// Value, style, anchor id, tag Scalar(String, TScalarStyle, usize, Option), /// The start of a YAML sequence (array). SequenceStart( /// The anchor ID of the start of the sequence. usize, /// An optional tag Option, ), /// The end of a YAML sequence (array). SequenceEnd, /// The start of a YAML mapping (object, hash). MappingStart( /// The anchor ID of the start of the mapping. usize, /// An optional tag Option, ), /// The end of a YAML mapping (object, hash). MappingEnd, } /// A YAML tag. #[derive(Clone, PartialEq, Debug, Eq)] pub struct Tag { /// Handle of the tag (`!` included). pub handle: String, /// The suffix of the tag. pub suffix: String, } impl Event { /// Create an empty scalar. fn empty_scalar() -> Event { // a null scalar Event::Scalar(String::new(), TScalarStyle::Plain, 0, None) } /// Create an empty scalar with the given anchor. fn empty_scalar_with_anchor(anchor: usize, tag: Option) -> Event { Event::Scalar(String::new(), TScalarStyle::Plain, anchor, tag) } } /// A YAML parser. #[derive(Debug)] pub struct Parser { scanner: Scanner, states: Vec, state: State, token: Option, current: Option<(Event, Marker)>, anchors: HashMap, anchor_id: usize, /// The tag directives (`%TAG`) the parser has encountered. /// /// Key is the handle, and value is the prefix. tags: HashMap, /// Make tags global across all documents. keep_tags: bool, } /// Trait to be implemented in order to use the low-level parsing API. /// /// The low-level parsing API is event-based (a push parser), calling [`EventReceiver::on_event`] /// for each YAML [`Event`] that occurs. /// The [`EventReceiver`] trait only receives events. In order to receive both events and their /// location in the source, use [`MarkedEventReceiver`]. Note that [`EventReceiver`]s implement /// [`MarkedEventReceiver`] automatically. /// /// # Event hierarchy /// The event stream starts with an [`Event::StreamStart`] event followed by an /// [`Event::DocumentStart`] event. If the YAML document starts with a mapping (an object), an /// [`Event::MappingStart`] event is emitted. If it starts with a sequence (an array), an /// [`Event::SequenceStart`] event is emitted. Otherwise, an [`Event::Scalar`] event is emitted. /// /// In a mapping, key-values are sent as consecutive events. The first event after an /// [`Event::MappingStart`] will be the key, and following its value. If the mapping contains no /// sub-mapping or sub-sequence, then even events (starting from 0) will always be keys and odd /// ones will always be values. The mapping ends when an [`Event::MappingEnd`] event is received. /// /// In a sequence, values are sent consecutively until the [`Event::SequenceEnd`] event. /// /// If a value is a sub-mapping or a sub-sequence, an [`Event::MappingStart`] or /// [`Event::SequenceStart`] event will be sent respectively. Following events until the associated /// [`Event::MappingStart`] or [`Event::SequenceEnd`] (beware of nested mappings or sequences) will /// be part of the value and not another key-value pair or element in the sequence. /// /// For instance, the following yaml: /// ```yaml /// a: b /// c: /// d: e /// f: /// - g /// - h /// ``` /// will emit (indented and commented for lisibility): /// ```text /// StreamStart, DocumentStart, MappingStart, /// Scalar("a", ..), Scalar("b", ..) /// Scalar("c", ..), MappingStart, Scalar("d", ..), Scalar("e", ..), MappingEnd, /// Scalar("f", ..), SequenceStart, Scalar("g", ..), Scalar("h", ..), SequenceEnd, /// MappingEnd, DocumentEnd, StreamEnd /// ``` /// /// # Example /// ``` /// # use yaml_rust2::parser::{Event, EventReceiver, Parser}; /// # /// /// Sink of events. Collects them into an array. /// struct EventSink { /// events: Vec, /// } /// /// /// Implement `on_event`, pushing into `self.events`. /// impl EventReceiver for EventSink { /// fn on_event(&mut self, ev: Event) { /// self.events.push(ev); /// } /// } /// /// /// Load events from a yaml string. /// fn str_to_events(yaml: &str) -> Vec { /// let mut sink = EventSink { events: Vec::new() }; /// let mut parser = Parser::new_from_str(yaml); /// // Load events using our sink as the receiver. /// parser.load(&mut sink, true).unwrap(); /// sink.events /// } /// ``` pub trait EventReceiver { /// Handler called for each YAML event that is emitted by the parser. fn on_event(&mut self, ev: Event); } /// Trait to be implemented for using the low-level parsing API. /// /// Functionally similar to [`EventReceiver`], but receives a [`Marker`] as well as the event. pub trait MarkedEventReceiver { /// Handler called for each event that occurs. fn on_event(&mut self, ev: Event, _mark: Marker); } impl MarkedEventReceiver for R { fn on_event(&mut self, ev: Event, _mark: Marker) { self.on_event(ev); } } /// A convenience alias for a `Result` of a parser event. pub type ParseResult = Result<(Event, Marker), ScanError>; impl<'a> Parser> { /// Create a new instance of a parser from a &str. #[must_use] pub fn new_from_str(value: &'a str) -> Self { Parser::new(value.chars()) } } impl> Parser { /// Create a new instance of a parser from the given input of characters. pub fn new(src: T) -> Parser { Parser { scanner: Scanner::new(src), states: Vec::new(), state: State::StreamStart, token: None, current: None, anchors: HashMap::new(), // valid anchor_id starts from 1 anchor_id: 1, tags: HashMap::new(), keep_tags: false, } } /// Whether to keep tags across multiple documents when parsing. /// /// This behavior is non-standard as per the YAML specification but can be encountered in the /// wild. This boolean allows enabling this non-standard extension. This would result in the /// parser accepting input from [test /// QLJ7](https://github.com/yaml/yaml-test-suite/blob/ccfa74e56afb53da960847ff6e6976c0a0825709/src/QLJ7.yaml) /// of the yaml-test-suite: /// /// ```yaml /// %TAG !prefix! tag:example.com,2011: /// --- !prefix!A /// a: b /// --- !prefix!B /// c: d /// --- !prefix!C /// e: f /// ``` /// /// With `keep_tags` set to `false`, the above YAML is rejected. As per the specification, tags /// only apply to the document immediately following them. This would error on `!prefix!B`. /// /// With `keep_tags` set to `true`, the above YAML is accepted by the parser. #[must_use] pub fn keep_tags(mut self, value: bool) -> Self { self.keep_tags = value; self } /// Try to load the next event and return it, but do not consuming it from `self`. /// /// Any subsequent call to [`Parser::peek`] will return the same value, until a call to /// [`Iterator::next`] or [`Parser::load`]. /// # Errors /// Returns `ScanError` when loading the next event fails. pub fn peek(&mut self) -> Result<&(Event, Marker), ScanError> { if let Some(ref x) = self.current { Ok(x) } else { self.current = Some(self.next_token()?); self.peek() } } /// Try to load the next event and return it, consuming it from `self`. /// # Errors /// Returns `ScanError` when loading the next event fails. pub fn next_token(&mut self) -> ParseResult { match self.current.take() { None => self.parse(), Some(v) => Ok(v), } } /// Peek at the next token from the scanner. fn peek_token(&mut self) -> Result<&Token, ScanError> { match self.token { None => { self.token = Some(self.scan_next_token()?); Ok(self.token.as_ref().unwrap()) } Some(ref tok) => Ok(tok), } } /// Extract and return the next token from the scanner. /// /// This function does _not_ make use of `self.token`. fn scan_next_token(&mut self) -> Result { let token = self.scanner.next(); match token { None => match self.scanner.get_error() { None => Err(ScanError::new(self.scanner.mark(), "unexpected eof")), Some(e) => Err(e), }, Some(tok) => Ok(tok), } } fn fetch_token(&mut self) -> Token { self.token .take() .expect("fetch_token needs to be preceded by peek_token") } /// Skip the next token from the scanner. fn skip(&mut self) { self.token = None; //self.peek_token(); } /// Pops the top-most state and make it the current state. fn pop_state(&mut self) { self.state = self.states.pop().unwrap(); } /// Push a new state atop the state stack. fn push_state(&mut self, state: State) { self.states.push(state); } fn parse(&mut self) -> ParseResult { if self.state == State::End { return Ok((Event::StreamEnd, self.scanner.mark())); } let (ev, mark) = self.state_machine()?; // println!("EV {:?}", ev); Ok((ev, mark)) } /// Load the YAML from the stream in `self`, pushing events into `recv`. /// /// The contents of the stream are parsed and the corresponding events are sent into the /// recveiver. For detailed explanations about how events work, see [`EventReceiver`]. /// /// If `multi` is set to `true`, the parser will allow parsing of multiple YAML documents /// inside the stream. /// /// Note that any [`EventReceiver`] is also a [`MarkedEventReceiver`], so implementing the /// former is enough to call this function. /// # Errors /// Returns `ScanError` when loading fails. pub fn load( &mut self, recv: &mut R, multi: bool, ) -> Result<(), ScanError> { if !self.scanner.stream_started() { let (ev, mark) = self.next_token()?; if ev != Event::StreamStart { return Err(ScanError::new(mark, "did not find expected ")); } recv.on_event(ev, mark); } if self.scanner.stream_ended() { // XXX has parsed? recv.on_event(Event::StreamEnd, self.scanner.mark()); return Ok(()); } loop { let (ev, mark) = self.next_token()?; if ev == Event::StreamEnd { recv.on_event(ev, mark); return Ok(()); } // clear anchors before a new document self.anchors.clear(); self.load_document(ev, mark, recv)?; if !multi { break; } } Ok(()) } fn load_document( &mut self, first_ev: Event, mark: Marker, recv: &mut R, ) -> Result<(), ScanError> { if first_ev != Event::DocumentStart { return Err(ScanError::new( mark, "did not find expected ", )); } recv.on_event(first_ev, mark); let (ev, mark) = self.next_token()?; self.load_node(ev, mark, recv)?; // DOCUMENT-END is expected. let (ev, mark) = self.next_token()?; assert_eq!(ev, Event::DocumentEnd); recv.on_event(ev, mark); Ok(()) } fn load_node( &mut self, first_ev: Event, mark: Marker, recv: &mut R, ) -> Result<(), ScanError> { match first_ev { Event::Alias(..) | Event::Scalar(..) => { recv.on_event(first_ev, mark); Ok(()) } Event::SequenceStart(..) => { recv.on_event(first_ev, mark); self.load_sequence(recv) } Event::MappingStart(..) => { recv.on_event(first_ev, mark); self.load_mapping(recv) } _ => { println!("UNREACHABLE EVENT: {first_ev:?}"); unreachable!(); } } } fn load_mapping(&mut self, recv: &mut R) -> Result<(), ScanError> { let (mut key_ev, mut key_mark) = self.next_token()?; while key_ev != Event::MappingEnd { // key self.load_node(key_ev, key_mark, recv)?; // value let (ev, mark) = self.next_token()?; self.load_node(ev, mark, recv)?; // next event let (ev, mark) = self.next_token()?; key_ev = ev; key_mark = mark; } recv.on_event(key_ev, key_mark); Ok(()) } fn load_sequence(&mut self, recv: &mut R) -> Result<(), ScanError> { let (mut ev, mut mark) = self.next_token()?; while ev != Event::SequenceEnd { self.load_node(ev, mark, recv)?; // next event let (next_ev, next_mark) = self.next_token()?; ev = next_ev; mark = next_mark; } recv.on_event(ev, mark); Ok(()) } fn state_machine(&mut self) -> ParseResult { // let next_tok = self.peek_token().cloned()?; // println!("cur_state {:?}, next tok: {:?}", self.state, next_tok); debug_print!("\n\x1B[;33mParser state: {:?} \x1B[;0m", self.state); match self.state { State::StreamStart => self.stream_start(), State::ImplicitDocumentStart => self.document_start(true), State::DocumentStart => self.document_start(false), State::DocumentContent => self.document_content(), State::DocumentEnd => self.document_end(), State::BlockNode => self.parse_node(true, false), // State::BlockNodeOrIndentlessSequence => self.parse_node(true, true), // State::FlowNode => self.parse_node(false, false), State::BlockMappingFirstKey => self.block_mapping_key(true), State::BlockMappingKey => self.block_mapping_key(false), State::BlockMappingValue => self.block_mapping_value(), State::BlockSequenceFirstEntry => self.block_sequence_entry(true), State::BlockSequenceEntry => self.block_sequence_entry(false), State::FlowSequenceFirstEntry => self.flow_sequence_entry(true), State::FlowSequenceEntry => self.flow_sequence_entry(false), State::FlowMappingFirstKey => self.flow_mapping_key(true), State::FlowMappingKey => self.flow_mapping_key(false), State::FlowMappingValue => self.flow_mapping_value(false), State::IndentlessSequenceEntry => self.indentless_sequence_entry(), State::FlowSequenceEntryMappingKey => self.flow_sequence_entry_mapping_key(), State::FlowSequenceEntryMappingValue => self.flow_sequence_entry_mapping_value(), State::FlowSequenceEntryMappingEnd => self.flow_sequence_entry_mapping_end(), State::FlowMappingEmptyValue => self.flow_mapping_value(true), /* impossible */ State::End => unreachable!(), } } fn stream_start(&mut self) -> ParseResult { match *self.peek_token()? { Token(mark, TokenType::StreamStart(_)) => { self.state = State::ImplicitDocumentStart; self.skip(); Ok((Event::StreamStart, mark)) } Token(mark, _) => Err(ScanError::new(mark, "did not find expected ")), } } fn document_start(&mut self, implicit: bool) -> ParseResult { while let TokenType::DocumentEnd = self.peek_token()?.1 { self.skip(); } match *self.peek_token()? { Token(mark, TokenType::StreamEnd) => { self.state = State::End; self.skip(); Ok((Event::StreamEnd, mark)) } Token( _, TokenType::VersionDirective(..) | TokenType::TagDirective(..) | TokenType::DocumentStart, ) => { // explicit document self.explicit_document_start() } Token(mark, _) if implicit => { self.parser_process_directives()?; self.push_state(State::DocumentEnd); self.state = State::BlockNode; Ok((Event::DocumentStart, mark)) } _ => { // explicit document self.explicit_document_start() } } } fn parser_process_directives(&mut self) -> Result<(), ScanError> { let mut version_directive_received = false; loop { let mut tags = HashMap::new(); match self.peek_token()? { Token(mark, TokenType::VersionDirective(_, _)) => { // XXX parsing with warning according to spec //if major != 1 || minor > 2 { // return Err(ScanError::new(tok.0, // "found incompatible YAML document")); //} if version_directive_received { return Err(ScanError::new(*mark, "duplicate version directive")); } version_directive_received = true; } Token(mark, TokenType::TagDirective(handle, prefix)) => { if tags.contains_key(handle) { return Err(ScanError::new(*mark, "the TAG directive must only be given at most once per handle in the same document")); } tags.insert(handle.to_string(), prefix.to_string()); } _ => break, } self.tags = tags; self.skip(); } Ok(()) } fn explicit_document_start(&mut self) -> ParseResult { self.parser_process_directives()?; match *self.peek_token()? { Token(mark, TokenType::DocumentStart) => { self.push_state(State::DocumentEnd); self.state = State::DocumentContent; self.skip(); Ok((Event::DocumentStart, mark)) } Token(mark, _) => Err(ScanError::new( mark, "did not find expected ", )), } } fn document_content(&mut self) -> ParseResult { match *self.peek_token()? { Token( mark, TokenType::VersionDirective(..) | TokenType::TagDirective(..) | TokenType::DocumentStart | TokenType::DocumentEnd | TokenType::StreamEnd, ) => { self.pop_state(); // empty scalar Ok((Event::empty_scalar(), mark)) } _ => self.parse_node(true, false), } } fn document_end(&mut self) -> ParseResult { let mut explicit_end = false; let marker: Marker = match *self.peek_token()? { Token(mark, TokenType::DocumentEnd) => { explicit_end = true; self.skip(); mark } Token(mark, _) => mark, }; if !self.keep_tags { self.tags.clear(); } if explicit_end { self.state = State::ImplicitDocumentStart; } else { if let Token(mark, TokenType::VersionDirective(..) | TokenType::TagDirective(..)) = *self.peek_token()? { return Err(ScanError::new( mark, "missing explicit document end marker before directive", )); } self.state = State::DocumentStart; } Ok((Event::DocumentEnd, marker)) } fn register_anchor(&mut self, name: String, _: &Marker) -> usize { // anchors can be overridden/reused // if self.anchors.contains_key(name) { // return Err(ScanError::new(*mark, // "while parsing anchor, found duplicated anchor")); // } let new_id = self.anchor_id; self.anchor_id += 1; self.anchors.insert(name, new_id); new_id } fn parse_node(&mut self, block: bool, indentless_sequence: bool) -> ParseResult { let mut anchor_id = 0; let mut tag = None; match *self.peek_token()? { Token(_, TokenType::Alias(_)) => { self.pop_state(); if let Token(mark, TokenType::Alias(name)) = self.fetch_token() { match self.anchors.get(&name) { None => { return Err(ScanError::new( mark, "while parsing node, found unknown anchor", )) } Some(id) => return Ok((Event::Alias(*id), mark)), } } unreachable!() } Token(_, TokenType::Anchor(_)) => { if let Token(mark, TokenType::Anchor(name)) = self.fetch_token() { anchor_id = self.register_anchor(name, &mark); if let TokenType::Tag(..) = self.peek_token()?.1 { if let TokenType::Tag(handle, suffix) = self.fetch_token().1 { tag = Some(self.resolve_tag(mark, &handle, suffix)?); } else { unreachable!() } } } else { unreachable!() } } Token(mark, TokenType::Tag(..)) => { if let TokenType::Tag(handle, suffix) = self.fetch_token().1 { tag = Some(self.resolve_tag(mark, &handle, suffix)?); if let TokenType::Anchor(_) = &self.peek_token()?.1 { if let Token(mark, TokenType::Anchor(name)) = self.fetch_token() { anchor_id = self.register_anchor(name, &mark); } else { unreachable!() } } } else { unreachable!() } } _ => {} } match *self.peek_token()? { Token(mark, TokenType::BlockEntry) if indentless_sequence => { self.state = State::IndentlessSequenceEntry; Ok((Event::SequenceStart(anchor_id, tag), mark)) } Token(_, TokenType::Scalar(..)) => { self.pop_state(); if let Token(mark, TokenType::Scalar(style, v)) = self.fetch_token() { Ok((Event::Scalar(v, style, anchor_id, tag), mark)) } else { unreachable!() } } Token(mark, TokenType::FlowSequenceStart) => { self.state = State::FlowSequenceFirstEntry; Ok((Event::SequenceStart(anchor_id, tag), mark)) } Token(mark, TokenType::FlowMappingStart) => { self.state = State::FlowMappingFirstKey; Ok((Event::MappingStart(anchor_id, tag), mark)) } Token(mark, TokenType::BlockSequenceStart) if block => { self.state = State::BlockSequenceFirstEntry; Ok((Event::SequenceStart(anchor_id, tag), mark)) } Token(mark, TokenType::BlockMappingStart) if block => { self.state = State::BlockMappingFirstKey; Ok((Event::MappingStart(anchor_id, tag), mark)) } // ex 7.2, an empty scalar can follow a secondary tag Token(mark, _) if tag.is_some() || anchor_id > 0 => { self.pop_state(); Ok((Event::empty_scalar_with_anchor(anchor_id, tag), mark)) } Token(mark, _) => Err(ScanError::new( mark, "while parsing a node, did not find expected node content", )), } } fn block_mapping_key(&mut self, first: bool) -> ParseResult { // skip BlockMappingStart if first { let _ = self.peek_token()?; //self.marks.push(tok.0); self.skip(); } match *self.peek_token()? { Token(_, TokenType::Key) => { self.skip(); if let Token(mark, TokenType::Key | TokenType::Value | TokenType::BlockEnd) = *self.peek_token()? { self.state = State::BlockMappingValue; // empty scalar Ok((Event::empty_scalar(), mark)) } else { self.push_state(State::BlockMappingValue); self.parse_node(true, true) } } // XXX(chenyh): libyaml failed to parse spec 1.2, ex8.18 Token(mark, TokenType::Value) => { self.state = State::BlockMappingValue; Ok((Event::empty_scalar(), mark)) } Token(mark, TokenType::BlockEnd) => { self.pop_state(); self.skip(); Ok((Event::MappingEnd, mark)) } Token(mark, _) => Err(ScanError::new( mark, "while parsing a block mapping, did not find expected key", )), } } fn block_mapping_value(&mut self) -> ParseResult { match *self.peek_token()? { Token(_, TokenType::Value) => { self.skip(); if let Token(mark, TokenType::Key | TokenType::Value | TokenType::BlockEnd) = *self.peek_token()? { self.state = State::BlockMappingKey; // empty scalar Ok((Event::empty_scalar(), mark)) } else { self.push_state(State::BlockMappingKey); self.parse_node(true, true) } } Token(mark, _) => { self.state = State::BlockMappingKey; // empty scalar Ok((Event::empty_scalar(), mark)) } } } fn flow_mapping_key(&mut self, first: bool) -> ParseResult { if first { let _ = self.peek_token()?; self.skip(); } let marker: Marker = { match *self.peek_token()? { Token(mark, TokenType::FlowMappingEnd) => mark, Token(mark, _) => { if !first { match *self.peek_token()? { Token(_, TokenType::FlowEntry) => self.skip(), Token(mark, _) => return Err(ScanError::new( mark, "while parsing a flow mapping, did not find expected ',' or '}'", )), } } match *self.peek_token()? { Token(_, TokenType::Key) => { self.skip(); if let Token( mark, TokenType::Value | TokenType::FlowEntry | TokenType::FlowMappingEnd, ) = *self.peek_token()? { self.state = State::FlowMappingValue; return Ok((Event::empty_scalar(), mark)); } self.push_state(State::FlowMappingValue); return self.parse_node(false, false); } Token(marker, TokenType::Value) => { self.state = State::FlowMappingValue; return Ok((Event::empty_scalar(), marker)); } Token(_, TokenType::FlowMappingEnd) => (), _ => { self.push_state(State::FlowMappingEmptyValue); return self.parse_node(false, false); } } mark } } }; self.pop_state(); self.skip(); Ok((Event::MappingEnd, marker)) } fn flow_mapping_value(&mut self, empty: bool) -> ParseResult { let mark: Marker = { if empty { let Token(mark, _) = *self.peek_token()?; self.state = State::FlowMappingKey; return Ok((Event::empty_scalar(), mark)); } match *self.peek_token()? { Token(marker, TokenType::Value) => { self.skip(); match self.peek_token()?.1 { TokenType::FlowEntry | TokenType::FlowMappingEnd => {} _ => { self.push_state(State::FlowMappingKey); return self.parse_node(false, false); } } marker } Token(marker, _) => marker, } }; self.state = State::FlowMappingKey; Ok((Event::empty_scalar(), mark)) } fn flow_sequence_entry(&mut self, first: bool) -> ParseResult { // skip FlowMappingStart if first { let _ = self.peek_token()?; //self.marks.push(tok.0); self.skip(); } match *self.peek_token()? { Token(mark, TokenType::FlowSequenceEnd) => { self.pop_state(); self.skip(); return Ok((Event::SequenceEnd, mark)); } Token(_, TokenType::FlowEntry) if !first => { self.skip(); } Token(mark, _) if !first => { return Err(ScanError::new( mark, "while parsing a flow sequence, expected ',' or ']'", )); } _ => { /* next */ } } match *self.peek_token()? { Token(mark, TokenType::FlowSequenceEnd) => { self.pop_state(); self.skip(); Ok((Event::SequenceEnd, mark)) } Token(mark, TokenType::Key) => { self.state = State::FlowSequenceEntryMappingKey; self.skip(); Ok((Event::MappingStart(0, None), mark)) } _ => { self.push_state(State::FlowSequenceEntry); self.parse_node(false, false) } } } fn indentless_sequence_entry(&mut self) -> ParseResult { match *self.peek_token()? { Token(_, TokenType::BlockEntry) => (), Token(mark, _) => { self.pop_state(); return Ok((Event::SequenceEnd, mark)); } } self.skip(); if let Token( mark, TokenType::BlockEntry | TokenType::Key | TokenType::Value | TokenType::BlockEnd, ) = *self.peek_token()? { self.state = State::IndentlessSequenceEntry; Ok((Event::empty_scalar(), mark)) } else { self.push_state(State::IndentlessSequenceEntry); self.parse_node(true, false) } } fn block_sequence_entry(&mut self, first: bool) -> ParseResult { // BLOCK-SEQUENCE-START if first { let _ = self.peek_token()?; //self.marks.push(tok.0); self.skip(); } match *self.peek_token()? { Token(mark, TokenType::BlockEnd) => { self.pop_state(); self.skip(); Ok((Event::SequenceEnd, mark)) } Token(_, TokenType::BlockEntry) => { self.skip(); if let Token(mark, TokenType::BlockEntry | TokenType::BlockEnd) = *self.peek_token()? { self.state = State::BlockSequenceEntry; Ok((Event::empty_scalar(), mark)) } else { self.push_state(State::BlockSequenceEntry); self.parse_node(true, false) } } Token(mark, _) => Err(ScanError::new( mark, "while parsing a block collection, did not find expected '-' indicator", )), } } fn flow_sequence_entry_mapping_key(&mut self) -> ParseResult { if let Token(mark, TokenType::Value | TokenType::FlowEntry | TokenType::FlowSequenceEnd) = *self.peek_token()? { self.skip(); self.state = State::FlowSequenceEntryMappingValue; Ok((Event::empty_scalar(), mark)) } else { self.push_state(State::FlowSequenceEntryMappingValue); self.parse_node(false, false) } } fn flow_sequence_entry_mapping_value(&mut self) -> ParseResult { match *self.peek_token()? { Token(_, TokenType::Value) => { self.skip(); self.state = State::FlowSequenceEntryMappingValue; if let Token(mark, TokenType::FlowEntry | TokenType::FlowSequenceEnd) = *self.peek_token()? { self.state = State::FlowSequenceEntryMappingEnd; Ok((Event::empty_scalar(), mark)) } else { self.push_state(State::FlowSequenceEntryMappingEnd); self.parse_node(false, false) } } Token(mark, _) => { self.state = State::FlowSequenceEntryMappingEnd; Ok((Event::empty_scalar(), mark)) } } } #[allow(clippy::unnecessary_wraps)] fn flow_sequence_entry_mapping_end(&mut self) -> ParseResult { self.state = State::FlowSequenceEntry; Ok((Event::MappingEnd, self.scanner.mark())) } /// Resolve a tag from the handle and the suffix. fn resolve_tag(&self, mark: Marker, handle: &str, suffix: String) -> Result { if handle == "!!" { // "!!" is a shorthand for "tag:yaml.org,2002:". However, that default can be // overridden. match self.tags.get("!!") { Some(prefix) => Ok(Tag { handle: prefix.to_string(), suffix, }), None => Ok(Tag { handle: "tag:yaml.org,2002:".to_string(), suffix, }), } } else if handle.is_empty() && suffix == "!" { // "!" introduces a local tag. Local tags may have their prefix overridden. match self.tags.get("") { Some(prefix) => Ok(Tag { handle: prefix.to_string(), suffix, }), None => Ok(Tag { handle: String::new(), suffix, }), } } else { // Lookup handle in our tag directives. let prefix = self.tags.get(handle); if let Some(prefix) = prefix { Ok(Tag { handle: prefix.to_string(), suffix, }) } else { // Otherwise, it may be a local handle. With a local handle, the handle is set to // "!" and the suffix to whatever follows it ("!foo" -> ("!", "foo")). // If the handle is of the form "!foo!", this cannot be a local handle and we need // to error. if handle.len() >= 2 && handle.starts_with('!') && handle.ends_with('!') { Err(ScanError::new(mark, "the handle wasn't declared")) } else { Ok(Tag { handle: handle.to_string(), suffix, }) } } } } } #[cfg(test)] mod test { use super::{Event, Parser}; use crate::YamlLoader; #[test] fn test_peek_eq_parse() { let s = " a0 bb: val a1: &x b1: 4 b2: d a2: 4 a3: [1, 2, 3] a4: - [a1, a2] - 2 a5: *x "; let mut p = Parser::new_from_str(s); while { let event_peek = p.peek().unwrap().clone(); let event = p.next_token().unwrap(); assert_eq!(event, event_peek); event.0 != Event::StreamEnd } {} } #[test] fn test_keep_tags_across_multiple_documents() { let text = r#" %YAML 1.1 %TAG !t! tag:test,2024: --- !t!1 &1 foo: "bar" --- !t!2 &2 baz: "qux" "#; let mut parser = Parser::new_from_str(text).keep_tags(true); let result = YamlLoader::load_from_parser(&mut parser); assert!(result.is_ok()); let docs = result.unwrap(); assert_eq!(docs.len(), 2); let yaml = &docs[0]; assert_eq!(yaml["foo"].as_str(), Some("bar")); let yaml = &docs[1]; assert_eq!(yaml["baz"].as_str(), Some("qux")); let mut parser = Parser::new_from_str(text).keep_tags(false); let result = YamlLoader::load_from_parser(&mut parser); assert!(result.is_err()); } } yaml-rust2-0.10.1/src/scanner.rs000064400000000000000000002555371046102023000145310ustar 00000000000000//! Home to the YAML Scanner. //! //! The scanner is the lowest-level parsing utility. It is the lexer / tokenizer, reading input a //! character at a time and emitting tokens that can later be interpreted by the [`crate::parser`] //! to check for more context and validity. //! //! Due to the grammar of YAML, the scanner has to have some context and is not error-free. #![allow(clippy::cast_possible_wrap)] #![allow(clippy::cast_sign_loss)] use std::{char, collections::VecDeque, error::Error, fmt}; use arraydeque::ArrayDeque; use crate::char_traits::{ as_hex, is_alpha, is_anchor_char, is_blank, is_blank_or_breakz, is_break, is_breakz, is_digit, is_flow, is_hex, is_tag_char, is_uri_char, is_z, }; /// The encoding of the input. Currently, only UTF-8 is supported. #[derive(Clone, Copy, PartialEq, Debug, Eq)] pub enum TEncoding { /// UTF-8 encoding. Utf8, } /// The style as which the scalar was written in the YAML document. #[derive(Clone, Copy, PartialEq, Debug, Eq)] pub enum TScalarStyle { /// A YAML plain scalar. Plain, /// A YAML single quoted scalar. SingleQuoted, /// A YAML double quoted scalar. DoubleQuoted, /// A YAML literal block (`|` block). Literal, /// A YAML folded block (`>` block). Folded, } /// A location in a yaml document. #[derive(Clone, Copy, PartialEq, Debug, Eq)] pub struct Marker { /// The index (in chars) in the input string. index: usize, /// The line (1-indexed). line: usize, /// The column (1-indexed). col: usize, } impl Marker { fn new(index: usize, line: usize, col: usize) -> Marker { Marker { index, line, col } } /// Return the index (in bytes) of the marker in the source. #[must_use] pub fn index(&self) -> usize { self.index } /// Return the line of the marker in the source. #[must_use] pub fn line(&self) -> usize { self.line } /// Return the column of the marker in the source. #[must_use] pub fn col(&self) -> usize { self.col } } /// An error that occurred while scanning. #[derive(Clone, PartialEq, Debug, Eq)] pub struct ScanError { /// The position at which the error happened in the source. mark: Marker, /// Human-readable details about the error. info: String, } impl ScanError { /// Create a new error from a location and an error string. #[must_use] pub fn new(loc: Marker, info: &str) -> ScanError { ScanError { mark: loc, info: info.to_owned(), } } /// Create a new error from a location and an error string. #[must_use] pub fn new_string(loc: Marker, info: String) -> ScanError { ScanError { mark: loc, info } } /// Return the marker pointing to the error in the source. #[must_use] pub fn marker(&self) -> &Marker { &self.mark } /// Return the information string describing the error that happened. #[must_use] pub fn info(&self) -> &str { self.info.as_ref() } } impl Error for ScanError { fn description(&self) -> &str { self.info.as_ref() } fn cause(&self) -> Option<&dyn Error> { None } } impl fmt::Display for ScanError { // col starts from 0 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { write!( formatter, "{} at byte {} line {} column {}", self.info, self.mark.index, self.mark.line, self.mark.col + 1, ) } } /// The contents of a scanner token. #[derive(Clone, PartialEq, Debug, Eq)] pub enum TokenType { /// The start of the stream. Sent first, before even [`TokenType::DocumentStart`]. StreamStart(TEncoding), /// The end of the stream, EOF. StreamEnd, /// A YAML version directive. VersionDirective( /// Major u32, /// Minor u32, ), /// A YAML tag directive (e.g.: `!!str`, `!foo!bar`, ...). TagDirective( /// Handle String, /// Prefix String, ), /// The start of a YAML document (`---`). DocumentStart, /// The end of a YAML document (`...`). DocumentEnd, /// The start of a sequence block. /// /// Sequence blocks are arrays starting with a `-`. BlockSequenceStart, /// The start of a sequence mapping. /// /// Sequence mappings are "dictionaries" with "key: value" entries. BlockMappingStart, /// End of the corresponding `BlockSequenceStart` or `BlockMappingStart`. BlockEnd, /// Start of an inline array (`[ a, b ]`). FlowSequenceStart, /// End of an inline array. FlowSequenceEnd, /// Start of an inline mapping (`{ a: b, c: d }`). FlowMappingStart, /// End of an inline mapping. FlowMappingEnd, /// An entry in a block sequence (c.f.: [`TokenType::BlockSequenceStart`]). BlockEntry, /// An entry in a flow sequence (c.f.: [`TokenType::FlowSequenceStart`]). FlowEntry, /// A key in a mapping. Key, /// A value in a mapping. Value, /// A reference to an anchor. Alias(String), /// A YAML anchor (`&`/`*`). Anchor(String), /// A YAML tag (starting with bangs `!`). Tag( /// The handle of the tag. String, /// The suffix of the tag. String, ), /// A regular YAML scalar. Scalar(TScalarStyle, String), } /// A scanner token. #[derive(Clone, PartialEq, Debug, Eq)] pub struct Token(pub Marker, pub TokenType); /// A scalar that was parsed and may correspond to a simple key. /// /// Upon scanning the following yaml: /// ```yaml /// a: b /// ``` /// We do not know that `a` is a key for a map until we have reached the following `:`. For this /// YAML, we would store `a` as a scalar token in the [`Scanner`], but not emit it yet. It would be /// kept inside the scanner until more context is fetched and we are able to know whether it is a /// plain scalar or a key. /// /// For example, see the following 2 yaml documents: /// ```yaml /// --- /// a: b # Here, `a` is a key. /// ... /// --- /// a # Here, `a` is a plain scalar. /// ... /// ``` /// An instance of [`SimpleKey`] is created in the [`Scanner`] when such ambiguity occurs. /// /// In both documents, scanning `a` would lead to the creation of a [`SimpleKey`] with /// [`Self::possible`] set to `true`. The token for `a` would be pushed in the [`Scanner`] but not /// yet emitted. Instead, more context would be fetched (through [`Scanner::fetch_more_tokens`]). /// /// In the first document, upon reaching the `:`, the [`SimpleKey`] would be inspected and our /// scalar `a` since it is a possible key, would be "turned" into a key. This is done by prepending /// a [`TokenType::Key`] to our scalar token in the [`Scanner`]. This way, the /// [`crate::parser::Parser`] would read the [`TokenType::Key`] token before the /// [`TokenType::Scalar`] token. /// /// In the second document however, reaching the EOF would stale the [`SimpleKey`] and no /// [`TokenType::Key`] would be emitted by the scanner. #[derive(Clone, PartialEq, Debug, Eq)] struct SimpleKey { /// Whether the token this [`SimpleKey`] refers to may still be a key. /// /// Sometimes, when we have more context, we notice that what we thought could be a key no /// longer can be. In that case, [`Self::possible`] is set to `false`. /// /// For instance, let us consider the following invalid YAML: /// ```yaml /// key /// : value /// ``` /// Upon reading the `\n` after `key`, the [`SimpleKey`] that was created for `key` is staled /// and [`Self::possible`] set to `false`. possible: bool, /// Whether the token this [`SimpleKey`] refers to is required to be a key. /// /// With more context, we may know for sure that the token must be a key. If the YAML is /// invalid, it may happen that the token be deemed not a key. In such event, an error has to /// be raised. This boolean helps us know when to raise such error. /// /// TODO(ethiraric, 30/12/2023): Example of when this happens. required: bool, /// The index of the token referred to by the [`SimpleKey`]. /// /// This is the index in the scanner, which takes into account both the tokens that have been /// emitted and those about to be emitted. See [`Scanner::tokens_parsed`] and /// [`Scanner::tokens`] for more details. token_number: usize, /// The position at which the token the [`SimpleKey`] refers to is. mark: Marker, } impl SimpleKey { /// Create a new [`SimpleKey`] at the given `Marker` and with the given flow level. fn new(mark: Marker) -> SimpleKey { SimpleKey { possible: false, required: false, token_number: 0, mark, } } } /// An indentation level on the stack of indentations. #[derive(Clone, Debug, Default)] struct Indent { /// The former indentation level. indent: isize, /// Whether, upon closing, this indents generates a `BlockEnd` token. /// /// There are levels of indentation which do not start a block. Examples of this would be: /// ```yaml /// - /// foo # ok /// - /// bar # ko, bar needs to be indented further than the `-`. /// - [ /// baz, # ok /// quux # ko, quux needs to be indented further than the '-'. /// ] # ko, the closing bracket needs to be indented further than the `-`. /// ``` /// /// The indentation level created by the `-` is for a single entry in the sequence. Emitting a /// `BlockEnd` when this indentation block ends would generate one `BlockEnd` per entry in the /// sequence, although we must have exactly one to end the sequence. needs_block_end: bool, } /// The size of the [`Scanner`] buffer. /// /// The buffer is statically allocated to avoid conditions for reallocations each time we /// consume/push a character. As of now, almost all lookaheads are 4 characters maximum, except: /// - Escape sequences parsing: some escape codes are 8 characters /// - Scanning indent in scalars: this looks ahead `indent + 2` characters /// /// This constant must be set to at least 8. When scanning indent in scalars, the lookahead is done /// in a single call if and only if the indent is `BUFFER_LEN - 2` or less. If the indent is higher /// than that, the code will fall back to a loop of lookaheads. const BUFFER_LEN: usize = 16; /// The YAML scanner. /// /// This corresponds to the low-level interface when reading YAML. The scanner emits token as they /// are read (akin to a lexer), but it also holds sufficient context to be able to disambiguate /// some of the constructs. It has understanding of indentation and whitespace and is able to /// generate error messages for some invalid YAML constructs. /// /// It is however not a full parser and needs [`crate::parser::Parser`] to fully detect invalid /// YAML documents. #[derive(Debug)] #[allow(clippy::struct_excessive_bools)] pub struct Scanner { /// The reader, providing with characters. rdr: T, /// The position of the cursor within the reader. mark: Marker, /// Buffer for tokens to be returned. /// /// This buffer can hold some temporary tokens that are not yet ready to be returned. For /// instance, if we just read a scalar, it can be a value or a key if an implicit mapping /// follows. In this case, the token stays in the `VecDeque` but cannot be returned from /// [`Self::next`] until we have more context. tokens: VecDeque, /// Buffer for the next characters to consume. buffer: ArrayDeque, /// The last error that happened. error: Option, /// Whether we have already emitted the `StreamStart` token. stream_start_produced: bool, /// Whether we have already emitted the `StreamEnd` token. stream_end_produced: bool, /// In some flow contexts, the value of a mapping is allowed to be adjacent to the `:`. When it /// is, the index at which the `:` may be must be stored in `adjacent_value_allowed_at`. adjacent_value_allowed_at: usize, /// Whether a simple key could potentially start at the current position. /// /// Simple keys are the opposite of complex keys which are keys starting with `?`. simple_key_allowed: bool, /// A stack of potential simple keys. /// /// Refer to the documentation of [`SimpleKey`] for a more in-depth explanation of what they /// are. simple_keys: Vec, /// The current indentation level. indent: isize, /// List of all block indentation levels we are in (except the current one). indents: Vec, /// Level of nesting of flow sequences. flow_level: u8, /// The number of tokens that have been returned from the scanner. /// /// This excludes the tokens from [`Self::tokens`]. tokens_parsed: usize, /// Whether a token is ready to be taken from [`Self::tokens`]. token_available: bool, /// Whether all characters encountered since the last newline were whitespace. leading_whitespace: bool, /// Whether we started a flow mapping. /// /// This is used to detect implicit flow mapping starts such as: /// ```yaml /// [ : foo ] # { null: "foo" } /// ``` flow_mapping_started: bool, /// Whether we currently are in an implicit flow mapping. implicit_flow_mapping: bool, } impl> Iterator for Scanner { type Item = Token; fn next(&mut self) -> Option { if self.error.is_some() { return None; } match self.next_token() { Ok(Some(tok)) => { debug_print!( " \x1B[;32m\u{21B3} {:?} \x1B[;36m{:?}\x1B[;m", tok.1, tok.0 ); Some(tok) } Ok(tok) => tok, Err(e) => { self.error = Some(e); None } } } } /// A convenience alias for scanner functions that may fail without returning a value. pub type ScanResult = Result<(), ScanError>; impl> Scanner { /// Creates the YAML tokenizer. pub fn new(rdr: T) -> Scanner { Scanner { rdr, buffer: ArrayDeque::new(), mark: Marker::new(0, 1, 0), tokens: VecDeque::new(), error: None, stream_start_produced: false, stream_end_produced: false, adjacent_value_allowed_at: 0, simple_key_allowed: true, simple_keys: Vec::new(), indent: -1, indents: Vec::new(), flow_level: 0, tokens_parsed: 0, token_available: false, leading_whitespace: true, flow_mapping_started: false, implicit_flow_mapping: false, } } /// Get a copy of the last error that was encountered, if any. /// /// This does not clear the error state and further calls to [`Self::get_error`] will return (a /// clone of) the same error. #[inline] pub fn get_error(&self) -> Option { self.error.clone() } /// Fill `self.buffer` with at least `count` characters. /// /// The characters that are extracted this way are not consumed but only placed in the buffer. #[inline] fn lookahead(&mut self, count: usize) { if self.buffer.len() >= count { return; } for _ in 0..(count - self.buffer.len()) { self.buffer .push_back(self.rdr.next().unwrap_or('\0')) .unwrap(); } } /// Consume the next character. It is assumed the next character is a blank. #[inline] fn skip_blank(&mut self) { self.buffer.pop_front(); self.mark.index += 1; self.mark.col += 1; } /// Consume the next character. It is assumed the next character is not a blank. #[inline] fn skip_non_blank(&mut self) { self.buffer.pop_front(); self.mark.index += 1; self.mark.col += 1; self.leading_whitespace = false; } /// Consume the next characters. It is assumed none of the next characters are blanks. #[inline] fn skip_n_non_blank(&mut self, n: usize) { self.buffer.drain(0..n); self.mark.index += n; self.mark.col += n; self.leading_whitespace = false; } /// Consume the next character. It is assumed the next character is a newline. #[inline] fn skip_nl(&mut self) { self.buffer.pop_front(); self.mark.index += 1; self.mark.col = 0; self.mark.line += 1; self.leading_whitespace = true; } /// Consume a linebreak (either CR, LF or CRLF), if any. Do nothing if there's none. #[inline] fn skip_linebreak(&mut self) { if self.buffer[0] == '\r' && self.buffer[1] == '\n' { // While technically not a blank, this does not matter as `self.leading_whitespace` // will be reset by `skip_nl`. self.skip_blank(); self.skip_nl(); } else if is_break(self.buffer[0]) { self.skip_nl(); } } /// Return the next character in the buffer. /// /// The character is not consumed. #[inline] fn ch(&self) -> char { self.buffer[0] } /// Look for the next character and return it. /// /// The character is not consumed. /// Equivalent to calling [`Self::lookahead`] and [`Self::ch`]. #[inline] fn look_ch(&mut self) -> char { self.lookahead(1); self.ch() } /// Read a character from the input stream, returning it directly. /// /// The buffer is bypassed and `self.mark` needs to be updated manually. #[inline] #[must_use] fn raw_read_ch(&mut self) -> char { self.rdr.next().unwrap_or('\0') } /// Return whether the next character is `c`. #[inline] fn ch_is(&self, c: char) -> bool { self.buffer[0] == c } /// Return whether the [`TokenType::StreamStart`] event has been emitted. #[inline] pub fn stream_started(&self) -> bool { self.stream_start_produced } /// Return whether the [`TokenType::StreamEnd`] event has been emitted. #[inline] pub fn stream_ended(&self) -> bool { self.stream_end_produced } /// Get the current position in the input stream. #[inline] pub fn mark(&self) -> Marker { self.mark } // Read and consume a line break (either `\r`, `\n` or `\r\n`). // // A `\n` is pushed into `s`. // // # Panics (in debug) // If the next characters do not correspond to a line break. #[inline] fn read_break(&mut self, s: &mut String) { let c = self.buffer[0]; let nc = self.buffer[1]; debug_assert!(is_break(c)); if c == '\r' && nc == '\n' { self.skip_blank(); } self.skip_nl(); s.push('\n'); } /// Check whether the next characters correspond to an end of document. /// /// [`Self::lookahead`] must have been called before calling this function. fn next_is_document_end(&self) -> bool { assert!(self.buffer.len() >= 4); self.buffer[0] == '.' && self.buffer[1] == '.' && self.buffer[2] == '.' && is_blank_or_breakz(self.buffer[3]) } /// Check whether the next characters correspond to a document indicator. /// /// [`Self::lookahead`] must have been called before calling this function. #[inline] fn next_is_document_indicator(&self) -> bool { assert!(self.buffer.len() >= 4); self.mark.col == 0 && (((self.buffer[0] == '-') && (self.buffer[1] == '-') && (self.buffer[2] == '-')) || ((self.buffer[0] == '.') && (self.buffer[1] == '.') && (self.buffer[2] == '.'))) && is_blank_or_breakz(self.buffer[3]) } /// Insert a token at the given position. fn insert_token(&mut self, pos: usize, tok: Token) { let old_len = self.tokens.len(); assert!(pos <= old_len); self.tokens.insert(pos, tok); } fn allow_simple_key(&mut self) { self.simple_key_allowed = true; } fn disallow_simple_key(&mut self) { self.simple_key_allowed = false; } /// Fetch the next token in the stream. /// # Errors /// Returns `ScanError` when the scanner does not find the next expected token. pub fn fetch_next_token(&mut self) -> ScanResult { self.lookahead(1); // eprintln!("--> fetch_next_token Cur {:?} {:?}", self.mark, self.ch()); if !self.stream_start_produced { self.fetch_stream_start(); return Ok(()); } self.skip_to_next_token()?; debug_print!( " \x1B[38;5;244m\u{2192} fetch_next_token after whitespace {:?} {:?}\x1B[m", self.mark, self.ch() ); self.stale_simple_keys()?; let mark = self.mark; self.unroll_indent(mark.col as isize); self.lookahead(4); if is_z(self.ch()) { self.fetch_stream_end()?; return Ok(()); } // Is it a directive? if self.mark.col == 0 && self.ch_is('%') { return self.fetch_directive(); } if self.mark.col == 0 && self.buffer[0] == '-' && self.buffer[1] == '-' && self.buffer[2] == '-' && is_blank_or_breakz(self.buffer[3]) { self.fetch_document_indicator(TokenType::DocumentStart)?; return Ok(()); } if self.mark.col == 0 && self.buffer[0] == '.' && self.buffer[1] == '.' && self.buffer[2] == '.' && is_blank_or_breakz(self.buffer[3]) { self.fetch_document_indicator(TokenType::DocumentEnd)?; self.skip_ws_to_eol(SkipTabs::Yes)?; if !is_breakz(self.ch()) { return Err(ScanError::new( self.mark, "invalid content after document end marker", )); } return Ok(()); } if (self.mark.col as isize) < self.indent { return Err(ScanError::new(self.mark, "invalid indentation")); } let c = self.buffer[0]; let nc = self.buffer[1]; match c { '[' => self.fetch_flow_collection_start(TokenType::FlowSequenceStart), '{' => self.fetch_flow_collection_start(TokenType::FlowMappingStart), ']' => self.fetch_flow_collection_end(TokenType::FlowSequenceEnd), '}' => self.fetch_flow_collection_end(TokenType::FlowMappingEnd), ',' => self.fetch_flow_entry(), '-' if is_blank_or_breakz(nc) => self.fetch_block_entry(), '?' if is_blank_or_breakz(nc) => self.fetch_key(), ':' if is_blank_or_breakz(nc) || (self.flow_level > 0 && (is_flow(nc) || self.mark.index == self.adjacent_value_allowed_at)) => { self.fetch_value() } // Is it an alias? '*' => self.fetch_anchor(true), // Is it an anchor? '&' => self.fetch_anchor(false), '!' => self.fetch_tag(), // Is it a literal scalar? '|' if self.flow_level == 0 => self.fetch_block_scalar(true), // Is it a folded scalar? '>' if self.flow_level == 0 => self.fetch_block_scalar(false), '\'' => self.fetch_flow_scalar(true), '"' => self.fetch_flow_scalar(false), // plain scalar '-' if !is_blank_or_breakz(nc) => self.fetch_plain_scalar(), ':' | '?' if !is_blank_or_breakz(nc) && self.flow_level == 0 => { self.fetch_plain_scalar() } '%' | '@' | '`' => Err(ScanError::new( self.mark, &format!("unexpected character: `{c}'"), )), _ => self.fetch_plain_scalar(), } } /// Return the next token in the stream. /// # Errors /// Returns `ScanError` when scanning fails to find an expected next token. pub fn next_token(&mut self) -> Result, ScanError> { if self.stream_end_produced { return Ok(None); } if !self.token_available { self.fetch_more_tokens()?; } let Some(t) = self.tokens.pop_front() else { return Err(ScanError::new( self.mark, "did not find expected next token", )); }; self.token_available = false; self.tokens_parsed += 1; if let TokenType::StreamEnd = t.1 { self.stream_end_produced = true; } Ok(Some(t)) } /// Fetch tokens from the token stream. /// # Errors /// Returns `ScanError` when loading fails. pub fn fetch_more_tokens(&mut self) -> ScanResult { let mut need_more; loop { if self.tokens.is_empty() { need_more = true; } else { need_more = false; // Stale potential keys that we know won't be keys. self.stale_simple_keys()?; // If our next token to be emitted may be a key, fetch more context. for sk in &self.simple_keys { if sk.possible && sk.token_number == self.tokens_parsed { need_more = true; break; } } } if !need_more { break; } self.fetch_next_token()?; } self.token_available = true; Ok(()) } /// Mark simple keys that can no longer be keys as such. /// /// This function sets `possible` to `false` to each key that, now we have more context, we /// know will not be keys. /// /// # Errors /// This function returns an error if one of the key we would stale was required to be a key. fn stale_simple_keys(&mut self) -> ScanResult { for sk in &mut self.simple_keys { if sk.possible // If not in a flow construct, simple keys cannot span multiple lines. && self.flow_level == 0 && (sk.mark.line < self.mark.line || sk.mark.index + 1024 < self.mark.index) { if sk.required { return Err(ScanError::new(self.mark, "simple key expect ':'")); } sk.possible = false; } } Ok(()) } /// Skip over all whitespace and comments until the next token. /// /// # Errors /// This function returns an error if a tabulation is encountered where there should not be /// one. fn skip_to_next_token(&mut self) -> ScanResult { loop { // TODO(chenyh) BOM match self.look_ch() { // Tabs may not be used as indentation. // "Indentation" only exists as long as a block is started, but does not exist // inside of flow-style constructs. Tabs are allowed as part of leading // whitespaces outside of indentation. // If a flow-style construct is in an indented block, its contents must still be // indented. Also, tabs are allowed anywhere in it if it has no content. '\t' if self.is_within_block() && self.leading_whitespace && (self.mark.col as isize) < self.indent => { self.skip_ws_to_eol(SkipTabs::Yes)?; // If we have content on that line with a tab, return an error. if !is_breakz(self.ch()) { return Err(ScanError::new( self.mark, "tabs disallowed within this context (block indentation)", )); } } '\t' | ' ' => self.skip_blank(), '\n' | '\r' => { self.lookahead(2); self.skip_linebreak(); if self.flow_level == 0 { self.allow_simple_key(); } } '#' => { while !is_breakz(self.look_ch()) { self.skip_non_blank(); } } _ => break, } } Ok(()) } /// Skip over YAML whitespace (` `, `\n`, `\r`). /// /// # Errors /// This function returns an error if no whitespace was found. fn skip_yaml_whitespace(&mut self) -> ScanResult { let mut need_whitespace = true; loop { match self.look_ch() { ' ' => { self.skip_blank(); need_whitespace = false; } '\n' | '\r' => { self.lookahead(2); self.skip_linebreak(); if self.flow_level == 0 { self.allow_simple_key(); } need_whitespace = false; } '#' => { while !is_breakz(self.look_ch()) { self.skip_non_blank(); } } _ => break, } } if need_whitespace { Err(ScanError::new(self.mark(), "expected whitespace")) } else { Ok(()) } } /// Skip yaml whitespace at most up to eol. Also skips comments. fn skip_ws_to_eol(&mut self, skip_tabs: SkipTabs) -> Result { let mut encountered_tab = false; let mut has_yaml_ws = false; loop { match self.look_ch() { ' ' => { has_yaml_ws = true; self.skip_blank(); } '\t' if skip_tabs != SkipTabs::No => { encountered_tab = true; self.skip_blank(); } // YAML comments must be preceded by whitespace. '#' if !encountered_tab && !has_yaml_ws => { return Err(ScanError::new( self.mark, "comments must be separated from other tokens by whitespace", )); } '#' => { while !is_breakz(self.look_ch()) { self.skip_non_blank(); } } _ => break, } } Ok(SkipTabs::Result(encountered_tab, has_yaml_ws)) } fn fetch_stream_start(&mut self) { let mark = self.mark; self.indent = -1; self.stream_start_produced = true; self.allow_simple_key(); self.tokens .push_back(Token(mark, TokenType::StreamStart(TEncoding::Utf8))); self.simple_keys.push(SimpleKey::new(Marker::new(0, 0, 0))); } fn fetch_stream_end(&mut self) -> ScanResult { // force new line if self.mark.col != 0 { self.mark.col = 0; self.mark.line += 1; } // If the stream ended, we won't have more context. We can stall all the simple keys we // had. If one was required, however, that was an error and we must propagate it. for sk in &mut self.simple_keys { if sk.required && sk.possible { return Err(ScanError::new(self.mark, "simple key expected")); } sk.possible = false; } self.unroll_indent(-1); self.remove_simple_key()?; self.disallow_simple_key(); self.tokens .push_back(Token(self.mark, TokenType::StreamEnd)); Ok(()) } fn fetch_directive(&mut self) -> ScanResult { self.unroll_indent(-1); self.remove_simple_key()?; self.disallow_simple_key(); let tok = self.scan_directive()?; self.tokens.push_back(tok); Ok(()) } fn scan_directive(&mut self) -> Result { let start_mark = self.mark; self.skip_non_blank(); let name = self.scan_directive_name()?; let tok = match name.as_ref() { "YAML" => self.scan_version_directive_value(&start_mark)?, "TAG" => self.scan_tag_directive_value(&start_mark)?, // XXX This should be a warning instead of an error _ => { // skip current line while !is_breakz(self.look_ch()) { self.skip_non_blank(); } // XXX return an empty TagDirective token Token( start_mark, TokenType::TagDirective(String::new(), String::new()), ) // return Err(ScanError::new(start_mark, // "while scanning a directive, found unknown directive name")) } }; self.skip_ws_to_eol(SkipTabs::Yes)?; if is_breakz(self.ch()) { self.lookahead(2); self.skip_linebreak(); Ok(tok) } else { Err(ScanError::new( start_mark, "while scanning a directive, did not find expected comment or line break", )) } } fn scan_version_directive_value(&mut self, mark: &Marker) -> Result { while is_blank(self.look_ch()) { self.skip_blank(); } let major = self.scan_version_directive_number(mark)?; if self.ch() != '.' { return Err(ScanError::new( *mark, "while scanning a YAML directive, did not find expected digit or '.' character", )); } self.skip_non_blank(); let minor = self.scan_version_directive_number(mark)?; Ok(Token(*mark, TokenType::VersionDirective(major, minor))) } fn scan_directive_name(&mut self) -> Result { let start_mark = self.mark; let mut string = String::new(); while is_alpha(self.look_ch()) { string.push(self.ch()); self.skip_non_blank(); } if string.is_empty() { return Err(ScanError::new( start_mark, "while scanning a directive, could not find expected directive name", )); } if !is_blank_or_breakz(self.ch()) { return Err(ScanError::new( start_mark, "while scanning a directive, found unexpected non-alphabetical character", )); } Ok(string) } fn scan_version_directive_number(&mut self, mark: &Marker) -> Result { let mut val = 0u32; let mut length = 0usize; while let Some(digit) = self.look_ch().to_digit(10) { if length + 1 > 9 { return Err(ScanError::new( *mark, "while scanning a YAML directive, found extremely long version number", )); } length += 1; val = val * 10 + digit; self.skip_non_blank(); } if length == 0 { return Err(ScanError::new( *mark, "while scanning a YAML directive, did not find expected version number", )); } Ok(val) } fn scan_tag_directive_value(&mut self, mark: &Marker) -> Result { /* Eat whitespaces. */ while is_blank(self.look_ch()) { self.skip_blank(); } let handle = self.scan_tag_handle(true, mark)?; /* Eat whitespaces. */ while is_blank(self.look_ch()) { self.skip_blank(); } let prefix = self.scan_tag_prefix(mark)?; self.lookahead(1); if is_blank_or_breakz(self.ch()) { Ok(Token(*mark, TokenType::TagDirective(handle, prefix))) } else { Err(ScanError::new( *mark, "while scanning TAG, did not find expected whitespace or line break", )) } } fn fetch_tag(&mut self) -> ScanResult { self.save_simple_key(); self.disallow_simple_key(); let tok = self.scan_tag()?; self.tokens.push_back(tok); Ok(()) } fn scan_tag(&mut self) -> Result { let start_mark = self.mark; let mut handle = String::new(); let mut suffix; // Check if the tag is in the canonical form (verbatim). self.lookahead(2); if self.buffer[1] == '<' { suffix = self.scan_verbatim_tag(&start_mark)?; } else { // The tag has either the '!suffix' or the '!handle!suffix' handle = self.scan_tag_handle(false, &start_mark)?; // Check if it is, indeed, handle. if handle.len() >= 2 && handle.starts_with('!') && handle.ends_with('!') { // A tag handle starting with "!!" is a secondary tag handle. let is_secondary_handle = handle == "!!"; suffix = self.scan_tag_shorthand_suffix(false, is_secondary_handle, "", &start_mark)?; } else { suffix = self.scan_tag_shorthand_suffix(false, false, &handle, &start_mark)?; "!".clone_into(&mut handle); // A special case: the '!' tag. Set the handle to '' and the // suffix to '!'. if suffix.is_empty() { handle.clear(); suffix = "!".to_owned(); } } } if is_blank_or_breakz(self.look_ch()) || (self.flow_level > 0 && is_flow(self.ch())) { // XXX: ex 7.2, an empty scalar can follow a secondary tag Ok(Token(start_mark, TokenType::Tag(handle, suffix))) } else { Err(ScanError::new( start_mark, "while scanning a tag, did not find expected whitespace or line break", )) } } fn scan_tag_handle(&mut self, directive: bool, mark: &Marker) -> Result { let mut string = String::new(); if self.look_ch() != '!' { return Err(ScanError::new( *mark, "while scanning a tag, did not find expected '!'", )); } string.push(self.ch()); self.skip_non_blank(); while is_alpha(self.look_ch()) { string.push(self.ch()); self.skip_non_blank(); } // Check if the trailing character is '!' and copy it. if self.ch() == '!' { string.push(self.ch()); self.skip_non_blank(); } else if directive && string != "!" { // It's either the '!' tag or not really a tag handle. If it's a %TAG // directive, it's an error. If it's a tag token, it must be a part of // URI. return Err(ScanError::new( *mark, "while parsing a tag directive, did not find expected '!'", )); } Ok(string) } /// Scan for a tag prefix (6.8.2.2). /// /// There are 2 kinds of tag prefixes: /// - Local: Starts with a `!`, contains only URI chars (`!foo`) /// - Global: Starts with a tag char, contains then URI chars (`!foo,2000:app/`) fn scan_tag_prefix(&mut self, start_mark: &Marker) -> Result { let mut string = String::new(); if self.look_ch() == '!' { // If we have a local tag, insert and skip `!`. string.push(self.ch()); self.skip_non_blank(); } else if !is_tag_char(self.ch()) { // Otherwise, check if the first global tag character is valid. return Err(ScanError::new(*start_mark, "invalid global tag character")); } else if self.ch() == '%' { // If it is valid and an escape sequence, escape it. string.push(self.scan_uri_escapes(start_mark)?); } else { // Otherwise, push the first character. string.push(self.ch()); self.skip_non_blank(); } while is_uri_char(self.look_ch()) { if self.ch() == '%' { string.push(self.scan_uri_escapes(start_mark)?); } else { string.push(self.ch()); self.skip_non_blank(); } } Ok(string) } /// Scan for a verbatim tag. /// /// The prefixing `!<` must _not_ have been skipped. fn scan_verbatim_tag(&mut self, start_mark: &Marker) -> Result { // Eat `!<` self.skip_non_blank(); self.skip_non_blank(); let mut string = String::new(); while is_uri_char(self.look_ch()) { if self.ch() == '%' { string.push(self.scan_uri_escapes(start_mark)?); } else { string.push(self.ch()); self.skip_non_blank(); } } if self.ch() != '>' { return Err(ScanError::new( *start_mark, "while scanning a verbatim tag, did not find the expected '>'", )); } self.skip_non_blank(); Ok(string) } fn scan_tag_shorthand_suffix( &mut self, _directive: bool, _is_secondary: bool, head: &str, mark: &Marker, ) -> Result { let mut length = head.len(); let mut string = String::new(); // Copy the head if needed. // Note that we don't copy the leading '!' character. if length > 1 { string.extend(head.chars().skip(1)); } while is_tag_char(self.look_ch()) { // Check if it is a URI-escape sequence. if self.ch() == '%' { string.push(self.scan_uri_escapes(mark)?); } else { string.push(self.ch()); self.skip_non_blank(); } length += 1; } if length == 0 { return Err(ScanError::new( *mark, "while parsing a tag, did not find expected tag URI", )); } Ok(string) } fn scan_uri_escapes(&mut self, mark: &Marker) -> Result { let mut width = 0usize; let mut code = 0u32; loop { self.lookahead(3); if !(self.ch() == '%' && is_hex(self.buffer[1]) && is_hex(self.buffer[2])) { return Err(ScanError::new( *mark, "while parsing a tag, did not find URI escaped octet", )); } let octet = (as_hex(self.buffer[1]) << 4) + as_hex(self.buffer[2]); if width == 0 { width = match octet { _ if octet & 0x80 == 0x00 => 1, _ if octet & 0xE0 == 0xC0 => 2, _ if octet & 0xF0 == 0xE0 => 3, _ if octet & 0xF8 == 0xF0 => 4, _ => { return Err(ScanError::new( *mark, "while parsing a tag, found an incorrect leading UTF-8 octet", )); } }; code = octet; } else { if octet & 0xc0 != 0x80 { return Err(ScanError::new( *mark, "while parsing a tag, found an incorrect trailing UTF-8 octet", )); } code = (code << 8) + octet; } self.skip_n_non_blank(3); width -= 1; if width == 0 { break; } } match char::from_u32(code) { Some(ch) => Ok(ch), None => Err(ScanError::new( *mark, "while parsing a tag, found an invalid UTF-8 codepoint", )), } } fn fetch_anchor(&mut self, alias: bool) -> ScanResult { self.save_simple_key(); self.disallow_simple_key(); let tok = self.scan_anchor(alias)?; self.tokens.push_back(tok); Ok(()) } fn scan_anchor(&mut self, alias: bool) -> Result { let mut string = String::new(); let start_mark = self.mark; self.skip_non_blank(); while is_anchor_char(self.look_ch()) { string.push(self.ch()); self.skip_non_blank(); } if string.is_empty() { return Err(ScanError::new(start_mark, "while scanning an anchor or alias, did not find expected alphabetic or numeric character")); } if alias { Ok(Token(start_mark, TokenType::Alias(string))) } else { Ok(Token(start_mark, TokenType::Anchor(string))) } } fn fetch_flow_collection_start(&mut self, tok: TokenType) -> ScanResult { // The indicators '[' and '{' may start a simple key. self.save_simple_key(); self.roll_one_col_indent(); self.increase_flow_level()?; self.allow_simple_key(); let start_mark = self.mark; self.skip_non_blank(); if tok == TokenType::FlowMappingStart { self.flow_mapping_started = true; } self.skip_ws_to_eol(SkipTabs::Yes)?; self.tokens.push_back(Token(start_mark, tok)); Ok(()) } fn fetch_flow_collection_end(&mut self, tok: TokenType) -> ScanResult { self.remove_simple_key()?; self.decrease_flow_level(); self.disallow_simple_key(); self.end_implicit_mapping(self.mark); let start_mark = self.mark; self.skip_non_blank(); self.skip_ws_to_eol(SkipTabs::Yes)?; // A flow collection within a flow mapping can be a key. In that case, the value may be // adjacent to the `:`. // ```yaml // - [ {a: b}:value ] // ``` if self.flow_level > 0 { self.adjacent_value_allowed_at = self.mark.index; } self.tokens.push_back(Token(start_mark, tok)); Ok(()) } /// Push the `FlowEntry` token and skip over the `,`. fn fetch_flow_entry(&mut self) -> ScanResult { self.remove_simple_key()?; self.allow_simple_key(); self.end_implicit_mapping(self.mark); let start_mark = self.mark; self.skip_non_blank(); self.skip_ws_to_eol(SkipTabs::Yes)?; self.tokens .push_back(Token(start_mark, TokenType::FlowEntry)); Ok(()) } fn increase_flow_level(&mut self) -> ScanResult { self.simple_keys.push(SimpleKey::new(Marker::new(0, 0, 0))); self.flow_level = self .flow_level .checked_add(1) .ok_or_else(|| ScanError::new(self.mark, "recursion limit exceeded"))?; Ok(()) } fn decrease_flow_level(&mut self) { if self.flow_level > 0 { self.flow_level -= 1; self.simple_keys.pop().unwrap(); } } /// Push the `Block*` token(s) and skip over the `-`. /// /// Add an indentation level and push a `BlockSequenceStart` token if needed, then push a /// `BlockEntry` token. /// This function only skips over the `-` and does not fetch the entry value. fn fetch_block_entry(&mut self) -> ScanResult { if self.flow_level > 0 { // - * only allowed in block return Err(ScanError::new( self.mark, r#""-" is only valid inside a block"#, )); } // Check if we are allowed to start a new entry. if !self.simple_key_allowed { return Err(ScanError::new( self.mark, "block sequence entries are not allowed in this context", )); } // ???, fixes test G9HC. if let Some(Token(mark, TokenType::Anchor(..) | TokenType::Tag(..))) = self.tokens.back() { if self.mark.col == 0 && mark.col == 0 && self.indent > -1 { return Err(ScanError::new(*mark, "invalid indentation for anchor")); } } // Skip over the `-`. let mark = self.mark; self.skip_non_blank(); // generate BLOCK-SEQUENCE-START if indented self.roll_indent(mark.col, None, TokenType::BlockSequenceStart, mark); let found_tabs = self.skip_ws_to_eol(SkipTabs::Yes)?.found_tabs(); self.lookahead(2); if found_tabs && self.buffer[0] == '-' && is_blank_or_breakz(self.buffer[1]) { return Err(ScanError::new( self.mark, "'-' must be followed by a valid YAML whitespace", )); } self.skip_ws_to_eol(SkipTabs::No)?; if is_break(self.look_ch()) || is_flow(self.ch()) { self.roll_one_col_indent(); } self.remove_simple_key()?; self.allow_simple_key(); self.tokens .push_back(Token(self.mark, TokenType::BlockEntry)); Ok(()) } fn fetch_document_indicator(&mut self, t: TokenType) -> ScanResult { self.unroll_indent(-1); self.remove_simple_key()?; self.disallow_simple_key(); let mark = self.mark; self.skip_n_non_blank(3); self.tokens.push_back(Token(mark, t)); Ok(()) } fn fetch_block_scalar(&mut self, literal: bool) -> ScanResult { self.save_simple_key(); self.allow_simple_key(); let tok = self.scan_block_scalar(literal)?; self.tokens.push_back(tok); Ok(()) } #[allow(clippy::too_many_lines)] fn scan_block_scalar(&mut self, literal: bool) -> Result { let start_mark = self.mark; let mut chomping = Chomping::Clip; let mut increment: usize = 0; let mut indent: usize = 0; let mut trailing_blank: bool; let mut leading_blank: bool = false; let style = if literal { TScalarStyle::Literal } else { TScalarStyle::Folded }; let mut string = String::new(); let mut leading_break = String::new(); let mut trailing_breaks = String::new(); let mut chomping_break = String::new(); // skip '|' or '>' self.skip_non_blank(); self.unroll_non_block_indents(); if self.look_ch() == '+' || self.ch() == '-' { if self.ch() == '+' { chomping = Chomping::Keep; } else { chomping = Chomping::Strip; } self.skip_non_blank(); if is_digit(self.look_ch()) { if self.ch() == '0' { return Err(ScanError::new( start_mark, "while scanning a block scalar, found an indentation indicator equal to 0", )); } increment = (self.ch() as usize) - ('0' as usize); self.skip_non_blank(); } } else if is_digit(self.ch()) { if self.ch() == '0' { return Err(ScanError::new( start_mark, "while scanning a block scalar, found an indentation indicator equal to 0", )); } increment = (self.ch() as usize) - ('0' as usize); self.skip_non_blank(); self.lookahead(1); if self.ch() == '+' || self.ch() == '-' { if self.ch() == '+' { chomping = Chomping::Keep; } else { chomping = Chomping::Strip; } self.skip_non_blank(); } } self.skip_ws_to_eol(SkipTabs::Yes)?; // Check if we are at the end of the line. if !is_breakz(self.look_ch()) { return Err(ScanError::new( start_mark, "while scanning a block scalar, did not find expected comment or line break", )); } if is_break(self.ch()) { self.lookahead(2); self.read_break(&mut chomping_break); } if self.look_ch() == '\t' { return Err(ScanError::new( start_mark, "a block scalar content cannot start with a tab", )); } if increment > 0 { indent = if self.indent >= 0 { (self.indent + increment as isize) as usize } else { increment } } // Scan the leading line breaks and determine the indentation level if needed. if indent == 0 { self.skip_block_scalar_first_line_indent(&mut indent, &mut trailing_breaks); } else { self.skip_block_scalar_indent(indent, &mut trailing_breaks); } // We have an end-of-stream with no content, e.g.: // ```yaml // - |+ // ``` if is_z(self.ch()) { let contents = match chomping { // We strip trailing linebreaks. Nothing remain. Chomping::Strip => String::new(), // There was no newline after the chomping indicator. _ if self.mark.line == start_mark.line() => String::new(), // We clip lines, and there was a newline after the chomping indicator. // All other breaks are ignored. Chomping::Clip => chomping_break, // We keep lines. There was a newline after the chomping indicator but nothing // else. Chomping::Keep if trailing_breaks.is_empty() => chomping_break, // Otherwise, the newline after chomping is ignored. Chomping::Keep => trailing_breaks, }; return Ok(Token(start_mark, TokenType::Scalar(style, contents))); } if self.mark.col < indent && (self.mark.col as isize) > self.indent { return Err(ScanError::new( self.mark, "wrongly indented line in block scalar", )); } let mut line_buffer = String::with_capacity(100); let start_mark = self.mark; while self.mark.col == indent && !is_z(self.ch()) { if indent == 0 { self.lookahead(4); if self.next_is_document_end() { break; } } // We are at the first content character of a content line. trailing_blank = is_blank(self.ch()); if !literal && !leading_break.is_empty() && !leading_blank && !trailing_blank { string.push_str(&trailing_breaks); if trailing_breaks.is_empty() { string.push(' '); } } else { string.push_str(&leading_break); string.push_str(&trailing_breaks); } leading_break.clear(); trailing_breaks.clear(); leading_blank = is_blank(self.ch()); self.scan_block_scalar_content_line(&mut string, &mut line_buffer); // break on EOF if is_z(self.ch()) { break; } self.lookahead(2); self.read_break(&mut leading_break); // Eat the following indentation spaces and line breaks. self.skip_block_scalar_indent(indent, &mut trailing_breaks); } // Chomp the tail. if chomping != Chomping::Strip { string.push_str(&leading_break); // If we had reached an eof but the last character wasn't an end-of-line, check if the // last line was indented at least as the rest of the scalar, then we need to consider // there is a newline. if is_z(self.ch()) && self.mark.col >= indent.max(1) { string.push('\n'); } } if chomping == Chomping::Keep { string.push_str(&trailing_breaks); } Ok(Token(start_mark, TokenType::Scalar(style, string))) } /// Retrieve the contents of the line, parsing it as a block scalar. /// /// The contents will be appended to `string`. `line_buffer` is used as a temporary buffer to /// store bytes before pushing them to `string` and thus avoiding reallocating more than /// necessary. `line_buffer` is assumed to be empty upon calling this function. It will be /// `clear`ed before the end of the function. /// /// This function assumed the first character to read is the first content character in the /// line. This function does not consume the line break character(s) after the line. fn scan_block_scalar_content_line(&mut self, string: &mut String, line_buffer: &mut String) { // Start by evaluating characters in the buffer. while !self.buffer.is_empty() && !is_breakz(self.ch()) { string.push(self.ch()); // We may technically skip non-blank characters. However, the only distinction is // to determine what is leading whitespace and what is not. Here, we read the // contents of the line until either eof or a linebreak. We know we will not read // `self.leading_whitespace` until the end of the line, where it will be reset. // This allows us to call a slightly less expensive function. self.skip_blank(); } // All characters that were in the buffer were consumed. We need to check if more // follow. if self.buffer.is_empty() { // We will read all consecutive non-breakz characters. We push them into a // temporary buffer. The main difference with going through `self.buffer` is that // characters are appended here as their real size (1B for ascii, or up to 4 bytes for // UTF-8). We can then use the internal `line_buffer` `Vec` to push data into `string` // (using `String::push_str`). let mut c = self.raw_read_ch(); while !is_breakz(c) { line_buffer.push(c); c = self.raw_read_ch(); } // Our last character read is stored in `c`. It is either an EOF or a break. In any // case, we need to push it back into `self.buffer` so it may be properly read // after. We must not insert it in `string`. self.buffer.push_back(c).unwrap(); // We need to manually update our position; we haven't called a `skip` function. self.mark.col += line_buffer.len(); self.mark.index += line_buffer.len(); // We can now append our bytes to our `string`. string.reserve(line_buffer.len()); string.push_str(line_buffer); // This clears the _contents_ without touching the _capacity_. line_buffer.clear(); } } /// Skip the block scalar indentation and empty lines. fn skip_block_scalar_indent(&mut self, indent: usize, breaks: &mut String) { loop { // Consume all spaces. Tabs cannot be used as indentation. if indent < BUFFER_LEN - 2 { self.lookahead(BUFFER_LEN); while self.mark.col < indent && self.ch() == ' ' { self.skip_blank(); } } else { loop { self.lookahead(BUFFER_LEN); while !self.buffer.is_empty() && self.mark.col < indent && self.ch() == ' ' { self.skip_blank(); } // If we reached our indent, we can break. We must also break if we have // reached content or EOF; that is, the buffer is not empty and the next // character is not a space. if self.mark.col == indent || (!self.buffer.is_empty() && self.ch() != ' ') { break; } } self.lookahead(2); } // If our current line is empty, skip over the break and continue looping. if is_break(self.ch()) { self.read_break(breaks); } else { // Otherwise, we have a content line. Return control. break; } } } /// Determine the indentation level for a block scalar from the first line of its contents. /// /// The function skips over whitespace-only lines and sets `indent` to the the longest /// whitespace line that was encountered. fn skip_block_scalar_first_line_indent(&mut self, indent: &mut usize, breaks: &mut String) { let mut max_indent = 0; loop { // Consume all spaces. Tabs cannot be used as indentation. while self.look_ch() == ' ' { self.skip_blank(); } if self.mark.col > max_indent { max_indent = self.mark.col; } if is_break(self.ch()) { // If our current line is empty, skip over the break and continue looping. self.lookahead(2); self.read_break(breaks); } else { // Otherwise, we have a content line. Return control. break; } } // In case a yaml looks like: // ```yaml // | // foo // bar // ``` // We need to set the indent to 0 and not 1. In all other cases, the indent must be at // least 1. When in the above example, `self.indent` will be set to -1. *indent = max_indent.max((self.indent + 1) as usize); if self.indent > 0 { *indent = (*indent).max(1); } } fn fetch_flow_scalar(&mut self, single: bool) -> ScanResult { self.save_simple_key(); self.disallow_simple_key(); let tok = self.scan_flow_scalar(single)?; // From spec: To ensure JSON compatibility, if a key inside a flow mapping is JSON-like, // YAML allows the following value to be specified adjacent to the “:”. self.skip_to_next_token()?; self.adjacent_value_allowed_at = self.mark.index; self.tokens.push_back(tok); Ok(()) } #[allow(clippy::too_many_lines)] fn scan_flow_scalar(&mut self, single: bool) -> Result { let start_mark = self.mark; let mut string = String::new(); let mut leading_break = String::new(); let mut trailing_breaks = String::new(); let mut whitespaces = String::new(); let mut leading_blanks; /* Eat the left quote. */ self.skip_non_blank(); loop { /* Check for a document indicator. */ self.lookahead(4); if self.mark.col == 0 && (((self.buffer[0] == '-') && (self.buffer[1] == '-') && (self.buffer[2] == '-')) || ((self.buffer[0] == '.') && (self.buffer[1] == '.') && (self.buffer[2] == '.'))) && is_blank_or_breakz(self.buffer[3]) { return Err(ScanError::new( start_mark, "while scanning a quoted scalar, found unexpected document indicator", )); } if is_z(self.ch()) { return Err(ScanError::new( start_mark, "while scanning a quoted scalar, found unexpected end of stream", )); } if (self.mark.col as isize) < self.indent { return Err(ScanError::new( start_mark, "invalid indentation in quoted scalar", )); } leading_blanks = false; self.consume_flow_scalar_non_whitespace_chars( single, &mut string, &mut leading_blanks, &start_mark, )?; match self.look_ch() { '\'' if single => break, '"' if !single => break, _ => {} } // Consume blank characters. while is_blank(self.ch()) || is_break(self.ch()) { if is_blank(self.ch()) { // Consume a space or a tab character. if leading_blanks { if self.ch() == '\t' && (self.mark.col as isize) < self.indent { return Err(ScanError::new( self.mark, "tab cannot be used as indentation", )); } self.skip_blank(); } else { whitespaces.push(self.ch()); self.skip_blank(); } } else { self.lookahead(2); // Check if it is a first line break. if leading_blanks { self.read_break(&mut trailing_breaks); } else { whitespaces.clear(); self.read_break(&mut leading_break); leading_blanks = true; } } self.lookahead(1); } // Join the whitespaces or fold line breaks. if leading_blanks { if leading_break.is_empty() { string.push_str(&leading_break); string.push_str(&trailing_breaks); trailing_breaks.clear(); leading_break.clear(); } else { if trailing_breaks.is_empty() { string.push(' '); } else { string.push_str(&trailing_breaks); trailing_breaks.clear(); } leading_break.clear(); } } else { string.push_str(&whitespaces); whitespaces.clear(); } } // loop // Eat the right quote. self.skip_non_blank(); // Ensure there is no invalid trailing content. self.skip_ws_to_eol(SkipTabs::Yes)?; match self.ch() { // These can be encountered in flow sequences or mappings. ',' | '}' | ']' if self.flow_level > 0 => {} // An end-of-line / end-of-stream is fine. No trailing content. c if is_breakz(c) => {} // ':' can be encountered if our scalar is a key. // Outside of flow contexts, keys cannot span multiple lines ':' if self.flow_level == 0 && start_mark.line == self.mark.line => {} // Inside a flow context, this is allowed. ':' if self.flow_level > 0 => {} _ => { return Err(ScanError::new( self.mark, "invalid trailing content after double-quoted scalar", )); } } let style = if single { TScalarStyle::SingleQuoted } else { TScalarStyle::DoubleQuoted }; Ok(Token(start_mark, TokenType::Scalar(style, string))) } /// Consume successive non-whitespace characters from a flow scalar. /// /// This function resolves escape sequences and stops upon encountering a whitespace, the end /// of the stream or the closing character for the scalar (`'` for single quoted scalars, `"` /// for double quoted scalars). /// /// # Errors /// Return an error if an invalid escape sequence is found. fn consume_flow_scalar_non_whitespace_chars( &mut self, single: bool, string: &mut String, leading_blanks: &mut bool, start_mark: &Marker, ) -> Result<(), ScanError> { self.lookahead(2); while !is_blank_or_breakz(self.ch()) { match self.ch() { // Check for an escaped single quote. '\'' if self.buffer[1] == '\'' && single => { string.push('\''); self.skip_n_non_blank(2); } // Check for the right quote. '\'' if single => break, '"' if !single => break, // Check for an escaped line break. '\\' if !single && is_break(self.buffer[1]) => { self.lookahead(3); self.skip_non_blank(); self.skip_linebreak(); *leading_blanks = true; break; } // Check for an escape sequence. '\\' if !single => { string.push(self.resolve_flow_scalar_escape_sequence(start_mark)?); } c => { string.push(c); self.skip_non_blank(); } } self.lookahead(2); } Ok(()) } /// Escape the sequence we encounter in a flow scalar. /// /// `self.ch()` must point to the `\` starting the escape sequence. /// /// # Errors /// Return an error if an invalid escape sequence is found. fn resolve_flow_scalar_escape_sequence( &mut self, start_mark: &Marker, ) -> Result { let mut code_length = 0usize; let mut ret = '\0'; match self.buffer[1] { '0' => ret = '\0', 'a' => ret = '\x07', 'b' => ret = '\x08', 't' | '\t' => ret = '\t', 'n' => ret = '\n', 'v' => ret = '\x0b', 'f' => ret = '\x0c', 'r' => ret = '\x0d', 'e' => ret = '\x1b', ' ' => ret = '\x20', '"' => ret = '"', '/' => ret = '/', '\\' => ret = '\\', // Unicode next line (#x85) 'N' => ret = char::from_u32(0x85).unwrap(), // Unicode non-breaking space (#xA0) '_' => ret = char::from_u32(0xA0).unwrap(), // Unicode line separator (#x2028) 'L' => ret = char::from_u32(0x2028).unwrap(), // Unicode paragraph separator (#x2029) 'P' => ret = char::from_u32(0x2029).unwrap(), 'x' => code_length = 2, 'u' => code_length = 4, 'U' => code_length = 8, _ => { return Err(ScanError::new( *start_mark, "while parsing a quoted scalar, found unknown escape character", )) } } self.skip_n_non_blank(2); // Consume an arbitrary escape code. if code_length > 0 { self.lookahead(code_length); let mut value = 0u32; for i in 0..code_length { if !is_hex(self.buffer[i]) { return Err(ScanError::new( *start_mark, "while parsing a quoted scalar, did not find expected hexadecimal number", )); } value = (value << 4) + as_hex(self.buffer[i]); } let Some(ch) = char::from_u32(value) else { return Err(ScanError::new( *start_mark, "while parsing a quoted scalar, found invalid Unicode character escape code", )); }; ret = ch; self.skip_n_non_blank(code_length); } Ok(ret) } fn fetch_plain_scalar(&mut self) -> ScanResult { self.save_simple_key(); self.disallow_simple_key(); let tok = self.scan_plain_scalar()?; self.tokens.push_back(tok); Ok(()) } /// Scan for a plain scalar. /// /// Plain scalars are the most readable but restricted style. They may span multiple lines in /// some contexts. #[allow(clippy::too_many_lines)] fn scan_plain_scalar(&mut self) -> Result { self.unroll_non_block_indents(); let indent = self.indent + 1; let start_mark = self.mark; if self.flow_level > 0 && (start_mark.col as isize) < indent { return Err(ScanError::new( start_mark, "invalid indentation in flow construct", )); } let mut string = String::with_capacity(32); let mut leading_break = String::with_capacity(32); let mut trailing_breaks = String::with_capacity(32); let mut whitespaces = String::with_capacity(32); loop { self.lookahead(4); if self.next_is_document_indicator() || self.ch() == '#' { break; } if self.flow_level > 0 && self.ch() == '-' && is_flow(self.buffer[1]) { return Err(ScanError::new( self.mark, "plain scalar cannot start with '-' followed by ,[]{}", )); } if !is_blank_or_breakz(self.ch()) && self.next_can_be_plain_scalar() { if self.leading_whitespace { if leading_break.is_empty() { string.push_str(&leading_break); string.push_str(&trailing_breaks); trailing_breaks.clear(); leading_break.clear(); } else { if trailing_breaks.is_empty() { string.push(' '); } else { string.push_str(&trailing_breaks); trailing_breaks.clear(); } leading_break.clear(); } self.leading_whitespace = false; } else if !whitespaces.is_empty() { string.push_str(&whitespaces); whitespaces.clear(); } // We can unroll the first iteration of the loop. string.push(self.ch()); self.skip_non_blank(); self.lookahead(2); // Add content non-blank characters to the scalar. while !is_blank_or_breakz(self.ch()) { if !self.next_can_be_plain_scalar() { break; } string.push(self.ch()); self.skip_non_blank(); self.lookahead(2); } } // We may reach the end of a plain scalar if: // - We reach eof // - We reach ": " // - We find a flow character in a flow context if !(is_blank(self.ch()) || is_break(self.ch())) { break; } // Process blank characters. while is_blank(self.look_ch()) || is_break(self.ch()) { if is_blank(self.ch()) { if !self.leading_whitespace { whitespaces.push(self.ch()); self.skip_blank(); } else if (self.mark.col as isize) < indent && self.ch() == '\t' { // Tabs in an indentation columns are allowed if and only if the line is // empty. Skip to the end of the line. self.skip_ws_to_eol(SkipTabs::Yes)?; if !is_breakz(self.ch()) { return Err(ScanError::new( start_mark, "while scanning a plain scalar, found a tab", )); } } else { self.skip_blank(); } } else { self.lookahead(2); // Check if it is a first line break if self.leading_whitespace { self.read_break(&mut trailing_breaks); } else { whitespaces.clear(); self.read_break(&mut leading_break); self.leading_whitespace = true; } } } // check indentation level if self.flow_level == 0 && (self.mark.col as isize) < indent { break; } } if self.leading_whitespace { self.allow_simple_key(); } Ok(Token( start_mark, TokenType::Scalar(TScalarStyle::Plain, string), )) } fn fetch_key(&mut self) -> ScanResult { let start_mark = self.mark; if self.flow_level == 0 { // Check if we are allowed to start a new key (not necessarily simple). if !self.simple_key_allowed { return Err(ScanError::new( self.mark, "mapping keys are not allowed in this context", )); } self.roll_indent( start_mark.col, None, TokenType::BlockMappingStart, start_mark, ); } else { // The parser, upon receiving a `Key`, will insert a `MappingStart` event. self.flow_mapping_started = true; } self.remove_simple_key()?; if self.flow_level == 0 { self.allow_simple_key(); } else { self.disallow_simple_key(); } self.skip_non_blank(); self.skip_yaml_whitespace()?; if self.ch() == '\t' { return Err(ScanError::new( self.mark(), "tabs disallowed in this context", )); } self.tokens.push_back(Token(start_mark, TokenType::Key)); Ok(()) } /// Fetch a value from a mapping (after a `:`). fn fetch_value(&mut self) -> ScanResult { let sk = self.simple_keys.last().unwrap().clone(); let start_mark = self.mark; self.implicit_flow_mapping = self.flow_level > 0 && !self.flow_mapping_started; // Skip over ':'. self.skip_non_blank(); if self.look_ch() == '\t' && !self.skip_ws_to_eol(SkipTabs::Yes)?.has_valid_yaml_ws() && (self.ch() == '-' || is_alpha(self.ch())) { return Err(ScanError::new( self.mark, "':' must be followed by a valid YAML whitespace", )); } if sk.possible { // insert simple key let tok = Token(sk.mark, TokenType::Key); self.insert_token(sk.token_number - self.tokens_parsed, tok); if self.implicit_flow_mapping { if sk.mark.line < start_mark.line { return Err(ScanError::new( start_mark, "illegal placement of ':' indicator", )); } self.insert_token( sk.token_number - self.tokens_parsed, Token(self.mark, TokenType::FlowMappingStart), ); } // Add the BLOCK-MAPPING-START token if needed. self.roll_indent( sk.mark.col, Some(sk.token_number), TokenType::BlockMappingStart, start_mark, ); self.roll_one_col_indent(); self.simple_keys.last_mut().unwrap().possible = false; self.disallow_simple_key(); } else { if self.implicit_flow_mapping { self.tokens .push_back(Token(self.mark, TokenType::FlowMappingStart)); } // The ':' indicator follows a complex key. if self.flow_level == 0 { if !self.simple_key_allowed { return Err(ScanError::new( start_mark, "mapping values are not allowed in this context", )); } self.roll_indent( start_mark.col, None, TokenType::BlockMappingStart, start_mark, ); } self.roll_one_col_indent(); if self.flow_level == 0 { self.allow_simple_key(); } else { self.disallow_simple_key(); } } self.tokens.push_back(Token(start_mark, TokenType::Value)); Ok(()) } /// Add an indentation level to the stack with the given block token, if needed. /// /// An indentation level is added only if: /// - We are not in a flow-style construct (which don't have indentation per-se). /// - The current column is further indented than the last indent we have registered. fn roll_indent(&mut self, col: usize, number: Option, tok: TokenType, mark: Marker) { if self.flow_level > 0 { return; } // If the last indent was a non-block indent, remove it. // This means that we prepared an indent that we thought we wouldn't use, but realized just // now that it is a block indent. if self.indent <= col as isize { if let Some(indent) = self.indents.last() { if !indent.needs_block_end { self.indent = indent.indent; self.indents.pop(); } } } if self.indent < col as isize { self.indents.push(Indent { indent: self.indent, needs_block_end: true, }); self.indent = col as isize; let tokens_parsed = self.tokens_parsed; match number { Some(n) => self.insert_token(n - tokens_parsed, Token(mark, tok)), None => self.tokens.push_back(Token(mark, tok)), } } } /// Pop indentation levels from the stack as much as needed. /// /// Indentation levels are popped from the stack while they are further indented than `col`. /// If we are in a flow-style construct (which don't have indentation per-se), this function /// does nothing. fn unroll_indent(&mut self, col: isize) { if self.flow_level > 0 { return; } while self.indent > col { let indent = self.indents.pop().unwrap(); self.indent = indent.indent; if indent.needs_block_end { self.tokens.push_back(Token(self.mark, TokenType::BlockEnd)); } } } /// Add an indentation level of 1 column that does not start a block. /// /// See the documentation of [`Indent::needs_block_end`] for more details. /// An indentation is not added if we are inside a flow level or if the last indent is already /// a non-block indent. fn roll_one_col_indent(&mut self) { if self.flow_level == 0 && self.indents.last().is_some_and(|x| x.needs_block_end) { self.indents.push(Indent { indent: self.indent, needs_block_end: false, }); self.indent += 1; } } /// Unroll all last indents created with [`Self::roll_one_col_indent`]. fn unroll_non_block_indents(&mut self) { while let Some(indent) = self.indents.last() { if indent.needs_block_end { break; } self.indent = indent.indent; self.indents.pop(); } } /// Mark the next token to be inserted as a potential simple key. fn save_simple_key(&mut self) { if self.simple_key_allowed { let required = self.flow_level == 0 && self.indent == (self.mark.col as isize) && self.indents.last().unwrap().needs_block_end; let mut sk = SimpleKey::new(self.mark); sk.possible = true; sk.required = required; sk.token_number = self.tokens_parsed + self.tokens.len(); self.simple_keys.pop(); self.simple_keys.push(sk); } } fn remove_simple_key(&mut self) -> ScanResult { let last = self.simple_keys.last_mut().unwrap(); if last.possible && last.required { return Err(ScanError::new(self.mark, "simple key expected")); } last.possible = false; Ok(()) } /// Check whether the next characters may be part of a plain scalar. /// /// This function assumes we are not given a blankz character. // For some reason, `#[inline]` is not enough. #[allow(clippy::inline_always)] #[inline(always)] fn next_can_be_plain_scalar(&self) -> bool { match self.ch() { // indicators can end a plain scalar, see 7.3.3. Plain Style ':' if is_blank_or_breakz(self.buffer[1]) || (self.flow_level > 0 && is_flow(self.buffer[1])) => { false } c if self.flow_level > 0 && is_flow(c) => false, _ => true, } } /// Return whether the scanner is inside a block but outside of a flow sequence. fn is_within_block(&self) -> bool { !self.indents.is_empty() } /// If an implicit mapping had started, end it. fn end_implicit_mapping(&mut self, mark: Marker) { if self.implicit_flow_mapping { self.implicit_flow_mapping = false; self.flow_mapping_started = false; self.tokens .push_back(Token(mark, TokenType::FlowMappingEnd)); } } } /// Behavior to adopt regarding treating tabs as whitespace. /// /// Although tab is a valid yaml whitespace, it doesn't always behave the same as a space. #[derive(Copy, Clone, Eq, PartialEq)] enum SkipTabs { /// Skip all tabs as whitespace. Yes, /// Don't skip any tab. Return from the function when encountering one. No, /// Return value from the function. Result( /// Whether tabs were encountered. bool, /// Whether at least 1 valid yaml whitespace has been encountered. bool, ), } impl SkipTabs { /// Whether tabs were found while skipping whitespace. /// /// This function must be called after a call to `skip_ws_to_eol`. fn found_tabs(self) -> bool { matches!(self, SkipTabs::Result(true, _)) } /// Whether a valid YAML whitespace has been found in skipped-over content. /// /// This function must be called after a call to `skip_ws_to_eol`. fn has_valid_yaml_ws(self) -> bool { matches!(self, SkipTabs::Result(_, true)) } } /// Chomping, how final line breaks and trailing empty lines are interpreted. /// /// See YAML spec 8.1.1.2. #[derive(PartialEq, Eq)] pub enum Chomping { /// The final line break and any trailing empty lines are excluded. Strip, /// The final line break is preserved, but trailing empty lines are excluded. Clip, /// The final line break and trailing empty lines are included. Keep, } #[cfg(test)] mod test { #[test] fn test_is_anchor_char() { use super::is_anchor_char; assert!(is_anchor_char('x')); } } yaml-rust2-0.10.1/src/yaml.rs000064400000000000000000001013161046102023000140230ustar 00000000000000//! YAML objects manipulation utilities. #![allow(clippy::module_name_repetitions)] use std::borrow::Cow; use std::ops::ControlFlow; use std::{collections::BTreeMap, convert::TryFrom, mem, ops::Index, ops::IndexMut}; #[cfg(feature = "encoding")] use encoding_rs::{Decoder, DecoderResult, Encoding}; use hashlink::LinkedHashMap; use crate::parser::{Event, MarkedEventReceiver, Parser, Tag}; use crate::scanner::{Marker, ScanError, TScalarStyle}; /// A YAML node is stored as this `Yaml` enumeration, which provides an easy way to /// access your YAML document. /// /// # Examples /// /// ``` /// use yaml_rust2::Yaml; /// let foo = Yaml::from_str("-123"); // convert the string to the appropriate YAML type /// assert_eq!(foo.as_i64().unwrap(), -123); /// /// // iterate over an Array /// let vec = Yaml::Array(vec![Yaml::Integer(1), Yaml::Integer(2)]); /// for v in vec.as_vec().unwrap() { /// assert!(v.as_i64().is_some()); /// } /// ``` #[derive(Clone, PartialEq, PartialOrd, Debug, Eq, Ord, Hash)] pub enum Yaml { /// Float types are stored as String and parsed on demand. /// Note that `f64` does NOT implement Eq trait and can NOT be stored in `BTreeMap`. Real(String), /// YAML int is stored as i64. Integer(i64), /// YAML scalar. String(String), /// YAML bool, e.g. `true` or `false`. Boolean(bool), /// YAML array, can be accessed as a [`Vec`]. Array(Array), /// YAML hash, can be accessed as a [`LinkedHashMap`]. /// /// Insertion order will match the order of insertion into the map. Hash(Hash), /// Alias, not fully supported yet. Alias(usize), /// YAML null, e.g. `null` or `~`. Null, /// Accessing a nonexistent node via the Index trait returns `BadValue`. This /// simplifies error handling in the calling code. Invalid type conversion also /// returns `BadValue`. BadValue, } /// The type contained in the `Yaml::Array` variant. This corresponds to YAML sequences. pub type Array = Vec; /// The type contained in the `Yaml::Hash` variant. This corresponds to YAML mappings. pub type Hash = LinkedHashMap; // parse f64 as Core schema // See: https://github.com/chyh1990/yaml-rust/issues/51 fn parse_f64(v: &str) -> Option { match v { ".inf" | ".Inf" | ".INF" | "+.inf" | "+.Inf" | "+.INF" => Some(f64::INFINITY), "-.inf" | "-.Inf" | "-.INF" => Some(f64::NEG_INFINITY), ".nan" | ".NaN" | ".NAN" => Some(f64::NAN), _ => v.parse::().ok(), } } /// Main structure for quickly parsing YAML. /// /// See [`YamlLoader::load_from_str`]. #[derive(Default)] pub struct YamlLoader { /// The different YAML documents that are loaded. docs: Vec, // states // (current node, anchor_id) tuple doc_stack: Vec<(Yaml, usize)>, key_stack: Vec, anchor_map: BTreeMap, /// An error, if one was encountered. error: Option, } impl MarkedEventReceiver for YamlLoader { fn on_event(&mut self, ev: Event, mark: Marker) { if self.error.is_some() { return; } if let Err(e) = self.on_event_impl(ev, mark) { self.error = Some(e); } } } /// An error that happened when loading a YAML document. #[derive(Debug)] pub enum LoadError { /// An I/O error. IO(std::io::Error), /// An error within the scanner. This indicates a malformed YAML input. Scan(ScanError), /// A decoding error (e.g.: Invalid UTF-8). Decode(std::borrow::Cow<'static, str>), } impl From for LoadError { fn from(error: std::io::Error) -> Self { LoadError::IO(error) } } impl YamlLoader { fn on_event_impl(&mut self, ev: Event, mark: Marker) -> Result<(), ScanError> { // println!("EV {:?}", ev); match ev { Event::DocumentStart | Event::Nothing | Event::StreamStart | Event::StreamEnd => { // do nothing } Event::DocumentEnd => { match self.doc_stack.len() { // empty document 0 => self.docs.push(Yaml::BadValue), 1 => self.docs.push(self.doc_stack.pop().unwrap().0), _ => unreachable!(), } } Event::SequenceStart(aid, _) => { self.doc_stack.push((Yaml::Array(Vec::new()), aid)); } Event::SequenceEnd => { let node = self.doc_stack.pop().unwrap(); self.insert_new_node(node, mark)?; } Event::MappingStart(aid, _) => { self.doc_stack.push((Yaml::Hash(Hash::new()), aid)); self.key_stack.push(Yaml::BadValue); } Event::MappingEnd => { self.key_stack.pop().unwrap(); let node = self.doc_stack.pop().unwrap(); self.insert_new_node(node, mark)?; } Event::Scalar(v, style, aid, tag) => { let node = if style != TScalarStyle::Plain { Yaml::String(v) } else if let Some(Tag { ref handle, ref suffix, }) = tag { if handle == "tag:yaml.org,2002:" { match suffix.as_ref() { "bool" => { // "true" or "false" match v.parse::() { Err(_) => Yaml::BadValue, Ok(v) => Yaml::Boolean(v), } } "int" => match v.parse::() { Err(_) => Yaml::BadValue, Ok(v) => Yaml::Integer(v), }, "float" => match parse_f64(&v) { Some(_) => Yaml::Real(v), None => Yaml::BadValue, }, "null" => match v.as_ref() { "~" | "null" => Yaml::Null, _ => Yaml::BadValue, }, _ => Yaml::String(v), } } else { Yaml::String(v) } } else { // Datatype is not specified, or unrecognized Yaml::from_str(&v) }; self.insert_new_node((node, aid), mark)?; } Event::Alias(id) => { let n = match self.anchor_map.get(&id) { Some(v) => v.clone(), None => Yaml::BadValue, }; self.insert_new_node((n, 0), mark)?; } } // println!("DOC {:?}", self.doc_stack); Ok(()) } fn insert_new_node(&mut self, node: (Yaml, usize), mark: Marker) -> Result<(), ScanError> { // valid anchor id starts from 1 if node.1 > 0 { self.anchor_map.insert(node.1, node.0.clone()); } if self.doc_stack.is_empty() { self.doc_stack.push(node); } else { let parent = self.doc_stack.last_mut().unwrap(); match *parent { (Yaml::Array(ref mut v), _) => v.push(node.0), (Yaml::Hash(ref mut h), _) => { let cur_key = self.key_stack.last_mut().unwrap(); // current node is a key if cur_key.is_badvalue() { *cur_key = node.0; // current node is a value } else { let mut newkey = Yaml::BadValue; mem::swap(&mut newkey, cur_key); if h.insert(newkey, node.0).is_some() { let inserted_key = h.back().unwrap().0; return Err(ScanError::new_string( mark, format!("{inserted_key:?}: duplicated key in mapping"), )); } } } _ => unreachable!(), } } Ok(()) } /// Load the given string as a set of YAML documents. /// /// The `source` is interpreted as YAML documents and is parsed. Parsing succeeds if and only /// if all documents are parsed successfully. An error in a latter document prevents the former /// from being returned. /// # Errors /// Returns `ScanError` when loading fails. pub fn load_from_str(source: &str) -> Result, ScanError> { Self::load_from_iter(source.chars()) } /// Load the contents of the given iterator as a set of YAML documents. /// /// The `source` is interpreted as YAML documents and is parsed. Parsing succeeds if and only /// if all documents are parsed successfully. An error in a latter document prevents the former /// from being returned. /// # Errors /// Returns `ScanError` when loading fails. pub fn load_from_iter>(source: I) -> Result, ScanError> { let mut parser = Parser::new(source); Self::load_from_parser(&mut parser) } /// Load the contents from the specified Parser as a set of YAML documents. /// /// Parsing succeeds if and only if all documents are parsed successfully. /// An error in a latter document prevents the former from being returned. /// # Errors /// Returns `ScanError` when loading fails. pub fn load_from_parser>( parser: &mut Parser, ) -> Result, ScanError> { let mut loader = YamlLoader::default(); parser.load(&mut loader, true)?; if let Some(e) = loader.error { Err(e) } else { Ok(loader.docs) } } /// Return a reference to the parsed Yaml documents. #[must_use] pub fn documents(&self) -> &[Yaml] { &self.docs } } /// The signature of the function to call when using [`YAMLDecodingTrap::Call`]. /// /// The arguments are as follows: /// * `malformation_length`: The length of the sequence the decoder failed to decode. /// * `bytes_read_after_malformation`: The number of lookahead bytes the decoder consumed after /// the malformation. /// * `input_at_malformation`: What the input buffer is at the malformation. /// This is the buffer starting at the malformation. The first `malformation_length` bytes are /// the problematic sequence. The following `bytes_read_after_malformation` are already stored /// in the decoder and will not be re-fed. /// * `output`: The output string. /// /// The function must modify `output` as it feels is best. For instance, one could recreate the /// behavior of [`YAMLDecodingTrap::Ignore`] with an empty function, [`YAMLDecodingTrap::Replace`] /// by pushing a `\u{FFFD}` into `output` and [`YAMLDecodingTrap::Strict`] by returning /// [`ControlFlow::Break`]. /// /// # Returns /// The function must return [`ControlFlow::Continue`] if decoding may continue or /// [`ControlFlow::Break`] if decoding must be aborted. An optional error string may be supplied. #[cfg(feature = "encoding")] pub type YAMLDecodingTrapFn = fn( malformation_length: u8, bytes_read_after_malformation: u8, input_at_malformation: &[u8], output: &mut String, ) -> ControlFlow>; /// The behavior [`YamlDecoder`] must have when an decoding error occurs. #[cfg(feature = "encoding")] #[derive(Copy, Clone, PartialEq, Eq)] pub enum YAMLDecodingTrap { /// Ignore the offending bytes, remove them from the output. Ignore, /// Error out. Strict, /// Replace them with the Unicode REPLACEMENT CHARACTER. Replace, /// Call the user-supplied function upon decoding malformation. Call(YAMLDecodingTrapFn), } /// `YamlDecoder` is a `YamlLoader` builder that allows you to supply your own encoding error trap. /// For example, to read a YAML file while ignoring Unicode decoding errors you can set the /// `encoding_trap` to `encoding::DecoderTrap::Ignore`. /// ```rust /// use yaml_rust2::yaml::{YamlDecoder, YAMLDecodingTrap}; /// /// let string = b"--- /// a\xa9: 1 /// b: 2.2 /// c: [1, 2] /// "; /// let out = YamlDecoder::read(string as &[u8]) /// .encoding_trap(YAMLDecodingTrap::Ignore) /// .decode() /// .unwrap(); /// ``` #[cfg(feature = "encoding")] pub struct YamlDecoder { source: T, trap: YAMLDecodingTrap, } #[cfg(feature = "encoding")] impl YamlDecoder { /// Create a `YamlDecoder` decoding the given source. pub fn read(source: T) -> YamlDecoder { YamlDecoder { source, trap: YAMLDecodingTrap::Strict, } } /// Set the behavior of the decoder when the encoding is invalid. pub fn encoding_trap(&mut self, trap: YAMLDecodingTrap) -> &mut Self { self.trap = trap; self } /// Run the decode operation with the source and trap the `YamlDecoder` was built with. /// /// # Errors /// Returns `LoadError` when decoding fails. pub fn decode(&mut self) -> Result, LoadError> { let mut buffer = Vec::new(); self.source.read_to_end(&mut buffer)?; // Check if the `encoding` library can detect encoding from the BOM, otherwise use // `detect_utf16_endianness`. let (encoding, _) = Encoding::for_bom(&buffer).unwrap_or_else(|| (detect_utf16_endianness(&buffer), 2)); let mut decoder = encoding.new_decoder(); let mut output = String::new(); // Decode the input buffer. decode_loop(&buffer, &mut output, &mut decoder, self.trap)?; YamlLoader::load_from_str(&output).map_err(LoadError::Scan) } } /// Perform a loop of [`Decoder::decode_to_string`], reallocating `output` if needed. #[cfg(feature = "encoding")] fn decode_loop( input: &[u8], output: &mut String, decoder: &mut Decoder, trap: YAMLDecodingTrap, ) -> Result<(), LoadError> { output.reserve(input.len()); let mut total_bytes_read = 0; loop { match decoder.decode_to_string_without_replacement(&input[total_bytes_read..], output, true) { // If the input is empty, we processed the whole input. (DecoderResult::InputEmpty, _) => break Ok(()), // If the output is full, we must reallocate. (DecoderResult::OutputFull, bytes_read) => { total_bytes_read += bytes_read; // The output is already reserved to the size of the input. We slowly resize. Here, // we're expecting that 10% of bytes will double in size when converting to UTF-8. output.reserve(input.len() / 10); } (DecoderResult::Malformed(malformed_len, bytes_after_malformed), bytes_read) => { total_bytes_read += bytes_read; match trap { // Ignore (skip over) malformed character. YAMLDecodingTrap::Ignore => {} // Replace them with the Unicode REPLACEMENT CHARACTER. YAMLDecodingTrap::Replace => { output.push('\u{FFFD}'); } // Otherwise error, getting as much context as possible. YAMLDecodingTrap::Strict => { let malformed_len = malformed_len as usize; let bytes_after_malformed = bytes_after_malformed as usize; let byte_idx = total_bytes_read - (malformed_len + bytes_after_malformed); let malformed_sequence = &input[byte_idx..byte_idx + malformed_len]; break Err(LoadError::Decode(Cow::Owned(format!( "Invalid character sequence at {byte_idx}: {malformed_sequence:?}", )))); } YAMLDecodingTrap::Call(callback) => { let byte_idx = total_bytes_read - ((malformed_len + bytes_after_malformed) as usize); let malformed_sequence = &input[byte_idx..byte_idx + malformed_len as usize]; if let ControlFlow::Break(error) = callback( malformed_len, bytes_after_malformed, &input[byte_idx..], output, ) { if error.is_empty() { break Err(LoadError::Decode(Cow::Owned(format!( "Invalid character sequence at {byte_idx}: {malformed_sequence:?}", )))); } break Err(LoadError::Decode(error)); } } } } } } } /// The encoding crate knows how to tell apart UTF-8 from UTF-16LE and utf-16BE, when the /// bytestream starts with BOM codepoint. /// However, it doesn't even attempt to guess the UTF-16 endianness of the input bytestream since /// in the general case the bytestream could start with a codepoint that uses both bytes. /// /// The YAML-1.2 spec mandates that the first character of a YAML document is an ASCII character. /// This allows the encoding to be deduced by the pattern of null (#x00) characters. // /// See spec at #[cfg(feature = "encoding")] fn detect_utf16_endianness(b: &[u8]) -> &'static Encoding { if b.len() > 1 && (b[0] != b[1]) { if b[0] == 0 { return encoding_rs::UTF_16BE; } else if b[1] == 0 { return encoding_rs::UTF_16LE; } } encoding_rs::UTF_8 } macro_rules! define_as ( ($name:ident, $t:ident, $yt:ident) => ( /// Get a copy of the inner object in the YAML enum if it is a `$t`. /// /// # Return /// If the variant of `self` is `Yaml::$yt`, return `Some($t)` with a copy of the `$t` contained. /// Otherwise, return `None`. #[must_use] pub fn $name(&self) -> Option<$t> { match *self { Yaml::$yt(v) => Some(v), _ => None } } ); ); macro_rules! define_as_ref ( ($name:ident, $t:ty, $yt:ident) => ( /// Get a reference to the inner object in the YAML enum if it is a `$t`. /// /// # Return /// If the variant of `self` is `Yaml::$yt`, return `Some(&$t)` with the `$t` contained. Otherwise, /// return `None`. #[must_use] pub fn $name(&self) -> Option<$t> { match *self { Yaml::$yt(ref v) => Some(v), _ => None } } ); ); macro_rules! define_as_mut_ref ( ($name:ident, $t:ty, $yt:ident) => ( /// Get a mutable reference to the inner object in the YAML enum if it is a `$t`. /// /// # Return /// If the variant of `self` is `Yaml::$yt`, return `Some(&mut $t)` with the `$t` contained. /// Otherwise, return `None`. #[must_use] pub fn $name(&mut self) -> Option<$t> { match *self { Yaml::$yt(ref mut v) => Some(v), _ => None } } ); ); macro_rules! define_into ( ($name:ident, $t:ty, $yt:ident) => ( /// Get the inner object in the YAML enum if it is a `$t`. /// /// # Return /// If the variant of `self` is `Yaml::$yt`, return `Some($t)` with the `$t` contained. Otherwise, /// return `None`. #[must_use] pub fn $name(self) -> Option<$t> { match self { Yaml::$yt(v) => Some(v), _ => None } } ); ); impl Yaml { define_as!(as_bool, bool, Boolean); define_as!(as_i64, i64, Integer); define_as_ref!(as_str, &str, String); define_as_ref!(as_hash, &Hash, Hash); define_as_ref!(as_vec, &Array, Array); define_as_mut_ref!(as_mut_hash, &mut Hash, Hash); define_as_mut_ref!(as_mut_vec, &mut Array, Array); define_into!(into_bool, bool, Boolean); define_into!(into_i64, i64, Integer); define_into!(into_string, String, String); define_into!(into_hash, Hash, Hash); define_into!(into_vec, Array, Array); /// Return whether `self` is a [`Yaml::Null`] node. #[must_use] pub fn is_null(&self) -> bool { matches!(*self, Yaml::Null) } /// Return whether `self` is a [`Yaml::BadValue`] node. #[must_use] pub fn is_badvalue(&self) -> bool { matches!(*self, Yaml::BadValue) } /// Return whether `self` is a [`Yaml::Array`] node. #[must_use] pub fn is_array(&self) -> bool { matches!(*self, Yaml::Array(_)) } /// Return whether `self` is a [`Yaml::Hash`] node. #[must_use] pub fn is_hash(&self) -> bool { matches!(*self, Yaml::Hash(_)) } /// Return the `f64` value contained in this YAML node. /// /// If the node is not a [`Yaml::Real`] YAML node or its contents is not a valid `f64` string, /// `None` is returned. #[must_use] pub fn as_f64(&self) -> Option { if let Yaml::Real(ref v) = self { parse_f64(v) } else { None } } /// Return the `f64` value contained in this YAML node. /// /// If the node is not a [`Yaml::Real`] YAML node or its contents is not a valid `f64` string, /// `None` is returned. #[must_use] pub fn into_f64(self) -> Option { self.as_f64() } /// If a value is null or otherwise bad (see variants), consume it and /// replace it with a given value `other`. Otherwise, return self unchanged. /// /// ``` /// use yaml_rust2::yaml::Yaml; /// /// assert_eq!(Yaml::BadValue.or(Yaml::Integer(3)), Yaml::Integer(3)); /// assert_eq!(Yaml::Integer(3).or(Yaml::BadValue), Yaml::Integer(3)); /// ``` #[must_use] pub fn or(self, other: Self) -> Self { match self { Yaml::BadValue | Yaml::Null => other, this => this, } } /// See `or` for behavior. This performs the same operations, but with /// borrowed values for less linear pipelines. #[must_use] pub fn borrowed_or<'a>(&'a self, other: &'a Self) -> &'a Self { match self { Yaml::BadValue | Yaml::Null => other, this => this, } } } #[allow(clippy::should_implement_trait)] impl Yaml { /// Convert a string to a [`Yaml`] node. /// /// [`Yaml`] does not implement [`std::str::FromStr`] since conversion may not fail. This /// function falls back to [`Yaml::String`] if nothing else matches. /// /// # Examples /// ``` /// # use yaml_rust2::yaml::Yaml; /// assert!(matches!(Yaml::from_str("42"), Yaml::Integer(42))); /// assert!(matches!(Yaml::from_str("0x2A"), Yaml::Integer(42))); /// assert!(matches!(Yaml::from_str("0o52"), Yaml::Integer(42))); /// assert!(matches!(Yaml::from_str("~"), Yaml::Null)); /// assert!(matches!(Yaml::from_str("null"), Yaml::Null)); /// assert!(matches!(Yaml::from_str("true"), Yaml::Boolean(true))); /// assert!(matches!(Yaml::from_str("3.14"), Yaml::Real(_))); /// assert!(matches!(Yaml::from_str("foo"), Yaml::String(_))); /// ``` #[must_use] pub fn from_str(v: &str) -> Yaml { if let Some(number) = v.strip_prefix("0x") { if let Ok(i) = i64::from_str_radix(number, 16) { return Yaml::Integer(i); } } else if let Some(number) = v.strip_prefix("0o") { if let Ok(i) = i64::from_str_radix(number, 8) { return Yaml::Integer(i); } } else if let Some(number) = v.strip_prefix('+') { if let Ok(i) = number.parse::() { return Yaml::Integer(i); } } match v { "" | "~" | "null" => Yaml::Null, "true" => Yaml::Boolean(true), "false" => Yaml::Boolean(false), _ => { if let Ok(integer) = v.parse::() { Yaml::Integer(integer) } else if parse_f64(v).is_some() { Yaml::Real(v.to_owned()) } else { Yaml::String(v.to_owned()) } } } } } static BAD_VALUE: Yaml = Yaml::BadValue; impl<'a> Index<&'a str> for Yaml { type Output = Yaml; /// Perform indexing if `self` is a mapping. /// /// # Return /// If `self` is a [`Yaml::Hash`], returns an immutable borrow to the value associated to the /// given key in the hash. /// /// This function returns a [`Yaml::BadValue`] if the underlying [`type@Hash`] does not contain /// [`Yaml::String`]`{idx}` as a key. /// /// This function also returns a [`Yaml::BadValue`] if `self` is not a [`Yaml::Hash`]. fn index(&self, idx: &'a str) -> &Yaml { let key = Yaml::String(idx.to_owned()); match self.as_hash() { Some(h) => h.get(&key).unwrap_or(&BAD_VALUE), None => &BAD_VALUE, } } } impl<'a> IndexMut<&'a str> for Yaml { /// Perform indexing if `self` is a mapping. /// /// Since we cannot return a mutable borrow to a static [`Yaml::BadValue`] as we return an /// immutable one in [`Index<&'a str>`], this function panics on out of bounds. /// /// # Panics /// This function panics if the given key is not contained in `self` (as per [`IndexMut`]). /// /// This function also panics if `self` is not a [`Yaml::Hash`]. fn index_mut(&mut self, idx: &'a str) -> &mut Yaml { let key = Yaml::String(idx.to_owned()); match self.as_mut_hash() { Some(h) => h.get_mut(&key).unwrap(), None => panic!("Not a hash type"), } } } impl Index for Yaml { type Output = Yaml; /// Perform indexing if `self` is a sequence or a mapping. /// /// # Return /// If `self` is a [`Yaml::Array`], returns an immutable borrow to the value located at the /// given index in the array. /// /// Otherwise, if `self` is a [`Yaml::Hash`], returns a borrow to the value whose key is /// [`Yaml::Integer`]`(idx)` (this would not work if the key is [`Yaml::String`]`("1")`. /// /// This function returns a [`Yaml::BadValue`] if the index given is out of range. If `self` is /// a [`Yaml::Array`], this is when the index is bigger or equal to the length of the /// underlying `Vec`. If `self` is a [`Yaml::Hash`], this is when the mapping sequence does not /// contain [`Yaml::Integer`]`(idx)` as a key. /// /// This function also returns a [`Yaml::BadValue`] if `self` is not a [`Yaml::Array`] nor a /// [`Yaml::Hash`]. fn index(&self, idx: usize) -> &Yaml { if let Some(v) = self.as_vec() { v.get(idx).unwrap_or(&BAD_VALUE) } else if let Some(v) = self.as_hash() { let key = Yaml::Integer(i64::try_from(idx).unwrap()); v.get(&key).unwrap_or(&BAD_VALUE) } else { &BAD_VALUE } } } impl IndexMut for Yaml { /// Perform indexing if `self` is a sequence or a mapping. /// /// Since we cannot return a mutable borrow to a static [`Yaml::BadValue`] as we return an /// immutable one in [`Index`], this function panics on out of bounds. /// /// # Panics /// This function panics if the index given is out of range (as per [`IndexMut`]). If `self` is /// a [`Yaml::Array`], this is when the index is bigger or equal to the length of the /// underlying `Vec`. If `self` is a [`Yaml::Hash`], this is when the mapping sequence does not /// contain [`Yaml::Integer`]`(idx)` as a key. /// /// This function also panics if `self` is not a [`Yaml::Array`] nor a [`Yaml::Hash`]. fn index_mut(&mut self, idx: usize) -> &mut Yaml { match self { Yaml::Array(sequence) => sequence.index_mut(idx), Yaml::Hash(mapping) => { let key = Yaml::Integer(i64::try_from(idx).unwrap()); mapping.get_mut(&key).unwrap() } _ => panic!("Attempting to index but `self` is not a sequence nor a mapping"), } } } impl IntoIterator for Yaml { type Item = Yaml; type IntoIter = YamlIter; /// Extract the [`Array`] from `self` and iterate over it. /// /// If `self` is **not** of the [`Yaml::Array`] variant, this function will not panic or return /// an error (as per the [`IntoIterator`] trait it cannot) but will instead return an iterator /// over an empty [`Array`]. Callers have to ensure (using [`Yaml::is_array`], [`matches`] or /// something similar) that the [`Yaml`] object is a [`Yaml::Array`] if they want to do error /// handling. /// /// # Examples /// ``` /// # use yaml_rust2::{Yaml, YamlLoader}; /// /// // An array of 2 integers, 1 and 2. /// let arr = &YamlLoader::load_from_str("- 1\n- 2").unwrap()[0]; /// /// assert_eq!(arr.clone().into_iter().count(), 2); /// assert_eq!(arr.clone().into_iter().next(), Some(Yaml::Integer(1))); /// assert_eq!(arr.clone().into_iter().nth(1), Some(Yaml::Integer(2))); /// /// // An empty array returns an empty iterator. /// let empty = Yaml::Array(vec![]); /// assert_eq!(empty.into_iter().count(), 0); /// /// // A hash with 2 key-value pairs, `(a, b)` and `(c, d)`. /// let hash = YamlLoader::load_from_str("a: b\nc: d").unwrap().remove(0); /// // The hash has 2 elements. /// assert_eq!(hash.as_hash().unwrap().iter().count(), 2); /// // But since `into_iter` can't be used with a `Yaml::Hash`, `into_iter` returns an empty /// // iterator. /// assert_eq!(hash.into_iter().count(), 0); /// ``` fn into_iter(self) -> Self::IntoIter { YamlIter { yaml: self.into_vec().unwrap_or_default().into_iter(), } } } /// An iterator over a [`Yaml`] node. pub struct YamlIter { yaml: std::vec::IntoIter, } impl Iterator for YamlIter { type Item = Yaml; fn next(&mut self) -> Option { self.yaml.next() } } #[cfg(test)] mod test { use super::{YAMLDecodingTrap, Yaml, YamlDecoder}; #[test] fn test_read_bom() { let s = b"\xef\xbb\xbf--- a: 1 b: 2.2 c: [1, 2] "; let out = YamlDecoder::read(s as &[u8]).decode().unwrap(); let doc = &out[0]; assert_eq!(doc["a"].as_i64().unwrap(), 1i64); assert!((doc["b"].as_f64().unwrap() - 2.2f64).abs() <= f64::EPSILON); assert_eq!(doc["c"][1].as_i64().unwrap(), 2i64); assert!(doc["d"][0].is_badvalue()); } #[test] fn test_read_utf16le() { let s = b"\xff\xfe-\x00-\x00-\x00 \x00a\x00:\x00 \x001\x00 \x00b\x00:\x00 \x002\x00.\x002\x00 \x00c\x00:\x00 \x00[\x001\x00,\x00 \x002\x00]\x00 \x00"; let out = YamlDecoder::read(s as &[u8]).decode().unwrap(); let doc = &out[0]; println!("GOT: {doc:?}"); assert_eq!(doc["a"].as_i64().unwrap(), 1i64); assert!((doc["b"].as_f64().unwrap() - 2.2f64) <= f64::EPSILON); assert_eq!(doc["c"][1].as_i64().unwrap(), 2i64); assert!(doc["d"][0].is_badvalue()); } #[test] fn test_read_utf16be() { let s = b"\xfe\xff\x00-\x00-\x00-\x00 \x00a\x00:\x00 \x001\x00 \x00b\x00:\x00 \x002\x00.\x002\x00 \x00c\x00:\x00 \x00[\x001\x00,\x00 \x002\x00]\x00 "; let out = YamlDecoder::read(s as &[u8]).decode().unwrap(); let doc = &out[0]; println!("GOT: {doc:?}"); assert_eq!(doc["a"].as_i64().unwrap(), 1i64); assert!((doc["b"].as_f64().unwrap() - 2.2f64).abs() <= f64::EPSILON); assert_eq!(doc["c"][1].as_i64().unwrap(), 2i64); assert!(doc["d"][0].is_badvalue()); } #[test] fn test_read_utf16le_nobom() { let s = b"-\x00-\x00-\x00 \x00a\x00:\x00 \x001\x00 \x00b\x00:\x00 \x002\x00.\x002\x00 \x00c\x00:\x00 \x00[\x001\x00,\x00 \x002\x00]\x00 \x00"; let out = YamlDecoder::read(s as &[u8]).decode().unwrap(); let doc = &out[0]; println!("GOT: {doc:?}"); assert_eq!(doc["a"].as_i64().unwrap(), 1i64); assert!((doc["b"].as_f64().unwrap() - 2.2f64).abs() <= f64::EPSILON); assert_eq!(doc["c"][1].as_i64().unwrap(), 2i64); assert!(doc["d"][0].is_badvalue()); } #[test] fn test_read_trap() { let s = b"--- a\xa9: 1 b: 2.2 c: [1, 2] "; let out = YamlDecoder::read(s as &[u8]) .encoding_trap(YAMLDecodingTrap::Ignore) .decode() .unwrap(); let doc = &out[0]; println!("GOT: {doc:?}"); assert_eq!(doc["a"].as_i64().unwrap(), 1i64); assert!((doc["b"].as_f64().unwrap() - 2.2f64).abs() <= f64::EPSILON); assert_eq!(doc["c"][1].as_i64().unwrap(), 2i64); assert!(doc["d"][0].is_badvalue()); } #[test] fn test_or() { assert_eq!(Yaml::Null.or(Yaml::Integer(3)), Yaml::Integer(3)); assert_eq!(Yaml::Integer(3).or(Yaml::Integer(7)), Yaml::Integer(3)); } } yaml-rust2-0.10.1/tests/basic.rs000064400000000000000000000260121046102023000145140ustar 00000000000000#![allow(clippy::bool_assert_comparison)] #![allow(clippy::float_cmp)] use std::vec; use yaml_rust2::{Yaml, YamlEmitter, YamlLoader}; #[test] fn test_api() { let s = " # from yaml-cpp example - name: Ogre position: [0, 5, 0] powers: - name: Club damage: 10 - name: Fist damage: 8 - name: Dragon position: [1, 0, 10] powers: - name: Fire Breath damage: 25 - name: Claws damage: 15 - name: Wizard position: [5, -3, 0] powers: - name: Acid Rain damage: 50 - name: Staff damage: 3 "; let docs = YamlLoader::load_from_str(s).unwrap(); let doc = &docs[0]; assert_eq!(doc[0]["name"].as_str().unwrap(), "Ogre"); let mut writer = String::new(); { let mut emitter = YamlEmitter::new(&mut writer); emitter.dump(doc).unwrap(); } assert!(!writer.is_empty()); } #[test] fn test_fail() { let s = " # syntax error scalar key: [1, 2]] key1:a2 "; let Err(error) = YamlLoader::load_from_str(s) else { panic!() }; assert_eq!( error.info(), "mapping values are not allowed in this context" ); assert_eq!( error.to_string(), "mapping values are not allowed in this context at byte 26 line 4 column 4" ); } #[test] fn test_coerce() { let s = "--- a: 1 b: 2.2 c: [1, 2] "; let out = YamlLoader::load_from_str(s).unwrap(); let doc = &out[0]; assert_eq!(doc["a"].as_i64().unwrap(), 1i64); assert_eq!(doc["b"].as_f64().unwrap(), 2.2f64); assert_eq!(doc["c"][1].as_i64().unwrap(), 2i64); assert!(doc["d"][0].is_badvalue()); } #[test] fn test_empty_doc() { let s: String = String::new(); YamlLoader::load_from_str(&s).unwrap(); let s: String = "---".to_owned(); assert_eq!(YamlLoader::load_from_str(&s).unwrap()[0], Yaml::Null); } #[test] fn test_parser() { let s: String = " # comment a0 bb: val a1: b1: 4 b2: d a2: 4 # i'm comment a3: [1, 2, 3] a4: - - a1 - a2 - 2 a5: 'single_quoted' a6: \"double_quoted\" a7: 你好 " .to_owned(); let out = YamlLoader::load_from_str(&s).unwrap(); let doc = &out[0]; assert_eq!(doc["a7"].as_str().unwrap(), "你好"); } #[test] fn test_multi_doc() { let s = " 'a scalar' --- 'a scalar' --- 'a scalar' "; let out = YamlLoader::load_from_str(s).unwrap(); assert_eq!(out.len(), 3); } #[test] fn test_anchor() { let s = " a1: &DEFAULT b1: 4 b2: d a2: *DEFAULT "; let out = YamlLoader::load_from_str(s).unwrap(); let doc = &out[0]; assert_eq!(doc["a2"]["b1"].as_i64().unwrap(), 4); } #[test] fn test_bad_anchor() { let s = " a1: &DEFAULT b1: 4 b2: *DEFAULT "; let out = YamlLoader::load_from_str(s).unwrap(); let doc = &out[0]; assert_eq!(doc["a1"]["b2"], Yaml::BadValue); } #[test] fn test_github_27() { // https://github.com/chyh1990/yaml-rust/issues/27 let s = "&a"; let out = YamlLoader::load_from_str(s).unwrap(); let doc = &out[0]; assert_eq!(*doc, Yaml::Null); } #[test] fn test_plain_datatype() { let s = " - 'string' - \"string\" - string - 123 - -321 - 1.23 - -1e4 - ~ - null - true - false - !!str 0 - !!int 100 - !!float 2 - !!null ~ - !!bool true - !!bool false - 0xFF # bad values - !!int string - !!float string - !!bool null - !!null val - 0o77 - [ 0xF, 0xF ] - +12345 - [ true, false ] "; let out = YamlLoader::load_from_str(s).unwrap(); let doc = &out[0]; assert_eq!(doc[0].as_str().unwrap(), "string"); assert_eq!(doc[1].as_str().unwrap(), "string"); assert_eq!(doc[2].as_str().unwrap(), "string"); assert_eq!(doc[3].as_i64().unwrap(), 123); assert_eq!(doc[4].as_i64().unwrap(), -321); assert_eq!(doc[5].as_f64().unwrap(), 1.23); assert_eq!(doc[6].as_f64().unwrap(), -1e4); assert!(doc[7].is_null()); assert!(doc[8].is_null()); assert_eq!(doc[9].as_bool().unwrap(), true); assert_eq!(doc[10].as_bool().unwrap(), false); assert_eq!(doc[11].as_str().unwrap(), "0"); assert_eq!(doc[12].as_i64().unwrap(), 100); assert_eq!(doc[13].as_f64().unwrap(), 2.0); assert!(doc[14].is_null()); assert_eq!(doc[15].as_bool().unwrap(), true); assert_eq!(doc[16].as_bool().unwrap(), false); assert_eq!(doc[17].as_i64().unwrap(), 255); assert!(doc[18].is_badvalue()); assert!(doc[19].is_badvalue()); assert!(doc[20].is_badvalue()); assert!(doc[21].is_badvalue()); assert_eq!(doc[22].as_i64().unwrap(), 63); assert_eq!(doc[23][0].as_i64().unwrap(), 15); assert_eq!(doc[23][1].as_i64().unwrap(), 15); assert_eq!(doc[24].as_i64().unwrap(), 12345); assert!(doc[25][0].as_bool().unwrap()); assert!(!doc[25][1].as_bool().unwrap()); } #[test] fn test_bad_hyphen() { // See: https://github.com/chyh1990/yaml-rust/issues/23 let s = "{-"; assert!(YamlLoader::load_from_str(s).is_err()); } #[test] fn test_issue_65() { // See: https://github.com/chyh1990/yaml-rust/issues/65 let b = "\n\"ll\\\"ll\\\r\n\"ll\\\"ll\\\r\r\r\rU\r\r\rU"; assert!(YamlLoader::load_from_str(b).is_err()); } #[test] fn test_issue_65_mwe() { // A MWE for `test_issue_65`. The error over there is that there is invalid trailing content // after a double quoted string. let b = r#""foo" l"#; assert!(YamlLoader::load_from_str(b).is_err()); } #[test] fn test_comment_after_tag() { // https://github.com/Ethiraric/yaml-rust2/issues/21#issuecomment-2053513507 let s = " %YAML 1.2 # This is a comment --- #------- foobar"; assert_eq!( YamlLoader::load_from_str(s), Ok(vec![Yaml::String(String::from("foobar"))]) ); } #[test] fn test_large_block_scalar_indent() { // https://github.com/Ethiraric/yaml-rust2/issues/29 // Tests the `loop` fallback of `skip_block_scalar_indent`. The indent in the YAML string must // be greater than `BUFFER_LEN - 2`. The second line is further indented with spaces, and the // resulting string should be "a\n b". let s = " a: |- a b "; let doc = &YamlLoader::load_from_str(s).unwrap()[0]; let Yaml::Hash(map) = doc else { dbg!(doc); panic!() }; assert_eq!(map.len(), 1); assert_eq!( map.get(&Yaml::String("a".to_string())), Some(&Yaml::String(String::from("a\n b"))) ); } #[test] fn test_bad_docstart() { assert!(YamlLoader::load_from_str("---This used to cause an infinite loop").is_ok()); assert_eq!( YamlLoader::load_from_str("----"), Ok(vec![Yaml::String(String::from("----"))]) ); assert_eq!( YamlLoader::load_from_str("--- #here goes a comment"), Ok(vec![Yaml::Null]) ); assert_eq!( YamlLoader::load_from_str("---- #here goes a comment"), Ok(vec![Yaml::String(String::from("----"))]) ); } #[test] fn test_plain_datatype_with_into_methods() { let s = " - 'string' - \"string\" - string - 123 - -321 - 1.23 - -1e4 - true - false - !!str 0 - !!int 100 - !!float 2 - !!bool true - !!bool false - 0xFF - 0o77 - +12345 - -.INF - .NAN - !!float .INF "; let mut out = YamlLoader::load_from_str(s).unwrap().into_iter(); let mut doc = out.next().unwrap().into_iter(); assert_eq!(doc.next().unwrap().into_string().unwrap(), "string"); assert_eq!(doc.next().unwrap().into_string().unwrap(), "string"); assert_eq!(doc.next().unwrap().into_string().unwrap(), "string"); assert_eq!(doc.next().unwrap().into_i64().unwrap(), 123); assert_eq!(doc.next().unwrap().into_i64().unwrap(), -321); assert_eq!(doc.next().unwrap().into_f64().unwrap(), 1.23); assert_eq!(doc.next().unwrap().into_f64().unwrap(), -1e4); assert_eq!(doc.next().unwrap().into_bool().unwrap(), true); assert_eq!(doc.next().unwrap().into_bool().unwrap(), false); assert_eq!(doc.next().unwrap().into_string().unwrap(), "0"); assert_eq!(doc.next().unwrap().into_i64().unwrap(), 100); assert_eq!(doc.next().unwrap().into_f64().unwrap(), 2.0); assert_eq!(doc.next().unwrap().into_bool().unwrap(), true); assert_eq!(doc.next().unwrap().into_bool().unwrap(), false); assert_eq!(doc.next().unwrap().into_i64().unwrap(), 255); assert_eq!(doc.next().unwrap().into_i64().unwrap(), 63); assert_eq!(doc.next().unwrap().into_i64().unwrap(), 12345); assert_eq!(doc.next().unwrap().into_f64().unwrap(), f64::NEG_INFINITY); assert!(doc.next().unwrap().into_f64().is_some()); assert_eq!(doc.next().unwrap().into_f64().unwrap(), f64::INFINITY); } #[test] fn test_hash_order() { let s = "--- b: ~ a: ~ c: ~ "; let out = YamlLoader::load_from_str(s).unwrap(); let first = out.into_iter().next().unwrap(); let mut iter = first.into_hash().unwrap().into_iter(); assert_eq!( Some((Yaml::String("b".to_owned()), Yaml::Null)), iter.next() ); assert_eq!( Some((Yaml::String("a".to_owned()), Yaml::Null)), iter.next() ); assert_eq!( Some((Yaml::String("c".to_owned()), Yaml::Null)), iter.next() ); assert_eq!(None, iter.next()); } #[test] fn test_integer_key() { let s = " 0: important: true 1: important: false "; let out = YamlLoader::load_from_str(s).unwrap(); let first = out.into_iter().next().unwrap(); assert_eq!(first[0]["important"].as_bool().unwrap(), true); } #[test] fn test_indentation_equality() { let four_spaces = YamlLoader::load_from_str( r" hash: with: indentations ", ) .unwrap() .into_iter() .next() .unwrap(); let two_spaces = YamlLoader::load_from_str( r" hash: with: indentations ", ) .unwrap() .into_iter() .next() .unwrap(); let one_space = YamlLoader::load_from_str( r" hash: with: indentations ", ) .unwrap() .into_iter() .next() .unwrap(); let mixed_spaces = YamlLoader::load_from_str( r" hash: with: indentations ", ) .unwrap() .into_iter() .next() .unwrap(); assert_eq!(four_spaces, two_spaces); assert_eq!(two_spaces, one_space); assert_eq!(four_spaces, mixed_spaces); } #[test] fn test_two_space_indentations() { // https://github.com/kbknapp/clap-rs/issues/965 let s = r" subcommands: - server: about: server related commands subcommands2: - server: about: server related commands subcommands3: - server: about: server related commands "; let out = YamlLoader::load_from_str(s).unwrap(); let doc = &out.into_iter().next().unwrap(); println!("{doc:#?}"); assert_eq!(doc["subcommands"][0]["server"], Yaml::Null); assert!(doc["subcommands2"][0]["server"].as_hash().is_some()); assert!(doc["subcommands3"][0]["server"].as_hash().is_some()); } #[test] fn test_recursion_depth_check_objects() { let s = "{a:".repeat(10_000) + &"}".repeat(10_000); assert!(YamlLoader::load_from_str(&s).is_err()); } #[test] fn test_recursion_depth_check_arrays() { let s = "[".repeat(10_000) + &"]".repeat(10_000); assert!(YamlLoader::load_from_str(&s).is_err()); } #[test] fn test_mapping_duplicates() { let s = r" a: foo a: bar"; assert!(YamlLoader::load_from_str(s).is_err()); } yaml-rust2-0.10.1/tests/emitter.rs000064400000000000000000000122031046102023000151010ustar 00000000000000use yaml_rust2::{YamlEmitter, YamlLoader}; #[allow(clippy::similar_names)] #[test] fn test_emit_simple() { let s = " # comment a0 bb: val a1: b1: 4 b2: d a2: 4 # i'm comment a3: [1, 2, 3] a4: - [a1, a2] - 2 "; let docs = YamlLoader::load_from_str(s).unwrap(); let doc = &docs[0]; let mut writer = String::new(); { let mut emitter = YamlEmitter::new(&mut writer); emitter.dump(doc).unwrap(); } println!("original:\n{s}"); println!("emitted:\n{writer}"); let docs_new = match YamlLoader::load_from_str(&writer) { Ok(y) => y, Err(e) => panic!("{}", e), }; let doc_new = &docs_new[0]; assert_eq!(doc, doc_new); } #[test] fn test_emit_complex() { let s = r" catalogue: product1: &coffee { name: Coffee, price: 2.5 , unit: 1l } product2: &cookies { name: Cookies!, price: 3.40 , unit: 400g} products: *coffee : amount: 4 *cookies : amount: 4 [1,2,3,4]: array key 2.4: real key true: bool key {}: empty hash key "; let docs = YamlLoader::load_from_str(s).unwrap(); let doc = &docs[0]; let mut writer = String::new(); { let mut emitter = YamlEmitter::new(&mut writer); emitter.dump(doc).unwrap(); } let docs_new = match YamlLoader::load_from_str(&writer) { Ok(y) => y, Err(e) => panic!("{}", e), }; let new_doc = &docs_new[0]; assert_eq!(doc, new_doc); } #[test] fn test_emit_avoid_quotes() { let s = r#"--- a7: 你好 boolean: "true" boolean2: "false" date: 2014-12-31 empty_string: "" empty_string1: " " empty_string2: " a" empty_string3: " a " exp: "12e7" field: ":" field2: "{" field3: "\\" field4: "\n" field5: "can't avoid quote" float: "2.6" int: "4" nullable: "null" nullable2: "~" products: "*coffee": amount: 4 "*cookies": amount: 4 ".milk": amount: 1 "2.4": real key "[1,2,3,4]": array key "true": bool key "{}": empty hash key x: test z: string with spaces"#; let docs = YamlLoader::load_from_str(s).unwrap(); let doc = &docs[0]; let mut writer = String::new(); { let mut emitter = YamlEmitter::new(&mut writer); emitter.dump(doc).unwrap(); } assert_eq!(s, writer, "actual:\n\n{writer}\n"); } #[test] fn emit_quoted_bools() { let input = r#"--- string0: yes string1: no string2: "true" string3: "false" string4: "~" null0: ~ [true, false]: real_bools [True, TRUE, False, FALSE, y,Y,yes,Yes,YES,n,N,no,No,NO,on,On,ON,off,Off,OFF]: false_bools bool0: true bool1: false"#; let expected = r#"--- string0: "yes" string1: "no" string2: "true" string3: "false" string4: "~" null0: ~ ? - true - false : real_bools ? - "True" - "TRUE" - "False" - "FALSE" - "y" - "Y" - "yes" - "Yes" - "YES" - "n" - "N" - "no" - "No" - "NO" - "on" - "On" - "ON" - "off" - "Off" - "OFF" : false_bools bool0: true bool1: false"#; let docs = YamlLoader::load_from_str(input).unwrap(); let doc = &docs[0]; let mut writer = String::new(); { let mut emitter = YamlEmitter::new(&mut writer); emitter.dump(doc).unwrap(); } assert_eq!( expected, writer, "expected:\n{expected}\nactual:\n{writer}\n", ); } #[test] fn test_empty_and_nested() { test_empty_and_nested_flag(false); } #[test] fn test_empty_and_nested_compact() { test_empty_and_nested_flag(true); } fn test_empty_and_nested_flag(compact: bool) { let s = if compact { r"--- a: b: c: hello d: {} e: - f - g - h: []" } else { r"--- a: b: c: hello d: {} e: - f - g - h: []" }; let docs = YamlLoader::load_from_str(s).unwrap(); let doc = &docs[0]; let mut writer = String::new(); { let mut emitter = YamlEmitter::new(&mut writer); emitter.compact(compact); emitter.dump(doc).unwrap(); } assert_eq!(s, writer); } #[test] fn test_nested_arrays() { let s = r"--- a: - b - - c - d - - e - f"; let docs = YamlLoader::load_from_str(s).unwrap(); let doc = &docs[0]; let mut writer = String::new(); { let mut emitter = YamlEmitter::new(&mut writer); emitter.dump(doc).unwrap(); } println!("original:\n{s}"); println!("emitted:\n{writer}"); assert_eq!(s, writer); } #[test] fn test_deeply_nested_arrays() { let s = r"--- a: - b - - c - d - - e - - f - - e"; let docs = YamlLoader::load_from_str(s).unwrap(); let doc = &docs[0]; let mut writer = String::new(); { let mut emitter = YamlEmitter::new(&mut writer); emitter.dump(doc).unwrap(); } println!("original:\n{s}"); println!("emitted:\n{writer}"); assert_eq!(s, writer); } #[test] fn test_nested_hashes() { let s = r"--- a: b: c: d: e: f"; let docs = YamlLoader::load_from_str(s).unwrap(); let doc = &docs[0]; let mut writer = String::new(); { let mut emitter = YamlEmitter::new(&mut writer); emitter.dump(doc).unwrap(); } println!("original:\n{s}"); println!("emitted:\n{writer}"); assert_eq!(s, writer); } yaml-rust2-0.10.1/tests/quickcheck.rs000064400000000000000000000012511046102023000155430ustar 00000000000000extern crate yaml_rust2; #[macro_use] extern crate quickcheck; use quickcheck::TestResult; use yaml_rust2::{Yaml, YamlEmitter, YamlLoader}; quickcheck! { fn test_check_weird_keys(xs: Vec) -> TestResult { let mut out_str = String::new(); let input = Yaml::Array(xs.into_iter().map(Yaml::String).collect()); { let mut emitter = YamlEmitter::new(&mut out_str); emitter.dump(&input).unwrap(); } match YamlLoader::load_from_str(&out_str) { Ok(output) => TestResult::from_bool(output.len() == 1 && input == output[0]), Err(err) => TestResult::error(err.to_string()), } } } yaml-rust2-0.10.1/tests/scanner.rs000064400000000000000000000250201046102023000150620ustar 00000000000000#![allow(clippy::enum_glob_use)] use yaml_rust2::{scanner::TokenType::*, scanner::*}; macro_rules! next { ($p:ident, $tk:pat) => {{ let tok = $p.next().unwrap(); match tok.1 { $tk => {} _ => panic!("unexpected token: {:?}", tok), } }}; } macro_rules! next_scalar { ($p:ident, $tk:expr, $v:expr) => {{ let tok = $p.next().unwrap(); match tok.1 { Scalar(style, ref v) => { assert_eq!(style, $tk); assert_eq!(v, $v); } _ => panic!("unexpected token: {:?}", tok), } }}; } macro_rules! end { ($p:ident) => {{ assert_eq!($p.next(), None); }}; } /// test cases in libyaml scanner.c #[test] fn test_empty() { let s = ""; let mut p = Scanner::new(s.chars()); next!(p, StreamStart(..)); next!(p, StreamEnd); end!(p); } #[test] fn test_scalar() { let s = "a scalar"; let mut p = Scanner::new(s.chars()); next!(p, StreamStart(..)); next!(p, Scalar(TScalarStyle::Plain, _)); next!(p, StreamEnd); end!(p); } #[test] fn test_explicit_scalar() { let s = "--- 'a scalar' ... "; let mut p = Scanner::new(s.chars()); next!(p, StreamStart(..)); next!(p, DocumentStart); next!(p, Scalar(TScalarStyle::SingleQuoted, _)); next!(p, DocumentEnd); next!(p, StreamEnd); end!(p); } #[test] fn test_multiple_documents() { let s = " 'a scalar' --- 'a scalar' --- 'a scalar' "; let mut p = Scanner::new(s.chars()); next!(p, StreamStart(..)); next!(p, Scalar(TScalarStyle::SingleQuoted, _)); next!(p, DocumentStart); next!(p, Scalar(TScalarStyle::SingleQuoted, _)); next!(p, DocumentStart); next!(p, Scalar(TScalarStyle::SingleQuoted, _)); next!(p, StreamEnd); end!(p); } #[test] fn test_a_flow_sequence() { let s = "[item 1, item 2, item 3]"; let mut p = Scanner::new(s.chars()); next!(p, StreamStart(..)); next!(p, FlowSequenceStart); next_scalar!(p, TScalarStyle::Plain, "item 1"); next!(p, FlowEntry); next!(p, Scalar(TScalarStyle::Plain, _)); next!(p, FlowEntry); next!(p, Scalar(TScalarStyle::Plain, _)); next!(p, FlowSequenceEnd); next!(p, StreamEnd); end!(p); } #[test] fn test_a_flow_mapping() { let s = " { a simple key: a value, # Note that the KEY token is produced. ? a complex key: another value, } "; let mut p = Scanner::new(s.chars()); next!(p, StreamStart(..)); next!(p, FlowMappingStart); next!(p, Key); next!(p, Scalar(TScalarStyle::Plain, _)); next!(p, Value); next!(p, Scalar(TScalarStyle::Plain, _)); next!(p, FlowEntry); next!(p, Key); next_scalar!(p, TScalarStyle::Plain, "a complex key"); next!(p, Value); next!(p, Scalar(TScalarStyle::Plain, _)); next!(p, FlowEntry); next!(p, FlowMappingEnd); next!(p, StreamEnd); end!(p); } #[test] fn test_block_sequences() { let s = " - item 1 - item 2 - - item 3.1 - item 3.2 - key 1: value 1 key 2: value 2 "; let mut p = Scanner::new(s.chars()); next!(p, StreamStart(..)); next!(p, BlockSequenceStart); next!(p, BlockEntry); next_scalar!(p, TScalarStyle::Plain, "item 1"); next!(p, BlockEntry); next_scalar!(p, TScalarStyle::Plain, "item 2"); next!(p, BlockEntry); next!(p, BlockSequenceStart); next!(p, BlockEntry); next_scalar!(p, TScalarStyle::Plain, "item 3.1"); next!(p, BlockEntry); next_scalar!(p, TScalarStyle::Plain, "item 3.2"); next!(p, BlockEnd); next!(p, BlockEntry); next!(p, BlockMappingStart); next!(p, Key); next_scalar!(p, TScalarStyle::Plain, "key 1"); next!(p, Value); next_scalar!(p, TScalarStyle::Plain, "value 1"); next!(p, Key); next_scalar!(p, TScalarStyle::Plain, "key 2"); next!(p, Value); next_scalar!(p, TScalarStyle::Plain, "value 2"); next!(p, BlockEnd); next!(p, BlockEnd); next!(p, StreamEnd); end!(p); } #[test] fn test_block_mappings() { let s = " a simple key: a value # The KEY token is produced here. ? a complex key : another value a mapping: key 1: value 1 key 2: value 2 a sequence: - item 1 - item 2 "; let mut p = Scanner::new(s.chars()); next!(p, StreamStart(..)); next!(p, BlockMappingStart); next!(p, Key); next!(p, Scalar(_, _)); next!(p, Value); next!(p, Scalar(_, _)); next!(p, Key); next!(p, Scalar(_, _)); next!(p, Value); next!(p, Scalar(_, _)); next!(p, Key); next!(p, Scalar(_, _)); next!(p, Value); // libyaml comment seems to be wrong next!(p, BlockMappingStart); next!(p, Key); next!(p, Scalar(_, _)); next!(p, Value); next!(p, Scalar(_, _)); next!(p, Key); next!(p, Scalar(_, _)); next!(p, Value); next!(p, Scalar(_, _)); next!(p, BlockEnd); next!(p, Key); next!(p, Scalar(_, _)); next!(p, Value); next!(p, BlockSequenceStart); next!(p, BlockEntry); next!(p, Scalar(_, _)); next!(p, BlockEntry); next!(p, Scalar(_, _)); next!(p, BlockEnd); next!(p, BlockEnd); next!(p, StreamEnd); end!(p); } #[test] fn test_no_block_sequence_start() { let s = " key: - item 1 - item 2 "; let mut p = Scanner::new(s.chars()); next!(p, StreamStart(..)); next!(p, BlockMappingStart); next!(p, Key); next_scalar!(p, TScalarStyle::Plain, "key"); next!(p, Value); next!(p, BlockEntry); next_scalar!(p, TScalarStyle::Plain, "item 1"); next!(p, BlockEntry); next_scalar!(p, TScalarStyle::Plain, "item 2"); next!(p, BlockEnd); next!(p, StreamEnd); end!(p); } #[test] fn test_collections_in_sequence() { let s = " - - item 1 - item 2 - key 1: value 1 key 2: value 2 - ? complex key : complex value "; let mut p = Scanner::new(s.chars()); next!(p, StreamStart(..)); next!(p, BlockSequenceStart); next!(p, BlockEntry); next!(p, BlockSequenceStart); next!(p, BlockEntry); next_scalar!(p, TScalarStyle::Plain, "item 1"); next!(p, BlockEntry); next_scalar!(p, TScalarStyle::Plain, "item 2"); next!(p, BlockEnd); next!(p, BlockEntry); next!(p, BlockMappingStart); next!(p, Key); next_scalar!(p, TScalarStyle::Plain, "key 1"); next!(p, Value); next_scalar!(p, TScalarStyle::Plain, "value 1"); next!(p, Key); next_scalar!(p, TScalarStyle::Plain, "key 2"); next!(p, Value); next_scalar!(p, TScalarStyle::Plain, "value 2"); next!(p, BlockEnd); next!(p, BlockEntry); next!(p, BlockMappingStart); next!(p, Key); next_scalar!(p, TScalarStyle::Plain, "complex key"); next!(p, Value); next_scalar!(p, TScalarStyle::Plain, "complex value"); next!(p, BlockEnd); next!(p, BlockEnd); next!(p, StreamEnd); end!(p); } #[test] fn test_collections_in_mapping() { let s = " ? a sequence : - item 1 - item 2 ? a mapping : key 1: value 1 key 2: value 2 "; let mut p = Scanner::new(s.chars()); next!(p, StreamStart(..)); next!(p, BlockMappingStart); next!(p, Key); next_scalar!(p, TScalarStyle::Plain, "a sequence"); next!(p, Value); next!(p, BlockSequenceStart); next!(p, BlockEntry); next_scalar!(p, TScalarStyle::Plain, "item 1"); next!(p, BlockEntry); next_scalar!(p, TScalarStyle::Plain, "item 2"); next!(p, BlockEnd); next!(p, Key); next_scalar!(p, TScalarStyle::Plain, "a mapping"); next!(p, Value); next!(p, BlockMappingStart); next!(p, Key); next_scalar!(p, TScalarStyle::Plain, "key 1"); next!(p, Value); next_scalar!(p, TScalarStyle::Plain, "value 1"); next!(p, Key); next_scalar!(p, TScalarStyle::Plain, "key 2"); next!(p, Value); next_scalar!(p, TScalarStyle::Plain, "value 2"); next!(p, BlockEnd); next!(p, BlockEnd); next!(p, StreamEnd); end!(p); } #[test] fn test_spec_ex7_3() { let s = " { ? foo :, : bar, } "; let mut p = Scanner::new(s.chars()); next!(p, StreamStart(..)); next!(p, FlowMappingStart); next!(p, Key); next_scalar!(p, TScalarStyle::Plain, "foo"); next!(p, Value); next!(p, FlowEntry); next!(p, Value); next_scalar!(p, TScalarStyle::Plain, "bar"); next!(p, FlowEntry); next!(p, FlowMappingEnd); next!(p, StreamEnd); end!(p); } #[test] fn test_plain_scalar_starting_with_indicators_in_flow() { // "Plain scalars must not begin with most indicators, as this would cause ambiguity with // other YAML constructs. However, the “:”, “?” and “-” indicators may be used as the first // character if followed by a non-space “safe” character, as this causes no ambiguity." let s = "{a: :b}"; let mut p = Scanner::new(s.chars()); next!(p, StreamStart(..)); next!(p, FlowMappingStart); next!(p, Key); next_scalar!(p, TScalarStyle::Plain, "a"); next!(p, Value); next_scalar!(p, TScalarStyle::Plain, ":b"); next!(p, FlowMappingEnd); next!(p, StreamEnd); end!(p); let s = "{a: ?b}"; let mut p = Scanner::new(s.chars()); next!(p, StreamStart(..)); next!(p, FlowMappingStart); next!(p, Key); next_scalar!(p, TScalarStyle::Plain, "a"); next!(p, Value); next_scalar!(p, TScalarStyle::Plain, "?b"); next!(p, FlowMappingEnd); next!(p, StreamEnd); end!(p); } #[test] fn test_plain_scalar_starting_with_indicators_in_block() { let s = ":a"; let mut p = Scanner::new(s.chars()); next!(p, StreamStart(..)); next_scalar!(p, TScalarStyle::Plain, ":a"); next!(p, StreamEnd); end!(p); let s = "?a"; let mut p = Scanner::new(s.chars()); next!(p, StreamStart(..)); next_scalar!(p, TScalarStyle::Plain, "?a"); next!(p, StreamEnd); end!(p); } #[test] fn test_plain_scalar_containing_indicators_in_block() { let s = "a:,b"; let mut p = Scanner::new(s.chars()); next!(p, StreamStart(..)); next_scalar!(p, TScalarStyle::Plain, "a:,b"); next!(p, StreamEnd); end!(p); let s = ":,b"; let mut p = Scanner::new(s.chars()); next!(p, StreamStart(..)); next_scalar!(p, TScalarStyle::Plain, ":,b"); next!(p, StreamEnd); end!(p); } #[test] fn test_scanner_cr() { let s = "---\r\n- tok1\r\n- tok2"; let mut p = Scanner::new(s.chars()); next!(p, StreamStart(..)); next!(p, DocumentStart); next!(p, BlockSequenceStart); next!(p, BlockEntry); next_scalar!(p, TScalarStyle::Plain, "tok1"); next!(p, BlockEntry); next_scalar!(p, TScalarStyle::Plain, "tok2"); next!(p, BlockEnd); next!(p, StreamEnd); end!(p); } #[test] fn test_uri() { // TODO } #[test] fn test_uri_escapes() { // TODO } yaml-rust2-0.10.1/tests/spec_test.rs000064400000000000000000000070011046102023000154210ustar 00000000000000#![allow(dead_code)] #![allow(non_upper_case_globals)] extern crate yaml_rust2; use yaml_rust2::parser::{Event, EventReceiver, Parser}; use yaml_rust2::scanner::TScalarStyle; // These names match the names used in the C++ test suite. #[allow(clippy::enum_variant_names)] #[derive(Clone, PartialEq, PartialOrd, Debug)] enum TestEvent { OnDocumentStart, OnDocumentEnd, OnSequenceStart, OnSequenceEnd, OnMapStart, OnMapEnd, OnScalar, OnAlias, OnNull, } struct YamlChecker { pub evs: Vec, } impl EventReceiver for YamlChecker { fn on_event(&mut self, ev: Event) { let tev = match ev { Event::DocumentStart => TestEvent::OnDocumentStart, Event::DocumentEnd => TestEvent::OnDocumentEnd, Event::SequenceStart(..) => TestEvent::OnSequenceStart, Event::SequenceEnd => TestEvent::OnSequenceEnd, Event::MappingStart(..) => TestEvent::OnMapStart, Event::MappingEnd => TestEvent::OnMapEnd, Event::Scalar(ref v, style, _, tag) => { if (v == "~" || v.is_empty()) && style == TScalarStyle::Plain && tag.is_none() { TestEvent::OnNull } else { TestEvent::OnScalar } } Event::Alias(_) => TestEvent::OnAlias, _ => return, // ignore other events }; self.evs.push(tev); } } fn str_to_test_events(docs: &str) -> Vec { let mut p = YamlChecker { evs: Vec::new() }; let mut parser = Parser::new_from_str(docs); parser.load(&mut p, true).unwrap(); p.evs } macro_rules! assert_next { ($v:expr, $p:pat) => { match $v.next().unwrap() { $p => {} e => { panic!("unexpected event: {:?} (expected {:?})", e, stringify!($p)); } } }; } // auto generated from handler_spec_test.cpp include!("specexamples.rs.inc"); include!("spec_test.rs.inc"); // hand-crafted tests //#[test] //fn test_hc_alias() { //} #[test] fn test_mapvec_legal() { use yaml_rust2::yaml::{Hash, Yaml}; use yaml_rust2::{YamlEmitter, YamlLoader}; // Emitting a `map>, _>` should result in legal yaml that // we can parse. let key = vec![Yaml::Integer(1), Yaml::Integer(2), Yaml::Integer(3)]; let mut keyhash = Hash::new(); keyhash.insert(Yaml::String("key".into()), Yaml::Array(key)); let val = vec![Yaml::Integer(4), Yaml::Integer(5), Yaml::Integer(6)]; let mut hash = Hash::new(); hash.insert(Yaml::Hash(keyhash), Yaml::Array(val)); let mut out_str = String::new(); { let mut emitter = YamlEmitter::new(&mut out_str); emitter.dump(&Yaml::Hash(hash)).unwrap(); } // At this point, we are tempted to naively render like this: // // ```yaml // --- // {key: // - 1 // - 2 // - 3}: // - 4 // - 5 // - 6 // ``` // // However, this doesn't work, because the key sequence [1, 2, 3] is // rendered in block mode, which is not legal (as far as I can tell) // inside the flow mode of the key. We need to either fully render // everything that's in a key in flow mode (which may make for some // long lines), or use the explicit map identifier '?': // // ```yaml // --- // ? // key: // - 1 // - 2 // - 3 // : // - 4 // - 5 // - 6 // ``` YamlLoader::load_from_str(&out_str).unwrap(); } yaml-rust2-0.10.1/tests/spec_test.rs.inc000064400000000000000000001545441046102023000162100ustar 00000000000000#[test] fn test_ex2_1_seq_scalars() { let mut v = str_to_test_events(EX2_1).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex2_2_mapping_scalars_to_scalars() { let mut v = str_to_test_events(EX2_2).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex2_3_mapping_scalars_to_sequences() { let mut v = str_to_test_events(EX2_3).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex2_4_sequence_of_mappings() { let mut v = str_to_test_events(EX2_4).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex2_5_sequence_of_sequences() { let mut v = str_to_test_events(EX2_5).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex2_6_mapping_of_mappings() { let mut v = str_to_test_events(EX2_6).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex2_7_two_documents_in_a_stream() { let mut v = str_to_test_events(EX2_7).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex2_8_play_by_play_feed() { let mut v = str_to_test_events(EX2_8).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex2_9_single_document_with_two_comments() { let mut v = str_to_test_events(EX2_9).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex2_10_simple_anchor() { let mut v = str_to_test_events(EX2_10).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnAlias); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex2_11_mapping_between_sequences() { let mut v = str_to_test_events(EX2_11).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex2_12_compact_nested_mapping() { let mut v = str_to_test_events(EX2_12).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex2_13_in_literals_newlines_are_preserved() { let mut v = str_to_test_events(EX2_13).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex2_14_in_folded_scalars_newlines_become_spaces() { let mut v = str_to_test_events(EX2_14).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex2_15_folded_newlines_are_preserved_for_more_indented_and_blank_lines() { let mut v = str_to_test_events(EX2_15).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex2_16_indentation_determines_scope() { let mut v = str_to_test_events(EX2_16).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex2_17_quoted_scalars() { let mut v = str_to_test_events(EX2_17).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex2_18_multi_line_flow_scalars() { let mut v = str_to_test_events(EX2_18).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex2_23_various_explicit_tags() { let mut v = str_to_test_events(EX2_23).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex2_24_global_tags() { let mut v = str_to_test_events(EX2_24).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnAlias); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnAlias); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex2_25_unordered_sets() { let mut v = str_to_test_events(EX2_25).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnNull); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnNull); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnNull); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex2_26_ordered_mappings() { let mut v = str_to_test_events(EX2_26).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex2_27_invoice() { let mut v = str_to_test_events(EX2_27).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnAlias); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex2_28_log_file() { let mut v = str_to_test_events(EX2_28).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex5_3_block_structure_indicators() { let mut v = str_to_test_events(EX5_3).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex5_4_flow_structure_indicators() { let mut v = str_to_test_events(EX5_4).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex5_6_node_property_indicators() { let mut v = str_to_test_events(EX5_6).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnAlias); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex5_7_block_scalar_indicators() { let mut v = str_to_test_events(EX5_7).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex5_8_quoted_scalar_indicators() { let mut v = str_to_test_events(EX5_8).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex5_11_line_break_characters() { let mut v = str_to_test_events(EX5_11).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex5_12_tabs_and_spaces() { let mut v = str_to_test_events(EX5_12).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex5_13_escaped_characters() { let mut v = str_to_test_events(EX5_13).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex6_1_indentation_spaces() { let mut v = str_to_test_events(EX6_1).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex6_2_indentation_indicators() { let mut v = str_to_test_events(EX6_2).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex6_3_separation_spaces() { let mut v = str_to_test_events(EX6_3).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex6_4_line_prefixes() { let mut v = str_to_test_events(EX6_4).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex6_5_empty_lines() { let mut v = str_to_test_events(EX6_5).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex6_6_line_folding() { let mut v = str_to_test_events(EX6_6).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex6_7_block_folding() { let mut v = str_to_test_events(EX6_7).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex6_8_flow_folding() { let mut v = str_to_test_events(EX6_8).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex6_9_separated_comment() { let mut v = str_to_test_events(EX6_9).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex6_12_separation_spaces_ii() { let mut v = str_to_test_events(EX6_12).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex6_13_reserved_directives() { let mut v = str_to_test_events(EX6_13).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex6_14_yaml_directive() { let mut v = str_to_test_events(EX6_14).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex6_16_tag_directive() { let mut v = str_to_test_events(EX6_16).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex6_18_primary_tag_handle() { let mut v = str_to_test_events(EX6_18).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex6_19_secondary_tag_handle() { let mut v = str_to_test_events(EX6_19).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex6_20_tag_handles() { let mut v = str_to_test_events(EX6_20).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex6_21_local_tag_prefix() { let mut v = str_to_test_events(EX6_21).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex6_22_global_tag_prefix() { let mut v = str_to_test_events(EX6_22).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex6_23_node_properties() { let mut v = str_to_test_events(EX6_23).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnAlias); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex6_24_verbatim_tags() { let mut v = str_to_test_events(EX6_24).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex6_26_tag_shorthands() { let mut v = str_to_test_events(EX6_26).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex6_28_non_specific_tags() { let mut v = str_to_test_events(EX6_28).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex6_29_node_anchors() { let mut v = str_to_test_events(EX6_29).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnAlias); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex7_1_alias_nodes() { let mut v = str_to_test_events(EX7_1).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnAlias); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnAlias); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[allow(dead_code)] fn test_ex7_2_empty_nodes() { let mut v = str_to_test_events(EX7_2).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex7_3_completely_empty_nodes() { let mut v = str_to_test_events(EX7_3).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnNull); assert_next!(v, TestEvent::OnNull); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex7_4_double_quoted_implicit_keys() { let mut v = str_to_test_events(EX7_4).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex7_5_double_quoted_line_breaks() { let mut v = str_to_test_events(EX7_5).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex7_6_double_quoted_lines() { let mut v = str_to_test_events(EX7_6).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex7_7_single_quoted_characters() { let mut v = str_to_test_events(EX7_7).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex7_8_single_quoted_implicit_keys() { let mut v = str_to_test_events(EX7_8).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex7_9_single_quoted_lines() { let mut v = str_to_test_events(EX7_9).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[allow(dead_code)] fn test_ex7_10_plain_characters() { let mut v = str_to_test_events(EX7_10).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex7_11_plain_implicit_keys() { let mut v = str_to_test_events(EX7_11).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex7_12_plain_lines() { let mut v = str_to_test_events(EX7_12).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex7_13_flow_sequence() { let mut v = str_to_test_events(EX7_13).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex7_14_flow_sequence_entries() { let mut v = str_to_test_events(EX7_14).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex7_15_flow_mappings() { let mut v = str_to_test_events(EX7_15).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex7_16_flow_mapping_entries() { let mut v = str_to_test_events(EX7_16).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnNull); assert_next!(v, TestEvent::OnNull); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[allow(dead_code)] fn test_ex7_17_flow_mapping_separate_values() { let mut v = str_to_test_events(EX7_17).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnNull); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnNull); assert_next!(v, TestEvent::OnNull); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex7_18_flow_mapping_adjacent_values() { let mut v = str_to_test_events(EX7_18).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnNull); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex7_19_single_pair_flow_mappings() { let mut v = str_to_test_events(EX7_19).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex7_20_single_pair_explicit_entry() { let mut v = str_to_test_events(EX7_20).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[allow(dead_code)] fn test_ex7_21_single_pair_implicit_entries() { let mut v = str_to_test_events(EX7_21).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnNull); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex7_23_flow_content() { let mut v = str_to_test_events(EX7_23).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex7_24_flow_nodes() { let mut v = str_to_test_events(EX7_24).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnAlias); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex8_1_block_scalar_header() { let mut v = str_to_test_events(EX8_1).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[allow(dead_code)] fn test_ex8_2_block_indentation_header() { let mut v = str_to_test_events(EX8_2).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex8_4_chomping_final_line_break() { let mut v = str_to_test_events(EX8_4).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex8_6_empty_scalar_chomping() { let mut v = str_to_test_events(EX8_6).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex8_7_literal_scalar() { let mut v = str_to_test_events(EX8_7).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex8_8_literal_content() { let mut v = str_to_test_events(EX8_8).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex8_9_folded_scalar() { let mut v = str_to_test_events(EX8_9).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex8_10_folded_lines() { let mut v = str_to_test_events(EX8_10).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex8_11_more_indented_lines() { let mut v = str_to_test_events(EX8_11).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex8_12_empty_separation_lines() { let mut v = str_to_test_events(EX8_12).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex8_13_final_empty_lines() { let mut v = str_to_test_events(EX8_13).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex8_14_block_sequence() { let mut v = str_to_test_events(EX8_14).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex8_15_block_sequence_entry_types() { let mut v = str_to_test_events(EX8_15).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnNull); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex8_16_block_mappings() { let mut v = str_to_test_events(EX8_16).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex8_17_explicit_block_mapping_entries() { let mut v = str_to_test_events(EX8_17).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnNull); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex8_18_implicit_block_mapping_entries() { let mut v = str_to_test_events(EX8_18).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnNull); assert_next!(v, TestEvent::OnNull); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex8_19_compact_block_mappings() { let mut v = str_to_test_events(EX8_19).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex8_20_block_node_types() { let mut v = str_to_test_events(EX8_20).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnDocumentEnd); } #[test] fn test_ex8_22_block_collection_nodes() { let mut v = str_to_test_events(EX8_22).into_iter(); assert_next!(v, TestEvent::OnDocumentStart); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnSequenceEnd); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapStart); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnScalar); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnMapEnd); assert_next!(v, TestEvent::OnDocumentEnd); } yaml-rust2-0.10.1/tests/specexamples.rs.inc000064400000000000000000000312241046102023000166750ustar 00000000000000const EX2_1 : &str = "- Mark McGwire\n- Sammy Sosa\n- Ken Griffey"; const EX2_2 : &str = "hr: 65 # Home runs\navg: 0.278 # Batting average\nrbi: 147 # Runs Batted In"; const EX2_3 : &str = "american:\n- Boston Red Sox\n- Detroit Tigers\n- New York Yankees\nnational:\n- New York Mets\n- Chicago Cubs\n- Atlanta Braves"; const EX2_4 : &str = "-\n name: Mark McGwire\n hr: 65\n avg: 0.278\n-\n name: Sammy Sosa\n hr: 63\n avg: 0.288"; const EX2_5 : &str = "- [name , hr, avg ]\n- [Mark McGwire, 65, 0.278]\n- [Sammy Sosa , 63, 0.288]"; const EX2_6 : &str = "Mark McGwire: {hr: 65, avg: 0.278}\nSammy Sosa: {\n hr: 63,\n avg: 0.288\n }"; const EX2_7 : &str = "# Ranking of 1998 home runs\n---\n- Mark McGwire\n- Sammy Sosa\n- Ken Griffey\n\n# Team ranking\n---\n- Chicago Cubs\n- St Louis Cardinals"; const EX2_8 : &str = "---\ntime: 20:03:20\nplayer: Sammy Sosa\naction: strike (miss)\n...\n---\ntime: 20:03:47\nplayer: Sammy Sosa\naction: grand slam\n..."; const EX2_9 : &str = "---\nhr: # 1998 hr ranking\n - Mark McGwire\n - Sammy Sosa\nrbi:\n # 1998 rbi ranking\n - Sammy Sosa\n - Ken Griffey"; const EX2_10 : &str = "---\nhr:\n - Mark McGwire\n # Following node labeled SS\n - &SS Sammy Sosa\nrbi:\n - *SS # Subsequent occurrence\n - Ken Griffey"; const EX2_11 : &str = "? - Detroit Tigers\n - Chicago cubs\n:\n - 2001-07-23\n\n? [ New York Yankees,\n Atlanta Braves ]\n: [ 2001-07-02, 2001-08-12,\n 2001-08-14 ]"; const EX2_12 : &str = "---\n# Products purchased\n- item : Super Hoop\n quantity: 1\n- item : Basketball\n quantity: 4\n- item : Big Shoes\n quantity: 1"; const EX2_13 : &str = "# ASCII Art\n--- |\n \\//||\\/||\n // || ||__"; const EX2_14 : &str = "--- >\n Mark McGwire's\n year was crippled\n by a knee injury."; const EX2_15 : &str = ">\n Sammy Sosa completed another\n fine season with great stats.\n \n 63 Home Runs\n 0.288 Batting Average\n \n What a year!"; const EX2_16 : &str = "name: Mark McGwire\naccomplishment: >\n Mark set a major league\n home run record in 1998.\nstats: |\n 65 Home Runs\n 0.278 Batting Average\n"; const EX2_17 : &str = "unicode: \"Sosa did fine.\\u263A\"\ncontrol: \"\\b1998\\t1999\\t2000\\n\"\nhex esc: \"\\x0d\\x0a is \\r\\n\"\n\nsingle: '\"Howdy!\" he cried.'\nquoted: ' # Not a ''comment''.'\ntie-fighter: '|\\-*-/|'"; const EX2_18 : &str = "plain:\n This unquoted scalar\n spans many lines.\n\nquoted: \"So does this\n quoted scalar.\\n\""; // TODO: 2.19 - 2.22 schema tags const EX2_23 : &str = "---\nnot-date: !!str 2002-04-28\n\npicture: !!binary |\n R0lGODlhDAAMAIQAAP//9/X\n 17unp5WZmZgAAAOfn515eXv\n Pz7Y6OjuDg4J+fn5OTk6enp\n 56enmleECcgggoBADs=\n\napplication specific tag: !something |\n The semantics of the tag\n above may be different for\n different documents."; const EX2_24 : &str = "%TAG ! tag:clarkevans.com,2002:\n--- !shape\n # Use the ! handle for presenting\n # tag:clarkevans.com,2002:circle\n- !circle\n center: &ORIGIN {x: 73, y: 129}\n radius: 7\n- !line\n start: *ORIGIN\n finish: { x: 89, y: 102 }\n- !label\n start: *ORIGIN\n color: 0xFFEEBB\n text: Pretty vector drawing."; const EX2_25 : &str = "# Sets are represented as a\n# Mapping where each key is\n# associated with a null value\n--- !!set\n? Mark McGwire\n? Sammy Sosa\n? Ken Griffey"; const EX2_26 : &str = "# Ordered maps are represented as\n# A sequence of mappings, with\n# each mapping having one key\n--- !!omap\n- Mark McGwire: 65\n- Sammy Sosa: 63\n- Ken Griffey: 58"; const EX2_27 : &str = "--- !\ninvoice: 34843\ndate : 2001-01-23\nbill-to: &id001\n given : Chris\n family : Dumars\n address:\n lines: |\n 458 Walkman Dr.\n Suite #292\n city : Royal Oak\n state : MI\n postal : 48046\nship-to: *id001\nproduct:\n - sku : BL394D\n quantity : 4\n description : Basketball\n price : 450.00\n - sku : BL4438H\n quantity : 1\n description : Super Hoop\n price : 2392.00\ntax : 251.42\ntotal: 4443.52\ncomments:\n Late afternoon is best.\n Backup contact is Nancy\n Billsmer @ 338-4338."; const EX2_28 : &str = "---\nTime: 2001-11-23 15:01:42 -5\nUser: ed\nWarning:\n This is an error message\n for the log file\n---\nTime: 2001-11-23 15:02:31 -5\nUser: ed\nWarning:\n A slightly different error\n message.\n---\nDate: 2001-11-23 15:03:17 -5\nUser: ed\nFatal:\n Unknown variable \"bar\"\nStack:\n - file: TopClass.py\n line: 23\n code: |\n x = MoreObject(\"345\\n\")\n - file: MoreClass.py\n line: 58\n code: |-\n foo = bar"; // TODO: 5.1 - 5.2 BOM const EX5_3 : &str = "sequence:\n- one\n- two\nmapping:\n ? sky\n : blue\n sea : green"; const EX5_4 : &str = "sequence: [ one, two, ]\nmapping: { sky: blue, sea: green }"; const EX5_5 : &str = "# Comment only."; const EX5_6 : &str = "anchored: !local &anchor value\nalias: *anchor"; const EX5_7 : &str = "literal: |\n some\n text\nfolded: >\n some\n text\n"; const EX5_8 : &str = "single: 'text'\ndouble: \"text\""; // TODO: 5.9 directive // TODO: 5.10 reserved indicator const EX5_11 : &str = "|\n Line break (no glyph)\n Line break (glyphed)\n"; const EX5_12 : &str = "# Tabs and spaces\nquoted: \"Quoted\t\"\nblock: |\n void main() {\n \tprintf(\"Hello, world!\\n\");\n }"; const EX5_13 : &str = "\"Fun with \\\\\n\\\" \\a \\b \\e \\f \\\n\\n \\r \\t \\v \\0 \\\n\\ \\_ \\N \\L \\P \\\n\\x41 \\u0041 \\U00000041\""; const EX5_14 : &str = "Bad escapes:\n \"\\c\n \\xq-\""; const EX6_1 : &str = " # Leading comment line spaces are\n # neither content nor indentation.\n \nNot indented:\n By one space: |\n By four\n spaces\n Flow style: [ # Leading spaces\n By two, # in flow style\n Also by two, # are neither\n \tStill by two # content nor\n ] # indentation."; const EX6_2 : &str = "? a\n: -\tb\n - -\tc\n - d"; const EX6_3 : &str = "- foo:\t bar\n- - baz\n -\tbaz"; const EX6_4 : &str = "plain: text\n lines\nquoted: \"text\n \tlines\"\nblock: |\n text\n \tlines\n"; const EX6_5 : &str = "Folding:\n \"Empty line\n \t\n as a line feed\"\nChomping: |\n Clipped empty lines\n "; const EX6_6 : &str = ">-\n trimmed\n \n \n\n as\n space"; const EX6_7 : &str = ">\n foo \n \n \t bar\n\n baz\n"; const EX6_8 : &str = "\"\n foo \n \n \t bar\n\n baz\n\""; const EX6_9 : &str = "key: # Comment\n value"; const EX6_10 : &str = " # Comment\n \n\n"; const EX6_11 : &str = "key: # Comment\n # lines\n value\n\n"; const EX6_12 : &str = "{ first: Sammy, last: Sosa }:\n# Statistics:\n hr: # Home runs\n 65\n avg: # Average\n 0.278"; const EX6_13 : &str = "%FOO bar baz # Should be ignored\n # with a warning.\n--- \"foo\""; const EX6_14 : &str = "%YAML 1.3 # Attempt parsing\n # with a warning\n---\n\"foo\""; const EX6_15 : &str = "%YAML 1.2\n%YAML 1.1\nfoo"; const EX6_16 : &str = "%TAG !yaml! tag:yaml.org,2002:\n---\n!yaml!str \"foo\""; const EX6_17 : &str = "%TAG ! !foo\n%TAG ! !foo\nbar"; const EX6_18 : &str = "# Private\n!foo \"bar\"\n...\n# Global\n%TAG ! tag:example.com,2000:app/\n---\n!foo \"bar\""; const EX6_19 : &str = "%TAG !! tag:example.com,2000:app/\n---\n!!int 1 - 3 # Interval, not integer"; const EX6_20 : &str = "%TAG !e! tag:example.com,2000:app/\n---\n!e!foo \"bar\""; const EX6_21 : &str = "%TAG !m! !my-\n--- # Bulb here\n!m!light fluorescent\n...\n%TAG !m! !my-\n--- # Color here\n!m!light green"; const EX6_22 : &str = "%TAG !e! tag:example.com,2000:app/\n---\n- !e!foo \"bar\""; const EX6_23 : &str = "!!str &a1 \"foo\":\n !!str bar\n&a2 baz : *a1"; const EX6_24 : &str = "! foo :\n ! baz"; const EX6_25 : &str = "- ! foo\n- !<$:?> bar\n"; const EX6_26 : &str = "%TAG !e! tag:example.com,2000:app/\n---\n- !local foo\n- !!str bar\n- !e!tag%21 baz\n"; const EX6_27a : &str = "%TAG !e! tag:example,2000:app/\n---\n- !e! foo"; const EX6_27b : &str = "%TAG !e! tag:example,2000:app/\n---\n- !h!bar baz"; const EX6_28 : &str = "# Assuming conventional resolution:\n- \"12\"\n- 12\n- ! 12"; const EX6_29 : &str = "First occurrence: &anchor Value\nSecond occurrence: *anchor"; const EX7_1 : &str = "First occurrence: &anchor Foo\nSecond occurrence: *anchor\nOverride anchor: &anchor Bar\nReuse anchor: *anchor"; const EX7_2 : &str = "{\n foo : !!str,\n !!str : bar,\n}"; const EX7_3 : &str = "{\n ? foo :,\n : bar,\n}\n"; const EX7_4 : &str = "\"implicit block key\" : [\n \"implicit flow key\" : value,\n ]"; const EX7_5 : &str = "\"folded \nto a space,\t\n \nto a line feed, or \t\\\n \\ \tnon-content\""; const EX7_6 : &str = "\" 1st non-empty\n\n 2nd non-empty \n\t3rd non-empty \""; const EX7_7 : &str = " 'here''s to \"quotes\"'"; const EX7_8 : &str = "'implicit block key' : [\n 'implicit flow key' : value,\n ]"; const EX7_9 : &str = "' 1st non-empty\n\n 2nd non-empty \n\t3rd non-empty '"; const EX7_10 : &str = "# Outside flow collection:\n- ::vector\n- \": - ()\"\n- Up, up, and away!\n- -123\n- http://example.com/foo#bar\n# Inside flow collection:\n- [ ::vector,\n \": - ()\",\n \"Up, up, and away!\",\n -123,\n http://example.com/foo#bar ]"; const EX7_11 : &str = "implicit block key : [\n implicit flow key : value,\n ]"; const EX7_12 : &str = "1st non-empty\n\n 2nd non-empty \n\t3rd non-empty"; const EX7_13 : &str = "- [ one, two, ]\n- [three ,four]"; const EX7_14 : &str = "[\n\"double\n quoted\", 'single\n quoted',\nplain\n text, [ nested ],\nsingle: pair,\n]"; const EX7_15 : &str = "- { one : two , three: four , }\n- {five: six,seven : eight}"; const EX7_16 : &str = "{\n? explicit: entry,\nimplicit: entry,\n?\n}"; const EX7_17 : &str = "{\nunquoted : \"separate\",\nhttp://foo.com,\nomitted value:,\n: omitted key,\n}"; const EX7_18 : &str = "{\n\"adjacent\":value,\n\"readable\":value,\n\"empty\":\n}"; const EX7_19 : &str = "[\nfoo: bar\n]"; const EX7_20 : &str = "[\n? foo\n bar : baz\n]"; const EX7_21 : &str = "- [ YAML : separate ]\n- [ : empty key entry ]\n- [ {JSON: like}:adjacent ]"; const EX7_22 : &str = "[ foo\n bar: invalid,"; // Note: we don't check (on purpose) the >1K chars for an // implicit key const EX7_23 : &str = "- [ a, b ]\n- { a: b }\n- \"a\"\n- 'b'\n- c"; const EX7_24 : &str = "- !!str \"a\"\n- 'b'\n- &anchor \"c\"\n- *anchor\n- !!str"; const EX8_1 : &str = "- | # Empty header\n literal\n- >1 # Indentation indicator\n folded\n- |+ # Chomping indicator\n keep\n\n- >1- # Both indicators\n strip\n"; const EX8_2 : &str = "- |\n detected\n- >\n \n \n # detected\n- |1\n explicit\n- >\n \t\n detected\n"; const EX8_3a : &str = "- |\n \n text"; const EX8_3b : &str = "- >\n text\n text"; const EX8_3c : &str = "- |2\n text"; const EX8_4 : &str = "strip: |-\n text\nclip: |\n text\nkeep: |+\n text\n"; const EX8_5 : &str = " # Strip\n # Comments:\nstrip: |-\n # text\n \n # Clip\n # comments:\n\nclip: |\n # text\n \n # Keep\n # comments:\n\nkeep: |+\n # text\n\n # Trail\n # Comments\n"; const EX8_6 : &str = "strip: >-\n\nclip: >\n\nkeep: |+\n\n"; const EX8_7 : &str = "|\n literal\n \ttext\n\n"; const EX8_8 : &str = "|\n \n \n literal\n \n \n text\n\n # Comment\n"; const EX8_9 : &str = ">\n folded\n text\n\n"; const EX8_10 : &str = ">\n\n folded\n line\n\n next\n line\n * bullet\n\n * list\n * lines\n\n last\n line\n\n# Comment\n"; const EX8_11 : &str = EX8_10; const EX8_12 : &str = EX8_10; const EX8_13 : &str = EX8_10; const EX8_14 : &str = "block sequence:\n - one\n - two : three\n"; const EX8_15 : &str = "- # Empty\n- |\n block node\n- - one # Compact\n - two # sequence\n- one: two # Compact mapping\n"; const EX8_16 : &str = "block mapping:\n key: value\n"; const EX8_17 : &str = "? explicit key # Empty value\n? |\n block key\n: - one # Explicit compact\n - two # block value\n"; // XXX libyaml failed this test const EX8_18 : &str = "plain key: in-line value\n: # Both empty\n\"quoted key\":\n- entry\n"; const EX8_19 : &str = "- sun: yellow\n- ? earth: blue\n : moon: white\n"; const EX8_20 : &str = "-\n \"flow in block\"\n- >\n Block scalar\n- !!map # Block collection\n foo : bar\n"; const EX8_21 : &str = "literal: |2\n value\nfolded:\n !foo\n >1\n value\n"; const EX8_22 : &str = "sequence: !!seq\n- entry\n- !!seq\n - nested\nmapping: !!map\n foo: bar\n"; yaml-rust2-0.10.1/tests/test_round_trip.rs000064400000000000000000000055671046102023000166730ustar 00000000000000extern crate yaml_rust2; use yaml_rust2::{Yaml, YamlEmitter, YamlLoader}; fn roundtrip(original: &Yaml) { let mut emitted = String::new(); YamlEmitter::new(&mut emitted).dump(original).unwrap(); let documents = YamlLoader::load_from_str(&emitted).unwrap(); println!("emitted {emitted}"); assert_eq!(documents.len(), 1); assert_eq!(documents[0], *original); } fn roundtrip_multiline(original: &Yaml) { let mut emitted = String::new(); let mut emitter = YamlEmitter::new(&mut emitted); emitter.multiline_strings(true); emitter.dump(original).unwrap(); let documents = YamlLoader::load_from_str(&emitted).unwrap(); println!("emitted {emitted}"); assert_eq!(documents.len(), 1); assert_eq!(documents[0], *original); } fn double_roundtrip(original: &str) { let parsed = YamlLoader::load_from_str(original).unwrap(); let mut serialized = String::new(); YamlEmitter::new(&mut serialized).dump(&parsed[0]).unwrap(); let reparsed = YamlLoader::load_from_str(&serialized).unwrap(); assert_eq!(parsed, reparsed); } #[test] fn test_escape_character() { let y = Yaml::String("\x1b".to_owned()); roundtrip(&y); } #[test] fn test_colon_in_string() { let y = Yaml::String("x: %".to_owned()); roundtrip(&y); } #[test] fn test_numberlike_strings() { let docs = [ r#"x: "1234""#, r#"x: "01234""#, r#""1234""#, r#""01234""#, r#"" 01234""#, r#""0x1234""#, r#"" 0x1234""#, ]; for doc in &docs { roundtrip(&Yaml::String((*doc).to_string())); double_roundtrip(doc); } } /// Example from #[test] fn test_issue133() { let doc = YamlLoader::load_from_str("\"0x123\"") .unwrap() .pop() .unwrap(); assert_eq!(doc, Yaml::String("0x123".to_string())); let mut out_str = String::new(); YamlEmitter::new(&mut out_str).dump(&doc).unwrap(); let doc2 = YamlLoader::load_from_str(&out_str).unwrap().pop().unwrap(); assert_eq!(doc, doc2); // This failed because the type has changed to a number now } #[test] fn test_newline() { let y = Yaml::Array(vec![Yaml::String("\n".to_owned())]); roundtrip(&y); } #[test] fn test_crlf() { let y = Yaml::Array(vec![Yaml::String("\r\n".to_owned())]); roundtrip(&y); } #[test] fn test_multiline_noline() { let y = Yaml::Array(vec![Yaml::String("a".to_owned())]); roundtrip_multiline(&y); } #[test] fn test_multiline_inner_newline() { let y = Yaml::Array(vec![Yaml::String("a\nb".to_owned())]); roundtrip_multiline(&y); } #[test] fn test_multiline_trailing_newline() { let y = Yaml::Array(vec![Yaml::String("a\n".to_owned())]); roundtrip_multiline(&y); } #[test] fn test_multiline_leading_newline() { let y = Yaml::Array(vec![Yaml::String("\na".to_owned())]); roundtrip_multiline(&y); } yaml-rust2-0.10.1/tests/yaml-test-suite.rs000064400000000000000000000230111046102023000164750ustar 00000000000000use std::fs::{self, DirEntry}; use libtest_mimic::{run_tests, Arguments, Outcome, Test}; use yaml_rust2::{ parser::{Event, EventReceiver, Parser, Tag}, scanner::TScalarStyle, yaml, ScanError, Yaml, YamlLoader, }; type Result> = std::result::Result; struct YamlTest { yaml_visual: String, yaml: String, expected_events: String, expected_error: bool, } fn main() -> Result<()> { let mut arguments = Arguments::from_args(); if arguments.num_threads.is_none() { arguments.num_threads = Some(1); } let tests: Vec> = std::fs::read_dir("tests/yaml-test-suite/src")? .map(|entry| -> Result<_> { let entry = entry?; let tests = load_tests_from_file(&entry)?; Ok(tests) }) .collect::>()?; let mut tests: Vec<_> = tests.into_iter().flatten().collect(); tests.sort_by_key(|t| t.name.clone()); run_tests(&arguments, tests, run_yaml_test).exit(); } fn run_yaml_test(test: &Test) -> Outcome { let desc = &test.data; let actual_events = parse_to_events(&desc.yaml); let events_diff = actual_events.map(|events| events_differ(&events, &desc.expected_events)); let mut error_text = match (&events_diff, desc.expected_error) { (Ok(x), true) => Some(format!("no error when expected: {x:#?}")), (Err(_), true) | (Ok(None), false) => None, (Err(e), false) => Some(format!("unexpected error {e:?}")), (Ok(Some(diff)), false) => Some(format!("events differ: {diff}")), }; // Show a caret on error. if let Some(text) = &mut error_text { use std::fmt::Write; let _ = writeln!(text, "\n### Input:\n{}\n### End", desc.yaml_visual); if let Err(err) = &events_diff { writeln!(text, "### Error position").unwrap(); let mut lines = desc.yaml.lines(); for _ in 0..(err.marker().line() - 1) { let l = lines.next().unwrap(); writeln!(text, "{l}").unwrap(); } writeln!(text, "\x1B[91;1m{}", lines.next().unwrap()).unwrap(); for _ in 0..err.marker().col() { write!(text, " ").unwrap(); } writeln!(text, "^\x1b[m").unwrap(); for l in lines { writeln!(text, "{l}").unwrap(); } writeln!(text, "### End error position").unwrap(); } } match error_text { None => Outcome::Passed, Some(txt) => Outcome::Failed { msg: Some(txt) }, } } fn load_tests_from_file(entry: &DirEntry) -> Result>> { let file_name = entry.file_name().to_string_lossy().to_string(); let test_name = file_name .strip_suffix(".yaml") .ok_or("unexpected filename")?; let tests = YamlLoader::load_from_str(&fs::read_to_string(entry.path())?)?; let tests = tests[0].as_vec().ok_or("no test list found in file")?; let mut result = vec![]; let mut current_test = yaml::Hash::new(); for (idx, test_data) in tests.iter().enumerate() { let name = if tests.len() > 1 { format!("{test_name}-{idx:02}") } else { test_name.to_string() }; // Test fields except `fail` are "inherited" let test_data = test_data.as_hash().unwrap(); current_test.remove(&Yaml::String("fail".into())); for (key, value) in test_data.clone() { current_test.insert(key, value); } let current_test = Yaml::Hash(current_test.clone()); // Much better indexing if current_test["skip"] != Yaml::BadValue { continue; } result.push(Test { name, kind: String::new(), is_ignored: false, is_bench: false, data: YamlTest { yaml_visual: current_test["yaml"].as_str().unwrap().to_string(), yaml: visual_to_raw(current_test["yaml"].as_str().unwrap()), expected_events: visual_to_raw(current_test["tree"].as_str().unwrap()), expected_error: current_test["fail"].as_bool() == Some(true), }, }); } Ok(result) } fn parse_to_events(source: &str) -> Result, ScanError> { let mut reporter = EventReporter::new(); Parser::new_from_str(source).load(&mut reporter, true)?; Ok(reporter.events) } struct EventReporter { events: Vec, } impl EventReporter { fn new() -> Self { Self { events: vec![] } } } impl EventReceiver for EventReporter { fn on_event(&mut self, ev: Event) { let line: String = match ev { Event::StreamStart => "+STR".into(), Event::StreamEnd => "-STR".into(), Event::DocumentStart => "+DOC".into(), Event::DocumentEnd => "-DOC".into(), Event::SequenceStart(idx, tag) => { format!("+SEQ{}{}", format_index(idx), format_tag(&tag)) } Event::SequenceEnd => "-SEQ".into(), Event::MappingStart(idx, tag) => { format!("+MAP{}{}", format_index(idx), format_tag(&tag)) } Event::MappingEnd => "-MAP".into(), Event::Scalar(ref text, style, idx, ref tag) => { let kind = match style { TScalarStyle::Plain => ":", TScalarStyle::SingleQuoted => "'", TScalarStyle::DoubleQuoted => r#"""#, TScalarStyle::Literal => "|", TScalarStyle::Folded => ">", }; format!( "=VAL{}{} {}{}", format_index(idx), format_tag(tag), kind, escape_text(text) ) } Event::Alias(idx) => format!("=ALI *{idx}"), Event::Nothing => return, }; self.events.push(line); } } fn format_index(idx: usize) -> String { if idx > 0 { format!(" &{idx}") } else { String::new() } } fn escape_text(text: &str) -> String { let mut text = text.to_owned(); for (ch, replacement) in [ ('\\', r"\\"), ('\n', "\\n"), ('\r', "\\r"), ('\x08', "\\b"), ('\t', "\\t"), ] { text = text.replace(ch, replacement); } text } fn format_tag(tag: &Option) -> String { if let Some(tag) = tag { format!(" <{}{}>", tag.handle, tag.suffix) } else { String::new() } } fn events_differ(actual: &[String], expected: &str) -> Option { let actual = actual.iter().map(Some).chain(std::iter::repeat(None)); let expected = expected_events(expected); let expected = expected.iter().map(Some).chain(std::iter::repeat(None)); for (idx, (act, exp)) in actual.zip(expected).enumerate() { return match (act, exp) { (Some(act), Some(exp)) => { if act == exp { continue; } else { Some(format!( "line {idx} differs: \n=> expected `{exp}`\n=> found `{act}`", )) } } (Some(a), None) => Some(format!("extra actual line: {a:?}")), (None, Some(e)) => Some(format!("extra expected line: {e:?}")), (None, None) => None, }; } unreachable!() } /// Convert the snippets from "visual" to "actual" representation fn visual_to_raw(yaml: &str) -> String { let mut yaml = yaml.to_owned(); for (pat, replacement) in [ ("␣", " "), ("»", "\t"), ("—", ""), // Tab line continuation ——» ("←", "\r"), ("⇔", "\u{FEFF}"), ("↵", ""), // Trailing newline marker ("∎\n", ""), ] { yaml = yaml.replace(pat, replacement); } yaml } /// Adapt the expectations to the yaml-rust reasonable limitations /// /// Drop information on node styles (flow/block) and anchor names. /// Both are things that can be omitted according to spec. fn expected_events(expected_tree: &str) -> Vec { let mut anchors = vec![]; expected_tree .split('\n') .map(|s| s.trim_start().to_owned()) .filter(|s| !s.is_empty()) .map(|mut s| { // Anchor name-to-number conversion if let Some(start) = s.find('&') { if s[..start].find(':').is_none() { let len = s[start..].find(' ').unwrap_or(s[start..].len()); anchors.push(s[start + 1..start + len].to_owned()); s = s.replace(&s[start..start + len], &format!("&{}", anchors.len())); } } // Alias nodes name-to-number if s.starts_with("=ALI") { let start = s.find('*').unwrap(); let name = &s[start + 1..]; let idx = anchors .iter() .enumerate() .filter(|(_, v)| v == &name) .last() .unwrap() .0; s = s.replace(&s[start..], &format!("*{}", idx + 1)); } // Dropping style information match &*s { "+DOC ---" => "+DOC".into(), "-DOC ..." => "-DOC".into(), s if s.starts_with("+SEQ []") => s.replacen("+SEQ []", "+SEQ", 1), s if s.starts_with("+MAP {}") => s.replacen("+MAP {}", "+MAP", 1), s => s.into(), } }) .collect() }