committed by
Alec Thomas
parent
184735e689
commit
3eb5e285ed
@@ -9,15 +9,15 @@ import "github.com/alecthomas/kong"
|
|||||||
|
|
||||||
var CLI struct {
|
var CLI struct {
|
||||||
Rm struct {
|
Rm struct {
|
||||||
Force bool `help:"Force removal."`
|
Force bool `kong:"help='Force removal.'"`
|
||||||
Recursive bool `help:"Recursively remove files."`
|
Recursive bool `kong:"help='Recursively remove files.'"`
|
||||||
|
|
||||||
Paths []string `help:"Paths to remove." type:"path"`
|
Paths []string `kong:"help='Paths to remove.',type='path'"`
|
||||||
} `help:"Remove files."`
|
} `kong:"help='Remove files.'"`
|
||||||
|
|
||||||
Ls struct {
|
Ls struct {
|
||||||
Paths []string `help:"Paths to list." type:"path"`
|
Paths []string `kong:"help='Paths to list.',type='path'"`
|
||||||
} `help:"List paths."`
|
} `kong:"help='List paths.'"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
+16
-8
@@ -1,20 +1,28 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/alecthomas/kong"
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/alecthomas/kong"
|
||||||
|
)
|
||||||
|
|
||||||
var CLI struct {
|
var CLI struct {
|
||||||
Rm struct {
|
Rm struct {
|
||||||
Force bool `help:"Force removal."`
|
Force bool `kong:"help='Force removal.'"`
|
||||||
Recursive bool `help:"Recursively remove files."`
|
Recursive bool `kong:"help='Recursively remove files.'"`
|
||||||
|
|
||||||
Paths []string `help:"Paths to remove." type:"path"`
|
Paths []string `kong:"help='Paths to remove.',type='path'"`
|
||||||
} `help:"Remove files."`
|
} `kong:"help='Remove files.'"`
|
||||||
|
|
||||||
Ls struct {
|
Ls struct {
|
||||||
Paths []string `help:"Paths to list." type:"path"`
|
Paths []string `kong:"help='Paths to list.',type='path'"`
|
||||||
} `help:"List paths."`
|
} `kong:"help='List paths.'"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
kong.Parse(&CLI)
|
cmd := kong.Parse(&CLI)
|
||||||
|
s, _ := json.Marshal(&CLI)
|
||||||
|
fmt.Println(cmd)
|
||||||
|
fmt.Println(string(s))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func build(ast interface{}) (app *Application, err error) {
|
func build(ast interface{}) (app *Application, err error) {
|
||||||
@@ -23,14 +22,21 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
node := buildNode(iv, true)
|
node, err := buildNode(iv, true)
|
||||||
|
if err != nil {
|
||||||
|
return node, err
|
||||||
|
}
|
||||||
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)
|
||||||
}
|
}
|
||||||
return node, nil
|
return node, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildNode(v reflect.Value, cmd bool) *Node {
|
func dashedString(s string) string {
|
||||||
|
return strings.Join(camelCase(s), "-")
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildNode(v reflect.Value, cmd bool) (*Node, error) {
|
||||||
node := &Node{}
|
node := &Node{}
|
||||||
for i := 0; i < v.NumField(); i++ {
|
for i := 0; i < v.NumField(); i++ {
|
||||||
ft := v.Type().Field(i)
|
ft := v.Type().Field(i)
|
||||||
@@ -41,38 +47,34 @@ func buildNode(v reflect.Value, cmd bool) *Node {
|
|||||||
|
|
||||||
name := ft.Tag.Get("name")
|
name := ft.Tag.Get("name")
|
||||||
if name == "" {
|
if name == "" {
|
||||||
name = strings.ToLower(strings.Join(camelCase(ft.Name), "-"))
|
name = strings.ToLower(dashedString(ft.Name))
|
||||||
}
|
}
|
||||||
decoder := DecoderForField(ft)
|
|
||||||
help, _ := ft.Tag.Lookup("help")
|
tag, err := parseTag(fv, ft.Tag.Get("kong"))
|
||||||
dflt := ft.Tag.Get("default")
|
if err != nil {
|
||||||
placeholder := ft.Tag.Get("placeholder")
|
return nil, err
|
||||||
if placeholder == "" {
|
|
||||||
placeholder = strings.ToUpper(strings.Join(camelCase(fv.Type().Name()), "-"))
|
|
||||||
}
|
}
|
||||||
short, _ := utf8.DecodeRuneInString(ft.Tag.Get("short"))
|
|
||||||
if short == utf8.RuneError {
|
decoder := DecoderForField(tag.Type, ft)
|
||||||
short = 0
|
|
||||||
}
|
|
||||||
// group := ft.Tag.Get("group")
|
|
||||||
_, required := ft.Tag.Lookup("required")
|
|
||||||
_, optional := ft.Tag.Lookup("optional")
|
|
||||||
// Force field to be an argument, not a flag.
|
|
||||||
_, arg := ft.Tag.Lookup("arg")
|
|
||||||
if !cmd {
|
if !cmd {
|
||||||
_, cmd = ft.Tag.Lookup("cmd")
|
cmd = tag.Cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
env := ft.Tag.Get("env")
|
env := ft.Tag.Get("env")
|
||||||
format := ft.Tag.Get("format")
|
format := ft.Tag.Get("format")
|
||||||
|
|
||||||
// Nested structs are either commands or args.
|
// Nested structs are either commands or args.
|
||||||
if ft.Type.Kind() == reflect.Struct && (cmd || arg) {
|
if ft.Type.Kind() == reflect.Struct && (cmd || tag.Arg) {
|
||||||
child := buildNode(fv, false)
|
child, err := buildNode(fv, false)
|
||||||
child.Help = help
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
child.Help = tag.Help
|
||||||
|
|
||||||
// A branching argument. This is a bit hairy, as we let buildNode() do the parsing, then check that
|
// A branching argument. This is a bit hairy, as we let buildNode() do the parsing, then check that
|
||||||
// a positional argument is provided to the child, and move it to the branching argument field.
|
// a positional argument is provided to the child, and move it to the branching argument field.
|
||||||
if arg {
|
if tag.Arg {
|
||||||
if len(child.Positional) == 0 {
|
if len(child.Positional) == 0 {
|
||||||
fail("positional branch %s.%s must have at least one child positional argument",
|
fail("positional branch %s.%s must have at least one child positional argument",
|
||||||
v.Type().Name(), ft.Name)
|
v.Type().Name(), ft.Name)
|
||||||
@@ -104,35 +106,35 @@ func buildNode(v reflect.Value, cmd bool) *Node {
|
|||||||
fail("no decoder for %s.%s (of type %s)", v.Type(), ft.Name, ft.Type)
|
fail("no decoder for %s.%s (of type %s)", v.Type(), ft.Name, ft.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
flag := !arg
|
flag := !tag.Arg
|
||||||
|
|
||||||
value := Value{
|
value := Value{
|
||||||
Name: name,
|
Name: name,
|
||||||
Flag: flag,
|
Flag: flag,
|
||||||
Help: help,
|
Help: tag.Help,
|
||||||
Default: dflt,
|
Default: tag.Default,
|
||||||
Decoder: decoder,
|
Decoder: decoder,
|
||||||
Value: fv,
|
Value: fv,
|
||||||
Field: ft,
|
Field: ft,
|
||||||
|
|
||||||
// Flags are optional by default, and args are required by default.
|
// Flags are optional by default, and args are required by default.
|
||||||
Required: (flag && required) || (arg && !optional),
|
Required: (flag && tag.Required) || (tag.Arg && !tag.Optional),
|
||||||
Format: format,
|
Format: format,
|
||||||
}
|
}
|
||||||
if arg {
|
if tag.Arg {
|
||||||
node.Positional = append(node.Positional, &value)
|
node.Positional = append(node.Positional, &value)
|
||||||
} else {
|
} else {
|
||||||
node.Flags = append(node.Flags, &Flag{
|
node.Flags = append(node.Flags, &Flag{
|
||||||
Value: value,
|
Value: value,
|
||||||
Short: short,
|
Short: tag.Short,
|
||||||
Placeholder: placeholder,
|
Placeholder: tag.Placeholder,
|
||||||
Env: env,
|
Env: env,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scan through argument positionals to ensure optional is never before a required
|
// Scan through argument positionals to ensure optional is never before a required.
|
||||||
last := true
|
last := true
|
||||||
for _, p := range node.Positional {
|
for _, p := range node.Positional {
|
||||||
if !last && p.Required {
|
if !last && p.Required {
|
||||||
@@ -142,5 +144,5 @@ func buildNode(v reflect.Value, cmd bool) *Node {
|
|||||||
last = p.Required
|
last = p.Required
|
||||||
}
|
}
|
||||||
|
|
||||||
return node
|
return node, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-7
@@ -70,7 +70,7 @@ var _ KindDecoder = &kindDecoder{}
|
|||||||
//
|
//
|
||||||
// eg.
|
// eg.
|
||||||
//
|
//
|
||||||
// Field string `type:"colour"`
|
// Field string `kong:"type='colour'`
|
||||||
// kong.RegisterDecoder(kong.NewNamedDecoder("colour", ...))
|
// kong.RegisterDecoder(kong.NewNamedDecoder("colour", ...))
|
||||||
type NamedDecoder interface {
|
type NamedDecoder interface {
|
||||||
Name() string
|
Name() string
|
||||||
@@ -99,12 +99,9 @@ var (
|
|||||||
// DecoderForField finds a decoder for a struct field.
|
// DecoderForField finds a decoder for a struct field.
|
||||||
//
|
//
|
||||||
// Will return nil if a decoder can not be determined.
|
// Will return nil if a decoder can not be determined.
|
||||||
func DecoderForField(field reflect.StructField) Decoder {
|
func DecoderForField(name string, field reflect.StructField) Decoder {
|
||||||
name, ok := field.Tag.Lookup("type")
|
if decoder, ok := namedDecoders[name]; ok {
|
||||||
if ok {
|
return decoder
|
||||||
if decoder, ok := namedDecoders[name]; ok {
|
|
||||||
return decoder
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return DecoderForType(field.Type)
|
return DecoderForType(field.Type)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ package kong
|
|||||||
import "os"
|
import "os"
|
||||||
|
|
||||||
// Parse constructs a new parser and parses the default command-line.
|
// Parse constructs a new parser and parses the default command-line.
|
||||||
func Parse(cli interface{}, options ...Option) {
|
func Parse(cli interface{}, options ...Option) string {
|
||||||
parser, err := New(cli, options...)
|
parser, err := New(cli, options...)
|
||||||
parser.FatalIfErrorf(err)
|
parser.FatalIfErrorf(err)
|
||||||
_, err = parser.Parse(os.Args[1:])
|
cmd, err := parser.Parse(os.Args[1:])
|
||||||
parser.FatalIfErrorf(err)
|
parser.FatalIfErrorf(err)
|
||||||
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,13 +33,7 @@ type Kong struct {
|
|||||||
|
|
||||||
// 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) {
|
||||||
model, err := build(ast)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
model.Name = filepath.Base(os.Args[0])
|
|
||||||
k := &Kong{
|
k := &Kong{
|
||||||
Model: model,
|
|
||||||
terminate: os.Exit,
|
terminate: os.Exit,
|
||||||
stdout: os.Stdout,
|
stdout: os.Stdout,
|
||||||
stderr: os.Stderr,
|
stderr: os.Stderr,
|
||||||
@@ -47,6 +41,14 @@ func New(ast interface{}, options ...Option) (*Kong, error) {
|
|||||||
helpContext: map[string]interface{}{},
|
helpContext: map[string]interface{}{},
|
||||||
helpFuncs: template.FuncMap{},
|
helpFuncs: template.FuncMap{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model, err := build(ast)
|
||||||
|
if err != nil {
|
||||||
|
return k, err
|
||||||
|
}
|
||||||
|
k.Model = model
|
||||||
|
k.Model.Name = filepath.Base(os.Args[0])
|
||||||
|
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option(k)
|
option(k)
|
||||||
}
|
}
|
||||||
@@ -91,7 +93,11 @@ func (k *Kong) reset(node *Node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (k *Kong) Errorf(format string, args ...interface{}) {
|
func (k *Kong) Errorf(format string, args ...interface{}) {
|
||||||
fmt.Fprintf(os.Stderr, k.Model.Name+": "+format, args...)
|
if k.Model != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, k.Model.Name+": "+format, args...)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(os.Stderr, format, args...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *Kong) FatalIfErrorf(err error, args ...interface{}) {
|
func (k *Kong) FatalIfErrorf(err error, args ...interface{}) {
|
||||||
@@ -99,9 +105,9 @@ func (k *Kong) FatalIfErrorf(err error, args ...interface{}) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
msg := err.Error()
|
msg := err.Error()
|
||||||
if len(args) == 0 {
|
if len(args) > 0 {
|
||||||
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\n", msg)
|
||||||
k.terminate(1)
|
k.terminate(1)
|
||||||
}
|
}
|
||||||
|
|||||||
+89
-33
@@ -17,11 +17,11 @@ func TestPositionalArguments(t *testing.T) {
|
|||||||
var cli struct {
|
var cli struct {
|
||||||
User struct {
|
User struct {
|
||||||
Create struct {
|
Create struct {
|
||||||
ID int `arg:""`
|
ID int `kong:"arg"`
|
||||||
First string `arg:""`
|
First string `kong:"arg"`
|
||||||
Last string `arg:""`
|
Last string `kong:"arg"`
|
||||||
} `cmd:""`
|
} `kong:"cmd"`
|
||||||
} `cmd:""`
|
} `kong:"cmd"`
|
||||||
}
|
}
|
||||||
p := mustNew(t, &cli)
|
p := mustNew(t, &cli)
|
||||||
cmd, err := p.Parse([]string{"user", "create", "10", "Alec", "Thomas"})
|
cmd, err := p.Parse([]string{"user", "create", "10", "Alec", "Thomas"})
|
||||||
@@ -43,21 +43,21 @@ func TestBranchingArgument(t *testing.T) {
|
|||||||
var cli struct {
|
var cli struct {
|
||||||
User struct {
|
User struct {
|
||||||
Create struct {
|
Create struct {
|
||||||
ID string `arg:""`
|
ID string `kong:"arg"`
|
||||||
First string `arg:""`
|
First string `kong:"arg"`
|
||||||
Last string `arg:""`
|
Last string `kong:"arg"`
|
||||||
} `cmd:""`
|
} `kong:"cmd"`
|
||||||
|
|
||||||
// Branching argument.
|
// Branching argument.
|
||||||
ID struct {
|
ID struct {
|
||||||
ID int `arg:""`
|
ID int `kong:"arg"`
|
||||||
Flag int
|
Flag int
|
||||||
Delete struct{} `cmd:""`
|
Delete struct{} `kong:"cmd"`
|
||||||
Rename struct {
|
Rename struct {
|
||||||
To string
|
To string
|
||||||
} `cmd:""`
|
} `kong:"cmd"`
|
||||||
} `arg:""`
|
} `kong:"arg"`
|
||||||
} `cmd:"" help:"User management."`
|
} `kong:"cmd,help='User management.'"`
|
||||||
}
|
}
|
||||||
p := mustNew(t, &cli)
|
p := mustNew(t, &cli)
|
||||||
cmd, err := p.Parse([]string{"user", "10", "delete"})
|
cmd, err := p.Parse([]string{"user", "10", "delete"})
|
||||||
@@ -73,7 +73,7 @@ func TestBranchingArgument(t *testing.T) {
|
|||||||
func TestResetWithDefaults(t *testing.T) {
|
func TestResetWithDefaults(t *testing.T) {
|
||||||
var cli struct {
|
var cli struct {
|
||||||
Flag string
|
Flag string
|
||||||
FlagWithDefault string `default:"default" `
|
FlagWithDefault string `kong:"default='default'"`
|
||||||
}
|
}
|
||||||
cli.Flag = "BLAH"
|
cli.Flag = "BLAH"
|
||||||
cli.FlagWithDefault = "BLAH"
|
cli.FlagWithDefault = "BLAH"
|
||||||
@@ -96,7 +96,7 @@ func TestFlagSlice(t *testing.T) {
|
|||||||
|
|
||||||
func TestArgSlice(t *testing.T) {
|
func TestArgSlice(t *testing.T) {
|
||||||
var cli struct {
|
var cli struct {
|
||||||
Slice []int `arg:""`
|
Slice []int `kong:"arg"`
|
||||||
Flag bool
|
Flag bool
|
||||||
}
|
}
|
||||||
parser := mustNew(t, &cli)
|
parser := mustNew(t, &cli)
|
||||||
@@ -117,8 +117,8 @@ func TestUnsupportedFieldErrors(t *testing.T) {
|
|||||||
func TestMatchingArgField(t *testing.T) {
|
func TestMatchingArgField(t *testing.T) {
|
||||||
var cli struct {
|
var cli struct {
|
||||||
ID struct {
|
ID struct {
|
||||||
NotID int `arg:""`
|
NotID int `kong:"arg"`
|
||||||
} `arg:""`
|
} `kong:"arg"`
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := New(&cli)
|
_, err := New(&cli)
|
||||||
@@ -127,9 +127,9 @@ func TestMatchingArgField(t *testing.T) {
|
|||||||
|
|
||||||
func TestCantMixPositionalAndBranches(t *testing.T) {
|
func TestCantMixPositionalAndBranches(t *testing.T) {
|
||||||
var cli struct {
|
var cli struct {
|
||||||
Arg string `arg:""`
|
Arg string `kong:"arg"`
|
||||||
Command struct {
|
Command struct {
|
||||||
} `cmd:""`
|
} `kong:"cmd"`
|
||||||
}
|
}
|
||||||
_, err := New(&cli)
|
_, err := New(&cli)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -140,8 +140,8 @@ func TestPropagatedFlags(t *testing.T) {
|
|||||||
Flag1 string
|
Flag1 string
|
||||||
Command1 struct {
|
Command1 struct {
|
||||||
Flag2 bool
|
Flag2 bool
|
||||||
Command2 struct{} `cmd:""`
|
Command2 struct{} `kong:"cmd"`
|
||||||
} `cmd:""`
|
} `kong:"cmd"`
|
||||||
}
|
}
|
||||||
|
|
||||||
parser := mustNew(t, &cli)
|
parser := mustNew(t, &cli)
|
||||||
@@ -153,7 +153,7 @@ func TestPropagatedFlags(t *testing.T) {
|
|||||||
|
|
||||||
func TestRequiredFlag(t *testing.T) {
|
func TestRequiredFlag(t *testing.T) {
|
||||||
var cli struct {
|
var cli struct {
|
||||||
Flag string `required:""`
|
Flag string `kong:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
parser := mustNew(t, &cli)
|
parser := mustNew(t, &cli)
|
||||||
@@ -163,7 +163,7 @@ func TestRequiredFlag(t *testing.T) {
|
|||||||
|
|
||||||
func TestOptionalArg(t *testing.T) {
|
func TestOptionalArg(t *testing.T) {
|
||||||
var cli struct {
|
var cli struct {
|
||||||
Arg string `arg:"" optional:""`
|
Arg string `kong:"arg,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
parser := mustNew(t, &cli)
|
parser := mustNew(t, &cli)
|
||||||
@@ -173,7 +173,7 @@ func TestOptionalArg(t *testing.T) {
|
|||||||
|
|
||||||
func TestRequiredArg(t *testing.T) {
|
func TestRequiredArg(t *testing.T) {
|
||||||
var cli struct {
|
var cli struct {
|
||||||
Arg string `arg:""`
|
Arg string `kong:"arg"`
|
||||||
}
|
}
|
||||||
|
|
||||||
parser := mustNew(t, &cli)
|
parser := mustNew(t, &cli)
|
||||||
@@ -183,8 +183,8 @@ func TestRequiredArg(t *testing.T) {
|
|||||||
|
|
||||||
func TestInvalidRequiredAfterOptional(t *testing.T) {
|
func TestInvalidRequiredAfterOptional(t *testing.T) {
|
||||||
var cli struct {
|
var cli struct {
|
||||||
ID int `arg:"" optional:""`
|
ID int `kong:"arg,optional"`
|
||||||
Name string `arg:""`
|
Name string `kong:"arg"`
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := New(&cli)
|
_, err := New(&cli)
|
||||||
@@ -194,9 +194,9 @@ func TestInvalidRequiredAfterOptional(t *testing.T) {
|
|||||||
func TestOptionalStructArg(t *testing.T) {
|
func TestOptionalStructArg(t *testing.T) {
|
||||||
var cli struct {
|
var cli struct {
|
||||||
Name struct {
|
Name struct {
|
||||||
Name string `arg:"" optional:""`
|
Name string `kong:"arg,optional"`
|
||||||
Enabled bool
|
Enabled bool
|
||||||
} `arg:"" optional:""`
|
} `kong:"arg,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
parser := mustNew(t, &cli)
|
parser := mustNew(t, &cli)
|
||||||
@@ -222,8 +222,8 @@ func TestOptionalStructArg(t *testing.T) {
|
|||||||
|
|
||||||
func TestMixedRequiredArgs(t *testing.T) {
|
func TestMixedRequiredArgs(t *testing.T) {
|
||||||
var cli struct {
|
var cli struct {
|
||||||
Name string `arg:""`
|
Name string `kong:"arg"`
|
||||||
ID int `arg:"" optional:""`
|
ID int `kong:"arg,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
parser := mustNew(t, &cli)
|
parser := mustNew(t, &cli)
|
||||||
@@ -244,10 +244,66 @@ func TestMixedRequiredArgs(t *testing.T) {
|
|||||||
|
|
||||||
func TestDefaultValueForOptionalArg(t *testing.T) {
|
func TestDefaultValueForOptionalArg(t *testing.T) {
|
||||||
var cli struct {
|
var cli struct {
|
||||||
Arg string `arg:"" optional:"" default:"default"`
|
Arg string `kong:"arg,optional,default='👌'"`
|
||||||
}
|
}
|
||||||
p := mustNew(t, &cli)
|
p := mustNew(t, &cli)
|
||||||
_, err := p.Parse(nil)
|
_, err := p.Parse(nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "default", cli.Arg)
|
require.Equal(t, "👌", cli.Arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoValueInTag(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Empty1 string `kong:"default"`
|
||||||
|
Empty2 string `kong:"default="`
|
||||||
|
}
|
||||||
|
p := mustNew(t, &cli)
|
||||||
|
_, err := p.Parse(nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "", cli.Empty1)
|
||||||
|
require.Equal(t, "", cli.Empty2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommaInQuotes(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Numbers string `kong:"default='1,2'"`
|
||||||
|
}
|
||||||
|
p := mustNew(t, &cli)
|
||||||
|
_, err := p.Parse(nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "1,2", cli.Numbers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnknownKey(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Numbers string `kong:"gak='hi'"`
|
||||||
|
}
|
||||||
|
_, err := New(&cli)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadString(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Numbers string `kong:"default='yay'n"`
|
||||||
|
}
|
||||||
|
_, err := New(&cli)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoQuoteEnd(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Numbers string `kong:"default='yay"`
|
||||||
|
}
|
||||||
|
_, err := New(&cli)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEscapedQuote(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
DoYouKnow string `kong:"default='i don\\'t know'"`
|
||||||
|
}
|
||||||
|
p := mustNew(t, &cli)
|
||||||
|
_, err := p.Parse(nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "i don't know", cli.DoYouKnow)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package kong
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Tag struct {
|
||||||
|
Cmd bool
|
||||||
|
Arg bool
|
||||||
|
Required bool
|
||||||
|
Optional bool
|
||||||
|
Help string
|
||||||
|
Type string
|
||||||
|
Default string
|
||||||
|
Format string
|
||||||
|
Placeholder string
|
||||||
|
Env string
|
||||||
|
Short rune
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCSV(s string) ([]string, error) {
|
||||||
|
num := 0
|
||||||
|
parts := []string{}
|
||||||
|
current := []rune{}
|
||||||
|
|
||||||
|
add := func() {
|
||||||
|
parts = append(parts, string(current))
|
||||||
|
current = []rune{}
|
||||||
|
num++
|
||||||
|
}
|
||||||
|
|
||||||
|
quotes := false
|
||||||
|
|
||||||
|
runes := []rune(s)
|
||||||
|
for idx := 0; idx < len(runes); idx++ {
|
||||||
|
r := runes[idx]
|
||||||
|
next := rune(0)
|
||||||
|
eof := false
|
||||||
|
if idx < len(runes)-1 {
|
||||||
|
next = runes[idx+1]
|
||||||
|
} else {
|
||||||
|
eof = true
|
||||||
|
}
|
||||||
|
if !quotes && r == ',' {
|
||||||
|
add()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if r == '\\' {
|
||||||
|
if next == '\'' {
|
||||||
|
idx++
|
||||||
|
r = '\''
|
||||||
|
}
|
||||||
|
} else if r == '\'' {
|
||||||
|
if quotes {
|
||||||
|
quotes = false
|
||||||
|
if next == ',' || eof {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return parts, fmt.Errorf("%v has an unexpected char at pos %v", s, idx)
|
||||||
|
} else {
|
||||||
|
quotes = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current = append(current, r)
|
||||||
|
}
|
||||||
|
if quotes {
|
||||||
|
return parts, fmt.Errorf("%v is not quoted properly", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
add()
|
||||||
|
|
||||||
|
return parts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTag(fv reflect.Value, s string) (*Tag, error) {
|
||||||
|
t := &Tag{}
|
||||||
|
if s == "" {
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parts, err := parseCSV(s)
|
||||||
|
if err != nil {
|
||||||
|
return t, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, part := range parts {
|
||||||
|
is := func(m string) bool { return part == m }
|
||||||
|
value := func(m string) (string, bool) {
|
||||||
|
split := strings.SplitN(part, "=", 2)
|
||||||
|
if split[0] != m {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
if len(split) == 1 {
|
||||||
|
return "", true
|
||||||
|
}
|
||||||
|
return split[1], true
|
||||||
|
}
|
||||||
|
|
||||||
|
if is("cmd") {
|
||||||
|
t.Cmd = true
|
||||||
|
} else if is("arg") {
|
||||||
|
t.Arg = true
|
||||||
|
} else if is("required") {
|
||||||
|
t.Required = true
|
||||||
|
} else if is("optional") {
|
||||||
|
t.Optional = true
|
||||||
|
} else if v, ok := value("default"); ok {
|
||||||
|
t.Default = v
|
||||||
|
} else if v, ok := value("help"); ok {
|
||||||
|
t.Help = v
|
||||||
|
} else if v, ok := value("type"); ok {
|
||||||
|
t.Type = v
|
||||||
|
} else if v, ok := value("placeholder"); ok {
|
||||||
|
t.Placeholder = v
|
||||||
|
} else if v, ok := value("env"); ok {
|
||||||
|
t.Env = v
|
||||||
|
} else if v, ok := value("rune"); ok {
|
||||||
|
t.Short, _ = utf8.DecodeRuneInString(v)
|
||||||
|
if t.Short == utf8.RuneError {
|
||||||
|
t.Short = 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return t, fmt.Errorf("%v is an unknown kong key", part)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Placeholder == "" {
|
||||||
|
t.Placeholder = strings.ToUpper(dashedString(fv.Type().Name()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user