diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 633df03..77dec89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,10 +30,20 @@ jobs: - name: Format run: cargo fmt --check - name: Lint - run: cargo clippy --all + run: cargo clippy --all-targets --all - name: Build run: cargo build --all --verbose - name: Run tests run: cargo test --all --verbose - name: Generate the documentation - run: cargo doc --all --verbose + 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 new file mode 100644 index 0000000..e04fc5a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,58 @@ +# 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`, + 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] + +### 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..51d6044 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,14 @@ [package] name = "range-cmp" version = "0.3.0" -edition = "2021" +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" +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..e3cc700 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,10 @@ 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. Its MSRV (minimum supported +Rust version) is 1.35. + ## 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..467a22a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,12 +48,20 @@ //! 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` +//! +//! 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`]. @@ -113,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. @@ -146,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, }, @@ -163,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, }, @@ -241,9 +246,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 {} @@ -295,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,