pax_global_header 0000666 0000000 0000000 00000000064 14572527075 0014530 g ustar 00root root 0000000 0000000 52 comment=a41b2e8f4ed34d80305fff68cf819f819c06ae7e
kong-0.9.0/ 0000775 0000000 0000000 00000000000 14572527075 0012474 5 ustar 00root root 0000000 0000000 kong-0.9.0/.github/ 0000775 0000000 0000000 00000000000 14572527075 0014034 5 ustar 00root root 0000000 0000000 kong-0.9.0/.github/FUNDING.yml 0000664 0000000 0000000 00000000025 14572527075 0015646 0 ustar 00root root 0000000 0000000 github: [alecthomas]
kong-0.9.0/.github/workflows/ 0000775 0000000 0000000 00000000000 14572527075 0016071 5 ustar 00root root 0000000 0000000 kong-0.9.0/.github/workflows/ci.yml 0000664 0000000 0000000 00000002606 14572527075 0017213 0 ustar 00root root 0000000 0000000 on:
push:
branches:
- master
pull_request:
name: CI
jobs:
test:
name: Test / Go ${{ matrix.go }}
runs-on: ubuntu-latest
strategy:
matrix:
# These are the release channels.
# Hermit will handle installing the right patch.
go: ["1.20", "1.21"]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Init Hermit
run: ./bin/hermit env -r >> "$GITHUB_ENV"
- name: Install Go ${{ matrix.go }}
run: |
hermit install go@"${GO_VERSION}"
go version
env:
GO_VERSION: ${{ matrix.go }}
- name: Test
run: go test ./...
test-windows:
name: Test / Windows / Go ${{ matrix.go }}
runs-on: windows-latest
strategy:
matrix:
# These are versions for GitHub's setup-go.
# '.x' will pick the latest patch release.
go: ["1.20.x", "1.21.x"]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test ./...
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Init Hermit
run: ./bin/hermit env -r >> "$GITHUB_ENV"
- name: golangci-lint
run: golangci-lint run
kong-0.9.0/.gitignore 0000664 0000000 0000000 00000000000 14572527075 0014452 0 ustar 00root root 0000000 0000000 kong-0.9.0/.golangci.yml 0000664 0000000 0000000 00000004143 14572527075 0015062 0 ustar 00root root 0000000 0000000 run:
tests: true
output:
print-issued-lines: false
linters:
enable-all: true
disable:
- maligned
- lll
- gochecknoglobals
- wsl
- funlen
- gocognit
- gomnd
- goprintffuncname
- paralleltest
- nlreturn
- goerr113
- ifshort
- testpackage
- wrapcheck
- exhaustivestruct
- forbidigo
- gci
- godot
- gofumpt
- cyclop
- errorlint
- nestif
- golint
- scopelint
- interfacer
- tagliatelle
- thelper
- godox
- goconst
- varnamelen
- ireturn
- exhaustruct
- nonamedreturns
- nilnil
- nosnakecase # deprecated since v1.48.1
- structcheck # deprecated since v1.49.0
- deadcode # deprecated since v1.49.0
- varcheck # deprecated since v1.49.0
- depguard # nothing to guard against yet
- tagalign # hurts readability of kong tags
linters-settings:
govet:
check-shadowing: true
# These govet checks are disabled by default, but they're useful.
enable:
- niliness
- sortslice
- unusedwrite
dupl:
threshold: 100
gocyclo:
min-complexity: 20
exhaustive:
default-signifies-exhaustive: true
issues:
max-per-linter: 0
max-same: 0
exclude-use-default: false
exclude:
- '^(G104|G204):'
# Very commonly not checked.
- 'Error return value of .(.*\.Help|.*\.MarkFlagRequired|(os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked'
- 'exported method (.*\.MarshalJSON|.*\.UnmarshalJSON) should have comment or be unexported'
- 'composite literal uses unkeyed fields'
- 'bad syntax for struct tag key'
- 'bad syntax for struct tag pair'
- 'result .* \(error\) is always nil'
exclude-rules:
# Don't warn on unused parameters.
# Parameter names are useful for documentation.
# Replacing them with '_' hides useful information.
- linters: [revive]
text: 'unused-parameter: parameter \S+ seems to be unused, consider removing or renaming it as _'
# Duplicate words are okay in tests.
- linters: [dupword]
path: _test\.go
kong-0.9.0/COPYING 0000664 0000000 0000000 00000002037 14572527075 0013531 0 ustar 00root root 0000000 0000000 Copyright (C) 2018 Alec Thomas
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
kong-0.9.0/README.md 0000664 0000000 0000000 00000102704 14572527075 0013757 0 ustar 00root root 0000000 0000000

# Kong is a command-line parser for Go
[](http://godoc.org/github.com/alecthomas/kong) [](https://circleci.com/gh/alecthomas/kong) [](https://goreportcard.com/report/github.com/alecthomas/kong) [](https://gophers.slack.com/messages/CN9DS8YF3)
- [Introduction](#introduction)
- [Help](#help)
- [Help as a user of a Kong application](#help-as-a-user-of-a-kong-application)
- [Defining help in Kong](#defining-help-in-kong)
- [Command handling](#command-handling)
- [Switch on the command string](#switch-on-the-command-string)
- [Attach a Run... error method to each command](#attach-a-run-error-method-to-each-command)
- [Hooks: BeforeReset, BeforeResolve, BeforeApply, AfterApply and the Bind option](#hooks-beforereset-beforeresolve-beforeapply-afterapply-and-the-bind-option)
- [Flags](#flags)
- [Commands and sub-commands](#commands-and-sub-commands)
- [Branching positional arguments](#branching-positional-arguments)
- [Positional arguments](#positional-arguments)
- [Slices](#slices)
- [Maps](#maps)
- [Pointers](#pointers)
- [Nested data structure](#nested-data-structure)
- [Custom named decoders](#custom-named-decoders)
- [Supported field types](#supported-field-types)
- [Custom decoders mappers](#custom-decoders-mappers)
- [Supported tags](#supported-tags)
- [Plugins](#plugins)
- [Dynamic Commands](#dynamic-commands)
- [Variable interpolation](#variable-interpolation)
- [Validation](#validation)
- [Modifying Kong's behaviour](#modifying-kongs-behaviour)
- [Namehelp and Descriptionhelp - set the application name description](#namehelp-and-descriptionhelp---set-the-application-name-description)
- [Configurationloader, paths... - load defaults from configuration files](#configurationloader-paths---load-defaults-from-configuration-files)
- [Resolver... - support for default values from external sources](#resolver---support-for-default-values-from-external-sources)
- [\*Mapper... - customising how the command-line is mapped to Go values](#mapper---customising-how-the-command-line-is-mapped-to-go-values)
- [ConfigureHelpHelpOptions and HelpHelpFunc - customising help](#configurehelphelpoptions-and-helphelpfunc---customising-help)
- [Bind... - bind values for callback hooks and Run methods](#bind---bind-values-for-callback-hooks-and-run-methods)
- [Other options](#other-options)
## Introduction
Kong aims to support arbitrarily complex command-line structures with as little developer effort as possible.
To achieve that, command-lines are expressed as Go types, with the structure and tags directing how the command line is mapped onto the struct.
For example, the following command-line:
shell rm [-f] [-r] ...
shell ls [ ...]
Can be represented by the following command-line structure:
```go
package main
import "github.com/alecthomas/kong"
var CLI struct {
Rm struct {
Force bool `help:"Force removal."`
Recursive bool `help:"Recursively remove files."`
Paths []string `arg:"" name:"path" help:"Paths to remove." type:"path"`
} `cmd:"" help:"Remove files."`
Ls struct {
Paths []string `arg:"" optional:"" name:"path" help:"Paths to list." type:"path"`
} `cmd:"" help:"List paths."`
}
func main() {
ctx := kong.Parse(&CLI)
switch ctx.Command() {
case "rm ":
case "ls":
default:
panic(ctx.Command())
}
}
```
## Help
### Help as a user of a Kong application
Every Kong application includes a `--help` flag that will display auto-generated help.
eg.
$ shell --help
usage: shell
A shell-like example app.
Flags:
--help Show context-sensitive help.
--debug Debug mode.
Commands:
rm ...
Remove files.
ls [ ...]
List paths.
If a command is provided, the help will show full detail on the command including all available flags.
eg.
$ shell --help rm
usage: shell rm ...
Remove files.
Arguments:
... Paths to remove.
Flags:
--debug Debug mode.
-f, --force Force removal.
-r, --recursive Recursively remove files.
### Defining help in Kong
Help is automatically generated from the command-line structure itself,
including `help:""` and other tags. [Variables](#variable-interpolation) will
also be interpolated into the help string.
Finally, any command, or argument type implementing the interface
`Help() string` will have this function called to retrieve more detail to
augment the help tag. This allows for much more descriptive text than can
fit in Go tags. [See \_examples/shell/help](./_examples/shell/help)
#### Showing the _command_'s detailed help
A command's additional help text is _not_ shown from top-level help, but can be displayed within contextual help:
**Top level help**
```bash
$ go run ./_examples/shell/help --help
Usage: help
An app demonstrating HelpProviders
Flags:
-h, --help Show context-sensitive help.
--flag Regular flag help
Commands:
echo Regular command help
```
**Contextual**
```bash
$ go run ./_examples/shell/help echo --help
Usage: help echo
Regular command help
🚀 additional command help
Arguments:
Regular argument help
Flags:
-h, --help Show context-sensitive help.
--flag Regular flag help
```
#### Showing an _argument_'s detailed help
Custom help will only be shown for _positional arguments with named fields_ ([see the README section on positional arguments for more details on what that means](../../../README.md#branching-positional-arguments))
**Contextual argument help**
```bash
$ go run ./_examples/shell/help msg --help
Usage: help echo
Regular argument help
📣 additional argument help
Flags:
-h, --help Show context-sensitive help.
--flag Regular flag help
```
## Command handling
There are two ways to handle commands in Kong.
### Switch on the command string
When you call `kong.Parse()` it will return a unique string representation of the command. Each command branch in the hierarchy will be a bare word and each branching argument or required positional argument will be the name surrounded by angle brackets. Here's an example:
There's an example of this pattern [here](https://github.com/alecthomas/kong/blob/master/_examples/shell/commandstring/main.go).
eg.
```go
package main
import "github.com/alecthomas/kong"
var CLI struct {
Rm struct {
Force bool `help:"Force removal."`
Recursive bool `help:"Recursively remove files."`
Paths []string `arg:"" name:"path" help:"Paths to remove." type:"path"`
} `cmd:"" help:"Remove files."`
Ls struct {
Paths []string `arg:"" optional:"" name:"path" help:"Paths to list." type:"path"`
} `cmd:"" help:"List paths."`
}
func main() {
ctx := kong.Parse(&CLI)
switch ctx.Command() {
case "rm ":
case "ls":
default:
panic(ctx.Command())
}
}
```
This has the advantage that it is convenient, but the downside that if you modify your CLI structure, the strings may change. This can be fragile.
### Attach a `Run(...) error` method to each command
A more robust approach is to break each command out into their own structs:
1. Break leaf commands out into separate structs.
2. Attach a `Run(...) error` method to all leaf commands.
3. Call `kong.Kong.Parse()` to obtain a `kong.Context`.
4. Call `kong.Context.Run(bindings...)` to call the selected parsed command.
Once a command node is selected by Kong it will search from that node back to the root. Each
encountered command node with a `Run(...) error` will be called in reverse order. This allows
sub-trees to be re-used fairly conveniently.
In addition to values bound with the `kong.Bind(...)` option, any values
passed through to `kong.Context.Run(...)` are also bindable to the target's
`Run()` arguments.
Finally, hooks can also contribute bindings via `kong.Context.Bind()` and `kong.Context.BindTo()`.
There's a full example emulating part of the Docker CLI [here](https://github.com/alecthomas/kong/tree/master/_examples/docker).
eg.
```go
type Context struct {
Debug bool
}
type RmCmd struct {
Force bool `help:"Force removal."`
Recursive bool `help:"Recursively remove files."`
Paths []string `arg:"" name:"path" help:"Paths to remove." type:"path"`
}
func (r *RmCmd) Run(ctx *Context) error {
fmt.Println("rm", r.Paths)
return nil
}
type LsCmd struct {
Paths []string `arg:"" optional:"" name:"path" help:"Paths to list." type:"path"`
}
func (l *LsCmd) Run(ctx *Context) error {
fmt.Println("ls", l.Paths)
return nil
}
var cli struct {
Debug bool `help:"Enable debug mode."`
Rm RmCmd `cmd:"" help:"Remove files."`
Ls LsCmd `cmd:"" help:"List paths."`
}
func main() {
ctx := kong.Parse(&cli)
// Call the Run() method of the selected parsed command.
err := ctx.Run(&Context{Debug: cli.Debug})
ctx.FatalIfErrorf(err)
}
```
## Hooks: BeforeReset(), BeforeResolve(), BeforeApply(), AfterApply() and the Bind() option
If a node in the grammar has a `BeforeReset(...)`, `BeforeResolve
(...)`, `BeforeApply(...) error` and/or `AfterApply(...) error` method, those
methods will be called before values are reset, before validation/assignment,
and after validation/assignment, respectively.
The `--help` flag is implemented with a `BeforeReset` hook.
Arguments to hooks are provided via the `Run(...)` method or `Bind(...)` option. `*Kong`, `*Context` and `*Path` are also bound and finally, hooks can also contribute bindings via `kong.Context.Bind()` and `kong.Context.BindTo()`.
eg.
```go
// A flag with a hook that, if triggered, will set the debug loggers output to stdout.
type debugFlag bool
func (d debugFlag) BeforeApply(logger *log.Logger) error {
logger.SetOutput(os.Stdout)
return nil
}
var cli struct {
Debug debugFlag `help:"Enable debug logging."`
}
func main() {
// Debug logger going to discard.
logger := log.New(io.Discard, "", log.LstdFlags)
ctx := kong.Parse(&cli, kong.Bind(logger))
// ...
}
```
Another example of using hooks is load the env-file:
```go
package main
import (
"fmt"
"github.com/alecthomas/kong"
"github.com/joho/godotenv"
)
type EnvFlag string
// BeforeResolve loads env file.
func (c EnvFlag) BeforeReset(ctx *kong.Context, trace *kong.Path) error {
path := string(ctx.FlagValue(trace.Flag).(EnvFlag)) // nolint
path = kong.ExpandPath(path)
if err := godotenv.Load(path); err != nil {
return err
}
return nil
}
var CLI struct {
EnvFile EnvFlag
Flag `env:"FLAG"`
}
func main() {
_ = kong.Parse(&CLI)
fmt.Println(CLI.Flag)
}
```
## Flags
Any [mapped](#mapper---customising-how-the-command-line-is-mapped-to-go-values) field in the command structure _not_ tagged with `cmd` or `arg` will be a flag. Flags are optional by default.
eg. The command-line `app [--flag="foo"]` can be represented by the following.
```go
type CLI struct {
Flag string
}
```
## Commands and sub-commands
Sub-commands are specified by tagging a struct field with `cmd`. Kong supports arbitrarily nested commands.
eg. The following struct represents the CLI structure `command [--flag="str"] sub-command`.
```go
type CLI struct {
Command struct {
Flag string
SubCommand struct {
} `cmd`
} `cmd`
}
```
If a sub-command is tagged with `default:"1"` it will be selected if there are no further arguments. If a sub-command is tagged with `default:"withargs"` it will be selected even if there are further arguments or flags and those arguments or flags are valid for the sub-command. This allows the user to omit the sub-command name on the CLI if its arguments/flags are not ambiguous with the sibling commands or flags.
## Branching positional arguments
In addition to sub-commands, structs can also be configured as branching positional arguments.
This is achieved by tagging an [unmapped](#mapper---customising-how-the-command-line-is-mapped-to-go-values) nested struct field with `arg`, then including a positional argument field inside that struct _with the same name_. For example, the following command structure:
app rename to
Can be represented with the following:
```go
var CLI struct {
Rename struct {
Name struct {
Name string `arg` // <-- NOTE: identical name to enclosing struct field.
To struct {
Name struct {
Name string `arg`
} `arg`
} `cmd`
} `arg`
} `cmd`
}
```
This looks a little verbose in this contrived example, but typically this will not be the case.
## Positional arguments
If a field is tagged with `arg:""` it will be treated as the final positional
value to be parsed on the command line. By default positional arguments are
required, but specifying `optional:""` will alter this.
If a positional argument is a slice, all remaining arguments will be appended
to that slice.
## Slices
Slice values are treated specially. First the input is split on the `sep:""` tag (defaults to `,`), then each element is parsed by the slice element type and appended to the slice. If the same value is encountered multiple times, elements continue to be appended.
To represent the following command-line:
cmd ls ...
You would use the following:
```go
var CLI struct {
Ls struct {
Files []string `arg:"" type:"existingfile"`
} `cmd`
}
```
## Maps
Maps are similar to slices except that only one key/value pair can be assigned per value, and the `sep` tag denotes the assignment character and defaults to `=`.
To represent the following command-line:
cmd config set = = ...
You would use the following:
```go
var CLI struct {
Config struct {
Set struct {
Config map[string]float64 `arg:"" type:"file:"`
} `cmd`
} `cmd`
}
```
For flags, multiple key+value pairs should be separated by `mapsep:"rune"` tag (defaults to `;`) eg. `--set="key1=value1;key2=value2"`.
## Pointers
Pointers work like the underlying type, except that you can differentiate between the presence of the zero value and no value being supplied.
For example:
```go
var CLI struct {
Foo *int
}
```
Would produce a nil value for `Foo` if no `--foo` argument is supplied, but would have a pointer to the value 0 if the argument `--foo=0` was supplied.
## Nested data structure
Kong support a nested data structure as well with `embed:""`. You can combine `embed:""` with `prefix:""`:
```go
var CLI struct {
Logging struct {
Level string `enum:"debug,info,warn,error" default:"info"`
Type string `enum:"json,console" default:"console"`
} `embed:"" prefix:"logging."`
}
```
This configures Kong to accept flags `--logging.level` and `--logging.type`.
## Custom named decoders
Kong includes a number of builtin custom type mappers. These can be used by
specifying the tag `type:""`. They are registered with the option
function `NamedMapper(name, mapper)`.
| Name | Description |
| -------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `path` | A path. ~ expansion is applied. `-` is accepted for stdout, and will be passed unaltered. |
| `existingfile` | An existing file. ~ expansion is applied. `-` is accepted for stdin, and will be passed unaltered. |
| `existingdir` | An existing directory. ~ expansion is applied. |
| `counter` | Increment a numeric field. Useful for `-vvv`. Can accept `-s`, `--long` or `--long=N`. |
| `filecontent` | Read the file at path into the field. ~ expansion is applied. `-` is accepted for stdin, and will be passed unaltered. |
Slices and maps treat type tags specially. For slices, the `type:""` tag
specifies the element type. For maps, the tag has the format
`tag:"[]:[]"` where either may be omitted.
## Supported field types
## Custom decoders (mappers)
Any field implementing `encoding.TextUnmarshaler` or `json.Unmarshaler` will use those interfaces
for decoding values. Kong also includes builtin support for many common Go types:
| Type | Description |
| --------------- | ----------------------------------------------------------------------------------------------------------- |
| `time.Duration` | Populated using `time.ParseDuration()`. |
| `time.Time` | Populated using `time.Parse()`. Format defaults to RFC3339 but can be overridden with the `format:"X"` tag. |
| `*os.File` | Path to a file that will be opened, or `-` for `os.Stdin`. File must be closed by the user. |
| `*url.URL` | Populated with `url.Parse()`. |
For more fine-grained control, if a field implements the
[MapperValue](https://godoc.org/github.com/alecthomas/kong#MapperValue)
interface it will be used to decode arguments into the field.
## Supported tags
Tags can be in two forms:
1. Standard Go syntax, eg. `kong:"required,name='foo'"`.
2. Bare tags, eg. `required:"" name:"foo"`
Both can coexist with standard Tag parsing.
| Tag | Description |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `cmd:""` | If present, struct is a command. |
| `arg:""` | If present, field is an argument. Required by default. |
| `env:"X,Y,..."` | Specify envars to use for default value. The envs are resolved in the declared order. The first value found is used. |
| `name:"X"` | Long name, for overriding field name. |
| `help:"X"` | Help text. |
| `type:"X"` | Specify [named types](#custom-named-decoders) to use. |
| `placeholder:"X"` | Placeholder text. |
| `default:"X"` | Default value. |
| `default:"1"` | On a command, make it the default. |
| `default:"withargs"` | On a command, make it the default and allow args/flags from that command |
| `short:"X"` | Short name, if flag. |
| `aliases:"X,Y"` | One or more aliases (for cmd or flag). |
| `required:""` | If present, flag/arg is required. |
| `optional:""` | If present, flag/arg is optional. |
| `hidden:""` | If present, command or flag is hidden. |
| `negatable:""` | If present on a `bool` field, supports prefixing a flag with `--no-` to invert the default value |
| `format:"X"` | Format for parsing input, if supported. |
| `sep:"X"` | Separator for sequences (defaults to ","). May be `none` to disable splitting. |
| `mapsep:"X"` | Separator for maps (defaults to ";"). May be `none` to disable splitting. |
| `enum:"X,Y,..."` | Set of valid values allowed for this flag. An enum field must be `required` or have a valid `default`. |
| `group:"X"` | Logical group for a flag or command. |
| `xor:"X,Y,..."` | Exclusive OR groups for flags. Only one flag in the group can be used which is restricted within the same command. When combined with `required`, at least one of the `xor` group will be required. |
| `prefix:"X"` | Prefix for all sub-flags. |
| `envprefix:"X"` | Envar prefix for all sub-flags. |
| `set:"K=V"` | Set a variable for expansion by child elements. Multiples can occur. |
| `embed:""` | If present, this field's children will be embedded in the parent. Useful for composition. |
| `passthrough:""` | If present on a positional argument, it stops flag parsing when encountered, as if `--` was processed before. Useful for external command wrappers, like `exec`. On a command it requires that the command contains only one argument of type `[]string` which is then filled with everything following the command, unparsed. |
| `-` | Ignore the field. Useful for adding non-CLI fields to a configuration struct. e.g `` `kong:"-"` `` |
## Plugins
Kong CLI's can be extended by embedding the `kong.Plugin` type and populating it with pointers to Kong annotated structs. For example:
```go
var pluginOne struct {
PluginOneFlag string
}
var pluginTwo struct {
PluginTwoFlag string
}
var cli struct {
BaseFlag string
kong.Plugins
}
cli.Plugins = kong.Plugins{&pluginOne, &pluginTwo}
```
Additionally if an interface type is embedded, it can also be populated with a Kong annotated struct.
## Dynamic Commands
While plugins give complete control over extending command-line interfaces, Kong
also supports dynamically adding commands via `kong.DynamicCommand()`.
## Variable interpolation
Kong supports limited variable interpolation into help strings, enum lists and
default values.
Variables are in the form:
${}
${=}
Variables are set with the `Vars{"key": "value", ...}` option. Undefined
variable references in the grammar without a default will result in an error at
construction time.
Variables can also be set via the `set:"K=V"` tag. In this case, those variables will be available for that
node and all children. This is useful for composition by allowing the same struct to be reused.
When interpolating into flag or argument help strings, some extra variables
are defined from the value itself:
${default}
${enum}
For flags with associated environment variables, the variable `${env}` can be
interpolated into the help string. In the absence of this variable in the
help string, Kong will append `($$${env})` to the help string.
eg.
```go
type cli struct {
Config string `type:"path" default:"${config_file}"`
}
func main() {
kong.Parse(&cli,
kong.Vars{
"config_file": "~/.app.conf",
})
}
```
## Validation
Kong does validation on the structure of a command-line, but also supports
extensible validation. Any node in the tree may implement the following
interface:
```go
type Validatable interface {
Validate() error
}
```
If one of these nodes is in the active command-line it will be called during
normal validation.
## Modifying Kong's behaviour
Each Kong parser can be configured via functional options passed to `New(cli interface{}, options...Option)`.
The full set of options can be found [here](https://godoc.org/github.com/alecthomas/kong#Option).
### `Name(help)` and `Description(help)` - set the application name description
Set the application name and/or description.
The name of the application will default to the binary name, but can be overridden with `Name(name)`.
As with all help in Kong, text will be wrapped to the terminal.
### `Configuration(loader, paths...)` - load defaults from configuration files
This option provides Kong with support for loading defaults from a set of configuration files. Each file is opened, if possible, and the loader called to create a resolver for that file.
eg.
```go
kong.Parse(&cli, kong.Configuration(kong.JSON, "/etc/myapp.json", "~/.myapp.json"))
```
[See the tests](https://github.com/alecthomas/kong/blob/master/resolver_test.go#L206) for an example of how the JSON file is structured.
#### List of Configuration Loaders
- [YAML](https://github.com/alecthomas/kong-yaml)
- [HCL](https://github.com/alecthomas/kong-hcl)
- [TOML](https://github.com/alecthomas/kong-toml)
- [JSON](https://github.com/alecthomas/kong)
### `Resolver(...)` - support for default values from external sources
Resolvers are Kong's extension point for providing default values from external sources. As an example, support for environment variables via the `env` tag is provided by a resolver. There's also a builtin resolver for JSON configuration files.
Example resolvers can be found in [resolver.go](https://github.com/alecthomas/kong/blob/master/resolver.go).
### `*Mapper(...)` - customising how the command-line is mapped to Go values
Command-line arguments are mapped to Go values via the Mapper interface:
```go
// A Mapper represents how a field is mapped from command-line values to Go.
//
// Mappers can be associated with concrete fields via pointer, reflect.Type, reflect.Kind, or via a "type" tag.
//
// Additionally, if a type implements the MapperValue interface, it will be used.
type Mapper interface {
// Decode ctx.Value with ctx.Scanner into target.
Decode(ctx *DecodeContext, target reflect.Value) error
}
```
All builtin Go types (as well as a bunch of useful stdlib types like `time.Time`) have mappers registered by default. Mappers for custom types can be added using `kong.??Mapper(...)` options. Mappers are applied to fields in four ways:
1. `NamedMapper(string, Mapper)` and using the tag key `type:""`.
2. `KindMapper(reflect.Kind, Mapper)`.
3. `TypeMapper(reflect.Type, Mapper)`.
4. `ValueMapper(interface{}, Mapper)`, passing in a pointer to a field of the grammar.
### `ConfigureHelp(HelpOptions)` and `Help(HelpFunc)` - customising help
The default help output is usually sufficient, but if not there are two solutions.
1. Use `ConfigureHelp(HelpOptions)` to configure how help is formatted (see [HelpOptions](https://godoc.org/github.com/alecthomas/kong#HelpOptions) for details).
2. Custom help can be wired into Kong via the `Help(HelpFunc)` option. The `HelpFunc` is passed a `Context`, which contains the parsed context for the current command-line. See the implementation of `PrintHelp` for an example.
3. Use `ValueFormatter(HelpValueFormatter)` if you want to just customize the help text that is accompanied by flags and arguments.
4. Use `Groups([]Group)` if you want to customize group titles or add a header.
### `Bind(...)` - bind values for callback hooks and Run() methods
See the [section on hooks](#hooks-beforeresolve-beforeapply-afterapply-and-the-bind-option) for details.
### Other options
The full set of options can be found [here](https://godoc.org/github.com/alecthomas/kong#Option).
kong-0.9.0/_examples/ 0000775 0000000 0000000 00000000000 14572527075 0014451 5 ustar 00root root 0000000 0000000 kong-0.9.0/_examples/docker/ 0000775 0000000 0000000 00000000000 14572527075 0015720 5 ustar 00root root 0000000 0000000 kong-0.9.0/_examples/docker/README.md 0000664 0000000 0000000 00000000146 14572527075 0017200 0 ustar 00root root 0000000 0000000 # Large-scale composed CLI
This directory illustrates how a large-scale CLI app could be structured.
kong-0.9.0/_examples/docker/commands.go 0000664 0000000 0000000 00000012247 14572527075 0020056 0 ustar 00root root 0000000 0000000 // nolint
package main
import "fmt"
type AttachCmd struct {
DetachKeys string `help:"Override the key sequence for detaching a container"`
NoStdin bool `help:"Do not attach STDIN"`
SigProxy bool `help:"Proxy all received signals to the process" default:"true"`
Container string `arg required help:"Container ID to attach to."`
}
func (a *AttachCmd) Run(globals *Globals) error {
fmt.Printf("Config: %s\n", globals.Config)
fmt.Printf("Attaching to: %v\n", a.Container)
fmt.Printf("SigProxy: %v\n", a.SigProxy)
return nil
}
type BuildCmd struct {
Arg string `arg required`
}
func (cmd *BuildCmd) Run(globals *Globals) error {
return nil
}
type CommitCmd struct {
Arg string `arg required`
}
func (cmd *CommitCmd) Run(globals *Globals) error {
return nil
}
type CpCmd struct {
Arg string `arg required`
}
func (cmd *CpCmd) Run(globals *Globals) error {
return nil
}
type CreateCmd struct {
Arg string `arg required`
}
func (cmd *CreateCmd) Run(globals *Globals) error {
return nil
}
type DeployCmd struct {
Arg string `arg required`
}
func (cmd *DeployCmd) Run(globals *Globals) error {
return nil
}
type DiffCmd struct {
Arg string `arg required`
}
func (cmd *DiffCmd) Run(globals *Globals) error {
return nil
}
type EventsCmd struct {
Arg string `arg required`
}
func (cmd *EventsCmd) Run(globals *Globals) error {
return nil
}
type ExecCmd struct {
Arg string `arg required`
}
func (cmd *ExecCmd) Run(globals *Globals) error {
return nil
}
type ExportCmd struct {
Arg string `arg required`
}
func (cmd *ExportCmd) Run(globals *Globals) error {
return nil
}
type HistoryCmd struct {
Arg string `arg required`
}
func (cmd *HistoryCmd) Run(globals *Globals) error {
return nil
}
type ImagesCmd struct {
Arg string `arg required`
}
func (cmd *ImagesCmd) Run(globals *Globals) error {
return nil
}
type ImportCmd struct {
Arg string `arg required`
}
func (cmd *ImportCmd) Run(globals *Globals) error {
return nil
}
type InfoCmd struct {
Arg string `arg required`
}
func (cmd *InfoCmd) Run(globals *Globals) error {
return nil
}
type InspectCmd struct {
Arg string `arg required`
}
func (cmd *InspectCmd) Run(globals *Globals) error {
return nil
}
type KillCmd struct {
Arg string `arg required`
}
func (cmd *KillCmd) Run(globals *Globals) error {
return nil
}
type LoadCmd struct {
Arg string `arg required`
}
func (cmd *LoadCmd) Run(globals *Globals) error {
return nil
}
type LoginCmd struct {
Arg string `arg required`
}
func (cmd *LoginCmd) Run(globals *Globals) error {
return nil
}
type LogoutCmd struct {
Arg string `arg required`
}
func (cmd *LogoutCmd) Run(globals *Globals) error {
return nil
}
type LogsCmd struct {
Arg string `arg required`
}
func (cmd *LogsCmd) Run(globals *Globals) error {
return nil
}
type PauseCmd struct {
Arg string `arg required`
}
func (cmd *PauseCmd) Run(globals *Globals) error {
return nil
}
type PortCmd struct {
Arg string `arg required`
}
func (cmd *PortCmd) Run(globals *Globals) error {
return nil
}
type PsCmd struct {
Arg string `arg required`
}
func (cmd *PsCmd) Run(globals *Globals) error {
return nil
}
type PullCmd struct {
Arg string `arg required`
}
func (cmd *PullCmd) Run(globals *Globals) error {
return nil
}
type PushCmd struct {
Arg string `arg required`
}
func (cmd *PushCmd) Run(globals *Globals) error {
return nil
}
type RenameCmd struct {
Arg string `arg required`
}
func (cmd *RenameCmd) Run(globals *Globals) error {
return nil
}
type RestartCmd struct {
Arg string `arg required`
}
func (cmd *RestartCmd) Run(globals *Globals) error {
return nil
}
type RmCmd struct {
Arg string `arg required`
}
func (cmd *RmCmd) Run(globals *Globals) error {
return nil
}
type RmiCmd struct {
Arg string `arg required`
}
func (cmd *RmiCmd) Run(globals *Globals) error {
return nil
}
type RunCmd struct {
Arg string `arg required`
}
func (cmd *RunCmd) Run(globals *Globals) error {
return nil
}
type SaveCmd struct {
Arg string `arg required`
}
func (cmd *SaveCmd) Run(globals *Globals) error {
return nil
}
type SearchCmd struct {
Arg string `arg required`
}
func (cmd *SearchCmd) Run(globals *Globals) error {
return nil
}
type StartCmd struct {
Arg string `arg required`
}
func (cmd *StartCmd) Run(globals *Globals) error {
return nil
}
type StatsCmd struct {
Arg string `arg required`
}
func (cmd *StatsCmd) Run(globals *Globals) error {
return nil
}
type StopCmd struct {
Arg string `arg required`
}
func (cmd *StopCmd) Run(globals *Globals) error {
return nil
}
type TagCmd struct {
Arg string `arg required`
}
func (cmd *TagCmd) Run(globals *Globals) error {
return nil
}
type TopCmd struct {
Arg string `arg required`
}
func (cmd *TopCmd) Run(globals *Globals) error {
return nil
}
type UnpauseCmd struct {
Arg string `arg required`
}
func (cmd *UnpauseCmd) Run(globals *Globals) error {
return nil
}
type UpdateCmd struct {
Arg string `arg required`
}
func (cmd *UpdateCmd) Run(globals *Globals) error {
return nil
}
type VersionCmd struct {
Arg string `arg required`
}
func (cmd *VersionCmd) Run(globals *Globals) error {
return nil
}
type WaitCmd struct {
Arg string `arg required`
}
func (cmd *WaitCmd) Run(globals *Globals) error {
return nil
}
kong-0.9.0/_examples/docker/main.go 0000664 0000000 0000000 00000011440 14572527075 0017173 0 ustar 00root root 0000000 0000000 // nolint
package main
import (
"fmt"
"github.com/alecthomas/kong"
)
type Globals struct {
Config string `help:"Location of client config files" default:"~/.docker" type:"path"`
Debug bool `short:"D" help:"Enable debug mode"`
Host []string `short:"H" help:"Daemon socket(s) to connect to"`
LogLevel string `short:"l" help:"Set the logging level (debug|info|warn|error|fatal)" default:"info"`
TLS bool `help:"Use TLS; implied by --tls-verify"`
TLSCACert string `name:"tls-ca-cert" help:"Trust certs signed only by this CA" default:"~/.docker/ca.pem" type:"path"`
TLSCert string `help:"Path to TLS certificate file" default:"~/.docker/cert.pem" type:"path"`
TLSKey string `help:"Path to TLS key file" default:"~/.docker/key.pem" type:"path"`
TLSVerify bool `help:"Use TLS and verify the remote"`
Version VersionFlag `name:"version" help:"Print version information and quit"`
}
type VersionFlag string
func (v VersionFlag) Decode(ctx *kong.DecodeContext) error { return nil }
func (v VersionFlag) IsBool() bool { return true }
func (v VersionFlag) BeforeApply(app *kong.Kong, vars kong.Vars) error {
fmt.Println(vars["version"])
app.Exit(0)
return nil
}
type CLI struct {
Globals
Attach AttachCmd `cmd:"" help:"Attach local standard input, output, and error streams to a running container"`
Build BuildCmd `cmd:"" help:"Build an image from a Dockerfile"`
Commit CommitCmd `cmd:"" help:"Create a new image from a container's changes"`
Cp CpCmd `cmd:"" help:"Copy files/folders between a container and the local filesystem"`
Create CreateCmd `cmd:"" help:"Create a new container"`
Deploy DeployCmd `cmd:"" help:"Deploy a new stack or update an existing stack"`
Diff DiffCmd `cmd:"" help:"Inspect changes to files or directories on a container's filesystem"`
Events EventsCmd `cmd:"" help:"Get real time events from the server"`
Exec ExecCmd `cmd:"" help:"Run a command in a running container"`
Export ExportCmd `cmd:"" help:"Export a container's filesystem as a tar archive"`
History HistoryCmd `cmd:"" help:"Show the history of an image"`
Images ImagesCmd `cmd:"" help:"List images"`
Import ImportCmd `cmd:"" help:"Import the contents from a tarball to create a filesystem image"`
Info InfoCmd `cmd:"" help:"Display system-wide information"`
Inspect InspectCmd `cmd:"" help:"Return low-level information on Docker objects"`
Kill KillCmd `cmd:"" help:"Kill one or more running containers"`
Load LoadCmd `cmd:"" help:"Load an image from a tar archive or STDIN"`
Login LoginCmd `cmd:"" help:"Log in to a Docker registry"`
Logout LogoutCmd `cmd:"" help:"Log out from a Docker registry"`
Logs LogsCmd `cmd:"" help:"Fetch the logs of a container"`
Pause PauseCmd `cmd:"" help:"Pause all processes within one or more containers"`
Port PortCmd `cmd:"" help:"List port mappings or a specific mapping for the container"`
Ps PsCmd `cmd:"" help:"List containers"`
Pull PullCmd `cmd:"" help:"Pull an image or a repository from a registry"`
Push PushCmd `cmd:"" help:"Push an image or a repository to a registry"`
Rename RenameCmd `cmd:"" help:"Rename a container"`
Restart RestartCmd `cmd:"" help:"Restart one or more containers"`
Rm RmCmd `cmd:"" help:"Remove one or more containers"`
Rmi RmiCmd `cmd:"" help:"Remove one or more images"`
Run RunCmd `cmd:"" help:"Run a command in a new container"`
Save SaveCmd `cmd:"" help:"Save one or more images to a tar archive (streamed to STDOUT by default)"`
Search SearchCmd `cmd:"" help:"Search the Docker Hub for images"`
Start StartCmd `cmd:"" help:"Start one or more stopped containers"`
Stats StatsCmd `cmd:"" help:"Display a live stream of container(s) resource usage statistics"`
Stop StopCmd `cmd:"" help:"Stop one or more running containers"`
Tag TagCmd `cmd:"" help:"Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE"`
Top TopCmd `cmd:"" help:"Display the running processes of a container"`
Unpause UnpauseCmd `cmd:"" help:"Unpause all processes within one or more containers"`
Update UpdateCmd `cmd:"" help:"Update configuration of one or more containers"`
Version VersionCmd `cmd:"" help:"Show the Docker version information"`
Wait WaitCmd `cmd:"" help:"Block until one or more containers stop, then print their exit codes"`
}
func main() {
cli := CLI{
Globals: Globals{
Version: VersionFlag("0.1.1"),
},
}
ctx := kong.Parse(&cli,
kong.Name("docker"),
kong.Description("A self-sufficient runtime for containers"),
kong.UsageOnError(),
kong.ConfigureHelp(kong.HelpOptions{
Compact: true,
}),
kong.Vars{
"version": "0.0.1",
})
err := ctx.Run(&cli.Globals)
ctx.FatalIfErrorf(err)
}
kong-0.9.0/_examples/server/ 0000775 0000000 0000000 00000000000 14572527075 0015757 5 ustar 00root root 0000000 0000000 kong-0.9.0/_examples/server/README.md 0000664 0000000 0000000 00000001110 14572527075 0017227 0 ustar 00root root 0000000 0000000 # An interactive SSH server
In addition to command-lines, Kong can be used interactively. This example
serves a Kong command-line over SSH.
Run with `go run .` then ssh to it like so:
```
$ ssh -p 6740 127.0.0.1
Welcome!
> ?
Example using Kong for interactive command parsing.
Commands:
help [ ...]
Show help.
status
Show server status.
> status
OK
> help status
Show server status.
Flags:
-v, --verbose Show verbose status information.
> status
OK
> status -v
OK
> status foo
error: unexpected argument foo
Show server status.
Flags:
> ^D
```
kong-0.9.0/_examples/server/console.go 0000664 0000000 0000000 00000001641 14572527075 0017752 0 ustar 00root root 0000000 0000000 // nolint: govet
package main
import (
"fmt"
"github.com/alecthomas/kong"
)
// Ensure the grammar compiles.
var _ = kong.Must(&grammar{})
// Server interface.
type grammar struct {
Help helpCmd `cmd:"" help:"Show help."`
Question helpCmd `cmd:"" hidden:"" name:"?" help:"Show help."`
Status statusCmd `cmd:"" help:"Show server status."`
}
type statusCmd struct {
Verbose bool `short:"v" help:"Show verbose status information."`
}
func (s *statusCmd) Run(ctx *kong.Context) error {
ctx.Printf("OK")
return nil
}
type helpCmd struct {
Command []string `arg:"" optional:"" help:"Show help on command."`
}
// Run shows help.
func (h *helpCmd) Run(realCtx *kong.Context) error {
ctx, err := kong.Trace(realCtx.Kong, h.Command)
if err != nil {
return err
}
if ctx.Error != nil {
return ctx.Error
}
err = ctx.PrintUsage(false)
if err != nil {
return err
}
fmt.Fprintln(realCtx.Stdout)
return nil
}
kong-0.9.0/_examples/server/go.mod 0000664 0000000 0000000 00000000716 14572527075 0017071 0 ustar 00root root 0000000 0000000 module kong_server
go 1.13
require (
github.com/alecthomas/colour v0.1.0
github.com/alecthomas/kong v0.8.1
github.com/chzyer/readline v1.5.1
github.com/chzyer/test v1.0.0 // indirect
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
github.com/gliderlabs/ssh v0.3.6
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/kr/pty v1.1.8
github.com/mattn/go-isatty v0.0.12 // indirect
golang.org/x/crypto v0.21.0
)
kong-0.9.0/_examples/server/go.sum 0000664 0000000 0000000 00000025466 14572527075 0017127 0 ustar 00root root 0000000 0000000 github.com/alecthomas/assert/v2 v2.1.0/go.mod h1:b/+1DI2Q6NckYi+3mXyH3wFb8qG37K/DuK80n7WefXA=
github.com/alecthomas/colour v0.1.0 h1:nOE9rJm6dsZ66RGWYSFrXw461ZIt9A6+nHgL7FRrDUk=
github.com/alecthomas/colour v0.1.0/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
github.com/alecthomas/kong v0.2.1 h1:V1tLBhyQBC4rsbXbcOvm3GBaytJSwRNX69fp1WJxbqQ=
github.com/alecthomas/kong v0.2.1/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
github.com/alecthomas/kong v0.8.1 h1:acZdn3m4lLRobeh3Zi2S2EpnXTd1mOL6U7xVml+vfkY=
github.com/alecthomas/kong v0.8.1/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U=
github.com/alecthomas/repr v0.1.0/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
github.com/gliderlabs/ssh v0.3.6 h1:ZzjlDa05TcFRICb3anf/dSPN3ewz1Zx6CMLPWgkm3b8=
github.com/gliderlabs/ssh v0.3.6/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
kong-0.9.0/_examples/server/main.go 0000664 0000000 0000000 00000006456 14572527075 0017245 0 ustar 00root root 0000000 0000000 package main
import (
"errors"
"fmt"
"io"
"log"
"os"
"strings"
"github.com/chzyer/readline"
"github.com/gliderlabs/ssh"
"github.com/google/shlex"
"github.com/kr/pty"
"golang.org/x/crypto/ssh/terminal"
"github.com/alecthomas/colour"
"github.com/alecthomas/kong"
)
// Handle a single SSH interactive connection.
func handle(log *log.Logger, s ssh.Session) error {
log.Printf("New SSH")
sshPty, _, isPty := s.Pty()
if !isPty {
return errors.New("no PTY requested")
}
log.Printf("Using TERM=%s width=%d height=%d", sshPty.Term, sshPty.Window.Width, sshPty.Window.Height)
cpty, tty, err := pty.Open()
if err != nil {
return err
}
defer tty.Close()
state, err := terminal.GetState(int(cpty.Fd()))
if err != nil {
return err
}
defer terminal.Restore(int(cpty.Fd()), state)
colour.Fprintln(tty, "^BWelcome!^R")
go io.Copy(cpty, s)
go io.Copy(s, cpty)
parser, err := buildShellParser(tty)
if err != nil {
return err
}
rl, err := readline.NewEx(&readline.Config{
Prompt: "> ",
Stderr: tty,
Stdout: tty,
Stdin: tty,
FuncOnWidthChanged: func(f func()) {},
FuncMakeRaw: func() error {
_, err := terminal.MakeRaw(int(cpty.Fd())) // nolint: govet
return err
},
FuncExitRaw: func() error { return nil },
})
if err != nil {
return err
}
log.Printf("Loop")
for {
tty.Sync()
var line string
line, err = rl.Readline()
if err != nil {
if err == io.EOF {
return nil
}
return err
}
var args []string
args, err := shlex.Split(string(line))
if err != nil {
parser.Errorf("%s", err)
continue
}
var ctx *kong.Context
ctx, err = parser.Parse(args)
if err != nil {
parser.Errorf("%s", err)
if err, ok := err.(*kong.ParseError); ok {
log.Println(err.Error())
err.Context.PrintUsage(false)
}
continue
}
err = ctx.Run(ctx)
if err != nil {
parser.Errorf("%s", err)
continue
}
}
}
func buildShellParser(tty *os.File) (*kong.Kong, error) {
parser, err := kong.New(&grammar{},
kong.Name(""),
kong.Description("Example using Kong for interactive command parsing."),
kong.Writers(tty, tty),
kong.Exit(func(int) {}),
kong.ConfigureHelp(kong.HelpOptions{
NoAppSummary: true,
}),
kong.NoDefaultHelp(),
)
return parser, err
}
func handlerWithError(handle func(log *log.Logger, s ssh.Session) error) ssh.Handler {
return func(s ssh.Session) {
prefix := fmt.Sprintf("%s->%s ", s.LocalAddr(), s.RemoteAddr())
l := log.New(os.Stdout, prefix, log.LstdFlags)
err := handle(l, s)
if err != nil {
log.Printf("error: %s", err)
s.Exit(1)
} else {
log.Printf("Bye")
s.Exit(0)
}
}
}
var cli struct {
HostKey string `type:"existingfile" help:"SSH host key to use." default:"server_rsa_key"`
Bind string `help:"Bind address for server." default:"127.0.0.1:6740"`
}
func main() {
ctx := kong.Parse(&cli,
kong.Name("server"),
kong.Description("A network server using Kong for interacting with clients."))
ssh.Handle(handlerWithError(handle))
log.Printf("SSH listening on: %s", cli.Bind)
log.Printf("Using host key: %s", cli.HostKey)
log.Println()
parts := strings.Split(cli.Bind, ":")
log.Printf("Connect with: ssh -p %s %s", parts[1], parts[0])
log.Println()
err := ssh.ListenAndServe(cli.Bind, nil, ssh.HostKeyFile(cli.HostKey))
ctx.FatalIfErrorf(err)
}
kong-0.9.0/_examples/server/server_rsa_key 0000664 0000000 0000000 00000003213 14572527075 0020724 0 ustar 00root root 0000000 0000000 -----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAxK1QbPQYibc0VFZdc6GHPka5oTeGnXsMZiyX4JbHUJl1oNDB
Xg9NATbft+q/6ZDjyVEQhq8xgLvYFkL8qBLt/6UAaOub0RtmPqQwmxoNLWuXFFwn
YlBKApQ4gf58/jOcGPpdEwfjkjwLb536Bni25XMU4cYrdIvQIhtMaK+Zqja/3MAC
V6ZRZZCd8hABJqaZu+3mRElnF1d7gfMvA/hhaq7Y5VYr8rUrBHHimrT/GEP6aCbf
Npo43SfRnUDu2+EAK7vA9cM8fg/O/mvNR1/9jzOWyr8ZDLD6R6iaZCQ2anEPcqim
MCOtibSeVOms07Zcn/TSsgmfGwa8rQkpXVJA5wIDAQABAoIBAQCTUccGdajTrzkx
WyfQ71NgoJV3XyIkYAEfn5N8FTTi+LAVb4kILanemP3mw55RE8isCV65pA0OgqYP
tsmOE+/WKAAwlxs1/LIPhekqpM7uEMMv6v9NMxrc562UIc36kyn/w7loAebCqNtg
FhMsOcu1/wfLPidau0eB5LTNTYtq5RuSKxoindvatk+Zmk0KjoA+f25MlwCEHQNr
ygpopclyTHVln2t3t0o97/a7dHa9+HlmVO4GxWvTTiqtcFErTGWtTUW8aeZFS83r
p+JZNxReSJ2MlM9bm15wJ0L86GTeYZQiaNuC1XETbFvX+9Ffkl+7EtsdYDLV1N6r
/eOP2f0hAoGBAOKVDHmnru7SVmbH5BI8HW5sd6IVztZM3+BKzy6AaPc4/FgG6MOr
bJyFbmN8S/gVi4OYOJXgfaeKcycYJFTjXUSnNRQ9eT0MseD9SxzEXV7RGtnvudiu
pbRmtBRtf3e4beaN9X4SfWk4+Frw7B8UsPXwV/09s7AW279cES565IkfAoGBAN42
TQSC/jQmJBpGSnqWfqQtKPTSKFoZ/JQbxoy9QckAMqVSFwBBgwQYr4MbI7WyjPRE
s43kpf+Sq/++fc3hyk5XAWBKscK0KLs0HBRZyLybQYI+f4/x2giVzKeRRNVa9nQa
VdIU8i+eO2AUzG690q89HGkRBsfXekjq5kXC9Cc5AoGAUY0b5F16FPMXrf6cFAQX
A7t+g5Qd0fvxSCUk1LPbE8Aq8vPpqyN0ABH2XVBLd4spn7+V/jvCfh7Su2txCCyd
USxtak+F53c+PqBr/HqgsJPKek5SMa8KbRfaENAoZMq4o5bMmQfGo6yhlvnHwpgL
6TkMMlWW6vYPOZzFglkxEDkCgYApT78Rz6ii2VRs7hR6pe/1Zc/vdAK8fYhPoLpQ
//5y9+5yfch467UH1e8LWMhSx1cdMoiPIKsb0JDZgviwhgGufs5qsHhL0mKgKxft
UKPZLKQJKsVcZYI7hl396Sv63mZjP2IlJG/CGpC/VB6NmAzLN3lIrzmrfYvmcoVN
AumRQQKBgB4Uznek3nq5ZQf93+ucvXpf0bLRqq1va7jYmRosyiCN52grbclj5uMq
vxr1uoqmgtCfqdgUbm0s+kVK6D4bPkz4HQOSXImXhLs8/KdixYfPLSarxYvTxZKg
mMF1XqcdRwSv3RZYtUbbF7dYQYsC1/ZKXvtPldeoDmTZ+U7b2qbE
-----END RSA PRIVATE KEY-----
kong-0.9.0/_examples/server/server_rsa_key.pub 0000664 0000000 0000000 00000000576 14572527075 0021522 0 ustar 00root root 0000000 0000000 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDErVBs9BiJtzRUVl1zoYc+RrmhN4adewxmLJfglsdQmXWg0MFeD00BNt+36r/pkOPJURCGrzGAu9gWQvyoEu3/pQBo65vRG2Y+pDCbGg0ta5cUXCdiUEoClDiB/nz+M5wY+l0TB+OSPAtvnfoGeLblcxThxit0i9AiG0xor5mqNr/cwAJXplFlkJ3yEAEmppm77eZESWcXV3uB8y8D+GFqrtjlVivytSsEceKatP8YQ/poJt82mjjdJ9GdQO7b4QAru8D1wzx+D87+a81HX/2PM5bKvxkMsPpHqJpkJDZqcQ9yqKYwI62JtJ5U6azTtlyf9NKyCZ8bBrytCSldUkDn
kong-0.9.0/_examples/shell/ 0000775 0000000 0000000 00000000000 14572527075 0015560 5 ustar 00root root 0000000 0000000 kong-0.9.0/_examples/shell/commandstring/ 0000775 0000000 0000000 00000000000 14572527075 0020425 5 ustar 00root root 0000000 0000000 kong-0.9.0/_examples/shell/commandstring/main.go 0000664 0000000 0000000 00000001554 14572527075 0021705 0 ustar 00root root 0000000 0000000 package main
import (
"fmt"
"github.com/alecthomas/kong"
)
var cli struct {
Debug bool `help:"Debug mode."`
Rm struct {
User string `help:"Run as user." short:"u" default:"default"`
Force bool `help:"Force removal." short:"f"`
Recursive bool `help:"Recursively remove files." short:"r"`
Paths []string `arg:"" help:"Paths to remove." type:"path" name:"path"`
} `cmd:"" help:"Remove files."`
Ls struct {
Paths []string `arg:"" optional:"" help:"Paths to list." type:"path"`
} `cmd:"" help:"List paths."`
}
func main() {
ctx := kong.Parse(&cli,
kong.Name("shell"),
kong.Description("A shell-like example app."),
kong.UsageOnError(),
kong.ConfigureHelp(kong.HelpOptions{
Compact: true,
Summary: true,
}))
switch ctx.Command() {
case "rm ":
fmt.Println(cli.Rm.Paths, cli.Rm.Force, cli.Rm.Recursive)
case "ls":
}
}
kong-0.9.0/_examples/shell/help/ 0000775 0000000 0000000 00000000000 14572527075 0016510 5 ustar 00root root 0000000 0000000 kong-0.9.0/_examples/shell/help/README.md 0000664 0000000 0000000 00000000371 14572527075 0017770 0 ustar 00root root 0000000 0000000 # Example of custom help providers
This example demonstrates how to add `Help() string` functions (ie. the `HelpProvider` interface) to your commands, arguments, and flags to augment the help text provided using `help:""` style tagged annotations.
kong-0.9.0/_examples/shell/help/main.go 0000664 0000000 0000000 00000001620 14572527075 0017762 0 ustar 00root root 0000000 0000000 package main
import (
"fmt"
"github.com/alecthomas/kong"
)
var cli struct {
Flag flagWithHelp `help:"Regular flag help"`
Echo commandWithHelp `cmd:"" help:"Regular command help"`
}
type flagWithHelp bool
func (f *flagWithHelp) Help() string {
return "🏁 additional flag help"
}
type commandWithHelp struct {
Msg argumentWithHelp `arg:"" help:"Regular argument help"`
}
func (c *commandWithHelp) Help() string {
return "🚀 additional command help"
}
type argumentWithHelp struct {
Msg string `arg:""`
}
func (f *argumentWithHelp) Help() string {
return "📣 additional argument help"
}
func main() {
ctx := kong.Parse(&cli,
kong.Name("help"),
kong.Description("An app demonstrating HelpProviders"),
kong.UsageOnError(),
kong.ConfigureHelp(kong.HelpOptions{
Compact: true,
Summary: false,
}))
switch ctx.Command() {
case "echo ":
fmt.Println(cli.Echo.Msg)
}
}
kong-0.9.0/benchmark_test.go 0000664 0000000 0000000 00000003200 14572527075 0016007 0 ustar 00root root 0000000 0000000 package kong
import (
"fmt"
"strconv"
"testing"
"github.com/alecthomas/assert/v2"
)
func BenchmarkKong_interpolate(b *testing.B) {
prepareKong := func(t testing.TB, count int) *Kong {
t.Helper()
k := &Kong{
vars: make(Vars, count),
registry: NewRegistry().RegisterDefaults(),
}
for i := 0; i < count; i++ {
helpVar := fmt.Sprintf("help_param%d", i)
k.vars[helpVar] = strconv.Itoa(i)
}
grammar := &struct {
Param0 string `help:"${help_param0}"`
}{}
model, err := build(k, grammar)
assert.NoError(t, err)
for i := 0; i < count; i++ {
model.Node.Flags = append(model.Node.Flags, &Flag{
Value: &Value{
Help: fmt.Sprintf("${help_param%d}", i),
Tag: newEmptyTag(),
},
})
}
k.Model = model
return k
}
for _, count := range []int{5, 500, 5000} {
count := count
b.Run(strconv.Itoa(count), func(b *testing.B) {
var err error
k := prepareKong(b, count)
for i := 0; i < b.N; i++ {
err = k.interpolate(k.Model.Node)
}
assert.NoError(b, err)
b.ReportAllocs()
})
}
}
func Benchmark_interpolateValue(b *testing.B) {
varsLen := 10000
k := &Kong{
vars: make(Vars, 10000),
registry: NewRegistry().RegisterDefaults(),
}
for i := 0; i < varsLen; i++ {
helpVar := fmt.Sprintf("help_param%d", i)
k.vars[helpVar] = strconv.Itoa(i)
}
grammar := struct {
Param9999 string `kong:"cmd,help=${help_param9999}"`
}{}
model, err := build(k, &grammar)
if err != nil {
b.FailNow()
}
k.Model = model
flag := k.Model.Flags[0]
for i := 0; i < b.N; i++ {
err = k.interpolateValue(flag.Value, k.vars)
if err != nil {
b.FailNow()
}
}
b.ReportAllocs()
}
kong-0.9.0/bin/ 0000775 0000000 0000000 00000000000 14572527075 0013244 5 ustar 00root root 0000000 0000000 kong-0.9.0/bin/.go-1.22.0.pkg 0000777 0000000 0000000 00000000000 14572527075 0016356 2hermit ustar 00root root 0000000 0000000 kong-0.9.0/bin/.golangci-lint-1.55.2.pkg 0000777 0000000 0000000 00000000000 14572527075 0020510 2hermit ustar 00root root 0000000 0000000 kong-0.9.0/bin/README.hermit.md 0000664 0000000 0000000 00000000416 14572527075 0016013 0 ustar 00root root 0000000 0000000 # Hermit environment
This is a [Hermit](https://github.com/cashapp/hermit) bin directory.
The symlinks in this directory are managed by Hermit and will automatically
download and install Hermit itself as well as packages. These packages are
local to this environment.
kong-0.9.0/bin/activate-hermit 0000775 0000000 0000000 00000001062 14572527075 0016257 0 ustar 00root root 0000000 0000000 #!/bin/bash
# This file must be used with "source bin/activate-hermit" from bash or zsh.
# You cannot run it directly
if [ "${BASH_SOURCE-}" = "$0" ]; then
echo "You must source this script: \$ source $0" >&2
exit 33
fi
BIN_DIR="$(dirname "${BASH_SOURCE[0]:-${(%):-%x}}")"
if "${BIN_DIR}/hermit" noop > /dev/null; then
eval "$("${BIN_DIR}/hermit" activate "${BIN_DIR}/..")"
if [ -n "${BASH-}" ] || [ -n "${ZSH_VERSION-}" ]; then
hash -r 2>/dev/null
fi
echo "Hermit environment $("${HERMIT_ENV}"/bin/hermit env HERMIT_ENV) activated"
fi
kong-0.9.0/bin/go 0000777 0000000 0000000 00000000000 14572527075 0015473 2.go-1.22.0.pkg ustar 00root root 0000000 0000000 kong-0.9.0/bin/gofmt 0000777 0000000 0000000 00000000000 14572527075 0016202 2.go-1.22.0.pkg ustar 00root root 0000000 0000000 kong-0.9.0/bin/golangci-lint 0000777 0000000 0000000 00000000000 14572527075 0021747 2.golangci-lint-1.55.2.pkg ustar 00root root 0000000 0000000 kong-0.9.0/bin/hermit 0000775 0000000 0000000 00000001361 14572527075 0014463 0 ustar 00root root 0000000 0000000 #!/bin/bash
set -eo pipefail
if [ -z "${HERMIT_STATE_DIR}" ]; then
case "$(uname -s)" in
Darwin)
export HERMIT_STATE_DIR="${HOME}/Library/Caches/hermit"
;;
Linux)
export HERMIT_STATE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/hermit"
;;
esac
fi
export HERMIT_DIST_URL="${HERMIT_DIST_URL:-https://github.com/cashapp/hermit/releases/download/stable}"
HERMIT_CHANNEL="$(basename "${HERMIT_DIST_URL}")"
export HERMIT_CHANNEL
export HERMIT_EXE=${HERMIT_EXE:-${HERMIT_STATE_DIR}/pkg/hermit@${HERMIT_CHANNEL}/hermit}
if [ ! -x "${HERMIT_EXE}" ]; then
echo "Bootstrapping ${HERMIT_EXE} from ${HERMIT_DIST_URL}" 1>&2
curl -fsSL "${HERMIT_DIST_URL}/install.sh" | /bin/bash 1>&2
fi
exec "${HERMIT_EXE}" --level=fatal exec "$0" -- "$@"
kong-0.9.0/bin/hermit.hcl 0000664 0000000 0000000 00000000000 14572527075 0015212 0 ustar 00root root 0000000 0000000 kong-0.9.0/build.go 0000664 0000000 0000000 00000023100 14572527075 0014116 0 ustar 00root root 0000000 0000000 package kong
import (
"fmt"
"reflect"
"strings"
)
// Plugins are dynamically embedded command-line structures.
//
// Each element in the Plugins list *must* be a pointer to a structure.
type Plugins []interface{}
func build(k *Kong, ast interface{}) (app *Application, err error) {
v := reflect.ValueOf(ast)
iv := reflect.Indirect(v)
if v.Kind() != reflect.Ptr || iv.Kind() != reflect.Struct {
return nil, fmt.Errorf("expected a pointer to a struct but got %T", ast)
}
app = &Application{}
extraFlags := k.extraFlags()
seenFlags := map[string]bool{}
for _, flag := range extraFlags {
seenFlags[flag.Name] = true
}
node, err := buildNode(k, iv, ApplicationNode, newEmptyTag(), seenFlags)
if err != nil {
return nil, err
}
if len(node.Positional) > 0 && len(node.Children) > 0 {
return nil, fmt.Errorf("can't mix positional arguments and branching arguments on %T", ast)
}
app.Node = node
app.Node.Flags = append(extraFlags, app.Node.Flags...)
app.Tag = newEmptyTag()
app.Tag.Vars = k.vars
return app, nil
}
func dashedString(s string) string {
return strings.Join(camelCase(s), "-")
}
type flattenedField struct {
field reflect.StructField
value reflect.Value
tag *Tag
}
func flattenedFields(v reflect.Value, ptag *Tag) (out []flattenedField, err error) {
v = reflect.Indirect(v)
for i := 0; i < v.NumField(); i++ {
ft := v.Type().Field(i)
fv := v.Field(i)
tag, err := parseTag(v, ft)
if err != nil {
return nil, err
}
if tag.Ignored {
continue
}
// Assign group if it's not already set.
if tag.Group == "" {
tag.Group = ptag.Group
}
// Accumulate prefixes.
tag.Prefix = ptag.Prefix + tag.Prefix
tag.EnvPrefix = ptag.EnvPrefix + tag.EnvPrefix
// Combine parent vars.
tag.Vars = ptag.Vars.CloneWith(tag.Vars)
// Command and embedded structs can be pointers, so we hydrate them now.
if (tag.Cmd || tag.Embed) && ft.Type.Kind() == reflect.Ptr {
fv = reflect.New(ft.Type.Elem()).Elem()
v.FieldByIndex(ft.Index).Set(fv.Addr())
}
if !ft.Anonymous && !tag.Embed {
if fv.CanSet() {
field := flattenedField{field: ft, value: fv, tag: tag}
out = append(out, field)
}
continue
}
// Embedded type.
if fv.Kind() == reflect.Interface {
fv = fv.Elem()
} else if fv.Type() == reflect.TypeOf(Plugins{}) {
for i := 0; i < fv.Len(); i++ {
fields, ferr := flattenedFields(fv.Index(i).Elem(), tag)
if ferr != nil {
return nil, ferr
}
out = append(out, fields...)
}
continue
}
sub, err := flattenedFields(fv, tag)
if err != nil {
return nil, err
}
out = append(out, sub...)
}
return out, nil
}
// Build a Node in the Kong data model.
//
// "v" is the value to create the node from, "typ" is the output Node type.
func buildNode(k *Kong, v reflect.Value, typ NodeType, tag *Tag, seenFlags map[string]bool) (*Node, error) {
node := &Node{
Type: typ,
Target: v,
Tag: tag,
}
fields, err := flattenedFields(v, tag)
if err != nil {
return nil, err
}
MAIN:
for _, field := range fields {
for _, r := range k.ignoreFields {
if r.MatchString(v.Type().Name() + "." + field.field.Name) {
continue MAIN
}
}
ft := field.field
fv := field.value
tag := field.tag
name := tag.Name
if name == "" {
name = tag.Prefix + k.flagNamer(ft.Name)
} else {
name = tag.Prefix + name
}
if len(tag.Envs) != 0 {
for i := range tag.Envs {
tag.Envs[i] = tag.EnvPrefix + tag.Envs[i]
}
}
// Nested structs are either commands or args, unless they implement the Mapper interface.
if field.value.Kind() == reflect.Struct && (tag.Cmd || tag.Arg) && k.registry.ForValue(fv) == nil {
typ := CommandNode
if tag.Arg {
typ = ArgumentNode
}
err = buildChild(k, node, typ, v, ft, fv, tag, name, seenFlags)
} else {
err = buildField(k, node, v, ft, fv, tag, name, seenFlags)
}
if err != nil {
return nil, err
}
}
// Validate if there are no duplicate names
if err := checkDuplicateNames(node, v); err != nil {
return nil, err
}
// "Unsee" flags.
for _, flag := range node.Flags {
delete(seenFlags, "--"+flag.Name)
if flag.Short != 0 {
delete(seenFlags, "-"+string(flag.Short))
}
}
if err := validatePositionalArguments(node); err != nil {
return nil, err
}
return node, nil
}
func validatePositionalArguments(node *Node) error {
var last *Value
for i, curr := range node.Positional {
if last != nil {
// Scan through argument positionals to ensure optional is never before a required.
if !last.Required && curr.Required {
return fmt.Errorf("%s: required %q cannot come after optional %q", node.FullPath(), curr.Name, last.Name)
}
// Cumulative argument needs to be last.
if last.IsCumulative() {
return fmt.Errorf("%s: argument %q cannot come after cumulative %q", node.FullPath(), curr.Name, last.Name)
}
}
last = curr
curr.Position = i
}
return nil
}
func buildChild(k *Kong, node *Node, typ NodeType, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) error {
child, err := buildNode(k, fv, typ, newEmptyTag(), seenFlags)
if err != nil {
return err
}
child.Name = name
child.Tag = tag
child.Parent = node
child.Help = tag.Help
child.Hidden = tag.Hidden
child.Group = buildGroupForKey(k, tag.Group)
child.Aliases = tag.Aliases
if provider, ok := fv.Addr().Interface().(HelpProvider); ok {
child.Detail = provider.Help()
}
// A branching argument. This is a bit hairy, as we let buildNode() do the parsing, then check that
// a positional argument is provided to the child, and move it to the branching argument field.
if tag.Arg {
if len(child.Positional) == 0 {
return failField(v, ft, "positional branch must have at least one child positional argument named %q", name)
}
if child.Positional[0].Name != name {
return failField(v, ft, "first field in positional branch must have the same name as the parent field (%s).", child.Name)
}
child.Argument = child.Positional[0]
child.Positional = child.Positional[1:]
if child.Help == "" {
child.Help = child.Argument.Help
}
} else {
if tag.HasDefault {
if node.DefaultCmd != nil {
return failField(v, ft, "can't have more than one default command under %s", node.Summary())
}
if tag.Default != "withargs" && (len(child.Children) > 0 || len(child.Positional) > 0) {
return failField(v, ft, "default command %s must not have subcommands or arguments", child.Summary())
}
node.DefaultCmd = child
}
if tag.Passthrough {
if len(child.Children) > 0 || len(child.Flags) > 0 {
return failField(v, ft, "passthrough command %s must not have subcommands or flags", child.Summary())
}
if len(child.Positional) != 1 {
return failField(v, ft, "passthrough command %s must contain exactly one positional argument", child.Summary())
}
if !checkPassthroughArg(child.Positional[0].Target) {
return failField(v, ft, "passthrough command %s must contain exactly one positional argument of []string type", child.Summary())
}
child.Passthrough = true
}
}
node.Children = append(node.Children, child)
if len(child.Positional) > 0 && len(child.Children) > 0 {
return failField(v, ft, "can't mix positional arguments and branching arguments")
}
return nil
}
func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) error {
mapper := k.registry.ForNamedValue(tag.Type, fv)
if mapper == nil {
return failField(v, ft, "unsupported field type %s, perhaps missing a cmd:\"\" tag?", ft.Type)
}
value := &Value{
Name: name,
Help: tag.Help,
OrigHelp: tag.Help,
HasDefault: tag.HasDefault,
Default: tag.Default,
DefaultValue: reflect.New(fv.Type()).Elem(),
Mapper: mapper,
Tag: tag,
Target: fv,
Enum: tag.Enum,
Passthrough: tag.Passthrough,
// Flags are optional by default, and args are required by default.
Required: (!tag.Arg && tag.Required) || (tag.Arg && !tag.Optional),
Format: tag.Format,
}
if tag.Arg {
node.Positional = append(node.Positional, value)
} else {
if seenFlags["--"+value.Name] {
return failField(v, ft, "duplicate flag --%s", value.Name)
}
seenFlags["--"+value.Name] = true
for _, alias := range tag.Aliases {
aliasFlag := "--" + alias
if seenFlags[aliasFlag] {
return failField(v, ft, "duplicate flag %s", aliasFlag)
}
seenFlags[aliasFlag] = true
}
if tag.Short != 0 {
if seenFlags["-"+string(tag.Short)] {
return failField(v, ft, "duplicate short flag -%c", tag.Short)
}
seenFlags["-"+string(tag.Short)] = true
}
flag := &Flag{
Value: value,
Aliases: tag.Aliases,
Short: tag.Short,
PlaceHolder: tag.PlaceHolder,
Envs: tag.Envs,
Group: buildGroupForKey(k, tag.Group),
Xor: tag.Xor,
Hidden: tag.Hidden,
}
value.Flag = flag
node.Flags = append(node.Flags, flag)
}
return nil
}
func buildGroupForKey(k *Kong, key string) *Group {
if key == "" {
return nil
}
for _, group := range k.groups {
if group.Key == key {
return &group
}
}
// No group provided with kong.ExplicitGroups. We create one ad-hoc for this key.
return &Group{
Key: key,
Title: key,
}
}
func checkDuplicateNames(node *Node, v reflect.Value) error {
seenNames := make(map[string]struct{})
for _, node := range node.Children {
if _, ok := seenNames[node.Name]; ok {
name := v.Type().Name()
if name == "" {
name = ""
}
return fmt.Errorf("duplicate command name %q in command %q", node.Name, name)
}
seenNames[node.Name] = struct{}{}
}
return nil
}
kong-0.9.0/callbacks.go 0000664 0000000 0000000 00000006014 14572527075 0014743 0 ustar 00root root 0000000 0000000 package kong
import (
"fmt"
"reflect"
"strings"
)
type bindings map[reflect.Type]func() (reflect.Value, error)
func (b bindings) String() string {
out := []string{}
for k := range b {
out = append(out, k.String())
}
return "bindings{" + strings.Join(out, ", ") + "}"
}
func (b bindings) add(values ...interface{}) bindings {
for _, v := range values {
v := v
b[reflect.TypeOf(v)] = func() (reflect.Value, error) { return reflect.ValueOf(v), nil }
}
return b
}
func (b bindings) addTo(impl, iface interface{}) {
valueOf := reflect.ValueOf(impl)
b[reflect.TypeOf(iface).Elem()] = func() (reflect.Value, error) { return valueOf, nil }
}
func (b bindings) addProvider(provider interface{}) error {
pv := reflect.ValueOf(provider)
t := pv.Type()
if t.Kind() != reflect.Func || t.NumIn() != 0 || t.NumOut() != 2 || t.Out(1) != reflect.TypeOf((*error)(nil)).Elem() {
return fmt.Errorf("%T must be a function with the signature func()(T, error)", provider)
}
rt := pv.Type().Out(0)
b[rt] = func() (reflect.Value, error) {
out := pv.Call(nil)
errv := out[1]
var err error
if !errv.IsNil() {
err = errv.Interface().(error) //nolint
}
return out[0], err
}
return nil
}
// Clone and add values.
func (b bindings) clone() bindings {
out := make(bindings, len(b))
for k, v := range b {
out[k] = v
}
return out
}
func (b bindings) merge(other bindings) bindings {
for k, v := range other {
b[k] = v
}
return b
}
func getMethod(value reflect.Value, name string) reflect.Value {
method := value.MethodByName(name)
if !method.IsValid() {
if value.CanAddr() {
method = value.Addr().MethodByName(name)
}
}
return method
}
func callFunction(f reflect.Value, bindings bindings) error {
if f.Kind() != reflect.Func {
return fmt.Errorf("expected function, got %s", f.Type())
}
in := []reflect.Value{}
t := f.Type()
if t.NumOut() != 1 || !t.Out(0).Implements(callbackReturnSignature) {
return fmt.Errorf("return value of %s must implement \"error\"", t)
}
for i := 0; i < t.NumIn(); i++ {
pt := t.In(i)
if argf, ok := bindings[pt]; ok {
argv, err := argf()
if err != nil {
return err
}
in = append(in, argv)
} else {
return fmt.Errorf("couldn't find binding of type %s for parameter %d of %s(), use kong.Bind(%s)", pt, i, t, pt)
}
}
out := f.Call(in)
if out[0].IsNil() {
return nil
}
return out[0].Interface().(error) //nolint
}
func callAnyFunction(f reflect.Value, bindings bindings) (out []any, err error) {
if f.Kind() != reflect.Func {
return nil, fmt.Errorf("expected function, got %s", f.Type())
}
in := []reflect.Value{}
t := f.Type()
for i := 0; i < t.NumIn(); i++ {
pt := t.In(i)
if argf, ok := bindings[pt]; ok {
argv, err := argf()
if err != nil {
return nil, err
}
in = append(in, argv)
} else {
return nil, fmt.Errorf("couldn't find binding of type %s for parameter %d of %s(), use kong.Bind(%s)", pt, i, t, pt)
}
}
outv := f.Call(in)
out = make([]any, len(outv))
for i, v := range outv {
out[i] = v.Interface()
}
return out, nil
}
kong-0.9.0/camelcase.go 0000664 0000000 0000000 00000005432 14572527075 0014744 0 ustar 00root root 0000000 0000000 package kong
// NOTE: This code is from https://github.com/fatih/camelcase. MIT license.
import (
"unicode"
"unicode/utf8"
)
// Split splits the camelcase word and returns a list of words. It also
// supports digits. Both lower camel case and upper camel case are supported.
// For more info please check: http://en.wikipedia.org/wiki/CamelCase
//
// Examples
//
// "" => [""]
// "lowercase" => ["lowercase"]
// "Class" => ["Class"]
// "MyClass" => ["My", "Class"]
// "MyC" => ["My", "C"]
// "HTML" => ["HTML"]
// "PDFLoader" => ["PDF", "Loader"]
// "AString" => ["A", "String"]
// "SimpleXMLParser" => ["Simple", "XML", "Parser"]
// "vimRPCPlugin" => ["vim", "RPC", "Plugin"]
// "GL11Version" => ["GL", "11", "Version"]
// "99Bottles" => ["99", "Bottles"]
// "May5" => ["May", "5"]
// "BFG9000" => ["BFG", "9000"]
// "BöseÜberraschung" => ["Böse", "Überraschung"]
// "Two spaces" => ["Two", " ", "spaces"]
// "BadUTF8\xe2\xe2\xa1" => ["BadUTF8\xe2\xe2\xa1"]
//
// Splitting rules
//
// 1. If string is not valid UTF-8, return it without splitting as
// single item array.
// 2. Assign all unicode characters into one of 4 sets: lower case
// letters, upper case letters, numbers, and all other characters.
// 3. Iterate through characters of string, introducing splits
// between adjacent characters that belong to different sets.
// 4. Iterate through array of split strings, and if a given string
// is upper case:
// if subsequent string is lower case:
// move last character of upper case string to beginning of
// lower case string
func camelCase(src string) (entries []string) {
// don't split invalid utf8
if !utf8.ValidString(src) {
return []string{src}
}
entries = []string{}
var runes [][]rune
lastClass := 0
// split into fields based on class of unicode character
for _, r := range src {
var class int
switch {
case unicode.IsLower(r):
class = 1
case unicode.IsUpper(r):
class = 2
case unicode.IsDigit(r):
class = 3
default:
class = 4
}
if class == lastClass {
runes[len(runes)-1] = append(runes[len(runes)-1], r)
} else {
runes = append(runes, []rune{r})
}
lastClass = class
}
// handle upper case -> lower case sequences, e.g.
// "PDFL", "oader" -> "PDF", "Loader"
for i := 0; i < len(runes)-1; i++ {
if unicode.IsUpper(runes[i][0]) && unicode.IsLower(runes[i+1][0]) {
runes[i+1] = append([]rune{runes[i][len(runes[i])-1]}, runes[i+1]...)
runes[i] = runes[i][:len(runes[i])-1]
}
}
// construct []string from results
for _, s := range runes {
if len(s) > 0 {
entries = append(entries, string(s))
}
}
return entries
}
kong-0.9.0/config_test.go 0000664 0000000 0000000 00000002210 14572527075 0015322 0 ustar 00root root 0000000 0000000 package kong_test
import (
"encoding/json"
"os"
"testing"
"github.com/alecthomas/assert/v2"
"github.com/alecthomas/kong"
)
func TestMultipleConfigLoading(t *testing.T) {
var cli struct {
Flag string `json:"flag,omitempty"`
}
cli.Flag = "first"
first, cleanFirst := makeConfig(t, &cli)
defer cleanFirst()
cli.Flag = ""
second, cleanSecond := makeConfig(t, &cli)
defer cleanSecond()
p := mustNew(t, &cli, kong.Configuration(kong.JSON, first, second))
_, err := p.Parse(nil)
assert.NoError(t, err)
assert.Equal(t, "first", cli.Flag)
}
func TestConfigValidation(t *testing.T) {
var cli struct {
Flag string `json:"flag,omitempty" enum:"valid" required:""`
}
cli.Flag = "invalid"
conf, cleanConf := makeConfig(t, &cli)
defer cleanConf()
p := mustNew(t, &cli, kong.Configuration(kong.JSON, conf))
_, err := p.Parse(nil)
assert.Error(t, err)
}
func makeConfig(t *testing.T, config interface{}) (path string, cleanup func()) {
t.Helper()
w, err := os.CreateTemp("", "")
assert.NoError(t, err)
defer w.Close()
err = json.NewEncoder(w).Encode(config)
assert.NoError(t, err)
return w.Name(), func() { os.Remove(w.Name()) }
}
kong-0.9.0/context.go 0000664 0000000 0000000 00000061066 14572527075 0014520 0 ustar 00root root 0000000 0000000 package kong
import (
"errors"
"fmt"
"os"
"reflect"
"sort"
"strconv"
"strings"
)
// Path records the nodes and parsed values from the current command-line.
type Path struct {
Parent *Node
// One of these will be non-nil.
App *Application
Positional *Positional
Flag *Flag
Argument *Argument
Command *Command
// Flags added by this node.
Flags []*Flag
// True if this Path element was created as the result of a resolver.
Resolved bool
}
// Node returns the Node associated with this Path, or nil if Path is a non-Node.
func (p *Path) Node() *Node {
switch {
case p.App != nil:
return p.App.Node
case p.Argument != nil:
return p.Argument
case p.Command != nil:
return p.Command
}
return nil
}
// Visitable returns the Visitable for this path element.
func (p *Path) Visitable() Visitable {
switch {
case p.App != nil:
return p.App
case p.Argument != nil:
return p.Argument
case p.Command != nil:
return p.Command
case p.Flag != nil:
return p.Flag
case p.Positional != nil:
return p.Positional
}
return nil
}
// Context contains the current parse context.
type Context struct {
*Kong
// A trace through parsed nodes.
Path []*Path
// Original command-line arguments.
Args []string
// Error that occurred during trace, if any.
Error error
values map[*Value]reflect.Value // Temporary values during tracing.
bindings bindings
resolvers []Resolver // Extra context-specific resolvers.
scan *Scanner
}
// Trace path of "args" through the grammar tree.
//
// The returned Context will include a Path of all commands, arguments, positionals and flags.
//
// This just constructs a new trace. To fully apply the trace you must call Reset(), Resolve(),
// Validate() and Apply().
func Trace(k *Kong, args []string) (*Context, error) {
c := &Context{
Kong: k,
Args: args,
Path: []*Path{
{App: k.Model, Flags: k.Model.Flags},
},
values: map[*Value]reflect.Value{},
scan: Scan(args...),
bindings: bindings{},
}
c.Error = c.trace(c.Model.Node)
return c, nil
}
// Bind adds bindings to the Context.
func (c *Context) Bind(args ...interface{}) {
c.bindings.add(args...)
}
// BindTo adds a binding to the Context.
//
// This will typically have to be called like so:
//
// BindTo(impl, (*MyInterface)(nil))
func (c *Context) BindTo(impl, iface interface{}) {
c.bindings.addTo(impl, iface)
}
// BindToProvider allows binding of provider functions.
//
// This is useful when the Run() function of different commands require different values that may
// not all be initialisable from the main() function.
func (c *Context) BindToProvider(provider interface{}) error {
return c.bindings.addProvider(provider)
}
// Value returns the value for a particular path element.
func (c *Context) Value(path *Path) reflect.Value {
switch {
case path.Positional != nil:
return c.values[path.Positional]
case path.Flag != nil:
return c.values[path.Flag.Value]
case path.Argument != nil:
return c.values[path.Argument.Argument]
}
panic("can only retrieve value for flag, argument or positional")
}
// Selected command or argument.
func (c *Context) Selected() *Node {
var selected *Node
for _, path := range c.Path {
switch {
case path.Command != nil:
selected = path.Command
case path.Argument != nil:
selected = path.Argument
}
}
return selected
}
// Empty returns true if there were no arguments provided.
func (c *Context) Empty() bool {
for _, path := range c.Path {
if !path.Resolved && path.App == nil {
return false
}
}
return true
}
// Validate the current context.
func (c *Context) Validate() error { //nolint: gocyclo
err := Visit(c.Model, func(node Visitable, next Next) error {
switch node := node.(type) {
case *Value:
ok := atLeastOneEnvSet(node.Tag.Envs)
if node.Enum != "" && (!node.Required || node.HasDefault || (len(node.Tag.Envs) != 0 && ok)) {
if err := checkEnum(node, node.Target); err != nil {
return err
}
}
case *Flag:
ok := atLeastOneEnvSet(node.Tag.Envs)
if node.Enum != "" && (!node.Required || node.HasDefault || (len(node.Tag.Envs) != 0 && ok)) {
if err := checkEnum(node.Value, node.Target); err != nil {
return err
}
}
}
return next(nil)
})
if err != nil {
return err
}
for _, el := range c.Path {
var (
value reflect.Value
desc string
)
switch node := el.Visitable().(type) {
case *Value:
value = node.Target
desc = node.ShortSummary()
case *Flag:
value = node.Target
desc = node.ShortSummary()
case *Application:
value = node.Target
desc = ""
case *Node:
value = node.Target
desc = node.Path()
}
if validate := isValidatable(value); validate != nil {
if err := validate.Validate(); err != nil {
if desc != "" {
return fmt.Errorf("%s: %w", desc, err)
}
return err
}
}
}
for _, resolver := range c.combineResolvers() {
if err := resolver.Validate(c.Model); err != nil {
return err
}
}
for _, path := range c.Path {
var value *Value
switch {
case path.Flag != nil:
value = path.Flag.Value
case path.Positional != nil:
value = path.Positional
}
if value != nil && value.Tag.Enum != "" {
if err := checkEnum(value, value.Target); err != nil {
return err
}
}
if err := checkMissingFlags(path.Flags); err != nil {
return err
}
}
// Check the terminal node.
node := c.Selected()
if node == nil {
node = c.Model.Node
}
// Find deepest positional argument so we can check if all required positionals have been provided.
positionals := 0
for _, path := range c.Path {
if path.Positional != nil {
positionals = path.Positional.Position + 1
}
}
if err := checkMissingChildren(node); err != nil {
return err
}
if err := checkMissingPositionals(positionals, node.Positional); err != nil {
return err
}
if err := checkXorDuplicates(c.Path); err != nil {
return err
}
if node.Type == ArgumentNode {
value := node.Argument
if value.Required && !value.Set {
return fmt.Errorf("%s is required", node.Summary())
}
}
return nil
}
// Flags returns the accumulated available flags.
func (c *Context) Flags() (flags []*Flag) {
for _, trace := range c.Path {
flags = append(flags, trace.Flags...)
}
return
}
// Command returns the full command path.
func (c *Context) Command() string {
command := []string{}
for _, trace := range c.Path {
switch {
case trace.Positional != nil:
command = append(command, "<"+trace.Positional.Name+">")
case trace.Argument != nil:
command = append(command, "<"+trace.Argument.Name+">")
case trace.Command != nil:
command = append(command, trace.Command.Name)
}
}
return strings.Join(command, " ")
}
// AddResolver adds a context-specific resolver.
//
// This is most useful in the BeforeResolve() hook.
func (c *Context) AddResolver(resolver Resolver) {
c.resolvers = append(c.resolvers, resolver)
}
// FlagValue returns the set value of a flag if it was encountered and exists, or its default value.
func (c *Context) FlagValue(flag *Flag) interface{} {
for _, trace := range c.Path {
if trace.Flag == flag {
v, ok := c.values[trace.Flag.Value]
if !ok {
break
}
return v.Interface()
}
}
if flag.Target.IsValid() {
return flag.Target.Interface()
}
return flag.DefaultValue.Interface()
}
// Reset recursively resets values to defaults (as specified in the grammar) or the zero value.
func (c *Context) Reset() error {
return Visit(c.Model.Node, func(node Visitable, next Next) error {
if value, ok := node.(*Value); ok {
return next(value.Reset())
}
return next(nil)
})
}
func (c *Context) endParsing() {
args := []string{}
for {
token := c.scan.Pop()
if token.Type == EOLToken {
break
}
args = append(args, token.String())
}
// Note: tokens must be pushed in reverse order.
for i := range args {
c.scan.PushTyped(args[len(args)-1-i], PositionalArgumentToken)
}
}
func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo
positional := 0
node.Active = true
flags := []*Flag{}
flagNode := node
if node.DefaultCmd != nil && node.DefaultCmd.Tag.Default == "withargs" {
// Add flags of the default command if the current node has one
// and that default command allows args / flags without explicitly
// naming the command on the CLI.
flagNode = node.DefaultCmd
}
for _, group := range flagNode.AllFlags(false) {
flags = append(flags, group...)
}
if node.Passthrough {
c.endParsing()
}
for !c.scan.Peek().IsEOL() {
token := c.scan.Peek()
switch token.Type {
case UntypedToken:
switch v := token.Value.(type) {
case string:
switch {
case v == "-":
fallthrough
default: //nolint
c.scan.Pop()
c.scan.PushTyped(token.Value, PositionalArgumentToken)
// Indicates end of parsing. All remaining arguments are treated as positional arguments only.
case v == "--":
c.scan.Pop()
c.endParsing()
// Long flag.
case strings.HasPrefix(v, "--"):
c.scan.Pop()
// Parse it and push the tokens.
parts := strings.SplitN(v[2:], "=", 2)
if len(parts) > 1 {
c.scan.PushTyped(parts[1], FlagValueToken)
}
c.scan.PushTyped(parts[0], FlagToken)
// Short flag.
case strings.HasPrefix(v, "-"):
c.scan.Pop()
// Note: tokens must be pushed in reverse order.
if tail := v[2:]; tail != "" {
c.scan.PushTyped(tail, ShortFlagTailToken)
}
c.scan.PushTyped(v[1:2], ShortFlagToken)
}
default:
c.scan.Pop()
c.scan.PushTyped(token.Value, PositionalArgumentToken)
}
case ShortFlagTailToken:
c.scan.Pop()
// Note: tokens must be pushed in reverse order.
if tail := token.String()[1:]; tail != "" {
c.scan.PushTyped(tail, ShortFlagTailToken)
}
c.scan.PushTyped(token.String()[0:1], ShortFlagToken)
case FlagToken:
if err := c.parseFlag(flags, token.String()); err != nil {
return err
}
case ShortFlagToken:
if err := c.parseFlag(flags, token.String()); err != nil {
return err
}
case FlagValueToken:
return fmt.Errorf("unexpected flag argument %q", token.Value)
case PositionalArgumentToken:
candidates := []string{}
// Ensure we've consumed all positional arguments.
if positional < len(node.Positional) {
arg := node.Positional[positional]
if arg.Passthrough {
c.endParsing()
}
arg.Active = true
err := arg.Parse(c.scan, c.getValue(arg))
if err != nil {
return err
}
c.Path = append(c.Path, &Path{
Parent: node,
Positional: arg,
})
positional++
break
}
// Assign token value to a branch name if tagged as an alias
// An alias will be ignored in the case of an existing command
cmds := make(map[string]bool)
for _, branch := range node.Children {
if branch.Type == CommandNode {
cmds[branch.Name] = true
}
}
for _, branch := range node.Children {
for _, a := range branch.Aliases {
_, ok := cmds[a]
if token.Value == a && !ok {
token.Value = branch.Name
break
}
}
}
// After positional arguments have been consumed, check commands next...
for _, branch := range node.Children {
if branch.Type == CommandNode && !branch.Hidden {
candidates = append(candidates, branch.Name)
}
if branch.Type == CommandNode && branch.Name == token.Value {
c.scan.Pop()
c.Path = append(c.Path, &Path{
Parent: node,
Command: branch,
Flags: branch.Flags,
})
return c.trace(branch)
}
}
// Finally, check arguments.
for _, branch := range node.Children {
if branch.Type == ArgumentNode {
arg := branch.Argument
if err := arg.Parse(c.scan, c.getValue(arg)); err == nil {
c.Path = append(c.Path, &Path{
Parent: node,
Argument: branch,
Flags: branch.Flags,
})
return c.trace(branch)
}
}
}
// If there is a default command that allows args and nothing else
// matches, take the branch of the default command
if node.DefaultCmd != nil && node.DefaultCmd.Tag.Default == "withargs" {
c.Path = append(c.Path, &Path{
Parent: node,
Command: node.DefaultCmd,
Flags: node.DefaultCmd.Flags,
})
return c.trace(node.DefaultCmd)
}
return findPotentialCandidates(token.String(), candidates, "unexpected argument %s", token)
default:
return fmt.Errorf("unexpected token %s", token)
}
}
return c.maybeSelectDefault(flags, node)
}
// End of the line, check for a default command, but only if we're not displaying help,
// otherwise we'd only ever display the help for the default command.
func (c *Context) maybeSelectDefault(flags []*Flag, node *Node) error {
for _, flag := range flags {
if flag.Name == "help" && flag.Set {
return nil
}
}
if node.DefaultCmd != nil {
c.Path = append(c.Path, &Path{
Parent: node.DefaultCmd,
Command: node.DefaultCmd,
Flags: node.DefaultCmd.Flags,
})
}
return nil
}
// Resolve walks through the traced path, applying resolvers to any unset flags.
func (c *Context) Resolve() error {
resolvers := c.combineResolvers()
if len(resolvers) == 0 {
return nil
}
inserted := []*Path{}
for _, path := range c.Path {
for _, flag := range path.Flags {
// Flag has already been set on the command-line.
if _, ok := c.values[flag.Value]; ok {
continue
}
// Pick the last resolved value.
var selected interface{}
for _, resolver := range resolvers {
s, err := resolver.Resolve(c, path, flag)
if err != nil {
return fmt.Errorf("%s: %w", flag.ShortSummary(), err)
}
if s == nil {
continue
}
selected = s
}
if selected == nil {
continue
}
scan := Scan().PushTyped(selected, FlagValueToken)
delete(c.values, flag.Value)
err := flag.Parse(scan, c.getValue(flag.Value))
if err != nil {
return err
}
inserted = append(inserted, &Path{
Flag: flag,
Resolved: true,
})
}
}
c.Path = append(c.Path, inserted...)
return nil
}
// Combine application-level resolvers and context resolvers.
func (c *Context) combineResolvers() []Resolver {
resolvers := []Resolver{}
resolvers = append(resolvers, c.Kong.resolvers...)
resolvers = append(resolvers, c.resolvers...)
return resolvers
}
func (c *Context) getValue(value *Value) reflect.Value {
v, ok := c.values[value]
if !ok {
v = reflect.New(value.Target.Type()).Elem()
switch v.Kind() {
case reflect.Ptr:
v.Set(reflect.New(v.Type().Elem()))
case reflect.Slice:
v.Set(reflect.MakeSlice(v.Type(), 0, 0))
case reflect.Map:
v.Set(reflect.MakeMap(v.Type()))
default:
}
c.values[value] = v
}
return v
}
// ApplyDefaults if they are not already set.
func (c *Context) ApplyDefaults() error {
return Visit(c.Model.Node, func(node Visitable, next Next) error {
var value *Value
switch node := node.(type) {
case *Flag:
value = node.Value
case *Node:
value = node.Argument
case *Value:
value = node
default:
}
if value != nil {
if err := value.ApplyDefault(); err != nil {
return err
}
}
return next(nil)
})
}
// Apply traced context to the target grammar.
func (c *Context) Apply() (string, error) {
path := []string{}
for _, trace := range c.Path {
var value *Value
switch {
case trace.App != nil:
case trace.Argument != nil:
path = append(path, "<"+trace.Argument.Name+">")
value = trace.Argument.Argument
case trace.Command != nil:
path = append(path, trace.Command.Name)
case trace.Flag != nil:
value = trace.Flag.Value
case trace.Positional != nil:
path = append(path, "<"+trace.Positional.Name+">")
value = trace.Positional
default:
panic("unsupported path ?!")
}
if value != nil {
value.Apply(c.getValue(value))
}
}
return strings.Join(path, " "), nil
}
func flipBoolValue(value reflect.Value) error {
if value.Kind() == reflect.Bool {
value.SetBool(!value.Bool())
return nil
}
if value.Kind() == reflect.Ptr {
if !value.IsNil() {
return flipBoolValue(value.Elem())
}
return nil
}
return fmt.Errorf("cannot negate a value of %s", value.Type().String())
}
func (c *Context) parseFlag(flags []*Flag, match string) (err error) {
candidates := []string{}
for _, flag := range flags {
long := "--" + flag.Name
matched := long == match
candidates = append(candidates, long)
if flag.Short != 0 {
short := "-" + string(flag.Short)
matched = matched || (short == match)
candidates = append(candidates, short)
}
for _, alias := range flag.Aliases {
alias = "--" + alias
matched = matched || (alias == match)
candidates = append(candidates, alias)
}
neg := "--no-" + flag.Name
if !matched && !(match == neg && flag.Tag.Negatable) {
continue
}
// Found a matching flag.
c.scan.Pop()
if match == neg && flag.Tag.Negatable {
flag.Negated = true
}
err := flag.Parse(c.scan, c.getValue(flag.Value))
if err != nil {
var expected *expectedError
if errors.As(err, &expected) && expected.token.InferredType().IsAny(FlagToken, ShortFlagToken) {
return fmt.Errorf("%s; perhaps try %s=%q?", err.Error(), flag.ShortSummary(), expected.token)
}
return err
}
if flag.Negated {
value := c.getValue(flag.Value)
err := flipBoolValue(value)
if err != nil {
return err
}
flag.Value.Apply(value)
}
c.Path = append(c.Path, &Path{Flag: flag})
return nil
}
return findPotentialCandidates(match, candidates, "unknown flag %s", match)
}
// Call an arbitrary function filling arguments with bound values.
func (c *Context) Call(fn any, binds ...interface{}) (out []interface{}, err error) {
fv := reflect.ValueOf(fn)
bindings := c.Kong.bindings.clone().add(binds...).add(c).merge(c.bindings) //nolint:govet
return callAnyFunction(fv, bindings)
}
// RunNode calls the Run() method on an arbitrary node.
//
// This is useful in conjunction with Visit(), for dynamically running commands.
//
// Any passed values will be bindable to arguments of the target Run() method. Additionally,
// all parent nodes in the command structure will be bound.
func (c *Context) RunNode(node *Node, binds ...interface{}) (err error) {
type targetMethod struct {
node *Node
method reflect.Value
binds bindings
}
methodBinds := c.Kong.bindings.clone().add(binds...).add(c).merge(c.bindings)
methods := []targetMethod{}
for i := 0; node != nil; i, node = i+1, node.Parent {
method := getMethod(node.Target, "Run")
methodBinds = methodBinds.clone()
for p := node; p != nil; p = p.Parent {
methodBinds = methodBinds.add(p.Target.Addr().Interface())
}
if method.IsValid() {
methods = append(methods, targetMethod{node, method, methodBinds})
}
}
if len(methods) == 0 {
return fmt.Errorf("no Run() method found in hierarchy of %s", c.Selected().Summary())
}
_, err = c.Apply()
if err != nil {
return err
}
for _, method := range methods {
if err = callFunction(method.method, method.binds); err != nil {
return err
}
}
return nil
}
// Run executes the Run() method on the selected command, which must exist.
//
// Any passed values will be bindable to arguments of the target Run() method. Additionally,
// all parent nodes in the command structure will be bound.
func (c *Context) Run(binds ...interface{}) (err error) {
node := c.Selected()
if node == nil {
if len(c.Path) > 0 {
selected := c.Path[0].Node()
if selected.Type == ApplicationNode {
method := getMethod(selected.Target, "Run")
if method.IsValid() {
return c.RunNode(selected, binds...)
}
}
}
return fmt.Errorf("no command selected")
}
return c.RunNode(node, binds...)
}
// PrintUsage to Kong's stdout.
//
// If summary is true, a summarised version of the help will be output.
func (c *Context) PrintUsage(summary bool) error {
options := c.helpOptions
options.Summary = summary
return c.help(options, c)
}
func checkMissingFlags(flags []*Flag) error {
xorGroupSet := map[string]bool{}
xorGroup := map[string][]string{}
missing := []string{}
for _, flag := range flags {
if flag.Set {
for _, xor := range flag.Xor {
xorGroupSet[xor] = true
}
}
if !flag.Required || flag.Set {
continue
}
if len(flag.Xor) > 0 {
for _, xor := range flag.Xor {
if xorGroupSet[xor] {
continue
}
xorGroup[xor] = append(xorGroup[xor], flag.Summary())
}
} else {
missing = append(missing, flag.Summary())
}
}
for xor, flags := range xorGroup {
if !xorGroupSet[xor] && len(flags) > 1 {
missing = append(missing, strings.Join(flags, " or "))
}
}
if len(missing) == 0 {
return nil
}
sort.Strings(missing)
return fmt.Errorf("missing flags: %s", strings.Join(missing, ", "))
}
func checkMissingChildren(node *Node) error {
missing := []string{}
missingArgs := []string{}
for _, arg := range node.Positional {
if arg.Required && !arg.Set {
missingArgs = append(missingArgs, arg.Summary())
}
}
if len(missingArgs) > 0 {
missing = append(missing, strconv.Quote(strings.Join(missingArgs, " ")))
}
for _, child := range node.Children {
if child.Hidden {
continue
}
if child.Argument != nil {
if !child.Argument.Required {
continue
}
missing = append(missing, strconv.Quote(child.Summary()))
} else {
missing = append(missing, strconv.Quote(child.Name))
}
}
if len(missing) == 0 {
return nil
}
if len(missing) > 5 {
missing = append(missing[:5], "...")
}
if len(missing) == 1 {
return fmt.Errorf("expected %s", missing[0])
}
return fmt.Errorf("expected one of %s", strings.Join(missing, ", "))
}
// If we're missing any positionals and they're required, return an error.
func checkMissingPositionals(positional int, values []*Value) error {
// All the positionals are in.
if positional >= len(values) {
return nil
}
// We're low on supplied positionals, but the missing one is optional.
if !values[positional].Required {
return nil
}
missing := []string{}
for ; positional < len(values); positional++ {
arg := values[positional]
// TODO(aat): Fix hardcoding of these env checks all over the place :\
if len(arg.Tag.Envs) != 0 {
if atLeastOneEnvSet(arg.Tag.Envs) {
continue
}
}
missing = append(missing, "<"+arg.Name+">")
}
if len(missing) == 0 {
return nil
}
return fmt.Errorf("missing positional arguments %s", strings.Join(missing, " "))
}
func checkEnum(value *Value, target reflect.Value) error {
switch target.Kind() {
case reflect.Slice, reflect.Array:
for i := 0; i < target.Len(); i++ {
if err := checkEnum(value, target.Index(i)); err != nil {
return err
}
}
return nil
case reflect.Map, reflect.Struct:
return errors.New("enum can only be applied to a slice or value")
case reflect.Ptr:
if target.IsNil() {
return nil
}
return checkEnum(value, target.Elem())
default:
enumSlice := value.EnumSlice()
v := fmt.Sprintf("%v", target)
enums := []string{}
for _, enum := range enumSlice {
if enum == v {
return nil
}
enums = append(enums, fmt.Sprintf("%q", enum))
}
return fmt.Errorf("%s must be one of %s but got %q", value.ShortSummary(), strings.Join(enums, ","), target.Interface())
}
}
func checkPassthroughArg(target reflect.Value) bool {
typ := target.Type()
switch typ.Kind() {
case reflect.Slice:
return typ.Elem().Kind() == reflect.String
default:
return false
}
}
func checkXorDuplicates(paths []*Path) error {
for _, path := range paths {
seen := map[string]*Flag{}
for _, flag := range path.Flags {
if !flag.Set {
continue
}
for _, xor := range flag.Xor {
if seen[xor] != nil {
return fmt.Errorf("--%s and --%s can't be used together", seen[xor].Name, flag.Name)
}
seen[xor] = flag
}
}
}
return nil
}
func findPotentialCandidates(needle string, haystack []string, format string, args ...interface{}) error {
if len(haystack) == 0 {
return fmt.Errorf(format, args...)
}
closestCandidates := []string{}
for _, candidate := range haystack {
if strings.HasPrefix(candidate, needle) || levenshtein(candidate, needle) <= 2 {
closestCandidates = append(closestCandidates, fmt.Sprintf("%q", candidate))
}
}
prefix := fmt.Sprintf(format, args...)
if len(closestCandidates) == 1 {
return fmt.Errorf("%s, did you mean %s?", prefix, closestCandidates[0])
} else if len(closestCandidates) > 1 {
return fmt.Errorf("%s, did you mean one of %s?", prefix, strings.Join(closestCandidates, ", "))
}
return fmt.Errorf("%s", prefix)
}
type validatable interface{ Validate() error }
func isValidatable(v reflect.Value) validatable {
if !v.IsValid() || (v.Kind() == reflect.Ptr || v.Kind() == reflect.Slice || v.Kind() == reflect.Map) && v.IsNil() {
return nil
}
if validate, ok := v.Interface().(validatable); ok {
return validate
}
if v.CanAddr() {
return isValidatable(v.Addr())
}
return nil
}
func atLeastOneEnvSet(envs []string) bool {
for _, env := range envs {
if _, ok := os.LookupEnv(env); ok {
return true
}
}
return false
}
kong-0.9.0/defaults.go 0000664 0000000 0000000 00000000615 14572527075 0014634 0 ustar 00root root 0000000 0000000 package kong
// ApplyDefaults if they are not already set.
func ApplyDefaults(target interface{}, options ...Option) error {
app, err := New(target, options...)
if err != nil {
return err
}
ctx, err := Trace(app, nil)
if err != nil {
return err
}
err = ctx.Resolve()
if err != nil {
return err
}
if err = ctx.ApplyDefaults(); err != nil {
return err
}
return ctx.Validate()
}
kong-0.9.0/defaults_test.go 0000664 0000000 0000000 00000001306 14572527075 0015671 0 ustar 00root root 0000000 0000000 package kong
import (
"testing"
"time"
"github.com/alecthomas/assert/v2"
)
func TestApplyDefaults(t *testing.T) {
type CLI struct {
Str string `default:"str"`
Duration time.Duration `default:"30s"`
}
tests := []struct {
name string
target CLI
expected CLI
}{
{name: "DefaultsWhenNotSet",
expected: CLI{Str: "str", Duration: time.Second * 30}},
{name: "PartiallySetDefaults",
target: CLI{Duration: time.Second},
expected: CLI{Str: "str", Duration: time.Second}},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
err := ApplyDefaults(&tt.target)
assert.NoError(t, err)
assert.Equal(t, tt.expected, tt.target)
})
}
}
kong-0.9.0/doc.go 0000664 0000000 0000000 00000001523 14572527075 0013571 0 ustar 00root root 0000000 0000000 // Package kong aims to support arbitrarily complex command-line structures with as little developer effort as possible.
//
// Here's an example:
//
// shell rm [-f] [-r] ...
// shell ls [ ...]
//
// This can be represented by the following command-line structure:
//
// package main
//
// import "github.com/alecthomas/kong"
//
// var CLI struct {
// Rm struct {
// Force bool `short:"f" help:"Force removal."`
// Recursive bool `short:"r" help:"Recursively remove files."`
//
// Paths []string `arg help:"Paths to remove." type:"path"`
// } `cmd help:"Remove files."`
//
// Ls struct {
// Paths []string `arg optional help:"Paths to list." type:"path"`
// } `cmd help:"List paths."`
// }
//
// func main() {
// kong.Parse(&CLI)
// }
//
// See https://github.com/alecthomas/kong for details.
package kong
kong-0.9.0/error.go 0000664 0000000 0000000 00000000445 14572527075 0014157 0 ustar 00root root 0000000 0000000 package kong
// ParseError is the error type returned by Kong.Parse().
//
// It contains the parse Context that triggered the error.
type ParseError struct {
error
Context *Context
}
// Unwrap returns the original cause of the error.
func (p *ParseError) Unwrap() error { return p.error }
kong-0.9.0/global.go 0000664 0000000 0000000 00000000466 14572527075 0014271 0 ustar 00root root 0000000 0000000 package kong
import (
"os"
)
// Parse constructs a new parser and parses the default command-line.
func Parse(cli interface{}, options ...Option) *Context {
parser, err := New(cli, options...)
if err != nil {
panic(err)
}
ctx, err := parser.Parse(os.Args[1:])
parser.FatalIfErrorf(err)
return ctx
}
kong-0.9.0/global_test.go 0000664 0000000 0000000 00000000772 14572527075 0015330 0 ustar 00root root 0000000 0000000 package kong
import (
"os"
"testing"
"github.com/alecthomas/assert/v2"
)
func TestParseHandlingBadBuild(t *testing.T) {
var cli struct {
Enabled bool `kong:"fail='"`
}
args := os.Args
defer func() {
os.Args = args
}()
os.Args = []string{os.Args[0], "hi"}
defer func() {
if r := recover(); r != nil {
assert.Equal(t, "fail=' is not quoted properly", r.(error).Error()) //nolint
}
}()
Parse(&cli, Exit(func(_ int) { panic("exiting") }))
t.Fatal("we were expecting a panic")
}
kong-0.9.0/go.mod 0000664 0000000 0000000 00000000274 14572527075 0013605 0 ustar 00root root 0000000 0000000 module github.com/alecthomas/kong
require (
github.com/alecthomas/assert/v2 v2.6.0
github.com/alecthomas/repr v0.4.0
)
require github.com/hexops/gotextdiff v1.0.3 // indirect
go 1.18
kong-0.9.0/go.sum 0000664 0000000 0000000 00000002044 14572527075 0013627 0 ustar 00root root 0000000 0000000 github.com/alecthomas/assert/v2 v2.4.1 h1:mwPZod/d35nlaCppr6sFP0rbCL05WH9fIo7lvsf47zo=
github.com/alecthomas/assert/v2 v2.4.1/go.mod h1:fw5suVxB+wfYJ3291t0hRTqtGzFYdSwstnRQdaQx2DM=
github.com/alecthomas/assert/v2 v2.5.0 h1:OJKYg53BQx06/bMRBSPDCO49CbCDNiUQXwdoNrt6x5w=
github.com/alecthomas/assert/v2 v2.5.0/go.mod h1:fw5suVxB+wfYJ3291t0hRTqtGzFYdSwstnRQdaQx2DM=
github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU=
github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/repr v0.3.0 h1:NeYzUPfjjlqHY4KtzgKJiWd6sVq2eNUPTi34PiFGjY8=
github.com/alecthomas/repr v0.3.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
kong-0.9.0/guesswidth.go 0000664 0000000 0000000 00000000361 14572527075 0015211 0 ustar 00root root 0000000 0000000 //go:build appengine || (!linux && !freebsd && !darwin && !dragonfly && !netbsd && !openbsd)
// +build appengine !linux,!freebsd,!darwin,!dragonfly,!netbsd,!openbsd
package kong
import "io"
func guessWidth(w io.Writer) int {
return 80
}
kong-0.9.0/guesswidth_unix.go 0000664 0000000 0000000 00000001575 14572527075 0016264 0 ustar 00root root 0000000 0000000 //go:build (!appengine && linux) || freebsd || darwin || dragonfly || netbsd || openbsd
// +build !appengine,linux freebsd darwin dragonfly netbsd openbsd
package kong
import (
"io"
"os"
"strconv"
"syscall"
"unsafe"
)
func guessWidth(w io.Writer) int {
// check if COLUMNS env is set to comply with
// http://pubs.opengroup.org/onlinepubs/009604499/basedefs/xbd_chap08.html
colsStr := os.Getenv("COLUMNS")
if colsStr != "" {
if cols, err := strconv.Atoi(colsStr); err == nil {
return cols
}
}
if t, ok := w.(*os.File); ok {
fd := t.Fd()
var dimensions [4]uint16
if _, _, err := syscall.Syscall6(
syscall.SYS_IOCTL,
uintptr(fd), //nolint: unconvert
uintptr(syscall.TIOCGWINSZ),
uintptr(unsafe.Pointer(&dimensions)), //nolint: gas
0, 0, 0,
); err == 0 {
if dimensions[1] == 0 {
return 80
}
return int(dimensions[1])
}
}
return 80
}
kong-0.9.0/help.go 0000664 0000000 0000000 00000034120 14572527075 0013753 0 ustar 00root root 0000000 0000000 package kong
import (
"bytes"
"fmt"
"go/doc"
"io"
"strings"
)
const (
defaultIndent = 2
defaultColumnPadding = 4
)
// Help flag.
type helpValue bool
func (h helpValue) BeforeReset(ctx *Context) error {
options := ctx.Kong.helpOptions
options.Summary = false
err := ctx.Kong.help(options, ctx)
if err != nil {
return err
}
ctx.Kong.Exit(0)
return nil
}
// HelpOptions for HelpPrinters.
type HelpOptions struct {
// Don't print top-level usage summary.
NoAppSummary bool
// Write a one-line summary of the context.
Summary bool
// Write help in a more compact, but still fully-specified, form.
Compact bool
// Tree writes command chains in a tree structure instead of listing them separately.
Tree bool
// Place the flags after the commands listing.
FlagsLast bool
// Indenter modulates the given prefix for the next layer in the tree view.
// The following exported templates can be used: kong.SpaceIndenter, kong.LineIndenter, kong.TreeIndenter
// The kong.SpaceIndenter will be used by default.
Indenter HelpIndenter
// Don't show the help associated with subcommands
NoExpandSubcommands bool
// Clamp the help wrap width to a value smaller than the terminal width.
// If this is set to a non-positive number, the terminal width is used; otherwise,
// the min of this value or the terminal width is used.
WrapUpperBound int
}
// Apply options to Kong as a configuration option.
func (h HelpOptions) Apply(k *Kong) error {
k.helpOptions = h
return nil
}
// HelpProvider can be implemented by commands/args to provide detailed help.
type HelpProvider interface {
// This string is formatted by go/doc and thus has the same formatting rules.
Help() string
}
// PlaceHolderProvider can be implemented by mappers to provide custom placeholder text.
type PlaceHolderProvider interface {
PlaceHolder(flag *Flag) string
}
// HelpIndenter is used to indent new layers in the help tree.
type HelpIndenter func(prefix string) string
// HelpPrinter is used to print context-sensitive help.
type HelpPrinter func(options HelpOptions, ctx *Context) error
// HelpValueFormatter is used to format the help text of flags and positional arguments.
type HelpValueFormatter func(value *Value) string
// DefaultHelpValueFormatter is the default HelpValueFormatter.
func DefaultHelpValueFormatter(value *Value) string {
if len(value.Tag.Envs) == 0 || HasInterpolatedVar(value.OrigHelp, "env") {
return value.Help
}
suffix := "(" + formatEnvs(value.Tag.Envs) + ")"
switch {
case strings.HasSuffix(value.Help, "."):
return value.Help[:len(value.Help)-1] + " " + suffix + "."
case value.Help == "":
return suffix
default:
return value.Help + " " + suffix
}
}
// DefaultShortHelpPrinter is the default HelpPrinter for short help on error.
func DefaultShortHelpPrinter(options HelpOptions, ctx *Context) error {
w := newHelpWriter(ctx, options)
cmd := ctx.Selected()
app := ctx.Model
if cmd == nil {
w.Printf("Usage: %s%s", app.Name, app.Summary())
w.Printf(`Run "%s --help" for more information.`, app.Name)
} else {
w.Printf("Usage: %s %s", app.Name, cmd.Summary())
w.Printf(`Run "%s --help" for more information.`, cmd.FullPath())
}
return w.Write(ctx.Stdout)
}
// DefaultHelpPrinter is the default HelpPrinter.
func DefaultHelpPrinter(options HelpOptions, ctx *Context) error {
if ctx.Empty() {
options.Summary = false
}
w := newHelpWriter(ctx, options)
selected := ctx.Selected()
if selected == nil {
printApp(w, ctx.Model)
} else {
printCommand(w, ctx.Model, selected)
}
return w.Write(ctx.Stdout)
}
func printApp(w *helpWriter, app *Application) {
if !w.NoAppSummary {
w.Printf("Usage: %s%s", app.Name, app.Summary())
}
printNodeDetail(w, app.Node, true)
cmds := app.Leaves(true)
if len(cmds) > 0 && app.HelpFlag != nil {
w.Print("")
if w.Summary {
w.Printf(`Run "%s --help" for more information.`, app.Name)
} else {
w.Printf(`Run "%s --help" for more information on a command.`, app.Name)
}
}
}
func printCommand(w *helpWriter, app *Application, cmd *Command) {
if !w.NoAppSummary {
w.Printf("Usage: %s %s", app.Name, cmd.Summary())
}
printNodeDetail(w, cmd, true)
if w.Summary && app.HelpFlag != nil {
w.Print("")
w.Printf(`Run "%s --help" for more information.`, cmd.FullPath())
}
}
func printNodeDetail(w *helpWriter, node *Node, hide bool) {
if node.Help != "" {
w.Print("")
w.Wrap(node.Help)
}
if w.Summary {
return
}
if node.Detail != "" {
w.Print("")
w.Wrap(node.Detail)
}
if len(node.Positional) > 0 {
w.Print("")
w.Print("Arguments:")
writePositionals(w.Indent(), node.Positional)
}
printFlags := func() {
if flags := node.AllFlags(true); len(flags) > 0 {
groupedFlags := collectFlagGroups(flags)
for _, group := range groupedFlags {
w.Print("")
if group.Metadata.Title != "" {
w.Wrap(group.Metadata.Title)
}
if group.Metadata.Description != "" {
w.Indent().Wrap(group.Metadata.Description)
w.Print("")
}
writeFlags(w.Indent(), group.Flags)
}
}
}
if !w.FlagsLast {
printFlags()
}
var cmds []*Node
if w.NoExpandSubcommands {
cmds = node.Children
} else {
cmds = node.Leaves(hide)
}
if len(cmds) > 0 {
iw := w.Indent()
if w.Tree {
w.Print("")
w.Print("Commands:")
writeCommandTree(iw, node)
} else {
groupedCmds := collectCommandGroups(cmds)
for _, group := range groupedCmds {
w.Print("")
if group.Metadata.Title != "" {
w.Wrap(group.Metadata.Title)
}
if group.Metadata.Description != "" {
w.Indent().Wrap(group.Metadata.Description)
w.Print("")
}
if w.Compact {
writeCompactCommandList(group.Commands, iw)
} else {
writeCommandList(group.Commands, iw)
}
}
}
}
if w.FlagsLast {
printFlags()
}
}
func writeCommandList(cmds []*Node, iw *helpWriter) {
for i, cmd := range cmds {
if cmd.Hidden {
continue
}
printCommandSummary(iw, cmd)
if i != len(cmds)-1 {
iw.Print("")
}
}
}
func writeCompactCommandList(cmds []*Node, iw *helpWriter) {
rows := [][2]string{}
for _, cmd := range cmds {
if cmd.Hidden {
continue
}
rows = append(rows, [2]string{cmd.Path(), cmd.Help})
}
writeTwoColumns(iw, rows)
}
func writeCommandTree(w *helpWriter, node *Node) {
rows := make([][2]string, 0, len(node.Children)*2)
for i, cmd := range node.Children {
if cmd.Hidden {
continue
}
rows = append(rows, w.CommandTree(cmd, "")...)
if i != len(node.Children)-1 {
rows = append(rows, [2]string{"", ""})
}
}
writeTwoColumns(w, rows)
}
type helpFlagGroup struct {
Metadata *Group
Flags [][]*Flag
}
func collectFlagGroups(flags [][]*Flag) []helpFlagGroup {
// Group keys in order of appearance.
groups := []*Group{}
// Flags grouped by their group key.
flagsByGroup := map[string][][]*Flag{}
for _, levelFlags := range flags {
levelFlagsByGroup := map[string][]*Flag{}
for _, flag := range levelFlags {
key := ""
if flag.Group != nil {
key = flag.Group.Key
groupAlreadySeen := false
for _, group := range groups {
if key == group.Key {
groupAlreadySeen = true
break
}
}
if !groupAlreadySeen {
groups = append(groups, flag.Group)
}
}
levelFlagsByGroup[key] = append(levelFlagsByGroup[key], flag)
}
for key, flags := range levelFlagsByGroup {
flagsByGroup[key] = append(flagsByGroup[key], flags)
}
}
out := []helpFlagGroup{}
// Ungrouped flags are always displayed first.
if ungroupedFlags, ok := flagsByGroup[""]; ok {
out = append(out, helpFlagGroup{
Metadata: &Group{Title: "Flags:"},
Flags: ungroupedFlags,
})
}
for _, group := range groups {
out = append(out, helpFlagGroup{Metadata: group, Flags: flagsByGroup[group.Key]})
}
return out
}
type helpCommandGroup struct {
Metadata *Group
Commands []*Node
}
func collectCommandGroups(nodes []*Node) []helpCommandGroup {
// Groups in order of appearance.
groups := []*Group{}
// Nodes grouped by their group key.
nodesByGroup := map[string][]*Node{}
for _, node := range nodes {
key := ""
if group := node.ClosestGroup(); group != nil {
key = group.Key
if _, ok := nodesByGroup[key]; !ok {
groups = append(groups, group)
}
}
nodesByGroup[key] = append(nodesByGroup[key], node)
}
out := []helpCommandGroup{}
// Ungrouped nodes are always displayed first.
if ungroupedNodes, ok := nodesByGroup[""]; ok {
out = append(out, helpCommandGroup{
Metadata: &Group{Title: "Commands:"},
Commands: ungroupedNodes,
})
}
for _, group := range groups {
out = append(out, helpCommandGroup{Metadata: group, Commands: nodesByGroup[group.Key]})
}
return out
}
func printCommandSummary(w *helpWriter, cmd *Command) {
w.Print(cmd.Summary())
if cmd.Help != "" {
w.Indent().Wrap(cmd.Help)
}
}
type helpWriter struct {
indent string
width int
lines *[]string
helpFormatter HelpValueFormatter
HelpOptions
}
func newHelpWriter(ctx *Context, options HelpOptions) *helpWriter {
lines := []string{}
wrapWidth := guessWidth(ctx.Stdout)
if options.WrapUpperBound > 0 && wrapWidth > options.WrapUpperBound {
wrapWidth = options.WrapUpperBound
}
w := &helpWriter{
indent: "",
width: wrapWidth,
lines: &lines,
helpFormatter: ctx.Kong.helpFormatter,
HelpOptions: options,
}
return w
}
func (h *helpWriter) Printf(format string, args ...interface{}) {
h.Print(fmt.Sprintf(format, args...))
}
func (h *helpWriter) Print(text string) {
*h.lines = append(*h.lines, strings.TrimRight(h.indent+text, " "))
}
// Indent returns a new helpWriter indented by two characters.
func (h *helpWriter) Indent() *helpWriter {
return &helpWriter{indent: h.indent + " ", lines: h.lines, width: h.width - 2, HelpOptions: h.HelpOptions, helpFormatter: h.helpFormatter}
}
func (h *helpWriter) String() string {
return strings.Join(*h.lines, "\n")
}
func (h *helpWriter) Write(w io.Writer) error {
for _, line := range *h.lines {
_, err := io.WriteString(w, line+"\n")
if err != nil {
return err
}
}
return nil
}
func (h *helpWriter) Wrap(text string) {
w := bytes.NewBuffer(nil)
doc.ToText(w, strings.TrimSpace(text), "", " ", h.width)
for _, line := range strings.Split(strings.TrimSpace(w.String()), "\n") {
h.Print(line)
}
}
func writePositionals(w *helpWriter, args []*Positional) {
rows := [][2]string{}
for _, arg := range args {
rows = append(rows, [2]string{arg.Summary(), w.helpFormatter(arg)})
}
writeTwoColumns(w, rows)
}
func writeFlags(w *helpWriter, groups [][]*Flag) {
rows := [][2]string{}
haveShort := false
for _, group := range groups {
for _, flag := range group {
if flag.Short != 0 {
haveShort = true
break
}
}
}
for i, group := range groups {
if i > 0 {
rows = append(rows, [2]string{"", ""})
}
for _, flag := range group {
if !flag.Hidden {
rows = append(rows, [2]string{formatFlag(haveShort, flag), w.helpFormatter(flag.Value)})
}
}
}
writeTwoColumns(w, rows)
}
func writeTwoColumns(w *helpWriter, rows [][2]string) {
maxLeft := 375 * w.width / 1000
if maxLeft < 30 {
maxLeft = 30
}
// Find size of first column.
leftSize := 0
for _, row := range rows {
if c := len(row[0]); c > leftSize && c < maxLeft {
leftSize = c
}
}
offsetStr := strings.Repeat(" ", leftSize+defaultColumnPadding)
for _, row := range rows {
buf := bytes.NewBuffer(nil)
doc.ToText(buf, row[1], "", strings.Repeat(" ", defaultIndent), w.width-leftSize-defaultColumnPadding)
lines := strings.Split(strings.TrimRight(buf.String(), "\n"), "\n")
line := fmt.Sprintf("%-*s", leftSize, row[0])
if len(row[0]) < maxLeft {
line += fmt.Sprintf("%*s%s", defaultColumnPadding, "", lines[0])
lines = lines[1:]
}
w.Print(line)
for _, line := range lines {
w.Printf("%s%s", offsetStr, line)
}
}
}
// haveShort will be true if there are short flags present at all in the help. Useful for column alignment.
func formatFlag(haveShort bool, flag *Flag) string {
flagString := ""
name := flag.Name
isBool := flag.IsBool()
isCounter := flag.IsCounter()
if flag.Short != 0 {
if isBool && flag.Tag.Negatable {
flagString += fmt.Sprintf("-%c, --[no-]%s", flag.Short, name)
} else {
flagString += fmt.Sprintf("-%c, --%s", flag.Short, name)
}
} else {
if isBool && flag.Tag.Negatable {
if haveShort {
flagString = fmt.Sprintf(" --[no-]%s", name)
} else {
flagString = fmt.Sprintf("--[no-]%s", name)
}
} else {
if haveShort {
flagString += fmt.Sprintf(" --%s", name)
} else {
flagString += fmt.Sprintf("--%s", name)
}
}
}
if !isBool && !isCounter {
flagString += fmt.Sprintf("=%s", flag.FormatPlaceHolder())
}
return flagString
}
// CommandTree creates a tree with the given node name as root and its children's arguments and sub commands as leaves.
func (h *HelpOptions) CommandTree(node *Node, prefix string) (rows [][2]string) {
var nodeName string
switch node.Type {
default:
nodeName += prefix + node.Name
if len(node.Aliases) != 0 {
nodeName += fmt.Sprintf(" (%s)", strings.Join(node.Aliases, ","))
}
case ArgumentNode:
nodeName += prefix + "<" + node.Name + ">"
}
rows = append(rows, [2]string{nodeName, node.Help})
if h.Indenter == nil {
prefix = SpaceIndenter(prefix)
} else {
prefix = h.Indenter(prefix)
}
for _, arg := range node.Positional {
rows = append(rows, [2]string{prefix + arg.Summary(), arg.Help})
}
for _, subCmd := range node.Children {
if subCmd.Hidden {
continue
}
rows = append(rows, h.CommandTree(subCmd, prefix)...)
}
return
}
// SpaceIndenter adds a space indent to the given prefix.
func SpaceIndenter(prefix string) string {
return prefix + strings.Repeat(" ", defaultIndent)
}
// LineIndenter adds line points to every new indent.
func LineIndenter(prefix string) string {
if prefix == "" {
return "- "
}
return strings.Repeat(" ", defaultIndent) + prefix
}
// TreeIndenter adds line points to every new indent and vertical lines to every layer.
func TreeIndenter(prefix string) string {
if prefix == "" {
return "|- "
}
return "|" + strings.Repeat(" ", defaultIndent) + prefix
}
func formatEnvs(envs []string) string {
formatted := make([]string, len(envs))
for i := range envs {
formatted[i] = "$" + envs[i]
}
return strings.Join(formatted, ", ")
}
kong-0.9.0/help_test.go 0000664 0000000 0000000 00000054764 14572527075 0015032 0 ustar 00root root 0000000 0000000 package kong_test
import (
"bytes"
"fmt"
"strings"
"testing"
"github.com/alecthomas/assert/v2"
"github.com/alecthomas/kong"
)
func panicsTrue(t *testing.T, f func()) {
defer func() {
if value := recover(); value != nil {
if boolval, ok := value.(bool); !ok || !boolval {
t.Fatalf("expected panic with true but got %v", value)
}
}
}()
f()
t.Fatal("expected panic did not occur")
}
type threeArg struct {
RequiredThree bool `required`
Three string `arg`
}
func (threeArg) Help() string {
return `Detailed help provided through the HelpProvider interface.`
}
func TestHelpOptionalArgs(t *testing.T) {
var cli struct {
One string `arg:"" optional:"" help:"One optional arg."`
Two string `arg:"" optional:"" help:"Two optional arg."`
}
w := bytes.NewBuffer(nil)
exited := false
app := mustNew(t, &cli,
kong.Name("test-app"),
kong.Writers(w, w),
kong.Exit(func(int) {
exited = true
panic(true) // Panic to fake "exit".
}),
)
panicsTrue(t, func() {
_, err := app.Parse([]string{"--help"})
assert.NoError(t, err)
})
assert.True(t, exited)
expected := `Usage: test-app [ []] [flags]
Arguments:
[] One optional arg.
[] Two optional arg.
Flags:
-h, --help Show context-sensitive help.
`
assert.Equal(t, expected, w.String())
}
func TestHelp(t *testing.T) {
var cli struct {
String string `help:"A string flag."`
Bool bool `help:"A bool flag with very long help that wraps a lot and is verbose and is really verbose."`
Slice []string `help:"A slice of strings." placeholder:"STR"`
Map map[string]int `help:"A map of strings to ints."`
Required bool `required help:"A required flag."`
Sort bool `negatable short:"s" help:"Is sortable or not."`
One struct {
Flag string `help:"Nested flag."`
} `cmd help:"A subcommand."`
Two struct {
Flag string `help:"Nested flag under two."`
RequiredTwo bool `required`
Three threeArg `arg help:"Sub-sub-arg."`
Four struct {
} `cmd help:"Sub-sub-command."`
} `cmd help:"Another subcommand."`
}
w := bytes.NewBuffer(nil)
exited := false
app := mustNew(t, &cli,
kong.Name("test-app"),
kong.Description("A test app."),
kong.Writers(w, w),
kong.Exit(func(int) {
exited = true
panic(true) // Panic to fake "exit".
}),
)
t.Run("Full", func(t *testing.T) {
panicsTrue(t, func() {
_, err := app.Parse([]string{"--help"})
assert.NoError(t, err)
})
assert.True(t, exited)
expected := `Usage: test-app --required [flags]
A test app.
Flags:
-h, --help Show context-sensitive help.
--string=STRING A string flag.
--bool A bool flag with very long help that wraps a lot
and is verbose and is really verbose.
--slice=STR,... A slice of strings.
--map=KEY=VALUE;... A map of strings to ints.
--required A required flag.
-s, --[no-]sort Is sortable or not.
Commands:
one --required [flags]
A subcommand.
two --required --required-two --required-three [flags]
Sub-sub-arg.
two four --required --required-two [flags]
Sub-sub-command.
Run "test-app --help" for more information on a command.
`
t.Log(w.String())
t.Log(expected)
assert.Equal(t, expected, w.String())
})
t.Run("Selected", func(t *testing.T) {
exited = false
w.Truncate(0)
panicsTrue(t, func() {
_, err := app.Parse([]string{"two", "hello", "--help"})
assert.NoError(t, err)
})
assert.True(t, exited)
expected := `Usage: test-app two --required --required-two --required-three [flags]
Sub-sub-arg.
Detailed help provided through the HelpProvider interface.
Flags:
-h, --help Show context-sensitive help.
--string=STRING A string flag.
--bool A bool flag with very long help that wraps a lot
and is verbose and is really verbose.
--slice=STR,... A slice of strings.
--map=KEY=VALUE;... A map of strings to ints.
--required A required flag.
-s, --[no-]sort Is sortable or not.
--flag=STRING Nested flag under two.
--required-two
--required-three
`
t.Log(expected)
t.Log(w.String())
assert.Equal(t, expected, w.String())
})
}
func TestFlagsLast(t *testing.T) {
var cli struct {
String string `help:"A string flag."`
Bool bool `help:"A bool flag with very long help that wraps a lot and is verbose and is really verbose."`
Slice []string `help:"A slice of strings." placeholder:"STR"`
Map map[string]int `help:"A map of strings to ints."`
Required bool `required help:"A required flag."`
One struct {
Flag string `help:"Nested flag."`
} `cmd help:"A subcommand."`
Two struct {
Flag string `help:"Nested flag under two."`
RequiredTwo bool `required`
Three threeArg `arg help:"Sub-sub-arg."`
Four struct {
} `cmd help:"Sub-sub-command."`
} `cmd help:"Another subcommand."`
}
w := bytes.NewBuffer(nil)
exited := false
app := mustNew(t, &cli,
kong.Name("test-app"),
kong.Description("A test app."),
kong.HelpOptions{
FlagsLast: true,
},
kong.Writers(w, w),
kong.Exit(func(int) {
exited = true
panic(true) // Panic to fake "exit".
}),
)
t.Run("Full", func(t *testing.T) {
panicsTrue(t, func() {
_, err := app.Parse([]string{"--help"})
assert.NoError(t, err)
})
assert.True(t, exited)
expected := `Usage: test-app --required [flags]
A test app.
Commands:
one --required [flags]
A subcommand.
two --required --required-two --required-three [flags]
Sub-sub-arg.
two four --required --required-two [flags]
Sub-sub-command.
Flags:
-h, --help Show context-sensitive help.
--string=STRING A string flag.
--bool A bool flag with very long help that wraps a lot
and is verbose and is really verbose.
--slice=STR,... A slice of strings.
--map=KEY=VALUE;... A map of strings to ints.
--required A required flag.
Run "test-app --help" for more information on a command.
`
t.Log(w.String())
t.Log(expected)
assert.Equal(t, expected, w.String())
})
t.Run("Selected", func(t *testing.T) {
exited = false
w.Truncate(0)
panicsTrue(t, func() {
_, err := app.Parse([]string{"two", "hello", "--help"})
assert.NoError(t, err)
})
assert.True(t, exited)
expected := `Usage: test-app two --required --required-two --required-three [flags]
Sub-sub-arg.
Detailed help provided through the HelpProvider interface.
Flags:
-h, --help Show context-sensitive help.
--string=STRING A string flag.
--bool A bool flag with very long help that wraps a lot
and is verbose and is really verbose.
--slice=STR,... A slice of strings.
--map=KEY=VALUE;... A map of strings to ints.
--required A required flag.
--flag=STRING Nested flag under two.
--required-two
--required-three
`
t.Log(expected)
t.Log(w.String())
assert.Equal(t, expected, w.String())
})
}
func TestHelpTree(t *testing.T) {
var cli struct {
One struct {
Thing struct {
Arg string `arg help:"argument"`
} `cmd help:"subcommand thing"`
Other struct {
Other string `arg help:"other arg"`
} `arg help:"subcommand other"`
} `cmd help:"subcommand one" group:"Group A" aliases:"un,uno"` // Groups are ignored in trees
Two struct {
Three threeArg `arg help:"Sub-sub-arg."`
Four struct {
} `cmd help:"Sub-sub-command." aliases:"for,fore"`
} `cmd help:"Another subcommand."`
}
w := bytes.NewBuffer(nil)
exited := false
app := mustNew(t, &cli,
kong.Name("test-app"),
kong.Description("A test app."),
kong.Writers(w, w),
kong.ConfigureHelp(kong.HelpOptions{
Tree: true,
Indenter: kong.LineIndenter,
}),
kong.Exit(func(int) {
exited = true
panic(true) // Panic to fake "exit".
}),
)
t.Run("Full", func(t *testing.T) {
panicsTrue(t, func() {
_, err := app.Parse([]string{"--help"})
assert.NoError(t, err)
})
assert.True(t, exited)
expected := `Usage: test-app [flags]
A test app.
Flags:
-h, --help Show context-sensitive help.
Commands:
one (un,uno) subcommand one
- thing subcommand thing
- argument
- subcommand other
two Another subcommand.
- Sub-sub-arg.
- four (for,fore) Sub-sub-command.
Run "test-app --help" for more information on a command.
`
if expected != w.String() {
t.Errorf("help command returned:\n%v\n\nwant:\n%v", w.String(), expected)
}
assert.Equal(t, expected, w.String())
})
t.Run("Selected", func(t *testing.T) {
exited = false
w.Truncate(0)
panicsTrue(t, func() {
_, err := app.Parse([]string{"one", "--help"})
assert.NoError(t, err)
})
assert.True(t, exited)
expected := `Usage: test-app one (un,uno) [flags]
subcommand one
Flags:
-h, --help Show context-sensitive help.
Commands:
thing subcommand thing
- argument
subcommand other
`
if expected != w.String() {
t.Errorf("help command returned:\n%v\n\nwant:\n%v", w.String(), expected)
}
assert.Equal(t, expected, w.String())
})
}
func TestHelpCompactNoExpand(t *testing.T) {
var cli struct {
One struct {
Thing struct {
Arg string `arg help:"argument"`
} `cmd help:"subcommand thing"`
Other struct {
Other string `arg help:"other arg"`
} `arg help:"subcommand other"`
} `cmd help:"subcommand one" group:"Group A" aliases:"un,uno"` // Groups are ignored in trees
Two struct {
Three threeArg `arg help:"Sub-sub-arg."`
Four struct {
} `cmd help:"Sub-sub-command." aliases:"for,fore"`
} `cmd help:"Another subcommand."`
}
w := bytes.NewBuffer(nil)
exited := false
app := mustNew(t, &cli,
kong.Name("test-app"),
kong.Description("A test app."),
kong.Writers(w, w),
kong.ConfigureHelp(kong.HelpOptions{
Compact: true,
NoExpandSubcommands: true,
}),
kong.Exit(func(int) {
exited = true
panic(true) // Panic to fake "exit".
}),
)
t.Run("Full", func(t *testing.T) {
panicsTrue(t, func() {
_, err := app.Parse([]string{"--help"})
assert.NoError(t, err)
})
assert.True(t, exited)
expected := `Usage: test-app [flags]
A test app.
Flags:
-h, --help Show context-sensitive help.
Commands:
two Another subcommand.
Group A
one (un,uno) subcommand one
Run "test-app --help" for more information on a command.
`
if expected != w.String() {
t.Errorf("help command returned:\n%v\n\nwant:\n%v", w.String(), expected)
}
assert.Equal(t, expected, w.String())
})
t.Run("Selected", func(t *testing.T) {
exited = false
w.Truncate(0)
panicsTrue(t, func() {
_, err := app.Parse([]string{"one", "--help"})
assert.NoError(t, err)
})
assert.True(t, exited)
expected := `Usage: test-app one (un,uno) [flags]
subcommand one
Flags:
-h, --help Show context-sensitive help.
Group A
one (un,uno) thing subcommand thing
one (un,uno) subcommand other
`
if expected != w.String() {
t.Errorf("help command returned:\n%v\n\nwant:\n%v", w.String(), expected)
}
assert.Equal(t, expected, w.String())
})
}
func TestEnvarAutoHelp(t *testing.T) {
var cli struct {
Flag string `env:"FLAG" help:"A flag."`
}
w := &strings.Builder{}
p := mustNew(t, &cli, kong.Writers(w, w), kong.Exit(func(int) {}))
_, err := p.Parse([]string{"--help"})
assert.NoError(t, err)
assert.Contains(t, w.String(), "A flag ($FLAG).")
}
func TestMultipleEnvarAutoHelp(t *testing.T) {
var cli struct {
Flag string `env:"FLAG1,FLAG2" help:"A flag."`
}
w := &strings.Builder{}
p := mustNew(t, &cli, kong.Writers(w, w), kong.Exit(func(int) {}))
_, err := p.Parse([]string{"--help"})
assert.NoError(t, err)
assert.Contains(t, w.String(), "A flag ($FLAG1, $FLAG2).")
}
//nolint:dupl // false positive
func TestEnvarAutoHelpWithEnvPrefix(t *testing.T) {
type Anonymous struct {
Flag string `env:"FLAG" help:"A flag."`
Other string `help:"A different flag."`
}
var cli struct {
Anonymous `envprefix:"ANON_"`
}
w := &strings.Builder{}
p := mustNew(t, &cli, kong.Writers(w, w), kong.Exit(func(int) {}))
_, err := p.Parse([]string{"--help"})
assert.NoError(t, err)
assert.Contains(t, w.String(), "A flag ($ANON_FLAG).")
assert.Contains(t, w.String(), "A different flag.")
}
//nolint:dupl // false positive
func TestMultipleEnvarAutoHelpWithEnvPrefix(t *testing.T) {
type Anonymous struct {
Flag string `env:"FLAG1,FLAG2" help:"A flag."`
Other string `help:"A different flag."`
}
var cli struct {
Anonymous `envprefix:"ANON_"`
}
w := &strings.Builder{}
p := mustNew(t, &cli, kong.Writers(w, w), kong.Exit(func(int) {}))
_, err := p.Parse([]string{"--help"})
assert.NoError(t, err)
assert.Contains(t, w.String(), "A flag ($ANON_FLAG1, $ANON_FLAG2).")
assert.Contains(t, w.String(), "A different flag.")
}
//nolint:dupl // false positive
func TestCustomValueFormatter(t *testing.T) {
var cli struct {
Flag string `env:"FLAG" help:"A flag."`
}
w := &strings.Builder{}
p := mustNew(t, &cli,
kong.Writers(w, w),
kong.Exit(func(int) {}),
kong.ValueFormatter(func(value *kong.Value) string {
return value.Help
}),
)
_, err := p.Parse([]string{"--help"})
assert.NoError(t, err)
assert.Contains(t, w.String(), "A flag.")
}
//nolint:dupl // false positive
func TestMultipleCustomValueFormatter(t *testing.T) {
var cli struct {
Flag string `env:"FLAG1,FLAG2" help:"A flag."`
}
w := &strings.Builder{}
p := mustNew(t, &cli,
kong.Writers(w, w),
kong.Exit(func(int) {}),
kong.ValueFormatter(func(value *kong.Value) string {
return value.Help
}),
)
_, err := p.Parse([]string{"--help"})
assert.NoError(t, err)
assert.Contains(t, w.String(), "A flag.")
}
func TestAutoGroup(t *testing.T) {
var cli struct {
GroupedAString string `help:"A string flag grouped in A."`
FreeString string `help:"A non grouped string flag."`
GroupedBString string `help:"A string flag grouped in B."`
FreeBool bool `help:"A non grouped bool flag."`
GroupedABool bool `help:"A bool flag grouped in A."`
One struct {
Flag string `help:"Nested flag."`
// Group is inherited from the parent command
Thing struct {
Arg string `arg help:"argument"`
} `cmd help:"subcommand thing"`
Other struct {
Other string `arg help:"other arg"`
} `arg help:"subcommand other"`
// ... but a subcommand can override it
Stuff struct {
Stuff string `arg help:"argument"`
} `arg help:"subcommand stuff"`
} `cmd help:"A subcommand grouped in A."`
Two struct {
Grouped1String string `help:"A string flag grouped in 1."`
AFreeString string `help:"A non grouped string flag."`
Grouped2String string `help:"A string flag grouped in 2."`
AGroupedAString bool `help:"A string flag grouped in A."`
Grouped1Bool bool `help:"A bool flag grouped in 1."`
} `cmd help:"A non grouped subcommand."`
Four struct {
Flag string `help:"Nested flag."`
} `cmd help:"Another subcommand grouped in B."`
Three struct {
Flag string `help:"Nested flag."`
} `cmd help:"Another subcommand grouped in A."`
}
w := bytes.NewBuffer(nil)
app := mustNew(t, &cli,
kong.Writers(w, w),
kong.Exit(func(int) {}),
kong.AutoGroup(func(parent kong.Visitable, flag *kong.Flag) *kong.Group {
if node, ok := parent.(*kong.Node); ok {
return &kong.Group{
Key: node.Name,
Title: strings.Title(node.Name) + " flags:", //nolint
}
}
return nil
}),
)
_, _ = app.Parse([]string{"--help", "two"})
assert.Equal(t, `Usage: test two [flags]
A non grouped subcommand.
Flags:
-h, --help Show context-sensitive help.
--grouped-a-string=STRING A string flag grouped in A.
--free-string=STRING A non grouped string flag.
--grouped-b-string=STRING A string flag grouped in B.
--free-bool A non grouped bool flag.
--grouped-a-bool A bool flag grouped in A.
Two flags:
--grouped-1-string=STRING A string flag grouped in 1.
--a-free-string=STRING A non grouped string flag.
--grouped-2-string=STRING A string flag grouped in 2.
--a-grouped-a-string A string flag grouped in A.
--grouped-1-bool A bool flag grouped in 1.
`, w.String())
}
func TestHelpGrouping(t *testing.T) {
var cli struct {
GroupedAString string `help:"A string flag grouped in A." group:"Group A"`
FreeString string `help:"A non grouped string flag."`
GroupedBString string `help:"A string flag grouped in B." group:"Group B"`
FreeBool bool `help:"A non grouped bool flag."`
GroupedABool bool `help:"A bool flag grouped in A." group:"Group A"`
One struct {
Flag string `help:"Nested flag."`
// Group is inherited from the parent command
Thing struct {
Arg string `arg help:"argument"`
} `cmd help:"subcommand thing"`
Other struct {
Other string `arg help:"other arg"`
} `arg help:"subcommand other"`
// ... but a subcommand can override it
Stuff struct {
Stuff string `arg help:"argument"`
} `arg help:"subcommand stuff" group:"Group B"`
} `cmd help:"A subcommand grouped in A." group:"Group A"`
Two struct {
Grouped1String string `help:"A string flag grouped in 1." group:"Group 1"`
AFreeString string `help:"A non grouped string flag."`
Grouped2String string `help:"A string flag grouped in 2." group:"Group 2"`
AGroupedAString bool `help:"A string flag grouped in A." group:"Group A"`
Grouped1Bool bool `help:"A bool flag grouped in 1." group:"Group 1"`
} `cmd help:"A non grouped subcommand."`
Four struct {
Flag string `help:"Nested flag."`
} `cmd help:"Another subcommand grouped in B." group:"Group B"`
Three struct {
Flag string `help:"Nested flag."`
} `cmd help:"Another subcommand grouped in A." group:"Group A"`
}
w := bytes.NewBuffer(nil)
exited := false
app := mustNew(t, &cli,
kong.Name("test-app"),
kong.Description("A test app."),
kong.Groups{
"Group A": "Group title taken from the kong.ExplicitGroups option\nA group header",
"Group 1": "Another group title, this time without header",
"Unknown key": "",
},
kong.Writers(w, w),
kong.Exit(func(int) {
exited = true
panic(true) // Panic to fake "exit".
}),
)
t.Run("Full", func(t *testing.T) {
panicsTrue(t, func() {
_, err := app.Parse([]string{"--help"})
assert.True(t, exited)
assert.NoError(t, err)
})
expected := `Usage: test-app [flags]
A test app.
Flags:
-h, --help Show context-sensitive help.
--free-string=STRING A non grouped string flag.
--free-bool A non grouped bool flag.
Group title taken from the kong.ExplicitGroups option
A group header
--grouped-a-string=STRING A string flag grouped in A.
--grouped-a-bool A bool flag grouped in A.
Group B
--grouped-b-string=STRING A string flag grouped in B.
Commands:
two [flags]
A non grouped subcommand.
Group title taken from the kong.ExplicitGroups option
A group header
one thing [flags]
subcommand thing
one [flags]
subcommand other
three [flags]
Another subcommand grouped in A.
Group B
one [flags]
subcommand stuff
four [flags]
Another subcommand grouped in B.
Run "test-app --help" for more information on a command.
`
t.Log(w.String())
t.Log(expected)
assert.Equal(t, expected, w.String())
})
t.Run("Selected", func(t *testing.T) {
exited = false
w.Truncate(0)
panicsTrue(t, func() {
_, err := app.Parse([]string{"two", "--help"})
assert.NoError(t, err)
assert.True(t, exited)
})
expected := `Usage: test-app two [flags]
A non grouped subcommand.
Flags:
-h, --help Show context-sensitive help.
--free-string=STRING A non grouped string flag.
--free-bool A non grouped bool flag.
--a-free-string=STRING A non grouped string flag.
Group title taken from the kong.ExplicitGroups option
A group header
--grouped-a-string=STRING A string flag grouped in A.
--grouped-a-bool A bool flag grouped in A.
--a-grouped-a-string A string flag grouped in A.
Group B
--grouped-b-string=STRING A string flag grouped in B.
Another group title, this time without header
--grouped-1-string=STRING A string flag grouped in 1.
--grouped-1-bool A bool flag grouped in 1.
Group 2
--grouped-2-string=STRING A string flag grouped in 2.
`
t.Log(expected)
t.Log(w.String())
assert.Equal(t, expected, w.String())
})
}
func TestUsageOnError(t *testing.T) {
var cli struct {
Flag string `help:"A required flag." required`
}
w := &strings.Builder{}
p := mustNew(t, &cli,
kong.Writers(w, w),
kong.Description("Some description."),
kong.Exit(func(int) {}),
kong.UsageOnError(),
)
_, err := p.Parse([]string{})
p.FatalIfErrorf(err)
expected := `Usage: test --flag=STRING [flags]
Some description.
Flags:
-h, --help Show context-sensitive help.
--flag=STRING A required flag.
test: error: missing flags: --flag=STRING
`
assert.Equal(t, expected, w.String())
}
func TestShortUsageOnError(t *testing.T) {
var cli struct {
Flag string `help:"A required flag." required`
}
w := &strings.Builder{}
p := mustNew(t, &cli,
kong.Writers(w, w),
kong.Description("Some description."),
kong.Exit(func(int) {}),
kong.ShortUsageOnError(),
)
_, err := p.Parse([]string{})
assert.Error(t, err)
p.FatalIfErrorf(err)
expected := `Usage: test --flag=STRING [flags]
Run "test --help" for more information.
test: error: missing flags: --flag=STRING
`
assert.Equal(t, expected, w.String())
}
func TestCustomShortUsageOnError(t *testing.T) {
var cli struct {
Flag string `help:"A required flag." required`
}
w := &strings.Builder{}
shortHelp := func(_ kong.HelpOptions, ctx *kong.Context) error {
fmt.Fprintln(ctx.Stdout, "🤷 wish I could help")
return nil
}
p := mustNew(t, &cli,
kong.Writers(w, w),
kong.Description("Some description."),
kong.Exit(func(int) {}),
kong.ShortHelp(shortHelp),
kong.ShortUsageOnError(),
)
_, err := p.Parse([]string{})
assert.Error(t, err)
p.FatalIfErrorf(err)
expected := `🤷 wish I could help
test: error: missing flags: --flag=STRING
`
assert.Equal(t, expected, w.String())
}
kong-0.9.0/helpwrap1.18_test.go 0000664 0000000 0000000 00000001735 14572527075 0016222 0 ustar 00root root 0000000 0000000 //go:build !go1.19
// +build !go1.19
package kong_test
import (
"bytes"
"testing"
"github.com/alecthomas/assert/v2"
"github.com/alecthomas/kong"
)
func TestCustomWrap(t *testing.T) {
var cli struct {
Flag string `help:"A string flag with very long help that wraps a lot and is verbose and is really verbose."`
}
w := bytes.NewBuffer(nil)
app := mustNew(t, &cli,
kong.Name("test-app"),
kong.Description("A test app."),
kong.HelpOptions{
WrapUpperBound: 50,
},
kong.Writers(w, w),
kong.Exit(func(int) {}),
)
_, err := app.Parse([]string{"--help"})
assert.NoError(t, err)
expected := `Usage: test-app
A test app.
Flags:
-h, --help Show context-sensitive
help.
--flag=STRING A string flag with very
long help that wraps a lot
and is verbose and is
really verbose.
`
t.Log(w.String())
t.Log(expected)
assert.Equal(t, expected, w.String())
}
kong-0.9.0/helpwrap1.19_test.go 0000664 0000000 0000000 00000002172 14572527075 0016217 0 ustar 00root root 0000000 0000000 // Wrapping of text changed in Go1.19 per https://github.com/alecthomas/kong/issues/325
// The test has been split pre-go1.19 and go1.19 and onwards.
//go:build go1.19
// +build go1.19
package kong_test
import (
"bytes"
"testing"
"github.com/alecthomas/assert/v2"
"github.com/alecthomas/kong"
)
func TestCustomWrap(t *testing.T) {
var cli struct {
Flag string `help:"A string flag with very long help that wraps a lot and is verbose and is really verbose."`
}
w := bytes.NewBuffer(nil)
app := mustNew(t, &cli,
kong.Name("test-app"),
kong.Description("A test app."),
kong.HelpOptions{
WrapUpperBound: 50,
},
kong.Writers(w, w),
kong.Exit(func(int) {}),
)
_, err := app.Parse([]string{"--help"})
assert.NoError(t, err)
expected := `Usage: test-app [flags]
A test app.
Flags:
-h, --help Show context-sensitive
help.
--flag=STRING A string flag with very
long help that wraps a
lot and is verbose and is
really verbose.
`
t.Log(w.String())
t.Log(expected)
assert.Equal(t, expected, w.String())
}
kong-0.9.0/hooks.go 0000664 0000000 0000000 00000001324 14572527075 0014146 0 ustar 00root root 0000000 0000000 package kong
// BeforeResolve is a documentation-only interface describing hooks that run before resolvers are applied.
type BeforeResolve interface {
// This is not the correct signature - see README for details.
BeforeResolve(args ...interface{}) error
}
// BeforeApply is a documentation-only interface describing hooks that run before values are set.
type BeforeApply interface {
// This is not the correct signature - see README for details.
BeforeApply(args ...interface{}) error
}
// AfterApply is a documentation-only interface describing hooks that run after values are set.
type AfterApply interface {
// This is not the correct signature - see README for details.
AfterApply(args ...interface{}) error
}
kong-0.9.0/interpolate.go 0000664 0000000 0000000 00000002343 14572527075 0015353 0 ustar 00root root 0000000 0000000 package kong
import (
"fmt"
"regexp"
)
var interpolationRegex = regexp.MustCompile(`(\$\$)|((?:\${([[:alpha:]_][[:word:]]*))(?:=([^}]+))?})|(\$)|([^$]+)`)
// HasInterpolatedVar returns true if the variable "v" is interpolated in "s".
func HasInterpolatedVar(s string, v string) bool {
matches := interpolationRegex.FindAllStringSubmatch(s, -1)
for _, match := range matches {
if name := match[3]; name == v {
return true
}
}
return false
}
// Interpolate variables from vars into s for substrings in the form ${var} or ${var=default}.
func interpolate(s string, vars Vars, updatedVars map[string]string) (string, error) {
out := ""
matches := interpolationRegex.FindAllStringSubmatch(s, -1)
if len(matches) == 0 {
return s, nil
}
for key, val := range updatedVars {
if vars[key] != val {
vars = vars.CloneWith(updatedVars)
break
}
}
for _, match := range matches {
if dollar := match[1]; dollar != "" {
out += "$"
} else if name := match[3]; name != "" {
value, ok := vars[name]
if !ok {
// No default value.
if match[4] == "" {
return "", fmt.Errorf("undefined variable ${%s}", name)
}
value = match[4]
}
out += value
} else {
out += match[0]
}
}
return out, nil
}
kong-0.9.0/interpolate_test.go 0000664 0000000 0000000 00000001661 14572527075 0016414 0 ustar 00root root 0000000 0000000 package kong
import (
"testing"
"github.com/alecthomas/assert/v2"
)
func TestInterpolate(t *testing.T) {
vars := map[string]string{
"age": "35",
"city": "Melbourne",
}
updatedVars := map[string]string{
"height": "180",
}
actual, err := interpolate("${name=Bobby Brown} is ${age} years old, ${height} cm tall, lives in ${city=}, and likes $${AUD}", vars, updatedVars)
assert.NoError(t, err)
assert.Equal(t, `Bobby Brown is 35 years old, 180 cm tall, lives in Melbourne, and likes ${AUD}`, actual)
}
func TestHasInterpolatedVar(t *testing.T) {
for _, tag := range []string{"name", "age", "height", "city"} {
assert.True(t, HasInterpolatedVar("${name=Bobby Brown} is ${age} years old, ${height} cm tall, lives in ${city=}, and likes $${AUD}", tag), tag)
}
for _, tag := range []string{"name", "age", "height", "AUD"} {
assert.False(t, HasInterpolatedVar("$name $$age {height} $${AUD}", tag), tag)
}
}
kong-0.9.0/kong.go 0000664 0000000 0000000 00000026717 14572527075 0013776 0 ustar 00root root 0000000 0000000 package kong
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"reflect"
"regexp"
"strings"
)
var (
callbackReturnSignature = reflect.TypeOf((*error)(nil)).Elem()
)
func failField(parent reflect.Value, field reflect.StructField, format string, args ...interface{}) error {
name := parent.Type().Name()
if name == "" {
name = ""
}
return fmt.Errorf("%s.%s: %s", name, field.Name, fmt.Sprintf(format, args...))
}
// Must creates a new Parser or panics if there is an error.
func Must(ast interface{}, options ...Option) *Kong {
k, err := New(ast, options...)
if err != nil {
panic(err)
}
return k
}
type usageOnError int
const (
shortUsage usageOnError = iota + 1
fullUsage
)
// Kong is the main parser type.
type Kong struct {
// Grammar model.
Model *Application
// Termination function (defaults to os.Exit)
Exit func(int)
Stdout io.Writer
Stderr io.Writer
bindings bindings
loader ConfigurationLoader
resolvers []Resolver
registry *Registry
ignoreFields []*regexp.Regexp
noDefaultHelp bool
usageOnError usageOnError
help HelpPrinter
shortHelp HelpPrinter
helpFormatter HelpValueFormatter
helpOptions HelpOptions
helpFlag *Flag
groups []Group
vars Vars
flagNamer func(string) string
// Set temporarily by Options. These are applied after build().
postBuildOptions []Option
embedded []embedded
dynamicCommands []*dynamicCommand
}
// New creates a new Kong parser on grammar.
//
// See the README (https://github.com/alecthomas/kong) for usage instructions.
func New(grammar interface{}, options ...Option) (*Kong, error) {
k := &Kong{
Exit: os.Exit,
Stdout: os.Stdout,
Stderr: os.Stderr,
registry: NewRegistry().RegisterDefaults(),
vars: Vars{},
bindings: bindings{},
helpFormatter: DefaultHelpValueFormatter,
ignoreFields: make([]*regexp.Regexp, 0),
flagNamer: func(s string) string {
return strings.ToLower(dashedString(s))
},
}
options = append(options, Bind(k))
for _, option := range options {
if err := option.Apply(k); err != nil {
return nil, err
}
}
if k.help == nil {
k.help = DefaultHelpPrinter
}
if k.shortHelp == nil {
k.shortHelp = DefaultShortHelpPrinter
}
model, err := build(k, grammar)
if err != nil {
return k, err
}
model.Name = filepath.Base(os.Args[0])
k.Model = model
k.Model.HelpFlag = k.helpFlag
// Embed any embedded structs.
for _, embed := range k.embedded {
tag, err := parseTagString(strings.Join(embed.tags, " ")) //nolint:govet
if err != nil {
return nil, err
}
tag.Embed = true
v := reflect.Indirect(reflect.ValueOf(embed.strct))
node, err := buildNode(k, v, CommandNode, tag, map[string]bool{})
if err != nil {
return nil, err
}
for _, child := range node.Children {
child.Parent = k.Model.Node
k.Model.Children = append(k.Model.Children, child)
}
k.Model.Flags = append(k.Model.Flags, node.Flags...)
}
// Synthesise command nodes.
for _, dcmd := range k.dynamicCommands {
tag, terr := parseTagString(strings.Join(dcmd.tags, " "))
if terr != nil {
return nil, terr
}
tag.Name = dcmd.name
tag.Help = dcmd.help
tag.Group = dcmd.group
tag.Cmd = true
v := reflect.Indirect(reflect.ValueOf(dcmd.cmd))
err = buildChild(k, k.Model.Node, CommandNode, reflect.Value{}, reflect.StructField{
Name: dcmd.name,
Type: v.Type(),
}, v, tag, dcmd.name, map[string]bool{})
if err != nil {
return nil, err
}
}
for _, option := range k.postBuildOptions {
if err = option.Apply(k); err != nil {
return nil, err
}
}
k.postBuildOptions = nil
if err = k.interpolate(k.Model.Node); err != nil {
return nil, err
}
k.bindings.add(k.vars)
return k, nil
}
type varStack []Vars
func (v *varStack) head() Vars { return (*v)[len(*v)-1] }
func (v *varStack) pop() { *v = (*v)[:len(*v)-1] }
func (v *varStack) push(vars Vars) Vars {
if len(*v) != 0 {
vars = (*v)[len(*v)-1].CloneWith(vars)
}
*v = append(*v, vars)
return vars
}
// Interpolate variables into model.
func (k *Kong) interpolate(node *Node) (err error) {
stack := varStack{}
return Visit(node, func(node Visitable, next Next) error {
switch node := node.(type) {
case *Node:
vars := stack.push(node.Vars())
node.Help, err = interpolate(node.Help, vars, nil)
if err != nil {
return fmt.Errorf("help for %s: %s", node.Path(), err)
}
err = next(nil)
stack.pop()
return err
case *Value:
return next(k.interpolateValue(node, stack.head()))
}
return next(nil)
})
}
func (k *Kong) interpolateValue(value *Value, vars Vars) (err error) {
if len(value.Tag.Vars) > 0 {
vars = vars.CloneWith(value.Tag.Vars)
}
if varsContributor, ok := value.Mapper.(VarsContributor); ok {
vars = vars.CloneWith(varsContributor.Vars(value))
}
if value.Enum, err = interpolate(value.Enum, vars, nil); err != nil {
return fmt.Errorf("enum for %s: %s", value.Summary(), err)
}
updatedVars := map[string]string{
"default": value.Default,
"enum": value.Enum,
}
if value.Default, err = interpolate(value.Default, vars, nil); err != nil {
return fmt.Errorf("default value for %s: %s", value.Summary(), err)
}
if value.Enum, err = interpolate(value.Enum, vars, nil); err != nil {
return fmt.Errorf("enum value for %s: %s", value.Summary(), err)
}
if value.Flag != nil {
for i, env := range value.Flag.Envs {
if value.Flag.Envs[i], err = interpolate(env, vars, nil); err != nil {
return fmt.Errorf("env value for %s: %s", value.Summary(), err)
}
}
value.Tag.Envs = value.Flag.Envs
updatedVars["env"] = ""
if len(value.Flag.Envs) != 0 {
updatedVars["env"] = value.Flag.Envs[0]
}
}
value.Help, err = interpolate(value.Help, vars, updatedVars)
if err != nil {
return fmt.Errorf("help for %s: %s", value.Summary(), err)
}
return nil
}
// Provide additional builtin flags, if any.
func (k *Kong) extraFlags() []*Flag {
if k.noDefaultHelp {
return nil
}
var helpTarget helpValue
value := reflect.ValueOf(&helpTarget).Elem()
helpFlag := &Flag{
Short: 'h',
Value: &Value{
Name: "help",
Help: "Show context-sensitive help.",
OrigHelp: "Show context-sensitive help.",
Target: value,
Tag: &Tag{},
Mapper: k.registry.ForValue(value),
DefaultValue: reflect.ValueOf(false),
},
}
helpFlag.Flag = helpFlag
k.helpFlag = helpFlag
return []*Flag{helpFlag}
}
// Parse arguments into target.
//
// The return Context can be used to further inspect the parsed command-line, to format help, to find the
// selected command, to run command Run() methods, and so on. See Context and README for more information.
//
// Will return a ParseError if a *semantically* invalid command-line is encountered (as opposed to a syntactically
// invalid one, which will report a normal error).
func (k *Kong) Parse(args []string) (ctx *Context, err error) {
ctx, err = Trace(k, args)
if err != nil {
return nil, err
}
if ctx.Error != nil {
return nil, &ParseError{error: ctx.Error, Context: ctx}
}
if err = k.applyHook(ctx, "BeforeReset"); err != nil {
return nil, &ParseError{error: err, Context: ctx}
}
if err = ctx.Reset(); err != nil {
return nil, &ParseError{error: err, Context: ctx}
}
if err = k.applyHook(ctx, "BeforeResolve"); err != nil {
return nil, &ParseError{error: err, Context: ctx}
}
if err = ctx.Resolve(); err != nil {
return nil, &ParseError{error: err, Context: ctx}
}
if err = k.applyHook(ctx, "BeforeApply"); err != nil {
return nil, &ParseError{error: err, Context: ctx}
}
if _, err = ctx.Apply(); err != nil {
return nil, &ParseError{error: err, Context: ctx}
}
if err = ctx.Validate(); err != nil {
return nil, &ParseError{error: err, Context: ctx}
}
if err = k.applyHook(ctx, "AfterApply"); err != nil {
return nil, &ParseError{error: err, Context: ctx}
}
return ctx, nil
}
func (k *Kong) applyHook(ctx *Context, name string) error {
for _, trace := range ctx.Path {
var value reflect.Value
switch {
case trace.App != nil:
value = trace.App.Target
case trace.Argument != nil:
value = trace.Argument.Target
case trace.Command != nil:
value = trace.Command.Target
case trace.Positional != nil:
value = trace.Positional.Target
case trace.Flag != nil:
value = trace.Flag.Value.Target
default:
panic("unsupported Path")
}
method := getMethod(value, name)
if !method.IsValid() {
continue
}
binds := k.bindings.clone()
binds.add(ctx, trace)
binds.add(trace.Node().Vars().CloneWith(k.vars))
binds.merge(ctx.bindings)
if err := callFunction(method, binds); err != nil {
return err
}
}
// Path[0] will always be the app root.
return k.applyHookToDefaultFlags(ctx, ctx.Path[0].Node(), name)
}
// Call hook on any unset flags with default values.
func (k *Kong) applyHookToDefaultFlags(ctx *Context, node *Node, name string) error {
if node == nil {
return nil
}
return Visit(node, func(n Visitable, next Next) error {
node, ok := n.(*Node)
if !ok {
return next(nil)
}
binds := k.bindings.clone().add(ctx).add(node.Vars().CloneWith(k.vars))
for _, flag := range node.Flags {
if !flag.HasDefault || ctx.values[flag.Value].IsValid() || !flag.Target.IsValid() {
continue
}
method := getMethod(flag.Target, name)
if !method.IsValid() {
continue
}
path := &Path{Flag: flag}
if err := callFunction(method, binds.clone().add(path)); err != nil {
return next(err)
}
}
return next(nil)
})
}
func formatMultilineMessage(w io.Writer, leaders []string, format string, args ...interface{}) {
lines := strings.Split(fmt.Sprintf(format, args...), "\n")
leader := ""
for _, l := range leaders {
if l == "" {
continue
}
leader += l + ": "
}
fmt.Fprintf(w, "%s%s\n", leader, lines[0])
for _, line := range lines[1:] {
fmt.Fprintf(w, "%*s%s\n", len(leader), " ", line)
}
}
// Printf writes a message to Kong.Stdout with the application name prefixed.
func (k *Kong) Printf(format string, args ...interface{}) *Kong {
formatMultilineMessage(k.Stdout, []string{k.Model.Name}, format, args...)
return k
}
// Errorf writes a message to Kong.Stderr with the application name prefixed.
func (k *Kong) Errorf(format string, args ...interface{}) *Kong {
formatMultilineMessage(k.Stderr, []string{k.Model.Name, "error"}, format, args...)
return k
}
// Fatalf writes a message to Kong.Stderr with the application name prefixed then exits with a non-zero status.
func (k *Kong) Fatalf(format string, args ...interface{}) {
k.Errorf(format, args...)
k.Exit(1)
}
// FatalIfErrorf terminates with an error message if err != nil.
func (k *Kong) FatalIfErrorf(err error, args ...interface{}) {
if err == nil {
return
}
msg := err.Error()
if len(args) > 0 {
msg = fmt.Sprintf(args[0].(string), args[1:]...) + ": " + err.Error() //nolint
}
// Maybe display usage information.
var parseErr *ParseError
if errors.As(err, &parseErr) {
switch k.usageOnError {
case fullUsage:
_ = k.help(k.helpOptions, parseErr.Context)
fmt.Fprintln(k.Stdout)
case shortUsage:
_ = k.shortHelp(k.helpOptions, parseErr.Context)
fmt.Fprintln(k.Stdout)
}
}
k.Fatalf("%s", msg)
}
// LoadConfig from path using the loader configured via Configuration(loader).
//
// "path" will have ~ and any variables expanded.
func (k *Kong) LoadConfig(path string) (Resolver, error) {
var err error
path = ExpandPath(path)
path, err = interpolate(path, k.vars, nil)
if err != nil {
return nil, err
}
r, err := os.Open(path) //nolint: gas
if err != nil {
return nil, err
}
defer r.Close()
return k.loader(r)
}
kong-0.9.0/kong.png 0000664 0000000 0000000 00000206002 14572527075 0014140 0 ustar 00root root 0000000 0000000 PNG
IHDR <