Validating Style
In this issue:
- Planning it out
- Try it, you'll like it
- Creating ANSI
- Format-SpectreStyleHashTable
- Format-PSStyleCSV
- Summary
Last time, I shared my experience in creating a tool that used the pwshSpectreConsole module to display a CSV file in style. The function has a defined "stylesheet" in the form of a hashtable of SpectreConsole styles.
@{
Delimiter = 'Aqua'
Quotes = 'CornflowerBlue'
Header = 'Gold1 Italic Bold'
Number = 'LightGoldenrod1'
DateTime = 'MediumOrchid1'
True = 'Chartreuse1'
False = 'LightCoral'
Text = 'LightSalmon1'
Comment = 'Green1 underline'
}
However, the function included a parameter to allow the user to specify their own hashtable. Or they can supply a partial hashtable and the function will merge the style settings.
As I was working on the function, I realized it would be nice to see the hashtable formatted using the specified style. In other words, show Aqua using the Aqua SpectreConsole color.
Let me take a few minutes and walk you through how I created Format-SpectreStyleHashtable.
Obviously, I need to pass a hashtable as a parameter.
param(
[Parameter(Position = 0, Mandatory, ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[hashtable]$Style
)
This function could be used with a hashtable of style values you want to use with Format-SpectreJson or my CSV function, so I am using a more generic parameter name. The name also matches the alias of the CsvStyleHash parameter in my function.
Planning it out
Whenever building something in PowerShell, I recommend outlining the steps you want to take. Start with the major steps. You might even start with the last step. For me it was:
# write a custom object showing style text using the style
In other words, I wanted the property value to be displayed in the corresponding style. This means the property will have to be formatted text. The best way to achieve this result is to use ANSI escape sequences like we use in $PSStyle. Recognizing this step, I can work backwards and fill in other steps.
By talking my way through the process I end up with a list of steps.
# create a temporary hashtable
# enumerate the style hashtable keys and values
# convert the SpectreConsole style to ANSI
# parse the value as [Spectre.Console.Style]
# convert decorations like italic to ANSI equivalent
# add the ANSI style to the temporary hashtable
# write a custom object showing style text using the style
All I need to do is write PowerShell code to fulfill these steps. The comments also serve as documentation when I am finished. Although there's nothing wrong with deleting an item if other parts of your code make it clear what is happening.
Based on my plan, I need to define a temporary hashtable.
# create a temporary ordered hashtable
# the user might expect to see keys in a particular order
$clone = [ordered]@{}
This prevents me from accidentally changing anything in the original hashtable.
I also know that in order to process each key/value pair, I need to enumerate the hashtable.
$Style.GetEnumerator() | ForEach-Object {
#process each key and value
#$_.key e.g. Delimiter
#$_.value e.g. Aqua
}
Try it, you'll like it
The crux of the function is to take a string like Aqua and first validate that it is a SpectreConsole color. And if it is, convert that color to an ANSI sequence. Actually, I need to take this a step further because I could have a value like Aqua italic. To process this, I need to use the Spectre.Console.Style class. Using my handy Get-TypeMember command, I can inspect this class.
PS C:\> Get-TypeMember Spectre.console.Style
Type: Spectre.Console.Style
Name MemberType ResultType IsStatic IsEnum
---- ---------- ---------- -------- ------
Combine Method Style
GetType Method Type
Parse Method Style True
ToMarkup Method String
TryParse Method Boolean True
WithBackground Method Style True
WithDecoration Method Style True
WithForeground Method Style True
WithLink Method Style True
Background Property Color
Decoration Property Decoration True
Foreground Property Color
Link Property String
Plain Property Style
To validate the style, I could try to create an instance of the object. But I see a few parsing methods that might be better choices. You'll see similar methods on other objects in the .NET Framework.
The Parse method, at least in this instance, will attempt to use the value to create a style object.
PS C:\> [Spectre.Console.Style]::parse("Aqua")
Foreground Background Decoration Link
---------- ---------- ---------- ----
aqua default None
An invalid color will throw an exception.
PS C:\> [Spectre.Console.Style]::parse("Aqua2")
MethodInvocationException: Exception calling "Parse" with "1" argument(s): "Could not find color or style 'Aqua2'."
I could use this in my code and invoke the static method.
Try {
$s = [Spectre.Console.Style]::Parse("Aqua2 italic")
}
catch {
Write-Warning "Invalid color or style"
}
The alternative is the TryParse() method.
PS C:\> [Spectre.Console.Style]::TryParse.OverloadDefinitions
static bool TryParse(string text, [ref] Spectre.Console.Style result)
This gives me a boolean result so I can use and If/Else statement. But let's look at this method because it is using something you will occasionally come across.
The method takes two parameters. The first is the text I want to try as a style. The second parameter is the result. If the method succeeds, the output will be store in this variable.
PS C:\> [Spectre.Console.Style]::TryParse("Aqua italic",$result)
MethodException: Argument: '2' should be a System.Management.Automation.PSReference. Use [ref].