From 13d7a0cd875bf24a5fe5bf3df8aa0d7f52ab1102 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Thu, 24 Feb 2022 00:24:20 +0100 Subject: sfeed_curses: add keybinds for home key and home and end key in urxvt \x1b[1~ home (putty and some terminals). \x1b[7~ urxvt home. \x1b[8~ urxvt end. Refactor repeated code also. --- sfeed_curses.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/sfeed_curses.c b/sfeed_curses.c index d12f170..299baaa 100644 --- a/sfeed_curses.c +++ b/sfeed_curses.c @@ -2112,23 +2112,25 @@ main(int argc, char *argv[]) case 'D': goto keyleft; /* arrow right */ case 'F': goto endpos; /* end */ case 'H': goto startpos; /* home */ + case '1': /* home */ case '4': /* end */ - if ((ch = readch()) < 0) - goto event; - if (ch == '~') - goto endpos; - continue; case '5': /* page up */ - if ((ch = readch()) < 0) - goto event; - if (ch == '~') - goto prevpage; - continue; case '6': /* page down */ + case '7': /* home: urxvt */ + case '8': /* end: urxvt */ + i = ch; if ((ch = readch()) < 0) goto event; - if (ch == '~') - goto nextpage; + if (ch == '~') { + switch (i) { + case '1': goto startpos; + case '4': goto endpos; + case '5': goto prevpage; + case '6': goto nextpage; + case '7': goto startpos; + case '8': goto endpos; + } + } continue; } break; -- cgit v1.2.3 From 3c2de33c958405c282a0d27d4bdddda434dbfa99 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Thu, 24 Feb 2022 00:37:32 +0100 Subject: sfeed_curses: fix a wrong comment about the arrow left and right keys Add autocmd comment. --- sfeed_curses.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sfeed_curses.c b/sfeed_curses.c index 299baaa..4072fdd 100644 --- a/sfeed_curses.c +++ b/sfeed_curses.c @@ -952,7 +952,7 @@ readch(void) struct timeval tv; if (cmdenv && *cmdenv) - return *(cmdenv++); + return *(cmdenv++); /* $SFEED_AUTOCMD */ for (;;) { FD_ZERO(&readfds); @@ -2108,8 +2108,8 @@ main(int argc, char *argv[]) break; case 'A': goto keyup; /* arrow up */ case 'B': goto keydown; /* arrow down */ - case 'C': goto keyright; /* arrow left */ - case 'D': goto keyleft; /* arrow right */ + case 'C': goto keyright; /* arrow right */ + case 'D': goto keyleft; /* arrow left */ case 'F': goto endpos; /* end */ case 'H': goto startpos; /* home */ case '1': /* home */ -- cgit v1.2.3 From 23736959669760b02644d3042ff67950d1d61a9e Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Thu, 24 Feb 2022 00:39:30 +0100 Subject: sfeed_curses: die(): use stdio buffered function/macro vdprintf() was changed back to vfprintf() in commit 06bb4583, but the write was not changed. Change it to be more consistent and use the stdio buffered functions/macro. --- sfeed_curses.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sfeed_curses.c b/sfeed_curses.c index 4072fdd..4be111f 100644 --- a/sfeed_curses.c +++ b/sfeed_curses.c @@ -229,8 +229,8 @@ die(const char *fmt, ...) if (saved_errno) fprintf(stderr, ": %s", strerror(saved_errno)); + putc('\n', stderr); fflush(stderr); - write(2, "\n", 1); _exit(1); } -- cgit v1.2.3 From 365d7205d357147c04375c65651df0c2c4d96f1b Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Thu, 24 Feb 2022 00:42:02 +0100 Subject: sfeed_mbox: use putc, it may be implemented as a macro No functional difference intended. --- sfeed_mbox.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sfeed_mbox.c b/sfeed_mbox.c index cd6e65d..6419d16 100644 --- a/sfeed_mbox.c +++ b/sfeed_mbox.c @@ -29,10 +29,10 @@ printcontent(const char *s, FILE *fp) { escapefrom: for (; *s == '>'; s++) - fputc('>', fp); + putc('>', fp); /* escape "From ", mboxrd-style. */ if (!strncmp(s, "From ", 5)) - fputc('>', fp); + putc('>', fp); for (; *s; s++) { switch (*s) { @@ -40,15 +40,15 @@ escapefrom: s++; switch (*s) { case 'n': - fputc('\n', fp); + putc('\n', fp); s++; goto escapefrom; - case '\\': fputc('\\', fp); break; - case 't': fputc('\t', fp); break; + case '\\': putc('\\', fp); break; + case 't': putc('\t', fp); break; } break; default: - fputc(*s, fp); break; + putc(*s, fp); break; } } } -- cgit v1.2.3 From d14aac682c5d52e3dacaadd13f4e5dd459c5bb9c Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Thu, 24 Feb 2022 00:49:18 +0100 Subject: sfeed_curses.1: clarify the 'R' keybind or SIGHUP will reload the url file also ... if it is set of course. --- sfeed_curses.1 | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sfeed_curses.1 b/sfeed_curses.1 index f507475..703d7a3 100644 --- a/sfeed_curses.1 +++ b/sfeed_curses.1 @@ -1,4 +1,4 @@ -.Dd February 4, 2022 +.Dd February 24, 2022 .Dt SFEED_CURSES 1 .Os .Sh NAME @@ -83,6 +83,9 @@ Go to the next feed in the feeds pane and open it. Redraw screen. .It R Reload all feed files which were specified as arguments on startup. +If +.Ev SFEED_URL_FILE +is set, it will reload the URLs from this file also. .It m Toggle mouse-mode. It supports xterm X10 and extended SGR encoding. @@ -182,6 +185,9 @@ Switch to the feeds pane. .Bl -tag -width Ds .It SIGHUP Reload all feed files which were specified as arguments on startup. +If +.Ev SFEED_URL_FILE +is set, it will reload the URLs from this file also. .It SIGINT Interrupt: when searching it cancels the line editor, otherwise it quits. .It SIGTERM -- cgit v1.2.3 From 43a4e46c50ab81bd82b856e244d9b186dfd0cdf9 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Thu, 24 Feb 2022 00:50:08 +0100 Subject: sfeed_curses: fix a redraw when reloading a file when reading from stdin When reading data from stdin and changing the URL file externally in some way and then pressing 'R' would not redraw (and highlight/unhighlight) the marked items. --- sfeed_curses.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sfeed_curses.c b/sfeed_curses.c index 4be111f..c911f67 100644 --- a/sfeed_curses.c +++ b/sfeed_curses.c @@ -1205,6 +1205,7 @@ updatenewitems(struct feed *f) size_t i; p = &panes[PaneItems]; + p->dirty = 1; f->totalnew = 0; for (i = 0; i < p->nrows; i++) { row = &(p->rows[i]); /* do not use pane_row_get() */ @@ -1238,8 +1239,6 @@ feed_load(struct feed *f, FILE *fp) p->rows[i].data = &(items.items[i]); /* do not use pane_row_get() */ updatenewitems(f); - - p->dirty = 1; } void -- cgit v1.2.3 From 4a9456848a45edbc097770df20dec216f44ca0f5 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Thu, 24 Feb 2022 19:25:06 +0100 Subject: sfeed_curses: remove continue in application key handling On an unknown or invalid sequence just use the key handling like the other keys do. --- sfeed_curses.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sfeed_curses.c b/sfeed_curses.c index c911f67..da0b0a9 100644 --- a/sfeed_curses.c +++ b/sfeed_curses.c @@ -2130,7 +2130,6 @@ main(int argc, char *argv[]) case '8': goto endpos; } } - continue; } break; keyup: -- cgit v1.2.3 From c1d2ebe8cac049808be400221c3ed4bda780cab7 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 25 Feb 2022 00:27:22 +0100 Subject: sfeed_curses: cast character for SFEED_AUTOCMD to unsigned char Otherwise a character like \xa0 (160) would be negative and goto the event, since a negative return value in readch() is used for errors or reserved for signal handling. Noticed while testing mouse X10 encoding with extended buttons, like button 7: SFEED_AUTOCMD="l$(printf '\x1b[M\xa0!!')j" ./sfeed_curses ~/.sfeed/feeds/* --- sfeed_curses.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sfeed_curses.c b/sfeed_curses.c index da0b0a9..4d77504 100644 --- a/sfeed_curses.c +++ b/sfeed_curses.c @@ -951,8 +951,10 @@ readch(void) fd_set readfds; struct timeval tv; - if (cmdenv && *cmdenv) - return *(cmdenv++); /* $SFEED_AUTOCMD */ + if (cmdenv && *cmdenv) { + b = *(cmdenv++); /* $SFEED_AUTOCMD */ + return (int)b; + } for (;;) { FD_ZERO(&readfds); -- cgit v1.2.3 From 8c0f231f734b5a9d736420eece311a87aa041103 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sat, 26 Feb 2022 12:29:39 +0100 Subject: README: example scripts to count new and unread items --- README | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/README b/README index 40037ab..398b96d 100644 --- a/README +++ b/README @@ -963,6 +963,58 @@ TSV format. - - - +Counting unread and total items +------------------------------- + +It can be useful to show the counts of unread items, for example in a +windowmanager or statusbar. + +The below example script counts the items of the last day in the same way the +formatting tools do: + + #!/bin/sh + # Count the new items of the last day. + LC_ALL=C awk -F '\t' -v "old=$(($(date +'%s') - 86400))" ' + { + total++; + } + int($1) >= old { + totalnew++; + } + END { + print "New: " totalnew; + print "Total: " total; + }' ~/.sfeed/urls ~/.sfeed/feeds/* + +The below example script counts the unread items using the sfeed_curses URL +file: + + #!/bin/sh + # Count the unread and total items from feeds using the URL file. + LC_ALL=C awk -F '\t' ' + # URL file: amount of fields is 1. + NF == 1 { + u[$0] = 1; # lookup table of URLs. + next; + } + # feed file: check by URL or id. + { + total++; + if (length($3)) { + if (u[$3]) + read++; + } else if (length($6)) { + if (u[$6]) + read++; + } + } + END { + print "Unread: " (total - read); + print "Total: " total; + }' ~/.sfeed/urls ~/.sfeed/feeds/* + +- - - + Running custom commands inside the program ------------------------------------------ -- cgit v1.2.3 From 489a3b12923ea6dc6a278bed023a40ab2b5375f0 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sat, 26 Feb 2022 12:37:38 +0100 Subject: README: small rewording --- README | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README b/README index 398b96d..459f513 100644 --- a/README +++ b/README @@ -1015,8 +1015,8 @@ file: - - - -Running custom commands inside the program ------------------------------------------- +Running custom commands inside the sfeed_curses program +------------------------------------------------------- Running commands inside the sfeed_curses program can be useful for example to sync items or mark all items across all feeds as read. It can be comfortable to @@ -1035,13 +1035,12 @@ or forkexec((char *[]) { "syncnews.sh", NULL }, 1); break; -The specified script should be in $PATH or an absolute path. +The specified script should be in $PATH or be an absolute path. Example of a `markallread.sh` shellscript to mark all URLs as read: #!/bin/sh # mark all items/URLs as read. - tmp=$(mktemp) (cat ~/.sfeed/urls; cut -f 3 ~/.sfeed/feeds/*) | \ awk '!x[$0]++' > "$tmp" && -- cgit v1.2.3 From 26c14ee6dff96b1f7812070a17db161090ef87bd Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sat, 5 Mar 2022 11:20:22 +0100 Subject: README: add a note about mouse button encoding issues in some terminals Maybe this is out-of-scope for the sfeed documentation though. --- README | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README b/README index 459f513..40c5483 100644 --- a/README +++ b/README @@ -1081,6 +1081,9 @@ testing sfeed_curses. Some of them might be fixed already upstream: middle-button, right-button is incorrect / reversed. - putty: the full reset attribute (ESC c, typically `rs1`) does not reset the window title. +- Mouse button encoding for extended buttons (like side-buttons) in some + terminals are unsupported or map to the same button: for example side-buttons 7 + and 8 map to the scroll buttons 4 and 5 in urxvt. License -- cgit v1.2.3 From 4925af753dd44b935588b83c814301244a09bdd7 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Mon, 7 Mar 2022 12:11:31 +0100 Subject: README: update codemadness.org URLs and use the full content feed by default --- README | 4 ++-- sfeedrc.5 | 4 ++-- sfeedrc.example | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README b/README index 40c5483..a313cd8 100644 --- a/README +++ b/README @@ -245,8 +245,8 @@ Find RSS/Atom feed URLs from a webpage: output example: - https://codemadness.org/blog/rss.xml application/rss+xml - https://codemadness.org/blog/atom.xml application/atom+xml + https://codemadness.org/atom.xml application/atom+xml + https://codemadness.org/atom_content.xml application/atom+xml - - - diff --git a/sfeedrc.5 b/sfeedrc.5 index aedbf59..95ffc04 100644 --- a/sfeedrc.5 +++ b/sfeedrc.5 @@ -1,4 +1,4 @@ -.Dd August 5, 2021 +.Dd March 7, 2022 .Dt SFEEDRC 5 .Os .Sh NAME @@ -136,7 +136,7 @@ shown below: # list of feeds to fetch: feeds() { # feed [basesiteurl] [encoding] - feed "codemadness" "https://www.codemadness.nl/atom.xml" + feed "codemadness" "https://www.codemadness.org/atom_content.xml" feed "explosm" "http://feeds.feedburner.com/Explosm" feed "golang github releases" "https://github.com/golang/go/releases.atom" feed "linux kernel" "https://www.kernel.org/feeds/kdist.xml" "https://www.kernel.org" diff --git a/sfeedrc.example b/sfeedrc.example index 5ead13d..28630ec 100644 --- a/sfeedrc.example +++ b/sfeedrc.example @@ -3,7 +3,7 @@ # list of feeds to fetch: feeds() { # feed [basesiteurl] [encoding] - feed "codemadness" "https://www.codemadness.nl/atom.xml" + feed "codemadness" "https://www.codemadness.org/atom_content.xml" feed "explosm" "http://feeds.feedburner.com/Explosm" feed "golang github releases" "https://github.com/golang/go/releases.atom" feed "linux kernel" "https://www.kernel.org/feeds/kdist.xml" "https://www.kernel.org" -- cgit v1.2.3 From 3dbdf1ccfa5f109f8d7489f913ed57aa8e9b5f11 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Mon, 7 Mar 2022 12:13:15 +0100 Subject: bump version to 1.3 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index de4d489..7d35bb1 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .POSIX: NAME = sfeed -VERSION = 1.2 +VERSION = 1.3 # curses theme, see themes/ directory. SFEED_THEME = mono -- cgit v1.2.3 From b6f7a3fe15f2f253a1653454f514b467aa20f821 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Mon, 14 Mar 2022 13:25:52 +0100 Subject: improve time(NULL) error checking Use errx, time(NULL) does not set errno. For sfeed_curses reset errno so it doesn't print a random error if it failed. POSIX recommends checking against (time_t)-1 on failure. Note that some implementation, like the OpenBSD man page says time() cannot fail. --- sfeed_atom.c | 4 ++-- sfeed_curses.c | 3 ++- sfeed_frames.c | 4 ++-- sfeed_gopher.c | 4 ++-- sfeed_html.c | 4 ++-- sfeed_mbox.c | 4 ++-- sfeed_plain.c | 4 ++-- 7 files changed, 14 insertions(+), 13 deletions(-) diff --git a/sfeed_atom.c b/sfeed_atom.c index 1dff75b..2aea54e 100644 --- a/sfeed_atom.c +++ b/sfeed_atom.c @@ -124,8 +124,8 @@ main(int argc, char *argv[]) if (pledge(argc == 1 ? "stdio" : "stdio rpath", NULL) == -1) err(1, "pledge"); - if ((now = time(NULL)) == -1) - err(1, "time"); + if ((now = time(NULL)) == (time_t)-1) + errx(1, "time"); if (!(tm = gmtime_r(&now, &tmnow))) err(1, "gmtime_r"); diff --git a/sfeed_curses.c b/sfeed_curses.c index 4d77504..1c6bc24 100644 --- a/sfeed_curses.c +++ b/sfeed_curses.c @@ -1308,7 +1308,8 @@ feeds_load(struct feed *feeds, size_t nfeeds) struct feed *f; size_t i; - if ((comparetime = time(NULL)) == -1) + errno = 0; + if ((comparetime = time(NULL)) == (time_t)-1) die("time"); /* 1 day is old news */ comparetime -= 86400; diff --git a/sfeed_frames.c b/sfeed_frames.c index 89838cc..d345c60 100644 --- a/sfeed_frames.c +++ b/sfeed_frames.c @@ -86,8 +86,8 @@ main(int argc, char *argv[]) if (!(feeds = calloc(argc, sizeof(struct feed)))) err(1, "calloc"); - if ((comparetime = time(NULL)) == -1) - err(1, "time"); + if ((comparetime = time(NULL)) == (time_t)-1) + errx(1, "time"); /* 1 day is old news */ comparetime -= 86400; diff --git a/sfeed_gopher.c b/sfeed_gopher.c index 9e8e10a..0b95b3d 100644 --- a/sfeed_gopher.c +++ b/sfeed_gopher.c @@ -137,8 +137,8 @@ main(int argc, char *argv[]) err(1, "pledge"); } - if ((comparetime = time(NULL)) == -1) - err(1, "time"); + if ((comparetime = time(NULL)) == (time_t)-1) + errx(1, "time"); /* 1 day is old news */ comparetime -= 86400; diff --git a/sfeed_html.c b/sfeed_html.c index d8abda7..240ea66 100644 --- a/sfeed_html.c +++ b/sfeed_html.c @@ -86,8 +86,8 @@ main(int argc, char *argv[]) if (!(feeds = calloc(argc, sizeof(struct feed)))) err(1, "calloc"); - if ((comparetime = time(NULL)) == -1) - err(1, "time"); + if ((comparetime = time(NULL)) == (time_t)-1) + errx(1, "time"); /* 1 day is old news */ comparetime -= 86400; diff --git a/sfeed_mbox.c b/sfeed_mbox.c index 6419d16..c2827d4 100644 --- a/sfeed_mbox.c +++ b/sfeed_mbox.c @@ -152,8 +152,8 @@ main(int argc, char *argv[]) user = "you"; if (gethostname(host, sizeof(host)) == -1) err(1, "gethostname"); - if ((now = time(NULL)) == -1) - err(1, "time"); + if ((now = time(NULL)) == (time_t)-1) + errx(1, "time"); if (!gmtime_r(&now, &tmnow)) err(1, "gmtime_r: can't get current time"); if (!strftime(mtimebuf, sizeof(mtimebuf), "%a %b %d %H:%M:%S %Y", &tmnow)) diff --git a/sfeed_plain.c b/sfeed_plain.c index df93a5a..45785e7 100644 --- a/sfeed_plain.c +++ b/sfeed_plain.c @@ -62,8 +62,8 @@ main(int argc, char *argv[]) if (pledge(argc == 1 ? "stdio" : "stdio rpath", NULL) == -1) err(1, "pledge"); - if ((comparetime = time(NULL)) == -1) - err(1, "time"); + if ((comparetime = time(NULL)) == (time_t)-1) + errx(1, "time"); /* 1 day is old news */ comparetime -= 86400; -- cgit v1.2.3 From 813a96b517ae96716fb018ff93ab2d6a4bbcda95 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Mon, 14 Mar 2022 14:07:20 +0100 Subject: sfeed_curses: use ttywrite() for writing to the tty consistently This should also suppress a compiler warning of an unchecked write() return value. --- sfeed_curses.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sfeed_curses.c b/sfeed_curses.c index 1c6bc24..d0ca7dd 100644 --- a/sfeed_curses.c +++ b/sfeed_curses.c @@ -986,7 +986,7 @@ lineeditor(void) int ch; for (;;) { - if (nchars + 1 >= cap) { + if (nchars + 2 >= cap) { cap = cap ? cap * 2 : 32; input = erealloc(input, cap); } @@ -999,11 +999,11 @@ lineeditor(void) if (!nchars) continue; input[--nchars] = '\0'; - write(1, "\b \b", 3); /* back, blank, back */ - continue; + ttywrite("\b \b"); /* back, blank, back */ } else if (ch >= ' ') { input[nchars] = ch; - write(1, &input[nchars], 1); + input[nchars + 1] = '\0'; + ttywrite(&input[nchars]); nchars++; } else if (ch < 0) { switch (sigstate) { -- cgit v1.2.3 From fad48ffa27af96ee0d9489ded88f80c1eeb238dc Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Mon, 14 Mar 2022 19:22:42 +0100 Subject: stricter error checking in file streams (input, output) This also makes the programs exit with a non-zero status when a read or write error occurs. This makes checking the exit status more reliable in scripts. A simple example to simulate a disk with no space left: curl -s 'https://codemadness.org/atom.xml' | sfeed > f /mnt/test: write failed, file system is full echo $? 0 Which now produces: curl -s 'https://codemadness.org/atom.xml' | sfeed > f /mnt/test: write failed, file system is full write error: echo $? 1 Tested with a small mfs on OpenBSD, fstab entry: swap /mnt/test mfs rw,nodev,nosuid,-s=1M 0 0 --- sfeed.c | 6 ++++++ sfeed_atom.c | 9 ++++++--- sfeed_frames.c | 15 +++++++++++---- sfeed_gopher.c | 10 +++++++--- sfeed_html.c | 11 ++++++----- sfeed_mbox.c | 9 ++++++--- sfeed_opml_import.c | 3 +++ sfeed_plain.c | 8 +++++--- sfeed_twtxt.c | 9 ++++++--- sfeed_web.c | 3 +++ sfeed_xmlenc.c | 3 +++ util.c | 10 ++++++++++ util.h | 1 + 13 files changed, 73 insertions(+), 24 deletions(-) diff --git a/sfeed.c b/sfeed.c index 4dd89c1..f2aa6b0 100644 --- a/sfeed.c +++ b/sfeed.c @@ -679,6 +679,9 @@ printfields(void) putchar(FieldSeparator); string_print_trimmed_multi(&ctx.fields[FeedFieldCategory].str); putchar('\n'); + + if (ferror(stdout)) /* check for errors but do not flush */ + checkfileerror(stdout, "", 'w'); } static int @@ -1059,5 +1062,8 @@ main(int argc, char *argv[]) /* NOTE: getnext is defined in xml.h for inline optimization */ xml_parse(&parser); + checkfileerror(stdin, "", 'r'); + checkfileerror(stdout, "", 'w'); + return 0; } diff --git a/sfeed_atom.c b/sfeed_atom.c index 2aea54e..07c34cd 100644 --- a/sfeed_atom.c +++ b/sfeed_atom.c @@ -43,7 +43,8 @@ printfeed(FILE *fp, const char *feedname) ssize_t linelen; int c; - while ((linelen = getline(&line, &linesize, fp)) > 0) { + while ((linelen = getline(&line, &linesize, fp)) > 0 && + !ferror(stdout)) { if (line[linelen - 1] == '\n') line[--linelen] = '\0'; parseline(line, fields); @@ -141,19 +142,21 @@ main(int argc, char *argv[]) if (argc == 1) { printfeed(stdin, ""); + checkfileerror(stdin, "", 'r'); } else { for (i = 1; i < argc; i++) { if (!(fp = fopen(argv[i], "r"))) err(1, "fopen: %s", argv[i]); name = ((name = strrchr(argv[i], '/'))) ? name + 1 : argv[i]; printfeed(fp, name); - if (ferror(fp)) - err(1, "ferror: %s", argv[i]); + checkfileerror(fp, argv[i], 'r'); fclose(fp); } } fputs("\n", stdout); + checkfileerror(stdout, "", 'w'); + return 0; } diff --git a/sfeed_frames.c b/sfeed_frames.c index d345c60..178a4a2 100644 --- a/sfeed_frames.c +++ b/sfeed_frames.c @@ -34,7 +34,8 @@ printfeed(FILE *fpitems, FILE *fpin, struct feed *f) } fputs("
\n", fpitems);
 
-	while ((linelen = getline(&line, &linesize, fpin)) > 0) {
+	while ((linelen = getline(&line, &linesize, fpin)) > 0 &&
+	       !ferror(fpitems)) {
 		if (line[linelen - 1] == '\n')
 			line[--linelen] = '\0';
 		parseline(line, fields);
@@ -114,6 +115,7 @@ main(int argc, char *argv[])
 	if (argc == 1) {
 		feeds[0].name = "";
 		printfeed(fpitems, stdin, &feeds[0]);
+		checkfileerror(stdin, "", 'r');
 	} else {
 		for (i = 1; i < argc; i++) {
 			name = ((name = strrchr(argv[i], '/'))) ? name + 1 : argv[i];
@@ -122,8 +124,8 @@ main(int argc, char *argv[])
 			if (!(fp = fopen(argv[i], "r")))
 				err(1, "fopen: %s", argv[i]);
 			printfeed(fpitems, fp, &feeds[i - 1]);
-			if (ferror(fp))
-				err(1, "ferror: %s", argv[i]);
+			checkfileerror(fp, argv[i], 'r');
+			checkfileerror(fpitems, "items.html", 'w');
 			fclose(fp);
 		}
 	}
@@ -174,10 +176,15 @@ main(int argc, char *argv[])
 	      "\n"
 	      "\n", fpindex);
 
+	checkfileerror(fpindex, "index.html", 'w');
+	checkfileerror(fpitems, "items.html", 'w');
+
 	fclose(fpindex);
 	fclose(fpitems);
-	if (fpmenu)
+	if (fpmenu) {
+		checkfileerror(fpmenu, "menu.html", 'w');
 		fclose(fpmenu);
+	}
 
 	return 0;
 }
diff --git a/sfeed_gopher.c b/sfeed_gopher.c
index 0b95b3d..7da41f6 100644
--- a/sfeed_gopher.c
+++ b/sfeed_gopher.c
@@ -50,7 +50,8 @@ printfeed(FILE *fpitems, FILE *fpin, struct feed *f)
 		fprintf(fpitems, "i\t\t%s\t%s\r\n", host, port);
 	}
 
-	while ((linelen = getline(&line, &linesize, fpin)) > 0) {
+	while ((linelen = getline(&line, &linesize, fpin)) > 0 &&
+	       !ferror(fpitems)) {
 		if (line[linelen - 1] == '\n')
 			line[--linelen] = '\0';
 		parseline(line, fields);
@@ -150,6 +151,8 @@ main(int argc, char *argv[])
 	if (argc == 1) {
 		f.name = "";
 		printfeed(stdout, stdin, &f);
+		checkfileerror(stdin, "", 'r');
+		checkfileerror(stdout, "", 'w');
 	} else {
 		if ((p = getenv("SFEED_GOPHER_PATH")))
 			prefixpath = p;
@@ -172,8 +175,8 @@ main(int argc, char *argv[])
 			if (!(fpitems = fopen(path, "wb")))
 				err(1, "fopen");
 			printfeed(fpitems, fp, &f);
-			if (ferror(fp))
-				err(1, "ferror: %s", argv[i]);
+			checkfileerror(fp, argv[i], 'r');
+			checkfileerror(fpitems, path, 'w');
 			fclose(fp);
 			fclose(fpitems);
 
@@ -186,6 +189,7 @@ main(int argc, char *argv[])
 			fprintf(fpindex, "\t%s\t%s\r\n", host, port);
 		}
 		fputs(".\r\n", fpindex);
+		checkfileerror(fpindex, "index", 'w');
 		fclose(fpindex);
 	}
 
diff --git a/sfeed_html.c b/sfeed_html.c
index 240ea66..f3cff2b 100644
--- a/sfeed_html.c
+++ b/sfeed_html.c
@@ -34,7 +34,8 @@ printfeed(FILE *fp, struct feed *f)
 	}
 	fputs("
\n", stdout);
 
-	while ((linelen = getline(&line, &linesize, fp)) > 0) {
+	while ((linelen = getline(&line, &linesize, fp)) > 0 &&
+	       !ferror(stdout)) {
 		if (line[linelen - 1] == '\n')
 			line[--linelen] = '\0';
 		parseline(line, fields);
@@ -109,8 +110,7 @@ main(int argc, char *argv[])
 	if (argc == 1) {
 		feeds[0].name = "";
 		printfeed(stdin, &feeds[0]);
-		if (ferror(stdin))
-			err(1, "ferror: :");
+		checkfileerror(stdin, "", 'r');
 	} else {
 		for (i = 1; i < argc; i++) {
 			name = ((name = strrchr(argv[i], '/'))) ? name + 1 : argv[i];
@@ -118,8 +118,7 @@ main(int argc, char *argv[])
 			if (!(fp = fopen(argv[i], "r")))
 				err(1, "fopen: %s", argv[i]);
 			printfeed(fp, &feeds[i - 1]);
-			if (ferror(fp))
-				err(1, "ferror: %s", argv[i]);
+			checkfileerror(fp, argv[i], 'r');
 			fclose(fp);
 		}
 	}
@@ -150,5 +149,7 @@ main(int argc, char *argv[])
 	fprintf(stdout, "\t\n\t(%lu/%lu) - Newsfeed\n\n",
 	        totalnew, total);
 
+	checkfileerror(stdout, "", 'w');
+
 	return 0;
 }
diff --git a/sfeed_mbox.c b/sfeed_mbox.c
index c2827d4..33c9ec6 100644
--- a/sfeed_mbox.c
+++ b/sfeed_mbox.c
@@ -63,7 +63,8 @@ printfeed(FILE *fp, const char *feedname)
 	ssize_t linelen;
 	int ishtml;
 
-	while ((linelen = getline(&line, &linesize, fp)) > 0) {
+	while ((linelen = getline(&line, &linesize, fp)) > 0 &&
+	       !ferror(stdout)) {
 		if (line[linelen - 1] == '\n')
 			line[--linelen] = '\0';
 		hash = djb2((unsigned char *)line, 5381ULL);
@@ -163,17 +164,19 @@ main(int argc, char *argv[])
 
 	if (argc == 1) {
 		printfeed(stdin, "");
+		checkfileerror(stdin, "", 'r');
 	} else {
 		for (i = 1; i < argc; i++) {
 			if (!(fp = fopen(argv[i], "r")))
 				err(1, "fopen: %s", argv[i]);
 			name = ((name = strrchr(argv[i], '/'))) ? name + 1 : argv[i];
 			printfeed(fp, name);
-			if (ferror(fp))
-				err(1, "ferror: %s", argv[i]);
+			checkfileerror(fp, argv[i], 'r');
 			fclose(fp);
 		}
 	}
 
+	checkfileerror(stdout, "", 'w');
+
 	return 0;
 }
diff --git a/sfeed_opml_import.c b/sfeed_opml_import.c
index 0844b5c..6c0bd0e 100644
--- a/sfeed_opml_import.c
+++ b/sfeed_opml_import.c
@@ -101,5 +101,8 @@ main(void)
 	xml_parse(&parser);
 	fputs("}\n", stdout);
 
+	checkfileerror(stdin, "", 'r');
+	checkfileerror(stdout, "", 'w');
+
 	return 0;
 }
diff --git a/sfeed_plain.c b/sfeed_plain.c
index 45785e7..c954eff 100644
--- a/sfeed_plain.c
+++ b/sfeed_plain.c
@@ -19,7 +19,8 @@ printfeed(FILE *fp, const char *feedname)
 	time_t parsedtime;
 	ssize_t linelen;
 
-	while ((linelen = getline(&line, &linesize, fp)) > 0) {
+	while ((linelen = getline(&line, &linesize, fp)) > 0 &&
+	       !ferror(stdout)) {
 		if (line[linelen - 1] == '\n')
 			line[--linelen] = '\0';
 		parseline(line, fields);
@@ -69,17 +70,18 @@ main(int argc, char *argv[])
 
 	if (argc == 1) {
 		printfeed(stdin, "");
+		checkfileerror(stdin, "", 'r');
 	} else {
 		for (i = 1; i < argc; i++) {
 			if (!(fp = fopen(argv[i], "r")))
 				err(1, "fopen: %s", argv[i]);
 			name = ((name = strrchr(argv[i], '/'))) ? name + 1 : argv[i];
 			printfeed(fp, name);
-			if (ferror(fp))
-				err(1, "ferror: %s", argv[i]);
+			checkfileerror(fp, argv[i], 'r');
 			fclose(fp);
 		}
 	}
+	checkfileerror(stdout, "", 'w');
 
 	return 0;
 }
diff --git a/sfeed_twtxt.c b/sfeed_twtxt.c
index 6107fc7..8514bd6 100644
--- a/sfeed_twtxt.c
+++ b/sfeed_twtxt.c
@@ -17,7 +17,8 @@ printfeed(FILE *fp, const char *feedname)
 	time_t parsedtime;
 	ssize_t linelen;
 
-	while ((linelen = getline(&line, &linesize, fp)) > 0) {
+	while ((linelen = getline(&line, &linesize, fp)) > 0 &&
+	       !ferror(stdout)) {
 		if (line[linelen - 1] == '\n')
 			line[--linelen] = '\0';
 		parseline(line, fields);
@@ -54,17 +55,19 @@ main(int argc, char *argv[])
 
 	if (argc == 1) {
 		printfeed(stdin, "");
+		checkfileerror(stdin, "", 'r');
 	} else {
 		for (i = 1; i < argc; i++) {
 			if (!(fp = fopen(argv[i], "r")))
 				err(1, "fopen: %s", argv[i]);
 			name = ((name = strrchr(argv[i], '/'))) ? name + 1 : argv[i];
 			printfeed(fp, name);
-			if (ferror(fp))
-				err(1, "ferror: %s", argv[i]);
+			checkfileerror(fp, argv[i], 'r');
 			fclose(fp);
 		}
 	}
 
+	checkfileerror(stdout, "", 'w');
+
 	return 0;
 }
diff --git a/sfeed_web.c b/sfeed_web.c
index 2d77d4c..704afe8 100644
--- a/sfeed_web.c
+++ b/sfeed_web.c
@@ -136,5 +136,8 @@ main(int argc, char *argv[])
 	/* NOTE: getnext is defined in xml.h for inline optimization */
 	xml_parse(&parser);
 
+	checkfileerror(stdin, "", 'r');
+	checkfileerror(stdout, "", 'w');
+
 	return 0;
 }
diff --git a/sfeed_xmlenc.c b/sfeed_xmlenc.c
index c6a43d4..67d7b0f 100644
--- a/sfeed_xmlenc.c
+++ b/sfeed_xmlenc.c
@@ -56,5 +56,8 @@ main(void)
 	/* NOTE: getnext is defined in xml.h for inline optimization */
 	xml_parse(&parser);
 
+	checkfileerror(stdin, "", 'r');
+	checkfileerror(stdout, "", 'w');
+
 	return 0;
 }
diff --git a/util.c b/util.c
index db43e0c..ed0b5c9 100644
--- a/util.c
+++ b/util.c
@@ -46,6 +46,16 @@ errx(int exitstatus, const char *fmt, ...)
 	exit(exitstatus);
 }
 
+/* Handle read or write errors for a FILE * stream */
+void
+checkfileerror(FILE *fp, const char *name, int mode)
+{
+	if (mode == 'r' && ferror(fp))
+		errx(1, "read error: %s", name);
+	else if (mode == 'w' && (fflush(fp) || ferror(fp)))
+		errx(1, "write error: %s", name);
+}
+
 /* strcasestr() included for portability */
 char *
 strcasestr(const char *h, const char *n)
diff --git a/util.h b/util.h
index 6115dcf..8053750 100644
--- a/util.h
+++ b/util.h
@@ -64,6 +64,7 @@ int uri_hasscheme(const char *);
 int uri_makeabs(struct uri *, struct uri *, struct uri *);
 int uri_parse(const char *, struct uri *);
 
+void checkfileerror(FILE *, const char *, int);
 void parseline(char *, char *[FieldLast]);
 void printutf8pad(FILE *, const char *, size_t, int);
 int  strtotime(const char *, time_t *);
-- 
cgit v1.2.3


From 0e26df03e5ab2387999492c2cc7c8cf379c0536d Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Tue, 15 Mar 2022 16:39:12 +0100
Subject: style.css: add dark mode support for example file

This stylesheet is for sfeed_frames and sfeed_html.
---
 style.css | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/style.css b/style.css
index 7ce6d39..e2b674e 100644
--- a/style.css
+++ b/style.css
@@ -57,3 +57,12 @@ body.frame {
 body.frame #sidebar br {
 	display: none;
 }
+@media (prefers-color-scheme: dark) {
+	body {
+		background-color: #000;
+		color: #bdbdbd;
+	}
+	a {
+		color: #56c8ff;
+	}
+}
-- 
cgit v1.2.3


From cae75cf30a64158f2120f28df0a02475fb970bcc Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Wed, 16 Mar 2022 18:57:02 +0100
Subject: sfeed_curses: refactor DEC function key parsing

First it expected a single digit number, but this way it makes it more easy and
logical to add Sun function keys: ESC [ num z

For archive/reference Sun function keys relevant to sfeed_curses:

num =
214: home
216: page up
220: end
222: page down

Noticed on OpenIndiana/Illumos.
---
 sfeed_curses.c | 36 +++++++++++++++++++-----------------
 1 file changed, 19 insertions(+), 17 deletions(-)

diff --git a/sfeed_curses.c b/sfeed_curses.c
index d0ca7dd..484593b 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -2114,24 +2114,26 @@ main(int argc, char *argv[])
 			case 'D': goto keyleft;  /* arrow left */
 			case 'F': goto endpos;   /* end */
 			case 'H': goto startpos; /* home */
-			case '1': /* home */
-			case '4': /* end */
-			case '5': /* page up */
-			case '6': /* page down */
-			case '7': /* home: urxvt */
-			case '8': /* end: urxvt */
-				i = ch;
-				if ((ch = readch()) < 0)
-					goto event;
-				if (ch == '~') {
-					switch (i) {
-					case '1': goto startpos;
-					case '4': goto endpos;
-					case '5': goto prevpage;
-					case '6': goto nextpage;
-					case '7': goto startpos;
-					case '8': goto endpos;
+			default:
+				if (!(ch >= '0' && ch <= '9'))
+					break;
+				for (i = ch - '0'; ;) {
+					if ((ch = readch()) < 0) {
+						goto event;
+					} else if (ch >= '0' && ch <= '9') {
+						i = (i * 10) + (ch - '0');
+						continue;
+					} else if (ch == '~') { /* DEC: ESC [ num ~ */
+						switch (i) {
+						case 1: goto startpos; /* home */
+						case 4: goto endpos;   /* end */
+						case 5: goto prevpage; /* page up */
+						case 6: goto nextpage; /* page down */
+						case 7: goto startpos; /* home: urxvt */
+						case 8: goto endpos;   /* end: urxvt */
+						}
 					}
+					break;
 				}
 			}
 			break;
-- 
cgit v1.2.3


From 49d22d1256498c449b44a80901887bc58b67148c Mon Sep 17 00:00:00 2001
From: NRK 
Date: Sat, 19 Mar 2022 16:45:02 +0600
Subject: rebuild sfeed_curses if the same theme changes

---
 Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index 7d35bb1..349bbf2 100644
--- a/Makefile
+++ b/Makefile
@@ -112,7 +112,7 @@ ${OBJ}: ${HDR}
 .c.o:
 	${CC} -o $@ -c $< ${SFEED_CFLAGS} ${SFEED_CPPFLAGS}
 
-sfeed_curses.o: sfeed_curses.c
+sfeed_curses.o: sfeed_curses.c themes/${SFEED_THEME}.h
 	${CC} -o $@ -c sfeed_curses.c ${SFEED_CURSES_CFLAGS} ${SFEED_CURSES_CPPFLAGS}
 
 sfeed_curses: ${LIB} sfeed_curses.o
-- 
cgit v1.2.3


From 665ce267d994f7feb090c0c695c8462ae2d87a50 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sun, 20 Mar 2022 13:19:12 +0100
Subject: check write errors on the output stream after each feed

This will detect write errors sooner.
---
 sfeed_atom.c  | 1 +
 sfeed_html.c  | 1 +
 sfeed_mbox.c  | 1 +
 sfeed_plain.c | 1 +
 sfeed_twtxt.c | 1 +
 5 files changed, 5 insertions(+)

diff --git a/sfeed_atom.c b/sfeed_atom.c
index 07c34cd..3ce5cf0 100644
--- a/sfeed_atom.c
+++ b/sfeed_atom.c
@@ -150,6 +150,7 @@ main(int argc, char *argv[])
 			name = ((name = strrchr(argv[i], '/'))) ? name + 1 : argv[i];
 			printfeed(fp, name);
 			checkfileerror(fp, argv[i], 'r');
+			checkfileerror(stdout, "", 'w');
 			fclose(fp);
 		}
 	}
diff --git a/sfeed_html.c b/sfeed_html.c
index f3cff2b..ce96687 100644
--- a/sfeed_html.c
+++ b/sfeed_html.c
@@ -119,6 +119,7 @@ main(int argc, char *argv[])
 				err(1, "fopen: %s", argv[i]);
 			printfeed(fp, &feeds[i - 1]);
 			checkfileerror(fp, argv[i], 'r');
+			checkfileerror(stdout, "", 'w');
 			fclose(fp);
 		}
 	}
diff --git a/sfeed_mbox.c b/sfeed_mbox.c
index 33c9ec6..30ac875 100644
--- a/sfeed_mbox.c
+++ b/sfeed_mbox.c
@@ -172,6 +172,7 @@ main(int argc, char *argv[])
 			name = ((name = strrchr(argv[i], '/'))) ? name + 1 : argv[i];
 			printfeed(fp, name);
 			checkfileerror(fp, argv[i], 'r');
+			checkfileerror(stdout, "", 'w');
 			fclose(fp);
 		}
 	}
diff --git a/sfeed_plain.c b/sfeed_plain.c
index c954eff..8b1f00f 100644
--- a/sfeed_plain.c
+++ b/sfeed_plain.c
@@ -78,6 +78,7 @@ main(int argc, char *argv[])
 			name = ((name = strrchr(argv[i], '/'))) ? name + 1 : argv[i];
 			printfeed(fp, name);
 			checkfileerror(fp, argv[i], 'r');
+			checkfileerror(stdout, "", 'w');
 			fclose(fp);
 		}
 	}
diff --git a/sfeed_twtxt.c b/sfeed_twtxt.c
index 8514bd6..1d8ab36 100644
--- a/sfeed_twtxt.c
+++ b/sfeed_twtxt.c
@@ -63,6 +63,7 @@ main(int argc, char *argv[])
 			name = ((name = strrchr(argv[i], '/'))) ? name + 1 : argv[i];
 			printfeed(fp, name);
 			checkfileerror(fp, argv[i], 'r');
+			checkfileerror(stdout, "", 'w');
 			fclose(fp);
 		}
 	}
-- 
cgit v1.2.3


From 587ce6fb85995f22ba6b0b9e3b944cb2896bc7cc Mon Sep 17 00:00:00 2001
From: Tommy Nguyen 
Date: Mon, 21 Mar 2022 10:37:01 +0100
Subject: sfeed_update: log FAILs to stderr

---
 sfeed_update | 19 ++++++++++++-------
 1 file changed, 12 insertions(+), 7 deletions(-)

diff --git a/sfeed_update b/sfeed_update
index ba9e242..b5f38bc 100755
--- a/sfeed_update
+++ b/sfeed_update
@@ -35,6 +35,11 @@ loadconfig() {
 
 # log(name, s)
 log() {
+	printf '[%s] %-50.50s %s\n' "$(date +'%H:%M:%S')" "$1" "$2"
+}
+
+# log_error(name, s)
+log_error() {
 	printf '[%s] %-50.50s %s\n' "$(date +'%H:%M:%S')" "$1" "$2" >&2
 }
 
@@ -97,7 +102,7 @@ _feed() {
 	[ -e "${sfeedfile}" ] || touch "${sfeedfile}" 2>/dev/null
 
 	if ! fetch "${name}" "${feedurl}" "${sfeedfile}" > "${tmpfeedfile}.fetch"; then
-		log "${name}" "FAIL (FETCH)"
+		log_error "${name}" "FAIL (FETCH)"
 		return
 	fi
 
@@ -105,20 +110,20 @@ _feed() {
 	[ "${encoding}" = "" ] && encoding=$(sfeed_xmlenc < "${tmpfeedfile}.fetch")
 
 	if ! convertencoding "${name}" "${encoding}" "utf-8" < "${tmpfeedfile}.fetch" > "${tmpfeedfile}.utf8"; then
-		log "${name}" "FAIL (ENCODING)"
+		log_error "${name}" "FAIL (ENCODING)"
 		return
 	fi
 	rm -f "${tmpfeedfile}.fetch"
 
 	# if baseurl is empty then use feedurl.
 	if ! parse "${name}" "${feedurl}" "${basesiteurl:-${feedurl}}" < "${tmpfeedfile}.utf8" > "${tmpfeedfile}.tsv"; then
-		log "${name}" "FAIL (PARSE)"
+		log_error "${name}" "FAIL (PARSE)"
 		return
 	fi
 	rm -f "${tmpfeedfile}.utf8"
 
 	if ! filter "${name}" < "${tmpfeedfile}.tsv" > "${tmpfeedfile}.filter"; then
-		log "${name}" "FAIL (FILTER)"
+		log_error "${name}" "FAIL (FILTER)"
 		return
 	fi
 	rm -f "${tmpfeedfile}.tsv"
@@ -130,20 +135,20 @@ _feed() {
 	fi
 
 	if ! merge "${name}" "${sfeedfile}" "${tmpfeedfile}.filter" > "${tmpfeedfile}.merge"; then
-		log "${name}" "FAIL (MERGE)"
+		log_error "${name}" "FAIL (MERGE)"
 		return
 	fi
 	rm -f "${tmpfeedfile}.filter"
 
 	if ! order "${name}" < "${tmpfeedfile}.merge" > "${tmpfeedfile}.order"; then
-		log "${name}" "FAIL (ORDER)"
+		log_error "${name}" "FAIL (ORDER)"
 		return
 	fi
 	rm -f "${tmpfeedfile}.merge"
 
 	# copy
 	if ! cp "${tmpfeedfile}.order" "${sfeedfile}"; then
-		log "${name}" "FAIL (COPY)"
+		log_error "${name}" "FAIL (COPY)"
 		return
 	fi
 	rm -f "${tmpfeedfile}.order"
-- 
cgit v1.2.3


From 254e16eb855460556bdf6dd50311a4114830c8d0 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Mon, 21 Mar 2022 10:58:22 +0100
Subject: sfeed_update.1: document logging and improve documentation of exit
 status

---
 sfeed_update.1 | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/sfeed_update.1 b/sfeed_update.1
index 81f2d7d..aca52de 100644
--- a/sfeed_update.1
+++ b/sfeed_update.1
@@ -1,4 +1,4 @@
-.Dd August 3, 2021
+.Dd March 21, 2022
 .Dt SFEED_UPDATE 1
 .Os
 .Sh NAME
@@ -54,8 +54,15 @@ can be sourced as a script, but it won't run the
 .Fn main
 entry-point.
 .El
+.Sh LOGGING
+When processing a feed it will log failures to stderr and non-failures to
+stdout in the format:
+.Bd -literal
+[HH:MM:SS] feedname message
+.Ed
 .Sh EXIT STATUS
 .Ex -std
+A (temporary) failure with processing a feed is not considered an error here.
 .Sh EXAMPLES
 To update your feeds and format them in various formats:
 .Bd -literal
-- 
cgit v1.2.3


From 373cabcf7b96ebafc8cd544de5f08bda3e81b375 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Mon, 21 Mar 2022 11:01:46 +0100
Subject: util.h: slightly improve portability

Tested with the scc compiler which is a pure c99 compiler.

sys/types.h is not needed here anymore (it was used for ssize_t).

Side-note: scc can now compile the sfeed parser program!
It requires these changes at the time of writing: Add a strcasecmp and
strncasecmp function and use getchar instead of getchar_unlocked.
---
 README | 2 +-
 util.h | 3 +--
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/README b/README
index a313cd8..95f4e9a 100644
--- a/README
+++ b/README
@@ -153,7 +153,7 @@ OS tested
 ---------
 
 - Linux,
-  compilers: clang, gcc, chibicc, cproc, lacc, pcc, tcc,
+  compilers: clang, gcc, chibicc, cproc, lacc, pcc, scc, tcc,
   libc: glibc, musl.
 - OpenBSD (clang, gcc).
 - NetBSD (with NetBSD curses).
diff --git a/util.h b/util.h
index 8053750..7923cdc 100644
--- a/util.h
+++ b/util.h
@@ -1,6 +1,5 @@
-#include 
-
 #include 
+#include 
 
 #ifdef __OpenBSD__
 #include 
-- 
cgit v1.2.3


From 125164fc972b3149aa65db4a839224dbf24ef8aa Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Mon, 21 Mar 2022 11:47:07 +0100
Subject: sfeed_update: set exit status non-zero if any of the feeds failed

In practise this may change the meaning of the examples:

	sfeed_update && pkill -SIGHUP sfeed_curses

An alternative:

	sfeed_update; pkill -SIGHUP sfeed_curses
---
 sfeed_update   | 11 +++++++++--
 sfeed_update.1 |  1 -
 2 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/sfeed_update b/sfeed_update
index b5f38bc..f2c97ae 100755
--- a/sfeed_update
+++ b/sfeed_update
@@ -41,6 +41,8 @@ log() {
 # log_error(name, s)
 log_error() {
 	printf '[%s] %-50.50s %s\n' "$(date +'%H:%M:%S')" "$1" "$2" >&2
+	# set error exit status indicator for parallel jobs.
+	rm -f "${sfeedtmpdir}/ok"
 }
 
 # fetch a feed via HTTP/HTTPS etc.
@@ -96,7 +98,7 @@ _feed() {
 
 	filename="$(printf '%s' "${name}" | tr '/' '_')"
 	sfeedfile="${sfeedpath}/${filename}"
-	tmpfeedfile="${sfeedtmpdir}/${filename}"
+	tmpfeedfile="${sfeedtmpdir}/feeds/${filename}"
 
 	# if file does not exist yet create it.
 	[ -e "${sfeedfile}" ] || touch "${sfeedfile}" 2>/dev/null
@@ -201,17 +203,22 @@ main() {
 	loadconfig "$1"
 	# fetch feeds and store in temporary directory.
 	sfeedtmpdir="$(mktemp -d '/tmp/sfeed_XXXXXX')"
+	mkdir -p "${sfeedtmpdir}/feeds"
+	touch "${sfeedtmpdir}/ok"
 	# make sure path exists.
 	mkdir -p "${sfeedpath}"
 	# fetch feeds specified in config file.
 	feeds
 	# wait till all feeds are fetched (concurrently).
 	[ ${signo} -eq 0 ] && wait
+	# check error exit status indicator for parallel jobs.
+	test -f "${sfeedtmpdir}/ok"
+	status=$?
 	# cleanup temporary files etc.
 	cleanup
 	# on signal SIGINT and SIGTERM exit with signal number + 128.
 	[ ${signo} -ne 0 ] && exit $((signo+128))
-	return 0
+	return ${status}
 }
 
 [ "${SFEED_UPDATE_INCLUDE}" = "1" ] || main "$@"
diff --git a/sfeed_update.1 b/sfeed_update.1
index aca52de..3edf551 100644
--- a/sfeed_update.1
+++ b/sfeed_update.1
@@ -62,7 +62,6 @@ stdout in the format:
 .Ed
 .Sh EXIT STATUS
 .Ex -std
-A (temporary) failure with processing a feed is not considered an error here.
 .Sh EXAMPLES
 To update your feeds and format them in various formats:
 .Bd -literal
-- 
cgit v1.2.3


From dd91c54d0e2ffe2e68ca719098d7d5507dbc62ea Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Mon, 21 Mar 2022 22:28:02 +0100
Subject: sfeed: make some tables const and read-only

---
 sfeed.c | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/sfeed.c b/sfeed.c
index f2aa6b0..9b3fd8f 100644
--- a/sfeed.c
+++ b/sfeed.c
@@ -127,7 +127,7 @@ static void xmltagstartparsed(XMLParser *, const char *, size_t, int);
 
 /* map tag name to TagId type */
 /* RSS, must be alphabetical order */
-static FeedTag rsstags[] = {
+static const FeedTag rsstags[] = {
 	{ STRP("author"),            RSSTagAuthor            },
 	{ STRP("category"),          RSSTagCategory          },
 	{ STRP("content:encoded"),   RSSTagContentEncoded    },
@@ -144,7 +144,7 @@ static FeedTag rsstags[] = {
 };
 
 /* Atom, must be alphabetical order */
-static FeedTag atomtags[] = {
+static const FeedTag atomtags[] = {
 	{ STRP("author"),            AtomTagAuthor           },
 	{ STRP("category"),          AtomTagCategory         },
 	{ STRP("content"),           AtomTagContent          },
@@ -161,14 +161,14 @@ static FeedTag atomtags[] = {
 };
 
 /* special case: nested  */
-static FeedTag atomtagauthor = { STRP("author"), AtomTagAuthor };
-static FeedTag atomtagauthorname = { STRP("name"), AtomTagAuthorName };
+static const FeedTag atomtagauthor = { STRP("author"), AtomTagAuthor };
+static const FeedTag atomtagauthorname = { STRP("name"), AtomTagAuthorName };
 
 /* reference to no / unknown tag */
-static FeedTag notag = { STRP(""), TagUnknown };
+static const FeedTag notag = { STRP(""), TagUnknown };
 
 /* map TagId type to RSS/Atom field, all tags must be defined */
-static int fieldmap[TagLast] = {
+static const int fieldmap[TagLast] = {
 	[TagUnknown]               = -1,
 	/* RSS */
 	[RSSTagDcdate]             = FeedFieldTime,
@@ -205,7 +205,7 @@ static int fieldmap[TagLast] = {
 
 static const int FieldSeparator = '\t';
 /* separator for multiple values in a field, separator should be 1 byte */
-static const char *FieldMultiSeparator = "|";
+static const char FieldMultiSeparator[] = "|";
 static struct uri baseuri;
 static const char *baseurl;
 
@@ -497,9 +497,9 @@ datetounix(long long year, int mon, int day, int hour, int min, int sec)
 static long
 gettzoffset(const char *s)
 {
-	static struct {
+	static const struct {
 		char *name;
-		const int offhour;
+		int offhour;
 	} tzones[] = {
 		{ "CDT", -5 * 3600 },
 		{ "CST", -6 * 3600 },
@@ -545,7 +545,7 @@ gettzoffset(const char *s)
 static int
 parsetime(const char *s, long long *tp)
 {
-	static struct {
+	static const struct {
 		char *name;
 		int len;
 	} mons[] = {
@@ -838,7 +838,7 @@ xmldataentity(XMLParser *p, const char *data, size_t datalen)
 static void
 xmltagstart(XMLParser *p, const char *t, size_t tl)
 {
-	FeedTag *f;
+	const FeedTag *f;
 
 	if (ISINCONTENT(ctx)) {
 		if (ctx.contenttype == ContentTypeHTML) {
-- 
cgit v1.2.3


From a9445184bb7b4975bc27e7b41e7bfd9f31f1c0ac Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Mon, 21 Mar 2022 23:03:47 +0100
Subject: README: update sfeed_update_xargs example

Set exit status non-zero if any of the feeds failed similar to what
sfeed_update also does now.
---
 README | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/README b/README
index 95f4e9a..648e63e 100644
--- a/README
+++ b/README
@@ -742,12 +742,18 @@ sfeed_update_xargs shellscript:
 	
 	# fetch feeds and store in temporary directory.
 	sfeedtmpdir="$(mktemp -d '/tmp/sfeed_XXXXXX')"
+	mkdir -p "${sfeedtmpdir}/feeds"
+	touch "${sfeedtmpdir}/ok"
 	# make sure path exists.
 	mkdir -p "${sfeedpath}"
 	# print feeds for parallel processing with xargs.
 	feeds | SFEED_UPDATE_CHILD="1" xargs -r -0 -P "${maxjobs}" -L 6 "$(readlink -f "$0")"
+	status=$?
+	# check error exit status indicator for parallel jobs.
+	test -f "${sfeedtmpdir}/ok" || status=1
 	# cleanup temporary files etc.
 	cleanup
+	exit ${status}
 
 - - -
 
-- 
cgit v1.2.3


From 5934cb409782091b6d4481ceccf77e82832167ec Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Tue, 22 Mar 2022 09:45:34 +0100
Subject: sfeed_update: return status in _feed() function

This can be useful for scripts, for example the sfeed_update_xargs example
script in the README. This way the process can signal an error and xargs will
exit with the code 123:

	"One or more invocations of utility returned a nonzero exit status."
---
 sfeed_update | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/sfeed_update b/sfeed_update
index f2c97ae..b9880c3 100755
--- a/sfeed_update
+++ b/sfeed_update
@@ -105,7 +105,7 @@ _feed() {
 
 	if ! fetch "${name}" "${feedurl}" "${sfeedfile}" > "${tmpfeedfile}.fetch"; then
 		log_error "${name}" "FAIL (FETCH)"
-		return
+		return 1
 	fi
 
 	# try to detect encoding (if not specified). if detecting the encoding fails assume utf-8.
@@ -113,50 +113,51 @@ _feed() {
 
 	if ! convertencoding "${name}" "${encoding}" "utf-8" < "${tmpfeedfile}.fetch" > "${tmpfeedfile}.utf8"; then
 		log_error "${name}" "FAIL (ENCODING)"
-		return
+		return 1
 	fi
 	rm -f "${tmpfeedfile}.fetch"
 
 	# if baseurl is empty then use feedurl.
 	if ! parse "${name}" "${feedurl}" "${basesiteurl:-${feedurl}}" < "${tmpfeedfile}.utf8" > "${tmpfeedfile}.tsv"; then
 		log_error "${name}" "FAIL (PARSE)"
-		return
+		return 1
 	fi
 	rm -f "${tmpfeedfile}.utf8"
 
 	if ! filter "${name}" < "${tmpfeedfile}.tsv" > "${tmpfeedfile}.filter"; then
 		log_error "${name}" "FAIL (FILTER)"
-		return
+		return 1
 	fi
 	rm -f "${tmpfeedfile}.tsv"
 
 	# new feed data is empty: no need for below stages.
 	if [ ! -s "${tmpfeedfile}.filter" ]; then
 		log "${name}" "OK"
-		return
+		return 0
 	fi
 
 	if ! merge "${name}" "${sfeedfile}" "${tmpfeedfile}.filter" > "${tmpfeedfile}.merge"; then
 		log_error "${name}" "FAIL (MERGE)"
-		return
+		return 1
 	fi
 	rm -f "${tmpfeedfile}.filter"
 
 	if ! order "${name}" < "${tmpfeedfile}.merge" > "${tmpfeedfile}.order"; then
 		log_error "${name}" "FAIL (ORDER)"
-		return
+		return 1
 	fi
 	rm -f "${tmpfeedfile}.merge"
 
 	# copy
 	if ! cp "${tmpfeedfile}.order" "${sfeedfile}"; then
 		log_error "${name}" "FAIL (COPY)"
-		return
+		return 1
 	fi
 	rm -f "${tmpfeedfile}.order"
 
 	# OK
 	log "${name}" "OK"
+	return 0
 }
 
 # fetch and process a feed in parallel.
-- 
cgit v1.2.3


From da2726ca7789f7b6edf85cee496f20fdc1332afc Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Tue, 22 Mar 2022 10:58:25 +0100
Subject: README: improve error handling in downloader example

- Return exit code, which makes xargs return 123 when any of the downloads
  failed.
- Write errors to stderr.
- Write non-errors to stdout.
---
 README | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/README b/README
index 648e63e..db61a18 100644
--- a/README
+++ b/README
@@ -783,7 +783,7 @@ arguments are specified then the data is read from stdin.
 		else
 			s="$2"
 		fi
-		printf '[%s]: %s: %s\n' "$(date +'%H:%M:%S')" "${s}" "$3" >&2
+		printf '[%s]: %s: %s\n' "$(date +'%H:%M:%S')" "${s}" "$3"
 	}
 	
 	# fetch(url, feedname)
@@ -809,8 +809,8 @@ arguments are specified then the data is read from stdin.
 		if [ "${feedname}" != "-" ]; then
 			mkdir -p "${feedname}"
 			if ! cd "${feedname}"; then
-				log "${feedname}" "${msg}: ${feedname}" "DIR FAIL"
-				exit 1
+				log "${feedname}" "${msg}: ${feedname}" "DIR FAIL" >&2
+				return 1
 			fi
 		fi
 	
@@ -825,8 +825,10 @@ arguments are specified then the data is read from stdin.
 			printf '%s\n' "${url}" >> "${cachefile}"
 			) 9>"${lockfile}"
 		else
-			log "${feedname}" "${msg}" "FAIL"
+			log "${feedname}" "${msg}" "FAIL" >&2
+			return 1
 		fi
+		return 0
 	}
 	
 	if [ "${SFEED_DOWNLOAD_CHILD}" = "1" ]; then
-- 
cgit v1.2.3


From 70426c5b55dd9d1f96860644d868fb3399e5a4df Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Tue, 22 Mar 2022 17:29:11 +0100
Subject: README: sfeed_download: remove a line

---
 README | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/README b/README
index db61a18..6ce4231 100644
--- a/README
+++ b/README
@@ -815,8 +815,7 @@ arguments are specified then the data is read from stdin.
 		fi
 	
 		log "${feedname}" "${msg}" "START"
-		fetch "${url}" "${feedname}"
-		if [ $? = 0 ]; then
+		if fetch "${url}" "${feedname}"; then
 			log "${feedname}" "${msg}" "OK"
 	
 			# append it safely in parallel to the cachefile on a
-- 
cgit v1.2.3


From 74cf6a026e13a6e275d37bc17014908a76b41042 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Wed, 23 Mar 2022 19:38:30 +0100
Subject: shellscripts: use [ for test consistently

---
 sfeed_markread | 4 ++--
 sfeed_update   | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/sfeed_markread b/sfeed_markread
index a40e572..4c509f3 100755
--- a/sfeed_markread
+++ b/sfeed_markread
@@ -9,7 +9,7 @@ usage() {
 }
 
 urlfile="${2:-${SFEED_URL_FILE}}"
-if test -z "${urlfile}"; then
+if [ -z "${urlfile}" ]; then
 	usage
 fi
 
@@ -20,7 +20,7 @@ read)
 unread)
 	tmp=$(mktemp)
 	trap "rm -f ${tmp}" EXIT
-	test -f "${urlfile}" || touch "${urlfile}" 2>/dev/null
+	[ -f "${urlfile}" ] || touch "${urlfile}" 2>/dev/null
 	LC_ALL=C awk -F '\t' '
 	{ FILENR += (FNR == 1) }
 	FILENR == 1 { urls[$0] = 1 }
diff --git a/sfeed_update b/sfeed_update
index b9880c3..2e54a59 100755
--- a/sfeed_update
+++ b/sfeed_update
@@ -213,7 +213,7 @@ main() {
 	# wait till all feeds are fetched (concurrently).
 	[ ${signo} -eq 0 ] && wait
 	# check error exit status indicator for parallel jobs.
-	test -f "${sfeedtmpdir}/ok"
+	[ -f "${sfeedtmpdir}/ok" ]
 	status=$?
 	# cleanup temporary files etc.
 	cleanup
-- 
cgit v1.2.3


From ca3f3fe68ae72fec6f607278bf88d30ab1497627 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Fri, 25 Mar 2022 15:43:47 +0100
Subject: change echo to printf and for sfeed_opml_export use a
 control-character separator

echo is unportable in this way and names containing characters like an option
(-n) or backslash or escape codes (\e, \n, etc) could be messy.

For awk set LC_ALL=C for simple collation.

This makes sfeed_opml_export slower in some shells that don't have printf
builtin though. For example with about 150 feeds in a config file it is a bit
slower on OpenBSD ksh.

time ./sfeed_opml_export | wc -l
     152

    0m00.29s real     0m00.05s user     0m00.20s system

time sfeed_opml_export | wc -l
     152
    0m00.02s real     0m00.00s user     0m00.03s system
---
 README            |  2 +-
 sfeed_markread    |  2 +-
 sfeed_opml_export | 12 ++++++++----
 sfeed_update      |  4 ++--
 4 files changed, 12 insertions(+), 8 deletions(-)

diff --git a/README b/README
index 6ce4231..9658ab6 100644
--- a/README
+++ b/README
@@ -578,7 +578,7 @@ procmail_maildirs.sh file:
 	mkdir -p "${maildir}/.cache"
 
 	if ! test -r "${procmailconfig}"; then
-		echo "Procmail configuration file \"${procmailconfig}\" does not exist or is not readable." >&2
+		printf "Procmail configuration file \"%s\" does not exist or is not readable.\n" "${procmailconfig}" >&2
 		echo "See procmailrc.example for an example." >&2
 		exit 1
 	fi
diff --git a/sfeed_markread b/sfeed_markread
index 4c509f3..b262bdd 100755
--- a/sfeed_markread
+++ b/sfeed_markread
@@ -2,7 +2,7 @@
 # Mark items as read/unread: the input is the read / unread URL per line.
 
 usage() {
-	echo "usage: $0  [urlfile]" >&2
+	printf "usage: %s  [urlfile]\n" "$0" >&2
 	echo "" >&2
 	echo "An urlfile must be specified as an argument or with the environment variable \$SFEED_URL_FILE" >&2
 	exit 1
diff --git a/sfeed_opml_export b/sfeed_opml_export
index eb22520..2a9396a 100755
--- a/sfeed_opml_export
+++ b/sfeed_opml_export
@@ -18,7 +18,7 @@ loadconfig() {
 	if [ -r "${path}" ]; then
 		. "${path}"
 	else
-		echo "Configuration file \"${config}\" cannot be read." >&2
+		printf "Configuration file \"%s\" cannot be read.\n" "${config}" >&2
 		echo "See sfeedrc.example for an example." >&2
 		exit 1
 	fi
@@ -27,8 +27,8 @@ loadconfig() {
 # override feed function to output OPML XML.
 # feed(name, feedurl, [basesiteurl], [encoding])
 feed() {
-	# TABs, newlines and echo options in field values are not checked.
-	echo "$1	$2"
+	# uses the characters 0x1f and 0x1e as a separator.
+	printf '%s\037%s\036' "$1" "$2"
 }
 
 # load config file.
@@ -43,7 +43,11 @@ cat <
 !
 
-feeds | awk -F '\t' '{
+feeds | LC_ALL=C awk '
+BEGIN {
+	FS = "\x1f"; RS = "\x1e";
+}
+{
 	gsub("&", "\\&");
 	gsub("\"", "\\"");
 	gsub("'"'"'", "\\'");
diff --git a/sfeed_update b/sfeed_update
index 2e54a59..857f537 100755
--- a/sfeed_update
+++ b/sfeed_update
@@ -27,7 +27,7 @@ loadconfig() {
 	if [ -r "${path}" ]; then
 		. "${path}"
 	else
-		echo "Configuration file \"${config}\" cannot be read." >&2
+		printf "Configuration file \"%s\" cannot be read.\n" "${config}" >&2
 		echo "See sfeedrc.example for an example." >&2
 		exit 1
 	fi
@@ -187,7 +187,7 @@ sighandler() {
 }
 
 feeds() {
-	echo "Configuration file \"${config}\" is invalid or does not contain a \"feeds\" function." >&2
+	printf "Configuration file \"%s\" is invalid or does not contain a \"feeds\" function.\n" "${config}" >&2
 	echo "See sfeedrc.example for an example." >&2
 }
 
-- 
cgit v1.2.3


From db1dcafd03997127f2cbc82376e2cc8df9b77356 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Fri, 25 Mar 2022 23:18:58 +0100
Subject: rm sys/types.h include and improve portability

This include is not needed. It was intended for ssize_t but this is already
defined by stdio.h for getline().
---
 sfeed_atom.c   | 2 --
 sfeed_curses.c | 1 -
 sfeed_frames.c | 2 --
 sfeed_gopher.c | 2 --
 sfeed_html.c   | 2 --
 sfeed_plain.c  | 2 --
 sfeed_twtxt.c  | 2 --
 7 files changed, 13 deletions(-)

diff --git a/sfeed_atom.c b/sfeed_atom.c
index 3ce5cf0..c74e5fa 100644
--- a/sfeed_atom.c
+++ b/sfeed_atom.c
@@ -1,5 +1,3 @@
-#include 
-
 #include 
 #include 
 #include 
diff --git a/sfeed_curses.c b/sfeed_curses.c
index 484593b..0d878f7 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -4,7 +4,6 @@
 #include 
 #include 
 
-#include 
 #include 
 #include 
 #include 
diff --git a/sfeed_frames.c b/sfeed_frames.c
index 178a4a2..7fcf3ee 100644
--- a/sfeed_frames.c
+++ b/sfeed_frames.c
@@ -1,5 +1,3 @@
-#include 
-
 #include 
 #include 
 #include 
diff --git a/sfeed_gopher.c b/sfeed_gopher.c
index 7da41f6..0d3ea38 100644
--- a/sfeed_gopher.c
+++ b/sfeed_gopher.c
@@ -1,5 +1,3 @@
-#include 
-
 #include 
 #include 
 #include 
diff --git a/sfeed_html.c b/sfeed_html.c
index ce96687..ebc9acd 100644
--- a/sfeed_html.c
+++ b/sfeed_html.c
@@ -1,5 +1,3 @@
-#include 
-
 #include 
 #include 
 #include 
diff --git a/sfeed_plain.c b/sfeed_plain.c
index 8b1f00f..732eb04 100644
--- a/sfeed_plain.c
+++ b/sfeed_plain.c
@@ -1,5 +1,3 @@
-#include 
-
 #include 
 #include 
 #include 
diff --git a/sfeed_twtxt.c b/sfeed_twtxt.c
index 1d8ab36..1bb9d3b 100644
--- a/sfeed_twtxt.c
+++ b/sfeed_twtxt.c
@@ -1,5 +1,3 @@
-#include 
-
 #include 
 #include 
 #include 
-- 
cgit v1.2.3


From 33ae34357f371f45ecb5f988bcdd961372130565 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sun, 27 Mar 2022 12:32:31 +0200
Subject: sfeed_curses: avoid non-interactive plumb process becoming a zombie

From POSIX:
Consequences of Process Termination:
https://pubs.opengroup.org/onlinepubs/9699919799/functions/_Exit.html#tag_16_01_03_01

"
[XSI] [Option Start] If the parent process of the calling process has set its
SA_NOCLDWAIT flag or has set the action for the SIGCHLD signal to SIG_IGN:

The process' status information (see Status Information), if any, shall be
discarded.

The lifetime of the calling process shall end immediately. If SA_NOCLDWAIT is
set, it is implementation-defined whether a SIGCHLD signal is sent to the
parent process.

If a thread in the parent process of the calling process is blocked in wait(),
waitpid(), or waitid(), and the parent process has no remaining child processes
in the set of waited-for children, the wait(), waitid(), or waitpid() function
shall fail and set errno to [ECHILD].
"

Noticed on Linux (but not on OpenBSD).

To reproduce:

- SFEED_PLUMBER_INTERACTIVE=0 SFEED_PLUMBER="less" sfeed_curses ~/.sfeed/feeds/*
- Then open and close the child program.
- Notice it becoming a zombie or "" in the process table.
---
 sfeed_curses.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/sfeed_curses.c b/sfeed_curses.c
index 0d878f7..6f151d4 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -558,6 +558,9 @@ init(void)
 	sigaction(SIGINT, &sa, NULL);
 	sigaction(SIGTERM, &sa, NULL);
 	sigaction(SIGWINCH, &sa, NULL);
+	/* ignore SIGCHLD: for non-interactive programs: don't become a zombie */
+	sa.sa_handler = SIG_IGN;
+	sigaction(SIGCHLD, &sa, NULL);
 }
 
 void
-- 
cgit v1.2.3


From b2264d5892343e61594d703330b85c33172ce00a Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sun, 27 Mar 2022 12:40:07 +0200
Subject: sfeed_curses: processexit: do not set and reset SIGINT for
 non-interactive plumbing

Only set/override it in the interactive case.
Also add some comments.

No functional change intended.
---
 sfeed_curses.c | 16 +++++++---------
 1 file changed, 7 insertions(+), 9 deletions(-)

diff --git a/sfeed_curses.c b/sfeed_curses.c
index 6f151d4..86a6f11 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -569,22 +569,20 @@ processexit(pid_t pid, int interactive)
 	pid_t wpid;
 	struct sigaction sa;
 
-	memset(&sa, 0, sizeof(sa));
-	sigemptyset(&sa.sa_mask);
-	sa.sa_flags = SA_RESTART; /* require BSD signal semantics */
-	sa.sa_handler = SIG_IGN;
-	sigaction(SIGINT, &sa, NULL);
-
 	if (interactive) {
+		/* ignore SIGINT (^C) in parent in interactive applications */
+		memset(&sa, 0, sizeof(sa));
+		sigemptyset(&sa.sa_mask);
+		sa.sa_flags = SA_RESTART; /* require BSD signal semantics */
+		sa.sa_handler = SIG_IGN;
+		sigaction(SIGINT, &sa, NULL);
+		/* wait for process to change state */
 		while ((wpid = wait(NULL)) >= 0 && wpid != pid)
 			;
 		init();
 		updatesidebar();
 		updategeom();
 		updatetitle();
-	} else {
-		sa.sa_handler = sighandler;
-		sigaction(SIGINT, &sa, NULL);
 	}
 }
 
-- 
cgit v1.2.3


From b75648df800e43cf32492c204d21663f013cff40 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sun, 27 Mar 2022 12:50:49 +0200
Subject: sfeed_curses: tiny rewording in comment

---
 sfeed_curses.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sfeed_curses.c b/sfeed_curses.c
index 86a6f11..b5d1901 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -570,7 +570,7 @@ processexit(pid_t pid, int interactive)
 	struct sigaction sa;
 
 	if (interactive) {
-		/* ignore SIGINT (^C) in parent in interactive applications */
+		/* ignore SIGINT (^C) in parent for interactive applications */
 		memset(&sa, 0, sizeof(sa));
 		sigemptyset(&sa.sa_mask);
 		sa.sa_flags = SA_RESTART; /* require BSD signal semantics */
-- 
cgit v1.2.3


From ebc33326e1da17131171a76a9136c1f1af4f0c55 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sun, 27 Mar 2022 14:57:05 +0200
Subject: Revert "rm sys/types.h include and improve portability"

This reverts commit db1dcafd03997127f2cbc82376e2cc8df9b77356.

This is needed. Tested on an (old) Slackware 11 install.
---
 sfeed_atom.c   | 2 ++
 sfeed_curses.c | 1 +
 sfeed_frames.c | 2 ++
 sfeed_gopher.c | 2 ++
 sfeed_html.c   | 2 ++
 sfeed_plain.c  | 2 ++
 sfeed_twtxt.c  | 2 ++
 7 files changed, 13 insertions(+)

diff --git a/sfeed_atom.c b/sfeed_atom.c
index c74e5fa..3ce5cf0 100644
--- a/sfeed_atom.c
+++ b/sfeed_atom.c
@@ -1,3 +1,5 @@
+#include 
+
 #include 
 #include 
 #include 
diff --git a/sfeed_curses.c b/sfeed_curses.c
index b5d1901..7d16b05 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -4,6 +4,7 @@
 #include 
 #include 
 
+#include 
 #include 
 #include 
 #include 
diff --git a/sfeed_frames.c b/sfeed_frames.c
index 7fcf3ee..178a4a2 100644
--- a/sfeed_frames.c
+++ b/sfeed_frames.c
@@ -1,3 +1,5 @@
+#include 
+
 #include 
 #include 
 #include 
diff --git a/sfeed_gopher.c b/sfeed_gopher.c
index 0d3ea38..7da41f6 100644
--- a/sfeed_gopher.c
+++ b/sfeed_gopher.c
@@ -1,3 +1,5 @@
+#include 
+
 #include 
 #include 
 #include 
diff --git a/sfeed_html.c b/sfeed_html.c
index ebc9acd..ce96687 100644
--- a/sfeed_html.c
+++ b/sfeed_html.c
@@ -1,3 +1,5 @@
+#include 
+
 #include 
 #include 
 #include 
diff --git a/sfeed_plain.c b/sfeed_plain.c
index 732eb04..8b1f00f 100644
--- a/sfeed_plain.c
+++ b/sfeed_plain.c
@@ -1,3 +1,5 @@
+#include 
+
 #include 
 #include 
 #include 
diff --git a/sfeed_twtxt.c b/sfeed_twtxt.c
index 1bb9d3b..1d8ab36 100644
--- a/sfeed_twtxt.c
+++ b/sfeed_twtxt.c
@@ -1,3 +1,5 @@
+#include 
+
 #include 
 #include 
 #include 
-- 
cgit v1.2.3


From ab0c25faa0f5378aaeb0f7b219ecdfb649c043f9 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sun, 27 Mar 2022 14:57:36 +0200
Subject: sfeed_curses: remove unneeded ctype.h include

---
 sfeed_curses.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/sfeed_curses.c b/sfeed_curses.c
index 7d16b05..b5d1901 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -4,7 +4,6 @@
 #include 
 #include 
 
-#include 
 #include 
 #include 
 #include 
-- 
cgit v1.2.3


From 2a47f88faae8e1e794c53ee7653d16dbe034bb15 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Mon, 28 Mar 2022 12:55:34 +0200
Subject: update documentation of sfeed_update example and clarify return
 status

Change the example to reload the feeds anyway, even if one of the feeds
(temporarily) failed to update.
---
 README         | 3 ++-
 sfeed_update.1 | 3 ++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/README b/README
index 9658ab6..86f30b2 100644
--- a/README
+++ b/README
@@ -1057,7 +1057,8 @@ Example of a `markallread.sh` shellscript to mark all URLs as read:
 Example of a `syncnews.sh` shellscript to update the feeds and reload them:
 
 	#!/bin/sh
-	sfeed_update && pkill -SIGHUP sfeed_curses
+	sfeed_update
+	pkill -SIGHUP sfeed_curses
 
 
 Open an URL directly in the same terminal
diff --git a/sfeed_update.1 b/sfeed_update.1
index 3edf551..893f290 100644
--- a/sfeed_update.1
+++ b/sfeed_update.1
@@ -1,4 +1,4 @@
-.Dd March 21, 2022
+.Dd March 28, 2022
 .Dt SFEED_UPDATE 1
 .Os
 .Sh NAME
@@ -62,6 +62,7 @@ stdout in the format:
 .Ed
 .Sh EXIT STATUS
 .Ex -std
+If any of the feeds failed to update then the return status is non-zero.
 .Sh EXAMPLES
 To update your feeds and format them in various formats:
 .Bd -literal
-- 
cgit v1.2.3


From df2250aa196b674c0783d3ba1862b1cfb5df5719 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Mon, 28 Mar 2022 13:07:58 +0200
Subject: sfeed_update: change return to exit in main

Pedantic change:

Make main more consistent since other functions in it exit too and main is not
supposed to return or used like that.
---
 sfeed_update | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sfeed_update b/sfeed_update
index 857f537..fc7447f 100755
--- a/sfeed_update
+++ b/sfeed_update
@@ -219,7 +219,7 @@ main() {
 	cleanup
 	# on signal SIGINT and SIGTERM exit with signal number + 128.
 	[ ${signo} -ne 0 ] && exit $((signo+128))
-	return ${status}
+	exit ${status}
 }
 
 [ "${SFEED_UPDATE_INCLUDE}" = "1" ] || main "$@"
-- 
cgit v1.2.3


From 30a70fa2dab1925b0eaea04f67e3f86b360386dd Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Mon, 28 Mar 2022 15:54:03 +0200
Subject: sfeed_curses: ignore SIGCHLD only for non-interactive programs

This is a regression from the introduced change.
Else wait(&status) returned -1 and status was uninitialized.

The status of the returned program in the markread() function is used to
visually show it as read/unread. If the program failed it is assumed the
program could not mark it and so it is visually unchanged.

Just to be sure also initialize status to -1 (which can never happen normally)
since the exitstatus range is 0-255.

https://man.openbsd.org/wait#ERRORS
[ECHILD]: "No status from the terminated child process is available because the calling
process has asked the system to discard such status by ignoring the signal
SIGCHLD or setting the flag SA_NOCLDWAIT for that signal."
---
 sfeed_curses.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/sfeed_curses.c b/sfeed_curses.c
index b5d1901..00189a0 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -558,9 +558,6 @@ init(void)
 	sigaction(SIGINT, &sa, NULL);
 	sigaction(SIGTERM, &sa, NULL);
 	sigaction(SIGWINCH, &sa, NULL);
-	/* ignore SIGCHLD: for non-interactive programs: don't become a zombie */
-	sa.sa_handler = SIG_IGN;
-	sigaction(SIGCHLD, &sa, NULL);
 }
 
 void
@@ -583,6 +580,10 @@ processexit(pid_t pid, int interactive)
 		updatesidebar();
 		updategeom();
 		updatetitle();
+	} else {
+		/* ignore SIGCHLD: for non-interactive programs: don't become a zombie */
+		sa.sa_handler = SIG_IGN;
+		sigaction(SIGCHLD, &sa, NULL);
 	}
 }
 
@@ -1836,7 +1837,7 @@ markread(struct pane *p, off_t from, off_t to, int isread)
 	FILE *fp;
 	off_t i;
 	const char *cmd;
-	int isnew = !isread, pid, wpid, status, visstart;
+	int isnew = !isread, pid, wpid, status = -1, visstart;
 
 	if (!urlfile || !p->nrows)
 		return;
-- 
cgit v1.2.3


From a58fa45f25da4f18d7b8c1a815884f67b965406f Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Mon, 28 Mar 2022 18:01:22 +0200
Subject: sfeed_curses: fix-up from previous commit: properly initialize struct
 sigaction

---
 sfeed_curses.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/sfeed_curses.c b/sfeed_curses.c
index 00189a0..abd6740 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -566,11 +566,12 @@ processexit(pid_t pid, int interactive)
 	pid_t wpid;
 	struct sigaction sa;
 
+	memset(&sa, 0, sizeof(sa));
+	sigemptyset(&sa.sa_mask);
+	sa.sa_flags = SA_RESTART; /* require BSD signal semantics */
+
 	if (interactive) {
 		/* ignore SIGINT (^C) in parent for interactive applications */
-		memset(&sa, 0, sizeof(sa));
-		sigemptyset(&sa.sa_mask);
-		sa.sa_flags = SA_RESTART; /* require BSD signal semantics */
 		sa.sa_handler = SIG_IGN;
 		sigaction(SIGINT, &sa, NULL);
 		/* wait for process to change state */
-- 
cgit v1.2.3


From 880256b8bfde746cd54993f3abcb4dc648895af7 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Mon, 28 Mar 2022 18:36:20 +0200
Subject: compatibility: replace iscntrl with own ISCNTRL macro

It is unspecified if the C locale iscntrl is compatible with ASCII or not.

Noticed when testing on OpenBSD 3.8 which uses extended ASCII and also uses the
C1 range for control-characters.  This breaks support with UTF-8.

Reference:
https://en.wikipedia.org/wiki/C0_and_C1_control_codes#C1_control_codes_for_general_use
C1 table.

Force an own definition of an ASCII-compatible control-character range since
sfeed expects input to be UTF-8 (or converted from iconv) and so output to be
UTF-8 aswell.
---
 sfeed.c             | 4 ++--
 sfeed_opml_import.c | 2 +-
 sfeed_web.c         | 2 +-
 util.h              | 3 +++
 4 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/sfeed.c b/sfeed.c
index 9b3fd8f..fc1249a 100644
--- a/sfeed.c
+++ b/sfeed.c
@@ -326,7 +326,7 @@ string_print_encoded(String *s)
 		case '\t': putchar('\\'); putchar('t'); break;
 		default:
 			/* ignore control chars */
-			if (!iscntrl((unsigned char)*p))
+			if (!ISCNTRL((unsigned char)*p))
 				putchar(*p);
 			break;
 		}
@@ -343,7 +343,7 @@ printtrimmed(const char *s)
 	for (; *p && p != e; p++) {
 		if (isspace((unsigned char)*p))
 			putchar(' '); /* any whitespace to space */
-		else if (!iscntrl((unsigned char)*p))
+		else if (!ISCNTRL((unsigned char)*p))
 			/* ignore other control chars */
 			putchar(*p);
 	}
diff --git a/sfeed_opml_import.c b/sfeed_opml_import.c
index 6c0bd0e..9a13e36 100644
--- a/sfeed_opml_import.c
+++ b/sfeed_opml_import.c
@@ -12,7 +12,7 @@ static void
 printsafe(const char *s)
 {
 	for (; *s; s++) {
-		if (iscntrl((unsigned char)*s))
+		if (ISCNTRL((unsigned char)*s))
 			continue;
 		else if (*s == '\\')
 			fputs("\\\\", stdout);
diff --git a/sfeed_web.c b/sfeed_web.c
index 704afe8..dfff0a3 100644
--- a/sfeed_web.c
+++ b/sfeed_web.c
@@ -16,7 +16,7 @@ static void
 printvalue(const char *s)
 {
 	for (; *s; s++)
-		if (!iscntrl((unsigned char)*s))
+		if (!ISCNTRL((unsigned char)*s))
 			putchar(*s);
 }
 
diff --git a/util.h b/util.h
index 7923cdc..c68cef4 100644
--- a/util.h
+++ b/util.h
@@ -8,6 +8,9 @@
 #define unveil(p1,p2) 0
 #endif
 
+/* control-character in the ASCII range 0-127: compatible with UTF-8 */
+#define ISCNTRL(c) ((c) < ' ' || (c) == 0x7f)
+
 #undef strcasestr
 char *strcasestr(const char *, const char *);
 #undef strlcat
-- 
cgit v1.2.3


From d5ee385b4b5f19934a00408a2addc70f965ea4a9 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Tue, 29 Mar 2022 11:03:54 +0200
Subject: compatibility: reduce the assumption the builtin libc locale is
 ASCII-compatible

This is not clearly defined by the C99 standard.
Define ctype-like macros to force it to be ASCII / UTF-8 (not extended ASCII or
something like noticed on OpenBSD 3.8).

(In practise modern libc libraries are all ASCII and UTF-8-compatible. Otherwise
this would break many programs)
---
 sfeed.c             | 50 +++++++++++++++++++++++++-------------------------
 sfeed_opml_import.c |  1 -
 sfeed_web.c         |  1 -
 sfeed_xmlenc.c      |  7 +++----
 util.c              |  9 ++++-----
 util.h              |  6 +++++-
 xml.c               | 20 +++++++++++---------
 7 files changed, 48 insertions(+), 46 deletions(-)

diff --git a/sfeed.c b/sfeed.c
index fc1249a..b969874 100644
--- a/sfeed.c
+++ b/sfeed.c
@@ -246,7 +246,7 @@ gettag(enum FeedType feedtype, const char *name, size_t namelen)
 static char *
 ltrim(const char *s)
 {
-	for (; isspace((unsigned char)*s); s++)
+	for (; ISSPACE((unsigned char)*s); s++)
 		;
 	return (char *)s;
 }
@@ -256,7 +256,7 @@ rtrim(const char *s)
 {
 	const char *e;
 
-	for (e = s + strlen(s); e > s && isspace((unsigned char)*(e - 1)); e--)
+	for (e = s + strlen(s); e > s && ISSPACE((unsigned char)*(e - 1)); e--)
 		;
 	return (char *)e;
 }
@@ -341,7 +341,7 @@ printtrimmed(const char *s)
 	p = ltrim(s);
 	e = rtrim(p);
 	for (; *p && p != e; p++) {
-		if (isspace((unsigned char)*p))
+		if (ISSPACE((unsigned char)*p))
 			putchar(' '); /* any whitespace to space */
 		else if (!ISCNTRL((unsigned char)*p))
 			/* ignore other control chars */
@@ -514,20 +514,20 @@ gettzoffset(const char *s)
 	long tzhour = 0, tzmin = 0;
 	size_t i;
 
-	for (; isspace((unsigned char)*s); s++)
+	for (; ISSPACE((unsigned char)*s); s++)
 		;
 	switch (*s) {
 	case '-': /* offset */
 	case '+':
-		for (i = 0, p = s + 1; i < 2 && isdigit((unsigned char)*p); i++, p++)
+		for (i = 0, p = s + 1; i < 2 && ISDIGIT((unsigned char)*p); i++, p++)
 			tzhour = (tzhour * 10) + (*p - '0');
 		if (*p == ':')
 			p++;
-		for (i = 0; i < 2 && isdigit((unsigned char)*p); i++, p++)
+		for (i = 0; i < 2 && ISDIGIT((unsigned char)*p); i++, p++)
 			tzmin = (tzmin * 10) + (*p - '0');
 		return ((tzhour * 3600) + (tzmin * 60)) * (s[0] == '-' ? -1 : 1);
 	default: /* timezone name */
-		for (i = 0; isalpha((unsigned char)s[i]); i++)
+		for (i = 0; ISALPHA((unsigned char)s[i]); i++)
 			;
 		if (i != 3)
 			return 0;
@@ -565,35 +565,35 @@ parsetime(const char *s, long long *tp)
 	int va[6] = { 0 }, i, j, v, vi;
 	size_t m;
 
-	for (; isspace((unsigned char)*s); s++)
+	for (; ISSPACE((unsigned char)*s); s++)
 		;
-	if (!isdigit((unsigned char)*s) && !isalpha((unsigned char)*s))
+	if (!ISDIGIT((unsigned char)*s) && !ISALPHA((unsigned char)*s))
 		return -1;
 
-	if (isdigit((unsigned char)s[0]) &&
-	    isdigit((unsigned char)s[1]) &&
-	    isdigit((unsigned char)s[2]) &&
-	    isdigit((unsigned char)s[3])) {
+	if (ISDIGIT((unsigned char)s[0]) &&
+	    ISDIGIT((unsigned char)s[1]) &&
+	    ISDIGIT((unsigned char)s[2]) &&
+	    ISDIGIT((unsigned char)s[3])) {
 		/* formats "%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S" or "%Y%m%d%H%M%S" */
 		vi = 0;
 	} else {
 		/* format: "[%a, ]%d %b %Y %H:%M:%S" */
 		/* parse "[%a, ]%d %b %Y " part, then use time parsing as above */
-		for (; isalpha((unsigned char)*s); s++)
+		for (; ISALPHA((unsigned char)*s); s++)
 			;
-		for (; isspace((unsigned char)*s); s++)
+		for (; ISSPACE((unsigned char)*s); s++)
 			;
 		if (*s == ',')
 			s++;
-		for (; isspace((unsigned char)*s); s++)
+		for (; ISSPACE((unsigned char)*s); s++)
 			;
-		for (v = 0, i = 0; i < 2 && isdigit((unsigned char)*s); s++, i++)
+		for (v = 0, i = 0; i < 2 && ISDIGIT((unsigned char)*s); s++, i++)
 			v = (v * 10) + (*s - '0');
 		va[2] = v; /* day */
-		for (; isspace((unsigned char)*s); s++)
+		for (; ISSPACE((unsigned char)*s); s++)
 			;
 		/* end of word month */
-		for (j = 0; isalpha((unsigned char)s[j]); j++)
+		for (j = 0; ISALPHA((unsigned char)s[j]); j++)
 			;
 		/* check month name */
 		if (j < 3 || j > 9)
@@ -609,15 +609,15 @@ parsetime(const char *s, long long *tp)
 		}
 		if (m >= 12)
 			return -1; /* no month found */
-		for (; isspace((unsigned char)*s); s++)
+		for (; ISSPACE((unsigned char)*s); s++)
 			;
-		for (v = 0, i = 0; i < 4 && isdigit((unsigned char)*s); s++, i++)
+		for (v = 0, i = 0; i < 4 && ISDIGIT((unsigned char)*s); s++, i++)
 			v = (v * 10) + (*s - '0');
 		/* obsolete short year: RFC2822 4.3 */
 		if (i <= 3)
 			v += (v >= 0 && v <= 49) ? 2000 : 1900;
 		va[0] = v; /* year */
-		for (; isspace((unsigned char)*s); s++)
+		for (; ISSPACE((unsigned char)*s); s++)
 			;
 		/* parse only regular time part, see below */
 		vi = 3;
@@ -626,20 +626,20 @@ parsetime(const char *s, long long *tp)
 	/* parse time parts (and possibly remaining date parts) */
 	for (; *s && vi < 6; vi++) {
 		for (i = 0, v = 0; i < ((vi == 0) ? 4 : 2) &&
-		                   isdigit((unsigned char)*s); s++, i++) {
+		                   ISDIGIT((unsigned char)*s); s++, i++) {
 			v = (v * 10) + (*s - '0');
 		}
 		va[vi] = v;
 
 		if ((vi < 2 && *s == '-') ||
-		    (vi == 2 && (*s == 'T' || isspace((unsigned char)*s))) ||
+		    (vi == 2 && (*s == 'T' || ISSPACE((unsigned char)*s))) ||
 		    (vi > 2 && *s == ':'))
 			s++;
 	}
 
 	/* skip milliseconds in for example: "%Y-%m-%dT%H:%M:%S.000Z" */
 	if (*s == '.') {
-		for (s++; isdigit((unsigned char)*s); s++)
+		for (s++; ISDIGIT((unsigned char)*s); s++)
 			;
 	}
 
diff --git a/sfeed_opml_import.c b/sfeed_opml_import.c
index 9a13e36..14b5444 100644
--- a/sfeed_opml_import.c
+++ b/sfeed_opml_import.c
@@ -1,4 +1,3 @@
-#include 
 #include 
 #include 
 
diff --git a/sfeed_web.c b/sfeed_web.c
index dfff0a3..630ab60 100644
--- a/sfeed_web.c
+++ b/sfeed_web.c
@@ -1,4 +1,3 @@
-#include 
 #include 
 #include 
 
diff --git a/sfeed_xmlenc.c b/sfeed_xmlenc.c
index 67d7b0f..7fc93ae 100644
--- a/sfeed_xmlenc.c
+++ b/sfeed_xmlenc.c
@@ -1,4 +1,3 @@
-#include 
 #include 
 #include 
 #include 
@@ -26,10 +25,10 @@ xmlattr(XMLParser *p, const char *t, size_t tl, const char *n, size_t nl,
 		return;
 
 	for (; *v; v++) {
-		if (isalpha((unsigned char)*v) ||
-		    isdigit((unsigned char)*v) ||
+		if (ISALPHA((unsigned char)*v) ||
+		    ISDIGIT((unsigned char)*v) ||
 		    *v == '.' || *v == ':' || *v == '-' || *v == '_')
-			putchar(tolower((unsigned char)*v));
+			putchar(TOLOWER((unsigned char)*v));
 	}
 }
 
diff --git a/util.c b/util.c
index ed0b5c9..0b7da06 100644
--- a/util.c
+++ b/util.c
@@ -1,4 +1,3 @@
-#include 
 #include 
 #include 
 #include 
@@ -66,8 +65,8 @@ strcasestr(const char *h, const char *n)
 		return (char *)h;
 
 	for (; *h; ++h) {
-		for (i = 0; n[i] && tolower((unsigned char)n[i]) ==
-		            tolower((unsigned char)h[i]); ++i)
+		for (i = 0; n[i] && TOLOWER((unsigned char)n[i]) ==
+		            TOLOWER((unsigned char)h[i]); ++i)
 			;
 		if (n[i] == '\0')
 			return (char *)h;
@@ -82,7 +81,7 @@ uri_hasscheme(const char *s)
 {
 	const char *p = s;
 
-	for (; isalpha((unsigned char)*p) || isdigit((unsigned char)*p) ||
+	for (; ISALPHA((unsigned char)*p) || ISDIGIT((unsigned char)*p) ||
 		       *p == '+' || *p == '-' || *p == '.'; p++)
 		;
 	/* scheme, except if empty and starts with ":" then it is a path */
@@ -109,7 +108,7 @@ uri_parse(const char *s, struct uri *u)
 	}
 
 	/* scheme / protocol part */
-	for (; isalpha((unsigned char)*p) || isdigit((unsigned char)*p) ||
+	for (; ISALPHA((unsigned char)*p) || ISDIGIT((unsigned char)*p) ||
 		       *p == '+' || *p == '-' || *p == '.'; p++)
 		;
 	/* scheme, except if empty and starts with ":" then it is a path */
diff --git a/util.h b/util.h
index c68cef4..fac6424 100644
--- a/util.h
+++ b/util.h
@@ -8,8 +8,12 @@
 #define unveil(p1,p2) 0
 #endif
 
-/* control-character in the ASCII range 0-127: compatible with UTF-8 */
+/* ctype-like macros, but always compatible with ASCII / UTF-8 */
+#define ISALPHA(c) ((((unsigned)c) | 32) - 'a' < 26)
 #define ISCNTRL(c) ((c) < ' ' || (c) == 0x7f)
+#define ISDIGIT(c) (((unsigned)c) - '0' < 10)
+#define ISSPACE(c) ((c) == ' ' || ((((unsigned)c) - '\t') < 5))
+#define TOLOWER(c) ((((unsigned)c) - 'A' < 26) ? ((c) | 32) : (c))
 
 #undef strcasestr
 char *strcasestr(const char *, const char *);
diff --git a/xml.c b/xml.c
index f16cbbb..3bd9557 100644
--- a/xml.c
+++ b/xml.c
@@ -1,4 +1,3 @@
-#include 
 #include 
 #include 
 #include 
@@ -6,6 +5,9 @@
 
 #include "xml.h"
 
+#define ISALPHA(c) ((((unsigned)c) | 32) - 'a' < 26)
+#define ISSPACE(c) ((c) == ' ' || ((((unsigned)c) - '\t') < 5))
+
 static void
 xml_parseattrs(XMLParser *x)
 {
@@ -13,7 +15,7 @@ xml_parseattrs(XMLParser *x)
 	int c, endsep, endname = 0, valuestart = 0;
 
 	while ((c = GETNEXT()) != EOF) {
-		if (isspace(c)) {
+		if (ISSPACE(c)) {
 			if (namelen)
 				endname = 1;
 			continue;
@@ -23,7 +25,7 @@ xml_parseattrs(XMLParser *x)
 			x->name[namelen] = '\0';
 			valuestart = 1;
 			endname = 1;
-		} else if (namelen && ((endname && !valuestart && isalpha(c)) || (c == '>' || c == '/'))) {
+		} else if (namelen && ((endname && !valuestart && ISALPHA(c)) || (c == '>' || c == '/'))) {
 			/* attribute without value */
 			x->name[namelen] = '\0';
 			if (x->xmlattrstart)
@@ -44,7 +46,7 @@ xml_parseattrs(XMLParser *x)
 			if (c == '\'' || c == '"') {
 				endsep = c;
 			} else {
-				endsep = ' '; /* isspace() */
+				endsep = ' '; /* ISSPACE() */
 				goto startvalue;
 			}
 
@@ -58,7 +60,7 @@ startvalue:
 					x->data[0] = c;
 					valuelen = 1;
 					while ((c = GETNEXT()) != EOF) {
-						if (c == endsep || (endsep == ' ' && (c == '>' || isspace(c))))
+						if (c == endsep || (endsep == ' ' && (c == '>' || ISSPACE(c))))
 							break;
 						if (valuelen < sizeof(x->data) - 1)
 							x->data[valuelen++] = c;
@@ -79,7 +81,7 @@ startvalue:
 							break;
 						}
 					}
-				} else if (c != endsep && !(endsep == ' ' && (c == '>' || isspace(c)))) {
+				} else if (c != endsep && !(endsep == ' ' && (c == '>' || ISSPACE(c)))) {
 					if (valuelen < sizeof(x->data) - 1) {
 						x->data[valuelen++] = c;
 					} else {
@@ -90,7 +92,7 @@ startvalue:
 						valuelen = 1;
 					}
 				}
-				if (c == endsep || (endsep == ' ' && (c == '>' || isspace(c)))) {
+				if (c == endsep || (endsep == ' ' && (c == '>' || ISSPACE(c)))) {
 					x->data[valuelen] = '\0';
 					if (x->xmlattr)
 						x->xmlattr(x, x->tag, x->taglen, x->name, namelen, x->data, valuelen);
@@ -328,7 +330,7 @@ xml_parse(XMLParser *x)
 				while ((c = GETNEXT()) != EOF) {
 					if (c == '/')
 						x->isshorttag = 1; /* short tag */
-					else if (c == '>' || isspace(c)) {
+					else if (c == '>' || ISSPACE(c)) {
 						x->tag[x->taglen] = '\0';
 						if (isend) { /* end tag, starts with xmltagend)
@@ -339,7 +341,7 @@ xml_parse(XMLParser *x)
 							/* start tag */
 							if (x->xmltagstart)
 								x->xmltagstart(x, x->tag, x->taglen);
-							if (isspace(c))
+							if (ISSPACE(c))
 								xml_parseattrs(x);
 							if (x->xmltagstartparsed)
 								x->xmltagstartparsed(x, x->tag, x->taglen, x->isshorttag);
-- 
cgit v1.2.3


From 275cc6c2d02d1d2782b4d13d9ef0889f3ee93876 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Tue, 29 Mar 2022 11:25:30 +0200
Subject: sfeed.1: add an example to convert to UTF-8 using iconv

---
 sfeed.1 | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/sfeed.1 b/sfeed.1
index 8075ca4..70354e3 100644
--- a/sfeed.1
+++ b/sfeed.1
@@ -1,4 +1,4 @@
-.Dd November 26, 2021
+.Dd March 29, 2022
 .Dt SFEED 1
 .Os
 .Sh NAME
@@ -55,6 +55,15 @@ Item, categories, multiple values are separated by the '|' character.
 .Bd -literal
 curl -s 'https://codemadness.org/atom.xml' | sfeed
 .Ed
+.Pp
+To convert the character set from a feed that is not UTF-8 encoded the
+.Xr iconv 1
+tool can be used:
+.Bd -literal
+curl -s 'https://codemadness.org/some_iso-8859-1_feed.xml' | \\
+iconv -f iso-8859-1 -t utf-8 | \\
+sfeed
+.Ed
 .Sh EXAMPLE SETUP
 1. Create a directory for the sfeedrc configuration and the feeds:
 .Bd -literal
-- 
cgit v1.2.3


From 3d4ad85bdd4e1f3f3dac229033dbcd5300ba2a18 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Thu, 31 Mar 2022 22:43:13 +0200
Subject: sfeed.c: rm ctype.h its not needed

---
 sfeed.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/sfeed.c b/sfeed.c
index b969874..a77afb3 100644
--- a/sfeed.c
+++ b/sfeed.c
@@ -1,4 +1,3 @@
-#include 
 #include 
 #include 
 #include 
-- 
cgit v1.2.3


From a6a8f296664be3d652015dd26bdaf3e2101e7f04 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Thu, 31 Mar 2022 22:54:48 +0200
Subject: sfeed_curses: improve waiting on processes and reaping them

- Wait on the exact process id and get its status.

- Handle SIGCHLD explicitly and reap zombie children: ignoring them by using
  sigaction(SIGCHLD, &sa, NULL); would be racy in this case because sfeed_curses
  has interactive and non-interactive programs.

Note while testing: if the markread program would be slow and in the meantime a
plumb process would exit. This signal is now pending and is a zombie process
until the SIGCHLD signal can be processed. This is fine.

This also fixes a regression from commit
30a70fa2dab1925b0eaea04f67e3f86b360386dd because SIGCHLD was ignored in the
parent for interactive processes aswell.  This broke reading the exit status of
the markread program, reproducable by plumbing an item and then trying to mark
it as read (with SFEED_URL_FILE set).
---
 sfeed_curses.c | 31 +++++++++++++++++--------------
 1 file changed, 17 insertions(+), 14 deletions(-)

diff --git a/sfeed_curses.c b/sfeed_curses.c
index abd6740..64fb314 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -554,6 +554,7 @@ init(void)
 	sigemptyset(&sa.sa_mask);
 	sa.sa_flags = SA_RESTART; /* require BSD signal semantics */
 	sa.sa_handler = sighandler;
+	sigaction(SIGCHLD, &sa, NULL);
 	sigaction(SIGHUP, &sa, NULL);
 	sigaction(SIGINT, &sa, NULL);
 	sigaction(SIGTERM, &sa, NULL);
@@ -563,7 +564,6 @@ init(void)
 void
 processexit(pid_t pid, int interactive)
 {
-	pid_t wpid;
 	struct sigaction sa;
 
 	memset(&sa, 0, sizeof(sa));
@@ -574,17 +574,12 @@ processexit(pid_t pid, int interactive)
 		/* ignore SIGINT (^C) in parent for interactive applications */
 		sa.sa_handler = SIG_IGN;
 		sigaction(SIGINT, &sa, NULL);
-		/* wait for process to change state */
-		while ((wpid = wait(NULL)) >= 0 && wpid != pid)
-			;
+		/* wait for process to change state, ignore errors */
+		waitpid(pid, NULL, 0);
 		init();
 		updatesidebar();
 		updategeom();
 		updatetitle();
-	} else {
-		/* ignore SIGCHLD: for non-interactive programs: don't become a zombie */
-		sa.sa_handler = SIG_IGN;
-		sigaction(SIGCHLD, &sa, NULL);
 	}
 }
 
@@ -1010,6 +1005,7 @@ lineeditor(void)
 		} else if (ch < 0) {
 			switch (sigstate) {
 			case 0:
+			case SIGCHLD:
 			case SIGWINCH:
 				/* continue editing: process signal later */
 				continue;
@@ -1582,6 +1578,7 @@ void
 sighandler(int signo)
 {
 	switch (signo) {
+	case SIGCHLD:
 	case SIGHUP:
 	case SIGINT:
 	case SIGTERM:
@@ -1838,7 +1835,7 @@ markread(struct pane *p, off_t from, off_t to, int isread)
 	FILE *fp;
 	off_t i;
 	const char *cmd;
-	int isnew = !isread, pid, wpid, status = -1, visstart;
+	int isnew = !isread, pid, status = -1, visstart;
 
 	if (!urlfile || !p->nrows)
 		return;
@@ -1869,11 +1866,9 @@ markread(struct pane *p, off_t from, off_t to, int isread)
 		status = WIFEXITED(status) ? WEXITSTATUS(status) : 127;
 		_exit(status);
 	default:
-		while ((wpid = wait(&status)) >= 0 && wpid != pid)
-			;
-
-		/* fail: exit statuscode was non-zero */
-		if (status)
+		/* waitpid() and block on process status change,
+		   fail if exit statuscode was unavailable or non-zero */
+		if (waitpid(pid, &status, 0) <= 0 || status)
 			break;
 
 		visstart = p->pos - (p->pos % p->height); /* visible start */
@@ -2343,6 +2338,14 @@ event:
 			continue; /* just a time-out, nothing to do */
 
 		switch (sigstate) {
+		case SIGCHLD:
+			/* wait on child processes so they don't become a zombie,
+			   do not block the parent process if there is no status,
+			   ignore errors */
+			while (waitpid((pid_t)-1, NULL, WNOHANG) > 0)
+				;
+			sigstate = 0;
+			break;
 		case SIGHUP:
 			feeds_reloadall();
 			sigstate = 0;
-- 
cgit v1.2.3


From b4344cc3b01ff098b8e3cc06aebf8a097dac78d3 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Fri, 1 Apr 2022 00:15:03 +0200
Subject: sfeed_curses: improve multiple signal handling

Keep a bitmask of the received signals and handle them all.
Also handle them in a particular order now.
---
 sfeed_curses.c | 64 +++++++++++++++++++++++-----------------------------------
 1 file changed, 25 insertions(+), 39 deletions(-)

diff --git a/sfeed_curses.c b/sfeed_curses.c
index 64fb314..55791f0 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -172,7 +172,8 @@ static time_t comparetime;
 static char *urlfile, **urls;
 static size_t nurls;
 
-volatile sig_atomic_t sigstate = 0;
+enum Signals { SigChld = 1, SigHup = 2, SigInt = 4, SigTerm = 8, SigWinch = 16 };
+volatile sig_atomic_t sigstate = 0; /* bitmask state of received signals */
 
 static char *plumbercmd = "xdg-open"; /* env variable: $SFEED_PLUMBER */
 static char *pipercmd = "sfeed_content"; /* env variable: $SFEED_PIPER */
@@ -1003,22 +1004,12 @@ lineeditor(void)
 			ttywrite(&input[nchars]);
 			nchars++;
 		} else if (ch < 0) {
-			switch (sigstate) {
-			case 0:
-			case SIGCHLD:
-			case SIGWINCH:
-				/* continue editing: process signal later */
-				continue;
-			case SIGINT:
-				/* cancel prompt, but do not quit */
-				sigstate = 0; /* reset: do not handle it */
-				break;
-			default: /* other: SIGHUP, SIGTERM */
-				/* cancel prompt and handle signal after */
-				break;
-			}
+			if (sigstate & SigInt)
+				sigstate &= ~SigInt; /* reset: do not handle it later */
+			else if (!sigstate || (sigstate & (SigChld | SigWinch)))
+				continue; /* do not cancel: process signal later */
 			free(input);
-			return NULL;
+			return NULL; /* cancel prompt */
 		}
 	}
 	return input;
@@ -1578,15 +1569,11 @@ void
 sighandler(int signo)
 {
 	switch (signo) {
-	case SIGCHLD:
-	case SIGHUP:
-	case SIGINT:
-	case SIGTERM:
-	case SIGWINCH:
-		/* SIGTERM is more important, do not override it */
-		if (sigstate != SIGTERM)
-			sigstate = signo;
-		break;
+	case SIGCHLD:  sigstate |= SigChld; break;
+	case SIGHUP:   sigstate |= SigHup; break;
+	case SIGINT:   sigstate |= SigInt; break;
+	case SIGTERM:  sigstate |= SigTerm; break;
+	case SIGWINCH: sigstate |= SigWinch; break;
 	}
 }
 
@@ -2337,28 +2324,27 @@ event:
 		else if (ch == -3 && sigstate == 0)
 			continue; /* just a time-out, nothing to do */
 
-		switch (sigstate) {
-		case SIGCHLD:
+		/* handle signals, in order of importance */
+		if (sigstate & SigChld) {
+			sigstate &= ~SigChld;
 			/* wait on child processes so they don't become a zombie,
 			   do not block the parent process if there is no status,
 			   ignore errors */
 			while (waitpid((pid_t)-1, NULL, WNOHANG) > 0)
 				;
-			sigstate = 0;
-			break;
-		case SIGHUP:
-			feeds_reloadall();
-			sigstate = 0;
-			break;
-		case SIGINT:
-		case SIGTERM:
+		}
+		if (sigstate & (SigInt | SigTerm)) {
 			cleanup();
-			_exit(128 + sigstate);
-		case SIGWINCH:
+			_exit(128 + (sigstate & SigTerm ? SIGTERM : SIGINT));
+		}
+		if (sigstate & SigHup) {
+			sigstate &= ~SigHup;
+			feeds_reloadall();
+		}
+		if (sigstate & SigWinch) {
+			sigstate &= ~SigWinch;
 			resizewin();
 			updategeom();
-			sigstate = 0;
-			break;
 		}
 
 		draw();
-- 
cgit v1.2.3


From f4cfa079c74b2dfdf99ffa987fecf23b2f0f4f14 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Fri, 1 Apr 2022 00:57:34 +0200
Subject: README: reflect example with the description

---
 README | 1 +
 1 file changed, 1 insertion(+)

diff --git a/README b/README
index 86f30b2..92fe929 100644
--- a/README
+++ b/README
@@ -76,6 +76,7 @@ HTML view (no frames), copy style.css for a default style:
 HTML view with the menu as frames, copy style.css for a default style:
 
 	mkdir -p "$HOME/.sfeed/frames"
+	cp style.css "$HOME/.sfeed/frames/style.css"
 	cd "$HOME/.sfeed/frames" && sfeed_frames $HOME/.sfeed/feeds/*
 
 To automatically update your feeds periodically and format them in a way you
-- 
cgit v1.2.3


From 6279bf321ee17d9aff27843dfc78b601df8cbd35 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Fri, 1 Apr 2022 00:57:49 +0200
Subject: sfeed_curses: small code-style change

---
 sfeed_curses.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/sfeed_curses.c b/sfeed_curses.c
index 55791f0..52dba93 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -1569,10 +1569,10 @@ void
 sighandler(int signo)
 {
 	switch (signo) {
-	case SIGCHLD:  sigstate |= SigChld; break;
-	case SIGHUP:   sigstate |= SigHup; break;
-	case SIGINT:   sigstate |= SigInt; break;
-	case SIGTERM:  sigstate |= SigTerm; break;
+	case SIGCHLD:  sigstate |= SigChld;  break;
+	case SIGHUP:   sigstate |= SigHup;   break;
+	case SIGINT:   sigstate |= SigInt;   break;
+	case SIGTERM:  sigstate |= SigTerm;  break;
 	case SIGWINCH: sigstate |= SigWinch; break;
 	}
 }
@@ -2324,7 +2324,7 @@ event:
 		else if (ch == -3 && sigstate == 0)
 			continue; /* just a time-out, nothing to do */
 
-		/* handle signals, in order of importance */
+		/* handle signals in a particular order */
 		if (sigstate & SigChld) {
 			sigstate &= ~SigChld;
 			/* wait on child processes so they don't become a zombie,
-- 
cgit v1.2.3


From b2d6839a8c025698cd80118331f21fa4bb459f02 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Fri, 1 Apr 2022 21:24:22 +0200
Subject: sfeed_curses: don't use bitmasks to keep the states for signals

Reported by Leon Fisher, thanks!

"Each signal should have its own sig_atomic_t variable if you want to
keep state for all of them independently."

A similar issue was referenced in a OpenBSD tech mailinglist thread:
https://marc.info/?l=openbsd-tech&m=162940120614641&w=2
---
 sfeed_curses.c | 45 ++++++++++++++++++++++++++-------------------
 1 file changed, 26 insertions(+), 19 deletions(-)

diff --git a/sfeed_curses.c b/sfeed_curses.c
index 52dba93..4f92115 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -172,8 +172,8 @@ static time_t comparetime;
 static char *urlfile, **urls;
 static size_t nurls;
 
-enum Signals { SigChld = 1, SigHup = 2, SigInt = 4, SigTerm = 8, SigWinch = 16 };
-volatile sig_atomic_t sigstate = 0; /* bitmask state of received signals */
+volatile sig_atomic_t state_sigchld = 0, state_sighup = 0, state_sigint = 0;
+volatile sig_atomic_t state_sigterm = 0, state_sigwinch = 0;
 
 static char *plumbercmd = "xdg-open"; /* env variable: $SFEED_PLUMBER */
 static char *pipercmd = "sfeed_content"; /* env variable: $SFEED_PIPER */
@@ -1004,9 +1004,11 @@ lineeditor(void)
 			ttywrite(&input[nchars]);
 			nchars++;
 		} else if (ch < 0) {
-			if (sigstate & SigInt)
-				sigstate &= ~SigInt; /* reset: do not handle it later */
-			else if (!sigstate || (sigstate & (SigChld | SigWinch)))
+			if (state_sigint)
+				state_sigint = 0; /* reset: do not handle it later */
+			else if (state_sighup || state_sigterm)
+				; /* cancel prompt and handle these signals */
+			else /* no signal, time-out or SIGCHLD or SIGWINCH */
 				continue; /* do not cancel: process signal later */
 			free(input);
 			return NULL; /* cancel prompt */
@@ -1569,11 +1571,11 @@ void
 sighandler(int signo)
 {
 	switch (signo) {
-	case SIGCHLD:  sigstate |= SigChld;  break;
-	case SIGHUP:   sigstate |= SigHup;   break;
-	case SIGINT:   sigstate |= SigInt;   break;
-	case SIGTERM:  sigstate |= SigTerm;  break;
-	case SIGWINCH: sigstate |= SigWinch; break;
+	case SIGCHLD:  state_sigchld = 1;  break;
+	case SIGHUP:   state_sighup = 1;   break;
+	case SIGINT:   state_sigint = 1;   break;
+	case SIGTERM:  state_sigterm = 1;  break;
+	case SIGWINCH: state_sigwinch = 1; break;
 	}
 }
 
@@ -2321,28 +2323,33 @@ nextpage:
 event:
 		if (ch == EOF)
 			goto end;
-		else if (ch == -3 && sigstate == 0)
+		else if (ch == -3 && !state_sigchld && !state_sighup &&
+		         !state_sigint && !state_sigterm && !state_sigwinch)
 			continue; /* just a time-out, nothing to do */
 
 		/* handle signals in a particular order */
-		if (sigstate & SigChld) {
-			sigstate &= ~SigChld;
+		if (state_sigchld) {
+			state_sigchld = 0;
 			/* wait on child processes so they don't become a zombie,
 			   do not block the parent process if there is no status,
 			   ignore errors */
 			while (waitpid((pid_t)-1, NULL, WNOHANG) > 0)
 				;
 		}
-		if (sigstate & (SigInt | SigTerm)) {
+		if (state_sigterm) {
 			cleanup();
-			_exit(128 + (sigstate & SigTerm ? SIGTERM : SIGINT));
+			_exit(128 + SIGTERM);
 		}
-		if (sigstate & SigHup) {
-			sigstate &= ~SigHup;
+		if (state_sigint) {
+			cleanup();
+			_exit(128 + SIGINT);
+		}
+		if (state_sighup) {
+			state_sighup = 0;
 			feeds_reloadall();
 		}
-		if (sigstate & SigWinch) {
-			sigstate &= ~SigWinch;
+		if (state_sigwinch) {
+			state_sigwinch = 0;
 			resizewin();
 			updategeom();
 		}
-- 
cgit v1.2.3


From 3eb3533fae6c43907adbd340b24bee79f709504e Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sat, 2 Apr 2022 00:32:28 +0200
Subject: sfeed_curses: line editor, temporarily disable the mouse when using
 the line editor

This allows pasting in the terminal and also doesnt spam mouse events, which
are not useful here.
---
 sfeed_curses.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/sfeed_curses.c b/sfeed_curses.c
index 4f92115..a12b41f 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -983,6 +983,8 @@ lineeditor(void)
 	size_t cap = 0, nchars = 0;
 	int ch;
 
+	if (usemouse)
+		mousemode(0);
 	for (;;) {
 		if (nchars + 2 >= cap) {
 			cap = cap ? cap * 2 : 32;
@@ -1011,9 +1013,12 @@ lineeditor(void)
 			else /* no signal, time-out or SIGCHLD or SIGWINCH */
 				continue; /* do not cancel: process signal later */
 			free(input);
-			return NULL; /* cancel prompt */
+			input = NULL;
+			break; /* cancel prompt */
 		}
 	}
+	if (usemouse)
+		mousemode(usemouse);
 	return input;
 }
 
-- 
cgit v1.2.3


From f4c4b97bba99c7848bf3cda287564db8219b0f40 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sat, 2 Apr 2022 00:33:29 +0200
Subject: sfeed_curses: line editor: handle SIGCHLD directly

This makes sure when processes exit they are reaped immediately (instead of
after closing the line editor).
---
 sfeed_curses.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/sfeed_curses.c b/sfeed_curses.c
index a12b41f..03c38fe 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -1006,12 +1006,19 @@ lineeditor(void)
 			ttywrite(&input[nchars]);
 			nchars++;
 		} else if (ch < 0) {
+			if (state_sigchld) {
+				state_sigchld = 0;
+				/* wait on child processes so they don't become a zombie */
+				while (waitpid((pid_t)-1, NULL, WNOHANG) > 0)
+					;
+			}
 			if (state_sigint)
-				state_sigint = 0; /* reset: do not handle it later */
+				state_sigint = 0; /* cancel prompt and don't handle this signal */
 			else if (state_sighup || state_sigterm)
 				; /* cancel prompt and handle these signals */
 			else /* no signal, time-out or SIGCHLD or SIGWINCH */
 				continue; /* do not cancel: process signal later */
+
 			free(input);
 			input = NULL;
 			break; /* cancel prompt */
-- 
cgit v1.2.3


From 031b279e81cf461584789c00ce7d10c4f1d0d90f Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sat, 2 Apr 2022 13:54:49 +0200
Subject: sfeed_curses: code-style: mouse mode: just use a constant

No functional change intended. Just slightly more clear.
---
 sfeed_curses.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/sfeed_curses.c b/sfeed_curses.c
index 03c38fe..d09f8a0 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -549,7 +549,7 @@ init(void)
 	cursormode(0);
 
 	if (usemouse)
-		mousemode(usemouse);
+		mousemode(1);
 
 	memset(&sa, 0, sizeof(sa));
 	sigemptyset(&sa.sa_mask);
@@ -1025,7 +1025,7 @@ lineeditor(void)
 		}
 	}
 	if (usemouse)
-		mousemode(usemouse);
+		mousemode(1);
 	return input;
 }
 
-- 
cgit v1.2.3


From 7225f6f9274173e65d002c886789c0f24d2045a6 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sat, 2 Apr 2022 17:35:31 +0200
Subject: bump version to 1.4

---
 Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index 349bbf2..5098ee6 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
 .POSIX:
 
 NAME = sfeed
-VERSION = 1.3
+VERSION = 1.4
 
 # curses theme, see themes/ directory.
 SFEED_THEME = mono
-- 
cgit v1.2.3


From c3f5dcec10b0ce7042cca38c90f54e86d1297cb6 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sun, 10 Apr 2022 18:28:33 +0200
Subject: sfeed_curses.1: fix typo: automaticly -> automatically

Noticed by the Debian scanner for typos (typo-in-manual-page):
https://mentors.debian.net/package/sfeed/
---
 sfeed_curses.1 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sfeed_curses.1 b/sfeed_curses.1
index 703d7a3..c981c00 100644
--- a/sfeed_curses.1
+++ b/sfeed_curses.1
@@ -98,7 +98,7 @@ height by 1 column.
 Use a fixed sidebar size for the current layout and increase the fixed width or
 height by 1 column.
 .It =
-Reset the sidebar size to automaticly adjust for the current layout.
+Reset the sidebar size to automatically adjust for the current layout.
 With the vertical layout the width is the longest feed name with the item
 counts right-aligned.
 With the horizontal layout the height is half of the window height (minus the
-- 
cgit v1.2.3


From 06cd065b6e4ee41d893caae5200bf276ae6a12bd Mon Sep 17 00:00:00 2001
From: Anders Damsgaard 
Date: Mon, 2 May 2022 14:46:25 +0200
Subject: manual pages: properly escape backslashes
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

From the mdoc(7) extended introduction[0]:

>To output a backslash, use the escape sequence ‘\e’. Never use the
>escape sequence ‘\\’ in any context.

0: https://mandoc.bsd.lv/mdoc/intro/escaping.html
---
 sfeed.1   | 4 ++--
 sfeed.5   | 4 ++--
 sfeedrc.5 | 2 +-
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/sfeed.1 b/sfeed.1
index 70354e3..34cd5fb 100644
--- a/sfeed.1
+++ b/sfeed.1
@@ -23,8 +23,8 @@ SPACE character.
 Control characters are removed.
 .Pp
 The content field can contain newlines and these are escaped.
-TABs, newlines and '\\' are escaped with '\\', so it becomes: '\\t', '\\n'
-and '\\\\'.
+TABs, newlines and '\e' are escaped with '\e', so it becomes: '\et', '\en'
+and '\e\e'.
 Other whitespace characters except spaces are removed.
 Control characters are removed.
 .Pp
diff --git a/sfeed.5 b/sfeed.5
index cd422d0..8b2638e 100644
--- a/sfeed.5
+++ b/sfeed.5
@@ -17,8 +17,8 @@ SPACE character.
 Control characters are removed.
 .Pp
 The content field can contain newlines and these are escaped.
-TABs, newlines and '\\' are escaped with '\\', so it becomes: '\\t', '\\n'
-and '\\\\'.
+TABs, newlines and '\e' are escaped with '\e', so it becomes: '\et', '\en'
+and '\e\e'.
 Other whitespace characters except spaces are removed.
 Control characters are removed.
 .Pp
diff --git a/sfeedrc.5 b/sfeedrc.5
index 95ffc04..10c234e 100644
--- a/sfeedrc.5
+++ b/sfeedrc.5
@@ -160,7 +160,7 @@ file:
 # fetch(name, url, feedfile)
 fetch() {
 	# allow for 1 redirect, hide User-Agent, timeout is 15 seconds.
-	curl -L --max-redirs 1 -H "User-Agent:" -f -s -m 15 \\
+	curl -L --max-redirs 1 -H "User-Agent:" -f -s -m 15 \e
 		"$2" 2>/dev/null
 }
 .Ed
-- 
cgit v1.2.3


From 796e311b746ef94f11efcf003e6b3c858597e447 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Mon, 2 May 2022 20:09:15 +0200
Subject: manual pages: properly escape some more backslashes

... and bump the date also.
---
 sfeed.1        | 6 +++---
 sfeed.5        | 2 +-
 sfeed_gopher.1 | 4 ++--
 sfeedrc.5      | 2 +-
 4 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/sfeed.1 b/sfeed.1
index 34cd5fb..c4a3c02 100644
--- a/sfeed.1
+++ b/sfeed.1
@@ -1,4 +1,4 @@
-.Dd March 29, 2022
+.Dd May 2, 2022
 .Dt SFEED 1
 .Os
 .Sh NAME
@@ -60,8 +60,8 @@ To convert the character set from a feed that is not UTF-8 encoded the
 .Xr iconv 1
 tool can be used:
 .Bd -literal
-curl -s 'https://codemadness.org/some_iso-8859-1_feed.xml' | \\
-iconv -f iso-8859-1 -t utf-8 | \\
+curl -s 'https://codemadness.org/some_iso-8859-1_feed.xml' | \e
+iconv -f iso-8859-1 -t utf-8 | \e
 sfeed
 .Ed
 .Sh EXAMPLE SETUP
diff --git a/sfeed.5 b/sfeed.5
index 8b2638e..be55c18 100644
--- a/sfeed.5
+++ b/sfeed.5
@@ -1,4 +1,4 @@
-.Dd November 23, 2021
+.Dd May 2, 2022
 .Dt SFEED 5
 .Os
 .Sh NAME
diff --git a/sfeed_gopher.1 b/sfeed_gopher.1
index a5b4e3a..671cd08 100644
--- a/sfeed_gopher.1
+++ b/sfeed_gopher.1
@@ -1,4 +1,4 @@
-.Dd July 31, 2021
+.Dd May 2, 2022
 .Dt SFEED_GOPHER 1
 .Os
 .Sh NAME
@@ -50,7 +50,7 @@ The default is "70".
 .Ex -std
 .Sh EXAMPLES
 .Bd -literal
-SFEED_GOPHER_HOST="codemadness.org" SFEED_GOPHER_PATH="/feeds/" \\
+SFEED_GOPHER_HOST="codemadness.org" SFEED_GOPHER_PATH="/feeds/" \e
 	sfeed_gopher ~/.sfeed/feeds/*
 .Ed
 .Sh SEE ALSO
diff --git a/sfeedrc.5 b/sfeedrc.5
index 10c234e..344ee42 100644
--- a/sfeedrc.5
+++ b/sfeedrc.5
@@ -1,4 +1,4 @@
-.Dd March 7, 2022
+.Dd May 2, 2022
 .Dt SFEEDRC 5
 .Os
 .Sh NAME
-- 
cgit v1.2.3


From f0ca847fca5100dd98fbbed9c49b08ba5f310ac5 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Wed, 4 May 2022 18:28:10 +0200
Subject: sfeed_curses.1: more details about signals and (non-)interactive
 programs

- SIGINT: reword, canceling the line editor is the exception.
- Document the order of signal handling.
- Document interactive and non-interactive behaviour in a separate section and
  in more detail.
  Remove repetition for each *_INTERACTIVE environment variable.
- Document SIGINT and SIGTERM exit status.
- Typo: "statusbar" -> "status bar".
- Reword a few sentences.

With additional changes and feedback from adc, thanks!
---
 sfeed_curses.1 | 52 ++++++++++++++++++++++++++++++++--------------------
 1 file changed, 32 insertions(+), 20 deletions(-)

diff --git a/sfeed_curses.1 b/sfeed_curses.1
index c981c00..30d6543 100644
--- a/sfeed_curses.1
+++ b/sfeed_curses.1
@@ -1,4 +1,4 @@
-.Dd February 24, 2022
+.Dd May 4, 2022
 .Dt SFEED_CURSES 1
 .Os
 .Sh NAME
@@ -102,7 +102,7 @@ Reset the sidebar size to automatically adjust for the current layout.
 With the vertical layout the width is the longest feed name with the item
 counts right-aligned.
 With the horizontal layout the height is half of the window height (minus the
-statusbar) or otherwise the total amount of visible feeds, whichever fits the
+status bar) or otherwise the total amount of visible feeds, whichever fits the
 best.
 .It t
 Toggle showing only feeds with new items in the sidebar.
@@ -188,13 +188,19 @@ Reload all feed files which were specified as arguments on startup.
 If
 .Ev SFEED_URL_FILE
 is set, it will reload the URLs from this file also.
+Cancels the line editor and handles the signal if received during a search.
 .It SIGINT
-Interrupt: when searching it cancels the line editor, otherwise it quits.
+Interrupt: quit.
+When searching, it only cancels the line editor and doesn't quit.
 .It SIGTERM
 Quit
 .It SIGWINCH
 Resize the pane dimensions relative to the terminal size.
+When searching, it handles the signal after closing the line editor.
 .El
+.Pp
+Signals are handled in the following order: SIGCHLD, SIGTERM, SIGINT, SIGHUP,
+SIGWINCH.
 .Sh ENVIRONMENT VARIABLES
 .Bl -tag -width Ds
 .It Ev SFEED_AUTOCMD
@@ -206,22 +212,13 @@ A program where the whole TAB-Separated Value line is piped to.
 By default this is "sfeed_content".
 .It Ev SFEED_PIPER_INTERACTIVE
 Handle the program interactively in the same terminal or not.
-If set to "1" then before execution it restores the terminal attributes and
-.Nm
-will wait until the program is finished.
-If set to "0" then it will suppress stdout and stderr output.
 By default this is set to "1".
 .It Ev SFEED_PLUMBER
 A program that receives the link URL or enclosure URL as a parameter.
 By default this is "xdg-open".
 .It Ev SFEED_PLUMBER_INTERACTIVE
 Handle the program interactively in the same terminal or not.
-If set to "1" then before execution it restores the terminal attributes and
-.Nm
-will wait until the program is finished.
-If set to "0" then it will suppress stdout and stderr output.
-For example this option is useful to open a text-mode browser in the same
-terminal.
+This option can be useful to open a text-mode browser in the same terminal.
 By default this is set to "0".
 .It Ev SFEED_YANKER
 A program where the URL or enclosure field is piped to, to copy it to a
@@ -229,10 +226,6 @@ clipboard.
 By default this is "xclip -r".
 .It Ev SFEED_YANKER_INTERACTIVE
 Handle the program interactively in the same terminal or not.
-If set to "1" then before execution it restores the terminal attributes and
-.Nm
-will wait until the program is finished.
-If set to "0" then it will suppress stdout and stderr output.
 By default this is set to "0".
 .It Ev SFEED_URL_FILE
 If this variable is set then a different mode is used to mark items as read,
@@ -267,10 +260,10 @@ It can also cause a race-condition issue if the feed data on disk is changed
 while having the UI open and offsets for the lines are different.
 A workaround for the race-condition issue is by sending the SIGHUP signal to
 .Nm
-directly after the data was updated.
-This forces
+after the data was updated.
+This makes
 .Nm
-to reload the latest feed data and update the correct line offsets.
+reload the latest feed data and update the correct line offsets.
 By default this is set to "0".
 .It Ev SFEED_FEED_PATH
 This variable is set by
@@ -279,8 +272,27 @@ when a feed is loaded.
 If the data was read from stdin this variable is unset.
 It can be used by the plumb or pipe program for scripting purposes.
 .El
+.Sh INTERACTIVE AND NON-INTERACTIVE PROGRAMS
+.Nm
+can pipe content, plumb and yank interactively or in a non-interactive manner.
+In interactive mode
+.Nm
+waits until the process exits.
+Stdout and stderr of the program are written as output.
+It stores and restores the terminal attributes before and after executing the
+program.
+The signals SIGHUP and SIGWINCH will be handled after
+.Nm
+has waited on the program.
+SIGINT is ignored while waiting on the program.
+.Pp
+In non-interactive mode
+.Nm
+doesn't wait until the process exits.
+Stdout and stderr of the program are not written as output.
 .Sh EXIT STATUS
 .Ex -std
+The exit status is 130 on SIGINT and 143 on SIGTERM.
 .Sh EXAMPLES
 .Bd -literal
 sfeed_curses ~/.sfeed/feeds/*
-- 
cgit v1.2.3


From 78e54359372d02d17e9ab453c7bc0cd725b807f2 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Thu, 5 May 2022 06:28:11 +0200
Subject: sfeed_curses: close stdin for spawning a plumb program in
 non-interactive mode

This is only for plumbing in non-interactive mode in forkexec(), but not piping
content.

Probably obvious what the descriptors are, but also add a few comments to dup2
of the file descriptors (stdin, stdout, stderr).

To reproduce a behaviour:

	Plumb script:

	#!/bin/sh
	dmenu

	Then launch it:

	SFEED_PLUMB_INTERACTIVE=0 SFEED_PLUMB=thescript sfeed_curses ~/.sfeed/feeds/*

The program now waits on input while in non-interactive mode and only seems to
hang.

After:

The program starts but just has no input passed to it.
---
 sfeed_curses.1 |  3 ++-
 sfeed_curses.c | 13 +++++++------
 2 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/sfeed_curses.1 b/sfeed_curses.1
index 30d6543..be68d92 100644
--- a/sfeed_curses.1
+++ b/sfeed_curses.1
@@ -1,4 +1,4 @@
-.Dd May 4, 2022
+.Dd May 5, 2022
 .Dt SFEED_CURSES 1
 .Os
 .Sh NAME
@@ -290,6 +290,7 @@ In non-interactive mode
 .Nm
 doesn't wait until the process exits.
 Stdout and stderr of the program are not written as output.
+When plumbing an URL then stdin is closed also.
 .Sh EXIT STATUS
 .Ex -std
 The exit status is 130 on SIGINT and 143 on SIGTERM.
diff --git a/sfeed_curses.c b/sfeed_curses.c
index d09f8a0..78071b7 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -604,8 +604,8 @@ pipeitem(const char *cmd, struct item *item, int field, int interactive)
 		die("fork");
 	case 0:
 		if (!interactive) {
-			dup2(devnullfd, 1);
-			dup2(devnullfd, 2);
+			dup2(devnullfd, 1); /* stdout */
+			dup2(devnullfd, 2); /* stderr */
 		}
 
 		errno = 0;
@@ -642,8 +642,9 @@ forkexec(char *argv[], int interactive)
 		die("fork");
 	case 0:
 		if (!interactive) {
-			dup2(devnullfd, 1);
-			dup2(devnullfd, 2);
+			dup2(devnullfd, 0); /* stdin */
+			dup2(devnullfd, 1); /* stdout */
+			dup2(devnullfd, 2); /* stderr */
 		}
 		if (execvp(argv[0], argv) == -1)
 			_exit(1);
@@ -1847,8 +1848,8 @@ markread(struct pane *p, off_t from, off_t to, int isread)
 	case -1:
 		die("fork");
 	case 0:
-		dup2(devnullfd, 1);
-		dup2(devnullfd, 2);
+		dup2(devnullfd, 1); /* stdout */
+		dup2(devnullfd, 2); /* stderr */
 
 		errno = 0;
 		if (!(fp = popen(cmd, "w")))
-- 
cgit v1.2.3


From c64dff716eced3fd554428b8d9e5d2f12ceac9e8 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sat, 7 May 2022 20:18:58 +0200
Subject: sfeed_curses: interrupt waitpid while interactive child program is
 running

This now handles SIGTERM on sfeed_curses properly while an interactive child program is running.

Test-case program:
https://git.codemadness.org/sfeed_tests/commit/cd4268d4f71b5d7ab0df593d95e70188475d76bb.html
---
 sfeed_curses.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/sfeed_curses.c b/sfeed_curses.c
index 78071b7..d22d16c 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -575,8 +575,14 @@ processexit(pid_t pid, int interactive)
 		/* ignore SIGINT (^C) in parent for interactive applications */
 		sa.sa_handler = SIG_IGN;
 		sigaction(SIGINT, &sa, NULL);
+
+		sa.sa_flags = 0; /* do not restart SIGTERM: this interrupts waitpid() */
+		sa.sa_handler = sighandler;
+		sigaction(SIGTERM, &sa, NULL);
+
 		/* wait for process to change state, ignore errors */
 		waitpid(pid, NULL, 0);
+
 		init();
 		updatesidebar();
 		updategeom();
-- 
cgit v1.2.3


From 8b7c95d1a6a3d640e0193f7ab0811694d62cd593 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sat, 7 May 2022 20:33:08 +0200
Subject: sfeed_curses: improve comment

Do not restart the syscall waitpid, explicitly mention not setting SA_RESTART.
---
 sfeed_curses.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sfeed_curses.c b/sfeed_curses.c
index d22d16c..a38a25e 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -576,7 +576,7 @@ processexit(pid_t pid, int interactive)
 		sa.sa_handler = SIG_IGN;
 		sigaction(SIGINT, &sa, NULL);
 
-		sa.sa_flags = 0; /* do not restart SIGTERM: this interrupts waitpid() */
+		sa.sa_flags = 0; /* SIGTERM: interrupt waitpid(), no SA_RESTART */
 		sa.sa_handler = sighandler;
 		sigaction(SIGTERM, &sa, NULL);
 
-- 
cgit v1.2.3


From d871d884d37a2d9e41278198ac52b50850aff969 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sat, 14 May 2022 12:27:50 +0200
Subject: manual pages: reword a few things a bit more clearly

---
 sfeed_gopher.1 |  4 ++--
 sfeed_plain.1  |  4 ++--
 sfeed_update.1 | 10 +++++-----
 3 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/sfeed_gopher.1 b/sfeed_gopher.1
index 671cd08..43a11c7 100644
--- a/sfeed_gopher.1
+++ b/sfeed_gopher.1
@@ -1,4 +1,4 @@
-.Dd May 2, 2022
+.Dd May 14, 2022
 .Dt SFEED_GOPHER 1
 .Os
 .Sh NAME
@@ -32,7 +32,7 @@ written to stdout and no files are written.
 .Pp
 Items with a timestamp from the last day compared to the system time at the
 time of formatting are counted and marked as new.
-Items are marked as new with the prefix "N".
+Items are marked as new with the prefix "N" at the start of the line.
 .Sh ENVIRONMENT
 .Bl -tag -width Ds
 .It Ev SFEED_GOPHER_PATH
diff --git a/sfeed_plain.1 b/sfeed_plain.1
index 559ce77..466ae41 100644
--- a/sfeed_plain.1
+++ b/sfeed_plain.1
@@ -1,4 +1,4 @@
-.Dd July 25, 2021
+.Dd May 14, 2022
 .Dt SFEED_PLAIN 1
 .Os
 .Sh NAME
@@ -26,7 +26,7 @@ is empty.
 .Pp
 Items with a timestamp from the last day compared to the system time at the
 time of formatting are marked as new.
-Items are marked as new with the prefix "N".
+Items are marked as new with the prefix "N" at the start of the line.
 .Pp
 .Nm
 aligns the output.
diff --git a/sfeed_update.1 b/sfeed_update.1
index 893f290..49cf76a 100644
--- a/sfeed_update.1
+++ b/sfeed_update.1
@@ -1,4 +1,4 @@
-.Dd March 28, 2022
+.Dd May 14, 2022
 .Dt SFEED_UPDATE 1
 .Os
 .Sh NAME
@@ -66,13 +66,13 @@ If any of the feeds failed to update then the return status is non-zero.
 .Sh EXAMPLES
 To update your feeds and format them in various formats:
 .Bd -literal
-# Update
+# Update feeds
 sfeed_update "configfile"
-# Plain-text list
+# Format to a plain-text list
 sfeed_plain ~/.sfeed/feeds/* > ~/.sfeed/feeds.txt
-# HTML
+# Format to HTML
 sfeed_html ~/.sfeed/feeds/* > ~/.sfeed/feeds.html
-# HTML with frames
+# Format to HTML with frames
 mkdir -p somedir && cd somedir && sfeed_frames ~/.sfeed/feeds/*
 .Ed
 .Sh SEE ALSO
-- 
cgit v1.2.3


From e1676198f01a1ba713e1bc9705db8df80cc47cfa Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sat, 14 May 2022 12:28:58 +0200
Subject: README: add progress indicator script

---
 README | 47 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 47 insertions(+)

diff --git a/README b/README
index 92fe929..92d77fe 100644
--- a/README
+++ b/README
@@ -971,6 +971,53 @@ TSV format.
 
 - - -
 
+Progress indicator
+------------------
+
+The below sfeed_update wrapper script counts the amount of feeds in a sfeedrc
+config.  It then calls sfeed_update and pipes the output lines to a function
+that counts the current progress. It writes the total progress to stderr.
+Alternative: pv -l -s totallines
+
+	#!/bin/sh
+	# Progress indicator script.
+	
+	# Pass lines as input to stdin and write progress status to stderr.
+	# progress(totallines)
+	progress() {
+		total="$(($1 + 0))" # must be a number, no divide by zero.
+		test "${total}" -le 0 -o "$1" != "${total}" && return
+	LC_ALL=C awk -v "total=${total}" '
+	{
+		counter++;
+		percent = (counter * 100) / total;
+		printf("\033[K") > "/dev/stderr"; # clear EOL
+		print $0;
+		printf("[%s/%s] %.0f%%\r", counter, total, percent) > "/dev/stderr";
+		fflush(); # flush all buffers per line.
+	}
+	END {
+		printf("\033[K") > "/dev/stderr";
+	}'
+	}
+	
+	# Counts the feeds from the sfeedrc config.
+	countfeeds() {
+		count=0
+	. "$1"
+	feed() {
+		count=$((count + 1))
+	}
+		feeds
+		echo "${count}"
+	}
+	
+	config="${1:-$HOME/.sfeed/sfeedrc}"
+	total=$(countfeeds "${config}")
+	sfeed_update "${config}" | progress "${total}"
+
+- - -
+
 Counting unread and total items
 -------------------------------
 
-- 
cgit v1.2.3


From 0391bae406896b068c9e82b76aa6c26c7928efc7 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Thu, 19 May 2022 21:32:42 +0200
Subject: README: make sure sfeed_update errors don't write to stderr

It would mess up the output since the progress bar writes to stderr aswell.
---
 README | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README b/README
index 92d77fe..7753a8e 100644
--- a/README
+++ b/README
@@ -1014,7 +1014,7 @@ Alternative: pv -l -s totallines
 	
 	config="${1:-$HOME/.sfeed/sfeedrc}"
 	total=$(countfeeds "${config}")
-	sfeed_update "${config}" | progress "${total}"
+	sfeed_update "${config}" 2>&1 | progress "${total}"
 
 - - -
 
-- 
cgit v1.2.3


From 41b46d04521618d33bb159cc963b1d8e6b6e173f Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sat, 21 May 2022 11:34:58 +0200
Subject: bump version to 1.5

---
 Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index 5098ee6..0772230 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
 .POSIX:
 
 NAME = sfeed
-VERSION = 1.4
+VERSION = 1.5
 
 # curses theme, see themes/ directory.
 SFEED_THEME = mono
-- 
cgit v1.2.3


From 6c0d824397cf59d75258fbed6c937d5bf6dafd23 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Tue, 31 May 2022 11:30:56 +0200
Subject: minicurses.h: remove unneeded include

In the past there was an ioctl() call in setupterm(), so this include is not
needed anymore.
---
 minicurses.h | 2 --
 1 file changed, 2 deletions(-)

diff --git a/minicurses.h b/minicurses.h
index ad24b5e..9b8112b 100644
--- a/minicurses.h
+++ b/minicurses.h
@@ -1,5 +1,3 @@
-#include 
-
 #undef  OK
 #define OK  (0)
 
-- 
cgit v1.2.3


From f8ef20b2593304fd08a4599edfe3a66aa3a845e3 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Tue, 31 May 2022 11:33:06 +0200
Subject: README: fix bug in an example for counting the new items of the last
 day

This should not use the plain-text URL file ($SFEED_URL_FILE).
---
 README | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README b/README
index 7753a8e..ece2f28 100644
--- a/README
+++ b/README
@@ -1039,7 +1039,7 @@ formatting tools do:
 	END {
 		print "New:   " totalnew;
 		print "Total: " total;
-	}' ~/.sfeed/urls ~/.sfeed/feeds/*
+	}' ~/.sfeed/feeds/*
 
 The below example script counts the unread items using the sfeed_curses URL
 file:
-- 
cgit v1.2.3


From cc3dd4534bcbbc750d563992b93b1131410a2a76 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sun, 5 Jun 2022 23:38:22 +0200
Subject: sfeed_curses: processexit(): remove unneeded code for non-interactive
 processes

This was used in commit a58fa45f25da4f18d7b8c1a815884f67b965406f and previous,
but the code for non-interactive process cleanup was later removed, so clearing
the struct is not not needed anymore, because it is unused.
---
 sfeed_curses.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/sfeed_curses.c b/sfeed_curses.c
index a38a25e..e05d1c9 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -567,11 +567,11 @@ processexit(pid_t pid, int interactive)
 {
 	struct sigaction sa;
 
-	memset(&sa, 0, sizeof(sa));
-	sigemptyset(&sa.sa_mask);
-	sa.sa_flags = SA_RESTART; /* require BSD signal semantics */
-
 	if (interactive) {
+		memset(&sa, 0, sizeof(sa));
+		sigemptyset(&sa.sa_mask);
+		sa.sa_flags = SA_RESTART; /* require BSD signal semantics */
+
 		/* ignore SIGINT (^C) in parent for interactive applications */
 		sa.sa_handler = SIG_IGN;
 		sigaction(SIGINT, &sa, NULL);
-- 
cgit v1.2.3


From 8737035999474283a28faa5780f43138f96ab82c Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Mon, 6 Jun 2022 11:00:26 +0200
Subject: themes: simplify empty macros, remove do { } while(0)

Multiple function calls should still be nested in a do { } while(0), but this
is hopefully obvious looking at the other lines.

While testing, gcc with -O0 created the same binary.
clang added a single jmp instruction.
---
 themes/mono.h           | 8 ++++----
 themes/mono_highlight.h | 8 ++++----
 themes/newsboat.h       | 8 ++++----
 3 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/themes/mono.h b/themes/mono.h
index 586ede0..253d76e 100644
--- a/themes/mono.h
+++ b/themes/mono.h
@@ -1,13 +1,13 @@
 /* default mono theme */
-#define THEME_ITEM_NORMAL()           do {                            } while(0)
-#define THEME_ITEM_FOCUS()            do {                            } while(0)
+#define THEME_ITEM_NORMAL()
+#define THEME_ITEM_FOCUS()
 #define THEME_ITEM_BOLD()             do { attrmode(ATTR_BOLD_ON);    } while(0)
 #define THEME_ITEM_SELECTED()         do { if (p->focused) attrmode(ATTR_REVERSE_ON); } while(0)
-#define THEME_SCROLLBAR_FOCUS()       do {                            } while(0)
+#define THEME_SCROLLBAR_FOCUS()
 #define THEME_SCROLLBAR_NORMAL()      do { attrmode(ATTR_FAINT_ON);   } while(0)
 #define THEME_SCROLLBAR_TICK_FOCUS()  do { attrmode(ATTR_REVERSE_ON); } while(0)
 #define THEME_SCROLLBAR_TICK_NORMAL() do { attrmode(ATTR_REVERSE_ON); } while(0)
 #define THEME_LINEBAR()               do { attrmode(ATTR_FAINT_ON);   } while(0)
 #define THEME_STATUSBAR()             do { attrmode(ATTR_REVERSE_ON); } while(0)
 #define THEME_INPUT_LABEL()           do { attrmode(ATTR_REVERSE_ON); } while(0)
-#define THEME_INPUT_NORMAL()          do {                            } while(0)
+#define THEME_INPUT_NORMAL()
diff --git a/themes/mono_highlight.h b/themes/mono_highlight.h
index 180f651..d57ae9c 100644
--- a/themes/mono_highlight.h
+++ b/themes/mono_highlight.h
@@ -1,15 +1,15 @@
 /* mono theme with highlighting of the active panel.
    The faint attribute may not work on all terminals though.
    The combination bold with faint generally does not work either. */
-#define THEME_ITEM_NORMAL()           do {                            } while(0)
-#define THEME_ITEM_FOCUS()            do {                            } while(0)
+#define THEME_ITEM_NORMAL()
+#define THEME_ITEM_FOCUS()
 #define THEME_ITEM_BOLD()             do { if (p->focused || !selected) attrmode(ATTR_BOLD_ON); } while(0)
 #define THEME_ITEM_SELECTED()         do { attrmode(ATTR_REVERSE_ON); if (!p->focused) attrmode(ATTR_FAINT_ON); } while(0)
-#define THEME_SCROLLBAR_FOCUS()       do {                            } while(0)
+#define THEME_SCROLLBAR_FOCUS()
 #define THEME_SCROLLBAR_NORMAL()      do { attrmode(ATTR_FAINT_ON);   } while(0)
 #define THEME_SCROLLBAR_TICK_FOCUS()  do { attrmode(ATTR_REVERSE_ON); } while(0)
 #define THEME_SCROLLBAR_TICK_NORMAL() do { attrmode(ATTR_REVERSE_ON); } while(0)
 #define THEME_LINEBAR()               do { attrmode(ATTR_FAINT_ON);   } while(0)
 #define THEME_STATUSBAR()             do { attrmode(ATTR_REVERSE_ON); } while(0)
 #define THEME_INPUT_LABEL()           do { attrmode(ATTR_REVERSE_ON); } while(0)
-#define THEME_INPUT_NORMAL()          do {                            } while(0)
+#define THEME_INPUT_NORMAL()
diff --git a/themes/newsboat.h b/themes/newsboat.h
index b00111d..79e9dd2 100644
--- a/themes/newsboat.h
+++ b/themes/newsboat.h
@@ -1,6 +1,6 @@
 /* newsboat-like (blue, yellow) */
-#define THEME_ITEM_NORMAL()           do {                          } while(0)
-#define THEME_ITEM_FOCUS()            do {                          } while(0)
+#define THEME_ITEM_NORMAL()
+#define THEME_ITEM_FOCUS()
 #define THEME_ITEM_BOLD()             do { attrmode(ATTR_BOLD_ON);  } while(0)
 #define THEME_ITEM_SELECTED()         do { if (p->focused) ttywrite("\x1b[93;44m"); } while(0) /* bright yellow fg, blue bg */
 #define THEME_SCROLLBAR_FOCUS()       do { ttywrite("\x1b[34m");    } while(0) /* blue fg */
@@ -9,5 +9,5 @@
 #define THEME_SCROLLBAR_TICK_NORMAL() do { ttywrite("\x1b[44m");    } while(0)
 #define THEME_LINEBAR()               do { ttywrite("\x1b[34m");    } while(0)
 #define THEME_STATUSBAR()             do { attrmode(ATTR_BOLD_ON); ttywrite("\x1b[93;44m"); } while(0)
-#define THEME_INPUT_LABEL()           do {                          } while(0)
-#define THEME_INPUT_NORMAL()          do {                          } while(0)
+#define THEME_INPUT_LABEL()
+#define THEME_INPUT_NORMAL()
-- 
cgit v1.2.3


From 0f2ddea7cfe31a651533cf170bc31627415db593 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sat, 18 Jun 2022 22:52:44 +0200
Subject: README: add setsid example

---
 README | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/README b/README
index ece2f28..06f2c02 100644
--- a/README
+++ b/README
@@ -1109,6 +1109,21 @@ Example of a `syncnews.sh` shellscript to update the feeds and reload them:
 	pkill -SIGHUP sfeed_curses
 
 
+Running programs in a new session
+---------------------------------
+
+By default processes are spawned in the same session and process group as
+sfeed_curses.  When sfeed_curses is closed this can also close the spawned
+process in some cases.
+
+When the setsid command-line program is available the following wrapper command
+can be used to run the program in a new session, for a plumb program:
+
+	setsid -f xdg-open "$@"
+
+Alternatively the code can be changed to call setsid() before execvp().
+
+
 Open an URL directly in the same terminal
 -----------------------------------------
 
-- 
cgit v1.2.3


From bd5a0f5d36730ebb11a32c0c089b468b9d60ebe1 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Mon, 20 Jun 2022 13:18:43 +0200
Subject: sfeed: fix a wrong comment

---
 sfeed.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sfeed.c b/sfeed.c
index a77afb3..deef397 100644
--- a/sfeed.c
+++ b/sfeed.c
@@ -757,7 +757,7 @@ xmlattrentity(XMLParser *p, const char *t, size_t tl, const char *n, size_t nl,
 		return;
 
 	/* try to translate entity, else just pass as data to
-	 * xmldata handler. */
+	 * xmlattr handler. */
 	if ((len = xml_entitytostr(data, buf, sizeof(buf))) > 0)
 		xmlattr(p, t, tl, n, nl, buf, (size_t)len);
 	else
-- 
cgit v1.2.3


From 4b97fc93a6f8ae8cd4e7412884ba13137bc09c45 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Mon, 20 Jun 2022 13:25:12 +0200
Subject: sfeed: reword a poorly-worded comment

---
 sfeed.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sfeed.c b/sfeed.c
index deef397..7b0ef6c 100644
--- a/sfeed.c
+++ b/sfeed.c
@@ -966,7 +966,7 @@ xmltagend(XMLParser *p, const char *t, size_t tl, int isshort)
 		return;
 
 	if (ISINCONTENT(ctx)) {
-		/* not close content field */
+		/* not a closed content field */
 		if (!istag(ctx.tag.name, ctx.tag.len, t, tl)) {
 			if (!isshort && ctx.contenttype == ContentTypeHTML) {
 				xmldata(p, "
Date: Tue, 21 Jun 2022 05:09:14 +0600
Subject: fix some typis

found via codespell

	$ codespell --ignore-regex Nd
---
 README       | 2 +-
 README.xml   | 4 ++--
 sfeed_update | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/README b/README
index 06f2c02..b994c55 100644
--- a/README
+++ b/README
@@ -834,7 +834,7 @@ arguments are specified then the data is read from stdin.
 	if [ "${SFEED_DOWNLOAD_CHILD}" = "1" ]; then
 		# Downloader helper for parallel downloading.
 		# Receives arguments: $1 = URL, $2 = title, $3 = feed filename or "-".
-		# It should write the URI to the cachefile if it is succesful.
+		# It should write the URI to the cachefile if it is successful.
 		downloader "$1" "$2" "$3"
 		exit $?
 	fi
diff --git a/README.xml b/README.xml
index edbac28..ee205c0 100644
--- a/README.xml
+++ b/README.xml
@@ -28,7 +28,7 @@ Supports
 
 - Tags in short-form ().
 - Tag attributes.
-- Short attributes without an explicity set value ().
+- Short attributes without an explicitly set value ().
 - Comments
 - CDATA sections.
 - Helper function (xml_entitytostr) to convert XML 1.0 / HTML 2.0 named entities
@@ -55,7 +55,7 @@ Caveats
 - The XML specification has no limits on tag and attribute names. For
   simplicity/sanity sake this XML parser takes some liberties. Tag and
   attribute names are truncated if they are excessively long.
-- Entity expansions are not parsed aswell as DOCTYPE, ATTLIST etc.
+- Entity expansions are not parsed as well as DOCTYPE, ATTLIST etc.
 
 
 Files used
diff --git a/sfeed_update b/sfeed_update
index fc7447f..44ce23c 100755
--- a/sfeed_update
+++ b/sfeed_update
@@ -182,7 +182,7 @@ sighandler() {
 	signo="$1"
 	# ignore TERM signal for myself.
 	trap -- "" TERM
-	# kill all running childs >:D
+	# kill all running children >:D
 	kill -TERM -$$
 }
 
-- 
cgit v1.2.3


From 6890a40099bf940bb7bf21c0f5b073766ff67f5f Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Tue, 5 Jul 2022 15:57:35 +0200
Subject: sfeed_curses: write out plumbercmd argument in a verbose manner

Write it in a more verbose, clear and C90-style manner.

This workarounds a bug in scc too (reported upstream and will be fixed of
course). All tools in sfeed can now be compiled with minicurses-mode with the
scc compiler and musl libc without any modifications needed.

scc: http://www.simple-cc.org/
---
 sfeed_curses.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/sfeed_curses.c b/sfeed_curses.c
index e05d1c9..bc2e8ba 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -1432,13 +1432,15 @@ feed_plumb_selected_item(struct pane *p, int field)
 {
 	struct row *row;
 	struct item *item;
-	char *cmd[] = { plumbercmd, NULL, NULL };
+	char *cmd[3]; /* will have: { plumbercmd, arg, NULL } */
 
 	if (!(row = pane_row_get(p, p->pos)))
 		return;
 	markread(p, p->pos, p->pos, 1);
 	item = row->data;
+	cmd[0] = plumbercmd;
 	cmd[1] = item->fields[field]; /* set first argument for plumber */
+	cmd[2] = NULL;
 	forkexec(cmd, plumberia);
 }
 
-- 
cgit v1.2.3


From 7b2bec5985b79a73613c0858a845b97ad614794d Mon Sep 17 00:00:00 2001
From: NRK 
Date: Tue, 5 Jul 2022 21:11:37 +0600
Subject: remove __dead code

this check is already done in util.h and is no longer needed after the
`sfeed_curses -> sfeed` merge.
---
 sfeed_curses.c | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/sfeed_curses.c b/sfeed_curses.c
index bc2e8ba..b75c2cc 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -208,11 +208,6 @@ ttywrite(const char *s)
 	return write(1, s, strlen(s));
 }
 
-/* Hint for compilers and static analyzers that a function exits. */
-#ifndef __dead
-#define __dead
-#endif
-
 /* Print to stderr, call cleanup() and _exit(). */
 __dead void
 die(const char *fmt, ...)
-- 
cgit v1.2.3


From 59ba2ed29bcbe67de6e9ee1e7fb18744f7490c22 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sun, 17 Jul 2022 12:46:34 +0200
Subject: sfeed_curses: cleanup code for handling read items from an URL file

This makes it easier to reuse the code from reading plain-text list of items
from a file and uses a struct urls to keep its state in a cleaner way.

+ some small code-style improvements and reshuffling.

Inspired by a question from NRK by mail about implementing a "read later"
feature.

(A quick local experiment shows it can be done in ~70 lines of added code).
---
 sfeed_curses.c | 82 ++++++++++++++++++++++++++++++++--------------------------
 1 file changed, 45 insertions(+), 37 deletions(-)

diff --git a/sfeed_curses.c b/sfeed_curses.c
index b75c2cc..9f57ef9 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -129,10 +129,16 @@ struct item {
 	off_t offset; /* line offset in file for lazyload */
 };
 
+struct urls {
+	char **items; /* array of urls */
+	size_t len;   /* amount of items */
+	size_t cap;   /* available capacity */
+};
+
 struct items {
-	struct item *items;     /* array of items */
-	size_t len;             /* amount of items */
-	size_t cap;             /* available capacity */
+	struct item *items; /* array of items */
+	size_t len;         /* amount of items */
+	size_t cap;         /* available capacity */
 };
 
 void alldirty(void);
@@ -144,9 +150,9 @@ void pane_draw(struct pane *);
 void sighandler(int);
 void updategeom(void);
 void updatesidebar(void);
-void urls_free(void);
-int urls_isnew(const char *);
-void urls_read(void);
+void urls_free(struct urls *);
+int urls_hasmatch(struct urls *, const char *);
+void urls_read(struct urls *, const char *);
 
 static struct linebar linebar;
 static struct statusbar statusbar;
@@ -169,8 +175,8 @@ static struct feed *feeds;
 static struct feed *curfeed;
 static size_t nfeeds; /* amount of feeds */
 static time_t comparetime;
-static char *urlfile, **urls;
-static size_t nurls;
+struct urls urls;
+static char *urlfile;
 
 volatile sig_atomic_t state_sigchld = 0, state_sighup = 0, state_sigint = 0;
 volatile sig_atomic_t state_sigterm = 0, state_sigwinch = 0;
@@ -1197,9 +1203,9 @@ feed_items_get(struct feed *f, FILE *fp, struct items *itemsret)
 		if (n <= 0 || feof(fp))
 			break;
 	}
-	itemsret->cap = cap;
 	itemsret->items = items;
 	itemsret->len = nitems;
+	itemsret->cap = cap;
 	free(line);
 }
 
@@ -1218,7 +1224,7 @@ updatenewitems(struct feed *f)
 		row = &(p->rows[i]); /* do not use pane_row_get() */
 		item = row->data;
 		if (urlfile)
-			item->isnew = urls_isnew(item->matchnew);
+			item->isnew = !urls_hasmatch(&urls, item->matchnew);
 		else
 			item->isnew = (item->timeok && item->timestamp >= comparetime);
 		row->bold = item->isnew;
@@ -1264,7 +1270,7 @@ feed_count(struct feed *f, FILE *fp)
 		parseline(line, fields);
 
 		if (urlfile) {
-			f->totalnew += urls_isnew(fields[fields[FieldLink][0] ? FieldLink : FieldId]);
+			f->totalnew += !urls_hasmatch(&urls, fields[fields[FieldLink][0] ? FieldLink : FieldId]);
 		} else {
 			parsedtime = 0;
 			if (!strtotime(fields[FieldUnixTimestamp], &parsedtime))
@@ -1383,9 +1389,9 @@ feeds_reloadall(void)
 
 	pos = panes[PaneItems].pos; /* store numeric item position */
 	feeds_set(curfeed); /* close and reopen feed if possible */
-	urls_read();
+	urls_read(&urls, urlfile);
 	feeds_load(feeds, nfeeds);
-	urls_free();
+	urls_free(&urls);
 	/* restore numeric item position */
 	pane_setpos(&panes[PaneItems], pos);
 	updatesidebar();
@@ -1408,10 +1414,10 @@ feed_open_selected(struct pane *p)
 		return;
 	f = row->data;
 	feeds_set(f);
-	urls_read();
+	urls_read(&urls, urlfile);
 	if (f->fp)
 		feed_load(f, f->fp);
-	urls_free();
+	urls_free(&urls);
 	/* redraw row: counts could be changed */
 	updatesidebar();
 	updatetitle();
@@ -1901,32 +1907,34 @@ urls_cmp(const void *v1, const void *v2)
 	return strcmp(*((char **)v1), *((char **)v2));
 }
 
-int
-urls_isnew(const char *url)
+void
+urls_free(struct urls *urls)
 {
-	return (!nurls ||
-	       bsearch(&url, urls, nurls, sizeof(char *), urls_cmp) == NULL);
+	while (urls->len > 0) {
+		urls->len--;
+		free(urls->items[urls->len]);
+	}
+	urls->items = NULL;
+	urls->len = 0;
+	urls->cap = 0;
 }
 
-void
-urls_free(void)
+int
+urls_hasmatch(struct urls *urls, const char *url)
 {
-	while (nurls > 0)
-		free(urls[--nurls]);
-	free(urls);
-	urls = NULL;
-	nurls = 0;
+	return (urls->len &&
+	       bsearch(&url, urls->items, urls->len, sizeof(char *), urls_cmp));
 }
 
 void
-urls_read(void)
+urls_read(struct urls *urls, const char *urlfile)
 {
 	FILE *fp;
 	char *line = NULL;
-	size_t linesiz = 0, cap = 0;
+	size_t linesiz = 0;
 	ssize_t n;
 
-	urls_free();
+	urls_free(urls);
 
 	if (!urlfile)
 		return;
@@ -1936,19 +1944,19 @@ urls_read(void)
 	while ((n = getline(&line, &linesiz, fp)) > 0) {
 		if (line[n - 1] == '\n')
 			line[--n] = '\0';
-		if (nurls + 1 >= cap) {
-			cap = cap ? cap * 2 : 16;
-			urls = erealloc(urls, cap * sizeof(char *));
+		if (urls->len + 1 >= urls->cap) {
+			urls->cap = urls->cap ? urls->cap * 2 : 16;
+			urls->items = erealloc(urls->items, urls->cap * sizeof(char *));
 		}
-		urls[nurls++] = estrdup(line);
+		urls->items[urls->len++] = estrdup(line);
 	}
 	if (ferror(fp))
 		die("getline: %s", urlfile);
 	fclose(fp);
 	free(line);
 
-	if (nurls > 0)
-		qsort(urls, nurls, sizeof(char *), urls_cmp);
+	if (urls->len > 0)
+		qsort(urls->items, urls->len, sizeof(char *), urls_cmp);
 }
 
 int
@@ -2016,9 +2024,9 @@ main(int argc, char *argv[])
 		nfeeds = argc - 1;
 	}
 	feeds_set(&feeds[0]);
-	urls_read();
+	urls_read(&urls, urlfile);
 	feeds_load(feeds, nfeeds);
-	urls_free();
+	urls_free(&urls);
 
 	if (!isatty(0)) {
 		if ((fd = open("/dev/tty", O_RDONLY)) == -1)
-- 
cgit v1.2.3


From b6731ae35b125963a37dcaacaf76169992ad1f67 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Wed, 20 Jul 2022 20:37:14 +0200
Subject: slightly improve some comments

---
 sfeed.c        | 4 ++--
 sfeed_curses.c | 2 +-
 sfeed_gopher.c | 2 +-
 sfeed_mbox.c   | 4 ++--
 xml.h          | 2 +-
 5 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/sfeed.c b/sfeed.c
index 7b0ef6c..8a2843e 100644
--- a/sfeed.c
+++ b/sfeed.c
@@ -530,7 +530,7 @@ gettzoffset(const char *s)
 			;
 		if (i != 3)
 			return 0;
-		/* compare tz and adjust offset relative to UTC */
+		/* compare timezone and adjust offset relative to UTC */
 		for (i = 0; i < sizeof(tzones) / sizeof(*tzones); i++) {
 			if (!memcmp(s, tzones[i].name, 3))
 				return tzones[i].offhour;
@@ -978,7 +978,7 @@ xmltagend(XMLParser *p, const char *t, size_t tl, int isshort)
 	} else if (ctx.tag.id && istag(ctx.tag.name, ctx.tag.len, t, tl)) {
 		/* matched tag end: close it */
 		/* copy also to the link field if the attribute isPermaLink="true"
-		   and it is not set by a tag with higher prio. */
+		   and it is not set by a tag with higher priority. */
 		if (ctx.tag.id == RSSTagGuidPermalinkTrue && ctx.field &&
 		    ctx.tag.id > ctx.fields[FeedFieldLink].tagid) {
 			string_clear(&ctx.fields[FeedFieldLink].str);
diff --git a/sfeed_curses.c b/sfeed_curses.c
index 9f57ef9..b1a56d6 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -130,7 +130,7 @@ struct item {
 };
 
 struct urls {
-	char **items; /* array of urls */
+	char **items; /* array of URLs */
 	size_t len;   /* amount of items */
 	size_t cap;   /* available capacity */
 };
diff --git a/sfeed_gopher.c b/sfeed_gopher.c
index 7da41f6..b4ce7d9 100644
--- a/sfeed_gopher.c
+++ b/sfeed_gopher.c
@@ -65,7 +65,7 @@ printfeed(FILE *fpitems, FILE *fpin, struct feed *f)
 
 		if (fields[FieldLink][0]) {
 			itemtype = 'h';
-			/* if it's a gopher URL then change it into a direntry */
+			/* if it's a gopher URL then change it into a DirEntity */
 			if (!strncmp(fields[FieldLink], "gopher://", 9) &&
 			    uri_parse(fields[FieldLink], &u) != -1) {
 				itemhost = u.host;
diff --git a/sfeed_mbox.c b/sfeed_mbox.c
index 30ac875..0cfd836 100644
--- a/sfeed_mbox.c
+++ b/sfeed_mbox.c
@@ -22,8 +22,8 @@ djb2(unsigned char *s, unsigned long long hash)
 }
 
 /* Unescape / decode fields printed by string_print_encoded()
- * "\\" to "\", "\t", to TAB, "\n" to newline. Unrecognised escape sequences
- * are ignored: "\z" etc. Mangle "From " in mboxrd style (always prefix >). */
+ * "\\" to "\", "\t", to TAB, "\n" to newline. Other escape sequences are
+ * ignored: "\z" etc. Mangle "From " in mboxrd style (always prefix >). */
 static void
 printcontent(const char *s, FILE *fp)
 {
diff --git a/xml.h b/xml.h
index ad53396..cef4a05 100644
--- a/xml.h
+++ b/xml.h
@@ -30,7 +30,7 @@ typedef struct xmlparser {
 	/* current tag */
 	char tag[1024];
 	size_t taglen;
-	/* current tag is in short form ?  */
+	/* current tag is in shortform ?  */
 	int isshorttag;
 	/* current attribute name */
 	char name[1024];
-- 
cgit v1.2.3


From d69467dff3c00ec0a17a5856ccbbe124a8eb7d99 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Wed, 17 Aug 2022 01:34:10 +0200
Subject: sfeed_curses: fix a memleak caused by a mistake in refactoring the
 code

Introduced by a code cleanup/refactor in commit
59ba2ed29bcbe67de6e9ee1e7fb18744f7490c22
---
 sfeed_curses.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/sfeed_curses.c b/sfeed_curses.c
index b1a56d6..4d2b698 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -1914,6 +1914,7 @@ urls_free(struct urls *urls)
 		urls->len--;
 		free(urls->items[urls->len]);
 	}
+	free(urls->items);
 	urls->items = NULL;
 	urls->len = 0;
 	urls->cap = 0;
-- 
cgit v1.2.3


From 411f5e83c38da298175b69c572485a421f44fb28 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Wed, 17 Aug 2022 16:47:52 +0200
Subject: bump version to 1.6

---
 Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index 0772230..9958954 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
 .POSIX:
 
 NAME = sfeed
-VERSION = 1.5
+VERSION = 1.6
 
 # curses theme, see themes/ directory.
 SFEED_THEME = mono
-- 
cgit v1.2.3


From 60e402d0f2c086dbbbd21436bb1b3aa5ad9b77d6 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Fri, 26 Aug 2022 21:55:35 +0200
Subject: improve comment: uppercase cdata -> CDATA

---
 xml.c | 2 +-
 xml.h | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/xml.c b/xml.c
index 3bd9557..a82053e 100644
--- a/xml.c
+++ b/xml.c
@@ -292,7 +292,7 @@ xml_parse(XMLParser *x)
 			if ((c = GETNEXT()) == EOF)
 				return;
 
-			if (c == '!') { /* cdata and comments */
+			if (c == '!') { /* CDATA and comments */
 				for (tagdatalen = 0; (c = GETNEXT()) != EOF;) {
 					/* NOTE: sizeof(x->data) must be at least sizeof("[CDATA[") */
 					if (tagdatalen <= sizeof("[CDATA[") - 1)
diff --git a/xml.h b/xml.h
index cef4a05..ad7d26b 100644
--- a/xml.h
+++ b/xml.h
@@ -34,7 +34,7 @@ typedef struct xmlparser {
 	int isshorttag;
 	/* current attribute name */
 	char name[1024];
-	/* data buffer used for tag data, cdata and attribute data */
+	/* data buffer used for tag data, CDATA and attribute data */
 	char data[BUFSIZ];
 } XMLParser;
 
-- 
cgit v1.2.3


From f29d7e8eb2bd877eaa11b5e15c5f14f9ee7116b0 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Thu, 15 Dec 2022 19:15:48 +0100
Subject: sfeed_update.1: "return status" -> "exit status"

---
 sfeed_update.1 | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/sfeed_update.1 b/sfeed_update.1
index 49cf76a..5e01589 100644
--- a/sfeed_update.1
+++ b/sfeed_update.1
@@ -1,4 +1,4 @@
-.Dd May 14, 2022
+.Dd December 15, 2022
 .Dt SFEED_UPDATE 1
 .Os
 .Sh NAME
@@ -62,7 +62,7 @@ stdout in the format:
 .Ed
 .Sh EXIT STATUS
 .Ex -std
-If any of the feeds failed to update then the return status is non-zero.
+If any of the feeds failed to update then the exit status is non-zero.
 .Sh EXAMPLES
 To update your feeds and format them in various formats:
 .Bd -literal
-- 
cgit v1.2.3


From d77bd7c1e0b38b3320134fed0f496182bf1354e7 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Tue, 20 Dec 2022 22:22:39 +0100
Subject: sfeed_curses.1: small rewording and tweaks in the example

---
 sfeed_curses.1 | 16 +++++++++-------
 1 file changed, 9 insertions(+), 7 deletions(-)

diff --git a/sfeed_curses.1 b/sfeed_curses.1
index be68d92..94ffd4c 100644
--- a/sfeed_curses.1
+++ b/sfeed_curses.1
@@ -1,4 +1,4 @@
-.Dd May 5, 2022
+.Dd December 20, 2022
 .Dt SFEED_CURSES 1
 .Os
 .Sh NAME
@@ -312,24 +312,26 @@ sfeed_curses ~/.sfeed/feeds/*
 Which does the following:
 .Bl -enum
 .It
+Set commands to execute automatically:
+.Pp
 Set the current layout to a horizontal mode ('2' keybind').
 Showing a feeds sidebar on the top and the feed items on the bottom.
-.It
+.Pp
 Toggle showing only feeds with new items in the sidebar ('t' keybind).
-.It
+.Pp
 Go to the first row in the current panel ('g' keybind).
-.It
+.Pp
 Load the current selected feed ('o' keybind').
 .It
 Set a file to use for managing read and unread items.
-This file is a plain-text file containing a list of read URLs, one URL per
-line.
+This is a plain-text file containing a list of read URLs, one URL per line.
 .It
 Check if this file for managing the read and unread items exists.
 If it doesn't exist yet then create an empty file.
 .It
 Start
-.Nm .
+.Nm
+and read the specified feed files.
 .El
 .Sh SEE ALSO
 .Xr sfeed 1 ,
-- 
cgit v1.2.3


From b7965c20cddaf8a33a289ba12fe9865d49d72d95 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Tue, 20 Dec 2022 22:23:21 +0100
Subject: LICENSE: bump year

---
 LICENSE | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/LICENSE b/LICENSE
index ea3a3be..e0a41be 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 ISC License
 
-Copyright (c) 2011-2022 Hiltjo Posthuma 
+Copyright (c) 2011-2023 Hiltjo Posthuma 
 
 Permission to use, copy, modify, and/or distribute this software for any
 purpose with or without fee is hereby granted, provided that the above
-- 
cgit v1.2.3


From 7d4043084c2e933603243f259f4f9f080e1d8911 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Wed, 28 Dec 2022 11:18:44 +0100
Subject: README: small rewording "can be supported" -> "are supported"

---
 README | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README b/README
index b994c55..a4c2d25 100644
--- a/README
+++ b/README
@@ -146,8 +146,8 @@ sfeed supports a subset of XML 1.0 and a subset of:
 - MediaRSS extensions (media:).
 - Dublin Core extensions (dc:).
 
-Other formats like JSONfeed, twtxt or certain RSS/Atom extensions can be
-supported by converting them to RSS/Atom or to the sfeed(5) format directly.
+Other formats like JSONfeed, twtxt or certain RSS/Atom extensions are supported
+by converting them to RSS/Atom or to the sfeed(5) format directly.
 
 
 OS tested
-- 
cgit v1.2.3


From 9460075b7cb2b909f164f5fb99380f7a9ec7aa58 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sat, 7 Jan 2023 11:05:58 +0100
Subject: sfeed.5: remove a caveat in the file format man page

It is a caveat while parsing the data in the sfeed(1) program.
Keep it described in the sfeed(1) man page.
---
 sfeed.5 | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/sfeed.5 b/sfeed.5
index be55c18..91bce98 100644
--- a/sfeed.5
+++ b/sfeed.5
@@ -1,4 +1,4 @@
-.Dd May 2, 2022
+.Dd January 7, 2023
 .Dt SFEED 5
 .Os
 .Sh NAME
@@ -48,6 +48,3 @@ Item, categories, multiple values are separated by the '|' character.
 .Xr sfeed_plain 1
 .Sh AUTHORS
 .An Hiltjo Posthuma Aq Mt hiltjo@codemadness.org
-.Sh CAVEATS
-If a timezone for the timestamp field is not in the RFC822 or RFC3339 format it
-is not supported and the timezone is interpreted as UTC+0.
-- 
cgit v1.2.3


From 6a762a1d62df6d6fe8e2fbd1e28cf9ee8d9a8103 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sat, 7 Jan 2023 12:08:14 +0100
Subject: sfeed.1: reference sfeed_opml_import

It is used in the examples.

(Do not directly reference iconv to keep some kind of clear reference list)
---
 sfeed.1 | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/sfeed.1 b/sfeed.1
index c4a3c02..79d43be 100644
--- a/sfeed.1
+++ b/sfeed.1
@@ -1,4 +1,4 @@
-.Dd May 2, 2022
+.Dd January 7, 2023
 .Dt SFEED 1
 .Os
 .Sh NAME
@@ -103,6 +103,7 @@ There are also other formatting programs included.
 The README file has more examples.
 .Sh SEE ALSO
 .Xr sfeed_curses 1 ,
+.Xr sfeed_opml_import 1 ,
 .Xr sfeed_plain 1 ,
 .Xr sfeed_update 1 ,
 .Xr sfeed 5 ,
-- 
cgit v1.2.3


From e77a675e83ccbea65a50fe85dc9cb49fff5e0443 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Wed, 18 Jan 2023 19:53:58 +0100
Subject: sfeedrc.5: improve documentation a bit

- Fix a typo "Url" -> "URL".
- Be a bit more clear some overridable functions read from stdin.
- Some small rewording.
---
 sfeedrc.5 | 23 +++++++++++++----------
 1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/sfeedrc.5 b/sfeedrc.5
index 344ee42..91f82ce 100644
--- a/sfeedrc.5
+++ b/sfeedrc.5
@@ -1,4 +1,4 @@
-.Dd May 2, 2022
+.Dd January 18, 2023
 .Dt SFEEDRC 5
 .Os
 .Sh NAME
@@ -59,12 +59,12 @@ is a shellscript each function can be overridden to change its behaviour,
 notable functions are:
 .Bl -tag -width Ds
 .It Fn fetch "name" "url" "feedfile"
-Fetch feed from URL and writes data to stdout, its arguments are:
+Fetch feed from URL and write the data to stdout, its arguments are:
 .Bl -tag -width Ds
 .It Fa name
 Specified name in configuration file (useful for logging).
 .It Fa url
-Url to fetch.
+URL to fetch.
 .It Fa feedfile
 Used feedfile (useful for comparing modification times).
 .El
@@ -73,8 +73,9 @@ By default the tool
 .Xr curl 1
 is used.
 .It Fn convertencoding "name" "from" "to"
-Convert from text-encoding to another and writes it to stdout, its arguments
-are:
+Convert data from stdin from one text-encoding to another and write it to
+stdout,
+its arguments are:
 .Bl -tag -width Ds
 .It Fa name
 Feed name.
@@ -88,9 +89,9 @@ By default the tool
 .Xr iconv 1
 is used.
 .It Fn parse "name" "feedurl" "basesiteurl"
-Parse and convert RSS/Atom XML to the
+Read RSS/Atom XML data from stdin, convert and write it as
 .Xr sfeed 5
-TSV format.
+data to stdout.
 .Bl -tag -width Ds
 .It Fa name
 Name of the feed.
@@ -103,13 +104,15 @@ This argument allows to fix relative item links.
 .It Fn filter "name"
 Filter
 .Xr sfeed 5
-data from stdin, write to stdout, its arguments are:
+data from stdin and write it to stdout, its arguments are:
 .Bl -tag -width Ds
 .It Fa name
 Feed name.
 .El
 .It Fn merge "name" "oldfile" "newfile"
-Merge data of oldfile with newfile and writes it to stdout, its arguments are:
+Merge
+.Xr sfeed 5
+data of oldfile with newfile and write it to stdout, its arguments are:
 .Bl -tag -width Ds
 .It Fa name
 Feed name.
@@ -121,7 +124,7 @@ New file.
 .It Fn order "name"
 Sort
 .Xr sfeed 5
-data from stdin, write to stdout, its arguments are:
+data from stdin and write it to stdout, its arguments are:
 .Bl -tag -width Ds
 .It Fa name
 Feed name.
-- 
cgit v1.2.3


From f83f6cd4dc21664721602ceb449631957e941215 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Fri, 3 Feb 2023 16:12:30 +0100
Subject: sfeed_gopher: remove PATH_MAX and restricting the path length

This make it also cleanly compile without any other changes on GNU/Hurd.

Reference: https://www.gnu.org/software/hurd/hurd/porting/guidelines.html
Section: "PATH_MAX, MAX_PATH, MAXPATHLEN, _POSIX_PATH_MAX"

The fopen() functions will return NULL when the path is too long and set
errno = ENAMETOOLONG.

"The length of a pathname exceeds {PATH_MAX}, or pathname resolution of a
symbolic link produced an intermediate result with a length that exceeds
{PATH_MAX}."

Reference: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fopen.html
---
 sfeed_gopher.c | 15 +++++----------
 1 file changed, 5 insertions(+), 10 deletions(-)

diff --git a/sfeed_gopher.c b/sfeed_gopher.c
index b4ce7d9..290d2d2 100644
--- a/sfeed_gopher.c
+++ b/sfeed_gopher.c
@@ -1,6 +1,5 @@
 #include 
 
-#include 
 #include 
 #include 
 #include 
@@ -123,8 +122,8 @@ int
 main(int argc, char *argv[])
 {
 	FILE *fpitems, *fpindex, *fp;
-	char *name, *p, path[PATH_MAX + 1];
-	int i, r;
+	char *name, *p;
+	int i;
 
 	if (argc == 1) {
 		if (pledge("stdio", NULL) == -1)
@@ -168,15 +167,11 @@ main(int argc, char *argv[])
 
 			if (!(fp = fopen(argv[i], "r")))
 				err(1, "fopen: %s", argv[i]);
-
-			r = snprintf(path, sizeof(path), "%s", name);
-			if (r < 0 || (size_t)r >= sizeof(path))
-				errx(1, "path truncation: %s", path);
-			if (!(fpitems = fopen(path, "wb")))
+			if (!(fpitems = fopen(name, "wb")))
 				err(1, "fopen");
 			printfeed(fpitems, fp, &f);
 			checkfileerror(fp, argv[i], 'r');
-			checkfileerror(fpitems, path, 'w');
+			checkfileerror(fpitems, name, 'w');
 			fclose(fp);
 			fclose(fpitems);
 
@@ -185,7 +180,7 @@ main(int argc, char *argv[])
 			gophertext(fpindex, name);
 			fprintf(fpindex, " (%lu/%lu)\t", f.totalnew, f.total);
 			gophertext(fpindex, prefixpath);
-			gophertext(fpindex, path);
+			gophertext(fpindex, name);
 			fprintf(fpindex, "\t%s\t%s\r\n", host, port);
 		}
 		fputs(".\r\n", fpindex);
-- 
cgit v1.2.3


From dbb7f7b66d2d10a4bf14a404b66fa20cbf8a02ca Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Fri, 3 Feb 2023 16:15:35 +0100
Subject: fix comment for ASCII symbols for dumb terminals

This was a copy-pasta.
---
 sfeed_curses.c | 6 +++---
 util.h         | 4 ++--
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/sfeed_curses.c b/sfeed_curses.c
index 4d2b698..abfa95b 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -37,10 +37,10 @@
 #define LINEBAR_SYMBOL_BAR     "\xe2\x94\x80" /* symbol: "light horizontal" */
 #define LINEBAR_SYMBOL_RIGHT   "\xe2\x94\xa4" /* symbol: "light vertical and left" */
 #else
-#define SCROLLBAR_SYMBOL_BAR   "|" /* symbol: "light vertical" */
+#define SCROLLBAR_SYMBOL_BAR   "|"
 #define SCROLLBAR_SYMBOL_TICK  " "
-#define LINEBAR_SYMBOL_BAR     "-" /* symbol: "light horizontal" */
-#define LINEBAR_SYMBOL_RIGHT   "|" /* symbol: "light vertical and left" */
+#define LINEBAR_SYMBOL_BAR     "-"
+#define LINEBAR_SYMBOL_RIGHT   "|"
 #endif
 
 /* color-theme */
diff --git a/util.h b/util.h
index fac6424..9138de7 100644
--- a/util.h
+++ b/util.h
@@ -26,8 +26,8 @@ size_t strlcpy(char *, const char *, size_t);
 #define PAD_TRUNCATE_SYMBOL    "\xe2\x80\xa6" /* symbol: "ellipsis" */
 #define UTF_INVALID_SYMBOL     "\xef\xbf\xbd" /* symbol: "replacement" */
 #else
-#define PAD_TRUNCATE_SYMBOL    "." /* symbol: "ellipsis" */
-#define UTF_INVALID_SYMBOL     "?" /* symbol: "replacement" */
+#define PAD_TRUNCATE_SYMBOL    "."
+#define UTF_INVALID_SYMBOL     "?"
 #endif
 
 /* feed info */
-- 
cgit v1.2.3


From aeb1398411ce245fa7982365640f7852d63b3d52 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sat, 4 Feb 2023 12:34:56 +0100
Subject: README: describe how to add new parsed tags and fields to sfeed.c

---
 README | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 90 insertions(+)

diff --git a/README b/README
index a4c2d25..fba7565 100644
--- a/README
+++ b/README
@@ -1070,6 +1070,96 @@ file:
 
 - - -
 
+sfeed.c: adding new XML tags or sfeed(5) fields to the parser
+-------------------------------------------------------------
+
+sfeed.c contains definitions to parse XML tags and map them to sfeed(5) TSV
+fields. Parsed RSS and Atom tag names are first stored as a TagId, which is a
+number.  This TagId is then mapped to the output field index.
+
+* Add a new TagId enum for the tag.
+
+* (optional) Add a new FeedField* enum for the new output field or you can map
+  it to an existing field.
+
+* Add the new XML tag name to the array variable of parsed RSS or Atom
+  tags: rsstags[] or atomtags[].
+
+  These must be defined in alphabetical order, because a binary search is used
+  which uses the strcasecmp() function.
+
+* Add the parsed TagId to the output field in the array variable fieldmap[].
+
+  When another tag is also mapped to the same output field then the tag with
+  the highest TagId number value overrides the mapped field: the order is from
+  least important to high.
+
+* If this defined tag is just using the inner data of the XML tag, then this
+  definition is enough. If it for example has to parse a certain attribute you
+  have to add a check for the TagId to the xmlattr() callback function.
+
+* (optional) Print the new field in the printfields() function.
+
+Below is a patch example to add the MRSS "media:content" field as a new field:
+
+diff --git a/sfeed.c b/sfeed.c
+--- a/sfeed.c
++++ b/sfeed.c
+@@ -50,7 +50,7 @@ enum TagId {
+ 	RSSTagGuidPermalinkTrue,
+ 	/* must be defined after GUID, because it can be a link (isPermaLink) */
+ 	RSSTagLink,
+-	RSSTagEnclosure,
++	RSSTagMediaContent, RSSTagEnclosure,
+ 	RSSTagAuthor, RSSTagDccreator,
+ 	RSSTagCategory,
+ 	/* Atom */
+@@ -81,7 +81,7 @@ typedef struct field {
+ enum {
+ 	FeedFieldTime = 0, FeedFieldTitle, FeedFieldLink, FeedFieldContent,
+ 	FeedFieldId, FeedFieldAuthor, FeedFieldEnclosure, FeedFieldCategory,
+-	FeedFieldLast
++	FeedFieldMediaContent, FeedFieldLast
+ };
+ 
+ typedef struct feedcontext {
+@@ -137,6 +137,7 @@ static const FeedTag rsstags[] = {
+ 	{ STRP("enclosure"),         RSSTagEnclosure         },
+ 	{ STRP("guid"),              RSSTagGuid              },
+ 	{ STRP("link"),              RSSTagLink              },
++	{ STRP("media:content"),     RSSTagMediaContent      },
+ 	{ STRP("media:description"), RSSTagMediaDescription  },
+ 	{ STRP("pubdate"),           RSSTagPubdate           },
+ 	{ STRP("title"),             RSSTagTitle             }
+@@ -180,6 +181,7 @@ static const int fieldmap[TagLast] = {
+ 	[RSSTagGuidPermalinkFalse] = FeedFieldId,
+ 	[RSSTagGuidPermalinkTrue]  = FeedFieldId, /* special-case: both a link and an id */
+ 	[RSSTagLink]               = FeedFieldLink,
++	[RSSTagMediaContent]       = FeedFieldMediaContent,
+ 	[RSSTagEnclosure]          = FeedFieldEnclosure,
+ 	[RSSTagAuthor]             = FeedFieldAuthor,
+ 	[RSSTagDccreator]          = FeedFieldAuthor,
+@@ -677,6 +679,8 @@ printfields(void)
+ 	string_print_uri(&ctx.fields[FeedFieldEnclosure].str);
+ 	putchar(FieldSeparator);
+ 	string_print_trimmed_multi(&ctx.fields[FeedFieldCategory].str);
++	putchar(FieldSeparator);
++	string_print_trimmed(&ctx.fields[FeedFieldMediaContent].str);
+ 	putchar('\n');
+ 
+ 	if (ferror(stdout)) /* check for errors but do not flush */
+@@ -718,7 +722,7 @@ xmlattr(XMLParser *p, const char *t, size_t tl, const char *n, size_t nl,
+ 	}
+ 
+ 	if (ctx.feedtype == FeedTypeRSS) {
+-		if (ctx.tag.id == RSSTagEnclosure &&
++		if ((ctx.tag.id == RSSTagEnclosure || ctx.tag.id == RSSTagMediaContent) &&
+ 		    isattr(n, nl, STRP("url"))) {
+ 			string_append(&tmpstr, v, vl);
+ 		} else if (ctx.tag.id == RSSTagGuid &&
+
+- - -
+
 Running custom commands inside the sfeed_curses program
 -------------------------------------------------------
 
-- 
cgit v1.2.3


From 3ee28da0f47d6a3b59d1c8493374df4e01bc0af4 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Thu, 16 Feb 2023 20:29:22 +0100
Subject: README: small rewording in how to add new parsed tags and fields

---
 README | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/README b/README
index fba7565..26de06c 100644
--- a/README
+++ b/README
@@ -1077,6 +1077,8 @@ sfeed.c contains definitions to parse XML tags and map them to sfeed(5) TSV
 fields. Parsed RSS and Atom tag names are first stored as a TagId, which is a
 number.  This TagId is then mapped to the output field index.
 
+Steps to modify the code:
+
 * Add a new TagId enum for the tag.
 
 * (optional) Add a new FeedField* enum for the new output field or you can map
@@ -1100,7 +1102,7 @@ number.  This TagId is then mapped to the output field index.
 
 * (optional) Print the new field in the printfields() function.
 
-Below is a patch example to add the MRSS "media:content" field as a new field:
+Below is a patch example to add the MRSS "media:content" tag as a new field:
 
 diff --git a/sfeed.c b/sfeed.c
 --- a/sfeed.c
-- 
cgit v1.2.3


From f95834446f8d722cc09b4ed8eab642d1cc01c505 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Thu, 16 Feb 2023 20:32:23 +0100
Subject: sfeed_curses: add SCO keys for next, prior (CSI I and CSI G)

This fixes the page up and page down keys in the cons25 console on
DragonFlyBSD.

See also the table:
https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
Section "Non-Function Keys".
---
 sfeed_curses.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/sfeed_curses.c b/sfeed_curses.c
index abfa95b..841f276 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -2119,12 +2119,15 @@ main(int argc, char *argv[])
 
 				mousereport(button, release, keymask, x - 1, y - 1);
 				break;
+			/* DEC/SUN: ESC O char, HP: ESC char or SCO: ESC [ char */
 			case 'A': goto keyup;    /* arrow up */
 			case 'B': goto keydown;  /* arrow down */
 			case 'C': goto keyright; /* arrow right */
 			case 'D': goto keyleft;  /* arrow left */
 			case 'F': goto endpos;   /* end */
+			case 'G': goto nextpage; /* page down */
 			case 'H': goto startpos; /* home */
+			case 'I': goto prevpage; /* page up */
 			default:
 				if (!(ch >= '0' && ch <= '9'))
 					break;
-- 
cgit v1.2.3


From eb635966d33e2718094aab9d6ee06b5655b8efeb Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Thu, 16 Feb 2023 20:33:51 +0100
Subject: sfeed_curses: add SUN keys support

This fixes the keys on the sun-color console on OpenIndiana/Illumos/OpenSolaris
(and other systems using these keys).

See also the table:
https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
Section "Non-Function Keys".
---
 sfeed_curses.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/sfeed_curses.c b/sfeed_curses.c
index 841f276..50080cf 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -2146,6 +2146,13 @@ main(int argc, char *argv[])
 						case 7: goto startpos; /* home: urxvt */
 						case 8: goto endpos;   /* end: urxvt */
 						}
+					} else if (ch == 'z') { /* SUN: ESC [ num z */
+						switch (i) {
+						case 214: goto startpos; /* home */
+						case 216: goto prevpage; /* page up */
+						case 220: goto endpos;   /* end */
+						case 222: goto nextpage; /* page down */
+						}
 					}
 					break;
 				}
-- 
cgit v1.2.3


From 9d6dd2a5072c0702cb56cced861ca4539e98c553 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Fri, 17 Feb 2023 15:39:29 +0100
Subject: sfeed_update, sfeed_opml_export, README: reference the example
 sfeedrc man page

... and some small rewording.
---
 README            | 4 ++--
 sfeed_opml_export | 2 +-
 sfeed_update      | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/README b/README
index 26de06c..dcff323 100644
--- a/README
+++ b/README
@@ -251,8 +251,8 @@ output example:
 
 - - -
 
-Make sure your sfeedrc config file exists, see sfeedrc.example. To update your
-feeds (configfile argument is optional):
+Make sure your sfeedrc config file exists, see the sfeedrc.example file. To
+update your feeds (configfile argument is optional):
 
 	sfeed_update "configfile"
 
diff --git a/sfeed_opml_export b/sfeed_opml_export
index 2a9396a..3835934 100755
--- a/sfeed_opml_export
+++ b/sfeed_opml_export
@@ -19,7 +19,7 @@ loadconfig() {
 		. "${path}"
 	else
 		printf "Configuration file \"%s\" cannot be read.\n" "${config}" >&2
-		echo "See sfeedrc.example for an example." >&2
+		echo "See the sfeedrc.example file or the sfeedrc(5) man page for an example." >&2
 		exit 1
 	fi
 }
diff --git a/sfeed_update b/sfeed_update
index 44ce23c..f989be6 100755
--- a/sfeed_update
+++ b/sfeed_update
@@ -28,7 +28,7 @@ loadconfig() {
 		. "${path}"
 	else
 		printf "Configuration file \"%s\" cannot be read.\n" "${config}" >&2
-		echo "See sfeedrc.example for an example." >&2
+		echo "See the sfeedrc.example file or the sfeedrc(5) man page for an example." >&2
 		exit 1
 	fi
 }
-- 
cgit v1.2.3


From 19a4c010f95ad17e02ca59e9949524e801ad22f2 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sat, 25 Feb 2023 14:40:11 +0100
Subject: bump version to 1.7

---
 Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index 9958954..d24de84 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
 .POSIX:
 
 NAME = sfeed
-VERSION = 1.6
+VERSION = 1.7
 
 # curses theme, see themes/ directory.
 SFEED_THEME = mono
-- 
cgit v1.2.3


From 426fa33dd276fbe662b3794c41db1ab6d59b3c2a Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Tue, 7 Mar 2023 21:04:36 +0100
Subject: sfeed_curses: fix (very hard to trigger) memleak when getline()
 returns EOF for lazyloaded items

Fix the code pattern of freeing the line when getline returns -1 but no error
flag is set on the stream (such as EOF).

Note that on errors (even ENOMEM: out-of-memory) an error flag is set on the
stream and the process would exit and clean up all it's resources.

This would be very hard to trigger. The following conditions would have to be
true:

* Lazyloading of items is enabled: SFEED_LAZYLOAD=1 is set.
* Items of the feed are read and their offsets stored.
* The line is read/lazy-loaded again by it's offset but returns EOF (not a read
  error) this time.
  This could maybe happen if the feed file was changed and made smaller while
  sfeed_curses is running and the remembered offset is now beyond the file.

  Note that the sfeed_curses(1) man page describes a workaround for a similar
  condition by sending SIGHUP if the sfeed(5) data was changed to reload the feed
  file.

References:
* https://man.openbsd.org/getline
  "It is the responsibility of the caller to free(3) *lineptr when it is no
  longer needed. Even when it fails, getdelim() may update *lineptr."
* https://pubs.opengroup.org/onlinepubs/9699919799/functions/getline.html
---
 sfeed_curses.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/sfeed_curses.c b/sfeed_curses.c
index 50080cf..1abb046 100644
--- a/sfeed_curses.c
+++ b/sfeed_curses.c
@@ -1791,6 +1791,7 @@ item_row_get(struct pane *p, off_t pos)
 		if ((linelen = getline(&line, &linesize, f->fp)) <= 0) {
 			if (ferror(f->fp))
 				die("getline: %s", f->path);
+			free(line);
 			return NULL;
 		}
 
-- 
cgit v1.2.3


From daf56f2cb0a6994c4be8f1b57f1eb4b4cb8c91ba Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Tue, 14 Mar 2023 18:30:47 +0100
Subject: Makefile: remove duplicate CPPFLAGS for sfeed_curses

---
 Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index d24de84..c167614 100644
--- a/Makefile
+++ b/Makefile
@@ -26,7 +26,7 @@ SFEED_CPPFLAGS = -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=700 -D_BSD_SOURCE
 SFEED_CURSES = sfeed_curses
 SFEED_CURSES_CFLAGS = ${CFLAGS}
 SFEED_CURSES_CPPFLAGS = -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=700 -D_BSD_SOURCE \
-	 -DSFEED_THEME=\"themes/${SFEED_THEME}.h\" ${SFEED_CPPFLAGS}
+	 -DSFEED_THEME=\"themes/${SFEED_THEME}.h\"
 SFEED_CURSES_LDFLAGS = ${LDFLAGS} -lcurses
 
 # Linux: some distros use ncurses and require -lncurses.
-- 
cgit v1.2.3


From d2f63645bf165726384f50f4e8b0f3ee048e7ae3 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sun, 26 Mar 2023 15:26:08 +0200
Subject: sfeed.c: datetounix: fix incorrect int type to long

Found while testing sfeed on MS-DOS with Open Watcom (for fun :)).

There an int is 16-bit and sfeed incorrectly wrapped the value, which produced
incorrect parsed UNIX timestamps as output.
---
 sfeed.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sfeed.c b/sfeed.c
index 8a2843e..2956505 100644
--- a/sfeed.c
+++ b/sfeed.c
@@ -436,7 +436,7 @@ string_print_timestamp(String *s)
 static long long
 datetounix(long long year, int mon, int day, int hour, int min, int sec)
 {
-	static const int secs_through_month[] = {
+	static const long secs_through_month[] = {
 		0, 31 * 86400, 59 * 86400, 90 * 86400,
 		120 * 86400, 151 * 86400, 181 * 86400, 212 * 86400,
 		243 * 86400, 273 * 86400, 304 * 86400, 334 * 86400 };
-- 
cgit v1.2.3


From 04db56ad05b207374c973b8651a6bfa14bbbac4b Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sun, 26 Mar 2023 15:30:18 +0200
Subject: README: add Open Watcom as a tested compiler

Tested sfeed, sfeed_plain and sfeed_html for now.

There are minor changes required for sfeed and some additional compatibility
function changes required to make the format tools compile.

Rough list of changes required:
- xml.c: rename getchar_unlocked -> getchar
- sfeed.c: EOVERFLOW -> ENOMEM
- wcwidth: just filter ASCII range < 32 = -1 and > 127 = 0, rest = 1.
- getline
- localtime_r -> just do a localtime + memcpy.

Make sure to increase stack size when compiling:

	owcc -k32768 -Os -s -o sfeed.exe sfeed.c util.c xml.c

Resulting .exe binary is 27KB, 17KB compressed with UPX.

The HTML output looks fine in Netscape Navigator and Internet Explorer 3 :)
---
 README | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README b/README
index dcff323..35ecd75 100644
--- a/README
+++ b/README
@@ -165,7 +165,7 @@ OS tested
 - Windows (cygwin gcc + mintty, mingw).
 - HaikuOS
 - SerenityOS
-- FreeDOS (djgpp).
+- FreeDOS (djgpp, Open Watcom).
 - FUZIX (sdcc -mz80, with the sfeed parser program).
 
 
-- 
cgit v1.2.3


From c7739b9c04b4b49888e1161164b1cdac0d9930ca Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sun, 26 Mar 2023 18:03:24 +0200
Subject: sfeed.c: datetounix: simplify calculation and make it slightly easier
 to read

This also fixes a calculation (possibly a compiler bug) with Open Watcom 1.9.
---
 sfeed.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sfeed.c b/sfeed.c
index 2956505..4337300 100644
--- a/sfeed.c
+++ b/sfeed.c
@@ -475,7 +475,7 @@ datetounix(long long year, int mon, int day, int hour, int min, int sec)
 			}
 		}
 		leaps += 97 * cycles + 24 * centuries - is_leap;
-		t = (year - 100) * 31536000LL + leaps * 86400LL + 946684800 + 86400;
+		t = ((year - 100) * 31536000LL) + (leaps * 86400LL) + 946771200LL;
 	}
 	t += secs_through_month[mon];
 	if (is_leap && mon >= 2)
-- 
cgit v1.2.3


From 92dfc055ce7a24bc8120c7eece3caca0ae84b653 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Sun, 26 Mar 2023 18:40:31 +0200
Subject: sfeed.c: parsetime improve parsing RFC2822 obsolete short year

https://datatracker.ietf.org/doc/html/rfc2822#section-4.3
"Where a two or three digit year occurs in a date, the year is to be
interpreted as follows: If a two digit year is encountered whose
value is between 00 and 49, the year is interpreted by adding 2000,
ending up with a value between 2000 and 2049.  If a two digit year is
encountered with a value between 50 and 99, or any three digit year
is encountered, the year is interpreted by adding 1900."

Improvement on commit 7086670e4335714e1df982bf1058082b7400b653

For example (output from TZ=UTC sfeed_plain):

input: Sun, 26 Jul 049 19:26:34
was: 2049-07-26 19:26
now: 1949-07-26 19:26 (because this is a 3-digit year)

input: Sun, 26 Jul 1 19:26:34
was: 2001-07-26 19:26
now: 0001-07-26 19:26 (because this is a 1-digit year and doesn't match the short year rule)

input: Sun, 26 Jul 001 19:26:34
was: 2001-07-26 19:26
now: 1901-07-26 19:26 (because this is a 3 digit year)

These cases are all added to the tests in the sfeed_tests repo (times.xml file).
---
 sfeed.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/sfeed.c b/sfeed.c
index 4337300..ab779b1 100644
--- a/sfeed.c
+++ b/sfeed.c
@@ -613,8 +613,8 @@ parsetime(const char *s, long long *tp)
 		for (v = 0, i = 0; i < 4 && ISDIGIT((unsigned char)*s); s++, i++)
 			v = (v * 10) + (*s - '0');
 		/* obsolete short year: RFC2822 4.3 */
-		if (i <= 3)
-			v += (v >= 0 && v <= 49) ? 2000 : 1900;
+		if (i == 2 || i == 3)
+			v += (i == 2 && v >= 0 && v <= 49) ? 2000 : 1900;
 		va[0] = v; /* year */
 		for (; ISSPACE((unsigned char)*s); s++)
 			;
-- 
cgit v1.2.3


From 5a27c58675ddf4113d64a84f715cb3fecb681a6d Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Wed, 5 Apr 2023 21:41:19 +0200
Subject: sfeed_update: fail early if creating a temporary directory or status
 file fails

If creating a temporary directory for the feed files failed then $sfeedtmpdir
would be empty and it would try to:

	mkdir -p "/feed"
	touch "/feed/ok"

After failing it would also still try to process all the feeds.

Now just fail early.

mktemp or touch themselve will print the actual error to stderr.
---
 sfeed_update | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/sfeed_update b/sfeed_update
index f989be6..3da45d3 100755
--- a/sfeed_update
+++ b/sfeed_update
@@ -203,9 +203,9 @@ main() {
 	# load config file.
 	loadconfig "$1"
 	# fetch feeds and store in temporary directory.
-	sfeedtmpdir="$(mktemp -d '/tmp/sfeed_XXXXXX')"
+	sfeedtmpdir="$(mktemp -d '/tmp/sfeed_XXXXXX')" || exit 1
 	mkdir -p "${sfeedtmpdir}/feeds"
-	touch "${sfeedtmpdir}/ok"
+	touch "${sfeedtmpdir}/ok" || exit 1
 	# make sure path exists.
 	mkdir -p "${sfeedpath}"
 	# fetch feeds specified in config file.
-- 
cgit v1.2.3


From 15d18283616a951f060daf8f96996def532b8e21 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Thu, 6 Apr 2023 19:35:24 +0200
Subject: README: fix typo JSONfeed -> JSON Feed

https://www.jsonfeed.org/version/1.1/
---
 README | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README b/README
index 35ecd75..6611793 100644
--- a/README
+++ b/README
@@ -146,8 +146,8 @@ sfeed supports a subset of XML 1.0 and a subset of:
 - MediaRSS extensions (media:).
 - Dublin Core extensions (dc:).
 
-Other formats like JSONfeed, twtxt or certain RSS/Atom extensions are supported
-by converting them to RSS/Atom or to the sfeed(5) format directly.
+Other formats like JSON Feed, twtxt or certain RSS/Atom extensions are
+supported by converting them to RSS/Atom or to the sfeed(5) format directly.
 
 
 OS tested
-- 
cgit v1.2.3


From f11a83207e85e9007cf908a50318d50ef1e7bab9 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Thu, 6 Apr 2023 20:22:00 +0200
Subject: README: improve error checking for temporary files in examples

Similar to the recent fix for sfeed_update in commit
5a27c58675ddf4113d64a84f715cb3fecb681a6d
---
 README | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/README b/README
index 6611793..3cb650d 100644
--- a/README
+++ b/README
@@ -742,9 +742,9 @@ sfeed_update_xargs shellscript:
 	}
 	
 	# fetch feeds and store in temporary directory.
-	sfeedtmpdir="$(mktemp -d '/tmp/sfeed_XXXXXX')"
+	sfeedtmpdir="$(mktemp -d '/tmp/sfeed_XXXXXX')" || exit 1
 	mkdir -p "${sfeedtmpdir}/feeds"
-	touch "${sfeedtmpdir}/ok"
+	touch "${sfeedtmpdir}/ok" || exit 1
 	# make sure path exists.
 	mkdir -p "${sfeedpath}"
 	# print feeds for parallel processing with xargs.
@@ -841,7 +841,7 @@ arguments are specified then the data is read from stdin.
 	
 	# ...else parent mode:
 	
-	tmp=$(mktemp)
+	tmp="$(mktemp)" || exit 1
 	trap "rm -f ${tmp}" EXIT
 	
 	[ -f "${cachefile}" ] || touch "${cachefile}"
@@ -1188,7 +1188,7 @@ Example of a `markallread.sh` shellscript to mark all URLs as read:
 
 	#!/bin/sh
 	# mark all items/URLs as read.
-	tmp=$(mktemp)
+	tmp="$(mktemp)" || exit 1
 	(cat ~/.sfeed/urls; cut -f 3 ~/.sfeed/feeds/*) | \
 	awk '!x[$0]++' > "$tmp" &&
 	mv "$tmp" ~/.sfeed/urls &&
-- 
cgit v1.2.3


From 0c938df27bf335fb290d6ea3612f5b397ed3da56 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Thu, 6 Apr 2023 20:22:54 +0200
Subject: README: sfeed_download: change youtube-dl to yt-dlp

This is an active maintained fork.
---
 README | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README b/README
index 3cb650d..e633a80 100644
--- a/README
+++ b/README
@@ -771,7 +771,7 @@ arguments are specified then the data is read from stdin.
 
 	#!/bin/sh
 	# sfeed_download: downloader for URLs and enclosures in sfeed(5) files.
-	# Dependencies: awk, curl, flock, xargs (-P), youtube-dl.
+	# Dependencies: awk, curl, flock, xargs (-P), yt-dlp.
 	
 	cachefile="${SFEED_CACHEFILE:-$HOME/.sfeed/downloaded_urls}"
 	jobs="${SFEED_JOBS:-4}"
@@ -791,7 +791,7 @@ arguments are specified then the data is read from stdin.
 	fetch() {
 		case "$1" in
 		*youtube.com*)
-			youtube-dl "$1";;
+			yt-dlp "$1";;
 		*.flac|*.ogg|*.m3u|*.m3u8|*.m4a|*.mkv|*.mp3|*.mp4|*.wav|*.webm)
 			# allow 2 redirects, hide User-Agent, connect timeout is 15 seconds.
 			curl -O -L --max-redirs 2 -H "User-Agent:" -f -s --connect-timeout 15 "$1";;
-- 
cgit v1.2.3


From a15c98f32d8a768fee7b6783f7cea710cc5878a9 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Fri, 7 Apr 2023 18:43:06 +0200
Subject: sfeed.c: datetounix: add comments

---
 sfeed.c | 17 ++++++++++++++---
 1 file changed, 14 insertions(+), 3 deletions(-)

diff --git a/sfeed.c b/sfeed.c
index ab779b1..ae6a955 100644
--- a/sfeed.c
+++ b/sfeed.c
@@ -432,10 +432,13 @@ string_print_timestamp(String *s)
 		printf("%lld", t);
 }
 
-/* Convert time fields. Returns a UNIX timestamp. */
+/* Convert time fields. Returns a signed (atleast) 64-bit UNIX timestamp.
+   Parameters should be passed as they are in a struct tm:
+   that is: year = year - 1900, month = month - 1. */
 static long long
 datetounix(long long year, int mon, int day, int hour, int min, int sec)
 {
+	/* seconds in a month in a regular (non-leap) year */
 	static const long secs_through_month[] = {
 		0, 31 * 86400, 59 * 86400, 90 * 86400,
 		120 * 86400, 151 * 86400, 181 * 86400, 212 * 86400,
@@ -443,7 +446,9 @@ datetounix(long long year, int mon, int day, int hour, int min, int sec)
 	int is_leap = 0, cycles, centuries = 0, leaps = 0, rem;
 	long long t;
 
+	/* optimization: handle common range year 1902 up to and including 2038 */
 	if (year - 2ULL <= 136) {
+		/* amount of leap days relative to 1970: every 4 years */
 		leaps = (year - 68) >> 2;
 		if (!((year - 68) & 3)) {
 			leaps--;
@@ -451,8 +456,11 @@ datetounix(long long year, int mon, int day, int hour, int min, int sec)
 		} else {
 			is_leap = 0;
 		}
-		t = 31536000 * (year - 70) + 86400 * leaps;
+		t = 31536000 * (year - 70) + (86400 * leaps); /* 365 * 86400 = 31536000 */
 	} else {
+		/* general leap year calculation:
+		   leap years occur mostly every 4 years but every 100 years
+		   a leap year is skipped unless the year is divisible by 400 */
 		cycles = (year - 100) / 400;
 		rem = (year - 100) % 400;
 		if (rem < 0) {
@@ -474,7 +482,10 @@ datetounix(long long year, int mon, int day, int hour, int min, int sec)
 				is_leap = !rem;
 			}
 		}
-		leaps += 97 * cycles + 24 * centuries - is_leap;
+		leaps += (97 * cycles) + (24 * centuries) - is_leap;
+
+		/* adjust 8 leap days from 1970 up to and including 2000:
+		   ((30 * 365) + 8) * 86400 = 946771200 */
 		t = ((year - 100) * 31536000LL) + (leaps * 86400LL) + 946771200LL;
 	}
 	t += secs_through_month[mon];
-- 
cgit v1.2.3


From 14cce37fa54608362dc2d19ff66d10b64e5777b1 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma 
Date: Mon, 10 Apr 2023 16:40:56 +0200
Subject: sfeed_atom: in Atom the default for a type is text

Save a few bytes in the output by removing it.

https://www.rfc-editor.org/rfc/rfc4287
3.1.1.  The "type" Attribute

The title is an atomTextConstruct.
---
 sfeed_atom.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/sfeed_atom.c b/sfeed_atom.c
index 3ce5cf0..cfaeb5b 100644
--- a/sfeed_atom.c
+++ b/sfeed_atom.c
@@ -95,7 +95,7 @@ printfeed(FILE *fp, const char *feedname)
 				/* NOTE: an RSS/Atom viewer may or may not format
 				   whitespace such as newlines.
 				   Workaround: type="html" and 
]]> */ - fputs("\t", stdout); + fputs("\t", stdout); } printcontent(fields[FieldContent]); fputs("\n", stdout); @@ -132,7 +132,7 @@ main(int argc, char *argv[]) fputs("\n" "\n" - "\tNewsfeed\n" + "\tNewsfeed\n" "\tsfeed\n", stdout); printf("\turn:newsfeed:%lld\n" "\t%04d-%02d-%02dT%02d:%02d:%02dZ\n", -- cgit v1.2.3 From bf378f4350b0e92efcefd9c0c34f7b5c57c098f4 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Mon, 10 Apr 2023 16:44:13 +0200 Subject: remove sfeed "branding" in outputs Technically for sfeed_atom this violates the RFC standard. There must be an author in the feed or for an item. However sfeed is not the author, it is a generator. All readers will read these feeds anyway. https://www.rfc-editor.org/rfc/rfc4287#section-4.1.1 "atom:feed elements MUST contain one or more atom:author elements, unless all of the atom:feed element's child atom:entry elements contain at least one atom:author element." *shrug* --- sfeed_atom.c | 3 +-- sfeed_mbox.c | 2 +- sfeed_opml_export | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/sfeed_atom.c b/sfeed_atom.c index cfaeb5b..aeea2de 100644 --- a/sfeed_atom.c +++ b/sfeed_atom.c @@ -132,8 +132,7 @@ main(int argc, char *argv[]) fputs("\n" "\n" - "\tNewsfeed\n" - "\tsfeed\n", stdout); + "\tNewsfeed\n", stdout); printf("\turn:newsfeed:%lld\n" "\t%04d-%02d-%02dT%02d:%02d:%02dZ\n", (long long)now, diff --git a/sfeed_mbox.c b/sfeed_mbox.c index 0cfd836..b5e7e3d 100644 --- a/sfeed_mbox.c +++ b/sfeed_mbox.c @@ -82,7 +82,7 @@ printfeed(FILE *fp, const char *feedname) printf("Date: %s\n", dtimebuf); /* invalid/missing: use current time */ } - printf("From: %s \n", fields[FieldAuthor][0] ? fields[FieldAuthor] : feedname); + printf("From: %s \n", fields[FieldAuthor][0] ? fields[FieldAuthor] : feedname); printf("To: %s <%s@%s>\n", user, user, host); printf("Subject: %s\n", fields[FieldTitle]); printf("Message-ID: <%s%s%llu@%s>\n", diff --git a/sfeed_opml_export b/sfeed_opml_export index 3835934..7c96d5d 100755 --- a/sfeed_opml_export +++ b/sfeed_opml_export @@ -38,7 +38,7 @@ cat < - OPML export from sfeed + OPML export ! -- cgit v1.2.3 From 3f1d323497e5aefce65a3758d1bc408130d9bbe0 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Mon, 10 Apr 2023 16:49:39 +0200 Subject: sfeed.c: slightly reduce stack size for entities A numeric entity could be 5 bytes, so use a round number of 8 bytes. No other change intended and no performance difference noticed. --- sfeed.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sfeed.c b/sfeed.c index ae6a955..e96ffa3 100644 --- a/sfeed.c +++ b/sfeed.c @@ -754,7 +754,7 @@ static void xmlattrentity(XMLParser *p, const char *t, size_t tl, const char *n, size_t nl, const char *data, size_t datalen) { - char buf[16]; + char buf[8]; int len; /* handles transforming inline XML to data */ @@ -831,7 +831,7 @@ xmldata(XMLParser *p, const char *s, size_t len) static void xmldataentity(XMLParser *p, const char *data, size_t datalen) { - char buf[16]; + char buf[8]; int len; if (!ctx.field) -- cgit v1.2.3 From ced0dd7f8a01dedad3ba16c4cf209aea673c3e82 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Mon, 10 Apr 2023 17:03:57 +0200 Subject: sfeed_json: add JSON output format tool This outputs the TSV data to JSON. It uses a subset of JSON Feed 1.1: https://www.jsonfeed.org/version/1.1/ --- Makefile | 1 + README | 1 + sfeed_json.1 | 48 +++++++++++++++++ sfeed_json.c | 171 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 221 insertions(+) create mode 100644 sfeed_json.1 create mode 100644 sfeed_json.c diff --git a/Makefile b/Makefile index c167614..a8493ba 100644 --- a/Makefile +++ b/Makefile @@ -51,6 +51,7 @@ BIN = \ sfeed_frames\ sfeed_gopher\ sfeed_html\ + sfeed_json\ sfeed_mbox\ sfeed_opml_import\ sfeed_plain\ diff --git a/README b/README index e633a80..a0a4815 100644 --- a/README +++ b/README @@ -186,6 +186,7 @@ sfeed_curses - Format feed data (TSV) to a curses interface. sfeed_frames - Format feed data (TSV) to HTML file(s) with frames. sfeed_gopher - Format feed data (TSV) to Gopher files. sfeed_html - Format feed data (TSV) to HTML. +sfeed_json - Format feed data (TSV) to JSON Feed. sfeed_opml_export - Generate an OPML XML file from a sfeedrc config file. sfeed_opml_import - Generate a sfeedrc config file from an OPML XML file. sfeed_markread - Mark items as read/unread, for use with sfeed_curses. diff --git a/sfeed_json.1 b/sfeed_json.1 new file mode 100644 index 0000000..8bd6ab0 --- /dev/null +++ b/sfeed_json.1 @@ -0,0 +1,48 @@ +.Dd March 8, 2023 +.Dt SFEED_JSON 1 +.Os +.Sh NAME +.Nm sfeed_json +.Nd format feed data to JSON Feed +.Sh SYNOPSIS +.Nm +.Op Ar +.Sh DESCRIPTION +.Nm +formats feed data (TSV) from +.Xr sfeed 1 +from stdin or for each +.Ar file +to stdout as JSON Feed data. +If one or more +.Ar file +arguments are specified then the basename of the +.Ar file +is used as the feed name in the output. +If no +.Ar file +arguments are specified and so the data is read from stdin then the feed name +is empty. +If +.Nm +is reading from one or more +.Ar file +arguments it will prefix the entry title with "[feed name] ". +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +.Bd -literal +curl -s 'https://codemadness.org/atom.xml' | sfeed | sfeed_json +.Ed +.Sh SEE ALSO +.Xr sfeed 1 , +.Xr sfeed_atom 1 , +.Xr sfeed 5 +.Sh STANDARDS +.Rs +.%T JSON Feed Version 1.1 +.%U https://www.jsonfeed.org/version/1.1/ +.%D Nov, 2022 +.Re +.Sh AUTHORS +.An Hiltjo Posthuma Aq Mt hiltjo@codemadness.org diff --git a/sfeed_json.c b/sfeed_json.c new file mode 100644 index 0000000..f6bb904 --- /dev/null +++ b/sfeed_json.c @@ -0,0 +1,171 @@ +#include +#include +#include +#include + +#include "util.h" + +static char *line; +static size_t linesize; +static int firstitem = 1; + +/* Unescape / decode fields printed by string_print_encoded() */ +static void +printcontent(const char *s) +{ + for (; *s; s++) { + switch (*s) { + case '\\': + s++; + switch (*s) { + case 'n': fputs("\\n", stdout); break; + case '\\': fputs("\\\\", stdout); break; + case 't': fputs("\\t", stdout); break; + } + break; /* ignore invalid escape sequence */ + case '"': fputs("\\\"", stdout); break; + default: + putchar(*s); + break; + } + } +} + +static void +printfield(const char *s) +{ + for (; *s; s++) { + if (*s == '\\') + fputs("\\\\", stdout); + else if (*s == '"') + fputs("\\\"", stdout); + else + putchar(*s); + } +} + +static void +printfeed(FILE *fp, const char *feedname) +{ + char *fields[FieldLast], timebuf[32]; + struct tm parsedtm, *tm; + time_t parsedtime; + ssize_t linelen; + int ch; + char *p, *s; + + while ((linelen = getline(&line, &linesize, fp)) > 0 && + !ferror(stdout)) { + if (line[linelen - 1] == '\n') + line[--linelen] = '\0'; + parseline(line, fields); + + if (!firstitem) + fputs(",\n", stdout); + firstitem = 0; + + fputs("{\n\t\"id\": \"", stdout); + printfield(fields[FieldId]); + fputs("\"", stdout); + + parsedtime = 0; + if (!strtotime(fields[FieldUnixTimestamp], &parsedtime) && + (tm = gmtime_r(&parsedtime, &parsedtm)) && + strftime(timebuf, sizeof(timebuf), "%Y-%m-%dT%H:%M:%SZ", tm)) { + fputs(",\n\t\"date_published\": \"", stdout); + fputs(timebuf, stdout); + fputs("\"", stdout); + } + + fputs(",\n\t\"title\": \"", stdout); + if (feedname[0]) { + fputs("[", stdout); + printfield(feedname); + fputs("] ", stdout); + } + printfield(fields[FieldTitle]); + fputs("\"", stdout); + + if (fields[FieldLink][0]) { + fputs(",\n\t\"url\": \"", stdout); + printfield(fields[FieldLink]); + fputs("\"", stdout); + } + + if (fields[FieldAuthor][0]) { + fputs(",\n\t\"authors\": [{\"name\": \"", stdout); + printfield(fields[FieldAuthor]); + fputs("\"}]", stdout); + } + + if (fields[FieldCategory][0]) { + fputs(",\n\t\"tags\": [", stdout); + + for (p = s = fields[FieldCategory]; ; s++) { + if (*s == '|' || *s == '\0') { + if (p != fields[FieldCategory]) + fputs(", ", stdout); + ch = *s; + *s = '\0'; /* temporary NUL terminate */ + fputs("\"", stdout); + printfield(p); + fputs("\"", stdout); + *s = ch; /* restore */ + p = s + 1; + } + if (*s == '\0') + break; + } + fputs("]", stdout); + } + + if (fields[FieldEnclosure][0]) { + fputs(",\n\t\"attachments\": [{\"url:\": \"", stdout); + printfield(fields[FieldEnclosure]); + fputs("\"}]", stdout); + } + + if (!strcmp(fields[FieldContentType], "html")) + fputs(",\n\t\"content_html\": \"", stdout); + else + fputs(",\n\t\"content_text\": \"", stdout); + printcontent(fields[FieldContent]); + fputs("\"\n}", stdout); + } +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + char *name; + int i; + + if (pledge(argc == 1 ? "stdio" : "stdio rpath", NULL) == -1) + err(1, "pledge"); + + fputs("{\n" + "\"version\": \"https://jsonfeed.org/version/1.1\",\n" + "\"title\": \"Newsfeed\",\n" + "\"items\": [\n", stdout); + + if (argc == 1) { + printfeed(stdin, ""); + checkfileerror(stdin, "", 'r'); + } else { + for (i = 1; i < argc; i++) { + if (!(fp = fopen(argv[i], "r"))) + err(1, "fopen: %s", argv[i]); + name = ((name = strrchr(argv[i], '/'))) ? name + 1 : argv[i]; + printfeed(fp, name); + checkfileerror(fp, argv[i], 'r'); + checkfileerror(stdout, "", 'w'); + fclose(fp); + } + } + fputs("]\n}\n", stdout); + + checkfileerror(stdout, "", 'w'); + + return 0; +} -- cgit v1.2.3 From 728270f69c34a84cb10aa891178c90c8fe36320d Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Wed, 12 Apr 2023 22:55:54 +0200 Subject: fix some typos --- README | 2 +- sfeed.c | 10 +++++----- sfeed_gopher.c | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README b/README index a0a4815..6892e89 100644 --- a/README +++ b/README @@ -38,7 +38,7 @@ Initial setup: cp sfeedrc.example "$HOME/.sfeed/sfeedrc" Edit the sfeedrc(5) configuration file and change any RSS/Atom feeds. This file -is included and evaluated as a shellscript for sfeed_update, so it's functions +is included and evaluated as a shellscript for sfeed_update, so its functions and behaviour can be overridden: $EDITOR "$HOME/.sfeed/sfeedrc" diff --git a/sfeed.c b/sfeed.c index e96ffa3..e2f44b8 100644 --- a/sfeed.c +++ b/sfeed.c @@ -383,7 +383,7 @@ string_print_trimmed_multi(String *s) } } -/* Print URL, if it's a relative URL then it uses the global `baseurl`. */ +/* Print URL, if it is a relative URL then it uses the global `baseurl`. */ static void printuri(char *s) { @@ -409,7 +409,7 @@ printuri(char *s) *e = c; /* restore NUL byte to original character */ } -/* Print URL, if it's a relative URL then it uses the global `baseurl`. */ +/* Print URL, if it is a relative URL then it uses the global `baseurl`. */ static void string_print_uri(String *s) { @@ -432,7 +432,7 @@ string_print_timestamp(String *s) printf("%lld", t); } -/* Convert time fields. Returns a signed (atleast) 64-bit UNIX timestamp. +/* Convert time fields. Returns a signed (at least) 64-bit UNIX timestamp. Parameters should be passed as they are in a struct tm: that is: year = year - 1900, month = month - 1. */ static long long @@ -907,7 +907,7 @@ xmltagstartparsed(XMLParser *p, const char *t, size_t tl, int isshort) return; } - /* set tag type based on it's attribute value */ + /* set tag type based on its attribute value */ if (ctx.tag.id == RSSTagGuid) { /* if empty the default is "true" */ if (!attrispermalink.len || @@ -1018,7 +1018,7 @@ xmltagend(XMLParser *p, const char *t, size_t tl, int isshort) } /* temporary string: for fields that cannot be processed - directly and need more context, for example by it's tag + directly and need more context, for example by its tag attributes, like the Atom link rel="alternate|enclosure". */ if (tmpstr.len && ctx.field) { if (ISFEEDFIELDMULTI(fieldmap[ctx.tag.id])) { diff --git a/sfeed_gopher.c b/sfeed_gopher.c index 290d2d2..f62c6ed 100644 --- a/sfeed_gopher.c +++ b/sfeed_gopher.c @@ -64,7 +64,7 @@ printfeed(FILE *fpitems, FILE *fpin, struct feed *f) if (fields[FieldLink][0]) { itemtype = 'h'; - /* if it's a gopher URL then change it into a DirEntity */ + /* if it is a gopher URL then change it into a DirEntity */ if (!strncmp(fields[FieldLink], "gopher://", 9) && uri_parse(fields[FieldLink], &u) != -1) { itemhost = u.host; -- cgit v1.2.3 From eb8d6cf63815bff6697ebc7ae1b83f998b6eab53 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Thu, 13 Apr 2023 00:34:23 +0200 Subject: atom, json, mbox: fix reading past the buffer with an escaped NUL byte (\ NUL) This would skip checking the end of the string of checking a NUL byte, because the iteration was done before checking it. It would proceed into the data that comes after. Note that sfeed itself can't generate such malformed data itself. Example input: 0 title link content\ html Would incorrect print "contenthtml" as the content. --- sfeed_atom.c | 2 ++ sfeed_json.c | 2 ++ sfeed_mbox.c | 2 ++ 3 files changed, 6 insertions(+) diff --git a/sfeed_atom.c b/sfeed_atom.c index aeea2de..ace7d5a 100644 --- a/sfeed_atom.c +++ b/sfeed_atom.c @@ -22,6 +22,8 @@ printcontent(const char *s) case '&': fputs("&", stdout); break; case '"': fputs(""", stdout); break; case '\\': + if (*(s + 1) == '\0') + break; s++; switch (*s) { case 'n': putchar('\n'); break; diff --git a/sfeed_json.c b/sfeed_json.c index f6bb904..e177d2b 100644 --- a/sfeed_json.c +++ b/sfeed_json.c @@ -16,6 +16,8 @@ printcontent(const char *s) for (; *s; s++) { switch (*s) { case '\\': + if (*(s + 1) == '\0') + break; s++; switch (*s) { case 'n': fputs("\\n", stdout); break; diff --git a/sfeed_mbox.c b/sfeed_mbox.c index b5e7e3d..c00971f 100644 --- a/sfeed_mbox.c +++ b/sfeed_mbox.c @@ -37,6 +37,8 @@ escapefrom: for (; *s; s++) { switch (*s) { case '\\': + if (*(s + 1) == '\0') + break; s++; switch (*s) { case 'n': -- cgit v1.2.3 From ebd7175e56141e8e8816b1b27f1abc773818b6aa Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 14 Apr 2023 19:11:03 +0200 Subject: slightly reduce stack size for entities A numeric entity could be 5 bytes, so use a round number of 8 bytes. --- sfeed_opml_import.c | 2 +- sfeed_web.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sfeed_opml_import.c b/sfeed_opml_import.c index 14b5444..9922133 100644 --- a/sfeed_opml_import.c +++ b/sfeed_opml_import.c @@ -70,7 +70,7 @@ static void xmlattrentity(XMLParser *p, const char *t, size_t tl, const char *n, size_t nl, const char *v, size_t vl) { - char buf[16]; + char buf[8]; int len; if ((len = xml_entitytostr(v, buf, sizeof(buf))) > 0) diff --git a/sfeed_web.c b/sfeed_web.c index 630ab60..0082f2d 100644 --- a/sfeed_web.c +++ b/sfeed_web.c @@ -103,7 +103,7 @@ static void xmlattrentity(XMLParser *p, const char *t, size_t tl, const char *a, size_t al, const char *v, size_t vl) { - char buf[16]; + char buf[8]; int len; if (!ishrefattr && !istypeattr) -- cgit v1.2.3 From eb5ca56e76013ac281129659efa2169ac125188e Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 14 Apr 2023 19:11:38 +0200 Subject: use errno ENOMEM instead of EOVERFLOW This matches the behaviour of setting errno for malloc/calloc on the following systems too: glibc, musl libc, OpenBSD libc. It is also more portable for (older) systems. --- sfeed.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sfeed.c b/sfeed.c index e2f44b8..af4534c 100644 --- a/sfeed.c +++ b/sfeed.c @@ -293,7 +293,7 @@ string_append(String *s, const char *data, size_t len) return; if (s->len >= SIZE_MAX - len) { - errno = EOVERFLOW; + errno = ENOMEM; err(1, "realloc"); } -- cgit v1.2.3 From aed47142611e23b363298bbb534c5cc4bd11a86b Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sun, 23 Apr 2023 14:03:55 +0200 Subject: bump version to 1.8 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a8493ba..fbe2aa1 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .POSIX: NAME = sfeed -VERSION = 1.7 +VERSION = 1.8 # curses theme, see themes/ directory. SFEED_THEME = mono -- cgit v1.2.3 From 6e4136753bd0faa15c198118b05c529d96d908c5 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Mon, 24 Apr 2023 22:01:23 +0200 Subject: sfeed_{curses,frames,gopher,html,plain}: SFEED_NEW_MAX_SECS By introducing the new environment variable $SFEED_NEW_MAX_SECS in all sfeed_* utilities marking feeds as new based on comparing their age, it is now possible to override this age limit. This allows, for example, to be notified about new feeds within the last hour with SFEED_NEW_MAX_SECS=3600 sfeed_plain ~/.sfeed/feeds/* while creating a beautiful web report for last week's news by SFEED_NEW_MAX_SECS=604800 sfeed_html ~/.sfeed/feeds/* --- README | 10 ++++++---- sfeed_curses.1 | 4 ++++ sfeed_curses.c | 11 ++++++++--- sfeed_frames.1 | 7 +++++++ sfeed_frames.c | 15 ++++++++++++--- sfeed_gopher.1 | 4 ++++ sfeed_gopher.c | 15 ++++++++++++--- sfeed_html.1 | 7 +++++++ sfeed_html.c | 15 ++++++++++++--- sfeed_plain.1 | 7 +++++++ sfeed_plain.c | 16 +++++++++++++--- 11 files changed, 92 insertions(+), 19 deletions(-) diff --git a/README b/README index 6892e89..313ab4c 100644 --- a/README +++ b/README @@ -289,10 +289,12 @@ Just like the other format programs included in sfeed you can run it like this: sfeed_curses < ~/.sfeed/feeds/xkcd -By default sfeed_curses marks the items of the last day as new/bold. To manage -read/unread items in a different way a plain-text file with a list of the read -URLs can be used. To enable this behaviour the path to this file can be -specified by setting the environment variable $SFEED_URL_FILE to the URL file: +By default sfeed_curses marks the items of the last day as new/bold. This limit +might be overridden by setting the environment variable $SFEED_NEW_MAX_SECS to +the desired maximum in seconds. To manage read/unread items in a different way +a plain-text file with a list of the read URLs can be used. To enable this +behaviour the path to this file can be specified by setting the environment +variable $SFEED_URL_FILE to the URL file: export SFEED_URL_FILE="$HOME/.sfeed/urls" [ -f "$SFEED_URL_FILE" ] || touch "$SFEED_URL_FILE" diff --git a/sfeed_curses.1 b/sfeed_curses.1 index 94ffd4c..b3cccf2 100644 --- a/sfeed_curses.1 +++ b/sfeed_curses.1 @@ -30,6 +30,7 @@ arguments are specified then the data is read from stdin and the feed name is .Pp Items with a timestamp from the last day compared to the system time at the time of loading the feed are marked as new and bold. +This value might be overridden through environment variables. There is also an alternative mode available to mark items as read by matching it against a list of URLs from a plain-text file. Items with an enclosure are marked with a @ symbol. @@ -207,6 +208,9 @@ SIGWINCH. Read and process a sequence of keys as input commands from this environment variable first, afterwards it reads from the tty as usual. This can be useful to automate certain actions at the start. +.It Ev SFEED_NEW_MAX_SECS +Overwrite the maximum age in seconds to mark feeds as new. +By default this is 86400, which equals one day. .It Ev SFEED_PIPER A program where the whole TAB-Separated Value line is piped to. By default this is "sfeed_content". diff --git a/sfeed_curses.c b/sfeed_curses.c index 1abb046..cce608e 100644 --- a/sfeed_curses.c +++ b/sfeed_curses.c @@ -191,6 +191,7 @@ static int plumberia = 0; /* env variable: $SFEED_PLUMBER_INTERACTIVE */ static int piperia = 1; /* env variable: $SFEED_PIPER_INTERACTIVE */ static int yankeria = 0; /* env variable: $SFEED_YANKER_INTERACTIVE */ static int lazyload = 0; /* env variable: $SFEED_LAZYLOAD */ +static int newmaxsecs = 86400; /* env variable: $SFEED_NEW_MAX_SECS */ int ttywritef(const char *fmt, ...) @@ -1322,8 +1323,7 @@ feeds_load(struct feed *feeds, size_t nfeeds) errno = 0; if ((comparetime = time(NULL)) == (time_t)-1) die("time"); - /* 1 day is old news */ - comparetime -= 86400; + comparetime -= newmaxsecs; for (i = 0; i < nfeeds; i++) { f = &feeds[i]; @@ -1967,7 +1967,7 @@ main(int argc, char *argv[]) struct pane *p; struct feed *f; struct row *row; - char *name, *tmp; + char *name, *tmp, *endptr; char *search = NULL; /* search text */ int button, ch, fd, i, keymask, release, x, y; off_t pos; @@ -1997,6 +1997,11 @@ main(int argc, char *argv[]) markunreadcmd = tmp; if ((tmp = getenv("SFEED_LAZYLOAD"))) lazyload = !strcmp(tmp, "1"); + if ((tmp = getenv("SFEED_NEW_MAX_SECS"))) { + newmaxsecs = (int) strtol(tmp, &endptr, 10); + if (*tmp == '\0' || *endptr != '\0' || newmaxsecs <= 0) + err(1, "cannot parse $SFEED_NEW_MAX_SECS"); + } urlfile = getenv("SFEED_URL_FILE"); /* can be NULL */ cmdenv = getenv("SFEED_AUTOCMD"); /* can be NULL */ diff --git a/sfeed_frames.1 b/sfeed_frames.1 index e29498b..976d5ed 100644 --- a/sfeed_frames.1 +++ b/sfeed_frames.1 @@ -23,6 +23,7 @@ file is not written. Items with a timestamp from the last day compared to the system time at the time of formatting are counted and marked as new. Items are marked as new using a bold markup. +This value might be overridden through environment variables. .Pp There is an example style.css stylesheet file included in the distribution. .Sh FILES WRITTEN @@ -37,6 +38,12 @@ feeds. The HTML file of the menu frame which contains navigation "anchor" links (like "#feedname") to the feed names in items.html. .El +.Sh ENVIRONMENT VARIABLES +.Bl -tag -width Ds +.It Ev SFEED_NEW_MAX_SECS +Overwrite the maximum age in seconds to mark feeds as new. +By default this is 86400, which equals one day. +.El .Sh EXIT STATUS .Ex -std .Sh EXAMPLES diff --git a/sfeed_frames.c b/sfeed_frames.c index 178a4a2..e52abd1 100644 --- a/sfeed_frames.c +++ b/sfeed_frames.c @@ -77,8 +77,9 @@ int main(int argc, char *argv[]) { FILE *fpindex, *fpitems, *fpmenu = NULL, *fp; - char *name; + char *name, *tmp, *endptr; int i, showsidebar = (argc > 1); + long l; struct feed *f; if (pledge("stdio rpath wpath cpath", NULL) == -1) @@ -89,8 +90,16 @@ main(int argc, char *argv[]) if ((comparetime = time(NULL)) == (time_t)-1) errx(1, "time"); - /* 1 day is old news */ - comparetime -= 86400; + + if ((tmp = getenv("SFEED_NEW_MAX_SECS"))) { + l = strtol(tmp, &endptr, 10); + if (*tmp == '\0' || *endptr != '\0' || l <= 0) + err(1, "cannot parse $SFEED_NEW_MAX_SECS"); + comparetime -= l; + } else { + /* 1 day is old news */ + comparetime -= 86400; + } /* write main index page */ if (!(fpindex = fopen("index.html", "wb"))) diff --git a/sfeed_gopher.1 b/sfeed_gopher.1 index 43a11c7..f602d2c 100644 --- a/sfeed_gopher.1 +++ b/sfeed_gopher.1 @@ -32,6 +32,7 @@ written to stdout and no files are written. .Pp Items with a timestamp from the last day compared to the system time at the time of formatting are counted and marked as new. +This value might be overridden through environment variables. Items are marked as new with the prefix "N" at the start of the line. .Sh ENVIRONMENT .Bl -tag -width Ds @@ -45,6 +46,9 @@ The default is "127.0.0.1". .It Ev SFEED_GOPHER_PORT This environment variable can be used as the Gopher Port field. The default is "70". +.It Ev SFEED_NEW_MAX_SECS +Overwrite the maximum age in seconds to mark feeds as new. +By default this is 86400, which equals one day. .El .Sh EXIT STATUS .Ex -std diff --git a/sfeed_gopher.c b/sfeed_gopher.c index f62c6ed..007a244 100644 --- a/sfeed_gopher.c +++ b/sfeed_gopher.c @@ -122,8 +122,9 @@ int main(int argc, char *argv[]) { FILE *fpitems, *fpindex, *fp; - char *name, *p; + char *name, *p, *tmp, *endptr; int i; + long l; if (argc == 1) { if (pledge("stdio", NULL) == -1) @@ -139,8 +140,16 @@ main(int argc, char *argv[]) if ((comparetime = time(NULL)) == (time_t)-1) errx(1, "time"); - /* 1 day is old news */ - comparetime -= 86400; + + if ((tmp = getenv("SFEED_NEW_MAX_SECS"))) { + l = strtol(tmp, &endptr, 10); + if (*tmp == '\0' || *endptr != '\0' || l <= 0) + err(1, "cannot parse $SFEED_NEW_MAX_SECS"); + comparetime -= l; + } else { + /* 1 day is old news */ + comparetime -= 86400; + } if ((p = getenv("SFEED_GOPHER_HOST"))) host = p; diff --git a/sfeed_html.1 b/sfeed_html.1 index efeb289..98784c5 100644 --- a/sfeed_html.1 +++ b/sfeed_html.1 @@ -26,9 +26,16 @@ is empty. .Pp Items with a timestamp from the last day compared to the system time at the time of formatting are counted and marked as new. +This value might be overridden through environment variables. Items are marked as new using a bold markup. .Pp There is an example style.css stylesheet file included in the distribution. +.Sh ENVIRONMENT VARIABLES +.Bl -tag -width Ds +.It Ev SFEED_NEW_MAX_SECS +Overwrite the maximum age in seconds to mark feeds as new. +By default this is 86400, which equals one day. +.El .Sh EXIT STATUS .Ex -std .Sh EXAMPLES diff --git a/sfeed_html.c b/sfeed_html.c index ce96687..89506be 100644 --- a/sfeed_html.c +++ b/sfeed_html.c @@ -78,9 +78,10 @@ int main(int argc, char *argv[]) { struct feed *f; - char *name; + char *name, *tmp, *endptr; FILE *fp; int i; + long l; if (pledge(argc == 1 ? "stdio" : "stdio rpath", NULL) == -1) err(1, "pledge"); @@ -89,8 +90,16 @@ main(int argc, char *argv[]) err(1, "calloc"); if ((comparetime = time(NULL)) == (time_t)-1) errx(1, "time"); - /* 1 day is old news */ - comparetime -= 86400; + + if ((tmp = getenv("SFEED_NEW_MAX_SECS"))) { + l = strtol(tmp, &endptr, 10); + if (*tmp == '\0' || *endptr != '\0' || l <= 0) + err(1, "cannot parse $SFEED_NEW_MAX_SECS"); + comparetime -= l; + } else { + /* 1 day is old news */ + comparetime -= 86400; + } fputs("\n" "\n" diff --git a/sfeed_plain.1 b/sfeed_plain.1 index 466ae41..93ed41d 100644 --- a/sfeed_plain.1 +++ b/sfeed_plain.1 @@ -26,6 +26,7 @@ is empty. .Pp Items with a timestamp from the last day compared to the system time at the time of formatting are marked as new. +This value might be overridden through environment variables. Items are marked as new with the prefix "N" at the start of the line. .Pp .Nm @@ -39,6 +40,12 @@ per rune, using .Xr mbtowc 3 and .Xr wcwidth 3 . +.Sh ENVIRONMENT VARIABLES +.Bl -tag -width Ds +.It Ev SFEED_NEW_MAX_SECS +Overwrite the maximum age in seconds to mark feeds as new. +By default this is 86400, which equals one day. +.El .Sh EXIT STATUS .Ex -std .Sh EXAMPLES diff --git a/sfeed_plain.c b/sfeed_plain.c index 8b1f00f..c790ec4 100644 --- a/sfeed_plain.c +++ b/sfeed_plain.c @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -52,8 +53,9 @@ int main(int argc, char *argv[]) { FILE *fp; - char *name; + char *name, *tmp, *endptr; int i; + long l; if (pledge("stdio rpath", NULL) == -1) err(1, "pledge"); @@ -65,8 +67,16 @@ main(int argc, char *argv[]) if ((comparetime = time(NULL)) == (time_t)-1) errx(1, "time"); - /* 1 day is old news */ - comparetime -= 86400; + + if ((tmp = getenv("SFEED_NEW_MAX_SECS"))) { + l = strtol(tmp, &endptr, 10); + if (*tmp == '\0' || *endptr != '\0' || l <= 0) + err(1, "cannot parse $SFEED_NEW_MAX_SECS"); + comparetime -= l; + } else { + /* 1 day is old news */ + comparetime -= 86400; + } if (argc == 1) { printfeed(stdin, ""); -- cgit v1.2.3 From ccee88cb02cb54f586d22c53e8bb8865817c3dc4 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sun, 7 May 2023 12:56:46 +0200 Subject: sfeed: datetounix: code-style, change , to separate lines (-Wcomma) --- sfeed.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/sfeed.c b/sfeed.c index af4534c..e7ffa05 100644 --- a/sfeed.c +++ b/sfeed.c @@ -470,12 +470,16 @@ datetounix(long long year, int mon, int day, int hour, int min, int sec) if (!rem) { is_leap = 1; } else { - if (rem >= 300) - centuries = 3, rem -= 300; - else if (rem >= 200) - centuries = 2, rem -= 200; - else if (rem >= 100) - centuries = 1, rem -= 100; + if (rem >= 300) { + centuries = 3; + rem -= 300; + } else if (rem >= 200) { + centuries = 2; + rem -= 200; + } else if (rem >= 100) { + centuries = 1; + rem -= 100; + } if (rem) { leaps = rem / 4U; rem %= 4U; -- cgit v1.2.3 From bdcbf8589716c047a732db3cc349ee6114ccc25f Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sun, 7 May 2023 12:39:26 +0200 Subject: iterate on previous commit which adds $SFEED_NEW_MAX_SECS Separate the common pattern to get the time to compare new items in format tools to the new util function: getcomparetime(). Some changes and notes: - Change it so it is OK to set this value to 0 or negative (in the future). - sfeed_curses: truncating newmaxsecs to an int would limit the value too much. - Just use strtotime() and parse the value to time_t. This is a signed long (32-bit, until 2038) or signed long long (64-bit) on most platforms. - sfeed_curses gets the comparison time on reload aswell and it needs errno = 0, because it uses die(). time() is not guaranteed to set an errno if it fails. - Rename environment variable to $SFEED_NEW_AGE. --- README | 6 +++--- sfeed_curses.1 | 2 +- sfeed_curses.c | 13 +++---------- sfeed_frames.1 | 2 +- sfeed_frames.c | 17 +++-------------- sfeed_gopher.1 | 2 +- sfeed_gopher.c | 17 +++-------------- sfeed_html.1 | 2 +- sfeed_html.c | 17 +++-------------- sfeed_plain.1 | 2 +- sfeed_plain.c | 18 +++--------------- util.c | 18 ++++++++++++++++++ util.h | 1 + 13 files changed, 42 insertions(+), 75 deletions(-) diff --git a/README b/README index 313ab4c..427435d 100644 --- a/README +++ b/README @@ -290,9 +290,9 @@ Just like the other format programs included in sfeed you can run it like this: sfeed_curses < ~/.sfeed/feeds/xkcd By default sfeed_curses marks the items of the last day as new/bold. This limit -might be overridden by setting the environment variable $SFEED_NEW_MAX_SECS to -the desired maximum in seconds. To manage read/unread items in a different way -a plain-text file with a list of the read URLs can be used. To enable this +might be overridden by setting the environment variable $SFEED_NEW_AGE to the +desired maximum in seconds. To manage read/unread items in a different way a +plain-text file with a list of the read URLs can be used. To enable this behaviour the path to this file can be specified by setting the environment variable $SFEED_URL_FILE to the URL file: diff --git a/sfeed_curses.1 b/sfeed_curses.1 index b3cccf2..56b2a92 100644 --- a/sfeed_curses.1 +++ b/sfeed_curses.1 @@ -208,7 +208,7 @@ SIGWINCH. Read and process a sequence of keys as input commands from this environment variable first, afterwards it reads from the tty as usual. This can be useful to automate certain actions at the start. -.It Ev SFEED_NEW_MAX_SECS +.It Ev SFEED_NEW_AGE Overwrite the maximum age in seconds to mark feeds as new. By default this is 86400, which equals one day. .It Ev SFEED_PIPER diff --git a/sfeed_curses.c b/sfeed_curses.c index cce608e..b6f995f 100644 --- a/sfeed_curses.c +++ b/sfeed_curses.c @@ -191,7 +191,6 @@ static int plumberia = 0; /* env variable: $SFEED_PLUMBER_INTERACTIVE */ static int piperia = 1; /* env variable: $SFEED_PIPER_INTERACTIVE */ static int yankeria = 0; /* env variable: $SFEED_YANKER_INTERACTIVE */ static int lazyload = 0; /* env variable: $SFEED_LAZYLOAD */ -static int newmaxsecs = 86400; /* env variable: $SFEED_NEW_MAX_SECS */ int ttywritef(const char *fmt, ...) @@ -1321,9 +1320,8 @@ feeds_load(struct feed *feeds, size_t nfeeds) size_t i; errno = 0; - if ((comparetime = time(NULL)) == (time_t)-1) - die("time"); - comparetime -= newmaxsecs; + if ((comparetime = getcomparetime()) == (time_t)-1) + die("getcomparetime"); for (i = 0; i < nfeeds; i++) { f = &feeds[i]; @@ -1967,7 +1965,7 @@ main(int argc, char *argv[]) struct pane *p; struct feed *f; struct row *row; - char *name, *tmp, *endptr; + char *name, *tmp; char *search = NULL; /* search text */ int button, ch, fd, i, keymask, release, x, y; off_t pos; @@ -1997,11 +1995,6 @@ main(int argc, char *argv[]) markunreadcmd = tmp; if ((tmp = getenv("SFEED_LAZYLOAD"))) lazyload = !strcmp(tmp, "1"); - if ((tmp = getenv("SFEED_NEW_MAX_SECS"))) { - newmaxsecs = (int) strtol(tmp, &endptr, 10); - if (*tmp == '\0' || *endptr != '\0' || newmaxsecs <= 0) - err(1, "cannot parse $SFEED_NEW_MAX_SECS"); - } urlfile = getenv("SFEED_URL_FILE"); /* can be NULL */ cmdenv = getenv("SFEED_AUTOCMD"); /* can be NULL */ diff --git a/sfeed_frames.1 b/sfeed_frames.1 index 976d5ed..455c1db 100644 --- a/sfeed_frames.1 +++ b/sfeed_frames.1 @@ -40,7 +40,7 @@ The HTML file of the menu frame which contains navigation "anchor" links (like .El .Sh ENVIRONMENT VARIABLES .Bl -tag -width Ds -.It Ev SFEED_NEW_MAX_SECS +.It Ev SFEED_NEW_AGE Overwrite the maximum age in seconds to mark feeds as new. By default this is 86400, which equals one day. .El diff --git a/sfeed_frames.c b/sfeed_frames.c index e52abd1..b2f75cf 100644 --- a/sfeed_frames.c +++ b/sfeed_frames.c @@ -77,9 +77,8 @@ int main(int argc, char *argv[]) { FILE *fpindex, *fpitems, *fpmenu = NULL, *fp; - char *name, *tmp, *endptr; + char *name; int i, showsidebar = (argc > 1); - long l; struct feed *f; if (pledge("stdio rpath wpath cpath", NULL) == -1) @@ -88,18 +87,8 @@ main(int argc, char *argv[]) if (!(feeds = calloc(argc, sizeof(struct feed)))) err(1, "calloc"); - if ((comparetime = time(NULL)) == (time_t)-1) - errx(1, "time"); - - if ((tmp = getenv("SFEED_NEW_MAX_SECS"))) { - l = strtol(tmp, &endptr, 10); - if (*tmp == '\0' || *endptr != '\0' || l <= 0) - err(1, "cannot parse $SFEED_NEW_MAX_SECS"); - comparetime -= l; - } else { - /* 1 day is old news */ - comparetime -= 86400; - } + if ((comparetime = getcomparetime()) == (time_t)-1) + errx(1, "getcomparetime"); /* write main index page */ if (!(fpindex = fopen("index.html", "wb"))) diff --git a/sfeed_gopher.1 b/sfeed_gopher.1 index f602d2c..bc45121 100644 --- a/sfeed_gopher.1 +++ b/sfeed_gopher.1 @@ -46,7 +46,7 @@ The default is "127.0.0.1". .It Ev SFEED_GOPHER_PORT This environment variable can be used as the Gopher Port field. The default is "70". -.It Ev SFEED_NEW_MAX_SECS +.It Ev SFEED_NEW_AGE Overwrite the maximum age in seconds to mark feeds as new. By default this is 86400, which equals one day. .El diff --git a/sfeed_gopher.c b/sfeed_gopher.c index 007a244..78e1f12 100644 --- a/sfeed_gopher.c +++ b/sfeed_gopher.c @@ -122,9 +122,8 @@ int main(int argc, char *argv[]) { FILE *fpitems, *fpindex, *fp; - char *name, *p, *tmp, *endptr; + char *name, *p; int i; - long l; if (argc == 1) { if (pledge("stdio", NULL) == -1) @@ -138,18 +137,8 @@ main(int argc, char *argv[]) err(1, "pledge"); } - if ((comparetime = time(NULL)) == (time_t)-1) - errx(1, "time"); - - if ((tmp = getenv("SFEED_NEW_MAX_SECS"))) { - l = strtol(tmp, &endptr, 10); - if (*tmp == '\0' || *endptr != '\0' || l <= 0) - err(1, "cannot parse $SFEED_NEW_MAX_SECS"); - comparetime -= l; - } else { - /* 1 day is old news */ - comparetime -= 86400; - } + if ((comparetime = getcomparetime()) == (time_t)-1) + errx(1, "getcomparetime"); if ((p = getenv("SFEED_GOPHER_HOST"))) host = p; diff --git a/sfeed_html.1 b/sfeed_html.1 index 98784c5..e517a63 100644 --- a/sfeed_html.1 +++ b/sfeed_html.1 @@ -32,7 +32,7 @@ Items are marked as new using a bold markup. There is an example style.css stylesheet file included in the distribution. .Sh ENVIRONMENT VARIABLES .Bl -tag -width Ds -.It Ev SFEED_NEW_MAX_SECS +.It Ev SFEED_NEW_AGE Overwrite the maximum age in seconds to mark feeds as new. By default this is 86400, which equals one day. .El diff --git a/sfeed_html.c b/sfeed_html.c index 89506be..9269413 100644 --- a/sfeed_html.c +++ b/sfeed_html.c @@ -78,28 +78,17 @@ int main(int argc, char *argv[]) { struct feed *f; - char *name, *tmp, *endptr; + char *name; FILE *fp; int i; - long l; if (pledge(argc == 1 ? "stdio" : "stdio rpath", NULL) == -1) err(1, "pledge"); if (!(feeds = calloc(argc, sizeof(struct feed)))) err(1, "calloc"); - if ((comparetime = time(NULL)) == (time_t)-1) - errx(1, "time"); - - if ((tmp = getenv("SFEED_NEW_MAX_SECS"))) { - l = strtol(tmp, &endptr, 10); - if (*tmp == '\0' || *endptr != '\0' || l <= 0) - err(1, "cannot parse $SFEED_NEW_MAX_SECS"); - comparetime -= l; - } else { - /* 1 day is old news */ - comparetime -= 86400; - } + if ((comparetime = getcomparetime()) == (time_t)-1) + errx(1, "getcomparetime"); fputs("\n" "\n" diff --git a/sfeed_plain.1 b/sfeed_plain.1 index 93ed41d..2e8e6ad 100644 --- a/sfeed_plain.1 +++ b/sfeed_plain.1 @@ -42,7 +42,7 @@ and .Xr wcwidth 3 . .Sh ENVIRONMENT VARIABLES .Bl -tag -width Ds -.It Ev SFEED_NEW_MAX_SECS +.It Ev SFEED_NEW_AGE Overwrite the maximum age in seconds to mark feeds as new. By default this is 86400, which equals one day. .El diff --git a/sfeed_plain.c b/sfeed_plain.c index c790ec4..f8ce7ec 100644 --- a/sfeed_plain.c +++ b/sfeed_plain.c @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -53,9 +52,8 @@ int main(int argc, char *argv[]) { FILE *fp; - char *name, *tmp, *endptr; + char *name; int i; - long l; if (pledge("stdio rpath", NULL) == -1) err(1, "pledge"); @@ -65,18 +63,8 @@ main(int argc, char *argv[]) if (pledge(argc == 1 ? "stdio" : "stdio rpath", NULL) == -1) err(1, "pledge"); - if ((comparetime = time(NULL)) == (time_t)-1) - errx(1, "time"); - - if ((tmp = getenv("SFEED_NEW_MAX_SECS"))) { - l = strtol(tmp, &endptr, 10); - if (*tmp == '\0' || *endptr != '\0' || l <= 0) - err(1, "cannot parse $SFEED_NEW_MAX_SECS"); - comparetime -= l; - } else { - /* 1 day is old news */ - comparetime -= 86400; - } + if ((comparetime = getcomparetime()) == (time_t)-1) + errx(1, "getcomparetime"); if (argc == 1) { printfeed(stdin, ""); diff --git a/util.c b/util.c index 0b7da06..e5aec3b 100644 --- a/util.c +++ b/util.c @@ -318,6 +318,24 @@ strtotime(const char *s, time_t *t) return 0; } +time_t +getcomparetime(void) +{ + time_t now, t; + char *p; + + if ((now = time(NULL)) == (time_t)-1) + return (time_t)-1; + + if ((p = getenv("SFEED_NEW_AGE"))) { + if (strtotime(p, &t) == -1) + return (time_t)-1; + return now - t; + } + + return now - 86400; /* 1 day is old news */ +} + /* Escape characters below as HTML 2.0 / XML 1.0. */ void xmlencode(const char *s, FILE *fp) diff --git a/util.h b/util.h index 9138de7..3ce5e41 100644 --- a/util.h +++ b/util.h @@ -71,6 +71,7 @@ int uri_makeabs(struct uri *, struct uri *, struct uri *); int uri_parse(const char *, struct uri *); void checkfileerror(FILE *, const char *, int); +time_t getcomparetime(void); void parseline(char *, char *[FieldLast]); void printutf8pad(FILE *, const char *, size_t, int); int strtotime(const char *, time_t *); -- cgit v1.2.3 From 3909786d90499355617cba619d3668771b7a56d3 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sun, 14 May 2023 23:37:40 +0200 Subject: xml.h: _XML_H_: macro name with an underscore is a reserved identifier Found with clang -Wreserved-macro-identifier See also: https://wiki.sei.cmu.edu/confluence/display/c/DCL37-C.+Do+not+declare+or+define+a+reserved+identifier --- xml.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xml.h b/xml.h index ad7d26b..122726c 100644 --- a/xml.h +++ b/xml.h @@ -1,5 +1,5 @@ -#ifndef _XML_H_ -#define _XML_H_ +#ifndef XML_H +#define XML_H #include -- cgit v1.2.3 From 538e7dc0ccf065bf59b952a46ff5658ea19f3bab Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sun, 14 May 2023 23:38:17 +0200 Subject: sfeed_curses.c: make struct urls static like the other variables --- sfeed_curses.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sfeed_curses.c b/sfeed_curses.c index b6f995f..f54b4d6 100644 --- a/sfeed_curses.c +++ b/sfeed_curses.c @@ -175,7 +175,7 @@ static struct feed *feeds; static struct feed *curfeed; static size_t nfeeds; /* amount of feeds */ static time_t comparetime; -struct urls urls; +static struct urls urls; static char *urlfile; volatile sig_atomic_t state_sigchld = 0, state_sighup = 0, state_sigint = 0; -- cgit v1.2.3 From 3f7b8da537dca5b97657519eb8544f02f6b32d55 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sun, 14 May 2023 23:58:54 +0200 Subject: sfeed_gopher: reduce scope and shadowing a variable --- sfeed_gopher.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sfeed_gopher.c b/sfeed_gopher.c index 78e1f12..af6a49c 100644 --- a/sfeed_gopher.c +++ b/sfeed_gopher.c @@ -7,7 +7,6 @@ #include "util.h" -static struct feed f; static char *prefixpath = "/", *host = "127.0.0.1", *port = "70"; /* default */ static char *line; static size_t linesize; @@ -121,6 +120,7 @@ printfeed(FILE *fpitems, FILE *fpin, struct feed *f) int main(int argc, char *argv[]) { + struct feed f; FILE *fpitems, *fpindex, *fp; char *name, *p; int i; -- cgit v1.2.3 From 5027669efb6a969afa2c8adcf7c2322dcf75de1f Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Mon, 15 May 2023 18:48:56 +0200 Subject: fix typo --- sfeed.1 | 4 ++-- sfeed.c | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sfeed.1 b/sfeed.1 index 79d43be..5c348a8 100644 --- a/sfeed.1 +++ b/sfeed.1 @@ -111,5 +111,5 @@ The README file has more examples. .Sh AUTHORS .An Hiltjo Posthuma Aq Mt hiltjo@codemadness.org .Sh CAVEATS -If a timezone for the timestamp field is not in the RFC822 or RFC3339 format it -is not supported and the timezone is interpreted as UTC+0. +If a timezone for the timestamp field is not in the RFC 822 or RFC 3339 format +it is not supported and the timezone is interpreted as UTC+0. diff --git a/sfeed.c b/sfeed.c index e7ffa05..888a575 100644 --- a/sfeed.c +++ b/sfeed.c @@ -504,10 +504,10 @@ datetounix(long long year, int mon, int day, int hour, int min, int sec) } /* Get timezone from string, return time offset in seconds from UTC. - * NOTE: only parses timezones in RFC-822, many other timezone names are + * NOTE: only parses timezones in RFC 822, many other timezone names are * ambiguous anyway. - * ANSI and military zones are defined wrong in RFC822 and are unsupported, - * see note on RFC2822 4.3 page 32. */ + * ANSI and military zones are defined wrong in RFC 822 and are unsupported, + * see note on RFC 2822 4.3 page 32. */ static long gettzoffset(const char *s) { @@ -627,7 +627,7 @@ parsetime(const char *s, long long *tp) ; for (v = 0, i = 0; i < 4 && ISDIGIT((unsigned char)*s); s++, i++) v = (v * 10) + (*s - '0'); - /* obsolete short year: RFC2822 4.3 */ + /* obsolete short year: RFC 2822 4.3 */ if (i == 2 || i == 3) v += (i == 2 && v >= 0 && v <= 49) ? 2000 : 1900; va[0] = v; /* year */ -- cgit v1.2.3 From bb89b1e4dbfc63857adb829c4fe31c63e9ca1b14 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Mon, 15 May 2023 18:53:19 +0200 Subject: sfeed_atom: gmtime_r make it consistent with sfeed_mbox When gmtime_r is called to get the current time show the same error message as sfeed_mbox does. --- sfeed_atom.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sfeed_atom.c b/sfeed_atom.c index ace7d5a..99facad 100644 --- a/sfeed_atom.c +++ b/sfeed_atom.c @@ -130,7 +130,7 @@ main(int argc, char *argv[]) if ((now = time(NULL)) == (time_t)-1) errx(1, "time"); if (!(tm = gmtime_r(&now, &tmnow))) - err(1, "gmtime_r"); + err(1, "gmtime_r: can't get current time"); fputs("\n" "\n" -- cgit v1.2.3 From 6245c2866bd38dcad51833f39677f99fb25fd0ef Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Tue, 16 May 2023 18:30:09 +0200 Subject: README: RSS 0.90+ is supported, not 0.91+ See also: https://www.rssboard.org/rss-0-9-0 --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index 427435d..fbe40b6 100644 --- a/README +++ b/README @@ -141,7 +141,7 @@ sfeed supports a subset of XML 1.0 and a subset of: - Atom 1.0 (RFC 4287): https://datatracker.ietf.org/doc/html/rfc4287 - Atom 0.3 (draft, historic). -- RSS 0.91+. +- RSS 0.90+. - RDF (when used with RSS). - MediaRSS extensions (media:). - Dublin Core extensions (dc:). -- cgit v1.2.3 From 21790adad1672689225efe38b6a59494181d323b Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Tue, 16 May 2023 20:11:21 +0200 Subject: improve to use proper includes Reduce using some of the unneeded sys/* headers too. This makes it slightly more portable or easier to port also. --- sfeed_atom.c | 2 -- sfeed_curses.c | 2 -- sfeed_frames.c | 2 -- sfeed_gopher.c | 2 -- sfeed_html.c | 2 -- sfeed_json.c | 3 +-- sfeed_plain.c | 2 -- sfeed_twtxt.c | 2 -- 8 files changed, 1 insertion(+), 16 deletions(-) diff --git a/sfeed_atom.c b/sfeed_atom.c index 99facad..d0b139d 100644 --- a/sfeed_atom.c +++ b/sfeed_atom.c @@ -1,5 +1,3 @@ -#include - #include #include #include diff --git a/sfeed_curses.c b/sfeed_curses.c index f54b4d6..0ce9a4e 100644 --- a/sfeed_curses.c +++ b/sfeed_curses.c @@ -1,7 +1,5 @@ #include #include -#include -#include #include #include diff --git a/sfeed_frames.c b/sfeed_frames.c index b2f75cf..53f44a6 100644 --- a/sfeed_frames.c +++ b/sfeed_frames.c @@ -1,5 +1,3 @@ -#include - #include #include #include diff --git a/sfeed_gopher.c b/sfeed_gopher.c index af6a49c..c879864 100644 --- a/sfeed_gopher.c +++ b/sfeed_gopher.c @@ -1,5 +1,3 @@ -#include - #include #include #include diff --git a/sfeed_html.c b/sfeed_html.c index 9269413..2142145 100644 --- a/sfeed_html.c +++ b/sfeed_html.c @@ -1,5 +1,3 @@ -#include - #include #include #include diff --git a/sfeed_json.c b/sfeed_json.c index e177d2b..4fe5942 100644 --- a/sfeed_json.c +++ b/sfeed_json.c @@ -1,7 +1,6 @@ #include -#include #include -#include +#include #include "util.h" diff --git a/sfeed_plain.c b/sfeed_plain.c index f8ce7ec..adeefdb 100644 --- a/sfeed_plain.c +++ b/sfeed_plain.c @@ -1,5 +1,3 @@ -#include - #include #include #include diff --git a/sfeed_twtxt.c b/sfeed_twtxt.c index 1d8ab36..1bb9d3b 100644 --- a/sfeed_twtxt.c +++ b/sfeed_twtxt.c @@ -1,5 +1,3 @@ -#include - #include #include #include -- cgit v1.2.3 From 96d7afc7d7511f05ba07c5acbb5bbfb2847bc126 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 9 Jun 2023 14:40:40 +0200 Subject: sfeed_update/sfeedrc: add url as parameter to the filter() and order() function This might make it easier to set filters or ordering by pattern matching on a group of feeds by the feed URL. For example youtube or reddit feeds. Another way which was already possible is prefixing names with for example: "reddit somename" or "yt somename". --- README | 2 +- sfeed_update | 8 ++++---- sfeedrc.5 | 10 +++++++--- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/README b/README index fbe40b6..d128360 100644 --- a/README +++ b/README @@ -336,7 +336,7 @@ filtering items per feed. It can be used to shorten URLs, filter away advertisements, strip tracking parameters and more. # filter fields. - # filter(name) + # filter(name, url) filter() { case "$1" in "tweakers") diff --git a/sfeed_update b/sfeed_update index 3da45d3..dadea9b 100755 --- a/sfeed_update +++ b/sfeed_update @@ -71,7 +71,7 @@ parse() { } # filter fields. -# filter(name) +# filter(name, url) filter() { cat } @@ -83,7 +83,7 @@ merge() { } # order by timestamp (descending). -# order(name) +# order(name, url) order() { sort -t ' ' -k1rn,1 } @@ -124,7 +124,7 @@ _feed() { fi rm -f "${tmpfeedfile}.utf8" - if ! filter "${name}" < "${tmpfeedfile}.tsv" > "${tmpfeedfile}.filter"; then + if ! filter "${name}" "${feedurl}" < "${tmpfeedfile}.tsv" > "${tmpfeedfile}.filter"; then log_error "${name}" "FAIL (FILTER)" return 1 fi @@ -142,7 +142,7 @@ _feed() { fi rm -f "${tmpfeedfile}.filter" - if ! order "${name}" < "${tmpfeedfile}.merge" > "${tmpfeedfile}.order"; then + if ! order "${name}" "${feedurl}" < "${tmpfeedfile}.merge" > "${tmpfeedfile}.order"; then log_error "${name}" "FAIL (ORDER)" return 1 fi diff --git a/sfeedrc.5 b/sfeedrc.5 index 91f82ce..16e0641 100644 --- a/sfeedrc.5 +++ b/sfeedrc.5 @@ -1,4 +1,4 @@ -.Dd January 18, 2023 +.Dd June 9, 2023 .Dt SFEEDRC 5 .Os .Sh NAME @@ -101,13 +101,15 @@ URL of the feed. Base URL of the feed links. This argument allows to fix relative item links. .El -.It Fn filter "name" +.It Fn filter "name" "url" Filter .Xr sfeed 5 data from stdin and write it to stdout, its arguments are: .Bl -tag -width Ds .It Fa name Feed name. +.It Fa url +URL of the feed. .El .It Fn merge "name" "oldfile" "newfile" Merge @@ -121,13 +123,15 @@ Old file. .It Fa newfile New file. .El -.It Fn order "name" +.It Fn order "name" "url" Sort .Xr sfeed 5 data from stdin and write it to stdout, its arguments are: .Bl -tag -width Ds .It Fa name Feed name. +.It Fa url +URL of the feed. .El .El .Sh EXAMPLES -- cgit v1.2.3 From 44a4824eeba93a13511251a907179393f247c201 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 9 Jun 2023 14:42:52 +0200 Subject: add comment to reference to the man pages and README file These file contain examples and more details and may be overlooked/forgotten. --- sfeedrc.example | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sfeedrc.example b/sfeedrc.example index 28630ec..af8e1c4 100644 --- a/sfeedrc.example +++ b/sfeedrc.example @@ -1,3 +1,6 @@ +# for more details see the sfeedrc(5) and sfeed_update(1) man pages +# and the README file. + #sfeedpath="$HOME/.sfeed/feeds" # list of feeds to fetch: -- cgit v1.2.3 From e96bdb644ae2f49125079e6218e2777caf633e1c Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 9 Jun 2023 14:44:34 +0200 Subject: README: improve note about CDNs and user-agent blocking Prompted by a question from a user. Specificly mention reddit.com now blocks clients which don't set the User-Agent header. It is specificly mentioned because there is a reddit feed in the example sfeedrc. --- README | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README b/README index d128360..8098a76 100644 --- a/README +++ b/README @@ -679,8 +679,8 @@ additional metadata from the previous request. CDNs blocking requests due to a missing HTTP User-Agent request header sfeed_update will not send the "User-Agent" header by default for privacy -reasons. Some CDNs like Cloudflare don't like this and will block such HTTP -requests. +reasons. Some CDNs like Cloudflare or websites like Reddit.com don't like this +and will block such HTTP requests. A custom User-Agent can be set by using the curl -H option, like so: -- cgit v1.2.3 From 229f33c4a66b8d4c60bfe39a068c34bf095f5728 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 9 Jun 2023 14:45:57 +0200 Subject: sfeedrc.5: improve example by setting a different User-Agent This is useful for sites that block clients that don't have a User-Agent header set. --- sfeedrc.5 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sfeedrc.5 b/sfeedrc.5 index 16e0641..2be5fc9 100644 --- a/sfeedrc.5 +++ b/sfeedrc.5 @@ -166,8 +166,8 @@ file: .Bd -literal # fetch(name, url, feedfile) fetch() { - # allow for 1 redirect, hide User-Agent, timeout is 15 seconds. - curl -L --max-redirs 1 -H "User-Agent:" -f -s -m 15 \e + # allow for 1 redirect, set User-Agent, timeout is 15 seconds. + curl -L --max-redirs 1 -H "User-Agent: sfeed" -f -s -m 15 \e "$2" 2>/dev/null } .Ed -- cgit v1.2.3 From 4601a1d688d05d300308d2bd861e16c1c81787a3 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sat, 1 Jul 2023 12:51:04 +0200 Subject: sfeedrc.5: remove branding (shaken, not stirred) --- sfeedrc.5 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sfeedrc.5 b/sfeedrc.5 index 2be5fc9..f32a0f2 100644 --- a/sfeedrc.5 +++ b/sfeedrc.5 @@ -1,4 +1,4 @@ -.Dd June 9, 2023 +.Dd July 1, 2023 .Dt SFEEDRC 5 .Os .Sh NAME @@ -167,7 +167,7 @@ file: # fetch(name, url, feedfile) fetch() { # allow for 1 redirect, set User-Agent, timeout is 15 seconds. - curl -L --max-redirs 1 -H "User-Agent: sfeed" -f -s -m 15 \e + curl -L --max-redirs 1 -H "User-Agent: 007" -f -s -m 15 \e "$2" 2>/dev/null } .Ed -- cgit v1.2.3 From 7afd4987e578631e41333867aa702e326cab87f1 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 7 Jul 2023 11:10:01 +0200 Subject: sfeed_curses: move one line down when marking an item as read or unread I don't mind either behaviour, but it has been suggested by a few people. For example the mutt mail client also has this behaviour. --- sfeed_curses.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sfeed_curses.c b/sfeed_curses.c index 0ce9a4e..95421fd 100644 --- a/sfeed_curses.c +++ b/sfeed_curses.c @@ -2337,6 +2337,7 @@ nextpage: if (selpane == PaneItems && panes[selpane].nrows) { p = &panes[selpane]; markread(p, p->pos, p->pos, ch == 'r'); + pane_scrolln(&panes[selpane], +1); } break; case 's': /* toggle layout between monocle or non-monocle */ -- cgit v1.2.3 From c63b60816ff996c89a07131da411269152852cd7 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 7 Jul 2023 11:43:41 +0200 Subject: sfeedrc.5: document each feed name should be unique Asked via e-mail. This confused a person which converted an OPML file which yielded an sfeedrc with duplicate names or multiple "unnamed" entries. This caused sfeed_update to fail, because it tried to merge files with the same name and causing a race-condition because the temporary file with the same name was already moved. --- sfeedrc.5 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sfeedrc.5 b/sfeedrc.5 index f32a0f2..7aabe75 100644 --- a/sfeedrc.5 +++ b/sfeedrc.5 @@ -1,4 +1,4 @@ -.Dd July 1, 2023 +.Dd July 7, 2023 .Dt SFEEDRC 5 .Os .Sh NAME @@ -37,6 +37,9 @@ Name of the feed, this is also used as the filename for the TAB-separated feed file. The feed name cannot contain the '/' character because it is a path separator, they will be replaced with '_'. +Each +.Fa name +should be unique. .It Fa feedurl URL to fetch the RSS/Atom data from, usually a HTTP or HTTPS URL. .It Op Fa basesiteurl -- cgit v1.2.3 From 92e833940f8a418157e84f8c1827c40a6a8a44c3 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 7 Jul 2023 11:47:31 +0200 Subject: sfeed_markread: fail early if creating a temporary file failed --- sfeed_markread | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sfeed_markread b/sfeed_markread index b262bdd..cad71bc 100755 --- a/sfeed_markread +++ b/sfeed_markread @@ -18,7 +18,7 @@ read) cat >> "${urlfile}" ;; unread) - tmp=$(mktemp) + tmp="$(mktemp)" || exit 1 trap "rm -f ${tmp}" EXIT [ -f "${urlfile}" ] || touch "${urlfile}" 2>/dev/null LC_ALL=C awk -F '\t' ' -- cgit v1.2.3 From f2e7a3fca1c05599456a60494fff61bb7b1ecf4e Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Mon, 31 Jul 2023 18:58:40 +0200 Subject: fix typo: current -> currently Reported by Allan Wind, thanks! --- sfeed_curses.1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sfeed_curses.1 b/sfeed_curses.1 index 56b2a92..4b2afbd 100644 --- a/sfeed_curses.1 +++ b/sfeed_curses.1 @@ -141,12 +141,12 @@ This will only work when .Ev SFEED_URL_FILE is set. .It f -Mark all items of the current loaded feed as read. +Mark all items of the currently loaded feed as read. This will only work when .Ev SFEED_URL_FILE is set. .It F -Mark all items of the current loaded feed as unread. +Mark all items of the currently loaded feed as unread. This will only work when .Ev SFEED_URL_FILE is set. @@ -325,7 +325,7 @@ Toggle showing only feeds with new items in the sidebar ('t' keybind). .Pp Go to the first row in the current panel ('g' keybind). .Pp -Load the current selected feed ('o' keybind'). +Load the currently selected feed ('o' keybind'). .It Set a file to use for managing read and unread items. This is a plain-text file containing a list of read URLs, one URL per line. -- cgit v1.2.3 From d6eb193ec08f576d60cfe0e9f79e43fdb9c9634a Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Tue, 1 Aug 2023 18:55:03 +0200 Subject: sfeed_json.1: make the formatting consistent with other similar man pages Such as sfeed_atom.1 or sfeed_twtxt.1 --- sfeed_json.1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sfeed_json.1 b/sfeed_json.1 index 8bd6ab0..4331bd7 100644 --- a/sfeed_json.1 +++ b/sfeed_json.1 @@ -1,4 +1,4 @@ -.Dd March 8, 2023 +.Dd August 1, 2023 .Dt SFEED_JSON 1 .Os .Sh NAME @@ -23,6 +23,7 @@ If no .Ar file arguments are specified and so the data is read from stdin then the feed name is empty. +.Pp If .Nm is reading from one or more -- cgit v1.2.3 From 3eb8fb2bb4fec18cc3e82b52086368bc837a41bd Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Tue, 1 Aug 2023 18:55:49 +0200 Subject: sfeed_curses.1: fix 2 typos --- sfeed_curses.1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sfeed_curses.1 b/sfeed_curses.1 index 4b2afbd..d40d453 100644 --- a/sfeed_curses.1 +++ b/sfeed_curses.1 @@ -1,4 +1,4 @@ -.Dd December 20, 2022 +.Dd August 1, 2023 .Dt SFEED_CURSES 1 .Os .Sh NAME @@ -318,14 +318,14 @@ Which does the following: .It Set commands to execute automatically: .Pp -Set the current layout to a horizontal mode ('2' keybind'). +Set the current layout to a horizontal mode ('2' keybind). Showing a feeds sidebar on the top and the feed items on the bottom. .Pp Toggle showing only feeds with new items in the sidebar ('t' keybind). .Pp Go to the first row in the current panel ('g' keybind). .Pp -Load the currently selected feed ('o' keybind'). +Load the currently selected feed ('o' keybind). .It Set a file to use for managing read and unread items. This is a plain-text file containing a list of read URLs, one URL per line. -- cgit v1.2.3 From 5e9dfb2a28a0305abb4e27df3b15214588c88468 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Tue, 1 Aug 2023 18:56:06 +0200 Subject: sfeed_update.1: remove mentioning of / in path names here This is documented in the sfeedrc.5 also where it is more suitable. --- sfeed_update.1 | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sfeed_update.1 b/sfeed_update.1 index 5e01589..ed61726 100644 --- a/sfeed_update.1 +++ b/sfeed_update.1 @@ -1,4 +1,4 @@ -.Dd December 15, 2022 +.Dd August 1, 2023 .Dt SFEED_UPDATE 1 .Os .Sh NAME @@ -42,8 +42,6 @@ format containing all items per feed. The .Nm script merges new items with this file. -The feed name cannot contain the '/' character because it is a path separator, -they will be replaced with '_'. .El .Sh ENVIRONMENT VARIABLES .Bl -tag -width Ds -- cgit v1.2.3 From ea04ae0101ae0607295ebea0a23364d31d1d37aa Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Wed, 9 Aug 2023 23:04:21 +0200 Subject: Makefile: change -lcurses to -lncurses for Gentoo in the comment Reported by commodorian, thanks! --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index fbe2aa1..9a02987 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ SFEED_CURSES_LDFLAGS = ${LDFLAGS} -lcurses # Gentoo Linux: some distros might also require -ltinfo and -D_DEFAULT_SOURCE # to prevent warnings about feature test macros. -#SFEED_CURSES_LDFLAGS = ${LDFLAGS} -lcurses -ltinfo +#SFEED_CURSES_LDFLAGS = ${LDFLAGS} -lncurses -ltinfo # FreeBSD: unset feature test macros for SIGWINCH etc. #SFEED_CURSES_CPPFLAGS = -- cgit v1.2.3 From 21a263cb27aeaf02b4a0a0319f435fac92f1ea28 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Tue, 15 Aug 2023 19:10:51 +0200 Subject: improve wording and small typos --- sfeed.c | 6 +++--- sfeed_curses.c | 2 +- sfeed_opml_import.c | 2 +- sfeed_web.c | 2 +- sfeed_xmlenc.c | 2 +- xml.c | 4 ++-- xml.h | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/sfeed.c b/sfeed.c index 888a575..cdd528c 100644 --- a/sfeed.c +++ b/sfeed.c @@ -724,8 +724,8 @@ xmlattr(XMLParser *p, const char *t, size_t tl, const char *n, size_t nl, if (!ctx.tag.id) return; - /* content-type may be: Atom: text, xhtml, html or mime-type. - MRSS (media:description): plain, html. */ + /* content-type may be for Atom: text, xhtml, html or a mime-type. + for MRSS (media:description): plain, html. */ if (ISCONTENTTAG(ctx)) { if (isattr(n, nl, STRP("type"))) string_append(&attrtype, v, vl); @@ -1073,7 +1073,7 @@ main(int argc, char *argv[]) parser.xmltagstart = xmltagstart; parser.xmltagstartparsed = xmltagstartparsed; - /* NOTE: getnext is defined in xml.h for inline optimization */ + /* NOTE: GETNEXT is defined in xml.h for inline optimization */ xml_parse(&parser); checkfileerror(stdin, "", 'r'); diff --git a/sfeed_curses.c b/sfeed_curses.c index 95421fd..0434812 100644 --- a/sfeed_curses.c +++ b/sfeed_curses.c @@ -1875,7 +1875,7 @@ markread(struct pane *p, off_t from, off_t to, int isread) _exit(status); default: /* waitpid() and block on process status change, - fail if exit statuscode was unavailable or non-zero */ + fail if the exit status code was unavailable or non-zero */ if (waitpid(pid, &status, 0) <= 0 || status) break; diff --git a/sfeed_opml_import.c b/sfeed_opml_import.c index 9922133..ce33aac 100644 --- a/sfeed_opml_import.c +++ b/sfeed_opml_import.c @@ -96,7 +96,7 @@ main(void) "# list of feeds to fetch:\n" "feeds() {\n" " # feed [basesiteurl] [encoding]\n", stdout); - /* NOTE: getnext is defined in xml.h for inline optimization */ + /* NOTE: GETNEXT is defined in xml.h for inline optimization */ xml_parse(&parser); fputs("}\n", stdout); diff --git a/sfeed_web.c b/sfeed_web.c index 0082f2d..e25e91c 100644 --- a/sfeed_web.c +++ b/sfeed_web.c @@ -132,7 +132,7 @@ main(int argc, char *argv[]) parser.xmltagstart = xmltagstart; parser.xmltagstartparsed = xmltagstartparsed; - /* NOTE: getnext is defined in xml.h for inline optimization */ + /* NOTE: GETNEXT is defined in xml.h for inline optimization */ xml_parse(&parser); checkfileerror(stdin, "", 'r'); diff --git a/sfeed_xmlenc.c b/sfeed_xmlenc.c index 7fc93ae..461c047 100644 --- a/sfeed_xmlenc.c +++ b/sfeed_xmlenc.c @@ -52,7 +52,7 @@ main(void) parser.xmlattrend = xmlattrend; parser.xmltagstart = xmltagstart; - /* NOTE: getnext is defined in xml.h for inline optimization */ + /* NOTE: GETNEXT is defined in xml.h for inline optimization */ xml_parse(&parser); checkfileerror(stdin, "", 'r'); diff --git a/xml.c b/xml.c index a82053e..1524d1f 100644 --- a/xml.c +++ b/xml.c @@ -317,7 +317,7 @@ xml_parse(XMLParser *x) x->taglen = 1; x->isshorttag = isend = 0; - /* treat processing instruction as shorttag, don't strip "?" prefix. */ + /* treat processing instruction as short tag, don't strip "?" prefix. */ if (c == '?') { x->isshorttag = 1; } else if (c == '/') { @@ -346,7 +346,7 @@ xml_parse(XMLParser *x) if (x->xmltagstartparsed) x->xmltagstartparsed(x, x->tag, x->taglen, x->isshorttag); } - /* call tagend for shortform or processing instruction */ + /* call tagend for short tag or processing instruction */ if (x->isshorttag) { if (x->xmltagend) x->xmltagend(x, x->tag, x->taglen, x->isshorttag); diff --git a/xml.h b/xml.h index 122726c..6f3cf71 100644 --- a/xml.h +++ b/xml.h @@ -30,7 +30,7 @@ typedef struct xmlparser { /* current tag */ char tag[1024]; size_t taglen; - /* current tag is in shortform ? */ + /* current tag is a short tag ? */ int isshorttag; /* current attribute name */ char name[1024]; -- cgit v1.2.3 From c43e677d80f5eae1e05bcb71d88008fb8ded8cab Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 18 Aug 2023 18:40:05 +0200 Subject: bump version to 1.9 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9a02987..b8f6f64 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .POSIX: NAME = sfeed -VERSION = 1.8 +VERSION = 1.9 # curses theme, see themes/ directory. SFEED_THEME = mono -- cgit v1.2.3 From a2af022b42beea7b8c29ca475f38efd10caf35e8 Mon Sep 17 00:00:00 2001 From: NRK Date: Thu, 21 Sep 2023 03:13:52 +0600 Subject: sfeed_gopher: mark function as static --- sfeed_gopher.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sfeed_gopher.c b/sfeed_gopher.c index c879864..f9ee9b8 100644 --- a/sfeed_gopher.c +++ b/sfeed_gopher.c @@ -11,7 +11,7 @@ static size_t linesize; static time_t comparetime; /* Escape characters in gopher, CR and LF are ignored */ -void +static void gophertext(FILE *fp, const char *s) { for (; *s; s++) { -- cgit v1.2.3 From db69f0f81d4796816c04e6330b963f907cbfded1 Mon Sep 17 00:00:00 2001 From: NRK Date: Thu, 21 Sep 2023 03:20:19 +0600 Subject: sfeed_curses: mark functions as static no reason for them not to be `static` and this also silences -Wmissing-prototypes warning (note: most of this patch was done by a sed(1) command.) --- sfeed_curses.c | 182 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 91 insertions(+), 91 deletions(-) diff --git a/sfeed_curses.c b/sfeed_curses.c index 0434812..3359340 100644 --- a/sfeed_curses.c +++ b/sfeed_curses.c @@ -139,18 +139,18 @@ struct items { size_t cap; /* available capacity */ }; -void alldirty(void); -void cleanup(void); -void draw(void); -int getsidebarsize(void); -void markread(struct pane *, off_t, off_t, int); -void pane_draw(struct pane *); -void sighandler(int); -void updategeom(void); -void updatesidebar(void); -void urls_free(struct urls *); -int urls_hasmatch(struct urls *, const char *); -void urls_read(struct urls *, const char *); +static void alldirty(void); +static void cleanup(void); +static void draw(void); +static int getsidebarsize(void); +static void markread(struct pane *, off_t, off_t, int); +static void pane_draw(struct pane *); +static void sighandler(int); +static void updategeom(void); +static void updatesidebar(void); +static void urls_free(struct urls *); +static int urls_hasmatch(struct urls *, const char *); +static void urls_read(struct urls *, const char *); static struct linebar linebar; static struct statusbar statusbar; @@ -190,7 +190,7 @@ static int piperia = 1; /* env variable: $SFEED_PIPER_INTERACTIVE */ static int yankeria = 0; /* env variable: $SFEED_YANKER_INTERACTIVE */ static int lazyload = 0; /* env variable: $SFEED_LAZYLOAD */ -int +static int ttywritef(const char *fmt, ...) { va_list ap; @@ -204,7 +204,7 @@ ttywritef(const char *fmt, ...) return n; } -int +static int ttywrite(const char *s) { if (!s) @@ -213,7 +213,7 @@ ttywrite(const char *s) } /* Print to stderr, call cleanup() and _exit(). */ -__dead void +__dead static void die(const char *fmt, ...) { va_list ap; @@ -234,7 +234,7 @@ die(const char *fmt, ...) _exit(1); } -void * +static void * erealloc(void *ptr, size_t size) { void *p; @@ -244,7 +244,7 @@ erealloc(void *ptr, size_t size) return p; } -void * +static void * ecalloc(size_t nmemb, size_t size) { void *p; @@ -254,7 +254,7 @@ ecalloc(size_t nmemb, size_t size) return p; } -char * +static char * estrdup(const char *s) { char *p; @@ -265,7 +265,7 @@ estrdup(const char *s) } /* Wrapper for tparm() which allows NULL parameter for str. */ -char * +static char * tparmnull(const char *str, long p1, long p2, long p3, long p4, long p5, long p6, long p7, long p8, long p9) { @@ -276,7 +276,7 @@ tparmnull(const char *str, long p1, long p2, long p3, long p4, long p5, long p6, } /* Counts column width of character string. */ -size_t +static size_t colw(const char *s) { wchar_t wc; @@ -308,7 +308,7 @@ colw(const char *s) /* Format `len` columns of characters. If string is shorter pad the rest with characters `pad`. */ -int +static int utf8pad(char *buf, size_t bufsiz, const char *s, size_t len, int pad) { wchar_t wc; @@ -373,13 +373,13 @@ utf8pad(char *buf, size_t bufsiz, const char *s, size_t len, int pad) return 0; } -void +static void resetstate(void) { ttywrite("\x1b""c"); /* rs1: reset title and state */ } -void +static void updatetitle(void) { unsigned long totalnew = 0, total = 0; @@ -392,32 +392,32 @@ updatetitle(void) ttywritef("\x1b]2;(%lu/%lu) - sfeed_curses\x1b\\", totalnew, total); } -void +static void appmode(int on) { ttywrite(tparmnull(on ? enter_ca_mode : exit_ca_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0)); } -void +static void mousemode(int on) { ttywrite(on ? "\x1b[?1000h" : "\x1b[?1000l"); /* xterm X10 mouse mode */ ttywrite(on ? "\x1b[?1006h" : "\x1b[?1006l"); /* extended SGR mouse mode */ } -void +static void cursormode(int on) { ttywrite(tparmnull(on ? cursor_normal : cursor_invisible, 0, 0, 0, 0, 0, 0, 0, 0, 0)); } -void +static void cursormove(int x, int y) { ttywrite(tparmnull(cursor_address, y, x, 0, 0, 0, 0, 0, 0, 0)); } -void +static void cursorsave(void) { /* do not save the cursor if it won't be restored anyway */ @@ -425,7 +425,7 @@ cursorsave(void) ttywrite(tparmnull(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); } -void +static void cursorrestore(void) { /* if the cursor cannot be hidden then move to a consistent position */ @@ -435,7 +435,7 @@ cursorrestore(void) cursormove(0, 0); } -void +static void attrmode(int mode) { switch (mode) { @@ -456,19 +456,19 @@ attrmode(int mode) } } -void +static void cleareol(void) { ttywrite(tparmnull(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0)); } -void +static void clearscreen(void) { ttywrite(tparmnull(clear_screen, 0, 0, 0, 0, 0, 0, 0, 0, 0)); } -void +static void cleanup(void) { struct sigaction sa; @@ -497,7 +497,7 @@ cleanup(void) sigaction(SIGWINCH, &sa, NULL); } -void +static void win_update(struct win *w, int width, int height) { if (width != w->width || height != w->height) @@ -506,7 +506,7 @@ win_update(struct win *w, int width, int height) w->height = height; } -void +static void resizewin(void) { struct winsize winsz; @@ -521,7 +521,7 @@ resizewin(void) alldirty(); } -void +static void init(void) { struct sigaction sa; @@ -561,7 +561,7 @@ init(void) sigaction(SIGWINCH, &sa, NULL); } -void +static void processexit(pid_t pid, int interactive) { struct sigaction sa; @@ -594,7 +594,7 @@ processexit(pid_t pid, int interactive) if `interactive` is 1 then cleanup and restore the tty and wait on the process. if 0 then don't do that and also write stdout and stderr to /dev/null. */ -void +static void pipeitem(const char *cmd, struct item *item, int field, int interactive) { FILE *fp; @@ -634,7 +634,7 @@ pipeitem(const char *cmd, struct item *item, int field, int interactive) } } -void +static void forkexec(char *argv[], int interactive) { pid_t pid; @@ -658,7 +658,7 @@ forkexec(char *argv[], int interactive) } } -struct row * +static struct row * pane_row_get(struct pane *p, off_t pos) { if (pos < 0 || pos >= p->nrows) @@ -669,7 +669,7 @@ pane_row_get(struct pane *p, off_t pos) return p->rows + pos; } -char * +static char * pane_row_text(struct pane *p, struct row *row) { /* custom formatter */ @@ -678,7 +678,7 @@ pane_row_text(struct pane *p, struct row *row) return row->text; } -int +static int pane_row_match(struct pane *p, struct row *row, const char *s) { if (p->row_match) @@ -686,7 +686,7 @@ pane_row_match(struct pane *p, struct row *row, const char *s) return (strcasestr(pane_row_text(p, row), s) != NULL); } -void +static void pane_row_draw(struct pane *p, off_t pos, int selected) { struct row *row; @@ -719,7 +719,7 @@ pane_row_draw(struct pane *p, off_t pos, int selected) cursorrestore(); } -void +static void pane_setpos(struct pane *p, off_t pos) { if (pos < 0) @@ -743,7 +743,7 @@ pane_setpos(struct pane *p, off_t pos) p->pos = pos; } -void +static void pane_scrollpage(struct pane *p, int pages) { off_t pos; @@ -761,13 +761,13 @@ pane_scrollpage(struct pane *p, int pages) } } -void +static void pane_scrolln(struct pane *p, int n) { pane_setpos(p, p->pos + n); } -void +static void pane_setfocus(struct pane *p, int on) { if (p->focused != on) { @@ -776,7 +776,7 @@ pane_setfocus(struct pane *p, int on) } } -void +static void pane_draw(struct pane *p) { off_t pos, y; @@ -793,7 +793,7 @@ pane_draw(struct pane *p) pane_row_draw(p, y + pos, (y + pos) == p->pos); } -void +static void setlayout(int n) { if (layout != LayoutMonocle) @@ -801,7 +801,7 @@ setlayout(int n) layout = n; } -void +static void updategeom(void) { int h, w, x = 0, y = 0; @@ -875,7 +875,7 @@ updategeom(void) alldirty(); } -void +static void scrollbar_setfocus(struct scrollbar *s, int on) { if (s->focused != on) { @@ -884,7 +884,7 @@ scrollbar_setfocus(struct scrollbar *s, int on) } } -void +static void scrollbar_update(struct scrollbar *s, off_t pos, off_t nrows, int pageheight) { int tickpos = 0, ticksize = 0; @@ -909,7 +909,7 @@ scrollbar_update(struct scrollbar *s, off_t pos, off_t nrows, int pageheight) s->ticksize = ticksize; } -void +static void scrollbar_draw(struct scrollbar *s) { off_t y; @@ -948,7 +948,7 @@ scrollbar_draw(struct scrollbar *s) cursorrestore(); } -int +static int readch(void) { unsigned char b; @@ -982,7 +982,7 @@ readch(void) } } -char * +static char * lineeditor(void) { char *input = NULL; @@ -1035,7 +1035,7 @@ lineeditor(void) return input; } -char * +static char * uiprompt(int x, int y, char *fmt, ...) { va_list ap; @@ -1065,7 +1065,7 @@ uiprompt(int x, int y, char *fmt, ...) return input; } -void +static void linebar_draw(struct linebar *b) { int i; @@ -1086,7 +1086,7 @@ linebar_draw(struct linebar *b) cursorrestore(); } -void +static void statusbar_draw(struct statusbar *s) { if (!s->dirty) @@ -1106,7 +1106,7 @@ statusbar_draw(struct statusbar *s) cursorrestore(); } -void +static void statusbar_update(struct statusbar *s, const char *text) { if (s->text && !strcmp(s->text, text)) @@ -1118,7 +1118,7 @@ statusbar_update(struct statusbar *s, const char *text) } /* Line to item, modifies and splits line in-place. */ -int +static int linetoitem(char *line, struct item *item) { char *fields[FieldLast]; @@ -1144,7 +1144,7 @@ linetoitem(char *line, struct item *item) return 0; } -void +static void feed_items_free(struct items *items) { size_t i; @@ -1159,7 +1159,7 @@ feed_items_free(struct items *items) items->cap = 0; } -void +static void feed_items_get(struct feed *f, FILE *fp, struct items *itemsret) { struct item *item, *items = NULL; @@ -1207,7 +1207,7 @@ feed_items_get(struct feed *f, FILE *fp, struct items *itemsret) free(line); } -void +static void updatenewitems(struct feed *f) { struct pane *p; @@ -1231,7 +1231,7 @@ updatenewitems(struct feed *f) f->total = p->nrows; } -void +static void feed_load(struct feed *f, FILE *fp) { /* static, reuse local buffers */ @@ -1252,7 +1252,7 @@ feed_load(struct feed *f, FILE *fp) updatenewitems(f); } -void +static void feed_count(struct feed *f, FILE *fp) { char *fields[FieldLast]; @@ -1281,7 +1281,7 @@ feed_count(struct feed *f, FILE *fp) free(line); } -void +static void feed_setenv(struct feed *f) { if (f && f->path) @@ -1291,7 +1291,7 @@ feed_setenv(struct feed *f) } /* Change feed, have one file open, reopen file if needed. */ -void +static void feeds_set(struct feed *f) { if (curfeed) { @@ -1311,7 +1311,7 @@ feeds_set(struct feed *f) curfeed = f; } -void +static void feeds_load(struct feed *feeds, size_t nfeeds) { struct feed *f; @@ -1354,7 +1354,7 @@ feeds_load(struct feed *feeds, size_t nfeeds) } /* find row position of the feed if visible, else return -1 */ -off_t +static off_t feeds_row_get(struct pane *p, struct feed *f) { struct row *row; @@ -1371,7 +1371,7 @@ feeds_row_get(struct pane *p, struct feed *f) return -1; } -void +static void feeds_reloadall(void) { struct pane *p; @@ -1400,7 +1400,7 @@ feeds_reloadall(void) pane_setpos(p, 0); } -void +static void feed_open_selected(struct pane *p) { struct feed *f; @@ -1424,7 +1424,7 @@ feed_open_selected(struct pane *p) } } -void +static void feed_plumb_selected_item(struct pane *p, int field) { struct row *row; @@ -1441,7 +1441,7 @@ feed_plumb_selected_item(struct pane *p, int field) forkexec(cmd, plumberia); } -void +static void feed_pipe_selected_item(struct pane *p) { struct row *row; @@ -1454,7 +1454,7 @@ feed_pipe_selected_item(struct pane *p) pipeitem(pipercmd, item, -1, piperia); } -void +static void feed_yank_selected_item(struct pane *p, int field) { struct row *row; @@ -1467,7 +1467,7 @@ feed_yank_selected_item(struct pane *p, int field) } /* calculate optimal (default) size */ -int +static int getsidebarsizedefault(void) { struct feed *feed; @@ -1500,7 +1500,7 @@ getsidebarsizedefault(void) return 0; } -int +static int getsidebarsize(void) { int size; @@ -1510,7 +1510,7 @@ getsidebarsize(void) return size; } -void +static void adjustsidebarsize(int n) { int size; @@ -1533,7 +1533,7 @@ adjustsidebarsize(int n) } } -void +static void updatesidebar(void) { struct pane *p; @@ -1585,7 +1585,7 @@ updatesidebar(void) p->pos = p->nrows - 1; } -void +static void sighandler(int signo) { switch (signo) { @@ -1597,7 +1597,7 @@ sighandler(int signo) } } -void +static void alldirty(void) { win.dirty = 1; @@ -1609,7 +1609,7 @@ alldirty(void) statusbar.dirty = 1; } -void +static void draw(void) { struct row *row; @@ -1643,7 +1643,7 @@ draw(void) statusbar_draw(&statusbar); } -void +static void mousereport(int button, int release, int keymask, int x, int y) { struct pane *p; @@ -1715,7 +1715,7 @@ mousereport(int button, int release, int keymask, int x, int y) } /* Custom formatter for feed row. */ -char * +static char * feed_row_format(struct pane *p, struct row *row) { /* static, reuse local buffers */ @@ -1756,7 +1756,7 @@ feed_row_format(struct pane *p, struct row *row) return text; } -int +static int feed_row_match(struct pane *p, struct row *row, const char *s) { struct feed *feed; @@ -1766,7 +1766,7 @@ feed_row_match(struct pane *p, struct row *row, const char *s) return (strcasestr(feed->name, s) != NULL); } -struct row * +static struct row * item_row_get(struct pane *p, off_t pos) { struct row *itemrow; @@ -1803,7 +1803,7 @@ item_row_get(struct pane *p, off_t pos) } /* Custom formatter for item row. */ -char * +static char * item_row_format(struct pane *p, struct row *row) { /* static, reuse local buffers */ @@ -1835,7 +1835,7 @@ item_row_format(struct pane *p, struct row *row) return text; } -void +static void markread(struct pane *p, off_t from, off_t to, int isread) { struct row *row; @@ -1898,13 +1898,13 @@ markread(struct pane *p, off_t from, off_t to, int isread) } } -int +static int urls_cmp(const void *v1, const void *v2) { return strcmp(*((char **)v1), *((char **)v2)); } -void +static void urls_free(struct urls *urls) { while (urls->len > 0) { @@ -1917,14 +1917,14 @@ urls_free(struct urls *urls) urls->cap = 0; } -int +static int urls_hasmatch(struct urls *urls, const char *url) { return (urls->len && bsearch(&url, urls->items, urls->len, sizeof(char *), urls_cmp)); } -void +static void urls_read(struct urls *urls, const char *urlfile) { FILE *fp; -- cgit v1.2.3 From da8adbc02e4667a68077e5f0cab8548e2bb81c1b Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Thu, 30 Nov 2023 17:59:18 +0100 Subject: sfeed_json: fix name of url field for attachments This was a typo: "url:" should be "url". --- sfeed_json.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sfeed_json.c b/sfeed_json.c index 4fe5942..4e1f52c 100644 --- a/sfeed_json.c +++ b/sfeed_json.c @@ -121,7 +121,7 @@ printfeed(FILE *fp, const char *feedname) } if (fields[FieldEnclosure][0]) { - fputs(",\n\t\"attachments\": [{\"url:\": \"", stdout); + fputs(",\n\t\"attachments\": [{\"url\": \"", stdout); printfield(fields[FieldEnclosure]); fputs("\"}]", stdout); } -- cgit v1.2.3 From 63308527f5197ddbcad6b06c5c1bbaf12f997e57 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sat, 9 Dec 2023 21:51:45 +0100 Subject: improve compatibility with zsh as a non-interactive shell In zsh the variables $path and $status are special. https://zsh.sourceforge.io/Doc/Release/Parameters.html#index-path https://zsh.sourceforge.io/Doc/Release/Parameters.html#index-status (No promises I will keep up with this insanity in the future though) --- README | 6 +++--- sfeed_opml_export | 8 ++++---- sfeed_update | 12 ++++++------ 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README b/README index 8098a76..85a3fb9 100644 --- a/README +++ b/README @@ -752,12 +752,12 @@ sfeed_update_xargs shellscript: mkdir -p "${sfeedpath}" # print feeds for parallel processing with xargs. feeds | SFEED_UPDATE_CHILD="1" xargs -r -0 -P "${maxjobs}" -L 6 "$(readlink -f "$0")" - status=$? + statuscode=$? # check error exit status indicator for parallel jobs. - test -f "${sfeedtmpdir}/ok" || status=1 + test -f "${sfeedtmpdir}/ok" || statuscode=1 # cleanup temporary files etc. cleanup - exit ${status} + exit ${statuscode} - - - diff --git a/sfeed_opml_export b/sfeed_opml_export index 7c96d5d..6420d5e 100755 --- a/sfeed_opml_export +++ b/sfeed_opml_export @@ -7,16 +7,16 @@ loadconfig() { if [ "$1" != "" ]; then # get absolute path of config file required for including. config="$1" - path=$(readlink -f "${config}" 2>/dev/null) + configpath=$(readlink -f "${config}" 2>/dev/null) else # default config location. config="$HOME/.sfeed/sfeedrc" - path="${config}" + configpath="${config}" fi # config is loaded here to be able to override $sfeedpath or functions. - if [ -r "${path}" ]; then - . "${path}" + if [ -r "${configpath}" ]; then + . "${configpath}" else printf "Configuration file \"%s\" cannot be read.\n" "${config}" >&2 echo "See the sfeedrc.example file or the sfeedrc(5) man page for an example." >&2 diff --git a/sfeed_update b/sfeed_update index dadea9b..2b7d89b 100755 --- a/sfeed_update +++ b/sfeed_update @@ -16,16 +16,16 @@ loadconfig() { if [ "$1" != "" ]; then # get absolute path of config file required for including. config="$1" - path=$(readlink -f "${config}" 2>/dev/null) + configpath=$(readlink -f "${config}" 2>/dev/null) else # default config location. config="$HOME/.sfeed/sfeedrc" - path="${config}" + configpath="${config}" fi # config is loaded here to be able to override $sfeedpath or functions. - if [ -r "${path}" ]; then - . "${path}" + if [ -r "${configpath}" ]; then + . "${configpath}" else printf "Configuration file \"%s\" cannot be read.\n" "${config}" >&2 echo "See the sfeedrc.example file or the sfeedrc(5) man page for an example." >&2 @@ -214,12 +214,12 @@ main() { [ ${signo} -eq 0 ] && wait # check error exit status indicator for parallel jobs. [ -f "${sfeedtmpdir}/ok" ] - status=$? + statuscode=$? # cleanup temporary files etc. cleanup # on signal SIGINT and SIGTERM exit with signal number + 128. [ ${signo} -ne 0 ] && exit $((signo+128)) - exit ${status} + exit ${statuscode} } [ "${SFEED_UPDATE_INCLUDE}" = "1" ] || main "$@" -- cgit v1.2.3 From a2aa09baf8a1f4a98313f8691d999eaff8b4ceea Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 15 Dec 2023 13:46:21 +0100 Subject: sfeed_update: add die() function for exit and cleanup, respect $TMPDIR - Add a die() helper function to cleanup and exit. - NOTE that with an empty sfeedtmpdir the case rm -rf "" is fine. - Respect $TMPDIR for creating temporary files like many UNIX tools do. - Fix: when creating "${sfeedtmpdir}/ok" fails for some reason cleanup the whole temporary directory as well. - Fix: when the feeds() function is not defined exit with status code 1 (this was incorrectly status code 0). Reproduce: sfeed_update /dev/null; echo $? --- sfeed_update | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/sfeed_update b/sfeed_update index 2b7d89b..014985c 100755 --- a/sfeed_update +++ b/sfeed_update @@ -29,7 +29,7 @@ loadconfig() { else printf "Configuration file \"%s\" cannot be read.\n" "${config}" >&2 echo "See the sfeedrc.example file or the sfeedrc(5) man page for an example." >&2 - exit 1 + die fi } @@ -178,6 +178,14 @@ cleanup() { rm -rf "${sfeedtmpdir}" } +# die(statuscode) +die() { + statuscode="${1:-1}" # default: exit 1 + # cleanup temporary files etc. + cleanup + exit "${statuscode}" +} + sighandler() { signo="$1" # ignore TERM signal for myself. @@ -189,6 +197,7 @@ sighandler() { feeds() { printf "Configuration file \"%s\" is invalid or does not contain a \"feeds\" function.\n" "${config}" >&2 echo "See sfeedrc.example for an example." >&2 + die } main() { @@ -203,9 +212,9 @@ main() { # load config file. loadconfig "$1" # fetch feeds and store in temporary directory. - sfeedtmpdir="$(mktemp -d '/tmp/sfeed_XXXXXX')" || exit 1 + sfeedtmpdir="$(mktemp -p "${TMPDIR:-/tmp}" -d 'sfeed_XXXXXX')" || die mkdir -p "${sfeedtmpdir}/feeds" - touch "${sfeedtmpdir}/ok" || exit 1 + touch "${sfeedtmpdir}/ok" || die # make sure path exists. mkdir -p "${sfeedpath}" # fetch feeds specified in config file. @@ -215,11 +224,9 @@ main() { # check error exit status indicator for parallel jobs. [ -f "${sfeedtmpdir}/ok" ] statuscode=$? - # cleanup temporary files etc. - cleanup # on signal SIGINT and SIGTERM exit with signal number + 128. - [ ${signo} -ne 0 ] && exit $((signo+128)) - exit ${statuscode} + [ ${signo} -ne 0 ] && die $((signo+128)) + die ${statuscode} } [ "${SFEED_UPDATE_INCLUDE}" = "1" ] || main "$@" -- cgit v1.2.3 From 7526bd914c314dd86cc7386bbfaf55ad0d130130 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 15 Dec 2023 13:48:49 +0100 Subject: README: sfeed_opml_export uses awk --- README | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README b/README index 85a3fb9..8ea13ed 100644 --- a/README +++ b/README @@ -108,7 +108,8 @@ Optional dependencies - POSIX sh(1), used by sfeed_update(1) and sfeed_opml_export(1). - POSIX utilities such as awk(1) and sort(1), - used by sfeed_content(1), sfeed_markread(1) and sfeed_update(1). + used by sfeed_content(1), sfeed_markread(1), sfeed_opml_export(1) and + sfeed_update(1). - curl(1) binary: https://curl.haxx.se/ , used by sfeed_update(1), but can be replaced with any tool like wget(1), OpenBSD ftp(1) or hurl(1): https://git.codemadness.org/hurl/ -- cgit v1.2.3 From 9754fe74f7b5c0600cc41eef8c6f5c8305a74a18 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Tue, 26 Dec 2023 15:17:17 +0100 Subject: sfeed_update: disallow using a directory as a config file Tested on NetBSD 5.1: evaluating directories as config files could allow garbage, so disallow it. Devices / fifo, etc are still allowed. --- sfeed_update | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sfeed_update b/sfeed_update index 014985c..79f23ee 100755 --- a/sfeed_update +++ b/sfeed_update @@ -24,7 +24,7 @@ loadconfig() { fi # config is loaded here to be able to override $sfeedpath or functions. - if [ -r "${configpath}" ]; then + if [ -r "${configpath}" ] && [ ! -d "${configpath}" ]; then . "${configpath}" else printf "Configuration file \"%s\" cannot be read.\n" "${config}" >&2 -- cgit v1.2.3 From 62bfed65ca91c34ea24b81b191c23d4542a7075b Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Tue, 26 Dec 2023 15:23:15 +0100 Subject: sfeed_update: mktemp: improve compatibility with older systems Tested on NetBSD 5.1: - mktemp -p doesn't exist there yet. - mktemp without any arguments/template doesnt work - mktemp -d without any arguments/template doesnt work --- sfeed_update | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sfeed_update b/sfeed_update index 79f23ee..0628e2e 100755 --- a/sfeed_update +++ b/sfeed_update @@ -212,7 +212,7 @@ main() { # load config file. loadconfig "$1" # fetch feeds and store in temporary directory. - sfeedtmpdir="$(mktemp -p "${TMPDIR:-/tmp}" -d 'sfeed_XXXXXX')" || die + sfeedtmpdir="$(mktemp -d "${TMPDIR:-/tmp}/sfeed_XXXXXX")" || die mkdir -p "${sfeedtmpdir}/feeds" touch "${sfeedtmpdir}/ok" || die # make sure path exists. -- cgit v1.2.3 From cdb8f7feb135adf6f18e389b4bbf47886089474a Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Tue, 26 Dec 2023 15:59:39 +0100 Subject: sfeed_update: use xargs -P -0 Some of the options, like -P are as of writing (2023) non-POSIX: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/xargs.html. However many systems support this useful extension for many years now. Some historic context: The xargs -0 option was added on 1996-06-11, about a year after the NetBSD import (over 27 years ago at the time of writing): http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/xargs/xargs.c?rev=1.2&content-type=text/x-cvsweb-markup On OpenBSD the xargs -P option was added on 2003-12-06 by syncing the FreeBSD code: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/xargs/xargs.c?rev=1.14&content-type=text/x-cvsweb-markup Looking at the imported git history log of GNU findutils (which has xargs), the very first commit already had the -0 and -P option on Sun Feb 4 20:35:16 1996 +0000. Tested on many systems, old and new, some notable: - OpenBSD 7.4 - Void Linux - FreeBSD 12 - NetBSD 9.3 - HaikuOS (uses GNU tools). - Slackware 11 - OpenBSD 3.8 - NetBSD 5.1 Some shells: - oksh - bash - dash - zsh During testing there are some incompatibilities found in parsing the fields so the arguments are passed as one argument which is split later on by the child program. --- sfeed_update | 48 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/sfeed_update b/sfeed_update index 0628e2e..309b348 100755 --- a/sfeed_update +++ b/sfeed_update @@ -163,14 +163,12 @@ _feed() { # fetch and process a feed in parallel. # feed(name, feedurl, [basesiteurl], [encoding]) feed() { - # wait until ${maxjobs} are finished: will stall the queue if an item - # is slow, but it is portable. - [ ${signo} -ne 0 ] && return - [ $((curjobs % maxjobs)) -eq 0 ] && wait - [ ${signo} -ne 0 ] && return - curjobs=$((curjobs + 1)) - - _feed "$@" & + # Job parameters for xargs. + # Specify fields as a single parameter separated by the NUL separator. + # These fields are split later by the child process, this allows xargs + # with empty fields across many implementations. + printf '%s\037%s\037%s\037%s\037%s\037%s\0' \ + "${config}" "${sfeedtmpdir}" "$1" "$2" "$3" "$4" } cleanup() { @@ -201,8 +199,6 @@ feeds() { } main() { - # job counter. - curjobs=0 # signal number received for parent. signo=0 # SIGINT: signal to interrupt parent. @@ -217,16 +213,36 @@ main() { touch "${sfeedtmpdir}/ok" || die # make sure path exists. mkdir -p "${sfeedpath}" - # fetch feeds specified in config file. - feeds - # wait till all feeds are fetched (concurrently). - [ ${signo} -eq 0 ] && wait - # check error exit status indicator for parallel jobs. - [ -f "${sfeedtmpdir}/ok" ] + + # print feeds for parallel processing with xargs. + feeds > "${sfeedtmpdir}/jobs" || die + SFEED_UPDATE_CHILD="1" xargs -s 65535 -x -0 -P "${maxjobs}" -n 1 \ + "$(readlink -f "${argv0}")" < "${sfeedtmpdir}/jobs" statuscode=$? + + # check error exit status indicator for parallel jobs. + [ -f "${sfeedtmpdir}/ok" ] || statuscode=1 # on signal SIGINT and SIGTERM exit with signal number + 128. [ ${signo} -ne 0 ] && die $((signo+128)) die ${statuscode} } +# process a single feed. +# parameters are: config, tmpdir, name, feedurl, basesiteurl, encoding +if [ "${SFEED_UPDATE_CHILD}" = "1" ]; then + IFS="" # "\037" + [ "$1" = "" ] && exit 0 # must have an argument set + printf '%s\n' "$1" | \ + while read -r config tmpdir name feedurl basesiteurl encoding; do + # load config file, sets $config. + loadconfig "${config}" + sfeedtmpdir="${tmpdir}" + _feed "${name}" "${feedurl}" "${basesiteurl}" "${encoding}" + exit "$?" + done + exit 0 +fi + +# ...else parent mode: +argv0="$0" # remember $0, in shells like zsh $0 is the function name. [ "${SFEED_UPDATE_INCLUDE}" = "1" ] || main "$@" -- cgit v1.2.3 From 89dbf565de7b980d2b336c58633b7a27681adaf5 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Tue, 26 Dec 2023 16:37:21 +0100 Subject: README: update dependency information for xargs for sfeed_update Remove the parallel xargs example because it is now the default. --- README | 58 ++-------------------------------------------------------- 1 file changed, 2 insertions(+), 56 deletions(-) diff --git a/README b/README index 8ea13ed..47d4d76 100644 --- a/README +++ b/README @@ -117,6 +117,8 @@ Optional dependencies used by sfeed_update(1). If the text in your RSS/Atom feeds are already UTF-8 encoded then you don't need this. For a minimal iconv implementation: https://git.etalabs.net/cgit/noxcuse/tree/src/iconv.c +- xargs with support for the -P and -0 option, + used by sfeed_update(1). - mandoc for documentation: https://mdocml.bsd.lv/ - curses (typically ncurses), otherwise see minicurses.h, used by sfeed_curses(1). @@ -706,62 +708,6 @@ sfeedrc file and change the curl options "-L --max-redirs 0". - - - -Shellscript to update feeds in parallel more efficiently using xargs -P. - -It creates a queue of the feeds with its settings, then uses xargs to process -them in parallel using the common, but non-POSIX -P option. This is more -efficient than the more portable solution in sfeed_update which can stall a -batch of $maxjobs in the queue if one item is slow. - -sfeed_update_xargs shellscript: - - #!/bin/sh - # update feeds, merge with old feeds using xargs in parallel mode (non-POSIX). - - # include script and reuse its functions, but do not start main(). - SFEED_UPDATE_INCLUDE="1" . sfeed_update - # load config file, sets $config. - loadconfig "$1" - - # process a single feed. - # args are: config, tmpdir, name, feedurl, basesiteurl, encoding - if [ "${SFEED_UPDATE_CHILD}" = "1" ]; then - sfeedtmpdir="$2" - _feed "$3" "$4" "$5" "$6" - exit $? - fi - - # ...else parent mode: - - # feed(name, feedurl, basesiteurl, encoding) - feed() { - # workaround: *BSD xargs doesn't handle empty fields in the middle. - name="${1:-$$}" - feedurl="${2:-http://}" - basesiteurl="${3:-${feedurl}}" - encoding="$4" - - printf '%s\0%s\0%s\0%s\0%s\0%s\0' "${config}" "${sfeedtmpdir}" \ - "${name}" "${feedurl}" "${basesiteurl}" "${encoding}" - } - - # fetch feeds and store in temporary directory. - sfeedtmpdir="$(mktemp -d '/tmp/sfeed_XXXXXX')" || exit 1 - mkdir -p "${sfeedtmpdir}/feeds" - touch "${sfeedtmpdir}/ok" || exit 1 - # make sure path exists. - mkdir -p "${sfeedpath}" - # print feeds for parallel processing with xargs. - feeds | SFEED_UPDATE_CHILD="1" xargs -r -0 -P "${maxjobs}" -L 6 "$(readlink -f "$0")" - statuscode=$? - # check error exit status indicator for parallel jobs. - test -f "${sfeedtmpdir}/ok" || statuscode=1 - # cleanup temporary files etc. - cleanup - exit ${statuscode} - -- - - - Shellscript to handle URLs and enclosures in parallel using xargs -P. This can be used to download and process URLs for downloading podcasts, -- cgit v1.2.3 From 87d5c99ebfee6e2255bd057e0eb45f1631b8b987 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Tue, 26 Dec 2023 16:34:58 +0100 Subject: sfeedrc: bump default maxjobs from 8 to 16 --- sfeed_update | 2 +- sfeedrc.5 | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sfeed_update b/sfeed_update index 309b348..94b64df 100755 --- a/sfeed_update +++ b/sfeed_update @@ -7,7 +7,7 @@ sfeedpath="$HOME/.sfeed/feeds" # used for processing feeds concurrently: wait until ${maxjobs} amount of # feeds are finished at a time. -maxjobs=8 +maxjobs=16 # load config (evaluate shellscript). # loadconfig(configfile) diff --git a/sfeedrc.5 b/sfeedrc.5 index 7aabe75..7640a28 100644 --- a/sfeedrc.5 +++ b/sfeedrc.5 @@ -1,4 +1,4 @@ -.Dd July 7, 2023 +.Dd December 26, 2023 .Dt SFEEDRC 5 .Os .Sh NAME @@ -18,7 +18,7 @@ The default is can be used to change the amount of concurrent .Fn feed jobs. -The default is 8. +The default is 16. .El .Sh FUNCTIONS .Bl -tag -width Ds -- cgit v1.2.3 From 20bfe3d26d68d69539740110d3663b7853dad0e2 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Tue, 26 Dec 2023 16:38:14 +0100 Subject: LICENSE: bump year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index e0a41be..dccacab 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ ISC License -Copyright (c) 2011-2023 Hiltjo Posthuma +Copyright (c) 2011-2024 Hiltjo Posthuma Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above -- cgit v1.2.3 From 8bdb849e449c5236a1ef7e8b4b91186790f7fb29 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Wed, 27 Dec 2023 13:16:40 +0100 Subject: sfeed_update: suppress output to stderr, like merge() already does Noticed while testing TMPDIR=/noaccess sort on Illumos/OpenIndiana, which gives a warning to stderr. For sort temporary directories might be used for large output. --- sfeed_update | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sfeed_update b/sfeed_update index 94b64df..85cd759 100755 --- a/sfeed_update +++ b/sfeed_update @@ -85,7 +85,7 @@ merge() { # order by timestamp (descending). # order(name, url) order() { - sort -t ' ' -k1rn,1 + sort -t ' ' -k1rn,1 2>/dev/null } # internal handler to fetch and process a feed. -- cgit v1.2.3 From 1a5fa7454c92c5497c23e179e599224f0d96a920 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Wed, 27 Dec 2023 13:18:03 +0100 Subject: sfeed_update: remove xargs -s Theres no need to specify. POSIX defines it should support at least LINE_MAX (2048 typically). OpenIndiana xargs doesn't conform to POSIX. It doesn't use the largest constraint but errors out. From POSIX: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/xargs.html "Values of size up to at least {LINE_MAX} bytes shall be supported, provided that the constraints specified in the DESCRIPTION are met. It shall not be considered an error if a value larger than that supported by the implementation or exceeding the constraints specified in the DESCRIPTION is given; xargs shall use the largest value it supports within the constraints." --- sfeed_update | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sfeed_update b/sfeed_update index 85cd759..1bdafed 100755 --- a/sfeed_update +++ b/sfeed_update @@ -216,7 +216,7 @@ main() { # print feeds for parallel processing with xargs. feeds > "${sfeedtmpdir}/jobs" || die - SFEED_UPDATE_CHILD="1" xargs -s 65535 -x -0 -P "${maxjobs}" -n 1 \ + SFEED_UPDATE_CHILD="1" xargs -x -0 -P "${maxjobs}" -n 1 \ "$(readlink -f "${argv0}")" < "${sfeedtmpdir}/jobs" statuscode=$? -- cgit v1.2.3 From 0a5e36032373b34558e62f309be0b0ef7e925459 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Wed, 27 Dec 2023 13:20:07 +0100 Subject: sfeed_update: rename local variables just in case The config is loaded for each child program. These could override these variables if the user specifies the same name. --- sfeed_update | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sfeed_update b/sfeed_update index 1bdafed..607c048 100755 --- a/sfeed_update +++ b/sfeed_update @@ -233,11 +233,11 @@ if [ "${SFEED_UPDATE_CHILD}" = "1" ]; then IFS="" # "\037" [ "$1" = "" ] && exit 0 # must have an argument set printf '%s\n' "$1" | \ - while read -r config tmpdir name feedurl basesiteurl encoding; do + while read -r _config _tmpdir _name _feedurl _basesiteurl _encoding; do # load config file, sets $config. - loadconfig "${config}" - sfeedtmpdir="${tmpdir}" - _feed "${name}" "${feedurl}" "${basesiteurl}" "${encoding}" + loadconfig "${_config}" + sfeedtmpdir="${_tmpdir}" + _feed "${_name}" "${_feedurl}" "${_basesiteurl}" "${_encoding}" exit "$?" done exit 0 -- cgit v1.2.3 From 03a0a0ddb450f33e59a911a8be1b17d31507fa43 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Thu, 28 Dec 2023 16:48:06 +0100 Subject: sfeed_opml_export: disallow using a directory as a config file Same as the commit 9754fe74f7b5c0600cc41eef8c6f5c8305a74a18 for sfeed_update Tested on NetBSD 5.1: evaluating directories as config files could allow garbage, so disallow it. Devices / fifo, etc are still allowed. --- sfeed_opml_export | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sfeed_opml_export b/sfeed_opml_export index 6420d5e..9e769f7 100755 --- a/sfeed_opml_export +++ b/sfeed_opml_export @@ -15,7 +15,7 @@ loadconfig() { fi # config is loaded here to be able to override $sfeedpath or functions. - if [ -r "${configpath}" ]; then + if [ -r "${configpath}" ] && [ ! -d "${configpath}" ]; then . "${configpath}" else printf "Configuration file \"%s\" cannot be read.\n" "${config}" >&2 -- cgit v1.2.3 From cbf92f526528fc995e309d2f13b7dcebfd1e5c75 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 29 Dec 2023 13:50:55 +0100 Subject: sfeed_update/sfeed_opml_export: only allow regular files Be more strict and only allow regular files. It makes no sense to use device files or fifos with sfeed_update and it can cause issues, because sfeed_update expects to read the config file for each (child) invocation also. --- sfeed_opml_export | 2 +- sfeed_update | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sfeed_opml_export b/sfeed_opml_export index 9e769f7..f949488 100755 --- a/sfeed_opml_export +++ b/sfeed_opml_export @@ -15,7 +15,7 @@ loadconfig() { fi # config is loaded here to be able to override $sfeedpath or functions. - if [ -r "${configpath}" ] && [ ! -d "${configpath}" ]; then + if [ -r "${configpath}" ] && [ -f "${configpath}" ]; then . "${configpath}" else printf "Configuration file \"%s\" cannot be read.\n" "${config}" >&2 diff --git a/sfeed_update b/sfeed_update index 607c048..1f797f8 100755 --- a/sfeed_update +++ b/sfeed_update @@ -24,7 +24,7 @@ loadconfig() { fi # config is loaded here to be able to override $sfeedpath or functions. - if [ -r "${configpath}" ] && [ ! -d "${configpath}" ]; then + if [ -r "${configpath}" ] && [ -f "${configpath}" ]; then . "${configpath}" else printf "Configuration file \"%s\" cannot be read.\n" "${config}" >&2 -- cgit v1.2.3 From 04c76f86b66d2c30cadc6fef4df98cb3077ebcd9 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 29 Dec 2023 13:56:42 +0100 Subject: sfeed_update: code-style and consistency: add some comments for functions --- sfeed_update | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sfeed_update b/sfeed_update index 1f797f8..18096ea 100755 --- a/sfeed_update +++ b/sfeed_update @@ -171,6 +171,7 @@ feed() { "${config}" "${sfeedtmpdir}" "$1" "$2" "$3" "$4" } +# cleanup() cleanup() { # remove temporary directory with feed files. rm -rf "${sfeedtmpdir}" @@ -184,6 +185,7 @@ die() { exit "${statuscode}" } +# sighandler(signo) sighandler() { signo="$1" # ignore TERM signal for myself. @@ -192,12 +194,14 @@ sighandler() { kill -TERM -$$ } +# feeds() feeds() { printf "Configuration file \"%s\" is invalid or does not contain a \"feeds\" function.\n" "${config}" >&2 echo "See sfeedrc.example for an example." >&2 die } +# main(args...) main() { # signal number received for parent. signo=0 -- cgit v1.2.3 From 19cc32850dde2fe3fff1f02d8470727b84a5f67b Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 29 Dec 2023 14:10:38 +0100 Subject: sfeed_gopher: fix a clang-analyzer warning When reading from stdin the fields `total` and `totalnew` structure of feed are incremented. However this value was uninitialized. For stdin the result was not read and unused anyway though. --- sfeed_gopher.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sfeed_gopher.c b/sfeed_gopher.c index f9ee9b8..3512b72 100644 --- a/sfeed_gopher.c +++ b/sfeed_gopher.c @@ -118,7 +118,7 @@ printfeed(FILE *fpitems, FILE *fpin, struct feed *f) int main(int argc, char *argv[]) { - struct feed f; + struct feed f = { 0 }; FILE *fpitems, *fpindex, *fp; char *name, *p; int i; -- cgit v1.2.3 From 391a556d308fe19b22614498d8bdefab0c3016be Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 29 Dec 2023 14:21:30 +0100 Subject: sfeed_update: reword some comments --- sfeed_update | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/sfeed_update b/sfeed_update index 18096ea..fd468a5 100755 --- a/sfeed_update +++ b/sfeed_update @@ -163,10 +163,10 @@ _feed() { # fetch and process a feed in parallel. # feed(name, feedurl, [basesiteurl], [encoding]) feed() { - # Job parameters for xargs. - # Specify fields as a single parameter separated by the NUL separator. - # These fields are split later by the child process, this allows xargs - # with empty fields across many implementations. + # Output job parameters for xargs. + # Specify fields as a single parameter separated by a NUL byte. + # The parameter is split into fields later by the child process, this + # allows using xargs with empty fields across many implementations. printf '%s\037%s\037%s\037%s\037%s\037%s\0' \ "${config}" "${sfeedtmpdir}" "$1" "$2" "$3" "$4" } @@ -238,7 +238,6 @@ if [ "${SFEED_UPDATE_CHILD}" = "1" ]; then [ "$1" = "" ] && exit 0 # must have an argument set printf '%s\n' "$1" | \ while read -r _config _tmpdir _name _feedurl _basesiteurl _encoding; do - # load config file, sets $config. loadconfig "${_config}" sfeedtmpdir="${_tmpdir}" _feed "${_name}" "${_feedurl}" "${_basesiteurl}" "${_encoding}" @@ -248,5 +247,5 @@ if [ "${SFEED_UPDATE_CHILD}" = "1" ]; then fi # ...else parent mode: -argv0="$0" # remember $0, in shells like zsh $0 is the function name. +argv0="$0" # store $0, in the zsh shell $0 is the name of the function. [ "${SFEED_UPDATE_INCLUDE}" = "1" ] || main "$@" -- cgit v1.2.3 From 1f76c85ab88f0a1a36c7769a0b8ac13b9072105b Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 29 Dec 2023 14:29:04 +0100 Subject: bump version to 2.0 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b8f6f64..3acea14 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .POSIX: NAME = sfeed -VERSION = 1.9 +VERSION = 2.0 # curses theme, see themes/ directory. SFEED_THEME = mono -- cgit v1.2.3 From 1f6dfac83e8325a42dc5faa2e31cc25593acdfc8 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sun, 30 Jun 2024 09:59:48 +0200 Subject: improve parsing whitespace after end tag names Simplified test-case: https://git.codemadness.org/sfeed_tests/commit/e091160c3125322193bd8f27691c87eaa48cfc93.html --- xml.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xml.c b/xml.c index 1524d1f..35b7961 100644 --- a/xml.c +++ b/xml.c @@ -333,6 +333,8 @@ xml_parse(XMLParser *x) else if (c == '>' || ISSPACE(c)) { x->tag[x->taglen] = '\0'; if (isend) { /* end tag, starts with ' && c != EOF) /* skip until > */ + c = GETNEXT(); if (x->xmltagend) x->xmltagend(x, x->tag, x->taglen, x->isshorttag); x->tag[0] = '\0'; -- cgit v1.2.3 From 19957bc272e745af7b56b79fa648e8b6b77113b1 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Thu, 4 Jul 2024 18:26:51 +0200 Subject: sfeed.c: separator T could be lowercase From RFC3339: 5.6. Internet Date/Time Format " [...] NOTE: Per [ABNF] and ISO8601, the "T" and "Z" characters in this syntax may alternatively be lower case "t" or "z" respectively. This date/time format may be used in some environments or contexts that distinguish between the upper- and lower-case letters 'A'-'Z' and 'a'-'z' (e.g. XML). Specifications that use this format in such environments MAY further limit the date/time syntax so that the letters 'T' and 'Z' used in the date/time syntax must always be upper case. Applications that generate this format SHOULD use upper case letters. " Reference: https://www.rfc-editor.org/rfc/rfc3339 --- sfeed.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sfeed.c b/sfeed.c index cdd528c..16141cf 100644 --- a/sfeed.c +++ b/sfeed.c @@ -646,7 +646,7 @@ parsetime(const char *s, long long *tp) va[vi] = v; if ((vi < 2 && *s == '-') || - (vi == 2 && (*s == 'T' || ISSPACE((unsigned char)*s))) || + (vi == 2 && (*s == 'T' || *s == 't' || ISSPACE((unsigned char)*s))) || (vi > 2 && *s == ':')) s++; } -- cgit v1.2.3