209 lines
5.2 KiB
D
209 lines
5.2 KiB
D
/*
|
|
* esv: read the Bible from your terminal
|
|
*
|
|
* The GPLv2 License (GPLv2)
|
|
* Copyright (c) 2023-2024 Jeremy Baxter
|
|
*
|
|
* esv is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* esv is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with esv. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
module esv;
|
|
|
|
import std.conv : to, ConvException;
|
|
import std.file : exists, mkdirRecurse, write, FileException;
|
|
import std.format : format;
|
|
import std.getopt : getopt, GetOptException;
|
|
import std.path : baseName, dirName, expandTilde;
|
|
import std.process : environment, executeShell;
|
|
import std.stdio : writeln, writefln;
|
|
import std.string : splitLines;
|
|
|
|
import esvapi;
|
|
import initial;
|
|
import util;
|
|
|
|
import cf = config;
|
|
|
|
@safe:
|
|
|
|
bool aFlag; /* audio */
|
|
string cFlag; /* config path */
|
|
bool fFlag, FFlag; /* footnotes */
|
|
bool hFlag, HFlag; /* headings */
|
|
int lFlag; /* line length */
|
|
bool lFlagSpecified;
|
|
bool nFlag, NFlag; /* verse numbers */
|
|
bool rFlag, RFlag; /* passage references */
|
|
bool VFlag; /* show version */
|
|
|
|
int
|
|
main(string[] args)
|
|
{
|
|
INIUnit ini;
|
|
ESVApi esv;
|
|
string apiKey;
|
|
string configPath;
|
|
|
|
sharedInit(args);
|
|
|
|
cFlag = null;
|
|
|
|
/* Parse command-line options */
|
|
try {
|
|
import std.getopt : config;
|
|
getopt(args,
|
|
config.bundling,
|
|
config.caseSensitive,
|
|
"a", &aFlag,
|
|
"c", &cFlag,
|
|
"F", &FFlag, "f", &fFlag,
|
|
"H", &HFlag, "h", &hFlag,
|
|
"l", &onLineLength,
|
|
"N", &NFlag, "n", &nFlag,
|
|
"R", &RFlag, "r", &rFlag,
|
|
"V", &VFlag,
|
|
);
|
|
} catch (GetOptException e) {
|
|
handleOptError(e.msg);
|
|
}
|
|
|
|
if (VFlag) {
|
|
writeln("esv " ~ cf.esvVersion);
|
|
return 0;
|
|
}
|
|
|
|
if (args.length < 3) {
|
|
stderr.writefln(
|
|
"usage: %s [-aFfHhNnRrV] [-c config] [-l length] book verses",
|
|
baseName(args[0]));
|
|
return 1;
|
|
}
|
|
|
|
enforceDie(bookValid(parseBook(args[1])),
|
|
"book '%s' does not exist", args[1]);
|
|
enforceDie(verseValid(args[2]),
|
|
"invalid verse format '%s'", args[2]);
|
|
|
|
/* determine configuration file: options take first priority,
|
|
* then environment variables, and then the default path */
|
|
config:
|
|
configPath = environment.get(cf.configEnv, cf.configPath)
|
|
.expandTilde();
|
|
try {
|
|
if (cFlag)
|
|
configPath = cFlag.expandTilde();
|
|
|
|
if (!configPath.exists()) {
|
|
mkdirRecurse(dirName(configPath));
|
|
configPath.write(format!
|
|
"## Configuration file for esv.
|
|
|
|
# An API key is required to access the ESV Bible API.
|
|
[api]
|
|
key = %s
|
|
|
|
# Settings that modify how passages are displayed:
|
|
[passage]
|
|
#footnotes = false
|
|
#headings = false
|
|
#passage-references = false
|
|
#verse-numbers = false
|
|
"(cf.apiKey));
|
|
}
|
|
readINIFile(ini, configPath);
|
|
} catch (FileException e) {
|
|
/* filesystem syscall errors */
|
|
die(e.msg);
|
|
}
|
|
|
|
apiKey = ini["api"].key("key");
|
|
enforceDie(apiKey != null,
|
|
"API key not present in configuration file; cannot proceed");
|
|
|
|
esv = ESVApi(apiKey);
|
|
|
|
if (aFlag) {
|
|
string tmpf, player;
|
|
|
|
try
|
|
tmpf = esv.getAudioPassage(parseBook(args[1]), args[2]);
|
|
catch (CurlException e)
|
|
die(e.msg);
|
|
player = environment.get(cf.playerEnv, cf.mp3Player);
|
|
|
|
/* check for an audio player */
|
|
enforceDie(
|
|
executeShell(
|
|
format!"command -v %s >/dev/null 2>&1"(player)
|
|
).status == 0,
|
|
player ~ " is required for audio mode; cannot continue");
|
|
|
|
/* esv has built-in support for mpg123 and mpv.
|
|
* Other players will work, just set ESV_PLAYER */
|
|
player ~=
|
|
player == "mpg123" ? " -q " :
|
|
player == "mpv" ? " --msg-level=all=no " : " ";
|
|
/* spawn the player */
|
|
executeShell(player ~ tmpf);
|
|
return 0;
|
|
}
|
|
|
|
esv.extraParameters = ini["api"].key("parameters", "");
|
|
|
|
/* Get [passage] keys */
|
|
foreach (string key; [
|
|
"footnotes",
|
|
"headings",
|
|
"passage-references",
|
|
"verse-numbers"
|
|
]) {
|
|
try
|
|
esv.opts.b["include-" ~ key] =
|
|
ini["passage"].keyAs!bool(key, true);
|
|
catch (INITypeException e)
|
|
die(configPath ~ ": " ~ e.msg);
|
|
}
|
|
/* Get line-length ([passage]) */
|
|
try
|
|
esv.opts.i["line-length"] =
|
|
lFlagSpecified ? lFlag :
|
|
ini["passage"].keyAs!int("line-length", terminalColumns());
|
|
catch (INITypeException e)
|
|
die(configPath ~ ": " ~ e.msg);
|
|
|
|
if (fFlag) esv.opts.b["include-footnotes"] = true;
|
|
if (hFlag) esv.opts.b["include-headings"] = true;
|
|
if (nFlag) esv.opts.b["include-verse-numbers"] = true;
|
|
if (rFlag) esv.opts.b["include-passage-references"] = true;
|
|
if (FFlag) esv.opts.b["include-footnotes"] = false;
|
|
if (HFlag) esv.opts.b["include-headings"] = false;
|
|
if (NFlag) esv.opts.b["include-verse-numbers"] = false;
|
|
if (RFlag) esv.opts.b["include-passage-references"] = false;
|
|
|
|
try
|
|
writeln(esv.getPassage(parseBook(args[1]), args[2]));
|
|
catch (CurlException e)
|
|
die(e.msg);
|
|
return 0;
|
|
}
|
|
|
|
private void
|
|
onLineLength(string flag, string value)
|
|
{
|
|
lFlagSpecified = true;
|
|
try
|
|
lFlag = value.to!int();
|
|
catch (ConvException e)
|
|
die("illegal argument to -l option -- must be a positive integer");
|
|
}
|