diff --git a/README.md b/README.md index bc761de..8b6f3d8 100644 --- a/README.md +++ b/README.md @@ -263,6 +263,33 @@ usage: example [--name NAME] error: error processing --name: missing period in "oops" ``` +### Description strings + +```go +type args struct { + Foo string +} + +func (args) Description() string { + return "this program does this and that" +} + +func main() { + var args args + arg.MustParse(&args) +} +``` + +```shell +$ ./example -h +this program does this and that +usage: example [--foo FOO] + +options: + --foo FOO + --help, -h display this help and exit +``` + ### Documentation https://godoc.org/github.com/alexflint/go-arg diff --git a/parse.go b/parse.go index 26b530a..3cd00aa 100644 --- a/parse.go +++ b/parse.go @@ -67,9 +67,10 @@ type Config struct { // Parser represents a set of command line options with destination values type Parser struct { - spec []*spec - config Config - version string + spec []*spec + config Config + version string + description string } // Versioned is the interface that the destination struct should implement to @@ -80,6 +81,14 @@ type Versioned interface { Version() string } +// Described is the interface that the destination struct should implement to +// make a description string appear at the top of the help message. +type Described interface { + // Description returns the string that will be printed on a line by itself + // at the top of the help message. + Description() string +} + // walkFields calls a function for each field of a struct, recursively expanding struct fields. func walkFields(v reflect.Value, visit func(field reflect.StructField, val reflect.Value, owner reflect.Type) bool) { t := v.Type() @@ -102,6 +111,9 @@ func NewParser(config Config, dests ...interface{}) (*Parser, error) { if dest, ok := dest.(Versioned); ok { p.version = dest.Version() } + if dest, ok := dest.(Described); ok { + p.description = dest.Description() + } v := reflect.ValueOf(dest) if v.Kind() != reflect.Ptr { panic(fmt.Sprintf("%s is not a pointer (did you forget an ampersand?)", v.Type())) diff --git a/usage.go b/usage.go index f096f58..c31a428 100644 --- a/usage.go +++ b/usage.go @@ -29,6 +29,9 @@ func (p *Parser) WriteUsage(w io.Writer) { } } + if p.description != "" { + fmt.Fprintln(w, p.description) + } if p.version != "" { fmt.Fprintln(w, p.version) } diff --git a/usage_test.go b/usage_test.go index e60efdb..29a952c 100644 --- a/usage_test.go +++ b/usage_test.go @@ -129,3 +129,31 @@ options: t.Fail() } } + +type described struct{} + +// Described returns the description for this program +func (described) Description() string { + return "this program does this and that" +} + +func TestUsageWithDescription(t *testing.T) { + expectedHelp := `this program does this and that +usage: example + +options: + --help, -h display this help and exit +` + os.Args[0] = "example" + p, err := NewParser(Config{}, &described{}) + require.NoError(t, err) + + var help bytes.Buffer + p.WriteHelp(&help) + actual := help.String() + t.Logf("Expected:\n%s", expectedHelp) + t.Logf("Actual:\n%s", actual) + if expectedHelp != actual { + t.Fail() + } +}