diff --git a/cmd/download.go b/cmd/download.go index 76a7299..7a3ed46 100644 --- a/cmd/download.go +++ b/cmd/download.go @@ -14,32 +14,33 @@ import ( "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{ - Use: "download", - Short: "Download tracks from Deezer", - Annotations: map[string]string{updateNoticeAnnotation: "true"}, -} + opts := &downloader.Options{} + cmd.PersistentFlags().StringVarP(&opts.Quality, "quality", "q", "mp3_320", "download quality [mp3_128, mp3_320, flac]") + cmd.PersistentFlags().DurationVarP(&opts.Timeout, "timeout", "t", 2*time.Minute, "timeout for each download (e.g. 10s, 1m, 2m30s)") + 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() { - RootCmd.AddCommand(downloadCmd) - - downloadCmd.PersistentFlags().StringVarP(&opts.Quality, "quality", "q", "mp3_320", "download quality [mp3_128, mp3_320, flac]") - downloadCmd.PersistentFlags().DurationVarP(&opts.Timeout, "timeout", "t", 2*time.Minute, "timeout for each download (e.g. 10s, 1m, 2m30s)") - downloadCmd.PersistentFlags().BoolVar(&opts.BPM, "bpm", false, "fetch BPM/key and add to file tags") - downloadCmd.PersistentFlags().BoolVar(&opts.Genre, "genre", false, "fetch genre and add to file tags") - 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), + // Every subcommand shares opts: registering the artist-only --limit flag + // stores its default in the shared struct, which Validate requires for all kinds. + cmd.AddCommand( + newDownloadSubCmd(deezer.KindAlbum, opts), + newDownloadSubCmd(deezer.KindPlaylist, opts), + newDownloadSubCmd(deezer.KindArtist, opts), + newDownloadSubCmd(deezer.KindTrack, opts), ) + + return cmd } -func newDownloadCmd(kind deezer.Kind) *cobra.Command { +func newDownloadSubCmd(kind deezer.Kind, opts *downloader.Options) *cobra.Command { cmd := &cobra.Command{ Use: fmt.Sprintf("%s <%s_id>", kind, kind), Short: downloadShort(kind), @@ -61,7 +62,7 @@ func newDownloadCmd(kind deezer.Kind) *cobra.Command { } 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) { return nil } diff --git a/cmd/login.go b/cmd/login.go index 4c7cd1e..8f266be 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -7,31 +7,29 @@ import ( "github.com/spf13/cobra" ) -var loginCmd = &cobra.Command{ - Use: "login", - Short: "Log in to Deezer with your email and password", - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - if err := auth.CheckGatewayEnv(); err != nil { - return err - } +func newLoginCmd() *cobra.Command { + return &cobra.Command{ + Use: "login", + Short: "Log in to Deezer with your email and password", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + if err := auth.CheckGatewayEnv(); err != nil { + return err + } - email, password, err := auth.PromptCredentials() - if err != nil { - return err - } + email, password, err := auth.PromptCredentials() + if err != nil { + return err + } - _, username, err := auth.Login(cmd.Context(), email, password) - if err != nil { - return err - } + _, username, err := auth.Login(cmd.Context(), email, password) + if err != nil { + return err + } - fmt.Printf("Successfully logged in as %s.\n", username) + fmt.Printf("Successfully logged in as %s.\n", username) - return nil - }, -} - -func init() { - RootCmd.AddCommand(loginCmd) + return nil + }, + } } diff --git a/cmd/logout.go b/cmd/logout.go index 3f6e537..e3829b6 100644 --- a/cmd/logout.go +++ b/cmd/logout.go @@ -7,21 +7,19 @@ import ( "github.com/spf13/cobra" ) -var logoutCmd = &cobra.Command{ - Use: "logout", - Short: "Remove stored Deezer credentials", - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - if err := auth.Clear(); err != nil { - return err - } +func newLogoutCmd() *cobra.Command { + return &cobra.Command{ + Use: "logout", + Short: "Remove stored Deezer credentials", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + if err := auth.Clear(); err != nil { + return err + } - fmt.Println("Successfully logged out.") + fmt.Println("Successfully logged out.") - return nil - }, -} - -func init() { - RootCmd.AddCommand(logoutCmd) + return nil + }, + } } diff --git a/cmd/root.go b/cmd/root.go index f3a41f2..c35aeb5 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -14,26 +14,40 @@ import ( 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 { + root := NewRootCmd() + var notice <-chan string - if wantsUpdateNotice() { + if wantsUpdateNotice(root) { notice = updater.StartCheck(ctx) } - err := RootCmd.ExecuteContext(ctx) + err := root.ExecuteContext(ctx) printUpdateNotice(notice) 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())) { return false } @@ -43,7 +57,7 @@ func wantsUpdateNotice() bool { return false } - target, _, err := RootCmd.Find(args) + target, _, err := root.Find(args) if err != nil || target == nil { return false } diff --git a/cmd/update.go b/cmd/update.go index 2944fb7..6f4a6d0 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -11,26 +11,34 @@ import ( "github.com/spf13/cobra" ) -var updateOpts struct { +type updateOptions struct { checkOnly bool force bool } -var updateCmd = &cobra.Command{ - Use: "update", - Short: "Update GoDeez to the latest version", - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - err := runUpdate(cmd.Context()) - if errors.Is(err, context.Canceled) { - return nil - } +func newUpdateCmd() *cobra.Command { + opts := &updateOptions{} + cmd := &cobra.Command{ + Use: "update", + Short: "Update GoDeez to the latest version", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + 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 { return err } @@ -47,13 +55,13 @@ func runUpdate(ctx context.Context) error { latest := release.Version() 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.") return nil } - if updateOpts.checkOnly { + if opts.checkOnly { fmt.Printf("Run `godeez update` to install %s.\n", latest) return nil @@ -66,10 +74,3 @@ func runUpdate(ctx context.Context) error { 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") -} diff --git a/cmd/version.go b/cmd/version.go index 77f0f48..6b6d89b 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -8,25 +8,23 @@ import ( "github.com/spf13/cobra" ) -var versionCmd = &cobra.Command{ - Use: "version", - Short: "Print the current version of GoDeez", - Args: cobra.NoArgs, - Run: func(cmd *cobra.Command, args []string) { - fmt.Printf("godeez %s\n", buildinfo.Version()) +func newVersionCmd() *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "Print the current version of GoDeez", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + fmt.Printf("godeez %s\n", buildinfo.Version()) - if commit := buildinfo.Commit(); commit != "" { - fmt.Printf(" commit: %s\n", commit) - } - if date := buildinfo.Date(); date != "" { - fmt.Printf(" built: %s\n", date) - } + if commit := buildinfo.Commit(); commit != "" { + fmt.Printf(" commit: %s\n", commit) + } + if date := buildinfo.Date(); date != "" { + fmt.Printf(" built: %s\n", date) + } - fmt.Printf(" go: %s\n", runtime.Version()) - fmt.Printf(" platform: %s/%s\n", runtime.GOOS, runtime.GOARCH) - }, -} - -func init() { - RootCmd.AddCommand(versionCmd) + fmt.Printf(" go: %s\n", runtime.Version()) + fmt.Printf(" platform: %s/%s\n", runtime.GOOS, runtime.GOARCH) + }, + } }