@@ -284,7 +284,9 @@ function Get-ModuleFastPlan {
284284 # HACK: I should be using the Id provided by the server, for now I'm just guessing because
285285 # I need to add it to the ComparableModuleSpec class
286286 Write-Debug " $currentModuleSpec `: Processing dependencies"
287- [List [ModuleFastSpec ]]$dependencies = $dependencyInfo | Parse- NugetDependency
287+ [List [ModuleFastSpec ]]$dependencies = $dependencyInfo | ForEach-Object {
288+ [ModuleFastSpec ]::new($PSItem.id , [NuGetRange ]$PSItem.range )
289+ }
288290 Write-Debug " $currentModuleSpec has $ ( $dependencies.count ) dependencies"
289291
290292 # TODO: Where loop filter maybe
@@ -702,8 +704,8 @@ class ModuleFastSpec : IComparable {
702704 static [SemanticVersion ]$MaxVersion = ' {0}.{0}.{0}' -f [int32 ]::MaxValue
703705 # Special string we use to translate between Version and SemanticVersion since SemanticVersion doesnt support Semver 2.0 properly and doesnt allow + only
704706 # Someone actually using this string may cause a conflict, it's not foolproof but it's better than nothing
705- static [string ]$VersionBuildIdentifier = ' MFbuild '
706- hidden static [string ]$buildVersionRegex = ' ^\d+\. \d+\. \d+\. \d+$'
707+ hidden static [string ]$SYSTEM_VERSION_LABEL = ' SYSTEMVERSION '
708+ hidden static [string ]$SYSTEM_VERSION_REGEX = ' ^(?<major> \d+)\.(?<minor> \d+)\.(?<build> \d+)\.(?<revision> \d+) $'
707709
708710 # These properties are effectively read only thanks to some wizardry
709711 hidden [uri ]$_DownloadLink
@@ -779,23 +781,23 @@ class ModuleFastSpec : IComparable {
779781 $this.Initialize ($Name , $requiredVersion , $requiredVersion , $null , $null )
780782 $this._DownloadLink = [uri ]$DownloadLink
781783 }
782-
783784 ModuleFastSpec([string ]$Name , [string ]$Min , [string ]$Max ) {
784785 [SemanticVersion ]$minVer = $min ? [ModuleFastSpec ]::ParseVersionString($min ) : $null
785786 [SemanticVersion ]$maxVer = $max ? [ModuleFastSpec ]::ParseVersionString($max ) : $null
786787 $this.Initialize ($Name , $minVer , $maxVer , $null , $null )
787788 }
788789
789-
790790 # These can be used for performance to avoid parsing to string and back. Probably makes little difference
791791 ModuleFastSpec([string ]$Name , [SemanticVersion ]$Required ) {
792792 $this.Initialize ($Name , $Required , $Required , $null , $null )
793793 }
794-
794+ ModuleFastSpec([string ]$Name , [NugetRange ]$Range ) {
795+ $Range.Min
796+ $this.Initialize ($Name , $range.Min , $range.Max , $null , $null )
797+ }
795798
796799
797800 # TODO: Version versions maybe? Probably should just use the parser and let those go to string
798-
799801 ModuleFastSpec([ModuleSpecification ]$ModuleSpec ) {
800802 $this.Initialize ($null , $null , $null , $null , $ModuleSpec )
801803 }
@@ -833,10 +835,10 @@ class ModuleFastSpec : IComparable {
833835
834836 # Parses either a assembly version or semver to a semver string
835837 static [SemanticVersion ] ParseVersionString([string ]$Version ) {
836- if ($null -eq $Version ) { return $null }
837- $result = if ( $Version -match [ModuleFastSpec ]::buildVersionRegex) {
838- [ ModuleFastSpec ]::ParseVersion($Version )
839- } else { $Version }
838+ if (-not $Version ) { throw [ NotSupportedException ] ' Null or empty strings are not supported ' }
839+ $result = $Version -match [ModuleFastSpec ]::SYSTEM_VERSION_REGEX `
840+ ? [ SemanticVersion ]::ParseVersion([ Version ] $Version )
841+ : [ SemanticVersion ] $Version
840842 return $result
841843 }
842844
@@ -845,47 +847,61 @@ class ModuleFastSpec : IComparable {
845847 # Needed because SemVer can't parse builds correctly
846848 # https://github.com/PowerShell/PowerShell/issues/14605
847849 static [SemanticVersion ]ParseVersion([Version ]$Version ) {
848- if ($null -eq $Version ) { return $null }
850+ if (-not $Version ) { throw [ NotSupportedException ] ' Null or empty strings are not supported ' }
849851
852+ [list [string ]]$buildLabels = @ ()
853+ $buildVersion = $null
854+ if ($Version.Build -eq -1 ) { $buildLabels.Add (' NOBUILD' ); $buildVersion = 0 }
855+ if ($Version.Revision -ne -1 ) {
856+ $buildLabels.Add (' HASREVISION' )
857+ }
858+ if ($buildLabels.count -eq 0 ) {
859+ # This version maps directly to semantic version and we can return early
860+ return [SemanticVersion ]::new($Version.Major , $Version.Minor , $Version.Build )
861+ }
862+
863+ # Otherwise we need to explicitly note this came from a system version for when we parse it back
864+ $buildLabels.Add ([ModuleFastSpec ]::SYSTEM_VERSION_LABEL)
865+ $preReleaseLabel = $null
850866 if ($Version.Revision -ge 0 ) {
851- [SemanticVersion ]::new(
852- $Version.Major ,
853- $Version.Minor -eq -1 ? 0 : $Version.Minor ,
854- $Version.Build -eq -1 ? 0 : $Version.Build ,
855- [ModuleFastSpec ]::VersionBuildIdentifier,
856- $Version.Revision
857- )
858- } else {
859- [SemanticVersion ]::new(
860- $Version.Major ,
861- $Version.Minor -eq -1 ? 0 : $Version.Minor ,
862- $Version.Build -eq -1 ? 0 : $Version.Build
863- )
867+ # We do this so that the sort order is correct in semver (prereleases sort before major versions and is lexically sorted)
868+ # Revision can't be 0 while build is -1, so we can skip any evaluation logic there.
869+ $preReleaseLabel = $Version.Revision.ToString ().PadLeft(10 , ' 0' )
870+ $buildVersion = $Version.Build + 1
864871 }
865- $versionWith4SectionsRegex = ' ^\d+\.\d+\.\d+\.\d+$'
866- $parsedVersion = $Version -match $versionWith4SectionsRegex `
867- ? ' {0}.{1}.{2}-{4}+{3}' -f $Version.Major , $Version.Minor , $Version.Build , $Version.Revision , [ModuleFastSpec ]::VersionBuildIdentifier
868- : $Version
869- return [SemanticVersion ]$parsedVersion
872+ $buildLabels.Reverse ()
873+ [string ]$buildLabel = $buildLabels -join ' .'
874+ # Nulls will return as 0, which we want. Major and Minor cannot be -1
875+ return [SemanticVersion ]::new($Version.Major , $Version.Minor , $buildVersion , $preReleaseLabel , $buildLabel )
870876 }
871877
872- # A way to go back from SemanticVersion
878+ # A way to go back from SemanticVersion, the anticedent to ParseVersion
873879 static [Version ]ParseSemanticVersion([SemanticVersion ]$Version ) {
874- if ($null -eq $Version ) { return $null }
875- return [Version ](' {0}.{1}.{2}{3}' -f
876- $Version.Major ,
877- ($Version.Minor ?? 0 ),
878- ($Version.Patch ?? 0 ),
879- (($Version.PreReleaseLabel -eq [ModuleFastSpec ]::VersionBuildIdentifier -and $Version.BuildLabel -gt 0 ) ? " .$ ( $Version.BuildLabel ) " : $null )
880- )
880+ if ($null -eq $Version ) { throw [NotSupportedException ]' Null or empty strings are not supported' }
881+
882+ [string []]$buildFlags = $Version.BuildLabel -split ' \.'
883+ if ($BuildFlags -notcontains [ModuleFastSpec ]::SYSTEM_VERSION_LABEL) {
884+ # This is a semantic-compatible version, we can just return it
885+ return [Version ]::new($Version.Major , $Version.Minor , $Version.Patch )
886+ }
887+ if ($buildFlags -contains ' NOBUILD' ) {
888+ return [Version ]::new($Version.Major , $Version.Minor )
889+ }
890+ # It is not possible to have no build version but have a revision version, we dont have to test for that
891+ if ($buildFlags -contains ' HASREVISION' ) {
892+ # A null prerelease label will map to 0, so this will correctly be for example 3.2.1.0 if it is null but NOREVISION wasnt flagged
893+ return [Version ]::new($Version.Major , $Version.Minor , $Version.Patch - 1 , $Version.PreReleaseLabel )
894+ }
895+
896+ throw [InvalidDataException ]" Unexpected situation when parsing SemanticVersion $Version to Version. This is a bug in ModuleFastSpec and should be reported"
881897 }
898+
882899 [Version ] ToVersion() {
883900 if (-not $this.Required ) { throw [NotSupportedException ]' You can only convert Required specs to a version.' }
884901 # Warning: Return type is not enforced by the method, that's why we did it explicitly here.
885902 return [Version ][ModuleFastSpec ]::ParseSemanticVersion($this.Required )
886903 }
887904
888-
889905 # ##Implicit Methods
890906
891907 # This string will be unique for each spec type, and can (probably)? Be safely used as a hashcode
@@ -1002,6 +1018,88 @@ class ModuleFastSpec : IComparable {
10021018 }
10031019}
10041020
1021+ # This is a helper function that processes nuget ranges.
1022+ # Reference: https://github.com/NuGet/NuGet.Client/blob/035850255a15b60437d22f9178c4206bafe0b6a9/src/NuGet.Core/NuGet.Versioning/VersionRangeFactory.cs#L91-L265
1023+ class NugetRange {
1024+ [SemanticVersion ]$Min
1025+ [SemanticVersion ]$Max
1026+ [boolean ]$MinInclusive = $true
1027+ [boolean ]$MaxInclusive = $true
1028+
1029+ NugetRange([string ]$string ) {
1030+ # Use a regex to parse a semantic version range inclusive
1031+ # of the NuGet versioning spec.
1032+ # Reference: https://docs.microsoft.com/en-us/nuget/concepts/package-versioning#version-ranges-and-wildcards
1033+ if ($string -as [SemanticVersion ]) {
1034+ $this.Min = $string
1035+ $this.Max = $string
1036+ return
1037+ }
1038+
1039+ # Matches for beginning and ending parens or brackets
1040+ # If it doesnt match this, we've already evaluted the possible other solution
1041+ if ($string -notmatch ' ^(\(|\[)(.+)(\)|\])$' ) {
1042+ throw " Invalid Nuget Range: $string "
1043+ }
1044+ $left , $range , $right = $Matches [1 .. 3 ]
1045+
1046+ $this.MinInclusive = $left -eq ' ['
1047+ $this.MaxInclusive = $right -eq ' ]'
1048+
1049+ if ($range -notmatch ' \,' ) {
1050+ $req = [String ]::IsNullOrWhiteSpace($range ) ? [ModuleFastSpec ]::MinVersion : [SemanticVersion ]$range
1051+ $this.Min = $req
1052+ $this.Max = $req
1053+ return
1054+ }
1055+ $minString , $maxString = $range.split (' ,' )
1056+ if (-not [String ]::IsNullOrWhiteSpace($minString.trim ())) { $minString.trim () }
1057+ if (-not [String ]::IsNullOrWhiteSpace($maxString.trim ())) { $maxString.trim () }
1058+ }
1059+
1060+ static [SemanticVersion ] Decrement([SemanticVersion ]$version ) {
1061+ if ($version.BuildLabel -or $version.PreReleaseLabel ) {
1062+ Write-Warning ' Decrementing a version with a build or prerelease label is not supported as the Powershell Semantic Version class cannot compare them anyways. We will decrement the patch version instead and strip the prerelease headers. Do not rely on this behavior, it will change. https://github.com/PowerShell/PowerShell/issues/18489'
1063+ }
1064+ if ($version.Patch -gt 0 ) {
1065+ return [SemanticVersion ]::new($version.Major , $version.Minor , $version.Patch - 1 )
1066+ }
1067+ if ($version.Minor -gt 0 ) {
1068+ if ($version.Patch -eq 0 ) {
1069+ return [SemanticVersion ]::new($version.Major , $version.Minor - 1 , [int ]::MaxValue)
1070+ }
1071+ return [SemanticVersion ]::new($version.Major , $version.Minor - 1 , $version.Patch )
1072+ }
1073+ if ($version.Major -gt 0 ) {
1074+ if ($version.Minor -eq 0 -and $version.Patch -eq 0 ) {
1075+ return [SemanticVersion ]::new($version.Major - 1 , [int ]::MaxValue, [int ]::MaxValue)
1076+ }
1077+ }
1078+ throw [ArgumentOutOfRangeException ]' Unexpected Decrement Scenario Occurred, this should never happen and is a bug in ModuleFastSpec'
1079+ }
1080+
1081+ static [SemanticVersion ] Increment([SemanticVersion ]$version ) {
1082+ if ($version.BuildLabel -or $version.PreReleaseLabel ) {
1083+ Write-Warning ' Incrementing a version with a build or prerelease label is not supported as the Powershell Semantic Version class cannot compare them anyways. We will decrement the patch version instead and strip the prerelease headers. Do not rely on this behavior, it will change. https://github.com/PowerShell/PowerShell/issues/18489'
1084+ }
1085+ if ($version.Patch -le [int ]::MaxValue) {
1086+ return [SemanticVersion ]::new($version.Major , $version.Minor , $version.Patch + 1 )
1087+ }
1088+ if ($version.Minor -gt 0 ) {
1089+ if ($version.Patch -eq [int ]::MaxValue) {
1090+ return [SemanticVersion ]::new($version.Major , $version.Minor + 1 , 0 )
1091+ }
1092+ return [SemanticVersion ]::new($version.Major , $version.Minor + 1 , $version.Patch )
1093+ }
1094+ if ($version.Major -gt 0 ) {
1095+ if ($version.Minor -eq 0 -and $version.Patch -eq 0 ) {
1096+ return [SemanticVersion ]::new($version.Major - 1 , [int ]::MaxValue, [int ]::MaxValue)
1097+ }
1098+ }
1099+ throw [ArgumentOutOfRangeException ]' Unexpected Increment Scenario Occurred, this should never happen and is a bug in ModuleFastSpec'
1100+ }
1101+ }
1102+
10051103# This is a module helper to create "getters" in classes
10061104function Add-Getters {
10071105 Get-Member - InputObject $this - MemberType Method - Force |
@@ -1071,62 +1169,6 @@ function Get-ModuleInfoAsync {
10711169 return $HttpClient.GetStringAsync ($uri , $CancellationToken )
10721170}
10731171
1074- filter Parse-NugetDependency ([Parameter (Mandatory , ValueFromPipeline )]$Dependency ) {
1075- # TODO: Dependency should be more strictly typed
1076-
1077- # NOTE: This can't be a modulespecification from the start because modulespecs are immutable
1078- $dep = @ {
1079- ModuleName = $Dependency.id
1080- }
1081- $Version = $Dependency.range
1082-
1083- # Treat a null result as "any"
1084- if ([String ]::IsNullOrEmpty($Version )) {
1085- $dep.ModuleVersion = ' 0.0.0'
1086- return [ModuleFastSpec ]$dep
1087- }
1088-
1089- # If it is a direct module specification, treat this as a required version.
1090- $exactVersion = $null
1091- if ([Version ]::TryParse($Version , [ref ]$exactVersion )) {
1092- $dep.RequiredVersion = $exactVersion
1093- return [ModuleFastSpec ]$dep
1094- }
1095-
1096- # If it is an open bound, set ModuleVersion to 0.0.0 (meaning any version)
1097- if ($version -eq ' (, )' ) {
1098- $dep.ModuleVersion = ' 0.0.0'
1099- return [ModuleFastSpec ]$dep
1100- }
1101-
1102- # If it is an exact match version (has brackets and doesn't have a comma), set version accordingly
1103- $ExactVersionRegex = ' \[([^,]+)\]'
1104- if ($version -match $ExactVersionRegex ) {
1105- $dep.RequiredVersion = $matches [1 ]
1106- return [ModuleFastSpec ]$dep
1107- }
1108-
1109- # Parse all other remainder options. For this purpose we ignore inclusive vs. exclusive
1110- # TODO: Add inclusive/exclusive parsing
1111- $version = $version -replace ' [\[\(\)\]]' , ' ' -split ' ,'
1112-
1113- $minimumVersion = $version [0 ].trim()
1114- $maximumVersion = $version [1 ].trim()
1115- if ($minimumVersion -and $maximumVersion -and ($minimumVersion -eq $maximumVersion )) {
1116- # If the minimum and maximum versions match, we treat this as an explicit version
1117- $dep.RequiredVersion = $minimumVersion
1118- return [ModuleFastSpec ]$dep
1119- } elseif ($minimumVersion -or $maximumVersion ) {
1120- if ($minimumVersion ) { $dep.ModuleVersion = $minimumVersion }
1121- if ($maximumVersion ) { $dep.MaximumVersion = $maximumVersion }
1122- } else {
1123- # If no matching version works, just set dep to a string of the modulename
1124- Write-Warning " $ ( $dep.ModuleName ) has an invalid version spec, falling back to maximum version."
1125- }
1126-
1127- return [ModuleFastSpec ]$dep
1128- }
1129-
11301172<#
11311173. SYNOPSIS
11321174Adds an existing PowerShell Modules path to the current session as well as the profile
@@ -1272,7 +1314,6 @@ function Limit-ModuleFastSpecs {
12721314 }
12731315 -not $Highest ? $Versions : @ ($Versions | Sort-Object - Descending | Select-Object - First 1 )
12741316}
1275-
12761317# endregion Helpers
12771318
12781319# Export-ModuleMember Get-ModuleFast
0 commit comments