// Package assets hält die eingebetteten Presets und Custom-Scripte sowie // den Drop-in-Ordner für eigene Vorlagen (~/.config/motd-assist/templates). package assets import ( "embed" "os" "path/filepath" "sort" "strings" ) //go:embed presets scripts var embedded embed.FS // Template ist eine installierbare Script-Vorlage. type Template struct { Name string Source string // "builtin" oder "user" } // TemplatesDir liefert den Drop-in-Ordner für eigene Vorlagen (XDG-konform). func TemplatesDir() (string, error) { if d := os.Getenv("XDG_CONFIG_HOME"); d != "" { return filepath.Join(d, "motd-assist", "templates"), nil } home, err := os.UserHomeDir() if err != nil { return "", err } return filepath.Join(home, ".config", "motd-assist", "templates"), nil } // EnsureTemplatesDir legt den Drop-in-Ordner an, falls er fehlt. func EnsureTemplatesDir() (string, error) { dir, err := TemplatesDir() if err != nil { return "", err } if err := os.MkdirAll(dir, 0o755); err != nil { return "", err } return dir, nil } // userTemplates liest den Drop-in-Ordner frisch bei jedem Aufruf, damit // neue Dateien ohne Neustart erkannt werden (TUI: Taste r). func userTemplates() map[string]string { dir, err := TemplatesDir() if err != nil { return nil } entries, err := os.ReadDir(dir) if err != nil { return nil } out := make(map[string]string, len(entries)) for _, e := range entries { if e.IsDir() { continue } if b, err := os.ReadFile(filepath.Join(dir, e.Name())); err == nil { out[e.Name()] = string(b) } } return out } func embeddedScriptNames() []string { entries, err := embedded.ReadDir("scripts") if err != nil { return nil } names := make([]string, 0, len(entries)) for _, e := range entries { if !e.IsDir() { names = append(names, e.Name()) } } return names } func embeddedContent(name string) (string, bool) { b, err := embedded.ReadFile("scripts/" + name) if err != nil { return "", false } return string(b), true } // Templates listet alle bekannten Vorlagen sortiert. Gleichnamige // User-Vorlagen überdecken Built-ins. func Templates() []Template { user := userTemplates() seen := make(map[string]string, len(user)+4) for name := range user { seen[name] = "user" } for _, name := range embeddedScriptNames() { if _, ok := seen[name]; !ok { seen[name] = "builtin" } } out := make([]Template, 0, len(seen)) for name, source := range seen { out = append(out, Template{Name: name, Source: source}) } sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name }) return out } // ScriptNames listet alle Vorlagen-Namen (Union aus Built-in und Drop-in). func ScriptNames() []string { names := make([]string, 0) for _, t := range Templates() { names = append(names, t.Name) } return names } // ScriptContent liefert den Inhalt einer Vorlage; User-Vorlagen aus dem // Drop-in-Ordner haben Vorrang vor Built-ins. func ScriptContent(name string) (string, bool) { if c, ok := userTemplates()[name]; ok { return c, true } return embeddedContent(name) } // MatchScript findet die Vorlage, deren Inhalt zu content passt, und // erkennt damit bereits installierte motd-assist-Scripte. func MatchScript(content string) (string, bool) { return MatchScriptBytes([]byte(content)) } // MatchScriptBytes ist die Byte-Variante von MatchScript. func MatchScriptBytes(b []byte) (string, bool) { content := strings.TrimSpace(string(b)) if content == "" { return "", false } for name, c := range userTemplates() { if strings.TrimSpace(c) == content { return name, true } } for _, name := range embeddedScriptNames() { if c, ok := embeddedContent(name); ok && strings.TrimSpace(c) == content { return name, true } } return "", false } // PresetFiles liefert Dateiname → Inhalt aller eingebetteten Presets. func PresetFiles() map[string]string { entries, err := embedded.ReadDir("presets") if err != nil { return nil } files := make(map[string]string, len(entries)) for _, e := range entries { if e.IsDir() { continue } if b, err := embedded.ReadFile("presets/" + e.Name()); err == nil { files[e.Name()] = string(b) } } return files }