mirror of
https://github.com/typst/typst
synced 2025-05-19 03:25:27 +08:00
Allow "imprecise" bounds for the compiler version (#4394)
This commit is contained in:
parent
ddce645ef0
commit
3d3489fbae
@ -40,7 +40,7 @@ pub struct PackageInfo {
|
|||||||
/// The path of the entrypoint into the package.
|
/// The path of the entrypoint into the package.
|
||||||
pub entrypoint: EcoString,
|
pub entrypoint: EcoString,
|
||||||
/// The minimum required compiler version for the package.
|
/// The minimum required compiler version for the package.
|
||||||
pub compiler: Option<PackageVersion>,
|
pub compiler: Option<VersionBound>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PackageManifest {
|
impl PackageManifest {
|
||||||
@ -62,7 +62,7 @@ impl PackageManifest {
|
|||||||
|
|
||||||
if let Some(required) = self.package.compiler {
|
if let Some(required) = self.package.compiler {
|
||||||
let current = PackageVersion::compiler();
|
let current = PackageVersion::compiler();
|
||||||
if current < required {
|
if !current.matches_ge(&required) {
|
||||||
return Err(eco_format!(
|
return Err(eco_format!(
|
||||||
"package requires typst {required} or newer \
|
"package requires typst {required} or newer \
|
||||||
(current version is {current})"
|
(current version is {current})"
|
||||||
@ -214,6 +214,62 @@ impl PackageVersion {
|
|||||||
patch: env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(),
|
patch: env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Performs an `==` match with the given version bound. Version elements
|
||||||
|
/// missing in the bound are ignored.
|
||||||
|
pub fn matches_eq(&self, bound: &VersionBound) -> bool {
|
||||||
|
self.major == bound.major
|
||||||
|
&& bound.minor.map_or(true, |minor| self.minor == minor)
|
||||||
|
&& bound.patch.map_or(true, |patch| self.patch == patch)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs a `>` match with the given version bound. The match only
|
||||||
|
/// succeeds if some version element in the bound is actually greater than
|
||||||
|
/// that of the version.
|
||||||
|
pub fn matches_gt(&self, bound: &VersionBound) -> bool {
|
||||||
|
if self.major != bound.major {
|
||||||
|
return self.major > bound.major;
|
||||||
|
}
|
||||||
|
let Some(minor) = bound.minor else { return false };
|
||||||
|
if self.minor != minor {
|
||||||
|
return self.minor > minor;
|
||||||
|
}
|
||||||
|
let Some(patch) = bound.patch else { return false };
|
||||||
|
if self.patch != patch {
|
||||||
|
return self.patch > patch;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs a `<` match with the given version bound. The match only
|
||||||
|
/// succeeds if some version element in the bound is actually less than that
|
||||||
|
/// of the version.
|
||||||
|
pub fn matches_lt(&self, bound: &VersionBound) -> bool {
|
||||||
|
if self.major != bound.major {
|
||||||
|
return self.major < bound.major;
|
||||||
|
}
|
||||||
|
let Some(minor) = bound.minor else { return false };
|
||||||
|
if self.minor != minor {
|
||||||
|
return self.minor < minor;
|
||||||
|
}
|
||||||
|
let Some(patch) = bound.patch else { return false };
|
||||||
|
if self.patch != patch {
|
||||||
|
return self.patch < patch;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs a `>=` match with the given versions. The match succeeds when
|
||||||
|
/// either a `==` or `>` match does.
|
||||||
|
pub fn matches_ge(&self, bound: &VersionBound) -> bool {
|
||||||
|
self.matches_eq(bound) || self.matches_gt(bound)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs a `<=` match with the given versions. The match succeeds when
|
||||||
|
/// either a `==` or `<` match does.
|
||||||
|
pub fn matches_le(&self, bound: &VersionBound) -> bool {
|
||||||
|
self.matches_eq(bound) || self.matches_lt(bound)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for PackageVersion {
|
impl FromStr for PackageVersion {
|
||||||
@ -265,3 +321,97 @@ impl<'de> Deserialize<'de> for PackageVersion {
|
|||||||
string.parse().map_err(serde::de::Error::custom)
|
string.parse().map_err(serde::de::Error::custom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A version bound for compatibility specification.
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
|
pub struct VersionBound {
|
||||||
|
/// The bounds's major version.
|
||||||
|
pub major: u32,
|
||||||
|
/// The bounds's minor version.
|
||||||
|
pub minor: Option<u32>,
|
||||||
|
/// The bounds's patch version. Can only be present if minor is too.
|
||||||
|
pub patch: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for VersionBound {
|
||||||
|
type Err = EcoString;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let mut parts = s.split('.');
|
||||||
|
let mut next = |kind| {
|
||||||
|
if let Some(part) = parts.next() {
|
||||||
|
part.parse::<u32>().map(Some).map_err(|_| {
|
||||||
|
eco_format!("`{part}` is not a valid {kind} version bound")
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let major = next("major")?
|
||||||
|
.ok_or_else(|| eco_format!("version bound is missing major version"))?;
|
||||||
|
let minor = next("minor")?;
|
||||||
|
let patch = next("patch")?;
|
||||||
|
if let Some(rest) = parts.next() {
|
||||||
|
Err(eco_format!("version bound has unexpected fourth component: `{rest}`"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self { major, minor, patch })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for VersionBound {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
Display::fmt(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for VersionBound {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.major)?;
|
||||||
|
if let Some(minor) = self.minor {
|
||||||
|
write!(f, ".{minor}")?;
|
||||||
|
}
|
||||||
|
if let Some(patch) = self.patch {
|
||||||
|
write!(f, ".{patch}")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for VersionBound {
|
||||||
|
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
|
||||||
|
s.collect_str(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for VersionBound {
|
||||||
|
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
|
||||||
|
let string = EcoString::deserialize(d)?;
|
||||||
|
string.parse().map_err(serde::de::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn version_version_match() {
|
||||||
|
let v1_1_1 = PackageVersion::from_str("1.1.1").unwrap();
|
||||||
|
|
||||||
|
assert!(v1_1_1.matches_eq(&VersionBound::from_str("1").unwrap()));
|
||||||
|
assert!(v1_1_1.matches_eq(&VersionBound::from_str("1.1").unwrap()));
|
||||||
|
assert!(!v1_1_1.matches_eq(&VersionBound::from_str("1.2").unwrap()));
|
||||||
|
|
||||||
|
assert!(!v1_1_1.matches_gt(&VersionBound::from_str("1").unwrap()));
|
||||||
|
assert!(v1_1_1.matches_gt(&VersionBound::from_str("1.0").unwrap()));
|
||||||
|
assert!(!v1_1_1.matches_gt(&VersionBound::from_str("1.1").unwrap()));
|
||||||
|
|
||||||
|
assert!(!v1_1_1.matches_lt(&VersionBound::from_str("1").unwrap()));
|
||||||
|
assert!(!v1_1_1.matches_lt(&VersionBound::from_str("1.1").unwrap()));
|
||||||
|
assert!(v1_1_1.matches_lt(&VersionBound::from_str("1.2").unwrap()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user