summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <github@jplamondonw.com>2016-12-18 15:37:23 -0500
committerJesse Plamondon-Willard <github@jplamondonw.com>2016-12-18 15:37:23 -0500
commit23988a3c33a7a1616c2d36a2c4b7e3a2d06f4216 (patch)
tree42b66e7aaac4cdb5fcb1723c2b21d2f2a92eb4cd /src
parent487ae1dce92a410984a7c13bf0f30bdd0d878aea (diff)
downloadSMAPI-23988a3c33a7a1616c2d36a2c4b7e3a2d06f4216.tar.gz
SMAPI-23988a3c33a7a1616c2d36a2c4b7e3a2d06f4216.tar.bz2
SMAPI-23988a3c33a7a1616c2d36a2c4b7e3a2d06f4216.zip
migrate manifest & version to interfaces with backwards compatibility (#197)
Diffstat (limited to 'src')
-rw-r--r--src/StardewModdingAPI/Constants.cs8
-rw-r--r--src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs2
-rw-r--r--src/StardewModdingAPI/Framework/ModAssemblyLoader.cs4
-rw-r--r--src/StardewModdingAPI/IManifest.cs27
-rw-r--r--src/StardewModdingAPI/ISemanticVersion.cs35
-rw-r--r--src/StardewModdingAPI/Manifest.cs60
-rw-r--r--src/StardewModdingAPI/Mod.cs15
-rw-r--r--src/StardewModdingAPI/Program.cs22
-rw-r--r--src/StardewModdingAPI/SemanticVersion.cs130
-rw-r--r--src/StardewModdingAPI/StardewModdingAPI.csproj3
-rw-r--r--src/StardewModdingAPI/Version.cs118
11 files changed, 311 insertions, 113 deletions
diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs
index f5b9e70a..57a89e76 100644
--- a/src/StardewModdingAPI/Constants.cs
+++ b/src/StardewModdingAPI/Constants.cs
@@ -26,7 +26,11 @@ namespace StardewModdingAPI
** Accessors
*********/
/// <summary>SMAPI's current semantic version.</summary>
- public static readonly Version Version = new Version(1, 4, 0, null);
+ [Obsolete("Use " + nameof(Constants) + "." + nameof(ApiVersion))]
+ public static readonly Version Version = (Version)Constants.ApiVersion;
+
+ /// <summary>SMAPI's current semantic version.</summary>
+ public static ISemanticVersion ApiVersion => new Version(1, 4, 0, null, suppressDeprecationWarning: true);
/// <summary>The minimum supported version of Stardew Valley.</summary>
public const string MinimumGameVersion = "1.1";
@@ -56,7 +60,7 @@ namespace StardewModdingAPI
public static string ExecutionPath => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
/// <summary>The title of the SMAPI console window.</summary>
- public static string ConsoleTitle => $"Stardew Modding API Console - Version {Constants.Version} - Mods Loaded: {Program.ModsLoaded}";
+ public static string ConsoleTitle => $"Stardew Modding API Console - Version {Constants.ApiVersion} - Mods Loaded: {Program.ModsLoaded}";
/// <summary>The directory path in which error logs should be stored.</summary>
public static string LogDir => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley", "ErrorLogs");
diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs
index 3dfbc78c..a747eaa8 100644
--- a/src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs
+++ b/src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs
@@ -36,7 +36,7 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting
/// <param name="paths">The paths for the cached assembly.</param>
/// <param name="hash">The MD5 hash of the original assembly.</param>
/// <param name="currentVersion">The current SMAPI version.</param>
- public bool IsUpToDate(CachePaths paths, string hash, Version currentVersion)
+ public bool IsUpToDate(CachePaths paths, string hash, ISemanticVersion currentVersion)
{
return hash == this.Hash
&& this.ApiVersion == currentVersion.ToString()
diff --git a/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs b/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs
index 1ceb8ad2..a2c4ac23 100644
--- a/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs
+++ b/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs
@@ -58,7 +58,7 @@ namespace StardewModdingAPI.Framework
CachePaths cachePaths = this.GetCachePaths(assemblyPath);
{
CacheEntry cacheEntry = File.Exists(cachePaths.Metadata) ? JsonConvert.DeserializeObject<CacheEntry>(File.ReadAllText(cachePaths.Metadata)) : null;
- if (cacheEntry != null && cacheEntry.IsUpToDate(cachePaths, hash, Constants.Version))
+ if (cacheEntry != null && cacheEntry.IsUpToDate(cachePaths, hash, Constants.ApiVersion))
return new RewriteResult(assemblyPath, cachePaths, assemblyBytes, cacheEntry.Hash, cacheEntry.UseCachedAssembly, isNewerThanCache: false); // no rewrite needed
}
this.Monitor.Log($"Preprocessing {Path.GetFileName(assemblyPath)} for compatibility...", LogLevel.Trace);
@@ -99,7 +99,7 @@ namespace StardewModdingAPI.Framework
// cache all results
foreach (RewriteResult result in results)
{
- CacheEntry cacheEntry = new CacheEntry(result.Hash, Constants.Version.ToString(), forceCacheAssemblies || result.UseCachedAssembly);
+ CacheEntry cacheEntry = new CacheEntry(result.Hash, Constants.ApiVersion.ToString(), forceCacheAssemblies || result.UseCachedAssembly);
File.WriteAllText(result.CachePaths.Metadata, JsonConvert.SerializeObject(cacheEntry));
if (forceCacheAssemblies || result.UseCachedAssembly)
File.WriteAllBytes(result.CachePaths.Assembly, result.AssemblyBytes);
diff --git a/src/StardewModdingAPI/IManifest.cs b/src/StardewModdingAPI/IManifest.cs
new file mode 100644
index 00000000..3e4b7513
--- /dev/null
+++ b/src/StardewModdingAPI/IManifest.cs
@@ -0,0 +1,27 @@
+namespace StardewModdingAPI
+{
+ /// <summary>A manifest which describes a mod for SMAPI.</summary>
+ public interface IManifest
+ {
+ /// <summary>The mod name.</summary>
+ string Name { get; set; }
+
+ /// <summary>A brief description of the mod.</summary>
+ string Description { get; set; }
+
+ /// <summary>The mod author's name.</summary>
+ string Author { get; }
+
+ /// <summary>The mod version.</summary>
+ ISemanticVersion Version { get; set; }
+
+ /// <summary>The minimum SMAPI version required by this mod, if any.</summary>
+ string MinimumApiVersion { get; set; }
+
+ /// <summary>The unique mod ID.</summary>
+ string UniqueID { get; set; }
+
+ /// <summary>The name of the DLL in the directory that has the <see cref="Mod.Entry"/> method.</summary>
+ string EntryDll { get; set; }
+ }
+} \ No newline at end of file
diff --git a/src/StardewModdingAPI/ISemanticVersion.cs b/src/StardewModdingAPI/ISemanticVersion.cs
new file mode 100644
index 00000000..f50752fe
--- /dev/null
+++ b/src/StardewModdingAPI/ISemanticVersion.cs
@@ -0,0 +1,35 @@
+using System;
+
+namespace StardewModdingAPI
+{
+ /// <summary>A semantic version with an optional release tag.</summary>
+ public interface ISemanticVersion : IComparable<ISemanticVersion>
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The major version incremented for major API changes.</summary>
+ int MajorVersion { get; }
+
+ /// <summary>The minor version incremented for backwards-compatible changes.</summary>
+ int MinorVersion { get; }
+
+ /// <summary>The patch version for backwards-compatible bug fixes.</summary>
+ int PatchVersion { get; }
+
+ /// <summary>An optional build tag.</summary>
+ string Build { get; }
+
+
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>Get whether this version is older than the specified version.</summary>
+ /// <param name="other">The version to compare with this instance.</param>
+ bool IsOlderThan(ISemanticVersion other);
+
+ /// <summary>Get whether this version is newer than the specified version.</summary>
+ /// <param name="other">The version to compare with this instance.</param>
+ bool IsNewerThan(ISemanticVersion other);
+ }
+} \ No newline at end of file
diff --git a/src/StardewModdingAPI/Manifest.cs b/src/StardewModdingAPI/Manifest.cs
index bfc66c43..981ff023 100644
--- a/src/StardewModdingAPI/Manifest.cs
+++ b/src/StardewModdingAPI/Manifest.cs
@@ -3,21 +3,52 @@ using Newtonsoft.Json;
namespace StardewModdingAPI
{
+ /// <summary>Wraps <see cref="Manifest"/> so it can implement <see cref="IManifest"/> without breaking backwards compatibility.</summary>
+ [Obsolete("Use " + nameof(IManifest) + " or " + nameof(Mod) + "." + nameof(Mod.ModManifest) + " instead")]
+ internal class ManifestImpl : Manifest, IManifest
+ {
+ /// <summary>The mod version.</summary>
+ public new ISemanticVersion Version
+ {
+ get { return base.Version; }
+ set { base.Version = (Version)value; }
+ }
+ }
+
/// <summary>A manifest which describes a mod for SMAPI.</summary>
public class Manifest
{
/*********
** Accessors
*********/
- /// <summary>Whether the manifest defined the deprecated <see cref="Authour"/> field.</summary>
- [JsonIgnore]
- internal bool UsedAuthourField { get; private set; }
-
/// <summary>The mod name.</summary>
- public virtual string Name { get; set; } = "";
+ public string Name { get; set; }
+
+ /// <summary>A brief description of the mod.</summary>
+ public string Description { get; set; }
/// <summary>The mod author's name.</summary>
- public virtual string Author { get; set; } = "";
+ public string Author { get; set; }
+
+ /// <summary>The mod version.</summary>
+ public Version Version { get; set; } = new Version(0, 0, 0, "", suppressDeprecationWarning: true);
+
+ /// <summary>The minimum SMAPI version required by this mod, if any.</summary>
+ public string MinimumApiVersion { get; set; }
+
+ /// <summary>The name of the DLL in the directory that has the <see cref="Mod.Entry"/> method.</summary>
+ public string EntryDll { get; set; }
+
+ /// <summary>The unique mod ID.</summary>
+ public string UniqueID { get; set; } = Guid.NewGuid().ToString();
+
+
+ /****
+ ** Obsolete
+ ****/
+ /// <summary>Whether the manifest defined the deprecated <see cref="Authour"/> field.</summary>
+ [JsonIgnore]
+ internal bool UsedAuthourField { get; private set; }
/// <summary>Obsolete.</summary>
[Obsolete("Use " + nameof(Manifest) + "." + nameof(Manifest.Author) + ".")]
@@ -31,23 +62,8 @@ namespace StardewModdingAPI
}
}
- /// <summary>The mod version.</summary>
- public virtual Version Version { get; set; } = new Version(0, 0, 0, "");
-
- /// <summary>A brief description of the mod.</summary>
- public virtual string Description { get; set; } = "";
-
- /// <summary>The unique mod ID.</summary>
- public virtual string UniqueID { get; set; } = Guid.NewGuid().ToString();
-
/// <summary>Whether the mod uses per-save config files.</summary>
[Obsolete("Use " + nameof(Mod) + "." + nameof(Mod.Helper) + "." + nameof(IModHelper.ReadConfig) + " instead")]
- public virtual bool PerSaveConfigs { get; set; }
-
- /// <summary>The minimum SMAPI version required by this mod, if any.</summary>
- public string MinimumApiVersion { get; set; }
-
- /// <summary>The name of the DLL in the directory that has the <see cref="Mod.Entry"/> method.</summary>
- public virtual string EntryDll { get; set; } = "";
+ public bool PerSaveConfigs { get; set; }
}
}
diff --git a/src/StardewModdingAPI/Mod.cs b/src/StardewModdingAPI/Mod.cs
index 21551771..f0f876fa 100644
--- a/src/StardewModdingAPI/Mod.cs
+++ b/src/StardewModdingAPI/Mod.cs
@@ -24,7 +24,18 @@ namespace StardewModdingAPI
public IMonitor Monitor { get; internal set; }
/// <summary>The mod's manifest.</summary>
- public Manifest Manifest { get; internal set; }
+ [Obsolete("Use " + nameof(Mod) + "." + nameof(ModManifest))]
+ public Manifest Manifest
+ {
+ get
+ {
+ Program.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(Manifest)}", "1.5", DeprecationLevel.Notice);
+ return (Manifest)this.ModManifest;
+ }
+ }
+
+ /// <summary>The mod's manifest.</summary>
+ public IManifest ModManifest { get; internal set; }
/// <summary>The full path to the mod's directory on the disk.</summary>
[Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.DirectoryPath) + " instead")]
@@ -94,7 +105,7 @@ namespace StardewModdingAPI
Program.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(Mod.PerSaveConfigFolder)}", "1.0", DeprecationLevel.Notice);
Program.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(Mod.PathOnDisk)}", "1.0"); // avoid redundant warnings
- if (!this.Manifest.PerSaveConfigs)
+ if (!((Manifest)this.Manifest).PerSaveConfigs)
{
this.Monitor.Log("Tried to fetch the per-save config folder, but this mod isn't configured to use per-save config files.", LogLevel.Error);
return "";
diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs
index 62b9dabd..7c9cdcc3 100644
--- a/src/StardewModdingAPI/Program.cs
+++ b/src/StardewModdingAPI/Program.cs
@@ -98,7 +98,7 @@ namespace StardewModdingAPI
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB");
// add info header
- Program.Monitor.Log($"SMAPI {Constants.Version} with Stardew Valley {Game1.version} on {Environment.OSVersion}", LogLevel.Info);
+ Program.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Game1.version} on {Environment.OSVersion}", LogLevel.Info);
// load user settings
{
@@ -191,9 +191,9 @@ namespace StardewModdingAPI
try
{
GitRelease release = UpdateHelper.GetLatestVersionAsync(Constants.GitHubRepository).Result;
- Version latestVersion = new Version(release.Tag);
- if (latestVersion.IsNewerThan(Constants.Version))
- Program.Monitor.Log($"You can update SMAPI from version {Constants.Version} to {latestVersion}", LogLevel.Alert);
+ ISemanticVersion latestVersion = new SemanticVersion(release.Tag);
+ if (latestVersion.IsNewerThan(Constants.ApiVersion))
+ Program.Monitor.Log($"You can update SMAPI from version {Constants.ApiVersion} to {latestVersion}", LogLevel.Alert);
}
catch (Exception ex)
{
@@ -212,7 +212,7 @@ namespace StardewModdingAPI
Program.StardewAssembly = Assembly.UnsafeLoadFrom(Program.GameExecutablePath);
Program.StardewProgramType = Program.StardewAssembly.GetType("StardewValley.Program", true);
Program.StardewGameInfo = Program.StardewProgramType.GetField("gamePtr");
- Game1.version += $"-Z_MODDED | SMAPI {Constants.Version}";
+ Game1.version += $"-Z_MODDED | SMAPI {Constants.ApiVersion}";
// add error interceptors
#if SMAPI_FOR_WINDOWS
@@ -335,7 +335,7 @@ namespace StardewModdingAPI
string errorPrefix = $"Couldn't load mod for manifest '{manifestPath}'";
// read manifest
- Manifest manifest;
+ ManifestImpl manifest;
try
{
// read manifest text
@@ -347,7 +347,7 @@ namespace StardewModdingAPI
}
// deserialise manifest
- manifest = helper.ReadJsonFile<Manifest>("manifest.json");
+ manifest = helper.ReadJsonFile<ManifestImpl>("manifest.json");
if (manifest == null)
{
Program.Monitor.Log($"{errorPrefix}: the manifest file does not exist.", LogLevel.Error);
@@ -374,8 +374,8 @@ namespace StardewModdingAPI
{
try
{
- Version minVersion = new Version(manifest.MinimumApiVersion);
- if (minVersion.IsNewerThan(Constants.Version))
+ ISemanticVersion minVersion = new SemanticVersion(manifest.MinimumApiVersion);
+ if (minVersion.IsNewerThan(Constants.ApiVersion))
{
Program.Monitor.Log($"{errorPrefix}: this mod requires SMAPI {minVersion} or later. Please update SMAPI to the latest version to use this mod.", LogLevel.Error);
continue;
@@ -473,11 +473,11 @@ namespace StardewModdingAPI
Program.ModRegistry.Add(manifest, modAssembly);
// hook up mod
- modEntry.Manifest = manifest;
+ modEntry.ModManifest = manifest;
modEntry.Helper = helper;
modEntry.Monitor = new Monitor(manifest.Name, Program.LogFile) { ShowTraceInConsole = Program.DeveloperMode };
modEntry.PathOnDisk = directory;
- Program.Monitor.Log($"Loaded mod: {modEntry.Manifest.Name} by {modEntry.Manifest.Author}, v{modEntry.Manifest.Version} | {modEntry.Manifest.Description}", LogLevel.Info);
+ Program.Monitor.Log($"Loaded mod: {modEntry.ModManifest.Name} by {modEntry.ModManifest.Author}, v{modEntry.ModManifest.Version} | {modEntry.ModManifest.Description}", LogLevel.Info);
Program.ModsLoaded += 1;
modEntry.Entry(); // deprecated since 1.0
modEntry.Entry((ModHelper)modEntry.Helper); // deprecated since 1.1
diff --git a/src/StardewModdingAPI/SemanticVersion.cs b/src/StardewModdingAPI/SemanticVersion.cs
new file mode 100644
index 00000000..b3d9ee4a
--- /dev/null
+++ b/src/StardewModdingAPI/SemanticVersion.cs
@@ -0,0 +1,130 @@
+using System;
+using System.Text.RegularExpressions;
+
+namespace StardewModdingAPI
+{
+ /// <summary>A semantic version with an optional release tag.</summary>
+ public class SemanticVersion : ISemanticVersion
+ {
+ /*********
+ ** Properties
+ *********/
+ /// <summary>A regular expression matching a semantic version string.</summary>
+ /// <remarks>Derived from https://github.com/maxhauser/semver.</remarks>
+ private static readonly Regex Regex = new Regex(@"^(?<major>\d+)(\.(?<minor>\d+))?(\.(?<patch>\d+))?(?<build>.*)$", RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture);
+
+
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The major version incremented for major API changes.</summary>
+ public int MajorVersion { get; set; }
+
+ /// <summary>The minor version incremented for backwards-compatible changes.</summary>
+ public int MinorVersion { get; set; }
+
+ /// <summary>The patch version for backwards-compatible bug fixes.</summary>
+ public int PatchVersion { get; set; }
+
+ /// <summary>An optional build tag.</summary>
+ public string Build { get; set; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <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 bug fixes.</param>
+ /// <param name="build">An optional build tag.</param>
+ public SemanticVersion(int major, int minor, int patch, string build = null)
+ {
+ this.MajorVersion = major;
+ this.MinorVersion = minor;
+ this.PatchVersion = patch;
+ this.Build = build;
+ }
+
+ /// <summary>Get an integer indicating whether this version precedes (less than 0), supercedes (more than 0), or is equivalent to (0) the specified version.</summary>
+ /// <param name="other">The version to compare with this instance.</param>
+ public int CompareTo(ISemanticVersion other)
+ {
+ // compare version numbers
+ if (this.MajorVersion != other.MajorVersion)
+ return this.MajorVersion - other.MajorVersion;
+ if (this.MinorVersion != other.MinorVersion)
+ return this.MinorVersion - other.MinorVersion;
+ if (this.PatchVersion != other.PatchVersion)
+ return this.PatchVersion - other.PatchVersion;
+
+ // stable version (without tag) supercedes prerelease (with tag)
+ bool curHasTag = !string.IsNullOrWhiteSpace(this.Build);
+ bool otherHasTag = !string.IsNullOrWhiteSpace(other.Build);
+ if (!curHasTag && otherHasTag)
+ return 1;
+ if (curHasTag && !otherHasTag)
+ return -1;
+
+ // else compare by string
+ return string.Compare(this.ToString(), other.ToString(), StringComparison.InvariantCultureIgnoreCase);
+ }
+
+ /// <summary>Get whether this version is older than the specified version.</summary>
+ /// <param name="other">The version to compare with this instance.</param>
+ public bool IsOlderThan(ISemanticVersion other)
+ {
+ return this.CompareTo(other) < 0;
+ }
+
+ /// <summary>Get whether this version is newer than the specified version.</summary>
+ /// <param name="other">The version to compare with this instance.</param>
+ public bool IsNewerThan(ISemanticVersion other)
+ {
+ return this.CompareTo(other) > 0;
+ }
+
+ /// <summary>Get a string representation of the version.</summary>
+ public override string ToString()
+ {
+ // version
+ string result = this.PatchVersion != 0
+ ? $"{this.MajorVersion}.{this.MinorVersion}.{this.PatchVersion}"
+ : $"{this.MajorVersion}.{this.MinorVersion}";
+
+ // tag
+ string tag = this.GetNormalisedTag(this.Build);
+ if (tag != null)
+ result += $"-{tag}";
+ return result;
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="version">The semantic version string.</param>
+ internal SemanticVersion(string version)
+ {
+ var match = SemanticVersion.Regex.Match(version);
+ if (!match.Success)
+ throw new FormatException($"The input '{version}' is not a semantic version.");
+
+ 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.Build = (match.Groups["build"].Success ? match.Groups["build"].Value : "").Trim(' ', '-', '.');
+ }
+
+ /// <summary>Get a normalised build tag.</summary>
+ /// <param name="tag">The tag to normalise.</param>
+ private string GetNormalisedTag(string tag)
+ {
+ tag = tag?.Trim().Trim('-', '.');
+ if (string.IsNullOrWhiteSpace(tag) || tag == "0")
+ return null;
+ return tag;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj
index 65083e67..875bc1f3 100644
--- a/src/StardewModdingAPI/StardewModdingAPI.csproj
+++ b/src/StardewModdingAPI/StardewModdingAPI.csproj
@@ -166,8 +166,10 @@
<Compile Include="Framework\Reflection\PrivateField.cs" />
<Compile Include="Framework\Reflection\PrivateMethod.cs" />
<Compile Include="Framework\Reflection\ReflectionHelper.cs" />
+ <Compile Include="IManifest.cs" />
<Compile Include="IModHelper.cs" />
<Compile Include="Framework\LogFileManager.cs" />
+ <Compile Include="ISemanticVersion.cs" />
<Compile Include="LogLevel.cs" />
<Compile Include="Framework\ModRegistry.cs" />
<Compile Include="Framework\UpdateHelper.cs" />
@@ -190,6 +192,7 @@
<Compile Include="IPrivateField.cs" />
<Compile Include="IPrivateMethod.cs" />
<Compile Include="IReflectionHelper.cs" />
+ <Compile Include="SemanticVersion.cs" />
<Compile Include="Version.cs" />
</ItemGroup>
<ItemGroup>
diff --git a/src/StardewModdingAPI/Version.cs b/src/StardewModdingAPI/Version.cs
index 7c6d319c..9c13381a 100644
--- a/src/StardewModdingAPI/Version.cs
+++ b/src/StardewModdingAPI/Version.cs
@@ -1,22 +1,14 @@
using System;
-using System.Text.RegularExpressions;
using Newtonsoft.Json;
using StardewModdingAPI.Framework;
namespace StardewModdingAPI
{
/// <summary>A semantic version with an optional release tag.</summary>
- public struct Version : IComparable<Version>
+ [Obsolete("Use " + nameof(SemanticVersion) + " or " + nameof(Manifest) + "." + nameof(Manifest.Version) + " instead")]
+ public struct Version : ISemanticVersion
{
/*********
- ** Properties
- *********/
- /// <summary>A regular expression matching a semantic version string.</summary>
- /// <remarks>Derived from https://github.com/maxhauser/semver.</remarks>
- private static readonly Regex Regex = new Regex(@"^(?<major>\d+)(\.(?<minor>\d+))?(\.(?<patch>\d+))?(?<build>.*)$", RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture);
-
-
- /*********
** Accessors
*********/
/// <summary>The major version incremented for major API changes.</summary>
@@ -39,7 +31,7 @@ namespace StardewModdingAPI
get
{
Program.DeprecationManager.Warn($"{nameof(Version)}.{nameof(Version.VersionString)}", "1.0", DeprecationLevel.Notice);
- return this.ToString();
+ return this.GetSemanticVersion().ToString();
}
}
@@ -53,92 +45,72 @@ namespace StardewModdingAPI
/// <param name="patch">The patch version for backwards-compatible bug fixes.</param>
/// <param name="build">An optional build tag.</param>
public Version(int major, int minor, int patch, string build)
+ : this(major, minor, patch, build, suppressDeprecationWarning: false)
+ { }
+
+ /// <summary>Get an integer indicating whether this version precedes (less than 0), supercedes (more than 0), or is equivalent to (0) the specified version.</summary>
+ /// <param name="other">The version to compare with this instance.</param>
+ public int CompareTo(Version other)
{
- this.MajorVersion = major;
- this.MinorVersion = minor;
- this.PatchVersion = patch;
- this.Build = build;
+ return this.GetSemanticVersion().CompareTo(other);
}
- /// <summary>Construct an instance.</summary>
- /// <param name="version">The semantic version string.</param>
- internal Version(string version)
+ /// <summary>Get whether this version is newer than the specified version.</summary>
+ /// <param name="other">The version to compare with this instance.</param>
+ [Obsolete("Use " + nameof(ISemanticVersion) + "." + nameof(ISemanticVersion.IsNewerThan) + " instead")]
+ public bool IsNewerThan(Version other)
{
- var match = Version.Regex.Match(version);
- if (!match.Success)
- throw new FormatException($"The input '{version}' is not a semantic version.");
-
- 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.Build = (match.Groups["build"].Success ? match.Groups["build"].Value : "").Trim(' ', '-', '.');
+ return this.GetSemanticVersion().IsNewerThan(other);
}
- /// <summary>Get an integer indicating whether this version precedes (less than 0), supercedes (more than 0), or is equivalent to (0) the specified version.</summary>
- /// <param name="other">The version to compare with this instance.</param>
- public int CompareTo(Version other)
+ /// <summary>Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. </summary>
+ /// <returns>A value that indicates the relative order of the objects being compared. The return value has these meanings: Value Meaning Less than zero This instance precedes <paramref name="other" /> in the sort order. Zero This instance occurs in the same position in the sort order as <paramref name="other" />. Greater than zero This instance follows <paramref name="other" /> in the sort order. </returns>
+ /// <param name="other">An object to compare with this instance. </param>
+ int IComparable<ISemanticVersion>.CompareTo(ISemanticVersion other)
{
- // compare version numbers
- if (this.MajorVersion != other.MajorVersion)
- return this.MajorVersion - other.MajorVersion;
- if (this.MinorVersion != other.MinorVersion)
- return this.MinorVersion - other.MinorVersion;
- if (this.PatchVersion != other.PatchVersion)
- return this.PatchVersion - other.PatchVersion;
-
- // stable version (without tag) supercedes prerelease (with tag)
- bool curHasTag = !string.IsNullOrWhiteSpace(this.Build);
- bool otherHasTag = !string.IsNullOrWhiteSpace(other.Build);
- if (!curHasTag && otherHasTag)
- return 1;
- if (curHasTag && !otherHasTag)
- return -1;
-
- // else compare by string
- return string.Compare(this.ToString(), other.ToString(), StringComparison.InvariantCultureIgnoreCase);
+ return this.GetSemanticVersion().CompareTo(other);
}
/// <summary>Get whether this version is older than the specified version.</summary>
/// <param name="other">The version to compare with this instance.</param>
- public bool IsOlderThan(Version other)
+ bool ISemanticVersion.IsOlderThan(ISemanticVersion other)
{
- return this.CompareTo(other) < 0;
+ return this.GetSemanticVersion().IsOlderThan(other);
}
/// <summary>Get whether this version is newer than the specified version.</summary>
/// <param name="other">The version to compare with this instance.</param>
- public bool IsNewerThan(Version other)
- {
- return this.CompareTo(other) > 0;
- }
-
- /// <summary>Get a string representation of the version.</summary>
- public override string ToString()
+ bool ISemanticVersion.IsNewerThan(ISemanticVersion other)
{
- // version
- string result = this.PatchVersion != 0
- ? $"{this.MajorVersion}.{this.MinorVersion}.{this.PatchVersion}"
- : $"{this.MajorVersion}.{this.MinorVersion}";
-
- // tag
- string tag = this.GetNormalisedTag(this.Build);
- if (tag != null)
- result += $"-{tag}";
- return result;
+ return this.GetSemanticVersion().IsNewerThan(other);
}
/*********
** Private methods
*********/
- /// <summary>Get a normalised build tag.</summary>
- /// <param name="tag">The tag to normalise.</param>
- private string GetNormalisedTag(string tag)
+ /// <summary>Construct an instance.</summary>
+ /// <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 bug fixes.</param>
+ /// <param name="build">An optional build tag.</param>
+ /// <param name="suppressDeprecationWarning">Whether to suppress the deprecation warning.</param>
+ internal Version(int major, int minor, int patch, string build, bool suppressDeprecationWarning)
+ {
+ if (!suppressDeprecationWarning)
+ Program.DeprecationManager.Warn($"{nameof(Version)}", "1.5", DeprecationLevel.Notice);
+
+ this.MajorVersion = major;
+ this.MinorVersion = minor;
+ this.PatchVersion = patch;
+ this.Build = build;
+ }
+
+ /// <summary>Get the equivalent semantic version.</summary>
+ /// <remarks>This is a hack so the struct can wrap <see cref="SemanticVersion"/> without a mutable backing field, which would cause a <see cref="StackOverflowException"/> due to recreating the struct value on each change.</remarks>
+ private SemanticVersion GetSemanticVersion()
{
- tag = tag?.Trim().Trim('-', '.');
- if (string.IsNullOrWhiteSpace(tag) || tag == "0")
- return null;
- return tag;
+ return new SemanticVersion(this.MajorVersion, this.MinorVersion, this.PatchVersion, this.Build);
}
}
}