use std::cmp::Ordering;
use std::iter::Peekable;
use std::slice::Iter;
use comp_op::CompOp;
use version_manifest::VersionManifest;
use version_part::VersionPart;
pub struct Version<'a> {
version: &'a str,
parts: Vec<VersionPart<'a>>,
manifest: Option<&'a VersionManifest>,
}
impl<'a> Version<'a> {
pub fn from(version: &'a str) -> Option<Self> {
let parts = Self::split_version_str(version, None);
if parts.is_none() {
return None;
}
Some(Version {
version: version,
parts: parts.unwrap(),
manifest: None,
})
}
pub fn from_manifest(version: &'a str, manifest: &'a VersionManifest) -> Option<Self> {
let parts = Self::split_version_str(version, Some(&manifest));
if parts.is_none() {
return None;
}
Some(Version {
version: version,
parts: parts.unwrap(),
manifest: Some(&manifest),
})
}
pub fn manifest(&self) -> Option<&VersionManifest> {
self.manifest
}
pub fn has_manifest(&self) -> bool {
self.manifest().is_some()
}
pub fn set_manifest(&mut self, manifest: Option<&'a VersionManifest>) {
self.manifest = manifest;
}
fn split_version_str(version: &'a str, manifest: Option<&'a VersionManifest>) -> Option<Vec<VersionPart<'a>>> {
let split = version.split('.');
let mut parts = Vec::new();
let mut used_manifest = &VersionManifest::new();
if manifest.is_some() {
used_manifest = manifest.unwrap();
}
let mut has_number = false;
for part in split {
if used_manifest.max_depth().is_some() && parts.len() >= used_manifest.max_depth_number() {
break;
}
if part.is_empty() {
continue;
}
match part.parse::<i32>() {
Ok(number) => {
parts.push(VersionPart::Number(number));
has_number = true;
},
Err(_) => {
if used_manifest.ignore_text() {
continue;
}
parts.push(VersionPart::Text(part))
},
}
}
if !has_number && !parts.is_empty() {
return None
}
Some(parts)
}
pub fn as_str(&self) -> &str {
&self.version
}
pub fn part(&self, index: usize) -> Result<&VersionPart<'a>, ()> {
if index >= self.parts.len() {
return Err(());
}
Ok(&self.parts[index])
}
pub fn parts(&self) -> &Vec<VersionPart<'a>> {
&self.parts
}
pub fn part_count(&self) -> usize {
self.parts.len()
}
pub fn compare(&self, other: &Version) -> CompOp {
Self::compare_iter(
self.parts.iter().peekable(),
other.parts.iter().peekable(),
)
}
pub fn compare_to(&self, other: &Version, operator: &CompOp) -> bool {
let result = self.compare(&other);
match result {
CompOp::Eq =>
match operator {
&CompOp::Eq | &CompOp::Le | &CompOp::Ge => true,
_ => false,
},
CompOp::Lt =>
match operator {
&CompOp::Ne | &CompOp::Lt | &CompOp::Le => true,
_ => false,
},
CompOp::Gt =>
match operator {
&CompOp::Ne | &CompOp::Gt | &CompOp::Ge => true,
_ => false,
},
_ => unreachable!(),
}
}
fn compare_iter(mut iter: Peekable<Iter<VersionPart<'a>>>, mut other_iter: Peekable<Iter<VersionPart<'a>>>) -> CompOp {
let mut other_part: Option<&VersionPart>;
loop {
match iter.next() {
Some(part) => {
match part {
&VersionPart::Number(_) => {},
_ => continue,
}
loop {
other_part = other_iter.next();
match other_part {
Some(val) =>
match val {
&VersionPart::Number(_) => break,
_ => {},
},
None => break,
}
}
if other_part.is_none() {
match part {
&VersionPart::Number(num) =>
if num == 0 {
continue;
},
_ => {},
}
return CompOp::Gt;
}
match part {
&VersionPart::Number(num) =>
match other_part.unwrap() {
&VersionPart::Number(other_num) => {
match num {
n if n < other_num => return CompOp::Lt,
n if n > other_num => return CompOp::Gt,
_ => continue,
}
},
_ => unreachable!(),
},
_ => unreachable!(),
}
},
None => break,
}
}
match other_iter.peek() {
Some(_) => Self::compare_iter(other_iter, iter).as_flipped(),
None => CompOp::Eq,
}
}
}
impl<'a> PartialOrd for Version<'a> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.compare(other).ord().unwrap())
}
}
impl<'a> PartialEq for Version<'a> {
fn eq(&self, other: &Self) -> bool {
self.compare_to(other, &CompOp::Eq)
}
}
#[cfg(test)]
mod tests {
use std::cmp;
use comp_op::CompOp;
use test::test_version::{TEST_VERSIONS, TEST_VERSIONS_ERROR};
use test::test_version_set::TEST_VERSION_SETS;
use version_manifest::VersionManifest;
use version_part::VersionPart;
use super::Version;
#[test]
fn from() {
for version in TEST_VERSIONS {
assert!(Version::from(&version.0).is_some());
}
for version in TEST_VERSIONS_ERROR {
assert!(Version::from(&version.0).is_none());
}
}
#[test]
fn from_manifest() {
let manifest = VersionManifest::new();
for version in TEST_VERSIONS {
assert_eq!(Version::from_manifest(&version.0, &manifest).unwrap().manifest, Some(&manifest));
}
for version in TEST_VERSIONS_ERROR {
assert!(Version::from_manifest(&version.0, &manifest).is_none());
}
}
#[test]
fn manifest() {
let manifest = VersionManifest::new();
let mut version = Version::from("1.2.3").unwrap();
version.manifest = Some(&manifest);
assert_eq!(version.manifest(), Some(&manifest));
version.manifest = None;
assert_eq!(version.manifest(), None);
}
#[test]
fn has_manifest() {
let manifest = VersionManifest::new();
let mut version = Version::from("1.2.3").unwrap();
version.manifest = Some(&manifest);
assert!(version.has_manifest());
version.manifest = None;
assert!(!version.has_manifest());
}
#[test]
fn set_manifest() {
let manifest = VersionManifest::new();
let mut version = Version::from("1.2.3").unwrap();
version.set_manifest(Some(&manifest));
assert_eq!(version.manifest, Some(&manifest));
version.set_manifest(None);
assert_eq!(version.manifest, None);
}
#[test]
fn as_str() {
for version in TEST_VERSIONS {
assert_eq!(Version::from(&version.0).unwrap().as_str(), version.0);
}
}
#[test]
fn part() {
for version in TEST_VERSIONS {
let ver = Version::from(&version.0).unwrap();
for i in 0..version.1 {
assert_eq!(ver.part(i), Ok(&ver.parts[i]));
}
assert!(ver.part(version.1).is_err());
}
}
#[test]
fn parts() {
for version in TEST_VERSIONS {
assert_eq!(Version::from(&version.0).unwrap().parts().len(), version.1);
}
}
#[test]
fn parts_max_depth() {
let mut manifest = VersionManifest::new();
for depth in 0..5 {
manifest.set_max_depth_number(depth);
for version in TEST_VERSIONS {
let ver = Version::from_manifest(&version.0, &manifest);
if ver.is_none() {
continue;
}
let count = ver.unwrap().parts().len();
if depth == 0 {
assert_eq!(count, version.1);
} else {
assert_eq!(count, cmp::min(version.1, depth));
}
}
}
}
#[test]
fn parts_ignore_text() {
let mut manifest = VersionManifest::new();
for ignore in vec![true, false] {
manifest.set_ignore_text(ignore);
let mut had_text = false;
for version in TEST_VERSIONS {
let ver = Version::from_manifest(&version.0, &manifest).unwrap();
for part in ver.parts() {
match part {
&VersionPart::Text(_) => {
had_text = true;
if !ignore {
break;
} },
_ => {},
}
}
}
assert_eq!(had_text, !ignore);
}
}
#[test]
fn part_count() {
for version in TEST_VERSIONS {
assert_eq!(Version::from(&version.0).unwrap().part_count(), version.1);
}
}
#[test]
fn compare() {
for entry in TEST_VERSION_SETS {
let version_a = Version::from(&entry.0).unwrap();
let version_b = Version::from(&entry.1).unwrap();
assert_eq!(
version_a.compare(&version_b),
entry.2.clone()
);
}
}
#[test]
fn compare_to() {
for entry in TEST_VERSION_SETS {
let version_a = Version::from(&entry.0).unwrap();
let version_b = Version::from(&entry.1).unwrap();
assert!(version_a.compare_to(&version_b, &entry.2));
assert_eq!(version_a.compare_to(&version_b, &entry.2.invert()), false);
}
assert!(
Version::from("1.2").unwrap().compare_to(
&Version::from("1.2.3").unwrap(),
&CompOp::Ne,
));
}
#[test]
fn partial_cmp() {
for entry in TEST_VERSION_SETS {
let version_a = Version::from(&entry.0).unwrap();
let version_b = Version::from(&entry.1).unwrap();
match entry.2 {
CompOp::Eq => assert!(version_a == version_b),
CompOp::Lt => assert!(version_a < version_b),
CompOp::Gt => assert!(version_a > version_b),
_ => unreachable!(),
}
}
}
#[test]
fn partial_eq() {
for entry in TEST_VERSION_SETS {
match entry.2 {
CompOp::Le | CompOp::Ge => continue,
_ => {}
}
let version_a = Version::from(&entry.0).unwrap();
let version_b = Version::from(&entry.1).unwrap();
let result = match entry.2 {
CompOp::Eq => true,
_ => false,
};
assert_eq!(version_a == version_b, result);
}
assert!(Version::from("1.2").unwrap() != Version::from("1.2.3").unwrap());
}
}