Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
328 lines
7.1 KiB
328 lines
7.1 KiB
package pretty |
|
|
|
import ( |
|
"fmt" |
|
"io" |
|
"reflect" |
|
"strconv" |
|
"text/tabwriter" |
|
|
|
"github.com/kr/text" |
|
) |
|
|
|
type formatter struct { |
|
v reflect.Value |
|
force bool |
|
quote 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{v: reflect.ValueOf(x), quote: true} |
|
} |
|
|
|
func (fo formatter) String() string { |
|
return fmt.Sprint(fo.v.Interface()) // unwrap it |
|
} |
|
|
|
func (fo formatter) passThrough(f fmt.State, c rune) { |
|
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.v.Interface()) |
|
} |
|
|
|
func (fo formatter) Format(f fmt.State, c rune) { |
|
if fo.force || c == 'v' && f.Flag('#') && f.Flag(' ') { |
|
w := tabwriter.NewWriter(f, 4, 4, 1, ' ', 0) |
|
p := &printer{tw: w, Writer: w, visited: make(map[visit]int)} |
|
p.printValue(fo.v, true, fo.quote) |
|
w.Flush() |
|
return |
|
} |
|
fo.passThrough(f, c) |
|
} |
|
|
|
type printer struct { |
|
io.Writer |
|
tw *tabwriter.Writer |
|
visited map[visit]int |
|
depth int |
|
} |
|
|
|
func (p *printer) indent() *printer { |
|
q := *p |
|
q.tw = tabwriter.NewWriter(p.Writer, 4, 4, 1, ' ', 0) |
|
q.Writer = text.NewIndentWriter(q.tw, []byte{'\t'}) |
|
return &q |
|
} |
|
|
|
func (p *printer) printInline(v reflect.Value, x interface{}, showType bool) { |
|
if showType { |
|
io.WriteString(p, v.Type().String()) |
|
fmt.Fprintf(p, "(%#v)", x) |
|
} else { |
|
fmt.Fprintf(p, "%#v", x) |
|
} |
|
} |
|
|
|
// printValue must keep track of already-printed pointer values to avoid |
|
// infinite recursion. |
|
type visit struct { |
|
v uintptr |
|
typ reflect.Type |
|
} |
|
|
|
func (p *printer) printValue(v reflect.Value, showType, quote bool) { |
|
if p.depth > 10 { |
|
io.WriteString(p, "!%v(DEPTH EXCEEDED)") |
|
return |
|
} |
|
|
|
switch v.Kind() { |
|
case reflect.Bool: |
|
p.printInline(v, v.Bool(), showType) |
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
|
p.printInline(v, v.Int(), showType) |
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: |
|
p.printInline(v, v.Uint(), showType) |
|
case reflect.Float32, reflect.Float64: |
|
p.printInline(v, v.Float(), showType) |
|
case reflect.Complex64, reflect.Complex128: |
|
fmt.Fprintf(p, "%#v", v.Complex()) |
|
case reflect.String: |
|
p.fmtString(v.String(), quote) |
|
case reflect.Map: |
|
t := v.Type() |
|
if showType { |
|
io.WriteString(p, t.String()) |
|
} |
|
writeByte(p, '{') |
|
if nonzero(v) { |
|
expand := !canInline(v.Type()) |
|
pp := p |
|
if expand { |
|
writeByte(p, '\n') |
|
pp = p.indent() |
|
} |
|
keys := v.MapKeys() |
|
for i := 0; i < v.Len(); i++ { |
|
showTypeInStruct := true |
|
k := keys[i] |
|
mv := v.MapIndex(k) |
|
pp.printValue(k, false, true) |
|
writeByte(pp, ':') |
|
if expand { |
|
writeByte(pp, '\t') |
|
} |
|
showTypeInStruct = t.Elem().Kind() == reflect.Interface |
|
pp.printValue(mv, showTypeInStruct, true) |
|
if expand { |
|
io.WriteString(pp, ",\n") |
|
} else if i < v.Len()-1 { |
|
io.WriteString(pp, ", ") |
|
} |
|
} |
|
if expand { |
|
pp.tw.Flush() |
|
} |
|
} |
|
writeByte(p, '}') |
|
case reflect.Struct: |
|
t := v.Type() |
|
if v.CanAddr() { |
|
addr := v.UnsafeAddr() |
|
vis := visit{addr, t} |
|
if vd, ok := p.visited[vis]; ok && vd < p.depth { |
|
p.fmtString(t.String()+"{(CYCLIC REFERENCE)}", false) |
|
break // don't print v again |
|
} |
|
p.visited[vis] = p.depth |
|
} |
|
|
|
if showType { |
|
io.WriteString(p, t.String()) |
|
} |
|
writeByte(p, '{') |
|
if nonzero(v) { |
|
expand := !canInline(v.Type()) |
|
pp := p |
|
if expand { |
|
writeByte(p, '\n') |
|
pp = p.indent() |
|
} |
|
for i := 0; i < v.NumField(); i++ { |
|
showTypeInStruct := true |
|
if f := t.Field(i); f.Name != "" { |
|
io.WriteString(pp, f.Name) |
|
writeByte(pp, ':') |
|
if expand { |
|
writeByte(pp, '\t') |
|
} |
|
showTypeInStruct = labelType(f.Type) |
|
} |
|
pp.printValue(getField(v, i), showTypeInStruct, true) |
|
if expand { |
|
io.WriteString(pp, ",\n") |
|
} else if i < v.NumField()-1 { |
|
io.WriteString(pp, ", ") |
|
} |
|
} |
|
if expand { |
|
pp.tw.Flush() |
|
} |
|
} |
|
writeByte(p, '}') |
|
case reflect.Interface: |
|
switch e := v.Elem(); { |
|
case e.Kind() == reflect.Invalid: |
|
io.WriteString(p, "nil") |
|
case e.IsValid(): |
|
pp := *p |
|
pp.depth++ |
|
pp.printValue(e, showType, true) |
|
default: |
|
io.WriteString(p, v.Type().String()) |
|
io.WriteString(p, "(nil)") |
|
} |
|
case reflect.Array, reflect.Slice: |
|
t := v.Type() |
|
if showType { |
|
io.WriteString(p, t.String()) |
|
} |
|
if v.Kind() == reflect.Slice && v.IsNil() && showType { |
|
io.WriteString(p, "(nil)") |
|
break |
|
} |
|
if v.Kind() == reflect.Slice && v.IsNil() { |
|
io.WriteString(p, "nil") |
|
break |
|
} |
|
writeByte(p, '{') |
|
expand := !canInline(v.Type()) |
|
pp := p |
|
if expand { |
|
writeByte(p, '\n') |
|
pp = p.indent() |
|
} |
|
for i := 0; i < v.Len(); i++ { |
|
showTypeInSlice := t.Elem().Kind() == reflect.Interface |
|
pp.printValue(v.Index(i), showTypeInSlice, true) |
|
if expand { |
|
io.WriteString(pp, ",\n") |
|
} else if i < v.Len()-1 { |
|
io.WriteString(pp, ", ") |
|
} |
|
} |
|
if expand { |
|
pp.tw.Flush() |
|
} |
|
writeByte(p, '}') |
|
case reflect.Ptr: |
|
e := v.Elem() |
|
if !e.IsValid() { |
|
writeByte(p, '(') |
|
io.WriteString(p, v.Type().String()) |
|
io.WriteString(p, ")(nil)") |
|
} else { |
|
pp := *p |
|
pp.depth++ |
|
writeByte(pp, '&') |
|
pp.printValue(e, true, true) |
|
} |
|
case reflect.Chan: |
|
x := v.Pointer() |
|
if showType { |
|
writeByte(p, '(') |
|
io.WriteString(p, v.Type().String()) |
|
fmt.Fprintf(p, ")(%#v)", x) |
|
} else { |
|
fmt.Fprintf(p, "%#v", x) |
|
} |
|
case reflect.Func: |
|
io.WriteString(p, v.Type().String()) |
|
io.WriteString(p, " {...}") |
|
case reflect.UnsafePointer: |
|
p.printInline(v, v.Pointer(), showType) |
|
case reflect.Invalid: |
|
io.WriteString(p, "nil") |
|
} |
|
} |
|
|
|
func canInline(t reflect.Type) bool { |
|
switch t.Kind() { |
|
case reflect.Map: |
|
return !canExpand(t.Elem()) |
|
case reflect.Struct: |
|
for i := 0; i < t.NumField(); i++ { |
|
if canExpand(t.Field(i).Type) { |
|
return false |
|
} |
|
} |
|
return true |
|
case reflect.Interface: |
|
return false |
|
case reflect.Array, reflect.Slice: |
|
return !canExpand(t.Elem()) |
|
case reflect.Ptr: |
|
return false |
|
case reflect.Chan, reflect.Func, reflect.UnsafePointer: |
|
return false |
|
} |
|
return true |
|
} |
|
|
|
func canExpand(t reflect.Type) bool { |
|
switch t.Kind() { |
|
case reflect.Map, reflect.Struct, |
|
reflect.Interface, reflect.Array, reflect.Slice, |
|
reflect.Ptr: |
|
return true |
|
} |
|
return false |
|
} |
|
|
|
func labelType(t reflect.Type) bool { |
|
switch t.Kind() { |
|
case reflect.Interface, reflect.Struct: |
|
return true |
|
} |
|
return false |
|
} |
|
|
|
func (p *printer) fmtString(s string, quote bool) { |
|
if quote { |
|
s = strconv.Quote(s) |
|
} |
|
io.WriteString(p, s) |
|
} |
|
|
|
func writeByte(w io.Writer, b byte) { |
|
w.Write([]byte{b}) |
|
} |
|
|
|
func getField(v reflect.Value, i int) reflect.Value { |
|
val := v.Field(i) |
|
if val.Kind() == reflect.Interface && !val.IsNil() { |
|
val = val.Elem() |
|
} |
|
return val |
|
}
|
|
|