Go Subcommand (gosubc) is a command-line code generation tool that creates subcommand code for command-line interfaces (CLIs) in Go from source code comments. It is designed to be installed and run as a standalone executable during your build process (e.g., via go generate), not imported as a library dependency in your Go code.
By leveraging specially formatted code comments, it automatically generates a dependency-less subcommand system, allowing you to focus on your application's core logic instead of boilerplate code.
Status: Pre-v1. The API and generated code structure may change.
- Convention over Configuration: Define your CLI structure with simple, intuitive code comments.
- Zero Dependencies: The generated code is self-contained and doesn't require any external libraries.
- Automatic Code Generation:
gosubcparses your Go files and generates a complete, ready-to-use CLI. - Parameter Auto-Mapping: Automatically maps CLI flags, positional arguments, and variadic arguments to function parameters.
- Rich Syntax Support: Supports custom flag names, default values, and description overrides via comments.
- Man Page Generation: Automatically generate Unix man pages for your CLI.
To install gosubc, use go install:
go install github.com/arran4/go-subcommand/cmd/gosubc@latestCreate a Go file and define a function that will serve as your command. Add a comment above the function in the format // FunctionName is a subcommand 'root-command sub-command...'.
Example main.go:
package main
import "fmt"
// PrintHelloWorld is a subcommand `my-app hello`
// This command prints "Hello, World!" to the console.
func PrintHelloWorld() {
fmt.Println("Hello, World!")
}Create a file named generate.go in the same directory (package main). This robust version checks if gosubc is installed; if not, it uses go run to fetch and run it. This ensures it works for everyone without manual installation steps.
package main
//go:generate sh -c "command -v gosubc >/dev/null 2>&1 && gosubc generate || go run github.com/arran4/go-subcommand/cmd/gosubc generate"Run go generate in your terminal:
go generateThis will create a cmd/my-app directory containing the generated CLI code.
You can now run your newly generated CLI:
go run ./cmd/my-app helloOutput:
Hello, World!
go-subcommand uses a specific comment syntax to configure your CLI.
The primary directive defines where the command lives in the CLI hierarchy:
// FuncName is a subcommand `root-cmd parent child`You can define aliases for a subcommand using the Aliases: or Alias: directive.
// MyFunc is a subcommand `app cmd`
// Aliases: c, command
func MyFunc() { ... }You can also use an inline syntax:
// MyFunc is a subcommand `app cmd` (aka: c)
func MyFunc() { ... }- Short Description: The text immediately following the subcommand definition (or prefixed with
thator--) becomes the short description used in usage lists. - Extended Help: Any subsequent lines that do not look like parameter definitions are treated as extended help text, displayed when the user requests help for that specific command.
// MyFunc is a subcommand `app cmd` -- Does something cool
//
// This is the extended help text. It can span multiple lines
// and provide detailed usage examples or explanations.Function parameters are automatically mapped to CLI flags. You can customize them using comments. go-subcommand looks for configuration in three places, in this priority order (highest to lowest):
Flags:Block: A dedicated block in the main function documentation.- Inline Comments: Comments on the same line as the parameter definition.
- Preceding Comments: Comments on the line immediately before the parameter.
This is the cleanest way to define multiple parameters. It must be an indented block following a line containing just Flags:.
// MyFunc is a subcommand `app cmd`
//
// Flags:
//
// username: --username -u (default: "guest") The user to greet
// count: --count -c (default: 1) Number of times
func MyFunc(username string, count int) { ... }Inside a Flags: block or inline/preceding comments, you can use the following syntax tokens to configure a parameter:
- Flags:
-f,--flag. One or more flag aliases. - Default Value:
default: valueordefault: "value". - Required:
required. Marks a flag as required; generated execution returns an error if it is omitted. - From Parent:
from parent. Maps a child parameter to a flag declared on an ancestor command. - Custom Parser:
parser: Funcorparser: "import/path".Func. Uses a custom string parser for the parameter. - Generator:
generator: Funcorgenerator: "import/path".Func. Populates the parameter from code instead of exposing it as a CLI flag. - Positional Argument:
@N(e.g.,@1,@2). Maps the Nth positional argument (1-based) to this parameter. - Variadic Arguments:
min...max(e.g.,1...3) or.... Maps remaining arguments to a slice. - Description: Any remaining text is treated as the parameter description.
Multiple parenthesized attributes can be combined with semicolons, for example (required; parser: ParseThing).
The following Go types are supported for function parameters:
string: (Default)int: Parsed as an integer.bool: Parsed as a boolean flag (no value required, e.g.,--verbose).time.Duration: Parsed usingtime.ParseDuration(e.g.,10s,1h).io.Readerandio.ReadCloser:-borrows standard input; any other value is opened as an input file.io.Writerandio.WriteCloser:-borrows standard output; any other value is opened with create, truncate, and write access.- Pointers such as
*int: preserve the difference between omitted and explicitly provided zero values. - Slices such as
[]string: support repeatable flags. error: (Return value only) Your function can return anerror, which will be propagated to the CLI exit code.
For I/O parameters, the literal values stdin and stdout are ordinary file paths; only - selects a standard stream. Files opened by generated code are closed on every exit path, while borrowed standard streams are never closed. Bare *os.File parameters are rejected for implicit CLI binding because their intended access mode is ambiguous; use the appropriate reader/writer interface or configure an explicit provider.
To accept positional arguments instead of flags, use the @N syntax.
// Greet is a subcommand `app greet`
//
// Flags:
//
// name: @1 The name to greet
func Greet(name string) {
fmt.Printf("Hello, %s!\n", name)
}Usage: app greet John
To accept a variable number of arguments, use a slice parameter and mark it with ....
// ProcessFiles is a subcommand `app process`
//
// Flags:
//
// files: ... List of files to process
func ProcessFiles(files ...string) {
for _, file := range files {
fmt.Println("Processing", file)
}
}Usage: app process file1.txt file2.txt file3.txt
You can define custom short and long flags.
// Serve is a subcommand `app serve`
//
// Flags:
//
// port: -p --port (default: 8080) Port to listen on
func Serve(port int) { ... }The generated custom argument parser natively supports GNU-style single-letter short codes grouped together (e.g., -abc is parsed as -a -b -c). It also correctly handles equals assignment (e.g., -v=123 or --flag="value").
The CLI uses a custom parser generated by gosubc rather than delegating entirely to the standard flag package (which is only used to manage underlying struct bindings). This ensures deterministic and bounded behaviour out-of-the-box.
- Termination Detection: The first standalone
--token encountered halts all flag parsing and subcommand recognition for the current command. - Positional Passthrough: All tokens following
--(including subsequent--tokens, unknown flags, or subcommand names) are treated strictly as positional arguments and passed through untouched. - Command Scope: The termination is contextual to the command level where it is encountered; an
app -- subcommandpassessubcommandas an argument toapp, whileapp subcommand -- childpasseschildas an argument tosubcommand.
To enforce that a specific flag must be provided at runtime, mark the parameter with the required keyword inside parentheses (e.g., (required)). The execution will fail gracefully if a user omits the required parameter. For optional parameters, omitting the flag relies on Go zero-values unless overridden by default:.
Nesting is implicit based on the command path string.
// Root command: `app`
// Child: `app users`
// Grandchild: `app users create`
// CreateUser is a subcommand `app users create`
func CreateUser(...) { ... }
// ListUsers is a subcommand `app users list`
func ListUsers(...) { ... }Subcommands can explicitly map a parameter to a flag declared by an ancestor command using (from parent).
// Parent is a subcommand `app parent`
//
// Flags:
//
// verbose: -v --verbose
func Parent(verbose bool) { ... }
// Child is a subcommand `app parent child`
//
// Flags:
//
// verbose: (from parent)
func Child(verbose bool) {
// verbose parameter here maps to Parent's verbose variable
}gosubc supports customizing the generated code and usage text templates using the --replace-template flag.
You can supply template overlays in three formats:
- Alias File Replacement:
--replace-template usage=path/to/myusage.gotmpl(available aliases:usage,man,cmd,root,templates). - Folder Overlay:
--replace-template path/to/templates_diroverlays a directory containing custom.gotmplfiles onto default templates. - txtar Archive Overlay:
--replace-template path/to/templates.txtaroverlays a.txtararchive containing custom template files.
Multiple overlays are applied in command-line order. Later definitions with the same template name replace earlier definitions, while unrelated named definitions from every layer remain available.
To view or export the built-in templates:
gosubc template layout: Displays the directory tree structure of built-in templates.gosubc template export [--output <dir>] [--as-txtar]: Exports all built-in templates to a directory or a single.txtararchive file.
Generated CLI usage output automatically wraps parameter descriptions based on the terminal width specified by the COLUMNS environment variable (falling back to 80 columns).
To generate man pages, pass the --man-dir flag to gosubc.
gosubc generate --man-dir ./manThis will generate standard Unix man pages in the specified directory, using the descriptions and extended help text from your comments.
Generates the Go code for your CLI.
--dir <path>: Root directory containinggo.mod. Defaults to current directory.--man-dir <path>: Directory to write man pages to.--replace-template <alias>=<file>|<dir>|<txtar>: Overlays custom templates onto built-in generation templates.
Manage generation templates.
gosubc template export [--output <path>] [--as-txtar]: Export built-in templates.gosubc template layout: Display template structure layout.
Lists all detected subcommands.
--dir <path>: Root directory.
Validates subcommand definitions for errors or conflicts.
--dir <path>: Root directory.
Generates release configuration.
--dir <path>: Root directory containinggo.mod. Defaults to current directory.--go-releaser-github-workflow: Generate GitHub Action workflow for GoReleaser.
Contributions are welcome! If you find a bug or have a feature request, please open an issue on our GitHub repository.
This project is licensed under the BSD 3-Clause License. See the LICENSE file for details.