From d7fb143ee05416994eb7aa78ab3455cc18ad3dc3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 28 Apr 2025 11:54:51 +0530 Subject: [PATCH] Allow explicitly setting boolean options in Go cli parser Matches C parser --- tools/cli/help.go | 10 ++++++++-- tools/cli/option.go | 34 +++++++++++++++++++++++++--------- tools/cli/parse-args.go | 8 +++++++- tools/cli/types_test.go | 5 +++++ 4 files changed, 45 insertions(+), 12 deletions(-) diff --git a/tools/cli/help.go b/tools/cli/help.go index 3b263d0fd..13bd1736e 100644 --- a/tools/cli/help.go +++ b/tools/cli/help.go @@ -82,8 +82,10 @@ func (self *Option) FormatOptionForMan(output io.Writer) { if self.IsList { defval = "" } - case BoolOption, CountOption: + case CountOption: defval = "" + case BoolOption: + defval = utils.IfElse(self.Default == "true", "yes", "no") } if defval != "" { @@ -111,7 +113,11 @@ func (self *Option) FormatOption(output io.Writer, formatter *markup.Context, sc if self.IsList { defval = "" } - case BoolOption, CountOption: + case CountOption: + defval = "" + case BoolOption: + yn := utils.IfElse(self.Default == "true", "yes", "no") + fmt.Fprintf(output, " %s", formatter.Italic("[="+yn+"]")) defval = "" } if defval != "" { diff --git a/tools/cli/option.go b/tools/cli/option.go index 848d00a75..7e5892b4c 100644 --- a/tools/cli/option.go +++ b/tools/cli/option.go @@ -160,16 +160,32 @@ func (self *Option) add_value(val string) error { name_without_hyphens := NormalizeOptionName(self.seen_option) switch self.OptionType { case BoolOption: - for _, x := range self.Aliases { - if x.NameWithoutHyphens == name_without_hyphens { - if x.IsUnset { - self.values_from_cmdline = append(self.values_from_cmdline, "false") - self.parsed_values_from_cmdline = append(self.parsed_values_from_cmdline, false) - } else { - self.values_from_cmdline = append(self.values_from_cmdline, "true") - self.parsed_values_from_cmdline = append(self.parsed_values_from_cmdline, true) + if val == "" { + for _, x := range self.Aliases { + if x.NameWithoutHyphens == name_without_hyphens { + if x.IsUnset { + self.values_from_cmdline = append(self.values_from_cmdline, "false") + self.parsed_values_from_cmdline = append(self.parsed_values_from_cmdline, false) + } else { + self.values_from_cmdline = append(self.values_from_cmdline, "true") + self.parsed_values_from_cmdline = append(self.parsed_values_from_cmdline, true) + } + return nil } - return nil + } + } else { + switch val { + case "y", "yes", "true": + self.values_from_cmdline = append(self.values_from_cmdline, "true") + self.parsed_values_from_cmdline = append(self.parsed_values_from_cmdline, true) + case "n", "no", "false": + self.values_from_cmdline = append(self.values_from_cmdline, "false") + self.parsed_values_from_cmdline = append(self.parsed_values_from_cmdline, false) + default: + return &ParseError{Option: self, Message: fmt.Sprintf(":yellow:`%s` is not a valid value for :bold:`%s`. Valid values: %s", + val, self.seen_option, "y, yes, true, n, no and false", + )} + } } case StringOption: diff --git a/tools/cli/parse-args.go b/tools/cli/parse-args.go index 9e8b0af49..dde4eb3c0 100644 --- a/tools/cli/parse-args.go +++ b/tools/cli/parse-args.go @@ -55,7 +55,13 @@ func (self *Command) parse_args(ctx *Context, args []string) error { } if has_val { if !needs_arg { - return &ParseError{Message: fmt.Sprintf("The option: :yellow:`%s` does not take values", opt_str)} + if opt.OptionType == BoolOption { + if err := opt.add_value(opt_val); err != nil { + return err + } + } else { + return &ParseError{Message: fmt.Sprintf("The option: :yellow:`%s` does not take values", opt_str)} + } } return opt.add_value(opt_val) } else if needs_arg { diff --git a/tools/cli/types_test.go b/tools/cli/types_test.go index 32ebe7360..17e9f42a3 100644 --- a/tools/cli/types_test.go +++ b/tools/cli/types_test.go @@ -86,6 +86,11 @@ func TestCLIParsing(t *testing.T) { "one", "two", ) rt(child1, "test child1", &options{}) + rt(child1, "test child1 --set-me one", &options{SetMe: true}, "one") + rt(child1, "test child1 --set-me=y one", &options{SetMe: true}, "one") + rt(child1, "test child1 --set-me=n one", &options{SetMe: false}, "one") + rt(child1, "test child1 --set-me=n --set-me one", &options{SetMe: true}, "one") + rt(child1, "test child1 --set-me=y -b=n one", &options{SetMe: false}, "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")