aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2026-08-27 13:49:00 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2026-08-27 13:49:00 +0200
commit2e3833dd30fafdc270e8d80a78af9116571dc247 (patch)
tree8428eb9938b8cf5351ff6e7b78b2968cc37b2e27
parentabbbf17bcfe6dfa663b7019c4892db09e27b3808 (diff)
Use subcommand args instead of flags for CLI
-rw-r--r--README.md14
-rw-r--r--cli/cli.go68
-rw-r--r--main.go5
3 files changed, 59 insertions, 28 deletions
diff --git a/README.md b/README.md
index 123b021..deaa9fb 100644
--- a/README.md
+++ b/README.md
@@ -21,11 +21,11 @@ Climb profile by 100m step.
```bash
mise run build
mise run dev-watch # Start the web server on http://localhost:8080
-go run . -fetch-osm # Download the France OSM PBF (resumable)
-go run . -extract-osm france-latest.osm.pbf # Extract mountain passes from a France OSM PBF
-go run . -backfill # Recompute and persist all ride computed values
-go run . -backtest # Benchmark computed vs stored ride values
-go run . -pprof # Expose pprof endpoints at `/debug/pprof`
+go run . fetch-osm # Download the France OSM PBF (resumable)
+go run . extract-osm france-latest.osm.pbf # Extract mountain passes from a France OSM PBF
+go run . backfill # Recompute and persist all ride computed values
+go run . backtest # Benchmark computed vs stored ride values
+go run . -- -pprof # Expose pprof endpoints at `/debug/pprof`
docker compose up -d pyroscope # Start the local Pyroscope server on http://localhost:4040
```
@@ -129,7 +129,8 @@ Answer "what slows the server" with function-level CPU profiles grouped by route
```bash
# Start the server with pprof (exposes http://localhost:8080/debug/pprof/)
-go run . -pprof
+go run . -- -pprof
+# or: go run . serve --pprof
# or: mise run dev -- -pprof
```
@@ -158,7 +159,6 @@ go tool pprof -http=:8081 http://localhost:8080/debug/pprof/heap # allocs
## TODO
-- cli args instea dof flags
- screaming archi + hexagon
- Compute estimated power, speed
- For a ride, scatter, one point per 100m segment. Slope vs speed
diff --git a/cli/cli.go b/cli/cli.go
index 1fdb866..c71fd9e 100644
--- a/cli/cli.go
+++ b/cli/cli.go
@@ -4,7 +4,6 @@ import (
"context"
"database/sql"
"errors"
- "flag"
"fmt"
"log/slog"
"math"
@@ -21,23 +20,26 @@ import (
"github.com/schollz/progressbar/v3"
)
-var (
- fetchOSM = flag.Bool("fetch-osm", false, "download the France OSM PBF (resumable) into france-latest.osm.pbf")
- extractOSM = flag.String("extract-osm", "", "extract mountain passes from an OSM PBF file into the database")
- backfill = flag.Bool("backfill", false, "recompute all stored ride values")
- backtest = flag.Bool("backtest", false, "run data benchmarks comparing computed values against stored reference values")
- enablePprof = flag.Bool("pprof", false, "expose pprof endpoints at /debug/pprof")
-)
+func Run(db *sql.DB, configPath string, appConfig config.Config, args []string, enablePprof bool) {
+ if len(args) == 0 {
+ runServer(db, configPath, appConfig, enablePprof)
+ return
+ }
-func Run(db *sql.DB, configPath string, appConfig config.Config) {
- switch {
- case *fetchOSM:
+ switch args[0] {
+ case "fetch-osm":
err := mountain_pass.FetchFrancePBF("france-latest.osm.pbf")
kcore.Expect(err, "failed to download France OSM PBF")
- case *extractOSM != "":
+ case "extract-osm":
+ if len(args) < 2 {
+ fmt.Fprintln(os.Stderr, "extract-osm requires a PBF file path argument")
+ printUsage()
+ os.Exit(2)
+ }
+ pbfPath := args[1]
enabled := progressBarEnabled()
var bar *progressbar.ProgressBar
- _, err := mountain_pass.ExtractMountainPasses(context.Background(), *extractOSM, db, func(current, total int64) {
+ _, err := mountain_pass.ExtractMountainPasses(context.Background(), pbfPath, db, func(current, total int64) {
if bar == nil && enabled {
bar = progressbar.DefaultBytes(total, "Extracting mountain passes")
}
@@ -49,7 +51,7 @@ func Run(db *sql.DB, configPath string, appConfig config.Config) {
bar.Finish()
}
kcore.Expect(err, "failed to extract mountain passes from OSM")
- case *backfill:
+ case "backfill":
enabled := progressBarEnabled()
var bar *progressbar.ProgressBar
count, err := ride.Backfill(db, func(current, total int) {
@@ -65,13 +67,41 @@ func Run(db *sql.DB, configPath string, appConfig config.Config) {
}
kcore.Expect(err, "failed to backfill ride values")
slog.Info("Backfilled ride values", "rides", count)
- case *backtest:
+ case "backtest":
kcore.Expect(runBacktest(db), "failed to run backtest")
+ case "serve":
+ pprof := enablePprof
+ for _, arg := range args[1:] {
+ if arg == "--pprof" || arg == "-pprof" || arg == "pprof" {
+ pprof = true
+ }
+ }
+ runServer(db, configPath, appConfig, pprof)
+ case "help", "-h", "--help":
+ printUsage()
default:
- runServer(db, configPath, appConfig)
+ fmt.Fprintf(os.Stderr, "unknown command %q\n", args[0])
+ printUsage()
+ os.Exit(2)
}
}
+func printUsage() {
+ fmt.Fprintf(os.Stderr, `Usage: biking_home [global flags] <command> [args]
+
+Global flags:
+ -config <path> path to YAML configuration file (default "config.yaml")
+ -pprof expose pprof endpoints at /debug/pprof (also: "serve --pprof")
+
+Commands:
+ serve start web server (default when no command given)
+ fetch-osm download the France OSM PBF into france-latest.osm.pbf
+ extract-osm <path> extract mountain passes from an OSM PBF file
+ backfill recompute all stored ride values
+ backtest run data benchmarks comparing computed values against stored reference values
+`)
+}
+
// progressBarEnabled reports whether stderr is attached to a terminal, so the
// progress bar only renders for interactive runs and stays out of piped or
// redirected output.
@@ -80,9 +110,9 @@ func progressBarEnabled() bool {
return err == nil && fi.Mode()&os.ModeCharDevice != 0
}
-func runServer(db *sql.DB, configPath string, appConfig config.Config) {
- server := web.NewServer(db, configPath).WithPprof(*enablePprof)
- if *enablePprof {
+func runServer(db *sql.DB, configPath string, appConfig config.Config, enablePprof bool) {
+ server := web.NewServer(db, configPath).WithPprof(enablePprof)
+ if enablePprof {
slog.Info("pprof enabled", "endpoint", appConfig.Server.PublicURL+"/debug/pprof/")
}
slog.Info("Starting web server", "address", appConfig.Server.PublicURL)
diff --git a/main.go b/main.go
index 8971433..397b3d6 100644
--- a/main.go
+++ b/main.go
@@ -13,7 +13,8 @@ import (
)
var (
- configFile = flag.String("config", "config.yaml", "path to the YAML configuration file")
+ configFile = flag.String("config", "config.yaml", "path to the YAML configuration file")
+ enablePprof = flag.Bool("pprof", false, "expose pprof endpoints at /debug/pprof")
)
func main() {
@@ -30,7 +31,7 @@ func main() {
db, err := sql.Open("sqlite3", appConfig.Database.Path)
kcore.Expect(err, "failed to open database")
defer db.Close()
- cli.Run(db, *configFile, appConfig)
+ cli.Run(db, *configFile, appConfig, flag.Args(), *enablePprof)
}
// startPyroscope starts the continuous profiler when enabled in the config and