Skip to content

Commit ac4889d

Browse files
authored
Merge pull request #121 from jkaninda/feat/subcommands
feat(cli): introduce support for CLI subcommand
2 parents a491149 + 79376d5 commit ac4889d

File tree

6 files changed

+768
-66
lines changed

6 files changed

+768
-66
lines changed

examples/cli/main.go

Lines changed: 69 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@
2525
package main
2626

2727
import (
28-
"github.com/jkaninda/okapi"
29-
"github.com/jkaninda/okapi/okapicli"
28+
"fmt"
3029
"log/slog"
3130
"os"
3231
"time"
32+
33+
"github.com/jkaninda/okapi"
34+
"github.com/jkaninda/okapi/okapicli"
3335
)
3436

3537
type ServerConfig struct {
@@ -47,69 +49,78 @@ type Config struct {
4749
}
4850

4951
func main() {
50-
cfg := &ServerConfig{}
51-
// Create default Okapi instance
5252
o := okapi.Default()
53+
cli := okapicli.New(o, "myapp")
5354

54-
// Create CLI instance
55-
cli := okapicli.New(o, "Okapi CLI Example").FromStruct(cfg)
56-
// Or
57-
// cli := okapicli.New(o, "Okapi CLI Example").
58-
// String("config", "c", "config.yaml", "Path to configuration file").
59-
// Int("port", "p", 8000, "HTTP server port").
60-
// Bool("debug", "d", false, "Enable debug mode")
61-
62-
// Parse flags
63-
if err := cli.Parse(); err != nil {
64-
panic(err)
65-
}
66-
67-
// Apply CLI options
68-
o.WithPort(cfg.Port)
69-
// Or
70-
// o.WithPort(cli.GetInt("port"))
71-
if cli.GetBool("debug") {
72-
o.WithDebug()
73-
}
55+
// Subcommand: serve
56+
serveCfg := &ServerConfig{}
57+
cli.Command("serve", "Start the HTTP server", func(cmd *okapicli.Command) error {
58+
// Apply parsed config
59+
cmd.Okapi().WithPort(serveCfg.Port)
60+
if serveCfg.Debug {
61+
cmd.Okapi().WithDebug()
62+
}
7463

75-
// Load configuration
76-
config := &Config{}
77-
if path := cli.GetString("config"); path != "" {
78-
slog.Info("Loading configuration", "path", path)
79-
if err := cli.LoadConfig(path, config); err != nil {
80-
slog.Error("Failed to load configuration", "error", err)
64+
// Load app config from file
65+
config := &Config{}
66+
if path := serveCfg.Config; path != "" {
67+
slog.Info("Loading configuration", "path", path)
68+
if err := cmd.CLI().LoadConfig(path, config); err != nil {
69+
slog.Error("Failed to load configuration", "error", err)
70+
}
8171
}
82-
}
8372

84-
// Define routes
85-
o.Get("/", func(ctx *okapi.Context) error {
86-
return ctx.OK(okapi.M{
87-
"message": "Hello, Okapi!",
73+
// Define routes
74+
cmd.Okapi().Get("/", func(ctx *okapi.Context) error {
75+
return ctx.OK(okapi.M{"message": "Hello, Okapi!"})
8876
})
77+
78+
// Run server with lifecycle hooks
79+
return cmd.CLI().RunServer(&okapicli.RunOptions{
80+
ShutdownTimeout: 30 * time.Second,
81+
Signals: []os.Signal{okapicli.SIGINT, okapicli.SIGTERM},
82+
OnStart: func() {
83+
slog.Info("Preparing resources before startup")
84+
if config.DatabaseURL != "" {
85+
slog.Info("Connecting to database")
86+
}
87+
},
88+
OnStarted: func() {
89+
slog.Info("Server started successfully")
90+
},
91+
OnShutdown: func() {
92+
slog.Info("Cleaning up before shutdown")
93+
},
94+
})
95+
}).FromStruct(serveCfg)
96+
97+
// Subcommand: worker
98+
cli.Command("worker", "Start application worker", func(cmd *okapicli.Command) error {
99+
100+
fmt.Println("Starting myapp wortker...")
101+
sleepTime := cmd.GetDuration("sleep") * time.Second
102+
slog.Info("Worker started successfully", "concurrency", cmd.GetInt("concurrency"), "sleep", sleepTime)
103+
104+
time.Sleep(sleepTime)
105+
fmt.Println("Stoping myapp wortker...")
106+
return nil
107+
}).Int("concurrency", "c", 3, "concurrency jobs").Duration("sleep", "", 5, "Sleep time")
108+
// Subcommand: version
109+
cli.Command("version", "Show the worker", func(cmd *okapicli.Command) error {
110+
fmt.Println("myapp v1.0.0")
111+
return nil
89112
})
90113

91-
// Run server with lifecycle hooks
92-
if err := cli.RunServer(&okapicli.RunOptions{
93-
ShutdownTimeout: 30 * time.Second, // Optional: customize shutdown timeout
94-
Signals: []os.Signal{okapicli.SIGINT, okapicli.SIGTERM}, // Optional: customize shutdown signals
95-
OnStart: func() {
96-
slog.Info("Preparing resources before startup")
97-
if config.DatabaseURL != "" {
98-
slog.Info("Connecting to database")
99-
}
100-
},
101-
OnStarted: func() {
102-
slog.Info("Server started successfully")
103-
},
104-
OnShutdown: func() {
105-
slog.Info("Cleaning up before shutdown")
106-
},
107-
}); err != nil {
108-
panic(err)
109-
}
114+
// Set "serve" as the default command (runs when no subcommand is given)
115+
cli.DefaultCommand("serve")
110116

111-
// Or use defaults
112-
// if err := cli.Run(); err != nil {
113-
// panic(err)
114-
// }
117+
// Execute: parses os.Args for the subcommand and runs it
118+
// Usage:
119+
// myapp serve --port 9090 --debug
120+
// myapp worker --concurrency 5 --slep 5
121+
// myapp version
122+
if err := cli.Execute(); err != nil {
123+
slog.Error("Error", "error", err)
124+
os.Exit(1)
125+
}
115126
}

helper.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func fPrintError(msg string, args ...interface{}) {
5050
if !ok {
5151
key = fmt.Sprintf("invalid_key_%d", i)
5252
}
53-
b.WriteString(fmt.Sprintf(" %s=%v", key, args[i+1]))
53+
fmt.Fprintf(&b, " %s=%v", key, args[i+1])
5454
}
5555

5656
b.WriteByte('\n')
@@ -65,7 +65,7 @@ func fPrint(msg string, args ...interface{}) {
6565
if !ok {
6666
key = fmt.Sprintf("invalid_key_%d", i)
6767
}
68-
b.WriteString(fmt.Sprintf(" %s=%v", key, args[i+1]))
68+
fmt.Fprintf(&b, " %s=%v", key, args[i+1])
6969
}
7070

7171
b.WriteByte('\n')

okapi.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1427,11 +1427,11 @@ func handleAccessLog(c *Context) error {
14271427
}
14281428
switch {
14291429
case status >= 500:
1430-
logger.Error("[okapi] Incoming request", logFields...)
1430+
logger.Error("[Okapi] Incoming request", logFields...)
14311431
case status >= 400:
1432-
logger.Warn("[okapi] Incoming request", logFields...)
1432+
logger.Warn("[Okapi] Incoming request", logFields...)
14331433
default:
1434-
logger.Info("[okapi] Incoming request", logFields...)
1434+
logger.Info("[Okapi] Incoming request", logFields...)
14351435
}
14361436
return err
14371437
}

0 commit comments

Comments
 (0)