Skip to content

Commit

Permalink
Implement template encoder
Browse files Browse the repository at this point in the history
  • Loading branch information
nineinchnick committed Mar 13, 2021
1 parent 2729f07 commit c897852
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 18 deletions.
94 changes: 85 additions & 9 deletions encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -619,13 +619,13 @@ func (enc *ExpandedEncoder) calcWidth(vals [][]*Value) {
offset++
}

mw := runewidth.StringWidth(string(rs.middle))
mw := runewidth.StringWidth(string(rs.middle))
offset += mw

enc.offsets[1] = offset

// second column is any value from any row but no less than the record header
enc.maxWidths[1] = max(0, len(enc.recordHeader(len(vals)-1)) - enc.maxWidths[0] - mw - 1)
enc.maxWidths[1] = max(0, len(enc.recordHeader(len(vals)-1))-enc.maxWidths[0]-mw-1)
for _, row := range vals {
for _, cell := range row {
if cell == nil {
Expand Down Expand Up @@ -668,7 +668,7 @@ func (enc *ExpandedEncoder) recordHeader(i int) string {
if enc.border != 0 {
header = fmt.Sprintf("[ RECORD %d ]", i+1)
}
return header
return header
}

// JSONEncoder is an unbuffered JSON encoder for result sets.
Expand Down Expand Up @@ -1021,6 +1021,13 @@ type TemplateEncoder struct {

// empty is the empty value.
empty *Value

// template is the parsed template
template Executor
}

type Executor interface {
Execute(io.Writer, interface{}) error
}

// NewTemplateEncoder creates a new template encoder using the provided options.
Expand Down Expand Up @@ -1048,7 +1055,64 @@ func (enc *TemplateEncoder) Encode(w io.Writer) error {
if enc.resultSet == nil {
return ErrResultSetIsNil
}
return nil

// get and check columns
cols, err := enc.resultSet.Columns()
if err != nil {
return err
}
clen := len(cols)
if clen == 0 {
return ErrResultSetHasNoColumns
}

headers, err := enc.formatter.Header(cols)
if err != nil {
return err
}

stop := make(chan struct{})
data := struct {
Rows <-chan []cell
Headers []*Value
}{
Rows: enc.rows(headers, stop),
Headers: headers,
}
err = enc.template.Execute(w, data)
close(stop)
return err
}

type cell struct {
Name string
Value string
}

func (enc *TemplateEncoder) rows(headers []*Value, stop <-chan struct{}) chan []cell {
// set up storage for results
r := make([]interface{}, len(headers))
for i := range headers {
r[i] = new(interface{})
}
row := make([]cell, len(headers))
result := make(chan []cell)
go func() {
defer close(result)
for enc.resultSet.Next() {
err := enc.scanAndFormat(headers, r, row)
if err != nil {
return
}
select {
case result <- row:
// sent successfully
case <-stop:
return
}
}
}()
return result
}

// EncodeAll encodes all result sets to the writer using the encoder settings.
Expand Down Expand Up @@ -1076,13 +1140,25 @@ func (enc *TemplateEncoder) EncodeAll(w io.Writer) error {
}

// scanAndFormat scans and formats values from the result set.
func (enc *TemplateEncoder) scanAndFormat(vals []interface{}) ([]*Value, error) {
// vals and result are passed as args to avoid allocation
func (enc *TemplateEncoder) scanAndFormat(headers []*Value, buf []interface{}, row []cell) error {
var err error
if err = enc.resultSet.Err(); err != nil {
return nil, err
return err
}
if err = enc.resultSet.Scan(vals...); err != nil {
return nil, err
if err = enc.resultSet.Scan(buf...); err != nil {
return err
}
return enc.formatter.Format(vals)
vals, err := enc.formatter.Format(buf)
if err != nil {
return err
}
for i, h := range headers {
v := vals[i]
if v == nil {
v = enc.empty
}
row[i] = cell{Name: string(h.Buf), Value: string(v.Buf)}
}
return nil
}
32 changes: 32 additions & 0 deletions encode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,35 @@ func TestJSONEncoder(t *testing.T) {
i++
}
}

func TestTemplateEncoder(t *testing.T) {
expected := `
Row 0:
author_id = "15"
name = "aoeu
test
"
z = ""
Row 1:
author_id = "15"
name = "aoeu
test
"
z = ""
`
template := `
{{ range $i, $r := .Rows }}Row {{ $i }}:
{{ range . }} {{ .Name }} = "{{ .Value }}"
{{ end }}
{{ end }}`
buf := new(bytes.Buffer)
if err := EncodeTemplateAll(buf, rs(), WithTextTemplate(template)); err != nil {
t.Fatalf("expected no error when Template encoding, got: %v", err)
}
actual := buf.String()
if actual != expected {
t.Fatalf("expected encoder to return:\n-- expected --\n%v\n-- end --\n\nbut got:\n-- encoded --\n%s\n-- end --", expected, actual)
}
}
32 changes: 23 additions & 9 deletions opts.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package tblfmt

import (
html "html/template"
"io"
"strconv"
txt "text/template"
"unicode/utf8"
)

Expand All @@ -28,9 +30,11 @@ func FromMap(opts map[string]string) (Builder, []Option) {
}
return NewCSVEncoder, csvOpts

case "html", "asciidoc", "latex", "latex-longtable", "troff-ms":
//return newErrEncoder, []Option{withError(fmt.Errorf("%q format not implemented", opts["format"]))}
return NewTemplateEncoder, []Option{WithNamedTemplate(opts["format"])}
case "html":
return NewTemplateEncoder, []Option{WithHtmlTemplate(opts["format"])}

case "asciidoc", "latex", "latex-longtable", "troff-ms":
return NewTemplateEncoder, []Option{WithTextTemplate(opts["format"])}

case "unaligned":
fallthrough
Expand Down Expand Up @@ -235,21 +239,31 @@ func WithBorder(border int) Option {
}
}

// WithTemplate is a encoder option to set the raw template used.
func WithTemplate(template string) Option {
// WithTextTemplate is a encoder option to set the raw text template used.
func WithTextTemplate(t string) Option {
return func(v interface{}) error {
switch v.(type) {
switch enc := v.(type) {
case *TemplateEncoder:
var err error
enc.template, err = txt.New("main").Parse(t)
if err != nil {
return err
}
}
return nil
}
}

// WithNamedTemplate is a encoder option to set the template used.
func WithNamedTemplate(name string) Option {
// WithHtmlTemplate is a encoder option to set the raw html template used.
func WithHtmlTemplate(t string) Option {
return func(v interface{}) error {
switch v.(type) {
switch enc := v.(type) {
case *TemplateEncoder:
var err error
enc.template, err = html.New("main").Parse(t)
if err != nil {
return err
}
}
return nil
}
Expand Down

0 comments on commit c897852

Please sign in to comment.