static void InlineAssertInvariants(CSVersion v) { #if DEBUG if (!_alreadyInCheck && v.IsValid) { _alreadyInCheck = true; try { if (v.IsLongForm) { Debug.Assert(v.NormalizedText == ComputeLongFormVersion(v.Major, v.Minor, v.Patch, v.PrereleaseNameIdx, v.PrereleaseNumber, v.PrereleasePatch, v.BuildMetaData)); } else { Debug.Assert(v.NormalizedText == ComputeShortFormVersion(v.Major, v.Minor, v.Patch, v.PrereleaseNameIdx, v.PrereleaseNumber, v.PrereleasePatch, v.BuildMetaData)); } //// Systematically checks that a valid CSVersion can be parsed back in Long or Short form. Debug.Assert(SVersion.TryParse(v.ToString(CSVersionFormat.Normalized)).Equals(v.ToNormalizedForm())); Debug.Assert(SVersion.TryParse(v.ToString(CSVersionFormat.LongForm)).Equals(v.ToLongForm())); } finally { _alreadyInCheck = false; } } #endif }
/// <summary> /// Parses the specified string to a constrained semantic version and throws an <see cref="ArgumentException"/> /// it the resulting <see cref="SVersion"/> is not a <see cref="CSVersion"/> or <see cref="SVersion.IsValid"/> is false. /// </summary> /// <param name="s">The string to parse.</param> /// <param name="checkBuildMetaDataSyntax">False to opt-out of strict <see cref="SVersion.BuildMetaData"/> compliance.</param> /// <returns>The CSVersion object.</returns> public static CSVersion Parse(string s, bool checkBuildMetaDataSyntax = true) { SVersion sv = SVersion.TryParse(s, true, checkBuildMetaDataSyntax); if (!sv.IsValid) { throw new ArgumentException(sv.ErrorMessage, nameof(s)); } return(sv as CSVersion ?? throw new ArgumentException("Not a CSVersion.", nameof(s))); }
/// <summary> /// Initializes a new <see cref="InformationalVersion"/> by parsing a string. /// This never throws: <see cref="IsValidSyntax"/> may be false and <see cref="ParseErrorMessage"/> exposes /// the error message. /// </summary> /// <param name="informationalVersion">Informational version. Can be null.</param> public InformationalVersion(string?informationalVersion) { if ((OriginalInformationalVersion = informationalVersion) != null) { Match m = _rV7.Match(informationalVersion); if (!m.Success) { m = _rV6.Match(informationalVersion); } if (!m.Success) { m = _rOld.Match(informationalVersion); } if (m.Success) { RawVersion = m.Groups[2].Value; CommitSha = m.Groups[3].Value; var v = SVersion.TryParse(RawVersion); Version = v.AsCSVersion != null?v.AsCSVersion.ToNormalizedForm() : v; if (DateTime.TryParseExact(m.Groups[4].Value, "u", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out DateTime t)) { CommitDate = t; if (t.Kind != DateTimeKind.Utc) { ParseErrorMessage = $"The CommitDate must be Utc: {m.Groups[4].Value} must be {DateTime.SpecifyKind( t, DateTimeKind.Utc ):u}."; } else if (!Version.IsValid) { ParseErrorMessage = "The SemVersion is invalid: " + Version.ErrorMessage; } else if (CommitSha.Length != 40 || !CommitSha.All(IsHexDigit)) { ParseErrorMessage = "The CommitSha is invalid (must be 40 hex digit)."; } else { IsValidSyntax = true; } } else { ParseErrorMessage = "The CommitDate is invalid.It must be a UTC DateTime in \"u\" format."; } } else { ParseErrorMessage = "The String to parse does not match the standard CSemVer informational version pattern."; } } else { ParseErrorMessage = "String to parse is null."; } }
/// <summary> /// Parses the specified string to a constrained semantic version and returns a <see cref="CSVersion"/> that /// may not be <see cref="SVersion.IsValid"/>. /// </summary> /// <param name="s">The string to parse.</param> /// <param name="checkBuildMetaDataSyntax">False to opt-out of strict <see cref="SVersion.BuildMetaData"/> compliance.</param> /// <returns>The CSVersion object that may not be <see cref="SVersion.IsValid"/>.</returns> public static CSVersion TryParse(string s, bool checkBuildMetaDataSyntax = true) { SVersion sv = SVersion.TryParse(s, true, checkBuildMetaDataSyntax); if (sv is CSVersion v) { return(v); } Debug.Assert(sv.IsValid == (sv.ErrorMessage == null)); return(new CSVersion(sv.ErrorMessage ?? "Not a CSVersion.", s)); }
/// <summary> /// Standard TryParse pattern that returns a boolean rather than the resulting <see cref="CSVersion"/>. /// See <see cref="TryParse(string,bool)"/>. /// </summary> /// <param name="s">String to parse.</param> /// <param name="v">Resulting version.</param> /// <param name="checkBuildMetaDataSyntax">False to opt-out of strict <see cref="SVersion.BuildMetaData"/> compliance.</param> /// <returns>True on success, false otherwise.</returns> public static bool TryParse(string s, out CSVersion v, bool checkBuildMetaDataSyntax = true) { v = null; SVersion sv = SVersion.TryParse(s, true, checkBuildMetaDataSyntax); if (!sv.IsValid) { return(false); } v = sv as CSVersion; return(v != null); }
/// <summary> /// Parses the specified string to a constrained semantic version and returns a <see cref="CSVersion"/> that /// may not be <see cref="SVersion.IsValid"/>. /// </summary> /// <param name="s">The string to parse.</param> /// <param name="checkBuildMetaDataSyntax">False to opt-out of strict <see cref="SVersion.BuildMetaData"/> compliance.</param> /// <returns>The CSVersion object that may not be <see cref="SVersion.IsValid"/>.</returns> public static CSVersion TryParse(string s, bool checkBuildMetaDataSyntax = true) { SVersion sv = SVersion.TryParse(s, true, checkBuildMetaDataSyntax); if (sv is CSVersion v) { return(v); } if (!sv.IsValid) { new CSVersion(sv.ErrorMessage, s); } return(new CSVersion("Not a CSVersion.", s)); }
static (SVersion?Version, int FMajor, int FMinor, string?Error, bool FourtPartLost) TryMatchFloatingVersion(ref ReadOnlySpan <char> s) { // Handling the marvelous "" (empty string), that is like '*'. if (s.Length == 0) { return(null, -1, 0, null, false); } var version = SVersion.TryParse(ref s); if (version.IsValid) { // If only the 3 first parts have been read: launch SkipExtraPartsAndPrereleaseIfAny on the head to skip // potential extra parts and prerelease. return(version, 0, 0, null, SkipExtraPartsAndPrereleaseIfAny(ref s)); } int major, minor = -1; if (TryMatchXStarInt(ref s, out major)) { if (major >= 0) { if (s.Length > 0 && TryMatch(ref s, '.')) { if (s.Length == 0 || !TryMatchXStarInt(ref s, out minor)) { return(null, 0, 0, "Expecting minor number or *.", false); } if (minor >= 0) { // If a fourth part caused the version parse to fail, handle it here. if (s.Length > 0 && TryMatch(ref s, '.') && s.Length > 0 && TryMatchNonNegativeInt(ref s, out int patch)) { return(SVersion.Create(major, minor, patch), 0, 0, null, SkipExtraPartsAndPrereleaseIfAny(ref s)); } } } } else { minor = 0; } // Forgetting any trailing "X.Y.*" since it is like "X.Y". // Even if the npm grammar allows "3.*-alpha" or "3.1.*+meta", we ignores this: https://semver.npmjs.com/ selects nothing. // We consider this stupid trail as being FourthPartLost. return(null, major, minor, null, SkipExtraPartsAndPrereleaseIfAny(ref s)); } return(null, 0, 0, version.ErrorMessage, false); }
static void InlineAssertInvariants(CSVersion v) { #if DEBUG if (!_alreadyInCheck && v.IsValid) { _alreadyInCheck = true; try { //// Systematically checks that a valid CSVersion can be parsed back in Long or Short form. Debug.Assert(SVersion.TryParse(v.ToString(CSVersionFormat.Normalized)).Equals(v)); Debug.Assert(SVersion.TryParse(v.ToString(CSVersionFormat.ShortForm)).Equals(v)); } finally { _alreadyInCheck = false; } } #endif }
static SVersion TryParseVersion(ref ReadOnlySpan <char> s, out bool fourthPartLost) { Debug.Assert(s.Length > 0); fourthPartLost = false; var v = SVersion.TryParse(ref s); if (v.IsValid) { // If only the 3 first parts have been read... fourthPartLost = SkipExtraPartsAndPrereleaseIfAny(ref s); } else { if (TryMatchNonNegativeInt(ref s, out int major)) { if (s.Length == 0 || !TryMatch(ref s, '.')) { return(SVersion.Create(major, 0, 0)); } if (!TryMatchNonNegativeInt(ref s, out int minor)) { return(new SVersion("Expected Nuget minor part.", null)); } // Try to save the fourth part: in such case the patch is read. int patch = 0; if (s.Length > 0 && TryMatch(ref s, '.') && s.Length > 0 && TryMatchNonNegativeInt(ref s, out patch) && s.Length > 0 && TryMatch(ref s, '.') && s.Length > 0 && TryMatchNonNegativeInt(ref s, out int _)) { fourthPartLost = true; } return(SVersion.Create(major, minor, patch)); } } return(v); }
/// <summary> /// Tries to parse a version bound: it is a <see cref="SVersion.TryParse(ref ReadOnlySpan{char}, bool, bool, bool)"/> that may be /// followed by an optional bracketed "[<see cref="TryParseLockAndMinQuality"/>]". /// The head is forwarded right after the match: on success, the head may be on any kind of character. /// </summary> /// <param name="head">The string to parse (leading and internal white spaces between tokens are skipped).</param> /// <param name="bound">The result. This is <see cref="SVersionBound.None"/> on error.</param> /// <param name="defaultLock">Default lock to apply if none is provided.</param> /// <param name="defaultQuality">Default quality to apply if none is provided.</param> /// <returns>True on success, false otherwise.</returns> public static bool TryParse(ref ReadOnlySpan <char> head, out SVersionBound bound, SVersionLock defaultLock = SVersionLock.None, PackageQuality defaultQuality = PackageQuality.None) { var sHead = head; bound = SVersionBound.None; var v = SVersion.TryParse(ref Trim(ref head), checkBuildMetaDataSyntax: false); if (!v.IsValid) { head = sHead; return(false); } SVersionLock l = SVersionLock.None; PackageQuality q = PackageQuality.None; if (Trim(ref head).Length > 0 && TryMatch(ref head, '[')) { // Allows empty []. Note that TryParseLockAndMinQuality calls Trim. TryParseLockAndMinQuality(ref head, out l, out q); // Match the closing ] if it's here. Ignores it if it's not here. if (Trim(ref head).Length > 0) { TryMatch(ref head, ']'); } } if (l == SVersionLock.None) { l = defaultLock; } if (q == PackageQuality.None) { q = defaultQuality; } bound = new SVersionBound(v, l, q); return(true); }