diff --git a/README.md b/README.md index 18dc556..e8b62a4 100644 --- a/README.md +++ b/README.md @@ -265,15 +265,33 @@ func (n *NameDotName) UnmarshalText(b []byte) error { return nil } +// optional: implement in case you want to display a default value in the usage string +func (n *NameDotName) MarshalText() (text []byte, err error) { + text = []byte(fmt.Sprintf("%s.%s", n.Head, n.Tail)) + return +} + func main() { var args struct { Name *NameDotName } + // set default + args.Name = &NameDotName{"file", "txt"} arg.MustParse(&args) fmt.Printf("%#v\n", args.Name) } ``` ```shell +$ ./example --help +Usage: test [--name NAME] + +Options: + --name NAME [default: file.txt] + --help, -h display this help and exit + +$ ./example +&main.NameDotName{Head:"file", Tail:"txt"} + $ ./example --name=foo.bar &main.NameDotName{Head:"foo", Tail:"bar"} diff --git a/usage.go b/usage.go index 4652b36..656ee9a 100644 --- a/usage.go +++ b/usage.go @@ -6,6 +6,7 @@ import ( "os" "reflect" "strings" + "encoding" ) // the width of the left column @@ -134,7 +135,15 @@ func printOption(w io.Writer, spec *spec) { 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() { - fmt.Fprintf(w, " [default: %v]", v) + if scalar, ok := v.Interface().(encoding.TextMarshaler); ok { + if value, err := scalar.MarshalText(); err != nil { + fmt.Fprintf(w, " [default: error: %v]", err) + } else { + fmt.Fprintf(w, " [default: %v]", string(value)) + } + } else { + fmt.Fprintf(w, " [default: %v]", v) + } } } fmt.Fprint(w, "\n") diff --git a/usage_test.go b/usage_test.go index 940bf40..4f179f0 100644 --- a/usage_test.go +++ b/usage_test.go @@ -7,12 +7,35 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "strings" + "fmt" + "errors" ) +type NameDotName struct { + Head, Tail string +} + +func (n *NameDotName) UnmarshalText(b []byte) error { + s := string(b) + pos := strings.Index(s, ".") + if pos == -1 { + return fmt.Errorf("missing period in %s", s) + } + n.Head = s[:pos] + n.Tail = s[pos+1:] + return nil +} + +func (n *NameDotName) MarshalText() (text []byte, err error) { + text = []byte(fmt.Sprintf("%s.%s", n.Head, n.Tail)) + return +} + func TestWriteUsage(t *testing.T) { - expectedUsage := "Usage: example [--name NAME] [--value VALUE] [--verbose] [--dataset DATASET] [--optimize OPTIMIZE] [--ids IDS] [--values VALUES] [--workers WORKERS] INPUT [OUTPUT [OUTPUT ...]]\n" + expectedUsage := "Usage: example [--name NAME] [--value VALUE] [--verbose] [--dataset DATASET] [--optimize OPTIMIZE] [--ids IDS] [--values VALUES] [--workers WORKERS] [--file FILE] INPUT [OUTPUT [OUTPUT ...]]\n" - expectedHelp := `Usage: example [--name NAME] [--value VALUE] [--verbose] [--dataset DATASET] [--optimize OPTIMIZE] [--ids IDS] [--values VALUES] [--workers WORKERS] INPUT [OUTPUT [OUTPUT ...]] + expectedHelp := `Usage: example [--name NAME] [--value VALUE] [--verbose] [--dataset DATASET] [--optimize OPTIMIZE] [--ids IDS] [--values VALUES] [--workers WORKERS] [--file FILE] INPUT [OUTPUT [OUTPUT ...]] Positional arguments: INPUT @@ -29,6 +52,7 @@ Options: --values VALUES Values [default: [3.14 42 256]] --workers WORKERS, -w WORKERS number of workers to start + --file FILE, -f FILE File with mandatory extension [default: scratch.txt] --help, -h display this help and exit ` var args struct { @@ -42,11 +66,13 @@ Options: Ids []int64 `help:"Ids"` Values []float64 `help:"Values"` Workers int `arg:"-w,env:WORKERS" help:"number of workers to start"` + File *NameDotName `arg:"-f" help:"File with mandatory extension"` } args.Name = "Foo Bar" args.Value = 42 args.Values = []float64{3.14, 42, 256} - p, err := NewParser(Config{}, &args) + args.File = &NameDotName{"scratch", "txt"} + p, err := NewParser(Config{"example"}, &args) require.NoError(t, err) os.Args[0] = "example" @@ -60,6 +86,42 @@ Options: assert.Equal(t, expectedHelp, help.String()) } +type MyEnum int + +func (n *MyEnum) UnmarshalText(b []byte) error { + b = []byte("Hello") + return nil +} + +func (n *MyEnum) MarshalText() (text []byte, err error) { + s := "There was a problem" + text = []byte(s) + err = errors.New(s) + return +} + +func TestUsageError(t *testing.T) { + expectedHelp := `Usage: example [--name NAME] + +Options: + --name NAME [default: error: There was a problem] + --help, -h display this help and exit +` + var args struct { + Name *MyEnum + } + v := MyEnum(42) + args.Name = &v + p, err := NewParser(Config{"example"}, &args) + + // 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) { expectedHelp := `Usage: example VERYLONGPOSITIONALWITHHELP