Clean up disparity between Context and Kong.
Previously, there was a confusing mix of functionality shared between
the two wherein you would need to use the Kong type for printing errors,
etc. but it did not have access to the context in order to print
context-sensitive usage information. This has been fixed.
Additionally, there are now fuzzy correction suggestions for flags and
commands
Also added a server example which shows how Kong can be used for parsing
in interactive shells. Run with:
$ go run ./_examples/server/*.go
Then interact with:
$ ssh -p 6740 127.0.0.1
This commit is contained in:
@@ -23,7 +23,7 @@
|
||||
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. [`HelpOptions(HelpPrinterOptions)` and `Help(HelpFunc)` - customising help](#helpoptionshelpprinteroptions-and-helphelpfunc---customising-help)
|
||||
1. [`ConfigureHelp(HelpOptions)` and `Help(HelpFunc)` - customising help](#configurehelphelpoptions-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. [Other options](#other-options)
|
||||
|
||||
@@ -61,12 +61,12 @@ var CLI struct {
|
||||
}
|
||||
|
||||
func main() {
|
||||
cmd := kong.Parse(&CLI)
|
||||
switch cmd {
|
||||
ctx := kong.Parse(&CLI)
|
||||
switch ctx.Command() {
|
||||
case "rm <path>":
|
||||
case "ls":
|
||||
default:
|
||||
panic(cmd)
|
||||
panic(ctx.Command())
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -142,12 +142,12 @@ var CLI struct {
|
||||
}
|
||||
|
||||
func main() {
|
||||
cmd := kong.Parse(&CLI)
|
||||
switch cmd {
|
||||
ctx := kong.Parse(&CLI)
|
||||
switch ctx.Command() {
|
||||
case "rm <path>":
|
||||
case "ls":
|
||||
default:
|
||||
panic(cmd)
|
||||
panic(ctx.Command())
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -197,15 +197,10 @@ var cli struct {
|
||||
}
|
||||
|
||||
func main() {
|
||||
parser := kong.Must(&cli)
|
||||
|
||||
// Parse and apply the command-line.
|
||||
ctx, err := parser.Parse(os.Args[1:])
|
||||
parser.FatalIfErrorf(err)
|
||||
|
||||
ctx := kong.Parse(&cli)
|
||||
// Call the Run() method of the selected parsed command.
|
||||
err = ctx.Run(cli.Debug)
|
||||
parser.FatalIfErrorf(err)
|
||||
ctx.FatalIfErrorf(err)
|
||||
}
|
||||
|
||||
```
|
||||
@@ -350,7 +345,7 @@ Both can coexist with standard Tag parsing.
|
||||
| `short:"X"` | Short name, if flag. |
|
||||
| `required` | If present, flag/arg is required. |
|
||||
| `optional` | If present, flag/arg is optional. |
|
||||
| `hidden` | If present, flag is hidden. |
|
||||
| `hidden` | If present, command or flag is hidden. |
|
||||
| `format:"X"` | Format for parsing input, if supported. |
|
||||
| `sep:"X"` | Separator for sequences (defaults to ","). May be `none` to disable splitting. |
|
||||
|
||||
@@ -406,11 +401,11 @@ All builtin Go types (as well as a bunch of useful stdlib types like `time.Time`
|
||||
3. `TypeMapper(reflect.Type, Mapper)`.
|
||||
4. `ValueMapper(interface{}, Mapper)`, passing in a pointer to a field of the grammar.
|
||||
|
||||
### `HelpOptions(HelpPrinterOptions)` and `Help(HelpFunc)` - customising help
|
||||
### `ConfigureHelp(HelpOptions)` and `Help(HelpFunc)` - customising help
|
||||
|
||||
The default help output is usually sufficient, but if not there are two solutions.
|
||||
|
||||
1. Use `HelpOptions(HelpPrinterOptions)` to configure how help is formatted (see [HelpPrinterOptions](https://godoc.org/github.com/alecthomas/kong#HelpPrinterOptions) for details).
|
||||
1. Use `ConfigureHelp(HelpOptions)` to configure how help is formatted (see [HelpOptions](https://godoc.org/github.com/alecthomas/kong#HelpOptions) for details).
|
||||
2. Custom help can be wired into Kong via the `Help(HelpFunc)` option. The `HelpFunc` is passed a `Context`, which contains the parsed context for the current command-line. See the implementation of `PrintHelp` for an example.
|
||||
|
||||
### `Hook(&field, HookFunc)` - callback hooks to execute when the command-line is parsed
|
||||
@@ -426,7 +421,7 @@ app := kong.Must(&CLI, kong.Hook(&CLI.Debug, func(ctx *Context, path *Path) erro
|
||||
}))
|
||||
```
|
||||
|
||||
Note: it is generally more advisable to use an imperative approach to building command-lines, eg.
|
||||
Note: it is generally less verbose to use an imperative approach to building command-lines, eg.
|
||||
|
||||
```go
|
||||
if CLI.Debug {
|
||||
@@ -434,7 +429,7 @@ if CLI.Debug {
|
||||
}
|
||||
```
|
||||
|
||||
But under some circumstances, hooks are the right choice.
|
||||
But under some circumstances, hooks can be useful.
|
||||
|
||||
### Other options
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/alecthomas/kong"
|
||||
)
|
||||
|
||||
@@ -68,20 +66,18 @@ type CLI struct {
|
||||
|
||||
func main() {
|
||||
cli := CLI{}
|
||||
parser := kong.Must(&cli,
|
||||
ctx := kong.Parse(&cli,
|
||||
kong.Name("docker"),
|
||||
kong.Description("A self-sufficient runtime for containers"),
|
||||
kong.UsageOnError(),
|
||||
kong.HelpOptions(kong.HelpPrinterOptions{
|
||||
kong.ConfigureHelp(kong.HelpOptions{
|
||||
Compact: true,
|
||||
}),
|
||||
//
|
||||
kong.Hook(&cli.VersionFlag, func(ctx *kong.Context, path *kong.Path) error {
|
||||
ctx.App.Printf("1.0.0").Exit(0)
|
||||
ctx.Printf("1.0.0").Exit(0)
|
||||
return nil
|
||||
}))
|
||||
ctx, err := parser.Parse(os.Args[1:])
|
||||
parser.FatalIfErrorf(err)
|
||||
err = ctx.Run(&cli.Globals)
|
||||
parser.FatalIfErrorf(err)
|
||||
err := ctx.Run(&cli.Globals)
|
||||
ctx.FatalIfErrorf(err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// nolint: govet
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/alecthomas/kong"
|
||||
)
|
||||
|
||||
// Ensure the grammar compiles.
|
||||
var _ = kong.Must(&grammar{})
|
||||
|
||||
// Server interface.
|
||||
type grammar struct {
|
||||
Help helpCmd `cmd help:"Show help."`
|
||||
Question helpCmd `cmd hidden name:"?" help:"Show help."`
|
||||
|
||||
Status statusCmd `cmd help:"Show server status."`
|
||||
}
|
||||
|
||||
type statusCmd struct {
|
||||
Verbose bool `short:"v" help:"Show verbose status information."`
|
||||
}
|
||||
|
||||
func (s *statusCmd) Run(ctx *kong.Context) error {
|
||||
ctx.Printf("OK")
|
||||
return nil
|
||||
}
|
||||
|
||||
type helpCmd struct {
|
||||
Command []string `arg optional help:"Show help on command."`
|
||||
}
|
||||
|
||||
// Run shows help.
|
||||
func (h *helpCmd) Run(realCtx *kong.Context) error {
|
||||
ctx, err := kong.Trace(realCtx.Kong, h.Command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ctx.Error != nil {
|
||||
return ctx.Error
|
||||
}
|
||||
err = ctx.PrintUsage(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(realCtx.Stdout)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
|
||||
"github.com/chzyer/readline"
|
||||
"github.com/gliderlabs/ssh"
|
||||
"github.com/google/shlex"
|
||||
"github.com/kr/pty"
|
||||
|
||||
"github.com/alecthomas/colour"
|
||||
"github.com/alecthomas/kong"
|
||||
)
|
||||
|
||||
type context struct {
|
||||
kong *kong.Context
|
||||
rl *readline.Instance
|
||||
}
|
||||
|
||||
func handle(log *log.Logger, s ssh.Session) error {
|
||||
log.Printf("New SSH")
|
||||
sshPty, _, isPty := s.Pty()
|
||||
if !isPty {
|
||||
return errors.New("No PTY requested")
|
||||
}
|
||||
log.Printf("Using TERM=%s width=%d height=%d", sshPty.Term, sshPty.Window.Width, sshPty.Window.Height)
|
||||
cpty, tty, err := pty.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tty.Close()
|
||||
state, err := terminal.GetState(int(cpty.Fd()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer terminal.Restore(int(cpty.Fd()), state)
|
||||
|
||||
colour.Fprintln(tty, "^BWelcome!^R")
|
||||
go io.Copy(cpty, s)
|
||||
go io.Copy(s, cpty)
|
||||
|
||||
parser, err := buildShellParser(tty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rl, err := readline.NewEx(&readline.Config{
|
||||
Prompt: "> ",
|
||||
Stderr: tty,
|
||||
Stdout: tty,
|
||||
Stdin: tty,
|
||||
FuncOnWidthChanged: func(f func()) {},
|
||||
FuncMakeRaw: func() error {
|
||||
_, err := terminal.MakeRaw(int(cpty.Fd())) // nolint: govet
|
||||
return err
|
||||
},
|
||||
FuncExitRaw: func() error { return nil },
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Loop")
|
||||
for {
|
||||
tty.Sync()
|
||||
|
||||
var line string
|
||||
line, err = rl.Readline()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
var args []string
|
||||
args, err := shlex.Split(string(line))
|
||||
if err != nil {
|
||||
parser.Errorf("%s", err)
|
||||
continue
|
||||
}
|
||||
var ctx *kong.Context
|
||||
ctx, err = parser.Parse(args)
|
||||
if err != nil {
|
||||
parser.Errorf("%s", err)
|
||||
if err, ok := err.(*kong.ParseError); ok {
|
||||
log.Println(err.Error())
|
||||
err.Context.PrintUsage(false)
|
||||
}
|
||||
continue
|
||||
}
|
||||
err = ctx.Run(ctx)
|
||||
if err != nil {
|
||||
parser.Errorf("%s", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func buildShellParser(tty *os.File) (*kong.Kong, error) {
|
||||
parser, err := kong.New(&grammar{},
|
||||
kong.Name(""),
|
||||
kong.Description("Example using Kong for interactive command parsing."),
|
||||
kong.Writers(tty, tty),
|
||||
kong.Exit(func(int) {}),
|
||||
kong.ConfigureHelp(kong.HelpOptions{
|
||||
NoAppSummary: true,
|
||||
}),
|
||||
kong.NoDefaultHelp(),
|
||||
)
|
||||
return parser, err
|
||||
}
|
||||
|
||||
func handlerWithError(handle func(log *log.Logger, s ssh.Session) error) ssh.Handler {
|
||||
return func(s ssh.Session) {
|
||||
prefix := fmt.Sprintf("%s->%s ", s.LocalAddr(), s.RemoteAddr())
|
||||
l := log.New(os.Stdout, prefix, log.LstdFlags)
|
||||
err := handle(l, s)
|
||||
if err != nil {
|
||||
log.Printf("error: %s", err)
|
||||
s.Exit(1)
|
||||
} else {
|
||||
log.Printf("Bye")
|
||||
s.Exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var cli struct {
|
||||
HostKey string `type:"existingfile" help:"SSH host key to use." default:"./_examples/server/server_rsa_key"`
|
||||
Bind string `help:"Bind address for server." default:"127.0.0.1:6740"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx := kong.Parse(&cli,
|
||||
kong.Name("server"),
|
||||
kong.Description("A network server using Kong for interacting with clients."))
|
||||
|
||||
ssh.Handle(handlerWithError(handle))
|
||||
log.Printf("SSH listening on: %s", cli.Bind)
|
||||
log.Printf("Using host key: %s", cli.HostKey)
|
||||
log.Println()
|
||||
parts := strings.Split(cli.Bind, ":")
|
||||
log.Printf("Connect with: ssh -p %s %s", parts[1], parts[0])
|
||||
log.Println()
|
||||
err := ssh.ListenAndServe(cli.Bind, nil, ssh.HostKeyFile(cli.HostKey))
|
||||
ctx.FatalIfErrorf(err)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAxK1QbPQYibc0VFZdc6GHPka5oTeGnXsMZiyX4JbHUJl1oNDB
|
||||
Xg9NATbft+q/6ZDjyVEQhq8xgLvYFkL8qBLt/6UAaOub0RtmPqQwmxoNLWuXFFwn
|
||||
YlBKApQ4gf58/jOcGPpdEwfjkjwLb536Bni25XMU4cYrdIvQIhtMaK+Zqja/3MAC
|
||||
V6ZRZZCd8hABJqaZu+3mRElnF1d7gfMvA/hhaq7Y5VYr8rUrBHHimrT/GEP6aCbf
|
||||
Npo43SfRnUDu2+EAK7vA9cM8fg/O/mvNR1/9jzOWyr8ZDLD6R6iaZCQ2anEPcqim
|
||||
MCOtibSeVOms07Zcn/TSsgmfGwa8rQkpXVJA5wIDAQABAoIBAQCTUccGdajTrzkx
|
||||
WyfQ71NgoJV3XyIkYAEfn5N8FTTi+LAVb4kILanemP3mw55RE8isCV65pA0OgqYP
|
||||
tsmOE+/WKAAwlxs1/LIPhekqpM7uEMMv6v9NMxrc562UIc36kyn/w7loAebCqNtg
|
||||
FhMsOcu1/wfLPidau0eB5LTNTYtq5RuSKxoindvatk+Zmk0KjoA+f25MlwCEHQNr
|
||||
ygpopclyTHVln2t3t0o97/a7dHa9+HlmVO4GxWvTTiqtcFErTGWtTUW8aeZFS83r
|
||||
p+JZNxReSJ2MlM9bm15wJ0L86GTeYZQiaNuC1XETbFvX+9Ffkl+7EtsdYDLV1N6r
|
||||
/eOP2f0hAoGBAOKVDHmnru7SVmbH5BI8HW5sd6IVztZM3+BKzy6AaPc4/FgG6MOr
|
||||
bJyFbmN8S/gVi4OYOJXgfaeKcycYJFTjXUSnNRQ9eT0MseD9SxzEXV7RGtnvudiu
|
||||
pbRmtBRtf3e4beaN9X4SfWk4+Frw7B8UsPXwV/09s7AW279cES565IkfAoGBAN42
|
||||
TQSC/jQmJBpGSnqWfqQtKPTSKFoZ/JQbxoy9QckAMqVSFwBBgwQYr4MbI7WyjPRE
|
||||
s43kpf+Sq/++fc3hyk5XAWBKscK0KLs0HBRZyLybQYI+f4/x2giVzKeRRNVa9nQa
|
||||
VdIU8i+eO2AUzG690q89HGkRBsfXekjq5kXC9Cc5AoGAUY0b5F16FPMXrf6cFAQX
|
||||
A7t+g5Qd0fvxSCUk1LPbE8Aq8vPpqyN0ABH2XVBLd4spn7+V/jvCfh7Su2txCCyd
|
||||
USxtak+F53c+PqBr/HqgsJPKek5SMa8KbRfaENAoZMq4o5bMmQfGo6yhlvnHwpgL
|
||||
6TkMMlWW6vYPOZzFglkxEDkCgYApT78Rz6ii2VRs7hR6pe/1Zc/vdAK8fYhPoLpQ
|
||||
//5y9+5yfch467UH1e8LWMhSx1cdMoiPIKsb0JDZgviwhgGufs5qsHhL0mKgKxft
|
||||
UKPZLKQJKsVcZYI7hl396Sv63mZjP2IlJG/CGpC/VB6NmAzLN3lIrzmrfYvmcoVN
|
||||
AumRQQKBgB4Uznek3nq5ZQf93+ucvXpf0bLRqq1va7jYmRosyiCN52grbclj5uMq
|
||||
vxr1uoqmgtCfqdgUbm0s+kVK6D4bPkz4HQOSXImXhLs8/KdixYfPLSarxYvTxZKg
|
||||
mMF1XqcdRwSv3RZYtUbbF7dYQYsC1/ZKXvtPldeoDmTZ+U7b2qbE
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@@ -0,0 +1 @@
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDErVBs9BiJtzRUVl1zoYc+RrmhN4adewxmLJfglsdQmXWg0MFeD00BNt+36r/pkOPJURCGrzGAu9gWQvyoEu3/pQBo65vRG2Y+pDCbGg0ta5cUXCdiUEoClDiB/nz+M5wY+l0TB+OSPAtvnfoGeLblcxThxit0i9AiG0xor5mqNr/cwAJXplFlkJ3yEAEmppm77eZESWcXV3uB8y8D+GFqrtjlVivytSsEceKatP8YQ/poJt82mjjdJ9GdQO7b4QAru8D1wzx+D87+a81HX/2PM5bKvxkMsPpHqJpkJDZqcQ9yqKYwI62JtJ5U6azTtlyf9NKyCZ8bBrytCSldUkDn
|
||||
@@ -23,13 +23,15 @@ var cli struct {
|
||||
}
|
||||
|
||||
func main() {
|
||||
cmd := kong.Parse(&cli, kong.Description("A shell-like example app."),
|
||||
ctx := kong.Parse(&cli,
|
||||
kong.Name("shell"),
|
||||
kong.Description("A shell-like example app."),
|
||||
kong.UsageOnError(),
|
||||
kong.HelpOptions(kong.HelpPrinterOptions{
|
||||
kong.ConfigureHelp(kong.HelpOptions{
|
||||
Compact: true,
|
||||
Summary: true,
|
||||
}))
|
||||
switch cmd {
|
||||
switch ctx.Command() {
|
||||
case "rm <path>":
|
||||
fmt.Println(cli.Rm.Paths, cli.Rm.Force, cli.Rm.Recursive)
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ func build(k *Kong, ast interface{}) (app *Application, err error) {
|
||||
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 = node
|
||||
app.Node.Flags = append(extraFlags, app.Node.Flags...)
|
||||
return app, nil
|
||||
}
|
||||
@@ -63,13 +63,13 @@ func buildNode(k *Kong, v reflect.Value, typ NodeType, seenFlags map[string]bool
|
||||
ft := field.field
|
||||
fv := field.value
|
||||
|
||||
name := ft.Tag.Get("name")
|
||||
tag := parseTag(fv, ft)
|
||||
|
||||
name := tag.Name
|
||||
if name == "" {
|
||||
name = strings.ToLower(dashedString(ft.Name))
|
||||
}
|
||||
|
||||
tag := parseTag(fv, ft)
|
||||
|
||||
// Nested structs are either commands or args.
|
||||
if ft.Type.Kind() == reflect.Struct && (tag.Cmd || tag.Arg) {
|
||||
typ := CommandNode
|
||||
@@ -105,6 +105,7 @@ func buildChild(k *Kong, node *Node, typ NodeType, v reflect.Value, ft reflect.S
|
||||
child := buildNode(k, fv, typ, seenFlags)
|
||||
child.Parent = node
|
||||
child.Help = tag.Help
|
||||
child.Hidden = tag.Hidden
|
||||
|
||||
// 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.
|
||||
|
||||
+59
-15
@@ -29,7 +29,7 @@ type Path struct {
|
||||
func (p *Path) Node() *Node {
|
||||
switch {
|
||||
case p.App != nil:
|
||||
return &p.App.Node
|
||||
return p.App.Node
|
||||
|
||||
case p.Argument != nil:
|
||||
return p.Argument
|
||||
@@ -42,7 +42,7 @@ func (p *Path) Node() *Node {
|
||||
|
||||
// Context contains the current parse context.
|
||||
type Context struct {
|
||||
App *Kong
|
||||
*Kong
|
||||
// A trace through parsed nodes.
|
||||
Path []*Path
|
||||
// Original command-line arguments.
|
||||
@@ -61,7 +61,7 @@ type Context struct {
|
||||
// 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,
|
||||
Kong: k,
|
||||
Args: args,
|
||||
Path: []*Path{
|
||||
{App: k.Model, Flags: k.Model.Flags},
|
||||
@@ -69,7 +69,7 @@ func Trace(k *Kong, args []string) (*Context, error) {
|
||||
values: map[*Value]reflect.Value{},
|
||||
scan: Scan(args...),
|
||||
}
|
||||
c.Error = c.trace(&c.App.Model.Node)
|
||||
c.Error = c.trace(c.Model.Node)
|
||||
return c, c.traceResolvers()
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ func (c *Context) Validate() error {
|
||||
// Check the terminal node.
|
||||
node := c.Selected()
|
||||
if node == nil {
|
||||
node = &c.App.Model.Node
|
||||
node = c.Model.Node
|
||||
}
|
||||
|
||||
// Find deepest positional argument so we can check if all required positionals have been provided.
|
||||
@@ -216,7 +216,10 @@ func (c *Context) reset(node *Node) error {
|
||||
func (c *Context) trace(node *Node) (err error) { // nolint: gocyclo
|
||||
positional := 0
|
||||
|
||||
flags := append(c.Flags(), node.Flags...)
|
||||
flags := []*Flag{}
|
||||
for _, group := range node.AllFlags(false) {
|
||||
flags = append(flags, group...)
|
||||
}
|
||||
|
||||
for !c.scan.Peek().IsEOL() {
|
||||
token := c.scan.Peek()
|
||||
@@ -285,6 +288,8 @@ func (c *Context) trace(node *Node) (err error) { // nolint: gocyclo
|
||||
return fmt.Errorf("unexpected flag argument %q", token.Value)
|
||||
|
||||
case PositionalArgumentToken:
|
||||
candidates := []string{}
|
||||
|
||||
// Ensure we've consumed all positional arguments.
|
||||
if positional < len(node.Positional) {
|
||||
arg := node.Positional[positional]
|
||||
@@ -302,6 +307,9 @@ func (c *Context) trace(node *Node) (err error) { // nolint: gocyclo
|
||||
|
||||
// After positional arguments have been consumed, check commands next...
|
||||
for _, branch := range node.Children {
|
||||
if branch.Type == CommandNode {
|
||||
candidates = append(candidates, branch.Name)
|
||||
}
|
||||
if branch.Type == CommandNode && branch.Name == token.Value {
|
||||
c.scan.Pop()
|
||||
c.Path = append(c.Path, &Path{
|
||||
@@ -327,8 +335,8 @@ func (c *Context) trace(node *Node) (err error) { // nolint: gocyclo
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("unexpected positional argument %s", token)
|
||||
|
||||
return findPotentialCandidates(token.Value, candidates, "unexpected argument %s", token)
|
||||
default:
|
||||
return fmt.Errorf("unexpected token %s", token)
|
||||
}
|
||||
@@ -336,9 +344,28 @@ func (c *Context) trace(node *Node) (err error) { // nolint: gocyclo
|
||||
return nil
|
||||
}
|
||||
|
||||
func findPotentialCandidates(needle string, haystack []string, format string, args ...interface{}) error {
|
||||
if len(haystack) == 0 {
|
||||
return fmt.Errorf(format, args...)
|
||||
}
|
||||
closestCandidates := []string{}
|
||||
for _, candidate := range haystack {
|
||||
if strings.HasPrefix(candidate, needle) || levenshtein(candidate, needle) <= 2 {
|
||||
closestCandidates = append(closestCandidates, fmt.Sprintf("%q", candidate))
|
||||
}
|
||||
}
|
||||
prefix := fmt.Sprintf(format, args...)
|
||||
if len(closestCandidates) == 1 {
|
||||
return fmt.Errorf("%s, did you mean %s?", prefix, closestCandidates[0])
|
||||
} else if len(closestCandidates) > 1 {
|
||||
return fmt.Errorf("%s, did you mean one of %s?", prefix, strings.Join(closestCandidates, ", "))
|
||||
}
|
||||
return fmt.Errorf("%s", prefix)
|
||||
}
|
||||
|
||||
// Walk through flags from existing nodes in the path.
|
||||
func (c *Context) traceResolvers() error {
|
||||
if len(c.App.resolvers) == 0 {
|
||||
if len(c.resolvers) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -349,7 +376,7 @@ func (c *Context) traceResolvers() error {
|
||||
if _, ok := c.values[flag.Value]; ok {
|
||||
continue
|
||||
}
|
||||
for _, resolver := range c.App.resolvers {
|
||||
for _, resolver := range c.resolvers {
|
||||
s, err := resolver(c, path, flag)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -386,7 +413,7 @@ func (c *Context) getValue(value *Value) reflect.Value {
|
||||
|
||||
// Apply traced context to the target grammar.
|
||||
func (c *Context) Apply() (string, error) {
|
||||
err := c.reset(&c.App.Model.Node)
|
||||
err := c.reset(c.Model.Node)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -420,8 +447,15 @@ func (c *Context) Apply() (string, error) {
|
||||
|
||||
func (c *Context) parseFlag(flags []*Flag, match string) (err error) {
|
||||
defer catch(&err)
|
||||
candidates := []string{}
|
||||
for _, flag := range flags {
|
||||
if "-"+string(flag.Short) != match && "--"+flag.Name != match {
|
||||
long := "--" + flag.Name
|
||||
short := "-" + string(flag.Short)
|
||||
candidates = append(candidates, long)
|
||||
if flag.Short != 0 {
|
||||
candidates = append(candidates, short)
|
||||
}
|
||||
if short != match && long != match {
|
||||
continue
|
||||
}
|
||||
// Found a matching flag.
|
||||
@@ -433,7 +467,7 @@ func (c *Context) parseFlag(flags []*Flag, match string) (err error) {
|
||||
c.Path = append(c.Path, &Path{Flag: flag})
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unknown flag %s", match)
|
||||
return findPotentialCandidates(match, candidates, "unknown flag %s", match)
|
||||
}
|
||||
|
||||
// Run executes the corresponding Run(params...) method on the target command selected by the parsed args.
|
||||
@@ -441,7 +475,7 @@ func (c *Context) parseFlag(flags []*Flag, match string) (err error) {
|
||||
// The target Run() method must exist and have the type signature "Run(params...) error".
|
||||
func (c *Context) Run(params ...interface{}) (err error) {
|
||||
defer catch(&err)
|
||||
expectedRunSignature, err := c.validateRun(&c.App.Model.Node, nil)
|
||||
expectedRunSignature, err := c.validateRun(c.Model.Node, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -478,6 +512,16 @@ func (c *Context) Run(params ...interface{}) (err error) {
|
||||
return result[0].Interface().(error)
|
||||
}
|
||||
|
||||
// PrintUsage to Kong's stdout.
|
||||
//
|
||||
// If summary is true, a summarised version of the help will be output.
|
||||
func (c *Context) PrintUsage(summary bool) error {
|
||||
options := c.helpOptions
|
||||
options.Summary = summary
|
||||
_ = c.help(options, c)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate that all commands have Run() methods and that their signatures are the same.
|
||||
func (c *Context) validateRun(node *Node, signature reflect.Type) (reflect.Type, error) {
|
||||
if node.Leaf() {
|
||||
@@ -554,12 +598,12 @@ func checkMissingChildren(node *Node) error {
|
||||
}
|
||||
|
||||
if len(missing) == 1 {
|
||||
return fmt.Errorf("%q should be followed by %s", node.Path(), missing[0])
|
||||
return fmt.Errorf("expected %s", missing[0])
|
||||
}
|
||||
if len(missing) > 5 {
|
||||
missing = append(missing[:5], "...")
|
||||
}
|
||||
return fmt.Errorf("%q should be followed by one of %s", node.Path(), strings.Join(missing, ", "))
|
||||
return fmt.Errorf("expected one of %s", strings.Join(missing, ", "))
|
||||
}
|
||||
|
||||
// If we're missing any positionals and they're required, return an error.
|
||||
|
||||
@@ -4,41 +4,13 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// App is the default global instance. It is populated by Parse().
|
||||
var App *Kong
|
||||
|
||||
// Parse constructs a new parser and parses the default command-line.
|
||||
func Parse(cli interface{}, options ...Option) string {
|
||||
func Parse(cli interface{}, options ...Option) *Context {
|
||||
parser, err := New(cli, options...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
App = parser
|
||||
ctx, err := parser.Parse(os.Args[1:])
|
||||
parser.FatalIfErrorf(err)
|
||||
return ctx.Command()
|
||||
}
|
||||
|
||||
// FatalIfErrorf terminates with an error message if err != nil.
|
||||
func FatalIfErrorf(err error, args ...interface{}) {
|
||||
if App == nil {
|
||||
panic("call kong.Parse() before using kong.FatalIfErrorf()")
|
||||
}
|
||||
App.FatalIfErrorf(err, args...)
|
||||
}
|
||||
|
||||
// Errorf writes a message to Kong.Stderr with the application name prefixed.
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
if App == nil {
|
||||
panic("call kong.Parse() before using kong.Errorf()")
|
||||
}
|
||||
App.Errorf(format, args...)
|
||||
}
|
||||
|
||||
// Printf writes a message to Kong.Stdout with the application name prefixed.
|
||||
func Printf(format string, args ...interface{}) {
|
||||
if App == nil {
|
||||
panic("call kong.Parse() before using kong.Printf()")
|
||||
}
|
||||
App.Printf(format, args...)
|
||||
return ctx
|
||||
}
|
||||
|
||||
@@ -31,6 +31,9 @@ func guessWidth(w io.Writer) int {
|
||||
uintptr(unsafe.Pointer(&dimensions)), // nolint: gas
|
||||
0, 0, 0,
|
||||
); err == 0 {
|
||||
if dimensions[1] == 0 {
|
||||
return 80
|
||||
}
|
||||
return int(dimensions[1])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,38 +13,43 @@ const (
|
||||
defaultColumnPadding = 4
|
||||
)
|
||||
|
||||
// HelpPrinterOptions for HelpPrinters.
|
||||
type HelpPrinterOptions struct {
|
||||
// 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 form, but still fully-specified.
|
||||
// Write help in a more compact, but still fully-specified, form.
|
||||
Compact bool
|
||||
}
|
||||
|
||||
// HelpPrinter is used to print context-sensitive help.
|
||||
type HelpPrinter func(options HelpPrinterOptions, ctx *Context) error
|
||||
type HelpPrinter func(options HelpOptions, ctx *Context) error
|
||||
|
||||
// DefaultHelpPrinter is the default HelpPrinter.
|
||||
func DefaultHelpPrinter(options HelpPrinterOptions, ctx *Context) error {
|
||||
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.App.Model)
|
||||
printApp(w, ctx.Model)
|
||||
} else {
|
||||
printCommand(w, ctx.App.Model, selected)
|
||||
printCommand(w, ctx.Model, selected)
|
||||
}
|
||||
return w.Write(ctx.App.Stdout)
|
||||
return w.Write(ctx.Stdout)
|
||||
}
|
||||
|
||||
func printApp(w *helpWriter, app *Application) {
|
||||
w.Printf("Usage: %s", app.Summary())
|
||||
printNodeDetail(w, &app.Node)
|
||||
cmds := app.Leaves()
|
||||
if len(cmds) > 0 {
|
||||
if !w.NoAppSummary {
|
||||
w.Printf("Usage: %s%s", app.Name, app.Summary())
|
||||
}
|
||||
printNodeDetail(w, app.Node)
|
||||
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)
|
||||
@@ -55,11 +60,12 @@ func printApp(w *helpWriter, app *Application) {
|
||||
}
|
||||
|
||||
func printCommand(w *helpWriter, app *Application, cmd *Command) {
|
||||
w.Printf("Usage: %s %s", app.Name, cmd.Summary())
|
||||
if !w.NoAppSummary {
|
||||
w.Printf("Usage: %s %s", app.Name, cmd.Summary())
|
||||
}
|
||||
printNodeDetail(w, cmd)
|
||||
if w.Summary {
|
||||
w.Print("")
|
||||
w.Printf(`Run "%s %s --help" for more information.`, app.Name, cmd.Path())
|
||||
if w.Summary && app.HelpFlag != nil {
|
||||
w.Printf(`Run "%s %s --help" for more information.`, app.Name, cmd.FullPath())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,12 +82,12 @@ func printNodeDetail(w *helpWriter, node *Node) {
|
||||
w.Print("Arguments:")
|
||||
writePositionals(w.Indent(), node.Positional)
|
||||
}
|
||||
if flags := node.AllFlags(); len(flags) > 0 {
|
||||
if flags := node.AllFlags(true); len(flags) > 0 {
|
||||
w.Print("")
|
||||
w.Print("Flags:")
|
||||
writeFlags(w.Indent(), flags)
|
||||
}
|
||||
cmds := node.Leaves()
|
||||
cmds := node.Leaves(true)
|
||||
if len(cmds) > 0 {
|
||||
w.Print("")
|
||||
w.Print("Commands:")
|
||||
@@ -114,16 +120,16 @@ type helpWriter struct {
|
||||
indent string
|
||||
width int
|
||||
lines *[]string
|
||||
HelpPrinterOptions
|
||||
HelpOptions
|
||||
}
|
||||
|
||||
func newHelpWriter(ctx *Context, options HelpPrinterOptions) *helpWriter {
|
||||
func newHelpWriter(ctx *Context, options HelpOptions) *helpWriter {
|
||||
lines := []string{}
|
||||
w := &helpWriter{
|
||||
indent: "",
|
||||
width: guessWidth(ctx.App.Stdout),
|
||||
lines: &lines,
|
||||
HelpPrinterOptions: options,
|
||||
indent: "",
|
||||
width: guessWidth(ctx.Stdout),
|
||||
lines: &lines,
|
||||
HelpOptions: options,
|
||||
}
|
||||
return w
|
||||
}
|
||||
@@ -137,7 +143,7 @@ func (h *helpWriter) Print(text string) {
|
||||
}
|
||||
|
||||
func (h *helpWriter) Indent() *helpWriter {
|
||||
return &helpWriter{indent: h.indent + " ", lines: h.lines, width: h.width - 2, HelpPrinterOptions: h.HelpPrinterOptions}
|
||||
return &helpWriter{indent: h.indent + " ", lines: h.lines, width: h.width - 2, HelpOptions: h.HelpOptions}
|
||||
}
|
||||
|
||||
func (h *helpWriter) String() string {
|
||||
|
||||
+5
-4
@@ -49,13 +49,13 @@ func TestHelp(t *testing.T) {
|
||||
)
|
||||
|
||||
t.Run("Full", func(t *testing.T) {
|
||||
require.Panics(t, func() {
|
||||
require.PanicsWithValue(t, true, func() {
|
||||
_, err := app.Parse([]string{"--help"})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
require.True(t, exited)
|
||||
t.Log(w.String())
|
||||
require.Equal(t, `Usage: test-app --required <command>
|
||||
require.Equal(t, `Usage: test-app --required <command>
|
||||
|
||||
A test app.
|
||||
|
||||
@@ -85,17 +85,18 @@ Run "test-app <command> --help" for more information on a command.
|
||||
t.Run("Selected", func(t *testing.T) {
|
||||
exited = false
|
||||
w.Truncate(0)
|
||||
require.Panics(t, func() {
|
||||
require.PanicsWithValue(t, true, func() {
|
||||
_, err := app.Parse([]string{"two", "hello", "--help"})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
require.True(t, exited)
|
||||
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.
|
||||
|
||||
Flags:
|
||||
--help Show context-sensitive help.
|
||||
--string=STRING A string flag.
|
||||
--bool A bool flag with very long help that wraps a lot and is
|
||||
verbose and is really verbose.
|
||||
|
||||
@@ -42,13 +42,15 @@ type Kong struct {
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
|
||||
before map[reflect.Value]HookFunc
|
||||
resolvers []ResolverFunc
|
||||
registry *Registry
|
||||
before map[reflect.Value]HookFunc
|
||||
resolvers []ResolverFunc
|
||||
registry *Registry
|
||||
|
||||
noDefaultHelp bool
|
||||
usageOnError bool
|
||||
help HelpPrinter
|
||||
helpOptions HelpPrinterOptions
|
||||
helpOptions HelpOptions
|
||||
helpFlag *Flag
|
||||
|
||||
// Set temporarily by Options. These are applied after build().
|
||||
postBuildOptions []Option
|
||||
@@ -83,6 +85,7 @@ func New(grammar interface{}, options ...Option) (*Kong, error) {
|
||||
}
|
||||
model.Name = filepath.Base(os.Args[0])
|
||||
k.Model = model
|
||||
k.Model.HelpFlag = k.helpFlag
|
||||
|
||||
for _, option := range k.postBuildOptions {
|
||||
if err := option(k); err != nil {
|
||||
@@ -121,6 +124,7 @@ func (k *Kong) extraFlags() []*Flag {
|
||||
k.Exit(1)
|
||||
return nil
|
||||
})
|
||||
k.helpFlag = helpFlag
|
||||
_ = hook(k)
|
||||
return []*Flag{helpFlag}
|
||||
}
|
||||
@@ -182,8 +186,15 @@ func (k *Kong) applyHooks(ctx *Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatMultilineMessage(w io.Writer, leader string, format string, args ...interface{}) {
|
||||
func formatMultilineMessage(w io.Writer, leaders []string, format string, args ...interface{}) {
|
||||
lines := strings.Split(fmt.Sprintf(format, args...), "\n")
|
||||
leader := ""
|
||||
for _, l := range leaders {
|
||||
if l == "" {
|
||||
continue
|
||||
}
|
||||
leader += l + ": "
|
||||
}
|
||||
fmt.Fprintf(w, "%s%s\n", leader, lines[0])
|
||||
for _, line := range lines[1:] {
|
||||
fmt.Fprintf(w, "%*s%s\n", len(leader), " ", line)
|
||||
@@ -192,16 +203,22 @@ func formatMultilineMessage(w io.Writer, leader string, format string, args ...i
|
||||
|
||||
// Printf writes a message to Kong.Stdout with the application name prefixed.
|
||||
func (k *Kong) Printf(format string, args ...interface{}) *Kong {
|
||||
formatMultilineMessage(k.Stdout, k.Model.Name+": ", format, args...)
|
||||
formatMultilineMessage(k.Stdout, []string{k.Model.Name}, format, args...)
|
||||
return k
|
||||
}
|
||||
|
||||
// Errorf writes a message to Kong.Stderr with the application name prefixed.
|
||||
func (k *Kong) Errorf(format string, args ...interface{}) *Kong {
|
||||
formatMultilineMessage(k.Stderr, k.Model.Name+": error: ", format, args...)
|
||||
formatMultilineMessage(k.Stderr, []string{k.Model.Name, "error"}, format, args...)
|
||||
return k
|
||||
}
|
||||
|
||||
// Fatalf writes a message to Kong.Stderr with the application name prefixed then exits with a non-zero status.
|
||||
func (k *Kong) Fatalf(format string, args ...interface{}) {
|
||||
k.Errorf(format, args...)
|
||||
k.Exit(1)
|
||||
}
|
||||
|
||||
// FatalIfErrorf terminates with an error message if err != nil.
|
||||
func (k *Kong) FatalIfErrorf(err error, args ...interface{}) {
|
||||
if err == nil {
|
||||
|
||||
@@ -207,6 +207,28 @@ func TestOptionalArg(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestOptionalArgWithDefault(t *testing.T) {
|
||||
var cli struct {
|
||||
Arg string `kong:"arg,optional,default='moo'"`
|
||||
}
|
||||
|
||||
parser := mustNew(t, &cli)
|
||||
_, err := parser.Parse([]string{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "moo", cli.Arg)
|
||||
}
|
||||
|
||||
func TestArgWithDefaultIsOptional(t *testing.T) {
|
||||
var cli struct {
|
||||
Arg string `kong:"arg,default='moo'"`
|
||||
}
|
||||
|
||||
parser := mustNew(t, &cli)
|
||||
_, err := parser.Parse([]string{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "moo", cli.Arg)
|
||||
}
|
||||
|
||||
func TestRequiredArg(t *testing.T) {
|
||||
var cli struct {
|
||||
Arg string `kong:"arg"`
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package kong
|
||||
|
||||
import "unicode/utf8"
|
||||
|
||||
// https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#Go
|
||||
// License: https://creativecommons.org/licenses/by-sa/3.0/
|
||||
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 {
|
||||
if a <= b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
@@ -9,7 +9,8 @@ import (
|
||||
|
||||
// Application is the root of the Kong model.
|
||||
type Application struct {
|
||||
Node
|
||||
*Node
|
||||
// Help flag, if the NoDefaultHelp() option is not specified.
|
||||
HelpFlag *Flag
|
||||
}
|
||||
|
||||
@@ -35,6 +36,7 @@ type Node struct {
|
||||
Parent *Node
|
||||
Name string
|
||||
Help string
|
||||
Hidden bool
|
||||
Flags []*Flag
|
||||
Positional []*Positional
|
||||
Children []*Node
|
||||
@@ -72,20 +74,33 @@ func (n *Node) findNode(key reflect.Value) *Node {
|
||||
}
|
||||
|
||||
// AllFlags returns flags from all ancestor branches encountered.
|
||||
func (n *Node) AllFlags() (out [][]*Flag) {
|
||||
//
|
||||
// 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()...)
|
||||
out = append(out, n.Parent.AllFlags(hide)...)
|
||||
}
|
||||
if len(n.Flags) > 0 {
|
||||
out = append(out, n.Flags)
|
||||
group := []*Flag{}
|
||||
for _, flag := range n.Flags {
|
||||
if !hide || !flag.Hidden {
|
||||
group = append(group, flag)
|
||||
}
|
||||
}
|
||||
if len(group) > 0 {
|
||||
out = append(out, group)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Leaves returns the leaf commands/arguments under Node.
|
||||
func (n *Node) Leaves() (out []*Node) {
|
||||
//
|
||||
// If "hidden" is true hidden leaves will be omitted.
|
||||
func (n *Node) Leaves(hide bool) (out []*Node) {
|
||||
var walk func(n *Node)
|
||||
walk = func(n *Node) {
|
||||
if hide && n.Hidden {
|
||||
return
|
||||
}
|
||||
if len(n.Children) == 0 && n.Type != ApplicationNode {
|
||||
out = append(out, n)
|
||||
}
|
||||
@@ -112,10 +127,10 @@ func (n *Node) Depth() int {
|
||||
return depth
|
||||
}
|
||||
|
||||
// Summary help string for the node.
|
||||
// Summary help string for the node (not including application name).
|
||||
func (n *Node) Summary() string {
|
||||
summary := n.Path()
|
||||
if flags := n.FlagSummary(); flags != "" {
|
||||
if flags := n.FlagSummary(true); flags != "" {
|
||||
summary += " " + flags
|
||||
}
|
||||
args := []string{}
|
||||
@@ -131,10 +146,10 @@ func (n *Node) Summary() string {
|
||||
}
|
||||
|
||||
// FlagSummary for the node.
|
||||
func (n *Node) FlagSummary() string {
|
||||
func (n *Node) FlagSummary(hide bool) string {
|
||||
required := []string{}
|
||||
count := 0
|
||||
for _, group := range n.AllFlags() {
|
||||
for _, group := range n.AllFlags(hide) {
|
||||
for _, flag := range group {
|
||||
count++
|
||||
if flag.Required {
|
||||
@@ -145,13 +160,22 @@ func (n *Node) FlagSummary() string {
|
||||
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())
|
||||
}
|
||||
|
||||
// Path through ancestors to this Node.
|
||||
func (n *Node) Path() (out string) {
|
||||
if n.Parent != nil {
|
||||
out += " " + n.Parent.Path()
|
||||
}
|
||||
switch n.Type {
|
||||
case ApplicationNode, CommandNode:
|
||||
case CommandNode:
|
||||
out += " " + n.Name
|
||||
case ArgumentNode:
|
||||
out += " " + "<" + n.Name + ">"
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ func TestModelApplicationCommands(t *testing.T) {
|
||||
}
|
||||
p := mustNew(t, &cli)
|
||||
actual := []string{}
|
||||
for _, cmd := range p.Model.Leaves() {
|
||||
for _, cmd := range p.Model.Leaves(false) {
|
||||
actual = append(actual, cmd.Path())
|
||||
}
|
||||
require.Equal(t, []string{"one two", "one three <four>"}, actual)
|
||||
|
||||
+2
-2
@@ -121,8 +121,8 @@ func Help(help HelpPrinter) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// HelpOptions sets the HelpPrinterOptions to use for printing help.
|
||||
func HelpOptions(options HelpPrinterOptions) Option {
|
||||
// ConfigureHelp sets the HelpOptions to use for printing help.
|
||||
func ConfigureHelp(options HelpOptions) Option {
|
||||
return func(k *Kong) error {
|
||||
k.helpOptions = options
|
||||
return nil
|
||||
|
||||
@@ -14,6 +14,7 @@ type Tag struct {
|
||||
Arg bool
|
||||
Required bool
|
||||
Optional bool
|
||||
Name string
|
||||
Help string
|
||||
Type string
|
||||
Default string
|
||||
@@ -125,6 +126,11 @@ func parseTag(fv reflect.Value, ft reflect.StructField) *Tag {
|
||||
t.Required = required
|
||||
t.Optional = optional
|
||||
t.Default = t.Get("default")
|
||||
// Arguments with defaults are always optional.
|
||||
if t.Arg && t.Default != "" {
|
||||
t.Optional = true
|
||||
}
|
||||
t.Name = t.Get("name")
|
||||
t.Help = t.Get("help")
|
||||
t.Type = t.Get("type")
|
||||
t.Env = t.Get("env")
|
||||
|
||||
Reference in New Issue
Block a user