// Package htmlw implements a RecordWriter for HTML.
package htmlw
import (
"bytes"
"context"
"encoding/base64"
"fmt"
"html"
"io"
"strconv"
"sync"
"time"
"github.com/shopspring/decimal"
"github.com/neilotoole/sq/cli/output"
"github.com/neilotoole/sq/libsq/core/errz"
"github.com/neilotoole/sq/libsq/core/kind"
"github.com/neilotoole/sq/libsq/core/record"
"github.com/neilotoole/sq/libsq/core/stringz"
)
// RecordWriter implements output.RecordWriter.
type recordWriter struct {
out io.Writer
pr *output.Printing
buf *bytes.Buffer
recMeta record.Meta
mu sync.Mutex
}
var _ output.NewRecordWriterFunc = NewRecordWriter
// NewRecordWriter an output.RecordWriter for HTML.
func NewRecordWriter(out io.Writer, pr *output.Printing) output.RecordWriter {
return &recordWriter{out: out, pr: pr}
}
// Open implements output.RecordWriter.
func (w *recordWriter) Open(_ context.Context, recMeta record.Meta) error {
w.recMeta = recMeta
w.buf = &bytes.Buffer{}
const header = `
sq output
`
w.buf.WriteString(header)
for _, field := range recMeta {
w.buf.WriteString(" \n")
}
w.buf.WriteString(" \n \n \n")
for _, field := range recMeta {
w.buf.WriteString(" ")
w.buf.WriteString(field.MungedName())
w.buf.WriteString(" | \n")
}
w.buf.WriteString("
\n \n \n")
return nil
}
// Flush implements output.RecordWriter.
func (w *recordWriter) Flush(context.Context) error {
w.mu.Lock()
defer w.mu.Unlock()
_, err := w.buf.WriteTo(w.out) // resets buf
return errz.Err(err)
}
// Close implements output.RecordWriter.
func (w *recordWriter) Close(ctx context.Context) error {
err := w.Flush(ctx)
if err != nil {
return err
}
const footer = `
`
_, err = w.out.Write([]byte(footer))
return errz.Err(err)
}
func (w *recordWriter) writeRecord(rec record.Record) error {
w.buf.WriteString(" \n")
var s string
for i, field := range rec {
w.buf.WriteString(" ")
switch val := field.(type) {
default:
s = html.EscapeString(fmt.Sprintf("%v", val))
// should never happen
case nil:
// nil is rendered as empty string, which this cell already is
case int64:
s = strconv.FormatInt(val, 10)
case string:
s = html.EscapeString(val)
case bool:
s = strconv.FormatBool(val)
case float64:
s = stringz.FormatFloat(val)
case decimal.Decimal:
s = stringz.FormatDecimal(val)
case []byte:
s = base64.StdEncoding.EncodeToString(val)
case time.Time:
switch w.recMeta[i].Kind() { //nolint:exhaustive
default:
s = w.pr.FormatDatetime(val)
case kind.Time:
s = w.pr.FormatTime(val)
case kind.Date:
s = w.pr.FormatDate(val)
}
}
w.buf.WriteString(s)
w.buf.WriteString(" | \n")
}
w.buf.WriteString("
\n")
return nil
}
// WriteRecords implements output.RecordWriter.
func (w *recordWriter) WriteRecords(ctx context.Context, recs []record.Record) error {
w.mu.Lock()
defer w.mu.Unlock()
var err error
for _, rec := range recs {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
err = w.writeRecord(rec)
if err != nil {
return err
}
}
return nil
}