aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYour Name <you@example.com>2020-11-05 15:50:11 -0500
committerYour Name <you@example.com>2020-11-05 15:50:11 -0500
commit2fe897e2cf750339a7e466aeafe64f45fb650f10 (patch)
treed27bbda7c1ef5368a90704843deea014e6c810de
downloadlibbible-2fe897e2cf750339a7e466aeafe64f45fb650f10.tar.gz
libbible-2fe897e2cf750339a7e466aeafe64f45fb650f10.tar.bz2
libbible-2fe897e2cf750339a7e466aeafe64f45fb650f10.zip
Initial commit
-rw-r--r--Makefile39
-rw-r--r--bible.cc182
-rw-r--r--libbible.cc218
-rw-r--r--libbible.h98
-rw-r--r--mods.cc126
-rw-r--r--modules/ESV2011.zipbin0 -> 12087753 bytes
-rw-r--r--modules/JPS.zipbin0 -> 1170889 bytes
-rw-r--r--readme1
-rw-r--r--settings.cc23
-rw-r--r--testLibbible.cc206
10 files changed, 893 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..61957d7
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,39 @@
+CC=g++
+LIBS=sword
+CFLAGS=-c -Wall -fPIC
+LDFLAGS=
+SOURCES=libbible.cc mods.cc settings.cc
+OBJECTS=$(SOURCES:.cc=.o)
+LIBRARY=libbible.so
+ifeq ($(PREFIX),)
+ PREFIX := /usr
+endif
+
+all: $(SOURCES) $(LIBRARY)
+
+install: $(LIBRARY)
+ install -d $(DESTDIR)$(PREFIX)/lib/
+ install -m 644 $(LIBRARY) $(DESTDIR)$(PREFIX)/lib/
+ install -d $(DESTDIR)$(PREFIX)/include/
+ install -m 644 libbible.h $(DESTDIR)$(PREFIX)/include/
+
+test: $(OBJECTS) testLibbible.o
+ $(CC) $(LDFLAGS) $(OBJECTS) testLibbible.o -o $@ `pkg-config $(LIBS) --libs` -lcppunit
+
+testLibbible.o: testLibbible.cc
+ $(CC) $(CFLAGS) testLibbible.cc -o $@
+
+bible: $(OBJECTS) bible.o
+ $(CC) $(LDFLAGS) $(OBJECTS) bible.o -o $@ `pkg-config $(LIBS) --libs`
+
+bible.o: bible.cc
+ $(CC) $(CFLAGS) bible.cc -o $@
+
+$(LIBRARY): $(OBJECTS)
+ $(CC) $(LDFLAGS) $(OBJECTS) -shared -o $@ `pkg-config $(LIBS) --libs`
+
+.cc.o:
+ $(CC) $(CFLAGS) $< -o $@ `pkg-config $(LIBS) --cflags`
+
+clean:
+ rm -rf *.o $(LIBRARY) test bible
diff --git a/bible.cc b/bible.cc
new file mode 100644
index 0000000..2846c5f
--- /dev/null
+++ b/bible.cc
@@ -0,0 +1,182 @@
+#include "libbible.h"
+#include <string>
+#include <algorithm>
+#include <getopt.h>
+
+using namespace std;
+
+void usage() {
+ printf("\nUsage:\n bible [options] [reference]\n\n");
+ printf("Print bible passages.\n\n");
+ printf("Options:\n");
+ printf(" -h, --help display this help message\n");
+ printf(" --list-modules list all installed modules\n");
+ printf(" -m, --module <mod> use specified module\n");
+ printf(" --set-default-module <mod> use specified module by default in future runs\n");
+ printf(" --list-books list books available in the current module\n");
+ printf(" --list-chapters <book> list chapters available in book in the current module\n");
+ printf(" -o, --omit-verse-numbers when printing verse text, skip printing verse and chapter numbers\n");
+ printf("\n\nExamples:\n bible Gal 5:22-23\n");
+ printf(" bible John 3:16\n bible Romans 12\n bible Matt 5:3-7:27\n");
+ printf(" bible Genesis 1-3\n");
+}
+
+string getDefaultModule() {
+ return libbible::settingsRead("module");
+}
+
+void listModules() {
+ map<string, vector<string>> mods = libbible::getModules();
+ string defaultMod = getDefaultModule();
+ printf("Modules Installed:\n");
+ for(auto pair : mods) {
+ if(pair.first == defaultMod) {
+ printf(" %s (default)\n", pair.first.c_str());
+ } else {
+ printf(" %s\n", pair.first.c_str());
+ }
+ }
+}
+
+void setDefaultModule(string modname) {
+ libbible::settingsWrite("module", modname);
+}
+
+void listBooks(string modname) {
+ map<string, vector<string>> mods = libbible::getModules();
+ if(mods.find(modname) == mods.end()) {
+ printf("ERROR: Module \"%s\" not installed!", modname.c_str());
+ } else {
+ printf("Books in Module %s:\n", modname.c_str());
+ for(string book : mods[modname]) {
+ printf(" %s\n", book.c_str());
+ }
+ }
+}
+
+void listChapters(string modname, string book) {
+ printf("Valid chapters for book %s in module %s:\n", book.c_str(), modname.c_str());
+ for(auto pass : libbible::getPassages(modname, book)) {
+ printf(" Chapter %d, Verses %d-%d\n", pass.chapterStart, pass.verseStart, pass.verseEnd);
+ }
+}
+
+int main(int argc, char* argv[]) {
+ static struct option long_options[] = {
+ {"help", no_argument, 0, 'h'},
+ {"list-modules", no_argument, 0, 0},
+ {"module", required_argument, 0, 'm'},
+ {"set-default-module", required_argument, 0, 0},
+ {"list-books", no_argument, 0, 0},
+ {"list-chapters", required_argument, 0, 0},
+ {"omit-verse-numbers", no_argument, 0, 'o'}
+ };
+ int opt, option_index;
+ string modname;
+ bool omitVerseNums = false;
+ bool doListBooks = false;
+ string listChaptersBook;
+ string option;
+ while ((opt = getopt_long(argc, argv, "hm:o", long_options, &option_index)) != -1) {
+ switch(opt) {
+ case 'h':
+ usage();
+ return 0;
+ case 'm':
+ modname = string(optarg);
+ break;
+ case 'o':
+ omitVerseNums = true;
+ break;
+ case 0:
+ option = string(long_options[option_index].name);
+ if(option == "list-modules") {
+ listModules();
+ return 0;
+ } else if(option == "set-default-module") {
+ setDefaultModule(string(optarg));
+ } else if(option == "list-books") {
+ doListBooks = true;
+ } else if(option == "list-chapters") {
+ listChaptersBook = string(optarg);
+ }
+ break;
+ default:
+ usage();
+ return 1;
+ }
+ }
+ if(modname.empty()) {
+ modname = getDefaultModule();
+ }
+ if(doListBooks) {
+ listBooks(modname);
+ }
+ if(! listChaptersBook.empty()) {
+ listChapters(modname, listChaptersBook);
+ }
+ string reference;
+ while(optind < argc) {
+ reference += argv[optind++];
+ reference += " ";
+ }
+
+ auto text = libbible::getText(libbible::passage{.modName = modname, .reference = reference});
+ int chapter = 0;
+ int verse = 0;
+ const char* indent = " ";
+ bool isNewline = true;
+ for(auto tex : text) {
+ if(!omitVerseNums && tex.chapter != chapter) {
+ printf("\nChapter %d:\n", tex.chapter);
+ }
+ bool isParagraph = false;
+ bool isIndent = false;
+ bool isDivineName = false;
+ bool isJesus = false;
+ for(string modifier : tex.modifiers) {
+ if(modifier == "paragraph") {
+ isParagraph = true;
+ } else if (modifier == "line indent0") {
+ isIndent = true;
+ } else if (modifier == "divineName") {
+ isDivineName = true;
+ } else if (modifier == "wordsOfJesus") {
+ isJesus = true;
+ }
+ }
+ if(isIndent) {
+ isParagraph = false;
+ if(isNewline) {
+ printf(indent);
+ }
+ }
+ if(isParagraph) {
+ printf(indent);
+ }
+ if(isDivineName) {
+ transform(tex.text.begin(), tex.text.end(), tex.text.begin(), ::toupper);
+ }
+ if(isJesus) {
+ printf("\033[;31m");
+ }
+ if(omitVerseNums && tex.verse != verse) {
+ printf(" ");
+ } else if(!omitVerseNums && tex.verse != verse) {
+ printf(" (%d) ", tex.verse);
+ }
+ chapter = tex.chapter;
+ verse = tex.verse;
+ printf(tex.text.c_str());
+ if(tex.text.back() == '\n') {
+ isNewline = true;
+ } else {
+ isNewline = false;
+ }
+ if(isJesus) {
+ printf("\033[0m");
+ }
+ }
+ printf("\n");
+ return 0;
+}
diff --git a/libbible.cc b/libbible.cc
new file mode 100644
index 0000000..7b08174
--- /dev/null
+++ b/libbible.cc
@@ -0,0 +1,218 @@
+#include "libbible.h"
+#include <sword/versekey.h>
+#include <sword/markupfiltmgr.h>
+#include <sword/swmodule.h>
+#include <sword/swmgr.h>
+#include <algorithm>
+
+using namespace sword;
+using namespace std;
+
+SWMgr library(new MarkupFilterMgr(FMT_XHTML));
+
+vector<string> getBooks(SWModule *target) {
+ vector<string> books;
+ VerseKey *key = (VerseKey *) target->getKey();
+ for(char t = 1; t <= key->getTestamentMax(); t++) {
+ key->setTestament(t);
+ for(char b = 1; b <= key->getBookMax(); b++) {
+ key->setBook(b);
+ // Bug (whose fault??) in JPS; they CLAIM to have two testaments,
+ // but they only have one, which causes repeats.
+ if(std::find(books.begin(), books.end(), key->getBookName()) == books.end()) {
+ books.push_back(key->getBookName());
+ }
+ }
+ }
+ return books;
+}
+
+map<string, vector<string>> libbible::getModules() {
+ library.load();
+ map<string, vector<string>> mods;
+ ModMap::iterator it;
+ for (it = library.getModules().begin(); it != library.getModules().end(); it++) {
+ string modName = (*it).second->getName();
+ SWModule *target = library.getModule(modName.c_str());
+ mods[modName] = getBooks(target);
+ }
+ return mods;
+}
+
+vector<libbible::passage> libbible::getPassages(string modName, string book) {
+ vector<libbible::passage> passages;
+ SWModule *target = library.getModule(modName.c_str());
+ target->setKey((book + " " + "1").c_str());
+ VerseKey *key = (VerseKey *) target->getKey();
+ int maxChapter = key->getChapterMax();
+ for(int chapter = 1; chapter <= maxChapter; chapter++) {
+ string ref = book + ' ' + to_string(chapter);
+ target->setKey(ref.c_str());
+ VerseKey *key = (VerseKey *) target->getKey();
+ libbible::passage pass;
+ pass.modName = modName;
+ pass.reference = ref;
+ pass.book = string(key->getBookName());
+ pass.bookShort = string(key->getBookAbbrev());
+ pass.chapterStart = chapter;
+ pass.chapterEnd = chapter;
+ pass.verseStart = 1;
+ pass.verseEnd = key->getVerseMax();
+ passages.push_back(pass);
+ }
+ return passages;
+}
+
+libbible::text getEmptyText(VerseKey *key) {
+ libbible::text t;
+ t.chapter = key->getChapter();
+ t.verse = key->getVerse();
+ t.book = key->getBook();
+ t.bookShort = key->getBookAbbrev();
+ return t;
+}
+
+void inferMissing(libbible::passage *pass, SWModule *target) {
+ //printf("Hey, I'm inferring missing parts!\n");
+ if(! pass->reference.empty()) {
+ // Let's use the target to help us
+ target->setKey(pass->reference.c_str());
+ VerseKey *key = (VerseKey *) target->getKey();
+ pass->book = string(key->getBookName());
+ pass->bookShort = string(key->getBookAbbrev());
+ pass->chapterStart = key->getChapter();
+ pass->verseStart = key->getVerse();
+ //printf("Results so far: book: %s; chapterStart: %d; verseStart: %d\n", pass->book.c_str(), pass->chapterStart, pass->verseStart);
+ // And now we just need chapterEnd and verseEnd. Yippee.
+ string ref = string(pass->reference);
+ ref.erase(remove(ref.begin(), ref.end(), ' '), ref.end());
+ if(ref.find('-') == string::npos) {
+ // There's no range!
+ if(ref.find(':') == string::npos) {
+ // It's a full chapter reference
+ pass->chapterEnd = pass->chapterStart;
+ pass->verseEnd = key->getVerseMax();
+ } else {
+ // It's a single verse reference
+ pass->chapterEnd = pass->chapterStart;
+ pass->verseEnd = pass->verseStart;
+ //printf("Hey, it's a single verse reference!\n");
+ }
+ } else {
+ if(ref.find(':') == string::npos) {
+ // It's a multi-full-chapter reference
+ pass->chapterEnd = stoi(ref.substr(ref.find_last_of('-')+1));
+ pass->verseEnd = key->getVerseMax();
+ } else {
+ // It falls in categories c:v-v or c:v-c:v (or, technically, c-c:v)
+ string rangeEnd = ref.substr(ref.find_last_of('-')+1);
+ if(rangeEnd.find(':') == string::npos) {
+ // It's c:v-v
+ pass->verseEnd = stoi(rangeEnd);
+ pass->chapterEnd = pass->chapterStart;
+ } else {
+ // It's c:v-c:v (or c-c:v, but code is the same)
+ pass->chapterEnd = stoi(rangeEnd.substr(0, rangeEnd.find(':')));
+ pass->verseEnd = stoi(rangeEnd.substr(rangeEnd.find(':')+1));
+ }
+ }
+ }
+ }
+ if(pass->book.empty()) {
+ pass->book = pass->bookShort;
+ }
+}
+
+vector<libbible::text> libbible::getText(libbible::passage pass) {
+ SWModule *target = library.getModule(pass.modName.c_str());
+ inferMissing(&pass, target);
+ target->setKey((pass.book
+ + " " + to_string(pass.chapterStart)
+ + ":" + to_string(pass.verseStart)).c_str());
+ VerseKey *key = (VerseKey *) target->getKey();
+ vector<libbible::text> texts;
+
+ bool endOfParagraph = false;
+
+ for(; key->getChapter() < pass.chapterEnd ||
+ (key->getChapter() == pass.chapterEnd && key->getVerse() <= pass.verseEnd);
+ (*key)++) {
+ texts.push_back(getEmptyText(key));
+
+ string text = string(target->renderText());
+
+ // Handle ¶ symbol if at beginning of line
+ if(text.find("¶") == 0) {
+ text.replace(0, 1, "\n");
+ endOfParagraph = true;
+ }
+
+ while(text[0] == ' ' || text[0] == '\t') {
+ text.erase(0, 1);
+ }
+
+ // Find and replace everything in "subs" map
+ const map<string, string> subs = {{"¶", "\n\t"}};
+ for(auto const &repl : subs) {
+ string::size_type location;
+ while((location = text.find(repl.first)) != string::npos) {
+ text.replace(location, repl.first.size(), repl.second);
+ }
+ }
+
+ if(key->getVerse() == 1 || endOfParagraph) {
+ if(find(texts.back().modifiers.begin(), texts.back().modifiers.end(), "paragraph") == texts.back().modifiers.end()) {
+ texts.back().modifiers.push_back("paragraph");
+ }
+ endOfParagraph = false;
+ }
+
+ // Variable to accumulate unterminated spans
+ std::vector<string> spans;
+ bool spansChanged = false;
+ // Iterate over text
+ for(auto i = text.begin(); i != text.end(); i++) {
+ if(*i != '<') {
+ if(spansChanged) {
+ spansChanged = false;
+ if(!texts.back().text.empty()) {
+ texts.push_back(getEmptyText(key));
+ }
+ for(string modifier : spans) {
+ if(find(texts.back().modifiers.begin(), texts.back().modifiers.end(), modifier) == texts.back().modifiers.end()) {
+ texts.back().modifiers.push_back(modifier);
+ }
+ }
+ }
+ texts.back().text += *i;
+ }
+ else {
+ string span;
+ for(; i != text.end(); i++) {
+ span.push_back(*i);
+ if(*i == '>') {
+ // The end of the span will be "</span>".
+ if(span == "</span>") {
+ if(! spans.empty()) {
+ spans.pop_back();
+ spansChanged = true;
+ }
+ } else {
+ // The span will be formatted "<span class=\"NAME\">"
+ // We want just the NAME
+ size_t start = span.find_first_of('"')+1;
+ size_t end = span.find_last_of('"');
+ if(start > 0) {
+ spans.push_back(span.substr(start, end-start));
+ spansChanged = true;
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+ endOfParagraph = (text[text.length()-1] == '\n');
+ }
+ return texts;
+}
diff --git a/libbible.h b/libbible.h
new file mode 100644
index 0000000..d21639a
--- /dev/null
+++ b/libbible.h
@@ -0,0 +1,98 @@
+#include <string>
+#include <vector>
+#include <map>
+
+namespace libbible {
+
+ struct text {
+ int chapter;
+ int verse;
+ std::string book;
+ std::string bookShort;
+ std::string text;
+ std::vector<std::string> modifiers; // e.g., paragraph, line indent0, divineName, wordsOfJesus
+ };
+
+ struct passage {
+ std::string modName;
+ std::string book;
+ std::string bookShort;
+ std::string reference;
+ int chapterStart;
+ int verseStart;
+ int chapterEnd;
+ int verseEnd;
+ };
+
+ /*
+ * @return Map of modName to supported books
+ */
+ std::map<std::string, std::vector<std::string>> getModules(void);
+
+ /*
+ * @return Vector of valid single full-chapter passages for a book
+ */
+ std::vector<struct passage> getPassages(std::string modName, std::string book);
+
+ /*
+ * @return Text for a passage
+ */
+ std::vector<struct text> getText(struct passage pass);
+
+ /**************************
+ * Methods dealing with mods
+ ***************************/
+
+ class Status {
+ public:
+ void update(unsigned long totalBytes, unsigned long completedBytes, std::string message);
+ };
+
+ /**
+ * @param status Status update method is called asynchronously as download progresses
+ * @return A mapping from language to bible version names
+ */
+ std::map<std::string, std::vector<std::string>> downloadModsAvailable(Status *status = nullptr);
+
+ /**
+ * Cancel an in-progress download
+ */
+ void terminateDownload(void);
+
+ /**
+ * @param language The language of the mod to install as provided from downloadModsAvailable
+ * @param name The name of the bible version as provided from downloadModsAvailable
+ * @see downloadModsAvailable()
+ */
+ void installModFromInternet(std::string language, std::string name);
+
+ /**
+ * @param filename Path to the .zip compressed module to be installed
+ */
+ void installModFromZip(std::string filename);
+
+ /**
+ * @param modname The name of the module to be removed
+ */
+ void uninstallMod(std::string modname);
+
+ /******************************
+ * Methods dealing with settings
+ *******************************/
+
+ /*
+ * From already established code, valid and useful values are:
+ * int fontsize: the last used size of the font
+ * string passage: the last looked-up passage
+ * string module: the last used module
+ */
+
+ void settingsWrite(std::string key, std::string value);
+
+ std::string settingsRead(std::string key);
+
+ void settingsWriteInt(std::string key, int value);
+
+ int settingsReadInt(std::string key);
+
+}
diff --git a/mods.cc b/mods.cc
new file mode 100644
index 0000000..c30fce4
--- /dev/null
+++ b/mods.cc
@@ -0,0 +1,126 @@
+#include "libbible.h"
+#include <sword/swmgr.h>
+#include <sword/swmodule.h>
+#include <sword/installmgr.h>
+#include <sword/filemgr.h>
+#include <sword/remotetrans.h>
+
+using namespace std;
+
+class myStatusReporter : public sword::StatusReporter {
+ public:
+ myStatusReporter(libbible::Status *status);
+ virtual ~myStatusReporter();
+ virtual void preStatus(long totalBytes, long completedBytes, const char *message);
+ virtual void update(unsigned long totalBytes, unsigned long completedBytes);
+ protected:
+ libbible::Status *status;
+ string message;
+};
+
+myStatusReporter::myStatusReporter(libbible::Status *s) {
+ status = s;
+}
+
+myStatusReporter::~myStatusReporter() {};
+
+void libbible::Status::update(unsigned long totalBytes, unsigned long completedBytes, string message) {}
+
+void myStatusReporter::preStatus(long totalBytes, long completedBytes, const char *msg) {
+ message = string(msg);
+ status->update((unsigned long) totalBytes, (unsigned long) completedBytes, message);
+}
+
+void myStatusReporter::update(unsigned long totalBytes, unsigned long completedBytes) {
+ status->update(totalBytes, completedBytes, message);
+}
+
+string basedir = (getenv("HOME")) + string("/.sword/");
+sword::InstallMgr *installMgr = new sword::InstallMgr((basedir + std::string("InstallMgr")).c_str(), nullptr);
+map<string, vector<pair<string, sword::InstallSource *>>> installSources;
+
+map<string, vector<string>> libbible::downloadModsAvailable(libbible::Status *status) {
+ installSources.clear();
+ mkdir((basedir + std::string("mods.d/")).c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+ mkdir((basedir + std::string("modules/")).c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+ myStatusReporter *msr = nullptr;
+ if(status) {
+ msr = new myStatusReporter(status);
+ }
+ free(installMgr);
+ installMgr = new sword::InstallMgr((basedir + std::string("InstallMgr")).c_str(), msr);
+ installMgr->setUserDisclaimerConfirmed(true);
+ string confpath = basedir + string("InstallMgr/InstallMgr.conf");
+ if(! sword::FileMgr::existsFile(confpath.c_str())) {
+ // Lifted directly from xiphos
+ sword::FileMgr::createParent(confpath.c_str());
+ sword::SWConfig config(confpath.c_str());
+ sword::InstallSource is("FTP");
+ is.caption = "CrossWire";
+ is.source = "ftp.crosswire.org";
+ is.directory = "/pub/sword/raw";
+ config["General"]["PassiveFTP"] = "true";
+ config["Sources"]["FTPSource"] = is.getConfEnt();
+ config.save();
+ }
+ installMgr->readInstallConf();
+ installMgr->refreshRemoteSourceConfiguration();
+ map<string, vector<string>> modsAvailable;
+ //printf("Getting langs...\n");
+ for(auto src : installMgr->sources) {
+ if(src.second->getMgr()->Modules.empty()) {
+ //printf("Refreshing remote source: %s\n", src.second->getConfEnt().c_str());
+ installMgr->refreshRemoteSource(src.second);
+ }
+ for(auto mod : src.second->getMgr()->Modules) {
+ auto *curMod = mod.second;
+ string type(curMod->getType());
+ if(type == "Biblical Texts") {
+ string language(curMod->getLanguage());
+ //printf("Got language %s\n", language.c_str());
+ vector<string> newMods;
+ vector<pair<string, sword::InstallSource *>> newSources;
+ // emplace only adds if key is unique
+ modsAvailable.emplace(language, newMods);
+ installSources.emplace(language, newSources);
+ modsAvailable[language].push_back(string(curMod->getName()));
+ pair<string, sword::InstallSource *> p(string(curMod->getName()), src.second);
+ installSources[language].push_back(p);
+ }
+ }
+ }
+ return modsAvailable;
+}
+
+void libbible::terminateDownload() {
+ installMgr->terminate();
+}
+
+void libbible::installModFromInternet(string language, string name) {
+ // Searching through map<string, vector<pair<string, sword::InstallSource *>>> installSources;
+ for (pair<string, sword::InstallSource *> p : installSources[language]) {
+ if(p.first == name) {
+ sword::SWMgr mgr(basedir.c_str());
+ installMgr->installModule(&mgr, 0, name.c_str(), p.second);
+ break;
+ }
+ }
+}
+
+void libbible::installModFromZip(string filename) {
+ // So... turns out it's a mite unsupported to install from a .zip
+ // Here's the deal. We do a syscall to unzip. We fancy like that.
+ // TODO: Use the ZipCompress module from SWORD instead.
+ string command = "unzip -o " + filename + " -d " + basedir + "&> /dev/null";
+ if(system(command.c_str())) {
+ //Uh oh...
+ printf("Something bad happened when unpacking %s\n", filename.c_str());
+ }
+
+}
+
+void libbible::uninstallMod(string modname) {
+ sword::SWMgr mgr(basedir.c_str());
+ sword::ModMap::iterator it = mgr.Modules.find(modname.c_str());
+ installMgr->removeModule(&mgr, it->second->getName());
+}
diff --git a/modules/ESV2011.zip b/modules/ESV2011.zip
new file mode 100644
index 0000000..b037436
--- /dev/null
+++ b/modules/ESV2011.zip
Binary files differ
diff --git a/modules/JPS.zip b/modules/JPS.zip
new file mode 100644
index 0000000..4f09ff8
--- /dev/null
+++ b/modules/JPS.zip
Binary files differ
diff --git a/readme b/readme
new file mode 100644
index 0000000..97cccf1
--- /dev/null
+++ b/readme
@@ -0,0 +1 @@
+This library provides a simplified interface to the SWORD project.
diff --git a/settings.cc b/settings.cc
new file mode 100644
index 0000000..848e22f
--- /dev/null
+++ b/settings.cc
@@ -0,0 +1,23 @@
+#include "libbible.h"
+#include <sword/swconfig.h>
+
+std::string path = (std::getenv("HOME")) + std::string("/.sword/libbible.conf");
+sword::SWConfig config(path.c_str());
+
+void libbible::settingsWrite(std::string key, std::string value) {
+ config["General"][key.c_str()] = sword::SWBuf(value.c_str());
+ config.save();
+}
+
+std::string libbible::settingsRead(std::string key) {
+ return config["General"][key.c_str()].c_str();
+}
+
+void libbible::settingsWriteInt(std::string key, int value) {
+ config["General"][key.c_str()] = sword::SWBuf(std::to_string(value).c_str());
+ config.save();
+}
+
+int libbible::settingsReadInt(std::string key) {
+ return atoi(config["General"][key.c_str()].c_str());
+}
diff --git a/testLibbible.cc b/testLibbible.cc
new file mode 100644
index 0000000..ad51c44
--- /dev/null
+++ b/testLibbible.cc
@@ -0,0 +1,206 @@
+//#include <libbible.h>
+#include "libbible.h"
+#include <string>
+#include <map>
+#include <vector>
+#include <cppunit/TestCase.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/ui/text/TextTestRunner.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+#include <cppunit/TestResult.h>
+#include <cppunit/TestResultCollector.h>
+#include <cppunit/TestRunner.h>
+#include <cppunit/BriefTestProgressListener.h>
+#include <cppunit/CompilerOutputter.h>
+#include <cppunit/XmlOutputter.h>
+#include <netinet/in.h>
+#include <iostream>
+
+using namespace CppUnit;
+using namespace std;
+
+//-----------------------------------------------------------------------------
+
+class TestLibbible : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE(TestLibbible);
+ CPPUNIT_TEST(testGetModules);
+ CPPUNIT_TEST(testGetPassages);
+ CPPUNIT_TEST(testGetText);
+ CPPUNIT_TEST(testSettings);
+ CPPUNIT_TEST_SUITE_END();
+
+ //public:
+ //void setUp(void);
+ //void tearDown(void);
+
+ protected:
+ void testGetModules(void);
+ void testGetPassages(void);
+ void testGetText(void);
+ void testSettings(void);
+
+};
+
+//-----------------------------------------------------------------------------
+
+void TestLibbible::testGetModules(void) {
+ map<string, vector<string>> mods = libbible::getModules();
+ for(auto pair : mods) {
+ libbible::uninstallMod(pair.first);
+ }
+ CPPUNIT_ASSERT(libbible::getModules().empty());
+ libbible::installModFromZip("modules/ESV2011.zip");
+ libbible::installModFromZip("modules/JPS.zip");
+ mods = libbible::getModules();
+ CPPUNIT_ASSERT(mods.find("ESV2011") != mods.end());
+ CPPUNIT_ASSERT(mods["ESV2011"].size() == 66);
+ CPPUNIT_ASSERT(mods["ESV2011"][7] == "Ruth");
+ CPPUNIT_ASSERT(mods["ESV2011"][42] == "John");
+ CPPUNIT_ASSERT(mods.find("JPS") != mods.end());
+ CPPUNIT_ASSERT(mods["JPS"].size() == 39);
+}
+
+void TestLibbible::testGetPassages(void) {
+ auto passages = libbible::getPassages("ESV2011", "Romans");
+ CPPUNIT_ASSERT(passages[0].modName == "ESV2011");
+ CPPUNIT_ASSERT(passages[0].book == "Romans");
+ CPPUNIT_ASSERT(passages[0].bookShort == "Rom");
+ CPPUNIT_ASSERT(passages[0].chapterStart == 1);
+ CPPUNIT_ASSERT(passages[0].verseStart == 1);
+ CPPUNIT_ASSERT(passages[0].chapterEnd == 1);
+ CPPUNIT_ASSERT(passages[0].verseEnd == 32);
+ CPPUNIT_ASSERT(passages.size() == 16);
+}
+
+vector<pair<int, int>> getChapVerses(std::vector<libbible::text> text) {
+ vector<pair<int, int>> chapVerses;
+ for(auto tex : text) {
+ //printf("Text is: `%s`\n", tex.text.c_str());
+ //for(auto modifier : tex.modifiers) {
+ // printf("\tModifiers include: %s\n", modifier.c_str());
+ //}
+ if(chapVerses.empty() ||
+ chapVerses.back().first != tex.chapter ||
+ chapVerses.back().second != tex.verse) {
+ chapVerses.push_back(pair<int, int>(tex.chapter, tex.verse));
+ }
+ }
+ return chapVerses;
+}
+
+void TestLibbible::testGetText(void) {
+ libbible::passage pass;
+ pass.modName = "ESV2011";
+ pass.bookShort = "Matt";
+ pass.chapterStart = 3;
+ pass.verseStart = 16;
+ pass.chapterEnd = 4;
+ pass.verseEnd = 7;
+ auto text = libbible::getText(pass);
+ // Verify that it includes every verse (3:16-17 + 4:1-7)
+ vector<pair<int, int>> chapVerses = getChapVerses(text);
+ vector<pair<int, int>> shouldContain = vector<pair<int, int>>({pair<int, int>(3, 16),
+ pair<int, int>(3, 17),
+ pair<int, int>(4, 1),
+ pair<int, int>(4, 2),
+ pair<int, int>(4, 3),
+ pair<int, int>(4, 4),
+ pair<int, int>(4, 5),
+ pair<int, int>(4, 6),
+ pair<int, int>(4, 7)});
+ CPPUNIT_ASSERT(chapVerses == shouldContain);
+ libbible::passage pass2;
+ pass2.modName = "ESV2011";
+ pass2.book = "John";
+ pass2.chapterStart = 3;
+ pass2.verseStart = 16;
+ pass2.chapterEnd = 3;
+ pass2.verseEnd = 16;
+ text = libbible::getText(pass2);
+ string allText;
+ for(auto tex : text) {
+ allText += tex.text;
+ }
+ //printf("Text is: `%s`\n", allText.c_str());
+ CPPUNIT_ASSERT(allText == " “For God so loved the world, that he gave his only Son, that whoever believes in him should not perish but have eternal life. ");
+
+ text = libbible::getText(libbible::passage{.modName = "ESV2011", .reference="John 3:3"});
+ allText.clear();
+ for(auto tex : text) {
+ allText += tex.text;
+ }
+ //printf("Text is: `%s`\n", allText.c_str());
+ CPPUNIT_ASSERT(allText == "Jesus answered him, “Truly, truly, I say to you, unless one is born again he cannot see the kingdom of God.” ");
+
+ text = libbible::getText(libbible::passage{.modName = "ESV2011", .reference="Gal 5:22-23"});
+ chapVerses = getChapVerses(text);
+ shouldContain = vector<pair<int, int>>({pair<int, int>(5, 22), pair<int, int>(5, 23)});
+ CPPUNIT_ASSERT(chapVerses == shouldContain);
+
+ text = libbible::getText(libbible::passage{.modName = "ESV2011", .reference="1 cor 1:31-2:1"});
+ chapVerses = getChapVerses(text);
+ shouldContain = vector<pair<int, int>>({pair<int, int>(1, 31), pair<int, int>(2, 1)});
+ CPPUNIT_ASSERT(chapVerses == shouldContain);
+
+ text = libbible::getText(libbible::passage{.modName = "ESV2011", .reference="ps 14-15"});
+ chapVerses = getChapVerses(text);
+ shouldContain = vector<pair<int, int>>({pair<int, int>(14, 1),
+ pair<int, int>(14, 2),
+ pair<int, int>(14, 3),
+ pair<int, int>(14, 4),
+ pair<int, int>(14, 5),
+ pair<int, int>(14, 6),
+ pair<int, int>(14, 7),
+ pair<int, int>(15, 1),
+ pair<int, int>(15, 2),
+ pair<int, int>(15, 3),
+ pair<int, int>(15, 4),
+ pair<int, int>(15, 5)});
+ CPPUNIT_ASSERT(chapVerses == shouldContain);
+}
+
+void TestLibbible::testSettings(void) {
+ libbible::settingsWrite("test", "foo");
+ CPPUNIT_ASSERT(libbible::settingsRead("test") == "foo");
+ libbible::settingsWrite("test", "bar");
+ CPPUNIT_ASSERT(libbible::settingsRead("test") == "bar");
+ libbible::settingsWriteInt("test", 5);
+ CPPUNIT_ASSERT(libbible::settingsReadInt("test") == 5);
+ libbible::settingsWrite("test", "");
+ CPPUNIT_ASSERT(libbible::settingsRead("test") == "");
+}
+//-----------------------------------------------------------------------------
+
+CPPUNIT_TEST_SUITE_REGISTRATION( TestLibbible );
+
+int main(int argc, char* argv[]) {
+ // informs test-listener about testresults
+ CPPUNIT_NS::TestResult testresult;
+
+ // register listener for collecting the test-results
+ CPPUNIT_NS::TestResultCollector collectedresults;
+ testresult.addListener (&collectedresults);
+
+ // register listener for per-test progress output
+ CPPUNIT_NS::BriefTestProgressListener progress;
+ testresult.addListener (&progress);
+
+ // insert test-suite at test-runner by registry
+ CPPUNIT_NS::TestRunner testrunner;
+ testrunner.addTest (CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest ());
+ testrunner.run(testresult);
+
+ // output results in compiler-format
+ CPPUNIT_NS::CompilerOutputter compileroutputter(&collectedresults, std::cerr);
+ compileroutputter.write ();
+
+ // Output XML for Jenkins CPPunit plugin
+ //ofstream xmlFileOut("testLibbibleResults.xml");
+ //XmlOutputter xmlOut(&collectedresults, xmlFileOut);
+ //xmlOut.write();
+
+ // return 0 if tests were successful
+ return collectedresults.wasSuccessful() ? 0 : 1;
+}