Public/ColorProfiles.ps1
|
function Export-ColorProfile { <# .SYNOPSIS Saves style profiles and registered color names to a file. .DESCRIPTION Export-ColorProfile writes the style profiles in [PSColorStyle]::Profiles, or those named, and every color name Register-ColorName added, to a JSON file that Import-ColorProfile reads back. The file also names the default style when it is one of the profiles saved. A profile holds the properties that differ from a new PSColorStyle's, and its colors as given. The file is UTF-8 without a byte order mark, with LF line ends, and the same in Windows PowerShell 5.1 and PowerShell 7. A file at the path is replaced. .PARAMETER Path The file to write. .PARAMETER Name The profiles to save. Wildcards are allowed. Default: every profile .EXAMPLE Export-ColorProfile -Path ~/colors.json .EXAMPLE Export-ColorProfile -Path ./build-colors.json -Name Build* Saves the profiles whose names start with Build. .INPUTS None .OUTPUTS None .NOTES Author: Mark Newton License: MIT Requires: PowerShell 5.1 or later .LINK https://github.com/MarkusMcNugen/PSWriteColorEX .LINK Import-ColorProfile #> [CmdletBinding()] [Alias('Export-ColourProfile')] param( [Parameter(Mandatory, Position = 0)] [string] $Path, [SupportsWildcards()] [string[]] $Name = '*' ) # The profiles named, in the order of their names in upper case, ordinal, the same everywhere $profileNames = [string[]]@([PSColorStyle]::Profiles.Keys | Where-Object { $key = $_ @($Name | Where-Object { $key -like $_ }).Count -gt 0 }) $keys = [string[]]@(foreach ($profileName in $profileNames) { $profileName.ToUpperInvariant() }) [Array]::Sort($keys, $profileNames, [System.StringComparer]::Ordinal) $blank = [PSColorStyle]::new() $document = [ordered]@{} $colors = [ordered]@{} foreach ($colorName in $script:CustomColors.Keys) { $rgb = $script:CustomColors[$colorName][4] $colors[$colorName] = '#{0:X2}{1:X2}{2:X2}' -f $rgb[0], $rgb[1], $rgb[2] } $document['Colors'] = $colors $default = $null $profiles = [System.Collections.Generic.List[object]]::new() foreach ($profileName in $profileNames) { $style = [PSColorStyle]::Profiles[$profileName] if ([object]::ReferenceEquals($style, [PSColorStyle]::Default)) { $default = $style.Name } $entry = [ordered]@{ Name = $style.Name } foreach ($property in $script:ColorProfileProperties) { $value = $style.$property if ($property -eq 'ForegroundColor' -or $property -eq 'BackgroundColor') { $entry[$property] = $value continue } $isDefault = if ($value -is [array]) { $value.Count -eq 0 } else { $value -eq $blank.$property } if ($null -ne $value -and -not $isDefault) { $entry[$property] = if ($value -is [char]) { [string]$value } else { $value } } } $profiles.Add($entry) } if ($null -ne $default) { $document['Default'] = $default } $document['Profiles'] = $profiles $file = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) $text = (ConvertTo-ColorJson -Value $document) + "`n" [System.IO.File]::WriteAllText($file, $text, [System.Text.UTF8Encoding]::new($false)) } function Import-ColorProfile { <# .SYNOPSIS Reads style profiles and color names from a file Export-ColorProfile wrote, or from a theme the module ships. .DESCRIPTION Import-ColorProfile registers the file's color names, replacing names in use, then adds its profiles to [PSColorStyle]::Profiles, replacing profiles of the same name, and makes the style the file names the default style. -Theme reads one of the module's themes instead: - A color theme registers its colors under names that start with the theme's name, such as DraculaPurple or Nord8 (SolarizedDark and SolarizedLight both use names that start with Solarized), and restyles the profiles Default, Error, Warning, Info, Success, Critical and Debug in its colors, with Default the default style. - CSS adds the CSS color names the color table lacks, such as Tomato, SlateGray and RebeccaPurple. A name already in the table keeps its color, and Grey, DarkGrey and LightGrey take the colors of Gray, DarkGray and LightGray. - PowerShell restyles Error, Warning, Info and Debug as PowerShell's own error, warning, verbose and debug output, and in PowerShell 7.2 and later Critical and Default as its error and formatting accents, with Default the default style. Each takes the text color, background and styles of PowerShell's setting and nothing else; a console color in it is written as the terminal's own color, as PowerShell writes it. .PARAMETER Path The file to read. .PARAMETER Theme The theme to read: CatppuccinFrappe, CatppuccinLatte, CatppuccinMacchiato, CatppuccinMocha, CSS, Dracula, GruvboxDark, GruvboxLight, Nord, OneDark, PowerShell, SolarizedDark, SolarizedLight or TokyoNight. .PARAMETER PassThru Writes the profiles read to the pipeline. .EXAMPLE Import-ColorProfile -Path ~/colors.json Write-ColorEX -Text 'Deploying' -StyleProfile ([PSColorStyle]::Profiles['Build']) .EXAMPLE Import-ColorProfile -Theme Dracula Write-ColorError 'Build failed' Write-ColorEX -Text 'In Dracula purple' -Color DraculaPurple Restyles the profiles in Dracula's colors and registers its palette. .EXAMPLE Import-ColorProfile -Theme PowerShell Makes Write-ColorError, Write-ColorWarning, Write-ColorInfo and Write-ColorDebug look like PowerShell's own error, warning, verbose and debug output. .EXAMPLE Import-ColorProfile -Theme CSS Write-ColorEX -Text 'Tomato' -Color Tomato .EXAMPLE # In a PowerShell profile script, so the styles are there in every session Import-ColorProfile -Path (Join-Path (Split-Path $PROFILE) 'colors.json') .INPUTS None .OUTPUTS None, or PSColorStyle with -PassThru .NOTES Author: Mark Newton License: MIT Requires: PowerShell 5.1 or later .LINK https://github.com/MarkusMcNugen/PSWriteColorEX .LINK Export-ColorProfile #> [CmdletBinding(DefaultParameterSetName = 'Path')] [Alias('Import-ColourProfile')] [OutputType([PSColorStyle])] param( [Parameter(Mandatory, Position = 0, ParameterSetName = 'Path')] [string] $Path, [Parameter(Mandatory, ParameterSetName = 'Theme')] [ValidateSet('CatppuccinFrappe', 'CatppuccinLatte', 'CatppuccinMacchiato', 'CatppuccinMocha', 'CSS', 'Dracula', 'GruvboxDark', 'GruvboxLight', 'Nord', 'OneDark', 'PowerShell', 'SolarizedDark', 'SolarizedLight', 'TokyoNight')] [string] $Theme, [switch] $PassThru ) if ($Theme -eq 'PowerShell') { $settings = Get-PowerShellColorSetting foreach ($style in (Get-PowerShellThemeStyle -PSStyleValue $settings.PSStyle -PrivateData $settings.PrivateData)) { $style.AddToProfiles() if ($style.Name -eq 'Default') { $style.SetAsDefault() } if ($PassThru) { $style } } return } $source = $Path if ($PSCmdlet.ParameterSetName -eq 'Theme') { # The theme's file, its name compared without case as -Theme compares it $themeFiles = @(Get-ChildItem -LiteralPath (Join-Path $script:ModuleRoot 'Themes') -Filter '*.json' -File -ErrorAction SilentlyContinue | Where-Object { $_.BaseName -eq $Theme }) if ($themeFiles.Count -eq 0) { $PSCmdlet.ThrowTerminatingError([System.Management.Automation.ErrorRecord]::new( [System.IO.FileNotFoundException]::new("The module has no file for the theme '$Theme'."), 'ThemeFileNotFound', [System.Management.Automation.ErrorCategory]::ObjectNotFound, $Theme)) } $file = $themeFiles[0].FullName $source = $file } else { $file = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) } try { $document = [System.IO.File]::ReadAllText($file) | ConvertFrom-Json -ErrorAction Stop } catch { $PSCmdlet.ThrowTerminatingError([System.Management.Automation.ErrorRecord]::new( [System.IO.InvalidDataException]::new("Cannot read the profiles in '$source'. $($_.Exception.Message)"), 'InvalidProfileFile', [System.Management.Automation.ErrorCategory]::InvalidData, $source)) } # The CSS theme adds only the names the color table lacks $namesInUse = $null if ($Theme -eq 'CSS') { $namesInUse = Get-ColorTableWithRGB } if ($document.Colors) { foreach ($property in $document.Colors.PSObject.Properties) { if ($null -ne $namesInUse -and $namesInUse.ContainsKey($property.Name)) { continue } Register-ColorName -Name $property.Name -Color (ConvertFrom-ColorJsonValue -Value $property.Value) -Force } } foreach ($entry in @($document.Profiles)) { if ($null -eq $entry -or [string]::IsNullOrEmpty($entry.Name)) { continue } $style = [PSColorStyle]::new([string]$entry.Name, $null, $null) foreach ($property in $script:ColorProfileProperties) { $member = $entry.PSObject.Properties[$property] if ($null -ne $member) { $style.$property = ConvertFrom-ColorJsonValue -Value $member.Value } } $style.AddToProfiles() if ($document.Default -and $document.Default -eq $style.Name) { $style.SetAsDefault() } if ($PassThru) { $style } } } function Remove-ColorProfile { <# .SYNOPSIS Removes style profiles from [PSColorStyle]::Profiles. .DESCRIPTION A removed built-in profile, such as Error, leaves its helper, Write-ColorError, with the colors and styles it starts with. A name with no profile gives an error. .PARAMETER Name The profiles to remove. .EXAMPLE Remove-ColorProfile -Name Build .INPUTS System.String[] Names piped in are removed. .OUTPUTS None .NOTES Author: Mark Newton License: MIT Requires: PowerShell 5.1 or later .LINK https://github.com/MarkusMcNugen/PSWriteColorEX .LINK Get-ColorProfiles #> [CmdletBinding(SupportsShouldProcess)] [Alias('Remove-ColourProfile')] param( [Parameter(Mandatory, Position = 0, ValueFromPipeline = $true)] [string[]] $Name ) process { foreach ($item in $Name) { if (-not [PSColorStyle]::Profiles.ContainsKey($item)) { $PSCmdlet.WriteError([System.Management.Automation.ErrorRecord]::new( [System.ArgumentException]::new("No profile named '$item'."), 'ProfileNotFound', [System.Management.Automation.ErrorCategory]::ObjectNotFound, $item)) continue } if ($PSCmdlet.ShouldProcess($item, 'Remove the style profile')) { [PSColorStyle]::Profiles.Remove($item) } } } } |