mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-22 00:08:04 +02:00
Move config code into its own package
This commit is contained in:
@@ -1,177 +0,0 @@
|
||||
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
func StringToBool(x string) bool {
|
||||
x = strings.ToLower(x)
|
||||
return x == "y" || x == "yes" || x == "true"
|
||||
}
|
||||
|
||||
type ConfigLine struct {
|
||||
Src_file, Line string
|
||||
Line_number int
|
||||
Err error
|
||||
}
|
||||
|
||||
type ConfigParser struct {
|
||||
LineHandler func(key, val string) error
|
||||
|
||||
bad_lines []ConfigLine
|
||||
seen_includes map[string]bool
|
||||
override_env []string
|
||||
}
|
||||
|
||||
type Scanner interface {
|
||||
Scan() bool
|
||||
Text() string
|
||||
Err() error
|
||||
}
|
||||
|
||||
func (self *ConfigParser) BadLines() []ConfigLine {
|
||||
return self.bad_lines
|
||||
}
|
||||
|
||||
func (self *ConfigParser) parse(scanner Scanner, name, base_path_for_includes string, depth int) error {
|
||||
if self.seen_includes[name] { // avoid include loops
|
||||
return nil
|
||||
}
|
||||
self.seen_includes[name] = true
|
||||
|
||||
recurse := func(r io.Reader, nname, base_path_for_includes string) error {
|
||||
if depth > 32 {
|
||||
return fmt.Errorf("Too many nested include directives while processing config file: %s", name)
|
||||
}
|
||||
escanner := bufio.NewScanner(r)
|
||||
return self.parse(escanner, nname, base_path_for_includes, depth+1)
|
||||
}
|
||||
|
||||
lnum := 0
|
||||
make_absolute := func(path string) (string, error) {
|
||||
if path == "" {
|
||||
return "", fmt.Errorf("Empty include paths not allowed")
|
||||
}
|
||||
if !filepath.IsAbs(path) {
|
||||
path = filepath.Join(base_path_for_includes, path)
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimLeft(scanner.Text(), " ")
|
||||
lnum++
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
key, val, _ := strings.Cut(line, " ")
|
||||
switch key {
|
||||
default:
|
||||
err := self.LineHandler(key, val)
|
||||
if err != nil {
|
||||
self.bad_lines = append(self.bad_lines, ConfigLine{Src_file: name, Line: line, Line_number: lnum, Err: err})
|
||||
}
|
||||
case "include", "globinclude", "envinclude":
|
||||
var includes []string
|
||||
switch key {
|
||||
case "include":
|
||||
aval, err := make_absolute(val)
|
||||
if err == nil {
|
||||
includes = []string{aval}
|
||||
}
|
||||
case "globinclude":
|
||||
aval, err := make_absolute(val)
|
||||
if err == nil {
|
||||
matches, err := filepath.Glob(aval)
|
||||
if err == nil {
|
||||
includes = matches
|
||||
}
|
||||
}
|
||||
case "envinclude":
|
||||
env := self.override_env
|
||||
if env == nil {
|
||||
env = os.Environ()
|
||||
}
|
||||
for _, x := range env {
|
||||
key, eval, _ := strings.Cut(x, "=")
|
||||
is_match, err := filepath.Match(val, key)
|
||||
if is_match && err == nil {
|
||||
err := recurse(strings.NewReader(eval), "<env var: "+key+">", base_path_for_includes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(includes) > 0 {
|
||||
for _, incpath := range includes {
|
||||
raw, err := os.ReadFile(incpath)
|
||||
if err == nil {
|
||||
err := recurse(bytes.NewReader(raw), incpath, filepath.Dir(incpath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if !errors.Is(err, fs.ErrNotExist) {
|
||||
return fmt.Errorf("Failed to process include %#v with error: %w", incpath, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *ConfigParser) ParseFiles(paths ...string) error {
|
||||
for _, path := range paths {
|
||||
apath, err := filepath.Abs(path)
|
||||
if err == nil {
|
||||
path = apath
|
||||
}
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
scanner := bufio.NewScanner(bytes.NewReader(raw))
|
||||
self.seen_includes = make(map[string]bool)
|
||||
err = self.parse(scanner, path, filepath.Dir(path), 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type LinesScanner struct {
|
||||
lines []string
|
||||
}
|
||||
|
||||
func (self *LinesScanner) Scan() bool {
|
||||
return len(self.lines) > 0
|
||||
}
|
||||
|
||||
func (self *LinesScanner) Text() string {
|
||||
ans := self.lines[0]
|
||||
self.lines = self.lines[1:]
|
||||
return ans
|
||||
}
|
||||
|
||||
func (self *LinesScanner) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *ConfigParser) ParseOverrides(overrides ...string) error {
|
||||
s := LinesScanner{lines: overrides}
|
||||
self.seen_includes = make(map[string]bool)
|
||||
return self.parse(&s, "<overrides>", ConfigDir(), 0)
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
func TestConfigParsing(t *testing.T) {
|
||||
tdir := t.TempDir()
|
||||
conf_file := filepath.Join(tdir, "a.conf")
|
||||
os.Mkdir(filepath.Join(tdir, "sub"), 0o700)
|
||||
os.WriteFile(conf_file, []byte(`
|
||||
# ignore me
|
||||
a one
|
||||
#: other
|
||||
include sub/b.conf
|
||||
b
|
||||
include non-existent
|
||||
globinclude sub/c?.conf
|
||||
`), 0o600)
|
||||
os.WriteFile(filepath.Join(tdir, "sub/b.conf"), []byte("incb cool\ninclude a.conf"), 0o600)
|
||||
os.WriteFile(filepath.Join(tdir, "sub/c1.conf"), []byte("inc1 cool"), 0o600)
|
||||
os.WriteFile(filepath.Join(tdir, "sub/c2.conf"), []byte("inc2 cool\nenvinclude ENVINCLUDE"), 0o600)
|
||||
os.WriteFile(filepath.Join(tdir, "sub/c.conf"), []byte("inc notcool"), 0o600)
|
||||
|
||||
var parsed_lines []string
|
||||
pl := func(key, val string) error {
|
||||
if key == "error" {
|
||||
return fmt.Errorf("%s", val)
|
||||
}
|
||||
parsed_lines = append(parsed_lines, key+" "+val)
|
||||
return nil
|
||||
}
|
||||
|
||||
p := ConfigParser{LineHandler: pl, override_env: []string{"ENVINCLUDE=env cool\ninclude c.conf"}}
|
||||
err := p.ParseFiles(conf_file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
diff := cmp.Diff([]string{"a one", "incb cool", "b ", "inc1 cool", "inc2 cool", "env cool", "inc notcool"}, parsed_lines)
|
||||
if diff != "" {
|
||||
t.Fatalf("Unexpected parsed config values:\n%s", diff)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user