Private/VersionRules.ps1
|
# Version parsing and rule matching. # # Rules: # Exact every component given in Expect must match ('8.0.34' -> only 8.0.34; '8.0' -> 8.0.x) # Minor same major.minor ('8.0' -> 8.0.x, not 8.2) # Major same major ('2' or '2.13' -> 2.x) # Minimum found >= expected ('2.40' -> 2.40 or newer) $script:SLRules = @('Exact', 'Minor', 'Major', 'Minimum') # A version: 1.2, 1.2.3 or 1.2.3.4, optionally followed by a prerelease tag # like -rc.2.26504.105 or -preview.7. The tag is kept so previews are never # reported as releases. $script:SLDefaultPattern = '\d+(\.\d+){1,3}(-[0-9A-Za-z][0-9A-Za-z.]*)?' # Pulls every version-looking string out of text, in order. function Get-SLVersionTexts { param([string] $Text, [string] $Pattern) if (-not $Text) { return @() } if (-not $Pattern) { $Pattern = $script:SLDefaultPattern } $found = @() foreach ($m in [regex]::Matches($Text, $Pattern)) { # A custom pattern may use a 'version' named group; otherwise take the whole match. if ($m.Groups['version'].Success) { $found += $m.Groups['version'].Value } else { $found += $m.Value } } # Emitted one per item; callers wrap in @() to always get an array. return $found } # Pulls the first version-looking string out of text. function Get-SLVersionText { param([string] $Text, [string] $Pattern) $all = @(Get-SLVersionTexts -Text $Text -Pattern $Pattern) if ($all.Count -eq 0) { return $null } return $all[0] } # True if the version carries a prerelease tag (-rc, -preview, -beta ...). function Test-SLPrerelease { param([string] $Version) return ($Version -match '^\D*\d+(\.\d+)*-') } # Splits '8.0.34' into an int array @(8,0,34). Any prerelease tag is ignored # for comparison. Returns $null if not numeric. function ConvertTo-SLVersionParts { param([string] $Version) if (-not $Version) { return $null } $core = $Version.Trim().TrimStart('v', 'V').Split('-')[0] $parts = @() foreach ($p in $core.Split('.')) { $n = 0 if (-not [int]::TryParse($p, [ref] $n)) { return $null } $parts += $n } return , $parts } # Compares two part arrays component by component, padding the shorter with zeros. # Returns -1, 0 or 1. function Compare-SLVersionParts { param([int[]] $A, [int[]] $B) $len = [Math]::Max($A.Count, $B.Count) for ($i = 0; $i -lt $len; $i++) { $x = 0; if ($i -lt $A.Count) { $x = $A[$i] } $y = 0; if ($i -lt $B.Count) { $y = $B[$i] } if ($x -lt $y) { return -1 } if ($x -gt $y) { return 1 } } return 0 } # From a list of versions, picks the highest release; falls back to the highest # prerelease only if there is no release. Returns $null for an empty list. function Select-SLBestVersion { param([string[]] $Versions) $list = @($Versions | Where-Object { $_ -and $null -ne (ConvertTo-SLVersionParts $_) }) if ($list.Count -eq 0) { return $null } $releases = @($list | Where-Object { -not (Test-SLPrerelease $_) }) if ($releases.Count -gt 0) { $list = $releases } $best = $list[0] foreach ($v in $list) { if ((Compare-SLVersion $v $best) -gt 0) { $best = $v } } return $best } # Full comparison including prerelease tags: 11.0.100 > 11.0.100-rc.2 > 11.0.100-rc.1. # Returns -1, 0 or 1. function Compare-SLVersion { param([string] $A, [string] $B) $c = Compare-SLVersionParts (ConvertTo-SLVersionParts $A) (ConvertTo-SLVersionParts $B) if ($c -ne 0) { return $c } $preA = Test-SLPrerelease $A $preB = Test-SLPrerelease $B if (-not $preA -and -not $preB) { return 0 } if (-not $preA) { return 1 } # a release beats its own prerelease if (-not $preB) { return -1 } # Both prerelease: compare the numbers inside the tags (rc.2 > rc.1). $numsA = @([regex]::Matches(($A -split '-', 2)[1], '\d+') | ForEach-Object { [int] $_.Value }) $numsB = @([regex]::Matches(($B -split '-', 2)[1], '\d+') | ForEach-Object { [int] $_.Value }) return (Compare-SLVersionParts $numsA $numsB) } # True if Found satisfies Expect under Rule. function Test-SLVersionRule { param( [Parameter(Mandatory)] [string] $Found, [Parameter(Mandatory)] [string] $Expect, [Parameter(Mandatory)] [string] $Rule ) $f = ConvertTo-SLVersionParts $Found $e = ConvertTo-SLVersionParts $Expect if ($null -eq $f -or $null -eq $e) { return $false } switch ($Rule) { 'Exact' { if ($f.Count -lt $e.Count) { return $false } for ($i = 0; $i -lt $e.Count; $i++) { if ($f[$i] -ne $e[$i]) { return $false } } return $true } 'Minor' { if ($e.Count -lt 2 -or $f.Count -lt 2) { return $false } return ($f[0] -eq $e[0] -and $f[1] -eq $e[1]) } 'Major' { return ($f[0] -eq $e[0]) } 'Minimum' { return ((Compare-SLVersionParts $f $e) -ge 0) } default { throw "Unknown rule '$Rule'. Use one of: $($script:SLRules -join ', ')." } } } |