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 | +++ |
A | LICENCE | | | 13 | +++++++++++++ |
A | Makefile | | | 48 | ++++++++++++++++++++++++++++++++++++++++++++++++ |
A | README | | | 17 | +++++++++++++++++ |
A | bibleverse.1 | | | 22 | ++++++++++++++++++++++ |
A | bibleverse.h | | | 18 | ++++++++++++++++++ |
A | book.c | | | 80 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | config.mk | | | 18 | ++++++++++++++++++ |
A | main.c | | | 84 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | util.c | | | 25 | +++++++++++++++++++++++++ |
A | verse.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;
+}
+