[822] | 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 | }
|
---|