summaryrefslogtreecommitdiff
path: root/lib/fmtutil
diff options
context:
space:
mode:
Diffstat (limited to 'lib/fmtutil')
-rw-r--r--lib/fmtutil/bitfield.go54
-rw-r--r--lib/fmtutil/fmt.go71
-rw-r--r--lib/fmtutil/fmt_test.go103
3 files changed, 228 insertions, 0 deletions
diff --git a/lib/fmtutil/bitfield.go b/lib/fmtutil/bitfield.go
new file mode 100644
index 0000000..b3dbe0a
--- /dev/null
+++ b/lib/fmtutil/bitfield.go
@@ -0,0 +1,54 @@
+// Copyright (C) 2022 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package util
+
+import (
+ "fmt"
+ "strings"
+)
+
+type BitfieldFormat uint8
+
+const (
+ HexNone = BitfieldFormat(iota)
+ HexLower
+ HexUpper
+)
+
+func BitfieldString[T ~uint8 | ~uint16 | ~uint32 | ~uint64](bitfield T, bitnames []string, cfg BitfieldFormat) string {
+ var out strings.Builder
+ switch cfg {
+ case HexNone:
+ // do nothing
+ case HexLower:
+ fmt.Fprintf(&out, "0x%0x(", uint64(bitfield))
+ case HexUpper:
+ fmt.Fprintf(&out, "0x%0X(", uint64(bitfield))
+ }
+ if bitfield == 0 {
+ out.WriteString("none")
+ } else {
+ rest := bitfield
+ first := true
+ for i := 0; rest != 0; i++ {
+ if rest&(1<<i) != 0 {
+ if !first {
+ out.WriteRune('|')
+ }
+ if i < len(bitnames) {
+ out.WriteString(bitnames[i])
+ } else {
+ fmt.Fprintf(&out, "(1<<%v)", i)
+ }
+ first = false
+ }
+ rest &^= 1 << i
+ }
+ }
+ if cfg != HexNone {
+ out.WriteRune(')')
+ }
+ return out.String()
+}
diff --git a/lib/fmtutil/fmt.go b/lib/fmtutil/fmt.go
new file mode 100644
index 0000000..c36ba2a
--- /dev/null
+++ b/lib/fmtutil/fmt.go
@@ -0,0 +1,71 @@
+// Copyright (C) 2022 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package util
+
+import (
+ "fmt"
+ "strings"
+)
+
+// FmtStateString returns the fmt.Printf string that produced a given
+// fmt.State and verb.
+func FmtStateString(st fmt.State, verb rune) string {
+ var ret strings.Builder
+ ret.WriteByte('%')
+ for _, flag := range []int{'-', '+', '#', ' ', '0'} {
+ if st.Flag(flag) {
+ ret.WriteByte(byte(flag))
+ }
+ }
+ if width, ok := st.Width(); ok {
+ fmt.Fprintf(&ret, "%v", width)
+ }
+ if prec, ok := st.Precision(); ok {
+ if prec == 0 {
+ ret.WriteByte('.')
+ } else {
+ fmt.Fprintf(&ret, ".%v", prec)
+ }
+ }
+ ret.WriteRune(verb)
+ return ret.String()
+}
+
+// FormatByteArrayStringer is function for helping to implement
+// fmt.Formatter for []byte or [n]byte types that have a custom string
+// representation. Use it like:
+//
+// type MyType [16]byte
+//
+// func (val MyType) String() string {
+// …
+// }
+//
+// func (val MyType) Format(f fmt.State, verb rune) {
+// util.FormatByteArrayStringer(val, val[:], f, verb)
+// }
+func FormatByteArrayStringer(
+ obj interface {
+ fmt.Stringer
+ fmt.Formatter
+ },
+ objBytes []byte,
+ f fmt.State, verb rune) {
+ switch verb {
+ case 'v':
+ if !f.Flag('#') {
+ FormatByteArrayStringer(obj, objBytes, f, 's') // as a string
+ } else {
+ byteStr := fmt.Sprintf("%#v", objBytes)
+ objType := fmt.Sprintf("%T", obj)
+ objStr := objType + strings.TrimPrefix(byteStr, "[]byte")
+ fmt.Fprintf(f, FmtStateString(f, 's'), objStr)
+ }
+ case 's', 'q': // string
+ fmt.Fprintf(f, FmtStateString(f, verb), obj.String())
+ default:
+ fmt.Fprintf(f, FmtStateString(f, verb), objBytes)
+ }
+}
diff --git a/lib/fmtutil/fmt_test.go b/lib/fmtutil/fmt_test.go
new file mode 100644
index 0000000..0aaebb5
--- /dev/null
+++ b/lib/fmtutil/fmt_test.go
@@ -0,0 +1,103 @@
+// Copyright (C) 2022 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package util_test
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+
+ "git.lukeshu.com/btrfs-progs-ng/lib/util"
+)
+
+type FmtState struct {
+ MWidth int
+ MPrec int
+ MFlagMinus bool
+ MFlagPlus bool
+ MFlagSharp bool
+ MFlagSpace bool
+ MFlagZero bool
+}
+
+func (st FmtState) Width() (int, bool) {
+ if st.MWidth < 1 {
+ return 0, false
+ }
+ return st.MWidth, true
+}
+
+func (st FmtState) Precision() (int, bool) {
+ if st.MPrec < 1 {
+ return 0, false
+ }
+ return st.MPrec, true
+}
+
+func (st FmtState) Flag(b int) bool {
+ switch b {
+ case '-':
+ return st.MFlagMinus
+ case '+':
+ return st.MFlagPlus
+ case '#':
+ return st.MFlagSharp
+ case ' ':
+ return st.MFlagSpace
+ case '0':
+ return st.MFlagZero
+ }
+ return false
+}
+
+func (st FmtState) Write([]byte) (int, error) {
+ panic("not implemented")
+}
+
+func (dst *FmtState) Format(src fmt.State, verb rune) {
+ if width, ok := src.Width(); ok {
+ dst.MWidth = width
+ }
+ if prec, ok := src.Precision(); ok {
+ dst.MPrec = prec
+ }
+ dst.MFlagMinus = src.Flag('-')
+ dst.MFlagPlus = src.Flag('+')
+ dst.MFlagSharp = src.Flag('#')
+ dst.MFlagSpace = src.Flag(' ')
+ dst.MFlagZero = src.Flag('0')
+}
+
+// letters only? No 'p', 'T', or 'w'.
+const verbs = "abcdefghijklmnoqrstuvxyzABCDEFGHIJKLMNOPQRSUVWXYZ"
+
+func FuzzFmtStateString(f *testing.F) {
+ f.Fuzz(func(t *testing.T,
+ width, prec uint8,
+ flagMinus, flagPlus, flagSharp, flagSpace, flagZero bool,
+ verbIdx uint8,
+ ) {
+ if flagMinus {
+ flagZero = false
+ }
+ input := FmtState{
+ MWidth: int(width),
+ MPrec: int(prec),
+ MFlagMinus: flagMinus,
+ MFlagPlus: flagPlus,
+ MFlagSharp: flagSharp,
+ MFlagSpace: flagSpace,
+ MFlagZero: flagZero,
+ }
+ verb := rune(verbs[int(verbIdx)%len(verbs)])
+
+ t.Logf("(%#v, %c) => %q", input, verb, util.FmtStateString(input, verb))
+
+ var output FmtState
+ assert.Equal(t, "", fmt.Sprintf(util.FmtStateString(input, verb), &output))
+ assert.Equal(t, input, output)
+ })
+}