├── .clippy.toml ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── renovate.json5 ├── settings.yml └── workflows │ ├── audit.yml │ ├── ci.yml │ ├── committed.yml │ ├── pre-commit.yml │ ├── rust-next.yml │ └── spelling.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── committed.toml ├── crates ├── core │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── LICENSE-APACHE │ ├── LICENSE-MIT │ ├── README.md │ ├── release.toml │ └── src │ │ ├── core.rs │ │ ├── lib.rs │ │ └── reflection.rs └── tree │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── LICENSE-APACHE │ ├── LICENSE-MIT │ ├── README.md │ ├── examples │ └── failures.rs │ ├── release.toml │ └── src │ └── lib.rs ├── deny.toml ├── examples └── case_tree.rs ├── release.toml ├── src ├── boolean.rs ├── boxed.rs ├── color.rs ├── constant.rs ├── float │ ├── close.rs │ └── mod.rs ├── function.rs ├── iter.rs ├── lib.rs ├── name.rs ├── ord.rs ├── path │ ├── existence.rs │ ├── fc.rs │ ├── fs.rs │ ├── ft.rs │ └── mod.rs ├── prelude.rs ├── str │ ├── adapters.rs │ ├── basics.rs │ ├── difference.rs │ ├── mod.rs │ ├── normalize.rs │ └── regex.rs └── utils.rs └── tests ├── empty_file └── hello_world /.clippy.toml: -------------------------------------------------------------------------------- 1 | allow-print-in-tests = true 2 | allow-expect-in-tests = true 3 | allow-unwrap-in-tests = true 4 | allow-dbg-in-tests = true 5 | disallowed-methods = [ 6 | { path = "std::option::Option::map_or", reason = "prefer `map(..).unwrap_or(..)` for legibility" }, 7 | { path = "std::option::Option::map_or_else", reason = "prefer `map(..).unwrap_or_else(..)` for legibility" }, 8 | { path = "std::result::Result::map_or", reason = "prefer `map(..).unwrap_or(..)` for legibility" }, 9 | { path = "std::result::Result::map_or_else", reason = "prefer `map(..).unwrap_or_else(..)` for legibility" }, 10 | { path = "std::iter::Iterator::for_each", reason = "prefer `for` for side-effects" }, 11 | { path = "std::iter::Iterator::try_for_each", reason = "prefer `for` for side-effects" }, 12 | ] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | I tried this code: 8 | 9 | 10 | 11 | I expected to see this happen: 12 | 13 | Instead this happened: 14 | 15 | ## Meta 16 | 17 | predicates-rs version: 18 | `rustc --version --verbose`: 19 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | schedule: [ 3 | 'before 5am on the first day of the month', 4 | ], 5 | semanticCommits: 'enabled', 6 | commitMessageLowerCase: 'never', 7 | configMigration: true, 8 | dependencyDashboard: true, 9 | customManagers: [ 10 | { 11 | customType: 'regex', 12 | fileMatch: [ 13 | '^rust-toolchain\\.toml$', 14 | 'Cargo.toml$', 15 | 'clippy.toml$', 16 | '\\.clippy.toml$', 17 | '^\\.github/workflows/ci.yml$', 18 | '^\\.github/workflows/rust-next.yml$', 19 | ], 20 | matchStrings: [ 21 | 'STABLE.*?(?\\d+\\.\\d+(\\.\\d+)?)', 22 | '(?\\d+\\.\\d+(\\.\\d+)?).*?STABLE', 23 | ], 24 | depNameTemplate: 'STABLE', 25 | packageNameTemplate: 'rust-lang/rust', 26 | datasourceTemplate: 'github-releases', 27 | }, 28 | ], 29 | packageRules: [ 30 | { 31 | commitMessageTopic: 'Rust Stable', 32 | matchManagers: [ 33 | 'custom.regex', 34 | ], 35 | matchDepNames: [ 36 | 'STABLE', 37 | ], 38 | extractVersion: '^(?\\d+\\.\\d+)', // Drop the patch version 39 | schedule: [ 40 | '* * * * *', 41 | ], 42 | automerge: true, 43 | }, 44 | // Goals: 45 | // - Keep version reqs low, ignoring compatible normal/build dependencies 46 | // - Take advantage of latest dev-dependencies 47 | // - Rollup safe upgrades to reduce CI runner load 48 | // - Help keep number of versions down by always using latest breaking change 49 | // - Have lockfile and manifest in-sync 50 | { 51 | matchManagers: [ 52 | 'cargo', 53 | ], 54 | matchDepTypes: [ 55 | 'build-dependencies', 56 | 'dependencies', 57 | ], 58 | matchCurrentVersion: '>=0.1.0', 59 | matchUpdateTypes: [ 60 | 'patch', 61 | ], 62 | enabled: false, 63 | }, 64 | { 65 | matchManagers: [ 66 | 'cargo', 67 | ], 68 | matchDepTypes: [ 69 | 'build-dependencies', 70 | 'dependencies', 71 | ], 72 | matchCurrentVersion: '>=1.0.0', 73 | matchUpdateTypes: [ 74 | 'minor', 75 | 'patch', 76 | ], 77 | enabled: false, 78 | }, 79 | { 80 | matchManagers: [ 81 | 'cargo', 82 | ], 83 | matchDepTypes: [ 84 | 'dev-dependencies', 85 | ], 86 | matchCurrentVersion: '>=0.1.0', 87 | matchUpdateTypes: [ 88 | 'patch', 89 | ], 90 | automerge: true, 91 | groupName: 'compatible (dev)', 92 | }, 93 | { 94 | matchManagers: [ 95 | 'cargo', 96 | ], 97 | matchDepTypes: [ 98 | 'dev-dependencies', 99 | ], 100 | matchCurrentVersion: '>=1.0.0', 101 | matchUpdateTypes: [ 102 | 'minor', 103 | 'patch', 104 | ], 105 | automerge: true, 106 | groupName: 'compatible (dev)', 107 | }, 108 | ], 109 | } 110 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | # These settings are synced to GitHub by https://probot.github.io/apps/settings/ 2 | 3 | repository: 4 | description: "Boolean-valued predicate functions in Rust" 5 | homepage: "docs.rs/predicates" 6 | topics: "rust test predicates" 7 | has_issues: true 8 | has_projects: false 9 | has_wiki: false 10 | has_downloads: true 11 | default_branch: master 12 | 13 | # Preference: people do clean commits 14 | allow_merge_commit: true 15 | # Backup in case we need to clean up commits 16 | allow_squash_merge: true 17 | # Not really needed 18 | allow_rebase_merge: false 19 | 20 | allow_auto_merge: true 21 | delete_branch_on_merge: true 22 | 23 | squash_merge_commit_title: "PR_TITLE" 24 | squash_merge_commit_message: "PR_BODY" 25 | merge_commit_message: "PR_BODY" 26 | 27 | labels: 28 | # Type 29 | - name: bug 30 | color: '#b60205' 31 | description: "Not as expected" 32 | - name: enhancement 33 | color: '#1d76db' 34 | description: "Improve the expected" 35 | # Flavor 36 | - name: question 37 | color: "#cc317c" 38 | description: "Uncertainty is involved" 39 | - name: breaking-change 40 | color: "#e99695" 41 | - name: good first issue 42 | color: '#c2e0c6' 43 | description: "Help wanted!" 44 | 45 | # This serves more as documentation. 46 | # Branch protection API was replaced by rulesets but settings isn't updated. 47 | # See https://github.com/repository-settings/app/issues/825 48 | # 49 | # branches: 50 | # - name: master 51 | # protection: 52 | # required_pull_request_reviews: null 53 | # required_conversation_resolution: true 54 | # required_status_checks: 55 | # # Required. Require branches to be up to date before merging. 56 | # strict: false 57 | # contexts: ["CI", "Spell Check with Typos"] 58 | # enforce_admins: false 59 | # restrictions: null 60 | -------------------------------------------------------------------------------- /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | pull_request: 8 | paths: 9 | - '**/Cargo.toml' 10 | - '**/Cargo.lock' 11 | push: 12 | branches: 13 | - master 14 | 15 | env: 16 | RUST_BACKTRACE: 1 17 | CARGO_TERM_COLOR: always 18 | CLICOLOR: 1 19 | 20 | concurrency: 21 | group: "${{ github.workflow }}-${{ github.ref }}" 22 | cancel-in-progress: true 23 | 24 | jobs: 25 | security_audit: 26 | permissions: 27 | issues: write # to create issues (actions-rs/audit-check) 28 | checks: write # to create check (actions-rs/audit-check) 29 | runs-on: ubuntu-latest 30 | # Prevent sudden announcement of a new advisory from failing ci: 31 | continue-on-error: true 32 | steps: 33 | - name: Checkout repository 34 | uses: actions/checkout@v4 35 | - uses: actions-rs/audit-check@v1 36 | with: 37 | token: ${{ secrets.GITHUB_TOKEN }} 38 | 39 | cargo_deny: 40 | permissions: 41 | issues: write # to create issues (actions-rs/audit-check) 42 | checks: write # to create check (actions-rs/audit-check) 43 | runs-on: ubuntu-latest 44 | strategy: 45 | matrix: 46 | checks: 47 | - bans licenses sources 48 | steps: 49 | - uses: actions/checkout@v4 50 | - uses: EmbarkStudios/cargo-deny-action@v2 51 | with: 52 | command: check ${{ matrix.checks }} 53 | rust-version: stable 54 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | pull_request: 8 | push: 9 | branches: 10 | - master 11 | 12 | env: 13 | RUST_BACKTRACE: 1 14 | CARGO_TERM_COLOR: always 15 | CLICOLOR: 1 16 | 17 | concurrency: 18 | group: "${{ github.workflow }}-${{ github.ref }}" 19 | cancel-in-progress: true 20 | 21 | jobs: 22 | ci: 23 | permissions: 24 | contents: none 25 | name: CI 26 | needs: [test, msrv, lockfile, docs, rustfmt, clippy, minimal-versions] 27 | runs-on: ubuntu-latest 28 | if: "always()" 29 | steps: 30 | - name: Failed 31 | run: exit 1 32 | if: "contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped')" 33 | test: 34 | name: Test 35 | strategy: 36 | matrix: 37 | os: ["ubuntu-latest", "windows-latest", "macos-latest"] 38 | rust: ["stable"] 39 | continue-on-error: ${{ matrix.rust != 'stable' }} 40 | runs-on: ${{ matrix.os }} 41 | steps: 42 | - name: Checkout repository 43 | uses: actions/checkout@v4 44 | - name: Install Rust 45 | uses: dtolnay/rust-toolchain@stable 46 | with: 47 | toolchain: ${{ matrix.rust }} 48 | - uses: Swatinem/rust-cache@v2 49 | - uses: taiki-e/install-action@cargo-hack 50 | - name: Build 51 | run: cargo test --workspace --no-run 52 | - name: Test 53 | run: cargo hack test --feature-powerset --workspace 54 | msrv: 55 | name: "Check MSRV" 56 | runs-on: ubuntu-latest 57 | steps: 58 | - name: Checkout repository 59 | uses: actions/checkout@v4 60 | - name: Install Rust 61 | uses: dtolnay/rust-toolchain@stable 62 | with: 63 | toolchain: stable 64 | - uses: Swatinem/rust-cache@v2 65 | - uses: taiki-e/install-action@cargo-hack 66 | - name: Default features 67 | run: cargo hack check --feature-powerset --locked --rust-version --ignore-private --workspace --all-targets 68 | minimal-versions: 69 | name: Minimal versions 70 | runs-on: ubuntu-latest 71 | steps: 72 | - name: Checkout repository 73 | uses: actions/checkout@v4 74 | - name: Install stable Rust 75 | uses: dtolnay/rust-toolchain@stable 76 | with: 77 | toolchain: stable 78 | - name: Install nightly Rust 79 | uses: dtolnay/rust-toolchain@stable 80 | with: 81 | toolchain: nightly 82 | - name: Downgrade dependencies to minimal versions 83 | run: cargo +nightly generate-lockfile -Z minimal-versions 84 | - name: Compile with minimal versions 85 | run: cargo +stable check --workspace --all-features --locked 86 | lockfile: 87 | runs-on: ubuntu-latest 88 | steps: 89 | - name: Checkout repository 90 | uses: actions/checkout@v4 91 | - name: Install Rust 92 | uses: dtolnay/rust-toolchain@stable 93 | with: 94 | toolchain: stable 95 | - uses: Swatinem/rust-cache@v2 96 | - name: "Is lockfile updated?" 97 | run: cargo update --workspace --locked 98 | docs: 99 | name: Docs 100 | runs-on: ubuntu-latest 101 | steps: 102 | - name: Checkout repository 103 | uses: actions/checkout@v4 104 | - name: Install Rust 105 | uses: dtolnay/rust-toolchain@stable 106 | with: 107 | toolchain: "1.87" # STABLE 108 | - uses: Swatinem/rust-cache@v2 109 | - name: Check documentation 110 | env: 111 | RUSTDOCFLAGS: -D warnings 112 | run: cargo doc --workspace --all-features --no-deps --document-private-items 113 | rustfmt: 114 | name: rustfmt 115 | runs-on: ubuntu-latest 116 | steps: 117 | - name: Checkout repository 118 | uses: actions/checkout@v4 119 | - name: Install Rust 120 | uses: dtolnay/rust-toolchain@stable 121 | with: 122 | toolchain: "1.87" # STABLE 123 | components: rustfmt 124 | - uses: Swatinem/rust-cache@v2 125 | - name: Check formatting 126 | run: cargo fmt --all -- --check 127 | clippy: 128 | name: clippy 129 | runs-on: ubuntu-latest 130 | permissions: 131 | security-events: write # to upload sarif results 132 | steps: 133 | - name: Checkout repository 134 | uses: actions/checkout@v4 135 | - name: Install Rust 136 | uses: dtolnay/rust-toolchain@stable 137 | with: 138 | toolchain: "1.87" # STABLE 139 | components: clippy 140 | - uses: Swatinem/rust-cache@v2 141 | - name: Install SARIF tools 142 | run: cargo install clippy-sarif --locked 143 | - name: Install SARIF tools 144 | run: cargo install sarif-fmt --locked 145 | - name: Check 146 | run: > 147 | cargo clippy --workspace --all-features --all-targets --message-format=json 148 | | clippy-sarif 149 | | tee clippy-results.sarif 150 | | sarif-fmt 151 | continue-on-error: true 152 | - name: Upload 153 | uses: github/codeql-action/upload-sarif@v3 154 | with: 155 | sarif_file: clippy-results.sarif 156 | wait-for-processing: true 157 | - name: Report status 158 | run: cargo clippy --workspace --all-features --all-targets -- -D warnings --allow deprecated 159 | coverage: 160 | name: Coverage 161 | runs-on: ubuntu-latest 162 | steps: 163 | - name: Checkout repository 164 | uses: actions/checkout@v4 165 | - name: Install Rust 166 | uses: dtolnay/rust-toolchain@stable 167 | with: 168 | toolchain: stable 169 | - uses: Swatinem/rust-cache@v2 170 | - name: Install cargo-tarpaulin 171 | run: cargo install cargo-tarpaulin 172 | - name: Gather coverage 173 | run: cargo tarpaulin --output-dir coverage --out lcov 174 | - name: Publish to Coveralls 175 | uses: coverallsapp/github-action@master 176 | with: 177 | github-token: ${{ secrets.GITHUB_TOKEN }} 178 | -------------------------------------------------------------------------------- /.github/workflows/committed.yml: -------------------------------------------------------------------------------- 1 | # Not run as part of pre-commit checks because they don't handle sending the correct commit 2 | # range to `committed` 3 | name: Lint Commits 4 | on: [pull_request] 5 | 6 | permissions: 7 | contents: read 8 | 9 | env: 10 | RUST_BACKTRACE: 1 11 | CARGO_TERM_COLOR: always 12 | CLICOLOR: 1 13 | 14 | concurrency: 15 | group: "${{ github.workflow }}-${{ github.ref }}" 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | committed: 20 | name: Lint Commits 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout Actions Repository 24 | uses: actions/checkout@v4 25 | with: 26 | fetch-depth: 0 27 | - name: Lint Commits 28 | uses: crate-ci/committed@master 29 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: pre-commit 2 | 3 | permissions: {} # none 4 | 5 | on: 6 | pull_request: 7 | push: 8 | branches: [master] 9 | 10 | env: 11 | RUST_BACKTRACE: 1 12 | CARGO_TERM_COLOR: always 13 | CLICOLOR: 1 14 | 15 | concurrency: 16 | group: "${{ github.workflow }}-${{ github.ref }}" 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | pre-commit: 21 | permissions: 22 | contents: read 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: actions/setup-python@v5 27 | with: 28 | python-version: '3.x' 29 | - uses: pre-commit/action@v3.0.1 30 | -------------------------------------------------------------------------------- /.github/workflows/rust-next.yml: -------------------------------------------------------------------------------- 1 | name: rust-next 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | schedule: 8 | - cron: '16 16 16 * *' 9 | 10 | env: 11 | RUST_BACKTRACE: 1 12 | CARGO_TERM_COLOR: always 13 | CLICOLOR: 1 14 | 15 | concurrency: 16 | group: "${{ github.workflow }}-${{ github.ref }}" 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | test: 21 | name: Test 22 | strategy: 23 | matrix: 24 | os: ["ubuntu-latest", "windows-latest", "macos-latest"] 25 | rust: ["stable", "beta"] 26 | include: 27 | - os: ubuntu-latest 28 | rust: "nightly" 29 | continue-on-error: ${{ matrix.rust != 'stable' }} 30 | runs-on: ${{ matrix.os }} 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v4 34 | - name: Install Rust 35 | uses: dtolnay/rust-toolchain@stable 36 | with: 37 | toolchain: ${{ matrix.rust }} 38 | - uses: Swatinem/rust-cache@v2 39 | - uses: taiki-e/install-action@cargo-hack 40 | - name: Build 41 | run: cargo test --workspace --no-run 42 | - name: Test 43 | run: cargo hack test --feature-powerset --workspace 44 | latest: 45 | name: "Check latest dependencies" 46 | runs-on: ubuntu-latest 47 | steps: 48 | - name: Checkout repository 49 | uses: actions/checkout@v4 50 | - name: Install Rust 51 | uses: dtolnay/rust-toolchain@stable 52 | with: 53 | toolchain: stable 54 | - uses: Swatinem/rust-cache@v2 55 | - uses: taiki-e/install-action@cargo-hack 56 | - name: Update dependencies 57 | run: cargo update 58 | - name: Build 59 | run: cargo test --workspace --no-run 60 | - name: Test 61 | run: cargo hack test --feature-powerset --workspace 62 | -------------------------------------------------------------------------------- /.github/workflows/spelling.yml: -------------------------------------------------------------------------------- 1 | name: Spelling 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: [pull_request] 7 | 8 | env: 9 | RUST_BACKTRACE: 1 10 | CARGO_TERM_COLOR: always 11 | CLICOLOR: 1 12 | 13 | concurrency: 14 | group: "${{ github.workflow }}-${{ github.ref }}" 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | spelling: 19 | name: Spell Check with Typos 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout Actions Repository 23 | uses: actions/checkout@v4 24 | - name: Spell Check Repo 25 | uses: crate-ci/typos@master 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.5.0 4 | hooks: 5 | - id: check-yaml 6 | stages: [commit] 7 | - id: check-json 8 | stages: [commit] 9 | - id: check-toml 10 | stages: [commit] 11 | - id: check-merge-conflict 12 | stages: [commit] 13 | - id: check-case-conflict 14 | stages: [commit] 15 | - id: detect-private-key 16 | stages: [commit] 17 | - repo: https://github.com/crate-ci/typos 18 | rev: v1.16.20 19 | hooks: 20 | - id: typos 21 | stages: [commit] 22 | - repo: https://github.com/crate-ci/committed 23 | rev: v1.0.20 24 | hooks: 25 | - id: committed 26 | stages: [commit-msg] 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | 8 | ## [Unreleased] - ReleaseDate 9 | 10 | ## [3.1.3] - 2024-12-19 11 | 12 | ### Features 13 | 14 | - The `boxed` function is now available for predicates with an `Item` type that 15 | is not `Sized`. 16 | 17 | ## [3.1.2] - 2024-07-25 18 | 19 | ## [3.1.1] - 2024-07-25 20 | 21 | ### Compatibility 22 | 23 | - Update MSRV to 1.74 24 | 25 | ## [3.1.0] - 2024-01-15 26 | 27 | ### Compatibility 28 | 29 | - Update MSRV to 1.70.0 30 | 31 | ### Internals 32 | 33 | - Remove `itertools` 34 | 35 | ## [3.0.4] - 2023-09-18 36 | 37 | ### Compatibility 38 | 39 | - Update MSRV to 1.69.0 40 | 41 | ### Internal 42 | 43 | - Update `itertools` to 0.11 44 | 45 | ## [3.0.3] - 2023-04-13 46 | 47 | ### Internal 48 | 49 | - Update anstyle to 1.0 50 | 51 | ## [3.0.2] - 2023-03-24 52 | 53 | ### Fixes 54 | 55 | - Respect `follow_links` when checking whether a path is backed by a file 56 | 57 | ## [3.0.1] - 2023-03-14 58 | 59 | ### Fixes 60 | 61 | - Show colors for diff header 62 | 63 | ## [3.0.0] - 2023-03-14 64 | 65 | ### Compatibility 66 | 67 | - `color` feature no longer conditionally applies color. Instead color will be applied when formatting as `{:#}` 68 | - `color-auto` feature was removed 69 | - Update MSRV to 1.64.0 70 | 71 | ## [2.1.5] - 2022-12-29 72 | 73 | ### Gixes 74 | 75 | - Further generalized borrowing of predicates with `Borrow` trait 76 | 77 | ## [2.1.4] - 2022-12-02 78 | 79 | ## [2.1.3] - 2022-11-13 80 | 81 | ## [2.1.2] - 2022-11-05 82 | 83 | ### Compatibility 84 | 85 | Update MSRV to 1.60 86 | 87 | ### Fixes 88 | 89 | - Hide internal-only optional dependencies 90 | 91 | ## [2.1.1] - 2022-01-11 92 | 93 | ## [2.1.0] - 2021-11-22 94 | 95 | ### Fixed 96 | 97 | - Report user's value (the actual) 98 | - Having the caller own this means you won't get intermediates like file paths 99 | - We already show it for the `diff` predicate 100 | - Now we show it in each leaf predicate (like Eq) and when adapting it (like taking a path and loading its file) 101 | - Remove redundant result with `eq_file` assertions 102 | - Clarify that the constant in Eq and Ord predicates is the expected value 103 | 104 | ## [2.0.3] - 2021-10-07 105 | 106 | ## [2.0.2] - 2021-08-16 107 | 108 | ### Added 109 | 110 | - All predicates now implement `Send` and `Sync` when it's appropriate 111 | 112 | ## [2.0.1] - 2021-07-26 113 | 114 | ### Changed 115 | 116 | - Upgraded `float-cmp` 117 | 118 | ## [2.0.0] - 2021-07-03 119 | 120 | #### Breaking Changes 121 | 122 | - `predicates::str::diff` was removed 123 | - `predicates::str::similar` was renamed to `diff` 124 | - The `difference` feature flag was renamed to `diff` 125 | - `diff().split` and `diff().distance` were removed 126 | 127 | #### Fixes 128 | 129 | - Shrink the output of Diffs because its redundant 130 | - Moved off of an unmaintained Diff library 131 | 132 | ## [1.0.8] - 2021-04-28 133 | 134 | ## [1.0.7] - 2021-01-29 135 | 136 | ## [1.0.6] - 2020-12-28 137 | 138 | ### Fixed 139 | - `NamePredicate` now adds itself to the `Case` returned by `find_case`. 140 | 141 | ## [1.0.5] - 2020-07-18 142 | 143 | ### Fixed 144 | 145 | - Update float-cmp dependency 146 | 147 | ## [1.0.4] - 2020-03-04 148 | ### Fixed 149 | 150 | - Upgrade normalize-line-endings 151 | 152 | ## [1.0.3] - 2020-02-25 153 | 154 | ## [1.0.2] - 2019-11-18 155 | ### Fixed 156 | - `BooleanPredicate` now implements `Predicate` where `T: ?Sized` 157 | ([#84](https://github.com/assert-rs/predicates-rs/pull/84)) 158 | 159 | ## [1.0.1] - 2019-04-22 160 | ### Changed 161 | - BooleanPredicate is no longer generic, and is always Send and Sync. 162 | 163 | ## [1.0.0] - 2018-10-06 164 | 165 | ## [0.9.1] - 2018-10-05 166 | ### Added 167 | - Created a predicate selection guide. 168 | 169 | ## [0.9.0] - 2018-07-30 170 | ### Added 171 | - Support `?Sized` types for `FnPredicate`. 172 | - Add `str_pred.normalize()` predicate. 173 | - Add reflection to `Predicate`. 174 | - Add support for predicates returning why they failed (`find_case`) which can 175 | be combined with the new `predicates-tree` crate. 176 | - Split out `predicates-core` for reducing ecosystem breaking changes. 177 | 178 | ### Changed 179 | - Predicates must also implement `PredicateReflection` 180 | 181 | ## [0.5.2] - 2018-07-20 182 | ### Added 183 | - **path:** support file-based str predicates ([4b430532](https://github.com/assert-rs/predicates-rs/commit/4b430532f7cd660bd813863871ede6f108e7be67), closes [#56](https://github.com/assert-rs/predicates-rs/issues/56)) 184 | - Expand trait coverage ([33972a7d](https://github.com/assert-rs/predicates-rs/commit/33972a7d0c92eb7f7c7e95af4bb35bea0ac810ab)) 185 | 186 | ## [0.5.1] - 2018-06-05 187 | ### Added 188 | - Fix eq for str ([7650e9e6](https://github.com/assert-rs/predicates-rs/commit/7650e9e6d43f2ddd047ad8defa0c724b31ebd1c4)) 189 | 190 | ## [0.5.0] - 2018-05-30 191 | ### Added 192 | - **trait:** 193 | - Allow naming `Predicate` expressions 194 | - **str:** 195 | - Add regex repetition count, closes #27 196 | - from_utf8 adapter, closes #21 197 | - Trimming predicate decorator 198 | - **path:** 199 | - `eq_file` predicate to test a file-under-test with a fixture, closes #32. 200 | - `eq_file(...).utf()` adapter to do string comparisons with the fixture 201 | - Add a `from_file_path` extension method to `Predicate<[u8]>` that turns it into a `Predicate`, closes #31. 202 | 203 | ### Breaking Changes 204 | - **trait:** 205 | - All `Predicate`s are now `Display` ([05216708](https://github.com/assert-rs/predicates-rs/commit/05216708359544f2c5f3a256f50c012f521c39a6), breaks [#](https://github.com/assert-rs/predicates-rs/issues/)) 206 | - Decouple boxing from trait ([f981fac3](https://github.com/assert-rs/predicates-rs/commit/f981fac39271746162365f3c577cffac730e1d97), breaks [#](https://github.com/assert-rs/predicates-rs/issues/)) 207 | - Decouple boolean logic from trait ([88b72f9e](https://github.com/assert-rs/predicates-rs/commit/88b72f9ef58a86f2af68c0510d99326f5e644f76), breaks [#](https://github.com/assert-rs/predicates-rs/issues/)) 208 | 209 | ## [0.4.0] - 2018-05-10 210 | ### Added 211 | - Define oldest supported version of Rust as 1.22. 212 | - CI that ensures 213 | - works on Windows and Linux 214 | - works on 1.22 to nightly 215 | - **float:** `is_close` Predicate (see #11). 216 | - **path:** 217 | - File type predicates: `is_file`, `is_dir`, `is_symlink` (see #8). 218 | - Existence predicate: `exists`, `missing` (see #8). 219 | - **str:** 220 | - Basic string predicates: `is_empty`, `starts_with`, `ends_with`, and `contains` with optional count (see #25). 221 | - Regex predicate (see #12). 222 | - Edit-distance predicate (see #9). 223 | 224 | ### Changed 225 | - Clearly delineate API from prelude (see #17). 226 | - Switch `Predicate` trait from Associated Types to Generics. 227 | - **iter:** 228 | - Renamed `set` predicates as `iter` predicates to clarify the intent from some implementation. 229 | - Remove ambiguity of predicate factories (see #24): 230 | - `contains` -> `in_iter` 231 | - `contains_hashable` -> `in_hash` 232 | - Turned `contains_ord` into a specialization of `in_iter` by adding a `sort` method. 233 | 234 | ## [0.3.0] - 2017-06-26 235 | ### Added 236 | - `BoxPredicate` type that wraps a `Predicate` trait object to make it easier 237 | to store and work with predicates through a program. Also implements `Debug` 238 | and `Display` wrappers as a convenience. 239 | - `FnPredicate` type that wraps a function of the type `Fn(&T) -> bool` in a 240 | `Predicate` type. 241 | 242 | ### Changed 243 | - The `boxed` function now returns a type `BoxPredicate` instead of a type 244 | alias. 245 | - The `Item` type parameter of `Predicate` no longer has the `Sized` 246 | restriction. 247 | 248 | ## [0.2.0] - 2017-06-02 249 | ### Added 250 | - This changelog 251 | 252 | ### Fixed 253 | - Made modules under `predicate` private, with their public interfaces exposed 254 | through `pub use` in the `predicate` `mod.rs` file. 255 | 256 | ## 0.1.0 - 2017-06-02 257 | ### Added 258 | - Initial commit of functional code 259 | - Continuous integration with Travis (Linux) and AppVeyor (Windows) 260 | - Basic README 261 | 262 | 263 | [Unreleased]: https://github.com/assert-rs/predicates-rs/compare/v3.1.3...HEAD 264 | [3.1.3]: https://github.com/assert-rs/predicates-rs/compare/v3.1.2...v3.1.3 265 | [3.1.2]: https://github.com/assert-rs/predicates-rs/compare/v3.1.1...v3.1.2 266 | [3.1.1]: https://github.com/assert-rs/predicates-rs/compare/v3.1.0...v3.1.1 267 | [3.1.0]: https://github.com/assert-rs/predicates-rs/compare/v3.0.4...v3.1.0 268 | [3.0.4]: https://github.com/assert-rs/predicates-rs/compare/v3.0.3...v3.0.4 269 | [3.0.3]: https://github.com/assert-rs/predicates-rs/compare/v3.0.2...v3.0.3 270 | [3.0.2]: https://github.com/assert-rs/predicates-rs/compare/v3.0.1...v3.0.2 271 | [3.0.1]: https://github.com/assert-rs/predicates-rs/compare/v3.0.0...v3.0.1 272 | [3.0.0]: https://github.com/assert-rs/predicates-rs/compare/v2.1.5...v3.0.0 273 | [2.1.5]: https://github.com/assert-rs/predicates-rs/compare/v2.1.4...v2.1.5 274 | [2.1.4]: https://github.com/assert-rs/predicates-rs/compare/v2.1.3...v2.1.4 275 | [2.1.3]: https://github.com/assert-rs/predicates-rs/compare/v2.1.2...v2.1.3 276 | [2.1.2]: https://github.com/assert-rs/predicates-rs/compare/v2.1.1...v2.1.2 277 | [2.1.1]: https://github.com/assert-rs/predicates-rs/compare/v2.1.0...v2.1.1 278 | [2.1.0]: https://github.com/assert-rs/predicates-rs/compare/v2.0.3...v2.1.0 279 | [2.0.3]: https://github.com/assert-rs/predicates-rs/compare/v2.0.2...v2.0.3 280 | [2.0.2]: https://github.com/assert-rs/predicates-rs/compare/v2.0.1...v2.0.2 281 | [2.0.1]: https://github.com/assert-rs/predicates-rs/compare/v2.0.0...v2.0.1 282 | [2.0.0]: https://github.com/assert-rs/predicates-rs/compare/v1.0.8...v2.0.0 283 | [1.0.8]: https://github.com/assert-rs/predicates-rs/compare/v1.0.7...v1.0.8 284 | [1.0.7]: https://github.com/assert-rs/predicates-rs/compare/v1.0.6...v1.0.7 285 | [1.0.6]: https://github.com/assert-rs/predicates-rs/compare/v1.0.5...v1.0.6 286 | [1.0.5]: https://github.com/assert-rs/predicates-rs/compare/v1.0.4...v1.0.5 287 | [1.0.4]: https://github.com/assert-rs/predicates-rs/compare/v1.0.3...v1.0.4 288 | [1.0.3]: https://github.com/assert-rs/predicates-rs/compare/v1.0.2...v1.0.3 289 | [1.0.2]: https://github.com/assert-rs/predicates-rs/compare/v1.0.1...v1.0.2 290 | [1.0.1]: https://github.com/assert-rs/predicates-rs/compare/v1.0.0...v1.0.1 291 | [1.0.0]: https://github.com/assert-rs/predicates-rs/compare/v0.9.1...v1.0.0 292 | [0.9.1]: https://github.com/assert-rs/predicates-rs/compare/v0.9.0...v0.9.1 293 | [0.9.0]: https://github.com/assert-rs/predicates-rs/compare/v0.5.2...v0.9.0 294 | [0.5.2]: https://github.com/assert-rs/predicates-rs/compare/v0.5.1...v0.5.2 295 | [0.5.1]: https://github.com/assert-rs/predicates-rs/compare/v0.5.0...v0.5.1 296 | [0.5.0]: https://github.com/assert-rs/predicates-rs/compare/v0.4.0...v0.5.0 297 | [0.4.0]: https://github.com/assert-rs/predicates-rs/compare/v0.3.0...v0.4.0 298 | [0.3.0]: https://github.com/assert-rs/predicates-rs/compare/v0.2.0...v0.3.0 299 | [0.2.0]: https://github.com/assert-rs/predicates-rs/compare/v0.1.0...v0.2.0 300 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to predicates-rs 2 | 3 | Thanks for wanting to contribute! There are many ways to contribute and we 4 | appreciate any level you're willing to do. 5 | 6 | ## Feature Requests 7 | 8 | Need some new functionality to help? You can let us know by opening an 9 | [issue][new issue]. It's helpful to look through [all issues][all issues] in 10 | case its already being talked about. 11 | 12 | ## Bug Reports 13 | 14 | Please let us know about what problems you run into, whether in behavior or 15 | ergonomics of API. You can do this by opening an [issue][new issue]. It's 16 | helpful to look through [all issues][all issues] in case its already being 17 | talked about. 18 | 19 | ## Pull Requests 20 | 21 | Looking for an idea? Check our [issues][issues]. If it's look more open ended, 22 | it is probably best to post on the issue how you are thinking of resolving the 23 | issue so you can get feedback early in the process. We want you to be 24 | successful and it can be discouraging to find out a lot of re-work is needed. 25 | 26 | Already have an idea? It might be good to first [create an issue][new issue] 27 | to propose it so we can make sure we are aligned and lower the risk of having 28 | to re-work some of it and the discouragement that goes along with that. 29 | 30 | ### Process 31 | 32 | As a heads up, we'll be running your PR through the following gauntlet: 33 | - warnings turned to compile errors 34 | - `cargo test` 35 | - `rustfmt` 36 | - `clippy` 37 | - `rustdoc` 38 | - [`committed`](https://github.com/crate-ci/committed) as we use [Conventional](https://www.conventionalcommits.org) commit style 39 | - [`typos`](https://github.com/crate-ci/typos) to check spelling 40 | 41 | Not everything can be checked automatically though. 42 | 43 | We request that the commit history gets cleaned up. 44 | We ask that commits are atomic, meaning they are complete and have a single responsibility. 45 | PRs should tell a cohesive story, with test and refactor commits that keep the 46 | fix or feature commits simple and clear. 47 | 48 | Specifically, we would encourage 49 | - File renames be isolated into their own commit 50 | - Add tests in a commit before their feature or fix, showing the current behavior. 51 | The diff for the feature/fix commit will then show how the behavior changed, 52 | making it clearer to reviewers and the community and showing people that the 53 | test is verifying the expected state. 54 | - e.g. [clap#5520](https://github.com/clap-rs/clap/pull/5520) 55 | 56 | Note that we are talking about ideals. 57 | We understand having a clean history requires more advanced git skills; 58 | feel free to ask us for help! 59 | We might even suggest where it would work to be lax. 60 | We also understand that editing some early commits may cause a lot of churn 61 | with merge conflicts which can make it not worth editing all of the history. 62 | 63 | For code organization, we recommend 64 | - Grouping `impl` blocks next to their type (or trait) 65 | - Grouping private items after the `pub` item that uses them. 66 | - The intent is to help people quickly find the "relevant" details, allowing them to "dig deeper" as needed. Or put another way, the `pub` items serve as a table-of-contents. 67 | - The exact order is fuzzy; do what makes sense 68 | 69 | ## Releasing 70 | 71 | Pre-requisites 72 | - Running `cargo login` 73 | - A member of `assert-rs:Maintainers` 74 | - Push permission to the repo 75 | - [`cargo-release`](https://github.com/crate-ci/cargo-release/) 76 | 77 | When we're ready to release, a project owner should do the following 78 | 1. Update the changelog (see `cargo release changes` for ideas) 79 | 2. Determine what the next version is, according to semver 80 | 3. Run [`cargo release -x `](https://github.com/crate-ci/cargo-release) 81 | 82 | [issues]: https://github.com/assert-rs/predicates-rs/issues 83 | [new issue]: https://github.com/assert-rs/predicates-rs/issues/new 84 | [all issues]: https://github.com/assert-rs/predicates-rs/issues?utf8=%E2%9C%93&q=is%3Aissue 85 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.0.4" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anstyle" 16 | version = "1.0.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" 19 | 20 | [[package]] 21 | name = "autocfg" 22 | version = "1.1.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 25 | 26 | [[package]] 27 | name = "difflib" 28 | version = "0.4.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 31 | 32 | [[package]] 33 | name = "float-cmp" 34 | version = "0.10.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" 37 | dependencies = [ 38 | "num-traits", 39 | ] 40 | 41 | [[package]] 42 | name = "memchr" 43 | version = "2.5.0" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 46 | 47 | [[package]] 48 | name = "normalize-line-endings" 49 | version = "0.3.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" 52 | 53 | [[package]] 54 | name = "num-traits" 55 | version = "0.2.16" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" 58 | dependencies = [ 59 | "autocfg", 60 | ] 61 | 62 | [[package]] 63 | name = "predicates" 64 | version = "3.1.3" 65 | dependencies = [ 66 | "anstyle", 67 | "difflib", 68 | "float-cmp", 69 | "normalize-line-endings", 70 | "predicates-core", 71 | "predicates-tree", 72 | "regex", 73 | ] 74 | 75 | [[package]] 76 | name = "predicates-core" 77 | version = "1.0.9" 78 | 79 | [[package]] 80 | name = "predicates-tree" 81 | version = "1.0.12" 82 | dependencies = [ 83 | "predicates", 84 | "predicates-core", 85 | "termtree", 86 | ] 87 | 88 | [[package]] 89 | name = "regex" 90 | version = "1.9.3" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" 93 | dependencies = [ 94 | "aho-corasick", 95 | "memchr", 96 | "regex-automata", 97 | "regex-syntax", 98 | ] 99 | 100 | [[package]] 101 | name = "regex-automata" 102 | version = "0.3.6" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" 105 | dependencies = [ 106 | "aho-corasick", 107 | "memchr", 108 | "regex-syntax", 109 | ] 110 | 111 | [[package]] 112 | name = "regex-syntax" 113 | version = "0.7.4" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" 116 | 117 | [[package]] 118 | name = "termtree" 119 | version = "0.5.1" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" 122 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["crates/*"] 3 | resolver = "2" 4 | 5 | [workspace.package] 6 | repository = "https://github.com/assert-rs/predicates-rs" 7 | license = "MIT OR Apache-2.0" 8 | edition = "2021" 9 | rust-version = "1.74" # MSRV 10 | include = [ 11 | "build.rs", 12 | "src/**/*", 13 | "Cargo.toml", 14 | "LICENSE*", 15 | "README.md", 16 | "benches/**/*", 17 | "examples/**/*" 18 | ] 19 | 20 | [workspace.lints.rust] 21 | rust_2018_idioms = { level = "warn", priority = -1 } 22 | unreachable_pub = "warn" 23 | unsafe_op_in_unsafe_fn = "warn" 24 | unused_lifetimes = "warn" 25 | unused_macro_rules = "warn" 26 | unused_qualifications = "warn" 27 | 28 | [workspace.lints.clippy] 29 | bool_assert_comparison = "allow" 30 | branches_sharing_code = "allow" 31 | checked_conversions = "warn" 32 | collapsible_else_if = "allow" 33 | create_dir = "warn" 34 | dbg_macro = "warn" 35 | debug_assert_with_mut_call = "warn" 36 | doc_markdown = "warn" 37 | empty_enum = "warn" 38 | enum_glob_use = "warn" 39 | expl_impl_clone_on_copy = "warn" 40 | explicit_deref_methods = "warn" 41 | explicit_into_iter_loop = "warn" 42 | fallible_impl_from = "warn" 43 | filter_map_next = "warn" 44 | flat_map_option = "warn" 45 | float_cmp_const = "warn" 46 | fn_params_excessive_bools = "warn" 47 | from_iter_instead_of_collect = "warn" 48 | if_same_then_else = "allow" 49 | implicit_clone = "warn" 50 | imprecise_flops = "warn" 51 | inconsistent_struct_constructor = "warn" 52 | inefficient_to_string = "warn" 53 | infinite_loop = "warn" 54 | invalid_upcast_comparisons = "warn" 55 | large_digit_groups = "warn" 56 | large_stack_arrays = "warn" 57 | large_types_passed_by_value = "warn" 58 | let_and_return = "allow" # sometimes good to name what you are returning 59 | linkedlist = "warn" 60 | lossy_float_literal = "warn" 61 | macro_use_imports = "warn" 62 | mem_forget = "warn" 63 | mutex_integer = "warn" 64 | needless_continue = "warn" 65 | needless_for_each = "warn" 66 | negative_feature_names = "warn" 67 | path_buf_push_overwrite = "warn" 68 | ptr_as_ptr = "warn" 69 | rc_mutex = "warn" 70 | redundant_feature_names = "warn" 71 | ref_option_ref = "warn" 72 | rest_pat_in_fully_bound_structs = "warn" 73 | result_large_err = "allow" 74 | same_functions_in_if_condition = "warn" 75 | self_named_module_files = "warn" 76 | semicolon_if_nothing_returned = "warn" 77 | str_to_string = "warn" 78 | string_add = "warn" 79 | string_add_assign = "warn" 80 | string_lit_as_bytes = "warn" 81 | string_to_string = "warn" 82 | todo = "warn" 83 | trait_duplication_in_bounds = "warn" 84 | uninlined_format_args = "warn" 85 | verbose_file_reads = "warn" 86 | wildcard_imports = "warn" 87 | zero_sized_map_values = "warn" 88 | 89 | [package] 90 | name = "predicates" 91 | version = "3.1.3" 92 | description = "An implementation of boolean-valued predicate functions." 93 | authors = ["Nick Stevens "] 94 | homepage = "https://github.com/assert-rs/predicates-rs" 95 | documentation = "https://docs.rs/predicates" 96 | readme = "README.md" 97 | categories = ["data-structures", "rust-patterns"] 98 | keywords = ["predicate", "boolean", "combinatorial", "match", "logic"] 99 | repository.workspace = true 100 | license.workspace = true 101 | edition.workspace = true 102 | rust-version.workspace = true 103 | include.workspace = true 104 | 105 | [package.metadata.docs.rs] 106 | all-features = true 107 | rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"] 108 | 109 | [package.metadata.release] 110 | pre-release-replacements = [ 111 | {file="src/lib.rs", search="predicates = \".*\"", replace="predicates = \"{{version}}\"", exactly=1}, 112 | {file="README.md", search="predicates = \".*\"", replace="predicates = \"{{version}}\"", exactly=1}, 113 | {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1}, 114 | {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1}, 115 | {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1}, 116 | {file="CHANGELOG.md", search="", replace="\n## [Unreleased] - ReleaseDate\n", exactly=1}, 117 | {file="CHANGELOG.md", search="", replace="\n[Unreleased]: https://github.com/assert-rs/predicates-rs/compare/{{tag_name}}...HEAD", exactly=1}, 118 | ] 119 | 120 | [features] 121 | default = ["diff", "regex", "float-cmp", "normalize-line-endings", "color"] 122 | diff = ["dep:difflib"] 123 | unstable = [] 124 | color = [] 125 | 126 | [dependencies] 127 | predicates-core = { version = "1.0", path = "crates/core" } 128 | difflib = { version = "0.4", optional = true } 129 | normalize-line-endings = { version = "0.3.0", optional = true } 130 | regex = { version="1.0", optional = true } 131 | float-cmp = { version="0.10", optional = true } 132 | anstyle = "1.0.0" 133 | 134 | [dev-dependencies] 135 | predicates-tree = { version = "1.0", path = "crates/tree" } 136 | 137 | [lints] 138 | workspace = true 139 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) Individual contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # predicates-rs 2 | 3 | > An implementation of **boolean-valued predicate functions** in Rust. 4 | 5 | [![Documentation](https://img.shields.io/badge/docs-master-blue.svg)](https://docs.rs/predicates) 6 | ![License](https://img.shields.io/crates/l/predicates.svg) 7 | [![Crates.io](https://img.shields.io/crates/v/predicates.svg?maxAge=2592000)](https://crates.io/crates/predicates) 8 | 9 | [Changelog](https://github.com/assert-rs/predicates-rs/blob/master/CHANGELOG.md) 10 | 11 | 12 | ## Usage 13 | 14 | First, add this to your `Cargo.toml`: 15 | 16 | ```toml 17 | [dependencies] 18 | predicates = "3.1.3" 19 | ``` 20 | 21 | Next, add this to your crate: 22 | 23 | ```rust 24 | extern crate predicates; 25 | 26 | use predicates::prelude::*; 27 | ``` 28 | 29 | For more information on using predicates, look at the 30 | [documentation](https://docs.rs/predicates) 31 | 32 | ## License 33 | 34 | `predicates-rs` is distributed under the terms of both the MIT license and the 35 | Apache License (Version 2.0). 36 | 37 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) 38 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or ) 39 | 40 | ## Credits 41 | 42 | Big thanks to [futures-rs](https://github.com/alexcrichton/futures-rs), whose 43 | slick API design informed a lot of decisions made on the API design of this 44 | library. 45 | -------------------------------------------------------------------------------- /committed.toml: -------------------------------------------------------------------------------- 1 | style="conventional" 2 | ignore_author_re="(dependabot|renovate)" 3 | merge_commit = false 4 | -------------------------------------------------------------------------------- /crates/core/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | 8 | ## [Unreleased] - ReleaseDate 9 | 10 | ## [1.0.9] - 2024-12-19 11 | 12 | ## [1.0.8] - 2024-07-25 13 | 14 | ## [1.0.7] - 2024-07-25 15 | 16 | ### Compatibility 17 | 18 | - Update MSRV to 1.70.0 19 | 20 | ## [1.0.6] - 2023-03-14 21 | 22 | ### Compatibility 23 | 24 | - Update MSRV to 1.64.0 25 | 26 | ## [1.0.5] 27 | 28 | ### Compatibility 29 | 30 | Update MSRV to 1.60 31 | 32 | ## [0.9.0] - 2018-07-30 33 | 34 | ### Added 35 | - Add reflection to `Predicate`. 36 | - Add support for predicates returning why they failed (`find_case`) which can 37 | be combined with the new `predicates-tree` crate. 38 | - Split out `predicates-core` for reducing ecosystem breaking changes. 39 | 40 | ### Changed 41 | - Predicates must also implement `PredicateReflection` 42 | 43 | 44 | [Unreleased]: https://github.com/assert-rs/predicates-rs/compare/predicates-core-v1.0.9...HEAD 45 | [1.0.9]: https://github.com/assert-rs/predicates-rs/compare/predicates-core-v1.0.8...predicates-core-v1.0.9 46 | [1.0.8]: https://github.com/assert-rs/predicates-rs/compare/predicates-core-v1.0.7...predicates-core-v1.0.8 47 | [1.0.7]: https://github.com/assert-rs/predicates-rs/compare/predicates-core-v1.0.6...predicates-core-v1.0.7 48 | [1.0.6]: https://github.com/assert-rs/predicates-rs/compare/v0.9.0...predicates-core-v1.0.6 49 | [0.9.0]: https://github.com/assert-rs/predicates-rs/compare/v0.5.2...v0.9.0 50 | -------------------------------------------------------------------------------- /crates/core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "predicates-core" 3 | version = "1.0.9" 4 | description = "An API for boolean-valued predicate functions." 5 | authors = ["Nick Stevens "] 6 | readme = "README.md" 7 | repository = "https://github.com/assert-rs/predicates-rs/tree/master/crates/core" 8 | homepage = "https://github.com/assert-rs/predicates-rs/tree/master/crates/core" 9 | documentation = "https://docs.rs/predicates-core" 10 | categories = ["data-structures", "rust-patterns"] 11 | keywords = ["predicate", "boolean", "combinatorial", "match", "logic"] 12 | license.workspace = true 13 | edition.workspace = true 14 | rust-version.workspace = true 15 | include.workspace = true 16 | 17 | [package.metadata.docs.rs] 18 | all-features = true 19 | rustdoc-args = ["--cfg", "docsrs"] 20 | 21 | [package.metadata.release] 22 | pre-release-replacements = [ 23 | {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1}, 24 | {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1}, 25 | {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1}, 26 | {file="CHANGELOG.md", search="", replace="\n## [Unreleased] - ReleaseDate\n", exactly=1}, 27 | {file="CHANGELOG.md", search="", replace="\n[Unreleased]: https://github.com/assert-rs/predicates-rs/compare/{{tag_name}}...HEAD", exactly=1}, 28 | ] 29 | 30 | -------------------------------------------------------------------------------- /crates/core/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /crates/core/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) Individual contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /crates/core/README.md: -------------------------------------------------------------------------------- 1 | # predicates-core 2 | 3 | > Traits for **boolean-valued predicate functions** in Rust. 4 | 5 | [![Build Status](https://dev.azure.com/assert-rs/assert-rs/_apis/build/status/predicates-rs?branchName=master)](https://dev.azure.com/assert-rs/assert-rs/_build/latest?definitionId=1&branchName=master) 6 | [![Documentation](https://img.shields.io/badge/docs-master-blue.svg)](https://docs.rs/predicates-core) 7 | ![License](https://img.shields.io/crates/l/predicates-core.svg) 8 | [![Crates.io](https://img.shields.io/crates/v/predicates-core.svg?maxAge=2592000)](https://crates.io/crates/predicates-core) 9 | 10 | [Changelog](https://github.com/assert-rs/predicates-rs/blob/master/crates/core/CHANGELOG.md) 11 | 12 | 13 | ## License 14 | 15 | `predicates-core` is distributed under the terms of both the MIT license and the 16 | Apache License (Version 2.0). 17 | 18 | See LICENSE-APACHE, and LICENSE-MIT for details. 19 | 20 | 21 | ## Credits 22 | 23 | Big thanks to [futures-rs](https://github.com/alexcrichton/futures-rs), whose 24 | slick API design informed a lot of decisions made on the API design of this 25 | library. 26 | -------------------------------------------------------------------------------- /crates/core/release.toml: -------------------------------------------------------------------------------- 1 | pre-release-replacements = [] 2 | -------------------------------------------------------------------------------- /crates/core/src/core.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use crate::reflection; 10 | 11 | /// Trait for generically evaluating a type against a dynamically created 12 | /// predicate function. 13 | /// 14 | /// The exact meaning of `eval` depends on the situation, but will usually 15 | /// mean that the evaluated item is in some sort of pre-defined set. This is 16 | /// different from `Ord` and `Eq` in that an `item` will almost never be the 17 | /// same type as the implementing `Predicate` type. 18 | pub trait Predicate: reflection::PredicateReflection { 19 | /// Execute this `Predicate` against `variable`, returning the resulting 20 | /// boolean. 21 | fn eval(&self, variable: &Item) -> bool; 22 | 23 | /// Find a case that proves this predicate as `expected` when run against `variable`. 24 | fn find_case<'a>(&'a self, expected: bool, variable: &Item) -> Option> { 25 | let actual = self.eval(variable); 26 | if expected == actual { 27 | Some(reflection::Case::new(None, actual)) 28 | } else { 29 | None 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /crates/core/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Composable first-order predicate trait. 10 | //! 11 | //! This library implements an interface to "predicates" - boolean-valued 12 | //! functions of one argument. This allows combinatorial logic to be created and 13 | //! assembled at runtime and then used one or more times for evaluating values. 14 | //! This sort of object is really useful when creating filters and checks that 15 | //! can be changed at runtime with user interaction - it allows a clean 16 | //! separation of concerns where the configuration code can be used to build up 17 | //! a predicate, and then that predicate can be given to the code that does the 18 | //! actual filtering without the filtering code knowing anything about user 19 | //! configuration. See the examples for how this can work. 20 | 21 | #![warn(missing_docs, missing_debug_implementations)] 22 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 23 | 24 | mod core; 25 | pub use crate::core::*; 26 | pub mod reflection; 27 | -------------------------------------------------------------------------------- /crates/core/src/reflection.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Introspect into the state of a `Predicate`. 10 | 11 | use std::borrow; 12 | use std::fmt; 13 | use std::slice; 14 | 15 | /// Introspect the state of a `Predicate`. 16 | pub trait PredicateReflection: fmt::Display { 17 | /// Parameters of the current `Predicate`. 18 | fn parameters<'a>(&'a self) -> Box> + 'a> { 19 | let params = vec![]; 20 | Box::new(params.into_iter()) 21 | } 22 | 23 | /// Nested `Predicate`s of the current `Predicate`. 24 | fn children<'a>(&'a self) -> Box> + 'a> { 25 | let params = vec![]; 26 | Box::new(params.into_iter()) 27 | } 28 | } 29 | 30 | /// A view of a `Predicate` parameter, provided by reflection. 31 | /// 32 | /// ```rust 33 | /// use predicates_core; 34 | /// 35 | /// let param = predicates_core::reflection::Parameter::new("key", &10); 36 | /// println!("{}", param); 37 | /// ``` 38 | pub struct Parameter<'a>(&'a str, &'a dyn fmt::Display); 39 | 40 | impl<'a> Parameter<'a> { 41 | /// Create a new `Parameter`. 42 | pub fn new(key: &'a str, value: &'a dyn fmt::Display) -> Self { 43 | Self(key, value) 44 | } 45 | 46 | /// Access the `Parameter` name. 47 | pub fn name(&self) -> &str { 48 | self.0 49 | } 50 | 51 | /// Access the `Parameter` value. 52 | pub fn value(&self) -> &dyn fmt::Display { 53 | self.1 54 | } 55 | } 56 | 57 | impl fmt::Display for Parameter<'_> { 58 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 59 | write!(f, "{}: {}", self.0, self.1) 60 | } 61 | } 62 | 63 | impl fmt::Debug for Parameter<'_> { 64 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 65 | write!(f, "({:?}, {})", self.0, self.1) 66 | } 67 | } 68 | 69 | /// A view of a `Predicate` child, provided by reflection. 70 | pub struct Child<'a>(&'a str, &'a dyn PredicateReflection); 71 | 72 | impl<'a> Child<'a> { 73 | /// Create a new `Predicate` child. 74 | pub fn new(key: &'a str, value: &'a dyn PredicateReflection) -> Self { 75 | Self(key, value) 76 | } 77 | 78 | /// Access the `Child`'s name. 79 | pub fn name(&self) -> &str { 80 | self.0 81 | } 82 | 83 | /// Access the `Child` `Predicate`. 84 | pub fn value(&self) -> &dyn PredicateReflection { 85 | self.1 86 | } 87 | } 88 | 89 | impl fmt::Display for Child<'_> { 90 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 91 | write!(f, "{}: {}", self.0, self.1) 92 | } 93 | } 94 | 95 | impl fmt::Debug for Child<'_> { 96 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 97 | write!(f, "({:?}, {})", self.0, self.1) 98 | } 99 | } 100 | 101 | /// A descriptive explanation for why a predicate failed. 102 | pub struct Case<'a> { 103 | predicate: Option<&'a dyn PredicateReflection>, 104 | result: bool, 105 | products: Vec, 106 | children: Vec>, 107 | } 108 | 109 | impl<'a> Case<'a> { 110 | /// Create a new `Case` describing the result of a `Predicate`. 111 | pub fn new(predicate: Option<&'a dyn PredicateReflection>, result: bool) -> Self { 112 | Self { 113 | predicate, 114 | result, 115 | products: Default::default(), 116 | children: Default::default(), 117 | } 118 | } 119 | 120 | /// Add an additional by product to a `Case`. 121 | pub fn add_product(mut self, product: Product) -> Self { 122 | self.products.push(product); 123 | self 124 | } 125 | 126 | /// Add an additional by product to a `Case`. 127 | pub fn add_child(mut self, child: Case<'a>) -> Self { 128 | self.children.push(child); 129 | self 130 | } 131 | 132 | /// The `Predicate` that produced this case. 133 | pub fn predicate(&self) -> Option<&dyn PredicateReflection> { 134 | self.predicate 135 | } 136 | 137 | /// The result of this case. 138 | pub fn result(&self) -> bool { 139 | self.result 140 | } 141 | 142 | /// Access the by-products from determining this case. 143 | pub fn products(&self) -> CaseProducts<'_> { 144 | CaseProducts(self.products.iter()) 145 | } 146 | 147 | /// Access the sub-cases. 148 | pub fn children(&self) -> CaseChildren<'_> { 149 | CaseChildren(self.children.iter()) 150 | } 151 | } 152 | 153 | impl fmt::Debug for Case<'_> { 154 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 155 | let predicate = if let Some(ref predicate) = self.predicate { 156 | format!("Some({})", predicate) 157 | } else { 158 | "None".to_owned() 159 | }; 160 | f.debug_struct("Case") 161 | .field("predicate", &predicate) 162 | .field("result", &self.result) 163 | .field("products", &self.products) 164 | .field("children", &self.children) 165 | .finish() 166 | } 167 | } 168 | 169 | /// Iterator over a `Case`s by-products. 170 | #[derive(Debug, Clone)] 171 | pub struct CaseProducts<'a>(slice::Iter<'a, Product>); 172 | 173 | impl<'a> Iterator for CaseProducts<'a> { 174 | type Item = &'a Product; 175 | 176 | fn next(&mut self) -> Option<&'a Product> { 177 | self.0.next() 178 | } 179 | 180 | fn size_hint(&self) -> (usize, Option) { 181 | self.0.size_hint() 182 | } 183 | 184 | fn count(self) -> usize { 185 | self.0.count() 186 | } 187 | } 188 | 189 | /// Iterator over a `Case`s sub-cases. 190 | #[derive(Debug, Clone)] 191 | pub struct CaseChildren<'a>(slice::Iter<'a, Case<'a>>); 192 | 193 | impl<'a> Iterator for CaseChildren<'a> { 194 | type Item = &'a Case<'a>; 195 | 196 | fn next(&mut self) -> Option<&'a Case<'a>> { 197 | self.0.next() 198 | } 199 | 200 | fn size_hint(&self) -> (usize, Option) { 201 | self.0.size_hint() 202 | } 203 | 204 | fn count(self) -> usize { 205 | self.0.count() 206 | } 207 | } 208 | 209 | /// A by-product of a predicate evaluation. 210 | /// 211 | /// ```rust 212 | /// use predicates_core; 213 | /// 214 | /// let product = predicates_core::reflection::Product::new("key", "value"); 215 | /// println!("{}", product); 216 | /// let product = predicates_core::reflection::Product::new(format!("key-{}", 5), 30); 217 | /// println!("{}", product); 218 | /// ``` 219 | pub struct Product(borrow::Cow<'static, str>, Box); 220 | 221 | impl Product { 222 | /// Create a new `Product`. 223 | pub fn new(key: S, value: D) -> Self 224 | where 225 | S: Into>, 226 | D: fmt::Display + 'static, 227 | { 228 | Self(key.into(), Box::new(value)) 229 | } 230 | 231 | /// Access the `Product` name. 232 | pub fn name(&self) -> &str { 233 | self.0.as_ref() 234 | } 235 | 236 | /// Access the `Product` value. 237 | pub fn value(&self) -> &dyn fmt::Display { 238 | &self.1 239 | } 240 | } 241 | 242 | impl fmt::Display for Product { 243 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 244 | write!(f, "{}: {}", self.0, self.1) 245 | } 246 | } 247 | 248 | impl fmt::Debug for Product { 249 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 250 | write!(f, "({:?}, {})", self.0, self.1) 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /crates/tree/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | 8 | ## [Unreleased] - ReleaseDate 9 | 10 | ## [1.0.12] - 2024-12-19 11 | 12 | ## [1.0.11] - 2024-07-25 13 | 14 | ## [1.0.10] - 2024-07-25 15 | 16 | ### Compatibility 17 | 18 | - Update MSRV to 1.70.0 19 | 20 | ## [1.0.9] - 2023-03-14 21 | 22 | ### Fixes 23 | 24 | - Pass `f.alternate()` down 25 | 26 | ## [1.0.8] - 2023-03-14 27 | 28 | ### Compatibility 29 | 30 | - Update MSRV to 1.64.0 31 | 32 | ### Fixes 33 | 34 | - Ensured `alternate` gets passed down which is needed by `predicates` v3 35 | 36 | ## [1.0.7] 37 | 38 | ### Fixes 39 | 40 | - Make `CaseTree` be `Send+Sync` 41 | 42 | ## [0.9.0] - 2018-07-30 43 | 44 | ### Added 45 | - Support for rendering a predicate as a tree. 46 | 47 | 48 | [Unreleased]: https://github.com/assert-rs/predicates-rs/compare/predicates-tree-v1.0.12...HEAD 49 | [1.0.12]: https://github.com/assert-rs/predicates-rs/compare/predicates-tree-v1.0.11...predicates-tree-v1.0.12 50 | [1.0.11]: https://github.com/assert-rs/predicates-rs/compare/predicates-tree-v1.0.10...predicates-tree-v1.0.11 51 | [1.0.10]: https://github.com/assert-rs/predicates-rs/compare/predicates-tree-v1.0.9...predicates-tree-v1.0.10 52 | [1.0.9]: https://github.com/assert-rs/predicates-rs/compare/predicates-tree-v1.0.8...predicates-tree-v1.0.9 53 | [1.0.8]: https://github.com/assert-rs/predicates-rs/compare/v0.9.0...predicates-tree-v1.0.8 54 | [0.9.0]: https://github.com/assert-rs/predicates-rs/compare/v0.5.2...v0.9.0 55 | -------------------------------------------------------------------------------- /crates/tree/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "predicates-tree" 3 | version = "1.0.12" 4 | authors = ["Nick Stevens "] 5 | description = "Render boolean-valued predicate functions results as a tree." 6 | readme = "README.md" 7 | repository = "https://github.com/assert-rs/predicates-rs/tree/master/crates/tree" 8 | homepage = "https://github.com/assert-rs/predicates-rs/tree/master/crates/tree" 9 | documentation = "https://docs.rs/predicates-tree" 10 | categories = ["data-structures", "rust-patterns"] 11 | keywords = ["predicate", "boolean", "combinatorial", "match", "logic"] 12 | license.workspace = true 13 | edition.workspace = true 14 | rust-version.workspace = true 15 | include.workspace = true 16 | 17 | [package.metadata.docs.rs] 18 | all-features = true 19 | rustdoc-args = ["--cfg", "docsrs"] 20 | 21 | [package.metadata.release] 22 | pre-release-replacements = [ 23 | {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1}, 24 | {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1}, 25 | {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1}, 26 | {file="CHANGELOG.md", search="", replace="\n## [Unreleased] - ReleaseDate\n", exactly=1}, 27 | {file="CHANGELOG.md", search="", replace="\n[Unreleased]: https://github.com/assert-rs/predicates-rs/compare/{{tag_name}}...HEAD", exactly=1}, 28 | ] 29 | 30 | [dependencies] 31 | predicates-core = { version = "1.0", path = "../core" } 32 | termtree = "0.5.0" 33 | 34 | [dev-dependencies] 35 | predicates = { version = "3.1", path = "../..", features = ["color"] } 36 | -------------------------------------------------------------------------------- /crates/tree/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /crates/tree/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) Individual contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /crates/tree/README.md: -------------------------------------------------------------------------------- 1 | # predicates-tree 2 | 3 | > Render **boolean-valued predicate functions** results as a tree. 4 | 5 | [![Build Status](https://dev.azure.com/assert-rs/assert-rs/_apis/build/status/predicates-rs?branchName=master)](https://dev.azure.com/assert-rs/assert-rs/_build/latest?definitionId=1&branchName=master) 6 | [![Documentation](https://img.shields.io/badge/docs-master-blue.svg)](https://docs.rs/predicates-tree) 7 | ![License](https://img.shields.io/crates/l/predicates-tree.svg) 8 | [![Crates.io](https://img.shields.io/crates/v/predicates-tree.svg?maxAge=2592000)](https://crates.io/crates/predicates-tree) 9 | 10 | [Changelog](https://github.com/assert-rs/predicates-rs/blob/master/predicates-tree/CHANGELOG.md) 11 | 12 | 13 | ## License 14 | 15 | `predicates-tree` is distributed under the terms of both the MIT license and the 16 | Apache License (Version 2.0). 17 | 18 | See LICENSE-APACHE, and LICENSE-MIT for details. 19 | -------------------------------------------------------------------------------- /crates/tree/examples/failures.rs: -------------------------------------------------------------------------------- 1 | use predicates::prelude::*; 2 | use predicates_tree::CaseTreeExt; 3 | 4 | fn main() { 5 | let expected = 10; 6 | let actual = 15; 7 | let pred = predicates::ord::eq(expected); 8 | if let Some(case) = pred.find_case(false, &actual) { 9 | let tree = case.tree(); 10 | println!("{}", tree); 11 | } 12 | 13 | let expected = [1, 2, 3]; 14 | let actual = 15; 15 | let pred = predicates::iter::in_iter(IntoIterator::into_iter(expected)); 16 | if let Some(case) = pred.find_case(false, &actual) { 17 | let tree = case.tree(); 18 | println!("{}", tree); 19 | } 20 | 21 | let expected = "Hello 22 | World! 23 | 24 | Goodbye!"; 25 | let actual = "Hello 26 | Moon! 27 | 28 | Goodbye!"; 29 | let pred = predicates::ord::eq(expected); 30 | if let Some(case) = pred.find_case(false, &actual) { 31 | let tree = case.tree(); 32 | println!("{}", tree); 33 | } 34 | 35 | let expected = "Hello 36 | World! 37 | 38 | Goodbye!"; 39 | let actual = "Hello 40 | Moon! 41 | 42 | Goodbye!"; 43 | let pred = predicates::str::diff(expected); 44 | if let Some(case) = pred.find_case(false, actual) { 45 | let tree = case.tree(); 46 | println!("{}", tree); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /crates/tree/release.toml: -------------------------------------------------------------------------------- 1 | pre-release-replacements = [] 2 | -------------------------------------------------------------------------------- /crates/tree/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Render `Case` as a tree. 10 | 11 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 12 | 13 | use std::fmt; 14 | 15 | use predicates_core::reflection; 16 | 17 | /// Render `Self` as a displayable tree. 18 | pub trait CaseTreeExt { 19 | /// Render `Self` as a displayable tree. 20 | fn tree(&self) -> CaseTree; 21 | } 22 | 23 | impl CaseTreeExt for reflection::Case<'_> { 24 | fn tree(&self) -> CaseTree { 25 | CaseTree(convert(self)) 26 | } 27 | } 28 | 29 | type CaseTreeInner = termtree::Tree; 30 | 31 | fn convert(case: &reflection::Case<'_>) -> CaseTreeInner { 32 | let mut leaves: Vec = vec![]; 33 | 34 | leaves.extend(case.predicate().iter().flat_map(|pred| { 35 | pred.parameters().map(|item| { 36 | let root = Displayable::new(&item); 37 | termtree::Tree::new(root).with_multiline(true) 38 | }) 39 | })); 40 | 41 | leaves.extend(case.products().map(|item| { 42 | let root = Displayable::new(item); 43 | termtree::Tree::new(root).with_multiline(true) 44 | })); 45 | 46 | leaves.extend(case.children().map(convert)); 47 | 48 | let root = case 49 | .predicate() 50 | .map(|p| Displayable::new(&p)) 51 | .unwrap_or_default(); 52 | CaseTreeInner::new(root).with_leaves(leaves) 53 | } 54 | 55 | /// A `Case` rendered as a tree for display. 56 | #[allow(missing_debug_implementations)] 57 | pub struct CaseTree(CaseTreeInner); 58 | 59 | impl fmt::Display for CaseTree { 60 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 61 | self.0.fmt(f) 62 | } 63 | } 64 | 65 | #[derive(Default)] 66 | struct Displayable { 67 | primary: String, 68 | alternate: String, 69 | } 70 | 71 | impl Displayable { 72 | fn new(display: &dyn std::fmt::Display) -> Self { 73 | let primary = format!("{}", display); 74 | let alternate = format!("{:#}", display); 75 | Self { primary, alternate } 76 | } 77 | } 78 | 79 | impl fmt::Display for Displayable { 80 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 81 | if f.alternate() { 82 | self.alternate.fmt(f) 83 | } else { 84 | self.primary.fmt(f) 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | # Note that all fields that take a lint level have these possible values: 2 | # * deny - An error will be produced and the check will fail 3 | # * warn - A warning will be produced, but the check will not fail 4 | # * allow - No warning or error will be produced, though in some cases a note 5 | # will be 6 | 7 | # Root options 8 | 9 | # The graph table configures how the dependency graph is constructed and thus 10 | # which crates the checks are performed against 11 | [graph] 12 | # If 1 or more target triples (and optionally, target_features) are specified, 13 | # only the specified targets will be checked when running `cargo deny check`. 14 | # This means, if a particular package is only ever used as a target specific 15 | # dependency, such as, for example, the `nix` crate only being used via the 16 | # `target_family = "unix"` configuration, that only having windows targets in 17 | # this list would mean the nix crate, as well as any of its exclusive 18 | # dependencies not shared by any other crates, would be ignored, as the target 19 | # list here is effectively saying which targets you are building for. 20 | targets = [ 21 | # The triple can be any string, but only the target triples built in to 22 | # rustc (as of 1.40) can be checked against actual config expressions 23 | #"x86_64-unknown-linux-musl", 24 | # You can also specify which target_features you promise are enabled for a 25 | # particular target. target_features are currently not validated against 26 | # the actual valid features supported by the target architecture. 27 | #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, 28 | ] 29 | # When creating the dependency graph used as the source of truth when checks are 30 | # executed, this field can be used to prune crates from the graph, removing them 31 | # from the view of cargo-deny. This is an extremely heavy hammer, as if a crate 32 | # is pruned from the graph, all of its dependencies will also be pruned unless 33 | # they are connected to another crate in the graph that hasn't been pruned, 34 | # so it should be used with care. The identifiers are [Package ID Specifications] 35 | # (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) 36 | #exclude = [] 37 | # If true, metadata will be collected with `--all-features`. Note that this can't 38 | # be toggled off if true, if you want to conditionally enable `--all-features` it 39 | # is recommended to pass `--all-features` on the cmd line instead 40 | all-features = false 41 | # If true, metadata will be collected with `--no-default-features`. The same 42 | # caveat with `all-features` applies 43 | no-default-features = false 44 | # If set, these feature will be enabled when collecting metadata. If `--features` 45 | # is specified on the cmd line they will take precedence over this option. 46 | #features = [] 47 | 48 | # The output table provides options for how/if diagnostics are outputted 49 | [output] 50 | # When outputting inclusion graphs in diagnostics that include features, this 51 | # option can be used to specify the depth at which feature edges will be added. 52 | # This option is included since the graphs can be quite large and the addition 53 | # of features from the crate(s) to all of the graph roots can be far too verbose. 54 | # This option can be overridden via `--feature-depth` on the cmd line 55 | feature-depth = 1 56 | 57 | # This section is considered when running `cargo deny check advisories` 58 | # More documentation for the advisories section can be found here: 59 | # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html 60 | [advisories] 61 | # The path where the advisory databases are cloned/fetched into 62 | #db-path = "$CARGO_HOME/advisory-dbs" 63 | # The url(s) of the advisory databases to use 64 | #db-urls = ["https://github.com/rustsec/advisory-db"] 65 | # A list of advisory IDs to ignore. Note that ignored advisories will still 66 | # output a note when they are encountered. 67 | ignore = [ 68 | #"RUSTSEC-0000-0000", 69 | #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, 70 | #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish 71 | #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, 72 | ] 73 | # If this is true, then cargo deny will use the git executable to fetch advisory database. 74 | # If this is false, then it uses a built-in git library. 75 | # Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. 76 | # See Git Authentication for more information about setting up git authentication. 77 | #git-fetch-with-cli = true 78 | 79 | # This section is considered when running `cargo deny check licenses` 80 | # More documentation for the licenses section can be found here: 81 | # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html 82 | [licenses] 83 | # List of explicitly allowed licenses 84 | # See https://spdx.org/licenses/ for list of possible licenses 85 | # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. 86 | allow = [ 87 | "MIT", 88 | "MIT-0", 89 | "Apache-2.0", 90 | "BSD-3-Clause", 91 | "MPL-2.0", 92 | "Unicode-DFS-2016", 93 | "CC0-1.0", 94 | "ISC", 95 | "OpenSSL", 96 | ] 97 | # The confidence threshold for detecting a license from license text. 98 | # The higher the value, the more closely the license text must be to the 99 | # canonical license text of a valid SPDX license file. 100 | # [possible values: any between 0.0 and 1.0]. 101 | confidence-threshold = 0.8 102 | # Allow 1 or more licenses on a per-crate basis, so that particular licenses 103 | # aren't accepted for every possible crate as with the normal allow list 104 | exceptions = [ 105 | # Each entry is the crate and version constraint, and its specific allow 106 | # list 107 | #{ allow = ["Zlib"], crate = "adler32" }, 108 | ] 109 | 110 | # Some crates don't have (easily) machine readable licensing information, 111 | # adding a clarification entry for it allows you to manually specify the 112 | # licensing information 113 | [[licenses.clarify]] 114 | # The package spec the clarification applies to 115 | crate = "ring" 116 | # The SPDX expression for the license requirements of the crate 117 | expression = "MIT AND ISC AND OpenSSL" 118 | # One or more files in the crate's source used as the "source of truth" for 119 | # the license expression. If the contents match, the clarification will be used 120 | # when running the license check, otherwise the clarification will be ignored 121 | # and the crate will be checked normally, which may produce warnings or errors 122 | # depending on the rest of your configuration 123 | license-files = [ 124 | # Each entry is a crate relative path, and the (opaque) hash of its contents 125 | { path = "LICENSE", hash = 0xbd0eed23 } 126 | ] 127 | 128 | [licenses.private] 129 | # If true, ignores workspace crates that aren't published, or are only 130 | # published to private registries. 131 | # To see how to mark a crate as unpublished (to the official registry), 132 | # visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. 133 | ignore = true 134 | # One or more private registries that you might publish crates to, if a crate 135 | # is only published to private registries, and ignore is true, the crate will 136 | # not have its license(s) checked 137 | registries = [ 138 | #"https://sekretz.com/registry 139 | ] 140 | 141 | # This section is considered when running `cargo deny check bans`. 142 | # More documentation about the 'bans' section can be found here: 143 | # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html 144 | [bans] 145 | # Lint level for when multiple versions of the same crate are detected 146 | multiple-versions = "warn" 147 | # Lint level for when a crate version requirement is `*` 148 | wildcards = "allow" 149 | # The graph highlighting used when creating dotgraphs for crates 150 | # with multiple versions 151 | # * lowest-version - The path to the lowest versioned duplicate is highlighted 152 | # * simplest-path - The path to the version with the fewest edges is highlighted 153 | # * all - Both lowest-version and simplest-path are used 154 | highlight = "all" 155 | # The default lint level for `default` features for crates that are members of 156 | # the workspace that is being checked. This can be overridden by allowing/denying 157 | # `default` on a crate-by-crate basis if desired. 158 | workspace-default-features = "allow" 159 | # The default lint level for `default` features for external crates that are not 160 | # members of the workspace. This can be overridden by allowing/denying `default` 161 | # on a crate-by-crate basis if desired. 162 | external-default-features = "allow" 163 | # List of crates that are allowed. Use with care! 164 | allow = [ 165 | #"ansi_term@0.11.0", 166 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" }, 167 | ] 168 | # List of crates to deny 169 | deny = [ 170 | #"ansi_term@0.11.0", 171 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" }, 172 | # Wrapper crates can optionally be specified to allow the crate when it 173 | # is a direct dependency of the otherwise banned crate 174 | #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] }, 175 | ] 176 | 177 | # List of features to allow/deny 178 | # Each entry the name of a crate and a version range. If version is 179 | # not specified, all versions will be matched. 180 | #[[bans.features]] 181 | #crate = "reqwest" 182 | # Features to not allow 183 | #deny = ["json"] 184 | # Features to allow 185 | #allow = [ 186 | # "rustls", 187 | # "__rustls", 188 | # "__tls", 189 | # "hyper-rustls", 190 | # "rustls", 191 | # "rustls-pemfile", 192 | # "rustls-tls-webpki-roots", 193 | # "tokio-rustls", 194 | # "webpki-roots", 195 | #] 196 | # If true, the allowed features must exactly match the enabled feature set. If 197 | # this is set there is no point setting `deny` 198 | #exact = true 199 | 200 | # Certain crates/versions that will be skipped when doing duplicate detection. 201 | skip = [ 202 | #"ansi_term@0.11.0", 203 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, 204 | ] 205 | # Similarly to `skip` allows you to skip certain crates during duplicate 206 | # detection. Unlike skip, it also includes the entire tree of transitive 207 | # dependencies starting at the specified crate, up to a certain depth, which is 208 | # by default infinite. 209 | skip-tree = [ 210 | #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies 211 | #{ crate = "ansi_term@0.11.0", depth = 20 }, 212 | ] 213 | 214 | # This section is considered when running `cargo deny check sources`. 215 | # More documentation about the 'sources' section can be found here: 216 | # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html 217 | [sources] 218 | # Lint level for what to happen when a crate from a crate registry that is not 219 | # in the allow list is encountered 220 | unknown-registry = "deny" 221 | # Lint level for what to happen when a crate from a git repository that is not 222 | # in the allow list is encountered 223 | unknown-git = "deny" 224 | # List of URLs for allowed crate registries. Defaults to the crates.io index 225 | # if not specified. If it is specified but empty, no registries are allowed. 226 | allow-registry = ["https://github.com/rust-lang/crates.io-index"] 227 | # List of URLs for allowed Git repositories 228 | allow-git = [] 229 | 230 | [sources.allow-org] 231 | # 1 or more github.com organizations to allow git sources for 232 | github = [] 233 | # 1 or more gitlab.com organizations to allow git sources for 234 | gitlab = [] 235 | # 1 or more bitbucket.org organizations to allow git sources for 236 | bitbucket = [] 237 | -------------------------------------------------------------------------------- /examples/case_tree.rs: -------------------------------------------------------------------------------- 1 | use predicates::prelude::*; 2 | use predicates_tree::CaseTreeExt; 3 | 4 | fn main() { 5 | let pred = predicate::ne(5).not().and(predicate::ge(5)); 6 | 7 | let var = 5; 8 | let case = pred.find_case(true, &var); 9 | if let Some(case) = case { 10 | println!("var is {var}"); 11 | println!("{}", case.tree()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | allow-branch = ["master"] 2 | -------------------------------------------------------------------------------- /src/boxed.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Predicate that can wrap other dynamically-called predicates in an 10 | //! easy-to-manage type. 11 | 12 | use std::fmt; 13 | 14 | use crate::reflection; 15 | use crate::utils; 16 | use crate::Predicate; 17 | 18 | /// `Predicate` that wraps another `Predicate` as a trait object, allowing 19 | /// sized storage of predicate types. 20 | pub struct BoxPredicate(Box + Send + Sync>); 21 | 22 | impl BoxPredicate 23 | where 24 | Item: ?Sized, 25 | { 26 | /// Creates a new `BoxPredicate`, a wrapper around a dynamically-dispatched 27 | /// `Predicate` type with useful trait impls. 28 | pub fn new

(inner: P) -> BoxPredicate 29 | where 30 | P: Predicate + Send + Sync + 'static, 31 | { 32 | BoxPredicate(Box::new(inner)) 33 | } 34 | } 35 | 36 | impl fmt::Debug for BoxPredicate 37 | where 38 | Item: ?Sized, 39 | { 40 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 41 | f.debug_struct("BoxPredicate").finish() 42 | } 43 | } 44 | 45 | impl reflection::PredicateReflection for BoxPredicate 46 | where 47 | Item: ?Sized, 48 | { 49 | fn parameters<'a>(&'a self) -> Box> + 'a> { 50 | self.0.parameters() 51 | } 52 | 53 | fn children<'a>(&'a self) -> Box> + 'a> { 54 | self.0.children() 55 | } 56 | } 57 | 58 | impl fmt::Display for BoxPredicate 59 | where 60 | Item: ?Sized, 61 | { 62 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 63 | self.0.fmt(f) 64 | } 65 | } 66 | 67 | impl Predicate for BoxPredicate 68 | where 69 | Item: ?Sized, 70 | { 71 | fn eval(&self, variable: &Item) -> bool { 72 | self.0.eval(variable) 73 | } 74 | 75 | fn find_case<'a>(&'a self, expected: bool, variable: &Item) -> Option> { 76 | utils::default_find_case(self, expected, variable) 77 | } 78 | } 79 | 80 | /// `Predicate` extension for boxing a `Predicate`. 81 | pub trait PredicateBoxExt 82 | where 83 | Self: Predicate, 84 | { 85 | /// Returns a `BoxPredicate` wrapper around this `Predicate` type. 86 | /// 87 | /// Returns a `BoxPredicate` wrapper around this `Predicate` type. The 88 | /// `BoxPredicate` type has a number of useful properties: 89 | /// 90 | /// - It stores the inner predicate as a trait object, so the type of 91 | /// `BoxPredicate` will always be the same even if steps are added or 92 | /// removed from the predicate. 93 | /// - It is a common type, allowing it to be stored in vectors or other 94 | /// collection types. 95 | /// - It implements `Debug` and `Display`. 96 | /// 97 | /// # Examples 98 | /// 99 | /// ``` 100 | /// use predicates::prelude::*; 101 | /// 102 | /// let predicates = vec![ 103 | /// predicate::always().boxed(), 104 | /// predicate::never().boxed(), 105 | /// ]; 106 | /// assert_eq!(true, predicates[0].eval(&4)); 107 | /// assert_eq!(false, predicates[1].eval(&4)); 108 | /// ``` 109 | fn boxed(self) -> BoxPredicate 110 | where 111 | Self: Sized + Send + Sync + 'static, 112 | { 113 | BoxPredicate::new(self) 114 | } 115 | } 116 | 117 | impl PredicateBoxExt for P where P: Predicate {} 118 | 119 | #[cfg(test)] 120 | mod test { 121 | use crate::prelude::*; 122 | 123 | #[test] 124 | fn unsized_boxed() { 125 | let p = predicate::always().boxed(); 126 | p.eval("4"); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/color.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy, Clone, Debug, Default)] 2 | pub(crate) struct Palette { 3 | description: anstyle::Style, 4 | var: anstyle::Style, 5 | expected: anstyle::Style, 6 | } 7 | 8 | impl Palette { 9 | pub(crate) fn new(alternate: bool) -> Self { 10 | if alternate && cfg!(feature = "color") { 11 | Self { 12 | description: anstyle::AnsiColor::Blue.on_default() | anstyle::Effects::BOLD, 13 | var: anstyle::AnsiColor::Red.on_default() | anstyle::Effects::BOLD, 14 | expected: anstyle::AnsiColor::Green.on_default() | anstyle::Effects::BOLD, 15 | } 16 | } else { 17 | Self::plain() 18 | } 19 | } 20 | 21 | pub(crate) fn plain() -> Self { 22 | Self { 23 | description: Default::default(), 24 | var: Default::default(), 25 | expected: Default::default(), 26 | } 27 | } 28 | 29 | pub(crate) fn description(self, display: D) -> Styled { 30 | Styled::new(display, self.description) 31 | } 32 | 33 | pub(crate) fn var(self, display: D) -> Styled { 34 | Styled::new(display, self.var) 35 | } 36 | 37 | pub(crate) fn expected(self, display: D) -> Styled { 38 | Styled::new(display, self.expected) 39 | } 40 | } 41 | 42 | #[derive(Debug)] 43 | pub(crate) struct Styled { 44 | display: D, 45 | style: anstyle::Style, 46 | } 47 | 48 | impl Styled { 49 | pub(crate) fn new(display: D, style: anstyle::Style) -> Self { 50 | Self { display, style } 51 | } 52 | } 53 | 54 | impl std::fmt::Display for Styled { 55 | #[inline] 56 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 57 | if f.alternate() { 58 | write!(f, "{}", self.style.render())?; 59 | self.display.fmt(f)?; 60 | write!(f, "{}", self.style.render_reset())?; 61 | Ok(()) 62 | } else { 63 | self.display.fmt(f) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/constant.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Definition of a constant (always true or always false) `Predicate`. 10 | 11 | use std::fmt; 12 | 13 | use crate::reflection; 14 | use crate::utils; 15 | use crate::Predicate; 16 | 17 | /// Predicate that always returns a constant (boolean) result. 18 | /// 19 | /// This is created by the `predicate::always` and `predicate::never` functions. 20 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 21 | pub struct BooleanPredicate { 22 | retval: bool, 23 | } 24 | 25 | impl Predicate for BooleanPredicate { 26 | fn eval(&self, _variable: &Item) -> bool { 27 | self.retval 28 | } 29 | 30 | fn find_case<'a>(&'a self, expected: bool, variable: &Item) -> Option> { 31 | utils::default_find_case(self, expected, variable) 32 | } 33 | } 34 | 35 | impl reflection::PredicateReflection for BooleanPredicate { 36 | fn parameters<'a>(&'a self) -> Box> + 'a> { 37 | let params = vec![reflection::Parameter::new("value", &self.retval)]; 38 | Box::new(params.into_iter()) 39 | } 40 | } 41 | 42 | impl fmt::Display for BooleanPredicate { 43 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 44 | let palette = crate::Palette::new(f.alternate()); 45 | write!(f, "{}", palette.expected(self.retval)) 46 | } 47 | } 48 | 49 | /// Creates a new `Predicate` that always returns `true`. 50 | /// 51 | /// # Examples 52 | /// 53 | /// ``` 54 | /// use predicates::prelude::*; 55 | /// 56 | /// let predicate_fn = predicate::always(); 57 | /// assert_eq!(true, predicate_fn.eval(&5)); 58 | /// assert_eq!(true, predicate_fn.eval(&10)); 59 | /// assert_eq!(true, predicate_fn.eval(&15)); 60 | /// // Won't work - Predicates can only operate on a single type 61 | /// // assert_eq!(true, predicate_fn.eval("hello")) 62 | /// ``` 63 | pub fn always() -> BooleanPredicate { 64 | BooleanPredicate { retval: true } 65 | } 66 | 67 | /// Creates a new `Predicate` that always returns `false`. 68 | /// 69 | /// # Examples 70 | /// 71 | /// ``` 72 | /// use predicates::prelude::*; 73 | /// 74 | /// let predicate_fn = predicate::never(); 75 | /// assert_eq!(false, predicate_fn.eval(&5)); 76 | /// assert_eq!(false, predicate_fn.eval(&10)); 77 | /// assert_eq!(false, predicate_fn.eval(&15)); 78 | /// // Won't work - Predicates can only operate on a single type 79 | /// // assert_eq!(false, predicate_fn.eval("hello")) 80 | /// ``` 81 | pub fn never() -> BooleanPredicate { 82 | BooleanPredicate { retval: false } 83 | } 84 | -------------------------------------------------------------------------------- /src/float/close.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use std::fmt; 10 | 11 | use float_cmp::ApproxEq; 12 | use float_cmp::Ulps; 13 | 14 | use crate::reflection; 15 | use crate::Predicate; 16 | 17 | /// Predicate that ensures two numbers are "close" enough, understanding that rounding errors 18 | /// occur. 19 | /// 20 | /// This is created by the `predicate::float::is_close`. 21 | #[derive(Debug, Clone, Copy, PartialEq)] 22 | pub struct IsClosePredicate { 23 | target: f64, 24 | epsilon: f64, 25 | ulps: ::U, 26 | } 27 | 28 | impl IsClosePredicate { 29 | /// Set the amount of error allowed. 30 | /// 31 | /// Values `1`-`5` should work in most cases. Sometimes more control is needed and you will 32 | /// need to set `IsClosePredicate::epsilon` separately from `IsClosePredicate::ulps`. 33 | /// 34 | /// # Examples 35 | /// 36 | /// ``` 37 | /// use predicates::prelude::*; 38 | /// 39 | /// let a = 0.15_f64 + 0.15_f64 + 0.15_f64; 40 | /// let predicate_fn = predicate::float::is_close(a).distance(5); 41 | /// ``` 42 | pub fn distance(mut self, distance: ::U) -> Self { 43 | self.epsilon = (distance as f64) * f64::EPSILON; 44 | self.ulps = distance; 45 | self 46 | } 47 | 48 | /// Set the absolute deviation allowed. 49 | /// 50 | /// This is meant to handle problems near `0`. Values `1.`-`5.` epislons should work in most 51 | /// cases. 52 | /// 53 | /// # Examples 54 | /// 55 | /// ``` 56 | /// use predicates::prelude::*; 57 | /// 58 | /// let a = 0.15_f64 + 0.15_f64 + 0.15_f64; 59 | /// let predicate_fn = predicate::float::is_close(a).epsilon(5.0 * f64::EPSILON); 60 | /// ``` 61 | pub fn epsilon(mut self, epsilon: f64) -> Self { 62 | self.epsilon = epsilon; 63 | self 64 | } 65 | 66 | /// Set the relative deviation allowed. 67 | /// 68 | /// This is meant to handle large numbers. Values `1`-`5` should work in most cases. 69 | /// 70 | /// # Examples 71 | /// 72 | /// ``` 73 | /// use predicates::prelude::*; 74 | /// 75 | /// let a = 0.15_f64 + 0.15_f64 + 0.15_f64; 76 | /// let predicate_fn = predicate::float::is_close(a).ulps(5); 77 | /// ``` 78 | pub fn ulps(mut self, ulps: ::U) -> Self { 79 | self.ulps = ulps; 80 | self 81 | } 82 | } 83 | 84 | impl Predicate for IsClosePredicate { 85 | fn eval(&self, variable: &f64) -> bool { 86 | variable.approx_eq( 87 | self.target, 88 | float_cmp::F64Margin { 89 | epsilon: self.epsilon, 90 | ulps: self.ulps, 91 | }, 92 | ) 93 | } 94 | 95 | fn find_case<'a>(&'a self, expected: bool, variable: &f64) -> Option> { 96 | let actual = self.eval(variable); 97 | if expected == actual { 98 | Some( 99 | reflection::Case::new(Some(self), actual) 100 | .add_product(reflection::Product::new( 101 | "actual epsilon", 102 | (variable - self.target).abs(), 103 | )) 104 | .add_product(reflection::Product::new( 105 | "actual ulps", 106 | variable.ulps(&self.target).abs(), 107 | )), 108 | ) 109 | } else { 110 | None 111 | } 112 | } 113 | } 114 | 115 | impl reflection::PredicateReflection for IsClosePredicate { 116 | fn parameters<'a>(&'a self) -> Box> + 'a> { 117 | let params = vec![ 118 | reflection::Parameter::new("epsilon", &self.epsilon), 119 | reflection::Parameter::new("ulps", &self.ulps), 120 | ]; 121 | Box::new(params.into_iter()) 122 | } 123 | } 124 | 125 | impl fmt::Display for IsClosePredicate { 126 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 127 | let palette = crate::Palette::new(f.alternate()); 128 | write!( 129 | f, 130 | "{} {} {}", 131 | palette.var("var"), 132 | palette.description("!="), 133 | palette.expected(self.target), 134 | ) 135 | } 136 | } 137 | 138 | /// Create a new `Predicate` that ensures two numbers are "close" enough, understanding that 139 | /// rounding errors occur. 140 | /// 141 | /// # Examples 142 | /// 143 | /// ``` 144 | /// use predicates::prelude::*; 145 | /// 146 | /// let a = 0.15_f64 + 0.15_f64 + 0.15_f64; 147 | /// let b = 0.1_f64 + 0.1_f64 + 0.25_f64; 148 | /// let predicate_fn = predicate::float::is_close(a); 149 | /// assert_eq!(true, predicate_fn.eval(&b)); 150 | /// assert_eq!(false, predicate_fn.distance(0).eval(&b)); 151 | /// ``` 152 | pub fn is_close(target: f64) -> IsClosePredicate { 153 | IsClosePredicate { 154 | target, 155 | epsilon: 2.0 * f64::EPSILON, 156 | ulps: 2, 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/float/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Float Predicates 10 | //! 11 | //! This module contains predicates specific to string handling. 12 | 13 | #[cfg(feature = "float-cmp")] 14 | mod close; 15 | #[cfg(feature = "float-cmp")] 16 | pub use self::close::{is_close, IsClosePredicate}; 17 | -------------------------------------------------------------------------------- /src/function.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Definition of `Predicate` for wrapping a `Fn(&T) -> bool` 10 | 11 | use std::fmt; 12 | use std::marker::PhantomData; 13 | 14 | use crate::reflection; 15 | use crate::utils; 16 | use crate::Predicate; 17 | 18 | /// Predicate that wraps a function over a reference that returns a `bool`. 19 | /// This type is returned by the `predicate::function` function. 20 | #[allow(clippy::derive_partial_eq_without_eq)] 21 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 22 | pub struct FnPredicate 23 | where 24 | F: Fn(&T) -> bool, 25 | T: ?Sized, 26 | { 27 | function: F, 28 | name: &'static str, 29 | _phantom: PhantomData, 30 | } 31 | 32 | unsafe impl Send for FnPredicate 33 | where 34 | F: Send + Fn(&T) -> bool, 35 | T: ?Sized, 36 | { 37 | } 38 | 39 | unsafe impl Sync for FnPredicate 40 | where 41 | F: Sync + Fn(&T) -> bool, 42 | T: ?Sized, 43 | { 44 | } 45 | 46 | impl FnPredicate 47 | where 48 | F: Fn(&T) -> bool, 49 | T: ?Sized, 50 | { 51 | /// Provide a descriptive name for this function. 52 | /// 53 | /// # Examples 54 | /// 55 | /// ``` 56 | /// use predicates::prelude::*; 57 | /// 58 | /// struct Example { 59 | /// string: String, 60 | /// number: i32, 61 | /// } 62 | /// 63 | /// let string_check = predicate::function(|x: &Example| x.string == "hello") 64 | /// .fn_name("is_hello"); 65 | /// println!("predicate: {}", string_check); 66 | /// ``` 67 | pub fn fn_name(mut self, name: &'static str) -> Self { 68 | self.name = name; 69 | self 70 | } 71 | } 72 | 73 | impl Predicate for FnPredicate 74 | where 75 | F: Fn(&T) -> bool, 76 | T: ?Sized, 77 | { 78 | fn eval(&self, variable: &T) -> bool { 79 | (self.function)(variable) 80 | } 81 | 82 | fn find_case<'a>(&'a self, expected: bool, variable: &T) -> Option> { 83 | utils::default_find_case(self, expected, variable) 84 | } 85 | } 86 | 87 | impl reflection::PredicateReflection for FnPredicate 88 | where 89 | F: Fn(&T) -> bool, 90 | T: ?Sized, 91 | { 92 | } 93 | 94 | impl fmt::Display for FnPredicate 95 | where 96 | F: Fn(&T) -> bool, 97 | T: ?Sized, 98 | { 99 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 100 | let palette = crate::Palette::new(f.alternate()); 101 | write!( 102 | f, 103 | "{}({})", 104 | palette.description(self.name), 105 | palette.var("var"), 106 | ) 107 | } 108 | } 109 | 110 | /// Creates a new predicate that wraps over the given function. The returned 111 | /// type implements `Predicate` and therefore has all combinators available to 112 | /// it. 113 | /// 114 | /// # Examples 115 | /// 116 | /// ``` 117 | /// use predicates::prelude::*; 118 | /// 119 | /// struct Example { 120 | /// string: String, 121 | /// number: i32, 122 | /// } 123 | /// 124 | /// let string_check = predicate::function(|x: &Example| x.string == "hello"); 125 | /// let number_check = predicate::function(|x: &Example| x.number == 42); 126 | /// let predicate_fn = string_check.and(number_check); 127 | /// let good_example = Example { string: "hello".into(), number: 42 }; 128 | /// assert_eq!(true, predicate_fn.eval(&good_example)); 129 | /// let bad_example = Example { string: "goodbye".into(), number: 0 }; 130 | /// assert_eq!(false, predicate_fn.eval(&bad_example)); 131 | /// ``` 132 | pub fn function(function: F) -> FnPredicate 133 | where 134 | F: Fn(&T) -> bool, 135 | T: ?Sized, 136 | { 137 | FnPredicate { 138 | function, 139 | name: "fn", 140 | _phantom: PhantomData, 141 | } 142 | } 143 | 144 | #[test] 145 | fn str_function() { 146 | let f = function(|x: &str| x == "hello"); 147 | assert!(f.eval("hello")); 148 | assert!(!f.eval("goodbye")); 149 | } 150 | -------------------------------------------------------------------------------- /src/iter.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Definition of `Predicate`s for comparisons of membership in a set. 10 | 11 | use std::collections::HashSet; 12 | use std::fmt; 13 | use std::hash::Hash; 14 | use std::iter::FromIterator; 15 | 16 | use crate::reflection; 17 | use crate::utils; 18 | use crate::Predicate; 19 | 20 | /// Predicate that returns `true` if `variable` is a member of the pre-defined 21 | /// set, otherwise returns `false`. 22 | /// 23 | /// Note that this implementation places the fewest restrictions on the 24 | /// underlying `Item` type at the expense of having the least performant 25 | /// implementation (linear search). If the type to be searched is `Hash + Eq`, 26 | /// it is much more efficient to use `HashableInPredicate` and 27 | /// `in_hash`. The implementation-specific predicates will be 28 | /// deprecated when Rust supports trait specialization. 29 | #[derive(Debug, Clone, PartialEq, Eq)] 30 | pub struct InPredicate 31 | where 32 | T: PartialEq + fmt::Debug, 33 | { 34 | inner: utils::DebugAdapter>, 35 | } 36 | 37 | impl InPredicate 38 | where 39 | T: Ord + fmt::Debug, 40 | { 41 | /// Creates a new predicate that will return `true` when the given `variable` is 42 | /// contained with the set of items provided. 43 | /// 44 | /// Note that this implementation requires `Item` to be `Ord`. The 45 | /// `InPredicate` uses a less efficient search algorithm but only 46 | /// requires `Item` implement `PartialEq`. The implementation-specific 47 | /// predicates will be deprecated when Rust supports trait specialization. 48 | /// 49 | /// # Examples 50 | /// 51 | /// ``` 52 | /// use predicates::prelude::*; 53 | /// 54 | /// let predicate_fn = predicate::in_iter(vec![1, 3, 5]).sort(); 55 | /// assert_eq!(true, predicate_fn.eval(&1)); 56 | /// assert_eq!(false, predicate_fn.eval(&2)); 57 | /// assert_eq!(true, predicate_fn.eval(&3)); 58 | /// 59 | /// let predicate_fn = predicate::in_iter(vec!["a", "c", "e"]).sort(); 60 | /// assert_eq!(true, predicate_fn.eval("a")); 61 | /// assert_eq!(false, predicate_fn.eval("b")); 62 | /// assert_eq!(true, predicate_fn.eval("c")); 63 | /// 64 | /// let predicate_fn = predicate::in_iter(vec![String::from("a"), String::from("c"), String::from("e")]).sort(); 65 | /// assert_eq!(true, predicate_fn.eval("a")); 66 | /// assert_eq!(false, predicate_fn.eval("b")); 67 | /// assert_eq!(true, predicate_fn.eval("c")); 68 | /// ``` 69 | pub fn sort(self) -> OrdInPredicate { 70 | let mut items = self.inner.debug; 71 | items.sort(); 72 | OrdInPredicate { 73 | inner: utils::DebugAdapter::new(items), 74 | } 75 | } 76 | } 77 | 78 | impl Predicate

for InPredicate 79 | where 80 | T: std::borrow::Borrow

+ PartialEq + fmt::Debug, 81 | P: PartialEq + fmt::Debug + ?Sized, 82 | { 83 | fn eval(&self, variable: &P) -> bool { 84 | self.inner.debug.iter().any(|x| x.borrow() == variable) 85 | } 86 | 87 | fn find_case<'a>(&'a self, expected: bool, variable: &P) -> Option> { 88 | utils::default_find_case(self, expected, variable).map(|case| { 89 | case.add_product(reflection::Product::new( 90 | "var", 91 | utils::DebugAdapter::new(variable).to_string(), 92 | )) 93 | }) 94 | } 95 | } 96 | 97 | impl reflection::PredicateReflection for InPredicate 98 | where 99 | T: PartialEq + fmt::Debug, 100 | { 101 | fn parameters<'a>(&'a self) -> Box> + 'a> { 102 | let params = vec![reflection::Parameter::new("values", &self.inner)]; 103 | Box::new(params.into_iter()) 104 | } 105 | } 106 | 107 | impl fmt::Display for InPredicate 108 | where 109 | T: PartialEq + fmt::Debug, 110 | { 111 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 112 | let palette = crate::Palette::new(f.alternate()); 113 | write!( 114 | f, 115 | "{} {} {}", 116 | palette.var("var"), 117 | palette.description("in"), 118 | palette.expected("values") 119 | ) 120 | } 121 | } 122 | 123 | /// Creates a new predicate that will return `true` when the given `variable` is 124 | /// contained with the set of items provided. 125 | /// 126 | /// Note that this implementation places the fewest restrictions on the 127 | /// underlying `Item` type at the expense of having the least performant 128 | /// implementation (linear search). If the type to be searched is `Hash + Eq`, 129 | /// it is much more efficient to use `HashableInPredicate` and 130 | /// `in_hash`. The implementation-specific predicates will be 131 | /// deprecated when Rust supports trait specialization. 132 | /// 133 | /// If you need to optimize this 134 | /// - Type is `Ord`, call `sort()` on this predicate. 135 | /// - Type is `Hash`, replace `in_iter` with `in_hash`. 136 | /// 137 | /// # Examples 138 | /// 139 | /// ``` 140 | /// use predicates::prelude::*; 141 | /// 142 | /// let predicate_fn = predicate::in_iter(vec![1, 3, 5]); 143 | /// assert_eq!(true, predicate_fn.eval(&1)); 144 | /// assert_eq!(false, predicate_fn.eval(&2)); 145 | /// assert_eq!(true, predicate_fn.eval(&3)); 146 | /// 147 | /// let predicate_fn = predicate::in_iter(vec!["a", "c", "e"]); 148 | /// assert_eq!(true, predicate_fn.eval("a")); 149 | /// assert_eq!(false, predicate_fn.eval("b")); 150 | /// assert_eq!(true, predicate_fn.eval("c")); 151 | /// 152 | /// let predicate_fn = predicate::in_iter(vec![String::from("a"), String::from("c"), String::from("e")]); 153 | /// assert_eq!(true, predicate_fn.eval("a")); 154 | /// assert_eq!(false, predicate_fn.eval("b")); 155 | /// assert_eq!(true, predicate_fn.eval("c")); 156 | /// ``` 157 | pub fn in_iter(iter: I) -> InPredicate 158 | where 159 | T: PartialEq + fmt::Debug, 160 | I: IntoIterator, 161 | { 162 | InPredicate { 163 | inner: utils::DebugAdapter::new(Vec::from_iter(iter)), 164 | } 165 | } 166 | 167 | /// Predicate that returns `true` if `variable` is a member of the pre-defined 168 | /// set, otherwise returns `false`. 169 | /// 170 | /// Note that this implementation requires `Item` to be `Ord`. The 171 | /// `InPredicate` uses a less efficient search algorithm but only 172 | /// requires `Item` implement `PartialEq`. The implementation-specific 173 | /// predicates will be deprecated when Rust supports trait specialization. 174 | /// 175 | /// This is created by the `predicate::in_iter(...).sort` function. 176 | #[derive(Debug, Clone, PartialEq, Eq)] 177 | pub struct OrdInPredicate 178 | where 179 | T: Ord + fmt::Debug, 180 | { 181 | inner: utils::DebugAdapter>, 182 | } 183 | 184 | impl Predicate

for OrdInPredicate 185 | where 186 | T: std::borrow::Borrow

+ Ord + fmt::Debug, 187 | P: Ord + fmt::Debug + ?Sized, 188 | { 189 | fn eval(&self, variable: &P) -> bool { 190 | self.inner 191 | .debug 192 | .binary_search_by(|x| x.borrow().cmp(variable)) 193 | .is_ok() 194 | } 195 | 196 | fn find_case<'a>(&'a self, expected: bool, variable: &P) -> Option> { 197 | utils::default_find_case(self, expected, variable).map(|case| { 198 | case.add_product(reflection::Product::new( 199 | "var", 200 | utils::DebugAdapter::new(variable).to_string(), 201 | )) 202 | }) 203 | } 204 | } 205 | 206 | impl reflection::PredicateReflection for OrdInPredicate 207 | where 208 | T: Ord + fmt::Debug, 209 | { 210 | fn parameters<'a>(&'a self) -> Box> + 'a> { 211 | let params = vec![reflection::Parameter::new("values", &self.inner)]; 212 | Box::new(params.into_iter()) 213 | } 214 | } 215 | 216 | impl fmt::Display for OrdInPredicate 217 | where 218 | T: Ord + fmt::Debug, 219 | { 220 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 221 | let palette = crate::Palette::new(f.alternate()); 222 | write!( 223 | f, 224 | "{} {} {}", 225 | palette.var("var"), 226 | palette.description("in"), 227 | palette.expected("values") 228 | ) 229 | } 230 | } 231 | 232 | /// Predicate that returns `true` if `variable` is a member of the pre-defined 233 | /// `HashSet`, otherwise returns `false`. 234 | /// 235 | /// Note that this implementation requires `Item` to be `Hash + Eq`. The 236 | /// `InPredicate` uses a less efficient search algorithm but only 237 | /// requires `Item` implement `PartialEq`. The implementation-specific 238 | /// predicates will be deprecated when Rust supports trait specialization. 239 | /// 240 | /// This is created by the `predicate::in_hash` function. 241 | #[derive(Debug, Clone, PartialEq, Eq)] 242 | pub struct HashableInPredicate 243 | where 244 | T: Hash + Eq + fmt::Debug, 245 | { 246 | inner: utils::DebugAdapter>, 247 | } 248 | 249 | impl Predicate

for HashableInPredicate 250 | where 251 | T: std::borrow::Borrow

+ Hash + Eq + fmt::Debug, 252 | P: Hash + Eq + fmt::Debug + ?Sized, 253 | { 254 | fn eval(&self, variable: &P) -> bool { 255 | self.inner.debug.contains(variable) 256 | } 257 | 258 | fn find_case<'a>(&'a self, expected: bool, variable: &P) -> Option> { 259 | utils::default_find_case(self, expected, variable).map(|case| { 260 | case.add_product(reflection::Product::new( 261 | "var", 262 | utils::DebugAdapter::new(variable).to_string(), 263 | )) 264 | }) 265 | } 266 | } 267 | 268 | impl reflection::PredicateReflection for HashableInPredicate 269 | where 270 | T: Hash + Eq + fmt::Debug, 271 | { 272 | fn parameters<'a>(&'a self) -> Box> + 'a> { 273 | let params = vec![reflection::Parameter::new("values", &self.inner)]; 274 | Box::new(params.into_iter()) 275 | } 276 | } 277 | 278 | impl fmt::Display for HashableInPredicate 279 | where 280 | T: Hash + Eq + fmt::Debug, 281 | { 282 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 283 | let palette = crate::Palette::new(f.alternate()); 284 | write!( 285 | f, 286 | "{} {} {}", 287 | palette.var("var"), 288 | palette.description("in"), 289 | palette.expected("values") 290 | ) 291 | } 292 | } 293 | 294 | /// Creates a new predicate that will return `true` when the given `variable` is 295 | /// contained with the set of items provided. 296 | /// 297 | /// Note that this implementation requires `Item` to be `Hash + Eq`. The 298 | /// `InPredicate` uses a less efficient search algorithm but only 299 | /// requires `Item` implement `PartialEq`. The implementation-specific 300 | /// predicates will be deprecated when Rust supports trait specialization. 301 | /// 302 | /// # Examples 303 | /// 304 | /// ``` 305 | /// use predicates::prelude::*; 306 | /// 307 | /// let predicate_fn = predicate::in_hash(vec![1, 3, 5]); 308 | /// assert_eq!(true, predicate_fn.eval(&1)); 309 | /// assert_eq!(false, predicate_fn.eval(&2)); 310 | /// assert_eq!(true, predicate_fn.eval(&3)); 311 | /// 312 | /// let predicate_fn = predicate::in_hash(vec!["a", "c", "e"]); 313 | /// assert_eq!(true, predicate_fn.eval("a")); 314 | /// assert_eq!(false, predicate_fn.eval("b")); 315 | /// assert_eq!(true, predicate_fn.eval("c")); 316 | /// 317 | /// let predicate_fn = predicate::in_hash(vec![String::from("a"), String::from("c"), String::from("e")]); 318 | /// assert_eq!(true, predicate_fn.eval("a")); 319 | /// assert_eq!(false, predicate_fn.eval("b")); 320 | /// assert_eq!(true, predicate_fn.eval("c")); 321 | /// ``` 322 | pub fn in_hash(iter: I) -> HashableInPredicate 323 | where 324 | T: Hash + Eq + fmt::Debug, 325 | I: IntoIterator, 326 | { 327 | HashableInPredicate { 328 | inner: utils::DebugAdapter::new(HashSet::from_iter(iter)), 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Composable first-order predicate functions. 10 | //! 11 | //! This library implements an interface to "predicates" - boolean-valued 12 | //! functions of one argument. This allows combinatorial logic to be created and 13 | //! assembled at runtime and then used one or more times for evaluating values. 14 | //! This sort of object is really useful when creating filters and checks that 15 | //! can be changed at runtime with user interaction - it allows a clean 16 | //! separation of concerns where the configuration code can be used to build up 17 | //! a predicate, and then that predicate can be given to the code that does the 18 | //! actual filtering without the filtering code knowing anything about user 19 | //! configuration. See the examples for how this can work. 20 | //! 21 | //! ## Installation 22 | //! 23 | //! Add this to your `Cargo.toml`: 24 | //! 25 | //! ```toml 26 | //! [dependencies] 27 | //! predicates = "3.1.3" 28 | //! ``` 29 | //! 30 | //! A [prelude] is available to bring in all extension traits as well as providing 31 | //! `prelude::predicate` which focuses on the 90% case of the API. 32 | //! ```rust 33 | //! use predicates::prelude::*; 34 | //! ``` 35 | //! 36 | //! ## Examples 37 | //! 38 | //! The simplest predicates are [`predicate::always`] and [`predicate::never`], which always 39 | //! returns `true` and always returns `false`, respectively. The values are simply ignored when 40 | //! evaluating against these predicates: 41 | //! ```rust 42 | //! use predicates::prelude::*; 43 | //! 44 | //! let always_true = predicate::always(); 45 | //! assert_eq!(true, always_true.eval(&5)); 46 | //! let always_false = predicate::never(); 47 | //! assert_eq!(false, always_false.eval(&5)); 48 | //! ``` 49 | //! 50 | //! Pre-made predicates are available for types that implement the `PartialOrd` and 51 | //! `PartialEq` traits. The following example uses `lt`, but `eq`, `ne`, `le`, `gt`, 52 | //! `ge` are also available. 53 | //! ```rust 54 | //! use predicates::prelude::*; 55 | //! 56 | //! let less_than_ten = predicate::lt(10); 57 | //! assert_eq!(true, less_than_ten.eval(&9)); 58 | //! assert_eq!(false, less_than_ten.eval(&11)); 59 | //! ``` 60 | //! 61 | //! Any function over a reference to the desired `Item` that returns `bool` 62 | //! can easily be made into a `Predicate` using the [`predicate::function`] 63 | //! function. 64 | //! ```rust 65 | //! use predicates::prelude::*; 66 | //! 67 | //! let bound = 5; 68 | //! let predicate_fn = predicate::function(|&x| x >= bound); 69 | //! let between_5_and_10 = predicate_fn.and(predicate::le(10)); 70 | //! assert_eq!(true, between_5_and_10.eval(&7)); 71 | //! assert_eq!(false, between_5_and_10.eval(&3)); 72 | //! ``` 73 | //! 74 | //! The `Predicate` type is actually a trait, and that trait implements a 75 | //! number of useful combinator functions. For example, evaluating for a value 76 | //! between two other values can be accomplished as follows: 77 | //! ```rust 78 | //! use predicates::prelude::*; 79 | //! 80 | //! let between_5_and_10 = predicate::ge(5).and(predicate::le(10)); 81 | //! assert_eq!(true, between_5_and_10.eval(&7)); 82 | //! assert_eq!(false, between_5_and_10.eval(&11)); 83 | //! assert_eq!(false, between_5_and_10.eval(&4)); 84 | //! ``` 85 | //! 86 | //! The `Predicate` trait is pretty simple, the core of it is an 87 | //! implementation of a `eval` function that takes a single argument and 88 | //! returns a `bool`. Implementing a custom `Predicate` still allows all the 89 | //! usual combinators of the `Predicate` trait to work! 90 | //! ```rust 91 | //! use std::fmt; 92 | //! 93 | //! use predicates::prelude::*; 94 | //! 95 | //! struct IsTheAnswer; 96 | //! impl Predicate for IsTheAnswer { 97 | //! fn eval(&self, variable: &i32) -> bool { 98 | //! *variable == 42 99 | //! } 100 | //! } 101 | //! impl predicates::reflection::PredicateReflection for IsTheAnswer {} 102 | //! impl fmt::Display for IsTheAnswer { 103 | //! fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 104 | //! write!(f, "var.is_the_answer()") 105 | //! } 106 | //! } 107 | //! 108 | //! assert_eq!(true, IsTheAnswer.eval(&42)); 109 | //! let almost_the_answer = IsTheAnswer.or(predicate::in_iter(vec![41, 43])); 110 | //! assert_eq!(true, almost_the_answer.eval(&41)); 111 | //! ``` 112 | //! 113 | //! ## Choosing a Predicate 114 | //! 115 | //! General predicates 116 | //! - [`predicate::always`] 117 | //! - [`predicate::never`] 118 | //! - [`predicate::function`] 119 | //! - [`predicate::in_iter`]: Specified value must be in the `Iterator`. 120 | //! - [`predicate::in_iter(...).sort`]: Optimization for repeatedly called predicates. 121 | //! - [`predicate::in_hash`]: Optimization for repeatedly called predicates. 122 | //! - [`predicate::eq`] 123 | //! - [`predicate::float::is_close`]: Use this instead of `eq` for floating point values. 124 | //! - [`predicate::ne`] 125 | //! - [`predicate::ge`] 126 | //! - [`predicate::gt`] 127 | //! - [`predicate::le`] 128 | //! - [`predicate::lt`] 129 | //! - [`predicate::name`]: Improve readability of failure reporting by providing a meaningful name. 130 | //! 131 | //! Combinators 132 | //! - [`pred_a.and(pred_b)`]: Both predicates must succeed. 133 | //! - [`pred_a.or(pred_b)`]: One or both predicates must succeed. 134 | //! - [`pred_a.not()`]: The predicate must fail. 135 | //! 136 | //! `String` predicates 137 | //! - [`predicate::str::is_empty`]: Specified string must be empty 138 | //! - [`str_pred = predicate::path::eq_file(...).utf8`]: Specified string must equal the contents 139 | //! of the given file. 140 | //! - [`predicate::str::diff`]: Same as `eq` except report a diff. See [`DifferencePredicate`] 141 | //! for more features. 142 | //! - [`predicate::str::starts_with`]: Specified string must start with the given needle. 143 | //! - [`predicate::str::ends_with`]: Specified string must end with the given needle. 144 | //! - [`predicate::str::contains`]: Specified string must contain the given needle. 145 | //! - [`predicate::str::contains(...).count`]: Required number of times the needle must show up. 146 | //! - [`predicate::str::is_match`]: Specified string must match the given regex. 147 | //! - [`predicate::str::is_match(...).count`]: Required number of times the match must show up. 148 | //! - [`str_pred.trim`]: Trim whitespace before passing it to `str_pred`. 149 | //! - [`str_pred.normalize`]: Normalize the line endings before passing it to `str_pred`. 150 | //! - [`bytes_pred = str_pred.from_utf8()`]: Reuse string predicates in other contexts, like the 151 | //! file system. 152 | //! 153 | //! File system predicates 154 | //! - [`predicate::path::exists`]: Specified path must exist on disk. 155 | //! - [`predicate::path::missing`]: Specified path must not exist on disk. 156 | //! - [`predicate::path::is_dir`]: Specified path is a directory. 157 | //! - [`predicate::path::is_file`]: Specified path is a file. 158 | //! - [`predicate::path::is_symlink`]: Specified path is a symlink. 159 | //! - [`path_pred = predicate::path::eq_file`]: Specified path's contents must equal the contents of the given 160 | //! file. 161 | //! - [`path_pred = bytes_pred.from_file_path`]: Specified path's contents must equal the `bytes_pred`. 162 | //! 163 | //! [`DifferencePredicate`]: crate::str::DifferencePredicate 164 | //! [`bytes_pred = str_pred.from_utf8()`]: prelude::PredicateStrExt::from_utf8() 165 | //! [`path_pred = bytes_pred.from_file_path`]: prelude::PredicateFileContentExt::from_file_path() 166 | //! [`path_pred = predicate::path::eq_file`]: prelude::predicate::path::eq_file() 167 | //! [`pred_a.and(pred_b)`]: boolean::PredicateBooleanExt::and() 168 | //! [`pred_a.not()`]: boolean::PredicateBooleanExt::not() 169 | //! [`pred_a.or(pred_b)`]: boolean::PredicateBooleanExt::or() 170 | //! [`predicate::always`]: constant::always() 171 | //! [`predicate::eq`]: ord::eq() 172 | //! [`predicate::float::is_close`]: prelude::predicate::float::is_close() 173 | //! [`predicate::function`]: function::function() 174 | //! [`predicate::ge`]: ord::ge() 175 | //! [`predicate::gt`]: ord::gt() 176 | //! [`predicate::in_hash`]: iter::in_hash() 177 | //! [`predicate::in_iter(...).sort`]: iter::InPredicate::sort() 178 | //! [`predicate::in_iter`]: iter::in_iter() 179 | //! [`predicate::le`]: ord::le() 180 | //! [`predicate::lt`]: ord::lt() 181 | //! [`predicate::name`]: name::PredicateNameExt::name() 182 | //! [`predicate::ne`]: ord::ne() 183 | //! [`predicate::never`]: constant::never() 184 | //! [`predicate::path::exists`]: prelude::predicate::path::exists() 185 | //! [`predicate::path::is_dir`]: prelude::predicate::path::is_dir() 186 | //! [`predicate::path::is_file`]: prelude::predicate::path::is_file() 187 | //! [`predicate::path::is_symlink`]: prelude::predicate::path::is_symlink() 188 | //! [`predicate::path::missing`]: prelude::predicate::path::missing() 189 | //! [`predicate::str::contains(...).count`]: str::ContainsPredicate::count() 190 | //! [`predicate::str::contains`]: prelude::predicate::str::contains() 191 | //! [`predicate::str::diff`]: prelude::predicate::str::diff() 192 | //! [`predicate::str::ends_with`]: prelude::predicate::str::ends_with() 193 | //! [`predicate::str::is_empty`]: prelude::predicate::str::is_empty() 194 | //! [`predicate::str::is_match(...).count`]: str::RegexPredicate::count() 195 | //! [`predicate::str::is_match`]: prelude::predicate::str::is_match() 196 | //! [`predicate::str::starts_with`]: prelude::predicate::str::starts_with() 197 | //! [`str_pred = predicate::path::eq_file(...).utf8`]: path::BinaryFilePredicate::utf8() 198 | //! [`str_pred.normalize`]: prelude::PredicateStrExt::normalize() 199 | //! [`str_pred.trim`]: prelude::PredicateStrExt::trim() 200 | 201 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 202 | #![warn(missing_docs)] 203 | #![warn(clippy::print_stderr)] 204 | #![warn(clippy::print_stdout)] 205 | 206 | pub mod prelude; 207 | 208 | pub use predicates_core::*; 209 | mod boxed; 210 | pub use crate::boxed::*; 211 | 212 | // core predicates 213 | pub mod constant; 214 | pub mod function; 215 | pub mod iter; 216 | pub mod name; 217 | pub mod ord; 218 | 219 | // combinators 220 | pub mod boolean; 221 | 222 | // specialized primitive `Predicate` types 223 | pub mod float; 224 | pub mod path; 225 | pub mod str; 226 | 227 | mod color; 228 | use color::Palette; 229 | mod utils; 230 | -------------------------------------------------------------------------------- /src/name.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Name predicate expressions. 10 | 11 | use std::fmt; 12 | use std::marker::PhantomData; 13 | 14 | use crate::reflection; 15 | use crate::Predicate; 16 | 17 | /// Augment an existing predicate with a name. 18 | /// 19 | /// This is created by the `PredicateNameExt::name` function. 20 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 21 | pub struct NamePredicate 22 | where 23 | M: Predicate, 24 | Item: ?Sized, 25 | { 26 | inner: M, 27 | name: &'static str, 28 | _phantom: PhantomData, 29 | } 30 | 31 | unsafe impl Send for NamePredicate 32 | where 33 | M: Predicate + Send, 34 | Item: ?Sized, 35 | { 36 | } 37 | 38 | unsafe impl Sync for NamePredicate 39 | where 40 | M: Predicate + Sync, 41 | Item: ?Sized, 42 | { 43 | } 44 | 45 | impl Predicate for NamePredicate 46 | where 47 | M: Predicate, 48 | Item: ?Sized, 49 | { 50 | fn eval(&self, item: &Item) -> bool { 51 | self.inner.eval(item) 52 | } 53 | 54 | fn find_case<'a>(&'a self, expected: bool, variable: &Item) -> Option> { 55 | self.inner 56 | .find_case(expected, variable) 57 | .map(|child_case| reflection::Case::new(Some(self), expected).add_child(child_case)) 58 | } 59 | } 60 | 61 | impl reflection::PredicateReflection for NamePredicate 62 | where 63 | M: Predicate, 64 | Item: ?Sized, 65 | { 66 | fn children<'a>(&'a self) -> Box> + 'a> { 67 | let params = vec![reflection::Child::new(self.name, &self.inner)]; 68 | Box::new(params.into_iter()) 69 | } 70 | } 71 | 72 | impl fmt::Display for NamePredicate 73 | where 74 | M: Predicate, 75 | Item: ?Sized, 76 | { 77 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 78 | let palette = crate::Palette::new(f.alternate()); 79 | write!(f, "{}", palette.description(self.name)) 80 | } 81 | } 82 | 83 | /// `Predicate` extension that adds naming predicate expressions. 84 | pub trait PredicateNameExt 85 | where 86 | Self: Predicate, 87 | { 88 | /// Name a predicate expression. 89 | /// 90 | /// # Examples 91 | /// 92 | /// ``` 93 | /// use predicates::prelude::*; 94 | /// 95 | /// let predicate_fn = predicate::str::is_empty().not().name("non-empty"); 96 | /// println!("{}", predicate_fn); 97 | /// ``` 98 | fn name(self, name: &'static str) -> NamePredicate 99 | where 100 | Self: Sized, 101 | { 102 | NamePredicate { 103 | inner: self, 104 | name, 105 | _phantom: PhantomData, 106 | } 107 | } 108 | } 109 | 110 | impl PredicateNameExt for P 111 | where 112 | P: Predicate, 113 | Item: ?Sized, 114 | { 115 | } 116 | -------------------------------------------------------------------------------- /src/ord.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, 2022 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Definition of `Predicate`s for comparisons over `Ord` and `Eq` types. 10 | 11 | use std::fmt; 12 | 13 | use crate::reflection; 14 | use crate::utils; 15 | use crate::Predicate; 16 | 17 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 18 | enum EqOps { 19 | Equal, 20 | NotEqual, 21 | } 22 | 23 | impl fmt::Display for EqOps { 24 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 25 | let op = match *self { 26 | EqOps::Equal => "==", 27 | EqOps::NotEqual => "!=", 28 | }; 29 | write!(f, "{op}") 30 | } 31 | } 32 | 33 | /// Predicate that returns `true` if `variable` matches the pre-defined `Eq` 34 | /// value, otherwise returns `false`. 35 | /// 36 | /// This is created by the `predicate::{eq, ne}` functions. 37 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 38 | pub struct EqPredicate { 39 | constant: T, 40 | op: EqOps, 41 | } 42 | 43 | impl Predicate

for EqPredicate 44 | where 45 | T: std::borrow::Borrow

+ fmt::Debug, 46 | P: fmt::Debug + PartialEq + ?Sized, 47 | { 48 | fn eval(&self, variable: &P) -> bool { 49 | match self.op { 50 | EqOps::Equal => variable.eq(self.constant.borrow()), 51 | EqOps::NotEqual => variable.ne(self.constant.borrow()), 52 | } 53 | } 54 | 55 | fn find_case<'a>(&'a self, expected: bool, variable: &P) -> Option> { 56 | utils::default_find_case(self, expected, variable).map(|case| { 57 | case.add_product(reflection::Product::new( 58 | "var", 59 | utils::DebugAdapter::new(variable).to_string(), 60 | )) 61 | }) 62 | } 63 | } 64 | 65 | impl reflection::PredicateReflection for EqPredicate where T: fmt::Debug {} 66 | 67 | impl fmt::Display for EqPredicate 68 | where 69 | T: fmt::Debug, 70 | { 71 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 72 | let palette = crate::Palette::new(f.alternate()); 73 | write!( 74 | f, 75 | "{} {} {}", 76 | palette.var("var"), 77 | palette.description(self.op), 78 | palette.expected(utils::DebugAdapter::new(&self.constant)), 79 | ) 80 | } 81 | } 82 | 83 | /// Creates a new predicate that will return `true` when the given `variable` is 84 | /// equal to a pre-defined value. 85 | /// 86 | /// # Examples 87 | /// 88 | /// ``` 89 | /// use predicates::prelude::*; 90 | /// 91 | /// let predicate_fn = predicate::eq(5); 92 | /// assert_eq!(true, predicate_fn.eval(&5)); 93 | /// assert_eq!(false, predicate_fn.eval(&10)); 94 | /// 95 | /// let predicate_fn = predicate::eq("Hello"); 96 | /// assert_eq!(true, predicate_fn.eval("Hello")); 97 | /// assert_eq!(false, predicate_fn.eval("Goodbye")); 98 | /// 99 | /// let predicate_fn = predicate::eq(String::from("Hello")); 100 | /// assert_eq!(true, predicate_fn.eval("Hello")); 101 | /// assert_eq!(false, predicate_fn.eval("Goodbye")); 102 | /// ``` 103 | pub fn eq(constant: T) -> EqPredicate 104 | where 105 | T: fmt::Debug + PartialEq, 106 | { 107 | EqPredicate { 108 | constant, 109 | op: EqOps::Equal, 110 | } 111 | } 112 | 113 | /// Creates a new predicate that will return `true` when the given `variable` is 114 | /// _not_ equal to a pre-defined value. 115 | /// 116 | /// # Examples 117 | /// 118 | /// ``` 119 | /// use predicates::prelude::*; 120 | /// 121 | /// let predicate_fn = predicate::ne(5); 122 | /// assert_eq!(false, predicate_fn.eval(&5)); 123 | /// assert_eq!(true, predicate_fn.eval(&10)); 124 | /// ``` 125 | pub fn ne(constant: T) -> EqPredicate 126 | where 127 | T: PartialEq + fmt::Debug, 128 | { 129 | EqPredicate { 130 | constant, 131 | op: EqOps::NotEqual, 132 | } 133 | } 134 | 135 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 136 | enum OrdOps { 137 | LessThan, 138 | LessThanOrEqual, 139 | GreaterThanOrEqual, 140 | GreaterThan, 141 | } 142 | 143 | impl fmt::Display for OrdOps { 144 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 145 | let op = match *self { 146 | OrdOps::LessThan => "<", 147 | OrdOps::LessThanOrEqual => "<=", 148 | OrdOps::GreaterThanOrEqual => ">=", 149 | OrdOps::GreaterThan => ">", 150 | }; 151 | write!(f, "{op}") 152 | } 153 | } 154 | 155 | /// Predicate that returns `true` if `variable` matches the pre-defined `Ord` 156 | /// value, otherwise returns `false`. 157 | /// 158 | /// This is created by the `predicate::{gt, ge, lt, le}` functions. 159 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 160 | pub struct OrdPredicate { 161 | constant: T, 162 | op: OrdOps, 163 | } 164 | 165 | impl Predicate

for OrdPredicate 166 | where 167 | T: std::borrow::Borrow

+ fmt::Debug, 168 | P: fmt::Debug + PartialOrd + ?Sized, 169 | { 170 | fn eval(&self, variable: &P) -> bool { 171 | match self.op { 172 | OrdOps::LessThan => variable.lt(self.constant.borrow()), 173 | OrdOps::LessThanOrEqual => variable.le(self.constant.borrow()), 174 | OrdOps::GreaterThanOrEqual => variable.ge(self.constant.borrow()), 175 | OrdOps::GreaterThan => variable.gt(self.constant.borrow()), 176 | } 177 | } 178 | 179 | fn find_case<'a>(&'a self, expected: bool, variable: &P) -> Option> { 180 | utils::default_find_case(self, expected, variable).map(|case| { 181 | case.add_product(reflection::Product::new( 182 | "var", 183 | utils::DebugAdapter::new(variable).to_string(), 184 | )) 185 | }) 186 | } 187 | } 188 | 189 | impl reflection::PredicateReflection for OrdPredicate where T: fmt::Debug {} 190 | 191 | impl fmt::Display for OrdPredicate 192 | where 193 | T: fmt::Debug, 194 | { 195 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 196 | let palette = crate::Palette::new(f.alternate()); 197 | write!( 198 | f, 199 | "{} {} {}", 200 | palette.var("var"), 201 | palette.description(self.op), 202 | palette.expected(utils::DebugAdapter::new(&self.constant)), 203 | ) 204 | } 205 | } 206 | 207 | /// Creates a new predicate that will return `true` when the given `variable` is 208 | /// less than a pre-defined value. 209 | /// 210 | /// # Examples 211 | /// 212 | /// ``` 213 | /// use predicates::prelude::*; 214 | /// 215 | /// let predicate_fn = predicate::lt(5); 216 | /// assert_eq!(true, predicate_fn.eval(&4)); 217 | /// assert_eq!(false, predicate_fn.eval(&6)); 218 | /// 219 | /// let predicate_fn = predicate::lt("b"); 220 | /// assert_eq!(true, predicate_fn.eval("a")); 221 | /// assert_eq!(false, predicate_fn.eval("c")); 222 | /// 223 | /// let predicate_fn = predicate::lt(String::from("b")); 224 | /// assert_eq!(true, predicate_fn.eval("a")); 225 | /// assert_eq!(false, predicate_fn.eval("c")); 226 | /// ``` 227 | pub fn lt(constant: T) -> OrdPredicate 228 | where 229 | T: fmt::Debug + PartialOrd, 230 | { 231 | OrdPredicate { 232 | constant, 233 | op: OrdOps::LessThan, 234 | } 235 | } 236 | 237 | /// Creates a new predicate that will return `true` when the given `variable` is 238 | /// less than or equal to a pre-defined value. 239 | /// 240 | /// # Examples 241 | /// 242 | /// ``` 243 | /// use predicates::prelude::*; 244 | /// 245 | /// let predicate_fn = predicate::le(5); 246 | /// assert_eq!(true, predicate_fn.eval(&4)); 247 | /// assert_eq!(true, predicate_fn.eval(&5)); 248 | /// assert_eq!(false, predicate_fn.eval(&6)); 249 | /// ``` 250 | pub fn le(constant: T) -> OrdPredicate 251 | where 252 | T: PartialOrd + fmt::Debug, 253 | { 254 | OrdPredicate { 255 | constant, 256 | op: OrdOps::LessThanOrEqual, 257 | } 258 | } 259 | 260 | /// Creates a new predicate that will return `true` when the given `variable` is 261 | /// greater than or equal to a pre-defined value. 262 | /// 263 | /// # Examples 264 | /// 265 | /// ``` 266 | /// use predicates::prelude::*; 267 | /// 268 | /// let predicate = predicate::ge(5); 269 | /// assert_eq!(false, predicate.eval(&4)); 270 | /// assert_eq!(true, predicate.eval(&5)); 271 | /// assert_eq!(true, predicate.eval(&6)); 272 | /// ``` 273 | pub fn ge(constant: T) -> OrdPredicate 274 | where 275 | T: PartialOrd + fmt::Debug, 276 | { 277 | OrdPredicate { 278 | constant, 279 | op: OrdOps::GreaterThanOrEqual, 280 | } 281 | } 282 | 283 | /// Creates a new predicate that will return `true` when the given `variable` is 284 | /// greater than a pre-defined value. 285 | /// 286 | /// # Examples 287 | /// 288 | /// ``` 289 | /// use predicates::prelude::*; 290 | /// 291 | /// let predicate_fn = predicate::gt(5); 292 | /// assert_eq!(false, predicate_fn.eval(&4)); 293 | /// assert_eq!(false, predicate_fn.eval(&5)); 294 | /// assert_eq!(true, predicate_fn.eval(&6)); 295 | /// ``` 296 | pub fn gt(constant: T) -> OrdPredicate 297 | where 298 | T: PartialOrd + fmt::Debug, 299 | { 300 | OrdPredicate { 301 | constant, 302 | op: OrdOps::GreaterThan, 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /src/path/existence.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use std::fmt; 10 | use std::path; 11 | 12 | use crate::reflection; 13 | use crate::utils; 14 | use crate::Predicate; 15 | 16 | /// Predicate that checks if a file is present 17 | /// 18 | /// This is created by the `predicate::path::exists` and `predicate::path::missing`. 19 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 20 | pub struct ExistencePredicate { 21 | exists: bool, 22 | } 23 | 24 | impl Predicate for ExistencePredicate { 25 | fn eval(&self, path: &path::Path) -> bool { 26 | path.exists() == self.exists 27 | } 28 | 29 | fn find_case<'a>( 30 | &'a self, 31 | expected: bool, 32 | variable: &path::Path, 33 | ) -> Option> { 34 | utils::default_find_case(self, expected, variable).map(|case| { 35 | case.add_product(reflection::Product::new( 36 | "var", 37 | variable.display().to_string(), 38 | )) 39 | }) 40 | } 41 | } 42 | 43 | impl reflection::PredicateReflection for ExistencePredicate {} 44 | 45 | impl fmt::Display for ExistencePredicate { 46 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 47 | let palette = crate::Palette::new(f.alternate()); 48 | write!( 49 | f, 50 | "{}({})", 51 | palette.description(if self.exists { "exists" } else { "missing" }), 52 | palette.var("var") 53 | ) 54 | } 55 | } 56 | 57 | /// Creates a new `Predicate` that ensures the path exists. 58 | /// 59 | /// # Examples 60 | /// 61 | /// ``` 62 | /// use std::path::Path; 63 | /// use predicates::prelude::*; 64 | /// 65 | /// let predicate_fn = predicate::path::exists(); 66 | /// assert_eq!(true, predicate_fn.eval(Path::new("Cargo.toml"))); 67 | /// ``` 68 | pub fn exists() -> ExistencePredicate { 69 | ExistencePredicate { exists: true } 70 | } 71 | 72 | /// Creates a new `Predicate` that ensures the path doesn't exist. 73 | /// 74 | /// # Examples 75 | /// 76 | /// ``` 77 | /// use std::path::Path; 78 | /// use predicates::prelude::*; 79 | /// 80 | /// let predicate_fn = predicate::path::missing(); 81 | /// assert_eq!(true, predicate_fn.eval(Path::new("non-existent-file.foo"))); 82 | /// ``` 83 | pub fn missing() -> ExistencePredicate { 84 | ExistencePredicate { exists: false } 85 | } 86 | -------------------------------------------------------------------------------- /src/path/fc.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use std::fmt; 10 | use std::fs; 11 | use std::io::{self, Read}; 12 | use std::path; 13 | 14 | use crate::reflection; 15 | use crate::Predicate; 16 | 17 | fn read_file(path: &path::Path) -> io::Result> { 18 | let mut buffer = Vec::new(); 19 | fs::File::open(path)?.read_to_end(&mut buffer)?; 20 | Ok(buffer) 21 | } 22 | 23 | /// Predicate adapter that converts a `path` predicate to a byte predicate on its content. 24 | /// 25 | /// This is created by `pred.from_path()`. 26 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 27 | pub struct FileContentPredicate

28 | where 29 | P: Predicate<[u8]>, 30 | { 31 | p: P, 32 | } 33 | 34 | impl

FileContentPredicate

35 | where 36 | P: Predicate<[u8]>, 37 | { 38 | fn eval(&self, path: &path::Path) -> io::Result { 39 | let buffer = read_file(path)?; 40 | Ok(self.p.eval(&buffer)) 41 | } 42 | } 43 | 44 | impl

reflection::PredicateReflection for FileContentPredicate

45 | where 46 | P: Predicate<[u8]>, 47 | { 48 | fn children<'a>(&'a self) -> Box> + 'a> { 49 | let params = vec![reflection::Child::new("predicate", &self.p)]; 50 | Box::new(params.into_iter()) 51 | } 52 | } 53 | 54 | impl

fmt::Display for FileContentPredicate

55 | where 56 | P: Predicate<[u8]>, 57 | { 58 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 59 | self.p.fmt(f) 60 | } 61 | } 62 | 63 | impl

Predicate for FileContentPredicate

64 | where 65 | P: Predicate<[u8]>, 66 | { 67 | fn eval(&self, path: &path::Path) -> bool { 68 | self.eval(path).unwrap_or(false) 69 | } 70 | 71 | fn find_case<'a>( 72 | &'a self, 73 | expected: bool, 74 | variable: &path::Path, 75 | ) -> Option> { 76 | let buffer = read_file(variable); 77 | match (expected, buffer) { 78 | (_, Ok(buffer)) => self.p.find_case(expected, &buffer).map(|case| { 79 | case.add_product(reflection::Product::new( 80 | "var", 81 | variable.display().to_string(), 82 | )) 83 | }), 84 | (true, Err(_)) => None, 85 | (false, Err(err)) => Some( 86 | reflection::Case::new(Some(self), false) 87 | .add_product(reflection::Product::new( 88 | "var", 89 | variable.display().to_string(), 90 | )) 91 | .add_product(reflection::Product::new("error", err)), 92 | ), 93 | } 94 | } 95 | } 96 | 97 | /// `Predicate` extension adapting a `slice` Predicate. 98 | pub trait PredicateFileContentExt 99 | where 100 | Self: Predicate<[u8]>, 101 | Self: Sized, 102 | { 103 | /// Returns a `FileContentPredicate` that adapts `Self` to a file content `Predicate`. 104 | /// 105 | /// # Examples 106 | /// 107 | /// ``` 108 | /// use predicates::prelude::*; 109 | /// use std::path::Path; 110 | /// 111 | /// let predicate_fn = predicate::str::is_empty().not().from_utf8().from_file_path(); 112 | /// assert_eq!(true, predicate_fn.eval(Path::new("./tests/hello_world"))); 113 | /// assert_eq!(false, predicate_fn.eval(Path::new("./tests/empty_file"))); 114 | /// ``` 115 | #[allow(clippy::wrong_self_convention)] 116 | fn from_file_path(self) -> FileContentPredicate { 117 | FileContentPredicate { p: self } 118 | } 119 | } 120 | 121 | impl

PredicateFileContentExt for P where P: Predicate<[u8]> {} 122 | -------------------------------------------------------------------------------- /src/path/fs.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use std::fmt; 10 | use std::fs; 11 | use std::io::{self, Read}; 12 | use std::path; 13 | 14 | use crate::reflection; 15 | use crate::utils; 16 | use crate::Predicate; 17 | 18 | fn read_file(path: &path::Path) -> io::Result> { 19 | let mut buffer = Vec::new(); 20 | fs::File::open(path)?.read_to_end(&mut buffer)?; 21 | Ok(buffer) 22 | } 23 | 24 | /// Predicate that compares file matches 25 | #[derive(Debug, Clone, PartialEq, Eq)] 26 | pub struct BinaryFilePredicate { 27 | path: path::PathBuf, 28 | content: utils::DebugAdapter>, 29 | } 30 | 31 | impl BinaryFilePredicate { 32 | fn eval(&self, path: &path::Path) -> io::Result { 33 | let content = read_file(path)?; 34 | Ok(self.content.debug == content) 35 | } 36 | 37 | /// Creates a new `Predicate` that ensures complete equality 38 | /// 39 | /// # Examples 40 | /// 41 | /// ``` 42 | /// use std::path::Path; 43 | /// use predicates::prelude::*; 44 | /// 45 | /// let predicate_file = predicate::path::eq_file(Path::new("Cargo.toml")).utf8().unwrap(); 46 | /// assert_eq!(true, predicate_file.eval(Path::new("Cargo.toml"))); 47 | /// assert_eq!(false, predicate_file.eval(Path::new("Cargo.lock"))); 48 | /// assert_eq!(false, predicate_file.eval(Path::new("src"))); 49 | /// 50 | /// assert_eq!(false, predicate_file.eval("Not a real Cargo.toml file content")); 51 | /// ``` 52 | pub fn utf8(self) -> Option { 53 | let path = self.path; 54 | let content = String::from_utf8(self.content.debug).ok()?; 55 | Some(StrFilePredicate { path, content }) 56 | } 57 | } 58 | 59 | impl Predicate for BinaryFilePredicate { 60 | fn eval(&self, path: &path::Path) -> bool { 61 | self.eval(path).unwrap_or(false) 62 | } 63 | 64 | fn find_case<'a>( 65 | &'a self, 66 | expected: bool, 67 | variable: &path::Path, 68 | ) -> Option> { 69 | utils::default_find_case(self, expected, variable) 70 | } 71 | } 72 | 73 | impl Predicate<[u8]> for BinaryFilePredicate { 74 | fn eval(&self, actual: &[u8]) -> bool { 75 | self.content.debug == actual 76 | } 77 | 78 | fn find_case<'a>(&'a self, expected: bool, variable: &[u8]) -> Option> { 79 | utils::default_find_case(self, expected, variable) 80 | } 81 | } 82 | 83 | impl reflection::PredicateReflection for BinaryFilePredicate { 84 | fn parameters<'a>(&'a self) -> Box> + 'a> { 85 | let params = vec![reflection::Parameter::new("content", &self.content)]; 86 | Box::new(params.into_iter()) 87 | } 88 | } 89 | 90 | impl fmt::Display for BinaryFilePredicate { 91 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 92 | let palette = crate::Palette::new(f.alternate()); 93 | write!( 94 | f, 95 | "{} {} {}", 96 | palette.var("var"), 97 | palette.description("is"), 98 | palette.expected(self.path.display()) 99 | ) 100 | } 101 | } 102 | 103 | /// Creates a new `Predicate` that ensures complete equality 104 | /// 105 | /// # Examples 106 | /// 107 | /// ``` 108 | /// use std::path::Path; 109 | /// use predicates::prelude::*; 110 | /// 111 | /// let predicate_file = predicate::path::eq_file(Path::new("Cargo.toml")); 112 | /// assert_eq!(true, predicate_file.eval(Path::new("Cargo.toml"))); 113 | /// assert_eq!(false, predicate_file.eval(Path::new("src"))); 114 | /// assert_eq!(false, predicate_file.eval(Path::new("src"))); 115 | /// ``` 116 | pub fn eq_file>(path: P) -> BinaryFilePredicate { 117 | let path = path.into(); 118 | let content = utils::DebugAdapter::new(read_file(&path).unwrap()); 119 | BinaryFilePredicate { path, content } 120 | } 121 | 122 | /// Predicate that compares string content of files 123 | #[derive(Debug, Clone, PartialEq, Eq)] 124 | pub struct StrFilePredicate { 125 | path: path::PathBuf, 126 | content: String, 127 | } 128 | 129 | impl StrFilePredicate { 130 | fn eval(&self, path: &path::Path) -> Option { 131 | let content = read_file(path).ok()?; 132 | let content = String::from_utf8(content).ok()?; 133 | Some(self.content == content) 134 | } 135 | } 136 | 137 | impl Predicate for StrFilePredicate { 138 | fn eval(&self, path: &path::Path) -> bool { 139 | self.eval(path).unwrap_or(false) 140 | } 141 | 142 | fn find_case<'a>( 143 | &'a self, 144 | expected: bool, 145 | variable: &path::Path, 146 | ) -> Option> { 147 | utils::default_find_case(self, expected, variable) 148 | } 149 | } 150 | 151 | impl Predicate for StrFilePredicate { 152 | fn eval(&self, actual: &str) -> bool { 153 | self.content == actual 154 | } 155 | 156 | fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { 157 | utils::default_find_case(self, expected, variable) 158 | } 159 | } 160 | 161 | impl reflection::PredicateReflection for StrFilePredicate { 162 | fn parameters<'a>(&'a self) -> Box> + 'a> { 163 | let params = vec![reflection::Parameter::new("content", &self.content)]; 164 | Box::new(params.into_iter()) 165 | } 166 | } 167 | 168 | impl fmt::Display for StrFilePredicate { 169 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 170 | let palette = crate::Palette::new(f.alternate()); 171 | write!( 172 | f, 173 | "{} {} {}", 174 | palette.var("var"), 175 | palette.description("is"), 176 | palette.expected(self.path.display()) 177 | ) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/path/ft.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use std::fmt; 10 | use std::fs; 11 | use std::io; 12 | use std::path; 13 | 14 | use crate::reflection; 15 | use crate::Predicate; 16 | 17 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 18 | enum FileType { 19 | File, 20 | Dir, 21 | Symlink, 22 | } 23 | 24 | impl FileType { 25 | fn from_path(path: &path::Path, follow: bool) -> io::Result { 26 | let file_type = if follow { 27 | path.metadata() 28 | } else { 29 | path.symlink_metadata() 30 | }? 31 | .file_type(); 32 | if file_type.is_dir() { 33 | return Ok(FileType::Dir); 34 | } 35 | if file_type.is_file() { 36 | return Ok(FileType::File); 37 | } 38 | Ok(FileType::Symlink) 39 | } 40 | 41 | fn eval(self, ft: fs::FileType) -> bool { 42 | match self { 43 | FileType::File => ft.is_file(), 44 | FileType::Dir => ft.is_dir(), 45 | FileType::Symlink => ft.is_symlink(), 46 | } 47 | } 48 | } 49 | 50 | impl fmt::Display for FileType { 51 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 52 | let t = match *self { 53 | FileType::File => "file", 54 | FileType::Dir => "dir", 55 | FileType::Symlink => "symlink", 56 | }; 57 | write!(f, "{t}") 58 | } 59 | } 60 | 61 | /// Predicate that checks the `std::fs::FileType`. 62 | /// 63 | /// This is created by the `predicate::path::is_file`, `predicate::path::is_dir`, and `predicate::path::is_symlink`. 64 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 65 | pub struct FileTypePredicate { 66 | ft: FileType, 67 | follow: bool, 68 | } 69 | 70 | impl FileTypePredicate { 71 | /// Follow symbolic links. 72 | /// 73 | /// When yes is true, symbolic links are followed as if they were normal directories and files. 74 | /// 75 | /// Default: disabled. 76 | pub fn follow_links(mut self, yes: bool) -> Self { 77 | self.follow = yes; 78 | self 79 | } 80 | 81 | /// Allow to create an `FileTypePredicate` from a `path` 82 | pub fn from_path(path: &path::Path) -> io::Result { 83 | Ok(FileTypePredicate { 84 | ft: FileType::from_path(path, true)?, 85 | follow: true, 86 | }) 87 | } 88 | } 89 | 90 | impl Predicate for FileTypePredicate { 91 | fn eval(&self, path: &path::Path) -> bool { 92 | let metadata = if self.follow { 93 | path.metadata() 94 | } else { 95 | path.symlink_metadata() 96 | }; 97 | metadata 98 | .map(|m| self.ft.eval(m.file_type())) 99 | .unwrap_or(false) 100 | } 101 | 102 | fn find_case<'a>( 103 | &'a self, 104 | expected: bool, 105 | variable: &path::Path, 106 | ) -> Option> { 107 | let actual_type = FileType::from_path(variable, self.follow); 108 | match (expected, actual_type) { 109 | (_, Ok(actual_type)) => { 110 | let result = self.ft == actual_type; 111 | if result == expected { 112 | Some( 113 | reflection::Case::new(Some(self), result) 114 | .add_product(reflection::Product::new("actual filetype", actual_type)), 115 | ) 116 | } else { 117 | None 118 | } 119 | } 120 | (true, Err(_)) => None, 121 | (false, Err(err)) => Some( 122 | reflection::Case::new(Some(self), false) 123 | .add_product(reflection::Product::new("error", err)), 124 | ), 125 | } 126 | } 127 | } 128 | 129 | impl reflection::PredicateReflection for FileTypePredicate { 130 | fn parameters<'a>(&'a self) -> Box> + 'a> { 131 | let params = vec![reflection::Parameter::new("follow", &self.follow)]; 132 | Box::new(params.into_iter()) 133 | } 134 | } 135 | 136 | impl fmt::Display for FileTypePredicate { 137 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 138 | let palette = crate::Palette::new(f.alternate()); 139 | write!( 140 | f, 141 | "{} {} {}", 142 | palette.var("var"), 143 | palette.description("is"), 144 | palette.expected(self.ft) 145 | ) 146 | } 147 | } 148 | 149 | /// Creates a new `Predicate` that ensures the path points to a file. 150 | /// 151 | /// # Examples 152 | /// 153 | /// ``` 154 | /// use std::path::Path; 155 | /// use predicates::prelude::*; 156 | /// 157 | /// let predicate_fn = predicate::path::is_file(); 158 | /// assert_eq!(true, predicate_fn.eval(Path::new("Cargo.toml"))); 159 | /// assert_eq!(false, predicate_fn.eval(Path::new("src"))); 160 | /// assert_eq!(false, predicate_fn.eval(Path::new("non-existent-file.foo"))); 161 | /// ``` 162 | pub fn is_file() -> FileTypePredicate { 163 | FileTypePredicate { 164 | ft: FileType::File, 165 | follow: false, 166 | } 167 | } 168 | 169 | /// Creates a new `Predicate` that ensures the path points to a directory. 170 | /// 171 | /// # Examples 172 | /// 173 | /// ``` 174 | /// use std::path::Path; 175 | /// use predicates::prelude::*; 176 | /// 177 | /// let predicate_fn = predicate::path::is_dir(); 178 | /// assert_eq!(false, predicate_fn.eval(Path::new("Cargo.toml"))); 179 | /// assert_eq!(true, predicate_fn.eval(Path::new("src"))); 180 | /// assert_eq!(false, predicate_fn.eval(Path::new("non-existent-file.foo"))); 181 | /// ``` 182 | pub fn is_dir() -> FileTypePredicate { 183 | FileTypePredicate { 184 | ft: FileType::Dir, 185 | follow: false, 186 | } 187 | } 188 | 189 | /// Creates a new `Predicate` that ensures the path points to a symlink. 190 | /// 191 | /// # Examples 192 | /// 193 | /// ``` 194 | /// use std::path::Path; 195 | /// use predicates::prelude::*; 196 | /// 197 | /// let predicate_fn = predicate::path::is_symlink(); 198 | /// assert_eq!(false, predicate_fn.eval(Path::new("Cargo.toml"))); 199 | /// assert_eq!(false, predicate_fn.eval(Path::new("src"))); 200 | /// assert_eq!(false, predicate_fn.eval(Path::new("non-existent-file.foo"))); 201 | /// ``` 202 | pub fn is_symlink() -> FileTypePredicate { 203 | FileTypePredicate { 204 | ft: FileType::Symlink, 205 | follow: false, 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/path/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Path Predicates 10 | //! 11 | //! This module contains predicates specific to the file system. 12 | 13 | mod existence; 14 | pub use self::existence::{exists, missing, ExistencePredicate}; 15 | mod ft; 16 | pub use self::ft::{is_dir, is_file, is_symlink, FileTypePredicate}; 17 | mod fc; 18 | pub use self::fc::{FileContentPredicate, PredicateFileContentExt}; 19 | mod fs; 20 | pub use self::fs::{eq_file, BinaryFilePredicate, StrFilePredicate}; 21 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Module that contains the essentials for working with predicates. 10 | 11 | pub use crate::boolean::PredicateBooleanExt; 12 | pub use crate::boxed::PredicateBoxExt; 13 | pub use crate::name::PredicateNameExt; 14 | pub use crate::path::PredicateFileContentExt; 15 | pub use crate::str::PredicateStrExt; 16 | pub use crate::Predicate; 17 | 18 | /// Predicate factories 19 | pub mod predicate { 20 | // primitive `Predicate` types 21 | pub use crate::constant::{always, never}; 22 | pub use crate::function::function; 23 | pub use crate::iter::{in_hash, in_iter}; 24 | pub use crate::ord::{eq, ge, gt, le, lt, ne}; 25 | 26 | /// `str` Predicate factories 27 | /// 28 | /// This module contains predicates specific to string handling. 29 | pub mod str { 30 | pub use crate::str::is_empty; 31 | pub use crate::str::{contains, ends_with, starts_with}; 32 | 33 | #[cfg(feature = "diff")] 34 | pub use crate::str::diff; 35 | 36 | #[cfg(feature = "regex")] 37 | pub use crate::str::is_match; 38 | } 39 | 40 | /// `Path` Predicate factories 41 | /// 42 | /// This module contains predicates specific to path handling. 43 | pub mod path { 44 | pub use crate::path::eq_file; 45 | pub use crate::path::{exists, missing}; 46 | pub use crate::path::{is_dir, is_file, is_symlink}; 47 | } 48 | 49 | /// `f64` Predicate factories 50 | /// 51 | /// This module contains predicates specific to float handling. 52 | pub mod float { 53 | #[cfg(feature = "float-cmp")] 54 | pub use crate::float::is_close; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/str/adapters.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use std::ffi; 10 | use std::fmt; 11 | use std::str; 12 | 13 | use crate::reflection; 14 | #[cfg(feature = "normalize-line-endings")] 15 | use crate::str::normalize::NormalizedPredicate; 16 | use crate::Predicate; 17 | 18 | /// Predicate adapter that trims the variable being tested. 19 | /// 20 | /// This is created by `pred.trim()`. 21 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 22 | pub struct TrimPredicate

23 | where 24 | P: Predicate, 25 | { 26 | p: P, 27 | } 28 | 29 | impl

Predicate for TrimPredicate

30 | where 31 | P: Predicate, 32 | { 33 | fn eval(&self, variable: &str) -> bool { 34 | self.p.eval(variable.trim()) 35 | } 36 | 37 | fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { 38 | self.p.find_case(expected, variable.trim()) 39 | } 40 | } 41 | 42 | impl

reflection::PredicateReflection for TrimPredicate

43 | where 44 | P: Predicate, 45 | { 46 | fn children<'a>(&'a self) -> Box> + 'a> { 47 | let params = vec![reflection::Child::new("predicate", &self.p)]; 48 | Box::new(params.into_iter()) 49 | } 50 | } 51 | 52 | impl

fmt::Display for TrimPredicate

53 | where 54 | P: Predicate, 55 | { 56 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 57 | self.p.fmt(f) 58 | } 59 | } 60 | 61 | /// Predicate adapter that converts a `str` predicate to byte predicate. 62 | /// 63 | /// This is created by `pred.from_utf8()`. 64 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 65 | pub struct Utf8Predicate

66 | where 67 | P: Predicate, 68 | { 69 | p: P, 70 | } 71 | 72 | impl

Predicate for Utf8Predicate

73 | where 74 | P: Predicate, 75 | { 76 | fn eval(&self, variable: &ffi::OsStr) -> bool { 77 | variable.to_str().map(|s| self.p.eval(s)).unwrap_or(false) 78 | } 79 | 80 | fn find_case<'a>( 81 | &'a self, 82 | expected: bool, 83 | variable: &ffi::OsStr, 84 | ) -> Option> { 85 | let var_str = variable.to_str(); 86 | match (expected, var_str) { 87 | (_, Some(var_str)) => self.p.find_case(expected, var_str).map(|child| { 88 | child.add_product(reflection::Product::new("var as str", var_str.to_owned())) 89 | }), 90 | (true, None) => None, 91 | (false, None) => Some( 92 | reflection::Case::new(Some(self), false) 93 | .add_product(reflection::Product::new("error", "Invalid UTF-8 string")), 94 | ), 95 | } 96 | } 97 | } 98 | 99 | impl

Predicate<[u8]> for Utf8Predicate

100 | where 101 | P: Predicate, 102 | { 103 | fn eval(&self, variable: &[u8]) -> bool { 104 | str::from_utf8(variable) 105 | .map(|s| self.p.eval(s)) 106 | .unwrap_or(false) 107 | } 108 | 109 | fn find_case<'a>(&'a self, expected: bool, variable: &[u8]) -> Option> { 110 | let var_str = str::from_utf8(variable); 111 | match (expected, var_str) { 112 | (_, Ok(var_str)) => self.p.find_case(expected, var_str).map(|child| { 113 | child.add_product(reflection::Product::new("var as str", var_str.to_owned())) 114 | }), 115 | (true, Err(_)) => None, 116 | (false, Err(err)) => Some( 117 | reflection::Case::new(Some(self), false) 118 | .add_product(reflection::Product::new("error", err)), 119 | ), 120 | } 121 | } 122 | } 123 | 124 | impl

reflection::PredicateReflection for Utf8Predicate

125 | where 126 | P: Predicate, 127 | { 128 | fn children<'a>(&'a self) -> Box> + 'a> { 129 | let params = vec![reflection::Child::new("predicate", &self.p)]; 130 | Box::new(params.into_iter()) 131 | } 132 | } 133 | 134 | impl

fmt::Display for Utf8Predicate

135 | where 136 | P: Predicate, 137 | { 138 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 139 | self.p.fmt(f) 140 | } 141 | } 142 | 143 | /// `Predicate` extension adapting a `str` Predicate. 144 | pub trait PredicateStrExt 145 | where 146 | Self: Predicate, 147 | Self: Sized, 148 | { 149 | /// Returns a `TrimPredicate` that ensures the data passed to `Self` is trimmed. 150 | /// 151 | /// # Examples 152 | /// 153 | /// ``` 154 | /// use predicates::prelude::*; 155 | /// 156 | /// let predicate_fn = predicate::str::is_empty().trim(); 157 | /// assert_eq!(true, predicate_fn.eval(" ")); 158 | /// assert_eq!(false, predicate_fn.eval(" Hello ")); 159 | /// ``` 160 | fn trim(self) -> TrimPredicate { 161 | TrimPredicate { p: self } 162 | } 163 | 164 | /// Returns a `Utf8Predicate` that adapts `Self` to a `[u8]` `Predicate`. 165 | /// 166 | /// # Examples 167 | /// 168 | /// ``` 169 | /// use predicates::prelude::*; 170 | /// use std::ffi::OsStr; 171 | /// 172 | /// let predicate_fn = predicate::str::is_empty().not().from_utf8(); 173 | /// assert_eq!(true, predicate_fn.eval(OsStr::new("Hello"))); 174 | /// assert_eq!(false, predicate_fn.eval(OsStr::new(""))); 175 | /// let variable: &[u8] = b""; 176 | /// assert_eq!(false, predicate_fn.eval(variable)); 177 | /// ``` 178 | #[allow(clippy::wrong_self_convention)] 179 | fn from_utf8(self) -> Utf8Predicate { 180 | Utf8Predicate { p: self } 181 | } 182 | 183 | /// Returns a `NormalizedPredicate` that ensures 184 | /// the newlines within the data passed to `Self` is normalised. 185 | /// 186 | /// # Examples 187 | /// 188 | /// ``` 189 | /// use predicates::prelude::*; 190 | /// 191 | /// let predicate_fn = predicate::eq("Hello World!\n").normalize(); 192 | /// assert_eq!(true, predicate_fn.eval("Hello World!\n")); 193 | /// assert_eq!(true, predicate_fn.eval("Hello World!\r")); 194 | /// assert_eq!(true, predicate_fn.eval("Hello World!\r\n")); 195 | /// assert_eq!(false, predicate_fn.eval("Goodbye")); 196 | /// ``` 197 | /// 198 | #[cfg(feature = "normalize-line-endings")] 199 | fn normalize(self) -> NormalizedPredicate { 200 | NormalizedPredicate { p: self } 201 | } 202 | } 203 | 204 | impl

PredicateStrExt for P where P: Predicate {} 205 | -------------------------------------------------------------------------------- /src/str/basics.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use std::fmt; 10 | 11 | use crate::reflection; 12 | use crate::utils; 13 | use crate::Predicate; 14 | 15 | /// Predicate that checks for empty strings. 16 | /// 17 | /// This is created by `predicates::str::is_empty`. 18 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 19 | pub struct IsEmptyPredicate {} 20 | 21 | impl Predicate for IsEmptyPredicate { 22 | fn eval(&self, variable: &str) -> bool { 23 | variable.is_empty() 24 | } 25 | 26 | fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { 27 | utils::default_find_case(self, expected, variable) 28 | .map(|case| case.add_product(reflection::Product::new("var", variable.to_owned()))) 29 | } 30 | } 31 | 32 | impl reflection::PredicateReflection for IsEmptyPredicate {} 33 | 34 | impl fmt::Display for IsEmptyPredicate { 35 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 36 | let palette = crate::Palette::new(f.alternate()); 37 | write!( 38 | f, 39 | "{}.{}()", 40 | palette.var("var"), 41 | palette.description("is_empty"), 42 | ) 43 | } 44 | } 45 | 46 | /// Creates a new `Predicate` that ensures a str is empty 47 | /// 48 | /// # Examples 49 | /// 50 | /// ``` 51 | /// use predicates::prelude::*; 52 | /// 53 | /// let predicate_fn = predicate::str::is_empty(); 54 | /// assert_eq!(true, predicate_fn.eval("")); 55 | /// assert_eq!(false, predicate_fn.eval("Food World")); 56 | /// ``` 57 | pub fn is_empty() -> IsEmptyPredicate { 58 | IsEmptyPredicate {} 59 | } 60 | 61 | /// Predicate checks start of str 62 | /// 63 | /// This is created by `predicates::str::starts_with`. 64 | #[derive(Debug, Clone, PartialEq, Eq)] 65 | pub struct StartsWithPredicate { 66 | pattern: String, 67 | } 68 | 69 | impl Predicate for StartsWithPredicate { 70 | fn eval(&self, variable: &str) -> bool { 71 | variable.starts_with(&self.pattern) 72 | } 73 | 74 | fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { 75 | utils::default_find_case(self, expected, variable) 76 | .map(|case| case.add_product(reflection::Product::new("var", variable.to_owned()))) 77 | } 78 | } 79 | 80 | impl reflection::PredicateReflection for StartsWithPredicate {} 81 | 82 | impl fmt::Display for StartsWithPredicate { 83 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 84 | let palette = crate::Palette::new(f.alternate()); 85 | write!( 86 | f, 87 | "{}.{}({:?})", 88 | palette.var("var"), 89 | palette.description("starts_with"), 90 | self.pattern 91 | ) 92 | } 93 | } 94 | 95 | /// Creates a new `Predicate` that ensures a str starts with `pattern` 96 | /// 97 | /// # Examples 98 | /// 99 | /// ``` 100 | /// use predicates::prelude::*; 101 | /// 102 | /// let predicate_fn = predicate::str::starts_with("Hello"); 103 | /// assert_eq!(true, predicate_fn.eval("Hello World")); 104 | /// assert_eq!(false, predicate_fn.eval("Goodbye World")); 105 | /// ``` 106 | pub fn starts_with

(pattern: P) -> StartsWithPredicate 107 | where 108 | P: Into, 109 | { 110 | StartsWithPredicate { 111 | pattern: pattern.into(), 112 | } 113 | } 114 | 115 | /// Predicate checks end of str 116 | /// 117 | /// This is created by `predicates::str::ends_with`. 118 | #[derive(Debug, Clone, PartialEq, Eq)] 119 | pub struct EndsWithPredicate { 120 | pattern: String, 121 | } 122 | 123 | impl Predicate for EndsWithPredicate { 124 | fn eval(&self, variable: &str) -> bool { 125 | variable.ends_with(&self.pattern) 126 | } 127 | 128 | fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { 129 | utils::default_find_case(self, expected, variable) 130 | .map(|case| case.add_product(reflection::Product::new("var", variable.to_owned()))) 131 | } 132 | } 133 | 134 | impl reflection::PredicateReflection for EndsWithPredicate {} 135 | 136 | impl fmt::Display for EndsWithPredicate { 137 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 138 | let palette = crate::Palette::new(f.alternate()); 139 | write!( 140 | f, 141 | "{}.{}({:?})", 142 | palette.var("var"), 143 | palette.description("ends_with"), 144 | self.pattern 145 | ) 146 | } 147 | } 148 | 149 | /// Creates a new `Predicate` that ensures a str ends with `pattern` 150 | /// 151 | /// # Examples 152 | /// 153 | /// ``` 154 | /// use predicates::prelude::*; 155 | /// 156 | /// let predicate_fn = predicate::str::ends_with("World"); 157 | /// assert_eq!(true, predicate_fn.eval("Hello World")); 158 | /// assert_eq!(false, predicate_fn.eval("Hello Moon")); 159 | /// ``` 160 | pub fn ends_with

(pattern: P) -> EndsWithPredicate 161 | where 162 | P: Into, 163 | { 164 | EndsWithPredicate { 165 | pattern: pattern.into(), 166 | } 167 | } 168 | 169 | /// Predicate that checks for patterns. 170 | /// 171 | /// This is created by `predicates::str:contains`. 172 | #[derive(Debug, Clone, PartialEq, Eq)] 173 | pub struct ContainsPredicate { 174 | pattern: String, 175 | } 176 | 177 | impl ContainsPredicate { 178 | /// Require a specific count of matches. 179 | /// 180 | /// # Examples 181 | /// 182 | /// ``` 183 | /// use predicates::prelude::*; 184 | /// 185 | /// let predicate_fn = predicate::str::contains("Two").count(2); 186 | /// assert_eq!(true, predicate_fn.eval("One Two Three Two One")); 187 | /// assert_eq!(false, predicate_fn.eval("One Two Three")); 188 | /// ``` 189 | pub fn count(self, count: usize) -> MatchesPredicate { 190 | MatchesPredicate { 191 | pattern: self.pattern, 192 | count, 193 | } 194 | } 195 | } 196 | 197 | impl Predicate for ContainsPredicate { 198 | fn eval(&self, variable: &str) -> bool { 199 | variable.contains(&self.pattern) 200 | } 201 | 202 | fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { 203 | utils::default_find_case(self, expected, variable) 204 | .map(|case| case.add_product(reflection::Product::new("var", variable.to_owned()))) 205 | } 206 | } 207 | 208 | impl reflection::PredicateReflection for ContainsPredicate {} 209 | 210 | impl fmt::Display for ContainsPredicate { 211 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 212 | let palette = crate::Palette::new(f.alternate()); 213 | write!( 214 | f, 215 | "{}.{}({})", 216 | palette.var("var"), 217 | palette.description("contains"), 218 | palette.expected(&self.pattern), 219 | ) 220 | } 221 | } 222 | 223 | /// Predicate that checks for repeated patterns. 224 | /// 225 | /// This is created by `predicates::str::contains(...).count`. 226 | #[derive(Debug, Clone, PartialEq, Eq)] 227 | pub struct MatchesPredicate { 228 | pattern: String, 229 | count: usize, 230 | } 231 | 232 | impl Predicate for MatchesPredicate { 233 | fn eval(&self, variable: &str) -> bool { 234 | variable.matches(&self.pattern).count() == self.count 235 | } 236 | 237 | fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { 238 | let actual_count = variable.matches(&self.pattern).count(); 239 | let result = self.count == actual_count; 240 | if result == expected { 241 | Some( 242 | reflection::Case::new(Some(self), result) 243 | .add_product(reflection::Product::new("var", variable.to_owned())) 244 | .add_product(reflection::Product::new("actual count", actual_count)), 245 | ) 246 | } else { 247 | None 248 | } 249 | } 250 | } 251 | 252 | impl reflection::PredicateReflection for MatchesPredicate { 253 | fn parameters<'a>(&'a self) -> Box> + 'a> { 254 | let params = vec![reflection::Parameter::new("count", &self.count)]; 255 | Box::new(params.into_iter()) 256 | } 257 | } 258 | 259 | impl fmt::Display for MatchesPredicate { 260 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 261 | let palette = crate::Palette::new(f.alternate()); 262 | write!( 263 | f, 264 | "{}.{}({})", 265 | palette.var("var"), 266 | palette.description("contains"), 267 | palette.expected(&self.pattern), 268 | ) 269 | } 270 | } 271 | 272 | /// Creates a new `Predicate` that ensures a str contains `pattern` 273 | /// 274 | /// # Examples 275 | /// 276 | /// ``` 277 | /// use predicates::prelude::*; 278 | /// 279 | /// let predicate_fn = predicate::str::contains("Two"); 280 | /// assert_eq!(true, predicate_fn.eval("One Two Three")); 281 | /// assert_eq!(false, predicate_fn.eval("Four Five Six")); 282 | /// ``` 283 | pub fn contains

(pattern: P) -> ContainsPredicate 284 | where 285 | P: Into, 286 | { 287 | ContainsPredicate { 288 | pattern: pattern.into(), 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /src/str/difference.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use std::borrow; 10 | use std::fmt; 11 | 12 | use crate::reflection; 13 | use crate::Predicate; 14 | 15 | /// Predicate that diffs two strings. 16 | /// 17 | /// This is created by the `predicate::str::diff`. 18 | #[derive(Debug, Clone, PartialEq, Eq)] 19 | pub struct DifferencePredicate { 20 | orig: borrow::Cow<'static, str>, 21 | } 22 | 23 | impl Predicate for DifferencePredicate { 24 | fn eval(&self, edit: &str) -> bool { 25 | edit == self.orig 26 | } 27 | 28 | fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { 29 | let result = variable != self.orig; 30 | if result == expected { 31 | None 32 | } else { 33 | let palette = crate::Palette::new(true); 34 | let orig: Vec<_> = self.orig.lines().map(|l| format!("{l}\n")).collect(); 35 | let variable: Vec<_> = variable.lines().map(|l| format!("{l}\n")).collect(); 36 | let diff = difflib::unified_diff( 37 | &orig, 38 | &variable, 39 | "", 40 | "", 41 | &palette.expected("orig").to_string(), 42 | &palette.var("var").to_string(), 43 | 0, 44 | ); 45 | let mut diff = colorize_diff(diff, palette); 46 | diff.insert(0, "\n".to_owned()); 47 | 48 | Some( 49 | reflection::Case::new(Some(self), result) 50 | .add_product(reflection::Product::new("diff", diff.join(""))), 51 | ) 52 | } 53 | } 54 | } 55 | 56 | impl reflection::PredicateReflection for DifferencePredicate { 57 | fn parameters<'a>(&'a self) -> Box> + 'a> { 58 | let params = vec![reflection::Parameter::new("original", &self.orig)]; 59 | Box::new(params.into_iter()) 60 | } 61 | } 62 | 63 | impl fmt::Display for DifferencePredicate { 64 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 65 | let palette = crate::Palette::new(f.alternate()); 66 | write!( 67 | f, 68 | "{:#} {:#} {:#}", 69 | palette.description("diff"), 70 | palette.expected("original"), 71 | palette.var("var"), 72 | ) 73 | } 74 | } 75 | 76 | /// Creates a new `Predicate` that diffs two strings. 77 | /// 78 | /// # Examples 79 | /// 80 | /// ``` 81 | /// use predicates::prelude::*; 82 | /// 83 | /// let predicate_fn = predicate::str::diff("Hello World"); 84 | /// assert_eq!(true, predicate_fn.eval("Hello World")); 85 | /// assert!(predicate_fn.find_case(false, "Hello World").is_none()); 86 | /// assert_eq!(false, predicate_fn.eval("Goodbye World")); 87 | /// assert!(predicate_fn.find_case(false, "Goodbye World").is_some()); 88 | /// ``` 89 | pub fn diff(orig: S) -> DifferencePredicate 90 | where 91 | S: Into>, 92 | { 93 | DifferencePredicate { orig: orig.into() } 94 | } 95 | 96 | #[cfg(feature = "color")] 97 | fn colorize_diff(mut lines: Vec, palette: crate::Palette) -> Vec { 98 | for (i, line) in lines.iter_mut().enumerate() { 99 | match (i, line.as_bytes().first()) { 100 | (0, _) => { 101 | if let Some((prefix, body)) = line.split_once(' ') { 102 | *line = format!("{:#} {}", palette.expected(prefix), body); 103 | } 104 | } 105 | (1, _) => { 106 | if let Some((prefix, body)) = line.split_once(' ') { 107 | *line = format!("{:#} {}", palette.var(prefix), body); 108 | } 109 | } 110 | (_, Some(b'-')) => { 111 | let (prefix, body) = line.split_at(1); 112 | *line = format!("{:#}{}", palette.expected(prefix), body); 113 | } 114 | (_, Some(b'+')) => { 115 | let (prefix, body) = line.split_at(1); 116 | *line = format!("{:#}{}", palette.var(prefix), body); 117 | } 118 | (_, Some(b'@')) => { 119 | *line = format!("{:#}", palette.description(&line)); 120 | } 121 | _ => (), 122 | } 123 | } 124 | lines 125 | } 126 | 127 | #[cfg(not(feature = "color"))] 128 | fn colorize_diff(lines: Vec, _palette: crate::Palette) -> Vec { 129 | lines 130 | } 131 | -------------------------------------------------------------------------------- /src/str/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! String Predicates 10 | //! 11 | //! This module contains predicates specific to string handling. 12 | 13 | mod basics; 14 | pub use self::basics::*; 15 | mod adapters; 16 | pub use self::adapters::*; 17 | 18 | #[cfg(feature = "diff")] 19 | mod difference; 20 | #[cfg(feature = "diff")] 21 | pub use self::difference::{diff, DifferencePredicate}; 22 | #[cfg(feature = "normalize-line-endings")] 23 | mod normalize; 24 | #[cfg(feature = "normalize-line-endings")] 25 | pub use self::normalize::NormalizedPredicate; 26 | 27 | #[cfg(feature = "regex")] 28 | mod regex; 29 | #[cfg(feature = "regex")] 30 | pub use self::regex::{is_match, RegexError, RegexPredicate}; 31 | -------------------------------------------------------------------------------- /src/str/normalize.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use crate::reflection; 10 | use crate::Predicate; 11 | use std::fmt; 12 | 13 | use normalize_line_endings::normalized; 14 | 15 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 16 | /// Predicate adapter that normalizes the newlines contained in the variable being tested. 17 | /// 18 | /// This is created by `pred.normalize()`. 19 | pub struct NormalizedPredicate

20 | where 21 | P: Predicate, 22 | { 23 | pub(crate) p: P, 24 | } 25 | 26 | impl

reflection::PredicateReflection for NormalizedPredicate

27 | where 28 | P: Predicate, 29 | { 30 | fn children<'a>(&'a self) -> Box> + 'a> { 31 | let params = vec![reflection::Child::new("predicate", &self.p)]; 32 | Box::new(params.into_iter()) 33 | } 34 | } 35 | 36 | impl

Predicate for NormalizedPredicate

37 | where 38 | P: Predicate, 39 | { 40 | fn eval(&self, variable: &str) -> bool { 41 | let variable = normalized(variable.chars()).collect::(); 42 | self.p.eval(&variable) 43 | } 44 | 45 | fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { 46 | let variable = normalized(variable.chars()).collect::(); 47 | self.p.find_case(expected, &variable) 48 | } 49 | } 50 | 51 | impl

fmt::Display for NormalizedPredicate

52 | where 53 | P: Predicate, 54 | { 55 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 56 | self.p.fmt(f) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/str/regex.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use std::fmt; 10 | 11 | use crate::reflection; 12 | use crate::utils; 13 | use crate::Predicate; 14 | 15 | /// An error that occurred during parsing or compiling a regular expression. 16 | pub type RegexError = regex::Error; 17 | 18 | /// Predicate that uses regex matching 19 | /// 20 | /// This is created by the `predicate::str::is_match`. 21 | #[derive(Debug, Clone)] 22 | pub struct RegexPredicate { 23 | re: regex::Regex, 24 | } 25 | 26 | impl RegexPredicate { 27 | /// Require a specific count of matches. 28 | /// 29 | /// # Examples 30 | /// 31 | /// ``` 32 | /// use predicates::prelude::*; 33 | /// 34 | /// let predicate_fn = predicate::str::is_match("T[a-z]*").unwrap().count(3); 35 | /// assert_eq!(true, predicate_fn.eval("One Two Three Two One")); 36 | /// assert_eq!(false, predicate_fn.eval("One Two Three")); 37 | /// ``` 38 | pub fn count(self, count: usize) -> RegexMatchesPredicate { 39 | RegexMatchesPredicate { re: self.re, count } 40 | } 41 | } 42 | 43 | impl Predicate for RegexPredicate { 44 | fn eval(&self, variable: &str) -> bool { 45 | self.re.is_match(variable) 46 | } 47 | 48 | fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { 49 | utils::default_find_case(self, expected, variable) 50 | .map(|case| case.add_product(reflection::Product::new("var", variable.to_owned()))) 51 | } 52 | } 53 | 54 | impl reflection::PredicateReflection for RegexPredicate {} 55 | 56 | impl fmt::Display for RegexPredicate { 57 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 58 | let palette = crate::Palette::new(f.alternate()); 59 | write!( 60 | f, 61 | "{}.{}({})", 62 | palette.var("var"), 63 | palette.description("is_match"), 64 | palette.expected(&self.re), 65 | ) 66 | } 67 | } 68 | 69 | /// Predicate that checks for repeated patterns. 70 | /// 71 | /// This is created by `predicates::str::is_match(...).count`. 72 | #[derive(Debug, Clone)] 73 | pub struct RegexMatchesPredicate { 74 | re: regex::Regex, 75 | count: usize, 76 | } 77 | 78 | impl Predicate for RegexMatchesPredicate { 79 | fn eval(&self, variable: &str) -> bool { 80 | self.re.find_iter(variable).count() == self.count 81 | } 82 | 83 | fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { 84 | let actual_count = self.re.find_iter(variable).count(); 85 | let result = self.count == actual_count; 86 | if result == expected { 87 | Some( 88 | reflection::Case::new(Some(self), result) 89 | .add_product(reflection::Product::new("var", variable.to_owned())) 90 | .add_product(reflection::Product::new("actual count", actual_count)), 91 | ) 92 | } else { 93 | None 94 | } 95 | } 96 | } 97 | 98 | impl reflection::PredicateReflection for RegexMatchesPredicate { 99 | fn parameters<'a>(&'a self) -> Box> + 'a> { 100 | let params = vec![reflection::Parameter::new("count", &self.count)]; 101 | Box::new(params.into_iter()) 102 | } 103 | } 104 | 105 | impl fmt::Display for RegexMatchesPredicate { 106 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 107 | let palette = crate::Palette::new(f.alternate()); 108 | write!( 109 | f, 110 | "{}.{}({})", 111 | palette.var("var"), 112 | palette.description("is_match"), 113 | palette.expected(&self.re), 114 | ) 115 | } 116 | } 117 | 118 | /// Creates a new `Predicate` that uses a regular expression to match the string. 119 | /// 120 | /// # Examples 121 | /// 122 | /// ``` 123 | /// use predicates::prelude::*; 124 | /// 125 | /// let predicate_fn = predicate::str::is_match("^Hello.*$").unwrap(); 126 | /// assert_eq!(true, predicate_fn.eval("Hello World")); 127 | /// assert_eq!(false, predicate_fn.eval("Food World")); 128 | /// ``` 129 | pub fn is_match(pattern: S) -> Result 130 | where 131 | S: AsRef, 132 | { 133 | regex::Regex::new(pattern.as_ref()).map(|re| RegexPredicate { re }) 134 | } 135 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The predicates-rs Project Developers. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use std::fmt; 10 | 11 | use crate::reflection; 12 | use crate::Predicate; 13 | 14 | #[derive(Clone, PartialEq, Eq)] 15 | pub(crate) struct DebugAdapter 16 | where 17 | T: fmt::Debug, 18 | { 19 | pub(crate) debug: T, 20 | } 21 | 22 | impl DebugAdapter 23 | where 24 | T: fmt::Debug, 25 | { 26 | pub(crate) fn new(debug: T) -> Self { 27 | Self { debug } 28 | } 29 | } 30 | 31 | impl fmt::Display for DebugAdapter 32 | where 33 | T: fmt::Debug, 34 | { 35 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 36 | write!(f, "{:#?}", self.debug) 37 | } 38 | } 39 | 40 | impl fmt::Debug for DebugAdapter 41 | where 42 | T: fmt::Debug, 43 | { 44 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 45 | self.debug.fmt(f) 46 | } 47 | } 48 | 49 | pub(crate) fn default_find_case<'a, P, Item>( 50 | pred: &'a P, 51 | expected: bool, 52 | variable: &Item, 53 | ) -> Option> 54 | where 55 | P: Predicate, 56 | Item: ?Sized, 57 | { 58 | let actual = pred.eval(variable); 59 | if expected == actual { 60 | Some(reflection::Case::new(Some(pred), actual)) 61 | } else { 62 | None 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/empty_file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/assert-rs/predicates-rs/36cf37e4c2039a1a9ca297c698d40851235c6155/tests/empty_file -------------------------------------------------------------------------------- /tests/hello_world: -------------------------------------------------------------------------------- 1 | Hello World! --------------------------------------------------------------------------------