readr

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

commit 7687a366dd35e55370e135973a15b77332515024
parent ea3b4ef7aa28bd5f0accb146805703c15a8e6919
Author: citbl <citbl@citbl.org>
Date:   Sun, 12 Oct 2025 16:06:58 +1000

1.3 with better safety and clean ups

Diffstat:
M.clangd | 7+++++++
Msrc/config.h | 2+-
Msrc/db.c | 2+-
Msrc/feeds.c | 18++++++++++--------
Msrc/readr_main.c | 11++---------
Msrc/render.c | 21+++++++++------------
Msrc/utils.c | 32++++++++++++++++++++++++--------
Msrc/utils.h | 10++++++++--
8 files changed, 62 insertions(+), 41 deletions(-)

diff --git a/.clangd b/.clangd @@ -3,6 +3,13 @@ CompileFlags: -x,c, -Wall, -Wextra, + -Wshadow, + -Wpointer-arith, + -Wcast-qual, + -Wcast-align, + -Wstrict-prototypes, + -Wmissing-prototypes, + -Wconversion, "-std=c99", -g, "-I/opt/homebrew/include" diff --git a/src/config.h b/src/config.h @@ -1,6 +1,6 @@ #pragma once -#define VERSION "v1.2" +#define VERSION "v1.3" // source file for RSS URLS, one per line // folder and file must exist diff --git a/src/db.c b/src/db.c @@ -155,7 +155,7 @@ db_fetch_posts(const char* feed_url) p->seen = sqlite3_column_int(st, 7); if (len == cap) { cap = cap ? cap * 2 : MAX_POST_PER_FEED; - rows = (db_post_t**)realloc(rows, sizeof(db_post_t*) * cap); + rows = (db_post_t**)realloc(rows, sizeof(db_post_t*) * (size_t)cap); if (rows == NULL) { fprintf(stderr, "could not reallocate for list of posts fetch from db\n"); break; diff --git a/src/feeds.c b/src/feeds.c @@ -34,7 +34,7 @@ load_app(char* contents) if (app.feeds_cap == app.feeds_len) { app.feeds_cap *= 2; - app.feeds = realloc(app.feeds, app.feeds_cap); + app.feeds = realloc(app.feeds, (size_t)app.feeds_cap); } app.feeds[app.feeds_len++] = feed; line = strtok(NULL, "\n"); @@ -46,20 +46,22 @@ load_app(char* contents) void fetch_feed(feed_t* feed, char* url) { - *feed = (feed_t) { .url = url, /*.posts_cap = POSTS_CAP*/ }; - static mrss_t* rss = NULL; + *feed = (feed_t) { .url = url }; + feed->title = (char*)ecalloc(FEED_CAP, sizeof(char)); + mrss_error_t rc = mrss_parse_url(url, &rss); if (rc != MRSS_OK || rss == NULL) { - char short_url[FEEDS_CAP - 1]; - strncpy(short_url, url, FEEDS_CAP - 2); - short_url[FEEDS_CAP - 2] = '\0'; - asprintf(&feed->title, "%s%s", "(bad) ", short_url); + snprintf(feed->title, FEED_CAP, "%s%s ", "(bad) ", url ? url : "(unknown feed url)"); return; } - feed->title = (rss->title != NULL) ? strndup(rss->title, FEED_CAP) : "Unknown feed"; + snprintf(feed->title, FEED_CAP, "%s", rss->title ? rss->title : "(unknown feedtitle)"); + + // size_t len = MIN(FEED_CAP, strlen(rss->title) + 1); + // feed->title = (rss->title != NULL) ? strndup(rss->title, len) : "Unknown feed"; + // feed->title[len - 1] = '\0'; for (mrss_item_t* it = rss->item; it; it = it->next) { char* title = (it->title && *it->title) ? it->title : ""; diff --git a/src/readr_main.c b/src/readr_main.c @@ -1,6 +1,5 @@ #include <stdlib.h> #include <string.h> -#include <locale.h> #include <mrss.h> #define UTILS_IMPL @@ -15,14 +14,8 @@ int main(void) { - // for unicode blocks/lines, curvy quotes etc. - const char* ok = setlocale(LC_ALL, "C.UTF-8"); - - if (!ok) { - perror("setlocale failed"); - return 1; - } - + switch_locale(); + validate_config(); db_create(); const char* path = expand_tilde(FEEDS); diff --git a/src/render.c b/src/render.c @@ -18,7 +18,7 @@ draw_bars(void) for (i = 2; i < height - 2; i++) { tb_set_cell(0, i, VERT_LINE, LINE_COLOR, BACK_COLOR); - tb_set_cell(FEED_CAP + 5, i, VERT_LINE, LINE_COLOR, BACK_COLOR); /* separator line */ + tb_set_cell(FEED_CAP + 2, i, VERT_LINE, LINE_COLOR, BACK_COLOR); /* separator line */ tb_set_cell(width - 1, i, VERT_LINE, LINE_COLOR, BACK_COLOR); } @@ -38,13 +38,13 @@ draw_bars(void) // corners top tb_set_cell(0, 1, 0x250C, LINE_COLOR, BACK_COLOR); - tb_set_cell(FEED_CAP + 5, 1, 0x252C, LINE_COLOR, BACK_COLOR); + tb_set_cell(FEED_CAP + 2, 1, 0x252C, LINE_COLOR, BACK_COLOR); tb_set_cell(width - 1, 1, 0x2510, LINE_COLOR, BACK_COLOR); // corners bottom tb_set_cell(0, height - 2, 0x2514, LINE_COLOR, BACK_COLOR); - tb_set_cell(FEED_CAP + 5, height - 2, 0x2534, LINE_COLOR, BACK_COLOR); + tb_set_cell(FEED_CAP + 2, height - 2, 0x2534, LINE_COLOR, BACK_COLOR); tb_set_cell(width - 1, height - 2, 0x2518, LINE_COLOR, BACK_COLOR); } @@ -60,7 +60,6 @@ render(app_t* app) { int i; uintattr_t color; - int title_len, domain_len; char title[TITLE_CAP] = { 0 }; char domain[DOMAIN_CAP] = { 0 }; db_post_t* post; @@ -92,15 +91,13 @@ render(app_t* app) for (i = 0; i < visible_len; i++) { post = selected_feed->posts[i]; - title_len = MIN(TITLE_CAP, strlen(post->title)) + 1; - strncpy(title, post->title, title_len - 1); - title[title_len - 1] = ' '; // overlapping with domain - title[title_len] = '\0'; + // title - added space for when title and domain overlap + snprintf(title, TITLE_CAP, "%s ", post->title ? post->title : "(bad title"); + // domain const char* domain_host = host_from_url(post->link); - domain_len = MIN(DOMAIN_CAP, strlen(domain_host)); - strncpy(domain, domain_host, domain_len); - domain[domain_len] = '\0'; + snprintf(domain, DOMAIN_CAP, "%s", domain_host ? domain_host : "(bad title)"); + int domain_len = (int)strlen(domain); if (app->selected_panel == 1 && i == app->selected_post) if (post->seen) @@ -113,7 +110,7 @@ render(app_t* app) color = (POST_COLOR); tb_print(width - domain_len - 2, 3 + i, DOMAIN_COLOR, BACK_COLOR, domain); - tb_print(FEED_CAP + 7, 3 + i, color, BACK_COLOR, title); + tb_print(FEED_CAP + 4, 3 + i, color, BACK_COLOR, title); } tb_present(); diff --git a/src/utils.c b/src/utils.c @@ -2,8 +2,31 @@ #include <strings.h> #include <errno.h> #include <stdarg.h> +#include <locale.h> #include "utils.h" +#include "config.h" + +void +validate_config(void) +{ + if (strlen(FEEDS) < 3 || strlen(FEEDS) > 1024) panic("invalid FEEDS path"); + if (strlen(DB_PATH) < 3 || strlen(DB_PATH) > 1024) panic("invalid DB_PATH path"); + + if (MAX_POST_PER_FEED < 1 || MAX_POST_PER_FEED > 256) panic("invalid MAX_POST_PER_FEED (1~256)"); + if (POSTS_CAP < 16 || POSTS_CAP > 512) panic("invalid POSTS_CAP (16~512)"); + if (FEEDS_CAP < 16 || FEEDS_CAP > 512) panic("invalid FEEDS_CAP (16~512)"); + if (TITLE_CAP < 16 || TITLE_CAP > 512) panic("invalid TITLE_CAP (16~512)"); + if (DOMAIN_CAP < 16 || DOMAIN_CAP > 512) panic("invalid DOMAIN_CAP (16~512)"); + if (URL_CAP < 2048 || URL_CAP > 8192) panic("invalid URL_CAP (2048~8192)"); +} + +void +switch_locale(void) +{ + // for unicode blocks/lines, curvy quotes etc. + if (!setlocale(LC_ALL, "C.UTF-8")) panic("could not switch to UTF-8!"); +} void panic(const char* fmt, ...) @@ -21,13 +44,6 @@ panic(const char* fmt, ...) exit(1); } -void -util_fill(int* dst, size_t n, int value) -{ - for (size_t i = 0; i < n; ++i) - dst[i] = value; -} - #if defined(_WIN32) // to be tested on windows, and then have a shower @@ -285,7 +301,7 @@ nonascii_replace(char* s, char to) } r += (i == need) ? need : 1; // tolerate invalid UTF-8 - if (w == (unsigned char*)s || w[-1] != ' ') *w++ = to; + if (w == (unsigned char*)s || w[-1] != ' ') *w++ = (unsigned char)to; } } *w = '\0'; diff --git a/src/utils.h b/src/utils.h @@ -5,13 +5,19 @@ #define MIN(A, B) ((A) < (B) ? (A) : (B)) #define MAX(A, B) ((A) > (B) ? (A) : (B)) +void validate_config(void); +void switch_locale(void); + void panic(const char*, ...); +void* ecalloc(size_t, size_t); + const char* get_home_dir(void); const char* expand_tilde(const char*); char* read_file(const char*); char* host_from_url(const char*); + +void nonascii_replace(char*, char); void remove_all_chars(char*, char); void remove_all_tags(char*); + int open_url(const char*); -void nonascii_replace(char*, char); -void* ecalloc(size_t, size_t);