Private/Functions/ObjectHelpers.ps1
|
# Object wrapper helper functions function Add-CoalescedProperty { param ( [PSCustomObject] [Parameter(Mandatory=$true, ValueFromPipeline=$true)] $On, [string] [Parameter(Mandatory=$true)] $To, [string[]] [Parameter(Mandatory=$true)] $From ) Process { if ($On.PSObject.Properties.Name -contains $To) { return # don't overwrite existing property } foreach ($PropertyName in $From) { if ($null -ne $On.$PropertyName) { $On | Add-Member -MemberType NoteProperty -Name $To -Value $On.$PropertyName return # use first non-null value } } } } function Get-GitlabDefaultSortProperty { param ( [Parameter(Mandatory, Position=0)] $Object ) # a type that defines a SortKey picks its own ordering; everything else # falls back to the first of these it has, most recent first $RecencyPropertyNames = @('UpdatedAt', 'LastActivityAt') $PropertyNames = $Object.PSObject.Properties.Name if ($PropertyNames -contains 'SortKey') { return @{ Property = 'SortKey'; Descending = $false } } foreach ($Candidate in $RecencyPropertyNames) { if ($PropertyNames -contains $Candidate) { return @{ Property = $Candidate; Descending = $true } } } } function Test-GitlabSortPreference { # a caller that was given -Sort or -OrderBy is passing the user's ordering # through to the API; leave that result in the order it came back $CallStack = Get-PSCallStack for ($i = 1; $i -lt $CallStack.Count; $i++) { $BoundParameters = $CallStack[$i].InvocationInfo.BoundParameters if (-not $BoundParameters) { continue } foreach ($Name in 'Sort', 'OrderBy') { $Value = $null if ($BoundParameters.TryGetValue($Name, [ref] $Value) -and -not [string]::IsNullOrWhiteSpace($Value)) { Write-Verbose "Sort: $Name was passed to [$($CallStack[$i].InvocationInfo.MyCommand.Name)], skipping default sort" return $true } } } $false } function New-GitlabObject { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Creates PSCustomObject wrappers, not a state-changing operation')] [CmdletBinding()] param( [Parameter(ValueFromPipeline)] $InputObject, [Parameter(Position=0)] [string] $DisplayType, [Parameter()] [switch] $PreserveCasing ) Begin { $Wrappers = New-Object 'Collections.Generic.List[object]' } Process { foreach ($item in $InputObject) { if ($item -is [hashtable]) { $item = [PSCustomObject]$item } $Wrapper = New-Object PSObject $item.PSObject.Properties | Sort-Object Name | ForEach-Object { $Name = if ($PreserveCasing) { $_.Name } else { $_.Name | ConvertTo-PascalCase } $Value = $_.Value if ($Value -is [string] -and $Value -match '^\d{4}-\d{2}-\d{2}$') { $Value = [datetime]::Parse($Value) } $Wrapper | Add-Member -MemberType NoteProperty -Name $Name -Value $Value if ($Value -is [datetime]) { $SortableName = "${Name}Sortable" $SortableValue = $Value.ToString('yyyy-MM-dd HH:mm') $Wrapper | Add-Member -MemberType NoteProperty -Name $SortableName -Value $SortableValue } } # aliases for common property names $Wrapper | Add-CoalescedProperty -From @('WebUrl', 'TargetUrl') -To 'Url' if ($DisplayType) { $Wrapper.PSTypeNames.Insert(0, $DisplayType) $IdentityPropertyName = $global:GitlabIdentityPropertyNameExemptions[$DisplayType] if ($IdentityPropertyName -eq $null) { $IdentityPropertyName = 'Iid' # default for anything that isn't explicitly mapped } if ($IdentityPropertyName -ne '') { if ($Wrapper.$IdentityPropertyName) { $TypeShortName = $DisplayType.Split('.') | Select-Object -Last 1 $Wrapper | Add-CoalescedProperty -From $IdentityPropertyName -To "$($TypeShortName)Id" } else { Write-Warning "$DisplayType does not have an identity field" } } } $Wrappers.Add($Wrapper) } } End { if ($Wrappers.Count -lt 2 -or (Test-GitlabSortPreference)) { Write-Output $Wrappers return } $Sort = Get-GitlabDefaultSortProperty $Wrappers[0] if ($Sort) { $Wrappers | Sort-Object -Property $Sort.Property -Descending:$Sort.Descending } else { Write-Output $Wrappers } } } |