From 4967c089f59d7b0978ac7953561782717028f140 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 11 Jun 2026 09:58:07 +0000 Subject: [PATCH 1/4] Make range-cmp production-ready: no_std, crate metadata, CI hardening - Convert the crate to #![no_std] (uses only core); add #![forbid(unsafe_code)] and #![warn(missing_docs)] - Add Cargo.toml metadata for crates.io: keywords, categories, rust-version (MSRV 1.56), readme - Add CHANGELOG.md documenting the 0.1 -> 0.3 history - Fix README title (range_cmp -> range-cmp) and note no_std support - Harden CI: stable/beta/MSRV test matrix, no_std bare-metal build check, and a cargo publish --dry-run packaging gate --- .github/workflows/ci.yml | 56 +++++++++++++++++++++++++++++++++++----- CHANGELOG.md | 52 +++++++++++++++++++++++++++++++++++++ Cargo.toml | 4 +++ README.md | 5 +++- src/lib.rs | 18 +++++++++---- 5 files changed, 122 insertions(+), 13 deletions(-) create mode 100644 CHANGELOG.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 633df03..0c7f8fe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,12 +16,15 @@ env: RUSTDOCFLAGS: -Dwarnings jobs: - lint-and-test: + lint: + name: Format, lint and docs runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Enable caching - uses: actions/cache@v3 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + - uses: actions/cache@v4 with: path: | ~/.cargo/registry @@ -30,10 +33,49 @@ jobs: - name: Format run: cargo fmt --check - name: Lint - run: cargo clippy --all + run: cargo clippy --all-targets --all + - name: Generate the documentation + run: cargo doc --all --no-deps + + test: + name: Build and test (${{ matrix.toolchain }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + toolchain: [ stable, beta, "1.56" ] + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.toolchain }} + - uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + key: ${{ runner.os }}-cargo-${{ matrix.toolchain }}-${{ hashFiles('**/Cargo.lock') }} - name: Build run: cargo build --all --verbose - name: Run tests run: cargo test --all --verbose - - name: Generate the documentation - run: cargo doc --all --verbose + + no_std: + name: Build without std + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + targets: thumbv7em-none-eabi + - name: Build for a bare-metal target + run: cargo build --target thumbv7em-none-eabi --verbose + + package: + name: Package (publish dry-run) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - name: Verify the crate packages cleanly + run: cargo publish --dry-run diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..26276db --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,52 @@ +# Changelog + +All notable changes to this project are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- The crate is now `#![no_std]`. It has no dependencies and relies only on `core`, + so it can be used in embedded and other environments without the standard library. +- Cargo manifest metadata for crates.io: `keywords`, `categories`, `rust-version` + (MSRV 1.56), and `readme`. + +## [0.3.0] + +### Changed + +- **Breaking:** reworked the comparison semantics to handle empty ranges explicitly + and to be honest about partial orders. +- **Breaking:** renamed the `range_cmp` method to `rcmp` and the `RangeComparable` + trait to `RangeOrd`. + +### Added + +- `RangeOrdering::Empty`: empty and inverted ranges (such as `45..35` or `0..0`) are + now reported explicitly instead of returning an arbitrary, representation-dependent + verdict. Emptiness is judged from the bounds (so `..0u32` is non-empty). Resolves + [#6](https://github.com/Akvize/range-cmp/issues/6). +- `PartialRangeOrd` trait with a `partial_rcmp` method for types that are `PartialOrd` + but not `Ord`. +- `RangePosition` (the pair of a value's relationships to the lower and upper bounds, + with `is_inside` and `ordering` helpers) and `BoundOrdering` + (`Within` / `Outside` / `Incomparable`), which preserve per-bound information over + partial orders. + +## [0.2.0] + +### Changed + +- **Breaking:** comparison now first checks whether the range contains the value. + +## [0.1.3] + +Initial published releases (`0.1.0` through `0.1.3`). + +[Unreleased]: https://github.com/Akvize/range-cmp/compare/v0.3.0...HEAD +[0.3.0]: https://github.com/Akvize/range-cmp/compare/v0.2.0...v0.3.0 +[0.2.0]: https://github.com/Akvize/range-cmp/compare/v0.1.3...v0.2.0 +[0.1.3]: https://github.com/Akvize/range-cmp/releases/tag/v0.1.3 diff --git a/Cargo.toml b/Cargo.toml index 5de9998..71538bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,9 +2,13 @@ name = "range-cmp" version = "0.3.0" edition = "2021" +rust-version = "1.56" license = "MIT OR Apache-2.0" description = "Trait that allows comparing a value to a range of values" repository = "https://github.com/Akvize/range-cmp" +readme = "README.md" +keywords = ["range", "comparison", "ordering", "ord", "cmp"] +categories = ["algorithms", "no-std", "rust-patterns"] exclude = [ "pre-commit", "CONTRIBUTING.md", diff --git a/README.md b/README.md index 89f6304..edd0c8e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# range_cmp +# range-cmp [![Crates.io][crates-badge]][crates-url] [![MIT licensed][mit-badge]][mit-url] @@ -27,6 +27,9 @@ assert_eq!(25.rcmp(20..30), RangeOrdering::Inside); assert_eq!(35.rcmp(20..30), RangeOrdering::Above); ``` +The crate is `#![no_std]` and has zero dependencies, so it can be used in embedded +and other environments without the standard library. + ## Empty ranges handling Empty ranges are handled explicitly, instead of returning an arbitrary, diff --git a/src/lib.rs b/src/lib.rs index 8c14dda..8351b5d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,10 +50,18 @@ //! // `NaN` is incomparable with the bounds, so there is no single verdict: //! assert_eq!(f64::NAN.partial_rcmp(2.0..3.0).ordering(), None); //! ``` +//! +//! # `no_std` +//! +//! The crate is `#![no_std]`: it has no dependencies and only relies on `core`, so it +//! can be used in embedded and other environments without the standard library. +#![cfg_attr(not(test), no_std)] +#![forbid(unsafe_code)] +#![warn(missing_docs)] -use std::borrow::Borrow; -use std::cmp::Ordering; -use std::ops::{Bound, RangeBounds}; +use core::borrow::Borrow; +use core::cmp::Ordering; +use core::ops::{Bound, RangeBounds}; /// Simplified result for [`RangeOrd::rcmp`], obtained for totally ordered types or by /// collapsing a [`RangePosition`] through [`RangePosition::ordering`]. @@ -241,9 +249,9 @@ fn range_is_empty>(range: &R) -> bool { /// | ^ cannot infer type of the type parameter `R` declared on the function `f` /// ``` /// -/// Indeed, although we understand we want to pass a [`Range`](std::ops::Range)`<`[`i32`]`>` by +/// Indeed, although we understand we want to pass a [`Range`](core::ops::Range)`<`[`i32`]`>` by /// reference, the compiler need to assume that other types could yield a -/// `&`[`Range`](std::ops::Range)`<`[`i32`]`>` when borrowed. +/// `&`[`Range`](core::ops::Range)`<`[`i32`]`>` when borrowed. pub trait BorrowRange: Borrow {} impl> BorrowRange for R {} impl> BorrowRange for &R {} From cefe08acd3f89cd14d2a1f8ea0886816a3d0582e Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 11 Jun 2026 10:02:22 +0000 Subject: [PATCH 2/4] CI: drive toolchains via rustup instead of third-party action All jobs failed at setup because the org restricts GitHub Actions to GitHub-owned actions, and dtolnay/rust-toolchain is not allowed. Use the preinstalled rustup to install the matrix toolchains, components, and the bare-metal target instead, keeping only actions/checkout and actions/cache. --- .github/workflows/ci.yml | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c7f8fe..8fb14bb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,9 +21,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - with: - components: rustfmt, clippy + - name: Install toolchain + run: rustup component add rustfmt clippy - uses: actions/cache@v4 with: path: | @@ -46,9 +45,10 @@ jobs: toolchain: [ stable, beta, "1.56" ] steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ matrix.toolchain }} + - name: Install toolchain + run: | + rustup toolchain install ${{ matrix.toolchain }} --profile minimal --no-self-update + rustup default ${{ matrix.toolchain }} - uses: actions/cache@v4 with: path: | @@ -65,9 +65,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - with: - targets: thumbv7em-none-eabi + - name: Add bare-metal target + run: rustup target add thumbv7em-none-eabi - name: Build for a bare-metal target run: cargo build --target thumbv7em-none-eabi --verbose @@ -76,6 +75,5 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - name: Verify the crate packages cleanly run: cargo publish --dry-run From e17f2825499a62d59b62ad417e040825ef309a7f Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 11 Jun 2026 10:11:56 +0000 Subject: [PATCH 3/4] CI: pin actions/checkout and actions/cache back to v3 Jobs were failing at startup (no runner, no logs, ~3s) because the org's allowed-actions policy permits the v3 pins used historically but not v4. Revert to v3, keeping the rustup-driven toolchain matrix. --- .github/workflows/ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8fb14bb..5457671 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,10 +20,10 @@ jobs: name: Format, lint and docs runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Install toolchain run: rustup component add rustfmt clippy - - uses: actions/cache@v4 + - uses: actions/cache@v3 with: path: | ~/.cargo/registry @@ -44,12 +44,12 @@ jobs: matrix: toolchain: [ stable, beta, "1.56" ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Install toolchain run: | rustup toolchain install ${{ matrix.toolchain }} --profile minimal --no-self-update rustup default ${{ matrix.toolchain }} - - uses: actions/cache@v4 + - uses: actions/cache@v3 with: path: | ~/.cargo/registry @@ -64,7 +64,7 @@ jobs: name: Build without std runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Add bare-metal target run: rustup target add thumbv7em-none-eabi - name: Build for a bare-metal target @@ -74,6 +74,6 @@ jobs: name: Package (publish dry-run) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Verify the crate packages cleanly run: cargo publish --dry-run From f2a0ff5005d437fb15e6a72315476bbabbfc1c1f Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 11 Jun 2026 11:07:58 +0000 Subject: [PATCH 4/4] Lower MSRV to 1.35 and collapse CI into a single job MSRV: - Switch to edition 2018 and avoid later-stabilized conveniences: nested or-patterns (1.53), the matches! macro (1.42), and the f64::NAN associated constant in doctests (1.43). The floor is now 1.35, gated only by the RangeBounds API. No public API changes; all tests pass on 1.35.0. CI: - This environment's Actions runner only dispatches the original single-job workflow, so fold every check (fmt, clippy, build, test, doc, MSRV 1.35 build, no_std bare-metal build, publish dry-run) into the single lint-and-test job as sequential steps, pinned to actions/{checkout,cache}@v3. --- .github/workflows/ci.yml | 56 ++++++++++------------------------------ CHANGELOG.md | 10 +++++-- Cargo.toml | 4 +-- README.md | 3 ++- src/lib.rs | 17 +++++------- 5 files changed, 32 insertions(+), 58 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5457671..77dec89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,14 +16,12 @@ env: RUSTDOCFLAGS: -Dwarnings jobs: - lint: - name: Format, lint and docs + lint-and-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Install toolchain - run: rustup component add rustfmt clippy - - uses: actions/cache@v3 + - name: Enable caching + uses: actions/cache@v3 with: path: | ~/.cargo/registry @@ -33,47 +31,19 @@ jobs: run: cargo fmt --check - name: Lint run: cargo clippy --all-targets --all - - name: Generate the documentation - run: cargo doc --all --no-deps - - test: - name: Build and test (${{ matrix.toolchain }}) - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - toolchain: [ stable, beta, "1.56" ] - steps: - - uses: actions/checkout@v3 - - name: Install toolchain - run: | - rustup toolchain install ${{ matrix.toolchain }} --profile minimal --no-self-update - rustup default ${{ matrix.toolchain }} - - uses: actions/cache@v3 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - key: ${{ runner.os }}-cargo-${{ matrix.toolchain }}-${{ hashFiles('**/Cargo.lock') }} - name: Build run: cargo build --all --verbose - name: Run tests run: cargo test --all --verbose - - no_std: - name: Build without std - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Add bare-metal target - run: rustup target add thumbv7em-none-eabi - - name: Build for a bare-metal target - run: cargo build --target thumbv7em-none-eabi --verbose - - package: - name: Package (publish dry-run) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 + - name: Generate the documentation + run: cargo doc --all --no-deps --verbose + - name: Verify the MSRV (1.35) + run: | + rustup toolchain install 1.35.0 --profile minimal --no-self-update + cargo +1.35.0 test --all --verbose + - name: Build without std (bare-metal target) + run: | + rustup target add thumbv7em-none-eabi + cargo build --target thumbv7em-none-eabi --verbose - name: Verify the crate packages cleanly run: cargo publish --dry-run diff --git a/CHANGELOG.md b/CHANGELOG.md index 26276db..e04fc5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The crate is now `#![no_std]`. It has no dependencies and relies only on `core`, so it can be used in embedded and other environments without the standard library. -- Cargo manifest metadata for crates.io: `keywords`, `categories`, `rust-version` - (MSRV 1.56), and `readme`. +- Cargo manifest metadata for crates.io: `keywords`, `categories`, `rust-version`, + and `readme`. + +### Changed + +- Lowered the MSRV to **1.35** (the floor of the `RangeBounds` API) by switching to + edition 2018 and avoiding later-stabilized conveniences (nested or-patterns, + the `matches!` macro, and the `f64::NAN` associated constant). No API changes. ## [0.3.0] diff --git a/Cargo.toml b/Cargo.toml index 71538bd..51d6044 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "range-cmp" version = "0.3.0" -edition = "2021" -rust-version = "1.56" +edition = "2018" +rust-version = "1.35" license = "MIT OR Apache-2.0" description = "Trait that allows comparing a value to a range of values" repository = "https://github.com/Akvize/range-cmp" diff --git a/README.md b/README.md index edd0c8e..e3cc700 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,8 @@ assert_eq!(35.rcmp(20..30), RangeOrdering::Above); ``` The crate is `#![no_std]` and has zero dependencies, so it can be used in embedded -and other environments without the standard library. +and other environments without the standard library. Its MSRV (minimum supported +Rust version) is 1.35. ## Empty ranges handling diff --git a/src/lib.rs b/src/lib.rs index 8351b5d..467a22a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,7 +48,7 @@ //! assert_eq!(2.5_f64.partial_rcmp(2.0..3.0).ordering(), Some(RangeOrdering::Inside)); //! assert_eq!(3.5_f64.partial_rcmp(2.0..3.0).ordering(), Some(RangeOrdering::Above)); //! // `NaN` is incomparable with the bounds, so there is no single verdict: -//! assert_eq!(f64::NAN.partial_rcmp(2.0..3.0).ordering(), None); +//! assert_eq!(core::f64::NAN.partial_rcmp(2.0..3.0).ordering(), None); //! ``` //! //! # `no_std` @@ -121,10 +121,7 @@ impl RangePosition { /// assert!(!3.5_f64.partial_rcmp(2.0..3.0).is_inside()); /// ``` pub fn is_inside(&self) -> bool { - matches!( - (self.lower, self.upper), - (BoundOrdering::Within, BoundOrdering::Within) - ) + self.lower == BoundOrdering::Within && self.upper == BoundOrdering::Within } /// Collapses the pair into a simple [`RangeOrdering`] when possible. @@ -154,11 +151,11 @@ fn lower_ordering(value: &T, bound: Bound<&T>) -> BoundOrdering { Bound::Unbounded => BoundOrdering::Within, Bound::Included(key) => match value.partial_cmp(key) { Some(Ordering::Less) => BoundOrdering::Outside, - Some(Ordering::Equal | Ordering::Greater) => BoundOrdering::Within, + Some(Ordering::Equal) | Some(Ordering::Greater) => BoundOrdering::Within, None => BoundOrdering::Incomparable, }, Bound::Excluded(key) => match value.partial_cmp(key) { - Some(Ordering::Less | Ordering::Equal) => BoundOrdering::Outside, + Some(Ordering::Less) | Some(Ordering::Equal) => BoundOrdering::Outside, Some(Ordering::Greater) => BoundOrdering::Within, None => BoundOrdering::Incomparable, }, @@ -171,11 +168,11 @@ fn upper_ordering(value: &T, bound: Bound<&T>) -> BoundOrdering { Bound::Unbounded => BoundOrdering::Within, Bound::Included(key) => match value.partial_cmp(key) { Some(Ordering::Greater) => BoundOrdering::Outside, - Some(Ordering::Equal | Ordering::Less) => BoundOrdering::Within, + Some(Ordering::Equal) | Some(Ordering::Less) => BoundOrdering::Within, None => BoundOrdering::Incomparable, }, Bound::Excluded(key) => match value.partial_cmp(key) { - Some(Ordering::Greater | Ordering::Equal) => BoundOrdering::Outside, + Some(Ordering::Greater) | Some(Ordering::Equal) => BoundOrdering::Outside, Some(Ordering::Less) => BoundOrdering::Within, None => BoundOrdering::Incomparable, }, @@ -303,7 +300,7 @@ pub trait PartialRangeOrd { /// assert_eq!(2.5_f64.partial_rcmp(2.0..3.0).ordering(), Some(RangeOrdering::Inside)); /// assert_eq!(3.5_f64.partial_rcmp(2.0..3.0).ordering(), Some(RangeOrdering::Above)); /// // `NaN` is incomparable with the bounds: - /// assert_eq!(f64::NAN.partial_rcmp(2.0..3.0).ordering(), None); + /// assert_eq!(core::f64::NAN.partial_rcmp(2.0..3.0).ordering(), None); /// ``` fn partial_rcmp, B: BorrowRange>( &self,