aboutsummaryrefslogtreecommitdiff
path: root/src/pms_main.c
diff options
context:
space:
mode:
authorMiguel Dalberto Pedro <miguel.pedro@precisioninno.com>2026-10-29 17:55:36 -0300
committerMiguel Dalberto Pedro <miguel.pedro@precisioninno.com>2026-10-29 17:55:36 -0300
commit915478aef931cb57fe56fe568ab4982454a29812 (patch)
tree33057f4773852d99e8cd7ad92d568e79f34847ee /src/pms_main.c
downloadpms-915478aef931cb57fe56fe568ab4982454a29812.tar.gz
pms-915478aef931cb57fe56fe568ab4982454a29812.zip
Initial commit: Padre Matos Soares (1932) Bible corpus and pms CLIHEADmaster
Diffstat (limited to 'src/pms_main.c')
-rw-r--r--src/pms_main.c297
1 files changed, 297 insertions, 0 deletions
diff --git a/src/pms_main.c b/src/pms_main.c
new file mode 100644
index 0000000..1924b6a
--- /dev/null
+++ b/src/pms_main.c
@@ -0,0 +1,297 @@
+#include "pms_config.h"
+#include "pms_data.h"
+#include "pms_ref.h"
+#include "pms_render.h"
+#include <ctype.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+static const char *usage_str =
+ "usage: pms [flags] [reference...]\n"
+ "\n"
+ "Bíblia Sagrada — Tradução e Comentários do Padre Manuel de Matos Soares (1932)\n"
+ "\n"
+ "Flags:\n"
+ " -A num show num verses of context after matching verses\n"
+ " -B num show num verses of context before matching verses\n"
+ " -C show matching verses in context of the chapter\n"
+ " -c show Padre Matos Soares's commentaries/footnotes for matching verses\n"
+ " -e highlighting of chapters and verse numbers\n"
+ " (default when output is a TTY)\n"
+ " -p output to less with chapter grouping, spacing, indentation,\n"
+ " and line wrapping\n"
+ " (default when output is a TTY)\n"
+ " -W no line wrap\n"
+ " -l list books\n"
+ " -h show help\n"
+ "\n"
+ "Reference:\n"
+ " <Book>\n"
+ " Individual book (e.g. \"Gn\", \"1Rs\", \"1Sm\", \"Mt\")\n"
+ " <Book>:<Chapter> or <Book> <Chapter>\n"
+ " Individual chapter of a book (e.g. \"Gn:1\", \"Sl 23\")\n"
+ " <Book>:<Chapter>:<Verse>[,<Verse>]...\n"
+ " Individual verse(s) of a specific chapter of a book (e.g. \"Jo 3:16\", \"Gn 1:1,3,5\")\n"
+ " <Book>:<Chapter>-<Chapter>\n"
+ " Range of chapters in a book (e.g. \"Gn 1-3\")\n"
+ " <Book>:<Chapter>:<Verse>-<Verse>\n"
+ " Range of verses in a book chapter (e.g. \"Gn 1:1-5\")\n"
+ " <Book>:<Chapter>:<Verse>-<Chapter>:<Verse>\n"
+ " Range of chapters and verses in a book (e.g. \"Gn 1:26-2:3\")\n"
+ "\n"
+ " /<Search>\n"
+ " All verses that match a pattern (case- and accent-insensitive)\n"
+ " <Book>/<Search>\n"
+ " All verses in a book that match a pattern\n"
+ " <Book>:<Chapter>/<Search>\n"
+ " All verses in a chapter of a book that match a pattern\n";
+
+static int get_terminal_width(void) {
+ struct winsize ws;
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0) {
+ return (ws.ws_col < 80) ? ws.ws_col : 80;
+ }
+ return 80;
+}
+
+static FILE *open_pager(pid_t *pager_pid) {
+ *pager_pid = -1;
+ int pipefd[2];
+ if (pipe(pipefd) != 0) {
+ return stdout;
+ }
+
+ pid_t pid = fork();
+ if (pid < 0) {
+ close(pipefd[0]);
+ close(pipefd[1]);
+ return stdout;
+ }
+
+ if (pid == 0) {
+ /* Child: run pager */
+ close(pipefd[1]);
+ if (dup2(pipefd[0], STDIN_FILENO) < 0) {
+ _exit(1);
+ }
+ close(pipefd[0]);
+
+ const char *pager = getenv("PAGER");
+ if (pager && pager[0] != '\0') {
+ execlp(pager, pager, (char *)NULL);
+ }
+ execlp("less", "less", "-J", "-I", "-R", "-f", "-", (char *)NULL);
+ execlp("more", "more", (char *)NULL);
+ _exit(1);
+ }
+
+ /* Parent */
+ close(pipefd[0]);
+ *pager_pid = pid;
+ FILE *fp = fdopen(pipefd[1], "w");
+ if (!fp) {
+ close(pipefd[1]);
+ *pager_pid = -1;
+ return stdout;
+ }
+ return fp;
+}
+
+static void close_pager(FILE *fp, pid_t pager_pid) {
+ if (fp && fp != stdout) {
+ fclose(fp);
+ }
+ if (pager_pid > 0) {
+ int status = 0;
+ waitpid(pager_pid, &status, 0);
+ }
+}
+
+static int run_query(const char *query_str, const pms_config *config, bool use_pager) {
+ pms_ref *ref = pms_newref();
+ if (!ref) {
+ return 1;
+ }
+ if (pms_parse_ref(query_str, ref) != 0) {
+ fprintf(stderr, "pms: referência inválida: %s\n", query_str);
+ pms_freeref(ref);
+ return 1;
+ }
+
+ pid_t pager_pid = -1;
+ FILE *out = stdout;
+ if (use_pager) {
+ out = open_pager(&pager_pid);
+ }
+
+ pms_render(ref, config, out);
+
+ if (use_pager) {
+ close_pager(out, pager_pid);
+ }
+ pms_freeref(ref);
+ return 0;
+}
+
+int main(int argc, char *argv[]) {
+ bool is_tty = isatty(STDOUT_FILENO) != 0;
+ signal(SIGPIPE, SIG_IGN);
+
+ pms_config config = {
+ .before_context = 0,
+ .after_context = 0,
+ .chapter_context = false,
+ .show_notes = false,
+ .highlighting = is_tty,
+ .pretty = is_tty,
+ .maximum_line_length = get_terminal_width(),
+ };
+
+ bool list_books = false;
+ bool no_wrap = false;
+
+ int opt;
+ while ((opt = getopt(argc, argv, "A:B:CcepwWlhn")) != -1) {
+ switch (opt) {
+ case 'A': {
+ char *endptr = NULL;
+ errno = 0;
+ long val = strtol(optarg, &endptr, 10);
+ if (errno != 0 || endptr == optarg || *endptr != '\0' || val < 0) {
+ fprintf(stderr, "pms: número inválido para -A: %s\n\n%s", optarg, usage_str);
+ return 1;
+ }
+ config.after_context = (int)val;
+ break;
+ }
+ case 'B': {
+ char *endptr = NULL;
+ errno = 0;
+ long val = strtol(optarg, &endptr, 10);
+ if (errno != 0 || endptr == optarg || *endptr != '\0' || val < 0) {
+ fprintf(stderr, "pms: número inválido para -B: %s\n\n%s", optarg, usage_str);
+ return 1;
+ }
+ config.before_context = (int)val;
+ break;
+ }
+ case 'C':
+ config.chapter_context = true;
+ break;
+ case 'c':
+ case 'n':
+ config.show_notes = true;
+ break;
+ case 'e':
+ config.highlighting = true;
+ break;
+ case 'p':
+ config.pretty = true;
+ break;
+ case 'W':
+ case 'w':
+ no_wrap = true;
+ break;
+ case 'l':
+ list_books = true;
+ break;
+ case 'h':
+ printf("%s", usage_str);
+ return 0;
+ default:
+ fprintf(stderr, "\n%s", usage_str);
+ return 1;
+ }
+ }
+
+ if (no_wrap) {
+ config.maximum_line_length = 0;
+ }
+
+ if (list_books) {
+ for (int i = 0; i < pms_books_length; i++) {
+ printf("%s (%s)\n", pms_books[i].name, pms_books[i].abbr);
+ }
+ return 0;
+ }
+
+ int rem_argc = argc - optind;
+ char **rem_argv = argv + optind;
+
+ if (rem_argc == 0) {
+ /* Interactive REPL mode */
+ char line[1024];
+ while (true) {
+ if (isatty(STDIN_FILENO)) {
+ printf("pms> ");
+ fflush(stdout);
+ }
+ if (!fgets(line, sizeof(line), stdin)) {
+ if (isatty(STDIN_FILENO)) {
+ printf("\n");
+ }
+ break;
+ }
+ size_t len = strlen(line);
+ while (len > 0 && isspace((unsigned char)line[len - 1])) {
+ line[--len] = '\0';
+ }
+ char *p = line;
+ while (*p && isspace((unsigned char)*p)) {
+ p++;
+ }
+ if (*p == '\0') {
+ continue;
+ }
+ if (strcmp(p, "q") == 0 || strcmp(p, "quit") == 0 ||
+ strcmp(p, "exit") == 0 || strcmp(p, "sair") == 0) {
+ break;
+ }
+ if (strcmp(p, ":c") == 0 || strcmp(p, "-c") == 0) {
+ config.show_notes = !config.show_notes;
+ printf("Comentários: %s\n", config.show_notes ? "activados" : "desactivados");
+ continue;
+ }
+ if (strcmp(p, "-l") == 0 || strcmp(p, ":l") == 0) {
+ for (int i = 0; i < pms_books_length; i++) {
+ printf("%s (%s)\n", pms_books[i].name, pms_books[i].abbr);
+ }
+ continue;
+ }
+ if (strcmp(p, "-h") == 0 || strcmp(p, ":h") == 0 || strcmp(p, "help") == 0) {
+ printf("%s", usage_str);
+ continue;
+ }
+ run_query(p, &config, is_tty && config.pretty);
+ }
+ return 0;
+ }
+
+ /* Join remaining CLI arguments into a single reference string */
+ size_t total_len = 0;
+ for (int i = 0; i < rem_argc; i++) {
+ total_len += strlen(rem_argv[i]) + 2;
+ }
+ char *joined = malloc(total_len + 1);
+ if (!joined) {
+ return 1;
+ }
+ joined[0] = '\0';
+ for (int i = 0; i < rem_argc; i++) {
+ if (i > 0) {
+ strcat(joined, " ");
+ }
+ strcat(joined, rem_argv[i]);
+ }
+
+ int rc = run_query(joined, &config, is_tty && config.pretty);
+ free(joined);
+ return rc;
+}