You're probably wondering "Why should I use this library instead of any other more popular alternatives?". Well, here's a quick overview...
-
Focus on functionality and performance. I will make sure to implement any common manipulations with semantic versions, and I will microoptimize the hell out of everything! See the benchmarks.
SemanticVersion
,SemverPreRelease
,SemverOptions
,SemanticVersionBuilder
,SemverComparer
,SemverComparison
. -
Implements
node-semver
's version ranges. Notably, advanced comparators and wildcards (^1.2.x
,~5.3
) are preserved as is, instead of being desugared into primitives like in all other libraries. That allows to interpret and manipulate version ranges more precisely.VersionRange
,ComparatorSet
,Comparator
,PartialVersion
,PartialComponent
,PrimitiveComparator
,AdvancedComparator
,CaretComparator
,HyphenRangeComparator
,TildeComparator
,XRangeComparator
. -
Operations with version ranges. Now this is definitely a unique feature - this library defines operations for
Comparator
,ComparatorSet
andVersionRange
. You can complement (~
), union (|
), intersect (&
) and desugar ranges. Soon you'll also be able to normalize, transform and minimize ranges. -
Default comparison ignores build metadata. I think it's more correct to have the default comparison be compliant with SemVer's specification. You can still do metadata-sensitive comparison using a custom comparer, if you want -
SemverComparer.IncludeBuild
. -
Out-of-the-box serialization support. Supports serialization/deserialization with
Newtonsoft.Json
,System.Text.Json
andSystem.Xml
(and any libraries usingTypeConverter
s) with no extra configuration needed. -
Thoroughly tested, 100% code coverage. All of the library's functionality has been covered by tests. There may be some really obscure edge cases that haven't been covered, if you encounter them, file an issue here!
-
.NET-style documentation. Written in the style of
System
namespace docs. I don't know if it's worth advertising, but I really like how descriptive and consistent it is, so I thought I should mention that.
SemanticVersion
class,SemverPreRelease
structVersionRange
,ComparatorSet
,Comparator
classesSemanticVersionBuilder
class and incrementing- Advanced
SemanticVersion
formatting
-
SemverPreRelease.ParseMultiple/Many
method; - Advanced
SemverPreRelease
formatting, maybe?; - Coercing versions?;
- Diffing versions?;
- Decrementing versions?;
- IsSubset/IsSuperset methods;
- Simplify methods;
- Normalize methods;
- Style transform methods;
- Minimize methods;
SemanticVersion
represents a valid semantic version as per the SemVer 2.0.0 specification. You can construct, parse, compare and format semantic versions and all of its components. SemverPreRelease
is a valid pre-release identifier, and SemverOptions
specifies a bunch of different parsing options for all scenarios.
var a = new SemanticVersion(1, 0, 0, ["alpha", 8]);
var b = SemanticVersion.Parse("=v 1.02-pre ", SemverOptions.Loose);
Console.WriteLine($"{a} < {b} = {a < b}");
// 1.0.0-alpha.8 < 1.2.0-pre = True
Important
Note that the default comparison doesn't account for build metadata!
For build metadata-sensitive comparison, use SemverComparer.IncludeBuild
or SemverComparer.Exact
.
var a = SemanticVersion.Parse("1.2.3");
var b = SemanticVersion.Parse("1.2.3+BUILD");
Console.WriteLine($"{a} == {b} = {a == b}");
// 1.2.3 == 1.2.3+BUILD = True
var cmp = SemverComparer.Exact;
Console.WriteLine($"{a} === {b} = {cmp.Equals(a, b)}");
// 1.2.3 === 1.2.3+BUILD = False
VersionRange
, ComparatorSet
, Comparator
classes provide methods for doing stuff with node-semver
's version ranges. Notably, advanced comparators and wildcards (^1.2.x
, ~5.3
) are preserved as is, instead of being desugared into primitives like in all other libraries.
var range = VersionRange.Parse("~5.3");
var a = SemanticVersion.Parse("5.0.1");
var b = SemanticVersion.Parse("5.3.2");
var c = SemanticVersion.Parse("5.5.0");
Console.WriteLine($"{a} satisfies {range}: {range.IsSatisfiedBy(a)}");
Console.WriteLine($"{b} satisfies {range}: {range.IsSatisfiedBy(b)}");
Console.WriteLine($"{c} satisfies {range}: {range.IsSatisfiedBy(c)}");
// 5.0.1 satisfies ~5.3: False
// 5.3.2 satisfies ~5.3: True
// 5.5.0 satisfies ~5.3: False
Additionally, you can complement (~
), union (|
) and intersect (&
) these version ranges! That's the best and the most complicated feature provided by this library. You can also Desugar
version ranges (^1.2.3
⇒ >=1.2.3 <2.0.0-0
), and check if comparator sets contain, intersect or touch other comparator sets (Contains
, Intersects
, Touches
).
var A = VersionRange.Parse(">=1.0.0 <2.0.0-0");
var B = VersionRange.Parse(">=1.3.0 <1.5.0-0");
Console.WriteLine($"A & B = {A & B}");
// A & B = >=1.3.0 <1.5.0-0
var C = VersionRange.Parse(">=1.4.0 <1.7.5");
Console.WriteLine($"(A & B) | C = {(A & B) | C}");
// (A & B) | C = >=1.3.0 <1.7.5
Important
By default, pre-releases in version ranges need to be opted-in.
>1.2.3-alpha.3
won't match 3.4.5-alpha.7
, since it has different major/minor/patch version components, and the developer (the one who created the version range) opted in only into the pre-releases of the 1.2.3
version, but not into the pre-releases of the 3.4.5
version. See node-semver
's README for more info.
You can, of course, suppress this behavior by adding a includePreReleases: true
parameter.
var range = VersionRange.Parse(">1.2.3-alpha.3");
var version = SemanticVersion.Parse("3.4.5-alpha.7");
Console.WriteLine($"Result: {range.IsSatisfiedBy(version)}");
// Result: False
Console.WriteLine($"Result (inc.pr): {range.IsSatisfiedBy(version, true)}");
// Result (inc.pr): True
SemanticVersionBuilder
can be used to manipulate semantic versions step by step.
The incrementing behaviour is consistent with node-semver
.
var builder = new SemanticVersionBuilder(1, 2, 3);
builder
.WithPatch(7)
.AppendPreRelease("alpha")
.AppendPreRelease(0);
var a = builder.ToVersion(); // 1.2.7-alpha.0
builder.IncrementPatch();
var b = builder.ToVersion(); // 1.2.7
builder.Increment(IncrementType.PreMinor, "beta");
var c = builder.ToVersion(); // 1.3.0-beta.0
You can change the order of the semantic version components, escape sequences of characters, specify pre-release and build metadata by indices, and etc. The formatting here is pretty powerful and well optimized, but, of course, not universal - there's only so much that you can ask of a simple formatting method...
var a = SemanticVersion.Parse("1.2.3-beta.5+DEV.09");
Console.WriteLine(a.ToString("'version:' m.p.M-r1.r0 (dd)"));
// version: 3.2.1-5.beta (DEV.09)
M
- major version component.m
- minor version component.p
- patch version component.rr
- all pre-release identifiers.dd
- all build metadata identifiers.
The standard, SemVer 2.0.0, format is M.m.p-rr+dd
.
mm
- optional minor version component. Omitted, if both minor and patch components are zero.pp
- optional patch version component. Omitted if it's zero.
When an optional component is omitted, a separator preceding it is omitted as well. For example: Formatting 1.2.0
using M.mm.pp
yields 1.2.0
, and formatting 2.0.0
- yields 2
.
r0
,r1
,r2
, … - the first/second/third/… pre-release identifier.r
- the pre-release identifier that comes after the last one specified. For example:r0.r.r.r5.r
would be equivalent tor0.r1.r2.r5.r6
.rr
- all of the pre-release identifiers that come after the last specified pre-release identifier, separated by.
dots. For example:r2.rr
would output all pre-releases except for the first two (r0
,r1
).
When a specified pre-release identifier doesn't exist, it is omitted, along with a separator preceding it. For example: Formatting 1.0.0-alpha.5
using M-r0.r1.rr
yields 1-alpha.5
, and formatting 1.0.0
- yields 1
.
d0
,d1
,d2
, … - the first/second/third/… build metadata identifier.d
- the build metadata identifier that comes after the last one specified. For example:d.d2.d.d5
would be equivalent tod0.d2.d3.d5
.dd
- all of the build metadata identifiers that come after the last specified build metadata identifier, separated by.
dots. For example:d1.dd
would output all build metadata except for the first one (d0
).
When a specified build metadata identifier doesn't exist, it is omitted, along with a separator preceding it. Formatting 1.0.0+dev
using M+d0.d1.dd
yields 1+dev
, and formatting 1.0.0
- yields 1
.
\Majo\r: M
- backslash-escaped characters, which are output as is (without the backslash, of course). The backslash itself can be escaped as well (\\M
).'map'
,"Arr!"
- quote-escaped sequence of characters, the contents of which are output as is (map
,Arr!
). Note that inside quote-escaped sequences,\
(backslash) doesn't escape and is instead output as is.
.
,-
,+
,_
,