// Package theming applies a selected colorscheme across starship, zellij, // yazi, lazygit, and NvChad. Themes are discovered from embedded manifest // files under assets//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//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 }