store default values during NewParser
This commit is contained in:
parent
5d3ebcceee
commit
cc768447a7
30
parse.go
30
parse.go
|
@ -1,6 +1,7 @@
|
||||||
package arg
|
package arg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding"
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -193,6 +194,22 @@ func NewParser(config Config, dests ...interface{}) (*Parser, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add nonzero field values as defaults
|
||||||
|
for _, spec := range cmd.specs {
|
||||||
|
if v := p.val(spec.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: %w", spec.dest, err)
|
||||||
|
}
|
||||||
|
spec.defaultVal = string(str)
|
||||||
|
} else {
|
||||||
|
spec.defaultVal = fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
p.cmd.specs = append(p.cmd.specs, cmd.specs...)
|
p.cmd.specs = append(p.cmd.specs, cmd.specs...)
|
||||||
p.cmd.subcommands = append(p.cmd.subcommands, cmd.subcommands...)
|
p.cmd.subcommands = append(p.cmd.subcommands, cmd.subcommands...)
|
||||||
|
|
||||||
|
@ -706,3 +723,16 @@ func findSubcommand(cmds []*command, name string) *command {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isZero returns true if v contains the zero value for its type
|
||||||
|
func isZero(v reflect.Value) bool {
|
||||||
|
t := v.Type()
|
||||||
|
if t.Kind() == reflect.Slice {
|
||||||
|
return v.IsNil()
|
||||||
|
}
|
||||||
|
if !t.Comparable() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
z := reflect.Zero(t)
|
||||||
|
return v.Interface() == z.Interface()
|
||||||
|
}
|
||||||
|
|
|
@ -1083,6 +1083,15 @@ func TestDefaultOptionValues(t *testing.T) {
|
||||||
assert.True(t, args.G)
|
assert.True(t, args.G)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDefaultUnparseable(t *testing.T) {
|
||||||
|
var args struct {
|
||||||
|
A int `default:"x"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := parse("", &args)
|
||||||
|
assert.EqualError(t, err, `error processing default value for --a: strconv.ParseInt: parsing "x": invalid syntax`)
|
||||||
|
}
|
||||||
|
|
||||||
func TestDefaultPositionalValues(t *testing.T) {
|
func TestDefaultPositionalValues(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
A int `arg:"positional" default:"123"`
|
A int `arg:"positional" default:"123"`
|
||||||
|
|
36
usage.go
36
usage.go
|
@ -1,11 +1,9 @@
|
||||||
package arg
|
package arg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -94,7 +92,7 @@ func (p *Parser) writeUsageForCommand(w io.Writer, cmd *command) {
|
||||||
fmt.Fprint(w, "\n")
|
fmt.Fprint(w, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func printTwoCols(w io.Writer, left, help string, defaultVal *string) {
|
func printTwoCols(w io.Writer, left, help string, defaultVal string) {
|
||||||
lhs := " " + left
|
lhs := " " + left
|
||||||
fmt.Fprint(w, lhs)
|
fmt.Fprint(w, lhs)
|
||||||
if help != "" {
|
if help != "" {
|
||||||
|
@ -105,8 +103,8 @@ func printTwoCols(w io.Writer, left, help string, defaultVal *string) {
|
||||||
}
|
}
|
||||||
fmt.Fprint(w, help)
|
fmt.Fprint(w, help)
|
||||||
}
|
}
|
||||||
if defaultVal != nil {
|
if defaultVal != "" {
|
||||||
fmt.Fprintf(w, " [default: %s]", *defaultVal)
|
fmt.Fprintf(w, " [default: %s]", defaultVal)
|
||||||
}
|
}
|
||||||
fmt.Fprint(w, "\n")
|
fmt.Fprint(w, "\n")
|
||||||
}
|
}
|
||||||
|
@ -136,7 +134,7 @@ func (p *Parser) writeHelpForCommand(w io.Writer, cmd *command) {
|
||||||
if len(positionals) > 0 {
|
if len(positionals) > 0 {
|
||||||
fmt.Fprint(w, "\nPositional arguments:\n")
|
fmt.Fprint(w, "\nPositional arguments:\n")
|
||||||
for _, spec := range positionals {
|
for _, spec := range positionals {
|
||||||
printTwoCols(w, strings.ToUpper(spec.long), spec.help, nil)
|
printTwoCols(w, strings.ToUpper(spec.long), spec.help, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,7 +163,7 @@ func (p *Parser) writeHelpForCommand(w io.Writer, cmd *command) {
|
||||||
if len(cmd.subcommands) > 0 {
|
if len(cmd.subcommands) > 0 {
|
||||||
fmt.Fprint(w, "\nCommands:\n")
|
fmt.Fprint(w, "\nCommands:\n")
|
||||||
for _, subcmd := range cmd.subcommands {
|
for _, subcmd := range cmd.subcommands {
|
||||||
printTwoCols(w, subcmd.name, subcmd.help, nil)
|
printTwoCols(w, subcmd.name, subcmd.help, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,29 +173,7 @@ func (p *Parser) printOption(w io.Writer, spec *spec) {
|
||||||
if spec.short != "" {
|
if spec.short != "" {
|
||||||
left += ", " + synopsis(spec, "-"+spec.short)
|
left += ", " + synopsis(spec, "-"+spec.short)
|
||||||
}
|
}
|
||||||
|
printTwoCols(w, left, spec.help, spec.defaultVal)
|
||||||
// If spec.dest is not the zero value then a default value has been added.
|
|
||||||
var v reflect.Value
|
|
||||||
if len(spec.dest.fields) > 0 {
|
|
||||||
v = p.val(spec.dest)
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultVal *string
|
|
||||||
if v.IsValid() {
|
|
||||||
z := reflect.Zero(v.Type())
|
|
||||||
if (v.Type().Comparable() && z.Type().Comparable() && v.Interface() != z.Interface()) || v.Kind() == reflect.Slice && !v.IsNil() {
|
|
||||||
if scalar, ok := v.Interface().(encoding.TextMarshaler); ok {
|
|
||||||
if value, err := scalar.MarshalText(); err != nil {
|
|
||||||
defaultVal = ptrTo(fmt.Sprintf("error: %v", err))
|
|
||||||
} else {
|
|
||||||
defaultVal = ptrTo(fmt.Sprintf("%v", string(value)))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
defaultVal = ptrTo(fmt.Sprintf("%v", v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
printTwoCols(w, left, spec.help, defaultVal)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func synopsis(spec *spec, form string) string {
|
func synopsis(spec *spec, form string) string {
|
||||||
|
|
|
@ -96,26 +96,37 @@ func (n *MyEnum) MarshalText() ([]byte, error) {
|
||||||
return nil, errors.New("There was a problem")
|
return nil, errors.New("There was a problem")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUsageError(t *testing.T) {
|
func TestUsageWithDefaults(t *testing.T) {
|
||||||
expectedHelp := `Usage: example [--name NAME]
|
expectedHelp := `Usage: example [--label LABEL] [--content CONTENT]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--name NAME [default: error: There was a problem]
|
--label LABEL [default: cat]
|
||||||
|
--content CONTENT [default: dog]
|
||||||
--help, -h display this help and exit
|
--help, -h display this help and exit
|
||||||
`
|
`
|
||||||
|
var args struct {
|
||||||
|
Label string
|
||||||
|
Content string `default:"dog"`
|
||||||
|
}
|
||||||
|
args.Label = "cat"
|
||||||
|
p, err := NewParser(Config{"example"}, &args)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
args.Label = "should_ignore_this"
|
||||||
|
|
||||||
|
var help bytes.Buffer
|
||||||
|
p.WriteHelp(&help)
|
||||||
|
assert.Equal(t, expectedHelp, help.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsageCannotMarshalToString(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Name *MyEnum
|
Name *MyEnum
|
||||||
}
|
}
|
||||||
v := MyEnum(42)
|
v := MyEnum(42)
|
||||||
args.Name = &v
|
args.Name = &v
|
||||||
p, err := NewParser(Config{"example"}, &args)
|
_, err := NewParser(Config{"example"}, &args)
|
||||||
|
assert.EqualError(t, err, `args.Name: error marshaling default value to string: There was a problem`)
|
||||||
// NB: some might might expect there to be an error here
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var help bytes.Buffer
|
|
||||||
p.WriteHelp(&help)
|
|
||||||
assert.Equal(t, expectedHelp, help.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUsageLongPositionalWithHelp_legacyForm(t *testing.T) {
|
func TestUsageLongPositionalWithHelp_legacyForm(t *testing.T) {
|
||||||
|
|
Loading…
Reference in New Issue