Terminal-Icons.psm1


function Add-Theme {
    [cmdletbinding(DefaultParameterSetName = 'Path', SupportsShouldProcess)]
    param(
        [Parameter(
            Mandatory,
            ParameterSetName  = 'Path',
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [SupportsWildcards()]
        [string[]]$Path,

        [Parameter(
            Mandatory,
            ParameterSetName = 'LiteralPath',
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('PSPath')]
        [string[]]$LiteralPath,

        [switch]$Force,

        [ValidateSet('Color', 'Icon')]
        [Parameter(Mandatory)]
        [string]$Type
    )

    process {
        # Resolve path(s)
        if ($PSCmdlet.ParameterSetName -eq 'Path') {
            $paths = Resolve-Path -Path $Path | Select-Object -ExpandProperty Path
        } elseif ($PSCmdlet.ParameterSetName -eq 'LiteralPath') {
            $paths = Resolve-Path -LiteralPath $LiteralPath | Select-Object -ExpandProperty Path
        }

        foreach ($resolvedPath in $paths) {
            if (Test-Path $resolvedPath) {
                $item = Get-Item -LiteralPath $resolvedPath

                $statusMsg  = "Adding $($type.ToLower()) theme [$($item.BaseName)]"
                $confirmMsg = "Are you sure you want to add file [$resolvedPath]?"
                $operation  = "Add $($Type.ToLower())"
                if ($PSCmdlet.ShouldProcess($statusMsg, $confirmMsg, $operation) -or $Force.IsPresent) {
                    if (-not $themeData.Themes.$Type.ContainsKey($item.BaseName) -or $Force.IsPresent) {

                        # Convert color theme into escape sequences for lookup later
                        if ($Type -eq 'Color') {
                            $colorData = ConvertFrom-Psd1 $item.FullName
                            # Directories
                            $colorData.Types.Directories.WellKnown.GetEnumerator().ForEach({
                                $script:colorSequences[$item.BaseName].Types.Directories[$_.Name] = ConvertFrom-RGBColor -RGB $_.Value
                            })
                            # Wellknown files
                            $colorData.Types.Files.WellKnown.GetEnumerator().ForEach({
                                $script:colorSequences[$item.BaseName].Types.Files.WellKnown[$_.Name] = ConvertFrom-RGBColor -RGB $_.Value
                            })
                            # File extensions
                            $colorData.Types.Files.GetEnumerator().Where({$_.Name -ne 'WellKnown'}).ForEach({
                                $script:colorSequences[$item.BaseName].Types.Files[$_.Name] = ConvertFrom-RGBColor -RGB $_.Value
                            })
                        }

                        $colorData = ConvertFrom-Psd1 $item.FullName
                        $themeData.Themes.$Type[$item.Basename] = $colorData
                        Save-Theme -Theme $themeData
                    } else {
                        Write-Error "$Type theme [$($item.BaseName)] already exists. Use the -Force switch to overwrite."
                    }
                }
            } else {
                Write-Error "Path [$resolvedPath] is not valid."
            }
        }
    }
}
function ConvertFrom-ColorEscapeSequence {
    [OutputType([string])]
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [string]$Sequence
    )

    process {
        # Example input sequence: 'e[38;2;135;206;250m'
        $arr = $Sequence.Split(';')
        $r   = '{0:x}' -f [int]$arr[2]
        $g   = '{0:x}' -f [int]$arr[3]
        $b   = '{0:x}' -f [int]$arr[4].TrimEnd('m')

        ($r + $g + $b).ToUpper()
    }
}
function ConvertFrom-Psd1 {
    [OutputType([hashtable])]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ValueFromPipeline)]
        [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformation()]
        [hashtable]$Data
    )

    process {
        return $Data
    }
}
function ConvertFrom-RGBColor {
    [OutputType([string])]
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [string]$RGB
    )

    process {
        $RGB = $RGB.Replace('#', '')
        $r   = [convert]::ToInt32($RGB.SubString(0,2), 16)
        $g   = [convert]::ToInt32($RGB.SubString(2,2), 16)
        $b   = [convert]::ToInt32($RGB.SubString(4,2), 16)

        $escape = [char]27
        "${escape}[38;2;$r;$g;$b`m"
    }
}
function Get-ThemeStoragePath {
    [CmdletBinding()]
    param()

    if ($IsLinux -or $IsMacOs) {
        if (-not ($path = @($env:XDG_CONFIG_DIRS -split ([IO.Path]::PathSeparator))[0])) {
            $path = [IO.Path]::Combine($HOME, '.local', 'share')
        }
    } else {
        if (-not ($path = $env:APPDATA)) {
            $path = [Environment]::GetFolderPath('ApplicationData')
        }
    }

    if ($path) {
        [IO.Path]::Combine($path, 'powershell', 'Community', 'Terminal-Icons', 'theme.xml')
    }
}
function Resolve-Icon {
    [OutputType([hashtable])]
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline)]
        [IO.FileSystemInfo]$FileInfo
    )

    begin {
        $icons  = $script:themeData.Themes.Icon[$themeData.CurrentIconTheme]
        $colors = $script:colorSequences[$themeData.CurrentColorTheme]
    }

    process {
        $displayInfo = @{
            Icon     = $null
            Color    = $null
            Target   = ''
        }

        if ($FileInfo.PSIsContainer) {
            $type = 'Directories'
        } else {
            $type = 'Files'
        }

        switch ($FileInfo.LinkType) {
            # Determine symlink or junction icon and color
            'Junction' {
                $iconName = $icons.Types.($type)['junction']
                $colorSeq = $colors.Types.($type)['junction']
                $displayInfo['Target'] = ' -> ' + $FileInfo.Target
                break
            }
            'SymbolicLink' {
                $iconName = $icons.Types.($type)['symlink']
                $colorSeq = $colors.Types.($type)['symlink']
                $displayInfo['Target'] = ' -> ' + $FileInfo.Target
                break
            } default {
                # Determine normal directory icon and color
                $iconName = $icons.Types.$type.WellKnown[$FileInfo.Name]
                if (-not $iconName) {
                    if ($FileInfo.PSIsContainer) {
                        $iconName = $icons.Types.$type[$FileInfo.Name]
                    } elseif ($icons.Types.$type.ContainsKey($FileInfo.Extension)) {
                        $iconName = $icons.Types.$type[$FileInfo.Extension]
                    } else {
                        # File probably has multiple extensions
                        # Fallback to computing the full extension
                        $firstDot = $FileInfo.Name.IndexOf('.')
                        if ($firstDot -ne -1) {
                            $fullExtension = $FileInfo.Name.Substring($firstDot)
                            $iconName = $icons.Types.$type[$fullExtension]
                        }
                    }
                    if (-not $iconName) {
                        $iconName = $icons.Types.$type['']
                    }
                }
                $colorSeq = $colors.Types.$type.WellKnown[$FileInfo.Name]
                if (-not $colorSeq) {
                    if ($FileInfo.PSIsContainer) {
                        $colorSeq = $colors.Types.$type[$FileInfo.Name]
                    } elseif ($colors.Types.$type.ContainsKey($FileInfo.Extension)) {
                        $colorSeq = $colors.Types.$type[$FileInfo.Extension]
                    } else {
                        # File probably has multiple extensions
                        # Fallback to computing the full extension
                        $firstDot = $FileInfo.Name.IndexOf('.')
                        if ($firstDot -ne -1) {
                            $fullExtension = $FileInfo.Name.Substring($firstDot)
                            $colorSeq = $colors.Types.$type[$fullExtension]
                        }
                    }
                    if (-not $colorSeq) {
                        $colorSeq = $colors.Types.$type['']
                    }
                }
            }
        }
        $displayInfo['Icon']  = $glyphs[$iconName]
        $displayInfo['Color'] = $colorSeq
        $displayInfo
    }
}
function Save-Theme {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [hashtable]$Theme
    )

    process {
        $themePath = Get-ThemeStoragePath
        $Theme | Export-CliXml -Path $themePath -Force
    }
}
function Set-Theme {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Name,

        [ValidateSet('Color', 'Icon')]
        [Parameter(Mandatory)]
        [string]$Type
    )

    if (-not $themeData.Themes.$Type.ContainsKey($Name)) {
        Write-Error "$Type theme [$Name] not found."
    } else {
        $themeData."Current$($Type)Theme" = $Name
        Save-Theme -Theme $themeData
    }
}
function Add-TerminalIconsColorTheme {
    <#
    .SYNOPSIS
        Add a Terminal-Icons color theme for the current user.
    .DESCRIPTION
        Add a Terminal-Icons color theme for the current user. The theme data
        is stored in the user's profile
    .PARAMETER Path
        The path to the Terminal-Icons color theme file.
    .PARAMETER LiteralPath
        The literal path to the Terminal-Icons color theme file.
    .PARAMETER Force
        Overwrite the color theme if it already exists in the profile.
    .EXAMPLE
        PS> Add-TerminalIconsColorTheme -Path ./my_color_theme.psd1
 
        Add the color theme contained in ./my_color_theme.psd1.
    .EXAMPLE
        PS> Get-ChildItem ./path/to/colorthemes | Add-TerminalIconsColorTheme -Force
 
        Add all color themes contained in the folder ./path/to/colorthemes and add them,
        overwriting existing ones if needed.
    .INPUTS
        System.String
 
        You can pipe a string that contains a path to 'Add-TerminalIconsColorTheme'.
    .OUTPUTS
        None.
    .NOTES
        'Add-TerminalIconsColorTheme' will not overwrite an existing theme by default.
        Add the -Force switch to overwrite.
    .LINK
        Add-TerminalIconsIconTheme
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '', Justification='Implemented in private function')]
    [CmdletBinding(DefaultParameterSetName = 'Path', SupportsShouldProcess)]
    param(
        [Parameter(
            Mandatory,
            ParameterSetName  = 'Path',
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [SupportsWildcards()]
        [string[]]$Path,

        [Parameter(
            Mandatory,
            ParameterSetName = 'LiteralPath',
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('PSPath')]
        [string[]]$LiteralPath,

        [switch]$Force
    )

    process {
        Add-Theme @PSBoundParameters -Type Color
    }
}
function Add-TerminalIconsIconTheme {
    <#
    .SYNOPSIS
        Add a Terminal-Icons icon theme for the current user.
    .DESCRIPTION
        Add a Terminal-Icons icon theme for the current user. The theme data
        is stored in the user's profile
    .PARAMETER Path
        The path to the Terminal-Icons icon theme file.
    .PARAMETER LiteralPath
        The literal path to the Terminal-Icons icon theme file.
    .PARAMETER Force
        Overwrite the icon theme if it already exists in the profile.
    .EXAMPLE
        PS> Add-Terminal-IconsIconTHeme -Path ./my_icon_theme.psd1
 
        Add the icon theme contained in ./my_icon_theme.psd1.
    .EXAMPLE
        PS> Get-ChildItem ./path/to/iconthemes | Add-TerminalIconsIconTheme -Force
 
        Add all icon themes contained in the folder ./path/to/iconthemes and add them,
        overwriting existing ones if needed.
    .INPUTS
        System.String
 
        You can pipe a string that contains a path to 'Add-TerminalIconsIconTheme'.
    .OUTPUTS
        None.
    .NOTES
        'Add-TerminalIconsIconTheme' will not overwrite an existing theme by default.
        Add the -Force switch to overwrite.
    .LINK
        Add-TerminalIconsColorTheme
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '', Justification='Implemented in private function')]
    [CmdletBinding(DefaultParameterSetName = 'Path', SupportsShouldProcess)]
    param(
        [Parameter(
            Mandatory,
            ParameterSetName  = 'Path',
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [SupportsWildcards()]
        [string[]]$Path,

        [Parameter(
            Mandatory,
            ParameterSetName = 'LiteralPath',
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('PSPath')]
        [string[]]$LiteralPath,

        [switch]$Force
    )

    process {
        Add-Theme @PSBoundParameters -Type Icon
    }
}

function Format-TerminalIcons {
    <#
    .SYNOPSIS
        Prepend a custom icon (with color) to the provided file or folder object when displayed.
    .DESCRIPTION
        Take the provided file or folder object and look up the appropriate icon and color to display.
    .PARAMETER FileInfo
        The file or folder to display
    .EXAMPLE
        Get-ChildItem
 
        List a directory. Terminal-Icons will be invoked automatically for display.
    .EXAMPLE
        Get-Item ./README.md | Format-TerminalIcons
 
        Get a file object and pass directly to Format-TerminalIcons.
    .INPUTS
        System.IO.FileSystemInfo
 
        You can pipe an objects that derive from System.IO.FileSystemInfo (System.IO.DIrectoryInfo and System.IO.FileInfo) to 'Format-TerminalIcons'.
    .OUTPUTS
        System.String
 
        Outputs a colorized string with an icon prepended.
    #>

    [OutputType([string])]
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [IO.FileSystemInfo]$FileInfo
    )

    process {
        $displayInfo = Resolve-Icon $FileInfo
        "$($displayInfo.Color)$($displayInfo.Icon) $($FileInfo.Name)$($displayInfo.Target)$($script:colorReset)"
    }
}
function Get-TerminalIconsColorTheme {
    <#
    .SYNOPSIS
        List the available color themes.
    .DESCRIPTION
        List the available color themes.
    .Example
        PS> Get-TerminalIconsColorTheme
 
        Get the list of available color themes.
    .INPUTS
        None.
    .OUTPUTS
        System.Collections.Hashtable
 
        An array of hashtables representing available color themes.
    .LINK
        Get-TerminalIconsIconTheme
    .LINK
        Get-TerminalIconsTheme
    #>

    $themeData.Themes.Color
}
function Get-TerminalIconsIconTheme {
    <#
    .SYNOPSIS
        List the available icon themes.
    .DESCRIPTION
        List the available icon themes.
    .Example
        PS> Get-TerminalIconsIconTheme
 
        Get the list of available icon themes.
    .INPUTS
        None.
    .OUTPUTS
        System.Collections.Hashtable
 
        An array of hashtables representing available icon themes.
    .LINK
        Get-TerminalIconsColorTheme
    .LINK
        Get-TerminalIconsTheme
    #>

    $themeData.Themes.Icon
}
function Get-TerminalIconsTheme {
    <#
    .SYNOPSIS
        Get the currently applied color and icon theme.
    .DESCRIPTION
        Get the currently applied color and icon theme.
    .EXAMPLE
        PS> Get-TerminalIconsTheme
 
        Get the currently applied Terminal-Icons color and icon theme.
    .INPUTS
        None.
    .OUTPUTS
        System.Management.Automation.PSCustomObject
 
        An object representing the currently applied color and icon theme.
    .LINK
        Get-TerminalIconsColorTheme
    .LINK
        Get-TerminalIconsIconTheme
    #>

    [CmdletBinding()]
    param()

    [pscustomobject]@{
        PSTypeName = 'TerminalIconsTheme'
        Color      = [pscustomobject]$themeData.Themes.Color[$themeData.CurrentColorTheme]
        Icon       = [pscustomobject]$themeData.Themes.Icon[$themeData.CurrentIconTheme]
    }
}
function Set-TerminalIconsColorTheme {
    <#
    .SYNOPSIS
        Set the Terminal-Icons color theme.
    .DESCRIPTION
        Set the Terminal-Icons color theme to a registered theme.
    .PARAMETER Name
        The name of a registered color theme.
    .EXAMPLE
        PS> Set-TerminalIconsColorTheme -Name devblackops
 
        Set the color theme to 'devblackops'.
    .INPUTS
        System.String
 
        The name of a registered color theme.
    .OUTPUTS
        None.
    .LINK
        Set-TerminalIconsIconTheme
    .LINK
        Get-TerminalIconsColorTheme
    .LINK
        Get-TerminalIconsIconTheme
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [string]$Name
    )

    process {
        Set-Theme -Name $Name -Type Color
    }
}
function Set-TerminalIconsIconTheme {
    <#
    .SYNOPSIS
        Set the Terminal-Icons icon theme.
    .DESCRIPTION
        Set the Terminal-Icons icon theme to a registered theme.
    .PARAMETER Name
        The name of a registered icon theme.
    .EXAMPLE
        PS> Set-TerminalIconsIconTheme -Name devblackops
 
        Set the icon theme to 'devblackops'.
    .INPUTS
        System.String
 
        The name of a registered icon theme.
    .OUTPUTS
        None.
    .LINK
        Set-TerminalIconsColorTheme
    .LINK
        Get-TerminalIconsColorTheme
    .LINK
        Get-TerminalIconsIconTheme
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [string]$Name
    )

    process {
        Set-Theme -Name $Name -Type Icon
    }
}
function Show-TerminalIconsTheme {
    <#
    .SYNOPSIS
        List example directories and files to show the currently applied color and icon themes.
    .DESCRIPTION
        List example directories and files to show the currently applied color and icon themes.
        The directory/file objects show are in memory only, they are not written to the filesystem.
    .EXAMPLE
        Show-TerminalIconsTheme
 
        List example directories and files to show the currently applied color and icon themes.
    .INPUTS
        None.
    .OUTPUTS
        System.IO.DirectoryInfo
    .OUTPUTS
        System.IO.FileInfo
    .NOTES
        Example directory and file objects only exist in memory. They are not written to the filesystem.
    .LINK
        Get-TerminalIconsColorTheme
    .LINK
        Get-TerminalIconsIconTheme
    .LINK
        Get-TerminalIconsTheme
    #>

    [CmdletBinding()]
    param()

    $directories = @(
        [IO.DirectoryInfo]::new('ExampleFolder')
        $themeData.Themes.Icon[$themeData.CurrentIconTheme].Types.Directories.WellKnown.Keys.ForEach({
            [IO.DirectoryInfo]::new($_)
        })
    )
    $wellKnownFiles = @(
        [IO.FileInfo]::new('ExampleFile')
        $themeData.Themes.Icon[$themeData.CurrentIconTheme].Types.Files.WellKnown.Keys.ForEach({
            [IO.FileInfo]::new($_)
        })
    )

    $extensions = $themeData.Themes.Icon[$themeData.CurrentIconTheme].Types.Files.Keys.Where({$_ -ne 'WellKnown'}).ForEach({
        [IO.FileInfo]::new("example$_")
    })

    $directories + $wellKnownFiles + $extensions | Sort-Object
}

# # Dot source public/private functions
# $public = @(Get-ChildItem -Path ([IO.Path]::Combine($PSScriptRoot, 'Public/*.ps1')) -Recurse -ErrorAction Stop)
# $private = @(Get-ChildItem -Path ([IO.Path]::Combine($PSScriptRoot, 'Private/*.ps1')) -Recurse -ErrorAction Stop)
# @($public + $private).ForEach({
# try {
# . $_.FullName
# } catch {
# throw $_
# $PSCmdlet.ThrowTerminatingError("Unable to dot source [$($import.FullName)]")
# }
# })

$glyphs     = . $PSScriptRoot/Data/glyphs.ps1
$escape     = [char]27
$colorReset = "${escape}[0m"

# Import module theme files
$colorThemes = @{}

# Converted color themes
$script:colorSequences = @{}

(Get-ChildItem -Path $PSScriptRoot/Data/colorThemes -Filter '*.psd1').Foreach({

    # Import the color theme and convert to escape sequences
    $colorData = ConvertFrom-Psd1 $_.FullName
    $colorSequences[$_.Basename] = @{
        Name = $colorData.Name
        Types = @{
            Directories = @{
                #'' = "`e[0m"
                symlink  = ''
                junction = ''
                WellKnown = @{}
            }
            Files = @{
                #'' = "`e[0m"
                symlink  = ''
                junction = ''
                WellKnown = @{}
            }
        }
    }
    # Directories
    $script:colorSequences[$colorData.Name].Types.Directories['symlink']  = ConvertFrom-RGBColor -RGB $colorData.Types.Directories['symlink']
    $script:colorSequences[$colorData.Name].Types.Directories['junction'] = ConvertFrom-RGBColor -RGB $colorData.Types.Directories['junction']
    $colorData.Types.Directories.WellKnown.GetEnumerator().ForEach({
        $script:colorSequences[$colorData.Name].Types.Directories[$_.Name] = ConvertFrom-RGBColor -RGB $_.Value
    })
    # Wellknown files
    $script:colorSequences[$colorData.Name].Types.Files['symlink']  = ConvertFrom-RGBColor -RGB $colorData.Types.Files['symlink']
    $script:colorSequences[$colorData.Name].Types.Files['junction'] = ConvertFrom-RGBColor -RGB $colorData.Types.Files['junction']
    $colorData.Types.Files.WellKnown.GetEnumerator().ForEach({
        $script:colorSequences[$colorData.Name].Types.Files.WellKnown[$_.Name] = ConvertFrom-RGBColor -RGB $_.Value
    })
    # File extensions
    $colorData.Types.Files.GetEnumerator().Where({$_.Name -ne 'WellKnown'}).ForEach({
        $script:colorSequences[$colorData.Name].Types.Files[$_.Name] = ConvertFrom-RGBColor -RGB $_.Value
    })

    $colorThemes.Add($colorData.Name, $colorData)
    $colorThemes[$colorData.Name].Types.Directories[''] = $colorReset
    $colorThemes[$colorData.Name].Types.Files['']       = $colorReset
})
$iconThemes = @{}
(Get-ChildItem -Path $PSScriptRoot/Data/iconThemes).Foreach({
    $iconThemes.Add($_.Basename, (Import-PowerShellDataFile -Path $_.FullName))
})

$defaultTheme = 'devblackops'

# Import local theme data
$themePath = Get-ThemeStoragePath
$themeBasePath = Split-Path $themePath
if (-not (Test-Path $themeBasePath)) {
    New-Item -Path $themeBasePath -ItemType Directory -Force
}

if (Test-Path $themePath) {
    $themeData = Import-CliXml -Path $themePath
}
if (-not $themeData) {
    # We have no theme data saved (first time use?)
    # Create one and save it
    $themeData = @{
        CurrentIconTheme  = $defaultTheme
        CurrentColorTheme = $defaultTheme
        Themes = @{
            Color = $colorThemes
            Icon  = $iconThemes
        }
    }
} else {
    # Load or set default theme (if missing)
    if ([string]::IsNullOrEmpty($themeData.CurrentColorTheme)) {
        $themeData.CurrentColorTheme = $defaultTheme
    }
    if ([string]::IsNullOrEmpty($themeData.CurrentIconTheme)) {
        $themeData.CurrentIconTheme = $defaultTheme
    }

    if ($null -eq $themeData.Themes -or $themeData.Themes.Count -eq 0) {
        $themeData.Themes = @{
            Color = @{}
            Icon  = @{}
        }
    }

    # Update the builtin themes
    $colorThemes.GetEnumerator().ForEach({
        $themeData.Themes.Color[$_.Name] = $_.Value
    })
    $iconThemes.GetEnumerator().ForEach({
        $themeData.Themes.Icon[$_.Name] = $_.Value
    })
}

$themeData | Export-Clixml -Path $themePath -Force

# Export-ModuleMember -Function $public.Basename

Update-FormatData -Prepend ([IO.Path]::Combine($PSScriptRoot, 'Terminal-Icons.format.ps1xml'))