2020-08-06 20:58:47 +03:00
|
|
|
package tablew
|
|
|
|
|
|
|
|
import (
|
2024-01-15 04:45:34 +03:00
|
|
|
"context"
|
2020-08-06 20:58:47 +03:00
|
|
|
"io"
|
2023-05-11 05:03:45 +03:00
|
|
|
"sync"
|
2020-08-06 20:58:47 +03:00
|
|
|
|
|
|
|
"github.com/neilotoole/sq/cli/output"
|
2024-01-15 04:45:34 +03:00
|
|
|
"github.com/neilotoole/sq/libsq/core/lg"
|
|
|
|
"github.com/neilotoole/sq/libsq/core/progress"
|
2023-11-20 04:06:36 +03:00
|
|
|
"github.com/neilotoole/sq/libsq/core/record"
|
2020-08-06 20:58:47 +03:00
|
|
|
)
|
|
|
|
|
2020-08-09 16:46:46 +03:00
|
|
|
type recordWriter struct {
|
2023-05-11 05:03:45 +03:00
|
|
|
mu sync.Mutex
|
2020-08-06 20:58:47 +03:00
|
|
|
tbl *table
|
2023-05-22 18:08:14 +03:00
|
|
|
recMeta record.Meta
|
2020-08-06 20:58:47 +03:00
|
|
|
rowCount int
|
2024-01-15 04:45:34 +03:00
|
|
|
bar *progress.Bar
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
|
2020-08-09 16:46:46 +03:00
|
|
|
// NewRecordWriter returns a RecordWriter for text table output.
|
2023-04-22 06:36:32 +03:00
|
|
|
func NewRecordWriter(out io.Writer, pr *output.Printing) output.RecordWriter {
|
|
|
|
tbl := &table{out: out, pr: pr, header: pr.ShowHeader}
|
2020-08-09 16:46:46 +03:00
|
|
|
w := &recordWriter{tbl: tbl}
|
2020-08-06 20:58:47 +03:00
|
|
|
w.tbl.reset()
|
|
|
|
return w
|
|
|
|
}
|
|
|
|
|
2020-08-09 16:46:46 +03:00
|
|
|
// Open implements output.RecordWriter.
|
2024-01-15 04:45:34 +03:00
|
|
|
func (w *recordWriter) Open(ctx context.Context, recMeta record.Meta) error {
|
|
|
|
w.mu.Lock()
|
|
|
|
defer w.mu.Unlock()
|
|
|
|
|
2020-08-06 20:58:47 +03:00
|
|
|
w.recMeta = recMeta
|
2024-01-15 04:45:34 +03:00
|
|
|
|
|
|
|
// We show a progress bar, because this writer batches all records and writes
|
|
|
|
// them together at the end. A better-behaved writer would stream records
|
|
|
|
// as they arrive (or at least batch them in smaller chunks). This will
|
|
|
|
// probably be fixed at some point, but there's a bit of a catch. The table
|
|
|
|
// determines the width of each column based on the widest value seen for that
|
|
|
|
// column. So, if we stream records as they arrive, we can't know the maximum
|
|
|
|
// width of each column until all records have been received. Thus,
|
|
|
|
// periodically flushing the output may result in inconsistent bar widths for
|
|
|
|
// subsequent batches. This is probably something that we'll have to live
|
|
|
|
// with. After all, this writer is intended for human/interactive use, and
|
|
|
|
// if the number of records is huge (triggering batching), then the user
|
|
|
|
// really should be using a machine-readable output format instead.
|
|
|
|
w.bar = progress.FromContext(ctx).NewUnitCounter("Preparing output", "rec", progress.OptMemUsage)
|
|
|
|
|
2020-08-06 20:58:47 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-01-15 04:45:34 +03:00
|
|
|
// Flush implements output.RecordWriter. It's a no-op for this writer.
|
|
|
|
func (w *recordWriter) Flush(context.Context) error {
|
|
|
|
w.mu.Lock()
|
|
|
|
defer w.mu.Unlock()
|
|
|
|
|
2020-08-06 20:58:47 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-08-09 16:46:46 +03:00
|
|
|
// Close implements output.RecordWriter.
|
2024-01-15 04:45:34 +03:00
|
|
|
func (w *recordWriter) Close(ctx context.Context) error {
|
|
|
|
w.mu.Lock()
|
|
|
|
defer w.mu.Unlock()
|
|
|
|
|
|
|
|
if w.bar != nil {
|
|
|
|
w.bar.Stop()
|
|
|
|
}
|
2020-08-06 20:58:47 +03:00
|
|
|
if w.rowCount == 0 {
|
|
|
|
// no data to write
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
w.tbl.tblImpl.SetAutoWrapText(false)
|
2023-07-09 04:34:53 +03:00
|
|
|
header := w.recMeta.MungedNames()
|
2020-08-06 20:58:47 +03:00
|
|
|
w.tbl.tblImpl.SetHeader(header)
|
|
|
|
|
2024-01-15 04:45:34 +03:00
|
|
|
lg.FromContext(ctx).Debug("RecordWriter (text): writing records to output", "recs", w.rowCount)
|
|
|
|
return w.tbl.writeAll(ctx)
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
|
2020-08-09 16:46:46 +03:00
|
|
|
// WriteRecords implements output.RecordWriter.
|
2024-01-15 04:45:34 +03:00
|
|
|
func (w *recordWriter) WriteRecords(ctx context.Context, 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
|
|
|
kinds := w.recMeta.Kinds()
|
|
|
|
|
|
|
|
var tblRows [][]string
|
|
|
|
for _, rec := range recs {
|
2024-01-15 04:45:34 +03:00
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return ctx.Err()
|
|
|
|
default:
|
|
|
|
}
|
2020-08-06 20:58:47 +03:00
|
|
|
tblRow := make([]string, len(rec))
|
|
|
|
|
|
|
|
for i, val := range rec {
|
|
|
|
tblRow[i] = w.tbl.renderResultCell(kinds[i], val)
|
|
|
|
}
|
|
|
|
|
|
|
|
tblRows = append(tblRows, tblRow)
|
|
|
|
w.rowCount++
|
2024-01-15 04:45:34 +03:00
|
|
|
w.bar.Incr(1)
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
|
2024-01-15 04:45:34 +03:00
|
|
|
return w.tbl.appendRows(ctx, tblRows)
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|