Format-YAML.ps1

function Format-YAML
{
    <#
    .SYNOPSIS
        Formats objects as YAML
    .DESCRIPTION
        Formats an object as YAML.
    .EXAMPLE
        Format-Yaml -InputObject @("a", "b", "c")
    .EXAMPLE
        @{a="b";c="d";e=@{f=@('g')}} | Format-Yaml
    #>

    [Management.Automation.Cmdlet("Format","Object")]
    [ValidateScript({return $true})]
    param(
    # The InputObject.
    [Parameter(ValueFromPipeline)]
    [PSObject]
    $InputObject,

    # If set, will make a YAML header by adding a YAML Document tag above and below output.
    [Alias('YAMLDocument')]
    [switch]
    $YamlHeader,

    [int]
    $Indent = 0,

    # The maximum depth of objects to include.
    # Beyond this depth, an empty string will be returned.
    [int]
    $Depth
    )

    begin {
        $toYaml = {
            param(
            [Parameter(ValueFromPipeline,Position=0)]$Object,
            [Object]$Parent,
            [Object]$GrandParent,
            [int]$Indent = 0)
            
            begin { $n = 0; $mySelf = $myInvocation.MyCommand.ScriptBlock }
            process {
                $n++
                if ($Object -eq $null) { return }

                if ($depth) {
                    $myDepth = $indent / 2
                    if ($myDepth -gt $depth) {
                        return ''
                    }
                }
            
                if ($Parent -and $Parent -is [Collections.IList]) {
                    if ($Parent.IndexOf($Object) -gt 0) { ' ' * $Indent }
                    '- '
                }
            
                #region Primitives
                if ( $Object -is [string] ) { # If it's a string
                    if ($object -match '\n') { # see if it's a multline string.
                        "|" # If it is, emit the multiline indicator
                        $indent +=2
                        foreach ($l in $object -split '(?>\r\n|\n)') { # and emit each line indented
                            [Environment]::NewLine
                            ' ' * $indent
                            $l
                        }
                        $indent -=2
                    } elseif ("$object".Contains('*')) {
                        "'$($Object -replace "'","''")'"
                    } else {
                        $object
                    }
            
                    if ($Parent -is [Collections.IList]) { # If the parent object was a list
                        [Environment]::NewLine # emit a newline.
                    }
                    return # Once the string has been emitted, return.
                }
                if ( $Object.GetType().IsPrimitive ) { # If it is a primitive type
                    "$Object".ToLower()  # Emit it in lowercase.
                    if ($Parent -is [Collections.IList]) {
                        [Environment]::NewLine
                    }
                    return
                }
                #endregion Primitives
            
                #region KVP
                if ( $Object -is [Collections.DictionaryEntry] -or $object -is [Management.Automation.PSPropertyInfo]) {
                    if ($Parent -isnot [Collections.IList] -and
                        ($GrandParent -isnot [Collections.IList] -or $n -gt 1)) {
                        [Environment]::NewLine + (" " * $Indent)
                    }
                    if ($object.Key -and $Object.Key -is [string]) {
                        $Object.Key +": "
                    } elseif ($object.Name -and $object.Name -is [string]) {
                        $Object.Name +": "
                    }
                }
            
                if ( $Object -is [Collections.DictionaryEntry] -or $Object -is [Management.Automation.PSPropertyInfo]) {
                    & $mySelf -Object $Object.Value -Parent $Object -GrandParent $parent -Indent $Indent
                    return
                }
                #endregion KVP
            
            
                #region Nested
                if ($parent -and ($Object -is [Collections.IDictionary] -or $Object  -is [PSObject])) {
                    $Indent += 2
                } 
                elseif ($object -is [Collections.IList]) {
                    $allPrimitive = 1
                    foreach ($Obj in $Object) { 
                        $allPrimitive = $allPrimitive -band (
                            $Obj -is [string] -or 
                            $obj.GetType().IsPrimitive
                        ) 
                    }
                    if ($parent -and -not $allPrimitive) {
                        $Indent += 2
                    }
                }
            
            
                if ( $Object -is [Collections.IDictionary] ) {
                    $Object.GetEnumerator() |
                        & $mySelf -Parent $Object -GrandParent $Parent -Indent $Indent
                } elseif ($Object -is [Collections.IList]) {
            
                    [Environment]::NewLine + (' ' * $Indent)
            
                    $Object |
                        & $mySelf -Parent $Object -GrandParent $Parent -Indent $Indent
            
                } 
                elseif ($object -is [enum]) {
                    $object.ToString()
                }
                elseif ($Object.PSObject.Properties) {
                    $Object.psobject.properties |
                        & $mySelf -Parent $Object -GrandParent $Parent -Indent $Indent
                }
            
                if ($Object -is [Collections.IDictionary] -or $Object  -is [PSCustomObject] -or $Object -is [Collections.IList]) {
                    if ($Parent -is [Collections.IList]) { [Environment]::NewLine }
                    $Indent -= 2;
                }
                #endregion Nested
            }                
        }
        function IndentString([string]$String,[int]$Indent) {
            @(foreach ($line in @($String -split '(?>\r\n|\n)')) {
                (' ' * $indent) + $line 
            }) -join [Environment]::NewLine
        }
        $inputWasNotPiped = $PSBoundParameters.InputObject -as [bool]
        $allInputObjects  = @()
    }

    process {
        if ($inputWasNotPiped) {
            IndentString ('' + $(if ($YamlHeader) { '---' + [Environment]::NewLine })  + (
                (& $toYaml -object $inputObject) -join '' -replace 
                    "$([Environment]::NewLine * 2)", [Environment]::NewLine
            ) + $(if ($YamlHeader) { [Environment]::NewLine  + '---'})) -Indent $Indent
        } else {
            $allInputObjects += $inputObject
        }
    }

    end {
        if (-not $allInputObjects) { return }
        if ($allInputObjects.Length -eq 1) {
            IndentString ('' + $(if ($YamlHeader) { '---' + [Environment]::NewLine}) + (
                (& $toYaml -object $inputObject) -join '' -replace 
                    "$([Environment]::NewLine * 2)", [Environment]::NewLine
            ) + $(if ($YamlHeader) { [Environment]::NewLine  + '---'})) -Indent $Indent
        } else {
            IndentString ('' + $(if ($YamlHeader) { '---' + [Environment]::NewLine})  + (
                (& $toYaml -object $allInputObjects) -join '' -replace 
                    "$([Environment]::NewLine * 2)", [Environment]::NewLine
            ) + $(if ($YamlHeader) { [Environment]::NewLine  + '---'})) -Indent $Indent
        }
    }
}