kongusage

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Nov 7, 2026 License: MIT Imports: 17 Imported by: 0

README

kongusage

License MIT Go version Test Coverage Status Release Go Reference

kongusage generates a usage spec of a CLI built with Kong. With the spec, the usage CLI provides shell completion of the program for bash, zsh, fish, nu and PowerShell, mise completes arguments of tasks which run it, and usage generates docs and checks compatibility between releases. A flag added to the CLI is enough to print the spec; features which Kong lacks, like examples or completion of values by a command, are added to it in KDL.

Usage

Add a flag to the Kong CLI struct:

type CLI struct {
    // ...
    UsageSpec kongusage.Flag
}

With kong.DefaultEnvars add the tag env:"-" to the flag: only the command line sets it, so help should not show a variable for it.

Then the program prints its usage spec in KDL by --usage-spec.

Usage-only features

The flag alone is enough to generate a spec, but the spec includes only what Kong knows. Features of usage which Kong has no counterpart for, e.g. examples, long help, notes or completion of values, are given in KDL by the method UsageKDL() of the root, command, flag and positional types and by the tag usage-kdl of their fields:

type CLI struct {
    UsageSpec kongusage.Flag `env:"-"`
    Force     bool           `help:"Overwrite files." usage-kdl:"effect=destructive"`
    Deploy    DeployCmd      `cmd:"" help:"Deploy the release."`
    Hosts     HostsCmd       `cmd:"" help:"Print known hosts, one per line." hidden:""`
}

func (CLI) UsageKDL() string {
    return `
license MIT
cmd "deploy" {
    example "my-kong-cli deploy --env=prod web1" header="Deploy to production"
    flag "--env <ENV>" { note "Deploying to prod needs approval." }
}
`
}

type DeployCmd struct {
    Env  string `placeholder:"ENV" enum:"prod,dev" default:"dev" help:"Environment."`
    Host Host   `arg:""                                          help:"Host to deploy to."`
}

type Host string

func (Host) UsageKDL() string {
    return `arg "" { complete run="my-kong-cli hosts" }`
}

The package doc explains the sources and where to put which feature; docs/usage-features.md lists the accepted nodes and how they are merged. When the tag changes the node given by UsageKDL(), mind that:

  • long_help and help_md are different nodes, so one does not replace the other; the same holds for before_help and before_help_md, after_help and after_help_md.
  • note and warning are different nodes too.
  • One example of the tag replaces all examples given by UsageKDL(), and so do note, warning, exit_code and output.
  • complete is replaced as a whole, e.g. complete type=file drops run of the old one.
Testing

Add a test which checks the spec, and install the usage CLI where tests run (the test fails without it and names the version to install):

func TestUsageSpec(t *testing.T) {
    kongusage.CheckSpec(t, kong.Must(&CLI{}))
}

The test is required for a CLI with usage-only features: --usage-spec panics on a mistake in them, and CheckSpec finds it. We recommend it for any CLI, as it also reports errors and warnings of usage lint and logs each part of the CLI which the spec fixes silently, e.g. a placeholder which usage cannot take as a value name; but a CLI which only adds the flag can do without it.

Set KONGUSAGE_TEST_SKIP_USAGE=1 where usage is not available (e.g. some CI platforms): then the test does not run usage and checks only usage-only features.

Mapping Kong to usage

docs/mapping.md describes how Kong maps to the spec and which Kong features are lost.

Example: shell completion

The usage CLI generates a completion script which reads the spec from the installed program:

usage generate completion zsh my-kong-cli --usage-cmd "my-kong-cli --usage-spec" --install

Replace zsh by bash, fish, nu or powershell for another shell. The script runs usage and my-kong-cli --usage-spec on each Tab, so completion always matches the installed version of the program, and usage must be in $PATH wherever the script is used.

Example: completion of a mise task

A mise task which runs the program can take the spec for completion:

[tasks.my-kong-cli]
usage = 'mount run="mise run -q my-kong-cli --usage-spec"'
raw_args = true # Kong parses arguments, usage is only for completion.
run = 'my-kong-cli'

mise runs the mount command only to complete arguments of the task, so the spec is never stale. raw_args = true makes mise pass arguments to run as is instead of parsing them by the spec, so --help and invalid flags reach Kong.

An alternative takes the spec from a file generated by my-kong-cli --usage-spec, so mise tasks info and mise generate task-docs show it too:

[tasks.my-kong-cli]
usage = 'include file="{{ config_root }}/my-kong-cli.usage.kdl"'
raw_args = true
run = 'my-kong-cli'

The file must be regenerated whenever the program changes. A missing or broken file makes the task fail and silently breaks completion of every task of the project.

Path completion

Completed paths are relative to the shell's directory, while the task runs in its dir, by default the mise config root directory, so a relative path completed in another directory is wrong for the program. A program which takes paths from the user as a task has to resolve them relative to MISE_ORIGINAL_CWD when it is set, unless the task runs in the shell's directory (dir = '{{cwd}}') or the user runs the task only from the mise config root directory.

Example: compatibility check

usage diff reports breaking changes of the command line since the released version, and exits 1 when there are any:

my-kong-cli --usage-spec | usage diff released.usage.kdl -

A new version of kongusage may change the spec of an unchanged CLI, which is reported as a change too.

Alternatives

  • kong-usage also generates a usage spec of a Kong CLI. It is a minimal solution, enough for simple CLIs: it maps commands, flags and positional arguments with their basic properties. A few mappings its README lists are not emitted (version, subcommand_required), and flags of a command are not completed after its subcommand.

kongusage (this project) aims to be a complete one: it describes as much of the behaviour of Kong as usage can express (e.g. flag groups, default commands, inherited flags, completion of values by their type), documents what is lost, takes usage-only features from the author of the CLI, and checks the spec by usage lint in a test.

Shell completion of a Kong CLI without usage, which neither the usage CLI nor mise tasks can use:

Documentation

Overview

Package kongusage adds usage spec support for Kong CLI.

A CLI declares a Flag, which prints the spec of the CLI for a shell completion script, a mise task, docs or a compatibility check. WriteSpec can be used to generate the spec without using a Flag.

How Kong maps to the spec and which Kong features are lost is described in docs/mapping.md.

Usage features

Features of usage which have no counterpart in Kong are supplied by the author of the CLI in KDL v2, as nodes of the usage spec:

  • by KDLProvider of the named root, command, flag or positional argument type;
  • by the tag `usage-kdl` of the field of a command, a flag or a positional argument.

UsageKDL of the root returns top-level nodes of the spec. UsageKDL of another type returns the node for a field of that type with an empty value, as the type does not know the name of the field: `cmd ""`, `flag ""` or `arg ""` (the last two are interchangeable) with properties and child nodes. Nested `cmd`, `flag` and `arg` nodes describe descendants of a node, named exactly as in the spec printed by Flag, e.g. `flag "-f --force"` or `arg "<file>..."`; UsageKDL of the type of a field is ignored when an ancestor describes its node.

Typical features, as UsageKDL of the root would give them:

license MIT
repository "https://github.com/example/my-kong-cli"
cmd "deploy" effect=destructive {
    help_md "Deploys the **current** release."
    example "my-kong-cli deploy --env=prod web1" header="Deploy to production"
    exit_code 3 "The deployment was rolled back."
    // The flag has placeholder:"ENV" and enum:"prod,dev".
    flag "--env <ENV>" long_help="Environment to deploy to." {
        note "Deploying to prod needs approval."
        choices { choice prod help="Production." }
    }
    arg "<host>" { complete run="my-kong-cli hosts" }
}

The tag holds properties and child nodes of the node of its field, without its name and value, and merges with the node given by UsageKDL (if any):

  • `usage-kdl:"effect=destructive"` adds the property or changes its value;
  • `usage-kdl:"{ note \"Slow.\" }"` adds the child node or replaces all child nodes of that name;
  • `usage-kdl:"long_help=\"Text.\""` and `usage-kdl:"{ long_help \"Text.\" }"` are the same node, as usage accepts it in either form;
  • `usage-kdl:"effect=#null"` or `usage-kdl:"{ example #null }"` removes it;
  • `usage-kdl:"{ flag \"--force\" effect=destructive }"` on a command changes the node of its flag, keeping the rest of the node.

Where to put features:

  • UsageKDL of the root: features of the whole CLI, long texts, and features of commands without a named type.
  • UsageKDL of a reusable type: features which hold wherever the type is used, e.g. `effect` of a command or `complete` of a value.
  • The tag: short features of one field, and changes of the node of its type for it.

For example:

type CLI struct {
	Force bool     `help:"Overwrite files." usage-kdl:"effect=destructive"`
	Run   RunCmd   `cmd:"" help:"Run a container."`
	Rm    RmCmd    `cmd:"" help:"Remove a container." usage-kdl:"effect=destructive"`
	Ls    struct{} `cmd:"" help:"List containers."`
}

func (CLI) UsageKDL() string {
	return `
license MIT
flag "--force" long_help="Overwrites existing files without asking."
cmd "ls" effect=read
`
}

type RunCmd struct {
	Image string `arg:"" help:"Image to run."`
}

func (RunCmd) UsageKDL() string {
	return `cmd "" effect=write { example "my-kong-cli run alpine" }`
}

type RmCmd struct {
	ID ContainerID `arg:"" help:"Container." usage-kdl:"{ note \"Stops it first.\" }"`
}

type ContainerID string

func (ContainerID) UsageKDL() string {
	return `arg "" { complete run="my-kong-cli ls" }`
}

The spec printed by Flag then has these nodes, besides other ones derived from Kong:

license MIT
flag "--force" help="Overwrite files." effect=destructive {
    long_help "Overwrites existing files without asking."
}
cmd "run" help="Run a container." effect=write {
    example "my-kong-cli run alpine"
    arg "<image>" help="Image to run."
}
cmd "rm" help="Remove a container." effect=destructive {
    arg "<id>" help="Container." {
        complete run="my-kong-cli ls"
        note "Stops it first."
    }
}
cmd "ls" help="List containers." effect=read

Which nodes, values and properties are accepted and how exactly the tag changes the node is described in docs/usage-features.md.

A CLI with usage features must have a test which calls CheckSpec, with the `usage` CLI installed, because Flag panics on a mistake in them. The test is recommended for any CLI: it also runs `usage lint` and logs parts of the CLI which the spec fixes silently.

Index

Constants

View Source
const MinUsageVersion = "6.12.0"

MinUsageVersion is the oldest supported usage version. The spec sets it as `min_usage_version`, and CheckSpec requires at least this version.

Variables

This section is empty.

Functions

func CheckSpec added in v0.2.0

func CheckSpec(t interface {
	Helper()
	Context() context.Context
	Logf(format string, args ...any)
	Fatalf(format string, args ...any)
}, k *kong.Kong,
)

CheckSpec fails the test t on mistakes in the usage spec of the CLI k:

  • on mistakes in usage features which make WriteSpec and thus Flag panic, e.g. a node, value or property which is not accepted, a name of a command, a flag or a positional argument which is not in the spec, or a node derived from Kong (see "Usage features" in the package doc);
  • on errors and warnings of `usage lint`, e.g. an example which the spec cannot parse.

Use it in a test of the CLI:

func TestUsageSpec(t *testing.T) {
	kongusage.CheckSpec(t, kong.Must(&CLI{}))
}

The spec is generated with the same Config as by Flag. CheckSpec logs each part of the CLI which the spec fixes silently, e.g. a placeholder which usage cannot take as a value name, as it is likely a mistake which is worth correcting in the CLI. It requires the `usage` CLI in PATH, of a version not older than MinUsageVersion; otherwise the test fails with the version to install. When the environment variable KONGUSAGE_TEST_SKIP_USAGE is not empty, it does not run usage, e.g. on platforms without it, and checks only usage features.

func WriteSpec added in v0.2.0

func WriteSpec(w io.Writer, k *kong.Kong, cfg Config) error

WriteSpec writes the usage spec of the CLI k to w in KDL.

It panics on an invalid cfg or on usage features of k it cannot merge (use CheckSpec in your tests to ensure this does not happen). Returns an error when writing to w fails.

Types

type Config added in v0.2.0

type Config struct {
	// NoDisplayOrder disables `display_order` of commands,
	// which keeps the order of their declaration in help of usage;
	// without it help of usage sorts commands by name.
	NoDisplayOrder bool
}

Config controls generation of the usage spec. The zero value of every field means "unset, use default". Usage features of the CLI are supplied by KDLProvider instead.

type ConfigProvider added in v0.2.0

type ConfigProvider interface {
	UsageConfig() Config
}

ConfigProvider may be implemented by the root of a Kong CLI to give Flag and CheckSpec a Config.

type Flag

type Flag bool

Flag prints the usage spec of the CLI in KDL and exits, like kong.VersionFlag.

Declare it as a field of the root of the CLI:

type CLI struct {
	UsageSpec kongusage.Flag
}

The flag has the default tag help (see Flag.Signature), which the tag of the field overrides.

Only the command line sets the flag, not an environment variable or a resolver: they are inherited by every run of the CLI, which would print the spec instead of doing its job. Add the tag `env:"-"` if using kong.DefaultEnvars, otherwise help shows a variable which the flag ignores.

The spec is generated by WriteSpec with the Config returned by the root of the CLI when it implements ConfigProvider or zero Config otherwise.

In a mise task the shell completes paths relative to its current directory, while the task runs in its own directory (by default the root of the mise config), so the program has to resolve relative paths given by the user against MISE_ORIGINAL_CWD when it is set.

func (*Flag) BeforeReset

func (*Flag) BeforeReset(app *kong.Kong, ctx *kong.Context, path *kong.Path) error

BeforeReset implements the Kong hook. Kong calls it before it applies defaults, environment variables and resolvers and checks required flags and arguments, so the flag works without them and despite invalid values in them.

func (*Flag) Signature added in v0.2.0

func (*Flag) Signature() string

Signature implements kong.Signature.

type KDLProvider added in v0.2.0

type KDLProvider interface {
	UsageKDL() string
}

KDLProvider may be implemented by the root of a Kong CLI and by the type of a command, a flag or a positional argument to supply usage features in KDL (see "Usage features" in the package doc).

Directories

Path Synopsis
internal
kdl
Package kdl builds a tree of KDL nodes and writes it as a KDL v2 document.
Package kdl builds a tree of KDL nodes and writes it as a KDL v2 document.
usagecli
Package usagecli runs the usage CLI for tests of kongusage and for its CheckSpec.
Package usagecli runs the usage CLI for tests of kongusage and for its CheckSpec.
usagekdl
Package usagekdl parses usage features of a CLI, given in KDL v2, merges them and adds them to the usage spec as docs/usage-features.md describes.
Package usagekdl parses usage features of a CLI, given in KDL v2, merges them and adds them to the usage spec as docs/usage-features.md describes.
usagetest
Package usagetest runs the usage CLI in tests.
Package usagetest runs the usage CLI in tests.

Jump to

Keyboard shortcuts

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