Code to conveniently read option values into a struct
This commit is contained in:
parent
1811949706
commit
707963b694
@ -12,8 +12,6 @@ import (
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
// OptionFromString {{{
|
||||
|
||||
/*
|
||||
Create an [Option] from a string. Syntax is::
|
||||
|
||||
@ -36,7 +34,10 @@ func OptionFromString(entries ...string) (*Option, error) {
|
||||
if mpat == nil {
|
||||
mpat = regexp.MustCompile("^([a-z]+)=(.+)")
|
||||
}
|
||||
ans := Option{}
|
||||
ans := Option{
|
||||
values_from_cmdline: make([]string, 0, 1),
|
||||
parsed_values_from_cmdline: make([]interface{}, 0, 1),
|
||||
}
|
||||
scanner := bufio.NewScanner(strings.NewReader(strings.Join(entries, "\n")))
|
||||
in_help := false
|
||||
prev_indent := 0
|
||||
@ -47,6 +48,12 @@ func OptionFromString(entries ...string) (*Option, error) {
|
||||
return len(x) - len(strings.TrimLeft(x, " \n\t\v\f"))
|
||||
}
|
||||
|
||||
set_default := func(x string) {
|
||||
if ans.Default == "" {
|
||||
ans.Default = x
|
||||
}
|
||||
}
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if ans.Aliases == nil {
|
||||
@ -117,18 +124,26 @@ func OptionFromString(entries ...string) (*Option, error) {
|
||||
ans.OptionType = StringOption
|
||||
case "int":
|
||||
ans.OptionType = IntegerOption
|
||||
set_default("0")
|
||||
case "float":
|
||||
ans.OptionType = FloatOption
|
||||
set_default("0")
|
||||
case "count":
|
||||
ans.OptionType = CountOption
|
||||
set_default("0")
|
||||
case "bool-set":
|
||||
ans.OptionType = BoolOption
|
||||
set_default("false")
|
||||
case "bool-reset":
|
||||
ans.OptionType = BoolOption
|
||||
set_default("true")
|
||||
for _, a := range ans.Aliases {
|
||||
a.IsUnset = true
|
||||
}
|
||||
case "list", "str", "string":
|
||||
case "list":
|
||||
ans.IsList = true
|
||||
fallthrough
|
||||
case "str", "string":
|
||||
ans.OptionType = StringOption
|
||||
default:
|
||||
return nil, fmt.Errorf("Unknown option type: %s", v)
|
||||
@ -138,5 +153,13 @@ func OptionFromString(entries ...string) (*Option, error) {
|
||||
}
|
||||
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{}
|
||||
}
|
||||
return &ans, nil
|
||||
} // }}}
|
||||
}
|
||||
|
||||
@ -25,13 +25,16 @@ func (self *Command) parse_args(ctx *Context, args []string) error {
|
||||
return &ParseError{Message: fmt.Sprintf("Unknown option: :yellow:`%s`", opt_str)}
|
||||
}
|
||||
opt.seen_option = opt_str
|
||||
needs_arg := opt.needs_argument()
|
||||
if has_val {
|
||||
if !opt.needs_argument() {
|
||||
if !needs_arg {
|
||||
return &ParseError{Message: fmt.Sprintf("The option: :yellow:`%s` does not take values", opt_str)}
|
||||
}
|
||||
return opt.add_value(opt_val)
|
||||
} else if opt.needs_argument() {
|
||||
} else if needs_arg {
|
||||
expecting_arg_for = opt
|
||||
} else {
|
||||
opt.add_value("")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ package cli
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -44,10 +45,12 @@ type Option struct {
|
||||
Hidden bool
|
||||
Depth int
|
||||
HelpText string
|
||||
IsList bool
|
||||
Parent *Command
|
||||
|
||||
values_from_cmdline []string
|
||||
parsed_values_from_cmdline []interface{}
|
||||
parsed_default interface{}
|
||||
seen_option string
|
||||
}
|
||||
|
||||
@ -77,6 +80,55 @@ func NormalizeOptionName(name string) string {
|
||||
return strings.ReplaceAll(strings.TrimLeft(name, "-"), "_", "-")
|
||||
}
|
||||
|
||||
func (self *Option) parsed_value() interface{} {
|
||||
if len(self.values_from_cmdline) == 0 {
|
||||
return self.parsed_default
|
||||
}
|
||||
switch self.OptionType {
|
||||
case CountOption:
|
||||
return len(self.parsed_values_from_cmdline)
|
||||
case StringOption:
|
||||
if self.IsList {
|
||||
return self.parsed_values_from_cmdline
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
return self.parsed_values_from_cmdline[len(self.parsed_values_from_cmdline)-1]
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Option) parse_value(val string) (interface{}, error) {
|
||||
switch self.OptionType {
|
||||
case BoolOption:
|
||||
switch val {
|
||||
case "true":
|
||||
return true, nil
|
||||
case "false":
|
||||
return false, nil
|
||||
default:
|
||||
return nil, &ParseError{Option: self, Message: fmt.Sprintf(":yellow:`%s` is not a valid value for :bold:`%s`.", val, self.seen_option)}
|
||||
}
|
||||
case StringOption:
|
||||
return val, nil
|
||||
case IntegerOption, CountOption:
|
||||
pval, err := strconv.ParseInt(val, 0, 0)
|
||||
if err != nil {
|
||||
return nil, &ParseError{Option: self, Message: fmt.Sprintf(
|
||||
":yellow:`%s` is not a valid number for :bold:`%s`. Only integers in decimal, hexadecimal, binary or octal notation are accepted.", val, self.seen_option)}
|
||||
}
|
||||
return pval, nil
|
||||
case FloatOption:
|
||||
pval, err := strconv.ParseFloat(val, 64)
|
||||
if err != nil {
|
||||
return nil, &ParseError{Option: self, Message: fmt.Sprintf(
|
||||
":yellow:`%s` is not a valid number for :bold:`%s`. Only floats in decimal and hexadecimal notation are accepted.", val, self.seen_option)}
|
||||
}
|
||||
return pval, nil
|
||||
default:
|
||||
return nil, &ParseError{Option: self, Message: fmt.Sprintf("Unknown option type for %s", self.Name)}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Option) add_value(val string) error {
|
||||
name_without_hyphens := NormalizeOptionName(self.seen_option)
|
||||
switch self.OptionType {
|
||||
@ -105,19 +157,10 @@ func (self *Option) add_value(val string) error {
|
||||
}
|
||||
self.values_from_cmdline = append(self.values_from_cmdline, val)
|
||||
self.parsed_values_from_cmdline = append(self.parsed_values_from_cmdline, val)
|
||||
case IntegerOption:
|
||||
pval, err := strconv.ParseInt(val, 0, 0)
|
||||
case IntegerOption, FloatOption:
|
||||
pval, err := self.parse_value(val)
|
||||
if err != nil {
|
||||
return &ParseError{Option: self, Message: fmt.Sprintf(
|
||||
":yellow:`%s` is not a valid number for :bold:`%s`. Only integers in decimal, hexadecimal, binary or octal notation are accepted.", val, self.seen_option)}
|
||||
}
|
||||
self.values_from_cmdline = append(self.values_from_cmdline, val)
|
||||
self.parsed_values_from_cmdline = append(self.parsed_values_from_cmdline, pval)
|
||||
case FloatOption:
|
||||
pval, err := strconv.ParseFloat(val, 64)
|
||||
if err != nil {
|
||||
return &ParseError{Option: self, Message: fmt.Sprintf(
|
||||
":yellow:`%s` is not a valid number for :bold:`%s`. Only floats in decimal and hexadecimal notation are accepted.", val, self.seen_option)}
|
||||
return err
|
||||
}
|
||||
self.values_from_cmdline = append(self.values_from_cmdline, val)
|
||||
self.parsed_values_from_cmdline = append(self.parsed_values_from_cmdline, pval)
|
||||
@ -333,3 +376,69 @@ func (self *Command) FindOption(name_with_hyphens string) *Option {
|
||||
type Context struct {
|
||||
SeenCommands []*Command
|
||||
}
|
||||
|
||||
func (self *Command) GetOptionValues(pointer_to_options_struct interface{}) error {
|
||||
m := make(map[string]*Option, 128)
|
||||
for _, g := range self.OptionGroups {
|
||||
for _, o := range g.Options {
|
||||
field_name := strings.ReplaceAll(strings.ToUpper(o.Name[:1]+o.Name[1:]), "-", "_")
|
||||
m[field_name] = o
|
||||
}
|
||||
}
|
||||
val := reflect.ValueOf(pointer_to_options_struct).Elem()
|
||||
if val.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("Need a pointer to a struct to set option values on")
|
||||
}
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
f := val.Field(i)
|
||||
field_name := val.Type().Field(i).Name
|
||||
if strings.ToUpper(field_name[:1]) != field_name[:1] || !f.CanSet() {
|
||||
continue
|
||||
}
|
||||
opt := m[field_name]
|
||||
if opt == nil {
|
||||
return fmt.Errorf("No option with the name: %s", field_name)
|
||||
}
|
||||
switch opt.OptionType {
|
||||
case IntegerOption, CountOption:
|
||||
if f.Kind() != reflect.Int {
|
||||
return fmt.Errorf("The field: %s must be an integer", field_name)
|
||||
}
|
||||
v := int64(opt.parsed_value().(int))
|
||||
if f.OverflowInt(v) {
|
||||
return fmt.Errorf("The value: %d is too large for the integer type used for the option: %s", v, field_name)
|
||||
}
|
||||
f.SetInt(v)
|
||||
case FloatOption:
|
||||
if f.Kind() != reflect.Float64 {
|
||||
return fmt.Errorf("The field: %s must be a float64", field_name)
|
||||
}
|
||||
v := opt.parsed_value().(float64)
|
||||
if f.OverflowFloat(v) {
|
||||
return fmt.Errorf("The value: %f is too large for the integer type used for the option: %s", v, field_name)
|
||||
}
|
||||
f.SetFloat(v)
|
||||
case BoolOption:
|
||||
if f.Kind() != reflect.Bool {
|
||||
return fmt.Errorf("The field: %s must be a boolean", field_name)
|
||||
}
|
||||
v := opt.parsed_value().(bool)
|
||||
f.SetBool(v)
|
||||
case StringOption:
|
||||
if opt.IsList {
|
||||
if f.Kind() != reflect.Slice {
|
||||
return fmt.Errorf("The field: %s must be a slice", field_name)
|
||||
}
|
||||
v := opt.parsed_value().([]string)
|
||||
f.Set(reflect.ValueOf(v))
|
||||
} else {
|
||||
if f.Kind() != reflect.String {
|
||||
return fmt.Errorf("The field: %s must be a string", field_name)
|
||||
}
|
||||
v := opt.parsed_value().(string)
|
||||
f.SetString(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user