Public/ConvertFrom-DhYaml.ps1
|
function ConvertFrom-DhYaml { <# .SYNOPSIS Generate a runnable PowerShell script from a YAML (or JSON) dashboard layout spec. .DESCRIPTION Reads a declarative layout file (the schema emitted by ConvertTo-DhYaml, or hand-written) and produces a .ps1 script of New-DhDashboard / Add-Dh* / Export-DhDashboard calls, in dependency-correct order (tables are declared before the blocks that reference them). The generated parameters are validated against the LIVE cmdlet metadata (Get-Command), so only real parameters are emitted and switch vs. bool is handled correctly. Keys that are not parameters (e.g. a `dataRows` count) are skipped. Table data: a `dataFile` key becomes `-Data (Import-Csv '<file>')`; an inline `data` array becomes `-Data @(...)`; otherwise a `-Data @() # TODO` placeholder is emitted for you to fill in. This tool requires the core DashHtml module to be available (it reads the cmdlet metadata). It does NOT run the generated script. .PARAMETER Path Path to the .yaml / .yml / .json layout file. .PARAMETER OutFile Optional path to write the generated .ps1. When omitted, the script text is returned. .PARAMETER OutputPath The -OutputPath baked into the generated Export-DhDashboard call. Defaults to '.\dashboard.html'. .EXAMPLE ConvertFrom-DhYaml -Path infra.yaml -OutFile build-infra.ps1 .\build-infra.ps1 # run it to produce the HTML #> [CmdletBinding()] param( [Parameter(Mandatory)] [string] $Path, [string] $OutFile, [string] $OutputPath = '.\dashboard.html' ) if (-not (Test-Path -LiteralPath $Path)) { throw "ConvertFrom-DhYaml: file not found: $Path" } $model = ConvertFrom-DhYamlText (Get-Content -LiteralPath $Path -Raw) if (-not (Test-DhYamlMap $model)) { throw "ConvertFrom-DhYaml: top-level document must be a mapping." } $sb = [System.Text.StringBuilder]::new() [void]$sb.AppendLine('#Requires -Version 7.0') [void]$sb.AppendLine('# Generated by ConvertFrom-DhYaml from ' + (Split-Path $Path -Leaf)) [void]$sb.AppendLine('Import-Module DashHtml -ErrorAction Stop') [void]$sb.AppendLine('') # ---- New-DhDashboard ---- $ndArgs = [System.Collections.Generic.List[string]]::new() $ndParams = Get-DhCmdletParamMeta 'New-DhDashboard' foreach ($k in $model.Keys) { if ($k -in @('summary','tables','blocks','globalFilters','alertBanners','links')) { continue } if ($k -eq 'autoRefresh') { $ar = $model[$k] # int seconds (from ConvertTo-DhYaml) -> 'Ns'; a string like '5m' (from a script) -> as-is $arStr = if ($ar -is [string]) { $ar } else { "$([int]$ar)s" } [void]$ndArgs.Add("-AutoRefresh '$arStr'"); continue } $arg = Format-DhArg -ParamMeta $ndParams -Key $k -Value $model[$k] if ($arg) { [void]$ndArgs.Add($arg) } } [void]$sb.AppendLine('$report = New-DhDashboard ' + ($ndArgs -join ' ')) [void]$sb.AppendLine('') # ---- Global summary (declared before tables so it renders at the top) ---- if ($model.Contains('summary') -and $model['summary']) { $s = $model['summary'] $callArgs = [System.Collections.Generic.List[string]]::new(); [void]$callArgs.Add('-Report $report') $meta = Get-DhCmdletParamMeta 'Add-DhSummary' foreach ($k in $s.Keys) { $arg = Format-DhArg -ParamMeta $meta -Key $k -Value $s[$k] if ($arg) { [void]$callArgs.Add($arg) } } [void]$sb.AppendLine('Add-DhSummary ' + ($callArgs -join ' ')) [void]$sb.AppendLine('') } # ---- Tables (BEFORE blocks) ---- if ($model.Contains('tables')) { $tblMeta = Get-DhCmdletParamMeta 'Add-DhTable' foreach ($t in @($model['tables'])) { $callArgs = [System.Collections.Generic.List[string]]::new(); [void]$callArgs.Add('-Report $report') foreach ($k in $t.Keys) { if ($k -in @('data','dataFile','dataRows')) { continue } $mapKey = if ($k -ieq 'id') { 'TableId' } else { $k } $arg = Format-DhArg -ParamMeta $tblMeta -Key $mapKey -Value $t[$k] if ($arg) { [void]$callArgs.Add($arg) } } # data resolution if ($t.Contains('dataFile') -and $t['dataFile']) { [void]$callArgs.Add("-Data (Import-Csv '$($t['dataFile'])')") } elseif ($t.Contains('data') -and $t['data']) { [void]$callArgs.Add('-Data ' + (ConvertTo-DhPsLiteral @($t['data']))) } else { $cntNote = if ($t.Contains('dataRows')) { " # TODO: supply data ($($t['dataRows']) rows in the source)" } else { ' # TODO: supply -Data' } [void]$callArgs.Add('-Data @()' + $cntNote) } [void]$sb.AppendLine('Add-DhTable ' + ($callArgs -join ' ')) } [void]$sb.AppendLine('') } # ---- Blocks ---- if ($model.Contains('blocks')) { foreach ($b in @($model['blocks'])) { $type = [string]$b['type'] $cmd = $script:DhTypeToCmdlet[$type.ToLower()] if (-not $cmd) { [void]$sb.AppendLine("# (skipped) unknown block type '$type'"); continue } $meta = Get-DhCmdletParamMeta $cmd $callArgs = [System.Collections.Generic.List[string]]::new(); [void]$callArgs.Add('-Report $report') foreach ($k in $b.Keys) { if ($k -ieq 'type') { continue } $arg = Format-DhArg -ParamMeta $meta -Key $k -Value $b[$k] if ($arg) { [void]$callArgs.Add($arg) } } [void]$sb.AppendLine("$cmd " + ($callArgs -join ' ')) } [void]$sb.AppendLine('') } # ---- Global filters / alert banners / links ---- foreach ($section in @( @{ Key='globalFilters'; Cmd='Add-DhGlobalFilter' }, @{ Key='alertBanners'; Cmd='Add-DhAlertBanner' }, @{ Key='links'; Cmd='Set-DhTableLink' } )) { if (-not $model.Contains($section.Key)) { continue } $meta = Get-DhCmdletParamMeta $section.Cmd if (-not $meta) { continue } foreach ($item in @($model[$section.Key])) { $callArgs = [System.Collections.Generic.List[string]]::new(); [void]$callArgs.Add('-Report $report') foreach ($k in $item.Keys) { $arg = Format-DhArg -ParamMeta $meta -Key $k -Value $item[$k] if ($arg) { [void]$callArgs.Add($arg) } } [void]$sb.AppendLine("$($section.Cmd) " + ($callArgs -join ' ')) } [void]$sb.AppendLine('') } [void]$sb.AppendLine("Export-DhDashboard -Report `$report -OutputPath '$OutputPath' -Force") $script = $sb.ToString() if ($OutFile) { Set-Content -Path $OutFile -Value $script -Encoding UTF8 Write-Verbose "ConvertFrom-DhYaml: wrote $OutFile" } else { $script } } function Get-DhCmdletParamMeta { <# Returns the cmdlet's parameter metadata dictionary, or $null if absent. #> param([string] $CmdletName) $cmd = Get-Command $CmdletName -ErrorAction SilentlyContinue if (-not $cmd) { return $null } return $cmd.Parameters } function Format-DhArg { <# .SYNOPSIS Render ' -Param <literal>' for a spec key IF the cmdlet has a matching parameter; '' otherwise. Handles switch vs bool. #> param([object] $ParamMeta, [string] $Key, [object] $Value) if (-not $ParamMeta) { return '' } # case-insensitive param lookup $paramName = $null foreach ($p in $ParamMeta.Keys) { if ($p -ieq $Key) { $paramName = $p; break } } if (-not $paramName) { return '' } if ($paramName -in $script:DhCommonParams) { return '' } $ptype = $ParamMeta[$paramName].ParameterType if ($ptype -eq [System.Management.Automation.SwitchParameter]) { if ($Value -is [bool]) { return $(if ($Value) { "-$paramName" } else { '' }) } return "-$paramName" } return "-$paramName $(ConvertTo-DhPsLiteral $Value)" } |