Private/Get-HDTWorkspaceChildKey.ps1

function Get-HDTWorkspaceChildKey {
    <#
        .SYNOPSIS
            Lists the keys written directly inside one block of a workspace
            document, and the lines each of them occupies.

        .DESCRIPTION
            WHAT AN INSERT NEEDS BEFORE IT CAN CHOOSE A PLACE. A new key goes
            where the document's own order puts it - name, then architecture,
            then language - and working out which sibling it belongs after means
            knowing which siblings are there and where each of them ends.

            IT LOOKS ONE LEVEL DOWN AND NO FURTHER. bootImage's own keys are its
            children; the entries of the extraContent underneath one of them are
            not, and reporting them would make an insert land inside a list.

            A BLOCK OF $null MEANS THE DOCUMENT ITSELF, whose children are the
            top-level keys. That is a real case rather than a convenience: the
            bootImage block is created by an insert at the top level, and it needs
            the same ordering answer as everything nested.

        .PARAMETER Line
            The document, already split into lines.

        .PARAMETER Block
            One block from Get-HDTWorkspaceKey, or $null for the document root.

        .INPUTS
            None. This command does not accept pipeline input.

        .OUTPUTS
            System.Management.Automation.PSCustomObject[] in document order:

              Name the key, as written
              Index the line the key starts on
              End the last line it owns
              Indent the column these keys are written at

        .EXAMPLE
            Get-HDTWorkspaceChildKey -Line $line -Block (Get-HDTWorkspaceKey -Line $line -Path @('bootImage'))
    #>

    [CmdletBinding()]
    [OutputType([pscustomobject[]])]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string[]] $Line,

        [Parameter(Mandatory = $true, Position = 1)]
        [AllowNull()]
        [object] $Block
    )

    Set-StrictMode -Version Latest
    $ErrorActionPreference = 'Stop'

    $from = 0
    $to = $Line.Count - 1
    $indent = 0

    if ($null -ne $Block) {
        $from = [int] $Block.Index + 1
        $to = [int] $Block.End
        $indent = [int] $Block.ChildIndent
    }

    if ($indent -lt 0) { return [pscustomobject[]] @() }

    $found = New-Object -TypeName System.Collections.ArrayList

    for ($i = $from; $i -le $to; $i++) {
        $text = $Line[$i]

        if ([string]::IsNullOrWhiteSpace($text)) { continue }
        if ($text -match '^ *#') { continue }

        $lead = 0
        if ($text -match '^( *)') { $lead = $Matches[1].Length }

        if ($lead -ne $indent) { continue }
        if ($text -match '^ *-( |$)') { continue }
        if ($text -notmatch '^ *([A-Za-z][\w-]*):') { continue }

        [void] $found.Add([pscustomobject] @{
                Name   = $Matches[1]
                Index  = $i
                End    = $i
                Indent = $indent
            })
    }

    # -- how far each of them reaches ----------------------------------------

    for ($k = 0; $k -lt $found.Count; $k++) {
        $end = $to
        if ($k + 1 -lt $found.Count) { $end = [int] $found[$k + 1].Index - 1 }

        # A blank line or a comment between two keys belongs to neither.
        while ($end -gt [int] $found[$k].Index -and
            ([string]::IsNullOrWhiteSpace($Line[$end]) -or $Line[$end] -match '^ *#')) {
            $end--
        }

        $found[$k].End = $end
    }

    return [pscustomobject[]] @($found)
}