Private/Catalog.ps1

# The cmdlet catalog: VirtualMetric.Catalog.json, generated from sdk/cli/catalog.yaml (go run ./cmd/pwshgen).
# The same catalog drives Cloud Shell in the console; the generic semantics of its kinds and transforms are
# implemented by both runtimes identically (see Invoke-VMetricCatalogCmdlet).

$script:VMetricCatalogSchemaVersion = 1

function Set-VMetricCatalogPath {
    # Points the module at another catalog file (tests), or back to the default with $null.
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Sets module state for tests only.')]
    [CmdletBinding()]
    param(
        [AllowNull()]
        [AllowEmptyString()]
        [string] $Path
    )

    $script:VMetricCatalogPath = $Path
    $script:VMetricCatalog = $null
}

function Get-VMetricCatalogPath {
    # The catalog to read: the one set by Set-VMetricCatalogPath, else the module folder's, else (in a source
    # checkout, before the generator has run) the tests' fixture.
    [CmdletBinding()]
    [OutputType([string])]
    param()

    if ($script:VMetricCatalogPath) {
        return $script:VMetricCatalogPath
    }
    $candidates = @(
        (Join-Path -Path $script:ModuleRoot -ChildPath 'VirtualMetric.Catalog.json')
        (Join-Path -Path $script:ModuleRoot -ChildPath '../tests/fixtures/VirtualMetric.Catalog.json')
    )
    foreach ($candidate in $candidates) {
        if (Test-Path -LiteralPath $candidate -PathType Leaf) {
            return (Resolve-Path -LiteralPath $candidate).ProviderPath
        }
    }
    return $candidates[0]
}

function Get-VMetricCatalog {
    # The catalog, read once and indexed: Cmdlets and Types by name (ignoring case), and ResourceTypes, the
    # resource type (VirtualMetric/devices) of each output type (VirtualMetric.Device) an engine cmdlet writes.
    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param()

    if ($script:VMetricCatalog) {
        return $script:VMetricCatalog
    }

    $path = Get-VMetricCatalogPath
    if (-not (Test-Path -LiteralPath $path -PathType Leaf)) {
        throw (New-VMetricException -Message "The cmdlet catalog is missing ($path). Reinstall the VirtualMetric module." `
                -Code 'CatalogNotFound' -Category ObjectNotFound -Terminating)
    }
    $raw = ConvertFrom-VMetricJson -Json ([System.IO.File]::ReadAllText($path, [System.Text.Encoding]::UTF8))
    $version = [int](Get-VMetricMember -InputObject $raw -Name 'version')
    if ($version -gt $script:VMetricCatalogSchemaVersion) {
        throw (New-VMetricException -Message "The cmdlet catalog is version $version; this module reads version $($script:VMetricCatalogSchemaVersion). Update the VirtualMetric module." `
                -Code 'CatalogVersionUnsupported' -Category InvalidData -Terminating)
    }

    $cmdlets = [System.Collections.Generic.Dictionary[string, object]]::new([System.StringComparer]::OrdinalIgnoreCase)
    foreach ($entry in (ConvertTo-VMetricArray -InputObject (Get-VMetricMember -InputObject $raw -Name 'cmdlets'))) {
        $name = [string](Get-VMetricMember -InputObject $entry -Name 'name')
        if ($name) {
            $cmdlets[$name] = $entry
        }
    }
    $types = [System.Collections.Generic.Dictionary[string, object]]::new([System.StringComparer]::OrdinalIgnoreCase)
    $rawTypes = Get-VMetricMember -InputObject $raw -Name 'types'
    if ($rawTypes) {
        foreach ($property in $rawTypes.psobject.Properties) {
            $types[$property.Name] = $property.Value
        }
    }
    $resourceTypes = [System.Collections.Generic.Dictionary[string, string]]::new([System.StringComparer]::OrdinalIgnoreCase)
    foreach ($entry in $cmdlets.Values) {
        $output = [string](Get-VMetricMember -InputObject $entry -Name 'output')
        $resourceType = [string](Get-VMetricMember -InputObject $entry -Name 'resourceType')
        if ($output -and $resourceType -and -not $resourceTypes.ContainsKey($output)) {
            $resourceTypes[$output] = $resourceType
        }
    }

    $script:VMetricCatalog = [pscustomobject]@{
        Path          = $path
        Version       = $version
        # The deployment property model version the engine kinds write resources in.
        ApiVersion    = [string](Get-VMetricMember -InputObject $raw -Name 'apiVersion')
        Cmdlets       = $cmdlets
        Types         = $types
        ResourceTypes = $resourceTypes
    }
    $script:VMetricCatalog
}

function Get-VMetricCatalogCmdlet {
    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Mandatory)]
        [string] $Name
    )

    $catalog = Get-VMetricCatalog
    $entry = $null
    if (-not $catalog.Cmdlets.TryGetValue($Name, [ref] $entry)) {
        throw (New-VMetricException -Message "The cmdlet catalog has no entry for $Name. Update the VirtualMetric module." `
                -Code 'CatalogEntryNotFound' -Category ObjectNotFound -Terminating)
    }
    $entry
}

function Get-VMetricCatalogType {
    # The catalog's description of an output type ($null when it has none).
    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        [AllowNull()]
        [AllowEmptyString()]
        [string] $Name
    )

    if (-not $Name) {
        return $null
    }
    $catalog = Get-VMetricCatalog
    $type = $null
    if ($catalog.Types.TryGetValue($Name, [ref] $type)) {
        return $type
    }
    return $null
}

function Get-VMetricCatalogParameter {
    # The params of a catalog entry, as an array.
    [CmdletBinding()]
    [OutputType([object[]])]
    param(
        [Parameter(Mandatory)]
        [object] $Entry
    )

    return , (ConvertTo-VMetricArray -InputObject (Get-VMetricMember -InputObject $Entry -Name 'params'))
}

function Find-VMetricReadCmdlet {
    # The get cmdlet that reads what an engine cmdlet writes: the same noun (Set-VMetricDevice ->
    # Get-VMetricDevice), else a get cmdlet with the same output type.
    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Mandatory)]
        [object] $Entry
    )

    $catalog = Get-VMetricCatalog
    $name = [string](Get-VMetricMember -InputObject $Entry -Name 'name')
    $noun = $name.Substring($name.IndexOf('-') + 1)
    $candidate = $null
    if ($catalog.Cmdlets.TryGetValue("Get-$noun", [ref] $candidate) -and (Get-VMetricMember -InputObject $candidate -Name 'kind') -eq 'get') {
        return $candidate
    }
    $output = [string](Get-VMetricMember -InputObject $Entry -Name 'output')
    if ($output) {
        foreach ($other in $catalog.Cmdlets.Values) {
            if ((Get-VMetricMember -InputObject $other -Name 'kind') -eq 'get' -and
                (Get-VMetricMember -InputObject $other -Name 'output') -eq $output -and
                (Get-VMetricMember -InputObject $other -Name 'item')) {
                return $other
            }
        }
    }
    return $null
}

function Get-VMetricNounText {
    # A cmdlet's noun as words for messages: Remove-VMetricPipelineChild -> "pipeline child".
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory)]
        [string] $CmdletName
    )

    $noun = $CmdletName.Substring($CmdletName.IndexOf('-') + 1)
    if ($noun.StartsWith('VMetric')) {
        $noun = $noun.Substring('VMetric'.Length)
    }
    $words = [regex]::Replace($noun, '(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])', ' ')
    return $words.ToLowerInvariant()
}