commit a987ad889c74589f729bfa3f1ab58530720d3bec
parent 87897854adc107cbf416f99f5d511e86bdd75c7f
Author: citbl <citbl@citbl.org>
Date: Fri, 10 Oct 2025 20:25:46 +1000
refactoring
Diffstat:
| M | TODO | | | 1 | + |
| M | db.c | | | 4 | +++- |
| A | utils.c | | | 283 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | utils.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