Skip to content

Commit

Permalink
#37 Add a domain model for semantic versions
Browse files Browse the repository at this point in the history
- SemVer has fields for PreRelease and BuildMetadata suffixes
- SemVer has parsing/writing functions
- add respective flat fields to ValidationPackageMetadata
- add respective static methods to ValidationPackageMetadata and ValidationPackageIndex
- added tests
  • Loading branch information
kMutagene committed Jun 21, 2024
1 parent 5313813 commit e200b5e
Show file tree
Hide file tree
Showing 10 changed files with 630 additions and 101 deletions.
2 changes: 1 addition & 1 deletion src/AVPRIndex/AVPRIndex.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<RepositoryType>git</RepositoryType>
<PackageReleaseNotes>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/RELEASE_NOTES.md"))</PackageReleaseNotes>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageVersion>0.1.3</PackageVersion>
<PackageVersion>0.2.0</PackageVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
143 changes: 130 additions & 13 deletions src/AVPRIndex/Domain.fs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,75 @@ module Domain =

let jsonSerializerOptions = JsonSerializerOptions(WriteIndented = true)

type SemVer() =
member val Major = -1 with get,set
member val Minor = -1 with get,set
member val Patch = -1 with get,set
member val PreRelease = "" with get,set
member val BuildMetadata = "" with get,set

override this.GetHashCode() =
hash (
this.Major,
this.Minor,
this.Patch,
this.PreRelease,
this.BuildMetadata
)

override this.Equals(other) =
match other with
| :? SemVer as s ->
(
this.Major,
this.Minor,
this.Patch,
this.PreRelease,
this.BuildMetadata
) = (
s.Major,
s.Minor,
s.Patch,
s.PreRelease,
s.BuildMetadata
)
| _ -> false

static member create (
major: int,
minor: int,
patch: int,
?PreRelease: string,
?BuildMetadata: string
) =
let tmp = SemVer(
Major = major,
Minor = minor,
Patch = patch
)
PreRelease |> Option.iter (fun x -> tmp.PreRelease <- x)
BuildMetadata |> Option.iter (fun x -> tmp.BuildMetadata <- x)
tmp

static member tryParse (version: string) =
match version |> Globals.SEMVER_REGEX.Match |> fun m -> m.Success, m.Groups with
| true, groups ->
let major = groups.["major"].Value |> int
let minor = groups.["minor"].Value |> int
let patch = groups.["patch"].Value |> int
let preRelease = groups.["prerelease"].Value
let buildMetadata = groups.["buildmetadata"].Value
Some(SemVer.create(major, minor, patch, preRelease, buildMetadata))
| _ -> None

static member toString (semVer: SemVer) =
match (semVer.PreRelease, semVer.BuildMetadata) with
| (pr, bm) when pr <> "" && bm <> "" -> $"{semVer.Major}.{semVer.Minor}.{semVer.Patch}-{pr}+{bm}"
| (pr, bm) when pr <> "" && bm = "" -> $"{semVer.Major}.{semVer.Minor}.{semVer.Patch}-{pr}"
| (pr, bm) when pr = "" && bm <> "" -> $"{semVer.Major}.{semVer.Minor}.{semVer.Patch}+{bm}"
| _ -> $"{semVer.Major}.{semVer.Minor}.{semVer.Patch}"


type Author() =
member val FullName = "" with get,set
member val Email = "" with get,set
Expand Down Expand Up @@ -43,16 +112,16 @@ module Domain =

static member create (
fullName: string,
?email: string,
?affiliation: string,
?affiliationLink: string
?Email: string,
?Affiliation: string,
?AffiliationLink: string
) =
let tmp = Author(
FullName = fullName
)
email |> Option.iter (fun x -> tmp.Email <- x)
affiliation |> Option.iter (fun x -> tmp.Affiliation <- x)
affiliationLink |> Option.iter (fun x -> tmp.AffiliationLink <- x)
Email |> Option.iter (fun x -> tmp.Email <- x)
Affiliation |> Option.iter (fun x -> tmp.Affiliation <- x)
AffiliationLink |> Option.iter (fun x -> tmp.AffiliationLink <- x)

tmp

Expand Down Expand Up @@ -85,12 +154,12 @@ module Domain =

static member create (
name: string,
?termSourceRef: string,
?termAccessionNumber: string
?TermSourceRef: string,
?TermAccessionNumber: string
) =
let tmp = new OntologyAnnotation(Name = name)
termSourceRef |> Option.iter (fun x -> tmp.TermSourceREF <- x)
termAccessionNumber |> Option.iter (fun x -> tmp.TermAccessionNumber <- x)
TermSourceRef |> Option.iter (fun x -> tmp.TermSourceREF <- x)
TermAccessionNumber |> Option.iter (fun x -> tmp.TermAccessionNumber <- x)
tmp

type ValidationPackageMetadata() =
Expand All @@ -101,6 +170,8 @@ module Domain =
member val MajorVersion = -1 with get,set
member val MinorVersion = -1 with get,set
member val PatchVersion = -1 with get,set
member val PreReleaseVersionSuffix = "" with get,set
member val BuildMetadataVersionSuffix = "" with get,set
// optional fields
member val Publish = false with get,set
member val Authors: Author [] = Array.empty<Author> with get,set
Expand All @@ -116,6 +187,8 @@ module Domain =
this.MajorVersion,
this.MinorVersion,
this.PatchVersion,
this.PreReleaseVersionSuffix,
this.BuildMetadataVersionSuffix,
this.Publish,
this.Authors,
this.Tags,
Expand All @@ -133,6 +206,8 @@ module Domain =
this.MajorVersion,
this.MinorVersion,
this.PatchVersion,
this.PreReleaseVersionSuffix,
this.BuildMetadataVersionSuffix,
this.Publish,
this.Authors,
this.Tags,
Expand All @@ -144,7 +219,9 @@ module Domain =
vpm.Description,
vpm.MajorVersion,
vpm.MinorVersion,
vpm.PatchVersion,
this.PatchVersion,
this.PreReleaseVersionSuffix,
this.BuildMetadataVersionSuffix,
vpm.Publish,
vpm.Authors,
vpm.Tags,
Expand All @@ -160,6 +237,8 @@ module Domain =
majorVersion: int,
minorVersion: int,
patchVersion: int,
?PreReleaseVersionSuffix: string,
?BuildMetadataVersionSuffix: string,
?Publish: bool,
?Authors: Author [],
?Tags: OntologyAnnotation [],
Expand All @@ -175,6 +254,8 @@ module Domain =
PatchVersion = patchVersion
)

PreReleaseVersionSuffix |> Option.iter (fun x -> tmp.PreReleaseVersionSuffix <- x)
BuildMetadataVersionSuffix |> Option.iter (fun x -> tmp.BuildMetadataVersionSuffix <- x)
Publish |> Option.iter (fun x -> tmp.Publish <- x)
Authors |> Option.iter (fun x -> tmp.Authors <- x)
Tags |> Option.iter (fun x -> tmp.Tags <- x)
Expand All @@ -183,7 +264,33 @@ module Domain =

tmp

static member getSemanticVersionString(m: ValidationPackageMetadata) = $"{m.MajorVersion}.{m.MinorVersion}.{m.PatchVersion}"
static member tryGetSemanticVersion(m: ValidationPackageMetadata) =
SemVer.create(
m.MajorVersion,
m.MinorVersion,
m.PatchVersion,
m.PreReleaseVersionSuffix,
m.BuildMetadataVersionSuffix
)
|> SemVer.toString
|> SemVer.tryParse // there is no buit-in validation on the constructor/create function, so we'll take a detour via parsing roundtrip using the regex

static member getSemanticVersion(m: ValidationPackageMetadata) =
m
|> ValidationPackageMetadata.tryGetSemanticVersion
|> Option.get

static member tryGetSemanticVersionString(m: ValidationPackageMetadata) =
m
|> ValidationPackageMetadata.tryGetSemanticVersion
|> Option.map SemVer.toString

static member getSemanticVersionString(m: ValidationPackageMetadata) =
m
|> ValidationPackageMetadata.tryGetSemanticVersion
|> Option.map SemVer.toString
|> Option.get


type ValidationPackageIndex =
{
Expand Down Expand Up @@ -240,7 +347,17 @@ module Domain =
printfn $"Indexed Package info:{System.Environment.NewLine}{json}"
printfn ""

static member getSemanticVersionString(i: ValidationPackageIndex) = $"{i.Metadata.MajorVersion}.{i.Metadata.MinorVersion}.{i.Metadata.PatchVersion}";
static member tryGetSemanticVersion(i: ValidationPackageIndex) =
i.Metadata |> ValidationPackageMetadata.tryGetSemanticVersion

static member getSemanticVersion(i: ValidationPackageIndex) =
i.Metadata |> ValidationPackageMetadata.getSemanticVersion

static member tryGetSemanticVersionString(i: ValidationPackageIndex) =
i.Metadata |> ValidationPackageMetadata.tryGetSemanticVersionString

static member getSemanticVersionString(i: ValidationPackageIndex) =
i.Metadata |> ValidationPackageMetadata.getSemanticVersionString

member this.PrettyPrint() =
$" {this.Metadata.Name} @ version {this.Metadata.MajorVersion}.{this.Metadata.MinorVersion}.{this.Metadata.PatchVersion}{System.Environment.NewLine}{_.Metadata.Description}{System.Environment.NewLine}Last Updated: {this.LastUpdated}{System.Environment.NewLine}"
10 changes: 9 additions & 1 deletion src/AVPRIndex/Globals.fs
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
namespace AVPRIndex

open System.Text
open System.Text.RegularExpressions

module Globals =

let [<Literal>] STAGING_AREA_RELATIVE_PATH = "StagingArea"
let [<Literal>] STAGING_AREA_RELATIVE_PATH = "StagingArea"

// https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
let [<Literal>] SEMVER_REGEX_PATTERN = @"^(?<major>0|[1-9]\d*)\.(?<minor>0|[1-9]\d*)\.(?<patch>0|[1-9]\d*)(?:-(?<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-]*))*))?(?:\+(?<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"

let SEMVER_REGEX = new Regex(SEMVER_REGEX_PATTERN)
1 change: 0 additions & 1 deletion src/AVPRIndex/MD5Hash.fs
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,3 @@ type Hash =
path
|> File.ReadAllText
|> Hash.hashString

6 changes: 6 additions & 0 deletions src/AVPRIndex/RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## v0.2.0

- Add `SemVer` type model for a full semantic version representation including PreRelease and Build metadata with parsing and formatting functions
- Improve functions for getting semantic version (strings) from `ValidationPackageMetadata` and `ValidationPackageIndex` types based on new `SemVer` type
- - Unify capitalization in Domain create functions

## v0.1.3

Add `BinaryContent` module to unify package content handling across downstream libraries
Expand Down
Loading

0 comments on commit e200b5e

Please sign in to comment.