mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-30 16:32:26 -05:00 
			
		
		
		
	
		
			
	
	
		
			734 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			734 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|  | // 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) | ||
|  | } |