Refactored verbose/header flags; inspect cmd now less verbose by default (#111)

* Refactored verbose/header flags; inspect cmd now less verbose by default

* Updated changelog

* README update
This commit is contained in:
Neil O'Toole 2022-12-16 20:46:37 -07:00 committed by GitHub
parent e425da528f
commit 703f6d2427
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 142 additions and 73 deletions

View File

@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- `--verbose` flag is now global
### Changed
- `sq inspect` shows less output by default (use `-v` to restore previous behavior)
### Fixed
- `sq inspect` can now deal with Postgres sources that have null values for constraint fields
## [v0.16.0] - 2022-11-20
### Added

View File

@ -142,17 +142,16 @@ But we're flying a bit blind here: how did we know about the `actor` table?
`sq inspect` is your friend (output abbreviated):
```sh
$ sq inspect
HANDLE DRIVER NAME FQ NAME SIZE TABLES LOCATION
@sakila_sl3 sqlite3 sakila.db sakila.db/main 5.6MB 21 sqlite3:///root/sakila.db
HANDLE DRIVER NAME FQ NAME SIZE TABLES LOCATION
@sakila_sl3 sqlite3 sakila.db sakila.db/main 5.6MB 21 sqlite3:/Users/neilotoole/work/sq/sq/drivers/sqlite3/testdata/sakila.db
TABLE ROWS TYPE SIZE NUM COLS COL NAMES COL TYPES
actor 200 table - 4 actor_id, first_name, last_name, last_update numeric, VARCHAR(45), VARCHAR(45), TIMESTAMP
address 603 table - 8 address_id, address, address2, district, city_id, postal_code, phone, last_update int, VARCHAR(50), VARCHAR(50), VARCHAR(20), INT, VARCHAR(10), VARCHAR(20), TIMESTAMP
category 16 table - 3 category_id, name, last_update
TABLE ROWS COL NAMES
actor 200 actor_id, first_name, last_name, last_update
address 603 address_id, address, address2, district, city_id, postal_code, phone, last_update
category 16 category_id, name, last_update
```
Use `--json` (`-j`) to output in JSON (output abbreviated):
Use the `--verbose` (`-v`) flag to see more detail. And use `--json` (`-j`) to output in JSON (output abbreviated):
```shell
$ sq inspect -j
@ -206,7 +205,7 @@ category.csv customer.csv film_actor.csv film_text.csv payment.csv sale
Note that you can also inspect an individual table:
```sh
$ sq inspect @sakila_sl3.actor
$ sq inspect -v @sakila_sl3.actor
TABLE ROWS TYPE SIZE NUM COLS COL NAMES COL TYPES
actor 200 table - 4 actor_id, first_name, last_name, last_update numeric, VARCHAR(45), VARCHAR(45), TIMESTAMP
@ -242,7 +241,7 @@ Now, execute the same query, but this time `sq` inserts the results into a new t
$ sq @xl_demo_xlsx.person --insert @sakila_sl3.person
Inserted 7 rows into @sakila_sl3.person
$ sq inspect @sakila_sl3.person
$ sq inspect -v @sakila_sl3.person
TABLE ROWS TYPE SIZE NUM COLS COL NAMES COL TYPES
person 7 table - 4 uid, username, email, address_id INTEGER, TEXT, TEXT, INTEGER

View File

@ -198,6 +198,7 @@ func newCommandTree(rc *RunContext) (rootCmd *cobra.Command) {
// The behavior of cobra in this regard seems to have
// changed? This particular incantation currently does the trick.
rootCmd.Flags().Bool(flagHelp, false, "Show sq help")
rootCmd.Flags().SortFlags = false
helpCmd := addCmd(rc, rootCmd, newHelpCmd())
rootCmd.SetHelpCommand(helpCmd)
@ -552,29 +553,30 @@ func newWriters(log lg.Log, cmd *cobra.Command, defaults config.Defaults, out, e
var fm *output.Formatting
fm, out2, errOut2 = getWriterFormatting(cmd, out, errOut)
// we need to determine --header here because the writer/format
// constructor functions, e.g. table.NewRecordWriter, require it.
printHeader := defaults.Header
if cmdFlagChanged(cmd, flagHeader) {
printHeader, _ = cmd.Flags().GetBool(flagHeader)
}
// FIXME: delete this section
//// we need to determine --header here because the writer/format
//// constructor functions, e.g. table.NewRecordWriter, require it.
//printHeader := defaults.Header
//if cmdFlagChanged(cmd, flagHeader) {
// printHeader, _ = cmd.Flags().GetBool(flagHeader)
//}
//
//verbose := false
//if cmdFlagChanged(cmd, flagVerbose) {
// verbose, _ = cmd.Flags().GetBool(flagVerbose)
//}
verbose := false
if cmdFlagChanged(cmd, flagVerbose) {
verbose, _ = cmd.Flags().GetBool(flagVerbose)
}
// Package tablew has writer impls for all of the writer interfaces,
// 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{
fmt: fm,
recordw: tablew.NewRecordWriter(out2, fm, printHeader),
recordw: tablew.NewRecordWriter(out2, fm),
metaw: tablew.NewMetadataWriter(out2, fm),
srcw: tablew.NewSourceWriter(out2, fm, printHeader, verbose),
srcw: tablew.NewSourceWriter(out2, fm),
pingw: tablew.NewPingWriter(out2, fm),
notifyw: tablew.NewNotifyWriter(out2, fm, printHeader),
notifyw: tablew.NewNotifyWriter(out2, fm),
errw: tablew.NewErrorWriter(errOut2, fm),
}
@ -593,18 +595,18 @@ func newWriters(log lg.Log, cmd *cobra.Command, defaults config.Defaults, out, e
// Table is the base format, already set above, no need to do anything.
case config.FormatTSV:
w.recordw = csvw.NewRecordWriter(out2, printHeader, csvw.Tab)
w.recordw = csvw.NewRecordWriter(out2, fm.ShowHeader, csvw.Tab)
w.pingw = csvw.NewPingWriter(out2, csvw.Tab)
case config.FormatCSV:
w.recordw = csvw.NewRecordWriter(out2, printHeader, csvw.Comma)
w.recordw = csvw.NewRecordWriter(out2, fm.ShowHeader, csvw.Comma)
w.pingw = csvw.NewPingWriter(out2, csvw.Comma)
case config.FormatXML:
w.recordw = xmlw.NewRecordWriter(out2, fm)
case config.FormatXLSX:
w.recordw = xlsxw.NewRecordWriter(out2, printHeader)
w.recordw = xlsxw.NewRecordWriter(out2, fm.ShowHeader)
case config.FormatRaw:
w.recordw = raww.NewRecordWriter(out2)
@ -635,6 +637,14 @@ func getWriterFormatting(cmd *cobra.Command, out, errOut io.Writer) (fm *output.
fm.Pretty, _ = cmd.Flags().GetBool(flagPretty)
}
if cmdFlagChanged(cmd, flagVerbose) {
fm.Verbose, _ = cmd.Flags().GetBool(flagVerbose)
}
if cmdFlagChanged(cmd, flagHeader) {
fm.ShowHeader, _ = cmd.Flags().GetBool(flagHeader)
}
// TODO: Should get this default value from config
colorize := true

View File

@ -17,7 +17,8 @@ func newInspectCmd() *cobra.Command {
RunE: execInspect,
Short: "Inspect data source schema and stats",
Long: `Inspect a data source, or a particular table in a source,
listing table details, column names and types, row counts, etc.
listing table details such as column names and row counts, etc. Use
the --verbose flag to see more detail.
If @HANDLE is not provided, the active data source is assumed.`,
Example: ` # inspect active data source
$ sq inspect
@ -25,6 +26,9 @@ If @HANDLE is not provided, the active data source is assumed.`,
# inspect @pg1 data source
$ sq inspect @pg1
# inspect @pg1 data source, showing verbose output
$ sq inspect -v @pg1
# inspect 'actor' in @pg1 data source
$ sq inspect @pg1.actor

View File

@ -12,7 +12,6 @@ func newSrcListCmd() *cobra.Command {
RunE: execSrcList,
}
cmd.Flags().BoolP(flagVerbose, flagVerboseShort, false, flagVerboseUsage)
cmd.Flags().BoolP(flagHeader, flagHeaderShort, false, flagHeaderUsage)
return cmd
}

View File

@ -85,6 +85,6 @@ More at https://sq.io
addQueryCmdFlags(cmd)
cmd.Flags().Bool(flagVersion, false, flagVersionUsage)
cmd.PersistentFlags().BoolP(flagMonochrome, flagMonochromeShort, false, flagMonochromeUsage)
cmd.PersistentFlags().BoolP(flagVerbose, flagVerboseShort, false, flagVerboseUsage)
return cmd
}

View File

@ -92,7 +92,7 @@ const (
flagVerbose = "verbose"
flagVerboseShort = "v"
flagVerboseUsage = "Print verbose data, if applicable"
flagVerboseUsage = "Print verbose output, if applicable"
flagVersion = "version"
flagVersionUsage = "Print sq version"

View File

@ -6,6 +6,14 @@ import "github.com/fatih/color"
type Formatting struct {
monochrome bool
// ShowHeader indicates that a header (e.g. a header row) should
// be printed where applicable.
ShowHeader bool
// Verbose indicates that verbose output should be printed where
// applicable.
Verbose bool
// Pretty indicates that output should be pretty-printed.
// Typically this means indentation, new lines, etc, but
// varies by output format.
@ -67,6 +75,8 @@ type Formatting struct {
// are enabled. The default indent is two spaces.
func NewFormatting() *Formatting {
fm := &Formatting{
ShowHeader: true,
Verbose: false,
Pretty: true,
monochrome: false,
Indent: " ",

View File

@ -42,8 +42,7 @@ func (w *mdWriter) DriverMetadata(drvrs []driver.Metadata) error {
// TableMetadata implements output.MetadataWriter.
func (w *mdWriter) TableMetadata(tblMeta *source.TableMetadata) error {
headers := []string{"TABLE", "ROWS", "TYPE", "SIZE", "NUM COLS", "COL NAMES", "COL TYPES"}
var headers []string
var rows [][]string
colNames := make([]string, len(tblMeta.Columns))
@ -59,22 +58,39 @@ func (w *mdWriter) TableMetadata(tblMeta *source.TableMetadata) error {
size = stringz.ByteSized(*tblMeta.Size, 1, "")
}
row := []string{
tblMeta.Name,
fmt.Sprintf("%d", tblMeta.RowCount),
tblMeta.TableType,
size,
fmt.Sprintf("%d", len(tblMeta.Columns)),
strings.Join(colNames, ", "),
strings.Join(colTypes, ", "),
}
rows = append(rows, row)
if w.tbl.fm.Verbose {
headers = []string{"TABLE", "ROWS", "TYPE", "SIZE", "NUM COLS", "COL NAMES", "COL TYPES"}
w.tbl.tblImpl.SetHeader(headers)
w.tbl.tblImpl.SetColTrans(0, w.tbl.fm.Handle.SprintFunc())
w.tbl.tblImpl.SetColTrans(1, w.tbl.fm.Number.SprintFunc())
w.tbl.tblImpl.SetColTrans(3, w.tbl.fm.Number.SprintFunc())
w.tbl.tblImpl.SetColTrans(4, w.tbl.fm.Number.SprintFunc())
w.tbl.tblImpl.SetHeader(headers)
w.tbl.tblImpl.SetColTrans(0, w.tbl.fm.Handle.SprintFunc())
w.tbl.tblImpl.SetColTrans(1, w.tbl.fm.Number.SprintFunc())
w.tbl.tblImpl.SetColTrans(3, w.tbl.fm.Number.SprintFunc())
w.tbl.tblImpl.SetColTrans(4, w.tbl.fm.Number.SprintFunc())
row := []string{
tblMeta.Name,
fmt.Sprintf("%d", tblMeta.RowCount),
tblMeta.TableType,
size,
fmt.Sprintf("%d", len(tblMeta.Columns)),
strings.Join(colNames, ", "),
strings.Join(colTypes, ", "),
}
rows = append(rows, row)
} else {
headers = []string{"TABLE", "ROWS", "COL NAMES"}
w.tbl.tblImpl.SetHeader(headers)
w.tbl.tblImpl.SetColTrans(0, w.tbl.fm.Handle.SprintFunc())
w.tbl.tblImpl.SetColTrans(1, w.tbl.fm.Number.SprintFunc())
row := []string{
tblMeta.Name,
fmt.Sprintf("%d", tblMeta.RowCount),
strings.Join(colNames, ", "),
}
rows = append(rows, row)
}
w.tbl.appendRowsAndRenderAll(rows)
return nil
@ -119,7 +135,21 @@ func (w *mdWriter) SourceMetadata(meta *source.Metadata) error {
w.tbl.reset()
fmt.Fprintln(w.tbl.out)
headers = []string{"TABLE", "ROWS", "TYPE", "SIZE", "NUM COLS", "COL NAMES", "COL TYPES"}
if w.tbl.fm.Verbose {
headers = []string{"TABLE", "ROWS", "TYPE", "SIZE", "NUM COLS", "COL NAMES", "COL TYPES"}
w.tbl.tblImpl.SetHeader(headers)
w.tbl.tblImpl.SetColTrans(0, w.tbl.fm.Handle.SprintFunc())
w.tbl.tblImpl.SetColTrans(1, w.tbl.fm.Number.SprintFunc())
w.tbl.tblImpl.SetColTrans(3, w.tbl.fm.Number.SprintFunc())
w.tbl.tblImpl.SetColTrans(4, w.tbl.fm.Number.SprintFunc())
} else {
headers = []string{"TABLE", "ROWS", "COL NAMES"}
w.tbl.tblImpl.SetHeader(headers)
w.tbl.tblImpl.SetColTrans(0, w.tbl.fm.Handle.SprintFunc())
w.tbl.tblImpl.SetColTrans(1, w.tbl.fm.Number.SprintFunc())
}
var rows [][]string
@ -137,24 +167,27 @@ func (w *mdWriter) SourceMetadata(meta *source.Metadata) error {
size = stringz.ByteSized(*tbl.Size, 1, "")
}
row = []string{
tbl.Name,
fmt.Sprintf("%d", tbl.RowCount),
tbl.TableType,
size,
fmt.Sprintf("%d", len(tbl.Columns)),
strings.Join(colNames, ", "),
strings.Join(colTypes, ", "),
if w.tbl.fm.Verbose {
row = []string{
tbl.Name,
fmt.Sprintf("%d", tbl.RowCount),
tbl.TableType,
size,
fmt.Sprintf("%d", len(tbl.Columns)),
strings.Join(colNames, ", "),
strings.Join(colTypes, ", "),
}
} else {
row = []string{
tbl.Name,
fmt.Sprintf("%d", tbl.RowCount),
strings.Join(colNames, ", "),
}
}
rows = append(rows, row)
}
w.tbl.tblImpl.SetHeader(headers)
w.tbl.tblImpl.SetColTrans(0, w.tbl.fm.Handle.SprintFunc())
w.tbl.tblImpl.SetColTrans(1, w.tbl.fm.Number.SprintFunc())
w.tbl.tblImpl.SetColTrans(3, w.tbl.fm.Number.SprintFunc())
w.tbl.tblImpl.SetColTrans(4, w.tbl.fm.Number.SprintFunc())
w.tbl.appendRowsAndRenderAll(rows)
return nil
}

View File

@ -12,8 +12,8 @@ type notifyWriter struct {
}
// NewNotifyWriter implements output.NotificationWriter.
func NewNotifyWriter(out io.Writer, fm *output.Formatting, header bool) output.NotificationWriter {
tbl := &table{out: out, header: header, fm: fm}
func NewNotifyWriter(out io.Writer, fm *output.Formatting) output.NotificationWriter {
tbl := &table{out: out, header: fm.ShowHeader, fm: fm }
w := &notifyWriter{tbl: tbl}
w.tbl.reset()
return w

View File

@ -14,8 +14,8 @@ type recordWriter struct {
}
// NewRecordWriter returns a RecordWriter for text table output.
func NewRecordWriter(out io.Writer, fm *output.Formatting, header bool) output.RecordWriter {
tbl := &table{out: out, fm: fm, header: header}
func NewRecordWriter(out io.Writer, fm *output.Formatting) output.RecordWriter {
tbl := &table{out: out, fm: fm, header: fm.ShowHeader}
w := &recordWriter{tbl: tbl}
w.tbl.reset()
return w

View File

@ -16,9 +16,9 @@ type sourceWriter struct {
// NewSourceWriter returns a source writer that outputs source
// details in text table format.
func NewSourceWriter(out io.Writer, fm *output.Formatting, header, verbose bool) output.SourceWriter {
tbl := &table{out: out, fm: fm, header: header}
w := &sourceWriter{tbl: tbl, verbose: verbose}
func NewSourceWriter(out io.Writer, fm *output.Formatting) output.SourceWriter {
tbl := &table{out: out, fm: fm, header: fm.ShowHeader}
w := &sourceWriter{tbl: tbl, verbose: fm.Verbose}
w.tbl.reset()
return w
}
@ -77,8 +77,8 @@ func (w *sourceWriter) SourceSet(ss *source.Set) error {
rows = append(rows, row)
}
w.tbl.tblImpl.SetHeaderDisable(!w.tbl.header)
w.tbl.tblImpl.SetColTrans(0, w.tbl.fm.Number.SprintFunc())
w.tbl.tblImpl.SetHeaderDisable(!w.tbl.fm.ShowHeader)
w.tbl.tblImpl.SetColTrans(0, w.tbl.fm.Handle.SprintFunc())
w.tbl.tblImpl.SetHeader([]string{"HANDLE", "DRIVER", "LOCATION", "OPTIONS"})
w.tbl.appendRowsAndRenderAll(rows)
return nil