A “stylish“, log-level based line printer for human-facing Go CLI applications.
The pencil package provides pencil.Pencil that implements nib.Nib with
- …custom prefixes
- …configurable verbosity level icons
The inkpen package composes pencil.Pencil and additionally provides…
- …colored output
- …automatic TTY and cross-platform terminal color support detection
Please note that this package has mainly been created for my personal use in mind to avoid copying source code between my CLI based projects that require a line printer for human-facing messages. The default configurations might not fit your needs, but the pencil.Pencil and inkpen.Inkpen implementations of the nib.Nib interface have been designed so that they can be flexibly adapted to different use cases and environments.
The nib package provides the currently latest API v0.
The nib.Nib interface consists of six functions that allow to print a formatted message with different verbosity levels:
Compile(v Verbosity, format string, args ...interface{}) string— compiles a message for the verbosity level using the given format and arguments..Debugf(format string, args ...interface{})— writes a message withdebugverbosity level for the given format and arguments.Errorf(format string, args ...interface{})— writes a message witherrorverbosity level for the given format and arguments.Fatalf(format string, args ...interface{})— writes a message withfatalverbosity level for the given format and arguments.Infof(format string, args ...interface{})— writes a message withinfoverbosity level for the given format and arguments.Successf(format string, args ...interface{})— writes a message withsuccessverbosity level for the given format and arguments.Warnf(format string, args ...interface{})— writes a message withwarnverbosity level for the given format and arguments.Writer() io.Writer— returns the underlyingio.Writer.
The pencil package implements this interface including features like custom prefixes and verbosity level icons.
The inkpen package composes pencil.Pencil and additionally comes with additional features like colored output including automatic TTY and cross-platform terminal color support detection.
For more details about the API, available packages and types please see the GoDoc reference documentation.
In addition to the possibility of implementing the nib.Nib interface yourself the pencil.Pencil type can be used:
package main
import (
"github.com/svengreb/nib/inkpen"
"github.com/svengreb/nib/pencil"
)
func main() {
// Create a new pencil
pen := pencil.New()
// ... or inkpen with default configurations where nib.InfoVerbosity is the default verbosity level.
ink := inkpen.New()
// Print a message with "info" level.
fruit := "coconut"
pen.Infof("My favorite fruits are %ss", fruit)
ink.Infof("My favorite fruits are %ss", fruit)
}Both types pencil.Pencil and inkpen.Inkpen were designed so that they can be flexibly adapted to different use cases and environments.
To customize a pencil.Pencil the New function accepts one or more pencil.Option.
A inkpen.Inkpen can also be customized with one or more pencil.Option by passing them to the inkpen.WithPencilOptions(pencilOpts ...pencil.Option) Option function via the New function.
The default verbosity level of pencil.Pencil is nib.InfoVerbosity that prints messages with “info“ scope.
You can adjust it to any other level through the pencil.WithVerbosity(v nib.Verbosity) Option function:
package main
import (
"github.com/svengreb/nib"
"github.com/svengreb/nib/inkpen"
"github.com/svengreb/nib/pencil"
)
func main() {
// Create a new pencil...
pen := pencil.New(pencil.WithVerbosity(nib.DebugVerbosity))
// ...or inkpen with default configurations and set the verbosity level from "info" to "debug" scope.
ink := inkpen.New(inkpen.WithPencilOptions(pencil.WithVerbosity(nib.DebugVerbosity)))
pen.Debugf("Raspberries are my second favorite fruit")
ink.Debugf("Raspberries are my second favorite fruit")
}Note that nib.Verbosity implements encoding.TextUnmarshaler to allow to unmarshal a textual representation of the level or get the nib.Verbosity based on the level scope name:
package main
import (
"fmt"
"os"
"github.com/svengreb/nib"
)
func main() {
// Get the textual representation of the verbosity level.
fmt.Println(nib.InfoVerbosity.String()) // "info"
// Get the nib.Verbosity from the given verbosity level scope name.
// The function returns an non-nil error when the given name is not valid.
v, err := nib.ParseVerbosity("error")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("Verbosity: %s\n", v.String()) // "error"
}To check whether the verbosity level is enabled, the *pencil.Pencil.Enabled(v nib.Verbosity) bool or *inkpen.Inkpen.Enabled(v nib.Verbosity) bool methods can be used:
package main
import (
"fmt"
"github.com/svengreb/nib"
"github.com/svengreb/nib/inkpen"
"github.com/svengreb/nib/pencil"
)
func main() {
// Create a new pencil
pen := pencil.New()
// ... or inkpen...
ink := inkpen.New()
// ...and check whether the verbosity level is enabled.
fmt.Println(pen.Enabled(nib.DebugVerbosity)) // false
fmt.Println(pen.Enabled(nib.ErrorVerbosity)) // true
fmt.Println(ink.Enabled(nib.FatalVerbosity)) // true
}The default icons are Unicode characters which are supported by almost all terminals nowadays, but there might be cases where you want to use simple ASCII characters instead.
You can change any verbosity level icon through the pencil.WithIcons(icons map[nib.Verbosity]rune) Option function:
package main
import (
"github.com/svengreb/nib"
"github.com/svengreb/nib/inkpen"
"github.com/svengreb/nib/pencil"
)
func main() {
// Create a new pencil...
pen := pencil.New(pencil.WithIcons(map[nib.Verbosity]rune{nib.InfoVerbosity: '>'}))
// ...or inkpen, that uses Unicode characters as verbosity level icons by default, and change the icon for the "info"
// verbosity level scope.
ink := inkpen.New(inkpen.WithPencilOptions(
pencil.WithIcons(map[nib.Verbosity]rune{nib.InfoVerbosity: '>'}),
))
pen.Infof("Cashew nuts can be combined very well with yogurt")
ink.Infof("Cashew nuts can be combined very well with yogurt")
}inkpen.Inkpen allows to customize the color of icons through the inkpen.WithIconColorFuncs(iconColorFuncs map[nib.Verbosity]IconColorFunc) Option function:
package main
import (
"github.com/fatih/color"
"github.com/svengreb/nib"
"github.com/svengreb/nib/inkpen"
)
func main() {
// Create a new inkpen and change the color function for the "info" verbosity level scope to use green instead of
// blue as foreground color.
ink := inkpen.New(inkpen.WithIconColorFuncs(map[nib.Verbosity]inkpen.IconColorFunc{
nib.InfoVerbosity: color.New(color.FgGreen).Sprintf,
}))
ink.Infof("Almonds taste very good with vanilla")
}To disable verbosity level icons to be printed at all use the pencil.UseIcons(useIcons bool) Option function:
package main
import (
"github.com/svengreb/nib/inkpen"
"github.com/svengreb/nib/pencil"
)
func main() {
// Create a new pencil...
pen := pencil.New(pencil.UseIcons(false))
// ...or inkpen and disable all verbosity level icons.
ink := inkpen.New(inkpen.WithPencilOptions(pencil.UseIcons(false)))
pen.Errorf("Cane sugar is not necessary for a delicious yogurt")
ink.Errorf("Cane sugar is not necessary for a delicious yogurt")
}Note that the provided map of icons gets merged with the default map in order to ensure there are no missing icons.
See the pencil package to get an overview of the icon characters that are used by default.
To check if verbosity level icons are enabled, the *pencil.Pencil.IconsEnabled() bool or *inkpen.Inkpen.IconsEnabled() bool methods can be used:
package main
import (
"fmt"
"github.com/svengreb/nib/pencil"
)
func main() {
// Create a new pencil...
pen := pencil.New()
// ...and check whether verbosity level icons are enabled.
fmt.Println(pen.IconsEnabled()) // true
}By default only the verbosity level icons are printed as prefix before the given message format and arguments.
You can add any amount of custom prefixes through the pencil.WithPrefixes(prefixes ...string) Option function:
package main
import (
"github.com/svengreb/nib/inkpen"
"github.com/svengreb/nib/pencil"
)
func main() {
// Create a new pencil...
pen := pencil.New(pencil.WithPrefixes("[fruit mixer]"))
// ... or inkpen and add a custom prefix that gets placed before the actual message.
ink := inkpen.New(inkpen.WithPencilOptions(pencil.WithPrefixes("[fruit mixer]")))
pen.Infof("Strawberries are also a very tasty ingredient for low-fat quark")
ink.Infof("Strawberries are also a very tasty ingredient for low-fat quark")
}By default pencil.Pencil uses os.Stderr while inkpen.Inkpen uses color.Output which in turn is a exported variable that makes use of github.com/mattn/go-colorable, a package for colored TTY output on multiple platforms.
You can use any io.Writer through the pencil.WithWriter(writer io.Writer) Option function:
package main
import (
"os"
"github.com/svengreb/nib/inkpen"
"github.com/svengreb/nib/pencil"
)
func main() {
// Create a new pencil...
pen := pencil.New(pencil.WithWriter(os.Stderr))
// ... or inkpen and change the output writer to use the OS error stream.
ink := inkpen.New(inkpen.WithPencilOptions(pencil.WithWriter(os.Stderr)))
pen.Errorf("Blueberries mixed with raspberries and yoghurt are a delicious dream")
ink.Errorf("Blueberries mixed with raspberries and yoghurt are a delicious dream")
}Both types pencil.Pencil and inkpen.Inkpen use recommended io.Writer by default to provide optimal compatibility for their specific features like colored output.
To allow to either reuse the default or configured io.Writer the Writer() io.Writer method of the nib.Nib API interface can be used:
package main
import (
"os"
"github.com/svengreb/nib/inkpen"
"github.com/svengreb/nib/pencil"
)
func main() {
// Create a new pencil...
pen := pencil.New(pencil.WithWriter(os.Stderr))
// ... or inkpen.
ink := inkpen.New()
_, _ = fmt.Fprintln(pen.Writer(), "Brazil nuts are also a delicious and healthy snack between meals")
_, _ = fmt.Fprintln(ink.Writer(), "Brazil nuts are also a delicious and healthy snack between meals")
}nib is an open source project and contributions are always welcome!
There are many ways to contribute, from writing- and improving documentation and tutorials, reporting bugs, submitting enhancement suggestions that can be added to nib by submitting pull requests.
Please take a moment to read the contributing guide to learn about the development process, the styleguides to which this project adheres as well as the branch organization and versioning model.
The guide also includes information about minimal, complete, and verifiable examples and other ways to contribute to the project like improving existing issues and giving feedback on issues and pull requests.
Copyright © 2019-present Sven Greb