Merge pull request #38 from alexflint/version_string

Add support for version strings
This commit is contained in:
Alex Flint 2016-09-13 18:47:19 -07:00 committed by GitHub
commit e6fdb157e9
4 changed files with 90 additions and 11 deletions

View File

@ -166,6 +166,28 @@ usage: samples [--foo FOO] [--bar BAR]
error: you must provide one of --foo and --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 ### Custom parsing
You can implement your own argument parser by implementing `encoding.TextUnmarshaler`: You can implement your own argument parser by implementing `encoding.TextUnmarshaler`:

View File

@ -27,6 +27,9 @@ type spec struct {
// ErrHelp indicates that -h or --help were provided // ErrHelp indicates that -h or --help were provided
var ErrHelp = errors.New("help requested by user") 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 // MustParse processes command line arguments and exits upon failure
func MustParse(dest ...interface{}) *Parser { func MustParse(dest ...interface{}) *Parser {
p, err := NewParser(Config{}, dest...) p, err := NewParser(Config{}, dest...)
@ -39,6 +42,10 @@ func MustParse(dest ...interface{}) *Parser {
p.WriteHelp(os.Stdout) p.WriteHelp(os.Stdout)
os.Exit(0) os.Exit(0)
} }
if err == ErrVersion {
fmt.Println(p.version)
os.Exit(0)
}
if err != nil { if err != nil {
p.Fail(err.Error()) p.Fail(err.Error())
} }
@ -63,12 +70,26 @@ type Config struct {
type Parser struct { type Parser struct {
spec []*spec spec []*spec
config Config 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 // NewParser constructs a parser from a list of destination structs
func NewParser(config Config, dests ...interface{}) (*Parser, error) { func NewParser(config Config, dests ...interface{}) (*Parser, error) {
var specs []*spec p := Parser{
config: config,
}
for _, dest := range dests { for _, dest := range dests {
if dest, ok := dest.(Versioned); ok {
p.version = dest.Version()
}
v := reflect.ValueOf(dest) v := reflect.ValueOf(dest)
if v.Kind() != reflect.Ptr { if v.Kind() != reflect.Ptr {
panic(fmt.Sprintf("%s is not a pointer (did you forget an ampersand?)", v.Type())) 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 == "" { if p.config.Program == "" {
config.Program = "program" p.config.Program = "program"
if len(os.Args) > 0 { if len(os.Args) > 0 {
config.Program = filepath.Base(os.Args[0]) p.config.Program = filepath.Base(os.Args[0])
} }
} }
return &Parser{ return &p, nil
spec: specs,
config: config,
}, nil
} }
// Parse processes the given command line option, storing the results in the field // 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" { if arg == "-h" || arg == "--help" {
return ErrHelp return ErrHelp
} }
if arg == "--version" {
return ErrVersion
}
if arg == "--" { if arg == "--" {
break break
} }

View File

@ -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) fmt.Fprintf(w, "usage: %s", p.config.Program)
// write the option component of the usage message // 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 // write the list of built in options
printOption(w, &spec{boolean: true, long: "help", short: "h", help: "display this help and exit"}) 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) { func printOption(w io.Writer, spec *spec) {

View File

@ -100,3 +100,32 @@ options:
p.WriteHelp(&help) p.WriteHelp(&help)
assert.Equal(t, expectedHelp, help.String()) 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()
}
}