Skip to content

Commit d75e186

Browse files
committed
fmt: add a function to recover the original format string given a State
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 <[email protected]> Reviewed-by: Russ Cox <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]> Run-TryBot: Russ Cox <[email protected]>
1 parent 27f1246 commit d75e186

File tree

3 files changed

+107
-0
lines changed

3 files changed

+107
-0
lines changed

api/next/51668.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pkg fmt, func FormatString(State, int32) string #51668

src/fmt/print.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"io"
1010
"os"
1111
"reflect"
12+
"strconv"
1213
"sync"
1314
"unicode/utf8"
1415
)
@@ -71,6 +72,31 @@ type GoStringer interface {
7172
GoString() string
7273
}
7374

75+
// FormatString returns a string representing the fully qualified formatting
76+
// directive captured by the State, followed by the argument verb. (State does not
77+
// itself contain the verb.) The result has a leading percent sign followed by any
78+
// flags, the width, and the precision. Missing flags, width, and precision are
79+
// omitted. This function allows a Formatter to reconstruct the original
80+
// directive triggering the call to Format.
81+
func FormatString(state State, verb rune) string {
82+
var tmp [16]byte // Use a local buffer.
83+
b := append(tmp[:0], '%')
84+
for _, c := range " +-#0" { // All known flags
85+
if state.Flag(int(c)) { // The argument is an int for historical reasons.
86+
b = append(b, byte(c))
87+
}
88+
}
89+
if w, ok := state.Width(); ok {
90+
b = strconv.AppendInt(b, int64(w), 10)
91+
}
92+
if p, ok := state.Precision(); ok {
93+
b = append(b, '.')
94+
b = strconv.AppendInt(b, int64(p), 10)
95+
}
96+
b = utf8.AppendRune(b, verb)
97+
return string(b)
98+
}
99+
74100
// Use simple []byte instead of bytes.Buffer to avoid large dependency.
75101
type buffer []byte
76102

src/fmt/state_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package fmt_test
6+
7+
import (
8+
"fmt"
9+
"testing"
10+
)
11+
12+
type testState struct {
13+
width int
14+
widthOK bool
15+
prec int
16+
precOK bool
17+
flag map[int]bool
18+
}
19+
20+
var _ fmt.State = testState{}
21+
22+
func (s testState) Write(b []byte) (n int, err error) {
23+
panic("unimplemented")
24+
}
25+
26+
func (s testState) Width() (wid int, ok bool) {
27+
return s.width, s.widthOK
28+
}
29+
30+
func (s testState) Precision() (prec int, ok bool) {
31+
return s.prec, s.precOK
32+
}
33+
34+
func (s testState) Flag(c int) bool {
35+
return s.flag[c]
36+
}
37+
38+
const NO = -1000
39+
40+
func mkState(w, p int, flags string) testState {
41+
s := testState{}
42+
if w != NO {
43+
s.width = w
44+
s.widthOK = true
45+
}
46+
if p != NO {
47+
s.prec = p
48+
s.precOK = true
49+
}
50+
s.flag = make(map[int]bool)
51+
for _, c := range flags {
52+
s.flag[int(c)] = true
53+
}
54+
return s
55+
}
56+
57+
func TestFormatString(t *testing.T) {
58+
var tests = []struct {
59+
width, prec int
60+
flags string
61+
result string
62+
}{
63+
{NO, NO, "", "%x"},
64+
{NO, 3, "", "%.3x"},
65+
{3, NO, "", "%3x"},
66+
{7, 3, "", "%7.3x"},
67+
{NO, NO, " +-#0", "% +-#0x"},
68+
{7, 3, "+", "%+7.3x"},
69+
{7, -3, "-", "%-7.-3x"},
70+
{7, 3, " ", "% 7.3x"},
71+
{7, 3, "#", "%#7.3x"},
72+
{7, 3, "0", "%07.3x"},
73+
}
74+
for _, test := range tests {
75+
got := fmt.FormatString(mkState(test.width, test.prec, test.flags), 'x')
76+
if got != test.result {
77+
t.Errorf("%v: got %s", test, got)
78+
}
79+
}
80+
}

0 commit comments

Comments
 (0)