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)
-}