- Split out grammar and ParseResluts to separate files and made them public to allow for greater flexibility
- added new tests for gramar parser monads (Still nee tests for full SemanticVersion parser )
This commit is contained in:
Родитель
4e96df1821
Коммит
e79e739a46
|
@ -43,7 +43,7 @@ namespace CMSIS.Pack.PackDescription
|
|||
get { return ApiVersion.ToString(); }
|
||||
set
|
||||
{
|
||||
ApiVersion = SemanticVersion.Parse( value, SemanticVersionOptions.PatchOptional );
|
||||
ApiVersion = SemanticVersion.Parse( value, ParseOptions.PatchOptional );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace CMSIS.Pack.PackDescription
|
|||
get { return Version.ToString( ); }
|
||||
set
|
||||
{
|
||||
Version = SemanticVersion.Parse( value, SemanticVersionOptions.PatchOptional );
|
||||
Version = SemanticVersion.Parse( value, ParseOptions.PatchOptional );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ namespace CMSIS.Pack.PackDescription
|
|||
get { return Version.ToString( ); }
|
||||
set
|
||||
{
|
||||
Version = SemanticVersion.Parse( value, SemanticVersionOptions.PatchOptional );
|
||||
Version = SemanticVersion.Parse( value, ParseOptions.PatchOptional );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ namespace CMSIS.Pack.PackDescription
|
|||
get { return Version.ToString( ); }
|
||||
set
|
||||
{
|
||||
Version = SemanticVersion.Parse( value, SemanticVersionOptions.PatchOptional );
|
||||
Version = SemanticVersion.Parse( value, ParseOptions.PatchOptional );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ namespace CMSIS.Pack
|
|||
Vendor = parts[ 0 ];
|
||||
Name = parts[ 1 ];
|
||||
SemanticVersion version;
|
||||
if( !SemanticVersion.TryParse( packVersion, SemanticVersionOptions.PatchOptional, out version ) )
|
||||
if( !SemanticVersion.TryParse( packVersion, ParseOptions.PatchOptional, out version ) )
|
||||
throw new ArgumentException( "Invalid semantic version provided", nameof( packVersion ) );
|
||||
Version = version;
|
||||
LocalPath = Path.Combine( Vendor, Name, Version.ToString( ) );
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Sprache;
|
||||
|
||||
namespace SemVer.NET
|
||||
{
|
||||
/// <summary>Grammar and parser for Semantic Version strings</summary>
|
||||
/// <remarks>
|
||||
/// This class uses the Sprache parser Combinator library to provide
|
||||
/// the core parsing support. A few key elements of the grammar are
|
||||
/// exposed for validation of components (such as when programatically
|
||||
/// constructing a new version the prerelease and build metadata identifiers
|
||||
/// require validation)
|
||||
/// </remarks>
|
||||
public static class Grammar
|
||||
{
|
||||
// For full details of the syntax (including formal BNF grammar)
|
||||
// see: https://github.com/mojombo/semver/blob/master/semver.md
|
||||
|
||||
private static Parser<char> Range( char start, char end )
|
||||
=> Parse.Chars( Enumerable.Range( start, end ).Select( i => ( char )i ).ToArray( ) );
|
||||
|
||||
// primitive single char parser monads
|
||||
private static Parser<char> Dot = Parse.Char('.');
|
||||
private static Parser<char> Zero = Parse.Char( '0' );
|
||||
private static Parser<char> Dash = Parse.Char( '-' );
|
||||
private static Parser<char> StartBuild = Parse.Char( '+' );
|
||||
private static Parser<char> StartPreRelease = Dash;
|
||||
|
||||
// single char parser combinator monads
|
||||
private static Parser<char> Letter = Range('a','z').Or( Range('A','Z'));
|
||||
private static Parser<char> NonDigit = Letter.Or( Dash );
|
||||
private static Parser<char> NonZeroDigit = Range( '1', '9' );
|
||||
private static Parser<char> Digit = Zero.Or( NonZeroDigit );
|
||||
private static Parser<char> IdentifierChar = Digit.Or( NonDigit );
|
||||
private static Parser<char> LeadingV = Parse.Chars( 'v', 'V' );
|
||||
|
||||
// char sequence parser combinator monads
|
||||
private static Parser<IEnumerable<char>> IdentifierCharacters = IdentifierChar.AtLeastOnce();
|
||||
private static Parser<IEnumerable<char>> Digits = Digit.AtLeastOnce();
|
||||
|
||||
private static Parser<IEnumerable<char>> NumericIdentifier
|
||||
= Zero.Once()
|
||||
.Or( NonZeroDigit.Once().Concat( Digits ) )
|
||||
.Or( NonZeroDigit.Once() );
|
||||
|
||||
private static Parser<IEnumerable<char>> AlphaNumericIdentifier
|
||||
= IdentifierCharacters.Concat( NonDigit.Once().Concat( IdentifierCharacters ) )
|
||||
.Or( IdentifierCharacters.Concat( NonDigit.Once() ) )
|
||||
.Or( NonDigit.Once().Concat( IdentifierCharacters ) )
|
||||
.Or( NonDigit.Once() );
|
||||
|
||||
/// <summary>Parser monad for a semantic version Build Metadata Identifier</summary>
|
||||
public static Parser<string> BuildIdentifier = AlphaNumericIdentifier
|
||||
.Or( Digits )
|
||||
.Text();
|
||||
|
||||
/// <summary>Parser monad for a semantic version Prerelease Identifier</summary>
|
||||
public static Parser<string> PrereleaseIdentifier = AlphaNumericIdentifier
|
||||
.Or( NumericIdentifier )
|
||||
.Text();
|
||||
|
||||
/// <summary>Parser monad to parse a dot separated sequence of Build Metadata Identifiers</summary>
|
||||
public static Parser<IEnumerable<string>> DotSeparatedBuildIdentifiers = BuildIdentifier.DelimitedBy( Dot );
|
||||
|
||||
/// <summary>Parser monad to parse a dot separated sequence of Prerelease Identifiers</summary>
|
||||
public static Parser<IEnumerable<string>> DotSeparatedReleaseIdentifiers = PrereleaseIdentifier.DelimitedBy( Dot );
|
||||
|
||||
/// <summary>Parser monad to parse the non leading 0 sequence of digits for the Major, Minor, or Patch build numbers</summary>
|
||||
public static Parser<string> BuildNumber = NumericIdentifier.Text();
|
||||
|
||||
/// <summary>Parser monad to parse a semantic version string into a <see cref="ParseResult"/></summary>
|
||||
public static Parser<ParseResult> SemanticVersion
|
||||
= from leadingV in LeadingV.Optional()
|
||||
from major in BuildNumber
|
||||
from sep1 in Dot
|
||||
from minor in BuildNumber
|
||||
from patch in ( from sep2 in Dot
|
||||
from patchValue in BuildNumber
|
||||
select patchValue
|
||||
).Optional()
|
||||
from preRelease in ( from start in StartPreRelease
|
||||
from preRelIds in DotSeparatedReleaseIdentifiers
|
||||
select preRelIds
|
||||
).Optional()
|
||||
from buildMetadata in ( from start in StartBuild
|
||||
from buildIds in DotSeparatedBuildIdentifiers
|
||||
select buildIds
|
||||
).Optional()
|
||||
select new ParseResult( leadingV.IsDefined ? new char?( leadingV.Get() ) : null
|
||||
, major
|
||||
, minor
|
||||
, patch.GetOrElse( string.Empty )
|
||||
, preRelease.GetOrElse( Enumerable.Empty<string>( ) )
|
||||
, buildMetadata.GetOrElse( Enumerable.Empty<string>( ) )
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SemVer.NET
|
||||
{
|
||||
/// <summary>Contains the results of parsing a Semantic version string</summary>
|
||||
/// <remarks>
|
||||
/// <para>In order to support variations on Semantic versions as well as gracefully
|
||||
/// handle publicly released components with errant Semantic Versions this
|
||||
/// result contains the captured parts. This allows for more restrictive usage,
|
||||
/// such as found in CSemVer and also can handle simple Major.minor versions
|
||||
/// (e.g. without any patch number). Ultimately, it is up to the class consuming
|
||||
/// the result to determine the behavior it allows</para>
|
||||
/// <para>The numeric parts are left as strings
|
||||
/// since, technically they can be any length. Though practically speaking a
|
||||
/// 32bit integer is generally considered enough.</para>
|
||||
/// </remarks>
|
||||
public class ParseResult
|
||||
{
|
||||
/// <summary>Constructs a new semantic version parse Result</summary>
|
||||
/// <param name="major">string containing the digits of the major component of the version</param>
|
||||
/// <param name="minor">string containing the digits of the minor component of the version</param>
|
||||
/// <param name="patch">potentially empty, but not <see langword="null"/>, string containing the digits of the patch component of the version</param>
|
||||
/// <param name="prereleaseParts">Collection of strings corresponding to the prerelease identifiers in the version</param>
|
||||
/// <param name="buildParts">Collection of strings corresponding to the build metadata identifiers in the version</param>
|
||||
/// <exception cref="ArgumentNullException">If any of the arguments is <see langword="null"/></exception>
|
||||
public ParseResult( char? leadingV
|
||||
, string major
|
||||
, string minor
|
||||
, string patch
|
||||
, IEnumerable<string> prereleaseParts
|
||||
, IEnumerable<string> buildParts
|
||||
)
|
||||
{
|
||||
if( major == null )
|
||||
throw new ArgumentNullException( nameof( major ) );
|
||||
|
||||
if( minor == null )
|
||||
throw new ArgumentNullException( nameof( minor ) );
|
||||
|
||||
if( patch == null )
|
||||
throw new ArgumentNullException( nameof( patch ) );
|
||||
|
||||
if( prereleaseParts == null )
|
||||
throw new ArgumentNullException( nameof( prereleaseParts ) );
|
||||
|
||||
if( buildParts == null )
|
||||
throw new ArgumentNullException( nameof( buildParts ) );
|
||||
|
||||
LeadingV = leadingV;
|
||||
Major = major;
|
||||
Minor = minor;
|
||||
Patch = patch;
|
||||
Prerelease = prereleaseParts;
|
||||
BuildMetadata = buildParts;
|
||||
}
|
||||
|
||||
/// <summary>Gets a potentially <see langword="null"/> leading 'v' or 'V' character</summary>
|
||||
/// <remarks>
|
||||
/// Technically a Semantic version number does not include a leading character,
|
||||
/// however, many common uses of version numbers use one by convention. This,
|
||||
/// can be tested for <see langword="null"/> and trigger an exception if the
|
||||
/// leading character is provided but strict conformance to the specification
|
||||
/// is required.
|
||||
/// </remarks>
|
||||
public char? LeadingV { get; }
|
||||
|
||||
/// <summary>Gets a string containing the digits of the Major version number</summary>
|
||||
public string Major { get; }
|
||||
|
||||
/// <summary>Gets a string containing the digits of the Minor version number</summary>
|
||||
public string Minor { get; }
|
||||
|
||||
/// <summary>Gets a string containing the digits of the Patch version number or an empty string if none was provided</summary>
|
||||
/// <remarks>
|
||||
/// Technically a correct Semantic Version requires the Patch value. However, to gracefully handle
|
||||
/// some real world erroneously versioned packages the grammar used in this library treats it as
|
||||
/// optional. The consumer, such as <see cref="SemanticVersion"/> can detect if the patch is not
|
||||
/// provided by calling <see cref="string.IsNullOrWhiteSpace(string)"/> and throwing an exception
|
||||
/// to maintain full compliance with the SemanticVersion specs. A consumer may also elect to allow
|
||||
/// the missing patch and apply a sensible default (such as "0") depending on the domain specific
|
||||
/// conditions.
|
||||
/// </remarks>
|
||||
public string Patch { get; }
|
||||
|
||||
/// <summary>Gets a collection of prerelease identifier strings</summary>
|
||||
public IEnumerable<string> Prerelease { get; }
|
||||
|
||||
/// <summary>Gets a collection of the build metadata identifier strings</summary>
|
||||
public IEnumerable<string> BuildMetadata { get; }
|
||||
}
|
||||
}
|
|
@ -39,6 +39,8 @@
|
|||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Grammar.cs" />
|
||||
<Compile Include="ParseResult.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="SemanticVersion.cs" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -7,24 +7,30 @@ using Sprache;
|
|||
|
||||
namespace SemVer.NET
|
||||
{
|
||||
/// <summary>Option flags for parsing a semantic version string</summary>
|
||||
[Flags]
|
||||
public enum SemanticVersionOptions
|
||||
public enum ParseOptions
|
||||
{
|
||||
/// <summary>No special handling, parsing proceeds according to Semantic Version Specification</summary>
|
||||
None = 0,
|
||||
/// <summary>Patch build number is optional, and if not present in the string a default of "0" is assumed</summary>
|
||||
PatchOptional = 1,
|
||||
//...
|
||||
// TODO: Add options to allow build metadata in precedence checks
|
||||
// see: https://github.com/mojombo/semver/issues/200
|
||||
//
|
||||
/// <summary>Allow a leading 'v' or 'V' as a common convention for version numbers</summary>
|
||||
AllowLeadingV = 2,
|
||||
}
|
||||
|
||||
// TODO: Add additional Comparer<SemanticVersion> implementation to handle build metadata in precedence
|
||||
// see: https://github.com/mojombo/semver/issues/200
|
||||
//
|
||||
|
||||
/// <summary>Version structure for versions based on Semantic Versioning v2.0 as defined by https://github.com/mojombo/semver/blob/master/semver.md </summary>
|
||||
/// <remarks>
|
||||
/// This class implements creating, parsing and comparing semantic version values. In
|
||||
/// <para>This class implements creating, parsing and comparing semantic version values. In
|
||||
/// addition to the standard support, this class includes an additional optional optimization
|
||||
/// where parsing a version string can assume a default patch value of 0 if none is specified.
|
||||
/// According to the formal Semantic Versioning v2.0 spec. the patch value is required, however
|
||||
/// some real world applications allow the looser definition.
|
||||
/// some real world applications allow the looser definition.</para>
|
||||
/// <para>Technically the Major, Minor, and Patch numbers have no length limits, thus this uses</para>
|
||||
/// </remarks>
|
||||
public struct SemanticVersion
|
||||
: IComparable
|
||||
|
@ -35,7 +41,7 @@ namespace SemVer.NET
|
|||
/// <param name="major">Major version number</param>
|
||||
/// <param name="minor">Minor Version number</param>
|
||||
/// <param name="patch">Patch version number</param>
|
||||
/// <param name="preReleaseParts">Array of individual pre-release parts (not including the separating '.')</param>
|
||||
/// <param name="preReleaseParts">Array of individual prerelease parts (not including the separating '.')</param>
|
||||
/// <param name="metadataParts">Array of individual Build Metadata parts (not including the separating '.')</param>
|
||||
public SemanticVersion( int major, int minor, int patch, IEnumerable<string> preReleaseParts, IEnumerable<string> metadataParts )
|
||||
{
|
||||
|
@ -56,7 +62,7 @@ namespace SemVer.NET
|
|||
|
||||
// Validate each part conforms to an "identifier" as defined by the spec
|
||||
if( !ValidatePrereleaseIdentifierParts( PreReleaseParts_ ) )
|
||||
throw new ArgumentException( "Invalid identifier for pre-release part", nameof( preReleaseParts ) );
|
||||
throw new ArgumentException( "Invalid identifier for prerelease part", nameof( preReleaseParts ) );
|
||||
|
||||
if( !ValidateBuildIdentifierParts( BuildMetadata_ ) )
|
||||
throw new ArgumentException( "Invalid identifier for build metadata part", nameof( metadataParts ) );
|
||||
|
@ -81,7 +87,7 @@ namespace SemVer.NET
|
|||
/// <summary>Indicates if this version is a development version (e.g. Major Version == 0 )</summary>
|
||||
public bool IsDevelopment => Major == 0;
|
||||
|
||||
/// <summary>Indicates if this version is a pre-release version (e.g. IsDevelopment or Has pre-release parts following the Patch)</summary>
|
||||
/// <summary>Indicates if this version is a prerelease version (e.g. IsDevelopment or Has prerelease parts following the Patch)</summary>
|
||||
public bool IsPrerelease => IsDevelopment || PreReleaseParts.Count > 0;
|
||||
|
||||
/// <summary>Indicates if this is a valid version</summary>
|
||||
|
@ -96,6 +102,24 @@ namespace SemVer.NET
|
|||
/// <summary>Patch version number</summary>
|
||||
public int Patch { get; }
|
||||
|
||||
/// <summary>List of identifier parts forming the prerelease value</summary>
|
||||
/// <remarks>
|
||||
/// Prerelease values are optional and follow the patch with a '-'. The prerelease
|
||||
/// value can consist of multiple parts separated by a '.', this list contains the
|
||||
/// individual parts without the leading '-' or separating '.'.
|
||||
/// </remarks>
|
||||
public IReadOnlyList<string> PreReleaseParts => Array.AsReadOnly( PreReleaseParts_ ?? EmptyStringArray );
|
||||
private readonly string[ ] PreReleaseParts_;
|
||||
|
||||
/// <summary>List of identifier parts forming the build Metadata value</summary>
|
||||
/// <remarks>
|
||||
/// Metadata values are optional and follow the patch with a '+'. The Metadata
|
||||
/// value can consist of multiple parts separated by a '.', this list contains
|
||||
/// the individual parts without the leading '+' or separating '.'.
|
||||
/// </remarks>
|
||||
public IReadOnlyList<string> BuildMetadata => Array.AsReadOnly( BuildMetadata_ ?? EmptyStringArray );
|
||||
private readonly string[ ] BuildMetadata_;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode( )
|
||||
{
|
||||
|
@ -122,10 +146,7 @@ namespace SemVer.NET
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals( SemanticVersion other )
|
||||
{
|
||||
return 0 == CompareTo( other );
|
||||
}
|
||||
public bool Equals( SemanticVersion other ) => 0 == CompareTo( other );
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int CompareTo( object obj )
|
||||
|
@ -142,16 +163,16 @@ namespace SemVer.NET
|
|||
// identifiers from left to right as follows:
|
||||
// 1) Major, minor, and patch versions are always compared numerically.
|
||||
// Example: 1.0.0 < 2.0.0 < 2.1.0 < 2.1.1.
|
||||
// 2) When major, minor, and patch are equal, a pre-release version has lower
|
||||
// 2) When major, minor, and patch are equal, a prerelease version has lower
|
||||
// precedence than a normal version.
|
||||
// Example: 1.0.0-alpha < 1.0.0.
|
||||
// 3) Precedence for two pre-release versions with the same major, minor, and
|
||||
// 3) Precedence for two prerelease versions with the same major, minor, and
|
||||
// patch version MUST be determined by comparing each dot separated identifier
|
||||
// from left to right until a difference is found as follows:
|
||||
// a) identifiers consisting of only digits are compared numerically
|
||||
// b) identifiers with letters or hyphens are compared lexically in ASCII sort order.
|
||||
// c) Numeric identifiers always have lower precedence than non-numeric identifiers.
|
||||
// d) A larger set of pre-release fields has a higher precedence than a smaller set,
|
||||
// d) A larger set of prerelease fields has a higher precedence than a smaller set,
|
||||
// if all of the preceding identifiers are equal.
|
||||
// Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0.
|
||||
/// <inheritdoc/>
|
||||
|
@ -204,24 +225,6 @@ namespace SemVer.NET
|
|||
return PreReleaseParts.Count.CompareTo( other.PreReleaseParts.Count );
|
||||
}
|
||||
|
||||
/// <summary>List of identifier parts forming the pre-release value</summary>
|
||||
/// <remarks>
|
||||
/// Pre-release values are optional and follow the patch with a '-'. The pre-release
|
||||
/// value can consist of multiple parts separated by a '.', this list contains the
|
||||
/// individual parts without the leading '-' or separating '.'.
|
||||
/// </remarks>
|
||||
public IReadOnlyList<string> PreReleaseParts => Array.AsReadOnly( PreReleaseParts_ ?? EmptyStringArray );
|
||||
private readonly string[ ] PreReleaseParts_;
|
||||
|
||||
/// <summary>List of identifier parts forming the build Metadata value</summary>
|
||||
/// <remarks>
|
||||
/// Metadata values are optional and follow the patch with a '+'. The Metadata
|
||||
/// value can consist of multiple parts separated by a '.', this list contains
|
||||
/// the individual parts without the leading '+' or separating '.'.
|
||||
/// </remarks>
|
||||
public IReadOnlyList<string> BuildMetadata => Array.AsReadOnly( BuildMetadata_ ?? EmptyStringArray );
|
||||
private readonly string[ ] BuildMetadata_;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString( ) => ToString( true );
|
||||
|
||||
|
@ -254,7 +257,7 @@ namespace SemVer.NET
|
|||
/// <summary>Parse a semantic version string into it's component parts</summary>
|
||||
/// <param name="versionString">String containing the version to parse</param>
|
||||
/// <returns>Parsed version details</returns>
|
||||
public static SemanticVersion Parse( string versionString ) => Parse( versionString, SemanticVersionOptions.None );
|
||||
public static SemanticVersion Parse( string versionString ) => Parse( versionString, ParseOptions.None );
|
||||
|
||||
/// <summary>Parse a semantic version string into it's component parts</summary>
|
||||
/// <param name="versionString">String containing the version to parse</param>
|
||||
|
@ -264,16 +267,16 @@ namespace SemVer.NET
|
|||
/// This overload of Parse allows for a non-standard version where the Patch value
|
||||
/// defaults to 0 if not present, instead of triggering an exception.
|
||||
/// </remarks>
|
||||
public static SemanticVersion Parse( string versionString, SemanticVersionOptions options )
|
||||
public static SemanticVersion Parse( string versionString, ParseOptions options )
|
||||
{
|
||||
try
|
||||
{
|
||||
var parts = SemanticVersionParser.Parse( versionString );
|
||||
var parts = Grammar.SemanticVersion.Parse( versionString );
|
||||
return new SemanticVersion( parts, options );
|
||||
}
|
||||
catch( ParseException ex )
|
||||
{
|
||||
throw new FormatException("Invalid SemanticVersion", ex);
|
||||
throw new FormatException("Invalid SemanticVersion", ex );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -281,20 +284,17 @@ namespace SemVer.NET
|
|||
/// <param name="versionString">String to parse</param>
|
||||
/// <param name="version">Version instance to construct</param>
|
||||
/// <returns>true if the string is a valid semantic version string that was successfully parsed into <paramref name="version"/></returns>
|
||||
public static bool TryParse( string versionString, out SemanticVersion version)
|
||||
{
|
||||
return TryParse( versionString, SemanticVersionOptions.None, out version );
|
||||
}
|
||||
public static bool TryParse( string versionString, out SemanticVersion version ) => TryParse( versionString, ParseOptions.None, out version );
|
||||
|
||||
/// <summary>Attempts to parse a version string into a new SemanticVersion instance</summary>
|
||||
/// <param name="versionString">String to parse</param>
|
||||
/// <param name="options">Options flags to control parsing variants and ambiguities in the spec</param>
|
||||
/// <param name="version">Version instance to construct</param>
|
||||
/// <returns>true if the string is a valid semantic version string that was successfully parsed into <paramref name="version"/></returns>
|
||||
public static bool TryParse( string versionString, SemanticVersionOptions options, out SemanticVersion version )
|
||||
public static bool TryParse( string versionString, ParseOptions options, out SemanticVersion version )
|
||||
{
|
||||
version = new SemanticVersion();
|
||||
var result = SemanticVersionParser.TryParse( versionString );
|
||||
var result = Grammar.SemanticVersion.TryParse( versionString );
|
||||
if( !result.WasSuccessful )
|
||||
return false;
|
||||
|
||||
|
@ -306,24 +306,18 @@ namespace SemVer.NET
|
|||
/// <param name="lhs">Left hand side of the comparison</param>
|
||||
/// <param name="rhs">Right hand side of the comparison</param>
|
||||
/// <returns><see langword="true"/> if the two versions are equivalent</returns>
|
||||
public static bool operator ==( SemanticVersion lhs, SemanticVersion rhs )
|
||||
{
|
||||
return lhs.Equals( rhs );
|
||||
}
|
||||
public static bool operator ==( SemanticVersion lhs, SemanticVersion rhs ) => lhs.Equals( rhs );
|
||||
|
||||
/// <summary>Compares two <see cref="SemanticVersion"/> instances for inequality</summary>
|
||||
/// <param name="lhs">Left hand side of the comparison</param>
|
||||
/// <param name="rhs">Right hand side of the comparison</param>
|
||||
/// <returns><see langword="true"/> if the two versions are not equivalent</returns>
|
||||
public static bool operator !=( SemanticVersion lhs, SemanticVersion rhs )
|
||||
{
|
||||
return !lhs.Equals( rhs );
|
||||
}
|
||||
public static bool operator !=( SemanticVersion lhs, SemanticVersion rhs ) => !lhs.Equals( rhs );
|
||||
|
||||
private static bool ValidateBuildIdentifierParts( IEnumerable<string> metadataParts )
|
||||
{
|
||||
var q = from part in metadataParts
|
||||
let result = BuildIdentifier.End().TryParse( part )
|
||||
let result = Grammar.BuildIdentifier.End().TryParse( part )
|
||||
where !result.WasSuccessful
|
||||
select part;
|
||||
return !q.Any( );
|
||||
|
@ -332,7 +326,7 @@ namespace SemVer.NET
|
|||
private static bool ValidatePrereleaseIdentifierParts( IEnumerable<string> metadataParts )
|
||||
{
|
||||
var q = from part in metadataParts
|
||||
let result = PrereleaseIdentifier.End( ).TryParse( part )
|
||||
let result = Grammar.PrereleaseIdentifier.End( ).TryParse( part )
|
||||
where !result.WasSuccessful
|
||||
select part;
|
||||
return !q.Any( );
|
||||
|
@ -346,113 +340,24 @@ namespace SemVer.NET
|
|||
// so it is only done once.
|
||||
private int? HashCode;
|
||||
|
||||
// When using the default constructor the pre-release and build meta arrays will be null
|
||||
// When using the default constructor the prerelease and build meta arrays will be null
|
||||
// The property accessors for those arrays will test for null and use this singleton empty
|
||||
// array if null to prevent null reference issues.
|
||||
static readonly string[] EmptyStringArray = new string [0];
|
||||
|
||||
private SemanticVersion( ParseResult parts, SemanticVersionOptions options )
|
||||
: this( parts.Major
|
||||
, parts.Minor
|
||||
, parts.Patch.GetOrDefault( )
|
||||
private SemanticVersion( ParseResult parts, ParseOptions options )
|
||||
: this( int.Parse( parts.Major )
|
||||
, int.Parse( parts.Minor )
|
||||
, string.IsNullOrWhiteSpace( parts.Patch ) ? 0 : int.Parse( parts.Patch )
|
||||
, parts.Prerelease
|
||||
, parts.BuildMetadata
|
||||
)
|
||||
{
|
||||
if( !options.HasFlag( SemanticVersionOptions.PatchOptional ) && !parts.Patch.IsDefined )
|
||||
if( !options.HasFlag( ParseOptions.PatchOptional ) && string.IsNullOrWhiteSpace( parts.Patch ) )
|
||||
throw new FormatException( "Patch component of Semantic Version is required" );
|
||||
|
||||
if( !options.HasFlag( ParseOptions.AllowLeadingV ) && parts.LeadingV.HasValue )
|
||||
throw new FormatException( "Leading 'v' characters not supported in SemanticVersion. Use ParseOptions.AllowLeadingV to enable this non-standard extension" );
|
||||
}
|
||||
|
||||
#region static parsers
|
||||
// For full details of the syntax (including formal BNF grammar)
|
||||
// see: https://github.com/mojombo/semver/blob/master/semver.md
|
||||
|
||||
private class ParseResult
|
||||
{
|
||||
internal ParseResult( int major
|
||||
, int minor
|
||||
, IOption<int> patch
|
||||
, IOption<IEnumerable<string>> prereleaseParts
|
||||
, IOption<IEnumerable<string>> buildParts
|
||||
)
|
||||
{
|
||||
Major = major;
|
||||
Minor = minor;
|
||||
Patch = patch;
|
||||
Prerelease = prereleaseParts.GetOrElse( Enumerable.Empty<string>() );
|
||||
BuildMetadata = buildParts.GetOrElse( Enumerable.Empty<string>() );
|
||||
}
|
||||
|
||||
internal int Major { get; }
|
||||
internal int Minor { get; }
|
||||
internal IOption<int> Patch { get; }
|
||||
internal IEnumerable<string> Prerelease { get; }
|
||||
internal IEnumerable<string> BuildMetadata { get; }
|
||||
}
|
||||
|
||||
private static Parser<char> Range( char start, char end ) => Sprache.Parse.Chars( Enumerable.Range( start, end ).Select( i=>(char)i ).ToArray() );
|
||||
|
||||
private static Parser<char> Dot = Sprache.Parse.Char('.');
|
||||
private static Parser<char> Zero = Sprache.Parse.Char( '0' );
|
||||
private static Parser<char> Dash = Sprache.Parse.Char( '-' );
|
||||
|
||||
private static Parser<char> StartBuild = Sprache.Parse.Char( '+' );
|
||||
private static Parser<char> StartPreRelease = Dash;
|
||||
|
||||
private static Parser<char> Letter = Range('a','z').Or( Range('A','Z'));
|
||||
private static Parser<char> NonDigit = Letter.Or( Dash );
|
||||
private static Parser<char> NonZeroDigit = Range( '1', '9' );
|
||||
private static Parser<char> Digit = Zero.Or( NonZeroDigit );
|
||||
private static Parser<char> IdentifierChar = Digit.Or( NonDigit );
|
||||
|
||||
private static Parser<IEnumerable<char>> IdentifierCharacters = IdentifierChar.AtLeastOnce();
|
||||
private static Parser<IEnumerable<char>> Digits = Digit.AtLeastOnce();
|
||||
|
||||
private static Parser<IEnumerable<char>> NumericIdentifier
|
||||
= Zero.Once()
|
||||
.Or( NonZeroDigit.Once().Concat( Digits ) )
|
||||
.Or( NonZeroDigit.Once() );
|
||||
|
||||
private static Parser<IEnumerable<char>> AlphaNumericIdentifier
|
||||
= IdentifierCharacters.Concat( NonDigit.Once().Concat( IdentifierCharacters ) )
|
||||
.Or( IdentifierCharacters.Concat( NonDigit.Once() ) )
|
||||
.Or( NonDigit.Once().Concat( IdentifierCharacters ) )
|
||||
.Or( NonDigit.Once() );
|
||||
|
||||
|
||||
private static Parser<string> BuildIdentifier = AlphaNumericIdentifier
|
||||
.Or( Digits )
|
||||
.Text();
|
||||
|
||||
private static Parser<IEnumerable<string>> DotSeparatedBuildIdentifiers = BuildIdentifier.DelimitedBy( Dot );
|
||||
|
||||
|
||||
private static Parser<string> PrereleaseIdentifier = AlphaNumericIdentifier
|
||||
.Or( NumericIdentifier )
|
||||
.Text();
|
||||
|
||||
private static Parser<IEnumerable<string>> DotSeparatedReleaseIdentifiers = PrereleaseIdentifier.DelimitedBy( Dot );
|
||||
|
||||
private static Parser<int> BuildNumber = from numString in NumericIdentifier.Text()
|
||||
select int.Parse( numString );
|
||||
|
||||
private static Parser<ParseResult> SemanticVersionParser
|
||||
= from major in BuildNumber
|
||||
from sep1 in Dot
|
||||
from minor in BuildNumber
|
||||
from patch in ( from sep2 in Dot
|
||||
from patchValue in BuildNumber
|
||||
select patchValue
|
||||
).Optional()
|
||||
from preRelease in ( from start in StartPreRelease
|
||||
from preRelIds in DotSeparatedReleaseIdentifiers
|
||||
select preRelIds
|
||||
).Optional()
|
||||
from buildMetadata in ( from start in StartBuild
|
||||
from buildIds in DotSeparatedBuildIdentifiers
|
||||
select buildIds
|
||||
).Optional()
|
||||
select new ParseResult( major, minor, patch, preRelease, buildMetadata );
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using SemVer.NET;
|
||||
using Sprache;
|
||||
|
||||
namespace UnitTests
|
||||
{
|
||||
[TestClass]
|
||||
public class GrammarTests
|
||||
{
|
||||
private const string validIdentifierNonDigits = "ABCDEFGHIJKMLNOPQRSTUVWXYZabcdefghijkmlnopqrstuvwxyz-";
|
||||
|
||||
// build metadata identifiers allow leading 0
|
||||
// but release identifiers don't (no explanation, given in spec on why...)
|
||||
|
||||
[TestMethod]
|
||||
public void ValidBuildIdentifierParserTest( )
|
||||
{
|
||||
Assert.AreEqual( "1234", Grammar.BuildIdentifier.End( ).Parse( "1234" ) );
|
||||
Assert.AreEqual( "01234", Grammar.BuildIdentifier.End( ).Parse( "01234" ) );
|
||||
Assert.AreEqual( "A01234", Grammar.BuildIdentifier.End( ).Parse( "A01234" ) );
|
||||
Assert.AreEqual( "0ABCD", Grammar.BuildIdentifier.End( ).Parse( "0ABCD" ) );
|
||||
Assert.AreEqual( validIdentifierNonDigits, Grammar.BuildIdentifier.End( ).Parse( validIdentifierNonDigits ) );
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException( typeof( ParseException ) )]
|
||||
public void InvalidBuildIdentifierFailsTest( )
|
||||
{
|
||||
Assert.AreEqual( "A$BCD", Grammar.PrereleaseIdentifier.End( ).Parse( "A$BCD" ) );
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ValidPrereleaseIdentifierParserTest( )
|
||||
{
|
||||
Assert.AreEqual( "1234", Grammar.PrereleaseIdentifier.End( ).Parse( "1234" ) );
|
||||
Assert.AreEqual( "A01234", Grammar.PrereleaseIdentifier.End( ).Parse( "A01234" ) );
|
||||
Assert.AreEqual( validIdentifierNonDigits, Grammar.PrereleaseIdentifier.End( ).Parse( validIdentifierNonDigits ) );
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException( typeof( ParseException ) )]
|
||||
public void Leading0NumericPrereleaseIdentifierFailsTest( )
|
||||
{
|
||||
Assert.AreEqual( "01234", Grammar.PrereleaseIdentifier.End( ).Parse( "01234" ) );
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException( typeof( ParseException ) )]
|
||||
public void Leading0AlphaNumericPrereleaseIdentifierFailsTest( )
|
||||
{
|
||||
Assert.AreEqual( "0ABCD", Grammar.PrereleaseIdentifier.End( ).Parse( "0ABCD" ) );
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ParsingDotSeparatedBuildIdentifiersSucceedsTest( )
|
||||
{
|
||||
string[ ] parts =
|
||||
{
|
||||
"1234",
|
||||
"01234",
|
||||
"A01234",
|
||||
"0ABCD",
|
||||
};
|
||||
|
||||
var parsedParts = Grammar.DotSeparatedBuildIdentifiers.End( ).Parse( string.Join( ".", parts ) ).ToArray( );
|
||||
Assert.AreEqual( parts.Length, parsedParts.Length );
|
||||
for( int i = 0; i < parts.Length; ++i )
|
||||
{
|
||||
Assert.AreEqual( parts[ i ], parsedParts[ i ], false, $"Mismatch at index: {i}" );
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ParsingDotSeparatedReleaseIdentifiersSucceedsTest( )
|
||||
{
|
||||
string[ ] parts =
|
||||
{
|
||||
"1234",
|
||||
"A01234",
|
||||
"BCDEF",
|
||||
};
|
||||
|
||||
var parsedParts = Grammar.DotSeparatedReleaseIdentifiers.End( ).Parse( string.Join( ".", parts ) ).ToArray( );
|
||||
Assert.AreEqual( parts.Length, parsedParts.Length );
|
||||
for( int i = 0; i < parts.Length; ++i )
|
||||
{
|
||||
Assert.AreEqual( parts[ i ], parsedParts[ i ], false, $"Mismatch at index: {i}" );
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ParsingValidBuildNumberSucceeds( )
|
||||
{
|
||||
Assert.AreEqual( "87654", Grammar.BuildNumber.End( ).Parse( "87654" ) );
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException( typeof( ParseException ) )]
|
||||
public void ParsingLeading0BuildNumberFails( )
|
||||
{
|
||||
Assert.AreEqual( "087654", Grammar.BuildNumber.End( ).Parse( "087654" ) );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -129,7 +129,7 @@ namespace UnitTests
|
|||
[TestMethod]
|
||||
public void StaticParseDefaultPatchTest()
|
||||
{
|
||||
var ver = SemanticVersion.Parse( "0.1-alpha.beta+foo-bar.baz", SemanticVersionOptions.PatchOptional );
|
||||
var ver = SemanticVersion.Parse( "0.1-alpha.beta+foo-bar.baz", ParseOptions.PatchOptional );
|
||||
Assert.AreEqual( 0, ver.Major );
|
||||
Assert.AreEqual( 1, ver.Minor );
|
||||
Assert.AreEqual( 0, ver.Patch );
|
||||
|
@ -149,7 +149,7 @@ namespace UnitTests
|
|||
[TestMethod]
|
||||
public void StaticParseSimpleMajorMinorOnlyTest( )
|
||||
{
|
||||
var ver = SemanticVersion.Parse( "2.1", SemanticVersionOptions.PatchOptional );
|
||||
var ver = SemanticVersion.Parse( "2.1", ParseOptions.PatchOptional );
|
||||
Assert.AreEqual( 2, ver.Major );
|
||||
Assert.AreEqual( 1, ver.Minor );
|
||||
Assert.AreEqual( 0, ver.Patch );
|
||||
|
@ -170,7 +170,7 @@ namespace UnitTests
|
|||
[TestMethod]
|
||||
public void StaticParseNumericIdentifier()
|
||||
{
|
||||
var ver = SemanticVersion.Parse( "2.0.1-2.alpha", SemanticVersionOptions.PatchOptional );
|
||||
var ver = SemanticVersion.Parse( "2.0.1-2.alpha", ParseOptions.PatchOptional );
|
||||
Assert.AreEqual( 2, ver.Major );
|
||||
Assert.AreEqual( 0, ver.Minor );
|
||||
Assert.AreEqual( 1, ver.Patch );
|
||||
|
|
|
@ -44,6 +44,10 @@
|
|||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Sprache, Version=2.0.0.0, Culture=neutral, PublicKeyToken=23dafc55df9bd3a3, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Sprache.JetBrains.2.0.0.44\lib\portable-net4+netcore45+win8+wp8+sl5+MonoAndroid1+MonoTouch1\Sprache.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
</ItemGroup>
|
||||
<Choose>
|
||||
|
@ -59,6 +63,7 @@
|
|||
</Otherwise>
|
||||
</Choose>
|
||||
<ItemGroup>
|
||||
<Compile Include="GrammarTests.cs" />
|
||||
<Compile Include="SemanticVersionTests.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
@ -68,6 +73,9 @@
|
|||
<Name>SemVer.NET</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Choose>
|
||||
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
|
||||
<ItemGroup>
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Sprache.JetBrains" version="2.0.0.44" targetFramework="net45" />
|
||||
</packages>
|
Загрузка…
Ссылка в новой задаче