Skip to content

Commit

Permalink
refactor(add): Pull out rust-version parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
epage committed Aug 24, 2023
1 parent 0c51462 commit b286246
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 10 deletions.
14 changes: 5 additions & 9 deletions src/cargo/ops/cargo_add/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ use crate::util::toml_mut::dependency::Source;
use crate::util::toml_mut::dependency::WorkspaceSource;
use crate::util::toml_mut::manifest::DepTable;
use crate::util::toml_mut::manifest::LocalManifest;
use crate::util::PartialVersion;
use crate::CargoResult;
use crate::Config;
use crate_spec::CrateSpec;
Expand Down Expand Up @@ -568,15 +569,10 @@ fn get_latest_dependency(

if config.cli_unstable().msrv_policy && honor_rust_version {
fn parse_msrv(rust_version: impl AsRef<str>) -> (u64, u64, u64) {
// HACK: `rust-version` is a subset of the `VersionReq` syntax that only ever
// has one comparator with a required minor and optional patch, and uses no
// other features. If in the future this syntax is expanded, this code will need
// to be updated.
let version_req = semver::VersionReq::parse(rust_version.as_ref()).unwrap();
assert!(version_req.comparators.len() == 1);
let comp = &version_req.comparators[0];
assert_eq!(comp.op, semver::Op::Caret);
assert_eq!(comp.pre, semver::Prerelease::EMPTY);
let comp = rust_version
.as_ref()
.parse::<PartialVersion>()
.expect("validated on parsing of manifest");
(comp.major, comp.minor.unwrap_or(0), comp.patch.unwrap_or(0))
}

Expand Down
2 changes: 1 addition & 1 deletion src/cargo/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub use self::progress::{Progress, ProgressStyle};
pub use self::queue::Queue;
pub use self::restricted_names::validate_package_name;
pub use self::rustc::Rustc;
pub use self::semver_ext::{OptVersionReq, VersionExt, VersionReqExt};
pub use self::semver_ext::{OptVersionReq, PartialVersion, VersionExt, VersionReqExt};
pub use self::to_semver::ToSemver;
pub use self::vcs::{existing_vcs_repo, FossilRepo, GitRepo, HgRepo, PijulRepo};
pub use self::workspace::{
Expand Down
54 changes: 54 additions & 0 deletions src/cargo/util/semver_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,60 @@ impl From<VersionReq> for OptVersionReq {
}
}

#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct PartialVersion {
pub major: u64,
pub minor: Option<u64>,
pub patch: Option<u64>,
}

impl std::str::FromStr for PartialVersion {
type Err = anyhow::Error;

fn from_str(value: &str) -> Result<Self, Self::Err> {
// HACK: `PartialVersion` is a subset of the `VersionReq` syntax that only ever
// has one comparator with a required minor and optional patch, and uses no
// other features.
let version_req = match semver::VersionReq::parse(value) {
// Exclude semver operators like `^` and pre-release identifiers
Ok(req) if value.chars().all(|c| c.is_ascii_digit() || c == '.') => req,
_ => anyhow::bail!("`rust-version` must be a value like \"1.32\""),
};
assert_eq!(
version_req.comparators.len(),
1,
"guarenteed by character check"
);
let comp = &version_req.comparators[0];
assert_eq!(comp.op, semver::Op::Caret, "guarenteed by character check");
assert_eq!(
comp.pre,
semver::Prerelease::EMPTY,
"guarenteed by character check"
);
Ok(PartialVersion {
major: comp.major,
minor: comp.minor,
patch: comp.patch,
})
}
}

impl Display for PartialVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.major.fmt(f)?;
if let Some(minor) = self.minor {
'.'.fmt(f)?;
minor.fmt(f)?;
}
if let Some(patch) = self.patch {
'.'.fmt(f)?;
patch.fmt(f)?;
}
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down

0 comments on commit b286246

Please sign in to comment.