Skip to content

Commit f3a333e

Browse files
add SemVersion.ParsedFrom
1 parent 86240c9 commit f3a333e

File tree

5 files changed

+197
-3
lines changed

5 files changed

+197
-3
lines changed

Semver.Test/SemVersionConstructorTests.cs

Lines changed: 147 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public void ConstructWithMajorMinorPatchTest(int major, int minor, int patch)
8888
}
8989
#endregion
9090

91-
#region SemVersion(int major, int minor = 0, int patch = 0, string prerelease, string build)
91+
#region SemVersion(int major, int minor = 0, int patch = 0, string prerelease = "", string build = "")
9292
/// <summary>
9393
/// Verifies the default values of the arguments to the primary constructor.
9494
/// </summary>
@@ -232,7 +232,8 @@ public void ConstructWithIdentifiersTest()
232232
[InlineData(0, -1, -1, InvalidMinorVersionMessage, "minor")]
233233
public void ConstructWithIdentifiersInvalidTest(int major, int minor, int patch, string expectedMessage, string expectedParamName)
234234
{
235-
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new SemVersion(major, minor, patch, Enumerable.Empty<PrereleaseIdentifier>()));
235+
var ex = Assert.Throws<ArgumentOutOfRangeException>(()
236+
=> new SemVersion(major, minor, patch, Enumerable.Empty<PrereleaseIdentifier>()));
236237

237238
Assert.StartsWith(expectedMessage, ex.Message);
238239
Assert.Equal(expectedParamName, ex.ParamName);
@@ -294,5 +295,149 @@ public void ConstructWithStringIdentifiersInvalidTest(int major, int minor, int
294295

295296
// TODO add tests for validation of prerelease and metadata
296297
#endregion
298+
299+
#region SemVersion.ParsedFrom(int major, int minor = 0, int patch = 0, string prerelease, string build)
300+
[Fact]
301+
public void ParsedFromDefaultValuesTest()
302+
{
303+
var v = SemVersion.ParsedFrom(1);
304+
305+
Assert.Equal(1, v.Major);
306+
Assert.Equal(0, v.Minor);
307+
Assert.Equal(0, v.Patch);
308+
Assert.Equal("", v.Prerelease);
309+
Assert.Empty(v.PrereleaseIdentifiers);
310+
Assert.Equal("", v.Metadata);
311+
Assert.Empty(v.MetadataIdentifiers);
312+
}
313+
314+
[Fact]
315+
public void ParsedFromTest()
316+
{
317+
var v = SemVersion.ParsedFrom(2, 3, 4, "pre.42", "build.42");
318+
319+
Assert.Equal(2, v.Major);
320+
Assert.Equal(3, v.Minor);
321+
Assert.Equal(4, v.Patch);
322+
Assert.Equal("pre.42", v.Prerelease);
323+
var expectedPrereleaseIdentifiers = new[] { new PrereleaseIdentifier("pre"), new PrereleaseIdentifier(42) };
324+
Assert.Equal(expectedPrereleaseIdentifiers, v.PrereleaseIdentifiers);
325+
Assert.Equal("build.42", v.Metadata);
326+
var expectedMetadata = new[] { new MetadataIdentifier("build"), new MetadataIdentifier("42") };
327+
Assert.Equal(expectedMetadata, v.MetadataIdentifiers);
328+
}
329+
330+
[Theory]
331+
[InlineData(-1, 0, 0, InvalidMajorVersionMessage, "major")]
332+
[InlineData(0, -1, 0, InvalidMinorVersionMessage, "minor")]
333+
[InlineData(0, 0, -1, InvalidPatchVersionMessage, "patch")]
334+
[InlineData(-1, -1, -1, InvalidMajorVersionMessage, "major")]
335+
[InlineData(0, -1, -1, InvalidMinorVersionMessage, "minor")]
336+
public void ParsedFromInvalidTest(
337+
int major,
338+
int minor,
339+
int patch,
340+
string expectedMessage,
341+
string expectedParamName)
342+
{
343+
var ex = Assert.Throws<ArgumentOutOfRangeException>(()
344+
=> SemVersion.ParsedFrom(major, minor, patch));
345+
346+
Assert.StartsWith(expectedMessage, ex.Message);
347+
Assert.Equal(expectedParamName, ex.ParamName);
348+
}
349+
350+
[Fact]
351+
public void ParsedFromPrereleaseEmptyString()
352+
{
353+
var v = SemVersion.ParsedFrom(1, 2, 3, "");
354+
355+
Assert.Equal(new SemVersion(1, 2, 3), v);
356+
}
357+
358+
[Fact]
359+
public void ParsedFromPrereleaseEmptyIdentifier()
360+
{
361+
var ex = Assert.Throws<ArgumentException>(() => SemVersion.ParsedFrom(1, 2, 3, "bar."));
362+
Assert.StartsWith("Prerelease identifier cannot be empty.", ex.Message);
363+
Assert.Equal("prerelease", ex.ParamName);
364+
}
365+
366+
[Fact]
367+
public void ParsedFromPrereleaseLeadingZeros()
368+
{
369+
var ex = Assert.Throws<ArgumentException>(()
370+
=> SemVersion.ParsedFrom(1, 2, 3, "bar.0123"));
371+
Assert.StartsWith("Leading zeros are not allowed on numeric prerelease identifiers '0123'.", ex.Message);
372+
Assert.Equal("prerelease", ex.ParamName);
373+
}
374+
375+
[Fact]
376+
public void ParsedFromPrereleaseLeadingZerosAllowed()
377+
{
378+
var v = SemVersion.ParsedFrom(1, 2, 3, "bar.0123", allowLeadingZeros: true);
379+
380+
Assert.Equal(new SemVersion(1, 2, 3, "bar.123"), v);
381+
}
382+
383+
[Fact]
384+
public void ParsedFromPrereleaseTooLarge()
385+
{
386+
var ex = Assert.Throws<OverflowException>(()
387+
=> SemVersion.ParsedFrom(1, 2, 3, "bar.99999999999999999"));
388+
Assert.StartsWith("Prerelease identifier '99999999999999999' was too large for Int32.", ex.Message);
389+
}
390+
391+
[Fact]
392+
public void ParsedFromPrereleaseInvalidCharacter()
393+
{
394+
var ex = Assert.Throws<ArgumentException>(()
395+
=> SemVersion.ParsedFrom(1, 2, 3, "bar.abc@123"));
396+
Assert.StartsWith("A prerelease identifier can contain only ASCII alphanumeric characters and hyphens 'abc@123'.", ex.Message);
397+
Assert.Equal("prerelease", ex.ParamName);
398+
}
399+
400+
[Fact]
401+
public void ParsedFromMetadataEmptyString()
402+
{
403+
var v = SemVersion.ParsedFrom(1, metadata: "");
404+
405+
Assert.Equal(new SemVersion(1, 0, 0), v);
406+
}
407+
408+
[Fact]
409+
public void ParsedFromMetadataEmptyIdentifier()
410+
{
411+
var ex = Assert.Throws<ArgumentException>(()
412+
=> SemVersion.ParsedFrom(1, metadata: "bar."));
413+
Assert.StartsWith("Metadata identifier cannot be empty.", ex.Message);
414+
Assert.Equal("metadata", ex.ParamName);
415+
}
416+
417+
[Fact]
418+
public void ParsedFromMetadataLeadingZeros()
419+
{
420+
var v = SemVersion.ParsedFrom(1, metadata: "bar.0123");
421+
422+
Assert.Equal(new SemVersion(1, 0, 0, "", "bar.0123"), v);
423+
}
424+
425+
[Fact]
426+
public void ParsedFromMetadataTooLarge()
427+
{
428+
var v = SemVersion.ParsedFrom(1, metadata: "bar.99999999999999999");
429+
430+
Assert.Equal(new SemVersion(1, 0, 0, "", "bar.99999999999999999"), v);
431+
}
432+
433+
[Fact]
434+
public void ParsedFromMetadataInvalidCharacter()
435+
{
436+
var ex = Assert.Throws<ArgumentException>(()
437+
=> SemVersion.ParsedFrom(1, metadata: "bar.abc@123"));
438+
Assert.StartsWith("A metadata identifier can contain only ASCII alphanumeric characters and hyphens 'abc@123'.", ex.Message);
439+
Assert.Equal("metadata", ex.ParamName);
440+
}
441+
#endregion
297442
}
298443
}

Semver/PublicAPI/net452/PublicAPI.Unshipped.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,5 @@ static Semver.PrereleaseIdentifier.operator !=(Semver.PrereleaseIdentifier left,
7070
static Semver.PrereleaseIdentifier.operator ==(Semver.PrereleaseIdentifier left, Semver.PrereleaseIdentifier right) -> bool
7171
static Semver.SemVersion.FromVersion(System.Version version) -> Semver.SemVersion
7272
static Semver.SemVersion.Parse(string version, Semver.SemVersionStyles style, int maxLength = 1024) -> Semver.SemVersion
73+
static Semver.SemVersion.ParsedFrom(int major, int minor = 0, int patch = 0, string prerelease = "", string metadata = "", bool allowLeadingZeros = false) -> Semver.SemVersion
7374
static Semver.SemVersion.TryParse(string version, Semver.SemVersionStyles style, out Semver.SemVersion semver, int maxLength = 1024) -> bool

Semver/PublicAPI/netstandard1.1/PublicAPI.Unshipped.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,5 @@ static Semver.PrereleaseIdentifier.operator !=(Semver.PrereleaseIdentifier left,
7070
static Semver.PrereleaseIdentifier.operator ==(Semver.PrereleaseIdentifier left, Semver.PrereleaseIdentifier right) -> bool
7171
static Semver.SemVersion.FromVersion(System.Version version) -> Semver.SemVersion
7272
static Semver.SemVersion.Parse(string version, Semver.SemVersionStyles style, int maxLength = 1024) -> Semver.SemVersion
73+
static Semver.SemVersion.ParsedFrom(int major, int minor = 0, int patch = 0, string prerelease = "", string metadata = "", bool allowLeadingZeros = false) -> Semver.SemVersion
7374
static Semver.SemVersion.TryParse(string version, Semver.SemVersionStyles style, out Semver.SemVersion semver, int maxLength = 1024) -> bool

Semver/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,5 @@ static Semver.PrereleaseIdentifier.operator !=(Semver.PrereleaseIdentifier left,
7070
static Semver.PrereleaseIdentifier.operator ==(Semver.PrereleaseIdentifier left, Semver.PrereleaseIdentifier right) -> bool
7171
static Semver.SemVersion.FromVersion(System.Version version) -> Semver.SemVersion
7272
static Semver.SemVersion.Parse(string version, Semver.SemVersionStyles style, int maxLength = 1024) -> Semver.SemVersion
73+
static Semver.SemVersion.ParsedFrom(int major, int minor = 0, int patch = 0, string prerelease = "", string metadata = "", bool allowLeadingZeros = false) -> Semver.SemVersion
7374
static Semver.SemVersion.TryParse(string version, Semver.SemVersionStyles style, out Semver.SemVersion semver, int maxLength = 1024) -> bool

Semver/SemVersion.cs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,52 @@ public SemVersion(int major, int minor = 0, int patch = 0,
254254
}
255255
}
256256

257+
/// <summary>
258+
/// Create a new instance of the <see cref="SemVersion" /> class. Parses prerelease
259+
/// and metadata identifiers from dot separated strings. If parsing is not needed, use a
260+
/// constructor instead.
261+
/// </summary>
262+
/// <param name="major">The major version number.</param>
263+
/// <param name="minor">The minor version number.</param>
264+
/// <param name="patch">The patch version number.</param>
265+
/// <param name="prerelease">The prerelease portion (e.g. "alpha.5").</param>
266+
/// <param name="metadata">The build metadata (e.g. "nightly.232").</param>
267+
/// <param name="allowLeadingZeros">Allow leading zeros in numeric prerelease identifiers. Leading
268+
/// zeros will be removed.</param>
269+
/// <exception cref="ArgumentOutOfRangeException">A <paramref name="major"/>,
270+
/// <paramref name="minor"/>, or <paramref name="patch"/> version number is negative.</exception>
271+
/// <exception cref="ArgumentException">A prerelease identifier is empty or contains invalid
272+
/// characters (i.e. characters that are not ASCII alphanumerics or hyphens) or has leading
273+
/// zeros for a numeric identifier when <paramref name="allowLeadingZeros"/> is
274+
/// <see langword="false"/>. Or, a metadata identifier is empty or contains invalid
275+
/// characters (i.e. characters that are not ASCII alphanumerics or hyphens).</exception>
276+
/// <exception cref="OverflowException">A numeric prerelease identifier value is too large
277+
/// for <see cref="int"/>.</exception>
278+
public static SemVersion ParsedFrom(int major, int minor = 0, int patch = 0,
279+
string prerelease = "", string metadata = "", bool allowLeadingZeros = false)
280+
{
281+
if (major < 0) throw new ArgumentOutOfRangeException(nameof(major), InvalidMajorVersionMessage);
282+
if (minor < 0) throw new ArgumentOutOfRangeException(nameof(minor), InvalidMinorVersionMessage);
283+
if (patch < 0) throw new ArgumentOutOfRangeException(nameof(patch), InvalidPatchVersionMessage);
284+
285+
if (prerelease is null) throw new ArgumentNullException(nameof(prerelease));
286+
var prereleaseIdentifiers = prerelease.Length == 0
287+
? ReadOnlyList<PrereleaseIdentifier>.Empty
288+
: prerelease.SplitAndMapToReadOnlyList('.',
289+
i => new PrereleaseIdentifier(i, allowLeadingZeros, nameof(prerelease)));
290+
if (allowLeadingZeros)
291+
// Leading zeros may have been removed, need to reconstruct the prerelease string
292+
prerelease = string.Join(".", prereleaseIdentifiers);
293+
294+
if (metadata is null) throw new ArgumentNullException(nameof(metadata));
295+
var metadataIdentifiers = metadata.Length == 0
296+
? ReadOnlyList<MetadataIdentifier>.Empty
297+
: metadata.SplitAndMapToReadOnlyList('.', i => new MetadataIdentifier(i, nameof(metadata)));
298+
299+
return new SemVersion(major, minor, patch,
300+
prerelease, prereleaseIdentifiers, metadata, metadataIdentifiers);
301+
}
302+
257303
/// <summary>
258304
/// Constructs a new instance of the <see cref="SemVersion"/> class from
259305
/// a <see cref="Version"/>.
@@ -703,7 +749,7 @@ public SemVersion With(
703749
/// <exception cref="ArgumentException">A prerelease identifier is empty or contains invalid
704750
/// characters (i.e. characters that are not ASCII alphanumerics or hyphens) or has leading
705751
/// zeros for a numeric identifier when <paramref name="allowLeadingZeros"/> is
706-
/// <see langword="false"/>. Or, metadata identifier is empty or contains invalid
752+
/// <see langword="false"/>. Or, a metadata identifier is empty or contains invalid
707753
/// characters (i.e. characters that are not ASCII alphanumerics or hyphens).</exception>
708754
/// <exception cref="OverflowException">A numeric prerelease identifier value is too large
709755
/// for <see cref="int"/>.</exception>

0 commit comments

Comments
 (0)