2023-04-26 18:16:42 +03:00
|
|
|
package cli
|
|
|
|
|
|
|
|
import (
|
2023-05-07 05:36:34 +03:00
|
|
|
"fmt"
|
2023-05-01 06:59:34 +03:00
|
|
|
"strings"
|
|
|
|
|
2023-11-20 04:06:36 +03:00
|
|
|
"github.com/samber/lo"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/spf13/pflag"
|
2023-08-04 08:41:33 +03:00
|
|
|
|
2024-01-15 04:45:34 +03:00
|
|
|
"github.com/neilotoole/sq/cli/config"
|
2023-11-20 04:06:36 +03:00
|
|
|
"github.com/neilotoole/sq/cli/output/xlsxw"
|
2023-05-19 17:24:18 +03:00
|
|
|
"github.com/neilotoole/sq/cli/run"
|
2023-04-26 18:16:42 +03:00
|
|
|
"github.com/neilotoole/sq/drivers/csv"
|
|
|
|
"github.com/neilotoole/sq/libsq/core/errz"
|
|
|
|
"github.com/neilotoole/sq/libsq/core/options"
|
2024-01-25 07:01:24 +03:00
|
|
|
"github.com/neilotoole/sq/libsq/core/progress"
|
2023-11-20 04:06:36 +03:00
|
|
|
"github.com/neilotoole/sq/libsq/core/timez"
|
2023-04-26 18:16:42 +03:00
|
|
|
"github.com/neilotoole/sq/libsq/driver"
|
2024-01-25 09:29:55 +03:00
|
|
|
"github.com/neilotoole/sq/libsq/files"
|
2024-01-27 01:18:38 +03:00
|
|
|
"github.com/neilotoole/sq/libsq/files/downloader"
|
2023-04-26 18:16:42 +03:00
|
|
|
"github.com/neilotoole/sq/libsq/source"
|
2023-11-21 00:42:38 +03:00
|
|
|
"github.com/neilotoole/sq/libsq/source/drivertype"
|
2023-04-26 18:16:42 +03:00
|
|
|
)
|
|
|
|
|
2023-05-03 15:36:10 +03:00
|
|
|
// getOptionsFromFlags builds options.Options from flags. In effect, a flag
|
2023-05-22 18:08:14 +03:00
|
|
|
// such as --ingest.header is mapped to an option.Opt of the same name. Note
|
|
|
|
// however that Opt.Flag and Opt.Key can differ.
|
2023-04-26 18:16:42 +03:00
|
|
|
//
|
2023-05-03 15:36:10 +03:00
|
|
|
// See also: getOptionsFromCmd, applySourceOptions, applyCollectionOptions.
|
|
|
|
func getOptionsFromFlags(flags *pflag.FlagSet, reg *options.Registry) (options.Options, error) {
|
2023-04-26 18:16:42 +03:00
|
|
|
o := options.Options{}
|
|
|
|
err := reg.Visit(func(opt options.Opt) error {
|
2023-05-22 18:08:14 +03:00
|
|
|
f := flags.Lookup(opt.Flag())
|
2023-04-26 18:16:42 +03:00
|
|
|
if f == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if !f.Changed {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.Value == nil {
|
|
|
|
// This shouldn't happen
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-01-15 04:45:34 +03:00
|
|
|
if bOpt, ok := opt.(options.Bool); ok {
|
|
|
|
// Special handling for bool, because
|
|
|
|
// the flag value could be inverted.
|
|
|
|
val, err := flags.GetBool(bOpt.Flag())
|
|
|
|
if err != nil {
|
|
|
|
return errz.Err(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if bOpt.FlagInverted() {
|
|
|
|
val = !val
|
|
|
|
}
|
|
|
|
o[bOpt.Key()] = val
|
|
|
|
} else {
|
|
|
|
o[opt.Key()] = f.Value.String()
|
|
|
|
}
|
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
o, err = reg.Process(o)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errz.Wrap(err, "options from flags")
|
|
|
|
}
|
|
|
|
|
|
|
|
return o, nil
|
|
|
|
}
|
|
|
|
|
2023-05-03 15:36:10 +03:00
|
|
|
// getSrcOptionsFromFlags returns the options.Options applicable to
|
|
|
|
// the driver type.
|
|
|
|
//
|
|
|
|
// See also: getOptionsFromFlags, getOptionsFromCmd, applySourceOptions, applyCollectionOptions.
|
|
|
|
func getSrcOptionsFromFlags(flags *pflag.FlagSet, reg *options.Registry,
|
2023-11-21 00:42:38 +03:00
|
|
|
typ drivertype.Type,
|
2023-05-03 15:36:10 +03:00
|
|
|
) (options.Options, error) {
|
|
|
|
srcOpts := filterOptionsForSrc(typ, reg.Opts()...)
|
|
|
|
srcReg := &options.Registry{}
|
|
|
|
srcReg.Add(srcOpts...)
|
|
|
|
return getOptionsFromFlags(flags, srcReg)
|
|
|
|
}
|
|
|
|
|
|
|
|
// getOptionsFromCmd returns the options.Options generated by merging
|
2023-04-26 18:16:42 +03:00
|
|
|
// config options and flag options.
|
|
|
|
//
|
2023-05-03 15:36:10 +03:00
|
|
|
// See also: getOptionsFromFlags, applySourceOptions, applyCollectionOptions.
|
|
|
|
func getOptionsFromCmd(cmd *cobra.Command) (options.Options, error) {
|
2023-05-19 17:24:18 +03:00
|
|
|
ru := run.FromContext(cmd.Context())
|
2023-04-26 18:16:42 +03:00
|
|
|
var configOpts options.Options
|
2023-05-19 17:24:18 +03:00
|
|
|
if ru.Config != nil && ru.Config.Options != nil {
|
|
|
|
configOpts = ru.Config.Options
|
2023-04-26 18:16:42 +03:00
|
|
|
} else {
|
|
|
|
configOpts = options.Options{}
|
|
|
|
}
|
|
|
|
|
2023-05-19 17:24:18 +03:00
|
|
|
flagOpts, err := getOptionsFromFlags(cmd.Flags(), ru.OptionsRegistry)
|
2023-04-26 18:16:42 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return options.Merge(configOpts, flagOpts), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// applySourceOptions merges options from config, src, and flags.
|
|
|
|
// The src.Options field may be replaced or mutated. It will always
|
|
|
|
// be non-nil (unless an error is returned).
|
|
|
|
//
|
2023-05-03 15:36:10 +03:00
|
|
|
// See also: getOptionsFromFlags, getOptionsFromCmd, applyCollectionOptions.
|
2023-04-26 18:16:42 +03:00
|
|
|
func applySourceOptions(cmd *cobra.Command, src *source.Source) error {
|
2023-05-19 17:24:18 +03:00
|
|
|
ru := run.FromContext(cmd.Context())
|
2023-04-26 18:16:42 +03:00
|
|
|
|
2023-05-19 17:24:18 +03:00
|
|
|
defaultOpts := ru.Config.Options
|
2023-04-26 18:16:42 +03:00
|
|
|
if defaultOpts == nil {
|
|
|
|
defaultOpts = options.Options{}
|
|
|
|
}
|
|
|
|
|
2024-01-15 04:45:34 +03:00
|
|
|
// FIXME: This should only apply source options?
|
2023-05-19 17:24:18 +03:00
|
|
|
flagOpts, err := getOptionsFromFlags(cmd.Flags(), ru.OptionsRegistry)
|
2023-04-26 18:16:42 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
srcOpts := src.Options
|
|
|
|
if srcOpts == nil {
|
|
|
|
srcOpts = options.Options{}
|
|
|
|
}
|
|
|
|
|
|
|
|
effectiveOpts := options.Merge(defaultOpts, srcOpts, flagOpts)
|
|
|
|
src.Options = effectiveOpts
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// applyCollectionOptions invokes applySourceOptions for
|
|
|
|
// each source in coll. The sources may have their Source.Options field
|
|
|
|
// mutated.
|
|
|
|
//
|
2023-05-03 15:36:10 +03:00
|
|
|
// See also: getOptionsFromCmd, getOptionsFromFlags, applySourceOptions.
|
2023-04-26 18:16:42 +03:00
|
|
|
func applyCollectionOptions(cmd *cobra.Command, coll *source.Collection) error {
|
|
|
|
return coll.Visit(func(src *source.Source) error {
|
|
|
|
return applySourceOptions(cmd, src)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// RegisterDefaultOpts registers the options.Opt instances
|
2023-05-01 06:59:34 +03:00
|
|
|
// that the CLI knows about.
|
2023-04-26 18:16:42 +03:00
|
|
|
func RegisterDefaultOpts(reg *options.Registry) {
|
2023-05-01 06:59:34 +03:00
|
|
|
reg.Add(
|
2023-05-07 05:36:34 +03:00
|
|
|
OptFormat,
|
2023-07-27 07:19:11 +03:00
|
|
|
OptErrorFormat,
|
2023-05-07 05:36:34 +03:00
|
|
|
OptDatetimeFormat,
|
|
|
|
OptDatetimeFormatAsNumber,
|
|
|
|
OptDateFormat,
|
|
|
|
OptDateFormatAsNumber,
|
|
|
|
OptTimeFormat,
|
|
|
|
OptTimeFormatAsNumber,
|
2023-08-20 16:22:24 +03:00
|
|
|
xlsxw.OptDatetimeFormat,
|
|
|
|
xlsxw.OptDateFormat,
|
|
|
|
xlsxw.OptTimeFormat,
|
2023-07-03 18:34:19 +03:00
|
|
|
driver.OptResultColRename,
|
2023-05-03 15:36:10 +03:00
|
|
|
OptVerbose,
|
2023-05-01 06:59:34 +03:00
|
|
|
OptPrintHeader,
|
2023-05-03 15:36:10 +03:00
|
|
|
OptMonochrome,
|
2024-01-15 04:45:34 +03:00
|
|
|
OptProgress,
|
|
|
|
OptProgressDelay,
|
2023-05-05 20:41:22 +03:00
|
|
|
OptCompact,
|
2023-05-22 18:08:14 +03:00
|
|
|
OptPingCmdTimeout,
|
2023-05-03 15:36:10 +03:00
|
|
|
OptShellCompletionTimeout,
|
2024-01-25 07:01:24 +03:00
|
|
|
OptShellCompletionLog,
|
2024-01-15 04:45:34 +03:00
|
|
|
config.OptConfigLockTimeout,
|
2023-05-05 17:32:50 +03:00
|
|
|
OptLogEnabled,
|
|
|
|
OptLogFile,
|
|
|
|
OptLogLevel,
|
2024-01-15 04:45:34 +03:00
|
|
|
OptLogFormat,
|
2023-05-19 17:24:18 +03:00
|
|
|
OptDiffNumLines,
|
2023-05-22 18:08:14 +03:00
|
|
|
OptDiffDataFormat,
|
2024-01-25 09:29:55 +03:00
|
|
|
files.OptHTTPRequestTimeout,
|
|
|
|
files.OptHTTPResponseTimeout,
|
|
|
|
files.OptHTTPSInsecureSkipVerify,
|
2024-01-27 01:18:38 +03:00
|
|
|
downloader.OptCache,
|
|
|
|
downloader.OptContinueOnError,
|
2023-05-01 06:59:34 +03:00
|
|
|
driver.OptConnMaxOpen,
|
|
|
|
driver.OptConnMaxIdle,
|
|
|
|
driver.OptConnMaxIdleTime,
|
|
|
|
driver.OptConnMaxLifetime,
|
2023-05-22 18:08:14 +03:00
|
|
|
driver.OptConnOpenTimeout,
|
2023-05-03 15:36:10 +03:00
|
|
|
driver.OptMaxRetryInterval,
|
|
|
|
driver.OptTuningErrgroupLimit,
|
|
|
|
driver.OptTuningRecChanSize,
|
|
|
|
OptTuningFlushThreshold,
|
2023-07-04 20:31:47 +03:00
|
|
|
driver.OptIngestHeader,
|
2024-01-15 04:45:34 +03:00
|
|
|
driver.OptIngestCache,
|
2024-01-25 09:29:55 +03:00
|
|
|
files.OptCacheLockTimeout,
|
2023-07-04 20:31:47 +03:00
|
|
|
driver.OptIngestColRename,
|
|
|
|
driver.OptIngestSampleSize,
|
2023-05-01 06:59:34 +03:00
|
|
|
csv.OptDelim,
|
|
|
|
csv.OptEmptyAsNull,
|
2024-01-15 06:56:54 +03:00
|
|
|
progress.OptDebugSleep,
|
2023-05-01 06:59:34 +03:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// filterOptionsForSrc returns a new slice containing only those
|
|
|
|
// opts that are applicable to src.
|
2023-11-21 00:42:38 +03:00
|
|
|
func filterOptionsForSrc(typ drivertype.Type, opts ...options.Opt) []options.Opt {
|
2023-05-03 15:36:10 +03:00
|
|
|
if len(opts) == 0 {
|
2023-05-01 06:59:34 +03:00
|
|
|
return opts
|
|
|
|
}
|
|
|
|
|
|
|
|
opts = lo.Reject(opts, func(opt options.Opt, index int) bool {
|
|
|
|
if opt == nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2023-07-09 04:34:53 +03:00
|
|
|
if !opt.HasTag(options.TagSource) {
|
2023-05-01 06:59:34 +03:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
key := opt.Key()
|
|
|
|
// Let's say the src has driver type "xlsx".
|
|
|
|
// If the opt has key "driver.csv.delim", we want to reject it.
|
|
|
|
// Thus, if the key has contains "driver", then it must also contain
|
|
|
|
// the src driver type.
|
2023-05-03 15:36:10 +03:00
|
|
|
if strings.Contains(key, "driver") && !strings.Contains(key, string(typ)) {
|
2023-05-01 06:59:34 +03:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
})
|
|
|
|
|
|
|
|
return opts
|
2023-04-26 18:16:42 +03:00
|
|
|
}
|
2023-05-07 05:36:34 +03:00
|
|
|
|
2023-05-22 18:08:14 +03:00
|
|
|
// addOptionFlag adds a flag derived from opt to flags, returning the
|
|
|
|
// flag name used.
|
2023-05-07 05:36:34 +03:00
|
|
|
func addOptionFlag(flags *pflag.FlagSet, opt options.Opt) (key string) {
|
2023-05-22 18:08:14 +03:00
|
|
|
key = opt.Flag()
|
|
|
|
|
2023-05-07 05:36:34 +03:00
|
|
|
switch opt := opt.(type) {
|
|
|
|
case options.Int:
|
|
|
|
if opt.Short() == 0 {
|
2023-05-22 18:08:14 +03:00
|
|
|
flags.Int(key, opt.Default(), opt.Usage())
|
2023-05-07 05:36:34 +03:00
|
|
|
return key
|
|
|
|
}
|
|
|
|
|
2023-05-22 18:08:14 +03:00
|
|
|
flags.IntP(key, string(opt.Short()), opt.Default(), opt.Usage())
|
2023-05-07 05:36:34 +03:00
|
|
|
return key
|
|
|
|
case options.Bool:
|
2024-01-15 04:45:34 +03:00
|
|
|
defVal := opt.Default()
|
|
|
|
if opt.FlagInverted() {
|
|
|
|
defVal = !defVal
|
|
|
|
}
|
2023-05-07 05:36:34 +03:00
|
|
|
if opt.Short() == 0 {
|
2024-01-15 04:45:34 +03:00
|
|
|
flags.Bool(key, defVal, opt.Usage())
|
2023-05-07 05:36:34 +03:00
|
|
|
return key
|
|
|
|
}
|
|
|
|
|
2024-01-15 04:45:34 +03:00
|
|
|
flags.BoolP(key, string(opt.Short()), defVal, opt.Usage())
|
2023-06-13 19:06:18 +03:00
|
|
|
return key
|
2023-05-07 05:36:34 +03:00
|
|
|
case options.Duration:
|
|
|
|
if opt.Short() == 0 {
|
2023-05-22 18:08:14 +03:00
|
|
|
flags.Duration(key, opt.Default(), opt.Usage())
|
2023-05-07 05:36:34 +03:00
|
|
|
return key
|
|
|
|
}
|
|
|
|
|
|
|
|
flags.DurationP(key, string(opt.Short()), opt.Get(nil), opt.Usage())
|
|
|
|
default:
|
|
|
|
// Treat as string
|
|
|
|
}
|
|
|
|
|
|
|
|
defVal := ""
|
2023-05-22 18:08:14 +03:00
|
|
|
if v := opt.DefaultAny(); v != nil {
|
2023-05-07 05:36:34 +03:00
|
|
|
defVal = fmt.Sprintf("%v", v)
|
|
|
|
}
|
|
|
|
|
|
|
|
if opt.Short() == 0 {
|
|
|
|
flags.String(key, defVal, opt.Usage())
|
|
|
|
return key
|
|
|
|
}
|
|
|
|
|
|
|
|
flags.StringP(key, string(opt.Short()), defVal, opt.Usage())
|
|
|
|
return key
|
|
|
|
}
|
|
|
|
|
|
|
|
// addTimeFormatOptsFlags adds the time format Opts flags to cmd.
|
2023-08-04 08:41:33 +03:00
|
|
|
// See: OptDatetimeFormat, OptDateFormat, OptTimeFormat,
|
|
|
|
// excelw.OptDatetimeFormat, excelw.OptDateFormat, excelw.OptTimeFormat.
|
2023-05-07 05:36:34 +03:00
|
|
|
func addTimeFormatOptsFlags(cmd *cobra.Command) {
|
|
|
|
key := addOptionFlag(cmd.Flags(), OptDatetimeFormat)
|
|
|
|
panicOn(cmd.RegisterFlagCompletionFunc(key, completeStrings(-1, timez.NamedLayouts()...)))
|
|
|
|
key = addOptionFlag(cmd.Flags(), OptDatetimeFormatAsNumber)
|
|
|
|
panicOn(cmd.RegisterFlagCompletionFunc(key, completeBool))
|
|
|
|
|
|
|
|
key = addOptionFlag(cmd.Flags(), OptDateFormat)
|
|
|
|
panicOn(cmd.RegisterFlagCompletionFunc(key, completeStrings(-1, timez.NamedLayouts()...)))
|
|
|
|
key = addOptionFlag(cmd.Flags(), OptDateFormatAsNumber)
|
|
|
|
panicOn(cmd.RegisterFlagCompletionFunc(key, completeBool))
|
|
|
|
|
|
|
|
key = addOptionFlag(cmd.Flags(), OptTimeFormat)
|
|
|
|
panicOn(cmd.RegisterFlagCompletionFunc(key, completeStrings(-1, timez.NamedLayouts()...)))
|
|
|
|
key = addOptionFlag(cmd.Flags(), OptTimeFormatAsNumber)
|
|
|
|
panicOn(cmd.RegisterFlagCompletionFunc(key, completeBool))
|
2023-08-04 08:41:33 +03:00
|
|
|
|
2023-08-20 16:22:24 +03:00
|
|
|
key = addOptionFlag(cmd.Flags(), xlsxw.OptDatetimeFormat)
|
2023-08-04 08:41:33 +03:00
|
|
|
panicOn(cmd.RegisterFlagCompletionFunc(key, completeStrings(
|
|
|
|
-1,
|
|
|
|
"yyyy-mm-dd hh:mm",
|
|
|
|
"dd/mm/yy h:mm am/pm",
|
|
|
|
"dd-mmm-yy h:mm:ss AM/PM",
|
|
|
|
)))
|
|
|
|
|
2023-08-20 16:22:24 +03:00
|
|
|
key = addOptionFlag(cmd.Flags(), xlsxw.OptDateFormat)
|
2023-08-04 08:41:33 +03:00
|
|
|
panicOn(cmd.RegisterFlagCompletionFunc(key, completeStrings(
|
|
|
|
-1,
|
|
|
|
"yyyy-mm-dd",
|
|
|
|
"dd/mm/yy",
|
|
|
|
"dd-mmm-yy",
|
|
|
|
)))
|
|
|
|
|
2023-08-20 16:22:24 +03:00
|
|
|
key = addOptionFlag(cmd.Flags(), xlsxw.OptTimeFormat)
|
2023-08-04 08:41:33 +03:00
|
|
|
panicOn(cmd.RegisterFlagCompletionFunc(key, completeStrings(
|
|
|
|
-1,
|
|
|
|
"hh:mm:ss",
|
|
|
|
"h:mm am/pm",
|
|
|
|
"h:mm:ss AM/PM",
|
|
|
|
)))
|
2023-05-07 05:36:34 +03:00
|
|
|
}
|