clone

fifteen puzzle games for the terminal, with daily puzzles, weekly ladders and xp, built on bubble tea

chore/app-model-decomp
puzzletea / config / config.go
config.go 3.4K
1// Package config provides persistent application configuration for PuzzleTea.2// Settings are stored as JSON in ~/.puzzletea/config.json alongside the game3// history database.4package config56import (7 "encoding/json"8 "errors"9 "fmt"10 "os"11 "path/filepath"1213 "github.com/FelineStateMachine/puzzletea/puzzle"14)1516// Config holds all user-configurable settings.17type Config struct {18 Theme string `json:"theme,omitempty"` // theme name; empty = default19 DBPath string `json:"db_path,omitempty"` // database path; empty = ~/.puzzletea/history.db20 Export ExportConfig `json:"export,omitempty"` // last-used export settings21 Create CreateConfig `json:"create,omitempty"` // last-used create settings22}2324// CreateConfig stores the app's last-used Create form values.25type CreateConfig struct {26 SelectedLeafIDs []string `json:"selected_leaf_ids,omitempty"`27 Elo int `json:"elo,omitempty"`28}2930// ExportConfig stores the app's last-used export form values.31type ExportConfig struct {32 Title string `json:"title,omitempty"`33 Header string `json:"header,omitempty"`34 Advert string `json:"advert,omitempty"`35 Volume int `json:"volume,omitempty"`36 SheetLayout string `json:"sheet_layout,omitempty"`37 Seed string `json:"seed,omitempty"`38 PDFOutputPath string `json:"pdf_output_path,omitempty"`39 JSONLEnabled bool `json:"jsonl_enabled,omitempty"`40 JSONLOutputPath string `json:"jsonl_output_path,omitempty"`41 Counts map[puzzle.GameID]map[puzzle.ModeID]int `json:"counts,omitempty"`42}4344// Default returns a Config with all settings at their zero values (the45// built-in defaults).46func Default() *Config {47 return &Config{48 DBPath: DefaultDBPath(),49 }50}5152// DefaultDBPath returns the default database path ~/.puzzletea/history.db.53func DefaultDBPath() string {54 home, _ := os.UserHomeDir()55 return filepath.Join(home, ".puzzletea", "history.db")56}5758// DefaultPath returns ~/.puzzletea/config.json.59func DefaultPath() string {60 home, _ := os.UserHomeDir()61 return filepath.Join(home, ".puzzletea", "config.json")62}6364// Load reads and parses a config file. If the file does not exist, a default65// Config is returned with no error. Parse errors are returned so callers can66// log a warning, but the application should still start with defaults.67func Load(path string) (*Config, error) {68 data, err := os.ReadFile(path)69 if err != nil {70 if errors.Is(err, os.ErrNotExist) {71 return Default(), nil72 }73 return Default(), fmt.Errorf("reading config: %w", err)74 }7576 cfg := Default()77 if err := json.Unmarshal(data, cfg); err != nil {78 return Default(), fmt.Errorf("parsing config: %w", err)79 }80 return cfg, nil81}8283// Save writes the config to disk, creating the parent directory if needed.84func (c *Config) Save(path string) error {85 dir := filepath.Dir(path)86 if err := os.MkdirAll(dir, 0o755); err != nil {87 return fmt.Errorf("creating config directory: %w", err)88 }8990 data, err := json.MarshalIndent(c, "", " ")91 if err != nil {92 return fmt.Errorf("marshaling config: %w", err)93 }94 data = append(data, '\n')9596 if err := os.WriteFile(path, data, 0o644); err != nil {97 return fmt.Errorf("writing config: %w", err)98 }99 return nil100}