Allow defining options using a struct

This commit is contained in:
Kovid Goyal 2022-09-21 19:15:55 +05:30
parent ef49634353
commit 8221713995
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 162 additions and 129 deletions

View File

@ -99,51 +99,52 @@ type multi_scan struct {
var mpat *regexp.Regexp
func option_from_string(overrides map[string]string, entries ...string) (*Option, error) {
if mpat == nil {
mpat = regexp.MustCompile("^([a-z]+)=(.+)")
}
func option_from_spec(spec OptionSpec) (*Option, error) {
ans := Option{
Help: spec.Help,
values_from_cmdline: make([]string, 0, 1),
parsed_values_from_cmdline: make([]any, 0, 1),
}
scanner := utils.NewScanLines(entries...)
in_help := false
prev_indent := 0
help := strings.Builder{}
help.Grow(2048)
default_was_set := false
indent_of_line := func(x string) int {
return len(x) - len(strings.TrimLeft(x, " \n\t\v\f"))
parts := strings.Split(spec.Name, " ")
ans.Name = camel_case_dest(parts[0])
ans.Aliases = make([]Alias, 0, len(parts))
for i, x := range parts {
ans.Aliases[i] = Alias{NameWithoutHyphens: strings.TrimLeft(x, "-"), IsShort: !strings.HasPrefix(x, "--")}
}
set_default := func(x string) {
if !default_was_set {
if spec.Dest != "" {
ans.Name = spec.Dest
}
ans.Depth = spec.Depth
if spec.Choices != "" {
parts := strings.Split(spec.Choices, ",")
ans.Choices = make(map[string]bool, len(parts))
ans.OptionType = StringOption
for i, x := range parts {
x = strings.TrimSpace(x)
ans.Choices[x] = true
if i == 0 && ans.Default == "" {
ans.Default = x
default_was_set = true
}
}
set_type := func(v string) error {
switch v {
} else {
switch spec.Type {
case "choice", "choices":
ans.OptionType = StringOption
case "int":
ans.OptionType = IntegerOption
set_default("0")
ans.Default = "0"
case "float":
ans.OptionType = FloatOption
set_default("0")
ans.Default = "0"
case "count":
ans.OptionType = CountOption
set_default("0")
ans.Default = "0"
case "bool-set":
ans.OptionType = BoolOption
set_default("false")
ans.Default = "false"
case "bool-reset":
ans.OptionType = BoolOption
set_default("true")
ans.Default = "true"
for _, a := range ans.Aliases {
a.IsUnset = true
}
@ -153,33 +154,40 @@ func option_from_string(overrides map[string]string, entries ...string) (*Option
case "str", "string":
ans.OptionType = StringOption
default:
return fmt.Errorf("Unknown option type: %s", v)
return nil, fmt.Errorf("Unknown option type: %s", spec.Type)
}
return nil
}
if dq, found := overrides["type"]; found {
err := set_type(dq)
if spec.Default != "" {
ans.Default = spec.Default
}
ans.Help = spec.Help
ans.Hidden = spec.Help == "!"
pval, err := ans.parse_value(ans.Default)
if err != nil {
return nil, err
}
ans.parsed_default = pval
if ans.IsList {
ans.parsed_default = []string{}
}
for scanner.Scan() {
line := scanner.Text()
if ans.Aliases == nil {
if strings.HasPrefix(line, "--") {
parts := strings.Split(line, " ")
if dq, found := overrides["dest"]; found {
ans.Name = camel_case_dest(dq)
} else {
ans.Name = camel_case_dest(parts[0])
if ans.Aliases == nil || len(ans.Aliases) == 0 {
return nil, fmt.Errorf("No --aliases specified for option")
}
ans.Aliases = make([]Alias, 0, len(parts))
for i, x := range parts {
ans.Aliases[i] = Alias{NameWithoutHyphens: strings.TrimLeft(x, "-"), IsShort: !strings.HasPrefix(x, "--")}
if ans.Name == "" {
return nil, fmt.Errorf("No dest specified for option")
}
}
} else if in_help {
return &ans, nil
}
func indent_of_line(x string) int {
return len(x) - len(strings.TrimLeft(x, " \n\t\v\f"))
}
func prepare_help_text_for_display(raw string) string {
help := strings.Builder{}
help.Grow(len(raw) + 256)
prev_indent := 0
for _, line := range utils.Splitlines(raw) {
if line != "" {
current_indent := indent_of_line(line)
if current_indent > 1 {
@ -201,7 +209,36 @@ func option_from_string(overrides map[string]string, entries ...string) (*Option
help.WriteString("\n")
}
}
}
return help.String()
}
func option_from_string(overrides map[string]string, entries ...string) (*Option, error) {
if mpat == nil {
mpat = regexp.MustCompile("^([a-z]+)=(.+)")
}
spec := OptionSpec{}
scanner := utils.NewScanLines(entries...)
in_help := false
help := strings.Builder{}
help.Grow(2048)
if dq, found := overrides["type"]; found {
spec.Type = dq
}
if dq, found := overrides["dest"]; found {
spec.Dest = dq
}
for scanner.Scan() {
line := scanner.Text()
if spec.Name == "" {
if strings.HasPrefix(line, "--") {
spec.Name = line
}
} else if in_help {
spec.Help += line + "\n"
} else {
line = strings.TrimSpace(line)
matches := mpat.FindStringSubmatch(line)
if matches == nil {
continue
@ -209,56 +246,28 @@ func option_from_string(overrides map[string]string, entries ...string) (*Option
k, v := matches[1], matches[2]
switch k {
case "choices":
parts := strings.Split(v, ",")
ans.Choices = make(map[string]bool, len(parts))
ans.OptionType = StringOption
for i, x := range parts {
x = strings.TrimSpace(x)
ans.Choices[x] = true
if i == 0 && ans.Default == "" {
ans.Default = x
}
}
spec.Choices = v
case "default":
ans.Default = v
if overrides["default"] == "" {
spec.Default = v
}
case "dest":
if dq, found := overrides["dest"]; found {
ans.Name = camel_case_dest(dq)
} else {
ans.Name = camel_case_dest(v)
if overrides["dest"] == "" {
spec.Dest = v
}
case "depth":
depth, err := strconv.ParseInt(v, 0, 0)
if err != nil {
return nil, err
}
ans.Depth = int(depth)
spec.Depth = int(depth)
case "condition", "completion":
default:
return nil, fmt.Errorf("Unknown option metadata key: %s", k)
case "type":
err := set_type(v)
if err != nil {
return nil, err
spec.Type = v
}
}
}
}
ans.HelpText = help.String()
ans.Hidden = ans.HelpText == "!"
pval, err := ans.parse_value(ans.Default)
if err != nil {
return nil, err
}
ans.parsed_default = pval
if ans.IsList {
ans.parsed_default = []string{}
}
if ans.Aliases == nil || len(ans.Aliases) == 0 {
return nil, fmt.Errorf("No --aliases specified for option")
}
if ans.Name == "" {
return nil, fmt.Errorf("No dest specified for option")
}
return &ans, nil
return option_from_spec(spec)
}

View File

@ -45,7 +45,7 @@ type Option struct {
OptionType OptionType
Hidden bool
Depth int
HelpText string
Help string
IsList bool
Parent *Command
@ -204,6 +204,16 @@ func (self *CommandGroup) FindSubCommand(name string) *Command {
return nil
}
type OptionSpec struct {
Name string
Type string
Dest string
Choices string
Depth int
Default string
Help string
}
type OptionGroup struct { // {{{
Options []*Option
Title string
@ -219,7 +229,15 @@ func (self *OptionGroup) Clone(parent *Command) *OptionGroup {
return &ans
}
func (self *OptionGroup) AddOption(parent *Command, items ...string) (*Option, error) {
func (self *OptionGroup) AddOption(parent *Command, spec OptionSpec) (*Option, error) {
ans, err := option_from_spec(spec)
if err == nil {
ans.Parent = parent
}
return ans, err
}
func (self *OptionGroup) AddOptionFromString(parent *Command, items ...string) (*Option, error) {
ans, err := OptionFromString(items...)
if err == nil {
ans.Parent = parent
@ -338,12 +356,10 @@ func (self *Command) Validate() error {
if seen_flags["-h"] || seen_flags["--help"] {
return &ParseError{Message: fmt.Sprintf("The --help or -h flags are assigned to an option other than Help in %s", self.Name)}
}
self.AddOption(fmt.Sprintf(`
--help -h
type: bool-set
Show help for this command
`))
_, err := self.Add(OptionSpec{Name: "--help -h", Type: "bool-set", Help: "Show help for this command"})
if err != nil {
return err
}
}
return nil
@ -424,12 +440,20 @@ func (self *Command) AddOptionGroup(title string) *OptionGroup {
return &ans
}
func (self *Command) AddOption(items ...string) (*Option, error) {
return self.AddOptionGroup("").AddOption(self, items...)
func (self *Command) AddOptionFromString(items ...string) (*Option, error) {
return self.AddOptionGroup("").AddOptionFromString(self, items...)
}
func (self *Command) AddOptionToGroup(group string, items ...string) (*Option, error) {
return self.AddOptionGroup(group).AddOption(self, items...)
func (self *Command) Add(s OptionSpec) (*Option, error) {
return self.AddOptionGroup("").AddOption(self, s)
}
func (self *Command) AddOptionToGroupFromString(group string, items ...string) (*Option, error) {
return self.AddOptionGroup(group).AddOptionFromString(self, items...)
}
func (self *Command) AddToGroup(group string, s OptionSpec) (*Option, error) {
return self.AddOptionGroup(group).AddOption(self, s)
}
func (self *Command) FindOption(name_with_hyphens string) *Option {