Files
streamrip-go/cmd/rip/main.go
T
JorenandClaude Opus 5 78d4f0316d 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>
2026-10-03 17:17:24 +02:00

129 lines
3.3 KiB
Go

package main
import (
"context"
"errors"
"fmt"
"os"
"os/signal"
"syscall"
"streamrip-go/internal/config"
"streamrip-go/internal/verbose"
_ "modernc.org/sqlite"
)
func main() {
os.Exit(run())
}
// run executes the CLI and returns the process exit code. It exists so that
// deferred cleanup (SQLite handles, progress bars, artwork temp dirs) always
// runs; calling os.Exit directly from the command bodies skipped every defer.
func run() int {
gopts, err := parseGlobalArgs(os.Args[1:])
if err != nil {
fmt.Fprintf(os.Stderr, "option error: %v\n", err)
return 2
}
if gopts.version {
printVersion(os.Stdout)
return 0
}
if gopts.help {
printMainHelp(os.Stdout)
return 0
}
if gopts.command == "" {
printMainHelp(os.Stdout)
return 2
}
if gopts.command == "help" {
if len(gopts.commandArgs) == 0 || isHelpArg(gopts.commandArgs[0]) {
printMainHelp(os.Stdout)
return 0
}
if printCommandHelp(os.Stdout, gopts.commandArgs[0]) {
return 0
}
fmt.Fprintf(os.Stderr, "unknown help topic: %s\n", gopts.commandArgs[0])
return 2
}
if gopts.command == "dev-help" {
printDeveloperHelp(os.Stdout)
return 0
}
if commandWantsHelp(gopts.commandArgs) {
if printCommandHelp(os.Stdout, gopts.command) {
return 0
}
fmt.Fprintf(os.Stderr, "unknown command: %s\n", gopts.command)
return 2
}
cfg, err := config.Load(gopts.configPath)
if err != nil {
if errors.Is(err, config.ErrOutdatedConfig) {
resolvedPath, upErr := config.UpgradeOutdated(gopts.configPath)
if upErr != nil {
fmt.Fprintf(os.Stderr, "config error: %v\n", err)
fmt.Fprintf(os.Stderr, "config auto-upgrade failed: %v\n", upErr)
return 1
}
fmt.Fprintf(os.Stderr, "config upgraded at %s\n", resolvedPath)
cfg, err = config.Load(gopts.configPath)
}
}
if err != nil {
fmt.Fprintf(os.Stderr, "config error: %v\n", err)
return 1
}
applyGlobalConfigOverrides(cfg, gopts)
verbose.SetLevel(gopts.verbose)
if gopts.verbose >= 2 {
fmt.Fprintln(os.Stderr, "verbose mode enabled (level 2: downloads + http)")
} else if gopts.verbose >= 1 {
fmt.Fprintln(os.Stderr, "verbose mode enabled (level 1: downloads)")
}
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
args := gopts.commandArgs
switch gopts.command {
case "url":
return cmdURL(ctx, cfg, gopts, args)
case "file":
return cmdFile(ctx, cfg, gopts, args)
case "config":
return cmdConfig(ctx, cfg, gopts, args)
case "database":
return cmdDatabase(ctx, cfg, gopts, args)
case "id":
return cmdID(ctx, cfg, gopts, args)
case "search":
return cmdSearch(ctx, cfg, gopts, args)
case "lastfm":
return cmdLastFM(ctx, cfg, gopts, args)
case "soundcloud-smoke":
return cmdSoundcloudSmoke(ctx, cfg, gopts, args)
case "qobuz-smoke":
return cmdQobuzSmoke(ctx, cfg, gopts, args)
case "qobuz-convert-rip-smoke":
return cmdQobuzConvertRipSmoke(ctx, cfg, gopts, args)
case "qobuz-search-smoke":
return cmdQobuzSearchSmoke(ctx, cfg, gopts, args)
case "tidal-search-smoke":
return cmdTidalSearchSmoke(ctx, cfg, gopts, args)
case "tidal-metadata-smoke":
return cmdTidalMetadataSmoke(ctx, cfg, gopts, args)
case "tidal-video-smoke":
return cmdTidalVideoSmoke(ctx, cfg, gopts, args)
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n", gopts.command)
return 2
}
}