mirror of
https://git.sr.ht/~joren/streamrip-go
synced 2026-10-16 22:07:30 +02:00
refactor: split the command dispatch out of main.go
Completes the restructuring started when main() became run() int. main.go goes
from 1217 lines to 130: global option handling, config loading, signal setup,
and a dispatch switch that delegates to one function per command.
The command bodies move to cmd_rip.go (url, file, id), cmd_search.go (search,
lastfm), cmd_config.go (config, database) and cmd_smoke.go (the developer smoke
commands), each taking (ctx, cfg, gopts, args) and returning an exit code.
They also stop reading the process global. Previously run() did
os.Args = append([]string{os.Args[0], gopts.command}, gopts.commandArgs...)
and the arms then indexed back into os.Args[2:], os.Args[5:] and so on, so the
already-parsed options were round-tripped through os.Args. The arms now take
the parsed gopts.commandArgs directly, which is a uniform index shift of two
(os.Args[5:] -> args[3:], len(os.Args) < 5 -> len(args) < 3).
No behaviour change. Verified by running each command's argument validation and
help path against the built binary, plus the existing suite.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
5 files changed
+936
-800
No files matched your search
@@ -0,0 +1,128 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"streamrip-go/internal/config"
|
||||
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// Commands that inspect local state: config and database.
|
||||
|
||||
func cmdConfig(_ context.Context, cfg *config.Config, _ globalOptions, args []string) int {
|
||||
var err error
|
||||
if len(args) < 1 {
|
||||
printCommandHelp(os.Stdout, "config")
|
||||
return 2
|
||||
}
|
||||
switch args[0] {
|
||||
case "open":
|
||||
vim := false
|
||||
for _, arg := range args[1:] {
|
||||
switch arg {
|
||||
case "-v", "--vim":
|
||||
vim = true
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "option error: unknown option %q\n", arg)
|
||||
return 2
|
||||
}
|
||||
}
|
||||
fmt.Printf("opening file at %s\n", cfg.Path)
|
||||
if err = openConfigInEditor(cfg.Path, vim); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "open config error: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
case "reset":
|
||||
yes := false
|
||||
for _, arg := range args[1:] {
|
||||
switch arg {
|
||||
case "-y", "--yes":
|
||||
yes = true
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "option error: unknown option %q\n", arg)
|
||||
return 2
|
||||
}
|
||||
}
|
||||
if !yes {
|
||||
if !term.IsTerminal(int(os.Stdin.Fd())) {
|
||||
fmt.Fprintln(os.Stderr, "reset requires --yes in non-interactive mode")
|
||||
return 2
|
||||
}
|
||||
ok, askErr := promptYesNo(fmt.Sprintf("Are you sure you want to reset the config file at %s? [y/N]: ", cfg.Path))
|
||||
if askErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "prompt error: %v\n", askErr)
|
||||
return 1
|
||||
}
|
||||
if !ok {
|
||||
fmt.Println("reset aborted")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
def := config.DefaultConfigData()
|
||||
// Separate copies: assigning the same value to both halves made
|
||||
// File.Qobuz.Secrets and Session.Qobuz.Secrets share one backing
|
||||
// array.
|
||||
cfg.File = config.CloneConfigData(def)
|
||||
cfg.Session = config.CloneConfigData(def)
|
||||
if err = cfg.SaveFile(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "reset config error: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
fmt.Printf("reset the config file at %s\n", cfg.Path)
|
||||
case "path":
|
||||
if len(args) > 1 {
|
||||
fmt.Fprintf(os.Stderr, "option error: unexpected argument %q\n", args[1])
|
||||
return 2
|
||||
}
|
||||
fmt.Printf("config path: '%s'\n", cfg.Path)
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unknown config command: %s\n", args[0])
|
||||
return 2
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func cmdDatabase(_ context.Context, cfg *config.Config, _ globalOptions, args []string) int {
|
||||
if len(args) < 2 || args[0] != "browse" {
|
||||
printCommandHelp(os.Stdout, "database")
|
||||
return 2
|
||||
}
|
||||
table := strings.ToLower(strings.TrimSpace(args[1]))
|
||||
switch table {
|
||||
case "downloads":
|
||||
rows, listErr := listDownloadsRows(cfg.Session.Database.DownloadsPath)
|
||||
if listErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "database browse error: %v\n", listErr)
|
||||
return 1
|
||||
}
|
||||
fmt.Println("downloads database")
|
||||
fmt.Println("row id")
|
||||
for i, id := range rows {
|
||||
fmt.Printf("%02d %s\n", i, id)
|
||||
}
|
||||
case "failed":
|
||||
rows, listErr := listFailedRows(cfg.Session.Database.FailedDownloadsPath)
|
||||
if listErr != nil && isNoSuchTableErr(listErr) && cfg.Session.Database.FailedDownloadsPath != cfg.Session.Database.DownloadsPath {
|
||||
rows, listErr = listFailedRows(cfg.Session.Database.DownloadsPath)
|
||||
}
|
||||
if listErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "database browse error: %v\n", listErr)
|
||||
return 1
|
||||
}
|
||||
fmt.Println("failed downloads database")
|
||||
fmt.Println("row source media_type id")
|
||||
for i, row := range rows {
|
||||
fmt.Printf("%02d %s %s %s\n", i, row.Source, row.MediaType, row.ID)
|
||||
}
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "invalid database %q. choose downloads or failed\n", table)
|
||||
return 2
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
Reference in new issue
Block a user