cgmnlm

colorful gemini line mode browser
git clone https://git.clttr.info/cgmnlm.git
Log (Feed) | Files | Refs (Tags) | README | LICENSE

commit 5a955c5f241b87018dfb0cda6872dc7ae2784222
parent dcc2b34d434d8e0f695e43a1f775759846e60417
Author: Drew DeVault <sir@cmpwn.com>
Date:   Sun, 20 Sep 2020 19:33:43 -0400

gmnlm: refactor

Diffstat:
Msrc/gmnlm.c | 309++++++++++++++++++++++++++++++++++++++++++-------------------------------------
1 file changed, 166 insertions(+), 143 deletions(-)

diff --git a/src/gmnlm.c b/src/gmnlm.c @@ -22,6 +22,16 @@ struct history { struct history *prev, *next; }; +struct browser { + bool pagination; + struct gemini_options opts; + + FILE *tty; + struct Curl_URL *url; + struct link *links; + struct history *history; +}; + static void usage(const char *argv_0) { @@ -39,7 +49,7 @@ history_free(struct history *history) } static bool -set_url(struct Curl_URL *url, char *new_url, struct history **history) +set_url(struct browser *browser, char *new_url, struct history **history) { if (history) { struct history *next = calloc(1, sizeof(struct history)); @@ -53,7 +63,7 @@ set_url(struct Curl_URL *url, char *new_url, struct history **history) } *history = next; } - if (curl_url_set(url, CURLUPART_URL, new_url, 0) != CURLUE_OK) { + if (curl_url_set(browser->url, CURLUPART_URL, new_url, 0) != CURLUE_OK) { fprintf(stderr, "Error: invalid URL\n"); return false; } @@ -71,27 +81,29 @@ trim_ws(char *in) } static void -display_gemini(FILE *tty, struct gemini_response *resp, - struct link **next, bool pagination) +display_gemini(struct browser *browser, struct gemini_response *resp) { + // TODO: Strip ANSI escape sequences int nlinks = 0; struct gemini_parser p; gemini_parser_init(&p, resp->bio); struct winsize ws; - ioctl(fileno(tty), TIOCGWINSZ, &ws); + ioctl(fileno(browser->tty), TIOCGWINSZ, &ws); int row = 0, col = 0; struct gemini_token tok; + struct link **next = &browser->links; while (gemini_parser_next(&p, &tok) == 0) { switch (tok.token) { case GEMINI_TEXT: // TODO: word wrap - col += fprintf(tty, " %s\n", trim_ws(tok.text)); + col += fprintf(browser->tty, " %s\n", + trim_ws(tok.text)); break; case GEMINI_LINK: - col += fprintf(tty, "[%d] %s\n", nlinks++, trim_ws( - tok.link.text ? tok.link.text : tok.link.url)); + col += fprintf(browser->tty, "[%d] %s\n", nlinks++, + trim_ws(tok.link.text ? tok.link.text : tok.link.url)); *next = calloc(1, sizeof(struct link)); (*next)->url = strdup(trim_ws(tok.link.url)); next = &(*next)->next; @@ -100,17 +112,20 @@ display_gemini(FILE *tty, struct gemini_response *resp, continue; // TODO case GEMINI_HEADING: for (int n = tok.heading.level; n; --n) { - col += fprintf(tty, "#"); + col += fprintf(browser->tty, "#"); } - col += fprintf(tty, " %s\n", trim_ws(tok.heading.title)); + col += fprintf(browser->tty, " %s\n", + trim_ws(tok.heading.title)); break; case GEMINI_LIST_ITEM: // TODO: Option to disable Unicode - col += fprintf(tty, " • %s\n", trim_ws(tok.list_item)); + col += fprintf(browser->tty, " • %s\n", + trim_ws(tok.list_item)); break; case GEMINI_QUOTE: // TODO: Option to disable Unicode - col += fprintf(tty, " | %s\n", trim_ws(tok.quote_text)); + col += fprintf(browser->tty, " | %s\n", + trim_ws(tok.quote_text)); break; } @@ -121,12 +136,14 @@ display_gemini(FILE *tty, struct gemini_response *resp, ++row; col = 0; - if (pagination && row >= ws.ws_row - 1) { - fprintf(tty, "[Enter for more, or q to stop] "); + // TODO: It would be nice if we could follow links from this + // prompt + if (browser->pagination && row >= ws.ws_row - 1) { + fprintf(browser->tty, "[Enter for more, or q to stop] "); size_t n = 0; char *l = NULL; - if (getline(&l, &n, tty) == -1) { + if (getline(&l, &n, browser->tty) == -1) { return; } if (strcmp(l, "q\n") == 0) { @@ -142,52 +159,163 @@ display_gemini(FILE *tty, struct gemini_response *resp, } static void -display_plaintext(FILE *tty, struct gemini_response *resp, bool pagination) +display_plaintext(struct browser *browser, struct gemini_response *resp) { + // TODO: Strip ANSI escape sequences struct winsize ws; int row = 0, col = 0; - ioctl(fileno(tty), TIOCGWINSZ, &ws); + ioctl(fileno(browser->tty), TIOCGWINSZ, &ws); char buf[BUFSIZ]; int n; while ((n = BIO_read(resp->bio, buf, sizeof(buf)) != 0)) { while (n) { - n -= fwrite(buf, 1, n, tty); + n -= fwrite(buf, 1, n, browser->tty); } } - (void)pagination; (void)row; (void)col; // TODO: generalize pagination + (void)row; (void)col; // TODO: generalize pagination } static void -display_response(FILE *tty, struct gemini_response *resp, - struct link **next, bool pagination) +display_response(struct browser *browser, struct gemini_response *resp) { if (strcmp(resp->meta, "text/gemini") == 0 || strncmp(resp->meta, "text/gemini;", 12) == 0) { - display_gemini(tty, resp, next, pagination); + display_gemini(browser, resp); return; } if (strncmp(resp->meta, "text/", 5) == 0) { - display_plaintext(tty, resp, pagination); + display_plaintext(browser, resp); return; } } -int -main(int argc, char *argv[]) +static char * +do_requests(struct browser *browser, struct gemini_response *resp) { - bool pagination = true; + char *plain_url; + int nredir = 0; + bool requesting = true; + while (requesting) { + CURLUcode uc = curl_url_get(browser->url, + CURLUPART_URL, &plain_url, 0); + assert(uc == CURLUE_OK); // Invariant + + enum gemini_result res = gemini_request( + plain_url, &browser->opts, resp); + if (res != GEMINI_OK) { + fprintf(stderr, "Error: %s\n", gemini_strerr(res, resp)); + requesting = false; + break; + } - struct Curl_URL *url = curl_url(); + switch (gemini_response_class(resp->status)) { + case GEMINI_STATUS_CLASS_INPUT: + assert(0); // TODO + case GEMINI_STATUS_CLASS_REDIRECT: + if (++nredir >= 5) { + requesting = false; + fprintf(stderr, "Error: maximum redirects (5) exceeded"); + break; + } + fprintf(stderr, "Following redirect to %s\n", resp->meta); + set_url(browser, resp->meta, NULL); + break; + case GEMINI_STATUS_CLASS_CLIENT_CERTIFICATE_REQUIRED: + assert(0); // TODO + case GEMINI_STATUS_CLASS_TEMPORARY_FAILURE: + case GEMINI_STATUS_CLASS_PERMANENT_FAILURE: + requesting = false; + fprintf(stderr, "Server returned %s %d %s\n", + resp->status / 10 == 4 ? + "TEMPORARY FAILURE" : "PERMANENT FALIURE", + resp->status, resp->meta); + break; + case GEMINI_STATUS_CLASS_SUCCESS: + requesting = false; + display_response(browser, resp); + break; + } - FILE *tty = fopen("/dev/tty", "w+"); + if (requesting) { + gemini_response_finish(resp); + } + } + + return plain_url; +} + +static bool +do_prompts(const char *prompt, struct browser *browser) +{ + bool prompting = true; + while (prompting) { + fprintf(browser->tty, "%s", prompt); + + size_t l = 0; + char *in = NULL; + ssize_t n = getline(&in, &l, browser->tty); + if (n == -1 && feof(browser->tty)) { + return false; + } + if (strcmp(in, "q\n") == 0) { + return false; + } + if (strcmp(in, "b\n") == 0) { + if (!browser->history->prev) { + fprintf(stderr, "At beginning of history\n"); + continue; + } + browser->history = browser->history->prev; + set_url(browser, browser->history->url, NULL); + break; + } + if (strcmp(in, "f\n") == 0) { + if (!browser->history->next) { + fprintf(stderr, "At end of history\n"); + continue; + } + browser->history = browser->history->next; + set_url(browser, browser->history->url, NULL); + break; + } + + struct link *link = browser->links; + char *endptr; + int linksel = (int)strtol(in, &endptr, 10); + if (endptr[0] == '\n' && linksel >= 0) { + while (linksel > 0 && link) { + link = link->next; + --linksel; + } + + if (!link) { + fprintf(stderr, "Error: no such link.\n"); + } else { + set_url(browser, link->url, &browser->history); + break; + } + } + free(in); + } + return true; +} + +int +main(int argc, char *argv[]) +{ + struct browser browser = { + .pagination = true, + .url = curl_url(), + .tty = fopen("/dev/tty", "w+"), + }; int c; while ((c = getopt(argc, argv, "hP")) != -1) { switch (c) { case 'P': - pagination = false; + browser.pagination = false; break; case 'h': usage(argv[0]); @@ -198,9 +326,8 @@ main(int argc, char *argv[]) } } - struct history *history; if (optind == argc - 1) { - set_url(url, argv[optind], &history); + set_url(&browser, argv[optind], &browser.history); } else { usage(argv[0]); return 1; @@ -208,65 +335,13 @@ main(int argc, char *argv[]) SSL_load_error_strings(); ERR_load_crypto_strings(); - struct gemini_options opts = { - .ssl_ctx = SSL_CTX_new(TLS_method()), - }; + browser.opts.ssl_ctx = SSL_CTX_new(TLS_method()); bool run = true; struct gemini_response resp; while (run) { - struct link *links; static char prompt[4096]; - char *plain_url; - - int nredir = 0; - bool requesting = true; - while (requesting) { - CURLUcode uc = curl_url_get(url, CURLUPART_URL, &plain_url, 0); - assert(uc == CURLUE_OK); // Invariant - - enum gemini_result res = gemini_request( - plain_url, &opts, &resp); - if (res != GEMINI_OK) { - fprintf(stderr, "Error: %s\n", - gemini_strerr(res, &resp)); - requesting = false; - break; - } - - switch (gemini_response_class(resp.status)) { - case GEMINI_STATUS_CLASS_INPUT: - assert(0); // TODO - case GEMINI_STATUS_CLASS_REDIRECT: - if (++nredir >= 5) { - requesting = false; - fprintf(stderr, "Error: maximum redirects (5) exceeded"); - break; - } - fprintf(stderr, - "Following redirect to %s\n", resp.meta); - set_url(url, resp.meta, NULL); - break; - case GEMINI_STATUS_CLASS_CLIENT_CERTIFICATE_REQUIRED: - assert(0); // TODO - case GEMINI_STATUS_CLASS_TEMPORARY_FAILURE: - case GEMINI_STATUS_CLASS_PERMANENT_FAILURE: - requesting = false; - fprintf(stderr, "Server returned %s %d %s\n", - resp.status / 10 == 4 ? - "TEMPORARY FAILURE" : "PERMANENT FALIURE", - resp.status, resp.meta); - break; - case GEMINI_STATUS_CLASS_SUCCESS: - requesting = false; - display_response(tty, &resp, &links, pagination); - break; - } - - if (requesting) { - gemini_response_finish(&resp); - } - } + char *plain_url = do_requests(&browser, &resp); snprintf(prompt, sizeof(prompt), "\n%s%s at %s\n" "[n]: follow Nth link; [o <url>]: open URL; " @@ -276,74 +351,22 @@ main(int argc, char *argv[]) resp.status == GEMINI_STATUS_SUCCESS ? " " : "", resp.status == GEMINI_STATUS_SUCCESS ? resp.meta : "", plain_url); - gemini_response_finish(&resp); - bool prompting = true; - while (prompting) { - fprintf(tty, "%s", prompt); - - size_t l = 0; - char *in = NULL; - ssize_t n = getline(&in, &l, tty); - if (n == -1 && feof(tty)) { - run = false; - break; - } - if (strcmp(in, "q\n") == 0) { - run = false; - break; - } - if (strcmp(in, "b\n") == 0) { - if (!history->prev) { - fprintf(stderr, "At beginning of history\n"); - continue; - } - history = history->prev; - set_url(url, history->url, NULL); - break; - } - if (strcmp(in, "f\n") == 0) { - if (!history->next) { - fprintf(stderr, "At end of history\n"); - continue; - } - history = history->next; - set_url(url, history->url, NULL); - break; - } - - struct link *link = links; - char *endptr; - int linksel = (int)strtol(in, &endptr, 10); - if (endptr[0] == '\n' && linksel >= 0) { - while (linksel > 0 && link) { - link = link->next; - --linksel; - } - - if (!link) { - fprintf(stderr, "Error: no such link.\n"); - } else { - set_url(url, link->url, &history); - break; - } - } - free(in); - } + run = do_prompts(prompt, &browser); - struct link *link = links; + struct link *link = browser.links; while (link) { struct link *next = link->next; free(link->url); free(link); link = next; } - links = NULL; + browser.links = NULL; } - history_free(history); - SSL_CTX_free(opts.ssl_ctx); - curl_url_cleanup(url); + history_free(browser.history); + SSL_CTX_free(browser.opts.ssl_ctx); + curl_url_cleanup(browser.url); return 0; }