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 withdebug
verbosity level for the given format and arguments.Errorf(format string, args ...interface{})
— writes a message witherror
verbosity level for the given format and arguments.Fatalf(format string, args ...interface{})
— writes a message withfatal
verbosity level for the given format and arguments.Infof(format string, args ...interface{})
— writes a message withinfo
verbosity level for the given format and arguments.Successf(format string, args ...interface{})
— writes a message withsuccess
verbosity level for the given format and arguments.Warnf(format string, args ...interface{})
— writes a message withwarn
verbosity 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