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
bibleref
tests/test_offline_kjv
tests/test_passage_query
data/private/
*.sw?
+22 -17
View File
@@ -10,6 +10,7 @@ import ./api_bible
import ./esv
import ./kjv
import ./mev
import ./passage_query
proc formatMarkdown(raw, translation: string): string =
var reference = ""
@@ -95,7 +96,7 @@ proc fetchPassages(reference, translation: string, cfg: CombinedConfig): seq[str
else:
raise newException(ValueError,
"unsupported translation '" & translation &
"'; supported translations: akjv, amp, esv, kjv, mev, nkjv, niv")
"'; supported translations: " & supportedTranslationsList())
when isMainModule:
const USAGE = """Usage:
@@ -115,6 +116,9 @@ Options:
Select a specific translation. Supported values
are 'akjv', 'amp', 'esv', 'kjv', 'mev',
'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
default this will be read either from the
@@ -156,24 +160,25 @@ Options:
cfgFileJson = parseFile(cfgFilePath)
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 queries = parsePassageQueries(reference, defaultTranslation)
let passages = fetchPassages(reference, translation, cfg)
let formattedPassages =
case $args["--output-format"]:
of "plain":
passages --> map(formatPlain(it, translation))
of "reading":
passages -->
map(formatPlain(it, translation, keepVerseNumbers = false))
of "text":
passages -->
map(it.multiReplace([(re"\[(\d+)\]", "$1")]))
of "raw": passages
else:
passages --> map(formatMarkdown(it, translation))
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))
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")