Add support for more option types to Go conf file parsing
This commit is contained in:
parent
3803d7e3c2
commit
da38cb3254
@ -442,8 +442,15 @@ def write_output(loc: str, defn: Definition) -> None:
|
||||
f.write(f'{c}\n')
|
||||
|
||||
|
||||
def go_type_data(parser_func: ParserFuncType, ctype: str) -> Tuple[str, str]:
|
||||
def go_type_data(parser_func: ParserFuncType, ctype: str, is_multiple: bool = False) -> Tuple[str, str]:
|
||||
if ctype:
|
||||
if ctype == 'string':
|
||||
if is_multiple:
|
||||
return 'string', '[]string{val}, nil'
|
||||
return 'string', 'val, nil'
|
||||
if ctype.startswith('strdict_'):
|
||||
_, rsep, fsep = ctype.split('_', 2)
|
||||
return 'map[string]string', f'config.ParseStrDict(val, `{rsep}`, `{fsep}`)'
|
||||
return f'*{ctype}', f'Parse{ctype}(val)'
|
||||
p = parser_func.__name__
|
||||
if p == 'int':
|
||||
@ -454,14 +461,26 @@ def go_type_data(parser_func: ParserFuncType, ctype: str) -> Tuple[str, str]:
|
||||
return 'float64', 'strconv.ParseFloat(val, 10, 64)'
|
||||
if p == 'to_bool':
|
||||
return 'bool', 'config.StringToBool(val), nil'
|
||||
if p == 'to_color':
|
||||
return 'style.RGBA', 'style.ParseColor(val)'
|
||||
if p == 'to_color_or_none':
|
||||
return 'style.NullableColor', 'style.ParseColorOrNone(val)'
|
||||
if p == 'positive_int':
|
||||
return 'uint64', 'strconv.ParseUint(val, 10, 64)'
|
||||
if p == 'positive_float':
|
||||
return 'float64', 'config.PositiveFloat(val, 10, 64)'
|
||||
if p == 'unit_float':
|
||||
return 'float64', 'config.UnitFloat(val, 10, 64)'
|
||||
if p == 'python_string':
|
||||
return 'string', 'config.StringLiteral(val, 10, 64)'
|
||||
th = get_type_hints(parser_func)
|
||||
rettype = th['return']
|
||||
return {int: 'int64', str: 'string', float: 'float64'}[rettype], f'{p}(val)'
|
||||
|
||||
|
||||
def gen_go_code(defn: Definition) -> str:
|
||||
lines = ['import "fmt"', 'import "strconv"', 'import "kitty/tools/config"',
|
||||
'var _ = fmt.Println', 'var _ = config.StringToBool', 'var _ = strconv.Atoi']
|
||||
lines = ['import "fmt"', 'import "strconv"', 'import "kitty/tools/config"', 'import "kitty/tools/utils/style"',
|
||||
'var _ = fmt.Println', 'var _ = config.StringToBool', 'var _ = strconv.Atoi', 'var _ = style.ParseColor']
|
||||
a = lines.append
|
||||
choices = {}
|
||||
go_types = {}
|
||||
@ -471,7 +490,7 @@ def gen_go_code(defn: Definition) -> str:
|
||||
for option in sorted(defn.iter_all_options(), key=lambda a: natural_keys(a.name)):
|
||||
name = option.name.capitalize()
|
||||
if isinstance(option, MultiOption):
|
||||
go_types[name], go_parsers[name] = go_type_data(option.parser_func, option.ctype)
|
||||
go_types[name], go_parsers[name] = go_type_data(option.parser_func, option.ctype, True)
|
||||
multiopts.add(name)
|
||||
else:
|
||||
defaults[name] = option.parser_func(option.defval_as_string)
|
||||
@ -497,6 +516,8 @@ def gen_go_code(defn: Definition) -> str:
|
||||
|
||||
a('func NewConfig() *Config {')
|
||||
a('return &Config{')
|
||||
from kitty.cli import serialize_as_go_string
|
||||
from kitty.fast_data_types import Color
|
||||
for name, pname in go_parsers.items():
|
||||
if name in multiopts:
|
||||
continue
|
||||
@ -507,6 +528,15 @@ def gen_go_code(defn: Definition) -> str:
|
||||
dval = f'{name}_{cval(d)}' if name in choices else f'`{d}`'
|
||||
elif isinstance(d, bool):
|
||||
dval = repr(d).lower()
|
||||
elif isinstance(d, dict):
|
||||
dval = 'map[string]string{'
|
||||
for k, v in d.items():
|
||||
dval += f'"{serialize_as_go_string(k)}": "{serialize_as_go_string(v)}",'
|
||||
dval += '}'
|
||||
elif isinstance(d, Color):
|
||||
dval = f'style.RGBA{{Red:{d.red}, Green: {d.green}, Blue: {d.blue}}}'
|
||||
if 'NullableColor' in go_types[name]:
|
||||
dval = f'style.NullableColor{{Color:{dval}}}'
|
||||
else:
|
||||
dval = repr(d)
|
||||
a(f'{name}: {dval},')
|
||||
|
||||
185
tools/config/utils.go
Normal file
185
tools/config/utils.go
Normal file
@ -0,0 +1,185 @@
|
||||
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"kitty/tools/utils"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
func ParseStrDict(val, record_sep, field_sep string) (map[string]string, error) {
|
||||
ans := make(map[string]string)
|
||||
for _, record := range strings.Split(val, record_sep) {
|
||||
key, val, found := strings.Cut(record, field_sep)
|
||||
if found {
|
||||
ans[key] = val
|
||||
}
|
||||
}
|
||||
return ans, nil
|
||||
}
|
||||
|
||||
func PositiveFloat(val string) (ans float64, err error) {
|
||||
ans, err = strconv.ParseFloat(val, 64)
|
||||
if err == nil {
|
||||
ans = utils.Max(0, ans)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func UnitFloat(val string) (ans float64, err error) {
|
||||
ans, err = strconv.ParseFloat(val, 64)
|
||||
if err == nil {
|
||||
ans = utils.Max(0, utils.Min(ans, 1))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func StringLiteral(val string) (string, error) {
|
||||
ans := strings.Builder{}
|
||||
ans.Grow(len(val))
|
||||
var buf [8]rune
|
||||
bufcount := 0
|
||||
buflimit := 0
|
||||
var prefix rune
|
||||
type State int
|
||||
const (
|
||||
normal State = iota
|
||||
backslash
|
||||
octal
|
||||
hex
|
||||
)
|
||||
var state State
|
||||
decode := func(base int) {
|
||||
text := string(buf[:bufcount])
|
||||
num, _ := strconv.ParseUint(text, base, 32)
|
||||
ans.WriteRune(rune(num))
|
||||
state = normal
|
||||
bufcount = 0
|
||||
buflimit = 0
|
||||
prefix = 0
|
||||
}
|
||||
|
||||
write_invalid_buf := func() {
|
||||
ans.WriteByte('\\')
|
||||
ans.WriteRune(prefix)
|
||||
for _, r := range buf[:bufcount] {
|
||||
ans.WriteRune(r)
|
||||
}
|
||||
state = normal
|
||||
bufcount = 0
|
||||
buflimit = 0
|
||||
prefix = 0
|
||||
}
|
||||
|
||||
var dispatch_ch_recurse func(rune)
|
||||
|
||||
dispatch_ch := func(ch rune) {
|
||||
switch state {
|
||||
case normal:
|
||||
switch ch {
|
||||
case '\\':
|
||||
state = backslash
|
||||
default:
|
||||
ans.WriteRune(ch)
|
||||
}
|
||||
case octal:
|
||||
switch ch {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7':
|
||||
if bufcount >= buflimit {
|
||||
decode(8)
|
||||
dispatch_ch_recurse(ch)
|
||||
} else {
|
||||
buf[bufcount] = ch
|
||||
bufcount++
|
||||
}
|
||||
default:
|
||||
decode(8)
|
||||
dispatch_ch_recurse(ch)
|
||||
}
|
||||
case hex:
|
||||
switch ch {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'A', 'b', 'B', 'c', 'C', 'd', 'D', 'e', 'E', 'f', 'F':
|
||||
buf[bufcount] = ch
|
||||
bufcount++
|
||||
if bufcount >= buflimit {
|
||||
decode(16)
|
||||
}
|
||||
default:
|
||||
write_invalid_buf()
|
||||
dispatch_ch_recurse(ch)
|
||||
}
|
||||
case backslash:
|
||||
switch ch {
|
||||
case '\n':
|
||||
case '\\':
|
||||
ans.WriteRune('\\')
|
||||
state = normal
|
||||
case '\'', '"':
|
||||
ans.WriteRune(ch)
|
||||
state = normal
|
||||
case 'a':
|
||||
ans.WriteRune('\a')
|
||||
state = normal
|
||||
case 'b':
|
||||
ans.WriteRune('\b')
|
||||
state = normal
|
||||
case 'f':
|
||||
ans.WriteRune('\f')
|
||||
state = normal
|
||||
case 'n':
|
||||
ans.WriteRune('\n')
|
||||
state = normal
|
||||
case 'r':
|
||||
ans.WriteRune('\r')
|
||||
state = normal
|
||||
case 't':
|
||||
ans.WriteRune('\t')
|
||||
state = normal
|
||||
case 'v':
|
||||
ans.WriteRune('\v')
|
||||
state = normal
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7':
|
||||
buf[0] = ch
|
||||
bufcount = 1
|
||||
buflimit = 3
|
||||
state = octal
|
||||
case 'x':
|
||||
bufcount = 0
|
||||
buflimit = 2
|
||||
state = hex
|
||||
prefix = ch
|
||||
case 'u':
|
||||
bufcount = 0
|
||||
buflimit = 4
|
||||
state = hex
|
||||
prefix = ch
|
||||
case 'U':
|
||||
bufcount = 0
|
||||
buflimit = 8
|
||||
state = hex
|
||||
prefix = ch
|
||||
default:
|
||||
ans.WriteByte('\\')
|
||||
ans.WriteRune(ch)
|
||||
state = normal
|
||||
}
|
||||
}
|
||||
}
|
||||
dispatch_ch_recurse = dispatch_ch
|
||||
for _, ch := range val {
|
||||
dispatch_ch(ch)
|
||||
}
|
||||
switch state {
|
||||
case octal:
|
||||
decode(8)
|
||||
case hex:
|
||||
write_invalid_buf()
|
||||
case backslash:
|
||||
ans.WriteRune('\\')
|
||||
}
|
||||
return ans.String(), nil
|
||||
}
|
||||
26
tools/config/utils_test.go
Normal file
26
tools/config/utils_test.go
Normal file
@ -0,0 +1,26 @@
|
||||
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
func TestStringLiteralParsing(t *testing.T) {
|
||||
for q, expected := range map[string]string{
|
||||
`abc`: `abc`,
|
||||
`a\nb\M`: "a\nb\\M",
|
||||
`a\x20\x1\u1234\123\12|`: "a \\x1\u1234\123\x0a|",
|
||||
} {
|
||||
actual, err := StringLiteral(q)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if expected != actual {
|
||||
t.Fatalf("Failed with input: %#v\n%#v != %#v", q, expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user