Allow using unabiguous long option prefixes

This commit is contained in:
Kovid Goyal 2022-09-30 13:29:06 +05:30
parent 7d5849cc17
commit 74b1cac344
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 60 additions and 3 deletions

View File

@ -347,6 +347,30 @@ func (self *Command) Add(s OptionSpec) *Option {
return self.AddToGroup("", s) return self.AddToGroup("", s)
} }
func (self *Command) FindOptions(name_with_hyphens string) []*Option {
ans := make([]*Option, 0, 4)
for _, g := range self.OptionGroups {
x := g.FindOptions(name_with_hyphens)
if x != nil {
ans = append(ans, x...)
}
}
depth := 0
for p := self.Parent; p != nil; p = p.Parent {
depth++
x := p.FindOptions(name_with_hyphens)
if x != nil {
for _, po := range x {
if po.Depth >= depth {
ans = append(ans, po)
}
}
}
}
return ans
}
func (self *Command) FindOption(name_with_hyphens string) *Option { func (self *Command) FindOption(name_with_hyphens string) *Option {
for _, g := range self.OptionGroups { for _, g := range self.OptionGroups {
q := g.FindOption(name_with_hyphens) q := g.FindOption(name_with_hyphens)

View File

@ -82,6 +82,19 @@ func (self *OptionGroup) AddOptionFromString(parent *Command, items ...string) (
return ans, err return ans, err
} }
func (self *OptionGroup) FindOptions(prefix_with_hyphens string) []*Option {
is_short := !strings.HasPrefix(prefix_with_hyphens, "--")
option_name := NormalizeOptionName(prefix_with_hyphens)
ans := make([]*Option, 0, 4)
for _, q := range self.Options {
if q.MatchingAlias(option_name, is_short) != "" {
ans = append(ans, q)
}
}
return ans
}
func (self *OptionGroup) FindOption(name_with_hyphens string) *Option { func (self *OptionGroup) FindOption(name_with_hyphens string) *Option {
is_short := !strings.HasPrefix(name_with_hyphens, "--") is_short := !strings.HasPrefix(name_with_hyphens, "--")
option_name := NormalizeOptionName(name_with_hyphens) option_name := NormalizeOptionName(name_with_hyphens)

View File

@ -75,6 +75,15 @@ func (self *Option) needs_argument() bool {
return self.OptionType != BoolOption && self.OptionType != CountOption return self.OptionType != BoolOption && self.OptionType != CountOption
} }
func (self *Option) MatchingAlias(prefix_without_hyphens string, is_short bool) string {
for _, a := range self.Aliases {
if a.IsShort == is_short && strings.HasPrefix(a.NameWithoutHyphens, prefix_without_hyphens) {
return a.String()
}
}
return ""
}
func (self *Option) HasAlias(name_without_hyphens string, is_short bool) bool { func (self *Option) HasAlias(name_without_hyphens string, is_short bool) bool {
for _, a := range self.Aliases { for _, a := range self.Aliases {
if a.IsShort == is_short && a.NameWithoutHyphens == name_without_hyphens { if a.IsShort == is_short && a.NameWithoutHyphens == name_without_hyphens {

View File

@ -20,9 +20,19 @@ func (self *Command) parse_args(ctx *Context, args []string) error {
consume_arg := func() string { ans := args_to_parse[0]; args_to_parse = args_to_parse[1:]; return ans } consume_arg := func() string { ans := args_to_parse[0]; args_to_parse = args_to_parse[1:]; return ans }
handle_option := func(opt_str string, has_val bool, opt_val string, val_not_allowed bool) error { handle_option := func(opt_str string, has_val bool, opt_val string, val_not_allowed bool) error {
opt := self.FindOption(opt_str) possible_options := self.FindOptions(opt_str)
if opt == nil { var opt *Option
if len(possible_options) == 1 {
opt = possible_options[0]
opt_str = opt.MatchingAlias(NormalizeOptionName(opt_str), !strings.HasPrefix(opt_str, "--"))
} else if len(possible_options) == 0 {
return &ParseError{Message: fmt.Sprintf("Unknown option: :yellow:`%s`", opt_str)} return &ParseError{Message: fmt.Sprintf("Unknown option: :yellow:`%s`", opt_str)}
} else {
ambi := make([]string, len(possible_options))
for i, o := range possible_options {
ambi[i] = o.MatchingAlias(NormalizeOptionName(opt_str), !strings.HasPrefix(opt_str, "--"))
}
return &ParseError{Message: fmt.Sprintf("Ambiguous option: :yellow:`%s` could be any of: %s", opt_str, strings.Join(ambi, ", "))}
} }
opt.seen_option = opt_str opt.seen_option = opt_str
needs_arg := opt.needs_argument() needs_arg := opt.needs_argument()

View File

@ -85,8 +85,9 @@ func TestCLIParsing(t *testing.T) {
) )
rt(child1, "test child1", &options{}) rt(child1, "test child1", &options{})
rt(child1, "test child1 --set-me --simple-string=foo one", &options{SimpleString: "foo", SetMe: true}, "one") rt(child1, "test child1 --set-me --simple-string=foo one", &options{SimpleString: "foo", SetMe: true}, "one")
rt(child1, "test child1 --set-me --simp=foo one", &options{SimpleString: "foo", SetMe: true}, "one")
rt(child1, "test child1 --set-me --simple-string= one", &options{SetMe: true}, "one") rt(child1, "test child1 --set-me --simple-string= one", &options{SetMe: true}, "one")
rt(child1, "test child1 --int -3 --simple-string -s --float=3.3", &options{SimpleString: "-s", Int: -3, Float: 3.3}) rt(child1, "test child1 --int -3 --simple-s -s --float=3.3", &options{SimpleString: "-s", Int: -3, Float: 3.3})
rt(child1, "test child1 --list -3 -p --list one", &options{FromParent: 1, List: []string{"-3", "one"}}) rt(child1, "test child1 --list -3 -p --list one", &options{FromParent: 1, List: []string{"-3", "one"}})
rt(gc1, "test -p child1 -p gc1 xxx", &empty_options{}, "xxx") rt(gc1, "test -p child1 -p gc1 xxx", &empty_options{}, "xxx")