Pansies.psm1

#Region '.\_init.ps1' 0
using namespace ColorMine.ColorSpaces
using namespace PoshCode.Pansies
using namespace ColorMine.Palettes
using namespace PoshCode.Pansies.Palettes
using namespace System.Collections.Generic

# On first import, if HostPreference doesn't exist, set it and strongly type it
if(!(Test-Path Variable:HostPreference) -or $HostPreference -eq $null) {
    [System.Management.Automation.ActionPreference]$global:HostPreference = "Continue"
}

Set-Variable HostPreference -Description "Dictates the action taken when a host message is delivered" -Visibility Public -Scope Global

if(-not $IsLinux -and -not $IsMacOS) {
    [PoshCode.Pansies.Console.WindowsHelper]::EnableVirtualTerminalProcessing()
}

if(Get-Command Add-MetadataConverter -ErrorAction SilentlyContinue) {
    Add-MetadataConverter @{
        RgbColor = { [PoshCode.Pansies.RgbColor]$args[0] }
        [PoshCode.Pansies.RgbColor] = { "RgbColor '$_'" }
    }
}
#EndRegion '.\_init.ps1' 23
#Region '.\Private\ConvertToCssColor.ps1' 0
function ConvertToCssColor {
    [CmdletBinding()]
    param(
        [Parameter(ParameterSetName="PListColorDictionary", Mandatory, Position = 0)]
        [Dictionary[string,object]]$colors,

        [Parameter(ParameterSetName="ColorValue", Mandatory, Position = 0)]
        [string]$color
    )
    end {
        if($PSCmdlet.ParameterSetName -eq "PListColorDictionary") {
            [int]$r = 255 * $colors["Red Component"]
            [int]$g = 255 * $colors["Green Component"]
            [int]$b = 255 * $colors["Blue Component"]
            [PoshCode.Pansies.RgbColor]::new($r, $g, $b).ToString()
        }
        if($PSCmdlet.ParameterSetName -eq "ColorValue") {
            [PoshCode.Pansies.RgbColor]::new($color).ToString()
        }
    }
}
#EndRegion '.\Private\ConvertToCssColor.ps1'
#Region '.\Private\ExportTheme.ps1' 0
function ExportTheme {
    <#
        .SYNOPSIS
            Imports themes by name
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        # The name of the theme
        [Parameter(Position = 0, Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$Name,

        [Parameter(ValueFromPipeline, Position = 1)]
        $InputObject,

        [switch]$Force,

        [switch]$Update,

        [switch]$PassThru,

        [ValidateSet("User", "Machine")]
        [string]$Scope = "User"
    )
    process {
        $NativeThemePath = Join-Path $(Get-ConfigurationPath -Scope $Scope) "$Name.theme.psd1"

        if(Test-Path -LiteralPath $NativeThemePath) {
            if($Update) {
                Write-Verbose "Updating $NativeThemePath"
                $Theme = Import-Metadata $NativeThemePath -ErrorAction Stop
                Update-Object -InputObject $Theme -UpdateObject $InputObject | Export-Metadata $NativeThemePath
            } elseif($Force -or $PSCmdlet.ShouldContinue("Overwrite $($NativeThemePath)?", "$Name Theme exists")) {
                Write-Verbose "Exporting to $NativeThemePath"
                $InputObject | Export-Metadata $NativeThemePath
            }
        } else {
            Write-Verbose "Exporting to $NativeThemePath"
            $InputObject | Export-Metadata $NativeThemePath
        }

        if($PassThru) {
            $InputObject | Add-Member NoteProperty Name $Name -Passthru |
                           Add-Member NoteProperty PSPath $NativeThemePath -Passthru
        }
    }
}
#EndRegion '.\Private\ExportTheme.ps1'
#Region '.\Private\FindVSCodeTheme.ps1' 0
function FindVsCodeTheme {
    [CmdletBinding()]
    param($Name)

    $VSCodeExtensions = @(
        # VS Code themes are in one of two places: in the app, or in your profile folder:
        Convert-Path "~\.vscode*\extensions\"
        # If `code` is in your path, we can guess where that is...
        Get-Command Code-Insiders, Code -ErrorAction Ignore |
            Split-Path | Split-Path | Join-Path -ChildPath "resources\app\extensions\"
    )
    $Warnings = @()

    $Themes = @(
        # If they passed a file path that exists, use just that one file
        if ($Specific = Test-Path -LiteralPath $Name) {
            $File = Convert-Path $Name
            $(
                if ($File.EndsWith(".json")) {
                    try {
                        # Write-Debug "Parsing json file: $File"
                        ConvertFrom-Json (Get-Content -Path $File -Raw -Encoding utf8) -ErrorAction SilentlyContinue
                    } catch {
                        Write-Error "Couldn't parse '$File'. $(
                        if($PSVersionTable.PSVersion.Major -lt 6) {
                            'You could try again with PowerShell Core, the JSON parser there works much better!'
                        })"

                    }
                } else {
                    # Write-Debug "Parsing PList file: $File"
                    Import-PList -Path $File
                }
            ) | Select-Object @{ Name = "Name"
                                 Expr = {
                                    if ($_.name) {
                                        $_.name
                                    } else {
                                        [IO.Path]::GetFileNameWithoutExtension($File)
                                    }
                                }
                           }, @{ Name = "Path"
                                 Expr = {$File}
                           }
        } else {
            $VSCodeExtensions  = $VSCodeExtensions | Join-Path -ChildPath "\*\package.json" -Resolve
            foreach ($File in $VSCodeExtensions) {
                # Write-Debug "Considering VSCode Extention $([IO.Path]::GetFileName([IO.Path]::GetDirectoryName($File)))"
                $JSON = Get-Content -Path $File -Raw -Encoding utf8
                try {
                    $Extension = ConvertFrom-Json $JSON -ErrorAction Stop
                    # if ($Extension.contributes.themes) {
                    # Write-Debug "Found $($Extension.contributes.themes.Count) themes"
                    # }
                    $Extension.contributes.themes |
                        Select-Object @{Name="Name" ; Expr={$_.label}},
                                      @{Name="Style"; Expr={$_.uiTheme}},
                                      @{Name="Path" ; Expr={Join-Path (Split-Path $File) $_.path -resolve}}
                } catch {
                    $Warning = "Couldn't parse some VSCode extensions."
                }
            }
        }
    )
    if($Themes.Count -eq 0) {
        throw "Could not find any VSCode themes. Please use a full path."
    }

    if($Specific -and $Themes.Count -eq 1) {
        $Themes
    }

    # Make sure we're comparing the name to a name
    $Name = [IO.Path]::GetFileName(($Name -replace "\.json$|\.tmtheme$"))
    Write-Verbose "Testing theme names for '$Name'"

    # increasingly fuzzy search: (eq -> like -> match)
    if(!($Theme = $Themes.Where{$_.name -eq $Name})) {
        if (!($Theme = $Themes.Where{$_.name -like $Name})) {
            if (!($Theme = $Themes.Where{$_.name -like "*$Name*"})) {
                foreach($Warning in $Warnings) {
                    Write-Warning $Warning
                }
                Write-Error "Couldn't find the theme '$Name', please try another: $(($Themes.name | Select-Object -Unique) -join ', ')"
            }
        }
    }
    if(@($Theme).Count -gt 1) {
        $Dupes = $(if(@($Theme.Name | Get-Unique).Count -gt 1) {$Theme.Name} else {$Theme.Path}) -join ", "
        Write-Warning "Found more than one theme for '$Name'. Using '$(@($Theme)[0].Path)', but you could try again for one of: $Dupes)"
    }
    @($Theme)[0]
}
#EndRegion '.\Private\FindVSCodeTheme.ps1'
#Region '.\Private\GetColorProperty.ps1' 0
function GetColorProperty{
    <#
        .SYNOPSIS
            Search the colors for a matching theme color name and returns the foreground
    #>

    param(
        # The array of colors
        [Array]$colors,

        # An array of (partial) scope names in priority order
        # The foreground color of the first matching scope in the tokens will be returned
        [string[]]$name
    )
    # Since we loaded the themes in order of prescedence, we take the first match that has a foreground color
    foreach ($pattern in $name) {
        # Normalize color
        if($foreground = @($colors.$pattern).Where{$_}[0]) {
            ConvertToCssColor $foreground
            return
        }
    }
}
#EndRegion '.\Private\GetColorProperty.ps1'
#Region '.\Private\GetColorScopeForeground.ps1' 0
function GetColorScopeForeground {
    <#
        .SYNOPSIS
            Search the tokens for a scope name with a foreground color
    #>

    param(
        # The array of tokens
        [Array]$tokens,

        # An array of (partial) scope names in priority order
        # The foreground color of the first matching scope in the tokens will be returned
        [string[]]$name
    )
    # Since we loaded the themes in order of prescedence, we take the first match that has a foreground color
    foreach ($pattern in $name) {
        foreach ($token in $tokens) {
            if (($token.scope -split "\s*,\s*" -match $pattern) -and $token.settings.foreground) {
                ConvertToCssColor $token.settings.foreground
                return
            }
        }
    }
}
#EndRegion '.\Private\GetColorScopeForeground.ps1'
#Region '.\Private\ImportJsonIncludeLast.ps1' 0
function ImportJsonIncludeLast {
    <#
        .SYNOPSIS
            Import VSCode json themes, including any included themes
    #>

    [CmdletBinding()]
    param([string[]]$Path)

    # take the first
    $themeFile, $Path = $Path
    $theme = Get-Content $themeFile | ConvertFrom-Json

    # Output all the colors or token colors
    if ($theme.colors) {
        $theme.colors
    }
    if ($theme.tokenColors) {
        $theme.tokenColors
    }

    # Recurse includes
    if ($theme.include) {
        $Path += $themeFile | Split-Path | Join-Path -Child $theme.include | convert-path
    }
    if ($Path) {
        ImportJsonIncludeLast $Path
    }
}
#EndRegion '.\Private\ImportJsonIncludeLast.ps1'
#Region '.\Private\ImportTheme.ps1' 0
function ImportTheme {
    <#
        .SYNOPSIS
            Imports themes by name
    #>

    [CmdletBinding()]
    param(
        # The name of the theme
        [Parameter(Position=0)]
        [string]$Name
    )

    $Name = $Name -replace "((\.theme)?\.psd1)?$" -replace '$', ".theme.psd1"

    $Path = if (!(Test-Path -LiteralPath $Name)) {
        Get-Theme $Name | Select-Object -First 1 -ExpandProperty PSPath
    } else {
        Convert-Path $Name
    }

    Write-Verbose "Importing $Name theme from $Path"
    $Theme = Import-Metadata $Path -ErrorAction Stop

    # Cast the ConsoleColors here
    if($Theme['ConsoleColors']) {
        $Theme['ConsoleColors'] = $Theme['ConsoleColors'].ForEach([RgbColor])
    }
    # Convert colors to escape sequences for PSReadline.Colors
    if ($Colors = $Theme.PSReadline.Colors) {
        foreach ($color in @($Colors.Keys)) {
            # If it doesn't start with ESC
            if ($Colors[$color] -notmatch "^$([char]27)") {
                try {
                    # Use the RGBColor
                    $Colors[$color] = ([RgbColor]$Colors[$color]).ToVtEscapeSequence() <# |
                        Add-Member -MemberType NoteProperty -Name Color -Value ([RgbColor]$Colors[$color]) -PassThru #>

                } catch {
                    Write-Warning "Skipped 'PSReadLine.$color', because '$($Colors[$color])' is neither a color nor an escape sequence"
                    $null = $Colors.Remove($color)
                }
            }
        }
        $Theme['PSReadline']['Colors'] = $Colors
    }

    $Theme
}
#EndRegion '.\Private\ImportTheme.ps1'
#Region '.\Public\ConvertFrom-iTermColors.ps1' 0
function ConvertFrom-iTermColors {
    <#
        .SYNOPSIS
            Convert a .itermcolors XML file into a partial theme
        .DESCRIPTION
            Generate a PANSIES PowerShell theme from iTermColors.
 
            For a collection of iTermColors files you can use, visit https:#ithub.com/mbadolato/iTerm2-Color-Schemes
 
        .EXAMPLE
            ConvertFrom-iTermColors Argonaut
 
            Will find the Argonaut.itermcolors file in the furrent directory (or in the module storage path)
     #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        # The name of (or full path to) an XML PList itermcolors scheme
        # If you provide just a name, will search for an .itermcolors file in the current folder and the theme
        [Alias("PSPath", "Name")]
        [Parameter(ValueFromPipelineByPropertyName)]
        [string]$Theme,

        [switch]$Force,

        [switch]$Update,

        [switch]$Passthru,

        [ValidateSet("User", "Machine")]
        [string]$Scope = "User"
    )

    begin {
        # In Windows Color Table order
        $PListColorNames = @(
            "Ansi 0 Color"   # DARK_BLACK
            "Ansi 4 Color"   # DARK_BLUE
            "Ansi 2 Color"   # DARK_GREEN
            "Ansi 6 Color"   # DARK_CYAN
            "Ansi 1 Color"   # DARK_RED
            "Ansi 5 Color"   # DARK_MAGENTA
            "Ansi 3 Color"   # DARK_YELLOW
            "Ansi 7 Color"   # DARK_WHITE
            "Ansi 8 Color"   # BRIGHT_BLACK
            "Ansi 12 Color"  # BRIGHT_BLUE
            "Ansi 10 Color"  # BRIGHT_GREEN
            "Ansi 14 Color"  # BRIGHT_CYAN
            "Ansi 9 Color"   # BRIGHT_RED
            "Ansi 13 Color"  # BRIGHT_MAGENTA
            "Ansi 11 Color"  # BRIGHT_YELLOW
            "Ansi 15 Color"  # BRIGHT_WHITE
        )
    }

    process {
        if (!(Test-Path $Theme)) {
            throw [System.IO.FileNotFoundException]::new("Palette file not found", $Theme)
        }

        if (!($pList = Import-PList $Theme)) {
            return
        }

        $ThemeOutput = @{
            Name = [IO.Path]::GetFileNameWithoutExtension($Theme)
            ConsoleColors = @(
                foreach($color in $PListColorNames) {
                    if(!$pList[$color]) {
                        Wait-Debugger
                        Write-Warning "Missing color $color"
                        $null
                    } else {
                        ConvertToCssColor $pList[$color]
                    }
                }
            )
        }
        if ($pList.ContainsKey("Foreground Color") -and $pList.ContainsKey("Background Color")) {
            $ThemeOutput.ConsoleForeground = ConvertToCssColor $pList["Foreground Color"]
            $ThemeOutput.ConsoleBackground = ConvertToCssColor $pList["Background Color"]
        }
        if ($pList.ContainsKey("Bold Color")) {
            if (!$ThemeOutput['PSReadLine']) {
                $ThemeOutput['PSReadLine'] = @{}
            }
            if (!$ThemeOutput['PSReadLine']['Colors']) {
                $ThemeOutput['PSReadLine']['Colors'] = @{}
            }
            $ThemeOutput['PSReadLine']['Colors']['Emphasis'] = ConvertToCssColor $pList["Bold Color"]
        }
        if ($pList.ContainsKey("Selection Color")) {
            if (!$ThemeOutput['PSReadLine']) {
                $ThemeOutput['PSReadLine'] = @{}
            }
            if (!$ThemeOutput['PSReadLine']['Colors']) {
                $ThemeOutput['PSReadLine']['Colors'] = @{}
            }
            $ThemeOutput['PSReadLine']['Colors']['Selection'] = ConvertToCssColor $pList["Selection Color"]
        }
        if(!($Name = $pList["name"])) {
            $Name = ([IO.Path]::GetFileNameWithoutExtension($Theme))
        }

        $ThemeOutput | ExportTheme -Name $Name -Passthru:$Passthru -Scope:$Scope -Force:$Force -Update:$Update
    }
}
#EndRegion '.\Public\ConvertFrom-iTermColors.ps1'
#Region '.\Public\ConvertFrom-VSCodeTheme.ps1' 0
function ConvertFrom-VSCodeTheme {
    <#
        .SYNOPSIS
            Convert a VSCode Theme file into a partial theme
        .DESCRIPTION
            Attempts to generate a PANSIES Powershell theme from a VSCode Theme.
 
            This feature is still experimental, and so far, includes theming:
 
            - The ConsoleColor palette (the base 16 ConsoleColors) from the `terminal.ansi*` colors in the VSCode theme
            - The PSReadLine colors (requires PSReadline 2.0.0 beta 2) from various named scopes
            - The PrivateData ConsoleColors (used for foreground of the output streams: Verbose, Error, Warning, Debug, Progress) from various named colors.
 
            NOTE: for now, even if everything works, there may be some colors for PSReadLine that aren't set, or that are set incorrectly (depending on the theme). If so, please let me know of themes you want to use or of colors that are wrong in the issues at https://GitHub.com/PoshCode/PANSIES/issues
        .EXAMPLE
            ConvertFrom-VSCodeTheme Dark+
            Import-Theme Dark+
 
            # This example shows how to convert the VSCode Dark+ default theme and then use it in your console.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        # The name of (or full path to) a vscode json theme
        # E.g. 'Dark+' or 'Monokai'
        [Alias("PSPath", "Name")]
        [Parameter(ValueFromPipelineByPropertyName)]
        [string]$Theme,

        # Overwrite any existing theme
        [switch]$Force,

        [switch]$Update,

        # Output the theme after importing it
        [switch]$Passthru,

        [ValidateSet("User", "Machine")]
        [string]$Scope = "User"
    )
    $ThemeSource = FindVsCodeTheme $Theme -ErrorAction Stop

    Write-Verbose "Importing $($ThemeSource.Path)"
    if($PSCmdlet.ShouldProcess($ThemeSource.Path, "Convert to $($ThemeSource.Name).theme.psd1")) {
        # Load the theme file and split the output into colors and tokencolors
        if($ThemeSource.Path.endswith(".json")) {
            $colors, $tokens = (ImportJsonIncludeLast $ThemeSource.Path).Where( {!$_.scope}, 'Split', 2)
        } else {
            $colors, $tokens = (Import-PList $ThemeSource.Path).settings.Where( {!$_.scope}, 'Split', 2)
            $colors = $colors.settings
        }

        # these should come from the colors, rather than the token scopes
        $DefaultTokenColor = GetColorProperty $colors 'editor.foreground', 'foreground', 'terminal.foreground'
        $SelectionColor = GetColorProperty $colors 'editor.selectionBackground', 'editor.selectionHighlightBackground', 'selection'
        $ErrorColor = @(@(GetColorProperty $colors 'errorForeground', 'editorError.foreground') + @(GetColorScopeForeground $tokens 'invalid'))[0]

        # I'm going to need some help figuring out what the best mappings are
        $CommandColor = GetColorScopeForeground $tokens 'support.function'
        $CommentColor = GetColorScopeForeground $tokens 'comment'
        $ContinuationPromptColor = GetColorScopeForeground $tokens 'constant.character'
        $EmphasisColor = GetColorScopeForeground $tokens 'markup.bold','markup.italic','emphasis','strong','constant.other.color', 'markup.heading'
        $KeywordColor = GetColorScopeForeground $tokens '^keyword.control$', '^keyword$', 'keyword.control', 'keyword'
        $MemberColor = GetColorScopeForeground $tokens 'variable.other.object.property', 'member', 'type.property', 'support.function.any-method', 'entity.name.function'
        $NumberColor = GetColorScopeForeground $tokens 'constant.numeric'
        $OperatorColor = GetColorScopeForeground $tokens 'keyword.operator$', 'keyword'
        $ParameterColor = GetColorScopeForeground $tokens 'parameter'
        $StringColor = GetColorScopeForeground $tokens '^string$'
        $TypeColor = GetColorScopeForeground $tokens '^storage.type$','^support.class$', '^entity.name.type.class$', '^entity.name.type$'
        $VariableColor = GetColorScopeForeground $tokens '^variable$', '^entity.name.variable$', '^variable.other$'


        $ThemeOutput = [Ordered]@{
            Name       = $ThemeSource.Name
            PSReadLine = @{
                Colors = @{
                    Command =            $CommandColor
                    Comment =            $CommentColor
                    ContinuationPrompt = $ContinuationPromptColor
                    DefaultToken =       $DefaultTokenColor
                    Emphasis =           $EmphasisColor
                    Error =              $ErrorColor
                    Keyword =            $KeywordColor
                    Member =             $MemberColor
                    Number =             $NumberColor
                    Operator =           $OperatorColor
                    Parameter =          $ParameterColor
                    Selection =          $SelectionColor
                    String =             $StringColor
                    Type =               $TypeColor
                    Variable =           $VariableColor
                }
            }
        }

        # If the VSCode Theme has terminal colors, export those
        if ($colors.'terminal.ansiBrightYellow') {
            Write-Verbose "Exporting ConsoleColors"
            $ThemeOutput['ConsoleColors'] = @(
                    GetColorProperty $colors "terminal.ansiBlack"
                    GetColorProperty $colors "terminal.ansiRed"
                    GetColorProperty $colors "terminal.ansiGreen"
                    GetColorProperty $colors "terminal.ansiYellow"
                    GetColorProperty $colors "terminal.ansiBlue"
                    GetColorProperty $colors "terminal.ansiMagenta"
                    GetColorProperty $colors "terminal.ansiCyan"
                    GetColorProperty $colors "terminal.ansiWhite"
                    GetColorProperty $colors "terminal.ansiBrightBlack"
                    GetColorProperty $colors "terminal.ansiBrightRed"
                    GetColorProperty $colors "terminal.ansiBrightGreen"
                    GetColorProperty $colors "terminal.ansiBrightYellow"
                    GetColorProperty $colors "terminal.ansiBrightBlue"
                    GetColorProperty $colors "terminal.ansiBrightMagenta"
                    GetColorProperty $colors "terminal.ansiBrightCyan"
                    GetColorProperty $colors "terminal.ansiBrightWhite"
                )
            if ($colors."terminal.background") {
                $ThemeOutput['ConsoleBackground'] = GetColorProperty $colors "terminal.background"
            }
            if ($colors."terminal.foreground") {
                $ThemeOutput['ConsoleForeground'] = GetColorProperty $colors "terminal.foreground"
            }
        }

        if (GetColorProperty $colors 'editorWarning.foreground') {
            $ThemeOutput['Host'] = @{
                'PrivateData' = @{
                    WarningForegroundColor  = GetColorProperty $colors 'editorWarning.foreground'
                    ErrorForegroundColor = GetColorProperty $Colors 'editorError.foreground'
                    VerboseForegroundColor = GetColorProperty $Colors 'editorInfo.foreground'
                    ProgressForegroundColor = GetColorProperty $Colors 'notifications.foreground'
                    ProgressBackgroundColor = GetColorProperty $Colors 'notifications.background'
                }
            }
        }

        if ($DebugPreference -in "Continue", "Inquire") {
            $global:colors = $colors
            $global:tokens = $tokens
            $global:Theme = $ThemeOutput
            ${function:global:Get-VSColorScope} = ${function:GetColorScopeForeground}
            ${function:global:Get-VSColor} = ${function:GetColorProperty}
            Write-Debug "For debugging, `$Theme, `$Colors, `$Tokens were copied to global variables, and Get-VSColor and Get-VSColorScope exported."
        }

        if ($ThemeOutput.PSReadLine.Colors.Values -contains $null) {
            Write-Warning "Some PSReadLine color values not set in '$($ThemeSource.Path)'"
        }

        $ThemeOutput | ExportTheme -Name $ThemeSource.Name -Passthru:$Passthru -Scope:$Scope -Force:$Force -Update:$Update
    }
}
#EndRegion '.\Public\ConvertFrom-VSCodeTheme.ps1'
#Region '.\Public\Export-PList.ps1' 0
function Export-PList {
    <#
        .SYNOPSIS
            Convert an object to an XML or Binary PList file.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        # The object(s) to convert
        [Parameter(Mandatory, ValueFromPipeline)]
        [Alias("io")]
        [Object[]]$InputObject,

        # The path to an XML or binary plist file (e.g. a .tmTheme file)
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0)]
        [Alias("PSPath")]
        [string]$Path,

        [switch]$Binary
    )

    begin {
        $Output = @()
    }
    process {
        $Output += $InputObject
    }
    end {
        $Parent = if($Parent = Split-Path $Path) {
            if(!(Test-Path -LiteralPath $Parent -PathType Container)) {
                New-Item -ItemType Directory -Path $Parent -Force
            }
            Convert-Path $Parent
        } else {
            Convert-Path (Get-Location -PSProvider FileSystem)
        }
        $Path = Join-Path $Parent -ChildPath (Split-Path $Path -Leaf)

        if($PSCmdlet.ShouldProcess($Path,"Export InputObject as PList $(if($Binary){'Binary'}else{'Xml'})") ) {
            if ($Binary) {
                [PoshCode.Pansies.Parsers.Plist]::WriteBinary($Output, $Path)
            } else {
                [PoshCode.Pansies.Parsers.Plist]::WriteXml($Output, $Path)
            }
        }
    }
}
#EndRegion '.\Public\Export-PList.ps1'
#Region '.\Public\Export-Theme.ps1' 0
function Export-Theme {
    # .EXTERNALHELP Pansies-help.xml
    [CmdletBinding()]
    param(
        # The name of the theme to export the current settings to
        [Parameter(Position = 0, Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$Name,

        [string]$Update,

        [switch]$Force,

        [switch]$Passthru,

        [ValidateSet("User", "Machine")]
        [string]$Scope = "User"
    )
    end {
        if($Update) {
            $Theme = ImportTheme $Name -ErrorAction SilentlyContinue
        }
        if(!$Theme) {
            $Theme = @{}
        }
        $Theme = $Theme | Update-Object @{
            ConsoleColors = (Get-ConsolePalette).ForEach({$_.ToString()})
        }

        $Theme = $Theme | Update-Object @{
            PSReadLine = @{
                # TODO: convert escape sequences to RGBColor values (e.g. 30's and 90's to colors, including 38;2;R;G;B;)
                Colors = ConvertFrom-Metadata (Get-PSReadLineOption | Select-Object *Color | ConvertTo-Metadata -AsHashtable)
            }
        }

        if($Host.Name -eq "ConsoleHost") {

            $Theme = $Theme | Update-Object @{
                Host = @{
                    PrivateData = ConvertFrom-Metadata ($Host.PrivateData | Select-Object * | ConvertTo-Metadata -AsHashtable)
                }
            }
        }

        $Theme | ExportTheme -Name $Name -Passthru:$Passthru -Scope:$Scope -Force:$Force
    }
}
#EndRegion '.\Public\Export-Theme.ps1'
#Region '.\Public\Get-Complement.ps1' 0
function Get-Complement {
    <#
    .EXTERNALHELP Pansies-help.xml
    #>

    [CmdletBinding()]
    [OutputType([PoshCode.Pansies.RgbColor])]
    param(
        # The source color to calculate the complement of
        [Parameter(ValueFromPipeline, Mandatory)]
        [PoshCode.Pansies.RgbColor]$Color,

        # Force the luminance to have "enough" contrast
        [switch]$ForceContrast,

        # Assume there are only 16 colors
        [switch]$ConsoleColor,

        # If set, output the input $Color before the complement
        [switch]$Passthru
    )
    process {
        if($ConsoleColor) {
            $Color = $Color.ConsoleColor
        }

        if($Passthru) { $Color }

        if($ConsoleColor) {
            if($Color.ToHunterLab().L -lt 50) {
                [PoshCode.Pansies.RgbColor][ConsoleColor]::White
            } else {
                [PoshCode.Pansies.RgbColor][ConsoleColor]::Black
            }
        } else {
            $Hsl = $Color.ToHsl()
            $Hsl.H = ($Hsl.H + 180) % 360

            if($ForceContrast) {
                $Lab = $Hsl.ToHunterLab()
                $Source = $Color.ToHunterLab()
                $Lab.L = ($Source.L + 50) % 100
                [PoshCode.Pansies.RgbColor]$Lab
            } else {
                [PoshCode.Pansies.RgbColor]$Hsl
            }
        }
    }
}
#EndRegion '.\Public\Get-Complement.ps1'
#Region '.\Public\Get-Gradient.ps1' 0
function Get-Gradient {
    <#
    .EXTERNALHELP Pansies-help.xml
    #>

    [CmdletBinding()]
    [OutputType([PoshCode.Pansies.RgbColor[][]],[PoshCode.Pansies.RgbColor[]])]
    param(
        [Parameter(Mandatory, Position=0)]
        [PoshCode.Pansies.RgbColor]$StartColor,

        [Parameter(Mandatory, Position=1)]
        [PoshCode.Pansies.RgbColor]$EndColor,

        [Parameter(Position=2)]
        [Alias("Length","Count","Steps")]
        [int]$Width = $Host.UI.RawUI.WindowSize.Width,

        [Parameter(Position=3)]
        [int]$Height = 1,
        [ValidateSet("CMY","CMYK","LAB","LCH","LUV","HunterLAB","HSL","HSV","HSB","RGB","XYZ","YXY")]
        $ColorSpace = "HunterLab",

        [switch]$Reverse,

        [switch]$Flatten
    )

    $Height = [Math]::Max(1, $Height)
    $Width = [Math]::Max(1, $Width)
    $Colors = new-object PoshCode.Pansies.RgbColor[][] $Height, $Width

    # Simple pythagorean distance
    $Size = [Math]::Sqrt(($Height - 1) * ($Height - 1) + ($Width - 1) * ($Width - 1))

    $Left = $StartColor."To$ColorSpace"()
    $Right = $EndColor."To$ColorSpace"()
    $StepSize = New-Object "${ColorSpace}Color" -Property @{
        Ordinals = $(
            foreach ($i in 0..($Left.Ordinals.Count-1)) {
                ($Right.Ordinals[$i] - $Left.Ordinals[$i]) / $Size
            }
        )
    }
    Write-Verbose "Size: $('{0:N2}' -f $Size) ($Width x $Height) ($($Colors.Length) x $($Colors[0].Length))"
    Write-Verbose "Diff: {$StepSize}"
    Write-Verbose "From: {$Left} $($StartColor.Ordinals)"
    Write-Verbose "To: {$Right} $($EndColor.Ordinals)"
    # For colors based on hue rotation, the math is slightly more complex:
    [bool]$RotatingColor = $StepSize | Get-Member H

    if($RotatingColor) {
        [Bool]$Change = [Math]::Abs($StepSize.H) -ge 180 / $Size
        if ($Reverse) { $Change = !$Change }
        if ($Change) {
            $StepSize.H = if ($StepSize.H -gt 0) {
                $StepSize.H - 360 / $Size
            } else {
                $StepSize.H + 360 / $Size
            }
        }
        $Ceiling = 360
    }

    for ($Line = 1; $Line -le $Height; $Line++) {
        for ($Column = 1; $Column -le $Width; $Column++) {
            $D = [Math]::Sqrt(($Line - 1) * ($Line - 1) + ($Column - 1) * ($Column - 1))

            $StepColor = New-Object "${ColorSpace}Color" -Property @{
                Ordinals = $(
                    foreach ($i in 0..$Left.Ordinals.Count) {
                        $Left.Ordinals[$i] + $StepSize.Ordinals[$i] * $D
                    }
                )
            }

            # For colors based on hue rotation, the math is slightly more complex:
            if($RotatingColor) {
                if($StepColor.H -lt 0) {
                    $StepColor.H += 360
                }
                $StepColor.H %= $Ceiling
            }
            $Ordinals1 = $StepColor.Ordinals

            $Colors[$Line - 1][$Column - 1] = $StepColor.ToRgb()
            $Ordinals2 = $Colors[$Line - 1][$Column - 1].Ordinals
            Write-Debug ("Step ${Line},${Column}: {0:N2}, {1:N2}, {2:N2} => {3:N2}, {4:N2}, {5:N2}" -f $Ordinals1[0], $Ordinals1[1], $Ordinals1[2], $Ordinals2[0], $Ordinals2[1], $Ordinals2[2] )
        }
    }

    if ($Flatten) {
        $Colors.GetEnumerator().GetEnumerator()
    } else {
        ,$Colors
    }

}
#EndRegion '.\Public\Get-Gradient.ps1'
#Region '.\Public\Get-Theme.ps1' 0
function Get-Theme {
    <#
        .SYNOPSIS
            List available PANSIES themes, optionally filtering
    #>

    [CmdletBinding()]
    param(
        # The name of the theme(s) to show. Supports wildcards, and defaults to * everything.
        [string]$Name = "*",

        # If set, only returns themes that include ConsoleColor
        [switch]$ConsoleColors,

        # If set, only returns themes that include PSReadline Colors
        [switch]$PSReadline
    )

    $Name = $Name -replace "((\.theme)?\.psd1)?$" -replace '$', ".theme.psd1"

    foreach($Theme in Join-Path $(
            Get-ConfigurationPath -Scope User -SkipCreatingFolder
            Get-ConfigurationPath -Scope Machine -SkipCreatingFolder
        ) -ChildPath $Name -Resolve -ErrorAction Ignore ) {
            if ($ConsoleColors -or $PSReadline) {
                $ThemeData = Import-Metadata -Path $Theme
                if($ConsoleColors -and !$ThemeData.ConsoleColors) {
                    continue
                }
                if($PSReadline -and !$ThemeData.PSReadline) {
                    continue
                }
            }
            $Name = if($ThemeData.Name) {
                $ThemeData.Name
            } else {
                [IO.Path]::GetFileName($Theme) -replace "\.theme\.psd1$"
            }
            $Name | Add-Member NoteProperty PSPath $Theme -PassThru |
                    Add-Member NoteProperty Name $Name -PassThru
    }
}
#EndRegion '.\Public\Get-Theme.ps1'
#Region '.\Public\Import-PList.ps1' 0
function Import-PList {
    <#
        .SYNOPSIS
            Convert an XML or Binary PList (property list) file to objects (arrays, string->object dictionaries, etc).
    #>

    [CmdletBinding()]
    param(
        # The path to an XML or binary plist file (e.g. a .tmTheme file)
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)]
        [Alias("PSPath")]
        [string]$Path
    )

    process {
        $Path = Convert-Path $Path

        [PoshCode.Pansies.Parsers.Plist]::ReadPlist($Path)
    }
}
#EndRegion '.\Public\Import-PList.ps1'
#Region '.\Public\Import-Theme.ps1' 0
function Import-Theme {
    <#
        .SYNOPSIS
            Import a theme file to style your console window and PowerShell session
    #>

    [CmdletBinding()]
    param(
        # A theme to import (can be the name of an installed PANSIES theme, or the full path to a psd1 file)
        [string]$Name,

        # By default, imported themes will update the default console colors (if they define console colors)
        # If SkipDefault is set, Import-Theme will leave the default Console Colors alone
        [switch]$SkipDefault
    )
    $Theme = ImportTheme $Name

    if ($ConsoleColors = $Theme.ConsoleColors) {
        Write-Verbose "Setting the console palette"
        Set-ConsolePalette -Colors $ConsoleColors -Default:!$SkipDefault
    }

    if ($PSReadLine = $Theme.PSReadLine) {
        Write-Verbose "Applying PSReadLineOptions"
        Set-PSReadLineOption @PSReadLine
    }

    if ($HostSettings = $Theme.Host) {
        function Update-Host {
            param($HostObject, $Settings)
            foreach ($key in $Settings.Keys) {
                try {
                    Write-Verbose "Applying Host settings: $key"
                    if ($HostObject.$key -is [ConsoleColor]) {
                        Write-Verbose "Converting '$($Settings.$Key)' to ConsoleColor"
                        $HostObject.$key = [ConsoleColor]([RgbColor]::ConsolePalette.FindClosestColorIndex([RgbColor]::new($Settings.$Key)))
                    } elseif ($Settings.$key -is [hashtable]) {
                        Update-Host $HostObject.$key $Settings.$Key
                    } else {
                        $HostObject.$key = $Settings.$Key
                    }
                } catch {
                    Write-Warning "Failed to apply Host '$key' = '$($Settings.$Key)' (it should have been a $($HostObject.$key.GetType().FullName)"
                }
            }
        }
        Update-Host $Host $HostSettings
    }
}
#EndRegion '.\Public\Import-Theme.ps1'
#Region '.\Public\Show-Theme.ps1' 0
function Show-Theme {
    [OutputType([string])]
    [CmdletBinding(DefaultParameterSetName="CurrentTheme")]
    param(
        [Alias("Theme","PSPath")]
        [Parameter(ValueFromPipelineByPropertyName)]
        [string]$Name,

        [Switch]$Tiny,

        [Switch]$MoreText,

        [Switch]$NoCodeSample
    )
    process {
        $e = [char]27
        foreach($Theme in Get-Theme $Name) {
            $Theme = ImportTheme $Theme.PSPath | Add-Member -Type NoteProperty -Name Name -Value $Theme.Name -Passthru -Force
            $Palette = $Theme.ConsoleColors

            -join $(
                "$($Theme.Name)`n"
                if($Palette) {
                    if($Palette.Count -lt 16) {
                        throw "Invalid theme: missing ConsoleColors (there should be 16, but are only $($Palette.Count))"
                    }

                    if (!$Tiny) {
                        $ansi = 30
                        $bold = $false
                        " Black Red Green Yellow Blue Magenta Cyan White Gray Dark Gray `n"
                        " 49m 40m 41m 42m 43m 44m 45m 46m 47m 100m 107m `n"
                        " 39m"

                        foreach($fg in @($null) + $Palette[0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15]) {
                            if($fg) {
                                "$(if($bold){"1;"}else{" "})$($ansi)m"
                                $ansi += $bold
                                $bold = !$bold
                            }
                            foreach($bg in @($null) + $Palette[0, 4, 2, 6, 1, 5, 3, 15, 7, 8]) {
                                $(if($null -ne $bg) { $bg.ToVtEscapeSequence($true) })
                                $(if($null -ne $fg) { $fg.ToVtEscapeSequence() })
                                " gYw $([char]27)[0m "
                            }
                            "`n"
                        }
                        "`n"
                    }

                    0..7 | ForEach-Object {
                        $Dark = $Palette[$_]
                        $Lite = $Palette[($_ + 8)]

                        New-Text " $([ConsoleColor]$_) $Dark ".PadRight(22) -Fore (Get-Complement $Dark -Force) -Back $Dark -LeaveColor
                        New-Text " $([ConsoleColor]$_+8) $Lite ".PadRight(18) -Fore (Get-Complement $Lite -Force) -Back $Lite
                        "`n"
                    }
                }
                if(($Syntax = $Theme.PSReadLine.Colors) -and !$NoCodeSample) {
                    "$e[8A$e[45G$($Syntax.Keyword)function $($Syntax.DefaultToken)Test-Syntax $($Syntax.DefaultToken){"
                    "$e[B$e[45G $($Syntax.Comment)# Demo Syntax Highlighting"
                    "$e[B$e[45G $($Syntax.DefaultToken)[$($Syntax.Type)CmdletBinding$($Syntax.DefaultToken)()]"
                    "$e[B$e[45G $($Syntax.Keyword)param$($Syntax.DefaultToken)( [$($Syntax.Type)IO.FileInfo$($Syntax.DefaultToken)]$($Syntax.Variable)`$Path $($Syntax.DefaultToken))"
                    "$e[B"
                    "$e[B$e[45G $($Syntax.Command)Write-Verbose $($Syntax.String)`"Testing in $($Syntax.Variable)`$($($Syntax.Command)Split-Path $($Syntax.Variable)`$PSScriptRoot $($Syntax.Parameter)-Leaf$($Syntax.Variable))$($Syntax.String)`" $($Syntax.Parameter)-Verbose"
                    "$e[B$e[45G $($Syntax.Variable)`$Env:PSModulePath $($Syntax.Operator)-split $($Syntax.String)';' $($Syntax.Operator)-notcontains $($Syntax.Variable)`$Path$($Syntax.DefaultToken).$($Syntax.Member)FullName"
                    "$e[B$e[45G$($Syntax.DefaultToken)}$e[39m$e[B"
                }

                if($Palette -and $MoreText) {
                    0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 | ForEach-Object {
                        $Color = $Palette[$_]
                        New-Text " This is a test " -Back Black -Fore $Color -LeaveColor
                        New-Text " showing more " -Back DarkGray -Fore $Color -LeaveColor
                        New-Text " sample text on " -Back Gray -Fore $Color -LeaveColor
                        New-Text " common backgrounds: " -Back White -Fore $Color -LeaveColor
                        New-Text " $Color $([ConsoleColor]$_) ".PadRight(22) -Fore $(if($_ -le 8 -and $_ -ne 7){"White"}else{"Black"}) -Back $Color
                        "`n"
                    }
                    "`n"
                }
            )
        }
    }
}
#EndRegion '.\Public\Show-Theme.ps1'

# SIG # Begin signature block
# MIIXzgYJKoZIhvcNAQcCoIIXvzCCF7sCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUkPdNUPcIbqW6IiDM8Pdo518v
# VnCgghMBMIID7jCCA1egAwIBAgIQfpPr+3zGTlnqS5p31Ab8OzANBgkqhkiG9w0B
# AQUFADCBizELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTEUMBIG
# A1UEBxMLRHVyYmFudmlsbGUxDzANBgNVBAoTBlRoYXd0ZTEdMBsGA1UECxMUVGhh
# d3RlIENlcnRpZmljYXRpb24xHzAdBgNVBAMTFlRoYXd0ZSBUaW1lc3RhbXBpbmcg
# Q0EwHhcNMTIxMjIxMDAwMDAwWhcNMjAxMjMwMjM1OTU5WjBeMQswCQYDVQQGEwJV
# UzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xMDAuBgNVBAMTJ1N5bWFu
# dGVjIFRpbWUgU3RhbXBpbmcgU2VydmljZXMgQ0EgLSBHMjCCASIwDQYJKoZIhvcN
# AQEBBQADggEPADCCAQoCggEBALGss0lUS5ccEgrYJXmRIlcqb9y4JsRDc2vCvy5Q
# WvsUwnaOQwElQ7Sh4kX06Ld7w3TMIte0lAAC903tv7S3RCRrzV9FO9FEzkMScxeC
# i2m0K8uZHqxyGyZNcR+xMd37UWECU6aq9UksBXhFpS+JzueZ5/6M4lc/PcaS3Er4
# ezPkeQr78HWIQZz/xQNRmarXbJ+TaYdlKYOFwmAUxMjJOxTawIHwHw103pIiq8r3
# +3R8J+b3Sht/p8OeLa6K6qbmqicWfWH3mHERvOJQoUvlXfrlDqcsn6plINPYlujI
# fKVOSET/GeJEB5IL12iEgF1qeGRFzWBGflTBE3zFefHJwXECAwEAAaOB+jCB9zAd
# BgNVHQ4EFgQUX5r1blzMzHSa1N197z/b7EyALt0wMgYIKwYBBQUHAQEEJjAkMCIG
# CCsGAQUFBzABhhZodHRwOi8vb2NzcC50aGF3dGUuY29tMBIGA1UdEwEB/wQIMAYB
# Af8CAQAwPwYDVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NybC50aGF3dGUuY29tL1Ro
# YXd0ZVRpbWVzdGFtcGluZ0NBLmNybDATBgNVHSUEDDAKBggrBgEFBQcDCDAOBgNV
# HQ8BAf8EBAMCAQYwKAYDVR0RBCEwH6QdMBsxGTAXBgNVBAMTEFRpbWVTdGFtcC0y
# MDQ4LTEwDQYJKoZIhvcNAQEFBQADgYEAAwmbj3nvf1kwqu9otfrjCR27T4IGXTdf
# plKfFo3qHJIJRG71betYfDDo+WmNI3MLEm9Hqa45EfgqsZuwGsOO61mWAK3ODE2y
# 0DGmCFwqevzieh1XTKhlGOl5QGIllm7HxzdqgyEIjkHq3dlXPx13SYcqFgZepjhq
# IhKjURmDfrYwggSjMIIDi6ADAgECAhAOz/Q4yP6/NW4E2GqYGxpQMA0GCSqGSIb3
# DQEBBQUAMF4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3Jh
# dGlvbjEwMC4GA1UEAxMnU3ltYW50ZWMgVGltZSBTdGFtcGluZyBTZXJ2aWNlcyBD
# QSAtIEcyMB4XDTEyMTAxODAwMDAwMFoXDTIwMTIyOTIzNTk1OVowYjELMAkGA1UE
# BhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMTQwMgYDVQQDEytT
# eW1hbnRlYyBUaW1lIFN0YW1waW5nIFNlcnZpY2VzIFNpZ25lciAtIEc0MIIBIjAN
# BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAomMLOUS4uyOnREm7Dv+h8GEKU5Ow
# mNutLA9KxW7/hjxTVQ8VzgQ/K/2plpbZvmF5C1vJTIZ25eBDSyKV7sIrQ8Gf2Gi0
# jkBP7oU4uRHFI/JkWPAVMm9OV6GuiKQC1yoezUvh3WPVF4kyW7BemVqonShQDhfu
# ltthO0VRHc8SVguSR/yrrvZmPUescHLnkudfzRC5xINklBm9JYDh6NIipdC6Anqh
# d5NbZcPuF3S8QYYq3AhMjJKMkS2ed0QfaNaodHfbDlsyi1aLM73ZY8hJnTrFxeoz
# C9Lxoxv0i77Zs1eLO94Ep3oisiSuLsdwxb5OgyYI+wu9qU+ZCOEQKHKqzQIDAQAB
# o4IBVzCCAVMwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAO
# BgNVHQ8BAf8EBAMCB4AwcwYIKwYBBQUHAQEEZzBlMCoGCCsGAQUFBzABhh5odHRw
# Oi8vdHMtb2NzcC53cy5zeW1hbnRlYy5jb20wNwYIKwYBBQUHMAKGK2h0dHA6Ly90
# cy1haWEud3Muc3ltYW50ZWMuY29tL3Rzcy1jYS1nMi5jZXIwPAYDVR0fBDUwMzAx
# oC+gLYYraHR0cDovL3RzLWNybC53cy5zeW1hbnRlYy5jb20vdHNzLWNhLWcyLmNy
# bDAoBgNVHREEITAfpB0wGzEZMBcGA1UEAxMQVGltZVN0YW1wLTIwNDgtMjAdBgNV
# HQ4EFgQURsZpow5KFB7VTNpSYxc/Xja8DeYwHwYDVR0jBBgwFoAUX5r1blzMzHSa
# 1N197z/b7EyALt0wDQYJKoZIhvcNAQEFBQADggEBAHg7tJEqAEzwj2IwN3ijhCcH
# bxiy3iXcoNSUA6qGTiWfmkADHN3O43nLIWgG2rYytG2/9CwmYzPkSWRtDebDZw73
# BaQ1bHyJFsbpst+y6d0gxnEPzZV03LZc3r03H0N45ni1zSgEIKOq8UvEiCmRDoDR
# EfzdXHZuT14ORUZBbg2w6jiasTraCXEQ/Bx5tIB7rGn0/Zy2DBYr8X9bCT2bW+IW
# yhOBbQAuOA2oKY8s4bL0WqkBrxWcLC9JG9siu8P+eJRRw4axgohd8D20UaF5Mysu
# e7ncIAkTcetqGVvP6KUwVyyJST+5z3/Jvz4iaGNTmr1pdKzFHTx/kuDDvBzYBHUw
# ggUwMIIEGKADAgECAhAECRgbX9W7ZnVTQ7VvlVAIMA0GCSqGSIb3DQEBCwUAMGUx
# CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
# dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9v
# dCBDQTAeFw0xMzEwMjIxMjAwMDBaFw0yODEwMjIxMjAwMDBaMHIxCzAJBgNVBAYT
# AlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2Vy
# dC5jb20xMTAvBgNVBAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNp
# Z25pbmcgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD407Mcfw4R
# r2d3B9MLMUkZz9D7RZmxOttE9X/lqJ3bMtdx6nadBS63j/qSQ8Cl+YnUNxnXtqrw
# nIal2CWsDnkoOn7p0WfTxvspJ8fTeyOU5JEjlpB3gvmhhCNmElQzUHSxKCa7JGnC
# wlLyFGeKiUXULaGj6YgsIJWuHEqHCN8M9eJNYBi+qsSyrnAxZjNxPqxwoqvOf+l8
# y5Kh5TsxHM/q8grkV7tKtel05iv+bMt+dDk2DZDv5LVOpKnqagqrhPOsZ061xPeM
# 0SAlI+sIZD5SlsHyDxL0xY4PwaLoLFH3c7y9hbFig3NBggfkOItqcyDQD2RzPJ6f
# pjOp/RnfJZPRAgMBAAGjggHNMIIByTASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1Ud
# DwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDAzB5BggrBgEFBQcBAQRtMGsw
# JAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcw
# AoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElE
# Um9vdENBLmNydDCBgQYDVR0fBHoweDA6oDigNoY0aHR0cDovL2NybDQuZGlnaWNl
# cnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDA6oDigNoY0aHR0cDov
# L2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDBP
# BgNVHSAESDBGMDgGCmCGSAGG/WwAAgQwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93
# d3cuZGlnaWNlcnQuY29tL0NQUzAKBghghkgBhv1sAzAdBgNVHQ4EFgQUWsS5eyoK
# o6XqcQPAYPkt9mV1DlgwHwYDVR0jBBgwFoAUReuir/SSy4IxLVGLp6chnfNtyA8w
# DQYJKoZIhvcNAQELBQADggEBAD7sDVoks/Mi0RXILHwlKXaoHV0cLToaxO8wYdd+
# C2D9wz0PxK+L/e8q3yBVN7Dh9tGSdQ9RtG6ljlriXiSBThCk7j9xjmMOE0ut119E
# efM2FAaK95xGTlz/kLEbBw6RFfu6r7VRwo0kriTGxycqoSkoGjpxKAI8LpGjwCUR
# 4pwUR6F6aGivm6dcIFzZcbEMj7uo+MUSaJ/PQMtARKUT8OZkDCUIQjKyNookAv4v
# cn4c10lFluhZHen6dGRrsutmQ9qzsIzV6Q3d9gEgzpkxYz0IGhizgZtPxpMQBvwH
# gfqL2vmCSfdibqFT+hKUGIUukpHqaGxEMrJmoecYpJpkUe8wggUwMIIEGKADAgEC
# AhALDZkX0sdOvwJhwzQTbV+7MA0GCSqGSIb3DQEBCwUAMHIxCzAJBgNVBAYTAlVT
# MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
# b20xMTAvBgNVBAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25p
# bmcgQ0EwHhcNMTgwNzEyMDAwMDAwWhcNMTkwNzE2MTIwMDAwWjBtMQswCQYDVQQG
# EwJVUzERMA8GA1UECBMITmV3IFlvcmsxFzAVBgNVBAcTDldlc3QgSGVucmlldHRh
# MRgwFgYDVQQKEw9Kb2VsIEguIEJlbm5ldHQxGDAWBgNVBAMTD0pvZWwgSC4gQmVu
# bmV0dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMJb3Cf3n+/pFJiO
# hQqN5m54FpyIktMRWe5VyF8465BnAtzw3ivMyN+3k8IoXQhMxpCsY1TJbLyydNR2
# QzwEEtGfcTVnlAJdFFlBsgIdK43waaML5EG7tzNJKhHQDiN9bVhLPTXrit80eCTI
# RpOA7435oVG8erDpxhJUK364myUrmSyF9SbUX7uE09CJJgtB7vqetl4G+1j+iFDN
# Xi3bu1BFMWJp+TtICM+Zc5Wb+ZaYAE6V8t5GCyH1nlAI3cPjqVm8y5NoynZTfOhV
# bHiV0QI2K5WrBBboR0q6nd4cy6NJ8u5axi6CdUhnDMH20NN2I0v+2MBkgLAzxPrX
# kjnaEGECAwEAAaOCAcUwggHBMB8GA1UdIwQYMBaAFFrEuXsqCqOl6nEDwGD5LfZl
# dQ5YMB0GA1UdDgQWBBTiwur/NVanABEKwjZDB3g6SZN1mTAOBgNVHQ8BAf8EBAMC
# B4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwdwYDVR0fBHAwbjA1oDOgMYYvaHR0cDov
# L2NybDMuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC1jcy1nMS5jcmwwNaAzoDGG
# L2h0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3VyZWQtY3MtZzEuY3Js
# MEwGA1UdIARFMEMwNwYJYIZIAYb9bAMBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v
# d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCAYGZ4EMAQQBMIGEBggrBgEFBQcBAQR4MHYw
# JAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBOBggrBgEFBQcw
# AoZCaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0U0hBMkFzc3Vy
# ZWRJRENvZGVTaWduaW5nQ0EuY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQEL
# BQADggEBADNNHuRAdX0ddONqaUf3H3pwa1K016C02P90xDIyMvw+hiUb4Z/xewnY
# jyplspD0NQB9ca2pnNIy1KwjJryRgq8gl3epSiWTbViVn6VDK2h0JXm54H6hczQ8
# sEshCW53znNVUUUfxGsVM9kMcwITHYftciW0J+SsGcfuuAIuF1g47KQXKWOMcUQl
# yrP5t0ywotTVcg/1HWAPFE0V0sFy+Or4n81+BWXOLaCXIeeryLYncAVUBT1DI6lk
# peRUj/99kkn+hz1q4hHTtfNpMTOApP64EEFGKICKkJdvhs1PjtGa+QdAkhcInTxk
# t/hIJPUb1nO4CsKp1gaVsRkkbcStJ2kxggQ3MIIEMwIBATCBhjByMQswCQYDVQQG
# EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
# cnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQgQ29kZSBT
# aWduaW5nIENBAhALDZkX0sdOvwJhwzQTbV+7MAkGBSsOAwIaBQCgeDAYBgorBgEE
# AYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwG
# CisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMCMGCSqGSIb3DQEJBDEWBBTMzM4P
# 0LoIP298Onabo8qBoPhEdzANBgkqhkiG9w0BAQEFAASCAQAAtfpfUhtupURfTlkt
# hRPAwwbvyZOiBy6G1UkiFRNsHvB0B++QpdZcMiPISvfD38xJF5Iq9OFCLNyizq5M
# WDL8e6+ip4w6/httXEWRoVa9MxbrCrJOti5sHrEHvpvo6o4O+b+dzZn2Ckbm3Tht
# XQ7ZDV691JPVvVTdJfoFg823krtXub+m+VAh95laaC7PtHejXwZGYA6M/xUvKkdS
# QYmr3FnBDJqpoyfFHgGzk/sAQupqEG0TRzo2eVUzgB0sMyllfrHeypLtYLKXCg86
# HXHnnp2ET5ti/y0md+RDxNL7mwelgSZht28iK0ILAarK4d72aMebB1mreF+fGAgF
# +Tl3oYICCzCCAgcGCSqGSIb3DQEJBjGCAfgwggH0AgEBMHIwXjELMAkGA1UEBhMC
# VVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMTAwLgYDVQQDEydTeW1h
# bnRlYyBUaW1lIFN0YW1waW5nIFNlcnZpY2VzIENBIC0gRzICEA7P9DjI/r81bgTY
# apgbGlAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJ
# KoZIhvcNAQkFMQ8XDTE4MDkwNDA2MDE1OVowIwYJKoZIhvcNAQkEMRYEFNgHZ81q
# KoagBBhJQlsPyKyJnMmSMA0GCSqGSIb3DQEBAQUABIIBAI1vQ33F3uNKruXdFyuS
# OcFwpCOS1LoSkBHHAEEb29+iGffyhMLNiUNN6rO3Kd64n0u1i12gxOEIyDUJ6VTv
# 3uXu8oY4vmAueQsFEOtzVxfYOvYr9jIGRcbU7yi/V/1YGlr9VTqeJzMMz5CCEemC
# G88jM+Uu/nJOApvDxWOPygU59VEHeYG1XzR2cjeYzW6VO+htB9UaiWjrmsU6KOh3
# 0ivf9MNZT4LcYNWP1p0Dm4FettnD/UKa3NjmjFxlviI8qqyP1iVS1zQTwfoMtGKS
# 6i+AHE3u94nLSClNZ7ZaEGqKoYeecOROUXdowYKcFlYgpo54VnKWv9xWzE9z7GEx
# 1x8=
# SIG # End signature block