summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkatalx2026-02-02 05:19:59 -0500
committerkatalx2026-02-02 05:19:59 -0500
commit0079fba20988051a99f552f99efea06705932489 (patch)
tree5261354df6f9648a5c79f67e5e1ca5cc50d343d5
parente24a6cc93cb639700779ade2cea9383849d9ee71 (diff)
full link and url navigation
-rw-r--r--main.c309
1 files 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;
}