diff --git a/README.md b/README.md index 66a9b46..4aa311e 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,28 @@ usage: samples [--foo FOO] [--bar BAR] error: you must provide one of --foo and --bar ``` +### Version strings + +```go +type args struct { + ... +} + +func (args) Version() string { + return "someprogram 4.3.0" +} + +func main() { + var args args + arg.MustParse(&args) +} +``` + +```shell +$ ./example --version +someprogram 4.3.0 +``` + ### Custom parsing You can implement your own argument parser by implementing `encoding.TextUnmarshaler`: diff --git a/parse.go b/parse.go index b1193d0..f5fdd7f 100644 --- a/parse.go +++ b/parse.go @@ -27,6 +27,9 @@ type spec struct { // ErrHelp indicates that -h or --help were provided var ErrHelp = errors.New("help requested by user") +// ErrVersion indicates that --version was provided +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...) @@ -39,6 +42,10 @@ func MustParse(dest ...interface{}) *Parser { p.WriteHelp(os.Stdout) os.Exit(0) } + if err == ErrVersion { + fmt.Println(p.version) + os.Exit(0) + } if err != nil { p.Fail(err.Error()) } @@ -61,14 +68,28 @@ type Config struct { // Parser represents a set of command line options with destination values type Parser struct { - spec []*spec - config Config + spec []*spec + config Config + version string +} + +// Versioned is the interface that the destination struct should implement to +// make a version string appear at the top of the help message. +type Versioned interface { + // Version returns the version string that will be printed on a line by itself + // at the top of the help message. + Version() string } // NewParser constructs a parser from a list of destination structs func NewParser(config Config, dests ...interface{}) (*Parser, error) { - var specs []*spec + p := Parser{ + config: config, + } for _, dest := range dests { + if dest, ok := dest.(Versioned); ok { + p.version = dest.Version() + } v := reflect.ValueOf(dest) if v.Kind() != reflect.Ptr { panic(fmt.Sprintf("%s is not a pointer (did you forget an ampersand?)", v.Type())) @@ -138,19 +159,16 @@ func NewParser(config Config, dests ...interface{}) (*Parser, error) { } } } - specs = append(specs, &spec) + p.spec = append(p.spec, &spec) } } - if config.Program == "" { - config.Program = "program" + if p.config.Program == "" { + p.config.Program = "program" if len(os.Args) > 0 { - config.Program = filepath.Base(os.Args[0]) + p.config.Program = filepath.Base(os.Args[0]) } } - return &Parser{ - spec: specs, - config: config, - }, nil + return &p, nil } // Parse processes the given command line option, storing the results in the field @@ -161,6 +179,9 @@ func (p *Parser) Parse(args []string) error { if arg == "-h" || arg == "--help" { return ErrHelp } + if arg == "--version" { + return ErrVersion + } if arg == "--" { break } diff --git a/usage.go b/usage.go index 2ee3953..f096f58 100644 --- a/usage.go +++ b/usage.go @@ -29,6 +29,10 @@ func (p *Parser) WriteUsage(w io.Writer) { } } + if p.version != "" { + fmt.Fprintln(w, p.version) + } + fmt.Fprintf(w, "usage: %s", p.config.Program) // write the option component of the usage message @@ -97,6 +101,9 @@ func (p *Parser) WriteHelp(w io.Writer) { // write the list of built in options printOption(w, &spec{boolean: true, long: "help", short: "h", help: "display this help and exit"}) + if p.version != "" { + printOption(w, &spec{boolean: true, long: "version", help: "display version and exit"}) + } } func printOption(w io.Writer, spec *spec) { diff --git a/usage_test.go b/usage_test.go index b63a7d0..e60efdb 100644 --- a/usage_test.go +++ b/usage_test.go @@ -100,3 +100,32 @@ options: p.WriteHelp(&help) assert.Equal(t, expectedHelp, help.String()) } + +type versioned struct{} + +// Version returns the version for this program +func (versioned) Version() string { + return "example 3.2.1" +} + +func TestUsageWithVersion(t *testing.T) { + expectedHelp := `example 3.2.1 +usage: example + +options: + --help, -h display this help and exit + --version display version and exit +` + os.Args[0] = "example" + p, err := NewParser(Config{}, &versioned{}) + 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() + } +}