github.com/dvln/pretty@v0.0.0-20161024040402-00a5f9316993/formatter.go (about)

     1  package pretty
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"reflect"
     7  	"strconv"
     8  	"strings"
     9  	"text/tabwriter"
    10  	"unicode"
    11  
    12  	"github.com/dvln/text"
    13  )
    14  
    15  // outputIndentLevel is covered in the SetOutputIndentLevel() func header
    16  var outputIndentLevel = 4
    17  
    18  // humanize is covered in the SetHumanize() function header
    19  var humanize = false
    20  
    21  // outputPrefixStr is covered in the SetOutputPrefixStr() function header
    22  var outputPrefixStr = ""
    23  
    24  // newlineAfterItems allows one to make the humanize output insert a blank
    25  // line between entries in a somewhat sensical way... normally that is off.
    26  var newlineAfterItems = false
    27  
    28  // sawCloseBracketLast is incremented when a json-like '}' is seen in
    29  // the output and then set back to 0 when anything else is seen (if one
    30  // has 2 or 3 '}' chars in a row it'll increment til a non '}' char is
    31  // seen... could be used to add spacing between items
    32  var sawCloseBracketLast = 0
    33  
    34  // currOutputLine only kicks on in 'humanize' active (set to true) mode, it
    35  // examines all output being dumped and tracks what is on the current line
    36  // of output and will clear that line when \n goes across the output.  This
    37  // is used to decide if a carriage return + indent is needed when in
    38  // human friendly output mode (if we see a ':' in the current line of output
    39  // it means a "<key>:" header has been printed and a newline/indent is needed
    40  // for the multi-line data to follow)
    41  var currOutputLine = ""
    42  
    43  type formatter struct {
    44  	v     reflect.Value
    45  	force bool
    46  	quote bool
    47  }
    48  
    49  // Formatter makes a wrapper, f, that will format x as go source with line
    50  // breaks and tabs. Object f responds to the "%v" formatting verb when both the
    51  // "#" and " " (space) flags are set, for example:
    52  //
    53  //     fmt.Sprintf("%# v", Formatter(x))
    54  //
    55  // If one of these two flags is not set, or any other verb is used, f will
    56  // format x according to the usual rules of package fmt.
    57  // In particular, if x satisfies fmt.Formatter, then x.Format will be called.
    58  func Formatter(x interface{}) (f fmt.Formatter) {
    59  	return formatter{v: reflect.ValueOf(x), quote: true}
    60  }
    61  
    62  func (fo formatter) String() string {
    63  	return fmt.Sprint(fo.v.Interface()) // unwrap it
    64  }
    65  
    66  func (fo formatter) passThrough(f fmt.State, c rune) {
    67  	s := "%"
    68  	for i := 0; i < 128; i++ {
    69  		if f.Flag(i) {
    70  			s += string(i)
    71  		}
    72  	}
    73  	if w, ok := f.Width(); ok {
    74  		s += fmt.Sprintf("%d", w)
    75  	}
    76  	if p, ok := f.Precision(); ok {
    77  		s += fmt.Sprintf(".%d", p)
    78  	}
    79  	s += string(c)
    80  	fmt.Fprintf(f, s, fo.v.Interface())
    81  }
    82  
    83  // OutputIndentLevel returns the current step-wise indent that will
    84  // be used when dumping output via pretty (defaults to 4 to start),
    85  // see SetOutputIndentLevel() to adjust.
    86  func OutputIndentLevel() int {
    87  	return outputIndentLevel
    88  }
    89  
    90  // SetOutputIndentLevel can be used to adjust the step-wise indent for
    91  // structure representations that are printed.  Give an integer number
    92  // of spaces (recommended 2 or 4, default is 4 to start)
    93  func SetOutputIndentLevel(indent int) {
    94  	outputIndentLevel = indent
    95  }
    96  
    97  // Humanize will return the current true/false state of if "humanizing" of
    98  // the output is active or not.  By default it starts off and you get what
    99  // 'pretty' was originally set up for, a go-like structure w/details.  See
   100  //  SetHumanize() to flip it on (and see what it does).
   101  func Humanize() bool {
   102  	return humanize
   103  }
   104  
   105  // SetHumanize can be used to flip on a more "Humanistic" form of output
   106  // from the 'pretty' package (by default this is false and the regular
   107  // 'pretty' package output that looks more like Go structures).  What
   108  // "humanize" means is that the output could be used as the readable text
   109  // output to your users.  When in humanize form 'pretty:' tags (like 'json:..'
   110  // tags on structs) are honored for overriding field names (spaces ok) and
   111  // supporting a form of omitempty (omitting nil intefaces/ptrs, empty arrays
   112  // or maps, etc... but does not suppress output for false or 0 int's now).
   113  // Anyhow, build a structure for your output, put json and pretty tags in
   114  // the structure and then dump output easily in JSON (via json marshal) or
   115  // dump the same struct to human friendly text for users.
   116  func SetHumanize(b bool) {
   117  	humanize = b
   118  }
   119  
   120  // NewlineAfterItems will return the current true/false state of if newlines
   121  // after each item is desired or not.  By default this is off.
   122  func NewlineAfterItems() bool {
   123  	return newlineAfterItems
   124  }
   125  
   126  // SetNewlineAfterItems can be used to adjust humanistic output format so
   127  // that there's an empty line between items that are printed.  By default
   128  // it's off but this can be used to flip that on by setting to true.
   129  func SetNewlineAfterItems(b bool) {
   130  	newlineAfterItems = b
   131  }
   132  
   133  // OutputPrefixStr returns the current overall text prefix string, see
   134  // the SetOutputPrefixStr() routine to set it.
   135  func OutputPrefixStr() string {
   136  	return outputPrefixStr
   137  }
   138  
   139  // SetOutputPrefixStr sets the output prefix string to the given string
   140  func SetOutputPrefixStr(s string) {
   141  	outputPrefixStr = s
   142  }
   143  
   144  func (fo formatter) Format(f fmt.State, c rune) {
   145  	if fo.force || c == 'v' && f.Flag('#') && f.Flag(' ') {
   146  		w := tabwriter.NewWriter(f, outputIndentLevel, outputIndentLevel, 1, ' ', 0)
   147  		p := &printer{tw: w, Writer: w, visited: make(map[visit]int)}
   148  		p.printValue(fo.v, true, fo.quote)
   149  		w.Flush()
   150  		return
   151  	}
   152  	fo.passThrough(f, c)
   153  }
   154  
   155  type printer struct {
   156  	io.Writer
   157  	tw      *tabwriter.Writer
   158  	visited map[visit]int
   159  	depth   int
   160  }
   161  
   162  func (p *printer) indent() *printer {
   163  	q := *p
   164  	q.tw = tabwriter.NewWriter(p.Writer, outputIndentLevel, outputIndentLevel, 1, ' ', 0)
   165  	q.Writer = text.NewIndentWriter(q.tw, []byte{'\t'})
   166  	return &q
   167  }
   168  
   169  func (p *printer) printInline(v reflect.Value, x interface{}, showType bool) {
   170  	if showType && !humanize {
   171  		writeString(p, v.Type().String())
   172  		fmt.Fprintf(p, "(%#v)", x)
   173  	} else {
   174  		result := fmt.Sprintf("%#v", x)
   175  		if humanize && result != "" && strings.TrimSpace(result) == "" {
   176  			fmt.Fprintf(p, "\"%s\"", result)
   177  		} else {
   178  			fmt.Fprintf(p, "%s", result)
   179  		}
   180  		if humanize {
   181  			lines := strings.Split(result, "\n")
   182  			currOutputLine = lines[len(lines)-1]
   183  		}
   184  	}
   185  }
   186  
   187  // tagOptions is the string following a comma in a struct field's "json"
   188  // tag, or the empty string. It does not include the leading comma.
   189  type tagOptions string
   190  
   191  // parseTag splits a struct field's json tag into its name and
   192  // comma-separated options.
   193  func parseTag(tag string) (string, tagOptions) {
   194  	if idx := strings.Index(tag, ","); idx != -1 {
   195  		return tag[:idx], tagOptions(tag[idx+1:])
   196  	}
   197  	return tag, tagOptions("")
   198  }
   199  
   200  // Contains reports whether a comma-separated list of options
   201  // contains a particular substr flag. substr must be surrounded by a
   202  // string boundary or commas.
   203  func (o tagOptions) Contains(optionName string) bool {
   204  	if len(o) == 0 {
   205  		return false
   206  	}
   207  	s := string(o)
   208  	for s != "" {
   209  		var next string
   210  		i := strings.Index(s, ",")
   211  		if i >= 0 {
   212  			s, next = s[:i], s[i+1:]
   213  		}
   214  		if s == optionName {
   215  			return true
   216  		}
   217  		s = next
   218  	}
   219  	return false
   220  }
   221  
   222  // isValidTag is borrowed from Go's encoding/json
   223  func isValidTag(s string) bool {
   224  	if s == "" {
   225  		return false
   226  	}
   227  	for _, c := range s {
   228  		switch {
   229  		case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c):
   230  			// Backslash and quote chars are reserved, but
   231  			// otherwise any punctuation chars are allowed
   232  			// in a tag name.
   233  		default:
   234  			if !unicode.IsLetter(c) && !unicode.IsDigit(c) {
   235  				return false
   236  			}
   237  		}
   238  	}
   239  	return true
   240  }
   241  
   242  // isEmptyValue determines for "humanistic" output if we want to see a given
   243  // type or not... different than JSON in that we typically do want to see
   244  // true or false settings, and even 0 values for various numerical types...
   245  func isEmptyValue(v reflect.Value) bool {
   246  	switch v.Kind() {
   247  	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
   248  		return v.Len() == 0
   249  	case reflect.Interface, reflect.Ptr:
   250  		return v.IsNil()
   251  	}
   252  	return false
   253  }
   254  
   255  // indentNeeded is called when there is an open bracket for a new structure
   256  // or map or array to be printed, normally we always need to toss in a
   257  // carriage return and indent *but* if we're doing humanized output we
   258  // don't show the {}'s nor do we do the newlines', we want the items
   259  // to appear at the very left margin and show cleanly from there
   260  func indentNeeded() bool {
   261  	if !humanize {
   262  		return true
   263  	}
   264  	if strings.ContainsRune(currOutputLine, ':') {
   265  		return true
   266  	}
   267  	return false
   268  }
   269  
   270  // newlineNeeded should only be used within the context of the humanized
   271  // output mode (see callers).  It's basically deciding when a newline
   272  // needs to be dumped ... since in humanized mode we're not dumping
   273  // the '{' and '}' characters at all (on their own lines in normal 'pretty'
   274  // pkg output) we need to be smarter about when newlines need to be
   275  // printed in such situations.  This type of 'pretty' output:
   276  // {    (normal pretty output prints the type before open bracket, which is "[]interface{}"
   277  //   {  (this ones type is a hash with one entry with value a struct)
   278  //     key1: {
   279  //       structfield1: value
   280  //       structfieldtwo: value
   281  //	   }
   282  //   },
   283  //   {
   284  //     key2: {
   285  //       structfield1: value
   286  //       structfieldtwo: value
   287  //	   }
   288  //   },
   289  // }
   290  // In humanized output form this comes out as:
   291  // key1:
   292  //   structfield1:   value
   293  //   structfieldtwo: value
   294  // key2:
   295  //   structfield1:   value
   296  //   structfieldtwo: value
   297  // ..
   298  // So all those newlines after the close brackets aren't needed, see
   299  // indentNeeded() above as that handles the opening brackets and indent.
   300  // Note that some folks may want a blank line between each entry and
   301  // that can be done by counting the close brackets
   302  func newlineNeeded() bool {
   303  	if sawCloseBracketLast == 0 || (newlineAfterItems && sawCloseBracketLast == 2) {
   304  		return true
   305  	}
   306  	return false
   307  }
   308  
   309  // printValue must keep track of already-printed pointer values to avoid
   310  // infinite recursion.
   311  type visit struct {
   312  	v   uintptr
   313  	typ reflect.Type
   314  }
   315  
   316  func (p *printer) printValue(v reflect.Value, showType, quote bool) {
   317  	if p.depth > 10 {
   318  		writeString(p, "!%v(DEPTH EXCEEDED)")
   319  		return
   320  	}
   321  
   322  	var expand bool
   323  
   324  	if humanize {
   325  		quote = false
   326  		showType = false
   327  		expand = true
   328  	}
   329  	switch v.Kind() {
   330  	case reflect.Bool:
   331  		p.printInline(v, v.Bool(), showType)
   332  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   333  		p.printInline(v, v.Int(), showType)
   334  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
   335  		p.printInline(v, v.Uint(), showType)
   336  	case reflect.Float32, reflect.Float64:
   337  		p.printInline(v, v.Float(), showType)
   338  	case reflect.Complex64, reflect.Complex128:
   339  		fmt.Fprintf(p, "%#v", v.Complex())
   340  	case reflect.String:
   341  		p.fmtString(v.String(), quote)
   342  	case reflect.Map:
   343  		t := v.Type()
   344  		if showType {
   345  			if !humanize {
   346  				writeString(p, t.String())
   347  			}
   348  		}
   349  		writeByte(p, '{') // '}' to balance the char
   350  		if nonzero(v) || humanize {
   351  			expand = !canInline(v.Type())
   352  			pp := p
   353  			if expand {
   354  				if indentNeeded() {
   355  					writeByte(p, '\n')
   356  					pp = p.indent()
   357  				}
   358  			}
   359  			keys := v.MapKeys()
   360  			for i := 0; i < v.Len(); i++ {
   361  				showTypeInStruct := true
   362  				if humanize {
   363  					showTypeInStruct = false
   364  				}
   365  				k := keys[i]
   366  				mv := v.MapIndex(k)
   367  				pp.printValue(k, false, true)
   368  				writeByte(pp, ':')
   369  				if expand {
   370  					writeByte(pp, '\t')
   371  				}
   372  				if !humanize {
   373  					showTypeInStruct = t.Elem().Kind() == reflect.Interface
   374  				}
   375  				pp.printValue(mv, showTypeInStruct, true)
   376  				if expand {
   377  					if humanize {
   378  						if newlineNeeded() {
   379  							writeString(pp, "\n")
   380  						}
   381  					} else {
   382  						writeString(pp, ",\n")
   383  					}
   384  				} else if i < v.Len()-1 {
   385  					writeString(pp, ", ")
   386  				}
   387  			}
   388  			if expand {
   389  				pp.tw.Flush()
   390  			}
   391  		}
   392  		// '{' to balance below line
   393  		writeByte(p, '}')
   394  	case reflect.Struct:
   395  		t := v.Type()
   396  		if v.CanAddr() {
   397  			addr := v.UnsafeAddr()
   398  			vis := visit{addr, t}
   399  			if vd, ok := p.visited[vis]; ok && vd < p.depth {
   400  				p.fmtString(t.String()+"{(CYCLIC REFERENCE)}", false)
   401  				break // don't print v again
   402  			}
   403  			p.visited[vis] = p.depth
   404  		}
   405  
   406  		if showType {
   407  			if !humanize {
   408  				writeString(p, t.String())
   409  			}
   410  		}
   411  		writeByte(p, '{') // '}' to balance the char
   412  		if nonzero(v) || humanize {
   413  			expand = !canInline(v.Type())
   414  			pp := p
   415  			if expand {
   416  				if indentNeeded() {
   417  					writeByte(p, '\n')
   418  					pp = p.indent()
   419  				}
   420  			}
   421  			for i := 0; i < v.NumField(); i++ {
   422  				showTypeInStruct := true
   423  				if humanize {
   424  					showTypeInStruct = false
   425  				}
   426  				if f := t.Field(i); f.Name != "" {
   427  					name := f.Name
   428  					omitEmpty := false
   429  					if humanize {
   430  						tag := f.Tag.Get("pretty")
   431  						if tag == "-" {
   432  							continue
   433  						}
   434  						newName, opts := parseTag(tag)
   435  						if isValidTag(newName) {
   436  							name = newName
   437  						}
   438  						omitEmpty = opts.Contains("omitempty")
   439  						val := getField(v, i)
   440  						if omitEmpty && isEmptyValue(val) {
   441  							continue
   442  						}
   443  					}
   444  					writeString(pp, name)
   445  					writeByte(pp, ':')
   446  					if expand {
   447  						writeByte(pp, '\t')
   448  					}
   449  					if !humanize {
   450  						showTypeInStruct = labelType(f.Type)
   451  					}
   452  				}
   453  				pp.printValue(getField(v, i), showTypeInStruct, true)
   454  				if humanize {
   455  					if newlineNeeded() {
   456  						writeByte(pp, '\n')
   457  					}
   458  				} else if expand {
   459  					writeString(pp, ",\n")
   460  				} else if i < v.NumField()-1 {
   461  					writeString(pp, ", ")
   462  				}
   463  			}
   464  			if expand {
   465  				pp.tw.Flush()
   466  			}
   467  		}
   468  		// '{' to balance below line
   469  		writeByte(p, '}')
   470  	case reflect.Interface:
   471  		switch e := v.Elem(); {
   472  		case e.Kind() == reflect.Invalid:
   473  			writeString(p, "nil")
   474  		case e.IsValid():
   475  			pp := *p
   476  			pp.depth++
   477  			pp.printValue(e, showType, true)
   478  		default:
   479  			writeString(p, v.Type().String())
   480  			writeString(p, "(nil)")
   481  		}
   482  	case reflect.Array, reflect.Slice:
   483  		t := v.Type()
   484  		if showType {
   485  			writeString(p, t.String())
   486  		}
   487  		if v.Kind() == reflect.Slice && v.IsNil() && showType {
   488  			writeString(p, "(nil)")
   489  			break
   490  		}
   491  		if v.Kind() == reflect.Slice && v.IsNil() {
   492  			writeString(p, "nil")
   493  			break
   494  		}
   495  		writeByte(p, '{') // '}' to balance the char
   496  		expand = !canInline(v.Type())
   497  		pp := p
   498  		if expand {
   499  			if indentNeeded() {
   500  				writeByte(p, '\n')
   501  				pp = p.indent()
   502  			}
   503  		}
   504  		for i := 0; i < v.Len(); i++ {
   505  			showTypeInSlice := t.Elem().Kind() == reflect.Interface
   506  			pp.printValue(v.Index(i), showTypeInSlice, true)
   507  			if humanize {
   508  				if newlineNeeded() {
   509  					writeByte(pp, '\n')
   510  				}
   511  			} else if expand {
   512  				writeString(pp, ",\n")
   513  			} else if i < v.Len()-1 {
   514  				writeString(pp, ", ")
   515  			}
   516  		}
   517  		if expand {
   518  			pp.tw.Flush()
   519  		}
   520  		// '{' to balance below line
   521  		writeByte(p, '}')
   522  	case reflect.Ptr:
   523  		e := v.Elem()
   524  		if !e.IsValid() {
   525  			if humanize {
   526  				writeString(p, "nil")
   527  			} else {
   528  				writeByte(p, '(')
   529  				writeString(p, v.Type().String())
   530  				writeString(p, ")(nil)")
   531  			}
   532  		} else {
   533  			pp := *p
   534  			pp.depth++
   535  			if !humanize {
   536  				writeByte(pp, '&')
   537  			}
   538  			pp.printValue(e, true, true)
   539  		}
   540  	case reflect.Chan:
   541  		x := v.Pointer()
   542  		if showType {
   543  			writeByte(p, '(')
   544  			writeString(p, v.Type().String())
   545  			fmt.Fprintf(p, ")(%#v)", x)
   546  		} else {
   547  			fmt.Fprintf(p, "%#v", x)
   548  		}
   549  	case reflect.Func:
   550  		writeString(p, v.Type().String())
   551  		writeString(p, " {...}")
   552  	case reflect.UnsafePointer:
   553  		p.printInline(v, v.Pointer(), showType)
   554  	case reflect.Invalid:
   555  		writeString(p, "nil")
   556  	}
   557  }
   558  
   559  func canInline(t reflect.Type) bool {
   560  	if humanize {
   561  		return false
   562  	}
   563  	switch t.Kind() {
   564  	case reflect.Map:
   565  		return !canExpand(t.Elem())
   566  	case reflect.Struct:
   567  		for i := 0; i < t.NumField(); i++ {
   568  			if canExpand(t.Field(i).Type) {
   569  				return false
   570  			}
   571  		}
   572  		return true
   573  	case reflect.Interface:
   574  		return false
   575  	case reflect.Array, reflect.Slice:
   576  		return !canExpand(t.Elem())
   577  	case reflect.Ptr:
   578  		return false
   579  	case reflect.Chan, reflect.Func, reflect.UnsafePointer:
   580  		return false
   581  	}
   582  	return true
   583  }
   584  
   585  func canExpand(t reflect.Type) bool {
   586  	switch t.Kind() {
   587  	case reflect.Map, reflect.Struct,
   588  		reflect.Interface, reflect.Array, reflect.Slice,
   589  		reflect.Ptr:
   590  		return true
   591  	}
   592  	return false
   593  }
   594  
   595  func labelType(t reflect.Type) bool {
   596  	switch t.Kind() {
   597  	case reflect.Interface, reflect.Struct:
   598  		return true
   599  	}
   600  	return false
   601  }
   602  
   603  func (p *printer) fmtString(s string, quote bool) {
   604  	if quote || (humanize && s != "" && strings.TrimSpace(s) == "") {
   605  		s = strconv.Quote(s)
   606  	}
   607  	writeString(p, s)
   608  }
   609  
   610  func writeByte(w io.Writer, b byte) {
   611  	// if "humanized" output don't print struct/array format chars '{' and '}'
   612  	// which are currently always done via writeByte only, sweet
   613  	if humanize && (b == '{' || b == '}') {
   614  		// '{' to balance below line, fixes dumb editor bracket matching
   615  		if b == '}' {
   616  			sawCloseBracketLast++
   617  		}
   618  		return
   619  	}
   620  	if humanize {
   621  		if b == '\n' {
   622  			currOutputLine = ""
   623  		} else {
   624  			currOutputLine = currOutputLine + string(b)
   625  		}
   626  	}
   627  	sawCloseBracketLast = 0
   628  	w.Write([]byte{b})
   629  }
   630  
   631  func writeString(w io.Writer, s string) {
   632  	if humanize {
   633  		// all close brackets (for fmt'ing) use writeByte() so zero it out
   634  		if s != "" {
   635  			sawCloseBracketLast = 0
   636  		}
   637  		// in case multi-line string, split it on newline, store curr last line
   638  		lines := strings.Split(s, "\n")
   639  		currOutputLine = lines[len(lines)-1]
   640  	}
   641  	io.WriteString(w, s)
   642  }
   643  
   644  func getField(v reflect.Value, i int) reflect.Value {
   645  	val := v.Field(i)
   646  	if val.Kind() == reflect.Interface && !val.IsNil() {
   647  		val = val.Elem()
   648  	}
   649  	return val
   650  }