Require cmd:"" or arg:"" for branching commands/args.

This commit is contained in:
Alec Thomas
2018-05-18 15:39:40 +10:00
parent a41bb0e4da
commit 67b1a12059
3 changed files with 37 additions and 32 deletions
+12 -9
View File
@@ -10,7 +10,7 @@ import (
func build(ast interface{}) (app *Application, err error) { func build(ast interface{}) (app *Application, err error) {
defer func() { defer func() {
msg := recover() msg := recover()
if test, ok := recover().(error); ok { if test, ok := msg.(error); ok {
app = nil app = nil
err = test err = test
} else if msg != nil { } else if msg != nil {
@@ -23,13 +23,16 @@ 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)
} }
return buildNode(iv), nil return buildNode(iv, true), nil
} }
func buildNode(v reflect.Value) *Node { func buildNode(v reflect.Value, cmd bool) *Node {
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)
if strings.ToLower(ft.Name[0:1]) == ft.Name[0:1] {
continue
}
fv := v.Field(i) fv := v.Field(i)
name := ft.Tag.Get("name") name := ft.Tag.Get("name")
@@ -37,10 +40,7 @@ func buildNode(v reflect.Value) *Node {
name = strings.ToLower(strings.Join(camelCase(ft.Name), "-")) name = strings.ToLower(strings.Join(camelCase(ft.Name), "-"))
} }
decoder := DecoderForField(ft) decoder := DecoderForField(ft)
help, ok := ft.Tag.Lookup("help") help, _ := ft.Tag.Lookup("help")
if !ok {
continue
}
dflt := ft.Tag.Get("default") dflt := ft.Tag.Get("default")
placeholder := ft.Tag.Get("placeholder") placeholder := ft.Tag.Get("placeholder")
if placeholder == "" { if placeholder == "" {
@@ -55,12 +55,15 @@ func buildNode(v reflect.Value) *Node {
_, optional := ft.Tag.Lookup("optional") _, optional := ft.Tag.Lookup("optional")
// Force field to be an argument, not a flag. // Force field to be an argument, not a flag.
_, arg := ft.Tag.Lookup("arg") _, arg := ft.Tag.Lookup("arg")
if !cmd {
_, cmd = ft.Tag.Lookup("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 && decoder == nil { if ft.Type.Kind() == reflect.Struct && (cmd || arg) {
child := buildNode(fv) child := buildNode(fv, false)
child.Help = help child.Help = 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
+24 -23
View File
@@ -17,11 +17,11 @@ func TestArgumentSequence(t *testing.T) {
var cli struct { var cli struct {
User struct { User struct {
Create struct { Create struct {
ID int `arg:"" help:""` ID int `arg:""`
First string `arg:"" help:""` First string `arg:""`
Last string `arg:"" help:""` Last string `arg:""`
} `help:""` } `cmd:""`
} `help:""` } `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"})
@@ -39,21 +39,21 @@ func TestBranchingArgument(t *testing.T) {
var cli struct { var cli struct {
User struct { User struct {
Create struct { Create struct {
ID string `arg:"" help:""` ID string `arg:""`
First string `arg:"" help:""` First string `arg:""`
Last string `arg:"" help:""` Last string `arg:""`
} `help:""` } `cmd:""`
// Branching argument. // Branching argument.
ID struct { ID struct {
ID int `arg:"" help:""` ID int `arg:""`
Flag int `help:""` Flag int
Delete struct{} `help:""` Delete struct{} `cmd:""`
Rename struct { Rename struct {
To string `help:""` To string
} `help:""` } `cmd:""`
} `arg:"" help:""` } `arg:""`
} `help:"Manage users."` } `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"})
@@ -64,8 +64,8 @@ func TestBranchingArgument(t *testing.T) {
func TestResetWithDefaults(t *testing.T) { func TestResetWithDefaults(t *testing.T) {
var cli struct { var cli struct {
Flag string `help:""` Flag string
FlagWithDefault string `default:"default" help:""` FlagWithDefault string `default:"default" `
} }
cli.Flag = "BLAH" cli.Flag = "BLAH"
cli.FlagWithDefault = "BLAH" cli.FlagWithDefault = "BLAH"
@@ -78,7 +78,7 @@ func TestResetWithDefaults(t *testing.T) {
func TestFlagSlice(t *testing.T) { func TestFlagSlice(t *testing.T) {
var cli struct { var cli struct {
Slice []int `help:""` Slice []int
} }
parser := mustNew(t, &cli) parser := mustNew(t, &cli)
_, err := parser.Parse([]string{"--slice=1,2,3"}) _, err := parser.Parse([]string{"--slice=1,2,3"})
@@ -88,8 +88,8 @@ func TestFlagSlice(t *testing.T) {
func TestArgSlice(t *testing.T) { func TestArgSlice(t *testing.T) {
var cli struct { var cli struct {
Slice []int `help:"" arg:""` Slice []int `arg:""`
Flag bool `help:""` Flag bool
} }
parser := mustNew(t, &cli) parser := mustNew(t, &cli)
_, err := parser.Parse([]string{"1", "2", "3", "--flag"}) _, err := parser.Parse([]string{"1", "2", "3", "--flag"})
@@ -100,7 +100,8 @@ func TestArgSlice(t *testing.T) {
func TestUnsupportedfieldErrors(t *testing.T) { func TestUnsupportedfieldErrors(t *testing.T) {
var cli struct { var cli struct {
Keys map[string]string `help:""` Keys map[string]string
} }
require.Panics(t, func() { mustNew(t, &cli) }) _, err := New("", "", &cli)
require.Error(t, err)
} }
+1
View File
@@ -20,6 +20,7 @@ type Node struct {
Children []*Branch Children []*Branch
} }
// A Value is either a flag or a variaable positional argument.
type Value struct { type Value struct {
Flag bool // True if flag, false if positional argument. Flag bool // True if flag, false if positional argument.
Name string Name string