version-compare-0.1.1/.cargo_vcs_info.json0000644000000001360000000000100141440ustar { "git": { "sha1": "9a965b448749892c921808946420d6372de0958f" }, "path_in_vcs": "" }version-compare-0.1.1/Cargo.lock0000644000000002220000000000100121130ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "version-compare" version = "0.1.1" version-compare-0.1.1/Cargo.toml0000644000000022170000000000100121440ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "version-compare" version = "0.1.1" authors = ["Tim Visee <3a4fb3964f@sinenomine.email>"] include = [ "/src", "/examples", "Cargo.toml", "LICENSE", "README.md", ] description = "Rust library to easily compare version numbers with no specific format, and test against various comparison operators." homepage = "https://timvisee.com/projects/version-compare/" documentation = "https://docs.rs/version-compare" readme = "README.md" keywords = [ "version", "compare", "comparison", "comparing", ] categories = [ "parser-implementations", "parsing", ] license = "MIT" repository = "https://gitlab.com/timvisee/version-compare" version-compare-0.1.1/Cargo.toml.orig000064400000000000000000000012071046102023000156230ustar 00000000000000[package] name = "version-compare" version = "0.1.1" authors = ["Tim Visee <3a4fb3964f@sinenomine.email>"] license = "MIT" readme = "README.md" homepage = "https://timvisee.com/projects/version-compare/" repository = "https://gitlab.com/timvisee/version-compare" documentation = "https://docs.rs/version-compare" description = "Rust library to easily compare version numbers with no specific format, and test against various comparison operators." keywords = ["version", "compare", "comparison", "comparing"] categories = ["parser-implementations", "parsing"] edition = "2018" include = ["/src", "/examples", "Cargo.toml", "LICENSE", "README.md"] version-compare-0.1.1/LICENSE000064400000000000000000000020371046102023000137430ustar 00000000000000Copyright (c) 2017 Tim Visée Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. version-compare-0.1.1/README.md000064400000000000000000000116041046102023000142150ustar 00000000000000[![Build status on GitLab CI][gitlab-ci-master-badge]][gitlab-ci-link] [![Crate version][crate-version-badge]][crate-link] [![Documentation][docs-badge]][docs] [![Download statistics][crate-download-badge]][crate-link] [![Coverage status][coverage-badge]][coverage-link] [![Dependencies][dependency-badge]][crate-link] [![License][crate-license-badge]][crate-link] [coverage-badge]: https://gitlab.com/timvisee/version-compare/badges/master/coverage.svg [coverage-link]: https://coveralls.io/gitlab/timvisee/version-compare [crate-download-badge]: https://img.shields.io/crates/d/version-compare.svg [crate-license-badge]: https://img.shields.io/crates/l/version-compare.svg [crate-link]: https://crates.io/crates/version-compare [crate-version-badge]: https://img.shields.io/crates/v/version-compare.svg [dependency-badge]: https://img.shields.io/badge/dependencies-none!-green.svg [docs-badge]: https://img.shields.io/docsrs/version-compare [docs]: https://docs.rs/version-compare [gitlab-ci-link]: https://gitlab.com/timvisee/version-compare/pipelines [gitlab-ci-master-badge]: https://gitlab.com/timvisee/version-compare/badges/master/pipeline.svg # Rust library: version-compare > Rust library to easily compare version numbers with no specific format, and test against various comparison operators. Comparing version numbers is hard, especially with weird version number formats. This library helps you to easily compare any kind of version number with no specific format using a best-effort approach. Two version numbers can be compared to each other to get a comparison operator (`<`, `==`, `>`), or test them against a comparison operator. Along with version comparison, the library provides various other tools for working with version numbers. Inspired by PHPs [version_compare()](http://php.net/manual/en/function.version-compare.php). _Note: Still a work in progress. Configurability is currently very limited. Things will change._ ### Formats Version numbers that would parse successfully include: `1`, `3.10.4.1`, `1.2.alpha`, `1.2.dev.4`, ` `, ` . -32 . 1`, `MyApp 3.2.0 / build 0932` ... See a list of how version numbers compare [here](https://github.com/timvisee/version-compare/blob/411ed7135741ed7cf2fcf4919012fb5412dc122b/src/test.rs#L50-L103). ## Example This library is very easy to use. Here's a basic usage example: `Cargo.toml`: ```toml [dependencies] version-compare = "0.1" ``` [`example.rs`](examples/example.rs): ```rust use version_compare::{compare, compare_to, Cmp, Version}; fn main() { let a = "1.2"; let b = "1.5.1"; // The following comparison operators are used: // - Cmp::Eq -> Equal // - Cmp::Ne -> Not equal // - Cmp::Lt -> Less than // - Cmp::Le -> Less than or equal // - Cmp::Ge -> Greater than or equal // - Cmp::Gt -> Greater than // Easily compare version strings assert_eq!(compare(a, b), Ok(Cmp::Lt)); assert_eq!(compare_to(a, b, Cmp::Le), Ok(true)); assert_eq!(compare_to(a, b, Cmp::Gt), Ok(false)); // Parse and wrap version strings as a Version let a = Version::from(a).unwrap(); let b = Version::from(b).unwrap(); // The Version can easily be compared with assert_eq!(a < b, true); assert_eq!(a <= b, true); assert_eq!(a > b, false); assert_eq!(a != b, true); assert_eq!(a.compare(&b), Cmp::Lt); assert_eq!(a.compare_to(&b, Cmp::Lt), true); // Or match the comparison operators match a.compare(b) { Cmp::Lt => println!("Version a is less than b"), Cmp::Eq => println!("Version a is equal to b"), Cmp::Gt => println!("Version a is greater than b"), _ => unreachable!(), } } ``` See the [`examples`](examples) directory for more. ## Features * Compare version numbers, get: `<`, `==`, `>` * Compare against a comparison operator (`<`, `<=`, `==`, `!=`, `>=`, `>`) * Parse complex and unspecified formats * Static, standalone methods to easily compare version strings in a single line of code #### Future ideas * Version ranges * Support for [npm-style](https://semver.npmjs.com/) operators (e.g. `^1.0` or `~1.0`) * Manifest: extend `Manifest` for to support a wide set of constraints * Building blocks for building your own specific version number parser * Batch comparisons #### Semver Version numbers using the [semver](http://semver.org/) format are compared correctly with no additional configuration. If your version number strings follow this exact format you may be better off using the [`semver`](https://crates.io/crates/semver) crate for more format specific features. If that isn't certain however, `version-compare` makes comparing a breeze. ## Builds This library is automatically build and tested every day and for each commit using CI services. See the current status here: https://gitlab.com/timvisee/version-compare/-/pipelines ## License This project is released under the MIT license. Check out the [LICENSE](LICENSE) file for more information. version-compare-0.1.1/examples/example.rs000064400000000000000000000027361046102023000165630ustar 00000000000000//! Usage examples of the version-compare crate. //! //! This shows various ways this library provides for comparing version numbers. //! The `assert_eq!(...)` macros are used to assert and show the expected output. //! //! Run this example by invoking `cargo run --example example`. use version_compare::{compare, compare_to, Cmp, Version}; fn main() { let a = "1.2"; let b = "1.5.1"; // The following comparison operators are used: // - Cmp::Eq -> Equal // - Cmp::Ne -> Not equal // - Cmp::Lt -> Less than // - Cmp::Le -> Less than or equal // - Cmp::Ge -> Greater than or equal // - Cmp::Gt -> Greater than // Easily compare version strings assert_eq!(compare(a, b), Ok(Cmp::Lt)); assert_eq!(compare_to(a, b, Cmp::Le), Ok(true)); assert_eq!(compare_to(a, b, Cmp::Gt), Ok(false)); // Parse and wrap version strings as a Version let a = Version::from(a).unwrap(); let b = Version::from(b).unwrap(); // The Version can easily be compared with assert_eq!(a < b, true); assert_eq!(a <= b, true); assert_eq!(a > b, false); assert_eq!(a != b, true); assert_eq!(a.compare(&b), Cmp::Lt); assert_eq!(a.compare_to(&b, Cmp::Lt), true); // Or match the comparison operators match a.compare(b) { Cmp::Lt => println!("Version a is less than b"), Cmp::Eq => println!("Version a is equal to b"), Cmp::Gt => println!("Version a is greater than b"), _ => unreachable!(), } } version-compare-0.1.1/examples/minimal.rs000064400000000000000000000010721046102023000165460ustar 00000000000000//! A minimal usage example of the version-compare crate. //! //! This compares two given version number strings, and outputs which is greater. //! //! Run this example by invoking `cargo run --example minimal`. use version_compare::{compare, Cmp}; fn main() { let a = "1.3"; let b = "1.2.4"; match compare(a, b) { Ok(Cmp::Lt) => println!("Version a is less than b"), Ok(Cmp::Eq) => println!("Version a is equal to b"), Ok(Cmp::Gt) => println!("Version a is greater than b"), _ => panic!("Invalid version number"), } } version-compare-0.1.1/src/cmp.rs000064400000000000000000000303451046102023000146550ustar 00000000000000//! Module with all supported comparison operators. //! //! This module provides an enum with all comparison operators that can be used with this library. //! The enum provides various useful helper functions to inverse or flip an operator. //! //! Methods like `Cmp::from_sign(">");` can be used to get a comparison operator by it's logical //! sign from a string. use std::cmp::Ordering; /// Comparison operators enum. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum Cmp { /// Equal (`==`, `=`). /// When version `A` is equal to `B`. Eq, /// Not equal (`!=`, `!`, `<>`). /// When version `A` is not equal to `B`. Ne, /// Less than (`<`). /// When version `A` is less than `B` but not equal. Lt, /// Less or equal (`<=`). /// When version `A` is less than or equal to `B`. Le, /// Greater or equal (`>=`). /// When version `A` is greater than or equal to `B`. Ge, /// Greater than (`>`). /// When version `A` is greater than `B` but not equal. Gt, } impl Cmp { /// Get a comparison operator by it's sign. /// Whitespaces are stripped from the sign string. /// An error is returned if the sign isn't recognized. /// /// The following signs are supported: /// /// * `==` _or_ `=` -> `Eq` /// * `!=` _or_ `!` _or_ `<>` -> `Ne` /// * `< ` -> `Lt` /// * `<=` -> `Le` /// * `>=` -> `Ge` /// * `> ` -> `Gt` /// /// # Examples /// /// ``` /// use version_compare::Cmp; /// /// assert_eq!(Cmp::from_sign("=="), Ok(Cmp::Eq)); /// assert_eq!(Cmp::from_sign("<"), Ok(Cmp::Lt)); /// assert_eq!(Cmp::from_sign(" >= "), Ok(Cmp::Ge)); /// assert!(Cmp::from_sign("*").is_err()); /// ``` #[allow(clippy::result_map_unit_fn)] pub fn from_sign>(sign: S) -> Result { match sign.as_ref().trim() { "==" | "=" => Ok(Cmp::Eq), "!=" | "!" | "<>" => Ok(Cmp::Ne), "<" => Ok(Cmp::Lt), "<=" => Ok(Cmp::Le), ">=" => Ok(Cmp::Ge), ">" => Ok(Cmp::Gt), _ => Err(()), } } /// Get a comparison operator by it's name. /// Names are case-insensitive, and whitespaces are stripped from the string. /// An error is returned if the name isn't recognized. /// /// # Examples /// /// ``` /// use version_compare::Cmp; /// /// assert_eq!(Cmp::from_name("eq"), Ok(Cmp::Eq)); /// assert_eq!(Cmp::from_name("lt"), Ok(Cmp::Lt)); /// assert_eq!(Cmp::from_name(" Ge "), Ok(Cmp::Ge)); /// assert!(Cmp::from_name("abc").is_err()); /// ``` #[allow(clippy::result_map_unit_fn)] pub fn from_name>(sign: S) -> Result { match sign.as_ref().trim().to_lowercase().as_str() { "eq" => Ok(Cmp::Eq), "ne" => Ok(Cmp::Ne), "lt" => Ok(Cmp::Lt), "le" => Ok(Cmp::Le), "ge" => Ok(Cmp::Ge), "gt" => Ok(Cmp::Gt), _ => Err(()), } } /// Get the comparison operator from Rusts `Ordering` enum. /// /// The following comparison operators are returned: /// /// * `Ordering::Less` -> `Lt` /// * `Ordering::Equal` -> `Eq` /// * `Ordering::Greater` -> `Gt` pub fn from_ord(ord: Ordering) -> Cmp { match ord { Ordering::Less => Cmp::Lt, Ordering::Equal => Cmp::Eq, Ordering::Greater => Cmp::Gt, } } /// Get the name of this comparison operator. /// /// # Examples /// /// ``` /// use version_compare::Cmp; /// /// assert_eq!(Cmp::Eq.name(), "eq"); /// assert_eq!(Cmp::Lt.name(), "lt"); /// assert_eq!(Cmp::Ge.name(), "ge"); /// ``` pub fn name<'a>(self) -> &'a str { match self { Cmp::Eq => "eq", Cmp::Ne => "ne", Cmp::Lt => "lt", Cmp::Le => "le", Cmp::Ge => "ge", Cmp::Gt => "gt", } } /// Get the inverted comparison operator. /// /// This uses the following bidirectional rules: /// /// * `Eq` <-> `Ne` /// * `Lt` <-> `Ge` /// * `Le` <-> `Gt` /// /// # Examples /// /// ``` /// use version_compare::Cmp; /// /// assert_eq!(Cmp::Eq.invert(), Cmp::Ne); /// assert_eq!(Cmp::Lt.invert(), Cmp::Ge); /// assert_eq!(Cmp::Gt.invert(), Cmp::Le); /// ``` #[must_use] pub fn invert(self) -> Self { match self { Cmp::Eq => Cmp::Ne, Cmp::Ne => Cmp::Eq, Cmp::Lt => Cmp::Ge, Cmp::Le => Cmp::Gt, Cmp::Ge => Cmp::Lt, Cmp::Gt => Cmp::Le, } } /// Get the opposite comparison operator. /// /// This uses the following bidirectional rules: /// /// * `Eq` <-> `Ne` /// * `Lt` <-> `Gt` /// * `Le` <-> `Ge` /// /// # Examples /// /// ``` /// use version_compare::Cmp; /// /// assert_eq!(Cmp::Eq.opposite(), Cmp::Ne); /// assert_eq!(Cmp::Lt.opposite(), Cmp::Gt); /// assert_eq!(Cmp::Ge.opposite(), Cmp::Le); /// ``` #[must_use] pub fn opposite(self) -> Self { match self { Cmp::Eq => Cmp::Ne, Cmp::Ne => Cmp::Eq, Cmp::Lt => Cmp::Gt, Cmp::Le => Cmp::Ge, Cmp::Ge => Cmp::Le, Cmp::Gt => Cmp::Lt, } } /// Get the flipped comparison operator. /// /// This uses the following bidirectional rules: /// /// * `Lt` <-> `Gt` /// * `Le` <-> `Ge` /// * Other operators are returned as is. /// /// # Examples /// /// ``` /// use version_compare::Cmp; /// /// assert_eq!(Cmp::Eq.flip(), Cmp::Eq); /// assert_eq!(Cmp::Lt.flip(), Cmp::Gt); /// assert_eq!(Cmp::Ge.flip(), Cmp::Le); /// ``` #[must_use] pub fn flip(self) -> Self { match self { Cmp::Lt => Cmp::Gt, Cmp::Le => Cmp::Ge, Cmp::Ge => Cmp::Le, Cmp::Gt => Cmp::Lt, _ => self, } } /// Get the sign for this comparison operator. /// /// The following signs are returned: /// /// * `Eq` -> `==` /// * `Ne` -> `!=` /// * `Lt` -> `< ` /// * `Le` -> `<=` /// * `Ge` -> `>=` /// * `Gt` -> `> ` /// /// Note: Some comparison operators also support other signs, /// such as `=` for `Eq` and `!` for `Ne`, /// these are never returned by this method however as the table above is used. /// /// # Examples /// /// ``` /// use version_compare::Cmp; /// /// assert_eq!(Cmp::Eq.sign(), "=="); /// assert_eq!(Cmp::Lt.sign(), "<"); /// assert_eq!(Cmp::Ge.flip().sign(), "<="); /// ``` pub fn sign(self) -> &'static str { match self { Cmp::Eq => "==", Cmp::Ne => "!=", Cmp::Lt => "<", Cmp::Le => "<=", Cmp::Ge => ">=", Cmp::Gt => ">", } } /// Get a factor (number) for this comparison operator. /// These factors can be useful for quick calculations. /// /// The following factor numbers are returned: /// /// * `Eq` _or_ `Ne` -> ` 0` /// * `Lt` _or_ `Le` -> `-1` /// * `Gt` _or_ `Ge` -> ` 1` /// /// # Examples /// /// ``` /// use version_compare::Version; /// /// let a = Version::from("1.2.3").unwrap(); /// let b = Version::from("1.3").unwrap(); /// /// assert_eq!(a.compare(&b).factor(), -1); /// assert_eq!(10 * b.compare(a).factor(), 10); /// ``` pub fn factor(self) -> i8 { match self { Cmp::Eq | Cmp::Ne => 0, Cmp::Lt | Cmp::Le => -1, Cmp::Gt | Cmp::Ge => 1, } } /// Get Rust's ordering for this comparison operator. /// /// The following comparison operators are supported: /// /// * `Eq` -> `Ordering::Equal` /// * `Lt` -> `Ordering::Less` /// * `Gt` -> `Ordering::Greater` /// /// For other comparison operators `None` is returned. /// /// # Examples /// /// ``` /// use std::cmp::Ordering; /// use version_compare::Version; /// /// let a = Version::from("1.2.3").unwrap(); /// let b = Version::from("1.3").unwrap(); /// /// assert_eq!(a.compare(b).ord().unwrap(), Ordering::Less); /// ``` pub fn ord(self) -> Option { match self { Cmp::Eq => Some(Ordering::Equal), Cmp::Lt => Some(Ordering::Less), Cmp::Gt => Some(Ordering::Greater), _ => None, } } } #[cfg_attr(tarpaulin, skip)] #[cfg(test)] mod tests { use std::cmp::Ordering; use super::Cmp; #[test] fn from_sign() { // Normal signs assert_eq!(Cmp::from_sign("==").unwrap(), Cmp::Eq); assert_eq!(Cmp::from_sign("=").unwrap(), Cmp::Eq); assert_eq!(Cmp::from_sign("!=").unwrap(), Cmp::Ne); assert_eq!(Cmp::from_sign("!").unwrap(), Cmp::Ne); assert_eq!(Cmp::from_sign("<>").unwrap(), Cmp::Ne); assert_eq!(Cmp::from_sign("<").unwrap(), Cmp::Lt); assert_eq!(Cmp::from_sign("<=").unwrap(), Cmp::Le); assert_eq!(Cmp::from_sign(">=").unwrap(), Cmp::Ge); assert_eq!(Cmp::from_sign(">").unwrap(), Cmp::Gt); // Exceptional cases assert_eq!(Cmp::from_sign(" <= ").unwrap(), Cmp::Le); assert_eq!(Cmp::from_sign("*"), Err(())); } #[test] fn from_name() { // Normal names assert_eq!(Cmp::from_name("eq").unwrap(), Cmp::Eq); assert_eq!(Cmp::from_name("ne").unwrap(), Cmp::Ne); assert_eq!(Cmp::from_name("lt").unwrap(), Cmp::Lt); assert_eq!(Cmp::from_name("le").unwrap(), Cmp::Le); assert_eq!(Cmp::from_name("ge").unwrap(), Cmp::Ge); assert_eq!(Cmp::from_name("gt").unwrap(), Cmp::Gt); // Exceptional cases assert_eq!(Cmp::from_name(" Le ").unwrap(), Cmp::Le); assert_eq!(Cmp::from_name("abc"), Err(())); } #[test] fn from_ord() { assert_eq!(Cmp::from_ord(Ordering::Less), Cmp::Lt); assert_eq!(Cmp::from_ord(Ordering::Equal), Cmp::Eq); assert_eq!(Cmp::from_ord(Ordering::Greater), Cmp::Gt); } #[test] fn name() { assert_eq!(Cmp::Eq.name(), "eq"); assert_eq!(Cmp::Ne.name(), "ne"); assert_eq!(Cmp::Lt.name(), "lt"); assert_eq!(Cmp::Le.name(), "le"); assert_eq!(Cmp::Ge.name(), "ge"); assert_eq!(Cmp::Gt.name(), "gt"); } #[test] fn invert() { assert_eq!(Cmp::Ne.invert(), Cmp::Eq); assert_eq!(Cmp::Eq.invert(), Cmp::Ne); assert_eq!(Cmp::Ge.invert(), Cmp::Lt); assert_eq!(Cmp::Gt.invert(), Cmp::Le); assert_eq!(Cmp::Lt.invert(), Cmp::Ge); assert_eq!(Cmp::Le.invert(), Cmp::Gt); } #[test] fn opposite() { assert_eq!(Cmp::Eq.opposite(), Cmp::Ne); assert_eq!(Cmp::Ne.opposite(), Cmp::Eq); assert_eq!(Cmp::Lt.opposite(), Cmp::Gt); assert_eq!(Cmp::Le.opposite(), Cmp::Ge); assert_eq!(Cmp::Ge.opposite(), Cmp::Le); assert_eq!(Cmp::Gt.opposite(), Cmp::Lt); } #[test] fn flip() { assert_eq!(Cmp::Eq.flip(), Cmp::Eq); assert_eq!(Cmp::Ne.flip(), Cmp::Ne); assert_eq!(Cmp::Lt.flip(), Cmp::Gt); assert_eq!(Cmp::Le.flip(), Cmp::Ge); assert_eq!(Cmp::Ge.flip(), Cmp::Le); assert_eq!(Cmp::Gt.flip(), Cmp::Lt); } #[test] fn sign() { assert_eq!(Cmp::Eq.sign(), "=="); assert_eq!(Cmp::Ne.sign(), "!="); assert_eq!(Cmp::Lt.sign(), "<"); assert_eq!(Cmp::Le.sign(), "<="); assert_eq!(Cmp::Ge.sign(), ">="); assert_eq!(Cmp::Gt.sign(), ">"); } #[test] fn factor() { assert_eq!(Cmp::Eq.factor(), 0); assert_eq!(Cmp::Ne.factor(), 0); assert_eq!(Cmp::Lt.factor(), -1); assert_eq!(Cmp::Le.factor(), -1); assert_eq!(Cmp::Ge.factor(), 1); assert_eq!(Cmp::Gt.factor(), 1); } #[test] fn ord() { assert_eq!(Cmp::Eq.ord(), Some(Ordering::Equal)); assert_eq!(Cmp::Ne.ord(), None); assert_eq!(Cmp::Lt.ord(), Some(Ordering::Less)); assert_eq!(Cmp::Le.ord(), None); assert_eq!(Cmp::Ge.ord(), None); assert_eq!(Cmp::Gt.ord(), Some(Ordering::Greater)); } } version-compare-0.1.1/src/compare.rs000064400000000000000000000065201046102023000155220ustar 00000000000000//! Version compare module, with useful static comparison methods. use crate::version::Version; use crate::Cmp; /// Compare two version number strings to each other. /// /// This compares version `a` to version `b`, and returns whether version `a` is greater, less /// or equal to version `b`. /// /// If either version number string is invalid an error is returned. /// /// One of the following operators is returned: /// /// * `Cmp::Eq` /// * `Cmp::Lt` /// * `Cmp::Gt` /// /// # Examples /// /// ``` /// use version_compare::{Cmp, compare}; /// /// assert_eq!(compare("1.2.3", "1.2.3"), Ok(Cmp::Eq)); /// assert_eq!(compare("1.2.3", "1.2.4"), Ok(Cmp::Lt)); /// assert_eq!(compare("1", "0.1"), Ok(Cmp::Gt)); /// ``` #[allow(clippy::result_map_unit_fn)] pub fn compare(a: A, b: B) -> Result where A: AsRef, B: AsRef, { let a = Version::from(a.as_ref()).ok_or(())?; let b = Version::from(b.as_ref()).ok_or(())?; Ok(a.compare(b)) } /// Compare two version number strings to each other and test against the given comparison /// `operator`. /// /// If either version number string is invalid an error is returned. /// /// # Examples /// /// ``` /// use version_compare::{Cmp, compare_to}; /// /// assert!(compare_to("1.2.3", "1.2.3", Cmp::Eq).unwrap()); /// assert!(compare_to("1.2.3", "1.2.3", Cmp::Le).unwrap()); /// assert!(compare_to("1.2.3", "1.2.4", Cmp::Lt).unwrap()); /// assert!(compare_to("1", "0.1", Cmp::Gt).unwrap()); /// assert!(compare_to("1", "0.1", Cmp::Ge).unwrap()); /// ``` #[allow(clippy::result_map_unit_fn)] pub fn compare_to(a: A, b: B, operator: Cmp) -> Result where A: AsRef, B: AsRef, { let a = Version::from(a.as_ref()).ok_or(())?; let b = Version::from(b.as_ref()).ok_or(())?; Ok(a.compare_to(b, operator)) } #[cfg_attr(tarpaulin, skip)] #[cfg(test)] mod tests { use crate::test::{COMBIS, COMBIS_ERROR}; use crate::Cmp; #[test] fn compare() { // Compare each version in the version set for entry in COMBIS { assert_eq!( super::compare(entry.0, entry.1), Ok(entry.2), "Testing that {} is {} {}", entry.0, entry.2.sign(), entry.1, ); } // Compare each error version in the version set for entry in COMBIS_ERROR { let result = super::compare(entry.0, entry.1); if result.is_ok() { assert!(result != Ok(entry.2)); } } } #[test] fn compare_to() { // Compare each version in the version set for entry in COMBIS { // Test assert!(super::compare_to(entry.0, entry.1, entry.2).unwrap()); // Make sure the inverse operator is not correct assert_eq!( super::compare_to(entry.0, entry.1, entry.2.invert()).unwrap(), false, ); } // Compare each error version in the version set for entry in COMBIS_ERROR { let result = super::compare_to(entry.0, entry.1, entry.2); if result.is_ok() { assert!(!result.unwrap()) } } // Assert an exceptional case, compare to not equal assert!(super::compare_to("1.2.3", "1.2", Cmp::Ne).unwrap()); } } version-compare-0.1.1/src/lib.rs000064400000000000000000000067751046102023000146560ustar 00000000000000//! Rust library to easily compare version numbers with no specific format, and test against various comparison operators. //! //! Comparing version numbers is hard, especially with weird version number formats. //! //! This library helps you to easily compare any kind of version number with no //! specific format using a best-effort approach. //! Two version numbers can be compared to each other to get a comparison operator //! (`<`, `==`, `>`), or test them against a comparison operator. //! //! Along with version comparison, the library provides various other tools for //! working with version numbers. //! //! Inspired by PHPs [version_compare()](http://php.net/manual/en/function.version-compare.php). //! //! ### Formats //! //! Version numbers that would parse successfully include: //! `1`, `3.10.4.1`, `1.2.alpha`, `1.2.dev.4`, ` `, ` . -32 . 1`, `MyApp 3.2.0 / build 0932` ... //! //! See a list of how version numbers compare [here](https://github.com/timvisee/version-compare/blob/411ed7135741ed7cf2fcf4919012fb5412dc122b/src/test.rs#L50-L103). //! //! ## Examples //! //! [example.rs](examples/example.rs): //! ```rust //! use version_compare::{compare, compare_to, Cmp, Version}; //! //! let a = "1.2"; //! let b = "1.5.1"; //! //! // The following comparison operators are used: //! // - Cmp::Eq -> Equal //! // - Cmp::Ne -> Not equal //! // - Cmp::Lt -> Less than //! // - Cmp::Le -> Less than or equal //! // - Cmp::Ge -> Greater than or equal //! // - Cmp::Gt -> Greater than //! //! // Easily compare version strings //! assert_eq!(compare(a, b), Ok(Cmp::Lt)); //! assert_eq!(compare_to(a, b, Cmp::Le), Ok(true)); //! assert_eq!(compare_to(a, b, Cmp::Gt), Ok(false)); //! //! // Parse and wrap version strings as a Version //! let a = Version::from(a).unwrap(); //! let b = Version::from(b).unwrap(); //! //! // The Version can easily be compared with //! assert_eq!(a < b, true); //! assert_eq!(a <= b, true); //! assert_eq!(a > b, false); //! assert_eq!(a != b, true); //! assert_eq!(a.compare(&b), Cmp::Lt); //! assert_eq!(a.compare_to(&b, Cmp::Lt), true); //! //! // Or match the comparison operators //! match a.compare(b) { //! Cmp::Lt => println!("Version a is less than b"), //! Cmp::Eq => println!("Version a is equal to b"), //! Cmp::Gt => println!("Version a is greater than b"), //! _ => unreachable!(), //! } //! ``` //! //! See the [`examples`](https://github.com/timvisee/version-compare/tree/master/examples) directory for more. //! //! ## Features //! //! * Compare version numbers, get: `<`, `==`, `>` //! * Compare against a comparison operator //! (`<`, `<=`, `==`, `!=`, `>=`, `>`) //! * Parse complex and unspecified formats //! * Static, standalone methods to easily compare version strings in a single line //! of code //! //! ### Semver //! //! Version numbers using the [semver](http://semver.org/) format are compared //! correctly with no additional configuration. //! //! If your version number strings follow this exact format you may be better off //! using the [`semver`](https://crates.io/crates/semver) crate for more format //! specific features. //! //! If that isn't certain however, `version-compare` makes comparing a breeze. //! //! _[View complete README](https://github.com/timvisee/version-compare/blob/master/README.md)_ mod cmp; mod compare; mod manifest; mod part; mod version; #[cfg(test)] mod test; // Re-exports pub use crate::cmp::Cmp; pub use crate::compare::{compare, compare_to}; pub use crate::manifest::Manifest; pub use crate::part::Part; pub use crate::version::Version; version-compare-0.1.1/src/manifest.rs000064400000000000000000000041411046102023000156770ustar 00000000000000//! Module for the version manifest. //! //! A version manifest can be used to configure and specify how versions are parsed and compared. //! For example, you can configure the maximum depth of a version number, and set whether text //! parts are ignored in a version string. /// Version manifest (configuration). /// /// A manifest (configuration) that is used respectively when parsing and comparing version strings. /// /// # Examples /// /// ```rust /// use version_compare::{Manifest, Version}; /// /// // Create manifest with max depth of 2 /// let mut manifest = Manifest::default(); /// manifest.max_depth = Some(2); /// /// // Version strings equal with manifest because we compare up-to 2 parts deep /// let a = Version::from_manifest("1.0.1", &manifest).unwrap(); /// let b = Version::from_manifest("1.0.2", &manifest).unwrap(); /// assert!(a == b); /// ``` #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct Manifest { /// The maximum depth of a version number. /// /// This specifies the maximum number of parts. There is no limit if `None` is set. pub max_depth: Option, /// Whether to ignore text parts in version strings. pub ignore_text: bool, } /// Version manifest implementation. impl Manifest { /// Check whether there's a maximum configured depth. /// /// # Examples /// /// ``` /// use version_compare::Manifest; /// /// let mut manifest = Manifest::default(); /// /// assert!(!manifest.has_max_depth()); /// /// manifest.max_depth = Some(3); /// assert!(manifest.has_max_depth()); /// ``` pub fn has_max_depth(&self) -> bool { self.max_depth.is_some() && self.max_depth.unwrap() > 0 } } #[cfg_attr(tarpaulin, skip)] #[cfg(test)] mod tests { use super::Manifest; #[test] fn has_max_depth() { let mut manifest = Manifest::default(); manifest.max_depth = Some(1); assert!(manifest.has_max_depth()); manifest.max_depth = Some(3); assert!(manifest.has_max_depth()); manifest.max_depth = None; assert!(!manifest.has_max_depth()); } } version-compare-0.1.1/src/part.rs000064400000000000000000000022301046102023000150340ustar 00000000000000//! Version part module. //! //! A module that provides the `Part` enum, with the specification of all available version //! parts. Each version string is broken down into these version parts when being parsed to a //! `Version`. use std::fmt; /// Version string part enum. /// /// Each version string is broken down into these version parts when being parsed to a `Version`. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Part<'a> { /// Numeric part, most common in version strings. /// /// Holds the numerical value. Number(i32), /// A text part. /// /// These parts usually hold text with an yet unknown definition. Holds the string slice. Text(&'a str), } impl<'a> fmt::Display for Part<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Part::Number(n) => write!(f, "{}", n), Part::Text(t) => write!(f, "{}", t), } } } #[cfg_attr(tarpaulin, skip)] #[cfg(test)] mod tests { use super::Part; #[test] fn display() { assert_eq!(format!("{}", Part::Number(123)), "123"); assert_eq!(format!("{}", Part::Text("123")), "123"); } } version-compare-0.1.1/src/test.rs000064400000000000000000000102361046102023000150520ustar 00000000000000use crate::Cmp; /// Struct containing a version number with some meta data. /// Such a set can be used for testing. /// /// # Arguments /// /// - `0`: The version string. /// - `1`: Number of version parts. pub struct Version(pub &'static str, pub usize); /// List of version numbers with metadata for dynamic tests pub const VERSIONS: &'static [Version] = &[ Version("1", 1), Version("1.2", 2), Version("1.2.3.4", 4), Version("1.2.3.4.5.6.7.8", 8), Version("0", 1), Version("0.0.0", 3), Version("1.0.0", 3), Version("0.0.1", 3), Version("", 0), Version(".", 0), Version("...", 0), Version("1.2.dev", 3), Version("1.2-dev", 3), Version("1.2.alpha.4", 4), Version("1.2-alpha-4", 4), Version("snapshot.1.2", 3), Version("snapshot-1.2", 3), // Issue: https://github.com/timvisee/version-compare/issues/26 Version("0.0.1-test.0222426166a", 6), Version("0.0.1-test.0222426166565421816516584651684351354", 5), Version("0.0.1-test.02224261665a", 5), Version("0.0.1-test.02224261665d7b1b689816d12f6bcacb", 5), ]; /// List of version numbers that contain errors with metadata for dynamic tests pub const VERSIONS_ERROR: &'static [Version] = &[ Version("abc", 1), Version("alpha.dev.snapshot", 3), Version("test. .snapshot", 3), ]; /// Struct containing two version numbers, and the comparison operator. /// Such a set can be used for testing. /// /// # Arguments /// /// - `0`: The main version. /// - `1`: The other version. /// - `2`: The comparison operator. pub struct VersionCombi(pub &'static str, pub &'static str, pub Cmp); /// List of version combinations for dynamic tests pub const COMBIS: &'static [VersionCombi] = &[ VersionCombi("1", "1", Cmp::Eq), VersionCombi("1.0.0.0", "1", Cmp::Eq), VersionCombi("1", "1.0.0.0", Cmp::Eq), VersionCombi("0", "0", Cmp::Eq), VersionCombi("0.0.0", "0", Cmp::Eq), VersionCombi("0", "0.0.0", Cmp::Eq), VersionCombi("", "", Cmp::Eq), VersionCombi("", "0.0", Cmp::Eq), VersionCombi("0.0", "", Cmp::Eq), VersionCombi("", "0.1", Cmp::Lt), VersionCombi("0.1", "", Cmp::Gt), VersionCombi("1.2.3", "1.2.3", Cmp::Eq), VersionCombi("1.2.3", "1.2.4", Cmp::Lt), VersionCombi("1.0.0.1", "1.0.0.0", Cmp::Gt), VersionCombi("1.0.0.0", "1.0.0.1", Cmp::Lt), VersionCombi("1.2.3.4", "1.2", Cmp::Gt), VersionCombi("1.2", "1.2.3.4", Cmp::Lt), VersionCombi("1.2.3.4", "2", Cmp::Lt), VersionCombi("2", "1.2.3.4", Cmp::Gt), VersionCombi("123", "123", Cmp::Eq), VersionCombi("123", "1.2.3", Cmp::Gt), VersionCombi("1.2.3", "123", Cmp::Lt), VersionCombi("1.1.2", "1.1.30-dev", Cmp::Lt), VersionCombi("1.2.3", "1.2.3.alpha", Cmp::Gt), VersionCombi("1.2.3", "1.2.3-dev", Cmp::Gt), VersionCombi("1.2.3 RC0", "1.2.3 rc1", Cmp::Lt), VersionCombi("1.2.3 rc2", "1.2.3 RC99", Cmp::Lt), VersionCombi("1.2.3 RC3", "1.2.3 RC1", Cmp::Gt), VersionCombi("1.2.3a", "1.2.3b", Cmp::Lt), VersionCombi("1.2.3b", "1.2.3a", Cmp::Gt), VersionCombi("1.2.3.dev", "1.2.3.alpha", Cmp::Gt), VersionCombi("1.2.3-dev", "1.2.3-alpha", Cmp::Gt), VersionCombi("1.2.3.dev.1", "1.2.3.alpha", Cmp::Gt), VersionCombi("1.2.3-dev-1", "1.2.3-alpha", Cmp::Gt), VersionCombi("version-compare 3.2.0 / build 0932", "3.2.5", Cmp::Lt), VersionCombi("version-compare 3.2.0 / build 0932", "3.1.1", Cmp::Gt), VersionCombi( "version-compare 1.4.1 / build 0043", "version-compare 1.4.1 / build 0043", Cmp::Eq, ), VersionCombi( "version-compare 1.4.1 / build 0042", "version-compare 1.4.1 / build 0043", Cmp::Lt, ), // Issue: https://github.com/timvisee/version-compare/issues/24 VersionCombi("7.2p1", "7.1", Cmp::Gt), // TODO: inspect these cases VersionCombi("snapshot.1.2.3", "1.2.3.alpha", Cmp::Lt), VersionCombi("snapshot-1.2.3", "1.2.3-alpha", Cmp::Lt), ]; /// List of invalid version combinations for dynamic tests pub const COMBIS_ERROR: &'static [VersionCombi] = &[ VersionCombi("1.2.3", "1.2.3", Cmp::Lt), VersionCombi("1.2", "1.2.0.0", Cmp::Ne), VersionCombi("1.2.3.dev", "dev", Cmp::Eq), VersionCombi("snapshot", "1", Cmp::Lt), ]; version-compare-0.1.1/src/version.rs000064400000000000000000000541431046102023000155650ustar 00000000000000//! Version module, which provides the `Version` struct as parsed version representation. //! //! Version numbers in the form of a string are parsed to a `Version` first, before any comparison //! is made. This struct provides many methods and features for easy comparison, probing and other //! things. use std::borrow::Borrow; use std::cmp::Ordering; use std::fmt; use std::iter::Peekable; use std::slice::Iter; use crate::{Cmp, Manifest, Part}; /// Version struct, wrapping a string, providing useful comparison functions. /// /// A version in string format can be parsed using methods like `Version::from("1.2.3");`, /// returning a `Result` with the parse result. /// /// The original version string can be accessed using `version.as_str()`. A `Version` that isn't /// derrived from a version string returns a generated string. /// /// The struct provides many methods for easy comparison and probing. /// /// # Examples /// /// ``` /// use version_compare::{Version}; /// /// let ver = Version::from("1.2.3").unwrap(); /// ``` #[derive(Clone, Eq)] pub struct Version<'a> { version: &'a str, parts: Vec>, manifest: Option<&'a Manifest>, } impl<'a> Version<'a> { /// Create a `Version` instance from a version string. /// /// The version string should be passed to the `version` parameter. /// /// # Examples /// /// ``` /// use version_compare::{Cmp, Version}; /// /// let a = Version::from("1.2.3").unwrap(); /// let b = Version::from("1.3.0").unwrap(); /// /// assert_eq!(a.compare(b), Cmp::Lt); /// ``` pub fn from(version: &'a str) -> Option { Some(Version { version, parts: split_version_str(version, None)?, manifest: None, }) } /// Create a `Version` instance from already existing parts /// /// /// # Examples /// /// ``` /// use version_compare::{Cmp, Version, Part}; /// /// let ver = Version::from_parts("1.0", vec![Part::Number(1), Part::Number(0)]); /// ``` pub fn from_parts(version: &'a str, parts: Vec>) -> Self { Version { version, parts, manifest: None, } } /// Create a `Version` instance from a version string with the given `manifest`. /// /// The version string should be passed to the `version` parameter. /// /// # Examples /// /// ``` /// use version_compare::{Cmp, Version, Manifest}; /// /// let manifest = Manifest::default(); /// let ver = Version::from_manifest("1.2.3", &manifest).unwrap(); /// /// assert_eq!(ver.compare(Version::from("1.2.3").unwrap()), Cmp::Eq); /// ``` pub fn from_manifest(version: &'a str, manifest: &'a Manifest) -> Option { Some(Version { version, parts: split_version_str(version, Some(manifest))?, manifest: Some(manifest), }) } /// Get the version manifest, if available. /// /// # Examples /// /// ``` /// use version_compare::Version; /// /// let version = Version::from("1.2.3").unwrap(); /// /// if version.has_manifest() { /// println!( /// "Maximum version part depth is {} for this version", /// version.manifest().unwrap().max_depth.unwrap_or(0), /// ); /// } else { /// println!("Version has no manifest"); /// } /// ``` pub fn manifest(&self) -> Option<&Manifest> { self.manifest } /// Check whether this version has a manifest. /// /// # Examples /// /// ``` /// use version_compare::Version; /// /// let version = Version::from("1.2.3").unwrap(); /// /// if version.has_manifest() { /// println!("This version does have a manifest"); /// } else { /// println!("This version does not have a manifest"); /// } /// ``` pub fn has_manifest(&self) -> bool { self.manifest().is_some() } /// Set the version manifest. /// /// # Examples /// /// ``` /// use version_compare::{Version, Manifest}; /// /// let manifest = Manifest::default(); /// let mut version = Version::from("1.2.3").unwrap(); /// /// version.set_manifest(Some(&manifest)); /// ``` pub fn set_manifest(&mut self, manifest: Option<&'a Manifest>) { self.manifest = manifest; // TODO: Re-parse the version string, because the manifest might have changed. } /// Get the original version string. /// /// # Examples /// /// ``` /// use version_compare::Version; /// /// let ver = Version::from("1.2.3").unwrap(); /// /// assert_eq!(ver.as_str(), "1.2.3"); /// ``` pub fn as_str(&self) -> &str { self.version } /// Get a specific version part by it's `index`. /// An error is returned if the given index is out of bound. /// /// # Examples /// /// ``` /// use version_compare::{Version, Part}; /// /// let ver = Version::from("1.2.3").unwrap(); /// /// assert_eq!(ver.part(0), Ok(Part::Number(1))); /// assert_eq!(ver.part(1), Ok(Part::Number(2))); /// assert_eq!(ver.part(2), Ok(Part::Number(3))); /// ``` #[allow(clippy::result_map_unit_fn)] pub fn part(&self, index: usize) -> Result, ()> { // Make sure the index is in-bound if index >= self.parts.len() { return Err(()); } Ok(self.parts[index]) } /// Get a vector of all version parts. /// /// # Examples /// /// ``` /// use version_compare::{Version, Part}; /// /// let ver = Version::from("1.2.3").unwrap(); /// /// assert_eq!(ver.parts(), [ /// Part::Number(1), /// Part::Number(2), /// Part::Number(3) /// ]); /// ``` pub fn parts(&self) -> &[Part<'a>] { self.parts.as_slice() } /// Compare this version to the given `other` version. /// /// This method returns one of the following comparison operators: /// /// * `Lt` /// * `Eq` /// * `Gt` /// /// Other comparison operators can be used when comparing, but aren't returned by this method. /// /// # Examples: /// /// ``` /// use version_compare::{Cmp, Version}; /// /// let a = Version::from("1.2").unwrap(); /// let b = Version::from("1.3.2").unwrap(); /// /// assert_eq!(a.compare(&b), Cmp::Lt); /// assert_eq!(b.compare(&a), Cmp::Gt); /// assert_eq!(a.compare(&a), Cmp::Eq); /// ``` pub fn compare(&self, other: V) -> Cmp where V: Borrow>, { compare_iter( self.parts.iter().peekable(), other.borrow().parts.iter().peekable(), ) } /// Compare this version to the given `other` version, /// and check whether the given comparison operator is valid. /// /// All comparison operators can be used. /// /// # Examples: /// /// ``` /// use version_compare::{Cmp, Version}; /// /// let a = Version::from("1.2").unwrap(); /// let b = Version::from("1.3.2").unwrap(); /// /// assert!(a.compare_to(&b, Cmp::Lt)); /// assert!(a.compare_to(&b, Cmp::Le)); /// assert!(a.compare_to(&a, Cmp::Eq)); /// assert!(a.compare_to(&a, Cmp::Le)); /// ``` pub fn compare_to(&self, other: V, operator: Cmp) -> bool where V: Borrow>, { match self.compare(other) { Cmp::Eq => matches!(operator, Cmp::Eq | Cmp::Le | Cmp::Ge), Cmp::Lt => matches!(operator, Cmp::Ne | Cmp::Lt | Cmp::Le), Cmp::Gt => matches!(operator, Cmp::Ne | Cmp::Gt | Cmp::Ge), _ => unreachable!(), } } } impl<'a> fmt::Display for Version<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.version) } } // Show just the version component parts as debug output impl<'a> fmt::Debug for Version<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if f.alternate() { write!(f, "{:#?}", self.parts) } else { write!(f, "{:?}", self.parts) } } } /// Implement the partial ordering trait for the version struct, to easily allow version comparison. impl<'a> PartialOrd for Version<'a> { fn partial_cmp(&self, other: &Self) -> Option { Some(self.compare(other).ord().unwrap()) } } /// Implement the partial equality trait for the version struct, to easily allow version comparison. impl<'a> PartialEq for Version<'a> { fn eq(&self, other: &Self) -> bool { self.compare_to(other, Cmp::Eq) } } /// Split the given version string, in it's version parts. fn split_version_str<'a>( version: &'a str, manifest: Option<&'a Manifest>, ) -> Option>> { // Split the version string, and create a vector to put the parts in let split = version.split(|c| !char::is_alphanumeric(c)); let mut parts = Vec::new(); // Get the manifest to follow let mut used_manifest = &Manifest::default(); if let Some(m) = manifest { used_manifest = m; } // Loop over the parts, and parse them for part in split { // We may not go over the maximum depth if used_manifest.max_depth.is_some() && parts.len() >= used_manifest.max_depth.unwrap_or(0) { break; } // Skip empty parts if part.is_empty() { continue; } // Try to parse the value as an number match part.parse::() { Ok(number) => { // Push the number part to the vector parts.push(Part::Number(number)); } Err(_) => { // Ignore text parts if specified if used_manifest.ignore_text { continue; } // Numbers suffixed by text should be split into a number and text as well, // if the number overflows, handle it as text let split_at = part .char_indices() .take(part.len() - 1) .take_while(|(_, c)| c.is_ascii_digit()) .map(|(i, c)| (i, c, part.chars().nth(i + 1).unwrap())) .filter(|(_, _, b)| b.is_alphabetic()) .map(|(i, _, _)| i) .next(); if let Some(at) = split_at { if let Ok(n) = part[..=at].parse() { parts.push(Part::Number(n)); parts.push(Part::Text(&part[at + 1..])); } else { parts.push(Part::Text(part)); } continue; } // Push the text part to the vector parts.push(Part::Text(part)) } } } // The version must contain a number part if any part was parsed if !parts.is_empty() && !parts.iter().any(|p| matches!(p, Part::Number(_))) { return None; } // Return the list of parts Some(parts) } /// Compare two version numbers based on the iterators of their version parts. /// /// This method returns one of the following comparison operators: /// /// * `Lt` /// * `Eq` /// * `Gt` /// /// Other comparison operators can be used when comparing, but aren't returned by this method. fn compare_iter<'a>( mut iter: Peekable>>, mut other_iter: Peekable>>, ) -> Cmp { // Iterate through the parts of this version let mut other_part: Option<&Part>; // Iterate over the iterator, without consuming it for part in &mut iter { // Get the part for the other version other_part = other_iter.next(); // If there are no parts left in the other version, try to determine the result if other_part.is_none() { // In the main version: if the current part is zero, continue to the next one match part { Part::Number(num) => { if *num == 0 { continue; } } Part::Text(_) => return Cmp::Lt, } // The main version is greater return Cmp::Gt; } // Match both parts as numbers to destruct their numerical values if let Part::Number(num) = part { if let Part::Number(other) = other_part.unwrap() { // Compare the numbers match num { n if n < other => return Cmp::Lt, n if n > other => return Cmp::Gt, _ => continue, } } } // Match both parts as strings else if let Part::Text(val) = part { if let Part::Text(other_val) = other_part.unwrap() { // normalize case let (val_lwr, other_val_lwr) = (val.to_lowercase(), other_val.to_lowercase()); // compare text: for instance, "RC1" will be less than "RC2", so this works out. #[allow(clippy::comparison_chain)] if val_lwr < other_val_lwr { return Cmp::Lt; } else if val_lwr > other_val_lwr { return Cmp::Gt; } } } } // Check whether we should iterate over the other iterator, if it has any items left match other_iter.peek() { // Compare based on the other iterator Some(_) => compare_iter(other_iter, iter).flip(), // Nothing more to iterate over, the versions should be equal None => Cmp::Eq, } } #[cfg_attr(tarpaulin, skip)] #[cfg(test)] mod tests { use std::cmp; use crate::test::{COMBIS, VERSIONS, VERSIONS_ERROR}; use crate::{Cmp, Manifest, Part}; use super::Version; #[test] // TODO: This doesn't really test whether this method fully works fn from() { // Test whether parsing works for each test version for version in VERSIONS { assert!(Version::from(version.0).is_some()); } // Test whether parsing works for each test invalid version for version in VERSIONS_ERROR { assert!(Version::from(version.0).is_none()); } } #[test] // TODO: This doesn't really test whether this method fully works fn from_manifest() { // Create a manifest let manifest = Manifest::default(); // Test whether parsing works for each test version for version in VERSIONS { assert_eq!( Version::from_manifest(version.0, &manifest) .unwrap() .manifest, Some(&manifest) ); } // Test whether parsing works for each test invalid version for version in VERSIONS_ERROR { assert!(Version::from_manifest(version.0, &manifest).is_none()); } } #[test] fn manifest() { let manifest = Manifest::default(); let mut version = Version::from("1.2.3").unwrap(); version.manifest = Some(&manifest); assert_eq!(version.manifest(), Some(&manifest)); version.manifest = None; assert_eq!(version.manifest(), None); } #[test] fn has_manifest() { let manifest = Manifest::default(); let mut version = Version::from("1.2.3").unwrap(); version.manifest = Some(&manifest); assert!(version.has_manifest()); version.manifest = None; assert!(!version.has_manifest()); } #[test] fn set_manifest() { let manifest = Manifest::default(); let mut version = Version::from("1.2.3").unwrap(); version.set_manifest(Some(&manifest)); assert_eq!(version.manifest, Some(&manifest)); version.set_manifest(None); assert_eq!(version.manifest, None); } #[test] fn as_str() { // Test for each test version for version in VERSIONS { // The input version string must be the same as the returned string assert_eq!(Version::from(version.0).unwrap().as_str(), version.0); } } #[test] fn part() { // Test for each test version for version in VERSIONS { // Create a version object let ver = Version::from(version.0).unwrap(); // Loop through each part for i in 0..version.1 { assert_eq!(ver.part(i), Ok(ver.parts[i])); } // A value outside the range must return an error assert!(ver.part(version.1).is_err()); } } #[test] fn parts() { // Test for each test version for version in VERSIONS { // The number of parts must match assert_eq!(Version::from(version.0).unwrap().parts().len(), version.1); } } #[test] fn parts_max_depth() { // Create a manifest let mut manifest = Manifest::default(); // Loop through a range of numbers for depth in 0..5 { // Set the maximum depth manifest.max_depth = if depth > 0 { Some(depth) } else { None }; // Test for each test version with the manifest for version in VERSIONS { // Create a version object, and count it's parts let ver = Version::from_manifest(&version.0, &manifest); // Some versions might be none, because not all of the start with a number when the // maximum depth is 1. A version string with only text isn't allowed, // resulting in none. if ver.is_none() { continue; } // Get the part count let count = ver.unwrap().parts().len(); // The number of parts must match if depth == 0 { assert_eq!(count, version.1); } else { assert_eq!(count, cmp::min(version.1, depth)); } } } } #[test] fn parts_ignore_text() { // Create a manifest let mut manifest = Manifest::default(); // Try this for true and false for ignore in vec![true, false] { // Set to ignore text manifest.ignore_text = ignore; // Keep track whether any version passed with text let mut had_text = false; // Test each test version for version in VERSIONS { // Create a version instance, and get it's parts let ver = Version::from_manifest(&version.0, &manifest).unwrap(); // Loop through all version parts for part in ver.parts() { match part { Part::Text(_) => { // Set the flag had_text = true; // Break the loop if we already reached text when not ignored if !ignore { break; } } _ => {} } } } // Assert had text assert_eq!(had_text, !ignore); } } #[test] fn compare() { // Compare each version in the version set for entry in COMBIS { // Get both versions let a = Version::from(entry.0).unwrap(); let b = Version::from(entry.1).unwrap(); // Compare them assert_eq!( a.compare(b), entry.2.clone(), "Testing that {} is {} {}", entry.0, entry.2.sign(), entry.1, ); } } #[test] fn compare_to() { // Compare each version in the version set for entry in COMBIS { // Get both versions let a = Version::from(entry.0).unwrap(); let b = Version::from(entry.1).unwrap(); // Test normally and inverse assert!(a.compare_to(&b, entry.2)); assert!(!a.compare_to(b, entry.2.invert())); } // Assert an exceptional case, compare to not equal assert!(Version::from("1.2") .unwrap() .compare_to(Version::from("1.2.3").unwrap(), Cmp::Ne,)); } #[test] fn display() { assert_eq!(format!("{}", Version::from("1.2.3").unwrap()), "1.2.3"); } #[test] fn debug() { assert_eq!( format!("{:?}", Version::from("1.2.3").unwrap()), "[Number(1), Number(2), Number(3)]", ); assert_eq!( format!("{:#?}", Version::from("1.2.3").unwrap()), "[\n Number(\n 1,\n ),\n Number(\n 2,\n ),\n Number(\n 3,\n ),\n]", ); } #[test] fn partial_cmp() { // Compare each version in the version set for entry in COMBIS { // Get both versions let a = Version::from(entry.0).unwrap(); let b = Version::from(entry.1).unwrap(); // Compare and assert match entry.2 { Cmp::Eq => assert!(a == b), Cmp::Lt => assert!(a < b), Cmp::Gt => assert!(a > b), _ => {} } } } #[test] fn partial_eq() { // Compare each version in the version set for entry in COMBIS { // Skip entries that are less or equal, or greater or equal match entry.2 { Cmp::Le | Cmp::Ge => continue, _ => {} } // Get both versions let a = Version::from(entry.0).unwrap(); let b = Version::from(entry.1).unwrap(); // Determine what the result should be let result = match entry.2 { Cmp::Eq => true, _ => false, }; // Test assert_eq!(a == b, result); } // Assert an exceptional case, compare to not equal assert!(Version::from("1.2").unwrap() != Version::from("1.2.3").unwrap()); } }