Skip to content
ConsoleMonster

How it works

The model behind every ConsoleMonster command: build a widget, compose widgets into a layout, then print it, run it live or export it.

This page explains the one model behind every command and class: a widget is a definition that is built first and drawn later, so the same report can be printed, kept on screen while work runs, or saved as HTML. The example builds a small report from three invented services and prints it once.

Try it

Install-Module ConsoleMonster -Scope CurrentUser
PowerShellhow-it-works.ps1
# Invented services; nothing is queried.
$services = @(
    [pscustomobject] @{ Name = 'Web'; Server = 'web01.example.com'; Status = 'Pass' }
    [pscustomobject] @{ Name = 'Mail'; Server = 'mail01.example.com'; Status = 'Warn' }
    [pscustomobject] @{ Name = 'Files'; Server = 'fs01.example.com'; Status = 'Pass' }
)

# Build: each New-Console* command returns a widget; containers take their children in -Content.
$report = New-ConsoleRows -Gap 1 -Content {
    New-ConsoleRule -Title '[accent bold]Service check[/]'
    New-ConsoleFacts -Content {
        @{ Value = 2; Label = 'healthy'; Role = 'Pass' }
        @{ Value = 1; Label = 'degraded'; Role = 'Warn' }
    }
    New-ConsolePanel -Title 'Services' -Content {
        $services | New-ConsoleTable -Property Name, Server, Status -StatusProperty Status
    }
}

# Render: print the finished widget once.
$report | Show-Console
Full program
Import-Module ConsoleMonster

# Invented services; nothing is queried.
$services = @(
    [pscustomobject] @{ Name = 'Web'; Server = 'web01.example.com'; Status = 'Pass' }
    [pscustomobject] @{ Name = 'Mail'; Server = 'mail01.example.com'; Status = 'Warn' }
    [pscustomobject] @{ Name = 'Files'; Server = 'fs01.example.com'; Status = 'Pass' }
)

# Build: each New-Console* command returns a widget; containers take their children in -Content.
$report = New-ConsoleRows -Gap 1 -Content {
    New-ConsoleRule -Title '[accent bold]Service check[/]'
    New-ConsoleFacts -Content {
        @{ Value = 2; Label = 'healthy'; Role = 'Pass' }
        @{ Value = 1; Label = 'degraded'; Role = 'Warn' }
    }
    New-ConsolePanel -Title 'Services' -Content {
        $services | New-ConsoleTable -Property Name, Server, Status -StatusProperty Status
    }
}

# Render: print the finished widget once.
$report | Show-Console

Run: ./how-it-works.ps1

Terminal
dotnet new console -n ServiceCheck
cd ServiceCheck
dotnet add package ConsoleMonster
C#Program.cs
// Invented services; nothing is queried.
var services = new Service[]
{
    new("Web", "web01.example.com", "Pass"),
    new("Mail", "mail01.example.com", "Warn"),
    new("Files", "fs01.example.com", "Pass")
};

// Build: widgets are ordinary objects; containers take their children.
var table = Table.For(services)
    .Column(row => row.Name)
    .Column(row => row.Server)
    .Column(row => row.Status, column => column.Presenter = CellPresenters.StatusWord);

Rows report = new Rows(
    new Rule("[accent bold]Service check[/]"),
    new ViewFacts().Add(2, "healthy", ThemeToken.Pass).Add(1, "degraded", ThemeToken.Warn),
    new Panel(table, "Services"))
{
    Gap = 1
};

// Render: print the finished widget once.
Terminal.Write(report);

record Service(string Name, string Server, string Status);
Full program
using ConsoleMonster;
using ConsoleMonster.Layout;
using ConsoleMonster.Presenters;
using ConsoleMonster.Styling;
using ConsoleMonster.Widgets;

// Invented services; nothing is queried.
var services = new Service[]
{
    new("Web", "web01.example.com", "Pass"),
    new("Mail", "mail01.example.com", "Warn"),
    new("Files", "fs01.example.com", "Pass")
};

// Build: widgets are ordinary objects; containers take their children.
var table = Table.For(services)
    .Column(row => row.Name)
    .Column(row => row.Server)
    .Column(row => row.Status, column => column.Presenter = CellPresenters.StatusWord);

Rows report = new Rows(
    new Rule("[accent bold]Service check[/]"),
    new ViewFacts().Add(2, "healthy", ThemeToken.Pass).Add(1, "degraded", ThemeToken.Warn),
    new Panel(table, "Services"))
{
    Gap = 1
};

// Render: print the finished widget once.
Terminal.Write(report);

record Service(string Name, string Server, string Status);

Run: dotnet run

Terminal
dotnet new console -n ServiceCheck
cd ServiceCheck
dotnet add package ConsoleMonster
C# fluentProgram.cs
// Invented services; nothing is queried.
var services = new Service[]
{
    new("Web", "web01.example.com", "Pass"),
    new("Mail", "mail01.example.com", "Warn"),
    new("Files", "fs01.example.com", "Pass")
};

// Build: widgets are ordinary objects; containers take their children.
var table = Ui.Table(services)
    .Column(row => row.Name)
    .Column(row => row.Server)
    .Column(row => row.Status, column => column.Presenter(CellPresenters.StatusWord));
Rows report = Ui.Rows().Gap(1).Content(rowsContent =>
{
    rowsContent.Rule("[accent bold]Service check[/]");
    rowsContent.Facts(facts => facts.Add(2, "healthy", ThemeToken.Pass)
        .Add(1, "degraded", ThemeToken.Warn));
    rowsContent.Panel(p => p.Title("Services").Content(panelContent =>
    {
        panelContent.Add(table);
    }));
});

// Render: print the finished widget once.
report.Show();

record Service(string Name, string Server, string Status);
Full program
using ConsoleMonster;
using ConsoleMonster.Layout;
using ConsoleMonster.Presenters;
using ConsoleMonster.Styling;
using ConsoleMonster.Widgets;

// Invented services; nothing is queried.
var services = new Service[]
{
    new("Web", "web01.example.com", "Pass"),
    new("Mail", "mail01.example.com", "Warn"),
    new("Files", "fs01.example.com", "Pass")
};

// Build: widgets are ordinary objects; containers take their children.
var table = Ui.Table(services)
    .Column(row => row.Name)
    .Column(row => row.Server)
    .Column(row => row.Status, column => column.Presenter(CellPresenters.StatusWord));
Rows report = Ui.Rows().Gap(1).Content(rowsContent =>
{
    rowsContent.Rule("[accent bold]Service check[/]");
    rowsContent.Facts(facts => facts.Add(2, "healthy", ThemeToken.Pass)
        .Add(1, "degraded", ThemeToken.Warn));
    rowsContent.Panel(p => p.Title("Services").Content(panelContent =>
    {
        panelContent.Add(table);
    }));
});

// Render: print the finished widget once.
report.Show();

record Service(string Name, string Server, string Status);

Run: dotnet run

Show-Console 80×1324-bitFull

A rule, a facts line and a panel holding a table, printed once at 80 columns.

Build, then render

Nothing is drawn while the report is being built. New-ConsoleRows, New-ConsoleRule, New-ConsoleFacts, New-ConsolePanel and New-ConsoleTable each return a widget object, and containers take their children in -Content. Only the last line draws:

$report = New-ConsoleRows -Gap 1 -Content {
    New-ConsoleRule -Title '[accent bold]Service check[/]'
    New-ConsolePanel -Title 'Services' -Content {
        $services | New-ConsoleTable -Property Name, Server, Status -StatusProperty Status
    }
}
$report | Show-Console
Rows report = new Rows(
    new Rule("[accent bold]Service check[/]"),
    new Panel(table, "Services")) { Gap = 1 };
Terminal.Write(report);
Ui.Rows().Gap(1).Content(c =>
{
    c.Rule("[accent bold]Service check[/]");
    c.Panel(p => p.Title("Services").Content(inner => inner.Add(table)));
}).Show();

Because a widget is only a definition, it can be kept in a variable, passed to a function, put inside another layout or rendered more than once. It measures itself against the width it is given when it is drawn, so the same object fits a 60-column window and a 200-column one.

The command families

The PowerShell verbs say what a command does with a widget:

CommandsWhat they doIn C#
New-Console*Build a widget and return it; nothing is printed.new Table(...), new Panel(...), or Ui.Table(), Ui.Panel()
Show-Console*Print a widget, or objects piped in, once.Terminal.Write(widget) or widget.Show()
Write-Console*Write text: a line with markup (Write-ConsoleText), a log line (Write-ConsoleLogEntry).Terminal.WriteMarkupLine(...)
Invoke-Console*, Start-Console*Run work while a widget stays on screen and updates: a dashboard, a per-item run, a test run.LiveSession, ConsoleRun
Export-Console*Save a widget or a recording as HTML, SVG, Markdown, text or PNG.ConsoleExporter.Save(...) or widget.Export(path)

Show-Console also takes ordinary objects: Get-Service | Show-Console picks a table, a record or a tree from the shape of the data. The widgets and tables pages list everything that can be built, each with its PowerShell, C# and fluent forms.

Composing layouts

Four containers arrange everything else, and they nest freely:

  • New-ConsoleRows (Rows) puts widgets one below another; -Gap adds blank rows between them.
  • New-ConsoleColumns (Columns) puts them side by side and stacks them again when the window is too narrow.
  • New-ConsolePanel (Panel) draws a border with a title around one child.
  • New-ConsoleLayoutGrid (LayoutGrid) fills equal columns row by row.

Layout covers sizing, alignment, padding, layers and scrolling.

Markup and theme roles

Text takes Spectre-style markup: [bold], [accent], [muted], [pass], [warn], [fail], closed with [/]. The colour words are roles, not colours: [pass] is green in the dark theme, a darker green in the light theme, and still readable when colour is off, because status words keep their symbol. The same roles appear as -Role Pass in PowerShell and ThemeToken.Pass in C#, so a facts line, a status column and a bar all agree. Themes shows the roles and how to change them.

Write-ConsoleText -Markup '[accent bold]Deploy[/] finished: [pass]3 passed[/], [fail]1 failed[/]'
Terminal.WriteMarkupLine("[accent bold]Deploy[/] finished: [pass]3 passed[/], [fail]1 failed[/]");

Where the output goes

By default a widget is drawn to the console with the colour depth and glyphs the terminal supports. Redirected output, CI logs and CONSOLEMONSTER_MODE=plain get plain lines instead, from the same widget. To send output somewhere else on purpose:

  • Export-ConsoleFrame writes a widget to an HTML page, an SVG image, Markdown, text or PNG, with the format taken from the file extension. In C#, ConsoleExporter.Save (or widget.Export(path) in fluent code) writes HTML, SVG or text the same way.
  • Use-ConsoleTarget runs a script block with all ConsoleMonster output going to a file or returned as a string; Terminal.Use(target) does the same in C#.
$report | Export-ConsoleFrame -Path ./service-check.html
$text = Use-ConsoleTarget -Width 80 { $report | Show-Console }
ConsoleExporter.Save(report, "service-check.html");
report.Export("service-check.html");

Your first report builds a complete report step by step and exports it.

Next

Edit this page on GitHub