diff options
-rw-r--r-- | docs/release-notes.md | 1 | ||||
-rw-r--r-- | src/SMAPI.Tests/Utilities/SemanticVersionTests.cs | 1 | ||||
-rw-r--r-- | src/StardewModdingAPI.Toolkit/SemanticVersion.cs | 22 |
3 files changed, 16 insertions, 8 deletions
diff --git a/docs/release-notes.md b/docs/release-notes.md index 8573c175..beba403a 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -27,6 +27,7 @@ * Added `IContentPack.WriteJsonFile` method. * Added IntelliSense documentation for the non-developers version of SMAPI. * Mods are no longer prevented from loading a PNG while the game is drawing. + * When comparing mod versions, SMAPI now considers `-unofficial` to be lower-precedence than any other value (e.g. `1.0-beta` is now considered newer than `1.0-unofficial` regardless of normal sorting). * Fixed `IContentPack.ReadJsonFile` allowing non-relative paths. * Fixed trace logs not showing path for invalid mods. * Fixed 'no update keys' warning not shown for mods with only invalid update keys. diff --git a/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs b/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs index 35d74b60..1782308b 100644 --- a/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs +++ b/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs @@ -127,6 +127,7 @@ namespace StardewModdingAPI.Tests.Utilities [TestCase("1.0-beta.1", "1.0-beta.2", ExpectedResult = -1)] [TestCase("1.0-beta.2", "1.0-beta.10", ExpectedResult = -1)] [TestCase("1.0-beta-2", "1.0-beta-10", ExpectedResult = -1)] + [TestCase("1.0-unofficial.1", "1.0-beta.1", ExpectedResult = -1)] // special case: 'unofficial' has lower priority than official releases // more than [TestCase("0.5.8", "0.5.7", ExpectedResult = 1)] diff --git a/src/StardewModdingAPI.Toolkit/SemanticVersion.cs b/src/StardewModdingAPI.Toolkit/SemanticVersion.cs index 156d58ce..2a78d2f0 100644 --- a/src/StardewModdingAPI.Toolkit/SemanticVersion.cs +++ b/src/StardewModdingAPI.Toolkit/SemanticVersion.cs @@ -4,7 +4,13 @@ using System.Text.RegularExpressions; namespace StardewModdingAPI.Toolkit { /// <summary>A semantic version with an optional release tag.</summary> - /// <remarks>The implementation is defined by Semantic Version 2.0 (http://semver.org/).</remarks> + /// <remarks> + /// The implementation is defined by Semantic Version 2.0 (http://semver.org/), with a few deviations: + /// - short-form "x.y" versions are supported (equivalent to "x.y.0"); + /// - hyphens are synonymous with dots in prerelease tags (like "-unofficial.3-pathoschild"); + /// - +build suffixes are not supported; + /// - and "-unofficial" in prerelease tags is always lower-precedence (e.g. "1.0-beta" is newer than "1.0-unofficial"). + /// </remarks> public class SemanticVersion : ISemanticVersion { /********* @@ -17,13 +23,7 @@ namespace StardewModdingAPI.Toolkit internal const string UnboundedVersionPattern = @"(?>(?<major>0|[1-9]\d*))\.(?>(?<minor>0|[1-9]\d*))(?>(?:\.(?<patch>0|[1-9]\d*))?)(?:-(?<prerelease>" + 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 three important deviations intended to support Stardew Valley mod conventions: - /// - allows short-form "x.y" versions; - /// - allows hyphens in prerelease tags as synonyms for dots (like "-unofficial-update.3"); - /// - doesn't allow '+build' suffixes. - /// </remarks> + /// <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); @@ -255,6 +255,12 @@ namespace StardewModdingAPI.Toolkit // compare if different if (curParts[i] != otherParts[i]) { + // unofficial is always lower-precedence + if (otherParts[i].Equals("unofficial", StringComparison.InvariantCultureIgnoreCase)) + return curNewer; + if (curParts[i].Equals("unofficial", StringComparison.InvariantCultureIgnoreCase)) + return curOlder; + // compare numerically if possible { if (int.TryParse(curParts[i], out int curNum) && int.TryParse(otherParts[i], out int otherNum)) |