use crate::syntax::{Clause, Op, Range, Requirement, Version, VersionKind};
pub trait MatchesVersion {
fn matches(&self, version: &Version) -> bool;
}
impl MatchesVersion for Version {
fn matches(&self, version: &Version) -> bool {
self == version
}
}
impl MatchesVersion for Requirement {
fn matches(&self, version: &Version) -> bool {
self.matches_op(version) && (version.prerelease.is_none() || self.matches_pre(version))
}
}
impl MatchesVersion for Clause {
fn matches(&self, version: &Version) -> bool {
match self {
Clause::All(reqs) => {
reqs.iter().all(|req| req.matches_op(version))
&& (version.prerelease.is_none()
|| reqs.iter().any(|req| req.matches_pre(version)))
}
Clause::Between(lower, upper) => {
let lower = lower.to_requirement(Op::GreaterEq);
let upper = upper.to_requirement(Op::LessEq);
lower.matches_op(version)
&& upper.matches_op(version)
&& (version.prerelease.is_none()
|| lower.matches_pre(version)
|| upper.matches_pre(version))
}
Clause::Only(req) => req.matches(version),
}
}
}
impl MatchesVersion for Range {
fn matches(&self, version: &Version) -> bool {
if self.clauses.is_empty() {
return version.prerelease.is_none();
}
self.clauses.iter().any(|clause| clause.matches(version))
}
}
#[derive(Debug, Clone)]
pub struct FormatOptions {
pub include_op: bool,
pub include_minor: bool,
pub include_scope: bool,
pub include_patch: bool,
pub include_prerelease: bool,
pub include_build: bool,
pub pad_major: Option<u8>,
pub pad_minor: Option<u8>,
pub pad_patch: Option<u8>,
pub separator: char,
}
impl FormatOptions {
pub fn new(kind: VersionKind) -> Self {
match kind {
VersionKind::Calendar => Self::calendar(),
VersionKind::Semantic => Self::semantic(),
}
}
pub fn calendar() -> Self {
Self {
pad_major: Some(4),
pad_minor: Some(2),
pad_patch: Some(2),
separator: '-',
..Default::default()
}
}
pub fn semantic() -> Self {
Self::default()
}
}
impl Default for FormatOptions {
fn default() -> Self {
Self {
include_op: true,
include_minor: true,
include_scope: true,
include_patch: true,
include_prerelease: true,
include_build: true,
pad_major: None,
pad_minor: None,
pad_patch: None,
separator: '.',
}
}
}
pub trait FormatsVersion {
fn to_formatted_string(&self, options: &FormatOptions) -> String;
}
impl FormatsVersion for Version {
fn to_formatted_string(&self, options: &FormatOptions) -> String {
let mut out = String::new();
if options.include_scope
&& let Some(scope) = &self.scope
{
out.push_str(scope);
out.push('-');
}
let pad = |out: &mut String, value: u32, width: Option<u8>| {
if let Some(width) = width {
let width = width as usize;
out.push_str(&format!("{value:0>width$}"));
} else {
out.push_str(&value.to_string());
}
};
pad(&mut out, self.major, options.pad_major);
if options.include_minor && !(self.kind == VersionKind::Calendar && self.minor == 0) {
out.push(options.separator);
pad(&mut out, self.minor, options.pad_minor);
if options.include_patch && !(self.kind == VersionKind::Calendar && self.patch == 0) {
out.push(options.separator);
pad(&mut out, self.patch, options.pad_patch);
}
}
if options.include_prerelease
&& let Some(pre) = &self.prerelease
{
out.push('-');
out.push_str(pre);
}
if options.include_build
&& let Some(build) = &self.build
{
out.push('+');
out.push_str(build);
}
out
}
}
impl FormatsVersion for Requirement {
fn to_formatted_string(&self, options: &FormatOptions) -> String {
let mut out = if options.include_op {
self.op.to_string()
} else {
String::new()
};
if options.include_scope
&& let Some(scope) = &self.scope
{
out.push_str(scope);
out.push('-');
}
let pad = |out: &mut String, value: &u32, width: Option<u8>| {
if let Some(width) = width {
let width = width as usize;
out.push_str(&format!("{value:0>width$}"));
} else {
out.push_str(&value.to_string());
}
};
if let Some(major) = &self.major {
pad(&mut out, major, options.pad_major);
if !options.include_minor {
} else if let Some(minor) = &self.minor {
out.push(options.separator);
pad(&mut out, minor, options.pad_minor);
if !options.include_patch {
} else if let Some(patch) = &self.patch {
out.push(options.separator);
pad(&mut out, patch, options.pad_patch);
} else if options.include_op && self.op == Op::Wildcard {
out.push(options.separator);
out.push('*');
}
} else if options.include_op && self.op == Op::Wildcard {
out.push(options.separator);
out.push('*');
}
} else if options.include_op && self.op == Op::Wildcard {
out.push('*');
}
if options.include_prerelease
&& let Some(pre) = &self.prerelease
{
out.push('-');
out.push_str(pre);
}
out
}
}