summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Geisler <mgeisler@google.com>2023-11-16 12:23:17 +0100
committerMartin Geisler <mgeisler@google.com>2023-11-16 12:23:17 +0100
commitbc9fa72e592f6dc45806077d359ef17693927561 (patch)
tree9c83e78ea74d26fe63d5098dc969275517eeaa93
parent1b60f813442a22a324c7e43628f2da2c42adda9e (diff)
downloadpredicates-upstream.tar.gz
Import 'predicates' crateupstream
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
-rw-r--r--.cargo_vcs_info.json6
-rw-r--r--Android.bp56
-rw-r--r--Cargo.lock140
-rw-r--r--Cargo.toml137
l---------LICENSE1
-rw-r--r--LICENSE-APACHE201
-rw-r--r--LICENSE-MIT19
-rw-r--r--METADATA20
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--OWNERS2
-rw-r--r--README.md45
-rw-r--r--cargo2android.json6
-rw-r--r--examples/case_tree.rs13
-rw-r--r--patches/use-termcolor.patch111
-rw-r--r--src/boolean.rs508
-rw-r--r--src/boxed.rs117
-rw-r--r--src/color.rs79
-rw-r--r--src/constant.rs83
-rw-r--r--src/float/close.rs158
-rw-r--r--src/float/mod.rs16
-rw-r--r--src/function.rs149
-rw-r--r--src/iter.rs330
-rw-r--r--src/lib.rs227
-rw-r--r--src/name.rs115
-rw-r--r--src/ord.rs304
-rw-r--r--src/path/existence.rs85
-rw-r--r--src/path/fc.rs121
-rw-r--r--src/path/fs.rs179
-rw-r--r--src/path/ft.rs207
-rw-r--r--src/path/mod.rs20
-rw-r--r--src/prelude.rs56
-rw-r--r--src/str/adapters.rs204
-rw-r--r--src/str/basics.rs290
-rw-r--r--src/str/difference.rs132
-rw-r--r--src/str/mod.rs30
-rw-r--r--src/str/normalize.rs58
-rw-r--r--src/str/regex.rs134
-rw-r--r--src/utils.rs64
38 files changed, 4423 insertions, 0 deletions
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 <nick@bitcurry.com>"]
+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 = """
+<!-- next-header -->
+## [Unreleased] - ReleaseDate
+"""
+search = "<!-- next-header -->"
+
+[[package.metadata.release.pre-release-replacements]]
+exactly = 1
+file = "CHANGELOG.md"
+replace = """
+<!-- next-url -->
+[Unreleased]: https://github.com/assert-rs/predicates-rs/compare/{{tag_name}}...HEAD"""
+search = "<!-- next-url -->"
+
+[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
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
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<D: std::fmt::Display>(self, display: D) -> Styled<D> {
+- Styled::new(display, self.description)
++ pub(crate) fn description<D: std::fmt::Display>(&self, display: D) -> Styled<D> {
++ Styled::new(display, self.description.clone())
+ }
+
+- pub(crate) fn var<D: std::fmt::Display>(self, display: D) -> Styled<D> {
+- Styled::new(display, self.var)
++ pub(crate) fn var<D: std::fmt::Display>(&self, display: D) -> Styled<D> {
++ Styled::new(display, self.var.clone())
+ }
+
+- pub(crate) fn expected<D: std::fmt::Display>(self, display: D) -> Styled<D> {
+- Styled::new(display, self.expected)
++ pub(crate) fn expected<D: std::fmt::Display>(&self, display: D) -> Styled<D> {
++ Styled::new(display, self.expected.clone())
+ }
+ }
+
+ #[derive(Debug)]
+ pub(crate) struct Styled<D> {
+ display: D,
+- style: anstyle::Style,
++ style: ColorSpec,
+ }
+
+ impl<D: std::fmt::Display> Styled<D> {
+- 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 <LICENSE-APACHE or
+// http://www.apache.org/license/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<M1, M2, Item>
+where
+ M1: Predicate<Item>,
+ M2: Predicate<Item>,
+ Item: ?Sized,
+{
+ a: M1,
+ b: M2,
+ _phantom: PhantomData<Item>,
+}
+
+unsafe impl<M1, M2, Item> Send for AndPredicate<M1, M2, Item>
+where
+ M1: Predicate<Item> + Send,
+ M2: Predicate<Item> + Send,
+ Item: ?Sized,
+{
+}
+
+unsafe impl<M1, M2, Item> Sync for AndPredicate<M1, M2, Item>
+where
+ M1: Predicate<Item> + Sync,
+ M2: Predicate<Item> + Sync,
+ Item: ?Sized,
+{
+}
+
+impl<M1, M2, Item> AndPredicate<M1, M2, Item>
+where
+ M1: Predicate<Item>,
+ M2: Predicate<Item>,
+ Item: ?Sized,
+{
+ /// Create a new `AndPredicate` over predicates `a` and `b`.
+ pub fn new(a: M1, b: M2) -> AndPredicate<M1, M2, Item> {
+ AndPredicate {
+ a,
+ b,
+ _phantom: PhantomData,
+ }
+ }
+}
+
+impl<M1, M2, Item> Predicate<Item> for AndPredicate<M1, M2, Item>
+where
+ M1: Predicate<Item>,
+ M2: Predicate<Item>,
+ 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<reflection::Case<'a>> {
+ 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<M1, M2, Item> reflection::PredicateReflection for AndPredicate<M1, M2, Item>
+where
+ M1: Predicate<Item>,
+ M2: Predicate<Item>,
+ Item: ?Sized,
+{
+ fn children<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Child<'a>> + 'a> {
+ let params = vec![
+ reflection::Child::new("left", &self.a),
+ reflection::Child::new("right", &self.b),
+ ];
+ Box::new(params.into_iter())
+ }
+}
+
+impl<M1, M2, Item> fmt::Display for AndPredicate<M1, M2, Item>
+where
+ M1: Predicate<Item>,
+ M2: Predicate<Item>,
+ 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<M1, M2, Item>
+where
+ M1: Predicate<Item>,
+ M2: Predicate<Item>,
+ Item: ?Sized,
+{
+ a: M1,
+ b: M2,
+ _phantom: PhantomData<Item>,
+}
+
+unsafe impl<M1, M2, Item> Send for OrPredicate<M1, M2, Item>
+where
+ M1: Predicate<Item> + Send,
+ M2: Predicate<Item> + Send,
+ Item: ?Sized,
+{
+}
+
+unsafe impl<M1, M2, Item> Sync for OrPredicate<M1, M2, Item>
+where
+ M1: Predicate<Item> + Sync,
+ M2: Predicate<Item> + Sync,
+ Item: ?Sized,
+{
+}
+
+impl<M1, M2, Item> OrPredicate<M1, M2, Item>
+where
+ M1: Predicate<Item>,
+ M2: Predicate<Item>,
+ Item: ?Sized,
+{
+ /// Create a new `OrPredicate` over predicates `a` and `b`.
+ pub fn new(a: M1, b: M2) -> OrPredicate<M1, M2, Item> {
+ OrPredicate {
+ a,
+ b,
+ _phantom: PhantomData,
+ }
+ }
+}
+
+impl<M1, M2, Item> Predicate<Item> for OrPredicate<M1, M2, Item>
+where
+ M1: Predicate<Item>,
+ M2: Predicate<Item>,
+ 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<reflection::Case<'a>> {
+ 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<M1, M2, Item> reflection::PredicateReflection for OrPredicate<M1, M2, Item>
+where
+ M1: Predicate<Item>,
+ M2: Predicate<Item>,
+ Item: ?Sized,
+{
+ fn children<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Child<'a>> + 'a> {
+ let params = vec![
+ reflection::Child::new("left", &self.a),
+ reflection::Child::new("right", &self.b),
+ ];
+ Box::new(params.into_iter())
+ }
+}
+
+impl<M1, M2, Item> fmt::Display for OrPredicate<M1, M2, Item>
+where
+ M1: Predicate<Item>,
+ M2: Predicate<Item>,
+ 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<M, Item>
+where
+ M: Predicate<Item>,
+ Item: ?Sized,
+{
+ inner: M,
+ _phantom: PhantomData<Item>,
+}
+
+unsafe impl<M, Item> Send for NotPredicate<M, Item>
+where
+ M: Predicate<Item> + Send,
+ Item: ?Sized,
+{
+}
+
+unsafe impl<M, Item> Sync for NotPredicate<M, Item>
+where
+ M: Predicate<Item> + Sync,
+ Item: ?Sized,
+{
+}
+
+impl<M, Item> NotPredicate<M, Item>
+where
+ M: Predicate<Item>,
+ Item: ?Sized,
+{
+ /// Create a new `NotPredicate` over predicate `inner`.
+ pub fn new(inner: M) -> NotPredicate<M, Item> {
+ NotPredicate {
+ inner,
+ _phantom: PhantomData,
+ }
+ }
+}
+
+impl<M, Item> Predicate<Item> for NotPredicate<M, Item>
+where
+ M: Predicate<Item>,
+ Item: ?Sized,
+{
+ fn eval(&self, item: &Item) -> bool {
+ !self.inner.eval(item)
+ }
+
+ fn find_case<'a>(&'a self, expected: bool, variable: &Item) -> Option<reflection::Case<'a>> {
+ self.inner
+ .find_case(!expected, variable)
+ .map(|child| reflection::Case::new(Some(self), expected).add_child(child))
+ }
+}
+
+impl<M, Item> reflection::PredicateReflection for NotPredicate<M, Item>
+where
+ M: Predicate<Item>,
+ Item: ?Sized,
+{
+ fn children<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Child<'a>> + 'a> {
+ let params = vec![reflection::Child::new("predicate", &self.inner)];
+ Box::new(params.into_iter())
+ }
+}
+
+impl<M, Item> fmt::Display for NotPredicate<M, Item>
+where
+ M: Predicate<Item>,
+ Item: ?Sized,
+{
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "(! {})", self.inner)
+ }
+}
+
+/// `Predicate` extension that adds boolean logic.
+pub trait PredicateBooleanExt<Item: ?Sized>
+where
+ Self: Predicate<Item>,
+{
+ /// 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<B>(self, other: B) -> AndPredicate<Self, B, Item>
+ where
+ B: Predicate<Item>,
+ 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<B>(self, other: B) -> OrPredicate<Self, B, Item>
+ where
+ B: Predicate<Item>,
+ 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<Self, Item>
+ where
+ Self: Sized,
+ {
+ NotPredicate::new(self)
+ }
+}
+
+impl<P, Item> PredicateBooleanExt<Item> for P
+where
+ P: Predicate<Item>,
+ 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 <LICENSE-APACHE or
+// http://www.apache.org/license/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<Item: ?Sized>(Box<dyn Predicate<Item> + Send + Sync>);
+
+impl<Item> BoxPredicate<Item>
+where
+ Item: ?Sized,
+{
+ /// Creates a new `BoxPredicate`, a wrapper around a dynamically-dispatched
+ /// `Predicate` type with useful trait impls.
+ pub fn new<P: Predicate<Item>>(inner: P) -> BoxPredicate<Item>
+ where
+ P: Send + Sync + 'static,
+ {
+ BoxPredicate(Box::new(inner))
+ }
+}
+
+impl<Item> fmt::Debug for BoxPredicate<Item>
+where
+ Item: ?Sized,
+{
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("BoxPredicate").finish()
+ }
+}
+
+impl<Item> reflection::PredicateReflection for BoxPredicate<Item>
+where
+ Item: ?Sized,
+{
+ fn parameters<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Parameter<'a>> + 'a> {
+ self.0.parameters()
+ }
+
+ fn children<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Child<'a>> + 'a> {
+ self.0.children()
+ }
+}
+
+impl<Item> fmt::Display for BoxPredicate<Item>
+where
+ Item: ?Sized,
+{
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+impl<Item> Predicate<Item> for BoxPredicate<Item>
+where
+ Item: ?Sized,
+{
+ fn eval(&self, variable: &Item) -> bool {
+ self.0.eval(variable)
+ }
+
+ fn find_case<'a>(&'a self, expected: bool, variable: &Item) -> Option<reflection::Case<'a>> {
+ utils::default_find_case(self, expected, variable)
+ }
+}
+
+/// `Predicate` extension for boxing a `Predicate`.
+pub trait PredicateBoxExt<Item: ?Sized>
+where
+ Self: Predicate<Item>,
+{
+ /// 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<Item>
+ where
+ Self: Sized + Send + Sync + 'static,
+ {
+ BoxPredicate::new(self)
+ }
+}
+
+impl<P, Item> PredicateBoxExt<Item> for P where P: Predicate<Item> {}
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<D: std::fmt::Display>(&self, display: D) -> Styled<D> {
+ Styled::new(display, self.description.clone())
+ }
+
+ pub(crate) fn var<D: std::fmt::Display>(&self, display: D) -> Styled<D> {
+ Styled::new(display, self.var.clone())
+ }
+
+ pub(crate) fn expected<D: std::fmt::Display>(&self, display: D) -> Styled<D> {
+ Styled::new(display, self.expected.clone())
+ }
+}
+
+#[derive(Debug)]
+pub(crate) struct Styled<D> {
+ display: D,
+ style: ColorSpec,
+}
+
+impl<D: std::fmt::Display> Styled<D> {
+ pub(crate) fn new(display: D, style: ColorSpec) -> Self {
+ Self { display, style }
+ }
+}
+
+impl<D: std::fmt::Display> std::fmt::Display for Styled<D> {
+ #[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 <LICENSE-APACHE or
+// http://www.apache.org/license/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<Item: ?Sized> Predicate<Item> for BooleanPredicate {
+ fn eval(&self, _variable: &Item) -> bool {
+ self.retval
+ }
+
+ fn find_case<'a>(&'a self, expected: bool, variable: &Item) -> Option<reflection::Case<'a>> {
+ utils::default_find_case(self, expected, variable)
+ }
+}
+
+impl reflection::PredicateReflection for BooleanPredicate {
+ fn parameters<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Parameter<'a>> + '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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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: <f64 as 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: <f64 as Ulps>::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: <f64 as Ulps>::U) -> Self {
+ self.ulps = ulps;
+ self
+ }
+}
+
+impl Predicate<f64> 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<reflection::Case<'a>> {
+ 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<dyn Iterator<Item = reflection::Parameter<'a>> + '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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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 <LICENSE-APACHE or
+// http://www.apache.org/license/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<F, T>
+where
+ F: Fn(&T) -> bool,
+ T: ?Sized,
+{
+ function: F,
+ name: &'static str,
+ _phantom: PhantomData<T>,
+}
+
+unsafe impl<F, T> Send for FnPredicate<F, T>
+where
+ F: Send + Fn(&T) -> bool,
+ T: ?Sized,
+{
+}
+
+unsafe impl<F, T> Sync for FnPredicate<F, T>
+where
+ F: Sync + Fn(&T) -> bool,
+ T: ?Sized,
+{
+}
+
+impl<F, T> FnPredicate<F, T>
+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<F, T> Predicate<T> for FnPredicate<F, T>
+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<reflection::Case<'a>> {
+ utils::default_find_case(self, expected, variable)
+ }
+}
+
+impl<F, T> reflection::PredicateReflection for FnPredicate<F, T>
+where
+ F: Fn(&T) -> bool,
+ T: ?Sized,
+{
+}
+
+impl<F, T> fmt::Display for FnPredicate<F, T>
+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<F, T>(function: F) -> FnPredicate<F, T>
+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 <LICENSE-APACHE or
+// http://www.apache.org/license/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<T>
+where
+ T: PartialEq + fmt::Debug,
+{
+ inner: utils::DebugAdapter<Vec<T>>,
+}
+
+impl<T> InPredicate<T>
+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<T> {
+ let mut items = self.inner.debug;
+ items.sort();
+ OrdInPredicate {
+ inner: utils::DebugAdapter::new(items),
+ }
+ }
+}
+
+impl<P, T> Predicate<P> for InPredicate<T>
+where
+ T: std::borrow::Borrow<P> + 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<reflection::Case<'a>> {
+ utils::default_find_case(self, expected, variable).map(|case| {
+ case.add_product(reflection::Product::new(
+ "var",
+ utils::DebugAdapter::new(variable).to_string(),
+ ))
+ })
+ }
+}
+
+impl<T> reflection::PredicateReflection for InPredicate<T>
+where
+ T: PartialEq + fmt::Debug,
+{
+ fn parameters<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Parameter<'a>> + 'a> {
+ let params = vec![reflection::Parameter::new("values", &self.inner)];
+ Box::new(params.into_iter())
+ }
+}
+
+impl<T> fmt::Display for InPredicate<T>
+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<I, T>(iter: I) -> InPredicate<T>
+where
+ T: PartialEq + fmt::Debug,
+ I: IntoIterator<Item = T>,
+{
+ 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<T>
+where
+ T: Ord + fmt::Debug,
+{
+ inner: utils::DebugAdapter<Vec<T>>,
+}
+
+impl<P, T> Predicate<P> for OrdInPredicate<T>
+where
+ T: std::borrow::Borrow<P> + 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<reflection::Case<'a>> {
+ utils::default_find_case(self, expected, variable).map(|case| {
+ case.add_product(reflection::Product::new(
+ "var",
+ utils::DebugAdapter::new(variable).to_string(),
+ ))
+ })
+ }
+}
+
+impl<T> reflection::PredicateReflection for OrdInPredicate<T>
+where
+ T: Ord + fmt::Debug,
+{
+ fn parameters<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Parameter<'a>> + 'a> {
+ let params = vec![reflection::Parameter::new("values", &self.inner)];
+ Box::new(params.into_iter())
+ }
+}
+
+impl<T> fmt::Display for OrdInPredicate<T>
+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<T>
+where
+ T: Hash + Eq + fmt::Debug,
+{
+ inner: utils::DebugAdapter<HashSet<T>>,
+}
+
+impl<P, T> Predicate<P> for HashableInPredicate<T>
+where
+ T: std::borrow::Borrow<P> + 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<reflection::Case<'a>> {
+ utils::default_find_case(self, expected, variable).map(|case| {
+ case.add_product(reflection::Product::new(
+ "var",
+ utils::DebugAdapter::new(variable).to_string(),
+ ))
+ })
+ }
+}
+
+impl<T> reflection::PredicateReflection for HashableInPredicate<T>
+where
+ T: Hash + Eq + fmt::Debug,
+{
+ fn parameters<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Parameter<'a>> + 'a> {
+ let params = vec![reflection::Parameter::new("values", &self.inner)];
+ Box::new(params.into_iter())
+ }
+}
+
+impl<T> fmt::Display for HashableInPredicate<T>
+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<I, T>(iter: I) -> HashableInPredicate<T>
+where
+ T: Hash + Eq + fmt::Debug,
+ I: IntoIterator<Item = T>,
+{
+ 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 <LICENSE-APACHE or
+// http://www.apache.org/license/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<i32> 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<M, Item>
+where
+ M: Predicate<Item>,
+ Item: ?Sized,
+{
+ inner: M,
+ name: &'static str,
+ _phantom: PhantomData<Item>,
+}
+
+unsafe impl<M, Item> Send for NamePredicate<M, Item>
+where
+ M: Predicate<Item> + Send,
+ Item: ?Sized,
+{
+}
+
+unsafe impl<M, Item> Sync for NamePredicate<M, Item>
+where
+ M: Predicate<Item> + Sync,
+ Item: ?Sized,
+{
+}
+
+impl<M, Item> Predicate<Item> for NamePredicate<M, Item>
+where
+ M: Predicate<Item>,
+ Item: ?Sized,
+{
+ fn eval(&self, item: &Item) -> bool {
+ self.inner.eval(item)
+ }
+
+ fn find_case<'a>(&'a self, expected: bool, variable: &Item) -> Option<reflection::Case<'a>> {
+ self.inner
+ .find_case(expected, variable)
+ .map(|child_case| reflection::Case::new(Some(self), expected).add_child(child_case))
+ }
+}
+
+impl<M, Item> reflection::PredicateReflection for NamePredicate<M, Item>
+where
+ M: Predicate<Item>,
+ Item: ?Sized,
+{
+ fn children<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Child<'a>> + 'a> {
+ let params = vec![reflection::Child::new(self.name, &self.inner)];
+ Box::new(params.into_iter())
+ }
+}
+
+impl<M, Item> fmt::Display for NamePredicate<M, Item>
+where
+ M: Predicate<Item>,
+ 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<Item: ?Sized>
+where
+ Self: Predicate<Item>,
+{
+ /// 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<Self, Item>
+ where
+ Self: Sized,
+ {
+ NamePredicate {
+ inner: self,
+ name,
+ _phantom: PhantomData,
+ }
+ }
+}
+
+impl<P, Item> PredicateNameExt<Item> for P
+where
+ P: Predicate<Item>,
+ 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 <LICENSE-APACHE or
+// http://www.apache.org/license/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<T> {
+ constant: T,
+ op: EqOps,
+}
+
+impl<P, T> Predicate<P> for EqPredicate<T>
+where
+ T: std::borrow::Borrow<P> + 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<reflection::Case<'a>> {
+ utils::default_find_case(self, expected, variable).map(|case| {
+ case.add_product(reflection::Product::new(
+ "var",
+ utils::DebugAdapter::new(variable).to_string(),
+ ))
+ })
+ }
+}
+
+impl<T> reflection::PredicateReflection for EqPredicate<T> where T: fmt::Debug {}
+
+impl<T> fmt::Display for EqPredicate<T>
+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<T>(constant: T) -> EqPredicate<T>
+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<T>(constant: T) -> EqPredicate<T>
+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<T> {
+ constant: T,
+ op: OrdOps,
+}
+
+impl<P, T> Predicate<P> for OrdPredicate<T>
+where
+ T: std::borrow::Borrow<P> + 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<reflection::Case<'a>> {
+ utils::default_find_case(self, expected, variable).map(|case| {
+ case.add_product(reflection::Product::new(
+ "var",
+ utils::DebugAdapter::new(variable).to_string(),
+ ))
+ })
+ }
+}
+
+impl<T> reflection::PredicateReflection for OrdPredicate<T> where T: fmt::Debug {}
+
+impl<T> fmt::Display for OrdPredicate<T>
+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<T>(constant: T) -> OrdPredicate<T>
+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<T>(constant: T) -> OrdPredicate<T>
+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<T>(constant: T) -> OrdPredicate<T>
+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<T>(constant: T) -> OrdPredicate<T>
+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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<path::Path> 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<reflection::Case<'a>> {
+ 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<Vec<u8>> {
+ 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<P>
+where
+ P: Predicate<[u8]>,
+{
+ p: P,
+}
+
+impl<P> FileContentPredicate<P>
+where
+ P: Predicate<[u8]>,
+{
+ fn eval(&self, path: &path::Path) -> io::Result<bool> {
+ let buffer = read_file(path)?;
+ Ok(self.p.eval(&buffer))
+ }
+}
+
+impl<P> reflection::PredicateReflection for FileContentPredicate<P>
+where
+ P: Predicate<[u8]>,
+{
+ fn children<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Child<'a>> + 'a> {
+ let params = vec![reflection::Child::new("predicate", &self.p)];
+ Box::new(params.into_iter())
+ }
+}
+
+impl<P> fmt::Display for FileContentPredicate<P>
+where
+ P: Predicate<[u8]>,
+{
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.p.fmt(f)
+ }
+}
+
+impl<P> Predicate<path::Path> for FileContentPredicate<P>
+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<reflection::Case<'a>> {
+ 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<Self> {
+ FileContentPredicate { p: self }
+ }
+}
+
+impl<P> 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<Vec<u8>> {
+ 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<Vec<u8>>,
+}
+
+impl BinaryFilePredicate {
+ fn eval(&self, path: &path::Path) -> io::Result<bool> {
+ 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<StrFilePredicate> {
+ let path = self.path;
+ let content = String::from_utf8(self.content.debug).ok()?;
+ Some(StrFilePredicate { path, content })
+ }
+}
+
+impl Predicate<path::Path> 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<reflection::Case<'a>> {
+ 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<reflection::Case<'a>> {
+ utils::default_find_case(self, expected, variable)
+ }
+}
+
+impl reflection::PredicateReflection for BinaryFilePredicate {
+ fn parameters<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Parameter<'a>> + '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<P: Into<path::PathBuf>>(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<bool> {
+ let content = read_file(path).ok()?;
+ let content = String::from_utf8(content).ok()?;
+ Some(self.content == content)
+ }
+}
+
+impl Predicate<path::Path> 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<reflection::Case<'a>> {
+ utils::default_find_case(self, expected, variable)
+ }
+}
+
+impl Predicate<str> for StrFilePredicate {
+ fn eval(&self, actual: &str) -> bool {
+ self.content == actual
+ }
+
+ fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option<reflection::Case<'a>> {
+ utils::default_find_case(self, expected, variable)
+ }
+}
+
+impl reflection::PredicateReflection for StrFilePredicate {
+ fn parameters<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Parameter<'a>> + '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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<FileType> {
+ 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<FileTypePredicate> {
+ Ok(FileTypePredicate {
+ ft: FileType::from_path(path, true)?,
+ follow: true,
+ })
+ }
+}
+
+impl Predicate<path::Path> 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<reflection::Case<'a>> {
+ 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<dyn Iterator<Item = reflection::Parameter<'a>> + '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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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 <LICENSE-APACHE or
+// http://www.apache.org/license/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<P>
+where
+ P: Predicate<str>,
+{
+ p: P,
+}
+
+impl<P> Predicate<str> for TrimPredicate<P>
+where
+ P: Predicate<str>,
+{
+ fn eval(&self, variable: &str) -> bool {
+ self.p.eval(variable.trim())
+ }
+
+ fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option<reflection::Case<'a>> {
+ self.p.find_case(expected, variable.trim())
+ }
+}
+
+impl<P> reflection::PredicateReflection for TrimPredicate<P>
+where
+ P: Predicate<str>,
+{
+ fn children<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Child<'a>> + 'a> {
+ let params = vec![reflection::Child::new("predicate", &self.p)];
+ Box::new(params.into_iter())
+ }
+}
+
+impl<P> fmt::Display for TrimPredicate<P>
+where
+ P: Predicate<str>,
+{
+ 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<P>
+where
+ P: Predicate<str>,
+{
+ p: P,
+}
+
+impl<P> Predicate<ffi::OsStr> for Utf8Predicate<P>
+where
+ P: Predicate<str>,
+{
+ 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<reflection::Case<'a>> {
+ 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<P> Predicate<[u8]> for Utf8Predicate<P>
+where
+ P: Predicate<str>,
+{
+ 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<reflection::Case<'a>> {
+ 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<P> reflection::PredicateReflection for Utf8Predicate<P>
+where
+ P: Predicate<str>,
+{
+ fn children<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Child<'a>> + 'a> {
+ let params = vec![reflection::Child::new("predicate", &self.p)];
+ Box::new(params.into_iter())
+ }
+}
+
+impl<P> fmt::Display for Utf8Predicate<P>
+where
+ P: Predicate<str>,
+{
+ 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<str>,
+ 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<Self> {
+ 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<Self> {
+ 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<Self> {
+ NormalizedPredicate { p: self }
+ }
+}
+
+impl<P> PredicateStrExt for P where P: Predicate<str> {}
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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<str> for IsEmptyPredicate {
+ fn eval(&self, variable: &str) -> bool {
+ variable.is_empty()
+ }
+
+ fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option<reflection::Case<'a>> {
+ 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<str> for StartsWithPredicate {
+ fn eval(&self, variable: &str) -> bool {
+ variable.starts_with(&self.pattern)
+ }
+
+ fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option<reflection::Case<'a>> {
+ 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<P>(pattern: P) -> StartsWithPredicate
+where
+ P: Into<String>,
+{
+ 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<str> for EndsWithPredicate {
+ fn eval(&self, variable: &str) -> bool {
+ variable.ends_with(&self.pattern)
+ }
+
+ fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option<reflection::Case<'a>> {
+ 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<P>(pattern: P) -> EndsWithPredicate
+where
+ P: Into<String>,
+{
+ 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<str> for ContainsPredicate {
+ fn eval(&self, variable: &str) -> bool {
+ variable.contains(&self.pattern)
+ }
+
+ fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option<reflection::Case<'a>> {
+ 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<str> 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<reflection::Case<'a>> {
+ 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<dyn Iterator<Item = reflection::Parameter<'a>> + '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<P>(pattern: P) -> ContainsPredicate
+where
+ P: Into<String>,
+{
+ 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<str> for DifferencePredicate {
+ fn eval(&self, edit: &str) -> bool {
+ edit == self.orig
+ }
+
+ fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option<reflection::Case<'a>> {
+ 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<dyn Iterator<Item = reflection::Parameter<'a>> + '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<S>(orig: S) -> DifferencePredicate
+where
+ S: Into<borrow::Cow<'static, str>>,
+{
+ DifferencePredicate { orig: orig.into() }
+}
+
+#[cfg(feature = "color")]
+fn colorize_diff(mut lines: Vec<String>, palette: crate::Palette) -> Vec<String> {
+ 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<String>, _palette: crate::Palette) -> Vec<String> {
+ 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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 <LICENSE-APACHE or
+// http://www.apache.org/license/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<P>
+where
+ P: Predicate<str>,
+{
+ pub(crate) p: P,
+}
+
+impl<P> reflection::PredicateReflection for NormalizedPredicate<P>
+where
+ P: Predicate<str>,
+{
+ fn children<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Child<'a>> + 'a> {
+ let params = vec![reflection::Child::new("predicate", &self.p)];
+ Box::new(params.into_iter())
+ }
+}
+
+impl<P> Predicate<str> for NormalizedPredicate<P>
+where
+ P: Predicate<str>,
+{
+ fn eval(&self, variable: &str) -> bool {
+ let variable = normalized(variable.chars()).collect::<String>();
+ self.p.eval(&variable)
+ }
+
+ fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option<reflection::Case<'a>> {
+ let variable = normalized(variable.chars()).collect::<String>();
+ self.p.find_case(expected, &variable)
+ }
+}
+
+impl<P> fmt::Display for NormalizedPredicate<P>
+where
+ P: Predicate<str>,
+{
+ 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<str> for RegexPredicate {
+ fn eval(&self, variable: &str) -> bool {
+ self.re.is_match(variable)
+ }
+
+ fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option<reflection::Case<'a>> {
+ 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<str> 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<reflection::Case<'a>> {
+ 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<dyn Iterator<Item = reflection::Parameter<'a>> + '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<S>(pattern: S) -> Result<RegexPredicate, RegexError>
+where
+ S: AsRef<str>,
+{
+ 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<T>
+where
+ T: fmt::Debug,
+{
+ pub(crate) debug: T,
+}
+
+impl<T> DebugAdapter<T>
+where
+ T: fmt::Debug,
+{
+ pub fn new(debug: T) -> Self {
+ Self { debug }
+ }
+}
+
+impl<T> fmt::Display for DebugAdapter<T>
+where
+ T: fmt::Debug,
+{
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{:#?}", self.debug)
+ }
+}
+
+impl<T> fmt::Debug for DebugAdapter<T>
+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<reflection::Case<'a>>
+where
+ P: Predicate<Item>,
+ Item: ?Sized,
+{
+ let actual = pred.eval(variable);
+ if expected == actual {
+ Some(reflection::Case::new(Some(pred), actual))
+ } else {
+ None
+ }
+}