diff --git a/SubSonic.Core.Extensions/Collections.cs b/SubSonic.Core.Extensions/Collections.cs index 86bfa0f..d0ed3de 100644 --- a/SubSonic.Core.Extensions/Collections.cs +++ b/SubSonic.Core.Extensions/Collections.cs @@ -3,7 +3,7 @@ namespace SubSonic.Core { - public static partial class Extensions + public static partial class CoreExtensions { public static void AddIfNotExist(this ICollection collection, TType element) { diff --git a/SubSonic.Core.Extensions/Enum.cs b/SubSonic.Core.Extensions/Enum.cs index 15881c0..8135a3f 100644 --- a/SubSonic.Core.Extensions/Enum.cs +++ b/SubSonic.Core.Extensions/Enum.cs @@ -4,7 +4,7 @@ namespace SubSonic.Core { - public static partial class Extensions + public static partial class CoreExtensions { public static TEnum Parse(this string source) where TEnum: struct diff --git a/SubSonic.Core.Extensions/SemVersion.cs b/SubSonic.Core.Extensions/SemVersion.cs new file mode 100644 index 0000000..b38d26a --- /dev/null +++ b/SubSonic.Core.Extensions/SemVersion.cs @@ -0,0 +1,248 @@ +// +// Copyright (c) Microsoft Corp (https://www.microsoft.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Globalization; +using System.Text; +using System.Text.RegularExpressions; + +namespace SubSonic.Core.Extensions +{ + public struct SemVersion : IComparable, IComparable, IEquatable + { + public static SemVersion Zero { get; } = new SemVersion(0, 0, 0, null, null, "0.0.0"); + + static readonly Regex SemVerRegex = + new Regex( + @"(?0|(?:[1-9]\d*))(?:\.(?0|(?:[1-9]\d*))(?:\.(?0|(?:[1-9]\d*)))?(?:\-(?[0-9A-Z\.-]+))?(?:\+(?[0-9A-Z\.-]+))?)?", + RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase + ); + + + public int Major { get; } + public int Minor { get; } + public int Patch { get; } + public string PreRelease { get; } + public string Meta { get; } + public bool IsPreRelease { get; } + public bool HasMeta { get; } + public string VersionString { get; } + + public SemVersion(int major, int minor, int patch, string preRelease = null, string meta = null) : + this(major, minor, patch, preRelease, meta, null) + { + } + + SemVersion(int major, int minor, int patch, string preRelease, string meta, string versionString) + { + Major = major; + Minor = minor; + Patch = patch; + IsPreRelease = !string.IsNullOrEmpty(preRelease); + HasMeta = !string.IsNullOrEmpty(meta); + PreRelease = IsPreRelease ? preRelease : null; + Meta = HasMeta ? meta : null; + + if (!string.IsNullOrEmpty(versionString)) + { + VersionString = versionString; + } + else + { + var sb = new StringBuilder(); + sb.AppendFormat(CultureInfo.InvariantCulture, "{0}.{1}.{2}", Major, Minor, Patch); + + if (IsPreRelease) + { + sb.AppendFormat(CultureInfo.InvariantCulture, "-{0}", PreRelease); + } + + if (HasMeta) + { + sb.AppendFormat(CultureInfo.InvariantCulture, "+{0}", Meta); + } + + VersionString = sb.ToString(); + } + } + + public static bool TryParse(string version, out SemVersion semVersion) + { + semVersion = Zero; + + if (string.IsNullOrEmpty(version)) + { + return false; + } + + var match = SemVerRegex.Match(version); + if (!match.Success) + { + return false; + } + + if (!int.TryParse( + match.Groups["Major"].Value, + NumberStyles.Integer, + CultureInfo.InvariantCulture, + out var major) || + !int.TryParse( + match.Groups["Minor"].Value, + NumberStyles.Integer, + CultureInfo.InvariantCulture, + out var minor) || + !int.TryParse( + match.Groups["Patch"].Value, + NumberStyles.Integer, + CultureInfo.InvariantCulture, + out var patch)) + { + return false; + } + + semVersion = new SemVersion( + major, + minor, + patch, + match.Groups["PreRelease"]?.Value, + match.Groups["Meta"]?.Value, + version); + + return true; + } + + + + public bool Equals(SemVersion other) + { + return Major == other.Major + && Minor == other.Minor + && Patch == other.Patch + && string.Equals(PreRelease, other.PreRelease, StringComparison.OrdinalIgnoreCase) + && string.Equals(Meta, other.Meta, StringComparison.OrdinalIgnoreCase); + } + + public int CompareTo(SemVersion other) + { + if (Equals(other)) + { + return 0; + } + + if (Major > other.Major) + { + return 1; + } + + if (Major < other.Major) + { + return -1; + } + + if (Minor > other.Minor) + { + return 1; + } + + if (Minor < other.Minor) + { + return -1; + } + + if (Patch > other.Patch) + { + return 1; + } + + if (Patch < other.Patch) + { + return -1; + } + + switch (StringComparer.InvariantCultureIgnoreCase.Compare(PreRelease, other.PreRelease)) + { + case 1: + return 1; + + case -1: + return -1; + + default: + return StringComparer.InvariantCultureIgnoreCase.Compare(Meta, other.Meta); + } + } + + public int CompareTo(object obj) + { + return obj is SemVersion semVersion + ? CompareTo(semVersion) + : -1; + } + + public override bool Equals(object obj) + { + return obj is SemVersion semVersion + && Equals(semVersion); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = Major; + hashCode = hashCode * 397 ^ Minor; + hashCode = hashCode * 397 ^ Patch; + hashCode = hashCode * 397 ^ (PreRelease != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(PreRelease) : 0); + hashCode = hashCode * 397 ^ (Meta != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(Meta) : 0); + return hashCode; + } + } + + public override string ToString() + => VersionString; + + // Define the is greater than operator. + public static bool operator >(SemVersion operand1, SemVersion operand2) + => operand1.CompareTo(operand2) == 1; + + // Define the is less than operator. + public static bool operator <(SemVersion operand1, SemVersion operand2) + => operand1.CompareTo(operand2) == -1; + + // Define the is greater than or equal to operator. + public static bool operator >=(SemVersion operand1, SemVersion operand2) + => operand1.CompareTo(operand2) >= 0; + + // Define the is less than or equal to operator. + public static bool operator <=(SemVersion operand1, SemVersion operand2) + => operand1.CompareTo(operand2) <= 0; + + public static bool operator ==(SemVersion left, SemVersion right) + { + return left.Equals(right); + } + + public static bool operator !=(SemVersion left, SemVersion right) + { + return !(left == right); + } + } +} \ No newline at end of file diff --git a/SubSonic.Core.Extensions/Strings.cs b/SubSonic.Core.Extensions/Strings.cs index 9c56951..e891b21 100644 --- a/SubSonic.Core.Extensions/Strings.cs +++ b/SubSonic.Core.Extensions/Strings.cs @@ -5,7 +5,7 @@ namespace SubSonic.Core { - public static partial class Extensions + public static partial class CoreExtensions { public static bool IsNullOrEmpty(this string source) { diff --git a/SubSonic.Core.Extensions/Utilities/Path.cs b/SubSonic.Core.Extensions/Utilities/Path.cs new file mode 100644 index 0000000..405d428 --- /dev/null +++ b/SubSonic.Core.Extensions/Utilities/Path.cs @@ -0,0 +1,30 @@ +using SubSonic.Core.Extensions; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace SubSonic.Core +{ + public partial class Utilities + { + public static string FindHighestVersionedDirectory(string parentFolder, Func validate) + { + string bestMatch = null; + var bestVersion = SemVersion.Zero; + foreach (var dir in Directory.EnumerateDirectories(parentFolder)) + { + var name = Path.GetFileName(dir); + if (SemVersion.TryParse(name, out var version) && version.Major >= 0) + { + if (version > bestVersion && (validate == null || validate(dir))) + { + bestVersion = version; + bestMatch = dir; + } + } + } + return bestMatch; + } + } +}