Skip to content

Instantly share code, notes, and snippets.

@rgorsuch
Created April 3, 2019 18:38
Show Gist options
  • Save rgorsuch/279db36ffc9b671334b8f8e19de3e9c1 to your computer and use it in GitHub Desktop.
Save rgorsuch/279db36ffc9b671334b8f8e19de3e9c1 to your computer and use it in GitHub Desktop.
package main
// This is a one-file boil-down of github.com/k0kubun/pp + github.com/mattn/go-colorable.
// It can do terminal coloring and pretty printing for ease of use in repl.it
// Paste it into a file in your repl.it and then use it like this, no import necessary.
//
// func main() {
// pp.Println(struct{ message, whom string }{
// message: "Hello", whom: "World"},
// )
// }
import (
"errors"
"fmt"
"io"
"os"
"reflect"
"runtime"
"sync"
"bytes"
"regexp"
"strconv"
"strings"
"text/tabwriter"
"time"
)
var (
out io.Writer
outLock sync.Mutex
defaultOut = NewColorableStdout()
currentScheme ColorScheme
// WithLineInfo add file name and line information to output
// call this function with care, because getting stack has performance penalty
WithLineInfo = false
pp PrettyPrinter
)
func init() {
out = defaultOut
currentScheme = defaultScheme
pp = NewPrettyPrinter()
}
type PrettyPrinter struct{}
func NewPrettyPrinter() PrettyPrinter {
return PrettyPrinter{}
}
// Print prints given arguments.
func Print(a ...interface{}) (n int, err error) {
return fmt.Fprint(out, formatAll(a)...)
}
// Printf prints a given format.
func (p PrettyPrinter) Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(out, format, formatAll(a)...)
}
// Println prints given arguments with newline.
func (p PrettyPrinter) Println(a ...interface{}) (n int, err error) {
return fmt.Fprintln(out, formatAll(a)...)
}
// Sprint formats given arguemnts and returns the result as string.
func (p PrettyPrinter) Sprint(a ...interface{}) string {
return fmt.Sprint(formatAll(a)...)
}
// Sprintf formats with pretty print and returns the result as string.
func (p PrettyPrinter) Sprintf(format string, a ...interface{}) string {
return fmt.Sprintf(format, formatAll(a)...)
}
// Sprintln formats given arguemnts with newline and returns the result as string.
func (p PrettyPrinter) Sprintln(a ...interface{}) string {
return fmt.Sprintln(formatAll(a)...)
}
// Fprint prints given arguments to a given writer.
func (p PrettyPrinter) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprint(w, formatAll(a)...)
}
// Fprintf prints format to a given writer.
func (p PrettyPrinter) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(w, format, formatAll(a)...)
}
// Fprintln prints given arguments to a given writer with newline.
func (p PrettyPrinter) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprintln(w, formatAll(a)...)
}
// Errorf formats given arguments and returns it as error type.
func (p PrettyPrinter) Errorf(format string, a ...interface{}) error {
return errors.New(p.Sprintf(format, a...))
}
// Fatal prints given arguments and finishes execution with exit status 1.
func (p PrettyPrinter) Fatal(a ...interface{}) {
fmt.Fprint(out, formatAll(a)...)
os.Exit(1)
}
// Fatalf prints a given format and finishes execution with exit status 1.
func (p PrettyPrinter) Fatalf(format string, a ...interface{}) {
fmt.Fprintf(out, format, formatAll(a)...)
os.Exit(1)
}
// Fatalln prints given arguments with newline and finishes execution with exit status 1.
func (p PrettyPrinter) Fatalln(a ...interface{}) {
fmt.Fprintln(out, formatAll(a)...)
os.Exit(1)
}
// Change Print* functions' output to a given writer.
// For example, you can limit output by ENV.
//
// func init() {
// if os.Getenv("DEBUG") == "" {
// pp.SetDefaultOutput(ioutil.Discard)
// }
// }
func (p PrettyPrinter) SetDefaultOutput(o io.Writer) {
outLock.Lock()
out = o
outLock.Unlock()
}
// GetDefaultOutput returns pp's default output.
func (p PrettyPrinter) GetDefaultOutput() io.Writer {
return out
}
// Change Print* functions' output to default one.
func (p PrettyPrinter) ResetDefaultOutput() {
outLock.Lock()
out = defaultOut
outLock.Unlock()
}
// SetColorScheme takes a colorscheme used by all future Print calls.
func (p PrettyPrinter) SetColorScheme(scheme ColorScheme) {
scheme.fixColors()
currentScheme = scheme
}
// ResetColorScheme resets colorscheme to default.
func (p PrettyPrinter) ResetColorScheme() {
currentScheme = defaultScheme
}
func formatAll(objects []interface{}) []interface{} {
results := []interface{}{}
if WithLineInfo {
_, fn, line, _ := runtime.Caller(2) // 2 because current Caller is pp itself
results = append(results, fmt.Sprintf("%s:%d\n", fn, line))
}
for _, object := range objects {
results = append(results, format(object))
}
return results
}
// file github.com/k0kubun/pp/printer.go
const (
indentWidth = 2
)
var (
// If the length of array or slice is larger than this,
// the buffer will be shorten as {...}.
BufferFoldThreshold = 1024
// PrintMapTypes when set to true will have map types will always appended to maps.
PrintMapTypes = true
)
func format(object interface{}) string {
return newPrinter(object).String()
}
func newPrinter(object interface{}) *printer {
buffer := bytes.NewBufferString("")
tw := new(tabwriter.Writer)
tw.Init(buffer, indentWidth, 0, 1, ' ', 0)
return &printer{
Buffer: buffer,
tw: tw,
depth: 0,
value: reflect.ValueOf(object),
visited: map[uintptr]bool{},
}
}
type printer struct {
*bytes.Buffer
tw *tabwriter.Writer
depth int
value reflect.Value
visited map[uintptr]bool
}
func (p *printer) String() string {
switch p.value.Kind() {
case reflect.Bool:
p.colorPrint(p.raw(), currentScheme.Bool)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Uintptr, reflect.Complex64, reflect.Complex128:
p.colorPrint(p.raw(), currentScheme.Integer)
case reflect.Float32, reflect.Float64:
p.colorPrint(p.raw(), currentScheme.Float)
case reflect.String:
p.printString()
case reflect.Map:
p.printMap()
case reflect.Struct:
p.printStruct()
case reflect.Array, reflect.Slice:
p.printSlice()
case reflect.Chan:
p.printf("(%s)(%s)", p.typeString(), p.pointerAddr())
case reflect.Interface:
p.printInterface()
case reflect.Ptr:
p.printPtr()
case reflect.Func:
p.printf("%s {...}", p.typeString())
case reflect.UnsafePointer:
p.printf("%s(%s)", p.typeString(), p.pointerAddr())
case reflect.Invalid:
p.print(p.nil())
default:
p.print(p.raw())
}
p.tw.Flush()
return p.Buffer.String()
}
func (p *printer) print(text string) {
fmt.Fprint(p.tw, text)
}
func (p *printer) printf(format string, args ...interface{}) {
text := fmt.Sprintf(format, args...)
p.print(text)
}
func (p *printer) println(text string) {
p.print(text + "\n")
}
func (p *printer) indentPrint(text string) {
p.print(p.indent() + text)
}
func (p *printer) indentPrintf(format string, args ...interface{}) {
text := fmt.Sprintf(format, args...)
p.indentPrint(text)
}
func (p *printer) colorPrint(text string, color uint16) {
p.print(colorize(text, color))
}
func (p *printer) printString() {
quoted := strconv.Quote(p.value.String())
quoted = quoted[1 : len(quoted)-1]
p.colorPrint(`"`, currentScheme.StringQuotation)
for len(quoted) > 0 {
pos := strings.IndexByte(quoted, '\\')
if pos == -1 {
p.colorPrint(quoted, currentScheme.String)
break
}
if pos != 0 {
p.colorPrint(quoted[0:pos], currentScheme.String)
}
n := 1
switch quoted[pos+1] {
case 'x': // "\x00"
n = 3
case 'u': // "\u0000"
n = 5
case 'U': // "\U00000000"
n = 9
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': // "\000"
n = 3
}
p.colorPrint(quoted[pos:pos+n+1], currentScheme.EscapedChar)
quoted = quoted[pos+n+1:]
}
p.colorPrint(`"`, currentScheme.StringQuotation)
}
func (p *printer) printMap() {
if p.value.Len() == 0 {
p.printf("%s{}", p.typeString())
return
}
if p.visited[p.value.Pointer()] {
p.printf("%s{...}", p.typeString())
return
}
p.visited[p.value.Pointer()] = true
if PrintMapTypes {
p.printf("%s{\n", p.typeString())
} else {
p.println("{")
}
p.indented(func() {
keys := p.value.MapKeys()
for i := 0; i < p.value.Len(); i++ {
value := p.value.MapIndex(keys[i])
p.indentPrintf("%s:\t%s,\n", p.format(keys[i]), p.format(value))
}
})
p.indentPrint("}")
}
func (p *printer) printStruct() {
if p.value.Type().String() == "time.Time" {
p.printTime()
return
}
if p.value.NumField() == 0 {
p.print(p.typeString() + "{}")
return
}
p.println(p.typeString() + "{")
p.indented(func() {
for i := 0; i < p.value.NumField(); i++ {
field := colorize(p.value.Type().Field(i).Name, currentScheme.FieldName)
value := p.value.Field(i)
p.indentPrintf("%s:\t%s,\n", field, p.format(value))
}
})
p.indentPrint("}")
}
func (p *printer) printTime() {
if !p.value.CanInterface() {
p.printf("(unexported time.Time)")
return
}
tm := p.value.Interface().(time.Time)
p.printf(
"%s-%s-%s %s:%s:%s %s",
colorize(strconv.Itoa(tm.Year()), currentScheme.Time),
colorize(fmt.Sprintf("%02d", tm.Month()), currentScheme.Time),
colorize(fmt.Sprintf("%02d", tm.Day()), currentScheme.Time),
colorize(fmt.Sprintf("%02d", tm.Hour()), currentScheme.Time),
colorize(fmt.Sprintf("%02d", tm.Minute()), currentScheme.Time),
colorize(fmt.Sprintf("%02d", tm.Second()), currentScheme.Time),
colorize(tm.Location().String(), currentScheme.Time),
)
}
func (p *printer) printSlice() {
if p.value.Len() == 0 {
p.printf("%s{}", p.typeString())
return
}
if p.value.Kind() == reflect.Slice {
if p.visited[p.value.Pointer()] {
// Stop travarsing cyclic reference
p.printf("%s{...}", p.typeString())
return
}
p.visited[p.value.Pointer()] = true
}
// Fold a large buffer
if p.value.Len() > BufferFoldThreshold {
p.printf("%s{...}", p.typeString())
return
}
p.println(p.typeString() + "{")
p.indented(func() {
groupsize := 0
switch p.value.Type().Elem().Kind() {
case reflect.Uint8:
groupsize = 16
case reflect.Uint16:
groupsize = 8
case reflect.Uint32:
groupsize = 8
case reflect.Uint64:
groupsize = 4
}
if groupsize > 0 {
for i := 0; i < p.value.Len(); i++ {
// indent for new group
if i%groupsize == 0 {
p.print(p.indent())
}
// slice element
p.printf("%s,", p.format(p.value.Index(i)))
// space or newline
if (i+1)%groupsize == 0 || i+1 == p.value.Len() {
p.print("\n")
} else {
p.print(" ")
}
}
} else {
for i := 0; i < p.value.Len(); i++ {
p.indentPrintf("%s,\n", p.format(p.value.Index(i)))
}
}
})
p.indentPrint("}")
}
func (p *printer) printInterface() {
e := p.value.Elem()
if e.Kind() == reflect.Invalid {
p.print(p.nil())
} else if e.IsValid() {
p.print(p.format(e))
} else {
p.printf("%s(%s)", p.typeString(), p.nil())
}
}
func (p *printer) printPtr() {
if p.visited[p.value.Pointer()] {
p.printf("&%s{...}", p.elemTypeString())
return
}
if p.value.Pointer() != 0 {
p.visited[p.value.Pointer()] = true
}
if p.value.Elem().IsValid() {
p.printf("&%s", p.format(p.value.Elem()))
} else {
p.printf("(%s)(%s)", p.typeString(), p.nil())
}
}
func (p *printer) pointerAddr() string {
return colorize(fmt.Sprintf("%#v", p.value.Pointer()), currentScheme.PointerAdress)
}
func (p *printer) typeString() string {
return p.colorizeType(p.value.Type().String())
}
func (p *printer) elemTypeString() string {
return p.colorizeType(p.value.Elem().Type().String())
}
func (p *printer) colorizeType(t string) string {
prefix := ""
if p.matchRegexp(t, `^\[\].+$`) {
prefix = "[]"
t = t[2:]
}
if p.matchRegexp(t, `^\[\d+\].+$`) {
num := regexp.MustCompile(`\d+`).FindString(t)
prefix = fmt.Sprintf("[%s]", colorize(num, currentScheme.ObjectLength))
t = t[2+len(num):]
}
if p.matchRegexp(t, `^[^\.]+\.[^\.]+$`) {
ts := strings.Split(t, ".")
t = fmt.Sprintf("%s.%s", ts[0], colorize(ts[1], currentScheme.StructName))
} else {
t = colorize(t, currentScheme.StructName)
}
return prefix + t
}
func (p *printer) matchRegexp(text, exp string) bool {
return regexp.MustCompile(exp).MatchString(text)
}
func (p *printer) indented(proc func()) {
p.depth++
proc()
p.depth--
}
func (p *printer) raw() string {
// Some value causes panic when Interface() is called.
switch p.value.Kind() {
case reflect.Bool:
return fmt.Sprintf("%#v", p.value.Bool())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return fmt.Sprintf("%#v", p.value.Int())
case reflect.Uint, reflect.Uintptr:
return fmt.Sprintf("%#v", p.value.Uint())
case reflect.Uint8:
return fmt.Sprintf("0x%02x", p.value.Uint())
case reflect.Uint16:
return fmt.Sprintf("0x%04x", p.value.Uint())
case reflect.Uint32:
return fmt.Sprintf("0x%08x", p.value.Uint())
case reflect.Uint64:
return fmt.Sprintf("0x%016x", p.value.Uint())
case reflect.Float32, reflect.Float64:
return fmt.Sprintf("%f", p.value.Float())
case reflect.Complex64, reflect.Complex128:
return fmt.Sprintf("%#v", p.value.Complex())
default:
return fmt.Sprintf("%#v", p.value.Interface())
}
}
func (p *printer) nil() string {
return colorize("nil", currentScheme.Nil)
}
func (p *printer) format(object interface{}) string {
pp := newPrinter(object)
pp.depth = p.depth
pp.visited = p.visited
if value, ok := object.(reflect.Value); ok {
pp.value = value
}
return pp.String()
}
func (p *printer) indent() string {
return strings.Repeat("\t", p.depth)
}
// file github.com/k0kubn/pp/color.go
const (
// No color
NoColor uint16 = 1 << 15
)
const (
// Foreground colors for ColorScheme.
_ uint16 = iota | NoColor
Black
Red
Green
Yellow
Blue
Magenta
Cyan
White
bitsForeground = 0
maskForegorund = 0xf
ansiForegroundOffset = 30 - 1
)
const (
// Background colors for ColorScheme.
_ uint16 = iota<<bitsBackground | NoColor
BackgroundBlack
BackgroundRed
BackgroundGreen
BackgroundYellow
BackgroundBlue
BackgroundMagenta
BackgroundCyan
BackgroundWhite
bitsBackground = 4
maskBackground = 0xf << bitsBackground
ansiBackgroundOffset = 40 - 1
)
const (
// Bold flag for ColorScheme.
Bold uint16 = 1<<bitsBold | NoColor
bitsBold = 8
maskBold = 1 << bitsBold
ansiBold = 1
)
// To use with SetColorScheme.
type ColorScheme struct {
Bool uint16
Integer uint16
Float uint16
String uint16
StringQuotation uint16
EscapedChar uint16
FieldName uint16
PointerAdress uint16
Nil uint16
Time uint16
StructName uint16
ObjectLength uint16
}
var (
// If you set false to this variable, you can use pretty formatter
// without coloring.
ColoringEnabled = true
defaultScheme = ColorScheme{
Bool: Cyan | Bold,
Integer: Blue | Bold,
Float: Magenta | Bold,
String: Red,
StringQuotation: Red | Bold,
EscapedChar: Magenta | Bold,
FieldName: Yellow,
PointerAdress: Blue | Bold,
Nil: Cyan | Bold,
Time: Blue | Bold,
StructName: Green,
ObjectLength: Blue,
}
)
func (cs *ColorScheme) fixColors() {
typ := reflect.Indirect(reflect.ValueOf(cs))
defaultType := reflect.ValueOf(defaultScheme)
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
if field.Uint() == 0 {
field.SetUint(defaultType.Field(i).Uint())
}
}
}
func colorize(text string, color uint16) string {
if !ColoringEnabled {
return text
}
foreground := color & maskForegorund >> bitsForeground
background := color & maskBackground >> bitsBackground
bold := color & maskBold
if foreground == 0 && background == 0 && bold == 0 {
return text
}
modBold := ""
modForeground := ""
modBackground := ""
if bold > 0 {
modBold = "\033[1m"
}
if foreground > 0 {
modForeground = fmt.Sprintf("\033[%dm", foreground+ansiForegroundOffset)
}
if background > 0 {
modBackground = fmt.Sprintf("\033[%dm", background+ansiBackgroundOffset)
}
return fmt.Sprintf("%s%s%s%s\033[0m", modForeground, modBackground, modBold, text)
}
// file github.com/mattn/go-colorable/colorable_others.go
// NewColorable return new instance of Writer which handle escape sequence.
func NewColorable(file *os.File) io.Writer {
if file == nil {
panic("nil passed instead of *os.File to NewColorable()")
}
return file
}
// NewColorableStdout return new instance of Writer which handle escape sequence for stdout.
func NewColorableStdout() io.Writer {
return os.Stdout
}
// NewColorableStderr return new instance of Writer which handle escape sequence for stderr.
func NewColorableStderr() io.Writer {
return os.Stderr
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment