readr

Minimal RSS reader (WIP)
Log | Files | Refs | README | LICENSE

commit 82d6b985067893135f275d8142a1bbf49ec8082f
parent 9c195cb929710440fd90dbc8eca305ac3aa02ea5
Author: citbl <citbl@citbl.org>
Date:   Wed,  8 Oct 2025 21:50:15 +1000

open comments and handle dead feeds

Diffstat:
Mconfig.h | 1+
Mfeeds.c | 25+++++++++++++------------
Mkeys.c | 22+++++++++++++++++++---
Mreadr.h | 3+--
Mrender.c | 8+++-----
Mtui.c | 28++++++++++++++--------------
6 files changed, 51 insertions(+), 36 deletions(-)

diff --git a/config.h b/config.h @@ -6,6 +6,7 @@ #define POSTS_CAP 128 #define FEEDS_CAP 32 #define TITLE_CAP 128 +#define URL_CAP 8192 #define TEXT_COLOR TB_WHITE #define BACK_COLOR TB_DEFAULT diff --git a/feeds.c b/feeds.c @@ -49,8 +49,11 @@ fetch_feed(feed_t* feed, char* url) static mrss_t* rss = NULL; mrss_error_t rc = mrss_parse_url(url, &rss); - if (rc != MRSS_OK) { - fprintf(stderr, "parse feed failed: %s\n", mrss_strerror(rc)); + if (rc != MRSS_OK || rss == NULL) { + char short_url[19]; + strncpy(short_url, url, 18); + short_url[18] = '\0'; + asprintf(&feed->title, "%s%s", "(bad) ", short_url); return; } @@ -59,16 +62,17 @@ fetch_feed(feed_t* feed, char* url) for (mrss_item_t* it = rss->item; it; it = it->next) { char* title = (it->title && *it->title) ? it->title : ""; const char* link = (it->link && *it->link) ? it->link : ""; + const char* comments = (it->comments && *it->comments) ? it->comments : ""; char* desc = (it->description && *it->description) ? it->description : ""; const char* date = (it->pubDate && *it->pubDate) ? it->pubDate : ""; const char* author = (it->author && *it->author) ? it->author : ""; remove_all_tags(desc); - const size_t title_ = strlen(title), link_ = strlen(link), desc_ = strlen(desc), - date_ = strlen(date), author_ = strlen(author); + const size_t title_ = strlen(title), link_ = strlen(link), comments_ = strlen(comments), + desc_ = strlen(desc), date_ = strlen(date), author_ = strlen(author); - size_t data_len = title_ + link_ + desc_ + date_ + author_ + 1; + size_t data_len = title_ + link_ + comments_ + desc_ + date_ + author_ + 1; char* d = calloc(data_len + 1, sizeof(char)); @@ -76,6 +80,7 @@ fetch_feed(feed_t* feed, char* url) strncat(d, title, title_); strncat(d, link, link_); + strncat(d, comments, comments_); strncat(d, desc, desc_); strncat(d, date, date_); strncat(d, author, author_); @@ -90,8 +95,9 @@ fetch_feed(feed_t* feed, char* url) .data_len = data_len, .title = (slice_t) { .start = 0, .len = title_ }, .link = (slice_t) { .start = title_, .len = link_ }, - .date = (slice_t) { .start = title_ + link_, .len = date_ }, - .author = (slice_t) { .start = title_ + link_ + date_, .len = author_ }, + .comments = (slice_t) { .start = title_ + link_, .len = comments_ }, + .date = (slice_t) { .start = title_ + link_ + comments_, .len = date_ }, + .author = (slice_t) { .start = title_ + link_ + comments_ + date_, .len = author_ }, }; if (feed->posts_len == feed->posts_cap) { @@ -99,11 +105,6 @@ fetch_feed(feed_t* feed, char* url) feed->posts = realloc(feed->posts, feed->posts_cap); } feed->posts[feed->posts_len++] = post; - - // if (it->link && *it->link) { - // const char* domain = host_from_url(it->link); - // printf("%-60.60s \t%s \t%s\n", title, domain, it->link); - // } } mrss_free(rss); diff --git a/keys.c b/keys.c @@ -1,4 +1,6 @@ #include <sys/param.h> + +#include "config.h" #include "readr.h" #include "tui.h" #include "utils.h" @@ -37,15 +39,29 @@ handle_key(app_t* app, struct tb_event ev) break; case TB_KEY_ENTER: { if (app->selected_panel == 1) { - static char url[512] = { 0 }; + char url[URL_CAP] = { 0 }; post_t* post = app->feeds[app->selected_feed]->posts[app->selected_post]; - size_t len = MIN(512, post->link.len); + size_t len = MIN(URL_CAP, post->link.len); + if (len == 0) return; strncpy(url, &post->data[post->link.start], len); url[len] = '\0'; open_url(url); } - } break; + break; + } default: break; } + + if (ev.ch == ' ') { // TB_KEY_SPACE for some reason doesn't work /shrug + if (app->selected_panel == 1) { + char url[URL_CAP] = { 0 }; + post_t* post = app->feeds[app->selected_feed]->posts[app->selected_post]; + size_t len = MIN(URL_CAP, post->comments.len); + if (len == 0) return; + strncpy(url, &post->data[post->comments.start], len); + url[len] = '\0'; + open_url(url); + } + } } diff --git a/readr.h b/readr.h @@ -14,7 +14,7 @@ typedef struct { typedef struct { const char* data; size_t data_len; - slice_t title, link, desc, date, author; + slice_t title, link, comments, desc, date, author; } post_t; typedef struct { @@ -22,7 +22,6 @@ typedef struct { char* title; post_t** posts; int posts_len, posts_cap; - } feed_t; typedef struct { diff --git a/render.c b/render.c @@ -1,7 +1,8 @@ +#include <sys/param.h> + #include "termbox2.h" #include "tui.h" #include "config.h" -#include <sys/param.h> static void draw_top_bar(void) @@ -12,7 +13,7 @@ draw_top_bar(void) } tb_print(1, 0, TEXT_COLOR, LINE_COLOR, "readr"); - tb_print(width - 10, 0, TEXT_COLOR, LINE_COLOR, "[q] quit"); + tb_print(width - 51, 0, TEXT_COLOR, LINE_COLOR, "[enter] open link [space] open comments [q] quit"); } static void @@ -52,11 +53,8 @@ render(app_t* app) size_t len = MIN(TITLE_CAP, post->title.len); strncpy(title, &post->data[post->title.start], len); title[len] = '\0'; - // printf("%s/%zu", title, post->title.len); - // exit(0); color = (app->selected_panel == 1 && i == app->selected_post) ? (POST_COLOR | TB_REVERSE) : (POST_COLOR); - // nonascii_replace(title, '\''); tb_print(FEED_CAP + 10, 4 + i, color, TB_DEFAULT, title); } diff --git a/tui.c b/tui.c @@ -6,30 +6,29 @@ int present(app_t* app) { - int ret = tb_init(); + struct tb_event ev = { 0 }; + int can_poll = -1; + int cannot_init = tb_init(); + tb_hide_cursor(); - if (ret) { + if (cannot_init) { fprintf(stderr, "could not TUI\n"); exit(1); } render(app); - struct tb_event ev; - int res; - while (1) { - res = tb_poll_event(&ev); + can_poll = tb_poll_event(&ev); - if (res == TB_OK) { + if (can_poll == TB_OK) { switch (ev.type) { case (TB_EVENT_KEY): - if (ev.key == TB_KEY_CTRL_Q || ev.key == TB_KEY_F10 || ev.ch == 'q') goto RIP; + if (ev.key == TB_KEY_CTRL_Q || ev.key == TB_KEY_ESC || ev.ch == 'q') goto RIP; handle_key(app, ev); break; case (TB_EVENT_MOUSE): - // todo handle mouse events break; case (TB_EVENT_RESIZE): break; @@ -37,15 +36,16 @@ present(app_t* app) render(app); - } else if (res == TB_ERR_POLL && tb_last_errno() == EINTR) { + } else if (can_poll == TB_ERR_POLL && tb_last_errno() == EINTR) { continue; - } else if (res != TB_ERR_NO_EVENT) { - fprintf(stderr, "(aborting) renderer error: %s\n", tb_strerror(res)); - return 1; + + } else if (can_poll != TB_ERR_NO_EVENT) { + fprintf(stderr, "(aborting) renderer error: %s\n", tb_strerror(can_poll)); + goto RIP; } } RIP: tb_shutdown(); - return 0; + exit(1); }