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.

270 lines
13 KiB

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