// Copyright 2013 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package pretty import ( "bufio" "bytes" "fmt" "io" "strconv" "strings" ) // a formatter stores stateful formatting information as well as being // an io.Writer for simplicity. type formatter struct { *bufio.Writer *Config // Self-referential structure tracking tagNumbers map[int]int // tagNumbers[id] = <#n> } // newFormatter creates a new buffered formatter. For the output to be written // to the given writer, this must be accompanied by a call to write (or Flush). func newFormatter(cfg *Config, w io.Writer) *formatter { return &formatter{ Writer: bufio.NewWriter(w), Config: cfg, tagNumbers: make(map[int]int), } } func (f *formatter) write(n node) { defer f.Flush() n.format(f, "") } func (f *formatter) tagFor(id int) int { if tag, ok := f.tagNumbers[id]; ok { return tag } if f.tagNumbers == nil { return 0 } tag := len(f.tagNumbers) + 1 f.tagNumbers[id] = tag return tag } type node interface { format(f *formatter, indent string) } func (f *formatter) compactString(n node) string { switch k := n.(type) { case stringVal: return string(k) case rawVal: return string(k) } buf := new(bytes.Buffer) f2 := newFormatter(&Config{Compact: true}, buf) f2.tagNumbers = f.tagNumbers // reuse tagNumbers just in case f2.write(n) return buf.String() } type stringVal string func (str stringVal) format(f *formatter, indent string) { f.WriteString(strconv.Quote(string(str))) } type rawVal string func (r rawVal) format(f *formatter, indent string) { f.WriteString(string(r)) } type keyval struct { key string val node } type keyvals []keyval func (l keyvals) format(f *formatter, indent string) { f.WriteByte('{') switch { case f.Compact: // All on one line: for i, kv := range l { if i > 0 { f.WriteByte(',') } f.WriteString(kv.key) f.WriteByte(':') kv.val.format(f, indent) } case f.Diffable: f.WriteByte('\n') inner := indent + " " // Each value gets its own line: for _, kv := range l { f.WriteString(inner) f.WriteString(kv.key) f.WriteString(": ") kv.val.format(f, inner) f.WriteString(",\n") } f.WriteString(indent) default: keyWidth := 0 for _, kv := range l { if kw := len(kv.key); kw > keyWidth { keyWidth = kw } } alignKey := indent + " " alignValue := strings.Repeat(" ", keyWidth) inner := alignKey + alignValue + " " // First and last line shared with bracket: for i, kv := range l { if i > 0 { f.WriteString(",\n") f.WriteString(alignKey) } f.WriteString(kv.key) f.WriteString(": ") f.WriteString(alignValue[len(kv.key):]) kv.val.format(f, inner) } } f.WriteByte('}') } type list []node func (l list) format(f *formatter, indent string) { if max := f.ShortList; max > 0 { short := f.compactString(l) if len(short) <= max { f.WriteString(short) return } } f.WriteByte('[') switch { case f.Compact: // All on one line: for i, v := range l { if i > 0 { f.WriteByte(',') } v.format(f, indent) } case f.Diffable: f.WriteByte('\n') inner := indent + " " // Each value gets its own line: for _, v := range l { f.WriteString(inner) v.format(f, inner) f.WriteString(",\n") } f.WriteString(indent) default: inner := indent + " " // First and last line shared with bracket: for i, v := range l { if i > 0 { f.WriteString(",\n") f.WriteString(inner) } v.format(f, inner) } } f.WriteByte(']') } type ref struct { id int } func (r ref) format(f *formatter, indent string) { fmt.Fprintf(f, "", f.tagFor(r.id)) } type target struct { id int value node } func (t target) format(f *formatter, indent string) { tag := fmt.Sprintf("<#%d> ", f.tagFor(t.id)) switch { case f.Diffable, f.Compact: // no indent changes default: indent += strings.Repeat(" ", len(tag)) } f.WriteString(tag) t.value.format(f, indent) }