codeberg.org/gruf/go-format@v1.0.6/formatter.go (about) 1 package format 2 3 import ( 4 "reflect" 5 "strconv" 6 _ "unsafe" 7 ) 8 9 // Formatter allows configuring value and string formatting. 10 type Formatter struct { 11 // MaxDepth specifies the max depth of fields the formatter will iterate. 12 // Once max depth is reached, value will simply be formatted as "...". 13 // e.g. 14 // 15 // MaxDepth=1 16 // type A struct{ 17 // Nested B 18 // } 19 // type B struct{ 20 // Nested C 21 // } 22 // type C struct{ 23 // Field string 24 // } 25 // 26 // Append(&buf, A{}) => {Nested={Nested={Field=...}}} 27 MaxDepth uint8 28 } 29 30 // Append will append formatted form of supplied values into 'buf'. 31 func (f Formatter) Append(buf *Buffer, v ...interface{}) { 32 for _, v := range v { 33 appendIfaceOrRValue(format{maxd: f.MaxDepth, buf: buf}, v) 34 buf.AppendByte(' ') 35 } 36 if len(v) > 0 { 37 buf.Truncate(1) 38 } 39 } 40 41 // Appendf will append the formatted string with supplied values into 'buf'. 42 // Supported format directives: 43 // - '{}' => format supplied arg, in place 44 // - '{0}' => format arg at index 0 of supplied, in place 45 // - '{:?}' => format supplied arg verbosely, in place 46 // - '{:k}' => format supplied arg as key, in place 47 // - '{:v}' => format supplied arg as value, in place 48 // - '{:T}' => format supplied arg's type, in place 49 // 50 // To escape either of '{}' simply append an additional brace e.g. 51 // - '{{' => '{' 52 // - '}}' => '}' 53 // - '{{}}' => '{}' 54 // - '{{:?}}' => '{:?}' 55 // 56 // More formatting directives might be included in the future. 57 func (f Formatter) Appendf(buf *Buffer, s string, a ...interface{}) { 58 const ( 59 // ground state 60 modeNone = uint8(0) 61 62 // prev reached '{' 63 modeOpen = uint8(1) 64 65 // prev reached '}' 66 modeClose = uint8(2) 67 68 // parsing directive index 69 modeIdx = uint8(3) 70 71 // parsing directive operands 72 modeOp = uint8(4) 73 ) 74 75 var ( 76 // mode is current parsing mode 77 mode uint8 78 79 // arg is the current arg index 80 arg int 81 82 // carg is current directive-set arg index 83 carg int 84 85 // last is the trailing cursor to see slice windows 86 last int 87 88 // idx is the current index in 's' 89 idx int 90 91 // fmt is the base argument formatter 92 fmt = format{ 93 maxd: f.MaxDepth, 94 buf: buf, 95 } 96 97 // NOTE: these functions are defined here as function 98 // locals as it turned out to be better for performance 99 // doing it this way, than encapsulating their logic in 100 // some kind of parsing structure. Maybe if the parser 101 // was pooled along with the buffers it might work out 102 // better, but then it makes more internal functions i.e. 103 // .Append() .Appendf() less accessible outside package. 104 // 105 // Currently, passing '-gcflags "-l=4"' causes a not 106 // insignificant decrease in ns/op, which is likely due 107 // to more aggressive function inlining, which this 108 // function can obviously stand to benefit from :) 109 110 // Str returns current string window slice, and updates 111 // the trailing cursor 'last' to current 'idx' 112 Str = func() string { 113 str := s[last:idx] 114 last = idx 115 return str 116 } 117 118 // MoveUp moves the trailing cursor 'last' just past 'idx' 119 MoveUp = func() { 120 last = idx + 1 121 } 122 123 // MoveUpTo moves the trailing cursor 'last' either up to 124 // closest '}', or current 'idx', whichever is furthest. 125 // NOTE: by calling bytealg.IndexByteString() directly (and 126 // not the strconv pass-through, we shave-off complexity 127 // which allows this function to be inlined). 128 MoveUpTo = func() { 129 i := bytealg_IndexBytes(s[idx:], '}') 130 if i >= 0 { 131 idx += i 132 } 133 MoveUp() 134 } 135 136 // ParseIndex parses an integer from the current string 137 // window, updating 'last' to 'idx'. The string window 138 // is ASSUMED to contain only valid ASCII numbers. This 139 // only returns false if number exceeds platform int size 140 ParseIndex = func() bool { 141 var str string 142 143 // Get current window 144 if str = Str(); len(str) < 1 { 145 return true 146 } 147 148 // Index HAS to fit within platform integer size 149 if !(strconv.IntSize == 32 && (0 < len(s) && len(s) < 10)) || 150 !(strconv.IntSize == 64 && (0 < len(s) && len(s) < 19)) { 151 return false 152 } 153 154 carg = 0 155 156 // Build integer from string 157 for i := 0; i < len(str); i++ { 158 carg = carg*10 + int(str[i]-'0') 159 } 160 161 return true 162 } 163 164 // ValidOp checks that for current ending idx, that a valid 165 // operand was achieved -- only 0 and 1 are acceptable lens 166 ValidOp = func() bool { 167 diff := idx - last 168 last = idx 169 return (diff < 2) 170 } 171 172 // AppendArg will take either the directive-set, or 173 // iterated arg index, check within bounds of 'a' and 174 // append the that argument formatted to the buffer. 175 // On failure, it will append an error string 176 AppendArg = func() { 177 // Look for idx 178 if carg < 0 { 179 carg = arg 180 } 181 182 // Incr idx 183 arg++ 184 185 if carg < len(a) { 186 if fmt.flags&rtypeBit != 0 { 187 // Append the arg's type string 188 appendIface(fmt, reflect.TypeOf(a[carg])) 189 } else { 190 // Append formatted argument value 191 appendIfaceOrRValue(fmt, a[carg]) 192 } 193 } else { 194 // No argument found for index 195 buf.AppendString(`!{MISSING_ARG}`) 196 } 197 } 198 199 // Reset will reset the mode to ground, the flags 200 // to empty and parsed 'carg' to empty 201 Reset = func() { 202 mode = modeNone 203 fmt.flags = 0 204 carg = -1 205 } 206 ) 207 208 for idx = 0; idx < len(s); idx++ { 209 // Get next char 210 c := s[idx] 211 212 switch mode { 213 // Ground mode 214 case modeNone: 215 switch c { 216 case '{': 217 // Enter open mode 218 buf.AppendString(Str()) 219 mode = modeOpen 220 MoveUp() 221 case '}': 222 // Enter close mode 223 buf.AppendString(Str()) 224 mode = modeClose 225 MoveUp() 226 } 227 228 // Encountered open '{' 229 case modeOpen: 230 switch c { 231 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': 232 // Starting index 233 mode = modeIdx 234 MoveUp() 235 case '{': 236 // Escaped bracket 237 buf.AppendByte('{') 238 mode = modeNone 239 MoveUp() 240 case '}': 241 // Format arg 242 AppendArg() 243 Reset() 244 MoveUp() 245 case ':': 246 // Starting operands 247 mode = modeOp 248 MoveUp() 249 default: 250 // Bad char, missing a close 251 buf.AppendString(`!{MISSING_CLOSE}`) 252 mode = modeNone 253 MoveUpTo() 254 } 255 256 // Encountered close '}' 257 case modeClose: 258 switch c { 259 case '}': 260 // Escaped close bracket 261 buf.AppendByte('}') 262 mode = modeNone 263 MoveUp() 264 default: 265 // Missing an open bracket 266 buf.AppendString(`!{MISSING_OPEN}`) 267 mode = modeNone 268 MoveUp() 269 } 270 271 // Preparing index 272 case modeIdx: 273 switch c { 274 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': 275 case ':': 276 if !ParseIndex() { 277 // Unable to parse an integer 278 buf.AppendString(`!{BAD_INDEX}`) 279 mode = modeNone 280 MoveUpTo() 281 } else { 282 // Starting operands 283 mode = modeOp 284 MoveUp() 285 } 286 case '}': 287 if !ParseIndex() { 288 // Unable to parse an integer 289 buf.AppendString(`!{BAD_INDEX}`) 290 } else { 291 // Format arg 292 AppendArg() 293 } 294 Reset() 295 MoveUp() 296 default: 297 // Not a valid index character 298 buf.AppendString(`!{BAD_INDEX}`) 299 mode = modeNone 300 MoveUpTo() 301 } 302 303 // Preparing operands 304 case modeOp: 305 switch c { 306 case 'k': 307 fmt.flags |= isKeyBit 308 case 'v': 309 fmt.flags |= isValBit 310 case '?': 311 fmt.flags |= vboseBit 312 case 'T': 313 fmt.flags |= rtypeBit 314 case '}': 315 if !ValidOp() { 316 // Bad operands parsed 317 buf.AppendString(`!{BAD_OPERAND}`) 318 } else { 319 // Format arg 320 AppendArg() 321 } 322 Reset() 323 MoveUp() 324 default: 325 // Not a valid operand char 326 buf.AppendString(`!{BAD_OPERAND}`) 327 Reset() 328 MoveUpTo() 329 } 330 } 331 } 332 333 // Append any remaining 334 buf.AppendString(s[last:]) 335 } 336 337 // formatter is the default formatter instance. 338 var formatter = Formatter{ 339 MaxDepth: 10, 340 } 341 342 // Append will append formatted form of supplied values into 'buf' using default formatter. 343 // See Formatter.Append() for more documentation. 344 func Append(buf *Buffer, v ...interface{}) { 345 formatter.Append(buf, v...) 346 } 347 348 // Appendf will append the formatted string with supplied values into 'buf' using default formatter. 349 // See Formatter.Appendf() for more documentation. 350 func Appendf(buf *Buffer, s string, a ...interface{}) { 351 formatter.Appendf(buf, s, a...) 352 } 353 354 //go:linkname bytealg_IndexBytes internal/bytealg.IndexByteString 355 func bytealg_IndexBytes(string, byte) int