Advanced Features¶
This page covers advanced BOA features for power users.
The Param Interface¶
Every parameter (whether using struct tags or programmatic configuration) implements the Param interface. Access it via HookContext.GetParam():
Param Methods¶
| Method | Description |
|---|---|
SetName(string) |
Override flag name |
SetShort(string) |
Set short flag |
SetEnv(string) |
Set environment variable |
SetDefault(any) |
Set default value |
SetAlternatives([]string) |
Set allowed values |
SetAlternativesFunc(func(...) []string) |
Set dynamic completion function |
SetStrictAlts(bool) |
Enable/disable strict validation |
SetRequiredFn(func() bool) |
Dynamic required condition |
SetIsEnabledFn(func() bool) |
Dynamic visibility |
GetName() string |
Get current flag name |
GetShort() string |
Get current short flag |
GetEnv() string |
Get current env var |
GetAlternatives() []string |
Get allowed values |
GetAlternativesFunc() |
Get dynamic completion function |
HasValue() bool |
Check if value was set |
IsRequired() bool |
Check if required |
IsEnabled() bool |
Check if visible |
Typed Parameter API (ParamT)¶
For type-safe parameter configuration, use boa.GetParamT[T]() instead of GetParam(). This returns a ParamT[T] interface with typed methods:
type Params struct {
Port int `descr:"Server port"`
Host string `descr:"Server host"`
}
boa.CmdT[Params]{
Use: "server",
InitFuncCtx: func(ctx *boa.HookContext, p *Params, cmd *cobra.Command) error {
// Type-safe: compiler ensures correct types
portParam := boa.GetParamT(ctx, &p.Port)
portParam.SetDefaultT(8080) // Takes int, not any
portParam.SetCustomValidatorT(func(port int) error {
if port < 1 || port > 65535 {
return fmt.Errorf("port must be between 1 and 65535")
}
return nil
})
hostParam := boa.GetParamT(ctx, &p.Host)
hostParam.SetDefaultT("localhost") // Takes string
hostParam.SetAlternatives([]string{"localhost", "0.0.0.0"})
return nil
},
}
type Params struct {
Port int `descr:"Server port"`
Host string `descr:"Server host"`
}
boa.NewCmdT[Params]("server").
WithInitFuncCtx(func(ctx *boa.HookContext, p *Params, cmd *cobra.Command) error {
// Type-safe: compiler ensures correct types
portParam := boa.GetParamT(ctx, &p.Port)
portParam.SetDefaultT(8080) // Takes int, not any
portParam.SetCustomValidatorT(func(port int) error {
if port < 1 || port > 65535 {
return fmt.Errorf("port must be between 1 and 65535")
}
return nil
})
hostParam := boa.GetParamT(ctx, &p.Host)
hostParam.SetDefaultT("localhost") // Takes string
hostParam.SetAlternatives([]string{"localhost", "0.0.0.0"})
return nil
})
ParamT Methods¶
The ParamT[T] interface provides typed methods plus all pass-through methods from Param:
| Typed Methods | Description |
|---|---|
SetDefaultT(T) |
Set default value with compile-time type checking |
SetCustomValidatorT(func(T) error) |
Set validation function that receives the typed value |
| Pass-through Methods | Description |
|---|---|
Param() |
Access the underlying untyped Param interface |
SetAlternatives([]string) |
Set allowed values |
SetStrictAlts(bool) |
Enable/disable strict validation |
SetAlternativesFunc(...) |
Set dynamic completion function |
SetEnv(string) |
Set environment variable |
SetShort(string) |
Set short flag |
SetName(string) |
Set flag name |
SetIsEnabledFn(func() bool) |
Dynamic visibility |
SetRequiredFn(func() bool) |
Dynamic required condition |
Conditional Requirements with ParamT¶
type DeployParams struct {
Environment string `descr:"Target environment" default:"dev"`
ProdKey string `descr:"Production API key" optional:"true"`
}
boa.NewCmdT[DeployParams]("deploy").
WithInitFuncCtx(func(ctx *boa.HookContext, p *DeployParams, cmd *cobra.Command) error {
// ProdKey is only required when deploying to production
prodKeyParam := boa.GetParamT(ctx, &p.ProdKey)
prodKeyParam.SetRequiredFn(func() bool {
return p.Environment == "prod"
})
return nil
})
Dynamic Shell Completion¶
AlternativesFunc¶
For completion suggestions that depend on runtime state (like fetching from an API), use SetAlternativesFunc via HookContext:
type Params struct {
Region string `descr:"AWS region"`
}
func main() {
boa.CmdT[Params]{
Use: "app",
InitFuncCtx: func(ctx *boa.HookContext, p *Params, cmd *cobra.Command) error {
ctx.GetParam(&p.Region).SetAlternativesFunc(
func(cmd *cobra.Command, args []string, toComplete string) []string {
// Could fetch from API, read from file, etc.
return []string{"us-east-1", "us-west-2", "eu-west-1"}
},
)
return nil
},
RunFunc: func(params *Params, cmd *cobra.Command, args []string) {
// ...
},
}.Run()
}
type Params struct {
Region string `descr:"AWS region"`
}
func main() {
boa.NewCmdT[Params]("app").
WithInitFuncCtx(func(ctx *boa.HookContext, p *Params, cmd *cobra.Command) error {
ctx.GetParam(&p.Region).SetAlternativesFunc(
func(cmd *cobra.Command, args []string, toComplete string) []string {
// Could fetch from API, read from file, etc.
return []string{"us-east-1", "us-west-2", "eu-west-1"}
},
)
return nil
}).
WithRunFunc(func(params *Params) {
// ...
}).
Run()
}
ValidArgsFunc¶
For positional argument completion:
Config File Loading¶
Load configuration from files in the PreValidate hook:
type AppConfig struct {
Host string
Port int
}
type Params struct {
ConfigFile string `descr:"Path to config file" optional:"true"`
AppConfig
}
func main() {
boa.CmdT[Params]{
Use: "app",
PreValidateFuncCtx: func(ctx *boa.HookContext, p *Params, cmd *cobra.Command, args []string) error {
fileParam := ctx.GetParam(&p.ConfigFile)
return boa.UnMarshalFromFileParam(fileParam, &p.AppConfig, nil)
},
RunFunc: func(p *Params, cmd *cobra.Command, args []string) {
fmt.Printf("Host: %s, Port: %d\n", p.Host, p.Port)
},
}.Run()
}
type AppConfig struct {
Host string
Port int
}
type Params struct {
ConfigFile string `descr:"Path to config file" optional:"true"`
AppConfig
}
func main() {
boa.NewCmdT[Params]("app").
WithPreValidateFuncCtx(func(ctx *boa.HookContext, p *Params, cmd *cobra.Command, args []string) error {
fileParam := ctx.GetParam(&p.ConfigFile)
return boa.UnMarshalFromFileParam(fileParam, &p.AppConfig, nil)
}).
WithRunFunc(func(p *Params) {
fmt.Printf("Host: %s, Port: %d\n", p.Host, p.Port)
}).
Run()
}
Custom unmarshal functions (YAML, TOML, etc.):
Checking Value Sources¶
Use HookContext in your run function to check how values were set:
Accessing Cobra Directly¶
Access the underlying Cobra command for features BOA doesn't wrap:
Or after flags are created:
Command Groups¶
Organize subcommands into groups in help output:
boa.CmdT[boa.NoParams]{
Use: "app",
Groups: []*cobra.Group{
{ID: "core", Title: "Core Commands:"},
{ID: "util", Title: "Utility Commands:"},
},
SubCmds: boa.SubCmds(
boa.CmdT[Params]{Use: "init", GroupID: "core"},
boa.CmdT[Params]{Use: "run", GroupID: "core"},
boa.CmdT[Params]{Use: "version", GroupID: "util"},
),
}
boa.NewCmdT[boa.NoParams]("app").
WithGroups(
&cobra.Group{ID: "core", Title: "Core Commands:"},
&cobra.Group{ID: "util", Title: "Utility Commands:"},
).
WithSubCmds(
boa.NewCmdT[Params]("init").WithGroupID("core"),
boa.NewCmdT[Params]("run").WithGroupID("core"),
boa.NewCmdT[Params]("version").WithGroupID("util"),
)
Testing Commands¶
Inject Arguments¶
Testing with Error Returns¶
Use RunFuncE and RunArgsE for testable commands that return errors:
func TestMyCommand(t *testing.T) {
err := boa.NewCmdT[Params]("app").
WithRunFuncE(func(p *Params) error {
if p.Port < 1024 {
return fmt.Errorf("port must be >= 1024")
}
return nil
}).
RunArgsE([]string{"--port", "80"})
if err == nil {
t.Fatal("expected error for port < 1024")
}
}
Use ToCobraE() when you need the underlying cobra command with RunE set:
func TestMyCommand(t *testing.T) {
cmd, err := boa.NewCmdT[Params]("app").
WithRunFuncE(func(p *Params) error {
return nil
}).
ToCobraE()
if err != nil {
t.Fatalf("setup failed: %v", err)
}
cmd.SetArgs([]string{"--name", "test"})
err = cmd.Execute()
// Assert on err
}
Validate Without Running¶
Interface-Based Hooks¶
Implement interfaces on your config struct instead of using With* methods:
type Config struct {
Host string
Port int
}
// Called during initialization
func (c *Config) Init() error {
return nil
}
// Called during initialization with HookContext
func (c *Config) InitCtx(ctx *boa.HookContext) error {
ctx.GetParam(&c.Port).SetDefault(boa.Default(8080))
return nil
}
// Called before validation
func (c *Config) PreValidate() error {
return nil
}
// Called before execution
func (c *Config) PreExecute() error {
return nil
}
Available interfaces:
CfgStructInit-Init() errorCfgStructInitCtx-InitCtx(ctx *HookContext) errorCfgStructPostCreate-PostCreate() errorCfgStructPostCreateCtx-PostCreateCtx(ctx *HookContext) errorCfgStructPreValidate-PreValidate() errorCfgStructPreValidateCtx-PreValidateCtx(ctx *HookContext) errorCfgStructPreExecute-PreExecute() errorCfgStructPreExecuteCtx-PreExecuteCtx(ctx *HookContext) error