duyong mac 5 mesiacov pred
rodič
commit
51e9472215
13 zmenil súbory, kde vykonal 873 pridanie a 4 odobranie
  1. 1 0
      .gitignore
  2. 4 0
      README.md
  3. 258 0
      app/app.go
  4. 89 0
      app/cmd.go
  5. 63 0
      app/config.go
  6. 63 0
      app/flagsets/flagsets.go
  7. 47 0
      app/help.go
  8. 23 0
      app/options.go
  9. 87 0
      app/version/verflag.go
  10. 71 0
      app/version/version.go
  11. 46 0
      examples/app/main.go
  12. 36 1
      go.mod
  13. 85 3
      go.sum

+ 1 - 0
.gitignore

@@ -23,3 +23,4 @@
 /vendor/
 /wb
 /logs/
+test.log

+ 4 - 0
README.md

@@ -0,0 +1,4 @@
+# 错误包
+
+# 日志包
+

+ 258 - 0
app/app.go

@@ -0,0 +1,258 @@
+package app
+
+import (
+	"days-gone/app/flagsets"
+	version2 "days-gone/app/version"
+	"days-gone/logs"
+	"fmt"
+	"github.com/fatih/color"
+	"github.com/moby/term"
+	"github.com/spf13/cobra"
+	"github.com/spf13/viper"
+	"io"
+	"os"
+	"runtime"
+	"strings"
+)
+
+var progressMessage = color.GreenString("==>")
+
+type App struct {
+	basename    string
+	name        string
+	description string
+	options     CommandLineOptions
+	runFunc     RunFunc
+	silence     bool
+	noVersion   bool
+	noConfig    bool
+	commands    []*Command
+	args        cobra.PositionalArgs
+	cmd         *cobra.Command
+}
+
+type RunFunc func(basename string) error
+
+type Option func(*App)
+
+func WithOptions(opt CommandLineOptions) Option {
+	return func(a *App) {
+		a.options = opt
+	}
+}
+
+func WithRunFunc(run RunFunc) Option {
+	return func(a *App) {
+		a.runFunc = run
+	}
+}
+
+func WithDescription(desc string) Option {
+	return func(a *App) {
+		a.description = desc
+	}
+}
+
+func WithSilence() Option {
+	return func(a *App) {
+		a.silence = true
+	}
+}
+
+func WithNoVersion() Option {
+	return func(a *App) {
+		a.noVersion = true
+	}
+}
+
+func WithNoConfig() Option {
+	return func(a *App) {
+		a.noConfig = true
+	}
+}
+
+func WithValidArgs(args cobra.PositionalArgs) Option {
+	return func(a *App) {
+		a.args = args
+	}
+}
+
+func WithDefaultValidArgs() Option {
+	return func(a *App) {
+		a.args = func(cmd *cobra.Command, args []string) error {
+			for _, arg := range args {
+				if len(arg) > 0 {
+					return fmt.Errorf("%q does not take any arguments, got %q", cmd.CommandPath(), args)
+				}
+			}
+
+			return nil
+		}
+	}
+}
+
+func NewApp(name string, basename string, opts ...Option) *App {
+	a := &App{
+		name:     name,
+		basename: basename,
+	}
+
+	for _, o := range opts {
+		o(a)
+	}
+
+	a.buildCommand()
+
+	return a
+}
+
+func (a *App) buildCommand() {
+	cmd := cobra.Command{
+		Use:   FormatBaseName(a.basename),
+		Short: a.name,
+		Long:  a.description,
+		// stop printing usage when the command errors
+		SilenceUsage:  true,
+		SilenceErrors: true,
+		Args:          a.args,
+	}
+	// cmd.SetUsageTemplate(usageTemplate)
+	cmd.SetOut(os.Stdout)
+	cmd.SetErr(os.Stderr)
+	cmd.Flags().SortFlags = true
+
+	//cliflag.InitFlags(cmd.Flags())
+
+	if len(a.commands) > 0 {
+		for _, command := range a.commands {
+			cmd.AddCommand(command.cobraCommand())
+		}
+		cmd.SetHelpCommand(helpCommand(FormatBaseName(a.basename)))
+	}
+	if a.runFunc != nil {
+		cmd.RunE = a.runCommand
+	}
+
+	var namedFlagSets flagsets.NamedFlagSets
+	if a.options != nil {
+		namedFlagSets = a.options.Flags()
+		fs := cmd.Flags()
+		for _, f := range namedFlagSets.FlagSets {
+			fs.AddFlagSet(f)
+		}
+	}
+
+	if !a.noVersion {
+		version2.AddFlags(namedFlagSets.FlagSet("global"))
+	}
+	if !a.noConfig {
+		addConfigFlag(a.basename, namedFlagSets.FlagSet("global"))
+	}
+
+	cmd.Flags().AddFlagSet(namedFlagSets.FlagSet("global"))
+
+	addCmdTemplate(&cmd, namedFlagSets)
+	a.cmd = &cmd
+}
+
+func (a *App) runCommand(cmd *cobra.Command, args []string) error {
+
+	if !a.noVersion {
+		// display application version information
+		version2.PrintAndExitIfRequested()
+	}
+
+	if !a.noConfig {
+		if err := viper.BindPFlags(cmd.Flags()); err != nil {
+			return err
+		}
+
+		if err := viper.Unmarshal(a.options); err != nil {
+			return err
+		}
+	}
+
+	if !a.silence {
+		logs.Infof("%v Starting %s ...", progressMessage, a.name)
+		if !a.noVersion {
+			logs.Infof("%v Version: `%s`", progressMessage, version2.Get().ToJSON())
+		}
+		if !a.noConfig {
+			logs.Infof("%v Config file used: `%s`", progressMessage, viper.ConfigFileUsed())
+		}
+	}
+	if a.options != nil {
+		if err := a.applyOptionRules(); err != nil {
+			return err
+		}
+	}
+	// run application
+	if a.runFunc != nil {
+		return a.runFunc(a.basename)
+	}
+
+	return nil
+}
+
+func (a *App) applyOptionRules() error {
+	if completeableOptions, ok := a.options.(CompleteOptions); ok {
+		if err := completeableOptions.Complete(); err != nil {
+			return err
+		}
+	}
+
+	if errs := a.options.Validate(); len(errs) != 0 {
+		return errs[0]
+	}
+
+	if printableOptions, ok := a.options.(PrintOptions); ok && !a.silence {
+		logs.Infof("%v Config: `%s`", progressMessage, printableOptions.String())
+	}
+
+	return nil
+}
+
+func (a *App) Run() {
+	if err := a.cmd.Execute(); err != nil {
+		fmt.Printf("%v %v\n", color.RedString("Error:"), err)
+		os.Exit(1)
+	}
+}
+
+func FormatBaseName(basename string) string {
+	// Make case-insensitive and strip executable suffix if present
+	if runtime.GOOS == "windows" {
+		basename = strings.ToLower(basename)
+		basename = strings.TrimSuffix(basename, ".exe")
+	}
+
+	return basename
+}
+
+func addCmdTemplate(cmd *cobra.Command, namedFlagSets flagsets.NamedFlagSets) {
+	usageFmt := "Usage:\n  %s\n"
+	cols, _, _ := TerminalSize(cmd.OutOrStdout())
+	cmd.SetUsageFunc(func(cmd *cobra.Command) error {
+		fmt.Fprintf(cmd.OutOrStderr(), usageFmt, cmd.UseLine())
+		flagsets.PrintSections(cmd.OutOrStderr(), namedFlagSets, cols)
+
+		return nil
+	})
+	cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
+		fmt.Fprintf(cmd.OutOrStdout(), "%s\n\n"+usageFmt, cmd.Long, cmd.UseLine())
+		flagsets.PrintSections(cmd.OutOrStdout(), namedFlagSets, cols)
+	})
+}
+
+// TerminalSize  获取当前终端宽高,非终端返回err,同时宽高返回0值
+func TerminalSize(w io.Writer) (int, int, error) {
+	outFd, isTerminal := term.GetFdInfo(w)
+	if !isTerminal {
+		return 0, 0, fmt.Errorf("given writer is no terminal")
+	}
+	winSize, err := term.GetWinsize(outFd)
+	if err != nil {
+		return 0, 0, err
+	}
+	return int(winSize.Width), int(winSize.Height), nil
+}

+ 89 - 0
app/cmd.go

@@ -0,0 +1,89 @@
+package app
+
+import (
+	"fmt"
+	"github.com/fatih/color"
+	"github.com/spf13/cobra"
+	"os"
+)
+
+// Command 应用子命令结构
+type Command struct {
+	usage    string
+	desc     string
+	options  CommandLineOptions
+	commands []*Command
+	runFunc  RunCommandFunc
+}
+
+type RunCommandFunc func(args []string) error
+
+type CommandOption func(*Command)
+
+func WithCommandOptions(opt CommandLineOptions) CommandOption {
+	return func(command *Command) {
+		command.options = opt
+	}
+}
+
+func WithCommandRunFunc(run RunCommandFunc) CommandOption {
+	return func(c *Command) {
+		c.runFunc = run
+	}
+}
+
+func NewCommand(usage, desc string, opts ...CommandOption) *Command {
+	c := &Command{
+		usage: usage,
+		desc:  desc,
+	}
+	for _, o := range opts {
+		o(c)
+	}
+	return c
+}
+
+// AddCommand 当前命令添加字命令
+func (c *Command) AddCommand(cmd *Command) {
+	c.commands = append(c.commands, cmd)
+}
+
+// AddCommands 当前命令添加字命令
+func (c *Command) AddCommands(cmds ...*Command) {
+	c.commands = append(c.commands, cmds...)
+}
+
+func (c *Command) cobraCommand() *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   c.usage,
+		Short: c.desc,
+	}
+	cmd.SetOut(os.Stdout)
+	cmd.Flags().SortFlags = false
+	if len(c.commands) > 0 {
+		for _, command := range c.commands {
+			cmd.AddCommand(command.cobraCommand())
+		}
+	}
+	if c.runFunc != nil {
+		cmd.Run = c.runCommand
+	}
+	if c.options != nil {
+		for _, f := range c.options.Flags().FlagSets {
+			cmd.Flags().AddFlagSet(f)
+		}
+		// c.comopts.AddFlags(cmd.Flags())
+	}
+	addHelpCommandFlag(c.usage, cmd.Flags())
+
+	return cmd
+}
+
+func (c *Command) runCommand(cmd *cobra.Command, args []string) {
+	if c.runFunc != nil {
+		if err := c.runFunc(args); err != nil {
+			fmt.Printf("%v %v\n", color.RedString("Error:"), err)
+			os.Exit(1)
+		}
+	}
+}

+ 63 - 0
app/config.go

@@ -0,0 +1,63 @@
+package app
+
+import (
+	"fmt"
+	"github.com/gosuri/uitable"
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+	"github.com/spf13/viper"
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+const configFlagName = "config"
+
+var cfgFile string
+
+func init() {
+	pflag.StringVarP(&cfgFile, "config", "c", cfgFile, "Read configuration from specified `FILE`, "+
+		"support JSON, TOML, YAML, HCL, or Java properties formats.")
+}
+
+func addConfigFlag(basename string, fs *pflag.FlagSet) {
+	fs.AddFlag(pflag.Lookup(configFlagName))
+
+	viper.AutomaticEnv()
+	viper.SetEnvPrefix(strings.Replace(strings.ToUpper(basename), "-", "_", -1))
+	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
+
+	cobra.OnInitialize(func() {
+		if cfgFile != "" {
+			viper.SetConfigFile(cfgFile)
+		} else {
+			viper.AddConfigPath(".")
+
+			if names := strings.Split(basename, "-"); len(names) > 1 {
+				viper.AddConfigPath(filepath.Join(os.Getenv("HOME"), "."+names[0]))
+				viper.AddConfigPath(filepath.Join("/etc", names[0]))
+			}
+
+			viper.SetConfigName(basename)
+		}
+
+		if err := viper.ReadInConfig(); err != nil {
+			_, _ = fmt.Fprintf(os.Stderr, "Error: failed to read configuration file(%s): %v\n", cfgFile, err)
+			os.Exit(1)
+		}
+	})
+}
+
+func printConfig() {
+	if keys := viper.AllKeys(); len(keys) > 0 {
+		fmt.Printf("%v Configuration items:\n", progressMessage)
+		table := uitable.New()
+		table.Separator = " "
+		table.MaxColWidth = 80
+		table.RightAlign(0)
+		for _, k := range keys {
+			table.AddRow(fmt.Sprintf("%s:", k), viper.Get(k))
+		}
+		fmt.Printf("%v", table)
+	}
+}

+ 63 - 0
app/flagsets/flagsets.go

@@ -0,0 +1,63 @@
+package flagsets
+
+import (
+	"bytes"
+	"fmt"
+	"github.com/spf13/pflag"
+	"io"
+	"strings"
+)
+
+// NamedFlagSets 自定义的选项集合
+type NamedFlagSets struct {
+	Order    []string
+	FlagSets map[string]*pflag.FlagSet
+}
+
+func (nfs *NamedFlagSets) FlagSet(name string) *pflag.FlagSet {
+
+	if nfs.FlagSets == nil {
+		nfs.FlagSets = make(map[string]*pflag.FlagSet)
+	}
+	if nfs.Order == nil {
+		nfs.Order = make([]string, 0)
+	}
+
+	if _, ok := nfs.FlagSets[name]; !ok {
+		nfs.FlagSets[name] = pflag.NewFlagSet(name, pflag.ExitOnError)
+		nfs.Order = append(nfs.Order, name)
+	}
+
+	return nfs.FlagSets[name]
+}
+
+func PrintSections(w io.Writer, fss NamedFlagSets, cols int) {
+	for _, name := range fss.Order {
+		fs := fss.FlagSets[name]
+		if !fs.HasFlags() {
+			continue
+		}
+
+		wideFS := pflag.NewFlagSet("", pflag.ExitOnError)
+		wideFS.AddFlagSet(fs)
+
+		var zzz string
+		if cols > 24 {
+			zzz = strings.Repeat("z", cols-24)
+			wideFS.Int(zzz, 0, strings.Repeat("z", cols-24))
+		}
+
+		var buf bytes.Buffer
+		fmt.Fprintf(&buf, "\n%s flags:\n\n%s", strings.ToUpper(name[:1])+name[1:], wideFS.FlagUsagesWrapped(cols))
+
+		if cols > 24 {
+			i := strings.Index(buf.String(), zzz)
+			lines := strings.Split(buf.String()[:i], "\n")
+
+			fmt.Fprint(w, strings.Join(lines[:len(lines)-1], "\n"))
+			fmt.Fprintln(w)
+		} else {
+			fmt.Fprint(w, buf.String())
+		}
+	}
+}

+ 47 - 0
app/help.go

@@ -0,0 +1,47 @@
+package app
+
+import (
+	"fmt"
+	"github.com/fatih/color"
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+	"strings"
+)
+
+const (
+	flagHelp          = "help"
+	flagHelpShorthand = "h"
+)
+
+func helpCommand(name string) *cobra.Command {
+	return &cobra.Command{
+		Use:   "help [command]",
+		Short: "Help about any command.",
+		Long: `Help provides help for any command in the application.
+Simply type ` + name + ` help [path to command] for full details.`,
+
+		Run: func(c *cobra.Command, args []string) {
+			cmd, _, e := c.Root().Find(args)
+			if cmd == nil || e != nil {
+				c.Printf("Unknown help topic %#q\n", args)
+				_ = c.Root().Usage()
+			} else {
+				cmd.InitDefaultHelpFlag() // make possible 'help' flag to be shown
+				_ = cmd.Help()
+			}
+		},
+	}
+}
+
+func addHelpFlag(name string, fs *pflag.FlagSet) {
+	fs.BoolP(flagHelp, flagHelpShorthand, false, fmt.Sprintf("Help for %s.", name))
+}
+
+func addHelpCommandFlag(usage string, fs *pflag.FlagSet) {
+	fs.BoolP(
+		flagHelp,
+		flagHelpShorthand,
+		false,
+		fmt.Sprintf("Help for the %s command.", color.GreenString(strings.Split(usage, " ")[0])),
+	)
+}

+ 23 - 0
app/options.go

@@ -0,0 +1,23 @@
+package app
+
+import (
+	"days-gone/app/flagsets"
+)
+
+// CommandLineOptions 从命令行中读取选项参数接口
+type CommandLineOptions interface {
+	Flags() flagsets.NamedFlagSets
+	Validate() []error
+}
+
+// CompleteOptions 选项补全抽象接口
+type CompleteOptions interface {
+	Complete() error
+}
+
+// PrintOptions 选项打印抽象接口
+type PrintOptions interface {
+	String() string
+}
+
+// 一些常用的第三方应用配置选项。

+ 87 - 0
app/version/verflag.go

@@ -0,0 +1,87 @@
+package version
+
+import (
+	"fmt"
+	"github.com/spf13/pflag"
+	"os"
+	"strconv"
+)
+
+type versionValue int
+
+const (
+	VersionFalse versionValue = 0
+	VersionTrue  versionValue = 1
+	VersionRaw   versionValue = 2
+)
+
+const strRawVersion string = "raw"
+
+func (v *versionValue) IsBoolFlag() bool {
+	return true
+}
+
+func (v *versionValue) Get() interface{} {
+	return v
+}
+
+func (v *versionValue) Set(s string) error {
+	if s == strRawVersion {
+		*v = VersionRaw
+		return nil
+	}
+	boolVal, err := strconv.ParseBool(s)
+	if boolVal {
+		*v = VersionTrue
+	} else {
+		*v = VersionFalse
+	}
+	return err
+}
+
+func (v *versionValue) String() string {
+	if *v == VersionRaw {
+		return strRawVersion
+	}
+	return fmt.Sprintf("%v", bool(*v == VersionTrue))
+}
+
+func (v *versionValue) Type() string {
+	return "version"
+}
+
+func VersionVar(p *versionValue, name string, value versionValue, usage string) {
+	*p = value
+	pflag.Var(p, name, usage)
+	// "--version" will be treated as "--version=true"
+	pflag.Lookup(name).NoOptDefVal = "true"
+}
+
+// Version wraps the VersionVar function.
+func Version(name string, value versionValue, usage string) *versionValue {
+	p := new(versionValue)
+	VersionVar(p, name, value, usage)
+	return p
+}
+
+const versionFlagName = "version"
+
+var versionFlag = Version(versionFlagName, VersionFalse, "Print version information and quit.")
+
+// AddFlags registers this package's flags on arbitrary FlagSets, such that they point to the
+// same value as the global flags.
+func AddFlags(fs *pflag.FlagSet) {
+	fs.AddFlag(pflag.Lookup(versionFlagName))
+}
+
+// PrintAndExitIfRequested will check if the -version flag was passed
+// and, if so, print the version and exit.
+func PrintAndExitIfRequested() {
+	if *versionFlag == VersionRaw {
+		fmt.Printf("%#v\n", Get())
+		os.Exit(0)
+	} else if *versionFlag == VersionTrue {
+		fmt.Printf("%s\n", Get())
+		os.Exit(0)
+	}
+}

+ 71 - 0
app/version/version.go

@@ -0,0 +1,71 @@
+package version
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/gosuri/uitable"
+	"runtime"
+)
+
+var (
+	// GitVersion is semantic version.
+	GitVersion = "v0.0.0-master+$Format:%h$"
+	// BuildDate in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ').
+	BuildDate = "1970-01-01T00:00:00Z"
+	// GitCommit sha1 from git, output of $(git rev-parse HEAD).
+	GitCommit = "$Format:%H$"
+	// GitTreeState state of git tree, either "clean" or "dirty".
+	GitTreeState = ""
+)
+
+type Info struct {
+	GitVersion   string `json:"gitVersion"`
+	GitCommit    string `json:"gitCommit"`
+	GitTreeState string `json:"gitTreeState"`
+	BuildDate    string `json:"buildDate"`
+	GoVersion    string `json:"goVersion"`
+	Compiler     string `json:"compiler"`
+	Platform     string `json:"platform"`
+}
+
+func (info Info) String() string {
+	if s, err := info.Text(); err == nil {
+		return string(s)
+	}
+
+	return info.GitVersion
+}
+
+func (info Info) ToJSON() string {
+	s, _ := json.Marshal(info)
+
+	return string(s)
+}
+
+func (info Info) Text() ([]byte, error) {
+	table := uitable.New()
+	table.RightAlign(0)
+	table.MaxColWidth = 80
+	table.Separator = " "
+	table.AddRow("gitVersion:", info.GitVersion)
+	table.AddRow("gitCommit:", info.GitCommit)
+	table.AddRow("gitTreeState:", info.GitTreeState)
+	table.AddRow("buildDate:", info.BuildDate)
+	table.AddRow("goVersion:", info.GoVersion)
+	table.AddRow("compiler:", info.Compiler)
+	table.AddRow("platform:", info.Platform)
+
+	return table.Bytes(), nil
+}
+
+func Get() Info {
+	return Info{
+		GitVersion:   GitVersion,
+		GitCommit:    GitCommit,
+		GitTreeState: GitTreeState,
+		BuildDate:    BuildDate,
+		GoVersion:    runtime.Version(),
+		Compiler:     runtime.Compiler,
+		Platform:     fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
+	}
+}

+ 46 - 0
examples/app/main.go

@@ -0,0 +1,46 @@
+package main
+
+import (
+	"days-gone/app"
+	"days-gone/app/flagsets"
+	"fmt"
+)
+
+const commandDesc = `pcm(phone cabinet manager)  system command description`
+
+func main() {
+	newApp("pcm").Run()
+}
+
+type Options struct {
+}
+
+func (o *Options) Flags() (fss flagsets.NamedFlagSets) {
+	return fss
+}
+
+func (o *Options) Validate() []error {
+	var errs []error
+
+	return errs
+}
+
+func newApp(basename string) *app.App {
+	opts := new(Options)
+	application := app.NewApp("pcm server",
+		basename,
+		app.WithOptions(opts),
+		app.WithNoConfig(),
+		app.WithDescription(commandDesc),
+		app.WithDefaultValidArgs(),
+		app.WithRunFunc(run(opts)),
+	)
+	return application
+}
+
+func run(opts *Options) app.RunFunc {
+	return func(basename string) error {
+		fmt.Println(123)
+		return nil
+	}
+}

+ 36 - 1
go.mod

@@ -2,4 +2,39 @@ module days-gone
 
 go 1.19
 
-require github.com/novalagung/gubrak v1.0.0
+require (
+	github.com/fatih/color v1.16.0
+	github.com/gosuri/uitable v0.0.4
+	github.com/novalagung/gubrak v1.0.0
+	github.com/spf13/cobra v1.8.0
+	github.com/spf13/pflag v1.0.5
+	github.com/spf13/viper v1.18.2
+	go.uber.org/zap v1.27.0
+)
+
+require (
+	github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
+	github.com/fsnotify/fsnotify v1.7.0 // indirect
+	github.com/hashicorp/hcl v1.0.0 // indirect
+	github.com/inconshreveable/mousetrap v1.1.0 // indirect
+	github.com/magiconair/properties v1.8.7 // indirect
+	github.com/mattn/go-colorable v0.1.13 // indirect
+	github.com/mattn/go-isatty v0.0.20 // indirect
+	github.com/mattn/go-runewidth v0.0.15 // indirect
+	github.com/mitchellh/mapstructure v1.5.0 // indirect
+	github.com/moby/term v0.5.0 // indirect
+	github.com/pelletier/go-toml/v2 v2.1.0 // indirect
+	github.com/rivo/uniseg v0.2.0 // indirect
+	github.com/sagikazarmark/locafero v0.4.0 // indirect
+	github.com/sagikazarmark/slog-shim v0.1.0 // indirect
+	github.com/sourcegraph/conc v0.3.0 // indirect
+	github.com/spf13/afero v1.11.0 // indirect
+	github.com/spf13/cast v1.6.0 // indirect
+	github.com/subosito/gotenv v1.6.0 // indirect
+	go.uber.org/multierr v1.10.0 // indirect
+	golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
+	golang.org/x/sys v0.15.0 // indirect
+	golang.org/x/text v0.14.0 // indirect
+	gopkg.in/ini.v1 v1.67.0 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)

+ 85 - 3
go.sum

@@ -1,10 +1,92 @@
+github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
+github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
 github.com/DefinitelyMod/gocsv v0.0.0-20181205141819-acfa5f112b45 h1:+OD9vawobD89HK04zwMokunBCSEeAb08VWAHPUMg+UE=
 github.com/DefinitelyMod/gocsv v0.0.0-20181205141819-acfa5f112b45/go.mod h1:+nlrAh0au59iC1KN5RA1h1NdiOQYlNOBrbtE1Plqht4=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
+github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
+github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
+github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
+github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY=
+github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
+github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
+github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
+github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
+github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
+github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
 github.com/novalagung/gubrak v1.0.0 h1:+iDvzUcSHUoa3bwP/ig40K2h9X+5cX2w5qcBb3izAwo=
 github.com/novalagung/gubrak v1.0.0/go.mod h1:lahTbjdK/OLI9Y4alRlf003XEwbiOj7ERkmDHFFbzLk=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
+github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
+github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
+github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
+github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
+github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
+github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
+github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
+github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
+github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
+github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
+github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
+github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
+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.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
+github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
+github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
+go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
+golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
+golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=