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.

562 lines
21 KiB

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