SemVer.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. using System;
  2. #if !NETSTANDARD
  3. using System.Globalization;
  4. using System.Runtime.Serialization;
  5. using System.Security.Permissions;
  6. #endif
  7. using System.Text.RegularExpressions;
  8. namespace SingularityGroup.HotReload.Editor.Semver
  9. {
  10. /// <summary>
  11. /// A semantic version implementation.
  12. /// Conforms to v2.0.0 of http://semver.org/
  13. /// </summary>
  14. #if NETSTANDARD
  15. sealed class SemVersion : IComparable<SemVersion>, IComparable
  16. #else
  17. [Serializable]
  18. sealed class SemVersion : IComparable<SemVersion>, IComparable, ISerializable
  19. #endif
  20. {
  21. public static SemVersion None = new SemVersion(0, 0, 0);
  22. public static string NoneString = new SemVersion(0, 0, 0).ToString();
  23. static Regex parseEx =
  24. new Regex(@"^(?<major>\d+)" +
  25. @"(\.(?<minor>\d+))?" +
  26. @"(\.(?<patch>\d+))?" +
  27. @"(\-(?<pre>[0-9A-Za-z\-\.]+))?" +
  28. @"(\+(?<build>[0-9A-Za-z\-\.]+))?$",
  29. #if NETSTANDARD
  30. RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture);
  31. #else
  32. RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.ExplicitCapture);
  33. #endif
  34. #if !NETSTANDARD
  35. /// <summary>
  36. /// Initializes a new instance of the <see cref="SemVersion" /> class.
  37. /// </summary>
  38. /// <param name="info"></param>
  39. /// <param name="context"></param>
  40. /// <exception cref="ArgumentNullException"></exception>
  41. private SemVersion(SerializationInfo info, StreamingContext context)
  42. {
  43. if (info == null) throw new ArgumentNullException("info");
  44. var semVersion = Parse(info.GetString("SemVersion"));
  45. Major = semVersion.Major;
  46. Minor = semVersion.Minor;
  47. Patch = semVersion.Patch;
  48. Prerelease = semVersion.Prerelease;
  49. Build = semVersion.Build;
  50. }
  51. #endif
  52. /// <summary>
  53. /// Initializes a new instance of the <see cref="SemVersion" /> class.
  54. /// </summary>
  55. /// <param name="major">The major version.</param>
  56. /// <param name="minor">The minor version.</param>
  57. /// <param name="patch">The patch version.</param>
  58. /// <param name="prerelease">The prerelease version (eg. "alpha").</param>
  59. /// <param name="build">The build eg ("nightly.232").</param>
  60. public SemVersion(int major, int minor = 0, int patch = 0, string prerelease = "", string build = "")
  61. {
  62. this.Major = major;
  63. this.Minor = minor;
  64. this.Patch = patch;
  65. this.Prerelease = prerelease ?? "";
  66. this.Build = build ?? "";
  67. }
  68. /// <summary>
  69. /// Initializes a new instance of the <see cref="SemVersion"/> class.
  70. /// </summary>
  71. /// <param name="version">The <see cref="System.Version"/> that is used to initialize
  72. /// the Major, Minor, Patch and Build properties.</param>
  73. public SemVersion(Version version)
  74. {
  75. if (version == null)
  76. throw new ArgumentNullException("version");
  77. this.Major = version.Major;
  78. this.Minor = version.Minor;
  79. if (version.Revision >= 0)
  80. {
  81. this.Patch = version.Revision;
  82. }
  83. this.Prerelease = String.Empty;
  84. if (version.Build > 0)
  85. {
  86. this.Build = version.Build.ToString();
  87. }
  88. else
  89. {
  90. this.Build = String.Empty;
  91. }
  92. }
  93. /// <summary>
  94. /// Parses the specified string to a semantic version.
  95. /// </summary>
  96. /// <param name="version">The version string.</param>
  97. /// <param name="strict">If set to <c>true</c> minor and patch version are required, else they default to 0.</param>
  98. /// <returns>The SemVersion object.</returns>
  99. /// <exception cref="System.InvalidOperationException">When a invalid version string is passed.</exception>
  100. public static SemVersion Parse(string version, bool strict = false)
  101. {
  102. var match = parseEx.Match(version);
  103. if (!match.Success)
  104. throw new ArgumentException("Invalid version.", "version");
  105. #if NETSTANDARD
  106. var major = int.Parse(match.Groups["major"].Value);
  107. #else
  108. var major = int.Parse(match.Groups["major"].Value, CultureInfo.InvariantCulture);
  109. #endif
  110. var minorMatch = match.Groups["minor"];
  111. int minor = 0;
  112. if (minorMatch.Success)
  113. {
  114. #if NETSTANDARD
  115. minor = int.Parse(minorMatch.Value);
  116. #else
  117. minor = int.Parse(minorMatch.Value, CultureInfo.InvariantCulture);
  118. #endif
  119. }
  120. else if (strict)
  121. {
  122. throw new InvalidOperationException("Invalid version (no minor version given in strict mode)");
  123. }
  124. var patchMatch = match.Groups["patch"];
  125. int patch = 0;
  126. if (patchMatch.Success)
  127. {
  128. #if NETSTANDARD
  129. patch = int.Parse(patchMatch.Value);
  130. #else
  131. patch = int.Parse(patchMatch.Value, CultureInfo.InvariantCulture);
  132. #endif
  133. }
  134. else if (strict)
  135. {
  136. throw new InvalidOperationException("Invalid version (no patch version given in strict mode)");
  137. }
  138. var prerelease = match.Groups["pre"].Value;
  139. var build = match.Groups["build"].Value;
  140. return new SemVersion(major, minor, patch, prerelease, build);
  141. }
  142. /// <summary>
  143. /// Parses the specified string to a semantic version.
  144. /// </summary>
  145. /// <param name="version">The version string.</param>
  146. /// <param name="semver">When the method returns, contains a SemVersion instance equivalent
  147. /// to the version string passed in, if the version string was valid, or <c>null</c> if the
  148. /// version string was not valid.</param>
  149. /// <param name="strict">If set to <c>true</c> minor and patch version are required, else they default to 0.</param>
  150. /// <returns><c>False</c> when a invalid version string is passed, otherwise <c>true</c>.</returns>
  151. public static bool TryParse(string version, out SemVersion semver, bool strict = false)
  152. {
  153. try
  154. {
  155. semver = Parse(version, strict);
  156. return true;
  157. }
  158. catch (Exception)
  159. {
  160. semver = null;
  161. return false;
  162. }
  163. }
  164. /// <summary>
  165. /// Tests the specified versions for equality.
  166. /// </summary>
  167. /// <param name="versionA">The first version.</param>
  168. /// <param name="versionB">The second version.</param>
  169. /// <returns>If versionA is equal to versionB <c>true</c>, else <c>false</c>.</returns>
  170. public static bool Equals(SemVersion versionA, SemVersion versionB)
  171. {
  172. if (ReferenceEquals(versionA, null))
  173. return ReferenceEquals(versionB, null);
  174. return versionA.Equals(versionB);
  175. }
  176. /// <summary>
  177. /// Compares the specified versions.
  178. /// </summary>
  179. /// <param name="versionA">The version to compare to.</param>
  180. /// <param name="versionB">The version to compare against.</param>
  181. /// <returns>If versionA &lt; versionB <c>&lt; 0</c>, if versionA &gt; versionB <c>&gt; 0</c>,
  182. /// if versionA is equal to versionB <c>0</c>.</returns>
  183. public static int Compare(SemVersion versionA, SemVersion versionB)
  184. {
  185. if (ReferenceEquals(versionA, null))
  186. return ReferenceEquals(versionB, null) ? 0 : -1;
  187. return versionA.CompareTo(versionB);
  188. }
  189. /// <summary>
  190. /// Make a copy of the current instance with optional altered fields.
  191. /// </summary>
  192. /// <param name="major">The major version.</param>
  193. /// <param name="minor">The minor version.</param>
  194. /// <param name="patch">The patch version.</param>
  195. /// <param name="prerelease">The prerelease text.</param>
  196. /// <param name="build">The build text.</param>
  197. /// <returns>The new version object.</returns>
  198. public SemVersion Change(int? major = null, int? minor = null, int? patch = null,
  199. string prerelease = null, string build = null)
  200. {
  201. return new SemVersion(
  202. major ?? this.Major,
  203. minor ?? this.Minor,
  204. patch ?? this.Patch,
  205. prerelease ?? this.Prerelease,
  206. build ?? this.Build);
  207. }
  208. /// <summary>
  209. /// Gets the major version.
  210. /// </summary>
  211. /// <value>
  212. /// The major version.
  213. /// </value>
  214. public int Major { get; private set; }
  215. /// <summary>
  216. /// Gets the minor version.
  217. /// </summary>
  218. /// <value>
  219. /// The minor version.
  220. /// </value>
  221. public int Minor { get; private set; }
  222. /// <summary>
  223. /// Gets the patch version.
  224. /// </summary>
  225. /// <value>
  226. /// The patch version.
  227. /// </value>
  228. public int Patch { get; private set; }
  229. /// <summary>
  230. /// Gets the pre-release version.
  231. /// </summary>
  232. /// <value>
  233. /// The pre-release version.
  234. /// </value>
  235. public string Prerelease { get; private set; }
  236. /// <summary>
  237. /// Gets the build version.
  238. /// </summary>
  239. /// <value>
  240. /// The build version.
  241. /// </value>
  242. public string Build { get; private set; }
  243. /// <summary>
  244. /// Returns a <see cref="System.String" /> that represents this instance.
  245. /// </summary>
  246. /// <returns>
  247. /// A <see cref="System.String" /> that represents this instance.
  248. /// </returns>
  249. public override string ToString()
  250. {
  251. var version = "" + Major + "." + Minor + "." + Patch;
  252. if (!String.IsNullOrEmpty(Prerelease))
  253. version += "-" + Prerelease;
  254. if (!String.IsNullOrEmpty(Build))
  255. version += "+" + Build;
  256. return version;
  257. }
  258. /// <summary>
  259. /// Compares the current instance with another object of the same type and returns an integer that indicates
  260. /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the
  261. /// other object.
  262. /// </summary>
  263. /// <param name="obj">An object to compare with this instance.</param>
  264. /// <returns>
  265. /// A value that indicates the relative order of the objects being compared.
  266. /// The return value has these meanings: Value Meaning Less than zero
  267. /// This instance precedes <paramref name="obj" /> in the sort order.
  268. /// Zero This instance occurs in the same position in the sort order as <paramref name="obj" />. i
  269. /// Greater than zero This instance follows <paramref name="obj" /> in the sort order.
  270. /// </returns>
  271. public int CompareTo(object obj)
  272. {
  273. return CompareTo((SemVersion)obj);
  274. }
  275. /// <summary>
  276. /// Compares the current instance with another object of the same type and returns an integer that indicates
  277. /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the
  278. /// other object.
  279. /// </summary>
  280. /// <param name="other">An object to compare with this instance.</param>
  281. /// <returns>
  282. /// A value that indicates the relative order of the objects being compared.
  283. /// The return value has these meanings: Value Meaning Less than zero
  284. /// This instance precedes <paramref name="other" /> in the sort order.
  285. /// Zero This instance occurs in the same position in the sort order as <paramref name="other" />. i
  286. /// Greater than zero This instance follows <paramref name="other" /> in the sort order.
  287. /// </returns>
  288. public int CompareTo(SemVersion other)
  289. {
  290. if (ReferenceEquals(other, null))
  291. return 1;
  292. var r = this.CompareByPrecedence(other);
  293. if (r != 0)
  294. return r;
  295. r = CompareComponent(this.Build, other.Build);
  296. return r;
  297. }
  298. /// <summary>
  299. /// Compares to semantic versions by precedence. This does the same as a Equals, but ignores the build information.
  300. /// </summary>
  301. /// <param name="other">The semantic version.</param>
  302. /// <returns><c>true</c> if the version precedence matches.</returns>
  303. public bool PrecedenceMatches(SemVersion other)
  304. {
  305. return CompareByPrecedence(other) == 0;
  306. }
  307. /// <summary>
  308. /// Compares to semantic versions by precedence. This does the same as a Equals, but ignores the build information.
  309. /// </summary>
  310. /// <param name="other">The semantic version.</param>
  311. /// <returns>
  312. /// A value that indicates the relative order of the objects being compared.
  313. /// The return value has these meanings: Value Meaning Less than zero
  314. /// This instance precedes <paramref name="other" /> in the version precedence.
  315. /// Zero This instance has the same precedence as <paramref name="other" />. i
  316. /// Greater than zero This instance has creater precedence as <paramref name="other" />.
  317. /// </returns>
  318. public int CompareByPrecedence(SemVersion other)
  319. {
  320. if (ReferenceEquals(other, null))
  321. return 1;
  322. var r = this.Major.CompareTo(other.Major);
  323. if (r != 0) return r;
  324. r = this.Minor.CompareTo(other.Minor);
  325. if (r != 0) return r;
  326. r = this.Patch.CompareTo(other.Patch);
  327. if (r != 0) return r;
  328. r = CompareComponent(this.Prerelease, other.Prerelease, true);
  329. return r;
  330. }
  331. static int CompareComponent(string a, string b, bool lower = false)
  332. {
  333. var aEmpty = String.IsNullOrEmpty(a);
  334. var bEmpty = String.IsNullOrEmpty(b);
  335. if (aEmpty && bEmpty)
  336. return 0;
  337. if (aEmpty)
  338. return lower ? 1 : -1;
  339. if (bEmpty)
  340. return lower ? -1 : 1;
  341. var aComps = a.Split('.');
  342. var bComps = b.Split('.');
  343. var minLen = Math.Min(aComps.Length, bComps.Length);
  344. for (int i = 0; i < minLen; i++)
  345. {
  346. var ac = aComps[i];
  347. var bc = bComps[i];
  348. int anum, bnum;
  349. var isanum = Int32.TryParse(ac, out anum);
  350. var isbnum = Int32.TryParse(bc, out bnum);
  351. int r;
  352. if (isanum && isbnum)
  353. {
  354. r = anum.CompareTo(bnum);
  355. if (r != 0) return anum.CompareTo(bnum);
  356. }
  357. else
  358. {
  359. if (isanum)
  360. return -1;
  361. if (isbnum)
  362. return 1;
  363. r = String.CompareOrdinal(ac, bc);
  364. if (r != 0)
  365. return r;
  366. }
  367. }
  368. return aComps.Length.CompareTo(bComps.Length);
  369. }
  370. /// <summary>
  371. /// Determines whether the specified <see cref="System.Object" /> is equal to this instance.
  372. /// </summary>
  373. /// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
  374. /// <returns>
  375. /// <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
  376. /// </returns>
  377. public override bool Equals(object obj)
  378. {
  379. if (ReferenceEquals(obj, null))
  380. return false;
  381. if (ReferenceEquals(this, obj))
  382. return true;
  383. var other = (SemVersion)obj;
  384. return this.Major == other.Major &&
  385. this.Minor == other.Minor &&
  386. this.Patch == other.Patch &&
  387. string.Equals(this.Prerelease, other.Prerelease, StringComparison.Ordinal) &&
  388. string.Equals(this.Build, other.Build, StringComparison.Ordinal);
  389. }
  390. /// <summary>
  391. /// Returns a hash code for this instance.
  392. /// </summary>
  393. /// <returns>
  394. /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
  395. /// </returns>
  396. public override int GetHashCode()
  397. {
  398. unchecked
  399. {
  400. int result = this.Major.GetHashCode();
  401. result = result * 31 + this.Minor.GetHashCode();
  402. result = result * 31 + this.Patch.GetHashCode();
  403. result = result * 31 + this.Prerelease.GetHashCode();
  404. result = result * 31 + this.Build.GetHashCode();
  405. return result;
  406. }
  407. }
  408. #if !NETSTANDARD
  409. [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
  410. public void GetObjectData(SerializationInfo info, StreamingContext context)
  411. {
  412. if (info == null) throw new ArgumentNullException("info");
  413. info.AddValue("SemVersion", ToString());
  414. }
  415. #endif
  416. /// <summary>
  417. /// The override of the equals operator.
  418. /// </summary>
  419. /// <param name="left">The left value.</param>
  420. /// <param name="right">The right value.</param>
  421. /// <returns>If left is equal to right <c>true</c>, else <c>false</c>.</returns>
  422. public static bool operator ==(SemVersion left, SemVersion right)
  423. {
  424. if(ReferenceEquals(right, null)) {
  425. return ReferenceEquals(left, null);
  426. }
  427. if(ReferenceEquals(left, null)) {
  428. return false;
  429. }
  430. return left.PrecedenceMatches(right);
  431. }
  432. /// <summary>
  433. /// The override of the un-equal operator.
  434. /// </summary>
  435. /// <param name="left">The left value.</param>
  436. /// <param name="right">The right value.</param>
  437. /// <returns>If left is not equal to right <c>true</c>, else <c>false</c>.</returns>
  438. public static bool operator !=(SemVersion left, SemVersion right)
  439. {
  440. return !(left == right);
  441. }
  442. /// <summary>
  443. /// The override of the greater operator.
  444. /// </summary>
  445. /// <param name="left">The left value.</param>
  446. /// <param name="right">The right value.</param>
  447. /// <returns>If left is greater than right <c>true</c>, else <c>false</c>.</returns>
  448. public static bool operator >(SemVersion left, SemVersion right)
  449. {
  450. return left.CompareByPrecedence(right) > 0;
  451. }
  452. /// <summary>
  453. /// The override of the greater than or equal operator.
  454. /// </summary>
  455. /// <param name="left">The left value.</param>
  456. /// <param name="right">The right value.</param>
  457. /// <returns>If left is greater than or equal to right <c>true</c>, else <c>false</c>.</returns>
  458. public static bool operator >=(SemVersion left, SemVersion right)
  459. {
  460. return left == right || left > right;
  461. }
  462. /// <summary>
  463. /// The override of the less operator.
  464. /// </summary>
  465. /// <param name="left">The left value.</param>
  466. /// <param name="right">The right value.</param>
  467. /// <returns>If left is less than right <c>true</c>, else <c>false</c>.</returns>
  468. public static bool operator <(SemVersion left, SemVersion right)
  469. {
  470. return left.CompareByPrecedence(right) < 0;
  471. }
  472. /// <summary>
  473. /// The override of the less than or equal operator.
  474. /// </summary>
  475. /// <param name="left">The left value.</param>
  476. /// <param name="right">The right value.</param>
  477. /// <returns>If left is less than or equal to right <c>true</c>, else <c>false</c>.</returns>
  478. public static bool operator <=(SemVersion left, SemVersion right)
  479. {
  480. return left == right || left < right;
  481. }
  482. }
  483. }