all repos — bibel @ 9410cd8d1fb198ef5a049c595cb51e4f5587e5e7

Unnamed repository; edit this file 'description' to name the repository.

internal/config.go (view raw)

  1package bible
  2
  3import (
  4	"fmt"
  5	"os"
  6	"path/filepath"
  7
  8	"github.com/adrg/xdg"
  9	"github.com/spf13/viper"
 10)
 11
 12// Config represents the application configuration
 13type Config struct {
 14	// Reading mode: "evangelion", "new_testament", "old_testament", "bible"
 15	ReadingMode string `toml:"reading_mode" mapstructure:"reading_mode"`
 16	// Output mode: "tui", "formatted", "plain"
 17	OutputMode string `toml:"output_mode" mapstructure:"output_mode"`
 18
 19	// Bible data file path (default: "books/pol_nbg.json")
 20	BiblePath string `toml:"bible_path" mapstructure:"bible_path"`
 21
 22	// TUI settings
 23	TUI struct {
 24		// Whether to show quit message (default: true)
 25		ShowQuitMessage bool `toml:"show_quit_message" mapstructure:"show_quit_message"`
 26
 27		// Box border style: "rounded", "double", "single", "hidden"
 28		BorderStyle string `toml:"border_style" mapstructure:"border_style"`
 29
 30		// Box border colour (default: adaptive to terminal)
 31		BorderColour string `toml:"border_colour" mapstructure:"border_colour"`
 32
 33		// Header colour (default: adaptive to terminal)
 34		HeaderColour string `toml:"header_colour" mapstructure:"header_colour"`
 35
 36		// Text colour (default: adaptive to terminal)
 37		TextColour string `toml:"text_colour" mapstructure:"text_colour"`
 38
 39		// Quit message colour (default: adaptive to terminal)
 40		QuitColour string `toml:"quit_colour" mapstructure:"quit_colour"`
 41	} `toml:"tui" mapstructure:"tui"`
 42
 43	// Formatter settings
 44	Formatter struct {
 45		// Whether to use ANSI colours in formatted mode (default: true)
 46		UseColours bool `toml:"use_colours" mapstructure:"use_colours"`
 47
 48		// Header format template (default: "{book} {chapter}\nw. {first_verse}-{second_verse}")
 49		HeaderFormat string `toml:"header_format" mapstructure:"header_format"`
 50	} `toml:"formatter" mapstructure:"formatter"`
 51
 52	// Date progression settings
 53	DateProgression struct {
 54		// Verses per day (default: 12)
 55		VersesPerDay int `toml:"verses_per_day" mapstructure:"verses_per_day"`
 56
 57		// Start date for progression (default: "1 January" of current year)
 58		StartDate string `toml:"start_date" mapstructure:"start_date"`
 59	} `toml:"date_progression" mapstructure:"date_progression"`
 60}
 61
 62// DefaultConfig returns a configuration with default values
 63func DefaultConfig() *Config {
 64	cfg := &Config{
 65		ReadingMode: "evangelion",
 66		OutputMode: "tui",
 67		BiblePath:  "books/pol_nbg.json",
 68	}
 69
 70	cfg.TUI.ShowQuitMessage = true
 71	cfg.TUI.BorderStyle = "rounded"
 72	cfg.TUI.BorderColour = "" // Empty means adaptive
 73	cfg.TUI.HeaderColour = "" // Empty means adaptive
 74	cfg.TUI.TextColour = ""   // Empty means adaptive
 75	cfg.TUI.QuitColour = ""   // Empty means adaptive
 76
 77	cfg.Formatter.UseColours = true
 78	cfg.Formatter.HeaderFormat = "{book} {chapter}\nw. {first_verse}-{second_verse}"
 79
 80	cfg.DateProgression.VersesPerDay = 12
 81	cfg.DateProgression.StartDate = "" // Empty means 1 January of current year
 82
 83	return cfg
 84}
 85
 86// LoadConfig loads configuration from file and environment
 87func LoadConfig() (*Config, error) {
 88	// Set up viper
 89	viper.SetConfigName("config") // Name of config file (without extension)
 90	viper.SetConfigType("toml")   // REQUIRED if the config file does not have the extension in the name
 91
 92	// Add XDG config paths
 93	configPath := filepath.Join(xdg.ConfigHome, "bibel")
 94	viper.AddConfigPath(configPath)
 95
 96	// Also check current directory for local config
 97	viper.AddConfigPath(".")
 98
 99	// Set defaults
100	defaultCfg := DefaultConfig()
101	viper.SetDefault("reading_mode", defaultCfg.ReadingMode)
102	viper.SetDefault("output_mode", defaultCfg.OutputMode)
103	viper.SetDefault("bible_path", defaultCfg.BiblePath)
104	viper.SetDefault("tui.show_quit_message", defaultCfg.TUI.ShowQuitMessage)
105	viper.SetDefault("tui.border_style", defaultCfg.TUI.BorderStyle)
106	viper.SetDefault("tui.border_colour", defaultCfg.TUI.BorderColour)
107	viper.SetDefault("tui.header_colour", defaultCfg.TUI.HeaderColour)
108	viper.SetDefault("tui.text_colour", defaultCfg.TUI.TextColour)
109	viper.SetDefault("tui.quit_colour", defaultCfg.TUI.QuitColour)
110	viper.SetDefault("formatter.use_colours", defaultCfg.Formatter.UseColours)
111	viper.SetDefault("formatter.header_format", defaultCfg.Formatter.HeaderFormat)
112	viper.SetDefault("date_progression.verses_per_day", defaultCfg.DateProgression.VersesPerDay)
113	viper.SetDefault("date_progression.start_date", defaultCfg.DateProgression.StartDate)
114
115	// Read in config file (if it exists)
116	if err := viper.ReadInConfig(); err != nil {
117		if _, ok := err.(viper.ConfigFileNotFoundError); ok {
118			// Config file not found; we'll use defaults
119			fmt.Fprintf(os.Stderr, "Note: No configuration file found at %s/config.toml\n", configPath)
120			fmt.Fprintf(os.Stderr, "Using default configuration. Run 'bibel --generate-config' to create a default config file.\n")
121		} else {
122			// Config file was found but another error was produced
123			return nil, fmt.Errorf("error reading config file: %w", err)
124		}
125	}
126
127	// Unmarshal config into struct
128	var cfg Config
129	if err := viper.Unmarshal(&cfg); err != nil {
130		return nil, fmt.Errorf("error unmarshalling config: %w", err)
131	}
132
133	// Apply defaults for any empty fields
134	defaultCfg = DefaultConfig()
135	if cfg.ReadingMode == "" {
136		cfg.ReadingMode = defaultCfg.ReadingMode
137	}
138	if cfg.OutputMode == "" {
139		cfg.OutputMode = defaultCfg.OutputMode
140	}
141	if cfg.BiblePath == "" {
142		cfg.BiblePath = defaultCfg.BiblePath
143	}
144	if cfg.TUI.BorderStyle == "" {
145		cfg.TUI.BorderStyle = defaultCfg.TUI.BorderStyle
146	}
147	if cfg.DateProgression.VersesPerDay == 0 {
148		cfg.DateProgression.VersesPerDay = defaultCfg.DateProgression.VersesPerDay
149	}
150
151	// Validate configuration
152	if err := cfg.Validate(); err != nil {
153		return nil, fmt.Errorf("invalid configuration: %w", err)
154	}
155
156	return &cfg, nil
157}
158
159// SaveConfig saves the configuration to file
160func SaveConfig(cfg *Config) error {
161	// Set up viper with current config
162	viper.Set("reading_mode", cfg.ReadingMode)
163	viper.Set("output_mode", cfg.OutputMode)
164	viper.Set("bible_path", cfg.BiblePath)
165	viper.Set("tui.show_quit_message", cfg.TUI.ShowQuitMessage)
166	viper.Set("tui.border_style", cfg.TUI.BorderStyle)
167	viper.Set("tui.border_colour", cfg.TUI.BorderColour)
168	viper.Set("tui.header_colour", cfg.TUI.HeaderColour)
169	viper.Set("tui.text_colour", cfg.TUI.TextColour)
170	viper.Set("tui.quit_colour", cfg.TUI.QuitColour)
171	viper.Set("formatter.use_colours", cfg.Formatter.UseColours)
172	viper.Set("formatter.header_format", cfg.Formatter.HeaderFormat)
173	viper.Set("date_progression.verses_per_day", cfg.DateProgression.VersesPerDay)
174	viper.Set("date_progression.start_date", cfg.DateProgression.StartDate)
175
176	// Ensure config directory exists
177	configPath := filepath.Join(xdg.ConfigHome, "bibel")
178	if err := os.MkdirAll(configPath, 0755); err != nil {
179		return fmt.Errorf("error creating config directory: %w", err)
180	}
181
182	// Write config file
183	configFile := filepath.Join(configPath, "config.toml")
184	if err := viper.WriteConfigAs(configFile); err != nil {
185		return fmt.Errorf("error writing config file: %w", err)
186	}
187
188	return nil
189}
190
191// GenerateDefaultConfig creates a default configuration file
192func GenerateDefaultConfig() error {
193	return SaveConfig(DefaultConfig())
194}
195
196// GetConfigPath returns the path to the configuration file
197
198// Validate checks if configuration values are valid
199func (c *Config) Validate() error {
200	// Validate reading mode
201	validReadingModes := map[string]bool{
202		"evangelion": true,
203		"new_testament": true,
204		"old_testament": true,
205		"bible": true,
206	}
207	// Allow empty reading mode (will use default)
208	if c.ReadingMode != "" && !validReadingModes[c.ReadingMode] {
209		return fmt.Errorf("invalid reading mode: %s, must be one of: evangelion, new_testament, old_testament, bible", c.ReadingMode)
210	}
211
212	// Validate output mode
213	validOutputModes := map[string]bool{
214		"tui":       true,
215		"formatted": true,
216		"plain":     true,
217	}
218	if !validOutputModes[c.OutputMode] {
219		return fmt.Errorf("invalid output mode: %s, must be one of: tui, formatted, plain", c.OutputMode)
220	}
221
222	// Validate TUI border style
223	validBorderStyles := map[string]bool{
224		"rounded": true,
225		"double":  true,
226		"single":  true,
227		"hidden":  true,
228	}
229	if !validBorderStyles[c.TUI.BorderStyle] {
230		return fmt.Errorf("invalid border style: %s, must be one of: rounded, double, single, hidden", c.TUI.BorderStyle)
231	}
232
233	// Validate verses per day
234	if c.DateProgression.VersesPerDay <= 0 {
235		return fmt.Errorf("verses per day must be positive, got: %d", c.DateProgression.VersesPerDay)
236	}
237
238	return nil
239}
240func GetConfigPath() string {
241	return filepath.Join(xdg.ConfigHome, "bibel", "config.toml")
242}