Skip to content

Instantly share code, notes, and snippets.

@terokorp
Created May 28, 2023 04:48
Show Gist options
  • Save terokorp/14004f3b8d635e6ee18cf2f8aa52379a to your computer and use it in GitHub Desktop.
Save terokorp/14004f3b8d635e6ee18cf2f8aa52379a to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
internal class SemVer : IComparable<SemVer>
{
// https://semver.org/spec/v2.0.0.html
public int Major { get; set; }
public int Minor { get; set; }
public int Patch { get; set; }
public VersionIdentifier PreRelease = new();
public VersionIdentifier BuildIdentifier = new();
public static SemVer Parse(string input)
{
if (TryParse(input, out SemVer result))
return result;
return null;
}
public static bool TryParse(string input, out SemVer result)
{
input = input.Trim();
Regex regex = new Regex(@"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$");
var matches = regex.Matches(input);
if (matches.Count == 1 && matches[0].Success)
{
result = new SemVer();
for (int i = 1; i < matches[0].Groups.Count; i++)
{
if (matches[0].Groups[i].Success)
result.SetIndex(i - 1, matches[0].Groups[i].Value);
}
return true;
}
result = null;
return false;
}
private void SetIndex(int i, string value)
{
switch (i)
{
case 0:
Major = int.Parse(value);
break;
case 1:
Minor = int.Parse(value);
break;
case 2:
Patch = int.Parse(value);
break;
case 3:
PreRelease.Parse(value);
break;
case 4:
BuildIdentifier.Parse(value);
break;
default:
throw new Exception("Invalid version number");
}
}
public override string ToString() => ToString(5);
public string ToString(int v)
{
string result = "";
result += Major;
if (v <= 1) return result;
result += ".";
result += Minor;
if (v < 2) return result;
result += ".";
result += Patch;
if (v <= 3) return result;
if (PreRelease.Count > 0)
{
result += "-";
result += PreRelease.ToDelimitedString();
}
if (v <= 4) return result;
if (BuildIdentifier.Count > 0)
{
result += "+";
result += BuildIdentifier.ToDelimitedString();
}
if (v <= 5) return result;
return result;
}
public int CompareTo(SemVer other)
{
if (Major > other.Major)
return 1;
if (Major < other.Major)
return -1;
if (Minor > other.Minor)
return 1;
if (Minor < other.Minor)
return -1;
if (Patch > other.Patch)
return 1;
if (Patch < other.Patch)
return -1;
// When major, minor, and patch are equal, a pre-release version has lower precedence than a normal version:
// Example: 1.0.0 - alpha < 1.0.0.
if (PreRelease.Count == 0 && other.PreRelease.Count > 0)
return 1;
if (PreRelease.Count > 0 && other.PreRelease.Count == 0)
return -1;
int diff = PreRelease.CompareTo(other.PreRelease);
if (diff != 0)
return diff;
if (BuildIdentifier.Count == 0 && other.BuildIdentifier.Count > 0)
return -1;
if (BuildIdentifier.Count > 0 && other.BuildIdentifier.Count == 0)
return 1;
diff = BuildIdentifier.CompareTo(other.BuildIdentifier);
if (diff != 0)
return diff;
return 0; // Cant find differences
}
public class VersionIdentifier : List<string>, IComparable<VersionIdentifier>
{
internal void Parse(string value)
{
foreach (string i in value.Split("."))
Add(i);
}
public string ToDelimitedString()
{
return string.Join(".", this);
}
public int CompareTo(VersionIdentifier other)
{
int count = Count < other.Count ? Count : other.Count;
Regex onlyNumbers = new Regex(@"^([0-9]+)$");
for (int i = 0; i < count; i++)
{
if (onlyNumbers.IsMatch(other[i]))
{
if (onlyNumbers.IsMatch(this[i]))
{
// Compare numbers
int diff = int.Parse(this[i]) - int.Parse(other[i]);
if (diff != 0)
return diff;
}
else
return 1;
}
else
{
if (!onlyNumbers.IsMatch(this[i]))
{
// Compare ascii, not sure is this right
int diff = this[i].CompareTo(other[i]);
if (diff != 0)
return diff;
}
else
return -1;
}
}
return 0;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment