diff options
Diffstat (limited to 'lib/fmtutil')
-rw-r--r-- | lib/fmtutil/bitfield.go | 54 | ||||
-rw-r--r-- | lib/fmtutil/fmt.go | 71 | ||||
-rw-r--r-- | lib/fmtutil/fmt_test.go | 103 |
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) + }) +} |