readr

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

commit a987ad889c74589f729bfa3f1ab58530720d3bec
parent 87897854adc107cbf416f99f5d511e86bdd75c7f
Author: citbl <citbl@citbl.org>
Date:   Fri, 10 Oct 2025 20:25:46 +1000

refactoring

Diffstat:
MTODO | 1+
Mdb.c | 4+++-
Autils.c | 283+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mutils.h | 286-------------------------------------------------------------------------------
4 files changed, 287 insertions(+), 287 deletions(-)

diff --git a/TODO b/TODO @@ -2,6 +2,7 @@ show more show domains show ending (html|pdf) ~caching, persist links and seen~ +scrolling instant display background fetching status bar diff --git a/db.c b/db.c @@ -1,5 +1,7 @@ #include <stdlib.h> +#include <stdio.h> +#include <string.h> #include <sqlite3.h> #include <sys/param.h> @@ -223,7 +225,7 @@ db_mark_as_seen(int id) } void -db_close() +db_close(void) { if (sqlite3_close(db) != SQLITE_OK) { fprintf(stderr, "prepare insert failed\n"); diff --git a/utils.c b/utils.c @@ -0,0 +1,283 @@ +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <stdbool.h> + +#include "utils.h" + +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 + +const char* +get_home_dir(void) +{ + const char* p = getenv("USERPROFILE"); + if (p && *p) return p; + const char* drv = getenv("HOMEDRIVE"); + const char* pth = getenv("HOMEPATH"); + if (drv && pth) { + size_t n = strlen(drv) + strlen(pth) + 1; + char* buf = (char*)malloc(n); + if (!buf) return NULL; + snprintf(buf, n, "%s%s", drv, pth); + return buf; /* caller frees if not an env ptr */ + } + return NULL; +} +#else + +// macos/linux + +#include <pwd.h> +#include <unistd.h> + +const char* +get_home_dir(void) +{ + const char* p = getenv("HOME"); + if (p && *p) return p; + return NULL; +} + +#endif + +const char* +expand_tilde(const char* path) +{ + if (!path) return NULL; + const char* home = get_home_dir(); + if (!home) home = ""; /* degrade to empty if unknown */ + + /* Worst case: every char is '~' → expand to home each time */ + size_t hlen = strlen(home); + size_t out_cap = 1; /* for '\0' */ + for (const char* s = path; *s; ++s) + out_cap += (*s == '~') ? hlen : 1; + + char* out = (char*)malloc(out_cap); + if (!out) return NULL; + + char* w = out; + for (const char* s = path; *s; ++s) { + if (*s == '~') { + memcpy(w, home, hlen); + w += hlen; + } else { + *w++ = *s; + } + } + *w = '\0'; + +#if defined(_WIN32) + /* If get_home_dir() allocated (HOMEDRIVE+HOMEPATH path), free it. */ + const char* up = getenv("USERPROFILE"); + if (!(up && *up)) { free((void*)home); } +#endif + return out; +} + +char* +read_file(const char* file_path) +{ + FILE* fp = fopen(file_path, "rb"); + if (fp == NULL) { return NULL; } + + if (fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + printf("Failed to find the end of the file\n"); + return NULL; + } + + long file_size = ftell(fp); + + if (file_size < 0) { + fclose(fp); + printf("Failed to determine the file size\n"); + return NULL; + } + + rewind(fp); + + // check for overflow before casting + if ((unsigned long)file_size >= SIZE_MAX) { + fclose(fp); + printf("File too large to fit in memory\n"); + return NULL; + } + + char* contents = (char*)calloc(1, (size_t)file_size + 1); + if (contents == NULL) { + printf("Failed to allocate memory to read file\n"); + fclose(fp); + return NULL; + } + + size_t bytes_read = fread(contents, 1, (size_t)file_size, fp); + if (bytes_read != (size_t)file_size) { + free(contents); + fclose(fp); + printf("Failed to read the file in its entirety\n"); + return NULL; + } + + contents[file_size] = '\0'; + + fclose(fp); + return contents; +} + +char* +host_from_url(const char* url) +{ + const char *p = url, *start, *end; + + // skip scheme + const char* scheme = strstr(p, "://"); + if (scheme) + p = scheme + 3; + else if (p[0] == '/' && p[1] == '/') + p += 2; + + // IPv6 literal in brackets + if (*p == '[') { + start = p + 1; + end = strchr(start, ']'); + if (!end) return NULL; + return strndup(start, (size_t)(end - start)); + } + + start = p; + + // stop at / ? # : (drop path, query, fragment, and port) + end = strpbrk(start, "/?:#:"); + if (!end) end = start + strlen(start); + + // optional: strip leading "www." + if (end - start >= 4 && strncasecmp(start, "www.", 4) == 0) start += 4; + + return strndup(start, (size_t)(end - start)); +} + +void +remove_all_chars(char* str, char c) +{ + char *pr = str, *pw = str; + while (*pr) { + *pw = *pr++; + pw += (*pw != c); + } + *pw = '\0'; +} + +void +remove_all_tags(char* str) +{ + char *pr = str, *pw = str; + bool in_tag = false; + + while (*pr) { + if (*pr == '<') { + in_tag = true; + pr++; + continue; + } else if (*pr == '>') { + in_tag = false; + pr++; + continue; + } + if (!in_tag) { *pw++ = *pr; } + pr++; + } + *pw = '\0'; +} + +// +// open_url +// + +#define _POSIX_C_SOURCE 200809L + +#ifdef _WIN32 +#include <windows.h> +int +open_url(const char* url) +{ + HINSTANCE r = ShellExecuteA(NULL, "open", url, NULL, NULL, SW_SHOWNORMAL); + return ((uintptr_t)r > 32) ? 0 : -1; // ShellExecute error if <= 32 +} +#else +static void +shell_quote(const char* in, char* out, size_t outsz) +{ + // wrap in single quotes and escape embedded single quotes: ' -> '\'' + size_t n = 0; + if (n + 1 < outsz) out[n++] = '\''; + for (; *in && n + 4 < outsz; ++in) { + if (*in == '\'') { + memcpy(out + n, "'\\''", 4); + n += 4; + } else + out[n++] = *in; + } + if (n + 2 <= outsz) { + out[n++] = '\''; + out[n] = '\0'; + } else if (outsz) + out[outsz - 1] = '\0'; +} + +int +open_url(const char* url) +{ + char q[4096]; + char cmd[4600]; + shell_quote(url, q, sizeof q); +#ifdef __APPLE__ + snprintf(cmd, sizeof cmd, "open %s >/dev/null 2>&1 &", q); +#else + snprintf(cmd, sizeof cmd, "xdg-open %s >/dev/null 2>&1 &", q); +#endif + return system(cmd); // 0 on success from the shell +} +#endif + +// +// nonascii_replace +// + +void +nonascii_replace(char* s, char to) +{ + unsigned char *r = (unsigned char*)s, *w = (unsigned char*)s; + while (*r) { + if (*r < 0x80) { // ASCII byte + *w++ = *r++; + } else { // non-ASCII: skip UTF-8 seq, write one space + size_t need = 1; + if ((*r & 0xE0) == 0xC0) + need = 2; + else if ((*r & 0xF0) == 0xE0) + need = 3; + else if ((*r & 0xF8) == 0xF0) + need = 4; + + size_t i = 1; + if (need > 1) { + for (; i < need && r[i]; ++i) + if ((r[i] & 0xC0) != 0x80) break; + } + r += (i == need) ? need : 1; // tolerate invalid UTF-8 + + if (w == (unsigned char*)s || w[-1] != ' ') *w++ = to; + } + } + *w = '\0'; +} diff --git a/utils.h b/utils.h @@ -1,10 +1,5 @@ #pragma once -#include <stdio.h> -#include <stdlib.h> -#include <strings.h> -#include <stdbool.h> - const char* get_home_dir(void); const char* expand_tilde(const char*); char* read_file(const char*); @@ -13,284 +8,3 @@ void remove_all_chars(char*, char); void remove_all_tags(char*); int open_url(const char*); void nonascii_replace(char*, char); - -#ifdef UTILS_IMPL - -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 - -const char* -get_home_dir(void) -{ - const char* p = getenv("USERPROFILE"); - if (p && *p) return p; - const char* drv = getenv("HOMEDRIVE"); - const char* pth = getenv("HOMEPATH"); - if (drv && pth) { - size_t n = strlen(drv) + strlen(pth) + 1; - char* buf = (char*)malloc(n); - if (!buf) return NULL; - snprintf(buf, n, "%s%s", drv, pth); - return buf; /* caller frees if not an env ptr */ - } - return NULL; -} -#else - -// macos/linux - -#include <pwd.h> -#include <unistd.h> - -const char* -get_home_dir(void) -{ - const char* p = getenv("HOME"); - if (p && *p) return p; - return NULL; -} - -#endif - -const char* -expand_tilde(const char* path) -{ - if (!path) return NULL; - const char* home = get_home_dir(); - if (!home) home = ""; /* degrade to empty if unknown */ - - /* Worst case: every char is '~' → expand to home each time */ - size_t hlen = strlen(home); - size_t out_cap = 1; /* for '\0' */ - for (const char* s = path; *s; ++s) - out_cap += (*s == '~') ? hlen : 1; - - char* out = (char*)malloc(out_cap); - if (!out) return NULL; - - char* w = out; - for (const char* s = path; *s; ++s) { - if (*s == '~') { - memcpy(w, home, hlen); - w += hlen; - } else { - *w++ = *s; - } - } - *w = '\0'; - -#if defined(_WIN32) - /* If get_home_dir() allocated (HOMEDRIVE+HOMEPATH path), free it. */ - const char* up = getenv("USERPROFILE"); - if (!(up && *up)) { free((void*)home); } -#endif - return out; -} - -char* -read_file(const char* file_path) -{ - FILE* fp = fopen(file_path, "rb"); - if (fp == NULL) { return NULL; } - - if (fseek(fp, 0, SEEK_END) != 0) { - fclose(fp); - printf("Failed to find the end of the file\n"); - return NULL; - } - - long file_size = ftell(fp); - - if (file_size < 0) { - fclose(fp); - printf("Failed to determine the file size\n"); - return NULL; - } - - rewind(fp); - - // check for overflow before casting - if ((unsigned long)file_size >= SIZE_MAX) { - fclose(fp); - printf("File too large to fit in memory\n"); - return NULL; - } - - char* contents = (char*)calloc(1, (size_t)file_size + 1); - if (contents == NULL) { - printf("Failed to allocate memory to read file\n"); - fclose(fp); - return NULL; - } - - size_t bytes_read = fread(contents, 1, (size_t)file_size, fp); - if (bytes_read != (size_t)file_size) { - free(contents); - fclose(fp); - printf("Failed to read the file in its entirety\n"); - return NULL; - } - - contents[file_size] = '\0'; - - fclose(fp); - return contents; -} - -char* -host_from_url(const char* url) -{ - const char *p = url, *start, *end; - - // skip scheme - const char* scheme = strstr(p, "://"); - if (scheme) - p = scheme + 3; - else if (p[0] == '/' && p[1] == '/') - p += 2; - - // IPv6 literal in brackets - if (*p == '[') { - start = p + 1; - end = strchr(start, ']'); - if (!end) return NULL; - return strndup(start, (size_t)(end - start)); - } - - start = p; - - // stop at / ? # : (drop path, query, fragment, and port) - end = strpbrk(start, "/?:#:"); - if (!end) end = start + strlen(start); - - // optional: strip leading "www." - if (end - start >= 4 && strncasecmp(start, "www.", 4) == 0) start += 4; - - return strndup(start, (size_t)(end - start)); -} - -void -remove_all_chars(char* str, char c) -{ - char *pr = str, *pw = str; - while (*pr) { - *pw = *pr++; - pw += (*pw != c); - } - *pw = '\0'; -} - -void -remove_all_tags(char* str) -{ - char *pr = str, *pw = str; - bool in_tag = false; - - while (*pr) { - if (*pr == '<') { - in_tag = true; - pr++; - continue; - } else if (*pr == '>') { - in_tag = false; - pr++; - continue; - } - if (!in_tag) { *pw++ = *pr; } - pr++; - } - *pw = '\0'; -} - -// -// open_url -// - -#define _POSIX_C_SOURCE 200809L - -#ifdef _WIN32 -#include <windows.h> -int -open_url(const char* url) -{ - HINSTANCE r = ShellExecuteA(NULL, "open", url, NULL, NULL, SW_SHOWNORMAL); - return ((uintptr_t)r > 32) ? 0 : -1; // ShellExecute error if <= 32 -} -#else -static void -shell_quote(const char* in, char* out, size_t outsz) -{ - // wrap in single quotes and escape embedded single quotes: ' -> '\'' - size_t n = 0; - if (n + 1 < outsz) out[n++] = '\''; - for (; *in && n + 4 < outsz; ++in) { - if (*in == '\'') { - memcpy(out + n, "'\\''", 4); - n += 4; - } else - out[n++] = *in; - } - if (n + 2 <= outsz) { - out[n++] = '\''; - out[n] = '\0'; - } else if (outsz) - out[outsz - 1] = '\0'; -} - -int -open_url(const char* url) -{ - char q[4096]; - char cmd[4600]; - shell_quote(url, q, sizeof q); -#ifdef __APPLE__ - snprintf(cmd, sizeof cmd, "open %s >/dev/null 2>&1 &", q); -#else - snprintf(cmd, sizeof cmd, "xdg-open %s >/dev/null 2>&1 &", q); -#endif - return system(cmd); // 0 on success from the shell -} -#endif - -// -// nonascii_replace -// - -void -nonascii_replace(char* s, char to) -{ - unsigned char *r = (unsigned char*)s, *w = (unsigned char*)s; - while (*r) { - if (*r < 0x80) { // ASCII byte - *w++ = *r++; - } else { // non-ASCII: skip UTF-8 seq, write one space - size_t need = 1; - if ((*r & 0xE0) == 0xC0) - need = 2; - else if ((*r & 0xF0) == 0xE0) - need = 3; - else if ((*r & 0xF8) == 0xF0) - need = 4; - - size_t i = 1; - if (need > 1) { - for (; i < need && r[i]; ++i) - if ((r[i] & 0xC0) != 0x80) break; - } - r += (i == need) ? need : 1; // tolerate invalid UTF-8 - - if (w == (unsigned char*)s || w[-1] != ' ') *w++ = to; - } - } - *w = '\0'; -} - -#endif