#include #include #include #include #include /* errors */ void perr(const char *s) { fprintf(stderr, "%s\n", s); } /* misc */ int running; int quit(const char *_) { puts("goodbye!"); running = 0; return 0; } /* documents */ struct doc { struct doc *prev, *next; struct { char *buf; size_t len, cap; } text; size_t ofs; }; struct doc *doc_head, *doc_tail, *doc_cur; void doc_free(struct doc *d) { free(d->text.buf); free(d); } int doc_new(void) { struct doc *d = calloc(1, sizeof *d); if (!d) { return -1; } d->text.buf = calloc(256, 1); if (!d->text.buf) { free(d); return -1; } d->text.len = 256; d->text.cap = 256; if (doc_cur) { struct doc *p = doc_cur->next; while (p) { struct doc *pp = p; p = p->next; doc_free(pp); } doc_tail = doc_cur; } if (doc_tail) { doc_tail->next = d; d->prev = doc_tail; } if (!doc_head) doc_head = d; doc_tail = d; doc_cur = d; return 0; } int doc_add(char *buf, size_t n) { if (!doc_cur) return -1; size_t c = doc_cur->text.cap; size_t tn = doc_cur->text.len + n + 1; if (c < tn) { while (c < tn) c <<= 1; char *s = realloc(doc_cur->text.buf, c); if (!s) { return -1; } doc_cur->text.buf = s; } memcpy(&doc_cur->text.buf[doc_cur->text.len], buf, n); doc_cur->text.len += 1; doc_cur->text.buf[doc_cur->text.len] = 0; return 0; } void doc_prev(void) { if (doc_cur && doc_cur->prev) { doc_cur = doc_cur->prev; } } void doc_next(void) { if (doc_cur && doc_cur->next) { doc_cur = doc_cur->next; } } void doc_fini(void) { while (doc_tail) { doc_head = doc_tail; doc_tail = doc_tail->prev;; doc_free(doc_head); } } /* pagination */ int pg_down(const char *_) { return 0; } /* navigation */ #define HOST_MAX 255 #define PATH_MAX 255 enum url_type { TYPE_GOPHERDOC, TYPE_GEMTEXT, TYPE_PLAIN }; struct url { char host[HOST_MAX]; char path[PATH_MAX]; size_t host_len, path_len; enum url_type type; }; int nav_to(const char *url) { return 0; } int nav_link_nr(unsigned long link_nr) { return 0; } /* commands */ struct cmd { char ch; int (*fn)(const char *); }; struct cmd cmd_tbl[] = { { 'g', nav_to }, { '\n', pg_down }, { 'q', quit } }; /* cmd is mutated when trimming strings */ void cmd_do(char *cmd) { if (isdigit(*cmd)) { errno = 0; unsigned long n = strtoul(cmd, NULL, 10); if (errno) { perr("invalid link number"); } else if (nav_link_nr(n)) { perr("navigation failure"); } } else { for (size_t i = 0; i < sizeof cmd_tbl / sizeof(struct cmd); i++) { if (cmd_tbl[i].ch == *cmd) { while (isspace(*++cmd)); for (int j = strlen(cmd) - 1; j > 0 && isspace(cmd[j]); j--) { cmd[j] = 0; } if (cmd_tbl[i].fn(cmd)) { perr("failed"); } goto found; } } perr("?"); found: ; } } int cmd_get(char *buf, size_t n) { fputs("* ", stdout); return !!fgets(buf, n, stdin); } int main(void) { char cmd_buf[1024]; running = 1; while (running && cmd_get(cmd_buf, sizeof cmd_buf)) { cmd_do(cmd_buf); } doc_fini(); return 0; }