all repos — bibel @ master

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

internal/formatter.go (view raw)

  1package bible
  2
  3import (
  4	"fmt"
  5	"strconv"
  6	"strings"
  7
  8	"github.com/charmbracelet/lipgloss"
  9)
 10
 11// Formatter handles formatting Bible text for display
 12type Formatter struct {
 13	bible        *Bible
 14	useColours   bool
 15	headerFormat string
 16	numbered     bool
 17	paragraphs   bool
 18}
 19
 20// NewFormatter creates a new formatter with default settings
 21func NewFormatter(bible *Bible) *Formatter {
 22	return &Formatter{
 23		bible:        bible,
 24		useColours:   true,
 25		headerFormat: "{book} {chapter}\nv. {first_verse}-{second_verse}",
 26		numbered:     false,
 27		paragraphs:   false,
 28	}
 29}
 30
 31// NewFormatterWithFullConfig creates a new formatter with full configuration
 32func NewFormatterWithFullConfig(bible *Bible, useColours bool, headerFormat string, numbered bool, paragraphs bool) *Formatter {
 33	return &Formatter{
 34		bible:        bible,
 35		useColours:   useColours,
 36		headerFormat: headerFormat,
 37		numbered:     numbered,
 38		paragraphs:   paragraphs,
 39	}
 40}
 41
 42// NewFormatterWithConfig creates a new formatter with configuration
 43func NewFormatterWithConfig(bible *Bible, useColours bool, headerFormat string) *Formatter {
 44	return NewFormatterWithFullConfig(bible, useColours, headerFormat, false, false)
 45}
 46
 47// FormatHeader formats the header for a bookmark using the configured template
 48func (f *Formatter) FormatHeader(bookmark *Bookmark) string {
 49	template := f.headerFormat
 50	if template == "" {
 51		// Fall back to default template
 52		template = "{book} {chapter}\nv. {first_verse}-{second_verse}"
 53	}
 54
 55	// Get book name from Bible data
 56	bookName := f.getBookName(bookmark.Book)
 57
 58	// Replace template variables
 59	result := template
 60	result = strings.ReplaceAll(result, "{book}", bookName)
 61	result = strings.ReplaceAll(result, "{chapter}", strconv.Itoa(bookmark.Chapter))
 62	result = strings.ReplaceAll(result, "{first_verse}", strconv.Itoa(bookmark.FirstVerse))
 63	result = strings.ReplaceAll(result, "{second_verse}", strconv.Itoa(bookmark.SecondVerse))
 64
 65	if !f.useColours {
 66		return result
 67	}
 68
 69	// Add ANSI colours - simple implementation
 70	// For more advanced templating, we'd need a proper template engine
 71	const (
 72		reset  = "\033[0m"
 73		green  = "\033[32m"
 74		yellow = "\033[33m"
 75	)
 76
 77	// Simple colouring - book and chapter in green, verse range in yellow
 78	lines := strings.Split(result, "\n")
 79	if len(lines) >= 1 {
 80		lines[0] = green + lines[0] + reset
 81	}
 82	if len(lines) >= 2 {
 83		lines[1] = yellow + lines[1] + reset
 84	}
 85	result = strings.Join(lines, "\n")
 86
 87	return result
 88}
 89
 90// getBookName retrieves the book name from Bible data
 91func (f *Formatter) getBookName(book BookIndex) string {
 92	// Try to find any verse from this book to get the book name
 93	// Start from chapter 1, verse 1 and work forward
 94	for chapter := 1; chapter <= 150; chapter++ {
 95		for verse := 1; verse <= 200; verse++ {
 96			if v, ok := f.bible.GetVerse(int(book), chapter, verse); ok {
 97				return v.BookName
 98			}
 99		}
100	}
101	// If we can't find the book in the data, return a generic name
102	return fmt.Sprintf("Book %d", book)
103} // FormatHeaderWithTemplate is deprecated - use FormatHeader instead
104func (f *Formatter) FormatHeaderWithTemplate(bookmark *Bookmark) string {
105	return f.FormatHeader(bookmark)
106}
107
108// FormatSnippet formats the verse range text
109func (f *Formatter) FormatSnippet(verses []*Verse) string {
110	// Use the new options-aware method
111	return f.FormatSnippetWithOptions(verses)
112}
113
114// FormatSnippetWithOptions formats the verse range text with configuration options
115func (f *Formatter) FormatSnippetWithOptions(verses []*Verse) string {
116	if len(verses) == 0 {
117		return "Error: No text matched"
118	}
119
120	var sb strings.Builder
121	for i, verse := range verses {
122		// Check for pilcrow to handle paragraph breaks
123		text := strings.TrimSpace(verse.Text)
124		hasPilcrow := strings.HasPrefix(text, "¶ ") || strings.HasPrefix(text, "¶")
125
126		// Remove pilcrow markers if present
127		if strings.HasPrefix(text, "¶ ") {
128			text = text[2:]
129		} else if strings.HasPrefix(text, "¶") {
130			text = text[1:]
131		}
132
133		// Handle paragraph breaks (blank line before verse with pilcrow)
134		if f.paragraphs && hasPilcrow && i > 0 {
135			// Add blank line before this verse (paragraph break)
136			// If numbered mode, we'll add a newline anyway, so just add one more
137			if f.numbered {
138				sb.WriteString("\n\n")
139			} else {
140				// In non-numbered mode, need space then blank line
141				if i > 0 {
142					sb.WriteString(" \n\n")
143				} else {
144					sb.WriteString("\n\n")
145				}
146			}
147		}
148
149		// Handle numbered output
150		if f.numbered {
151			if i > 0 && (!f.paragraphs || !hasPilcrow) {
152				// Don't add newline if we already added one for paragraph
153				sb.WriteString("\n")
154			}
155			fmt.Fprintf(&sb, "%d. %s", verse.VerseNum, text)
156		} else {
157			// Regular mode - join with spaces
158			// Don't add space if we just added paragraph break
159			if i > 0 && (!f.paragraphs || !hasPilcrow) {
160				sb.WriteString(" ")
161			}
162			sb.WriteString(text)
163		}
164	}
165
166	return sb.String()
167}
168
169// FormatSnippetForTUI formats verse text for TUI with lipgloss styling
170func (f *Formatter) FormatSnippetForTUI(verses []*Verse, style lipgloss.Style, numberStyle lipgloss.Style, contentWidth int) string {
171	if len(verses) == 0 {
172		return "Error: No text matched"
173	}
174
175	var resultLines []string
176
177	for i, verse := range verses {
178		// Process verse text
179		text := strings.TrimSpace(verse.Text)
180		hasPilcrow := strings.HasPrefix(text, "¶ ") || strings.HasPrefix(text, "¶")
181
182		// Remove pilcrow markers if present
183		if strings.HasPrefix(text, "¶ ") {
184			text = text[2:]
185		} else if strings.HasPrefix(text, "¶") {
186			text = text[1:]
187		}
188
189		// Handle paragraph breaks (blank line before verse with pilcrow)
190		if f.paragraphs && hasPilcrow && i > 0 {
191			// Add blank line before this verse (paragraph break)
192			resultLines = append(resultLines, "")
193		}
194
195		if f.numbered {
196			// For numbered mode: "1. text"
197			// Estimate number takes about 4 characters (for verse numbers up to 3 digits + ". ")
198			const numberWidthEstimate = 4
199			remainingWidth := max(1, contentWidth-numberWidthEstimate)
200
201			numberPart := numberStyle.Render(fmt.Sprintf("%d. ", verse.VerseNum))
202			textPart := style.Width(remainingWidth).Render(text)
203			resultLines = append(resultLines, numberPart+textPart)
204		} else {
205			// For non-numbered mode, we'll handle text accumulation separately
206			// Store plain text for now, will join and render later
207			if len(resultLines) == 0 || resultLines[len(resultLines)-1] == "" {
208				// Start new line
209				resultLines = append(resultLines, text)
210			} else {
211				// Continue current line with space
212				resultLines[len(resultLines)-1] = resultLines[len(resultLines)-1] + " " + text
213			}
214		}
215	}
216
217	// Apply styling to non-numbered lines
218	if !f.numbered {
219		for i, line := range resultLines {
220			if line != "" { // Keep empty lines (paragraph breaks) as is
221				resultLines[i] = style.Width(contentWidth).Render(line)
222			}
223		}
224	}
225
226	return strings.Join(resultLines, "\n")
227}
228
229// ExtractAndFormat extracts verses for a bookmark and formats them
230func (f *Formatter) ExtractAndFormat(bible *Bible, bookmark *Bookmark) string {
231	verses := bible.GetVerseRange(int(bookmark.Book), bookmark.Chapter,
232		bookmark.FirstVerse, bookmark.SecondVerse)
233	return f.FormatSnippet(verses)
234}
235
236// ExtractAndFormatWithHeader extracts verses and formats with header
237func (f *Formatter) ExtractAndFormatWithHeader(bible *Bible, bookmark *Bookmark) string {
238	header := f.FormatHeader(bookmark)
239	content := f.ExtractAndFormat(bible, bookmark)
240	return header + "\n" + content
241}