Skip to content

Commit

Permalink
Add new sub crate unity-version
Browse files Browse the repository at this point in the history
Description
===========

This is a new implementation of the Unity version struct and parsing
methods to represent a Unity version inside uvm. It will be used
with future patches. The old representation tried to combine a normal unity version
with an optional revision hash. I constructed this as a seperate type. Conversion
methods are not yet available because I need to see how I will actually use both
types.
  • Loading branch information
Larusso committed Nov 18, 2024
1 parent 70f7eb0 commit 91e3692
Show file tree
Hide file tree
Showing 9 changed files with 631 additions and 36 deletions.
220 changes: 186 additions & 34 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]

resolver = "1"
members = [
"uvm_core",
"uvm_cli",
Expand All @@ -8,4 +8,4 @@ members = [
"uvm_move_dir",
"commands/*",
"install/*"
]
, "unity-version"]
17 changes: 17 additions & 0 deletions unity-version/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "unity-version"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
derive_more = { version = "0.99.17", default-features = false, features = ["deref", "deref_mut", "as_ref", "as_mut", "display"] }
nom = "7.1.3"
semver = "1.0.22"
serde = { version = "1.0.197", features = ["derive"] }
thiserror = "1.0.57"

[dev-dependencies]
proptest = "1.4.0"
quickcheck = "1.0.3"
7 changes: 7 additions & 0 deletions unity-version/proptest-regressions/version/mod.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc da6c38f66a9854bc766759a3e02b8fd53c14111d713e836ebe5375a8e1c1bda1 # shrinks to s = "0.0.0a20000000000000000000"
7 changes: 7 additions & 0 deletions unity-version/proptest-regressions/version/release_type.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 14d49815deb3a87ab8c16b04563519beb30569ae6f03e422e0beac7e3bdfadf3 # shrinks to s = "alpha"
5 changes: 5 additions & 0 deletions unity-version/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod version;
pub use version::Version;
pub use version::ReleaseType;


219 changes: 219 additions & 0 deletions unity-version/src/version/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
use derive_more::Display;
use std::{cmp::Ordering, str::FromStr};

use nom::{
branch::alt,
character::complete::{char, digit1},
combinator::map_res,
error::{context, convert_error, VerboseError},
sequence::tuple,
IResult,
};

mod release_type;
mod revision_hash;
pub use release_type::ReleaseType;
pub use revision_hash::RevisionHash;

#[derive(Eq, Debug, Clone, Hash, PartialOrd, Display)]
#[display(fmt = "{}{}{}", base, release_type, revision)]
pub struct Version {
base: semver::Version,
release_type: ReleaseType,
revision: u64,
}

impl Ord for Version {
fn cmp(&self, other: &Version) -> Ordering {
self.base
.cmp(&other.base)
.then(self.release_type.cmp(&other.release_type))
.then(self.revision.cmp(&other.revision))
}
}

impl PartialEq for Version {
fn eq(&self, other: &Self) -> bool {
self.base == other.base
&& self.release_type == other.release_type
&& self.revision == other.revision
}
}

impl AsRef<Version> for Version {
fn as_ref(&self) -> &Self {
self
}
}

impl AsMut<Version> for Version {
fn as_mut(&mut self) -> &mut Self {
self
}
}

impl FromStr for Version {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match parse_version(s) {
Ok((_, version)) => Ok(version),
Err(err) => {
let verbose_error = match err {
nom::Err::Error(e) | nom::Err::Failure(e) => e,
_ => VerboseError {
errors: vec![(s, nom::error::VerboseErrorKind::Context("unknown error"))],
},
};
Err(convert_error(s, verbose_error))
}
}
}
}

impl Version {
pub fn new(
major: u64,
minor: u64,
patch: u64,
release_type: ReleaseType,
revision: u64,
) -> Version {
let base = semver::Version::new(major, minor, patch);
Version {
base,
release_type,
revision,
}
}

pub fn release_type(&self) -> ReleaseType {
self.release_type
}

pub fn major(&self) -> u64 {
self.base.major
}

pub fn minor(&self) -> u64 {
self.base.minor
}

pub fn patch(&self) -> u64 {
self.base.patch
}

pub fn revision(&self) -> u64 {
self.revision
}
}

#[derive(Eq, Debug, Clone, Hash, Display)]
#[display(fmt = "{} ({})", version, revision)]
pub struct CompleteVersion{
version: Version,
revision: RevisionHash
}


impl PartialEq for CompleteVersion {
fn eq(&self, other: &Self) -> bool {
self.version == other.version
}
}

impl PartialOrd for CompleteVersion {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.version.partial_cmp(&other.version)
}
}


fn parse_release_type(input: &str) -> IResult<&str, ReleaseType, VerboseError<&str>> {
context(
"release type",
map_res(alt((char('f'), char('b'), char('a'), char('p'))), |c| {
ReleaseType::try_from(c)
}),
)(input)
}

fn parse_version(input: &str) -> IResult<&str, Version, VerboseError<&str>> {
context(
"version",
tuple((
context("major version", map_res(digit1, |s: &str| s.parse::<u64>())),
char('.'),
context("minor version", map_res(digit1, |s: &str| s.parse::<u64>())),
char('.'),
context("patch version", map_res(digit1, |s: &str| s.parse::<u64>())),
context("release type", parse_release_type),
context("revision", map_res(digit1, |s: &str| s.parse::<u64>())),
)),
)(input)
.map(
|(next_input, (major, _, minor, _, patch, release_type, revision))| {
let base = semver::Version::new(major, minor, patch);
(
next_input,
Version {
base,
release_type,
revision,
},
)
},
)
}

#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;

#[test]
fn parse_version_string_with_valid_input() {
let version_string = "1.2.3f4";
let version = Version::from_str(version_string);
assert!(version.is_ok(), "valid input returns a version")
}

#[test]
fn splits_version_string_into_components() {
let version_string = "11.2.3f4";
let version = Version::from_str(version_string).unwrap();

assert!(version.base.major == 11, "parse correct major component");
assert!(version.base.minor == 2, "parse correct minor component");
assert!(version.base.patch == 3, "parse correct patch component");

assert_eq!(version.release_type, ReleaseType::Final);
assert!(version.revision == 4, "parse correct revision component");
}

proptest! {
#[test]
fn from_str_does_not_crash(s in "\\PC*") {
let _v = Version::from_str(&s);
}

#[test]
fn from_str_supports_all_valid_cases(
major in 0u64..=u64::MAX,
minor in 0u64..=u64::MAX,
patch in 0u64..=u64::MAX,
release_type in prop_oneof!["f", "p", "b", "a"],
revision in 0u64..=u64::MAX,
) {
let version_string = format!("{}.{}.{}{}{}", major, minor, patch, release_type, revision);
let version = Version::from_str(&version_string).unwrap();

assert!(version.base.major == major, "parse correct major component");
assert!(version.base.minor == minor, "parse correct minor component");
assert!(version.base.patch == patch, "parse correct patch component");

assert_eq!(version.release_type, ReleaseType::from_str(&release_type).unwrap());
assert!(version.revision == revision, "parse correct revision component");
}
}
}
Loading

0 comments on commit 91e3692

Please sign in to comment.