use crate::error::{CtxError, Result};
use semver::{Version, VersionReq};
fn has_operator(require: &str) -> bool {
require
.chars()
.any(|c| matches!(c, '^' | '~' | '>' | '<' | '=' | '*' | ',') || c.is_whitespace())
}
fn pad_bare_version(require: &str) -> String {
let (core, suffix) = match require.find(['-', '+']) {
Some(idx) => require.split_at(idx),
None => (require, ""),
};
let dots = core.matches('.').count();
match dots {
0 => format!("{core}.0.0{suffix}"),
1 => format!("{core}.0{suffix}"),
_ => require.to_string(),
}
}
pub fn satisfies(current: &Version, require: &str) -> Result<bool> {
let require = require.trim();
if require.is_empty() {
return Err(CtxError::Other(
"--require needs a version (e.g. \"0.2\") or requirement (e.g. \">=0.2\")".to_string(),
));
}
if has_operator(require) {
let req = VersionReq::parse(require).map_err(|e| {
CtxError::Other(format!("invalid version requirement '{require}': {e}"))
})?;
return Ok(req.matches(current));
}
let padded = pad_bare_version(require);
let required = Version::parse(&padded)
.map_err(|e| CtxError::Other(format!("invalid version '{require}': {e}")))?;
Ok(current >= &required)
}
#[cfg(test)]
mod tests {
use super::*;
fn v(s: &str) -> Version {
Version::parse(s).unwrap()
}
#[test]
fn test_bare_versions_are_floors() {
let current = v("0.2.1");
assert!(satisfies(¤t, "0.1").unwrap());
assert!(satisfies(¤t, "0.2").unwrap());
assert!(satisfies(¤t, "0.2.1").unwrap());
assert!(!satisfies(¤t, "0.2.2").unwrap());
assert!(!satisfies(¤t, "999.0").unwrap());
assert!(!satisfies(¤t, "999").unwrap());
}
#[test]
fn test_operator_expressions_use_semver_req() {
let current = v("0.2.1");
assert!(satisfies(¤t, "^0.2").unwrap());
assert!(!satisfies(¤t, "^0.1").unwrap()); assert!(satisfies(¤t, ">=0.1").unwrap());
assert!(!satisfies(¤t, ">=999").unwrap());
assert!(satisfies(¤t, ">=0.2, <0.3").unwrap());
assert!(satisfies(¤t, "0.2.*").unwrap());
}
#[test]
fn test_garbage_is_an_error() {
let current = v("0.2.1");
assert!(satisfies(¤t, "garbage").is_err());
assert!(satisfies(¤t, "").is_err());
assert!(satisfies(¤t, ">>nope").is_err());
}
#[test]
fn test_bare_prerelease_versions_parse() {
let current = v("0.3.0");
assert!(satisfies(¤t, "0.3.0-alpha").unwrap());
assert!(!satisfies(&v("0.3.0-alpha"), "0.3.0").unwrap());
}
}