codeberg.org/gruf/go-format@v1.0.6/formatter.go (about)

     1  package format
     2  
     3  import (
     4  	"reflect"
     5  	"strconv"
     6  	_ "unsafe"
     7  )
     8  
     9  // Formatter allows configuring value and string formatting.
    10  type Formatter struct {
    11  	// MaxDepth specifies the max depth of fields the formatter will iterate.
    12  	// Once max depth is reached, value will simply be formatted as "...".
    13  	// e.g.
    14  	//
    15  	// MaxDepth=1
    16  	// type A struct{
    17  	//     Nested B
    18  	// }
    19  	// type B struct{
    20  	//     Nested C
    21  	// }
    22  	// type C struct{
    23  	//     Field string
    24  	// }
    25  	//
    26  	// Append(&buf, A{}) => {Nested={Nested={Field=...}}}
    27  	MaxDepth uint8
    28  }
    29  
    30  // Append will append formatted form of supplied values into 'buf'.
    31  func (f Formatter) Append(buf *Buffer, v ...interface{}) {
    32  	for _, v := range v {
    33  		appendIfaceOrRValue(format{maxd: f.MaxDepth, buf: buf}, v)
    34  		buf.AppendByte(' ')
    35  	}
    36  	if len(v) > 0 {
    37  		buf.Truncate(1)
    38  	}
    39  }
    40  
    41  // Appendf will append the formatted string with supplied values into 'buf'.
    42  // Supported format directives:
    43  // - '{}'   => format supplied arg, in place
    44  // - '{0}'  => format arg at index 0 of supplied, in place
    45  // - '{:?}' => format supplied arg verbosely, in place
    46  // - '{:k}' => format supplied arg as key, in place
    47  // - '{:v}' => format supplied arg as value, in place
    48  // - '{:T}' => format supplied arg's type, in place
    49  //
    50  // To escape either of '{}' simply append an additional brace e.g.
    51  // - '{{'     => '{'
    52  // - '}}'     => '}'
    53  // - '{{}}'   => '{}'
    54  // - '{{:?}}' => '{:?}'
    55  //
    56  // More formatting directives might be included in the future.
    57  func (f Formatter) Appendf(buf *Buffer, s string, a ...interface{}) {
    58  	const (
    59  		// ground state
    60  		modeNone = uint8(0)
    61  
    62  		// prev reached '{'
    63  		modeOpen = uint8(1)
    64  
    65  		// prev reached '}'
    66  		modeClose = uint8(2)
    67  
    68  		// parsing directive index
    69  		modeIdx = uint8(3)
    70  
    71  		// parsing directive operands
    72  		modeOp = uint8(4)
    73  	)
    74  
    75  	var (
    76  		// mode is current parsing mode
    77  		mode uint8
    78  
    79  		// arg is the current arg index
    80  		arg int
    81  
    82  		// carg is current directive-set arg index
    83  		carg int
    84  
    85  		// last is the trailing cursor to see slice windows
    86  		last int
    87  
    88  		// idx is the current index in 's'
    89  		idx int
    90  
    91  		// fmt is the base argument formatter
    92  		fmt = format{
    93  			maxd: f.MaxDepth,
    94  			buf:  buf,
    95  		}
    96  
    97  		// NOTE: these functions are defined here as function
    98  		// locals as it turned out to be better for performance
    99  		// doing it this way, than encapsulating their logic in
   100  		// some kind of parsing structure. Maybe if the parser
   101  		// was pooled along with the buffers it might work out
   102  		// better, but then it makes more internal functions i.e.
   103  		// .Append() .Appendf() less accessible outside package.
   104  		//
   105  		// Currently, passing '-gcflags "-l=4"' causes a not
   106  		// insignificant decrease in ns/op, which is likely due
   107  		// to more aggressive function inlining, which this
   108  		// function can obviously stand to benefit from :)
   109  
   110  		// Str returns current string window slice, and updates
   111  		// the trailing cursor 'last' to current 'idx'
   112  		Str = func() string {
   113  			str := s[last:idx]
   114  			last = idx
   115  			return str
   116  		}
   117  
   118  		// MoveUp moves the trailing cursor 'last' just past 'idx'
   119  		MoveUp = func() {
   120  			last = idx + 1
   121  		}
   122  
   123  		// MoveUpTo moves the trailing cursor 'last' either up to
   124  		// closest '}', or current 'idx', whichever is furthest.
   125  		// NOTE: by calling bytealg.IndexByteString() directly (and
   126  		// not the strconv pass-through, we shave-off complexity
   127  		// which allows this function to be inlined).
   128  		MoveUpTo = func() {
   129  			i := bytealg_IndexBytes(s[idx:], '}')
   130  			if i >= 0 {
   131  				idx += i
   132  			}
   133  			MoveUp()
   134  		}
   135  
   136  		// ParseIndex parses an integer from the current string
   137  		// window, updating 'last' to 'idx'. The string window
   138  		// is ASSUMED to contain only valid ASCII numbers. This
   139  		// only returns false if number exceeds platform int size
   140  		ParseIndex = func() bool {
   141  			var str string
   142  
   143  			// Get current window
   144  			if str = Str(); len(str) < 1 {
   145  				return true
   146  			}
   147  
   148  			// Index HAS to fit within platform integer size
   149  			if !(strconv.IntSize == 32 && (0 < len(s) && len(s) < 10)) ||
   150  				!(strconv.IntSize == 64 && (0 < len(s) && len(s) < 19)) {
   151  				return false
   152  			}
   153  
   154  			carg = 0
   155  
   156  			// Build integer from string
   157  			for i := 0; i < len(str); i++ {
   158  				carg = carg*10 + int(str[i]-'0')
   159  			}
   160  
   161  			return true
   162  		}
   163  
   164  		// ValidOp checks that for current ending idx, that a valid
   165  		// operand was achieved -- only 0 and 1 are acceptable lens
   166  		ValidOp = func() bool {
   167  			diff := idx - last
   168  			last = idx
   169  			return (diff < 2)
   170  		}
   171  
   172  		// AppendArg will take either the directive-set, or
   173  		// iterated arg index, check within bounds of 'a' and
   174  		// append the that argument formatted to the buffer.
   175  		// On failure, it will append an error string
   176  		AppendArg = func() {
   177  			// Look for idx
   178  			if carg < 0 {
   179  				carg = arg
   180  			}
   181  
   182  			// Incr idx
   183  			arg++
   184  
   185  			if carg < len(a) {
   186  				if fmt.flags&rtypeBit != 0 {
   187  					// Append the arg's type string
   188  					appendIface(fmt, reflect.TypeOf(a[carg]))
   189  				} else {
   190  					// Append formatted argument value
   191  					appendIfaceOrRValue(fmt, a[carg])
   192  				}
   193  			} else {
   194  				// No argument found for index
   195  				buf.AppendString(`!{MISSING_ARG}`)
   196  			}
   197  		}
   198  
   199  		// Reset will reset the mode to ground, the flags
   200  		// to empty and parsed 'carg' to  empty
   201  		Reset = func() {
   202  			mode = modeNone
   203  			fmt.flags = 0
   204  			carg = -1
   205  		}
   206  	)
   207  
   208  	for idx = 0; idx < len(s); idx++ {
   209  		// Get next char
   210  		c := s[idx]
   211  
   212  		switch mode {
   213  		// Ground mode
   214  		case modeNone:
   215  			switch c {
   216  			case '{':
   217  				// Enter open mode
   218  				buf.AppendString(Str())
   219  				mode = modeOpen
   220  				MoveUp()
   221  			case '}':
   222  				// Enter close mode
   223  				buf.AppendString(Str())
   224  				mode = modeClose
   225  				MoveUp()
   226  			}
   227  
   228  		// Encountered open '{'
   229  		case modeOpen:
   230  			switch c {
   231  			case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
   232  				// Starting index
   233  				mode = modeIdx
   234  				MoveUp()
   235  			case '{':
   236  				// Escaped bracket
   237  				buf.AppendByte('{')
   238  				mode = modeNone
   239  				MoveUp()
   240  			case '}':
   241  				// Format arg
   242  				AppendArg()
   243  				Reset()
   244  				MoveUp()
   245  			case ':':
   246  				// Starting operands
   247  				mode = modeOp
   248  				MoveUp()
   249  			default:
   250  				// Bad char, missing a close
   251  				buf.AppendString(`!{MISSING_CLOSE}`)
   252  				mode = modeNone
   253  				MoveUpTo()
   254  			}
   255  
   256  		// Encountered close '}'
   257  		case modeClose:
   258  			switch c {
   259  			case '}':
   260  				// Escaped close bracket
   261  				buf.AppendByte('}')
   262  				mode = modeNone
   263  				MoveUp()
   264  			default:
   265  				// Missing an open bracket
   266  				buf.AppendString(`!{MISSING_OPEN}`)
   267  				mode = modeNone
   268  				MoveUp()
   269  			}
   270  
   271  		// Preparing index
   272  		case modeIdx:
   273  			switch c {
   274  			case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
   275  			case ':':
   276  				if !ParseIndex() {
   277  					// Unable to parse an integer
   278  					buf.AppendString(`!{BAD_INDEX}`)
   279  					mode = modeNone
   280  					MoveUpTo()
   281  				} else {
   282  					// Starting operands
   283  					mode = modeOp
   284  					MoveUp()
   285  				}
   286  			case '}':
   287  				if !ParseIndex() {
   288  					// Unable to parse an integer
   289  					buf.AppendString(`!{BAD_INDEX}`)
   290  				} else {
   291  					// Format arg
   292  					AppendArg()
   293  				}
   294  				Reset()
   295  				MoveUp()
   296  			default:
   297  				// Not a valid index character
   298  				buf.AppendString(`!{BAD_INDEX}`)
   299  				mode = modeNone
   300  				MoveUpTo()
   301  			}
   302  
   303  		// Preparing operands
   304  		case modeOp:
   305  			switch c {
   306  			case 'k':
   307  				fmt.flags |= isKeyBit
   308  			case 'v':
   309  				fmt.flags |= isValBit
   310  			case '?':
   311  				fmt.flags |= vboseBit
   312  			case 'T':
   313  				fmt.flags |= rtypeBit
   314  			case '}':
   315  				if !ValidOp() {
   316  					// Bad operands parsed
   317  					buf.AppendString(`!{BAD_OPERAND}`)
   318  				} else {
   319  					// Format arg
   320  					AppendArg()
   321  				}
   322  				Reset()
   323  				MoveUp()
   324  			default:
   325  				// Not a valid operand char
   326  				buf.AppendString(`!{BAD_OPERAND}`)
   327  				Reset()
   328  				MoveUpTo()
   329  			}
   330  		}
   331  	}
   332  
   333  	// Append any remaining
   334  	buf.AppendString(s[last:])
   335  }
   336  
   337  // formatter is the default formatter instance.
   338  var formatter = Formatter{
   339  	MaxDepth: 10,
   340  }
   341  
   342  // Append will append formatted form of supplied values into 'buf' using default formatter.
   343  // See Formatter.Append() for more documentation.
   344  func Append(buf *Buffer, v ...interface{}) {
   345  	formatter.Append(buf, v...)
   346  }
   347  
   348  // Appendf will append the formatted string with supplied values into 'buf' using default formatter.
   349  // See Formatter.Appendf() for more documentation.
   350  func Appendf(buf *Buffer, s string, a ...interface{}) {
   351  	formatter.Appendf(buf, s, a...)
   352  }
   353  
   354  //go:linkname bytealg_IndexBytes internal/bytealg.IndexByteString
   355  func bytealg_IndexBytes(string, byte) int