From 0b090c2698607fcdef039fb703c8c6bac5fceca0 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Tue, 14 Apr 2026 11:31:39 +0300 Subject: refactor(cli): split run* handlers into files (ask 24) Move runImport, runQuery, runReportFromFiles, runDaemon, runCreateClientKey, and runTests into dedicated cmd_*.go files. Keep Execute routing in cli.go. defaultListenFromEnv stays with daemon. Made-with: Cursor --- internal/cli/cli.go | 164 ---------------------------------- internal/cli/cmd_create_client_key.go | 47 ++++++++++ internal/cli/cmd_daemon.go | 43 +++++++++ internal/cli/cmd_import.go | 38 ++++++++ internal/cli/cmd_query.go | 35 ++++++++ internal/cli/cmd_report_from_files.go | 38 ++++++++ internal/cli/cmd_tests.go | 7 ++ 7 files changed, 208 insertions(+), 164 deletions(-) create mode 100644 internal/cli/cmd_create_client_key.go create mode 100644 internal/cli/cmd_daemon.go create mode 100644 internal/cli/cmd_import.go create mode 100644 internal/cli/cmd_query.go create mode 100644 internal/cli/cmd_report_from_files.go create mode 100644 internal/cli/cmd_tests.go diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 3c686db..e791512 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -1,24 +1,12 @@ package cli import ( - "context" - "errors" - "flag" "fmt" - "os" - "os/signal" - "syscall" - "codeberg.org/snonux/goprecords/internal/authkeys" - "codeberg.org/snonux/goprecords/internal/daemon" - "codeberg.org/snonux/goprecords/internal/goprecords" - "codeberg.org/snonux/goprecords/internal/storage" "codeberg.org/snonux/goprecords/internal/version" ) -// Execute parses command‑line arguments and runs the appropriate sub‑command. func Execute(args []string) error { - // Handle version flag early. if len(args) > 0 && (args[0] == "-version" || args[0] == "--version") { fmt.Println(version.Tag) return nil @@ -33,7 +21,6 @@ func Execute(args []string) error { return runDaemon(args[1:]) } - // No subcommand – treat args as flags for a direct report from files. if len(args) == 0 { return runReportFromFiles(nil) } @@ -49,154 +36,3 @@ func Execute(args []string) error { return runReportFromFiles(args) } } - -func runImport(args []string) error { - fs := flag.NewFlagSet("import", flag.ExitOnError) - statsDir := fs.String("stats-dir", "", "Directory containing .records files (required)") - dbPath := fs.String("db", "goprecords.db", "SQLite database path") - if err := fs.Parse(args); err != nil { - return err - } - if *statsDir == "" { - fmt.Fprintln(os.Stderr, "import: missing required flag: -stats-dir") - fs.Usage() - return fmt.Errorf("missing -stats-dir") - } - ctx := context.Background() - db, err := storage.Open(ctx, *dbPath) - if err != nil { - return fmt.Errorf("open db: %w", err) - } - defer db.Close() - if err := storage.CreateSchema(ctx, db); err != nil { - return fmt.Errorf("schema: %w", err) - } - if err := storage.ImportFromDir(ctx, db, *statsDir); err != nil { - return fmt.Errorf("import: %w", err) - } - fmt.Fprintf(os.Stderr, "imported %s into %s\n", *statsDir, *dbPath) - return nil -} - -func runQuery(args []string) error { - fs := flag.NewFlagSet("query", flag.ExitOnError) - dbPath := fs.String("db", "goprecords.db", "SQLite database path") - rf := goprecords.RegisterReportFlags(fs) - if err := fs.Parse(args); err != nil { - return err - } - ctx := context.Background() - db, err := storage.Open(ctx, *dbPath) - if err != nil { - return fmt.Errorf("open db: %w", err) - } - defer db.Close() - aggregates, err := goprecords.LoadAggregates(ctx, db) - if err != nil { - return fmt.Errorf("load: %w", err) - } - cfg, err := rf.Parse() - if err != nil { - return err - } - return goprecords.WriteReports(os.Stdout, aggregates, cfg) -} - -func runReportFromFiles(args []string) error { - if args == nil { - args = []string{} - } - fs := flag.NewFlagSet("goprecords", flag.ExitOnError) - statsDir := fs.String("stats-dir", "", "The uptimed raw record input dir (required)") - rf := goprecords.RegisterReportFlags(fs) - if err := fs.Parse(args); err != nil { - return err - } - if *statsDir == "" { - fmt.Fprintln(os.Stderr, "missing required flag: -stats-dir") - fs.Usage() - return fmt.Errorf("missing -stats-dir") - } - cfg, err := rf.Parse() - if err != nil { - return err - } - ctx := context.Background() - aggr := goprecords.NewAggregator(*statsDir) - aggregates, err := aggr.Aggregate(ctx) - if err != nil { - return err - } - return goprecords.WriteReports(os.Stdout, aggregates, cfg) -} - -func runTests() error { - return goprecords.RunIntegrationTests("./fixtures") -} - -func defaultListenFromEnv() string { - if s := os.Getenv("GOPRECORDS_LISTEN"); s != "" { - return s - } - return ":8080" -} - -func runDaemon(args []string) error { - fs := flag.NewFlagSet("daemon", flag.ExitOnError) - fs.SetOutput(os.Stdout) - statsDir := fs.String("stats-dir", os.Getenv("GOPRECORDS_STATS_DIR"), "Uptimed stats directory (required; env GOPRECORDS_STATS_DIR)") - listen := fs.String("listen", defaultListenFromEnv(), "TCP listen address (env GOPRECORDS_LISTEN, default :8080)") - authDB := fs.String("auth-db", "", "SQLite file for upload API keys (default: /goprecords-auth.db)") - if err := fs.Parse(args); err != nil { - return err - } - if *statsDir == "" { - fmt.Fprintln(os.Stdout, "daemon: missing required flag: -stats-dir (or GOPRECORDS_STATS_DIR)") - fs.Usage() - return fmt.Errorf("missing -stats-dir") - } - ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) - defer stop() - err := daemon.Run(ctx, daemon.Config{StatsDir: *statsDir, Addr: *listen, AuthDB: *authDB}) - if err != nil && !errors.Is(err, context.Canceled) { - return err - } - return nil -} - -func runCreateClientKey(hostname string, args []string) error { - if hostname == "" { - return fmt.Errorf("create-client-key: hostname required") - } - fs := flag.NewFlagSet("create-client-key", flag.ExitOnError) - fs.SetOutput(os.Stderr) - statsDir := fs.String("stats-dir", "", "Uptimed stats directory (sets default auth-db path)") - authDB := fs.String("auth-db", "", "SQLite file for upload API keys (default: /goprecords-auth.db)") - if err := fs.Parse(args); err != nil { - return err - } - authPath := *authDB - if authPath == "" { - if *statsDir == "" { - fmt.Fprintln(os.Stderr, "create-client-key: need -stats-dir or -auth-db") - fs.Usage() - return fmt.Errorf("missing -stats-dir or -auth-db") - } - authPath = authkeys.DefaultPath(*statsDir) - } - ctx := context.Background() - store, err := authkeys.OpenStore(ctx, authPath) - if err != nil { - return fmt.Errorf("open auth db: %w", err) - } - defer store.Close() - if err := store.EnsureSchema(ctx); err != nil { - return fmt.Errorf("schema: %w", err) - } - token, err := store.CreateKey(ctx, hostname) - if err != nil { - return fmt.Errorf("create key: %w", err) - } - fmt.Println(token) - return nil -} diff --git a/internal/cli/cmd_create_client_key.go b/internal/cli/cmd_create_client_key.go new file mode 100644 index 0000000..d5caf28 --- /dev/null +++ b/internal/cli/cmd_create_client_key.go @@ -0,0 +1,47 @@ +package cli + +import ( + "context" + "flag" + "fmt" + "os" + + "codeberg.org/snonux/goprecords/internal/authkeys" +) + +func runCreateClientKey(hostname string, args []string) error { + if hostname == "" { + return fmt.Errorf("create-client-key: hostname required") + } + fs := flag.NewFlagSet("create-client-key", flag.ExitOnError) + fs.SetOutput(os.Stderr) + statsDir := fs.String("stats-dir", "", "Uptimed stats directory (sets default auth-db path)") + authDB := fs.String("auth-db", "", "SQLite file for upload API keys (default: /goprecords-auth.db)") + if err := fs.Parse(args); err != nil { + return err + } + authPath := *authDB + if authPath == "" { + if *statsDir == "" { + fmt.Fprintln(os.Stderr, "create-client-key: need -stats-dir or -auth-db") + fs.Usage() + return fmt.Errorf("missing -stats-dir or -auth-db") + } + authPath = authkeys.DefaultPath(*statsDir) + } + ctx := context.Background() + store, err := authkeys.OpenStore(ctx, authPath) + if err != nil { + return fmt.Errorf("open auth db: %w", err) + } + defer store.Close() + if err := store.EnsureSchema(ctx); err != nil { + return fmt.Errorf("schema: %w", err) + } + token, err := store.CreateKey(ctx, hostname) + if err != nil { + return fmt.Errorf("create key: %w", err) + } + fmt.Println(token) + return nil +} diff --git a/internal/cli/cmd_daemon.go b/internal/cli/cmd_daemon.go new file mode 100644 index 0000000..2bd74a8 --- /dev/null +++ b/internal/cli/cmd_daemon.go @@ -0,0 +1,43 @@ +package cli + +import ( + "context" + "errors" + "flag" + "fmt" + "os" + "os/signal" + "syscall" + + "codeberg.org/snonux/goprecords/internal/daemon" +) + +func defaultListenFromEnv() string { + if s := os.Getenv("GOPRECORDS_LISTEN"); s != "" { + return s + } + return ":8080" +} + +func runDaemon(args []string) error { + fs := flag.NewFlagSet("daemon", flag.ExitOnError) + fs.SetOutput(os.Stdout) + statsDir := fs.String("stats-dir", os.Getenv("GOPRECORDS_STATS_DIR"), "Uptimed stats directory (required; env GOPRECORDS_STATS_DIR)") + listen := fs.String("listen", defaultListenFromEnv(), "TCP listen address (env GOPRECORDS_LISTEN, default :8080)") + authDB := fs.String("auth-db", "", "SQLite file for upload API keys (default: /goprecords-auth.db)") + if err := fs.Parse(args); err != nil { + return err + } + if *statsDir == "" { + fmt.Fprintln(os.Stdout, "daemon: missing required flag: -stats-dir (or GOPRECORDS_STATS_DIR)") + fs.Usage() + return fmt.Errorf("missing -stats-dir") + } + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer stop() + err := daemon.Run(ctx, daemon.Config{StatsDir: *statsDir, Addr: *listen, AuthDB: *authDB}) + if err != nil && !errors.Is(err, context.Canceled) { + return err + } + return nil +} diff --git a/internal/cli/cmd_import.go b/internal/cli/cmd_import.go new file mode 100644 index 0000000..f3ecad5 --- /dev/null +++ b/internal/cli/cmd_import.go @@ -0,0 +1,38 @@ +package cli + +import ( + "context" + "flag" + "fmt" + "os" + + "codeberg.org/snonux/goprecords/internal/storage" +) + +func runImport(args []string) error { + fs := flag.NewFlagSet("import", flag.ExitOnError) + statsDir := fs.String("stats-dir", "", "Directory containing .records files (required)") + dbPath := fs.String("db", "goprecords.db", "SQLite database path") + if err := fs.Parse(args); err != nil { + return err + } + if *statsDir == "" { + fmt.Fprintln(os.Stderr, "import: missing required flag: -stats-dir") + fs.Usage() + return fmt.Errorf("missing -stats-dir") + } + ctx := context.Background() + db, err := storage.Open(ctx, *dbPath) + if err != nil { + return fmt.Errorf("open db: %w", err) + } + defer db.Close() + if err := storage.CreateSchema(ctx, db); err != nil { + return fmt.Errorf("schema: %w", err) + } + if err := storage.ImportFromDir(ctx, db, *statsDir); err != nil { + return fmt.Errorf("import: %w", err) + } + fmt.Fprintf(os.Stderr, "imported %s into %s\n", *statsDir, *dbPath) + return nil +} diff --git a/internal/cli/cmd_query.go b/internal/cli/cmd_query.go new file mode 100644 index 0000000..a22aeb4 --- /dev/null +++ b/internal/cli/cmd_query.go @@ -0,0 +1,35 @@ +package cli + +import ( + "context" + "flag" + "fmt" + "os" + + "codeberg.org/snonux/goprecords/internal/goprecords" + "codeberg.org/snonux/goprecords/internal/storage" +) + +func runQuery(args []string) error { + fs := flag.NewFlagSet("query", flag.ExitOnError) + dbPath := fs.String("db", "goprecords.db", "SQLite database path") + rf := goprecords.RegisterReportFlags(fs) + if err := fs.Parse(args); err != nil { + return err + } + ctx := context.Background() + db, err := storage.Open(ctx, *dbPath) + if err != nil { + return fmt.Errorf("open db: %w", err) + } + defer db.Close() + aggregates, err := goprecords.LoadAggregates(ctx, db) + if err != nil { + return fmt.Errorf("load: %w", err) + } + cfg, err := rf.Parse() + if err != nil { + return err + } + return goprecords.WriteReports(os.Stdout, aggregates, cfg) +} diff --git a/internal/cli/cmd_report_from_files.go b/internal/cli/cmd_report_from_files.go new file mode 100644 index 0000000..ca2bb8c --- /dev/null +++ b/internal/cli/cmd_report_from_files.go @@ -0,0 +1,38 @@ +package cli + +import ( + "context" + "flag" + "fmt" + "os" + + "codeberg.org/snonux/goprecords/internal/goprecords" +) + +func runReportFromFiles(args []string) error { + if args == nil { + args = []string{} + } + fs := flag.NewFlagSet("goprecords", flag.ExitOnError) + statsDir := fs.String("stats-dir", "", "The uptimed raw record input dir (required)") + rf := goprecords.RegisterReportFlags(fs) + if err := fs.Parse(args); err != nil { + return err + } + if *statsDir == "" { + fmt.Fprintln(os.Stderr, "missing required flag: -stats-dir") + fs.Usage() + return fmt.Errorf("missing -stats-dir") + } + cfg, err := rf.Parse() + if err != nil { + return err + } + ctx := context.Background() + aggr := goprecords.NewAggregator(*statsDir) + aggregates, err := aggr.Aggregate(ctx) + if err != nil { + return err + } + return goprecords.WriteReports(os.Stdout, aggregates, cfg) +} diff --git a/internal/cli/cmd_tests.go b/internal/cli/cmd_tests.go new file mode 100644 index 0000000..643b515 --- /dev/null +++ b/internal/cli/cmd_tests.go @@ -0,0 +1,7 @@ +package cli + +import "codeberg.org/snonux/goprecords/internal/goprecords" + +func runTests() error { + return goprecords.RunIntegrationTests("./fixtures") +} -- cgit v1.2.3