#nullable enable using IPA.Config.Data; using IPA.Config.Stores; using IPA.Config.Stores.Converters; using System; using System.Collections.Generic; using SVersion = SemVer.Version; using Version = Hive.Versioning.Version; namespace IPA.Utilities { /// /// A type that wraps so that the string of the version is stored when the string is /// not a valid . /// public class AlmostVersion : IComparable, IComparable, #pragma warning disable CS0618 // Type or member is obsolete IComparable #pragma warning restore CS0618 // Type or member is obsolete { /// /// Represents a storage type of either parsed object or raw . /// public enum StoredAs { /// /// The version was stored as a . /// SemVer, /// /// The version was stored as a . /// String } /// /// Creates a new with the version string provided in . /// /// the version string to store public AlmostVersion(string vertext) { if (!TryParseFrom(vertext, StoredAs.SemVer)) _ = TryParseFrom(vertext, StoredAs.String); } /// /// Creates an from the provided in . /// /// the to store public AlmostVersion(Version ver) { SemverValue = ver; StorageMode = StoredAs.SemVer; } /// /// Creates an from the provided in . /// /// the to store [Obsolete("Use Hive.Versioning.Version constructor instead.")] public AlmostVersion(SVersion ver) : this(ver?.UnderlyingVersion ?? throw new ArgumentNullException(nameof(ver))) { } /// /// Creates an from the version string in stored using /// the storage mode specified in . /// /// the text to parse as an /// the storage mode to store the version in public AlmostVersion(string vertext, StoredAs mode) { if (!TryParseFrom(vertext, mode)) throw new ArgumentException($"{nameof(vertext)} could not be stored as {mode}!"); } /// /// Creates a new from the version string in stored the /// same way as the passed in . /// /// the text to parse as an /// an to copy the storage mode of public AlmostVersion(string vertext, AlmostVersion copyMode) { if (copyMode is null) throw new ArgumentNullException(nameof(copyMode)); if (!TryParseFrom(vertext, copyMode.StorageMode)) _ = TryParseFrom(vertext, StoredAs.String); // silently parse differently } private bool TryParseFrom(string str, StoredAs mode) { if (mode == StoredAs.SemVer) { StorageMode = StoredAs.SemVer; var result = Version.TryParse(str, out var version); SemverValue = version; return result; } else { StringValue = str; StorageMode = StoredAs.String; return true; } } /// /// The value of the if it was stored as a . /// /// the stored value as a , or if not stored as a string. public string? StringValue { get; private set; } /// /// The value of the if it was stored as a . /// /// the stored value as a , or if not stored as a version. public Version? SemverValue { get; private set; } /// /// The way the value is stored, whether it be as a or a . /// /// the storage mode used to store this value public StoredAs StorageMode { get; private set; } /// /// Gets a string representation of the current version. If the value is stored as a string, this returns it. If it is /// stored as a , it is equivalent to calling . /// /// a string representation of the current version /// public override string ToString() => StorageMode == StoredAs.SemVer ? SemverValue!.ToString() : StringValue!; /// /// Compares to the in using /// or , depending on the current store. /// /// /// The storage methods of the two objects must be the same, or this will throw an . /// /// the to compare to /// less than 0 if is considered bigger than , 0 if equal, and greater than zero if smaller /// public int CompareTo(AlmostVersion other) { if (other is null) return 1; return StorageMode == StoredAs.SemVer && other.StorageMode == StoredAs.SemVer ? SemverValue!.CompareTo(other.SemverValue!) : string.Compare(ToString(), other.ToString(), StringComparison.Ordinal); } /// /// Compares to the in using . /// /// /// The storage method of must be , else an will /// be thrown. /// /// the to compare to /// less than 0 if is considered bigger than , 0 if equal, and greater than zero if smaller /// public int CompareTo(Version other) { if (StorageMode != StoredAs.SemVer) throw new InvalidOperationException("Cannot compare a SemVer version with an AlmostVersion stored as a string!"); return SemverValue!.CompareTo(other); } /// /// Compares to the in using . /// /// /// The storage method of must be , else an will /// be thrown. /// /// the to compare to /// less than 0 if is considered bigger than , 0 if equal, and greater than zero if smaller /// [Obsolete("Use the Hive.Versioning.Version overload instead.")] public int CompareTo(SVersion other) => CompareTo(other.UnderlyingVersion); /// /// Performs a strict equality check between and . /// /// /// This may return where returns /// /// the object to compare to /// if they are equal, otherwise /// public override bool Equals(object obj) { return obj is AlmostVersion version && SemverValue == version.SemverValue && StringValue == version.StringValue && StorageMode == version.StorageMode; } /// /// Default generated hash code function generated by VS. /// /// a value unique to each object, except those that are considered equal by /// public override int GetHashCode() { var hashCode = -126402897; hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(SemverValue); hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(StringValue); hashCode = (hashCode * -1521134295) + StorageMode.GetHashCode(); return hashCode; } /// /// Compares two versions, only taking into account the numeric part of the version if they are stored as s, /// or strict equality if they are stored as s. /// /// /// This is a looser equality than , meaning that this may return where /// does not. /// /// the first value to compare /// the second value to compare /// if they are mostly equal, otherwise /// public static bool operator==(AlmostVersion l, AlmostVersion r) { if (l is null && r is null) return true; if (l is null || r is null) return false; if (l.StorageMode != r.StorageMode) return false; return l.StorageMode == StoredAs.SemVer ? Utils.VersionCompareNoPrerelease(l.SemverValue!, r.SemverValue!) == 0 : l.StringValue == r.StringValue; } /// /// The opposite of . Equivalent to !(l == r). /// /// the first value to compare /// the second value to compare /// if they are not mostly equal, otherwise /// public static bool operator!=(AlmostVersion l, AlmostVersion r) => !(l == r); // implicitly convertible from Version #pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CA2225 // Operator overloads have named alternates /// /// Implicitly converts a to using . /// /// the to convert /// [Obsolete("Use Hive.Versioning.Version instead of SemVer.Version")] public static implicit operator AlmostVersion?(SVersion? ver) => ver is null ? null : new(ver); // implicitly convertible to Version /// /// Implicitly converts an to , if applicable, using . /// If not applicable, returns /// /// the to convert to a /// [Obsolete("Use Hive.Versioning.Version instead of SemVer.Version")] public static implicit operator SVersion?(AlmostVersion? av) => av?.SemverValue is not null ? SVersion.ForHiveVersion(av.SemverValue) : null; #pragma warning restore CS0618 // Type or member is obsolete /// /// Implicitly converts a to using . /// /// the to convert /// public static implicit operator AlmostVersion?(Version? ver) => ver is null ? null : new(ver); // implicitly convertible to Version /// /// Implicitly converts an to , if applicable, using . /// If not applicable, returns /// /// the to convert to a /// public static implicit operator Version?(AlmostVersion av) => av?.SemverValue; #pragma warning restore CA2225 // Operator overloads have named alternates public static bool operator <(AlmostVersion left, AlmostVersion right) => left is null ? right is not null : left.CompareTo(right) < 0; public static bool operator <=(AlmostVersion left, AlmostVersion right) => left is null || left.CompareTo(right) <= 0; public static bool operator >(AlmostVersion left, AlmostVersion right) => left is not null && left.CompareTo(right) > 0; public static bool operator >=(AlmostVersion left, AlmostVersion right) => left is null ? right is null : left.CompareTo(right) >= 0; } /// /// A for s. /// public sealed class AlmostVersionConverter : ValueConverter { /// /// Converts a node into an . /// /// the node to convert /// the owner of the new object /// public override AlmostVersion? FromValue(Value? value, object parent) => Converter.Default.FromValue(value, parent) switch { { } v => new(v), _ => null }; /// /// Converts an to a node. /// /// the to convert /// the parent of /// a node representing public override Value? ToValue(AlmostVersion? obj, object parent) => Value.From(obj?.ToString()); } }