package config import ( "bytes" "encoding/json" "fmt" "image/color" "log/slog" "github.com/mazznoer/csscolorparser" mjson "go.starlark.net/lib/json" "go.starlark.net/starlark" "go.starlark.net/syntax" ) type Lark struct { t *starlark.Thread config *starlark.Dict log *slog.Logger } var colorSchemes = map[string]string{"default": `{ "foreground":"#e5dae5", "background":"#151515", "cursorColor2": "#e5dae5", "color0": "#202020", "color8": "#735264", "color1": "#e84f4f", "color9": "#d43131", "color2": "#b8d68c", "color10": "#578d3b", "color3": "#e2a959", "color11": "#f39713", "color4": "#7dc1cf", "color12": "#4e9fb1", "color5": "#9b64fb", "color13": "#7c1ede", "color6": "#6d878d", "color14": "#42717b", "color7": "#dddddd", "color15": "#dddddd" }`} var defaultConfig = fmt.Sprintf(` config.update({ "title": "erm", "initial_cols": 120, "initial_rows": 24, "font": { "style": "Fira Mono", "size": 12.0, }, }) config.update({"colors": json.decode('%s')}) `, compact(colorSchemes["default"])) func NewLark(log *slog.Logger) (*Lark, error) { t := &starlark.Thread{ Name: "config thread", } config := starlark.NewDict(0) preConfig := starlark.StringDict{ "json": mjson.Module, "config": config, } _, err := starlark.ExecFileOptions(syntax.LegacyFileOptions(), t, "preload.star", defaultConfig, preConfig) if err != nil { if evalErr, ok := err.(*starlark.EvalError); ok { log.Error("failed to evaluate starlark preload", "trace", evalErr.Backtrace()) } return nil, err } l := &Lark{ t: t, config: config, log: log, } if err := l.loadConfig(); err != nil { log.Error("failed to load config", "err", err) } log.Info("config", "conf", config) return l, nil } func (o *Lark) loadConfig() error { path, err := getConfigPath() if err != nil { return err } o.log.Info("loading config", "path", path) preConfig := starlark.StringDict{ "json": mjson.Module, "config": o.config, } _, err = starlark.ExecFileOptions(syntax.LegacyFileOptions(), o.t, path, nil, preConfig) if err != nil { if evalErr, ok := err.(*starlark.EvalError); ok { o.log.Error("failed to evaluate starlark config", "path", path, "trace", evalErr.Backtrace()) } return err } return nil } func Must[T any](value T, err error) T { if err != nil { panic(err) } return value } func Default[T any](x T) func(value T, err error) T { return func(value T, err error) T { if err != nil { return x } return value } } func (l *Lark) Truthy(path ...string) bool { val, err := l.Val(path...) if err != nil { return false } return bool(val.Truth()) } func (l *Lark) Val(path ...string) (starlark.Value, error) { val := l.config for idx, item := range path { tmpVal, ok, err := val.Get(starlark.String(item)) if err != nil { return nil, err } if !ok { return nil, ErrNotFound } if len(path)-1 == idx { return tmpVal, nil } val, ok = tmpVal.(*starlark.Dict) if !ok { return nil, ErrWrongType } } return val, nil } func (l *Lark) Float64(path ...string) (float64, error) { val, err := l.Val(path...) if err != nil { return 0, err } str, ok := val.(starlark.Float) if !ok { return 0, ErrWrongType } return float64(str), nil } func (l *Lark) Int(path ...string) (int, error) { val, err := l.Val(path...) if err != nil { return 0, err } str, ok := val.(starlark.Int) if !ok { return 0, ErrWrongType } return int(str.BigInt().Int64()), nil } func (l *Lark) Str(path ...string) (string, error) { val, err := l.Val(path...) if err != nil { return "", err } str, ok := val.(starlark.String) if !ok { return "", ErrWrongType } return str.GoString(), nil } func (l *Lark) Color(path ...string) (color.Color, error) { str, err := l.Str(path...) if err != nil { return nil, err } clor, err := csscolorparser.Parse(str) if err != nil { return nil, err } return clor, nil } func (l *Lark) Font(path ...string) (*Font, error) { // make sure the parent element exists _, err := l.Val(path...) if err != nil { return nil, err } fill := &Font{} if x, err := l.Str(append(path, "family")...); err == nil { fill.Family = x } if x, err := l.Str(append(path, "style")...); err == nil { fill.Style = x } if x, err := l.Float64(append(path, "size")...); err == nil { fill.Size = x } if x, err := l.Float64(append(path, "dpi")...); err == nil { fill.DPI = x } fill.Ligatures = l.Truthy(append(path, "ligatures")...) return fill, nil } func compact(x string) string { dst := &bytes.Buffer{} if err := json.Compact(dst, []byte(x)); err != nil { panic(err) } return dst.String() }