106 lines
2.8 KiB
Go
106 lines
2.8 KiB
Go
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/viper"
|
|
"time"
|
|
)
|
|
|
|
// Opt is a single command-line option
|
|
type Opt struct {
|
|
DestP interface{} // pointer to the destination
|
|
Flag string
|
|
Default interface{}
|
|
Desc string
|
|
}
|
|
|
|
// NewOpt creates a new command line option.
|
|
func NewOpt(destP interface{}, flag string, dflt interface{}, desc string) Opt {
|
|
return Opt{
|
|
DestP: destP,
|
|
Flag: flag,
|
|
Default: dflt,
|
|
Desc: desc,
|
|
}
|
|
}
|
|
|
|
// Program parses CLI options
|
|
type Program struct {
|
|
// Run is invoked by cobra on execute.
|
|
Run func() error
|
|
// Name is the name of the program in help usage and the env var prefix.
|
|
Name string
|
|
// Opts are the command line/env var options to the program
|
|
Opts []Opt
|
|
}
|
|
|
|
// NewCommand creates a new cobra command to be executed that respects env vars.
|
|
//
|
|
// Uses the upper-case version of the program's name as a prefix
|
|
// to all environment variables.
|
|
//
|
|
// This is to simplify the viper/cobra boilerplate.
|
|
func NewCommand(p *Program) *cobra.Command {
|
|
var cmd = &cobra.Command{
|
|
Use: p.Name,
|
|
Args: cobra.NoArgs,
|
|
RunE: func(_ *cobra.Command, _ []string) error {
|
|
return p.Run()
|
|
},
|
|
}
|
|
|
|
viper.SetEnvPrefix(strings.ToUpper(p.Name))
|
|
viper.AutomaticEnv()
|
|
// This normalizes "-" to an underscore in env names.
|
|
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
|
|
|
for _, o := range p.Opts {
|
|
switch o.DestP.(type) {
|
|
case *string:
|
|
if o.Default == nil {
|
|
o.Default = ""
|
|
}
|
|
cmd.Flags().StringVar(o.DestP.(*string), o.Flag, o.Default.(string), o.Desc)
|
|
viper.BindPFlag(o.Flag, cmd.Flags().Lookup(o.Flag))
|
|
*o.DestP.(*string) = viper.GetString(o.Flag)
|
|
case *int:
|
|
if o.Default == nil {
|
|
o.Default = 0
|
|
}
|
|
cmd.Flags().IntVar(o.DestP.(*int), o.Flag, o.Default.(int), o.Desc)
|
|
viper.BindPFlag(o.Flag, cmd.Flags().Lookup(o.Flag))
|
|
*o.DestP.(*int) = viper.GetInt(o.Flag)
|
|
case *bool:
|
|
if o.Default == nil {
|
|
o.Default = false
|
|
}
|
|
cmd.Flags().BoolVar(o.DestP.(*bool), o.Flag, o.Default.(bool), o.Desc)
|
|
viper.BindPFlag(o.Flag, cmd.Flags().Lookup(o.Flag))
|
|
*o.DestP.(*bool) = viper.GetBool(o.Flag)
|
|
case *time.Duration:
|
|
if o.Default == nil {
|
|
o.Default = time.Duration(0)
|
|
}
|
|
cmd.Flags().DurationVar(o.DestP.(*time.Duration), o.Flag, o.Default.(time.Duration), o.Desc)
|
|
viper.BindPFlag(o.Flag, cmd.Flags().Lookup(o.Flag))
|
|
*o.DestP.(*time.Duration) = viper.GetDuration(o.Flag)
|
|
case *[]string:
|
|
if o.Default == nil {
|
|
o.Default = []string{}
|
|
}
|
|
cmd.Flags().StringSliceVar(o.DestP.(*[]string), o.Flag, o.Default.([]string), o.Desc)
|
|
viper.BindPFlag(o.Flag, cmd.Flags().Lookup(o.Flag))
|
|
*o.DestP.(*[]string) = viper.GetStringSlice(o.Flag)
|
|
default:
|
|
// if you get a panic here, sorry about that!
|
|
// anyway, go ahead and make a PR and add another type.
|
|
panic(fmt.Errorf("unknown destination type %t", o.DestP))
|
|
}
|
|
}
|
|
|
|
return cmd
|
|
}
|