forked from mystiq/dex
076cd77469
Signed-off-by: Stephan Renatus <srenatus@chef.io>
223 lines
4.4 KiB
Go
223 lines
4.4 KiB
Go
// 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, "<see #%d>", 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)
|
|
}
|