diff options
Diffstat (limited to 'src')
29 files changed, 548 insertions, 142 deletions
diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs index ace560e5..6e4cb95d 100644 --- a/src/SMAPI.Installer/InteractiveInstaller.cs +++ b/src/SMAPI.Installer/InteractiveInstaller.cs @@ -152,7 +152,7 @@ namespace StardewModdingApi.Installer ** Get platform & set window title ****/ Platform platform = EnvironmentUtility.DetectPlatform(); - Console.Title = $"SMAPI {new SemanticVersionImpl(this.GetType().Assembly.GetName().Version)} installer on {platform} {EnvironmentUtility.GetFriendlyPlatformName(platform)}"; + Console.Title = $"SMAPI {this.GetDisplayVersion(this.GetType().Assembly.GetName().Version)} installer on {platform} {EnvironmentUtility.GetFriendlyPlatformName(platform)}"; Console.WriteLine(); #if SMAPI_FOR_WINDOWS @@ -421,6 +421,16 @@ namespace StardewModdingApi.Installer /********* ** Private methods *********/ + /// <summary>Get the display text for an assembly version.</summary> + /// <param name="version">The assembly version.</param> + private string GetDisplayVersion(Version version) + { + string str = $"{version.Major}.{version.Minor}"; + if (version.Build != 0) + str += $".{version.Build}"; + return str; + } + /// <summary>Get the value of a key in the Windows registry.</summary> /// <param name="key">The full path of the registry key relative to HKLM.</param> /// <param name="name">The name of the value.</param> diff --git a/src/SMAPI.Internal/SMAPI.Internal.projitems b/src/SMAPI.Internal/SMAPI.Internal.projitems index dadae4b0..33b8cbfa 100644 --- a/src/SMAPI.Internal/SMAPI.Internal.projitems +++ b/src/SMAPI.Internal/SMAPI.Internal.projitems @@ -16,6 +16,5 @@ <Compile Include="$(MSBuildThisFileDirectory)Models\ModSeachModel.cs" /> <Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\MonitorColorScheme.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Platform.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)SemanticVersionImpl.cs" /> </ItemGroup> </Project>
\ No newline at end of file diff --git a/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs b/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs index 3fec8215..41e0201d 100644 --- a/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs +++ b/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Web.Script.Serialization; -using StardewModdingAPI.Internal; +using StardewModdingAPI.Toolkit; namespace StardewModdingAPI.ModBuildConfig.Framework { @@ -132,9 +132,9 @@ namespace StardewModdingAPI.ModBuildConfig.Framework int minor = versionFields.ContainsKey("MinorVersion") ? (int)versionFields["MinorVersion"] : 0; int patch = versionFields.ContainsKey("PatchVersion") ? (int)versionFields["PatchVersion"] : 0; string tag = versionFields.ContainsKey("Build") ? (string)versionFields["Build"] : null; - return new SemanticVersionImpl(major, minor, patch, tag).ToString(); + return new SemanticVersion(major, minor, patch, tag).ToString(); } - return new SemanticVersionImpl(versionObj.ToString()).ToString(); // SMAPI 2.0+ + return new SemanticVersion(versionObj.ToString()).ToString(); // SMAPI 2.0+ } diff --git a/src/SMAPI.ModBuildConfig/StardewModdingAPI.ModBuildConfig.csproj b/src/SMAPI.ModBuildConfig/StardewModdingAPI.ModBuildConfig.csproj index e0ea876f..6a52daac 100644 --- a/src/SMAPI.ModBuildConfig/StardewModdingAPI.ModBuildConfig.csproj +++ b/src/SMAPI.ModBuildConfig/StardewModdingAPI.ModBuildConfig.csproj @@ -55,6 +55,12 @@ <ItemGroup> <Content Include="assets\nuget-icon.png" /> </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\StardewModdingAPI.Toolkit\StardewModdingAPI.Toolkit.csproj"> + <Project>{ea5cfd2e-9453-4d29-b80f-8e0ea23f4ac6}</Project> + <Name>StardewModdingAPI.Toolkit</Name> + </ProjectReference> + </ItemGroup> <Import Project="..\SMAPI.Internal\SMAPI.Internal.projitems" Label="Shared" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="..\..\build\common.targets" /> diff --git a/src/SMAPI.Tests/StardewModdingAPI.Tests.csproj b/src/SMAPI.Tests/StardewModdingAPI.Tests.csproj index f4d7b3e3..04c8d12f 100644 --- a/src/SMAPI.Tests/StardewModdingAPI.Tests.csproj +++ b/src/SMAPI.Tests/StardewModdingAPI.Tests.csproj @@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Import Project="..\packages\NUnit.3.10.1\build\NUnit.props" Condition="Exists('..\packages\NUnit.3.10.1\build\NUnit.props')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> @@ -56,7 +56,7 @@ <Compile Include="..\..\build\GlobalAssemblyInfo.cs"> <Link>Properties\GlobalAssemblyInfo.cs</Link> </Compile> - <Compile Include="Core\PathUtilitiesTests.cs" /> + <Compile Include="Toolkit\PathUtilitiesTests.cs" /> <Compile Include="Utilities\SemanticVersionTests.cs" /> <Compile Include="Utilities\SDateTests.cs" /> <Compile Include="Core\TranslationTests.cs" /> @@ -73,6 +73,10 @@ <Project>{f1a573b0-f436-472c-ae29-0b91ea6b9f8f}</Project> <Name>StardewModdingAPI</Name> </ProjectReference> + <ProjectReference Include="..\StardewModdingAPI.Toolkit\StardewModdingAPI.Toolkit.csproj"> + <Project>{ea5cfd2e-9453-4d29-b80f-8e0ea23f4ac6}</Project> + <Name>StardewModdingAPI.Toolkit</Name> + </ProjectReference> </ItemGroup> <ItemGroup> <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" /> diff --git a/src/SMAPI.Tests/Core/PathUtilitiesTests.cs b/src/SMAPI.Tests/Toolkit/PathUtilitiesTests.cs index 268ba504..229b9a14 100644 --- a/src/SMAPI.Tests/Core/PathUtilitiesTests.cs +++ b/src/SMAPI.Tests/Toolkit/PathUtilitiesTests.cs @@ -1,7 +1,7 @@ using NUnit.Framework; -using StardewModdingAPI.Framework.Utilities; +using StardewModdingAPI.Toolkit.Utilities; -namespace StardewModdingAPI.Tests.Core +namespace StardewModdingAPI.Tests.Toolkit { /// <summary>Unit tests for <see cref="PathUtilities"/>.</summary> [TestFixture] diff --git a/src/SMAPI.Web/Controllers/IndexController.cs b/src/SMAPI.Web/Controllers/IndexController.cs index 08b7363a..0cc3c37a 100644 --- a/src/SMAPI.Web/Controllers/IndexController.cs +++ b/src/SMAPI.Web/Controllers/IndexController.cs @@ -5,7 +5,7 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Memory; -using StardewModdingAPI.Internal; +using StardewModdingAPI.Toolkit; using StardewModdingAPI.Web.Framework.Clients.GitHub; using StardewModdingAPI.Web.ViewModels; @@ -105,7 +105,7 @@ namespace StardewModdingAPI.Web.Controllers foreach (GitAsset asset in release.Assets) { Match match = Regex.Match(asset.FileName, @"SMAPI-(?<version>[\d\.]+(?:-.+)?)-installer(?<forDevs>-for-developers)?.zip"); - if (!match.Success || !SemanticVersionImpl.TryParse(match.Groups["version"].Value, out SemanticVersionImpl version)) + if (!match.Success || !SemanticVersion.TryParse(match.Groups["version"].Value, out ISemanticVersion version)) continue; bool isBeta = version.Tag != null; bool isForDevs = match.Groups["forDevs"].Success; @@ -127,7 +127,7 @@ namespace StardewModdingAPI.Web.Controllers public GitAsset Asset { get; } /// <summary>The SMAPI version.</summary> - public SemanticVersionImpl Version { get; } + public ISemanticVersion Version { get; } /// <summary>Whether this is a beta download.</summary> public bool IsBeta { get; } @@ -145,7 +145,7 @@ namespace StardewModdingAPI.Web.Controllers /// <param name="version">The SMAPI version.</param> /// <param name="isBeta">Whether this is a beta download.</param> /// <param name="isForDevs">Whether this is a 'for developers' download.</param> - public ReleaseVersion(GitRelease release, GitAsset asset, SemanticVersionImpl version, bool isBeta, bool isForDevs) + public ReleaseVersion(GitRelease release, GitAsset asset, ISemanticVersion version, bool isBeta, bool isForDevs) { this.Release = release; this.Asset = asset; diff --git a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs index 163176fd..c50e643a 100644 --- a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs +++ b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; -using StardewModdingAPI.Internal; +using StardewModdingAPI.Toolkit; using StardewModdingAPI.Web.Framework.LogParsing.Models; namespace StardewModdingAPI.Web.Framework.LogParsing @@ -31,7 +31,7 @@ namespace StardewModdingAPI.Web.Framework.LogParsing /// <summary>A regex pattern matching an entry in SMAPI's mod list.</summary> /// <remarks>The author name and description are optional.</remarks> - private readonly Regex ModListEntryPattern = new Regex(@"^ (?<name>.+?) (?<version>" + SemanticVersionImpl.UnboundedVersionPattern + @")(?: by (?<author>[^\|]+))?(?: \| (?<description>.+))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private readonly Regex ModListEntryPattern = new Regex(@"^ (?<name>.+?) (?<version>" + SemanticVersion.UnboundedVersionPattern + @")(?: by (?<author>[^\|]+))?(?: \| (?<description>.+))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase); /// <summary>A regex pattern matching the start of SMAPI's content pack list.</summary> private readonly Regex ContentPackListStartPattern = new Regex(@"^Loaded \d+ content packs:$", RegexOptions.Compiled | RegexOptions.IgnoreCase); diff --git a/src/SMAPI.Web/Framework/VersionConstraint.cs b/src/SMAPI.Web/Framework/VersionConstraint.cs index 1502f5d8..2d6ec603 100644 --- a/src/SMAPI.Web/Framework/VersionConstraint.cs +++ b/src/SMAPI.Web/Framework/VersionConstraint.cs @@ -1,5 +1,5 @@ using Microsoft.AspNetCore.Routing.Constraints; -using StardewModdingAPI.Internal; +using StardewModdingAPI.Toolkit; namespace StardewModdingAPI.Web.Framework { @@ -11,6 +11,6 @@ namespace StardewModdingAPI.Web.Framework *********/ /// <summary>Construct an instance.</summary> public VersionConstraint() - : base(SemanticVersionImpl.Regex) { } + : base(SemanticVersion.Regex) { } } } diff --git a/src/SMAPI.Web/StardewModdingAPI.Web.csproj b/src/SMAPI.Web/StardewModdingAPI.Web.csproj index e4678269..9210565a 100644 --- a/src/SMAPI.Web/StardewModdingAPI.Web.csproj +++ b/src/SMAPI.Web/StardewModdingAPI.Web.csproj @@ -17,11 +17,14 @@ <PackageReference Include="Microsoft.AspNetCore.Rewrite" Version="2.0.2" /> <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.0.2" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.1" /> - <PackageReference Include="Pathoschild.Http.FluentClient" Version="3.1.0" /> + <PackageReference Include="Pathoschild.Http.FluentClient" Version="3.2.0" /> </ItemGroup> <ItemGroup> <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="1.0.1" /> </ItemGroup> <Import Project="..\SMAPI.Internal\SMAPI.Internal.projitems" Label="Shared" /> + <ItemGroup> + <ProjectReference Include="..\StardewModdingAPI.Toolkit\StardewModdingAPI.Toolkit.csproj" /> + </ItemGroup> </Project> diff --git a/src/SMAPI.sln b/src/SMAPI.sln index dec26694..0eb42cce 100644 --- a/src/SMAPI.sln +++ b/src/SMAPI.sln @@ -60,6 +60,8 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "StardewModdingAPI.Internal" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StardewModdingAPI.Mods.SaveBackup", "SMAPI.Mods.SaveBackup\StardewModdingAPI.Mods.SaveBackup.csproj", "{E272EB5D-8C57-417E-8E60-C1079D3F53C4}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StardewModdingAPI.Toolkit", "StardewModdingAPI.Toolkit\StardewModdingAPI.Toolkit.csproj", "{EA5CFD2E-9453-4D29-B80F-8E0EA23F4AC6}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution SMAPI.Internal\SMAPI.Internal.projitems*{443ddf81-6aaf-420a-a610-3459f37e5575}*SharedItemsImports = 4 @@ -68,46 +70,50 @@ Global SMAPI.Internal\SMAPI.Internal.projitems*{f1a573b0-f436-472c-ae29-0b91ea6b9f8f}*SharedItemsImports = 4 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x86 = Debug|x86 - Release|x86 = Release|x86 + Debug|Default = Debug|Default + Release|Default = Release|Default EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {28480467-1A48-46A7-99F8-236D95225359}.Debug|x86.ActiveCfg = Debug|x86 - {28480467-1A48-46A7-99F8-236D95225359}.Debug|x86.Build.0 = Debug|x86 - {28480467-1A48-46A7-99F8-236D95225359}.Release|x86.ActiveCfg = Release|x86 - {28480467-1A48-46A7-99F8-236D95225359}.Release|x86.Build.0 = Release|x86 - {F1A573B0-F436-472C-AE29-0B91EA6B9F8F}.Debug|x86.ActiveCfg = Debug|x86 - {F1A573B0-F436-472C-AE29-0B91EA6B9F8F}.Debug|x86.Build.0 = Debug|x86 - {F1A573B0-F436-472C-AE29-0B91EA6B9F8F}.Release|x86.ActiveCfg = Release|x86 - {F1A573B0-F436-472C-AE29-0B91EA6B9F8F}.Release|x86.Build.0 = Release|x86 - {443DDF81-6AAF-420A-A610-3459F37E5575}.Debug|x86.ActiveCfg = Debug|x86 - {443DDF81-6AAF-420A-A610-3459F37E5575}.Debug|x86.Build.0 = Debug|x86 - {443DDF81-6AAF-420A-A610-3459F37E5575}.Release|x86.ActiveCfg = Release|x86 - {443DDF81-6AAF-420A-A610-3459F37E5575}.Release|x86.Build.0 = Release|x86 - {36CCB19E-92EB-48C7-9615-98EEFD45109B}.Debug|x86.ActiveCfg = Debug|x86 - {36CCB19E-92EB-48C7-9615-98EEFD45109B}.Debug|x86.Build.0 = Debug|x86 - {36CCB19E-92EB-48C7-9615-98EEFD45109B}.Release|x86.ActiveCfg = Release|x86 - {36CCB19E-92EB-48C7-9615-98EEFD45109B}.Release|x86.Build.0 = Release|x86 - {A308F679-51A3-4006-92D5-BAEC7EBD01A1}.Debug|x86.ActiveCfg = Debug|Any CPU - {A308F679-51A3-4006-92D5-BAEC7EBD01A1}.Debug|x86.Build.0 = Debug|Any CPU - {A308F679-51A3-4006-92D5-BAEC7EBD01A1}.Release|x86.ActiveCfg = Release|Any CPU - {A308F679-51A3-4006-92D5-BAEC7EBD01A1}.Release|x86.Build.0 = Release|Any CPU - {EA4F1E80-743F-4A1D-9757-AE66904A196A}.Debug|x86.ActiveCfg = Debug|x86 - {EA4F1E80-743F-4A1D-9757-AE66904A196A}.Debug|x86.Build.0 = Debug|x86 - {EA4F1E80-743F-4A1D-9757-AE66904A196A}.Release|x86.ActiveCfg = Release|x86 - {EA4F1E80-743F-4A1D-9757-AE66904A196A}.Release|x86.Build.0 = Release|x86 - {80AD8528-AA49-4731-B4A6-C691845815A1}.Debug|x86.ActiveCfg = Debug|Any CPU - {80AD8528-AA49-4731-B4A6-C691845815A1}.Debug|x86.Build.0 = Debug|Any CPU - {80AD8528-AA49-4731-B4A6-C691845815A1}.Release|x86.ActiveCfg = Release|Any CPU - {80AD8528-AA49-4731-B4A6-C691845815A1}.Release|x86.Build.0 = Release|Any CPU - {0CF97929-B0D0-4D73-B7BF-4FF7191035F9}.Debug|x86.ActiveCfg = Debug|Any CPU - {0CF97929-B0D0-4D73-B7BF-4FF7191035F9}.Debug|x86.Build.0 = Debug|Any CPU - {0CF97929-B0D0-4D73-B7BF-4FF7191035F9}.Release|x86.ActiveCfg = Release|Any CPU - {0CF97929-B0D0-4D73-B7BF-4FF7191035F9}.Release|x86.Build.0 = Release|Any CPU - {E272EB5D-8C57-417E-8E60-C1079D3F53C4}.Debug|x86.ActiveCfg = Debug|x86 - {E272EB5D-8C57-417E-8E60-C1079D3F53C4}.Debug|x86.Build.0 = Debug|x86 - {E272EB5D-8C57-417E-8E60-C1079D3F53C4}.Release|x86.ActiveCfg = Release|x86 - {E272EB5D-8C57-417E-8E60-C1079D3F53C4}.Release|x86.Build.0 = Release|x86 + {28480467-1A48-46A7-99F8-236D95225359}.Debug|Default.ActiveCfg = Debug|x86 + {28480467-1A48-46A7-99F8-236D95225359}.Debug|Default.Build.0 = Debug|x86 + {28480467-1A48-46A7-99F8-236D95225359}.Release|Default.ActiveCfg = Release|x86 + {28480467-1A48-46A7-99F8-236D95225359}.Release|Default.Build.0 = Release|x86 + {F1A573B0-F436-472C-AE29-0B91EA6B9F8F}.Debug|Default.ActiveCfg = Debug|x86 + {F1A573B0-F436-472C-AE29-0B91EA6B9F8F}.Debug|Default.Build.0 = Debug|x86 + {F1A573B0-F436-472C-AE29-0B91EA6B9F8F}.Release|Default.ActiveCfg = Release|x86 + {F1A573B0-F436-472C-AE29-0B91EA6B9F8F}.Release|Default.Build.0 = Release|x86 + {443DDF81-6AAF-420A-A610-3459F37E5575}.Debug|Default.ActiveCfg = Debug|x86 + {443DDF81-6AAF-420A-A610-3459F37E5575}.Debug|Default.Build.0 = Debug|x86 + {443DDF81-6AAF-420A-A610-3459F37E5575}.Release|Default.ActiveCfg = Release|x86 + {443DDF81-6AAF-420A-A610-3459F37E5575}.Release|Default.Build.0 = Release|x86 + {36CCB19E-92EB-48C7-9615-98EEFD45109B}.Debug|Default.ActiveCfg = Debug|x86 + {36CCB19E-92EB-48C7-9615-98EEFD45109B}.Debug|Default.Build.0 = Debug|x86 + {36CCB19E-92EB-48C7-9615-98EEFD45109B}.Release|Default.ActiveCfg = Release|x86 + {36CCB19E-92EB-48C7-9615-98EEFD45109B}.Release|Default.Build.0 = Release|x86 + {A308F679-51A3-4006-92D5-BAEC7EBD01A1}.Debug|Default.ActiveCfg = Debug|Any CPU + {A308F679-51A3-4006-92D5-BAEC7EBD01A1}.Debug|Default.Build.0 = Debug|Any CPU + {A308F679-51A3-4006-92D5-BAEC7EBD01A1}.Release|Default.ActiveCfg = Release|Any CPU + {A308F679-51A3-4006-92D5-BAEC7EBD01A1}.Release|Default.Build.0 = Release|Any CPU + {EA4F1E80-743F-4A1D-9757-AE66904A196A}.Debug|Default.ActiveCfg = Debug|x86 + {EA4F1E80-743F-4A1D-9757-AE66904A196A}.Debug|Default.Build.0 = Debug|x86 + {EA4F1E80-743F-4A1D-9757-AE66904A196A}.Release|Default.ActiveCfg = Release|x86 + {EA4F1E80-743F-4A1D-9757-AE66904A196A}.Release|Default.Build.0 = Release|x86 + {80AD8528-AA49-4731-B4A6-C691845815A1}.Debug|Default.ActiveCfg = Debug|Any CPU + {80AD8528-AA49-4731-B4A6-C691845815A1}.Debug|Default.Build.0 = Debug|Any CPU + {80AD8528-AA49-4731-B4A6-C691845815A1}.Release|Default.ActiveCfg = Release|Any CPU + {80AD8528-AA49-4731-B4A6-C691845815A1}.Release|Default.Build.0 = Release|Any CPU + {0CF97929-B0D0-4D73-B7BF-4FF7191035F9}.Debug|Default.ActiveCfg = Debug|Any CPU + {0CF97929-B0D0-4D73-B7BF-4FF7191035F9}.Debug|Default.Build.0 = Debug|Any CPU + {0CF97929-B0D0-4D73-B7BF-4FF7191035F9}.Release|Default.ActiveCfg = Release|Any CPU + {0CF97929-B0D0-4D73-B7BF-4FF7191035F9}.Release|Default.Build.0 = Release|Any CPU + {E272EB5D-8C57-417E-8E60-C1079D3F53C4}.Debug|Default.ActiveCfg = Debug|x86 + {E272EB5D-8C57-417E-8E60-C1079D3F53C4}.Debug|Default.Build.0 = Debug|x86 + {E272EB5D-8C57-417E-8E60-C1079D3F53C4}.Release|Default.ActiveCfg = Release|x86 + {E272EB5D-8C57-417E-8E60-C1079D3F53C4}.Release|Default.Build.0 = Release|x86 + {EA5CFD2E-9453-4D29-B80F-8E0EA23F4AC6}.Debug|Default.ActiveCfg = Debug|Any CPU + {EA5CFD2E-9453-4D29-B80F-8E0EA23F4AC6}.Debug|Default.Build.0 = Debug|Any CPU + {EA5CFD2E-9453-4D29-B80F-8E0EA23F4AC6}.Release|Default.ActiveCfg = Release|Any CPU + {EA5CFD2E-9453-4D29-B80F-8E0EA23F4AC6}.Release|Default.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/SMAPI/Framework/Content/ContentCache.cs b/src/SMAPI/Framework/Content/ContentCache.cs index c2818cdd..a5dfac9d 100644 --- a/src/SMAPI/Framework/Content/ContentCache.cs +++ b/src/SMAPI/Framework/Content/ContentCache.cs @@ -4,8 +4,8 @@ using System.Diagnostics.Contracts; using System.Linq; using Microsoft.Xna.Framework; using StardewModdingAPI.Framework.Reflection; -using StardewModdingAPI.Framework.Utilities; using StardewModdingAPI.Internal; +using StardewModdingAPI.Toolkit.Utilities; using StardewValley; namespace StardewModdingAPI.Framework.Content diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 15d39a5c..1336f3e9 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -8,8 +8,8 @@ using Microsoft.Xna.Framework.Content; using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.ContentManagers; using StardewModdingAPI.Framework.Reflection; -using StardewModdingAPI.Framework.Utilities; using StardewModdingAPI.Metadata; +using StardewModdingAPI.Toolkit.Utilities; using StardewValley; namespace StardewModdingAPI.Framework diff --git a/src/SMAPI/Framework/ContentPack.cs b/src/SMAPI/Framework/ContentPack.cs index 071fb872..ee6df1ec 100644 --- a/src/SMAPI/Framework/ContentPack.cs +++ b/src/SMAPI/Framework/ContentPack.cs @@ -3,7 +3,7 @@ using System.IO; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Framework.Serialisation; -using StardewModdingAPI.Framework.Utilities; +using StardewModdingAPI.Toolkit.Utilities; using xTile; namespace StardewModdingAPI.Framework diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index ce26c980..671dc21e 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -9,7 +9,7 @@ using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Framework.ContentManagers; using StardewModdingAPI.Framework.Exceptions; -using StardewModdingAPI.Framework.Utilities; +using StardewModdingAPI.Toolkit.Utilities; using StardewValley; using xTile; using xTile.Format; diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index 1e07dafa..e8726938 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -6,7 +6,7 @@ using StardewModdingAPI.Events; using StardewModdingAPI.Framework.Input; using StardewModdingAPI.Framework.Models; using StardewModdingAPI.Framework.Serialisation; -using StardewModdingAPI.Framework.Utilities; +using StardewModdingAPI.Toolkit.Utilities; namespace StardewModdingAPI.Framework.ModHelpers { diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index b5339183..d46caa55 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -7,7 +7,7 @@ using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.ModData; using StardewModdingAPI.Framework.Models; using StardewModdingAPI.Framework.Serialisation; -using StardewModdingAPI.Framework.Utilities; +using StardewModdingAPI.Toolkit.Utilities; namespace StardewModdingAPI.Framework.ModLoading { diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 76c12351..d5f5fdcd 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -25,9 +25,9 @@ using StardewModdingAPI.Framework.ModHelpers; using StardewModdingAPI.Framework.ModLoading; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Framework.Serialisation; -using StardewModdingAPI.Framework.Utilities; using StardewModdingAPI.Internal; using StardewModdingAPI.Internal.Models; +using StardewModdingAPI.Toolkit.Utilities; using StardewValley; using Monitor = StardewModdingAPI.Framework.Monitor; using SObject = StardewValley.Object; diff --git a/src/SMAPI/SemanticVersion.cs b/src/SMAPI/SemanticVersion.cs index 9db1cf14..3ee3ccf3 100644 --- a/src/SMAPI/SemanticVersion.cs +++ b/src/SMAPI/SemanticVersion.cs @@ -1,6 +1,5 @@ using System; using Newtonsoft.Json; -using StardewModdingAPI.Internal; namespace StardewModdingAPI { @@ -11,7 +10,7 @@ namespace StardewModdingAPI ** Properties *********/ /// <summary>The underlying semantic version implementation.</summary> - private readonly SemanticVersionImpl Version; + private readonly Toolkit.ISemanticVersion Version; /********* @@ -40,20 +39,20 @@ namespace StardewModdingAPI /// <param name="build">An optional build tag.</param> [JsonConstructor] public SemanticVersion(int majorVersion, int minorVersion, int patchVersion, string build = null) - : this(new SemanticVersionImpl(majorVersion, minorVersion, patchVersion, build)) { } + : this(new Toolkit.SemanticVersion(majorVersion, minorVersion, patchVersion, build)) { } /// <summary>Construct an instance.</summary> /// <param name="version">The semantic version string.</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) - : this(new SemanticVersionImpl(version)) { } + : this(new Toolkit.SemanticVersion(version)) { } /// <summary>Construct an instance.</summary> /// <param name="version">The assembly version.</param> /// <exception cref="ArgumentNullException">The <paramref name="version"/> is null.</exception> public SemanticVersion(Version version) - : this(new SemanticVersionImpl(version)) { } + : this(new Toolkit.SemanticVersion(version)) { } /// <summary>Whether this is a pre-release version.</summary> public bool IsPrerelease() @@ -67,7 +66,8 @@ namespace StardewModdingAPI /// <remarks>The implementation is defined by Semantic Version 2.0 (http://semver.org/).</remarks> public int CompareTo(ISemanticVersion other) { - return this.Version.CompareTo(other.MajorVersion, other.MinorVersion, other.PatchVersion, other.Build); + Toolkit.ISemanticVersion toolkitOther = new Toolkit.SemanticVersion(other.MajorVersion, other.MinorVersion, other.PatchVersion, other.Build); + return this.Version.CompareTo(toolkitOther); } /// <summary>Get whether this version is older than the specified version.</summary> @@ -137,7 +137,7 @@ namespace StardewModdingAPI /// <returns>Returns whether parsing the version succeeded.</returns> internal static bool TryParse(string version, out ISemanticVersion parsed) { - if (SemanticVersionImpl.TryParse(version, out SemanticVersionImpl versionImpl)) + if (Toolkit.SemanticVersion.TryParse(version, out Toolkit.ISemanticVersion versionImpl)) { parsed = new SemanticVersion(versionImpl); return true; @@ -153,7 +153,7 @@ namespace StardewModdingAPI *********/ /// <summary>Construct an instance.</summary> /// <param name="version">The underlying semantic version implementation.</param> - private SemanticVersion(SemanticVersionImpl version) + private SemanticVersion(Toolkit.ISemanticVersion version) { this.Version = version; } diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj index f4aee551..96b3aa5b 100644 --- a/src/SMAPI/StardewModdingAPI.csproj +++ b/src/SMAPI/StardewModdingAPI.csproj @@ -176,7 +176,6 @@ <Compile Include="Framework\StateTracking\LocationTracker.cs" /> <Compile Include="Framework\StateTracking\PlayerTracker.cs" /> <Compile Include="Framework\Utilities\ContextHash.cs" /> - <Compile Include="Framework\Utilities\PathUtilities.cs" /> <Compile Include="IContentPack.cs" /> <Compile Include="IManifestContentPackFor.cs" /> <Compile Include="IMultiplayerHelper.cs" /> @@ -333,6 +332,12 @@ <ItemGroup> <Analyzer Include="..\SMAPI.ModBuildConfig.Analyzer\bin\netstandard1.3\StardewModdingAPI.ModBuildConfig.Analyzer.dll" /> </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\StardewModdingAPI.Toolkit\StardewModdingAPI.Toolkit.csproj"> + <Project>{ea5cfd2e-9453-4d29-b80f-8e0ea23f4ac6}</Project> + <Name>StardewModdingAPI.Toolkit</Name> + </ProjectReference> + </ItemGroup> <Import Project="..\SMAPI.Internal\SMAPI.Internal.projitems" Label="Shared" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="..\..\build\common.targets" /> diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityClient.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityClient.cs new file mode 100644 index 00000000..d0da42df --- /dev/null +++ b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityClient.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading.Tasks; +using HtmlAgilityPack; +using Pathoschild.Http.Client; + +namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki +{ + /// <summary>An HTTP client for fetching mod metadata from the wiki compatibility list.</summary> + public class WikiCompatibilityClient : IDisposable + { + /********* + ** Properties + *********/ + /// <summary>The underlying HTTP client.</summary> + private readonly IClient Client; + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="userAgent">The user agent for the wiki API.</param> + /// <param name="baseUrl">The base URL for the wiki API.</param> + public WikiCompatibilityClient(string userAgent, string baseUrl = "https://stardewvalleywiki.com/mediawiki/api.php") + { + this.Client = new FluentClient(baseUrl).SetUserAgent(userAgent); + } + + /// <summary>Fetch mod compatibility entries.</summary> + public async Task<WikiCompatibilityEntry[]> FetchAsync() + { + // fetch HTML + ResponseModel response = await this.Client + .GetAsync("") + .WithArguments(new + { + action = "parse", + page = "Modding:SMAPI_compatibility", + format = "json" + }) + .As<ResponseModel>(); + string html = response.Parse.Text["*"]; + + // parse HTML + var doc = new HtmlDocument(); + doc.LoadHtml(html); + + // find mod entries + HtmlNodeCollection modNodes = doc.DocumentNode.SelectNodes("table[@id='mod-list']//tr[@class='mod']"); + if (modNodes == null) + throw new InvalidOperationException("Can't parse wiki compatibility list, no mods found."); + + // parse + return this.ParseEntries(modNodes).ToArray(); + } + + /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary> + public void Dispose() + { + this.Client?.Dispose(); + } + + + /********* + ** Private methods + *********/ + /// <summary>Parse valid mod compatibility entries.</summary> + /// <param name="nodes">The HTML compatibility entries.</param> + private IEnumerable<WikiCompatibilityEntry> ParseEntries(IEnumerable<HtmlNode> nodes) + { + foreach (HtmlNode node in nodes) + { + // parse status + WikiCompatibilityStatus status; + { + string rawStatus = node.GetAttributeValue("data-status", null); + if (rawStatus == null) + continue; // not a mod node? + if (!Enum.TryParse(rawStatus, true, out status)) + throw new InvalidOperationException($"Unknown status '{rawStatus}' when parsing compatibility list."); + } + + // parse unofficial version + ISemanticVersion unofficialVersion = null; + { + string rawUnofficialVersion = node.GetAttributeValue("data-unofficial-version", null); + SemanticVersion.TryParse(rawUnofficialVersion, out unofficialVersion); + } + + // parse other fields + string name = node.Descendants("td").FirstOrDefault()?.InnerText?.Trim(); + string summary = node.Descendants("td").FirstOrDefault(p => p.GetAttributeValue("class", null) == "summary")?.InnerText.Trim(); + string[] ids = this.GetAttribute(node, "data-id")?.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(p => p.Trim()).ToArray() ?? new string[0]; + int? nexusID = this.GetNullableIntAttribute(node, "data-nexus-id"); + int? chucklefishID = this.GetNullableIntAttribute(node, "data-chucklefish-id"); + string githubRepo = this.GetAttribute(node, "data-github"); + string customSourceUrl = this.GetAttribute(node, "data-custom-source"); + string customUrl = this.GetAttribute(node, "data-custom-url"); + + // yield model + yield return new WikiCompatibilityEntry + { + ID = ids, + Name = name, + Status = status, + NexusID = nexusID, + ChucklefishID = chucklefishID, + GitHubRepo = githubRepo, + CustomSourceUrl = customSourceUrl, + CustomUrl = customUrl, + UnofficialVersion = unofficialVersion, + Summary = summary + }; + } + } + + /// <summary>Get a nullable integer attribute value.</summary> + /// <param name="node">The HTML node.</param> + /// <param name="attributeName">The attribute name.</param> + private int? GetNullableIntAttribute(HtmlNode node, string attributeName) + { + string raw = this.GetAttribute(node, attributeName); + if (raw != null && int.TryParse(raw, out int value)) + return value; + return null; + } + + /// <summary>Get a strings attribute value.</summary> + /// <param name="node">The HTML node.</param> + /// <param name="attributeName">The attribute name.</param> + private string GetAttribute(HtmlNode node, string attributeName) + { + string raw = node.GetAttributeValue(attributeName, null); + if (raw != null) + raw = HtmlEntity.DeEntitize(raw); + return raw; + } + + /// <summary>The response model for the MediaWiki parse API.</summary> + [SuppressMessage("ReSharper", "ClassNeverInstantiated.Local")] + [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")] + private class ResponseModel + { + /// <summary>The parse API results.</summary> + public ResponseParseModel Parse { get; set; } + } + + /// <summary>The inner response model for the MediaWiki parse API.</summary> + [SuppressMessage("ReSharper", "ClassNeverInstantiated.Local")] + [SuppressMessage("ReSharper", "CollectionNeverUpdated.Local")] + [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")] + private class ResponseParseModel + { + /// <summary>The parsed text.</summary> + public IDictionary<string, string> Text { get; set; } + } + } +} diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityEntry.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityEntry.cs new file mode 100644 index 00000000..8bc66e20 --- /dev/null +++ b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityEntry.cs @@ -0,0 +1,36 @@ +namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki +{ + /// <summary>An entry in the mod compatibility list.</summary> + public class WikiCompatibilityEntry + { + /// <summary>The mod's unique ID. A mod may have multiple current IDs in rare cases (e.g. due to parallel releases or unofficial updates).</summary> + public string[] ID { get; set; } + + /// <summary>The mod's display name.</summary> + public string Name { get; set; } + + /// <summary>The mod ID on Nexus.</summary> + public int? NexusID { get; set; } + + /// <summary>The mod ID in the Chucklefish mod repo.</summary> + public int? ChucklefishID { get; set; } + + /// <summary>The GitHub repository in the form 'owner/repo'.</summary> + public string GitHubRepo { get; set; } + + /// <summary>The URL to a non-GitHub source repo.</summary> + public string CustomSourceUrl { get; set; } + + /// <summary>The custom mod page URL (if applicable).</summary> + public string CustomUrl { get; set; } + + /// <summary>The version of the latest unofficial update, if applicable.</summary> + public ISemanticVersion UnofficialVersion { get; set; } + + /// <summary>The compatibility status.</summary> + public WikiCompatibilityStatus Status { get; set; } + + /// <summary>The human-readable summary of the compatibility status or workaround, without HTML formatitng.</summary> + public string Summary { get; set; } + } +} diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityStatus.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityStatus.cs new file mode 100644 index 00000000..a1d2dfae --- /dev/null +++ b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityStatus.cs @@ -0,0 +1,27 @@ +namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki +{ + /// <summary>The compatibility status for a mod.</summary> + public enum WikiCompatibilityStatus + { + /// <summary>The mod is compatible.</summary> + Ok = 0, + + /// <summary>The mod is compatible if you use an optional official download.</summary> + Optional = 1, + + /// <summary>The mod is compatible if you use an unofficial update.</summary> + Unofficial = 2, + + /// <summary>The mod isn't compatible, but the player can fix it or there's a good alternative.</summary> + Workaround = 3, + + /// <summary>The mod isn't compatible.</summary> + Broken = 4, + + /// <summary>The mod is no longer maintained by the author, and an unofficial update or continuation is unlikely.</summary> + Abandoned = 5, + + /// <summary>The mod is no longer needed and should be removed.</summary> + Obsolete = 6 + } +} diff --git a/src/StardewModdingAPI.Toolkit/ISemanticVersion.cs b/src/StardewModdingAPI.Toolkit/ISemanticVersion.cs new file mode 100644 index 00000000..ca62d393 --- /dev/null +++ b/src/StardewModdingAPI.Toolkit/ISemanticVersion.cs @@ -0,0 +1,46 @@ +using System; + +namespace StardewModdingAPI.Toolkit +{ + /// <summary>A semantic version with an optional release tag.</summary> + public interface ISemanticVersion : IComparable<ISemanticVersion>, IEquatable<ISemanticVersion> + { + /********* + ** Accessors + *********/ + /// <summary>The major version incremented for major API changes.</summary> + int Major { get; } + + /// <summary>The minor version incremented for backwards-compatible changes.</summary> + int Minor { get; } + + /// <summary>The patch version for backwards-compatible bug fixes.</summary> + int Patch { get; } + + /// <summary>An optional prerelease tag.</summary> + string Tag { get; } + + + /********* + ** Accessors + *********/ + /// <summary>Whether this is a pre-release version.</summary> + bool IsPrerelease(); + + /// <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); + + /// <summary>Get whether this version is between two specified versions (inclusively).</summary> + /// <param name="min">The minimum version.</param> + /// <param name="max">The maximum version.</param> + bool IsBetween(ISemanticVersion min, ISemanticVersion max); + + /// <summary>Get a string representation of the version.</summary> + string ToString(); + } +} diff --git a/src/StardewModdingAPI.Toolkit/ModToolkit.cs b/src/StardewModdingAPI.Toolkit/ModToolkit.cs new file mode 100644 index 00000000..6136186e --- /dev/null +++ b/src/StardewModdingAPI.Toolkit/ModToolkit.cs @@ -0,0 +1,33 @@ +using System.Threading.Tasks; +using StardewModdingAPI.Toolkit.Framework.Clients.Wiki; + +namespace StardewModdingAPI.Toolkit +{ + /// <summary>A convenience wrapper for the various tools.</summary> + public class ModToolkit + { + /********* + ** Properties + *********/ + /// <summary>The default HTTP user agent for the toolkit.</summary> + private readonly string UserAgent; + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + public ModToolkit() + { + ISemanticVersion version = new SemanticVersion(this.GetType().Assembly.GetName().Version); + this.UserAgent = $"SMAPI Mod Handler Toolkit/{version}"; + } + + /// <summary>Extract mod metadata from the wiki compatibility list.</summary> + public async Task<WikiCompatibilityEntry[]> GetWikiCompatibilityListAsync() + { + var client = new WikiCompatibilityClient(this.UserAgent); + return await client.FetchAsync(); + } + } +} diff --git a/src/StardewModdingAPI.Toolkit/Properties/AssemblyInfo.cs b/src/StardewModdingAPI.Toolkit/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..22dcdd96 --- /dev/null +++ b/src/StardewModdingAPI.Toolkit/Properties/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +[assembly: AssemblyTitle("SMAPI.Toolkit")] +[assembly: AssemblyDescription("A library which encapsulates mod-handling logic for mod managers and tools. Not intended for use by mods.")] +[assembly: AssemblyProduct("SMAPI Toolkit")] +[assembly: AssemblyVersion("0.1.0")] +[assembly: AssemblyFileVersion("0.1.0")] +[assembly: InternalsVisibleTo("StardewModdingAPI")] +[assembly: InternalsVisibleTo("StardewModdingAPI.Web")] diff --git a/src/SMAPI.Internal/SemanticVersionImpl.cs b/src/StardewModdingAPI.Toolkit/SemanticVersion.cs index 7ae34f07..bd85f990 100644 --- a/src/SMAPI.Internal/SemanticVersionImpl.cs +++ b/src/StardewModdingAPI.Toolkit/SemanticVersion.cs @@ -1,13 +1,30 @@ using System; using System.Text.RegularExpressions; -namespace StardewModdingAPI.Internal +namespace StardewModdingAPI.Toolkit { - /// <summary>A low-level implementation of a semantic version with an optional release tag.</summary> + /// <summary>A semantic version with an optional release tag.</summary> /// <remarks>The implementation is defined by Semantic Version 2.0 (http://semver.org/).</remarks> - internal class SemanticVersionImpl : IComparable<SemanticVersionImpl> + public class SemanticVersion : ISemanticVersion { /********* + ** Properties + *********/ + /// <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>(?>[a-z0-9]+[\-\.]?)+))?"; + + /// <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> + internal static readonly Regex Regex = new Regex($@"^{SemanticVersion.UnboundedVersionPattern}$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture); + + + /********* ** Accessors *********/ /// <summary>The major version incremented for major API changes.</summary> @@ -22,18 +39,6 @@ namespace StardewModdingAPI.Internal /// <summary>An optional prerelease tag.</summary> public string Tag { get; } - /// <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>(?>[a-z0-9]+[\-\.]?)+))?"; - - /// <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> - internal static readonly Regex Regex = new Regex($@"^{SemanticVersionImpl.UnboundedVersionPattern}$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture); /********* ** Public methods @@ -41,9 +46,9 @@ namespace StardewModdingAPI.Internal /// <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="patch">The patch version for backwards-compatible fixes.</param> /// <param name="tag">An optional prerelease tag.</param> - public SemanticVersionImpl(int major, int minor, int patch, string tag = null) + public SemanticVersion(int major, int minor, int patch, string tag = null) { this.Major = major; this.Minor = minor; @@ -54,7 +59,7 @@ namespace StardewModdingAPI.Internal /// <summary>Construct an instance.</summary> /// <param name="version">The assembly version.</param> /// <exception cref="ArgumentNullException">The <paramref name="version"/> is null.</exception> - public SemanticVersionImpl(Version version) + public SemanticVersion(Version version) { if (version == null) throw new ArgumentNullException(nameof(version), "The input version can't be null."); @@ -68,12 +73,12 @@ namespace StardewModdingAPI.Internal /// <param name="version">The semantic version string.</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 SemanticVersionImpl(string version) + public SemanticVersion(string version) { // parse if (version == null) throw new ArgumentNullException(nameof(version), "The input version string can't be null."); - var match = SemanticVersionImpl.Regex.Match(version.Trim()); + var match = SemanticVersion.Regex.Match(version.Trim()); if (!match.Success) throw new FormatException($"The input '{version}' isn't a valid semantic version."); @@ -87,20 +92,100 @@ namespace StardewModdingAPI.Internal /// <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> /// <exception cref="ArgumentNullException">The <paramref name="other"/> value is null.</exception> - public int CompareTo(SemanticVersionImpl other) + public int CompareTo(ISemanticVersion other) { if (other == null) throw new ArgumentNullException(nameof(other)); return this.CompareTo(other.Major, other.Minor, other.Patch, other.Tag); } + /// <summary>Indicates whether the current object is equal to another object of the same type.</summary> + /// <returns>true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false.</returns> + /// <param name="other">An object to compare with this object.</param> + public bool Equals(ISemanticVersion other) + { + return other != null && this.CompareTo(other) == 0; + } + + /// <summary>Whether this is a pre-release version.</summary> + public bool IsPrerelease() + { + return !string.IsNullOrWhiteSpace(this.Tag); + } + + /// <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 whether this version is between two specified versions (inclusively).</summary> + /// <param name="min">The minimum version.</param> + /// <param name="max">The maximum version.</param> + public bool IsBetween(ISemanticVersion min, ISemanticVersion max) + { + return this.CompareTo(min) >= 0 && this.CompareTo(max) <= 0; + } + + /// <summary>Get a string representation of the version.</summary> + public override string ToString() + { + // version + string result = this.Patch != 0 + ? $"{this.Major}.{this.Minor}.{this.Patch}" + : $"{this.Major}.{this.Minor}"; + + // tag + string tag = this.Tag; + if (tag != null) + result += $"-{tag}"; + return result; + } + + /// <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) + { + try + { + parsed = new SemanticVersion(version); + return true; + } + catch + { + parsed = null; + return false; + } + } + + + /********* + ** Private methods + *********/ + /// <summary>Get a normalised build tag.</summary> + /// <param name="tag">The tag to normalise.</param> + private string GetNormalisedTag(string tag) + { + tag = tag?.Trim(); + return !string.IsNullOrWhiteSpace(tag) ? tag : null; + } /// <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="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="otherTag">The prerelease tag to compare with this instance.</param> - public int CompareTo(int otherMajor, int otherMinor, int otherPatch, string otherTag) + private int CompareTo(int otherMajor, int otherMinor, int otherPatch, string otherTag) { const int same = 0; const int curNewer = 1; @@ -148,52 +233,7 @@ namespace StardewModdingAPI.Internal } // fallback (this should never happen) - return string.Compare(this.ToString(), new SemanticVersionImpl(otherMajor, otherMinor, otherPatch, otherTag).ToString(), StringComparison.InvariantCultureIgnoreCase); - } - - /// <summary>Get a string representation of the version.</summary> - public override string ToString() - { - // version - string result = this.Patch != 0 - ? $"{this.Major}.{this.Minor}.{this.Patch}" - : $"{this.Major}.{this.Minor}"; - - // tag - string tag = this.Tag; - if (tag != null) - result += $"-{tag}"; - return result; - } - - /// <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> - internal static bool TryParse(string version, out SemanticVersionImpl parsed) - { - try - { - parsed = new SemanticVersionImpl(version); - return true; - } - catch - { - parsed = null; - return false; - } - } - - - /********* - ** Private methods - *********/ - /// <summary>Get a normalised build tag.</summary> - /// <param name="tag">The tag to normalise.</param> - private string GetNormalisedTag(string tag) - { - tag = tag?.Trim(); - return !string.IsNullOrWhiteSpace(tag) ? tag : null; + return string.Compare(this.ToString(), new SemanticVersion(otherMajor, otherMinor, otherPatch, otherTag).ToString(), StringComparison.InvariantCultureIgnoreCase); } } } diff --git a/src/StardewModdingAPI.Toolkit/StardewModdingAPI.Toolkit.csproj b/src/StardewModdingAPI.Toolkit/StardewModdingAPI.Toolkit.csproj new file mode 100644 index 00000000..6688a2a1 --- /dev/null +++ b/src/StardewModdingAPI.Toolkit/StardewModdingAPI.Toolkit.csproj @@ -0,0 +1,20 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFrameworks>net4.5;netstandard2.0</TargetFrameworks> + <GenerateAssemblyInfo>false</GenerateAssemblyInfo> + </PropertyGroup> + + <PropertyGroup> + <OutputPath>..\..\bin\$(Configuration)\SMAPI.Toolkit</OutputPath> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="HtmlAgilityPack" Version="1.7.2" /> + <PackageReference Include="Newtonsoft.Json" Version="11.0.2" /> + <PackageReference Include="Pathoschild.Http.FluentClient" Version="3.2.0" /> + </ItemGroup> + + <Import Project="..\..\build\common.targets" /> + +</Project> diff --git a/src/SMAPI/Framework/Utilities/PathUtilities.cs b/src/StardewModdingAPI.Toolkit/Utilities/PathUtilities.cs index 51d45ebd..2e74e7d9 100644 --- a/src/SMAPI/Framework/Utilities/PathUtilities.cs +++ b/src/StardewModdingAPI.Toolkit/Utilities/PathUtilities.cs @@ -3,10 +3,10 @@ using System.Diagnostics.Contracts; using System.IO; using System.Linq; -namespace StardewModdingAPI.Framework.Utilities +namespace StardewModdingAPI.Toolkit.Utilities { /// <summary>Provides utilities for normalising file paths.</summary> - internal static class PathUtilities + public static class PathUtilities { /********* ** Properties |