codeberg.org/gruf/go-format@v1.0.6/format.go (about) 1 package format 2 3 import ( 4 "reflect" 5 "strconv" 6 "unsafe" 7 ) 8 9 // Formattable defines a type capable of being formatted and appended to a byte buffer. 10 type Formattable interface { 11 AppendFormat([]byte) []byte 12 } 13 14 // format is the object passed among the append___ formatting functions. 15 type format struct { 16 flags uint8 // 'isKey' and 'verbose' flags 17 drefs uint8 // current value deref count 18 curd uint8 // current depth 19 maxd uint8 // maximum depth 20 buf *Buffer // out buffer 21 } 22 23 const ( 24 // flag bit constants. 25 isKeyBit = uint8(1) << 0 // set to indicate key formatting 26 isValBit = uint8(1) << 1 // set to indicate value formatting 27 vboseBit = uint8(1) << 2 // set to indicate verbose formatting 28 panicBit = uint8(1) << 3 // set after panic to prevent recursion 29 rtypeBit = uint8(1) << 4 // set to indicate only print type within .Appendf() 30 ) 31 32 // AtMaxDepth returns whether format is currently at max depth. 33 func (f format) AtMaxDepth() bool { 34 return f.curd > f.maxd 35 } 36 37 // Derefs returns no. times current value has been dereferenced. 38 func (f format) Derefs() uint8 { 39 return f.drefs 40 } 41 42 // IsKey returns whether the isKey flag is set. 43 func (f format) IsKey() bool { 44 return (f.flags & isKeyBit) != 0 45 } 46 47 // IsValue returns whether the isVal flag is set. 48 func (f format) IsValue() bool { 49 return (f.flags & isValBit) != 0 50 } 51 52 // Verbose returns whether the verbose flag is set. 53 func (f format) Verbose() bool { 54 return (f.flags & vboseBit) != 0 55 } 56 57 // Panic returns whether the panic flag is set. 58 func (f format) Panic() bool { 59 return (f.flags & panicBit) != 0 60 } 61 62 // SetIsKey returns format instance with the isKey bit set to value. 63 func (f format) SetIsKey() format { 64 return format{ 65 flags: f.flags & ^isValBit | isKeyBit, 66 curd: f.curd, 67 maxd: f.maxd, 68 buf: f.buf, 69 } 70 } 71 72 // SetIsValue returns format instance with the isVal bit set to value. 73 func (f format) SetIsValue() format { 74 return format{ 75 flags: f.flags & ^isKeyBit | isValBit, 76 curd: f.curd, 77 maxd: f.maxd, 78 buf: f.buf, 79 } 80 } 81 82 // SetPanic returns format instance with the panic bit set to value. 83 func (f format) SetPanic() format { 84 return format{ 85 flags: f.flags | panicBit /* handle panic as value */ | isValBit & ^isKeyBit, 86 curd: f.curd, 87 maxd: f.maxd, 88 buf: f.buf, 89 } 90 } 91 92 // IncrDepth returns format instance with depth incremented. 93 func (f format) IncrDepth() format { 94 return format{ 95 flags: f.flags, 96 curd: f.curd + 1, 97 maxd: f.maxd, 98 buf: f.buf, 99 } 100 } 101 102 // IncrDerefs returns format instance with dereference count incremented. 103 func (f format) IncrDerefs() format { 104 return format{ 105 flags: f.flags, 106 drefs: f.drefs + 1, 107 curd: f.curd, 108 maxd: f.maxd, 109 buf: f.buf, 110 } 111 } 112 113 // appendType appends a type using supplied type str. 114 func appendType(fmt format, t string) { 115 for i := uint8(0); i < fmt.Derefs(); i++ { 116 fmt.buf.AppendByte('*') 117 } 118 fmt.buf.AppendString(t) 119 } 120 121 // appendNilType Appends nil to buf, type included if verbose. 122 func appendNilType(fmt format, t string) { 123 if !fmt.Verbose() { 124 fmt.buf.AppendString(`nil`) 125 return 126 } 127 fmt.buf.AppendByte('(') 128 appendType(fmt, t) 129 fmt.buf.AppendString(`)(nil)`) 130 } 131 132 // appendByte Appends a single byte to buf. 133 func appendByte(fmt format, b byte) { 134 if fmt.IsKey() || fmt.IsValue() || fmt.Verbose() { 135 fmt.buf.B = strconv.AppendQuoteRune(fmt.buf.B, rune(b)) 136 } else { 137 fmt.buf.AppendByte(b) 138 } 139 } 140 141 // appendBytes Appends a quoted byte slice to buf. 142 func appendBytes(fmt format, b []byte) { 143 if b == nil { 144 // Bytes CAN be nil formatted 145 appendNilType(fmt, `[]byte`) 146 } else { 147 // Append bytes as slice 148 fmt.buf.AppendByte('[') 149 for i := 0; i < len(b); i++ { 150 fmt.buf.AppendByte(b[i]) 151 fmt.buf.AppendByte(',') 152 } 153 if len(b) > 0 { 154 fmt.buf.Truncate(1) 155 } 156 fmt.buf.AppendByte(']') 157 } 158 } 159 160 // appendString Appends an escaped, double-quoted string to buf. 161 func appendString(fmt format, s string) { 162 switch { 163 // Key in a key-value pair 164 case fmt.IsKey(): 165 if !strconv.CanBackquote(s) { 166 // Requires quoting AND escaping 167 fmt.buf.B = strconv.AppendQuote(fmt.buf.B, s) 168 } else if containsSpaceOrTab(s) { 169 // Contains space, needs quotes 170 fmt.buf.AppendString(`"` + s + `"`) 171 } else { 172 // All else write as-is 173 fmt.buf.AppendString(s) 174 } 175 176 // Value in a key-value pair (always escape+quote) 177 case fmt.IsValue(): 178 fmt.buf.B = strconv.AppendQuote(fmt.buf.B, s) 179 180 // Verbose but neither key nor value (always quote) 181 case fmt.Verbose(): 182 fmt.buf.AppendString(`"` + s + `"`) 183 184 // All else 185 default: 186 fmt.buf.AppendString(s) 187 } 188 } 189 190 // appendBool Appends a formatted bool to buf. 191 func appendBool(fmt format, b bool) { 192 fmt.buf.B = strconv.AppendBool(fmt.buf.B, b) 193 } 194 195 // appendInt Appends a formatted int to buf. 196 func appendInt(fmt format, i int64) { 197 fmt.buf.B = strconv.AppendInt(fmt.buf.B, i, 10) 198 } 199 200 // appendUint Appends a formatted uint to buf. 201 func appendUint(fmt format, u uint64) { 202 fmt.buf.B = strconv.AppendUint(fmt.buf.B, u, 10) 203 } 204 205 // appendFloat Appends a formatted float to buf. 206 func appendFloat(fmt format, f float64) { 207 fmt.buf.B = strconv.AppendFloat(fmt.buf.B, f, 'f', -1, 64) 208 } 209 210 // appendComplex Appends a formatted complex128 to buf. 211 func appendComplex(fmt format, c complex128) { 212 appendFloat(fmt, real(c)) 213 fmt.buf.AppendByte('+') 214 appendFloat(fmt, imag(c)) 215 fmt.buf.AppendByte('i') 216 } 217 218 // isNil will safely check if 'v' is nil without dealing with weird Go interface nil bullshit. 219 func isNil(i interface{}) bool { 220 return (*(*struct{ _, v unsafe.Pointer })(unsafe.Pointer(&i))).v == nil //nolint 221 } 222 223 // appendIfaceOrReflectValue will attempt to append as interface, falling back to reflection. 224 func appendIfaceOrRValue(fmt format, i interface{}) { 225 if !appendIface(fmt, i) { 226 appendRValue(fmt, reflect.ValueOf(i)) 227 } 228 } 229 230 // appendValueNext checks for interface methods before performing appendRValue, checking + incr depth. 231 func appendRValueOrIfaceNext(fmt format, v reflect.Value) { 232 // Check we haven't hit max 233 if fmt.AtMaxDepth() { 234 fmt.buf.AppendString("...") 235 return 236 } 237 238 // Incr the depth 239 fmt = fmt.IncrDepth() 240 241 // Make actual call 242 if !v.CanInterface() || 243 !appendIface(fmt, v.Interface()) { 244 appendRValue(fmt, v) 245 } 246 } 247 248 // catchPanic is a deferrable panic catcher. 249 func catchPanic(fmt format) { 250 if r := recover(); r != nil { 251 // DON'T recurse catchPanic() 252 if fmt.Panic() { 253 panic(r) 254 } 255 256 // Attempt to decode panic into buf 257 fmt.buf.AppendString(`!{PANIC=`) 258 appendIfaceOrRValue(fmt.SetPanic(), r) 259 fmt.buf.AppendByte('}') 260 } 261 } 262 263 // appendIface parses and Appends a formatted interface value to buf. 264 func appendIface(fmt format, i interface{}) (ok bool) { 265 // Default true 266 ok = true 267 268 switch i := i.(type) { 269 // Nil type 270 case nil: 271 fmt.buf.AppendString(`nil`) 272 273 // Reflect types 274 case reflect.Type: 275 switch { 276 case isNil(i) /* safer nil check */ : 277 appendNilType(fmt, `reflect.Type`) 278 case fmt.Verbose(): 279 appendType(fmt, `reflect.Type`) 280 fmt.buf.AppendString(`(` + i.String() + `)`) 281 default: 282 fmt.buf.AppendString(i.String()) 283 } 284 case reflect.Value: 285 appendType(fmt, `reflect.Value`) 286 fmt.buf.AppendByte('(') 287 fmt.flags |= vboseBit 288 appendRValue(fmt, i) 289 fmt.buf.AppendByte(')') 290 291 // Bytes and string types 292 case byte /* also uint8 */ : 293 appendByte(fmt, i) 294 case []byte: 295 appendBytes(fmt, i) 296 case string: 297 appendString(fmt, i) 298 299 // Int types 300 case int: 301 appendInt(fmt, int64(i)) 302 case int8: 303 appendInt(fmt, int64(i)) 304 case int16: 305 appendInt(fmt, int64(i)) 306 case int32 /* also rune */ : 307 appendInt(fmt, int64(i)) 308 case int64: 309 appendInt(fmt, i) 310 311 // Uint types 312 case uint: 313 appendUint(fmt, uint64(i)) 314 case uint16: 315 appendUint(fmt, uint64(i)) 316 case uint32: 317 appendUint(fmt, uint64(i)) 318 case uint64: 319 appendUint(fmt, i) 320 321 // Float types 322 case float32: 323 appendFloat(fmt, float64(i)) 324 case float64: 325 appendFloat(fmt, i) 326 327 // Bool type 328 case bool: 329 appendBool(fmt, i) 330 331 // Complex types 332 case complex64: 333 appendComplex(fmt, complex128(i)) 334 case complex128: 335 appendComplex(fmt, i) 336 337 // Method types 338 case error: 339 switch { 340 case fmt.Verbose(): 341 return false 342 case isNil(i) /* use safer nil check */ : 343 appendNilType(fmt, reflect.TypeOf(i).String()) 344 default: 345 defer catchPanic(fmt) 346 appendString(fmt, i.Error()) 347 } 348 case Formattable: 349 switch { 350 case fmt.Verbose(): 351 return false 352 case isNil(i) /* use safer nil check */ : 353 t := reflect.TypeOf(i) 354 appendNilType(fmt, t.String()) 355 default: 356 defer catchPanic(fmt) 357 fmt.buf.B = i.AppendFormat(fmt.buf.B) 358 } 359 case interface{ String() string }: 360 switch { 361 case fmt.Verbose(): 362 return false 363 case isNil(i) /* use safer nil check */ : 364 t := reflect.TypeOf(i) 365 appendNilType(fmt, t.String()) 366 default: 367 defer catchPanic(fmt) 368 appendString(fmt, i.String()) 369 } 370 371 // No quick handler 372 default: 373 return false 374 } 375 376 return ok 377 } 378 379 // appendReflectValue will safely append a reflected value. 380 func appendRValue(fmt format, v reflect.Value) { 381 switch v.Kind() { 382 // String and byte types 383 case reflect.Uint8: 384 appendByte(fmt, byte(v.Uint())) 385 case reflect.String: 386 appendString(fmt, v.String()) 387 388 // Float tpyes 389 case reflect.Float32, reflect.Float64: 390 appendFloat(fmt, v.Float()) 391 392 // Int types 393 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 394 appendInt(fmt, v.Int()) 395 396 // Uint types 397 case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64: 398 appendUint(fmt, v.Uint()) 399 400 // Complex types 401 case reflect.Complex64, reflect.Complex128: 402 appendComplex(fmt, v.Complex()) 403 404 // Bool type 405 case reflect.Bool: 406 appendBool(fmt, v.Bool()) 407 408 // Slice and array types 409 case reflect.Array: 410 appendArrayType(fmt, v) 411 case reflect.Slice: 412 if v.IsNil() { 413 appendNilType(fmt, v.Type().String()) 414 } else { 415 appendArrayType(fmt, v) 416 } 417 418 // Map types 419 case reflect.Map: 420 if v.IsNil() { 421 appendNilType(fmt, v.Type().String()) 422 } else { 423 appendMapType(fmt, v) 424 } 425 426 // Struct types 427 case reflect.Struct: 428 appendStructType(fmt, v) 429 430 // Deref'able ptr types 431 case reflect.Ptr, reflect.Interface: 432 if v.IsNil() { 433 appendNilType(fmt, v.Type().String()) 434 } else { 435 appendRValue(fmt.IncrDerefs(), v.Elem()) 436 } 437 438 // 'raw' pointer types 439 case reflect.UnsafePointer: 440 appendType(fmt, `unsafe.Pointer`) 441 fmt.buf.AppendByte('(') 442 if u := v.Pointer(); u != 0 { 443 fmt.buf.AppendString("0x") 444 fmt.buf.B = strconv.AppendUint(fmt.buf.B, uint64(u), 16) 445 } else { 446 fmt.buf.AppendString(`nil`) 447 } 448 fmt.buf.AppendByte(')') 449 case reflect.Uintptr: 450 appendType(fmt, `uintptr`) 451 fmt.buf.AppendByte('(') 452 if u := v.Uint(); u != 0 { 453 fmt.buf.AppendString("0x") 454 fmt.buf.B = strconv.AppendUint(fmt.buf.B, u, 16) 455 } else { 456 fmt.buf.AppendString(`nil`) 457 } 458 fmt.buf.AppendByte(')') 459 460 // Generic types we don't *exactly* handle 461 case reflect.Func, reflect.Chan: 462 if v.IsNil() { 463 appendNilType(fmt, v.Type().String()) 464 } else { 465 fmt.buf.AppendString(v.String()) 466 } 467 468 // Unhandled kind 469 default: 470 fmt.buf.AppendString(v.String()) 471 } 472 } 473 474 // appendArrayType Appends an array of unknown type (parsed by reflection) to buf, unlike appendSliceType does NOT catch nil slice. 475 func appendArrayType(fmt format, v reflect.Value) { 476 // Prepend type if verbose 477 if fmt.Verbose() { 478 t := v.Type() 479 appendType(fmt, t.String()) 480 } 481 482 // get no. elements 483 n := v.Len() 484 485 fmt.buf.AppendByte('[') 486 487 // Append values 488 for i := 0; i < n; i++ { 489 appendRValueOrIfaceNext(fmt.SetIsValue(), v.Index(i)) 490 fmt.buf.AppendByte(',') 491 } 492 493 // Drop last comma 494 if n > 0 { 495 fmt.buf.Truncate(1) 496 } 497 498 fmt.buf.AppendByte(']') 499 } 500 501 // appendMapType Appends a map of unknown types (parsed by reflection) to buf. 502 func appendMapType(fmt format, v reflect.Value) { 503 // Prepend type if verbose 504 if fmt.Verbose() { 505 t := v.Type() 506 appendType(fmt, t.String()) 507 } 508 509 // Get a map iterator 510 r := v.MapRange() 511 n := v.Len() 512 513 fmt.buf.AppendByte('{') 514 515 // Iterate pairs 516 for r.Next() { 517 appendRValueOrIfaceNext(fmt.SetIsKey(), r.Key()) 518 fmt.buf.AppendByte('=') 519 appendRValueOrIfaceNext(fmt.SetIsValue(), r.Value()) 520 fmt.buf.AppendByte(' ') 521 } 522 523 // Drop last space 524 if n > 0 { 525 fmt.buf.Truncate(1) 526 } 527 528 fmt.buf.AppendByte('}') 529 } 530 531 // appendStructType Appends a struct (as a set of key-value fields) to buf. 532 func appendStructType(fmt format, v reflect.Value) { 533 // Get value type & no. fields 534 t := v.Type() 535 n := v.NumField() 536 537 // Prepend type if verbose 538 if fmt.Verbose() { 539 t := v.Type() 540 appendType(fmt, t.String()) 541 } 542 543 fmt.buf.AppendByte('{') 544 545 // Iterate fields 546 for i := 0; i < n; i++ { 547 vfield := v.Field(i) 548 tfield := t.Field(i) 549 550 // Append field name 551 fmt.buf.AppendString(tfield.Name) 552 fmt.buf.AppendByte('=') 553 appendRValueOrIfaceNext(fmt.SetIsValue(), vfield) 554 555 // Iter written count 556 fmt.buf.AppendByte(' ') 557 } 558 559 // Drop last space 560 if n > 0 { 561 fmt.buf.Truncate(1) 562 } 563 564 fmt.buf.AppendByte('}') 565 } 566 567 // containsSpaceOrTab checks if "s" contains space or tabs. 568 // NOTE: we rely on bytealg.IndexByteString() as it's ASM. 569 func containsSpaceOrTab(s string) bool { 570 if i := bytealg_IndexBytes(s, ' '); i != -1 { 571 return true 572 } else if i := bytealg_IndexBytes(s, '\t'); i != -1 { 573 return true 574 } 575 return false 576 }