A powerful and type-safe Go library for building command-line interfaces with hierarchical commands, option groups, and automatic argument parsing and validation.
- Hierarchical Commands: Build complex nested command structures like
docker container run - Type-Safe Options: Automatic parsing and validation for strings, integers, floats, and booleans
- Option Groups: Create mutually exclusive or inclusive option groups
- Fluent API: Clean, readable command definitions with method chaining
- Custom Error Handling: Flexible error handling with custom error handlers
- Context-Based: Rich context object for accessing parsed arguments and flags
go get github.com/DilemaFixer/Cmdpackage main
import (
"fmt"
ctx "github.com/DilemaFixer/Cmd/context"
p "github.com/DilemaFixer/Cmd/parser"
rtr "github.com/DilemaFixer/Cmd/router"
)
func main() {
// Parse command line input
input := "server start --host=localhost --port=8080 --debug"
parsedInput, err := p.ParseInput(input)
if err != nil {
fmt.Println(err)
return
}
// Create context and router
context := ctx.NewContext(parsedInput)
iterator := rtr.NewRoutingIterator(context)
router := rtr.NewRouter()
// Define command with options
router.Endpoint("server").
Endpoint("start").
Description("Start HTTP server").
StringOption("host").
IntOption("port").
BoolOption("debug").
Handler(startHandler).
Register()
// Execute command
router.Route(*context, iterator)
}
func startHandler(ctx ctx.Context) error {
host := ctx.GetValueOrDefault("host", "localhost")
port := ctx.GetValueOrDefault("port", "3000")
debug := ctx.GetValueAsBool("debug")
fmt.Printf("Starting server on %s:%s (debug: %v)\n", host, port, debug)
return nil
}parsedInput, err := p.ParseInput("command subcommand --flag=value --bool-flag")parsedInput, err := p.ParseOSArgs()args := []string{"command", "subcommand", "--flag=value"}
parsedInput, err := p.ParseArgs(args)router := rtr.NewRouter()
router.Endpoint("version").
Description("Show version information").
BoolOption("short").
Handler(versionHandler).
Register()Usage:
myapp version --shortrouter.NewCmd("docker").
NewSub("container").
Endpoint("run").
RequiredString("image").
StringOption("name").
IntOption("port").
BoolOption("detach").
Handler(containerRunHandler).
Build().
Endpoint("stop").
RequiredString("container").
BoolOption("force").
Handler(containerStopHandler).
Build().
Build().
Register()Usage:
myapp docker container run --image=nginx --name=web --port=80 --detach
myapp docker container stop --container=web --forceThe library supports various option types with automatic validation:
router.Endpoint("config").
// String options
StringOption("name"). // Optional: --name=value
RequiredString("config"). // Required: --config=value
// Integer options
IntOption("count"). // Optional: --count=42
RequiredInt("port"). // Required: --port=8080
// Float options
FloatOption("ratio"). // Optional: --ratio=3.14
RequiredFloat("threshold"). // Required: --threshold=0.95
// Boolean options (flags)
BoolOption("enable"). // Optional: --enable
RequiredBool("force"). // Required: --force
Handler(configHandler).
Register()Only one group can be active at a time:
router.Endpoint("backup").
// Mutually exclusive storage options
ExclusiveGroup("local", "--local").
RequiredString("path").
BoolOption("compress").
StringOption("encryption").
EndGroup().
ExclusiveGroup("remote", "--remote").
RequiredString("host").
RequiredString("user").
StringOption("key").
EndGroup().
ExclusiveGroup("cloud", "--s3").
RequiredString("bucket").
RequiredString("region").
EndGroup().
SetGroupsCanBeIgnored(false). // At least one group required
Handler(backupHandler).
Register()Usage:
# Valid - only one group active
myapp backup --local --path=/backup --compress
# Invalid - multiple exclusive groups
myapp backup --local --path=/backup --remote --host=server.comGroups that can be used together:
router.Endpoint("deploy").
RequiredString("environment").
// Optional resource configuration
Group("resources", "--resources").
IntOption("memory").
IntOption("cpu").
StringOption("storage").
EndGroup().
// Optional monitoring setup
Group("monitoring", "--monitoring").
BoolOption("metrics").
BoolOption("logging").
StringOption("alerts").
EndGroup().
Handler(deployHandler).
Register()Usage:
# Can use both groups together
myapp deploy --environment=prod --resources --memory=512 --cpu=2 --monitoring --metrics --loggingAccess parsed options and commands in your handlers:
func handler(ctx ctx.Context) error {
// Check if flag was provided
if ctx.IsFlagExist("port") {
// Flag exists
}
// Check if flag has a value
if ctx.IsFlagHaveValue("host") {
// Flag has non-empty value
}
return nil
}func handler(ctx ctx.Context) error {
// String values
host, err := ctx.GetValueAsString("host")
if err != nil {
return err
}
// Integer values
port, err := ctx.GetValueAsInt("port")
count32, err := ctx.GetValueAsInt32("count")
count64, err := ctx.GetValueAsInt64("bigcount")
// Float values
ratio32, err := ctx.GetValueAsFloat32("ratio")
ratio64, err := ctx.GetValueAsFloat64("precision")
// Boolean values (returns false if flag doesn't exist)
debug := ctx.GetValueAsBool("debug")
// Default values
timeout := ctx.GetValueOrDefault("timeout", "30s")
return nil
}func handler(ctx ctx.Context) error {
// Get main command
cmd := ctx.GetCommand()
// Check command
if ctx.IsCommandEqual("serve") {
// Handle serve command
}
// Check subcommands
if ctx.IsSubcommandExist("start") {
// Subcommand exists
}
// Get all subcommands
subs := ctx.GetSubcommandsAsArr()
// Get all flags
flagsMap := ctx.GetFlagsAsMap()
flagsArray := ctx.GetFlagsAsArr()
keys := ctx.GetFlagsKeysAsArr()
values := ctx.GetFlagsValuesAsArr()
return nil
}router := rtr.NewRouter()
router.CustomErrorHandler(func(err error, ctx ctx.Context) {
fmt.Printf("Command failed: %v\n", err)
fmt.Printf("Command: %s\n", ctx.GetCommand())
fmt.Printf("Flags: %v\n", ctx.GetFlagsAsMap())
os.Exit(1)
})- Missing required options:
"Required 'config' flag not exist" - Invalid option values:
"Option port with type Int have error" - Type validation:
"Option debug with type Bool have value" - Conflicting groups:
"group requires solitude" - Unknown commands:
"Point with name 'unknown' not found"
router.NewCmd("file").
Endpoint("copy").
RequiredString("source").
RequiredString("destination").
IntOption("buffer-size").
IntOption("threads").
BoolOption("verify").
BoolOption("overwrite").
FloatOption("throttle").
Handler(fileCopyHandler).
Build().
Endpoint("compress").
RequiredString("input").
RequiredString("output").
RequiredInt("level").
IntOption("threads").
BoolOption("recursive").
StringOption("format").
Handler(fileCompressHandler).
Build().
Register()Usage:
myapp file copy --source=/home/user/file.txt --destination=/backup/ --buffer-size=8192 --verify
myapp file compress --input=/data/logs --output=/compressed.tar.gz --level=9 --threads=4router.NewCmd("db").
NewSub("migrate").
Endpoint("up").
IntOption("steps").
BoolOption("dry-run").
BoolOption("verbose").
Handler(migrateUpHandler).
Build().
Endpoint("down").
RequiredInt("steps").
BoolOption("dry-run").
BoolOption("force").
Handler(migrateDownHandler).
Build().
Build().
Register()Usage:
myapp db migrate up --steps=3 --dry-run
myapp db migrate down --steps=2 --forcerouter.Endpoint("deploy").
RequiredString("environment").
ExclusiveGroup("docker", "--docker").
RequiredString("image").
StringOption("registry").
BoolOption("build").
EndGroup().
ExclusiveGroup("kubernetes", "--kubernetes").
RequiredString("namespace").
IntOption("replicas").
BoolOption("wait").
EndGroup().
Group("resources", "--resources").
IntOption("memory").
IntOption("cpu").
EndGroup().
SetGroupsCanBeIgnored(false).
Handler(deployHandler).
Register()Usage:
myapp deploy --environment=prod --docker --image=myapp:latest --resources --memory=1024 --cpu=4
myapp deploy --environment=staging --kubernetes --namespace=staging --replicas=3 --resources --memory=512 --cpu=2The fluent API allows clean command definitions:
router.NewCmd("service").
Description("Service management").
NewSub("database").
Endpoint("migrate").
Description("Run migrations").
RequiredString("direction").
BoolOption("dry-run").
Group("connection", "--db").
RequiredString("host").
IntOption("port").
EndGroup().
Handler(migrateHandler).
Build().
Build().
Register()type ParsedInput struct {
Command string // Main command name
Subcommands []string // List of subcommands
InputFlags []InputFlag // Parsed flags with values
}type InputFlag struct {
Name string // Flag name (without --)
Value string // Flag value (empty for boolean flags)
}- Always check required parameters in your handlers
- Validate business logic beyond type checking
- Use custom error handlers for better user experience
- Provide helpful error messages with context
func handler(ctx ctx.Context) error {
// Check required parameters
if !ctx.IsFlagExist("config") {
return fmt.Errorf("config file is required")
}
// Validate values
port, err := ctx.GetValueAsInt("port")
if err != nil {
return fmt.Errorf("invalid port: %v", err)
}
if port < 1 || port > 65535 {
return fmt.Errorf("port must be between 1-65535, got: %d", port)
}
return nil
}