use core::cmp::Ordering;
use lazy_static::lazy_static;
use rand::{distributions::Alphanumeric, Rng};
use regex::Regex;
use std::fmt;
use std::rc::Rc;
lazy_static! {
static ref ALPHABET_REGEX: Regex =
Regex::new(r"[a-zA-Z0-9_]+").expect("Can't compile ALPHABET_REGEX regex");
static ref SEMVER_REGEX: Regex = Regex::new(
r"^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
).expect("Can't compile SEMVER_REGEX regex");
static ref NUMERIC_REGEX: Regex = Regex::new(r"[0-9]+").expect("Can't compile NUMERIC_REGEX regex");
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
struct SubToken {
s: String,
n: Option<i64>,
}
impl SubToken {
fn new(s: &str) -> Self {
let n = s.parse::<i64>().ok();
SubToken {
s: s.to_string(),
n,
}
}
fn custom_char_order(&self, c: char) -> u8 {
match c {
'_' => 0,
'a'..='z' => 1 + (c as u8 - b'a'),
'A'..='Z' => 27 + (c as u8 - b'A'),
'0'..='9' => 53 + (c as u8 - b'0'),
_ => 255, }
}
fn compare_subtokens(&self, a: &str, b: &str) -> Ordering {
a.chars()
.zip(b.chars())
.map(|(ac, bc)| self.custom_char_order(ac).cmp(&self.custom_char_order(bc)))
.find(|&ordering| ordering != Ordering::Equal)
.unwrap_or_else(|| a.len().cmp(&b.len()))
}
}
#[test]
fn test_subtoken_new() {
let a = SubToken::new("1");
assert_eq!(a.s, "1");
assert_eq!(a.n, Some(1));
let a = SubToken::new("a");
assert_eq!(a.s, "a");
assert_eq!(a.n, None);
let a = SubToken::new("a1");
assert_eq!(a.s, "a1");
}
#[test]
fn test_subtoken_numeric_ordering() {
assert!(SubToken::new("2") < SubToken::new("10"));
assert!(SubToken::new("9") < SubToken::new("100"));
assert!(SubToken::new("122") > SubToken::new("34"));
assert!(SubToken::new("01") < SubToken::new("1"));
assert_eq!(SubToken::new("2").cmp(&SubToken::new("10")), Ordering::Less);
assert_eq!(
SubToken::new("2").partial_cmp(&SubToken::new("10")),
Some(Ordering::Less)
);
}
#[test]
fn test_version_numeric_ordering() {
let a: RerVersion = "2.34.1".try_into().unwrap();
let b: RerVersion = "2.122.2".try_into().unwrap();
assert!(a < b, "2.34.1 should sort below 2.122.2");
let a: RerVersion = "1.9.0".try_into().unwrap();
let b: RerVersion = "1.10.0".try_into().unwrap();
assert!(a < b, "1.9.0 should sort below 1.10.0");
}
impl Ord for SubToken {
fn cmp(&self, other: &Self) -> Ordering {
match (self.n, other.n) {
(None, None) => self.compare_subtokens(&self.s, &other.s),
(None, Some(_)) => Ordering::Less,
(Some(_), None) => Ordering::Greater,
(Some(a), Some(b)) => a
.cmp(&b)
.then_with(|| self.compare_subtokens(&self.s, &other.s)),
}
}
}
impl PartialOrd for SubToken {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl fmt::Display for SubToken {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.s)
}
}
#[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
struct AlphanumericVersionToken {
subtokens: Vec<SubToken>,
}
impl AlphanumericVersionToken {
fn new(token: &str) -> Result<Self, &'static str> {
if !ALPHABET_REGEX.is_match(token) {
Err("Invalid version token")
} else {
Ok(Self {
subtokens: Self::parse(token),
})
}
}
#[allow(dead_code)] fn create_random_token_string() -> Result<Self, &'static str> {
let s: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(7)
.map(char::from)
.collect();
Self::new(&s)
}
fn parse(s: &str) -> Vec<SubToken> {
let mut subtokens = Vec::new();
let mut alphas = NUMERIC_REGEX.split(s).peekable();
let mut numerics = NUMERIC_REGEX.find_iter(s).peekable();
while alphas.peek().is_some() || numerics.peek().is_some() {
if let Some(alpha) = alphas.next() {
if !alpha.is_empty() {
subtokens.push(SubToken::new(alpha));
}
}
if let Some(numeric) = numerics.next() {
subtokens.push(SubToken::new(numeric.as_str()));
}
}
subtokens
}
}
#[test]
fn test_generate_random_version() {
AlphanumericVersionToken::create_random_token_string().unwrap();
}
impl fmt::Display for AlphanumericVersionToken {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
self.subtokens
.iter()
.map(ToString::to_string)
.collect::<String>()
)
}
}
impl AlphanumericVersionToken {
fn lowest() -> Self {
AlphanumericVersionToken {
subtokens: vec![SubToken::new("_")],
}
}
fn bump(&self) -> Self {
let mut next_subtokens = self.subtokens.clone();
let last = next_subtokens
.pop()
.expect("Token should have at least one subtoken");
if last.n.is_some() {
next_subtokens.push(last);
next_subtokens.push(SubToken::new("_"));
} else {
let new_last = SubToken::new(&(last.s + "_"));
next_subtokens.push(new_last);
}
AlphanumericVersionToken {
subtokens: next_subtokens,
}
}
}
#[allow(dead_code)] impl AlphanumericVersionToken {
pub fn compare(&self, other: &Self) -> Ordering {
self.subtokens.iter().cmp(other.subtokens.iter())
}
}
#[test]
fn test_bump_alpha_num() {
let a = AlphanumericVersionToken::new("1").unwrap();
assert_eq!(a.subtokens[0].n, Some(1));
let b = AlphanumericVersionToken::new("1_").unwrap();
assert_eq!(b.subtokens[0].n, Some(1));
assert_eq!(b.subtokens[1].s, "_");
assert_eq!(b.subtokens[1].n, None);
assert_eq!(a.bump(), b);
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RerVersion(Rc<RerVersionInner>);
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct RerVersionInner {
tokens: Vec<AlphanumericVersionToken>,
seps: Vec<char>,
}
impl RerVersion {
fn parse_from_string(s: &str) -> Result<Self, &'static str> {
if !ALPHABET_REGEX.is_match(s) {
Err("Invalid version token")
} else {
let mut tokens = Vec::new();
let mut seps: Vec<char> = Vec::new();
let toks = ALPHABET_REGEX.find_iter(s);
let mut seps_iter = ALPHABET_REGEX.split(s);
for tok in toks {
tokens.push(AlphanumericVersionToken::new(tok.as_str())?);
if let Some(sep) = seps_iter.next() {
if let Some(c) = sep.chars().next() {
seps.push(c)
}
}
}
Ok(RerVersion(Rc::new(RerVersionInner { tokens, seps })))
}
}
}
impl RerVersion {
pub fn lowest() -> Self {
RerVersion(Rc::new(RerVersionInner {
tokens: vec![AlphanumericVersionToken::lowest()],
seps: vec![],
}))
}
pub fn bump(&self) -> Self {
let mut next_tokens = self.0.tokens.clone();
let last = next_tokens
.pop()
.expect("Token should have at least one subtoken");
next_tokens.push(last.bump());
RerVersion(Rc::new(RerVersionInner {
tokens: next_tokens,
seps: self.0.seps.clone(),
}))
}
}
impl fmt::Display for RerVersion {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut s = String::new();
for (i, token) in self.0.tokens.iter().enumerate() {
s.push_str(&token.to_string());
if i < self.0.seps.len() {
s.push(self.0.seps[i]);
}
}
write!(f, "{}", s)
}
}
impl TryFrom<&str> for RerVersion {
type Error = &'static str;
fn try_from(s: &str) -> Result<Self, Self::Error> {
RerVersion::parse_from_string(s)
}
}
impl TryFrom<String> for RerVersion {
type Error = &'static str;
fn try_from(s: String) -> Result<Self, Self::Error> {
RerVersion::parse_from_string(&s)
}
}
#[test]
fn test_from() {
let v: RerVersion = "1.2.3-alpha+beta".try_into().unwrap();
assert_eq!(v.to_string(), "1.2.3-alpha+beta");
}
#[test]
fn test_display() {
let v = RerVersion::parse_from_string("1.2.3-alpha+beta").unwrap();
assert_eq!(v.to_string(), "1.2.3-alpha+beta");
}
#[test]
fn test_from_str() {
let v = RerVersion::parse_from_string("1.2.3").unwrap();
assert_eq!(v.0.tokens.len(), 3);
assert_eq!(v.0.seps.len(), 2);
let v = RerVersion::parse_from_string("1.2.3-alpha").unwrap();
assert_eq!(v.0.tokens.len(), 4);
assert_eq!(v.0.seps.len(), 3);
let v = RerVersion::parse_from_string("1.2.3-alpha+beta").unwrap();
assert_eq!(v.0.tokens.len(), 5);
assert_eq!(v.0.seps.len(), 4);
let v: RerVersion = "2.0.0_".try_into().unwrap();
assert_eq!(v.0.tokens[2], AlphanumericVersionToken::new("0_").unwrap());
assert_eq!(v.0.seps, vec!['.', '.']);
}
#[test]
fn test_order() {
let a = RerVersion::parse_from_string("1.2.3").unwrap();
let b = RerVersion::parse_from_string("1.2.4").unwrap();
assert!(a < b);
let a = RerVersion::parse_from_string("1.2.3").unwrap();
let b = RerVersion::parse_from_string("1.2.3-alpha").unwrap();
assert!(a < b);
let a = RerVersion::parse_from_string("2.0.0").unwrap();
let b = RerVersion::parse_from_string("2.0.0_").unwrap();
assert!(a < b);
}
#[test]
fn test_bump_rez_version() {
let a: RerVersion = "1.2.3".try_into().unwrap();
let b: RerVersion = "1.2.3_".try_into().unwrap();
assert_eq!(a.bump(), b);
}
#[test]
fn test_compare_subtoken() {
let a = SubToken::new("1");
let b = SubToken::new("2");
assert!(a < b);
let a = SubToken::new("1");
let b = SubToken::new("1");
assert!(a == b);
let a = SubToken::new("1");
let b = SubToken::new("1a");
assert!(a >= b);
let a = SubToken::new("a");
let b = SubToken::new("1");
assert!(a < b);
let a = SubToken::new("a");
let b = SubToken::new("a");
assert!(a == b);
let a = SubToken::new("a");
let b = SubToken::new("A");
assert!(a < b);
}
#[test]
fn test_alphanumeric_version_token_compare() {
let a = AlphanumericVersionToken::new("3").unwrap();
let b = AlphanumericVersionToken::new("4").unwrap();
assert!(a < b);
let a = AlphanumericVersionToken::new("01").unwrap();
let b = AlphanumericVersionToken::new("1").unwrap();
assert!(a < b);
let a = AlphanumericVersionToken::new("beta").unwrap();
let b = AlphanumericVersionToken::new("1").unwrap();
assert!(a < b);
let a = AlphanumericVersionToken::new("a").unwrap();
let b = AlphanumericVersionToken::new("A").unwrap();
assert!(a < b);
let a = AlphanumericVersionToken::new("alpha3").unwrap();
let b = AlphanumericVersionToken::new("alpha4").unwrap();
assert!(a < b);
let a = AlphanumericVersionToken::new("alpha").unwrap();
let b = AlphanumericVersionToken::new("alpha3").unwrap();
assert!(a < b);
let a = AlphanumericVersionToken::new("gamma33").unwrap();
let b = AlphanumericVersionToken::new("33gamma").unwrap();
assert!(a < b);
}