internal/formatter.go (view raw)
1package bible
2
3import (
4 "fmt"
5 "strings"
6)
7
8// Formatter handles formatting Bible text for display
9type Formatter struct {
10 bible *Bible
11 useColours bool
12 headerFormat string
13}
14
15// NewFormatter creates a new formatter with default settings
16func NewFormatter(bible *Bible) *Formatter {
17 return &Formatter{
18 bible: bible,
19 useColours: true,
20 headerFormat: "{book} {chapter}\nv. {first_verse}-{second_verse}",
21 }
22}
23
24// NewFormatterWithConfig creates a new formatter with configuration
25func NewFormatterWithConfig(bible *Bible, useColours bool, headerFormat string) *Formatter {
26 return &Formatter{
27 bible: bible,
28 useColours: useColours,
29 headerFormat: headerFormat,
30 }
31}
32
33// FormatHeader formats the header for a bookmark using the configured template
34func (f *Formatter) FormatHeader(bookmark *Bookmark) string {
35 template := f.headerFormat
36 if template == "" {
37 // Fall back to default template
38 template = "{book} {chapter}\nv. {first_verse}-{second_verse}"
39 }
40
41 // Get book name from Bible data
42 bookName := f.getBookName(bookmark.Book)
43
44 // Replace template variables
45 result := template
46 result = strings.ReplaceAll(result, "{book}", bookName)
47 result = strings.ReplaceAll(result, "{chapter}", fmt.Sprintf("%d", bookmark.Chapter))
48 result = strings.ReplaceAll(result, "{first_verse}", fmt.Sprintf("%d", bookmark.FirstVerse))
49 result = strings.ReplaceAll(result, "{second_verse}", fmt.Sprintf("%d", bookmark.SecondVerse))
50
51 if !f.useColours {
52 return result
53 }
54
55 // Add ANSI colours - simple implementation
56 // For more advanced templating, we'd need a proper template engine
57 const (
58 reset = "\033[0m"
59 green = "\033[32m"
60 yellow = "\033[33m"
61 )
62
63 // Simple colouring - book and chapter in green, verse range in yellow
64 lines := strings.Split(result, "\n")
65 if len(lines) >= 1 {
66 lines[0] = green + lines[0] + reset
67 }
68 if len(lines) >= 2 {
69 lines[1] = yellow + lines[1] + reset
70 }
71 result = strings.Join(lines, "\n")
72
73 return result
74}
75
76// getBookName retrieves the book name from Bible data
77func (f *Formatter) getBookName(book BookIndex) string {
78 // Try to find any verse from this book to get the book name
79 // Start from chapter 1, verse 1 and work forward
80 for chapter := 1; chapter <= 150; chapter++ {
81 for verse := 1; verse <= 200; verse++ {
82 if v, ok := f.bible.GetVerse(int(book), chapter, verse); ok {
83 return v.BookName
84 }
85 }
86 }
87 // If we can't find the book in the data, return a generic name
88 return fmt.Sprintf("Book %d", book)
89} // FormatHeaderWithTemplate is deprecated - use FormatHeader instead
90func (f *Formatter) FormatHeaderWithTemplate(bookmark *Bookmark) string {
91 return f.FormatHeader(bookmark)
92}
93
94// FormatSnippet formats the verse range text
95func (f *Formatter) FormatSnippet(verses []*Verse) string {
96 if len(verses) == 0 {
97 return "Error: No text matched"
98 }
99
100 var sb strings.Builder
101 for i, verse := range verses {
102 if i > 0 {
103 sb.WriteString(" ")
104 }
105 // Remove any leading pilcrow (¶) and trim
106 text := strings.TrimSpace(verse.Text)
107 if strings.HasPrefix(text, "¶ ") {
108 text = text[2:]
109 } else if strings.HasPrefix(text, "¶") {
110 text = text[1:]
111 }
112 sb.WriteString(text)
113 }
114
115 return sb.String()
116}
117
118// ExtractAndFormat extracts verses for a bookmark and formats them
119func (f *Formatter) ExtractAndFormat(bible *Bible, bookmark *Bookmark) string {
120 verses := bible.GetVerseRange(int(bookmark.Book), bookmark.Chapter,
121 bookmark.FirstVerse, bookmark.SecondVerse)
122 return f.FormatSnippet(verses)
123}
124
125// ExtractAndFormatWithHeader extracts verses and formats with header
126func (f *Formatter) ExtractAndFormatWithHeader(bible *Bible, bookmark *Bookmark) string {
127 header := f.FormatHeader(bookmark)
128 content := f.ExtractAndFormat(bible, bookmark)
129 return header + "\n" + content
130}
131