From 6870327508a043ff9c6952d2aa1f73b3a5a2f7a9 Mon Sep 17 00:00:00 2001 From: Neil O'Toole Date: Mon, 22 Feb 2021 00:37:00 -0700 Subject: [PATCH] Cobra upgrade: includes shell completion work (#81) Addressed #80 --- .golangci.yml | 2 +- .goreleaser.yml | 2 +- README.md | 6 + cli/cli.go | 228 ++++++++++++------ cli/cli_test.go | 31 ++- cli/cmd_add.go | 65 ++--- cli/cmd_completion.go | 156 +++++------- cli/cmd_driver.go | 46 ++++ cli/cmd_drivers.go | 30 --- cli/cmd_help.go | 7 +- cli/cmd_inspect.go | 55 +++-- cli/cmd_list.go | 14 +- cli/cmd_ping.go | 53 ++-- cli/cmd_remove.go | 23 +- cli/cmd_root.go | 41 ++-- cli/cmd_scratch.go | 26 +- cli/cmd_slq.go | 47 ++-- cli/cmd_sql.go | 32 +-- cli/cmd_src.go | 30 +-- cli/cmd_tbl.go | 91 ++++--- cli/cmd_version.go | 8 +- cli/completion.go | 362 ++++++++++++++++++++++++++++ cli/config/config.go | 26 +- cli/config/config_test.go | 20 +- cli/config/testdata/bad.01.sq.yml | 2 +- cli/config/testdata/bad.08.sq.yml | 2 + cli/config/testdata/good.01.sq.yml | 3 +- cli/output/tablew/metadatawriter.go | 4 +- cli/run_test.go | 7 +- cli/source.go | 9 +- drivers/mysql/mysql.go | 4 +- drivers/postgres/postgres.go | 2 +- drivers/sqlite3/sqlite3.go | 17 +- drivers/sqlserver/sqlserver.go | 4 +- go.mod | 38 +-- go.sum | 267 ++++++++++++++++---- libsq/core/sqlz/sqlz.go | 4 +- libsq/driver/driver.go | 2 +- libsq/source/set.go | 21 +- libsq/source/source.go | 7 +- main.go | 2 +- 41 files changed, 1229 insertions(+), 567 deletions(-) create mode 100644 cli/cmd_driver.go delete mode 100644 cli/cmd_drivers.go create mode 100644 cli/completion.go create mode 100644 cli/config/testdata/bad.08.sq.yml diff --git a/.golangci.yml b/.golangci.yml index 63b016dc..c75c6206 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -131,4 +131,4 @@ run: service: golangci-lint-version: 1.27.x # use the fixed version to not introduce new linters unexpectedly prepare: - - echo "here I can run custom commands, but no preparation needed for this repo" \ No newline at end of file + - echo "here I can run custom commands, but no preparation needed for this repo" diff --git a/.goreleaser.yml b/.goreleaser.yml index d9fa6a2a..790d2016 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -101,7 +101,7 @@ brews: name: sq homepage: "https://sq.io" description: "sq is a swiss army knife for data" - caveats: "This is a preview release of sq. Use with caution." + caveats: "For shell completion installation instructions, execute: sq completion --help" tap: owner: neilotoole diff --git a/README.md b/README.md index 272628c9..5a34f7f2 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ or dropping tables. For other installation options, see [here](https://github.com/neilotoole/sq/wiki/Home#Install). +It is strongly advised to install [shell completion](#shell-completion). ### macOS @@ -52,6 +53,11 @@ sudo rpm -i https://github.com/neilotoole/sq/releases/latest/download/sq-linux-a yum localinstall -y https://github.com/neilotoole/sq/releases/latest/download/sq-linux-amd64.rpm ``` +## Shell completion + +Shell completion is available for `bash`, `zsh`, `fish`, and `powershell`. + +Execute `sq completion --help` for installation instructions. ## Quickstart diff --git a/cli/cli.go b/cli/cli.go index 6cc5ef0f..0093bffc 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -32,6 +32,7 @@ import ( "path/filepath" "strconv" "strings" + "sync" "github.com/fatih/color" "github.com/mattn/go-colorable" @@ -68,6 +69,10 @@ import ( "github.com/neilotoole/sq/libsq/source" ) +func init() { + cobra.EnableCommandSorting = false +} + // errNoMsg is a sentinel error indicating that a command // has failed, but that no error message should be printed. // This is useful in the case where any error information may @@ -77,7 +82,7 @@ var errNoMsg = errors.New("") // Execute builds a RunContext using ctx and default // settings, and invokes ExecuteWith. func Execute(ctx context.Context, stdin *os.File, stdout, stderr io.Writer, args []string) error { - rc, err := newDefaultRunContext(ctx, stdin, stdout, stderr) + rc, err := newDefaultRunContext(stdin, stdout, stderr) if err != nil { printError(rc, err) return err @@ -85,65 +90,95 @@ func Execute(ctx context.Context, stdin *os.File, stdout, stderr io.Writer, args defer rc.Close() // ok to call rc.Close on nil rc - return ExecuteWith(rc, args) + return ExecuteWith(ctx, rc, args) } // ExecuteWith invokes the cobra CLI framework, ultimately // resulting in a command being executed. The caller must // invoke rc.Close. -func ExecuteWith(rc *RunContext, args []string) error { +func ExecuteWith(ctx context.Context, rc *RunContext, args []string) error { rc.Log.Debugf("EXECUTE: %s", strings.Join(args, " ")) rc.Log.Debugf("Build: %s %s %s", buildinfo.Version, buildinfo.Commit, buildinfo.Timestamp) rc.Log.Debugf("Config (cfg version %q) from: %s", rc.Config.Version, rc.ConfigStore.Location()) + // NOTE: (Feb 2021): The project has been finally been upgraded + // to spf13/cobra v1.1.3, which does provide support + // for context.Context. However, the sq codebase is still using + // the workaround to smuggle Context to the commands. Presumably + // we'll refactor the code at some point to make use of cobra's + // support for Context. + + ctx = WithRunContext(ctx, rc) + rootCmd := newCommandTree(rc) + var err error // The following is a workaround for the fact that cobra doesn't // currently (as of 2017) support executing the root command with // arbitrary args. That is to say, if you execute: // - // sq arg1 arg2 + // $ sq @sakila_sl3.actor // - // then cobra will look for a command named "arg1", and when it - // doesn't find such a command, it returns an "unknown command" - // error. - cmd, _, err := rootCmd.Find(args[1:]) - if err != nil { - // This err will be the "unknown command" error. - // cobra still returns cmd though. It should be - // the root cmd. - if cmd == nil || cmd.Name() != rootCmd.Name() { - // should never happen - panic(fmt.Sprintf("bad cobra cmd state: %v", cmd)) - } + // then cobra will look for a command named "@sakila_sl3.actor", + // and when it doesn't find such a command, it returns + // an "unknown command" error. + // + // NOTE: This entire mechanism is ancient. Perhaps cobra + // now handles this situation? - // If we have args [sq, arg1, arg2] then we redirect - // to the "slq" command by modifying args to - // look like: [query, arg1, arg2] -- noting that SetArgs - // doesn't want the first args element. - queryCmdArgs := append([]string{"slq"}, args[1:]...) - rootCmd.SetArgs(queryCmdArgs) - } else { - if cmd.Name() == rootCmd.Name() { - // Not sure why we have two paths to this, but it appears - // that we've found the root cmd again, so again - // we redirect to "query" cmd. - - a := append([]string{"slq"}, args[1:]...) - rootCmd.SetArgs(a) + // We need to perform handling for autocomplete + if len(args) > 0 && args[0] == "__complete" { + if hasMatchingChildCommand(rootCmd, args[1]) { + // If there is a matching child command, we let rootCmd + // handle it, as per normal. + rootCmd.SetArgs(args) } else { - // It's just a normal command like "sq ls" or such. + // There's no command matching the first argument to __complete. + // Therefore, we assume that we want to perform completion + // for the "slq" command (which is the pseudo-root command). + effectiveArgs := append([]string{"__complete", "slq"}, args[1:]...) + rootCmd.SetArgs(effectiveArgs) + } + } else { + var cmd *cobra.Command + cmd, _, err = rootCmd.Find(args) + if err != nil { + // This err will be the "unknown command" error. + // cobra still returns cmd though. It should be + // the root cmd. + if cmd == nil || cmd.Name() != rootCmd.Name() { + // Not sure if this can happen anymore? Can prob delete? + panic(fmt.Sprintf("bad cobra cmd state: %v", cmd)) + } - // Explicitly set the args on rootCmd as this makes - // cobra happy when this func is executed via tests. - // Haven't explored the reason why. - rootCmd.SetArgs(args[1:]) + // If we have args [sq, arg1, arg2] then we redirect + // to the "slq" command by modifying args to + // look like: [query, arg1, arg2] -- noting that SetArgs + // doesn't want the first args element. + effectiveArgs := append([]string{"slq"}, args...) + rootCmd.SetArgs(effectiveArgs) + } else { + if cmd.Name() == rootCmd.Name() { + // Not sure why we have two paths to this, but it appears + // that we've found the root cmd again, so again + // we redirect to "slq" cmd. + + a := append([]string{"slq"}, args...) + rootCmd.SetArgs(a) + } else { + // It's just a normal command like "sq ls" or such. + + // Explicitly set the args on rootCmd as this makes + // cobra happy when this func is executed via tests. + // Haven't explored the reason why. + rootCmd.SetArgs(args) + } } } - // Execute the rootCmd; cobra will find the appropriate + // Execute rootCmd; cobra will find the appropriate // sub-command, and ultimately execute that command. - err = rootCmd.Execute() + err = rootCmd.ExecuteContext(ctx) if err != nil { printError(rc, err) } @@ -151,11 +186,17 @@ func ExecuteWith(rc *RunContext, args []string) error { return err } +// cobraMu exists because cobra relies upon package-level +// constructs. This does not sit well with parallel tests. +var cobraMu sync.Mutex + // newCommandTree builds sq's command tree, returning // the root cobra command. func newCommandTree(rc *RunContext) (rootCmd *cobra.Command) { - rootCmd = newRootCmd() + cobraMu.Lock() + defer cobraMu.Unlock() + rootCmd = newRootCmd() rootCmd.SetOut(rc.Out) rootCmd.SetErr(rc.ErrOut) @@ -164,42 +205,52 @@ 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") - helpCmd := addCmd(rc, rootCmd, newHelpCmd) + rootCmd.Flags().SortFlags = false + helpCmd := addCmd(rc, rootCmd, newHelpCmd()) rootCmd.SetHelpCommand(helpCmd) - addCmd(rc, rootCmd, newSLQCmd) - addCmd(rc, rootCmd, newSQLCmd) + addCmd(rc, rootCmd, newSLQCmd()) + addCmd(rc, rootCmd, newSQLCmd()) - addCmd(rc, rootCmd, newSrcCommand) - addCmd(rc, rootCmd, newSrcAddCmd) - addCmd(rc, rootCmd, newSrcListCmd) - addCmd(rc, rootCmd, newSrcRemoveCmd) - addCmd(rc, rootCmd, newScratchCmd) + addCmd(rc, rootCmd, newSrcCommand()) + addCmd(rc, rootCmd, newSrcAddCmd()) + addCmd(rc, rootCmd, newSrcListCmd()) + addCmd(rc, rootCmd, newSrcRemoveCmd()) + addCmd(rc, rootCmd, newScratchCmd()) - addCmd(rc, rootCmd, newInspectCmd) - addCmd(rc, rootCmd, newPingCmd) + addCmd(rc, rootCmd, newInspectCmd()) + addCmd(rc, rootCmd, newPingCmd()) - addCmd(rc, rootCmd, newVersionCmd) - addCmd(rc, rootCmd, newDriversCmd) + addCmd(rc, rootCmd, newVersionCmd()) - tblCmd := addCmd(rc, rootCmd, newTblCmd) - addCmd(rc, tblCmd, newTblCopyCmd) - addCmd(rc, tblCmd, newTblTruncateCmd) - addCmd(rc, tblCmd, newTblDropCmd) + driverCmd := addCmd(rc, rootCmd, newDriverCmd()) + addCmd(rc, driverCmd, newDriverListCmd()) - addCmd(rc, rootCmd, newInstallBashCompletionCmd) - addCmd(rc, rootCmd, newGenerateZshCompletionCmd) + tblCmd := addCmd(rc, rootCmd, newTblCmd()) + addCmd(rc, tblCmd, newTblCopyCmd()) + addCmd(rc, tblCmd, newTblTruncateCmd()) + addCmd(rc, tblCmd, newTblDropCmd()) + + addCmd(rc, rootCmd, newCompletionCmd()) return rootCmd } -// runFunc is an expansion of cobra's RunE func that -// adds a RunContext as the first param. -type runFunc func(rc *RunContext, cmd *cobra.Command, args []string) error +// hasMatchingChildCommand returns true if s is a full or prefix +// match for any of cmd's children. For example, if cmd has +// children [inspect, ls, rm], then "insp" or "ls" would return true. +func hasMatchingChildCommand(cmd *cobra.Command, s string) bool { + for _, child := range cmd.Commands() { + if strings.HasPrefix(child.Name(), s) { + return true + } + } + return false +} // addCmd adds the command returned by cmdFn to parentCmd. -func addCmd(rc *RunContext, parentCmd *cobra.Command, cmdFn func() (*cobra.Command, runFunc)) *cobra.Command { - cmd, fn := cmdFn() +func addCmd(rc *RunContext, parentCmd, cmd *cobra.Command) *cobra.Command { + cmd.Flags().SortFlags = false if cmd.Name() != "help" { // Don't add the --help flag to the help command. @@ -209,18 +260,19 @@ func addCmd(rc *RunContext, parentCmd *cobra.Command, cmdFn func() (*cobra.Comma cmd.PreRunE = func(cmd *cobra.Command, args []string) error { rc.Cmd = cmd rc.Args = args - err := rc.preRunE() + err := rc.init() return err } + runE := cmd.RunE cmd.RunE = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed(flagVersion) { // Bit of a hack: flag --version on any command // results in execVersion being invoked - return execVersion(rc, cmd, args) + return execVersion(cmd, args) } - return fn(rc, cmd, args) + return runE(cmd, args) } // We handle the errors ourselves (rather than let cobra do it) @@ -232,13 +284,26 @@ func addCmd(rc *RunContext, parentCmd *cobra.Command, cmdFn func() (*cobra.Comma return cmd } +type runContextKey struct{} + +// WithRunContext returns ctx with rc added as a value. +func WithRunContext(ctx context.Context, rc *RunContext) context.Context { + if ctx == nil { + ctx = context.Background() + } + + return context.WithValue(ctx, runContextKey{}, rc) +} + +// RunContextFrom extracts the RunContext added to ctx via WithRunContext. +func RunContextFrom(ctx context.Context) *RunContext { + return ctx.Value(runContextKey{}).(*RunContext) +} + // RunContext is a container for injectable resources passed // to all execX funcs. The Close method should be invoked when // the RunContext is no longer needed. type RunContext struct { - // Context is the run's context.Context. - Context context.Context - // Stdin typically is os.Stdin, but can be changed for testing. Stdin *os.File @@ -269,6 +334,9 @@ type RunContext struct { // Log is the run's logger. Log lg.Log + initOnce sync.Once + initErr error + // writers holds the various writer types that // the CLI uses to print output. writers *writers @@ -288,12 +356,11 @@ type RunContext struct { // example if there's a config error). We do this to provide // enough framework so that such an error can be logged or // printed per the normal mechanisms if at all possible. -func newDefaultRunContext(ctx context.Context, stdin *os.File, stdout, stderr io.Writer) (*RunContext, error) { +func newDefaultRunContext(stdin *os.File, stdout, stderr io.Writer) (*RunContext, error) { rc := &RunContext{ - Context: ctx, - Stdin: stdin, - Out: stdout, - ErrOut: stderr, + Stdin: stdin, + Out: stdout, + ErrOut: stderr, } log, clnup, loggingErr := defaultLogging() @@ -325,10 +392,21 @@ func newDefaultRunContext(ctx context.Context, stdin *os.File, stdout, stderr io return rc, nil } -// preRunE is invoked by cobra prior to the command RunE being +// init is invoked by cobra prior to the command RunE being // invoked. It sets up the registry, databases, writers and related -// fundamental components. -func (rc *RunContext) preRunE() error { +// fundamental components. Subsequent invocations of this method +// are no-op. +func (rc *RunContext) init() error { + rc.initOnce.Do(func() { + rc.initErr = rc.doInit() + }) + + return rc.initErr +} + +// doInit performs the actual work of initializing rc. +// It must only be invoked once. +func (rc *RunContext) doInit() error { rc.clnup = cleanup.New() log, cfg := rc.Log, rc.Config diff --git a/cli/cli_test.go b/cli/cli_test.go index 78ebcce8..87bbe217 100644 --- a/cli/cli_test.go +++ b/cli/cli_test.go @@ -2,6 +2,7 @@ package cli_test import ( "bytes" + "context" "fmt" "image/gif" "io/ioutil" @@ -28,27 +29,23 @@ func TestSmoke(t *testing.T) { t.Parallel() // Execute a bunch of smoke test cases. - sqargs := func(a ...string) []string { - return append([]string{"sq"}, a...) - } - testCases := []struct { a []string // errBecause, if non-empty, indicates an error is expected. errBecause string }{ - {a: sqargs("ls")}, - {a: sqargs("ls", "-v")}, - {a: sqargs("ls", "--help")}, - {a: sqargs("inspect"), errBecause: "no active data source"}, - {a: sqargs("inspect", "--help")}, - {a: sqargs("version")}, - {a: sqargs("--version")}, - {a: sqargs("help")}, - {a: sqargs("--help")}, - {a: sqargs("ping", "all")}, - {a: sqargs("ping", "--help")}, - {a: sqargs("ping"), errBecause: "no active data source"}, + {a: []string{"ls"}}, + {a: []string{"ls", "-v"}}, + {a: []string{"ls", "--help"}}, + {a: []string{"inspect"}, errBecause: "no active data source"}, + {a: []string{"inspect", "--help"}}, + {a: []string{"version"}}, + {a: []string{"--version"}}, + {a: []string{"help"}}, + {a: []string{"--help"}}, + {a: []string{"ping", "all"}}, + {a: []string{"ping", "--help"}}, + {a: []string{"ping"}, errBecause: "no active data source"}, } for _, tc := range testCases { @@ -58,7 +55,7 @@ func TestSmoke(t *testing.T) { t.Parallel() rc, out, errOut := newTestRunCtx(testlg.New(t)) - err := cli.ExecuteWith(rc, tc.a) + err := cli.ExecuteWith(context.Background(), rc, tc.a) // We log sq's output before doing assert, because it reads // better in testing's output that way. diff --git a/cli/cmd_add.go b/cli/cmd_add.go index 9d57d820..9ea8d1d4 100644 --- a/cli/cmd_add.go +++ b/cli/cmd_add.go @@ -12,34 +12,34 @@ import ( "github.com/neilotoole/sq/libsq/source" ) -func newSrcAddCmd() (*cobra.Command, runFunc) { +func newSrcAddCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "add [--driver=TYPE] [--handle=@HANDLE] LOCATION", + Use: "add [--driver=TYPE] [--handle=@HANDLE] LOCATION", + RunE: execSrcAdd, Example: ` # add a Postgres source; will have generated handle @sakila_pg - sq add 'postgres://user:pass@localhost/sakila?sslmode=disable' + $ sq add 'postgres://user:pass@localhost/sakila?sslmode=disable' # same as above, but explicitly setting flags - sq add --handle=@sakila_pg --driver=postgres 'postgres://user:pass@localhost/sakila?sslmode=disable' + $ sq add --handle=@sakila_pg --driver=postgres 'postgres://user:pass@localhost/sakila?sslmode=disable' # same as above, but with short flags - sq add -h @sakila_pg --d postgres 'postgres://user:pass@localhost/sakila?sslmode=disable' + $ sq add -h @sakila_pg --d postgres 'postgres://user:pass@localhost/sakila?sslmode=disable' - # add a SQL Server source; will have generated handle @sakila_mssql - sq add 'sqlserver://user:pass@localhost?database=sakila' + # add a SQL Server source; will have generated handle @sakila_mssql or similar + $ sq add 'sqlserver://user:pass@localhost?database=sakila' # add a sqlite db - sq add ./testdata/sqlite1.db + $ sq add ./testdata/sqlite1.db # add an Excel spreadsheet, with options - sq add ./testdata/test1.xlsx --opts=header=true + $ sq add ./testdata/test1.xlsx --opts=header=true # add a CSV source, with options - sq add ./testdata/person.csv --opts='header=true' + $ sq add ./testdata/person.csv --opts=header=true # add a CSV source from a server (will be downloaded) - sq add https://sq.io/testdata/actor.csv + $ sq add https://sq.io/testdata/actor.csv `, - Short: "Add data source", Long: `Add data source specified by LOCATION and optionally identified by @HANDLE. The format of LOCATION varies, but is generally a DB connection string, a file path, or a URL. @@ -53,35 +53,41 @@ on LOCATION and the source driver type. If flag --driver is omitted, sq will attempt to determine the type from LOCATION via file suffix, content type, etc.. If the result -is ambiguous, specify the driver tye type via flag --driver. +is ambiguous, specify the driver type via flag --driver. -Flag --opts sets source specific options. Generally opts are relevant +Flag --opts sets source-specific options. Generally opts are relevant to document source types (such as a CSV file). The most common use is to specify that the document has a header row: - sq add actor.csv --opts=header=true + $ sq add actor.csv --opts=header=true -Available source driver types can be listed via "sq drivers". +Available source driver types can be listed via "sq driver ls". At a minimum, the following drivers are bundled: - sqlite3 SQLite3 - postgres Postgres - sqlserver Microsoft SQL Server - xlsx Microsoft Excel XLSX - mysql MySQL - csv Comma-Separated Values - tsv Tab-Separated Values + sqlite3 SQLite + postgres PostgreSQL + sqlserver Microsoft SQL Server + mysql MySQL + csv Comma-Separated Values + tsv Tab-Separated Values + json JSON + jsona JSON Array: LF-delimited JSON arrays + jsonl JSON Lines: LF-delimited JSON objects + xlsx Microsoft Excel XLSX `, + Short: "Add data source", } cmd.Flags().StringP(flagDriver, flagDriverShort, "", flagDriverUsage) + _ = cmd.RegisterFlagCompletionFunc(flagDriver, completeDriverType) cmd.Flags().StringP(flagSrcOptions, "", "", flagSrcOptionsUsage) cmd.Flags().StringP(flagHandle, flagHandleShort, "", flagHandleUsage) - return cmd, execSrcAdd + return cmd } -func execSrcAdd(rc *RunContext, cmd *cobra.Command, args []string) error { +func execSrcAdd(cmd *cobra.Command, args []string) error { + rc := RunContextFrom(cmd.Context()) if len(args) != 1 { return errz.Errorf(msgInvalidArgs) } @@ -94,9 +100,8 @@ func execSrcAdd(rc *RunContext, cmd *cobra.Command, args []string) error { if cmd.Flags().Changed(flagDriver) { val, _ := cmd.Flags().GetString(flagDriver) typ = source.Type(strings.TrimSpace(val)) - } else { - typ, err = rc.files.Type(rc.Context, loc) + typ, err = rc.files.Type(cmd.Context(), loc) if err != nil { return err } @@ -148,8 +153,8 @@ func execSrcAdd(rc *RunContext, cmd *cobra.Command, args []string) error { // unlike the other SQL DBs sq supports so far. // Both of these forms are allowed: // - // sq add sqlite3:///path/to/sakila.db - // sq add /path/to/sakila.db + // $ sq add sqlite3:///path/to/sakila.db + // $ sq add /path/to/sakila.db // // The second form is particularly nice for bash completion etc. if typ == sqlite3.Type { @@ -182,7 +187,7 @@ func execSrcAdd(rc *RunContext, cmd *cobra.Command, args []string) error { } // TODO: should we really be pinging this src right now? - err = drvr.Ping(rc.Context, src) + err = drvr.Ping(cmd.Context(), src) if err != nil { return errz.Wrapf(err, "failed to ping %s [%s]", src.Handle, src.RedactedLocation()) } diff --git a/cli/cmd_completion.go b/cli/cmd_completion.go index deb447e1..7d13d51b 100644 --- a/cli/cmd_completion.go +++ b/cli/cmd_completion.go @@ -1,109 +1,75 @@ package cli import ( - "os" - "runtime" - "github.com/spf13/cobra" + + "github.com/neilotoole/sq/libsq/core/errz" ) -const bashCompletionFunc = ` - -__sq_list_sources() -{ - local sq_output out - if sq_output=$(sq ls 2>/dev/null); then - out=($(echo "${sq_output}" | awk 'NR > 1 {print $1}')) - COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) ) - fi -} - -__sq_get_resource() -{ - if [[ ${#nouns[@]} -eq 0 ]]; then - return 1 - fi - __sq_list_sources ${nouns[${#nouns[@]} -1]} - if [[ $? -eq 0 ]]; then - return 0 - fi -} - -__custom_func() { - case ${last_command} in - sq_ls | sq_src | sq_rm | sq_inspect ) - __sq_list_sources - return - ;; - *) - ;; - esac -} -` - -func newInstallBashCompletionCmd() (*cobra.Command, runFunc) { +func newCompletionCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "install-bash-completion", - Short: "Install bash completion script on Unix-ish systems.", - Hidden: true, + Use: "completion [bash|zsh|fish|powershell]", + Short: "Generate completion script", + RunE: execCompletion, + Long: `To load completions: + +Bash: + +$ source <(sq completion bash) + +# To load completions for each session, execute once: +Linux: + $ sq completion bash > /etc/bash_completion.d/sq +MacOS: + $ sq completion bash > /usr/local/etc/bash_completion.d/sq + +Zsh: + +# If shell completion is not already enabled in your environment you will need +# to enable it. You can execute the following once: + +$ echo "autoload -U compinit; compinit" >> ~/.zshrc + +# To load completions for each session, execute once: +$ sq completion zsh > "${fpath[1]}/_sq" + +# You will need to start a new shell for this setup to take effect. + +Fish: + +$ sq completion fish | source + +# To load completions for each session, execute once: +$ sq completion fish > ~/.config/fish/completions/sq.fish + +Powershell: + +PS> sq completion powershell | Out-String | Invoke-Expression + +# To load completions for every new session, run: +PS> sq completion powershell > sq.ps1 +# and source this file from your powershell profile. +`, + DisableFlagsInUseLine: true, + ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, + Args: cobra.ExactValidArgs(1), } - return cmd, execInstallBashCompletion + return cmd } -func execInstallBashCompletion(rc *RunContext, cmd *cobra.Command, args []string) error { - log := rc.Log - var path string - - switch runtime.GOOS { - case "windows": - log.Warnf("skipping install bash completion on windows") - return nil - case "darwin": - path = "/usr/local/etc/bash_completion.d/sq" +func execCompletion(cmd *cobra.Command, args []string) error { + rc := RunContextFrom(cmd.Context()) + switch args[0] { + case "bash": + return cmd.Root().GenBashCompletion(rc.Out) + case "zsh": + return cmd.Root().GenZshCompletion(rc.Out) + case "fish": + return cmd.Root().GenFishCompletion(rc.Out, true) + case "powershell": + return cmd.Root().GenPowerShellCompletion(rc.Out) default: - // it's unixish - path = " /etc/bash_completion.d/sq" + return errz.Errorf("invalid arg: %s", args[0]) } - - // TODO: only write if necessary (check for version/timestamp/checksum) - err := cmd.Root().GenBashCompletionFile(path) - if err != nil { - log.Warnf("failed to write bash completion to %q: %v", path, err) - return err - } - - return nil -} - -func newGenerateZshCompletionCmd() (*cobra.Command, runFunc) { - cmd := &cobra.Command{ - Use: "gen-zsh-completion", - Short: "Generate zsh completion script on Unix-ish systems.", - Hidden: true, - } - return cmd, execGenerateZshCompletion -} - -func execGenerateZshCompletion(rc *RunContext, cmd *cobra.Command, args []string) error { - log := rc.Log - var path string - - switch runtime.GOOS { - case "windows": - log.Warnf("skipping install zsh completion on windows") - return nil - case "darwin": - path = "/usr/local/etc/bash_completion.d/sq" - default: - // it's unixish - path = " /etc/bash_completion.d/sq" - } - - err := cmd.Root().GenZshCompletion(os.Stdout) - if err != nil { - log.Warnf("failed to write zsh completion to %q: %v", path, err) - return err - } - return nil } diff --git a/cli/cmd_driver.go b/cli/cmd_driver.go new file mode 100644 index 00000000..d229cdad --- /dev/null +++ b/cli/cmd_driver.go @@ -0,0 +1,46 @@ +package cli + +import ( + "github.com/spf13/cobra" +) + +func newDriverCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "driver", + Short: "List or manage drivers", + RunE: func(cmd *cobra.Command, args []string) error { + return cmd.Help() + }, + + Example: ` # List drivers + $ sq driver ls + + # Install User Driver [TBD] + $ sq driver install ./rss.sq.yml +`, + } + + return cmd +} + +func newDriverListCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "ls", + Short: "List available drivers", + Args: cobra.ExactArgs(0), + RunE: execDriverList, + } + + cmd.Flags().BoolP(flagJSON, flagJSONShort, false, flagJSONUsage) + cmd.Flags().BoolP(flagTable, flagTableShort, false, flagTableUsage) + cmd.Flags().BoolP(flagHeader, flagHeaderShort, false, flagHeaderUsage) + cmd.Flags().BoolP(flagMonochrome, flagMonochromeShort, false, flagMonochromeUsage) + + return cmd +} + +func execDriverList(cmd *cobra.Command, args []string) error { + rc := RunContextFrom(cmd.Context()) + drvrs := rc.registry.DriversMetadata() + return rc.writers.metaw.DriverMetadata(drvrs) +} diff --git a/cli/cmd_drivers.go b/cli/cmd_drivers.go deleted file mode 100644 index cbfbc50e..00000000 --- a/cli/cmd_drivers.go +++ /dev/null @@ -1,30 +0,0 @@ -package cli - -import ( - "github.com/spf13/cobra" - - "github.com/neilotoole/sq/libsq/core/errz" -) - -func newDriversCmd() (*cobra.Command, runFunc) { - cmd := &cobra.Command{ - Use: "drivers", - Short: "List available drivers", - } - - cmd.Flags().BoolP(flagJSON, flagJSONShort, false, flagJSONUsage) - cmd.Flags().BoolP(flagTable, flagTableShort, false, flagTableUsage) - cmd.Flags().BoolP(flagHeader, flagHeaderShort, false, flagHeaderUsage) - cmd.Flags().BoolP(flagMonochrome, flagMonochromeShort, false, flagMonochromeUsage) - - return cmd, execDrivers -} - -func execDrivers(rc *RunContext, cmd *cobra.Command, args []string) error { - if len(args) > 0 { - return errz.Errorf("invalid arguments: zero arguments expected") - } - - drvrs := rc.registry.DriversMetadata() - return rc.writers.metaw.DriverMetadata(drvrs) -} diff --git a/cli/cmd_help.go b/cli/cmd_help.go index 9777d078..f5a22f66 100644 --- a/cli/cmd_help.go +++ b/cli/cmd_help.go @@ -2,16 +2,17 @@ package cli import "github.com/spf13/cobra" -func newHelpCmd() (*cobra.Command, runFunc) { +func newHelpCmd() *cobra.Command { cmd := &cobra.Command{ Use: "help", Short: "Show sq help", Hidden: true, + RunE: execHelp, } - return cmd, execHelp + return cmd } -func execHelp(rc *RunContext, cmd *cobra.Command, args []string) error { +func execHelp(cmd *cobra.Command, args []string) error { return cmd.Root().Help() } diff --git a/cli/cmd_inspect.go b/cli/cmd_inspect.go index ae7e9bf5..6be66b41 100644 --- a/cli/cmd_inspect.go +++ b/cli/cmd_inspect.go @@ -7,40 +7,44 @@ import ( "github.com/neilotoole/sq/libsq/source" ) -func newInspectCmd() (*cobra.Command, runFunc) { +func newInspectCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "inspect [@HANDLE|@HANDLE.TABLE|.TABLE]", - Example: ` # inspect active data source - sq inspect - - # inspect @pg1 data source - sq inspect @pg1 - - # inspect 'tbluser' in @pg1 data source - sq inspect @pg1.tbluser - - # inspect 'tbluser' in active data source - sq inspect .tbluser - - # inspect piped data - cat data.xlsx | sq inspect`, + Use: "inspect [@HANDLE|@HANDLE.TABLE|.TABLE]", + Args: cobra.MaximumNArgs(1), + ValidArgsFunction: (&handleTableCompleter{ + max: 1, + }).complete, + 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. If @HANDLE is not provided, the active data source is assumed.`, + Example: ` # inspect active data source + $ sq inspect + + # inspect @pg1 data source + $ sq inspect @pg1 + + # inspect 'actor' in @pg1 data source + $ sq inspect @pg1.actor + + # inspect 'actor' in active data source + $ sq inspect .actor + + # inspect piped data + $ cat data.xlsx | sq inspect`, } cmd.Flags().BoolP(flagJSON, flagJSONShort, false, flagJSONUsage) cmd.Flags().BoolP(flagTable, flagTableShort, false, flagTableUsage) cmd.Flags().Bool(flagInspectFull, false, flagInspectFullUsage) - return cmd, execInspect + return cmd } -func execInspect(rc *RunContext, cmd *cobra.Command, args []string) error { - if len(args) > 1 { - return errz.Errorf("too many arguments") - } +func execInspect(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + rc := RunContextFrom(ctx) srcs := rc.Config.Sources @@ -56,7 +60,7 @@ func execInspect(rc *RunContext, cmd *cobra.Command, args []string) error { // - We're inspecting the active src // check if there's input on stdin - src, err = checkStdinSource(rc) + src, err = checkStdinSource(ctx, rc) if err != nil { return err } @@ -107,15 +111,14 @@ func execInspect(rc *RunContext, cmd *cobra.Command, args []string) error { } } - dbase, err := rc.databases.Open(rc.Context, src) + dbase, err := rc.databases.Open(ctx, src) if err != nil { return errz.Wrapf(err, "failed to inspect %s", src.Handle) } - //defer rc.Log.WarnIfCloseError(dbase) if table != "" { var tblMeta *source.TableMetadata - tblMeta, err = dbase.TableMetadata(rc.Context, table) + tblMeta, err = dbase.TableMetadata(ctx, table) if err != nil { return err } @@ -123,7 +126,7 @@ func execInspect(rc *RunContext, cmd *cobra.Command, args []string) error { return rc.writers.metaw.TableMetadata(tblMeta) } - meta, err := dbase.SourceMetadata(rc.Context) + meta, err := dbase.SourceMetadata(ctx) if err != nil { return errz.Wrapf(err, "failed to read %s source metadata", src.Handle) } diff --git a/cli/cmd_list.go b/cli/cmd_list.go index 99652b60..25c4df07 100644 --- a/cli/cmd_list.go +++ b/cli/cmd_list.go @@ -2,25 +2,23 @@ package cli import ( "github.com/spf13/cobra" - - "github.com/neilotoole/sq/libsq/core/errz" ) -func newSrcListCmd() (*cobra.Command, runFunc) { +func newSrcListCmd() *cobra.Command { cmd := &cobra.Command{ Use: "ls", Short: "List data sources", + Args: cobra.ExactArgs(0), + RunE: execSrcList, } cmd.Flags().BoolP(flagVerbose, flagVerboseShort, false, flagVerboseUsage) cmd.Flags().BoolP(flagHeader, flagHeaderShort, false, flagHeaderUsage) - return cmd, execSrcList + return cmd } -func execSrcList(rc *RunContext, cmd *cobra.Command, args []string) error { - if len(args) != 0 { - return errz.Errorf(msgInvalidArgs) - } +func execSrcList(cmd *cobra.Command, args []string) error { + rc := RunContextFrom(cmd.Context()) return rc.writers.srcw.SourceSet(rc.Config.Sources) } diff --git a/cli/cmd_ping.go b/cli/cmd_ping.go index e5ab527e..d044038a 100644 --- a/cli/cmd_ping.go +++ b/cli/cmd_ping.go @@ -14,27 +14,41 @@ import ( "github.com/neilotoole/sq/libsq/source" ) -func newPingCmd() (*cobra.Command, runFunc) { +func newPingCmd() *cobra.Command { + argsFn := func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + // Suggestions are: handles, plus the string "all". + rc := RunContextFrom(cmd.Context()) + suggestions := append([]string{"all"}, rc.Config.Sources.Handles()...) + + return suggestions, cobra.ShellCompDirectiveNoFileComp + } + cmd := &cobra.Command{ - Use: "ping [@HANDLE|all]", + Use: "ping [all|@HANDLE [@HANDLE_N]]", + //Args: cobra.MaximumNArgs(1), + RunE: execPing, + ValidArgsFunction: argsFn, + + Short: "Ping data sources", + Long: `Ping data sources to check connection health. If no arguments provided, the +active data source is pinged. Provide the handles of one or more sources +to ping those sources, or "all" to ping all sources. + +The exit code is 1 if ping fails for any of the sources.`, Example: ` # ping active data source - sq ping + $ sq ping # ping all data sources - sq ping all + $ sq ping all + + # ping @my1 and @pg1 + $ sq ping @my1 @pg1 # ping @my1 with 2s timeout - sq ping @my1 --timeout=2s - - # ping @my1 and @pg1 - sq ping @my1 @pg1 + $ sq ping @my1 --timeout=2s # output in TSV format - sq ping --tsv @my1`, - - Short: "Check data source connection health", - Long: `Ping data sources to check connection health. If no arguments provided, the -active data source is pinged. The exit code is 1 if ping fails for any of the sources.`, + $ sq ping --tsv @my1`, } cmd.Flags().BoolP(flagTable, flagTableShort, false, flagTableUsage) @@ -42,10 +56,11 @@ active data source is pinged. The exit code is 1 if ping fails for any of the so cmd.Flags().BoolP(flagTSV, flagTSVShort, false, flagTSVUsage) cmd.Flags().Duration(flagTimeout, time.Second*10, flagTimeoutPingUsage) - return cmd, execPing + return cmd } -func execPing(rc *RunContext, cmd *cobra.Command, args []string) error { +func execPing(cmd *cobra.Command, args []string) error { + rc := RunContextFrom(cmd.Context()) cfg := rc.Config var srcs []*source.Source var gotAll bool @@ -93,19 +108,23 @@ func execPing(rc *RunContext, cmd *cobra.Command, args []string) error { } } - timeout := cfg.Defaults.Timeout + timeout := cfg.Defaults.PingTimeout if cmdFlagChanged(cmd, flagTimeout) { timeout, _ = cmd.Flags().GetDuration(flagTimeout) } rc.Log.Debugf("Using timeout value: %s", timeout) - return pingSources(rc.Context, rc.Log, rc.registry, srcs, rc.writers.pingw, timeout) + return pingSources(cmd.Context(), rc.Log, rc.registry, srcs, rc.writers.pingw, timeout) } // pingSources pings each of the sources in srcs, and prints results // to w. If any error occurs pinging any of srcs, that error is printed // inline as part of the ping results, and an errNoMsg is returned. +// +// NOTE: This ping code has an ancient lineage, in that it was written +// originally laid down before context.Context was a thing. Thus, +// the entire thing could probably be rewritten for simplicity. func pingSources(ctx context.Context, log lg.Log, dp driver.Provider, srcs []*source.Source, w output.PingWriter, timeout time.Duration) error { w.Open(srcs) defer log.WarnIfFuncError(w.Close) diff --git a/cli/cmd_remove.go b/cli/cmd_remove.go index 039ee8f5..240ce883 100644 --- a/cli/cmd_remove.go +++ b/cli/cmd_remove.go @@ -4,26 +4,23 @@ import ( "fmt" "github.com/spf13/cobra" - - "github.com/neilotoole/sq/libsq/core/errz" ) -func newSrcRemoveCmd() (*cobra.Command, runFunc) { +func newSrcRemoveCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "rm @HANDLE", - Example: ` sq rm @my1`, - Aliases: []string{"remove"}, - Short: "Remove data source", + Use: "rm @HANDLE", + Example: ` $ sq rm @my1`, + Short: "Remove data source", + Args: cobra.ExactArgs(1), + RunE: execSrcRemove, + ValidArgsFunction: completeHandle(1), } - return cmd, execSrcRemove + return cmd } -func execSrcRemove(rc *RunContext, cmd *cobra.Command, args []string) error { - if len(args) != 1 { - return errz.Errorf(msgInvalidArgs) - } - +func execSrcRemove(cmd *cobra.Command, args []string) error { + rc := RunContextFrom(cmd.Context()) cfg := rc.Config src, err := cfg.Sources.Get(args[0]) if err != nil { diff --git a/cli/cmd_root.go b/cli/cmd_root.go index 212a01e5..28884141 100644 --- a/cli/cmd_root.go +++ b/cli/cmd_root.go @@ -19,66 +19,67 @@ output to a database table. You can query using sq's own jq-like syntax, or in native SQL. +Execute "sq completion --help" for how to install shell completion. + More at https://sq.io `, Example: ` # pipe an Excel file and output the first 10 rows from sheet1 - cat data.xlsx | sq '.sheet1 | .[0:10]' + $ cat data.xlsx | sq '.sheet1 | .[0:10]' # add Postgres source identified by handle @sakila_pg - sq add --handle=@sakila_pg 'postgres://user:pass@localhost:5432/sakila?sslmode=disable' + $ sq add --handle=@sakila_pg 'postgres://user:pass@localhost:5432/sakila?sslmode=disable' # add SQL Server source; will have generated handle @sakila_mssql - sq add 'sqlserver://user:pass@localhost?database=sakila' + $ sq add 'sqlserver://user:pass@localhost?database=sakila' # list available data sources - sq ls + $ sq ls # ping all data sources - sq ping all + $ sq ping all # set active data source - sq src @sakila_pg + $ sq src @sakila_pg # get specified cols from table address in active data source - sq '.address | .address_id, .city, .country' + $ sq '.address | .address_id, .city, .country' # get metadata (schema, stats etc) for data source - sq inspect @sakila_pg + $ sq inspect @sakila_pg # get metadata for a table - sq inspect @pg1.person + $ sq inspect @pg1.person # output in JSON - sq -j '.person | .uid, .username, .email' + $ sq -j '.person | .uid, .username, .email' # output in table format (with header) - sq -th '.person | .uid, .username, .email' + $ sq -th '.person | .uid, .username, .email' # output in table format (no header) - sq -t '.person | .uid, .username, .email' + $ sq -t '.person | .uid, .username, .email' # output to a HTML file - sq --html '@sakila_sl3.actor' -o actor.html + $ sq --html '@sakila_sl3.actor' -o actor.html # join across data sources - sq '@my1.person, @pg1.address | join(.uid) | .username, .email, .city' + $ sq '@my1.person, @pg1.address | join(.uid) | .username, .email, .city' # insert query results into a table in another data source - sq --insert=@pg1.person '@my1.person | .username, .email' + $ sq --insert=@pg1.person '@my1.person | .username, .email' # execute a database-native SQL query, specifying the source - sq sql --src=@pg1 'SELECT uid, username, email FROM person LIMIT 2' + $ sq sql --src=@pg1 'SELECT uid, username, email FROM person LIMIT 2' # copy a table (in the same source) - sq tbl copy @sakila_sl3.actor .actor2 + $ sq tbl copy @sakila_sl3.actor .actor2 # truncate tables - sq tbl truncate @sakila_sl3.actor2 + $ sq tbl truncate @sakila_sl3.actor2 # drop table - sq tbl drop @sakila_sl3.actor2 + $ sq tbl drop @sakila_sl3.actor2 `, - BashCompletionFunction: bashCompletionFunc, } addQueryCmdFlags(cmd) diff --git a/cli/cmd_scratch.go b/cli/cmd_scratch.go index 383fc9e8..722a9b8c 100644 --- a/cli/cmd_scratch.go +++ b/cli/cmd_scratch.go @@ -2,7 +2,6 @@ package cli import ( "github.com/neilotoole/sq/drivers/sqlite3" - "github.com/neilotoole/sq/libsq/core/errz" "github.com/neilotoole/sq/libsq/source" "github.com/spf13/cobra" @@ -10,24 +9,26 @@ import ( // TODO: dump all this "internal" stuff: make the options as follows: @HANDLE, file, memory -func newScratchCmd() (*cobra.Command, runFunc) { +func newScratchCmd() *cobra.Command { cmd := &cobra.Command{ Use: "scratch [@HANDLE|internal|internal:file|internal:mem|@scratch]", // This command is likely to be ditched in favor of a generalized "config" cmd // such as "sq config scratchdb=@my1" Hidden: true, + Args: cobra.ExactArgs(1), + RunE: execScratch, Example: ` # get scratch data source - sq scratch + $ sq scratch # set @my1 as scratch data source - sq scratch @my1 + $ sq scratch @my1 # use the default embedded db - sq scratch internal + $ sq scratch internal # explicitly specify use of embedded file db - sq scratch internal:file + $ sq scratch internal:file # explicitly specify use of embedded memory db - sq scratch internal:mem + $ sq scratch internal:mem # restore default scratch db (equivalent to "internal") - sq scratch @scratch`, + $ sq scratch @scratch`, Short: "Get or set scratch data source", Long: `Get or set scratch data source. The scratch db is used internally by sq for multiple purposes such as importing non-SQL data, or cross-database joins. If no argument provided, get the current scratch data @@ -35,14 +36,11 @@ source. Otherwise, set @HANDLE or an internal db as the scratch data source. The `, } - return cmd, execScratch + return cmd } -func execScratch(rc *RunContext, cmd *cobra.Command, args []string) error { - if len(args) > 1 { - return errz.Errorf(msgInvalidArgs) - } - +func execScratch(cmd *cobra.Command, args []string) error { + rc := RunContextFrom(cmd.Context()) cfg := rc.Config var src *source.Source diff --git a/cli/cmd_slq.go b/cli/cmd_slq.go index 841db01b..2bb7d01c 100644 --- a/cli/cmd_slq.go +++ b/cli/cmd_slq.go @@ -15,24 +15,28 @@ import ( "github.com/neilotoole/sq/libsq/source" ) -func newSLQCmd() (*cobra.Command, runFunc) { +func newSLQCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "slq", - Short: "Execute SLQ query", - Hidden: false, + Use: "slq", + Short: "Execute SLQ query", + Hidden: true, + Args: cobra.MaximumNArgs(1), + RunE: execSLQ, + ValidArgsFunction: completeSLQ, } addQueryCmdFlags(cmd) cmd.Flags().Bool(flagVersion, false, flagVersionUsage) - return cmd, execSLQ + return cmd } -func execSLQ(rc *RunContext, cmd *cobra.Command, args []string) error { +func execSLQ(cmd *cobra.Command, args []string) error { + rc := RunContextFrom(cmd.Context()) srcs := rc.Config.Sources // check if there's input on stdin - src, err := checkStdinSource(rc) + src, err := checkStdinSource(cmd.Context(), rc) if err != nil { return err } @@ -65,7 +69,7 @@ func execSLQ(rc *RunContext, cmd *cobra.Command, args []string) error { if !cmdFlagChanged(cmd, flagInsert) { // The user didn't specify the --insert=@src.tbl flag, // so we just want to print the records. - return execSLQPrint(rc) + return execSLQPrint(cmd.Context(), rc) } // Instead of printing the records, they will be @@ -85,19 +89,19 @@ func execSLQ(rc *RunContext, cmd *cobra.Command, args []string) error { return err } - return execSLQInsert(rc, destSrc, destTbl) + return execSLQInsert(cmd.Context(), rc, destSrc, destTbl) } // execSQLInsert executes the SLQ and inserts resulting records // into destTbl in destSrc. -func execSLQInsert(rc *RunContext, destSrc *source.Source, destTbl string) error { +func execSLQInsert(ctx context.Context, rc *RunContext, destSrc *source.Source, destTbl string) error { args, srcs, dbases := rc.Args, rc.Config.Sources, rc.databases - slq, err := preprocessUserSLQ(rc, args) + slq, err := preprocessUserSLQ(ctx, rc, args) if err != nil { return err } - ctx, cancelFn := context.WithCancel(rc.Context) + ctx, cancelFn := context.WithCancel(ctx) defer cancelFn() destDB, err := dbases.Open(ctx, destSrc) @@ -132,14 +136,14 @@ func execSLQInsert(rc *RunContext, destSrc *source.Source, destTbl string) error } // execSLQPrint executes the SLQ query, and prints output to writer. -func execSLQPrint(rc *RunContext) error { - slq, err := preprocessUserSLQ(rc, rc.Args) +func execSLQPrint(ctx context.Context, rc *RunContext) error { + slq, err := preprocessUserSLQ(ctx, rc, rc.Args) if err != nil { return err } recw := output.NewRecordWriterAdapter(rc.writers.recordw) - execErr := libsq.ExecuteSLQ(rc.Context, rc.Log, rc.databases, rc.databases, rc.Config.Sources, slq, recw) + execErr := libsq.ExecuteSLQ(ctx, rc.Log, rc.databases, rc.databases, rc.Config.Sources, slq, recw) _, waitErr := recw.Wait() if execErr != nil { return execErr @@ -171,8 +175,8 @@ func execSLQPrint(rc *RunContext) error { // to the query. This allows a query where the first selector // segment is the table name. // -// $ sq '.person' --> sq '@active.person' -func preprocessUserSLQ(rc *RunContext, args []string) (string, error) { +// $ sq '.person' --> $ sq '@active.person' +func preprocessUserSLQ(ctx context.Context, rc *RunContext, args []string) (string, error) { log, reg, dbases, srcs := rc.Log, rc.registry, rc.databases, rc.Config.Sources activeSrc := srcs.Active() @@ -204,13 +208,13 @@ func preprocessUserSLQ(rc *RunContext, args []string) (string, error) { // This isn't a monotable src, so we can't // just select @stdin.data. Instead we'll select // the first table name, as found in the source meta. - dbase, err := dbases.Open(rc.Context, activeSrc) + dbase, err := dbases.Open(ctx, activeSrc) if err != nil { return "", err } defer log.WarnIfCloseError(dbase) - srcMeta, err := dbase.SourceMetadata(rc.Context) + srcMeta, err := dbase.SourceMetadata(ctx) if err != nil { return "", err } @@ -299,9 +303,14 @@ func addQueryCmdFlags(cmd *cobra.Command) { cmd.Flags().BoolP(flagPretty, "", true, flagPrettyUsage) cmd.Flags().StringP(flagInsert, "", "", flagInsertUsage) + _ = cmd.RegisterFlagCompletionFunc(flagInsert, (&handleTableCompleter{onlySQL: true, handleRequired: true}).complete) + cmd.Flags().StringP(flagActiveSrc, "", "", flagActiveSrcUsage) + _ = cmd.RegisterFlagCompletionFunc(flagActiveSrc, completeHandle(0)) // The driver flag can be used if data is piped to sq over stdin cmd.Flags().StringP(flagDriver, "", "", flagQueryDriverUsage) + _ = cmd.RegisterFlagCompletionFunc(flagDriver, completeDriverType) + cmd.Flags().StringP(flagSrcOptions, "", "", flagQuerySrcOptionsUsage) } diff --git a/cli/cmd_sql.go b/cli/cmd_sql.go index d2ebc455..83a122dd 100644 --- a/cli/cmd_sql.go +++ b/cli/cmd_sql.go @@ -16,7 +16,7 @@ import ( "github.com/neilotoole/sq/libsq/core/stringz" ) -func newSQLCmd() (*cobra.Command, runFunc) { +func newSQLCmd() *cobra.Command { cmd := &cobra.Command{ Use: "sql QUERY|STMT", Short: "Execute DB-native SQL query or statement", @@ -28,17 +28,18 @@ If flag --query is set, sq will run the input as a query (SELECT) and return the query rows. If flag --exec is set, sq will execute the input and return the result. If neither flag is set, sq attempts to determine the appropriate mode.`, + RunE: execSQL, Example: ` # Select from active source - sq sql 'SELECT * FROM actor' + $ sq sql 'SELECT * FROM actor' # Select from a specified source - sq sql --src=@sakila_pg12 'SELECT * FROM actor' + $ sq sql --src=@sakila_pg12 'SELECT * FROM actor' # Drop table @sakila_pg12.actor - sq sql --exec --src=@sakila_pg12 'DROP TABLE actor' + $ sq sql --exec --src=@sakila_pg12 'DROP TABLE actor' # Select from active source and write results to @sakila_ms17.actor - sq sql 'SELECT * FROM actor' --insert=@sakila_ms17.actor`, + $ sq sql 'SELECT * FROM actor' --insert=@sakila_ms17.actor`, } addQueryCmdFlags(cmd) @@ -48,10 +49,11 @@ flag is set, sq attempts to determine the appropriate mode.`, // User explicitly wants to execute the SQL using sql.DB.Exec cmd.Flags().Bool(flagSQLExec, false, flagSQLExecUsage) - return cmd, execSQL + return cmd } -func execSQL(rc *RunContext, cmd *cobra.Command, args []string) error { +func execSQL(cmd *cobra.Command, args []string) error { + rc := RunContextFrom(cmd.Context()) switch len(args) { default: // FIXME: we should allow multiple args and concat them @@ -64,7 +66,7 @@ func execSQL(rc *RunContext, cmd *cobra.Command, args []string) error { } } - err := determineSources(rc) + err := determineSources(cmd.Context(), rc) if err != nil { return err } @@ -77,7 +79,7 @@ func execSQL(rc *RunContext, cmd *cobra.Command, args []string) error { if !cmdFlagChanged(cmd, flagInsert) { // The user didn't specify the --insert=@src.tbl flag, // so we just want to print the records. - return execSQLPrint(rc, activeSrc) + return execSQLPrint(cmd.Context(), rc, activeSrc) } // Instead of printing the records, they will be @@ -97,20 +99,20 @@ func execSQL(rc *RunContext, cmd *cobra.Command, args []string) error { return err } - return execSQLInsert(rc, activeSrc, destSrc, destTbl) + return execSQLInsert(cmd.Context(), rc, activeSrc, destSrc, destTbl) } // execSQLPrint executes the SQL and prints resulting records // to the configured writer. -func execSQLPrint(rc *RunContext, fromSrc *source.Source) error { +func execSQLPrint(ctx context.Context, rc *RunContext, fromSrc *source.Source) error { args := rc.Args - dbase, err := rc.databases.Open(rc.Context, fromSrc) + dbase, err := rc.databases.Open(ctx, fromSrc) if err != nil { return err } recw := output.NewRecordWriterAdapter(rc.writers.recordw) - err = libsq.QuerySQL(rc.Context, rc.Log, dbase, recw, args[0]) + err = libsq.QuerySQL(ctx, rc.Log, dbase, recw, args[0]) if err != nil { return err } @@ -120,10 +122,10 @@ func execSQLPrint(rc *RunContext, fromSrc *source.Source) error { // execSQLInsert executes the SQL and inserts resulting records // into destTbl in destSrc. -func execSQLInsert(rc *RunContext, fromSrc, destSrc *source.Source, destTbl string) error { +func execSQLInsert(ctx context.Context, rc *RunContext, fromSrc, destSrc *source.Source, destTbl string) error { args := rc.Args dbases := rc.databases - ctx, cancelFn := context.WithCancel(rc.Context) + ctx, cancelFn := context.WithCancel(ctx) defer cancelFn() fromDB, err := dbases.Open(ctx, fromSrc) diff --git a/cli/cmd_src.go b/cli/cmd_src.go index 79027ccd..90fa1ea6 100644 --- a/cli/cmd_src.go +++ b/cli/cmd_src.go @@ -2,35 +2,29 @@ package cli import ( "github.com/spf13/cobra" - - "github.com/neilotoole/sq/libsq/core/errz" ) -func newSrcCommand() (*cobra.Command, runFunc) { +func newSrcCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "src [@HANDLE]", + Use: "src [@HANDLE]", + RunE: execSrc, Example: ` # get active data source - sq src + $ sq src + # set @my1 as active data source - sq src @my1`, - // RunE: execSrc, - Short: "Get or set active data source", + $ sq src @my1`, + Args: cobra.MaximumNArgs(1), + ValidArgsFunction: completeHandle(1), + Short: "Get or set active data source", Long: `Get or set active data source. If no argument provided, get the active data source. Otherwise, set @HANDLE as the active data source.`, } - //cmd.Flags().BoolP(flagJSON, flagJSONShort, false, flagJSONUsage) - //cmd.Flags().BoolP(flagTable, flagTableShort, false, flagTableUsage) - //cmd.Flags().BoolP(flagHeader, flagHeaderShort, false, flagHeaderUsage) - - return cmd, execSrc + return cmd } -func execSrc(rc *RunContext, cmd *cobra.Command, args []string) error { - if len(args) > 1 { - return errz.Errorf(msgInvalidArgs) - } - +func execSrc(cmd *cobra.Command, args []string) error { + rc := RunContextFrom(cmd.Context()) cfg := rc.Config if len(args) == 0 { diff --git a/cli/cmd_tbl.go b/cli/cmd_tbl.go index bc4e4fc3..caac426f 100644 --- a/cli/cmd_tbl.go +++ b/cli/cmd_tbl.go @@ -11,52 +11,55 @@ import ( "github.com/neilotoole/sq/libsq/source" ) -func newTblCmd() (*cobra.Command, runFunc) { +func newTblCmd() *cobra.Command { cmd := &cobra.Command{ Use: "tbl", Short: "Common actions on tables (copy, truncate, drop)", - + RunE: func(cmd *cobra.Command, args []string) error { + return cmd.Help() + }, Example: ` # Copy table actor to new table actor2 - sq tbl copy @sakila_sl3.actor actor2 + $ sq tbl copy @sakila_sl3.actor actor2 # Truncate table actor2 - sq tbl truncate @sakila_sl3.actor2 + $ sq tbl truncate @sakila_sl3.actor2 # Drop table actor2 - sq tbl drop @sakila_sl3.actor2`, + $ sq tbl drop @sakila_sl3.actor2`, } - return cmd, func(rc *RunContext, cmd *cobra.Command, args []string) error { - return cmd.Help() - } + return cmd } -func newTblCopyCmd() (*cobra.Command, runFunc) { +func newTblCopyCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "copy @HANDLE.TABLE NEWTABLE", - Short: "Make a copy of a table", - Long: `Make a copy of a table in the same database. The table data is also copied by default.`, - Example: ` # Copy table "actor" in @sakila_sl3" to new table "actor2" - sq tbl copy @sakila_sl3.actor .actor2 + Use: "copy @HANDLE.TABLE NEWTABLE", + Short: "Make a copy of a table", + Long: `Make a copy of a table in the same database. The table data is also copied by default.`, + ValidArgsFunction: completeTblCopy, + RunE: execTblCopy, + Example: ` # Copy table "actor" in @sakila_sl3 to new table "actor2" + $ sq tbl copy @sakila_sl3.actor .actor2 # Copy table "actor" in active src to table "actor2" - sq tbl copy .actor .actor2 + $ sq tbl copy .actor .actor2 # Copy table "actor" in active src to generated table name (e.g. "@sakila_sl3.actor_copy__1ae03e9b") - sq tbl copy .actor + $ sq tbl copy .actor # Copy table structure, but don't copy table data - sq tbl copy --data=false .actor + $ sq tbl copy --data=false .actor `, } cmd.Flags().BoolP(flagJSON, flagJSONShort, false, flagJSONUsage) cmd.Flags().Bool(flagTblData, true, flagTblDataUsage) - return cmd, execTblCopy + return cmd } -func execTblCopy(rc *RunContext, cmd *cobra.Command, args []string) error { +func execTblCopy(cmd *cobra.Command, args []string) error { + rc := RunContextFrom(cmd.Context()) if len(args) == 0 || len(args) > 2 { return errz.New("one or two table args required") } @@ -108,12 +111,12 @@ func execTblCopy(rc *RunContext, cmd *cobra.Command, args []string) error { } var dbase driver.Database - dbase, err = rc.databases.Open(rc.Context, tblHandles[0].src) + dbase, err = rc.databases.Open(cmd.Context(), tblHandles[0].src) if err != nil { return err } - copied, err := sqlDrvr.CopyTable(rc.Context, dbase.DB(), tblHandles[0].tbl, tblHandles[1].tbl, copyData) + copied, err := sqlDrvr.CopyTable(cmd.Context(), dbase.DB(), tblHandles[0].tbl, tblHandles[1].tbl, copyData) if err != nil { return errz.Wrapf(err, "failed tbl copy %s.%s --> %s.%s", tblHandles[0].handle, tblHandles[0].tbl, @@ -137,28 +140,35 @@ func execTblCopy(rc *RunContext, cmd *cobra.Command, args []string) error { return nil } -func newTblTruncateCmd() (*cobra.Command, runFunc) { +func newTblTruncateCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "truncate @HANDLE.TABLE", + Use: "truncate @HANDLE.TABLE|.TABLE", Short: "Truncate one or more tables", + Long: `Truncate one or more tables. Note that this command +only applies to SQL sources.`, + RunE: execTblTruncate, + ValidArgsFunction: (&handleTableCompleter{ + onlySQL: true, + }).complete, Example: ` # truncate table "actor"" in source @sakila_sl3 - sq tbl truncate @sakila_sl3.actor + $ sq tbl truncate @sakila_sl3.actor # truncate table "payment"" in the active src - sq tbl truncate .payment + $ sq tbl truncate .payment # truncate multiple tables - sq tbl truncate .payment @sakila_sl3.actor + $ sq tbl truncate .payment @sakila_sl3.actor `, } cmd.Flags().BoolP(flagJSON, flagJSONShort, false, flagJSONUsage) cmd.Flags().BoolP(flagTable, flagTableShort, false, flagTableUsage) - return cmd, execTblTruncate + return cmd } -func execTblTruncate(rc *RunContext, cmd *cobra.Command, args []string) (err error) { +func execTblTruncate(cmd *cobra.Command, args []string) (err error) { + rc := RunContextFrom(cmd.Context()) var tblHandles []tblHandle tblHandles, err = parseTableHandleArgs(rc.registry, rc.Config.Sources, args) if err != nil { @@ -167,7 +177,7 @@ func execTblTruncate(rc *RunContext, cmd *cobra.Command, args []string) (err err for _, tblH := range tblHandles { var affected int64 - affected, err = tblH.drvr.Truncate(rc.Context, tblH.src, tblH.tbl, true) + affected, err = tblH.drvr.Truncate(cmd.Context(), tblH.src, tblH.tbl, true) if err != nil { return err } @@ -180,25 +190,32 @@ func execTblTruncate(rc *RunContext, cmd *cobra.Command, args []string) (err err return nil } -func newTblDropCmd() (*cobra.Command, runFunc) { +func newTblDropCmd() *cobra.Command { cmd := &cobra.Command{ Use: "drop @HANDLE.TABLE", Short: "Drop one or more tables", + Long: `Drop one or more tables. Note that this command +only applies to SQL sources.`, + RunE: execTblDrop, + ValidArgsFunction: (&handleTableCompleter{ + onlySQL: true, + }).complete, Example: `# drop table "actor" in src @sakila_sl3 - sq tbl drop @sakila_sl3.actor + $ sq tbl drop @sakila_sl3.actor # drop table "payment"" in the active src - sq tbl drop .payment + $ sq tbl drop .payment # drop multiple tables - sq drop .payment @sakila_sl3.actor + $ sq drop .payment @sakila_sl3.actor `, } - return cmd, execTblDrop + return cmd } -func execTblDrop(rc *RunContext, cmd *cobra.Command, args []string) (err error) { +func execTblDrop(cmd *cobra.Command, args []string) (err error) { + rc := RunContextFrom(cmd.Context()) var tblHandles []tblHandle tblHandles, err = parseTableHandleArgs(rc.registry, rc.Config.Sources, args) if err != nil { @@ -212,11 +229,11 @@ func execTblDrop(rc *RunContext, cmd *cobra.Command, args []string) (err error) } var dbase driver.Database - dbase, err = rc.databases.Open(rc.Context, tblH.src) + dbase, err = rc.databases.Open(cmd.Context(), tblH.src) if err != nil { return err } - err = sqlDrvr.DropTable(rc.Context, dbase.DB(), tblH.tbl, false) + err = sqlDrvr.DropTable(cmd.Context(), dbase.DB(), tblH.tbl, false) if err != nil { return err } diff --git a/cli/cmd_version.go b/cli/cmd_version.go index da374b1c..f942c558 100644 --- a/cli/cmd_version.go +++ b/cli/cmd_version.go @@ -8,16 +8,18 @@ import ( "github.com/neilotoole/sq/cli/buildinfo" ) -func newVersionCmd() (*cobra.Command, runFunc) { +func newVersionCmd() *cobra.Command { cmd := &cobra.Command{ Use: "version", Short: "Print sq version", + RunE: execVersion, } - return cmd, execVersion + return cmd } -func execVersion(rc *RunContext, cmd *cobra.Command, args []string) error { +func execVersion(cmd *cobra.Command, args []string) error { + rc := RunContextFrom(cmd.Context()) rc.writers.fmt.Hilite.Fprintf(rc.Out, "sq %s", buildinfo.Version) if len(buildinfo.Commit) > 0 { diff --git a/cli/completion.go b/cli/completion.go new file mode 100644 index 00000000..f2e93e32 --- /dev/null +++ b/cli/completion.go @@ -0,0 +1,362 @@ +package cli + +import ( + "context" + "strings" + + "github.com/spf13/cobra" + + "github.com/neilotoole/sq/libsq/source" +) + +// completionFunc is a shell completion function. +type completionFunc func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) + +var ( + _ completionFunc = completeDriverType + _ completionFunc = completeSLQ + _ completionFunc = new(handleTableCompleter).complete +) + +// completeHandle is a completionFunc that suggests handles. +// The max arg is the maximum number of completions. Set to 0 +// for no limit. +func completeHandle(max int) completionFunc { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if max > 0 && len(args) >= max { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + rc := RunContextFrom(cmd.Context()) + handles := rc.Config.Sources.Handles() + + return handles, cobra.ShellCompDirectiveNoFileComp + } +} + +// completeSLQ is a completionFunc that completes SLQ queries. +// The completion functionality is rudimentary: it only +// completes the "table select" segment (that is, the @HANDLE.NAME) +// segment. +func completeSLQ(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveError + } + + c := &handleTableCompleter{} + return c.complete(cmd, args, toComplete) +} + +// completeDriverType is a completionFunc that suggests drivers. +func completeDriverType(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + rc := RunContextFrom(cmd.Context()) + if rc.databases == nil { + err := rc.init() + if err != nil { + rc.Log.Error(err) + return nil, cobra.ShellCompDirectiveError + } + } + + drivers := rc.registry.Drivers() + types := make([]string, len(drivers)) + for i, driver := range rc.registry.Drivers() { + types[i] = string(driver.DriverMetadata().Type) + } + + return types, cobra.ShellCompDirectiveNoFileComp +} + +// completeTblCopy is a completionFunc for the "tbl copy" command. +func completeTblCopy(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + // Example invocation: + // + // sq tbl copy @sakila_sl3.actor .new_table + // + // Note that the second arg can only be a table name (and + // not a @HANDLE.TABLE), and it must also be a _new_ table + // (because we can't copy over an existing table), thus + // we only suggest "." for the second arg, forcing the user + // to supply the rest of that new table name. + switch len(args) { + case 0: + c := &handleTableCompleter{onlySQL: true} + return c.complete(cmd, args, toComplete) + case 1: + return []string{"."}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace + default: + return nil, cobra.ShellCompDirectiveError + } +} + +// handleTableCompleter encapsulates completion of a handle +// ("@sakila_sl3"), table (".actor"), or @HANDLE.TABLE +// ("@sakila_sl3.actor"). Its complete method is a completionFunc. +type handleTableCompleter struct { + // onlySQL, when true, filters out non-SQL sources. + onlySQL bool + + // handleRequired, when true, means that only @HANDLE.TABLE + // suggestions are offered. That is, naked .TABLE suggestions + // will not be offered. + handleRequired bool + + // max indicates the maximum number of completions + // to offer. Use 0 to indicate no limit. Frequently this + // is set to 1 to if the command accepts only one argument. + max int +} + +// complete is the completionFunc for handleTableCompleter. +func (c *handleTableCompleter) complete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + rc := RunContextFrom(cmd.Context()) + if err := rc.init(); err != nil { + rc.Log.Error(err) + return nil, cobra.ShellCompDirectiveError + } + + // We don't want the user to wait around forever for + // shell completion, so we set a timeout. Typically + // this is something like 500ms. + ctx, cancelFn := context.WithTimeout(cmd.Context(), rc.Config.Defaults.ShellCompletionTimeout) + defer cancelFn() + + if c.max > 0 && len(args) >= c.max { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + if toComplete == "" { + if c.handleRequired { + return c.completeHandle(ctx, rc, args, toComplete) + } + return c.completeEither(ctx, rc, args, toComplete) + } + + // There's some input. We expect the input to be of the + // the form "@handle" or ".table". That is, the input should + // start with either '@' or '.'. + switch toComplete[0] { + default: + // User input was something other than '@' or '.' + return nil, cobra.ShellCompDirectiveError + case '@': + return c.completeHandle(ctx, rc, args, toComplete) + case '.': + if c.handleRequired { + return nil, cobra.ShellCompDirectiveError + } + return c.completeTableOnly(ctx, rc, args, toComplete) + } +} + +// completeTableOnly returns suggestions given input beginning with +// a period. Effectively this is completion for tables in the +// active src. +func (c *handleTableCompleter) completeTableOnly(ctx context.Context, rc *RunContext, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + activeSrc := rc.Config.Sources.Active() + if activeSrc == nil { + rc.Log.Error("Active source is nil") + return nil, cobra.ShellCompDirectiveError + } + + if c.onlySQL { + isSQL, err := handleIsSQLDriver(rc, activeSrc.Handle) + if err != nil { + rc.Log.Error(err) + return nil, cobra.ShellCompDirectiveError + } + if !isSQL { + return nil, cobra.ShellCompDirectiveNoFileComp + } + } + + tables, err := getTableNamesForHandle(ctx, rc, activeSrc.Handle) + if err != nil { + rc.Log.Error(err) + return nil, cobra.ShellCompDirectiveError + } + + var suggestions []string + for _, table := range tables { + if strings.HasPrefix(table, toComplete[1:]) { + suggestions = append(suggestions, "."+table) + } + } + + return suggestions, cobra.ShellCompDirectiveNoFileComp +} + +// completeHandle returns suggestions given input beginning with +// a '@'. The returned suggestions could be @HANDLE, or @HANDLE.TABLE. +func (c *handleTableCompleter) completeHandle(ctx context.Context, rc *RunContext, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + // We're dealing with a handle. + + // But we could be dealing with just the handle ("@sakila_sl3") + // or a @HANDLE.TABLE ("@sakila_sl3.actor"). + if strings.ContainsRune(toComplete, '.') { + if strings.Count(toComplete, ".") > 1 { + // Can only have one period + return nil, cobra.ShellCompDirectiveError + } + + // It's a handle with a full handle and at least a + // partial table name, such as "@sakila_sl3.fil" + handle, partialTbl, err := source.ParseTableHandle(strings.TrimSuffix(toComplete, ".")) + if err != nil { + rc.Log.Error(err) + return nil, cobra.ShellCompDirectiveError + } + + if c.onlySQL { + isSQL, err := handleIsSQLDriver(rc, handle) + if err != nil { + rc.Log.Error(err) + return nil, cobra.ShellCompDirectiveError + } + + if !isSQL { + return nil, cobra.ShellCompDirectiveNoFileComp + } + } + + tables, err := getTableNamesForHandle(ctx, rc, handle) + if err != nil { + rc.Log.Error(err) + return nil, cobra.ShellCompDirectiveError + } + + var suggestions []string + for _, table := range tables { + if strings.HasPrefix(table, partialTbl) { + suggestions = append(suggestions, handle+"."+table) + } + } + + return suggestions, cobra.ShellCompDirectiveNoFileComp + } + + handles := rc.Config.Sources.Handles() + // Else, we're dealing with just a handle so far + var matchingHandles []string + for _, handle := range handles { + if strings.HasPrefix(handle, toComplete) { + if c.onlySQL { + isSQL, err := handleIsSQLDriver(rc, handle) + if err != nil { + rc.Log.Error(err) + return nil, cobra.ShellCompDirectiveError + } + if !isSQL { + continue + } + } + + matchingHandles = append(matchingHandles, handle) + } + } + + switch len(matchingHandles) { + default: + return matchingHandles, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace + case 0: + return nil, cobra.ShellCompDirectiveNoFileComp + case 1: + // Only one handle match, so we will present that complete + // handle, plus a suggestion (@HANDLE.TABLE) for each of the tables + // for that handle + } + + tables, err := getTableNamesForHandle(ctx, rc, matchingHandles[0]) + if err != nil { + rc.Log.Error(err) + return nil, cobra.ShellCompDirectiveError + } + + suggestions := []string{matchingHandles[0]} + for _, table := range tables { + suggestions = append(suggestions, matchingHandles[0]+"."+table) + } + + return suggestions, cobra.ShellCompDirectiveNoFileComp +} + +func (c *handleTableCompleter) completeEither(ctx context.Context, rc *RunContext, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + // There's no input yet. + // Therefore we want to return a union of all handles + // plus the tables from the active source. + activeSrc := rc.Config.Sources.Active() + if activeSrc == nil { + rc.Log.Error("Active source is nil") + return nil, cobra.ShellCompDirectiveError + } + + var activeSrcTables []string + isSQL, err := handleIsSQLDriver(rc, activeSrc.Handle) + if err != nil { + rc.Log.Error(err) + return nil, cobra.ShellCompDirectiveError + } + + if !c.onlySQL || isSQL { + activeSrcTables, err = getTableNamesForHandle(ctx, rc, activeSrc.Handle) + if err != nil { + rc.Log.Error(err) + return nil, cobra.ShellCompDirectiveError + } + } + + var suggestions []string + for _, table := range activeSrcTables { + suggestions = append(suggestions, "."+table) + } + + for _, src := range rc.Config.Sources.Items() { + if c.onlySQL { + isSQL, err = handleIsSQLDriver(rc, src.Handle) + if err != nil { + rc.Log.Error(err) + return nil, cobra.ShellCompDirectiveError + } + if !isSQL { + continue + } + } + + suggestions = append(suggestions, src.Handle) + } + + return suggestions, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace +} + +func handleIsSQLDriver(rc *RunContext, handle string) (bool, error) { + src, err := rc.Config.Sources.Get(handle) + if err != nil { + return false, err + } + + driver, err := rc.registry.DriverFor(src.Type) + if err != nil { + return false, err + } + + return driver.DriverMetadata().IsSQL, nil +} + +func getTableNamesForHandle(ctx context.Context, rc *RunContext, handle string) ([]string, error) { + src, err := rc.Config.Sources.Get(handle) + if err != nil { + return nil, err + } + + db, err := rc.databases.Open(ctx, src) + if err != nil { + return nil, err + } + + md, err := db.SourceMetadata(ctx) + if err != nil { + return nil, err + } + + return md.TableNames(), nil +} diff --git a/cli/config/config.go b/cli/config/config.go index 7dc27803..a8e480ad 100644 --- a/cli/config/config.go +++ b/cli/config/config.go @@ -36,11 +36,21 @@ type Ext struct { UserDrivers []*userdriver.DriverDef `yaml:"user_drivers" json:"user_drivers"` } -// Defaults contains sq default values. +// Defaults contains default config values. type Defaults struct { - Timeout time.Duration `yaml:"timeout" json:"timeout"` - Format Format `yaml:"output_format" json:"output_format"` - Header bool `yaml:"output_header" json:"output_header"` + // Format is the default output format: json, table, etc. + Format Format `yaml:"output_format" json:"output_format"` + + // Header determines if a header should be printed (if relevant + // for the output format). + Header bool `yaml:"output_header" json:"output_header"` + + // PingTimeout is the allowed time for a ping. + PingTimeout time.Duration `yaml:"ping_timeout" json:"ping_timeout"` + + // ShellCompletionTimeout is the time allowed for the shell + // completion callback to execute. + ShellCompletionTimeout time.Duration `yaml:"shell_completion_timeout" json:"shell_completion_timeout"` } // New returns a config instance with default options set. @@ -66,11 +76,15 @@ func initCfg(cfg *Config) { cfg.Defaults.Format = FormatTable } - if cfg.Defaults.Timeout == 0 { + if cfg.Defaults.PingTimeout == 0 { // Probably should be setting this in the New function, // but we haven't yet defined cli's behavior wrt // a zero timeout. Does it mean no timeout? - cfg.Defaults.Timeout = 10 * time.Second + cfg.Defaults.PingTimeout = 10 * time.Second + } + + if cfg.Defaults.ShellCompletionTimeout == 0 { + cfg.Defaults.ShellCompletionTimeout = time.Millisecond * 500 } } diff --git a/cli/config/config_test.go b/cli/config/config_test.go index 1e3ebc6a..45c2264e 100644 --- a/cli/config/config_test.go +++ b/cli/config/config_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/neilotoole/sq/cli/config" + "github.com/neilotoole/sq/testh" "github.com/neilotoole/sq/testh/proj" ) @@ -70,14 +71,21 @@ func TestFileStore_Load(t *testing.T) { fs := &config.YAMLFileStore{HookLoad: hookExpand} for _, match := range good { - fs.Path = match - _, err = fs.Load() - require.NoError(t, err, match) + match := match + t.Run(testh.Name(match), func(t *testing.T) { + fs.Path = match + _, err = fs.Load() + require.NoError(t, err, match) + }) + } for _, match := range bad { - fs.Path = match - _, err = fs.Load() - require.Error(t, err, match) + match := match + t.Run(testh.Name(match), func(t *testing.T) { + fs.Path = match + _, err = fs.Load() + require.Error(t, err, match) + }) } } diff --git a/cli/config/testdata/bad.01.sq.yml b/cli/config/testdata/bad.01.sq.yml index 8cb108d7..707c9c17 100644 --- a/cli/config/testdata/bad.01.sq.yml +++ b/cli/config/testdata/bad.01.sq.yml @@ -1,2 +1,2 @@ defaults: - timeout: not_a_duration + ping_timeout: not_a_duration diff --git a/cli/config/testdata/bad.08.sq.yml b/cli/config/testdata/bad.08.sq.yml new file mode 100644 index 00000000..ca40504e --- /dev/null +++ b/cli/config/testdata/bad.08.sq.yml @@ -0,0 +1,2 @@ +defaults: + shell_completion_timeout: not_a_duration diff --git a/cli/config/testdata/good.01.sq.yml b/cli/config/testdata/good.01.sq.yml index 529d545e..850017d5 100644 --- a/cli/config/testdata/good.01.sq.yml +++ b/cli/config/testdata/good.01.sq.yml @@ -1,5 +1,6 @@ defaults: - timeout: 10s + ping_timeout: 10s + shell_completion_timeout: 1s output_format: table output_header: true sources: diff --git a/cli/output/tablew/metadatawriter.go b/cli/output/tablew/metadatawriter.go index 56db5769..4074cd90 100644 --- a/cli/output/tablew/metadatawriter.go +++ b/cli/output/tablew/metadatawriter.go @@ -96,7 +96,7 @@ func (w *mdWriter) SourceMetadata(meta *source.Metadata) error { meta.Name, stringz.ByteSized(meta.Size, 1, ""), fmt.Sprintf("%d", len(meta.Tables)), - meta.Location, + source.RedactLocation(meta.Location), } } else { headers = []string{"HANDLE", "DRIVER", "NAME", "FQ NAME", "SIZE", "TABLES", "LOCATION"} @@ -110,7 +110,7 @@ func (w *mdWriter) SourceMetadata(meta *source.Metadata) error { meta.FQName, stringz.ByteSized(meta.Size, 1, ""), fmt.Sprintf("%d", len(meta.Tables)), - meta.Location, + source.RedactLocation(meta.Location), } } diff --git a/cli/run_test.go b/cli/run_test.go index 8cdf3953..50fd6ccd 100644 --- a/cli/run_test.go +++ b/cli/run_test.go @@ -29,7 +29,6 @@ func newTestRunCtx(log lg.Log) (rc *cli.RunContext, out, errOut *bytes.Buffer) { errOut = &bytes.Buffer{} rc = &cli.RunContext{ - Context: context.Background(), Stdin: os.Stdin, Out: out, ErrOut: errOut, @@ -102,11 +101,7 @@ func (ru *run) exec(args ...string) error { return err } - if len(args) > 0 && args[0] != "sq" { - args = append([]string{"sq"}, args...) - } - - execErr := cli.ExecuteWith(ru.rc, args) + execErr := cli.ExecuteWith(context.Background(), ru.rc, args) if !ru.hushOutput { // We log sq's output now (before calling rc.Close) because diff --git a/cli/source.go b/cli/source.go index 5e2eb6b2..c186674a 100644 --- a/cli/source.go +++ b/cli/source.go @@ -1,6 +1,7 @@ package cli import ( + "context" "strings" "github.com/neilotoole/lg" @@ -17,7 +18,7 @@ import ( // mutate rc.Config.Sources as necessary. If no error // is returned, it is guaranteed that there's an active // source on the source set. -func determineSources(rc *RunContext) error { +func determineSources(ctx context.Context, rc *RunContext) error { cmd, srcs := rc.Cmd, rc.Config.Sources activeSrc, err := activeSrcFromFlagsOrConfig(cmd, srcs) if err != nil { @@ -26,7 +27,7 @@ func determineSources(rc *RunContext) error { // Note: ^ activeSrc could still be nil // check if there's input on stdin - stdinSrc, err := checkStdinSource(rc) + stdinSrc, err := checkStdinSource(ctx, rc) if err != nil { return err } @@ -95,7 +96,7 @@ func activeSrcFromFlagsOrConfig(cmd *cobra.Command, srcs *source.Set) (*source.S // If there is, that pipe is inspected, and if it has recognizable // input, a new source instance with handle @stdin is constructed // and returned. -func checkStdinSource(rc *RunContext) (*source.Source, error) { +func checkStdinSource(ctx context.Context, rc *RunContext) (*source.Source, error) { cmd := rc.Cmd f := rc.Stdin @@ -140,7 +141,7 @@ func checkStdinSource(rc *RunContext) (*source.Source, error) { } if typ == source.TypeNone { - typ, err = rc.files.TypeStdin(rc.Context) + typ, err = rc.files.TypeStdin(ctx) if err != nil { return nil, err } diff --git a/drivers/mysql/mysql.go b/drivers/mysql/mysql.go index f55b5027..012df5ea 100644 --- a/drivers/mysql/mysql.go +++ b/drivers/mysql/mysql.go @@ -171,7 +171,7 @@ func (d *driveri) CopyTable(ctx context.Context, db sqlz.DB, fromTable, toTable stmt += " WHERE 0" } - affected, err := sqlz.ExecResult(ctx, db, stmt) + affected, err := sqlz.ExecAffected(ctx, db, stmt) if err != nil { return 0, errz.Err(err) } @@ -324,7 +324,7 @@ func (d *driveri) Truncate(ctx context.Context, src *source.Source, tbl string, return 0, errz.Append(err, errz.Err(tx.Rollback())) } - affected, err = sqlz.ExecResult(ctx, tx, fmt.Sprintf("TRUNCATE TABLE `%s`", tbl)) + affected, err = sqlz.ExecAffected(ctx, tx, fmt.Sprintf("TRUNCATE TABLE `%s`", tbl)) if err != nil { return affected, errz.Append(err, errz.Err(tx.Rollback())) } diff --git a/drivers/postgres/postgres.go b/drivers/postgres/postgres.go index f86ea107..cedb909c 100644 --- a/drivers/postgres/postgres.go +++ b/drivers/postgres/postgres.go @@ -243,7 +243,7 @@ func (d *driveri) CopyTable(ctx context.Context, db sqlz.DB, fromTable, toTable stmt += " WITH NO DATA" } - affected, err := sqlz.ExecResult(ctx, db, stmt) + affected, err := sqlz.ExecAffected(ctx, db, stmt) if err != nil { return 0, errz.Err(err) } diff --git a/drivers/sqlite3/sqlite3.go b/drivers/sqlite3/sqlite3.go index 4c2f4090..f1d1091e 100644 --- a/drivers/sqlite3/sqlite3.go +++ b/drivers/sqlite3/sqlite3.go @@ -103,16 +103,27 @@ func (d *driveri) Truncate(ctx context.Context, src *source.Source, tbl string, return 0, errz.Err(err) } - affected, err = sqlz.ExecResult(ctx, tx, fmt.Sprintf("DELETE FROM %q", tbl)) + affected, err = sqlz.ExecAffected(ctx, tx, fmt.Sprintf("DELETE FROM %q", tbl)) if err != nil { return affected, errz.Append(err, errz.Err(tx.Rollback())) } if reset { - _, err = sqlz.ExecResult(ctx, tx, "UPDATE sqlite_sequence SET seq = 0 WHERE name = ?", tbl) + // First check that the sqlite_sequence table event exists. It + // may not exist if there are no auto-increment columns? + const q = `SELECT COUNT(name) FROM sqlite_master WHERE type='table' AND name='sqlite_sequence'` + var count int64 + err = tx.QueryRowContext(ctx, q).Scan(&count) if err != nil { return 0, errz.Append(err, errz.Err(tx.Rollback())) } + + if count > 0 { + _, err = tx.ExecContext(ctx, "UPDATE sqlite_sequence SET seq = 0 WHERE name = ?", tbl) + if err != nil { + return 0, errz.Append(err, errz.Err(tx.Rollback())) + } + } } return affected, errz.Err(tx.Commit()) @@ -188,7 +199,7 @@ func (d *driveri) CopyTable(ctx context.Context, db sqlz.DB, fromTable, toTable } stmt := fmt.Sprintf("INSERT INTO %q SELECT * FROM %q", toTable, fromTable) - affected, err := sqlz.ExecResult(ctx, db, stmt) + affected, err := sqlz.ExecAffected(ctx, db, stmt) if err != nil { return 0, errz.Err(err) } diff --git a/drivers/sqlserver/sqlserver.go b/drivers/sqlserver/sqlserver.go index 0e0b1bbf..1f5237fe 100644 --- a/drivers/sqlserver/sqlserver.go +++ b/drivers/sqlserver/sqlserver.go @@ -159,7 +159,7 @@ func (d *driveri) Truncate(ctx context.Context, src *source.Source, tbl string, } defer d.log.WarnIfFuncError(db.Close) - affected, err = sqlz.ExecResult(ctx, db, fmt.Sprintf("DELETE FROM %q", tbl)) + affected, err = sqlz.ExecAffected(ctx, db, fmt.Sprintf("DELETE FROM %q", tbl)) if err != nil { return affected, errz.Wrapf(err, "truncate: failed to delete from %q", tbl) } @@ -285,7 +285,7 @@ func (d *driveri) CopyTable(ctx context.Context, db sqlz.DB, fromTable, toTable stmt = fmt.Sprintf("SELECT TOP(0) * INTO %q FROM %q", toTable, fromTable) } - affected, err := sqlz.ExecResult(ctx, db, stmt) + affected, err := sqlz.ExecAffected(ctx, db, stmt) if err != nil { return 0, errz.Err(err) } diff --git a/go.mod b/go.mod index ade441e9..3d61f281 100644 --- a/go.mod +++ b/go.mod @@ -1,60 +1,48 @@ module github.com/neilotoole/sq -go 1.14 +go 1.15 + +// Using forked cobra for now because v1.1.3 does not pass Context +// to valid args completion funcs. There's an open PR for +// this: https://github.com/spf13/cobra/pull/1265 +replace github.com/spf13/cobra v1.1.3 => github.com/neilotoole/cobra v1.1.4-0.20210220092732-c11dbd416310 require ( - github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect - github.com/alecthomas/units v0.0.0-20201120081800-1786d5ef83d4 // indirect github.com/antlr/antlr4 v0.0.0-20191011202612-ad2bd05285ca - github.com/aws/aws-sdk-go v1.12.10 // indirect - github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect - github.com/buger/jsonparser v1.0.0 + github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect github.com/c2h5oh/datasize v0.0.0-20170519143321-54516c931ae9 + github.com/coreos/go-etcd v2.0.0+incompatible // indirect + github.com/cpuguy83/go-md2man v1.0.10 // indirect github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec github.com/djherbis/fscache v0.10.1 - github.com/dustin/go-humanize v1.0.0 // indirect github.com/emirpasic/gods v1.9.0 github.com/fatih/color v1.9.0 - github.com/go-ini/ini v1.30.0 // indirect github.com/go-sql-driver/mysql v1.5.0 - github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect github.com/google/uuid v1.1.1 github.com/h2non/filetype v1.1.0 - github.com/hashicorp/go-cleanhttp v0.0.0-20170211013415-3573b8b52aa7 // indirect - github.com/hashicorp/go-getter v0.0.0-20171007181130-2f449c791e6a - github.com/hashicorp/go-version v0.0.0-20170914154128-fc61389e27c7 // indirect github.com/jackc/pgconn v1.5.0 github.com/jackc/pgx/v4 v4.6.0 - github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 // indirect - github.com/jondot/goweight v1.0.5 // indirect - github.com/kr/pretty v0.2.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/magefile/mage v1.9.0 github.com/mattn/go-colorable v0.1.4 github.com/mattn/go-isatty v0.0.12 github.com/mattn/go-runewidth v0.0.4 github.com/mattn/go-sqlite3 v2.0.3+incompatible - github.com/mattn/go-zglob v0.0.3 // indirect github.com/mitchellh/go-homedir v1.1.0 - github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 // indirect github.com/neilotoole/errgroup v0.1.5 github.com/neilotoole/lg v0.3.0 - github.com/nlopes/slack v0.1.0 github.com/pkg/errors v0.9.1 github.com/ryboe/q v1.0.12 github.com/satori/go.uuid v1.2.0 github.com/segmentio/encoding v0.1.14 github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 - github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // indirect - github.com/spf13/cobra v0.0.5 - github.com/spf13/pflag v1.0.3 + github.com/spf13/cobra v1.1.3 + github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.5.1 - github.com/tbruyelle/hipchat-go v0.0.0-20160921153256-749fb9e14beb github.com/tealeg/xlsx/v2 v2.0.1 github.com/testcontainers/testcontainers-go v0.5.0 - github.com/thoas/go-funk v0.7.0 // indirect - github.com/ulikunitz/xz v0.5.4 // indirect github.com/xo/dburl v0.0.0-20200124232849-e9ec94f52bc3 + github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 // indirect go.uber.org/atomic v1.5.0 go.uber.org/multierr v1.4.0 golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de @@ -62,5 +50,5 @@ require ( golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 gopkg.in/djherbis/atime.v1 v1.0.0 // indirect gopkg.in/djherbis/stream.v1 v1.3.1 // indirect - gopkg.in/yaml.v2 v2.2.8 + gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 75336063..1548e328 100644 --- a/go.sum +++ b/go.sum @@ -1,45 +1,60 @@ cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/hcsshim v0.8.6 h1:ZfF0+zZeYdzMIVMZHKtDKJvLHj76XCuVae/jNkjj0IA= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20201120081800-1786d5ef83d4 h1:EBTWhcAX7rNQ80RLwLCpHZBBrJuzallFHnF+yMXo928= -github.com/alecthomas/units v0.0.0-20201120081800-1786d5ef83d4/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/antlr/antlr4 v0.0.0-20191011202612-ad2bd05285ca h1:QHbltbNkVcw97h4zA/L8gA4o3dJiFvBZ0gyZHrYXHbs= github.com/antlr/antlr4 v0.0.0-20191011202612-ad2bd05285ca/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/aws/aws-sdk-go v1.12.10 h1:ihg0UOujHVcFciyc6zs/q5VLhoG1K+oDLqgpCxkAh04= -github.com/aws/aws-sdk-go v1.12.10/go.mod h1:ZRmQr0FajVIyZ4ZzBYKG5P3ZqPz9IHG41ZoMu1ADI3k= -github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= -github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= -github.com/buger/jsonparser v1.0.0 h1:etJTGF5ESxjI0Ic2UaLQs2LQQpa8G9ykQScukbh4L8A= -github.com/buger/jsonparser v1.0.0/go.mod h1:tgcrVJ81GPSF0mz+0nu1Xaz0fazGPrmmJfJtxjbHhUQ= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/c2h5oh/datasize v0.0.0-20170519143321-54516c931ae9 h1:N6In6gI4jApKZ6F8QqeVgPti7612P5EdLISBNbjQ8tk= github.com/c2h5oh/datasize v0.0.0-20170519143321-54516c931ae9/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -47,6 +62,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec h1:NfhRXXFDPxcF5Cwo06DzeIaE7uuJtAUhsDwH3LNsjos= github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/djherbis/fscache v0.10.1 h1:hDv+RGyvD+UDKyRYuLoVNbuRTnf2SrA2K3VyR1br9lk= github.com/djherbis/fscache v0.10.1/go.mod h1:yyPYtkNnnPXsW+81lAcQS6yab3G2CRfnPLotBvtbf0c= github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible h1:dvc1KSkIYTVjZgHf/CTC2diTYC8PzhaA5sFISRfNVrE= @@ -57,22 +74,22 @@ github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKoh github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dustin/go-humanize v0.0.0-20180713052910-9f541cc9db5d h1:lDrio3iIdNb0Gw9CgH7cQF+iuB5mOOjdJ9ERNJCBgb4= -github.com/dustin/go-humanize v0.0.0-20180713052910-9f541cc9db5d/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/emirpasic/gods v1.9.0 h1:rUF4PuzEjMChMiNsVjdI+SyLu7rEqpQ5reNFnhC7oFo= github.com/emirpasic/gods v1.9.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/frankban/quicktest v1.5.0 h1:Tb4jWdSpdjKzTUicPnY61PZxKbDoGa7ABbrReT3gQVY= github.com/frankban/quicktest v1.5.0/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= -github.com/go-ini/ini v1.30.0 h1:bcFeUQUA+99t1cZPXmtc7HpGv2KTlZGIFeBDWQh2DRw= -github.com/go-ini/ini v1.30.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= @@ -84,39 +101,69 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0= -github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/h2non/filetype v1.1.0 h1:Or/gjocJrJRNK/Cri/TDEKFjAR+cfG6eK65NGYB6gBA= github.com/h2non/filetype v1.1.0/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= -github.com/hashicorp/go-cleanhttp v0.0.0-20170211013415-3573b8b52aa7 h1:67fHcS+inUoiIqWCKIqeDuq2AlPHNHPiTqp97LdQ+bc= -github.com/hashicorp/go-cleanhttp v0.0.0-20170211013415-3573b8b52aa7/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-getter v0.0.0-20171007181130-2f449c791e6a h1:deqk3CV92a9CYhyfYjIJPAB2X4/vheVghmoBCno6WQM= -github.com/hashicorp/go-getter v0.0.0-20171007181130-2f449c791e6a/go.mod h1:6rdJFnhkXnzGOJbvkrdv4t9nLwKcVA+tmbQeUlkIzrU= -github.com/hashicorp/go-version v0.0.0-20170914154128-fc61389e27c7 h1:Tijq+ZHupzK8WfomfH2s5dpKkpZd2TcN2i1LDbzWbwk= -github.com/hashicorp/go-version v0.0.0-20170914154128-fc61389e27c7/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= @@ -163,17 +210,18 @@ github.com/jackc/pgx/v4 v4.6.0/go.mod h1:vPh43ZzxijXUVJ+t/EmXBtFmbFVO72cuneCT9oA github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE= -github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jondot/goweight v1.0.5 h1:aRpnyj1G8BLLNhem8xezuuV0GlFz4G11e3/UtBU/FlQ= -github.com/jondot/goweight v1.0.5/go.mod h1:3PRcpOwkyspe1t4+KCNgauas+aNDTSSCwZ6AQ4kDD/A= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= @@ -192,10 +240,13 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magefile/mage v1.9.0 h1:t3AU2wNwehMCW97vuqQLtw6puppWXHO+O2MHo5a50XE= github.com/magefile/mage v1.9.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -208,25 +259,31 @@ github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/ github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-zglob v0.0.0-20180803001819-2ea3427bfa53 h1:tGfIHhDghvEnneeRhODvGYOt305TPwingKt6p90F4MU= -github.com/mattn/go-zglob v0.0.0-20180803001819-2ea3427bfa53/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= -github.com/mattn/go-zglob v0.0.3 h1:6Ry4EYsScDyt5di4OI6xw1bYhOqfE5S33Z1OPy+d+To= -github.com/mattn/go-zglob v0.0.3/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE= github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/neilotoole/cobra v1.1.4-0.20210220092732-c11dbd416310 h1:++ilwBgxD5woFaMRfHmbJDbZ4mpoJT/ghS2I5pjrn+A= +github.com/neilotoole/cobra v1.1.4-0.20210220092732-c11dbd416310/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/neilotoole/errgroup v0.1.5 h1:DxEGoIfFm5ooGicidR+okiHjoOaGRKFaSxDPVZuuu2I= github.com/neilotoole/errgroup v0.1.5/go.mod h1:Q2nLGf+594h0CLBs/Mbg6qOr7GtqDK7C2S41udRnToE= github.com/neilotoole/lg v0.3.0 h1:2/IESY8l903AeK9nvH3AWVI/p8up+8wmKWIuts7PpxY= github.com/neilotoole/lg v0.3.0/go.mod h1:CUHyAinxegpXWV2uPbGP8R3OzRMlAd78YxtA9xiOeFA= -github.com/nlopes/slack v0.1.0 h1:YnVhdQvWT/m0TDh3VNpSoCBDlD7Y4pz1qUqb/NrNyUs= -github.com/nlopes/slack v0.1.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -238,6 +295,7 @@ github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVo github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= @@ -246,36 +304,55 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryboe/q v1.0.12 h1:Ehx4fAUEaZ5KW9Cf9w+ShQ41r5Znc5076jPdnqbfZ3o= github.com/ryboe/q v1.0.12/go.mod h1:6xM6Lm3ZAh5H8d85zLohckSJcqIjJspf1gGkWWS8YGs= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/segmentio/encoding v0.1.14 h1:BfnglNbNRohLaBLf93uP5/IwKqeWrezXK/g6IRnj75c= github.com/segmentio/encoding v0.1.14/go.mod h1:RWhr02uzMB9gQC1x+MfYxedtmBibb9cZ6Vv9VxRSSbw= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8= -github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= +github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -287,25 +364,23 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/tbruyelle/hipchat-go v0.0.0-20160921153256-749fb9e14beb h1:mb7xv0kx9XpGsLy5kCCa6+3HqSj495cEBQNMgljqZ48= -github.com/tbruyelle/hipchat-go v0.0.0-20160921153256-749fb9e14beb/go.mod h1:CJEWrlDz1qHCF/nywogFd3AqHUWbKCdpu9pSAdf1OzY= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tealeg/xlsx/v2 v2.0.1 h1:RP+VEscpPFjH2FnpKh1p9HVLAk1htqb9Urcxi2AU1ns= github.com/tealeg/xlsx/v2 v2.0.1/go.mod h1:l9GvhCCjdaIGkAyZcFedDALcYcXUOei55f6umRMOz9c= github.com/testcontainers/testcontainers-go v0.5.0 h1:u7jCdf130QHBY5KK9xkFzZpkep24Rtl8HdWscMO9H/8= github.com/testcontainers/testcontainers-go v0.5.0/go.mod h1:BXwe1JilTOLT8cmVyPMDbIw7e+8UCGeAhxjBwguG5wQ= -github.com/thoas/go-funk v0.0.0-20180716193722-1060394a7713 h1:knaxjm6QMbUMNvuaSnJZmw0gRX4V/79JVUQiziJGM84= -github.com/thoas/go-funk v0.0.0-20180716193722-1060394a7713/go.mod h1:mlR+dHGb+4YgXkf13rkQTuzrneeHANxOm6+ZnEV9HsA= -github.com/thoas/go-funk v0.7.0 h1:GmirKrs6j6zJbhJIficOsz2aAI7700KsU/5YrdHRM1Y= -github.com/thoas/go-funk v0.7.0/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/ulikunitz/xz v0.5.4 h1:zATC2OoZ8H1TZll3FpbX+ikwmadbO699PE06cIkm9oU= -github.com/ulikunitz/xz v0.5.4/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xo/dburl v0.0.0-20200124232849-e9ec94f52bc3 h1:NC3CI7do3KHtiuYhk1CdS9V2qS3jNa7Fs2Afcnnt+IE= github.com/xo/dburl v0.0.0-20200124232849-e9ec94f52bc3/go.mod h1:A47W3pdWONaZmXuLZgfKLAVgUY0qvfTRM5vVDKS40S4= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY= @@ -321,6 +396,7 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -328,6 +404,7 @@ golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200117160349-530e935923ad h1:Jh8cai0fqIK+f6nG0UgPW5wFk8wmiMhM3AyciDBdtQg= golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -335,36 +412,75 @@ golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+v golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= @@ -372,32 +488,67 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180810170437-e96c4e24768d/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.17.0 h1:TRJYBgMclJvGYn2rIMjj+h9KtMt5r1Ij7ODVRIZkwhk= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= -gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -411,15 +562,25 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gotest.tools v0.0.0-20181223230014-1083505acf35 h1:zpdCK+REwbk+rqjJmHhiCN6iBIigrZ39glqSF0P3KF0= gotest.tools v0.0.0-20181223230014-1083505acf35/go.mod h1:R//lfYlUuTOTfblYI3lGoAAAebUdzjvbmQsuB7Ykd90= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/libsq/core/sqlz/sqlz.go b/libsq/core/sqlz/sqlz.go index 6c553003..25e99ef1 100644 --- a/libsq/core/sqlz/sqlz.go +++ b/libsq/core/sqlz/sqlz.go @@ -42,9 +42,9 @@ type DB interface { Preparer } -// ExecResult invokes db.ExecContext, returning the count of rows +// ExecAffected invokes db.ExecContext, returning the count of rows // affected and any error. -func ExecResult(ctx context.Context, db Execer, query string, args ...interface{}) (affected int64, err error) { +func ExecAffected(ctx context.Context, db Execer, query string, args ...interface{}) (affected int64, err error) { var res sql.Result res, err = db.ExecContext(ctx, query, args...) if err != nil { diff --git a/libsq/driver/driver.go b/libsq/driver/driver.go index d1f53514..3a8363c2 100644 --- a/libsq/driver/driver.go +++ b/libsq/driver/driver.go @@ -135,7 +135,7 @@ type SQLDriver interface { // must honor the table name and column names and kinds from tblDef. CreateTable(ctx context.Context, db sqlz.DB, tblDef *sqlmodel.TableDef) error - // TableExists returns true if there's an exist table tbl in db. + // TableExists returns true if there's an existing table tbl in db. TableExists(ctx context.Context, db sqlz.DB, tbl string) (bool, error) // CopyTable copies fromTable into a new table toTable. diff --git a/libsq/source/set.go b/libsq/source/set.go index 9ad11ae8..87870fd7 100644 --- a/libsq/source/set.go +++ b/libsq/source/set.go @@ -90,14 +90,6 @@ func (s *Set) Add(src *Source) error { return nil } -// IndexOf returns the index of handle in s. -func (s *Set) IndexOf(handle string) (int, *Source) { - s.mu.Lock() - defer s.mu.Unlock() - - return s.indexOf(handle) -} - // Exists returns true if handle already exists in the set. func (s *Set) Exists(handle string) bool { s.mu.Lock() @@ -263,6 +255,19 @@ func (s *Set) Remove(handle string) error { return nil } +// Handles returns the set of source handles. +func (s *Set) Handles() []string { + s.mu.Lock() + defer s.mu.Unlock() + + handles := make([]string, len(s.data.Items)) + for i := range s.data.Items { + handles[i] = s.data.Items[i].Handle + } + + return handles +} + // VerifySetIntegrity verifies the internal state of s. // Typically this func is invoked after s has been loaded // from config, verifying that the config is not corrupt. diff --git a/libsq/source/source.go b/libsq/source/source.go index ee8d6188..53702799 100644 --- a/libsq/source/source.go +++ b/libsq/source/source.go @@ -67,8 +67,13 @@ func (s *Source) RedactedLocation() string { if s == nil { return "" } - loc := s.Location + return RedactLocation(s.Location) +} +// RedactLocation returns a redacted version of the source +// location loc, with the password component (if any) of +// the location masked. +func RedactLocation(loc string) string { switch { case loc == "": return "" diff --git a/main.go b/main.go index 5f34afb4..b3931c33 100644 --- a/main.go +++ b/main.go @@ -21,7 +21,7 @@ func main() { cancelFn() }() - err := cli.Execute(ctx, os.Stdin, os.Stdout, os.Stderr, os.Args) + err := cli.Execute(ctx, os.Stdin, os.Stdout, os.Stderr, os.Args[1:]) if err != nil { cancelFn() os.Exit(1)