Private/ConvertFrom-HDTYaml.ps1
|
function ConvertFrom-HDTYaml { <# .SYNOPSIS Parses YAML text into an ordered dictionary, turning a parser exception into a configuration error that names the file and line. .DESCRIPTION This is the ONLY place in the engine that mentions ConvertFrom-Yaml. Everything else asks for a document and gets either an ordered dictionary or a pointed error: malformed YAML produces a pointed configuration error, not a crash. Three behaviours are deliberate: 1. The dependency is imported lazily. powershell-yaml is NOT in the manifest's RequiredModules: that would make the whole module unimportable wherever the dependency is absent, and it would complicate staging into WinPE, which has no gallery. A missing module is reported as HDTDependencyError naming the module and the command that installs it. 2. -Ordered is mandatory, not a nicety. Without it the parser returns a hashtable whose key order differs between Windows PowerShell 5.1 and pwsh 7 - the same document was observed yielding two different orders. rules.yaml applies its set: keys in document order, so that difference would make variable resolution depend on which engine happened to be running. 3. Nothing of the parser's own exception types escapes. A YAML syntax error surfaces as a MethodInvocationException wrapping a YamlDotNet exception which carries .Start.Line; that line is lifted into the message and the whole chain is dropped, so no caller can come to depend on a third-party exception type. The parser's own sentence is kept, because it is the part that says what is actually wrong. An empty or whitespace-only document parses to $null rather than throwing. "The file exists and says nothing" is a fact the validator reports in its own words, not a parse failure. .PARAMETER Yaml The document text, as read through an IFileSystem. .PARAMETER Path The path the text came from. Used for the error message and the TargetObject only - this function never reads the file itself. .OUTPUTS System.Collections.Specialized.OrderedDictionary, or $null for an empty document. A document whose root is a sequence returns a list; rejecting that is the validator's job, not the parser's. .EXAMPLE ConvertFrom-HDTYaml -Yaml $FileSystem.ReadAllText($path) -Path $path The canonical call: the filesystem is injected, this is pure text in and objects out. #> [CmdletBinding()] [OutputType([System.Collections.Specialized.OrderedDictionary])] param( [Parameter(Mandatory = $true, Position = 0)] [AllowEmptyString()] [string] $Yaml, [Parameter(Mandatory = $true, Position = 1)] [ValidateNotNullOrEmpty()] [string] $Path ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' if ($null -eq (Get-Command -Name ConvertFrom-Yaml -ErrorAction SilentlyContinue)) { try { Import-Module -Name powershell-yaml -ErrorAction Stop } catch { $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord ` -Message 'the powershell-yaml module is required to read HDT configuration and could not be imported. Run: Install-Module powershell-yaml -Scope AllUsers.' ` -Path $Path -ErrorId 'HDTDependencyError' -Category NotInstalled)) } } if ([string]::IsNullOrWhiteSpace($Yaml)) { return $null } $document = $null $failure = $null try { $document = ConvertFrom-Yaml -Yaml $Yaml -Ordered } catch { $failure = $_ } if ($null -ne $failure) { $inner = $failure.Exception while ($null -ne $inner.InnerException) { $inner = $inner.InnerException } $line = 0 if (@($inner.PSObject.Properties.Name) -contains 'Start') { $start = $inner.Start if ($null -ne $start -and @($start.PSObject.Properties.Name) -contains 'Line') { $line = [int] $start.Line } } $message = 'the YAML in this file could not be parsed. {0}' -f $inner.Message if ($line -gt 0) { $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord -Message $message -Path $Path -Line $line)) } $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord -Message $message -Path $Path)) } return $document } |