178 lines
5.9 KiB
Nim
178 lines
5.9 KiB
Nim
# Nim CLI for retrieving Biblical passages
|
||
# © 2023 Jonathan Bernard
|
||
|
||
## Simple command-line tool for retrieving Biblical passages.
|
||
|
||
import std/[json, logging, os, re, strutils, wordwrap]
|
||
import cliutils, docopt, zero_functional
|
||
|
||
import ./api_bible
|
||
import ./esv
|
||
|
||
proc formatMarkdown(raw, translation: string): string =
|
||
var reference = ""
|
||
var inVerse = false
|
||
var verseLines = newSeq[string]()
|
||
|
||
for line in raw.splitLines:
|
||
if reference.len == 0: reference = line.strip
|
||
if inVerse:
|
||
if line.startsWith("Footnotes"): inVerse = false
|
||
elif line.isEmptyOrWhitespace and verseLines[^1] != "":
|
||
verseLines.add("")
|
||
elif not line.match(re"^\s+[^\s]"): continue
|
||
elif line.match(re"$(.*)\(ESV\)$"): verseLines.add(line[0 ..< ^5])
|
||
else: verseLines.add(line)
|
||
elif line.match(re"^\s+\[\d+\]"):
|
||
inVerse = true
|
||
verseLines.add(line)
|
||
|
||
let wrapped = (verseLines -->
|
||
map(if it.len > 90: it.strip else: it & " ").
|
||
map(it.multiReplace([(re"\((\d+)\)", ""), (re"\[(\d+)\]", "**$1**")])).
|
||
map(wrapWords(it, maxLineWidth = 74, newLine = "\p"))).join("\p")
|
||
|
||
result = (wrapped.splitLines --> map("> " & it)).
|
||
join("\p") & "\p> -- *" & reference & " (" &
|
||
translation.toUpperAscii & ")*"
|
||
|
||
proc formatPlain(
|
||
raw,
|
||
translation: string,
|
||
keepVerseNumbers = true): string =
|
||
|
||
var reference = ""
|
||
var inVerse = false
|
||
var verseLines = newSeq[string]()
|
||
|
||
for line in raw.splitLines:
|
||
if reference.len == 0: reference = line.strip
|
||
if inVerse:
|
||
if line.startsWith("Footnotes"): inVerse = false
|
||
elif line.isEmptyOrWhitespace and verseLines[^1] != "":
|
||
verseLines.add("")
|
||
elif not line.match(re"^\s+[^\s]"): continue
|
||
elif line.match(re"$(.*)\(ESV\)$"): verseLines.add(line[0 ..< ^5])
|
||
else: verseLines.add(line)
|
||
elif line.match(re"^\s+\[\d+\]"):
|
||
inVerse = true
|
||
verseLines.add(line)
|
||
|
||
let wrapped = (verseLines -->
|
||
map(if it.len > 90: it.strip else: it & " ").
|
||
map(
|
||
if keepVerseNumbers:
|
||
it.multiReplace([(re"\((\d+)\)", ""), (re"\[(\d+)\]", "$1")])
|
||
else:
|
||
it.multiReplace([(re"\((\d+)\)", ""), (re"\[(\d+)\]", "")])).
|
||
map(wrapWords(it, maxLineWidth = 74, newLine = "\p"))).join("\p")
|
||
|
||
result = (wrapped.splitLines --> map(it)).
|
||
join("\p") & "\p– " & reference & " (" & translation.toUpperAscii & ")"
|
||
|
||
proc fetchPassages(reference, translation: string, cfg: CombinedConfig): seq[string] =
|
||
case translation
|
||
of "esv":
|
||
esv.fetchPassages(
|
||
reference,
|
||
cfg.getVal("esv-api-token"),
|
||
cfg.getVal("esv-api-root", "https://api.esv.org"))
|
||
of "amp", "nkjv", "niv":
|
||
api_bible.fetchPassages(
|
||
reference,
|
||
translation,
|
||
cfg.getVal("api-bible-api-key"),
|
||
cfg.getVal("api-bible-root", api_bible.apiBibleRoot),
|
||
cfg.getVal(
|
||
"api-bible-" & translation & "-bible-id",
|
||
api_bible.defaultBibleId(translation)))
|
||
else:
|
||
raise newException(ValueError,
|
||
"unsupported translation '" & translation &
|
||
"'; supported translations: amp, esv, nkjv, niv")
|
||
|
||
when isMainModule:
|
||
const USAGE = """Usage:
|
||
bibleref <reference> [options]
|
||
|
||
Options:
|
||
|
||
--debug Log debug information.
|
||
|
||
--echo-args Echo back the arguments that were passed on the
|
||
command line for debugging purposes.
|
||
|
||
-f, --output-format <format> Select a specific output format. Valid values
|
||
are 'raw', 'markdown', 'plain', 'reading'.
|
||
|
||
-t, --translation <translation>
|
||
Select a specific translation. Supported values
|
||
are 'amp', 'esv', 'nkjv', and 'niv'. Defaults
|
||
to 'esv'.
|
||
|
||
--esv-api-token <token> Provide the API token on the command line. By
|
||
default this will be read either from the
|
||
.bibleref.cfg.json file or the ESV_API_TOKEN
|
||
envionment variable.
|
||
|
||
--api-bible-api-key <key> Provide the API.Bible API key for translations
|
||
backed by api.bible.
|
||
|
||
--api-bible-root <url> Override the API.Bible API root. Defaults to
|
||
https://rest.api.bible/v1.
|
||
|
||
--api-bible-amp-bible-id <id> Override the API.Bible Bible ID for AMP.
|
||
|
||
--api-bible-niv-bible-id <id> Override the API.Bible Bible ID for NIV.
|
||
|
||
--api-bible-nkjv-bible-id <id>
|
||
Override the API.Bible Bible ID for NKJV.
|
||
"""
|
||
|
||
let consoleLogger = newConsoleLogger(
|
||
levelThreshold=lvlInfo,
|
||
fmtStr="bibleref - $levelname: ")
|
||
logging.addHandler(consoleLogger)
|
||
|
||
try:
|
||
# Parse arguments
|
||
let args = docopt(USAGE, version = "1.0.0")
|
||
|
||
if args["--debug"]:
|
||
consoleLogger.levelThreshold = lvlDebug
|
||
|
||
if args["--echo-args"]: stderr.writeLine($args)
|
||
|
||
let cfgFilePath = getEnv("HOME") / ".bibleref.cfg.json"
|
||
var cfgFileJson = newJObject()
|
||
if fileExists(cfgFilePath):
|
||
debug "Loading config from " & cfgFilePath
|
||
cfgFileJson = parseFile(cfgFilePath)
|
||
|
||
let cfg = CombinedConfig(docopt: args, json: cfgFileJson)
|
||
let translation = cfg.getVal("translation", "esv").strip.toLowerAscii
|
||
let reference = $args["<reference>"]
|
||
|
||
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))
|
||
|
||
echo formattedPassages.join("\p\p")
|
||
|
||
except CatchableError:
|
||
fatal getCurrentExceptionMsg()
|
||
debug getCurrentException().getStackTrace()
|
||
quit(QuitFailure)
|