Add hook support.
This commit is contained in:
committed by
Gerald Kaszuba
parent
f60fe01f08
commit
cf89213e1e
@@ -3,25 +3,29 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/alecthomas/kong"
|
"github.com/alecthomas/kong"
|
||||||
)
|
)
|
||||||
|
|
||||||
var CLI struct {
|
var CLI struct {
|
||||||
Rm struct {
|
Help bool `kong:"help='Display help.'"`
|
||||||
|
Rm struct {
|
||||||
Force bool `kong:"help='Force removal.'"`
|
Force bool `kong:"help='Force removal.'"`
|
||||||
Recursive bool `kong:"help='Recursively remove files.'"`
|
Recursive bool `kong:"help='Recursively remove files.'"`
|
||||||
|
|
||||||
Paths []string `kong:"help='Paths to remove.',type='path'"`
|
Paths []string `kong:"arg,help='Paths to remove.',type='path'"`
|
||||||
} `kong:"help='Remove files.'"`
|
} `kong:"cmd,help='Remove files.'"`
|
||||||
|
|
||||||
Ls struct {
|
Ls struct {
|
||||||
Paths []string `kong:"help='Paths to list.',type='path'"`
|
Paths []string `kong:"help='Paths to list.',type='path'"`
|
||||||
} `kong:"help='List paths.'"`
|
} `kong:"cmd,help='List paths.'"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cmd := kong.Parse(&CLI)
|
app := kong.Must(&CLI).Hook(&CLI.Help, kong.Help(nil, nil))
|
||||||
|
cmd, err := app.Parse(os.Args[1:])
|
||||||
|
app.FatalIfErrorf(err)
|
||||||
s, _ := json.Marshal(&CLI)
|
s, _ := json.Marshal(&CLI)
|
||||||
fmt.Println(cmd)
|
fmt.Println(cmd)
|
||||||
fmt.Println(string(s))
|
fmt.Println(string(s))
|
||||||
|
|||||||
@@ -14,23 +14,11 @@ func build(ast interface{}) (app *Application, err error) {
|
|||||||
return nil, fmt.Errorf("expected a pointer to a struct but got %T", ast)
|
return nil, fmt.Errorf("expected a pointer to a struct but got %T", ast)
|
||||||
}
|
}
|
||||||
|
|
||||||
app = &Application{
|
app = &Application{}
|
||||||
// Synthesize a --help flag.
|
node := buildNode(iv, map[string]bool{})
|
||||||
HelpFlag: &Flag{
|
|
||||||
Value: Value{
|
|
||||||
Name: "help",
|
|
||||||
Help: "Show context-sensitive help.",
|
|
||||||
Flag: true,
|
|
||||||
Value: reflect.New(reflect.TypeOf(false)).Elem(),
|
|
||||||
Decoder: kindDecoders[reflect.Bool],
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
node := buildNode(iv, map[string]bool{"help": true})
|
|
||||||
if len(node.Positional) > 0 && len(node.Children) > 0 {
|
if len(node.Positional) > 0 && len(node.Children) > 0 {
|
||||||
return nil, fmt.Errorf("can't mix positional arguments and branching arguments on %T", ast)
|
return nil, fmt.Errorf("can't mix positional arguments and branching arguments on %T", ast)
|
||||||
}
|
}
|
||||||
// Prepend --help flag.
|
|
||||||
node.Flags = append([]*Flag{app.HelpFlag}, node.Flags...)
|
|
||||||
app.Node = *node
|
app.Node = *node
|
||||||
return app, nil
|
return app, nil
|
||||||
}
|
}
|
||||||
@@ -40,7 +28,9 @@ func dashedString(s string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildNode(v reflect.Value, seenFlags map[string]bool) *Node {
|
func buildNode(v reflect.Value, seenFlags map[string]bool) *Node {
|
||||||
node := &Node{}
|
node := &Node{
|
||||||
|
Target: v,
|
||||||
|
}
|
||||||
for i := 0; i < v.NumField(); i++ {
|
for i := 0; i < v.NumField(); i++ {
|
||||||
ft := v.Type().Field(i)
|
ft := v.Type().Field(i)
|
||||||
if strings.ToLower(ft.Name[0:1]) == ft.Name[0:1] {
|
if strings.ToLower(ft.Name[0:1]) == ft.Name[0:1] {
|
||||||
|
|||||||
+21
-38
@@ -2,12 +2,13 @@ package kong
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseTrace records the nodes and parsed values from the current command-line.
|
// Trace records the nodes and parsed values from the current command-line.
|
||||||
type ParseTrace struct {
|
type Trace struct {
|
||||||
// One of these will be non-nil.
|
// One of these will be non-nil.
|
||||||
App *Application
|
App *Application
|
||||||
Positional *Value
|
Positional *Value
|
||||||
@@ -22,9 +23,12 @@ type ParseTrace struct {
|
|||||||
Value reflect.Value
|
Value reflect.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
type ParseContext struct {
|
type Context struct {
|
||||||
Trace []*ParseTrace // A trace through parsed nodes.
|
Trace []*Trace // A trace through parsed nodes.
|
||||||
Error error // Error that occurred during trace, if any.
|
Error error // Error that occurred during trace, if any.
|
||||||
|
|
||||||
|
Stdout io.Writer
|
||||||
|
Stderr io.Writer
|
||||||
|
|
||||||
node *Node // Current node being parsed.
|
node *Node // Current node being parsed.
|
||||||
|
|
||||||
@@ -34,7 +38,7 @@ type ParseContext struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Flags returns the accumulated available flags.
|
// Flags returns the accumulated available flags.
|
||||||
func (p *ParseContext) Flags() (flags []*Flag) {
|
func (p *Context) Flags() (flags []*Flag) {
|
||||||
for _, trace := range p.Trace {
|
for _, trace := range p.Trace {
|
||||||
flags = append(flags, trace.Flags...)
|
flags = append(flags, trace.Flags...)
|
||||||
}
|
}
|
||||||
@@ -42,7 +46,7 @@ func (p *ParseContext) Flags() (flags []*Flag) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Command returns the full command path.
|
// Command returns the full command path.
|
||||||
func (p *ParseContext) Command() (command []string) {
|
func (p *Context) Command() (command []string) {
|
||||||
for _, trace := range p.Trace {
|
for _, trace := range p.Trace {
|
||||||
switch {
|
switch {
|
||||||
case trace.Positional != nil:
|
case trace.Positional != nil:
|
||||||
@@ -56,30 +60,8 @@ func (p *ParseContext) Command() (command []string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trace parses the command-line, validating and collecting matching grammar nodes.
|
|
||||||
func Trace(args []string, app *Application) (*ParseContext, error) {
|
|
||||||
p := &ParseContext{
|
|
||||||
app: app,
|
|
||||||
args: args,
|
|
||||||
}
|
|
||||||
p.Trace = append(p.Trace, &ParseTrace{
|
|
||||||
App: app,
|
|
||||||
Flags: append([]*Flag{}, app.Flags...),
|
|
||||||
})
|
|
||||||
err := p.reset(&p.app.Node)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p.Error = p.trace(&p.app.Node)
|
|
||||||
if err = checkMissingFlags(p.Flags()); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlagValue returns the set value of a flag, if it was encountered and exists.
|
// FlagValue returns the set value of a flag, if it was encountered and exists.
|
||||||
func (p *ParseContext) FlagValue(flag *Flag) reflect.Value {
|
func (p *Context) FlagValue(flag *Flag) reflect.Value {
|
||||||
for _, trace := range p.Trace {
|
for _, trace := range p.Trace {
|
||||||
if trace.Flag == flag {
|
if trace.Flag == flag {
|
||||||
return trace.Value
|
return trace.Value
|
||||||
@@ -89,7 +71,7 @@ func (p *ParseContext) FlagValue(flag *Flag) reflect.Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Recursively reset values to defaults (as specified in the grammar) or the zero value.
|
// Recursively reset values to defaults (as specified in the grammar) or the zero value.
|
||||||
func (p *ParseContext) reset(node *Node) error {
|
func (p *Context) reset(node *Node) error {
|
||||||
p.scan = Scan(p.args...)
|
p.scan = Scan(p.args...)
|
||||||
for _, flag := range node.Flags {
|
for _, flag := range node.Flags {
|
||||||
err := flag.Value.Reset()
|
err := flag.Value.Reset()
|
||||||
@@ -124,7 +106,7 @@ func (p *ParseContext) reset(node *Node) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ParseContext) trace(node *Node) (err error) { // nolint: gocyclo
|
func (p *Context) trace(node *Node) (err error) { // nolint: gocyclo
|
||||||
positional := 0
|
positional := 0
|
||||||
p.node = node
|
p.node = node
|
||||||
flags := append(p.Flags(), node.Flags...)
|
flags := append(p.Flags(), node.Flags...)
|
||||||
@@ -203,7 +185,7 @@ func (p *ParseContext) trace(node *Node) (err error) { // nolint: gocyclo
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
p.Trace = append(p.Trace, &ParseTrace{Positional: arg, Value: value, Flags: node.Flags})
|
p.Trace = append(p.Trace, &Trace{Positional: arg, Value: value, Flags: node.Flags})
|
||||||
positional++
|
positional++
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -214,9 +196,10 @@ func (p *ParseContext) trace(node *Node) (err error) { // nolint: gocyclo
|
|||||||
case branch.Command != nil:
|
case branch.Command != nil:
|
||||||
if branch.Command.Name == token.Value {
|
if branch.Command.Name == token.Value {
|
||||||
p.scan.Pop()
|
p.scan.Pop()
|
||||||
p.Trace = append(p.Trace, &ParseTrace{
|
p.Trace = append(p.Trace, &Trace{
|
||||||
Command: branch.Command,
|
Command: branch.Command,
|
||||||
Flags: node.Flags,
|
Flags: node.Flags,
|
||||||
|
Value: branch.Command.Target,
|
||||||
})
|
})
|
||||||
return p.trace(branch.Command)
|
return p.trace(branch.Command)
|
||||||
}
|
}
|
||||||
@@ -224,7 +207,7 @@ func (p *ParseContext) trace(node *Node) (err error) { // nolint: gocyclo
|
|||||||
case branch.Argument != nil:
|
case branch.Argument != nil:
|
||||||
arg := branch.Argument.Argument
|
arg := branch.Argument.Argument
|
||||||
if value, err := arg.Parse(p.scan); err == nil {
|
if value, err := arg.Parse(p.scan); err == nil {
|
||||||
p.Trace = append(p.Trace, &ParseTrace{
|
p.Trace = append(p.Trace, &Trace{
|
||||||
Argument: branch.Argument,
|
Argument: branch.Argument,
|
||||||
Value: value,
|
Value: value,
|
||||||
Flags: node.Flags,
|
Flags: node.Flags,
|
||||||
@@ -252,7 +235,7 @@ func (p *ParseContext) trace(node *Node) (err error) { // nolint: gocyclo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply traced context to the target grammar.
|
// Apply traced context to the target grammar.
|
||||||
func (p *ParseContext) Apply() (string, error) {
|
func (p *Context) Apply() (string, error) {
|
||||||
path := []string{}
|
path := []string{}
|
||||||
for _, trace := range p.Trace {
|
for _, trace := range p.Trace {
|
||||||
switch {
|
switch {
|
||||||
@@ -324,7 +307,7 @@ func checkMissingPositionals(positional int, values []*Value) error {
|
|||||||
return fmt.Errorf("missing positional arguments %s", strings.Join(missing, " "))
|
return fmt.Errorf("missing positional arguments %s", strings.Join(missing, " "))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ParseContext) matchFlags(flags []*Flag, matcher func(f *Flag) bool) (err error) {
|
func (p *Context) matchFlags(flags []*Flag, matcher func(f *Flag) bool) (err error) {
|
||||||
defer catch(&err)
|
defer catch(&err)
|
||||||
token := p.scan.Peek()
|
token := p.scan.Peek()
|
||||||
for _, flag := range flags {
|
for _, flag := range flags {
|
||||||
@@ -335,7 +318,7 @@ func (p *ParseContext) matchFlags(flags []*Flag, matcher func(f *Flag) bool) (er
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
p.Trace = append(p.Trace, &ParseTrace{Flag: flag, Value: value})
|
p.Trace = append(p.Trace, &Trace{Flag: flag, Value: value})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1 @@
|
|||||||
package kong
|
package kong
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTraceErrorPartiallySucceeds(t *testing.T) {
|
|
||||||
var cli struct {
|
|
||||||
One struct {
|
|
||||||
Two struct {
|
|
||||||
} `kong:"cmd"`
|
|
||||||
} `kong:"cmd"`
|
|
||||||
}
|
|
||||||
p := mustNew(t, &cli)
|
|
||||||
trace, err := Trace([]string{"one", "bad"}, p.Model)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Error(t, trace.Error)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package kong
|
package kong
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"text/template"
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,20 +17,20 @@ usage: {{.Name}}
|
|||||||
|
|
||||||
var defaultHelpTemplate = template.Must(template.New("help").Parse(defaultHelp))
|
var defaultHelpTemplate = template.Must(template.New("help").Parse(defaultHelp))
|
||||||
|
|
||||||
// WriteHelp to w.
|
// Help returns a Hook that will display help and exit.
|
||||||
//
|
func Help(tmpl *template.Template, tmplctx map[string]interface{}) Hook {
|
||||||
// If w is nil, the default stdout writer will be used.
|
return func(app *Kong, ctx *Context, trace *Trace) error {
|
||||||
//
|
merged := map[string]interface{}{
|
||||||
// If args are provided, help will be written in the context o
|
"Application": app.Model,
|
||||||
func (k *Kong) WriteHelp(w io.Writer, args ...interface{}) error {
|
}
|
||||||
if w == nil {
|
for k, v := range tmplctx {
|
||||||
w = k.stdout
|
merged[k] = v
|
||||||
|
}
|
||||||
|
err := tmpl.Execute(app.Stdout, merged)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
app.Exit(0)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
ctx := map[string]interface{}{
|
|
||||||
"Application": k.Model,
|
|
||||||
}
|
|
||||||
for k, v := range k.helpContext {
|
|
||||||
ctx[k] = v
|
|
||||||
}
|
|
||||||
return k.help.Execute(w, ctx)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,12 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"text/template"
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Hook func(app *Kong, ctx *Context, trace *Trace) error
|
||||||
|
|
||||||
// Error reported by Kong.
|
// Error reported by Kong.
|
||||||
type Error struct{ msg string }
|
type Error struct{ msg string }
|
||||||
|
|
||||||
@@ -17,29 +20,39 @@ func fail(format string, args ...interface{}) {
|
|||||||
panic(Error{fmt.Sprintf(format, args...)})
|
panic(Error{fmt.Sprintf(format, args...)})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Must(ast interface{}, options ...Option) *Kong {
|
||||||
|
k, err := New(ast, options...)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
|
||||||
// Kong is the main parser type.
|
// Kong is the main parser type.
|
||||||
type Kong struct {
|
type Kong struct {
|
||||||
Model *Application
|
Model *Application
|
||||||
// Termination function (defaults to os.Exit)
|
// Termination function (defaults to os.Exit)
|
||||||
terminate func(int)
|
Exit func(int)
|
||||||
|
|
||||||
stdout io.Writer
|
Stdout io.Writer
|
||||||
stderr io.Writer
|
Stderr io.Writer
|
||||||
|
|
||||||
help *template.Template
|
help *template.Template
|
||||||
helpContext map[string]interface{}
|
helpContext map[string]interface{}
|
||||||
helpFuncs template.FuncMap
|
helpFuncs template.FuncMap
|
||||||
|
hooks map[reflect.Value]Hook
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Kong parser into ast.
|
// New creates a new Kong parser into ast.
|
||||||
func New(ast interface{}, options ...Option) (*Kong, error) {
|
func New(ast interface{}, options ...Option) (*Kong, error) {
|
||||||
k := &Kong{
|
k := &Kong{
|
||||||
terminate: os.Exit,
|
Exit: os.Exit,
|
||||||
stdout: os.Stdout,
|
Stdout: os.Stdout,
|
||||||
stderr: os.Stderr,
|
Stderr: os.Stderr,
|
||||||
help: defaultHelpTemplate,
|
help: defaultHelpTemplate,
|
||||||
helpContext: map[string]interface{}{},
|
helpContext: map[string]interface{}{},
|
||||||
helpFuncs: template.FuncMap{},
|
helpFuncs: template.FuncMap{},
|
||||||
|
hooks: map[reflect.Value]Hook{},
|
||||||
}
|
}
|
||||||
|
|
||||||
model, err := build(ast)
|
model, err := build(ast)
|
||||||
@@ -56,26 +69,91 @@ func New(ast interface{}, options ...Option) (*Kong, error) {
|
|||||||
return k, nil
|
return k, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trace parses the command-line, validating and collecting matching grammar nodes.
|
||||||
|
func (k *Kong) Trace(args []string) (*Context, error) {
|
||||||
|
p := &Context{
|
||||||
|
app: k.Model,
|
||||||
|
args: args,
|
||||||
|
Trace: []*Trace{
|
||||||
|
{App: k.Model, Flags: append([]*Flag{}, k.Model.Flags...), Value: k.Model.Target},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := p.reset(&p.app.Node)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p.Error = p.trace(&p.app.Node)
|
||||||
|
if err = checkMissingFlags(p.Flags()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hook to execute when a command is encountered.
|
||||||
|
func (k *Kong) Hook(ptr interface{}, hook Hook) *Kong {
|
||||||
|
k.hooks[reflect.ValueOf(ptr)] = hook
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
|
||||||
// Parse arguments into target.
|
// 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>".
|
||||||
func (k *Kong) Parse(args []string) (command string, err error) {
|
func (k *Kong) Parse(args []string) (command string, err error) {
|
||||||
defer catch(&err)
|
defer catch(&err)
|
||||||
ctx, err := Trace(args, k.Model)
|
ctx, err := k.Trace(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
if err := k.applyHooks(ctx); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
if ctx.Error != nil {
|
if ctx.Error != nil {
|
||||||
return "", ctx.Error
|
return "", ctx.Error
|
||||||
}
|
}
|
||||||
if value := ctx.FlagValue(k.Model.HelpFlag); value.IsValid() && value.Bool() {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
return ctx.Apply()
|
return ctx.Apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *Kong) Errorf(format string, args ...interface{}) {
|
func (k *Kong) applyHooks(ctx *Context) error {
|
||||||
fmt.Fprintf(os.Stderr, k.Model.Name+": "+format, args...)
|
for _, trace := range ctx.Trace {
|
||||||
|
var key reflect.Value
|
||||||
|
switch {
|
||||||
|
case trace.App != nil:
|
||||||
|
key = trace.App.Target
|
||||||
|
case trace.Argument != nil:
|
||||||
|
key = trace.Argument.Target
|
||||||
|
case trace.Command != nil:
|
||||||
|
key = trace.Command.Target
|
||||||
|
case trace.Positional != nil:
|
||||||
|
key = trace.Positional.Value
|
||||||
|
case trace.Flag != nil:
|
||||||
|
key = trace.Flag.Value.Value
|
||||||
|
default:
|
||||||
|
panic("unsupported Trace")
|
||||||
|
}
|
||||||
|
if key.IsValid() {
|
||||||
|
key = key.Addr()
|
||||||
|
}
|
||||||
|
if hook := k.hooks[key]; hook != nil {
|
||||||
|
if err := hook(k, ctx, trace); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Printf writes a message to Kong.Stdout with the application name prefixed.
|
||||||
|
func (k *Kong) Printf(format string, args ...interface{}) {
|
||||||
|
fmt.Fprintf(k.Stdout, k.Model.Name+": "+format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf writes a message to Kong.Stderr with the application name prefixed.
|
||||||
|
func (k *Kong) Errorf(format string, args ...interface{}) {
|
||||||
|
fmt.Fprintf(k.Stderr, k.Model.Name+": "+format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FatalIfError terminates with an error message if err != nil.
|
||||||
func (k *Kong) FatalIfErrorf(err error, args ...interface{}) {
|
func (k *Kong) FatalIfErrorf(err error, args ...interface{}) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
@@ -85,7 +163,7 @@ func (k *Kong) FatalIfErrorf(err error, args ...interface{}) {
|
|||||||
msg = fmt.Sprintf(args[0].(string), args...) + ": " + err.Error()
|
msg = fmt.Sprintf(args[0].(string), args...) + ": " + err.Error()
|
||||||
}
|
}
|
||||||
k.Errorf("%s\n", msg)
|
k.Errorf("%s\n", msg)
|
||||||
k.terminate(1)
|
k.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func catch(err *error) {
|
func catch(err *error) {
|
||||||
|
|||||||
+65
-10
@@ -1,6 +1,7 @@
|
|||||||
package kong
|
package kong
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -311,16 +312,6 @@ func TestInvalidDefaultErrors(t *testing.T) {
|
|||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHelp(t *testing.T) {
|
|
||||||
var cli struct {
|
|
||||||
Flag string
|
|
||||||
}
|
|
||||||
p := mustNew(t, &cli)
|
|
||||||
_, err := p.Parse([]string{"--flag=hello", "--help"})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotEqual(t, "hello", cli.Flag)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCommandMissingTagIsInvalid(t *testing.T) {
|
func TestCommandMissingTagIsInvalid(t *testing.T) {
|
||||||
var cli struct {
|
var cli struct {
|
||||||
One struct{}
|
One struct{}
|
||||||
@@ -352,3 +343,67 @@ func TestDuplicateFlagOnPeerCommandIsOkay(t *testing.T) {
|
|||||||
_, err := New(&cli)
|
_, err := New(&cli)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTraceErrorPartiallySucceeds(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
One struct {
|
||||||
|
Two struct {
|
||||||
|
} `kong:"cmd"`
|
||||||
|
} `kong:"cmd"`
|
||||||
|
}
|
||||||
|
p := mustNew(t, &cli)
|
||||||
|
trace, err := p.Trace([]string{"one", "bad"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Error(t, trace.Error)
|
||||||
|
require.Equal(t, []string{"one"}, trace.Command())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHooks(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
One struct {
|
||||||
|
Two string `kong:"arg,optional"`
|
||||||
|
Three string
|
||||||
|
} `kong:"cmd"`
|
||||||
|
}
|
||||||
|
type values struct {
|
||||||
|
one bool
|
||||||
|
two string
|
||||||
|
three string
|
||||||
|
}
|
||||||
|
hooked := values{}
|
||||||
|
var tests = []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
values values
|
||||||
|
}{
|
||||||
|
{"Command", "one", values{true, "", ""}},
|
||||||
|
{"Arg", "one two", values{true, "two", ""}},
|
||||||
|
{"Flag", "one --three=three", values{true, "", "three"}},
|
||||||
|
{"ArgAndFlag", "one two --three=three", values{true, "two", "three"}},
|
||||||
|
}
|
||||||
|
p := mustNew(t, &cli).
|
||||||
|
Hook(&cli.One, func(app *Kong, ctx *Context, trace *Trace) error {
|
||||||
|
hooked.one = true
|
||||||
|
return nil
|
||||||
|
}).
|
||||||
|
Hook(&cli.One.Two, func(app *Kong, ctx *Context, trace *Trace) error {
|
||||||
|
hooked.two = trace.Value.String()
|
||||||
|
return nil
|
||||||
|
}).
|
||||||
|
Hook(&cli.One.Three, func(app *Kong, ctx *Context, trace *Trace) error {
|
||||||
|
hooked.three = trace.Value.String()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
hooked = values{}
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
_, err := p.Parse(strings.Split(test.input, " "))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, test.values, hooked)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHelp(t *testing.T) {
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ type Node struct {
|
|||||||
Flags []*Flag
|
Flags []*Flag
|
||||||
Positional []*Value
|
Positional []*Value
|
||||||
Children []*Branch
|
Children []*Branch
|
||||||
|
Target reflect.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Value is either a flag or a variable positional argument.
|
// A Value is either a flag or a variable positional argument.
|
||||||
|
|||||||
+3
-3
@@ -9,7 +9,7 @@ type Option func(k *Kong)
|
|||||||
|
|
||||||
// ExitFunction overrides the function used to terminate. This is useful for testing or interactive use.
|
// ExitFunction overrides the function used to terminate. This is useful for testing or interactive use.
|
||||||
func ExitFunction(exit func(int)) Option {
|
func ExitFunction(exit func(int)) Option {
|
||||||
return func(k *Kong) { k.terminate = exit }
|
return func(k *Kong) { k.Exit = exit }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name overrides the application name.
|
// Name overrides the application name.
|
||||||
@@ -37,7 +37,7 @@ func HelpContext(context map[string]interface{}) Option {
|
|||||||
// Writers overrides the default writers. Useful for testing or interactive use.
|
// Writers overrides the default writers. Useful for testing or interactive use.
|
||||||
func Writers(stdout, stderr io.Writer) Option {
|
func Writers(stdout, stderr io.Writer) Option {
|
||||||
return func(k *Kong) {
|
return func(k *Kong) {
|
||||||
k.stdout = stdout
|
k.Stdout = stdout
|
||||||
k.stderr = stderr
|
k.Stderr = stderr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -12,7 +12,7 @@ func TestOptions(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "name", p.Model.Name)
|
require.Equal(t, "name", p.Model.Name)
|
||||||
require.Equal(t, "description", p.Model.Help)
|
require.Equal(t, "description", p.Model.Help)
|
||||||
require.Nil(t, p.stdout)
|
require.Nil(t, p.Stdout)
|
||||||
require.Nil(t, p.stderr)
|
require.Nil(t, p.Stderr)
|
||||||
require.Nil(t, p.terminate)
|
require.Nil(t, p.Exit)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user