More efficient multi line scanning

This commit is contained in:
Kovid Goyal 2022-09-21 06:37:23 +05:30
parent 2ddbe2a2bc
commit a3a89b3e21
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 151 additions and 35 deletions

View File

@ -3,12 +3,13 @@
package cli package cli
import ( import (
"bufio"
"fmt" "fmt"
"kitty/tools/utils" "reflect"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"kitty/tools/utils"
) )
var _ = fmt.Print var _ = fmt.Print
@ -41,6 +42,51 @@ the specified depth.
Set the help text to "!" to have an option hidden. Set the help text to "!" to have an option hidden.
*/ */
func OptionFromString(entries ...string) (*Option, error) { func OptionFromString(entries ...string) (*Option, error) {
return option_from_string(map[string]string{}, entries...)
}
func OptionsFromStruct(pointer_to_options_struct interface{}) ([]*Option, error) {
val := reflect.ValueOf(pointer_to_options_struct).Elem()
if val.Kind() != reflect.Struct {
return nil, fmt.Errorf("Need a pointer to a struct to set option values on")
}
ans := make([]*Option, 0, val.NumField())
for i := 0; i < val.NumField(); i++ {
f := val.Field(i)
field_name := val.Type().Field(i).Name
tag := val.Type().Field(i).Tag
if utils.Capitalize(field_name) != field_name || !f.CanSet() {
continue
}
typ := "str"
switch f.Kind() {
case reflect.Slice:
typ = "list"
case reflect.Int:
typ = "int"
case reflect.Float64:
typ = "float"
case reflect.Bool:
typ = "bool-set"
}
overrides := map[string]string{"dest": field_name, "type": typ}
opt, err := option_from_string(overrides, string(tag))
if err != nil {
return nil, err
}
ans = append(ans, opt)
}
return ans, nil
}
type multi_scan struct {
entries []string
}
var mpat *regexp.Regexp
func option_from_string(overrides map[string]string, entries ...string) (*Option, error) {
if mpat == nil { if mpat == nil {
mpat = regexp.MustCompile("^([a-z]+)=(.+)") mpat = regexp.MustCompile("^([a-z]+)=(.+)")
} }
@ -48,7 +94,7 @@ func OptionFromString(entries ...string) (*Option, error) {
values_from_cmdline: make([]string, 0, 1), values_from_cmdline: make([]string, 0, 1),
parsed_values_from_cmdline: make([]interface{}, 0, 1), parsed_values_from_cmdline: make([]interface{}, 0, 1),
} }
scanner := bufio.NewScanner(strings.NewReader(strings.Join(entries, "\n"))) scanner := utils.NewScanLines(entries...)
in_help := false in_help := false
prev_indent := 0 prev_indent := 0
help := strings.Builder{} help := strings.Builder{}
@ -66,12 +112,55 @@ func OptionFromString(entries ...string) (*Option, error) {
} }
} }
set_type := func(v string) error {
switch v {
case "choice", "choices":
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":
ans.IsList = true
fallthrough
case "str", "string":
ans.OptionType = StringOption
default:
return fmt.Errorf("Unknown option type: %s", v)
}
return nil
}
if dq, found := overrides["type"]; found {
err := set_type(dq)
if err != nil {
return nil, err
}
}
for scanner.Scan() { for scanner.Scan() {
line := scanner.Text() line := scanner.Text()
if ans.Aliases == nil { if ans.Aliases == nil {
if strings.HasPrefix(line, "--") { if strings.HasPrefix(line, "--") {
parts := strings.Split(line, " ") parts := strings.Split(line, " ")
ans.Name = camel_case_dest(parts[0]) if dq, found := overrides["dest"]; found {
ans.Name = camel_case_dest(dq)
} else {
ans.Name = camel_case_dest(parts[0])
}
ans.Aliases = make([]Alias, 0, len(parts)) ans.Aliases = make([]Alias, 0, len(parts))
for i, x := range parts { for i, x := range parts {
ans.Aliases[i] = Alias{NameWithoutHyphens: strings.TrimLeft(x, "-"), IsShort: !strings.HasPrefix(x, "--")} ans.Aliases[i] = Alias{NameWithoutHyphens: strings.TrimLeft(x, "-"), IsShort: !strings.HasPrefix(x, "--")}
@ -120,7 +209,11 @@ func OptionFromString(entries ...string) (*Option, error) {
case "default": case "default":
ans.Default = v ans.Default = v
case "dest": case "dest":
ans.Name = camel_case_dest(v) if dq, found := overrides["dest"]; found {
ans.Name = camel_case_dest(dq)
} else {
ans.Name = camel_case_dest(v)
}
case "depth": case "depth":
depth, err := strconv.ParseInt(v, 0, 0) depth, err := strconv.ParseInt(v, 0, 0)
if err != nil { if err != nil {
@ -131,34 +224,9 @@ func OptionFromString(entries ...string) (*Option, error) {
default: default:
return nil, fmt.Errorf("Unknown option metadata key: %s", k) return nil, fmt.Errorf("Unknown option metadata key: %s", k)
case "type": case "type":
switch v { err := set_type(v)
case "choice", "choices": if err != nil {
ans.OptionType = StringOption return nil, err
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":
ans.IsList = true
fallthrough
case "str", "string":
ans.OptionType = StringOption
default:
return nil, fmt.Errorf("Unknown option type: %s", v)
} }
} }
} }
@ -173,5 +241,11 @@ func OptionFromString(entries ...string) (*Option, error) {
if ans.IsList { if ans.IsList {
ans.parsed_default = []string{} 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 &ans, nil
} }

View File

@ -69,8 +69,6 @@ func (self *Option) HasAlias(name_without_hyphens string, is_short bool) bool {
return false return false
} }
var mpat *regexp.Regexp
type ParseError struct { type ParseError struct {
Option *Option Option *Option
Message string Message string

View File

@ -3,6 +3,7 @@
package utils package utils
import ( import (
"bufio"
"fmt" "fmt"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
@ -18,3 +19,46 @@ func Capitalize(x string) string {
cr := strings.ToUpper(string(s)) cr := strings.ToUpper(string(s))
return cr + x[sz:] return cr + x[sz:]
} }
type ScanLines struct {
entries []string
scanner *bufio.Scanner
}
func NewScanLines(entries ...string) *ScanLines {
return &ScanLines{entries: entries}
}
func (self *ScanLines) Scan() bool {
if self.scanner == nil {
if len(self.entries) == 0 {
return false
}
self.scanner = bufio.NewScanner(strings.NewReader(self.entries[0]))
self.entries = self.entries[1:]
return self.Scan()
} else {
if self.scanner.Scan() {
return true
}
self.scanner = nil
return self.Scan()
}
}
func (self *ScanLines) Text() string {
if self.scanner == nil {
return ""
}
return self.scanner.Text()
}
func Splitlines(x string) []string {
ans := make([]string, 0, 8)
scanner := bufio.NewScanner(strings.NewReader(x))
for scanner.Scan() {
ans = append(ans, scanner.Text())
}
return ans
}