Skip to content

MortalFlesh/console-style

Repository files navigation

Console style

NuGet Version and Downloads count Check

One of the most boring tasks when creating console commands is to deal with the styling of the command's input and output. Displaying titles and tables or asking questions to the user involves a lot of repetitive code.

This library is inspired by SymfonyStyle.

Installation

dotnet add package ConsoleStyle

Basic Usage

open MF.ConsoleStyle

let console = ConsoleStyle()

console.Title "Hello World!"

for output:

Hello world!
============

Input

Ask

let name = console.Ask "What's your name?"
let name = console.Ask("What's your %s?", "name")

Example:

What's your name? {stdin}

Output - single

Function example color note
mainTitle console.MainTitle "ConsoleStyle" cyan see output 👇
  _____                        __        ____  __           __
 / ___/ ___   ___   ___ ___   / / ___   / __/ / /_  __ __  / / ___
/ /__  / _ \ / _ \ (_-</ _ \ / / / -_) _\ \  / __/ / // / / / / -_)
\___/  \___//_//_//___/\___//_/  \__/ /___/  \__/  \_, / /_/  \__/
                                                  /___/

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

Function example color note
title console.Title "Title of foo" cyan see output 👇
Title of foo
============
Function example color note
section console.Secion "Section of foo" dark-yellow see output 👇
Section of foo
--------------
Function example color note
message console.Message "a simple message" default
newLine console.NewLine() default
subTitle console.SubTitle "Sub title" yellow Text is natively underlined
error console.Error "Something went wrong!" red This output goes to the stderr
success console.Success "Done" green
indent console.Indent "Something indented" default adds spaces at the beginning

NOTE: most of the functions allows formatted variant with up to 5 args - see Formatting

Output - many

Function example color note
messages console.Messages "-prefix-" ["line 1"; "line 2"] default see output 👇
-prefix-line 1
-prefix-line 2
Function example color note
options console.Options "Foo options" [ ["first"; "desc 1"]; ["second"; "desc 2"] ] default with yellow title see output 👇
Foo options
    - first   desc 1
    - second  desc 2
Function example color note
simpleOptions console.SimpleOptions "Foo options" [ ["first"; "desc 1"]; ["second", "desc 2"] ] Same as options, but without line prefix. default with yellow title see output 👇
Foo options
    first   desc 1
    second  desc 2
Function example color note
groupedOptions console.GroupedOptions ":" "Grouped options" [ ["first"; "desc 1"]; ["group:first"; "desc"; "group 1"]; ["group:second"; "desc"; "group 2"]; ["second"; "desc 2"] ] Grouped options by their prefix, if there is any. default with yellow title see output 👇
Grouped options
    first         desc 1
    second        desc 2
 group
    group:first   desc    group 1
    group:second  desc    group 2
Function example color note
list console.List ["line 1"; "line 2"] default see output 👇
 - line 1
 - line 2

Formatting

Since string formatting with sprintf is handled by compiler and is not easy to reproduce, there are explicit functions for formatting up to 5 parameters. It is still type safe and compiler friendly (only limitation is for number of parameters, but you can still simply use sprintf directly).

console.Message("Format %s parameter", "one")
console.Message("Format %s, %s parameter", "one", "two")
console.Message("Format %s, %d and %d parameter", "one", 2, 3)
console.Message(sprintf "Format %s, %s, %s and %s parameter" "one" "two" "three" "more ...")

NOTE: Other functions allowing formatting works the same way.

Markup in text

There is a special tag for formatting a part of text (<c:COLOR>text</c>).

Formatting -> Usage

  • Bold <c:|b>Bold</c>
  • Dim <c:|d>Dim</c>
  • Italic <c:|i>Italic</c>
  • Underline <c:|u>Underline</c>
  • Reverse <c:|r>Reverse</c>
  • StrikeThrough <c:|s>StrikeThrough</c>
  • Foreground <c:COLOR>Colored</c>
  • Background <c:|bg:COLOR>Colored</c>

Combining formatting:

  • minimal markup is <c:>text</c>
  • colors <c:black|bg:red>text with black foreground and red background</c>
  • bold and underlined <c:|bu>Bold and underlined</c>
  • all formatting options <c:#D20000|bg:blue|bdiurs>Over formatted</c>

Available colors:

Named colors

Colors

TIP: Given color is normalized, so you can use - or _ as separator or even use different case for colors. (Example: darkblue is same as dark_blue, DARK--Blue, dark-blue, ...)

You can also use RGB and RGBA color codes

More Colors

NOTE: You can use colors as both foreground and background for a text.

Usage

console.Message "Hello <c:green>world</c>!" // `Hello` and `!` will be in default color, and `world` will be green.
console.Message "<c:red>Hello</c> <c:green>world</c>!"  // Different color for every word.

console.SimpleOptions "Options:" [
    [ "option1"; "This is the <c:magenta>first</c> option"; "<c:yellow>[default: \"foo\"]</c>" ]
    [ "option2"; "This is the <c:magenta>second</c> option" ]
]

Output complex components

Table

console.Table [ "FirstName"; "Surname" ] [
    [ "Jon"; "Snow" ]
    [ "Peter"; "Parker" ]
]

Output:

----------- ---------
 FirstName   Surname
----------- ---------
 Jon         Snow
 Peter       Parker
----------- ---------

Tabs

See in the example dir

Tabs

// First line
[ "red"; "green"; "yellow"; "blue"; "purple"; "orange"; "gray" ]
|> List.map (fun color -> { Tab.parseColor color "Sample" with Value = Some color })
|> console.Tabs

// Second line
[ "#ed1017"; "#67c355"; "#f3d22b"; "#1996f0"; "#9064cb"; "#ff9603"; "#babab8" ]
|> List.map (fun color -> { Tab.parseColor color "Sample" with Value = Some color })
|> fun tabs -> console.Tabs(tabs, 10)

// Third line
[
    "#ed1017", "#9e2e22"
    "#67c355", "#087a3f"
    "#f3d22b", "#faa727"
    "#1996f0", "#0278be"
    "#9064cb", "#6a3390"
    "#ff9603", "#faa727"
    "#babab8", "#333333"
]
|> List.mapi (fun i (color, darker) -> {
    Tab.parseColor color (sprintf "<c:dark-gray|bg:%s|ub>Metric</c>" color)
        with Value = Some <| sprintf "<c:magenta|bg:%s> %02d </c><c:|bg:%s>%% </c>" darker (i * 10) darker
    }
)
|> fun tabs -> console.Tabs(tabs, 10)

Progress bar

For more info see https://github.com/Mpdreamz/shellprogressbar

let total = 10
let progressBar = console.ProgressStart "Starting..." total

for _ in 1 .. total do
    console.ProgressAdvance progressBar

console.ProgressFinish progressBar

TIP: For more examples (async, with children, etc) see the example/Program.fs

Styling

There is a Style settings where you can set up some attributes

type Style = {
    /// Whether and how to show a date time
    ShowDateTime: ShowDateTime

    /// Underline used for main title
    MainTitleUnderline: Underline

    /// Underline used for title
    TitleUnderline: Underline

    /// Underline used for section
    SectionUnderline: Underline

    /// Indentation used in many places (options, date time, ...)
    Indentation: Indentation

    /// Block length is shared for all blocks (success, warning, ...)
    BlockLength: int

    /// Custom tags, available in the markup
    CustomTags: CustomTag list
}

let console = ConsoleStyle(style)

NOTE: There is also a default styles which you can just override

let enhancedStyle = { Style.defaults with Indentation = Indentation "  " }

Custom tags

See more in the example dir

Custom Tags

let style = {
    Style.defaults with
        CustomTags = [
            CustomTag.createWithMarkup (TagName "customTag") {
                Bold = true
                Dim = true
                Italic = true
                Underline = true
                Reverse = true
                StrikeThrough = true
                Foreground = Some "black"
                Background = Some "red"
            }
            CustomTag.createAndParseMarkup (TagName "service") ":#0b6695|bg:#f79333|u" |> orFail
            {
                Tag = TagName "name"
                Markup = MarkupString.create "#23ae91"
            }
        ]
}

let console = ConsoleStyle(style)

console.Section "Custom tags"
console.Message ("Now the <customTag>custom tags</customTag> example")
console.Table [ "Tag"; "Value" ] [
    [ "service"; "<service>domain-context</service>" ]
    [ "name"; "<name>Jon Snow</name>" ]
]

NOTE: There are default custom tags for a simplification of some basic formatting - number to style numbers (<c:magenta>) - u for underline (<c:|u>) - b for bold (<c:|b>) - i for italic (<c:|i>)

Output

There are multiple outputs available

  • Console - prints output with Console.Write functions (Default)
  • Print - prints output with printf and eprintf functions
  • Buffered - buffer every write into string and offers it on Fetch method
  • Stream - writes to the given System.IO.Stream
  • NoMarkup - writes to the given IOutput and remove markup by given Style
  • Combined - combination of other outputs

Usage

use bufferedOutput = Output.BufferOutput(Verbosity.Normal)
let console = ConsoleStyle(bufferedOutput)

console.Message("Hello")

let content: string = bufferedOutput.Fetch()

Verbosity

There are 5 levels of verbosity and every higher (more verbose) level will just add more information to previous - meaning, that VeryVerbose = Normal + Verbose + More and so on (except of Quiet which is oposite)

Level =
    | Quiet
    | Normal
    | Verbose
    | VeryVerbose
    | Debug

Default level is Normal

Some functions have a different output based on current verbosity level

On Quiet only input functions will show an output, no other output is shown (some components are not even inicialized)

  • ask function will show a question

From VeryVerbose it shows time (and date on Debug) on the start of each line (not for multiline outputs)

Note: not every level adds information to output

Usage

You can set a level of verbosity by

let console = ConsoleStyle(Verbosity.Verbose)

// or change it on the fly
console.Verbosity <- Verbosity.Verbose

You can use a verbosity level in your application directly by

console.IsQuiet()
console.IsNormal()
console.IsVerbose()
console.IsVeryVerbose()
console.IsDebug()

Example

As it was mentioned before, each level is addition to previous, so we have (except of Quiet)

⬇️ function AND level ➡️ Quiet Normal Verbose VeryVerbose Debug
IsQuiet() true false false false false
IsNormal() false true true true true
IsVerbose() false false true true true
IsVeryVerbose() false false false true true
IsDebug() false false false false true