Merge _private files
diff --git a/command.go b/command.go
index 13332ae..9184aec 100644
--- a/command.go
+++ b/command.go
@@ -1,5 +1,12 @@
 package flags
 
+import (
+	"reflect"
+	"sort"
+	"strings"
+	"unsafe"
+)
+
 // 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
@@ -47,6 +54,13 @@
 	Usage() string
 }
 
+type lookup struct {
+	shortNames map[string]*Option
+	longNames  map[string]*Option
+
+	commands map[string]*Command
+}
+
 // 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
@@ -104,3 +118,285 @@
 
 	return ret
 }
+
+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
+		}
+
+		positional := mtag.Get("positional-args")
+
+		if len(positional) != 0 {
+			stype := realval.Type()
+
+			for i := 0; i < stype.NumField(); i++ {
+				field := stype.Field(i)
+
+				m := newMultiTag((string(field.Tag)))
+
+				if err := m.Parse(); err != nil {
+					return true, err
+				}
+
+				name := m.Get("positional-arg-name")
+
+				if len(name) == 0 {
+					name = field.Name
+				}
+
+				arg := &Arg{
+					Name:        name,
+					Description: m.Get("description"),
+
+					value: realval.Field(i),
+					tag:   m,
+				}
+
+				c.args = append(c.args, arg)
+
+				if len(mtag.Get("required")) != 0 {
+					c.ArgsRequired = true
+				}
+			}
+
+			return true, nil
+		}
+
+		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")
+			subcommandsOptional := mtag.Get("subcommands-optional")
+			aliases := mtag.GetMany("alias")
+
+			subc, err := c.AddCommand(subcommand, shortDescription, longDescription, ptrval.Interface())
+
+			subc.Hidden = mtag.Get("hidden") != ""
+
+			if err != nil {
+				return true, err
+			}
+
+			if len(subcommandsOptional) > 0 {
+				subc.SubcommandsOptional = true
+			}
+
+			if len(aliases) > 0 {
+				subc.Aliases = aliases
+			}
+
+			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) eachOption(f func(*Command, *Group, *Option)) {
+	c.eachCommand(func(c *Command) {
+		c.eachGroup(func(g *Group) {
+			for _, option := range g.options {
+				f(c, g, option)
+			}
+		})
+	}, true)
+}
+
+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(cc *Command, g *Group)) {
+	c.eachGroup(func(g *Group) {
+		f(c, g)
+	})
+
+	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),
+		commands:   make(map[string]*Command),
+	}
+
+	parent := c.parent
+
+	var parents []*Command
+
+	for parent != nil {
+		if cmd, ok := parent.(*Command); ok {
+			parents = append(parents, cmd)
+			parent = cmd.parent
+		} else {
+			parent = nil
+		}
+	}
+
+	for i := len(parents) - 1; i >= 0; i-- {
+		parents[i].fillLookup(&ret, true)
+	}
+
+	c.fillLookup(&ret, false)
+	return ret
+}
+
+func (c *Command) fillLookup(ret *lookup, onlyOptions bool) {
+	c.eachGroup(func(g *Group) {
+		for _, option := range g.options {
+			if option.ShortName != 0 {
+				ret.shortNames[string(option.ShortName)] = option
+			}
+
+			if len(option.LongName) > 0 {
+				ret.longNames[option.LongNameWithNamespace()] = option
+			}
+		}
+	})
+
+	if onlyOptions {
+		return
+	}
+
+	for _, subcommand := range c.commands {
+		ret.commands[subcommand.Name] = subcommand
+
+		for _, a := range subcommand.Aliases {
+			ret.commands[a] = subcommand
+		}
+	}
+}
+
+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) sortedVisibleCommands() []*Command {
+	ret := commandList(c.visibleCommands())
+	sort.Sort(ret)
+
+	return []*Command(ret)
+}
+
+func (c *Command) visibleCommands() []*Command {
+	ret := make([]*Command, 0, len(c.commands))
+
+	for _, cmd := range c.commands {
+		if !cmd.Hidden {
+			ret = append(ret, cmd)
+		}
+	}
+
+	return ret
+}
+
+func (c *Command) match(name string) bool {
+	if c.Name == name {
+		return true
+	}
+
+	for _, v := range c.Aliases {
+		if v == name {
+			return true
+		}
+	}
+
+	return false
+}
+
+func (c *Command) hasCliOptions() bool {
+	ret := false
+
+	c.eachGroup(func(g *Group) {
+		if g.isBuiltinHelp {
+			return
+		}
+
+		for _, opt := range g.options {
+			if opt.canCli() {
+				ret = true
+			}
+		}
+	})
+
+	return ret
+}
+
+func (c *Command) fillParseState(s *parseState) {
+	s.positional = make([]*Arg, len(c.args))
+	copy(s.positional, c.args)
+
+	s.lookup = c.makeLookup()
+	s.command = c
+}
diff --git a/command_private.go b/command_private.go
deleted file mode 100644
index f2a2435..0000000
--- a/command_private.go
+++ /dev/null
@@ -1,297 +0,0 @@
-package flags
-
-import (
-	"reflect"
-	"sort"
-	"strings"
-	"unsafe"
-)
-
-type lookup struct {
-	shortNames map[string]*Option
-	longNames  map[string]*Option
-
-	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
-		}
-
-		positional := mtag.Get("positional-args")
-
-		if len(positional) != 0 {
-			stype := realval.Type()
-
-			for i := 0; i < stype.NumField(); i++ {
-				field := stype.Field(i)
-
-				m := newMultiTag((string(field.Tag)))
-
-				if err := m.Parse(); err != nil {
-					return true, err
-				}
-
-				name := m.Get("positional-arg-name")
-
-				if len(name) == 0 {
-					name = field.Name
-				}
-
-				arg := &Arg{
-					Name:        name,
-					Description: m.Get("description"),
-
-					value: realval.Field(i),
-					tag:   m,
-				}
-
-				c.args = append(c.args, arg)
-
-				if len(mtag.Get("required")) != 0 {
-					c.ArgsRequired = true
-				}
-			}
-
-			return true, nil
-		}
-
-		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")
-			subcommandsOptional := mtag.Get("subcommands-optional")
-			aliases := mtag.GetMany("alias")
-
-			subc, err := c.AddCommand(subcommand, shortDescription, longDescription, ptrval.Interface())
-
-			subc.Hidden = mtag.Get("hidden") != ""
-
-			if err != nil {
-				return true, err
-			}
-
-			if len(subcommandsOptional) > 0 {
-				subc.SubcommandsOptional = true
-			}
-
-			if len(aliases) > 0 {
-				subc.Aliases = aliases
-			}
-
-			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) eachOption(f func(*Command, *Group, *Option)) {
-	c.eachCommand(func(c *Command) {
-		c.eachGroup(func(g *Group) {
-			for _, option := range g.options {
-				f(c, g, option)
-			}
-		})
-	}, true)
-}
-
-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(cc *Command, g *Group)) {
-	c.eachGroup(func(g *Group) {
-		f(c, g)
-	})
-
-	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),
-		commands:   make(map[string]*Command),
-	}
-
-	parent := c.parent
-
-	var parents []*Command
-
-	for parent != nil {
-		if cmd, ok := parent.(*Command); ok {
-			parents = append(parents, cmd)
-			parent = cmd.parent
-		} else {
-			parent = nil
-		}
-	}
-
-	for i := len(parents) - 1; i >= 0; i-- {
-		parents[i].fillLookup(&ret, true)
-	}
-
-	c.fillLookup(&ret, false)
-	return ret
-}
-
-func (c *Command) fillLookup(ret *lookup, onlyOptions bool) {
-	c.eachGroup(func(g *Group) {
-		for _, option := range g.options {
-			if option.ShortName != 0 {
-				ret.shortNames[string(option.ShortName)] = option
-			}
-
-			if len(option.LongName) > 0 {
-				ret.longNames[option.LongNameWithNamespace()] = option
-			}
-		}
-	})
-
-	if onlyOptions {
-		return
-	}
-
-	for _, subcommand := range c.commands {
-		ret.commands[subcommand.Name] = subcommand
-
-		for _, a := range subcommand.Aliases {
-			ret.commands[a] = subcommand
-		}
-	}
-}
-
-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) sortedVisibleCommands() []*Command {
-	ret := commandList(c.visibleCommands())
-	sort.Sort(ret)
-
-	return []*Command(ret)
-}
-
-func (c *Command) visibleCommands() []*Command {
-	ret := make([]*Command, 0, len(c.commands))
-
-	for _, cmd := range c.commands {
-		if !cmd.Hidden {
-			ret = append(ret, cmd)
-		}
-	}
-
-	return ret
-}
-
-func (c *Command) match(name string) bool {
-	if c.Name == name {
-		return true
-	}
-
-	for _, v := range c.Aliases {
-		if v == name {
-			return true
-		}
-	}
-
-	return false
-}
-
-func (c *Command) hasCliOptions() bool {
-	ret := false
-
-	c.eachGroup(func(g *Group) {
-		if g.isBuiltinHelp {
-			return
-		}
-
-		for _, opt := range g.options {
-			if opt.canCli() {
-				ret = true
-			}
-		}
-	})
-
-	return ret
-}
-
-func (c *Command) fillParseState(s *parseState) {
-	s.positional = make([]*Arg, len(c.args))
-	copy(s.positional, c.args)
-
-	s.lookup = c.makeLookup()
-	s.command = c
-}
diff --git a/group.go b/group.go
index 0e74f92..8ab809e 100644
--- a/group.go
+++ b/group.go
@@ -6,7 +6,10 @@
 
 import (
 	"errors"
+	"reflect"
 	"strings"
+	"unicode/utf8"
+	"unsafe"
 )
 
 // ErrNotPointerToStruct indicates that a provided data container is not
@@ -50,6 +53,8 @@
 	data interface{}
 }
 
+type scanHandler func(reflect.Value, *reflect.StructField) (bool, error)
+
 // 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.
@@ -92,3 +97,253 @@
 
 	return ret
 }
+
+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.LongNameWithNamespace() && 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) 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") != "")
+		choices := mtag.GetMany("choice")
+		hidden := (mtag.Get("hidden") != "")
+
+		option := &Option{
+			Description:      description,
+			ShortName:        short,
+			LongName:         longname,
+			Default:          def,
+			EnvDefaultKey:    mtag.Get("env"),
+			EnvDefaultDelim:  mtag.Get("env-delim"),
+			OptionalArgument: optional,
+			OptionalValue:    optionalValue,
+			Required:         required,
+			ValueName:        valueName,
+			DefaultMask:      defaultMask,
+			Choices:          choices,
+			Hidden:           hidden,
+
+			group: g,
+
+			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 != "" {
+				longName := option.LongNameWithNamespace()
+
+				if otherOption, ok := longNames[longName]; ok {
+					duplicateError = newErrorf(ErrDuplicatedFlag, "option `%s' uses the same long name as option `%s'", option, otherOption)
+					return
+				}
+				longNames[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")
+
+		group, err := g.AddGroup(subgroup, description, ptrval.Interface())
+		if err != nil {
+			return true, err
+		}
+
+		group.Namespace = mtag.Get("namespace")
+		group.Hidden = mtag.Get("hidden") != ""
+
+		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)
+}
diff --git a/group_private.go b/group_private.go
deleted file mode 100644
index b54bfd2..0000000
--- a/group_private.go
+++ /dev/null
@@ -1,259 +0,0 @@
-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.LongNameWithNamespace() && 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) 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") != "")
-		choices := mtag.GetMany("choice")
-		hidden := (mtag.Get("hidden") != "")
-
-		option := &Option{
-			Description:      description,
-			ShortName:        short,
-			LongName:         longname,
-			Default:          def,
-			EnvDefaultKey:    mtag.Get("env"),
-			EnvDefaultDelim:  mtag.Get("env-delim"),
-			OptionalArgument: optional,
-			OptionalValue:    optionalValue,
-			Required:         required,
-			ValueName:        valueName,
-			DefaultMask:      defaultMask,
-			Choices:          choices,
-			Hidden:           hidden,
-
-			group: g,
-
-			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 != "" {
-				longName := option.LongNameWithNamespace()
-
-				if otherOption, ok := longNames[longName]; ok {
-					duplicateError = newErrorf(ErrDuplicatedFlag, "option `%s' uses the same long name as option `%s'", option, otherOption)
-					return
-				}
-				longNames[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")
-
-		group, err := g.AddGroup(subgroup, description, ptrval.Interface())
-		if err != nil {
-			return true, err
-		}
-
-		group.Namespace = mtag.Get("namespace")
-		group.Hidden = mtag.Get("hidden") != ""
-
-		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)
-}
diff --git a/ini.go b/ini.go
index 7225052..cfdf57c 100644
--- a/ini.go
+++ b/ini.go
@@ -1,8 +1,14 @@
 package flags
 
 import (
+	"bufio"
 	"fmt"
 	"io"
+	"os"
+	"reflect"
+	"sort"
+	"strconv"
+	"strings"
 )
 
 // IniError contains location information on where an error occured.
@@ -55,6 +61,20 @@
 	parser *Parser
 }
 
+type iniValue struct {
+	Name       string
+	Value      string
+	Quoted     bool
+	LineNumber uint
+}
+
+type iniSection []iniValue
+
+type ini struct {
+	File     string
+	Sections map[string]iniSection
+}
+
 // NewIniParser creates a new ini parser for a given Parser.
 func NewIniParser(p *Parser) *IniParser {
 	return &IniParser{
@@ -138,3 +158,436 @@
 func (i *IniParser) Write(writer io.Writer, options IniOptions) {
 	writeIni(i, writer, options)
 }
+
+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(cmd *Command, group *Group, namespace string, writer io.Writer, options IniOptions) {
+	var sname string
+
+	if len(namespace) != 0 {
+		sname = namespace
+	}
+
+	if cmd.Group != group && len(group.ShortDescription) != 0 {
+		if len(sname) != 0 {
+			sname += "."
+		}
+
+		sname += group.ShortDescription
+	}
+
+	sectionwritten := false
+	comments := (options & IniIncludeComments) != IniNone
+
+	for _, option := range group.options {
+		if option.isFunc() || option.Hidden {
+			continue
+		}
+
+		if len(option.tag.Get("no-ini")) != 0 {
+			continue
+		}
+
+		val := option.value
+
+		if (options&IniIncludeDefaults) == IniNone && option.valueIsDefault() {
+			continue
+		}
+
+		if !sectionwritten {
+			fmt.Fprintf(writer, "[%s]\n", sname)
+			sectionwritten = true
+		}
+
+		if comments && len(option.Description) != 0 {
+			fmt.Fprintf(writer, "; %s\n", option.Description)
+		}
+
+		oname := optionIniName(option)
+
+		commentOption := (options&(IniIncludeDefaults|IniCommentDefaults)) == IniIncludeDefaults|IniCommentDefaults && option.valueIsDefault()
+
+		kind := val.Type().Kind()
+		switch kind {
+		case reflect.Slice:
+			kind = val.Type().Elem().Kind()
+
+			if val.Len() == 0 {
+				writeOption(writer, oname, kind, "", "", true, option.iniQuote)
+			} else {
+				for idx := 0; idx < val.Len(); idx++ {
+					v, _ := convertToString(val.Index(idx), option.tag)
+
+					writeOption(writer, oname, kind, "", v, commentOption, option.iniQuote)
+				}
+			}
+		case reflect.Map:
+			kind = val.Type().Elem().Kind()
+
+			if val.Len() == 0 {
+				writeOption(writer, oname, kind, "", "", true, option.iniQuote)
+			} else {
+				mkeys := val.MapKeys()
+				keys := make([]string, len(val.MapKeys()))
+				kkmap := make(map[string]reflect.Value)
+
+				for i, k := range mkeys {
+					keys[i], _ = convertToString(k, option.tag)
+					kkmap[keys[i]] = k
+				}
+
+				sort.Strings(keys)
+
+				for _, k := range keys {
+					v, _ := convertToString(val.MapIndex(kkmap[k]), option.tag)
+
+					writeOption(writer, oname, kind, k, v, commentOption, option.iniQuote)
+				}
+			}
+		default:
+			v, _ := convertToString(val, option.tag)
+
+			writeOption(writer, oname, kind, "", v, commentOption, option.iniQuote)
+		}
+
+		if comments {
+			fmt.Fprintln(writer)
+		}
+	}
+
+	if sectionwritten && !comments {
+		fmt.Fprintln(writer)
+	}
+}
+
+func writeOption(writer io.Writer, optionName string, optionType reflect.Kind, optionKey string, optionValue string, commentOption bool, forceQuote bool) {
+	if forceQuote || (optionType == reflect.String && !isPrint(optionValue)) {
+		optionValue = strconv.Quote(optionValue)
+	}
+
+	comment := ""
+	if commentOption {
+		comment = "; "
+	}
+
+	fmt.Fprintf(writer, "%s%s =", comment, optionName)
+
+	if optionKey != "" {
+		fmt.Fprintf(writer, " %s:%s", optionKey, optionValue)
+	} else if optionValue != "" {
+		fmt.Fprintf(writer, " %s", optionValue)
+	}
+
+	fmt.Fprintln(writer)
+}
+
+func writeCommandIni(command *Command, namespace string, writer io.Writer, options IniOptions) {
+	command.eachGroup(func(group *Group) {
+		if !group.Hidden {
+			writeGroupIni(command, group, namespace, writer, options)
+		}
+	})
+
+	for _, c := range command.commands {
+		var nns string
+
+		if c.Hidden {
+			continue
+		}
+
+		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 writeIniToFile(parser *IniParser, filename string, options IniOptions) error {
+	file, err := os.Create(filename)
+
+	if err != nil {
+		return err
+	}
+
+	defer file.Close()
+
+	writeIni(parser, file, options)
+
+	return nil
+}
+
+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 := &ini{
+		File:     filename,
+		Sections: make(map[string]iniSection),
+	}
+
+	reader := bufio.NewReader(contents)
+
+	// Empty global section
+	section := make(iniSection, 0, 10)
+	sectionname := ""
+
+	ret.Sections[sectionname] = section
+
+	var lineno uint
+
+	for {
+		line, err := readFullLine(reader)
+
+		if err == io.EOF {
+			break
+		} else 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] == ';' || 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.Sections[name]
+
+			if section == nil {
+				section = make(iniSection, 0, 10)
+				ret.Sections[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])
+		quoted := false
+
+		if len(value) != 0 && value[0] == '"' {
+			if v, err := strconv.Unquote(value); err == nil {
+				value = v
+
+				quoted = true
+			} else {
+				return nil, &IniError{
+					Message:    err.Error(),
+					File:       filename,
+					LineNumber: lineno,
+				}
+			}
+		}
+
+		section = append(section, iniValue{
+			Name:       name,
+			Value:      value,
+			Quoted:     quoted,
+			LineNumber: lineno,
+		})
+
+		ret.Sections[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
+
+	var quotesLookup = make(map[*Option]bool)
+
+	for name, section := range ini.Sections {
+		groups := i.matchingGroups(name)
+
+		if len(groups) == 0 {
+			return newErrorf(ErrUnknownGroup, "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 &IniError{
+						Message:    fmt.Sprintf("unknown option: %s", inival.Name),
+						File:       ini.File,
+						LineNumber: inival.LineNumber,
+					}
+				}
+
+				continue
+			}
+
+			pval := &inival.Value
+
+			if !opt.canArgument() && len(inival.Value) == 0 {
+				pval = nil
+			} else {
+				if opt.value.Type().Kind() == reflect.Map {
+					parts := strings.SplitN(inival.Value, ":", 2)
+
+					// only handle unquoting
+					if len(parts) == 2 && parts[1][0] == '"' {
+						if v, err := strconv.Unquote(parts[1]); err == nil {
+							parts[1] = v
+
+							inival.Quoted = true
+						} else {
+							return &IniError{
+								Message:    err.Error(),
+								File:       ini.File,
+								LineNumber: inival.LineNumber,
+							}
+						}
+
+						s := parts[0] + ":" + parts[1]
+
+						pval = &s
+					}
+				}
+			}
+
+			if err := opt.set(pval); err != nil {
+				return &IniError{
+					Message:    err.Error(),
+					File:       ini.File,
+					LineNumber: inival.LineNumber,
+				}
+			}
+
+			// either all INI values are quoted or only values who need quoting
+			if _, ok := quotesLookup[opt]; !inival.Quoted || !ok {
+				quotesLookup[opt] = inival.Quoted
+			}
+
+			opt.tag.Set("_read-ini-name", inival.Name)
+		}
+	}
+
+	for opt, quoted := range quotesLookup {
+		opt.iniQuote = quoted
+	}
+
+	return nil
+}
diff --git a/ini_private.go b/ini_private.go
deleted file mode 100644
index 45434ec..0000000
--- a/ini_private.go
+++ /dev/null
@@ -1,458 +0,0 @@
-package flags
-
-import (
-	"bufio"
-	"fmt"
-	"io"
-	"os"
-	"reflect"
-	"sort"
-	"strconv"
-	"strings"
-)
-
-type iniValue struct {
-	Name       string
-	Value      string
-	Quoted     bool
-	LineNumber uint
-}
-
-type iniSection []iniValue
-type ini struct {
-	File     string
-	Sections 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(cmd *Command, group *Group, namespace string, writer io.Writer, options IniOptions) {
-	var sname string
-
-	if len(namespace) != 0 {
-		sname = namespace
-	}
-
-	if cmd.Group != group && len(group.ShortDescription) != 0 {
-		if len(sname) != 0 {
-			sname += "."
-		}
-
-		sname += group.ShortDescription
-	}
-
-	sectionwritten := false
-	comments := (options & IniIncludeComments) != IniNone
-
-	for _, option := range group.options {
-		if option.isFunc() || option.Hidden {
-			continue
-		}
-
-		if len(option.tag.Get("no-ini")) != 0 {
-			continue
-		}
-
-		val := option.value
-
-		if (options&IniIncludeDefaults) == IniNone && option.valueIsDefault() {
-			continue
-		}
-
-		if !sectionwritten {
-			fmt.Fprintf(writer, "[%s]\n", sname)
-			sectionwritten = true
-		}
-
-		if comments && len(option.Description) != 0 {
-			fmt.Fprintf(writer, "; %s\n", option.Description)
-		}
-
-		oname := optionIniName(option)
-
-		commentOption := (options&(IniIncludeDefaults|IniCommentDefaults)) == IniIncludeDefaults|IniCommentDefaults && option.valueIsDefault()
-
-		kind := val.Type().Kind()
-		switch kind {
-		case reflect.Slice:
-			kind = val.Type().Elem().Kind()
-
-			if val.Len() == 0 {
-				writeOption(writer, oname, kind, "", "", true, option.iniQuote)
-			} else {
-				for idx := 0; idx < val.Len(); idx++ {
-					v, _ := convertToString(val.Index(idx), option.tag)
-
-					writeOption(writer, oname, kind, "", v, commentOption, option.iniQuote)
-				}
-			}
-		case reflect.Map:
-			kind = val.Type().Elem().Kind()
-
-			if val.Len() == 0 {
-				writeOption(writer, oname, kind, "", "", true, option.iniQuote)
-			} else {
-				mkeys := val.MapKeys()
-				keys := make([]string, len(val.MapKeys()))
-				kkmap := make(map[string]reflect.Value)
-
-				for i, k := range mkeys {
-					keys[i], _ = convertToString(k, option.tag)
-					kkmap[keys[i]] = k
-				}
-
-				sort.Strings(keys)
-
-				for _, k := range keys {
-					v, _ := convertToString(val.MapIndex(kkmap[k]), option.tag)
-
-					writeOption(writer, oname, kind, k, v, commentOption, option.iniQuote)
-				}
-			}
-		default:
-			v, _ := convertToString(val, option.tag)
-
-			writeOption(writer, oname, kind, "", v, commentOption, option.iniQuote)
-		}
-
-		if comments {
-			fmt.Fprintln(writer)
-		}
-	}
-
-	if sectionwritten && !comments {
-		fmt.Fprintln(writer)
-	}
-}
-
-func writeOption(writer io.Writer, optionName string, optionType reflect.Kind, optionKey string, optionValue string, commentOption bool, forceQuote bool) {
-	if forceQuote || (optionType == reflect.String && !isPrint(optionValue)) {
-		optionValue = strconv.Quote(optionValue)
-	}
-
-	comment := ""
-	if commentOption {
-		comment = "; "
-	}
-
-	fmt.Fprintf(writer, "%s%s =", comment, optionName)
-
-	if optionKey != "" {
-		fmt.Fprintf(writer, " %s:%s", optionKey, optionValue)
-	} else if optionValue != "" {
-		fmt.Fprintf(writer, " %s", optionValue)
-	}
-
-	fmt.Fprintln(writer)
-}
-
-func writeCommandIni(command *Command, namespace string, writer io.Writer, options IniOptions) {
-	command.eachGroup(func(group *Group) {
-		if !group.Hidden {
-			writeGroupIni(command, group, namespace, writer, options)
-		}
-	})
-
-	for _, c := range command.commands {
-		var nns string
-
-		if c.Hidden {
-			continue
-		}
-
-		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 writeIniToFile(parser *IniParser, filename string, options IniOptions) error {
-	file, err := os.Create(filename)
-
-	if err != nil {
-		return err
-	}
-
-	defer file.Close()
-
-	writeIni(parser, file, options)
-
-	return nil
-}
-
-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 := &ini{
-		File:     filename,
-		Sections: make(map[string]iniSection),
-	}
-
-	reader := bufio.NewReader(contents)
-
-	// Empty global section
-	section := make(iniSection, 0, 10)
-	sectionname := ""
-
-	ret.Sections[sectionname] = section
-
-	var lineno uint
-
-	for {
-		line, err := readFullLine(reader)
-
-		if err == io.EOF {
-			break
-		} else 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] == ';' || 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.Sections[name]
-
-			if section == nil {
-				section = make(iniSection, 0, 10)
-				ret.Sections[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])
-		quoted := false
-
-		if len(value) != 0 && value[0] == '"' {
-			if v, err := strconv.Unquote(value); err == nil {
-				value = v
-
-				quoted = true
-			} else {
-				return nil, &IniError{
-					Message:    err.Error(),
-					File:       filename,
-					LineNumber: lineno,
-				}
-			}
-		}
-
-		section = append(section, iniValue{
-			Name:       name,
-			Value:      value,
-			Quoted:     quoted,
-			LineNumber: lineno,
-		})
-
-		ret.Sections[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
-
-	var quotesLookup = make(map[*Option]bool)
-
-	for name, section := range ini.Sections {
-		groups := i.matchingGroups(name)
-
-		if len(groups) == 0 {
-			return newErrorf(ErrUnknownGroup, "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 &IniError{
-						Message:    fmt.Sprintf("unknown option: %s", inival.Name),
-						File:       ini.File,
-						LineNumber: inival.LineNumber,
-					}
-				}
-
-				continue
-			}
-
-			pval := &inival.Value
-
-			if !opt.canArgument() && len(inival.Value) == 0 {
-				pval = nil
-			} else {
-				if opt.value.Type().Kind() == reflect.Map {
-					parts := strings.SplitN(inival.Value, ":", 2)
-
-					// only handle unquoting
-					if len(parts) == 2 && parts[1][0] == '"' {
-						if v, err := strconv.Unquote(parts[1]); err == nil {
-							parts[1] = v
-
-							inival.Quoted = true
-						} else {
-							return &IniError{
-								Message:    err.Error(),
-								File:       ini.File,
-								LineNumber: inival.LineNumber,
-							}
-						}
-
-						s := parts[0] + ":" + parts[1]
-
-						pval = &s
-					}
-				}
-			}
-
-			if err := opt.set(pval); err != nil {
-				return &IniError{
-					Message:    err.Error(),
-					File:       ini.File,
-					LineNumber: inival.LineNumber,
-				}
-			}
-
-			// either all INI values are quoted or only values who need quoting
-			if _, ok := quotesLookup[opt]; !inival.Quoted || !ok {
-				quotesLookup[opt] = inival.Quoted
-			}
-
-			opt.tag.Set("_read-ini-name", inival.Name)
-		}
-	}
-
-	for opt, quoted := range quotesLookup {
-		opt.iniQuote = quoted
-	}
-
-	return nil
-}
diff --git a/option.go b/option.go
index 8764e25..b4b867d 100644
--- a/option.go
+++ b/option.go
@@ -3,6 +3,8 @@
 import (
 	"fmt"
 	"reflect"
+	"strings"
+	"syscall"
 	"unicode/utf8"
 )
 
@@ -168,3 +170,242 @@
 func (option *Option) IsSet() bool {
 	return option.isSet
 }
+
+// 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 {
+	kind := option.value.Type().Kind()
+
+	if (kind == reflect.Map || kind == reflect.Slice) && !option.isSet {
+		option.empty()
+	}
+
+	option.isSet = true
+
+	if len(option.Choices) != 0 {
+		found := false
+
+		for _, choice := range option.Choices {
+			if choice == *value {
+				found = true
+				break
+			}
+		}
+
+		if !found {
+			allowed := strings.Join(option.Choices[0:len(option.Choices)-1], ", ")
+
+			if len(option.Choices) > 1 {
+				allowed += " or " + option.Choices[len(option.Choices)-1]
+			}
+
+			return newErrorf(ErrInvalidChoice,
+				"Invalid value `%s' for option `%s'. Allowed values are: %s",
+				*value, option, allowed)
+		}
+	}
+
+	if option.isFunc() {
+		return option.call(value)
+	} else if value != nil {
+		return convert(*value, option.value, option.tag)
+	}
+
+	return convert("", option.value, option.tag)
+}
+
+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) emptyValue() reflect.Value {
+	tp := option.value.Type()
+
+	if tp.Kind() == reflect.Map {
+		return reflect.MakeMap(tp)
+	}
+
+	return reflect.Zero(tp)
+}
+
+func (option *Option) empty() {
+	if !option.isFunc() {
+		option.value.Set(option.emptyValue())
+	}
+}
+
+func (option *Option) clearDefault() {
+	usedDefault := option.Default
+	if envKey := option.EnvDefaultKey; envKey != "" {
+		// os.Getenv() makes no distinction between undefined and
+		// empty values, so we use syscall.Getenv()
+		if value, ok := syscall.Getenv(envKey); ok {
+			if option.EnvDefaultDelim != "" {
+				usedDefault = strings.Split(value,
+					option.EnvDefaultDelim)
+			} else {
+				usedDefault = []string{value}
+			}
+		}
+	}
+
+	if len(usedDefault) > 0 {
+		option.empty()
+
+		for _, d := range usedDefault {
+			option.set(&d)
+		}
+	} else {
+		tp := option.value.Type()
+
+		switch tp.Kind() {
+		case reflect.Map:
+			if option.value.IsNil() {
+				option.empty()
+			}
+		case reflect.Slice:
+			if option.value.IsNil() {
+				option.empty()
+			}
+		}
+	}
+}
+
+func (option *Option) valueIsDefault() bool {
+	// Check if the value of the option corresponds to its
+	// default value
+	emptyval := option.emptyValue()
+
+	checkvalptr := reflect.New(emptyval.Type())
+	checkval := reflect.Indirect(checkvalptr)
+
+	checkval.Set(emptyval)
+
+	if len(option.Default) != 0 {
+		for _, v := range option.Default {
+			convert(v, checkval, option.tag)
+		}
+	}
+
+	return reflect.DeepEqual(option.value.Interface(), checkval.Interface())
+}
+
+func (option *Option) isUnmarshaler() Unmarshaler {
+	v := option.value
+
+	for {
+		if !v.CanInterface() {
+			break
+		}
+
+		i := v.Interface()
+
+		if u, ok := i.(Unmarshaler); ok {
+			return u
+		}
+
+		if !v.CanAddr() {
+			break
+		}
+
+		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
+		}
+	}
+}
+
+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
+}
+
+func (option *Option) updateDefaultLiteral() {
+	defs := option.Default
+	def := ""
+
+	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 {
+		l := len(defs) - 1
+
+		for i := 0; i < l; i++ {
+			def += quoteIfNeeded(defs[i]) + ", "
+		}
+
+		def += quoteIfNeeded(defs[l])
+	}
+
+	option.defaultLiteral = def
+}
diff --git a/option_private.go b/option_private.go
deleted file mode 100644
index 444334e..0000000
--- a/option_private.go
+++ /dev/null
@@ -1,246 +0,0 @@
-package flags
-
-import (
-	"reflect"
-	"strings"
-	"syscall"
-)
-
-// 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 {
-	kind := option.value.Type().Kind()
-
-	if (kind == reflect.Map || kind == reflect.Slice) && !option.isSet {
-		option.empty()
-	}
-
-	option.isSet = true
-
-	if len(option.Choices) != 0 {
-		found := false
-
-		for _, choice := range option.Choices {
-			if choice == *value {
-				found = true
-				break
-			}
-		}
-
-		if !found {
-			allowed := strings.Join(option.Choices[0:len(option.Choices)-1], ", ")
-
-			if len(option.Choices) > 1 {
-				allowed += " or " + option.Choices[len(option.Choices)-1]
-			}
-
-			return newErrorf(ErrInvalidChoice,
-				"Invalid value `%s' for option `%s'. Allowed values are: %s",
-				*value, option, allowed)
-		}
-	}
-
-	if option.isFunc() {
-		return option.call(value)
-	} else if value != nil {
-		return convert(*value, option.value, option.tag)
-	}
-
-	return convert("", option.value, option.tag)
-}
-
-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) emptyValue() reflect.Value {
-	tp := option.value.Type()
-
-	if tp.Kind() == reflect.Map {
-		return reflect.MakeMap(tp)
-	}
-
-	return reflect.Zero(tp)
-}
-
-func (option *Option) empty() {
-	if !option.isFunc() {
-		option.value.Set(option.emptyValue())
-	}
-}
-
-func (option *Option) clearDefault() {
-	usedDefault := option.Default
-	if envKey := option.EnvDefaultKey; envKey != "" {
-		// os.Getenv() makes no distinction between undefined and
-		// empty values, so we use syscall.Getenv()
-		if value, ok := syscall.Getenv(envKey); ok {
-			if option.EnvDefaultDelim != "" {
-				usedDefault = strings.Split(value,
-					option.EnvDefaultDelim)
-			} else {
-				usedDefault = []string{value}
-			}
-		}
-	}
-
-	if len(usedDefault) > 0 {
-		option.empty()
-
-		for _, d := range usedDefault {
-			option.set(&d)
-		}
-	} else {
-		tp := option.value.Type()
-
-		switch tp.Kind() {
-		case reflect.Map:
-			if option.value.IsNil() {
-				option.empty()
-			}
-		case reflect.Slice:
-			if option.value.IsNil() {
-				option.empty()
-			}
-		}
-	}
-}
-
-func (option *Option) valueIsDefault() bool {
-	// Check if the value of the option corresponds to its
-	// default value
-	emptyval := option.emptyValue()
-
-	checkvalptr := reflect.New(emptyval.Type())
-	checkval := reflect.Indirect(checkvalptr)
-
-	checkval.Set(emptyval)
-
-	if len(option.Default) != 0 {
-		for _, v := range option.Default {
-			convert(v, checkval, option.tag)
-		}
-	}
-
-	return reflect.DeepEqual(option.value.Interface(), checkval.Interface())
-}
-
-func (option *Option) isUnmarshaler() Unmarshaler {
-	v := option.value
-
-	for {
-		if !v.CanInterface() {
-			break
-		}
-
-		i := v.Interface()
-
-		if u, ok := i.(Unmarshaler); ok {
-			return u
-		}
-
-		if !v.CanAddr() {
-			break
-		}
-
-		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
-		}
-	}
-}
-
-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
-}
-
-func (option *Option) updateDefaultLiteral() {
-	defs := option.Default
-	def := ""
-
-	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 {
-		l := len(defs) - 1
-
-		for i := 0; i < l; i++ {
-			def += quoteIfNeeded(defs[i]) + ", "
-		}
-
-		def += quoteIfNeeded(defs[l])
-	}
-
-	option.defaultLiteral = def
-}
diff --git a/parser.go b/parser.go
index 8dedd0e..2f74b9a 100644
--- a/parser.go
+++ b/parser.go
@@ -5,8 +5,13 @@
 package flags
 
 import (
+	"bytes"
+	"fmt"
 	"os"
 	"path"
+	"sort"
+	"strings"
+	"unicode/utf8"
 )
 
 // A Parser provides command line option parsing. It can contain several
@@ -98,6 +103,17 @@
 	Default = HelpFlag | PrintErrors | PassDoubleDash
 )
 
+type parseState struct {
+	arg        string
+	args       []string
+	retargs    []string
+	positional []*Arg
+	err        error
+
+	command *Command
+	lookup  lookup
+}
+
 // 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
@@ -299,3 +315,322 @@
 
 	return s.retargs, nil
 }
+
+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(parser *Parser) error {
+	c := parser.Command
+
+	var required []*Option
+
+	for c != nil {
+		c.eachGroup(func(g *Group) {
+			for _, option := range g.options {
+				if !option.isSet && option.Required {
+					required = append(required, option)
+				}
+			}
+		})
+
+		c = c.Active
+	}
+
+	if len(required) == 0 {
+		if len(p.positional) > 0 && p.command.ArgsRequired {
+			var reqnames []string
+
+			for _, arg := range p.positional {
+				if arg.isRemaining() {
+					break
+				}
+
+				reqnames = append(reqnames, "`"+arg.Name+"`")
+			}
+
+			if len(reqnames) == 0 {
+				return nil
+			}
+
+			var msg string
+
+			if len(reqnames) == 1 {
+				msg = fmt.Sprintf("the required argument %s was not provided", reqnames[0])
+			} else {
+				msg = fmt.Sprintf("the required arguments %s and %s were not provided",
+					strings.Join(reqnames[:len(reqnames)-1], ", "), reqnames[len(reqnames)-1])
+			}
+
+			p.err = newError(ErrRequired, msg)
+			return p.err
+		}
+
+		return nil
+	}
+
+	names := make([]string, 0, len(required))
+
+	for _, k := range required {
+		names = append(names, "`"+k.String()+"'")
+	}
+
+	sort.Strings(names)
+
+	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.sortedVisibleCommands()
+	cmdnames := make([]string, len(commands))
+
+	for i, v := range commands {
+		cmdnames[i] = v.Name
+	}
+
+	var msg string
+	var errtype ErrorType
+
+	if len(p.retargs) != 0 {
+		c, l := closestChoice(p.retargs[0], cmdnames)
+		msg = fmt.Sprintf("Unknown command `%s'", p.retargs[0])
+		errtype = ErrUnknownCommand
+
+		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 {
+		errtype = ErrCommandRequired
+
+		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(errtype, msg)
+}
+
+func (p *Parser) parseOption(s *parseState, name string, option *Option, canarg bool, argument *string) (err error) {
+	if !option.canArgument() {
+		if argument != nil {
+			return newErrorf(ErrNoArgumentForBool, "bool flag `%s' cannot have an argument", option)
+		}
+
+		err = option.set(nil)
+	} else if argument != nil || (canarg && !s.eof()) {
+		var arg string
+
+		if argument != nil {
+			arg = *argument
+		} else {
+			arg = s.pop()
+
+			if argumentIsOption(arg) {
+				return newErrorf(ErrExpectedArgument, "expected argument for flag `%s', but got option `%s'", option, arg)
+			} else if p.Options&PassDoubleDash != 0 && arg == "--" {
+				return newErrorf(ErrExpectedArgument, "expected argument for flag `%s', but got double dash `--'", option)
+			}
+		}
+
+		if option.tag.Get("unquote") != "false" {
+			arg, err = unquoteIfPossible(arg)
+		}
+
+		if err == nil {
+			err = option.set(&arg)
+		}
+	} else if option.OptionalArgument {
+		option.empty()
+
+		for _, v := range option.OptionalValue {
+			err = option.set(&v)
+
+			if err != nil {
+				break
+			}
+		}
+	} else {
+		err = newErrorf(ErrExpectedArgument, "expected argument for flag `%s'", option)
+	}
+
+	if err != nil {
+		if _, ok := err.(*Error); !ok {
+			err = newErrorf(ErrMarshal, "invalid argument for flag `%s' (expected %s): %s",
+				option,
+				option.value.Type(),
+				err.Error())
+		}
+	}
+
+	return err
+}
+
+func (p *Parser) parseLong(s *parseState, name string, argument *string) 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 newErrorf(ErrUnknownFlag, "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) 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 err
+			}
+		} else {
+			return newErrorf(ErrUnknownFlag, "unknown flag `%s'", shortname)
+		}
+
+		// Only the first option can have a concatted argument, so just
+		// clear argument here
+		argument = nil
+	}
+
+	return nil
+}
+
+func (p *parseState) addArgs(args ...string) error {
+	for len(p.positional) > 0 && len(args) > 0 {
+		arg := p.positional[0]
+
+		if err := convert(args[0], arg.value, arg.tag); err != nil {
+			return err
+		}
+
+		if !arg.isRemaining() {
+			p.positional = p.positional[1:]
+		}
+
+		args = args[1:]
+	}
+
+	p.retargs = append(p.retargs, args...)
+	return nil
+}
+
+func (p *Parser) parseNonOption(s *parseState) error {
+	if len(s.positional) > 0 {
+		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 PassAfterNonOption is set then all remaining arguments
+		// are considered positional
+		if err := s.addArgs(s.arg); err != nil {
+			return err
+		}
+
+		if err := s.addArgs(s.args...); err != nil {
+			return err
+		}
+
+		s.args = []string{}
+	} else {
+		return s.addArgs(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
+}
+
+func (p *Parser) clearIsSet() {
+	p.eachCommand(func(c *Command) {
+		c.eachGroup(func(g *Group) {
+			for _, option := range g.options {
+				option.isSet = false
+			}
+		})
+	}, true)
+}
diff --git a/parser_private.go b/parser_private.go
deleted file mode 100644
index 7469c03..0000000
--- a/parser_private.go
+++ /dev/null
@@ -1,340 +0,0 @@
-package flags
-
-import (
-	"bytes"
-	"fmt"
-	"os"
-	"sort"
-	"strings"
-	"unicode/utf8"
-)
-
-type parseState struct {
-	arg        string
-	args       []string
-	retargs    []string
-	positional []*Arg
-	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(parser *Parser) error {
-	c := parser.Command
-
-	var required []*Option
-
-	for c != nil {
-		c.eachGroup(func(g *Group) {
-			for _, option := range g.options {
-				if !option.isSet && option.Required {
-					required = append(required, option)
-				}
-			}
-		})
-
-		c = c.Active
-	}
-
-	if len(required) == 0 {
-		if len(p.positional) > 0 && p.command.ArgsRequired {
-			var reqnames []string
-
-			for _, arg := range p.positional {
-				if arg.isRemaining() {
-					break
-				}
-
-				reqnames = append(reqnames, "`"+arg.Name+"`")
-			}
-
-			if len(reqnames) == 0 {
-				return nil
-			}
-
-			var msg string
-
-			if len(reqnames) == 1 {
-				msg = fmt.Sprintf("the required argument %s was not provided", reqnames[0])
-			} else {
-				msg = fmt.Sprintf("the required arguments %s and %s were not provided",
-					strings.Join(reqnames[:len(reqnames)-1], ", "), reqnames[len(reqnames)-1])
-			}
-
-			p.err = newError(ErrRequired, msg)
-			return p.err
-		}
-
-		return nil
-	}
-
-	names := make([]string, 0, len(required))
-
-	for _, k := range required {
-		names = append(names, "`"+k.String()+"'")
-	}
-
-	sort.Strings(names)
-
-	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.sortedVisibleCommands()
-	cmdnames := make([]string, len(commands))
-
-	for i, v := range commands {
-		cmdnames[i] = v.Name
-	}
-
-	var msg string
-	var errtype ErrorType
-
-	if len(p.retargs) != 0 {
-		c, l := closestChoice(p.retargs[0], cmdnames)
-		msg = fmt.Sprintf("Unknown command `%s'", p.retargs[0])
-		errtype = ErrUnknownCommand
-
-		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 {
-		errtype = ErrCommandRequired
-
-		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(errtype, msg)
-}
-
-func (p *Parser) parseOption(s *parseState, name string, option *Option, canarg bool, argument *string) (err error) {
-	if !option.canArgument() {
-		if argument != nil {
-			return newErrorf(ErrNoArgumentForBool, "bool flag `%s' cannot have an argument", option)
-		}
-
-		err = option.set(nil)
-	} else if argument != nil || (canarg && !s.eof()) {
-		var arg string
-
-		if argument != nil {
-			arg = *argument
-		} else {
-			arg = s.pop()
-
-			if argumentIsOption(arg) {
-				return newErrorf(ErrExpectedArgument, "expected argument for flag `%s', but got option `%s'", option, arg)
-			} else if p.Options&PassDoubleDash != 0 && arg == "--" {
-				return newErrorf(ErrExpectedArgument, "expected argument for flag `%s', but got double dash `--'", option)
-			}
-		}
-
-		if option.tag.Get("unquote") != "false" {
-			arg, err = unquoteIfPossible(arg)
-		}
-
-		if err == nil {
-			err = option.set(&arg)
-		}
-	} else if option.OptionalArgument {
-		option.empty()
-
-		for _, v := range option.OptionalValue {
-			err = option.set(&v)
-
-			if err != nil {
-				break
-			}
-		}
-	} else {
-		err = newErrorf(ErrExpectedArgument, "expected argument for flag `%s'", option)
-	}
-
-	if err != nil {
-		if _, ok := err.(*Error); !ok {
-			err = newErrorf(ErrMarshal, "invalid argument for flag `%s' (expected %s): %s",
-				option,
-				option.value.Type(),
-				err.Error())
-		}
-	}
-
-	return err
-}
-
-func (p *Parser) parseLong(s *parseState, name string, argument *string) 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 newErrorf(ErrUnknownFlag, "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) 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 err
-			}
-		} else {
-			return newErrorf(ErrUnknownFlag, "unknown flag `%s'", shortname)
-		}
-
-		// Only the first option can have a concatted argument, so just
-		// clear argument here
-		argument = nil
-	}
-
-	return nil
-}
-
-func (p *parseState) addArgs(args ...string) error {
-	for len(p.positional) > 0 && len(args) > 0 {
-		arg := p.positional[0]
-
-		if err := convert(args[0], arg.value, arg.tag); err != nil {
-			return err
-		}
-
-		if !arg.isRemaining() {
-			p.positional = p.positional[1:]
-		}
-
-		args = args[1:]
-	}
-
-	p.retargs = append(p.retargs, args...)
-	return nil
-}
-
-func (p *Parser) parseNonOption(s *parseState) error {
-	if len(s.positional) > 0 {
-		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 PassAfterNonOption is set then all remaining arguments
-		// are considered positional
-		if err := s.addArgs(s.arg); err != nil {
-			return err
-		}
-
-		if err := s.addArgs(s.args...); err != nil {
-			return err
-		}
-
-		s.args = []string{}
-	} else {
-		return s.addArgs(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
-}
-
-func (p *Parser) clearIsSet() {
-	p.eachCommand(func(c *Command) {
-		c.eachGroup(func(g *Group) {
-			for _, option := range g.options {
-				option.isSet = false
-			}
-		})
-	}, true)
-}