Skip to content

Commit c6fd12b

Browse files
rjmholtRobert Holt
authored and
Robert Holt
committed
Add PowerShellVersion class
1 parent 5d48b1d commit c6fd12b

File tree

5 files changed

+246
-12
lines changed

5 files changed

+246
-12
lines changed

CrossCompatibility/CrossCompatibility/CrossCompatibility.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
</ItemGroup>
1919

2020
<ItemGroup>
21+
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
2122
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
2223
</ItemGroup>
2324

CrossCompatibility/CrossCompatibility/Utility/JsonProfileSerializer.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ private static IList<JsonConverter> GetFormatConverters()
9090
return new List<JsonConverter>()
9191
{
9292
new VersionConverter(),
93+
new PowerShellVersionJsonConverter(),
9394
new StringEnumConverter()
9495
};
9596
}

CrossCompatibility/CrossCompatibility/Utility/PowerShellVersion.cs

Lines changed: 118 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,109 @@
11
using System;
22
using System.Text;
3+
using Newtonsoft.Json;
34

45
namespace Microsoft.PowerShell.CrossCompatibility.Utility
56
{
67
public class PowerShellVersion
78
{
8-
public static PowerShellVersion FromSemVer(dynamic semver)
9+
public static PowerShellVersion Create(dynamic versionInput)
910
{
10-
if (semver.GetType().FullName != "System.Management.Automation.SemanticVersion")
11+
switch (versionInput)
1112
{
12-
throw new ArgumentException($"{nameof(semver)} must be of type 'System.Management.Automation.SemanticVersion'");
13+
case Version systemVersion:
14+
return new PowerShellVersion(systemVersion);
15+
16+
case string versionString:
17+
return Parse(versionString);
1318
}
1419

15-
return new PowerShellVersion(semver.Major, semver.Minor, semver.Patch, semver.PreReleaseLabel);
20+
return new PowerShellVersion(versionInput.Major, versionInput.Minor, versionInput.Patch, versionInput.PreReleaseLabel);
1621
}
1722

1823
public static PowerShellVersion Parse(string versionStr)
1924
{
25+
if (versionStr == null)
26+
{
27+
throw new ArgumentNullException(nameof(versionStr));
28+
}
29+
30+
int[] versionParts = new int[3];
31+
32+
int sectionStartOffset = 0;
33+
int dotCount = 0;
34+
int i;
35+
for (i = 0; i < versionStr.Length; i++)
36+
{
37+
switch (versionStr[i])
38+
{
39+
case '.':
40+
// Parse the part of the string before this dot into an integer
41+
versionParts[dotCount] = int.Parse(versionStr.Substring(sectionStartOffset, i - sectionStartOffset));
42+
sectionStartOffset = i + 1;
43+
dotCount++;
44+
45+
// If we have 3 dots, we have seen all we can, so collect up and parse
46+
if (dotCount == 3)
47+
{
48+
int revision = int.Parse(versionStr.Substring(i + 1));
49+
return new PowerShellVersion(versionParts[0], versionParts[1], versionParts[2], revision);
50+
}
51+
continue;
52+
53+
case '-':
54+
if (dotCount > 2)
55+
{
56+
throw new ArgumentException($"Semantic version string '{versionStr}' contains too many dot separators to be a v2 semantic version");
57+
}
58+
59+
versionParts[dotCount] = int.Parse(versionStr.Substring(sectionStartOffset, i - sectionStartOffset));
60+
string label = versionStr.Substring(i + 1);
61+
return new PowerShellVersion(versionParts[0], versionParts[1], versionParts[2], label);
62+
}
63+
}
64+
65+
if (dotCount == 0)
66+
{
67+
throw new ArgumentException($"Version string '{versionStr}' must contain at least one dot separator");
68+
}
69+
70+
versionParts[dotCount] = int.Parse(versionStr.Substring(sectionStartOffset, i - sectionStartOffset));
71+
72+
return new PowerShellVersion(versionParts[0], versionParts[1], versionParts[2], preReleaseLabel: null);
73+
}
74+
75+
public static bool TryParse(string versionStr, out PowerShellVersion version)
76+
{
77+
try
78+
{
79+
version = Parse(versionStr);
80+
return true;
81+
}
82+
catch
83+
{
84+
version = null;
85+
return false;
86+
}
87+
}
88+
89+
public static explicit operator Version(PowerShellVersion psVersion)
90+
{
91+
if (psVersion.PreReleaseLabel != null)
92+
{
93+
throw new InvalidCastException($"Cannot convert version '{psVersion}' to System.Version, since there is a pre-release label");
94+
}
95+
96+
if (psVersion.Revision != null)
97+
{
98+
return new Version(psVersion.Major, psVersion.Minor, psVersion.Patch, psVersion.Revision.Value);
99+
}
100+
101+
return new Version(psVersion.Major, psVersion.Minor, psVersion.Patch);
102+
}
103+
104+
public static explicit operator PowerShellVersion(string versionString)
105+
{
106+
return PowerShellVersion.Parse(versionString);
20107
}
21108

22109
public PowerShellVersion(Version version)
@@ -74,4 +161,31 @@ public override string ToString()
74161
return sb.ToString();
75162
}
76163
}
164+
165+
public class PowerShellVersionJsonConverter : JsonConverter
166+
{
167+
public override bool CanConvert(Type objectType)
168+
{
169+
return objectType == typeof(PowerShellVersion)
170+
|| objectType == typeof(Version)
171+
|| objectType.FullName == "System.Management.Automation.SemanticVersion";
172+
}
173+
174+
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
175+
{
176+
string s = (string)reader.Value;
177+
return PowerShellVersion.Parse(s);
178+
}
179+
180+
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
181+
{
182+
if (value.GetType() != typeof(string))
183+
{
184+
writer.WriteValue(value.ToString());
185+
return;
186+
}
187+
188+
writer.WriteValue(PowerShellVersion.Create(value).ToString());
189+
}
190+
}
77191
}

CrossCompatibility/Tests/UtilityApi.Tests.ps1

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,129 @@ Describe "Type name transformation" {
2727
It "Null type gives null type name" {
2828
[Microsoft.PowerShell.CrossCompatibility.Utility.TypeDataConversion]::GetFullTypeName($null) | Should -Be $null
2929
}
30+
}
31+
32+
Describe "PowerShell version object" {
33+
Context "Version parsing" {
34+
BeforeAll {
35+
$genericVerCases = @(
36+
@{ VerStr = '6'; Major = 6; Minor = 0; Patch = 0 }
37+
@{ VerStr = '6.1'; Major = 6; Minor = 1; Patch = 0 }
38+
@{ VerStr = '5.2.7'; Major = 5; Minor = 2; Patch = 7 }
39+
@{ VerStr = '512.2124.71'; Major = 512; Minor = 2124; Patch = 71 }
40+
)
41+
42+
$semVerCases = @(
43+
@{ VerStr = '6.1.0-rc.1'; Major = 6; Minor = 1; Patch = 0; Label = 'rc.1' }
44+
@{ VerStr = '6-preview.2'; Major = 6; Minor = 0; Patch = 0; Label = 'preview.2' }
45+
@{ VerStr = '6.2-preview.2'; Major = 6; Minor = 2; Patch = 0; Label = 'preview.2' }
46+
)
47+
48+
$systemVerCases = @(
49+
@{ VerStr = '5.2.1.12312'; Major = 5; Minor = 2; Patch = 1; Revision = 12312 }
50+
)
51+
52+
$versionFailCases = @(
53+
@{ VerStr = 'banana' }
54+
@{ VerStr = '' }
55+
@{ VerStr = '1.' }
56+
@{ VerStr = '.6' }
57+
@{ VerStr = '5.1.' }
58+
@{ VerStr = '5.1.2.' }
59+
@{ VerStr = '4.1.5.7.' }
60+
@{ VerStr = '4.1.5.7.4' }
61+
@{ VerStr = '4.1.5.7-rc.2' }
62+
@{ VerStr = '4.1.5.-rc.2' }
63+
)
64+
}
65+
66+
It "Parses version string '<VerStr>' as <Major>.<Minor>.<Patch>" -TestCases $semVerCases {
67+
param([string]$VerStr, [int]$Major, [int]$Minor, [int]$Patch)
68+
69+
$v = [Microsoft.PowerShell.CrossCompatibility.Utility.PowerShellVersion]::Parse($VerStr)
70+
71+
$v.Major | Should -Be $Major
72+
$v.Minor | Should -Be $Minor
73+
$v.Patch | Should -Be $Patch
74+
}
75+
76+
It "Parses version string '<VerStr>' as <Major>.<Minor>.<Patch>-<Label>" -TestCases $semVerCases {
77+
param([string]$VerStr, [int]$Major, [int]$Minor, [int]$Patch, [string]$Label)
78+
79+
$v = [Microsoft.PowerShell.CrossCompatibility.Utility.PowerShellVersion]::Parse($VerStr)
80+
81+
$v.Major | Should -Be $Major
82+
$v.Minor | Should -Be $Minor
83+
$v.Patch | Should -Be $Patch
84+
$v.PreReleaseLabel | Should -BeExactly $Label
85+
}
86+
87+
It "Parses version string '<VerStr>' as <Major>.<Minor>.<Patch>.<Revision>" -TestCases $systemVerCases {
88+
param([string]$VerStr, [int]$Major, [int]$Minor, [int]$Patch, [int]$Revision)
89+
90+
$v = [Microsoft.PowerShell.CrossCompatibility.Utility.PowerShellVersion]::Parse($VerStr)
91+
92+
$v.Major | Should -Be $Major
93+
$v.Minor | Should -Be $Minor
94+
$v.Patch | Should -Be $Patch
95+
$v.Revision | Should -Be $Revision
96+
}
97+
98+
It "Does not parse '<VerStr>' as a version" -TestCases $versionFailCases {
99+
param([string]$VerStr)
100+
101+
{ [Microsoft.PowerShell.CrossCompatibility.Utility.PowerShellVersion]::Parse($VerStr) } | Should -Throw
102+
}
103+
}
104+
105+
Context "Version creation from other versions" {
106+
BeforeAll {
107+
$versionCreationTests = @(
108+
@{ Version = '6.1'; Major = 6; Minor = 1; Patch = 0 }
109+
@{ Version = '6.1.4'; Major = 6; Minor = 1; Patch = 4; }
110+
@{ Version = '5.1.8-preview.2'; Major = 5; Minor = 1; Patch = 8; Label = 'preview.2' }
111+
@{ Version = [version]'4.2'; Major = 4; Minor = 2; Patch = -1; Revision = -1 }
112+
@{ Version = [version]'4.2.1'; Major = 4; Minor = 2; Patch = 1; Revision = -1 }
113+
@{ Version = [version]'4.2.1.7'; Major = 4; Minor = 2; Patch = 1; Revision = 7 }
114+
)
115+
116+
if ($PSVersionTable.PSVersion.Major -ge 6)
117+
{
118+
$versionCreationTests += @(
119+
@{ Version = [semver]'6.1.2'; Major = 6; Minor = 1; Patch = 2; Label = $null }
120+
@{ Version = [semver]'6.1.2-rc.1'; Major = 6; Minor = 1; Patch = 2; Label = 'rc.1' }
121+
@{ Version = [semver]'6.1-rc.1'; Major = 6; Minor = 1; Patch = 0; Label = 'rc.1' }
122+
@{ Version = [semver]'6-rc.1'; Major = 6; Minor = 0; Patch = 0; Label = 'rc.1' }
123+
)
124+
}
125+
126+
$versionCreationFailTests = @(
127+
@{ Version = $null }
128+
@{ Version = New-Object 'object' }
129+
@{ Version = 'Hello' }
130+
)
131+
}
132+
133+
It "Creates a PowerShellVersion from '<Version>'" -TestCases $versionCreationTests {
134+
param($Version, [int]$Major, [int]$Minor, [int]$Patch, [int]$Revision, $Label)
135+
136+
$v = [Microsoft.PowerShell.CrossCompatibility.Utility.PowerShellVersion]::Create($Version)
137+
138+
$v.Major | Should -Be $Major
139+
$v.Minor | Should -Be $Minor
140+
$v.Patch | Should -Be $Patch
141+
$v.PreReleaseLabel | Should -Be $Label
142+
143+
if ($Revision)
144+
{
145+
$v.Revision | Should -Be $Revision
146+
}
147+
}
148+
149+
It "Does not create a PowerShellVersion from <Version>" -TestCases $versionCreationFailTests {
150+
param($Version)
151+
152+
{ [Microsoft.PowerShell.CrossCompatibility.Utility.PowerShellVersion]::Create($Version) } | Should -Throw
153+
}
154+
}
30155
}

Tests/Rules/UseCompatibleCmdlets2.Tests.ps1

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,7 @@ Describe 'UseCompatibleCmdlets2' {
7474

7575
for ($i = 0; $i -lt $diagnostics.Count; $i++)
7676
{
77-
try
78-
{
79-
$psVersion = [version]$diagnostics[$i].TargetPlatform.PowerShell.Version
80-
}
81-
catch
82-
{
83-
$psVersion = [semver]$diagnostics[$i].TargetPlatform.PowerShell.Version
84-
}
77+
$psVersion = [Microsoft.PowerShell.CrossCompatibility.Utility.PowerShellVersion]$diagnostics[$i].TargetPlatform.PowerShell.Version
8578

8679
$diagnostics[$i].Command | Should -BeExactly $Commands[$i]
8780
$diagnostics[$i].TargetPlatform.OperatingSystem.Family | Should -Be $OS

0 commit comments

Comments
 (0)