diff --git a/tools/config/api.go b/tools/config/api.go index 7f4967762..d9bf80779 100644 --- a/tools/config/api.go +++ b/tools/config/api.go @@ -29,7 +29,9 @@ type ConfigLine struct { } type ConfigParser struct { - LineHandler func(key, val string) error + LineHandler func(key, val string) error + CommentsHandler func(line string) error + SourceHandler func(text, path string) bad_lines []ConfigLine seen_includes map[string]bool @@ -73,7 +75,16 @@ func (self *ConfigParser) parse(scanner Scanner, name, base_path_for_includes st for scanner.Scan() { line := strings.TrimLeft(scanner.Text(), " ") lnum++ - if line == "" || strings.HasPrefix(line, "#") { + if line == "" { + continue + } + if line[0] == '#' { + if self.CommentsHandler != nil { + err := self.CommentsHandler(line) + if err != nil { + self.bad_lines = append(self.bad_lines, ConfigLine{Src_file: name, Line: line, Line_number: lnum, Err: err}) + } + } continue } key, val, _ := strings.Cut(line, " ") @@ -149,6 +160,9 @@ func (self *ConfigParser) ParseFiles(paths ...string) error { if err != nil { return err } + if self.SourceHandler != nil { + self.SourceHandler(utils.UnsafeBytesToString(raw), path) + } } return nil } diff --git a/tools/themes/collection.go b/tools/themes/collection.go index c82321680..d9bc3b350 100644 --- a/tools/themes/collection.go +++ b/tools/themes/collection.go @@ -4,12 +4,12 @@ package themes import ( "archive/zip" - "bufio" "encoding/json" "errors" "fmt" "io" "io/fs" + "kitty/tools/config" "kitty/tools/utils" "kitty/tools/utils/style" "net/http" @@ -136,52 +136,45 @@ type ThemeMetadata struct { Author string `json:"author"` } -func parse_theme_metadata(raw string) *ThemeMetadata { - scanner := bufio.NewScanner(strings.NewReader(raw)) +func parse_theme_metadata(path string) (*ThemeMetadata, string, error) { var in_metadata, in_blurb, finished_metadata bool ans := ThemeMetadata{} settings := utils.NewSet[string]() - for scanner.Scan() { - line := strings.TrimSpace(scanner.Text()) - if line == "" { - continue + read_is_dark := func(key, val string) (err error) { + settings.Add(key) + if key == "background" { + val = strings.TrimSpace(val) + if val != "" { + bg, err := style.ParseColor(val) + if err == nil { + ans.Is_dark = utils.Max(bg.Red, bg.Green, bg.Green) < 115 + } + } } + return + } + read_metadata := func(line string) (err error) { is_block := strings.HasPrefix(line, "## ") if in_metadata && !is_block { finished_metadata = true } if finished_metadata { - if line[0] != '#' { - key, val, found := strings.Cut(line, " ") - if found { - settings.Add(key) - if key == "background" { - val = strings.TrimSpace(val) - if val != "" { - bg, err := style.ParseColor(val) - if err == nil { - ans.Is_dark = utils.Max(bg.Red, bg.Green, bg.Green) < 115 - } - } - } - } - } - continue + return } if !in_metadata && is_block { in_metadata = true } if !in_metadata { - continue + return } line = line[3:] if in_blurb { ans.Blurb += " " + line - continue + return } key, val, found := strings.Cut(line, ":") if !found { - continue + return } key = strings.TrimSpace(strings.ToLower(key)) val = strings.TrimSpace(val) @@ -198,9 +191,16 @@ func parse_theme_metadata(raw string) *ThemeMetadata { case "license": ans.License = val } + return + } + source := "" + cp := config.ConfigParser{LineHandler: read_is_dark, CommentsHandler: read_metadata, SourceHandler: func(code, path string) { source = code }} + err := cp.ParseFiles(path) + if err != nil { + return nil, "", err } ans.Num_settings = settings.Len() - return &ans + return &ans, source, nil } type Theme struct { @@ -217,7 +217,7 @@ type Themes struct { } var camel_case_pat = (&utils.Once[*regexp.Regexp]{Run: func() *regexp.Regexp { - return regexp.MustCompile(`[a-z][A-Z]`) + return regexp.MustCompile(`([a-z])([A-Z])`) }}).Get func theme_name_from_file_name(fname string) string { @@ -237,12 +237,10 @@ func (self *Themes) add_from_dir(dirpath string) error { } for _, e := range entries { if !e.IsDir() && strings.HasSuffix(e.Name(), ".conf") { - confb, err := os.ReadFile(e.Name()) + m, conf, err := parse_theme_metadata(filepath.Join(dirpath, e.Name())) if err != nil { return err } - conf := utils.UnsafeBytesToString(confb) - m := parse_theme_metadata(conf) if m.Name == "" { m.Name = theme_name_from_file_name(e.Name()) } diff --git a/tools/themes/collection_test.go b/tools/themes/collection_test.go new file mode 100644 index 000000000..b1728a8c7 --- /dev/null +++ b/tools/themes/collection_test.go @@ -0,0 +1,46 @@ +// License: GPLv3 Copyright: 2023, Kovid Goyal, + +package themes + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" +) + +var _ = fmt.Print + +func TestThemeCollections(t *testing.T) { + for fname, expected := range map[string]string{ + "moose": "Moose", + "mooseCat": "Moose Cat", + "a_bC": "A B C", + } { + actual := theme_name_from_file_name(fname) + if diff := cmp.Diff(expected, actual); diff != "" { + t.Fatalf("Unexpected theme name for %s:\n%s", fname, diff) + } + } + + tdir := t.TempDir() + + pt := func(expected ThemeMetadata, lines ...string) { + os.WriteFile(filepath.Join(tdir, "temp.conf"), []byte(strings.Join(lines, "\n")), 0o600) + actual, _, err := parse_theme_metadata(filepath.Join(tdir, "temp.conf")) + if err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(&expected, actual); diff != "" { + t.Fatalf("Failed to parse:\n%s\n\n%s", strings.Join(lines, "\n"), diff) + } + } + pt(ThemeMetadata{Name: "XYZ", Blurb: "a b", Author: "A", Is_dark: true, Num_settings: 2}, + "# some crap", " ", "## ", "## author: A", "## name: XYZ", "## blurb: a", "## b", "", "color red", "background black", "include inc.conf") + os.WriteFile(filepath.Join(tdir, "inc.conf"), []byte("background white"), 0o600) + pt(ThemeMetadata{Name: "XYZ", Blurb: "a b", Author: "A", Num_settings: 2}, + "# some crap", " ", "## ", "## author: A", "## name: XYZ", "## blurb: a", "## b", "", "color red", "background black", "include inc.conf") +}