sq/cli/output/markdownw/markdownw.go

143 lines
3.0 KiB
Go
Raw Normal View History

2020-08-06 20:58:47 +03:00
// Package markdownw implements writers for Markdown.
package markdownw
import (
"bytes"
"encoding/base64"
"fmt"
"html"
"io"
"strconv"
"strings"
2023-05-11 05:03:45 +03:00
"sync"
2020-08-06 20:58:47 +03:00
"time"
"github.com/neilotoole/sq/libsq/core/record"
"github.com/neilotoole/sq/cli/output"
"github.com/neilotoole/sq/libsq/core/kind"
"github.com/neilotoole/sq/libsq/core/stringz"
2020-08-06 20:58:47 +03:00
)
// RecordWriter implements output.RecordWriter.
type RecordWriter struct {
2023-05-11 05:03:45 +03:00
mu sync.Mutex
recMeta record.Meta
pr *output.Printing
2020-08-06 20:58:47 +03:00
out io.Writer
buf *bytes.Buffer
}
var _ output.NewRecordWriterFunc = NewRecordWriter
2020-08-06 20:58:47 +03:00
// NewRecordWriter returns a writer instance.
func NewRecordWriter(out io.Writer, pr *output.Printing) output.RecordWriter {
return &RecordWriter{out: out, pr: pr}
2020-08-06 20:58:47 +03:00
}
// Open implements output.RecordWriter.
func (w *RecordWriter) Open(recMeta record.Meta) error {
2020-08-06 20:58:47 +03:00
w.recMeta = recMeta
w.buf = &bytes.Buffer{}
// Write the header
for i, field := range recMeta {
w.buf.WriteString("| ")
w.buf.WriteString(field.Name() + " ")
if i == len(recMeta)-1 {
w.buf.WriteString("|\n")
}
}
// Write the header separator row
for i := range recMeta {
w.buf.WriteString("| --- ")
if i == len(recMeta)-1 {
w.buf.WriteString("|\n")
}
}
return nil
}
// Flush implements output.RecordWriter.
func (w *RecordWriter) Flush() error {
_, err := w.buf.WriteTo(w.out) // resets buf
return err
}
// Close implements output.RecordWriter.
func (w *RecordWriter) Close() error {
return w.Flush()
}
func (w *RecordWriter) writeRecord(rec record.Record) error {
2020-08-06 20:58:47 +03:00
var s string
for i, field := range rec {
w.buf.WriteString("| ")
switch val := field.(type) {
default:
// should never happen
s = escapeMarkdown(fmt.Sprintf("%v", val))
case nil:
// nil is rendered as empty string, which this cell already is
case *int64:
s = strconv.FormatInt(*val, 10)
case *string:
s = escapeMarkdown(*val)
case *bool:
s = strconv.FormatBool(*val)
case *float64:
s = stringz.FormatFloat(*val)
case *[]byte:
s = base64.StdEncoding.EncodeToString(*val)
case *time.Time:
switch w.recMeta[i].Kind() { //nolint:exhaustive
2020-08-06 20:58:47 +03:00
default:
s = w.pr.FormatDatetime(*val)
case kind.Time:
s = w.pr.FormatTime(*val)
case kind.Date:
s = w.pr.FormatDate(*val)
2020-08-06 20:58:47 +03:00
}
}
w.buf.WriteString(s + " ")
}
w.buf.WriteString("|\n")
return nil
}
// WriteRecords implements output.RecordWriter.
func (w *RecordWriter) WriteRecords(recs []record.Record) error {
2023-05-11 05:03:45 +03:00
w.mu.Lock()
defer w.mu.Unlock()
2020-08-06 20:58:47 +03:00
var err error
for _, rec := range recs {
err = w.writeRecord(rec)
if err != nil {
return err
}
}
return nil
}
// escapeMarkdown is quick effort at escaping markdown
// table cell text. It is not at all tested. Replace this
// function with a real library call at the earliest opportunity.
func escapeMarkdown(s string) string {
s = html.EscapeString(s)
s = strings.ReplaceAll(s, "|", "|")
s = strings.ReplaceAll(s, "\r\n", "<br/>")
s = strings.ReplaceAll(s, "\n", "<br/>")
2020-08-06 20:58:47 +03:00
return s
}