changed NewParser to take options at the end rather than config at the front
This commit is contained in:
parent
a1e2b672ea
commit
4aea783023
|
@ -314,7 +314,7 @@ func Example_writeHelpForSubcommand() {
|
|||
osExit = func(int) {}
|
||||
stdout = os.Stdout
|
||||
|
||||
p, err := NewParser(Config{}, &args)
|
||||
p, err := NewParser(&args, WithProgramName("example"))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
@ -363,7 +363,7 @@ func Example_writeHelpForSubcommandNested() {
|
|||
osExit = func(int) {}
|
||||
stdout = os.Stdout
|
||||
|
||||
p, err := NewParser(Config{}, &args)
|
||||
p, err := NewParser(&args, WithProgramName("example"))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
|
94
v2/parse.go
94
v2/parse.go
|
@ -1,7 +1,6 @@
|
|||
package arg
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -73,7 +72,7 @@ var ErrVersion = errors.New("version requested by user")
|
|||
|
||||
// MustParse processes command line arguments and exits upon failure
|
||||
func MustParse(dest interface{}) *Parser {
|
||||
p, err := NewParser(Config{}, dest)
|
||||
p, err := NewParser(dest)
|
||||
if err != nil {
|
||||
fmt.Fprintln(stdout, err)
|
||||
osExit(-1)
|
||||
|
@ -96,25 +95,18 @@ func MustParse(dest interface{}) *Parser {
|
|||
}
|
||||
|
||||
// Parse processes command line arguments and stores them in dest
|
||||
func Parse(dest interface{}) error {
|
||||
p, err := NewParser(Config{}, dest)
|
||||
func Parse(dest interface{}, options ...ParserOption) error {
|
||||
p, err := NewParser(dest, options...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return p.Parse(os.Args, os.Environ())
|
||||
}
|
||||
|
||||
// Config represents configuration options for an argument parser
|
||||
type Config struct {
|
||||
// Program is the name of the program used in the help text
|
||||
Program string
|
||||
}
|
||||
|
||||
// Parser represents a set of command line options with destination values
|
||||
type Parser struct {
|
||||
cmd *Command // the top-level command
|
||||
root reflect.Value // destination struct to fill will values
|
||||
config Config // configuration passed to NewParser
|
||||
version string // version from the argument struct
|
||||
prologue string // prologue for help text (from the argument struct)
|
||||
epilogue string // epilogue for help text (from the argument struct)
|
||||
|
@ -170,58 +162,58 @@ func walkFieldsImpl(t reflect.Type, visit func(field reflect.StructField, owner
|
|||
}
|
||||
}
|
||||
|
||||
// the ParserOption interface matches options for the parser constructor
|
||||
type ParserOption interface {
|
||||
parserOption()
|
||||
}
|
||||
|
||||
type programNameParserOption struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (programNameParserOption) parserOption() {}
|
||||
|
||||
// WithProgramName overrides the name of the program as displayed in help test
|
||||
func WithProgramName(name string) ParserOption {
|
||||
return programNameParserOption{s: name}
|
||||
}
|
||||
|
||||
// NewParser constructs a parser from a list of destination structs
|
||||
func NewParser(config Config, dest interface{}) (*Parser, error) {
|
||||
// first pick a name for the command for use in the usage text
|
||||
var name string
|
||||
switch {
|
||||
case config.Program != "":
|
||||
name = config.Program
|
||||
case len(os.Args) > 0:
|
||||
name = filepath.Base(os.Args[0])
|
||||
default:
|
||||
name = "program"
|
||||
}
|
||||
|
||||
// construct a parser
|
||||
p := Parser{
|
||||
cmd: &Command{name: name},
|
||||
config: config,
|
||||
seen: make(map[*Argument]bool),
|
||||
}
|
||||
|
||||
// make a list of roots
|
||||
p.root = reflect.ValueOf(dest)
|
||||
|
||||
// process each of the destination values
|
||||
func NewParser(dest interface{}, options ...ParserOption) (*Parser, error) {
|
||||
// check the destination type
|
||||
t := reflect.TypeOf(dest)
|
||||
if t.Kind() != reflect.Ptr {
|
||||
panic(fmt.Sprintf("%s is not a pointer (did you forget an ampersand?)", t))
|
||||
}
|
||||
|
||||
cmd, err := cmdFromStruct(name, path{}, t)
|
||||
// pick a program name for help text and usage output
|
||||
program := "program"
|
||||
if len(os.Args) > 0 {
|
||||
program = filepath.Base(os.Args[0])
|
||||
}
|
||||
|
||||
// apply the options
|
||||
for _, opt := range options {
|
||||
switch opt := opt.(type) {
|
||||
case programNameParserOption:
|
||||
program = opt.s
|
||||
}
|
||||
}
|
||||
|
||||
// build the root command from the struct
|
||||
cmd, err := cmdFromStruct(program, path{}, t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// add nonzero field values as defaults
|
||||
for _, arg := range cmd.args {
|
||||
if v := p.val(arg.dest); v.IsValid() && !isZero(v) {
|
||||
if defaultVal, ok := v.Interface().(encoding.TextMarshaler); ok {
|
||||
str, err := defaultVal.MarshalText()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: error marshaling default value to string: %v", arg.dest, err)
|
||||
}
|
||||
arg.defaultVal = string(str)
|
||||
} else {
|
||||
arg.defaultVal = fmt.Sprintf("%v", v)
|
||||
}
|
||||
}
|
||||
// construct the parser
|
||||
p := Parser{
|
||||
seen: make(map[*Argument]bool),
|
||||
root: reflect.ValueOf(dest),
|
||||
cmd: cmd,
|
||||
}
|
||||
|
||||
p.cmd.args = append(p.cmd.args, cmd.args...)
|
||||
p.cmd.subcommands = append(p.cmd.subcommands, cmd.subcommands...)
|
||||
|
||||
// check for version, prologue, and epilogue
|
||||
if dest, ok := dest.(Versioned); ok {
|
||||
p.version = dest.Version()
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ func pparse(cmdline string, dest interface{}) (*Parser, error) {
|
|||
}
|
||||
|
||||
func parseWithEnv(dest interface{}, cmdline string, env ...string) (*Parser, error) {
|
||||
p, err := NewParser(Config{}, dest)
|
||||
p, err := NewParser(dest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -813,7 +813,7 @@ func TestDefaultValuesIgnored(t *testing.T) {
|
|||
// just checking that default values are not automatically applied
|
||||
// in ProcessCommandLine or ProcessEnvironment
|
||||
|
||||
p, err := NewParser(Config{}, &args)
|
||||
p, err := NewParser(&args)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = p.ProcessCommandLine(nil)
|
||||
|
@ -1293,7 +1293,7 @@ func TestReuseParser(t *testing.T) {
|
|||
Foo string `arg:"required"`
|
||||
}
|
||||
|
||||
p, err := NewParser(Config{}, &args)
|
||||
p, err := NewParser(&args)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = p.Parse([]string{"program", "--foo=abc"}, nil)
|
||||
|
@ -1405,7 +1405,7 @@ func TestUnexportedFieldsSkipped(t *testing.T) {
|
|||
unexported struct{}
|
||||
}
|
||||
|
||||
_, err := NewParser(Config{}, &args)
|
||||
_, err := NewParser(&args)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ func TestSubcommandNotAPointer(t *testing.T) {
|
|||
var args struct {
|
||||
A string `arg:"subcommand"`
|
||||
}
|
||||
_, err := NewParser(Config{}, &args)
|
||||
_, err := NewParser(&args)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ func TestSubcommandNotAPointerToStruct(t *testing.T) {
|
|||
var args struct {
|
||||
A struct{} `arg:"subcommand"`
|
||||
}
|
||||
_, err := NewParser(Config{}, &args)
|
||||
_, err := NewParser(&args)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ func TestPositionalAndSubcommandNotAllowed(t *testing.T) {
|
|||
A string `arg:"positional"`
|
||||
B *struct{} `arg:"subcommand"`
|
||||
}
|
||||
_, err := NewParser(Config{}, &args)
|
||||
_, err := NewParser(&args)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ func TestSubcommandNamesBeforeParsing(t *testing.T) {
|
|||
var args struct {
|
||||
List *listCmd `arg:"subcommand"`
|
||||
}
|
||||
p, err := NewParser(Config{}, &args)
|
||||
p, err := NewParser(&args)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, p.Subcommand())
|
||||
assert.Nil(t, p.SubcommandNames())
|
||||
|
@ -400,7 +400,7 @@ func TestValForNilStruct(t *testing.T) {
|
|||
Sub *subcmd `arg:"subcommand"`
|
||||
}
|
||||
|
||||
p, err := NewParser(Config{}, &cmd)
|
||||
p, err := NewParser(&cmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
typ := reflect.TypeOf(cmd)
|
||||
|
|
|
@ -50,19 +50,19 @@ Options:
|
|||
--optimize OPTIMIZE, -O OPTIMIZE
|
||||
optimization level
|
||||
--ids IDS Ids
|
||||
--values VALUES Values [default: [3.14 42 256]]
|
||||
--values VALUES Values
|
||||
--workers WORKERS, -w WORKERS
|
||||
number of workers to start [default: 10, env: WORKERS]
|
||||
--testenv TESTENV, -a TESTENV [env: TEST_ENV]
|
||||
--file FILE, -f FILE File with mandatory extension [default: scratch.txt]
|
||||
--file FILE, -f FILE File with mandatory extension
|
||||
--help, -h display this help and exit
|
||||
`
|
||||
|
||||
var args struct {
|
||||
Input string `arg:"positional,required"`
|
||||
Output []string `arg:"positional" help:"list of outputs"`
|
||||
Name string `help:"name to use"`
|
||||
Value int `help:"secret value"`
|
||||
Name string `help:"name to use" default:"Foo Bar"`
|
||||
Value int `help:"secret value" default:"42"`
|
||||
Verbose bool `arg:"-v" help:"verbosity level"`
|
||||
Dataset string `help:"dataset to use"`
|
||||
Optimize int `arg:"-O" help:"optimization level"`
|
||||
|
@ -72,11 +72,7 @@ Options:
|
|||
TestEnv string `arg:"-a,env:TEST_ENV"`
|
||||
File *NameDotName `arg:"-f" help:"File with mandatory extension"`
|
||||
}
|
||||
args.Name = "Foo Bar"
|
||||
args.Value = 42
|
||||
args.Values = []float64{3.14, 42, 256}
|
||||
args.File = &NameDotName{"scratch", "txt"}
|
||||
p, err := NewParser(Config{Program: "example"}, &args)
|
||||
p, err := NewParser(&args, WithProgramName("example"))
|
||||
require.NoError(t, err)
|
||||
|
||||
os.Args[0] = "example"
|
||||
|
@ -112,11 +108,10 @@ Options:
|
|||
--help, -h display this help and exit
|
||||
`
|
||||
var args struct {
|
||||
Label string
|
||||
Label string `default:"cat"`
|
||||
Content string `default:"dog"`
|
||||
}
|
||||
args.Label = "cat"
|
||||
p, err := NewParser(Config{Program: "example"}, &args)
|
||||
p, err := NewParser(&args, WithProgramName("example"))
|
||||
require.NoError(t, err)
|
||||
|
||||
args.Label = "should_ignore_this"
|
||||
|
@ -130,16 +125,6 @@ Options:
|
|||
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
|
||||
}
|
||||
|
||||
func TestUsageCannotMarshalToString(t *testing.T) {
|
||||
var args struct {
|
||||
Name *MyEnum
|
||||
}
|
||||
v := MyEnum(42)
|
||||
args.Name = &v
|
||||
_, err := NewParser(Config{Program: "example"}, &args)
|
||||
assert.EqualError(t, err, `args.Name: error marshaling default value to string: There was a problem`)
|
||||
}
|
||||
|
||||
func TestUsageLongPositionalWithHelp_legacyForm(t *testing.T) {
|
||||
expectedUsage := "Usage: example [VERYLONGPOSITIONALWITHHELP]"
|
||||
|
||||
|
@ -157,7 +142,7 @@ Options:
|
|||
VeryLongPositionalWithHelp string `arg:"positional,help:this positional argument is very long but cannot include commas"`
|
||||
}
|
||||
|
||||
p, err := NewParser(Config{Program: "example"}, &args)
|
||||
p, err := NewParser(&args, WithProgramName("example"))
|
||||
require.NoError(t, err)
|
||||
|
||||
var help bytes.Buffer
|
||||
|
@ -186,7 +171,7 @@ Options:
|
|||
VeryLongPositionalWithHelp string `arg:"positional" help:"this positional argument is very long, and includes: commas, colons etc"`
|
||||
}
|
||||
|
||||
p, err := NewParser(Config{Program: "example"}, &args)
|
||||
p, err := NewParser(&args, WithProgramName("example"))
|
||||
require.NoError(t, err)
|
||||
|
||||
var help bytes.Buffer
|
||||
|
@ -207,10 +192,7 @@ Usage: myprogram
|
|||
Options:
|
||||
--help, -h display this help and exit
|
||||
`
|
||||
config := Config{
|
||||
Program: "myprogram",
|
||||
}
|
||||
p, err := NewParser(config, &struct{}{})
|
||||
p, err := NewParser(&struct{}{}, WithProgramName("myprogram"))
|
||||
require.NoError(t, err)
|
||||
|
||||
os.Args[0] = "example"
|
||||
|
@ -242,8 +224,7 @@ Options:
|
|||
--help, -h display this help and exit
|
||||
--version display version and exit
|
||||
`
|
||||
os.Args[0] = "example"
|
||||
p, err := NewParser(Config{}, &versioned{})
|
||||
p, err := NewParser(&versioned{}, WithProgramName("example"))
|
||||
require.NoError(t, err)
|
||||
|
||||
var help bytes.Buffer
|
||||
|
@ -272,8 +253,7 @@ Usage: example
|
|||
Options:
|
||||
--help, -h display this help and exit
|
||||
`
|
||||
os.Args[0] = "example"
|
||||
p, err := NewParser(Config{}, &described{})
|
||||
p, err := NewParser(&described{}, WithProgramName("example"))
|
||||
require.NoError(t, err)
|
||||
|
||||
var help bytes.Buffer
|
||||
|
@ -304,7 +284,7 @@ Options:
|
|||
For more information visit github.com/alexflint/go-arg
|
||||
`
|
||||
os.Args[0] = "example"
|
||||
p, err := NewParser(Config{}, &epilogued{})
|
||||
p, err := NewParser(&epilogued{}, WithProgramName("example"))
|
||||
require.NoError(t, err)
|
||||
|
||||
var help bytes.Buffer
|
||||
|
@ -323,7 +303,7 @@ func TestUsageForRequiredPositionals(t *testing.T) {
|
|||
Required2 string `arg:"positional,required"`
|
||||
}
|
||||
|
||||
p, err := NewParser(Config{Program: "example"}, &args)
|
||||
p, err := NewParser(&args, WithProgramName("example"))
|
||||
require.NoError(t, err)
|
||||
|
||||
var usage bytes.Buffer
|
||||
|
@ -340,7 +320,7 @@ func TestUsageForMixedPositionals(t *testing.T) {
|
|||
Optional2 string `arg:"positional"`
|
||||
}
|
||||
|
||||
p, err := NewParser(Config{Program: "example"}, &args)
|
||||
p, err := NewParser(&args, WithProgramName("example"))
|
||||
require.NoError(t, err)
|
||||
|
||||
var usage bytes.Buffer
|
||||
|
@ -356,7 +336,7 @@ func TestUsageForRepeatedPositionals(t *testing.T) {
|
|||
Repeated []string `arg:"positional,required"`
|
||||
}
|
||||
|
||||
p, err := NewParser(Config{Program: "example"}, &args)
|
||||
p, err := NewParser(&args, WithProgramName("example"))
|
||||
require.NoError(t, err)
|
||||
|
||||
var usage bytes.Buffer
|
||||
|
@ -374,7 +354,7 @@ func TestUsageForMixedAndRepeatedPositionals(t *testing.T) {
|
|||
Repeated []string `arg:"positional"`
|
||||
}
|
||||
|
||||
p, err := NewParser(Config{Program: "example"}, &args)
|
||||
p, err := NewParser(&args, WithProgramName("example"))
|
||||
require.NoError(t, err)
|
||||
|
||||
var usage bytes.Buffer
|
||||
|
@ -398,7 +378,7 @@ Options:
|
|||
RequiredMultiple []string `arg:"positional,required" help:"required multiple positional"`
|
||||
}
|
||||
|
||||
p, err := NewParser(Config{Program: "example"}, &args)
|
||||
p, err := NewParser(&args, WithProgramName("example"))
|
||||
require.NoError(t, err)
|
||||
|
||||
var help bytes.Buffer
|
||||
|
@ -440,7 +420,7 @@ Global options:
|
|||
}
|
||||
|
||||
os.Args[0] = "example"
|
||||
p, err := NewParser(Config{}, &args)
|
||||
p, err := NewParser(&args)
|
||||
require.NoError(t, err)
|
||||
|
||||
_ = p.Parse([]string{"child", "nested", "value"}, nil)
|
||||
|
@ -458,7 +438,7 @@ func TestNonexistentSubcommand(t *testing.T) {
|
|||
var args struct {
|
||||
sub *struct{} `arg:"subcommand"`
|
||||
}
|
||||
p, err := NewParser(Config{}, &args)
|
||||
p, err := NewParser(&args)
|
||||
require.NoError(t, err)
|
||||
|
||||
var b bytes.Buffer
|
||||
|
@ -497,7 +477,7 @@ Options:
|
|||
ShortOnly string `arg:"-a,--" help:"some help" default:"some val" placeholder:"PLACEHOLDER"`
|
||||
ShortOnly2 string `arg:"-b,--,required" help:"some help2"`
|
||||
}
|
||||
p, err := NewParser(Config{Program: "example"}, &args)
|
||||
p, err := NewParser(&args, WithProgramName("example"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
var help bytes.Buffer
|
||||
|
@ -524,7 +504,7 @@ Options:
|
|||
Dog string
|
||||
Cat string `arg:"-c,--"`
|
||||
}
|
||||
p, err := NewParser(Config{Program: "example"}, &args)
|
||||
p, err := NewParser(&args, WithProgramName("example"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
var help bytes.Buffer
|
||||
|
@ -552,7 +532,7 @@ Options:
|
|||
EnvOnlyOverriden string `arg:"--,env:CUSTOM"`
|
||||
}
|
||||
|
||||
p, err := NewParser(Config{Program: "example"}, &args)
|
||||
p, err := NewParser(&args, WithProgramName("example"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
var help bytes.Buffer
|
||||
|
@ -586,7 +566,7 @@ error: something went wrong
|
|||
var args struct {
|
||||
Foo int
|
||||
}
|
||||
p, err := NewParser(Config{Program: "example"}, &args)
|
||||
p, err := NewParser(&args, WithProgramName("example"))
|
||||
require.NoError(t, err)
|
||||
p.Fail("something went wrong")
|
||||
|
||||
|
@ -616,7 +596,7 @@ error: something went wrong
|
|||
var args struct {
|
||||
Sub *struct{} `arg:"subcommand"`
|
||||
}
|
||||
p, err := NewParser(Config{Program: "example"}, &args)
|
||||
p, err := NewParser(&args, WithProgramName("example"))
|
||||
require.NoError(t, err)
|
||||
|
||||
err = p.FailSubcommand("something went wrong", "sub")
|
||||
|
|
Loading…
Reference in New Issue