aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJiyong Park <jiyong@google.com>2022-02-25 01:04:09 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2022-02-25 01:04:09 +0000
commit28ecd5bb3f9ea5b67b77ba63920d331b214f003c (patch)
tree39e87ff6596d44a290395796fc0243640564e419
parent8bbaabde9b893dbb68c0178b8689e0eccc6c26dd (diff)
parentb49cead11b3a5856338de4d57d267c86b97b66ff (diff)
downloadsemver-28ecd5bb3f9ea5b67b77ba63920d331b214f003c.tar.gz
Import semver 1.0.5 am: 2ab0327381 am: 7b09a04a83 am: b49cead11b
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/semver/+/1991753 Change-Id: If47df493dda20e2dc1827904987d38c0a417be71
-rw-r--r--Cargo.lock16
-rw-r--r--Cargo.toml38
-rw-r--r--Cargo.toml.orig22
l---------LICENSE1
-rw-r--r--LICENSE-APACHE201
-rw-r--r--LICENSE-MIT23
-rw-r--r--METADATA20
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--OWNERS1
-rw-r--r--README.md84
-rw-r--r--benches/parse.rs24
-rw-r--r--build.rs79
-rw-r--r--src/backport.rs63
-rw-r--r--src/display.rs165
-rw-r--r--src/error.rs98
-rw-r--r--src/eval.rs181
-rw-r--r--src/identifier.rs356
-rw-r--r--src/impls.rs156
-rw-r--r--src/lib.rs533
-rw-r--r--src/parse.rs405
-rw-r--r--src/serde.rs74
-rw-r--r--tests/node/mod.rs43
-rw-r--r--tests/test_identifier.rs45
-rw-r--r--tests/test_version.rs241
-rw-r--r--tests/test_version_req.rs443
-rw-r--r--tests/util/mod.rs39
26 files changed, 3351 insertions, 0 deletions
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..7dcbc7e
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,16 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "semver"
+version = "1.0.5"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.136"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..f1f5e01
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,38 @@
+# 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"
+rust-version = "1.31"
+name = "semver"
+version = "1.0.5"
+authors = ["David Tolnay <dtolnay@gmail.com>"]
+description = "Parser and evaluator for Cargo's flavor of Semantic Versioning"
+documentation = "https://docs.rs/semver"
+readme = "README.md"
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/dtolnay/semver"
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
+rustdoc-args = [
+ "--cfg",
+ "doc_cfg",
+]
+
+[dependencies.serde]
+version = "1.0"
+optional = true
+default-features = false
+
+[features]
+default = ["std"]
+std = []
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..2fa715c
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,22 @@
+[package]
+name = "semver"
+version = "1.0.5"
+authors = ["David Tolnay <dtolnay@gmail.com>"]
+edition = "2018"
+rust-version = "1.31"
+license = "MIT OR Apache-2.0"
+description = "Parser and evaluator for Cargo's flavor of Semantic Versioning"
+repository = "https://github.com/dtolnay/semver"
+documentation = "https://docs.rs/semver"
+readme = "README.md"
+
+[features]
+default = ["std"]
+std = []
+
+[dependencies]
+serde = { version = "1.0", optional = true, default-features = false }
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
+rustdoc-args = ["--cfg", "doc_cfg"]
diff --git a/LICENSE b/LICENSE
new file mode 120000
index 0000000..6b579aa
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1 @@
+LICENSE-APACHE \ No newline at end of file
diff --git a/LICENSE-APACHE b/LICENSE-APACHE
new file mode 100644
index 0000000..16fe87b
--- /dev/null
+++ b/LICENSE-APACHE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/LICENSE-MIT b/LICENSE-MIT
new file mode 100644
index 0000000..31aa793
--- /dev/null
+++ b/LICENSE-MIT
@@ -0,0 +1,23 @@
+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.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..e999a46
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,20 @@
+name: "semver"
+description: "Parser and evaluator for Cargo\'s flavor of Semantic Versioning"
+third_party {
+ url {
+ type: HOMEPAGE
+ value: "https://crates.io/crates/semver"
+ }
+ url {
+ type: ARCHIVE
+ value: "https://static.crates.io/crates/semver/semver-1.0.5.crate"
+ }
+ version: "1.0.5"
+ # Dual-licensed, using the least restrictive per go/thirdpartylicenses#same.
+ license_type: NOTICE
+ last_upgrade_date {
+ year: 2022
+ month: 2
+ day: 18
+ }
+}
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..45dc4dd
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1 @@
+include platform/prebuilts/rust:master:/OWNERS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..109efa3
--- /dev/null
+++ b/README.md
@@ -0,0 +1,84 @@
+semver
+======
+
+[<img alt="github" src="https://img.shields.io/badge/github-dtolnay/semver-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="20">](https://github.com/dtolnay/semver)
+[<img alt="crates.io" src="https://img.shields.io/crates/v/semver.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/semver)
+[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-semver-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K" height="20">](https://docs.rs/semver/1.0.0)
+[<img alt="build status" src="https://img.shields.io/github/workflow/status/dtolnay/semver/CI/master?style=for-the-badge" height="20">](https://github.com/dtolnay/semver/actions?query=branch%3Amaster)
+
+A parser and evaluator for Cargo's flavor of Semantic Versioning.
+
+Semantic Versioning (see <https://semver.org>) is a guideline for how version
+numbers are assigned and incremented. It is widely followed within the
+Cargo/crates.io ecosystem for Rust.
+
+```toml
+[dependencies]
+semver = "1.0"
+```
+
+*Compiler support: requires rustc 1.31+*
+
+<br>
+
+## Example
+
+```rust
+use semver::{BuildMetadata, Prerelease, Version, VersionReq};
+
+fn main() {
+ let req = VersionReq::parse(">=1.2.3, <1.8.0").unwrap();
+
+ // Check whether this requirement matches version 1.2.3-alpha.1 (no)
+ let version = Version {
+ major: 1,
+ minor: 2,
+ patch: 3,
+ pre: Prerelease::new("alpha.1").unwrap(),
+ build: BuildMetadata::EMPTY,
+ };
+ assert!(!req.matches(&version));
+
+ // Check whether it matches 1.3.0 (yes it does)
+ let version = Version::parse("1.3.0").unwrap();
+ assert!(req.matches(&version));
+}
+```
+
+<br>
+
+## Scope of this crate
+
+Besides Cargo, several other package ecosystems and package managers for other
+languages also use SemVer:&ensp;RubyGems/Bundler for Ruby, npm for JavaScript,
+Composer for PHP, CocoaPods for Objective-C...
+
+The `semver` crate is specifically intended to implement Cargo's interpretation
+of Semantic Versioning.
+
+Where the various tools differ in their interpretation or implementation of the
+spec, this crate follows the implementation choices made by Cargo. If you are
+operating on version numbers from some other package ecosystem, you will want to
+use a different semver library which is appropriate to that ecosystem.
+
+The extent of Cargo's SemVer support is documented in the *[Specifying
+Dependencies]* chapter of the Cargo reference.
+
+[Specifying Dependencies]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html
+
+<br>
+
+#### License
+
+<sup>
+Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
+2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
+</sup>
+
+<br>
+
+<sub>
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
+be dual licensed as above, without any additional terms or conditions.
+</sub>
diff --git a/benches/parse.rs b/benches/parse.rs
new file mode 100644
index 0000000..d6aded7
--- /dev/null
+++ b/benches/parse.rs
@@ -0,0 +1,24 @@
+#![feature(test)]
+
+extern crate test;
+
+use semver::{Prerelease, Version, VersionReq};
+use test::{black_box, Bencher};
+
+#[bench]
+fn parse_prerelease(b: &mut Bencher) {
+ let text = "x.7.z.92";
+ b.iter(|| black_box(text).parse::<Prerelease>().unwrap());
+}
+
+#[bench]
+fn parse_version(b: &mut Bencher) {
+ let text = "1.0.2021-beta+exp.sha.5114f85";
+ b.iter(|| black_box(text).parse::<Version>().unwrap());
+}
+
+#[bench]
+fn parse_version_req(b: &mut Bencher) {
+ let text = ">=1.2.3, <2.0.0";
+ b.iter(|| black_box(text).parse::<VersionReq>().unwrap());
+}
diff --git a/build.rs b/build.rs
new file mode 100644
index 0000000..b39468d
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,79 @@
+use std::env;
+use std::process::Command;
+use std::str;
+
+fn main() {
+ let compiler = match rustc_minor_version() {
+ Some(compiler) => compiler,
+ None => return,
+ };
+
+ if compiler < 32 {
+ // u64::from_ne_bytes.
+ // https://doc.rust-lang.org/std/primitive.u64.html#method.from_ne_bytes
+ println!("cargo:rustc-cfg=no_from_ne_bytes");
+ }
+
+ if compiler < 33 {
+ // Exhaustive integer patterns. On older compilers, a final `_` arm is
+ // required even if every possible integer value is otherwise covered.
+ // https://github.com/rust-lang/rust/issues/50907
+ println!("cargo:rustc-cfg=no_exhaustive_int_match");
+ }
+
+ if compiler < 36 {
+ // extern crate alloc.
+ // https://blog.rust-lang.org/2019/07/04/Rust-1.36.0.html#the-alloc-crate-is-stable
+ println!("cargo:rustc-cfg=no_alloc_crate");
+ }
+
+ if compiler < 39 {
+ // const Vec::new.
+ // https://doc.rust-lang.org/std/vec/struct.Vec.html#method.new
+ println!("cargo:rustc-cfg=no_const_vec_new");
+ }
+
+ if compiler < 40 {
+ // #[non_exhaustive].
+ // https://blog.rust-lang.org/2019/12/19/Rust-1.40.0.html#non_exhaustive-structs-enums-and-variants
+ println!("cargo:rustc-cfg=no_non_exhaustive");
+ }
+
+ if compiler < 45 {
+ // String::strip_prefix.
+ // https://doc.rust-lang.org/std/primitive.str.html#method.repeat
+ println!("cargo:rustc-cfg=no_str_strip_prefix");
+ }
+
+ if compiler < 46 {
+ // #[track_caller].
+ // https://blog.rust-lang.org/2020/08/27/Rust-1.46.0.html#track_caller
+ println!("cargo:rustc-cfg=no_track_caller");
+ }
+
+ if compiler < 52 {
+ // #![deny(unsafe_op_in_unsafe_fn)].
+ // https://github.com/rust-lang/rust/issues/71668
+ println!("cargo:rustc-cfg=no_unsafe_op_in_unsafe_fn_lint");
+ }
+
+ if compiler < 53 {
+ // Efficient intrinsics for count-leading-zeros and count-trailing-zeros
+ // on NonZero integers stabilized in 1.53.0. On many architectures these
+ // are more efficient than counting zeros on ordinary zeroable integers.
+ // https://doc.rust-lang.org/std/num/struct.NonZeroU64.html#method.leading_zeros
+ // https://doc.rust-lang.org/std/num/struct.NonZeroU64.html#method.trailing_zeros
+ println!("cargo:rustc-cfg=no_nonzero_bitscan");
+ }
+}
+
+fn rustc_minor_version() -> Option<u32> {
+ let rustc = env::var_os("RUSTC")?;
+ let output = Command::new(rustc).arg("--version").output().ok()?;
+ let version = str::from_utf8(&output.stdout).ok()?;
+ let mut pieces = version.split('.');
+ if pieces.next() != Some("rustc 1") {
+ return None;
+ }
+ pieces.next()?.parse().ok()
+}
diff --git a/src/backport.rs b/src/backport.rs
new file mode 100644
index 0000000..f3a69c9
--- /dev/null
+++ b/src/backport.rs
@@ -0,0 +1,63 @@
+#[cfg(no_str_strip_prefix)] // rustc <1.45
+pub(crate) trait StripPrefixExt {
+ fn strip_prefix(&self, ch: char) -> Option<&str>;
+}
+
+#[cfg(no_str_strip_prefix)]
+impl StripPrefixExt for str {
+ fn strip_prefix(&self, ch: char) -> Option<&str> {
+ if self.starts_with(ch) {
+ Some(&self[ch.len_utf8()..])
+ } else {
+ None
+ }
+ }
+}
+
+#[cfg(no_from_ne_bytes)] // rustc <1.32
+pub(crate) trait FromNeBytes {
+ fn from_ne_bytes(bytes: [u8; 8]) -> Self;
+}
+
+#[cfg(no_from_ne_bytes)]
+impl FromNeBytes for u64 {
+ fn from_ne_bytes(bytes: [u8; 8]) -> Self {
+ unsafe { std::mem::transmute(bytes) }
+ }
+}
+
+pub(crate) use crate::alloc::vec::Vec;
+
+#[cfg(no_alloc_crate)] // rustc <1.36
+pub(crate) mod alloc {
+ pub use std::vec;
+
+ pub mod alloc {
+ use std::mem;
+
+ pub struct Layout {
+ size: usize,
+ }
+
+ impl Layout {
+ pub unsafe fn from_size_align_unchecked(size: usize, align: usize) -> Self {
+ assert_eq!(align, 2);
+ Layout { size }
+ }
+ }
+
+ pub unsafe fn alloc(layout: Layout) -> *mut u8 {
+ let len_u16 = (layout.size + 1) / 2;
+ let mut vec = Vec::new();
+ vec.reserve_exact(len_u16);
+ let ptr: *mut u16 = vec.as_mut_ptr();
+ mem::forget(vec);
+ ptr as *mut u8
+ }
+
+ pub unsafe fn dealloc(ptr: *mut u8, layout: Layout) {
+ let len_u16 = (layout.size + 1) / 2;
+ unsafe { Vec::from_raw_parts(ptr as *mut u16, 0, len_u16) };
+ }
+ }
+}
diff --git a/src/display.rs b/src/display.rs
new file mode 100644
index 0000000..3c2871b
--- /dev/null
+++ b/src/display.rs
@@ -0,0 +1,165 @@
+use crate::{BuildMetadata, Comparator, Op, Prerelease, Version, VersionReq};
+use core::fmt::{self, Alignment, Debug, Display, Write};
+
+impl Display for Version {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ let do_display = |formatter: &mut fmt::Formatter| -> fmt::Result {
+ write!(formatter, "{}.{}.{}", self.major, self.minor, self.patch)?;
+ if !self.pre.is_empty() {
+ write!(formatter, "-{}", self.pre)?;
+ }
+ if !self.build.is_empty() {
+ write!(formatter, "+{}", self.build)?;
+ }
+ Ok(())
+ };
+
+ let do_len = || -> usize {
+ digits(self.major)
+ + 1
+ + digits(self.minor)
+ + 1
+ + digits(self.patch)
+ + !self.pre.is_empty() as usize
+ + self.pre.len()
+ + !self.build.is_empty() as usize
+ + self.build.len()
+ };
+
+ pad(formatter, do_display, do_len)
+ }
+}
+
+impl Display for VersionReq {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ if self.comparators.is_empty() {
+ return formatter.write_str("*");
+ }
+ for (i, comparator) in self.comparators.iter().enumerate() {
+ if i > 0 {
+ formatter.write_str(", ")?;
+ }
+ write!(formatter, "{}", comparator)?;
+ }
+ Ok(())
+ }
+}
+
+impl Display for Comparator {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ let op = match self.op {
+ Op::Exact => "=",
+ Op::Greater => ">",
+ Op::GreaterEq => ">=",
+ Op::Less => "<",
+ Op::LessEq => "<=",
+ Op::Tilde => "~",
+ Op::Caret => "^",
+ Op::Wildcard => "",
+ #[cfg(no_non_exhaustive)]
+ Op::__NonExhaustive => unreachable!(),
+ };
+ formatter.write_str(op)?;
+ write!(formatter, "{}", self.major)?;
+ if let Some(minor) = &self.minor {
+ write!(formatter, ".{}", minor)?;
+ if let Some(patch) = &self.patch {
+ write!(formatter, ".{}", patch)?;
+ if !self.pre.is_empty() {
+ write!(formatter, "-{}", self.pre)?;
+ }
+ } else if self.op == Op::Wildcard {
+ formatter.write_str(".*")?;
+ }
+ } else if self.op == Op::Wildcard {
+ formatter.write_str(".*")?;
+ }
+ Ok(())
+ }
+}
+
+impl Display for Prerelease {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str(self.as_str())
+ }
+}
+
+impl Display for BuildMetadata {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str(self.as_str())
+ }
+}
+
+impl Debug for Version {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ let mut debug = formatter.debug_struct("Version");
+ debug
+ .field("major", &self.major)
+ .field("minor", &self.minor)
+ .field("patch", &self.patch);
+ if !self.pre.is_empty() {
+ debug.field("pre", &self.pre);
+ }
+ if !self.build.is_empty() {
+ debug.field("build", &self.build);
+ }
+ debug.finish()
+ }
+}
+
+impl Debug for Prerelease {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ write!(formatter, "Prerelease(\"{}\")", self)
+ }
+}
+
+impl Debug for BuildMetadata {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ write!(formatter, "BuildMetadata(\"{}\")", self)
+ }
+}
+
+fn pad(
+ formatter: &mut fmt::Formatter,
+ do_display: impl FnOnce(&mut fmt::Formatter) -> fmt::Result,
+ do_len: impl FnOnce() -> usize,
+) -> fmt::Result {
+ let min_width = match formatter.width() {
+ Some(min_width) => min_width,
+ None => return do_display(formatter),
+ };
+
+ let len = do_len();
+ if len >= min_width {
+ return do_display(formatter);
+ }
+
+ let default_align = Alignment::Left;
+ let align = formatter.align().unwrap_or(default_align);
+ let padding = min_width - len;
+ let (pre_pad, post_pad) = match align {
+ Alignment::Left => (0, padding),
+ Alignment::Right => (padding, 0),
+ Alignment::Center => (padding / 2, (padding + 1) / 2),
+ };
+
+ let fill = formatter.fill();
+ for _ in 0..pre_pad {
+ formatter.write_char(fill)?;
+ }
+
+ do_display(formatter)?;
+
+ for _ in 0..post_pad {
+ formatter.write_char(fill)?;
+ }
+ Ok(())
+}
+
+fn digits(val: u64) -> usize {
+ if val < 10 {
+ 1
+ } else {
+ 1 + digits(val / 10)
+ }
+}
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 0000000..72749c2
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,98 @@
+use crate::parse::Error;
+use core::fmt::{self, Debug, Display};
+
+pub(crate) enum ErrorKind {
+ UnexpectedEnd(Position),
+ UnexpectedChar(Position, char),
+ UnexpectedCharAfter(Position, char),
+ ExpectedCommaFound(Position, char),
+ LeadingZero(Position),
+ Overflow(Position),
+ EmptySegment(Position),
+ IllegalCharacter(Position),
+ WildcardNotTheOnlyComparator(char),
+ UnexpectedAfterWildcard,
+ ExcessiveComparators,
+}
+
+#[derive(Copy, Clone, Eq, PartialEq)]
+pub(crate) enum Position {
+ Major,
+ Minor,
+ Patch,
+ Pre,
+ Build,
+}
+
+#[cfg(feature = "std")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
+impl std::error::Error for Error {}
+
+impl Display for Error {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ match &self.kind {
+ ErrorKind::UnexpectedEnd(pos) => {
+ write!(formatter, "unexpected end of input while parsing {}", pos)
+ }
+ ErrorKind::UnexpectedChar(pos, ch) => {
+ write!(
+ formatter,
+ "unexpected character {:?} while parsing {}",
+ ch, pos,
+ )
+ }
+ ErrorKind::UnexpectedCharAfter(pos, ch) => {
+ write!(formatter, "unexpected character {:?} after {}", ch, pos)
+ }
+ ErrorKind::ExpectedCommaFound(pos, ch) => {
+ write!(formatter, "expected comma after {}, found {:?}", pos, ch)
+ }
+ ErrorKind::LeadingZero(pos) => {
+ write!(formatter, "invalid leading zero in {}", pos)
+ }
+ ErrorKind::Overflow(pos) => {
+ write!(formatter, "value of {} exceeds u64::MAX", pos)
+ }
+ ErrorKind::EmptySegment(pos) => {
+ write!(formatter, "empty identifier segment in {}", pos)
+ }
+ ErrorKind::IllegalCharacter(pos) => {
+ write!(formatter, "unexpected character in {}", pos)
+ }
+ ErrorKind::WildcardNotTheOnlyComparator(ch) => {
+ write!(
+ formatter,
+ "wildcard req ({}) must be the only comparator in the version req",
+ ch,
+ )
+ }
+ ErrorKind::UnexpectedAfterWildcard => {
+ formatter.write_str("unexpected character after wildcard in version req")
+ }
+ ErrorKind::ExcessiveComparators => {
+ formatter.write_str("excessive number of version comparators")
+ }
+ }
+ }
+}
+
+impl Display for Position {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str(match self {
+ Position::Major => "major version number",
+ Position::Minor => "minor version number",
+ Position::Patch => "patch version number",
+ Position::Pre => "pre-release identifier",
+ Position::Build => "build metadata",
+ })
+ }
+}
+
+impl Debug for Error {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str("Error(\"")?;
+ Display::fmt(self, formatter)?;
+ formatter.write_str("\")")?;
+ Ok(())
+ }
+}
diff --git a/src/eval.rs b/src/eval.rs
new file mode 100644
index 0000000..e6e3894
--- /dev/null
+++ b/src/eval.rs
@@ -0,0 +1,181 @@
+use crate::{Comparator, Op, Version, VersionReq};
+
+pub(crate) fn matches_req(req: &VersionReq, ver: &Version) -> bool {
+ for cmp in &req.comparators {
+ if !matches_impl(cmp, ver) {
+ return false;
+ }
+ }
+
+ if ver.pre.is_empty() {
+ return true;
+ }
+
+ // If a version has a prerelease tag (for example, 1.2.3-alpha.3) then it
+ // will only be allowed to satisfy req if at least one comparator with the
+ // same major.minor.patch also has a prerelease tag.
+ for cmp in &req.comparators {
+ if pre_is_compatible(cmp, ver) {
+ return true;
+ }
+ }
+
+ false
+}
+
+pub(crate) fn matches_comparator(cmp: &Comparator, ver: &Version) -> bool {
+ matches_impl(cmp, ver) && (ver.pre.is_empty() || pre_is_compatible(cmp, ver))
+}
+
+fn matches_impl(cmp: &Comparator, ver: &Version) -> bool {
+ match cmp.op {
+ Op::Exact | Op::Wildcard => matches_exact(cmp, ver),
+ Op::Greater => matches_greater(cmp, ver),
+ Op::GreaterEq => matches_exact(cmp, ver) || matches_greater(cmp, ver),
+ Op::Less => matches_less(cmp, ver),
+ Op::LessEq => matches_exact(cmp, ver) || matches_less(cmp, ver),
+ Op::Tilde => matches_tilde(cmp, ver),
+ Op::Caret => matches_caret(cmp, ver),
+ #[cfg(no_non_exhaustive)]
+ Op::__NonExhaustive => unreachable!(),
+ }
+}
+
+fn matches_exact(cmp: &Comparator, ver: &Version) -> bool {
+ if ver.major != cmp.major {
+ return false;
+ }
+
+ if let Some(minor) = cmp.minor {
+ if ver.minor != minor {
+ return false;
+ }
+ }
+
+ if let Some(patch) = cmp.patch {
+ if ver.patch != patch {
+ return false;
+ }
+ }
+
+ ver.pre == cmp.pre
+}
+
+fn matches_greater(cmp: &Comparator, ver: &Version) -> bool {
+ if ver.major != cmp.major {
+ return ver.major > cmp.major;
+ }
+
+ match cmp.minor {
+ None => return false,
+ Some(minor) => {
+ if ver.minor != minor {
+ return ver.minor > minor;
+ }
+ }
+ }
+
+ match cmp.patch {
+ None => return false,
+ Some(patch) => {
+ if ver.patch != patch {
+ return ver.patch > patch;
+ }
+ }
+ }
+
+ ver.pre > cmp.pre
+}
+
+fn matches_less(cmp: &Comparator, ver: &Version) -> bool {
+ if ver.major != cmp.major {
+ return ver.major < cmp.major;
+ }
+
+ match cmp.minor {
+ None => return false,
+ Some(minor) => {
+ if ver.minor != minor {
+ return ver.minor < minor;
+ }
+ }
+ }
+
+ match cmp.patch {
+ None => return false,
+ Some(patch) => {
+ if ver.patch != patch {
+ return ver.patch < patch;
+ }
+ }
+ }
+
+ ver.pre < cmp.pre
+}
+
+fn matches_tilde(cmp: &Comparator, ver: &Version) -> bool {
+ if ver.major != cmp.major {
+ return false;
+ }
+
+ if let Some(minor) = cmp.minor {
+ if ver.minor != minor {
+ return false;
+ }
+ }
+
+ if let Some(patch) = cmp.patch {
+ if ver.patch != patch {
+ return ver.patch > patch;
+ }
+ }
+
+ ver.pre >= cmp.pre
+}
+
+fn matches_caret(cmp: &Comparator, ver: &Version) -> bool {
+ if ver.major != cmp.major {
+ return false;
+ }
+
+ let minor = match cmp.minor {
+ None => return true,
+ Some(minor) => minor,
+ };
+
+ let patch = match cmp.patch {
+ None => {
+ if cmp.major > 0 {
+ return ver.minor >= minor;
+ } else {
+ return ver.minor == minor;
+ }
+ }
+ Some(patch) => patch,
+ };
+
+ if cmp.major > 0 {
+ if ver.minor != minor {
+ return ver.minor > minor;
+ } else if ver.patch != patch {
+ return ver.patch > patch;
+ }
+ } else if minor > 0 {
+ if ver.minor != minor {
+ return false;
+ } else if ver.patch != patch {
+ return ver.patch > patch;
+ }
+ } else if ver.minor != minor || ver.patch != patch {
+ return false;
+ }
+
+ ver.pre >= cmp.pre
+}
+
+fn pre_is_compatible(cmp: &Comparator, ver: &Version) -> bool {
+ cmp.major == ver.major
+ && cmp.minor == Some(ver.minor)
+ && cmp.patch == Some(ver.patch)
+ && !cmp.pre.is_empty()
+}
diff --git a/src/identifier.rs b/src/identifier.rs
new file mode 100644
index 0000000..500239e
--- /dev/null
+++ b/src/identifier.rs
@@ -0,0 +1,356 @@
+// This module implements Identifier, a short-optimized string allowed to
+// contain only the ASCII characters hyphen, dot, 0-9, A-Z, a-z.
+//
+// As of mid-2021, the distribution of pre-release lengths on crates.io is:
+//
+// length count length count length count
+// 0 355929 11 81 24 2
+// 1 208 12 48 25 6
+// 2 236 13 55 26 10
+// 3 1909 14 25 27 4
+// 4 1284 15 15 28 1
+// 5 1742 16 35 30 1
+// 6 3440 17 9 31 5
+// 7 5624 18 6 32 1
+// 8 1321 19 12 36 2
+// 9 179 20 2 37 379
+// 10 65 23 11
+//
+// and the distribution of build metadata lengths is:
+//
+// length count length count length count
+// 0 364445 8 7725 18 1
+// 1 72 9 16 19 1
+// 2 7 10 85 20 1
+// 3 28 11 17 22 4
+// 4 9 12 10 26 1
+// 5 68 13 9 27 1
+// 6 73 14 10 40 5
+// 7 53 15 6
+//
+// Therefore it really behooves us to be able to use the entire 8 bytes of a
+// pointer for inline storage. For both pre-release and build metadata there are
+// vastly more strings with length exactly 8 bytes than the sum over all lengths
+// longer than 8 bytes.
+//
+// To differentiate the inline representation from the heap allocated long
+// representation, we'll allocate heap pointers with 2-byte alignment so that
+// they are guaranteed to have an unset least significant bit. Then in the repr
+// we store for pointers, we rotate a 1 into the most significant bit of the
+// most significant byte, which is never set for an ASCII byte.
+//
+// Inline repr:
+//
+// 0xxxxxxx 0xxxxxxx 0xxxxxxx 0xxxxxxx 0xxxxxxx 0xxxxxxx 0xxxxxxx 0xxxxxxx
+//
+// Heap allocated repr:
+//
+// 1ppppppp pppppppp pppppppp pppppppp pppppppp pppppppp pppppppp pppppppp 0
+// ^ most significant bit least significant bit of orig ptr, rotated out ^
+//
+// Since the most significant bit doubles as a sign bit for the similarly sized
+// signed integer type, the CPU has an efficient instruction for inspecting it,
+// meaning we can differentiate between an inline repr and a heap allocated repr
+// in one instruction. Effectively an inline repr always looks like a positive
+// i64 while a heap allocated repr always looks like a negative i64.
+//
+// For the inline repr, we store \0 padding on the end of the stored characters,
+// and thus the string length is readily determined efficiently by a cttz (count
+// trailing zeros) or bsf (bit scan forward) instruction.
+//
+// For the heap allocated repr, the length is encoded as a base-128 varint at
+// the head of the allocation.
+//
+// Empty strings are stored as an all-1 bit pattern, corresponding to -1i64.
+// Consequently the all-0 bit pattern is never a legal representation in any
+// repr, leaving it available as a niche for downstream code. For example this
+// allows size_of::<Version>() == size_of::<Option<Version>>().
+
+use crate::alloc::alloc::{alloc, dealloc, Layout};
+#[cfg(no_from_ne_bytes)]
+use crate::backport::FromNeBytes;
+use core::mem;
+use core::num::{NonZeroU64, NonZeroUsize};
+use core::ptr;
+use core::slice;
+use core::str;
+
+#[repr(transparent)]
+pub(crate) struct Identifier {
+ repr: NonZeroU64,
+}
+
+impl Identifier {
+ const EMPTY: NonZeroU64 = unsafe { NonZeroU64::new_unchecked(!0) };
+
+ pub(crate) const fn empty() -> Self {
+ // `mov rax, -1`
+ Identifier { repr: Self::EMPTY }
+ }
+
+ // SAFETY: string must be ASCII and not contain \0 bytes.
+ pub(crate) unsafe fn new_unchecked(string: &str) -> Self {
+ let len = string.len();
+ let repr = match len as u64 {
+ 0 => Self::EMPTY,
+ 1..=8 => {
+ let mut bytes = [0u8; 8];
+ // SAFETY: string is big enough to read len bytes, bytes is big
+ // enough to write len bytes, and they do not overlap.
+ unsafe { ptr::copy_nonoverlapping(string.as_ptr(), bytes.as_mut_ptr(), len) };
+ // SAFETY: it's nonzero because the input string was at least 1
+ // byte of ASCII and did not contain \0.
+ unsafe { NonZeroU64::new_unchecked(u64::from_ne_bytes(bytes)) }
+ }
+ 9..=0xff_ffff_ffff_ffff => {
+ // SAFETY: len is in a range that does not contain 0.
+ let size = bytes_for_varint(unsafe { NonZeroUsize::new_unchecked(len) }) + len;
+ let align = 2;
+ // SAFETY: align is not zero, align is a power of two, and
+ // rounding size up to align does not overflow usize::MAX.
+ let layout = unsafe { Layout::from_size_align_unchecked(size, align) };
+ // SAFETY: layout's size is nonzero.
+ let ptr = unsafe { alloc(layout) };
+ let mut write = ptr;
+ let mut varint_remaining = len;
+ while varint_remaining > 0 {
+ // SAFETY: size is bytes_for_varint(len) bytes + len bytes.
+ // This is writing the first bytes_for_varint(len) bytes.
+ unsafe { ptr::write(write, varint_remaining as u8 | 0x80) };
+ varint_remaining >>= 7;
+ // SAFETY: still in bounds of the same allocation.
+ write = unsafe { write.add(1) };
+ }
+ // SAFETY: size is bytes_for_varint(len) bytes + len bytes. This
+ // is writing to the last len bytes.
+ unsafe { ptr::copy_nonoverlapping(string.as_ptr(), write, len) };
+ ptr_to_repr(ptr)
+ }
+ 0x100_0000_0000_0000..=0xffff_ffff_ffff_ffff => {
+ unreachable!("please refrain from storing >64 petabytes of text in semver version");
+ }
+ #[cfg(no_exhaustive_int_match)] // rustc <1.33
+ _ => unreachable!(),
+ };
+ Identifier { repr }
+ }
+
+ pub(crate) fn is_empty(&self) -> bool {
+ // `cmp rdi, -1` -- basically: `repr as i64 == -1`
+ self.repr == Self::EMPTY
+ }
+
+ fn is_inline(&self) -> bool {
+ // `test rdi, rdi` -- basically: `repr as i64 >= 0`
+ self.repr.get() >> 63 == 0
+ }
+
+ fn is_empty_or_inline(&self) -> bool {
+ // `cmp rdi, -2` -- basically: `repr as i64 > -2`
+ self.is_empty() || self.is_inline()
+ }
+
+ pub(crate) fn as_str(&self) -> &str {
+ if self.is_empty() {
+ ""
+ } else if self.is_inline() {
+ // SAFETY: repr is in the inline representation.
+ unsafe { inline_as_str(&self.repr) }
+ } else {
+ // SAFETY: repr is in the heap allocated representation.
+ unsafe { ptr_as_str(&self.repr) }
+ }
+ }
+}
+
+impl Clone for Identifier {
+ fn clone(&self) -> Self {
+ let repr = if self.is_empty_or_inline() {
+ self.repr
+ } else {
+ let ptr = repr_to_ptr(self.repr);
+ // SAFETY: ptr is one of our own heap allocations.
+ let len = unsafe { decode_len(ptr) };
+ let size = bytes_for_varint(len) + len.get();
+ let align = 2;
+ // SAFETY: align is not zero, align is a power of two, and rounding
+ // size up to align does not overflow usize::MAX. This is just
+ // duplicating a previous allocation where all of these guarantees
+ // were already made.
+ let layout = unsafe { Layout::from_size_align_unchecked(size, align) };
+ // SAFETY: layout's size is nonzero.
+ let clone = unsafe { alloc(layout) };
+ // SAFETY: new allocation cannot overlap the previous one (this was
+ // not a realloc). The argument ptrs are readable/writeable
+ // respectively for size bytes.
+ unsafe { ptr::copy_nonoverlapping(ptr, clone, size) }
+ ptr_to_repr(clone)
+ };
+ Identifier { repr }
+ }
+}
+
+impl Drop for Identifier {
+ fn drop(&mut self) {
+ if self.is_empty_or_inline() {
+ return;
+ }
+ let ptr = repr_to_ptr_mut(self.repr);
+ // SAFETY: ptr is one of our own heap allocations.
+ let len = unsafe { decode_len(ptr) };
+ let size = bytes_for_varint(len) + len.get();
+ let align = 2;
+ // SAFETY: align is not zero, align is a power of two, and rounding
+ // size up to align does not overflow usize::MAX. These guarantees were
+ // made when originally allocating this memory.
+ let layout = unsafe { Layout::from_size_align_unchecked(size, align) };
+ // SAFETY: ptr was previously allocated by the same allocator with the
+ // same layout.
+ unsafe { dealloc(ptr, layout) }
+ }
+}
+
+impl PartialEq for Identifier {
+ fn eq(&self, rhs: &Self) -> bool {
+ if self.is_empty_or_inline() {
+ // Fast path (most common)
+ self.repr == rhs.repr
+ } else if rhs.is_empty_or_inline() {
+ false
+ } else {
+ // SAFETY: both reprs are in the heap allocated representation.
+ unsafe { ptr_as_str(&self.repr) == ptr_as_str(&rhs.repr) }
+ }
+ }
+}
+
+// We use heap pointers that are 2-byte aligned, meaning they have an
+// insignificant 0 in the least significant bit. We take advantage of that
+// unneeded bit to rotate a 1 into the most significant bit to make the repr
+// distinguishable from ASCII bytes.
+fn ptr_to_repr(ptr: *mut u8) -> NonZeroU64 {
+ // `mov eax, 1`
+ // `shld rax, rdi, 63`
+ let repr = (ptr as u64 | 1).rotate_right(1);
+
+ // SAFETY: the most significant bit of repr is known to be set, so the value
+ // is not zero.
+ unsafe { NonZeroU64::new_unchecked(repr) }
+}
+
+// Shift out the 1 previously placed into the most significant bit of the least
+// significant byte. Shift in a low 0 bit to reconstruct the original 2-byte
+// aligned pointer.
+fn repr_to_ptr(repr: NonZeroU64) -> *const u8 {
+ // `lea rax, [rdi + rdi]`
+ (repr.get() << 1) as *const u8
+}
+
+fn repr_to_ptr_mut(repr: NonZeroU64) -> *mut u8 {
+ repr_to_ptr(repr) as *mut u8
+}
+
+// Compute the length of the inline string, assuming the argument is in short
+// string representation. Short strings are stored as 1 to 8 nonzero ASCII
+// bytes, followed by \0 padding for the remaining bytes.
+fn inline_len(repr: NonZeroU64) -> NonZeroUsize {
+ // Rustc >=1.53 has intrinsics for counting zeros on a non-zeroable integer.
+ // On many architectures these are more efficient than counting on ordinary
+ // zeroable integers (bsf vs cttz). On rustc <1.53 without those intrinsics,
+ // we count zeros in the u64 rather than the NonZeroU64.
+ #[cfg(no_nonzero_bitscan)]
+ let repr = repr.get();
+
+ #[cfg(target_endian = "little")]
+ let zero_bits_on_string_end = repr.leading_zeros();
+ #[cfg(target_endian = "big")]
+ let zero_bits_on_string_end = repr.trailing_zeros();
+
+ let nonzero_bytes = 8 - zero_bits_on_string_end as usize / 8;
+
+ // SAFETY: repr is nonzero, so it has at most 63 zero bits on either end,
+ // thus at least one nonzero byte.
+ unsafe { NonZeroUsize::new_unchecked(nonzero_bytes) }
+}
+
+// SAFETY: repr must be in the inline representation, i.e. at least 1 and at
+// most 8 nonzero ASCII bytes padded on the end with \0 bytes.
+unsafe fn inline_as_str(repr: &NonZeroU64) -> &str {
+ let ptr = repr as *const NonZeroU64 as *const u8;
+ let len = inline_len(*repr).get();
+ // SAFETY: we are viewing the nonzero ASCII prefix of the inline repr's
+ // contents as a slice of bytes. Input/output lifetimes are correctly
+ // associated.
+ let slice = unsafe { slice::from_raw_parts(ptr, len) };
+ // SAFETY: the string contents are known to be only ASCII bytes, which are
+ // always valid UTF-8.
+ unsafe { str::from_utf8_unchecked(slice) }
+}
+
+// Decode varint. Varints consist of between one and eight base-128 digits, each
+// of which is stored in a byte with most significant bit set. Adjacent to the
+// varint in memory there is guaranteed to be at least 9 ASCII bytes, each of
+// which has an unset most significant bit.
+//
+// SAFETY: ptr must be one of our own heap allocations, with the varint header
+// already written.
+unsafe fn decode_len(ptr: *const u8) -> NonZeroUsize {
+ // SAFETY: There is at least one byte of varint followed by at least 9 bytes
+ // of string content, which is at least 10 bytes total for the allocation,
+ // so reading the first two is no problem.
+ let [first, second] = unsafe { ptr::read(ptr as *const [u8; 2]) };
+ if second < 0x80 {
+ // SAFETY: the length of this heap allocated string has been encoded as
+ // one base-128 digit, so the length is at least 9 and at most 127. It
+ // cannot be zero.
+ unsafe { NonZeroUsize::new_unchecked((first & 0x7f) as usize) }
+ } else {
+ return unsafe { decode_len_cold(ptr) };
+
+ // Identifiers 128 bytes or longer. This is not exercised by any crate
+ // version currently published to crates.io.
+ #[cold]
+ #[inline(never)]
+ unsafe fn decode_len_cold(mut ptr: *const u8) -> NonZeroUsize {
+ let mut len = 0;
+ let mut shift = 0;
+ loop {
+ // SAFETY: varint continues while there are bytes having the
+ // most significant bit set, i.e. until we start hitting the
+ // ASCII string content with msb unset.
+ let byte = unsafe { *ptr };
+ if byte < 0x80 {
+ // SAFETY: the string length is known to be 128 bytes or
+ // longer.
+ return unsafe { NonZeroUsize::new_unchecked(len) };
+ }
+ // SAFETY: still in bounds of the same allocation.
+ ptr = unsafe { ptr.add(1) };
+ len += ((byte & 0x7f) as usize) << shift;
+ shift += 7;
+ }
+ }
+ }
+}
+
+// SAFETY: repr must be in the heap allocated representation, with varint header
+// and string contents already written.
+unsafe fn ptr_as_str(repr: &NonZeroU64) -> &str {
+ let ptr = repr_to_ptr(*repr);
+ let len = unsafe { decode_len(ptr) };
+ let header = bytes_for_varint(len);
+ let slice = unsafe { slice::from_raw_parts(ptr.add(header), len.get()) };
+ // SAFETY: all identifier contents are ASCII bytes, which are always valid
+ // UTF-8.
+ unsafe { str::from_utf8_unchecked(slice) }
+}
+
+// Number of base-128 digits required for the varint representation of a length.
+fn bytes_for_varint(len: NonZeroUsize) -> usize {
+ #[cfg(no_nonzero_bitscan)] // rustc <1.53
+ let len = len.get();
+
+ let usize_bits = mem::size_of::<usize>() * 8;
+ let len_bits = usize_bits - len.leading_zeros() as usize;
+ (len_bits + 6) / 7
+}
diff --git a/src/impls.rs b/src/impls.rs
new file mode 100644
index 0000000..c3b6c60
--- /dev/null
+++ b/src/impls.rs
@@ -0,0 +1,156 @@
+use crate::backport::*;
+use crate::identifier::Identifier;
+use crate::{BuildMetadata, Comparator, Prerelease, VersionReq};
+use core::cmp::Ordering;
+use core::hash::{Hash, Hasher};
+use core::iter::FromIterator;
+use core::ops::Deref;
+
+impl Default for Identifier {
+ fn default() -> Self {
+ Identifier::empty()
+ }
+}
+
+impl Eq for Identifier {}
+
+impl Hash for Identifier {
+ fn hash<H: Hasher>(&self, hasher: &mut H) {
+ self.as_str().hash(hasher);
+ }
+}
+
+impl Deref for Prerelease {
+ type Target = str;
+
+ fn deref(&self) -> &Self::Target {
+ self.identifier.as_str()
+ }
+}
+
+impl Deref for BuildMetadata {
+ type Target = str;
+
+ fn deref(&self) -> &Self::Target {
+ self.identifier.as_str()
+ }
+}
+
+impl PartialOrd for Prerelease {
+ fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
+ Some(Ord::cmp(self, rhs))
+ }
+}
+
+impl PartialOrd for BuildMetadata {
+ fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
+ Some(Ord::cmp(self, rhs))
+ }
+}
+
+impl Ord for Prerelease {
+ fn cmp(&self, rhs: &Self) -> Ordering {
+ match self.is_empty() {
+ true if rhs.is_empty() => return Ordering::Equal,
+ // A real release compares greater than prerelease.
+ true => return Ordering::Greater,
+ // Prerelease compares less than the real release.
+ false if rhs.is_empty() => return Ordering::Less,
+ false => {}
+ }
+
+ let lhs = self.as_str().split('.');
+ let mut rhs = rhs.as_str().split('.');
+
+ for lhs in lhs {
+ let rhs = match rhs.next() {
+ // Spec: "A larger set of pre-release fields has a higher
+ // precedence than a smaller set, if all of the preceding
+ // identifiers are equal."
+ None => return Ordering::Greater,
+ Some(rhs) => rhs,
+ };
+
+ let string_cmp = || Ord::cmp(lhs, rhs);
+ let is_ascii_digit = |b: u8| b.is_ascii_digit();
+ let ordering = match (
+ lhs.bytes().all(is_ascii_digit),
+ rhs.bytes().all(is_ascii_digit),
+ ) {
+ // Respect numeric ordering, for example 99 < 100. Spec says:
+ // "Identifiers consisting of only digits are compared
+ // numerically."
+ (true, true) => Ord::cmp(&lhs.len(), &rhs.len()).then_with(string_cmp),
+ // Spec: "Numeric identifiers always have lower precedence than
+ // non-numeric identifiers."
+ (true, false) => return Ordering::Less,
+ (false, true) => return Ordering::Greater,
+ // Spec: "Identifiers with letters or hyphens are compared
+ // lexically in ASCII sort order."
+ (false, false) => string_cmp(),
+ };
+
+ if ordering != Ordering::Equal {
+ return ordering;
+ }
+ }
+
+ if rhs.next().is_none() {
+ Ordering::Equal
+ } else {
+ Ordering::Less
+ }
+ }
+}
+
+impl Ord for BuildMetadata {
+ fn cmp(&self, rhs: &Self) -> Ordering {
+ let lhs = self.as_str().split('.');
+ let mut rhs = rhs.as_str().split('.');
+
+ for lhs in lhs {
+ let rhs = match rhs.next() {
+ None => return Ordering::Greater,
+ Some(rhs) => rhs,
+ };
+
+ let is_ascii_digit = |b: u8| b.is_ascii_digit();
+ let ordering = match (
+ lhs.bytes().all(is_ascii_digit),
+ rhs.bytes().all(is_ascii_digit),
+ ) {
+ (true, true) => {
+ // 0 < 00 < 1 < 01 < 001 < 2 < 02 < 002 < 10
+ let lhval = lhs.trim_start_matches('0');
+ let rhval = rhs.trim_start_matches('0');
+ Ord::cmp(&lhval.len(), &rhval.len())
+ .then_with(|| Ord::cmp(lhval, rhval))
+ .then_with(|| Ord::cmp(&lhs.len(), &rhs.len()))
+ }
+ (true, false) => return Ordering::Less,
+ (false, true) => return Ordering::Greater,
+ (false, false) => Ord::cmp(lhs, rhs),
+ };
+
+ if ordering != Ordering::Equal {
+ return ordering;
+ }
+ }
+
+ if rhs.next().is_none() {
+ Ordering::Equal
+ } else {
+ Ordering::Less
+ }
+ }
+}
+
+impl FromIterator<Comparator> for VersionReq {
+ fn from_iter<I>(iter: I) -> Self
+ where
+ I: IntoIterator<Item = Comparator>,
+ {
+ let comparators = Vec::from_iter(iter);
+ VersionReq { comparators }
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..da178d5
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,533 @@
+//! [![github]](https://github.com/dtolnay/semver)&ensp;[![crates-io]](https://crates.io/crates/semver)&ensp;[![docs-rs]](https://docs.rs/semver)
+//!
+//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
+//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
+//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K
+//!
+//! <br>
+//!
+//! A parser and evaluator for Cargo's flavor of Semantic Versioning.
+//!
+//! Semantic Versioning (see <https://semver.org>) is a guideline for how
+//! version numbers are assigned and incremented. It is widely followed within
+//! the Cargo/crates.io ecosystem for Rust.
+//!
+//! <br>
+//!
+//! # Example
+//!
+//! ```
+//! use semver::{BuildMetadata, Prerelease, Version, VersionReq};
+//!
+//! fn main() {
+//! let req = VersionReq::parse(">=1.2.3, <1.8.0").unwrap();
+//!
+//! // Check whether this requirement matches version 1.2.3-alpha.1 (no)
+//! let version = Version {
+//! major: 1,
+//! minor: 2,
+//! patch: 3,
+//! pre: Prerelease::new("alpha.1").unwrap(),
+//! build: BuildMetadata::EMPTY,
+//! };
+//! assert!(!req.matches(&version));
+//!
+//! // Check whether it matches 1.3.0 (yes it does)
+//! let version = Version::parse("1.3.0").unwrap();
+//! assert!(req.matches(&version));
+//! }
+//! ```
+//!
+//! <br><br>
+//!
+//! # Scope of this crate
+//!
+//! Besides Cargo, several other package ecosystems and package managers for
+//! other languages also use SemVer:&ensp;RubyGems/Bundler for Ruby, npm for
+//! JavaScript, Composer for PHP, CocoaPods for Objective-C...
+//!
+//! The `semver` crate is specifically intended to implement Cargo's
+//! interpretation of Semantic Versioning.
+//!
+//! Where the various tools differ in their interpretation or implementation of
+//! the spec, this crate follows the implementation choices made by Cargo. If
+//! you are operating on version numbers from some other package ecosystem, you
+//! will want to use a different semver library which is appropriate to that
+//! ecosystem.
+//!
+//! The extent of Cargo's SemVer support is documented in the *[Specifying
+//! Dependencies]* chapter of the Cargo reference.
+//!
+//! [Specifying Dependencies]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html
+
+#![doc(html_root_url = "https://docs.rs/semver/1.0.5")]
+#![cfg_attr(doc_cfg, feature(doc_cfg))]
+#![cfg_attr(all(not(feature = "std"), not(no_alloc_crate)), no_std)]
+#![cfg_attr(not(no_unsafe_op_in_unsafe_fn_lint), deny(unsafe_op_in_unsafe_fn))]
+#![cfg_attr(no_unsafe_op_in_unsafe_fn_lint, allow(unused_unsafe))]
+#![cfg_attr(no_str_strip_prefix, allow(unstable_name_collisions))]
+#![allow(
+ clippy::cast_lossless,
+ clippy::cast_possible_truncation,
+ clippy::doc_markdown,
+ clippy::items_after_statements,
+ clippy::manual_map,
+ clippy::match_bool,
+ clippy::missing_errors_doc,
+ clippy::must_use_candidate,
+ clippy::needless_doctest_main,
+ clippy::option_if_let_else,
+ clippy::ptr_as_ptr,
+ clippy::redundant_else,
+ clippy::semicolon_if_nothing_returned, // https://github.com/rust-lang/rust-clippy/issues/7324
+ clippy::similar_names,
+ clippy::unnested_or_patterns,
+ clippy::unseparated_literal_suffix,
+ clippy::wildcard_imports
+)]
+
+#[cfg(not(no_alloc_crate))]
+extern crate alloc;
+
+mod backport;
+mod display;
+mod error;
+mod eval;
+mod identifier;
+mod impls;
+mod parse;
+
+#[cfg(feature = "serde")]
+mod serde;
+
+use crate::alloc::vec::Vec;
+use crate::identifier::Identifier;
+use core::str::FromStr;
+
+#[allow(unused_imports)]
+use crate::backport::*;
+
+pub use crate::parse::Error;
+
+/// **SemVer version** as defined by <https://semver.org>.
+///
+/// # Syntax
+///
+/// - The major, minor, and patch numbers may be any integer 0 through u64::MAX.
+/// When representing a SemVer version as a string, each number is written as
+/// a base 10 integer. For example, `1.0.119`.
+///
+/// - Leading zeros are forbidden in those positions. For example `1.01.00` is
+/// invalid as a SemVer version.
+///
+/// - The pre-release identifier, if present, must conform to the syntax
+/// documented for [`Prerelease`].
+///
+/// - The build metadata, if present, must conform to the syntax documented for
+/// [`BuildMetadata`].
+///
+/// - Whitespace is not allowed anywhere in the version.
+///
+/// # Total ordering
+///
+/// Given any two SemVer versions, one is less than, greater than, or equal to
+/// the other. Versions may be compared against one another using Rust's usual
+/// comparison operators.
+///
+/// - The major, minor, and patch number are compared numerically from left to
+/// right, lexicographically ordered as a 3-tuple of integers. So for example
+/// version `1.5.0` is less than version `1.19.0`, despite the fact that
+/// "1.19.0" &lt; "1.5.0" as ASCIIbetically compared strings and 1.19 &lt; 1.5
+/// as real numbers.
+///
+/// - When major, minor, and patch are equal, a pre-release version is
+/// considered less than the ordinary release:&ensp;version `1.0.0-alpha.1` is
+/// less than version `1.0.0`.
+///
+/// - Two pre-releases of the same major, minor, patch are compared by
+/// lexicographic ordering of dot-separated components of the pre-release
+/// string.
+///
+/// - Identifiers consisting of only digits are compared
+/// numerically:&ensp;`1.0.0-pre.8` is less than `1.0.0-pre.12`.
+///
+/// - Identifiers that contain a letter or hyphen are compared in ASCII sort
+/// order:&ensp;`1.0.0-pre12` is less than `1.0.0-pre8`.
+///
+/// - Any numeric identifier is always less than any non-numeric
+/// identifier:&ensp;`1.0.0-pre.1` is less than `1.0.0-pre.x`.
+///
+/// Example:&ensp;`1.0.0-alpha`&ensp;&lt;&ensp;`1.0.0-alpha.1`&ensp;&lt;&ensp;`1.0.0-alpha.beta`&ensp;&lt;&ensp;`1.0.0-beta`&ensp;&lt;&ensp;`1.0.0-beta.2`&ensp;&lt;&ensp;`1.0.0-beta.11`&ensp;&lt;&ensp;`1.0.0-rc.1`&ensp;&lt;&ensp;`1.0.0`
+#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub struct Version {
+ pub major: u64,
+ pub minor: u64,
+ pub patch: u64,
+ pub pre: Prerelease,
+ pub build: BuildMetadata,
+}
+
+/// **SemVer version requirement** describing the intersection of some version
+/// comparators, such as `>=1.2.3, <1.8`.
+///
+/// # Syntax
+///
+/// - Either `*` (meaning "any"), or one or more comma-separated comparators.
+///
+/// - A [`Comparator`] is an operator ([`Op`]) and a partial version, separated
+/// by optional whitespace. For example `>=1.0.0` or `>=1.0`.
+///
+/// - Build metadata is syntactically permitted on the partial versions, but is
+/// completely ignored, as it's never relevant to whether any comparator
+/// matches a particular version.
+///
+/// - Whitespace is permitted around commas and around operators. Whitespace is
+/// not permitted within a partial version, i.e. anywhere between the major
+/// version number and its minor, patch, pre-release, or build metadata.
+#[derive(Clone, Eq, PartialEq, Hash, Debug)]
+#[cfg_attr(no_const_vec_new, derive(Default))]
+pub struct VersionReq {
+ pub comparators: Vec<Comparator>,
+}
+
+/// A pair of comparison operator and partial version, such as `>=1.2`. Forms
+/// one piece of a VersionReq.
+#[derive(Clone, Eq, PartialEq, Hash, Debug)]
+pub struct Comparator {
+ pub op: Op,
+ pub major: u64,
+ pub minor: Option<u64>,
+ /// Patch is only allowed if minor is Some.
+ pub patch: Option<u64>,
+ /// Non-empty pre-release is only allowed if patch is Some.
+ pub pre: Prerelease,
+}
+
+/// SemVer comparison operator: `=`, `>`, `>=`, `<`, `<=`, `~`, `^`, `*`.
+///
+/// # Op::Exact
+/// - &ensp;**`=I.J.K`**&emsp;&mdash;&emsp;exactly the version I.J.K
+/// - &ensp;**`=I.J`**&emsp;&mdash;&emsp;equivalent to `>=I.J.0, <I.(J+1).0`
+/// - &ensp;**`=I`**&emsp;&mdash;&emsp;equivalent to `>=I.0.0, <(I+1).0.0`
+///
+/// # Op::Greater
+/// - &ensp;**`>I.J.K`**
+/// - &ensp;**`>I.J`**&emsp;&mdash;&emsp;equivalent to `>=I.(J+1).0`
+/// - &ensp;**`>I`**&emsp;&mdash;&emsp;equivalent to `>=(I+1).0.0`
+///
+/// # Op::GreaterEq
+/// - &ensp;**`>=I.J.K`**
+/// - &ensp;**`>=I.J`**&emsp;&mdash;&emsp;equivalent to `>=I.J.0`
+/// - &ensp;**`>=I`**&emsp;&mdash;&emsp;equivalent to `>=I.0.0`
+///
+/// # Op::Less
+/// - &ensp;**`<I.J.K`**
+/// - &ensp;**`<I.J`**&emsp;&mdash;&emsp;equivalent to `<I.J.0`
+/// - &ensp;**`<I`**&emsp;&mdash;&emsp;equivalent to `<I.0.0`
+///
+/// # Op::LessEq
+/// - &ensp;**`<=I.J.K`**
+/// - &ensp;**`<=I.J`**&emsp;&mdash;&emsp;equivalent to `<I.(J+1).0`
+/// - &ensp;**`<=I`**&emsp;&mdash;&emsp;equivalent to `<(I+1).0.0`
+///
+/// # Op::Tilde&emsp;("patch" updates)
+/// *Tilde requirements allow the **patch** part of the semver version (the third number) to increase.*
+/// - &ensp;**`~I.J.K`**&emsp;&mdash;&emsp;equivalent to `>=I.J.K, <I.(J+1).0`
+/// - &ensp;**`~I.J`**&emsp;&mdash;&emsp;equivalent to `=I.J`
+/// - &ensp;**`~I`**&emsp;&mdash;&emsp;equivalent to `=I`
+///
+/// # Op::Caret&emsp;("compatible" updates)
+/// *Caret requirements allow parts that are **right of the first nonzero** part of the semver version to increase.*
+/// - &ensp;**`^I.J.K`**&ensp;(for I\>0)&emsp;&mdash;&emsp;equivalent to `>=I.J.K, <(I+1).0.0`
+/// - &ensp;**`^0.J.K`**&ensp;(for J\>0)&emsp;&mdash;&emsp;equivalent to `>=0.J.K, <0.(J+1).0`
+/// - &ensp;**`^0.0.K`**&emsp;&mdash;&emsp;equivalent to `=0.0.K`
+/// - &ensp;**`^I.J`**&ensp;(for I\>0 or J\>0)&emsp;&mdash;&emsp;equivalent to `^I.J.0`
+/// - &ensp;**`^0.0`**&emsp;&mdash;&emsp;equivalent to `=0.0`
+/// - &ensp;**`^I`**&emsp;&mdash;&emsp;equivalent to `=I`
+///
+/// # Op::Wildcard
+/// - &ensp;**`I.J.*`**&emsp;&mdash;&emsp;equivalent to `=I.J`
+/// - &ensp;**`I.*`**&ensp;or&ensp;**`I.*.*`**&emsp;&mdash;&emsp;equivalent to `=I`
+#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+#[cfg_attr(not(no_non_exhaustive), non_exhaustive)]
+pub enum Op {
+ Exact,
+ Greater,
+ GreaterEq,
+ Less,
+ LessEq,
+ Tilde,
+ Caret,
+ Wildcard,
+
+ #[cfg(no_non_exhaustive)] // rustc <1.40
+ #[doc(hidden)]
+ __NonExhaustive,
+}
+
+/// Optional pre-release identifier on a version string. This comes after `-` in
+/// a SemVer version, like `1.0.0-alpha.1`
+///
+/// # Examples
+///
+/// Some real world pre-release idioms drawn from crates.io:
+///
+/// - **[mio]** <code>0.7.0-<b>alpha.1</b></code> &mdash; the most common style
+/// for numbering pre-releases.
+///
+/// - **[pest]** <code>1.0.0-<b>beta.8</b></code>,&ensp;<code>1.0.0-<b>rc.0</b></code>
+/// &mdash; this crate makes a distinction between betas and release
+/// candidates.
+///
+/// - **[sassers]** <code>0.11.0-<b>shitshow</b></code> &mdash; ???.
+///
+/// - **[atomic-utils]** <code>0.0.0-<b>reserved</b></code> &mdash; a squatted
+/// crate name.
+///
+/// [mio]: https://crates.io/crates/mio
+/// [pest]: https://crates.io/crates/pest
+/// [atomic-utils]: https://crates.io/crates/atomic-utils
+/// [sassers]: https://crates.io/crates/sassers
+///
+/// *Tip:* Be aware that if you are planning to number your own pre-releases,
+/// you should prefer to separate the numeric part from any non-numeric
+/// identifiers by using a dot in between. That is, prefer pre-releases
+/// `alpha.1`, `alpha.2`, etc rather than `alpha1`, `alpha2` etc. The SemVer
+/// spec's rule for pre-release precedence has special treatment of numeric
+/// components in the pre-release string, but only if there are no non-digit
+/// characters in the same dot-separated component. So you'd have `alpha.2` &lt;
+/// `alpha.11` as intended, but `alpha11` &lt; `alpha2`.
+///
+/// # Syntax
+///
+/// Pre-release strings are a series of dot separated identifiers immediately
+/// following the patch version. Identifiers must comprise only ASCII
+/// alphanumerics and hyphens: `0-9`, `A-Z`, `a-z`, `-`. Identifiers must not be
+/// empty. Numeric identifiers must not include leading zeros.
+///
+/// # Total ordering
+///
+/// Pre-releases have a total order defined by the SemVer spec. It uses
+/// lexicographic ordering of dot-separated components. Identifiers consisting
+/// of only digits are compared numerically. Otherwise, identifiers are compared
+/// in ASCII sort order. Any numeric identifier is always less than any
+/// non-numeric identifier.
+///
+/// Example:&ensp;`alpha`&ensp;&lt;&ensp;`alpha.85`&ensp;&lt;&ensp;`alpha.90`&ensp;&lt;&ensp;`alpha.200`&ensp;&lt;&ensp;`alpha.0a`&ensp;&lt;&ensp;`alpha.1a0`&ensp;&lt;&ensp;`alpha.a`&ensp;&lt;&ensp;`beta`
+#[derive(Default, Clone, Eq, PartialEq, Hash)]
+pub struct Prerelease {
+ identifier: Identifier,
+}
+
+/// Optional build metadata identifier. This comes after `+` in a SemVer
+/// version, as in `0.8.1+zstd.1.5.0`.
+///
+/// # Examples
+///
+/// Some real world build metadata idioms drawn from crates.io:
+///
+/// - **[libgit2-sys]** <code>0.12.20+<b>1.1.0</b></code> &mdash; for this
+/// crate, the build metadata indicates the version of the C libgit2 library
+/// that the Rust crate is built against.
+///
+/// - **[mashup]** <code>0.1.13+<b>deprecated</b></code> &mdash; just the word
+/// "deprecated" for a crate that has been superseded by another. Eventually
+/// people will take notice of this in Cargo's build output where it lists the
+/// crates being compiled.
+///
+/// - **[google-bigquery2]** <code>2.0.4+<b>20210327</b></code> &mdash; this
+/// library is automatically generated from an official API schema, and the
+/// build metadata indicates the date on which that schema was last captured.
+///
+/// - **[fbthrift-git]** <code>0.0.6+<b>c7fcc0e</b></code> &mdash; this crate is
+/// published from snapshots of a big company monorepo. In monorepo
+/// development, there is no concept of versions, and all downstream code is
+/// just updated atomically in the same commit that breaking changes to a
+/// library are landed. Therefore for crates.io purposes, every published
+/// version must be assumed to be incompatible with the previous. The build
+/// metadata provides the source control hash of the snapshotted code.
+///
+/// [libgit2-sys]: https://crates.io/crates/libgit2-sys
+/// [mashup]: https://crates.io/crates/mashup
+/// [google-bigquery2]: https://crates.io/crates/google-bigquery2
+/// [fbthrift-git]: https://crates.io/crates/fbthrift-git
+///
+/// # Syntax
+///
+/// Build metadata is a series of dot separated identifiers immediately
+/// following the patch or pre-release version. Identifiers must comprise only
+/// ASCII alphanumerics and hyphens: `0-9`, `A-Z`, `a-z`, `-`. Identifiers must
+/// not be empty. Leading zeros *are* allowed, unlike any other place in the
+/// SemVer grammar.
+///
+/// # Total ordering
+///
+/// Build metadata is ignored in evaluating `VersionReq`; it plays no role in
+/// whether a `Version` matches any one of the comparison operators.
+///
+/// However for comparing build metadatas among one another, they do have a
+/// total order which is determined by lexicographic ordering of dot-separated
+/// components. Identifiers consisting of only digits are compared numerically.
+/// Otherwise, identifiers are compared in ASCII sort order. Any numeric
+/// identifier is always less than any non-numeric identifier.
+///
+/// Example:&ensp;`demo`&ensp;&lt;&ensp;`demo.85`&ensp;&lt;&ensp;`demo.90`&ensp;&lt;&ensp;`demo.090`&ensp;&lt;&ensp;`demo.200`&ensp;&lt;&ensp;`demo.1a0`&ensp;&lt;&ensp;`demo.a`&ensp;&lt;&ensp;`memo`
+#[derive(Default, Clone, Eq, PartialEq, Hash)]
+pub struct BuildMetadata {
+ identifier: Identifier,
+}
+
+impl Version {
+ /// Create `Version` with an empty pre-release and build metadata.
+ ///
+ /// Equivalent to:
+ ///
+ /// ```
+ /// # use semver::{BuildMetadata, Prerelease, Version};
+ /// #
+ /// # const fn new(major: u64, minor: u64, patch: u64) -> Version {
+ /// Version {
+ /// major,
+ /// minor,
+ /// patch,
+ /// pre: Prerelease::EMPTY,
+ /// build: BuildMetadata::EMPTY,
+ /// }
+ /// # }
+ /// ```
+ pub const fn new(major: u64, minor: u64, patch: u64) -> Self {
+ Version {
+ major,
+ minor,
+ patch,
+ pre: Prerelease::EMPTY,
+ build: BuildMetadata::EMPTY,
+ }
+ }
+
+ /// Create `Version` by parsing from string representation.
+ ///
+ /// # Errors
+ ///
+ /// Possible reasons for the parse to fail include:
+ ///
+ /// - `1.0` &mdash; too few numeric components. A SemVer version must have
+ /// exactly three. If you are looking at something that has fewer than
+ /// three numbers in it, it's possible it is a `VersionReq` instead (with
+ /// an implicit default `^` comparison operator).
+ ///
+ /// - `1.0.01` &mdash; a numeric component has a leading zero.
+ ///
+ /// - `1.0.unknown` &mdash; unexpected character in one of the components.
+ ///
+ /// - `1.0.0-` or `1.0.0+` &mdash; the pre-release or build metadata are
+ /// indicated present but empty.
+ ///
+ /// - `1.0.0-alpha_123` &mdash; pre-release or build metadata have something
+ /// outside the allowed characters, which are `0-9`, `A-Z`, `a-z`, `-`,
+ /// and `.` (dot).
+ ///
+ /// - `23456789999999999999.0.0` &mdash; overflow of a u64.
+ pub fn parse(text: &str) -> Result<Self, Error> {
+ Version::from_str(text)
+ }
+}
+
+impl VersionReq {
+ /// A `VersionReq` with no constraint on the version numbers it matches.
+ /// Equivalent to `VersionReq::parse("*").unwrap()`.
+ ///
+ /// In terms of comparators this is equivalent to `>=0.0.0`.
+ ///
+ /// Counterintuitively a `*` VersionReq does not match every possible
+ /// version number. In particular, in order for *any* `VersionReq` to match
+ /// a pre-release version, the `VersionReq` must contain at least one
+ /// `Comparator` that has an explicit major, minor, and patch version
+ /// identical to the pre-release being matched, and that has a nonempty
+ /// pre-release component. Since `*` is not written with an explicit major,
+ /// minor, and patch version, and does not contain a nonempty pre-release
+ /// component, it does not match any pre-release versions.
+ #[cfg(not(no_const_vec_new))] // rustc <1.39
+ pub const STAR: Self = VersionReq {
+ comparators: Vec::new(),
+ };
+
+ /// Create `VersionReq` by parsing from string representation.
+ ///
+ /// # Errors
+ ///
+ /// Possible reasons for the parse to fail include:
+ ///
+ /// - `>a.b` &mdash; unexpected characters in the partial version.
+ ///
+ /// - `@1.0.0` &mdash; unrecognized comparison operator.
+ ///
+ /// - `^1.0.0, ` &mdash; unexpected end of input.
+ ///
+ /// - `>=1.0 <2.0` &mdash; missing comma between comparators.
+ ///
+ /// - `*.*` &mdash; unsupported wildcard syntax.
+ pub fn parse(text: &str) -> Result<Self, Error> {
+ VersionReq::from_str(text)
+ }
+
+ /// Evaluate whether the given `Version` satisfies the version requirement
+ /// described by `self`.
+ pub fn matches(&self, version: &Version) -> bool {
+ eval::matches_req(self, version)
+ }
+}
+
+/// The default VersionReq is the same as [`VersionReq::STAR`].
+#[cfg(not(no_const_vec_new))]
+impl Default for VersionReq {
+ fn default() -> Self {
+ VersionReq::STAR
+ }
+}
+
+impl Comparator {
+ pub fn parse(text: &str) -> Result<Self, Error> {
+ Comparator::from_str(text)
+ }
+
+ pub fn matches(&self, version: &Version) -> bool {
+ eval::matches_comparator(self, version)
+ }
+}
+
+impl Prerelease {
+ pub const EMPTY: Self = Prerelease {
+ identifier: Identifier::empty(),
+ };
+
+ pub fn new(text: &str) -> Result<Self, Error> {
+ Prerelease::from_str(text)
+ }
+
+ pub fn as_str(&self) -> &str {
+ self.identifier.as_str()
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.identifier.is_empty()
+ }
+}
+
+impl BuildMetadata {
+ pub const EMPTY: Self = BuildMetadata {
+ identifier: Identifier::empty(),
+ };
+
+ pub fn new(text: &str) -> Result<Self, Error> {
+ BuildMetadata::from_str(text)
+ }
+
+ pub fn as_str(&self) -> &str {
+ self.identifier.as_str()
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.identifier.is_empty()
+ }
+}
diff --git a/src/parse.rs b/src/parse.rs
new file mode 100644
index 0000000..bf70739
--- /dev/null
+++ b/src/parse.rs
@@ -0,0 +1,405 @@
+use crate::backport::*;
+use crate::error::{ErrorKind, Position};
+use crate::identifier::Identifier;
+use crate::{BuildMetadata, Comparator, Op, Prerelease, Version, VersionReq};
+use core::str::FromStr;
+
+/// Error parsing a SemVer version or version requirement.
+///
+/// # Example
+///
+/// ```
+/// use semver::Version;
+///
+/// fn main() {
+/// let err = Version::parse("1.q.r").unwrap_err();
+///
+/// // "unexpected character 'q' while parsing minor version number"
+/// eprintln!("{}", err);
+/// }
+/// ```
+pub struct Error {
+ pub(crate) kind: ErrorKind,
+}
+
+impl FromStr for Version {
+ type Err = Error;
+
+ fn from_str(text: &str) -> Result<Self, Self::Err> {
+ let mut pos = Position::Major;
+ let (major, text) = numeric_identifier(text, pos)?;
+ let text = dot(text, pos)?;
+
+ pos = Position::Minor;
+ let (minor, text) = numeric_identifier(text, pos)?;
+ let text = dot(text, pos)?;
+
+ pos = Position::Patch;
+ let (patch, text) = numeric_identifier(text, pos)?;
+
+ if text.is_empty() {
+ return Ok(Version::new(major, minor, patch));
+ }
+
+ let (pre, text) = if let Some(text) = text.strip_prefix('-') {
+ pos = Position::Pre;
+ let (pre, text) = prerelease_identifier(text)?;
+ if pre.is_empty() {
+ return Err(Error::new(ErrorKind::EmptySegment(pos)));
+ }
+ (pre, text)
+ } else {
+ (Prerelease::EMPTY, text)
+ };
+
+ let (build, text) = if let Some(text) = text.strip_prefix('+') {
+ pos = Position::Build;
+ let (build, text) = build_identifier(text)?;
+ if build.is_empty() {
+ return Err(Error::new(ErrorKind::EmptySegment(pos)));
+ }
+ (build, text)
+ } else {
+ (BuildMetadata::EMPTY, text)
+ };
+
+ if let Some(unexpected) = text.chars().next() {
+ return Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected)));
+ }
+
+ Ok(Version {
+ major,
+ minor,
+ patch,
+ pre,
+ build,
+ })
+ }
+}
+
+impl FromStr for VersionReq {
+ type Err = Error;
+
+ fn from_str(text: &str) -> Result<Self, Self::Err> {
+ let text = text.trim_start_matches(' ');
+ if let Some((ch, text)) = wildcard(text) {
+ let rest = text.trim_start_matches(' ');
+ if rest.is_empty() {
+ #[cfg(not(no_const_vec_new))]
+ return Ok(VersionReq::STAR);
+ #[cfg(no_const_vec_new)] // rustc <1.39
+ return Ok(VersionReq {
+ comparators: Vec::new(),
+ });
+ } else if rest.starts_with(',') {
+ return Err(Error::new(ErrorKind::WildcardNotTheOnlyComparator(ch)));
+ } else {
+ return Err(Error::new(ErrorKind::UnexpectedAfterWildcard));
+ }
+ }
+
+ let depth = 0;
+ let mut comparators = Vec::new();
+ let len = version_req(text, &mut comparators, depth)?;
+ unsafe { comparators.set_len(len) }
+ Ok(VersionReq { comparators })
+ }
+}
+
+impl FromStr for Comparator {
+ type Err = Error;
+
+ fn from_str(text: &str) -> Result<Self, Self::Err> {
+ let text = text.trim_start_matches(' ');
+ let (comparator, pos, rest) = comparator(text)?;
+ if !rest.is_empty() {
+ let unexpected = rest.chars().next().unwrap();
+ return Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected)));
+ }
+ Ok(comparator)
+ }
+}
+
+impl FromStr for Prerelease {
+ type Err = Error;
+
+ fn from_str(text: &str) -> Result<Self, Self::Err> {
+ let (pre, rest) = prerelease_identifier(text)?;
+ if !rest.is_empty() {
+ return Err(Error::new(ErrorKind::IllegalCharacter(Position::Pre)));
+ }
+ Ok(pre)
+ }
+}
+
+impl FromStr for BuildMetadata {
+ type Err = Error;
+
+ fn from_str(text: &str) -> Result<Self, Self::Err> {
+ let (build, rest) = build_identifier(text)?;
+ if !rest.is_empty() {
+ return Err(Error::new(ErrorKind::IllegalCharacter(Position::Build)));
+ }
+ Ok(build)
+ }
+}
+
+impl Error {
+ fn new(kind: ErrorKind) -> Self {
+ Error { kind }
+ }
+}
+
+impl Op {
+ const DEFAULT: Self = Op::Caret;
+}
+
+fn numeric_identifier(input: &str, pos: Position) -> Result<(u64, &str), Error> {
+ let mut len = 0;
+ let mut value = 0u64;
+
+ while let Some(&digit) = input.as_bytes().get(len) {
+ if digit < b'0' || digit > b'9' {
+ break;
+ }
+ if value == 0 && len > 0 {
+ return Err(Error::new(ErrorKind::LeadingZero(pos)));
+ }
+ match value
+ .checked_mul(10)
+ .and_then(|value| value.checked_add((digit - b'0') as u64))
+ {
+ Some(sum) => value = sum,
+ None => return Err(Error::new(ErrorKind::Overflow(pos))),
+ }
+ len += 1;
+ }
+
+ if len > 0 {
+ Ok((value, &input[len..]))
+ } else if let Some(unexpected) = input[len..].chars().next() {
+ Err(Error::new(ErrorKind::UnexpectedChar(pos, unexpected)))
+ } else {
+ Err(Error::new(ErrorKind::UnexpectedEnd(pos)))
+ }
+}
+
+fn wildcard(input: &str) -> Option<(char, &str)> {
+ if let Some(rest) = input.strip_prefix('*') {
+ Some(('*', rest))
+ } else if let Some(rest) = input.strip_prefix('x') {
+ Some(('x', rest))
+ } else if let Some(rest) = input.strip_prefix('X') {
+ Some(('X', rest))
+ } else {
+ None
+ }
+}
+
+fn dot(input: &str, pos: Position) -> Result<&str, Error> {
+ if let Some(rest) = input.strip_prefix('.') {
+ Ok(rest)
+ } else if let Some(unexpected) = input.chars().next() {
+ Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected)))
+ } else {
+ Err(Error::new(ErrorKind::UnexpectedEnd(pos)))
+ }
+}
+
+fn prerelease_identifier(input: &str) -> Result<(Prerelease, &str), Error> {
+ let (string, rest) = identifier(input, Position::Pre)?;
+ let identifier = unsafe { Identifier::new_unchecked(string) };
+ Ok((Prerelease { identifier }, rest))
+}
+
+fn build_identifier(input: &str) -> Result<(BuildMetadata, &str), Error> {
+ let (string, rest) = identifier(input, Position::Build)?;
+ let identifier = unsafe { Identifier::new_unchecked(string) };
+ Ok((BuildMetadata { identifier }, rest))
+}
+
+fn identifier(input: &str, pos: Position) -> Result<(&str, &str), Error> {
+ let mut accumulated_len = 0;
+ let mut segment_len = 0;
+ let mut segment_has_nondigit = false;
+
+ loop {
+ match input.as_bytes().get(accumulated_len + segment_len) {
+ Some(b'A'..=b'Z') | Some(b'a'..=b'z') | Some(b'-') => {
+ segment_len += 1;
+ segment_has_nondigit = true;
+ }
+ Some(b'0'..=b'9') => {
+ segment_len += 1;
+ }
+ boundary => {
+ if segment_len == 0 {
+ if accumulated_len == 0 && boundary != Some(&b'.') {
+ return Ok(("", input));
+ } else {
+ return Err(Error::new(ErrorKind::EmptySegment(pos)));
+ }
+ }
+ if pos == Position::Pre
+ && segment_len > 1
+ && !segment_has_nondigit
+ && input[accumulated_len..].starts_with('0')
+ {
+ return Err(Error::new(ErrorKind::LeadingZero(pos)));
+ }
+ accumulated_len += segment_len;
+ if boundary == Some(&b'.') {
+ accumulated_len += 1;
+ segment_len = 0;
+ segment_has_nondigit = false;
+ } else {
+ return Ok(input.split_at(accumulated_len));
+ }
+ }
+ }
+ }
+}
+
+fn op(input: &str) -> (Op, &str) {
+ let bytes = input.as_bytes();
+ if bytes.get(0) == Some(&b'=') {
+ (Op::Exact, &input[1..])
+ } else if bytes.get(0) == Some(&b'>') {
+ if bytes.get(1) == Some(&b'=') {
+ (Op::GreaterEq, &input[2..])
+ } else {
+ (Op::Greater, &input[1..])
+ }
+ } else if bytes.get(0) == Some(&b'<') {
+ if bytes.get(1) == Some(&b'=') {
+ (Op::LessEq, &input[2..])
+ } else {
+ (Op::Less, &input[1..])
+ }
+ } else if bytes.get(0) == Some(&b'~') {
+ (Op::Tilde, &input[1..])
+ } else if bytes.get(0) == Some(&b'^') {
+ (Op::Caret, &input[1..])
+ } else {
+ (Op::DEFAULT, input)
+ }
+}
+
+fn comparator(input: &str) -> Result<(Comparator, Position, &str), Error> {
+ let (mut op, text) = op(input);
+ let default_op = input.len() == text.len();
+ let text = text.trim_start_matches(' ');
+
+ let mut pos = Position::Major;
+ let (major, text) = numeric_identifier(text, pos)?;
+ let mut has_wildcard = false;
+
+ let (minor, text) = if let Some(text) = text.strip_prefix('.') {
+ pos = Position::Minor;
+ if let Some((_, text)) = wildcard(text) {
+ has_wildcard = true;
+ if default_op {
+ op = Op::Wildcard;
+ }
+ (None, text)
+ } else {
+ let (minor, text) = numeric_identifier(text, pos)?;
+ (Some(minor), text)
+ }
+ } else {
+ (None, text)
+ };
+
+ let (patch, text) = if let Some(text) = text.strip_prefix('.') {
+ pos = Position::Patch;
+ if let Some((_, text)) = wildcard(text) {
+ if default_op {
+ op = Op::Wildcard;
+ }
+ (None, text)
+ } else if has_wildcard {
+ return Err(Error::new(ErrorKind::UnexpectedAfterWildcard));
+ } else {
+ let (patch, text) = numeric_identifier(text, pos)?;
+ (Some(patch), text)
+ }
+ } else {
+ (None, text)
+ };
+
+ let (pre, text) = if patch.is_some() && text.starts_with('-') {
+ pos = Position::Pre;
+ let text = &text[1..];
+ let (pre, text) = prerelease_identifier(text)?;
+ if pre.is_empty() {
+ return Err(Error::new(ErrorKind::EmptySegment(pos)));
+ }
+ (pre, text)
+ } else {
+ (Prerelease::EMPTY, text)
+ };
+
+ let text = if patch.is_some() && text.starts_with('+') {
+ pos = Position::Build;
+ let text = &text[1..];
+ let (build, text) = build_identifier(text)?;
+ if build.is_empty() {
+ return Err(Error::new(ErrorKind::EmptySegment(pos)));
+ }
+ text
+ } else {
+ text
+ };
+
+ let text = text.trim_start_matches(' ');
+
+ let comparator = Comparator {
+ op,
+ major,
+ minor,
+ patch,
+ pre,
+ };
+
+ Ok((comparator, pos, text))
+}
+
+fn version_req(input: &str, out: &mut Vec<Comparator>, depth: usize) -> Result<usize, Error> {
+ let (comparator, pos, text) = match comparator(input) {
+ Ok(success) => success,
+ Err(mut error) => {
+ if let Some((ch, mut rest)) = wildcard(input) {
+ rest = rest.trim_start_matches(' ');
+ if rest.is_empty() || rest.starts_with(',') {
+ error.kind = ErrorKind::WildcardNotTheOnlyComparator(ch);
+ }
+ }
+ return Err(error);
+ }
+ };
+
+ if text.is_empty() {
+ out.reserve_exact(depth + 1);
+ unsafe { out.as_mut_ptr().add(depth).write(comparator) }
+ return Ok(depth + 1);
+ }
+
+ let text = if let Some(text) = text.strip_prefix(',') {
+ text.trim_start_matches(' ')
+ } else {
+ let unexpected = text.chars().next().unwrap();
+ return Err(Error::new(ErrorKind::ExpectedCommaFound(pos, unexpected)));
+ };
+
+ const MAX_COMPARATORS: usize = 32;
+ if depth + 1 == MAX_COMPARATORS {
+ return Err(Error::new(ErrorKind::ExcessiveComparators));
+ }
+
+ // Recurse to collect parsed Comparator objects on the stack. We perform a
+ // single allocation to allocate exactly the right sized Vec only once the
+ // total number of comparators is known.
+ let len = version_req(text, out, depth + 1)?;
+ unsafe { out.as_mut_ptr().add(depth).write(comparator) }
+ Ok(len)
+}
diff --git a/src/serde.rs b/src/serde.rs
new file mode 100644
index 0000000..06eceb6
--- /dev/null
+++ b/src/serde.rs
@@ -0,0 +1,74 @@
+use crate::{Version, VersionReq};
+use core::fmt;
+use serde::de::{Deserialize, Deserializer, Error, Visitor};
+use serde::ser::{Serialize, Serializer};
+
+impl Serialize for Version {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ serializer.collect_str(self)
+ }
+}
+
+impl Serialize for VersionReq {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ serializer.collect_str(self)
+ }
+}
+
+impl<'de> Deserialize<'de> for Version {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ struct VersionVisitor;
+
+ impl<'de> Visitor<'de> for VersionVisitor {
+ type Value = Version;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str("semver version")
+ }
+
+ fn visit_str<E>(self, string: &str) -> Result<Self::Value, E>
+ where
+ E: Error,
+ {
+ string.parse().map_err(Error::custom)
+ }
+ }
+
+ deserializer.deserialize_str(VersionVisitor)
+ }
+}
+
+impl<'de> Deserialize<'de> for VersionReq {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ struct VersionReqVisitor;
+
+ impl<'de> Visitor<'de> for VersionReqVisitor {
+ type Value = VersionReq;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str("semver version")
+ }
+
+ fn visit_str<E>(self, string: &str) -> Result<Self::Value, E>
+ where
+ E: Error,
+ {
+ string.parse().map_err(Error::custom)
+ }
+ }
+
+ deserializer.deserialize_str(VersionReqVisitor)
+ }
+}
diff --git a/tests/node/mod.rs b/tests/node/mod.rs
new file mode 100644
index 0000000..eb50673
--- /dev/null
+++ b/tests/node/mod.rs
@@ -0,0 +1,43 @@
+#![cfg(test_node_semver)]
+
+use semver::Version;
+use std::fmt::{self, Display};
+use std::process::Command;
+
+#[derive(Default, Eq, PartialEq, Hash, Debug)]
+pub(super) struct VersionReq(semver::VersionReq);
+
+impl VersionReq {
+ pub(super) const STAR: Self = VersionReq(semver::VersionReq::STAR);
+
+ pub(super) fn matches(&self, version: &Version) -> bool {
+ let out = Command::new("node")
+ .arg("-e")
+ .arg(format!(
+ "console.log(require('semver').satisfies('{}', '{}'))",
+ version,
+ self.to_string().replace(',', ""),
+ ))
+ .output()
+ .unwrap();
+ if out.stdout == b"true\n" {
+ true
+ } else if out.stdout == b"false\n" {
+ false
+ } else {
+ let s = String::from_utf8_lossy(&out.stdout) + String::from_utf8_lossy(&out.stderr);
+ panic!("unexpected output: {}", s);
+ }
+ }
+}
+
+impl Display for VersionReq {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ Display::fmt(&self.0, formatter)
+ }
+}
+
+#[cfg_attr(not(no_track_caller), track_caller)]
+pub(super) fn req(text: &str) -> VersionReq {
+ VersionReq(crate::util::req(text))
+}
diff --git a/tests/test_identifier.rs b/tests/test_identifier.rs
new file mode 100644
index 0000000..dc888c9
--- /dev/null
+++ b/tests/test_identifier.rs
@@ -0,0 +1,45 @@
+#![allow(
+ clippy::eq_op,
+ clippy::needless_pass_by_value,
+ clippy::toplevel_ref_arg,
+ clippy::wildcard_imports
+)]
+
+mod util;
+
+use crate::util::*;
+use semver::Prerelease;
+
+#[test]
+fn test_new() {
+ fn test(identifier: Prerelease, expected: &str) {
+ assert_eq!(identifier.is_empty(), expected.is_empty());
+ assert_eq!(identifier.len(), expected.len());
+ assert_eq!(identifier.as_str(), expected);
+ assert_eq!(identifier, identifier);
+ assert_eq!(identifier, identifier.clone());
+ }
+
+ let ref mut string = String::new();
+ let limit = if cfg!(miri) { 40 } else { 280 }; // miri is slow
+ for _ in 0..limit {
+ test(prerelease(string), string);
+ string.push('1');
+ }
+
+ if !cfg!(miri) {
+ let ref string = string.repeat(20000);
+ test(prerelease(string), string);
+ }
+}
+
+#[test]
+fn test_eq() {
+ assert_eq!(prerelease("-"), prerelease("-"));
+ assert_ne!(prerelease("a"), prerelease("aa"));
+ assert_ne!(prerelease("aa"), prerelease("a"));
+ assert_ne!(prerelease("aaaaaaaaa"), prerelease("a"));
+ assert_ne!(prerelease("a"), prerelease("aaaaaaaaa"));
+ assert_ne!(prerelease("aaaaaaaaa"), prerelease("bbbbbbbbb"));
+ assert_ne!(build_metadata("1"), build_metadata("001"));
+}
diff --git a/tests/test_version.rs b/tests/test_version.rs
new file mode 100644
index 0000000..93a528c
--- /dev/null
+++ b/tests/test_version.rs
@@ -0,0 +1,241 @@
+#![allow(
+ clippy::nonminimal_bool,
+ clippy::too_many_lines,
+ clippy::wildcard_imports
+)]
+
+mod util;
+
+use crate::util::*;
+use semver::{BuildMetadata, Prerelease, Version};
+
+#[test]
+fn test_parse() {
+ let err = version_err("");
+ assert_to_string(
+ err,
+ "unexpected end of input while parsing major version number",
+ );
+
+ let err = version_err(" ");
+ assert_to_string(
+ err,
+ "unexpected character ' ' while parsing major version number",
+ );
+
+ let err = version_err("1");
+ assert_to_string(
+ err,
+ "unexpected end of input while parsing major version number",
+ );
+
+ let err = version_err("1.2");
+ assert_to_string(
+ err,
+ "unexpected end of input while parsing minor version number",
+ );
+
+ let err = version_err("1.2.3-");
+ assert_to_string(err, "empty identifier segment in pre-release identifier");
+
+ let err = version_err("a.b.c");
+ assert_to_string(
+ err,
+ "unexpected character 'a' while parsing major version number",
+ );
+
+ let err = version_err("1.2.3 abc");
+ assert_to_string(err, "unexpected character ' ' after patch version number");
+
+ let err = version_err("1.2.3-01");
+ assert_to_string(err, "invalid leading zero in pre-release identifier");
+
+ let parsed = version("1.2.3");
+ let expected = Version::new(1, 2, 3);
+ assert_eq!(parsed, expected);
+ let expected = Version {
+ major: 1,
+ minor: 2,
+ patch: 3,
+ pre: Prerelease::EMPTY,
+ build: BuildMetadata::EMPTY,
+ };
+ assert_eq!(parsed, expected);
+
+ let parsed = version("1.2.3-alpha1");
+ let expected = Version {
+ major: 1,
+ minor: 2,
+ patch: 3,
+ pre: prerelease("alpha1"),
+ build: BuildMetadata::EMPTY,
+ };
+ assert_eq!(parsed, expected);
+
+ let parsed = version("1.2.3+build5");
+ let expected = Version {
+ major: 1,
+ minor: 2,
+ patch: 3,
+ pre: Prerelease::EMPTY,
+ build: build_metadata("build5"),
+ };
+ assert_eq!(parsed, expected);
+
+ let parsed = version("1.2.3+5build");
+ let expected = Version {
+ major: 1,
+ minor: 2,
+ patch: 3,
+ pre: Prerelease::EMPTY,
+ build: build_metadata("5build"),
+ };
+ assert_eq!(parsed, expected);
+
+ let parsed = version("1.2.3-alpha1+build5");
+ let expected = Version {
+ major: 1,
+ minor: 2,
+ patch: 3,
+ pre: prerelease("alpha1"),
+ build: build_metadata("build5"),
+ };
+ assert_eq!(parsed, expected);
+
+ let parsed = version("1.2.3-1.alpha1.9+build5.7.3aedf");
+ let expected = Version {
+ major: 1,
+ minor: 2,
+ patch: 3,
+ pre: prerelease("1.alpha1.9"),
+ build: build_metadata("build5.7.3aedf"),
+ };
+ assert_eq!(parsed, expected);
+
+ let parsed = version("1.2.3-0a.alpha1.9+05build.7.3aedf");
+ let expected = Version {
+ major: 1,
+ minor: 2,
+ patch: 3,
+ pre: prerelease("0a.alpha1.9"),
+ build: build_metadata("05build.7.3aedf"),
+ };
+ assert_eq!(parsed, expected);
+
+ let parsed = version("0.4.0-beta.1+0851523");
+ let expected = Version {
+ major: 0,
+ minor: 4,
+ patch: 0,
+ pre: prerelease("beta.1"),
+ build: build_metadata("0851523"),
+ };
+ assert_eq!(parsed, expected);
+
+ // for https://nodejs.org/dist/index.json, where some older npm versions are "1.1.0-beta-10"
+ let parsed = version("1.1.0-beta-10");
+ let expected = Version {
+ major: 1,
+ minor: 1,
+ patch: 0,
+ pre: prerelease("beta-10"),
+ build: BuildMetadata::EMPTY,
+ };
+ assert_eq!(parsed, expected);
+}
+
+#[test]
+fn test_eq() {
+ assert_eq!(version("1.2.3"), version("1.2.3"));
+ assert_eq!(version("1.2.3-alpha1"), version("1.2.3-alpha1"));
+ assert_eq!(version("1.2.3+build.42"), version("1.2.3+build.42"));
+ assert_eq!(version("1.2.3-alpha1+42"), version("1.2.3-alpha1+42"));
+}
+
+#[test]
+fn test_ne() {
+ assert_ne!(version("0.0.0"), version("0.0.1"));
+ assert_ne!(version("0.0.0"), version("0.1.0"));
+ assert_ne!(version("0.0.0"), version("1.0.0"));
+ assert_ne!(version("1.2.3-alpha"), version("1.2.3-beta"));
+ assert_ne!(version("1.2.3+23"), version("1.2.3+42"));
+}
+
+#[test]
+fn test_display() {
+ assert_to_string(version("1.2.3"), "1.2.3");
+ assert_to_string(version("1.2.3-alpha1"), "1.2.3-alpha1");
+ assert_to_string(version("1.2.3+build.42"), "1.2.3+build.42");
+ assert_to_string(version("1.2.3-alpha1+42"), "1.2.3-alpha1+42");
+}
+
+#[test]
+fn test_lt() {
+ assert!(version("0.0.0") < version("1.2.3-alpha2"));
+ assert!(version("1.0.0") < version("1.2.3-alpha2"));
+ assert!(version("1.2.0") < version("1.2.3-alpha2"));
+ assert!(version("1.2.3-alpha1") < version("1.2.3"));
+ assert!(version("1.2.3-alpha1") < version("1.2.3-alpha2"));
+ assert!(!(version("1.2.3-alpha2") < version("1.2.3-alpha2")));
+ assert!(version("1.2.3+23") < version("1.2.3+42"));
+}
+
+#[test]
+fn test_le() {
+ assert!(version("0.0.0") <= version("1.2.3-alpha2"));
+ assert!(version("1.0.0") <= version("1.2.3-alpha2"));
+ assert!(version("1.2.0") <= version("1.2.3-alpha2"));
+ assert!(version("1.2.3-alpha1") <= version("1.2.3-alpha2"));
+ assert!(version("1.2.3-alpha2") <= version("1.2.3-alpha2"));
+ assert!(version("1.2.3+23") <= version("1.2.3+42"));
+}
+
+#[test]
+fn test_gt() {
+ assert!(version("1.2.3-alpha2") > version("0.0.0"));
+ assert!(version("1.2.3-alpha2") > version("1.0.0"));
+ assert!(version("1.2.3-alpha2") > version("1.2.0"));
+ assert!(version("1.2.3-alpha2") > version("1.2.3-alpha1"));
+ assert!(version("1.2.3") > version("1.2.3-alpha2"));
+ assert!(!(version("1.2.3-alpha2") > version("1.2.3-alpha2")));
+ assert!(!(version("1.2.3+23") > version("1.2.3+42")));
+}
+
+#[test]
+fn test_ge() {
+ assert!(version("1.2.3-alpha2") >= version("0.0.0"));
+ assert!(version("1.2.3-alpha2") >= version("1.0.0"));
+ assert!(version("1.2.3-alpha2") >= version("1.2.0"));
+ assert!(version("1.2.3-alpha2") >= version("1.2.3-alpha1"));
+ assert!(version("1.2.3-alpha2") >= version("1.2.3-alpha2"));
+ assert!(!(version("1.2.3+23") >= version("1.2.3+42")));
+}
+
+#[test]
+fn test_spec_order() {
+ let vs = [
+ "1.0.0-alpha",
+ "1.0.0-alpha.1",
+ "1.0.0-alpha.beta",
+ "1.0.0-beta",
+ "1.0.0-beta.2",
+ "1.0.0-beta.11",
+ "1.0.0-rc.1",
+ "1.0.0",
+ ];
+ let mut i = 1;
+ while i < vs.len() {
+ let a = version(vs[i - 1]);
+ let b = version(vs[i]);
+ assert!(a < b, "nope {:?} < {:?}", a, b);
+ i += 1;
+ }
+}
+
+#[test]
+fn test_align() {
+ let version = version("1.2.3-rc1");
+ assert_eq!("1.2.3-rc1 ", format!("{:20}", version));
+ assert_eq!("*****1.2.3-rc1******", format!("{:*^20}", version));
+ assert_eq!(" 1.2.3-rc1", format!("{:>20}", version));
+}
diff --git a/tests/test_version_req.rs b/tests/test_version_req.rs
new file mode 100644
index 0000000..2ea33ab
--- /dev/null
+++ b/tests/test_version_req.rs
@@ -0,0 +1,443 @@
+#![allow(
+ clippy::missing_panics_doc,
+ clippy::shadow_unrelated,
+ clippy::toplevel_ref_arg,
+ clippy::wildcard_imports
+)]
+
+mod node;
+mod util;
+
+use crate::util::*;
+use std::collections::hash_map::DefaultHasher;
+use std::hash::{Hash, Hasher};
+
+#[cfg(test_node_semver)]
+use node::{req, VersionReq};
+#[cfg(not(test_node_semver))]
+use semver::VersionReq;
+
+#[cfg_attr(not(no_track_caller), track_caller)]
+fn assert_match_all(req: &VersionReq, versions: &[&str]) {
+ for string in versions {
+ let parsed = version(string);
+ assert!(req.matches(&parsed), "did not match {}", string);
+ }
+}
+
+#[cfg_attr(not(no_track_caller), track_caller)]
+fn assert_match_none(req: &VersionReq, versions: &[&str]) {
+ for string in versions {
+ let parsed = version(string);
+ assert!(!req.matches(&parsed), "matched {}", string);
+ }
+}
+
+#[test]
+fn test_basic() {
+ let ref r = req("1.0.0");
+ assert_to_string(r, "^1.0.0");
+ assert_match_all(r, &["1.0.0", "1.1.0", "1.0.1"]);
+ assert_match_none(r, &["0.9.9", "0.10.0", "0.1.0", "1.0.0-pre", "1.0.1-pre"]);
+}
+
+#[test]
+#[cfg(not(no_const_vec_new))]
+fn test_default() {
+ let ref r = VersionReq::default();
+ assert_eq!(r, &VersionReq::STAR);
+}
+
+#[test]
+fn test_exact() {
+ let ref r = req("=1.0.0");
+ assert_to_string(r, "=1.0.0");
+ assert_match_all(r, &["1.0.0"]);
+ assert_match_none(r, &["1.0.1", "0.9.9", "0.10.0", "0.1.0", "1.0.0-pre"]);
+
+ let ref r = req("=0.9.0");
+ assert_to_string(r, "=0.9.0");
+ assert_match_all(r, &["0.9.0"]);
+ assert_match_none(r, &["0.9.1", "1.9.0", "0.0.9", "0.9.0-pre"]);
+
+ let ref r = req("=0.0.2");
+ assert_to_string(r, "=0.0.2");
+ assert_match_all(r, &["0.0.2"]);
+ assert_match_none(r, &["0.0.1", "0.0.3", "0.0.2-pre"]);
+
+ let ref r = req("=0.1.0-beta2.a");
+ assert_to_string(r, "=0.1.0-beta2.a");
+ assert_match_all(r, &["0.1.0-beta2.a"]);
+ assert_match_none(r, &["0.9.1", "0.1.0", "0.1.1-beta2.a", "0.1.0-beta2"]);
+
+ let ref r = req("=0.1.0+meta");
+ assert_to_string(r, "=0.1.0");
+ assert_match_all(r, &["0.1.0", "0.1.0+meta", "0.1.0+any"]);
+}
+
+#[test]
+pub fn test_greater_than() {
+ let ref r = req(">= 1.0.0");
+ assert_to_string(r, ">=1.0.0");
+ assert_match_all(r, &["1.0.0", "2.0.0"]);
+ assert_match_none(r, &["0.1.0", "0.0.1", "1.0.0-pre", "2.0.0-pre"]);
+
+ let ref r = req(">= 2.1.0-alpha2");
+ assert_to_string(r, ">=2.1.0-alpha2");
+ assert_match_all(r, &["2.1.0-alpha2", "2.1.0-alpha3", "2.1.0", "3.0.0"]);
+ assert_match_none(
+ r,
+ &["2.0.0", "2.1.0-alpha1", "2.0.0-alpha2", "3.0.0-alpha2"],
+ );
+}
+
+#[test]
+pub fn test_less_than() {
+ let ref r = req("< 1.0.0");
+ assert_to_string(r, "<1.0.0");
+ assert_match_all(r, &["0.1.0", "0.0.1"]);
+ assert_match_none(r, &["1.0.0", "1.0.0-beta", "1.0.1", "0.9.9-alpha"]);
+
+ let ref r = req("<= 2.1.0-alpha2");
+ assert_match_all(r, &["2.1.0-alpha2", "2.1.0-alpha1", "2.0.0", "1.0.0"]);
+ assert_match_none(
+ r,
+ &["2.1.0", "2.2.0-alpha1", "2.0.0-alpha2", "1.0.0-alpha2"],
+ );
+
+ let ref r = req(">1.0.0-alpha, <1.0.0");
+ assert_match_all(r, &["1.0.0-beta"]);
+
+ let ref r = req(">1.0.0-alpha, <1.0");
+ assert_match_none(r, &["1.0.0-beta"]);
+
+ let ref r = req(">1.0.0-alpha, <1");
+ assert_match_none(r, &["1.0.0-beta"]);
+}
+
+#[test]
+pub fn test_multiple() {
+ let ref r = req("> 0.0.9, <= 2.5.3");
+ assert_to_string(r, ">0.0.9, <=2.5.3");
+ assert_match_all(r, &["0.0.10", "1.0.0", "2.5.3"]);
+ assert_match_none(r, &["0.0.8", "2.5.4"]);
+
+ let ref r = req("0.3.0, 0.4.0");
+ assert_to_string(r, "^0.3.0, ^0.4.0");
+ assert_match_none(r, &["0.0.8", "0.3.0", "0.4.0"]);
+
+ let ref r = req("<= 0.2.0, >= 0.5.0");
+ assert_to_string(r, "<=0.2.0, >=0.5.0");
+ assert_match_none(r, &["0.0.8", "0.3.0", "0.5.1"]);
+
+ let ref r = req("0.1.0, 0.1.4, 0.1.6");
+ assert_to_string(r, "^0.1.0, ^0.1.4, ^0.1.6");
+ assert_match_all(r, &["0.1.6", "0.1.9"]);
+ assert_match_none(r, &["0.1.0", "0.1.4", "0.2.0"]);
+
+ let err = req_err("> 0.1.0,");
+ assert_to_string(
+ err,
+ "unexpected end of input while parsing major version number",
+ );
+
+ let err = req_err("> 0.3.0, ,");
+ assert_to_string(
+ err,
+ "unexpected character ',' while parsing major version number",
+ );
+
+ let ref r = req(">=0.5.1-alpha3, <0.6");
+ assert_to_string(r, ">=0.5.1-alpha3, <0.6");
+ assert_match_all(
+ r,
+ &[
+ "0.5.1-alpha3",
+ "0.5.1-alpha4",
+ "0.5.1-beta",
+ "0.5.1",
+ "0.5.5",
+ ],
+ );
+ assert_match_none(
+ r,
+ &["0.5.1-alpha1", "0.5.2-alpha3", "0.5.5-pre", "0.5.0-pre"],
+ );
+ assert_match_none(r, &["0.6.0", "0.6.0-pre"]);
+
+ // https://github.com/steveklabnik/semver/issues/56
+ let err = req_err("1.2.3 - 2.3.4");
+ assert_to_string(err, "expected comma after patch version number, found '-'");
+}
+
+#[test]
+pub fn test_whitespace_delimited_comparator_sets() {
+ // https://github.com/steveklabnik/semver/issues/55
+ let err = req_err("> 0.0.9 <= 2.5.3");
+ assert_to_string(err, "expected comma after patch version number, found '<'");
+}
+
+#[test]
+pub fn test_tilde() {
+ let ref r = req("~1");
+ assert_match_all(r, &["1.0.0", "1.0.1", "1.1.1"]);
+ assert_match_none(r, &["0.9.1", "2.9.0", "0.0.9"]);
+
+ let ref r = req("~1.2");
+ assert_match_all(r, &["1.2.0", "1.2.1"]);
+ assert_match_none(r, &["1.1.1", "1.3.0", "0.0.9"]);
+
+ let ref r = req("~1.2.2");
+ assert_match_all(r, &["1.2.2", "1.2.4"]);
+ assert_match_none(r, &["1.2.1", "1.9.0", "1.0.9", "2.0.1", "0.1.3"]);
+
+ let ref r = req("~1.2.3-beta.2");
+ assert_match_all(r, &["1.2.3", "1.2.4", "1.2.3-beta.2", "1.2.3-beta.4"]);
+ assert_match_none(r, &["1.3.3", "1.1.4", "1.2.3-beta.1", "1.2.4-beta.2"]);
+}
+
+#[test]
+pub fn test_caret() {
+ let ref r = req("^1");
+ assert_match_all(r, &["1.1.2", "1.1.0", "1.2.1", "1.0.1"]);
+ assert_match_none(r, &["0.9.1", "2.9.0", "0.1.4"]);
+ assert_match_none(r, &["1.0.0-beta1", "0.1.0-alpha", "1.0.1-pre"]);
+
+ let ref r = req("^1.1");
+ assert_match_all(r, &["1.1.2", "1.1.0", "1.2.1"]);
+ assert_match_none(r, &["0.9.1", "2.9.0", "1.0.1", "0.1.4"]);
+
+ let ref r = req("^1.1.2");
+ assert_match_all(r, &["1.1.2", "1.1.4", "1.2.1"]);
+ assert_match_none(r, &["0.9.1", "2.9.0", "1.1.1", "0.0.1"]);
+ assert_match_none(r, &["1.1.2-alpha1", "1.1.3-alpha1", "2.9.0-alpha1"]);
+
+ let ref r = req("^0.1.2");
+ assert_match_all(r, &["0.1.2", "0.1.4"]);
+ assert_match_none(r, &["0.9.1", "2.9.0", "1.1.1", "0.0.1"]);
+ assert_match_none(r, &["0.1.2-beta", "0.1.3-alpha", "0.2.0-pre"]);
+
+ let ref r = req("^0.5.1-alpha3");
+ assert_match_all(
+ r,
+ &[
+ "0.5.1-alpha3",
+ "0.5.1-alpha4",
+ "0.5.1-beta",
+ "0.5.1",
+ "0.5.5",
+ ],
+ );
+ assert_match_none(
+ r,
+ &[
+ "0.5.1-alpha1",
+ "0.5.2-alpha3",
+ "0.5.5-pre",
+ "0.5.0-pre",
+ "0.6.0",
+ ],
+ );
+
+ let ref r = req("^0.0.2");
+ assert_match_all(r, &["0.0.2"]);
+ assert_match_none(r, &["0.9.1", "2.9.0", "1.1.1", "0.0.1", "0.1.4"]);
+
+ let ref r = req("^0.0");
+ assert_match_all(r, &["0.0.2", "0.0.0"]);
+ assert_match_none(r, &["0.9.1", "2.9.0", "1.1.1", "0.1.4"]);
+
+ let ref r = req("^0");
+ assert_match_all(r, &["0.9.1", "0.0.2", "0.0.0"]);
+ assert_match_none(r, &["2.9.0", "1.1.1"]);
+
+ let ref r = req("^1.4.2-beta.5");
+ assert_match_all(
+ r,
+ &["1.4.2", "1.4.3", "1.4.2-beta.5", "1.4.2-beta.6", "1.4.2-c"],
+ );
+ assert_match_none(
+ r,
+ &[
+ "0.9.9",
+ "2.0.0",
+ "1.4.2-alpha",
+ "1.4.2-beta.4",
+ "1.4.3-beta.5",
+ ],
+ );
+}
+
+#[test]
+pub fn test_wildcard() {
+ let err = req_err("");
+ assert_to_string(
+ err,
+ "unexpected end of input while parsing major version number",
+ );
+
+ let ref r = req("*");
+ assert_match_all(r, &["0.9.1", "2.9.0", "0.0.9", "1.0.1", "1.1.1"]);
+ assert_match_none(r, &["1.0.0-pre"]);
+
+ for s in &["x", "X"] {
+ assert_eq!(*r, req(s));
+ }
+
+ let ref r = req("1.*");
+ assert_match_all(r, &["1.2.0", "1.2.1", "1.1.1", "1.3.0"]);
+ assert_match_none(r, &["0.0.9", "1.2.0-pre"]);
+
+ for s in &["1.x", "1.X", "1.*.*"] {
+ assert_eq!(*r, req(s));
+ }
+
+ let ref r = req("1.2.*");
+ assert_match_all(r, &["1.2.0", "1.2.2", "1.2.4"]);
+ assert_match_none(r, &["1.9.0", "1.0.9", "2.0.1", "0.1.3", "1.2.2-pre"]);
+
+ for s in &["1.2.x", "1.2.X"] {
+ assert_eq!(*r, req(s));
+ }
+}
+
+#[test]
+pub fn test_logical_or() {
+ // https://github.com/steveklabnik/semver/issues/57
+ let err = req_err("=1.2.3 || =2.3.4");
+ assert_to_string(err, "expected comma after patch version number, found '|'");
+
+ let err = req_err("1.1 || =1.2.3");
+ assert_to_string(err, "expected comma after minor version number, found '|'");
+
+ let err = req_err("6.* || 8.* || >= 10.*");
+ assert_to_string(err, "expected comma after minor version number, found '|'");
+}
+
+#[test]
+pub fn test_any() {
+ #[cfg(not(no_const_vec_new))]
+ let ref r = VersionReq::STAR;
+ #[cfg(no_const_vec_new)]
+ let ref r = VersionReq {
+ comparators: Vec::new(),
+ };
+ assert_match_all(r, &["0.0.1", "0.1.0", "1.0.0"]);
+}
+
+#[test]
+pub fn test_pre() {
+ let ref r = req("=2.1.1-really.0");
+ assert_match_all(r, &["2.1.1-really.0"]);
+}
+
+#[test]
+pub fn test_parse_errors() {
+ let err = req_err("\0");
+ assert_to_string(
+ err,
+ "unexpected character '\\u{0}' while parsing major version number",
+ );
+
+ let err = req_err(">= >= 0.0.2");
+ assert_to_string(
+ err,
+ "unexpected character '>' while parsing major version number",
+ );
+
+ let err = req_err(">== 0.0.2");
+ assert_to_string(
+ err,
+ "unexpected character '=' while parsing major version number",
+ );
+
+ let err = req_err("a.0.0");
+ assert_to_string(
+ err,
+ "unexpected character 'a' while parsing major version number",
+ );
+
+ let err = req_err("1.0.0-");
+ assert_to_string(err, "empty identifier segment in pre-release identifier");
+
+ let err = req_err(">=");
+ assert_to_string(
+ err,
+ "unexpected end of input while parsing major version number",
+ );
+}
+
+#[test]
+fn test_cargo3202() {
+ let ref r = req("0.*.*");
+ assert_to_string(r, "0.*");
+ assert_match_all(r, &["0.5.0"]);
+
+ let ref r = req("0.0.*");
+ assert_to_string(r, "0.0.*");
+}
+
+#[test]
+fn test_digit_after_wildcard() {
+ let err = req_err("*.1");
+ assert_to_string(err, "unexpected character after wildcard in version req");
+
+ let err = req_err("1.*.1");
+ assert_to_string(err, "unexpected character after wildcard in version req");
+
+ let err = req_err(">=1.*.1");
+ assert_to_string(err, "unexpected character after wildcard in version req");
+}
+
+#[test]
+fn test_eq_hash() {
+ fn calculate_hash(value: impl Hash) -> u64 {
+ let mut hasher = DefaultHasher::new();
+ value.hash(&mut hasher);
+ hasher.finish()
+ }
+
+ assert!(req("^1") == req("^1"));
+ assert!(calculate_hash(req("^1")) == calculate_hash(req("^1")));
+ assert!(req("^1") != req("^2"));
+}
+
+#[test]
+fn test_leading_digit_in_pre_and_build() {
+ for op in &["=", ">", ">=", "<", "<=", "~", "^"] {
+ // digit then alpha
+ req(&format!("{} 1.2.3-1a", op));
+ req(&format!("{} 1.2.3+1a", op));
+
+ // digit then alpha (leading zero)
+ req(&format!("{} 1.2.3-01a", op));
+ req(&format!("{} 1.2.3+01", op));
+
+ // multiple
+ req(&format!("{} 1.2.3-1+1", op));
+ req(&format!("{} 1.2.3-1-1+1-1-1", op));
+ req(&format!("{} 1.2.3-1a+1a", op));
+ req(&format!("{} 1.2.3-1a-1a+1a-1a-1a", op));
+ }
+}
+
+#[test]
+fn test_wildcard_and_another() {
+ let err = req_err("*, 0.20.0-any");
+ assert_to_string(
+ err,
+ "wildcard req (*) must be the only comparator in the version req",
+ );
+
+ let err = req_err("0.20.0-any, *");
+ assert_to_string(
+ err,
+ "wildcard req (*) must be the only comparator in the version req",
+ );
+
+ let err = req_err("0.20.0-any, *, 1.0");
+ assert_to_string(
+ err,
+ "wildcard req (*) must be the only comparator in the version req",
+ );
+}
diff --git a/tests/util/mod.rs b/tests/util/mod.rs
new file mode 100644
index 0000000..5cc142c
--- /dev/null
+++ b/tests/util/mod.rs
@@ -0,0 +1,39 @@
+#![allow(dead_code)]
+
+use semver::{BuildMetadata, Error, Prerelease, Version, VersionReq};
+use std::fmt::Display;
+
+#[cfg_attr(not(no_track_caller), track_caller)]
+pub(super) fn version(text: &str) -> Version {
+ Version::parse(text).unwrap()
+}
+
+#[cfg_attr(not(no_track_caller), track_caller)]
+pub(super) fn version_err(text: &str) -> Error {
+ Version::parse(text).unwrap_err()
+}
+
+#[cfg_attr(not(no_track_caller), track_caller)]
+pub(super) fn req(text: &str) -> VersionReq {
+ VersionReq::parse(text).unwrap()
+}
+
+#[cfg_attr(not(no_track_caller), track_caller)]
+pub(super) fn req_err(text: &str) -> Error {
+ VersionReq::parse(text).unwrap_err()
+}
+
+#[cfg_attr(not(no_track_caller), track_caller)]
+pub(super) fn prerelease(text: &str) -> Prerelease {
+ Prerelease::new(text).unwrap()
+}
+
+#[cfg_attr(not(no_track_caller), track_caller)]
+pub(super) fn build_metadata(text: &str) -> BuildMetadata {
+ BuildMetadata::new(text).unwrap()
+}
+
+#[cfg_attr(not(no_track_caller), track_caller)]
+pub(super) fn assert_to_string(value: impl Display, expected: &str) {
+ assert_eq!(value.to_string(), expected);
+}