// Package preset definiert das Preset-Schema und das Laden/Speichern // von Built-in- und User-Presets. package preset import ( "encoding/json" "errors" "fmt" "os" "path/filepath" "regexp" "sort" "motd-assist/internal/assets" ) // Entry beschreibt den Soll-Zustand eines Scripts innerhalb eines Presets. // Die Reihenfolge im Scripts-Array bestimmt die Ziel-Reihenfolge im MOTD. type Entry struct { Name string `json:"name"` // Zieldateiname, z.B. "10-brand-header" Enabled bool `json:"enabled"` // Executable-Bit gesetzt Install string `json:"install,omitempty"` // gebündelte Vorlage, leer = vorhandenes Script } // Preset ist eine benannte MOTD-Konfiguration. type Preset struct { Name string `json:"name"` Description string `json:"description,omitempty"` Scripts []Entry `json:"scripts"` Source string `json:"-"` // "builtin" oder "user" } var safeName = regexp.MustCompile(`[^a-zA-Z0-9._-]+`) // Dir liefert das User-Preset-Verzeichnis (XDG-konform). func Dir() (string, error) { if d := os.Getenv("XDG_CONFIG_HOME"); d != "" { return filepath.Join(d, "motd-assist", "presets"), nil } home, err := os.UserHomeDir() if err != nil { return "", err } return filepath.Join(home, ".config", "motd-assist", "presets"), nil } // LoadAll liefert alle Presets: Built-ins, durch User-Presets mit // gleichem Namen überschrieben. Sortiert nach Name. func LoadAll() ([]Preset, error) { byName := make(map[string]Preset) for file, content := range assets.PresetFiles() { var p Preset if err := json.Unmarshal([]byte(content), &p); err != nil { return nil, fmt.Errorf("builtin preset %s: %w", file, err) } p.Source = "builtin" byName[p.Name] = p } dir, err := Dir() if err != nil { return nil, err } entries, err := os.ReadDir(dir) if err == nil { for _, e := range entries { if e.IsDir() || filepath.Ext(e.Name()) != ".json" { continue } b, err := os.ReadFile(filepath.Join(dir, e.Name())) if err != nil { continue } var p Preset if err := json.Unmarshal(b, &p); err != nil { continue } p.Source = "user" byName[p.Name] = p } } else if !errors.Is(err, os.ErrNotExist) { return nil, err } presets := make([]Preset, 0, len(byName)) for _, p := range byName { presets = append(presets, p) } sort.Slice(presets, func(i, j int) bool { return presets[i].Name < presets[j].Name }) return presets, nil } // SaveUser schreibt ein Preset ins User-Verzeichnis (überschreibt Built-ins // mit gleichem Namen zur Laufzeit). func SaveUser(p Preset) (string, error) { if p.Name == "" { return "", errors.New("Preset-Name fehlt") } dir, err := Dir() if err != nil { return "", err } if err := os.MkdirAll(dir, 0o755); err != nil { return "", err } b, err := json.MarshalIndent(p, "", " ") if err != nil { return "", err } path := filepath.Join(dir, safeName.ReplaceAllString(p.Name, "-")+".json") return path, os.WriteFile(path, append(b, '\n'), 0o644) } // DeleteUser entfernt ein User-Preset. Built-ins können nicht gelöscht werden. func DeleteUser(name string) error { dir, err := Dir() if err != nil { return err } path := filepath.Join(dir, safeName.ReplaceAllString(name, "-")+".json") if err := os.Remove(path); err != nil { return fmt.Errorf("nur User-Presets sind löschbar: %w", err) } return nil }