diff options
-rw-r--r-- | Makefile | 39 | ||||
-rw-r--r-- | bible.cc | 182 | ||||
-rw-r--r-- | libbible.cc | 218 | ||||
-rw-r--r-- | libbible.h | 98 | ||||
-rw-r--r-- | mods.cc | 126 | ||||
-rw-r--r-- | modules/ESV2011.zip | bin | 0 -> 12087753 bytes | |||
-rw-r--r-- | modules/JPS.zip | bin | 0 -> 1170889 bytes | |||
-rw-r--r-- | readme | 1 | ||||
-rw-r--r-- | settings.cc | 23 | ||||
-rw-r--r-- | testLibbible.cc | 206 |
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); + +} @@ -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 Binary files differnew file mode 100644 index 0000000..b037436 --- /dev/null +++ b/modules/ESV2011.zip diff --git a/modules/JPS.zip b/modules/JPS.zip Binary files differnew file mode 100644 index 0000000..4f09ff8 --- /dev/null +++ b/modules/JPS.zip @@ -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; +} |