From ed56713461e104847a50b4d0d5fceb33bb3ac63a Mon Sep 17 00:00:00 2001 From: Michael Sarahan Date: Mon, 14 Oct 2019 22:03:00 -0500 Subject: [PATCH] more pep440 impl; remove manifest in favor of function param --- src/custom_parts/mod.rs | 1 + src/custom_parts/pep440.rs | 96 +++++++++ src/lib.rs | 14 +- src/parsers/conda.rs | 0 src/parsers/default.rs | 44 ++++ src/parsers/mod.rs | 2 + src/version.rs | 406 +++++-------------------------------- src/version_manifest.rs | 282 -------------------------- src/version_part.rs | 124 +++-------- 9 files changed, 236 insertions(+), 733 deletions(-) create mode 100644 src/custom_parts/mod.rs create mode 100644 src/custom_parts/pep440.rs create mode 100644 src/parsers/conda.rs create mode 100644 src/parsers/default.rs create mode 100644 src/parsers/mod.rs delete mode 100644 src/version_manifest.rs diff --git a/src/custom_parts/mod.rs b/src/custom_parts/mod.rs new file mode 100644 index 0000000..48b9dbf --- /dev/null +++ b/src/custom_parts/mod.rs @@ -0,0 +1 @@ +pub mod pep440; \ No newline at end of file diff --git a/src/custom_parts/pep440.rs b/src/custom_parts/pep440.rs new file mode 100644 index 0000000..083fe18 --- /dev/null +++ b/src/custom_parts/pep440.rs @@ -0,0 +1,96 @@ +use std::cmp::Ordering; +use std::fmt; +use regex::Regex; + +#[derive(Debug, Copy, Clone)] +pub struct PEP440String<'a> { + pre: i16, + alpha: &'a str, + post: i16, +} + +impl<'a> PEP440String<'a> { + fn new(input: &'a str) -> PEP440String { + lazy_static! { + static ref RE: Regex = Regex::new(r"^(\d*)([a-zA-Z]*)(\d*)").unwrap(); + } + + let caps = RE.captures(input).unwrap(); + let pre: i16 = caps.get(1).map_or(0, |m| m.as_str().parse().unwrap()); + let alpha = caps.get(2).map_or("", |m| m.as_str()); + let post: i16 = caps.get(3).map_or(0, |m| m.as_str().parse().unwrap()); + + PEP440String{pre, alpha, post } + } + + pub fn empty() -> PEP440String<'a> { + PEP440String {pre: 0, alpha: "", post: 0} + } +} + +fn compare_pep440_str<'a>(left: &'a str, right: &'a str) -> Option { + lazy_static! { static ref DEV_RE: Regex = Regex::new("dev").unwrap(); } + lazy_static! { static ref POST_RE: Regex = Regex::new("post").unwrap(); } + + let is_dev = (DEV_RE.is_match(left), DEV_RE.is_match(right)); + let is_post = (POST_RE.is_match(left), POST_RE.is_match(right)); + + let str_match = left.partial_cmp(right); + match str_match { + Some(Ordering::Equal) => Some(Ordering::Equal), + _ => match is_dev { + (false, true) => Some(Ordering::Greater), + (true, false) => Some(Ordering::Less), + _ => match is_post { + (true, true) => Some(Ordering::Equal), + (false, true) => Some(Ordering::Less), + (true, false) => Some(Ordering::Greater), + // this is the final fallback to lexicographic sorting, if neither + // dev nor post are in effect + (false, false) => left.partial_cmp(right), + } + } + } +} + +impl<'a> PartialOrd for PEP440String<'a> { + fn partial_cmp(&self, other: &Self) -> Option { + match self.pre.partial_cmp(&other.pre) { + Some(Ordering::Greater) => Some(Ordering::Greater), + Some(Ordering::Less) => Some(Ordering::Less), + Some(Ordering::Equal) => match compare_pep440_str(self.alpha, &other.alpha) { + Some(Ordering::Equal) => self.post.partial_cmp(&other.post), + Some(Ordering::Greater) => Some(Ordering::Greater), + Some(Ordering::Less) => Some(Ordering::Less), + _ => panic!() + } + _ => panic!() + } + } +} + +impl<'a> PartialEq for PEP440String<'a> { + fn eq(&self, other: &Self) -> bool { + self.partial_cmp(&other).unwrap() == Ordering::Equal + } +} + +impl<'a> fmt::Display for PEP440String<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}{}{}", self.pre, self.alpha, self.post) + } +} + +#[cfg_attr(tarpaulin, skip)] +#[cfg(test)] +mod tests { + use super::PEP440String; + + #[test] + fn compare_implict_leading_zero() { + assert_eq!(PEP440String::new("0dev"), PEP440String::new("dev")); + // epoch of any value trumps integer (priority) + // assert!(VersionPart::Epoch(value: 0) > VersionPart::Integer(value: 1); + // assert!(Version::Epoch{0} > Version::String{"abc"}); + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 9401604..423f752 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,15 +54,19 @@ #[macro_use] extern crate lazy_static; pub mod comp_op; -//pub mod version; -pub mod version_manifest; -pub mod version_part; +pub mod version; +pub mod version_compare; +pub mod parsers; +// Ideally no one interacts directly with the parts +mod version_part; +mod custom_parts; #[cfg(test)] mod test; // Reexports pub use crate::comp_op::CompOp; -//pub use crate::version::Version; -pub use crate::version_manifest::VersionManifest; +pub use crate::version::Version; pub use crate::version_part::VersionPart; +pub use crate::version_compare::VersionCompare; +pub use crate::parsers::default::default_parser; diff --git a/src/parsers/conda.rs b/src/parsers/conda.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/parsers/default.rs b/src/parsers/default.rs new file mode 100644 index 0000000..40e98ce --- /dev/null +++ b/src/parsers/default.rs @@ -0,0 +1,44 @@ +use crate::version_part::VersionPart; + +/// Split the given version string, in it's version parts. +/// TODO: Move this method to some sort of helper class, maybe as part of `VersionPart`. +pub fn default_parser( + version: &str, +) -> Option> { + // Split the version string, and create a vector to put the parts in + // TODO: split at specific separators instead + let split = version.split(|c| !char::is_alphanumeric(c)); + let mut parts = Vec::new(); + + // Flag to determine whether this version number contains any number part + let mut has_number = false; + + // Loop over the parts, and parse them + for part in split { + // Skip empty parts + if part.is_empty() { + continue; + } + + // Try to parse the value as an number + match part.parse::() { + Ok(number) => { + // Push the number part to the vector, and set the has number flag + parts.push(VersionPart::Integer(number)); + has_number = true; + } + Err(_) => { + // Push the text part to the vector + parts.push(VersionPart::LexicographicString(part)); + } + } + } + + // The version must contain a number part, if any part was parsed + if !has_number && !parts.is_empty() { + return None; + } + + // Return the list of parts + Some(parts) +} \ No newline at end of file diff --git a/src/parsers/mod.rs b/src/parsers/mod.rs new file mode 100644 index 0000000..82908c9 --- /dev/null +++ b/src/parsers/mod.rs @@ -0,0 +1,2 @@ +pub mod conda; +pub mod default; \ No newline at end of file diff --git a/src/version.rs b/src/version.rs index ba544be..9aea953 100644 --- a/src/version.rs +++ b/src/version.rs @@ -10,8 +10,8 @@ use std::iter::Peekable; use std::slice::Iter; use crate::comp_op::CompOp; -use crate::version_manifest::VersionManifest; -use crate::version_part::VersionPart; +use crate::version_part::{VersionPart, ProvideEmptyImpl}; +use crate::parsers::default::default_parser; /// Version struct, which is a representation for a parsed version string. /// @@ -25,8 +25,7 @@ use crate::version_part::VersionPart; /// The struct provides many methods for comparison and probing. pub struct Version<'a> { version: &'a str, - parts: Vec>, - manifest: Option<&'a VersionManifest>, + parts: Vec>, } impl<'a> Version<'a> { @@ -43,175 +42,37 @@ impl<'a> Version<'a> { /// /// assert_eq!(ver.compare(&Version::from("1.2.3").unwrap()), CompOp::Eq); /// ``` - pub fn from(version: &'a str) -> Option { - // Split the version string - let parts = Self::split_version_str(version, None); - - // Return nothing if the parts are none - if parts.is_none() { - return None; - } - - // Create and return the object - Some(Version { - version: version, - parts: parts.unwrap(), - manifest: None, - }) + pub fn from(version: &'a str) -> Option { + Version::parse(version, &default_parser) } - /// Create a `Version` instance from a version string with the given `manifest`. + /// Create a `Version` instance from a version string with the given `parser` function. /// - /// The version string should be passed to the `version` parameter. + /// The version string should be passed to the `version` parameter. Additional parsers + /// are in the "parsers" module. This is the primary means of customizing behavior. /// /// # Examples /// /// ``` - /// use version_compare::{CompOp, Version, VersionManifest}; + /// use version_compare::{CompOp, Version, default_parser}; /// - /// let manifest = VersionManifest::new(); - /// let ver = Version::from_manifest("1.2.3", &manifest).unwrap(); + /// let ver = Version::parse("1.2.3", &default_parser).unwrap(); /// /// assert_eq!(ver.compare(&Version::from("1.2.3").unwrap()), CompOp::Eq); /// ``` - pub fn from_manifest(version: &'a str, manifest: &'a VersionManifest) -> Option { - // Split the version string - let parts = Self::split_version_str(version, Some(&manifest)); + pub fn parse(version: &'a str, parser: &dyn Fn(&'a str) -> Option>>) -> Option { + let parts: Option>> = parser(version); - // Return nothing if the parts are none if parts.is_none() { return None; } - // Create and return the object - Some(Version { - version: version, + Some(Self { + version, parts: parts.unwrap(), - manifest: Some(&manifest), }) } - /// Get the version manifest, if available. - /// - /// # Examples - /// - /// ``` - /// use version_compare::Version; - /// - /// let version = Version::from("1.2.3").unwrap(); - /// - /// if version.has_manifest() { - /// println!( - /// "Maximum version part depth is {} for this version", - /// version.manifest().unwrap().max_depth_number() - /// ); - /// } else { - /// println!("Version has no manifest"); - /// } - /// ``` - pub fn manifest(&self) -> Option<&VersionManifest> { - self.manifest - } - - /// Check whether this version has a manifest. - /// - /// # Examples - /// - /// ``` - /// use version_compare::Version; - /// - /// let version = Version::from("1.2.3").unwrap(); - /// - /// if version.has_manifest() { - /// println!("This version does have a manifest"); - /// } else { - /// println!("This version does not have a manifest"); - /// } - /// ``` - pub fn has_manifest(&self) -> bool { - self.manifest().is_some() - } - - /// Set the version manifest. - /// - /// # Examples - /// - /// ``` - /// use version_compare::{Version, VersionManifest}; - /// - /// let manifest = VersionManifest::new(); - /// let mut version = Version::from("1.2.3").unwrap(); - /// - /// version.set_manifest(Some(&manifest)); - /// ``` - pub fn set_manifest(&mut self, manifest: Option<&'a VersionManifest>) { - self.manifest = manifest; - - // TODO: Re-parse the version string, because the manifest might have changed. - } - - /// Split the given version string, in it's version parts. - /// TODO: Move this method to some sort of helper class, maybe as part of `VersionPart`. - fn split_version_str( - version: &'a str, - manifest: Option<&'a VersionManifest>, - ) -> Option>> { - // Split the version string, and create a vector to put the parts in - // TODO: split at specific separators instead - let split = version.split(|c| !char::is_alphanumeric(c)); - let mut parts = Vec::new(); - - // Get the manifest to follow - let mut used_manifest = &VersionManifest::new(); - if manifest.is_some() { - used_manifest = manifest.unwrap(); - } - - // Flag to determine whether this version number contains any number part - let mut has_number = false; - - // Loop over the parts, and parse them - for part in split { - // We may not go over the maximum depth - if used_manifest.max_depth().is_some() - && parts.len() >= used_manifest.max_depth_number() - { - break; - } - - // Skip empty parts - if part.is_empty() { - continue; - } - - // Try to parse the value as an number - match part.parse::() { - Ok(number) => { - // Push the number part to the vector, and set the has number flag - parts.push(get_integer_part(number)); - has_number = true; - } - Err(_) => { - // Ignore text parts if specified - if used_manifest.ignore_text() { - continue; - } - - // Push the text part to the vector - parts.push(get_lexicographic_string_part(part)); - } - } - } - - // The version must contain a number part, if any part was parsed - if !has_number && !parts.is_empty() { - return None; - } - - // Return the list of parts - Some(parts) - } - /// Get the original version string. /// /// # Examples @@ -237,11 +98,11 @@ impl<'a> Version<'a> { /// /// let ver = Version::from("1.2.3").unwrap(); /// - /// assert_eq!(ver.part(0), Ok(&VersionPart::Number(1))); - /// assert_eq!(ver.part(1), Ok(&VersionPart::Number(2))); - /// assert_eq!(ver.part(2), Ok(&VersionPart::Number(3))); + /// assert_eq!(ver.part(0), Ok(&VersionPart::Integer(1))); + /// assert_eq!(ver.part(1), Ok(&VersionPart::Integer(2))); + /// assert_eq!(ver.part(2), Ok(&VersionPart::Integer(3))); /// ``` - pub fn part(&self, index: usize) -> Result<&dyn VersionPart<'a>, ()> { + pub fn part(&self, index: usize) -> Result<&VersionPart<'a>, ()> { // Make sure the index is in-bound if index >= self.parts.len() { return Err(()); @@ -261,12 +122,12 @@ impl<'a> Version<'a> { /// let ver = Version::from("1.2.3").unwrap(); /// /// assert_eq!(ver.parts(), &vec![ - /// VersionPart::Number(1), - /// VersionPart::Number(2), - /// VersionPart::Number(3) + /// VersionPart::Integer(1), + /// VersionPart::Integer(2), + /// VersionPart::Integer(3) /// ]); /// ``` - pub fn parts(&self) -> &Vec> { + pub fn parts(&self) -> &Vec { &self.parts } @@ -359,66 +220,41 @@ impl<'a> Version<'a> { /// /// Other comparison operators can be used when comparing, but aren't returned by this method. fn compare_iter( - mut iter: Peekable>>, - mut other_iter: Peekable>>, + mut iter: Peekable>, + mut other_iter: Peekable>, ) -> CompOp { - // Iterate through the parts of this version - let mut other_part: Option<&dyn VersionPart<'a>>; - // Iterate over the iterator, without consuming it loop { - match iter.next() { - Some(part) => { - // Get the part for the other version - other_part = other_iter.next(); - - // If there are no parts left in the other version, try to determine the result - if other_part.is_none() { - // In the main version: if the current part is zero, continue to the next one - match part { - &VersionPart::Number(num) => { - if num == 0 { - continue; - } - } - &VersionPart::Text(_) => return CompOp::Lt, - } - - // The main version is greater - return CompOp::Gt; - } - - // Match both part as numbers to destruct their numerical values - match part { - &VersionPart::Number(num) => match other_part.unwrap() { - &VersionPart::Number(other_num) => { - // Compare the numbers - match num { - n if n < other_num => return CompOp::Lt, - n if n > other_num => return CompOp::Gt, - _ => continue, - } - } - _ => {} - }, - _ => {} - } - } - None => break, - } - } - - // Check whether we should iterate over the other iterator, if it has any items left - match other_iter.peek() { - // Compare based on the other iterator - Some(_) => Self::compare_iter(other_iter, iter).as_flipped(), - - // Nothing more to iterate over, the versions should be equal - None => CompOp::Eq, + let i1 = iter.next(); + let i2 = other_iter.next(); + let _cmp = match (i1, i2) { + (Some(i), None) => match i.partial_cmp(&i.get_empty()) { + Some(Ordering::Less) => return CompOp::Lt, + Some(Ordering::Greater) => return CompOp::Gt, + Some(Ordering::Equal) => return CompOp::Eq, + _ => panic!() + }, + (None, Some(j)) => match j.get_empty().partial_cmp(j) { + Some(Ordering::Less) => return CompOp::Lt, + Some(Ordering::Greater) => return CompOp::Gt, + Some(Ordering::Equal) => return CompOp::Eq, + _ => panic!() + }, + (Some(i), Some(j)) => match i.partial_cmp(j) { + Some(Ordering::Greater) => return CompOp::Gt, + Some(Ordering::Less) => return CompOp::Lt, + // This is the only loop branch that continues + Some(Ordering::Equal) => Ordering::Equal, + _ => panic!() + }, + // both versions are the same length and are equal for all values + (None, None) => return CompOp::Eq + }; } } } + impl<'a> fmt::Display for Version<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.version) @@ -439,7 +275,7 @@ impl<'a> fmt::Debug for Version<'a> { /// Implement the partial ordering trait for the version struct, to easily allow version comparison. impl<'a> PartialOrd for Version<'a> { fn partial_cmp(&self, other: &Self) -> Option { - Some(self.compare(other).ord().unwrap()) + self.compare(other).ord() } } @@ -453,12 +289,9 @@ impl<'a> PartialEq for Version<'a> { #[cfg_attr(tarpaulin, skip)] #[cfg(test)] mod tests { - use std::cmp; - use crate::comp_op::CompOp; use crate::test::test_version::{TEST_VERSIONS, TEST_VERSIONS_ERROR}; use crate::test::test_version_set::TEST_VERSION_SETS; - use crate::version_manifest::VersionManifest; // use crate::version_part::VersionPart; use super::Version; @@ -477,64 +310,6 @@ mod tests { } } - #[test] - // TODO: This doesn't really test whether this method fully works - fn from_manifest() { - // Create a manifest - let manifest = VersionManifest::new(); - - // Test whether parsing works for each test version - for version in TEST_VERSIONS { - assert_eq!( - Version::from_manifest(&version.0, &manifest) - .unwrap() - .manifest, - Some(&manifest) - ); - } - - // Test whether parsing works for each test invalid version - 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() { // Test for each test version @@ -570,81 +345,6 @@ mod tests { } } - #[test] - fn parts_max_depth() { - // Create a manifest - let mut manifest = VersionManifest::new(); - - // Loop through a range of numbers - for depth in 0..5 { - // Set the maximum depth - manifest.set_max_depth_number(depth); - - // Test for each test version with the manifest - for version in TEST_VERSIONS { - // Create a version object, and count it's parts - let ver = Version::from_manifest(&version.0, &manifest); - - // Some versions might be none, because not all of the start with a number when the - // maximum depth is 1. A version string with only text isn't allowed, - // resulting in none. - if ver.is_none() { - continue; - } - - // Get the part count - let count = ver.unwrap().parts().len(); - - // The number of parts must match - if depth == 0 { - assert_eq!(count, version.1); - } else { - assert_eq!(count, cmp::min(version.1, depth)); - } - } - } - } - - #[test] - fn parts_ignore_text() { - // Create a manifest - let mut manifest = VersionManifest::new(); - - // Try this for true and false - for ignore in vec![true, false] { - // Set to ignore text - manifest.set_ignore_text(ignore); - - // Keep track whether any version passed with text - let mut had_text = false; - - // Test each test version - for version in TEST_VERSIONS { - // Create a version instance, and get it's parts - let ver = Version::from_manifest(&version.0, &manifest).unwrap(); - - // Loop through all version parts -// for part in ver.parts() { -// match part { -// &VersionPart::Text(_) => { -// // Set the flag -// had_text = true; -// -// // Break the loop if we already reached text when not ignored -// if !ignore { -// break; -// } -// } -// _ => {} -// } -// } - } - - // Assert had text - assert_eq!(had_text, !ignore); - } - } - #[test] fn part_count() { // Test for each test version @@ -704,11 +404,11 @@ mod tests { fn debug() { assert_eq!( format!("{:?}", Version::from("1.2.3").unwrap()), - "[Number(1), Number(2), Number(3)]", + "[Integer(1), Integer(2), Integer(3)]", ); assert_eq!( format!("{:#?}", Version::from("1.2.3").unwrap()), - "[\n Number(\n 1,\n ),\n Number(\n 2,\n ),\n Number(\n 3,\n ),\n]", + "[\n Integer(\n 1,\n ),\n Integer(\n 2,\n ),\n Integer(\n 3,\n ),\n]", ); } diff --git a/src/version_manifest.rs b/src/version_manifest.rs deleted file mode 100644 index 3bf2e18..0000000 --- a/src/version_manifest.rs +++ /dev/null @@ -1,282 +0,0 @@ -//! Module for the version manifest. -//! -//! A version manifest can be used to configure and specify how versions are parsed and compared. -//! For example, you can configure the maximum depth of a version number, and set whether text -//! parts are ignored in a version string. - -/// Version manifest (configuration). -/// -/// A manifest (configuration) that is used respectively when parsing and comparing version strings. -#[derive(Debug, PartialEq)] -pub struct VersionManifest { - /// The maximum depth of a version number. This specifies the maximum number of parts. - max_depth: Option, - - /// True to ignore text parts in version strings. - ignore_text: bool, -} - -/// Version manifest implementation. -impl VersionManifest { - /// Constructor. - /// - /// # Examples - /// - /// ``` - /// use version_compare::VersionManifest; - /// - /// let mut manifest = VersionManifest::new(); - /// - /// // Ignore text parts - /// manifest.set_ignore_text(true); - /// ``` - pub fn new() -> Self { - VersionManifest { - max_depth: None, - ignore_text: false, - } - } - - /// The maximum depth of a version number. - /// None if no depth is configured. - /// - /// # Examples - /// - /// ``` - /// use version_compare::VersionManifest; - /// - /// let manifest = VersionManifest::new(); - /// - /// match manifest.max_depth() { - /// &Some(depth) => println!("Maximum depth of {}", depth), - /// &None => println!("No maximum depth") - /// } - /// ``` - pub fn max_depth(&self) -> &Option { - &self.max_depth - } - - /// The maximum depth of a version number as numerical value. - /// Zero is returned if no depth is configured. - /// - /// # Examples - /// - /// ``` - /// use version_compare::VersionManifest; - /// - /// let manifest = VersionManifest::new(); - /// - /// println!("Maximum depth of {}", manifest.max_depth_number()); - /// ``` - pub fn max_depth_number(&self) -> usize { - if self.max_depth.is_some() { - self.max_depth.unwrap() - } else { - 0 - } - } - - /// Set the maximum depth of a version number. - /// - /// # Examples - /// - /// ``` - /// use version_compare::VersionManifest; - /// - /// let mut manifest = VersionManifest::new(); - /// - /// // Set the maximum depth to 3 - /// manifest.set_max_depth(Some(3)); - /// - /// // Don't use a maximum depth - /// manifest.set_max_depth(None); - /// ``` - pub fn set_max_depth(&mut self, max_depth: Option) { - if max_depth.is_some() && max_depth.unwrap() > 0 { - self.max_depth = max_depth; - } else { - self.max_depth = None; - } - } - - /// Set the maximum depth of a version number. - /// Use zero to disable the maximum depth. - /// - /// # Examples - /// - /// ``` - /// use version_compare::VersionManifest; - /// - /// let mut manifest = VersionManifest::new(); - /// - /// // Set the maximum depth to 3 - /// manifest.set_max_depth_number(3); - /// - /// // Don't use a maximum depth - /// manifest.set_max_depth_number(0); - /// ``` - pub fn set_max_depth_number(&mut self, max_depth: usize) { - if max_depth > 0 { - self.max_depth = Some(max_depth); - } else { - self.max_depth = None; - } - } - - /// Check whether there's a maximum configured depth. - /// - /// # Examples - /// - /// ``` - /// use version_compare::VersionManifest; - /// - /// let mut manifest = VersionManifest::new(); - /// - /// assert!(!manifest.has_max_depth()); - /// - /// manifest.set_max_depth(Some(3)); - /// assert!(manifest.has_max_depth()); - /// ``` - pub fn has_max_depth(&self) -> bool { - self.max_depth.is_some() && self.max_depth.unwrap() > 0 - } - - /// Check whether to ignore text parts in version numbers. - /// - /// # Examples - /// - /// ``` - /// use version_compare::VersionManifest; - /// - /// let manifest = VersionManifest::new(); - /// - /// if manifest.ignore_text() { - /// println!("Text parts are ignored"); - /// } else { - /// println!("Text parts are not ignored"); - /// } - /// ``` - pub fn ignore_text(&self) -> bool { - self.ignore_text - } - - /// Set whether to ignore text parts. - /// - /// # Examples - /// - /// ``` - /// use version_compare::VersionManifest; - /// - /// let mut manifest = VersionManifest::new(); - /// - /// // Ignore text parts - /// manifest.set_ignore_text(true); - /// - /// // Don't ignore text parts - /// manifest.set_ignore_text(false); - /// ``` - pub fn set_ignore_text(&mut self, ignore_text: bool) { - self.ignore_text = ignore_text; - } -} - -#[cfg_attr(tarpaulin, skip)] -#[cfg(test)] -mod tests { - use crate::version_manifest::VersionManifest; - - #[test] - fn max_depth() { - let mut manifest = VersionManifest::new(); - - manifest.max_depth = Some(1); - assert_eq!(manifest.max_depth(), &Some(1)); - - manifest.max_depth = Some(3); - assert_eq!(manifest.max_depth(), &Some(3)); - - manifest.max_depth = None; - assert_eq!(manifest.max_depth(), &None); - } - - #[test] - fn max_depth_number() { - let mut manifest = VersionManifest::new(); - - manifest.max_depth = Some(1); - assert_eq!(manifest.max_depth_number(), 1); - - manifest.max_depth = Some(3); - assert_eq!(manifest.max_depth_number(), 3); - - manifest.max_depth = None; - assert_eq!(manifest.max_depth_number(), 0); - } - - #[test] - fn set_max_depth() { - let mut manifest = VersionManifest::new(); - - manifest.set_max_depth(Some(1)); - assert_eq!(manifest.max_depth, Some(1)); - - manifest.set_max_depth(Some(3)); - assert_eq!(manifest.max_depth, Some(3)); - - manifest.set_max_depth(Some(0)); - assert_eq!(manifest.max_depth, None); - - manifest.set_max_depth(None); - assert_eq!(manifest.max_depth, None); - } - - #[test] - fn set_max_depth_number() { - let mut manifest = VersionManifest::new(); - - manifest.set_max_depth_number(1); - assert_eq!(manifest.max_depth, Some(1)); - - manifest.set_max_depth_number(3); - assert_eq!(manifest.max_depth, Some(3)); - - manifest.set_max_depth_number(0); - assert_eq!(manifest.max_depth, None); - } - - #[test] - fn has_max_depth() { - let mut manifest = VersionManifest::new(); - - manifest.max_depth = Some(1); - assert!(manifest.has_max_depth()); - - manifest.max_depth = Some(3); - assert!(manifest.has_max_depth()); - - manifest.max_depth = None; - assert!(!manifest.has_max_depth()); - } - - #[test] - fn ignore_text() { - let mut manifest = VersionManifest::new(); - - manifest.ignore_text = true; - assert!(manifest.ignore_text()); - - manifest.ignore_text = false; - assert!(!manifest.ignore_text()); - } - - #[test] - fn set_ignore_text() { - let mut manifest = VersionManifest::new(); - - manifest.set_ignore_text(true); - assert!(manifest.ignore_text); - - manifest.set_ignore_text(false); - assert!(!manifest.ignore_text); - } -} diff --git a/src/version_part.rs b/src/version_part.rs index 0455a67..95c3431 100644 --- a/src/version_part.rs +++ b/src/version_part.rs @@ -5,111 +5,52 @@ //! `Version`. use std::cmp::Ordering; -use regex::Regex; +use std::fmt; -#[derive(Debug, Copy, Clone)] -pub struct PEP440String<'a> { - pre: i16, - alpha: &'a str, - post: i16, -} - -impl<'a> PEP440String<'a> { - fn new(input: &'a str) -> PEP440String { - lazy_static! { - static ref re: Regex = Regex::new("(\\d*)(post|dev|[a-zA-Z]+)(\\d*)").unwrap(); - } +use crate::custom_parts::pep440::PEP440String; - let caps = re.captures(input).unwrap(); - let pre: i16 = caps.get(1).map_or(0, |m| m.as_str().parse().unwrap()); - let alpha = caps.get(2).map_or("", |m| m.as_str()); - let post: i16 = caps.get(3).map_or(0, |m| m.as_str().parse().unwrap()); - - PEP440String{pre, alpha, post } - } +#[derive(Debug, Copy, Clone)] +pub enum VersionPart<'a> { + Epoch(i16), + Integer(i32), + LexicographicString(&'a str), + PEP440String(PEP440String<'a>), } -fn compare_pep440_str<'a>(left: &'a str, right: &'a str) -> Option { - lazy_static! { static ref dev_re: Regex = Regex::new("dev").unwrap(); } - lazy_static! { static ref post_re: Regex = Regex::new("post").unwrap(); } - - let is_dev = (dev_re.is_match(left), dev_re.is_match(right)); - let is_post = (post_re.is_match(left), post_re.is_match(right)); - - let str_match = left.partial_cmp(right); - match str_match { - Some(Ordering::Equal) => Some(Ordering::Equal), - _ => match is_dev { - (false, true) => Some(Ordering::Greater), - (true, false) => Some(Ordering::Less), - _ => match is_post { - (true, true) => Some(Ordering::Equal), - (false, true) => Some(Ordering::Less), - (true, false) => Some(Ordering::Greater), - // this is the final fallback to lexicographic sorting, if neither - // dev nor post are in effect - (false, false) => left.partial_cmp(right), - _ => panic!(), - } - } - } +pub trait ProvideEmptyImpl{ + fn get_empty(&self) -> VersionPart; } -impl<'a> PartialOrd for PEP440String<'a> { - fn partial_cmp(&self, other: &Self) -> Option { - match self.pre.partial_cmp(&other.pre) { - Some(Ordering::Greater) => Some(Ordering::Greater), - Some(Ordering::Less) => Some(Ordering::Less), - Some(Ordering::Equal) => match compare_pep440_str(self.alpha, &other.alpha) { - Some(Ordering::Equal) => self.post.partial_cmp(&other.post), - Some(Ordering::Greater) => Some(Ordering::Greater), - Some(Ordering::Less) => Some(Ordering::Less), - _ => panic!() - } - _ => panic!() +impl<'a> ProvideEmptyImpl for VersionPart<'a> { + fn get_empty(&self) -> VersionPart { + match self { + VersionPart::Epoch(_i) => VersionPart::Epoch(0), + VersionPart::Integer(_i) => VersionPart::Integer(0), + VersionPart::LexicographicString(_i) => VersionPart::LexicographicString(""), + VersionPart::PEP440String(_i) => VersionPart::PEP440String(PEP440String::empty()), } } } -impl<'a> PartialEq for PEP440String<'a> { - fn eq(&self, other: &Self) -> bool { - self.partial_cmp(&other).unwrap() == Ordering::Equal - } -} - - -#[derive(Debug, Copy, Clone)] -pub enum VersionPart<'a> { - Epoch(i16), - Integer(i32), - LexicographicString(&'a str), - PEP440String(&'a PEP440String<'a>), - Bool(bool), -} - impl<'a> PartialOrd for VersionPart<'a> { fn partial_cmp(&self, other: &Self) -> Option { match (self, other) { (VersionPart::Epoch(a), VersionPart::Epoch(b)) => a.partial_cmp(b), (VersionPart::Integer(a), VersionPart::Integer(b)) => a.partial_cmp(b), (VersionPart::LexicographicString(a), VersionPart::LexicographicString(b)) => a.partial_cmp(b), - (VersionPart::Bool(a), VersionPart::Bool(b)) => a.partial_cmp(b), + (VersionPart::PEP440String(a), VersionPart::PEP440String(b)) => a.partial_cmp(b), // Match simple position in the list, but reverse it because things at the top are higher _ => Some(match self { - &VersionPart::Epoch(a) => 0, - &VersionPart::Integer(a) => 1, - &VersionPart::LexicographicString(a) => 2, - &VersionPart::PEP440String(a) => 3, - &VersionPart::Bool(a) => 4, - _ => panic!() + &VersionPart::Epoch(_a) => 0, + &VersionPart::Integer(_a) => 1, + &VersionPart::LexicographicString(_a) => 2, + &VersionPart::PEP440String(_a) => 3, }.partial_cmp( match other { - &VersionPart::Epoch(a) => &0, - &VersionPart::Integer(a) => &1, - &VersionPart::LexicographicString(a) => &2, - &VersionPart::PEP440String(a) => &3, - &VersionPart::Bool(a) => &4, - _ => panic!() + &VersionPart::Epoch(_a) => &0, + &VersionPart::Integer(_a) => &1, + &VersionPart::LexicographicString(_a) => &2, + &VersionPart::PEP440String(_a) => &3, } ).unwrap().reverse()) } @@ -122,14 +63,11 @@ impl<'a> PartialEq for VersionPart<'a> { } } -//impl<'a> fmt::Display for VersionPart<'a> { -// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { -// match self { -// VersionPart::Number(n) => write!(f, "{}", n), -// VersionPart::Text(t) => write!(f, "{}", t), -// } -// } -//} +impl<'a> fmt::Display for VersionPart<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:#?}", self) + } +} #[cfg_attr(tarpaulin, skip)] #[cfg(test)]