Просмотр исходного кода

Pull in go-flags, modified to build on Solaris

Jakob Borg 12 лет назад
Родитель
Сommit
cd2040a7d2
40 измененных файлов с 4835 добавлено и 1 удалено
  1. 26 0
      github.com/jessevdk/go-flags/LICENSE
  2. 128 0
      github.com/jessevdk/go-flags/README.md
  3. 82 0
      github.com/jessevdk/go-flags/assert_test.go
  4. 16 0
      github.com/jessevdk/go-flags/check_crosscompile.sh
  5. 61 0
      github.com/jessevdk/go-flags/closest.go
  6. 84 0
      github.com/jessevdk/go-flags/command.go
  7. 161 0
      github.com/jessevdk/go-flags/command_private.go
  8. 255 0
      github.com/jessevdk/go-flags/command_test.go
  9. 315 0
      github.com/jessevdk/go-flags/convert.go
  10. 113 0
      github.com/jessevdk/go-flags/error.go
  11. 95 0
      github.com/jessevdk/go-flags/example_test.go
  12. 23 0
      github.com/jessevdk/go-flags/examples/add.go
  13. 75 0
      github.com/jessevdk/go-flags/examples/main.go
  14. 23 0
      github.com/jessevdk/go-flags/examples/rm.go
  15. 141 0
      github.com/jessevdk/go-flags/flags.go
  16. 80 0
      github.com/jessevdk/go-flags/group.go
  17. 263 0
      github.com/jessevdk/go-flags/group_private.go
  18. 160 0
      github.com/jessevdk/go-flags/group_test.go
  19. 275 0
      github.com/jessevdk/go-flags/help.go
  20. 153 0
      github.com/jessevdk/go-flags/help_test.go
  21. 146 0
      github.com/jessevdk/go-flags/ini.go
  22. 333 0
      github.com/jessevdk/go-flags/ini_private.go
  23. 170 0
      github.com/jessevdk/go-flags/ini_test.go
  24. 85 0
      github.com/jessevdk/go-flags/long_test.go
  25. 134 0
      github.com/jessevdk/go-flags/man.go
  26. 78 0
      github.com/jessevdk/go-flags/marshal_test.go
  27. 140 0
      github.com/jessevdk/go-flags/multitag.go
  28. 95 0
      github.com/jessevdk/go-flags/option.go
  29. 125 0
      github.com/jessevdk/go-flags/option_private.go
  30. 45 0
      github.com/jessevdk/go-flags/options_test.go
  31. 54 0
      github.com/jessevdk/go-flags/optstyle_other.go
  32. 85 0
      github.com/jessevdk/go-flags/optstyle_windows.go
  33. 212 0
      github.com/jessevdk/go-flags/parser.go
  34. 243 0
      github.com/jessevdk/go-flags/parser_private.go
  35. 81 0
      github.com/jessevdk/go-flags/pointer_test.go
  36. 169 0
      github.com/jessevdk/go-flags/short_test.go
  37. 39 0
      github.com/jessevdk/go-flags/tag_test.go
  38. 5 0
      github.com/jessevdk/go-flags/termsize.go
  39. 66 0
      github.com/jessevdk/go-flags/unknown_test.go
  40. 1 1
      main.go

+ 26 - 0
github.com/jessevdk/go-flags/LICENSE

@@ -0,0 +1,26 @@
+Copyright (c) 2012 Jesse van den Kieboom. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+     notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+     copyright notice, this list of conditions and the following disclaimer
+     in the documentation and/or other materials provided with the
+     distribution.
+   * Neither the name of Google Inc. nor the names of its
+     contributors may be used to endorse or promote products derived from
+     this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 128 - 0
github.com/jessevdk/go-flags/README.md

@@ -0,0 +1,128 @@
+go-flags: a go library for parsing command line arguments
+=========================================================
+
+This library provides similar functionality to the builtin flag library of
+go, but provides much more functionality and nicer formatting. From the
+documentation:
+
+Package flags provides an extensive command line option parser.
+The flags package is similar in functionality to the go builtin flag package
+but provides more options and uses reflection to provide a convenient and
+succinct way of specifying command line options.
+
+Supported features:
+* Options with short names (-v)
+* Options with long names (--verbose)
+* Options with and without arguments (bool v.s. other type)
+* Options with optional arguments and default values
+* Multiple option groups each containing a set of options
+* Generate and print well-formatted help message
+* Passing remaining command line arguments after -- (optional)
+* Ignoring unknown command line options (optional)
+* Supports -I/usr/include -I=/usr/include -I /usr/include option argument specification
+* Supports multiple short options -aux
+* Supports all primitive go types (string, int{8..64}, uint{8..64}, float)
+* Supports same option multiple times (can store in slice or last option counts)
+* Supports maps
+* Supports function callbacks
+
+The flags package uses structs, reflection and struct field tags
+to allow users to specify command line options. This results in very simple
+and consise specification of your application options. For example:
+
+    type Options struct {
+        Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
+    }
+
+This specifies one option with a short name -v and a long name --verbose.
+When either -v or --verbose is found on the command line, a 'true' value
+will be appended to the Verbose field. e.g. when specifying -vvv, the
+resulting value of Verbose will be {[true, true, true]}.
+
+Example:
+--------
+	var opts struct {
+		// Slice of bool will append 'true' each time the option
+		// is encountered (can be set multiple times, like -vvv)
+		Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
+
+		// Example of automatic marshalling to desired type (uint)
+		Offset uint `long:"offset" description:"Offset"`
+
+		// Example of a callback, called each time the option is found.
+		Call func(string) `short:"c" description:"Call phone number"`
+
+		// Example of a required flag
+		Name string `short:"n" long:"name" description:"A name" required:"true"`
+
+		// Example of a value name
+		File string `short:"f" long:"file" description:"A file" value-name:"FILE"`
+
+		// Example of a pointer
+		Ptr *int `short:"p" description:"A pointer to an integer"`
+
+		// Example of a slice of strings
+		StringSlice []string `short:"s" description:"A slice of strings"`
+
+		// Example of a slice of pointers
+		PtrSlice []*string `long:"ptrslice" description:"A slice of pointers to string"`
+
+		// Example of a map
+		IntMap map[string]int `long:"intmap" description:"A map from string to int"`
+	}
+
+	// Callback which will invoke callto:<argument> to call a number.
+	// Note that this works just on OS X (and probably only with
+	// Skype) but it shows the idea.
+	opts.Call = func(num string) {
+		cmd := exec.Command("open", "callto:"+num)
+		cmd.Start()
+		cmd.Process.Release()
+	}
+
+	// Make some fake arguments to parse.
+	args := []string{
+		"-vv",
+		"--offset=5",
+		"-n", "Me",
+		"-p", "3",
+		"-s", "hello",
+		"-s", "world",
+		"--ptrslice", "hello",
+		"--ptrslice", "world",
+		"--intmap", "a:1",
+		"--intmap", "b:5",
+		"arg1",
+		"arg2",
+		"arg3",
+	}
+
+	// Parse flags from `args'. Note that here we use flags.ParseArgs for
+	// the sake of making a working example. Normally, you would simply use
+	// flags.Parse(&opts) which uses os.Args
+	args, err := flags.ParseArgs(&opts, args)
+
+	if err != nil {
+		panic(err)
+		os.Exit(1)
+	}
+
+	fmt.Printf("Verbosity: %v\n", opts.Verbose)
+	fmt.Printf("Offset: %d\n", opts.Offset)
+	fmt.Printf("Name: %s\n", opts.Name)
+	fmt.Printf("Ptr: %d\n", *opts.Ptr)
+	fmt.Printf("StringSlice: %v\n", opts.StringSlice)
+	fmt.Printf("PtrSlice: [%v %v]\n", *opts.PtrSlice[0], *opts.PtrSlice[1])
+	fmt.Printf("IntMap: [a:%v b:%v]\n", opts.IntMap["a"], opts.IntMap["b"])
+	fmt.Printf("Remaining args: %s\n", strings.Join(args, " "))
+
+	// Output: Verbosity: [true true]
+	// Offset: 5
+	// Name: Me
+	// Ptr: 3
+	// StringSlice: [hello world]
+	// PtrSlice: [hello world]
+	// IntMap: [a:1 b:5]
+	// Remaining args: arg1 arg2 arg3
+
+More information can be found in the godocs: <http://godoc.org/github.com/jessevdk/go-flags>

+ 82 - 0
github.com/jessevdk/go-flags/assert_test.go

@@ -0,0 +1,82 @@
+package flags
+
+import (
+	"testing"
+)
+
+func assertString(t *testing.T, a string, b string) {
+	if a != b {
+		t.Errorf("Expected %#v, but got %#v", b, a)
+	}
+}
+func assertStringArray(t *testing.T, a []string, b []string) {
+	if len(a) != len(b) {
+		t.Errorf("Expected %#v, but got %#v", b, a)
+		return
+	}
+
+	for i, v := range a {
+		if b[i] != v {
+			t.Errorf("Expected %#v, but got %#v", b, a)
+			return
+		}
+	}
+}
+
+func assertBoolArray(t *testing.T, a []bool, b []bool) {
+	if len(a) != len(b) {
+		t.Errorf("Expected %#v, but got %#v", b, a)
+		return
+	}
+
+	for i, v := range a {
+		if b[i] != v {
+			t.Errorf("Expected %#v, but got %#v", b, a)
+			return
+		}
+	}
+}
+
+func assertParserSuccess(t *testing.T, data interface{}, args ...string) (*Parser, []string) {
+	parser := NewParser(data, Default&^PrintErrors)
+	ret, err := parser.ParseArgs(args)
+
+	if err != nil {
+		t.Fatalf("Unexpected parse error: %s", err)
+		return nil, nil
+	}
+
+	return parser, ret
+}
+
+func assertParseSuccess(t *testing.T, data interface{}, args ...string) []string {
+	_, ret := assertParserSuccess(t, data, args...)
+	return ret
+}
+
+func assertError(t *testing.T, err error, typ ErrorType, msg string) {
+	if err == nil {
+		t.Fatalf("Expected error: %s", msg)
+		return
+	}
+
+	if e, ok := err.(*Error); !ok {
+		t.Fatalf("Expected Error type, but got %#v", err)
+		return
+	} else {
+		if e.Type != typ {
+			t.Errorf("Expected error type {%s}, but got {%s}", typ, e.Type)
+		}
+
+		if e.Message != msg {
+			t.Errorf("Expected error message %#v, but got %#v", msg, e.Message)
+		}
+	}
+}
+
+func assertParseFail(t *testing.T, typ ErrorType, msg string, data interface{}, args ...string) {
+	parser := NewParser(data, Default&^PrintErrors)
+	_, err := parser.ParseArgs(args)
+
+	assertError(t, err, typ, msg)
+}

+ 16 - 0
github.com/jessevdk/go-flags/check_crosscompile.sh

@@ -0,0 +1,16 @@
+#!/bin/bash
+
+set -e
+
+echo '# linux arm7'
+GOARM=7 GOARCH=arm GOOS=linux go build
+echo '# linux arm5'
+GOARM=5 GOARCH=arm GOOS=linux go build
+echo '# windows 386'
+GOARCH=386 GOOS=windows go build
+echo '# windows amd64'
+GOARCH=amd64 GOOS=windows go build
+echo '# darwin'
+GOARCH=amd64 GOOS=darwin go build
+echo '# freebsd'
+GOARCH=amd64 GOOS=freebsd go build

+ 61 - 0
github.com/jessevdk/go-flags/closest.go

@@ -0,0 +1,61 @@
+package flags
+
+func levenshtein(s string, t string) int {
+	if len(s) == 0 {
+		return len(t)
+	}
+
+	if len(t) == 0 {
+		return len(s)
+	}
+
+	var l1, l2, l3 int
+
+	if len(s) == 1 {
+		l1 = len(t) + 1
+	} else {
+		l1 = levenshtein(s[1:len(s)-1], t) + 1
+	}
+
+	if len(t) == 1 {
+		l2 = len(s) + 1
+	} else {
+		l2 = levenshtein(t[1:len(t)-1], s) + 1
+	}
+
+	l3 = levenshtein(s[1:len(s)], t[1:len(t)])
+
+	if s[0] != t[0] {
+		l3 += 1
+	}
+
+	if l2 < l1 {
+		l1 = l2
+	}
+
+	if l1 < l3 {
+		return l1
+	}
+
+	return l3
+}
+
+func closestChoice(cmd string, choices []string) (string, int) {
+	if len(choices) == 0 {
+		return "", 0
+	}
+
+	mincmd := -1
+	mindist := -1
+
+	for i, c := range choices {
+		l := levenshtein(cmd, c)
+
+		if mincmd < 0 || l < mindist {
+			mindist = l
+			mincmd = i
+		}
+	}
+
+	return choices[mincmd], mindist
+}

+ 84 - 0
github.com/jessevdk/go-flags/command.go

@@ -0,0 +1,84 @@
+package flags
+
+// Command represents an application command. Commands can be added to the
+// parser (which itself is a command) and are selected/executed when its name
+// is specified on the command line. The Command type embeds a Group and
+// therefore also carries a set of command specific options.
+type Command struct {
+	// Embedded, see Group for more information
+	*Group
+
+	// The name by which the command can be invoked
+	Name string
+
+	// The active sub command (set by parsing) or nil
+	Active *Command
+
+	commands            []*Command
+	hasBuiltinHelpGroup bool
+}
+
+// Commander is an interface which can be implemented by any command added in
+// the options. When implemented, the Execute method will be called for the last
+// specified (sub)command providing the remaining command line arguments.
+type Commander interface {
+	// Execute will be called for the last active (sub)command. The
+	// args argument contains the remaining command line arguments. The
+	// error that Execute returns will be eventually passed out of the
+	// Parse method of the Parser.
+	Execute(args []string) error
+}
+
+// Usage is an interface which can be implemented to show a custom usage string
+// in the help message shown for a command.
+type Usage interface {
+	// Usage is called for commands to allow customized printing of command
+	// usage in the generated help message.
+	Usage() string
+}
+
+// AddCommand adds a new command to the parser with the given name and data. The
+// data needs to be a pointer to a struct from which the fields indicate which
+// options are in the command. The provided data can implement the Command and
+// Usage interfaces.
+func (c *Command) AddCommand(command string, shortDescription string, longDescription string, data interface{}) (*Command, error) {
+	cmd := newCommand(command, shortDescription, longDescription, data)
+
+	if err := cmd.scan(); err != nil {
+		return nil, err
+	}
+
+	c.commands = append(c.commands, cmd)
+	return cmd, nil
+}
+
+// AddGroup adds a new group to the command with the given name and data. The
+// data needs to be a pointer to a struct from which the fields indicate which
+// options are in the group.
+func (c *Command) AddGroup(shortDescription string, longDescription string, data interface{}) (*Group, error) {
+	group := newGroup(shortDescription, longDescription, data)
+
+	if err := group.scanType(c.scanSubCommandHandler(group)); err != nil {
+		return nil, err
+	}
+
+	c.groups = append(c.groups, group)
+	return group, nil
+}
+
+// Commands returns a list of subcommands of this command.
+func (c *Command) Commands() []*Command {
+	return c.commands
+}
+
+// Find locates the subcommand with the given name and returns it. If no such
+// command can be found Find will return nil.
+func (c *Command) Find(name string) *Command {
+	for _, cc := range c.commands {
+		if cc.Name == name {
+			return cc
+		}
+	}
+
+	return nil
+}

+ 161 - 0
github.com/jessevdk/go-flags/command_private.go

@@ -0,0 +1,161 @@
+package flags
+
+import (
+	"reflect"
+	"sort"
+	"strings"
+	"unsafe"
+)
+
+type lookup struct {
+	shortNames map[string]*Option
+	longNames  map[string]*Option
+
+	required map[*Option]bool
+	commands map[string]*Command
+}
+
+func newCommand(name string, shortDescription string, longDescription string, data interface{}) *Command {
+	return &Command{
+		Group: newGroup(shortDescription, longDescription, data),
+		Name:  name,
+	}
+}
+
+func (c *Command) scanSubCommandHandler(parentg *Group) scanHandler {
+	f := func(realval reflect.Value, sfield *reflect.StructField) (bool, error) {
+		mtag := newMultiTag(string(sfield.Tag))
+
+		if err := mtag.Parse(); err != nil {
+			return true, err
+		}
+
+		subcommand := mtag.Get("command")
+
+		if len(subcommand) != 0 {
+			ptrval := reflect.NewAt(realval.Type(), unsafe.Pointer(realval.UnsafeAddr()))
+
+			shortDescription := mtag.Get("description")
+			longDescription := mtag.Get("long-description")
+
+			if _, err := c.AddCommand(subcommand, shortDescription, longDescription, ptrval.Interface()); err != nil {
+				return true, err
+			}
+
+			return true, nil
+		}
+
+		return parentg.scanSubGroupHandler(realval, sfield)
+	}
+
+	return f
+}
+
+func (c *Command) scan() error {
+	return c.scanType(c.scanSubCommandHandler(c.Group))
+}
+
+func (c *Command) eachCommand(f func(*Command), recurse bool) {
+	f(c)
+
+	for _, cc := range c.commands {
+		if recurse {
+			cc.eachCommand(f, true)
+		} else {
+			f(cc)
+		}
+	}
+}
+
+func (c *Command) eachActiveGroup(f func(g *Group)) {
+	c.eachGroup(f)
+
+	if c.Active != nil {
+		c.Active.eachActiveGroup(f)
+	}
+}
+
+func (c *Command) addHelpGroups(showHelp func() error) {
+	if !c.hasBuiltinHelpGroup {
+		c.addHelpGroup(showHelp)
+		c.hasBuiltinHelpGroup = true
+	}
+
+	for _, cc := range c.commands {
+		cc.addHelpGroups(showHelp)
+	}
+}
+
+func (c *Command) makeLookup() lookup {
+	ret := lookup{
+		shortNames: make(map[string]*Option),
+		longNames:  make(map[string]*Option),
+
+		required: make(map[*Option]bool),
+		commands: make(map[string]*Command),
+	}
+
+	c.eachGroup(func(g *Group) {
+		for _, option := range g.options {
+			if option.Required && option.canCli() {
+				ret.required[option] = true
+			}
+
+			if option.ShortName != 0 {
+				ret.shortNames[string(option.ShortName)] = option
+			}
+
+			if len(option.LongName) > 0 {
+				ret.longNames[option.LongName] = option
+			}
+		}
+	})
+
+	for _, subcommand := range c.commands {
+		ret.commands[subcommand.Name] = subcommand
+	}
+
+	return ret
+}
+
+func (c *Command) groupByName(name string) *Group {
+	if grp := c.Group.groupByName(name); grp != nil {
+		return grp
+	}
+
+	for _, subc := range c.commands {
+		prefix := subc.Name + "."
+
+		if strings.HasPrefix(name, prefix) {
+			if grp := subc.groupByName(name[len(prefix):]); grp != nil {
+				return grp
+			}
+		} else if name == subc.Name {
+			return subc.Group
+		}
+	}
+
+	return nil
+}
+
+type commandList []*Command
+
+func (c commandList) Less(i, j int) bool {
+	return c[i].Name < c[j].Name
+}
+
+func (c commandList) Len() int {
+	return len(c)
+}
+
+func (c commandList) Swap(i, j int) {
+	c[i], c[j] = c[j], c[i]
+}
+
+func (c *Command) sortedCommands() []*Command {
+	ret := make(commandList, len(c.commands))
+	copy(ret, c.commands)
+
+	sort.Sort(ret)
+	return []*Command(ret)
+}

+ 255 - 0
github.com/jessevdk/go-flags/command_test.go

@@ -0,0 +1,255 @@
+package flags
+
+import (
+	"testing"
+)
+
+func TestCommandInline(t *testing.T) {
+	var opts = struct {
+		Value bool `short:"v"`
+
+		Command struct {
+			G bool `short:"g"`
+		} `command:"cmd"`
+	}{}
+
+	p, ret := assertParserSuccess(t, &opts, "-v", "cmd", "-g")
+
+	assertStringArray(t, ret, []string{})
+
+	if p.Active == nil {
+		t.Errorf("Expected active command")
+	}
+
+	if !opts.Value {
+		t.Errorf("Expected Value to be true")
+	}
+
+	if !opts.Command.G {
+		t.Errorf("Expected Command.G to be true")
+	}
+
+	if p.Command.Find("cmd") != p.Active {
+		t.Errorf("Expected to find command `cmd' to be active")
+	}
+}
+
+func TestCommandInlineMulti(t *testing.T) {
+	var opts = struct {
+		Value bool `short:"v"`
+
+		C1 struct {
+		} `command:"c1"`
+
+		C2 struct {
+			G bool `short:"g"`
+		} `command:"c2"`
+	}{}
+
+	p, ret := assertParserSuccess(t, &opts, "-v", "c2", "-g")
+
+	assertStringArray(t, ret, []string{})
+
+	if p.Active == nil {
+		t.Errorf("Expected active command")
+	}
+
+	if !opts.Value {
+		t.Errorf("Expected Value to be true")
+	}
+
+	if !opts.C2.G {
+		t.Errorf("Expected C2.G to be true")
+	}
+
+	if p.Command.Find("c1") == nil {
+		t.Errorf("Expected to find command `c1'")
+	}
+
+	if c2 := p.Command.Find("c2"); c2 == nil {
+		t.Errorf("Expected to find command `c2'")
+	} else if c2 != p.Active {
+		t.Errorf("Expected to find command `c2' to be active")
+	}
+}
+
+func TestCommandFlagOrder1(t *testing.T) {
+	var opts = struct {
+		Value bool `short:"v"`
+
+		Command struct {
+			G bool `short:"g"`
+		} `command:"cmd"`
+	}{}
+
+	assertParseFail(t, ErrUnknownFlag, "unknown flag `g'", &opts, "-v", "-g", "cmd")
+}
+
+func TestCommandFlagOrder2(t *testing.T) {
+	var opts = struct {
+		Value bool `short:"v"`
+
+		Command struct {
+			G bool `short:"g"`
+		} `command:"cmd"`
+	}{}
+
+	assertParseFail(t, ErrUnknownFlag, "unknown flag `v'", &opts, "cmd", "-v", "-g")
+}
+
+func TestCommandEstimate(t *testing.T) {
+	var opts = struct {
+		Value bool `short:"v"`
+
+		Cmd1 struct {
+		} `command:"remove"`
+
+		Cmd2 struct {
+		} `command:"add"`
+	}{}
+
+	p := NewParser(&opts, None)
+	_, err := p.ParseArgs([]string{})
+
+	assertError(t, err, ErrRequired, "Please specify one command of: add or remove")
+}
+
+type testCommand struct {
+	G        bool `short:"g"`
+	Executed bool
+	EArgs    []string
+}
+
+func (c *testCommand) Execute(args []string) error {
+	c.Executed = true
+	c.EArgs = args
+
+	return nil
+}
+
+func TestCommandExecute(t *testing.T) {
+	var opts = struct {
+		Value bool `short:"v"`
+
+		Command testCommand `command:"cmd"`
+	}{}
+
+	assertParseSuccess(t, &opts, "-v", "cmd", "-g", "a", "b")
+
+	if !opts.Value {
+		t.Errorf("Expected Value to be true")
+	}
+
+	if !opts.Command.Executed {
+		t.Errorf("Did not execute command")
+	}
+
+	if !opts.Command.G {
+		t.Errorf("Expected Command.C to be true")
+	}
+
+	assertStringArray(t, opts.Command.EArgs, []string{"a", "b"})
+}
+
+func TestCommandClosest(t *testing.T) {
+	var opts = struct {
+		Value bool `short:"v"`
+
+		Cmd1 struct {
+		} `command:"remove"`
+
+		Cmd2 struct {
+		} `command:"add"`
+	}{}
+
+	assertParseFail(t, ErrRequired, "Unknown command `addd', did you mean `add'?", &opts, "-v", "addd")
+}
+
+func TestCommandAdd(t *testing.T) {
+	var opts = struct {
+		Value bool `short:"v"`
+	}{}
+
+	var cmd = struct {
+		G bool `short:"g"`
+	}{}
+
+	p := NewParser(&opts, Default)
+	c, err := p.AddCommand("cmd", "", "", &cmd)
+
+	if err != nil {
+		t.Fatalf("Unexpected error: %v", err)
+		return
+	}
+
+	ret, err := p.ParseArgs([]string{"-v", "cmd", "-g", "rest"})
+
+	if err != nil {
+		t.Fatalf("Unexpected error: %v", err)
+		return
+	}
+
+	assertStringArray(t, ret, []string{"rest"})
+
+	if !opts.Value {
+		t.Errorf("Expected Value to be true")
+	}
+
+	if !cmd.G {
+		t.Errorf("Expected Command.G to be true")
+	}
+
+	if p.Command.Find("cmd") != c {
+		t.Errorf("Expected to find command `cmd'")
+	}
+
+	if p.Commands()[0] != c {
+		t.Errorf("Espected command #v, but got #v", c, p.Commands()[0])
+	}
+
+	if c.Options()[0].ShortName != 'g' {
+		t.Errorf("Expected short name `g' but got %v", c.Options()[0].ShortName)
+	}
+}
+
+func TestCommandNestedInline(t *testing.T) {
+	var opts = struct {
+		Value bool `short:"v"`
+
+		Command struct {
+			G bool `short:"g"`
+
+			Nested struct {
+				N string `long:"n"`
+			} `command:"nested"`
+		} `command:"cmd"`
+	}{}
+
+	p, ret := assertParserSuccess(t, &opts, "-v", "cmd", "-g", "nested", "--n", "n", "rest")
+
+	assertStringArray(t, ret, []string{"rest"})
+
+	if !opts.Value {
+		t.Errorf("Expected Value to be true")
+	}
+
+	if !opts.Command.G {
+		t.Errorf("Expected Command.G to be true")
+	}
+
+	assertString(t, opts.Command.Nested.N, "n")
+
+	if c := p.Command.Find("cmd"); c == nil {
+		t.Errorf("Expected to find command `cmd'")
+	} else {
+		if c != p.Active {
+			t.Errorf("Expected `cmd' to be the active parser command")
+		}
+
+		if nested := c.Find("nested"); nested == nil {
+			t.Errorf("Expected to find command `nested'")
+		} else if nested != c.Active {
+			t.Errorf("Expected to find command `nested' to be the active `cmd' command")
+		}
+	}
+}

+ 315 - 0
github.com/jessevdk/go-flags/convert.go

@@ -0,0 +1,315 @@
+// Copyright 2012 Jesse van den Kieboom. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package flags
+
+import (
+	"fmt"
+	"reflect"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// Marshaler is the interface implemented by types that can marshal themselves
+// to a string representation of the flag.
+type Marshaler interface {
+	// MarshalFlag marshals a flag value to its string representation.
+	MarshalFlag() (string, error)
+}
+
+// Unmarshaler is the interface implemented by types that can unmarshal a flag
+// argument to themselves. The provided value is directly passed from the
+// command line.
+type Unmarshaler interface {
+	// UnmarshalFlag unmarshals a string value representation to the flag
+	// value (which therefore needs to be a pointer receiver).
+	UnmarshalFlag(value string) error
+}
+
+func getBase(options multiTag, base int) (int, error) {
+	sbase := options.Get("base")
+
+	var err error
+	var ivbase int64
+
+	if sbase != "" {
+		ivbase, err = strconv.ParseInt(sbase, 10, 32)
+		base = int(ivbase)
+	}
+
+	return base, err
+}
+
+func convertMarshal(val reflect.Value) (bool, string, error) {
+	// Check first for the Marshaler interface
+	if val.Type().NumMethod() > 0 && val.CanInterface() {
+		if marshaler, ok := val.Interface().(Marshaler); ok {
+			ret, err := marshaler.MarshalFlag()
+			return true, ret, err
+		}
+	}
+
+	return false, "", nil
+}
+
+func convertToString(val reflect.Value, options multiTag) (string, error) {
+	if ok, ret, err := convertMarshal(val); ok {
+		return ret, err
+	}
+
+	tp := val.Type()
+
+	// Support for time.Duration
+	if tp == reflect.TypeOf((*time.Duration)(nil)).Elem() {
+		stringer := val.Interface().(fmt.Stringer)
+		return stringer.String(), nil
+	}
+
+	switch tp.Kind() {
+	case reflect.String:
+		return val.String(), nil
+	case reflect.Bool:
+		if val.Bool() {
+			return "true", nil
+		}
+
+		return "false", nil
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		base, _ := getBase(options, 10)
+		return strconv.FormatInt(val.Int(), base), nil
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		base, _ := getBase(options, 10)
+		return strconv.FormatUint(val.Uint(), base), nil
+	case reflect.Float32, reflect.Float64:
+		return strconv.FormatFloat(val.Float(), 'g', -1, tp.Bits()), nil
+	case reflect.Slice:
+		if val.Len() == 0 {
+			return "", nil
+		}
+
+		ret := "["
+
+		for i := 0; i < val.Len(); i++ {
+			if i != 0 {
+				ret += ", "
+			}
+
+			item, err := convertToString(val.Index(i), options)
+
+			if err != nil {
+				return "", err
+			}
+
+			ret += item
+		}
+
+		return ret + "]", nil
+	case reflect.Map:
+		ret := "{"
+
+		for i, key := range val.MapKeys() {
+			if i != 0 {
+				ret += ", "
+			}
+
+			item, err := convertToString(val.MapIndex(key), options)
+
+			if err != nil {
+				return "", err
+			}
+
+			ret += item
+		}
+
+		return ret + "}", nil
+	case reflect.Ptr:
+		return convertToString(reflect.Indirect(val), options)
+	case reflect.Interface:
+		if !val.IsNil() {
+			return convertToString(val.Elem(), options)
+		}
+	}
+
+	return "", nil
+}
+
+func convertUnmarshal(val string, retval reflect.Value) (bool, error) {
+	if retval.Type().NumMethod() > 0 && retval.CanInterface() {
+		if unmarshaler, ok := retval.Interface().(Unmarshaler); ok {
+			return true, unmarshaler.UnmarshalFlag(val)
+		}
+	}
+
+	if retval.Type().Kind() != reflect.Ptr && retval.CanAddr() {
+		return convertUnmarshal(val, retval.Addr())
+	}
+
+	if retval.Type().Kind() == reflect.Interface && !retval.IsNil() {
+		return convertUnmarshal(val, retval.Elem())
+	}
+
+	return false, nil
+}
+
+func convert(val string, retval reflect.Value, options multiTag) error {
+	if ok, err := convertUnmarshal(val, retval); ok {
+		return err
+	}
+
+	tp := retval.Type()
+
+	// Support for time.Duration
+	if tp == reflect.TypeOf((*time.Duration)(nil)).Elem() {
+		parsed, err := time.ParseDuration(val)
+
+		if err != nil {
+			return err
+		}
+
+		retval.SetInt(int64(parsed))
+		return nil
+	}
+
+	switch tp.Kind() {
+	case reflect.String:
+		retval.SetString(val)
+	case reflect.Bool:
+		if val == "" {
+			retval.SetBool(true)
+		} else {
+			b, err := strconv.ParseBool(val)
+
+			if err != nil {
+				return err
+			}
+
+			retval.SetBool(b)
+		}
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		base, err := getBase(options, 10)
+
+		if err != nil {
+			return err
+		}
+
+		parsed, err := strconv.ParseInt(val, base, tp.Bits())
+
+		if err != nil {
+			return err
+		}
+
+		retval.SetInt(parsed)
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		base, err := getBase(options, 10)
+
+		if err != nil {
+			return err
+		}
+
+		parsed, err := strconv.ParseUint(val, base, tp.Bits())
+
+		if err != nil {
+			return err
+		}
+
+		retval.SetUint(parsed)
+	case reflect.Float32, reflect.Float64:
+		parsed, err := strconv.ParseFloat(val, tp.Bits())
+
+		if err != nil {
+			return err
+		}
+
+		retval.SetFloat(parsed)
+	case reflect.Slice:
+		elemtp := tp.Elem()
+
+		elemvalptr := reflect.New(elemtp)
+		elemval := reflect.Indirect(elemvalptr)
+
+		if err := convert(val, elemval, options); err != nil {
+			return err
+		}
+
+		retval.Set(reflect.Append(retval, elemval))
+	case reflect.Map:
+		parts := strings.SplitN(val, ":", 2)
+
+		key := parts[0]
+		var value string
+
+		if len(parts) == 2 {
+			value = parts[1]
+		}
+
+		keytp := tp.Key()
+		keyval := reflect.New(keytp)
+
+		if err := convert(key, keyval, options); err != nil {
+			return err
+		}
+
+		valuetp := tp.Elem()
+		valueval := reflect.New(valuetp)
+
+		if err := convert(value, valueval, options); err != nil {
+			return err
+		}
+
+		if retval.IsNil() {
+			retval.Set(reflect.MakeMap(tp))
+		}
+
+		retval.SetMapIndex(reflect.Indirect(keyval), reflect.Indirect(valueval))
+	case reflect.Ptr:
+		if retval.IsNil() {
+			retval.Set(reflect.New(retval.Type().Elem()))
+		}
+
+		return convert(val, reflect.Indirect(retval), options)
+	case reflect.Interface:
+		if !retval.IsNil() {
+			return convert(val, retval.Elem(), options)
+		}
+	}
+
+	return nil
+}
+
+func wrapText(s string, l int, prefix string) string {
+	// Basic text wrapping of s at spaces to fit in l
+	var ret string
+
+	s = strings.TrimSpace(s)
+
+	for len(s) > l {
+		// Try to split on space
+		suffix := ""
+
+		pos := strings.LastIndex(s[:l], " ")
+
+		if pos < 0 {
+			pos = l - 1
+			suffix = "-\n"
+		}
+
+		if len(ret) != 0 {
+			ret += "\n" + prefix
+		}
+
+		ret += strings.TrimSpace(s[:pos]) + suffix
+		s = strings.TrimSpace(s[pos:])
+	}
+
+	if len(s) > 0 {
+		if len(ret) != 0 {
+			ret += "\n" + prefix
+		}
+
+		return ret + s
+	}
+
+	return ret
+}

+ 113 - 0
github.com/jessevdk/go-flags/error.go

@@ -0,0 +1,113 @@
+package flags
+
+import (
+	"fmt"
+)
+
+// ErrorType represents the type of error.
+type ErrorType uint
+
+const (
+	// ErrUnknown indicates a generic error.
+	ErrUnknown ErrorType = iota
+
+	// ErrExpectedArgument indicates that an argument was expected.
+	ErrExpectedArgument
+
+	// ErrUnknownFlag indicates an unknown flag.
+	ErrUnknownFlag
+
+	// ErrUnknownGroup indicates an unknown group.
+	ErrUnknownGroup
+
+	// ErrMarshal indicates a marshalling error while converting values.
+	ErrMarshal
+
+	// ErrHelp indicates that the builtin help was shown (the error
+	// contains the help message).
+	ErrHelp
+
+	// ErrNoArgumentForBool indicates that an argument was given for a
+	// boolean flag (which don't not take any arguments).
+	ErrNoArgumentForBool
+
+	// ErrRequired indicates that a required flag was not provided.
+	ErrRequired
+
+	// ErrShortNameTooLong indicates that a short flag name was specified,
+	// longer than one character.
+	ErrShortNameTooLong
+
+	// ErrDuplicatedFlag indicates that a short or long flag has been
+	// defined more than once
+	ErrDuplicatedFlag
+
+	// ErrTag indicates an error while parsing flag tags.
+	ErrTag
+)
+
+// String returns a string representation of the error type.
+func (e ErrorType) String() string {
+	switch e {
+	case ErrUnknown:
+		return "unknown"
+	case ErrExpectedArgument:
+		return "expected argument"
+	case ErrUnknownFlag:
+		return "unknown flag"
+	case ErrUnknownGroup:
+		return "unknown group"
+	case ErrMarshal:
+		return "marshal"
+	case ErrHelp:
+		return "help"
+	case ErrNoArgumentForBool:
+		return "no argument for bool"
+	case ErrRequired:
+		return "required"
+	case ErrShortNameTooLong:
+		return "short name too long"
+	case ErrDuplicatedFlag:
+		return "duplicated flag"
+	case ErrTag:
+		return "tag"
+	}
+
+	return "unknown"
+}
+
+// Error represents a parser error. The error returned from Parse is of this
+// type. The error contains both a Type and Message.
+type Error struct {
+	// The type of error
+	Type ErrorType
+
+	// The error message
+	Message string
+}
+
+// Error returns the error's message
+func (e *Error) Error() string {
+	return e.Message
+}
+
+func newError(tp ErrorType, message string) *Error {
+	return &Error{
+		Type:    tp,
+		Message: message,
+	}
+}
+
+func newErrorf(tp ErrorType, format string, args ...interface{}) *Error {
+	return newError(tp, fmt.Sprintf(format, args...))
+}
+
+func wrapError(err error) *Error {
+	ret, ok := err.(*Error)
+
+	if !ok {
+		return newError(ErrUnknown, err.Error())
+	}
+
+	return ret
+}

+ 95 - 0
github.com/jessevdk/go-flags/example_test.go

@@ -0,0 +1,95 @@
+// Example of use of the flags package.
+package flags
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+	"strings"
+)
+
+func Example() {
+	var opts struct {
+		// Slice of bool will append 'true' each time the option
+		// is encountered (can be set multiple times, like -vvv)
+		Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
+
+		// Example of automatic marshalling to desired type (uint)
+		Offset uint `long:"offset" description:"Offset"`
+
+		// Example of a callback, called each time the option is found.
+		Call func(string) `short:"c" description:"Call phone number"`
+
+		// Example of a required flag
+		Name string `short:"n" long:"name" description:"A name" required:"true"`
+
+		// Example of a value name
+		File string `short:"f" long:"file" description:"A file" value-name:"FILE"`
+
+		// Example of a pointer
+		Ptr *int `short:"p" description:"A pointer to an integer"`
+
+		// Example of a slice of strings
+		StringSlice []string `short:"s" description:"A slice of strings"`
+
+		// Example of a slice of pointers
+		PtrSlice []*string `long:"ptrslice" description:"A slice of pointers to string"`
+
+		// Example of a map
+		IntMap map[string]int `long:"intmap" description:"A map from string to int"`
+	}
+
+	// Callback which will invoke callto:<argument> to call a number.
+	// Note that this works just on OS X (and probably only with
+	// Skype) but it shows the idea.
+	opts.Call = func(num string) {
+		cmd := exec.Command("open", "callto:"+num)
+		cmd.Start()
+		cmd.Process.Release()
+	}
+
+	// Make some fake arguments to parse.
+	args := []string{
+		"-vv",
+		"--offset=5",
+		"-n", "Me",
+		"-p", "3",
+		"-s", "hello",
+		"-s", "world",
+		"--ptrslice", "hello",
+		"--ptrslice", "world",
+		"--intmap", "a:1",
+		"--intmap", "b:5",
+		"arg1",
+		"arg2",
+		"arg3",
+	}
+
+	// Parse flags from `args'. Note that here we use flags.ParseArgs for
+	// the sake of making a working example. Normally, you would simply use
+	// flags.Parse(&opts) which uses os.Args
+	args, err := ParseArgs(&opts, args)
+
+	if err != nil {
+		panic(err)
+		os.Exit(1)
+	}
+
+	fmt.Printf("Verbosity: %v\n", opts.Verbose)
+	fmt.Printf("Offset: %d\n", opts.Offset)
+	fmt.Printf("Name: %s\n", opts.Name)
+	fmt.Printf("Ptr: %d\n", *opts.Ptr)
+	fmt.Printf("StringSlice: %v\n", opts.StringSlice)
+	fmt.Printf("PtrSlice: [%v %v]\n", *opts.PtrSlice[0], *opts.PtrSlice[1])
+	fmt.Printf("IntMap: [a:%v b:%v]\n", opts.IntMap["a"], opts.IntMap["b"])
+	fmt.Printf("Remaining args: %s\n", strings.Join(args, " "))
+
+	// Output: Verbosity: [true true]
+	// Offset: 5
+	// Name: Me
+	// Ptr: 3
+	// StringSlice: [hello world]
+	// PtrSlice: [hello world]
+	// IntMap: [a:1 b:5]
+	// Remaining args: arg1 arg2 arg3
+}

+ 23 - 0
github.com/jessevdk/go-flags/examples/add.go

@@ -0,0 +1,23 @@
+package main
+
+import (
+	"fmt"
+)
+
+type AddCommand struct {
+	All bool `short:"a" long:"all" description:"Add all files"`
+}
+
+var addCommand AddCommand
+
+func (x *AddCommand) Execute(args []string) error {
+	fmt.Printf("Adding (all=%v): %#v\n", x.All, args)
+	return nil
+}
+
+func init() {
+	parser.AddCommand("add",
+		"Add a file",
+		"The add command adds a file to the repository. Use -a to add all files.",
+		&addCommand)
+}

+ 75 - 0
github.com/jessevdk/go-flags/examples/main.go

@@ -0,0 +1,75 @@
+package main
+
+import (
+	"errors"
+	"fmt"
+	"github.com/calmh/syncthing/github.com/jessevdk/go-flags"
+	"os"
+	"strconv"
+	"strings"
+)
+
+type EditorOptions struct {
+	Input	string	`short:"i" long:"input" description:"Input file" default:"-"`
+	Output	string	`short:"o" long:"output" description:"Output file" default:"-"`
+}
+
+type Point struct {
+	X, Y int
+}
+
+func (p *Point) UnmarshalFlag(value string) error {
+	parts := strings.Split(value, ",")
+
+	if len(parts) != 2 {
+		return errors.New("Expected two numbers separated by a ,")
+	}
+
+	x, err := strconv.ParseInt(parts[0], 10, 32)
+
+	if err != nil {
+		return err
+	}
+
+	y, err := strconv.ParseInt(parts[1], 10, 32)
+
+	if err != nil {
+		return err
+	}
+
+	p.X = int(x)
+	p.Y = int(y)
+
+	return nil
+}
+
+func (p Point) MarshalFlag() (string, error) {
+	return fmt.Sprintf("%d,%d", p.X, p.Y), nil
+}
+
+type Options struct {
+	// Example of verbosity with level
+	Verbose	[]bool	`short:"v" long:"verbose" description:"Verbose output"`
+
+	// Example of optional value
+	User	string	`short:"u" long:"user" description:"User name" optional:"yes" optional-value:"pancake"`
+
+	// Example of map with multiple default values
+	Users	map[string]string	`long:"users" description:"User e-mail map" default:"system:[email protected]" default:"admin:[email protected]"`
+
+	// Example of option group
+	Editor	EditorOptions	`group:"Editor Options"`
+
+	// Example of custom type Marshal/Unmarshal
+	Point	Point	`long:"point" description:"A x,y point" default:"1,2"`
+}
+
+var options Options
+
+var parser = flags.NewParser(&options, flags.Default)
+
+func main() {
+	if _, err := parser.Parse(); err != nil {
+		os.Exit(1)
+	}
+}

+ 23 - 0
github.com/jessevdk/go-flags/examples/rm.go

@@ -0,0 +1,23 @@
+package main
+
+import (
+	"fmt"
+)
+
+type RmCommand struct {
+	Force bool `short:"f" long:"force" description:"Force removal of files"`
+}
+
+var rmCommand RmCommand
+
+func (x *RmCommand) Execute(args []string) error {
+	fmt.Printf("Removing (force=%v): %#v\n", x.Force, args)
+	return nil
+}
+
+func init() {
+	parser.AddCommand("rm",
+		"Remove a file",
+		"The rm command removes a file to the repository. Use -f to force removal of files.",
+		&rmCommand)
+}

+ 141 - 0
github.com/jessevdk/go-flags/flags.go

@@ -0,0 +1,141 @@
+// Copyright 2012 Jesse van den Kieboom. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package flags provides an extensive command line option parser.
+// The flags package is similar in functionality to the go builtin flag package
+// but provides more options and uses reflection to provide a convenient and
+// succinct way of specifying command line options.
+//
+// Supported features:
+//     Options with short names (-v)
+//     Options with long names (--verbose)
+//     Options with and without arguments (bool v.s. other type)
+//     Options with optional arguments and default values
+//     Multiple option groups each containing a set of options
+//     Generate and print well-formatted help message
+//     Passing remaining command line arguments after -- (optional)
+//     Ignoring unknown command line options (optional)
+//     Supports -I/usr/include -I=/usr/include -I /usr/include option argument specification
+//     Supports multiple short options -aux
+//     Supports all primitive go types (string, int{8..64}, uint{8..64}, float)
+//     Supports same option multiple times (can store in slice or last option counts)
+//     Supports maps
+//     Supports function callbacks
+//
+// Additional features specific to Windows:
+//     Options with short names (/v)
+//     Options with long names (/verbose)
+//     Windows-style options with arguments use a colon as the delimiter
+//     Modify generated help message with Windows-style / options
+//
+// The flags package uses structs, reflection and struct field tags
+// to allow users to specify command line options. This results in very simple
+// and consise specification of your application options. For example:
+//
+//     type Options struct {
+//         Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
+//     }
+//
+// This specifies one option with a short name -v and a long name --verbose.
+// When either -v or --verbose is found on the command line, a 'true' value
+// will be appended to the Verbose field. e.g. when specifying -vvv, the
+// resulting value of Verbose will be {[true, true, true]}.
+//
+// Slice options work exactly the same as primitive type options, except that
+// whenever the option is encountered, a value is appended to the slice.
+//
+// Map options from string to primitive type are also supported. On the command
+// line, you specify the value for such an option as key:value. For example
+//
+//     type Options struct {
+//         AuthorInfo string[string] `short:"a"`
+//     }
+//
+// Then, the AuthorInfo map can be filled with something like
+// -a name:Jesse -a "surname:van den Kieboom".
+//
+// Finally, for full control over the conversion between command line argument
+// values and options, user defined types can choose to implement the Marshaler
+// and Unmarshaler interfaces.
+//
+// Available field tags:
+//     short:          the short name of the option (single character)
+//     long:           the long name of the option
+//     description:    the description of the option (optional)
+//     optional:       whether an argument of the option is optional (optional)
+//     optional-value: the value of an optional option when the option occurs
+//                     without an argument. This tag can be specified multiple
+//                     times in the case of maps or slices (optional)
+//     default:        the default value of an option. This tag can be specified
+//                     multiple times in the case of slices or maps (optional).
+//     default-mask:   when specified, this value will be displayed in the help
+//                     instead of the actual default value. This is useful
+//                     mostly for hiding otherwise sensitive information from
+//                     showing up in the help. If default-mask takes the special
+//                     value "-", then no default value will be shown at all
+//                     (optional)
+//     required:       whether an option is required to appear on the command
+//                     line. If a required option is not present, the parser
+//                     will return ErrRequired.
+//     base:           a base (radix) used to convert strings to integer values,
+//                     the default base is 10 (i.e. decimal) (optional)
+//     value-name:     the name of the argument value (to be shown in the help,
+//                     (optional)
+//     group:          when specified on a struct field, makes the struct field
+//                     a separate group with the given name (optional).
+//     command:        when specified on a struct field, makes the struct field
+//                     a (sub)command with the given name (optional).
+//
+// Either short: or long: must be specified to make the field eligible as an
+// option.
+//
+//
+// Option groups:
+//
+// Option groups are a simple way to semantically separate your options. The
+// only real difference is in how your options will appear in the builtin
+// generated help. All options in a particular group are shown together in the
+// help under the name of the group.
+//
+// There are currently three ways to specify option groups.
+//
+//     1. Use NewNamedParser specifying the various option groups.
+//     2. Use AddGroup to add a group to an existing parser.
+//     3. Add a struct field to the toplevel options annotated with the
+//        group:"group-name" tag.
+//
+//
+//
+// Commands:
+//
+// The flags package also has basic support for commands. Commands are often
+// used in monolithic applications that support various commands or actions.
+// Take git for example, all of the add, commit, checkout, etc. are called
+// commands. Using commands you can easily separate multiple functions of your
+// application.
+//
+// There are currently two ways to specifiy a command.
+//
+//     1. Use AddCommand on an existing parser.
+//     2. Add a struct field to your options struct annotated with the
+//        command:"command-name" tag.
+//
+// The most common, idiomatic way to implement commands is to define a global
+// parser instance and implement each command in a separate file. These
+// command files should define a go init function which calls AddCommand on
+// the global parser.
+//
+// When parsing ends and there is an active command and that command implements
+// the Commander interface, then its Execute method will be run with the
+// remaining command line arguments.
+//
+// Command structs can have options which become valid to parse after the
+// command has been specified on the command line. It is currently not valid
+// to specify options from the parent level of the command after the command
+// name has occurred. Thus, given a toplevel option "-v" and a command "add":
+//
+//     Valid:   ./app -v add
+//     Invalid: ./app add -v
+//
+package flags

+ 80 - 0
github.com/jessevdk/go-flags/group.go

@@ -0,0 +1,80 @@
+// Copyright 2012 Jesse van den Kieboom. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package flags
+
+import (
+	"errors"
+	"strings"
+)
+
+// ErrNotPointerToStruct indicates that a provided data container is not
+// a pointer to a struct. Only pointers to structs are valid data containers
+// for options.
+var ErrNotPointerToStruct = errors.New("provided data is not a pointer to struct")
+
+// Group represents an option group. Option groups can be used to logically
+// group options together under a description. Groups are only used to provide
+// more structure to options both for the user (as displayed in the help message)
+// and for you, since groups can be nested.
+type Group struct {
+	// A short description of the group. The
+	// short description is primarily used in the builtin generated help
+	// message
+	ShortDescription string
+
+	// A long description of the group. The long
+	// description is primarily used to present information on commands
+	// (Command embeds Group) in the builtin generated help and man pages.
+	LongDescription string
+
+	// All the options in the group
+	options []*Option
+
+	// All the subgroups
+	groups []*Group
+
+	data interface{}
+}
+
+// AddGroup adds a new group to the command with the given name and data. The
+// data needs to be a pointer to a struct from which the fields indicate which
+// options are in the group.
+func (g *Group) AddGroup(shortDescription string, longDescription string, data interface{}) (*Group, error) {
+	group := newGroup(shortDescription, longDescription, data)
+
+	if err := group.scan(); err != nil {
+		return nil, err
+	}
+
+	g.groups = append(g.groups, group)
+	return group, nil
+}
+
+// Groups returns the list of groups embedded in this group.
+func (g *Group) Groups() []*Group {
+	return g.groups
+}
+
+// Options returns the list of options in this group.
+func (g *Group) Options() []*Option {
+	return g.options
+}
+
+// Find locates the subgroup with the given short description and returns it.
+// If no such group can be found Find will return nil. Note that the description
+// is matched case insensitively.
+func (g *Group) Find(shortDescription string) *Group {
+	lshortDescription := strings.ToLower(shortDescription)
+
+	var ret *Group
+
+	g.eachGroup(func(gg *Group) {
+		if gg != g && strings.ToLower(gg.ShortDescription) == lshortDescription {
+			ret = gg
+		}
+	})
+
+	return ret
+}

+ 263 - 0
github.com/jessevdk/go-flags/group_private.go

@@ -0,0 +1,263 @@
+package flags
+
+import (
+	"reflect"
+	"unicode/utf8"
+	"unsafe"
+)
+
+type scanHandler func(reflect.Value, *reflect.StructField) (bool, error)
+
+func newGroup(shortDescription string, longDescription string, data interface{}) *Group {
+	return &Group{
+		ShortDescription: shortDescription,
+		LongDescription:  longDescription,
+
+		data: data,
+	}
+}
+
+func (g *Group) optionByName(name string, namematch func(*Option, string) bool) *Option {
+	prio := 0
+	var retopt *Option
+
+	for _, opt := range g.options {
+		if namematch != nil && namematch(opt, name) && prio < 4 {
+			retopt = opt
+			prio = 4
+		}
+
+		if name == opt.field.Name && prio < 3 {
+			retopt = opt
+			prio = 3
+		}
+
+		if name == opt.LongName && prio < 2 {
+			retopt = opt
+			prio = 2
+		}
+
+		if opt.ShortName != 0 && name == string(opt.ShortName) && prio < 1 {
+			retopt = opt
+			prio = 1
+		}
+	}
+
+	return retopt
+}
+
+func (g *Group) storeDefaults() {
+	for _, option := range g.options {
+		// First. empty out the value
+		if len(option.Default) > 0 {
+			option.clear()
+		}
+
+		for _, d := range option.Default {
+			option.set(&d)
+		}
+
+		if !option.value.CanSet() {
+			continue
+		}
+
+		option.defaultValue = reflect.ValueOf(option.value.Interface())
+	}
+}
+
+func (g *Group) eachGroup(f func(*Group)) {
+	f(g)
+
+	for _, gg := range g.groups {
+		gg.eachGroup(f)
+	}
+}
+
+func (g *Group) scanStruct(realval reflect.Value, sfield *reflect.StructField, handler scanHandler) error {
+	stype := realval.Type()
+
+	if sfield != nil {
+		if ok, err := handler(realval, sfield); err != nil {
+			return err
+		} else if ok {
+			return nil
+		}
+	}
+
+	for i := 0; i < stype.NumField(); i++ {
+		field := stype.Field(i)
+
+		// PkgName is set only for non-exported fields, which we ignore
+		if field.PkgPath != "" {
+			continue
+		}
+
+		mtag := newMultiTag(string(field.Tag))
+
+		if err := mtag.Parse(); err != nil {
+			return err
+		}
+
+		// Skip fields with the no-flag tag
+		if mtag.Get("no-flag") != "" {
+			continue
+		}
+
+		// Dive deep into structs or pointers to structs
+		kind := field.Type.Kind()
+		fld := realval.Field(i)
+
+		if kind == reflect.Struct {
+			if err := g.scanStruct(fld, &field, handler); err != nil {
+				return err
+			}
+		} else if kind == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct {
+			if fld.IsNil() {
+				fld.Set(reflect.New(fld.Type().Elem()))
+			}
+
+			if err := g.scanStruct(reflect.Indirect(fld), &field, handler); err != nil {
+				return err
+			}
+		}
+
+		longname := mtag.Get("long")
+		shortname := mtag.Get("short")
+
+		// Need at least either a short or long name
+		if longname == "" && shortname == "" && mtag.Get("ini-name") == "" {
+			continue
+		}
+
+		short := rune(0)
+		rc := utf8.RuneCountInString(shortname)
+
+		if rc > 1 {
+			return newErrorf(ErrShortNameTooLong,
+				"short names can only be 1 character long, not `%s'",
+				shortname)
+
+		} else if rc == 1 {
+			short, _ = utf8.DecodeRuneInString(shortname)
+		}
+
+		description := mtag.Get("description")
+		def := mtag.GetMany("default")
+		optionalValue := mtag.GetMany("optional-value")
+		valueName := mtag.Get("value-name")
+		defaultMask := mtag.Get("default-mask")
+
+		optional := (mtag.Get("optional") != "")
+		required := (mtag.Get("required") != "")
+
+		option := &Option{
+			Description:      description,
+			ShortName:        short,
+			LongName:         longname,
+			Default:          def,
+			OptionalArgument: optional,
+			OptionalValue:    optionalValue,
+			Required:         required,
+			ValueName:        valueName,
+			DefaultMask:      defaultMask,
+
+			field: field,
+			value: realval.Field(i),
+			tag:   mtag,
+		}
+
+		g.options = append(g.options, option)
+	}
+
+	return nil
+}
+
+func (g *Group) checkForDuplicateFlags() *Error {
+	shortNames := make(map[rune]*Option)
+	longNames := make(map[string]*Option)
+
+	var duplicateError *Error
+
+	g.eachGroup(func(g *Group) {
+		for _, option := range g.options {
+			if option.LongName != "" {
+				if otherOption, ok := longNames[option.LongName]; ok {
+					duplicateError = newErrorf(ErrDuplicatedFlag, "option `%s' uses the same long name as option `%s'", option, otherOption)
+					return
+				}
+				longNames[option.LongName] = option
+			}
+			if option.ShortName != 0 {
+				if otherOption, ok := shortNames[option.ShortName]; ok {
+					duplicateError = newErrorf(ErrDuplicatedFlag, "option `%s' uses the same short name as option `%s'", option, otherOption)
+					return
+				}
+				shortNames[option.ShortName] = option
+			}
+		}
+	})
+
+	return duplicateError
+}
+
+func (g *Group) scanSubGroupHandler(realval reflect.Value, sfield *reflect.StructField) (bool, error) {
+	mtag := newMultiTag(string(sfield.Tag))
+
+	if err := mtag.Parse(); err != nil {
+		return true, err
+	}
+
+	subgroup := mtag.Get("group")
+
+	if len(subgroup) != 0 {
+		ptrval := reflect.NewAt(realval.Type(), unsafe.Pointer(realval.UnsafeAddr()))
+		description := mtag.Get("description")
+
+		if _, err := g.AddGroup(subgroup, description, ptrval.Interface()); err != nil {
+			return true, err
+		}
+
+		return true, nil
+	}
+
+	return false, nil
+}
+
+func (g *Group) scanType(handler scanHandler) error {
+	// Get all the public fields in the data struct
+	ptrval := reflect.ValueOf(g.data)
+
+	if ptrval.Type().Kind() != reflect.Ptr {
+		panic(ErrNotPointerToStruct)
+	}
+
+	stype := ptrval.Type().Elem()
+
+	if stype.Kind() != reflect.Struct {
+		panic(ErrNotPointerToStruct)
+	}
+
+	realval := reflect.Indirect(ptrval)
+
+	if err := g.scanStruct(realval, nil, handler); err != nil {
+		return err
+	}
+
+	if err := g.checkForDuplicateFlags(); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (g *Group) scan() error {
+	return g.scanType(g.scanSubGroupHandler)
+}
+
+func (g *Group) groupByName(name string) *Group {
+	if len(name) == 0 {
+		return g
+	}
+
+	return g.Find(name)
+}

+ 160 - 0
github.com/jessevdk/go-flags/group_test.go

@@ -0,0 +1,160 @@
+package flags
+
+import (
+	"testing"
+)
+
+func TestGroupInline(t *testing.T) {
+	var opts = struct {
+		Value bool `short:"v"`
+
+		Group struct {
+			G bool `short:"g"`
+		} `group:"Grouped Options"`
+	}{}
+
+	p, ret := assertParserSuccess(t, &opts, "-v", "-g")
+
+	assertStringArray(t, ret, []string{})
+
+	if !opts.Value {
+		t.Errorf("Expected Value to be true")
+	}
+
+	if !opts.Group.G {
+		t.Errorf("Expected Group.G to be true")
+	}
+
+	if p.Command.Group.Find("Grouped Options") == nil {
+		t.Errorf("Expected to find group `Grouped Options'")
+	}
+}
+
+func TestGroupAdd(t *testing.T) {
+	var opts = struct {
+		Value bool `short:"v"`
+	}{}
+
+	var grp = struct {
+		G bool `short:"g"`
+	}{}
+
+	p := NewParser(&opts, Default)
+	g, err := p.AddGroup("Grouped Options", "", &grp)
+
+	if err != nil {
+		t.Fatalf("Unexpected error: %v", err)
+		return
+	}
+
+	ret, err := p.ParseArgs([]string{"-v", "-g", "rest"})
+
+	if err != nil {
+		t.Fatalf("Unexpected error: %v", err)
+		return
+	}
+
+	assertStringArray(t, ret, []string{"rest"})
+
+	if !opts.Value {
+		t.Errorf("Expected Value to be true")
+	}
+
+	if !grp.G {
+		t.Errorf("Expected Group.G to be true")
+	}
+
+	if p.Command.Group.Find("Grouped Options") != g {
+		t.Errorf("Expected to find group `Grouped Options'")
+	}
+
+	if p.Groups()[1] != g {
+		t.Errorf("Espected group #v,	 but got #v", g, p.Groups()[0])
+	}
+
+	if g.Options()[0].ShortName != 'g' {
+		t.Errorf("Expected short name `g' but got %v", g.Options()[0].ShortName)
+	}
+}
+
+func TestGroupNestedInline(t *testing.T) {
+	var opts = struct {
+		Value bool `short:"v"`
+
+		Group struct {
+			G bool `short:"g"`
+
+			Nested struct {
+				N string `long:"n"`
+			} `group:"Nested Options"`
+		} `group:"Grouped Options"`
+	}{}
+
+	p, ret := assertParserSuccess(t, &opts, "-v", "-g", "--n", "n", "rest")
+
+	assertStringArray(t, ret, []string{"rest"})
+
+	if !opts.Value {
+		t.Errorf("Expected Value to be true")
+	}
+
+	if !opts.Group.G {
+		t.Errorf("Expected Group.G to be true")
+	}
+
+	assertString(t, opts.Group.Nested.N, "n")
+
+	if p.Command.Group.Find("Grouped Options") == nil {
+		t.Errorf("Expected to find group `Grouped Options'")
+	}
+
+	if p.Command.Group.Find("Nested Options") == nil {
+		t.Errorf("Expected to find group `Nested Options'")
+	}
+}
+
+func TestDuplicateShortFlags(t *testing.T) {
+	var opts struct {
+		Verbose   []bool   `short:"v" long:"verbose" description:"Show verbose debug information"`
+		Variables []string `short:"v" long:"variable" description:"Set a variable value."`
+	}
+
+	args := []string{
+		"--verbose",
+		"-v", "123",
+		"-v", "456",
+	}
+
+	_, err := ParseArgs(&opts, args)
+
+	if err == nil {
+		t.Errorf("Expected an error with type ErrDuplicatedFlag")
+	} else {
+		err2 := err.(*Error)
+		if err2.Type != ErrDuplicatedFlag {
+			t.Errorf("Expected an error with type ErrDuplicatedFlag")
+		}
+	}
+}
+
+func TestDuplicateLongFlags(t *testing.T) {
+	var opts struct {
+		Test1 []bool   `short:"a" long:"testing" description:"Test 1"`
+		Test2 []string `short:"b" long:"testing" description:"Test 2."`
+	}
+
+	args := []string{
+		"--testing",
+	}
+
+	_, err := ParseArgs(&opts, args)
+
+	if err == nil {
+		t.Errorf("Expected an error with type ErrDuplicatedFlag")
+	} else {
+		err2 := err.(*Error)
+		if err2.Type != ErrDuplicatedFlag {
+			t.Errorf("Expected an error with type ErrDuplicatedFlag")
+		}
+	}
+}

+ 275 - 0
github.com/jessevdk/go-flags/help.go

@@ -0,0 +1,275 @@
+// Copyright 2012 Jesse van den Kieboom. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package flags
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"io"
+	"reflect"
+	"strings"
+	"unicode/utf8"
+)
+
+type alignmentInfo struct {
+	maxLongLen      int
+	hasShort        bool
+	hasValueName    bool
+	terminalColumns int
+}
+
+func (p *Parser) getAlignmentInfo() alignmentInfo {
+	ret := alignmentInfo{
+		maxLongLen:      0,
+		hasShort:        false,
+		hasValueName:    false,
+		terminalColumns: getTerminalColumns(),
+	}
+
+	if ret.terminalColumns <= 0 {
+		ret.terminalColumns = 80
+	}
+
+	p.eachActiveGroup(func(grp *Group) {
+		for _, info := range grp.options {
+			if info.ShortName != 0 {
+				ret.hasShort = true
+			}
+
+			lv := utf8.RuneCountInString(info.ValueName)
+
+			if lv != 0 {
+				ret.hasValueName = true
+			}
+
+			l := utf8.RuneCountInString(info.LongName) + lv
+
+			if l > ret.maxLongLen {
+				ret.maxLongLen = l
+			}
+		}
+	})
+
+	return ret
+}
+
+func (p *Parser) writeHelpOption(writer *bufio.Writer, option *Option, info alignmentInfo) {
+	line := &bytes.Buffer{}
+
+	distanceBetweenOptionAndDescription := 2
+	paddingBeforeOption := 2
+
+	line.WriteString(strings.Repeat(" ", paddingBeforeOption))
+
+	if option.ShortName != 0 {
+		line.WriteRune(defaultShortOptDelimiter)
+		line.WriteRune(option.ShortName)
+	} else if info.hasShort {
+		line.WriteString("  ")
+	}
+
+	descstart := info.maxLongLen + paddingBeforeOption + distanceBetweenOptionAndDescription
+
+	if info.hasShort {
+		descstart += 2
+	}
+
+	if info.maxLongLen > 0 {
+		descstart += 4
+	}
+
+	if info.hasValueName {
+		descstart += 3
+	}
+
+	if len(option.LongName) > 0 {
+		if option.ShortName != 0 {
+			line.WriteString(", ")
+		} else if info.hasShort {
+			line.WriteString("  ")
+		}
+
+		line.WriteString(defaultLongOptDelimiter)
+		line.WriteString(option.LongName)
+	}
+
+	if option.canArgument() {
+		line.WriteRune(defaultNameArgDelimiter)
+
+		if len(option.ValueName) > 0 {
+			line.WriteString(option.ValueName)
+		}
+	}
+
+	written := line.Len()
+	line.WriteTo(writer)
+
+	if option.Description != "" {
+		dw := descstart - written
+		writer.WriteString(strings.Repeat(" ", dw))
+
+		def := ""
+		defs := option.Default
+
+		if len(option.DefaultMask) != 0 {
+			if option.DefaultMask != "-" {
+				def = option.DefaultMask
+			}
+		} else if len(defs) == 0 && option.canArgument() {
+			var showdef bool
+
+			switch option.field.Type.Kind() {
+			case reflect.Func, reflect.Ptr:
+				showdef = !option.value.IsNil()
+			case reflect.Slice, reflect.String, reflect.Array:
+				showdef = option.value.Len() > 0
+			case reflect.Map:
+				showdef = !option.value.IsNil() && option.value.Len() > 0
+			default:
+				zeroval := reflect.Zero(option.field.Type)
+				showdef = !reflect.DeepEqual(zeroval.Interface(), option.value.Interface())
+			}
+
+			if showdef {
+				def, _ = convertToString(option.value, option.tag)
+			}
+		} else if len(defs) != 0 {
+			def = strings.Join(defs, ", ")
+		}
+
+		var desc string
+
+		if def != "" {
+			desc = fmt.Sprintf("%s (%v)", option.Description, def)
+		} else {
+			desc = option.Description
+		}
+
+		writer.WriteString(wrapText(desc,
+			info.terminalColumns-descstart,
+			strings.Repeat(" ", descstart)))
+	}
+
+	writer.WriteString("\n")
+}
+
+func maxCommandLength(s []*Command) int {
+	if len(s) == 0 {
+		return 0
+	}
+
+	ret := len(s[0].Name)
+
+	for _, v := range s[1:] {
+		l := len(v.Name)
+
+		if l > ret {
+			ret = l
+		}
+	}
+
+	return ret
+}
+
+// WriteHelp writes a help message containing all the possible options and
+// their descriptions to the provided writer. Note that the HelpFlag parser
+// option provides a convenient way to add a -h/--help option group to the
+// command line parser which will automatically show the help messages using
+// this method.
+func (p *Parser) WriteHelp(writer io.Writer) {
+	if writer == nil {
+		return
+	}
+
+	wr := bufio.NewWriter(writer)
+	aligninfo := p.getAlignmentInfo()
+
+	cmd := p.Command
+
+	for cmd.Active != nil {
+		cmd = cmd.Active
+	}
+
+	if p.Name != "" {
+		wr.WriteString("Usage:\n")
+		wr.WriteString(" ")
+
+		allcmd := p.Command
+
+		for allcmd != nil {
+			var usage string
+
+			if allcmd == p.Command {
+				if len(p.Usage) != 0 {
+					usage = p.Usage
+				} else {
+					usage = "[OPTIONS]"
+				}
+			} else if us, ok := allcmd.data.(Usage); ok {
+				usage = us.Usage()
+			} else {
+				usage = fmt.Sprintf("[%s-OPTIONS]", allcmd.Name)
+			}
+
+			if len(usage) != 0 {
+				fmt.Fprintf(wr, " %s %s", allcmd.Name, usage)
+			} else {
+				fmt.Fprintf(wr, " %s", allcmd.Name)
+			}
+
+			allcmd = allcmd.Active
+		}
+
+		fmt.Fprintln(wr)
+
+		if len(cmd.LongDescription) != 0 {
+			fmt.Fprintln(wr)
+
+			t := wrapText(cmd.LongDescription,
+				aligninfo.terminalColumns,
+				"")
+
+			fmt.Fprintln(wr, t)
+		}
+	}
+
+	p.eachActiveGroup(func(grp *Group) {
+		first := true
+
+		for _, info := range grp.options {
+			if info.canCli() {
+				if first {
+					fmt.Fprintf(wr, "\n%s:\n", grp.ShortDescription)
+					first = false
+				}
+
+				p.writeHelpOption(wr, info, aligninfo)
+			}
+		}
+	})
+
+	scommands := cmd.sortedCommands()
+
+	if len(scommands) > 0 {
+		maxnamelen := maxCommandLength(scommands)
+
+		fmt.Fprintln(wr)
+		fmt.Fprintln(wr, "Available commands:")
+
+		for _, c := range scommands {
+			fmt.Fprintf(wr, "  %s", c.Name)
+
+			if len(c.ShortDescription) > 0 {
+				pad := strings.Repeat(" ", maxnamelen-len(c.Name))
+				fmt.Fprintf(wr, "%s  %s", pad, c.ShortDescription)
+			}
+
+			fmt.Fprintln(wr)
+		}
+	}
+
+	wr.Flush()
+}

+ 153 - 0
github.com/jessevdk/go-flags/help_test.go

@@ -0,0 +1,153 @@
+package flags
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"testing"
+	"time"
+)
+
+func helpDiff(a, b string) (string, error) {
+	atmp, err := ioutil.TempFile("", "help-diff")
+
+	if err != nil {
+		return "", err
+	}
+
+	btmp, err := ioutil.TempFile("", "help-diff")
+
+	if err != nil {
+		return "", err
+	}
+
+	if _, err := io.WriteString(atmp, a); err != nil {
+		return "", err
+	}
+
+	if _, err := io.WriteString(btmp, b); err != nil {
+		return "", err
+	}
+
+	ret, err := exec.Command("diff", "-u", "-d", "--label", "got", atmp.Name(), "--label", "expected", btmp.Name()).Output()
+
+	os.Remove(atmp.Name())
+	os.Remove(btmp.Name())
+
+	return string(ret), nil
+}
+
+type helpOptions struct {
+	Verbose  []bool       `short:"v" long:"verbose" description:"Show verbose debug information" ini-name:"verbose"`
+	Call     func(string) `short:"c" description:"Call phone number" ini-name:"call"`
+	PtrSlice []*string    `long:"ptrslice" description:"A slice of pointers to string"`
+
+	OnlyIni string `ini-name:"only-ini" description:"Option only available in ini"`
+
+	Other struct {
+		StringSlice []string       `short:"s" description:"A slice of strings"`
+		IntMap      map[string]int `long:"intmap" description:"A map from string to int" ini-name:"int-map"`
+	} `group:"Other Options"`
+}
+
+func TestHelp(t *testing.T) {
+	var opts helpOptions
+
+	p := NewNamedParser("TestHelp", HelpFlag)
+	p.AddGroup("Application Options", "The application options", &opts)
+
+	_, err := p.ParseArgs([]string{"--help"})
+
+	if err == nil {
+		t.Fatalf("Expected help error")
+	}
+
+	if e, ok := err.(*Error); !ok {
+		t.Fatalf("Expected flags.Error, but got %#T", err)
+	} else {
+		if e.Type != ErrHelp {
+			t.Errorf("Expected flags.ErrHelp type, but got %s", e.Type)
+		}
+
+		expected := `Usage:
+  TestHelp [OPTIONS]
+
+Application Options:
+  -v, --verbose   Show verbose debug information
+  -c=             Call phone number
+      --ptrslice= A slice of pointers to string
+
+Other Options:
+  -s=             A slice of strings
+      --intmap=   A map from string to int
+
+Help Options:
+  -h, --help      Show this help message
+`
+
+		if e.Message != expected {
+			ret, err := helpDiff(e.Message, expected)
+
+			if err != nil {
+				t.Errorf("Unexpected diff error: %s", err)
+				t.Errorf("Unexpected help message, expected:\n\n%s\n\nbut got\n\n%s", expected, e.Message)
+			} else {
+				t.Errorf("Unexpected help message:\n\n%s", ret)
+			}
+		}
+	}
+}
+
+func TestMan(t *testing.T) {
+	var opts helpOptions
+
+	p := NewNamedParser("TestMan", HelpFlag)
+	p.ShortDescription = "Test manpage generation"
+	p.LongDescription = "This is a somewhat longer description of what this does"
+	p.AddGroup("Application Options", "The application options", &opts)
+
+	var buf bytes.Buffer
+	p.WriteManPage(&buf)
+
+	got := buf.String()
+
+	tt := time.Now()
+
+	expected := fmt.Sprintf(`.TH TestMan 1 "%s"
+.SH NAME
+TestMan \- Test manpage generation
+.SH SYNOPSIS
+\fBTestMan\fP [OPTIONS]
+.SH DESCRIPTION
+This is a somewhat longer description of what this does
+.SH OPTIONS
+.TP
+\fB-v, --verbose\fP
+Show verbose debug information
+.TP
+\fB-c\fP
+Call phone number
+.TP
+\fB--ptrslice\fP
+A slice of pointers to string
+.TP
+\fB-s\fP
+A slice of strings
+.TP
+\fB--intmap\fP
+A map from string to int
+`, tt.Format("2 January 2006"))
+
+	if got != expected {
+		ret, err := helpDiff(got, expected)
+
+		if err != nil {
+			t.Errorf("Unexpected man page, expected:\n\n%s\n\nbut got\n\n%s", expected, got)
+		} else {
+			t.Errorf("Unexpected man page:\n\n%s", ret)
+		}
+	}
+}

+ 146 - 0
github.com/jessevdk/go-flags/ini.go

@@ -0,0 +1,146 @@
+package flags
+
+import (
+	"fmt"
+	"io"
+	"os"
+)
+
+// IniError contains location information on where in the ini file an error
+// occured.
+type IniError struct {
+	// The error message.
+	Message    string
+
+	// The filename of the file in which the error occurred.
+	File       string
+
+	// The line number at which the error occurred.
+	LineNumber uint
+}
+
+// Error provides a "file:line: message" formatted message of the ini error.
+func (x *IniError) Error() string {
+	return fmt.Sprintf("%s:%d: %s",
+		x.File,
+		x.LineNumber,
+		x.Message)
+}
+
+// IniOptions for writing ini files
+type IniOptions uint
+
+const (
+	// IniNone indicates no options.
+	IniNone IniOptions = 0
+
+	// IniIncludeDefaults indicates that default values should be written
+	// when writing options to an ini file.
+	IniIncludeDefaults = 1 << iota
+
+	// IniIncludeComments indicates that comments containing the description
+	// of an option should be written when writing options to an ini file.
+	IniIncludeComments
+
+	// IniDefault provides a default set of options.
+	IniDefault = IniIncludeComments
+)
+
+// IniParser is a utility to read and write flags options from and to ini
+// files.
+type IniParser struct {
+	parser *Parser
+}
+
+// NewIniParser creates a new ini parser for a given Parser.
+func NewIniParser(p *Parser) *IniParser {
+	return &IniParser{
+		parser: p,
+	}
+}
+
+// IniParse is a convenience function to parse command line options with default
+// settings from an ini file. The provided data is a pointer to a struct
+// representing the default option group (named "Application Options"). For
+// more control, use flags.NewParser.
+func IniParse(filename string, data interface{}) error {
+	p := NewParser(data, Default)
+	return NewIniParser(p).ParseFile(filename)
+}
+
+// ParseFile parses flags from an ini formatted file. See Parse for more
+// information on the ini file foramt. The returned errors can be of the type
+// flags.Error or flags.IniError.
+func (i *IniParser) ParseFile(filename string) error {
+	i.parser.storeDefaults()
+
+	ini, err := readIniFromFile(filename)
+
+	if err != nil {
+		return err
+	}
+
+	return i.parse(ini)
+}
+
+// Parse parses flags from an ini format. You can use ParseFile as a
+// convenience function to parse from a filename instead of a general
+// io.Reader.
+//
+// The format of the ini file is as follows:
+//
+//     [Option group name]
+//     option = value
+//
+// Each section in the ini file represents an option group or command in the
+// flags parser. The default flags parser option group (i.e. when using
+// flags.Parse) is named 'Application Options'. The ini option name is matched
+// in the following order:
+//
+//     1. Compared to the ini-name tag on the option struct field (if present)
+//     2. Compared to the struct field name
+//     3. Compared to the option long name (if present)
+//     4. Compared to the option short name (if present)
+//
+// Sections for nested groups and commands can be addressed using a dot `.'
+// namespacing notation (i.e [subcommand.Options]). Group section names are
+// matched case insensitive.
+//
+// The returned errors can be of the type flags.Error or
+// flags.IniError.
+func (i *IniParser) Parse(reader io.Reader) error {
+	i.parser.storeDefaults()
+
+	ini, err := readIni(reader, "")
+
+	if err != nil {
+		return err
+	}
+
+	return i.parse(ini)
+}
+
+// WriteFile writes the flags as ini format into a file. See WriteIni
+// for more information. The returned error occurs when the specified file
+// could not be opened for writing.
+func (i *IniParser) WriteFile(filename string, options IniOptions) error {
+	file, err := os.Create(filename)
+
+	if err != nil {
+		return err
+	}
+
+	defer file.Close()
+	i.Write(file, options)
+
+	return nil
+}
+
+// Write writes the current values of all the flags to an ini format.
+// See Parse for more information on the ini file format. You typically
+// call this only after settings have been parsed since the default values of each
+// option are stored just before parsing the flags (this is only relevant when
+// IniIncludeDefaults is _not_ set in options).
+func (i *IniParser) Write(writer io.Writer, options IniOptions) {
+	writeIni(i, writer, options)
+}

+ 333 - 0
github.com/jessevdk/go-flags/ini_private.go

@@ -0,0 +1,333 @@
+package flags
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"os"
+	"reflect"
+	"strings"
+)
+
+type iniValue struct {
+	Name  string
+	Value string
+}
+
+type iniSection []iniValue
+type ini map[string]iniSection
+
+func readFullLine(reader *bufio.Reader) (string, error) {
+	var line []byte
+
+	for {
+		l, more, err := reader.ReadLine()
+
+		if err != nil {
+			return "", err
+		}
+
+		if line == nil && !more {
+			return string(l), nil
+		}
+
+		line = append(line, l...)
+
+		if !more {
+			break
+		}
+	}
+
+	return string(line), nil
+}
+
+func optionIniName(option *Option) string {
+	name := option.tag.Get("_read-ini-name")
+
+	if len(name) != 0 {
+		return name
+	}
+
+	name = option.tag.Get("ini-name")
+
+	if len(name) != 0 {
+		return name
+	}
+
+	return option.field.Name
+}
+
+func writeGroupIni(group *Group, namespace string, writer io.Writer, options IniOptions) {
+	var sname string
+
+	if len(namespace) != 0 {
+		sname = namespace + "." + group.ShortDescription
+	} else {
+		sname = group.ShortDescription
+	}
+
+	sectionwritten := false
+	comments := (options & IniIncludeComments) != IniNone
+
+	for _, option := range group.options {
+		if option.isFunc() {
+			continue
+		}
+
+		if len(option.tag.Get("no-ini")) != 0 {
+			continue
+		}
+
+		val := option.value
+
+		if (options&IniIncludeDefaults) == IniNone &&
+			reflect.DeepEqual(val, option.defaultValue) {
+			continue
+		}
+
+		if !sectionwritten {
+			fmt.Fprintf(writer, "[%s]\n", sname)
+			sectionwritten = true
+		}
+
+		if comments {
+			fmt.Fprintf(writer, "; %s\n", option.Description)
+		}
+
+		oname := optionIniName(option)
+
+		switch val.Type().Kind() {
+		case reflect.Slice:
+			for idx := 0; idx < val.Len(); idx++ {
+				v, _ := convertToString(val.Index(idx), option.tag)
+				fmt.Fprintf(writer, "%s = %s\n", oname, v)
+			}
+
+			if val.Len() == 0 {
+				fmt.Fprintf(writer, "; %s =\n", oname)
+			}
+		case reflect.Map:
+			for _, key := range val.MapKeys() {
+				k, _ := convertToString(key, option.tag)
+				v, _ := convertToString(val.MapIndex(key), option.tag)
+
+				fmt.Fprintf(writer, "%s = %s:%s\n", oname, k, v)
+			}
+
+			if val.Len() == 0 {
+				fmt.Fprintf(writer, "; %s =\n", oname)
+			}
+		default:
+			v, _ := convertToString(val, option.tag)
+
+			if len(v) != 0 {
+				fmt.Fprintf(writer, "%s = %s\n", oname, v)
+			} else {
+				fmt.Fprintf(writer, "%s =\n", oname)
+			}
+		}
+
+		if comments {
+			fmt.Fprintln(writer)
+		}
+	}
+
+	if sectionwritten && !comments {
+		fmt.Fprintln(writer)
+	}
+}
+
+func writeCommandIni(command *Command, namespace string, writer io.Writer, options IniOptions) {
+	command.eachGroup(func(group *Group) {
+		writeGroupIni(group, namespace, writer, options)
+	})
+
+	for _, c := range command.commands {
+		var nns string
+
+		if len(namespace) != 0 {
+			nns = c.Name + "." + nns
+		} else {
+			nns = c.Name
+		}
+
+		writeCommandIni(c, nns, writer, options)
+	}
+}
+
+func writeIni(parser *IniParser, writer io.Writer, options IniOptions) {
+	writeCommandIni(parser.parser.Command, "", writer, options)
+}
+
+func readIniFromFile(filename string) (ini, error) {
+	file, err := os.Open(filename)
+
+	if err != nil {
+		return nil, err
+	}
+
+	defer file.Close()
+
+	return readIni(file, filename)
+}
+
+func readIni(contents io.Reader, filename string) (ini, error) {
+	ret := make(ini)
+
+	reader := bufio.NewReader(contents)
+
+	// Empty global section
+	section := make(iniSection, 0, 10)
+	sectionname := ""
+
+	ret[sectionname] = section
+
+	var lineno uint
+
+	for {
+		line, err := readFullLine(reader)
+
+		if err == io.EOF {
+			break
+		}
+
+		if err != nil {
+			return nil, err
+		}
+
+		lineno++
+		line = strings.TrimSpace(line)
+
+		// Skip empty lines and lines starting with ; (comments)
+		if len(line) == 0 || line[0] == ';' {
+			continue
+		}
+
+		if line[0] == '[' {
+			if line[0] != '[' || line[len(line)-1] != ']' {
+				return nil, &IniError{
+					Message:    "malformed section header",
+					File:       filename,
+					LineNumber: lineno,
+				}
+			}
+
+			name := strings.TrimSpace(line[1 : len(line)-1])
+
+			if len(name) == 0 {
+				return nil, &IniError{
+					Message:    "empty section name",
+					File:       filename,
+					LineNumber: lineno,
+				}
+			}
+
+			sectionname = name
+			section = ret[name]
+
+			if section == nil {
+				section = make(iniSection, 0, 10)
+				ret[name] = section
+			}
+
+			continue
+		}
+
+		// Parse option here
+		keyval := strings.SplitN(line, "=", 2)
+
+		if len(keyval) != 2 {
+			return nil, &IniError{
+				Message:    fmt.Sprintf("malformed key=value (%s)", line),
+				File:       filename,
+				LineNumber: lineno,
+			}
+		}
+
+		name := strings.TrimSpace(keyval[0])
+		value := strings.TrimSpace(keyval[1])
+
+		section = append(section, iniValue{
+			Name:  name,
+			Value: value,
+		})
+
+		ret[sectionname] = section
+	}
+
+	return ret, nil
+}
+
+func (i *IniParser) matchingGroups(name string) []*Group {
+	if len(name) == 0 {
+		var ret []*Group
+
+		i.parser.eachGroup(func(g *Group) {
+			ret = append(ret, g)
+		})
+
+		return ret
+	}
+
+	g := i.parser.groupByName(name)
+
+	if g != nil {
+		return []*Group{g}
+	}
+
+	return nil
+}
+
+func (i *IniParser) parse(ini ini) error {
+	p := i.parser
+
+	for name, section := range ini {
+		groups := i.matchingGroups(name)
+
+		if len(groups) == 0 {
+			return newError(ErrUnknownGroup,
+				fmt.Sprintf("could not find option group `%s'", name))
+		}
+
+		for _, inival := range section {
+			var opt *Option
+
+			for _, group := range groups {
+				opt = group.optionByName(inival.Name, func(o *Option, n string) bool {
+					return strings.ToLower(o.tag.Get("ini-name")) == strings.ToLower(n)
+				})
+
+				if opt != nil && len(opt.tag.Get("no-ini")) != 0 {
+					opt = nil
+				}
+
+				if opt != nil {
+					break
+				}
+			}
+
+			if opt == nil {
+				if (p.Options & IgnoreUnknown) == None {
+					return newError(ErrUnknownFlag,
+						fmt.Sprintf("unknown option: %s", inival.Name))
+				}
+
+				continue
+			}
+
+			pval := &inival.Value
+
+			if !opt.canArgument() && len(inival.Value) == 0 {
+				pval = nil
+			}
+
+			if err := opt.set(pval); err != nil {
+				return wrapError(err)
+			}
+
+			opt.tag.Set("_read-ini-name", inival.Name)
+		}
+	}
+
+	return nil
+}

+ 170 - 0
github.com/jessevdk/go-flags/ini_test.go

@@ -0,0 +1,170 @@
+package flags
+
+import (
+	"bytes"
+	"strings"
+	"testing"
+)
+
+func TestWriteIni(t *testing.T) {
+	var opts helpOptions
+
+	p := NewNamedParser("TestIni", Default)
+	p.AddGroup("Application Options", "The application options", &opts)
+
+	p.ParseArgs([]string{"-vv", "--intmap=a:2", "--intmap", "b:3"})
+
+	inip := NewIniParser(p)
+
+	var b bytes.Buffer
+	inip.Write(&b, IniDefault|IniIncludeDefaults)
+
+	got := b.String()
+	expected := `[Application Options]
+; Show verbose debug information
+verbose = true
+verbose = true
+
+; A slice of pointers to string
+; PtrSlice =
+
+; Option only available in ini
+only-ini =
+
+[Other Options]
+; A slice of strings
+; StringSlice =
+
+; A map from string to int
+int-map = a:2
+int-map = b:3
+
+`
+
+	if got != expected {
+		ret, err := helpDiff(got, expected)
+
+		if err != nil {
+			t.Errorf("Unexpected ini, expected:\n\n%s\n\nbut got\n\n%s", expected, got)
+		} else {
+			t.Errorf("Unexpected ini:\n\n%s", ret)
+		}
+	}
+}
+
+func TestReadIni(t *testing.T) {
+	var opts helpOptions
+
+	p := NewNamedParser("TestIni", Default)
+	p.AddGroup("Application Options", "The application options", &opts)
+
+	inip := NewIniParser(p)
+
+	inic := `
+; Show verbose debug information
+verbose = true
+verbose = true
+
+[Application Options]
+; A slice of pointers to string
+; PtrSlice =
+
+[Other Options]
+; A slice of strings
+; StringSlice =
+
+; A map from string to int
+int-map = a:2
+int-map = b:3
+
+`
+
+	b := strings.NewReader(inic)
+	err := inip.Parse(b)
+
+	if err != nil {
+		t.Fatalf("Unexpected error: %s", err)
+	}
+
+	assertBoolArray(t, opts.Verbose, []bool{true, true})
+
+	if v, ok := opts.Other.IntMap["a"]; !ok {
+		t.Errorf("Expected \"a\" in Other.IntMap")
+	} else if v != 2 {
+		t.Errorf("Expected Other.IntMap[\"a\"] = 2, but got %v", v)
+	}
+
+	if v, ok := opts.Other.IntMap["b"]; !ok {
+		t.Errorf("Expected \"b\" in Other.IntMap")
+	} else if v != 3 {
+		t.Errorf("Expected Other.IntMap[\"b\"] = 3, but got %v", v)
+	}
+}
+
+func TestIniCommands(t *testing.T) {
+	var opts struct {
+		Value string `short:"v" long:"value"`
+
+		Add struct {
+			Name int `short:"n" long:"name" ini-name:"AliasName"`
+
+			Other struct {
+				O string `short:"o" long:"other"`
+			} `group:"Other Options"`
+		} `command:"add"`
+	}
+
+	p := NewNamedParser("TestIni", Default)
+	p.AddGroup("Application Options", "The application options", &opts)
+
+	inip := NewIniParser(p)
+
+	inic := `[Application Options]
+value = some value
+
+[add]
+AliasName = 5
+
+[add.Other Options]
+other = subgroup
+`
+
+	b := strings.NewReader(inic)
+	err := inip.Parse(b)
+
+	if err != nil {
+		t.Fatalf("Unexpected error: %s", err)
+	}
+
+	assertString(t, opts.Value, "some value")
+
+	if opts.Add.Name != 5 {
+		t.Errorf("Expected opts.Add.Name to be 5, but got %v", opts.Add.Name)
+	}
+
+	assertString(t, opts.Add.Other.O, "subgroup")
+}
+
+func TestIniNoIni(t *testing.T) {
+	var opts struct {
+		Value string `short:"v" long:"value" no-ini:"yes"`
+	}
+
+	p := NewNamedParser("TestIni", Default)
+	p.AddGroup("Application Options", "The application options", &opts)
+
+	inip := NewIniParser(p)
+
+	inic := `[Application Options]
+value = some value
+`
+
+	b := strings.NewReader(inic)
+	err := inip.Parse(b)
+
+	if err == nil {
+		t.Fatalf("Expected error")
+	}
+
+	assertError(t, err, ErrUnknownFlag, "unknown option: value")
+}

+ 85 - 0
github.com/jessevdk/go-flags/long_test.go

@@ -0,0 +1,85 @@
+package flags
+
+import (
+	"testing"
+)
+
+func TestLong(t *testing.T) {
+	var opts = struct {
+		Value bool `long:"value"`
+	}{}
+
+	ret := assertParseSuccess(t, &opts, "--value")
+
+	assertStringArray(t, ret, []string{})
+
+	if !opts.Value {
+		t.Errorf("Expected Value to be true")
+	}
+}
+
+func TestLongArg(t *testing.T) {
+	var opts = struct {
+		Value string `long:"value"`
+	}{}
+
+	ret := assertParseSuccess(t, &opts, "--value", "value")
+
+	assertStringArray(t, ret, []string{})
+	assertString(t, opts.Value, "value")
+}
+
+func TestLongArgEqual(t *testing.T) {
+	var opts = struct {
+		Value string `long:"value"`
+	}{}
+
+	ret := assertParseSuccess(t, &opts, "--value=value")
+
+	assertStringArray(t, ret, []string{})
+	assertString(t, opts.Value, "value")
+}
+
+func TestLongDefault(t *testing.T) {
+	var opts = struct {
+		Value string `long:"value" default:"value"`
+	}{}
+
+	ret := assertParseSuccess(t, &opts)
+
+	assertStringArray(t, ret, []string{})
+	assertString(t, opts.Value, "value")
+}
+
+func TestLongOptional(t *testing.T) {
+	var opts = struct {
+		Value string `long:"value" optional:"yes" optional-value:"value"`
+	}{}
+
+	ret := assertParseSuccess(t, &opts, "--value")
+
+	assertStringArray(t, ret, []string{})
+	assertString(t, opts.Value, "value")
+}
+
+func TestLongOptionalArg(t *testing.T) {
+	var opts = struct {
+		Value string `long:"value" optional:"yes" optional-value:"value"`
+	}{}
+
+	ret := assertParseSuccess(t, &opts, "--value", "no")
+
+	assertStringArray(t, ret, []string{"no"})
+	assertString(t, opts.Value, "value")
+}
+
+func TestLongOptionalArgEqual(t *testing.T) {
+	var opts = struct {
+		Value string `long:"value" optional:"yes" optional-value:"value"`
+	}{}
+
+	ret := assertParseSuccess(t, &opts, "--value=value", "no")
+
+	assertStringArray(t, ret, []string{"no"})
+	assertString(t, opts.Value, "value")
+}

+ 134 - 0
github.com/jessevdk/go-flags/man.go

@@ -0,0 +1,134 @@
+package flags
+
+import (
+	"fmt"
+	"io"
+	"strings"
+	"time"
+)
+
+func formatForMan(wr io.Writer, s string) {
+	for {
+		idx := strings.IndexRune(s, '`')
+
+		if idx < 0 {
+			fmt.Fprintf(wr, "%s", s)
+			break
+		}
+
+		fmt.Fprintf(wr, "%s", s[:idx])
+
+		s = s[idx+1:]
+		idx = strings.IndexRune(s, '\'')
+
+		if idx < 0 {
+			fmt.Fprintf(wr, "%s", s)
+			break
+		}
+
+		fmt.Fprintf(wr, "\\fB%s\\fP", s[:idx])
+		s = s[idx+1:]
+	}
+}
+
+func writeManPageOptions(wr io.Writer, grp *Group) {
+	grp.eachGroup(func(group *Group) {
+		for _, opt := range group.options {
+			if !opt.canCli() {
+				continue
+			}
+
+			fmt.Fprintln(wr, ".TP")
+			fmt.Fprintf(wr, "\\fB")
+
+			if opt.ShortName != 0 {
+				fmt.Fprintf(wr, "-%c", opt.ShortName)
+			}
+
+			if len(opt.LongName) != 0 {
+				if opt.ShortName != 0 {
+					fmt.Fprintf(wr, ", ")
+				}
+
+				fmt.Fprintf(wr, "--%s", opt.LongName)
+			}
+
+			fmt.Fprintln(wr, "\\fP")
+			formatForMan(wr, opt.Description)
+			fmt.Fprintln(wr, "")
+		}
+	})
+}
+
+func writeManPageSubCommands(wr io.Writer, name string, root *Command) {
+	commands := root.sortedCommands()
+
+	for _, c := range commands {
+		var nn string
+
+		if len(name) != 0 {
+			nn = name + " " + c.Name
+		} else {
+			nn = c.Name
+		}
+
+		writeManPageCommand(wr, nn, c)
+	}
+}
+
+func writeManPageCommand(wr io.Writer, name string, command *Command) {
+	fmt.Fprintf(wr, ".SS %s\n", name)
+	fmt.Fprintln(wr, command.ShortDescription)
+
+	if len(command.LongDescription) > 0 {
+		fmt.Fprintln(wr, "")
+
+		cmdstart := fmt.Sprintf("The %s command", command.Name)
+
+		if strings.HasPrefix(command.LongDescription, cmdstart) {
+			fmt.Fprintf(wr, "The \\fI%s\\fP command", command.Name)
+
+			formatForMan(wr, command.LongDescription[len(cmdstart):])
+			fmt.Fprintln(wr, "")
+		} else {
+			formatForMan(wr, command.LongDescription)
+			fmt.Fprintln(wr, "")
+		}
+	}
+
+	writeManPageOptions(wr, command.Group)
+	writeManPageSubCommands(wr, name, command)
+}
+
+// WriteManPage writes a basic man page in groff format to the specified
+// writer.
+func (p *Parser) WriteManPage(wr io.Writer) {
+	t := time.Now()
+
+	fmt.Fprintf(wr, ".TH %s 1 \"%s\"\n", p.Name, t.Format("2 January 2006"))
+	fmt.Fprintln(wr, ".SH NAME")
+	fmt.Fprintf(wr, "%s \\- %s\n", p.Name, p.ShortDescription)
+	fmt.Fprintln(wr, ".SH SYNOPSIS")
+
+	usage := p.Usage
+
+	if len(usage) == 0 {
+		usage = "[OPTIONS]"
+	}
+
+	fmt.Fprintf(wr, "\\fB%s\\fP %s\n", p.Name, usage)
+	fmt.Fprintln(wr, ".SH DESCRIPTION")
+
+	formatForMan(wr, p.LongDescription)
+	fmt.Fprintln(wr, "")
+
+	fmt.Fprintln(wr, ".SH OPTIONS")
+
+	writeManPageOptions(wr, p.Command.Group)
+
+	if len(p.commands) > 0 {
+		fmt.Fprintln(wr, ".SH COMMANDS")
+
+		writeManPageSubCommands(wr, "", p.Command)
+	}
+}

+ 78 - 0
github.com/jessevdk/go-flags/marshal_test.go

@@ -0,0 +1,78 @@
+package flags
+
+import (
+	"fmt"
+	"testing"
+)
+
+type marshalled bool
+
+func (m *marshalled) UnmarshalFlag(value string) error {
+	if value == "yes" {
+		*m = true
+	} else if value == "no" {
+		*m = false
+	} else {
+		return fmt.Errorf("`%s' is not a valid value, please specify `yes' or `no'", value)
+	}
+
+	return nil
+}
+
+func (m marshalled) MarshalFlag() string {
+	if m {
+		return "yes"
+	}
+
+	return "no"
+}
+
+func TestMarshal(t *testing.T) {
+	var opts = struct {
+		Value marshalled `short:"v"`
+	}{}
+
+	ret := assertParseSuccess(t, &opts, "-v=yes")
+
+	assertStringArray(t, ret, []string{})
+
+	if !opts.Value {
+		t.Errorf("Expected Value to be true")
+	}
+}
+
+func TestMarshalDefault(t *testing.T) {
+	var opts = struct {
+		Value marshalled `short:"v" default:"yes"`
+	}{}
+
+	ret := assertParseSuccess(t, &opts)
+
+	assertStringArray(t, ret, []string{})
+
+	if !opts.Value {
+		t.Errorf("Expected Value to be true")
+	}
+}
+
+func TestMarshalOptional(t *testing.T) {
+	var opts = struct {
+		Value marshalled `short:"v" optional:"yes" optional-value:"yes"`
+	}{}
+
+	ret := assertParseSuccess(t, &opts, "-v")
+
+	assertStringArray(t, ret, []string{})
+
+	if !opts.Value {
+		t.Errorf("Expected Value to be true")
+	}
+}
+
+func TestMarshalError(t *testing.T) {
+	var opts = struct {
+		Value marshalled `short:"v"`
+	}{}
+
+	assertParseFail(t, ErrMarshal, "invalid argument for flag `-v' (expected flags.marshalled): `invalid' is not a valid value, please specify `yes' or `no'", &opts, "-vinvalid")
+}

+ 140 - 0
github.com/jessevdk/go-flags/multitag.go

@@ -0,0 +1,140 @@
+package flags
+
+import (
+	"strconv"
+)
+
+type multiTag struct {
+	value string
+	cache map[string][]string
+}
+
+func newMultiTag(v string) multiTag {
+	return multiTag{
+		value: v,
+	}
+}
+
+func (x *multiTag) scan() (map[string][]string, error) {
+	v := x.value
+
+	ret := make(map[string][]string)
+
+	// This is mostly copied from reflect.StructTag.Get
+	for v != "" {
+		i := 0
+
+		// Skip whitespace
+		for i < len(v) && v[i] == ' ' {
+			i++
+		}
+
+		v = v[i:]
+
+		if v == "" {
+			break
+		}
+
+		// Scan to colon to find key
+		i = 0
+
+		for i < len(v) && v[i] != ' ' && v[i] != ':' && v[i] != '"' {
+			i++
+		}
+
+		if i >= len(v) {
+			return nil, newErrorf(ErrTag, "expected `:' after key name, but got end of tag (in `%v`)", x.value)
+		}
+
+		if v[i] != ':' {
+			return nil, newErrorf(ErrTag, "expected `:' after key name, but got `%v' (in `%v`)", v[i], x.value)
+		}
+
+		if i+1 >= len(v) {
+			return nil, newErrorf(ErrTag, "expected `\"' to start tag value at end of tag (in `%v`)", x.value)
+		}
+
+		if v[i+1] != '"' {
+			return nil, newErrorf(ErrTag, "expected `\"' to start tag value, but got `%v' (in `%v`)", v[i+1], x.value)
+		}
+
+		name := v[:i]
+		v = v[i+1:]
+
+		// Scan quoted string to find value
+		i = 1
+
+		for i < len(v) && v[i] != '"' {
+			if v[i] == '\n' {
+				return nil, newErrorf(ErrTag, "unexpected newline in tag value `%v' (in `%v`)", name, x.value)
+			}
+
+			if v[i] == '\\' {
+				i++
+			}
+			i++
+		}
+
+		if i >= len(v) {
+			return nil, newErrorf(ErrTag, "expected end of tag value `\"' at end of tag (in `%v`)", x.value)
+		}
+
+		val, err := strconv.Unquote(v[:i+1])
+
+		if err != nil {
+			return nil, newErrorf(ErrTag, "Malformed value of tag `%v:%v` => %v (in `%v`)", name, v[:i+1], err, x.value)
+		}
+
+		v = v[i+1:]
+
+		ret[name] = append(ret[name], val)
+	}
+
+	return ret, nil
+}
+
+func (x *multiTag) Parse() error {
+	vals, err := x.scan()
+	x.cache = vals
+
+	return err
+}
+
+func (x *multiTag) cached() map[string][]string {
+	if x.cache == nil {
+		cache, _ := x.scan()
+
+		if cache == nil {
+			cache = make(map[string][]string)
+		}
+
+		x.cache = cache
+	}
+
+	return x.cache
+}
+
+func (x *multiTag) Get(key string) string {
+	c := x.cached()
+
+	if v, ok := c[key]; ok {
+		return v[len(v)-1]
+	}
+
+	return ""
+}
+
+func (x *multiTag) GetMany(key string) []string {
+	c := x.cached()
+	return c[key]
+}
+
+func (x *multiTag) Set(key string, value string) {
+	c := x.cached()
+	c[key] = []string{value}
+}
+
+func (x *multiTag) SetMany(key string, value []string) {
+	c := x.cached()
+	c[key] = value
+}

+ 95 - 0
github.com/jessevdk/go-flags/option.go

@@ -0,0 +1,95 @@
+package flags
+
+import (
+	"fmt"
+	"reflect"
+	"unicode/utf8"
+)
+
+// Option flag information. Contains a description of the option, short and
+// long name as well as a default value and whether an argument for this
+// flag is optional.
+type Option struct {
+	// The description of the option flag. This description is shown
+	// automatically in the builtin help.
+	Description string
+
+	// The short name of the option (a single character). If not 0, the
+	// option flag can be 'activated' using -<ShortName>. Either ShortName
+	// or LongName needs to be non-empty.
+	ShortName rune
+
+	// The long name of the option. If not "", the option flag can be
+	// activated using --<LongName>. Either ShortName or LongName needs
+	// to be non-empty.
+	LongName string
+
+	// The default value of the option.
+	Default []string
+
+	// If true, specifies that the argument to an option flag is optional.
+	// When no argument to the flag is specified on the command line, the
+	// value of Default will be set in the field this option represents.
+	// This is only valid for non-boolean options.
+	OptionalArgument bool
+
+	// The optional value of the option. The optional value is used when
+	// the option flag is marked as having an OptionalArgument. This means
+	// that when the flag is specified, but no option argument is given,
+	// the value of the field this option represents will be set to
+	// OptionalValue. This is only valid for non-boolean options.
+	OptionalValue []string
+
+	// If true, the option _must_ be specified on the command line. If the
+	// option is not specified, the parser will generate an ErrRequired type
+	// error.
+	Required bool
+
+	// A name for the value of an option shown in the Help as --flag [ValueName]
+	ValueName string
+
+	// A mask value to show in the help instead of the default value. This
+	// is useful for hiding sensitive information in the help, such as
+	// passwords.
+	DefaultMask string
+
+	// The struct field which the option represents.
+	field reflect.StructField
+
+	// The struct field value which the option represents.
+	value reflect.Value
+
+	defaultValue reflect.Value
+	iniUsedName  string
+	tag          multiTag
+}
+
+// String converts an option to a human friendly readable string describing the
+// option.
+func (option *Option) String() string {
+	var s string
+	var short string
+
+	if option.ShortName != 0 {
+		data := make([]byte, utf8.RuneLen(option.ShortName))
+		utf8.EncodeRune(data, option.ShortName)
+		short = string(data)
+
+		if len(option.LongName) != 0 {
+			s = fmt.Sprintf("%s%s, %s%s",
+				string(defaultShortOptDelimiter), short,
+				defaultLongOptDelimiter, option.LongName)
+		} else {
+			s = fmt.Sprintf("%s%s", string(defaultShortOptDelimiter), short)
+		}
+	} else if len(option.LongName) != 0 {
+		s = fmt.Sprintf("%s%s", defaultLongOptDelimiter, option.LongName)
+	}
+
+	return s
+}
+
+// Value returns the option value as an interface{}.
+func (option *Option) Value() interface{} {
+	return option.value.Interface()
+}

+ 125 - 0
github.com/jessevdk/go-flags/option_private.go

@@ -0,0 +1,125 @@
+package flags
+
+import (
+	"reflect"
+)
+
+// Set the value of an option to the specified value. An error will be returned
+// if the specified value could not be converted to the corresponding option
+// value type.
+func (option *Option) set(value *string) error {
+	if option.isFunc() {
+		return option.call(value)
+	} else if value != nil {
+		return convert(*value, option.value, option.tag)
+	} else {
+		return convert("", option.value, option.tag)
+	}
+
+	return nil
+}
+
+func (option *Option) canCli() bool {
+	return option.ShortName != 0 || len(option.LongName) != 0
+}
+
+func (option *Option) canArgument() bool {
+	if u := option.isUnmarshaler(); u != nil {
+		return true
+	}
+
+	return !option.isBool()
+}
+
+func (option *Option) clear() {
+	tp := option.value.Type()
+
+	switch tp.Kind() {
+	case reflect.Func:
+		// Skip
+	case reflect.Map:
+		// Empty the map
+		option.value.Set(reflect.MakeMap(tp))
+	default:
+		zeroval := reflect.Zero(tp)
+		option.value.Set(zeroval)
+	}
+}
+
+func (option *Option) isUnmarshaler() Unmarshaler {
+	v := option.value
+
+	for {
+		if !v.CanInterface() {
+			return nil
+		}
+
+		i := v.Interface()
+
+		if u, ok := i.(Unmarshaler); ok {
+			return u
+		}
+
+		if !v.CanAddr() {
+			return nil
+		}
+
+		v = v.Addr()
+	}
+
+	return nil
+}
+
+func (option *Option) isBool() bool {
+	tp := option.value.Type()
+
+	for {
+		switch tp.Kind() {
+		case reflect.Bool:
+			return true
+		case reflect.Slice:
+			return (tp.Elem().Kind() == reflect.Bool)
+		case reflect.Func:
+			return tp.NumIn() == 0
+		case reflect.Ptr:
+			tp = tp.Elem()
+		default:
+			return false
+		}
+	}
+
+	return false
+}
+
+func (option *Option) isFunc() bool {
+	return option.value.Type().Kind() == reflect.Func
+}
+
+func (option *Option) call(value *string) error {
+	var retval []reflect.Value
+
+	if value == nil {
+		retval = option.value.Call(nil)
+	} else {
+		tp := option.value.Type().In(0)
+
+		val := reflect.New(tp)
+		val = reflect.Indirect(val)
+
+		if err := convert(*value, val, option.tag); err != nil {
+			return err
+		}
+
+		retval = option.value.Call([]reflect.Value{val})
+	}
+
+	if len(retval) == 1 && retval[0].Type() == reflect.TypeOf((*error)(nil)).Elem() {
+		if retval[0].Interface() == nil {
+			return nil
+		}
+
+		return retval[0].Interface().(error)
+	}
+
+	return nil
+}

+ 45 - 0
github.com/jessevdk/go-flags/options_test.go

@@ -0,0 +1,45 @@
+package flags
+
+import (
+	"testing"
+)
+
+func TestPassDoubleDash(t *testing.T) {
+	var opts = struct {
+		Value bool `short:"v"`
+	}{}
+
+	p := NewParser(&opts, PassDoubleDash)
+	ret, err := p.ParseArgs([]string{"-v", "--", "-v", "-g"})
+
+	if err != nil {
+		t.Fatalf("Unexpected error: %v", err)
+		return
+	}
+
+	if !opts.Value {
+		t.Errorf("Expected Value to be true")
+	}
+
+	assertStringArray(t, ret, []string{"-v", "-g"})
+}
+
+func TestPassAfterNonOption(t *testing.T) {
+	var opts = struct {
+		Value bool `short:"v"`
+	}{}
+
+	p := NewParser(&opts, PassAfterNonOption)
+	ret, err := p.ParseArgs([]string{"-v", "arg", "-v", "-g"})
+
+	if err != nil {
+		t.Fatalf("Unexpected error: %v", err)
+		return
+	}
+
+	if !opts.Value {
+		t.Errorf("Expected Value to be true")
+	}
+
+	assertStringArray(t, ret, []string{"arg", "-v", "-g"})
+}

+ 54 - 0
github.com/jessevdk/go-flags/optstyle_other.go

@@ -0,0 +1,54 @@
+// +build !windows
+
+package flags
+
+import (
+	"strings"
+)
+
+const (
+	defaultShortOptDelimiter = '-'
+	defaultLongOptDelimiter  = "--"
+	defaultNameArgDelimiter  = '='
+)
+
+func argumentIsOption(arg string) bool {
+	return len(arg) > 0 && arg[0] == '-'
+}
+
+// stripOptionPrefix returns the option without the prefix and whether or
+// not the option is a long option or not.
+func stripOptionPrefix(optname string) (prefix string, name string, islong bool) {
+	if strings.HasPrefix(optname, "--") {
+		return "--", optname[2:], true
+	} else if strings.HasPrefix(optname, "-") {
+		return "-", optname[1:], false
+	}
+
+	return "", optname, false
+}
+
+// splitOption attempts to split the passed option into a name and an argument.
+// When there is no argument specified, nil will be returned for it.
+func splitOption(prefix string, option string, islong bool) (string, *string) {
+	pos := strings.Index(option, "=")
+
+	if (islong && pos >= 0) || (!islong && pos == 1) {
+		rest := option[pos+1:]
+		return option[:pos], &rest
+	}
+
+	return option, nil
+}
+
+// addHelpGroup adds a new group that contains default help parameters.
+func (c *Command) addHelpGroup(showHelp func() error) *Group {
+	var help struct {
+		ShowHelp func() error `short:"h" long:"help" description:"Show this help message"`
+	}
+
+	help.ShowHelp = showHelp
+	ret, _ := c.AddGroup("Help Options", "", &help)
+
+	return ret
+}

+ 85 - 0
github.com/jessevdk/go-flags/optstyle_windows.go

@@ -0,0 +1,85 @@
+package flags
+
+import (
+	"strings"
+)
+
+// Windows uses a front slash for both short and long options.  Also it uses
+// a colon for name/argument delimter.
+const (
+	defaultShortOptDelimiter = '/'
+	defaultLongOptDelimiter  = "/"
+	defaultNameArgDelimiter  = ':'
+)
+
+func argumentIsOption(arg string) bool {
+	// Windows-style options allow front slash for the option
+	// delimiter.
+	return len(arg) > 0 && (arg[0] == '-' || arg[0] == '/')
+}
+
+// stripOptionPrefix returns the option without the prefix and whether or
+// not the option is a long option or not.
+func stripOptionPrefix(optname string) (prefix string, name string, islong bool) {
+	// Determine if the argument is a long option or not.  Windows
+	// typically supports both long and short options with a single
+	// front slash as the option delimiter, so handle this situation
+	// nicely.
+	possplit := 0
+
+	if strings.HasPrefix(optname, "--") {
+		possplit = 2
+		islong = true
+	} else if strings.HasPrefix(optname, "-") {
+		possplit = 1
+		islong = false
+	} else if strings.HasPrefix(optname, "/") {
+		possplit = 1
+		islong = len(optname) > 2
+	}
+
+	return optname[:possplit], optname[possplit:], islong
+}
+
+// splitOption attempts to split the passed option into a name and an argument.
+// When there is no argument specified, nil will be returned for it.
+func splitOption(prefix string, option string, islong bool) (string, *string) {
+	if len(option) == 0 {
+		return option, nil
+	}
+
+	// Windows typically uses a colon for the option name and argument
+	// delimiter while POSIX typically uses an equals.  Support both styles,
+	// but don't allow the two to be mixed.  That is to say /foo:bar and
+	// --foo=bar are acceptable, but /foo=bar and --foo:bar are not.
+	var pos int
+
+	if prefix == "/" {
+		pos = strings.Index(option, ":")
+	} else if len(prefix) > 0 {
+		pos = strings.Index(option, "=")
+	}
+
+	if (islong && pos >= 0) || (!islong && pos == 1) {
+		rest := option[pos+1:]
+		return option[:pos], &rest
+	}
+
+	return option, nil
+}
+
+// addHelpGroup adds a new group that contains default help parameters.
+func (c *Command) addHelpGroup(showHelp func() error) *Group {
+	// Windows CLI applications typically use /? for help, so make both
+	// that available as well as the POSIX style h and help.
+	var help struct {
+		ShowHelpWindows func() error `short:"?" description:"Show this help message"`
+		ShowHelpPosix   func() error `short:"h" long:"help" description:"Show this help message"`
+	}
+
+	help.ShowHelpWindows = showHelp
+	help.ShowHelpPosix = showHelp
+
+	ret, _ := c.AddGroup("Help Options", "", &help)
+	return ret
+}

+ 212 - 0
github.com/jessevdk/go-flags/parser.go

@@ -0,0 +1,212 @@
+// Copyright 2012 Jesse van den Kieboom. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package flags
+
+import (
+	"os"
+	"path"
+)
+
+// A Parser provides command line option parsing. It can contain several
+// option groups each with their own set of options.
+type Parser struct {
+	// Embedded, see Command for more information
+	*Command
+
+	// A usage string to be displayed in the help message.
+	Usage   string
+
+	// Option flags changing the behavior of the parser.
+	Options Options
+
+	internalError error
+}
+
+// Options provides parser options that change the behavior of the option
+// parser.
+type Options uint
+
+const (
+	// None indicates no options.
+	None Options = 0
+
+	// HelpFlag adds a default Help Options group to the parser containing
+	// -h and --help options. When either -h or --help is specified on the
+	// command line, the parser will return the special error of type
+	// ErrHelp. When PrintErrors is also specified, then the help message
+	// will also be automatically printed to os.Stderr.
+	HelpFlag = 1 << iota
+
+	// PassDoubleDash passes all arguments after a double dash, --, as
+	// remaining command line arguments (i.e. they will not be parsed for
+	// flags).
+	PassDoubleDash
+
+	// IgnoreUnknown ignores any unknown options and passes them as
+	// remaining command line arguments instead of generating an error.
+	IgnoreUnknown
+
+	// PrintErrors prints any errors which occurred during parsing to
+	// os.Stderr.
+	PrintErrors
+
+	// PassAfterNonOption passes all arguments after the first non option
+	// as remaining command line arguments. This is equivalent to strict
+	// POSIX processing.
+	PassAfterNonOption
+
+	// Default is a convenient default set of options which should cover
+	// most of the uses of the flags package.
+	Default = HelpFlag | PrintErrors | PassDoubleDash
+)
+
+// Parse is a convenience function to parse command line options with default
+// settings. The provided data is a pointer to a struct representing the
+// default option group (named "Application Options"). For more control, use
+// flags.NewParser.
+func Parse(data interface{}) ([]string, error) {
+	return NewParser(data, Default).Parse()
+}
+
+// ParseArgs is a convenience function to parse command line options with default
+// settings. The provided data is a pointer to a struct representing the
+// default option group (named "Application Options"). The args argument is
+// the list of command line arguments to parse. If you just want to parse the
+// default program command line arguments (i.e. os.Args), then use flags.Parse
+// instead. For more control, use flags.NewParser.
+func ParseArgs(data interface{}, args []string) ([]string, error) {
+	return NewParser(data, Default).ParseArgs(args)
+}
+
+// NewParser creates a new parser. It uses os.Args[0] as the application
+// name and then calls Parser.NewNamedParser (see Parser.NewNamedParser for
+// more details). The provided data is a pointer to a struct representing the
+// default option group (named "Application Options"), or nil if the default
+// group should not be added. The options parameter specifies a set of options
+// for the parser.
+func NewParser(data interface{}, options Options) *Parser {
+	ret := NewNamedParser(path.Base(os.Args[0]), options)
+
+	if data != nil {
+		_, ret.internalError = ret.AddGroup("Application Options", "", data)
+	}
+
+	return ret
+}
+
+// NewNamedParser creates a new parser. The appname is used to display the
+// executable name in the builtin help message. Option groups and commands can
+// be added to this parser by using AddGroup and AddCommand.
+func NewNamedParser(appname string, options Options) *Parser {
+	return &Parser{
+		Command: newCommand(appname, "", "", nil),
+		Options: options,
+	}
+}
+
+// Parse parses the command line arguments from os.Args using Parser.ParseArgs.
+// For more detailed information see ParseArgs.
+func (p *Parser) Parse() ([]string, error) {
+	return p.ParseArgs(os.Args[1:])
+}
+
+// ParseArgs parses the command line arguments according to the option groups that
+// were added to the parser. On successful parsing of the arguments, the
+// remaining, non-option, arguments (if any) are returned. The returned error
+// indicates a parsing error and can be used with PrintError to display
+// contextual information on where the error occurred exactly.
+//
+// When the common help group has been added (AddHelp) and either -h or --help
+// was specified in the command line arguments, a help message will be
+// automatically printed. Furthermore, the special error type ErrHelp is returned.
+// It is up to the caller to exit the program if so desired.
+func (p *Parser) ParseArgs(args []string) ([]string, error) {
+	if p.internalError != nil {
+		return nil, p.internalError
+	}
+
+	p.eachCommand(func(c *Command) {
+		p.eachGroup(func(g *Group) {
+			g.storeDefaults()
+		})
+	}, true)
+
+	// Add builtin help group to all commands if necessary
+	if (p.Options & HelpFlag) != None {
+		p.addHelpGroups(p.showBuiltinHelp)
+	}
+
+	s := &parseState{
+		args:    args,
+		retargs: make([]string, 0, len(args)),
+		command: p.Command,
+		lookup:  p.makeLookup(),
+	}
+
+	for !s.eof() {
+		arg := s.pop()
+
+		// When PassDoubleDash is set and we encounter a --, then
+		// simply append all the rest as arguments and break out
+		if (p.Options&PassDoubleDash) != None && arg == "--" {
+			s.retargs = append(s.retargs, s.args...)
+			break
+		}
+
+		if !argumentIsOption(arg) {
+			// Note: this also sets s.err, so we can just check for
+			// nil here and use s.err later
+			if p.parseNonOption(s) != nil {
+				break
+			}
+
+			continue
+		}
+
+		var err error
+		var option *Option
+
+		prefix, optname, islong := stripOptionPrefix(arg)
+		optname, argument := splitOption(prefix, optname, islong)
+
+		if islong {
+			option, err = p.parseLong(s, optname, argument)
+		} else {
+			option, err = p.parseShort(s, optname, argument)
+		}
+
+		if err != nil {
+			ignoreUnknown := (p.Options & IgnoreUnknown) != None
+			parseErr := wrapError(err)
+
+			if !(parseErr.Type == ErrUnknownFlag && ignoreUnknown) {
+				s.err = parseErr
+				break
+			}
+
+			if ignoreUnknown {
+				s.retargs = append(s.retargs, arg)
+			}
+		} else {
+			delete(s.lookup.required, option)
+		}
+	}
+
+	if s.err == nil {
+		s.checkRequired()
+	}
+
+	if s.err != nil {
+		return nil, p.printError(s.err)
+	}
+
+	if len(s.command.commands) != 0 {
+		return nil, p.printError(s.estimateCommand())
+	} else if cmd, ok := s.command.data.(Commander); ok {
+		return nil, p.printError(cmd.Execute(s.retargs))
+	}
+
+	return s.retargs, nil
+}

+ 243 - 0
github.com/jessevdk/go-flags/parser_private.go

@@ -0,0 +1,243 @@
+package flags
+
+import (
+	"bytes"
+	"fmt"
+	"os"
+	"strings"
+	"unicode/utf8"
+)
+
+type parseState struct {
+	arg     string
+	args    []string
+	retargs []string
+	err     error
+
+	command *Command
+	lookup  lookup
+}
+
+func (p *parseState) eof() bool {
+	return len(p.args) == 0
+}
+
+func (p *parseState) pop() string {
+	if p.eof() {
+		return ""
+	}
+
+	p.arg = p.args[0]
+	p.args = p.args[1:]
+
+	return p.arg
+}
+
+func (p *parseState) peek() string {
+	if p.eof() {
+		return ""
+	}
+
+	return p.args[0]
+}
+
+func (p *parseState) checkRequired() error {
+	required := p.lookup.required
+
+	if len(required) == 0 {
+		return nil
+	}
+
+	names := make([]string, 0, len(required))
+
+	for k := range required {
+		names = append(names, "`"+k.String()+"'")
+	}
+
+	var msg string
+
+	if len(names) == 1 {
+		msg = fmt.Sprintf("the required flag %s was not specified", names[0])
+	} else {
+		msg = fmt.Sprintf("the required flags %s and %s were not specified",
+			strings.Join(names[:len(names)-1], ", "), names[len(names)-1])
+	}
+
+	p.err = newError(ErrRequired, msg)
+	return p.err
+}
+
+func (p *parseState) estimateCommand() error {
+	commands := p.command.sortedCommands()
+	cmdnames := make([]string, len(commands))
+
+	for i, v := range commands {
+		cmdnames[i] = v.Name
+	}
+
+	var msg string
+
+	if len(p.retargs) != 0 {
+		c, l := closestChoice(p.retargs[0], cmdnames)
+		msg = fmt.Sprintf("Unknown command `%s'", p.retargs[0])
+
+		if float32(l)/float32(len(c)) < 0.5 {
+			msg = fmt.Sprintf("%s, did you mean `%s'?", msg, c)
+		} else if len(cmdnames) == 1 {
+			msg = fmt.Sprintf("%s. You should use the %s command",
+				msg,
+				cmdnames[0])
+		} else {
+			msg = fmt.Sprintf("%s. Please specify one command of: %s or %s",
+				msg,
+				strings.Join(cmdnames[:len(cmdnames)-1], ", "),
+				cmdnames[len(cmdnames)-1])
+		}
+	} else {
+		if len(cmdnames) == 1 {
+			msg = fmt.Sprintf("Please specify the %s command", cmdnames[0])
+		} else {
+			msg = fmt.Sprintf("Please specify one command of: %s or %s",
+				strings.Join(cmdnames[:len(cmdnames)-1], ", "),
+				cmdnames[len(cmdnames)-1])
+		}
+	}
+
+	return newError(ErrRequired, msg)
+}
+
+func (p *Parser) parseOption(s *parseState, name string, option *Option, canarg bool, argument *string) (retoption *Option, err error) {
+	if !option.canArgument() {
+		if argument != nil {
+			msg := fmt.Sprintf("bool flag `%s' cannot have an argument", option)
+			return option, newError(ErrNoArgumentForBool, msg)
+		}
+
+		err = option.set(nil)
+	} else if argument != nil {
+		err = option.set(argument)
+	} else if canarg && !s.eof() {
+		arg := s.pop()
+		err = option.set(&arg)
+	} else if option.OptionalArgument {
+		option.clear()
+
+		for _, v := range option.OptionalValue {
+			err = option.set(&v)
+
+			if err != nil {
+				break
+			}
+		}
+	} else {
+		msg := fmt.Sprintf("expected argument for flag `%s'", option)
+		err = newError(ErrExpectedArgument, msg)
+	}
+
+	if err != nil {
+		if _, ok := err.(*Error); !ok {
+			msg := fmt.Sprintf("invalid argument for flag `%s' (expected %s): %s",
+				option,
+				option.value.Type(),
+				err.Error())
+
+			err = newError(ErrMarshal, msg)
+		}
+	}
+
+	return option, err
+}
+
+func (p *Parser) parseLong(s *parseState, name string, argument *string) (option *Option, err error) {
+	if option := s.lookup.longNames[name]; option != nil {
+		// Only long options that are required can consume an argument
+		// from the argument list
+		canarg := !option.OptionalArgument
+
+		return p.parseOption(s, name, option, canarg, argument)
+	}
+
+	return nil, newError(ErrUnknownFlag, fmt.Sprintf("unknown flag `%s'", name))
+}
+
+func (p *Parser) splitShortConcatArg(s *parseState, optname string) (string, *string) {
+	c, n := utf8.DecodeRuneInString(optname)
+
+	if n == len(optname) {
+		return optname, nil
+	}
+
+	first := string(c)
+
+	if option := s.lookup.shortNames[first]; option != nil && option.canArgument() {
+		arg := optname[n:]
+		return first, &arg
+	}
+
+	return optname, nil
+}
+
+func (p *Parser) parseShort(s *parseState, optname string, argument *string) (option *Option, err error) {
+	if argument == nil {
+		optname, argument = p.splitShortConcatArg(s, optname)
+	}
+
+	for i, c := range optname {
+		shortname := string(c)
+
+		if option = s.lookup.shortNames[shortname]; option != nil {
+			// Only the last short argument can consume an argument from
+			// the arguments list, and only if it's non optional
+			canarg := (i+utf8.RuneLen(c) == len(optname)) && !option.OptionalArgument
+
+			if _, err := p.parseOption(s, shortname, option, canarg, argument); err != nil {
+				return option, err
+			}
+		} else {
+			return nil, newError(ErrUnknownFlag, fmt.Sprintf("unknown flag `%s'", shortname))
+		}
+
+		// Only the first option can have a concatted argument, so just
+		// clear argument here
+		argument = nil
+	}
+
+	return option, nil
+}
+
+func (p *Parser) parseNonOption(s *parseState) error {
+	if cmd := s.lookup.commands[s.arg]; cmd != nil {
+		if err := s.checkRequired(); err != nil {
+			return err
+		}
+
+		s.command.Active = cmd
+
+		s.command = cmd
+		s.lookup = cmd.makeLookup()
+	} else if (p.Options & PassAfterNonOption) != None {
+		// If PassAfterNonOption is set then all remaining arguments
+		// are considered positional
+		s.retargs = append(append(s.retargs, s.arg), s.args...)
+		s.args = []string{}
+	} else {
+		s.retargs = append(s.retargs, s.arg)
+	}
+
+	return nil
+}
+
+func (p *Parser) showBuiltinHelp() error {
+	var b bytes.Buffer
+
+	p.WriteHelp(&b)
+	return newError(ErrHelp, b.String())
+}
+
+func (p *Parser) printError(err error) error {
+	if err != nil && (p.Options&PrintErrors) != None {
+		fmt.Fprintln(os.Stderr, err)
+	}
+
+	return err
+}

+ 81 - 0
github.com/jessevdk/go-flags/pointer_test.go

@@ -0,0 +1,81 @@
+package flags
+
+import (
+	"testing"
+)
+
+func TestPointerBool(t *testing.T) {
+	var opts = struct {
+		Value *bool `short:"v"`
+	}{}
+
+	ret := assertParseSuccess(t, &opts, "-v")
+
+	assertStringArray(t, ret, []string{})
+
+	if !*opts.Value {
+		t.Errorf("Expected Value to be true")
+	}
+}
+
+func TestPointerString(t *testing.T) {
+	var opts = struct {
+		Value *string `short:"v"`
+	}{}
+
+	ret := assertParseSuccess(t, &opts, "-v", "value")
+
+	assertStringArray(t, ret, []string{})
+	assertString(t, *opts.Value, "value")
+}
+
+func TestPointerSlice(t *testing.T) {
+	var opts = struct {
+		Value *[]string `short:"v"`
+	}{}
+
+	ret := assertParseSuccess(t, &opts, "-v", "value1", "-v", "value2")
+
+	assertStringArray(t, ret, []string{})
+	assertStringArray(t, *opts.Value, []string{"value1", "value2"})
+}
+
+func TestPointerMap(t *testing.T) {
+	var opts = struct {
+		Value *map[string]int `short:"v"`
+	}{}
+
+	ret := assertParseSuccess(t, &opts, "-v", "k1:2", "-v", "k2:-5")
+
+	assertStringArray(t, ret, []string{})
+
+	if v, ok := (*opts.Value)["k1"]; !ok {
+		t.Errorf("Expected key \"k1\" to exist")
+	} else if v != 2 {
+		t.Errorf("Expected \"k1\" to be 2, but got %#v", v)
+	}
+
+	if v, ok := (*opts.Value)["k2"]; !ok {
+		t.Errorf("Expected key \"k2\" to exist")
+	} else if v != -5 {
+		t.Errorf("Expected \"k2\" to be -5, but got %#v", v)
+	}
+}
+
+type PointerGroup struct {
+	Value bool `short:"v"`
+}
+
+func TestPointerGroup(t *testing.T) {
+	var opts = struct {
+		Group *PointerGroup `group:"Group Options"`
+	}{}
+
+	ret := assertParseSuccess(t, &opts, "-v")
+
+	assertStringArray(t, ret, []string{})
+
+	if !opts.Group.Value {
+		t.Errorf("Expected Group.Value to be true")
+	}
+}

+ 169 - 0
github.com/jessevdk/go-flags/short_test.go

@@ -0,0 +1,169 @@
+package flags
+
+import (
+	"testing"
+)
+
+func TestShort(t *testing.T) {
+	var opts = struct {
+		Value bool `short:"v"`
+	}{}
+
+	ret := assertParseSuccess(t, &opts, "-v")
+
+	assertStringArray(t, ret, []string{})
+
+	if !opts.Value {
+		t.Errorf("Expected Value to be true")
+	}
+}
+
+func TestShortTooLong(t *testing.T) {
+	var opts = struct {
+		Value bool `short:"vv"`
+	}{}
+
+	assertParseFail(t, ErrShortNameTooLong, "short names can only be 1 character long, not `vv'", &opts)
+}
+
+func TestShortRequired(t *testing.T) {
+	var opts = struct {
+		Value bool `short:"v" required:"true"`
+	}{}
+
+	assertParseFail(t, ErrRequired, "the required flag `-v' was not specified", &opts)
+}
+
+func TestShortMultiConcat(t *testing.T) {
+	var opts = struct {
+		V bool `short:"v"`
+		O bool `short:"o"`
+		F bool `short:"f"`
+	}{}
+
+	ret := assertParseSuccess(t, &opts, "-vo", "-f")
+
+	assertStringArray(t, ret, []string{})
+
+	if !opts.V {
+		t.Errorf("Expected V to be true")
+	}
+
+	if !opts.O {
+		t.Errorf("Expected O to be true")
+	}
+
+	if !opts.F {
+		t.Errorf("Expected F to be true")
+	}
+}
+
+func TestShortMultiSlice(t *testing.T) {
+	var opts = struct {
+		Values []bool `short:"v"`
+	}{}
+
+	ret := assertParseSuccess(t, &opts, "-v", "-v")
+
+	assertStringArray(t, ret, []string{})
+	assertBoolArray(t, opts.Values, []bool{true, true})
+}
+
+func TestShortMultiSliceConcat(t *testing.T) {
+	var opts = struct {
+		Values []bool `short:"v"`
+	}{}
+
+	ret := assertParseSuccess(t, &opts, "-vvv")
+
+	assertStringArray(t, ret, []string{})
+	assertBoolArray(t, opts.Values, []bool{true, true, true})
+}
+
+func TestShortWithEqualArg(t *testing.T) {
+	var opts = struct {
+		Value string `short:"v"`
+	}{}
+
+	ret := assertParseSuccess(t, &opts, "-v=value")
+
+	assertStringArray(t, ret, []string{})
+	assertString(t, opts.Value, "value")
+}
+
+func TestShortWithArg(t *testing.T) {
+	var opts = struct {
+		Value string `short:"v"`
+	}{}
+
+	ret := assertParseSuccess(t, &opts, "-vvalue")
+
+	assertStringArray(t, ret, []string{})
+	assertString(t, opts.Value, "value")
+}
+
+func TestShortArg(t *testing.T) {
+	var opts = struct {
+		Value string `short:"v"`
+	}{}
+
+	ret := assertParseSuccess(t, &opts, "-v", "value")
+
+	assertStringArray(t, ret, []string{})
+	assertString(t, opts.Value, "value")
+}
+
+func TestShortMultiWithEqualArg(t *testing.T) {
+	var opts = struct {
+		F     []bool `short:"f"`
+		Value string `short:"v"`
+	}{}
+
+	assertParseFail(t, ErrExpectedArgument, "expected argument for flag `-v'", &opts, "-ffv=value")
+}
+
+func TestShortMultiArg(t *testing.T) {
+	var opts = struct {
+		F     []bool `short:"f"`
+		Value string `short:"v"`
+	}{}
+
+	ret := assertParseSuccess(t, &opts, "-ffv", "value")
+
+	assertStringArray(t, ret, []string{})
+	assertBoolArray(t, opts.F, []bool{true, true})
+	assertString(t, opts.Value, "value")
+}
+
+func TestShortMultiArgConcatFail(t *testing.T) {
+	var opts = struct {
+		F     []bool `short:"f"`
+		Value string `short:"v"`
+	}{}
+
+	assertParseFail(t, ErrExpectedArgument, "expected argument for flag `-v'", &opts, "-ffvvalue")
+}
+
+func TestShortMultiArgConcat(t *testing.T) {
+	var opts = struct {
+		F     []bool `short:"f"`
+		Value string `short:"v"`
+	}{}
+
+	ret := assertParseSuccess(t, &opts, "-vff")
+
+	assertStringArray(t, ret, []string{})
+	assertString(t, opts.Value, "ff")
+}
+
+func TestShortOptional(t *testing.T) {
+	var opts = struct {
+		F     []bool `short:"f"`
+		Value string `short:"v" optional:"yes" optional-value:"value"`
+	}{}
+
+	ret := assertParseSuccess(t, &opts, "-fv", "f")
+
+	assertStringArray(t, ret, []string{"f"})
+	assertString(t, opts.Value, "value")
+}

+ 39 - 0
github.com/jessevdk/go-flags/tag_test.go

@@ -0,0 +1,39 @@
+package flags
+
+import (
+	"testing"
+)
+
+func TestTagMissingColon(t *testing.T) {
+	var opts = struct {
+		Value bool `short`
+	}{}
+
+	assertParseFail(t, ErrTag, "expected `:' after key name, but got end of tag (in `short`)", &opts, "")
+}
+
+func TestTagMissingValue(t *testing.T) {
+	var opts = struct {
+		Value bool `short:`
+	}{}
+
+	assertParseFail(t, ErrTag, "expected `\"' to start tag value at end of tag (in `short:`)", &opts, "")
+}
+
+func TestTagMissingQuote(t *testing.T) {
+	var opts = struct {
+		Value bool `short:"v`
+	}{}
+
+	assertParseFail(t, ErrTag, "expected end of tag value `\"' at end of tag (in `short:\"v`)", &opts, "")
+}
+
+func TestTagNewline(t *testing.T) {
+	var opts = struct {
+		Value bool `long:"verbose" description:"verbose
+something"`
+	}{}
+
+	assertParseFail(t, ErrTag, "unexpected newline in tag value `description' (in `long:\"verbose\" description:\"verbose\nsomething\"`)", &opts, "")
+}
+

+ 5 - 0
github.com/jessevdk/go-flags/termsize.go

@@ -0,0 +1,5 @@
+package flags
+
+func getTerminalColumns() int {
+	return 80
+}

+ 66 - 0
github.com/jessevdk/go-flags/unknown_test.go

@@ -0,0 +1,66 @@
+package flags
+
+import (
+	"testing"
+)
+
+func TestUnknownFlags(t *testing.T) {
+	var opts = struct {
+		Verbose []bool `short:"v" long:"verbose" description:"Verbose output"`
+	}{}
+
+	args := []string{
+		"-f",
+	}
+
+	p := NewParser(&opts, 0)
+	args, err := p.ParseArgs(args)
+
+	if err == nil {
+		t.Fatal("Expected error for unknown argument")
+	}
+}
+
+func TestIgnoreUnknownFlags(t *testing.T) {
+	var opts = struct {
+		Verbose []bool `short:"v" long:"verbose" description:"Verbose output"`
+	}{}
+
+	args := []string{
+		"hello",
+		"world",
+		"-v",
+		"--foo=bar",
+		"--verbose",
+		"-f",
+	}
+
+	p := NewParser(&opts, IgnoreUnknown)
+	args, err := p.ParseArgs(args)
+
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	exargs := []string{
+		"hello",
+		"world",
+		"--foo=bar",
+		"-f",
+	}
+
+	issame := (len(args) == len(exargs))
+
+	if issame {
+		for i := 0; i < len(args); i++ {
+			if args[i] != exargs[i] {
+				issame = false
+				break
+			}
+		}
+	}
+
+	if !issame {
+		t.Fatalf("Expected %v but got %v", exargs, args)
+	}
+}

+ 1 - 1
main.go

@@ -16,8 +16,8 @@ import (
 
 
 	"github.com/calmh/ini"
 	"github.com/calmh/ini"
 	"github.com/calmh/syncthing/discover"
 	"github.com/calmh/syncthing/discover"
+	flags "github.com/calmh/syncthing/github.com/jessevdk/go-flags"
 	"github.com/calmh/syncthing/protocol"
 	"github.com/calmh/syncthing/protocol"
-	flags "github.com/jessevdk/go-flags"
 )
 )
 
 
 type Options struct {
 type Options struct {