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}