From 0079fba20988051a99f552f99efea06705932489 Mon Sep 17 00:00:00 2001 From: katalx Date: Mon, 2 Feb 2026 05:19:59 -0500 Subject: full link and url navigation --- main.c | 309 +++++++++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 255 insertions(+), 54 deletions(-) diff --git a/main.c b/main.c index 38f0359..241a569 100644 --- a/main.c +++ b/main.c @@ -31,22 +31,6 @@ str_fmt(Arena *a, const char *fmt, ...) return (Str) { s, n }; } -Str -uint_to_str(unsigned src, Arena *a) -{ - Str r = { 0 }; - do { - str_catc(&r, (src % 10) + '0', a); - src /= 10; - } while(src); - for (int i = 0, j = r.n - 1; i < j; i++, j--) { - char t = r.s[i]; - r.s[i] = r.s[j]; - r.s[j] = t; - } - return r; -} - /* address cache */ #define ADDR_MAP_MAX 128 @@ -73,15 +57,17 @@ addr_get(Str host, int port, Arena *scratch) for (int i = 0; i < addr_mapc; i++) { AddrMap *m = &addr_mapv[i]; - if (m->port != port) continue; - if (str_eql((Str) { m->host, m->hostn }, host)) + printf("%.*s:%d = %.*s:%d?\n", + (int)m->hostn, m->host, m->port, + (int)host.n, host.s, port); + if (port == m->port && str_eql((Str) { m->host, m->hostn }, host)) return m->ai; } - puts("lookup"); + printf("lookup %.*s\n", (int)host.n, host.s); struct addrinfo *ai; int e = getaddrinfo(str_to_cstr(host, scratch), - str_to_cstr(uint_to_str(port, scratch), scratch), + str_to_cstr(str_fmt(scratch, "%d", port), scratch), &(struct addrinfo) { .ai_family = AF_INET, .ai_socktype = SOCK_STREAM, @@ -144,7 +130,8 @@ addr_conn(Str host, int port, Arena *scratch) /* url parsing */ -#define PROT_DEFAULT PROT_GOPHER +#define PROTO_DEFAULT PROT_GOPHER +#define PORT_DEFAULT 70 typedef enum { PROT_UNKNOWN, @@ -208,7 +195,8 @@ parse_request(Str url, Request *req) break; } } else { - req->proto = PROT_DEFAULT; + req->proto = PROTO_DEFAULT; + req->port = PORT_DEFAULT; } /* host & port */ @@ -327,9 +315,9 @@ parse_gophermap_line(Doc *d, Str s) DocLine *l = doc_push_line(d, dsp); if (item != 'i') { Str url = str_fmt(&d->arena, - "=> gopher://%.*s:%d/%.*s", + "gopher://%.*s:%d/%c%.*s", (int)host.n, host.s, - port, + port, item, (int)sel.n, sel.s); doc_link(d, l, url); } @@ -429,6 +417,100 @@ fetch(Str *buf, Request req, Arena *perm, Arena *scratch) } } +/* navigation */ + +#define HIST_MAX 64 + +typedef struct { + Doc doc; + DocLine *ln; +} NavDoc; + +typedef struct { + NavDoc histv[HIST_MAX]; + int histc, histi; +} NavState; + +void +nav_free_doc(NavDoc *nd) +{ + arena_free(&nd->doc.arena); +} + +void +nav_init(NavState *ns) +{ + memset(ns, 0, sizeof(NavState)); +} + +void +nav_fini(NavState *ns) +{ + for (int i = 0; i < ns->histc; i++) + nav_free_doc(&ns->histv[i]); +} + +int +nav_to(NavState *ns, Str url, Arena *scratch) +{ + Request req; + if (parse_request(url, &req)) { + fprintf(stderr, "bad request\n"); + return -1; + } + + Arena a = { 0 }; + Str buf = { 0 }; + arena_reserve(&a, 1L << 20); + DocType t = fetch(&buf, req, &a, scratch); + if (t == DOC_ERROR) { + arena_free(&a); + fprintf(stderr, "fetch failed\n"); + return -1; + } + + Doc d = { + .arena = a, + .src = buf, + .type = t, + }; + if (parse_doc(&d)) { + arena_free(&a); + fprintf(stderr, "parse error\n"); + return -1; + } + + while (ns->histc > ns->histi + 1) + nav_free_doc(&ns->histv[--ns->histc]); + if (ns->histc == HIST_MAX) { + nav_free_doc(&ns->histv[0]); + for (int i = 1; i < ns->histc; i++) + ns->histv[i-1] = ns->histv[i]; + ns->histc--; + } + if (ns->histi < ns->histc) + ns->histi++; + ns->histv[ns->histi] = (NavDoc) { + .doc = d, + .ln = d.head + }; + ns->histc = ns->histi + 1; + return 0; +} + +int +nav_link(NavState *ns, int link, Arena *scratch) +{ + if (!ns->histc) + return -1; + NavDoc *nd = &ns->histv[ns->histi]; + if (link < 1 || link > nd->doc.links.n) + return -1; + write_str(STDOUT_FILENO, nd->doc.links.v[link - 1]); + puts(""); + return nav_to(ns, nd->doc.links.v[link - 1], scratch); +} + void term_size(int *rows, int *cols) { @@ -438,48 +520,167 @@ term_size(int *rows, int *cols) if (cols) *cols = sz.ws_col; } +void +nav_putln(DocLine *l) +{ + if (l->link) printf("[%d]", l->link); + printf("\t%.*s\n", (int)l->s.n, l->s.s); +} + +void +nav_pgdn(NavState *ns) +{ + if (!ns->histc) { + puts("(no document)"); + return; + } + int rows; + term_size(&rows, NULL); + NavDoc *nd = &ns->histv[ns->histi]; + for (int i = 0; i < rows - 1; i++) { + nav_putln(nd->ln); + if (!nd->ln->next) break; + nd->ln = nd->ln->next; + } +} + +void +nav_pgup(NavState *ns) +{ + if (!ns->histc) { + puts("(no document)"); + return; + } + int rows; + term_size(&rows, NULL); + NavDoc *nd = &ns->histv[ns->histi]; + for (int i = 0; i < 2 * (rows - 1); i++) { + if (!nd->ln->prev) break; + nd->ln = nd->ln->prev; + } + nav_pgdn(ns); +} + +void +nav_repg(NavState *ns) +{ + if (!ns->histc) { + puts("(no document)"); + return; + } + int rows; + term_size(&rows, NULL); + NavDoc *nd = &ns->histv[ns->histi]; + for (int i = 0; i < (rows - 1); i++) { + if (!nd->ln->prev) break; + nd->ln = nd->ln->prev; + } + nav_pgdn(ns); +} + +void +nav_prev(NavState *ns) +{ + if (ns->histi == 0) { + puts("(at beginning)"); + return; + } + ns->histi--; + nav_repg(ns); +} + +void +nav_next(NavState *ns) +{ + if (ns->histi + 1 >= ns->histc) { + puts("(at end)"); + return; + } + ns->histi++; + nav_repg(ns); +} + +void +nav_prompt(NavState *ns) +{ + if (ns->histi < ns->histc && ns->histv[ns->histi].ln->next) + printf("MORE"); + printf("* "); + fflush(stdout); +} + int main(void) { Arena scratch = { 0 }; - Request req = { 0 }; + NavState ns = { 0 }; + arena_reserve(&scratch, 1L << 20); addr_init(); - printf("parse_request() -> %d\n", parse_request(S("gopher://tilde.town/1~wrmr"), &req)); - printf("host = %.*s\n", (int)req.host.n, req.host.s); - printf("path = %.*s\n", (int)req.path.n, req.path.s); - printf("prot = %d\n", req.proto); - printf("port = %d\n", req.port); - - Str buf = { 0 }; - DocType t = fetch(&buf, req, &scratch, &scratch); - printf("fetch() -> %d\n", t); - Doc doc = { .arena = scratch, .src = buf, .type = t }; - printf("parse() -> %d\n", parse_doc(&doc)); + if (nav_to(&ns, S("gopher://tilde.town"), &scratch)) { + fprintf(stderr, "failed to load homepage\n"); + goto err; + } + nav_pgdn(&ns); - DocLine *l = doc.head; - for (;;) { - int ln; - term_size(&ln, NULL); - for (int i = 0; i < ln - 1; i++) { - if (l->link > 0) printf("[%d]", l->link); - printf("\t%.*s\n", (int)l->s.n, l->s.s); - if (!l->next) break; - l = l->next; - } + while (!feof(stdin)) { + arena_reset(&scratch); + nav_prompt(&ns); char buf[1024] = { 0 }; - if (l->next) printf("MORE"); - printf("* "); - fflush(stdout); - fgets(buf, 1023, stdin); - if (buf[0] == 'q') break; - if (buf[0] == 'b') { - for (int i = 0; l->prev && i < 2 * (ln - 1); i++) - l = l->prev; + fgets(buf, sizeof(buf) - 1, stdin); + Str s = str_trim(str_from_cstr(buf)); + if (s.n == 0) s = S("f"); + switch (s.s[0]) { + case 'f': + nav_pgdn(&ns); + break; + case 'b': + nav_pgup(&ns); + break; + case 'r': + nav_repg(&ns); + break; + case 'p': + nav_prev(&ns); + break; + case 'n': + nav_next(&ns); + break; + case 'q': + goto ok; + case 'g': + if (nav_to(&ns, str_skip(s, 1), &scratch)) + puts("failed"); + else + nav_pgdn(&ns); + break; + case '1': case '2': case '3': + case '4': case '5': case '6': + case '7': case '8': case '9': { + int p = parse_port(s); + if (p == -1) { + puts("invalid link number!"); + } else { + if (nav_link(&ns, p, &scratch)) + puts("failed"); + else + nav_pgdn(&ns); + } + break; + default: + puts("?"); + break; + } } } +ok: puts("Goodbye!"); addr_fini(); return 0; + +err: + puts("Error occurred. Aborting."); + addr_fini(); + return 1; } -- cgit v1.2.3