Basic Examples¶
Practical, copy-paste-ready examples for common BOA CLI patterns.
Minimal CLI Tool (Hello World)¶
The simplest possible BOA CLI tool. One required parameter, one action.
package main
import (
"fmt"
"github.com/GiGurra/boa/pkg/boa"
"github.com/spf13/cobra"
)
type Params struct {
Name string `descr:"Your name"`
}
func main() {
boa.CmdT[Params]{
Use: "hello",
Short: "Say hello",
RunFunc: func(p *Params, cmd *cobra.Command, args []string) {
fmt.Printf("Hello, %s!\n", p.Name)
},
}.Run()
}
Required and Optional Flags¶
By default, all plain-type fields are required. Mark fields optional with optional:"true".
package main
import (
"fmt"
"github.com/GiGurra/boa/pkg/boa"
"github.com/spf13/cobra"
)
type Params struct {
Host string `descr:"Server hostname"`
Port int `descr:"Server port" optional:"true"`
Verbose bool `descr:"Enable verbose logging" optional:"true"`
}
func main() {
boa.CmdT[Params]{
Use: "server",
Short: "Start a server",
RunFunc: func(p *Params, cmd *cobra.Command, args []string) {
fmt.Printf("Starting server on %s:%d (verbose=%v)\n", p.Host, p.Port, p.Verbose)
},
}.Run()
}
$ go run . --host localhost
Starting server on localhost:0 (verbose=false)
$ go run . --host localhost --port 8080 --verbose
Starting server on localhost:8080 (verbose=true)
$ go run .
# Error: required flag "host" not set
Pointer Fields for Optional Parameters¶
Use pointer types (*string, *int, *bool) when you need to distinguish "not provided" from "zero value". Pointer fields are always optional by default.
package main
import (
"fmt"
"github.com/GiGurra/boa/pkg/boa"
"github.com/spf13/cobra"
)
type Params struct {
Name string `descr:"Your name"`
Retries *int `descr:"Retry count"`
Output *string `descr:"Output file"`
Force *bool `descr:"Force overwrite"`
}
func main() {
boa.CmdT[Params]{
Use: "app",
Short: "Demo pointer fields",
RunFunc: func(p *Params, cmd *cobra.Command, args []string) {
fmt.Printf("Name: %s\n", p.Name)
if p.Retries != nil {
fmt.Printf("Retries: %d\n", *p.Retries)
} else {
fmt.Println("Retries: not set (nil)")
}
if p.Output != nil {
fmt.Printf("Output: %s\n", *p.Output)
} else {
fmt.Println("Output: not set (nil)")
}
if p.Force != nil {
fmt.Printf("Force: %v\n", *p.Force)
} else {
fmt.Println("Force: not set (nil)")
}
},
}.Run()
}
$ go run . --name alice
Name: alice
Retries: not set (nil)
Output: not set (nil)
Force: not set (nil)
$ go run . --name alice --retries 3 --output results.json --force
Name: alice
Retries: 3
Output: results.json
Force: true
Use required:"true" to override the default-optional behavior of pointer fields:
Boolean Flags¶
Boolean fields default to false when optional. On the CLI, --verbose (without a value) sets the flag to true.
package main
import (
"fmt"
"github.com/GiGurra/boa/pkg/boa"
"github.com/spf13/cobra"
)
type Params struct {
DryRun bool `descr:"Simulate without making changes" optional:"true"`
Verbose bool `descr:"Enable verbose output" short:"v" optional:"true"`
Force bool `descr:"Skip confirmation prompts" short:"f" optional:"true"`
}
func main() {
boa.CmdT[Params]{
Use: "deploy",
Short: "Deploy the application",
RunFunc: func(p *Params, cmd *cobra.Command, args []string) {
if p.DryRun {
fmt.Println("[DRY RUN] Would deploy...")
} else {
fmt.Println("Deploying...")
}
if p.Verbose {
fmt.Println(" verbose output enabled")
}
if p.Force {
fmt.Println(" skipping confirmations")
}
},
}.Run()
}
$ go run . --dry-run --verbose
[DRY RUN] Would deploy...
verbose output enabled
$ go run . -v -f
Deploying...
verbose output enabled
skipping confirmations
Positional Arguments¶
Use positional:"true" to accept arguments by position instead of flags. Positional arguments are matched in struct field order.
package main
import (
"fmt"
"github.com/GiGurra/boa/pkg/boa"
"github.com/spf13/cobra"
)
type Params struct {
Source string `positional:"true" descr:"Source file"`
Dest string `positional:"true" descr:"Destination file"`
Force bool `descr:"Overwrite existing" optional:"true"`
}
func main() {
boa.CmdT[Params]{
Use: "copy",
Short: "Copy a file",
RunFunc: func(p *Params, cmd *cobra.Command, args []string) {
fmt.Printf("Copying %s -> %s (force=%v)\n", p.Source, p.Dest, p.Force)
},
}.Run()
}
$ go run . input.txt output.txt
Copying input.txt -> output.txt (force=false)
$ go run . input.txt output.txt --force
Copying input.txt -> output.txt (force=true)
Help output shows positional arguments in the usage line:
Optional Positional Arguments¶
type Params struct {
File string `positional:"true" descr:"Input file"`
Output string `positional:"true" optional:"true" default:"stdout" descr:"Output file"`
}
$ go run . data.csv
# Output defaults to "stdout"
$ go run . data.csv results.json
# Output is "results.json"
Slice Positional Arguments¶
A slice positional argument consumes all remaining positional arguments:
package main
import (
"fmt"
"github.com/GiGurra/boa/pkg/boa"
"github.com/spf13/cobra"
)
type Params struct {
Names []string `positional:"true" descr:"Names to greet"`
}
func main() {
boa.CmdT[Params]{
Use: "greet",
Short: "Greet people",
RunFunc: func(p *Params, cmd *cobra.Command, args []string) {
for _, name := range p.Names {
fmt.Printf("Hello, %s!\n", name)
}
},
}.Run()
}
Default Values¶
Set defaults with the default struct tag. Fields with defaults are automatically optional since they always have a value.
package main
import (
"fmt"
"github.com/GiGurra/boa/pkg/boa"
"github.com/spf13/cobra"
)
type Params struct {
Host string `descr:"Server host" default:"localhost"`
Port int `descr:"Server port" default:"8080"`
LogLevel string `descr:"Log level" default:"info"`
Tags []string `descr:"Tags" default:"[web,api]"`
}
func main() {
boa.CmdT[Params]{
Use: "server",
Short: "Start the server",
RunFunc: func(p *Params, cmd *cobra.Command, args []string) {
fmt.Printf("Host: %s\nPort: %d\nLog: %s\nTags: %v\n",
p.Host, p.Port, p.LogLevel, p.Tags)
},
}.Run()
}
$ go run .
Host: localhost
Port: 8080
Log: info
Tags: [web api]
$ go run . --port 3000 --log-level debug
Host: localhost
Port: 3000
Log: debug
Tags: [web api]
Programmatic Defaults¶
Set defaults dynamically in an InitFuncCtx hook:
boa.CmdT[Params]{
Use: "server",
InitFuncCtx: func(ctx *boa.HookContext, p *Params, cmd *cobra.Command) error {
ctx.GetParam(&p.Port).SetDefault(boa.Default(8080))
ctx.GetParam(&p.Host).SetDefault(boa.Default("localhost"))
return nil
},
RunFunc: func(p *Params, cmd *cobra.Command, args []string) {
// ...
},
}.Run()
Short Flags¶
By default, BOA auto-assigns short flags from the first letter of the flag name (skipping -h which is reserved for help). Override with the short tag.
package main
import (
"fmt"
"github.com/GiGurra/boa/pkg/boa"
"github.com/spf13/cobra"
)
type Params struct {
Name string `descr:"Your name" short:"n"`
Output string `descr:"Output file" short:"o"`
Verbose bool `descr:"Verbose output" short:"v" optional:"true"`
Count int `descr:"Repeat count" short:"c" default:"1"`
}
func main() {
boa.CmdT[Params]{
Use: "greet",
Short: "Greet someone",
RunFunc: func(p *Params, cmd *cobra.Command, args []string) {
for i := 0; i < p.Count; i++ {
fmt.Printf("Hello, %s!\n", p.Name)
}
},
}.Run()
}
Environment Variables¶
Bind parameters to environment variables with the env tag. Environment variables take precedence over defaults but are overridden by CLI flags.
package main
import (
"fmt"
"github.com/GiGurra/boa/pkg/boa"
"github.com/spf13/cobra"
)
type Params struct {
Host string `descr:"Server host" env:"APP_HOST" default:"localhost"`
Port int `descr:"Server port" env:"APP_PORT" default:"8080"`
APIKey string `descr:"API key" env:"APP_API_KEY"`
LogLevel string `descr:"Log level" env:"APP_LOG_LEVEL" default:"info"`
}
func main() {
boa.CmdT[Params]{
Use: "server",
Short: "Start the server",
RunFunc: func(p *Params, cmd *cobra.Command, args []string) {
fmt.Printf("Host: %s\nPort: %d\nAPI Key: %s\nLog: %s\n",
p.Host, p.Port, p.APIKey, p.LogLevel)
},
}.Run()
}
$ APP_API_KEY=secret123 APP_PORT=3000 go run .
Host: localhost
Port: 3000
API Key: secret123
Log: info
# CLI overrides env var:
$ APP_PORT=3000 go run . --port 9090
Host: localhost
Port: 9090
API Key:
...
Environment variables are shown in help output:
Flags:
--api-key string API key (env: APP_API_KEY) (required)
--host string Server host (env: APP_HOST) (default "localhost")
--log-level string Log level (env: APP_LOG_LEVEL) (default "info")
--port int Server port (env: APP_PORT) (default 8080)
Auto-Deriving Environment Variables¶
Instead of tagging each field, use ParamEnricherEnv to auto-derive env var names from flag names:
boa.CmdT[Params]{
Use: "server",
ParamEnrich: boa.ParamEnricherCombine(
boa.ParamEnricherDefault,
boa.ParamEnricherEnv,
),
RunFunc: func(p *Params, cmd *cobra.Command, args []string) {
// ServerHost -> --server-host -> SERVER_HOST
// MaxRetries -> --max-retries -> MAX_RETRIES
},
}.Run()
Env Var Prefix¶
Add a prefix to all auto-generated env var names:
boa.CmdT[Params]{
Use: "server",
ParamEnrich: boa.ParamEnricherCombine(
boa.ParamEnricherDefault,
boa.ParamEnricherEnv,
boa.ParamEnricherEnvPrefix("MYAPP"),
),
RunFunc: func(p *Params, cmd *cobra.Command, args []string) {
// Port -> --port -> PORT -> MYAPP_PORT
},
}.Run()
Slice Parameters¶
Slice fields accept comma-separated values or repeated flags.
package main
import (
"fmt"
"github.com/GiGurra/boa/pkg/boa"
"github.com/spf13/cobra"
)
type Params struct {
Numbers []int `descr:"List of numbers"`
Tags []string `descr:"Tags to apply" default:"[web,api]"`
}
func main() {
boa.CmdT[Params]{
Use: "demo",
Short: "Demo slice flags",
RunFunc: func(p *Params, cmd *cobra.Command, args []string) {
fmt.Printf("Numbers: %v\nTags: %v\n", p.Numbers, p.Tags)
},
}.Run()
}
$ go run . --numbers 1,2,3
Numbers: [1 2 3]
Tags: [web api]
$ go run . --numbers 1,2,3 --tags prod,backend
Numbers: [1 2 3]
Tags: [prod backend]
Enum Values with Alternatives¶
Restrict a parameter to specific values with the alts tag. By default, alternatives are strict (validation enforced).
package main
import (
"fmt"
"github.com/GiGurra/boa/pkg/boa"
"github.com/spf13/cobra"
)
type Params struct {
Format string `descr:"Output format" alts:"json,yaml,table" default:"table"`
LogLevel string `descr:"Log level" alts:"debug,info,warn,error" default:"info"`
Color string `descr:"Color theme" alts:"light,dark,auto" strict:"false" default:"auto"`
}
func main() {
boa.CmdT[Params]{
Use: "export",
Short: "Export data",
RunFunc: func(p *Params, cmd *cobra.Command, args []string) {
fmt.Printf("Format: %s, Log: %s, Color: %s\n", p.Format, p.LogLevel, p.Color)
},
}.Run()
}
$ go run . --format json
Format: json, Log: info, Color: auto
$ go run . --format xml
# Error: invalid value "xml" for flag --format: must be one of [json yaml table]
# Color accepts any value since strict:"false":
$ go run . --color custom-theme
Format: table, Log: info, Color: custom-theme
Alternatives also power shell completion -- pressing Tab suggests valid values.
Checking If a Value Was Provided¶
Use RunFuncCtx and ctx.HasValue() to check whether a parameter was explicitly set (via CLI, env var, or default) vs left unset.
package main
import (
"fmt"
"github.com/GiGurra/boa/pkg/boa"
"github.com/spf13/cobra"
)
type Params struct {
Host string `descr:"Server host" default:"localhost"`
Port *int `descr:"Server port"`
}
func main() {
boa.CmdT[Params]{
Use: "server",
Short: "Start server",
RunFuncCtx: func(ctx *boa.HookContext, p *Params, cmd *cobra.Command, args []string) {
fmt.Printf("Host: %s\n", p.Host)
if p.Port != nil {
fmt.Printf("Port: %d\n", *p.Port)
} else {
fmt.Println("Port: auto-assign")
}
if ctx.HasValue(&p.Host) {
fmt.Println("(host was explicitly configured)")
}
},
}.Run()
}