- 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:
Steve Maillet 2016-04-04 22:17:35 -07:00
Родитель 4e96df1821
Коммит e79e739a46
13 изменённых файлов: 376 добавлений и 161 удалений

Просмотреть файл

@ -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( ) );

98
SemVer.NET/Grammar.cs Normal file
Просмотреть файл

@ -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>( ) )
);
}
}

92
SemVer.NET/ParseResult.cs Normal file
Просмотреть файл

@ -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
}
}

106
UnitTests/GrammarTests.cs Normal file
Просмотреть файл

@ -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>