Skip to content

EliziumNet/Krayola

Repository files navigation

🌈 Elizium.Krayola

A B A B A B A B

Colourful console writing with PowerShell

Table of contents

Introduction

The module can be installed using the standard install-module command:

PS> install-module -Name Elizium.Krayola

Krayola provides the capability to write consistent and colourful PowerShell console applications. The key here is that it produces structured output according to user defined formats. A point of note that should be noted by powershell developers is that generally commands should not write to the console. This is for various reasons including (but not limited to)

  • PowerShell can be run a wide variety of scenarios and not all are interactive and makes no assumption about the host. That host may or may not be a console and commands should not assume the presence of one.
  • commands should be designed to be flexible to enable easy re-use, so if a command is writing to the host, it maybe not be easy to use it from other commands where output could interfere with their own operation.

However, bearing the above points in mind, some commands are designed specifically for a console (because they are ui commands) and in this circumstance, writing to the console is fine. There are other other writer functions (eg Write-Information/Write-Debug etc) can be used, but none of these are able to write in colours controlled by the client. Krayola can be used to assist in writing colourful output in a structured manner in accordance with a user definable theme: the 'Krayola Theme'.

Quick Start

  • Create a Scribbler instance using the New-Scribbler factory function:
  [Scribbler]$scribbler = New-Scribbler

This creates the object the client needs to begin writing coloured content. It uses the default Krayola theme and a default Krayon.

  • Next queue up some content:
  $scribbler.Text('Greetings ').Blue().Text('Happy Scripters. ').Reset().Text("It's time to ").Red().Text('colour the world!').Ln().End();
  • Now flush the buffer, so content is displayed:
  $scribbler.Flush();

The above code results in the following output:

picture

All the standard console colours are supported. The background colour can be set, by prepending 'bg' to the standard colour name eg:

$scribbler.bgBlue().Text('Oceans apart ...')

This sets the background colour to blue, then writes 'Oceans apart ...' with the background set to blue.

More formatted content can be displayed using methods such as Pair or a Line

Using the API

The Main Commands

COMMAND-NAME DESCRIPTION
Get-KrayolaTheme Gets Krayola theme
New-Krayon Krayon factory
New-Line Line factory
New-Pair Pair factory
New-Scribbler Scribbler factory

Krayola Theme

Using The Krayola Theme

This is just a hash table, which must contain the following items:

KEY-NAME TYPE DESCRIPTION
FORMAT string A string containing a placeholder for the Key and the Value. It represents how the whole key/value pair should be represented. It must contain the KEY-PLACE-HOLDER and VALUE-PLACE-HOLDER strings.
KEY-PLACE-HOLDER string The place holder that identifies the Key in the FORMAT string.
VALUE-PLACE-HOLDER string The place holder that identifies the Value in the FORMAT string.
KEY-COLOURS string[] Array of 1 or 2 items only, the first is the foreground colour and the optional second value is the background colour, that specifies how Keys are displayed
VALUE-COLOURS string[] The same as KEY-COLOURS but it applies to Values
AFFIRM-COLOURS string[] The highlight colour applied to affirmed Values
OPEN string Specifies the leading wrapper around the whole key/value pair collection, typically '('
CLOSE string Specifies the tail wrapper around the whole key/value pair collection typically ')'
SEPARATOR string Specifies a sequence of characters that separates the Key/Vale pairs, typically ','
META-COLOURS string[] Meta characters include OPEN, CLOSE, SEPARATOR and any other character in the FORMAT which is not the KEY or VALUE
MESSAGE-COLOURS string[] The colours that describe the optional message that appears preceding the Key/Value pair collection.
MESSAGE-SUFFIX string Specifies a sequence of characters that separates the MESSAGE (if present) from the Key/Value pair collection.

An example Theme is as follows:

$ExampleTheme = @{
  "FORMAT"             = "'<%KEY%>'='<%VALUE%>'";
  "KEY-PLACE-HOLDER"   = "<%KEY%>";
  "VALUE-PLACE-HOLDER" = "<%VALUE%>";
  "KEY-COLOURS"        = @("DarkCyan");
  "VALUE-COLOURS"      = @("White");
  "OPEN"               = "[";
  "CLOSE"              = "]";
  "SEPARATOR"          = ", ";
  "AFFIRM-COLOURS"     = @("Red");
  "META-COLOURS"       = @("Yellow");
  "MESSAGE-COLOURS"    = @("Cyan");
  "MESSAGE-SUFFIX"     = " // "
}

⚠️ Note that the tokens <%KEY%> and <%VALUE%> are also defined inside the FORMAT. You will see an error if the FORMAT does not contain these place holders.

The Krayola theme in the above example is illustrated in the following image:

picture

function Get-KrayolaTheme

Helper function Get-KrayolaTheme

Helper function that makes it easier for client applications to get a Krayola theme from the environment, which is compatible with the terminal colours being used. This helps keep output from different applications consistent.

The parameters:

  • KrayolaThemeName (optional)

If $KrayolaThemeName is specified, then it is used to lookup the theme in the global $KrayolaThemes hash table exposed by the Krayola module. If either the theme specified does not exist or not specified, then a default theme is used. The default theme created should be compatible with the dark/lightness of the background of the terminal currently in use. By default, a dark terminal is assumed and the colours used show up clearly against a dark background. If KRAYOLA_LIGHT_TERMINAL is defined as an environment variable (can be set to any string apart from empty string/white space), then the colours chosen show up best against a light background.

Classes

✨ See Classes

COMMAND-NAME
Couplet
Krayon
Line
Scribbler

A tale of the Scribbler/Krayon vs the Legacy functions

A tale of the 🌷 Scribbler/Krayon vs the 🥀 Legacy functions

Once upon a time, The Krayola PowerShell module came into being. Upon creation, 3 colour writing functions were built. They were known as Write-In-Colour, Write-RawPairsInColour and Write-ThemedPairsInColour. Now for a time, the creator was able to write brand new commands using these functions and all was well.

The commands produced such colourful 🌈 displays, that word began to spread among the local towns 🏙️ folk 👪. More and more of the locals caught the scripting bug 🐛 and were 😃 happy to create scripts that both delighted and marvelled their friends 🧝 and neighbours 🧞.

One day, a busy little unicorn 🦄 by the name of Twilight started to find it difficult writing colourful scripts 📜 quickly. 'Why oh why do I constantly get array indexing errors' lamented Twilight. 'Well, ...' replied her friend Willow 🦆, 'you must always remember to use the comma operator when dealing with single item arrays'. 'But I do', cried Twilight. Willow tried to ease her despair 😒, 'There, there. Fear not, we all make mistakes from time to time, nobody is perfect. Actually, I know what you mean. Sometimes, I trip over and see those pesky error messages, particularly when creating multi-dimensional arrays, but those functions we use are all we have.' 😦.

Twilight, thought for a while and asked 'Well how about, we go back to the creator and asked if there were another way we could write in colour'. Willow replied, 'you know what, that may just do the job. We could go and visit the creator bearing gifts of Musik 🎶 and poetry' 🎓. Twilight heartily 💞 agreed, 'Ok let's make haste so we can get back in time before the Setting Sun'. Willow enquired, 'Oh, what a way with words you have. The Setting sun? What on 🌐 Earth Inferno 🔥 do you mean?'. Twilight apologised, 'Oh sorry about that, my passion for 1990's techno music and goth rock sometimes gets the better of me and I just can't resist an obscure reference. The Setting Sun is a song by a pair of Brothers of the Chemical variety that once exited the planet dust and Earth Inferno is probably the best live album of all time.'. Willow, retorted 'Come on now, let's not waste any more time. You pick up that 'Recycled Plastik' over there, and I'll bring 'From My Mind To Yours', I'm sure that fan of Plastik will appreciate both of those delights.

So off they trotted. After treading a long and winding road, they eventually found the creator. "Rat-a-tat-a-tat", sounded the door as they knocked to alert him of their plea 🙏. The door opened and they were warmly welcomed into his abode. After a round a milk and cookies, Willow and Twilight stated their case.

With sympathy, the creator explained, 'I wrote those functions in haste and I too encountered the issues of which you tell. Do you know what? You are in luck. Let me introduce you to my 2 new friends 😊 🙃'. As though pulling rabbits 🐰 out of a hat, the creator waved his hand in a gesture of good will and out popped 2 shiny new classes named Scribbler 🐌 and Krayon 😺.

'Wow', gasped Twilight and Willow. Twilight piped up 'I very much like your 2 new friends'. Willow agreed and added, 'Krayon, seems a little shy and appears to be hiding behind Scribbler. Is he ok?'. The creator, detected the slight concern expressed by his visitors and assuaged them, 'Yeah, he's fine, in fact he's more than fine. He's very happy to help you with your colouring tasks, but all he asks is for you to speak with his younger sister Scribbler'.

Twilight and Willow glanced at each other simultaneously and high-fived 👍. Scribbler chimed in, 'Whenever you need some colour, come to me and I will help. Krayon and myself make a great team. Just tell me the colours you want your text in and I'll collect them all up in a little bag called StringBuilder. You can Flush me at any time, and I'll chat with Krayon and he'll make play with the Host. And if you like, I can be quiet too. I know you like to write unit tests for your scripts, but the software engineering gods advise that tests should be quick and noise-less. Just let me know by either waving the 'Test' flag 🚩 or the 'Silent' flag 🏁. And you also need to know, if you wave the Test flag, I will be quiet unless I find 'EliziumTest' 🧪 in the environment. So your wish is my command'.

'Wowzer, Willow, today has been great', cried Twilight. 'Too right, now we don't ever have to get bogged down with those pesky little array indexing errors and can avoid those trouble-some multi-dimensional arrays. I generally don't have a problem with multi-dimensional arrays, but in PowerShell, they really do make my head itch! 🤔'.

And finally, the creator thanked them for their gifts 😎 and replied in kind, 'I'm so pleased you brought me that 'Recycled Plastik', may all my good vibes and well wishes go 'From My Mind To Yours'. Do yourself a favour and branch out a little. This is a bit off beat and some say an acquired taste, but you ought to check out 4️⃣ 👸 ✨ 'For Her Light' as he handed them a copy of Earth Inferno. 'Enjoy! Oh and don't forget, those old functions are deprecated so use them no more, they are destined for the realms of the Underworld 💀, long live Scribbler and Krayon'; 'Hoorah!!'. They all cried.

The happy scripters left the creator with a skip in their step and joy in their hearts 💝.

... and they all scripted happily ever after 😂.

And now back to reality.

🐌 Scribbler vs 😺 Krayon

Both the Scribbler and Krayon have a similar fluent api, as illustrated in the table below. (PS, the ✔️'s are hyperlinks)

method Scribbler Krayon Description
Scribblers
Scribble [string] ✔️ ✔️ Write a structured string
Text
Text [string] ✔️ Write piece of text in current colours
TextLn [string] ✔️ Write piece of text with a new line in current colours
Pair
Pair [couplet] ✔️ ✔️ Write key/value pair in format defined by Krayola Theme
PairLn [couplet] ✔️ ✔️ Write key/value pair with a new line in format defined by Krayola Theme
Pair [PSCustomObject] ✔️ ✔️ Write PSCustomObject key/value pair in format defined by Krayola Theme
PairLn [PSCustomObject] ✔️ ✔️ Write PSCustomObject key/value pair with a new line in format defined by Krayola Theme
Line
Line [line] ✔️ ✔️ Write a collection of pairs expressed as a line instance in format defined by Krayola Theme
NakedLine [line] ✔️ ✔️ Write a collection of pairs expressed as a naked line instance in format defined by Krayola Theme
Line [string], [line] ✔️ ✔️ Write a collection of pairs expressed as a line instance in format defined by Krayola Theme, with a preceding message
NakedLine [string], [line] ✔️ ✔️ Write a collection of pairs expressed as a naked line instance in format defined by Krayola Theme, with a preceding message
Message
Message [string] ✔️ ✔️ Write piece of text in colours defined by Krayola Theme ('MESSAGE-COLOURS')
MessageLn [string] ✔️ ✔️ Write piece of text with a new line in colours defined by Krayola Theme ('MESSAGE-COLOURS')
MessageNoSuffix [string] ✔️ ✔️ Write piece of text in colours defined by Krayola Theme ('MESSAGE-COLOURS'), without the suffix
MessageNoSuffixLn [string] ✔️ ✔️ Write piece of text with a new line in colours defined by Krayola Theme ('MESSAGE-COLOURS'), without the suffix
Dynamic Colours
fore [string] ✔️ ✔️ Set the current foreground colour
back [string] ✔️ ✔️ Set the current background colour
defaultFore [string] ✔️ ✔️ Set the default foreground colour
defaultBack [string] ✔️ ✔️ Set the default background colour
getDefaultFore ✔️ Get the default foreground colour
getDefaultBack ✔️ Get the default background colour
Static Colours
<colour> ✔️ ✔️ Set the current foreground literal colour
bg<colour> ✔️ ✔️ Set the current background literal colour
Control
End ✔️ Ends the current fluent call chain
Ln ✔️ ✔️ Issue a carriage return
Reset ✔️ ✔️ Reset foreground and background colours back to defaults
Flush ✔️ Apply the contents of buffer onto Krayon and clear the buffer
Theme
ThemeColour [string] ✔️ ✔️ Sets the foreground and background colours to that defined by this Krayola Theme item
Utility
Snippets [string[]] ✔️ Create Snippets from items specified
WithArgSnippet [string], [string] ✔️ Create a Snippet with an argument
PairSnippet [couplet] ✔️ Create a Snippet from a key/value pair
LineSnippet [line] ✔️ Create a Snippet from a collection of pairs expressed as a line instance
Native [string] ✔️ Extract payload text from a structured string
🐌 Scribbler

It is advised that the Scribbler should be used for all host writing operations. One of the criticisms of using Write-Host is that doing so means that any generated output can't be controlled or re-directed at will. Using the Scribbler appeases this criticism because content is stored inside a buffer (StringBuilder) which can easily be intercepted and suppressed as so required.

🎯 Scribbler.Scribble ([string]$source)

Stores the structured string inside the Scribbler buffer, eg:

  [string]$redSnippet = '&[red]';
  [string]$structured = "$($redSnippet)hello world";
  $scribbler.Scribble($structured);

⚠️ Note, that Scribble is seen as a compound method, designed to be used for more than a single item of content so it is not a fluent method call (ie, it does not return anything).

In the above example, the structured string contains a method call (&[red]) and some text content (hello world). The reader can think of a structured string being akin to a mark up language like HTML or XML, with the Krayon executing the structured string content in much the same way as a javascript engine executes HTML but obviously on a much larger scale. Most methods defined on the Krayon are available to be used within a structured string. There are however, some methods that do not make sense to be present inside a structure string. These include any getter such as getDefaultFore and also the Text method; it doesn't make sense to invoke Text, because the required text is designed to be embedded in the structure string anyway. If you refer back to the method call table, any item in the Scribbler column with an ❌ can not be used inside a structured string.

📌 Another point worthy of note is that in the above example, we can see that the meta characters have been included inside the snippet. In this case, it assumes that the Krayon has been configured to recognise the characters '&', '[' and ']', but these are not the default values. The default values are deliberately other characters not easily accessible from the keyboard. Rather, the user should use the utility method Scribbler.Snippets. Whether the user uses the Snippets method or includes the raw content inline is largely a matter of style. The advantage of using the snippets method is that the meta characters configured in the Krayon can be changed without causing rippling code changes through the rest of the code base, but some might say, the code is easier to read with the meta characters embedded inline.

⤴️➡️

🎯 Scribbler.Pair ([couplet]$couplet)

Stores the representation of a pair defined as a [couplet] inside the Scribbler buffer, eg:

  [couplet]$pair = [couplet]::new('Album', 'Pungent Effulgent', $true);
  $scribbler.Pair($pair);

⤴️➡️

🎯 Scribbler.PairLn ([couplet]$couplet)

Stores the representation of a pair defined as a [couplet] inside the Scribbler buffer and issues a carriage return, eg:

  [couplet]$pair = [couplet]::new('festivity', 'The airance of grievances', $true);
  $scribbler.PairLn($pair);

⤴️➡️

🎯 Scribbler.Pair ([PSCustomObject]$couplet)

Stores the representation of a pair defined as a [PSCustomObject] inside the Scribbler buffer, eg:

  [PSCustomObject]$pair = @{
    Key = 'Album';
    Value = 'Snivilisation';
  }
  $scribbler.Pair($pair);

⤴️➡️

Stores the representation of a pair defined as a [PSCustomObject] inside the Scribbler buffer and issues a carriage return, eg:

  [PSCustomObject]$pair = @{
    Key = 'Album';
    Value = 'Insides';
  }
  $scribbler.Pair($pair);
🎯 Scribbler.PairLn ([PSCustomObject]$couplet)

Stores the representation of a pair defined as a [PSCustomObject] inside the Scribbler buffer and issues a carriage return, eg:

  [PSCustomObject]$pair = @{
    Key = 'Album';
    Value = 'Diversions';
  }
  $scribbler.PairLn($pair);

⤴️➡️

🎯 Scribbler.Line ([line]$line)

Stores the representation of a line defined as a collection of pairs inside the Scribbler buffer, eg:

  [array]$pairs = @(
    $(New-Pair('name', 'frank')),
    $(New-Pair('festivity', "The airance of grievances", $true))
  );

  $scribbler.Line($(New-Line($pairs)));

⤴️➡️

🎯 Scribbler.NakedLine ([line]$line)

The format of a line is defined by the Krayola theme. Typically, a line is a collection of pairs, bookended by the 'OPEN' and 'CLOSE' entries in the theme. In the Krayola theme example, we can see there are 5 entries, with each entry containing 2 lines. The first line of each entry is a regular line bookend by '[' (OPEN) and ']' (CLOSE), the second line is a Naked Line that do no render with the OPEN and CLOSE tokens.

Stores the representation of a naked line defined as a collection of pairs inside the Scribbler buffer, eg:

  [array]$pairs = @(
    $(New-Pair('name', 'frank')),
    $(New-Pair('festivity', 'The airance of grievances', $true))
  );

  $scribbler.NakedLine($(New-Line($pairs)));

⤴️➡️

🎯 Scribbler.Line ([string]$message, [line]$line)

A line with a message is illustrated in the Krayola theme example. It shows 5 numbered entries each entry consisting of 2 lines, where the first line has a message 'Rename Item' followed by the message suffix (Krayola theme entry: 'MESSAGE-SUFFIX').

Stores the representation of a line defined as a collection of pairs inside the Scribbler buffer with a preceding message, eg:

  [array]$pairs = @(
    $(New-Pair('name', 'frank')),
    $(New-Pair('festivity', 'The airance of grievances', $true)),
    $(New-Pair('radiccio ', 'the naked and the dub', $true))
  );

  [string]$message = 'Festivus for the rest of us';
  $scribbler.Naked($message, $(New-Line($pairs)));

⤴️➡️

🎯 Scribbler.NakedLine ([string]$message, [line]$line)

A line with a message is illustrated in the Krayola theme example. It shows 5 numbered entries each entry consisting of 2 lines, where the first line has a message 'Rename Item' followed by the message suffix (Krayola theme entry: 'MESSAGE-SUFFIX').

Stores the representation of a naked line defined as a collection of pairs inside the Scribbler buffer with a preceding message, eg:

  [array]$pairs = @(
    $(New-Pair('name', 'frank')),
    $(New-Pair('festivity', 'The airance of grievances', $true)),
    $(New-Pair('radiccio ', 'the naked and the dub', $true))
  );

  [string]$message = 'Festivus for the rest of us';
  $scribbler.NakedLine($message, $(New-Line($pairs)));

⤴️➡️

🎯 Scribbler.Message ([string]$message)

Stores the representation of a message, whose colours are defined by the Krayola theme as 'MESSAGE-COLOURS' followed by the message suffix defined by the theme as 'MESSAGE-SUFFIX' eg:

  [string]$message = 'Festivus for the rest of us';
  $scribbler.Message($message);

⤴️➡️

🎯 Scribbler.MessageLn

Stores the representation of a message, whose colours are defined by the Krayola theme as 'MESSAGE-COLOURS' followed by the message suffix defined by the theme as 'MESSAGE-SUFFIX' and issues a carriage return eg:

  [string]$message = 'Festivus for the rest of us';
  $scribbler.MessageLn($message);

⤴️➡️

🎯 Scribbler.MessageNoSuffix ([string]$message)

Stores the representation of a message, whose colours are defined by the Krayola theme as 'MESSAGE-COLOURS' without a suffix (Krayola theme entry 'MESSAGE-SUFFIX') eg:

  [string]$message = 'Festivus for the rest of us';
  $scribbler.MessageNoSuffix($message);

⤴️➡️

🎯 Scribbler.MessageNoSuffixLn ([string]$message)

Stores the representation of a message, whose colours are defined by the Krayola theme as 'MESSAGE-COLOURS' without a suffix (Krayola theme entry 'MESSAGE-SUFFIX') and issues a carriage return eg:

  [string]$message = 'Festivus for the rest of us';
  $scribbler.MessageNoSuffixLn($message);

⤴️➡️

🎯 Scribbler.Fore ([string]$colour)

Sets the current foreground colour and stores in Scribbler buffer. Any text written after this point will inherit this colour, until the colour is changed by some other colour altering method call, eg:

  $scribbler.fore('red');

⤴️➡️

🎯 Scribbler.Back ([string]$colour)

Sets the current background colour and stores in Scribbler buffer. Any text written after this point will inherit this colour, until the colour is changed by some other colour altering method call, eg:

  $scribbler.back('red');

⤴️➡️

🎯 Scribbler.DefaultFore ([string]$colour)

Sets the default foreground colour and stores in Scribbler buffer. This is the foreground colour that is set when a Reset is invoked, eg:

  $scribbler.defaultFore('cyan');

⤴️➡️

🎯 Scribbler.DefaultBack ([string]$colour)

Sets the default background colour and stores in Scribbler buffer. This is the background colour that is set when a Reset is invoked, eg:

  $scribbler.defaultBack('yellow');

⤴️➡️

🎯 Scribbler.<Colour> ()

Colours can be set literally on the Scribbler using the Console colours as direct method invokes. See Quick Start for an example of this in play. Any text written after this point will inherit this colour, until the colour is changed by some other colour altering method call.

To set the foreground colour to blue:

  $scribbler.blue();

⤴️➡️

🎯 Scribbler.bg<Colour> ()

Background colours can be set literally on the Scribbler using the Console colours as direct method invokes. See Quick Start for an example of this in play. Any text written after this point will inherit this colour, until the colour is changed by some other colour altering method call. To differentiate the colour from being a foreground colour, the user should precede the colour with 'bg' eg, to set the background colour to white:

  $scribbler.bgWhite();

⤴️➡️

🎯 Scribbler.Ln ()

Issues the string representation of a carriage return and stores in the Scribbler buffer, eg:

  $scribbler.Ln();

⤴️➡️

🎯 Scribbler.Reset ()

Stores an instruction to reset the foreground and background colours to the defaults, inside the Scribbler buffer, eg:

  $scribbler.Reset();

⤴️➡️

🎯 Scribbler.Flush ()

Takes the content currently accumulated inside the Scribbler buffer and replays it on the Krayon. It is at this point that content is rendered in the host. The buffer is cleared ready to accept more content.

  $scribbler.Flush();

⚠️ The reader should be aware, if the client has accumulated a lot of content in a relatively long running operation, from the end user's perspective, it is not wise to allow too much content to build up without the Scribbler being flushed on a regular basis, because this could detract from the real time nature of a running operation. For example, lets say a command is performing a bulk rename operation; it would be unwise to only flush the content once at the end of a batch, because under the covers actions are occurring that are not being reflected back to the user in real time.

⤴️

🎯 Scribbler.ThemeColour

Allows, writing content according to the colours defined in the Krayola theme. Instead of explicitly setting a colour, the client can select colours via Krayola entities. The current list of theme entities are (item in brackets is the Krayola theme entry):

  • affirm (AFFIRM-COLOURS): the affirmation colour, used to apply emphasis to an item
  • key (KEY-COLOURS): the colour associated with the key value of a key/value pair
  • message (MESSAGE-COLOURS): the colour associated with the message value of a key/value pair
  • meta (META-COLOURS): the colour associated with the meta value of a key/value pair
  • value (VALUE-COLOURS): the colour associated with the value value of a key/value pair
  $scribbler.ThemeColour('affirm').Text('Greetings Earthlings').Ln().End();

⤴️➡️

🎯 Scribbler.Snippets ([string[]]$items)

This is a helper method that simplifies the action of specifying method invokes on Krayola. For the purposes of this description, let's assume that Krayola has been configured (via New-Krayon factory function) to interpret '&', '[' and ']' as the method invocation tokens. Without the Snippets method, the user would have to either embedded these tokens inside a structured string

  [string]$someContentStmt = 'blah';
  [string]$someOtherContentStmt = 'some more blah';

  $scribbler.Scribble($(
      "&[Reset]" +
      "$($someContentStmt)&[ln]$($someOtherContentStmt)&[ln]" +
      "&[ln]"
    ));

The alternative and recommended way to do this would be to use the Snippets method:

  [string]$resetSn = $scribbler.Snippets(@('Reset'));
  [string]$lnSn = $scribbler.Snippets(@('Ln'));

  $scribbler.Scribble($(
      "$($resetSn)" +
      "blah$($lnSn)some more blah$($lnSn)" +
      "$($lnSn)"
    ));

or

  [string]$resetSn = $scribbler.Snippets(@('Reset'));
  [string]$lnSn = $scribbler.Snippets(@('Ln'));
  [string]$someContentStmt = 'blah';
  [string]$someOtherContentStmt = 'some more blah';

  $scribbler.Scribble($(
      "$($resetSn)" +
      "$($someContentStmt)$($lnSn)$($someOtherContentStmt)$($lnSn)" +
      "$($lnSn)"
    ));

Of course, since the parameter items is an array, multiple items can be passed in, and a sequence of snippets is returned that represents them, eg:

  [string]$headerSnippet = $scribbler.Snippets(@('white', 'bgDarkBlue'));

is the equivalent of:

  [string]$headerSnippet = "&[white]&[bgDarkBlue]"

which sets the foreground colour to white and the background colour to dark blue.

⤴️

🎯 Scribbler.WithArgSnippet ([string]$method, [string]$arg)

The Snippets helper method is only suitable for Krayon methods that do not take argument. To invoke a Krayon method which takes a single argument, the client should use WithArgSnippet, eg:

  [string]$metaSnippet = $scribbler.WithArgSnippet('ThemeColour', 'meta');

is equivalent to:

  [string]$metaSnippet = '&[ThemeColour, meta]';

and results in queuing up a call to method ThemeColour passing in 'meta' as the single parameter and storing this in the Scribbler buffer.

The pros/cons explained in Scribbler.Snippets also applies to Scribbler.WithArgSnippet

📌 The methods on Krayon (that can be invoked via Scribble) have been crafted to either accept either no parameters or a single parameter. However, there are times when a compound value needs to be represented. For these compound values (eg a key/value pair or a sequence of pairs), they can be represented as a single value, ie a key/value can be represented as a comma separated string (see PairSnippet) and a sequence of pairs can be represented as semi-colon separated collection of pairs (see LineSnippet).

⤴️

🎯 Scribbler.PairSnippet ([couplet]$pair)

Utility method that returns a snippet which represents a pair, by converting it into a comma separated string value, eg

  [couplet]$pair = New-Pair('Title', 'At The Gates Of Silent Memory');
  [string]$pairSn = $scribbler.PairSnippet($pair);
  $scribbler.Scribble($pairSn);

is equivalent to

  [string]$pairSn = "&[Title, At The Gates Of Silent Memory]";
  $scribbler.Scribble($pairSn);

⤴️

🎯 Scribbler.LineSnippet ([line]$line)

Utility method that returns a snippet which represents a line, by converting it into a single string value of semi-colon separated pair values, eg

  [array]$pairs = @(
    $(New-Pair('four', '(Paradise Regained)')),
    $(New-Pair('five', 'Submission')),
    $(New-Pair('six', 'Sumerland (What Dreams May Come)'))
  );
  [line]$line = New-Line($pairs);
  [string]$lineSn = $scribbler.LineSnippet($line);

where the returned line snippet $lineSn is:

'&[Line,four,(Paradise Regained),False;five,Submission,False;six,Sumerland (What Dreams May Come),False]'

📌 Note that each pair constituent of the line is fully represented, so the False value seen here represents the Affirm property of each pair. The first item Line, represents the Line method that will eventually be called on the Krayon, with the remainder being the string representation of that line.

📌 Also note that using the Scribbler.LineSnippet method handles the required ',' and ';' escaping, eg

  [array]$pairs = @(
    $(New-Pair('four,side-1', '(Paradise Regained)')),
    $(New-Pair('five,side-1', 'Submission')),
    $(New-Pair('six,side-1', 'Sumerland, (What Dreams May Come)'))
  );
  [line]$line = New-Line($pairs);
  [string]$lineSn = $scribbler.LineSnippet($line);

where the returned line snippet $lineSn is:

'&[Line,four,side-1,(Paradise Regained),False;five,side-1,Submission,False;six,side-1,Sumerland, (What Dreams May Come),False]'

so:

  • 'four,side-1': is escaped to become: 'four\,side-1'
  • 'five,side-1': is escaped to become: 'five\,side-1'
  • 'six,side-1': is escaped to become: 'six\,side-1'
  • 'Sumerland, (What Dreams May Come)': is escaped to become: 'Sumerland\, (What Dreams May Come)'

⤴️

😺 Krayon

Responsible for writing directly to the host using the colours specified by the user either by direct method calls or via a Structured String

🎯 Krayon.Scribble ([string]$source)

Parses the structured string and writes to the host via methods calls on itself, eg

  [string]$stmt = "&[Reset]who &[magenta]watches &[cyan]the &[gray]&[bgBlack]watchers";
  $krayon.Scribble($stmt).End();

⤴️➡️

🎯 Krayon.Text ([string]$value)

Writes text to the console using the current foreground and background colours, eg

  $krayon.Green().Text('Greeting Earthlings');

⤴️

🎯 Krayon.TextLn ([string]$value)

Writes text to the console using the current foreground and background colours and issues a carriage return, eg

  $krayon.Green().TextLn('Greeting Earthlings');

⤴️

🎯 Krayon.Pair ([couplet]$couplet)

Writes a key/value pair whose structure and colour is defined by the Krayola theme (FORMAT, KEY-PLACE-HOLDER, VALUE-PLACE-HOLDER, KEY-COLOURS, VALUE-COLOURS, META-COLOURS), eg

  [couplet]$pair = [couplet]::new('Album', 'Pungent Effulgent', $true);
  $krayon.Pair($pair);

⤴️➡️

🎯 Krayon.PairLn ([couplet]$couplet)

Writes a key/value pair whose structure and colour is defined by the Krayola theme (FORMAT, KEY-PLACE-HOLDER, VALUE-PLACE-HOLDER, KEY-COLOURS, VALUE-COLOURS, META-COLOURS) and issues a carriage return, eg

  [couplet]$pair = [couplet]::new('festivity', 'The airance of grievances', $true);
  $krayon.PairLn($pair);

⤴️➡️

🎯 Krayon.Pair ([PSCustomObject]$couplet)

Writes a PSCustomObject key/value pair whose structure and colour is defined by the Krayola theme (FORMAT, KEY-PLACE-HOLDER, VALUE-PLACE-HOLDER, KEY-COLOURS, VALUE-COLOURS, META-COLOURS), eg

  [PSCustomObject]$pair = @{
    Key = 'Album';
    Value = 'Snivilisation';
  }
  $krayon.Pair($pair);

⤴️➡️

🎯 Krayon.PairLn ([PSCustomObject]$couplet)

Writes a PSCustomObject key/value pair whose structure and colour is defined by the Krayola theme (FORMAT, KEY-PLACE-HOLDER, VALUE-PLACE-HOLDER, KEY-COLOURS, VALUE-COLOURS, META-COLOURS) and issues a carriage return, eg

  [PSCustomObject]$pair = @{
    Key = 'Album';
    Value = 'Diversions';
  }
  $scribbler.PairLn($pair);

⤴️➡️

🎯 Krayon.Line ([line]$line)

Writes a line (a collection of pairs) whose structure and colour is defined by the Krayola theme (FORMAT, KEY-PLACE-HOLDER, VALUE-PLACE-HOLDER, KEY-COLOURS, VALUE-COLOURS, META-COLOURS, OPEN, CLOSE) eg

  [array]$pairs = @(
    $(New-Pair('name', 'frank')),
    $(New-Pair('festivity', "The airance of grievances", $true))
  );

  $krayon.Line($(New-Line($pairs)));

When writing a line, as new line is implied so a carriage return is also written.

⤴️➡️

🎯 Krayon.NakedLine ([line]$line)

Writes a naked line (a collection of pairs) whose structure and colour is defined by the Krayola theme (FORMAT, KEY-PLACE-HOLDER, VALUE-PLACE-HOLDER, KEY-COLOURS, VALUE-COLOURS, META-COLOURS), eg

  [array]$pairs = @(
    $(New-Pair('name', 'frank')),
    $(New-Pair('festivity', 'The airance of grievances', $true))
  );

  $krayon.NakedLine($(New-Line($pairs)));

⤴️➡️

🎯 Krayon.Line ([string]$message, [line]$line)

Writes a message and a line (a collection of pairs) whose structure and colour is defined by the Krayola theme (FORMAT, KEY-PLACE-HOLDER, VALUE-PLACE-HOLDER, KEY-COLOURS, VALUE-COLOURS, META-COLOURS, OPEN, CLOSE, MESSAGE-COLOURS), eg

  [array]$pairs = @(
    $(New-Pair('name', 'frank')),
    $(New-Pair('festivity', 'The airance of grievances', $true)),
    $(New-Pair('radiccio ', 'the naked and the dub', $true))
  );

  [string]$message = 'Festivus for the rest of us';
  $krayon.Naked($message, $(New-Line($pairs)));

⤴️➡️

🎯 Krayon.NakedLine ([string]$message, [line]$line)

Writes a message and a naked line (a collection of pairs) whose structure and colour is defined by the Krayola theme (FORMAT, KEY-PLACE-HOLDER, VALUE-PLACE-HOLDER, KEY-COLOURS, VALUE-COLOURS, META-COLOURS, MESSAGE-COLOURS), eg

  [array]$pairs = @(
    $(New-Pair('name', 'frank')),
    $(New-Pair('festivity', 'The airance of grievances', $true)),
    $(New-Pair('radiccio ', 'the naked and the dub', $true))
  );

  [string]$message = 'Festivus for the rest of us';
  $krayon.NakedLine($message, $(New-Line($pairs)));

⤴️➡️

🎯 Krayon.Message ([string]$message)

Write a message to the host whose structure and colour is defined by the Krayola theme (MESSAGE-COLOURS), eg

  [string]$message = 'Festivus for the rest of us';
  $krayon.Message($message);

⤴️➡️

🎯 Krayon.MessageLn ([string]$message)

Write a message to the host whose structure and colour is defined by the Krayola theme (MESSAGE-COLOURS) and issues a carriage return, eg

  [string]$message = 'Festivus for the rest of us';
  $krayon.MessageLn($message);

⤴️➡️

🎯 Krayon.MessageNoSuffix ([string]$message)

Write a message without the message suffix (MESSAGE-SUFFIX) to the host whose structure and colour is defined by the Krayola theme (MESSAGE-COLOURS), eg

  [string]$message = 'Festivus for the rest of us';
  $krayon.MessageNoSuffix($message);

⤴️➡️

🎯 Krayon.MessageNoSuffixLn ([string]$message)

Write a message without the message suffix (MESSAGE-SUFFIX) to the host whose structure and colour is defined by the Krayola theme (MESSAGE-COLOURS) and issues a carriage return, eg

  [string]$message = 'Festivus for the rest of us';
  $krayon.MessageNoSuffixLn($message);

⤴️➡️

🎯 Krayon.Fore ([string]$colour)

Sets the foreground colour to the value specified, eg

  [string]$colour = 'red';
  $krayon.fore($colour);

which is the same as doing

  $krayon.Red();

⤴️➡️

🎯 Krayon.Back ([string]$colour)

Sets the background colour to the value specified, eg

  [string]$colour = 'black';
  $krayon.back($colour);

which is the same as doing

  $krayon.bgBlack();

⤴️➡️

🎯 Krayon.DefaultFore ([string]$colour)

Sets the default foreground colour to the value specified, eg

  [string]$colour = 'cyan';
  $krayon.defaultFore($colour);

⤴️➡️

🎯 Krayon.DefaultBack ([string]$colour)

Sets the default background colour to the value specified, eg

  [string]$colour = 'yellow';
  $krayon.defaultFore($colour);

⤴️➡️

🎯 Krayon.<Colour> ()

Sets the foreground colour, eg

  $krayon.blue();

or indeed

  [string]$colour = 'blue';
  $krayon.$colour();

⤴️➡️

🎯 Krayon.bg<Colour> ()

Sets the background colour, eg

  $krayon.bgBlue();

or indeed

  [string]$colour = 'bgBlue';
  $krayon.$colour();

⤴️➡️

🎯 Krayon.End ()

Terminates the fluent call chain returning nothing. This is required because in PowerShell any value returned but not consumed makes up part of the output of the calling function and can almost certainly cause un-intended output, eg from a function

  $krayon.bgBlue();

results in the Krayon instance being returned and because its return value has not been assigned to another variable, the Krayon ends up being serialised (ToString()) and directed to the console. In this case we should do

  $krayon.bgBlue().End();

or indeed assigning to $null

  $null = $krayon.bgBlue();

or casting to void

  [void]$krayon.bgBlue();

or piping to Out-Null

  $krayon.bgBlue() | Out-Null

The End function is provided as a more convenient and elegant way of achieving this.

📌 It should be noted that the issue of unconsumed returned values being directed to the host by default does not apply when calling from a class method.

⤴️➡️

🎯 Krayon.Ln ()

Issues a carriage return to the host so that any subsequent text written occurs on a new line.

  $krayon.Yellow().Text('Greetings Earthlings').Ln().End();

⤴️➡️

🎯 Krayon.Reset ()

Reset the foreground and background colours to the defaults.

  $krayon.Reset().Yellow().Text('Greetings Earthlings').Ln().End();

⤴️➡️

🎯 Krayon.ThemeColour ([string]$val)

Allows, writing content according to the colours defined in the Krayola theme. Instead of explicitly setting a colour, the client can select colours via Krayola entities. The current list of theme entities are (item in brackets is the Krayola theme entry):

  • affirm (AFFIRM-COLOURS): the affirmation colour, used to apply emphasis to an item
  • key (KEY-COLOURS): the colour associated with the key value of a key/value pair
  • message (MESSAGE-COLOURS): the colour associated with the message value of a key/value pair
  • meta (META-COLOURS): the colour associated with the meta value of a key/value pair
  • value (VALUE-COLOURS): the colour associated with the value value of a key/value pair
  $krayon.ThemeColour('affirm').Text('Greetings Earthlings').Ln().End();

⤴️➡️

🎯 Krayon.Native

Extract payload text from a structured string, eg

  [string]$snippet = '&[ThemeColour, affirm]';
  [string]$structured = "hello $($snippet)world";
  [string]$nativeValue = $krayon.Native($structured)

where the returned $nativeValue is

'hello world'

Supporting Utilities

COMMAND-NAME DESCRIPTION
Get-DefaultHostUiColours Get default host colours
Get-EnvironmentVariable Get variable from Env
Get-IsKrayolaLightTerminal Has user declare light theme
Show-ConsoleColours Show console colours

Deprecated

⚠️ No longer supported and will be removed; use the Scribbler instead.

COMMAND-NAME DESCRIPTION
Write-InColour DEPRECATED
Write-RawPairsInColour DEPRECATED
Write-ThemedPairsInColour DEPRECATED

Helper functions

function Get-DefaultHostUiColours

Helper function Get-DefaultHostUiColours

Get the default colours from the Host ($Host).

function Get-EnvironmentVariable

Helper function Get-EnvironmentVariable

Just a utility function that retrieves the value of a variable from the environment with the option to specify a default, if it does exist. Typically, this is used to aid unit-testing.

function Get-IsKrayolaLightTerminal

Helper function Get-IsKrayolaLightTerminal

Gets the value of the environment variable KRAYOLA_LIGHT_TERMINAL as a boolean.

For use by applications that need to use a Krayola theme that is dependent on whether a light or dark background colour is in effect in the current terminal.

function Show-ConsoleColours

Helper function Show-ConsoleColours

Function that simply displays all the available console colours as they are represented in text in the colour they represent. This will aid in defining custom themes. Just invoke the function with no arguments in your PowerShell session.

Global pre-defined Themes

The module exports a global variable $KrayolaThemes hash-table, which contains some predefined themes. The user can use one of these (currently defined as "EMERGENCY-THEME", "ROUND-THEME", "SQUARE-THEME" and "ANGULAR-THEME"). This list may be added to in the future. $KrayolaThemes, is not a read only variable, so if the client requires, they can add their own.

The client would typically use an explicit theme, illustrated as follows:

  New-Krayon -Theme $KrayolaThemes["SQUARE-THEME"]

or

  New-Krayon -Theme $(Get-KrayolaTheme -KrayolaThemeName 'SQUARE-THEME')

Developer Notes

This module has the following developer dependencies:

After cloning the repo, change to the Elizium.Krayola directory from the root. You can look at the build script Elizium.Krayola.build.ps1, it will contain various tasks, the most important of which are explained below

Running build tasks

To build the module and run the unit tests:

invoke-build

To build the module only:

invoke-build build

To Run the unit tests only (assuming already built)

invoke-build tests

To build the documents:

invoke-build buildhelp

Problem rebuilding modified classes in the same PowerShell session

⚠️ Krayola make use of PowerShell classes. Because of the nature of classes in PowerShell, re-building edited code can cause errors. This is not a fault of the Krayola code, it's just the way PowerShell classes have been designed.

What you will find is, if a class has been modified then rebuilt in the same session, you may find multiple class errors like so:

[-] Krayon.given: pair.and: pair created via default constructor.should: write pair 31ms (30ms|1ms)
 PSInvalidCastException: Cannot convert the "Krayon" value of type "Krayon" to type "Krayon".
 ArgumentTransformationMetadataException: Cannot convert the "Krayon" value of type "Krayon" to type "Krayon".
 at <ScriptBlock>, ..\github\PoSh\Krayola\Elizium.Krayola\Tests\Krayon.tests.ps1:21

Fear not, this is just reporting that the class definition has changed and because of this difference, one can't be substituted for another in the same PowerShell session (this is in contrast to the way functions work, where you can simply re-define a function in the same session and it will replace the previous definition. This luxury has not been afforded to classes unfortunately). All that's required is to restart a new session. The rebuild in the new session should progress without these errors.

It is a bit onerous having to restart a session for every build, but below is a function that can be defined in the users powershell profile that when invoked, begins a restart loop. Now, when an exit is issued, the session is automatically restarted:

Helper function restart-session

Insert this into your PowerShell session file.

function Get-TagPath {
  return Join-Path $env:temp -ChildPath 'restart-session.tag.txt';
}

function Restart-Session {
  [Alias('ress')]
  param()
 
  [string]$tagPath = Get-TagPath;
  if (-not([string]::IsNullOrEmpty($env:tag))) {
    Set-Content -Path $tagPath -Value $env:tag;
  }
  elseif (Test-Path -Path $tagPath) {
    Remove-Item -Path $tagPath;
  }

  [System.Management.Automation.PathInfo]$pathInfo = Get-Location;
  while ($true) {
    pwsh -Command {
      [string]$tagPath = Get-TagPath;
      [string]$greeting = "🍺 Restarted!";
      if (Test-Path -Path $tagPath) {
        $tag = Get-Content -Path $tagPath;

        if (($tag -is [string]) -or ($tag -is [string[]])) {
          $env:tag = $tag;
          $greeting = "🍺 Restarted! (Pester Tag: '$env:tag' ✔️)";
        }
      }

      Write-Host -ForegroundColor 'Cyan' $greeting;
    } -NoExit -WorkingDirectory $($pathInfo.Path)
    if ($LASTEXITCODE) {
      break
    }
  }
}

Another feature this function possesses is the restoration of the Tag environment variable. The Tag is used to control which testcases Pester runs. Pester contains a Tag in its configuration and when set, it will only run those test cases decorated with this tag value.

So, when a restart occurs, the Tag if set is restored and you will see which tag is in play as part of the restart. If no tag is found then no tag is restored. This function just helps the tedium of having to keep redefining the Tag in-between restarts, as now this is automatically restored.

The sequence goes:

  • Set the tag (if you want one): "$env:tag = 'Current'"
  • restart session: "restart-session" (this saves the current tag; written to a temp file)

After restart, tag is restored and the restart message will indicate as such

  • Modify class definition
  • first build should be ok
  • Re-edit the class definition, then rebuild => this will fail
  • run "exit", this will automatically restart the session, restoring the Tag value

... and repeat.

EliziumTest flag

As has been documented elsewhere, the user can set this flag in the environment (just set it to any non $null value).

By default, $env:EliziumTest, will not be present, this means, that the unit tests in Krayola will run in silent mode. However, there are some tests which are less valuable in silent mode, doing so would invalidate them to some degree. There are only a few of the tests in this category (tagged as 'Host') and it's because they require Write-Host to be invoked. Theoretically, one could mock out the Write-Host call, but some errors can be much easier to spot visually. This generally is not the best technique in unit-testing, but these test cases have been backed up by non noisy equivalents to make sure all bases are covered.

During development, it is very useful to get a visual on how ui commands are behaving. This was the rationale behind the introduction of this flag. So when EliziumTest is defined, the user will see more output that reflects the execution of the Scribbler and Krayon.

About

Colourful console writing with Powershell

Resources

License

Stars

Watchers

Forks

Packages

No packages published