gotosocial/vendor/codeberg.org/gruf/go-kv/format/formatter.go
kim 098dbe6ff4
[chore] use our own logging implementation (#716)
* first commit

Signed-off-by: kim <grufwub@gmail.com>

* replace logging with our own log library

Signed-off-by: kim <grufwub@gmail.com>

* fix imports

Signed-off-by: kim <grufwub@gmail.com>

* fix log imports

Signed-off-by: kim <grufwub@gmail.com>

* add license text

Signed-off-by: kim <grufwub@gmail.com>

* fix package import cycle between config and log package

Signed-off-by: kim <grufwub@gmail.com>

* fix empty kv.Fields{} being passed to WithFields()

Signed-off-by: kim <grufwub@gmail.com>

* fix uses of log.WithFields() with whitespace issues and empty slices

Signed-off-by: kim <grufwub@gmail.com>

* *linter related grumbling*

Signed-off-by: kim <grufwub@gmail.com>

* gofmt the codebase! also fix more log.WithFields() formatting issues

Signed-off-by: kim <grufwub@gmail.com>

* update testrig code to match new changes

Signed-off-by: kim <grufwub@gmail.com>

* fix error wrapping in non fmt.Errorf function

Signed-off-by: kim <grufwub@gmail.com>

* add benchmarking of log.Caller() vs non-cached

Signed-off-by: kim <grufwub@gmail.com>

* fix syslog tests, add standard build tags to test runner to ensure consistency

Signed-off-by: kim <grufwub@gmail.com>

* make syslog tests more robust

Signed-off-by: kim <grufwub@gmail.com>

* fix caller depth arithmatic (is that how you spell it?)

Signed-off-by: kim <grufwub@gmail.com>

* update to use unkeyed fields in kv.Field{} instances

Signed-off-by: kim <grufwub@gmail.com>

* update go-kv library

Signed-off-by: kim <grufwub@gmail.com>

* update libraries list

Signed-off-by: kim <grufwub@gmail.com>

* fuck you linter get nerfed

Signed-off-by: kim <grufwub@gmail.com>

Co-authored-by: tobi <31960611+tsmethurst@users.noreply.github.com>
2022-07-19 10:47:55 +02:00

350 lines
8.1 KiB
Go

package format
import (
"strconv"
"strings"
"codeberg.org/gruf/go-byteutil"
)
// Formatter allows configuring value and string formatting.
type Formatter struct {
// MaxDepth specifies the max depth of fields the formatter will iterate.
// Once max depth is reached, value will simply be formatted as "...".
// e.g.
//
// MaxDepth=1
// type A struct{
// Nested B
// }
// type B struct{
// Nested C
// }
// type C struct{
// Field string
// }
//
// Append(&buf, A{}) => {Nested={Nested={Field=...}}}
MaxDepth uint8
}
// Append will append formatted form of supplied values into 'buf'.
func (f *Formatter) Append(buf *byteutil.Buffer, v ...interface{}) {
fmt := format{Buffer: buf, Config: f}
for i := 0; i < len(v); i++ {
fmt.AppendInterfaceOrReflect(v[i])
fmt.Buffer.B = append(fmt.Buffer.B, ' ')
}
if len(v) > 0 {
fmt.Buffer.Truncate(1)
}
}
// Appendf will append the formatted string with supplied values into 'buf'.
// Supported format directives:
// - '{}' => format supplied arg, in place
// - '{0}' => format arg at index 0 of supplied, in place
// - '{:?}' => format supplied arg verbosely, in place
// - '{:k}' => format supplied arg as key, in place
// - '{:v}' => format supplied arg as value, in place
//
// To escape either of '{}' simply append an additional brace e.g.
// - '{{' => '{'
// - '}}' => '}'
// - '{{}}' => '{}'
// - '{{:?}}' => '{:?}'
//
// More formatting directives might be included in the future.
func (f *Formatter) Appendf(buf *byteutil.Buffer, s string, a ...interface{}) {
const (
// ground state
modeNone = uint8(0)
// prev reached '{'
modeOpen = uint8(1)
// prev reached '}'
modeClose = uint8(2)
// parsing directive index
modeIdx = uint8(3)
// parsing directive operands
modeOp = uint8(4)
)
var (
// mode is current parsing mode
mode uint8
// arg is the current arg index
arg int
// carg is current directive-set arg index
carg int
// last is the trailing cursor to see slice windows
last int
// idx is the current index in 's'
idx int
// fmt is the base argument formatter
fmt = format{
Config: f,
Buffer: buf,
}
// NOTE: these functions are defined here as function
// locals as it turned out to be better for performance
// doing it this way, than encapsulating their logic in
// some kind of parsing structure. Maybe if the parser
// was pooled along with the buffers it might work out
// better, but then it makes more internal functions i.e.
// .Append() .Appendf() less accessible outside package.
//
// Currently, passing '-gcflags "-l=4"' causes a not
// insignificant decrease in ns/op, which is likely due
// to more aggressive function inlining, which this
// function can obviously stand to benefit from :)
// Str returns current string window slice, and updates
// the trailing cursor 'last' to current 'idx'
Str = func() string {
str := s[last:idx]
last = idx
return str
}
// MoveUp moves the trailing cursor 'last' just past 'idx'
MoveUp = func() {
last = idx + 1
}
// MoveUpTo moves the trailing cursor 'last' either up to
// closest '}', or current 'idx', whichever is furthest.
// NOTE: by calling bytealg.IndexByteString() directly (and
// not the strconv pass-through, we shave-off complexity
// which allows this function to be inlined).
MoveUpTo = func() {
i := strings.IndexByte(s[idx:], '}')
if i >= 0 {
idx += i
}
MoveUp()
}
// ParseIndex parses an integer from the current string
// window, updating 'last' to 'idx'. The string window
// is ASSUMED to contain only valid ASCII numbers. This
// only returns false if number exceeds platform int size
ParseIndex = func() bool {
var str string
// Get current window
if str = Str(); len(str) < 1 {
return true
}
// Index HAS to fit within platform integer size
if !(strconv.IntSize == 32 && (0 < len(s) && len(s) < 10)) ||
!(strconv.IntSize == 64 && (0 < len(s) && len(s) < 19)) {
return false
}
carg = 0
// Build integer from string
for i := 0; i < len(str); i++ {
carg = carg*10 + int(str[i]-'0')
}
return true
}
// ValidOp checks that for current ending idx, that a valid
// operand was achieved -- only 0, 1 are valid numbers.
ValidOp = func() bool {
diff := (idx - last)
last = idx
return diff < 2
}
// AppendArg will take either the directive-set, or
// iterated arg index, check within bounds of 'a' and
// append the that argument formatted to the buffer.
// On failure, it will append an error string
AppendArg = func() {
// Look for idx
if carg < 0 {
carg = arg
}
// Incr idx
arg++
if carg < len(a) {
// Append formatted argument value
fmt.AppendInterfaceOrReflect(a[carg])
} else {
// No argument found for index
fmt.Buffer.B = append(fmt.Buffer.B, `!{MISSING_ARG}`...)
}
}
// Reset will reset the mode to ground, the flags
// to empty and parsed 'carg' to empty
Reset = func() {
mode = modeNone
fmt.CurDepth = 0
fmt.Flags = 0
fmt.VType = ""
fmt.Derefs = 0
carg = -1
}
)
for idx = 0; idx < len(s); idx++ {
// Get next char
c := s[idx]
switch mode {
// Ground mode
case modeNone:
switch c {
case '{':
// Enter open mode
fmt.Buffer.B = append(fmt.Buffer.B, Str()...)
mode = modeOpen
MoveUp()
case '}':
// Enter close mode
fmt.Buffer.B = append(fmt.Buffer.B, Str()...)
mode = modeClose
MoveUp()
}
// Encountered open '{'
case modeOpen:
switch c {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
// Starting index
mode = modeIdx
MoveUp()
case '{':
// Escaped bracket
fmt.Buffer.B = append(fmt.Buffer.B, '{')
mode = modeNone
MoveUp()
case '}':
// Format arg
AppendArg()
Reset()
MoveUp()
case ':':
// Starting operands
mode = modeOp
MoveUp()
default:
// Bad char, missing a close
fmt.Buffer.B = append(fmt.Buffer.B, `!{MISSING_CLOSE}`...)
mode = modeNone
MoveUpTo()
}
// Encountered close '}'
case modeClose:
switch c {
case '}':
// Escaped close bracket
fmt.Buffer.B = append(fmt.Buffer.B, '}')
mode = modeNone
MoveUp()
default:
// Missing an open bracket
fmt.Buffer.B = append(fmt.Buffer.B, `!{MISSING_OPEN}`...)
mode = modeNone
MoveUp()
}
// Preparing index
case modeIdx:
switch c {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
case ':':
if !ParseIndex() {
// Unable to parse an integer
fmt.Buffer.B = append(fmt.Buffer.B, `!{BAD_INDEX}`...)
mode = modeNone
MoveUpTo()
} else {
// Starting operands
mode = modeOp
MoveUp()
}
case '}':
if !ParseIndex() {
// Unable to parse an integer
fmt.Buffer.B = append(fmt.Buffer.B, `!{BAD_INDEX}`...)
} else {
// Format arg
AppendArg()
}
Reset()
MoveUp()
default:
// Not a valid index character
fmt.Buffer.B = append(fmt.Buffer.B, `!{BAD_INDEX}`...)
mode = modeNone
MoveUpTo()
}
// Preparing operands
case modeOp:
switch c {
case 'k':
fmt.Flags |= IsKeyBit
case 'v':
fmt.Flags |= IsValBit
case '?':
fmt.Flags |= VboseBit
case '}':
if !ValidOp() {
// Bad operands parsed
fmt.Buffer.B = append(fmt.Buffer.B, `!{BAD_OPERAND}`...)
} else {
// Format arg
AppendArg()
}
Reset()
MoveUp()
default:
// Not a valid operand char
fmt.Buffer.B = append(fmt.Buffer.B, `!{BAD_OPERAND}`...)
Reset()
MoveUpTo()
}
}
}
// Append any remaining
fmt.Buffer.B = append(fmt.Buffer.B, s[last:]...)
}
// formatter is the default formatter instance.
var formatter = Formatter{
MaxDepth: 10,
}
// Append will append formatted form of supplied values into 'buf' using default formatter.
// See Formatter.Append() for more documentation.
func Append(buf *byteutil.Buffer, v ...interface{}) {
formatter.Append(buf, v...)
}
// Appendf will append the formatted string with supplied values into 'buf' using default formatter.
// See Formatter.Appendf() for more documentation.
func Appendf(buf *byteutil.Buffer, s string, a ...interface{}) {
formatter.Appendf(buf, s, a...)
}