From bc9fa72e592f6dc45806077d359ef17693927561 Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Thu, 16 Nov 2023 12:23:17 +0100 Subject: Import 'predicates' crate Request Document: go/android-rust-importing-crates For CL Reviewers: go/android3p#cl-review For Build Team: go/ab-third-party-imports Bug: 310602145 Change-Id: Ia60caeea1e8284c27bc759ac33db995aba18d96a --- .cargo_vcs_info.json | 6 + Android.bp | 56 +++++ Cargo.lock | 140 ++++++++++++ Cargo.toml | 137 ++++++++++++ LICENSE | 1 + LICENSE-APACHE | 201 ++++++++++++++++++ LICENSE-MIT | 19 ++ METADATA | 20 ++ MODULE_LICENSE_APACHE2 | 0 OWNERS | 2 + README.md | 45 ++++ cargo2android.json | 6 + examples/case_tree.rs | 13 ++ patches/use-termcolor.patch | 111 ++++++++++ src/boolean.rs | 508 ++++++++++++++++++++++++++++++++++++++++++++ src/boxed.rs | 117 ++++++++++ src/color.rs | 79 +++++++ src/constant.rs | 83 ++++++++ src/float/close.rs | 158 ++++++++++++++ src/float/mod.rs | 16 ++ src/function.rs | 149 +++++++++++++ src/iter.rs | 330 ++++++++++++++++++++++++++++ src/lib.rs | 227 ++++++++++++++++++++ src/name.rs | 115 ++++++++++ src/ord.rs | 304 ++++++++++++++++++++++++++ src/path/existence.rs | 85 ++++++++ src/path/fc.rs | 121 +++++++++++ src/path/fs.rs | 179 ++++++++++++++++ src/path/ft.rs | 207 ++++++++++++++++++ src/path/mod.rs | 20 ++ src/prelude.rs | 56 +++++ src/str/adapters.rs | 204 ++++++++++++++++++ src/str/basics.rs | 290 +++++++++++++++++++++++++ src/str/difference.rs | 132 ++++++++++++ src/str/mod.rs | 30 +++ src/str/normalize.rs | 58 +++++ src/str/regex.rs | 134 ++++++++++++ src/utils.rs | 64 ++++++ 38 files changed, 4423 insertions(+) create mode 100644 .cargo_vcs_info.json create mode 100644 Android.bp create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 120000 LICENSE create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 METADATA create mode 100644 MODULE_LICENSE_APACHE2 create mode 100644 OWNERS create mode 100644 README.md create mode 100644 cargo2android.json create mode 100644 examples/case_tree.rs create mode 100644 patches/use-termcolor.patch create mode 100644 src/boolean.rs create mode 100644 src/boxed.rs create mode 100644 src/color.rs create mode 100644 src/constant.rs create mode 100644 src/float/close.rs create mode 100644 src/float/mod.rs create mode 100644 src/function.rs create mode 100644 src/iter.rs create mode 100644 src/lib.rs create mode 100644 src/name.rs create mode 100644 src/ord.rs create mode 100644 src/path/existence.rs create mode 100644 src/path/fc.rs create mode 100644 src/path/fs.rs create mode 100644 src/path/ft.rs create mode 100644 src/path/mod.rs create mode 100644 src/prelude.rs create mode 100644 src/str/adapters.rs create mode 100644 src/str/basics.rs create mode 100644 src/str/difference.rs create mode 100644 src/str/mod.rs create mode 100644 src/str/normalize.rs create mode 100644 src/str/regex.rs create mode 100644 src/utils.rs diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..0ee28c7 --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "f4cf923c3367390e50dd81efe9e9b9498dea0f11" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..7dcd542 --- /dev/null +++ b/Android.bp @@ -0,0 +1,56 @@ +// This file is generated by cargo2android.py --config cargo2android.json. +// Do not modify this file as changes will be overridden on upgrade. + + + +rust_library { + name: "libpredicates", + host_supported: true, + crate_name: "predicates", + cargo_env_compat: true, + cargo_pkg_version: "3.0.4", + srcs: ["src/lib.rs"], + edition: "2021", + features: [ + "color", + "regex", + ], + rustlibs: [ + "libtermcolor", + "libitertools", + "libpredicates_core", + "libregex", + ], + apex_available: [ + "//apex_available:platform", + "//apex_available:anyapex", + ], + product_available: true, + vendor_available: true, +} + +rust_test { + name: "predicates_test_src_lib", + host_supported: true, + crate_name: "predicates", + cargo_env_compat: true, + cargo_pkg_version: "3.0.4", + srcs: ["src/lib.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2021", + features: [ + "color", + "regex", + ], + rustlibs: [ + "libtermcolor", + "libitertools", + "libpredicates_core", + "libpredicates_tree", + "libregex", + ], +} diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..078e0b0 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,140 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstyle" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "predicates" +version = "3.0.4" +dependencies = [ + "anstyle", + "difflib", + "float-cmp", + "itertools", + "normalize-line-endings", + "predicates-core", + "predicates-tree", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" + +[[package]] +name = "predicates-tree" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "regex" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2659c95 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,137 @@ +# 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.69.0" +name = "predicates" +version = "3.0.4" +authors = ["Nick Stevens "] +include = [ + "build.rs", + "src/**/*", + "Cargo.toml", + "LICENSE*", + "README.md", + "benches/**/*", + "examples/**/*", +] +description = "An implementation of boolean-valued predicate functions." +homepage = "https://github.com/assert-rs/predicates-rs" +documentation = "https://docs.rs/predicates" +readme = "README.md" +keywords = [ + "predicate", + "boolean", + "combinatorial", + "match", + "logic", +] +categories = [ + "data-structures", + "rust-patterns", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/assert-rs/predicates-rs" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = [ + "--cfg", + "docsrs", +] + +[[package.metadata.release.pre-release-replacements]] +exactly = 1 +file = "src/lib.rs" +replace = "predicates = \"{{version}}\"" +search = "predicates = \".*\"" + +[[package.metadata.release.pre-release-replacements]] +exactly = 1 +file = "README.md" +replace = "predicates = \"{{version}}\"" +search = "predicates = \".*\"" + +[[package.metadata.release.pre-release-replacements]] +file = "CHANGELOG.md" +min = 1 +replace = "{{version}}" +search = "Unreleased" + +[[package.metadata.release.pre-release-replacements]] +exactly = 1 +file = "CHANGELOG.md" +replace = "...{{tag_name}}" +search = '\.\.\.HEAD' + +[[package.metadata.release.pre-release-replacements]] +file = "CHANGELOG.md" +min = 1 +replace = "{{date}}" +search = "ReleaseDate" + +[[package.metadata.release.pre-release-replacements]] +exactly = 1 +file = "CHANGELOG.md" +replace = """ + +## [Unreleased] - ReleaseDate +""" +search = "" + +[[package.metadata.release.pre-release-replacements]] +exactly = 1 +file = "CHANGELOG.md" +replace = """ + +[Unreleased]: https://github.com/assert-rs/predicates-rs/compare/{{tag_name}}...HEAD""" +search = "" + +[dependencies.anstyle] +version = "1.0.0" + +[dependencies.difflib] +version = "0.4" +optional = true + +[dependencies.float-cmp] +version = "0.9" +optional = true + +[dependencies.itertools] +version = "0.11" + +[dependencies.normalize-line-endings] +version = "0.3.0" +optional = true + +[dependencies.predicates-core] +version = "1.0" + +[dependencies.regex] +version = "1.0" +optional = true + +[dev-dependencies.predicates-tree] +version = "1.0" + +[features] +color = [] +default = [ + "diff", + "regex", + "float-cmp", + "normalize-line-endings", + "color", +] +diff = ["dep:difflib"] +unstable = [] diff --git a/LICENSE b/LICENSE new file mode 120000 index 0000000..6b579aa --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +LICENSE-APACHE \ No newline at end of file diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..a2d0108 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) Individual contributors + +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. diff --git a/METADATA b/METADATA new file mode 100644 index 0000000..3537a63 --- /dev/null +++ b/METADATA @@ -0,0 +1,20 @@ +name: "predicates" +description: "An implementation of boolean-valued predicate functions." +third_party { + identifier { + type: "crates.io" + value: "https://crates.io/crates/predicates" + } + identifier { + type: "Archive" + value: "https://static.crates.io/crates/predicates/predicates-3.0.4.crate" + } + version: "3.0.4" + # Dual-licensed, using the least restrictive per go/thirdpartylicenses#same. + license_type: NOTICE + last_upgrade_date { + year: 2023 + month: 11 + day: 6 + } +} diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 diff --git a/OWNERS b/OWNERS new file mode 100644 index 0000000..48bea6e --- /dev/null +++ b/OWNERS @@ -0,0 +1,2 @@ +# Bug component: 688011 +include platform/prebuilts/rust:main:/OWNERS diff --git a/README.md b/README.md new file mode 100644 index 0000000..b7d0118 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# predicates-rs + +> An implementation of **boolean-valued predicate functions** in Rust. + +[![Documentation](https://img.shields.io/badge/docs-master-blue.svg)](https://docs.rs/predicates) +![License](https://img.shields.io/crates/l/predicates.svg) +[![Crates.io](https://img.shields.io/crates/v/predicates.svg?maxAge=2592000)](https://crates.io/crates/predicates) + +[Changelog](https://github.com/assert-rs/predicates-rs/blob/master/CHANGELOG.md) + + +## Usage + +First, add this to your `Cargo.toml`: + +```toml +[dependencies] +predicates = "3.0.4" +``` + +Next, add this to your crate: + +```rust +extern crate predicates; + +use predicates::prelude::*; +``` + +For more information on using predicates, look at the +[documentation](https://docs.rs/predicates) + + +## License + +`predicates-rs` is distributed under the terms of both the MIT license and the +Apache License (Version 2.0). + +See LICENSE-APACHE, and LICENSE-MIT for details. + + +## Credits + +Big thanks to [futures-rs](https://github.com/alexcrichton/futures-rs), whose +slick API design informed a lot of decisions made on the API design of this +library. diff --git a/cargo2android.json b/cargo2android.json new file mode 100644 index 0000000..6958d82 --- /dev/null +++ b/cargo2android.json @@ -0,0 +1,6 @@ +{ + "device": true, + "features": "regex,color", + "run": true, + "tests": true +} \ No newline at end of file diff --git a/examples/case_tree.rs b/examples/case_tree.rs new file mode 100644 index 0000000..f9c75dc --- /dev/null +++ b/examples/case_tree.rs @@ -0,0 +1,13 @@ +use predicates::prelude::*; +use predicates_tree::CaseTreeExt; + +fn main() { + let pred = predicate::ne(5).not().and(predicate::ge(5)); + + let var = 5; + let case = pred.find_case(true, &var); + if let Some(case) = case { + println!("var is {}", var); + println!("{}", case.tree()); + } +} diff --git a/patches/use-termcolor.patch b/patches/use-termcolor.patch new file mode 100644 index 0000000..9831f7d --- /dev/null +++ b/patches/use-termcolor.patch @@ -0,0 +1,111 @@ +--- a/Android.bp 2023-11-07 14:29:55.304929829 +0100 ++++ b/Android.bp 2023-11-07 14:30:05.916987569 +0100 +@@ -16,7 +16,7 @@ + "regex", + ], + rustlibs: [ +- "libanstyle", ++ "libtermcolor", + "libitertools", + "libpredicates_core", + "libregex", +@@ -47,7 +47,7 @@ + "regex", + ], + rustlibs: [ +- "libanstyle", ++ "libtermcolor", + "libitertools", + "libpredicates_core", + "libpredicates_tree", +--- a/src/color.rs 2006-07-24 03:21:28.000000000 +0200 ++++ b/src/color.rs 2023-11-07 14:26:51.131915579 +0100 +@@ -1,17 +1,29 @@ +-#[derive(Copy, Clone, Debug, Default)] ++use std::io::Write; ++use termcolor::{Color, ColorSpec, WriteColor}; ++ ++#[derive(Clone, Debug, Default)] + pub(crate) struct Palette { +- description: anstyle::Style, +- var: anstyle::Style, +- expected: anstyle::Style, ++ description: ColorSpec, ++ var: ColorSpec, ++ expected: ColorSpec, + } + + impl Palette { + pub(crate) fn new(alternate: bool) -> Self { + if alternate && cfg!(feature = "color") { + Self { +- description: anstyle::AnsiColor::Blue.on_default() | anstyle::Effects::BOLD, +- var: anstyle::AnsiColor::Red.on_default() | anstyle::Effects::BOLD, +- expected: anstyle::AnsiColor::Green.on_default() | anstyle::Effects::BOLD, ++ description: ColorSpec::new() ++ .set_fg(Some(Color::Blue)) ++ .set_bold(true) ++ .clone(), ++ var: ColorSpec::new() ++ .set_fg(Some(Color::Red)) ++ .set_bold(true) ++ .clone(), ++ expected: ColorSpec::new() ++ .set_fg(Some(Color::Green)) ++ .set_bold(true) ++ .clone(), + } + } else { + Self::plain() +@@ -26,27 +38,27 @@ + } + } + +- pub(crate) fn description(self, display: D) -> Styled { +- Styled::new(display, self.description) ++ pub(crate) fn description(&self, display: D) -> Styled { ++ Styled::new(display, self.description.clone()) + } + +- pub(crate) fn var(self, display: D) -> Styled { +- Styled::new(display, self.var) ++ pub(crate) fn var(&self, display: D) -> Styled { ++ Styled::new(display, self.var.clone()) + } + +- pub(crate) fn expected(self, display: D) -> Styled { +- Styled::new(display, self.expected) ++ pub(crate) fn expected(&self, display: D) -> Styled { ++ Styled::new(display, self.expected.clone()) + } + } + + #[derive(Debug)] + pub(crate) struct Styled { + display: D, +- style: anstyle::Style, ++ style: ColorSpec, + } + + impl Styled { +- pub(crate) fn new(display: D, style: anstyle::Style) -> Self { ++ pub(crate) fn new(display: D, style: ColorSpec) -> Self { + Self { display, style } + } + } +@@ -55,10 +67,11 @@ + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if f.alternate() { +- write!(f, "{}", self.style.render())?; +- self.display.fmt(f)?; +- write!(f, "{}", self.style.render_reset())?; +- Ok(()) ++ let mut buf = termcolor::Buffer::ansi(); ++ buf.set_color(&self.style).unwrap(); ++ write!(&mut buf, "{}", &self.display).unwrap(); ++ buf.reset().unwrap(); ++ write!(f, "{}", String::from_utf8(buf.into_inner()).unwrap()) + } else { + self.display.fmt(f) + } diff --git a/src/boolean.rs b/src/boolean.rs new file mode 100644 index 0000000..a730f14 --- /dev/null +++ b/src/boolean.rs @@ -0,0 +1,508 @@ +// Copyright (c) 2018 The predicates-rs Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Definition of boolean logic combinators over `Predicate`s. + +use std::fmt; +use std::marker::PhantomData; + +use crate::reflection; +use crate::Predicate; + +/// Predicate that combines two `Predicate`s, returning the AND of the results. +/// +/// This is created by the `Predicate::and` function. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct AndPredicate +where + M1: Predicate, + M2: Predicate, + Item: ?Sized, +{ + a: M1, + b: M2, + _phantom: PhantomData, +} + +unsafe impl Send for AndPredicate +where + M1: Predicate + Send, + M2: Predicate + Send, + Item: ?Sized, +{ +} + +unsafe impl Sync for AndPredicate +where + M1: Predicate + Sync, + M2: Predicate + Sync, + Item: ?Sized, +{ +} + +impl AndPredicate +where + M1: Predicate, + M2: Predicate, + Item: ?Sized, +{ + /// Create a new `AndPredicate` over predicates `a` and `b`. + pub fn new(a: M1, b: M2) -> AndPredicate { + AndPredicate { + a, + b, + _phantom: PhantomData, + } + } +} + +impl Predicate for AndPredicate +where + M1: Predicate, + M2: Predicate, + Item: ?Sized, +{ + fn eval(&self, item: &Item) -> bool { + self.a.eval(item) && self.b.eval(item) + } + + fn find_case<'a>(&'a self, expected: bool, variable: &Item) -> Option> { + let child_a = self.a.find_case(expected, variable); + match (expected, child_a) { + (true, Some(child_a)) => self.b.find_case(expected, variable).map(|child_b| { + reflection::Case::new(Some(self), expected) + .add_child(child_a) + .add_child(child_b) + }), + (true, None) => None, + (false, Some(child_a)) => { + Some(reflection::Case::new(Some(self), expected).add_child(child_a)) + } + (false, None) => self + .b + .find_case(expected, variable) + .map(|child_b| reflection::Case::new(Some(self), expected).add_child(child_b)), + } + } +} + +impl reflection::PredicateReflection for AndPredicate +where + M1: Predicate, + M2: Predicate, + Item: ?Sized, +{ + fn children<'a>(&'a self) -> Box> + 'a> { + let params = vec![ + reflection::Child::new("left", &self.a), + reflection::Child::new("right", &self.b), + ]; + Box::new(params.into_iter()) + } +} + +impl fmt::Display for AndPredicate +where + M1: Predicate, + M2: Predicate, + Item: ?Sized, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "({} && {})", self.a, self.b) + } +} + +#[cfg(test)] +mod test_and { + use crate::prelude::*; + + #[test] + fn find_case_true() { + assert!(predicate::always() + .and(predicate::always()) + .find_case(true, &5) + .is_some()); + } + + #[test] + fn find_case_true_left_fail() { + assert!(predicate::never() + .and(predicate::always()) + .find_case(true, &5) + .is_none()); + } + + #[test] + fn find_case_true_right_fail() { + assert!(predicate::always() + .and(predicate::never()) + .find_case(true, &5) + .is_none()); + } + + #[test] + fn find_case_true_fails() { + assert!(predicate::never() + .and(predicate::never()) + .find_case(true, &5) + .is_none()); + } + + #[test] + fn find_case_false() { + assert!(predicate::never() + .and(predicate::never()) + .find_case(false, &5) + .is_some()); + } + + #[test] + fn find_case_false_fails() { + assert!(predicate::always() + .and(predicate::always()) + .find_case(false, &5) + .is_none()); + } + + #[test] + fn find_case_false_left_fail() { + assert!(predicate::never() + .and(predicate::always()) + .find_case(false, &5) + .is_some()); + } + + #[test] + fn find_case_false_right_fail() { + assert!(predicate::always() + .and(predicate::never()) + .find_case(false, &5) + .is_some()); + } +} + +/// Predicate that combines two `Predicate`s, returning the OR of the results. +/// +/// This is created by the `Predicate::or` function. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct OrPredicate +where + M1: Predicate, + M2: Predicate, + Item: ?Sized, +{ + a: M1, + b: M2, + _phantom: PhantomData, +} + +unsafe impl Send for OrPredicate +where + M1: Predicate + Send, + M2: Predicate + Send, + Item: ?Sized, +{ +} + +unsafe impl Sync for OrPredicate +where + M1: Predicate + Sync, + M2: Predicate + Sync, + Item: ?Sized, +{ +} + +impl OrPredicate +where + M1: Predicate, + M2: Predicate, + Item: ?Sized, +{ + /// Create a new `OrPredicate` over predicates `a` and `b`. + pub fn new(a: M1, b: M2) -> OrPredicate { + OrPredicate { + a, + b, + _phantom: PhantomData, + } + } +} + +impl Predicate for OrPredicate +where + M1: Predicate, + M2: Predicate, + Item: ?Sized, +{ + fn eval(&self, item: &Item) -> bool { + self.a.eval(item) || self.b.eval(item) + } + + fn find_case<'a>(&'a self, expected: bool, variable: &Item) -> Option> { + let child_a = self.a.find_case(expected, variable); + match (expected, child_a) { + (true, Some(child_a)) => { + Some(reflection::Case::new(Some(self), expected).add_child(child_a)) + } + (true, None) => self + .b + .find_case(expected, variable) + .map(|child_b| reflection::Case::new(Some(self), expected).add_child(child_b)), + (false, Some(child_a)) => self.b.find_case(expected, variable).map(|child_b| { + reflection::Case::new(Some(self), expected) + .add_child(child_a) + .add_child(child_b) + }), + (false, None) => None, + } + } +} + +impl reflection::PredicateReflection for OrPredicate +where + M1: Predicate, + M2: Predicate, + Item: ?Sized, +{ + fn children<'a>(&'a self) -> Box> + 'a> { + let params = vec![ + reflection::Child::new("left", &self.a), + reflection::Child::new("right", &self.b), + ]; + Box::new(params.into_iter()) + } +} + +impl fmt::Display for OrPredicate +where + M1: Predicate, + M2: Predicate, + Item: ?Sized, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "({} || {})", self.a, self.b) + } +} + +#[cfg(test)] +mod test_or { + use crate::prelude::*; + + #[test] + fn find_case_true() { + assert!(predicate::always() + .or(predicate::always()) + .find_case(true, &5) + .is_some()); + } + + #[test] + fn find_case_true_left_fail() { + assert!(predicate::never() + .or(predicate::always()) + .find_case(true, &5) + .is_some()); + } + + #[test] + fn find_case_true_right_fail() { + assert!(predicate::always() + .or(predicate::never()) + .find_case(true, &5) + .is_some()); + } + + #[test] + fn find_case_true_fails() { + assert!(predicate::never() + .or(predicate::never()) + .find_case(true, &5) + .is_none()); + } + + #[test] + fn find_case_false() { + assert!(predicate::never() + .or(predicate::never()) + .find_case(false, &5) + .is_some()); + } + + #[test] + fn find_case_false_fails() { + assert!(predicate::always() + .or(predicate::always()) + .find_case(false, &5) + .is_none()); + } + + #[test] + fn find_case_false_left_fail() { + assert!(predicate::never() + .or(predicate::always()) + .find_case(false, &5) + .is_none()); + } + + #[test] + fn find_case_false_right_fail() { + assert!(predicate::always() + .or(predicate::never()) + .find_case(false, &5) + .is_none()); + } +} + +/// Predicate that returns a `Predicate` taking the logical NOT of the result. +/// +/// This is created by the `Predicate::not` function. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct NotPredicate +where + M: Predicate, + Item: ?Sized, +{ + inner: M, + _phantom: PhantomData, +} + +unsafe impl Send for NotPredicate +where + M: Predicate + Send, + Item: ?Sized, +{ +} + +unsafe impl Sync for NotPredicate +where + M: Predicate + Sync, + Item: ?Sized, +{ +} + +impl NotPredicate +where + M: Predicate, + Item: ?Sized, +{ + /// Create a new `NotPredicate` over predicate `inner`. + pub fn new(inner: M) -> NotPredicate { + NotPredicate { + inner, + _phantom: PhantomData, + } + } +} + +impl Predicate for NotPredicate +where + M: Predicate, + Item: ?Sized, +{ + fn eval(&self, item: &Item) -> bool { + !self.inner.eval(item) + } + + fn find_case<'a>(&'a self, expected: bool, variable: &Item) -> Option> { + self.inner + .find_case(!expected, variable) + .map(|child| reflection::Case::new(Some(self), expected).add_child(child)) + } +} + +impl reflection::PredicateReflection for NotPredicate +where + M: Predicate, + Item: ?Sized, +{ + fn children<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Child::new("predicate", &self.inner)]; + Box::new(params.into_iter()) + } +} + +impl fmt::Display for NotPredicate +where + M: Predicate, + Item: ?Sized, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "(! {})", self.inner) + } +} + +/// `Predicate` extension that adds boolean logic. +pub trait PredicateBooleanExt +where + Self: Predicate, +{ + /// Compute the logical AND of two `Predicate` results, returning the result. + /// + /// # Examples + /// + /// ``` + /// use predicates::prelude::*; + /// + /// let predicate_fn1 = predicate::always().and(predicate::always()); + /// let predicate_fn2 = predicate::always().and(predicate::never()); + /// assert_eq!(true, predicate_fn1.eval(&4)); + /// assert_eq!(false, predicate_fn2.eval(&4)); + fn and(self, other: B) -> AndPredicate + where + B: Predicate, + Self: Sized, + { + AndPredicate::new(self, other) + } + + /// Compute the logical OR of two `Predicate` results, returning the result. + /// + /// # Examples + /// + /// ``` + /// use predicates::prelude::*; + /// + /// let predicate_fn1 = predicate::always().or(predicate::always()); + /// let predicate_fn2 = predicate::always().or(predicate::never()); + /// let predicate_fn3 = predicate::never().or(predicate::never()); + /// assert_eq!(true, predicate_fn1.eval(&4)); + /// assert_eq!(true, predicate_fn2.eval(&4)); + /// assert_eq!(false, predicate_fn3.eval(&4)); + fn or(self, other: B) -> OrPredicate + where + B: Predicate, + Self: Sized, + { + OrPredicate::new(self, other) + } + + /// Compute the logical NOT of a `Predicate`, returning the result. + /// + /// # Examples + /// + /// ``` + /// use predicates::prelude::*; + /// + /// let predicate_fn1 = predicate::always().not(); + /// let predicate_fn2 = predicate::never().not(); + /// assert_eq!(false, predicate_fn1.eval(&4)); + /// assert_eq!(true, predicate_fn2.eval(&4)); + fn not(self) -> NotPredicate + where + Self: Sized, + { + NotPredicate::new(self) + } +} + +impl PredicateBooleanExt for P +where + P: Predicate, + Item: ?Sized, +{ +} diff --git a/src/boxed.rs b/src/boxed.rs new file mode 100644 index 0000000..d64e204 --- /dev/null +++ b/src/boxed.rs @@ -0,0 +1,117 @@ +// Copyright (c) 2018 The predicates-rs Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Predicate that can wrap other dynamically-called predicates in an +//! easy-to-manage type. + +use std::fmt; + +use crate::reflection; +use crate::utils; +use crate::Predicate; + +/// `Predicate` that wraps another `Predicate` as a trait object, allowing +/// sized storage of predicate types. +pub struct BoxPredicate(Box + Send + Sync>); + +impl BoxPredicate +where + Item: ?Sized, +{ + /// Creates a new `BoxPredicate`, a wrapper around a dynamically-dispatched + /// `Predicate` type with useful trait impls. + pub fn new>(inner: P) -> BoxPredicate + where + P: Send + Sync + 'static, + { + BoxPredicate(Box::new(inner)) + } +} + +impl fmt::Debug for BoxPredicate +where + Item: ?Sized, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("BoxPredicate").finish() + } +} + +impl reflection::PredicateReflection for BoxPredicate +where + Item: ?Sized, +{ + fn parameters<'a>(&'a self) -> Box> + 'a> { + self.0.parameters() + } + + fn children<'a>(&'a self) -> Box> + 'a> { + self.0.children() + } +} + +impl fmt::Display for BoxPredicate +where + Item: ?Sized, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl Predicate for BoxPredicate +where + Item: ?Sized, +{ + fn eval(&self, variable: &Item) -> bool { + self.0.eval(variable) + } + + fn find_case<'a>(&'a self, expected: bool, variable: &Item) -> Option> { + utils::default_find_case(self, expected, variable) + } +} + +/// `Predicate` extension for boxing a `Predicate`. +pub trait PredicateBoxExt +where + Self: Predicate, +{ + /// Returns a `BoxPredicate` wrapper around this `Predicate` type. + /// + /// Returns a `BoxPredicate` wrapper around this `Predicate type. The + /// `BoxPredicate` type has a number of useful properties: + /// + /// - It stores the inner predicate as a trait object, so the type of + /// `BoxPredicate` will always be the same even if steps are added or + /// removed from the predicate. + /// - It is a common type, allowing it to be stored in vectors or other + /// collection types. + /// - It implements `Debug` and `Display`. + /// + /// # Examples + /// + /// ``` + /// use predicates::prelude::*; + /// + /// let predicates = vec![ + /// predicate::always().boxed(), + /// predicate::never().boxed(), + /// ]; + /// assert_eq!(true, predicates[0].eval(&4)); + /// assert_eq!(false, predicates[1].eval(&4)); + /// ``` + fn boxed(self) -> BoxPredicate + where + Self: Sized + Send + Sync + 'static, + { + BoxPredicate::new(self) + } +} + +impl PredicateBoxExt for P where P: Predicate {} diff --git a/src/color.rs b/src/color.rs new file mode 100644 index 0000000..2b9449c --- /dev/null +++ b/src/color.rs @@ -0,0 +1,79 @@ +use std::io::Write; +use termcolor::{Color, ColorSpec, WriteColor}; + +#[derive(Clone, Debug, Default)] +pub(crate) struct Palette { + description: ColorSpec, + var: ColorSpec, + expected: ColorSpec, +} + +impl Palette { + pub(crate) fn new(alternate: bool) -> Self { + if alternate && cfg!(feature = "color") { + Self { + description: ColorSpec::new() + .set_fg(Some(Color::Blue)) + .set_bold(true) + .clone(), + var: ColorSpec::new() + .set_fg(Some(Color::Red)) + .set_bold(true) + .clone(), + expected: ColorSpec::new() + .set_fg(Some(Color::Green)) + .set_bold(true) + .clone(), + } + } else { + Self::plain() + } + } + + pub(crate) fn plain() -> Self { + Self { + description: Default::default(), + var: Default::default(), + expected: Default::default(), + } + } + + pub(crate) fn description(&self, display: D) -> Styled { + Styled::new(display, self.description.clone()) + } + + pub(crate) fn var(&self, display: D) -> Styled { + Styled::new(display, self.var.clone()) + } + + pub(crate) fn expected(&self, display: D) -> Styled { + Styled::new(display, self.expected.clone()) + } +} + +#[derive(Debug)] +pub(crate) struct Styled { + display: D, + style: ColorSpec, +} + +impl Styled { + pub(crate) fn new(display: D, style: ColorSpec) -> Self { + Self { display, style } + } +} + +impl std::fmt::Display for Styled { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if f.alternate() { + let mut buf = termcolor::Buffer::ansi(); + buf.set_color(&self.style).unwrap(); + write!(&mut buf, "{}", &self.display).unwrap(); + buf.reset().unwrap(); + write!(f, "{}", String::from_utf8(buf.into_inner()).unwrap()) + } else { + self.display.fmt(f) + } + } +} diff --git a/src/constant.rs b/src/constant.rs new file mode 100644 index 0000000..79cbb86 --- /dev/null +++ b/src/constant.rs @@ -0,0 +1,83 @@ +// Copyright (c) 2018 The predicates-rs Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Definition of a constant (always true or always false) `Predicate`. + +use std::fmt; + +use crate::reflection; +use crate::utils; +use crate::Predicate; + +/// Predicate that always returns a constant (boolean) result. +/// +/// This is created by the `predicate::always` and `predicate::never` functions. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct BooleanPredicate { + retval: bool, +} + +impl Predicate for BooleanPredicate { + fn eval(&self, _variable: &Item) -> bool { + self.retval + } + + fn find_case<'a>(&'a self, expected: bool, variable: &Item) -> Option> { + utils::default_find_case(self, expected, variable) + } +} + +impl reflection::PredicateReflection for BooleanPredicate { + fn parameters<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Parameter::new("value", &self.retval)]; + Box::new(params.into_iter()) + } +} + +impl fmt::Display for BooleanPredicate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let palette = crate::Palette::new(f.alternate()); + write!(f, "{}", palette.expected(self.retval)) + } +} + +/// Creates a new `Predicate` that always returns `true`. +/// +/// # Examples +/// +/// ``` +/// use predicates::prelude::*; +/// +/// let predicate_fn = predicate::always(); +/// assert_eq!(true, predicate_fn.eval(&5)); +/// assert_eq!(true, predicate_fn.eval(&10)); +/// assert_eq!(true, predicate_fn.eval(&15)); +/// // Won't work - Predicates can only operate on a single type +/// // assert_eq!(true, predicate_fn.eval("hello")) +/// ``` +pub fn always() -> BooleanPredicate { + BooleanPredicate { retval: true } +} + +/// Creates a new `Predicate` that always returns `false`. +/// +/// # Examples +/// +/// ``` +/// use predicates::prelude::*; +/// +/// let predicate_fn = predicate::never(); +/// assert_eq!(false, predicate_fn.eval(&5)); +/// assert_eq!(false, predicate_fn.eval(&10)); +/// assert_eq!(false, predicate_fn.eval(&15)); +/// // Won't work - Predicates can only operate on a single type +/// // assert_eq!(false, predicate_fn.eval("hello")) +/// ``` +pub fn never() -> BooleanPredicate { + BooleanPredicate { retval: false } +} diff --git a/src/float/close.rs b/src/float/close.rs new file mode 100644 index 0000000..e642eab --- /dev/null +++ b/src/float/close.rs @@ -0,0 +1,158 @@ +// Copyright (c) 2018 The predicates-rs Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::fmt; + +use float_cmp::ApproxEq; +use float_cmp::Ulps; + +use crate::reflection; +use crate::Predicate; + +/// Predicate that ensures two numbers are "close" enough, understanding that rounding errors +/// occur. +/// +/// This is created by the `predicate::float::is_close`. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct IsClosePredicate { + target: f64, + epsilon: f64, + ulps: ::U, +} + +impl IsClosePredicate { + /// Set the amount of error allowed. + /// + /// Values `1`-`5` should work in most cases. Sometimes more control is needed and you will + /// need to set `IsClosePredicate::epsilon` separately from `IsClosePredicate::ulps`. + /// + /// # Examples + /// + /// ``` + /// use predicates::prelude::*; + /// + /// let a = 0.15_f64 + 0.15_f64 + 0.15_f64; + /// let predicate_fn = predicate::float::is_close(a).distance(5); + /// ``` + pub fn distance(mut self, distance: ::U) -> Self { + self.epsilon = (distance as f64) * ::std::f64::EPSILON; + self.ulps = distance; + self + } + + /// Set the absolute deviation allowed. + /// + /// This is meant to handle problems near `0`. Values `1.`-`5.` epislons should work in most + /// cases. + /// + /// # Examples + /// + /// ``` + /// use predicates::prelude::*; + /// + /// let a = 0.15_f64 + 0.15_f64 + 0.15_f64; + /// let predicate_fn = predicate::float::is_close(a).epsilon(5.0 * ::std::f64::EPSILON); + /// ``` + pub fn epsilon(mut self, epsilon: f64) -> Self { + self.epsilon = epsilon; + self + } + + /// Set the relative deviation allowed. + /// + /// This is meant to handle large numbers. Values `1`-`5` should work in most cases. + /// + /// # Examples + /// + /// ``` + /// use predicates::prelude::*; + /// + /// let a = 0.15_f64 + 0.15_f64 + 0.15_f64; + /// let predicate_fn = predicate::float::is_close(a).ulps(5); + /// ``` + pub fn ulps(mut self, ulps: ::U) -> Self { + self.ulps = ulps; + self + } +} + +impl Predicate for IsClosePredicate { + fn eval(&self, variable: &f64) -> bool { + variable.approx_eq( + self.target, + float_cmp::F64Margin { + epsilon: self.epsilon, + ulps: self.ulps, + }, + ) + } + + fn find_case<'a>(&'a self, expected: bool, variable: &f64) -> Option> { + let actual = self.eval(variable); + if expected == actual { + Some( + reflection::Case::new(Some(self), actual) + .add_product(reflection::Product::new( + "actual epsilon", + (variable - self.target).abs(), + )) + .add_product(reflection::Product::new( + "actual ulps", + variable.ulps(&self.target).abs(), + )), + ) + } else { + None + } + } +} + +impl reflection::PredicateReflection for IsClosePredicate { + fn parameters<'a>(&'a self) -> Box> + 'a> { + let params = vec![ + reflection::Parameter::new("epsilon", &self.epsilon), + reflection::Parameter::new("ulps", &self.ulps), + ]; + Box::new(params.into_iter()) + } +} + +impl fmt::Display for IsClosePredicate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let palette = crate::Palette::new(f.alternate()); + write!( + f, + "{} {} {}", + palette.var("var"), + palette.description("!="), + palette.expected(self.target), + ) + } +} + +/// Create a new `Predicate` that ensures two numbers are "close" enough, understanding that +/// rounding errors occur. +/// +/// # Examples +/// +/// ``` +/// use predicates::prelude::*; +/// +/// let a = 0.15_f64 + 0.15_f64 + 0.15_f64; +/// let b = 0.1_f64 + 0.1_f64 + 0.25_f64; +/// let predicate_fn = predicate::float::is_close(a); +/// assert_eq!(true, predicate_fn.eval(&b)); +/// assert_eq!(false, predicate_fn.distance(0).eval(&b)); +/// ``` +pub fn is_close(target: f64) -> IsClosePredicate { + IsClosePredicate { + target, + epsilon: 2.0 * ::std::f64::EPSILON, + ulps: 2, + } +} diff --git a/src/float/mod.rs b/src/float/mod.rs new file mode 100644 index 0000000..76c3377 --- /dev/null +++ b/src/float/mod.rs @@ -0,0 +1,16 @@ +// Copyright (c) 2018 The predicates-rs Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Float Predicates +//! +//! This module contains predicates specific to string handling. + +#[cfg(feature = "float-cmp")] +mod close; +#[cfg(feature = "float-cmp")] +pub use self::close::{is_close, IsClosePredicate}; diff --git a/src/function.rs b/src/function.rs new file mode 100644 index 0000000..e6eecaa --- /dev/null +++ b/src/function.rs @@ -0,0 +1,149 @@ +// Copyright (c) 2018 The predicates-rs Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Definition of `Predicate` for wrapping a `Fn(&T) -> bool` + +use std::fmt; +use std::marker::PhantomData; + +use crate::reflection; +use crate::utils; +use crate::Predicate; + +/// Predicate that wraps a function over a reference that returns a `bool`. +/// This type is returned by the `predicate::function` function. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct FnPredicate +where + F: Fn(&T) -> bool, + T: ?Sized, +{ + function: F, + name: &'static str, + _phantom: PhantomData, +} + +unsafe impl Send for FnPredicate +where + F: Send + Fn(&T) -> bool, + T: ?Sized, +{ +} + +unsafe impl Sync for FnPredicate +where + F: Sync + Fn(&T) -> bool, + T: ?Sized, +{ +} + +impl FnPredicate +where + F: Fn(&T) -> bool, + T: ?Sized, +{ + /// Provide a descriptive name for this function. + /// + /// # Examples + /// + /// ``` + /// use predicates::prelude::*; + /// + /// struct Example { + /// string: String, + /// number: i32, + /// } + /// + /// let string_check = predicate::function(|x: &Example| x.string == "hello") + /// .fn_name("is_hello"); + /// println!("predicate: {}", string_check); + /// ``` + pub fn fn_name(mut self, name: &'static str) -> Self { + self.name = name; + self + } +} + +impl Predicate for FnPredicate +where + F: Fn(&T) -> bool, + T: ?Sized, +{ + fn eval(&self, variable: &T) -> bool { + (self.function)(variable) + } + + fn find_case<'a>(&'a self, expected: bool, variable: &T) -> Option> { + utils::default_find_case(self, expected, variable) + } +} + +impl reflection::PredicateReflection for FnPredicate +where + F: Fn(&T) -> bool, + T: ?Sized, +{ +} + +impl fmt::Display for FnPredicate +where + F: Fn(&T) -> bool, + T: ?Sized, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let palette = crate::Palette::new(f.alternate()); + write!( + f, + "{}({})", + palette.description(self.name), + palette.var("var"), + ) + } +} + +/// Creates a new predicate that wraps over the given function. The returned +/// type implements `Predicate` and therefore has all combinators available to +/// it. +/// +/// # Examples +/// +/// ``` +/// use predicates::prelude::*; +/// +/// struct Example { +/// string: String, +/// number: i32, +/// } +/// +/// let string_check = predicate::function(|x: &Example| x.string == "hello"); +/// let number_check = predicate::function(|x: &Example| x.number == 42); +/// let predicate_fn = string_check.and(number_check); +/// let good_example = Example { string: "hello".into(), number: 42 }; +/// assert_eq!(true, predicate_fn.eval(&good_example)); +/// let bad_example = Example { string: "goodbye".into(), number: 0 }; +/// assert_eq!(false, predicate_fn.eval(&bad_example)); +/// ``` +pub fn function(function: F) -> FnPredicate +where + F: Fn(&T) -> bool, + T: ?Sized, +{ + FnPredicate { + function, + name: "fn", + _phantom: PhantomData, + } +} + +#[test] +fn str_function() { + let f = function(|x: &str| x == "hello"); + assert!(f.eval("hello")); + assert!(!f.eval("goodbye")); +} diff --git a/src/iter.rs b/src/iter.rs new file mode 100644 index 0000000..03b69b2 --- /dev/null +++ b/src/iter.rs @@ -0,0 +1,330 @@ +// Copyright (c) 2018 The predicates-rs Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Definition of `Predicate`s for comparisons of membership in a set. + +use std::collections::HashSet; +use std::fmt; +use std::hash::Hash; +use std::iter::FromIterator; + +use crate::reflection; +use crate::utils; +use crate::Predicate; + +/// Predicate that returns `true` if `variable` is a member of the pre-defined +/// set, otherwise returns `false`. +/// +/// Note that this implementation places the fewest restrictions on the +/// underlying `Item` type at the expense of having the least performant +/// implementation (linear search). If the type to be searched is `Hash + Eq`, +/// it is much more efficient to use `HashableInPredicate` and +/// `in_hash`. The implementation-specific predicates will be +/// deprecated when Rust supports trait specialization. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct InPredicate +where + T: PartialEq + fmt::Debug, +{ + inner: utils::DebugAdapter>, +} + +impl InPredicate +where + T: Ord + fmt::Debug, +{ + /// Creates a new predicate that will return `true` when the given `variable` is + /// contained with the set of items provided. + /// + /// Note that this implementation requires `Item` to be `Ord`. The + /// `InPredicate` uses a less efficient search algorithm but only + /// requires `Item` implement `PartialEq`. The implementation-specific + /// predicates will be deprecated when Rust supports trait specialization. + /// + /// # Examples + /// + /// ``` + /// use predicates::prelude::*; + /// + /// let predicate_fn = predicate::in_iter(vec![1, 3, 5]).sort(); + /// assert_eq!(true, predicate_fn.eval(&1)); + /// assert_eq!(false, predicate_fn.eval(&2)); + /// assert_eq!(true, predicate_fn.eval(&3)); + /// + /// let predicate_fn = predicate::in_iter(vec!["a", "c", "e"]).sort(); + /// assert_eq!(true, predicate_fn.eval("a")); + /// assert_eq!(false, predicate_fn.eval("b")); + /// assert_eq!(true, predicate_fn.eval("c")); + /// + /// let predicate_fn = predicate::in_iter(vec![String::from("a"), String::from("c"), String::from("e")]).sort(); + /// assert_eq!(true, predicate_fn.eval("a")); + /// assert_eq!(false, predicate_fn.eval("b")); + /// assert_eq!(true, predicate_fn.eval("c")); + /// ``` + pub fn sort(self) -> OrdInPredicate { + let mut items = self.inner.debug; + items.sort(); + OrdInPredicate { + inner: utils::DebugAdapter::new(items), + } + } +} + +impl Predicate

for InPredicate +where + T: std::borrow::Borrow

+ PartialEq + fmt::Debug, + P: PartialEq + fmt::Debug + ?Sized, +{ + fn eval(&self, variable: &P) -> bool { + self.inner.debug.iter().any(|x| x.borrow() == variable) + } + + fn find_case<'a>(&'a self, expected: bool, variable: &P) -> Option> { + utils::default_find_case(self, expected, variable).map(|case| { + case.add_product(reflection::Product::new( + "var", + utils::DebugAdapter::new(variable).to_string(), + )) + }) + } +} + +impl reflection::PredicateReflection for InPredicate +where + T: PartialEq + fmt::Debug, +{ + fn parameters<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Parameter::new("values", &self.inner)]; + Box::new(params.into_iter()) + } +} + +impl fmt::Display for InPredicate +where + T: PartialEq + fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let palette = crate::Palette::new(f.alternate()); + write!( + f, + "{} {} {}", + palette.var("var"), + palette.description("in"), + palette.expected("values") + ) + } +} + +/// Creates a new predicate that will return `true` when the given `variable` is +/// contained with the set of items provided. +/// +/// Note that this implementation places the fewest restrictions on the +/// underlying `Item` type at the expense of having the least performant +/// implementation (linear search). If the type to be searched is `Hash + Eq`, +/// it is much more efficient to use `HashableInPredicate` and +/// `in_hash`. The implementation-specific predicates will be +/// deprecated when Rust supports trait specialization. +/// +/// If you need to optimize this +/// - Type is `Ord`, call `sort()` on this predicate. +/// - Type is `Hash`, replace `in_iter` with `in_hash`. +/// +/// # Examples +/// +/// ``` +/// use predicates::prelude::*; +/// +/// let predicate_fn = predicate::in_iter(vec![1, 3, 5]); +/// assert_eq!(true, predicate_fn.eval(&1)); +/// assert_eq!(false, predicate_fn.eval(&2)); +/// assert_eq!(true, predicate_fn.eval(&3)); +/// +/// let predicate_fn = predicate::in_iter(vec!["a", "c", "e"]); +/// assert_eq!(true, predicate_fn.eval("a")); +/// assert_eq!(false, predicate_fn.eval("b")); +/// assert_eq!(true, predicate_fn.eval("c")); +/// +/// let predicate_fn = predicate::in_iter(vec![String::from("a"), String::from("c"), String::from("e")]); +/// assert_eq!(true, predicate_fn.eval("a")); +/// assert_eq!(false, predicate_fn.eval("b")); +/// assert_eq!(true, predicate_fn.eval("c")); +/// ``` +pub fn in_iter(iter: I) -> InPredicate +where + T: PartialEq + fmt::Debug, + I: IntoIterator, +{ + InPredicate { + inner: utils::DebugAdapter::new(Vec::from_iter(iter)), + } +} + +/// Predicate that returns `true` if `variable` is a member of the pre-defined +/// set, otherwise returns `false`. +/// +/// Note that this implementation requires `Item` to be `Ord`. The +/// `InPredicate` uses a less efficient search algorithm but only +/// requires `Item` implement `PartialEq`. The implementation-specific +/// predicates will be deprecated when Rust supports trait specialization. +/// +/// This is created by the `predicate::in_iter(...).sort` function. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct OrdInPredicate +where + T: Ord + fmt::Debug, +{ + inner: utils::DebugAdapter>, +} + +impl Predicate

for OrdInPredicate +where + T: std::borrow::Borrow

+ Ord + fmt::Debug, + P: Ord + fmt::Debug + ?Sized, +{ + fn eval(&self, variable: &P) -> bool { + self.inner + .debug + .binary_search_by(|x| x.borrow().cmp(variable)) + .is_ok() + } + + fn find_case<'a>(&'a self, expected: bool, variable: &P) -> Option> { + utils::default_find_case(self, expected, variable).map(|case| { + case.add_product(reflection::Product::new( + "var", + utils::DebugAdapter::new(variable).to_string(), + )) + }) + } +} + +impl reflection::PredicateReflection for OrdInPredicate +where + T: Ord + fmt::Debug, +{ + fn parameters<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Parameter::new("values", &self.inner)]; + Box::new(params.into_iter()) + } +} + +impl fmt::Display for OrdInPredicate +where + T: Ord + fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let palette = crate::Palette::new(f.alternate()); + write!( + f, + "{} {} {}", + palette.var("var"), + palette.description("in"), + palette.expected("values") + ) + } +} + +/// Predicate that returns `true` if `variable` is a member of the pre-defined +/// `HashSet`, otherwise returns `false`. +/// +/// Note that this implementation requires `Item` to be `Hash + Eq`. The +/// `InPredicate` uses a less efficient search algorithm but only +/// requires `Item` implement `PartialEq`. The implementation-specific +/// predicates will be deprecated when Rust supports trait specialization. +/// +/// This is created by the `predicate::in_hash` function. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct HashableInPredicate +where + T: Hash + Eq + fmt::Debug, +{ + inner: utils::DebugAdapter>, +} + +impl Predicate

for HashableInPredicate +where + T: std::borrow::Borrow

+ Hash + Eq + fmt::Debug, + P: Hash + Eq + fmt::Debug + ?Sized, +{ + fn eval(&self, variable: &P) -> bool { + self.inner.debug.contains(variable) + } + + fn find_case<'a>(&'a self, expected: bool, variable: &P) -> Option> { + utils::default_find_case(self, expected, variable).map(|case| { + case.add_product(reflection::Product::new( + "var", + utils::DebugAdapter::new(variable).to_string(), + )) + }) + } +} + +impl reflection::PredicateReflection for HashableInPredicate +where + T: Hash + Eq + fmt::Debug, +{ + fn parameters<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Parameter::new("values", &self.inner)]; + Box::new(params.into_iter()) + } +} + +impl fmt::Display for HashableInPredicate +where + T: Hash + Eq + fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let palette = crate::Palette::new(f.alternate()); + write!( + f, + "{} {} {}", + palette.var("var"), + palette.description("in"), + palette.expected("values") + ) + } +} + +/// Creates a new predicate that will return `true` when the given `variable` is +/// contained with the set of items provided. +/// +/// Note that this implementation requires `Item` to be `Hash + Eq`. The +/// `InPredicate` uses a less efficient search algorithm but only +/// requires `Item` implement `PartialEq`. The implementation-specific +/// predicates will be deprecated when Rust supports trait specialization. +/// +/// # Examples +/// +/// ``` +/// use predicates::prelude::*; +/// +/// let predicate_fn = predicate::in_hash(vec![1, 3, 5]); +/// assert_eq!(true, predicate_fn.eval(&1)); +/// assert_eq!(false, predicate_fn.eval(&2)); +/// assert_eq!(true, predicate_fn.eval(&3)); +/// +/// let predicate_fn = predicate::in_hash(vec!["a", "c", "e"]); +/// assert_eq!(true, predicate_fn.eval("a")); +/// assert_eq!(false, predicate_fn.eval("b")); +/// assert_eq!(true, predicate_fn.eval("c")); +/// +/// let predicate_fn = predicate::in_hash(vec![String::from("a"), String::from("c"), String::from("e")]); +/// assert_eq!(true, predicate_fn.eval("a")); +/// assert_eq!(false, predicate_fn.eval("b")); +/// assert_eq!(true, predicate_fn.eval("c")); +/// ``` +pub fn in_hash(iter: I) -> HashableInPredicate +where + T: Hash + Eq + fmt::Debug, + I: IntoIterator, +{ + HashableInPredicate { + inner: utils::DebugAdapter::new(HashSet::from_iter(iter)), + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..f2a1f9e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,227 @@ +// Copyright (c) 2018 The predicates-rs Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Composable first-order predicate functions. +//! +//! This library implements an interface to "predicates" - boolean-valued +//! functions of one argument. This allows combinatorial logic to be created and +//! assembled at runtime and then used one or more times for evaluating values. +//! This sort of object is really useful when creating filters and checks that +//! can be changed at runtime with user interaction - it allows a clean +//! separation of concerns where the configuration code can be used to build up +//! a predicate, and then that predicate can be given to the code that does the +//! actual filtering without the filtering code knowing anything about user +//! configuration. See the examples for how this can work. +//! +//! ## Installation +//! +//! Add this to your `Cargo.toml`: +//! +//! ```toml +//! [dependencies] +//! predicates = "3.0.4" +//! ``` +//! +//! A [prelude] is available to bring in all extension traits as well as providing +//! `prelude::predicate` which focuses on the 90% case of the API. +//! ```rust +//! use predicates::prelude::*; +//! ``` +//! +//! ## Examples +//! +//! The simplest predicates are [`predicate::always`] and [`predicate::never`], which always +//! returns `true` and always returns `false`, respectively. The values are simply ignored when +//! evaluating against these predicates: +//! ```rust +//! use predicates::prelude::*; +//! +//! let always_true = predicate::always(); +//! assert_eq!(true, always_true.eval(&5)); +//! let always_false = predicate::never(); +//! assert_eq!(false, always_false.eval(&5)); +//! ``` +//! +//! Pre-made predicates are available for types that implement the `PartialOrd` and +//! `PartialEq` traits. The following example uses `lt`, but `eq`, `ne`, `le`, `gt`, +//! `ge` are also available. +//! ```rust +//! use predicates::prelude::*; +//! +//! let less_than_ten = predicate::lt(10); +//! assert_eq!(true, less_than_ten.eval(&9)); +//! assert_eq!(false, less_than_ten.eval(&11)); +//! ``` +//! +//! Any function over a reference to the desired `Item` that returns `bool` +//! can easily be made into a `Predicate` using the [`predicate::function`] +//! function. +//! ```rust +//! use predicates::prelude::*; +//! +//! let bound = 5; +//! let predicate_fn = predicate::function(|&x| x >= bound); +//! let between_5_and_10 = predicate_fn.and(predicate::le(10)); +//! assert_eq!(true, between_5_and_10.eval(&7)); +//! assert_eq!(false, between_5_and_10.eval(&3)); +//! ``` +//! +//! The `Predicate` type is actually a trait, and that trait implements a +//! number of useful combinator functions. For example, evaluating for a value +//! between two other values can be accomplished as follows: +//! ```rust +//! use predicates::prelude::*; +//! +//! let between_5_and_10 = predicate::ge(5).and(predicate::le(10)); +//! assert_eq!(true, between_5_and_10.eval(&7)); +//! assert_eq!(false, between_5_and_10.eval(&11)); +//! assert_eq!(false, between_5_and_10.eval(&4)); +//! ``` +//! +//! The `Predicate` trait is pretty simple, the core of it is an +//! implementation of a `eval` function that takes a single argument and +//! returns a `bool`. Implementing a custom `Predicate` still allows all the +//! usual combinators of the `Predicate` trait to work! +//! ```rust +//! use std::fmt; +//! +//! use predicates::prelude::*; +//! +//! struct IsTheAnswer; +//! impl Predicate for IsTheAnswer { +//! fn eval(&self, variable: &i32) -> bool { +//! *variable == 42 +//! } +//! } +//! impl predicates::reflection::PredicateReflection for IsTheAnswer {} +//! impl fmt::Display for IsTheAnswer { +//! fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +//! write!(f, "var.is_the_answer()") +//! } +//! } +//! +//! assert_eq!(true, IsTheAnswer.eval(&42)); +//! let almost_the_answer = IsTheAnswer.or(predicate::in_iter(vec![41, 43])); +//! assert_eq!(true, almost_the_answer.eval(&41)); +//! ``` +//! +//! ## Choosing a Predicate +//! +//! General predicates +//! - [`predicate::always`] +//! - [`predicate::never`] +//! - [`predicate::function`] +//! - [`predicate::in_iter`]: Specified value must be in the `Iterator`. +//! - [`predicate::in_iter(...).sort`]: Optimization for repeatedly called predicates. +//! - [`predicate::in_hash`]: Optimization for repeatedly called predicates. +//! - [`predicate::eq`] +//! - [`predicate::float::is_close`]: Use this instead of `eq` for floating point values. +//! - [`predicate::ne`] +//! - [`predicate::ge`] +//! - [`predicate::gt`] +//! - [`predicate::le`] +//! - [`predicate::lt`] +//! - [`predicate::name`]: Improve readability of failure reporting by providing a meaningful name. +//! +//! Combinators +//! - [`pred_a.and(pred_b)`]: Both predicates must succeed. +//! - [`pred_a.or(pred_b)`]: One or both predicates must succeed. +//! - [`pred_a.not()`]: The predicate must fail. +//! +//! `String` predicates +//! - [`predicate::str::is_empty`]: Specified string must be empty +//! - [`str_pred = predicate::path::eq_file(...).utf8`]: Specified string must equal the contents +//! of the given file. +//! - [`predicate::str::diff`]: Same as `eq` except report a diff. See [`DifferencePredicate`] +//! for more features. +//! - [`predicate::str::starts_with`]: Specified string must start with the given needle. +//! - [`predicate::str::ends_with`]: Specified string must end with the given needle. +//! - [`predicate::str::contains`]: Specified string must contain the given needle. +//! - [`predicate::str::contains(...).count`]: Required number of times the needle must show up. +//! - [`predicate::str::is_match`]: Specified string must match the given regex. +//! - [`predicate::str::is_match(...).count`]: Required number of times the match must show up. +//! - [`str_pred.trim`]: Trim whitespace before passing it to `str_pred`. +//! - [`str_pred.normalize`]: Normalize the line endings before passing it to `str_pred`. +//! - [`bytes_pred = str_pred.from_utf8()`]: Reuse string predicates in other contexts, like the +//! file system. +//! +//! File system predicates +//! - [`predicate::path::exists`]: Specified path must exist on disk. +//! - [`predicate::path::missing`]: Specified path must not exist on disk. +//! - [`predicate::path::is_dir`]: Specified path is a directory. +//! - [`predicate::path::is_file`]: Specified path is a file. +//! - [`predicate::path::is_symlink`]: Specified path is a symlink. +//! - [`path_pred = predicate::path::eq_file`]: Specified path's contents must equal the contents of the given +//! file. +//! - [`path_pred = bytes_pred.from_file_path`]: Specified path's contents must equal the `bytes_pred`. +//! +//! [`DifferencePredicate`]: crate::str::DifferencePredicate +//! [`bytes_pred = str_pred.from_utf8()`]: prelude::PredicateStrExt::from_utf8() +//! [`path_pred = bytes_pred.from_file_path`]: prelude::PredicateFileContentExt::from_file_path() +//! [`path_pred = predicate::path::eq_file`]: prelude::predicate::path::eq_file() +//! [`pred_a.and(pred_b)`]: boolean::PredicateBooleanExt::and() +//! [`pred_a.not()`]: boolean::PredicateBooleanExt::not() +//! [`pred_a.or(pred_b)`]: boolean::PredicateBooleanExt::or() +//! [`predicate::always`]: constant::always() +//! [`predicate::eq`]: ord::eq() +//! [`predicate::float::is_close`]: prelude::predicate::float::is_close() +//! [`predicate::function`]: function::function() +//! [`predicate::ge`]: ord::ge() +//! [`predicate::gt`]: ord::gt() +//! [`predicate::in_hash`]: iter::in_hash() +//! [`predicate::in_iter(...).sort`]: iter::InPredicate::sort() +//! [`predicate::in_iter`]: iter::in_iter() +//! [`predicate::le`]: ord::le() +//! [`predicate::lt`]: ord::lt() +//! [`predicate::name`]: name::PredicateNameExt::name() +//! [`predicate::ne`]: ord::ne() +//! [`predicate::never`]: constant::never() +//! [`predicate::path::exists`]: prelude::predicate::path::exists() +//! [`predicate::path::is_dir`]: prelude::predicate::path::is_dir() +//! [`predicate::path::is_file`]: prelude::predicate::path::is_file() +//! [`predicate::path::is_symlink`]: prelude::predicate::path::is_symlink() +//! [`predicate::path::missing`]: prelude::predicate::path::missing() +//! [`predicate::str::contains(...).count`]: str::ContainsPredicate::count() +//! [`predicate::str::contains`]: prelude::predicate::str::contains() +//! [`predicate::str::diff`]: prelude::predicate::str::diff() +//! [`predicate::str::ends_with`]: prelude::predicate::str::ends_with() +//! [`predicate::str::is_empty`]: prelude::predicate::str::is_empty() +//! [`predicate::str::is_match(...).count`]: str::RegexPredicate::count() +//! [`predicate::str::is_match`]: prelude::predicate::str::is_match() +//! [`predicate::str::starts_with`]: prelude::predicate::str::starts_with() +//! [`str_pred = predicate::path::eq_file(...).utf8`]: path::BinaryFilePredicate::utf8() +//! [`str_pred.normalize`]: prelude::PredicateStrExt::normalize() +//! [`str_pred.trim`]: prelude::PredicateStrExt::trim() + +#![warn(missing_docs, missing_debug_implementations)] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] + +pub mod prelude; + +pub use predicates_core::*; +mod boxed; +pub use crate::boxed::*; + +// core predicates +pub mod constant; +pub mod function; +pub mod iter; +pub mod name; +pub mod ord; + +// combinators +pub mod boolean; + +// specialized primitive `Predicate` types +pub mod float; +pub mod path; +pub mod str; + +mod color; +use color::Palette; +mod utils; diff --git a/src/name.rs b/src/name.rs new file mode 100644 index 0000000..d204145 --- /dev/null +++ b/src/name.rs @@ -0,0 +1,115 @@ +// Copyright (c) 2018 The predicates-rs Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Name predicate expressions. + +use std::fmt; +use std::marker::PhantomData; + +use crate::reflection; +use crate::Predicate; + +/// Augment an existing predicate with a name. +/// +/// This is created by the `PredicateNameExt::name` function. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct NamePredicate +where + M: Predicate, + Item: ?Sized, +{ + inner: M, + name: &'static str, + _phantom: PhantomData, +} + +unsafe impl Send for NamePredicate +where + M: Predicate + Send, + Item: ?Sized, +{ +} + +unsafe impl Sync for NamePredicate +where + M: Predicate + Sync, + Item: ?Sized, +{ +} + +impl Predicate for NamePredicate +where + M: Predicate, + Item: ?Sized, +{ + fn eval(&self, item: &Item) -> bool { + self.inner.eval(item) + } + + fn find_case<'a>(&'a self, expected: bool, variable: &Item) -> Option> { + self.inner + .find_case(expected, variable) + .map(|child_case| reflection::Case::new(Some(self), expected).add_child(child_case)) + } +} + +impl reflection::PredicateReflection for NamePredicate +where + M: Predicate, + Item: ?Sized, +{ + fn children<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Child::new(self.name, &self.inner)]; + Box::new(params.into_iter()) + } +} + +impl fmt::Display for NamePredicate +where + M: Predicate, + Item: ?Sized, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let palette = crate::Palette::new(f.alternate()); + write!(f, "{}", palette.description(self.name)) + } +} + +/// `Predicate` extension that adds naming predicate expressions. +pub trait PredicateNameExt +where + Self: Predicate, +{ + /// Name a predicate expression. + /// + /// # Examples + /// + /// ``` + /// use predicates::prelude::*; + /// + /// let predicate_fn = predicate::str::is_empty().not().name("non-empty"); + /// println!("{}", predicate_fn); + /// ``` + fn name(self, name: &'static str) -> NamePredicate + where + Self: Sized, + { + NamePredicate { + inner: self, + name, + _phantom: PhantomData, + } + } +} + +impl PredicateNameExt for P +where + P: Predicate, + Item: ?Sized, +{ +} diff --git a/src/ord.rs b/src/ord.rs new file mode 100644 index 0000000..1a78c8d --- /dev/null +++ b/src/ord.rs @@ -0,0 +1,304 @@ +// Copyright (c) 2018, 2022 The predicates-rs Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Definition of `Predicate`s for comparisons over `Ord` and `Eq` types. + +use std::fmt; + +use crate::reflection; +use crate::utils; +use crate::Predicate; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum EqOps { + Equal, + NotEqual, +} + +impl fmt::Display for EqOps { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let op = match *self { + EqOps::Equal => "==", + EqOps::NotEqual => "!=", + }; + write!(f, "{}", op) + } +} + +/// Predicate that returns `true` if `variable` matches the pre-defined `Eq` +/// value, otherwise returns `false`. +/// +/// This is created by the `predicate::{eq, ne}` functions. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct EqPredicate { + constant: T, + op: EqOps, +} + +impl Predicate

for EqPredicate +where + T: std::borrow::Borrow

+ fmt::Debug, + P: fmt::Debug + PartialEq + ?Sized, +{ + fn eval(&self, variable: &P) -> bool { + match self.op { + EqOps::Equal => variable.eq(self.constant.borrow()), + EqOps::NotEqual => variable.ne(self.constant.borrow()), + } + } + + fn find_case<'a>(&'a self, expected: bool, variable: &P) -> Option> { + utils::default_find_case(self, expected, variable).map(|case| { + case.add_product(reflection::Product::new( + "var", + utils::DebugAdapter::new(variable).to_string(), + )) + }) + } +} + +impl reflection::PredicateReflection for EqPredicate where T: fmt::Debug {} + +impl fmt::Display for EqPredicate +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let palette = crate::Palette::new(f.alternate()); + write!( + f, + "{} {} {}", + palette.var("var"), + palette.description(self.op), + palette.expected(utils::DebugAdapter::new(&self.constant)), + ) + } +} + +/// Creates a new predicate that will return `true` when the given `variable` is +/// equal to a pre-defined value. +/// +/// # Examples +/// +/// ``` +/// use predicates::prelude::*; +/// +/// let predicate_fn = predicate::eq(5); +/// assert_eq!(true, predicate_fn.eval(&5)); +/// assert_eq!(false, predicate_fn.eval(&10)); +/// +/// let predicate_fn = predicate::eq("Hello"); +/// assert_eq!(true, predicate_fn.eval("Hello")); +/// assert_eq!(false, predicate_fn.eval("Goodbye")); +/// +/// let predicate_fn = predicate::eq(String::from("Hello")); +/// assert_eq!(true, predicate_fn.eval("Hello")); +/// assert_eq!(false, predicate_fn.eval("Goodbye")); +/// ``` +pub fn eq(constant: T) -> EqPredicate +where + T: fmt::Debug + PartialEq, +{ + EqPredicate { + constant, + op: EqOps::Equal, + } +} + +/// Creates a new predicate that will return `true` when the given `variable` is +/// _not_ equal to a pre-defined value. +/// +/// # Examples +/// +/// ``` +/// use predicates::prelude::*; +/// +/// let predicate_fn = predicate::ne(5); +/// assert_eq!(false, predicate_fn.eval(&5)); +/// assert_eq!(true, predicate_fn.eval(&10)); +/// ``` +pub fn ne(constant: T) -> EqPredicate +where + T: PartialEq + fmt::Debug, +{ + EqPredicate { + constant, + op: EqOps::NotEqual, + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum OrdOps { + LessThan, + LessThanOrEqual, + GreaterThanOrEqual, + GreaterThan, +} + +impl fmt::Display for OrdOps { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let op = match *self { + OrdOps::LessThan => "<", + OrdOps::LessThanOrEqual => "<=", + OrdOps::GreaterThanOrEqual => ">=", + OrdOps::GreaterThan => ">", + }; + write!(f, "{}", op) + } +} + +/// Predicate that returns `true` if `variable` matches the pre-defined `Ord` +/// value, otherwise returns `false`. +/// +/// This is created by the `predicate::{gt, ge, lt, le}` functions. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct OrdPredicate { + constant: T, + op: OrdOps, +} + +impl Predicate

for OrdPredicate +where + T: std::borrow::Borrow

+ fmt::Debug, + P: fmt::Debug + PartialOrd + ?Sized, +{ + fn eval(&self, variable: &P) -> bool { + match self.op { + OrdOps::LessThan => variable.lt(self.constant.borrow()), + OrdOps::LessThanOrEqual => variable.le(self.constant.borrow()), + OrdOps::GreaterThanOrEqual => variable.ge(self.constant.borrow()), + OrdOps::GreaterThan => variable.gt(self.constant.borrow()), + } + } + + fn find_case<'a>(&'a self, expected: bool, variable: &P) -> Option> { + utils::default_find_case(self, expected, variable).map(|case| { + case.add_product(reflection::Product::new( + "var", + utils::DebugAdapter::new(variable).to_string(), + )) + }) + } +} + +impl reflection::PredicateReflection for OrdPredicate where T: fmt::Debug {} + +impl fmt::Display for OrdPredicate +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let palette = crate::Palette::new(f.alternate()); + write!( + f, + "{} {} {}", + palette.var("var"), + palette.description(self.op), + palette.expected(utils::DebugAdapter::new(&self.constant)), + ) + } +} + +/// Creates a new predicate that will return `true` when the given `variable` is +/// less than a pre-defined value. +/// +/// # Examples +/// +/// ``` +/// use predicates::prelude::*; +/// +/// let predicate_fn = predicate::lt(5); +/// assert_eq!(true, predicate_fn.eval(&4)); +/// assert_eq!(false, predicate_fn.eval(&6)); +/// +/// let predicate_fn = predicate::lt("b"); +/// assert_eq!(true, predicate_fn.eval("a")); +/// assert_eq!(false, predicate_fn.eval("c")); +/// +/// let predicate_fn = predicate::lt(String::from("b")); +/// assert_eq!(true, predicate_fn.eval("a")); +/// assert_eq!(false, predicate_fn.eval("c")); +/// ``` +pub fn lt(constant: T) -> OrdPredicate +where + T: fmt::Debug + PartialOrd, +{ + OrdPredicate { + constant, + op: OrdOps::LessThan, + } +} + +/// Creates a new predicate that will return `true` when the given `variable` is +/// less than or equal to a pre-defined value. +/// +/// # Examples +/// +/// ``` +/// use predicates::prelude::*; +/// +/// let predicate_fn = predicate::le(5); +/// assert_eq!(true, predicate_fn.eval(&4)); +/// assert_eq!(true, predicate_fn.eval(&5)); +/// assert_eq!(false, predicate_fn.eval(&6)); +/// ``` +pub fn le(constant: T) -> OrdPredicate +where + T: PartialOrd + fmt::Debug, +{ + OrdPredicate { + constant, + op: OrdOps::LessThanOrEqual, + } +} + +/// Creates a new predicate that will return `true` when the given `variable` is +/// greater than or equal to a pre-defined value. +/// +/// # Examples +/// +/// ``` +/// use predicates::prelude::*; +/// +/// let predicate = predicate::ge(5); +/// assert_eq!(false, predicate.eval(&4)); +/// assert_eq!(true, predicate.eval(&5)); +/// assert_eq!(true, predicate.eval(&6)); +/// ``` +pub fn ge(constant: T) -> OrdPredicate +where + T: PartialOrd + fmt::Debug, +{ + OrdPredicate { + constant, + op: OrdOps::GreaterThanOrEqual, + } +} + +/// Creates a new predicate that will return `true` when the given `variable` is +/// greater than a pre-defined value. +/// +/// # Examples +/// +/// ``` +/// use predicates::prelude::*; +/// +/// let predicate_fn = predicate::gt(5); +/// assert_eq!(false, predicate_fn.eval(&4)); +/// assert_eq!(false, predicate_fn.eval(&5)); +/// assert_eq!(true, predicate_fn.eval(&6)); +/// ``` +pub fn gt(constant: T) -> OrdPredicate +where + T: PartialOrd + fmt::Debug, +{ + OrdPredicate { + constant, + op: OrdOps::GreaterThan, + } +} diff --git a/src/path/existence.rs b/src/path/existence.rs new file mode 100644 index 0000000..343a9c1 --- /dev/null +++ b/src/path/existence.rs @@ -0,0 +1,85 @@ +// Copyright (c) 2018 The predicates-rs Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::fmt; +use std::path; + +use crate::reflection; +use crate::utils; +use crate::Predicate; + +/// Predicate that checks if a file is present +/// +/// This is created by the `predicate::path::exists` and `predicate::path::missing`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ExistencePredicate { + exists: bool, +} + +impl Predicate for ExistencePredicate { + fn eval(&self, path: &path::Path) -> bool { + path.exists() == self.exists + } + + fn find_case<'a>( + &'a self, + expected: bool, + variable: &path::Path, + ) -> Option> { + utils::default_find_case(self, expected, variable).map(|case| { + case.add_product(reflection::Product::new( + "var", + variable.display().to_string(), + )) + }) + } +} + +impl reflection::PredicateReflection for ExistencePredicate {} + +impl fmt::Display for ExistencePredicate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let palette = crate::Palette::new(f.alternate()); + write!( + f, + "{}({})", + palette.description(if self.exists { "exists" } else { "missing" }), + palette.var("var") + ) + } +} + +/// Creates a new `Predicate` that ensures the path exists. +/// +/// # Examples +/// +/// ``` +/// use std::path::Path; +/// use predicates::prelude::*; +/// +/// let predicate_fn = predicate::path::exists(); +/// assert_eq!(true, predicate_fn.eval(Path::new("Cargo.toml"))); +/// ``` +pub fn exists() -> ExistencePredicate { + ExistencePredicate { exists: true } +} + +/// Creates a new `Predicate` that ensures the path doesn't exist. +/// +/// # Examples +/// +/// ``` +/// use std::path::Path; +/// use predicates::prelude::*; +/// +/// let predicate_fn = predicate::path::missing(); +/// assert_eq!(true, predicate_fn.eval(Path::new("non-existent-file.foo"))); +/// ``` +pub fn missing() -> ExistencePredicate { + ExistencePredicate { exists: false } +} diff --git a/src/path/fc.rs b/src/path/fc.rs new file mode 100644 index 0000000..f16c156 --- /dev/null +++ b/src/path/fc.rs @@ -0,0 +1,121 @@ +// Copyright (c) 2018 The predicates-rs Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::fmt; +use std::fs; +use std::io::{self, Read}; +use std::path; + +use crate::reflection; +use crate::Predicate; + +fn read_file(path: &path::Path) -> io::Result> { + let mut buffer = Vec::new(); + fs::File::open(path)?.read_to_end(&mut buffer)?; + Ok(buffer) +} + +/// Predicate adapter that converts a `path` predicate to a byte predicate on its content. +/// +/// This is created by `pred.from_path()`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct FileContentPredicate

+where + P: Predicate<[u8]>, +{ + p: P, +} + +impl

FileContentPredicate

+where + P: Predicate<[u8]>, +{ + fn eval(&self, path: &path::Path) -> io::Result { + let buffer = read_file(path)?; + Ok(self.p.eval(&buffer)) + } +} + +impl

reflection::PredicateReflection for FileContentPredicate

+where + P: Predicate<[u8]>, +{ + fn children<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Child::new("predicate", &self.p)]; + Box::new(params.into_iter()) + } +} + +impl

fmt::Display for FileContentPredicate

+where + P: Predicate<[u8]>, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.p.fmt(f) + } +} + +impl

Predicate for FileContentPredicate

+where + P: Predicate<[u8]>, +{ + fn eval(&self, path: &path::Path) -> bool { + self.eval(path).unwrap_or(false) + } + + fn find_case<'a>( + &'a self, + expected: bool, + variable: &path::Path, + ) -> Option> { + let buffer = read_file(variable); + match (expected, buffer) { + (_, Ok(buffer)) => self.p.find_case(expected, &buffer).map(|case| { + case.add_product(reflection::Product::new( + "var", + variable.display().to_string(), + )) + }), + (true, Err(_)) => None, + (false, Err(err)) => Some( + reflection::Case::new(Some(self), false) + .add_product(reflection::Product::new( + "var", + variable.display().to_string(), + )) + .add_product(reflection::Product::new("error", err)), + ), + } + } +} + +/// `Predicate` extension adapting a `slice` Predicate. +pub trait PredicateFileContentExt +where + Self: Predicate<[u8]>, + Self: Sized, +{ + /// Returns a `FileContentPredicate` that adapts `Self` to a file content `Predicate`. + /// + /// # Examples + /// + /// ``` + /// use predicates::prelude::*; + /// use std::path::Path; + /// + /// let predicate_fn = predicate::str::is_empty().not().from_utf8().from_file_path(); + /// assert_eq!(true, predicate_fn.eval(Path::new("./tests/hello_world"))); + /// assert_eq!(false, predicate_fn.eval(Path::new("./tests/empty_file"))); + /// ``` + #[allow(clippy::wrong_self_convention)] + fn from_file_path(self) -> FileContentPredicate { + FileContentPredicate { p: self } + } +} + +impl

PredicateFileContentExt for P where P: Predicate<[u8]> {} diff --git a/src/path/fs.rs b/src/path/fs.rs new file mode 100644 index 0000000..9db46b2 --- /dev/null +++ b/src/path/fs.rs @@ -0,0 +1,179 @@ +// Copyright (c) 2018 The predicates-rs Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::fmt; +use std::fs; +use std::io::{self, Read}; +use std::path; + +use crate::reflection; +use crate::utils; +use crate::Predicate; + +fn read_file(path: &path::Path) -> io::Result> { + let mut buffer = Vec::new(); + fs::File::open(path)?.read_to_end(&mut buffer)?; + Ok(buffer) +} + +/// Predicate that compares file matches +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BinaryFilePredicate { + path: path::PathBuf, + content: utils::DebugAdapter>, +} + +impl BinaryFilePredicate { + fn eval(&self, path: &path::Path) -> io::Result { + let content = read_file(path)?; + Ok(self.content.debug == content) + } + + /// Creates a new `Predicate` that ensures complete equality + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// use predicates::prelude::*; + /// + /// let predicate_file = predicate::path::eq_file(Path::new("Cargo.toml")).utf8().unwrap(); + /// assert_eq!(true, predicate_file.eval(Path::new("Cargo.toml"))); + /// assert_eq!(false, predicate_file.eval(Path::new("Cargo.lock"))); + /// assert_eq!(false, predicate_file.eval(Path::new("src"))); + /// + /// assert_eq!(false, predicate_file.eval("Not a real Cargo.toml file content")); + /// ``` + pub fn utf8(self) -> Option { + let path = self.path; + let content = String::from_utf8(self.content.debug).ok()?; + Some(StrFilePredicate { path, content }) + } +} + +impl Predicate for BinaryFilePredicate { + fn eval(&self, path: &path::Path) -> bool { + self.eval(path).unwrap_or(false) + } + + fn find_case<'a>( + &'a self, + expected: bool, + variable: &path::Path, + ) -> Option> { + utils::default_find_case(self, expected, variable) + } +} + +impl Predicate<[u8]> for BinaryFilePredicate { + fn eval(&self, actual: &[u8]) -> bool { + self.content.debug == actual + } + + fn find_case<'a>(&'a self, expected: bool, variable: &[u8]) -> Option> { + utils::default_find_case(self, expected, variable) + } +} + +impl reflection::PredicateReflection for BinaryFilePredicate { + fn parameters<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Parameter::new("content", &self.content)]; + Box::new(params.into_iter()) + } +} + +impl fmt::Display for BinaryFilePredicate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let palette = crate::Palette::new(f.alternate()); + write!( + f, + "{} {} {}", + palette.var("var"), + palette.description("is"), + palette.expected(self.path.display()) + ) + } +} + +/// Creates a new `Predicate` that ensures complete equality +/// +/// # Examples +/// +/// ``` +/// use std::path::Path; +/// use predicates::prelude::*; +/// +/// let predicate_file = predicate::path::eq_file(Path::new("Cargo.toml")); +/// assert_eq!(true, predicate_file.eval(Path::new("Cargo.toml"))); +/// assert_eq!(false, predicate_file.eval(Path::new("src"))); +/// assert_eq!(false, predicate_file.eval(Path::new("src"))); +/// ``` +pub fn eq_file>(path: P) -> BinaryFilePredicate { + let path = path.into(); + let content = utils::DebugAdapter::new(read_file(&path).unwrap()); + BinaryFilePredicate { path, content } +} + +/// Predicate that compares string content of files +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StrFilePredicate { + path: path::PathBuf, + content: String, +} + +impl StrFilePredicate { + fn eval(&self, path: &path::Path) -> Option { + let content = read_file(path).ok()?; + let content = String::from_utf8(content).ok()?; + Some(self.content == content) + } +} + +impl Predicate for StrFilePredicate { + fn eval(&self, path: &path::Path) -> bool { + self.eval(path).unwrap_or(false) + } + + fn find_case<'a>( + &'a self, + expected: bool, + variable: &path::Path, + ) -> Option> { + utils::default_find_case(self, expected, variable) + } +} + +impl Predicate for StrFilePredicate { + fn eval(&self, actual: &str) -> bool { + self.content == actual + } + + fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { + utils::default_find_case(self, expected, variable) + } +} + +impl reflection::PredicateReflection for StrFilePredicate { + fn parameters<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Parameter::new("content", &self.content)]; + Box::new(params.into_iter()) + } +} + +impl fmt::Display for StrFilePredicate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let palette = crate::Palette::new(f.alternate()); + write!( + f, + "{} {} {}", + palette.var("var"), + palette.description("is"), + palette.expected(self.path.display()) + ) + } +} diff --git a/src/path/ft.rs b/src/path/ft.rs new file mode 100644 index 0000000..aceed55 --- /dev/null +++ b/src/path/ft.rs @@ -0,0 +1,207 @@ +// Copyright (c) 2018 The predicates-rs Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::fmt; +use std::fs; +use std::io; +use std::path; + +use crate::reflection; +use crate::Predicate; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum FileType { + File, + Dir, + Symlink, +} + +impl FileType { + fn from_path(path: &path::Path, follow: bool) -> io::Result { + let file_type = if follow { + path.metadata() + } else { + path.symlink_metadata() + }? + .file_type(); + if file_type.is_dir() { + return Ok(FileType::Dir); + } + if file_type.is_file() { + return Ok(FileType::File); + } + Ok(FileType::Symlink) + } + + fn eval(self, ft: fs::FileType) -> bool { + match self { + FileType::File => ft.is_file(), + FileType::Dir => ft.is_dir(), + FileType::Symlink => ft.is_symlink(), + } + } +} + +impl fmt::Display for FileType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let t = match *self { + FileType::File => "file", + FileType::Dir => "dir", + FileType::Symlink => "symlink", + }; + write!(f, "{}", t) + } +} + +/// Predicate that checks the `std::fs::FileType`. +/// +/// This is created by the `predicate::path::is_file`, `predicate::path::is_dir`, and `predicate::path::is_symlink`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct FileTypePredicate { + ft: FileType, + follow: bool, +} + +impl FileTypePredicate { + /// Follow symbolic links. + /// + /// When yes is true, symbolic links are followed as if they were normal directories and files. + /// + /// Default: disabled. + pub fn follow_links(mut self, yes: bool) -> Self { + self.follow = yes; + self + } + + /// Allow to create an `FileTypePredicate` from a `path` + pub fn from_path(path: &path::Path) -> io::Result { + Ok(FileTypePredicate { + ft: FileType::from_path(path, true)?, + follow: true, + }) + } +} + +impl Predicate for FileTypePredicate { + fn eval(&self, path: &path::Path) -> bool { + let metadata = if self.follow { + path.metadata() + } else { + path.symlink_metadata() + }; + metadata + .map(|m| self.ft.eval(m.file_type())) + .unwrap_or(false) + } + + fn find_case<'a>( + &'a self, + expected: bool, + variable: &path::Path, + ) -> Option> { + let actual_type = FileType::from_path(variable, self.follow); + match (expected, actual_type) { + (_, Ok(actual_type)) => { + let result = self.ft == actual_type; + if result == expected { + Some( + reflection::Case::new(Some(self), result) + .add_product(reflection::Product::new("actual filetype", actual_type)), + ) + } else { + None + } + } + (true, Err(_)) => None, + (false, Err(err)) => Some( + reflection::Case::new(Some(self), false) + .add_product(reflection::Product::new("error", err)), + ), + } + } +} + +impl reflection::PredicateReflection for FileTypePredicate { + fn parameters<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Parameter::new("follow", &self.follow)]; + Box::new(params.into_iter()) + } +} + +impl fmt::Display for FileTypePredicate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let palette = crate::Palette::new(f.alternate()); + write!( + f, + "{} {} {}", + palette.var("var"), + palette.description("is"), + palette.expected(self.ft) + ) + } +} + +/// Creates a new `Predicate` that ensures the path points to a file. +/// +/// # Examples +/// +/// ``` +/// use std::path::Path; +/// use predicates::prelude::*; +/// +/// let predicate_fn = predicate::path::is_file(); +/// assert_eq!(true, predicate_fn.eval(Path::new("Cargo.toml"))); +/// assert_eq!(false, predicate_fn.eval(Path::new("src"))); +/// assert_eq!(false, predicate_fn.eval(Path::new("non-existent-file.foo"))); +/// ``` +pub fn is_file() -> FileTypePredicate { + FileTypePredicate { + ft: FileType::File, + follow: false, + } +} + +/// Creates a new `Predicate` that ensures the path points to a directory. +/// +/// # Examples +/// +/// ``` +/// use std::path::Path; +/// use predicates::prelude::*; +/// +/// let predicate_fn = predicate::path::is_dir(); +/// assert_eq!(false, predicate_fn.eval(Path::new("Cargo.toml"))); +/// assert_eq!(true, predicate_fn.eval(Path::new("src"))); +/// assert_eq!(false, predicate_fn.eval(Path::new("non-existent-file.foo"))); +/// ``` +pub fn is_dir() -> FileTypePredicate { + FileTypePredicate { + ft: FileType::Dir, + follow: false, + } +} + +/// Creates a new `Predicate` that ensures the path points to a symlink. +/// +/// # Examples +/// +/// ``` +/// use std::path::Path; +/// use predicates::prelude::*; +/// +/// let predicate_fn = predicate::path::is_symlink(); +/// assert_eq!(false, predicate_fn.eval(Path::new("Cargo.toml"))); +/// assert_eq!(false, predicate_fn.eval(Path::new("src"))); +/// assert_eq!(false, predicate_fn.eval(Path::new("non-existent-file.foo"))); +/// ``` +pub fn is_symlink() -> FileTypePredicate { + FileTypePredicate { + ft: FileType::Symlink, + follow: false, + } +} diff --git a/src/path/mod.rs b/src/path/mod.rs new file mode 100644 index 0000000..8c0ed9a --- /dev/null +++ b/src/path/mod.rs @@ -0,0 +1,20 @@ +// Copyright (c) 2018 The predicates-rs Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Path Predicates +//! +//! This module contains predicates specific to the file system. + +mod existence; +pub use self::existence::{exists, missing, ExistencePredicate}; +mod ft; +pub use self::ft::{is_dir, is_file, is_symlink, FileTypePredicate}; +mod fc; +pub use self::fc::{FileContentPredicate, PredicateFileContentExt}; +mod fs; +pub use self::fs::{eq_file, BinaryFilePredicate, StrFilePredicate}; diff --git a/src/prelude.rs b/src/prelude.rs new file mode 100644 index 0000000..8cfd447 --- /dev/null +++ b/src/prelude.rs @@ -0,0 +1,56 @@ +// Copyright (c) 2018 The predicates-rs Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Module that contains the essentials for working with predicates. + +pub use crate::boolean::PredicateBooleanExt; +pub use crate::boxed::PredicateBoxExt; +pub use crate::name::PredicateNameExt; +pub use crate::path::PredicateFileContentExt; +pub use crate::str::PredicateStrExt; +pub use crate::Predicate; + +/// Predicate factories +pub mod predicate { + // primitive `Predicate` types + pub use crate::constant::{always, never}; + pub use crate::function::function; + pub use crate::iter::{in_hash, in_iter}; + pub use crate::ord::{eq, ge, gt, le, lt, ne}; + + /// `str` Predicate factories + /// + /// This module contains predicates specific to string handling. + pub mod str { + pub use crate::str::is_empty; + pub use crate::str::{contains, ends_with, starts_with}; + + #[cfg(feature = "diff")] + pub use crate::str::diff; + + #[cfg(feature = "regex")] + pub use crate::str::is_match; + } + + /// `Path` Predicate factories + /// + /// This module contains predicates specific to path handling. + pub mod path { + pub use crate::path::eq_file; + pub use crate::path::{exists, missing}; + pub use crate::path::{is_dir, is_file, is_symlink}; + } + + /// `f64` Predicate factories + /// + /// This module contains predicates specific to float handling. + pub mod float { + #[cfg(feature = "float-cmp")] + pub use crate::float::is_close; + } +} diff --git a/src/str/adapters.rs b/src/str/adapters.rs new file mode 100644 index 0000000..8ecd848 --- /dev/null +++ b/src/str/adapters.rs @@ -0,0 +1,204 @@ +// Copyright (c) 2018 The predicates-rs Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::ffi; +use std::fmt; +use std::str; + +use crate::reflection; +#[cfg(feature = "normalize-line-endings")] +use crate::str::normalize::NormalizedPredicate; +use crate::Predicate; + +/// Predicate adaper that trims the variable being tested. +/// +/// This is created by `pred.trim()`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct TrimPredicate

+where + P: Predicate, +{ + p: P, +} + +impl

Predicate for TrimPredicate

+where + P: Predicate, +{ + fn eval(&self, variable: &str) -> bool { + self.p.eval(variable.trim()) + } + + fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { + self.p.find_case(expected, variable.trim()) + } +} + +impl

reflection::PredicateReflection for TrimPredicate

+where + P: Predicate, +{ + fn children<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Child::new("predicate", &self.p)]; + Box::new(params.into_iter()) + } +} + +impl

fmt::Display for TrimPredicate

+where + P: Predicate, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.p.fmt(f) + } +} + +/// Predicate adaper that converts a `str` predicate to byte predicate. +/// +/// This is created by `pred.from_utf8()`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Utf8Predicate

+where + P: Predicate, +{ + p: P, +} + +impl

Predicate for Utf8Predicate

+where + P: Predicate, +{ + fn eval(&self, variable: &ffi::OsStr) -> bool { + variable.to_str().map(|s| self.p.eval(s)).unwrap_or(false) + } + + fn find_case<'a>( + &'a self, + expected: bool, + variable: &ffi::OsStr, + ) -> Option> { + let var_str = variable.to_str(); + match (expected, var_str) { + (_, Some(var_str)) => self.p.find_case(expected, var_str).map(|child| { + child.add_product(reflection::Product::new("var as str", var_str.to_owned())) + }), + (true, None) => None, + (false, None) => Some( + reflection::Case::new(Some(self), false) + .add_product(reflection::Product::new("error", "Invalid UTF-8 string")), + ), + } + } +} + +impl

Predicate<[u8]> for Utf8Predicate

+where + P: Predicate, +{ + fn eval(&self, variable: &[u8]) -> bool { + str::from_utf8(variable) + .map(|s| self.p.eval(s)) + .unwrap_or(false) + } + + fn find_case<'a>(&'a self, expected: bool, variable: &[u8]) -> Option> { + let var_str = str::from_utf8(variable); + match (expected, var_str) { + (_, Ok(var_str)) => self.p.find_case(expected, var_str).map(|child| { + child.add_product(reflection::Product::new("var as str", var_str.to_owned())) + }), + (true, Err(_)) => None, + (false, Err(err)) => Some( + reflection::Case::new(Some(self), false) + .add_product(reflection::Product::new("error", err)), + ), + } + } +} + +impl

reflection::PredicateReflection for Utf8Predicate

+where + P: Predicate, +{ + fn children<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Child::new("predicate", &self.p)]; + Box::new(params.into_iter()) + } +} + +impl

fmt::Display for Utf8Predicate

+where + P: Predicate, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.p.fmt(f) + } +} + +/// `Predicate` extension adapting a `str` Predicate. +pub trait PredicateStrExt +where + Self: Predicate, + Self: Sized, +{ + /// Returns a `TrimPredicate` that ensures the data passed to `Self` is trimmed. + /// + /// # Examples + /// + /// ``` + /// use predicates::prelude::*; + /// + /// let predicate_fn = predicate::str::is_empty().trim(); + /// assert_eq!(true, predicate_fn.eval(" ")); + /// assert_eq!(false, predicate_fn.eval(" Hello ")); + /// ``` + fn trim(self) -> TrimPredicate { + TrimPredicate { p: self } + } + + /// Returns a `Utf8Predicate` that adapts `Self` to a `[u8]` `Predicate`. + /// + /// # Examples + /// + /// ``` + /// use predicates::prelude::*; + /// use std::ffi::OsStr; + /// + /// let predicate_fn = predicate::str::is_empty().not().from_utf8(); + /// assert_eq!(true, predicate_fn.eval(OsStr::new("Hello"))); + /// assert_eq!(false, predicate_fn.eval(OsStr::new(""))); + /// let variable: &[u8] = b""; + /// assert_eq!(false, predicate_fn.eval(variable)); + /// ``` + #[allow(clippy::wrong_self_convention)] + fn from_utf8(self) -> Utf8Predicate { + Utf8Predicate { p: self } + } + + /// Returns a `NormalizedPredicate` that ensures + /// the newlines within the data passed to `Self` is normalised. + /// + /// # Examples + /// + /// ``` + /// use predicates::prelude::*; + /// + /// let predicate_fn = predicate::eq("Hello World!\n").normalize(); + /// assert_eq!(true, predicate_fn.eval("Hello World!\n")); + /// assert_eq!(true, predicate_fn.eval("Hello World!\r")); + /// assert_eq!(true, predicate_fn.eval("Hello World!\r\n")); + /// assert_eq!(false, predicate_fn.eval("Goodbye")); + /// ``` + /// + #[cfg(feature = "normalize-line-endings")] + fn normalize(self) -> NormalizedPredicate { + NormalizedPredicate { p: self } + } +} + +impl

PredicateStrExt for P where P: Predicate {} diff --git a/src/str/basics.rs b/src/str/basics.rs new file mode 100644 index 0000000..4128a8e --- /dev/null +++ b/src/str/basics.rs @@ -0,0 +1,290 @@ +// Copyright (c) 2018 The predicates-rs Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::fmt; + +use crate::reflection; +use crate::utils; +use crate::Predicate; + +/// Predicate that checks for empty strings. +/// +/// This is created by `predicates::str::is_empty`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct IsEmptyPredicate {} + +impl Predicate for IsEmptyPredicate { + fn eval(&self, variable: &str) -> bool { + variable.is_empty() + } + + fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { + utils::default_find_case(self, expected, variable) + .map(|case| case.add_product(reflection::Product::new("var", variable.to_owned()))) + } +} + +impl reflection::PredicateReflection for IsEmptyPredicate {} + +impl fmt::Display for IsEmptyPredicate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let palette = crate::Palette::new(f.alternate()); + write!( + f, + "{}.{}()", + palette.var("var"), + palette.description("is_empty"), + ) + } +} + +/// Creates a new `Predicate` that ensures a str is empty +/// +/// # Examples +/// +/// ``` +/// use predicates::prelude::*; +/// +/// let predicate_fn = predicate::str::is_empty(); +/// assert_eq!(true, predicate_fn.eval("")); +/// assert_eq!(false, predicate_fn.eval("Food World")); +/// ``` +pub fn is_empty() -> IsEmptyPredicate { + IsEmptyPredicate {} +} + +/// Predicate checks start of str +/// +/// This is created by `predicates::str::starts_with`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StartsWithPredicate { + pattern: String, +} + +impl Predicate for StartsWithPredicate { + fn eval(&self, variable: &str) -> bool { + variable.starts_with(&self.pattern) + } + + fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { + utils::default_find_case(self, expected, variable) + .map(|case| case.add_product(reflection::Product::new("var", variable.to_owned()))) + } +} + +impl reflection::PredicateReflection for StartsWithPredicate {} + +impl fmt::Display for StartsWithPredicate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let palette = crate::Palette::new(f.alternate()); + write!( + f, + "{}.{}({:?})", + palette.var("var"), + palette.description("starts_with"), + self.pattern + ) + } +} + +/// Creates a new `Predicate` that ensures a str starts with `pattern` +/// +/// # Examples +/// +/// ``` +/// use predicates::prelude::*; +/// +/// let predicate_fn = predicate::str::starts_with("Hello"); +/// assert_eq!(true, predicate_fn.eval("Hello World")); +/// assert_eq!(false, predicate_fn.eval("Goodbye World")); +/// ``` +pub fn starts_with

(pattern: P) -> StartsWithPredicate +where + P: Into, +{ + StartsWithPredicate { + pattern: pattern.into(), + } +} + +/// Predicate checks end of str +/// +/// This is created by `predicates::str::ends_with`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EndsWithPredicate { + pattern: String, +} + +impl Predicate for EndsWithPredicate { + fn eval(&self, variable: &str) -> bool { + variable.ends_with(&self.pattern) + } + + fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { + utils::default_find_case(self, expected, variable) + .map(|case| case.add_product(reflection::Product::new("var", variable.to_owned()))) + } +} + +impl reflection::PredicateReflection for EndsWithPredicate {} + +impl fmt::Display for EndsWithPredicate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let palette = crate::Palette::new(f.alternate()); + write!( + f, + "{}.{}({:?})", + palette.var("var"), + palette.description("ends_with"), + self.pattern + ) + } +} + +/// Creates a new `Predicate` that ensures a str ends with `pattern` +/// +/// # Examples +/// +/// ``` +/// use predicates::prelude::*; +/// +/// let predicate_fn = predicate::str::ends_with("World"); +/// assert_eq!(true, predicate_fn.eval("Hello World")); +/// assert_eq!(false, predicate_fn.eval("Hello Moon")); +/// ``` +pub fn ends_with

(pattern: P) -> EndsWithPredicate +where + P: Into, +{ + EndsWithPredicate { + pattern: pattern.into(), + } +} + +/// Predicate that checks for patterns. +/// +/// This is created by `predicates::str:contains`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ContainsPredicate { + pattern: String, +} + +impl ContainsPredicate { + /// Require a specific count of matches. + /// + /// # Examples + /// + /// ``` + /// use predicates::prelude::*; + /// + /// let predicate_fn = predicate::str::contains("Two").count(2); + /// assert_eq!(true, predicate_fn.eval("One Two Three Two One")); + /// assert_eq!(false, predicate_fn.eval("One Two Three")); + /// ``` + pub fn count(self, count: usize) -> MatchesPredicate { + MatchesPredicate { + pattern: self.pattern, + count, + } + } +} + +impl Predicate for ContainsPredicate { + fn eval(&self, variable: &str) -> bool { + variable.contains(&self.pattern) + } + + fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { + utils::default_find_case(self, expected, variable) + .map(|case| case.add_product(reflection::Product::new("var", variable.to_owned()))) + } +} + +impl reflection::PredicateReflection for ContainsPredicate {} + +impl fmt::Display for ContainsPredicate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let palette = crate::Palette::new(f.alternate()); + write!( + f, + "{}.{}({})", + palette.var("var"), + palette.description("contains"), + palette.expected(&self.pattern), + ) + } +} + +/// Predicate that checks for repeated patterns. +/// +/// This is created by `predicates::str::contains(...).count`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MatchesPredicate { + pattern: String, + count: usize, +} + +impl Predicate for MatchesPredicate { + fn eval(&self, variable: &str) -> bool { + variable.matches(&self.pattern).count() == self.count + } + + fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { + let actual_count = variable.matches(&self.pattern).count(); + let result = self.count == actual_count; + if result == expected { + Some( + reflection::Case::new(Some(self), result) + .add_product(reflection::Product::new("var", variable.to_owned())) + .add_product(reflection::Product::new("actual count", actual_count)), + ) + } else { + None + } + } +} + +impl reflection::PredicateReflection for MatchesPredicate { + fn parameters<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Parameter::new("count", &self.count)]; + Box::new(params.into_iter()) + } +} + +impl fmt::Display for MatchesPredicate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let palette = crate::Palette::new(f.alternate()); + write!( + f, + "{}.{}({})", + palette.var("var"), + palette.description("contains"), + palette.expected(&self.pattern), + ) + } +} + +/// Creates a new `Predicate` that ensures a str contains `pattern` +/// +/// # Examples +/// +/// ``` +/// use predicates::prelude::*; +/// +/// let predicate_fn = predicate::str::contains("Two"); +/// assert_eq!(true, predicate_fn.eval("One Two Three")); +/// assert_eq!(false, predicate_fn.eval("Four Five Six")); +/// ``` +pub fn contains

(pattern: P) -> ContainsPredicate +where + P: Into, +{ + ContainsPredicate { + pattern: pattern.into(), + } +} diff --git a/src/str/difference.rs b/src/str/difference.rs new file mode 100644 index 0000000..6c4de21 --- /dev/null +++ b/src/str/difference.rs @@ -0,0 +1,132 @@ +// Copyright (c) 2018 The predicates-rs Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::borrow; +use std::fmt; + +use crate::reflection; +use crate::Predicate; + +/// Predicate that diffs two strings. +/// +/// This is created by the `predicate::str::diff`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DifferencePredicate { + orig: borrow::Cow<'static, str>, +} + +impl Predicate for DifferencePredicate { + fn eval(&self, edit: &str) -> bool { + edit == self.orig + } + + fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { + let result = variable != self.orig; + if result == expected { + None + } else { + let palette = crate::Palette::new(true); + let orig: Vec<_> = self.orig.lines().map(|l| format!("{}\n", l)).collect(); + let variable: Vec<_> = variable.lines().map(|l| format!("{}\n", l)).collect(); + let diff = difflib::unified_diff( + &orig, + &variable, + "", + "", + &palette.expected("orig").to_string(), + &palette.var("var").to_string(), + 0, + ); + let mut diff = colorize_diff(diff, palette); + diff.insert(0, "\n".to_owned()); + + Some( + reflection::Case::new(Some(self), result).add_product(reflection::Product::new( + "diff", + itertools::join(diff.iter(), ""), + )), + ) + } + } +} + +impl reflection::PredicateReflection for DifferencePredicate { + fn parameters<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Parameter::new("original", &self.orig)]; + Box::new(params.into_iter()) + } +} + +impl fmt::Display for DifferencePredicate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let palette = crate::Palette::new(f.alternate()); + write!( + f, + "{:#} {:#} {:#}", + palette.description("diff"), + palette.expected("original"), + palette.var("var"), + ) + } +} + +/// Creates a new `Predicate` that diffs two strings. +/// +/// # Examples +/// +/// ``` +/// use predicates::prelude::*; +/// +/// let predicate_fn = predicate::str::diff("Hello World"); +/// assert_eq!(true, predicate_fn.eval("Hello World")); +/// assert!(predicate_fn.find_case(false, "Hello World").is_none()); +/// assert_eq!(false, predicate_fn.eval("Goodbye World")); +/// assert!(predicate_fn.find_case(false, "Goodbye World").is_some()); +/// ``` +pub fn diff(orig: S) -> DifferencePredicate +where + S: Into>, +{ + DifferencePredicate { orig: orig.into() } +} + +#[cfg(feature = "color")] +fn colorize_diff(mut lines: Vec, palette: crate::Palette) -> Vec { + for (i, line) in lines.iter_mut().enumerate() { + match (i, line.as_bytes().first()) { + (0, _) => { + if let Some((prefix, body)) = line.split_once(' ') { + *line = format!("{:#} {}", palette.expected(prefix), body); + } + } + (1, _) => { + if let Some((prefix, body)) = line.split_once(' ') { + *line = format!("{:#} {}", palette.var(prefix), body); + } + } + (_, Some(b'-')) => { + let (prefix, body) = line.split_at(1); + *line = format!("{:#}{}", palette.expected(prefix), body); + } + (_, Some(b'+')) => { + let (prefix, body) = line.split_at(1); + *line = format!("{:#}{}", palette.var(prefix), body); + } + (_, Some(b'@')) => { + *line = format!("{:#}", palette.description(&line)); + } + _ => (), + } + } + lines +} + +#[cfg(not(feature = "color"))] +fn colorize_diff(lines: Vec, _palette: crate::Palette) -> Vec { + lines +} diff --git a/src/str/mod.rs b/src/str/mod.rs new file mode 100644 index 0000000..0d0dffd --- /dev/null +++ b/src/str/mod.rs @@ -0,0 +1,30 @@ +// Copyright (c) 2018 The predicates-rs Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! String Predicates +//! +//! This module contains predicates specific to string handling. + +mod basics; +pub use self::basics::*; +mod adapters; +pub use self::adapters::*; + +#[cfg(feature = "diff")] +mod difference; +#[cfg(feature = "diff")] +pub use self::difference::{diff, DifferencePredicate}; +#[cfg(feature = "normalize-line-endings")] +mod normalize; +#[cfg(feature = "normalize-line-endings")] +pub use self::normalize::NormalizedPredicate; + +#[cfg(feature = "regex")] +mod regex; +#[cfg(feature = "regex")] +pub use self::regex::{is_match, RegexError, RegexPredicate}; diff --git a/src/str/normalize.rs b/src/str/normalize.rs new file mode 100644 index 0000000..6a65733 --- /dev/null +++ b/src/str/normalize.rs @@ -0,0 +1,58 @@ +// Copyright (c) 2018 The predicates-rs Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::reflection; +use crate::Predicate; +use std::fmt; + +use normalize_line_endings::normalized; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// Predicate adapter that normalizes the newlines contained in the variable being tested. +/// +/// This is created by `pred.normalize()`. +pub struct NormalizedPredicate

+where + P: Predicate, +{ + pub(crate) p: P, +} + +impl

reflection::PredicateReflection for NormalizedPredicate

+where + P: Predicate, +{ + fn children<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Child::new("predicate", &self.p)]; + Box::new(params.into_iter()) + } +} + +impl

Predicate for NormalizedPredicate

+where + P: Predicate, +{ + fn eval(&self, variable: &str) -> bool { + let variable = normalized(variable.chars()).collect::(); + self.p.eval(&variable) + } + + fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { + let variable = normalized(variable.chars()).collect::(); + self.p.find_case(expected, &variable) + } +} + +impl

fmt::Display for NormalizedPredicate

+where + P: Predicate, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.p.fmt(f) + } +} diff --git a/src/str/regex.rs b/src/str/regex.rs new file mode 100644 index 0000000..abb46f8 --- /dev/null +++ b/src/str/regex.rs @@ -0,0 +1,134 @@ +// Copyright (c) 2018 The predicates-rs Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::fmt; + +use crate::reflection; +use crate::utils; +use crate::Predicate; + +/// An error that occurred during parsing or compiling a regular expression. +pub type RegexError = regex::Error; + +/// Predicate that uses regex matching +/// +/// This is created by the `predicate::str::is_match`. +#[derive(Debug, Clone)] +pub struct RegexPredicate { + re: regex::Regex, +} + +impl RegexPredicate { + /// Require a specific count of matches. + /// + /// # Examples + /// + /// ``` + /// use predicates::prelude::*; + /// + /// let predicate_fn = predicate::str::is_match("T[a-z]*").unwrap().count(3); + /// assert_eq!(true, predicate_fn.eval("One Two Three Two One")); + /// assert_eq!(false, predicate_fn.eval("One Two Three")); + /// ``` + pub fn count(self, count: usize) -> RegexMatchesPredicate { + RegexMatchesPredicate { re: self.re, count } + } +} + +impl Predicate for RegexPredicate { + fn eval(&self, variable: &str) -> bool { + self.re.is_match(variable) + } + + fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { + utils::default_find_case(self, expected, variable) + .map(|case| case.add_product(reflection::Product::new("var", variable.to_owned()))) + } +} + +impl reflection::PredicateReflection for RegexPredicate {} + +impl fmt::Display for RegexPredicate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let palette = crate::Palette::new(f.alternate()); + write!( + f, + "{}.{}({})", + palette.var("var"), + palette.description("is_match"), + palette.expected(&self.re), + ) + } +} + +/// Predicate that checks for repeated patterns. +/// +/// This is created by `predicates::str::is_match(...).count`. +#[derive(Debug, Clone)] +pub struct RegexMatchesPredicate { + re: regex::Regex, + count: usize, +} + +impl Predicate for RegexMatchesPredicate { + fn eval(&self, variable: &str) -> bool { + self.re.find_iter(variable).count() == self.count + } + + fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { + let actual_count = self.re.find_iter(variable).count(); + let result = self.count == actual_count; + if result == expected { + Some( + reflection::Case::new(Some(self), result) + .add_product(reflection::Product::new("var", variable.to_owned())) + .add_product(reflection::Product::new("actual count", actual_count)), + ) + } else { + None + } + } +} + +impl reflection::PredicateReflection for RegexMatchesPredicate { + fn parameters<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Parameter::new("count", &self.count)]; + Box::new(params.into_iter()) + } +} + +impl fmt::Display for RegexMatchesPredicate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let palette = crate::Palette::new(f.alternate()); + write!( + f, + "{}.{}({})", + palette.var("var"), + palette.description("is_match"), + palette.expected(&self.re), + ) + } +} + +/// Creates a new `Predicate` that uses a regular expression to match the string. +/// +/// # Examples +/// +/// ``` +/// use predicates::prelude::*; +/// +/// let predicate_fn = predicate::str::is_match("^Hello.*$").unwrap(); +/// assert_eq!(true, predicate_fn.eval("Hello World")); +/// assert_eq!(false, predicate_fn.eval("Food World")); +/// ``` +pub fn is_match(pattern: S) -> Result +where + S: AsRef, +{ + regex::Regex::new(pattern.as_ref()).map(|re| RegexPredicate { re }) +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..023c545 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,64 @@ +// Copyright (c) 2018 The predicates-rs Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::fmt; + +use crate::reflection; +use crate::Predicate; + +#[derive(Clone, PartialEq, Eq)] +pub(crate) struct DebugAdapter +where + T: fmt::Debug, +{ + pub(crate) debug: T, +} + +impl DebugAdapter +where + T: fmt::Debug, +{ + pub fn new(debug: T) -> Self { + Self { debug } + } +} + +impl fmt::Display for DebugAdapter +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:#?}", self.debug) + } +} + +impl fmt::Debug for DebugAdapter +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.debug.fmt(f) + } +} + +pub(crate) fn default_find_case<'a, P, Item>( + pred: &'a P, + expected: bool, + variable: &Item, +) -> Option> +where + P: Predicate, + Item: ?Sized, +{ + let actual = pred.eval(variable); + if expected == actual { + Some(reflection::Case::new(Some(pred), actual)) + } else { + None + } +} -- cgit v1.2.3