Use variadic options to configure Kong.
This commit is contained in:
@@ -2,8 +2,9 @@ package kong
|
|||||||
|
|
||||||
import "os"
|
import "os"
|
||||||
|
|
||||||
func Parse(cli interface{}) {
|
// Parse constructs a new parser and parses the default command-line.
|
||||||
parser, err := New("", "", cli)
|
func Parse(cli interface{}, options ...Option) {
|
||||||
|
parser, err := New(cli, options...)
|
||||||
parser.FatalIfErrorf(err)
|
parser.FatalIfErrorf(err)
|
||||||
_, err = parser.Parse(os.Args[1:])
|
_, err = parser.Parse(os.Args[1:])
|
||||||
parser.FatalIfErrorf(err)
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Error struct{ msg string }
|
type Error struct{ msg string }
|
||||||
@@ -16,27 +18,40 @@ func fail(format string, args ...interface{}) {
|
|||||||
panic(Error{fmt.Sprintf(format, args...)})
|
panic(Error{fmt.Sprintf(format, args...)})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
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.
|
// New creates a new Kong parser into ast.
|
||||||
func New(name, description string, ast interface{}) (*Kong, error) {
|
func New(ast interface{}, options ...Option) (*Kong, error) {
|
||||||
if name == "" {
|
|
||||||
name = filepath.Base(os.Args[0])
|
|
||||||
}
|
|
||||||
model, err := build(ast)
|
model, err := build(ast)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
model.Name = name
|
model.Name = filepath.Base(os.Args[0])
|
||||||
model.Help = description
|
k := &Kong{
|
||||||
return &Kong{
|
Model: model,
|
||||||
Model: model,
|
terminate: os.Exit,
|
||||||
Terminate: os.Exit,
|
stdout: os.Stdout,
|
||||||
}, nil
|
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.
|
// 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)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,5 +254,5 @@ 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", msg)
|
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 {
|
func mustNew(t *testing.T, cli interface{}) *Kong {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
parser, err := New("", "", cli)
|
parser, err := New(cli)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return parser
|
return parser
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestArgumentSequence(t *testing.T) {
|
func TestPositionalArguments(t *testing.T) {
|
||||||
var cli struct {
|
var cli struct {
|
||||||
User struct {
|
User struct {
|
||||||
Create struct {
|
Create struct {
|
||||||
@@ -27,6 +27,10 @@ func TestArgumentSequence(t *testing.T) {
|
|||||||
cmd, err := p.Parse([]string{"user", "create", "10", "Alec", "Thomas"})
|
cmd, err := p.Parse([]string{"user", "create", "10", "Alec", "Thomas"})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "user create <id> <first> <last>", cmd)
|
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) {
|
func TestBranchingArgument(t *testing.T) {
|
||||||
@@ -60,6 +64,10 @@ func TestBranchingArgument(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 10, cli.User.ID.ID)
|
require.Equal(t, 10, cli.User.ID.ID)
|
||||||
require.Equal(t, "user <id> delete", cmd)
|
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) {
|
func TestResetWithDefaults(t *testing.T) {
|
||||||
@@ -102,7 +110,7 @@ func TestUnsupportedFieldErrors(t *testing.T) {
|
|||||||
var cli struct {
|
var cli struct {
|
||||||
Keys map[string]string
|
Keys map[string]string
|
||||||
}
|
}
|
||||||
_, err := New("", "", &cli)
|
_, err := New(&cli)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,6 +121,6 @@ func TestMatchingArgField(t *testing.T) {
|
|||||||
} `arg:""`
|
} `arg:""`
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := New("", "", &cli)
|
_, err := New(&cli)
|
||||||
require.Error(t, err)
|
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