Use variadic options to configure Kong.
This commit is contained in:
@@ -2,8 +2,9 @@ package kong
|
||||
|
||||
import "os"
|
||||
|
||||
func Parse(cli interface{}) {
|
||||
parser, err := New("", "", cli)
|
||||
// Parse constructs a new parser and parses the default command-line.
|
||||
func Parse(cli interface{}, options ...Option) {
|
||||
parser, err := New(cli, options...)
|
||||
parser.FatalIfErrorf(err)
|
||||
_, err = parser.Parse(os.Args[1:])
|
||||
parser.FatalIfErrorf(err)
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package kong
|
||||
|
||||
import (
|
||||
"io"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
const defaultHelp = `{{- with .Application -}}
|
||||
usage: {{.Name}}
|
||||
|
||||
{{- end -}}
|
||||
`
|
||||
|
||||
var defaultHelpTemplate = template.Must(template.New("help").Parse(defaultHelp))
|
||||
|
||||
// WriteHelp to w. If w is nil, the default stdout writer will be used.
|
||||
func (k *Kong) WriteHelp(w io.Writer) error {
|
||||
if w == nil {
|
||||
w = k.stdout
|
||||
}
|
||||
ctx := map[string]interface{}{
|
||||
"Application": k.Model,
|
||||
}
|
||||
for k, v := range k.helpContext {
|
||||
ctx[k] = v
|
||||
}
|
||||
return k.help.Execute(w, ctx)
|
||||
}
|
||||
@@ -2,10 +2,12 @@ package kong
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
type Error struct{ msg string }
|
||||
@@ -16,27 +18,40 @@ func fail(format string, args ...interface{}) {
|
||||
panic(Error{fmt.Sprintf(format, args...)})
|
||||
}
|
||||
|
||||
// Kong is the main parser type.
|
||||
type Kong struct {
|
||||
Model *Application
|
||||
// Termination function (defaults to os.Exit)
|
||||
Terminate func(int)
|
||||
terminate func(int)
|
||||
|
||||
stdout io.Writer
|
||||
stderr io.Writer
|
||||
|
||||
help *template.Template
|
||||
helpContext map[string]interface{}
|
||||
helpFuncs template.FuncMap
|
||||
}
|
||||
|
||||
// New creates a new Kong parser into ast.
|
||||
func New(name, description string, ast interface{}) (*Kong, error) {
|
||||
if name == "" {
|
||||
name = filepath.Base(os.Args[0])
|
||||
}
|
||||
func New(ast interface{}, options ...Option) (*Kong, error) {
|
||||
model, err := build(ast)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
model.Name = name
|
||||
model.Help = description
|
||||
return &Kong{
|
||||
Model: model,
|
||||
Terminate: os.Exit,
|
||||
}, nil
|
||||
model.Name = filepath.Base(os.Args[0])
|
||||
k := &Kong{
|
||||
Model: model,
|
||||
terminate: os.Exit,
|
||||
stdout: os.Stdout,
|
||||
stderr: os.Stderr,
|
||||
help: defaultHelpTemplate,
|
||||
helpContext: map[string]interface{}{},
|
||||
helpFuncs: template.FuncMap{},
|
||||
}
|
||||
for _, option := range options {
|
||||
option(k)
|
||||
}
|
||||
return k, nil
|
||||
}
|
||||
|
||||
// Parse arguments into target.
|
||||
@@ -183,6 +198,24 @@ func (k *Kong) applyNode(scan *Scanner, node *Node) (command []string, err error
|
||||
return nil, fmt.Errorf("unexpected token %s", token)
|
||||
}
|
||||
}
|
||||
if positional < len(node.Positional) {
|
||||
missing := []string{}
|
||||
for ; positional < len(node.Positional); positional++ {
|
||||
missing = append(missing, "<"+node.Positional[positional].Name+">")
|
||||
}
|
||||
return nil, fmt.Errorf("missing positional arguments %s", strings.Join(missing, " "))
|
||||
}
|
||||
if len(node.Children) > 0 {
|
||||
missing := []string{}
|
||||
for _, child := range node.Children {
|
||||
if child.Argument != nil {
|
||||
missing = append(missing, "<"+child.Argument.Name+">")
|
||||
} else {
|
||||
missing = append(missing, child.Command.Name)
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("expected one of %s", strings.Join(missing, ", "))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -221,5 +254,5 @@ func (k *Kong) FatalIfErrorf(err error, args ...interface{}) {
|
||||
msg = fmt.Sprintf(args[0].(string), args...) + ": " + err.Error()
|
||||
}
|
||||
k.Errorf("%s", msg)
|
||||
k.Terminate(1)
|
||||
k.terminate(1)
|
||||
}
|
||||
|
||||
+12
-4
@@ -8,12 +8,12 @@ import (
|
||||
|
||||
func mustNew(t *testing.T, cli interface{}) *Kong {
|
||||
t.Helper()
|
||||
parser, err := New("", "", cli)
|
||||
parser, err := New(cli)
|
||||
require.NoError(t, err)
|
||||
return parser
|
||||
}
|
||||
|
||||
func TestArgumentSequence(t *testing.T) {
|
||||
func TestPositionalArguments(t *testing.T) {
|
||||
var cli struct {
|
||||
User struct {
|
||||
Create struct {
|
||||
@@ -27,6 +27,10 @@ func TestArgumentSequence(t *testing.T) {
|
||||
cmd, err := p.Parse([]string{"user", "create", "10", "Alec", "Thomas"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "user create <id> <first> <last>", cmd)
|
||||
t.Run("Missing", func(t *testing.T) {
|
||||
_, err := p.Parse([]string{"user", "create", "10"})
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBranchingArgument(t *testing.T) {
|
||||
@@ -60,6 +64,10 @@ func TestBranchingArgument(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 10, cli.User.ID.ID)
|
||||
require.Equal(t, "user <id> delete", cmd)
|
||||
t.Run("Missing", func(t *testing.T) {
|
||||
_, err = p.Parse([]string{"user"})
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestResetWithDefaults(t *testing.T) {
|
||||
@@ -102,7 +110,7 @@ func TestUnsupportedFieldErrors(t *testing.T) {
|
||||
var cli struct {
|
||||
Keys map[string]string
|
||||
}
|
||||
_, err := New("", "", &cli)
|
||||
_, err := New(&cli)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
@@ -113,6 +121,6 @@ func TestMatchingArgField(t *testing.T) {
|
||||
} `arg:""`
|
||||
}
|
||||
|
||||
_, err := New("", "", &cli)
|
||||
_, err := New(&cli)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package kong
|
||||
|
||||
import (
|
||||
"io"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
type Option func(k *Kong)
|
||||
|
||||
// ExitFunction overrides the function used to terminate. This is useful for testing or interactive use.
|
||||
func ExitFunction(exit func(int)) Option {
|
||||
return func(k *Kong) { k.terminate = exit }
|
||||
}
|
||||
|
||||
// Name overrides the application name.
|
||||
func Name(name string) Option {
|
||||
return func(k *Kong) { k.Model.Name = name }
|
||||
}
|
||||
|
||||
// Description sets the application description.
|
||||
func Description(description string) Option {
|
||||
return func(k *Kong) { k.Model.Help = description }
|
||||
}
|
||||
|
||||
// HelpTemplate overrides the default help template.
|
||||
func HelpTemplate(template *template.Template) Option {
|
||||
return func(k *Kong) { k.help = template }
|
||||
}
|
||||
|
||||
// HelpContext sets extra context in the help template.
|
||||
//
|
||||
// The key "Application" will always be available and is the root of the application model.
|
||||
func HelpContext(context map[string]interface{}) Option {
|
||||
return func(k *Kong) { k.helpContext = context }
|
||||
}
|
||||
|
||||
// Writers overrides the default writers. Useful for testing or interactive use.
|
||||
func Writers(stdout, stderr io.Writer) Option {
|
||||
return func(k *Kong) { k.stdout = stdout }
|
||||
}
|
||||
Reference in New Issue
Block a user