version-compare-0.0.10/.github/FUNDING.yml010066400017500001750000000001511354417705500163110ustar0000000000000000# Funding links github: - timvisee patreon: timvisee ko_fi: timvisee open_collective: tidelift: custom: version-compare-0.0.10/.gitignore010066400017500001750000000011321354417705500151240ustar0000000000000000# Rust language /target/ ## Remove Cargo.lock from gitignore if creating an executable, leave it for libraries ## More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock #Cargo.lock # JetBrains IDE .idea/ *.iml out gen !/.idea/dictionaries !/.idea/dictionaries/* # File-based project format: *.iws # IntelliJ /out/ # mpeltonen/sbt-idea plugin .idea_modules/ # JIRA plugin atlassian-ide-plugin.xml # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties # Vim .*.sw[po] version-compare-0.0.10/.travis.yml010066400017500001750000000010511354420762500152420ustar0000000000000000# Configuration for Travis CI language: rust sudo: required rust: - stable - beta - nightly # Dependencies for coverage addons: apt: packages: - libssl-dev - pkg-config - cmake - zlib1g-dev # Main build script: - cargo clean - cargo build - cargo test - cargo run --example example - cargo run --example minimal - cargo bench - cargo doc # Measure coverage, and upload to coveralls after_success: | cargo install cargo-tarpaulin -f cargo tarpaulin --ciserver travis-ci --coveralls $TRAVIS_JOB_ID version-compare-0.0.10/Cargo.toml.orig010066400017500001750000000011751354420651300160220ustar0000000000000000[package] name = "version-compare" version = "0.0.10" authors = ["timvisee "] license = "MIT" readme = "README.md" homepage = "https://github.com/timvisee/version-compare" repository = "https://github.com/timvisee/version-compare" documentation = "https://docs.rs/version-compare" description = "A Rust library to easily compare version numbers, and test them against various comparison operators." keywords = ["version", "compare", "comparison", "comparing"] categories = ["parser-implementations"] edition = "2018" [badges] travis-ci = { repository = "timvisee/version-compare" } [features] default = [] dev = [] version-compare-0.0.10/Cargo.toml0000644000000022020000000000000122540ustar00# 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 believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "version-compare" version = "0.0.10" authors = ["timvisee "] description = "A Rust library to easily compare version numbers, and test them against various comparison operators." homepage = "https://github.com/timvisee/version-compare" documentation = "https://docs.rs/version-compare" readme = "README.md" keywords = ["version", "compare", "comparison", "comparing"] categories = ["parser-implementations"] license = "MIT" repository = "https://github.com/timvisee/version-compare" [features] default = [] dev = [] [badges.travis-ci] repository = "timvisee/version-compare" version-compare-0.0.10/Cargo.toml.orig0000644000000022030000000000000132140ustar00# 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 believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "version-compare" version = "0.0.10" authors = ["timvisee "] description = "A Rust library to easily compare version numbers, and test them against various comparison operators." homepage = "https://github.com/timvisee/version-compare" documentation = "https://docs.rs/version-compare" readme = "README.md" keywords = ["version", "compare", "comparison", "comparing"] categories = ["parser-implementations"] license = "MIT" repository = "https://github.com/timvisee/version-compare" [features] default = [] dev = [] [badges.travis-ci] repository = "timvisee/version-compare" version-compare-0.0.10/LICENSE010066400017500001750000000020371354417662700141520ustar0000000000000000Copyright (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.0.10/README.md010066400017500001750000000151611354420650000144060ustar0000000000000000[![Build status on Travis CI][travis-master-badge]][travis-link] [![Built status on AppVeyor][appveyor-master-badge]][appveyor-master-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]][dependency-link] [![License][crate-license-badge]][crate-link] [crate-version-badge]: https://img.shields.io/crates/v/version-compare.svg [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 [coverage-badge]: https://coveralls.io/repos/github/timvisee/version-compare/badge.svg?branch=master [coverage-link]: https://coveralls.io/github/timvisee/version-compare?branch=master [dependency-badge]: https://img.shields.io/badge/dependencies-none!-green.svg [dependency-link]: https://libraries.io/github/timvisee/version-compare [docs]: https://docs.rs/version-compare [docs-badge]: https://docs.rs/version-compare/badge.svg # Rust library: version-compare > A Rust library to easily compare version numbers in any format, and test them against various comparison operators. Comparing version numbers is hard. Especially when version numbers get really complex, or when their formatting differs. This library helps you to easily compare any kind of version number with minimal code. Two version numbers can be compared to each other, to get a relevant comparison operator (`<`, `==`, `>`), or version numbers can be tested against any given comparison operator. Along with version comparison, the library also features other useful tools. For example: version numbers can be parsed to inspect a version number by it's bare numeric or text based parts. Inspired by PHPs [version_compare()](http://php.net/manual/en/function.version-compare.php). **Note:** This library is still a work in progress. See the list below for a list of currently available and future features. ### Version formats A list of version number examples that are parsed successfully: - `1` - `3.10.4.1` - `1.2.alpha` - `1.2.dev.4` - ` ` _(empty)_ - ` . -32 . 1` _(undefined formats)_ - `MyApp 3.2.0 / build 0932` _(complex formats, not fully functional yet)_ - _Many more and support for custom formats to come..._ ### Semver Version number formats like [_semver_](http://semver.org/) try to make version numbers consistent and manageable, there are too many projects however that don't follow such format. Version-compare makes working with them easy and supports semver formats out of the box with zero configuration. ## Features * Compare two version numbers, get: `<`, `==` or `>`. * Compare two version numbers against any comparison operator, get: `true` or `false`. * Parse complex and undefined version number formats. * Static, single-statement methods available. The following features will be added in a later version: * Support for text parts in version strings. * Version manifest, to specify detailed version number constraints. * Version ranges, and tests against them. * Support for operators in version strings, [npm-style](https://docs.npmjs.com/misc/semver), and tests against them. * Batch comparisons. ## Example This library is very easy to use. Here's a basic usage example: Cargo.toml: ```toml [dependencies] version-compare = "0.0.10" ``` [example.rs:](examples/example.rs) ```rust extern crate version_compare; use version_compare::{CompOp, Version, VersionCompare}; fn main() { // Define some version numbers let a = "1.2"; let b = "1.5.1"; // The following comparison operators are used: // - CompOp::Eq -> Equal // - CompOp::Ne -> Not equal // - CompOp::Lt -> Less than // - CompOp::Le -> Less than or equal // - CompOp::Ge -> Greater than or equal // - CompOp::Gt -> Greater than // Easily compare version strings assert_eq!(VersionCompare::compare(&a, &b).unwrap(), CompOp::Lt); assert_eq!(VersionCompare::compare_to(&a, &b, &CompOp::Le).unwrap(), true); assert_eq!(VersionCompare::compare_to(&a, &b, &CompOp::Gt).unwrap(), false); // Version string parsing let a_ver = Version::from(a).unwrap(); let b_ver = Version::from(b).unwrap(); // Directly compare parsed versions assert_eq!(a_ver < b_ver, true); assert_eq!(a_ver <= b_ver, true); assert_eq!(a_ver > b_ver, false); assert_eq!(a_ver != b_ver, true); assert_eq!(a_ver.compare(&b_ver), CompOp::Lt); assert_eq!(b_ver.compare(&a_ver), CompOp::Gt); assert_eq!(a_ver.compare_to(&b_ver, &CompOp::Lt), true); // Match match a_ver.compare(&b_ver) { CompOp::Lt => println!("Version a is less than b"), CompOp::Eq => println!("Version a is equal to b"), CompOp::Gt => println!("Version a is greater than b"), _ => unreachable!() } } ``` Check out the [examples](examples) directory for more complete examples. ## Builds This library is automatically build and tested for each commit using CI services. | Service | Platforms | Branch | Build Status | | | --------: | :----------- | :---------- | :------------------------------------------------------------: | :---------------------------------- | | Travis CI | Linux, macOS | master | [![Build status][travis-master-badge]][travis-link] | [View Status][travis-link] | | Travis CI | Linux, macOS | last commit | [![Build status][travis-last-badge]][travis-link] | [View Status][travis-link] | | AppVeyor | Windows | master | [![Build status][appveyor-master-badge]][appveyor-master-link] | [View Status][appveyor-master-link] | | AppVeyor | Windows | last commit | [![Build status][appveyor-last-badge]][appveyor-last-link] | [View Status][appveyor-last-link] | [travis-master-badge]: https://travis-ci.org/timvisee/version-compare.svg?branch=master [travis-last-badge]: https://travis-ci.org/timvisee/version-compare.svg [travis-link]: https://travis-ci.org/timvisee/version-compare [appveyor-master-badge]: https://ci.appveyor.com/api/projects/status/nikhmuoonooo05a6/branch/master?svg=true [appveyor-last-badge]: https://ci.appveyor.com/api/projects/status/nikhmuoonooo05a6?svg=true [appveyor-master-link]: https://ci.appveyor.com/project/timvisee/version-compare/branch/master [appveyor-last-link]: https://ci.appveyor.com/project/timvisee/version-compare ## License This project is released under the MIT license. Check out the [LICENSE](LICENSE) file for more information. version-compare-0.0.10/appveyor.yml010066400017500001750000000012711354417662700155340ustar0000000000000000environment: RUSTUP_USE_HYPER: 1 CARGO_HTTP_CHECK_REVOKE: false matrix: - TARGET: x86_64-pc-windows-msvc OTHER_TARGET: i686-pc-windows-msvc MAKE_TARGETS: test-unit-x86_64-pc-windows-msvc install: - appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe - rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin - rustup target add %OTHER_TARGET% - rustc -V - cargo -V - git submodule update --init clone_depth: 1 build: false test_script: - cargo build - cargo test - cargo run --example example - cargo run --example minimal - cargo benchversion-compare-0.0.10/examples/example.rs010066400017500001750000000036051354417705500167620ustar0000000000000000//! Usage examples of the version-compare library. //! //! This file shows various ways this library supports for comparing version numbers, //! and it shows various ways of implementing it in code logic such as with a `match` statement. //! //! The `assert_eq!(...)` macros are used to assert the returned value by a given statement. //! //! You can run this example file by using the command `cargo run --example example`. extern crate version_compare; use version_compare::{CompOp, Version, VersionCompare}; fn main() { // Define some version numbers let a = "1.2"; let b = "1.5.1"; // The following comparison operators are used: // - CompOp::Eq -> Equal // - CompOp::Ne -> Not equal // - CompOp::Lt -> Less than // - CompOp::Le -> Less than or equal // - CompOp::Ge -> Greater than or equal // - CompOp::Gt -> Greater than // Easily compare version strings assert_eq!(VersionCompare::compare(&a, &b).unwrap(), CompOp::Lt); assert_eq!( VersionCompare::compare_to(&a, &b, &CompOp::Le).unwrap(), true ); assert_eq!( VersionCompare::compare_to(&a, &b, &CompOp::Gt).unwrap(), false ); // Version string parsing let a_ver = Version::from(a).unwrap(); let b_ver = Version::from(b).unwrap(); // Directly compare parsed versions assert_eq!(a_ver < b_ver, true); assert_eq!(a_ver <= b_ver, true); assert_eq!(a_ver > b_ver, false); assert_eq!(a_ver != b_ver, true); assert_eq!(a_ver.compare(&b_ver), CompOp::Lt); assert_eq!(b_ver.compare(&a_ver), CompOp::Gt); assert_eq!(a_ver.compare_to(&b_ver, &CompOp::Lt), true); // Match match a_ver.compare(&b_ver) { CompOp::Lt => println!("Version a is less than b"), CompOp::Eq => println!("Version a is equal to b"), CompOp::Gt => println!("Version a is greater than b"), _ => unreachable!(), } } version-compare-0.0.10/examples/minimal.rs010066400017500001750000000012761354417705500167570ustar0000000000000000//! The most minimal usage example of the version-compare library. //! //! This example compares two given version numbers, and matches the comparison result. //! //! You can run this example file by using the command `cargo run --example minimal`. extern crate version_compare; use version_compare::{CompOp, VersionCompare}; fn main() { // Define some version numbers let a = "1.3"; let b = "1.2.4"; // Match match VersionCompare::compare(&a, &b).unwrap() { CompOp::Lt => println!("Version a is less than b"), CompOp::Eq => println!("Version a is equal to b"), CompOp::Gt => println!("Version a is greater than b"), _ => unreachable!(), } } version-compare-0.0.10/src/comp_op.rs010066400017500001750000000370241354420762500157330ustar0000000000000000//! 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 `CompOp::from_sign(">");` can be used to get a comparison operator by it's logical //! sign from a string. use std::cmp::Ordering; /// Enum of supported comparison operators. #[derive(Debug, Clone, PartialEq)] pub enum CompOp { /// 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 CompOp { /// 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::CompOp; /// /// assert_eq!(CompOp::from_sign("=="), Ok(CompOp::Eq)); /// assert_eq!(CompOp::from_sign("<"), Ok(CompOp::Lt)); /// assert_eq!(CompOp::from_sign(" >= "), Ok(CompOp::Ge)); /// assert!(CompOp::from_sign("*").is_err()); /// ``` pub fn from_sign(sign: &str) -> Result { match sign.trim().as_ref() { "==" | "=" => Ok(CompOp::Eq), "!=" | "!" | "<>" => Ok(CompOp::Ne), "<" => Ok(CompOp::Lt), "<=" => Ok(CompOp::Le), ">=" => Ok(CompOp::Ge), ">" => Ok(CompOp::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::CompOp; /// /// assert_eq!(CompOp::from_name("eq"), Ok(CompOp::Eq)); /// assert_eq!(CompOp::from_name("lt"), Ok(CompOp::Lt)); /// assert_eq!(CompOp::from_name(" Ge "), Ok(CompOp::Ge)); /// assert!(CompOp::from_name("abc").is_err()); /// ``` pub fn from_name(sign: &str) -> Result { match sign.trim().to_lowercase().as_ref() { "eq" => Ok(CompOp::Eq), "ne" => Ok(CompOp::Ne), "lt" => Ok(CompOp::Lt), "le" => Ok(CompOp::Le), "ge" => Ok(CompOp::Ge), "gt" => Ok(CompOp::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) -> CompOp { match ord { Ordering::Less => CompOp::Lt, Ordering::Equal => CompOp::Eq, Ordering::Greater => CompOp::Gt, } } /// Get the name of this comparison operator. /// /// # Examples /// /// ``` /// use version_compare::CompOp; /// /// assert_eq!(CompOp::Eq.name(), "eq"); /// assert_eq!(CompOp::Lt.name(), "lt"); /// assert_eq!(CompOp::Ge.name(), "ge"); /// ``` pub fn name(&self) -> &str { match self { &CompOp::Eq => "eq", &CompOp::Ne => "ne", &CompOp::Lt => "lt", &CompOp::Le => "le", &CompOp::Ge => "ge", &CompOp::Gt => "gt", } } /// Covert to the inverted comparison operator. /// /// This uses the following bidirectional rules: /// /// * `Eq` <-> `Ne` /// * `Lt` <-> `Ge` /// * `Le` <-> `Gt` /// /// # Examples /// /// ``` /// use version_compare::CompOp; /// /// assert_eq!(CompOp::Eq.as_inverted(), CompOp::Ne); /// assert_eq!(CompOp::Lt.as_inverted(), CompOp::Ge); /// assert_eq!(CompOp::Gt.as_inverted(), CompOp::Le); /// ``` pub fn as_inverted(self) -> Self { self.invert() } /// Get the inverted comparison operator. /// /// This uses the following bidirectional rules: /// /// * `Eq` <-> `Ne` /// * `Lt` <-> `Ge` /// * `Le` <-> `Gt` /// /// # Examples /// /// ``` /// use version_compare::CompOp; /// /// assert_eq!(CompOp::Eq.invert(), CompOp::Ne); /// assert_eq!(CompOp::Lt.invert(), CompOp::Ge); /// assert_eq!(CompOp::Gt.invert(), CompOp::Le); /// ``` pub fn invert(&self) -> Self { match self { &CompOp::Eq => CompOp::Ne, &CompOp::Ne => CompOp::Eq, &CompOp::Lt => CompOp::Ge, &CompOp::Le => CompOp::Gt, &CompOp::Ge => CompOp::Lt, &CompOp::Gt => CompOp::Le, } } /// Convert to the opposite comparison operator. /// /// This uses the following bidirectional rules: /// /// * `Eq` <-> `Ne` /// * `Lt` <-> `Gt` /// * `Le` <-> `Ge` /// /// # Examples /// /// ``` /// use version_compare::CompOp; /// /// assert_eq!(CompOp::Eq.as_opposite(), CompOp::Ne); /// assert_eq!(CompOp::Lt.as_opposite(), CompOp::Gt); /// assert_eq!(CompOp::Ge.as_opposite(), CompOp::Le); /// ``` pub fn as_opposite(self) -> Self { self.opposite() } /// Get the opposite comparison operator. /// /// This uses the following bidirectional rules: /// /// * `Eq` <-> `Ne` /// * `Lt` <-> `Gt` /// * `Le` <-> `Ge` /// /// # Examples /// /// ``` /// use version_compare::CompOp; /// /// assert_eq!(CompOp::Eq.opposite(), CompOp::Ne); /// assert_eq!(CompOp::Lt.opposite(), CompOp::Gt); /// assert_eq!(CompOp::Ge.opposite(), CompOp::Le); /// ``` pub fn opposite(&self) -> Self { match self { &CompOp::Eq => CompOp::Ne, &CompOp::Ne => CompOp::Eq, &CompOp::Lt => CompOp::Gt, &CompOp::Le => CompOp::Ge, &CompOp::Ge => CompOp::Le, &CompOp::Gt => CompOp::Lt, } } /// Convert to the flipped comparison operator. /// /// This uses the following bidirectional rules: /// /// * `Lt` <-> `Gt` /// * `Le` <-> `Ge` /// * Other operators are returned as is. /// /// # Examples /// /// ``` /// use version_compare::CompOp; /// /// assert_eq!(CompOp::Eq.as_flipped(), CompOp::Eq); /// assert_eq!(CompOp::Lt.as_flipped(), CompOp::Gt); /// assert_eq!(CompOp::Ge.as_flipped(), CompOp::Le); /// ``` pub fn as_flipped(self) -> Self { self.flip() } /// 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::CompOp; /// /// assert_eq!(CompOp::Eq.flip(), CompOp::Eq); /// assert_eq!(CompOp::Lt.flip(), CompOp::Gt); /// assert_eq!(CompOp::Ge.flip(), CompOp::Le); /// ``` pub fn flip(&self) -> Self { match self { &CompOp::Lt => CompOp::Gt, &CompOp::Le => CompOp::Ge, &CompOp::Ge => CompOp::Le, &CompOp::Gt => CompOp::Lt, _ => self.clone(), } } /// 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::CompOp; /// /// assert_eq!(CompOp::Eq.sign(), "=="); /// assert_eq!(CompOp::Lt.sign(), "<"); /// assert_eq!(CompOp::Ge.flip().sign(), "<="); /// ``` pub fn sign(&self) -> &'static str { match self { &CompOp::Eq => "==", &CompOp::Ne => "!=", &CompOp::Lt => "<", &CompOp::Le => "<=", &CompOp::Ge => ">=", &CompOp::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 ver_a = Version::from("1.2.3").unwrap(); /// let ver_b = Version::from("1.3").unwrap(); /// /// assert_eq!(ver_a.compare(&ver_b).factor(), -1); /// assert_eq!(10 * ver_b.compare(&ver_a).factor(), 10); /// ``` pub fn factor(&self) -> i8 { match self { &CompOp::Eq | &CompOp::Ne => 0, &CompOp::Lt | &CompOp::Le => -1, &CompOp::Gt | &CompOp::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 ver_a = Version::from("1.2.3").unwrap(); /// let ver_b = Version::from("1.3").unwrap(); /// /// assert_eq!(ver_a.compare(&ver_b).ord().unwrap(), Ordering::Less); /// ``` pub fn ord(&self) -> Option { match self { &CompOp::Eq => Some(Ordering::Equal), &CompOp::Lt => Some(Ordering::Less), &CompOp::Gt => Some(Ordering::Greater), _ => None, } } } #[cfg_attr(tarpaulin, skip)] #[cfg(test)] mod tests { use std::cmp::Ordering; use super::CompOp; #[test] fn from_sign() { // Normal signs assert_eq!(CompOp::from_sign("==").unwrap(), CompOp::Eq); assert_eq!(CompOp::from_sign("=").unwrap(), CompOp::Eq); assert_eq!(CompOp::from_sign("!=").unwrap(), CompOp::Ne); assert_eq!(CompOp::from_sign("!").unwrap(), CompOp::Ne); assert_eq!(CompOp::from_sign("<>").unwrap(), CompOp::Ne); assert_eq!(CompOp::from_sign("<").unwrap(), CompOp::Lt); assert_eq!(CompOp::from_sign("<=").unwrap(), CompOp::Le); assert_eq!(CompOp::from_sign(">=").unwrap(), CompOp::Ge); assert_eq!(CompOp::from_sign(">").unwrap(), CompOp::Gt); // Exceptional cases assert_eq!(CompOp::from_sign(" <= ").unwrap(), CompOp::Le); assert!(CompOp::from_sign("*").is_err()); } #[test] fn from_name() { // Normal names assert_eq!(CompOp::from_name("eq").unwrap(), CompOp::Eq); assert_eq!(CompOp::from_name("ne").unwrap(), CompOp::Ne); assert_eq!(CompOp::from_name("lt").unwrap(), CompOp::Lt); assert_eq!(CompOp::from_name("le").unwrap(), CompOp::Le); assert_eq!(CompOp::from_name("ge").unwrap(), CompOp::Ge); assert_eq!(CompOp::from_name("gt").unwrap(), CompOp::Gt); // Exceptional cases assert_eq!(CompOp::from_name(" Le ").unwrap(), CompOp::Le); assert!(CompOp::from_name("abc").is_err()); } #[test] fn from_ord() { assert_eq!(CompOp::from_ord(Ordering::Less), CompOp::Lt); assert_eq!(CompOp::from_ord(Ordering::Equal), CompOp::Eq); assert_eq!(CompOp::from_ord(Ordering::Greater), CompOp::Gt); } #[test] fn name() { assert_eq!(CompOp::Eq.name(), "eq"); assert_eq!(CompOp::Ne.name(), "ne"); assert_eq!(CompOp::Lt.name(), "lt"); assert_eq!(CompOp::Le.name(), "le"); assert_eq!(CompOp::Ge.name(), "ge"); assert_eq!(CompOp::Gt.name(), "gt"); } #[test] fn as_inverted() { assert_eq!(CompOp::Ne.as_inverted(), CompOp::Eq); assert_eq!(CompOp::Eq.as_inverted(), CompOp::Ne); assert_eq!(CompOp::Ge.as_inverted(), CompOp::Lt); assert_eq!(CompOp::Gt.as_inverted(), CompOp::Le); assert_eq!(CompOp::Lt.as_inverted(), CompOp::Ge); assert_eq!(CompOp::Le.as_inverted(), CompOp::Gt); } #[test] fn invert() { assert_eq!(CompOp::Ne.invert(), CompOp::Eq); assert_eq!(CompOp::Eq.invert(), CompOp::Ne); assert_eq!(CompOp::Ge.invert(), CompOp::Lt); assert_eq!(CompOp::Gt.invert(), CompOp::Le); assert_eq!(CompOp::Lt.invert(), CompOp::Ge); assert_eq!(CompOp::Le.invert(), CompOp::Gt); } #[test] fn as_opposite() { assert_eq!(CompOp::Ne.as_opposite(), CompOp::Eq); assert_eq!(CompOp::Eq.as_opposite(), CompOp::Ne); assert_eq!(CompOp::Gt.as_opposite(), CompOp::Lt); assert_eq!(CompOp::Ge.as_opposite(), CompOp::Le); assert_eq!(CompOp::Le.as_opposite(), CompOp::Ge); assert_eq!(CompOp::Lt.as_opposite(), CompOp::Gt); } #[test] fn opposite() { assert_eq!(CompOp::Eq.opposite(), CompOp::Ne); assert_eq!(CompOp::Ne.opposite(), CompOp::Eq); assert_eq!(CompOp::Lt.opposite(), CompOp::Gt); assert_eq!(CompOp::Le.opposite(), CompOp::Ge); assert_eq!(CompOp::Ge.opposite(), CompOp::Le); assert_eq!(CompOp::Gt.opposite(), CompOp::Lt); } #[test] fn as_flipped() { assert_eq!(CompOp::Eq.as_flipped(), CompOp::Eq); assert_eq!(CompOp::Ne.as_flipped(), CompOp::Ne); assert_eq!(CompOp::Lt.as_flipped(), CompOp::Gt); assert_eq!(CompOp::Le.as_flipped(), CompOp::Ge); assert_eq!(CompOp::Ge.as_flipped(), CompOp::Le); assert_eq!(CompOp::Gt.as_flipped(), CompOp::Lt); } #[test] fn flip() { assert_eq!(CompOp::Eq.flip(), CompOp::Eq); assert_eq!(CompOp::Ne.flip(), CompOp::Ne); assert_eq!(CompOp::Lt.flip(), CompOp::Gt); assert_eq!(CompOp::Le.flip(), CompOp::Ge); assert_eq!(CompOp::Ge.flip(), CompOp::Le); assert_eq!(CompOp::Gt.flip(), CompOp::Lt); } #[test] fn sign() { assert_eq!(CompOp::Eq.sign(), "=="); assert_eq!(CompOp::Ne.sign(), "!="); assert_eq!(CompOp::Lt.sign(), "<"); assert_eq!(CompOp::Le.sign(), "<="); assert_eq!(CompOp::Ge.sign(), ">="); assert_eq!(CompOp::Gt.sign(), ">"); } #[test] fn factor() { assert_eq!(CompOp::Eq.factor(), 0); assert_eq!(CompOp::Ne.factor(), 0); assert_eq!(CompOp::Lt.factor(), -1); assert_eq!(CompOp::Le.factor(), -1); assert_eq!(CompOp::Ge.factor(), 1); assert_eq!(CompOp::Gt.factor(), 1); } #[test] fn ord() { assert_eq!(CompOp::Eq.ord(), Some(Ordering::Equal)); assert_eq!(CompOp::Ne.ord(), None); assert_eq!(CompOp::Lt.ord(), Some(Ordering::Less)); assert_eq!(CompOp::Le.ord(), None); assert_eq!(CompOp::Ge.ord(), None); assert_eq!(CompOp::Gt.ord(), Some(Ordering::Greater)); } } version-compare-0.0.10/src/lib.rs010066400017500001750000000053271354420514700150430ustar0000000000000000//! A Rust library to easily compare version numbers in any format, //! and test them against various comparison operators. //! //! Comparing version numbers is hard. Especially when version numbers get really complex, //! or when their formatting differs. //! //! This library helps you to easily compare any kind of version number with minimal code. //! Two version numbers can be compared to each other, to get a relevant comparison operator (<, ==, >), //! or version numbers can be tested against any given comparison operator. //! //! Along with version comparison, the library also features other useful tools. //! For example: version numbers can be parsed to inspect a version number by it's bare numeric or text based parts. //! //! Inspired by PHPs [version_compare()](http://php.net/manual/en/function.version-compare.php). //! //! ### Version formats //! A list of version number examples that are parsed successfully: //! //! - `1` //! - `3.10.4.1` //! - `1.2.alpha` //! - `1.2.dev.4` //! - ` ` _(empty)_ //! - ` . -32 . 1` _(undefined formats)_ //! - `MyApp 3.2.0 / build 0932` _(complex formats, not fully functional yet)_ //! - _Many more and support for custom formats to come..._ //! //! ### Semver //! Version number formats like [_semver_](http://semver.org/) try to make version numbers consistent and manageable, //! there are too many projects however that don't follow such format. //! //! Version-compare makes working with them easy and supports semver formats out of the box with zero configuration. //! //! ## Features //! * Compare two version numbers, get: `<`, `==` or `>`. //! * Compare two version numbers against any comparison operator, get `true` or `false`. //! * Parse complex version numbers. //! * Static, single-statement methods available. //! //! The following features will be added in a later version: //! //! * Support for text parts in version strings. //! * Version manifest, to specify detailed version number constraints. //! * Version ranges, and tests against them. //! * Support for operators in version strings, [npm-style](https://docs.npmjs.com/misc/semver), and tests against them. //! * Batch comparisons. //! //! ## Examples //! Check out the [examples](https://github.com/timvisee/version-compare/tree/master/examples) directory for all available examples. //! //! //! _[View complete README](https://github.com/timvisee/version-compare/blob/master/README.md)_ pub mod comp_op; pub mod version; pub mod version_compare; pub mod version_manifest; pub mod version_part; #[cfg(test)] mod test; // Reexports pub use crate::comp_op::CompOp; pub use crate::version::Version; pub use crate::version_compare::VersionCompare; pub use crate::version_manifest::VersionManifest; pub use crate::version_part::VersionPart; version-compare-0.0.10/src/test/mod.rs010066400017500001750000000000601354417705500160260ustar0000000000000000pub mod test_version; pub mod test_version_set; version-compare-0.0.10/src/test/test_version.rs010066400017500001750000000024301354417761700200020ustar0000000000000000/// 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 TestVersion(pub &'static str, pub usize); /// List of version numbers with metadata for dynamic tests pub const TEST_VERSIONS: &'static [TestVersion] = &[ TestVersion("1", 1), TestVersion("1.2", 2), TestVersion("1.2.3.4", 4), TestVersion("1.2.3.4.5.6.7.8", 8), TestVersion("0", 1), TestVersion("0.0.0", 3), TestVersion("1.0.0", 3), TestVersion("0.0.1", 3), TestVersion("", 0), TestVersion(".", 0), TestVersion("...", 0), TestVersion("1.2.dev", 3), TestVersion("1.2-dev", 3), TestVersion("1.2.alpha.4", 4), TestVersion("1.2-alpha-4", 4), TestVersion("snapshot.1.2", 3), TestVersion("snapshot-1.2", 3), // TODO: inspect and fix this case // TestVersion("version-compare 2.1.8.1 / build 209", 4), ]; /// List of version numbers that contain errors with metadata for dynamic tests pub const TEST_VERSIONS_ERROR: &'static [TestVersion] = &[ TestVersion("abc", 1), TestVersion("alpha.dev.snapshot", 3), TestVersion("test. .snapshot", 3), // TODO: broken case, decide what to do here // TestVersion("$", 1), ]; version-compare-0.0.10/src/test/test_version_set.rs010066400017500001750000000053621354420514700206520ustar0000000000000000use crate::comp_op::CompOp; /// 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 TestVersionSet(pub &'static str, pub &'static str, pub CompOp); /// List of version sets for dynamic tests pub const TEST_VERSION_SETS: &'static [TestVersionSet] = &[ TestVersionSet("1", "1", CompOp::Eq), TestVersionSet("1.0.0.0", "1", CompOp::Eq), TestVersionSet("1", "1.0.0.0", CompOp::Eq), TestVersionSet("0", "0", CompOp::Eq), TestVersionSet("0.0.0", "0", CompOp::Eq), TestVersionSet("0", "0.0.0", CompOp::Eq), TestVersionSet("", "", CompOp::Eq), TestVersionSet("", "0.0", CompOp::Eq), TestVersionSet("0.0", "", CompOp::Eq), TestVersionSet("", "0.1", CompOp::Lt), TestVersionSet("0.1", "", CompOp::Gt), TestVersionSet("1.2.3", "1.2.3", CompOp::Eq), TestVersionSet("1.2.3", "1.2.4", CompOp::Lt), TestVersionSet("1.0.0.1", "1.0.0.0", CompOp::Gt), TestVersionSet("1.0.0.0", "1.0.0.1", CompOp::Lt), TestVersionSet("1.2.3.4", "1.2", CompOp::Gt), TestVersionSet("1.2", "1.2.3.4", CompOp::Lt), TestVersionSet("1.2.3.4", "2", CompOp::Lt), TestVersionSet("2", "1.2.3.4", CompOp::Gt), TestVersionSet("123", "123", CompOp::Eq), TestVersionSet("123", "1.2.3", CompOp::Gt), TestVersionSet("1.2.3", "123", CompOp::Lt), TestVersionSet("1.1.2", "1.1.30-dev", CompOp::Lt), TestVersionSet("1.2.3", "1.2.3.alpha", CompOp::Gt), TestVersionSet("1.2.3", "1.2.3-dev", CompOp::Gt), TestVersionSet("1.2.3.dev", "1.2.3.alpha", CompOp::Eq), TestVersionSet("1.2.3-dev", "1.2.3-alpha", CompOp::Eq), TestVersionSet("1.2.3.dev.1", "1.2.3.alpha", CompOp::Gt), TestVersionSet("1.2.3-dev-1", "1.2.3-alpha", CompOp::Gt), TestVersionSet("version-compare 3.2.0 / build 0932", "3.2.5", CompOp::Lt), TestVersionSet("version-compare 3.2.0 / build 0932", "3.1.1", CompOp::Gt), TestVersionSet( "version-compare 1.4.1 / build 0043", "version-compare 1.4.1 / build 0043", CompOp::Eq, ), TestVersionSet( "version-compare 1.4.1 / build 0042", "version-compare 1.4.1 / build 0043", CompOp::Lt, ), // TODO: inspect these cases TestVersionSet("snapshot.1.2.3", "1.2.3.alpha", CompOp::Lt), TestVersionSet("snapshot-1.2.3", "1.2.3-alpha", CompOp::Lt), ]; /// List of invalid version sets for dynamic tests pub const TEST_VERSION_SETS_ERROR: &'static [TestVersionSet] = &[ TestVersionSet("1.2.3", "1.2.3", CompOp::Lt), TestVersionSet("1.2", "1.2.0.0", CompOp::Ne), TestVersionSet("1.2.3.dev", "dev", CompOp::Eq), TestVersionSet("snapshot", "1", CompOp::Lt), ]; version-compare-0.0.10/src/version.rs010066400017500001750000000575511354420762500157730ustar0000000000000000//! 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::cmp::Ordering; use std::fmt; use std::iter::Peekable; use std::slice::Iter; use crate::comp_op::CompOp; use crate::version_manifest::VersionManifest; use crate::version_part::VersionPart; /// Version struct, which is a representation for a parsed version string. /// /// A version in string format can be parsed using methods like `Version::from("1.2.3");`. /// These methods return a `Result` holding the parsed version or an error on failure. /// /// The original version string is stored in the struct, and can be accessed using the /// `version.as_str()` method. Note, that when the version wasn't parsed from a string /// representation, the returned value is generated. /// /// The struct provides many methods for comparison and probing. pub struct Version<'a> { version: &'a str, parts: Vec>, manifest: Option<&'a VersionManifest>, } 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::{CompOp, Version}; /// /// let ver = Version::from("1.2.3").unwrap(); /// /// assert_eq!(ver.compare(&Version::from("1.2.3").unwrap()), CompOp::Eq); /// ``` pub fn from(version: &'a str) -> Option { // Split the version string let parts = Self::split_version_str(version, None); // Return nothing if the parts are none if parts.is_none() { return None; } // Create and return the object Some(Version { version: version, parts: parts.unwrap(), 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::{CompOp, Version, VersionManifest}; /// /// let manifest = VersionManifest::new(); /// let ver = Version::from_manifest("1.2.3", &manifest).unwrap(); /// /// assert_eq!(ver.compare(&Version::from("1.2.3").unwrap()), CompOp::Eq); /// ``` pub fn from_manifest(version: &'a str, manifest: &'a VersionManifest) -> Option { // Split the version string let parts = Self::split_version_str(version, Some(&manifest)); // Return nothing if the parts are none if parts.is_none() { return None; } // Create and return the object Some(Version { version: version, parts: parts.unwrap(), 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_number() /// ); /// } else { /// println!("Version has no manifest"); /// } /// ``` pub fn manifest(&self) -> Option<&VersionManifest> { 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, VersionManifest}; /// /// let manifest = VersionManifest::new(); /// let mut version = Version::from("1.2.3").unwrap(); /// /// version.set_manifest(Some(&manifest)); /// ``` pub fn set_manifest(&mut self, manifest: Option<&'a VersionManifest>) { self.manifest = manifest; // TODO: Re-parse the version string, because the manifest might have changed. } /// Split the given version string, in it's version parts. /// TODO: Move this method to some sort of helper class, maybe as part of `VersionPart`. fn split_version_str( version: &'a str, manifest: Option<&'a VersionManifest>, ) -> Option>> { // Split the version string, and create a vector to put the parts in // TODO: split at specific separators instead let split = version.split(|c| !char::is_alphanumeric(c)); let mut parts = Vec::new(); // Get the manifest to follow let mut used_manifest = &VersionManifest::new(); if manifest.is_some() { used_manifest = manifest.unwrap(); } // Flag to determine whether this version number contains any number part let mut has_number = false; // 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_number() { 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, and set the has number flag parts.push(VersionPart::Number(number)); has_number = true; } Err(_) => { // Ignore text parts if specified if used_manifest.ignore_text() { continue; } // Push the text part to the vector parts.push(VersionPart::Text(part)) } } } // The version must contain a number part, if any part was parsed if !has_number && !parts.is_empty() { return None; } // Return the list of parts Some(parts) } /// 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, VersionPart}; /// /// let ver = Version::from("1.2.3").unwrap(); /// /// assert_eq!(ver.part(0), Ok(&VersionPart::Number(1))); /// assert_eq!(ver.part(1), Ok(&VersionPart::Number(2))); /// assert_eq!(ver.part(2), Ok(&VersionPart::Number(3))); /// ``` pub fn part(&self, index: usize) -> Result<&VersionPart<'a>, ()> { // Make sure the index is in-bound if index >= self.parts.len() { return Err(()); } // Return the requested part Ok(&self.parts[index]) } /// Get a vector of all version parts. /// /// # Examples /// /// ``` /// use version_compare::{Version, VersionPart}; /// /// let ver = Version::from("1.2.3").unwrap(); /// /// assert_eq!(ver.parts(), &vec![ /// VersionPart::Number(1), /// VersionPart::Number(2), /// VersionPart::Number(3) /// ]); /// ``` pub fn parts(&self) -> &Vec> { &self.parts } /// Get the number of parts in this version string. /// /// # Examples /// /// ``` /// use version_compare::Version; /// /// let ver_a = Version::from("1.2.3").unwrap(); /// let ver_b = Version::from("1.2.3.4").unwrap(); /// /// assert_eq!(ver_a.part_count(), 3); /// assert_eq!(ver_b.part_count(), 4); /// ``` pub fn part_count(&self) -> usize { self.parts.len() } /// 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::{CompOp, Version}; /// /// assert_eq!(Version::from("1.2").unwrap().compare(&Version::from("1.3.2").unwrap()), CompOp::Lt); /// assert_eq!(Version::from("1.9").unwrap().compare(&Version::from("1.9").unwrap()), CompOp::Eq); /// assert_eq!(Version::from("0.3.0.0").unwrap().compare(&Version::from("0.3").unwrap()), CompOp::Eq); /// assert_eq!(Version::from("2").unwrap().compare(&Version::from("1.7.3").unwrap()), CompOp::Gt); /// ``` pub fn compare(&self, other: &'a Version) -> CompOp { // Compare the versions with their peekable iterators Self::compare_iter(self.parts.iter().peekable(), other.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::{CompOp, Version}; /// /// assert!(Version::from("1.2").unwrap().compare_to(&Version::from("1.3.2").unwrap(), &CompOp::Lt)); /// assert!(Version::from("1.2").unwrap().compare_to(&Version::from("1.3.2").unwrap(), &CompOp::Le)); /// assert!(Version::from("1.2").unwrap().compare_to(&Version::from("1.2").unwrap(), &CompOp::Eq)); /// assert!(Version::from("1.2").unwrap().compare_to(&Version::from("1.2").unwrap(), &CompOp::Le)); /// ``` pub fn compare_to(&self, other: &Version, operator: &CompOp) -> bool { // Get the comparison result let result = self.compare(&other); // Match the result against the given operator match result { CompOp::Eq => match operator { &CompOp::Eq | &CompOp::Le | &CompOp::Ge => true, _ => false, }, CompOp::Lt => match operator { &CompOp::Ne | &CompOp::Lt | &CompOp::Le => true, _ => false, }, CompOp::Gt => match operator { &CompOp::Ne | &CompOp::Gt | &CompOp::Ge => true, _ => false, }, _ => unreachable!(), } } /// 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( mut iter: Peekable>>, mut other_iter: Peekable>>, ) -> CompOp { // Iterate through the parts of this version let mut other_part: Option<&VersionPart>; // Iterate over the iterator, without consuming it loop { match iter.next() { Some(part) => { // 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 { &VersionPart::Number(num) => { if num == 0 { continue; } } &VersionPart::Text(_) => return CompOp::Lt, } // The main version is greater return CompOp::Gt; } // Match both part as numbers to destruct their numerical values match part { &VersionPart::Number(num) => match other_part.unwrap() { &VersionPart::Number(other_num) => { // Compare the numbers match num { n if n < other_num => return CompOp::Lt, n if n > other_num => return CompOp::Gt, _ => continue, } } _ => {} }, _ => {} } } None => break, } } // 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(_) => Self::compare_iter(other_iter, iter).as_flipped(), // Nothing more to iterate over, the versions should be equal None => CompOp::Eq, } } } 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, &CompOp::Eq) } } #[cfg_attr(tarpaulin, skip)] #[cfg(test)] mod tests { use std::cmp; use crate::comp_op::CompOp; use crate::test::test_version::{TEST_VERSIONS, TEST_VERSIONS_ERROR}; use crate::test::test_version_set::TEST_VERSION_SETS; use crate::version_manifest::VersionManifest; use crate::version_part::VersionPart; 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 TEST_VERSIONS { assert!(Version::from(&version.0).is_some()); } // Test whether parsing works for each test invalid version for version in TEST_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 = VersionManifest::new(); // Test whether parsing works for each test version for version in TEST_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 TEST_VERSIONS_ERROR { assert!(Version::from_manifest(&version.0, &manifest).is_none()); } } #[test] fn manifest() { let manifest = VersionManifest::new(); 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 = VersionManifest::new(); 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 = VersionManifest::new(); 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 TEST_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 TEST_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 TEST_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 = VersionManifest::new(); // Loop through a range of numbers for depth in 0..5 { // Set the maximum depth manifest.set_max_depth_number(depth); // Test for each test version with the manifest for version in TEST_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 = VersionManifest::new(); // Try this for true and false for ignore in vec![true, false] { // Set to ignore text manifest.set_ignore_text(ignore); // Keep track whether any version passed with text let mut had_text = false; // Test each test version for version in TEST_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 { &VersionPart::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 part_count() { // Test for each test version for version in TEST_VERSIONS { // The number of parts must match the metadata assert_eq!(Version::from(&version.0).unwrap().part_count(), version.1); } } #[test] fn compare() { // Compare each version in the version set for entry in TEST_VERSION_SETS { // Get both versions let version_a = Version::from(&entry.0).unwrap(); let version_b = Version::from(&entry.1).unwrap(); // Compare them assert_eq!( version_a.compare(&version_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 TEST_VERSION_SETS { // Get both versions let version_a = Version::from(&entry.0).unwrap(); let version_b = Version::from(&entry.1).unwrap(); // Test assert!(version_a.compare_to(&version_b, &entry.2)); // Make sure the inverse operator is not correct assert_eq!(version_a.compare_to(&version_b, &entry.2.invert()), false); } // Assert an exceptional case, compare to not equal assert!(Version::from("1.2") .unwrap() .compare_to(&Version::from("1.2.3").unwrap(), &CompOp::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 TEST_VERSION_SETS { // Get both versions let version_a = Version::from(&entry.0).unwrap(); let version_b = Version::from(&entry.1).unwrap(); // Compare and assert match entry.2 { CompOp::Eq => assert!(version_a == version_b), CompOp::Lt => assert!(version_a < version_b), CompOp::Gt => assert!(version_a > version_b), _ => {} } } } #[test] fn partial_eq() { // Compare each version in the version set for entry in TEST_VERSION_SETS { // Skip entries that are less or equal, or greater or equal match entry.2 { CompOp::Le | CompOp::Ge => continue, _ => {} } // Get both versions let version_a = Version::from(&entry.0).unwrap(); let version_b = Version::from(&entry.1).unwrap(); // Determine what the result should be let result = match entry.2 { CompOp::Eq => true, _ => false, }; // Test assert_eq!(version_a == version_b, result); } // Assert an exceptional case, compare to not equal assert!(Version::from("1.2").unwrap() != Version::from("1.2.3").unwrap()); } } version-compare-0.0.10/src/version_compare.rs010066400017500001750000000111501354420762500174620ustar0000000000000000//! Version compare module, with useful static comparison methods. //! //! This module provides the `VersionCompare` struct, which provides many static functions, that are //! useful for version comparison. use crate::comp_op::CompOp; use crate::version::Version; /// The main library structure, which provides various static methods for easy version comparison. /// /// This structure uses static methods only, and doesn't need to be constructed. pub struct VersionCompare {} impl VersionCompare { /// 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`. /// /// The two given version numbers must be valid, or an error will be returned. /// /// One of the following ok results may be returned: /// /// * `CompOp::Eq` /// * `CompOp::Lt` /// * `CompOp::Gt` /// /// # Examples /// /// ``` /// use version_compare::{CompOp, VersionCompare}; /// /// // Compare version numbers /// assert_eq!(VersionCompare::compare("1.2.3", "1.2.3"), Ok(CompOp::Eq)); /// assert_eq!(VersionCompare::compare("1.2.3", "1.2.4"), Ok(CompOp::Lt)); /// assert_eq!(VersionCompare::compare("1", "0.1"), Ok(CompOp::Gt)); /// ``` pub fn compare(a: &str, b: &str) -> Result { // Create version instances let a_ver = Version::from(a); let b_ver = Version::from(b); // Both version numbers must have been parsed if a_ver.is_none() || b_ver.is_none() { return Err(()); } // Compare and return the result Ok(a_ver.unwrap().compare(&b_ver.unwrap())) } /// Compare two version number strings to each other and check whether the given comparison /// `operator` is valid. /// /// The two given version numbers must be valid, or an error will be returned. /// /// # Examples /// /// ``` /// use version_compare::{CompOp, VersionCompare}; /// /// // Compare version numbers /// assert!(VersionCompare::compare_to("1.2.3", "1.2.3", &CompOp::Eq).unwrap()); /// assert!(VersionCompare::compare_to("1.2.3", "1.2.3", &CompOp::Le).unwrap()); /// assert!(VersionCompare::compare_to("1.2.3", "1.2.4", &CompOp::Lt).unwrap()); /// assert!(VersionCompare::compare_to("1", "0.1", &CompOp::Gt).unwrap()); /// assert!(VersionCompare::compare_to("1", "0.1", &CompOp::Ge).unwrap()); /// ``` pub fn compare_to(a: &str, b: &str, operator: &CompOp) -> Result { // Create version instances let a_ver = Version::from(a); let b_ver = Version::from(b); // Both version numbers must have been parsed if a_ver.is_none() || b_ver.is_none() { return Err(()); } // Compare and return the result Ok(a_ver.unwrap().compare_to(&b_ver.unwrap(), &operator)) } } #[cfg_attr(tarpaulin, skip)] #[cfg(test)] mod tests { use crate::comp_op::CompOp; use crate::test::test_version_set::{TEST_VERSION_SETS, TEST_VERSION_SETS_ERROR}; use super::VersionCompare; #[test] fn compare() { // Compare each version in the version set for entry in TEST_VERSION_SETS { assert_eq!( VersionCompare::compare(&entry.0, &entry.1), Ok(entry.2.clone()), "Testing that {} is {} {}", &entry.0, &entry.2.sign(), &entry.1 ); } // Compare each error version in the version set for entry in TEST_VERSION_SETS_ERROR { let result = VersionCompare::compare(&entry.0, &entry.1); if result.is_ok() { assert!(result != Ok(entry.2.clone())); } } } #[test] fn compare_to() { // Compare each version in the version set for entry in TEST_VERSION_SETS { // Test assert!(VersionCompare::compare_to(&entry.0, &entry.1, &entry.2).unwrap()); // Make sure the inverse operator is not correct assert_eq!( VersionCompare::compare_to(&entry.0, &entry.1, &entry.2.invert()).unwrap(), false ); } // Compare each error version in the version set for entry in TEST_VERSION_SETS_ERROR { let result = VersionCompare::compare_to(&entry.0, &entry.1, &entry.2); if result.is_ok() { assert!(!result.unwrap()) } } // Assert an exceptional case, compare to not equal assert!(VersionCompare::compare_to("1.2.3", "1.2", &CompOp::Ne).unwrap()); } } version-compare-0.0.10/src/version_manifest.rs010066400017500001750000000162571354420762500176570ustar0000000000000000//! 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. #[derive(Debug, PartialEq)] pub struct VersionManifest { /// The maximum depth of a version number. This specifies the maximum number of parts. max_depth: Option, /// True to ignore text parts in version strings. ignore_text: bool, } /// Version manifest implementation. impl VersionManifest { /// Constructor. /// /// # Examples /// /// ``` /// use version_compare::VersionManifest; /// /// let mut manifest = VersionManifest::new(); /// /// // Ignore text parts /// manifest.set_ignore_text(true); /// ``` pub fn new() -> Self { VersionManifest { max_depth: None, ignore_text: false, } } /// The maximum depth of a version number. /// None if no depth is configured. /// /// # Examples /// /// ``` /// use version_compare::VersionManifest; /// /// let manifest = VersionManifest::new(); /// /// match manifest.max_depth() { /// &Some(depth) => println!("Maximum depth of {}", depth), /// &None => println!("No maximum depth") /// } /// ``` pub fn max_depth(&self) -> &Option { &self.max_depth } /// The maximum depth of a version number as numerical value. /// Zero is returned if no depth is configured. /// /// # Examples /// /// ``` /// use version_compare::VersionManifest; /// /// let manifest = VersionManifest::new(); /// /// println!("Maximum depth of {}", manifest.max_depth_number()); /// ``` pub fn max_depth_number(&self) -> usize { if self.max_depth.is_some() { self.max_depth.unwrap() } else { 0 } } /// Set the maximum depth of a version number. /// /// # Examples /// /// ``` /// use version_compare::VersionManifest; /// /// let mut manifest = VersionManifest::new(); /// /// // Set the maximum depth to 3 /// manifest.set_max_depth(Some(3)); /// /// // Don't use a maximum depth /// manifest.set_max_depth(None); /// ``` pub fn set_max_depth(&mut self, max_depth: Option) { if max_depth.is_some() && max_depth.unwrap() > 0 { self.max_depth = max_depth; } else { self.max_depth = None; } } /// Set the maximum depth of a version number. /// Use zero to disable the maximum depth. /// /// # Examples /// /// ``` /// use version_compare::VersionManifest; /// /// let mut manifest = VersionManifest::new(); /// /// // Set the maximum depth to 3 /// manifest.set_max_depth_number(3); /// /// // Don't use a maximum depth /// manifest.set_max_depth_number(0); /// ``` pub fn set_max_depth_number(&mut self, max_depth: usize) { if max_depth > 0 { self.max_depth = Some(max_depth); } else { self.max_depth = None; } } /// Check whether there's a maximum configured depth. /// /// # Examples /// /// ``` /// use version_compare::VersionManifest; /// /// let mut manifest = VersionManifest::new(); /// /// assert!(!manifest.has_max_depth()); /// /// manifest.set_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 } /// Check whether to ignore text parts in version numbers. /// /// # Examples /// /// ``` /// use version_compare::VersionManifest; /// /// let manifest = VersionManifest::new(); /// /// if manifest.ignore_text() { /// println!("Text parts are ignored"); /// } else { /// println!("Text parts are not ignored"); /// } /// ``` pub fn ignore_text(&self) -> bool { self.ignore_text } /// Set whether to ignore text parts. /// /// # Examples /// /// ``` /// use version_compare::VersionManifest; /// /// let mut manifest = VersionManifest::new(); /// /// // Ignore text parts /// manifest.set_ignore_text(true); /// /// // Don't ignore text parts /// manifest.set_ignore_text(false); /// ``` pub fn set_ignore_text(&mut self, ignore_text: bool) { self.ignore_text = ignore_text; } } #[cfg_attr(tarpaulin, skip)] #[cfg(test)] mod tests { use crate::version_manifest::VersionManifest; #[test] fn max_depth() { let mut manifest = VersionManifest::new(); manifest.max_depth = Some(1); assert_eq!(manifest.max_depth(), &Some(1)); manifest.max_depth = Some(3); assert_eq!(manifest.max_depth(), &Some(3)); manifest.max_depth = None; assert_eq!(manifest.max_depth(), &None); } #[test] fn max_depth_number() { let mut manifest = VersionManifest::new(); manifest.max_depth = Some(1); assert_eq!(manifest.max_depth_number(), 1); manifest.max_depth = Some(3); assert_eq!(manifest.max_depth_number(), 3); manifest.max_depth = None; assert_eq!(manifest.max_depth_number(), 0); } #[test] fn set_max_depth() { let mut manifest = VersionManifest::new(); manifest.set_max_depth(Some(1)); assert_eq!(manifest.max_depth, Some(1)); manifest.set_max_depth(Some(3)); assert_eq!(manifest.max_depth, Some(3)); manifest.set_max_depth(Some(0)); assert_eq!(manifest.max_depth, None); manifest.set_max_depth(None); assert_eq!(manifest.max_depth, None); } #[test] fn set_max_depth_number() { let mut manifest = VersionManifest::new(); manifest.set_max_depth_number(1); assert_eq!(manifest.max_depth, Some(1)); manifest.set_max_depth_number(3); assert_eq!(manifest.max_depth, Some(3)); manifest.set_max_depth_number(0); assert_eq!(manifest.max_depth, None); } #[test] fn has_max_depth() { let mut manifest = VersionManifest::new(); 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()); } #[test] fn ignore_text() { let mut manifest = VersionManifest::new(); manifest.ignore_text = true; assert!(manifest.ignore_text()); manifest.ignore_text = false; assert!(!manifest.ignore_text()); } #[test] fn set_ignore_text() { let mut manifest = VersionManifest::new(); manifest.set_ignore_text(true); assert!(manifest.ignore_text); manifest.set_ignore_text(false); assert!(!manifest.ignore_text); } } version-compare-0.0.10/src/version_part.rs010066400017500001750000000023111354420762500170010ustar0000000000000000//! Version part module. //! //! A module that provides the `VersionPart` 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; /// Enum of version string parts. /// /// Each version string is broken down into these version parts when being parsed to a `Version`. #[derive(Debug, PartialEq)] pub enum VersionPart<'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 VersionPart<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { VersionPart::Number(n) => write!(f, "{}", n), VersionPart::Text(t) => write!(f, "{}", t), } } } #[cfg_attr(tarpaulin, skip)] #[cfg(test)] mod tests { use crate::version_part::VersionPart; #[test] fn display() { assert_eq!(format!("{}", VersionPart::Number(123)), "123"); assert_eq!(format!("{}", VersionPart::Text("123")), "123"); } } version-compare-0.0.10/.cargo_vcs_info.json0000644000000001120000000000000142540ustar00{ "git": { "sha1": "73955987f003ec58509b19764042c87ad573464c" } } version-compare-0.0.10/Cargo.lock0000644000000002240000000000000122330ustar00# This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "version-compare" version = "0.0.10"