lab.nexedi.com/kirr/go123@v0.0.0-20240207185015-8299741fa871/xfmt/fmt.go (about) 1 // Copyright (C) 2017-2019 Nexedi SA and Contributors. 2 // Kirill Smelkov <kirr@nexedi.com> 3 // 4 // This program is free software: you can Use, Study, Modify and Redistribute 5 // it under the terms of the GNU General Public License version 3, or (at your 6 // option) any later version, as published by the Free Software Foundation. 7 // 8 // You can also Link and Combine this program with other software covered by 9 // the terms of any of the Free Software licenses or any of the Open Source 10 // Initiative approved licenses and Convey the resulting work. Corresponding 11 // source of such a combination shall include the source code for all other 12 // software used. 13 // 14 // This program is distributed WITHOUT ANY WARRANTY; without even the implied 15 // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 16 // 17 // See COPYING file for full licensing terms. 18 // See https://www.nexedi.com/licensing for rationale and options. 19 20 // Package xfmt provides addons to std fmt and strconv packages with focus on 21 // formatting text without allocations. 22 // 23 // For example if in fmt speak you have 24 // 25 // s := fmt.Sprintf("hello %q %d %x", "world", 1, []byte("data")) 26 // 27 // xfmt analog would be 28 // 29 // buf := xfmt.Buffer{} 30 // buf .S("hello ") .Q("world") .C(' ') .D(1) .C(' ') .Xb([]byte("data")) 31 // s := buf.Bytes() 32 // 33 // xfmt.Buffer can be reused several times via Buffer.Reset() . 34 package xfmt 35 36 import ( 37 "encoding/hex" 38 "strconv" 39 "unicode/utf8" 40 41 "lab.nexedi.com/kirr/go123/mem" 42 "lab.nexedi.com/kirr/go123/xbytes" 43 ) 44 45 const ( 46 hexdigits = "0123456789abcdef" 47 ) 48 49 // Stringer is interface for natively formatting a value representation via xfmt. 50 type Stringer interface { 51 // XFmtString method is used to append formatted value to destination buffer 52 // The grown buffer have to be returned 53 XFmtString(b []byte) []byte 54 } 55 56 // Buffer provides syntactic sugar for formatting mimicking fmt.Printf style. 57 // 58 // XXX combine with bytes.Buffer ? 59 type Buffer []byte 60 61 // Reset empties the buffer keeping underlying storage for future formattings. 62 func (b *Buffer) Reset() { 63 *b = (*b)[:0] 64 } 65 66 // Bytes returns buffer storage as []byte. 67 func (b Buffer) Bytes() []byte { 68 return []byte(b) 69 } 70 71 // Append appends to b formatted x. 72 // 73 // NOTE sadly since x is interface it makes real value substituted to it 74 // escape to heap (not so a problem since usually they already are) but then also 75 // if x has non-pointer receiver convT2I _allocates_ memory for the value copy. 76 // 77 // -> always pass to append &object, even if object has non-pointer XFmtString receiver. 78 func Append(b []byte, x Stringer) []byte { 79 return x.XFmtString(b) 80 } 81 82 // V, similarly to %v, adds x formatted by default rules. 83 func (b *Buffer) V(x Stringer) *Buffer { 84 *b = Append(*b, x) 85 return b 86 } 87 88 // S appends string formatted by %s. 89 func (b *Buffer) S(s string) *Buffer { 90 *b = append(*b, s...) 91 return b 92 } 93 94 // Sb appends []byte formatted by %s. 95 func (b *Buffer) Sb(x []byte) *Buffer { 96 *b = append(*b, x...) 97 return b 98 } 99 100 // Q appends string formatted by %q. 101 func (b *Buffer) Q(s string) *Buffer { 102 *b = strconv.AppendQuote(*b, s) 103 return b 104 } 105 106 // Qb appends []byte formatted by %q. 107 func (b *Buffer) Qb(s []byte) *Buffer { 108 *b = strconv.AppendQuote(*b, mem.String(s)) 109 return b 110 } 111 112 // Qcb appends byte formatted by %q. 113 func (b *Buffer) Qcb(c byte) *Buffer { 114 return b.Qc(rune(c)) 115 } 116 117 // Qc appends rune formatted by %q. 118 func (b *Buffer) Qc(c rune) *Buffer { 119 *b = strconv.AppendQuoteRune(*b, c) 120 return b 121 } 122 123 // Cb appends byte formatted by %c. 124 func (b *Buffer) Cb(c byte) *Buffer { 125 *b = append(*b, c) 126 return b 127 } 128 129 // AppendRune appends to b UTF-8 encoding of r. 130 func AppendRune(b []byte, r rune) []byte { 131 l := len(b) 132 b = xbytes.Grow(b, utf8.UTFMax) 133 n := utf8.EncodeRune(b[l:], r) 134 return b[:l+n] 135 } 136 137 // C appends rune formatted by %c. 138 func (b *Buffer) C(r rune) *Buffer { 139 *b = AppendRune(*b, r) 140 return b 141 } 142 143 // D appends int formatted by %d. 144 func (b *Buffer) D(i int) *Buffer { 145 *b = strconv.AppendInt(*b, int64(i), 10) 146 return b 147 } 148 149 // D64 appends int64 formatted by %d. 150 func (b *Buffer) D64(i int64) *Buffer { 151 *b = strconv.AppendInt(*b, i, 10) 152 return b 153 } 154 155 // X appends int formatted by %x. 156 func (b *Buffer) X(i int) *Buffer { 157 *b = strconv.AppendInt(*b, int64(i), 16) 158 return b 159 } 160 161 // AppendHex appends to b hex representation of x. 162 func AppendHex(b []byte, x []byte) []byte { 163 lx := hex.EncodedLen(len(x)) 164 lb := len(b) 165 b = xbytes.Grow(b, lx) 166 hex.Encode(b[lb:], x) 167 return b 168 } 169 170 // Xb appends []byte formatted by %x. 171 func (b *Buffer) Xb(x []byte) *Buffer { 172 *b = AppendHex(*b, x) 173 return b 174 } 175 176 // Xs appends string formatted by %x. 177 func (b *Buffer) Xs(x string) *Buffer { 178 return b.Xb(mem.Bytes(x)) 179 } 180 181 // TODO XX = %X 182 183 // AppendHex016 appends to b x formatted 16-character hex string. 184 func AppendHex016(b []byte, x uint64) []byte { 185 // like sprintf("%016x") but faster and less allocations 186 l := len(b) 187 b = xbytes.Grow(b, 16) 188 bb := b[l:] 189 for i := 15; i >= 0; i-- { 190 bb[i] = hexdigits[x&0xf] 191 x >>= 4 192 } 193 return b 194 } 195 196 // X016, similarly to %016x, adds hex representation of uint64 x. 197 func (b *Buffer) X016(x uint64) *Buffer { 198 *b = AppendHex016(*b, x) 199 return b 200 }