zz_rotini.go
go generate ./... turns your spec into one Go file, internal/cmd/<name>/zz_rotini.go. It is
rewritten on every run, so never edit it. This page explains what is in it and which files around
it are yours.
From spec to code
For a spec with a todo root command and an add sub-command, the file holds:
| In the spec | In zz_rotini.go |
|---|---|
| each command’s flags | a struct: TodoAddFlags { Priority string; Tag []string } |
| each command’s arguments | a struct: TodoAddArguments { Title string } |
its env: and config: inputs, when it declares any | TodoAddEnv and TodoAddConfig structs, reached as in.TodoAdd.Env and in.TodoAdd.Config |
its stdin: document, when it declares one | a TodoAddStdin struct |
its output:, when it declares one | a TodoAddOutput type (see structured output) |
each schema under the root’s schemas: | a Go type of the same name, such as Task, with json tags; a property not listed under required: gets omitempty |
| the command and its parents | TodoAddInputs, the type your handler passes to rtx.Inputs |
| the command tree | a rotini.Definition the runtime parses against |
| every command | a method on the ProgramHandlers interface, which returns the command’s handler (todoAddHandler) |
| configuration files and the env prefix | InputSettings, which tells the runtime where values come from |
the conf’s generate.features: | Help(path...), and Completion(shell), Man(path...) and Markdown(path...) when those features are on |
| every command’s usage line | Usage(path...), generated whether or not the help feature is on |
It ends with what main.go and your tests use: NewProgram(), which builds the program with the
generated handlers, and NewProgramWith(handlers), which builds it with a set you pass, to swap
or wrap a command’s handler. Handlers() returns the generated set, for NewProgramWith to
embed. main.go builds its program with NewProgram(); nothing is built at package init.
zz_rotini.go (abridged)
// Code generated by rotini; DO NOT EDIT.
package todo
type TodoAddFlags struct {
Priority string `rotini:"priority"`
Tag []string `rotini:"tag"`
}
type TodoAddArguments struct {
Title string `rotini:"title"`
}
type TodoAddCommandInputs struct {
Flags TodoAddFlags
Arguments TodoAddArguments
}
type TodoAddInputs struct {
Todo TodoCommandInputs
TodoAdd TodoAddCommandInputs
}
func NewProgram() *rotini.Program {
return NewProgramWith(&handlers{})
}
func NewProgramWith(handlers ProgramHandlers) *rotini.Program {
return rotini.NewWiredProgram(definition, func(name string) (rotini.Handler, bool) {
switch name {
case "Todo":
return handlers.Todo(), true
case "TodoAdd":
return handlers.TodoAdd(), true
}
return nil, false
},
rotini.Env(),
rotini.ConfigFiles(),
rotini.YAML(),
).
WithInputSettings(InputSettings).
WithHelp(Help)
}
func Handlers() ProgramHandlers {
return &handlers{}
}A handler reads its own values from in.TodoAdd and a parent’s flags from in.Todo. The types
generated from schemas: are ordinary Go types, so your own code, such as a store that saves
tasks, can use them too. The generated handler set must implement ProgramHandlers, so the
compiler reports a command that has no handler. The switch finds each command’s handler by name
without reflection, which keeps the binary smaller.
To replace one command’s handler, for a test double or to wrap it, embed the generated set and override that method:
replacing one handler
type fakeSync struct{ todo.ProgramHandlers }
func (fakeSync) TodoSync() rotini.Handler { return &stubSync{} }
todo.NewProgramWith(fakeSync{todo.Handlers()}).Run(args)A hand-written handler set can do the same with rotini.NewProgramFunc; rotini.NewProgram(def, handlers) finds the methods by reflection.
The list after the switch is the runtime features the spec uses, such as environment inputs,
a YAML configuration file or a url flag. The program links only those, so a small CLI’s
binary is smaller and starts faster. go generate keeps the list in step with the spec; see
what a program links. Env and config fields write their enum
values, aliases and time layouts in their struct tags as comma-separated lists, with \
escaping a ,, = or \ inside a value.
To put the input and output types in a package of their own, for example so a handler in
another package can import them, declare a models target under the conf’s
packages.
What stays yours
- The handler files, one per command, such as
todo_add.go. Each is created the first time its command appears in the spec and is never overwritten. When you remove a command from the spec, the nextgo generatedisables its handler file by adding//go:build ignoreat the top, so it is out of the build but your code is still there, and the one after deletes it, edits and all. Each step is reported. To keep the file, delete itsvar _ rotini.Handlerline, or list it under the conf’skeep. main.go, created once and never touched again.- Any other file you add to the package, such as a test or a file declaring a dependency. Rotini only removes files it wrote.
- An editable template a feature seeds into its
template_dirwithtemplate: true, written once when missing and never touched again.
Renaming a command, or changing its filename:, is a removal and an addition: the old handler
file is disabled and then deleted, and a new, empty one is created. Move your code across
before the second go generate, or recover it from git afterwards.
What rotini rewrites on every run, and never asks you to edit:
- the generated file, and the models file when the conf declares one;
- each feature’s rendered pages under its
embed_dir(removing pages for commands that are gone); - the JSON Schema copies named under
generate.schemas, which are never removed.
Nothing is written outside the paths your conf names.
Names
Generated names follow Go’s conventions: api-group becomes APIGroup, and base-url becomes
BaseURL. A type’s prefix is the command path, so todo add gives TodoAdd... types and the
handler type todoAddHandler. A command named port-forward gets the handler file
todo_port_forward.go.