clone

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

chore/regenerate-vhs-gifs
puzzletea / config / config.go
config.go 3.2K
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}2223// ExportConfig stores the app's last-used export form values.24type ExportConfig struct {25 Title string `json:"title,omitempty"`26 Header string `json:"header,omitempty"`27 Advert string `json:"advert,omitempty"`28 Volume int `json:"volume,omitempty"`29 SheetLayout string `json:"sheet_layout,omitempty"`30 Seed string `json:"seed,omitempty"`31 PDFOutputPath string `json:"pdf_output_path,omitempty"`32 JSONLEnabled bool `json:"jsonl_enabled,omitempty"`33 JSONLOutputPath string `json:"jsonl_output_path,omitempty"`34 Counts map[puzzle.GameID]map[puzzle.ModeID]int `json:"counts,omitempty"`35}3637// Default returns a Config with all settings at their zero values (the38// built-in defaults).39func Default() *Config {40 return &Config{41 DBPath: DefaultDBPath(),42 }43}4445// DefaultDBPath returns the default database path ~/.puzzletea/history.db.46func DefaultDBPath() string {47 home, _ := os.UserHomeDir()48 return filepath.Join(home, ".puzzletea", "history.db")49}5051// DefaultPath returns ~/.puzzletea/config.json.52func DefaultPath() string {53 home, _ := os.UserHomeDir()54 return filepath.Join(home, ".puzzletea", "config.json")55}5657// Load reads and parses a config file. If the file does not exist, a default58// Config is returned with no error. Parse errors are returned so callers can59// log a warning, but the application should still start with defaults.60func Load(path string) (*Config, error) {61 data, err := os.ReadFile(path)62 if err != nil {63 if errors.Is(err, os.ErrNotExist) {64 return Default(), nil65 }66 return Default(), fmt.Errorf("reading config: %w", err)67 }6869 cfg := Default()70 if err := json.Unmarshal(data, cfg); err != nil {71 return Default(), fmt.Errorf("parsing config: %w", err)72 }73 return cfg, nil74}7576// Save writes the config to disk, creating the parent directory if needed.77func (c *Config) Save(path string) error {78 dir := filepath.Dir(path)79 if err := os.MkdirAll(dir, 0o755); err != nil {80 return fmt.Errorf("creating config directory: %w", err)81 }8283 data, err := json.MarshalIndent(c, "", " ")84 if err != nil {85 return fmt.Errorf("marshaling config: %w", err)86 }87 data = append(data, '\n')8889 if err := os.WriteFile(path, data, 0o644); err != nil {90 return fmt.Errorf("writing config: %w", err)91 }92 return nil93}