#include "pms_config.h" #include "pms_data.h" #include "pms_ref.h" #include "pms_render.h" #include #include #include #include #include #include #include #include #include #include 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" " \n" " Individual book (e.g. \"Gn\", \"1Rs\", \"1Sm\", \"Mt\")\n" " : or \n" " Individual chapter of a book (e.g. \"Gn:1\", \"Sl 23\")\n" " ::[,]...\n" " Individual verse(s) of a specific chapter of a book (e.g. \"Jo 3:16\", \"Gn 1:1,3,5\")\n" " :-\n" " Range of chapters in a book (e.g. \"Gn 1-3\")\n" " ::-\n" " Range of verses in a book chapter (e.g. \"Gn 1:1-5\")\n" " ::-:\n" " Range of chapters and verses in a book (e.g. \"Gn 1:26-2:3\")\n" "\n" " /\n" " All verses that match a pattern (case- and accent-insensitive)\n" " /\n" " All verses in a book that match a pattern\n" " :/\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; }