From 2b7d6d45df599168711d9c210db83419097b6d44 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 18 Feb 2023 21:58:14 +0530 Subject: [PATCH] Finish up config parser port --- kitty_tests/ssh.py | 2 -- tools/utils/config.go | 71 +++++++++++++++++++++++++++----------- tools/utils/config_test.go | 52 ++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 22 deletions(-) create mode 100644 tools/utils/config_test.go diff --git a/kitty_tests/ssh.py b/kitty_tests/ssh.py index a6b9d6046..dc6458aee 100644 --- a/kitty_tests/ssh.py +++ b/kitty_tests/ssh.py @@ -11,8 +11,6 @@ from functools import lru_cache from kittens.ssh.config import load_config from kittens.ssh.main import bootstrap_script, get_connection_data, wrap_bootstrap_script -from kittens.ssh.options.types import Options as SSHOptions -from kittens.ssh.options.utils import DELETE_ENV_VAR from kittens.transfer.utils import set_paths from kitty.constants import is_macos, runtime_dir from kitty.fast_data_types import CURSOR_BEAM, shm_unlink diff --git a/tools/utils/config.go b/tools/utils/config.go index a46b6d9d5..3eecc4f3e 100644 --- a/tools/utils/config.go +++ b/tools/utils/config.go @@ -4,8 +4,10 @@ package utils import ( "bufio" + "bytes" "errors" "fmt" + "io" "io/fs" "os" "path/filepath" @@ -26,8 +28,11 @@ type ConfigLine struct { } type ConfigParser struct { - BadLines []ConfigLine LineHandler func(key, val string) error + + bad_lines []ConfigLine + seen_includes map[string]bool + override_env []string } type Scanner interface { @@ -36,7 +41,24 @@ type Scanner interface { Err() error } -func (self *ConfigParser) parse(scanner Scanner, name, base_path_for_includes string) 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 == "" { @@ -58,7 +80,7 @@ func (self *ConfigParser) parse(scanner Scanner, name, base_path_for_includes st default: err := self.LineHandler(key, val) if err != nil { - self.BadLines = append(self.BadLines, ConfigLine{Src_file: name, Line: line, Line_number: lnum, Err: err}) + 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 @@ -77,12 +99,15 @@ func (self *ConfigParser) parse(scanner Scanner, name, base_path_for_includes st } } case "envinclude": - for _, x := range os.Environ() { + 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 { - escanner := bufio.NewScanner(strings.NewReader(eval)) - err := self.parse(escanner, "", base_path_for_includes) + err := recurse(strings.NewReader(eval), "", base_path_for_includes) if err != nil { return err } @@ -93,8 +118,7 @@ func (self *ConfigParser) parse(scanner Scanner, name, base_path_for_includes st for _, incpath := range includes { raw, err := os.ReadFile(incpath) if err == nil { - escanner := bufio.NewScanner(strings.NewReader(UnsafeBytesToString(raw))) - err := self.parse(escanner, incpath, filepath.Dir(incpath)) + err := recurse(bytes.NewReader(raw), incpath, filepath.Dir(incpath)) if err != nil { return err } @@ -108,18 +132,24 @@ func (self *ConfigParser) parse(scanner Scanner, name, base_path_for_includes st return nil } -func (self *ConfigParser) ParseFile(path string) error { - apath, err := filepath.Abs(path) - if err == nil { - path = apath +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 + } } - f, err := os.Open(path) - if err != nil { - return err - } - defer f.Close() - scanner := bufio.NewScanner(f) - return self.parse(scanner, f.Name(), filepath.Dir(f.Name())) + return nil } type LinesScanner struct { @@ -142,5 +172,6 @@ func (self *LinesScanner) Err() error { func (self *ConfigParser) ParseOverrides(overrides ...string) error { s := LinesScanner{lines: overrides} - return self.parse(&s, "", ConfigDir()) + self.seen_includes = make(map[string]bool) + return self.parse(&s, "", ConfigDir(), 0) } diff --git a/tools/utils/config_test.go b/tools/utils/config_test.go new file mode 100644 index 000000000..07430f78b --- /dev/null +++ b/tools/utils/config_test.go @@ -0,0 +1,52 @@ +// License: GPLv3 Copyright: 2023, Kovid Goyal, + +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) + } +}