Created
April 3, 2019 18:38
-
-
Save rgorsuch/279db36ffc9b671334b8f8e19de3e9c1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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