Package command provides a simple, declarative way to build tree-based CLI commands and subcommands. A key design feature is that command implementations do not need to import this package, only the standard flag package.
Each command can have its own flags and nested subcommands. The package automatically handles parsing, help text generation, and execution.
go get resenje.org/commandpackage main
import (
"flag"
"fmt"
"resenje.org/command"
)
func main() {
command.Main(&command.Command{
Help: "A simple greeter.",
Execute: executeGreeter,
})
}
func executeGreeter(fs *flag.FlagSet) func(args []string) error {
var greeting string
fs.StringVar(&greeting, "greeting", "Hello", "The greeting to use")
return func(args []string) error {
if len(args) == 0 {
fmt.Println("Please provide a name.")
return nil
}
fmt.Printf("%s, %s!\n", greeting, args[0])
return nil
}
}package main
import (
"flag"
"fmt"
"resenje.org/command"
)
func main() {
command.Main(&command.Command{
Help: "A git-like app",
Subcommands: []*command.Command{
{
Name: "add",
Help: "Add a new remote",
Execute: executeAdd,
},
},
})
}
func executeAdd(fs *flag.FlagSet) func(args []string) error {
var dryRun bool
fs.BoolVar(&dryRun, "n", false, "Do not actually add, just show")
return func(args []string) error {
if len(args) < 2 {
fmt.Println("Error: remote name and URL required.")
return nil
}
name, url := args[0], args[1]
if dryRun {
fmt.Printf("Would add remote %s with URL %s\n", name, url)
} else {
fmt.Printf("Adding remote %s with URL %s\n", name, url)
}
return nil
}
}For commands that do not require any flags, you can use the WithoutFlags helper to simplify the Execute function.
package main
import (
"fmt"
"strings"
"resenje.org/command"
)
func main() {
command.Main(&command.Command{
Subcommands: []*command.Command{
&command.Command{
Name: "echo",
Help: "Prints its arguments to the screen.",
Execute: command.WithoutFlags(echoFunc),
},
},
})
}
func echoFunc(args []string) error {
fmt.Println(strings.Join(args, " "))
return nil
}Running this with go run . echo hello world would output:
hello world
You can use the WithMiddleware helper to add functionality that runs before or after a command's execution. This is useful for cross-cutting concerns like logging, metrics, or authentication.
A middleware is a function that takes the next execution function and returns a new one.
// This middleware logs a message before and after the command runs.
func loggingMiddleware(next func(args []string) error) func(args []string) error {
return func(args []string) error {
fmt.Println("Middleware: before execution")
err := next(args)
fmt.Println("Middleware: after execution")
return err
}
}
// In your main function, wrap the Execute function with WithMiddleware.
var rootCmd = &command.Command{
Help: "A simple command with middleware.",
Execute: command.WithMiddleware(
func(fs *flag.FlagSet) func(args []string) error {
return func(args []string) error {
fmt.Println("Executing command")
return nil
}
},
loggingMiddleware, // Apply the middleware
),
}Running this application would produce the following output, showing that the middleware wraps the core command logic:
Middleware: before execution
Executing command
Middleware: after execution
The help text is automatically generated based on the Help fields of the Command structs.
Running the nested example with go run . remote -h will produce the following output:
USAGE
main remote [options...] command
Manage remotes
COMMANDS
add Add a new remote
OPTIONS
-h Show help for this command.
There are several excellent CLI libraries in the Go ecosystem. This library is designed to be a simple, dependency-free solution for creating basic to moderately complex CLI tools.
Here is a brief comparison with two of the most popular libraries, spf13/cobra and urfave/cli.
| Feature | resenje.org/command |
urfave/cli |
spf13/cobra |
|---|---|---|---|
| Subcommands | ✅ | ✅ | ✅ |
| Automatic Help | ✅ | ✅ | ✅ |
| Shell Completion | ❌ | ✅ | ✅ |
| Flag Parsing | Standard flag
|
Advanced | Advanced (pflag) |
| Config Files | ❌ | Via plugin | ✅ (with Viper) |
| Generator Tool | ❌ | ❌ | ✅ |
| Dependencies | None | None | Multiple |
| Simplicity | Very High | High | Medium |
-
Simplicity: The API is minimal and easy to learn. If you know how to use the standard
flagpackage, you can be productive immediately. - Zero Dependencies: It only uses the Go standard library. This keeps your binaries small and your dependency tree clean.
-
Decoupled Command Logic: Command implementations do not need to import
resenje.org/command. TheExecutefunction only receives a*flag.FlagSet, allowing command logic to be written and tested in complete isolation from the command runner. This promotes reusable and portable code. - Declarative API: The struct-based definition for commands makes the hierarchy clear and easy to read.
- Limited Features: It lacks advanced features found in other libraries, such as shell completion, configuration file integration, and command generators.
-
Basic Flag Support: It uses the standard
flagpackage, which is less powerful than thepflaglibrary used by Cobra (e.g.,pflagoffers better POSIX compatibility and more flag types).
This library is an excellent choice when you need to build a CLI with subcommands but do not require the extensive features of a library like Cobra. It is ideal for internal tools, simple daemons, or any application where you want to maintain zero dependencies and prefer a minimal API.