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  }