all repos — bibel @ 01c758ad65f39049a6b57f85c0e09c74b5f1f1ea

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