// 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(" \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 = `
") w.buf.WriteString(field.MungedName()) w.buf.WriteString("
` _, 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 }