all repos — bibel @ e61fc65a61631125f782ff5d302389fe7c4a9228

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

feat: add verbose logging for config and bible loading

- Add `verbose` flag to `LoadConfig` and `LoadBible` functions
  - Implement `VerboseLogger` with conditional output (Info, Warning,
    Section, Path, Success)
  - Replace direct `fmt.Fprintf` calls in `internal/config.go` with
    structured logger
  - Add detailed logging for Bible metadata (name, language, verse
    count) in `internal/loader.go`
- Optimise `GetChapterText` by using `strings.Builder` instead of string
  concatenation
- Update `cmd/bibel.go` to pass `args.Verbose` when loading config and
  Bible data
- Add missing newlines at end of `easterprogression.go` and `config.go`
  (cosmetic)
- Update `CHANGELOG.md` with `0.11.0`
Maxwell Jensen 85795372+maxwelljens@users.noreply.github.com
Sat, 18 Apr 2026 15:07:19 +0200
commit

e61fc65a61631125f782ff5d302389fe7c4a9228

parent

995003ab506a7dc2fc1a212da5b5d9bd2cdd58ad

M CHANGELOG.mdCHANGELOG.md

@@ -8,6 +8,17 @@ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased] +## [0.11.0] - 2026-04-18 + +### Added + +- **New flag**: `-v/--verbose` flag to print with more information if needed. + +### Fixed + +- Add missing newlines at end of `easterprogression.go` and `config.go` + (cosmetic) + ## [0.10.0] - 2026-04-18 ### Changed
M cmd/bibel.gocmd/bibel.go

@@ -47,7 +47,7 @@ return

} // Load configuration - config, err := bible.LoadConfig() + config, err := bible.LoadConfig(args.Verbose) if err != nil { fmt.Fprintf(os.Stderr, "Error loading config: %v\n", err) os.Exit(1)

@@ -64,7 +64,7 @@ config.ReadingMode = args.Reading

} // Load Bible data - bibleData, err := bible.LoadBible(config.BiblePath) + bibleData, err := bible.LoadBible(config.BiblePath, args.Verbose) if err != nil { fmt.Fprintf(os.Stderr, "Error loading Bible data: %v\n", err) os.Exit(1)

@@ -125,4 +125,4 @@ if _, err := program.Run(); err != nil {

fmt.Fprintf(os.Stderr, "Error running TUI: %v\n", err) os.Exit(1) } -} +}
M internal/config.gointernal/config.go

@@ -106,7 +106,9 @@ return cfg

} // LoadConfig loads configuration from file and environment -func LoadConfig() (*Config, error) { +func LoadConfig(verbose bool) (*Config, error) { + logger := NewVerboseLogger(verbose) + logger.Section("Loading Configuration") // Set up viper viper.SetConfigName("config") // Name of config file (without extension) viper.SetConfigType("toml") // REQUIRED if the config file does not have the extension in the name

@@ -137,8 +139,8 @@ // Read in config file (if it exists)

if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok { // Config file not found; we'll use defaults - fmt.Fprintf(os.Stderr, "Note: No configuration file found at %s/config.toml\n", configPath) - fmt.Fprintf(os.Stderr, "Using default configuration. Run 'bibel --generate-config' to create a default config file.\n") + logger.Info("No configuration file found at %s/config.toml", configPath) + logger.Info("Using default configuration. Run 'bibel --generate-config' to create a default config file.") } else { // Config file was found but another error was produced return nil, fmt.Errorf("error reading config file: %w", err)

@@ -176,16 +178,21 @@ // If BiblePath is still empty (default or from config), try to find a Bible file in XDG data directory

if cfg.BiblePath == "" { biblePath, err := findBibleFileInDataDir() if err != nil { - return nil, fmt.Errorf("failed to find Bible data file: %w", err) + logger.Warning("Failed to find Bible data file: %v", err) + return nil, fmt.Errorf("failed to find Bible data file: %w", err) } cfg.BiblePath = biblePath - fmt.Fprintf(os.Stderr, "Using Bible file from XDG data directory: %s\n", cfg.BiblePath) + logger.Info("Using Bible file from XDG data directory") + logger.Path(cfg.BiblePath) } // Validate configuration + logger.Section("Validating Configuration") if err := cfg.Validate(); err != nil { + logger.Warning("Configuration validation failed: %v", err) return nil, fmt.Errorf("invalid configuration: %w", err) } + logger.Success("Configuration validated successfully") return &cfg, nil }

@@ -282,4 +289,4 @@ return nil

} func GetConfigPath() string { return filepath.Join(xdg.ConfigHome, "bibel", "config.toml") -} +}
M internal/easterprogression.gointernal/easterprogression.go

@@ -157,3 +157,4 @@

return elapsedDuration.Seconds() / totalDuration.Seconds(), nil } } +
M internal/loader.gointernal/loader.go

@@ -3,6 +3,7 @@

import ( "encoding/json" "os" + "strings" ) // Bible represents the complete Bible data

@@ -18,9 +19,14 @@ byBookChapterVerse map[int]map[int]map[int]*Verse

} // LoadBible loads Bible data from a JSON file -func LoadBible(filePath string) (*Bible, error) { +func LoadBible(filePath string, verbose bool) (*Bible, error) { + logger := NewVerboseLogger(verbose) + logger.Info("Loading Bible data from file") + logger.Path(filePath) + data, err := os.ReadFile(filePath) if err != nil { + logger.Warning("Failed to read Bible file: %v", err) return nil, err }

@@ -30,6 +36,13 @@ return nil, err

} bible.buildIndex() + logger.Success("Bible data loaded successfully") + logger.WithFields(map[string]any{ + "metadata.name": bible.Metadata.Name, + "metadata.lang": bible.Metadata.Lang, + "verse_count": len(bible.Verses), + }, "Bible metadata") + return &bible, nil }

@@ -68,18 +81,18 @@ // GetChapterText retrieves the full text of a chapter

func (b *Bible) GetChapterText(book, chapter int) string { if chapterMap, ok := b.byBookChapterVerse[book]; ok { if verseMap, ok := chapterMap[chapter]; ok { - var text string + var text strings.Builder // We need to get verses in order // Since we don't know the max verse number, we'll iterate // This could be optimized but works for now for i := 1; ; i++ { if verse, ok := verseMap[i]; ok { - text += verse.Text + " " + text.WriteString(verse.Text + " ") } else { break } } - return text + return text.String() } } return ""

@@ -111,3 +124,4 @@ }

return verses } +
A internal/verbose.go

@@ -0,0 +1,137 @@

+package bible + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/charmbracelet/lipgloss" +) + +// VerboseLogger handles verbose output with lipgloss styling +type VerboseLogger struct { + enabled bool + styles *VerboseStyles +} + +// VerboseStyles defines styles for verbose output +type VerboseStyles struct { + Info lipgloss.Style + Warning lipgloss.Style + Success lipgloss.Style + Path lipgloss.Style + Label lipgloss.Style + Value lipgloss.Style +} + +// NewVerboseLogger creates a new verbose logger +func NewVerboseLogger(enabled bool) *VerboseLogger { + return &VerboseLogger{ + enabled: enabled, + styles: createVerboseStyles(), + } +} + +// createVerboseStyles creates and returns the verbose output styles +func createVerboseStyles() *VerboseStyles { + return &VerboseStyles{ + Info: lipgloss.NewStyle().Foreground(lipgloss.Color("4")), + Warning: lipgloss.NewStyle().Foreground(lipgloss.Color("3")), + Success: lipgloss.NewStyle().Foreground(lipgloss.Color("2")), + Path: lipgloss.NewStyle().Foreground(lipgloss.Color("12")), + Label: lipgloss.NewStyle().Foreground(lipgloss.Color("8")), + Value: lipgloss.NewStyle().Foreground(lipgloss.Color("13")), + } +} + +// Info prints an informational message +func (vl *VerboseLogger) Info(format string, args ...any) { + if !vl.enabled { + return + } + message := fmt.Sprintf(format, args...) + styled := vl.styles.Info.Render("INFO: ") + message + fmt.Fprintln(os.Stderr, styled) +} + +// Warning prints a warning message +func (vl *VerboseLogger) Warning(format string, args ...any) { + if !vl.enabled { + return + } + message := fmt.Sprintf(format, args...) + styled := vl.styles.Warning.Render("WARN: ") + message + fmt.Fprintln(os.Stderr, styled) +} + +// Success prints a success message +func (vl *VerboseLogger) Success(format string, args ...any) { + if !vl.enabled { + return + } + message := fmt.Sprintf(format, args...) + styled := vl.styles.Success.Render("SUCCESS: ") + message + fmt.Fprintln(os.Stderr, styled) +} + +// Path prints a file path with styling +func (vl *VerboseLogger) Path(path string) { + if !vl.enabled { + return + } + // Make path absolute for clarity + absPath, err := filepath.Abs(path) + if err != nil { + absPath = path + } + styled := vl.styles.Label.Render("Path: ") + vl.styles.Path.Render(absPath) + fmt.Fprintln(os.Stderr, styled) +} + +// Config prints configuration information +func (vl *VerboseLogger) Config(format string, args ...any) { + if !vl.enabled { + return + } + message := fmt.Sprintf(format, args...) + styled := vl.styles.Label.Render("Config: ") + message + fmt.Fprintln(os.Stderr, styled) +} + +// Value prints a key-value pair +func (vl *VerboseLogger) Value(key string, value any) { + if !vl.enabled { + return + } + styled := vl.styles.Label.Render(key+": ") + vl.styles.Value.Render(fmt.Sprintf("%v", value)) + fmt.Fprintln(os.Stderr, styled) +} + +// Section prints a section header +func (vl *VerboseLogger) Section(title string) { + if !vl.enabled { + return + } + styled := vl.styles.Info.Render("=== " + title + " ===") + fmt.Fprintln(os.Stderr, styled) +} + +// WithFields creates a formatted message with key-value pairs +func (vl *VerboseLogger) WithFields(fields map[string]any, message string) { + if !vl.enabled { + return + } + + var parts []string + for key, value := range fields { + parts = append(parts, fmt.Sprintf("%s=%v", key, value)) + } + + fieldsStr := strings.Join(parts, " ") + styled := vl.styles.Label.Render("Fields: ") + message + if fieldsStr != "" { + styled += " " + vl.styles.Info.Render("["+fieldsStr+"]") + } + fmt.Fprintln(os.Stderr, styled) +}