about summary refs log tree commit diff
path: root/vendor/modernc.org/strutil
diff options
context:
space:
mode:
authorEmile <git@emile.space>2024-08-16 19:50:26 +0200
committerEmile <git@emile.space>2024-08-16 19:50:26 +0200
commit1a57267a17c2fc17fb6e104846fabc3e363c326c (patch)
tree1e574e3a80622086dc3c81ff9cba65ef7049b1a9 /vendor/modernc.org/strutil
initial commit
Diffstat (limited to 'vendor/modernc.org/strutil')
-rw-r--r--vendor/modernc.org/strutil/AUTHORS12
-rw-r--r--vendor/modernc.org/strutil/CONTRIBUTORS9
-rw-r--r--vendor/modernc.org/strutil/LICENSE27
-rw-r--r--vendor/modernc.org/strutil/Makefile59
-rw-r--r--vendor/modernc.org/strutil/README8
-rw-r--r--vendor/modernc.org/strutil/strutil.go733
6 files changed, 848 insertions, 0 deletions
diff --git a/vendor/modernc.org/strutil/AUTHORS b/vendor/modernc.org/strutil/AUTHORS
new file mode 100644
index 0000000..d04c450
--- /dev/null
+++ b/vendor/modernc.org/strutil/AUTHORS
@@ -0,0 +1,12 @@
+# This file lists authors for copyright purposes.  This file is distinct from
+# the CONTRIBUTORS files.  See the latter for an explanation.
+#
+# Names should be added to this file as:
+#     Name or Organization <email address>
+#
+# The email address is not required for organizations.
+#
+# Please keep the list sorted.
+
+CZ.NIC z.s.p.o. <kontakt@nic.cz>
+Jan Mercl <0xjnml@gmail.com>
diff --git a/vendor/modernc.org/strutil/CONTRIBUTORS b/vendor/modernc.org/strutil/CONTRIBUTORS
new file mode 100644
index 0000000..5e86f06
--- /dev/null
+++ b/vendor/modernc.org/strutil/CONTRIBUTORS
@@ -0,0 +1,9 @@
+# This file lists people who contributed code to this repository.  The AUTHORS
+# file lists the copyright holders; this file lists people.
+#
+# Names should be added to this file like so:
+#     Name <email address>
+#
+# Please keep the list sorted.
+
+Jan Mercl <0xjnml@gmail.com>
diff --git a/vendor/modernc.org/strutil/LICENSE b/vendor/modernc.org/strutil/LICENSE
new file mode 100644
index 0000000..2fdd92c
--- /dev/null
+++ b/vendor/modernc.org/strutil/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2014 The strutil Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the names of the authors nor the names of the
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/modernc.org/strutil/Makefile b/vendor/modernc.org/strutil/Makefile
new file mode 100644
index 0000000..d19e2d7
--- /dev/null
+++ b/vendor/modernc.org/strutil/Makefile
@@ -0,0 +1,59 @@
+# Copyright (c) 2014 The sortutil Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+.PHONY:	all clean cover cpu editor internalError later mem nuke todo edit
+
+grep=--include=*.go --include=*.l --include=*.y --include=*.yy
+ngrep='TODOOK\|parser\.go\|scanner\.go\|.*_string\.go'
+
+all: editor
+	go vet 2>&1 | grep -v $(ngrep) || true
+	golint 2>&1 | grep -v $(ngrep) || true
+	make todo
+	unused . || true
+	misspell *.go
+	gosimple || true
+	maligned || true
+	unconvert -apply
+
+clean:
+	go clean
+	rm -f *~ *.test *.out
+
+cover:
+	t=$(shell tempfile) ; go test -coverprofile $$t && go tool cover -html $$t && unlink $$t
+
+cpu: clean
+	go test -run @ -bench . -cpuprofile cpu.out
+	go tool pprof -lines *.test cpu.out
+
+edit:
+	@ 1>/dev/null 2>/dev/null gvim -p Makefile *.go
+
+editor:
+	unconvert -apply || true
+	gofmt -l -s -w *.go
+	go test -i
+	go test 2>&1 | tee log
+	go install
+
+internalError:
+	egrep -ho '"internal error.*"' *.go | sort | cat -n
+
+later:
+	@grep -n $(grep) LATER * || true
+	@grep -n $(grep) MAYBE * || true
+
+mem: clean
+	go test -run @ -bench . -memprofile mem.out -memprofilerate 1 -timeout 24h
+	go tool pprof -lines -web -alloc_space *.test mem.out
+
+nuke: clean
+	go clean -i
+
+todo:
+	@grep -nr $(grep) ^[[:space:]]*_[[:space:]]*=[[:space:]][[:alpha:]][[:alnum:]]* * | grep -v $(ngrep) || true
+	@grep -nr $(grep) TODO * | grep -v $(ngrep) || true
+	@grep -nr $(grep) BUG * | grep -v $(ngrep) || true
+	@grep -nr $(grep) [^[:alpha:]]println * | grep -v $(ngrep) || true
diff --git a/vendor/modernc.org/strutil/README b/vendor/modernc.org/strutil/README
new file mode 100644
index 0000000..0ace653
--- /dev/null
+++ b/vendor/modernc.org/strutil/README
@@ -0,0 +1,8 @@
+modernc.orgall-able mirror of modified code already published at:
+http://git.nic.cz/redmine/projects/gostrutil/repository
+
+Online godoc documentation for this package (should be) available at:
+http://gopkgdoc.appspot.com/pkg/modernc.org/strutil
+
+Installation:
+$ go get modernc.org/strutil
diff --git a/vendor/modernc.org/strutil/strutil.go b/vendor/modernc.org/strutil/strutil.go
new file mode 100644
index 0000000..7d07b79
--- /dev/null
+++ b/vendor/modernc.org/strutil/strutil.go
@@ -0,0 +1,733 @@
+// Copyright (c) 2014 The sortutil Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package strutil collects utils supplemental to the standard strings package.
+package strutil // import "modernc.org/strutil"
+
+import (
+	"bytes"
+	"encoding/base32"
+	"encoding/base64"
+	"fmt"
+	"io"
+	"os"
+	"path/filepath"
+	"reflect"
+	"runtime"
+	"sort"
+	"strconv"
+	"strings"
+	"sync"
+)
+
+// Base32ExtDecode decodes base32 extended (RFC 4648) text to binary data.
+func Base32ExtDecode(text []byte) (data []byte, err error) {
+	n := base32.HexEncoding.DecodedLen(len(text))
+	data = make([]byte, n)
+	decoder := base32.NewDecoder(base32.HexEncoding, bytes.NewBuffer(text))
+	if n, err = decoder.Read(data); err != nil {
+		n = 0
+	}
+	data = data[:n]
+	return
+}
+
+// Base32ExtEncode encodes binary data to base32 extended (RFC 4648) encoded text.
+func Base32ExtEncode(data []byte) (text []byte) {
+	n := base32.HexEncoding.EncodedLen(len(data))
+	buf := bytes.NewBuffer(make([]byte, 0, n))
+	encoder := base32.NewEncoder(base32.HexEncoding, buf)
+	encoder.Write(data)
+	encoder.Close()
+	if buf.Len() != n {
+		panic("internal error")
+	}
+	return buf.Bytes()
+}
+
+// Base64Decode decodes base64 text to binary data.
+func Base64Decode(text []byte) (data []byte, err error) {
+	n := base64.StdEncoding.DecodedLen(len(text))
+	data = make([]byte, n)
+	decoder := base64.NewDecoder(base64.StdEncoding, bytes.NewBuffer(text))
+	if n, err = decoder.Read(data); err != nil {
+		n = 0
+	}
+	data = data[:n]
+	return
+}
+
+// Base64Encode encodes binary data to base64 encoded text.
+func Base64Encode(data []byte) (text []byte) {
+	n := base64.StdEncoding.EncodedLen(len(data))
+	buf := bytes.NewBuffer(make([]byte, 0, n))
+	encoder := base64.NewEncoder(base64.StdEncoding, buf)
+	encoder.Write(data)
+	encoder.Close()
+	if buf.Len() != n {
+		panic("internal error")
+	}
+	return buf.Bytes()
+}
+
+// Formatter is an io.Writer extended by a fmt.Printf like function Format
+type Formatter interface {
+	io.Writer
+	Format(format string, args ...interface{}) (n int, errno error)
+}
+
+type indentFormatter struct {
+	io.Writer
+	indent      []byte
+	indentLevel int
+	state       int
+}
+
+const (
+	st0 = iota
+	stBOL
+	stPERC
+	stBOLPERC
+)
+
+// IndentFormatter returns a new Formatter which interprets %i and %u in the
+// Format() format string as indent and undent commands. The commands can
+// nest. The Formatter writes to io.Writer 'w' and inserts one 'indent'
+// string per current indent level value.
+// Behaviour of commands reaching negative indent levels is undefined.
+//	IndentFormatter(os.Stdout, "\t").Format("abc%d%%e%i\nx\ny\n%uz\n", 3)
+// output:
+//	abc3%e
+//		x
+//		y
+//	z
+// The Go quoted string literal form of the above is:
+//	"abc%%e\n\tx\n\tx\nz\n"
+// The commands can be scattered between separate invocations of Format(),
+// i.e. the formatter keeps track of the indent level and knows if it is
+// positioned on start of a line and should emit indentation(s).
+// The same output as above can be produced by e.g.:
+//	f := IndentFormatter(os.Stdout, " ")
+//	f.Format("abc%d%%e%i\nx\n", 3)
+//	f.Format("y\n%uz\n")
+func IndentFormatter(w io.Writer, indent string) Formatter {
+	return &indentFormatter{w, []byte(indent), 0, stBOL}
+}
+
+func (f *indentFormatter) format(flat bool, format string, args ...interface{}) (n int, errno error) {
+	buf := []byte{}
+	for i := 0; i < len(format); i++ {
+		c := format[i]
+		switch f.state {
+		case st0:
+			switch c {
+			case '\n':
+				cc := c
+				if flat && f.indentLevel != 0 {
+					cc = ' '
+				}
+				buf = append(buf, cc)
+				f.state = stBOL
+			case '%':
+				f.state = stPERC
+			default:
+				buf = append(buf, c)
+			}
+		case stBOL:
+			switch c {
+			case '\n':
+				cc := c
+				if flat && f.indentLevel != 0 {
+					cc = ' '
+				}
+				buf = append(buf, cc)
+			case '%':
+				f.state = stBOLPERC
+			default:
+				if !flat {
+					for i := 0; i < f.indentLevel; i++ {
+						buf = append(buf, f.indent...)
+					}
+				}
+				buf = append(buf, c)
+				f.state = st0
+			}
+		case stBOLPERC:
+			switch c {
+			case 'i':
+				f.indentLevel++
+				f.state = stBOL
+			case 'u':
+				f.indentLevel--
+				f.state = stBOL
+			default:
+				if !flat {
+					for i := 0; i < f.indentLevel; i++ {
+						buf = append(buf, f.indent...)
+					}
+				}
+				buf = append(buf, '%', c)
+				f.state = st0
+			}
+		case stPERC:
+			switch c {
+			case 'i':
+				f.indentLevel++
+				f.state = st0
+			case 'u':
+				f.indentLevel--
+				f.state = st0
+			default:
+				buf = append(buf, '%', c)
+				f.state = st0
+			}
+		default:
+			panic("unexpected state")
+		}
+	}
+	switch f.state {
+	case stPERC, stBOLPERC:
+		buf = append(buf, '%')
+	}
+	return f.Write([]byte(fmt.Sprintf(string(buf), args...)))
+}
+
+func (f *indentFormatter) Format(format string, args ...interface{}) (n int, errno error) {
+	return f.format(false, format, args...)
+}
+
+type flatFormatter indentFormatter
+
+// FlatFormatter returns a newly created Formatter with the same functionality as the one returned
+// by IndentFormatter except it allows a newline in the 'format' string argument of Format
+// to pass through iff indent level is currently zero.
+//
+// If indent level is non-zero then such new lines are changed to a space character.
+// There is no indent string, the %i and %u format verbs are used solely to determine the indent level.
+//
+// The FlatFormatter is intended for flattening of normally nested structure textual representation to
+// a one top level structure per line form.
+//	FlatFormatter(os.Stdout, " ").Format("abc%d%%e%i\nx\ny\n%uz\n", 3)
+// output in the form of a Go quoted string literal:
+//	"abc3%%e x y z\n"
+func FlatFormatter(w io.Writer) Formatter {
+	return (*flatFormatter)(IndentFormatter(w, "").(*indentFormatter))
+}
+
+func (f *flatFormatter) Format(format string, args ...interface{}) (n int, errno error) {
+	return (*indentFormatter)(f).format(true, format, args...)
+}
+
+// Pool handles aligning of strings having equal values to the same string instance.
+// Intended use is to conserve some memory e.g. where a large number of identically valued strings
+// with non identical backing arrays may exists in several semantically distinct instances of some structs.
+// Pool is *not* concurrent access safe. It doesn't handle common prefix/suffix aligning,
+// e.g. having s1 == "abc" and s2 == "bc", s2 is not automatically aligned as s1[1:].
+type Pool struct {
+	pool map[string]string
+}
+
+// NewPool returns a newly created Pool.
+func NewPool() *Pool {
+	return &Pool{map[string]string{}}
+}
+
+// Align returns a string with the same value as its argument. It guarantees that
+// all aligned strings share a single instance in memory.
+func (p *Pool) Align(s string) string {
+	if a, ok := p.pool[s]; ok {
+		return a
+	}
+
+	s = StrPack(s)
+	p.pool[s] = s
+	return s
+}
+
+// Count returns the number of items in the pool.
+func (p *Pool) Count() int {
+	return len(p.pool)
+}
+
+// GoPool is a concurrent access safe version of Pool.
+type GoPool struct {
+	pool map[string]string
+	rwm  *sync.RWMutex
+}
+
+// NewGoPool returns a newly created GoPool.
+func NewGoPool() (p *GoPool) {
+	return &GoPool{map[string]string{}, &sync.RWMutex{}}
+}
+
+// Align returns a string with the same value as its argument. It guarantees that
+// all aligned strings share a single instance in memory.
+func (p *GoPool) Align(s string) (y string) {
+	if s != "" {
+		p.rwm.RLock()               // R++
+		if a, ok := p.pool[s]; ok { // found
+			p.rwm.RUnlock() // R--
+			return a
+		}
+
+		p.rwm.RUnlock() // R--
+		// not found but with a race condition, retry within a write lock
+		p.rwm.Lock()                // W++
+		defer p.rwm.Unlock()        // W--
+		if a, ok := p.pool[s]; ok { // done in a race
+			return a
+		}
+
+		// we won
+		s = StrPack(s)
+		p.pool[s] = s
+		return s
+	}
+
+	return
+}
+
+// Count returns the number of items in the pool.
+func (p *GoPool) Count() int {
+	return len(p.pool)
+}
+
+// Dict is a string <-> id bijection. Dict is *not* concurrent access safe for assigning new ids
+// to strings not yet contained in the bijection.
+// Id for an empty string is guaranteed to be 0,
+// thus Id for any non empty string is guaranteed to be non zero.
+type Dict struct {
+	si map[string]int
+	is []string
+}
+
+// NewDict returns a newly created Dict.
+func NewDict() (d *Dict) {
+	d = &Dict{map[string]int{}, []string{}}
+	d.Id("")
+	return
+}
+
+// Count returns the number of items in the dict.
+func (d *Dict) Count() int {
+	return len(d.is)
+}
+
+// Id maps string s to its numeric identificator.
+func (d *Dict) Id(s string) (y int) {
+	if y, ok := d.si[s]; ok {
+		return y
+	}
+
+	s = StrPack(s)
+	y = len(d.is)
+	d.si[s] = y
+	d.is = append(d.is, s)
+	return
+}
+
+// S maps an id to its string value and ok == true. Id values not contained in the bijection
+// return "", false.
+func (d *Dict) S(id int) (s string, ok bool) {
+	if id >= len(d.is) {
+		return "", false
+	}
+	return d.is[id], true
+}
+
+// GoDict is a concurrent access safe version of Dict.
+type GoDict struct {
+	si  map[string]int
+	is  []string
+	rwm *sync.RWMutex
+}
+
+// NewGoDict returns a newly created GoDict.
+func NewGoDict() (d *GoDict) {
+	d = &GoDict{map[string]int{}, []string{}, &sync.RWMutex{}}
+	d.Id("")
+	return
+}
+
+// Count returns the number of items in the dict.
+func (d *GoDict) Count() int {
+	return len(d.is)
+}
+
+// Id maps string s to its numeric identificator. The implementation honors getting
+// an existing id at the cost of assigning a new one.
+func (d *GoDict) Id(s string) (y int) {
+	d.rwm.RLock()             // R++
+	if y, ok := d.si[s]; ok { // found
+		d.rwm.RUnlock() // R--
+		return y
+	}
+
+	d.rwm.RUnlock() // R--
+
+	// not found but with a race condition
+	d.rwm.Lock()              // W++ recheck with write lock
+	defer d.rwm.Unlock()      // W--
+	if y, ok := d.si[s]; ok { // some other goroutine won already
+		return y
+	}
+
+	// a race free not found state => insert the string
+	s = StrPack(s)
+	y = len(d.is)
+	d.si[s] = y
+	d.is = append(d.is, s)
+	return
+}
+
+// S maps an id to its string value and ok == true. Id values not contained in the bijection
+// return "", false.
+func (d *GoDict) S(id int) (s string, ok bool) {
+	d.rwm.RLock()         // R++
+	defer d.rwm.RUnlock() // R--
+	if id >= len(d.is) {
+		return "", false
+	}
+	return d.is[id], true
+}
+
+// StrPack returns a new instance of s which is tightly packed in memory.
+// It is intended for avoiding the situation where having a live reference
+// to a string slice over an unreferenced biger underlying string keeps the biger one
+// in memory anyway - it can't be GCed.
+func StrPack(s string) string {
+	return string([]byte(s)) // T(U(T)) intentional.
+}
+
+// JoinFields returns strings in flds joined by sep. Flds may contain arbitrary
+// bytes, including the sep as they are safely escaped. JoinFields panics if
+// sep is the backslash character or if len(sep) != 1.
+func JoinFields(flds []string, sep string) string {
+	if len(sep) != 1 || sep == "\\" {
+		panic("invalid separator")
+	}
+
+	a := make([]string, len(flds))
+	for i, v := range flds {
+		v = strings.Replace(v, "\\", "\\0", -1)
+		a[i] = strings.Replace(v, sep, "\\1", -1)
+	}
+	return strings.Join(a, sep)
+}
+
+// SplitFields splits s, which must be produced by JoinFields using the same
+// sep, into flds.  SplitFields panics if sep is the backslash character or if
+// len(sep) != 1.
+func SplitFields(s, sep string) (flds []string) {
+	if len(sep) != 1 || sep == "\\" {
+		panic("invalid separator")
+	}
+
+	a := strings.Split(s, sep)
+	r := make([]string, len(a))
+	for i, v := range a {
+		v = strings.Replace(v, "\\1", sep, -1)
+		r[i] = strings.Replace(v, "\\0", "\\", -1)
+	}
+	return r
+}
+
+// PrettyPrintHooks allow to customize the result of PrettyPrint for types
+// listed in the map value.
+type PrettyPrintHooks map[reflect.Type]func(f Formatter, v interface{}, prefix, suffix string)
+
+// PrettyString returns the output of PrettyPrint as a string.
+func PrettyString(v interface{}, prefix, suffix string, hooks PrettyPrintHooks) string {
+	var b bytes.Buffer
+	PrettyPrint(&b, v, prefix, suffix, hooks)
+	return b.String()
+}
+
+// PrettyPrint pretty prints v to w. Zero values and unexported struct fields
+// are omitted.
+//
+// Force printing of zero values of struct fields by including in the field tag
+// PrettyPrint:"zero".
+//
+// Enable using a String method, if any, of a struct field type by including in
+// the field tag PrettyPrint:"stringer".
+//
+// The tags can be combined as in PrettyPrint:"zero,stringer". The order is not
+// important, so PrettyPrint:stringer,zero has the same effect.
+//
+// A hook attached to the field type has priority over the struct field tag
+// described above.
+func PrettyPrint(w io.Writer, v interface{}, prefix, suffix string, hooks PrettyPrintHooks) {
+	if v == nil {
+		return
+	}
+
+	f := IndentFormatter(w, "· ")
+
+	defer func() {
+		if e := recover(); e != nil {
+			f.Format("\npanic: %v", e)
+		}
+	}()
+
+	prettyPrint(nil, f, prefix, suffix, v, hooks, false, false)
+}
+
+func prettyPrint(protect map[interface{}]struct{}, sf Formatter, prefix, suffix string, v interface{}, hooks PrettyPrintHooks, zero, stringer bool) {
+	if v == nil {
+		return
+	}
+
+	rt := reflect.TypeOf(v)
+	if handler := hooks[rt]; handler != nil {
+		handler(sf, v, prefix, suffix)
+		return
+	}
+
+	rv := reflect.ValueOf(v)
+	if stringer {
+		if _, ok := v.(fmt.Stringer); ok {
+			sf.Format("%s%s", prefix, v)
+			sf.Format(suffix)
+			return
+		}
+	}
+
+	switch rt.Kind() {
+	case reflect.Slice:
+		if rv.Len() == 0 && !zero {
+			return
+		}
+
+		sf.Format("%s[]%T{ // len %d%i\n", prefix, rv.Index(0).Interface(), rv.Len())
+		for i := 0; i < rv.Len(); i++ {
+			prettyPrint(protect, sf, fmt.Sprintf("%d: ", i), ",\n", rv.Index(i).Interface(), hooks, false, false)
+		}
+		suffix = strings.Replace(suffix, "%", "%%", -1)
+		sf.Format("%u}" + suffix)
+	case reflect.Array:
+		if reflect.Zero(rt).Interface() == rv.Interface() && !zero {
+			return
+		}
+
+		sf.Format("%s[%d]%T{%i\n", prefix, rv.Len(), rv.Index(0).Interface())
+		for i := 0; i < rv.Len(); i++ {
+			prettyPrint(protect, sf, fmt.Sprintf("%d: ", i), ",\n", rv.Index(i).Interface(), hooks, false, false)
+		}
+		suffix = strings.Replace(suffix, "%", "%%", -1)
+		sf.Format("%u}" + suffix)
+	case reflect.Struct:
+		if rt.NumField() == 0 {
+			return
+		}
+
+		if reflect.DeepEqual(reflect.Zero(rt).Interface(), rv.Interface()) && !zero {
+			return
+		}
+
+		sf.Format("%s%T{%i\n", prefix, v)
+		for i := 0; i < rt.NumField(); i++ {
+			f := rv.Field(i)
+			if !f.CanInterface() {
+				continue
+			}
+
+			var stringer, zero bool
+			ft := rt.Field(i)
+			if tag, ok := ft.Tag.Lookup("PrettyPrint"); ok {
+				a := strings.Split(tag, ",")
+				for _, v := range a {
+					switch strings.TrimSpace(v) {
+					case "stringer":
+						stringer = true
+					case "zero":
+						zero = true
+					}
+				}
+			}
+			prettyPrint(protect, sf, fmt.Sprintf("%s: ", rt.Field(i).Name), ",\n", f.Interface(), hooks, zero, stringer)
+		}
+		suffix = strings.Replace(suffix, "%", "%%", -1)
+		sf.Format("%u}" + suffix)
+	case reflect.Ptr:
+		if rv.IsNil() && !zero {
+			return
+		}
+
+		rvi := rv.Interface()
+		if _, ok := protect[rvi]; ok {
+			suffix = strings.Replace(suffix, "%", "%%", -1)
+			sf.Format("%s&%T{ /* recursive/repetitive pointee not shown */ }"+suffix, prefix, rv.Elem().Interface())
+			return
+		}
+
+		if protect == nil {
+			protect = map[interface{}]struct{}{}
+		}
+		protect[rvi] = struct{}{}
+		prettyPrint(protect, sf, prefix+"&", suffix, rv.Elem().Interface(), hooks, false, false)
+	case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int8:
+		if v := rv.Int(); v != 0 || zero {
+			suffix = strings.Replace(suffix, "%", "%%", -1)
+			sf.Format("%s%v"+suffix, prefix, v)
+		}
+	case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint8:
+		if v := rv.Uint(); v != 0 || zero {
+			suffix = strings.Replace(suffix, "%", "%%", -1)
+			sf.Format("%s%v"+suffix, prefix, v)
+		}
+	case reflect.Float32, reflect.Float64:
+		if v := rv.Float(); v != 0 || zero {
+			suffix = strings.Replace(suffix, "%", "%%", -1)
+			sf.Format("%s%v"+suffix, prefix, v)
+		}
+	case reflect.Complex64, reflect.Complex128:
+		if v := rv.Complex(); v != 0 || zero {
+			suffix = strings.Replace(suffix, "%", "%%", -1)
+			sf.Format("%s%v"+suffix, prefix, v)
+		}
+	case reflect.Uintptr:
+		if v := rv.Uint(); v != 0 || zero {
+			suffix = strings.Replace(suffix, "%", "%%", -1)
+			sf.Format("%s%v"+suffix, prefix, v)
+		}
+	case reflect.UnsafePointer:
+		s := fmt.Sprintf("%p", rv.Interface())
+		if s == "0x0" && !zero {
+			return
+		}
+
+		suffix = strings.Replace(suffix, "%", "%%", -1)
+		sf.Format("%s%s"+suffix, prefix, s)
+	case reflect.Bool:
+		if v := rv.Bool(); v || zero {
+			suffix = strings.Replace(suffix, "%", "%%", -1)
+			sf.Format("%s%v"+suffix, prefix, rv.Bool())
+		}
+	case reflect.String:
+		s := rv.Interface().(string)
+		if s == "" && !zero {
+			return
+		}
+
+		suffix = strings.Replace(suffix, "%", "%%", -1)
+		sf.Format("%s%q"+suffix, prefix, s)
+	case reflect.Chan:
+		if reflect.Zero(rt).Interface() == rv.Interface() && !zero {
+			return
+		}
+
+		c := rv.Cap()
+		s := ""
+		if c != 0 {
+			s = fmt.Sprintf("// capacity: %d", c)
+		}
+		suffix = strings.Replace(suffix, "%", "%%", -1)
+		sf.Format("%s%s %s%s"+suffix, prefix, rt.ChanDir(), rt.Elem().Name(), s)
+	case reflect.Func:
+		if rv.IsNil() && !zero {
+			return
+		}
+
+		var in, out []string
+		for i := 0; i < rt.NumIn(); i++ {
+			x := reflect.Zero(rt.In(i))
+			in = append(in, fmt.Sprintf("%T", x.Interface()))
+		}
+		if rt.IsVariadic() {
+			i := len(in) - 1
+			in[i] = "..." + in[i][2:]
+		}
+		for i := 0; i < rt.NumOut(); i++ {
+			out = append(out, rt.Out(i).Name())
+		}
+		s := "(" + strings.Join(in, ", ") + ")"
+		t := strings.Join(out, ", ")
+		if len(out) > 1 {
+			t = "(" + t + ")"
+		}
+		if t != "" {
+			t = " " + t
+		}
+		suffix = strings.Replace(suffix, "%", "%%", -1)
+		sf.Format("%sfunc%s%s { ... }"+suffix, prefix, s, t)
+	case reflect.Map:
+		keys := rv.MapKeys()
+		if len(keys) == 0 && !zero {
+			return
+		}
+
+		var buf bytes.Buffer
+		nf := IndentFormatter(&buf, "· ")
+		var skeys []string
+		for i, k := range keys {
+			prettyPrint(protect, nf, "", "", k.Interface(), hooks, false, false)
+			skeys = append(skeys, fmt.Sprintf("%s%10d", buf.Bytes(), i))
+			buf.Reset()
+		}
+		sort.Strings(skeys)
+		sf.Format("%s%T{%i\n", prefix, v)
+		for _, k := range skeys {
+			si := strings.TrimSpace(k[len(k)-10:])
+			k = k[:len(k)-10]
+			n, _ := strconv.ParseUint(si, 10, 64)
+			mv := rv.MapIndex(keys[n])
+			prettyPrint(protect, sf, fmt.Sprintf("%s: ", k), ",\n", mv.Interface(), hooks, false, false)
+		}
+		suffix = strings.Replace(suffix, "%", "%%", -1)
+		sf.Format("%u}" + suffix)
+	}
+}
+
+// Gopath returns the value of the $GOPATH environment variable or its default
+// value if not set.
+func Gopath() string {
+	if r := os.Getenv("GOPATH"); r != "" {
+		return r
+	}
+
+	// go1.8: https://github.com/golang/go/blob/74628a8b9f102bddd5078ee426efe0fd57033115/doc/code.html#L122
+	switch runtime.GOOS {
+	case "plan9":
+		return os.Getenv("home")
+	case "windows":
+		return filepath.Join(os.Getenv("USERPROFILE"), "go")
+	default:
+		return filepath.Join(os.Getenv("HOME"), "go")
+	}
+}
+
+// Homepath returns the user's home directory path.
+func Homepath() string {
+	// go1.8: https://github.com/golang/go/blob/74628a8b9f102bddd5078ee426efe0fd57033115/doc/code.html#L122
+	switch runtime.GOOS {
+	case "plan9":
+		return os.Getenv("home")
+	case "windows":
+		return os.Getenv("USERPROFILE")
+	default:
+		return os.Getenv("HOME")
+	}
+}
+
+// ImportPath returns the import path of the caller or an error, if any.
+func ImportPath() (string, error) {
+	_, file, _, ok := runtime.Caller(1)
+	if !ok {
+		return "", fmt.Errorf("runtime.Caller failed")
+	}
+
+	gopath := Gopath()
+	for _, v := range filepath.SplitList(gopath) {
+		gp := filepath.Join(v, "src")
+		path, err := filepath.Rel(gp, file)
+		if err != nil {
+			continue
+		}
+
+		return filepath.Dir(path), nil
+	}
+
+	return "", fmt.Errorf("cannot determine import path using GOPATH=%s", gopath)
+}