You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

329 lines
17 KiB

  1. #nullable enable
  2. using IPA.Config.Data;
  3. using IPA.Config.Stores;
  4. using IPA.Config.Stores.Converters;
  5. using System;
  6. using System.Collections.Generic;
  7. using SVersion = SemVer.Version;
  8. using Version = Hive.Versioning.Version;
  9. namespace IPA.Utilities
  10. {
  11. /// <summary>
  12. /// A type that wraps <see cref="Version"/> so that the string of the version is stored when the string is
  13. /// not a valid <see cref="Version"/>.
  14. /// </summary>
  15. public class AlmostVersion : IComparable<AlmostVersion>, IComparable<Version>,
  16. #pragma warning disable CS0618 // Type or member is obsolete
  17. IComparable<SVersion>
  18. #pragma warning restore CS0618 // Type or member is obsolete
  19. {
  20. /// <summary>
  21. /// Represents a storage type of either parsed <see cref="Version"/> object or raw <see cref="String"/>.
  22. /// </summary>
  23. public enum StoredAs
  24. {
  25. /// <summary>
  26. /// The version was stored as a <see cref="SVersion"/>.
  27. /// </summary>
  28. SemVer,
  29. /// <summary>
  30. /// The version was stored as a <see cref="String"/>.
  31. /// </summary>
  32. String
  33. }
  34. /// <summary>
  35. /// Creates a new <see cref="AlmostVersion"/> with the version string provided in <paramref name="vertext"/>.
  36. /// </summary>
  37. /// <param name="vertext">the version string to store</param>
  38. public AlmostVersion(string vertext)
  39. {
  40. if (!TryParseFrom(vertext, StoredAs.SemVer))
  41. _ = TryParseFrom(vertext, StoredAs.String);
  42. }
  43. /// <summary>
  44. /// Creates an <see cref="AlmostVersion"/> from the <see cref="Version"/> provided in <paramref name="ver"/>.
  45. /// </summary>
  46. /// <param name="ver">the <see cref="Version"/> to store</param>
  47. public AlmostVersion(Version ver)
  48. {
  49. SemverValue = ver;
  50. StorageMode = StoredAs.SemVer;
  51. }
  52. /// <summary>
  53. /// Creates an <see cref="AlmostVersion"/> from the <see cref="SVersion"/> provided in <paramref name="ver"/>.
  54. /// </summary>
  55. /// <param name="ver">the <see cref="SVersion"/> to store</param>
  56. [Obsolete("Use Hive.Versioning.Version constructor instead.")]
  57. public AlmostVersion(SVersion ver) : this(ver?.UnderlyingVersion ?? throw new ArgumentNullException(nameof(ver))) { }
  58. /// <summary>
  59. /// Creates an <see cref="AlmostVersion"/> from the version string in <paramref name="vertext"/> stored using
  60. /// the storage mode specified in <paramref name="mode"/>.
  61. /// </summary>
  62. /// <param name="vertext">the text to parse as an <see cref="AlmostVersion"/></param>
  63. /// <param name="mode">the storage mode to store the version in</param>
  64. public AlmostVersion(string vertext, StoredAs mode)
  65. {
  66. if (!TryParseFrom(vertext, mode))
  67. throw new ArgumentException($"{nameof(vertext)} could not be stored as {mode}!");
  68. }
  69. /// <summary>
  70. /// Creates a new <see cref="AlmostVersion"/> from the version string in <paramref name="vertext"/> stored the
  71. /// same way as the <see cref="AlmostVersion"/> passed in <paramref name="copyMode"/>.
  72. /// </summary>
  73. /// <param name="vertext">the text to parse as an <see cref="AlmostVersion"/></param>
  74. /// <param name="copyMode">an <see cref="AlmostVersion"/> to copy the storage mode of</param>
  75. public AlmostVersion(string vertext, AlmostVersion copyMode)
  76. {
  77. if (copyMode is null)
  78. throw new ArgumentNullException(nameof(copyMode));
  79. if (!TryParseFrom(vertext, copyMode.StorageMode))
  80. _ = TryParseFrom(vertext, StoredAs.String); // silently parse differently
  81. }
  82. private bool TryParseFrom(string str, StoredAs mode)
  83. {
  84. if (mode == StoredAs.SemVer)
  85. {
  86. StorageMode = StoredAs.SemVer;
  87. #if BeatSaber
  88. var index = str.IndexOf('_');
  89. var versionString = index >= 0 ? str.Substring(0, index) : str;
  90. var result = Version.TryParse(versionString, out var version);
  91. #else
  92. var result = Version.TryParse(str, out var version);
  93. #endif
  94. SemverValue = version;
  95. return result;
  96. }
  97. else
  98. {
  99. StringValue = str;
  100. StorageMode = StoredAs.String;
  101. return true;
  102. }
  103. }
  104. /// <summary>
  105. /// The value of the <see cref="AlmostVersion"/> if it was stored as a <see cref="string"/>.
  106. /// </summary>
  107. /// <value>the stored value as a <see cref="string"/>, or <see langword="null"/> if not stored as a string.</value>
  108. public string? StringValue { get; private set; }
  109. /// <summary>
  110. /// The value of the <see cref="AlmostVersion"/> if it was stored as a <see cref="Version"/>.
  111. /// </summary>
  112. /// <value>the stored value as a <see cref="Version"/>, or <see langword="null"/> if not stored as a version.</value>
  113. public Version? SemverValue { get; private set; }
  114. /// <summary>
  115. /// The way the value is stored, whether it be as a <see cref="Version"/> or a <see cref="string"/>.
  116. /// </summary>
  117. /// <value>the storage mode used to store this value</value>
  118. public StoredAs StorageMode { get; private set; }
  119. /// <summary>
  120. /// Gets a string representation of the current version. If the value is stored as a string, this returns it. If it is
  121. /// stored as a <see cref="Version"/>, it is equivalent to calling <see cref="Version.ToString()"/>.
  122. /// </summary>
  123. /// <returns>a string representation of the current version</returns>
  124. /// <seealso cref="object.ToString"/>
  125. public override string ToString() =>
  126. StorageMode == StoredAs.SemVer ? SemverValue!.ToString() : StringValue!;
  127. /// <summary>
  128. /// Compares <see langword="this"/> to the <see cref="AlmostVersion"/> in <paramref name="other"/> using <see cref="Version.CompareTo(Version)"/>
  129. /// or <see cref="string.CompareTo(string)"/>, depending on the current store.
  130. /// </summary>
  131. /// <remarks>
  132. /// The storage methods of the two objects must be the same, or this will throw an <see cref="InvalidOperationException"/>.
  133. /// </remarks>
  134. /// <param name="other">the <see cref="AlmostVersion"/> to compare to</param>
  135. /// <returns>less than 0 if <paramref name="other"/> is considered bigger than <see langword="this"/>, 0 if equal, and greater than zero if smaller</returns>
  136. /// <seealso cref="CompareTo(Version)"/>
  137. public int CompareTo(AlmostVersion other)
  138. {
  139. if (other is null) return 1;
  140. return StorageMode == StoredAs.SemVer && other.StorageMode == StoredAs.SemVer
  141. ? SemverValue!.CompareTo(other.SemverValue!)
  142. : string.Compare(ToString(), other.ToString(), StringComparison.Ordinal);
  143. }
  144. /// <summary>
  145. /// Compares <see langword="this"/> to the <see cref="Version"/> in <paramref name="other"/> using <see cref="Version.CompareTo(Version)"/>.
  146. /// </summary>
  147. /// <remarks>
  148. /// The storage method of <see langword="this"/> must be <see cref="StoredAs.SemVer"/>, else an <see cref="InvalidOperationException"/> will
  149. /// be thrown.
  150. /// </remarks>
  151. /// <param name="other">the <see cref="Version"/> to compare to</param>
  152. /// <returns>less than 0 if <paramref name="other"/> is considered bigger than <see langword="this"/>, 0 if equal, and greater than zero if smaller</returns>
  153. /// <seealso cref="CompareTo(AlmostVersion)"/>
  154. public int CompareTo(Version other)
  155. {
  156. if (StorageMode != StoredAs.SemVer)
  157. throw new InvalidOperationException("Cannot compare a SemVer version with an AlmostVersion stored as a string!");
  158. return SemverValue!.CompareTo(other);
  159. }
  160. /// <summary>
  161. /// Compares <see langword="this"/> to the <see cref="SVersion"/> in <paramref name="other"/> using <see cref="Version.CompareTo(Version)"/>.
  162. /// </summary>
  163. /// <remarks>
  164. /// The storage method of <see langword="this"/> must be <see cref="StoredAs.SemVer"/>, else an <see cref="InvalidOperationException"/> will
  165. /// be thrown.
  166. /// </remarks>
  167. /// <param name="other">the <see cref="SVersion"/> to compare to</param>
  168. /// <returns>less than 0 if <paramref name="other"/> is considered bigger than <see langword="this"/>, 0 if equal, and greater than zero if smaller</returns>
  169. /// <seealso cref="CompareTo(AlmostVersion)"/>
  170. [Obsolete("Use the Hive.Versioning.Version overload instead.")]
  171. public int CompareTo(SVersion other) => CompareTo(other.UnderlyingVersion);
  172. /// <summary>
  173. /// Performs a strict equality check between <see langword="this"/> and <paramref name="obj"/>.
  174. /// </summary>
  175. /// <remarks>
  176. /// This may return <see langword="false"/> where <see cref="operator ==(AlmostVersion, AlmostVersion)"/> returns <see langword="true"/>
  177. /// </remarks>
  178. /// <param name="obj">the object to compare to</param>
  179. /// <returns><see langword="true"/> if they are equal, <see langword="false"/> otherwise</returns>
  180. /// <seealso cref="object.Equals(object)"/>
  181. public override bool Equals(object? obj)
  182. {
  183. return obj is AlmostVersion version &&
  184. SemverValue == version.SemverValue &&
  185. StringValue == version.StringValue &&
  186. StorageMode == version.StorageMode;
  187. }
  188. /// <summary>
  189. /// Default generated hash code function generated by VS.
  190. /// </summary>
  191. /// <returns>a value unique to each object, except those that are considered equal by <see cref="Equals(object)"/></returns>
  192. /// <seealso cref="object.GetHashCode"/>
  193. public override int GetHashCode()
  194. {
  195. var hashCode = -126402897;
  196. hashCode = (hashCode * -1521134295) + EqualityComparer<Version?>.Default.GetHashCode(SemverValue);
  197. hashCode = (hashCode * -1521134295) + EqualityComparer<string?>.Default.GetHashCode(StringValue);
  198. hashCode = (hashCode * -1521134295) + StorageMode.GetHashCode();
  199. return hashCode;
  200. }
  201. /// <summary>
  202. /// Compares two versions, only taking into account the numeric part of the version if they are stored as <see cref="Version"/>s,
  203. /// or strict equality if they are stored as <see cref="string"/>s.
  204. /// </summary>
  205. /// <remarks>
  206. /// This is a looser equality than <see cref="Equals(object)"/>, meaning that this may return <see langword="true"/> where <see cref="Equals(object)"/>
  207. /// does not.
  208. /// </remarks>
  209. /// <param name="l">the first value to compare</param>
  210. /// <param name="r">the second value to compare</param>
  211. /// <returns><see langword="true"/> if they are mostly equal, <see langword="false"/> otherwise</returns>
  212. /// <seealso cref="Equals(object)"/>
  213. public static bool operator==(AlmostVersion l, AlmostVersion r)
  214. {
  215. if (l is null && r is null) return true;
  216. if (l is null || r is null) return false;
  217. if (l.StorageMode != r.StorageMode) return false;
  218. return l.StorageMode == StoredAs.SemVer
  219. ? Utils.VersionCompareNoPrerelease(l.SemverValue!, r.SemverValue!) == 0
  220. : l.StringValue == r.StringValue;
  221. }
  222. /// <summary>
  223. /// The opposite of <see cref="operator ==(AlmostVersion, AlmostVersion)"/>. Equivalent to <c>!(l == r)</c>.
  224. /// </summary>
  225. /// <param name="l">the first value to compare</param>
  226. /// <param name="r">the second value to compare</param>
  227. /// <returns><see langword="true"/> if they are not mostly equal, <see langword="false"/> otherwise</returns>
  228. /// <seealso cref="operator ==(AlmostVersion, AlmostVersion)"/>
  229. public static bool operator!=(AlmostVersion l, AlmostVersion r) => !(l == r);
  230. // implicitly convertible from Version
  231. #pragma warning disable CS0618 // Type or member is obsolete
  232. #pragma warning disable CA2225 // Operator overloads have named alternates
  233. /// <summary>
  234. /// Implicitly converts a <see cref="SVersion"/> to <see cref="AlmostVersion"/> using <see cref="AlmostVersion(SVersion)"/>.
  235. /// </summary>
  236. /// <param name="ver">the <see cref="SVersion"/> to convert</param>
  237. /// <seealso cref="AlmostVersion(SVersion)"/>
  238. [Obsolete("Use Hive.Versioning.Version instead of SemVer.Version")]
  239. public static implicit operator AlmostVersion?(SVersion? ver) => ver is null ? null : new(ver);
  240. // implicitly convertible to Version
  241. /// <summary>
  242. /// Implicitly converts an <see cref="AlmostVersion"/> to <see cref="SVersion"/>, if applicable, using <see cref="SemverValue"/>.
  243. /// If not applicable, returns <see langword="null"/>
  244. /// </summary>
  245. /// <param name="av">the <see cref="AlmostVersion"/> to convert to a <see cref="SVersion"/></param>
  246. /// <seealso cref="SemverValue"/>
  247. [Obsolete("Use Hive.Versioning.Version instead of SemVer.Version")]
  248. public static implicit operator SVersion?(AlmostVersion? av) => av?.SemverValue is not null ? SVersion.ForHiveVersion(av.SemverValue) : null;
  249. #pragma warning restore CS0618 // Type or member is obsolete
  250. /// <summary>
  251. /// Implicitly converts a <see cref="SVersion"/> to <see cref="AlmostVersion"/> using <see cref="AlmostVersion(SVersion)"/>.
  252. /// </summary>
  253. /// <param name="ver">the <see cref="SVersion"/> to convert</param>
  254. /// <seealso cref="AlmostVersion(SVersion)"/>
  255. public static implicit operator AlmostVersion?(Version? ver) => ver is null ? null : new(ver);
  256. // implicitly convertible to Version
  257. /// <summary>
  258. /// Implicitly converts an <see cref="AlmostVersion"/> to <see cref="SVersion"/>, if applicable, using <see cref="SemverValue"/>.
  259. /// If not applicable, returns <see langword="null"/>
  260. /// </summary>
  261. /// <param name="av">the <see cref="AlmostVersion"/> to convert to a <see cref="SVersion"/></param>
  262. /// <seealso cref="SemverValue"/>
  263. public static implicit operator Version?(AlmostVersion av) => av?.SemverValue;
  264. #pragma warning restore CA2225 // Operator overloads have named alternates
  265. public static bool operator <(AlmostVersion left, AlmostVersion right)
  266. => left is null ? right is not null : left.CompareTo(right) < 0;
  267. public static bool operator <=(AlmostVersion left, AlmostVersion right)
  268. => left is null || left.CompareTo(right) <= 0;
  269. public static bool operator >(AlmostVersion left, AlmostVersion right)
  270. => left is not null && left.CompareTo(right) > 0;
  271. public static bool operator >=(AlmostVersion left, AlmostVersion right)
  272. => left is null ? right is null : left.CompareTo(right) >= 0;
  273. }
  274. /// <summary>
  275. /// A <see cref="ValueConverter{T}"/> for <see cref="AlmostVersion"/>s.
  276. /// </summary>
  277. public sealed class AlmostVersionConverter : ValueConverter<AlmostVersion>
  278. {
  279. /// <summary>
  280. /// Converts a <see cref="Text"/> node into an <see cref="AlmostVersion"/>.
  281. /// </summary>
  282. /// <param name="value">the <see cref="Text"/> node to convert</param>
  283. /// <param name="parent">the owner of the new object</param>
  284. /// <returns></returns>
  285. public override AlmostVersion? FromValue(Value? value, object parent)
  286. => Converter<string>.Default.FromValue(value, parent) switch
  287. {
  288. { } v => new(v),
  289. _ => null
  290. };
  291. /// <summary>
  292. /// Converts an <see cref="AlmostVersion"/> to a <see cref="Text"/> node.
  293. /// </summary>
  294. /// <param name="obj">the <see cref="AlmostVersion"/> to convert</param>
  295. /// <param name="parent">the parent of <paramref name="obj"/></param>
  296. /// <returns>a <see cref="Text"/> node representing <paramref name="obj"/></returns>
  297. public override Value? ToValue(AlmostVersion? obj, object parent)
  298. => Value.From(obj?.ToString());
  299. }
  300. }