From 1bf0a6ea05023e6e1564cc421e1d5761db6f7471 Mon Sep 17 00:00:00 2001 From: Steve Maillet Date: Thu, 7 Apr 2016 22:00:44 -0700 Subject: [PATCH] -Moved CMSIS-PAck specific version parsing out of SemanticVersion classes to keep SemanticVersion clean and conformant, while still handling the lrelaxed parsing needs of CMSIS - refactored more of the XSD generated classes - Added Support to the SemanticVersion Grammar and parser to handle superset of valid syntax. All Valid Semantic versions parse, and a number of officially invalid ones do. This allows re-use of the primary parsing with other variants that have more lax syntax constraints. - Added VersionQualifier to handle special cases of optional leading character in some forms of relaxed syntax --- CMSIS.Pack/CMSIS.Pack.csproj | 4 +- CMSIS.Pack/PackDescription/ApiType.cs | 2 +- .../PackDescription/ComponenOrBundleGroup.cs | 3 +- CMSIS.Pack/PackDescription/Component.cs | 3 +- .../PackDescription/ComponentCategory.cs | 4 +- CMSIS.Pack/PackDescription/Debug.cs | 43 ++++++ CMSIS.Pack/PackDescription/DebugType.cs | 111 -------------- CMSIS.Pack/PackDescription/DebugVarsType.cs | 63 +++----- CMSIS.Pack/PackDescription/DeviceType.cs | 4 +- .../PackDescription/DeviceTypeVariant.cs | 4 +- CMSIS.Pack/PackDescription/Family.cs | 4 +- CMSIS.Pack/PackDescription/Release.cs | 2 +- CMSIS.Pack/PackDescription/SubFamily.cs | 4 +- CMSIS.Pack/VersionParser.cs | 65 ++++++++ SemVer.NET/Grammar.cs | 15 +- SemVer.NET/ParseResult.cs | 27 ++-- SemVer.NET/SemVer.NET.csproj | 1 + SemVer.NET/SemanticVersion.cs | 140 ++++++++---------- UnitTests/SemanticVersionTests.cs | 68 ++++----- UnitTests/UnitTests.csproj | 1 + 20 files changed, 273 insertions(+), 295 deletions(-) create mode 100644 CMSIS.Pack/PackDescription/Debug.cs delete mode 100644 CMSIS.Pack/PackDescription/DebugType.cs create mode 100644 CMSIS.Pack/VersionParser.cs diff --git a/CMSIS.Pack/CMSIS.Pack.csproj b/CMSIS.Pack/CMSIS.Pack.csproj index d922dc5..5f85e5f 100644 --- a/CMSIS.Pack/CMSIS.Pack.csproj +++ b/CMSIS.Pack/CMSIS.Pack.csproj @@ -48,6 +48,7 @@ + @@ -75,7 +76,7 @@ - + @@ -123,6 +124,7 @@ + diff --git a/CMSIS.Pack/PackDescription/ApiType.cs b/CMSIS.Pack/PackDescription/ApiType.cs index bc0bbff..de3a930 100644 --- a/CMSIS.Pack/PackDescription/ApiType.cs +++ b/CMSIS.Pack/PackDescription/ApiType.cs @@ -43,7 +43,7 @@ namespace CMSIS.Pack.PackDescription get { return ApiVersion.ToString(); } set { - ApiVersion = SemanticVersion.Parse( value ); + ApiVersion = VersionParser.Parse( value ); } } diff --git a/CMSIS.Pack/PackDescription/ComponenOrBundleGroup.cs b/CMSIS.Pack/PackDescription/ComponenOrBundleGroup.cs index 310d00f..5238907 100644 --- a/CMSIS.Pack/PackDescription/ComponenOrBundleGroup.cs +++ b/CMSIS.Pack/PackDescription/ComponenOrBundleGroup.cs @@ -22,8 +22,7 @@ namespace CMSIS.Pack.PackDescription get { return Version.ToString( ); } set { - // NOTE: Gracefully handle some real-world PDSC files with simple versions - Version = SemanticVersion.Parse( value, ParseOptions.PatchOptional ); + Version = VersionParser.Parse( value ); } } diff --git a/CMSIS.Pack/PackDescription/Component.cs b/CMSIS.Pack/PackDescription/Component.cs index a6bed49..4b88c13 100644 --- a/CMSIS.Pack/PackDescription/Component.cs +++ b/CMSIS.Pack/PackDescription/Component.cs @@ -43,8 +43,7 @@ namespace CMSIS.Pack.PackDescription get { return Version.ToString( ); } set { - // NOTE: Gracefully handle some real-world PDSC files with simple versions - Version = SemanticVersion.Parse( value, ParseOptions.PatchOptional ); + Version = VersionParser.Parse( value ); } } diff --git a/CMSIS.Pack/PackDescription/ComponentCategory.cs b/CMSIS.Pack/PackDescription/ComponentCategory.cs index 966a07f..c40f3f2 100644 --- a/CMSIS.Pack/PackDescription/ComponentCategory.cs +++ b/CMSIS.Pack/PackDescription/ComponentCategory.cs @@ -39,7 +39,7 @@ namespace CMSIS.Pack.PackDescription public string RawCversionString { get { return Version.ToString(); } - set { Version = SemanticVersion.Parse( value ); } + set { Version = VersionParser.Parse( value ); } } [XmlIgnore] @@ -51,7 +51,7 @@ namespace CMSIS.Pack.PackDescription public string RawCapiversionString { get { return ApiVersion.ToString(); } - set { ApiVersion = SemanticVersion.Parse( value ); } + set { ApiVersion = VersionParser.Parse( value ); } } [XmlIgnore] diff --git a/CMSIS.Pack/PackDescription/Debug.cs b/CMSIS.Pack/PackDescription/Debug.cs new file mode 100644 index 0000000..259d0f7 --- /dev/null +++ b/CMSIS.Pack/PackDescription/Debug.cs @@ -0,0 +1,43 @@ +using System; +using System.ComponentModel; +using System.Xml; +using System.Xml.Serialization; +using Sprache; + +namespace CMSIS.Pack.PackDescription +{ + [Serializable( )] + public class Debug + { + /// + [XmlElement( "datapatch")] + public DebugDataPatch[] Datapatch { get; set; } + + /// + [XmlAttribute( "__dp", Form=System.Xml.Schema.XmlSchemaForm.Qualified)] + [DefaultValue( 0 )] + public uint DebugPortId { get; set; } = 0; + + /// + [XmlAttribute( "__ap", Form=System.Xml.Schema.XmlSchemaForm.Qualified)] + [DefaultValue( 0 )] + public uint AccessPortIndex { get; set; } = 0; + + /// + [XmlAttribute( "svd", Form=System.Xml.Schema.XmlSchemaForm.Qualified)] + public string SvdFile { get; set; } + + /// + [XmlAttribute( "Pname", Form = System.Xml.Schema.XmlSchemaForm.Qualified )] + public string ProcessorName + { + get { return ProcessorName_; } + set { ProcessorName_ = Parsers.RestrictedString.Parse( value ); } + } + private string ProcessorName_; + + /// + [XmlAnyAttribute( )] + public XmlAttribute[ ] AnyAttr { get; set; } + } +} \ No newline at end of file diff --git a/CMSIS.Pack/PackDescription/DebugType.cs b/CMSIS.Pack/PackDescription/DebugType.cs deleted file mode 100644 index 2fcdf20..0000000 --- a/CMSIS.Pack/PackDescription/DebugType.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Xml; -using System.Xml.Serialization; -using Sprache; - -namespace CMSIS.Pack.PackDescription -{ - [Serializable( )] - public class DebugType { - - private DebugDataPatch[] datapatchField; - - private uint @__dpField; - - private bool @__dpFieldSpecified; - - private uint @__apField; - - private bool @__apFieldSpecified; - - private string svdField; - - private XmlAttribute[] anyAttrField; - - /// - [XmlElement( "datapatch")] - public DebugDataPatch[] datapatch { - get { - return datapatchField; - } - set { - datapatchField = value; - } - } - - /// - [XmlAttribute( Form=System.Xml.Schema.XmlSchemaForm.Qualified)] - public uint @__dp { - get { - return @__dpField; - } - set { - @__dpField = value; - } - } - - /// - [XmlIgnore( )] - public bool @__dpSpecified { - get { - return @__dpFieldSpecified; - } - set { - @__dpFieldSpecified = value; - } - } - - /// - [XmlAttribute( Form=System.Xml.Schema.XmlSchemaForm.Qualified)] - public uint @__ap { - get { - return @__apField; - } - set { - @__apField = value; - } - } - - /// - [XmlIgnore( )] - public bool @__apSpecified { - get { - return @__apFieldSpecified; - } - set { - @__apFieldSpecified = value; - } - } - - /// - [XmlAttribute( Form=System.Xml.Schema.XmlSchemaForm.Qualified)] - public string svd { - get { - return svdField; - } - set { - svdField = value; - } - } - - /// - [XmlAttribute( "Pname", Form = System.Xml.Schema.XmlSchemaForm.Qualified )] - public string ProcessorName - { - get { return ProcessorName_; } - set { ProcessorName_ = Parsers.RestrictedString.Parse( value ); } - } - private string ProcessorName_; - - /// - [XmlAnyAttribute( )] - public XmlAttribute[ ] AnyAttr { - get { - return anyAttrField; - } - set { - anyAttrField = value; - } - } - } -} \ No newline at end of file diff --git a/CMSIS.Pack/PackDescription/DebugVarsType.cs b/CMSIS.Pack/PackDescription/DebugVarsType.cs index 7049ce1..06d374e 100644 --- a/CMSIS.Pack/PackDescription/DebugVarsType.cs +++ b/CMSIS.Pack/PackDescription/DebugVarsType.cs @@ -1,43 +1,32 @@ using System; using System.Xml; using System.Xml.Serialization; +using SemVer.NET; using Sprache; namespace CMSIS.Pack.PackDescription { [Serializable( )] - public class DebugVarsType { - - private string configfileField; - - private string versionField; - - private XmlAttribute[] anyAttrField; - - private string valueField; - + public class DebugVarsType + { /// - [XmlAttribute( Form=System.Xml.Schema.XmlSchemaForm.Qualified)] - public string configfile { - get { - return configfileField; - } - set { - configfileField = value; - } - } - - /// - [XmlAttribute( Form=System.Xml.Schema.XmlSchemaForm.Qualified)] - public string version { - get { - return versionField; - } - set { - versionField = value; + [XmlAttribute( "configfile", Form=System.Xml.Schema.XmlSchemaForm.Qualified )] + public string ConfigFile { get; set; } + + [XmlAttribute( "version", Form = System.Xml.Schema.XmlSchemaForm.Qualified )] + [System.ComponentModel.EditorBrowsable( System.ComponentModel.EditorBrowsableState.Never )] + public string RawVersionString + { + get { return Version.ToString( ); } + set + { + Version = VersionParser.Parse( value ); } } + [XmlIgnore] + public SemanticVersion Version { get; set; } + /// [XmlAttribute( "Pname", Form = System.Xml.Schema.XmlSchemaForm.Qualified )] public string ProcessorName @@ -49,24 +38,10 @@ namespace CMSIS.Pack.PackDescription /// [XmlAnyAttribute( )] - public XmlAttribute[ ] AnyAttr { - get { - return anyAttrField; - } - set { - anyAttrField = value; - } - } + public XmlAttribute[ ] AnyAttr { get; set; } /// [XmlText( )] - public string Value { - get { - return valueField; - } - set { - valueField = value; - } - } + public string Value { get; set; } } } \ No newline at end of file diff --git a/CMSIS.Pack/PackDescription/DeviceType.cs b/CMSIS.Pack/PackDescription/DeviceType.cs index 1e2ac53..bbb909b 100644 --- a/CMSIS.Pack/PackDescription/DeviceType.cs +++ b/CMSIS.Pack/PackDescription/DeviceType.cs @@ -27,7 +27,7 @@ namespace CMSIS.Pack.PackDescription private DebugPort[] debugportField; - private DebugType[] debugField; + private Debug[] debugField; private TraceType[] traceField; @@ -150,7 +150,7 @@ namespace CMSIS.Pack.PackDescription /// [XmlElement( "debug")] - public DebugType[] debug { + public Debug[] debug { get { return debugField; } diff --git a/CMSIS.Pack/PackDescription/DeviceTypeVariant.cs b/CMSIS.Pack/PackDescription/DeviceTypeVariant.cs index a78a826..b0ca9b6 100644 --- a/CMSIS.Pack/PackDescription/DeviceTypeVariant.cs +++ b/CMSIS.Pack/PackDescription/DeviceTypeVariant.cs @@ -28,7 +28,7 @@ namespace CMSIS.Pack.PackDescription private DebugPort[] debugportField; - private DebugType[] debugField; + private Debug[] debugField; private TraceType[] traceField; @@ -149,7 +149,7 @@ namespace CMSIS.Pack.PackDescription /// [XmlElement( "debug")] - public DebugType[] debug { + public Debug[] debug { get { return debugField; } diff --git a/CMSIS.Pack/PackDescription/Family.cs b/CMSIS.Pack/PackDescription/Family.cs index 8a3adde..9fde228 100644 --- a/CMSIS.Pack/PackDescription/Family.cs +++ b/CMSIS.Pack/PackDescription/Family.cs @@ -28,7 +28,7 @@ namespace CMSIS.Pack.PackDescription private DebugPort[] debugportField; - private DebugType[] debugField; + private Debug[] debugField; private TraceType[] traceField; @@ -155,7 +155,7 @@ namespace CMSIS.Pack.PackDescription /// [XmlElement( "debug")] - public DebugType[] debug { + public Debug[] debug { get { return debugField; } diff --git a/CMSIS.Pack/PackDescription/Release.cs b/CMSIS.Pack/PackDescription/Release.cs index 885117a..4551445 100644 --- a/CMSIS.Pack/PackDescription/Release.cs +++ b/CMSIS.Pack/PackDescription/Release.cs @@ -17,7 +17,7 @@ namespace CMSIS.Pack.PackDescription get { return Version.ToString( ); } set { - Version = SemanticVersion.Parse( value ); + Version = VersionParser.Parse( value ); } } diff --git a/CMSIS.Pack/PackDescription/SubFamily.cs b/CMSIS.Pack/PackDescription/SubFamily.cs index c4cc9a3..0317d68 100644 --- a/CMSIS.Pack/PackDescription/SubFamily.cs +++ b/CMSIS.Pack/PackDescription/SubFamily.cs @@ -28,7 +28,7 @@ namespace CMSIS.Pack.PackDescription private DebugPort[] debugportField; - private DebugType[] debugField; + private Debug[] debugField; private TraceType[] traceField; @@ -151,7 +151,7 @@ namespace CMSIS.Pack.PackDescription /// [XmlElement( "debug")] - public DebugType[] debug { + public Debug[] debug { get { return debugField; } diff --git a/CMSIS.Pack/VersionParser.cs b/CMSIS.Pack/VersionParser.cs new file mode 100644 index 0000000..055eda4 --- /dev/null +++ b/CMSIS.Pack/VersionParser.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using SemVer.NET; +using Sprache; + +namespace CMSIS.Pack +{ + // CMSIS-Pack uses a relaxed syntax for semantic versions. + // Deviations from the official spec are: + // 1) Trailing 0s on Minor and Patch are ignored + // 2) Patch is optional, if not present a default of 0 is assumed + // 3) The hyphen prerelease delimiter is optional if the first + // character of the prerelease identifier is a letter + // + // This utility class provides for relaxed parsing to generate + // a full SemanticVersion that, when converted back to a string + // will conform to the SemanticVersion 2.0.0 specs. Thus, this + // class serves to normalize the nonstandard versions into the + // standard form. + internal class VersionParser + { + internal static SemanticVersion Parse( string version) + { + var parts = Grammar.SemanticVersion.Parse( version ); + + // handle case #2 + var patch = string.IsNullOrWhiteSpace( parts.Patch ) ? "0" : parts.Patch; + + // handle case #1 + patch = patch.Length > 1 ? patch.TrimEnd('0') : patch; + var minor = parts.Minor.Length > 1 ? parts.Minor.TrimEnd('0') : parts.Minor; + + if( parts.LeadingV.HasValue ) + throw new FormatException( "Leading 'v' characters not supported in CMSIS-PACK Versions" ); + + return new SemanticVersion( BigInteger.Parse( parts.Major ) + , BigInteger.Parse( minor ) + , BigInteger.Parse( patch ) + , GetPrereleaseQualifiers( parts.Prerelease ) + , parts.BuildMetadata?.Identifiers ?? Enumerable.Empty( ) + ); + } + + private static IEnumerable GetPrereleaseQualifiers( VersionQualifier qualifier ) + { + if( qualifier == null ) + yield break; + + var firstIdentifier = qualifier.Identifiers.FirstOrDefault( ); + if( firstIdentifier == null ) + yield break; + + // handles case #3 + if( char.IsLetter( qualifier.LeadingChar ) ) + firstIdentifier = $"{qualifier.LeadingChar}{firstIdentifier}"; + + yield return firstIdentifier; + + foreach( var identifier in qualifier.Identifiers.Skip( 1 ) ) + yield return identifier; + } + } +} diff --git a/SemVer.NET/Grammar.cs b/SemVer.NET/Grammar.cs index 4d3bfe8..a540765 100644 --- a/SemVer.NET/Grammar.cs +++ b/SemVer.NET/Grammar.cs @@ -70,6 +70,10 @@ namespace SemVer.NET public static Parser BuildNumber = NumericIdentifier.Text(); /// Parser monad to parse a semantic version string into a + /// + /// This parser allows for a number of variations of SemanticVersions in the wild by parsing a + /// superset of SemanticVersion + /// public static Parser SemanticVersion = from leadingV in LeadingV.Optional() from major in BuildNumber @@ -79,20 +83,21 @@ namespace SemVer.NET from patchValue in BuildNumber select patchValue ).Optional() - from preRelease in ( from start in StartPreRelease + from preRelease in ( from start in StartPreRelease.Or( Letter ) from preRelIds in DotSeparatedReleaseIdentifiers - select preRelIds + select new VersionQualifier( start, preRelIds ) ).Optional() from buildMetadata in ( from start in StartBuild from buildIds in DotSeparatedBuildIdentifiers - select buildIds + select new VersionQualifier( start, buildIds ) ).Optional() select new ParseResult( leadingV.IsDefined ? new char?( leadingV.Get() ) : null , major , minor , patch.GetOrElse( string.Empty ) - , preRelease.GetOrElse( Enumerable.Empty( ) ) - , buildMetadata.GetOrElse( Enumerable.Empty( ) ) + , preRelease.GetOrDefault( ) + , buildMetadata.GetOrDefault( ) ); } + } diff --git a/SemVer.NET/ParseResult.cs b/SemVer.NET/ParseResult.cs index 663b225..71735e5 100644 --- a/SemVer.NET/ParseResult.cs +++ b/SemVer.NET/ParseResult.cs @@ -3,6 +3,19 @@ using System.Collections.Generic; namespace SemVer.NET { + /// Parse result of a Version Prerelease or build qualifier + public class VersionQualifier + { + public VersionQualifier( char leadingChar, IEnumerable identifiers) + { + LeadingChar = leadingChar; + Identifiers = identifiers; + } + + public char LeadingChar { get; } + public IEnumerable Identifiers { get; } + } + /// Contains the results of parsing a Semantic version string /// /// In order to support variations on Semantic versions as well as gracefully @@ -27,8 +40,8 @@ namespace SemVer.NET , string major , string minor , string patch - , IEnumerable prereleaseParts - , IEnumerable buildParts + , VersionQualifier prereleaseParts + , VersionQualifier buildParts ) { if( major == null ) @@ -40,12 +53,6 @@ namespace SemVer.NET 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; @@ -83,9 +90,9 @@ namespace SemVer.NET public string Patch { get; } /// Gets a collection of prerelease identifier strings - public IEnumerable Prerelease { get; } + public VersionQualifier Prerelease { get; } /// Gets a collection of the build metadata identifier strings - public IEnumerable BuildMetadata { get; } + public VersionQualifier BuildMetadata { get; } } } \ No newline at end of file diff --git a/SemVer.NET/SemVer.NET.csproj b/SemVer.NET/SemVer.NET.csproj index 8778b67..04b9e1e 100644 --- a/SemVer.NET/SemVer.NET.csproj +++ b/SemVer.NET/SemVer.NET.csproj @@ -37,6 +37,7 @@ + diff --git a/SemVer.NET/SemanticVersion.cs b/SemVer.NET/SemanticVersion.cs index 33f541d..58c9542 100644 --- a/SemVer.NET/SemanticVersion.cs +++ b/SemVer.NET/SemanticVersion.cs @@ -2,36 +2,21 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Numerics; using System.Text; using Sprache; namespace SemVer.NET { - /// Option flags for parsing a semantic version string - [Flags] - public enum ParseOptions - { - /// No special handling, parsing proceeds according to Semantic Version Specification - None = 0, - /// Patch build number is optional, and if not present in the string a default of "0" is assumed - PatchOptional = 1, - /// Allow a leading 'v' or 'V' as a common convention for version numbers - AllowLeadingV = 2, - } - - // TODO: Add additional Comparer implementation to handle build metadata in precedence - // see: https://github.com/mojombo/semver/issues/200 - // - - /// Version structure for versions based on Semantic Versioning v2.0 as defined by https://github.com/mojombo/semver/blob/master/semver.md + /// Version structure for versions based on SemanticVersion 2.0.0 /// - /// 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. - /// Technically the Major, Minor, and Patch numbers have no length limits, thus this uses + /// This class implements creating, parsing, and comparing semantic version values. + /// + /// Technically, the Major, Minor, and Patch numbers have no length limits, thus this + /// class uses as the type for each of the numeric parts. + /// /// + /// public struct SemanticVersion : IComparable , IComparable @@ -43,7 +28,7 @@ namespace SemVer.NET /// Patch version number /// Array of individual prerelease parts (not including the separating '.') /// Array of individual Build Metadata parts (not including the separating '.') - public SemanticVersion( int major, int minor, int patch, IEnumerable preReleaseParts, IEnumerable metadataParts ) + public SemanticVersion( BigInteger major, BigInteger minor, BigInteger patch, IEnumerable preReleaseParts, IEnumerable metadataParts ) { if( major < 0 ) throw new ArgumentOutOfRangeException( nameof( major ) ); @@ -57,15 +42,18 @@ namespace SemVer.NET Major = major; Minor = minor; Patch = patch; - PreReleaseParts_ = ( preReleaseParts ?? Enumerable.Empty() ).ToArray(); - BuildMetadata_ = ( metadataParts ?? Enumerable.Empty() ).ToArray(); + var prereleaseList = preReleaseParts?.ToList().AsReadOnly() ?? EmptyStringList; + var buildMetadataList = metadataParts?.ToList().AsReadOnly() ?? EmptyStringList; // Validate each part conforms to an "identifier" as defined by the spec - if( !ValidatePrereleaseIdentifierParts( PreReleaseParts_ ) ) + if( !ValidatePrereleaseIdentifierParts( prereleaseList ) ) throw new ArgumentException( "Invalid identifier for prerelease part", nameof( preReleaseParts ) ); - if( !ValidateBuildIdentifierParts( BuildMetadata_ ) ) + if( !ValidateBuildIdentifierParts( buildMetadataList ) ) throw new ArgumentException( "Invalid identifier for build metadata part", nameof( metadataParts ) ); + + PreReleaseParts_ = prereleaseList; + BuildMetadata_ = buildMetadataList; HashCode = null; } @@ -94,13 +82,13 @@ namespace SemVer.NET public bool IsValid => Major >= 0 && Minor >= 0 && Patch >= 0; /// Major version number - public int Major { get; } + public BigInteger Major { get; } /// Minor version number - public int Minor { get; } + public BigInteger Minor { get; } /// Patch version number - public int Patch { get; } + public BigInteger Patch { get; } /// List of identifier parts forming the prerelease value /// @@ -108,8 +96,8 @@ namespace SemVer.NET /// value can consist of multiple parts separated by a '.', this list contains the /// individual parts without the leading '-' or separating '.'. /// - public IReadOnlyList PreReleaseParts => Array.AsReadOnly( PreReleaseParts_ ?? EmptyStringArray ); - private readonly string[ ] PreReleaseParts_; + public IReadOnlyList PreReleaseParts => PreReleaseParts_ ?? EmptyStringList; + private IReadOnlyList PreReleaseParts_; /// List of identifier parts forming the build Metadata value /// @@ -117,8 +105,8 @@ namespace SemVer.NET /// value can consist of multiple parts separated by a '.', this list contains /// the individual parts without the leading '+' or separating '.'. /// - public IReadOnlyList BuildMetadata => Array.AsReadOnly( BuildMetadata_ ?? EmptyStringArray ); - private readonly string[ ] BuildMetadata_; + public IReadOnlyList BuildMetadata => BuildMetadata_ ?? EmptyStringList; + private IReadOnlyList BuildMetadata_; /// public override int GetHashCode( ) @@ -239,13 +227,13 @@ namespace SemVer.NET public string ToString( bool includeBuildMetadata ) { var bldr = new StringBuilder( string.Format( CultureInfo.InvariantCulture, "{0}.{1}.{2}", Major, Minor, Patch ) ); - if( PreReleaseParts.Count > 0 ) + if( ( PreReleaseParts?.Count ?? 0 ) > 0 ) { bldr.Append( '-' ); bldr.Append( string.Join( ".", PreReleaseParts ) ); } - if( BuildMetadata.Count > 0 && includeBuildMetadata ) + if( ( BuildMetadata?.Count ?? 0 ) > 0 && includeBuildMetadata ) { bldr.Append( '+' ); bldr.Append( string.Join( ".", BuildMetadata ) ); @@ -257,22 +245,23 @@ namespace SemVer.NET /// Parse a semantic version string into it's component parts /// String containing the version to parse /// Parsed version details - public static SemanticVersion Parse( string versionString ) => Parse( versionString, ParseOptions.None ); - - /// Parse a semantic version string into it's component parts - /// String containing the version to parse - /// Flag indicating non-standard optional default (0) for the patch if not present - /// Parsed version details - /// - /// 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. - /// - public static SemanticVersion Parse( string versionString, ParseOptions options ) + public static SemanticVersion Parse( string versionString ) { try { var parts = Grammar.SemanticVersion.Parse( versionString ); - return new SemanticVersion( parts, options ); + if( string.IsNullOrWhiteSpace( parts.Patch ) ) + throw new FormatException( "Patch component of Semantic Version is required" ); + + if( parts.LeadingV.HasValue ) + throw new FormatException( "Leading 'v' characters not supported in strict SemanticVersion" ); + + return new SemanticVersion( BigInteger.Parse( parts.Major ) + , BigInteger.Parse( parts.Minor ) + , BigInteger.Parse( parts.Patch ) + , parts.Prerelease?.Identifiers ?? Enumerable.Empty() + , parts.BuildMetadata?.Identifiers ?? Enumerable.Empty( ) + ); } catch( ParseException ex ) { @@ -280,25 +269,41 @@ namespace SemVer.NET } } - /// Attempts to parse a version string into a new SemanticVersion instance - /// String to parse - /// Version instance to construct - /// true if the string is a valid semantic version string that was successfully parsed into - public static bool TryParse( string versionString, out SemanticVersion version ) => TryParse( versionString, ParseOptions.None, out version ); - /// Attempts to parse a version string into a new SemanticVersion instance /// String to parse /// Options flags to control parsing variants and ambiguities in the spec /// Version instance to construct /// true if the string is a valid semantic version string that was successfully parsed into - public static bool TryParse( string versionString, ParseOptions options, out SemanticVersion version ) + public static bool TryParse( string versionString, out SemanticVersion version ) { - version = new SemanticVersion(); + version = default( SemanticVersion ); var result = Grammar.SemanticVersion.TryParse( versionString ); if( !result.WasSuccessful ) return false; - version = new SemanticVersion( result.Value, options ); + var parts = result.Value; + if( string.IsNullOrWhiteSpace( parts.Patch ) ) + return false; + + if( parts.LeadingV.HasValue ) + return false; + + BigInteger major,minor,patch; + if( !BigInteger.TryParse( parts.Major, out major ) ) + return false; + + if( !BigInteger.TryParse( parts.Minor, out minor ) ) + return false; + + if( !BigInteger.TryParse( parts.Patch, out patch ) ) + return false; + + version = new SemanticVersion( major + , minor + , patch + , parts.Prerelease?.Identifiers ?? Enumerable.Empty( ) + , parts.BuildMetadata?.Identifiers ?? Enumerable.Empty( ) + ); return true; } @@ -334,7 +339,7 @@ namespace SemVer.NET private int ComputeHashCode( ) => ToString( false ).GetHashCode( ); - // this is intentionally not a read-only field + // This is intentionally not a read-only field // its a private field and used like a C++ mutable. // It caches the HashCode that's expensive to compute // so it is only done once. @@ -343,21 +348,6 @@ namespace SemVer.NET // 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, 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( 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" ); - } + static readonly IReadOnlyList EmptyStringList = Enumerable.Empty().ToList().AsReadOnly(); } } \ No newline at end of file diff --git a/UnitTests/SemanticVersionTests.cs b/UnitTests/SemanticVersionTests.cs index 21387aa..bf2ab16 100644 --- a/UnitTests/SemanticVersionTests.cs +++ b/UnitTests/SemanticVersionTests.cs @@ -1,4 +1,5 @@ using System; +using System.Numerics; using Microsoft.VisualStudio.TestTools.UnitTesting; using SemVer.NET; @@ -126,39 +127,40 @@ namespace UnitTests VerifyToStringReverseParse( ver ); } - [TestMethod] - public void StaticParseDefaultPatchTest() - { - 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 ); - Assert.IsTrue( ver.IsValid ); - Assert.IsTrue( ver.IsDevelopment ); - Assert.IsTrue( ver.IsPrerelease ); - Assert.AreEqual( 2, ver.PreReleaseParts.Count ); - Assert.AreEqual( "alpha", ver.PreReleaseParts[ 0 ] ); - Assert.AreEqual( "beta", ver.PreReleaseParts[ 1 ] ); - Assert.AreEqual( 2, ver.BuildMetadata.Count ); - Assert.AreEqual( "foo-bar", ver.BuildMetadata[ 0 ] ); - Assert.AreEqual( "baz", ver.BuildMetadata[ 1 ] ); - Assert.AreEqual( "0.1.0-alpha.beta+foo-bar.baz", ver.ToString( ) ); - VerifyToStringReverseParse( ver ); - } + // TODO: move to CMSIS-Pack specific tests + //[TestMethod] + //public void StaticParseDefaultPatchTest() + //{ + // 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 ); + // Assert.IsTrue( ver.IsValid ); + // Assert.IsTrue( ver.IsDevelopment ); + // Assert.IsTrue( ver.IsPrerelease ); + // Assert.AreEqual( 2, ver.PreReleaseParts.Count ); + // Assert.AreEqual( "alpha", ver.PreReleaseParts[ 0 ] ); + // Assert.AreEqual( "beta", ver.PreReleaseParts[ 1 ] ); + // Assert.AreEqual( 2, ver.BuildMetadata.Count ); + // Assert.AreEqual( "foo-bar", ver.BuildMetadata[ 0 ] ); + // Assert.AreEqual( "baz", ver.BuildMetadata[ 1 ] ); + // Assert.AreEqual( "0.1.0-alpha.beta+foo-bar.baz", ver.ToString( ) ); + // VerifyToStringReverseParse( ver ); + //} - [TestMethod] - public void StaticParseSimpleMajorMinorOnlyTest( ) - { - var ver = SemanticVersion.Parse( "2.1", ParseOptions.PatchOptional ); - Assert.AreEqual( 2, ver.Major ); - Assert.AreEqual( 1, ver.Minor ); - Assert.AreEqual( 0, ver.Patch ); - Assert.IsTrue( ver.IsValid ); - Assert.IsFalse( ver.IsDevelopment ); - Assert.IsFalse( ver.IsPrerelease ); - Assert.AreEqual( 0, ver.PreReleaseParts.Count ); - VerifyToStringReverseParse( ver ); - } + //[TestMethod] + //public void StaticParseSimpleMajorMinorOnlyTest( ) + //{ + // var ver = SemanticVersion.Parse( "2.1", ParseOptions.PatchOptional ); + // Assert.AreEqual( 2, ver.Major ); + // Assert.AreEqual( 1, ver.Minor ); + // Assert.AreEqual( 0, ver.Patch ); + // Assert.IsTrue( ver.IsValid ); + // Assert.IsFalse( ver.IsDevelopment ); + // Assert.IsFalse( ver.IsPrerelease ); + // Assert.AreEqual( 0, ver.PreReleaseParts.Count ); + // VerifyToStringReverseParse( ver ); + //} [TestMethod] [ExpectedException( typeof( FormatException ))] @@ -170,7 +172,7 @@ namespace UnitTests [TestMethod] public void StaticParseNumericIdentifier() { - var ver = SemanticVersion.Parse( "2.0.1-2.alpha", ParseOptions.PatchOptional ); + var ver = SemanticVersion.Parse( "2.0.1-2.alpha" ); Assert.AreEqual( 2, ver.Major ); Assert.AreEqual( 0, ver.Minor ); Assert.AreEqual( 1, ver.Patch ); diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj index 041fce4..81ad9af 100644 --- a/UnitTests/UnitTests.csproj +++ b/UnitTests/UnitTests.csproj @@ -49,6 +49,7 @@ True +