/climate

"CLI Mate" autogenerates CLIs from structs / functions (nested subcommands, global / local flags, help generation, typo suggestions, shell completion etc.)

Primary LanguageGoThe UnlicenseUnlicense

Climate

Climate "CLI Mate" aims to make creating CLIs in Go easy (and fun!), similar to python-fire.
It's also built on top of Cobra and so comes with "batteries included" (help, shell completion etc.).

Usage

// Exported struct fields are automatically declared as flags --
// 1. Field names are converted to kebab-case and are used as flag names.
// That said, users can pass flags in camelCase, PascalCase, snake_case or
// SCREAMING_SNAKE_CASE and everything just works (thanks to normalization).
// 2. Field types are used as flag types (string, bool, int, etc.).
// 3. "short" subfield tags (under the "cli" tags) are used as short flag names
// (as is). It's also possible to omit the value, in which case the first
// letter of the field name is used.
// 4. "default" field tags are used as default values (of course, with
// automatic type conversion from raw string to the actual field type).
// 5. Field docs / comments are used* as flag usage strings (as is).
// 6. "required" subfield tags (under the "cli" tags) are used to mark the
// flags as required (i.e., the command is errored out without these flags).
type greetOptions struct {
Greeting string `cli:"short" default:"Hello"` // greeting to use
Name string `cli:"short=n" default:"World"` // name to greet
Times int `cli:"short,required"` // number of times to greet
}
// Func is automatically converted to a command --
// 1. Param names are converted to kebab-case and used* as part of the usage
// string ("command [opts] [args]", for example).
// 2. (Optional) First argument if a struct pointer, is used to declare flags.
// 3. (Optional) Next argument if a string slice is used to collect args.
// 4. Doc is used* as long help string (as is).
// 5. Usage directive is used* to explicitly set the usage string.
// Greet someone.
func greet(opts *greetOptions) {
for i := 0; i < opts.Times; i++ {
fmt.Printf("%v, %v!\n", opts.Greeting, opts.Name)
}
}
// * These only work if you generate and pass along "metadata" like below --
//go:generate go run github.com/avamsi/climate/cmd/cligen --out=md.cli
//go:embed md.cli
var md []byte
func main() {
climate.RunAndExit(climate.Func(greet), climate.WithMetadata(md))
}

$ greet --help

Greet someone.

Usage:
  greet [opts]

Flags:
  -g, --greeting string (default Hello)  greeting to use
  -n, --name     string (default World)  name to greet
  -t, --times    int                     number of times to greet
  -h, --help                             help for greet
  -v, --version                          version for greet

Subcommands

// See ../greet/main.go first for some details that are not covered here.
// Struct is automatically converted to a command --
// 1. Struct names are converted to lowercase and used as the command name.
// 2. Struct fields are automatically declared as "global" flags.
// 3. Struct methods are automatically converted to subcommands --
// 1. Method names are converted to lowercase and used as the command name.
// 2. Method docs are truncated and are used* as short help strings.
// 3. Method directives are used* to declare aliases or explicitly set the
// short help strings (//cli:aliases, for example).
// 4. "Sub-structs" are automatically converted to subcommands, recursively.
// Jujutsu (an experimental VCS).
type jj struct {
Repository string `cli:"short=R"` // `path` to the repo to operate on
IgnoreWorkingCopy bool // don't snapshot / update the working copy
}
// Create a new repo in the given directory.
func (j *jj) Init(ctx context.Context, dir *string) {
fmt.Println("init", ctx, j, dir)
}
type squashOptions struct {
Revision string `cli:"short" default:"@"`
Interactive bool `cli:"short"` // interactively choose which parts to squash
}
// Move changes from a revision into its parent.
//
// After moving the changes into the parent, the child revision will have the
// same content state as before. If that means that the change is now empty
// compared to its parent, it will be abandoned. Without `--interactive`, the
// child change will always be empty.
//
//cli:aliases am, amend
func (j *jj) Squash(opts *squashOptions, paths [5]string) {
fmt.Println("squash", j, opts, paths)
}
// Commands for working with the underlying Git repo.
type git struct {
J *jj
}
// Manage Git remotes.
func (g *git) Remote() error {
return errors.New("not implemented")
}
// Update the underlying Git repo with changes made in the repo.
func (g *git) Export() {
fmt.Println("export", g.J)
}
//go:generate go run github.com/avamsi/climate/cmd/cligen --out=md.cli
//go:embed md.cli
var md []byte
func main() {
// Note the recursive struct embedding below, which lets us create "deep"
// subcommands like this (indentation implies subcommand) --
//
// jj
// init
// squash
// git
// remote
// export
// util
// completion
p := climate.Struct[jj](climate.Struct[git](), climate.Struct[util.Util]())
climate.RunAndExit(p, climate.WithMetadata(md))
}

$ jj --help

Jujutsu (an experimental VCS).

Usage:
  jj [command]

Available Commands:
  completion  Generate the autocompletion script for the specified shell
  git         Commands for working with the underlying Git repo
  help        Help about any command
  init        Create a new repo in the given directory
  squash      Move changes from a revision into its parent
  util        Infrequently used commands such as for generating shell completions
  version     Display jj's version information

Flags:
  -R, --repository          path  path to the repo to operate on
      --ignore-working-copy       don't snapshot / update the working copy
  -h, --help                      help for jj
  -v, --version                   version for jj

Use "jj [command] --help" for more information about a command.
$ jj git --help

Commands for working with the underlying Git repo.

Usage:
  jj git [command]

Available Commands:
  export      Update the underlying Git repo with changes made in the repo
  remote      Manage Git remotes

Flags:
  -h, --help  help for git

Global Flags:
      --ignore-working-copy       don't snapshot / update the working copy
  -R, --repository          path  path to the repo to operate on

Use "jj git [command] --help" for more information about a command.