Skip to content

Latest commit

 

History

681 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Go Subcommand

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.

Key Features

  • 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: gosubc parses 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.

Installation

To install gosubc, use go install:

go install github.com/arran4/go-subcommand/cmd/gosubc@latest

Getting Started

1. Define Your Commands

Create 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!")
}

2. Add a generate.go File

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"

3. Generate the CLI

Run go generate in your terminal:

go generate

This will create a cmd/my-app directory containing the generated CLI code.

4. Run Your New CLI

You can now run your newly generated CLI:

go run ./cmd/my-app hello

Output:

Hello, World!

Comment Syntax Guide

go-subcommand uses a specific comment syntax to configure your CLI.

Subcommand Definition

The primary directive defines where the command lives in the CLI hierarchy:

// FuncName is a subcommand `root-cmd parent child`

Subcommand Aliases

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() { ... }

Description & Extended Help

  • Short Description: The text immediately following the subcommand definition (or prefixed with that or -- ) 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.

Parameter Configuration

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):

  1. Flags: Block: A dedicated block in the main function documentation.
  2. Inline Comments: Comments on the same line as the parameter definition.
  3. Preceding Comments: Comments on the line immediately before the parameter.

The Flags: Block

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) { ... }

Syntax Reference

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: value or default: "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: Func or parser: "import/path".Func. Uses a custom string parser for the parameter.
  • Generator: generator: Func or generator: "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).

Supported Types

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 using time.ParseDuration (e.g., 10s, 1h).
  • io.Reader and io.ReadCloser: - borrows standard input; any other value is opened as an input file.
  • io.Writer and io.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 an error, 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.

Advanced Usage

Positional Arguments

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

Variadic Arguments

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

Custom Flags

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) { ... }

GNU-Style Flag Grouping & Assignments

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").

GNU-Style -- Termination (Parser Architecture)

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 -- subcommand passes subcommand as an argument to app, while app subcommand -- child passes child as an argument to subcommand.

Required vs Optional Parameters

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 Commands

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(...) { ... }

Parent Flags (Inheritance)

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
}

Template Customization & Line Wrapping

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_dir overlays a directory containing custom .gotmpl files onto default templates.
  • txtar Archive Overlay: --replace-template path/to/templates.txtar overlays a .txtar archive 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 .txtar archive 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).

Man Page Generation

To generate man pages, pass the --man-dir flag to gosubc.

gosubc generate --man-dir ./man

This will generate standard Unix man pages in the specified directory, using the descriptions and extended help text from your comments.

CLI Reference

gosubc generate

Generates the Go code for your CLI.

  • --dir <path>: Root directory containing go.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.

gosubc template

Manage generation templates.

  • gosubc template export [--output <path>] [--as-txtar]: Export built-in templates.
  • gosubc template layout: Display template structure layout.

gosubc list

Lists all detected subcommands.

  • --dir <path>: Root directory.

gosubc validate

Validates subcommand definitions for errors or conflicts.

  • --dir <path>: Root directory.

gosubc goreleaser

Generates release configuration.

  • --dir <path>: Root directory containing go.mod. Defaults to current directory.
  • --go-releaser-github-workflow: Generate GitHub Action workflow for GoReleaser.

Contributing

Contributions are welcome! If you find a bug or have a feature request, please open an issue on our GitHub repository.

License

This project is licensed under the BSD 3-Clause License. See the LICENSE file for details.

About

Generates dependencyless go command line argument subcommand system

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages