Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion internal/themes/themes.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,13 +239,24 @@ func (s *Service) Remove(id string) error {
}

func (s *Service) customThemes() ([]Theme, error) {
entries, err := os.ReadDir(s.dir)
// Stat before reading: a file sitting where the themes directory belongs
// makes ReadDir report not-exist on Windows, which the "nothing imported
// yet" branch below would swallow into an empty list on that OS and an
// error everywhere else.
info, err := os.Stat(s.dir)
if os.IsNotExist(err) {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("read themes dir: %w", err)
}
if !info.IsDir() {
return nil, fmt.Errorf("themes path is not a directory: %s", s.dir)
}
entries, err := os.ReadDir(s.dir)
if err != nil {
return nil, fmt.Errorf("read themes dir: %w", err)
}
themes := make([]Theme, 0, len(entries))
for _, entry := range entries {
if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" {
Expand Down
24 changes: 23 additions & 1 deletion internal/themes/themes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -577,14 +577,36 @@ func TestImportRejectsWindowsDeviceNames(t *testing.T) {
}
}

// \r? because the repo pins no .gitattributes, so a Windows checkout hands this
// test CRLF and a bare \n matches nothing there.
var docJSONBlock = regexp.MustCompile("(?s)```json\r?\n(.*?)```")

func TestDocJSONBlockReadsBothLineEndings(t *testing.T) {
for _, tt := range []struct{ name, newline string }{
{name: "lf", newline: "\n"},
{name: "crlf", newline: "\r\n"},
} {
t.Run(tt.name, func(t *testing.T) {
doc := "text" + tt.newline + "```json" + tt.newline + "{}" + tt.newline + "```"
block := docJSONBlock.FindStringSubmatch(doc)
if block == nil {
t.Fatalf("no JSON block found in a %s document", tt.name)
}
if strings.TrimSpace(block[1]) != "{}" {
t.Fatalf("block = %q, want the JSON body", block[1])
}
})
}
}

// The documented example is the shape users copy from, so it answers to the
// validator like any other imported theme.
func TestDocumentedExampleIsImportable(t *testing.T) {
data, err := os.ReadFile(filepath.Join("..", "..", "docs", "themes.md"))
if err != nil {
t.Fatalf("read docs/themes.md: %v", err)
}
block := regexp.MustCompile("(?s)```json\n(.*?)```").FindSubmatch(data)
block := docJSONBlock.FindSubmatch(data)
if block == nil {
t.Fatal("docs/themes.md has no JSON example")
}
Expand Down