2023-04-19 08:28:09 +03:00
|
|
|
package cli
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
|
|
|
|
"github.com/fatih/color"
|
|
|
|
"github.com/mattn/go-colorable"
|
|
|
|
"github.com/neilotoole/sq/cli/config"
|
|
|
|
"github.com/neilotoole/sq/cli/flag"
|
|
|
|
"github.com/neilotoole/sq/cli/output"
|
|
|
|
"github.com/neilotoole/sq/cli/output/csvw"
|
|
|
|
"github.com/neilotoole/sq/cli/output/htmlw"
|
|
|
|
"github.com/neilotoole/sq/cli/output/jsonw"
|
|
|
|
"github.com/neilotoole/sq/cli/output/markdownw"
|
|
|
|
"github.com/neilotoole/sq/cli/output/raww"
|
|
|
|
"github.com/neilotoole/sq/cli/output/tablew"
|
|
|
|
"github.com/neilotoole/sq/cli/output/xlsxw"
|
|
|
|
"github.com/neilotoole/sq/cli/output/xmlw"
|
|
|
|
"github.com/neilotoole/sq/cli/output/yamlw"
|
|
|
|
"github.com/neilotoole/sq/libsq/core/lg"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
)
|
|
|
|
|
|
|
|
// writers is a container for the various output writers.
|
|
|
|
type writers struct {
|
2023-04-22 06:36:32 +03:00
|
|
|
pr *output.Printing
|
2023-04-19 08:28:09 +03:00
|
|
|
|
|
|
|
recordw output.RecordWriter
|
|
|
|
metaw output.MetadataWriter
|
|
|
|
srcw output.SourceWriter
|
|
|
|
errw output.ErrorWriter
|
|
|
|
pingw output.PingWriter
|
|
|
|
versionw output.VersionWriter
|
|
|
|
configw output.ConfigWriter
|
|
|
|
}
|
|
|
|
|
|
|
|
// newWriters returns a writers instance configured per defaults and/or
|
|
|
|
// flags from cmd. The returned out2/errOut2 values may differ
|
|
|
|
// from the out/errOut args (e.g. decorated to support colorization).
|
2023-04-22 06:36:32 +03:00
|
|
|
func newWriters(cmd *cobra.Command, opts config.Options, out, errOut io.Writer,
|
2023-04-19 08:28:09 +03:00
|
|
|
) (w *writers, out2, errOut2 io.Writer) {
|
2023-04-22 06:36:32 +03:00
|
|
|
var pr *output.Printing
|
|
|
|
pr, out2, errOut2 = getPrinting(cmd, &opts, out, errOut)
|
2023-04-19 08:28:09 +03:00
|
|
|
log := lg.FromContext(cmd.Context())
|
|
|
|
|
|
|
|
// Package tablew has writer impls for each of the writer interfaces,
|
|
|
|
// so we use its writers as the baseline. Later we check the format
|
|
|
|
// flags and set the various writer fields depending upon which
|
|
|
|
// writers the format implements.
|
|
|
|
w = &writers{
|
2023-04-22 06:36:32 +03:00
|
|
|
pr: pr,
|
|
|
|
recordw: tablew.NewRecordWriter(out2, pr),
|
|
|
|
metaw: tablew.NewMetadataWriter(out2, pr),
|
|
|
|
srcw: tablew.NewSourceWriter(out2, pr),
|
|
|
|
pingw: tablew.NewPingWriter(out2, pr),
|
|
|
|
errw: tablew.NewErrorWriter(errOut2, pr),
|
|
|
|
versionw: tablew.NewVersionWriter(out2, pr),
|
|
|
|
configw: tablew.NewConfigWriter(out2, pr),
|
2023-04-19 08:28:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Invoke getFormat to see if the format was specified
|
|
|
|
// via config or flag.
|
|
|
|
format := getFormat(cmd, opts)
|
|
|
|
|
2023-04-22 06:36:32 +03:00
|
|
|
//nolint:exhaustive
|
|
|
|
switch format {
|
2023-04-19 08:28:09 +03:00
|
|
|
default:
|
|
|
|
// No format specified, use JSON
|
2023-04-22 06:36:32 +03:00
|
|
|
w.recordw = jsonw.NewStdRecordWriter(out2, pr)
|
|
|
|
w.metaw = jsonw.NewMetadataWriter(out2, pr)
|
|
|
|
w.srcw = jsonw.NewSourceWriter(out2, pr)
|
|
|
|
w.errw = jsonw.NewErrorWriter(log, errOut2, pr)
|
|
|
|
w.versionw = jsonw.NewVersionWriter(out2, pr)
|
|
|
|
w.pingw = jsonw.NewPingWriter(out2, pr)
|
|
|
|
w.configw = jsonw.NewConfigWriter(out2, pr)
|
2023-04-19 08:28:09 +03:00
|
|
|
|
|
|
|
case config.FormatTable:
|
|
|
|
// Table is the base format, already set above, no need to do anything.
|
|
|
|
|
|
|
|
case config.FormatTSV:
|
2023-04-22 06:36:32 +03:00
|
|
|
w.recordw = csvw.NewRecordWriter(out2, pr.ShowHeader, csvw.Tab)
|
2023-04-19 08:28:09 +03:00
|
|
|
w.pingw = csvw.NewPingWriter(out2, csvw.Tab)
|
|
|
|
|
|
|
|
case config.FormatCSV:
|
2023-04-22 06:36:32 +03:00
|
|
|
w.recordw = csvw.NewRecordWriter(out2, pr.ShowHeader, csvw.Comma)
|
2023-04-19 08:28:09 +03:00
|
|
|
w.pingw = csvw.NewPingWriter(out2, csvw.Comma)
|
|
|
|
|
|
|
|
case config.FormatXML:
|
2023-04-22 06:36:32 +03:00
|
|
|
w.recordw = xmlw.NewRecordWriter(out2, pr)
|
2023-04-19 08:28:09 +03:00
|
|
|
|
|
|
|
case config.FormatXLSX:
|
2023-04-22 06:36:32 +03:00
|
|
|
w.recordw = xlsxw.NewRecordWriter(out2, pr.ShowHeader)
|
2023-04-19 08:28:09 +03:00
|
|
|
|
|
|
|
case config.FormatRaw:
|
|
|
|
w.recordw = raww.NewRecordWriter(out2)
|
|
|
|
|
|
|
|
case config.FormatHTML:
|
|
|
|
w.recordw = htmlw.NewRecordWriter(out2)
|
|
|
|
|
|
|
|
case config.FormatMarkdown:
|
|
|
|
w.recordw = markdownw.NewRecordWriter(out2)
|
|
|
|
|
|
|
|
case config.FormatJSONA:
|
2023-04-22 06:36:32 +03:00
|
|
|
w.recordw = jsonw.NewArrayRecordWriter(out2, pr)
|
2023-04-19 08:28:09 +03:00
|
|
|
|
|
|
|
case config.FormatJSONL:
|
2023-04-22 06:36:32 +03:00
|
|
|
w.recordw = jsonw.NewObjectRecordWriter(out2, pr)
|
2023-04-19 08:28:09 +03:00
|
|
|
|
|
|
|
case config.FormatYAML:
|
2023-04-22 06:36:32 +03:00
|
|
|
w.configw = yamlw.NewConfigWriter(out2, pr)
|
|
|
|
w.metaw = yamlw.NewMetadataWriter(out2, pr)
|
2023-04-19 08:28:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return w, out2, errOut2
|
|
|
|
}
|
|
|
|
|
2023-04-22 06:36:32 +03:00
|
|
|
// getPrinting returns a Printing instance and
|
2023-04-19 08:28:09 +03:00
|
|
|
// colorable or non-colorable writers. It is permissible
|
|
|
|
// for the cmd arg to be nil.
|
2023-04-22 06:36:32 +03:00
|
|
|
func getPrinting(cmd *cobra.Command, opts *config.Options,
|
2023-04-19 08:28:09 +03:00
|
|
|
out, errOut io.Writer,
|
2023-04-22 06:36:32 +03:00
|
|
|
) (pr *output.Printing, out2, errOut2 io.Writer) {
|
|
|
|
pr = output.NewPrinting()
|
2023-04-19 08:28:09 +03:00
|
|
|
|
|
|
|
if cmdFlagChanged(cmd, flag.Pretty) {
|
2023-04-22 06:36:32 +03:00
|
|
|
pr.Pretty, _ = cmd.Flags().GetBool(flag.Pretty)
|
2023-04-19 08:28:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if cmdFlagChanged(cmd, flag.Verbose) {
|
2023-04-22 06:36:32 +03:00
|
|
|
pr.Verbose, _ = cmd.Flags().GetBool(flag.Verbose)
|
2023-04-19 08:28:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if cmdFlagChanged(cmd, flag.Header) {
|
2023-04-22 06:36:32 +03:00
|
|
|
pr.ShowHeader, _ = cmd.Flags().GetBool(flag.Header)
|
2023-04-19 08:28:09 +03:00
|
|
|
} else if opts != nil {
|
2023-04-22 06:36:32 +03:00
|
|
|
pr.ShowHeader = opts.Header
|
2023-04-19 08:28:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Should get this default value from config
|
|
|
|
colorize := true
|
|
|
|
|
|
|
|
if cmdFlagChanged(cmd, flag.Output) {
|
|
|
|
// We're outputting to a file, thus no color.
|
|
|
|
colorize = false
|
|
|
|
} else if cmdFlagChanged(cmd, flag.Monochrome) {
|
|
|
|
if mono, _ := cmd.Flags().GetBool(flag.Monochrome); mono {
|
|
|
|
colorize = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !colorize {
|
|
|
|
color.NoColor = true // TODO: shouldn't rely on package-level var
|
2023-04-22 06:36:32 +03:00
|
|
|
pr.EnableColor(false)
|
2023-04-19 08:28:09 +03:00
|
|
|
out2 = out
|
|
|
|
errOut2 = errOut
|
2023-04-22 06:36:32 +03:00
|
|
|
return pr, out2, errOut2
|
2023-04-19 08:28:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// We do want to colorize
|
|
|
|
if !isColorTerminal(out) {
|
|
|
|
// But out can't be colorized.
|
|
|
|
color.NoColor = true
|
2023-04-22 06:36:32 +03:00
|
|
|
pr.EnableColor(false)
|
2023-04-19 08:28:09 +03:00
|
|
|
out2, errOut2 = out, errOut
|
2023-04-22 06:36:32 +03:00
|
|
|
return pr, out2, errOut2
|
2023-04-19 08:28:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// out can be colorized.
|
|
|
|
color.NoColor = false
|
2023-04-22 06:36:32 +03:00
|
|
|
pr.EnableColor(true)
|
2023-04-19 08:28:09 +03:00
|
|
|
out2 = colorable.NewColorable(out.(*os.File))
|
|
|
|
|
|
|
|
// Check if we can colorize errOut
|
|
|
|
if isColorTerminal(errOut) {
|
|
|
|
errOut2 = colorable.NewColorable(errOut.(*os.File))
|
|
|
|
} else {
|
|
|
|
// errOut2 can't be colorized, but since we're colorizing
|
|
|
|
// out, we'll apply the non-colorable filter to errOut.
|
|
|
|
errOut2 = colorable.NewNonColorable(errOut)
|
|
|
|
}
|
|
|
|
|
2023-04-22 06:36:32 +03:00
|
|
|
return pr, out2, errOut2
|
2023-04-19 08:28:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func getFormat(cmd *cobra.Command, defaults config.Options) config.Format {
|
|
|
|
var format config.Format
|
|
|
|
|
|
|
|
switch {
|
|
|
|
// cascade through the format flags in low-to-high order of precedence.
|
|
|
|
case cmdFlagChanged(cmd, flag.TSV):
|
|
|
|
format = config.FormatTSV
|
|
|
|
case cmdFlagChanged(cmd, flag.CSV):
|
|
|
|
format = config.FormatCSV
|
|
|
|
case cmdFlagChanged(cmd, flag.XLSX):
|
|
|
|
format = config.FormatXLSX
|
|
|
|
case cmdFlagChanged(cmd, flag.XML):
|
|
|
|
format = config.FormatXML
|
|
|
|
case cmdFlagChanged(cmd, flag.Raw):
|
|
|
|
format = config.FormatRaw
|
|
|
|
case cmdFlagChanged(cmd, flag.HTML):
|
|
|
|
format = config.FormatHTML
|
|
|
|
case cmdFlagChanged(cmd, flag.Markdown):
|
|
|
|
format = config.FormatMarkdown
|
|
|
|
case cmdFlagChanged(cmd, flag.Table):
|
|
|
|
format = config.FormatTable
|
|
|
|
case cmdFlagChanged(cmd, flag.JSONL):
|
|
|
|
format = config.FormatJSONL
|
|
|
|
case cmdFlagChanged(cmd, flag.JSONA):
|
|
|
|
format = config.FormatJSONA
|
|
|
|
case cmdFlagChanged(cmd, flag.JSON):
|
|
|
|
format = config.FormatJSON
|
|
|
|
case cmdFlagChanged(cmd, flag.YAML):
|
|
|
|
format = config.FormatYAML
|
|
|
|
default:
|
|
|
|
// no format flag, use the config value
|
|
|
|
format = defaults.Format
|
|
|
|
}
|
|
|
|
return format
|
|
|
|
}
|