cgmnlm

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

commit 601f9008863af571980c1cd39920483d59cfbfb4
parent 852bc7198f9d1d838d76d74a006cc2a2e63e4f1c
Author: Drew DeVault <sir@cmpwn.com>
Date:   Sun, 20 Sep 2020 23:50:50 -0400

Implement bookmarks

Diffstat:
Mconfigure | 3++-
Ainclude/util.h | 12++++++++++++
Msrc/gmnlm.c | 83++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Asrc/util.c | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 152 insertions(+), 8 deletions(-)

diff --git a/configure b/configure @@ -16,7 +16,8 @@ gmnlm() { src/escape.c \ src/gmnlm.c \ src/parser.c \ - src/url.c + src/url.c \ + src/util.c } all="gmni gmnlm" diff --git a/include/util.h b/include/util.h @@ -0,0 +1,12 @@ +#ifndef GEMINI_UTIL_H +#define GEMINI_UTIL_H + +struct pathspec { + const char *var; + const char *path; +}; + +char *getpath(const struct pathspec *paths, size_t npaths); +int mkdirs(char *path, mode_t mode); + +#endif diff --git a/src/gmnlm.c b/src/gmnlm.c @@ -12,6 +12,7 @@ #include <unistd.h> #include "gmni.h" #include "url.h" +#include "util.h" struct link { char *url; @@ -29,6 +30,7 @@ struct browser { FILE *tty; char *plain_url; + char *page_title; struct Curl_URL *url; struct link *links; struct history *history; @@ -52,6 +54,8 @@ const char *help_msg = "N\tFollow Nth link (where N is a number)\n" "b\tBack (in the page history)\n" "f\tForward (in the page history)\n" + "m\tSave bookmark\n" + "M\tBrowse bookmarks\n" "\n" "Other commands include:\n\n" "<Enter>\tread more lines\n" @@ -98,6 +102,63 @@ set_url(struct browser *browser, char *new_url, struct history **history) return true; } +static char * +get_data_pathfmt() +{ + const struct pathspec paths[] = { + {.var = "GMNIDATA", .path = "/%s"}, + {.var = "XDG_DATA_HOME", .path = "/gmni/%s"}, + {.var = "HOME", .path = "/.local/share/gmni/%s"} + }; + return getpath(paths, sizeof(paths) / sizeof(paths[0])); +} + +static char * +trim_ws(char *in) +{ + while (*in && isspace(*in)) ++in; + return in; +} + +static void +save_bookmark(struct browser *browser) +{ + const char *path_fmt = get_data_pathfmt(); + static char path[PATH_MAX+1]; + snprintf(path, sizeof(path), path_fmt, "bookmarks.gmi"); + mkdirs(path, 0755); + + FILE *f = fopen(path, "a"); + if (!f) { + fprintf(stderr, "Error opening %s for writing: %s\n", + path, strerror(errno)); + return; + } + + char *title = browser->page_title; + if (title) { + title = trim_ws(browser->page_title); + } + + fprintf(f, "=> %s%s%s\n", browser->plain_url, + title ? " " : "", title ? title : ""); + fclose(f); + + fprintf(browser->tty, "Bookmark saved: %s\n", + title ? title : browser->plain_url); +} + +static void +open_bookmarks(struct browser *browser) +{ + const char *path_fmt = get_data_pathfmt(); + static char path[PATH_MAX+1]; + snprintf(path, sizeof(path), path_fmt, "bookmarks.gmi"); + static char url[PATH_MAX+1+7]; + snprintf(url, sizeof(url), "file://%s", path); + set_url(browser, url, &browser->history); +} + static enum prompt_result do_prompts(const char *prompt, struct browser *browser) { @@ -134,6 +195,16 @@ do_prompts(const char *prompt, struct browser *browser) set_url(browser, browser->history->url, NULL); result = PROMPT_ANSWERED; goto exit; + case 'm': + if (in[1]) break; + save_bookmark(browser); + result = PROMPT_AGAIN; + goto exit; + case 'M': + if (in[1]) break; + open_bookmarks(browser); + result = PROMPT_ANSWERED; + goto exit; case 'f': if (in[1]) break; if (!browser->history->next) { @@ -205,13 +276,6 @@ exit_re: return result; } -static char * -trim_ws(char *in) -{ - while (*in && isspace(*in)) ++in; - return in; -} - static int wrap(FILE *f, char *s, struct winsize *ws, int *row, int *col) { @@ -258,6 +322,8 @@ display_gemini(struct browser *browser, struct gemini_response *resp) int nlinks = 0; struct gemini_parser p; gemini_parser_init(&p, resp->bio); + free(browser->page_title); + browser->page_title = NULL; struct winsize ws; ioctl(fileno(browser->tty), TIOCGWINSZ, &ws); @@ -302,6 +368,9 @@ repeat: } break; case GEMINI_HEADING: + if (!browser->page_title) { + browser->page_title = strdup(tok.heading.title); + } if (text == NULL) { for (int n = tok.heading.level; n; --n) { col += fprintf(out, "#"); diff --git a/src/util.c b/src/util.c @@ -0,0 +1,62 @@ +#include <assert.h> +#include <errno.h> +#include <libgen.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include "util.h" + +static void +posix_dirname(char *path, char *dname) +{ + char p[PATH_MAX+1]; + char *t; + + assert(strlen(path) <= PATH_MAX); + + strcpy(p, path); + t = dirname(path); + memmove(dname, t, strlen(t) + 1); + + /* restore the path if dirname worked in-place */ + if (t == path && path != dname) { + strcpy(path, p); + } +} + +/** Make directory and all of its parents */ +int +mkdirs(char *path, mode_t mode) +{ + char dname[PATH_MAX + 1]; + posix_dirname(path, dname); + if (strcmp(dname, "/") == 0) { + return 0; + } + if (mkdirs(dname, mode) != 0) { + return -1; + } + if (mkdir(path, mode) != 0 && errno != EEXIST) { + return -1; + } + errno = 0; + return 0; +} + +char * +getpath(const struct pathspec *paths, size_t npaths) { + for (size_t i = 0; i < npaths; i++) { + const char *var = ""; + if (paths[i].var) { + var = getenv(paths[i].var); + } + if (var) { + char *out = calloc(1, + strlen(var) + strlen(paths[i].path) + 1); + strcat(strcat(out, var), paths[i].path); + return out; + } + } + return NULL; +}