Skip to content

Generated Code ​

Development preview

The Go framework is not ready for adoption or testing. These pages describe work in progress; APIs, generated code, and behavior may change. See the overview for the current scope and limitations.

usage generate go lowers a KDL spec into one Go file. The output is gofmt-clean and carries the standard // Code generated … DO NOT EDIT. header.

bash
usage generate go -f mycli.usage.kdl -o tables.go -p mycli
FlagMeaning
-f --file <FILE>the KDL spec (- for stdin)
--spec <SPEC>a raw spec string instead of a file
-o --out-file <PATH>output path (- for stdout)
-p --package <NAME>package clause; defaults to the spec's bin made into an identifier

What the file exports ​

go
const Version = "1.2.3"        // only when the spec declares a version

const (                        // one key per command, flag, and argument
	CmdRoot     uint64 = 1
	FlagVerbose uint64 = 2
	ArgFile     uint64 = 3
	CmdInstall  uint64 = 4
	// …
)

var Root *argv.Command         // the hot parse table
var Meta argv.Metadata         // validation metadata (required, choices, env, defaults, relations)
var HelpText argv.HelpTable    // help text per entry
var HelpMeta argv.HelpSpec     // root-level page furniture (name, bin, version, about)

type Cli struct { /* … */ }    // one struct for the root
type InstallCmd struct { /* … */ }  // and one per command

func Parse(args []string) (*Cli, error)

Parse takes the tokens after the program name. A spec that declares multicall needs argv[0] rewritten first, because a symlink ls -> busybox has no ls word in os.Args[1:]:

go
args := argv.RewriteMulticall(os.Args[0], os.Args[1:], HelpMeta.Name, HelpMeta.Bin)
cli, err := Parse(args)

RewriteMulticall prepends argv[0]'s basename when it is an applet rather than the dispatcher (name / bin). Path components and a trailing .exe are stripped. A dispatcher invocation is a no-op.

The three tables are separate on purpose: reference only Root and the linker drops the validation metadata and help text. On mise's spec that's the difference between a 2.60MB and a 2.82MB contribution to the binary. Dispatch on the key constants, never on Name strings — a rename in the spec then fails to compile instead of silently misrouting.

The structs ​

For the spec on the intro page:

go
// Cli is the whole command line.
type Cli struct {
	Verbose bool        // FlagVerbose
	Jobs    string      // FlagJobs
	File    string      // ArgFile
	Install *InstallCmd // CmdInstall
}

// InstallCmd is `install`.
type InstallCmd struct {
	Force bool   // FlagInstallForce
	Pkg   string // ArgInstallPkg
}
  • The root struct is always Cli; a subcommand's is the Pascal-cased path plus Cmd (config ls → ConfigLsCmd).
  • Subcommands are pointers, and at most one per level is non-nil — that's how you tell which path was taken.
  • Field types: count flags → int; value-less flags → bool; var flags/args → []string; everything else → string. There is no type inference from the spec — a spec says what a value is called, never what type it is. Convert with the typed helpers, or opt in to explicit generated types below.
  • A flag and a command sharing a name are disambiguated by kind: a --shell flag beside a shell command yields fields Shell and ShellCmd, not Shell2.

Explicit Go field types ​

Types are opt-in generator settings, not inferred from names like <duration>:

sh
usage generate go -f mycli.usage.kdl -o tables.go \
  --field-type FlagTimeout=duration --field-type FlagJobs=int

Use the generated key constants to identify fields, including their command prefix (for example, FlagRunTimeout). Repeat --field-type KEY=TYPE for each binding. Supported types are int, int64, uint64, float64, bool, and duration. A duration field has Go type time.Duration; variadic entries become slices of the chosen type. Unconfigured fields and portable spec semantics are unchanged.

Parse converts the final resolved text, after argv/env/conditional-default/default precedence and ordinary validation. Overridden values do not cause conversion errors. Absent fields keep their Go zero value, while an explicitly supplied empty string is converted and may be rejected. Errors use argv.CodeInvalidValue and identify the entry and rejected value. int rejects overflow on the target architecture; use int64 for fixed-width values. Duration notation is Go's, such as 1m30s, and conversions use the typed helpers.

Unknown keys, duplicate bindings, unsupported type names, valueless flags, and count flags are rejected at generation time. Clause-instance fields and custom converters are not supported yet. This experimental interface does not change the shared KDL schema. Library callers can use generate_with_types and ValueType alongside the unchanged generate API.

What Parse enforces ​

Parse walks the events, fills the structs, then — for the commands the words actually selected — applies fallbacks and checks:

  1. values resolve argv → env → default_if → default, per entry
  2. required, choices, var_min/var_max are checked
  3. conflicts, requires, required_if, required_if_eq, and required_unless are checked across the selected commands

A value-less flag set from an env var goes through argv.EnvTruth (usage-lib's narrow allow-list: 1, true, True, TRUE); a default on one compares against the literal "true". count fields are never filled from env or defaults — a count is occurrences, and only the command line has those. A default_subcommand routes in the parser, so the defaulted command's struct is filled with no caller involvement.

Three things Parse deliberately does not do:

  • overrides is not applied. If your spec uses it, call argv.ApplyOverrides yourself, then argv.CheckDisplaced on each key it reports: losing a pair unsets a flag, it does not unsay the word the flag was given.
  • Help and version are not printed — they come back as *argv.Error with CodeHelp / CodeVersion for you to render (Help and errors).
  • No chain comes back with an error. The renderers want the command chain; recover it with argv.Walk(Root, args), which returns the chain even for lines that failed to parse.

Using it against a real spec ​

From the tests over mise's actual 211-command spec:

go
cli, err := mise.Parse([]string{"use", "-g", "node@20"})
// cli.Use != nil; cli.Use.Global == true; cli.Use.ToolVersion == []string{"node@20"}
// cli.Config == nil — a command nobody ran is nil

cli, _ = mise.Parse([]string{"tasks", "run", "build", "extra", "--", "--verbose"})
run := cli.Tasks.Run
// run.Task == "build"; run.Args == []string{"extra"}; run.ArgsLast == []string{"--verbose"}

_, err = mise.Parse([]string{"--log-level", "chatty"})
e := err.(*argv.Error) // e.Code == argv.CodeInvalidChoice
MIT LicenseCopyright © 2026jdx.dev