#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;
#if BeatSaber
var index = str.IndexOf('_');
var versionString = index >= 0 ? str.Substring(0, index) : str;
var result = Version.TryParse(versionString, out var version);
#else
var result = Version.TryParse(str, out var version);
#endif
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());
}
}