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.

244 lines
12 KiB

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