semver-1.0.23/.cargo_vcs_info.json0000644000000001360000000000100124200ustar { "git": { "sha1": "69efd3cc770ead273a06ad1788477b3092996d29" }, "path_in_vcs": "" }semver-1.0.23/.github/FUNDING.yml000064400000000000000000000000201046102023000143550ustar 00000000000000github: dtolnay semver-1.0.23/.github/workflows/ci.yml000064400000000000000000000072621046102023000157320ustar 00000000000000name: CI on: push: pull_request: workflow_dispatch: schedule: [cron: "40 1 * * *"] permissions: contents: read env: RUSTFLAGS: -Dwarnings jobs: pre_ci: uses: dtolnay/.github/.github/workflows/pre_ci.yml@master test: name: Rust ${{matrix.rust}} needs: pre_ci if: needs.pre_ci.outputs.continue 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@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{matrix.rust}} - name: Enable type layout randomization run: echo RUSTFLAGS=${RUSTFLAGS}\ -Zrandomize-layout >> $GITHUB_ENV if: matrix.rust == 'nightly' - 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 needs: pre_ci if: needs.pre_ci.outputs.continue runs-on: ubuntu-latest timeout-minutes: 45 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - run: npm install semver - run: cargo test env: RUSTFLAGS: --cfg test_node_semver ${{env.RUSTFLAGS}} minimal: name: Minimal versions needs: pre_ci if: needs.pre_ci.outputs.continue runs-on: ubuntu-latest timeout-minutes: 45 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - run: cargo generate-lockfile -Z minimal-versions - run: cargo check --locked --features serde doc: name: Documentation needs: pre_ci if: needs.pre_ci.outputs.continue runs-on: ubuntu-latest timeout-minutes: 45 env: RUSTDOCFLAGS: -Dwarnings steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - uses: dtolnay/install@cargo-docs-rs - run: cargo docs-rs clippy: name: Clippy runs-on: ubuntu-latest if: github.event_name != 'pull_request' timeout-minutes: 45 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@clippy - run: cargo clippy --tests --benches -- -Dclippy::all -Dclippy::pedantic miri: name: Miri needs: pre_ci if: needs.pre_ci.outputs.continue runs-on: ubuntu-latest env: MIRIFLAGS: -Zmiri-strict-provenance timeout-minutes: 45 steps: - uses: actions/checkout@v4 - 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 needs: pre_ci if: needs.pre_ci.outputs.continue runs-on: ubuntu-latest timeout-minutes: 45 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - uses: dtolnay/install@cargo-fuzz - run: cargo fuzz check outdated: name: Outdated runs-on: ubuntu-latest if: github.event_name != 'pull_request' timeout-minutes: 45 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: dtolnay/install@cargo-outdated - run: cargo outdated --workspace --exit-code 1 - run: cargo outdated --manifest-path fuzz/Cargo.toml --exit-code 1 semver-1.0.23/.gitignore000064400000000000000000000001021046102023000131710ustar 00000000000000/node_modules /package-lock.json /package.json /target Cargo.lock semver-1.0.23/Cargo.toml0000644000000031660000000000100104240ustar # 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.23" authors = ["David Tolnay "] build = "build.rs" autobins = false autoexamples = false autotests = false autobenches = false 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] rustdoc-args = [ "--cfg", "doc_cfg", "--generate-link-to-definition", ] targets = ["x86_64-unknown-linux-gnu"] [lib] name = "semver" path = "src/lib.rs" doc-scrape-examples = false [[test]] name = "test_version_req" path = "tests/test_version_req.rs" [[test]] name = "test_identifier" path = "tests/test_identifier.rs" [[test]] name = "test_autotrait" path = "tests/test_autotrait.rs" [[test]] name = "test_version" path = "tests/test_version.rs" [[bench]] name = "parse" path = "benches/parse.rs" [dependencies.serde] version = "1.0.194" optional = true default-features = false [features] default = ["std"] std = [] semver-1.0.23/Cargo.toml.orig000064400000000000000000000012671046102023000141050ustar 00000000000000[package] name = "semver" version = "1.0.23" authors = ["David Tolnay "] 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.194", 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", "--generate-link-to-definition"] semver-1.0.23/LICENSE-APACHE000064400000000000000000000227731046102023000131470ustar 00000000000000 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 semver-1.0.23/LICENSE-MIT000064400000000000000000000017771046102023000126600ustar 00000000000000Permission 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. semver-1.0.23/README.md000064400000000000000000000056351046102023000125000ustar 00000000000000semver ====== [github](https://github.com/dtolnay/semver) [crates.io](https://crates.io/crates/semver) [docs.rs](https://docs.rs/semver) [build status](https://github.com/dtolnay/semver/actions?query=branch%3Amaster) A parser and evaluator for Cargo's flavor of Semantic Versioning. Semantic Versioning (see ) 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+*
## 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)); } ```
## Scope of this crate Besides Cargo, several other package ecosystems and package managers for other languages also use SemVer: 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
#### License Licensed under either of Apache License, Version 2.0 or MIT license at your option.
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. semver-1.0.23/benches/parse.rs000064400000000000000000000010641046102023000143000ustar 00000000000000#![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::().unwrap()); } #[bench] fn parse_version(b: &mut Bencher) { let text = "1.0.2021-beta+exp.sha.5114f85"; b.iter(|| black_box(text).parse::().unwrap()); } #[bench] fn parse_version_req(b: &mut Bencher) { let text = ">=1.2.3, <2.0.0"; b.iter(|| black_box(text).parse::().unwrap()); } semver-1.0.23/build.rs000064400000000000000000000063451046102023000126650ustar 00000000000000use 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 >= 80 { println!("cargo:rustc-check-cfg=cfg(doc_cfg)"); println!("cargo:rustc-check-cfg=cfg(no_alloc_crate)"); println!("cargo:rustc-check-cfg=cfg(no_const_vec_new)"); println!("cargo:rustc-check-cfg=cfg(no_exhaustive_int_match)"); println!("cargo:rustc-check-cfg=cfg(no_non_exhaustive)"); println!("cargo:rustc-check-cfg=cfg(no_nonzero_bitscan)"); println!("cargo:rustc-check-cfg=cfg(no_str_strip_prefix)"); println!("cargo:rustc-check-cfg=cfg(no_track_caller)"); println!("cargo:rustc-check-cfg=cfg(no_unsafe_op_in_unsafe_fn_lint)"); println!("cargo:rustc-check-cfg=cfg(test_node_semver)"); } 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 { 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() } semver-1.0.23/src/backport.rs000064400000000000000000000010141046102023000141460ustar 00000000000000#[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; } semver-1.0.23/src/display.rs000064400000000000000000000111751046102023000140170ustar 00000000000000use 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) } } semver-1.0.23/src/error.rs000064400000000000000000000077741046102023000135150ustar 00000000000000use crate::parse::Error; use core::fmt::{self, Debug, Display}; pub(crate) enum ErrorKind { Empty, 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::Empty => formatter.write_str("empty string, expected a semver version"), 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) } } } semver-1.0.23/src/eval.rs000064400000000000000000000103461046102023000133000ustar 00000000000000use 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() } semver-1.0.23/src/identifier.rs000064400000000000000000000443031046102023000144730ustar 00000000000000// 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::() == size_of::>(). 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::>(); // 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, 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 = 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::()]; // 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>(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::() < 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 isize::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 { // `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) -> *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) -> *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) -> &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::() * 8; let len_bits = usize_bits - len.leading_zeros() as usize; (len_bits + 6) / 7 } semver-1.0.23/src/impls.rs000064400000000000000000000111321046102023000134670ustar 00000000000000use 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(&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 { Some(self.cmp(rhs)) } } impl PartialOrd for BuildMetadata { fn partial_cmp(&self, rhs: &Self) -> Option { Some(self.cmp(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 for VersionReq { fn from_iter(iter: I) -> Self where I: IntoIterator, { let comparators = Vec::from_iter(iter); VersionReq { comparators } } } semver-1.0.23/src/lib.rs000064400000000000000000000524711046102023000131240ustar 00000000000000//! [![github]](https://github.com/dtolnay/semver) [![crates-io]](https://crates.io/crates/semver) [![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 //! //!
//! //! A parser and evaluator for Cargo's flavor of Semantic Versioning. //! //! Semantic Versioning (see ) is a guideline for how //! version numbers are assigned and incremented. It is widely followed within //! the Cargo/crates.io ecosystem for Rust. //! //!
//! //! # 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)); //! } //! ``` //! //!

//! //! # Scope of this crate //! //! Besides Cargo, several other package ecosystems and package managers for //! other languages also use SemVer: 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.23")] #![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::incompatible_msrv, clippy::items_after_statements, clippy::manual_map, clippy::match_bool, clippy::missing_errors_doc, clippy::must_use_candidate, clippy::needless_doctest_main, 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::identifier::Identifier; use core::cmp::Ordering; use core::str::FromStr; #[allow(unused_imports)] use crate::backport::*; pub use crate::parse::Error; /// **SemVer version** as defined by . /// /// # 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" < "1.5.0" as ASCIIbetically compared strings and 1.19 < 1.5 /// as real numbers. /// /// - When major, minor, and patch are equal, a pre-release version is /// considered less than the ordinary release: 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: `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: `1.0.0-pre12` is less than `1.0.0-pre8`. /// /// - Any numeric identifier is always less than any non-numeric /// identifier: `1.0.0-pre.1` is less than `1.0.0-pre.x`. /// /// Example: `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` #[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, } /// 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, /// Patch is only allowed if minor is Some. pub patch: Option, /// Non-empty pre-release is only allowed if patch is Some. pub pre: Prerelease, } /// SemVer comparison operator: `=`, `>`, `>=`, `<`, `<=`, `~`, `^`, `*`. /// /// # Op::Exact /// -  **`=I.J.K`** — exactly the version I.J.K /// -  **`=I.J`** — equivalent to `>=I.J.0, =I.0.0, <(I+1).0.0` /// /// # Op::Greater /// -  **`>I.J.K`** /// -  **`>I.J`** — equivalent to `>=I.(J+1).0` /// -  **`>I`** — equivalent to `>=(I+1).0.0` /// /// # Op::GreaterEq /// -  **`>=I.J.K`** /// -  **`>=I.J`** — equivalent to `>=I.J.0` /// -  **`>=I`** — equivalent to `>=I.0.0` /// /// # Op::Less /// -  **`=I.J.K, 0) — equivalent to `>=I.J.K, <(I+1).0.0` /// -  **`^0.J.K`** (for J\>0) — equivalent to `>=0.J.K, <0.(J+1).0` /// -  **`^0.0.K`** — equivalent to `=0.0.K` /// -  **`^I.J`** (for I\>0 or J\>0) — equivalent to `^I.J.0` /// -  **`^0.0`** — equivalent to `=0.0` /// -  **`^I`** — equivalent to `=I` /// /// # Op::Wildcard /// -  **`I.J.*`** — equivalent to `=I.J` /// -  **`I.*`** or **`I.*.*`** — 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]** 0.7.0-alpha.1 — the most common style /// for numbering pre-releases. /// /// - **[pest]** 1.0.0-beta.8, 1.0.0-rc.0 /// — this crate makes a distinction between betas and release /// candidates. /// /// - **[sassers]** 0.11.0-shitshow — ???. /// /// - **[atomic-utils]** 0.0.0-reserved — 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` < /// `alpha.11` as intended, but `alpha11` < `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: `alpha` < `alpha.85` < `alpha.90` < `alpha.200` < `alpha.0a` < `alpha.1a0` < `alpha.a` < `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]** 0.12.20+1.1.0 — for this /// crate, the build metadata indicates the version of the C libgit2 library /// that the Rust crate is built against. /// /// - **[mashup]** 0.1.13+deprecated — 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]** 2.0.4+20210327 — 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]** 0.0.6+c7fcc0e — 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: `demo` < `demo.85` < `demo.90` < `demo.090` < `demo.200` < `demo.1a0` < `demo.a` < `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` — 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` — a numeric component has a leading zero. /// /// - `1.0.unknown` — unexpected character in one of the components. /// /// - `1.0.0-` or `1.0.0+` — the pre-release or build metadata are /// indicated present but empty. /// /// - `1.0.0-alpha_123` — pre-release or build metadata have something /// outside the allowed characters, which are `0-9`, `A-Z`, `a-z`, `-`, /// and `.` (dot). /// /// - `23456789999999999999.0.0` — overflow of a u64. pub fn parse(text: &str) -> Result { Version::from_str(text) } /// Compare the major, minor, patch, and pre-release value of two versions, /// disregarding build metadata. Versions that differ only in build metadata /// are considered equal. This comparison is what the SemVer spec refers to /// as "precedence". /// /// # Example /// /// ``` /// use semver::Version; /// /// let mut versions = [ /// "1.20.0+c144a98".parse::().unwrap(), /// "1.20.0".parse().unwrap(), /// "1.0.0".parse().unwrap(), /// "1.0.0-alpha".parse().unwrap(), /// "1.20.0+bc17664".parse().unwrap(), /// ]; /// /// // This is a stable sort, so it preserves the relative order of equal /// // elements. The three 1.20.0 versions differ only in build metadata so /// // they are not reordered relative to one another. /// versions.sort_by(Version::cmp_precedence); /// assert_eq!(versions, [ /// "1.0.0-alpha".parse().unwrap(), /// "1.0.0".parse().unwrap(), /// "1.20.0+c144a98".parse().unwrap(), /// "1.20.0".parse().unwrap(), /// "1.20.0+bc17664".parse().unwrap(), /// ]); /// /// // Totally order the versions, including comparing the build metadata. /// versions.sort(); /// assert_eq!(versions, [ /// "1.0.0-alpha".parse().unwrap(), /// "1.0.0".parse().unwrap(), /// "1.20.0".parse().unwrap(), /// "1.20.0+bc17664".parse().unwrap(), /// "1.20.0+c144a98".parse().unwrap(), /// ]); /// ``` pub fn cmp_precedence(&self, other: &Self) -> Ordering { Ord::cmp( &(self.major, self.minor, self.patch, &self.pre), &(other.major, other.minor, other.patch, &other.pre), ) } } 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` — unexpected characters in the partial version. /// /// - `@1.0.0` — unrecognized comparison operator. /// /// - `^1.0.0, ` — unexpected end of input. /// /// - `>=1.0 <2.0` — missing comma between comparators. /// /// - `*.*` — unsupported wildcard syntax. pub fn parse(text: &str) -> Result { 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 { 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 { 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 { BuildMetadata::from_str(text) } pub fn as_str(&self) -> &str { self.identifier.as_str() } pub fn is_empty(&self) -> bool { self.identifier.is_empty() } } semver-1.0.23/src/parse.rs000064400000000000000000000301541046102023000134620ustar 00000000000000use 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 { if text.is_empty() { return Err(Error::new(ErrorKind::Empty)); } 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 { 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 { 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 { 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 { 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, depth: usize) -> Result { 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) } semver-1.0.23/src/serde.rs000064400000000000000000000054201046102023000134500ustar 00000000000000use 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(&self, serializer: S) -> Result where S: Serializer, { serializer.collect_str(self) } } impl Serialize for VersionReq { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.collect_str(self) } } impl Serialize for Comparator { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.collect_str(self) } } impl<'de> Deserialize<'de> for Version { fn deserialize(deserializer: D) -> Result 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(self, string: &str) -> Result where E: Error, { string.parse().map_err(Error::custom) } } deserializer.deserialize_str(VersionVisitor) } } impl<'de> Deserialize<'de> for VersionReq { fn deserialize(deserializer: D) -> Result 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(self, string: &str) -> Result where E: Error, { string.parse().map_err(Error::custom) } } deserializer.deserialize_str(VersionReqVisitor) } } impl<'de> Deserialize<'de> for Comparator { fn deserialize(deserializer: D) -> Result 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(self, string: &str) -> Result where E: Error, { string.parse().map_err(Error::custom) } } deserializer.deserialize_str(ComparatorVisitor) } } semver-1.0.23/tests/node/mod.rs000064400000000000000000000023221046102023000144230ustar 00000000000000#![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)) } semver-1.0.23/tests/test_autotrait.rs000064400000000000000000000006461046102023000160010ustar 00000000000000#![allow(clippy::extra_unused_type_parameters)] fn assert_send_sync() {} #[test] fn test() { assert_send_sync::(); assert_send_sync::(); assert_send_sync::(); assert_send_sync::(); assert_send_sync::(); assert_send_sync::(); assert_send_sync::(); } semver-1.0.23/tests/test_identifier.rs000064400000000000000000000026241046102023000161050ustar 00000000000000#![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")); } #[test] fn test_prerelease() { let err = prerelease_err("1.b\0"); assert_to_string(err, "unexpected character in pre-release identifier"); } semver-1.0.23/tests/test_version.rs000064400000000000000000000163221046102023000154500ustar 00000000000000#![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, "empty string, expected a semver version"); 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 err = version_err("1.2.3++"); assert_to_string(err, "empty identifier segment in build metadata"); let err = version_err("07"); assert_to_string(err, "invalid leading zero in major version number"); let err = version_err("111111111111111111111.0.0"); assert_to_string(err, "value of major version number exceeds u64::MAX"); let err = version_err("8\0"); assert_to_string(err, "unexpected character '\\0' after major version number"); 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)); } semver-1.0.23/tests/test_version_req.rs000064400000000000000000000327771046102023000163330ustar 00000000000000#![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 '-'"); let err = req_err(">1, >2, >3, >4, >5, >6, >7, >8, >9, >10, >11, >12, >13, >14, >15, >16, >17, >18, >19, >20, >21, >22, >23, >24, >25, >26, >27, >28, >29, >30, >31, >32, >33"); assert_to_string(err, "excessive number of version comparators"); } #[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() { 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_comparator_parse() { let parsed = comparator("1.2.3-alpha"); assert_to_string(parsed, "^1.2.3-alpha"); let parsed = comparator("2.X"); assert_to_string(parsed, "2.*"); let parsed = comparator("2"); assert_to_string(parsed, "^2"); let parsed = comparator("2.x.x"); assert_to_string(parsed, "2.*"); let err = comparator_err("1.2.3-01"); assert_to_string(err, "invalid leading zero in pre-release identifier"); let err = comparator_err("1.2.3+4."); assert_to_string(err, "empty identifier segment in build metadata"); let err = comparator_err(">"); assert_to_string( err, "unexpected end of input while parsing major version number", ); let err = comparator_err("1."); assert_to_string( err, "unexpected end of input while parsing minor version number", ); let err = comparator_err("1.*."); assert_to_string(err, "unexpected character after wildcard in version req"); let err = comparator_err("1.2.3+4ÿ"); assert_to_string(err, "unexpected character 'ÿ' after build metadata"); } #[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", ); } semver-1.0.23/tests/util/mod.rs000064400000000000000000000030141046102023000144520ustar 00000000000000#![allow(dead_code)] use semver::{BuildMetadata, Comparator, 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 comparator(text: &str) -> Comparator { Comparator::parse(text).unwrap() } #[cfg_attr(not(no_track_caller), track_caller)] pub(super) fn comparator_err(text: &str) -> Error { Comparator::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 prerelease_err(text: &str) -> Error { Prerelease::new(text).unwrap_err() } #[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); }