diff options
| author | Miguel Dalberto Pedro <miguel.pedro@precisioninno.com> | 2026-10-29 17:55:36 -0300 |
|---|---|---|
| committer | Miguel Dalberto Pedro <miguel.pedro@precisioninno.com> | 2026-10-29 17:55:36 -0300 |
| commit | 915478aef931cb57fe56fe568ab4982454a29812 (patch) | |
| tree | 33057f4773852d99e8cd7ad92d568e79f34847ee /src/pms_main.c | |
| download | pms-915478aef931cb57fe56fe568ab4982454a29812.tar.gz pms-915478aef931cb57fe56fe568ab4982454a29812.zip | |
Diffstat (limited to 'src/pms_main.c')
| -rw-r--r-- | src/pms_main.c | 297 |
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; +} |
