Add path, existingfile and existingdir types.
- Document custom types. - Add docker example.
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
<!-- markdownlint-disable MD013 MD033 -->
|
<!-- markdownlint-disable MD013 MD033 -->
|
||||||
<p align="center"><img width="90%" src="kong.png" /></p>
|
<p align="center"><img width="90%" src="kong.png" /></p>
|
||||||
|
|
||||||
# Kong is a command-line parser for Go [](https://circleci.com/gh/alecthomas/kong)
|
# Kong is a command-line parser for Go [](https://circleci.com/gh/alecthomas/kong)
|
||||||
|
|
||||||
<!-- MarkdownTOC -->
|
<!-- MarkdownTOC -->
|
||||||
|
|
||||||
@@ -13,13 +13,14 @@
|
|||||||
1. [Terminating positional arguments](#terminating-positional-arguments)
|
1. [Terminating positional arguments](#terminating-positional-arguments)
|
||||||
1. [Slices](#slices)
|
1. [Slices](#slices)
|
||||||
1. [Maps](#maps)
|
1. [Maps](#maps)
|
||||||
|
1. [Custom named types](#custom-named-types)
|
||||||
1. [Supported tags](#supported-tags)
|
1. [Supported tags](#supported-tags)
|
||||||
1. [Modifying Kong's behaviour](#modifying-kongs-behaviour)
|
1. [Modifying Kong's behaviour](#modifying-kongs-behaviour)
|
||||||
1. [`Name(help)` and `Description(help)` - set the application name description](#namehelp-and-descriptionhelp---set-the-application-name-description)
|
1. [`Name(help)` and `Description(help)` - set the application name description](#namehelp-and-descriptionhelp---set-the-application-name-description)
|
||||||
1. [`Configuration(loader, paths...)` - load defaults from configuration files](#configurationloader-paths---load-defaults-from-configuration-files)
|
1. [`Configuration(loader, paths...)` - load defaults from configuration files](#configurationloader-paths---load-defaults-from-configuration-files)
|
||||||
1. [`Resolver(...)` - support for default values from external sources](#resolver---support-for-default-values-from-external-sources)
|
1. [`Resolver(...)` - support for default values from external sources](#resolver---support-for-default-values-from-external-sources)
|
||||||
1. [`*Mapper(...)` - customising how the command-line is mapped to Go values](#mapper---customising-how-the-command-line-is-mapped-to-go-values)
|
1. [`*Mapper(...)` - customising how the command-line is mapped to Go values](#mapper---customising-how-the-command-line-is-mapped-to-go-values)
|
||||||
1. [`HelpOptions(...HelpOption)` and `Help(HelpFunc)` - customising help](#helpoptionshelpoption-and-helphelpfunc---customising-help)
|
1. [`HelpOptions(HelpPrinterOptions)` and `Help(HelpFunc)` - customising help](#helpoptionshelpprinteroptions-and-helphelpfunc---customising-help)
|
||||||
1. [`Hook(&field, HookFunc)` - callback hooks to execute when the command-line is parsed](#hookfield-hookfunc---callback-hooks-to-execute-when-the-command-line-is-parsed)
|
1. [`Hook(&field, HookFunc)` - callback hooks to execute when the command-line is parsed](#hookfield-hookfunc---callback-hooks-to-execute-when-the-command-line-is-parsed)
|
||||||
1. [Other options](#other-options)
|
1. [Other options](#other-options)
|
||||||
|
|
||||||
@@ -176,7 +177,7 @@ You would use the following:
|
|||||||
```go
|
```go
|
||||||
var CLI struct {
|
var CLI struct {
|
||||||
Ls struct {
|
Ls struct {
|
||||||
Files []string `arg`
|
Files []string `arg type:"existingfile"`
|
||||||
} `cmd`
|
} `cmd`
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -195,12 +196,30 @@ You would use the following:
|
|||||||
var CLI struct {
|
var CLI struct {
|
||||||
Config struct {
|
Config struct {
|
||||||
Set struct {
|
Set struct {
|
||||||
Config map[string]float64 `arg`
|
Config map[string]float64 `arg type:"file:"`
|
||||||
} `cmd`
|
} `cmd`
|
||||||
} `cmd`
|
} `cmd`
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Custom named types
|
||||||
|
|
||||||
|
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 |
|
||||||
|
|-------------------|---------------------------------------------------|
|
||||||
|
| `file` | A path. ~ expansion is applied. |
|
||||||
|
| `existingfile` | An existing path. ~ expansion is applied. |
|
||||||
|
| `existingdir` | An existing directory. ~ expansion is applied. |
|
||||||
|
|
||||||
|
|
||||||
|
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 tags
|
## Supported tags
|
||||||
|
|
||||||
Tags can be in two forms:
|
Tags can be in two forms:
|
||||||
@@ -217,7 +236,7 @@ Both can coexist with standard Tag parsing.
|
|||||||
| `env:"X"` | Specify envar to use for default value.
|
| `env:"X"` | Specify envar to use for default value.
|
||||||
| `name:"X"` | Long name, for overriding field name. |
|
| `name:"X"` | Long name, for overriding field name. |
|
||||||
| `help:"X"` | Help text. |
|
| `help:"X"` | Help text. |
|
||||||
| `type:"X"` | Specify named Mapper to use. |
|
| `type:"X"` | Specify [named types](#custom-named-types) to use. |
|
||||||
| `placeholder:"X"` | Placeholder text. |
|
| `placeholder:"X"` | Placeholder text. |
|
||||||
| `default:"X"` | Default value. |
|
| `default:"X"` | Default value. |
|
||||||
| `short:"X"` | Short name, if flag. |
|
| `short:"X"` | Short name, if flag. |
|
||||||
@@ -279,11 +298,11 @@ All builtin Go types (as well as a bunch of useful stdlib types like `time.Time`
|
|||||||
3. `TypeMapper(reflect.Type, Mapper)`.
|
3. `TypeMapper(reflect.Type, Mapper)`.
|
||||||
4. `ValueMapper(interface{}, Mapper)`, passing in a pointer to a field of the grammar.
|
4. `ValueMapper(interface{}, Mapper)`, passing in a pointer to a field of the grammar.
|
||||||
|
|
||||||
### `HelpOptions(...HelpOption)` and `Help(HelpFunc)` - customising help
|
### `HelpOptions(HelpPrinterOptions)` and `Help(HelpFunc)` - customising help
|
||||||
|
|
||||||
The default help output is usually sufficient, but if it's not, there are two solutions.
|
The default help output is usually sufficient, but if not there are two solutions.
|
||||||
|
|
||||||
1. Use `HelpOptions(options...HelpOption)` to configure the default help (see [HelpOption](https://godoc.org/github.com/alecthomas/kong#HelpOption) for details).
|
1. Use `HelpOptions(HelpPrinterOptions)` to configure how help is formatted (see [HelpPrinterOptions](https://godoc.org/github.com/alecthomas/kong#HelpPrinterOptions) for details).
|
||||||
2. Custom help can be wired into Kong via the `Help(HelpFunc)` option. The `HelpFunc` is passed a `Context`, which contains the parsed context for the current command-line. See the implementation of `PrintHelp` for an example.
|
2. Custom help can be wired into Kong via the `Help(HelpFunc)` option. The `HelpFunc` is passed a `Context`, which contains the parsed context for the current command-line. See the implementation of `PrintHelp` for an example.
|
||||||
|
|
||||||
### `Hook(&field, HookFunc)` - callback hooks to execute when the command-line is parsed
|
### `Hook(&field, HookFunc)` - callback hooks to execute when the command-line is parsed
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
// nolint
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/alecthomas/kong"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AttachCmd struct {
|
||||||
|
DetachKeys string `help:"Override the key sequence for detaching a container"`
|
||||||
|
NoStdin bool `help:"Do not attach STDIN"`
|
||||||
|
SigProxy bool `help:"Proxy all received signals to the process" default:"true"`
|
||||||
|
|
||||||
|
Container string `arg required help:"Container ID to attach to."`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AttachCmd) Run() error {
|
||||||
|
fmt.Printf("Attaching to: %v\n", a.Container)
|
||||||
|
fmt.Printf("SigProxy: %v\n", a.SigProxy)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cli struct {
|
||||||
|
Config string `help:"Location of client config files" default:"~/.docker" type:"path"`
|
||||||
|
Debug bool `short:"D" help:"Enable debug mode"`
|
||||||
|
Host []string `short:"H" help:"Daemon socket(s) to connect to"`
|
||||||
|
LogLevel string `short:"l" help:"Set the logging level (debug|info|warn|error|fatal)" default:"info"`
|
||||||
|
TLS bool `help:"Use TLS; implied by --tlsverify"`
|
||||||
|
TLSCACert string `name:"tls-ca-cert" help:"Trust certs signed only by this CA" default:"~/.docker/ca.pem" type:"path"`
|
||||||
|
TLSCert string `name:"tls-cert" help:"Path to TLS certificate file" default:"~/.docker/cert.pem" type:"path"`
|
||||||
|
TLSKey string `help:"Path to TLS key file" default:"~/.docker/key.pem" type:"path"`
|
||||||
|
TLSVerify bool `help:"Use TLS and verify the remote"`
|
||||||
|
PrintVersion bool `name:"version" help:"Print version information and quit"`
|
||||||
|
|
||||||
|
Attach AttachCmd `cmd help:"Attach local standard input, output, and error streams to a running container"`
|
||||||
|
|
||||||
|
Build struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Build an image from a Dockerfile"`
|
||||||
|
Commit struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Create a new image from a container's changes"`
|
||||||
|
Cp struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Copy files/folders between a container and the local filesystem"`
|
||||||
|
Create struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Create a new container"`
|
||||||
|
Deploy struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Deploy a new stack or update an existing stack"`
|
||||||
|
Diff struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Inspect changes to files or directories on a container's filesystem"`
|
||||||
|
Events struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Get real time events from the server"`
|
||||||
|
Exec struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Run a command in a running container"`
|
||||||
|
Export struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Export a container's filesystem as a tar archive"`
|
||||||
|
History struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Show the history of an image"`
|
||||||
|
Images struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"List images"`
|
||||||
|
Import struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Import the contents from a tarball to create a filesystem image"`
|
||||||
|
Info struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Display system-wide information"`
|
||||||
|
Inspect struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Return low-level information on Docker objects"`
|
||||||
|
Kill struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Kill one or more running containers"`
|
||||||
|
Load struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Load an image from a tar archive or STDIN"`
|
||||||
|
Login struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Log in to a Docker registry"`
|
||||||
|
Logout struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Log out from a Docker registry"`
|
||||||
|
Logs struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Fetch the logs of a container"`
|
||||||
|
Pause struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Pause all processes within one or more containers"`
|
||||||
|
Port struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"List port mappings or a specific mapping for the container"`
|
||||||
|
Ps struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"List containers"`
|
||||||
|
Pull struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Pull an image or a repository from a registry"`
|
||||||
|
Push struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Push an image or a repository to a registry"`
|
||||||
|
Rename struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Rename a container"`
|
||||||
|
Restart struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Restart one or more containers"`
|
||||||
|
Rm struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Remove one or more containers"`
|
||||||
|
Rmi struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Remove one or more images"`
|
||||||
|
Run struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Run a command in a new container"`
|
||||||
|
Save struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Save one or more images to a tar archive (streamed to STDOUT by default)"`
|
||||||
|
Search struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Search the Docker Hub for images"`
|
||||||
|
Start struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Start one or more stopped containers"`
|
||||||
|
Stats struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Display a live stream of container(s) resource usage statistics"`
|
||||||
|
Stop struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Stop one or more running containers"`
|
||||||
|
Tag struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE"`
|
||||||
|
Top struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Display the running processes of a container"`
|
||||||
|
Unpause struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Unpause all processes within one or more containers"`
|
||||||
|
Update struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Update configuration of one or more containers"`
|
||||||
|
Version struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Show the Docker version information"`
|
||||||
|
Wait struct {
|
||||||
|
Arg string `arg required`
|
||||||
|
} `cmd help:"Block until one or more containers stop, then print their exit codes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cmd := kong.Parse(&cli,
|
||||||
|
kong.Name("docker"),
|
||||||
|
kong.Description("A self-sufficient runtime for containers"),
|
||||||
|
kong.UsageOnError(),
|
||||||
|
kong.HelpOptions(kong.HelpPrinterOptions{
|
||||||
|
Compact: true,
|
||||||
|
}))
|
||||||
|
var err error
|
||||||
|
switch cmd {
|
||||||
|
case "attach <container>":
|
||||||
|
fmt.Println(cli.Config)
|
||||||
|
err = cli.Attach.Run()
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("unsupported command " + cmd)
|
||||||
|
}
|
||||||
|
kong.FatalIfErrorf(err)
|
||||||
|
}
|
||||||
@@ -24,9 +24,13 @@ var cli struct {
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cmd := kong.Parse(&cli, kong.Description("A shell-like example app."),
|
cmd := kong.Parse(&cli, kong.Description("A shell-like example app."),
|
||||||
kong.HelpOptions(kong.CompactHelp()))
|
kong.UsageOnError(),
|
||||||
|
kong.HelpOptions(kong.HelpPrinterOptions{
|
||||||
|
Compact: true,
|
||||||
|
Summary: true,
|
||||||
|
}))
|
||||||
switch cmd {
|
switch cmd {
|
||||||
case "rm <paths>":
|
case "rm <path>":
|
||||||
fmt.Println(cli.Rm.Paths, cli.Rm.Force, cli.Rm.Recursive)
|
fmt.Println(cli.Rm.Paths, cli.Rm.Force, cli.Rm.Recursive)
|
||||||
|
|
||||||
case "ls":
|
case "ls":
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ func buildChild(k *Kong, node *Node, typ NodeType, v reflect.Value, ft reflect.S
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) {
|
func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) {
|
||||||
mapper := k.registry.ForNamedType(tag.Type, fv)
|
mapper := k.registry.ForNamedValue(tag.Type, fv)
|
||||||
if mapper == nil {
|
if mapper == nil {
|
||||||
fail("unsupported field type %s.%s (of type %s)", v.Type(), ft.Name, ft.Type)
|
fail("unsupported field type %s.%s (of type %s)", v.Type(), ft.Name, ft.Type)
|
||||||
}
|
}
|
||||||
|
|||||||
+10
@@ -97,6 +97,16 @@ func Trace(k *Kong, args []string) (*Context, error) {
|
|||||||
return c, c.traceResolvers()
|
return c, c.traceResolvers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Empty returns true if there were no arguments provided.
|
||||||
|
func (c *Context) Empty() bool {
|
||||||
|
for _, path := range c.Path {
|
||||||
|
if !path.Resolved && path.App == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Validate the current context.
|
// Validate the current context.
|
||||||
func (c *Context) Validate() error {
|
func (c *Context) Validate() error {
|
||||||
for _, path := range c.Path {
|
for _, path := range c.Path {
|
||||||
|
|||||||
@@ -13,46 +13,54 @@ const (
|
|||||||
defaultColumnPadding = 4
|
defaultColumnPadding = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
// HelpOption configures the default help.
|
// HelpPrinterOptions for HelpPrinters.
|
||||||
type HelpOption func(options *helpWriterOptions)
|
type HelpPrinterOptions struct {
|
||||||
|
// Write a one-line summary of the context.
|
||||||
|
Summary bool
|
||||||
|
|
||||||
// CompactHelp writes help in a more compact form.
|
// Write help in a more compact form, but still fully-specified.
|
||||||
func CompactHelp() HelpOption {
|
Compact bool
|
||||||
return func(options *helpWriterOptions) {
|
|
||||||
options.compact = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HelpPrinter returns a HelpFunction configured with the given HelpOptions.
|
// HelpPrinter is used to print context-sensitive help.
|
||||||
func HelpPrinter(options ...HelpOption) HelpFunction {
|
type HelpPrinter func(options HelpPrinterOptions, ctx *Context) error
|
||||||
return func(ctx *Context) error {
|
|
||||||
w := newHelpWriter(guessWidth(ctx.App.Stdout))
|
// DefaultHelpPrinter is the default HelpPrinter.
|
||||||
for _, option := range options {
|
func DefaultHelpPrinter(options HelpPrinterOptions, ctx *Context) error {
|
||||||
option(&w.options)
|
if ctx.Empty() {
|
||||||
}
|
options.Summary = false
|
||||||
selected := ctx.Selected()
|
|
||||||
if selected == nil {
|
|
||||||
printApp(w, ctx.App.Model)
|
|
||||||
} else {
|
|
||||||
printCommand(w, ctx.App.Model, selected)
|
|
||||||
}
|
|
||||||
return w.Write(ctx.App.Stdout)
|
|
||||||
}
|
}
|
||||||
|
w := newHelpWriter(ctx, options)
|
||||||
|
selected := ctx.Selected()
|
||||||
|
if selected == nil {
|
||||||
|
printApp(w, ctx.App.Model)
|
||||||
|
} else {
|
||||||
|
printCommand(w, ctx.App.Model, selected)
|
||||||
|
}
|
||||||
|
return w.Write(ctx.App.Stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
func printApp(w *helpWriter, app *Application) {
|
func printApp(w *helpWriter, app *Application) {
|
||||||
w.Printf("Usage: %s", app.Summary())
|
w.Printf("Usage: %s", app.Summary())
|
||||||
printNodeDetail(w, &app.Node)
|
printNodeDetail(w, &app.Node)
|
||||||
cmds := app.Leaves()
|
cmds := app.Leaves()
|
||||||
if len(cmds) > 0 {
|
if len(cmds) > 0 {
|
||||||
w.Print("")
|
w.Print("")
|
||||||
w.Printf(`Run "%s <command> --help" for more information on a command.`, app.Name)
|
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) {
|
func printCommand(w *helpWriter, app *Application, cmd *Command) {
|
||||||
w.Printf("Usage: %s %s", app.Name, cmd.Summary())
|
w.Printf("Usage: %s %s", app.Name, cmd.Summary())
|
||||||
printNodeDetail(w, cmd)
|
printNodeDetail(w, cmd)
|
||||||
|
if w.Summary {
|
||||||
|
w.Print("")
|
||||||
|
w.Printf(`Run "%s %s --help" for more information.`, app.Name, cmd.Path())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func printNodeDetail(w *helpWriter, node *Node) {
|
func printNodeDetail(w *helpWriter, node *Node) {
|
||||||
@@ -60,6 +68,9 @@ func printNodeDetail(w *helpWriter, node *Node) {
|
|||||||
w.Print("")
|
w.Print("")
|
||||||
w.Wrap(node.Help)
|
w.Wrap(node.Help)
|
||||||
}
|
}
|
||||||
|
if w.Summary {
|
||||||
|
return
|
||||||
|
}
|
||||||
if len(node.Positional) > 0 {
|
if len(node.Positional) > 0 {
|
||||||
w.Print("")
|
w.Print("")
|
||||||
w.Print("Arguments:")
|
w.Print("Arguments:")
|
||||||
@@ -75,7 +86,7 @@ func printNodeDetail(w *helpWriter, node *Node) {
|
|||||||
w.Print("")
|
w.Print("")
|
||||||
w.Print("Commands:")
|
w.Print("Commands:")
|
||||||
iw := w.Indent()
|
iw := w.Indent()
|
||||||
if w.options.compact {
|
if w.Compact {
|
||||||
rows := [][2]string{}
|
rows := [][2]string{}
|
||||||
for _, cmd := range cmds {
|
for _, cmd := range cmds {
|
||||||
rows = append(rows, [2]string{cmd.Path(), cmd.Help})
|
rows = append(rows, [2]string{cmd.Path(), cmd.Help})
|
||||||
@@ -100,23 +111,21 @@ func printCommandSummary(w *helpWriter, cmd *Command) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type helpWriter struct {
|
type helpWriter struct {
|
||||||
indent string
|
indent string
|
||||||
width int
|
width int
|
||||||
lines *[]string
|
lines *[]string
|
||||||
options helpWriterOptions
|
HelpPrinterOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
type helpWriterOptions struct {
|
func newHelpWriter(ctx *Context, options HelpPrinterOptions) *helpWriter {
|
||||||
compact bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newHelpWriter(width int) *helpWriter {
|
|
||||||
lines := []string{}
|
lines := []string{}
|
||||||
return &helpWriter{
|
w := &helpWriter{
|
||||||
indent: "",
|
indent: "",
|
||||||
width: width,
|
width: guessWidth(ctx.App.Stdout),
|
||||||
lines: &lines,
|
lines: &lines,
|
||||||
|
HelpPrinterOptions: options,
|
||||||
}
|
}
|
||||||
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *helpWriter) Printf(format string, args ...interface{}) {
|
func (h *helpWriter) Printf(format string, args ...interface{}) {
|
||||||
@@ -128,7 +137,7 @@ func (h *helpWriter) Print(text string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *helpWriter) Indent() *helpWriter {
|
func (h *helpWriter) Indent() *helpWriter {
|
||||||
return &helpWriter{indent: h.indent + " ", lines: h.lines, width: h.width - 2, options: h.options}
|
return &helpWriter{indent: h.indent + " ", lines: h.lines, width: h.width - 2, HelpPrinterOptions: h.HelpPrinterOptions}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *helpWriter) String() string {
|
func (h *helpWriter) String() string {
|
||||||
|
|||||||
+2
-2
@@ -53,7 +53,7 @@ func TestHelp(t *testing.T) {
|
|||||||
})
|
})
|
||||||
require.True(t, exited)
|
require.True(t, exited)
|
||||||
t.Log(w.String())
|
t.Log(w.String())
|
||||||
require.Equal(t, `Usage: test-app --required <command>
|
require.Equal(t, `Usage: test-app --required <command>
|
||||||
|
|
||||||
A test app.
|
A test app.
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ Run "test-app <command> --help" for more information on a command.
|
|||||||
})
|
})
|
||||||
require.True(t, exited)
|
require.True(t, exited)
|
||||||
t.Log(w.String())
|
t.Log(w.String())
|
||||||
require.Equal(t, `Usage: test-app two <three> --required --required-two --required-three
|
require.Equal(t, `Usage: test-app two <three> --required --required-two --required-three
|
||||||
|
|
||||||
Sub-sub-arg.
|
Sub-sub-arg.
|
||||||
|
|
||||||
|
|||||||
@@ -38,13 +38,13 @@ type Kong struct {
|
|||||||
Stdout io.Writer
|
Stdout io.Writer
|
||||||
Stderr io.Writer
|
Stderr io.Writer
|
||||||
|
|
||||||
before map[reflect.Value]HookFunc
|
before map[reflect.Value]HookFunc
|
||||||
resolvers []ResolverFunc
|
resolvers []ResolverFunc
|
||||||
registry *Registry
|
registry *Registry
|
||||||
noDefaultHelp bool
|
noDefaultHelp bool
|
||||||
noUsageOnError bool
|
usageOnError bool
|
||||||
help func(*Context) error
|
help HelpPrinter
|
||||||
helpOptions []HelpOption
|
helpOptions HelpPrinterOptions
|
||||||
|
|
||||||
// Set temporarily by Options. These are applied after build().
|
// Set temporarily by Options. These are applied after build().
|
||||||
postBuildOptions []Option
|
postBuildOptions []Option
|
||||||
@@ -70,7 +70,7 @@ func New(grammar interface{}, options ...Option) (*Kong, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if k.help == nil {
|
if k.help == nil {
|
||||||
k.help = HelpPrinter(k.helpOptions...)
|
k.help = DefaultHelpPrinter
|
||||||
}
|
}
|
||||||
|
|
||||||
model, err := build(k, grammar)
|
model, err := build(k, grammar)
|
||||||
@@ -108,7 +108,9 @@ func (k *Kong) extraFlags() []*Flag {
|
|||||||
}
|
}
|
||||||
helpFlag.Flag = helpFlag
|
helpFlag.Flag = helpFlag
|
||||||
hook := Hook(&helpValue, func(ctx *Context, path *Path) error {
|
hook := Hook(&helpValue, func(ctx *Context, path *Path) error {
|
||||||
err := k.help(ctx)
|
options := k.helpOptions
|
||||||
|
options.Summary = false
|
||||||
|
err := k.help(options, ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -119,15 +121,22 @@ func (k *Kong) extraFlags() []*Flag {
|
|||||||
return []*Flag{helpFlag}
|
return []*Flag{helpFlag}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Help writes help for the given args to the stdout io.Writer associated with this Kong.
|
// Help writes help for the given error to the stdout io.Writer associated with this Kong.
|
||||||
|
//
|
||||||
|
// "err" should be the error returned by Parse().
|
||||||
//
|
//
|
||||||
// See Help() and Writers() for overriding the help function and stdout, respectively.
|
// See Help() and Writers() for overriding the help function and stdout, respectively.
|
||||||
func (k *Kong) Help(args []string) error {
|
func (k *Kong) Help(err error) error {
|
||||||
ctx, err := Trace(k, args)
|
var ctx *Context
|
||||||
if err != nil {
|
if perr, ok := err.(*ParseError); ok {
|
||||||
return err
|
ctx = perr.Context
|
||||||
|
} else {
|
||||||
|
ctx, err = Trace(k, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return k.help(ctx)
|
return k.help(k.helpOptions, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse arguments into target.
|
// Parse arguments into target.
|
||||||
@@ -215,9 +224,11 @@ func (k *Kong) FatalIfErrorf(err error, args ...interface{}) {
|
|||||||
}
|
}
|
||||||
k.Errorf("%s", msg)
|
k.Errorf("%s", msg)
|
||||||
// Maybe display usage information.
|
// Maybe display usage information.
|
||||||
if err, ok := err.(*ParseError); ok && !k.noUsageOnError {
|
if err, ok := err.(*ParseError); ok && k.usageOnError {
|
||||||
fmt.Fprintln(k.Stdout)
|
fmt.Fprintln(k.Stdout)
|
||||||
_ = k.help(err.Context)
|
options := k.helpOptions
|
||||||
|
options.Summary = true
|
||||||
|
_ = k.help(options, err.Context)
|
||||||
}
|
}
|
||||||
k.Exit(1)
|
k.Exit(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package kong
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/bits"
|
"math/bits"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -20,9 +21,9 @@ type DecodeContext struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithScanner creates a clone of this context with a new Scanner.
|
// WithScanner creates a clone of this context with a new Scanner.
|
||||||
func (d *DecodeContext) WithScanner(scan *Scanner) *DecodeContext {
|
func (r *DecodeContext) WithScanner(scan *Scanner) *DecodeContext {
|
||||||
return &DecodeContext{
|
return &DecodeContext{
|
||||||
Value: d.Value,
|
Value: r.Value,
|
||||||
Scan: scan,
|
Scan: scan,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,8 +45,8 @@ type BoolMapper interface {
|
|||||||
// A MapperFunc is a single function that complies with the Mapper interface.
|
// A MapperFunc is a single function that complies with the Mapper interface.
|
||||||
type MapperFunc func(ctx *DecodeContext, target reflect.Value) error
|
type MapperFunc func(ctx *DecodeContext, target reflect.Value) error
|
||||||
|
|
||||||
func (d MapperFunc) Decode(ctx *DecodeContext, target reflect.Value) error { //nolint: golint
|
func (m MapperFunc) Decode(ctx *DecodeContext, target reflect.Value) error { //nolint: golint
|
||||||
return d(ctx, target)
|
return m(ctx, target)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Registry contains a set of mappers and supporting lookup methods.
|
// A Registry contains a set of mappers and supporting lookup methods.
|
||||||
@@ -66,42 +67,52 @@ func NewRegistry() *Registry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForNamedType finds a mapper for a value with a user-specified type.
|
// ForNamedValue finds a mapper for a value with a user-specified name.
|
||||||
//
|
//
|
||||||
// Will return nil if a mapper can not be determined.
|
// Will return nil if a mapper can not be determined.
|
||||||
func (d *Registry) ForNamedType(name string, value reflect.Value) Mapper {
|
func (r *Registry) ForNamedValue(name string, value reflect.Value) Mapper {
|
||||||
if mapper, ok := d.names[name]; ok {
|
if mapper, ok := r.names[name]; ok {
|
||||||
return mapper
|
return mapper
|
||||||
}
|
}
|
||||||
return d.ForValue(value)
|
return r.ForValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForValue looks up the Mapper for a reflect.Value.
|
// ForValue looks up the Mapper for a reflect.Value.
|
||||||
func (d *Registry) ForValue(value reflect.Value) Mapper {
|
func (r *Registry) ForValue(value reflect.Value) Mapper {
|
||||||
if mapper, ok := d.values[value]; ok {
|
if mapper, ok := r.values[value]; ok {
|
||||||
return mapper
|
return mapper
|
||||||
}
|
}
|
||||||
return d.ForType(value.Type())
|
return r.ForType(value.Type())
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForType finds a mapper from a type, by type, then kind.
|
// ForType finds a mapper from a type, by type, then kind.
|
||||||
//
|
//
|
||||||
// Will return nil if a mapper can not be determined.
|
// Will return nil if a mapper can not be determined.
|
||||||
func (d *Registry) ForType(typ reflect.Type) Mapper {
|
func (r *Registry) ForType(typ reflect.Type) Mapper {
|
||||||
var mapper Mapper
|
var mapper Mapper
|
||||||
var ok bool
|
var ok bool
|
||||||
if mapper, ok = d.types[typ]; ok {
|
if mapper, ok = r.types[typ]; ok {
|
||||||
return mapper
|
return mapper
|
||||||
} else if mapper, ok = d.kinds[typ.Kind()]; ok {
|
} else if mapper, ok = r.kinds[typ.Kind()]; ok {
|
||||||
return mapper
|
return mapper
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterKind registers a Mapper for a reflect.Kind.
|
// RegisterKind registers a Mapper for a reflect.Kind.
|
||||||
func (d *Registry) RegisterKind(kind reflect.Kind, mapper Mapper) *Registry {
|
func (r *Registry) RegisterKind(kind reflect.Kind, mapper Mapper) *Registry {
|
||||||
d.kinds[kind] = mapper
|
r.kinds[kind] = mapper
|
||||||
return d
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterName registeres a mapper to be used if the value mapper has a "type" tag matching name.
|
// RegisterName registeres a mapper to be used if the value mapper has a "type" tag matching name.
|
||||||
@@ -110,31 +121,31 @@ func (d *Registry) RegisterKind(kind reflect.Kind, mapper Mapper) *Registry {
|
|||||||
//
|
//
|
||||||
// Mapper string `kong:"type='colour'`
|
// Mapper string `kong:"type='colour'`
|
||||||
// registry.RegisterName("colour", ...)
|
// registry.RegisterName("colour", ...)
|
||||||
func (d *Registry) RegisterName(name string, mapper Mapper) *Registry {
|
func (r *Registry) RegisterName(name string, mapper Mapper) *Registry {
|
||||||
d.names[name] = mapper
|
r.names[name] = mapper
|
||||||
return d
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterType registers a Mapper for a reflect.Type.
|
// RegisterType registers a Mapper for a reflect.Type.
|
||||||
func (d *Registry) RegisterType(typ reflect.Type, mapper Mapper) *Registry {
|
func (r *Registry) RegisterType(typ reflect.Type, mapper Mapper) *Registry {
|
||||||
d.types[typ] = mapper
|
r.types[typ] = mapper
|
||||||
return d
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterValue registers a Mapper by pointer to the field value.
|
// RegisterValue registers a Mapper by pointer to the field value.
|
||||||
func (d *Registry) RegisterValue(ptr interface{}, mapper Mapper) *Registry {
|
func (r *Registry) RegisterValue(ptr interface{}, mapper Mapper) *Registry {
|
||||||
key := reflect.ValueOf(ptr)
|
key := reflect.ValueOf(ptr)
|
||||||
if key.Kind() != reflect.Ptr {
|
if key.Kind() != reflect.Ptr {
|
||||||
panic("expected a pointer")
|
panic("expected a pointer")
|
||||||
}
|
}
|
||||||
key = key.Elem()
|
key = key.Elem()
|
||||||
d.values[key] = mapper
|
r.values[key] = mapper
|
||||||
return d
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterDefaults registers Mappers for all builtin supported Go types and some common stdlib types.
|
// RegisterDefaults registers Mappers for all builtin supported Go types and some common stdlib types.
|
||||||
func (d *Registry) RegisterDefaults() *Registry {
|
func (r *Registry) RegisterDefaults() *Registry {
|
||||||
return d.RegisterKind(reflect.Int, intDecoder(bits.UintSize)).
|
return r.RegisterKind(reflect.Int, intDecoder(bits.UintSize)).
|
||||||
RegisterKind(reflect.Int8, intDecoder(8)).
|
RegisterKind(reflect.Int8, intDecoder(8)).
|
||||||
RegisterKind(reflect.Int16, intDecoder(16)).
|
RegisterKind(reflect.Int16, intDecoder(16)).
|
||||||
RegisterKind(reflect.Int32, intDecoder(32)).
|
RegisterKind(reflect.Int32, intDecoder(32)).
|
||||||
@@ -153,8 +164,11 @@ func (d *Registry) RegisterDefaults() *Registry {
|
|||||||
RegisterKind(reflect.Bool, boolMapper{}).
|
RegisterKind(reflect.Bool, boolMapper{}).
|
||||||
RegisterType(reflect.TypeOf(time.Time{}), timeDecoder()).
|
RegisterType(reflect.TypeOf(time.Time{}), timeDecoder()).
|
||||||
RegisterType(reflect.TypeOf(time.Duration(0)), durationDecoder()).
|
RegisterType(reflect.TypeOf(time.Duration(0)), durationDecoder()).
|
||||||
RegisterKind(reflect.Slice, sliceDecoder(d)).
|
RegisterKind(reflect.Slice, sliceDecoder(r)).
|
||||||
RegisterKind(reflect.Map, mapDecoder(d))
|
RegisterKind(reflect.Map, mapDecoder(r)).
|
||||||
|
RegisterName("path", pathMapper(r)).
|
||||||
|
RegisterName("existingfile", existingFileMapper(r)).
|
||||||
|
RegisterName("existingdir", existingDirMapper(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
type boolMapper struct{}
|
type boolMapper struct{}
|
||||||
@@ -167,11 +181,11 @@ func (boolMapper) IsBool() bool { return true }
|
|||||||
|
|
||||||
func durationDecoder() MapperFunc {
|
func durationDecoder() MapperFunc {
|
||||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||||
d, err := time.ParseDuration(ctx.Scan.PopValue("duration"))
|
r, err := time.ParseDuration(ctx.Scan.PopValue("duration"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
target.Set(reflect.ValueOf(d))
|
target.Set(reflect.ValueOf(r))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -227,7 +241,7 @@ func floatDecoder(bits int) MapperFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapDecoder(d *Registry) MapperFunc {
|
func mapDecoder(r *Registry) MapperFunc {
|
||||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||||
if target.IsNil() {
|
if target.IsNil() {
|
||||||
target.Set(reflect.MakeMap(target.Type()))
|
target.Set(reflect.MakeMap(target.Type()))
|
||||||
@@ -240,15 +254,24 @@ func mapDecoder(d *Registry) MapperFunc {
|
|||||||
}
|
}
|
||||||
key, value := parts[0], parts[1]
|
key, value := parts[0], parts[1]
|
||||||
|
|
||||||
|
keyTypeName, valueTypeName := "", ""
|
||||||
|
if typ := ctx.Value.Tag.Type; typ != "" {
|
||||||
|
parts := strings.Split(typ, ":")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return fmt.Errorf("type:\"\" on map field must be in the form \"[<keytype>]:[<valuetype>]\"")
|
||||||
|
}
|
||||||
|
keyTypeName, valueTypeName = parts[0], parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
keyScanner := Scan(key)
|
keyScanner := Scan(key)
|
||||||
keyDecoder := d.ForType(el.Key())
|
keyDecoder := r.ForNamedType(keyTypeName, el.Key())
|
||||||
keyValue := reflect.New(el.Key()).Elem()
|
keyValue := reflect.New(el.Key()).Elem()
|
||||||
if err := keyDecoder.Decode(ctx.WithScanner(keyScanner), keyValue); err != nil {
|
if err := keyDecoder.Decode(ctx.WithScanner(keyScanner), keyValue); err != nil {
|
||||||
return fmt.Errorf("invalid map key %q", key)
|
return fmt.Errorf("invalid map key %q", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
valueScanner := Scan(value)
|
valueScanner := Scan(value)
|
||||||
valueDecoder := d.ForType(el.Elem())
|
valueDecoder := r.ForNamedType(valueTypeName, el.Elem())
|
||||||
valueValue := reflect.New(el.Elem()).Elem()
|
valueValue := reflect.New(el.Elem()).Elem()
|
||||||
if err := valueDecoder.Decode(ctx.WithScanner(valueScanner), valueValue); err != nil {
|
if err := valueDecoder.Decode(ctx.WithScanner(valueScanner), valueValue); err != nil {
|
||||||
return fmt.Errorf("invalid map value %q", value)
|
return fmt.Errorf("invalid map value %q", value)
|
||||||
@@ -259,7 +282,7 @@ func mapDecoder(d *Registry) MapperFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sliceDecoder(d *Registry) MapperFunc {
|
func sliceDecoder(r *Registry) MapperFunc {
|
||||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||||
el := target.Type().Elem()
|
el := target.Type().Elem()
|
||||||
sep := ctx.Value.Tag.Sep
|
sep := ctx.Value.Tag.Sep
|
||||||
@@ -271,7 +294,7 @@ func sliceDecoder(d *Registry) MapperFunc {
|
|||||||
tokens := ctx.Scan.PopWhile(func(t Token) bool { return t.IsValue() })
|
tokens := ctx.Scan.PopWhile(func(t Token) bool { return t.IsValue() })
|
||||||
childScanner = Scan(tokens...)
|
childScanner = Scan(tokens...)
|
||||||
}
|
}
|
||||||
childDecoder := d.ForType(el)
|
childDecoder := r.ForNamedType(ctx.Value.Tag.Type, el)
|
||||||
if childDecoder == nil {
|
if childDecoder == nil {
|
||||||
return fmt.Errorf("no mapper for element type of %s", target.Type())
|
return fmt.Errorf("no mapper for element type of %s", target.Type())
|
||||||
}
|
}
|
||||||
@@ -287,6 +310,56 @@ func sliceDecoder(d *Registry) MapperFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pathMapper(r *Registry) MapperFunc {
|
||||||
|
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||||
|
if target.Kind() == reflect.Slice {
|
||||||
|
return sliceDecoder(r)(ctx, target)
|
||||||
|
}
|
||||||
|
path := ctx.Scan.PopValue("file")
|
||||||
|
path = expandPath(path)
|
||||||
|
target.SetString(path)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
path := ctx.Scan.PopValue("file")
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
path := ctx.Scan.PopValue("file")
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SplitEscaped splits a string on a separator.
|
// 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.
|
// It differs from strings.Split() in that the separator can exist in a field by escaping it with a \. eg.
|
||||||
|
|||||||
+23
-3
@@ -34,10 +34,16 @@ func TestNamedMapper(t *testing.T) {
|
|||||||
require.Equal(t, "MOO", cli.Flag)
|
require.Equal(t, "MOO", cli.Flag)
|
||||||
}
|
}
|
||||||
|
|
||||||
type testMooMapper struct{}
|
type testMooMapper struct {
|
||||||
|
text string
|
||||||
|
}
|
||||||
|
|
||||||
func (testMooMapper) Decode(ctx *DecodeContext, target reflect.Value) error {
|
func (t testMooMapper) Decode(ctx *DecodeContext, target reflect.Value) error {
|
||||||
target.SetString("MOO")
|
if t.text == "" {
|
||||||
|
target.SetString("MOO")
|
||||||
|
} else {
|
||||||
|
target.SetString(t.text)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (testMooMapper) IsBool() bool { return true }
|
func (testMooMapper) IsBool() bool { return true }
|
||||||
@@ -75,3 +81,17 @@ func TestJoinEscaped(t *testing.T) {
|
|||||||
require.Equal(t, `a\,b,c`, JoinEscaped([]string{`a,b`, `c`}, ','))
|
require.Equal(t, `a\,b,c`, JoinEscaped([]string{`a,b`, `c`}, ','))
|
||||||
require.Equal(t, JoinEscaped(SplitEscaped(`a\,b,c`, ','), ','), `a\,b,c`)
|
require.Equal(t, JoinEscaped(SplitEscaped(`a\,b,c`, ','), ','), `a\,b,c`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMapWithNamedTypes(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
TypedValue map[string]string `type:":moo"`
|
||||||
|
TypedKey map[string]string `type:"upper:"`
|
||||||
|
}
|
||||||
|
k := mustNew(t, &cli, NamedMapper("moo", testMooMapper{}), NamedMapper("upper", testUppercaseMapper{}))
|
||||||
|
_, err := k.Parse([]string{"--typed-value", "first=5s", "--typed-value", "second=10s"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, map[string]string{"first": "MOO", "second": "MOO"}, cli.TypedValue)
|
||||||
|
_, err = k.Parse([]string{"--typed-key", "first=5s", "--typed-key", "second=10s"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, map[string]string{"FIRST": "5s", "SECOND": "10s"}, cli.TypedKey)
|
||||||
|
}
|
||||||
|
|||||||
+7
-12
@@ -111,31 +111,26 @@ func Hook(ptr interface{}, hook HookFunc) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HelpFunction is the type of a function used to display help.
|
// Help printer to use.
|
||||||
type HelpFunction func(*Context) error
|
func Help(help HelpPrinter) Option {
|
||||||
|
|
||||||
// Help function to use.
|
|
||||||
//
|
|
||||||
// Defaults to PrintHelp.
|
|
||||||
func Help(help HelpFunction) Option {
|
|
||||||
return func(k *Kong) error {
|
return func(k *Kong) error {
|
||||||
k.help = help
|
k.help = help
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HelpOptions specifies options for the default help printer, if used.
|
// HelpOptions sets the HelpPrinterOptions to use for printing help.
|
||||||
func HelpOptions(options ...HelpOption) Option {
|
func HelpOptions(options HelpPrinterOptions) Option {
|
||||||
return func(k *Kong) error {
|
return func(k *Kong) error {
|
||||||
k.helpOptions = options
|
k.helpOptions = options
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NoUsageOnError configures Kong to NOT display context-sensitive usage if FatalIfErrorf is called with an error.
|
// UsageOnError configures Kong to display context-sensitive usage if FatalIfErrorf is called with an error.
|
||||||
func NoUsageOnError() Option {
|
func UsageOnError() Option {
|
||||||
return func(k *Kong) error {
|
return func(k *Kong) error {
|
||||||
k.noUsageOnError = true
|
k.usageOnError = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user