1 | // Copyright (c) 2014 The sortutil Authors. All rights reserved.
|
---|
2 | // Use of this source code is governed by a BSD-style
|
---|
3 | // license that can be found in the LICENSE file.
|
---|
4 |
|
---|
5 | // Package strutil collects utils supplemental to the standard strings package.
|
---|
6 | package strutil // import "modernc.org/strutil"
|
---|
7 |
|
---|
8 | import (
|
---|
9 | "bytes"
|
---|
10 | "encoding/base32"
|
---|
11 | "encoding/base64"
|
---|
12 | "fmt"
|
---|
13 | "io"
|
---|
14 | "os"
|
---|
15 | "path/filepath"
|
---|
16 | "reflect"
|
---|
17 | "runtime"
|
---|
18 | "sort"
|
---|
19 | "strconv"
|
---|
20 | "strings"
|
---|
21 | "sync"
|
---|
22 | )
|
---|
23 |
|
---|
24 | // Base32ExtDecode decodes base32 extended (RFC 4648) text to binary data.
|
---|
25 | func Base32ExtDecode(text []byte) (data []byte, err error) {
|
---|
26 | n := base32.HexEncoding.DecodedLen(len(text))
|
---|
27 | data = make([]byte, n)
|
---|
28 | decoder := base32.NewDecoder(base32.HexEncoding, bytes.NewBuffer(text))
|
---|
29 | if n, err = decoder.Read(data); err != nil {
|
---|
30 | n = 0
|
---|
31 | }
|
---|
32 | data = data[:n]
|
---|
33 | return
|
---|
34 | }
|
---|
35 |
|
---|
36 | // Base32ExtEncode encodes binary data to base32 extended (RFC 4648) encoded text.
|
---|
37 | func Base32ExtEncode(data []byte) (text []byte) {
|
---|
38 | n := base32.HexEncoding.EncodedLen(len(data))
|
---|
39 | buf := bytes.NewBuffer(make([]byte, 0, n))
|
---|
40 | encoder := base32.NewEncoder(base32.HexEncoding, buf)
|
---|
41 | encoder.Write(data)
|
---|
42 | encoder.Close()
|
---|
43 | if buf.Len() != n {
|
---|
44 | panic("internal error")
|
---|
45 | }
|
---|
46 | return buf.Bytes()
|
---|
47 | }
|
---|
48 |
|
---|
49 | // Base64Decode decodes base64 text to binary data.
|
---|
50 | func Base64Decode(text []byte) (data []byte, err error) {
|
---|
51 | n := base64.StdEncoding.DecodedLen(len(text))
|
---|
52 | data = make([]byte, n)
|
---|
53 | decoder := base64.NewDecoder(base64.StdEncoding, bytes.NewBuffer(text))
|
---|
54 | if n, err = decoder.Read(data); err != nil {
|
---|
55 | n = 0
|
---|
56 | }
|
---|
57 | data = data[:n]
|
---|
58 | return
|
---|
59 | }
|
---|
60 |
|
---|
61 | // Base64Encode encodes binary data to base64 encoded text.
|
---|
62 | func Base64Encode(data []byte) (text []byte) {
|
---|
63 | n := base64.StdEncoding.EncodedLen(len(data))
|
---|
64 | buf := bytes.NewBuffer(make([]byte, 0, n))
|
---|
65 | encoder := base64.NewEncoder(base64.StdEncoding, buf)
|
---|
66 | encoder.Write(data)
|
---|
67 | encoder.Close()
|
---|
68 | if buf.Len() != n {
|
---|
69 | panic("internal error")
|
---|
70 | }
|
---|
71 | return buf.Bytes()
|
---|
72 | }
|
---|
73 |
|
---|
74 | // Formatter is an io.Writer extended by a fmt.Printf like function Format
|
---|
75 | type Formatter interface {
|
---|
76 | io.Writer
|
---|
77 | Format(format string, args ...interface{}) (n int, errno error)
|
---|
78 | }
|
---|
79 |
|
---|
80 | type indentFormatter struct {
|
---|
81 | io.Writer
|
---|
82 | indent []byte
|
---|
83 | indentLevel int
|
---|
84 | state int
|
---|
85 | }
|
---|
86 |
|
---|
87 | const (
|
---|
88 | st0 = iota
|
---|
89 | stBOL
|
---|
90 | stPERC
|
---|
91 | stBOLPERC
|
---|
92 | )
|
---|
93 |
|
---|
94 | // IndentFormatter returns a new Formatter which interprets %i and %u in the
|
---|
95 | // Format() format string as indent and undent commands. The commands can
|
---|
96 | // nest. The Formatter writes to io.Writer 'w' and inserts one 'indent'
|
---|
97 | // string per current indent level value.
|
---|
98 | // Behaviour of commands reaching negative indent levels is undefined.
|
---|
99 | // IndentFormatter(os.Stdout, "\t").Format("abc%d%%e%i\nx\ny\n%uz\n", 3)
|
---|
100 | // output:
|
---|
101 | // abc3%e
|
---|
102 | // x
|
---|
103 | // y
|
---|
104 | // z
|
---|
105 | // The Go quoted string literal form of the above is:
|
---|
106 | // "abc%%e\n\tx\n\tx\nz\n"
|
---|
107 | // The commands can be scattered between separate invocations of Format(),
|
---|
108 | // i.e. the formatter keeps track of the indent level and knows if it is
|
---|
109 | // positioned on start of a line and should emit indentation(s).
|
---|
110 | // The same output as above can be produced by e.g.:
|
---|
111 | // f := IndentFormatter(os.Stdout, " ")
|
---|
112 | // f.Format("abc%d%%e%i\nx\n", 3)
|
---|
113 | // f.Format("y\n%uz\n")
|
---|
114 | func IndentFormatter(w io.Writer, indent string) Formatter {
|
---|
115 | return &indentFormatter{w, []byte(indent), 0, stBOL}
|
---|
116 | }
|
---|
117 |
|
---|
118 | func (f *indentFormatter) format(flat bool, format string, args ...interface{}) (n int, errno error) {
|
---|
119 | buf := []byte{}
|
---|
120 | for i := 0; i < len(format); i++ {
|
---|
121 | c := format[i]
|
---|
122 | switch f.state {
|
---|
123 | case st0:
|
---|
124 | switch c {
|
---|
125 | case '\n':
|
---|
126 | cc := c
|
---|
127 | if flat && f.indentLevel != 0 {
|
---|
128 | cc = ' '
|
---|
129 | }
|
---|
130 | buf = append(buf, cc)
|
---|
131 | f.state = stBOL
|
---|
132 | case '%':
|
---|
133 | f.state = stPERC
|
---|
134 | default:
|
---|
135 | buf = append(buf, c)
|
---|
136 | }
|
---|
137 | case stBOL:
|
---|
138 | switch c {
|
---|
139 | case '\n':
|
---|
140 | cc := c
|
---|
141 | if flat && f.indentLevel != 0 {
|
---|
142 | cc = ' '
|
---|
143 | }
|
---|
144 | buf = append(buf, cc)
|
---|
145 | case '%':
|
---|
146 | f.state = stBOLPERC
|
---|
147 | default:
|
---|
148 | if !flat {
|
---|
149 | for i := 0; i < f.indentLevel; i++ {
|
---|
150 | buf = append(buf, f.indent...)
|
---|
151 | }
|
---|
152 | }
|
---|
153 | buf = append(buf, c)
|
---|
154 | f.state = st0
|
---|
155 | }
|
---|
156 | case stBOLPERC:
|
---|
157 | switch c {
|
---|
158 | case 'i':
|
---|
159 | f.indentLevel++
|
---|
160 | f.state = stBOL
|
---|
161 | case 'u':
|
---|
162 | f.indentLevel--
|
---|
163 | f.state = stBOL
|
---|
164 | default:
|
---|
165 | if !flat {
|
---|
166 | for i := 0; i < f.indentLevel; i++ {
|
---|
167 | buf = append(buf, f.indent...)
|
---|
168 | }
|
---|
169 | }
|
---|
170 | buf = append(buf, '%', c)
|
---|
171 | f.state = st0
|
---|
172 | }
|
---|
173 | case stPERC:
|
---|
174 | switch c {
|
---|
175 | case 'i':
|
---|
176 | f.indentLevel++
|
---|
177 | f.state = st0
|
---|
178 | case 'u':
|
---|
179 | f.indentLevel--
|
---|
180 | f.state = st0
|
---|
181 | default:
|
---|
182 | buf = append(buf, '%', c)
|
---|
183 | f.state = st0
|
---|
184 | }
|
---|
185 | default:
|
---|
186 | panic("unexpected state")
|
---|
187 | }
|
---|
188 | }
|
---|
189 | switch f.state {
|
---|
190 | case stPERC, stBOLPERC:
|
---|
191 | buf = append(buf, '%')
|
---|
192 | }
|
---|
193 | return f.Write([]byte(fmt.Sprintf(string(buf), args...)))
|
---|
194 | }
|
---|
195 |
|
---|
196 | func (f *indentFormatter) Format(format string, args ...interface{}) (n int, errno error) {
|
---|
197 | return f.format(false, format, args...)
|
---|
198 | }
|
---|
199 |
|
---|
200 | type flatFormatter indentFormatter
|
---|
201 |
|
---|
202 | // FlatFormatter returns a newly created Formatter with the same functionality as the one returned
|
---|
203 | // by IndentFormatter except it allows a newline in the 'format' string argument of Format
|
---|
204 | // to pass through iff indent level is currently zero.
|
---|
205 | //
|
---|
206 | // If indent level is non-zero then such new lines are changed to a space character.
|
---|
207 | // There is no indent string, the %i and %u format verbs are used solely to determine the indent level.
|
---|
208 | //
|
---|
209 | // The FlatFormatter is intended for flattening of normally nested structure textual representation to
|
---|
210 | // a one top level structure per line form.
|
---|
211 | // FlatFormatter(os.Stdout, " ").Format("abc%d%%e%i\nx\ny\n%uz\n", 3)
|
---|
212 | // output in the form of a Go quoted string literal:
|
---|
213 | // "abc3%%e x y z\n"
|
---|
214 | func FlatFormatter(w io.Writer) Formatter {
|
---|
215 | return (*flatFormatter)(IndentFormatter(w, "").(*indentFormatter))
|
---|
216 | }
|
---|
217 |
|
---|
218 | func (f *flatFormatter) Format(format string, args ...interface{}) (n int, errno error) {
|
---|
219 | return (*indentFormatter)(f).format(true, format, args...)
|
---|
220 | }
|
---|
221 |
|
---|
222 | // Pool handles aligning of strings having equal values to the same string instance.
|
---|
223 | // Intended use is to conserve some memory e.g. where a large number of identically valued strings
|
---|
224 | // with non identical backing arrays may exists in several semantically distinct instances of some structs.
|
---|
225 | // Pool is *not* concurrent access safe. It doesn't handle common prefix/suffix aligning,
|
---|
226 | // e.g. having s1 == "abc" and s2 == "bc", s2 is not automatically aligned as s1[1:].
|
---|
227 | type Pool struct {
|
---|
228 | pool map[string]string
|
---|
229 | }
|
---|
230 |
|
---|
231 | // NewPool returns a newly created Pool.
|
---|
232 | func NewPool() *Pool {
|
---|
233 | return &Pool{map[string]string{}}
|
---|
234 | }
|
---|
235 |
|
---|
236 | // Align returns a string with the same value as its argument. It guarantees that
|
---|
237 | // all aligned strings share a single instance in memory.
|
---|
238 | func (p *Pool) Align(s string) string {
|
---|
239 | if a, ok := p.pool[s]; ok {
|
---|
240 | return a
|
---|
241 | }
|
---|
242 |
|
---|
243 | s = StrPack(s)
|
---|
244 | p.pool[s] = s
|
---|
245 | return s
|
---|
246 | }
|
---|
247 |
|
---|
248 | // Count returns the number of items in the pool.
|
---|
249 | func (p *Pool) Count() int {
|
---|
250 | return len(p.pool)
|
---|
251 | }
|
---|
252 |
|
---|
253 | // GoPool is a concurrent access safe version of Pool.
|
---|
254 | type GoPool struct {
|
---|
255 | pool map[string]string
|
---|
256 | rwm *sync.RWMutex
|
---|
257 | }
|
---|
258 |
|
---|
259 | // NewGoPool returns a newly created GoPool.
|
---|
260 | func NewGoPool() (p *GoPool) {
|
---|
261 | return &GoPool{map[string]string{}, &sync.RWMutex{}}
|
---|
262 | }
|
---|
263 |
|
---|
264 | // Align returns a string with the same value as its argument. It guarantees that
|
---|
265 | // all aligned strings share a single instance in memory.
|
---|
266 | func (p *GoPool) Align(s string) (y string) {
|
---|
267 | if s != "" {
|
---|
268 | p.rwm.RLock() // R++
|
---|
269 | if a, ok := p.pool[s]; ok { // found
|
---|
270 | p.rwm.RUnlock() // R--
|
---|
271 | return a
|
---|
272 | }
|
---|
273 |
|
---|
274 | p.rwm.RUnlock() // R--
|
---|
275 | // not found but with a race condition, retry within a write lock
|
---|
276 | p.rwm.Lock() // W++
|
---|
277 | defer p.rwm.Unlock() // W--
|
---|
278 | if a, ok := p.pool[s]; ok { // done in a race
|
---|
279 | return a
|
---|
280 | }
|
---|
281 |
|
---|
282 | // we won
|
---|
283 | s = StrPack(s)
|
---|
284 | p.pool[s] = s
|
---|
285 | return s
|
---|
286 | }
|
---|
287 |
|
---|
288 | return
|
---|
289 | }
|
---|
290 |
|
---|
291 | // Count returns the number of items in the pool.
|
---|
292 | func (p *GoPool) Count() int {
|
---|
293 | return len(p.pool)
|
---|
294 | }
|
---|
295 |
|
---|
296 | // Dict is a string <-> id bijection. Dict is *not* concurrent access safe for assigning new ids
|
---|
297 | // to strings not yet contained in the bijection.
|
---|
298 | // Id for an empty string is guaranteed to be 0,
|
---|
299 | // thus Id for any non empty string is guaranteed to be non zero.
|
---|
300 | type Dict struct {
|
---|
301 | si map[string]int
|
---|
302 | is []string
|
---|
303 | }
|
---|
304 |
|
---|
305 | // NewDict returns a newly created Dict.
|
---|
306 | func NewDict() (d *Dict) {
|
---|
307 | d = &Dict{map[string]int{}, []string{}}
|
---|
308 | d.Id("")
|
---|
309 | return
|
---|
310 | }
|
---|
311 |
|
---|
312 | // Count returns the number of items in the dict.
|
---|
313 | func (d *Dict) Count() int {
|
---|
314 | return len(d.is)
|
---|
315 | }
|
---|
316 |
|
---|
317 | // Id maps string s to its numeric identificator.
|
---|
318 | func (d *Dict) Id(s string) (y int) {
|
---|
319 | if y, ok := d.si[s]; ok {
|
---|
320 | return y
|
---|
321 | }
|
---|
322 |
|
---|
323 | s = StrPack(s)
|
---|
324 | y = len(d.is)
|
---|
325 | d.si[s] = y
|
---|
326 | d.is = append(d.is, s)
|
---|
327 | return
|
---|
328 | }
|
---|
329 |
|
---|
330 | // S maps an id to its string value and ok == true. Id values not contained in the bijection
|
---|
331 | // return "", false.
|
---|
332 | func (d *Dict) S(id int) (s string, ok bool) {
|
---|
333 | if id >= len(d.is) {
|
---|
334 | return "", false
|
---|
335 | }
|
---|
336 | return d.is[id], true
|
---|
337 | }
|
---|
338 |
|
---|
339 | // GoDict is a concurrent access safe version of Dict.
|
---|
340 | type GoDict struct {
|
---|
341 | si map[string]int
|
---|
342 | is []string
|
---|
343 | rwm *sync.RWMutex
|
---|
344 | }
|
---|
345 |
|
---|
346 | // NewGoDict returns a newly created GoDict.
|
---|
347 | func NewGoDict() (d *GoDict) {
|
---|
348 | d = &GoDict{map[string]int{}, []string{}, &sync.RWMutex{}}
|
---|
349 | d.Id("")
|
---|
350 | return
|
---|
351 | }
|
---|
352 |
|
---|
353 | // Count returns the number of items in the dict.
|
---|
354 | func (d *GoDict) Count() int {
|
---|
355 | return len(d.is)
|
---|
356 | }
|
---|
357 |
|
---|
358 | // Id maps string s to its numeric identificator. The implementation honors getting
|
---|
359 | // an existing id at the cost of assigning a new one.
|
---|
360 | func (d *GoDict) Id(s string) (y int) {
|
---|
361 | d.rwm.RLock() // R++
|
---|
362 | if y, ok := d.si[s]; ok { // found
|
---|
363 | d.rwm.RUnlock() // R--
|
---|
364 | return y
|
---|
365 | }
|
---|
366 |
|
---|
367 | d.rwm.RUnlock() // R--
|
---|
368 |
|
---|
369 | // not found but with a race condition
|
---|
370 | d.rwm.Lock() // W++ recheck with write lock
|
---|
371 | defer d.rwm.Unlock() // W--
|
---|
372 | if y, ok := d.si[s]; ok { // some other goroutine won already
|
---|
373 | return y
|
---|
374 | }
|
---|
375 |
|
---|
376 | // a race free not found state => insert the string
|
---|
377 | s = StrPack(s)
|
---|
378 | y = len(d.is)
|
---|
379 | d.si[s] = y
|
---|
380 | d.is = append(d.is, s)
|
---|
381 | return
|
---|
382 | }
|
---|
383 |
|
---|
384 | // S maps an id to its string value and ok == true. Id values not contained in the bijection
|
---|
385 | // return "", false.
|
---|
386 | func (d *GoDict) S(id int) (s string, ok bool) {
|
---|
387 | d.rwm.RLock() // R++
|
---|
388 | defer d.rwm.RUnlock() // R--
|
---|
389 | if id >= len(d.is) {
|
---|
390 | return "", false
|
---|
391 | }
|
---|
392 | return d.is[id], true
|
---|
393 | }
|
---|
394 |
|
---|
395 | // StrPack returns a new instance of s which is tightly packed in memory.
|
---|
396 | // It is intended for avoiding the situation where having a live reference
|
---|
397 | // to a string slice over an unreferenced biger underlying string keeps the biger one
|
---|
398 | // in memory anyway - it can't be GCed.
|
---|
399 | func StrPack(s string) string {
|
---|
400 | return string([]byte(s)) // T(U(T)) intentional.
|
---|
401 | }
|
---|
402 |
|
---|
403 | // JoinFields returns strings in flds joined by sep. Flds may contain arbitrary
|
---|
404 | // bytes, including the sep as they are safely escaped. JoinFields panics if
|
---|
405 | // sep is the backslash character or if len(sep) != 1.
|
---|
406 | func JoinFields(flds []string, sep string) string {
|
---|
407 | if len(sep) != 1 || sep == "\\" {
|
---|
408 | panic("invalid separator")
|
---|
409 | }
|
---|
410 |
|
---|
411 | a := make([]string, len(flds))
|
---|
412 | for i, v := range flds {
|
---|
413 | v = strings.Replace(v, "\\", "\\0", -1)
|
---|
414 | a[i] = strings.Replace(v, sep, "\\1", -1)
|
---|
415 | }
|
---|
416 | return strings.Join(a, sep)
|
---|
417 | }
|
---|
418 |
|
---|
419 | // SplitFields splits s, which must be produced by JoinFields using the same
|
---|
420 | // sep, into flds. SplitFields panics if sep is the backslash character or if
|
---|
421 | // len(sep) != 1.
|
---|
422 | func SplitFields(s, sep string) (flds []string) {
|
---|
423 | if len(sep) != 1 || sep == "\\" {
|
---|
424 | panic("invalid separator")
|
---|
425 | }
|
---|
426 |
|
---|
427 | a := strings.Split(s, sep)
|
---|
428 | r := make([]string, len(a))
|
---|
429 | for i, v := range a {
|
---|
430 | v = strings.Replace(v, "\\1", sep, -1)
|
---|
431 | r[i] = strings.Replace(v, "\\0", "\\", -1)
|
---|
432 | }
|
---|
433 | return r
|
---|
434 | }
|
---|
435 |
|
---|
436 | // PrettyPrintHooks allow to customize the result of PrettyPrint for types
|
---|
437 | // listed in the map value.
|
---|
438 | type PrettyPrintHooks map[reflect.Type]func(f Formatter, v interface{}, prefix, suffix string)
|
---|
439 |
|
---|
440 | // PrettyString returns the output of PrettyPrint as a string.
|
---|
441 | func PrettyString(v interface{}, prefix, suffix string, hooks PrettyPrintHooks) string {
|
---|
442 | var b bytes.Buffer
|
---|
443 | PrettyPrint(&b, v, prefix, suffix, hooks)
|
---|
444 | return b.String()
|
---|
445 | }
|
---|
446 |
|
---|
447 | // PrettyPrint pretty prints v to w. Zero values and unexported struct fields
|
---|
448 | // are omitted.
|
---|
449 | //
|
---|
450 | // Force printing of zero values of struct fields by including in the field tag
|
---|
451 | // PrettyPrint:"zero".
|
---|
452 | //
|
---|
453 | // Enable using a String method, if any, of a struct field type by including in
|
---|
454 | // the field tag PrettyPrint:"stringer".
|
---|
455 | //
|
---|
456 | // The tags can be combined as in PrettyPrint:"zero,stringer". The order is not
|
---|
457 | // important, so PrettyPrint:stringer,zero has the same effect.
|
---|
458 | //
|
---|
459 | // A hook attached to the field type has priority over the struct field tag
|
---|
460 | // described above.
|
---|
461 | func PrettyPrint(w io.Writer, v interface{}, prefix, suffix string, hooks PrettyPrintHooks) {
|
---|
462 | if v == nil {
|
---|
463 | return
|
---|
464 | }
|
---|
465 |
|
---|
466 | f := IndentFormatter(w, "· ")
|
---|
467 |
|
---|
468 | defer func() {
|
---|
469 | if e := recover(); e != nil {
|
---|
470 | f.Format("\npanic: %v", e)
|
---|
471 | }
|
---|
472 | }()
|
---|
473 |
|
---|
474 | prettyPrint(nil, f, prefix, suffix, v, hooks, false, false)
|
---|
475 | }
|
---|
476 |
|
---|
477 | func prettyPrint(protect map[interface{}]struct{}, sf Formatter, prefix, suffix string, v interface{}, hooks PrettyPrintHooks, zero, stringer bool) {
|
---|
478 | if v == nil {
|
---|
479 | return
|
---|
480 | }
|
---|
481 |
|
---|
482 | rt := reflect.TypeOf(v)
|
---|
483 | if handler := hooks[rt]; handler != nil {
|
---|
484 | handler(sf, v, prefix, suffix)
|
---|
485 | return
|
---|
486 | }
|
---|
487 |
|
---|
488 | rv := reflect.ValueOf(v)
|
---|
489 | if stringer {
|
---|
490 | if _, ok := v.(fmt.Stringer); ok {
|
---|
491 | sf.Format("%s%s", prefix, v)
|
---|
492 | sf.Format(suffix)
|
---|
493 | return
|
---|
494 | }
|
---|
495 | }
|
---|
496 |
|
---|
497 | switch rt.Kind() {
|
---|
498 | case reflect.Slice:
|
---|
499 | if rv.Len() == 0 && !zero {
|
---|
500 | return
|
---|
501 | }
|
---|
502 |
|
---|
503 | sf.Format("%s[]%T{ // len %d%i\n", prefix, rv.Index(0).Interface(), rv.Len())
|
---|
504 | for i := 0; i < rv.Len(); i++ {
|
---|
505 | prettyPrint(protect, sf, fmt.Sprintf("%d: ", i), ",\n", rv.Index(i).Interface(), hooks, false, false)
|
---|
506 | }
|
---|
507 | suffix = strings.Replace(suffix, "%", "%%", -1)
|
---|
508 | sf.Format("%u}" + suffix)
|
---|
509 | case reflect.Array:
|
---|
510 | if reflect.Zero(rt).Interface() == rv.Interface() && !zero {
|
---|
511 | return
|
---|
512 | }
|
---|
513 |
|
---|
514 | sf.Format("%s[%d]%T{%i\n", prefix, rv.Len(), rv.Index(0).Interface())
|
---|
515 | for i := 0; i < rv.Len(); i++ {
|
---|
516 | prettyPrint(protect, sf, fmt.Sprintf("%d: ", i), ",\n", rv.Index(i).Interface(), hooks, false, false)
|
---|
517 | }
|
---|
518 | suffix = strings.Replace(suffix, "%", "%%", -1)
|
---|
519 | sf.Format("%u}" + suffix)
|
---|
520 | case reflect.Struct:
|
---|
521 | if rt.NumField() == 0 {
|
---|
522 | return
|
---|
523 | }
|
---|
524 |
|
---|
525 | if reflect.DeepEqual(reflect.Zero(rt).Interface(), rv.Interface()) && !zero {
|
---|
526 | return
|
---|
527 | }
|
---|
528 |
|
---|
529 | sf.Format("%s%T{%i\n", prefix, v)
|
---|
530 | for i := 0; i < rt.NumField(); i++ {
|
---|
531 | f := rv.Field(i)
|
---|
532 | if !f.CanInterface() {
|
---|
533 | continue
|
---|
534 | }
|
---|
535 |
|
---|
536 | var stringer, zero bool
|
---|
537 | ft := rt.Field(i)
|
---|
538 | if tag, ok := ft.Tag.Lookup("PrettyPrint"); ok {
|
---|
539 | a := strings.Split(tag, ",")
|
---|
540 | for _, v := range a {
|
---|
541 | switch strings.TrimSpace(v) {
|
---|
542 | case "stringer":
|
---|
543 | stringer = true
|
---|
544 | case "zero":
|
---|
545 | zero = true
|
---|
546 | }
|
---|
547 | }
|
---|
548 | }
|
---|
549 | prettyPrint(protect, sf, fmt.Sprintf("%s: ", rt.Field(i).Name), ",\n", f.Interface(), hooks, zero, stringer)
|
---|
550 | }
|
---|
551 | suffix = strings.Replace(suffix, "%", "%%", -1)
|
---|
552 | sf.Format("%u}" + suffix)
|
---|
553 | case reflect.Ptr:
|
---|
554 | if rv.IsNil() && !zero {
|
---|
555 | return
|
---|
556 | }
|
---|
557 |
|
---|
558 | rvi := rv.Interface()
|
---|
559 | if _, ok := protect[rvi]; ok {
|
---|
560 | suffix = strings.Replace(suffix, "%", "%%", -1)
|
---|
561 | sf.Format("%s&%T{ /* recursive/repetitive pointee not shown */ }"+suffix, prefix, rv.Elem().Interface())
|
---|
562 | return
|
---|
563 | }
|
---|
564 |
|
---|
565 | if protect == nil {
|
---|
566 | protect = map[interface{}]struct{}{}
|
---|
567 | }
|
---|
568 | protect[rvi] = struct{}{}
|
---|
569 | prettyPrint(protect, sf, prefix+"&", suffix, rv.Elem().Interface(), hooks, false, false)
|
---|
570 | case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int8:
|
---|
571 | if v := rv.Int(); v != 0 || zero {
|
---|
572 | suffix = strings.Replace(suffix, "%", "%%", -1)
|
---|
573 | sf.Format("%s%v"+suffix, prefix, v)
|
---|
574 | }
|
---|
575 | case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint8:
|
---|
576 | if v := rv.Uint(); v != 0 || zero {
|
---|
577 | suffix = strings.Replace(suffix, "%", "%%", -1)
|
---|
578 | sf.Format("%s%v"+suffix, prefix, v)
|
---|
579 | }
|
---|
580 | case reflect.Float32, reflect.Float64:
|
---|
581 | if v := rv.Float(); v != 0 || zero {
|
---|
582 | suffix = strings.Replace(suffix, "%", "%%", -1)
|
---|
583 | sf.Format("%s%v"+suffix, prefix, v)
|
---|
584 | }
|
---|
585 | case reflect.Complex64, reflect.Complex128:
|
---|
586 | if v := rv.Complex(); v != 0 || zero {
|
---|
587 | suffix = strings.Replace(suffix, "%", "%%", -1)
|
---|
588 | sf.Format("%s%v"+suffix, prefix, v)
|
---|
589 | }
|
---|
590 | case reflect.Uintptr:
|
---|
591 | if v := rv.Uint(); v != 0 || zero {
|
---|
592 | suffix = strings.Replace(suffix, "%", "%%", -1)
|
---|
593 | sf.Format("%s%v"+suffix, prefix, v)
|
---|
594 | }
|
---|
595 | case reflect.UnsafePointer:
|
---|
596 | s := fmt.Sprintf("%p", rv.Interface())
|
---|
597 | if s == "0x0" && !zero {
|
---|
598 | return
|
---|
599 | }
|
---|
600 |
|
---|
601 | suffix = strings.Replace(suffix, "%", "%%", -1)
|
---|
602 | sf.Format("%s%s"+suffix, prefix, s)
|
---|
603 | case reflect.Bool:
|
---|
604 | if v := rv.Bool(); v || zero {
|
---|
605 | suffix = strings.Replace(suffix, "%", "%%", -1)
|
---|
606 | sf.Format("%s%v"+suffix, prefix, rv.Bool())
|
---|
607 | }
|
---|
608 | case reflect.String:
|
---|
609 | s := rv.Interface().(string)
|
---|
610 | if s == "" && !zero {
|
---|
611 | return
|
---|
612 | }
|
---|
613 |
|
---|
614 | suffix = strings.Replace(suffix, "%", "%%", -1)
|
---|
615 | sf.Format("%s%q"+suffix, prefix, s)
|
---|
616 | case reflect.Chan:
|
---|
617 | if reflect.Zero(rt).Interface() == rv.Interface() && !zero {
|
---|
618 | return
|
---|
619 | }
|
---|
620 |
|
---|
621 | c := rv.Cap()
|
---|
622 | s := ""
|
---|
623 | if c != 0 {
|
---|
624 | s = fmt.Sprintf("// capacity: %d", c)
|
---|
625 | }
|
---|
626 | suffix = strings.Replace(suffix, "%", "%%", -1)
|
---|
627 | sf.Format("%s%s %s%s"+suffix, prefix, rt.ChanDir(), rt.Elem().Name(), s)
|
---|
628 | case reflect.Func:
|
---|
629 | if rv.IsNil() && !zero {
|
---|
630 | return
|
---|
631 | }
|
---|
632 |
|
---|
633 | var in, out []string
|
---|
634 | for i := 0; i < rt.NumIn(); i++ {
|
---|
635 | x := reflect.Zero(rt.In(i))
|
---|
636 | in = append(in, fmt.Sprintf("%T", x.Interface()))
|
---|
637 | }
|
---|
638 | if rt.IsVariadic() {
|
---|
639 | i := len(in) - 1
|
---|
640 | in[i] = "..." + in[i][2:]
|
---|
641 | }
|
---|
642 | for i := 0; i < rt.NumOut(); i++ {
|
---|
643 | out = append(out, rt.Out(i).Name())
|
---|
644 | }
|
---|
645 | s := "(" + strings.Join(in, ", ") + ")"
|
---|
646 | t := strings.Join(out, ", ")
|
---|
647 | if len(out) > 1 {
|
---|
648 | t = "(" + t + ")"
|
---|
649 | }
|
---|
650 | if t != "" {
|
---|
651 | t = " " + t
|
---|
652 | }
|
---|
653 | suffix = strings.Replace(suffix, "%", "%%", -1)
|
---|
654 | sf.Format("%sfunc%s%s { ... }"+suffix, prefix, s, t)
|
---|
655 | case reflect.Map:
|
---|
656 | keys := rv.MapKeys()
|
---|
657 | if len(keys) == 0 && !zero {
|
---|
658 | return
|
---|
659 | }
|
---|
660 |
|
---|
661 | var buf bytes.Buffer
|
---|
662 | nf := IndentFormatter(&buf, "· ")
|
---|
663 | var skeys []string
|
---|
664 | for i, k := range keys {
|
---|
665 | prettyPrint(protect, nf, "", "", k.Interface(), hooks, false, false)
|
---|
666 | skeys = append(skeys, fmt.Sprintf("%s%10d", buf.Bytes(), i))
|
---|
667 | buf.Reset()
|
---|
668 | }
|
---|
669 | sort.Strings(skeys)
|
---|
670 | sf.Format("%s%T{%i\n", prefix, v)
|
---|
671 | for _, k := range skeys {
|
---|
672 | si := strings.TrimSpace(k[len(k)-10:])
|
---|
673 | k = k[:len(k)-10]
|
---|
674 | n, _ := strconv.ParseUint(si, 10, 64)
|
---|
675 | mv := rv.MapIndex(keys[n])
|
---|
676 | prettyPrint(protect, sf, fmt.Sprintf("%s: ", k), ",\n", mv.Interface(), hooks, false, false)
|
---|
677 | }
|
---|
678 | suffix = strings.Replace(suffix, "%", "%%", -1)
|
---|
679 | sf.Format("%u}" + suffix)
|
---|
680 | }
|
---|
681 | }
|
---|
682 |
|
---|
683 | // Gopath returns the value of the $GOPATH environment variable or its default
|
---|
684 | // value if not set.
|
---|
685 | func Gopath() string {
|
---|
686 | if r := os.Getenv("GOPATH"); r != "" {
|
---|
687 | return r
|
---|
688 | }
|
---|
689 |
|
---|
690 | // go1.8: https://github.com/golang/go/blob/74628a8b9f102bddd5078ee426efe0fd57033115/doc/code.html#L122
|
---|
691 | switch runtime.GOOS {
|
---|
692 | case "plan9":
|
---|
693 | return os.Getenv("home")
|
---|
694 | case "windows":
|
---|
695 | return filepath.Join(os.Getenv("USERPROFILE"), "go")
|
---|
696 | default:
|
---|
697 | return filepath.Join(os.Getenv("HOME"), "go")
|
---|
698 | }
|
---|
699 | }
|
---|
700 |
|
---|
701 | // Homepath returns the user's home directory path.
|
---|
702 | func Homepath() string {
|
---|
703 | // go1.8: https://github.com/golang/go/blob/74628a8b9f102bddd5078ee426efe0fd57033115/doc/code.html#L122
|
---|
704 | switch runtime.GOOS {
|
---|
705 | case "plan9":
|
---|
706 | return os.Getenv("home")
|
---|
707 | case "windows":
|
---|
708 | return os.Getenv("USERPROFILE")
|
---|
709 | default:
|
---|
710 | return os.Getenv("HOME")
|
---|
711 | }
|
---|
712 | }
|
---|
713 |
|
---|
714 | // ImportPath returns the import path of the caller or an error, if any.
|
---|
715 | func ImportPath() (string, error) {
|
---|
716 | _, file, _, ok := runtime.Caller(1)
|
---|
717 | if !ok {
|
---|
718 | return "", fmt.Errorf("runtime.Caller failed")
|
---|
719 | }
|
---|
720 |
|
---|
721 | gopath := Gopath()
|
---|
722 | for _, v := range filepath.SplitList(gopath) {
|
---|
723 | gp := filepath.Join(v, "src")
|
---|
724 | path, err := filepath.Rel(gp, file)
|
---|
725 | if err != nil {
|
---|
726 | continue
|
---|
727 | }
|
---|
728 |
|
---|
729 | return filepath.Dir(path), nil
|
---|
730 | }
|
---|
731 |
|
---|
732 | return "", fmt.Errorf("cannot determine import path using GOPATH=%s", gopath)
|
---|
733 | }
|
---|