feat(theming): add theme discovery with gruvbox assets
This commit is contained in:
97
internal/theming/theming.go
Normal file
97
internal/theming/theming.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// Package theming applies a selected colorscheme across starship, zellij,
|
||||
// yazi, lazygit, and NvChad. Themes are discovered from embedded manifest
|
||||
// files under assets/<id>/theme.yaml at package init time.
|
||||
package theming
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
//go:embed assets
|
||||
var assetsFS embed.FS
|
||||
|
||||
// Theme describes a colorscheme discovered under assets/<id>/theme.yaml.
|
||||
type Theme struct {
|
||||
ID string
|
||||
DisplayName string
|
||||
NvChadTheme string
|
||||
}
|
||||
|
||||
type manifest struct {
|
||||
DisplayName string `yaml:"display_name"`
|
||||
NvChad string `yaml:"nvchad"`
|
||||
}
|
||||
|
||||
var discovered []Theme
|
||||
|
||||
func init() {
|
||||
themes, err := discover()
|
||||
if err != nil {
|
||||
// Package is embed-only; discovery failure is a programmer error,
|
||||
// not runtime input. Panic so tests surface the mistake early.
|
||||
panic(fmt.Errorf("theming: discover themes: %w", err))
|
||||
}
|
||||
discovered = themes
|
||||
}
|
||||
|
||||
func discover() ([]Theme, error) {
|
||||
entries, err := fs.ReadDir(assetsFS, "assets")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out []Theme
|
||||
for _, e := range entries {
|
||||
if !e.IsDir() {
|
||||
continue
|
||||
}
|
||||
id := e.Name()
|
||||
m, err := readManifest(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("theme %q: %w", id, err)
|
||||
}
|
||||
out = append(out, Theme{ID: id, DisplayName: m.DisplayName, NvChadTheme: m.NvChad})
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID })
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func readManifest(id string) (manifest, error) {
|
||||
data, err := assetsFS.ReadFile(filepath.Join("assets", id, "theme.yaml"))
|
||||
if err != nil {
|
||||
return manifest{}, err
|
||||
}
|
||||
var m manifest
|
||||
if err := yaml.Unmarshal(data, &m); err != nil {
|
||||
return manifest{}, err
|
||||
}
|
||||
if m.DisplayName == "" {
|
||||
return manifest{}, fmt.Errorf("empty display_name")
|
||||
}
|
||||
if m.NvChad == "" {
|
||||
return manifest{}, fmt.Errorf("empty nvchad")
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// List returns the themes sorted by ID.
|
||||
func List() []Theme {
|
||||
out := make([]Theme, len(discovered))
|
||||
copy(out, discovered)
|
||||
return out
|
||||
}
|
||||
|
||||
// Get returns the theme with the given ID, if present.
|
||||
func Get(id string) (Theme, bool) {
|
||||
for _, t := range discovered {
|
||||
if t.ID == id {
|
||||
return t, true
|
||||
}
|
||||
}
|
||||
return Theme{}, false
|
||||
}
|
||||
Reference in New Issue
Block a user