Import semver 1.0.16 upstream upstream/1.0.16
authorDongHun Kwak <dh0128.kwak@samsung.com>
Tue, 28 Feb 2023 03:45:34 +0000 (12:45 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Tue, 28 Feb 2023 03:45:34 +0000 (12:45 +0900)
27 files changed:
.cargo_vcs_info.json [new file with mode: 0644]
.clippy.toml [new file with mode: 0644]
.github/FUNDING.yml [new file with mode: 0644]
.github/workflows/ci.yml [new file with mode: 0644]
.gitignore [new file with mode: 0644]
Cargo.toml [new file with mode: 0644]
Cargo.toml.orig [new file with mode: 0644]
LICENSE-APACHE [new file with mode: 0644]
LICENSE-MIT [new file with mode: 0644]
README.md [new file with mode: 0644]
benches/parse.rs [new file with mode: 0644]
build.rs [new file with mode: 0644]
src/backport.rs [new file with mode: 0644]
src/display.rs [new file with mode: 0644]
src/error.rs [new file with mode: 0644]
src/eval.rs [new file with mode: 0644]
src/identifier.rs [new file with mode: 0644]
src/impls.rs [new file with mode: 0644]
src/lib.rs [new file with mode: 0644]
src/parse.rs [new file with mode: 0644]
src/serde.rs [new file with mode: 0644]
tests/node/mod.rs [new file with mode: 0644]
tests/test_autotrait.rs [new file with mode: 0644]
tests/test_identifier.rs [new file with mode: 0644]
tests/test_version.rs [new file with mode: 0644]
tests/test_version_req.rs [new file with mode: 0644]
tests/util/mod.rs [new file with mode: 0644]

diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644 (file)
index 0000000..54870e9
--- /dev/null
@@ -0,0 +1,6 @@
+{
+  "git": {
+    "sha1": "dda4b834a9c896d7e1c8d17d5e326386bb346b50"
+  },
+  "path_in_vcs": ""
+}
\ No newline at end of file
diff --git a/.clippy.toml b/.clippy.toml
new file mode 100644 (file)
index 0000000..3d30690
--- /dev/null
@@ -0,0 +1 @@
+msrv = "1.31.0"
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644 (file)
index 0000000..7507077
--- /dev/null
@@ -0,0 +1 @@
+github: dtolnay
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644 (file)
index 0000000..0bfe3a7
--- /dev/null
@@ -0,0 +1,92 @@
+name: CI
+
+on:
+  push:
+  pull_request:
+  schedule: [cron: "40 1 * * *"]
+
+permissions:
+  contents: read
+
+env:
+  RUSTFLAGS: -Dwarnings
+
+jobs:
+  test:
+    name: Rust ${{matrix.rust}}
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        rust: [nightly, beta, stable, 1.52.0, 1.46.0, 1.40.0, 1.39.0, 1.36.0, 1.33.0, 1.32.0, 1.31.0]
+    timeout-minutes: 45
+    steps:
+      - uses: actions/checkout@v3
+      - uses: dtolnay/rust-toolchain@master
+        with:
+          toolchain: ${{matrix.rust}}
+      - run: cargo test
+      - run: cargo check --no-default-features
+      - run: cargo check --features serde
+      - run: cargo check --no-default-features --features serde
+
+  node:
+    name: Node
+    runs-on: ubuntu-latest
+    timeout-minutes: 45
+    steps:
+      - uses: actions/checkout@v3
+      - uses: dtolnay/rust-toolchain@stable
+      - run: npm install semver
+      - run: cargo test
+        env:
+          RUSTFLAGS: --cfg test_node_semver ${{env.RUSTFLAGS}}
+
+  clippy:
+    name: Clippy
+    runs-on: ubuntu-latest
+    if: github.event_name != 'pull_request'
+    timeout-minutes: 45
+    steps:
+      - uses: actions/checkout@v3
+      - uses: dtolnay/rust-toolchain@clippy
+      - run: cargo clippy --tests --benches -- -Dclippy::all -Dclippy::pedantic
+
+  miri:
+    name: Miri
+    runs-on: ubuntu-latest
+    env:
+      MIRIFLAGS: -Zmiri-strict-provenance
+    timeout-minutes: 45
+    steps:
+      - uses: actions/checkout@v3
+      - uses: dtolnay/rust-toolchain@miri
+      - name: Run cargo miri test (64-bit little endian)
+        run: cargo miri test --target x86_64-unknown-linux-gnu
+      - name: Run cargo miri test (64-bit big endian)
+        run: cargo miri test --target powerpc64-unknown-linux-gnu
+      - name: Run cargo miri test (32-bit little endian)
+        run: cargo miri test --target i686-unknown-linux-gnu
+      - name: Run cargo miri test (32-bit big endian)
+        run: cargo miri test --target mips-unknown-linux-gnu
+
+  fuzz:
+    name: Fuzz
+    runs-on: ubuntu-latest
+    timeout-minutes: 45
+    steps:
+      - uses: actions/checkout@v3
+      - uses: dtolnay/rust-toolchain@nightly
+      - uses: dtolnay/install@cargo-fuzz
+      - run: cargo fuzz build -O
+
+  outdated:
+    name: Outdated
+    runs-on: ubuntu-latest
+    if: github.event_name != 'pull_request'
+    timeout-minutes: 45
+    steps:
+      - uses: actions/checkout@v3
+      - uses: dtolnay/install@cargo-outdated
+      - run: cargo outdated --workspace --exit-code 1
+      - run: cargo outdated --manifest-path fuzz/Cargo.toml --exit-code 1
diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..744c1ef
--- /dev/null
@@ -0,0 +1,5 @@
+/node_modules
+/package-lock.json
+/package.json
+/target
+Cargo.lock
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644 (file)
index 0000000..e0bfea2
--- /dev/null
@@ -0,0 +1,46 @@
+# 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.16"
+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"
+keywords = ["cargo"]
+categories = [
+    "data-structures",
+    "no-std",
+]
+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",
+]
+
+[lib]
+doc-scrape-examples = false
+
+[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 (file)
index 0000000..307c361
--- /dev/null
@@ -0,0 +1,26 @@
+[package]
+name = "semver"
+version = "1.0.16"
+authors = ["David Tolnay <dtolnay@gmail.com>"]
+categories = ["data-structures", "no-std"]
+description = "Parser and evaluator for Cargo's flavor of Semantic Versioning"
+documentation = "https://docs.rs/semver"
+edition = "2018"
+keywords = ["cargo"]
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/dtolnay/semver"
+rust-version = "1.31"
+
+[features]
+default = ["std"]
+std = []
+
+[dependencies]
+serde = { version = "1.0", optional = true, default-features = false }
+
+[lib]
+doc-scrape-examples = false
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
+rustdoc-args = ["--cfg", "doc_cfg"]
diff --git a/LICENSE-APACHE b/LICENSE-APACHE
new file mode 100644 (file)
index 0000000..16fe87b
--- /dev/null
@@ -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 (file)
index 0000000..31aa793
--- /dev/null
@@ -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/README.md b/README.md
new file mode 100644 (file)
index 0000000..a9a1cb8
--- /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&logo=docs.rs" height="20">](https://docs.rs/semver)
+[<img alt="build status" src="https://img.shields.io/github/actions/workflow/status/dtolnay/semver/ci.yml?branch=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 (file)
index 0000000..d6aded7
--- /dev/null
@@ -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 (file)
index 0000000..81ad970
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,75 @@
+use std::env;
+use std::process::Command;
+use std::str;
+
+fn main() {
+    println!("cargo:rerun-if-changed=build.rs");
+
+    let compiler = match rustc_minor_version() {
+        Some(compiler) => compiler,
+        None => return,
+    };
+
+    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.strip_prefix
+        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 (file)
index 0000000..b5e1d02
--- /dev/null
@@ -0,0 +1,23 @@
+#[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
+        }
+    }
+}
+
+pub(crate) use crate::alloc::vec::Vec;
+
+#[cfg(no_alloc_crate)] // rustc <1.36
+pub(crate) mod alloc {
+    pub use std::alloc;
+    pub use std::vec;
+}
diff --git a/src/display.rs b/src/display.rs
new file mode 100644 (file)
index 0000000..3c2871b
--- /dev/null
@@ -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 (file)
index 0000000..a514e3f
--- /dev/null
@@ -0,0 +1,124 @@
+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 {}",
+                    QuotedChar(*ch),
+                    pos,
+                )
+            }
+            ErrorKind::UnexpectedCharAfter(pos, ch) => {
+                write!(
+                    formatter,
+                    "unexpected character {} after {}",
+                    QuotedChar(*ch),
+                    pos,
+                )
+            }
+            ErrorKind::ExpectedCommaFound(pos, ch) => {
+                write!(
+                    formatter,
+                    "expected comma after {}, found {}",
+                    pos,
+                    QuotedChar(*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(())
+    }
+}
+
+struct QuotedChar(char);
+
+impl Display for QuotedChar {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        // Standard library versions prior to https://github.com/rust-lang/rust/pull/95345
+        // print character 0 as '\u{0}'. We prefer '\0' to keep error messages
+        // the same across all supported Rust versions.
+        if self.0 == '\0' {
+            formatter.write_str("'\\0'")
+        } else {
+            write!(formatter, "{:?}", self.0)
+        }
+    }
+}
diff --git a/src/eval.rs b/src/eval.rs
new file mode 100644 (file)
index 0000000..e6e3894
--- /dev/null
@@ -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 (file)
index 0000000..0273ae6
--- /dev/null
@@ -0,0 +1,422 @@
+// 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, handle_alloc_error, Layout};
+use core::isize;
+use core::mem;
+use core::num::{NonZeroU64, NonZeroUsize};
+use core::ptr::{self, NonNull};
+use core::slice;
+use core::str;
+use core::usize;
+
+const PTR_BYTES: usize = mem::size_of::<NonNull<u8>>();
+
+// If pointers are already 8 bytes or bigger, then 0. If pointers are smaller
+// than 8 bytes, then Identifier will contain a byte array to raise its size up
+// to 8 bytes total.
+const TAIL_BYTES: usize = 8 * (PTR_BYTES < 8) as usize - PTR_BYTES * (PTR_BYTES < 8) as usize;
+
+#[repr(C, align(8))]
+pub(crate) struct Identifier {
+    head: NonNull<u8>,
+    tail: [u8; TAIL_BYTES],
+}
+
+impl Identifier {
+    pub(crate) const fn empty() -> Self {
+        // This is a separate constant because unsafe function calls are not
+        // allowed in a const fn body, only in a const, until later rustc than
+        // what we support.
+        const HEAD: NonNull<u8> = unsafe { NonNull::new_unchecked(!0 as *mut u8) };
+
+        // `mov rax, -1`
+        Identifier {
+            head: HEAD,
+            tail: [!0; TAIL_BYTES],
+        }
+    }
+
+    // SAFETY: string must be ASCII and not contain \0 bytes.
+    pub(crate) unsafe fn new_unchecked(string: &str) -> Self {
+        let len = string.len();
+        debug_assert!(len <= isize::MAX as usize);
+        match len as u64 {
+            0 => Self::empty(),
+            1..=8 => {
+                let mut bytes = [0u8; mem::size_of::<Identifier>()];
+                // 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: the head field is nonzero because the input string
+                // was at least 1 byte of ASCII and did not contain \0.
+                unsafe { mem::transmute::<[u8; mem::size_of::<Identifier>()], Identifier>(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;
+                // On 32-bit and 16-bit architecture, check for size overflowing
+                // isize::MAX. Making an allocation request bigger than this to
+                // the allocator is considered UB. All allocations (including
+                // static ones) are limited to isize::MAX so we're guaranteed
+                // len <= isize::MAX, and we know bytes_for_varint(len) <= 5
+                // because 128**5 > isize::MAX, which means the only problem
+                // that can arise is when isize::MAX - 5 <= len <= isize::MAX.
+                // This is pretty much guaranteed to be malicious input so we
+                // don't need to care about returning a good error message.
+                if mem::size_of::<usize>() < 8 {
+                    let max_alloc = usize::MAX / 2 - align;
+                    assert!(size <= max_alloc);
+                }
+                // SAFETY: align is not zero, align is a power of two, and
+                // rounding size up to align does not overflow isize::MAX.
+                let layout = unsafe { Layout::from_size_align_unchecked(size, align) };
+                // SAFETY: layout's size is nonzero.
+                let ptr = unsafe { alloc(layout) };
+                if ptr.is_null() {
+                    handle_alloc_error(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) };
+                Identifier {
+                    head: ptr_to_repr(ptr),
+                    tail: [0; TAIL_BYTES],
+                }
+            }
+            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!(),
+        }
+    }
+
+    pub(crate) fn is_empty(&self) -> bool {
+        // `cmp rdi, -1` -- basically: `repr as i64 == -1`
+        let empty = Self::empty();
+        let is_empty = self.head == empty.head && self.tail == empty.tail;
+        // The empty representation does nothing on Drop. We can't let this one
+        // drop normally because `impl Drop for Identifier` calls is_empty; that
+        // would be an infinite recursion.
+        mem::forget(empty);
+        is_empty
+    }
+
+    fn is_inline(&self) -> bool {
+        // `test rdi, rdi` -- basically: `repr as i64 >= 0`
+        self.head.as_ptr() as usize >> (PTR_BYTES * 8 - 1) == 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) }
+        } else {
+            // SAFETY: repr is in the heap allocated representation.
+            unsafe { ptr_as_str(&self.head) }
+        }
+    }
+}
+
+impl Clone for Identifier {
+    fn clone(&self) -> Self {
+        if self.is_empty_or_inline() {
+            Identifier {
+                head: self.head,
+                tail: self.tail,
+            }
+        } else {
+            let ptr = repr_to_ptr(self.head);
+            // 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 isize::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) };
+            if clone.is_null() {
+                handle_alloc_error(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) }
+            Identifier {
+                head: ptr_to_repr(clone),
+                tail: [0; TAIL_BYTES],
+            }
+        }
+    }
+}
+
+impl Drop for Identifier {
+    fn drop(&mut self) {
+        if self.is_empty_or_inline() {
+            return;
+        }
+        let ptr = repr_to_ptr_mut(self.head);
+        // 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.head == rhs.head && self.tail == rhs.tail
+        } else if rhs.is_empty_or_inline() {
+            false
+        } else {
+            // SAFETY: both reprs are in the heap allocated representation.
+            unsafe { ptr_as_str(&self.head) == ptr_as_str(&rhs.head) }
+        }
+    }
+}
+
+unsafe impl Send for Identifier {}
+unsafe impl Sync for Identifier {}
+
+// 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(original: *mut u8) -> NonNull<u8> {
+    // `mov eax, 1`
+    // `shld rax, rdi, 63`
+    let modified = (original as usize | 1).rotate_right(1);
+
+    // `original + (modified - original)`, but being mindful of provenance.
+    let diff = modified.wrapping_sub(original as usize);
+    let modified = original.wrapping_add(diff);
+
+    // SAFETY: the most significant bit of repr is known to be set, so the value
+    // is not zero.
+    unsafe { NonNull::new_unchecked(modified) }
+}
+
+// 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(modified: NonNull<u8>) -> *const u8 {
+    // `lea rax, [rdi + rdi]`
+    let modified = modified.as_ptr();
+    let original = (modified as usize) << 1;
+
+    // `modified + (original - modified)`, but being mindful of provenance.
+    let diff = original.wrapping_sub(modified as usize);
+    modified.wrapping_add(diff)
+}
+
+fn repr_to_ptr_mut(repr: NonNull<u8>) -> *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.
+//
+// SAFETY: the identifier must indeed be in the inline representation.
+unsafe fn inline_len(repr: &Identifier) -> NonZeroUsize {
+    // SAFETY: Identifier's layout is align(8) and at least size 8. We're doing
+    // an aligned read of the first 8 bytes from it. The bytes are not all zero
+    // because inline strings are at least 1 byte long and cannot contain \0.
+    let repr = unsafe { ptr::read(repr as *const Identifier as *const NonZeroU64) };
+
+    // 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: &Identifier) -> &str {
+    let ptr = repr as *const Identifier as *const u8;
+    let len = unsafe { 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: &NonNull<u8>) -> &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 (file)
index 0000000..c3b6c60
--- /dev/null
@@ -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 (file)
index 0000000..32ed96d
--- /dev/null
@@ -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&logo=docs.rs
+//!
+//! <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.16")]
+#![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 (file)
index 0000000..6a8f6ff
--- /dev/null
@@ -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.first() == Some(&b'=') {
+        (Op::Exact, &input[1..])
+    } else if bytes.first() == Some(&b'>') {
+        if bytes.get(1) == Some(&b'=') {
+            (Op::GreaterEq, &input[2..])
+        } else {
+            (Op::Greater, &input[1..])
+        }
+    } else if bytes.first() == Some(&b'<') {
+        if bytes.get(1) == Some(&b'=') {
+            (Op::LessEq, &input[2..])
+        } else {
+            (Op::Less, &input[1..])
+        }
+    } else if bytes.first() == Some(&b'~') {
+        (Op::Tilde, &input[1..])
+    } else if bytes.first() == 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 (file)
index 0000000..1fcc7d8
--- /dev/null
@@ -0,0 +1,109 @@
+use crate::{Comparator, 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 Serialize for Comparator {
+    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)
+    }
+}
+
+impl<'de> Deserialize<'de> for Comparator {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        struct ComparatorVisitor;
+
+        impl<'de> Visitor<'de> for ComparatorVisitor {
+            type Value = Comparator;
+
+            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+                formatter.write_str("semver comparator")
+            }
+
+            fn visit_str<E>(self, string: &str) -> Result<Self::Value, E>
+            where
+                E: Error,
+            {
+                string.parse().map_err(Error::custom)
+            }
+        }
+
+        deserializer.deserialize_str(ComparatorVisitor)
+    }
+}
diff --git a/tests/node/mod.rs b/tests/node/mod.rs
new file mode 100644 (file)
index 0000000..eb50673
--- /dev/null
@@ -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_autotrait.rs b/tests/test_autotrait.rs
new file mode 100644 (file)
index 0000000..af8534b
--- /dev/null
@@ -0,0 +1,12 @@
+fn assert_send_sync<T: Send + Sync>() {}
+
+#[test]
+fn test() {
+    assert_send_sync::<semver::BuildMetadata>();
+    assert_send_sync::<semver::Comparator>();
+    assert_send_sync::<semver::Error>();
+    assert_send_sync::<semver::Prerelease>();
+    assert_send_sync::<semver::Version>();
+    assert_send_sync::<semver::VersionReq>();
+    assert_send_sync::<semver::Op>();
+}
diff --git a/tests/test_identifier.rs b/tests/test_identifier.rs
new file mode 100644 (file)
index 0000000..dc888c9
--- /dev/null
@@ -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 (file)
index 0000000..93a528c
--- /dev/null
@@ -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 (file)
index 0000000..98a03ac
--- /dev/null
@@ -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 '\\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 (file)
index 0000000..5cc142c
--- /dev/null
@@ -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);
+}