]> Cypherpunks repositories - gostls13.git/commitdiff
fmt: add a function to recover the original format string given a State
authorRob Pike <r@golang.org>
Tue, 19 Apr 2022 04:06:41 +0000 (14:06 +1000)
committerRob Pike <r@golang.org>
Sat, 6 Aug 2022 09:19:31 +0000 (09:19 +0000)
Sometimes when implementing a Formatter it's helpful to use the fmt
package without invoking the formatter. This new function, FormatString,
makes that easier in some cases by recreating the original formatting
directive (such as "%3.2f") that caused Formatter.Format to be
called.

The original Formatter interface is probably not what we would
design today, but we're stuck with it. FormatString, although it
takes a State as an argument, compensates by making Formatter a
little more flexible.

The State does not include the verb so (unlike in the issue), we
must provide it explicitly in the call to FormatString. Doing it there
minimizes allocations by returning the complete format string.

Fixes #51668
Updates #51195

Change-Id: Ie31c8256515864b2f460df45fbd231286b8b7a28
Reviewed-on: https://go-review.googlesource.com/c/go/+/400875
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Run-TryBot: Russ Cox <rsc@golang.org>

api/next/51668.txt [new file with mode: 0644]
src/fmt/print.go
src/fmt/state_test.go [new file with mode: 0644]

diff --git a/api/next/51668.txt b/api/next/51668.txt
new file mode 100644 (file)
index 0000000..c0c2e07
--- /dev/null
@@ -0,0 +1 @@
+pkg fmt, func FormatString(State, int32) string #51668
index 2af7bd0c426e03e2123bffcb2fb11cae46935075..85f70439f3be5075e689a8690e17ac3b16b978fd 100644 (file)
@@ -9,6 +9,7 @@ import (
        "io"
        "os"
        "reflect"
+       "strconv"
        "sync"
        "unicode/utf8"
 )
@@ -71,6 +72,31 @@ type GoStringer interface {
        GoString() string
 }
 
+// FormatString returns a string representing the fully qualified formatting
+// directive captured by the State, followed by the argument verb. (State does not
+// itself contain the verb.) The result has a leading percent sign followed by any
+// flags, the width, and the precision. Missing flags, width, and precision are
+// omitted. This function allows a Formatter to reconstruct the original
+// directive triggering the call to Format.
+func FormatString(state State, verb rune) string {
+       var tmp [16]byte // Use a local buffer.
+       b := append(tmp[:0], '%')
+       for _, c := range " +-#0" { // All known flags
+               if state.Flag(int(c)) { // The argument is an int for historical reasons.
+                       b = append(b, byte(c))
+               }
+       }
+       if w, ok := state.Width(); ok {
+               b = strconv.AppendInt(b, int64(w), 10)
+       }
+       if p, ok := state.Precision(); ok {
+               b = append(b, '.')
+               b = strconv.AppendInt(b, int64(p), 10)
+       }
+       b = utf8.AppendRune(b, verb)
+       return string(b)
+}
+
 // Use simple []byte instead of bytes.Buffer to avoid large dependency.
 type buffer []byte
 
diff --git a/src/fmt/state_test.go b/src/fmt/state_test.go
new file mode 100644 (file)
index 0000000..fda660a
--- /dev/null
@@ -0,0 +1,80 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fmt_test
+
+import (
+       "fmt"
+       "testing"
+)
+
+type testState struct {
+       width   int
+       widthOK bool
+       prec    int
+       precOK  bool
+       flag    map[int]bool
+}
+
+var _ fmt.State = testState{}
+
+func (s testState) Write(b []byte) (n int, err error) {
+       panic("unimplemented")
+}
+
+func (s testState) Width() (wid int, ok bool) {
+       return s.width, s.widthOK
+}
+
+func (s testState) Precision() (prec int, ok bool) {
+       return s.prec, s.precOK
+}
+
+func (s testState) Flag(c int) bool {
+       return s.flag[c]
+}
+
+const NO = -1000
+
+func mkState(w, p int, flags string) testState {
+       s := testState{}
+       if w != NO {
+               s.width = w
+               s.widthOK = true
+       }
+       if p != NO {
+               s.prec = p
+               s.precOK = true
+       }
+       s.flag = make(map[int]bool)
+       for _, c := range flags {
+               s.flag[int(c)] = true
+       }
+       return s
+}
+
+func TestFormatString(t *testing.T) {
+       var tests = []struct {
+               width, prec int
+               flags       string
+               result      string
+       }{
+               {NO, NO, "", "%x"},
+               {NO, 3, "", "%.3x"},
+               {3, NO, "", "%3x"},
+               {7, 3, "", "%7.3x"},
+               {NO, NO, " +-#0", "% +-#0x"},
+               {7, 3, "+", "%+7.3x"},
+               {7, -3, "-", "%-7.-3x"},
+               {7, 3, " ", "% 7.3x"},
+               {7, 3, "#", "%#7.3x"},
+               {7, 3, "0", "%07.3x"},
+       }
+       for _, test := range tests {
+               got := fmt.FormatString(mkState(test.width, test.prec, test.flags), 'x')
+               if got != test.result {
+                       t.Errorf("%v: got %s", test, got)
+               }
+       }
+}