go_subcommand

package module
v0.0.12 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 5, 2026 License: BSD-3-Clause Imports: 22 Imported by: 0

README

Go Subcommand

Go Subcommand generates subcommand code for command-line interfaces (CLIs) in Go from source code comments. 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).

Option 1: Standard (Recommended) Use this if gosubc is installed in your system PATH.

package main

//go:generate gosubc generate

Option 2: Robust (Recommended for teams) This 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`
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".
  • 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.
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).
  • error: (Return value only) Your function can return an error, which will be propagated to the CLI exit code.

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

Lists all detected subcommands.

  • --dir <path>: Root directory.
gosubc validate

Validates subcommand definitions for errors or conflicts.

  • --dir <path>: Root directory.

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.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Generate

func Generate(dir string, manDir string) error

Generate is a subcommand `gosubc generate` that generates the subcommand code param dir (default: ".") Project root directory containing go.mod param manDir (--man-dir) Directory to generate man pages in (optional)

func GenerateWithFS added in v0.0.11

func GenerateWithFS(inputFS fs.FS, writer FileWriter, dir string, manDir string) error

GenerateWithFS generates code using provided FS and Writer

func List

func List(dir string) error

List is a subcommand `gosubc list` that lists the subcommands param dir (default: ".") The project root directory containing go.mod

func ParseGoFile

func ParseGoFile(fset *token.FileSet, filename, importPath string, file io.Reader, cmdTree *CommandsTree) error

func ParseSubCommandComments

func ParseSubCommandComments(text string) (cmdName string, subCommandSequence []string, description string, extendedHelp string, params map[string]ParsedParam, ok bool)

func SanitizeToIdentifier added in v0.0.11

func SanitizeToIdentifier(name string) string

SanitizeToIdentifier converts a string into a valid Go identifier (CamelCase). It handles hyphens, underscores, and other non-alphanumeric characters by acting as delimiters for CamelCasing.

func ToKebabCase added in v0.0.11

func ToKebabCase(s string) string

ToKebabCase converts a CamelCase string to kebab-case. It handles acronyms (e.g. JSONData -> json-data) and simple cases (CamelCase -> camel-case).

func Validate

func Validate(dir string) error

Validate is a subcommand `gosubc validate` that validates the subcommand code param dir (default: ".") The project root directory containing go.mod

Types

type Command

type Command struct {
	*DataModel
	MainCmdName  string
	SubCommands  []*SubCommand
	PackagePath  string
	ImportPath   string
	Description  string
	ExtendedHelp string
	FunctionName string
	Parameters   []*FunctionParameter
	ReturnsError bool
	ReturnCount  int
}

type CommandTree

type CommandTree struct {
	CommandName string
	*SubCommandTree
	FunctionName string
	Parameters   []*FunctionParameter
	ReturnsError bool
	ReturnCount  int
	Description  string
	ExtendedHelp string
}

type CommandsTree

type CommandsTree struct {
	Commands    map[string]*CommandTree
	PackagePath string
}

func (*CommandsTree) Insert

func (cst *CommandsTree) Insert(importPath, packageName, cmdName string, subcommandSequence []string, s *SubCommand)

type DataModel

type DataModel struct {
	PackageName string
	Commands    []*Command
}

func ParseGoFiles

func ParseGoFiles(fsys fs.FS, root string) (*DataModel, error)

ParseGoFiles parses the Go files in the provided filesystem to build the command model. It expects a go.mod file at the root of the filesystem (or root directory).

type FileWriter added in v0.0.11

type FileWriter interface {
	WriteFile(path string, content []byte, perm os.FileMode) error
	MkdirAll(path string, perm os.FileMode) error
}

FileWriter interface allows mocking file system writes

type FunctionParameter

type FunctionParameter struct {
	Name               string
	Type               string
	FlagAliases        []string
	Default            string
	Description        string
	IsPositional       bool
	PositionalArgIndex int
	IsVarArg           bool
	VarArgMin          int
	VarArgMax          int
}

func (*FunctionParameter) FlagString added in v0.0.11

func (p *FunctionParameter) FlagString() string

type NameAllocator added in v0.0.11

type NameAllocator struct {
	// contains filtered or unexported fields
}

NameAllocator manages the assignment of unique identifier names.

func NewNameAllocator added in v0.0.11

func NewNameAllocator() *NameAllocator

NewNameAllocator creates a new allocator with pre-reserved names.

func (*NameAllocator) Allocate added in v0.0.11

func (na *NameAllocator) Allocate(input string) string

Allocate generates a unique name based on the input string. It sanitizes the input and handles collisions by appending numbers.

type OSFileWriter added in v0.0.11

type OSFileWriter struct{}

OSFileWriter implements FileWriter using os package

func (*OSFileWriter) MkdirAll added in v0.0.11

func (w *OSFileWriter) MkdirAll(path string, perm os.FileMode) error

func (*OSFileWriter) WriteFile added in v0.0.11

func (w *OSFileWriter) WriteFile(path string, content []byte, perm os.FileMode) error

type ParsedParam added in v0.0.11

type ParsedParam struct {
	Flags              []string
	Default            string
	Description        string
	IsPositional       bool
	PositionalArgIndex int
	IsVarArg           bool
	VarArgMin          int
	VarArgMax          int
}

type SubCommand

type SubCommand struct {
	*Command
	Parent                 *SubCommand
	SubCommands            []*SubCommand
	SubCommandName         string
	SubCommandStructName   string
	SubCommandFunctionName string
	SubCommandDescription  string
	SubCommandExtendedHelp string
	ImportPath             string
	SubCommandPackageName  string
	UsageFileName          string
	Parameters             []*FunctionParameter
	ReturnsError           bool
	ReturnCount            int
}

func (*SubCommand) HasSubcommands

func (sc *SubCommand) HasSubcommands() bool

func (*SubCommand) MaxFlagLength added in v0.0.11

func (sc *SubCommand) MaxFlagLength() int

func (*SubCommand) ParentCmdName

func (sc *SubCommand) ParentCmdName() string

func (*SubCommand) ProgName

func (sc *SubCommand) ProgName() string

func (*SubCommand) SubCommandSequence

func (sc *SubCommand) SubCommandSequence() string

type SubCommandTree

type SubCommandTree struct {
	SubCommands map[string]*SubCommandTree
	*SubCommand
}

func NewSubCommandTree

func NewSubCommandTree(subCommand *SubCommand) *SubCommandTree

func (*SubCommandTree) Insert

func (sct *SubCommandTree) Insert(importPath, packageName string, sequence []string, s *SubCommand)

Directories

Path Synopsis
cmd
gosubc command
returns module
examples
basic1 module
complex module
returns module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL