Implement a robust Context.Run().
This helps when composing large applications from separate command structs.
This commit is contained in:
@@ -7,6 +7,9 @@
|
||||
|
||||
1. [Introduction](#introduction)
|
||||
1. [Help](#help)
|
||||
1. [Command handling](#command-handling)
|
||||
1. [Switch on the command string](#switch-on-the-command-string)
|
||||
1. [Attach a `Run(...) error` method to each command](#attach-a-run-error-method-to-each-command)
|
||||
1. [Flags](#flags)
|
||||
1. [Commands and sub-commands](#commands-and-sub-commands)
|
||||
1. [Branching positional arguments](#branching-positional-arguments)
|
||||
@@ -49,16 +52,22 @@ var CLI struct {
|
||||
Force bool `help:"Force removal."`
|
||||
Recursive bool `help:"Recursively remove files."`
|
||||
|
||||
Paths []string `arg help:"Paths to remove." type:"path"`
|
||||
Paths []string `arg name:"path" help:"Paths to remove." type:"path"`
|
||||
} `cmd help:"Remove files."`
|
||||
|
||||
Ls struct {
|
||||
Paths []string `arg optional help:"Paths to list." type:"path"`
|
||||
Paths []string `arg optional name:"path" help:"Paths to list." type:"path"`
|
||||
} `cmd help:"List paths."`
|
||||
}
|
||||
|
||||
func main() {
|
||||
kong.Parse(&CLI)
|
||||
cmd := kong.Parse(&CLI)
|
||||
switch cmd {
|
||||
case "rm <path>":
|
||||
case "ls":
|
||||
default:
|
||||
panic(cmd)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -78,10 +87,10 @@ eg.
|
||||
--debug Debug mode.
|
||||
|
||||
Commands:
|
||||
rm <paths> ...
|
||||
rm <path> ...
|
||||
Remove files.
|
||||
|
||||
ls [<paths> ...]
|
||||
ls [<path> ...]
|
||||
List paths.
|
||||
|
||||
If a command is provided, the help will show full detail on the command including all available flags.
|
||||
@@ -102,6 +111,69 @@ eg.
|
||||
-f, --force Force removal.
|
||||
-r, --recursive Recursively remove files.
|
||||
|
||||
## Command handling
|
||||
|
||||
There are two ways to handle commands in Kong.
|
||||
|
||||
### Switch on the command string
|
||||
|
||||
When you call `kong.Parse()` it will return a unique string representation of the command. Each command branch in the hierarchy will be a bare word and each branching argument or required positional argument will be the name surrounded by angle brackets.
|
||||
|
||||
This has the advantage that it is convenient, but the downside that if you modify your CLI structure, the strings may change. This can be fragile.
|
||||
|
||||
### Attach a `Run(...) error` method to each command
|
||||
|
||||
A more robust approach is to break each command out into their own structs:
|
||||
|
||||
1. Attach a `Run(...) error` method to all leaf commands (`Run()` signatures must match).
|
||||
2. Call `kong.Kong.Parse()` to obtain a `kong.Context`.
|
||||
3. Call `kong.Context.Run(params...)` to call the selected parsed command.
|
||||
|
||||
eg.
|
||||
|
||||
```go
|
||||
type RmCmd struct {
|
||||
Force bool `help:"Force removal."`
|
||||
Recursive bool `help:"Recursively remove files."`
|
||||
|
||||
Paths []string `arg name:"path" help:"Paths to remove." type:"path"`
|
||||
}
|
||||
|
||||
func (r *RmCmd) Run(debug bool) error {
|
||||
fmt.Println("rm", r.Paths)
|
||||
return nil
|
||||
}
|
||||
|
||||
type LsCmd struct {
|
||||
Paths []string `arg optional name:"path" help:"Paths to list." type:"path"`
|
||||
}
|
||||
|
||||
func (l *LsCmd) Run(debug bool) error {
|
||||
fmt.Println("ls", l.Paths)
|
||||
return nil
|
||||
}
|
||||
|
||||
var cli struct {
|
||||
Debug bool `help:"Enable debug mode."`
|
||||
|
||||
Rm RmCmd `cmd help:"Remove files."`
|
||||
Ls LsCmd `cmd help:"List paths."`
|
||||
}
|
||||
|
||||
func main() {
|
||||
parser := kong.Must(&cli)
|
||||
|
||||
// Parse and apply the command-line.
|
||||
ctx, err := parser.Parse(os.Args[1:])
|
||||
parser.FatalIfErrorf(err)
|
||||
|
||||
// Call the Run() method of the selected parsed command.
|
||||
err = ctx.Run(cli.Debug)
|
||||
parser.FatalIfErrorf(err)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Flags
|
||||
|
||||
Any [mapped](#mapper---customising-how-the-command-line-is-mapped-to-go-values) field in the command structure *not* tagged with `cmd` or `arg` will be a flag. Flags are optional by default.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Large-scale composed CLI
|
||||
|
||||
This directory illustrates how a large-scale CLI app could be structured.
|
||||
@@ -0,0 +1,339 @@
|
||||
// nolint
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type AttachCmd struct {
|
||||
DetachKeys string `help:"Override the key sequence for detaching a container"`
|
||||
NoStdin bool `help:"Do not attach STDIN"`
|
||||
SigProxy bool `help:"Proxy all received signals to the process" default:"true"`
|
||||
|
||||
Container string `arg required help:"Container ID to attach to."`
|
||||
}
|
||||
|
||||
func (a *AttachCmd) Run(globals *Globals) error {
|
||||
fmt.Printf("Config: %s\n", globals.Config)
|
||||
fmt.Printf("Attaching to: %v\n", a.Container)
|
||||
fmt.Printf("SigProxy: %v\n", a.SigProxy)
|
||||
return nil
|
||||
}
|
||||
|
||||
type BuildCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *BuildCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type CommitCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *CommitCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type CpCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *CpCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type CreateCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *CreateCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type DeployCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *DeployCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type DiffCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *DiffCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type EventsCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *EventsCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type ExecCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *ExecCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type ExportCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *ExportCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type HistoryCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *HistoryCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type ImagesCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *ImagesCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type ImportCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *ImportCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type InfoCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *InfoCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type InspectCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *InspectCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type KillCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *KillCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type LoadCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *LoadCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type LoginCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *LoginCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type LogoutCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *LogoutCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type LogsCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *LogsCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type PauseCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *PauseCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type PortCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *PortCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type PsCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *PsCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type PullCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *PullCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type PushCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *PushCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type RenameCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *RenameCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type RestartCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *RestartCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type RmCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *RmCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type RmiCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *RmiCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type RunCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *RunCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type SaveCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *SaveCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type SearchCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *SearchCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type StartCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *StartCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type StatsCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *StatsCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type StopCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *StopCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type TagCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *TagCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type TopCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *TopCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type UnpauseCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *UnpauseCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type UpdateCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *UpdateCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type VersionCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *VersionCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type WaitCmd struct {
|
||||
Arg string `arg required`
|
||||
}
|
||||
|
||||
func (cmd *WaitCmd) Run(globals *Globals) error {
|
||||
return nil
|
||||
}
|
||||
+64
-157
@@ -2,177 +2,84 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"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."`
|
||||
type Globals struct {
|
||||
Config string `help:"Location of client config files" default:"~/.docker" type:"path"`
|
||||
Debug bool `short:"D" help:"Enable debug mode"`
|
||||
Host []string `short:"H" help:"Daemon socket(s) to connect to"`
|
||||
LogLevel string `short:"l" help:"Set the logging level (debug|info|warn|error|fatal)" default:"info"`
|
||||
TLS bool `help:"Use TLS; implied by --tls-verify"`
|
||||
TLSCACert string `name:"tls-ca-cert" help:"Trust certs signed only by this CA" default:"~/.docker/ca.pem" type:"path"`
|
||||
TLSCert string `help:"Path to TLS certificate file" default:"~/.docker/cert.pem" type:"path"`
|
||||
TLSKey string `help:"Path to TLS key file" default:"~/.docker/key.pem" type:"path"`
|
||||
TLSVerify bool `help:"Use TLS and verify the remote"`
|
||||
}
|
||||
|
||||
func (a *AttachCmd) Run() error {
|
||||
fmt.Printf("Attaching to: %v\n", a.Container)
|
||||
fmt.Printf("SigProxy: %v\n", a.SigProxy)
|
||||
return nil
|
||||
}
|
||||
type CLI struct {
|
||||
Globals
|
||||
VersionFlag bool `name:"version" help:"Print version information and quit"`
|
||||
|
||||
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"`
|
||||
Attach AttachCmd `cmd help:"Attach local standard input, output, and error streams to a running container"`
|
||||
Build BuildCmd `cmd help:"Build an image from a Dockerfile"`
|
||||
Commit CommitCmd `cmd help:"Create a new image from a container's changes"`
|
||||
Cp CpCmd `cmd help:"Copy files/folders between a container and the local filesystem"`
|
||||
Create CreateCmd `cmd help:"Create a new container"`
|
||||
Deploy DeployCmd `cmd help:"Deploy a new stack or update an existing stack"`
|
||||
Diff DiffCmd `cmd help:"Inspect changes to files or directories on a container's filesystem"`
|
||||
Events EventsCmd `cmd help:"Get real time events from the server"`
|
||||
Exec ExecCmd `cmd help:"Run a command in a running container"`
|
||||
Export ExportCmd `cmd help:"Export a container's filesystem as a tar archive"`
|
||||
History HistoryCmd `cmd help:"Show the history of an image"`
|
||||
Images ImagesCmd `cmd help:"List images"`
|
||||
Import ImportCmd `cmd help:"Import the contents from a tarball to create a filesystem image"`
|
||||
Info InfoCmd `cmd help:"Display system-wide information"`
|
||||
Inspect InspectCmd `cmd help:"Return low-level information on Docker objects"`
|
||||
Kill KillCmd `cmd help:"Kill one or more running containers"`
|
||||
Load LoadCmd `cmd help:"Load an image from a tar archive or STDIN"`
|
||||
Login LoginCmd `cmd help:"Log in to a Docker registry"`
|
||||
Logout LogoutCmd `cmd help:"Log out from a Docker registry"`
|
||||
Logs LogsCmd `cmd help:"Fetch the logs of a container"`
|
||||
Pause PauseCmd `cmd help:"Pause all processes within one or more containers"`
|
||||
Port PortCmd `cmd help:"List port mappings or a specific mapping for the container"`
|
||||
Ps PsCmd `cmd help:"List containers"`
|
||||
Pull PullCmd `cmd help:"Pull an image or a repository from a registry"`
|
||||
Push PushCmd `cmd help:"Push an image or a repository to a registry"`
|
||||
Rename RenameCmd `cmd help:"Rename a container"`
|
||||
Restart RestartCmd `cmd help:"Restart one or more containers"`
|
||||
Rm RmCmd `cmd help:"Remove one or more containers"`
|
||||
Rmi RmiCmd `cmd help:"Remove one or more images"`
|
||||
Run RunCmd `cmd help:"Run a command in a new container"`
|
||||
Save SaveCmd `cmd help:"Save one or more images to a tar archive (streamed to STDOUT by default)"`
|
||||
Search SearchCmd `cmd help:"Search the Docker Hub for images"`
|
||||
Start StartCmd `cmd help:"Start one or more stopped containers"`
|
||||
Stats StatsCmd `cmd help:"Display a live stream of container(s) resource usage statistics"`
|
||||
Stop StopCmd `cmd help:"Stop one or more running containers"`
|
||||
Tag TagCmd `cmd help:"Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE"`
|
||||
Top TopCmd `cmd help:"Display the running processes of a container"`
|
||||
Unpause UnpauseCmd `cmd help:"Unpause all processes within one or more containers"`
|
||||
Update UpdateCmd `cmd help:"Update configuration of one or more containers"`
|
||||
Version VersionCmd `cmd help:"Show the Docker version information"`
|
||||
Wait WaitCmd `cmd help:"Block until one or more containers stop, then print their exit codes"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
cmd := kong.Parse(&cli,
|
||||
cli := CLI{}
|
||||
parser := kong.Must(&cli,
|
||||
kong.Name("docker"),
|
||||
kong.Description("A self-sufficient runtime for containers"),
|
||||
kong.UsageOnError(),
|
||||
kong.HelpOptions(kong.HelpPrinterOptions{
|
||||
Compact: true,
|
||||
}),
|
||||
//
|
||||
kong.Hook(&cli.VersionFlag, func(ctx *kong.Context, path *kong.Path) error {
|
||||
ctx.App.Printf("1.0.0").Exit(0)
|
||||
return nil
|
||||
}))
|
||||
var err error
|
||||
switch cmd {
|
||||
case "attach <container>":
|
||||
fmt.Println(cli.Config)
|
||||
err = cli.Attach.Run()
|
||||
|
||||
default:
|
||||
panic("unsupported command " + cmd)
|
||||
}
|
||||
kong.FatalIfErrorf(err)
|
||||
err := parser.Run(os.Args[1:], "ofo")
|
||||
parser.FatalIfErrorf(err)
|
||||
}
|
||||
|
||||
+110
-25
@@ -42,15 +42,37 @@ func (p *Path) Node() *Node {
|
||||
|
||||
// Context contains the current parse context.
|
||||
type Context struct {
|
||||
App *Kong
|
||||
Path []*Path // A trace through parsed nodes.
|
||||
Args []string // Original command-line arguments.
|
||||
Error error // Error that occurred during trace, if any.
|
||||
App *Kong
|
||||
// A trace through parsed nodes.
|
||||
Path []*Path
|
||||
// Original command-line arguments.
|
||||
Args []string
|
||||
// Error that occurred during trace, if any.
|
||||
Error error
|
||||
|
||||
values map[*Value]reflect.Value // Temporary values during tracing.
|
||||
scan *Scanner
|
||||
}
|
||||
|
||||
// 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,
|
||||
Args: args,
|
||||
Path: []*Path{
|
||||
{App: k.Model, Flags: k.Model.Flags},
|
||||
},
|
||||
values: map[*Value]reflect.Value{},
|
||||
scan: Scan(args...),
|
||||
}
|
||||
c.Error = c.trace(&c.App.Model.Node)
|
||||
return c, c.traceResolvers()
|
||||
}
|
||||
|
||||
// Value returns the value for a particular path element.
|
||||
func (c *Context) Value(path *Path) reflect.Value {
|
||||
switch {
|
||||
@@ -78,25 +100,6 @@ func (c *Context) Selected() *Node {
|
||||
return selected
|
||||
}
|
||||
|
||||
// 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,
|
||||
Args: args,
|
||||
Path: []*Path{
|
||||
{App: k.Model, Flags: k.Model.Flags},
|
||||
},
|
||||
values: map[*Value]reflect.Value{},
|
||||
scan: Scan(args...),
|
||||
}
|
||||
c.Error = c.trace(&c.App.Model.Node)
|
||||
return c, c.traceResolvers()
|
||||
}
|
||||
|
||||
// Empty returns true if there were no arguments provided.
|
||||
func (c *Context) Empty() bool {
|
||||
for _, path := range c.Path {
|
||||
@@ -153,7 +156,8 @@ func (c *Context) Flags() (flags []*Flag) {
|
||||
}
|
||||
|
||||
// Command returns the full command path.
|
||||
func (c *Context) Command() (command []string) {
|
||||
func (c *Context) Command() string {
|
||||
command := []string{}
|
||||
for _, trace := range c.Path {
|
||||
switch {
|
||||
case trace.Positional != nil:
|
||||
@@ -166,7 +170,7 @@ func (c *Context) Command() (command []string) {
|
||||
command = append(command, trace.Command.Name)
|
||||
}
|
||||
}
|
||||
return
|
||||
return strings.Join(command, " ")
|
||||
}
|
||||
|
||||
// FlagValue returns the set value of a flag, if it was encountered and exists.
|
||||
@@ -432,6 +436,87 @@ func (c *Context) parseFlag(flags []*Flag, match string) (err error) {
|
||||
return fmt.Errorf("unknown flag %s", match)
|
||||
}
|
||||
|
||||
// Run executes the corresponding Run(params...) method on the target command selected by the parsed args.
|
||||
//
|
||||
// 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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if expectedRunSignature.NumIn() != len(params) {
|
||||
return fmt.Errorf("expected %d params but received %d; does not match target Run() signature of %s",
|
||||
expectedRunSignature.NumIn(), len(params), expectedRunSignature)
|
||||
}
|
||||
for i, param := range params {
|
||||
if reflect.TypeOf(param) != expectedRunSignature.In(i) {
|
||||
return fmt.Errorf("param %d is of type %s but should be of type %s to match target Run() signature of %s",
|
||||
i, reflect.TypeOf(param), expectedRunSignature.In(i), expectedRunSignature)
|
||||
}
|
||||
}
|
||||
node := c.Selected()
|
||||
if node == nil {
|
||||
return fmt.Errorf("no command selected")
|
||||
}
|
||||
method, err := getRunMethod(node.Target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = c.Apply()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reflectedParams := []reflect.Value{}
|
||||
for _, param := range params {
|
||||
reflectedParams = append(reflectedParams, reflect.ValueOf(param))
|
||||
}
|
||||
result := method.Call(reflectedParams)
|
||||
if result[0].IsNil() {
|
||||
return nil
|
||||
}
|
||||
return result[0].Interface().(error)
|
||||
}
|
||||
|
||||
// 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() {
|
||||
method, err := getRunMethod(node.Target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if signature == nil {
|
||||
signature = method.Type()
|
||||
} else if signature != method.Type() {
|
||||
return nil, fmt.Errorf("Run() methods are not consistent on %s, expected %s but got %s", node.Target.Type(), signature, method.Type())
|
||||
}
|
||||
if signature.NumOut() != 1 || signature.Out(0) != expectedRunReturnSignature {
|
||||
return nil, fmt.Errorf("Run() method on %s should return (error)", node.Target.Type())
|
||||
}
|
||||
}
|
||||
for _, child := range node.Children {
|
||||
if childSignature, err := c.validateRun(child, signature); err != nil {
|
||||
return nil, err
|
||||
} else if signature == nil {
|
||||
signature = childSignature
|
||||
}
|
||||
}
|
||||
return signature, nil
|
||||
}
|
||||
|
||||
func getRunMethod(value reflect.Value) (reflect.Value, error) {
|
||||
method := value.MethodByName("Run")
|
||||
if !method.IsValid() {
|
||||
if value.CanAddr() {
|
||||
method = value.Addr().MethodByName("Run")
|
||||
}
|
||||
if !method.IsValid() {
|
||||
return method, fmt.Errorf("no Run() method on %s", value.Type())
|
||||
}
|
||||
}
|
||||
return method, nil
|
||||
}
|
||||
|
||||
func checkMissingFlags(flags []*Flag) error {
|
||||
missing := []string{}
|
||||
for _, flag := range flags {
|
||||
|
||||
@@ -14,9 +14,9 @@ func Parse(cli interface{}, options ...Option) string {
|
||||
panic(err)
|
||||
}
|
||||
App = parser
|
||||
cmd, err := parser.Parse(os.Args[1:])
|
||||
ctx, err := parser.Parse(os.Args[1:])
|
||||
parser.FatalIfErrorf(err)
|
||||
return cmd
|
||||
return ctx.Command()
|
||||
}
|
||||
|
||||
// FatalIfErrorf terminates with an error message if err != nil.
|
||||
|
||||
+7
-5
@@ -1,10 +1,12 @@
|
||||
package kong
|
||||
package kong_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/alecthomas/kong"
|
||||
)
|
||||
|
||||
func TestHelp(t *testing.T) {
|
||||
@@ -37,10 +39,10 @@ func TestHelp(t *testing.T) {
|
||||
w := bytes.NewBuffer(nil)
|
||||
exited := false
|
||||
app := mustNew(t, &cli,
|
||||
Name("test-app"),
|
||||
Description("A test app."),
|
||||
Writers(w, w),
|
||||
Exit(func(int) {
|
||||
kong.Name("test-app"),
|
||||
kong.Description("A test app."),
|
||||
kong.Writers(w, w),
|
||||
kong.Exit(func(int) {
|
||||
exited = true
|
||||
panic(true) // Panic to fake "exit".
|
||||
}),
|
||||
|
||||
@@ -9,6 +9,10 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
expectedRunReturnSignature = reflect.TypeOf((*error)(nil)).Elem()
|
||||
)
|
||||
|
||||
// Error reported by Kong.
|
||||
type Error struct{ msg string }
|
||||
|
||||
@@ -121,47 +125,32 @@ func (k *Kong) extraFlags() []*Flag {
|
||||
return []*Flag{helpFlag}
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (k *Kong) Help(err error) error {
|
||||
var ctx *Context
|
||||
if perr, ok := err.(*ParseError); ok {
|
||||
ctx = perr.Context
|
||||
} else {
|
||||
ctx, err = Trace(k, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return k.help(k.helpOptions, ctx)
|
||||
}
|
||||
|
||||
// Parse arguments into target.
|
||||
//
|
||||
// The returned "command" is a space separated path to the final selected command, if any. Commands appear as
|
||||
// the command name while positional arguments are the argument name surrounded by "<argument>".
|
||||
// The return Context can be used to further inspect the parsed command-line, to format help, to find the
|
||||
// selected command, to run command Run() methods, and so on. See Context and README for more information.
|
||||
//
|
||||
// Will return a ParseError if a *semantically* invalid command-line is encountered (as opposed to a syntactically
|
||||
// invalid one, which will report a normal error).
|
||||
func (k *Kong) Parse(args []string) (command string, err error) {
|
||||
func (k *Kong) Parse(args []string) (ctx *Context, err error) {
|
||||
defer catch(&err)
|
||||
ctx, err := Trace(k, args)
|
||||
ctx, err = Trace(k, args)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
if err = k.applyHooks(ctx); err != nil {
|
||||
return "", &ParseError{error: err, Context: ctx}
|
||||
return nil, &ParseError{error: err, Context: ctx}
|
||||
}
|
||||
if ctx.Error != nil {
|
||||
return "", &ParseError{error: ctx.Error, Context: ctx}
|
||||
return nil, &ParseError{error: ctx.Error, Context: ctx}
|
||||
}
|
||||
if err = ctx.Validate(); err != nil {
|
||||
return "", &ParseError{error: err, Context: ctx}
|
||||
return nil, &ParseError{error: err, Context: ctx}
|
||||
}
|
||||
return ctx.Apply()
|
||||
if _, err = ctx.Apply(); err != nil {
|
||||
return nil, &ParseError{error: err, Context: ctx}
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func (k *Kong) applyHooks(ctx *Context) error {
|
||||
|
||||
+62
-26
@@ -1,23 +1,26 @@
|
||||
package kong
|
||||
package kong_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/alecthomas/kong"
|
||||
)
|
||||
|
||||
func mustNew(t *testing.T, cli interface{}, options ...Option) *Kong {
|
||||
func mustNew(t *testing.T, cli interface{}, options ...kong.Option) *kong.Kong {
|
||||
t.Helper()
|
||||
options = append([]Option{
|
||||
Name("test"),
|
||||
Exit(func(int) {
|
||||
options = append([]kong.Option{
|
||||
kong.Name("test"),
|
||||
kong.Exit(func(int) {
|
||||
t.Helper()
|
||||
t.Fatalf("unexpected exit()")
|
||||
}),
|
||||
}, options...)
|
||||
parser, err := New(cli, options...)
|
||||
parser, err := kong.New(cli, options...)
|
||||
require.NoError(t, err)
|
||||
return parser
|
||||
}
|
||||
@@ -33,9 +36,9 @@ func TestPositionalArguments(t *testing.T) {
|
||||
} `kong:"cmd"`
|
||||
}
|
||||
p := mustNew(t, &cli)
|
||||
cmd, err := p.Parse([]string{"user", "create", "10", "Alec", "Thomas"})
|
||||
ctx, err := p.Parse([]string{"user", "create", "10", "Alec", "Thomas"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "user create <id> <first> <last>", cmd)
|
||||
require.Equal(t, "user create <id> <first> <last>", ctx.Command())
|
||||
t.Run("Missing", func(t *testing.T) {
|
||||
_, err := p.Parse([]string{"user", "create", "10"})
|
||||
require.Error(t, err)
|
||||
@@ -69,10 +72,10 @@ func TestBranchingArgument(t *testing.T) {
|
||||
} `kong:"cmd,help='User management.'"`
|
||||
}
|
||||
p := mustNew(t, &cli)
|
||||
cmd, err := p.Parse([]string{"user", "10", "delete"})
|
||||
ctx, err := p.Parse([]string{"user", "10", "delete"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 10, cli.User.ID.ID)
|
||||
require.Equal(t, "user <id> delete", cmd)
|
||||
require.Equal(t, "user <id> delete", ctx.Command())
|
||||
t.Run("Missing", func(t *testing.T) {
|
||||
_, err = p.Parse([]string{"user"})
|
||||
require.Error(t, err)
|
||||
@@ -143,7 +146,7 @@ func TestUnsupportedFieldErrors(t *testing.T) {
|
||||
var cli struct {
|
||||
Keys struct{}
|
||||
}
|
||||
_, err := New(&cli)
|
||||
_, err := kong.New(&cli)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
@@ -154,7 +157,7 @@ func TestMatchingArgField(t *testing.T) {
|
||||
} `kong:"arg"`
|
||||
}
|
||||
|
||||
_, err := New(&cli)
|
||||
_, err := kong.New(&cli)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
@@ -164,7 +167,7 @@ func TestCantMixPositionalAndBranches(t *testing.T) {
|
||||
Command struct {
|
||||
} `kong:"cmd"`
|
||||
}
|
||||
_, err := New(&cli)
|
||||
_, err := kong.New(&cli)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
@@ -220,7 +223,7 @@ func TestInvalidRequiredAfterOptional(t *testing.T) {
|
||||
Name string `kong:"arg"`
|
||||
}
|
||||
|
||||
_, err := New(&cli)
|
||||
_, err := kong.New(&cli)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
@@ -288,7 +291,7 @@ func TestCommandMissingTagIsInvalid(t *testing.T) {
|
||||
var cli struct {
|
||||
One struct{}
|
||||
}
|
||||
_, err := New(&cli)
|
||||
_, err := kong.New(&cli)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
@@ -299,7 +302,7 @@ func TestDuplicateFlag(t *testing.T) {
|
||||
Flag bool
|
||||
} `kong:"cmd"`
|
||||
}
|
||||
_, err := New(&cli)
|
||||
_, err := kong.New(&cli)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
@@ -312,7 +315,7 @@ func TestDuplicateFlagOnPeerCommandIsOkay(t *testing.T) {
|
||||
Flag bool
|
||||
} `kong:"cmd"`
|
||||
}
|
||||
_, err := New(&cli)
|
||||
_, err := kong.New(&cli)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -324,10 +327,10 @@ func TestTraceErrorPartiallySucceeds(t *testing.T) {
|
||||
} `kong:"cmd"`
|
||||
}
|
||||
p := mustNew(t, &cli)
|
||||
ctx, err := Trace(p, []string{"one", "bad"})
|
||||
ctx, err := kong.Trace(p, []string{"one", "bad"})
|
||||
require.NoError(t, err)
|
||||
require.Error(t, ctx.Error)
|
||||
require.Equal(t, []string{"one"}, ctx.Command())
|
||||
require.Equal(t, "one", ctx.Command())
|
||||
}
|
||||
|
||||
func TestHooks(t *testing.T) {
|
||||
@@ -353,13 +356,13 @@ func TestHooks(t *testing.T) {
|
||||
{"Flag", "one --three=three", values{true, "", "three"}},
|
||||
{"ArgAndFlag", "one two --three=three", values{true, "two", "three"}},
|
||||
}
|
||||
setOne := func(ctx *Context, path *Path) error { hooked.one = true; return nil }
|
||||
setTwo := func(ctx *Context, path *Path) error { hooked.two = ctx.Value(path).String(); return nil }
|
||||
setThree := func(ctx *Context, path *Path) error { hooked.three = ctx.Value(path).String(); return nil }
|
||||
setOne := func(ctx *kong.Context, path *kong.Path) error { hooked.one = true; return nil }
|
||||
setTwo := func(ctx *kong.Context, path *kong.Path) error { hooked.two = ctx.Value(path).String(); return nil }
|
||||
setThree := func(ctx *kong.Context, path *kong.Path) error { hooked.three = ctx.Value(path).String(); return nil }
|
||||
p := mustNew(t, &cli,
|
||||
Hook(&cli.One, setOne),
|
||||
Hook(&cli.One.Two, setTwo),
|
||||
Hook(&cli.One.Three, setThree))
|
||||
kong.Hook(&cli.One, setOne),
|
||||
kong.Hook(&cli.One.Two, setTwo),
|
||||
kong.Hook(&cli.One.Three, setThree))
|
||||
|
||||
for _, test := range tests {
|
||||
hooked = values{}
|
||||
@@ -450,7 +453,40 @@ func TestSliceWithDisabledSeparator(t *testing.T) {
|
||||
func TestMultilineMessage(t *testing.T) {
|
||||
w := &bytes.Buffer{}
|
||||
var cli struct{}
|
||||
p := mustNew(t, &cli, Writers(w, w))
|
||||
p := mustNew(t, &cli, kong.Writers(w, w))
|
||||
p.Printf("hello\nworld")
|
||||
require.Equal(t, "test: hello\n world\n", w.String())
|
||||
}
|
||||
|
||||
type cmdWithRun struct {
|
||||
Arg string `arg:""`
|
||||
}
|
||||
|
||||
func (c *cmdWithRun) Run(key string) error {
|
||||
c.Arg += key
|
||||
if key == "ERROR" {
|
||||
return fmt.Errorf("ERROR")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type grammarWithRun struct {
|
||||
One cmdWithRun `cmd:""`
|
||||
Two cmdWithRun `cmd:""`
|
||||
}
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
cli := &grammarWithRun{}
|
||||
p := mustNew(t, cli)
|
||||
|
||||
ctx, err := p.Parse([]string{"one", "two"})
|
||||
require.NoError(t, err)
|
||||
err = ctx.Run("hello")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "twohello", cli.One.Arg)
|
||||
|
||||
ctx, err = p.Parse([]string{"two", "three"})
|
||||
require.NoError(t, err)
|
||||
err = ctx.Run("ERROR")
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
+12
-10
@@ -1,4 +1,4 @@
|
||||
package kong
|
||||
package kong_test
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
@@ -7,13 +7,15 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/alecthomas/kong"
|
||||
)
|
||||
|
||||
func TestValueMapper(t *testing.T) {
|
||||
var cli struct {
|
||||
Flag string
|
||||
}
|
||||
k := mustNew(t, &cli, ValueMapper(&cli.Flag, testMooMapper{}))
|
||||
k := mustNew(t, &cli, kong.ValueMapper(&cli.Flag, testMooMapper{}))
|
||||
_, err := k.Parse(nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "", cli.Flag)
|
||||
@@ -26,7 +28,7 @@ func TestNamedMapper(t *testing.T) {
|
||||
var cli struct {
|
||||
Flag string `type:"moo"`
|
||||
}
|
||||
k := mustNew(t, &cli, NamedMapper("moo", testMooMapper{}))
|
||||
k := mustNew(t, &cli, kong.NamedMapper("moo", testMooMapper{}))
|
||||
_, err := k.Parse(nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "", cli.Flag)
|
||||
@@ -39,7 +41,7 @@ type testMooMapper struct {
|
||||
text string
|
||||
}
|
||||
|
||||
func (t testMooMapper) Decode(ctx *DecodeContext, target reflect.Value) error {
|
||||
func (t testMooMapper) Decode(ctx *kong.DecodeContext, target reflect.Value) error {
|
||||
if t.text == "" {
|
||||
target.SetString("MOO")
|
||||
} else {
|
||||
@@ -73,14 +75,14 @@ func TestDurationMapper(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSplitEscaped(t *testing.T) {
|
||||
require.Equal(t, []string{"a", "b"}, SplitEscaped("a,b", ','))
|
||||
require.Equal(t, []string{"a,b", "c"}, SplitEscaped(`a\,b,c`, ','))
|
||||
require.Equal(t, []string{"a", "b"}, kong.SplitEscaped("a,b", ','))
|
||||
require.Equal(t, []string{"a,b", "c"}, kong.SplitEscaped(`a\,b,c`, ','))
|
||||
}
|
||||
|
||||
func TestJoinEscaped(t *testing.T) {
|
||||
require.Equal(t, `a,b`, JoinEscaped([]string{"a", "b"}, ','))
|
||||
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, `a,b`, kong.JoinEscaped([]string{"a", "b"}, ','))
|
||||
require.Equal(t, `a\,b,c`, kong.JoinEscaped([]string{`a,b`, `c`}, ','))
|
||||
require.Equal(t, kong.JoinEscaped(kong.SplitEscaped(`a\,b,c`, ','), ','), `a\,b,c`)
|
||||
}
|
||||
|
||||
func TestMapWithNamedTypes(t *testing.T) {
|
||||
@@ -88,7 +90,7 @@ func TestMapWithNamedTypes(t *testing.T) {
|
||||
TypedValue map[string]string `type:":moo"`
|
||||
TypedKey map[string]string `type:"upper:"`
|
||||
}
|
||||
k := mustNew(t, &cli, NamedMapper("moo", testMooMapper{}), NamedMapper("upper", testUppercaseMapper{}))
|
||||
k := mustNew(t, &cli, kong.NamedMapper("moo", testMooMapper{}), kong.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)
|
||||
|
||||
@@ -43,6 +43,11 @@ type Node struct {
|
||||
Argument *Value // Populated when Type is ArgumentNode.
|
||||
}
|
||||
|
||||
// Leaf returns true if this Node is a leaf node.
|
||||
func (n *Node) Leaf() bool {
|
||||
return len(n.Children) == 0
|
||||
}
|
||||
|
||||
// Find a command/argument/flag by pointer to its field.
|
||||
//
|
||||
// Returns nil if not found. Panics if ptr is not a pointer.
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package kong
|
||||
package kong_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
@@ -92,6 +92,8 @@ func Writers(stdout, stderr io.Writer) Option {
|
||||
}
|
||||
|
||||
// HookFunc is a callback tied to a field of the grammar, called before a value is applied.
|
||||
//
|
||||
// "ctx" is the current parse Context, "path" is the Path entry corresponding to the hooked value.
|
||||
type HookFunc func(ctx *Context, path *Path) error
|
||||
|
||||
// Hook to apply before a command, flag or positional argument is encountered.
|
||||
|
||||
+5
-3
@@ -1,4 +1,4 @@
|
||||
package kong
|
||||
package kong_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -7,11 +7,13 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/alecthomas/kong"
|
||||
)
|
||||
|
||||
func TestOptions(t *testing.T) {
|
||||
var cli struct{}
|
||||
p, err := New(&cli, Name("name"), Description("description"), Writers(nil, nil), Exit(nil))
|
||||
p, err := kong.New(&cli, kong.Name("name"), kong.Description("description"), kong.Writers(nil, nil), kong.Exit(nil))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "name", p.Model.Name)
|
||||
require.Equal(t, "description", p.Model.Help)
|
||||
@@ -42,7 +44,7 @@ func TestConfigLoading(t *testing.T) {
|
||||
err = json.NewEncoder(second).Encode(&cli)
|
||||
require.NoError(t, err)
|
||||
|
||||
p := mustNew(t, &cli, Configuration(JSON, first.Name(), second.Name()))
|
||||
p := mustNew(t, &cli, kong.Configuration(kong.JSON, first.Name(), second.Name()))
|
||||
_, err = p.Parse(nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "first", cli.Flag)
|
||||
|
||||
+17
-15
@@ -1,4 +1,4 @@
|
||||
package kong
|
||||
package kong_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/alecthomas/kong"
|
||||
)
|
||||
|
||||
type envMap map[string]string
|
||||
@@ -23,7 +25,7 @@ func tempEnv(env envMap) func() {
|
||||
}
|
||||
}
|
||||
|
||||
func newEnvParser(t *testing.T, cli interface{}, env envMap) (*Kong, func()) {
|
||||
func newEnvParser(t *testing.T, cli interface{}, env envMap) (*kong.Kong, func()) {
|
||||
t.Helper()
|
||||
restoreEnv := tempEnv(env)
|
||||
parser := mustNew(t, cli)
|
||||
@@ -111,10 +113,10 @@ func TestJSONBasic(t *testing.T) {
|
||||
"slice_with_commas": ["a,b", "c"]
|
||||
}`
|
||||
|
||||
r, err := JSON(strings.NewReader(json))
|
||||
r, err := kong.JSON(strings.NewReader(json))
|
||||
require.NoError(t, err)
|
||||
|
||||
parser := mustNew(t, &cli, Resolver(r))
|
||||
parser := mustNew(t, &cli, kong.Resolver(r))
|
||||
_, err = parser.Parse([]string{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "🍕", cli.String)
|
||||
@@ -127,14 +129,14 @@ func TestResolvedValueTriggersHooks(t *testing.T) {
|
||||
var cli struct {
|
||||
Int int
|
||||
}
|
||||
resolver := func(context *Context, parent *Path, flag *Flag) (string, error) {
|
||||
resolver := func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (string, error) {
|
||||
if flag.Name == "int" {
|
||||
return "1", nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
hooked := 0
|
||||
p := mustNew(t, &cli, Resolver(resolver), Hook(&cli.Int, func(ctx *Context, path *Path) error {
|
||||
p := mustNew(t, &cli, kong.Resolver(resolver), kong.Hook(&cli.Int, func(ctx *kong.Context, path *kong.Path) error {
|
||||
hooked++
|
||||
return nil
|
||||
}))
|
||||
@@ -152,7 +154,7 @@ func TestResolvedValueTriggersHooks(t *testing.T) {
|
||||
|
||||
type testUppercaseMapper struct{}
|
||||
|
||||
func (testUppercaseMapper) Decode(ctx *DecodeContext, target reflect.Value) error {
|
||||
func (testUppercaseMapper) Decode(ctx *kong.DecodeContext, target reflect.Value) error {
|
||||
value := ctx.Scan.PopValue("lowercase")
|
||||
target.SetString(strings.ToUpper(value))
|
||||
return nil
|
||||
@@ -167,7 +169,7 @@ func TestResolversWithMappers(t *testing.T) {
|
||||
defer restoreEnv()
|
||||
|
||||
parser := mustNew(t, &cli,
|
||||
NamedMapper("upper", testUppercaseMapper{}),
|
||||
kong.NamedMapper("upper", testUppercaseMapper{}),
|
||||
)
|
||||
_, err := parser.Parse([]string{})
|
||||
require.NoError(t, err)
|
||||
@@ -179,14 +181,14 @@ func TestResolverWithBool(t *testing.T) {
|
||||
Bool bool
|
||||
}
|
||||
|
||||
resolver := func(context *Context, parent *Path, flag *Flag) (string, error) {
|
||||
resolver := func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (string, error) {
|
||||
if flag.Name == "bool" {
|
||||
return "true", nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
p := mustNew(t, &cli, Resolver(resolver))
|
||||
p := mustNew(t, &cli, kong.Resolver(resolver))
|
||||
|
||||
_, err := p.Parse(nil)
|
||||
require.NoError(t, err)
|
||||
@@ -198,21 +200,21 @@ func TestLastResolverWins(t *testing.T) {
|
||||
Int []int
|
||||
}
|
||||
|
||||
var first ResolverFunc = func(context *Context, parent *Path, flag *Flag) (string, error) {
|
||||
var first kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (string, error) {
|
||||
if flag.Name == "int" {
|
||||
return "1", nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
var second ResolverFunc = func(context *Context, parent *Path, flag *Flag) (string, error) {
|
||||
var second kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (string, error) {
|
||||
if flag.Name == "int" {
|
||||
return "2", nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
p := mustNew(t, &cli, Resolver(first), Resolver(second))
|
||||
p := mustNew(t, &cli, kong.Resolver(first), kong.Resolver(second))
|
||||
_, err := p.Parse(nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []int{2}, cli.Int)
|
||||
@@ -223,13 +225,13 @@ func TestResolverSatisfiesRequired(t *testing.T) {
|
||||
var cli struct {
|
||||
Int int `required`
|
||||
}
|
||||
resolver := func(context *Context, parent *Path, flag *Flag) (string, error) {
|
||||
resolver := func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (string, error) {
|
||||
if flag.Name == "int" {
|
||||
return "1", nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
_, err := mustNew(t, &cli, Resolver(resolver)).Parse(nil)
|
||||
_, err := mustNew(t, &cli, kong.Resolver(resolver)).Parse(nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, cli.Int)
|
||||
}
|
||||
|
||||
+5
-3
@@ -1,9 +1,11 @@
|
||||
package kong
|
||||
package kong_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/alecthomas/kong"
|
||||
)
|
||||
|
||||
func TestDefaultValueForOptionalArg(t *testing.T) {
|
||||
@@ -42,7 +44,7 @@ func TestBadString(t *testing.T) {
|
||||
var cli struct {
|
||||
Numbers string `kong:"default='yay'n"`
|
||||
}
|
||||
_, err := New(&cli)
|
||||
_, err := kong.New(&cli)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
@@ -50,7 +52,7 @@ func TestNoQuoteEnd(t *testing.T) {
|
||||
var cli struct {
|
||||
Numbers string `kong:"default='yay"`
|
||||
}
|
||||
_, err := New(&cli)
|
||||
_, err := kong.New(&cli)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user