Git Product home page Git Product logo

spectrecoff's Introduction

SpectreCoff

Spectre Console for F# - A thin, opinionated wrapper around Spectre.Console featuring Dumpify.

Available at Nuget: EluciusFTW.SpectreCoff.

Table of Contents

Goals and Philosophy

Before we get into the details, we'd like to outline our goals and our guiding principles for designing the SpectreCoff api surface.

  1. Make Spectre.Console available for console applications in F# in an idiomatic way.

    We expose separate functionality in different modules, as functions, with typed arguments instead of generics resp. object-typing. Since many of Spectre's functions can handle multiple different kinds of content that often means wrapping your content in discriminated union type. We believe that the expression of intent as well as the resulting robustness and clarity far outweigh the 'overhead' of wrapping.

  2. Provide a simple and consistent api surface.

    In SpectreCoff, we follow the structure Spectre.Console provides very closely.

    • Features of Spectre are translated into modules of the same name.
    • Whenever possible, each module exposes a function producing 'the module thing' that is of same name as the module. This will be in form of an OutputPayload.
    • The special module Output (which also defines the type OutputPayload), provides the function toConsole with which everything can be printed.

    In the example of the figlet widget of Spectre, which translates into the figlet module, it looks like this:

    "Hello World"    // figlet content
    |> figlet        // main function of the module producing the figlet as an OutputPayload 
    |> toConsole     // toConsole function of the figlet module

    Of course, for more complex objects, there will be more parameters needed. To achieve this simplicity, the main function uses some defaults (in this example the alignment of the figlet). These defaults can be overwritten 'globally' (as they are just static variables in the module), or passed to other functions taking in more arguments, e.g.,

    "Hello again"
    |> alignedFiglet Left 
    |> toConsole
    
    // if all your figlets should be left-aligned, you can also set that as the default and use the main figlet function
    defaultAlignment <- Left
  3. Add a bit of sprinke on top.

    Spectre is great in providing ways to customize output. We wanted to add a bit on top to make it easier to utilize custom styles consistently throughout applications. Among other things, we decided to include three different semantic levels of output, namely: calm, pumped and edgy, which we also call convenience styles. These are supported throughout the modules, and each style can be customized individually.

  4. Bake the cake and eat it, too.

    We want to feel the joy, and pain, of using our api in the fullest. That's why we have included a cli project in this repository, where we expose the full documentation as well as provide examples for each functionality, using the api itself.

    dotnet run figlet doc            // prints the documentation of the figlet module
    dotnet run figlet example        // shows examples of the module in action
  5. Bonus: Do the same for Dumpify

    Along the way we also added a separate module wrapping the functionality of Dumpify following the same principles. While the main focus remains with Spectre.Console (hence the name of the package), we do think Dumpify's capabilities are useful and related (it uses Spectre.Console internally, after all) enough to just slap it on top of our package.

SpectreCoff Package

SpectreCoff is organized in modules which mirror the features of Spectre.Console. It also contains an additional module exposing the capabilities of Dumpify. The source code for the nuget package can be found in the subfolder /src/spectrecoff/.

Modules

For a list of all modules available, see here. But before checking them out, we advise to read the remainder of this section.

Output Payloads

An important abstraction in SpectreCoff is the OutputPayload type, which is a discriminated union type of all the things that can be sent to the console. As a consequence, any act of producing output in SpectreCoff looks like this:

payload |> toConsole

All modules have functions (often of the same name as the module) to create the respective OutputPayload, like in the example from the introduction above:

"Hello World"    // figlet content
|> figlet        // the function of same name as the module, which creates the OutputPayload 
|> toConsole     // this sends the payload to the console

This function uses some reasonable defaults for layout and styling. These are stored in mutable variables in the module and can be globally adjusted by changing their values. For one-time changes, most modules have another create function - prefixed with custom - which takes in more parameters adjusting the options, layout and styling of the output.

Some of the more complex Spectre.Console objects are inherently mutable, e.g., a table, where rows might be added or removed after creation. In those cases, the main function returns the Spectre.Console type instead, and provides a toOutputPayload function which can be used to map them to a payload just before sending them to the console:

// This creates a Spectre.Console Table
let exampleTable = table columns rows

// This maps and prints the initial table
exampleTable
|> toOutputPayload
|> toConsole

// This adds another row to the table
addRowToTable exampleTable row

// This prints the table again, including the new row
exampleTable
|> toOutputPayload
|> toConsole

Payloads can easily be composed using the Many payload and then printed all at once. Here is a more comprehensive example (without going into details what each payload means at this point):

Many [
    MarkupC (Color.Green, "Hello friends,")           // Use any available color
    BlankLine    
    Pumped "Welcome to my party tomorrow night!"      // Use the Pumped convenience style
    BL                                                // short for BlankLine
    C "Please bring ... "                             // short for Calm
    BI [                                              // short for BulletItems
        C "some snacks,"        
        P "some games,"                               // short for Pumped
        E "and some creepy stories!"                  // short for Edgy
    ]
    C "See you "; P "later ... "; NL                  // Mixing list separators to indicate the line is also possible
    Emoji "alien_monster"
] |> toConsole

You can find a complete list of all payload types here.

Markup

Spectre.Console provides many possibilities to mark up text, which technically are not grouped into features resp. modules. All of these are also encoded in cases of the OutputPayload type, with lot's of helper functions. See here for all details on formatting and styling text output.

Convenience Styles

Additionally, this package introduces three named convenience styles, with which we can easily provide a consistent and semantically meaningful styling across the modules: Calm, Pumped and Edgy.

// Says hello world, emphatically
Pumped "Hello world" |> toConsole

The convenience styles can globally be altered by mutating the corresponding variables, e.g.,

pumpedLook <- { Color = Color.Yellow; Decorations = [ Decoration.Italic ] }

In fact, the package also provides several themes out of the box, which can be selected using the selectTheme function and provide a theme from the SpectreCoffThemes discriminated union type:

// A composite payload of all convenience styles
let sample = 
    Many [                          
        Calm "The calm fox"
        Pumped "jumps pumped"
        Edgy "over the edgy fence"
    ]
// sets the theme and prints the sample
let printExampleUsing theme = 
    selectTheme theme               
    sample |> toConsole

printExampleUsing Volcano
printExampleUsing NeonLights

You can also build your own custom theme and use it with the applyTheme function.

Encoding issues

Note: Several features of Spectre.Console depend on UTF8 Encoding. If you experience unexpected output when handling UTF8 characters check the Spectre.Console best practices.

Versioning

We are using NerdBank.GitVersioning and follow the version scheme: <major>.<minor>.<git-depth> for out releases.

Since this package is a wrapper around Spectre.Console, we will synchronize our major and minor versions with the ones of the Spectre dependency we are wrapping.

Note: In particular, the third number in the version does not have the same meaning as the patches in SemVer. Increments in that number may contain breaking changes, in contrast to patch versions in SemVer.

SpectreCoff Cli

This repository also contains a CLI in the folder in /src/spectrecoff-cli/, with examples of each module, as well as some utilities.

Run Examples

You can see each module in action by using the cli included in this repository in /src/spectrecoff-cli/. Simply run

dotnet run <module-name> example

in the cli subfolder and substitute the module name by and spectrecoff module you can find in the module list. The source files of these commands are also good sources for more examples on how to use the module.

Discover Themes

As mentioned above, SpectreCoff has some built in themes that can be used. The CLI supports discovery of the themes:

dotnet run theme list                              // lists all available themes
dotnet run theme example --theme <themeName>       // shows a sample of the convenience styles of that theme

Related Work

In SpectreCoff we take the approach of providing types and functions wrapping the Spectre.Console api. If you prefer dsls via computation expressions, check out this awesome project (hey, even if you don't, check it out anyway!):

  • fs-spectre - ๐Ÿ‘ป๐Ÿ’ป Spectre.Console with F# style.

Also, if you want to create a cli using Spectre.Console.Cli (recently the cli part was extracted into a separate package), you can use my starter template:

License

Copyright ยฉ Guy Buss, Daniel Muckelbauer

SpectreCoff is provided as-is under the MIT license. See the LICENSE.md file included in the repository.

Feedback and Contributing

All feedback welcome! All contributions are welcome!

spectrecoff's People

Contributors

danielmthedev avatar eluciusftw avatar jedinja avatar mathemamartin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

spectrecoff's Issues

Table.withTitle does crash with an Exception because the Decorations list is empty which fails in aggregate

Using the function

let withTitle title (table: Table) =
    table.Title <- TableTitle (title, toSpectreStyle pumpedLook with Decorations = [] })
    table

fails, because Decorations is empty:

Unhandled exception. System.ArgumentException: Die Eingabeliste war leer. (Parameter 'list')
   at Microsoft.FSharp.Collections.ListModule.Reduce[T](FSharpFunc`2 reduction, FSharpList`1 list) in D:\a\_work\1\s\src\FSharp.Core\list.fs:line 306
   at SpectreCoff.Styling.toSpectreStyle(Look look)
...

The issue most likely also exists in the withCaption function.

Lower FSharp.Core requirements

Hello,

Currently FSharp.Core version is using the one bundled in the .NET version used to package the project.

Is it possible to lower FSharp.Core version, unless you are using a version 7 exclusive feature? This is to avoid conflict and warning between packages.

The action required is to add <PackageReference Update="FSharp.Core" Version="6.0.0" /> in the fsproj. The example provided, should lower to version 6 of FSharp.Core. You use https://nuget.info/ to check what is the result of the packaging without having to publish a new version on NuGet feed.

Having issues getting console output from inside MailboxProcessor?

Hello, appreciate all the work put into this project!

I'm having an issue with getting console output, and I'm too smoothbrained to figure it out.

My library is designed to be referenced inside a .fsx file, to expose a bunch of primitives to the user.
I want to use SpectreCoff from inside a MailboxProcessor. Something like this:

  let private printer (mbox: MailboxProcessor<AType>) =
      
      let rec ringRing () = async {
          let! msg = mbox.Receive ()
          MCD (Color.Blue, [Decoration.Bold], $"{msg.someProperty}") |> toConsole
          do! ringRing ()
      }

      ringRing ()

printer has a public function that takes my AType and Posts into the mailbox.

When I call into the library to do the printing, I get nothing at all. Which is strange.
However, if I reference SpectreCoff and the Spectre.Console inside the .fsx AND print something visible to the console, then I also get the console output from inside the mailbox.
So I must

  • ref the packages in the .fsx
  • open the namespaces in the .fsx
  • print something visible from the .fsx
  • get wanted output.

If I try to just NL |> toConsole that isn't enough, strangely.

Any ideas why this is? I could bodge something in that 'wakes up' the console output, but I feel like I shouldn't have to. Am I missing something in the docs about configuring the console before using it?

Thanks for having a look.

Emoji output

Hello,

The first time I've try to use the Emoji, I've struggle to make it work. After some investigation, I'm create the following small function to enabled it. Do you thing it should/could be incorporated in your library by default, or at least documented somewhere?

spectreconsole/spectre.console#113

let toUtf8Console =
        System.Console.OutputEncoding <- System.Text.Encoding.UTF8
        toConsole

Custom colors not working.

Due to a change in the Spectre.Console.Color.ToString() on August 3 2020 (not sure when it was merged to the main branch though), using custom colors throws an error.

The culprit is the Output.stringify function which uses Spectre.Console.Color.ToString().

Rewriting it as such:

let private stringify (foregroundColorOption: Color option) (backgroundColorOption: Color option) decorations =
    let foregroundColorPart =
        match foregroundColorOption with
        | Some color -> [$"rgb({color.R},{color.G},{color.B})"] //[color.ToString()]
        | None -> []

    let backgroundColorPart =
        match backgroundColorOption with
        | Some color -> [$"on rgb({color.R},{color.G},{color.B})"] //[$"on {color.ToString()}"]
        | None -> []

    let decorationParts = stringifyDecorations decorations

    foregroundColorPart
    @backgroundColorPart
    @decorationParts
    |> joinSeparatedBy " "

seems to compile and work. I just replaced the calls to ToString.

Allow setting PageSize for MultiSelectionPrompt

I have a selection list with exactly 11 entries, which with the default PageSize of 10 means I have to scroll for just the last item.
I'd like to be able to set the PageSize in one of the functions creating a MultiSelectionPrompt.

I'll prepare a PR

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.