Render embedded plain footnotes
This commit is contained in:
+34
-13
@@ -98,6 +98,21 @@ proc fetchPassages(reference, translation: string, cfg: CombinedConfig): seq[str
|
||||
"unsupported translation '" & translation &
|
||||
"'; supported translations: " & supportedTranslationsList())
|
||||
|
||||
proc fetchPlainPassages(
|
||||
reference,
|
||||
translation: string,
|
||||
keepVerseNumbers: bool,
|
||||
cfg: CombinedConfig): seq[string] =
|
||||
|
||||
case translation
|
||||
of "akjv", "kjv":
|
||||
result = kjv.fetchPlainPassages(reference, keepVerseNumbers)
|
||||
of "mev":
|
||||
result = mev.fetchPlainPassages(reference, keepVerseNumbers)
|
||||
else:
|
||||
for passage in fetchPassages(reference, translation, cfg):
|
||||
result.add(formatPlain(passage, translation, keepVerseNumbers))
|
||||
|
||||
when isMainModule:
|
||||
const USAGE = """Usage:
|
||||
bibleref <reference> [options]
|
||||
@@ -167,19 +182,25 @@ Options:
|
||||
|
||||
var formattedPassages: seq[string] = @[]
|
||||
for query in queries:
|
||||
for passage in fetchPassages(query.referenceText, query.translation, cfg):
|
||||
formattedPassages.add(
|
||||
case $args["--output-format"]:
|
||||
of "plain":
|
||||
formatPlain(passage, query.translation)
|
||||
of "reading":
|
||||
formatPlain(passage, query.translation, keepVerseNumbers = false)
|
||||
of "text":
|
||||
passage.multiReplace([(re"\[(\d+)\]", "$1")])
|
||||
of "raw":
|
||||
passage
|
||||
else:
|
||||
formatMarkdown(passage, query.translation))
|
||||
case $args["--output-format"]
|
||||
of "plain":
|
||||
formattedPassages.add(fetchPlainPassages(
|
||||
query.referenceText,
|
||||
query.translation,
|
||||
keepVerseNumbers = true,
|
||||
cfg))
|
||||
else:
|
||||
for passage in fetchPassages(query.referenceText, query.translation, cfg):
|
||||
formattedPassages.add(
|
||||
case $args["--output-format"]:
|
||||
of "reading":
|
||||
formatPlain(passage, query.translation, keepVerseNumbers = false)
|
||||
of "text":
|
||||
passage.multiReplace([(re"\[(\d+)\]", "$1")])
|
||||
of "raw":
|
||||
passage
|
||||
else:
|
||||
formatMarkdown(passage, query.translation))
|
||||
|
||||
echo formattedPassages.join("\p\p")
|
||||
|
||||
|
||||
+207
-2
@@ -1,4 +1,4 @@
|
||||
import std/[strutils, tables]
|
||||
import std/[algorithm, json, strutils, tables, wordwrap]
|
||||
|
||||
import ./reference_parser
|
||||
|
||||
@@ -16,6 +16,12 @@ type
|
||||
breakKind: SegmentBreak
|
||||
indent: int
|
||||
text: string
|
||||
annotations: seq[BibleAnnotation]
|
||||
|
||||
BibleAnnotation = object
|
||||
kind: string
|
||||
offset: int
|
||||
targetText: string
|
||||
|
||||
BibleIndex = object
|
||||
segments: seq[BibleSegment]
|
||||
@@ -49,6 +55,31 @@ proc parseSegmentBreak(s: string): SegmentBreak =
|
||||
else:
|
||||
raise newException(ValueError, "invalid embedded Bible segment break: " & s)
|
||||
|
||||
proc parseAnnotations(metadata: string): seq[BibleAnnotation] =
|
||||
if metadata.strip.len == 0:
|
||||
return
|
||||
|
||||
let node = parseJson(metadata)
|
||||
if node.kind != JObject or not node.hasKey("annotations"):
|
||||
return
|
||||
|
||||
for item in node["annotations"].getElems:
|
||||
let targetText =
|
||||
if item.hasKey("targetText"): item["targetText"].getStr
|
||||
else: ""
|
||||
|
||||
if targetText.strip.len == 0:
|
||||
continue
|
||||
|
||||
result.add(BibleAnnotation(
|
||||
kind:
|
||||
if item.hasKey("kind"): item["kind"].getStr
|
||||
else: "",
|
||||
offset:
|
||||
if item.hasKey("offset"): item["offset"].getInt
|
||||
else: 0,
|
||||
targetText: targetText))
|
||||
|
||||
proc addSegment(index: var BibleIndex, segment: BibleSegment) =
|
||||
index.segments.add(segment)
|
||||
index.verses[verseKey(segment.code, segment.chapter, segment.verse)] = true
|
||||
@@ -92,7 +123,10 @@ proc loadBibleIndex(rows, translationName: string): BibleIndex =
|
||||
segment: parseInt(parts[3]),
|
||||
breakKind: parseSegmentBreak(parts[4]),
|
||||
indent: parseInt(parts[5]),
|
||||
text: parts[6]))
|
||||
text: parts[6],
|
||||
annotations:
|
||||
if parts.len == 8: parseAnnotations(parts[7])
|
||||
else: @[]))
|
||||
|
||||
proc requireLastChapter(index: BibleIndex, code: string): int =
|
||||
if not index.lastChapterByBook.hasKey(code):
|
||||
@@ -245,3 +279,174 @@ proc fetchPassages*(rows, reference, translationName: string): seq[string] =
|
||||
let index = loadBibleIndex(rows, translationName)
|
||||
for parsedReference in parseReferences(reference):
|
||||
result.add(fetchReference(index, parsedReference))
|
||||
|
||||
proc footnoteLabel(index: int): string =
|
||||
var n = index
|
||||
while true:
|
||||
result.insert($char(ord('a') + (n mod 26)), 0)
|
||||
n = (n div 26) - 1
|
||||
if n < 0:
|
||||
break
|
||||
|
||||
proc addSegmentWithFootnotes(
|
||||
line: var string,
|
||||
segment: BibleSegment,
|
||||
keepVerseNumbers: bool,
|
||||
notes: var seq[string]) =
|
||||
|
||||
var text = segment.text
|
||||
var annotations = segment.annotations
|
||||
annotations.sort(proc(a, b: BibleAnnotation): int = cmp(a.offset, b.offset))
|
||||
|
||||
var inserted = 0
|
||||
for annotation in annotations:
|
||||
let label = footnoteLabel(notes.len)
|
||||
notes.add(annotation.targetText)
|
||||
|
||||
let insertAt = max(0, min(text.len, annotation.offset + inserted))
|
||||
let marker = "[" & label & "]"
|
||||
text.insert(marker, insertAt)
|
||||
inserted += marker.len
|
||||
|
||||
let marker =
|
||||
if keepVerseNumbers: $segment.verse & " "
|
||||
else: ""
|
||||
|
||||
let content = marker & text
|
||||
if line.len == 0:
|
||||
line.add(repeat(" ", segment.indent) & content)
|
||||
elif segment.breakKind == sbSame:
|
||||
line.appendSameLine(content)
|
||||
else:
|
||||
line.add("\n" & repeat(" ", segment.indent) & content)
|
||||
|
||||
proc addFootnotes(lines: var seq[string], notes: seq[string]) =
|
||||
if notes.len == 0:
|
||||
return
|
||||
|
||||
lines.add("")
|
||||
for idx, note in notes:
|
||||
lines.add(footnoteLabel(idx) & ". " & note)
|
||||
|
||||
proc selectedChapters(index: BibleIndex, reference: PassageReference):
|
||||
seq[tuple[chapter: int, verses: Table[string, bool]]] =
|
||||
|
||||
let code = reference.book.code
|
||||
|
||||
if reference.ranges.len == 0:
|
||||
for chapter in 1 .. index.requireLastChapter(code):
|
||||
var verses: Table[string, bool]
|
||||
for verse in 1 .. index.requireLastVerse(code, chapter):
|
||||
discard index.requireVerse(code, chapter, verse)
|
||||
verses[verseKey(code, chapter, verse)] = true
|
||||
result.add((chapter, verses))
|
||||
else:
|
||||
var chapterPositions: Table[int, int]
|
||||
for range in reference.ranges:
|
||||
let selected = index.selectedVerses(reference, range)
|
||||
for key in selected.keys:
|
||||
let parts = key.split('\t')
|
||||
let chapter = parseInt(parts[1])
|
||||
if not chapterPositions.hasKey(chapter):
|
||||
chapterPositions[chapter] = result.len
|
||||
var verses: Table[string, bool]
|
||||
result.add((chapter, verses))
|
||||
result[chapterPositions[chapter]].verses[key] = true
|
||||
|
||||
proc plainChapter(
|
||||
index: BibleIndex,
|
||||
code: string,
|
||||
selected: Table[string, bool],
|
||||
keepVerseNumbers: bool): string =
|
||||
|
||||
var lines: seq[string]
|
||||
var currentLine = ""
|
||||
var notes: seq[string]
|
||||
var emittedVerses: Table[string, bool]
|
||||
var firstSelected = true
|
||||
|
||||
proc flushLine() =
|
||||
if currentLine.len > 0:
|
||||
lines.add(currentLine)
|
||||
currentLine = ""
|
||||
|
||||
for segment in index.segments:
|
||||
if segment.code != code:
|
||||
continue
|
||||
|
||||
let key = verseKey(segment.code, segment.chapter, segment.verse)
|
||||
if not selected.hasKey(key):
|
||||
continue
|
||||
|
||||
var adjusted = segment
|
||||
if firstSelected:
|
||||
adjusted = normalizeFirstSelectedSegment(adjusted)
|
||||
|
||||
let startsNewParagraph =
|
||||
adjusted.breakKind == sbParagraph and
|
||||
not firstSelected and
|
||||
(not emittedVerses.hasKey(key))
|
||||
|
||||
if startsNewParagraph:
|
||||
flushLine()
|
||||
lines.add("")
|
||||
elif adjusted.breakKind == sbLine:
|
||||
flushLine()
|
||||
|
||||
let marker =
|
||||
if emittedVerses.hasKey(key): false
|
||||
else:
|
||||
emittedVerses[key] = true
|
||||
true
|
||||
|
||||
var renderSegment = adjusted
|
||||
if not marker:
|
||||
renderSegment.verse = 0
|
||||
|
||||
if marker:
|
||||
currentLine.addSegmentWithFootnotes(renderSegment, keepVerseNumbers, notes)
|
||||
else:
|
||||
var noVerseSegment = renderSegment
|
||||
noVerseSegment.text = renderSegment.text
|
||||
noVerseSegment.breakKind = sbSame
|
||||
currentLine.addSegmentWithFootnotes(noVerseSegment, false, notes)
|
||||
|
||||
firstSelected = false
|
||||
|
||||
flushLine()
|
||||
lines.addFootnotes(notes)
|
||||
|
||||
var wrapped: seq[string]
|
||||
for line in lines:
|
||||
if line.len == 0:
|
||||
wrapped.add("")
|
||||
continue
|
||||
|
||||
let prepared =
|
||||
if line.len > 90: line.strip
|
||||
else: line & " "
|
||||
wrapped.add(wrapWords(prepared, maxLineWidth = 74, newLine = "\p"))
|
||||
wrapped.join("\p")
|
||||
|
||||
proc fetchPlainPassages*(
|
||||
rows,
|
||||
reference,
|
||||
translationName: string,
|
||||
keepVerseNumbers = true): seq[string] =
|
||||
|
||||
let index = loadBibleIndex(rows, translationName)
|
||||
for parsedReference in parseReferences(reference):
|
||||
var parts: seq[string]
|
||||
for chapter in index.selectedChapters(parsedReference):
|
||||
parts.add(index.plainChapter(
|
||||
parsedReference.book.code,
|
||||
chapter.verses,
|
||||
keepVerseNumbers))
|
||||
|
||||
let body = parts.join("\p\p")
|
||||
let referenceSeparator =
|
||||
if body.contains("\pa. "): "\p\p"
|
||||
else: "\p"
|
||||
|
||||
result.add(body & referenceSeparator & "– " & $parsedReference & " (" &
|
||||
translationName.toUpperAscii & ")")
|
||||
|
||||
@@ -5,3 +5,6 @@ const kjvRows = embeddedTranslationData("kjv")
|
||||
|
||||
proc fetchPassages*(reference: string): seq[string] =
|
||||
embedded_bible.fetchPassages(kjvRows, reference, "KJV")
|
||||
|
||||
proc fetchPlainPassages*(reference: string, keepVerseNumbers = true): seq[string] =
|
||||
embedded_bible.fetchPlainPassages(kjvRows, reference, "KJV", keepVerseNumbers)
|
||||
|
||||
@@ -11,3 +11,10 @@ proc fetchPassages*(reference: string): seq[string] =
|
||||
else:
|
||||
raise newException(ValueError,
|
||||
"MEV data is not embedded; generate data/private/mev.tsv and rebuild")
|
||||
|
||||
proc fetchPlainPassages*(reference: string, keepVerseNumbers = true): seq[string] =
|
||||
when hasEmbeddedTranslationData("mev"):
|
||||
embedded_bible.fetchPlainPassages(mevRows, reference, "MEV", keepVerseNumbers)
|
||||
else:
|
||||
raise newException(ValueError,
|
||||
"MEV data is not embedded; generate data/private/mev.tsv and rebuild")
|
||||
|
||||
Reference in New Issue
Block a user