refactor: build cobra commands with constructors

This commit is contained in:
Mathis Maquenne
2026-08-05 11:35:52 +02:00
parent 0ab25addd5
commit 3c9da5f24a
6 changed files with 121 additions and 111 deletions
+23 -22
View File
@@ -14,32 +14,33 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var opts downloader.Options func newDownloadCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "download",
Short: "Download tracks from Deezer",
Annotations: map[string]string{updateNoticeAnnotation: "true"},
}
var downloadCmd = &cobra.Command{ opts := &downloader.Options{}
Use: "download", cmd.PersistentFlags().StringVarP(&opts.Quality, "quality", "q", "mp3_320", "download quality [mp3_128, mp3_320, flac]")
Short: "Download tracks from Deezer", cmd.PersistentFlags().DurationVarP(&opts.Timeout, "timeout", "t", 2*time.Minute, "timeout for each download (e.g. 10s, 1m, 2m30s)")
Annotations: map[string]string{updateNoticeAnnotation: "true"}, cmd.PersistentFlags().BoolVar(&opts.BPM, "bpm", false, "fetch BPM/key and add to file tags")
} cmd.PersistentFlags().BoolVar(&opts.Genre, "genre", false, "fetch genre and add to file tags")
cmd.PersistentFlags().BoolVar(&opts.Strict, "strict", false, "fail the download if the requested quality is unavailable")
func init() { // Every subcommand shares opts: registering the artist-only --limit flag
RootCmd.AddCommand(downloadCmd) // stores its default in the shared struct, which Validate requires for all kinds.
cmd.AddCommand(
downloadCmd.PersistentFlags().StringVarP(&opts.Quality, "quality", "q", "mp3_320", "download quality [mp3_128, mp3_320, flac]") newDownloadSubCmd(deezer.KindAlbum, opts),
downloadCmd.PersistentFlags().DurationVarP(&opts.Timeout, "timeout", "t", 2*time.Minute, "timeout for each download (e.g. 10s, 1m, 2m30s)") newDownloadSubCmd(deezer.KindPlaylist, opts),
downloadCmd.PersistentFlags().BoolVar(&opts.BPM, "bpm", false, "fetch BPM/key and add to file tags") newDownloadSubCmd(deezer.KindArtist, opts),
downloadCmd.PersistentFlags().BoolVar(&opts.Genre, "genre", false, "fetch genre and add to file tags") newDownloadSubCmd(deezer.KindTrack, opts),
downloadCmd.PersistentFlags().BoolVar(&opts.Strict, "strict", false, "fail the download if the requested quality is unavailable")
downloadCmd.AddCommand(
newDownloadCmd(deezer.KindAlbum),
newDownloadCmd(deezer.KindPlaylist),
newDownloadCmd(deezer.KindArtist),
newDownloadCmd(deezer.KindTrack),
) )
return cmd
} }
func newDownloadCmd(kind deezer.Kind) *cobra.Command { func newDownloadSubCmd(kind deezer.Kind, opts *downloader.Options) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: fmt.Sprintf("%s <%s_id>", kind, kind), Use: fmt.Sprintf("%s <%s_id>", kind, kind),
Short: downloadShort(kind), Short: downloadShort(kind),
@@ -61,7 +62,7 @@ func newDownloadCmd(kind deezer.Kind) *cobra.Command {
} }
defer st.Close() defer st.Close()
err = downloader.New(cfg, st, kind).Run(cmd.Context(), opts, args[0]) err = downloader.New(cfg, st, kind).Run(cmd.Context(), *opts, args[0])
if errors.Is(err, context.Canceled) { if errors.Is(err, context.Canceled) {
return nil return nil
} }
+21 -23
View File
@@ -7,31 +7,29 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var loginCmd = &cobra.Command{ func newLoginCmd() *cobra.Command {
Use: "login", return &cobra.Command{
Short: "Log in to Deezer with your email and password", Use: "login",
Args: cobra.NoArgs, Short: "Log in to Deezer with your email and password",
RunE: func(cmd *cobra.Command, args []string) error { Args: cobra.NoArgs,
if err := auth.CheckGatewayEnv(); err != nil { RunE: func(cmd *cobra.Command, args []string) error {
return err if err := auth.CheckGatewayEnv(); err != nil {
} return err
}
email, password, err := auth.PromptCredentials() email, password, err := auth.PromptCredentials()
if err != nil { if err != nil {
return err return err
} }
_, username, err := auth.Login(cmd.Context(), email, password) _, username, err := auth.Login(cmd.Context(), email, password)
if err != nil { if err != nil {
return err return err
} }
fmt.Printf("Successfully logged in as %s.\n", username) fmt.Printf("Successfully logged in as %s.\n", username)
return nil return nil
}, },
} }
func init() {
RootCmd.AddCommand(loginCmd)
} }
+13 -15
View File
@@ -7,21 +7,19 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var logoutCmd = &cobra.Command{ func newLogoutCmd() *cobra.Command {
Use: "logout", return &cobra.Command{
Short: "Remove stored Deezer credentials", Use: "logout",
Args: cobra.NoArgs, Short: "Remove stored Deezer credentials",
RunE: func(cmd *cobra.Command, args []string) error { Args: cobra.NoArgs,
if err := auth.Clear(); err != nil { RunE: func(cmd *cobra.Command, args []string) error {
return err if err := auth.Clear(); err != nil {
} return err
}
fmt.Println("Successfully logged out.") fmt.Println("Successfully logged out.")
return nil return nil
}, },
} }
func init() {
RootCmd.AddCommand(logoutCmd)
} }
+24 -10
View File
@@ -14,26 +14,40 @@ import (
const updateNoticeAnnotation = "godeez:update-notice" const updateNoticeAnnotation = "godeez:update-notice"
var RootCmd = &cobra.Command{
Use: "godeez",
Short: "GoDeez is a tool to download music from Deezer",
SilenceUsage: true,
}
func Execute(ctx context.Context) error { func Execute(ctx context.Context) error {
root := NewRootCmd()
var notice <-chan string var notice <-chan string
if wantsUpdateNotice() { if wantsUpdateNotice(root) {
notice = updater.StartCheck(ctx) notice = updater.StartCheck(ctx)
} }
err := RootCmd.ExecuteContext(ctx) err := root.ExecuteContext(ctx)
printUpdateNotice(notice) printUpdateNotice(notice)
return err return err
} }
func wantsUpdateNotice() bool { func NewRootCmd() *cobra.Command {
root := &cobra.Command{
Use: "godeez",
Short: "GoDeez is a tool to download music from Deezer",
SilenceUsage: true,
}
root.AddCommand(
newDownloadCmd(),
newLoginCmd(),
newLogoutCmd(),
newUpdateCmd(),
newVersionCmd(),
)
return root
}
func wantsUpdateNotice(root *cobra.Command) bool {
if !term.IsTerminal(int(os.Stderr.Fd())) { if !term.IsTerminal(int(os.Stderr.Fd())) {
return false return false
} }
@@ -43,7 +57,7 @@ func wantsUpdateNotice() bool {
return false return false
} }
target, _, err := RootCmd.Find(args) target, _, err := root.Find(args)
if err != nil || target == nil { if err != nil || target == nil {
return false return false
} }
+23 -22
View File
@@ -11,26 +11,34 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var updateOpts struct { type updateOptions struct {
checkOnly bool checkOnly bool
force bool force bool
} }
var updateCmd = &cobra.Command{ func newUpdateCmd() *cobra.Command {
Use: "update", opts := &updateOptions{}
Short: "Update GoDeez to the latest version", cmd := &cobra.Command{
Args: cobra.NoArgs, Use: "update",
RunE: func(cmd *cobra.Command, args []string) error { Short: "Update GoDeez to the latest version",
err := runUpdate(cmd.Context()) Args: cobra.NoArgs,
if errors.Is(err, context.Canceled) { RunE: func(cmd *cobra.Command, args []string) error {
return nil err := runUpdate(cmd.Context(), opts)
} if errors.Is(err, context.Canceled) {
return nil
}
return err return err
}, },
}
cmd.Flags().BoolVar(&opts.checkOnly, "check", false, "only report whether an update is available")
cmd.Flags().BoolVar(&opts.force, "force", false, "reinstall even if already up to date")
return cmd
} }
func runUpdate(ctx context.Context) error { func runUpdate(ctx context.Context, opts *updateOptions) error {
if err := updater.CheckUpdatable(); err != nil { if err := updater.CheckUpdatable(); err != nil {
return err return err
} }
@@ -47,13 +55,13 @@ func runUpdate(ctx context.Context) error {
latest := release.Version() latest := release.Version()
fmt.Printf("Current: %s\nLatest: %s\n", current, latest) fmt.Printf("Current: %s\nLatest: %s\n", current, latest)
if !updater.IsNewer(current, latest) && !updateOpts.force { if !updater.IsNewer(current, latest) && !opts.force {
fmt.Println("Already up to date.") fmt.Println("Already up to date.")
return nil return nil
} }
if updateOpts.checkOnly { if opts.checkOnly {
fmt.Printf("Run `godeez update` to install %s.\n", latest) fmt.Printf("Run `godeez update` to install %s.\n", latest)
return nil return nil
@@ -66,10 +74,3 @@ func runUpdate(ctx context.Context) error {
return nil return nil
} }
func init() {
RootCmd.AddCommand(updateCmd)
updateCmd.Flags().BoolVar(&updateOpts.checkOnly, "check", false, "only report whether an update is available")
updateCmd.Flags().BoolVar(&updateOpts.force, "force", false, "reinstall even if already up to date")
}
+17 -19
View File
@@ -8,25 +8,23 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var versionCmd = &cobra.Command{ func newVersionCmd() *cobra.Command {
Use: "version", return &cobra.Command{
Short: "Print the current version of GoDeez", Use: "version",
Args: cobra.NoArgs, Short: "Print the current version of GoDeez",
Run: func(cmd *cobra.Command, args []string) { Args: cobra.NoArgs,
fmt.Printf("godeez %s\n", buildinfo.Version()) Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("godeez %s\n", buildinfo.Version())
if commit := buildinfo.Commit(); commit != "" { if commit := buildinfo.Commit(); commit != "" {
fmt.Printf(" commit: %s\n", commit) fmt.Printf(" commit: %s\n", commit)
} }
if date := buildinfo.Date(); date != "" { if date := buildinfo.Date(); date != "" {
fmt.Printf(" built: %s\n", date) fmt.Printf(" built: %s\n", date)
} }
fmt.Printf(" go: %s\n", runtime.Version()) fmt.Printf(" go: %s\n", runtime.Version())
fmt.Printf(" platform: %s/%s\n", runtime.GOOS, runtime.GOARCH) fmt.Printf(" platform: %s/%s\n", runtime.GOOS, runtime.GOARCH)
}, },
} }
func init() {
RootCmd.AddCommand(versionCmd)
} }