From 4bee6840dc2e60b25bfcb79c987f923224966021 Mon Sep 17 00:00:00 2001 From: Daniel Ponte Date: Sun, 24 Nov 2024 10:38:19 -0500 Subject: [PATCH] Move to urfave/cli --- cmd/stillbox/main.go | 58 ++++++++++++++++++-------- go.mod | 7 ++-- go.sum | 14 +++---- internal/common/common.go | 21 ++++------ pkg/cmd/admin/admin.go | 85 ++++++++++++++++++++++++--------------- pkg/cmd/serve/serve.go | 15 ++++--- pkg/config/config.go | 2 +- pkg/config/parse.go | 25 +++++------- 8 files changed, 130 insertions(+), 97 deletions(-) diff --git a/cmd/stillbox/main.go b/cmd/stillbox/main.go index b32a0cd..32eec62 100644 --- a/cmd/stillbox/main.go +++ b/cmd/stillbox/main.go @@ -13,28 +13,52 @@ import ( "dynatron.me/x/stillbox/pkg/cmd/serve" "dynatron.me/x/stillbox/pkg/config" - "github.com/spf13/cobra" + "github.com/urfave/cli/v2" ) +const DefaultConfig = "config.yaml" + func main() { + configFile := DefaultConfig log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: common.TimeFormat}) - rootCmd := &cobra.Command{ - Use: common.AppName, - } - rootCmd.PersistentFlags().BoolP("version", "V", false, "show version") - cfg := config.New(rootCmd) - rootCmd.PreRun = func(cmd *cobra.Command, args []string) { - v, _ := rootCmd.PersistentFlags().GetBool("version") - if v { - fmt.Print(version.String()) - os.Exit(0) - } + cfg := config.New(&configFile) + app := &cli.App{ + Name: common.AppName, + Usage: "a scanner call server", + UseShortOptionHandling: true, + Before: cfg.Before, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "config", + Value: DefaultConfig, + Usage: "configuration file", + Destination: &configFile, + Aliases: []string{"c"}, + }, + &cli.BoolFlag{ + Name: "version", + Aliases: []string{"V"}, + Action: func(_ *cli.Context, v bool) error { + if v { + fmt.Print(version.String()) + os.Exit(0) + } + + return nil + }, + DisableDefaultText: true, + }, + }, + Commands: []*cli.Command{ + serve.Command(cfg), + admin.Command(cfg), + }, } - cmds := append([]*cobra.Command{serve.Command(cfg)}, admin.Command(cfg)...) - rootCmd.AddCommand(cmds...) - - // cobra is already checking for errors and will print them - _ = rootCmd.Execute() + err := app.Run(os.Args) + if err != nil { + os.Stderr.Write([]byte("Error: " + err.Error() + "\n")) + os.Exit(1) + } } diff --git a/go.mod b/go.mod index 0f36159..47af3ec 100644 --- a/go.mod +++ b/go.mod @@ -23,9 +23,9 @@ require ( github.com/knadh/koanf/v2 v2.1.2 github.com/nikoksr/notify v1.1.0 github.com/rs/zerolog v1.33.0 - github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 github.com/tcolgate/mp3 v0.0.0-20170426193717-e79c5a46d300 + github.com/urfave/cli/v2 v2.27.5 golang.org/x/crypto v0.29.0 golang.org/x/sync v0.9.0 golang.org/x/term v0.26.0 @@ -36,6 +36,7 @@ require ( require ( github.com/ajg/form v1.5.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect @@ -44,7 +45,6 @@ require ( github.com/goccy/go-json v0.10.3 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect @@ -61,9 +61,10 @@ require ( github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/segmentio/asm v1.2.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.2 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect go.uber.org/atomic v1.11.0 // indirect golang.org/x/exp/shiny v0.0.0-20241108190413-2d47ceb2692f // indirect golang.org/x/image v0.22.0 // indirect diff --git a/go.sum b/go.sum index 126273f..c0df874 100644 --- a/go.sum +++ b/go.sum @@ -11,7 +11,8 @@ github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoU github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -73,8 +74,6 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0= github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= @@ -148,14 +147,11 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99 github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= @@ -167,6 +163,10 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tcolgate/mp3 v0.0.0-20170426193717-e79c5a46d300 h1:XQdibLKagjdevRB6vAjVY4qbSr8rQ610YzTkWcxzxSI= github.com/tcolgate/mp3 v0.0.0-20170426193717-e79c5a46d300/go.mod h1:FNa/dfN95vAYCNFrIKRrlRo+MBLbwmR9Asa5f2ljmBI= +github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= +github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= diff --git a/internal/common/common.go b/internal/common/common.go index 7eb0e70..ea19e5f 100644 --- a/internal/common/common.go +++ b/internal/common/common.go @@ -1,38 +1,31 @@ package common import ( - "github.com/spf13/cobra" + "github.com/urfave/cli/v2" ) const ( - AppName = "stillbox" + AppName = "stillbox" EnvPrefix = "STILLBOX_" ) - const ( TimeFormat = "Jan 2 15:04:05" ) type cmdOptions interface { - Options(*cobra.Command, []string) error + Options(*cli.Context) error Execute() error } -func RunE(c cmdOptions) func(cmd *cobra.Command, args []string) error { - return func(cmd *cobra.Command, args []string) error { - err := c.Options(cmd, args) +func Action(c cmdOptions) cli.ActionFunc { + return func(ctx *cli.Context) error { + err := c.Options(ctx) if err != nil { - cmd.SilenceUsage = true return err } - err = c.Execute() - if err != nil { - cmd.SilenceUsage = true - } - - return err + return c.Execute() } } diff --git a/pkg/cmd/admin/admin.go b/pkg/cmd/admin/admin.go index d515bf4..8a0aae8 100644 --- a/pkg/cmd/admin/admin.go +++ b/pkg/cmd/admin/admin.go @@ -8,7 +8,7 @@ import ( "dynatron.me/x/stillbox/pkg/config" "dynatron.me/x/stillbox/pkg/database" - "github.com/spf13/cobra" + "github.com/urfave/cli/v2" "golang.org/x/crypto/bcrypt" "golang.org/x/term" ) @@ -115,59 +115,78 @@ func readPassword(prompt string) (string, error) { } // Command is the users command. -func Command(cfg *config.Configuration) []*cobra.Command { - userCmd := &cobra.Command{ - Use: "users", - Aliases: []string{"u"}, - Short: "administers the server", - PersistentPreRunE: cfg.PreRunE(), +func Command(cfg *config.Configuration) *cli.Command { + c := &cfg.Config + userCmd := &cli.Command{ + Name: "users", + Aliases: []string{"u"}, + Usage: "administers users", + Subcommands: []*cli.Command{ + addUserCommand(c), + passwdCommand(c), + }, } - userCmd.AddCommand(addUserCommand(&cfg.Config), passwdCommand(&cfg.Config)) - return []*cobra.Command{userCmd} + return userCmd } -func addUserCommand(cfg *config.Config) *cobra.Command { - c := &cobra.Command{ - Use: "add", - Short: "adds a user", - RunE: func(cmd *cobra.Command, args []string) error { +func addUserCommand(cfg *config.Config) *cli.Command { + c := &cli.Command{ + Name: "add", + Description: "adds a user", + UsageText: "stillbox users add [-a] [-m email] [username]", + Args: true, + Action: func(ctx *cli.Context) error { + if ctx.Args().Len() != 1 { + return errors.New(ctx.Command.Usage) + } + db, err := database.NewClient(context.Background(), cfg.DB) if err != nil { return err } - username := args[0] - isAdmin, err := cmd.Flags().GetBool("admin") - if err != nil { - return err - } - email, err := cmd.Flags().GetString("email") - if err != nil { - return err - } + username := ctx.Args().Get(0) + isAdmin := ctx.Bool("admin") + email := ctx.String("email") return AddUser(database.CtxWithDB(context.Background(), db), username, email, isAdmin) }, - Args: cobra.ExactArgs(1), + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "admin", + Aliases: []string{"a"}, + Value: false, + Usage: "user is an admin", + }, + &cli.StringFlag{ + Name: "email", + Usage: "email address", + Aliases: []string{"m"}, + }, + }, } - c.Flags().BoolP("admin", "a", false, "is admin") - c.Flags().StringP("email", "m", "", "email address") return c } -func passwdCommand(cfg *config.Config) *cobra.Command { - c := &cobra.Command{ - Use: "passwd userid", - Short: "changes password for a user", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { +func passwdCommand(cfg *config.Config) *cli.Command { + c := &cli.Command{ + Name: "passwd", + Usage: "changes password for a user", + UsageText: "stillbox users passwd [username]", + Args: true, + Action: func(ctx *cli.Context) error { + if ctx.Args().Len() != 1 { + return errors.New(ctx.Command.Usage) + } + db, err := database.NewClient(context.Background(), cfg.DB) if err != nil { return err } - username := args[0] + username := ctx.Args().Get(0) + return Passwd(database.CtxWithDB(context.Background(), db), username) }, } diff --git a/pkg/cmd/serve/serve.go b/pkg/cmd/serve/serve.go index f5ea062..c1d2304 100644 --- a/pkg/cmd/serve/serve.go +++ b/pkg/cmd/serve/serve.go @@ -9,20 +9,19 @@ import ( "dynatron.me/x/stillbox/pkg/config" "dynatron.me/x/stillbox/pkg/server" - "github.com/spf13/cobra" + "github.com/urfave/cli/v2" ) type ServeOptions struct { cfg *config.Configuration } -func Command(cfg *config.Configuration) *cobra.Command { +func Command(cfg *config.Configuration) *cli.Command { opts := makeOptions(cfg) - serveCmd := &cobra.Command{ - Use: "serve", - Short: "starts the" + common.AppName + " server", - PersistentPreRunE: cfg.PreRunE(), - RunE: common.RunE(opts), + serveCmd := &cli.Command{ + Name: "serve", + Usage: "starts the " + common.AppName + " server", + Action: common.Action(opts), } return serveCmd @@ -34,7 +33,7 @@ func makeOptions(cfg *config.Configuration) *ServeOptions { } } -func (o *ServeOptions) Options(_ *cobra.Command, args []string) error { +func (o *ServeOptions) Options(_ *cli.Context) error { return nil } diff --git a/pkg/config/config.go b/pkg/config/config.go index e934d06..7d67a9b 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -12,7 +12,7 @@ import ( type Configuration struct { Config - configPath string `yaml:"-"` + configPath *string `yaml:"-"` } type Config struct { diff --git a/pkg/config/parse.go b/pkg/config/parse.go index e8bff5a..f8e9ac0 100644 --- a/pkg/config/parse.go +++ b/pkg/config/parse.go @@ -11,34 +11,31 @@ import ( "github.com/knadh/koanf/providers/env" "github.com/knadh/koanf/providers/file" "github.com/knadh/koanf/v2" - // "github.com/knadh/koanf/providers/posflag" "github.com/rs/zerolog/log" - "github.com/spf13/cobra" + "github.com/urfave/cli/v2" ) -func (c *Configuration) PreRunE() func(*cobra.Command, []string) error { - return func(cmd *cobra.Command, args []string) error { - return c.ReadConfig() +func New(configFile *string) *Configuration { + if configFile == nil { + panic("configFile must not be nil") } + + return &Configuration{configPath: configFile} } -func New(rootCommand *cobra.Command) *Configuration { - c := &Configuration{} - - rootCommand.PersistentFlags().StringVarP(&c.configPath, "config", "c", "config.yaml", "configuration file") - - return c +func (c *Configuration) Before(ctx *cli.Context) error { + return c.ReadConfig() } func (c *Configuration) ReadConfig() error { - log.Info().Str("configPath", c.configPath).Msg("read config") + log.Info().Str("configPath", *c.configPath).Msg("read config") return c.read() } func (c *Configuration) read() error { k := koanf.New(".") - err := k.Load(file.Provider(c.configPath), yaml.Parser()) + err := k.Load(file.Provider(*c.configPath), yaml.Parser()) if err != nil { return err } @@ -52,7 +49,7 @@ func (c *Configuration) read() error { koanf.UnmarshalConf{ Tag: "yaml", DecoderConfig: &mapstructure.DecoderConfig{ - Result: &c.Config, + Result: &c.Config, WeaklyTypedInput: true, DecodeHook: mapstructure.ComposeDecodeHookFunc( mapstructure.StringToTimeDurationHookFunc(),