Merge pull request #38 from alexflint/version_string
Add support for version strings
This commit is contained in:
commit
e6fdb157e9
22
README.md
22
README.md
|
@ -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`:
|
||||||
|
|
43
parse.go
43
parse.go
|
@ -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())
|
||||||
}
|
}
|
||||||
|
@ -61,14 +68,28 @@ type Config struct {
|
||||||
|
|
||||||
// Parser represents a set of command line options with destination values
|
// Parser represents a set of command line options with destination values
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
7
usage.go
7
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)
|
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) {
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue