Private/Set-HDTRuleKey.ps1

function Set-HDTRuleKey {
    <#
        .SYNOPSIS
            Replaces, inserts or removes one key of one rule, leaving every other
            line byte-identical.
 
        .DESCRIPTION
            THE THREE-WAY SPLICE EVERY RULE EDIT COMES DOWN TO: rewrite the key's
            lines if it is there, insert them if it is not, drop them if the
            caller passed nothing. Written once, because when:, set: and setFrom:
            differ only in what goes in them.
 
            A NEW KEY GOES WHERE A RULE IS WRITTEN, which is name, then when,
            then set or setFrom. That is the order the engine's own documents and
            every worked example use, and it is the order an administrator
            scanning a diff expects: what the rule is called, when it applies,
            what it does. The key is inserted after the last key that precedes it
            in that order and is actually present, so a rule that has no when:
            still gets its set: in the right place.
 
            IT NEVER TOUCHES THE ENTRY LINE. `- name: X` is a list item whose
            first key happens to be the name; rewriting it as a plain key would
            fold the rule into the one above it. Renaming is done by the cmdlet
            that knows it is renaming.
 
        .PARAMETER Line
            The document, already split into lines.
 
        .PARAMETER Block
            The rule's block, from Get-HDTRuleBlock, resolved against this
            same Line.
 
        .PARAMETER Key
            The key to write - 'when', 'set' or 'setFrom'.
 
        .PARAMETER Text
            The lines the key becomes. Empty removes the key.
 
        .INPUTS
            None. This command does not accept pipeline input.
 
        .OUTPUTS
            System.String[]
 
        .EXAMPLE
            Set-HDTRuleKey -Line $line -Block $block -Key 'set' -Text $written
 
        .EXAMPLE
            Set-HDTRuleKey -Line $line -Block $block -Key 'when' -Text @()
 
            Removes the rule's conditions, so it always applies.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'Returns a copy of in-memory lines. Save-HDTRuleDocument is the only command that writes, and it carries ShouldProcess.')]
    [CmdletBinding()]
    [OutputType([string[]])]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string[]] $Line,

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

        [Parameter(Mandatory = $true, Position = 2)]
        [ValidateNotNullOrEmpty()]
        [string] $Key,

        [Parameter(Mandatory = $true, Position = 3)]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string[]] $Text
    )

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

    # The order a rule is written in, and therefore the order a new key is
    # inserted into.
    $order = @('name', 'when', 'set', 'setFrom')

    # Named for what it holds rather than $key: PowerShell variable names are
    # case-insensitive, so a local $key IS the $Key parameter.
    $declared = @(Get-HDTRuleKey -Line $Line -Block $Block)
    $found = @($declared | Where-Object { $_.Name -eq $Key })

    $replaceFrom = -1
    $replaceTo = -1
    $insertAfter = [int] $Block.Entry

    if (@($found).Count -gt 0) {
        $replaceFrom = [int] $found[0].Index
        $replaceTo = [int] $found[0].End
    } else {
        $rank = [array]::IndexOf($order, $Key)

        foreach ($current in $declared) {
            $at = [array]::IndexOf($order, [string] $current.Name)

            # An unknown key keeps whatever place it has in the file rather than
            # being jumped over; the document validator is what refuses it.
            if ($at -lt 0) { $at = $order.Count }

            if ($at -lt $rank -and [int] $current.End -gt $insertAfter) {
                $insertAfter = [int] $current.End
            }
        }
    }

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

    for ($i = 0; $i -lt $Line.Count; $i++) {
        if ($replaceFrom -ge 0 -and $i -ge $replaceFrom -and $i -le $replaceTo) {
            if ($i -eq $replaceFrom) {
                foreach ($current in $Text) { [void] $result.Add($current) }
            }

            continue
        }

        [void] $result.Add($Line[$i])

        if ($replaceFrom -lt 0 -and $i -eq $insertAfter) {
            foreach ($current in $Text) { [void] $result.Add($current) }
        }
    }

    return [string[]] @($result)
}