diff --git a/cmd/goyco/cli.go b/cmd/goyco/cli.go index 4bf800e..ef72350 100644 --- a/cmd/goyco/cli.go +++ b/cmd/goyco/cli.go @@ -1,14 +1,24 @@ package main import ( + "context" "errors" "flag" "fmt" + "io" "os" + "sync" "goyco/cmd/goyco/commands" + "goyco/internal/config" "github.com/joho/godotenv" + "github.com/urfave/cli/v3" +) + +var ( + helpPrinterOnce sync.Once + defaultHelpPrinter func(io.Writer, string, interface{}) ) func loadDotEnv() { @@ -55,3 +65,121 @@ func printRunUsage() { fmt.Fprintln(os.Stderr, "Usage: goyco run") fmt.Fprintln(os.Stderr, "\nStart the web application in foreground.") } + +func buildRootCommand(cfg *config.Config) *cli.Command { + helpPrinterOnce.Do(func() { + defaultHelpPrinter = cli.HelpPrinter + cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { + if cmd, ok := data.(*cli.Command); ok && cmd.Root() == cmd { + printRootUsage() + return + } + defaultHelpPrinter(w, templ, data) + } + }) + + root := &cli.Command{ + Name: "goyco", + Usage: "Y Combinator-style news aggregation platform API", + UsageText: "goyco []", + HideVersion: true, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "json", + Usage: "output results in JSON format", + Value: cfg.CLI.JSONOutputDefault, + }, + }, + Before: func(ctx context.Context, cmd *cli.Command) (context.Context, error) { + commands.SetJSONOutput(cmd.Bool("json")) + return ctx, nil + }, + Action: func(_ context.Context, cmd *cli.Command) error { + if cmd.NArg() == 0 { + printRootUsage() + return nil + } + printRootUsage() + return fmt.Errorf("unknown command: %s", cmd.Args().First()) + }, + Commands: []*cli.Command{ + { + Name: "run", + Usage: "start the web application in foreground", + SkipFlagParsing: true, + Action: func(_ context.Context, cmd *cli.Command) error { + return handleRunCommand(cfg, cmd.Args().Slice()) + }, + }, + { + Name: "start", + Usage: "start the web application in background", + SkipFlagParsing: true, + Action: func(_ context.Context, cmd *cli.Command) error { + return commands.HandleStartCommand(cfg, cmd.Args().Slice()) + }, + }, + { + Name: "stop", + Usage: "stop the daemon", + SkipFlagParsing: true, + Action: func(_ context.Context, cmd *cli.Command) error { + return commands.HandleStopCommand(cfg, cmd.Args().Slice()) + }, + }, + { + Name: "status", + Usage: "check if the daemon is running", + SkipFlagParsing: true, + Action: func(_ context.Context, cmd *cli.Command) error { + return commands.HandleStatusCommand(cfg, cmd.Name, cmd.Args().Slice()) + }, + }, + { + Name: "migrate", + Aliases: []string{"migrations"}, + Usage: "apply database migrations", + SkipFlagParsing: true, + Action: func(_ context.Context, cmd *cli.Command) error { + return commands.HandleMigrateCommand(cfg, cmd.Name, cmd.Args().Slice()) + }, + }, + { + Name: "user", + Usage: "manage users (create, update, delete, lock, list)", + SkipFlagParsing: true, + Action: func(_ context.Context, cmd *cli.Command) error { + return commands.HandleUserCommand(cfg, cmd.Name, cmd.Args().Slice()) + }, + }, + { + Name: "post", + Usage: "manage posts (delete, list, search)", + SkipFlagParsing: true, + Action: func(_ context.Context, cmd *cli.Command) error { + return commands.HandlePostCommand(cfg, cmd.Name, cmd.Args().Slice()) + }, + }, + { + Name: "prune", + Usage: "hard delete users and posts (posts, all)", + SkipFlagParsing: true, + Action: func(_ context.Context, cmd *cli.Command) error { + return commands.HandlePruneCommand(cfg, cmd.Name, cmd.Args().Slice()) + }, + }, + { + Name: "seed", + Usage: "seed database with random data", + SkipFlagParsing: true, + Action: func(_ context.Context, cmd *cli.Command) error { + return commands.HandleSeedCommand(cfg, cmd.Name, cmd.Args().Slice()) + }, + }, + }, + Writer: os.Stdout, + ErrWriter: os.Stderr, + } + + return root +} diff --git a/cmd/goyco/main.go b/cmd/goyco/main.go index 5ace962..17d0ef5 100644 --- a/cmd/goyco/main.go +++ b/cmd/goyco/main.go @@ -12,8 +12,8 @@ package main import ( + "context" "errors" - "flag" "fmt" "log" "os" @@ -63,33 +63,9 @@ func run(args []string) error { docs.SwaggerInfo.Schemes = append(docs.SwaggerInfo.Schemes, "https") } - rootFS := flag.NewFlagSet("goyco", flag.ContinueOnError) - rootFS.SetOutput(os.Stderr) - rootFS.Usage = printRootUsage - showHelp := rootFS.Bool("help", false, "show this help message") - jsonOutput := rootFS.Bool("json", cfg.CLI.JSONOutputDefault, "output results in JSON format") - - if err := rootFS.Parse(args); err != nil { - if errors.Is(err, flag.ErrHelp) { - return nil - } - return fmt.Errorf("failed to parse arguments: %w", err) - } - - if *showHelp { - printRootUsage() - return nil - } - - commands.SetJSONOutput(*jsonOutput) - - remaining := rootFS.Args() - if len(remaining) == 0 { - printRootUsage() - return nil - } - - return dispatchCommand(cfg, remaining[0], remaining[1:]) + root := buildRootCommand(cfg) + runArgs := append([]string{os.Args[0]}, args...) + return root.Run(context.Background(), runArgs) } func dispatchCommand(cfg *config.Config, name string, args []string) error {