Add translation-aware passage queries

This commit is contained in:
2026-06-14 08:46:34 -05:00
parent 42d2587704
commit 176fa46816
4 changed files with 131 additions and 17 deletions
+1
View File
@@ -1,5 +1,6 @@
esv_api esv_api
bibleref bibleref
tests/test_offline_kjv tests/test_offline_kjv
tests/test_passage_query
data/private/ data/private/
*.sw? *.sw?
+22 -17
View File
@@ -10,6 +10,7 @@ import ./api_bible
import ./esv import ./esv
import ./kjv import ./kjv
import ./mev import ./mev
import ./passage_query
proc formatMarkdown(raw, translation: string): string = proc formatMarkdown(raw, translation: string): string =
var reference = "" var reference = ""
@@ -95,7 +96,7 @@ proc fetchPassages(reference, translation: string, cfg: CombinedConfig): seq[str
else: else:
raise newException(ValueError, raise newException(ValueError,
"unsupported translation '" & translation & "unsupported translation '" & translation &
"'; supported translations: akjv, amp, esv, kjv, mev, nkjv, niv") "'; supported translations: " & supportedTranslationsList())
when isMainModule: when isMainModule:
const USAGE = """Usage: const USAGE = """Usage:
@@ -115,6 +116,9 @@ Options:
Select a specific translation. Supported values Select a specific translation. Supported values
are 'akjv', 'amp', 'esv', 'kjv', 'mev', are 'akjv', 'amp', 'esv', 'kjv', 'mev',
'nkjv', and 'niv'. Defaults to 'esv'. 'nkjv', and 'niv'. Defaults to 'esv'.
Individual references may override this with a
trailing marker, for example:
'John 3:16 (KJV); John 3:16 (ESV)'.
--esv-api-token <token> Provide the API token on the command line. By --esv-api-token <token> Provide the API token on the command line. By
default this will be read either from the default this will be read either from the
@@ -156,24 +160,25 @@ Options:
cfgFileJson = parseFile(cfgFilePath) cfgFileJson = parseFile(cfgFilePath)
let cfg = CombinedConfig(docopt: args, json: cfgFileJson) let cfg = CombinedConfig(docopt: args, json: cfgFileJson)
let translation = cfg.getVal("translation", "esv").strip.toLowerAscii let defaultTranslation = cfg.getVal("translation", "esv")
let reference = $args["<reference>"] let reference = $args["<reference>"]
let queries = parsePassageQueries(reference, defaultTranslation)
let passages = fetchPassages(reference, translation, cfg) var formattedPassages: seq[string] = @[]
for query in queries:
let formattedPassages = for passage in fetchPassages(query.referenceText, query.translation, cfg):
case $args["--output-format"]: formattedPassages.add(
of "plain": case $args["--output-format"]:
passages --> map(formatPlain(it, translation)) of "plain":
of "reading": formatPlain(passage, query.translation)
passages --> of "reading":
map(formatPlain(it, translation, keepVerseNumbers = false)) formatPlain(passage, query.translation, keepVerseNumbers = false)
of "text": of "text":
passages --> passage.multiReplace([(re"\[(\d+)\]", "$1")])
map(it.multiReplace([(re"\[(\d+)\]", "$1")])) of "raw":
of "raw": passages passage
else: else:
passages --> map(formatMarkdown(it, translation)) formatMarkdown(passage, query.translation))
echo formattedPassages.join("\p\p") echo formattedPassages.join("\p\p")
+64
View File
@@ -0,0 +1,64 @@
import std/strutils
import ./reference_parser
type PassageQuery* = object
reference*: PassageReference
translation*: string
const SupportedTranslations* = [
"akjv", "amp", "esv", "kjv", "mev", "niv", "nkjv"
]
proc supportedTranslationsList*(): string =
SupportedTranslations.join(", ")
proc normalizeTranslation*(translation: string): string =
result = translation.strip.toLowerAscii
for supported in SupportedTranslations:
if result == supported:
return
raise newException(ValueError,
"unsupported translation '" & translation &
"'; supported translations: " & supportedTranslationsList())
proc splitTrailingTranslationMarker(
input: string): tuple[referenceText: string, translation: string] =
let text = input.strip
if not text.endsWith(")"):
return (text, "")
let openIdx = text.rfind("(")
if openIdx < 0:
return (text, "")
let referenceText = text[0 ..< openIdx].strip
let translation = text[openIdx + 1 ..< text.len - 1].strip
if referenceText.len == 0 or translation.len == 0:
return (text, "")
(referenceText, translation)
proc parsePassageQuery*(input, defaultTranslation: string): PassageQuery =
let parsed = splitTrailingTranslationMarker(input)
result.reference = parseReference(parsed.referenceText)
result.translation =
if parsed.translation.len > 0:
normalizeTranslation(parsed.translation)
else:
normalizeTranslation(defaultTranslation)
proc parsePassageQueries*(input, defaultTranslation: string): seq[PassageQuery] =
for rawRef in input.split(';'):
let refText = rawRef.strip
if refText.len > 0:
result.add(parsePassageQuery(refText, defaultTranslation))
if result.len == 0:
raise newException(ValueError, "empty Bible reference")
proc referenceText*(query: PassageQuery): string =
$query.reference
+44
View File
@@ -0,0 +1,44 @@
import std/unittest
import ../src/passage_query
suite "passage query parser":
test "uses the default translation when no marker is present":
let queries = parsePassageQueries("John 3:16", "kjv")
check queries.len == 1
check queries[0].referenceText == "John 3:16"
check queries[0].translation == "kjv"
test "uses a trailing translation marker":
let queries = parsePassageQueries("2 John 5 (KJV)", "esv")
check queries.len == 1
check queries[0].referenceText == "2 John 5"
check queries[0].translation == "kjv"
test "parses mixed translation queries":
let queries = parsePassageQueries("2 John 5 (KJV); 2 John 5 (ESV)", "mev")
check queries.len == 2
check queries[0].referenceText == "2 John 5"
check queries[0].translation == "kjv"
check queries[1].referenceText == "2 John 5"
check queries[1].translation == "esv"
test "uses the default translation per unmarked reference":
let queries = parsePassageQueries("John 3:16; Psalm 23 (MEV)", "nkjv")
check queries.len == 2
check queries[0].referenceText == "John 3:16"
check queries[0].translation == "nkjv"
check queries[1].referenceText == "Psalms 23"
check queries[1].translation == "mev"
test "rejects unknown translation markers":
expect ValueError:
discard parsePassageQueries("John 3:16 (XYZ)", "esv")
test "rejects unknown default translations":
expect ValueError:
discard parsePassageQueries("John 3:16", "xyz")