Merge pull request #163 from pierrec/master

Option new methods
diff --git a/.travis.yml b/.travis.yml
index 3165f00..c47256e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,12 +1,14 @@
 language: go
 
+go:
+  - 1.6
+
 install:
   # go-flags
   - go get -d -v ./...
   - go build -v ./...
 
   # linting
-  - go get golang.org/x/tools/cmd/vet
   - go get github.com/golang/lint
   - go install github.com/golang/lint/golint
 
diff --git a/arg.go b/arg.go
index d160644..8ec6204 100644
--- a/arg.go
+++ b/arg.go
@@ -12,9 +12,12 @@
 	// A description of the positional argument (used in the help)
 	Description string
 
-	// Whether a positional argument is required
+	// The minimal number of required positional arguments
 	Required int
 
+	// The maximum number of required positional arguments
+	RequiredMaximum int
+
 	value reflect.Value
 	tag   multiTag
 }
diff --git a/arg_test.go b/arg_test.go
index 117e90e..c7c0a61 100644
--- a/arg_test.go
+++ b/arg_test.go
@@ -131,3 +131,33 @@
 	assertString(t, opts.Positional.Rest[1], "rest2")
 	assertString(t, opts.Positional.Rest[2], "rest3")
 }
+
+func TestPositionalRequiredRestRangeFail(t *testing.T) {
+	var opts = struct {
+		Value bool `short:"v"`
+
+		Positional struct {
+			Rest []string `required:"1-2"`
+		} `positional-args:"yes"`
+	}{}
+
+	p := NewParser(&opts, None)
+	_, err := p.ParseArgs([]string{"rest1", "rest2", "rest3"})
+
+	assertError(t, err, ErrRequired, "the required argument `Rest (at most 2 arguments, but got 3)` was not provided")
+}
+
+func TestPositionalRequiredRestRangeEmptyFail(t *testing.T) {
+	var opts = struct {
+		Value bool `short:"v"`
+
+		Positional struct {
+			Rest []string `required:"0-0"`
+		} `positional-args:"yes"`
+	}{}
+
+	p := NewParser(&opts, None)
+	_, err := p.ParseArgs([]string{"some", "thing"})
+
+	assertError(t, err, ErrRequired, "the required argument `Rest (zero arguments)` was not provided")
+}
diff --git a/command.go b/command.go
index a30f560..2662843 100644
--- a/command.go
+++ b/command.go
@@ -112,9 +112,9 @@
 	return nil
 }
 
-// Find an option that is part of the command, or any of its
-// parent commands, by matching its long name
-// (including the option namespace).
+// FindOptionByLongName finds an option that is part of the command, or any of
+// its parent commands, by matching its long name (including the option
+// namespace).
 func (c *Command) FindOptionByLongName(longName string) (option *Option) {
 	for option == nil && c != nil {
 		option = c.Group.FindOptionByLongName(longName)
@@ -125,9 +125,9 @@
 	return option
 }
 
-// Find an option that is part of the command, or any of its
-// parent commands, by matching its long name
-// (including the option namespace).
+// FindOptionByShortName finds an option that is part of the command, or any of
+// its parent commands, by matching its long name (including the option
+// namespace).
 func (c *Command) FindOptionByShortName(shortName rune) (option *Option) {
 	for option == nil && c != nil {
 		option = c.Group.FindOptionByShortName(shortName)
@@ -181,22 +181,36 @@
 					name = field.Name
 				}
 
-				var required int
+				required := -1
+				requiredMaximum := -1
 
 				sreq := m.Get("required")
 
 				if sreq != "" {
 					required = 1
 
-					if preq, err := strconv.ParseInt(sreq, 10, 32); err == nil {
-						required = int(preq)
+					rng := strings.SplitN(sreq, "-", 2)
+
+					if len(rng) > 1 {
+						if preq, err := strconv.ParseInt(rng[0], 10, 32); err == nil {
+							required = int(preq)
+						}
+
+						if preq, err := strconv.ParseInt(rng[1], 10, 32); err == nil {
+							requiredMaximum = int(preq)
+						}
+					} else {
+						if preq, err := strconv.ParseInt(sreq, 10, 32); err == nil {
+							required = int(preq)
+						}
 					}
 				}
 
 				arg := &Arg{
-					Name:        name,
-					Description: m.Get("description"),
-					Required:    required,
+					Name:            name,
+					Description:     m.Get("description"),
+					Required:        required,
+					RequiredMaximum: requiredMaximum,
 
 					value: realval.Field(i),
 					tag:   m,
diff --git a/command_test.go b/command_test.go
index 72d397d..dc04b66 100644
--- a/command_test.go
+++ b/command_test.go
@@ -430,17 +430,31 @@
 func TestDefaultOnCommand(t *testing.T) {
 	var opts = struct {
 		Command struct {
-			G bool `short:"g" default:"true"`
+			G string `short:"g" default:"value"`
 		} `command:"cmd"`
 	}{}
 
 	assertParseSuccess(t, &opts, "cmd")
 
-	if !opts.Command.G {
-		t.Errorf("Expected G to be true")
+	if opts.Command.G != "value" {
+		t.Errorf("Expected G to be \"value\"")
 	}
 }
 
+func TestAfterNonCommand(t *testing.T) {
+	var opts = struct {
+		Value bool `short:"v"`
+
+		Cmd1 struct {
+		} `command:"remove"`
+
+		Cmd2 struct {
+		} `command:"add"`
+	}{}
+
+	assertParseFail(t, ErrUnknownCommand, "Unknown command `nocmd'. Please specify one command of: add or remove", &opts, "nocmd", "remove")
+}
+
 func TestSubcommandsOptional(t *testing.T) {
 	var opts = struct {
 		Value bool `short:"v"`
@@ -467,17 +481,41 @@
 	}
 }
 
+func TestSubcommandsOptionalAfterNonCommand(t *testing.T) {
+	var opts = struct {
+		Value bool `short:"v"`
+
+		Cmd1 struct {
+		} `command:"remove"`
+
+		Cmd2 struct {
+		} `command:"add"`
+	}{}
+
+	p := NewParser(&opts, None)
+	p.SubcommandsOptional = true
+
+	retargs, err := p.ParseArgs([]string{"nocmd", "remove"})
+
+	if err != nil {
+		t.Fatalf("Unexpected error: %v", err)
+		return
+	}
+
+	assertStringArray(t, retargs, []string{"nocmd", "remove"})
+}
+
 func TestCommandAlias(t *testing.T) {
 	var opts = struct {
 		Command struct {
-			G bool `short:"g" default:"true"`
+			G string `short:"g" default:"value"`
 		} `command:"cmd" alias:"cm"`
 	}{}
 
 	assertParseSuccess(t, &opts, "cm")
 
-	if !opts.Command.G {
-		t.Errorf("Expected G to be true")
+	if opts.Command.G != "value" {
+		t.Errorf("Expected G to be \"value\"")
 	}
 }
 
diff --git a/completion.go b/completion.go
index 894f1d6..708fa9e 100644
--- a/completion.go
+++ b/completion.go
@@ -77,7 +77,7 @@
 	n := make([]Completion, 0, len(names))
 
 	for k, opt := range names {
-		if strings.HasPrefix(k, match) {
+		if strings.HasPrefix(k, match) && !opt.Hidden {
 			n = append(n, Completion{
 				Item:        prefix + k,
 				Description: opt.Description,
diff --git a/completion_test.go b/completion_test.go
index f440fd7..72ee158 100644
--- a/completion_test.go
+++ b/completion_test.go
@@ -40,6 +40,7 @@
 	Debug    bool `short:"d" long:"debug" description:"Enable debug"`
 	Version  bool `long:"version" description:"Show version"`
 	Required bool `long:"required" required:"true" description:"This is required"`
+	Hidden   bool `long:"hidden" hidden:"true" description:"This is hidden"`
 
 	AddCommand struct {
 		Positional struct {
diff --git a/error.go b/error.go
index 2f27aee..05528d8 100644
--- a/error.go
+++ b/error.go
@@ -55,6 +55,9 @@
 	// ErrInvalidChoice indicates an invalid option value which only allows
 	// a certain number of choices.
 	ErrInvalidChoice
+
+	// ErrInvalidTag indicates an invalid tag or invalid use of an existing tag
+	ErrInvalidTag
 )
 
 func (e ErrorType) String() string {
@@ -87,6 +90,8 @@
 		return "unknown command"
 	case ErrInvalidChoice:
 		return "invalid choice"
+	case ErrInvalidTag:
+		return "invalid tag"
 	}
 
 	return "unrecognized error type"
diff --git a/example_test.go b/example_test.go
index f7be2bb..4321ed8 100644
--- a/example_test.go
+++ b/example_test.go
@@ -41,7 +41,7 @@
 
 		// Example of positional arguments
 		Args struct {
-			Id   string
+			ID   string
 			Num  int
 			Rest []string
 		} `positional-args:"yes" required:"yes"`
@@ -92,7 +92,7 @@
 	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("Filename: %v\n", opts.Filename)
-	fmt.Printf("Args.Id: %s\n", opts.Args.Id)
+	fmt.Printf("Args.ID: %s\n", opts.Args.ID)
 	fmt.Printf("Args.Num: %d\n", opts.Args.Num)
 	fmt.Printf("Args.Rest: %v\n", opts.Args.Rest)
 
@@ -104,7 +104,7 @@
 	// PtrSlice: [hello world]
 	// IntMap: [a:1 b:5]
 	// Filename: hello.go
-	// Args.Id: id
+	// Args.ID: id
 	// Args.Num: 10
 	// Args.Rest: [remaining1 remaining2]
 }
diff --git a/group.go b/group.go
index debb8de..b1ef6ed 100644
--- a/group.go
+++ b/group.go
@@ -110,16 +110,16 @@
 	return option
 }
 
-// Find an option that is part of the group, or any of its subgroups,
-// by matching its long name (including the option namespace).
+// FindOptionByLongName finds an option that is part of the group, or any of its
+// subgroups, by matching its long name (including the option namespace).
 func (g *Group) FindOptionByLongName(longName string) *Option {
 	return g.findOption(func(option *Option) bool {
 		return option.LongNameWithNamespace() == longName
 	})
 }
 
-// Find an option that is part of the group, or any of its subgroups,
-// by matching its short name.
+// FindOptionByShortName finds an option that is part of the group, or any of
+// its subgroups, by matching its short name.
 func (g *Group) FindOptionByShortName(shortName rune) *Option {
 	return g.findOption(func(option *Option) bool {
 		return option.ShortName == shortName
@@ -276,6 +276,12 @@
 			tag:   mtag,
 		}
 
+		if option.isBool() && option.Default != nil {
+			return newErrorf(ErrInvalidTag,
+				"boolean flag `%s' may not have default values, they always default to `false' and can only be turned on",
+				option.shortAndLongName())
+		}
+
 		g.options = append(g.options, option)
 	}
 
diff --git a/help.go b/help.go
index c0b808d..1b0c2c6 100644
--- a/help.go
+++ b/help.go
@@ -412,22 +412,41 @@
 			}
 		})
 
-		if len(c.args) > 0 {
+		var args []*Arg
+		for _, arg := range c.args {
+			if arg.Description != "" {
+				args = append(args, arg)
+			}
+		}
+
+		if len(args) > 0 {
 			if c == p.Command {
 				fmt.Fprintf(wr, "\nArguments:\n")
 			} else {
 				fmt.Fprintf(wr, "\n[%s command arguments]\n", c.Name)
 			}
 
-			maxlen := aligninfo.descriptionStart()
+			descStart := aligninfo.descriptionStart() + paddingBeforeOption
 
-			for _, arg := range c.args {
-				prefix := strings.Repeat(" ", paddingBeforeOption)
-				fmt.Fprintf(wr, "%s%s", prefix, arg.Name)
+			for _, arg := range args {
+				argPrefix := strings.Repeat(" ", paddingBeforeOption)
+				argPrefix += arg.Name
 
 				if len(arg.Description) > 0 {
-					align := strings.Repeat(" ", maxlen-len(arg.Name)-1)
-					fmt.Fprintf(wr, ":%s%s", align, arg.Description)
+					argPrefix += ":"
+					wr.WriteString(argPrefix)
+
+					// Space between "arg:" and the description start
+					descPadding := strings.Repeat(" ", descStart-len(argPrefix))
+					// How much space the description gets before wrapping
+					descWidth := aligninfo.terminalColumns - 1 - descStart
+					// Whitespace to which we can indent new description lines
+					descPrefix := strings.Repeat(" ", descStart)
+
+					wr.WriteString(descPadding)
+					wr.WriteString(wrapText(arg.Description, descWidth, descPrefix))
+				} else {
+					wr.WriteString(argPrefix)
 				}
 
 				fmt.Fprintln(wr)
diff --git a/help_test.go b/help_test.go
index 33d21bf..1248ebe 100644
--- a/help_test.go
+++ b/help_test.go
@@ -53,8 +53,9 @@
 	} `command:"hidden-command" description:"A hidden command" hidden:"yes"`
 
 	Args struct {
-		Filename string `positional-arg-name:"filename" description:"A filename"`
-		Number   int    `positional-arg-name:"num" description:"A number"`
+		Filename     string  `positional-arg-name:"filename" description:"A filename with a long description to trigger line wrapping"`
+		Number       int     `positional-arg-name:"num" description:"A number"`
+		HiddenInHelp float32 `positional-arg-name:"hidden-in-help" required:"yes"`
 	} `positional-args:"yes"`
 }
 
@@ -84,7 +85,8 @@
 
 		if runtime.GOOS == "windows" {
 			expected = `Usage:
-  TestHelp [OPTIONS] [filename] [num] <command>
+  TestHelp [OPTIONS] [filename] [num] [hidden-in-help] <command>
+
 
 Application Options:
   /v, /verbose                              Show verbose debug information
@@ -129,7 +131,7 @@
 `
 		} else {
 			expected = `Usage:
-  TestHelp [OPTIONS] [filename] [num] <command>
+  TestHelp [OPTIONS] [filename] [num] [hidden-in-help] <command>
 
 Application Options:
   -v, --verbose                             Show verbose debug information
@@ -165,7 +167,8 @@
   -h, --help                                Show this help message
 
 Arguments:
-  filename:                                 A filename
+  filename:                                 A filename with a long description
+                                            to trigger line wrapping
   num:                                      A number
 
 Available commands:
@@ -213,6 +216,8 @@
 .SH DESCRIPTION
 This is a somewhat \fBlonger\fP description of what this does
 .SH OPTIONS
+.SS Application Options
+The application options
 .TP
 \fB\fB\-v\fR, \fB\-\-verbose\fR\fP
 Show verbose debug information
@@ -245,15 +250,18 @@
 .TP
 \fB\fB\-\-opt-with-choices\fR \fIchoice\fR\fP
 Option with choices
+.SS Other Options
 .TP
 \fB\fB\-s\fR <default: \fI"some", "value"\fR>\fP
 A slice of strings
 .TP
 \fB\fB\-\-intmap\fR <default: \fI"a:1"\fR>\fP
 A map from string to int
+.SS Subgroup
 .TP
 \fB\fB\-\-sip.opt\fR\fP
 This is a subgroup option
+.SS Subsubgroup
 .TP
 \fB\fB\-\-sip.sap.opt\fR\fP
 This is a subsubgroup option
diff --git a/ini.go b/ini.go
index a6d6c27..16e27c0 100644
--- a/ini.go
+++ b/ini.go
@@ -145,7 +145,7 @@
 	return i.parse(ini)
 }
 
-// WriteFile writes the flags as ini format into a file. See WriteIni
+// WriteFile writes the flags as ini format into a file. See Write
 // 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 {
diff --git a/ini_test.go b/ini_test.go
index a033db9..1d25d45 100644
--- a/ini_test.go
+++ b/ini_test.go
@@ -21,7 +21,7 @@
 		expected string
 	}{
 		{
-			[]string{"-vv", "--intmap=a:2", "--intmap", "b:3", "filename", "0", "command"},
+			[]string{"-vv", "--intmap=a:2", "--intmap", "b:3", "filename", "0", "3.14", "command"},
 			IniDefault,
 			`[Application Options]
 ; Show verbose debug information
@@ -42,7 +42,7 @@
 `,
 		},
 		{
-			[]string{"-vv", "--intmap=a:2", "--intmap", "b:3", "filename", "0", "command"},
+			[]string{"-vv", "--intmap=a:2", "--intmap", "b:3", "filename", "0", "3.14", "command"},
 			IniDefault | IniIncludeDefaults,
 			`[Application Options]
 ; Show verbose debug information
@@ -104,7 +104,7 @@
 `,
 		},
 		{
-			[]string{"filename", "0", "command"},
+			[]string{"filename", "0", "3.14", "command"},
 			IniDefault | IniIncludeDefaults | IniCommentDefaults,
 			`[Application Options]
 ; Show verbose debug information
@@ -164,7 +164,7 @@
 `,
 		},
 		{
-			[]string{"--default=New value", "--default-array=New value", "--default-map=new:value", "filename", "0", "command"},
+			[]string{"--default=New value", "--default-array=New value", "--default-map=new:value", "filename", "0", "3.14", "command"},
 			IniDefault | IniIncludeDefaults | IniCommentDefaults,
 			`[Application Options]
 ; Show verbose debug information
diff --git a/man.go b/man.go
index 8e4a8b7..0cb114e 100644
--- a/man.go
+++ b/man.go
@@ -38,10 +38,21 @@
 
 func writeManPageOptions(wr io.Writer, grp *Group) {
 	grp.eachGroup(func(group *Group) {
-		if group.Hidden {
+		if group.Hidden || len(group.options) == 0 {
 			return
 		}
 
+		// If the parent (grp) has any subgroups, display their descriptions as
+		// subsection headers similar to the output of --help.
+		if group.ShortDescription != "" && len(grp.groups) > 0 {
+			fmt.Fprintf(wr, ".SS %s\n", group.ShortDescription)
+
+			if group.LongDescription != "" {
+				formatForMan(wr, group.LongDescription)
+				fmt.Fprintln(wr, "")
+			}
+		}
+
 		for _, opt := range group.options {
 			if !opt.canCli() || opt.Hidden {
 				continue
diff --git a/marshal_test.go b/marshal_test.go
index 59c9cce..4cfe865 100644
--- a/marshal_test.go
+++ b/marshal_test.go
@@ -5,13 +5,13 @@
 	"testing"
 )
 
-type marshalled bool
+type marshalled string
 
 func (m *marshalled) UnmarshalFlag(value string) error {
 	if value == "yes" {
-		*m = true
+		*m = "true"
 	} else if value == "no" {
-		*m = false
+		*m = "false"
 	} else {
 		return fmt.Errorf("`%s' is not a valid value, please specify `yes' or `no'", value)
 	}
@@ -20,7 +20,7 @@
 }
 
 func (m marshalled) MarshalFlag() (string, error) {
-	if m {
+	if m == "true" {
 		return "yes", nil
 	}
 
@@ -42,8 +42,8 @@
 
 	assertStringArray(t, ret, []string{})
 
-	if !opts.Value {
-		t.Errorf("Expected Value to be true")
+	if opts.Value != "true" {
+		t.Errorf("Expected Value to be \"true\"")
 	}
 }
 
@@ -56,8 +56,8 @@
 
 	assertStringArray(t, ret, []string{})
 
-	if !opts.Value {
-		t.Errorf("Expected Value to be true")
+	if opts.Value != "true" {
+		t.Errorf("Expected Value to be \"true\"")
 	}
 }
 
@@ -70,8 +70,8 @@
 
 	assertStringArray(t, ret, []string{})
 
-	if !opts.Value {
-		t.Errorf("Expected Value to be true")
+	if opts.Value != "true" {
+		t.Errorf("Expected Value to be \"true\"")
 	}
 }
 
@@ -83,6 +83,28 @@
 	assertParseFail(t, ErrMarshal, fmt.Sprintf("invalid argument for flag `%cv' (expected flags.marshalled): `invalid' is not a valid value, please specify `yes' or `no'", defaultShortOptDelimiter), &opts, "-vinvalid")
 }
 
+func TestUnmarshalPositionalError(t *testing.T) {
+	var opts = struct {
+		Args struct {
+			Value marshalled
+		} `positional-args:"yes"`
+	}{}
+
+	parser := NewParser(&opts, Default&^PrintErrors)
+	_, err := parser.ParseArgs([]string{"invalid"})
+
+	msg := "`invalid' is not a valid value, please specify `yes' or `no'"
+
+	if err == nil {
+		assertFatalf(t, "Expected error: %s", msg)
+		return
+	}
+
+	if err.Error() != msg {
+		assertErrorf(t, "Expected error message %#v, but got %#v", msg, err.Error())
+	}
+}
+
 func TestMarshalError(t *testing.T) {
 	var opts = struct {
 		Value marshalledError `short:"v"`
diff --git a/option.go b/option.go
index ad28e7f..3635df3 100644
--- a/option.go
+++ b/option.go
@@ -1,6 +1,7 @@
 package flags
 
 import (
+	"bytes"
 	"fmt"
 	"reflect"
 	"strings"
@@ -416,3 +417,22 @@
 
 	option.defaultLiteral = def
 }
+
+func (option *Option) shortAndLongName() string {
+	ret := &bytes.Buffer{}
+
+	if option.ShortName != 0 {
+		ret.WriteRune(defaultShortOptDelimiter)
+		ret.WriteRune(option.ShortName)
+	}
+
+	if len(option.LongName) != 0 {
+		if option.ShortName != 0 {
+			ret.WriteRune('/')
+		}
+
+		ret.WriteString(option.LongName)
+	}
+
+	return ret.String()
+}
diff --git a/parser.go b/parser.go
index 7064553..6477daa 100644
--- a/parser.go
+++ b/parser.go
@@ -42,6 +42,16 @@
 	// You can override this default behavior by specifying a custom CompletionHandler.
 	CompletionHandler func(items []Completion)
 
+	// CommandHandler is a function that gets called to handle execution of a
+	// command. By default, the command will simply be executed. This can be
+	// overridden to perform certain actions (such as applying global flags)
+	// just before the command is executed. Note that if you override the
+	// handler it is your responsibility to call the command.Execute function.
+	//
+	// The command passed into CommandHandler may be nil in case there is no
+	// command to be executed when parsing has finished.
+	CommandHandler func(command Commander, args []string) error
+
 	internalError error
 }
 
@@ -299,7 +309,13 @@
 	} else if len(s.command.commands) != 0 && !s.command.SubcommandsOptional {
 		reterr = s.estimateCommand()
 	} else if cmd, ok := s.command.data.(Commander); ok {
-		reterr = cmd.Execute(s.retargs)
+		if p.CommandHandler != nil {
+			reterr = p.CommandHandler(cmd, s.retargs)
+		} else {
+			reterr = cmd.Execute(s.retargs)
+		}
+	} else if p.CommandHandler != nil {
+		reterr = p.CommandHandler(nil, s.retargs)
 	}
 
 	if reterr != nil {
@@ -362,7 +378,7 @@
 			var reqnames []string
 
 			for _, arg := range p.positional {
-				argRequired := (!arg.isRemaining() && p.command.ArgsRequired) || arg.Required != 0
+				argRequired := (!arg.isRemaining() && p.command.ArgsRequired) || arg.Required != -1 || arg.RequiredMaximum != -1
 
 				if !argRequired {
 					continue
@@ -379,6 +395,20 @@
 						}
 
 						reqnames = append(reqnames, "`"+arg.Name+" (at least "+fmt.Sprintf("%d", arg.Required)+" "+arguments+")`")
+					} else if arg.RequiredMaximum != -1 && arg.value.Len() > arg.RequiredMaximum {
+						if arg.RequiredMaximum == 0 {
+							reqnames = append(reqnames, "`"+arg.Name+" (zero arguments)`")
+						} else {
+							var arguments string
+
+							if arg.RequiredMaximum > 1 {
+								arguments = "arguments, but got " + fmt.Sprintf("%d", arg.value.Len())
+							} else {
+								arguments = "argument"
+							}
+
+							reqnames = append(reqnames, "`"+arg.Name+" (at most "+fmt.Sprintf("%d", arg.RequiredMaximum)+" "+arguments+")`")
+						}
 					}
 				} else {
 					reqnames = append(reqnames, "`"+arg.Name+"`")
@@ -586,6 +616,7 @@
 		arg := p.positional[0]
 
 		if err := convert(args[0], arg.value, arg.tag); err != nil {
+			p.err = err
 			return err
 		}
 
@@ -605,10 +636,19 @@
 		return s.addArgs(s.arg)
 	}
 
-	if cmd := s.lookup.commands[s.arg]; cmd != nil {
-		s.command.Active = cmd
-		cmd.fillParseState(s)
-	} else if (p.Options & PassAfterNonOption) != None {
+	if len(s.command.commands) > 0 && len(s.retargs) == 0 {
+		if cmd := s.lookup.commands[s.arg]; cmd != nil {
+			s.command.Active = cmd
+			cmd.fillParseState(s)
+
+			return nil
+		} else if !s.command.SubcommandsOptional {
+			s.addArgs(s.arg)
+			return newErrorf(ErrUnknownCommand, "Unknown command `%s'", s.arg)
+		}
+	}
+
+	if (p.Options & PassAfterNonOption) != None {
 		// If PassAfterNonOption is set then all remaining arguments
 		// are considered positional
 		if err := s.addArgs(s.arg); err != nil {
diff --git a/parser_test.go b/parser_test.go
index b57dbee..9f527b7 100644
--- a/parser_test.go
+++ b/parser_test.go
@@ -4,6 +4,7 @@
 	"fmt"
 	"os"
 	"reflect"
+	"runtime"
 	"strconv"
 	"strings"
 	"testing"
@@ -17,7 +18,7 @@
 	Float64        float64 `long:"f"`
 	Float64Default float64 `long:"fd" default:"-3.14"`
 
-	NumericFlag bool `short:"3" default:"false"`
+	NumericFlag bool `short:"3"`
 
 	String            string `long:"str"`
 	StringDefault     string `long:"strd" default:"abc"`
@@ -132,6 +133,18 @@
 	}
 }
 
+func TestNoDefaultsForBools(t *testing.T) {
+	var opts struct {
+		DefaultBool bool `short:"d" default:"true"`
+	}
+
+	if runtime.GOOS == "windows" {
+		assertParseFail(t, ErrInvalidTag, "boolean flag `/d' may not have default values, they always default to `false' and can only be turned on", &opts)
+	} else {
+		assertParseFail(t, ErrInvalidTag, "boolean flag `-d' may not have default values, they always default to `false' and can only be turned on", &opts)
+	}
+}
+
 func TestUnquoting(t *testing.T) {
 	var tests = []struct {
 		arg   string
@@ -202,28 +215,33 @@
 	}
 }
 
-// envRestorer keeps a copy of a set of env variables and can restore the env from them
-type envRestorer struct {
+// EnvRestorer keeps a copy of a set of env variables and can restore the env from them
+type EnvRestorer struct {
 	env map[string]string
 }
 
-func (r *envRestorer) Restore() {
+func (r *EnvRestorer) Restore() {
 	os.Clearenv()
+
 	for k, v := range r.env {
 		os.Setenv(k, v)
 	}
 }
 
 // EnvSnapshot returns a snapshot of the currently set env variables
-func EnvSnapshot() *envRestorer {
-	r := envRestorer{make(map[string]string)}
+func EnvSnapshot() *EnvRestorer {
+	r := EnvRestorer{make(map[string]string)}
+
 	for _, kv := range os.Environ() {
 		parts := strings.SplitN(kv, "=", 2)
+
 		if len(parts) != 2 {
 			panic("got a weird env variable: " + kv)
 		}
+
 		r.env[parts[0]] = parts[1]
 	}
+
 	return &r
 }
 
@@ -481,7 +499,93 @@
 	}
 
 	assertParseSuccess(t, &opts, "-v")
+
 	if !opts.V {
 		t.Errorf("Expected V to be true")
 	}
 }
+
+type command struct {
+}
+
+func (c *command) Execute(args []string) error {
+	return nil
+}
+
+func TestCommandHandlerNoCommand(t *testing.T) {
+	var opts = struct {
+		Value bool `short:"v"`
+	}{}
+
+	parser := NewParser(&opts, Default&^PrintErrors)
+
+	var executedCommand Commander
+	var executedArgs []string
+
+	executed := false
+
+	parser.CommandHandler = func(command Commander, args []string) error {
+		executed = true
+
+		executedCommand = command
+		executedArgs = args
+
+		return nil
+	}
+
+	_, err := parser.ParseArgs([]string{"arg1", "arg2"})
+
+	if err != nil {
+		t.Fatalf("Unexpected parse error: %s", err)
+	}
+
+	if !executed {
+		t.Errorf("Expected command handler to be executed")
+	}
+
+	if executedCommand != nil {
+		t.Errorf("Did not exect an executed command")
+	}
+
+	assertStringArray(t, executedArgs, []string{"arg1", "arg2"})
+}
+
+func TestCommandHandler(t *testing.T) {
+	var opts = struct {
+		Value bool `short:"v"`
+
+		Command command `command:"cmd"`
+	}{}
+
+	parser := NewParser(&opts, Default&^PrintErrors)
+
+	var executedCommand Commander
+	var executedArgs []string
+
+	executed := false
+
+	parser.CommandHandler = func(command Commander, args []string) error {
+		executed = true
+
+		executedCommand = command
+		executedArgs = args
+
+		return nil
+	}
+
+	_, err := parser.ParseArgs([]string{"cmd", "arg1", "arg2"})
+
+	if err != nil {
+		t.Fatalf("Unexpected parse error: %s", err)
+	}
+
+	if !executed {
+		t.Errorf("Expected command handler to be executed")
+	}
+
+	if executedCommand == nil {
+		t.Errorf("Expected command handler to be executed")
+	}
+
+	assertStringArray(t, executedArgs, []string{"arg1", "arg2"})
+}