summaryrefslogtreecommitdiff
path: root/src/SMAPI.Toolkit/SemanticVersion.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI.Toolkit/SemanticVersion.cs')
-rw-r--r--src/SMAPI.Toolkit/SemanticVersion.cs72
1 files changed, 46 insertions, 26 deletions
diff --git a/src/SMAPI.Toolkit/SemanticVersion.cs b/src/SMAPI.Toolkit/SemanticVersion.cs
index 4955dcae..5ead6dc8 100644
--- a/src/SMAPI.Toolkit/SemanticVersion.cs
+++ b/src/SMAPI.Toolkit/SemanticVersion.cs
@@ -1,5 +1,6 @@
using System;
using System.Text.RegularExpressions;
+using StardewModdingAPI.Toolkit.Framework;
namespace StardewModdingAPI.Toolkit
{
@@ -9,6 +10,8 @@ namespace StardewModdingAPI.Toolkit
/// - short-form "x.y" versions are supported (equivalent to "x.y.0");
/// - hyphens are synonymous with dots in prerelease tags and build metadata (like "-unofficial.3-pathoschild");
/// - and "-unofficial" in prerelease tags is always lower-precedence (e.g. "1.0-beta" is newer than "1.0-unofficial").
+ ///
+ /// This optionally also supports four-part versions, a non-standard extension used by Stardew Valley on ported platforms to represent platform-specific patches to a ported version, represented as a fourth number in the version string.
/// </remarks>
public class SemanticVersion : ISemanticVersion
{
@@ -16,14 +19,7 @@ namespace StardewModdingAPI.Toolkit
** Fields
*********/
/// <summary>A regex pattern matching a valid prerelease or build metadata tag.</summary>
- internal const string TagPattern = @"(?>[a-z0-9]+[\-\.]?)+";
-
- /// <summary>A regex pattern matching a version within a larger string.</summary>
- internal const string UnboundedVersionPattern = @"(?>(?<major>0|[1-9]\d*))\.(?>(?<minor>0|[1-9]\d*))(?>(?:\.(?<patch>0|[1-9]\d*))?)(?:-(?<prerelease>" + SemanticVersion.TagPattern + "))?(?:\\+(?<buildmetadata>" + SemanticVersion.TagPattern + "))?";
-
- /// <summary>A regular expression matching a semantic version string.</summary>
- /// <remarks>This pattern is derived from the BNF documentation in the <a href="https://github.com/mojombo/semver">semver repo</a>, with deviations to support the Stardew Valley mod conventions (see remarks on <see cref="SemanticVersion"/>).</remarks>
- internal static readonly Regex Regex = new Regex($@"^{SemanticVersion.UnboundedVersionPattern}$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture);
+ private const string TagPattern = @"(?>[a-z0-9]+[\-\.]?)+";
/*********
@@ -38,6 +34,9 @@ namespace StardewModdingAPI.Toolkit
/// <summary>The patch version for backwards-compatible bug fixes.</summary>
public int PatchVersion { get; }
+ /// <summary>The platform release. This is a non-standard semver extension used by Stardew Valley on ported platforms to represent platform-specific patches to a ported version, represented as a fourth number in the version string.</summary>
+ public int PlatformRelease { get; }
+
/// <summary>An optional prerelease tag.</summary>
public string PrereleaseTag { get; }
@@ -52,13 +51,15 @@ namespace StardewModdingAPI.Toolkit
/// <param name="major">The major version incremented for major API changes.</param>
/// <param name="minor">The minor version incremented for backwards-compatible changes.</param>
/// <param name="patch">The patch version for backwards-compatible fixes.</param>
+ /// <param name="platformRelease">The platform-specific version (if applicable).</param>
/// <param name="prereleaseTag">An optional prerelease tag.</param>
/// <param name="buildMetadata">Optional build metadata. This is ignored when determining version precedence.</param>
- public SemanticVersion(int major, int minor, int patch, string prereleaseTag = null, string buildMetadata = null)
+ public SemanticVersion(int major, int minor, int patch, int platformRelease = 0, string prereleaseTag = null, string buildMetadata = null)
{
this.MajorVersion = major;
this.MinorVersion = minor;
this.PatchVersion = patch;
+ this.PlatformRelease = platformRelease;
this.PrereleaseTag = this.GetNormalizedTag(prereleaseTag);
this.BuildMetadata = this.GetNormalizedTag(buildMetadata);
@@ -82,23 +83,22 @@ namespace StardewModdingAPI.Toolkit
/// <summary>Construct an instance.</summary>
/// <param name="version">The semantic version string.</param>
+ /// <param name="allowNonStandard">Whether to recognize non-standard semver extensions.</param>
/// <exception cref="ArgumentNullException">The <paramref name="version"/> is null.</exception>
/// <exception cref="FormatException">The <paramref name="version"/> is not a valid semantic version.</exception>
- public SemanticVersion(string version)
+ public SemanticVersion(string version, bool allowNonStandard = false)
{
- // parse
if (version == null)
throw new ArgumentNullException(nameof(version), "The input version string can't be null.");
- var match = SemanticVersion.Regex.Match(version.Trim());
- if (!match.Success)
+ if (!SemanticVersionReader.TryParse(version, allowNonStandard, out int major, out int minor, out int patch, out int platformRelease, out string prereleaseTag, out string buildMetadata) || (!allowNonStandard && platformRelease != 0))
throw new FormatException($"The input '{version}' isn't a valid semantic version.");
- // initialize
- this.MajorVersion = int.Parse(match.Groups["major"].Value);
- this.MinorVersion = match.Groups["minor"].Success ? int.Parse(match.Groups["minor"].Value) : 0;
- this.PatchVersion = match.Groups["patch"].Success ? int.Parse(match.Groups["patch"].Value) : 0;
- this.PrereleaseTag = match.Groups["prerelease"].Success ? this.GetNormalizedTag(match.Groups["prerelease"].Value) : null;
- this.BuildMetadata = match.Groups["buildmetadata"].Success ? this.GetNormalizedTag(match.Groups["buildmetadata"].Value) : null;
+ this.MajorVersion = major;
+ this.MinorVersion = minor;
+ this.PatchVersion = patch;
+ this.PlatformRelease = platformRelease;
+ this.PrereleaseTag = prereleaseTag;
+ this.BuildMetadata = buildMetadata;
this.AssertValid();
}
@@ -110,7 +110,7 @@ namespace StardewModdingAPI.Toolkit
{
if (other == null)
throw new ArgumentNullException(nameof(other));
- return this.CompareTo(other.MajorVersion, other.MinorVersion, other.PatchVersion, other.PrereleaseTag);
+ return this.CompareTo(other.MajorVersion, other.MinorVersion, other.PatchVersion, (other as SemanticVersion)?.PlatformRelease ?? 0, other.PrereleaseTag);
}
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
@@ -139,7 +139,7 @@ namespace StardewModdingAPI.Toolkit
/// <exception cref="FormatException">The specified version is not a valid semantic version.</exception>
public bool IsOlderThan(string other)
{
- return this.IsOlderThan(new SemanticVersion(other));
+ return this.IsOlderThan(new SemanticVersion(other, allowNonStandard: true));
}
/// <summary>Get whether this version is newer than the specified version.</summary>
@@ -154,7 +154,7 @@ namespace StardewModdingAPI.Toolkit
/// <exception cref="FormatException">The specified version is not a valid semantic version.</exception>
public bool IsNewerThan(string other)
{
- return this.IsNewerThan(new SemanticVersion(other));
+ return this.IsNewerThan(new SemanticVersion(other, allowNonStandard: true));
}
/// <summary>Get whether this version is between two specified versions (inclusively).</summary>
@@ -171,13 +171,15 @@ namespace StardewModdingAPI.Toolkit
/// <exception cref="FormatException">One of the specified versions is not a valid semantic version.</exception>
public bool IsBetween(string min, string max)
{
- return this.IsBetween(new SemanticVersion(min), new SemanticVersion(max));
+ return this.IsBetween(new SemanticVersion(min, allowNonStandard: true), new SemanticVersion(max, allowNonStandard: true));
}
/// <summary>Get a string representation of the version.</summary>
public override string ToString()
{
string version = $"{this.MajorVersion}.{this.MinorVersion}.{this.PatchVersion}";
+ if (this.PlatformRelease != 0)
+ version += $".{this.PlatformRelease}";
if (this.PrereleaseTag != null)
version += $"-{this.PrereleaseTag}";
if (this.BuildMetadata != null)
@@ -185,15 +187,30 @@ namespace StardewModdingAPI.Toolkit
return version;
}
+ /// <summary>Whether the version uses non-standard extensions, like four-part game versions on some platforms.</summary>
+ public bool IsNonStandard()
+ {
+ return this.PlatformRelease != 0;
+ }
+
/// <summary>Parse a version string without throwing an exception if it fails.</summary>
/// <param name="version">The version string.</param>
/// <param name="parsed">The parsed representation.</param>
/// <returns>Returns whether parsing the version succeeded.</returns>
public static bool TryParse(string version, out ISemanticVersion parsed)
{
+ return SemanticVersion.TryParseNonStandard(version, out parsed) && !parsed.IsNonStandard();
+ }
+
+ /// <summary>Parse a version string without throwing an exception if it fails, including support for non-standard extensions like <see cref="IPlatformSpecificVersion"/>.</summary>
+ /// <param name="version">The version string.</param>
+ /// <param name="parsed">The parsed representation.</param>
+ /// <returns>Returns whether parsing the version succeeded.</returns>
+ public static bool TryParseNonStandard(string version, out ISemanticVersion parsed)
+ {
try
{
- parsed = new SemanticVersion(version);
+ parsed = new SemanticVersion(version, true);
return true;
}
catch
@@ -219,8 +236,9 @@ namespace StardewModdingAPI.Toolkit
/// <param name="otherMajor">The major version to compare with this instance.</param>
/// <param name="otherMinor">The minor version to compare with this instance.</param>
/// <param name="otherPatch">The patch version to compare with this instance.</param>
+ /// <param name="otherPlatformRelease">The non-standard platform release to compare with this instance.</param>
/// <param name="otherTag">The prerelease tag to compare with this instance.</param>
- private int CompareTo(int otherMajor, int otherMinor, int otherPatch, string otherTag)
+ private int CompareTo(int otherMajor, int otherMinor, int otherPatch, int otherPlatformRelease, string otherTag)
{
const int same = 0;
const int curNewer = 1;
@@ -233,6 +251,8 @@ namespace StardewModdingAPI.Toolkit
return this.MinorVersion.CompareTo(otherMinor);
if (this.PatchVersion != otherPatch)
return this.PatchVersion.CompareTo(otherPatch);
+ if (this.PlatformRelease != otherPlatformRelease)
+ return this.PlatformRelease.CompareTo(otherPlatformRelease);
if (this.PrereleaseTag == otherTag)
return same;
@@ -274,7 +294,7 @@ namespace StardewModdingAPI.Toolkit
}
// fallback (this should never happen)
- return string.Compare(this.ToString(), new SemanticVersion(otherMajor, otherMinor, otherPatch, otherTag).ToString(), StringComparison.InvariantCultureIgnoreCase);
+ return string.Compare(this.ToString(), new SemanticVersion(otherMajor, otherMinor, otherPatch, otherPlatformRelease, otherTag).ToString(), StringComparison.InvariantCultureIgnoreCase);
}
/// <summary>Assert that the current version is valid.</summary>