blob: 3958316f878ae5e50aee80b781a0b299e50b0e9d [file] [log] [blame]
package pretty
import (
"fmt"
"io"
"reflect"
)
const (
limit = 50
)
var (
commaLFBytes = []byte(",\n")
curlyBytes = []byte("{}")
openCurlyLFBytes = []byte("{\n")
spacePlusLFBytes = []byte(" +\n")
)
type formatter struct {
d int
x interface{}
omit bool
}
// Formatter makes a wrapper, f, that will format x as go source with line
// breaks and tabs. Object f responds to the "%v" formatting verb when both the
// "#" and " " (space) flags are set, for example:
//
// fmt.Sprintf("%# v", Formatter(x))
//
// If one of these two flags is not set, or any other verb is used, f will
// format x according to the usual rules of package fmt.
// In particular, if x satisfies fmt.Formatter, then x.Format will be called.
func Formatter(x interface{}) (f fmt.Formatter) {
return formatter{x: x}
}
func (fo formatter) String() string {
return fmt.Sprint(fo.x) // unwrap it
}
func (fo formatter) passThrough(f fmt.State, c int) {
s := "%"
for i := 0; i < 128; i++ {
if f.Flag(i) {
s += string(i)
}
}
if w, ok := f.Width(); ok {
s += fmt.Sprintf("%d", w)
}
if p, ok := f.Precision(); ok {
s += fmt.Sprintf(".%d", p)
}
s += string(c)
fmt.Fprintf(f, s, fo.x)
}
func (fo formatter) Format(f fmt.State, c int) {
if c == 'v' && f.Flag('#') && f.Flag(' ') {
fo.format(f)
return
}
fo.passThrough(f, c)
}
func (fo formatter) format(w io.Writer) {
v := reflect.ValueOf(fo.x)
switch v.Kind() {
case reflect.String:
lim := limit - 8*fo.d
s := v.String()
z := len(s)
n := (z + lim - 1) / lim
if n < 1 {
n = 1 // empty string still produces output
}
sep := append(spacePlusLFBytes, make([]byte, fo.d)...)
for j := 0; j < fo.d; j++ {
sep[3+j] = '\t'
}
for i := 0; i < n; i++ {
if i > 0 {
w.Write(sep)
}
l, h := i*lim, (i+1)*lim
if h > z {
h = z
}
fmt.Fprintf(w, "%#v", s[l:h])
}
return
case reflect.Ptr:
e := v.Elem()
if !e.IsValid() {
fmt.Fprintf(w, "%#v", fo.x)
} else {
writeByte(w, '&')
fmt.Fprintf(w, "%# v", formatter{d: fo.d, x: e.Interface()})
}
case reflect.Slice:
s := fmt.Sprintf("%#v", fo.x)
if len(s) < limit {
io.WriteString(w, s)
return
}
t := v.Type()
io.WriteString(w, reflect.TypeOf(fo.x).String())
w.Write(openCurlyLFBytes)
for i := 0; i < v.Len(); i++ {
for j := 0; j < fo.d+1; j++ {
writeByte(w, '\t')
}
inner := formatter{d: fo.d + 1, x: v.Index(i).Interface(), omit: t.Elem().Kind() != reflect.Interface}
fmt.Fprintf(w, "%# v", inner)
w.Write(commaLFBytes)
}
for j := 0; j < fo.d; j++ {
writeByte(w, '\t')
}
writeByte(w, '}')
case reflect.Struct:
t := v.Type()
if reflect.DeepEqual(reflect.Zero(t).Interface(), fo.x) {
if !fo.omit {
io.WriteString(w, t.String())
}
w.Write(curlyBytes)
return
}
s := fmt.Sprintf("%#v", fo.x)
if fo.omit {
s = s[len(t.String()):]
}
if len(s) < limit {
io.WriteString(w, s)
return
}
if !fo.omit {
io.WriteString(w, t.String())
}
w.Write(openCurlyLFBytes)
var max int
for i := 0; i < v.NumField(); i++ {
if v := t.Field(i); v.Name != "" {
if len(v.Name)+2 > max {
max = len(v.Name) + 2
}
}
}
for i := 0; i < v.NumField(); i++ {
if f := t.Field(i); f.Name != "" {
for j := 0; j < fo.d+1; j++ {
writeByte(w, '\t')
}
io.WriteString(w, f.Name)
writeByte(w, ':')
for j := len(f.Name) + 1; j < max; j++ {
writeByte(w, ' ')
}
inner := formatter{d: fo.d + 1, x: v.Field(i).Interface()}
io.WriteString(w, fmt.Sprintf("%# v", inner))
w.Write(commaLFBytes)
}
}
for j := 0; j < fo.d; j++ {
writeByte(w, '\t')
}
writeByte(w, '}')
default:
fmt.Fprintf(w, "%#v", fo.x)
}
}
func writeByte(w io.Writer, b byte) {
w.Write([]byte{b})
}