Public/ConvertFrom-ItermColors.ps1
function ConvertFrom-ItermColors { <# .NAME ConvertFrom-ItermColors .SYNOPSIS Converts .itermcolor files into a format that can be used in Window Terminal Settings. Depending on the parameters provided, will either integrate generated schemes into the setting files, or generate a separate file from the existing settings file. Any schemes already present in the setting files will be preserved. .DESCRIPTION Since there is currently no settings UI in Windows Terminal Settings app and the format that is used to express colour schemes is vastly different to that used by iterm, it is not easy to leverage the work done by others in creating desirable terminal schemes. This function makes it easier to apply iterm colour schemes into Windows Terminal. There are multiple ways to use this function: 1) generate an Output file (denoted by $Out parameter), which will contain a JSON object containing the colour schemes converted from iterm to Windows Terminal format. 2) generate a new Dry Run file which is a copy of the current Windows Terminal Settings file with the converted schemes integrated into it. 3) make a backup, of the Settings file, then integrate the generated schemes into the current Settings file. (See caveats further down below). The function errs on the side of caution, and by default works in 'Dry Run' mode. Due to the caveats, this method is effectively the same as not using the $SaveTerminalSettings switch, using $Out instead, because in this scenario, the user would be expected to open up the generated file and copy the generated scheme objects into the current Settings file. This is the recommended way to use this command. If the user wants to integrate the generated schemes into the Settings file automatically, then the $Force switch should be specified. In this case, the current live Settings file is backed up and then over-written by the new content. Existing schemes are preserved. And the caveats ... 1) For some reason, Microsoft decided to include comments inside the JSON setting file (probably in lieu of there not being a proper settings UI, making configuring the settings easier). However, comments are not part of the current JSON schema (although they are permitted in the rarely and sparsely supported json5 spec), which means that this conversion process will not preserve the comments. There is an alternative api that supposedly supports non standard JSON features, newtonsoft.json.ConvertTo-JsonNewtonsoft/ConvertFrom-JsonNewtonsoft but using these functions yield unsatisfactory results. 2) ConvertFrom-Json/Converto-Json do not properly handle the profiles .PARAMETER $Path The parent directory to iterate .PARAMETER $Filter The filter to apply to Get-ChildItem .PARAMETER $Out The output file written to with the JSON represented the converted iterm themes. This content is is just a fragment of the settings file, in fact it's a JSON object which contains a single member named 'schemes' (after the corresponding entry in the Windows Terminal Settings file.) which is set to an array of scheme objects. .PARAMETER $SaveTerminalSettings switch, to indcate that the converted schemes should be saved into a complete settings file. Which settings file depends on the presence of the Force paramter, which .PARAMETER $Force switch to indicate whether live settings should be modified to include generated schemes. To avoid accidental invocation, needs to be used in addition to SaveTerminalSettings. .PARAMETER $DryRunFile When run in Dry Run mode (by default), this is the path of the file written to conatain the current Windows Terminal Settings file with newly generated schemes as converted from iterm files specified by the $Path. .PARAMETER $BackupFile When not in Dry Run mode ($Force and $SaveTerminalSettings specified), this paramter specifies the path to backup the live Windows Terminal Settings file to. .PARAMETER $ThemeName The name of a Krayola Theme, that has been configured inside the global $KrayolaThemes hashtable variable. If not present, then an internal theme is used. The Krayola Theme shapes how output of this command is generated to the consle. #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseBOMForUnicodeEncodedFile', '')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] [Alias('cfic', 'Make-WtSchemesIC')] param ( [Parameter(Mandatory = $true)] [ValidateScript( { return Test-Path $_ })] [string] $Path, [Parameter(Mandatory = $false)] [string]$Filter = '*', [Parameter(Mandatory = $false)] [AllowEmptyString()] [ValidateScript( { return ([string]::IsNullOrWhiteSpace($_) ) ` -or (-not(Test-Path $_ -PathType 'Leaf')) })] [string]$Out, [switch]$SaveTerminalSettings, [switch]$Force, [Parameter(Mandatory = $false)] [AllowEmptyString()] [string]$DryRunFile = '~/Windows.Terminal.dry-run.settings.json', [Parameter(Mandatory = $false)] [ValidateScript( { return -not(Test-Path $_ -PathType 'Leaf') })] [string]$BackupFile = "~/Windows.Terminal.back-up.settings.json", [Parameter(Mandatory = $false)] [AllowEmptyString()] [string] $ThemeName ) # Local function composeAll, builds the json content representing all the schemes # previously collated. # function composeAll { [OutputType([string])] param( [Parameter()] [System.Collections.Hashtable]$Schemes ) [string]$outputContent = '{ "schemes": ['; [string]$close = '] }'; [System.Collections.IDictionaryEnumerator]$enumerator = $Schemes.GetEnumerator(); if ($Schemes.Count -gt 0) { while ($enumerator.MoveNext()) { [System.Collections.DictionaryEntry]$entry = $enumerator.Current; [string]$themeFragment = $entry.Value; $outputContent += ($themeFragment + ','); } [int]$last = $outputContent.LastIndexOf(','); $outputContent = $outputContent.Substring(0, $last); } $outputContent += $close; $outputContent = $outputContent | ConvertTo-Json | ConvertFrom-Json; return $outputContent; } # composeAll # Local function containsScheme, predicate that returns true if SchemeName is present # in the Schemes collection. # function containsScheme { [OutputType([boolean])] param( [Parameter()] [string]$SchemeName, [Parameter()] [object[]]$Schemes ) $found = $Schemes | Where-Object { $_.name -eq $SchemeName }; return ($null -ne $found); } # containsScheme # Local function combineContent, combines the new Content just generated with # the existing Settings file. function combineContent { param( [Parameter()] [string]$Content, [Parameter()] [string]$SettingsPath, [Parameter()] [string]$OutputPath ) [string]$settingsContentRaw = Get-Content -Path $SettingsPath -Raw; [PSCustomObject]$settingsObject = [PSCustomObject] ($settingsContentRaw | ConvertFrom-Json); $settingsSchemes = $settingsObject.schemes; [PSCustomObject]$contentObject = [PSCustomObject] ($Content | ConvertFrom-Json) [System.Collections.ArrayList]$integratedSchemes = New-Object ` -TypeName System.Collections.ArrayList -ArgumentList @(, $settingsSchemes); [System.Collections.Hashtable]$integrationTheme = Get-KrayolaTheme; $integrationTheme['VALUE-COLOURS'] = @(, @('Blue')); [System.Collections.Hashtable]$skippingTheme = Get-KrayolaTheme; $skippingTheme['VALUE-COLOURS'] = @(, @('Red')); foreach ($sch in $contentObject.schemes) { [string[][]]$pairs = @(, @('Scheme name', $sch.name)); if (-not(containsScheme -SchemeName $sch.name -Schemes $settingsSchemes)) { Write-ThemedPairsInColour -Pairs $pairs -Theme $integrationTheme ` -Message 'Integrating new theme'; $null = $integratedSchemes.Add($sch); } else { Write-ThemedPairsInColour -Pairs $pairs -Theme $skippingTheme ` -Message 'Skipping existing theme'; } } $settingsObject.schemes = ($integratedSchemes | Sort-Object -Property name); Set-Content -Path $OutputPath -Value $($settingsObject | ConvertTo-Json); } # combineContent [scriptblock]$containsXML = { # Not making assumption about suffix of the specfied source file(s), since # the only requirement is that the content of the file is xml. # param ( [System.IO.FileSystemInfo]$underscore ) try { return ([xml]@(Get-Content -Path $underscore.Fullname)).ChildNodes.Count -gt 0; } catch { return $false; } } # $containsXML [System.Collections.Hashtable]$displayTheme = Get-KrayolaTheme -KrayolaThemeName $ThemeName; [System.Collections.Hashtable]$passThru = @{ 'BODY' = 'import-ItermColors'; 'MESSAGE' = 'Importing Terminal Scheme'; 'KRAYOLA-THEME' = $displayTheme; 'ITEM-LABEL' = 'Scheme filename'; 'PRODUCT-LABEL' = 'Scheme name'; } [scriptblock]$wrapper = { # This wrapper is required because you can't pass a function name as a variable # without PowerShell mistaking it for an invoke request. # param( $_underscore, $_index, $_passthru, $_trigger ) return write-HostItemDecorator -Underscore $_underscore ` -Index $_index ` -PassThru $_passthru ` -Trigger $_trigger; } $null = invoke-ForeachFile -Path $Path -Body $wrapper -PassThru $passThru ` -Condition $containsXML -Filter $Filter; # Now collate the accumulated results stored inside the passthru # if ($passThru.ContainsKey('ACCUMULATOR')) { [System.Collections.Hashtable]$accumulator = $passThru['ACCUMULATOR']; if ($accumulator) { [string]$outputContent = composeAll -Schemes $accumulator; [string]$copyFromOutputUserHint = [string]::Empty; if ($SaveTerminalSettings.ToBool()) { if ($Force.ToBool()) { # Backup file (NB, WhatIf is set because the force write is not going into effect) # Copy-Item -Path $(Resolve-Path -Path $WindowsTerminalSettingsPath) -Destination $BackupFile -WhatIf; [string]$pseudoWindowsSettingsPath = '~/Windows.Terminal.pseudo.settings.json'; # This line should be specifying $WindowsTerminalSettingsPath as the OutputPath, # but this is being avoided until (if ever) a reliable way of reading and writing # JSON comments is found. Until that happens, the recommended user scenario is to use # SaveTerminalSettings without the Force switch and then subsquently manually copy the # scehemes from the generated Dry Run file to the real Settings file. # $copyFromOutputUserHint = $pseudoWindowsSettingsPath; combineContent -Content $outputContent -SettingsPath $WindowsTerminalSettingsPath ` -OutputPath $pseudoWindowsSettingsPath; } else { $copyFromOutputUserHint = $DryRunFile; combineContent -Content $outputContent -SettingsPath $WindowsTerminalSettingsPath ` -OutputPath $DryRunFile; } } else { $copyFromOutputUserHint = $out; Set-Content -Path $Out -Value $outputContent; } if (-not([string]::IsNullOrWhiteSpace($copyFromOutputUserHint))) { [System.Collections.Hashtable]$userHintTheme = Get-KrayolaTheme; $userHintTheme['VALUE-COLOURS'] = @(, @('Green')); [string[][]]$notice = @(, @('generated file', $copyFromOutputUserHint)); Write-ThemedPairsInColour -Pairs $notice -Theme $userHintTheme ` -Message 'Manual intervention notice !!!, Please open'; [string[][]]$pasteSchemes = @(, @('Windows Terminal Settings file', $WindowsTerminalSettingsPath)); Write-ThemedPairsInColour -Pairs $pasteSchemes -Theme $userHintTheme ` -Message 'Copy & paste "schemes" into'; } } } } # ConvertFrom-ItermColors |