1// Copyright 2013 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package pretty
16
17import (
18 "bufio"
19 "bytes"
20 "fmt"
21 "io"
22 "strconv"
23 "strings"
24)
25
26// a formatter stores stateful formatting information as well as being
27// an io.Writer for simplicity.
28type formatter struct {
29 *bufio.Writer
30 *Config
31
32 // Self-referential structure tracking
33 tagNumbers map[int]int // tagNumbers[id] = <#n>
34}
35
36// newFormatter creates a new buffered formatter. For the output to be written
37// to the given writer, this must be accompanied by a call to write (or Flush).
38func newFormatter(cfg *Config, w io.Writer) *formatter {
39 return &formatter{
40 Writer: bufio.NewWriter(w),
41 Config: cfg,
42 tagNumbers: make(map[int]int),
43 }
44}
45
46func (f *formatter) write(n node) {
47 defer f.Flush()
48 n.format(f, "")
49}
50
51func (f *formatter) tagFor(id int) int {
52 if tag, ok := f.tagNumbers[id]; ok {
53 return tag
54 }
55 if f.tagNumbers == nil {
56 return 0
57 }
58 tag := len(f.tagNumbers) + 1
59 f.tagNumbers[id] = tag
60 return tag
61}
62
63type node interface {
64 format(f *formatter, indent string)
65}
66
67func (f *formatter) compactString(n node) string {
68 switch k := n.(type) {
69 case stringVal:
70 return string(k)
71 case rawVal:
72 return string(k)
73 }
74
75 buf := new(bytes.Buffer)
76 f2 := newFormatter(&Config{Compact: true}, buf)
77 f2.tagNumbers = f.tagNumbers // reuse tagNumbers just in case
78 f2.write(n)
79 return buf.String()
80}
81
82type stringVal string
83
84func (str stringVal) format(f *formatter, indent string) {
85 f.WriteString(strconv.Quote(string(str)))
86}
87
88type rawVal string
89
90func (r rawVal) format(f *formatter, indent string) {
91 f.WriteString(string(r))
92}
93
94type keyval struct {
95 key string
96 val node
97}
98
99type keyvals []keyval
100
101func (l keyvals) format(f *formatter, indent string) {
102 f.WriteByte('{')
103
104 switch {
105 case f.Compact:
106 // All on one line:
107 for i, kv := range l {
108 if i > 0 {
109 f.WriteByte(',')
110 }
111 f.WriteString(kv.key)
112 f.WriteByte(':')
113 kv.val.format(f, indent)
114 }
115 case f.Diffable:
116 f.WriteByte('\n')
117 inner := indent + " "
118 // Each value gets its own line:
119 for _, kv := range l {
120 f.WriteString(inner)
121 f.WriteString(kv.key)
122 f.WriteString(": ")
123 kv.val.format(f, inner)
124 f.WriteString(",\n")
125 }
126 f.WriteString(indent)
127 default:
128 keyWidth := 0
129 for _, kv := range l {
130 if kw := len(kv.key); kw > keyWidth {
131 keyWidth = kw
132 }
133 }
134 alignKey := indent + " "
135 alignValue := strings.Repeat(" ", keyWidth)
136 inner := alignKey + alignValue + " "
137 // First and last line shared with bracket:
138 for i, kv := range l {
139 if i > 0 {
140 f.WriteString(",\n")
141 f.WriteString(alignKey)
142 }
143 f.WriteString(kv.key)
144 f.WriteString(": ")
145 f.WriteString(alignValue[len(kv.key):])
146 kv.val.format(f, inner)
147 }
148 }
149
150 f.WriteByte('}')
151}
152
153type list []node
154
155func (l list) format(f *formatter, indent string) {
156 if max := f.ShortList; max > 0 {
157 short := f.compactString(l)
158 if len(short) <= max {
159 f.WriteString(short)
160 return
161 }
162 }
163
164 f.WriteByte('[')
165
166 switch {
167 case f.Compact:
168 // All on one line:
169 for i, v := range l {
170 if i > 0 {
171 f.WriteByte(',')
172 }
173 v.format(f, indent)
174 }
175 case f.Diffable:
176 f.WriteByte('\n')
177 inner := indent + " "
178 // Each value gets its own line:
179 for _, v := range l {
180 f.WriteString(inner)
181 v.format(f, inner)
182 f.WriteString(",\n")
183 }
184 f.WriteString(indent)
185 default:
186 inner := indent + " "
187 // First and last line shared with bracket:
188 for i, v := range l {
189 if i > 0 {
190 f.WriteString(",\n")
191 f.WriteString(inner)
192 }
193 v.format(f, inner)
194 }
195 }
196
197 f.WriteByte(']')
198}
199
200type ref struct {
201 id int
202}
203
204func (r ref) format(f *formatter, indent string) {
205 fmt.Fprintf(f, "<see #%d>", f.tagFor(r.id))
206}
207
208type target struct {
209 id int
210 value node
211}
212
213func (t target) format(f *formatter, indent string) {
214 tag := fmt.Sprintf("<#%d> ", f.tagFor(t.id))
215 switch {
216 case f.Diffable, f.Compact:
217 // no indent changes
218 default:
219 indent += strings.Repeat(" ", len(tag))
220 }
221 f.WriteString(tag)
222 t.value.format(f, indent)
223}