An interface driven cli lib for go.
Have you ever said
Why can't writing CLIs be as easy as defining a struct and writing a function?
Then quack is for you.
- ποΈ Struct-based CLI - Define commands using simple Go structs
- π― Positional arguments - Use
arg:"1",arg:"2"tags for positional args - π Repeated arguments - Slices are automatically treated as variadic
- π·οΈ Named options - Support for short (
-f) and long (--file) flags - πͺ Nested sub-commands - Easy command hierarchies
- π Multiple frameworks - Works with Cobra and urfave/cli v2
Integration with spf13/cobra
The BindCobra API creates a cobra.Command from the given structure. This allows for easy integration
with existing CLIs that use this framework.
cmd, err := quack.BindCobra("myapp", new(MyCommand))Integration with urfave/cli v3
The BindUrfave API creates a cli.Command from the given structure, providing seamless integration
with urfave/cli v3.
cmd, err := quack.BindUrfave("myapp", new(MyCommand))For urfave-specific features like accessing the *cli.Command, your command can implement the UrfaveCommand interface:
type MyCmd struct {
Name string
}
func (m *MyCmd) Run(ctx context.Context, cmd *cli.Command) error {
// Access urfave-specific command features
fmt.Println(cmd.Name)
return nil
}Both Cobra and urfave/cli v3 are now fully supported! Feel free to file an issue if you'd like support for additional frameworks.
main.go
type ToHex struct {
Input int
}
func (t ToHex) Run() {
fmt.Printf("%x", t.Input)
}
func main() {
quack.MustBind("tohex", new(ToHex)).Execute()
}Can now be run
go run main.go --input 12334
302e
Use the arg tag to specify positional arguments:
type CopyCmd struct {
Source string `arg:"1"`
Target string `arg:"2"`
}
func (c *CopyCmd) Run([]string) {
fmt.Printf("Copying %s to %s\n", c.Source, c.Target)
}
func main() {
quack.MustBind("copy", new(CopyCmd)).Execute()
}$ go run main.go copy source.txt target.txt
Copying source.txt to target.txtSlices are automatically treated as variadic arguments:
type CompileCmd struct {
Files []string `arg:"1"` // Consumes all remaining args
}
func (c *CompileCmd) Run([]string) {
fmt.Printf("Compiling: %v\n", c.Files)
}$ go run main.go compile file1.go file2.go file3.go
Compiling: [file1.go file2.go file3.go]type BuildCmd struct {
Verbose bool `short:"v" help:"Enable verbose output"`
Output string `short:"o" help:"Output file"`
Files []string `arg:"1" help:"Source files"`
}
func (b *BuildCmd) Run([]string) {
if b.Verbose {
fmt.Printf("Building %v -> %s\n", b.Files, b.Output)
}
// ... build logic
}$ go run main.go build -v -o app.bin main.go utils.go
Building [main.go utils.go] -> app.binSlices work as flags too:
type ServerCmd struct {
Port int `short:"p" default:"8080"`
Allowed []string `short:"a" help:"Allowed IPs"`
}$ go run main.go server -a 192.168.1.1 -a 10.0.0.1examples/deeply_nested/main.go
package main
import "github.com/eliothedeman/quack"
type a struct {
}
func (a) SubCommands() quack.Map {
return quack.Map{
"b": new(b),
}
}
type b struct {
}
func (b) SubCommands() quack.Map {
return quack.Map{
"c": new(c),
}
}
type c struct {
XX string `default:"YYY" short:"x"`
Y int `help:"this is a help message"`
Z bool `default:"true"`
}
func (c) Run([]string) {
}
func (c) Help() string {
return "the nested c command"
}
func main() {
quack.MustBind("nested", new(a))
}go run examples/deeply_nested/main.go b -h
Usage: b <cmd> [args]
c the nested c command
go run examples/deeply_nested/main.go b c -h
Usage: c [args]
the nested c command
Flags:
--z (default=true)
Options:
-x, --xx string (default='YYY')
--y int (default=0) this is a help message
| Tag | Description | Example |
|---|---|---|
arg:"N" |
Positional argument at position N (1-indexed) | arg:"1" |
short:"x" |
Short flag name | short:"v" for -v |
long:"name" |
Long flag name (auto-generated from field name if not specified) | long:"verbose" |
default:"value" |
Default value | default:"8080" |
help:"text" |
Help text for the option | help:"Port to listen on" |
ignore:"" |
Ignore this field | ignore:"" |
Note: Slice types are automatically treated as repeated/variadic - no special tag needed!