diff --git a/README.md b/README.md
index 489a28e..aefc505 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+

# Kong is a command-line parser for Go [](https://circleci.com/gh/alecthomas/kong)
@@ -8,17 +9,20 @@
1. [Help](#help)
1. [Flags](#flags)
1. [Commands and sub-commands](#commands-and-sub-commands)
+1. [Branching positional arguments](#branching-positional-arguments)
+1. [Terminating positional arguments](#terminating-positional-arguments)
1. [Supported tags](#supported-tags)
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. [`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. [`*Mapper(...)` - customising how the command-line is mapped to Go values](#mapper---customising-how-the-command-line-is-mapped-to-go-values)
1. [`Help(HelpFunc)` - customising help](#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. [Other options](#other-options)
-
## Introduction
Kong aims to support arbitrarily complex command-line structures with as little developer effort as possible.
@@ -27,10 +31,8 @@ To achieve that, command-lines are expressed as Go types, with the structure and
For example, the following command-line:
-```
-shell rm [-f] [-r] ...
-shell ls [ ...]
-```
+ shell rm [-f] [-r] ...
+ shell ls [ ...]
Can be represented by the following command-line structure:
@@ -63,59 +65,78 @@ Help is automatically generated. With no other arguments provided, help will dis
eg.
-```
-$ shell --help
-usage: shell
+ $ shell --help
+ usage: shell
-A shell-like example app.
+ A shell-like example app.
-Flags:
- --help Show context-sensitive help.
- --debug Debug mode.
+ Flags:
+ --help Show context-sensitive help.
+ --debug Debug mode.
-Commands:
- rm ...
- Remove files.
+ Commands:
+ rm ...
+ Remove files.
- ls [ ...]
- List paths.
-```
+ ls [ ...]
+ List paths.
If a command is provided, the help will show full detail on the command including all available flags.
eg.
-```
-$ shell --help rm
-usage: shell rm ...
+ $ shell --help rm
+ usage: shell rm ...
-Remove files.
+ Remove files.
-Arguments:
- ... Paths to remove.
+ Arguments:
+ ... Paths to remove.
-Flags:
- --debug Debug mode.
+ Flags:
+ --debug Debug mode.
- -f, --force Force removal.
- -r, --recursive Recursively remove files.
-```
+ -f, --force Force removal.
+ -r, --recursive Recursively remove files.
## Flags
-Any field in the command structure *not* tagged with `cmd` or `arg` will be a flag. Flags are optional by default.
+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
-Kong supports arbitrarily nested commands and positional arguments. Nested structs tagged with `cmd` will be treated as commands.
+Sub-commands are specified by tagging a struct field with `cmd`. Kong supports arbitrarily nested commands.
-Arguments can also optionally have children, in order to support commands like the following:
+eg. The following struct represents the CLI structure `command [--flag="str"] sub-command`.
-```
-app rename to
+```go
+type CLI struct {
+ Command struct {
+ Flag string
+
+ SubCommand struct {
+ } `cmd`
+ } `cmd`
+}
```
-This is achieved by tagging a nested struct with `arg`, then including a positional argument field inside that struct with the same name. For example:
+## Branching positional arguments
+
+In addition to sub-commands, structs can also be configured as branching positional arguments.
+
+This is achieved by tagging an [unmapped](#mapper---customising-how-the-command-line-is-mapped-to-go-values) nested struct field with `arg`, then including a positional argument field inside that struct _with the same name_. For example, the following command structure:
+
+ app rename to
+
+Can be represented with the following:
```go
var CLI struct {
@@ -131,8 +152,13 @@ var CLI struct {
} `cmd`
}
```
+
This looks a little verbose in this contrived example, but typically this will not be the case.
+## Terminating positional arguments
+
+If a [mapped type](#mapper---customising-how-the-command-line-is-mapped-to-go-values) is tagged with `arg`,
+
## Supported tags
Tags can be in two forms:
@@ -147,12 +173,12 @@ Both can coexist with standard Tag parsing.
| `cmd` | If present, struct is a command. |
| `arg` | If present, field is an argument. |
| `env:"X"` | Specify envar to use for default value.
-| `type:"X"` | Specify named Mapper to use. |
+| `name:"X"` | Long name, for overriding field name. |
| `help:"X"` | Help text. |
+| `type:"X"` | Specify named Mapper to use. |
| `placeholder:"X"` | Placeholder text. |
| `default:"X"` | Default value. |
| `short:"X"` | Short name, if flag. |
-| `name:"X"` | Long name, for overriding field name. |
| `required` | If present, flag/arg is required. |
| `optional` | If present, flag/arg is optional. |
| `hidden` | If present, flag is hidden. |
@@ -163,7 +189,15 @@ Both can coexist with standard Tag parsing.
Each Kong parser can be configured via functional options passed to `New(cli interface{}, options...Option)`.
-The full set of options can be found in [options.go](https://github.com/alecthomas/kong/blob/master/options.go).
+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
@@ -201,8 +235,7 @@ All builtin Go types (as well as a bunch of useful stdlib types like `time.Time`
1. `NamedMapper(string, Mapper)` and using the tag key `type:""`.
2. `KindMapper(reflect.Kind, Mapper)`.
3. `TypeMapper(reflect.Type, Mapper)`.
-4. `ValueMapper(interface{}, Mapper)`, passing in a pointer to a field of the grammar.
-
+4. `ValueMapper(interface{}, Mapper)`, passing in a pointer to a field of the grammar.
### `Help(HelpFunc)` - customising help
@@ -230,3 +263,7 @@ if CLI.Debug {
```
But under some circumstances, hooks are the right choice.
+
+### Other options
+
+The full set of options can be found [here](https://godoc.org/github.com/alecthomas/kong#Option).
diff --git a/context.go b/context.go
index 20ef8c5..1f39e74 100755
--- a/context.go
+++ b/context.go
@@ -55,6 +55,8 @@ func (c *Context) Selected() *Node {
// Trace path of "args" through the gammar tree.
//
// The returned Context will include a Path of all commands, arguments, positionals and flags.
+//
+// Note that this will not modify the target grammar. Call Apply() to do so.
func Trace(k *Kong, args []string) (*Context, error) {
c := &Context{
App: k,
@@ -62,17 +64,10 @@ func Trace(k *Kong, args []string) (*Context, error) {
Path: []*Path{
{App: k.Model, Flags: k.Model.Flags, Value: k.Model.Target},
},
- }
- err := c.reset(&c.App.Model.Node)
- if err != nil {
- return nil, err
+ scan: Scan(args...),
}
c.Error = c.trace(&c.App.Model.Node)
- err = c.traceResolvers()
- if err != nil {
- return nil, err
- }
- return c, nil
+ return c, c.traceResolvers()
}
// Validate the current context.
@@ -155,7 +150,6 @@ func (c *Context) FlagValue(flag *Flag) reflect.Value {
// Recursively reset values to defaults (as specified in the grammar) or the zero value.
func (c *Context) reset(node *Node) error {
- c.scan = Scan(c.args...)
for _, flag := range node.Flags {
err := flag.Value.Reset()
if err != nil {
@@ -310,31 +304,6 @@ func (c *Context) trace(node *Node) (err error) { // nolint: gocyclo
return nil
}
-// Apply traced context to the target grammar.
-func (c *Context) Apply() (string, error) {
- path := []string{}
-
- for _, trace := range c.Path {
- switch {
- case trace.App != nil:
- case trace.Argument != nil:
- path = append(path, "<"+trace.Argument.Name+">")
- trace.Argument.Argument.Apply(trace.Value)
- case trace.Command != nil:
- path = append(path, trace.Command.Name)
- case trace.Flag != nil:
- trace.Flag.Value.Apply(trace.Value)
- case trace.Positional != nil:
- path = append(path, "<"+trace.Positional.Name+">")
- trace.Positional.Apply(trace.Value)
- default:
- panic("unsupported path ?!")
- }
- }
-
- return strings.Join(path, " "), nil
-}
-
// Walk through flags from existing nodes in the path.
func (c *Context) traceResolvers() error {
if len(c.App.resolvers) == 0 {
@@ -370,6 +339,36 @@ func (c *Context) traceResolvers() error {
return nil
}
+// Apply traced context to the target grammar.
+func (c *Context) Apply() (string, error) {
+ err := c.reset(&c.App.Model.Node)
+ if err != nil {
+ return "", err
+ }
+
+ path := []string{}
+
+ for _, trace := range c.Path {
+ switch {
+ case trace.App != nil:
+ case trace.Argument != nil:
+ path = append(path, "<"+trace.Argument.Name+">")
+ trace.Argument.Argument.Apply(trace.Value)
+ case trace.Command != nil:
+ path = append(path, trace.Command.Name)
+ case trace.Flag != nil:
+ trace.Flag.Value.Apply(trace.Value)
+ case trace.Positional != nil:
+ path = append(path, "<"+trace.Positional.Name+">")
+ trace.Positional.Apply(trace.Value)
+ default:
+ panic("unsupported path ?!")
+ }
+ }
+
+ return strings.Join(path, " "), nil
+}
+
func (c *Context) matchFlags(flags []*Flag, matcher func(f *Flag) bool) (err error) {
defer catch(&err)
token := c.scan.Peek()
diff --git a/global_test.go b/global_test.go
index e371c62..ed0ee83 100644
--- a/global_test.go
+++ b/global_test.go
@@ -25,7 +25,7 @@ func TestParseHandlingBadBuild(t *testing.T) {
}
}()
- Parse(&cli, ExitFunction(func(_ int) { panic("exiting") }))
+ Parse(&cli, Exit(func(_ int) { panic("exiting") }))
require.Fail(t, "we were expecting a panic")
}
diff --git a/help_test.go b/help_test.go
index b1dfffe..5d5e76d 100644
--- a/help_test.go
+++ b/help_test.go
@@ -38,7 +38,7 @@ func TestHelp(t *testing.T) {
Name("test-app"),
Description("A test app."),
Writers(w, w),
- ExitFunction(func(int) {
+ Exit(func(int) {
exited = true
panic(true) // Panic to fake "exit".
}),
diff --git a/kong.go b/kong.go
index dc2fbf3..98ea668 100644
--- a/kong.go
+++ b/kong.go
@@ -108,9 +108,15 @@ func (k *Kong) extraFlags() []*Flag {
return []*Flag{helpFlag}
}
-// Trace parses the command-line, validating and collecting matching grammar nodes.
-func (k *Kong) Trace(args []string) (*Context, error) {
- return Trace(k, args)
+// Help writes help for the given args to the stdout io.Writer associated with this Kong.
+//
+// See Help() and Writers() for overriding the help function and stdout, respectively.
+func (k *Kong) Help(args []string) error {
+ ctx, err := Trace(k, args)
+ if err != nil {
+ return err
+ }
+ return k.help(ctx)
}
// Parse arguments into target.
@@ -119,7 +125,7 @@ func (k *Kong) Trace(args []string) (*Context, error) {
// the command name while positional arguments are the argument name surrounded by "".
func (k *Kong) Parse(args []string) (command string, err error) {
defer catch(&err)
- ctx, err := k.Trace(args)
+ ctx, err := Trace(k, args)
if err != nil {
return "", err
}
@@ -165,13 +171,15 @@ func (k *Kong) applyHooks(ctx *Context) error {
}
// Printf writes a message to Kong.Stdout with the application name prefixed.
-func (k *Kong) Printf(format string, args ...interface{}) {
+func (k *Kong) Printf(format string, args ...interface{}) *Kong {
fmt.Fprintf(k.Stdout, k.Model.Name+": "+format, args...)
+ return k
}
// Errorf writes a message to Kong.Stderr with the application name prefixed.
-func (k *Kong) Errorf(format string, args ...interface{}) {
+func (k *Kong) Errorf(format string, args ...interface{}) *Kong {
fmt.Fprintf(k.Stderr, k.Model.Name+": error: "+format, args...)
+ return k
}
// FatalIfErrorf terminates with an error message if err != nil.
diff --git a/kong_test.go b/kong_test.go
index 1835ec4..5a169e4 100644
--- a/kong_test.go
+++ b/kong_test.go
@@ -10,7 +10,7 @@ import (
func mustNew(t *testing.T, cli interface{}, options ...Option) *Kong {
t.Helper()
options = append([]Option{
- ExitFunction(func(int) {
+ Exit(func(int) {
t.Helper()
t.Fatalf("unexpected exit()")
}),
@@ -322,7 +322,7 @@ func TestTraceErrorPartiallySucceeds(t *testing.T) {
} `kong:"cmd"`
}
p := mustNew(t, &cli)
- ctx, err := p.Trace([]string{"one", "bad"})
+ ctx, err := Trace(p, []string{"one", "bad"})
require.NoError(t, err)
require.Error(t, ctx.Error)
require.Equal(t, []string{"one"}, ctx.Command())
diff --git a/options.go b/options.go
index db43d7e..93d00e9 100755
--- a/options.go
+++ b/options.go
@@ -12,8 +12,8 @@ import (
// An Option applies optional changes to the Kong application.
type Option func(k *Kong)
-// ExitFunction overrides the function used to terminate. This is useful for testing or interactive use.
-func ExitFunction(exit func(int)) Option {
+// Exit overrides the function used to terminate. This is useful for testing or interactive use.
+func Exit(exit func(int)) Option {
return func(k *Kong) { k.Exit = exit }
}
diff --git a/options_test.go b/options_test.go
index fed4a81..ffbc521 100644
--- a/options_test.go
+++ b/options_test.go
@@ -11,7 +11,7 @@ import (
func TestOptions(t *testing.T) {
var cli struct{}
- p, err := New(&cli, Name("name"), Description("description"), Writers(nil, nil), ExitFunction(nil))
+ p, err := New(&cli, Name("name"), Description("description"), Writers(nil, nil), Exit(nil))
require.NoError(t, err)
require.Equal(t, "name", p.Model.Name)
require.Equal(t, "description", p.Model.Help)