feat: gitea client
This commit is contained in:
0
vendor/github.com/alecthomas/kong/.gitignore
generated
vendored
Normal file
0
vendor/github.com/alecthomas/kong/.gitignore
generated
vendored
Normal file
85
vendor/github.com/alecthomas/kong/.golangci.yml
generated
vendored
Normal file
85
vendor/github.com/alecthomas/kong/.golangci.yml
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
run:
|
||||
tests: true
|
||||
|
||||
output:
|
||||
print-issued-lines: false
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- lll
|
||||
- gochecknoglobals
|
||||
- wsl
|
||||
- funlen
|
||||
- gocognit
|
||||
- goprintffuncname
|
||||
- paralleltest
|
||||
- nlreturn
|
||||
- testpackage
|
||||
- wrapcheck
|
||||
- forbidigo
|
||||
- gci
|
||||
- godot
|
||||
- gofumpt
|
||||
- cyclop
|
||||
- errorlint
|
||||
- nestif
|
||||
- tagliatelle
|
||||
- thelper
|
||||
- godox
|
||||
- goconst
|
||||
- varnamelen
|
||||
- ireturn
|
||||
- exhaustruct
|
||||
- nonamedreturns
|
||||
- nilnil
|
||||
- depguard # nothing to guard against yet
|
||||
- tagalign # hurts readability of kong tags
|
||||
- tenv # deprecated since v1.64, but not removed yet
|
||||
- mnd
|
||||
- perfsprint
|
||||
- err113
|
||||
- copyloopvar
|
||||
- intrange
|
||||
- nakedret
|
||||
- recvcheck # value receivers are intentionally used for copies
|
||||
|
||||
linters-settings:
|
||||
govet:
|
||||
# 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'
|
||||
- 'Error return value of `fmt.Fprintln` is not checked'
|
||||
|
||||
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
|
||||
19
vendor/github.com/alecthomas/kong/COPYING
generated
vendored
Normal file
19
vendor/github.com/alecthomas/kong/COPYING
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
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.
|
||||
775
vendor/github.com/alecthomas/kong/README.md
generated
vendored
Normal file
775
vendor/github.com/alecthomas/kong/README.md
generated
vendored
Normal file
@@ -0,0 +1,775 @@
|
||||
<!-- markdownlint-disable MD013 MD033 -->
|
||||
<p align="center"><img width="90%" src="kong.png" /></p>
|
||||
|
||||
# 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)
|
||||
|
||||
- [Version 1.0.0 Release](#version-100-release)
|
||||
- [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()](#hooks-beforereset-beforeresolve-beforeapply-afterapply)
|
||||
- [The Bind() option](#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)
|
||||
- [`Name(help)` and `Description(help)` - set the application name description](#namehelp-and-descriptionhelp---set-the-application-name-description)
|
||||
- [`Configuration(loader, 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)
|
||||
- [`ConfigureHelp(HelpOptions)` and `Help(HelpFunc)` - customising help](#configurehelphelpoptions-and-helphelpfunc---customising-help)
|
||||
- [Injecting values into `Run()` methods](#injecting-values-into-run-methods)
|
||||
- [Other options](#other-options)
|
||||
|
||||
## Version 1.0.0 Release
|
||||
|
||||
Kong has been stable for a long time, so it seemed appropriate to cut a 1.0 release.
|
||||
|
||||
There is one breaking change, [#436](https://github.com/alecthomas/kong/pull/436), which should effect relatively few users.
|
||||
|
||||
## 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] <paths> ...
|
||||
shell ls [<paths> ...]
|
||||
|
||||
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 <path>":
|
||||
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 <command>
|
||||
|
||||
A shell-like example app.
|
||||
|
||||
Flags:
|
||||
--help Show context-sensitive help.
|
||||
--debug Debug mode.
|
||||
|
||||
Commands:
|
||||
rm <path> ...
|
||||
Remove files.
|
||||
|
||||
ls [<path> ...]
|
||||
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 <paths> ...
|
||||
|
||||
Remove files.
|
||||
|
||||
Arguments:
|
||||
<paths> ... 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 <command>
|
||||
|
||||
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 <msg>
|
||||
|
||||
Regular command help
|
||||
|
||||
🚀 additional command help
|
||||
|
||||
Arguments:
|
||||
<msg> 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](#branching-positional-arguments))
|
||||
|
||||
**Contextual argument help**
|
||||
|
||||
```bash
|
||||
$ go run ./_examples/shell/help msg --help
|
||||
Usage: help echo <msg>
|
||||
|
||||
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 <path>":
|
||||
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()
|
||||
|
||||
If a node in the CLI, or any of its embedded fields, implements a `BeforeReset(...) error`, `BeforeResolve
|
||||
(...) error`, `BeforeApply(...) error` and/or `AfterApply(...) error` method, those will be called as Kong
|
||||
resets, resolves, validates, and assigns values to the node.
|
||||
|
||||
| Hook | Description |
|
||||
| --------------- | ----------------------------------------------------------------------------------------------------------- |
|
||||
| `BeforeReset` | Invoked before values are reset to their defaults (as defined by the grammar) or to zero values |
|
||||
| `BeforeResolve` | Invoked before resolvers are applied to a node |
|
||||
| `BeforeApply` | Invoked before the traced command line arguments are applied to the grammar |
|
||||
| `AfterApply` | Invoked after command line arguments are applied to the grammar **and validated**` |
|
||||
|
||||
The `--help` flag is implemented with a `BeforeReset` hook.
|
||||
|
||||
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))
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
It's also possible to register these hooks with the functional options
|
||||
`kong.WithBeforeReset`, `kong.WithBeforeResolve`, `kong.WithBeforeApply`, and
|
||||
`kong.WithAfterApply`.
|
||||
|
||||
## The Bind() option
|
||||
|
||||
Arguments to hooks are provided via the `Run(...)` method or `Bind(...)` option. `*Kong`, `*Context`, `*Path` and parent commands are also bound and finally, hooks can also contribute bindings via `kong.Context.Bind()` and `kong.Context.BindTo()`.
|
||||
|
||||
eg:
|
||||
|
||||
```go
|
||||
type CLI struct {
|
||||
Debug bool `help:"Enable debug mode."`
|
||||
|
||||
Rm RmCmd `cmd:"" help:"Remove files."`
|
||||
Ls LsCmd `cmd:"" help:"List paths."`
|
||||
}
|
||||
|
||||
type AuthorName string
|
||||
|
||||
// ...
|
||||
func (l *LsCmd) Run(cli *CLI) error {
|
||||
// use cli.Debug here !!
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RmCmD) Run(author AuthorName) error{
|
||||
// use binded author here
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
var cli CLI
|
||||
|
||||
ctx := kong.Parse(&cli, Bind(AuthorName("penguin")))
|
||||
err := ctx.Run()
|
||||
```
|
||||
|
||||
## 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 <name> to <name>
|
||||
|
||||
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:"<rune>"` 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 <file> <file> ...
|
||||
|
||||
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 <key>=<value> <key>=<value> ...
|
||||
|
||||
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:"<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:"[<key>]:[<value>]"` 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 input, if flag. e.g. `` `placeholder:"<the-placeholder>"` `` will show `--flag-name=<the-placeholder>` when displaying help. |
|
||||
| `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 |
|
||||
| `negatable:"X"` | If present on a `bool` field, supports `--X` 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. |
|
||||
| `and:"X,Y,..."` | AND groups for flags. All flags in the group must be used in the same command. When combined with `required`, all flags in the group will be required. |
|
||||
| `prefix:"X"` | Prefix for all sub-flags. |
|
||||
| `envprefix:"X"` | Envar prefix for all sub-flags. |
|
||||
| `xorprefix:"X"` | Prefix for all sub-flags in XOR/AND groups. |
|
||||
| `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:"<mode>"`[^1] | 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:"-"` `` |
|
||||
|
||||
[^1]: `<mode>` can be `partial` or `all` (the default). `all` will pass through all arguments including flags, including
|
||||
flags. `partial` will validate flags until the first positional argument is encountered, then pass through all remaining
|
||||
positional arguments.
|
||||
|
||||
## 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, placeholder strings,
|
||||
enum lists and default values.
|
||||
|
||||
Variables are in the form:
|
||||
|
||||
${<name>}
|
||||
${<name>=<default>}
|
||||
|
||||
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 either of the following interfaces:
|
||||
|
||||
```go
|
||||
type Validatable interface {
|
||||
Validate() error
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
type Validatable interface {
|
||||
Validate(kctx *kong.Context) 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 any, 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:"<name>"`.
|
||||
2. `KindMapper(reflect.Kind, Mapper)`.
|
||||
3. `TypeMapper(reflect.Type, Mapper)`.
|
||||
4. `ValueMapper(any, 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 `DefaultHelpPrinter` 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.
|
||||
|
||||
### Injecting values into `Run()` methods
|
||||
|
||||
There are several ways to inject values into `Run()` methods:
|
||||
|
||||
1. Use `Bind()` to bind values directly.
|
||||
2. Use `BindTo()` to bind values to an interface type.
|
||||
3. Use `BindToProvider()` to bind values to a function that provides the value.
|
||||
4. Implement `Provide<Type>() error` methods on the command structure.
|
||||
|
||||
### Other options
|
||||
|
||||
The full set of options can be found [here](https://godoc.org/github.com/alecthomas/kong#Option).
|
||||
411
vendor/github.com/alecthomas/kong/build.go
generated
vendored
Normal file
411
vendor/github.com/alecthomas/kong/build.go
generated
vendored
Normal file
@@ -0,0 +1,411 @@
|
||||
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 []any
|
||||
|
||||
func build(k *Kong, ast any) (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)
|
||||
if v.Kind() != reflect.Struct {
|
||||
return out, nil
|
||||
}
|
||||
ignored := map[string]bool{}
|
||||
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 || ignored[ft.Name] {
|
||||
ignored[ft.Name] = true
|
||||
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
|
||||
tag.XorPrefix = ptag.XorPrefix + tag.XorPrefix
|
||||
// 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...)
|
||||
}
|
||||
out = removeIgnored(out, ignored)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func removeIgnored(fields []flattenedField, ignored map[string]bool) []flattenedField {
|
||||
j := 0
|
||||
for i := 0; i < len(fields); i++ {
|
||||
if ignored[fields[i].field.Name] {
|
||||
continue
|
||||
}
|
||||
if i != j {
|
||||
fields[j] = fields[i]
|
||||
}
|
||||
j++
|
||||
}
|
||||
if j != len(fields) {
|
||||
fields = fields[:j]
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
// 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) { //nolint:gocyclo
|
||||
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]
|
||||
}
|
||||
}
|
||||
|
||||
if len(tag.Xor) != 0 {
|
||||
for i := range tag.Xor {
|
||||
tag.Xor[i] = tag.XorPrefix + tag.Xor[i]
|
||||
}
|
||||
}
|
||||
|
||||
if len(tag.And) != 0 {
|
||||
for i := range tag.And {
|
||||
tag.And[i] = tag.XorPrefix + tag.And[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 negFlag := negatableFlagName(flag.Name, flag.Tag.Negatable); negFlag != "" {
|
||||
delete(seenFlags, negFlag)
|
||||
}
|
||||
for _, aflag := range flag.Aliases {
|
||||
delete(seenFlags, "--"+aflag)
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
PassthroughMode: tag.PassthroughMode,
|
||||
|
||||
// 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
|
||||
}
|
||||
if tag.Negatable != "" {
|
||||
negFlag := negatableFlagName(value.Name, tag.Negatable)
|
||||
if seenFlags[negFlag] {
|
||||
return failField(v, ft, "duplicate negation flag %s", negFlag)
|
||||
}
|
||||
seenFlags[negFlag] = 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,
|
||||
And: tag.And,
|
||||
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 = "<anonymous struct>"
|
||||
}
|
||||
return fmt.Errorf("duplicate command name %q in command %q", node.Name, name)
|
||||
}
|
||||
|
||||
seenNames[node.Name] = struct{}{}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
231
vendor/github.com/alecthomas/kong/callbacks.go
generated
vendored
Normal file
231
vendor/github.com/alecthomas/kong/callbacks.go
generated
vendored
Normal file
@@ -0,0 +1,231 @@
|
||||
package kong
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// binding is a single binding registered with Kong.
|
||||
type binding struct {
|
||||
// fn is a function that returns a value of the target type.
|
||||
fn reflect.Value
|
||||
|
||||
// val is a value of the target type.
|
||||
// Must be set if done and singleton are true.
|
||||
val reflect.Value
|
||||
|
||||
// singleton indicates whether the binding is a singleton.
|
||||
// If true, the binding will be resolved once and cached.
|
||||
singleton bool
|
||||
|
||||
// done indicates whether a singleton binding has been resolved.
|
||||
// If singleton is false, this field is ignored.
|
||||
done bool
|
||||
}
|
||||
|
||||
// newValueBinding builds a binding with an already resolved value.
|
||||
func newValueBinding(v reflect.Value) *binding {
|
||||
return &binding{val: v, done: true, singleton: true}
|
||||
}
|
||||
|
||||
// newFunctionBinding builds a binding with a function
|
||||
// that will return a value of the target type.
|
||||
//
|
||||
// The function signature must be func(...) (T, error) or func(...) T
|
||||
// where parameters are recursively resolved.
|
||||
func newFunctionBinding(f reflect.Value, singleton bool) *binding {
|
||||
return &binding{fn: f, singleton: singleton}
|
||||
}
|
||||
|
||||
// Get returns the pre-resolved value for the binding,
|
||||
// or false if the binding is not resolved.
|
||||
func (b *binding) Get() (v reflect.Value, ok bool) {
|
||||
return b.val, b.done
|
||||
}
|
||||
|
||||
// Set sets the value of the binding to the given value,
|
||||
// marking it as resolved.
|
||||
//
|
||||
// If the binding is not a singleton, this method does nothing.
|
||||
func (b *binding) Set(v reflect.Value) {
|
||||
if b.singleton {
|
||||
b.val = v
|
||||
b.done = true
|
||||
}
|
||||
}
|
||||
|
||||
// A map of type to function that returns a value of that type.
|
||||
//
|
||||
// The function should have the signature func(...) (T, error). Arguments are recursively resolved.
|
||||
type bindings map[reflect.Type]*binding
|
||||
|
||||
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 ...any) bindings {
|
||||
for _, v := range values {
|
||||
val := reflect.ValueOf(v)
|
||||
b[val.Type()] = newValueBinding(val)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b bindings) addTo(impl, iface any) {
|
||||
val := reflect.ValueOf(impl)
|
||||
b[reflect.TypeOf(iface).Elem()] = newValueBinding(val)
|
||||
}
|
||||
|
||||
func (b bindings) addProvider(provider any, singleton bool) error {
|
||||
pv := reflect.ValueOf(provider)
|
||||
t := pv.Type()
|
||||
if t.Kind() != reflect.Func {
|
||||
return fmt.Errorf("%T must be a function", provider)
|
||||
}
|
||||
|
||||
if t.NumOut() == 0 {
|
||||
return fmt.Errorf("%T must be a function with the signature func(...)(T, error) or func(...) T", provider)
|
||||
}
|
||||
if t.NumOut() == 2 {
|
||||
if t.Out(1) != reflect.TypeOf((*error)(nil)).Elem() {
|
||||
return fmt.Errorf("missing error; %T must be a function with the signature func(...)(T, error) or func(...) T", provider)
|
||||
}
|
||||
}
|
||||
rt := pv.Type().Out(0)
|
||||
b[rt] = newFunctionBinding(pv, singleton)
|
||||
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
|
||||
}
|
||||
|
||||
// getMethods gets all methods with the given name from the given value
|
||||
// and any embedded fields.
|
||||
//
|
||||
// Returns a slice of bound methods that can be called directly.
|
||||
func getMethods(value reflect.Value, name string) (methods []reflect.Value) {
|
||||
if value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
if !value.IsValid() {
|
||||
return
|
||||
}
|
||||
|
||||
if method := getMethod(value, name); method.IsValid() {
|
||||
methods = append(methods, method)
|
||||
}
|
||||
|
||||
if value.Kind() != reflect.Struct {
|
||||
return
|
||||
}
|
||||
// If the current value is a struct, also consider embedded fields.
|
||||
// Two kinds of embedded fields are considered if they're exported:
|
||||
//
|
||||
// - standard Go embedded fields
|
||||
// - fields tagged with `embed:""`
|
||||
t := value.Type()
|
||||
for i := 0; i < value.NumField(); i++ {
|
||||
fieldValue := value.Field(i)
|
||||
field := t.Field(i)
|
||||
|
||||
if !field.IsExported() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Consider a field embedded if it's actually embedded
|
||||
// or if it's tagged with `embed:""`.
|
||||
_, isEmbedded := field.Tag.Lookup("embed")
|
||||
isEmbedded = isEmbedded || field.Anonymous
|
||||
if isEmbedded {
|
||||
methods = append(methods, getMethods(fieldValue, name)...)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func callFunction(f reflect.Value, bindings bindings) error {
|
||||
if f.Kind() != reflect.Func {
|
||||
return fmt.Errorf("expected function, got %s", f.Type())
|
||||
}
|
||||
t := f.Type()
|
||||
if t.NumOut() != 1 || !t.Out(0).Implements(callbackReturnSignature) {
|
||||
return fmt.Errorf("return value of %s must implement \"error\"", t)
|
||||
}
|
||||
out, err := callAnyFunction(f, bindings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ferr := out[0]
|
||||
if ferrv := reflect.ValueOf(ferr); !ferrv.IsValid() || ((ferrv.Kind() == reflect.Interface || ferrv.Kind() == reflect.Pointer) && ferrv.IsNil()) {
|
||||
return nil
|
||||
}
|
||||
return ferr.(error) //nolint:forcetypeassert
|
||||
}
|
||||
|
||||
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)
|
||||
binding, ok := bindings[pt]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("couldn't find binding of type %s for parameter %d of %s(), use kong.Bind(%s)", pt, i, t, pt)
|
||||
}
|
||||
|
||||
// Don't need to call the function if the value is already resolved.
|
||||
if val, ok := binding.Get(); ok {
|
||||
in = append(in, val)
|
||||
continue
|
||||
}
|
||||
|
||||
// Recursively resolve binding functions.
|
||||
argv, err := callAnyFunction(binding.fn, bindings)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", pt, err)
|
||||
}
|
||||
if ferrv := reflect.ValueOf(argv[len(argv)-1]); ferrv.IsValid() && ferrv.Type().Implements(callbackReturnSignature) && !ferrv.IsNil() {
|
||||
return nil, ferrv.Interface().(error) //nolint:forcetypeassert
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(argv[0])
|
||||
binding.Set(val)
|
||||
in = append(in, val)
|
||||
}
|
||||
outv := f.Call(in)
|
||||
out = make([]any, len(outv))
|
||||
for i, v := range outv {
|
||||
out[i] = v.Interface()
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
90
vendor/github.com/alecthomas/kong/camelcase.go
generated
vendored
Normal file
90
vendor/github.com/alecthomas/kong/camelcase.go
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
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
|
||||
}
|
||||
1188
vendor/github.com/alecthomas/kong/context.go
generated
vendored
Normal file
1188
vendor/github.com/alecthomas/kong/context.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
21
vendor/github.com/alecthomas/kong/defaults.go
generated
vendored
Normal file
21
vendor/github.com/alecthomas/kong/defaults.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
package kong
|
||||
|
||||
// ApplyDefaults if they are not already set.
|
||||
func ApplyDefaults(target any, 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()
|
||||
}
|
||||
32
vendor/github.com/alecthomas/kong/doc.go
generated
vendored
Normal file
32
vendor/github.com/alecthomas/kong/doc.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
// 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] <paths> ...
|
||||
// shell ls [<paths> ...]
|
||||
//
|
||||
// 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
|
||||
21
vendor/github.com/alecthomas/kong/error.go
generated
vendored
Normal file
21
vendor/github.com/alecthomas/kong/error.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
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
|
||||
exitCode int
|
||||
}
|
||||
|
||||
// Unwrap returns the original cause of the error.
|
||||
func (p *ParseError) Unwrap() error { return p.error }
|
||||
|
||||
// ExitCode returns the status that Kong should exit with if it fails with a ParseError.
|
||||
func (p *ParseError) ExitCode() int {
|
||||
if p.exitCode == 0 {
|
||||
return exitNotOk
|
||||
}
|
||||
return p.exitCode
|
||||
}
|
||||
32
vendor/github.com/alecthomas/kong/exit.go
generated
vendored
Normal file
32
vendor/github.com/alecthomas/kong/exit.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
package kong
|
||||
|
||||
import "errors"
|
||||
|
||||
const (
|
||||
exitOk = 0
|
||||
exitNotOk = 1
|
||||
|
||||
// Semantic exit codes from https://github.com/square/exit?tab=readme-ov-file#about
|
||||
exitUsageError = 80
|
||||
)
|
||||
|
||||
// ExitCoder is an interface that may be implemented by an error value to
|
||||
// provide an integer exit code. The method ExitCode should return an integer
|
||||
// that is intended to be used as the exit code for the application.
|
||||
type ExitCoder interface {
|
||||
ExitCode() int
|
||||
}
|
||||
|
||||
// exitCodeFromError returns the exit code for the given error.
|
||||
// If err implements the exitCoder interface, the ExitCode method is called.
|
||||
// Otherwise, exitCodeFromError returns 0 if err is nil, and 1 if it is not.
|
||||
func exitCodeFromError(err error) int {
|
||||
var e ExitCoder
|
||||
if errors.As(err, &e) {
|
||||
return e.ExitCode()
|
||||
} else if err == nil {
|
||||
return exitOk
|
||||
}
|
||||
|
||||
return exitNotOk
|
||||
}
|
||||
16
vendor/github.com/alecthomas/kong/global.go
generated
vendored
Normal file
16
vendor/github.com/alecthomas/kong/global.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
package kong
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// Parse constructs a new parser and parses the default command-line.
|
||||
func Parse(cli any, 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
|
||||
}
|
||||
9
vendor/github.com/alecthomas/kong/guesswidth.go
generated
vendored
Normal file
9
vendor/github.com/alecthomas/kong/guesswidth.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build tinygo || appengine || (!linux && !freebsd && !darwin && !dragonfly && !netbsd && !openbsd)
|
||||
|
||||
package kong
|
||||
|
||||
import "io"
|
||||
|
||||
func guessWidth(w io.Writer) int {
|
||||
return 80
|
||||
}
|
||||
41
vendor/github.com/alecthomas/kong/guesswidth_unix.go
generated
vendored
Normal file
41
vendor/github.com/alecthomas/kong/guesswidth_unix.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
//go:build !tinygo && ((!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
|
||||
}
|
||||
577
vendor/github.com/alecthomas/kong/help.go
generated
vendored
Normal file
577
vendor/github.com/alecthomas/kong/help.go
generated
vendored
Normal file
@@ -0,0 +1,577 @@
|
||||
package kong
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/doc"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultIndent = 2
|
||||
defaultColumnPadding = 4
|
||||
)
|
||||
|
||||
// Help flag.
|
||||
type helpFlag bool
|
||||
|
||||
func (h helpFlag) IgnoreDefault() {}
|
||||
|
||||
func (h helpFlag) BeforeReset(ctx *Context) error {
|
||||
options := ctx.Kong.helpOptions
|
||||
options.Summary = false
|
||||
err := ctx.printHelp(options)
|
||||
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
|
||||
|
||||
// ValueFormatter is used to format the help text of flags and positional arguments.
|
||||
ValueFormatter HelpValueFormatter
|
||||
}
|
||||
|
||||
// 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 <command> --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
|
||||
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,
|
||||
HelpOptions: options,
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func (h *helpWriter) Printf(format string, args ...any) {
|
||||
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}
|
||||
}
|
||||
|
||||
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) //nolint:staticcheck // cross-package links not possible
|
||||
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.HelpOptions.ValueFormatter(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.HelpOptions.ValueFormatter(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) //nolint:staticcheck // cross-package links not possible
|
||||
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()
|
||||
|
||||
short := ""
|
||||
if flag.Short != 0 {
|
||||
short = "-" + string(flag.Short) + ", "
|
||||
} else if haveShort {
|
||||
short = " "
|
||||
}
|
||||
|
||||
if isBool && flag.Tag.Negatable == negatableDefault {
|
||||
name = "[no-]" + name
|
||||
} else if isBool && flag.Tag.Negatable != "" {
|
||||
name += "/" + flag.Tag.Negatable
|
||||
}
|
||||
|
||||
flagString += fmt.Sprintf("%s--%s", short, 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, ", ")
|
||||
}
|
||||
32
vendor/github.com/alecthomas/kong/hooks.go
generated
vendored
Normal file
32
vendor/github.com/alecthomas/kong/hooks.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
package kong
|
||||
|
||||
// BeforeReset is a documentation-only interface describing hooks that run before defaults values are applied.
|
||||
type BeforeReset interface {
|
||||
// This is not the correct signature - see README for details.
|
||||
BeforeReset(args ...any) error
|
||||
}
|
||||
|
||||
// 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 ...any) 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 ...any) 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 ...any) error
|
||||
}
|
||||
|
||||
// AfterRun is a documentation-only interface describing hooks that run after Run() returns.
|
||||
type AfterRun interface {
|
||||
// This is not the correct signature - see README for details.
|
||||
// AfterRun is called after Run() returns.
|
||||
AfterRun(args ...any) error
|
||||
}
|
||||
52
vendor/github.com/alecthomas/kong/interpolate.go
generated
vendored
Normal file
52
vendor/github.com/alecthomas/kong/interpolate.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
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
|
||||
}
|
||||
503
vendor/github.com/alecthomas/kong/kong.go
generated
vendored
Normal file
503
vendor/github.com/alecthomas/kong/kong.go
generated
vendored
Normal file
@@ -0,0 +1,503 @@
|
||||
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 ...any) error {
|
||||
name := parent.Type().Name()
|
||||
if name == "" {
|
||||
name = "<anonymous struct>"
|
||||
}
|
||||
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 any, 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
|
||||
allowHyphenated 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
|
||||
|
||||
hooks map[string][]reflect.Value
|
||||
}
|
||||
|
||||
// New creates a new Kong parser on grammar.
|
||||
//
|
||||
// See the README (https://github.com/alecthomas/kong) for usage instructions.
|
||||
func New(grammar any, options ...Option) (*Kong, error) {
|
||||
k := &Kong{
|
||||
Exit: os.Exit,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
registry: NewRegistry().RegisterDefaults(),
|
||||
vars: Vars{},
|
||||
bindings: bindings{},
|
||||
hooks: make(map[string][]reflect.Value),
|
||||
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, " "))
|
||||
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)
|
||||
|
||||
if err = checkOverlappingXorAnd(k); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return k, nil
|
||||
}
|
||||
|
||||
func checkOverlappingXorAnd(k *Kong) error {
|
||||
xorGroups := map[string][]string{}
|
||||
andGroups := map[string][]string{}
|
||||
for _, flag := range k.Model.Node.Flags {
|
||||
for _, xor := range flag.Xor {
|
||||
xorGroups[xor] = append(xorGroups[xor], flag.Name)
|
||||
}
|
||||
for _, and := range flag.And {
|
||||
andGroups[and] = append(andGroups[and], flag.Name)
|
||||
}
|
||||
}
|
||||
for xor, xorSet := range xorGroups {
|
||||
for and, andSet := range andGroups {
|
||||
overlappingEntries := []string{}
|
||||
for _, xorTag := range xorSet {
|
||||
for _, andTag := range andSet {
|
||||
if xorTag == andTag {
|
||||
overlappingEntries = append(overlappingEntries, xorTag)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(overlappingEntries) > 1 {
|
||||
return fmt.Errorf("invalid xor and combination, %s and %s overlap with more than one: %s", xor, and, overlappingEntries)
|
||||
}
|
||||
}
|
||||
}
|
||||
return 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))
|
||||
}
|
||||
|
||||
initialVars := vars.CloneWith(nil)
|
||||
for n, v := range initialVars {
|
||||
if vars[n], err = interpolate(v, initialVars, nil); err != nil {
|
||||
return fmt.Errorf("variable %s for %s: %s", n, value.Summary(), err)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
updatedVars := map[string]string{
|
||||
"default": value.Default,
|
||||
"enum": value.Enum,
|
||||
}
|
||||
if value.Flag != nil {
|
||||
for i, env := range value.Flag.Envs {
|
||||
if value.Flag.Envs[i], err = interpolate(env, vars, updatedVars); 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.Flag.PlaceHolder, err = interpolate(value.Flag.PlaceHolder, vars, updatedVars)
|
||||
if err != nil {
|
||||
return fmt.Errorf("placeholder value for %s: %s", value.Summary(), err)
|
||||
}
|
||||
}
|
||||
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 helpFlag
|
||||
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 { // Trace is not expected to return an err
|
||||
return nil, &ParseError{error: err, Context: ctx, exitCode: exitUsageError}
|
||||
}
|
||||
if ctx.Error != nil {
|
||||
return nil, &ParseError{error: ctx.Error, Context: ctx, exitCode: exitUsageError}
|
||||
}
|
||||
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 { // Apply is not expected to return an err
|
||||
return nil, &ParseError{error: err, Context: ctx}
|
||||
}
|
||||
if err = ctx.Validate(); err != nil {
|
||||
return nil, &ParseError{error: err, Context: ctx, exitCode: exitUsageError}
|
||||
}
|
||||
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")
|
||||
}
|
||||
for _, method := range k.getMethods(value, name) {
|
||||
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)
|
||||
}
|
||||
|
||||
func (k *Kong) getMethods(value reflect.Value, name string) []reflect.Value {
|
||||
return append(
|
||||
// Identify callbacks by reflecting on value
|
||||
getMethods(value, name),
|
||||
|
||||
// Identify callbacks that were registered with a kong.Option
|
||||
k.hooks[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
|
||||
}
|
||||
for _, method := range getMethods(flag.Target, name) {
|
||||
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 ...any) {
|
||||
lines := strings.Split(strings.TrimRight(fmt.Sprintf(format, args...), "\n"), "\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 ...any) *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 ...any) *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 status 1.
|
||||
func (k *Kong) Fatalf(format string, args ...any) {
|
||||
k.Errorf(format, args...)
|
||||
k.Exit(1)
|
||||
}
|
||||
|
||||
// FatalIfErrorf terminates with an error message if err != nil.
|
||||
// If the error implements the ExitCoder interface, the ExitCode() method is called and
|
||||
// the application exits with that status. Otherwise, the application exits with status 1.
|
||||
func (k *Kong) FatalIfErrorf(err error, args ...any) {
|
||||
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:
|
||||
_ = parseErr.Context.printHelp(k.helpOptions)
|
||||
fmt.Fprintln(k.Stdout)
|
||||
case shortUsage:
|
||||
_ = k.shortHelp(k.helpOptions, parseErr.Context)
|
||||
fmt.Fprintln(k.Stdout)
|
||||
}
|
||||
}
|
||||
k.Errorf("%s", msg)
|
||||
k.Exit(exitCodeFromError(err))
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
BIN
vendor/github.com/alecthomas/kong/kong.png
generated
vendored
Normal file
BIN
vendor/github.com/alecthomas/kong/kong.png
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
BIN
vendor/github.com/alecthomas/kong/kong.sketch
generated
vendored
Normal file
BIN
vendor/github.com/alecthomas/kong/kong.sketch
generated
vendored
Normal file
Binary file not shown.
11
vendor/github.com/alecthomas/kong/lefthook.yml
generated
vendored
Normal file
11
vendor/github.com/alecthomas/kong/lefthook.yml
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
output:
|
||||
- success
|
||||
- failure
|
||||
pre-push:
|
||||
parallel: true
|
||||
jobs:
|
||||
- name: test
|
||||
run: go test -v ./...
|
||||
|
||||
- name: lint
|
||||
run: golangci-lint run
|
||||
39
vendor/github.com/alecthomas/kong/levenshtein.go
generated
vendored
Normal file
39
vendor/github.com/alecthomas/kong/levenshtein.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
package kong
|
||||
|
||||
import "unicode/utf8"
|
||||
|
||||
// Copied from https://github.com/daviddengcn/go-algs/blob/fe23fabd9d0670e4675326040ba7c285c7117b4c/ed/ed.go#L31
|
||||
// License: https://github.com/daviddengcn/go-algs/blob/fe23fabd9d0670e4675326040ba7c285c7117b4c/LICENSE
|
||||
func levenshtein(a, b string) int {
|
||||
f := make([]int, utf8.RuneCountInString(b)+1)
|
||||
|
||||
for j := range f {
|
||||
f[j] = j
|
||||
}
|
||||
|
||||
for _, ca := range a {
|
||||
j := 1
|
||||
fj1 := f[0] // fj1 is the value of f[j - 1] in last iteration
|
||||
f[0]++
|
||||
for _, cb := range b {
|
||||
mn := min(f[j]+1, f[j-1]+1) // delete & insert
|
||||
if cb != ca {
|
||||
mn = min(mn, fj1+1) // change
|
||||
} else {
|
||||
mn = min(mn, fj1) // matched
|
||||
}
|
||||
|
||||
fj1, f[j] = f[j], mn // save f[j] to fj1(j is about to increase), update f[j] to mn
|
||||
j++
|
||||
}
|
||||
}
|
||||
|
||||
return f[len(f)-1]
|
||||
}
|
||||
|
||||
func min(a, b int) int { //nolint:predeclared
|
||||
if a <= b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
938
vendor/github.com/alecthomas/kong/mapper.go
generated
vendored
Normal file
938
vendor/github.com/alecthomas/kong/mapper.go
generated
vendored
Normal file
@@ -0,0 +1,938 @@
|
||||
package kong
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/bits"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
mapperValueType = reflect.TypeOf((*MapperValue)(nil)).Elem()
|
||||
boolMapperValueType = reflect.TypeOf((*BoolMapperValue)(nil)).Elem()
|
||||
jsonUnmarshalerType = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()
|
||||
textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
|
||||
binaryUnmarshalerType = reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem()
|
||||
)
|
||||
|
||||
// DecodeContext is passed to a Mapper's Decode().
|
||||
//
|
||||
// It contains the Value being decoded into and the Scanner to parse from.
|
||||
type DecodeContext struct {
|
||||
// Value being decoded into.
|
||||
Value *Value
|
||||
// Scan contains the input to scan into Target.
|
||||
Scan *Scanner
|
||||
}
|
||||
|
||||
// WithScanner creates a clone of this context with a new Scanner.
|
||||
func (r *DecodeContext) WithScanner(scan *Scanner) *DecodeContext {
|
||||
return &DecodeContext{
|
||||
Value: r.Value,
|
||||
Scan: scan,
|
||||
}
|
||||
}
|
||||
|
||||
// MapperValue may be implemented by fields in order to provide custom mapping.
|
||||
// Mappers may additionally implement PlaceHolderProvider to provide custom placeholder text.
|
||||
type MapperValue interface {
|
||||
Decode(ctx *DecodeContext) error
|
||||
}
|
||||
|
||||
// BoolMapperValue may be implemented by fields in order to provide custom mappings for boolean values.
|
||||
type BoolMapperValue interface {
|
||||
MapperValue
|
||||
IsBool() bool
|
||||
}
|
||||
|
||||
type mapperValueAdapter struct {
|
||||
isBool bool
|
||||
}
|
||||
|
||||
func (m *mapperValueAdapter) Decode(ctx *DecodeContext, target reflect.Value) error {
|
||||
if target.Type().Implements(mapperValueType) {
|
||||
return target.Interface().(MapperValue).Decode(ctx) //nolint
|
||||
}
|
||||
return target.Addr().Interface().(MapperValue).Decode(ctx) //nolint
|
||||
}
|
||||
|
||||
func (m *mapperValueAdapter) IsBool() bool {
|
||||
return m.isBool
|
||||
}
|
||||
|
||||
type textUnmarshalerAdapter struct{}
|
||||
|
||||
func (m *textUnmarshalerAdapter) Decode(ctx *DecodeContext, target reflect.Value) error {
|
||||
var value string
|
||||
err := ctx.Scan.PopValueInto("value", &value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if target.Type().Implements(textUnmarshalerType) {
|
||||
return target.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value)) //nolint
|
||||
}
|
||||
return target.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value)) //nolint
|
||||
}
|
||||
|
||||
type binaryUnmarshalerAdapter struct{}
|
||||
|
||||
func (m *binaryUnmarshalerAdapter) Decode(ctx *DecodeContext, target reflect.Value) error {
|
||||
var value string
|
||||
err := ctx.Scan.PopValueInto("value", &value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if target.Type().Implements(binaryUnmarshalerType) {
|
||||
return target.Interface().(encoding.BinaryUnmarshaler).UnmarshalBinary([]byte(value)) //nolint
|
||||
}
|
||||
return target.Addr().Interface().(encoding.BinaryUnmarshaler).UnmarshalBinary([]byte(value)) //nolint
|
||||
}
|
||||
|
||||
type jsonUnmarshalerAdapter struct{}
|
||||
|
||||
func (j *jsonUnmarshalerAdapter) Decode(ctx *DecodeContext, target reflect.Value) error {
|
||||
var value string
|
||||
err := ctx.Scan.PopValueInto("value", &value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if target.Type().Implements(jsonUnmarshalerType) {
|
||||
return target.Interface().(json.Unmarshaler).UnmarshalJSON([]byte(value)) //nolint
|
||||
}
|
||||
return target.Addr().Interface().(json.Unmarshaler).UnmarshalJSON([]byte(value)) //nolint
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// VarsContributor can be implemented by a Mapper to contribute Vars during interpolation.
|
||||
type VarsContributor interface {
|
||||
Vars(ctx *Value) Vars
|
||||
}
|
||||
|
||||
// A BoolMapper is a Mapper to a value that is a boolean.
|
||||
//
|
||||
// This is used solely for formatting help.
|
||||
type BoolMapper interface {
|
||||
Mapper
|
||||
IsBool() bool
|
||||
}
|
||||
|
||||
// BoolMapperExt allows a Mapper to dynamically determine if a value is a boolean.
|
||||
type BoolMapperExt interface {
|
||||
Mapper
|
||||
IsBoolFromValue(v reflect.Value) bool
|
||||
}
|
||||
|
||||
// A MapperFunc is a single function that complies with the Mapper interface.
|
||||
type MapperFunc func(ctx *DecodeContext, target reflect.Value) error
|
||||
|
||||
func (m MapperFunc) Decode(ctx *DecodeContext, target reflect.Value) error { //nolint: revive
|
||||
return m(ctx, target)
|
||||
}
|
||||
|
||||
// A Registry contains a set of mappers and supporting lookup methods.
|
||||
type Registry struct {
|
||||
names map[string]Mapper
|
||||
types map[reflect.Type]Mapper
|
||||
kinds map[reflect.Kind]Mapper
|
||||
values map[reflect.Value]Mapper
|
||||
}
|
||||
|
||||
// NewRegistry creates a new (empty) Registry.
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{
|
||||
names: map[string]Mapper{},
|
||||
types: map[reflect.Type]Mapper{},
|
||||
kinds: map[reflect.Kind]Mapper{},
|
||||
values: map[reflect.Value]Mapper{},
|
||||
}
|
||||
}
|
||||
|
||||
// ForNamedValue finds a mapper for a value with a user-specified name.
|
||||
//
|
||||
// Will return nil if a mapper can not be determined.
|
||||
func (r *Registry) ForNamedValue(name string, value reflect.Value) Mapper {
|
||||
if mapper, ok := r.names[name]; ok {
|
||||
return mapper
|
||||
}
|
||||
return r.ForValue(value)
|
||||
}
|
||||
|
||||
// ForValue looks up the Mapper for a reflect.Value.
|
||||
func (r *Registry) ForValue(value reflect.Value) Mapper {
|
||||
if mapper, ok := r.values[value]; ok {
|
||||
return mapper
|
||||
}
|
||||
return r.ForType(value.Type())
|
||||
}
|
||||
|
||||
// ForNamedType finds a mapper for a type with a user-specified name.
|
||||
//
|
||||
// Will return nil if a mapper can not be determined.
|
||||
func (r *Registry) ForNamedType(name string, typ reflect.Type) Mapper {
|
||||
if mapper, ok := r.names[name]; ok {
|
||||
return mapper
|
||||
}
|
||||
return r.ForType(typ)
|
||||
}
|
||||
|
||||
// ForType finds a mapper from a type, by type, then kind.
|
||||
//
|
||||
// Will return nil if a mapper can not be determined.
|
||||
func (r *Registry) ForType(typ reflect.Type) Mapper {
|
||||
// Check if the type implements MapperValue.
|
||||
for _, impl := range []reflect.Type{typ, reflect.PtrTo(typ)} {
|
||||
if impl.Implements(mapperValueType) {
|
||||
// FIXME: This should pass in the bool mapper.
|
||||
return &mapperValueAdapter{impl.Implements(boolMapperValueType)}
|
||||
}
|
||||
}
|
||||
// Next, try explicitly registered types.
|
||||
var mapper Mapper
|
||||
var ok bool
|
||||
if mapper, ok = r.types[typ]; ok {
|
||||
return mapper
|
||||
}
|
||||
// Next try stdlib unmarshaler interfaces.
|
||||
for _, impl := range []reflect.Type{typ, reflect.PtrTo(typ)} {
|
||||
switch {
|
||||
case impl.Implements(textUnmarshalerType):
|
||||
return &textUnmarshalerAdapter{}
|
||||
case impl.Implements(binaryUnmarshalerType):
|
||||
return &binaryUnmarshalerAdapter{}
|
||||
case impl.Implements(jsonUnmarshalerType):
|
||||
return &jsonUnmarshalerAdapter{}
|
||||
}
|
||||
}
|
||||
// Finally try registered kinds.
|
||||
if mapper, ok = r.kinds[typ.Kind()]; ok {
|
||||
return mapper
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterKind registers a Mapper for a reflect.Kind.
|
||||
func (r *Registry) RegisterKind(kind reflect.Kind, mapper Mapper) *Registry {
|
||||
r.kinds[kind] = mapper
|
||||
return r
|
||||
}
|
||||
|
||||
// RegisterName registers a mapper to be used if the value mapper has a "type" tag matching name.
|
||||
//
|
||||
// eg.
|
||||
//
|
||||
// Mapper string `kong:"type='colour'`
|
||||
// registry.RegisterName("colour", ...)
|
||||
func (r *Registry) RegisterName(name string, mapper Mapper) *Registry {
|
||||
r.names[name] = mapper
|
||||
return r
|
||||
}
|
||||
|
||||
// RegisterType registers a Mapper for a reflect.Type.
|
||||
func (r *Registry) RegisterType(typ reflect.Type, mapper Mapper) *Registry {
|
||||
r.types[typ] = mapper
|
||||
return r
|
||||
}
|
||||
|
||||
// RegisterValue registers a Mapper by pointer to the field value.
|
||||
func (r *Registry) RegisterValue(ptr any, mapper Mapper) *Registry {
|
||||
key := reflect.ValueOf(ptr)
|
||||
if key.Kind() != reflect.Ptr {
|
||||
panic("expected a pointer")
|
||||
}
|
||||
key = key.Elem()
|
||||
r.values[key] = mapper
|
||||
return r
|
||||
}
|
||||
|
||||
// RegisterDefaults registers Mappers for all builtin supported Go types and some common stdlib types.
|
||||
func (r *Registry) RegisterDefaults() *Registry {
|
||||
return r.RegisterKind(reflect.Int, intDecoder(bits.UintSize)).
|
||||
RegisterKind(reflect.Int8, intDecoder(8)).
|
||||
RegisterKind(reflect.Int16, intDecoder(16)).
|
||||
RegisterKind(reflect.Int32, intDecoder(32)).
|
||||
RegisterKind(reflect.Int64, intDecoder(64)).
|
||||
RegisterKind(reflect.Uint, uintDecoder(bits.UintSize)).
|
||||
RegisterKind(reflect.Uint8, uintDecoder(8)).
|
||||
RegisterKind(reflect.Uint16, uintDecoder(16)).
|
||||
RegisterKind(reflect.Uint32, uintDecoder(32)).
|
||||
RegisterKind(reflect.Uint64, uintDecoder(64)).
|
||||
RegisterKind(reflect.Float32, floatDecoder(32)).
|
||||
RegisterKind(reflect.Float64, floatDecoder(64)).
|
||||
RegisterKind(reflect.String, MapperFunc(func(ctx *DecodeContext, target reflect.Value) error {
|
||||
return ctx.Scan.PopValueInto("string", target.Addr().Interface())
|
||||
})).
|
||||
RegisterKind(reflect.Bool, boolMapper{}).
|
||||
RegisterKind(reflect.Slice, sliceDecoder(r)).
|
||||
RegisterKind(reflect.Map, mapDecoder(r)).
|
||||
RegisterType(reflect.TypeOf(time.Time{}), timeDecoder()).
|
||||
RegisterType(reflect.TypeOf(time.Duration(0)), durationDecoder()).
|
||||
RegisterType(reflect.TypeOf(&url.URL{}), urlMapper()).
|
||||
RegisterType(reflect.TypeOf(&os.File{}), fileMapper(r)).
|
||||
RegisterName("path", pathMapper(r)).
|
||||
RegisterName("existingfile", existingFileMapper(r)).
|
||||
RegisterName("existingdir", existingDirMapper(r)).
|
||||
RegisterName("counter", counterMapper()).
|
||||
RegisterName("filecontent", fileContentMapper(r)).
|
||||
RegisterKind(reflect.Ptr, ptrMapper{r})
|
||||
}
|
||||
|
||||
type boolMapper struct{}
|
||||
|
||||
func (boolMapper) Decode(ctx *DecodeContext, target reflect.Value) error {
|
||||
if ctx.Scan.Peek().Type == FlagValueToken {
|
||||
token := ctx.Scan.Pop()
|
||||
switch v := token.Value.(type) {
|
||||
case string:
|
||||
v = strings.ToLower(v)
|
||||
switch v {
|
||||
case "true", "1", "yes":
|
||||
target.SetBool(true)
|
||||
|
||||
case "false", "0", "no":
|
||||
target.SetBool(false)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("bool value must be true, 1, yes, false, 0 or no but got %q", v)
|
||||
}
|
||||
|
||||
case bool:
|
||||
target.SetBool(v)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("expected bool but got %q (%T)", token.Value, token.Value)
|
||||
}
|
||||
} else {
|
||||
target.SetBool(true)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (boolMapper) IsBool() bool { return true }
|
||||
|
||||
func durationDecoder() MapperFunc {
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
t, err := ctx.Scan.PopValue("duration")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var d time.Duration
|
||||
switch v := t.Value.(type) {
|
||||
case string:
|
||||
d, err = time.ParseDuration(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("expected duration but got %q: %v", v, err)
|
||||
}
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
|
||||
d = reflect.ValueOf(v).Convert(reflect.TypeOf(time.Duration(0))).Interface().(time.Duration) //nolint: forcetypeassert
|
||||
default:
|
||||
return fmt.Errorf("expected duration but got %q", v)
|
||||
}
|
||||
target.Set(reflect.ValueOf(d))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func timeDecoder() MapperFunc {
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
format := time.RFC3339
|
||||
if ctx.Value.Format != "" {
|
||||
format = ctx.Value.Format
|
||||
}
|
||||
var value string
|
||||
if err := ctx.Scan.PopValueInto("time", &value); err != nil {
|
||||
return err
|
||||
}
|
||||
t, err := time.Parse(format, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
target.Set(reflect.ValueOf(t))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func intDecoder(bits int) MapperFunc { //nolint: dupl
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
t, err := ctx.Scan.PopValue("int")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var sv string
|
||||
switch v := t.Value.(type) {
|
||||
case string:
|
||||
sv = v
|
||||
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
||||
sv = fmt.Sprintf("%v", v)
|
||||
|
||||
case float32, float64:
|
||||
sv = fmt.Sprintf("%0.f", v)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("expected an int but got %q (%T)", t, t.Value)
|
||||
}
|
||||
n, err := strconv.ParseInt(sv, 0, bits)
|
||||
if err != nil {
|
||||
return fmt.Errorf("expected a valid %d bit int but got %q", bits, sv)
|
||||
}
|
||||
target.SetInt(n)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func uintDecoder(bits int) MapperFunc { //nolint: dupl
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
t, err := ctx.Scan.PopValue("uint")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var sv string
|
||||
switch v := t.Value.(type) {
|
||||
case string:
|
||||
sv = v
|
||||
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
||||
sv = fmt.Sprintf("%v", v)
|
||||
|
||||
case float32, float64:
|
||||
sv = fmt.Sprintf("%0.f", v)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("expected an int but got %q (%T)", t, t.Value)
|
||||
}
|
||||
n, err := strconv.ParseUint(sv, 0, bits)
|
||||
if err != nil {
|
||||
return fmt.Errorf("expected a valid %d bit uint but got %q", bits, sv)
|
||||
}
|
||||
target.SetUint(n)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func floatDecoder(bits int) MapperFunc {
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
t, err := ctx.Scan.PopValue("float")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch v := t.Value.(type) {
|
||||
case string:
|
||||
n, err := strconv.ParseFloat(v, bits)
|
||||
if err != nil {
|
||||
return fmt.Errorf("expected a float but got %q (%T)", t, t.Value)
|
||||
}
|
||||
target.SetFloat(n)
|
||||
|
||||
case float32:
|
||||
target.SetFloat(float64(v))
|
||||
|
||||
case float64:
|
||||
target.SetFloat(v)
|
||||
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
||||
target.Set(reflect.ValueOf(v))
|
||||
|
||||
default:
|
||||
return fmt.Errorf("expected an int but got %q (%T)", t, t.Value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func mapDecoder(r *Registry) MapperFunc {
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
if target.IsNil() {
|
||||
target.Set(reflect.MakeMap(target.Type()))
|
||||
}
|
||||
el := target.Type()
|
||||
mapsep := ctx.Value.Tag.MapSep
|
||||
var childScanner *Scanner
|
||||
if ctx.Value.Flag != nil {
|
||||
t := ctx.Scan.Pop()
|
||||
// If decoding a flag, we need an value.
|
||||
if t.IsEOL() {
|
||||
return fmt.Errorf("missing value, expecting \"<key>=<value>%c...\"", mapsep)
|
||||
}
|
||||
switch v := t.Value.(type) {
|
||||
case string:
|
||||
childScanner = ScanAsType(t.Type, SplitEscaped(v, mapsep)...)
|
||||
|
||||
case []map[string]any:
|
||||
for _, m := range v {
|
||||
err := jsonTranscode(m, target.Addr().Interface())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case map[string]any:
|
||||
return jsonTranscode(v, target.Addr().Interface())
|
||||
|
||||
default:
|
||||
return fmt.Errorf("invalid map value %q (of type %T)", t, t.Value)
|
||||
}
|
||||
} else {
|
||||
tokens := ctx.Scan.PopWhile(func(t Token) bool { return t.IsValue() })
|
||||
childScanner = ScanFromTokens(tokens...)
|
||||
}
|
||||
for !childScanner.Peek().IsEOL() {
|
||||
var token string
|
||||
err := childScanner.PopValueInto("map", &token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parts := strings.SplitN(token, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("expected \"<key>=<value>\" but got %q", token)
|
||||
}
|
||||
key, value := parts[0], parts[1]
|
||||
|
||||
keyTypeName, valueTypeName := "", ""
|
||||
if typ := ctx.Value.Tag.Type; typ != "" {
|
||||
parts := strings.Split(typ, ":")
|
||||
if len(parts) != 2 {
|
||||
return errors.New("type:\"\" on map field must be in the form \"[<keytype>]:[<valuetype>]\"")
|
||||
}
|
||||
keyTypeName, valueTypeName = parts[0], parts[1]
|
||||
}
|
||||
|
||||
keyScanner := ScanAsType(FlagValueToken, key)
|
||||
keyDecoder := r.ForNamedType(keyTypeName, el.Key())
|
||||
keyValue := reflect.New(el.Key()).Elem()
|
||||
if err := keyDecoder.Decode(ctx.WithScanner(keyScanner), keyValue); err != nil {
|
||||
return fmt.Errorf("invalid map key %q", key)
|
||||
}
|
||||
|
||||
valueScanner := ScanAsType(FlagValueToken, value)
|
||||
valueDecoder := r.ForNamedType(valueTypeName, el.Elem())
|
||||
valueValue := reflect.New(el.Elem()).Elem()
|
||||
if err := valueDecoder.Decode(ctx.WithScanner(valueScanner), valueValue); err != nil {
|
||||
return fmt.Errorf("invalid map value %q", value)
|
||||
}
|
||||
|
||||
target.SetMapIndex(keyValue, valueValue)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func sliceDecoder(r *Registry) MapperFunc {
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
el := target.Type().Elem()
|
||||
sep := ctx.Value.Tag.Sep
|
||||
var childScanner *Scanner
|
||||
if ctx.Value.Flag != nil {
|
||||
t := ctx.Scan.Pop()
|
||||
// If decoding a flag, we need a value.
|
||||
tail := ""
|
||||
if sep != -1 {
|
||||
tail += string(sep) + "..."
|
||||
}
|
||||
if t.IsEOL() {
|
||||
return fmt.Errorf("missing value, expecting \"<arg>%s\"", tail)
|
||||
}
|
||||
switch v := t.Value.(type) {
|
||||
case string:
|
||||
childScanner = ScanAsType(t.Type, SplitEscaped(v, sep)...)
|
||||
|
||||
case []any:
|
||||
return jsonTranscode(v, target.Addr().Interface())
|
||||
|
||||
default:
|
||||
v = []any{v}
|
||||
return jsonTranscode(v, target.Addr().Interface())
|
||||
}
|
||||
} else {
|
||||
tokens := ctx.Scan.PopWhile(func(t Token) bool { return t.IsValue() })
|
||||
childScanner = ScanFromTokens(tokens...)
|
||||
}
|
||||
childDecoder := r.ForNamedType(ctx.Value.Tag.Type, el)
|
||||
if childDecoder == nil {
|
||||
return fmt.Errorf("no mapper for element type of %s", target.Type())
|
||||
}
|
||||
for !childScanner.Peek().IsEOL() {
|
||||
childValue := reflect.New(el).Elem()
|
||||
err := childDecoder.Decode(ctx.WithScanner(childScanner), childValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
target.Set(reflect.Append(target, childValue))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func pathMapper(r *Registry) MapperFunc {
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
if target.Kind() == reflect.Slice {
|
||||
return sliceDecoder(r)(ctx, target)
|
||||
}
|
||||
if target.Kind() == reflect.Ptr && target.Elem().Kind() == reflect.String {
|
||||
if target.IsNil() {
|
||||
return nil
|
||||
}
|
||||
target = target.Elem()
|
||||
}
|
||||
if target.Kind() != reflect.String {
|
||||
return fmt.Errorf("\"path\" type must be applied to a string not %s", target.Type())
|
||||
}
|
||||
var path string
|
||||
err := ctx.Scan.PopValueInto("file", &path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if path != "-" {
|
||||
path = ExpandPath(path)
|
||||
}
|
||||
target.SetString(path)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func fileMapper(r *Registry) MapperFunc {
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
if target.Kind() == reflect.Slice {
|
||||
return sliceDecoder(r)(ctx, target)
|
||||
}
|
||||
var path string
|
||||
err := ctx.Scan.PopValueInto("file", &path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var file *os.File
|
||||
if path == "-" {
|
||||
file = os.Stdin
|
||||
} else {
|
||||
path = ExpandPath(path)
|
||||
file, err = os.Open(path) //nolint: gosec
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
target.Set(reflect.ValueOf(file))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func existingFileMapper(r *Registry) MapperFunc {
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
if target.Kind() == reflect.Slice {
|
||||
return sliceDecoder(r)(ctx, target)
|
||||
}
|
||||
if target.Kind() != reflect.String {
|
||||
return fmt.Errorf("\"existingfile\" type must be applied to a string not %s", target.Type())
|
||||
}
|
||||
var path string
|
||||
err := ctx.Scan.PopValueInto("file", &path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !ctx.Value.Active || (ctx.Value.Set && ctx.Value.Target.Type() == target.Type()) {
|
||||
// early return to avoid checking extra files that may not exist;
|
||||
// this hack only works because the value provided on the cli is
|
||||
// checked before the default value is checked (if default is set).
|
||||
return nil
|
||||
}
|
||||
|
||||
if path != "-" {
|
||||
path = ExpandPath(path)
|
||||
stat, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stat.IsDir() {
|
||||
return fmt.Errorf("%q exists but is a directory", path)
|
||||
}
|
||||
}
|
||||
target.SetString(path)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func existingDirMapper(r *Registry) MapperFunc {
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
if target.Kind() == reflect.Slice {
|
||||
return sliceDecoder(r)(ctx, target)
|
||||
}
|
||||
if target.Kind() != reflect.String {
|
||||
return fmt.Errorf("\"existingdir\" must be applied to a string not %s", target.Type())
|
||||
}
|
||||
var path string
|
||||
err := ctx.Scan.PopValueInto("file", &path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !ctx.Value.Active || (ctx.Value.Set && ctx.Value.Target.Type() == target.Type()) {
|
||||
// early return to avoid checking extra dirs that may not exist;
|
||||
// this hack only works because the value provided on the cli is
|
||||
// checked before the default value is checked (if default is set).
|
||||
return nil
|
||||
}
|
||||
|
||||
path = ExpandPath(path)
|
||||
stat, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !stat.IsDir() {
|
||||
return fmt.Errorf("%q exists but is not a directory", path)
|
||||
}
|
||||
target.SetString(path)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func fileContentMapper(r *Registry) MapperFunc {
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
if target.Kind() != reflect.Slice && target.Elem().Kind() != reflect.Uint8 {
|
||||
return fmt.Errorf("\"filecontent\" must be applied to []byte not %s", target.Type())
|
||||
}
|
||||
var path string
|
||||
err := ctx.Scan.PopValueInto("file", &path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !ctx.Value.Active || ctx.Value.Set {
|
||||
// early return to avoid checking extra dirs that may not exist;
|
||||
// this hack only works because the value provided on the cli is
|
||||
// checked before the default value is checked (if default is set).
|
||||
return nil
|
||||
}
|
||||
|
||||
var data []byte
|
||||
if path != "-" {
|
||||
path = ExpandPath(path)
|
||||
data, err = os.ReadFile(path) //nolint:gosec
|
||||
} else {
|
||||
data, err = io.ReadAll(os.Stdin)
|
||||
}
|
||||
if err != nil {
|
||||
if info, statErr := os.Stat(path); statErr == nil && info.IsDir() {
|
||||
return fmt.Errorf("%q exists but is a directory: %w", path, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
target.SetBytes(data)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type ptrMapper struct {
|
||||
r *Registry
|
||||
}
|
||||
|
||||
var _ BoolMapperExt = (*ptrMapper)(nil)
|
||||
|
||||
// IsBoolFromValue implements BoolMapperExt
|
||||
func (p ptrMapper) IsBoolFromValue(target reflect.Value) bool {
|
||||
elem := reflect.New(target.Type().Elem()).Elem()
|
||||
nestedMapper := p.r.ForValue(elem)
|
||||
if nestedMapper == nil {
|
||||
return false
|
||||
}
|
||||
if bm, ok := nestedMapper.(BoolMapper); ok && bm.IsBool() {
|
||||
return true
|
||||
}
|
||||
if bm, ok := nestedMapper.(BoolMapperExt); ok && bm.IsBoolFromValue(target) {
|
||||
return true
|
||||
}
|
||||
return target.Kind() == reflect.Ptr && target.Type().Elem().Kind() == reflect.Bool
|
||||
}
|
||||
|
||||
func (p ptrMapper) Decode(ctx *DecodeContext, target reflect.Value) error {
|
||||
elem := reflect.New(target.Type().Elem()).Elem()
|
||||
nestedMapper := p.r.ForValue(elem)
|
||||
if nestedMapper == nil {
|
||||
return fmt.Errorf("cannot find mapper for %v", target.Type().Elem().String())
|
||||
}
|
||||
err := nestedMapper.Decode(ctx, elem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
target.Set(elem.Addr())
|
||||
return nil
|
||||
}
|
||||
|
||||
func counterMapper() MapperFunc {
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
if ctx.Scan.Peek().Type == FlagValueToken {
|
||||
t, err := ctx.Scan.PopValue("counter")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch v := t.Value.(type) {
|
||||
case string:
|
||||
n, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("expected a counter but got %q (%T)", t, t.Value)
|
||||
}
|
||||
target.SetInt(n)
|
||||
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
||||
target.Set(reflect.ValueOf(v))
|
||||
|
||||
default:
|
||||
return fmt.Errorf("expected a counter but got %q (%T)", t, t.Value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch target.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
target.SetInt(target.Int() + 1)
|
||||
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
target.SetUint(target.Uint() + 1)
|
||||
|
||||
case reflect.Float32, reflect.Float64:
|
||||
target.SetFloat(target.Float() + 1)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("type:\"counter\" must be used with a numeric field")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func urlMapper() MapperFunc {
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
var urlStr string
|
||||
err := ctx.Scan.PopValueInto("url", &urlStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
url, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
target.Set(reflect.ValueOf(url))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// SplitEscaped splits a string on a separator.
|
||||
//
|
||||
// It differs from strings.Split() in that the separator can exist in a field by escaping it with a \. eg.
|
||||
//
|
||||
// SplitEscaped(`hello\,there,bob`, ',') == []string{"hello,there", "bob"}
|
||||
func SplitEscaped(s string, sep rune) (out []string) {
|
||||
if sep == -1 {
|
||||
return []string{s}
|
||||
}
|
||||
escaped := false
|
||||
token := ""
|
||||
for i, ch := range s {
|
||||
switch {
|
||||
case escaped:
|
||||
if ch != sep {
|
||||
token += `\`
|
||||
}
|
||||
token += string(ch)
|
||||
escaped = false
|
||||
case ch == '\\' && i < len(s)-1:
|
||||
escaped = true
|
||||
case ch == sep && !escaped:
|
||||
out = append(out, token)
|
||||
token = ""
|
||||
escaped = false
|
||||
default:
|
||||
token += string(ch)
|
||||
}
|
||||
}
|
||||
if token != "" {
|
||||
out = append(out, token)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// JoinEscaped joins a slice of strings on sep, but also escapes any instances of sep in the fields with \. eg.
|
||||
//
|
||||
// JoinEscaped([]string{"hello,there", "bob"}, ',') == `hello\,there,bob`
|
||||
func JoinEscaped(s []string, sep rune) string {
|
||||
escaped := []string{}
|
||||
for _, e := range s {
|
||||
escaped = append(escaped, strings.ReplaceAll(e, string(sep), `\`+string(sep)))
|
||||
}
|
||||
return strings.Join(escaped, string(sep))
|
||||
}
|
||||
|
||||
// NamedFileContentFlag is a flag value that loads a file's contents and filename into its value.
|
||||
type NamedFileContentFlag struct {
|
||||
Filename string
|
||||
Contents []byte
|
||||
}
|
||||
|
||||
func (f *NamedFileContentFlag) Decode(ctx *DecodeContext) error { //nolint: revive
|
||||
var filename string
|
||||
err := ctx.Scan.PopValueInto("filename", &filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// This allows unsetting of file content flags.
|
||||
if filename == "" {
|
||||
*f = NamedFileContentFlag{}
|
||||
return nil
|
||||
}
|
||||
filename = ExpandPath(filename)
|
||||
data, err := os.ReadFile(filename) //nolint: gosec
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open %q: %v", filename, err)
|
||||
}
|
||||
f.Contents = data
|
||||
f.Filename = filename
|
||||
return nil
|
||||
}
|
||||
|
||||
// FileContentFlag is a flag value that loads a file's contents into its value.
|
||||
type FileContentFlag []byte
|
||||
|
||||
func (f *FileContentFlag) Decode(ctx *DecodeContext) error { //nolint: revive
|
||||
var filename string
|
||||
err := ctx.Scan.PopValueInto("filename", &filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// This allows unsetting of file content flags.
|
||||
if filename == "" {
|
||||
*f = nil
|
||||
return nil
|
||||
}
|
||||
filename = ExpandPath(filename)
|
||||
data, err := os.ReadFile(filename) //nolint: gosec
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open %q: %v", filename, err)
|
||||
}
|
||||
*f = data
|
||||
return nil
|
||||
}
|
||||
|
||||
func jsonTranscode(in, out any) error {
|
||||
data, err := json.Marshal(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = json.Unmarshal(data, out); err != nil {
|
||||
return fmt.Errorf("%#v -> %T: %v", in, out, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
514
vendor/github.com/alecthomas/kong/model.go
generated
vendored
Normal file
514
vendor/github.com/alecthomas/kong/model.go
generated
vendored
Normal file
@@ -0,0 +1,514 @@
|
||||
package kong
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A Visitable component in the model.
|
||||
type Visitable interface {
|
||||
node()
|
||||
}
|
||||
|
||||
// Application is the root of the Kong model.
|
||||
type Application struct {
|
||||
*Node
|
||||
// Help flag, if the NoDefaultHelp() option is not specified.
|
||||
HelpFlag *Flag
|
||||
}
|
||||
|
||||
// Argument represents a branching positional argument.
|
||||
type Argument = Node
|
||||
|
||||
// Command represents a command in the CLI.
|
||||
type Command = Node
|
||||
|
||||
// NodeType is an enum representing the type of a Node.
|
||||
type NodeType int
|
||||
|
||||
// Node type enumerations.
|
||||
const (
|
||||
ApplicationNode NodeType = iota
|
||||
CommandNode
|
||||
ArgumentNode
|
||||
)
|
||||
|
||||
// Node is a branch in the CLI. ie. a command or positional argument.
|
||||
type Node struct {
|
||||
Type NodeType
|
||||
Parent *Node
|
||||
Name string
|
||||
Help string // Short help displayed in summaries.
|
||||
Detail string // Detailed help displayed when describing command/arg alone.
|
||||
Group *Group
|
||||
Hidden bool
|
||||
Flags []*Flag
|
||||
Positional []*Positional
|
||||
Children []*Node
|
||||
DefaultCmd *Node
|
||||
Target reflect.Value // Pointer to the value in the grammar that this Node is associated with.
|
||||
Tag *Tag
|
||||
Aliases []string
|
||||
Passthrough bool // Set to true to stop flag parsing when encountered.
|
||||
Active bool // Denotes the node is part of an active branch in the CLI.
|
||||
|
||||
Argument *Value // Populated when Type is ArgumentNode.
|
||||
}
|
||||
|
||||
func (*Node) node() {}
|
||||
|
||||
// Leaf returns true if this Node is a leaf node.
|
||||
func (n *Node) Leaf() bool {
|
||||
return len(n.Children) == 0
|
||||
}
|
||||
|
||||
// Find a command/argument/flag by pointer to its field.
|
||||
//
|
||||
// Returns nil if not found. Panics if ptr is not a pointer.
|
||||
func (n *Node) Find(ptr any) *Node {
|
||||
key := reflect.ValueOf(ptr)
|
||||
if key.Kind() != reflect.Ptr {
|
||||
panic("expected a pointer")
|
||||
}
|
||||
return n.findNode(key)
|
||||
}
|
||||
|
||||
func (n *Node) findNode(key reflect.Value) *Node {
|
||||
if n.Target == key {
|
||||
return n
|
||||
}
|
||||
for _, child := range n.Children {
|
||||
if found := child.findNode(key); found != nil {
|
||||
return found
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllFlags returns flags from all ancestor branches encountered.
|
||||
//
|
||||
// If "hide" is true hidden flags will be omitted.
|
||||
func (n *Node) AllFlags(hide bool) (out [][]*Flag) {
|
||||
if n.Parent != nil {
|
||||
out = append(out, n.Parent.AllFlags(hide)...)
|
||||
}
|
||||
group := []*Flag{}
|
||||
for _, flag := range n.Flags {
|
||||
if !hide || !flag.Hidden {
|
||||
flag.Active = true
|
||||
group = append(group, flag)
|
||||
}
|
||||
}
|
||||
if len(group) > 0 {
|
||||
out = append(out, group)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Leaves returns the leaf commands/arguments under Node.
|
||||
//
|
||||
// If "hidden" is true hidden leaves will be omitted.
|
||||
func (n *Node) Leaves(hide bool) (out []*Node) {
|
||||
_ = Visit(n, func(nd Visitable, next Next) error {
|
||||
if nd == n {
|
||||
return next(nil)
|
||||
}
|
||||
if node, ok := nd.(*Node); ok {
|
||||
if hide && node.Hidden {
|
||||
return nil
|
||||
}
|
||||
if len(node.Children) == 0 && node.Type != ApplicationNode {
|
||||
out = append(out, node)
|
||||
}
|
||||
}
|
||||
return next(nil)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Depth of the command from the application root.
|
||||
func (n *Node) Depth() int {
|
||||
depth := 0
|
||||
p := n.Parent
|
||||
for p != nil && p.Type != ApplicationNode {
|
||||
depth++
|
||||
p = p.Parent
|
||||
}
|
||||
return depth
|
||||
}
|
||||
|
||||
// Summary help string for the node (not including application name).
|
||||
func (n *Node) Summary() string {
|
||||
summary := n.Path()
|
||||
if flags := n.FlagSummary(true); flags != "" {
|
||||
summary += " " + flags
|
||||
}
|
||||
args := []string{}
|
||||
optional := 0
|
||||
for _, arg := range n.Positional {
|
||||
argSummary := arg.Summary()
|
||||
if arg.Tag.Optional {
|
||||
optional++
|
||||
argSummary = strings.TrimRight(argSummary, "]")
|
||||
}
|
||||
args = append(args, argSummary)
|
||||
}
|
||||
if len(args) != 0 {
|
||||
summary += " " + strings.Join(args, " ") + strings.Repeat("]", optional)
|
||||
} else if len(n.Children) > 0 {
|
||||
summary += " <command>"
|
||||
}
|
||||
allFlags := n.Flags
|
||||
if n.Parent != nil {
|
||||
allFlags = append(allFlags, n.Parent.Flags...)
|
||||
}
|
||||
for _, flag := range allFlags {
|
||||
if _, ok := flag.Target.Interface().(helpFlag); ok {
|
||||
continue
|
||||
}
|
||||
if !flag.Required {
|
||||
summary += " [flags]"
|
||||
break
|
||||
}
|
||||
}
|
||||
return summary
|
||||
}
|
||||
|
||||
// FlagSummary for the node.
|
||||
func (n *Node) FlagSummary(hide bool) string {
|
||||
required := []string{}
|
||||
count := 0
|
||||
for _, group := range n.AllFlags(hide) {
|
||||
for _, flag := range group {
|
||||
count++
|
||||
if flag.Required {
|
||||
required = append(required, flag.Summary())
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.Join(required, " ")
|
||||
}
|
||||
|
||||
// FullPath is like Path() but includes the Application root node.
|
||||
func (n *Node) FullPath() string {
|
||||
root := n
|
||||
for root.Parent != nil {
|
||||
root = root.Parent
|
||||
}
|
||||
return strings.TrimSpace(root.Name + " " + n.Path())
|
||||
}
|
||||
|
||||
// Vars returns the combined Vars defined by all ancestors of this Node.
|
||||
func (n *Node) Vars() Vars {
|
||||
if n == nil {
|
||||
return Vars{}
|
||||
}
|
||||
return n.Parent.Vars().CloneWith(n.Tag.Vars)
|
||||
}
|
||||
|
||||
// Path through ancestors to this Node.
|
||||
func (n *Node) Path() (out string) {
|
||||
if n.Parent != nil {
|
||||
out += " " + n.Parent.Path()
|
||||
}
|
||||
switch n.Type {
|
||||
case CommandNode:
|
||||
out += " " + n.Name
|
||||
if len(n.Aliases) > 0 {
|
||||
out += fmt.Sprintf(" (%s)", strings.Join(n.Aliases, ","))
|
||||
}
|
||||
case ArgumentNode:
|
||||
out += " " + "<" + n.Name + ">"
|
||||
default:
|
||||
}
|
||||
return strings.TrimSpace(out)
|
||||
}
|
||||
|
||||
// ClosestGroup finds the first non-nil group in this node and its ancestors.
|
||||
func (n *Node) ClosestGroup() *Group {
|
||||
switch {
|
||||
case n.Group != nil:
|
||||
return n.Group
|
||||
case n.Parent != nil:
|
||||
return n.Parent.ClosestGroup()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// A Value is either a flag or a variable positional argument.
|
||||
type Value struct {
|
||||
Flag *Flag // Nil if positional argument.
|
||||
Name string
|
||||
Help string
|
||||
OrigHelp string // Original help string, without interpolated variables.
|
||||
HasDefault bool
|
||||
Default string
|
||||
DefaultValue reflect.Value
|
||||
Enum string
|
||||
Mapper Mapper
|
||||
Tag *Tag
|
||||
Target reflect.Value
|
||||
Required bool
|
||||
Set bool // Set to true when this value is set through some mechanism.
|
||||
Format string // Formatting directive, if applicable.
|
||||
Position int // Position (for positional arguments).
|
||||
Passthrough bool // Deprecated: Use PassthroughMode instead. Set to true to stop flag parsing when encountered.
|
||||
PassthroughMode PassthroughMode //
|
||||
Active bool // Denotes the value is part of an active branch in the CLI.
|
||||
}
|
||||
|
||||
// EnumMap returns a map of the enums in this value.
|
||||
func (v *Value) EnumMap() map[string]bool {
|
||||
parts := strings.Split(v.Enum, ",")
|
||||
out := make(map[string]bool, len(parts))
|
||||
for _, part := range parts {
|
||||
out[strings.TrimSpace(part)] = true
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// EnumSlice returns a slice of the enums in this value.
|
||||
func (v *Value) EnumSlice() []string {
|
||||
parts := strings.Split(v.Enum, ",")
|
||||
out := make([]string, len(parts))
|
||||
for i, part := range parts {
|
||||
out[i] = strings.TrimSpace(part)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ShortSummary returns a human-readable summary of the value, not including any placeholders/defaults.
|
||||
func (v *Value) ShortSummary() string {
|
||||
if v.Flag != nil {
|
||||
return fmt.Sprintf("--%s", v.Name)
|
||||
}
|
||||
argText := "<" + v.Name + ">"
|
||||
if v.IsCumulative() {
|
||||
argText += " ..."
|
||||
}
|
||||
if !v.Required {
|
||||
argText = "[" + argText + "]"
|
||||
}
|
||||
return argText
|
||||
}
|
||||
|
||||
// Summary returns a human-readable summary of the value.
|
||||
func (v *Value) Summary() string {
|
||||
if v.Flag != nil {
|
||||
if v.IsBool() {
|
||||
return fmt.Sprintf("--%s", v.Name)
|
||||
}
|
||||
return fmt.Sprintf("--%s=%s", v.Name, v.Flag.FormatPlaceHolder())
|
||||
}
|
||||
argText := "<" + v.Name + ">"
|
||||
if v.IsCumulative() {
|
||||
argText += " ..."
|
||||
}
|
||||
if !v.Required {
|
||||
argText = "[" + argText + "]"
|
||||
}
|
||||
return argText
|
||||
}
|
||||
|
||||
// IsCumulative returns true if the type can be accumulated into.
|
||||
func (v *Value) IsCumulative() bool {
|
||||
return v.IsSlice() || v.IsMap()
|
||||
}
|
||||
|
||||
// IsSlice returns true if the value is a slice.
|
||||
func (v *Value) IsSlice() bool {
|
||||
return v.Target.Type().Name() == "" && v.Target.Kind() == reflect.Slice
|
||||
}
|
||||
|
||||
// IsMap returns true if the value is a map.
|
||||
func (v *Value) IsMap() bool {
|
||||
return v.Target.Kind() == reflect.Map
|
||||
}
|
||||
|
||||
// IsBool returns true if the underlying value is a boolean.
|
||||
func (v *Value) IsBool() bool {
|
||||
if m, ok := v.Mapper.(BoolMapperExt); ok && m.IsBoolFromValue(v.Target) {
|
||||
return true
|
||||
}
|
||||
if m, ok := v.Mapper.(BoolMapper); ok && m.IsBool() {
|
||||
return true
|
||||
}
|
||||
return v.Target.Kind() == reflect.Bool
|
||||
}
|
||||
|
||||
// IsCounter returns true if the value is a counter.
|
||||
func (v *Value) IsCounter() bool {
|
||||
return v.Tag.Type == "counter"
|
||||
}
|
||||
|
||||
// Parse tokens into value, parse, and validate, but do not write to the field.
|
||||
func (v *Value) Parse(scan *Scanner, target reflect.Value) (err error) {
|
||||
if target.Kind() == reflect.Ptr && target.IsNil() {
|
||||
target.Set(reflect.New(target.Type().Elem()))
|
||||
}
|
||||
err = v.Mapper.Decode(&DecodeContext{Value: v, Scan: scan}, target)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", v.ShortSummary(), err)
|
||||
}
|
||||
v.Set = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply value to field.
|
||||
func (v *Value) Apply(value reflect.Value) {
|
||||
v.Target.Set(value)
|
||||
v.Set = true
|
||||
}
|
||||
|
||||
// ApplyDefault value to field if it is not already set.
|
||||
func (v *Value) ApplyDefault() error {
|
||||
if reflectValueIsZero(v.Target) {
|
||||
return v.Reset()
|
||||
}
|
||||
v.Set = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reset this value to its default, either the zero value or the parsed result of its envar,
|
||||
// or its "default" tag.
|
||||
//
|
||||
// Does not include resolvers.
|
||||
func (v *Value) Reset() error {
|
||||
v.Target.Set(reflect.Zero(v.Target.Type()))
|
||||
if len(v.Tag.Envs) != 0 {
|
||||
for _, env := range v.Tag.Envs {
|
||||
envar, ok := os.LookupEnv(env)
|
||||
// Parse the first non-empty ENV in the list
|
||||
if ok {
|
||||
err := v.Parse(ScanFromTokens(Token{Type: FlagValueToken, Value: envar}), v.Target)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s (from envar %s=%q)", err, env, envar)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if v.HasDefault {
|
||||
return v.Parse(ScanFromTokens(Token{Type: FlagValueToken, Value: v.Default}), v.Target)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*Value) node() {}
|
||||
|
||||
// A Positional represents a non-branching command-line positional argument.
|
||||
type Positional = Value
|
||||
|
||||
// A Flag represents a command-line flag.
|
||||
type Flag struct {
|
||||
*Value
|
||||
Group *Group // Logical grouping when displaying. May also be used by configuration loaders to group options logically.
|
||||
Xor []string
|
||||
And []string
|
||||
PlaceHolder string
|
||||
Envs []string
|
||||
Aliases []string
|
||||
Short rune
|
||||
Hidden bool
|
||||
Negated bool
|
||||
}
|
||||
|
||||
func (f *Flag) String() string {
|
||||
out := "--" + f.Name
|
||||
if f.Short != 0 {
|
||||
out = fmt.Sprintf("-%c, %s", f.Short, out)
|
||||
}
|
||||
if !f.IsBool() && !f.IsCounter() {
|
||||
out += "=" + f.FormatPlaceHolder()
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// FormatPlaceHolder formats the placeholder string for a Flag.
|
||||
func (f *Flag) FormatPlaceHolder() string {
|
||||
placeholderHelper, ok := f.Value.Mapper.(PlaceHolderProvider)
|
||||
if ok {
|
||||
return placeholderHelper.PlaceHolder(f)
|
||||
}
|
||||
tail := ""
|
||||
if f.Value.IsSlice() && f.Value.Tag.Sep != -1 && f.Tag.Type == "" {
|
||||
tail += string(f.Value.Tag.Sep) + "..."
|
||||
}
|
||||
if f.PlaceHolder != "" {
|
||||
return f.PlaceHolder + tail
|
||||
}
|
||||
if f.HasDefault {
|
||||
if f.Value.Target.Kind() == reflect.String {
|
||||
return strconv.Quote(f.Default) + tail
|
||||
}
|
||||
return f.Default + tail
|
||||
}
|
||||
if f.Value.IsMap() {
|
||||
if f.Value.Tag.MapSep != -1 && f.Tag.Type == "" {
|
||||
tail = string(f.Value.Tag.MapSep) + "..."
|
||||
}
|
||||
return "KEY=VALUE" + tail
|
||||
}
|
||||
if f.Tag != nil && f.Tag.TypeName != "" {
|
||||
return strings.ToUpper(dashedString(f.Tag.TypeName)) + tail
|
||||
}
|
||||
return strings.ToUpper(f.Name) + tail
|
||||
}
|
||||
|
||||
// Group holds metadata about a command or flag group used when printing help.
|
||||
type Group struct {
|
||||
// Key is the `group` field tag value used to identify this group.
|
||||
Key string
|
||||
// Title is displayed above the grouped items.
|
||||
Title string
|
||||
// Description is optional and displayed under the Title when non empty.
|
||||
// It can be used to introduce the group's purpose to the user.
|
||||
Description string
|
||||
}
|
||||
|
||||
// This is directly from the Go 1.13 source code.
|
||||
func reflectValueIsZero(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return math.Float64bits(v.Float()) == 0
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
c := v.Complex()
|
||||
return math.Float64bits(real(c)) == 0 && math.Float64bits(imag(c)) == 0
|
||||
case reflect.Array:
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
if !reflectValueIsZero(v.Index(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
|
||||
return v.IsNil()
|
||||
case reflect.String:
|
||||
return v.Len() == 0
|
||||
case reflect.Struct:
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
if !reflectValueIsZero(v.Field(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
default:
|
||||
// This should never happens, but will act as a safeguard for
|
||||
// later, as a default value doesn't makes sense here.
|
||||
panic(&reflect.ValueError{
|
||||
Method: "reflect.Value.IsZero",
|
||||
Kind: v.Kind(),
|
||||
})
|
||||
}
|
||||
}
|
||||
19
vendor/github.com/alecthomas/kong/negatable.go
generated
vendored
Normal file
19
vendor/github.com/alecthomas/kong/negatable.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
package kong
|
||||
|
||||
// negatableDefault is a placeholder value for the Negatable tag to indicate
|
||||
// the negated flag is --no-<flag-name>. This is needed as at the time of
|
||||
// parsing a tag, the field's flag name is not yet known.
|
||||
const negatableDefault = "_"
|
||||
|
||||
// negatableFlagName returns the name of the flag for a negatable field, or
|
||||
// an empty string if the field is not negatable.
|
||||
func negatableFlagName(name, negation string) string {
|
||||
switch negation {
|
||||
case "":
|
||||
return ""
|
||||
case negatableDefault:
|
||||
return "--no-" + name
|
||||
default:
|
||||
return "--" + negation
|
||||
}
|
||||
}
|
||||
559
vendor/github.com/alecthomas/kong/options.go
generated
vendored
Normal file
559
vendor/github.com/alecthomas/kong/options.go
generated
vendored
Normal file
@@ -0,0 +1,559 @@
|
||||
package kong
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// An Option applies optional changes to the Kong application.
|
||||
type Option interface {
|
||||
Apply(k *Kong) error
|
||||
}
|
||||
|
||||
// OptionFunc is function that adheres to the Option interface.
|
||||
type OptionFunc func(k *Kong) error
|
||||
|
||||
func (o OptionFunc) Apply(k *Kong) error { return o(k) } //nolint: revive
|
||||
|
||||
// Vars sets the variables to use for interpolation into help strings and default values.
|
||||
//
|
||||
// See README for details.
|
||||
type Vars map[string]string
|
||||
|
||||
// Apply lets Vars act as an Option.
|
||||
func (v Vars) Apply(k *Kong) error {
|
||||
for key, value := range v {
|
||||
k.vars[key] = value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloneWith clones the current Vars and merges "vars" onto the clone.
|
||||
func (v Vars) CloneWith(vars Vars) Vars {
|
||||
out := make(Vars, len(v)+len(vars))
|
||||
for key, value := range v {
|
||||
out[key] = value
|
||||
}
|
||||
for key, value := range vars {
|
||||
out[key] = value
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Exit overrides the function used to terminate. This is useful for testing or interactive use.
|
||||
func Exit(exit func(int)) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.Exit = exit
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// WithHyphenPrefixedParameters enables or disables hyphen-prefixed parameters.
|
||||
//
|
||||
// These are disabled by default.
|
||||
func WithHyphenPrefixedParameters(enable bool) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.allowHyphenated = enable
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
type embedded struct {
|
||||
strct any
|
||||
tags []string
|
||||
}
|
||||
|
||||
// Embed a struct into the root of the CLI.
|
||||
//
|
||||
// "strct" must be a pointer to a structure.
|
||||
func Embed(strct any, tags ...string) Option {
|
||||
t := reflect.TypeOf(strct)
|
||||
if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct {
|
||||
panic("kong: Embed() must be called with a pointer to a struct")
|
||||
}
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.embedded = append(k.embedded, embedded{strct, tags})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
type dynamicCommand struct {
|
||||
name string
|
||||
help string
|
||||
group string
|
||||
tags []string
|
||||
cmd any
|
||||
}
|
||||
|
||||
// DynamicCommand registers a dynamically constructed command with the root of the CLI.
|
||||
//
|
||||
// This is useful for command-line structures that are extensible via user-provided plugins.
|
||||
//
|
||||
// "tags" is a list of extra tag strings to parse, in the form <key>:"<value>".
|
||||
func DynamicCommand(name, help, group string, cmd any, tags ...string) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.dynamicCommands = append(k.dynamicCommands, &dynamicCommand{
|
||||
name: name,
|
||||
help: help,
|
||||
group: group,
|
||||
cmd: cmd,
|
||||
tags: tags,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// NoDefaultHelp disables the default help flags.
|
||||
func NoDefaultHelp() Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.noDefaultHelp = true
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// PostBuild provides read/write access to kong.Kong after initial construction of the model is complete but before
|
||||
// parsing occurs.
|
||||
//
|
||||
// This is useful for, e.g., adding short options to flags, updating help, etc.
|
||||
func PostBuild(fn func(*Kong) error) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.postBuildOptions = append(k.postBuildOptions, OptionFunc(fn))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// WithBeforeReset registers a hook to run before fields values are reset to their defaults
|
||||
// (as specified in the grammar) or to zero values.
|
||||
func WithBeforeReset(fn any) Option {
|
||||
return withHook("BeforeReset", fn)
|
||||
}
|
||||
|
||||
// WithBeforeResolve registers a hook to run before resolvers are applied.
|
||||
func WithBeforeResolve(fn any) Option {
|
||||
return withHook("BeforeResolve", fn)
|
||||
}
|
||||
|
||||
// WithBeforeApply registers a hook to run before command line arguments are applied to the grammar.
|
||||
func WithBeforeApply(fn any) Option {
|
||||
return withHook("BeforeApply", fn)
|
||||
}
|
||||
|
||||
// WithAfterApply registers a hook to run after values are applied to the grammar and validated.
|
||||
func WithAfterApply(fn any) Option {
|
||||
return withHook("AfterApply", fn)
|
||||
}
|
||||
|
||||
// withHook registers a named hook.
|
||||
func withHook(name string, fn any) Option {
|
||||
value := reflect.ValueOf(fn)
|
||||
if value.Kind() != reflect.Func {
|
||||
panic(fmt.Errorf("expected function, got %s", value.Type()))
|
||||
}
|
||||
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.hooks[name] = append(k.hooks[name], value)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Name overrides the application name.
|
||||
func Name(name string) Option {
|
||||
return PostBuild(func(k *Kong) error {
|
||||
k.Model.Name = name
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Description sets the application description.
|
||||
func Description(description string) Option {
|
||||
return PostBuild(func(k *Kong) error {
|
||||
k.Model.Help = description
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// TypeMapper registers a mapper to a type.
|
||||
func TypeMapper(typ reflect.Type, mapper Mapper) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.registry.RegisterType(typ, mapper)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// KindMapper registers a mapper to a kind.
|
||||
func KindMapper(kind reflect.Kind, mapper Mapper) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.registry.RegisterKind(kind, mapper)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ValueMapper registers a mapper to a field value.
|
||||
func ValueMapper(ptr any, mapper Mapper) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.registry.RegisterValue(ptr, mapper)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// NamedMapper registers a mapper to a name.
|
||||
func NamedMapper(name string, mapper Mapper) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.registry.RegisterName(name, mapper)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Writers overrides the default writers. Useful for testing or interactive use.
|
||||
func Writers(stdout, stderr io.Writer) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.Stdout = stdout
|
||||
k.Stderr = stderr
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Bind binds values for hooks and Run() function arguments.
|
||||
//
|
||||
// Any arguments passed will be available to the receiving hook functions, but may be omitted. Additionally, *Kong and
|
||||
// the current *Context will also be made available.
|
||||
//
|
||||
// There are two hook points:
|
||||
//
|
||||
// BeforeApply(...) error
|
||||
// AfterApply(...) error
|
||||
//
|
||||
// Called before validation/assignment, and immediately after validation/assignment, respectively.
|
||||
func Bind(args ...any) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.bindings.add(args...)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// BindTo allows binding of implementations to interfaces.
|
||||
//
|
||||
// BindTo(impl, (*iface)(nil))
|
||||
func BindTo(impl, iface any) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.bindings.addTo(impl, iface)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// BindToProvider binds an injected value to a provider function.
|
||||
//
|
||||
// The provider function must have one of the following signatures:
|
||||
//
|
||||
// func(...) (T, error)
|
||||
// func(...) T
|
||||
//
|
||||
// Where arguments to the function are injected by Kong.
|
||||
//
|
||||
// This is useful when the Run() function of different commands require different values that may
|
||||
// not all be initialisable from the main() function.
|
||||
func BindToProvider(provider any) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
return k.bindings.addProvider(provider, false /* singleton */)
|
||||
})
|
||||
}
|
||||
|
||||
// BindSingletonProvider binds an injected value to a provider function.
|
||||
// The provider function must have the signature:
|
||||
//
|
||||
// func(...) (T, error)
|
||||
// func(...) T
|
||||
//
|
||||
// Unlike [BindToProvider], the provider function will only be called
|
||||
// at most once, and the result will be cached and reused
|
||||
// across multiple recipients of the injected value.
|
||||
func BindSingletonProvider(provider any) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
return k.bindings.addProvider(provider, true /* singleton */)
|
||||
})
|
||||
}
|
||||
|
||||
// Help printer to use.
|
||||
func Help(help HelpPrinter) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.help = help
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ShortHelp configures the short usage message.
|
||||
//
|
||||
// It should be used together with kong.ShortUsageOnError() to display a
|
||||
// custom short usage message on errors.
|
||||
func ShortHelp(shortHelp HelpPrinter) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.shortHelp = shortHelp
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// HelpFormatter configures how the help text is formatted.
|
||||
//
|
||||
// Deprecated: Use ValueFormatter() instead.
|
||||
func HelpFormatter(helpFormatter HelpValueFormatter) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.helpFormatter = helpFormatter
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ValueFormatter configures how the help text is formatted.
|
||||
func ValueFormatter(helpFormatter HelpValueFormatter) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.helpFormatter = helpFormatter
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ConfigureHelp sets the HelpOptions to use for printing help.
|
||||
func ConfigureHelp(options HelpOptions) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.helpOptions = options
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// AutoGroup automatically assigns groups to flags.
|
||||
func AutoGroup(format func(parent Visitable, flag *Flag) *Group) Option {
|
||||
return PostBuild(func(kong *Kong) error {
|
||||
parents := []Visitable{kong.Model}
|
||||
return Visit(kong.Model, func(node Visitable, next Next) error {
|
||||
if flag, ok := node.(*Flag); ok && flag.Group == nil {
|
||||
flag.Group = format(parents[len(parents)-1], flag)
|
||||
}
|
||||
parents = append(parents, node)
|
||||
defer func() { parents = parents[:len(parents)-1] }()
|
||||
return next(nil)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Groups associates `group` field tags with group metadata.
|
||||
//
|
||||
// This option is used to simplify Kong tags while providing
|
||||
// rich group information such as title and optional description.
|
||||
//
|
||||
// Each key in the "groups" map corresponds to the value of a
|
||||
// `group` Kong tag, while the first line of the value will be
|
||||
// the title, and subsequent lines if any will be the description of
|
||||
// the group.
|
||||
//
|
||||
// See also ExplicitGroups for a more structured alternative.
|
||||
type Groups map[string]string
|
||||
|
||||
func (g Groups) Apply(k *Kong) error { //nolint: revive
|
||||
for key, info := range g {
|
||||
lines := strings.Split(info, "\n")
|
||||
title := strings.TrimSpace(lines[0])
|
||||
description := ""
|
||||
if len(lines) > 1 {
|
||||
description = strings.TrimSpace(strings.Join(lines[1:], "\n"))
|
||||
}
|
||||
k.groups = append(k.groups, Group{
|
||||
Key: key,
|
||||
Title: title,
|
||||
Description: description,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExplicitGroups associates `group` field tags with their metadata.
|
||||
//
|
||||
// It can be used to provide a title or header to a command or flag group.
|
||||
func ExplicitGroups(groups []Group) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.groups = groups
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// UsageOnError configures Kong to display context-sensitive usage if FatalIfErrorf is called with an error.
|
||||
func UsageOnError() Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.usageOnError = fullUsage
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ShortUsageOnError configures Kong to display context-sensitive short
|
||||
// usage if FatalIfErrorf is called with an error. The default short
|
||||
// usage message can be overridden with kong.ShortHelp(...).
|
||||
func ShortUsageOnError() Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.usageOnError = shortUsage
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ClearResolvers clears all existing resolvers.
|
||||
func ClearResolvers() Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.resolvers = nil
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Resolvers registers flag resolvers.
|
||||
func Resolvers(resolvers ...Resolver) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.resolvers = append(k.resolvers, resolvers...)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// IgnoreFields will cause kong.New() to skip field names that match any
|
||||
// of the provided regex patterns. This is useful if you are not able to add a
|
||||
// kong="-" struct tag to a struct/element before the call to New.
|
||||
//
|
||||
// Example: When referencing protoc generated structs, you will likely want to
|
||||
// ignore/skip XXX_* fields.
|
||||
func IgnoreFields(regexes ...string) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
for _, r := range regexes {
|
||||
if r == "" {
|
||||
return errors.New("regex input cannot be empty")
|
||||
}
|
||||
|
||||
re, err := regexp.Compile(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to compile regex: %v", err)
|
||||
}
|
||||
|
||||
k.ignoreFields = append(k.ignoreFields, re)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ConfigurationLoader is a function that builds a resolver from a file.
|
||||
type ConfigurationLoader func(r io.Reader) (Resolver, error)
|
||||
|
||||
// Configuration provides Kong with support for loading defaults from a set of configuration files.
|
||||
//
|
||||
// Paths will be opened in order, and "loader" will be used to provide a Resolver which is registered with Kong.
|
||||
//
|
||||
// Note: The JSON function is a ConfigurationLoader.
|
||||
//
|
||||
// ~ and variable expansion will occur on the provided paths.
|
||||
func Configuration(loader ConfigurationLoader, paths ...string) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.loader = loader
|
||||
for _, path := range paths {
|
||||
f, err := os.Open(ExpandPath(path))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
f.Close()
|
||||
|
||||
resolver, err := k.LoadConfig(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %v", path, err)
|
||||
}
|
||||
if resolver != nil {
|
||||
k.resolvers = append(k.resolvers, resolver)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ExpandPath is a helper function to expand a relative or home-relative path to an absolute path.
|
||||
//
|
||||
// eg. ~/.someconf -> /home/alec/.someconf
|
||||
func ExpandPath(path string) string {
|
||||
if filepath.IsAbs(path) {
|
||||
return path
|
||||
}
|
||||
if strings.HasPrefix(path, "~/") {
|
||||
user, err := user.Current()
|
||||
if err != nil {
|
||||
return path
|
||||
}
|
||||
return filepath.Join(user.HomeDir, path[2:])
|
||||
}
|
||||
abspath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return path
|
||||
}
|
||||
return abspath
|
||||
}
|
||||
|
||||
func siftStrings(ss []string, filter func(s string) bool) []string {
|
||||
i := 0
|
||||
ss = append([]string(nil), ss...)
|
||||
for _, s := range ss {
|
||||
if filter(s) {
|
||||
ss[i] = s
|
||||
i++
|
||||
}
|
||||
}
|
||||
return ss[0:i]
|
||||
}
|
||||
|
||||
// DefaultEnvars option inits environment names for flags.
|
||||
// The name will not generate if tag "env" is "-".
|
||||
// Predefined environment variables are skipped.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// --some.value -> PREFIX_SOME_VALUE
|
||||
func DefaultEnvars(prefix string) Option {
|
||||
processFlag := func(flag *Flag) {
|
||||
switch env := flag.Envs; {
|
||||
case flag.Name == "help":
|
||||
return
|
||||
case len(env) == 1 && env[0] == "-":
|
||||
flag.Envs = nil
|
||||
return
|
||||
case len(env) > 0:
|
||||
return
|
||||
}
|
||||
replacer := strings.NewReplacer("-", "_", ".", "_")
|
||||
names := append([]string{prefix}, camelCase(replacer.Replace(flag.Name))...)
|
||||
names = siftStrings(names, func(s string) bool { return !(s == "_" || strings.TrimSpace(s) == "") })
|
||||
name := strings.ToUpper(strings.Join(names, "_"))
|
||||
flag.Envs = append(flag.Envs, name)
|
||||
flag.Value.Tag.Envs = append(flag.Value.Tag.Envs, name)
|
||||
}
|
||||
|
||||
var processNode func(node *Node)
|
||||
processNode = func(node *Node) {
|
||||
for _, flag := range node.Flags {
|
||||
processFlag(flag)
|
||||
}
|
||||
for _, node := range node.Children {
|
||||
processNode(node)
|
||||
}
|
||||
}
|
||||
|
||||
return PostBuild(func(k *Kong) error {
|
||||
processNode(k.Model.Node)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// FlagNamer allows you to override the default kebab-case automated flag name generation.
|
||||
func FlagNamer(namer func(fieldName string) string) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.flagNamer = namer
|
||||
return nil
|
||||
})
|
||||
}
|
||||
18
vendor/github.com/alecthomas/kong/renovate.json5
generated
vendored
Normal file
18
vendor/github.com/alecthomas/kong/renovate.json5
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
$schema: "https://docs.renovatebot.com/renovate-schema.json",
|
||||
extends: [
|
||||
"config:recommended",
|
||||
":semanticCommits",
|
||||
":semanticCommitTypeAll(chore)",
|
||||
":semanticCommitScope(deps)",
|
||||
"group:allNonMajor",
|
||||
"schedule:earlyMondays", // Run once a week.
|
||||
],
|
||||
packageRules: [
|
||||
{
|
||||
"matchPackageNames": ["golangci-lint"],
|
||||
"matchManagers": ["hermit"],
|
||||
"enabled": false
|
||||
},
|
||||
]
|
||||
}
|
||||
68
vendor/github.com/alecthomas/kong/resolver.go
generated
vendored
Normal file
68
vendor/github.com/alecthomas/kong/resolver.go
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
package kong
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A Resolver resolves a Flag value from an external source.
|
||||
type Resolver interface {
|
||||
// Validate configuration against Application.
|
||||
//
|
||||
// This can be used to validate that all provided configuration is valid within this application.
|
||||
Validate(app *Application) error
|
||||
|
||||
// Resolve the value for a Flag.
|
||||
Resolve(context *Context, parent *Path, flag *Flag) (any, error)
|
||||
}
|
||||
|
||||
// ResolverFunc is a convenience type for non-validating Resolvers.
|
||||
type ResolverFunc func(context *Context, parent *Path, flag *Flag) (any, error)
|
||||
|
||||
var _ Resolver = ResolverFunc(nil)
|
||||
|
||||
func (r ResolverFunc) Resolve(context *Context, parent *Path, flag *Flag) (any, error) { //nolint: revive
|
||||
return r(context, parent, flag)
|
||||
}
|
||||
func (r ResolverFunc) Validate(app *Application) error { return nil } //nolint: revive
|
||||
|
||||
// JSON returns a Resolver that retrieves values from a JSON source.
|
||||
//
|
||||
// Flag names are used as JSON keys indirectly, by tring snake_case and camelCase variants.
|
||||
func JSON(r io.Reader) (Resolver, error) {
|
||||
values := map[string]any{}
|
||||
err := json.NewDecoder(r).Decode(&values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var f ResolverFunc = func(context *Context, parent *Path, flag *Flag) (any, error) {
|
||||
name := strings.ReplaceAll(flag.Name, "-", "_")
|
||||
snakeCaseName := snakeCase(flag.Name)
|
||||
raw, ok := values[name]
|
||||
if ok {
|
||||
return raw, nil
|
||||
} else if raw, ok = values[snakeCaseName]; ok {
|
||||
return raw, nil
|
||||
}
|
||||
raw = values
|
||||
for _, part := range strings.Split(name, ".") {
|
||||
if values, ok := raw.(map[string]any); ok {
|
||||
raw, ok = values[part]
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func snakeCase(name string) string {
|
||||
name = strings.Join(strings.Split(strings.Title(name), "-"), "") //nolint:staticcheck // Unicode punctuation not an issue
|
||||
return strings.ToLower(name[:1]) + name[1:]
|
||||
}
|
||||
236
vendor/github.com/alecthomas/kong/scanner.go
generated
vendored
Normal file
236
vendor/github.com/alecthomas/kong/scanner.go
generated
vendored
Normal file
@@ -0,0 +1,236 @@
|
||||
package kong
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TokenType is the type of a token.
|
||||
type TokenType int
|
||||
|
||||
// Token types.
|
||||
const (
|
||||
UntypedToken TokenType = iota
|
||||
EOLToken
|
||||
FlagToken // --<flag>
|
||||
FlagValueToken // =<value>
|
||||
ShortFlagToken // -<short>[<tail]
|
||||
ShortFlagTailToken // <tail>
|
||||
PositionalArgumentToken // <arg>
|
||||
)
|
||||
|
||||
func (t TokenType) String() string {
|
||||
switch t {
|
||||
case UntypedToken:
|
||||
return "untyped"
|
||||
case EOLToken:
|
||||
return "<EOL>"
|
||||
case FlagToken: // --<flag>
|
||||
return "long flag"
|
||||
case FlagValueToken: // =<value>
|
||||
return "flag value"
|
||||
case ShortFlagToken: // -<short>[<tail]
|
||||
return "short flag"
|
||||
case ShortFlagTailToken: // <tail>
|
||||
return "short flag remainder"
|
||||
case PositionalArgumentToken: // <arg>
|
||||
return "positional argument"
|
||||
}
|
||||
panic("unsupported type")
|
||||
}
|
||||
|
||||
// Token created by Scanner.
|
||||
type Token struct {
|
||||
Value any
|
||||
Type TokenType
|
||||
}
|
||||
|
||||
func (t Token) String() string {
|
||||
switch t.Type {
|
||||
case FlagToken:
|
||||
return fmt.Sprintf("--%v", t.Value)
|
||||
|
||||
case ShortFlagToken:
|
||||
return fmt.Sprintf("-%v", t.Value)
|
||||
|
||||
case EOLToken:
|
||||
return "EOL"
|
||||
|
||||
default:
|
||||
return fmt.Sprintf("%v", t.Value)
|
||||
}
|
||||
}
|
||||
|
||||
// IsEOL returns true if this Token is past the end of the line.
|
||||
func (t Token) IsEOL() bool {
|
||||
return t.Type == EOLToken
|
||||
}
|
||||
|
||||
// IsAny returns true if the token's type is any of those provided.
|
||||
func (t TokenType) IsAny(types ...TokenType) bool {
|
||||
for _, typ := range types {
|
||||
if t == typ {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// InferredType tries to infer the type of a token.
|
||||
func (t Token) InferredType() TokenType {
|
||||
if t.Type != UntypedToken {
|
||||
return t.Type
|
||||
}
|
||||
if v, ok := t.Value.(string); ok {
|
||||
if strings.HasPrefix(v, "--") { //nolint: gocritic
|
||||
return FlagToken
|
||||
} else if v == "-" {
|
||||
return PositionalArgumentToken
|
||||
} else if strings.HasPrefix(v, "-") {
|
||||
return ShortFlagToken
|
||||
}
|
||||
}
|
||||
return t.Type
|
||||
}
|
||||
|
||||
// IsValue returns true if token is usable as a parseable value.
|
||||
//
|
||||
// A parseable value is either a value typed token, or an untyped token NOT starting with a hyphen.
|
||||
func (t Token) IsValue() bool {
|
||||
tt := t.InferredType()
|
||||
return tt.IsAny(FlagValueToken, ShortFlagTailToken, PositionalArgumentToken) ||
|
||||
(tt == UntypedToken && !strings.HasPrefix(t.String(), "-"))
|
||||
}
|
||||
|
||||
// Scanner is a stack-based scanner over command-line tokens.
|
||||
//
|
||||
// Initially all tokens are untyped. As the parser consumes tokens it assigns types, splits tokens, and pushes them back
|
||||
// onto the stream.
|
||||
//
|
||||
// For example, the token "--foo=bar" will be split into the following by the parser:
|
||||
//
|
||||
// [{FlagToken, "foo"}, {FlagValueToken, "bar"}]
|
||||
type Scanner struct {
|
||||
allowHyphenated bool
|
||||
args []Token
|
||||
}
|
||||
|
||||
// ScanAsType creates a new Scanner from args with the given type.
|
||||
func ScanAsType(ttype TokenType, args ...string) *Scanner {
|
||||
s := &Scanner{}
|
||||
for _, arg := range args {
|
||||
s.args = append(s.args, Token{Value: arg, Type: ttype})
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Scan creates a new Scanner from args with untyped tokens.
|
||||
func Scan(args ...string) *Scanner {
|
||||
return ScanAsType(UntypedToken, args...)
|
||||
}
|
||||
|
||||
// ScanFromTokens creates a new Scanner from a slice of tokens.
|
||||
func ScanFromTokens(tokens ...Token) *Scanner {
|
||||
return &Scanner{args: tokens}
|
||||
}
|
||||
|
||||
// AllowHyphenPrefixedParameters enables or disables hyphen-prefixed flag parameters on this Scanner.
|
||||
//
|
||||
// Disabled by default.
|
||||
func (s *Scanner) AllowHyphenPrefixedParameters(enable bool) *Scanner {
|
||||
s.allowHyphenated = enable
|
||||
return s
|
||||
}
|
||||
|
||||
// Len returns the number of input arguments.
|
||||
func (s *Scanner) Len() int {
|
||||
return len(s.args)
|
||||
}
|
||||
|
||||
// Pop the front token off the Scanner.
|
||||
func (s *Scanner) Pop() Token {
|
||||
if len(s.args) == 0 {
|
||||
return Token{Type: EOLToken}
|
||||
}
|
||||
arg := s.args[0]
|
||||
s.args = s.args[1:]
|
||||
return arg
|
||||
}
|
||||
|
||||
type expectedError struct {
|
||||
context string
|
||||
token Token
|
||||
}
|
||||
|
||||
func (e *expectedError) Error() string {
|
||||
return fmt.Sprintf("expected %s value but got %q (%s)", e.context, e.token, e.token.InferredType())
|
||||
}
|
||||
|
||||
// PopValue pops a value token, or returns an error.
|
||||
//
|
||||
// "context" is used to assist the user if the value can not be popped, eg. "expected <context> value but got <type>"
|
||||
func (s *Scanner) PopValue(context string) (Token, error) {
|
||||
t := s.Pop()
|
||||
if !s.allowHyphenated && !t.IsValue() {
|
||||
return t, &expectedError{context, t}
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// PopValueInto pops a value token into target or returns an error.
|
||||
//
|
||||
// "context" is used to assist the user if the value can not be popped, eg. "expected <context> value but got <type>"
|
||||
func (s *Scanner) PopValueInto(context string, target any) error {
|
||||
t, err := s.PopValue(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return jsonTranscode(t.Value, target)
|
||||
}
|
||||
|
||||
// PopWhile predicate returns true.
|
||||
func (s *Scanner) PopWhile(predicate func(Token) bool) (values []Token) {
|
||||
for predicate(s.Peek()) {
|
||||
values = append(values, s.Pop())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// PopUntil predicate returns true.
|
||||
func (s *Scanner) PopUntil(predicate func(Token) bool) (values []Token) {
|
||||
for !predicate(s.Peek()) {
|
||||
values = append(values, s.Pop())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Peek at the next Token or return an EOLToken.
|
||||
func (s *Scanner) Peek() Token {
|
||||
if len(s.args) == 0 {
|
||||
return Token{Type: EOLToken}
|
||||
}
|
||||
return s.args[0]
|
||||
}
|
||||
|
||||
// PeekAll remaining tokens
|
||||
func (s *Scanner) PeekAll() []Token {
|
||||
return s.args
|
||||
}
|
||||
|
||||
// Push an untyped Token onto the front of the Scanner.
|
||||
func (s *Scanner) Push(arg any) *Scanner {
|
||||
s.PushToken(Token{Value: arg})
|
||||
return s
|
||||
}
|
||||
|
||||
// PushTyped pushes a typed token onto the front of the Scanner.
|
||||
func (s *Scanner) PushTyped(arg any, typ TokenType) *Scanner {
|
||||
s.PushToken(Token{Value: arg, Type: typ})
|
||||
return s
|
||||
}
|
||||
|
||||
// PushToken pushes a preconstructed Token onto the front of the Scanner.
|
||||
func (s *Scanner) PushToken(token Token) *Scanner {
|
||||
s.args = append([]Token{token}, s.args...)
|
||||
return s
|
||||
}
|
||||
433
vendor/github.com/alecthomas/kong/tag.go
generated
vendored
Normal file
433
vendor/github.com/alecthomas/kong/tag.go
generated
vendored
Normal file
@@ -0,0 +1,433 @@
|
||||
package kong
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// PassthroughMode indicates how parameters are passed through when "passthrough" is set.
|
||||
type PassthroughMode int
|
||||
|
||||
const (
|
||||
// PassThroughModeNone indicates passthrough mode is disabled.
|
||||
PassThroughModeNone PassthroughMode = iota
|
||||
// PassThroughModeAll indicates that all parameters, including flags, are passed through. It is the default.
|
||||
PassThroughModeAll
|
||||
// PassThroughModePartial will validate flags until the first positional argument is encountered, then pass through all remaining positional arguments.
|
||||
PassThroughModePartial
|
||||
)
|
||||
|
||||
// Tag represents the parsed state of Kong tags in a struct field tag.
|
||||
type Tag struct {
|
||||
Ignored bool // Field is ignored by Kong. ie. kong:"-"
|
||||
Cmd bool
|
||||
Arg bool
|
||||
Required bool
|
||||
Optional bool
|
||||
Name string
|
||||
Help string
|
||||
Type string
|
||||
TypeName string
|
||||
HasDefault bool
|
||||
Default string
|
||||
Format string
|
||||
PlaceHolder string
|
||||
Envs []string
|
||||
Short rune
|
||||
Hidden bool
|
||||
Sep rune
|
||||
MapSep rune
|
||||
Enum string
|
||||
Group string
|
||||
Xor []string
|
||||
And []string
|
||||
Vars Vars
|
||||
Prefix string // Optional prefix on anonymous structs. All sub-flags will have this prefix.
|
||||
EnvPrefix string
|
||||
XorPrefix string // Optional prefix on XOR/AND groups.
|
||||
Embed bool
|
||||
Aliases []string
|
||||
Negatable string
|
||||
Passthrough bool // Deprecated: use PassthroughMode instead.
|
||||
PassthroughMode PassthroughMode
|
||||
|
||||
// Storage for all tag keys for arbitrary lookups.
|
||||
items map[string][]string
|
||||
}
|
||||
|
||||
func (t *Tag) String() string {
|
||||
out := []string{}
|
||||
for key, list := range t.items {
|
||||
for _, value := range list {
|
||||
out = append(out, fmt.Sprintf("%s:%q", key, value))
|
||||
}
|
||||
}
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
|
||||
type tagChars struct {
|
||||
sep, quote, assign rune
|
||||
needsUnquote bool
|
||||
}
|
||||
|
||||
var kongChars = tagChars{sep: ',', quote: '\'', assign: '=', needsUnquote: false}
|
||||
var bareChars = tagChars{sep: ' ', quote: '"', assign: ':', needsUnquote: true}
|
||||
|
||||
//nolint:gocyclo
|
||||
func parseTagItems(tagString string, chr tagChars) (map[string][]string, error) {
|
||||
d := map[string][]string{}
|
||||
key := []rune{}
|
||||
value := []rune{}
|
||||
quotes := false
|
||||
inKey := true
|
||||
|
||||
add := func() error {
|
||||
// Bare tags are quoted, therefore we need to unquote them in the same fashion reflect.Lookup() (implicitly)
|
||||
// unquotes "kong tags".
|
||||
s := string(value)
|
||||
|
||||
if chr.needsUnquote && s != "" {
|
||||
if unquoted, err := strconv.Unquote(fmt.Sprintf(`"%s"`, s)); err == nil {
|
||||
s = unquoted
|
||||
} else {
|
||||
return fmt.Errorf("unquoting tag value `%s`: %w", s, err)
|
||||
}
|
||||
}
|
||||
|
||||
d[string(key)] = append(d[string(key)], s)
|
||||
key = []rune{}
|
||||
value = []rune{}
|
||||
inKey = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
runes := []rune(tagString)
|
||||
for idx := 0; idx < len(runes); idx++ {
|
||||
r := runes[idx]
|
||||
next := rune(0)
|
||||
eof := false
|
||||
if idx < len(runes)-1 {
|
||||
next = runes[idx+1]
|
||||
} else {
|
||||
eof = true
|
||||
}
|
||||
if !quotes && r == chr.sep {
|
||||
if err := add(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
if r == chr.assign && inKey {
|
||||
inKey = false
|
||||
continue
|
||||
}
|
||||
if r == '\\' {
|
||||
if next == chr.quote {
|
||||
idx++
|
||||
|
||||
// We need to keep the backslashes, otherwise subsequent unquoting cannot work
|
||||
if chr.needsUnquote {
|
||||
value = append(value, r)
|
||||
}
|
||||
|
||||
r = chr.quote
|
||||
}
|
||||
} else if r == chr.quote {
|
||||
if quotes {
|
||||
quotes = false
|
||||
if next == chr.sep || eof {
|
||||
continue
|
||||
}
|
||||
return nil, fmt.Errorf("%v has an unexpected char at pos %v", tagString, idx)
|
||||
}
|
||||
quotes = true
|
||||
continue
|
||||
}
|
||||
if inKey {
|
||||
key = append(key, r)
|
||||
} else {
|
||||
value = append(value, r)
|
||||
}
|
||||
}
|
||||
if quotes {
|
||||
return nil, fmt.Errorf("%v is not quoted properly", tagString)
|
||||
}
|
||||
|
||||
if err := add(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func getTagInfo(tag reflect.StructTag) (string, tagChars) {
|
||||
s, ok := tag.Lookup("kong")
|
||||
if ok {
|
||||
return s, kongChars
|
||||
}
|
||||
|
||||
return string(tag), bareChars
|
||||
}
|
||||
|
||||
func newEmptyTag() *Tag {
|
||||
return &Tag{items: map[string][]string{}}
|
||||
}
|
||||
|
||||
func tagSplitFn(r rune) bool {
|
||||
return r == ',' || r == ' '
|
||||
}
|
||||
|
||||
func parseTagString(s string) (*Tag, error) {
|
||||
items, err := parseTagItems(s, bareChars)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t := &Tag{
|
||||
items: items,
|
||||
}
|
||||
err = hydrateTag(t, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %s", s, err)
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func parseTag(parent reflect.Value, ft reflect.StructField) (*Tag, error) {
|
||||
if ft.Tag.Get("kong") == "-" {
|
||||
t := newEmptyTag()
|
||||
t.Ignored = true
|
||||
return t, nil
|
||||
}
|
||||
items := map[string][]string{}
|
||||
// First use a [Signature] if present
|
||||
signatureTag, ok := maybeGetSignature(ft.Type)
|
||||
if ok {
|
||||
signatureItems, err := parseTagItems(getTagInfo(signatureTag))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = signatureItems
|
||||
}
|
||||
// Next overlay the field's tags.
|
||||
fieldItems, err := parseTagItems(getTagInfo(ft.Tag))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for key, value := range fieldItems {
|
||||
// Prepend field tag values
|
||||
items[key] = append(value, items[key]...)
|
||||
}
|
||||
|
||||
t := &Tag{
|
||||
items: items,
|
||||
}
|
||||
err = hydrateTag(t, ft.Type)
|
||||
if err != nil {
|
||||
return nil, failField(parent, ft, "%s", err)
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func hydrateTag(t *Tag, typ reflect.Type) error { //nolint: gocyclo
|
||||
var typeName string
|
||||
var isBool bool
|
||||
var isBoolPtr bool
|
||||
if typ != nil {
|
||||
typeName = typ.Name()
|
||||
isBool = typ.Kind() == reflect.Bool
|
||||
isBoolPtr = typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Bool
|
||||
}
|
||||
var err error
|
||||
t.Cmd = t.Has("cmd")
|
||||
t.Arg = t.Has("arg")
|
||||
required := t.Has("required")
|
||||
optional := t.Has("optional")
|
||||
if required && optional {
|
||||
return fmt.Errorf("can't specify both required and optional")
|
||||
}
|
||||
t.Required = required
|
||||
t.Optional = optional
|
||||
t.HasDefault = t.Has("default")
|
||||
t.Default = t.Get("default")
|
||||
// Arguments with defaults are always optional.
|
||||
if t.Arg && t.HasDefault {
|
||||
t.Optional = true
|
||||
} else if t.Arg && !optional { // Arguments are required unless explicitly made optional.
|
||||
t.Required = true
|
||||
}
|
||||
t.Name = t.Get("name")
|
||||
t.Help = t.Get("help")
|
||||
t.Type = t.Get("type")
|
||||
t.TypeName = typeName
|
||||
for _, env := range t.GetAll("env") {
|
||||
t.Envs = append(t.Envs, strings.FieldsFunc(env, tagSplitFn)...)
|
||||
}
|
||||
t.Short, err = t.GetRune("short")
|
||||
if err != nil && t.Get("short") != "" {
|
||||
return fmt.Errorf("invalid short flag name %q: %s", t.Get("short"), err)
|
||||
}
|
||||
t.Hidden = t.Has("hidden")
|
||||
t.Format = t.Get("format")
|
||||
t.Sep, _ = t.GetSep("sep", ',')
|
||||
t.MapSep, _ = t.GetSep("mapsep", ';')
|
||||
t.Group = t.Get("group")
|
||||
for _, xor := range t.GetAll("xor") {
|
||||
t.Xor = append(t.Xor, strings.FieldsFunc(xor, tagSplitFn)...)
|
||||
}
|
||||
for _, and := range t.GetAll("and") {
|
||||
t.And = append(t.And, strings.FieldsFunc(and, tagSplitFn)...)
|
||||
}
|
||||
t.Prefix = t.Get("prefix")
|
||||
t.EnvPrefix = t.Get("envprefix")
|
||||
t.XorPrefix = t.Get("xorprefix")
|
||||
t.Embed = t.Has("embed")
|
||||
if t.Has("negatable") {
|
||||
if !isBool && !isBoolPtr {
|
||||
return fmt.Errorf("negatable can only be set on booleans")
|
||||
}
|
||||
negatable := t.Get("negatable")
|
||||
if negatable == "" {
|
||||
negatable = negatableDefault // placeholder for default negation of --no-<flag>
|
||||
}
|
||||
t.Negatable = negatable
|
||||
}
|
||||
aliases := t.Get("aliases")
|
||||
if len(aliases) > 0 {
|
||||
t.Aliases = append(t.Aliases, strings.FieldsFunc(aliases, tagSplitFn)...)
|
||||
}
|
||||
t.Vars = Vars{}
|
||||
for _, set := range t.GetAll("set") {
|
||||
parts := strings.SplitN(set, "=", 2)
|
||||
if len(parts) == 0 {
|
||||
return fmt.Errorf("set should be in the form key=value but got %q", set)
|
||||
}
|
||||
t.Vars[parts[0]] = parts[1]
|
||||
}
|
||||
t.PlaceHolder = t.Get("placeholder")
|
||||
t.Enum = t.Get("enum")
|
||||
scalarType := typ == nil || !(typ.Kind() == reflect.Slice || typ.Kind() == reflect.Map || typ.Kind() == reflect.Ptr)
|
||||
if t.Enum != "" && !(t.Required || t.HasDefault) && scalarType {
|
||||
return fmt.Errorf("enum value is only valid if it is either required or has a valid default value")
|
||||
}
|
||||
passthrough := t.Has("passthrough")
|
||||
if passthrough && !t.Arg && !t.Cmd {
|
||||
return fmt.Errorf("passthrough only makes sense for positional arguments or commands")
|
||||
}
|
||||
t.Passthrough = passthrough
|
||||
if t.Passthrough {
|
||||
passthroughMode := t.Get("passthrough")
|
||||
switch passthroughMode {
|
||||
case "partial":
|
||||
t.PassthroughMode = PassThroughModePartial
|
||||
case "all", "":
|
||||
t.PassthroughMode = PassThroughModeAll
|
||||
default:
|
||||
return fmt.Errorf("invalid passthrough mode %q, must be one of 'partial' or 'all'", passthroughMode)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Has returns true if the tag contained the given key.
|
||||
func (t *Tag) Has(k string) bool {
|
||||
_, ok := t.items[k]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Get returns the value of the given tag.
|
||||
//
|
||||
// Note that this will return the empty string if the tag is missing.
|
||||
func (t *Tag) Get(k string) string {
|
||||
values := t.items[k]
|
||||
if len(values) == 0 {
|
||||
return ""
|
||||
}
|
||||
return values[0]
|
||||
}
|
||||
|
||||
// GetAll returns all encountered values for a tag, in the case of multiple occurrences.
|
||||
func (t *Tag) GetAll(k string) []string {
|
||||
return t.items[k]
|
||||
}
|
||||
|
||||
// GetBool returns true if the given tag looks like a boolean truth string.
|
||||
func (t *Tag) GetBool(k string) (bool, error) {
|
||||
return strconv.ParseBool(t.Get(k))
|
||||
}
|
||||
|
||||
// GetFloat parses the given tag as a float64.
|
||||
func (t *Tag) GetFloat(k string) (float64, error) {
|
||||
return strconv.ParseFloat(t.Get(k), 64)
|
||||
}
|
||||
|
||||
// GetInt parses the given tag as an int64.
|
||||
func (t *Tag) GetInt(k string) (int64, error) {
|
||||
return strconv.ParseInt(t.Get(k), 10, 64)
|
||||
}
|
||||
|
||||
// GetRune parses the given tag as a rune.
|
||||
func (t *Tag) GetRune(k string) (rune, error) {
|
||||
value := t.Get(k)
|
||||
r, size := utf8.DecodeRuneInString(value)
|
||||
if r == utf8.RuneError || size < len(value) {
|
||||
return 0, errors.New("invalid rune")
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// GetSep parses the given tag as a rune separator, allowing for a default or none.
|
||||
// The separator is returned, or -1 if "none" is specified. If the tag value is an
|
||||
// invalid utf8 sequence, the default rune is returned as well as an error. If the
|
||||
// tag value is more than one rune, the first rune is returned as well as an error.
|
||||
func (t *Tag) GetSep(k string, dflt rune) (rune, error) {
|
||||
tv := t.Get(k)
|
||||
if tv == "none" {
|
||||
return -1, nil
|
||||
} else if tv == "" {
|
||||
return dflt, nil
|
||||
}
|
||||
r, size := utf8.DecodeRuneInString(tv)
|
||||
if r == utf8.RuneError {
|
||||
return dflt, fmt.Errorf(`%v:"%v" has a rune error`, k, tv)
|
||||
} else if size != len(tv) {
|
||||
return r, fmt.Errorf(`%v:"%v" is more than a single rune`, k, tv)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// Signature allows flags, args and commands to supply a default set of tags,
|
||||
// that can be overridden by the field itself.
|
||||
type Signature interface {
|
||||
// Signature returns default tags for the flag, arg or command.
|
||||
//
|
||||
// eg. `name:"migrate" help:"Run migrations" aliases:"mig,mg"`.
|
||||
Signature() string
|
||||
}
|
||||
|
||||
var signatureOverrideType = reflect.TypeOf((*Signature)(nil)).Elem()
|
||||
|
||||
func maybeGetSignature(t reflect.Type) (reflect.StructTag, bool) {
|
||||
ut := t
|
||||
if ut.Kind() == reflect.Pointer {
|
||||
ut = ut.Elem()
|
||||
}
|
||||
ptr := reflect.New(ut)
|
||||
var sig string
|
||||
for _, v := range []reflect.Value{ptr, ptr.Elem()} {
|
||||
if v.Type().Implements(signatureOverrideType) {
|
||||
sig = v.Interface().(Signature).Signature() //nolint:forcetypeassert
|
||||
break
|
||||
}
|
||||
}
|
||||
sig = strings.TrimSpace(sig)
|
||||
if sig == "" {
|
||||
return "", false
|
||||
}
|
||||
return reflect.StructTag(sig), true
|
||||
}
|
||||
57
vendor/github.com/alecthomas/kong/util.go
generated
vendored
Normal file
57
vendor/github.com/alecthomas/kong/util.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
package kong
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// ConfigFlag uses the configured (via kong.Configuration(loader)) configuration loader to load configuration
|
||||
// from a file specified by a flag.
|
||||
//
|
||||
// Use this as a flag value to support loading of custom configuration via a flag.
|
||||
type ConfigFlag string
|
||||
|
||||
// BeforeResolve adds a resolver.
|
||||
func (c ConfigFlag) BeforeResolve(kong *Kong, ctx *Context, trace *Path) error {
|
||||
if kong.loader == nil {
|
||||
return fmt.Errorf("kong must be configured with kong.Configuration(...)")
|
||||
}
|
||||
path := string(ctx.FlagValue(trace.Flag).(ConfigFlag)) //nolint
|
||||
resolver, err := kong.LoadConfig(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.AddResolver(resolver)
|
||||
return nil
|
||||
}
|
||||
|
||||
// VersionFlag is a flag type that can be used to display a version number, stored in the "version" variable.
|
||||
type VersionFlag bool
|
||||
|
||||
// BeforeReset writes the version variable and terminates with a 0 exit status.
|
||||
func (v VersionFlag) BeforeReset(app *Kong, vars Vars) error {
|
||||
fmt.Fprintln(app.Stdout, vars["version"])
|
||||
app.Exit(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChangeDirFlag changes the current working directory to a path specified by a flag
|
||||
// early in the parsing process, changing how other flags resolve relative paths.
|
||||
//
|
||||
// Use this flag to provide a "git -C" like functionality.
|
||||
//
|
||||
// It is not compatible with custom named decoders, e.g., existingdir.
|
||||
type ChangeDirFlag string
|
||||
|
||||
// Decode is used to create a side effect of changing the current working directory.
|
||||
func (c ChangeDirFlag) Decode(ctx *DecodeContext) error {
|
||||
var path string
|
||||
err := ctx.Scan.PopValueInto("string", &path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path = ExpandPath(path)
|
||||
ctx.Value.Target.Set(reflect.ValueOf(ChangeDirFlag(path)))
|
||||
return os.Chdir(path)
|
||||
}
|
||||
58
vendor/github.com/alecthomas/kong/visit.go
generated
vendored
Normal file
58
vendor/github.com/alecthomas/kong/visit.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
package kong
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Next should be called by Visitor to proceed with the walk.
|
||||
//
|
||||
// The walk will terminate if "err" is non-nil.
|
||||
type Next func(err error) error
|
||||
|
||||
// Visitor can be used to walk all nodes in the model.
|
||||
type Visitor func(node Visitable, next Next) error
|
||||
|
||||
// Visit all nodes.
|
||||
func Visit(node Visitable, visitor Visitor) error {
|
||||
return visitor(node, func(err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch node := node.(type) {
|
||||
case *Application:
|
||||
return visitNodeChildren(node.Node, visitor)
|
||||
case *Node:
|
||||
return visitNodeChildren(node, visitor)
|
||||
case *Value:
|
||||
case *Flag:
|
||||
return Visit(node.Value, visitor)
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported node type %T", node))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func visitNodeChildren(node *Node, visitor Visitor) error {
|
||||
if node.Argument != nil {
|
||||
if err := Visit(node.Argument, visitor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, flag := range node.Flags {
|
||||
if err := Visit(flag, visitor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, pos := range node.Positional {
|
||||
if err := Visit(pos, visitor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, child := range node.Children {
|
||||
if err := Visit(child, visitor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user