diff --git a/encode.go b/encode.go index 46c7ec0..f6fd255 100644 --- a/encode.go +++ b/encode.go @@ -2,6 +2,7 @@ package tblfmt import ( "bufio" + "bytes" "encoding/csv" "encoding/json" "fmt" @@ -39,6 +40,9 @@ type TableEncoder struct { // lineStyle is the table line style. lineStyle LineStyle + // rowStyles is the table of row style. + rowStyles map[[4]rune]rowStyle + // formatter handles formatting values prior to output. formatter Formatter @@ -94,6 +98,7 @@ func NewTableEncoder(resultSet ResultSet, opts ...Option) (Encoder, error) { border: 1, tab: 8, lineStyle: ASCIILineStyle(), + rowStyles: make(map[[4]rune]rowStyle), formatter: NewEscapeFormatter(WithHeaderAlign(AlignCenter)), summary: DefaultTableSummary(), empty: &Value{ @@ -117,6 +122,7 @@ func NewTableEncoder(resultSet ResultSet, opts ...Option) (Encoder, error) { enc.lineStyle.Wrap, enc.lineStyle.End, } { + enc.rowStyles[l] = enc.rowStyle(l) for _, r := range l { if r != 0 && runewidth.RuneWidth(r) != 1 { return nil, ErrInvalidLineStyle @@ -214,7 +220,7 @@ func (enc *TableEncoder) Encode(w io.Writer) error { // draw end border if enc.border >= 2 { - enc.divider(enc.rowStyle(enc.lineStyle.End)) + enc.divider(enc.rowStyles[enc.lineStyle.End]) } } @@ -239,7 +245,7 @@ func (enc *TableEncoder) initBuffers() { } func (enc *TableEncoder) encodeVals(vals [][]*Value) error { - rs := enc.rowStyle(enc.lineStyle.Row) + rs := enc.rowStyles[enc.lineStyle.Row] // print buffered vals for i := 0; i < len(vals); i++ { enc.row(vals[i], rs) @@ -308,7 +314,7 @@ func (enc *TableEncoder) nextResults() ([][]*Value, error) { func (enc *TableEncoder) calcWidth(vals [][]*Value) { // calc offsets and widths for this batch of rows var offset int - rs := enc.rowStyle(enc.lineStyle.Row) + rs := enc.rowStyles[enc.lineStyle.Row] offset += runewidth.StringWidth(string(rs.left)) for i, h := range enc.headers { if i != 0 { @@ -339,7 +345,7 @@ func (enc *TableEncoder) calcWidth(vals [][]*Value) { } func (enc *TableEncoder) header() { - rs := enc.rowStyle(enc.lineStyle.Row) + rs := enc.rowStyles[enc.lineStyle.Row] if enc.title != nil && enc.title.Width != 0 { maxWidth := ((enc.tableWidth() - enc.title.Width) / 2) + enc.title.Width @@ -348,12 +354,12 @@ func (enc *TableEncoder) header() { } // draw top border if enc.border >= 2 && !enc.inline { - enc.divider(enc.rowStyle(enc.lineStyle.Top)) + enc.divider(enc.rowStyles[enc.lineStyle.Top]) } // draw the header row with top border style if enc.inline { - rs = enc.rowStyle(enc.lineStyle.Top) + rs = enc.rowStyles[enc.lineStyle.Top] } // write header @@ -361,7 +367,7 @@ func (enc *TableEncoder) header() { if !enc.inline { // draw mid divider - enc.divider(enc.rowStyle(enc.lineStyle.Mid)) + enc.divider(enc.rowStyles[enc.lineStyle.Mid]) } } @@ -400,14 +406,15 @@ func (enc TableEncoder) rowStyle(r [4]rune) rowStyle { middle = string(r[2]) + spacer } - return rowStyle{ + rs := rowStyle{ left: []byte(left), wrapper: []byte(string(enc.lineStyle.Wrap[1])), middle: []byte(middle), right: []byte(right + string(enc.newline)), - filler: []byte(filler), + filler: bytes.Repeat([]byte(filler), 8)[:len(filler)], hasWrapping: runewidth.RuneWidth(enc.lineStyle.Row[1]) > 0, } + return rs } // scanAndFormat scans and formats values from the result set. @@ -449,7 +456,7 @@ func (enc *TableEncoder) divider(rs rowStyle) { // tableWidth calculates total table width. func (enc *TableEncoder) tableWidth() int { - rs := enc.rowStyle(enc.lineStyle.Mid) + rs := enc.rowStyles[enc.lineStyle.Mid] width := runewidth.StringWidth(string(rs.left)) + runewidth.StringWidth(string(rs.right)) for i, w := range enc.maxWidths { @@ -682,7 +689,7 @@ func (enc *ExpandedEncoder) Encode(w io.Writer) error { } func (enc *ExpandedEncoder) encodeVals(vals [][]*Value) error { - rs := enc.rowStyle(enc.lineStyle.Row) + rs := enc.rowStyles[enc.lineStyle.Row] // print buffered vals for i := 0; i < len(vals); i++ { enc.record(i, vals[i], rs) @@ -701,7 +708,7 @@ func (enc *ExpandedEncoder) encodeVals(vals [][]*Value) error { // draw end border if enc.border >= 2 && enc.scanCount != 0 { - enc.divider(enc.rowStyle(enc.lineStyle.End)) + enc.divider(enc.rowStyles[enc.lineStyle.End]) } return nil } @@ -732,7 +739,7 @@ func (enc *ExpandedEncoder) EncodeAll(w io.Writer) error { } func (enc *ExpandedEncoder) calcWidth(vals [][]*Value) { - rs := enc.rowStyle(enc.lineStyle.Row) + rs := enc.rowStyles[enc.lineStyle.Row] offset := runewidth.StringWidth(string(rs.left)) @@ -770,9 +777,9 @@ func (enc *ExpandedEncoder) record(i int, vals []*Value, rs rowStyle) { headerRS := rs header := enc.recordHeader(i) if enc.border != 0 { - headerRS = enc.rowStyle(enc.lineStyle.Top) + headerRS = enc.rowStyles[enc.lineStyle.Top] if i != 0 { - headerRS = enc.rowStyle(enc.lineStyle.Mid) + headerRS = enc.rowStyles[enc.lineStyle.Mid] } } diff --git a/fmt.go b/fmt.go index b7bc5ec..95e3d77 100644 --- a/fmt.go +++ b/fmt.go @@ -47,6 +47,9 @@ type EscapeFormatter struct { // If nil, the standard encoding/json.Encoder will be used instead. marshaler func(interface{}) ([]byte, error) + defaultMarshaler *json.Encoder + defaultMarshalerBuffer *bytes.Buffer + // prefix is indent prefix used by the JSON encoder when Marshaler is nil. prefix string @@ -79,11 +82,15 @@ type EscapeFormatter struct { // values. func NewEscapeFormatter(opts ...EscapeFormatterOption) *EscapeFormatter { f := &EscapeFormatter{ - mask: "%d", - timeFormat: time.RFC3339Nano, - indent: " ", - valuesPool: newValuesPool(), + mask: "%d", + timeFormat: time.RFC3339Nano, + indent: " ", + valuesPool: newValuesPool(), + defaultMarshalerBuffer: new(bytes.Buffer), } + f.defaultMarshaler = json.NewEncoder(f.defaultMarshalerBuffer) + f.defaultMarshaler.SetIndent(f.prefix, f.indent) + f.defaultMarshaler.SetEscapeHTML(f.escapeHTML) f.Configure(opts...) return f } @@ -288,17 +295,15 @@ func (f *EscapeFormatter) Format(vals []interface{}) ([]*Value, error) { res[i] = f.valuesPool.newRaw(buf) } else { // json encode - buf := new(bytes.Buffer) - enc := json.NewEncoder(buf) - enc.SetIndent(f.prefix, f.indent) - enc.SetEscapeHTML(f.escapeHTML) - if err := enc.Encode(v); err != nil { + // TODO reuse buffer and reset it before every use + f.defaultMarshalerBuffer.Reset() + if err := f.defaultMarshaler.Encode(v); err != nil { return nil, err } if f.escapeJSON { - res[i] = f.valuesPool.newRaw(bytes.TrimSpace(buf.Bytes())) + res[i] = f.valuesPool.newRaw(bytes.TrimSpace(f.defaultMarshalerBuffer.Bytes())) } else { - res[i] = f.valuesPool.formatBytes(bytes.TrimSpace(buf.Bytes()), f.invalid, f.invalidWidth, false) + res[i] = f.valuesPool.formatBytes(bytes.TrimSpace(f.defaultMarshalerBuffer.Bytes()), f.invalid, f.invalidWidth, false) res[i].Raw = true } } diff --git a/go.mod b/go.mod index ffe21c8..4ad9c6b 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/xo/tblfmt require ( - github.com/mattn/go-runewidth v0.0.9 + github.com/mattn/go-runewidth v0.0.10 github.com/nathan-fiscaletti/consolesize-go v0.0.0-20210105204122-a87d9f614b9d ) diff --git a/go.sum b/go.sum index 7905545..168c338 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= +github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/nathan-fiscaletti/consolesize-go v0.0.0-20210105204122-a87d9f614b9d h1:PQW4Aqovdqc9efHl9EVA+bhKmuZ4ME1HvSYYDvaDiK0= github.com/nathan-fiscaletti/consolesize-go v0.0.0-20210105204122-a87d9f614b9d/go.mod h1:cxIIfNMTwff8f/ZvRouvWYF6wOoO7nj99neWSx2q/Es= +github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= diff --git a/util.go b/util.go index b3649ad..6f47c57 100644 --- a/util.go +++ b/util.go @@ -114,10 +114,10 @@ func repeat(w io.Writer, b []byte, c int) []byte { w.Write(b[:c]) return b } - nb := b[:cap(b)] - for len(nb) < c { - nb = append(nb, nb...) + b = b[:cap(b)] + for len(b) < c { + b = append(b, b...) } - w.Write(nb[:c]) - return nb[:len(b)] + w.Write(b[:c]) + return b[:1] }