bibleverse

Print verse(s) from the Bible
git clone git://git.janpasierb.com/bibleverse.git
Log | Files | Refs | README

commit 30c03ff6c0b22c94e0ad3794379d7f21f152f25f
Author: JP <JP@JP.com>
Date:   Fri, 19 Nov 2021 18:22:19 +0000

Initial commit, ver. 1.0

Diffstat:
A.gitignore | 3+++
ALICENCE | 13+++++++++++++
AMakefile | 48++++++++++++++++++++++++++++++++++++++++++++++++
AREADME | 17+++++++++++++++++
Abibleverse.1 | 22++++++++++++++++++++++
Abibleverse.h | 18++++++++++++++++++
Abook.c | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aconfig.mk | 18++++++++++++++++++
Amain.c | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Autil.c | 25+++++++++++++++++++++++++
Averse.c | 214+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
11 files changed, 542 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,3 @@ +*.db +*.o +tags diff --git a/LICENCE b/LICENCE @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + +Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> + +Everyone is permitted to copy and distribute verbatim or modified +copies of this license document, and changing it is allowed as long +as the name is changed. + +DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/Makefile b/Makefile @@ -0,0 +1,48 @@ +include config.mk + +SRC = main.c book.c verse.c util.c +OBJ = ${SRC:.c=.o} + +all: options bibleverse + +options: + @echo bibleverse build options: + @echo "CFLAGS = ${CFLAGS}" + @echo "CC = ${CC}" + +.c.o: + ${CC} -c ${CFLAGS} $< + +${OBJ}: bibleverse.h config.mk + +bibleverse: ${OBJ} + ${CC} -o $@ ${OBJ} ${LDFLAGS} + +clean: + rm -f bibleverse ${OBJ} bibleverse-${VERSION}.tar.gz + +dist: clean + mkdir -p bibleverse-${VERSION} + cp -R LICENCE Makefile README bibleverse.1 bibleverse.db\ + bibleverse.h ${SRC} bibleverse-${VERSION} + tar -cf bibleverse-${VERSION}.tar bibleverse-${VERSION} + gzip bibleverse-${VERSION}.tar + rm -rf bibleverse-${VERSION} + +install: all + mkdir -p ${PREFIX}/bin + cp -f bibleverse ${PREFIX}/bin + chmod 755 ${PREFIX}/bin/bibleverse + mkdir -p ${SHAREPREFIX}/bibleverse + cp -r bibleverse.db ${SHAREPREFIX}/bibleverse + chmod 555 ${SHAREPREFIX}/bibleverse/bibleverse.db + mkdir -p ${MANPREFIX}/man1 + sed "s/VERSION/${VERSION}/g" < bibleverse.1 > ${MANPREFIX}/man1/bibleverse.1 + chmod 644 ${MANPREFIX}/man1/bibleverse.1 + gzip ${MANPREFIX}/man1/bibleverse.1 + +uninstall: + rm -rf ${PREFIX}/bin/bibleverse ${MANPREFIX}/man1/bibleverse.1\ + ${SHAREPREFIX}/bibleverse/bibleverse.db + +.PHONY: all options clean dist install uninstall diff --git a/README b/README @@ -0,0 +1,17 @@ +bibleverse +========== +Displays verses from the bible (King James Version). + +Requirements +------------ +You will need sqlite3 installed. + +Installation +------------ +Edit config.mk as necessary then enter the following command to build and install bibleverse (as root, if necessary): + + make clean install + +Running bibleverse +------------------ +Please consult the manpage or run bibleverse with no arguments to see usage help-text diff --git a/bibleverse.1 b/bibleverse.1 @@ -0,0 +1,22 @@ +.\" Manpage for bibleverse +.TH man 1 "18 November 2021" "1.0" "bibleverse man page" +.SH NAME +bibleverse \- Display bible verse(s) +.SH SYNOPSIS +.B bibleverse +<book> <chapter>[:verse[-verse]] +.SH DESCRIPTION +Display bible verse(s) (King James Version) for either: the whole chapter, a single verse from a chapter or range of verses from a chapter +.SH OPTIONS +.B book +\- Full book name. If the book name is partial, a list of available books matching the input will be printed + +.B chapter +\- Chapter number of the chosen book + +.B verse +\- Verse of the chosen chapter or a range of verses in the format "verse_min-verse_max" +.SH BUGS +This software contains no bugs +.SH AUTHOR +JP diff --git a/bibleverse.h b/bibleverse.h @@ -0,0 +1,18 @@ +#include<sqlite3.h> + +struct book { + int id; + char* name; +}; + +struct verse { + int chapter; + int verse_min; + int verse_max; +}; + +int isNumber(char*); +int print_verses(int, char*, struct verse*, sqlite3*); +struct verse* parse_verse_numbers(char*); +struct book* find_book(char*, sqlite3*); + diff --git a/book.c b/book.c @@ -0,0 +1,80 @@ +#include<sqlite3.h> +#include<stdio.h> +#include<stdlib.h> +#include<string.h> +#include"bibleverse.h" + +#define BOOK_NAME_LEN 202 + +static char* book_sql = "SELECT id, name FROM books WHERE name LIKE ?1"; + +struct book* find_book(char* book_name, sqlite3* db) { + + sqlite3_stmt* res; + int rc = sqlite3_prepare_v2(db, book_sql, -1, &res, 0); + + if(rc == SQLITE_OK) { + char wildcarded_book[BOOK_NAME_LEN] = {0}; + + snprintf(wildcarded_book, sizeof(wildcarded_book), "%%%s%%", book_name); + + rc = sqlite3_bind_text(res, 1, wildcarded_book, -1, SQLITE_STATIC); + + if(rc != SQLITE_OK) { + fprintf(stderr, "Cannot bind argument: %s\n", sqlite3_errmsg(db)); + + return NULL; + } + } else { + fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db)); + + return NULL; + } + + int i = 0; + struct book* books; + + while(sqlite3_step(res) == SQLITE_ROW) { + if(!i) { + books = malloc(sizeof(struct book)); + } else { + books = realloc(books, sizeof(struct book) * (i + 1)); + } + + int id = sqlite3_column_int(res, 0); + char* name = (char*) sqlite3_column_text(res, 1); + + books[i].id = id; + books[i].name = (char*) malloc((strlen(name) + 1) * sizeof(char*)); + strcpy(books[i].name, name); + + ++i; + + if(!strcmp(name, book_name)) { + break; //I'm sinning here and assuming first result will be the one that matches in full + } + } + + if(i != 1) { + if(i == 0) { + fprintf(stderr, "No books found under the name \"%s\"\n", book_name); + } + else if(i > 1) { + fprintf(stderr, "There are multiple books matching the query \"%s\". Please narrow down your search:\n", book_name); + + for(int h = 0; h < i; h++) { + fprintf(stderr, "%s\n", books[h].name); + free(books[h].name); + } + + free(books); + } + + return NULL; + } + + sqlite3_finalize(res); + + return books; +} + diff --git a/config.mk b/config.mk @@ -0,0 +1,18 @@ +# bibleverse version +VERSION = 1.0 + +# Customise below config to suit your system + +# Paths +PREFIX = /usr/local +SHAREPREFIX = ${PREFIX}/share +MANPREFIX = ${SHAREPREFIX}/man + +LIBS = -lsqlite3 + +# Flags +CFLAGS = -std=c99 -pedantic -Wall +LDFLAGS = ${LIBS} + +# Compilers and Linkers +CC = cc diff --git a/main.c b/main.c @@ -0,0 +1,84 @@ +#include<sqlite3.h> +#include<stdlib.h> +#include<stdio.h> +#include"bibleverse.h" + +#define DB_NAME "/usr/local/share/bibleverse/bibleverse.db" +#define BOOK_NAME_LEN 200 + +static char* usage_err = "Usage: bibleverse <book> <chapter>[:verse[-verse]]\n"; + +int main(int argc, char** argv) { + + int is_book_numbered = 0; + sqlite3* db; + + if(argc > 4 || argc < 3) { + fprintf(stderr, usage_err); + + return 1; + } + + int rc = sqlite3_open(DB_NAME, &db); + + if(rc != SQLITE_OK) { + fprintf(stderr, "Cannot open database %s: %s\n", DB_NAME, sqlite3_errmsg(db)); + sqlite3_close(db); + + return 1; + } + + char book_name[BOOK_NAME_LEN] = {0}; + + if(isNumber(argv[1])) { + is_book_numbered = 1; + snprintf(book_name, sizeof(book_name), "%s %s", argv[1], argv[2]); + } else { + snprintf(book_name, sizeof(book_name), "%s", argv[1]); + } + + struct book* books = find_book(book_name, db); + + if(books == NULL) { + fprintf(stderr, "\nFailed to find a requested book\n"); + sqlite3_close(db); + + return 1; + } + + char* verse_numbers = argv[2 + is_book_numbered]; + + if(verse_numbers == NULL) { + fprintf(stderr, usage_err); + sqlite3_close(db); + + return 1; + } + + struct verse* ver = parse_verse_numbers(verse_numbers); + + if(ver == NULL) { + fprintf(stderr, "Failed to parse the verse (range) \"%s\"\n", verse_numbers); + sqlite3_close(db); + + return 1; + } + + rc = print_verses(books[0].id, books[0].name, ver, db); + + free(books[0].name); + free(books); + free(ver); + + if(rc) { + fprintf(stderr, "Failed to print requested verse(s)\n"); + sqlite3_close(db); + + return 1; + } + + sqlite3_close(db); + + return 0; +} + diff --git a/util.c b/util.c @@ -0,0 +1,25 @@ +#include<ctype.h> +#include<stdlib.h> +#include<string.h> +#include"bibleverse.h" + +int isNumber(char* string) { + int len = strlen(string); + char* termstr = malloc((len + 1) * sizeof(char*)); + char* termstr_orig = termstr; + strcpy(termstr, string); + termstr[len] = '\0'; + + while(*termstr != '\0') { + if(!isdigit(*termstr)) { + free(termstr_orig); + return 0; + } + ++termstr; + } + + free(termstr_orig); + + return 1; +} + diff --git a/verse.c b/verse.c @@ -0,0 +1,214 @@ +#include<sqlite3.h> +#include<stdio.h> +#include<string.h> +#include<stdlib.h> +#include<ctype.h> +#include"bibleverse.h" + +struct verse* parse_verse_numbers(char* numbers) { + struct verse* ver = malloc(sizeof *ver); + + if(isNumber(numbers)) { + ver->chapter = atoi(numbers); + ver->verse_min = 0; + ver->verse_max = 0; + } else { + const char book_delim[2] = ":"; + char* token; + + token = strtok(numbers, book_delim); + + if(token == NULL) { + return NULL; + } else { + if(isNumber(token)) { + ver->chapter = atoi(token); + + token = strtok(NULL, book_delim); + + if(token == NULL) { + return NULL; + } else { + if(isNumber(token)) { + ver->verse_min = atoi(token); + ver->verse_max = 0; + } else { + const char verse_delim[2] = "-"; + + token = strtok(token, verse_delim); + + if(token == NULL) { + return NULL; + } else { + if(isNumber(token)) { + ver->verse_min = atoi(token); + + token = strtok(NULL, verse_delim); + + if(token == NULL) { + return NULL; + } else { + if(isNumber(token)) { + ver->verse_max = atoi(token); + + token = strtok(NULL, verse_delim); + + if(token != NULL) { + return NULL; + } + } else { + return NULL; + } + } + } else { + return NULL; + } + } + } + } + } else { + return NULL; + } + } + } + + return ver; +} + +int print_verses(int book_id, char* book_name, struct verse* ver, sqlite3* db) { + const char* full_chapter_sql = + "SELECT chapter_id, verse_id, verse FROM verses WHERE book_id = ?1 AND chapter_id = ?2"; + const char* single_verse_sql = + "SELECT chapter_id, verse_id, verse FROM verses WHERE book_id = ?1 AND chapter_id = ?2 AND verse_id = ?3"; + const char* range_verse_sql = + "SELECT chapter_id, verse_id, verse FROM verses WHERE book_id = ?1 AND chapter_id = ?2 AND verse_id BETWEEN ?3 AND ?4"; + + int rc; + sqlite3_stmt* res; + + if(ver->verse_min == 0) { + rc = sqlite3_prepare_v2(db, full_chapter_sql, -1, &res, 0); + if(rc != SQLITE_OK) { + fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db)); + + return 1; + } + + rc = sqlite3_bind_int(res, 1, book_id); + if(rc != SQLITE_OK) { + fprintf(stderr, "Failed to bind argument: %s\n", sqlite3_errmsg(db)); + + return 1; + } + + rc = sqlite3_bind_int(res, 2, ver->chapter); + if(rc != SQLITE_OK) { + fprintf(stderr, "Failed to bind argument: %s\n", sqlite3_errmsg(db)); + + return 1; + } + } else { + if(ver->verse_max == 0) { + rc= sqlite3_prepare_v2(db, single_verse_sql, -1, &res, 0); + if(rc != SQLITE_OK) { + fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db)); + + return 1; + } + + rc = sqlite3_bind_int(res, 1, book_id); + if(rc != SQLITE_OK) { + fprintf(stderr, "Failed to bind argument: %s\n", sqlite3_errmsg(db)); + + return 1; + } + + rc = sqlite3_bind_int(res, 2, ver->chapter); + if(rc != SQLITE_OK) { + fprintf(stderr, "Failed to bind argument: %s\n", sqlite3_errmsg(db)); + + return 1; + } + + rc = sqlite3_bind_int(res, 3, ver->verse_min); + if(rc != SQLITE_OK) { + fprintf(stderr, "Failed to bind argument: %s\n", sqlite3_errmsg(db)); + + return 1; + } + } else { + if(ver->verse_min > ver->verse_max) { + fprintf(stderr, "Starting verse number cannot be larger than ending verse number\n"); + + return 1; + } + + rc= sqlite3_prepare_v2(db, range_verse_sql, -1, &res, 0); + if(rc != SQLITE_OK) { + fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db)); + + return 1; + } + + rc = sqlite3_bind_int(res, 1, book_id); + if(rc != SQLITE_OK) { + fprintf(stderr, "Failed to bind argument: %s\n", sqlite3_errmsg(db)); + + return 1; + } + + rc = sqlite3_bind_int(res, 2, ver->chapter); + if(rc != SQLITE_OK) { + fprintf(stderr, "Failed to bind argument: %s\n", sqlite3_errmsg(db)); + + return 1; + } + + rc = sqlite3_bind_int(res, 3, ver->verse_min); + if(rc != SQLITE_OK) { + fprintf(stderr, "Failed to bind argument: %s\n", sqlite3_errmsg(db)); + + return 1; + } + + rc = sqlite3_bind_int(res, 4, ver->verse_max); + if(rc != SQLITE_OK) { + fprintf(stderr, "Failed to bind argument: %s\n", sqlite3_errmsg(db)); + + return 1; + } + } + } + + int c = 0; + + while(sqlite3_step(res) == SQLITE_ROW) { + if(!c) { + fprintf(stdout, "\033[0;32m"); + fprintf(stdout, "Word of God\n\n"); + fprintf(stdout, "\033[0m"); + fprintf(stdout, "%s ", book_name); + fprintf(stdout, "\033[0;32m"); + fprintf(stdout, "%d:\n", sqlite3_column_int(res, 0)); + } + + fprintf(stdout, "\033[0;32m"); + fprintf(stdout, "%d", sqlite3_column_int(res, 1)); + fprintf(stdout, "\033[0m"); + fprintf(stdout, " %s ", sqlite3_column_text(res, 2)); + + ++c; + } + + sqlite3_finalize(res); + + if(c == 0) { + fprintf(stderr, "No verses found\n"); + return 1; + } + + fprintf(stdout, "\n"); + + return 0; +} +