Use flag --compact instead of --pretty (#216)

This commit is contained in:
Neil O'Toole 2023-05-05 11:41:22 -06:00 committed by GitHub
parent f0aa65791b
commit 964417dba7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 479 additions and 160 deletions

View File

@ -21,14 +21,14 @@ are several minor breaking changes ☢️.
- `sq config location` prints the location of the config dir.
- `--config` flag is now honored globally.
- Many more knobs are exposed in config.
- Added flags `--log`, `--log.file` and `--log.level`.
- These values can also be set in config via `sq config edit` or `sq config set log.level DEBUG` etc.
- And they can also be set via envars, e.g.
```shell
export SQ_LOG=true
export SQ_LOG_FILE=/var/log/sq.log
export SQ_LOG_LEVEL=WARN
```
- Logging is much more configurable. There are new knobs:
```shell
$ sq config set log true
$ sq config set log.level INFO
$ sq config set log.file /var/log/sq.log
```
There are also equivalent flags (`--log`, `--log.file` and `--log.level`) and
envars (`SQ_LOG`, `SQ_LOG_FILE` and `SQ_LOG_LEVEL`).
- Several more commands support YAML output:
- [`sq group`](https://sq.io/docs/cmd/group)
- [`sq ls`](https://sq.io/docs/cmd/ls)
@ -41,6 +41,9 @@ are several minor breaking changes ☢️.
- The structure of `sq`'s config file (`sq.yml`) has changed. The config
file is automatically upgraded when using the new version.
- The default location of the `sq` log file has changed. The new location
is platform-dependent. Use `sq config get log.file -v` to view the location,
or `sq config set log.file /path/to/sq.log` to set it.
- ☢️ Envar `SQ_CONFIG` replaces `SQ_CONFIGDIR`.
- ☢️ Envar `SQ_LOG_FILE` replaces `SQ_LOGFILE`.
- ☢️ Format flag `--table` is renamed to `--text`. This is changed because while the
@ -75,6 +78,13 @@ are several minor breaking changes ☢️.
# now
$ sq add ./actor.csv -n @actor
```
- ☢️ The `--pretty` flag has been removed. Its only previous use was with the
`json` format, where if `--pretty=false` would output the JSON in compact form.
To better align with jq, there is now a `--compact` / `-c` flag that behaves
identically to `jq`.
- ☢️ Because of the above `--compact` / `-c` flag, the short form of the `--csv`
flag is changing from `-c` to `-C`. It's an unfortunate change, but alignment
with jq's behavior is an overarching principle that justifies the change.
## [v0.33.0] - 2023-04-15

View File

@ -138,7 +138,7 @@ More examples:
}
cmd.Flags().BoolP(flag.JSON, flag.JSONShort, false, flag.JSONUsage)
cmd.Flags().Bool(flag.Pretty, true, flag.PrettyUsage)
cmd.Flags().BoolP(flag.Compact, flag.CompactShort, false, flag.CompactUsage)
cmd.Flags().BoolP(flag.YAML, flag.YAMLShort, false, flag.YAMLUsage)
cmd.Flags().StringP(flag.AddDriver, flag.AddDriverShort, "", flag.AddDriverUsage)

View File

@ -5,6 +5,8 @@ import (
"path/filepath"
"testing"
"github.com/neilotoole/sq/libsq/core/options"
"github.com/neilotoole/sq/testh/tutil"
"github.com/neilotoole/sq/drivers/csv"
@ -24,50 +26,168 @@ import (
func TestCmdAdd(t *testing.T) {
t.Parallel()
type query struct {
// q is the SLQ query to execute
q string
wantRows int
wantCols int
}
actorDataQuery := &query{
q: ".data",
wantRows: sakila.TblActorCount,
wantCols: len(sakila.TblActorCols()),
}
th := testh.New(t)
testCases := []struct {
loc string // first arg to "add" cmd
driver string // --driver flag
handle string // --handle flag
wantHandle string
wantType source.DriverType
wantErr bool
loc string // first arg to "add" cmd
driver string // --driver flag
handle string // --handle flag
wantHandle string
wantType source.DriverType
wantOptions options.Options
wantErr bool
query *query
}{
{loc: "", wantErr: true},
{loc: " ", wantErr: true},
{loc: "/", wantErr: true},
{loc: "../../", wantErr: true},
{loc: "does/not/exist", wantErr: true},
{loc: "_", wantErr: true},
{loc: ".", wantErr: true},
{loc: "/", wantErr: true},
{loc: "../does/not/exist.csv", wantErr: true},
{loc: proj.Rel(sakila.PathCSVActor), handle: "@h1", wantHandle: "@h1", wantType: csv.TypeCSV}, // relative path
{loc: proj.Abs(sakila.PathCSVActor), handle: "@h1", wantHandle: "@h1", wantType: csv.TypeCSV}, // absolute path
{loc: proj.Abs(sakila.PathCSVActor), wantHandle: "@actor", wantType: csv.TypeCSV},
{loc: proj.Abs(sakila.PathCSVActor), driver: "csv", wantHandle: "@actor", wantType: csv.TypeCSV},
{loc: proj.Abs(sakila.PathCSVActor), driver: "xlsx", wantErr: true},
// sqlite can be added both with and without the scheme "sqlite://"
{
loc: "sqlite3://" + proj.Abs(sakila.PathSL3), wantHandle: "@sakila",
wantType: sqlite3.Type,
}, // with scheme
loc: "",
wantErr: true,
},
{
loc: proj.Abs(sakila.PathSL3), wantHandle: "@sakila",
wantType: sqlite3.Type,
}, // without scheme, abs path
loc: " ",
wantErr: true,
},
{
loc: proj.Rel(sakila.PathSL3), wantHandle: "@sakila",
wantType: sqlite3.Type,
}, // without scheme, relative path
{loc: th.Source(sakila.Pg).Location, wantHandle: "@sakila", wantType: postgres.Type},
{loc: th.Source(sakila.MS).Location, wantHandle: "@sakila", wantType: sqlserver.Type},
{loc: th.Source(sakila.My).Location, wantHandle: "@sakila", wantType: mysql.Type},
{loc: proj.Abs(sakila.PathCSVActor), handle: source.StdinHandle, wantErr: true}, // reserved handle
{loc: proj.Abs(sakila.PathCSVActor), handle: source.ActiveHandle, wantErr: true}, // reserved handle
{loc: proj.Abs(sakila.PathCSVActor), handle: source.ScratchHandle, wantErr: true}, // reserved handle
{loc: proj.Abs(sakila.PathCSVActor), handle: source.JoinHandle, wantErr: true}, // reserved handle
loc: "/",
wantErr: true,
},
{
loc: "../../",
wantErr: true,
},
{
loc: "does/not/exist",
wantErr: true,
},
{
loc: "_",
wantErr: true,
},
{
loc: ".",
wantErr: true,
},
{
loc: "/",
wantErr: true,
},
{
loc: "../does/not/exist.csv",
wantErr: true,
},
{
loc: proj.Rel(sakila.PathCSVActor),
handle: "@h1",
wantHandle: "@h1",
wantType: csv.TypeCSV,
query: actorDataQuery,
},
{
loc: proj.Abs(sakila.PathCSVActor),
handle: "@h1",
wantHandle: "@h1",
wantType: csv.TypeCSV,
},
{
loc: proj.Abs(sakila.PathCSVActor),
wantHandle: "@actor",
wantType: csv.TypeCSV,
},
{
loc: proj.Abs(sakila.PathCSVActor),
driver: "csv",
wantHandle: "@actor",
wantType: csv.TypeCSV,
},
{
loc: proj.Abs(sakila.PathCSVActor),
driver: "xlsx",
wantErr: true,
},
{
loc: proj.Rel(sakila.PathTSVActor),
handle: "@h1",
wantHandle: "@h1",
wantType: csv.TypeTSV,
query: actorDataQuery,
},
{
loc: proj.Rel(sakila.PathTSVActorNoHeader),
handle: "@h1",
wantHandle: "@h1",
wantType: csv.TypeTSV,
query: actorDataQuery,
},
{
// sqlite can be added both with and without the scheme "sqlite://"
loc: "sqlite3://" + proj.Abs(sakila.PathSL3),
wantHandle: "@sakila",
wantType: sqlite3.Type,
},
{
// with scheme
loc: proj.Abs(sakila.PathSL3),
wantHandle: "@sakila",
wantType: sqlite3.Type,
},
{
// without scheme, abs path
loc: proj.Rel(sakila.PathSL3),
wantHandle: "@sakila",
wantType: sqlite3.Type,
},
{
// without scheme, relative path
loc: th.Source(sakila.Pg).Location,
wantHandle: "@sakila",
wantType: postgres.Type,
},
{
loc: th.Source(sakila.MS).Location,
wantHandle: "@sakila",
wantType: sqlserver.Type,
},
{
loc: th.Source(sakila.My).Location,
wantHandle: "@sakila",
wantType: mysql.Type,
},
{
loc: proj.Abs(sakila.PathCSVActor),
handle: source.StdinHandle, // reserved handle
wantErr: true,
},
{
loc: proj.Abs(sakila.PathCSVActor),
handle: source.ActiveHandle, // reserved handle
wantErr: true,
},
{
loc: proj.Abs(sakila.PathCSVActor),
handle: source.ScratchHandle, // reserved handle
wantErr: true,
},
{
loc: proj.Abs(sakila.PathCSVActor),
handle: source.JoinHandle, // reserved handle
wantErr: true,
},
}
for _, tc := range testCases {
@ -98,6 +218,21 @@ func TestCmdAdd(t *testing.T) {
require.NoError(t, err)
require.Equal(t, tc.wantHandle, gotSrc.Handle)
require.Equal(t, tc.wantType, gotSrc.Type)
require.Equal(t, len(tc.wantOptions), len(gotSrc.Options))
if tc.query == nil {
return
}
ru = newRun(th.Context, t, ru)
err = ru.Exec(tc.query.q, "--json")
var results []map[string]any
ru.Bind(&results)
require.NoError(t, err)
require.Equal(t, tc.query.wantRows, len(results))
if tc.query.wantRows > 0 {
require.Equal(t, tc.query.wantCols, len(results[0]))
}
})
}
}

View File

@ -11,7 +11,7 @@ func newConfigCmd() *cobra.Command {
Use: "config",
Args: cobra.NoArgs,
Short: "Manage config",
Long: `View and edit base and source-specific config.`,
Long: `View and edit config.`,
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
@ -24,14 +24,17 @@ func newConfigCmd() *cobra.Command {
# Show base config including unset and default values.
$ sq config get -v
# Set config value
# Set option value
$ sq config set format json
# Edit base config
$ sq config edit
# Edit config for source
$ sq config edit @sakila`,
$ sq config edit @sakila
# Delete option (reset to default value)
$ sq config set -D log.level`,
}
return cmd

View File

@ -12,7 +12,8 @@ func newConfigGetCmd() *cobra.Command {
Use: "get",
Short: "Show config",
Long: `Show config. By default, only explicitly set options are shown.
Use the --verbose flag (in text output format) to see all options.`,
Use the -v flag to see all options. When flag --src is provided, show config
just for that source.`,
Args: cobra.MaximumNArgs(1),
ValidArgsFunction: completeOptKey,
RunE: execConfigGet,
@ -40,7 +41,7 @@ Use the --verbose flag (in text output format) to see all options.`,
cmd.Flags().BoolP(flag.JSON, flag.JSONShort, false, flag.JSONUsage)
cmd.Flags().BoolP(flag.YAML, flag.YAMLShort, false, flag.YAMLUsage)
cmd.Flags().Bool(flag.Pretty, true, flag.PrettyUsage)
cmd.Flags().BoolP(flag.Compact, flag.CompactShort, false, flag.CompactUsage)
cmd.Flags().String(flag.ConfigSrc, "", flag.ConfigSrcUsage)
panicOn(cmd.RegisterFlagCompletionFunc(flag.ConfigSrc, completeHandle(1)))

View File

@ -34,7 +34,7 @@ Use "sq config get -v" to see available options.`,
}
cmd.Flags().BoolP(flag.JSON, flag.JSONShort, false, flag.JSONUsage)
cmd.Flags().Bool(flag.Pretty, true, flag.PrettyUsage)
cmd.Flags().BoolP(flag.Compact, flag.CompactShort, false, flag.CompactUsage)
cmd.Flags().BoolP(flag.YAML, flag.YAMLShort, false, flag.YAMLUsage)
cmd.Flags().String(flag.ConfigSrc, "", flag.ConfigSrcUsage)

View File

@ -32,7 +32,7 @@ func newDriverListCmd() *cobra.Command {
cmd.Flags().BoolP(flag.JSON, flag.JSONShort, false, flag.JSONUsage)
cmd.Flags().BoolP(flag.YAML, flag.YAMLShort, false, flag.YAMLUsage)
cmd.Flags().Bool(flag.Pretty, true, flag.PrettyUsage)
cmd.Flags().BoolP(flag.Compact, flag.CompactShort, false, flag.CompactUsage)
return cmd
}

View File

@ -34,7 +34,7 @@ Use 'sq ls -g' to list groups.`,
}
cmd.Flags().BoolP(flag.JSON, flag.JSONShort, false, flag.JSONUsage)
cmd.Flags().Bool(flag.Pretty, true, flag.PrettyUsage)
cmd.Flags().BoolP(flag.Compact, flag.CompactShort, false, flag.CompactUsage)
cmd.Flags().BoolP(flag.YAML, flag.YAMLShort, false, flag.YAMLUsage)
return cmd

View File

@ -45,7 +45,7 @@ If @HANDLE is not provided, the active data source is assumed.`,
}
cmd.Flags().BoolP(flag.JSON, flag.JSONShort, false, flag.JSONUsage)
cmd.Flags().Bool(flag.Pretty, true, flag.PrettyUsage)
cmd.Flags().BoolP(flag.Compact, flag.CompactShort, false, flag.CompactUsage)
cmd.Flags().BoolP(flag.YAML, flag.YAMLShort, false, flag.YAMLUsage)
return cmd

View File

@ -40,7 +40,7 @@ any further descendants.
}
cmd.Flags().BoolP(flag.JSON, flag.JSONShort, false, flag.JSONUsage)
cmd.Flags().Bool(flag.Pretty, true, flag.PrettyUsage)
cmd.Flags().BoolP(flag.Compact, flag.CompactShort, false, flag.CompactUsage)
cmd.Flags().BoolP(flag.YAML, flag.YAMLShort, false, flag.YAMLUsage)
cmd.Flags().BoolP(flag.ListGroup, flag.ListGroupShort, false, flag.ListGroupUsage)

View File

@ -44,7 +44,7 @@ source handles are files, and groups are directories.`,
}
cmd.Flags().BoolP(flag.JSON, flag.JSONShort, false, flag.JSONUsage)
cmd.Flags().Bool(flag.Pretty, true, flag.PrettyUsage)
cmd.Flags().BoolP(flag.Compact, flag.CompactShort, false, flag.CompactUsage)
cmd.Flags().BoolP(flag.YAML, flag.YAMLShort, false, flag.YAMLUsage)
return cmd

View File

@ -65,7 +65,7 @@ The exit code is 1 if ping fails for any of the sources.`,
cmd.Flags().BoolP(flag.JSON, flag.JSONShort, false, flag.JSONUsage)
cmd.Flags().BoolP(flag.CSV, flag.CSVShort, false, flag.CSVUsage)
cmd.Flags().BoolP(flag.TSV, flag.TSVShort, false, flag.TSVUsage)
cmd.Flags().Bool(flag.Pretty, true, flag.PrettyUsage)
cmd.Flags().BoolP(flag.Compact, flag.CompactShort, false, flag.CompactUsage)
cmd.Flags().Duration(flag.PingTimeout, time.Second*10, flag.PingTimeoutUsage)
return cmd

View File

@ -33,7 +33,7 @@ may have changed, if that source or group was removed.`,
}
cmd.Flags().BoolP(flag.JSON, flag.JSONShort, false, flag.JSONUsage)
cmd.Flags().Bool(flag.Pretty, true, flag.PrettyUsage)
cmd.Flags().BoolP(flag.Compact, flag.CompactShort, false, flag.CompactUsage)
cmd.Flags().BoolP(flag.YAML, flag.YAMLShort, false, flag.YAMLUsage)
return cmd
}

View File

@ -347,7 +347,7 @@ func addQueryCmdFlags(cmd *cobra.Command) {
cmd.Flags().BoolP(flag.Raw, flag.RawShort, false, flag.RawUsage)
cmd.Flags().BoolP(flag.XLSX, flag.XLSXShort, false, flag.XLSXUsage)
cmd.Flags().BoolP(flag.XML, flag.XMLShort, false, flag.XMLUsage)
cmd.Flags().Bool(flag.Pretty, true, flag.PrettyUsage)
cmd.Flags().BoolP(flag.Compact, flag.CompactShort, false, flag.CompactUsage)
cmd.Flags().StringP(flag.Output, flag.OutputShort, "", flag.OutputUsage)

View File

@ -22,7 +22,7 @@ source. Otherwise, set @HANDLE as the active data source.`,
}
cmd.Flags().BoolP(flag.JSON, flag.JSONShort, false, flag.JSONUsage)
cmd.Flags().Bool(flag.Pretty, true, flag.PrettyUsage)
cmd.Flags().BoolP(flag.Compact, flag.CompactShort, false, flag.CompactUsage)
cmd.Flags().BoolP(flag.YAML, flag.YAMLShort, false, flag.YAMLUsage)
return cmd

View File

@ -55,7 +55,7 @@ func newTblCopyCmd() *cobra.Command {
}
cmd.Flags().BoolP(flag.JSON, flag.JSONShort, false, flag.JSONUsage)
cmd.Flags().Bool(flag.Pretty, true, flag.PrettyUsage)
cmd.Flags().BoolP(flag.Compact, flag.CompactShort, false, flag.CompactUsage)
cmd.Flags().Bool(flag.TblData, true, flag.TblDataUsage)
return cmd

View File

@ -51,7 +51,7 @@ Before upgrading, check the changelog: https://sq.io/changelog`,
}
cmd.Flags().BoolP(flag.JSON, flag.JSONShort, false, flag.JSONUsage)
cmd.Flags().Bool(flag.Pretty, true, flag.PrettyUsage)
cmd.Flags().BoolP(flag.Compact, flag.CompactShort, false, flag.CompactUsage)
return cmd
}

View File

@ -9,7 +9,7 @@ const (
ConfigSrcUsage = "Config for source"
CSV = "csv"
CSVShort = "c"
CSVShort = "C"
CSVUsage = "Output CSV"
AddDriver = "driver"
@ -72,8 +72,9 @@ const (
PasswordPromptShort = "p"
PasswordPromptUsage = "Read password from stdin or prompt"
Pretty = "pretty"
PrettyUsage = "Pretty-print output"
Compact = "compact"
CompactShort = "c"
CompactUsage = "Compact instead of pretty-printed output"
Raw = "raw"
RawShort = "r"

View File

@ -133,7 +133,7 @@ func RegisterDefaultOpts(reg *options.Registry) {
OptVerbose,
OptPrintHeader,
OptMonochrome,
OptPretty,
OptCompact,
OptPingTimeout,
OptShellCompletionTimeout,
OptLogEnabled,

View File

@ -202,7 +202,7 @@ type punc struct {
func newPunc(pr *output.Printing) punc {
var p punc
if pr == nil || pr.IsMonochrome() || !pr.Pretty {
if pr == nil || pr.IsMonochrome() || pr.Compact {
p.comma = append(p.comma, ',')
p.colon = append(p.colon, ':')
p.lBrace = append(p.lBrace, '{')

View File

@ -35,7 +35,7 @@ func (w *errorWriter) Error(err error) {
val := w.pr.Error.Sprint(string(b)) // trim the newline
var s string
if w.pr.Pretty {
if !w.pr.Compact {
s = fmt.Sprintf(tplPretty, key, val)
} else {
s = fmt.Sprintf(tplNoPretty, key, val)

View File

@ -33,26 +33,91 @@ func TestEncode(t *testing.T) {
v any
want string
}{
{name: "nil", pretty: true, v: nil, want: "null\n"},
{name: "slice_empty", pretty: true, v: []int{}, want: "[]\n"},
{name: "slice_1_pretty", pretty: true, v: []any{1}, want: "[\n 1\n]\n"},
{name: "slice_1_no_pretty", v: []any{1}, want: "[1]\n"},
{name: "slice_2_pretty", pretty: true, v: []any{1, true}, want: "[\n 1,\n true\n]\n"},
{name: "slice_2_no_pretty", v: []any{1, true}, want: "[1,true]\n"},
{name: "map_int_empty", pretty: true, v: map[string]int{}, want: "{}\n"},
{name: "map_interface_empty", pretty: true, v: map[string]any{}, want: "{}\n"},
{name: "map_interface_empty_sorted", pretty: true, sortMap: true, v: map[string]any{}, want: "{}\n"},
{name: "map_1_pretty", pretty: true, sortMap: true, v: map[string]any{"one": 1}, want: "{\n \"one\": 1\n}\n"},
{name: "map_1_no_pretty", sortMap: true, v: map[string]any{"one": 1}, want: "{\"one\":1}\n"},
{
name: "map_2_pretty", pretty: true, sortMap: true, v: map[string]any{"one": 1, "two": 2},
want: "{\n \"one\": 1,\n \"two\": 2\n}\n",
name: "nil",
pretty: true,
v: nil,
want: "null\n",
},
{
name: "map_2_no_pretty", sortMap: true, v: map[string]any{"one": 1, "two": 2},
want: "{\"one\":1,\"two\":2}\n",
name: "slice_empty",
pretty: true,
v: []int{},
want: "[]\n",
},
{
name: "slice_1_pretty",
pretty: true,
v: []any{1},
want: "[\n 1\n]\n",
},
{
name: "slice_1_no_pretty",
v: []any{1},
want: "[1]\n",
},
{
name: "slice_2_pretty",
pretty: true,
v: []any{1, true},
want: "[\n 1,\n true\n]\n",
},
{
name: "slice_2_no_pretty",
v: []any{1, true},
want: "[1,true]\n",
},
{
name: "map_int_empty",
pretty: true,
v: map[string]int{},
want: "{}\n",
},
{
name: "map_interface_empty",
pretty: true,
v: map[string]any{},
want: "{}\n",
},
{
name: "map_interface_empty_sorted",
pretty: true,
sortMap: true,
v: map[string]any{},
want: "{}\n",
},
{
name: "map_1_pretty",
pretty: true,
sortMap: true,
v: map[string]any{"one": 1},
want: "{\n \"one\": 1\n}\n",
},
{
name: "map_1_no_pretty",
sortMap: true,
v: map[string]any{"one": 1},
want: "{\"one\":1}\n",
},
{
name: "map_2_pretty",
pretty: true,
sortMap: true,
v: map[string]any{"one": 1, "two": 2},
want: "{\n \"one\": 1,\n \"two\": 2\n}\n",
},
{
name: "map_2_no_pretty",
sortMap: true,
v: map[string]any{"one": 1, "two": 2},
want: "{\"one\":1,\"two\":2}\n",
},
{
name: "tinystruct",
pretty: true,
v: TinyStruct{FBool: true},
want: "{\n \"f_bool\": true\n}\n",
},
{name: "tinystruct", pretty: true, v: TinyStruct{FBool: true}, want: "{\n \"f_bool\": true\n}\n"},
}
for _, tc := range testCases {
@ -60,7 +125,7 @@ func TestEncode(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
pr := output.NewPrinting()
pr.Pretty = tc.pretty
pr.Compact = !tc.pretty
pr.EnableColor(tc.color)
buf := &bytes.Buffer{}
@ -69,7 +134,7 @@ func TestEncode(t *testing.T) {
enc.SetSortMapKeys(tc.sortMap)
enc.SetColors(internal.NewColors(pr))
if pr.Pretty {
if !pr.Compact {
enc.SetIndent("", pr.Indent)
}
@ -88,11 +153,36 @@ func TestEncode_Slice(t *testing.T) {
v []any
want string
}{
{name: "nil", pretty: true, v: nil, want: "null\n"},
{name: "empty", pretty: true, v: []any{}, want: "[]\n"},
{name: "one", pretty: true, v: []any{1}, want: "[\n 1\n]\n"},
{name: "two", pretty: true, v: []any{1, true}, want: "[\n 1,\n true\n]\n"},
{name: "three", pretty: true, v: []any{1, true, "hello"}, want: "[\n 1,\n true,\n \"hello\"\n]\n"},
{
name: "nil",
pretty: true,
v: nil,
want: "null\n",
},
{
name: "empty",
pretty: true,
v: []any{},
want: "[]\n",
},
{
name: "one",
pretty: true,
v: []any{1},
want: "[\n 1\n]\n",
},
{
name: "two",
pretty: true,
v: []any{1, true},
want: "[\n 1,\n true\n]\n",
},
{
name: "three",
pretty: true,
v: []any{1, true, "hello"},
want: "[\n 1,\n true,\n \"hello\"\n]\n",
},
}
for _, tc := range testCases {
@ -100,14 +190,14 @@ func TestEncode_Slice(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
pr := output.NewPrinting()
pr.Pretty = tc.pretty
pr.Compact = !tc.pretty
pr.EnableColor(tc.color)
buf := &bytes.Buffer{}
enc := jcolorenc.NewEncoder(buf)
enc.SetEscapeHTML(false)
enc.SetColors(internal.NewColors(pr))
if pr.Pretty {
if !pr.Compact {
enc.SetIndent("", " ")
}
@ -136,12 +226,14 @@ func TestEncode_SmallStruct(t *testing.T) {
want string
}{
{
pretty: false, color: false,
want: "{\"f_int\":7,\"f_slice\":[64,true],\"f_map\":{\"m_float64\":64.64,\"m_string\":\"hello\"},\"f_tinystruct\":{\"f_bool\":true},\"f_string\":\"hello\"}\n",
pretty: false,
color: false,
want: "{\"f_int\":7,\"f_slice\":[64,true],\"f_map\":{\"m_float64\":64.64,\"m_string\":\"hello\"},\"f_tinystruct\":{\"f_bool\":true},\"f_string\":\"hello\"}\n",
},
{
pretty: true, color: false,
want: "{\n \"f_int\": 7,\n \"f_slice\": [\n 64,\n true\n ],\n \"f_map\": {\n \"m_float64\": 64.64,\n \"m_string\": \"hello\"\n },\n \"f_tinystruct\": {\n \"f_bool\": true\n },\n \"f_string\": \"hello\"\n}\n",
pretty: true,
color: false,
want: "{\n \"f_int\": 7,\n \"f_slice\": [\n 64,\n true\n ],\n \"f_map\": {\n \"m_float64\": 64.64,\n \"m_string\": \"hello\"\n },\n \"f_tinystruct\": {\n \"f_bool\": true\n },\n \"f_string\": \"hello\"\n}\n",
},
}
@ -150,7 +242,7 @@ func TestEncode_SmallStruct(t *testing.T) {
t.Run(fmt.Sprintf("pretty_%v__color_%v", tc.pretty, tc.color), func(t *testing.T) {
pr := output.NewPrinting()
pr.Pretty = tc.pretty
pr.Compact = !tc.pretty
pr.EnableColor(tc.color)
buf := &bytes.Buffer{}
@ -159,7 +251,7 @@ func TestEncode_SmallStruct(t *testing.T) {
enc.SetSortMapKeys(true)
enc.SetColors(internal.NewColors(pr))
if pr.Pretty {
if !pr.Compact {
enc.SetIndent("", " ")
}
@ -205,7 +297,7 @@ func TestEncode_Map_Nested(t *testing.T) {
t.Run(fmt.Sprintf("pretty_%v__color_%v", tc.pretty, tc.color), func(t *testing.T) {
pr := output.NewPrinting()
pr.Pretty = tc.pretty
pr.Compact = !tc.pretty
pr.EnableColor(tc.color)
buf := &bytes.Buffer{}
@ -214,7 +306,7 @@ func TestEncode_Map_Nested(t *testing.T) {
enc.SetSortMapKeys(true)
enc.SetColors(internal.NewColors(pr))
if pr.Pretty {
if !pr.Compact {
enc.SetIndent("", " ")
}
@ -237,19 +329,53 @@ func TestEncode_Map_StringNotInterface(t *testing.T) {
v map[string]bool
want string
}{
{pretty: false, sortMap: true, v: map[string]bool{}, want: "{}\n"},
{pretty: false, sortMap: false, v: map[string]bool{}, want: "{}\n"},
{pretty: true, sortMap: true, v: map[string]bool{}, want: "{}\n"},
{pretty: true, sortMap: false, v: map[string]bool{}, want: "{}\n"},
{pretty: false, sortMap: true, v: map[string]bool{"one": true}, want: "{\"one\":true}\n"},
{pretty: false, sortMap: false, v: map[string]bool{"one": true}, want: "{\"one\":true}\n"},
{
pretty: false, sortMap: true, v: map[string]bool{"one": true, "two": false},
want: "{\"one\":true,\"two\":false}\n",
pretty: false,
sortMap: true,
v: map[string]bool{},
want: "{}\n",
},
{
pretty: true, sortMap: true, v: map[string]bool{"one": true, "two": false},
want: "{\n \"one\": true,\n \"two\": false\n}\n",
pretty: false,
sortMap: false,
v: map[string]bool{},
want: "{}\n",
},
{
pretty: true,
sortMap: true,
v: map[string]bool{},
want: "{}\n",
},
{
pretty: true,
sortMap: false,
v: map[string]bool{},
want: "{}\n",
},
{
pretty: false,
sortMap: true,
v: map[string]bool{"one": true},
want: "{\"one\":true}\n",
},
{
pretty: false,
sortMap: false,
v: map[string]bool{"one": true},
want: "{\"one\":true}\n",
},
{
pretty: false,
sortMap: true,
v: map[string]bool{"one": true, "two": false},
want: "{\"one\":true,\"two\":false}\n",
},
{
pretty: true,
sortMap: true,
v: map[string]bool{"one": true, "two": false},
want: "{\n \"one\": true,\n \"two\": false\n}\n",
},
}
@ -258,7 +384,7 @@ func TestEncode_Map_StringNotInterface(t *testing.T) {
t.Run(fmt.Sprintf("size_%d__pretty_%v__color_%v", len(tc.v), tc.pretty, tc.color), func(t *testing.T) {
pr := output.NewPrinting()
pr.Pretty = tc.pretty
pr.Compact = !tc.pretty
pr.EnableColor(tc.color)
buf := &bytes.Buffer{}
@ -266,7 +392,7 @@ func TestEncode_Map_StringNotInterface(t *testing.T) {
enc.SetEscapeHTML(false)
enc.SetSortMapKeys(tc.sortMap)
enc.SetColors(internal.NewColors(pr))
if pr.Pretty {
if !pr.Compact {
enc.SetIndent("", pr.Indent)
}
@ -292,12 +418,29 @@ func TestEncode_RawMessage(t *testing.T) {
v any
want string
}{
{name: "empty", pretty: false, v: jcolorenc.RawMessage(`{}`), want: "{}\n"},
{name: "no_pretty", pretty: false, v: raw, want: "{\"one\":1,\"two\":2}\n"},
{name: "pretty", pretty: true, v: raw, want: "{\n \"one\": 1,\n \"two\": 2\n}\n"},
{
name: "pretty_struct", pretty: true, v: RawStruct{FString: "hello", FRaw: raw},
want: "{\n \"f_string\": \"hello\",\n \"f_raw\": {\n \"one\": 1,\n \"two\": 2\n }\n}\n",
name: "empty",
pretty: false,
v: jcolorenc.RawMessage(`{}`),
want: "{}\n",
},
{
name: "no_pretty",
pretty: false,
v: raw,
want: "{\"one\":1,\"two\":2}\n",
},
{
name: "pretty",
pretty: true,
v: raw,
want: "{\n \"one\": 1,\n \"two\": 2\n}\n",
},
{
name: "pretty_struct",
pretty: true,
v: RawStruct{FString: "hello", FRaw: raw},
want: "{\n \"f_string\": \"hello\",\n \"f_raw\": {\n \"one\": 1,\n \"two\": 2\n }\n}\n",
},
}
@ -306,7 +449,7 @@ func TestEncode_RawMessage(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
pr := output.NewPrinting()
pr.Pretty = tc.pretty
pr.Compact = !tc.pretty
pr.EnableColor(tc.color)
buf := &bytes.Buffer{}
@ -314,7 +457,7 @@ func TestEncode_RawMessage(t *testing.T) {
enc.SetEscapeHTML(false)
enc.SetSortMapKeys(true)
enc.SetColors(internal.NewColors(pr))
if pr.Pretty {
if !pr.Compact {
enc.SetIndent("", pr.Indent)
}
@ -339,21 +482,47 @@ func TestEncode_Map_StringRawMessage(t *testing.T) {
v map[string]jcolorenc.RawMessage
want string
}{
{pretty: false, sortMap: true, v: map[string]jcolorenc.RawMessage{}, want: "{}\n"},
{pretty: false, sortMap: false, v: map[string]jcolorenc.RawMessage{}, want: "{}\n"},
{pretty: true, sortMap: true, v: map[string]jcolorenc.RawMessage{}, want: "{}\n"},
{pretty: true, sortMap: false, v: map[string]jcolorenc.RawMessage{}, want: "{}\n"},
{
pretty: false, sortMap: true, v: map[string]jcolorenc.RawMessage{"msg1": raw, "msg2": raw},
want: "{\"msg1\":{\"one\":1,\"two\":2},\"msg2\":{\"one\":1,\"two\":2}}\n",
pretty: false,
sortMap: true,
v: map[string]jcolorenc.RawMessage{},
want: "{}\n",
},
{
pretty: true, sortMap: true, v: map[string]jcolorenc.RawMessage{"msg1": raw, "msg2": raw},
want: "{\n \"msg1\": {\n \"one\": 1,\n \"two\": 2\n },\n \"msg2\": {\n \"one\": 1,\n \"two\": 2\n }\n}\n",
pretty: false,
sortMap: false,
v: map[string]jcolorenc.RawMessage{},
want: "{}\n",
},
{
pretty: true, sortMap: false, v: map[string]jcolorenc.RawMessage{"msg1": raw},
want: "{\n \"msg1\": {\n \"one\": 1,\n \"two\": 2\n }\n}\n",
pretty: true,
sortMap: true,
v: map[string]jcolorenc.RawMessage{},
want: "{}\n",
},
{
pretty: true,
sortMap: false,
v: map[string]jcolorenc.RawMessage{},
want: "{}\n",
},
{
pretty: false,
sortMap: true,
v: map[string]jcolorenc.RawMessage{"msg1": raw, "msg2": raw},
want: "{\"msg1\":{\"one\":1,\"two\":2},\"msg2\":{\"one\":1,\"two\":2}}\n",
},
{
pretty: true,
sortMap: true,
v: map[string]jcolorenc.RawMessage{"msg1": raw, "msg2": raw},
want: "{\n \"msg1\": {\n \"one\": 1,\n \"two\": 2\n },\n \"msg2\": {\n \"one\": 1,\n \"two\": 2\n }\n}\n",
},
{
pretty: true,
sortMap: false,
v: map[string]jcolorenc.RawMessage{"msg1": raw},
want: "{\n \"msg1\": {\n \"one\": 1,\n \"two\": 2\n }\n}\n",
},
}
@ -363,7 +532,7 @@ func TestEncode_Map_StringRawMessage(t *testing.T) {
name := fmt.Sprintf("size_%d__pretty_%v__color_%v__sort_%v", len(tc.v), tc.pretty, tc.color, tc.sortMap)
t.Run(name, func(t *testing.T) {
pr := output.NewPrinting()
pr.Pretty = tc.pretty
pr.Compact = !tc.pretty
pr.EnableColor(tc.color)
buf := &bytes.Buffer{}
@ -371,7 +540,7 @@ func TestEncode_Map_StringRawMessage(t *testing.T) {
enc.SetEscapeHTML(false)
enc.SetSortMapKeys(tc.sortMap)
enc.SetColors(internal.NewColors(pr))
if pr.Pretty {
if !pr.Compact {
enc.SetIndent("", pr.Indent)
}
@ -405,7 +574,7 @@ func TestEncode_BigStruct(t *testing.T) {
t.Run(fmt.Sprintf("pretty_%v__color_%v", tc.pretty, tc.color), func(t *testing.T) {
pr := output.NewPrinting()
pr.Pretty = tc.pretty
pr.Compact = !tc.pretty
pr.EnableColor(tc.color)
buf := &bytes.Buffer{}
@ -414,7 +583,7 @@ func TestEncode_BigStruct(t *testing.T) {
enc.SetSortMapKeys(true)
enc.SetColors(internal.NewColors(pr))
if pr.Pretty {
if !pr.Compact {
enc.SetIndent("", " ")
}
@ -430,12 +599,11 @@ func TestEncode_BigStruct(t *testing.T) {
// has a fast path).
//
// NOTE: Currently the encoder is broken wrt colors enabled
//
// for non-string map keys. It's possible we don't actually need
// to address this for sq purposes.
// for non-string map keys. It's possible we don't actually need
// to address this for sq purposes.
func TestEncode_Map_Not_StringInterface(t *testing.T) {
pr := output.NewPrinting()
pr.Pretty = true
pr.Compact = false
pr.EnableColor(true)
buf := &bytes.Buffer{}
@ -443,7 +611,7 @@ func TestEncode_Map_Not_StringInterface(t *testing.T) {
enc.SetEscapeHTML(false)
enc.SetSortMapKeys(true)
enc.SetColors(internal.NewColors(pr))
if pr.Pretty {
if !pr.Compact {
enc.SetIndent("", " ")
}

View File

@ -16,7 +16,7 @@ func writeJSON(out io.Writer, pr *output.Printing, v any) error {
enc := jcolorenc.NewEncoder(out)
enc.SetColors(internal.NewColors(pr))
enc.SetEscapeHTML(false)
if pr.Pretty {
if !pr.Compact {
enc.SetIndent("", pr.Indent)
}

View File

@ -154,7 +154,7 @@ func TestRecordWriters(t *testing.T) {
buf := &bytes.Buffer{}
pr := output.NewPrinting()
pr.EnableColor(tc.color)
pr.Pretty = tc.pretty
pr.Compact = !tc.pretty
w := tc.factoryFn(buf, pr)
@ -204,7 +204,7 @@ func TestErrorWriter(t *testing.T) {
t.Run(t.Name(), func(t *testing.T) {
buf := &bytes.Buffer{}
pr := output.NewPrinting()
pr.Pretty = tc.pretty
pr.Compact = !tc.pretty
pr.EnableColor(tc.color)
errw := jsonw.NewErrorWriter(slogt.New(t), buf, pr)

View File

@ -152,7 +152,7 @@ func (w *stdWriter) Close() error {
return w.err
}
if w.recsWritten && w.pr.Pretty {
if w.recsWritten && !w.pr.Compact {
w.outBuf.WriteRune('\n')
}
@ -187,7 +187,7 @@ func newStdTemplate(recMeta sqlz.RecordMeta, pr *output.Printing) (*stdTemplate,
}
}
if !pr.Pretty {
if pr.Compact {
tpl[0] = append(tpl[0], pnc.lBrace...)
tpl[0] = append(tpl[0], fieldNames[0]...)
tpl[0] = append(tpl[0], pnc.colon...)
@ -394,20 +394,20 @@ func newJSONObjectsTemplate(recMeta sqlz.RecordMeta, pr *output.Printing) ([][]b
tpl[0] = append(tpl[0], pnc.lBrace...)
tpl[0] = append(tpl[0], fieldNames[0]...)
tpl[0] = append(tpl[0], pnc.colon...)
if pr.Pretty {
if !pr.Compact {
tpl[0] = append(tpl[0], ' ')
}
for i := 1; i < len(fieldNames); i++ {
tpl[i] = append(tpl[i], pnc.comma...)
if pr.Pretty {
if !pr.Compact {
tpl[i] = append(tpl[i], ' ')
}
tpl[i] = append(tpl[i], fieldNames[i]...)
tpl[i] = append(tpl[i], pnc.colon...)
if pr.Pretty {
if !pr.Compact {
tpl[i] = append(tpl[i], ' ')
}
}
@ -426,7 +426,7 @@ func newJSONArrayTemplate(recMeta sqlz.RecordMeta, pr *output.Printing) ([][]byt
for i := 1; i < len(recMeta); i++ {
tpl[i] = append(tpl[i], pnc.comma...)
if pr.Pretty {
if !pr.Compact {
tpl[i] = append(tpl[i], ' ')
}
}

View File

@ -21,10 +21,10 @@ type Printing struct {
// applicable.
Verbose bool
// Pretty indicates that output should be pretty-printed.
// Compact indicates that output should not be pretty-printed.
// Typically this means indentation, new lines, etc., but
// varies by output format.
Pretty bool
Compact bool
// Indent is the indent string to use when pretty-printing,
// typically two spaces.
@ -100,7 +100,7 @@ func NewPrinting() *Printing {
pr := &Printing{
ShowHeader: true,
Verbose: false,
Pretty: true,
Compact: false,
Redact: true,
FlushThreshold: 1000,
monochrome: false,
@ -141,7 +141,7 @@ func (pr *Printing) LogValue() slog.Value {
slog.Bool("verbose", pr.Verbose),
slog.Bool("header", pr.ShowHeader),
slog.Bool("monochrome", pr.monochrome),
slog.Bool("pretty", pr.Pretty),
slog.Bool("compact", pr.Compact),
slog.Bool("redact", pr.Redact),
slog.Int("flush-threshold", pr.FlushThreshold),
slog.String("indent", pr.Indent),

View File

@ -61,7 +61,7 @@ func (w *recordWriter) Open(recMeta sqlz.RecordMeta) error {
w.recMeta = recMeta
var indent, newline string
if w.pr.Pretty {
if !w.pr.Compact {
indent = w.pr.Indent
newline = "\n"
}

View File

@ -73,7 +73,7 @@ func TestRecordWriter_Actor(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
pr := output.NewPrinting()
pr.EnableColor(tc.color)
pr.Pretty = tc.pretty
pr.Compact = !tc.pretty
recMeta, recs := testh.RecordsFromTbl(t, sakila.SL3, sakila.TblActor)
recs = recs[0:tc.numRecs]

View File

@ -62,10 +62,10 @@ a particular command, sq falls back to 'text'. Available formats:
"format",
)
OptPretty = options.NewBool(
"pretty",
true,
`Prettyify output. Only applies to some output formats.`,
OptCompact = options.NewBool(
"compact",
false,
`Compact instead of pretty-printed output`,
"format",
)
@ -184,7 +184,7 @@ func getPrinting(cmd *cobra.Command, opts options.Options, out, errOut io.Writer
pr.Verbose = OptVerbose.Get(opts)
pr.FlushThreshold = OptTuningFlushThreshold.Get(opts)
pr.Pretty = OptPretty.Get(opts)
pr.Compact = OptCompact.Get(opts)
switch {
case cmdFlagChanged(cmd, flag.Header):

View File

@ -234,4 +234,5 @@ const (
PathCSVActor = "drivers/csv/testdata/sakila-csv/actor.csv"
PathCSVActorNoHeader = "drivers/csv/testdata/sakila-csv-noheader/actor.csv"
PathTSVActor = "drivers/csv/testdata/sakila-tsv/actor.tsv"
PathTSVActorNoHeader = "drivers/csv/testdata/sakila-tsv-noheader/actor.tsv"
)