/* * esvsearch: search the Bible from your terminal * * The GPLv2 License (GPLv2) * Copyright (c) 2023-2025 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 . */ module esvsearch; import std.file : FileException; import std.getopt : getopt, GetOptException; import std.path : baseName, expandTilde; import std.process : environment; import std.regex : regex, matchAll, replaceFirst; import std.stdio : writeln, writefln; import std.string : tr; import esvapi; import initial; import util; import cf = config; @safe: string cFlag; /* config path */ bool eFlag; /* exact matches */ bool mFlag; /* machine readable */ bool VFlag; /* show version */ string machineReadableFmt(string reference, string content) { /* match the start of the reference against bibleBooks * to identify what book it's from, so we can replace * spaces in the book name with underscores :-) */ foreach (string book; bibleBooks) { auto match = reference.matchAll(regex("^(" ~ book ~ ") \\d")); if (!match.empty) { assert(match.captures[1] == book && bookValid(match.captures[1])); reference = reference.replaceFirst( regex('^' ~ book), book.tr(" ", "_")); } } return reference ~ " / " ~ content ~ "\n"; } int main(string[] args) { INIUnit ini; ESVApi esv; string apiKey; string configPath; string query; sharedInit(args); cFlag = null; /* Parse command-line options */ try { import std.getopt : config; getopt(args, config.bundling, config.caseSensitive, "c", &cFlag, "e", &eFlag, "m", &mFlag, "V", &VFlag, ); } catch (GetOptException e) { handleOptError(e.msg); } if (VFlag) { writeln("esvsearch from esv " ~ cf.esvVersion); return 0; } if (args.length < 2) { stderr.writefln("usage: %s [-emV] [-c config] query", baseName(args[0])); return 1; } query = args[1].dup(); query = eFlag ? `"` ~ query ~ `"` : query; /* determine configuration file: options take first priority, * then environment variables, and then the default path */ configPath = environment.get(cf.configEnv, cf.configPath) .expandTilde(); try { if (cFlag) configPath = cFlag.expandTilde(); readINIFile(ini, configPath); } catch (FileException e) { /* filesystem syscall errors */ import core.stdc.errno : ENOENT; if (e.errno != ENOENT) die(e.msg); warn(configPath ~ ": no such file or directory"); warn("Invoke esv to create an initial configuration file."); } apiKey = ini["api"].key("key"); enforceDie(apiKey != null, "API key not present in configuration file; cannot proceed"); esv = ESVApi(apiKey); foreach (char ch; args[1]) { enforceDie(ch != '"', "query is invalid; remove any double quotes"); } try writeln(mFlag ? esv.searchFormat(query, &machineReadableFmt) : esv.searchFormat(query)); catch (ESVException) die("no results"); catch (CurlException e) die(e.msg); return 0; }