-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
752 additions
and
1 deletion.
There are no files selected for viewing
Submodule libversion
deleted from
ce9642
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/target |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
[package] | ||
name = "libversion" | ||
description = "Advanced version string comparison algorithm" | ||
version = "0.2.0" | ||
authors = ["Dmitry Marakasov <[email protected]>"] | ||
repository = "https://github.com/repology/libversion-rs" | ||
readme = "README.md" | ||
categories = ["algorithms", "version", "comparison"] | ||
license = "MIT" | ||
edition = "2021" | ||
|
||
[dependencies] | ||
bitflags = "2.6.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
Copyright (c) 2024 Dmitry Marakasov <[email protected]> | ||
|
||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
# libversion | ||
|
||
![CI](https://github.com/repology/libversion-rs/workflows/CI/badge.svg) | ||
[![Coverage Status](https://coveralls.io/repos/github/repology/libversion-rs/badge.svg?branch=master)](https://coveralls.io/github/repology/libversion-rs?branch=master) | ||
[![Github commits (since latest release)](https://img.shields.io/github/commits-since/repology/libversion-rs/latest.svg)](https://github.com/repology/libversion-rs) | ||
|
||
Advanced version string comparison library. | ||
|
||
Need to compare software, package or whatever versions? Comparing | ||
`1.0` and `1.1` could be easy, but are you ready for more | ||
complex cases like `1.2-x.3~alpha4`? **libversion** is, which | ||
is proven by using the library in [Repology](https://repology.org/) | ||
project which relies on comparing software version strings, even | ||
if they are written in different formats. | ||
|
||
## Features | ||
|
||
A short list of version features libversion handles for you: | ||
|
||
* Simple versions, obviously: `0.9 < 1.0 < 1.1` | ||
* Omitting insignificant components: `1.0 == 1.0.0` | ||
* Leading zeroes: `1.001 == 1.1` | ||
* Unusual separators: `1_2~3 == 1.2.3` | ||
* Letter suffixes: `1.2 < 1.2a < 1.2b < 1.3` | ||
* Alphanumeric prerelease components: | ||
* `1.0alpha1 == 1.0.alpha1 == 1.0a1 == 1.0.a1` | ||
* `1.0alpha1 < 1.0alpha2 < 1.0beta1 < 1.0rc1 < 1.0` | ||
* Awareness of prerelease keywords: while `1.0 < 1.0a-1` (_a_ treated | ||
as version addendum), but `1.0alpha-1 < 1.0` (_alpha_ is treated | ||
as prerelease marker) | ||
* Awareness of _patch_, _post_ and _pl_ keywords: while `1.0alpha1 < 1.0` | ||
(_alpha_ is pre-release), but `1.0 < 1.0patch1 < 1.1` (_patch_ is post-release) | ||
* Customizable handling of _p_ keyword (it may mean either _patch_ or _pre_, | ||
and since libversion cannot guess, this is controlled with an external flag) | ||
|
||
See [ALGORITHM.md](https://github.com/repology/libversion/blob/master/doc/ALGORITHM.md) for more elaborate description | ||
of inner logic. | ||
|
||
## API | ||
|
||
### Version comparison | ||
|
||
``` | ||
fn version_compare2(v1: &str, v2: &str); | ||
fn version_compare4(v1: &str, v2: &str, v1_flags: Flags, int v2_flags: Flags); | ||
``` | ||
|
||
Compares version strings `v1` and `v2`. | ||
|
||
Returns **-1** if `v1` is lower than `v2`, **0** if `v1` is equal to `v2` and **1** if `v1` is higher than `v2`. | ||
|
||
Thread safe, does not produce errors, does not allocate dynamic memory (this needs to checked for rust implementation), | ||
O(N) computational complexity, O(1) stack memory requirements. | ||
|
||
4-argument form allows specifying flags for each version argument to | ||
tune comparison behavior in specific cases. Currently supported `flags` | ||
values are: | ||
|
||
* `Flags:PIsPatch` _p_ letter is treated as _patch_ | ||
(post-release) instead of _pre_ (pre-release). | ||
* `Flags::AnyIsPatch` any letter sequence is treated as | ||
post-release (useful for handling patchsets as in | ||
`1.2foopatchset3.barpatchset4`). | ||
* `Flags::LowerBound` derive lowest possible version with | ||
the given prefix. For example, lower bound for `1.0` is such | ||
imaginary version `?` that it's higher than any release before | ||
`1.0` and lower than any prerelease of `1.0`. | ||
E.g. `0.999` < lower bound(`1.0`) < `1.0alpha0`. | ||
* `Flags::UpperBound` derive highest possible version with | ||
the given prefix. Opposite of `Flags::LowerBound`. | ||
|
||
If both `flags` are zero, `version_compare4` acts exactly the same | ||
as `version_compare2`. | ||
|
||
## Example | ||
|
||
(TODO: check that this compiles after publishing the crate) | ||
|
||
```c | ||
use libversion::{Flags, version_compare2, version_compare4}; | ||
|
||
int main() { | ||
// 0.99 < 1.11 | ||
assert_eq!(version_compare2("0.99", "1.11"), -1); | ||
|
||
// 1.0 == 1.0.0 | ||
assert_eq!(version_compare2("1.0", "1.0.0"), 0); | ||
|
||
// 1.0alpha1 < 1.0.rc1 | ||
assert_eq!(version_compare2("1.0alpha1", "1.0.rc1"), -1); | ||
|
||
// 1.0 > 1.0.rc1 | ||
assert_eq!(version_compare2("1.0", "1.0-rc1"), 1); | ||
|
||
// 1.2.3alpha4 is the same as 1.2.3~a4 | ||
assert_eq!(version_compare2("1.2.3alpha4", "1.2.3~a4"), 0); | ||
|
||
// by default, `p' is treated as `pre'... | ||
assert_eq!(version_compare2("1.0p1", "1.0pre1"), 0); | ||
assert_eq!(version_compare2("1.0p1", "1.0post1"), -1); | ||
assert_eq!(version_compare2("1.0p1", "1.0patch1"), -1); | ||
|
||
// ...but this is tunable: here it's handled as `patch` | ||
assert_eq!(version_compare4("1.0p1", "1.0pre1", Flags::PIsPatch, Flags::empty()), 1); | ||
assert_eq!(version_compare4("1.0p1", "1.0post1", Flags::PIsPatch, Flags::empty()), 0); | ||
assert_eq!(version_compare4("1.0p1", "1.0patch1", Flags::PIsPatch, Flags::empty()), 0); | ||
|
||
// a way to check that the version belongs to a given release | ||
assert_eq!(version_compare4("1.0alpha1", "1.0", Flags::empty(), Flags::LowerBound), 1); | ||
assert_eq!(version_compare4("1.0alpha1", "1.0", Flags::empty(), Flags::UpperBound), -1); | ||
|
||
assert_eq!(version_compare4("1.0.1", "1.0", Flags::empty(), Flags::LowerBound), 1); | ||
assert_eq!(version_compare4("1.0.1", "1.0", Flags::empty(), Flags::UpperBound), -1); | ||
// 1.0alpha1 and 1.0.1 belong to 1.0 release, e.g. they lie between | ||
// (lowest possible version in 1.0) and (highest possible version in 1.0) | ||
} | ||
``` | ||
|
||
## Bindings and compatible implementations | ||
|
||
* Python: [py-libversion](https://github.com/repology/py-libversion) by @AMDmi3 | ||
* Go: [golibversion](https://github.com/saenai255/golibversion) by @saenai255 | ||
* Rust: [libversion-rs](https://github.com/repology/libversion-rs) by @AMDmi3 | ||
|
||
## Author | ||
|
||
* [Dmitry Marakasov](https://github.com/AMDmi3) <[email protected]> | ||
|
||
## License | ||
|
||
* [MIT](COPYING) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
#[derive(Debug, PartialEq, Eq)] | ||
pub enum Component<'a> { | ||
LowerBound, | ||
PreRelease(u8), | ||
Zero, | ||
PostRelease(u8), | ||
NonZero(&'a str), | ||
LetterSuffix(u8), | ||
UpperBound, | ||
} | ||
|
||
impl Component<'_> { | ||
fn discriminant(&self) -> u8 { | ||
unsafe { *(self as *const Self as *const u8) } | ||
} | ||
} | ||
|
||
impl Ord for Component<'_> { | ||
fn cmp(&self, other: &Self) -> std::cmp::Ordering { | ||
self.discriminant() | ||
.cmp(&other.discriminant()) | ||
.then_with(|| match (self, other) { | ||
(Component::PreRelease(a), Component::PreRelease(b)) => a.cmp(&b), | ||
(Component::PostRelease(a), Component::PostRelease(b)) => a.cmp(&b), | ||
(Component::NonZero(a), Component::NonZero(b)) => { | ||
a.len().cmp(&b.len()).then_with(|| a.cmp(&b)) | ||
} | ||
(Component::LetterSuffix(a), Component::LetterSuffix(b)) => a.cmp(&b), | ||
_ => std::cmp::Ordering::Equal, | ||
}) | ||
} | ||
} | ||
|
||
impl PartialOrd for Component<'_> { | ||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { | ||
Some(self.cmp(other)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
use crate::component::Component; | ||
use crate::parse::{get_next_version_component, SomeComponents}; | ||
use crate::Flags; | ||
use std::mem; | ||
|
||
pub struct VersionComponentIterator<'a> { | ||
rest_of_version: &'a str, | ||
carried_component: Option<Component<'a>>, | ||
flags: Flags, | ||
} | ||
|
||
impl VersionComponentIterator<'_> { | ||
pub fn new<'a>(version: &'a str, flags: Flags) -> VersionComponentIterator<'a> { | ||
return VersionComponentIterator { | ||
rest_of_version: version, | ||
carried_component: None, | ||
flags, | ||
}; | ||
} | ||
|
||
pub fn next(&mut self) -> Component { | ||
if let Some(component) = mem::take(&mut self.carried_component) { | ||
return component; | ||
} | ||
|
||
let (components, rest_of_version) = | ||
get_next_version_component(self.rest_of_version, self.flags); | ||
|
||
self.rest_of_version = rest_of_version; | ||
|
||
match components { | ||
SomeComponents::One(component) => { | ||
return component; | ||
} | ||
SomeComponents::Two(component1, component2) => { | ||
self.carried_component = Some(component2); | ||
return component1; | ||
} | ||
} | ||
} | ||
|
||
pub fn is_exhausted(&self) -> bool { | ||
return self.rest_of_version.is_empty() && self.carried_component.is_none(); | ||
} | ||
} |
Oops, something went wrong.