Skip to content

shrimple πŸ‡΅πŸ‡± πŸ³οΈβ€βš§οΈ

shrimple mind. shrimple problems. complex solutions. she/her

Atom/RSS feeds dish for a browser capable of framesets β€” with some Perl

Posted on February[²⁰26], Monday 16.February[²⁰26], Monday 16. By Shrimple 1 Comment on Atom/RSS feeds dish for a browser capable of framesets β€” with some Perl

Just seeing what time was the last update to an RSS feed should be enough for most. And the RSS feeds already have titles, so one might not need a full OPML, but nothing more than a list of URLs as a flat file.

Although what follows has some room for improvement, feed reading can look like this

Feeds with text content layed out into a frameset by this script will be comfortable to read in graphical mode of Links browser. Framesets are great for sidebar UI. In the bottom part of the sidebar you have the selection of feeds. The script can embed you the original date string in the article view, and a time-units-since suffix into the feed selection document. It can be seen that a Unicode emoji is displayed by Links as a “blast” character at least in this compilation.
Some blogs will only provide a snippet into the feed, and thus the script renders that as a “Read full article” link, that leads directly to the article, right in the browser. You can navigate back from it when you’re done reading. It can be seen that a Unicode apostrophe in a link is rendered by Links as three asterisks at least in this compilation.
Framesets are also still supported by modern browsers, so this is a neat option for usual users as well. You fetch the feeds once and then you are in just a bunch of local static HTML files that happen to be a frameset.

My idea consists of two programs, for potential composability and to not bother with any kind of PATH:

  • one gets spawned in a directory and lays out the articles from the feed XML passed to it on standard input,
  • and one gets passed a list of feed URLs on standard input, and an argument with the path to the former script (it is not hardcoded to be composed with just that first one, although expects from the provided one not just the nav.html that it will link but also aΒ title.txt and aΒ date.txt in the directory after its execution) β€” and it creates theΒ index.html with the frameset (constant content) and aΒ feeds.html with the list of feeds and when were they last updated

#!/usr/bin/env perl
use v5.36;
use strict;
use warnings;

use XML::LibXML;
use File::Path qw(make_path);

my $dom = XML::LibXML->load_xml(IO => \*STDIN);

my $xc = XML::LibXML::XPathContext->new($dom);
$xc->registerNs(atom => 'http://www.w3.org/2005/Atom');

sub safe_id ($id, $fallback) {
    my $base = ($id && $id =~ /\S/) ? $id : $fallback;
    $base =~ s/[^A-Za-z0-9._-]/_/g;
    return $base;
}

sub text_of ($node, $xpath) {
    my ($n) = $xc->findnodes($xpath, $node);
    return defined $n ? $n->textContent : undef;
}

sub write_file ($path, $content) {
    open my $fh, '>:encoding(UTF-8)', $path
        or die "Cannot write $path: $!";
    print $fh $content;
    close $fh;
}

# ---

my @entries;

if ($xc->exists('/rss/channel/item')) {
    @entries = $xc->findnodes('/rss/channel/item');
}
else {
    @entries = $xc->findnodes('/atom:feed/atom:entry');
}

my $feed_title =
    text_of($dom, '/rss/channel/title')
    // text_of($dom, '/atom:feed/atom:title')
    // 'Feed';

my $feed_date =
    text_of($dom, '/rss/channel/lastBuildDate')
    // text_of($dom, '/atom:feed/atom:updated');

write_file('title.txt', $feed_title);
write_file('date.txt', $feed_date);

# ---

my $nav = "<html><head><title>$feed_title</title></head><body>";
$nav .= "<h2>$feed_title</h2><ul>";

for my $i (0 .. $#entries) {
    my $e = $entries[$i];

    my $raw_id =
        text_of($e, './guid')
        // text_of($e, './atom:id');

    my $id = safe_id($raw_id, "entry-" . ($i + 1));

    my $title =
        text_of($e, './title')
        // text_of($e, './atom:title')
        // 'Untitled';

    $nav .= qq{<li><a target="main" href="$id.html">$title</a></li>};
}

$nav .= "</ul></body></html>";
write_file('nav.html', $nav);

# ---

for my $i (0 .. $#entries) {
    my $e = $entries[$i];

    my $raw_id =
        text_of($e, './guid')
        // text_of($e, './atom:id');

    my $id = safe_id($raw_id, "entry-" . ($i + 1));

    my $title =
        text_of($e, './title')
        // text_of($e, './atom:title')
        // 'Untitled';

    my $date =
        text_of($e, './pubDate')
        // text_of($e, './atom:updated')
        // text_of($e, './atom:published')
        // '';

    my $body =
        text_of($e, './description')
        // text_of($e, './atom:summary')
        // text_of($e, './atom:content')
        // '';

    my $url =
        text_of($e, './link')
        // text_of($e, './atom:link[@rel="alternate"]/@href')
        // text_of($e, './atom:link[1]/@href');

    my $html = <<"HTML";
<html>
  <head><title>$title</title></head>
  <body>
    <h1>$title</h1>
    <p>$date</p>
    <p>$body</p>
HTML

    if ($url) {
        $html .= qq{<p><a target="_top" href="$url">Read full article</a></p>};
    }

    $html .= "</body></html>";

    write_file("$id.html", $html);
}

say "Done.";


#!/usr/bin/env perl
use v5.36;
use strict;
use warnings;
use String::Util 'trim';

use File::Path qw(make_path);
use IPC::Run qw(start);

sub write_file ($path, $content) {
    open my $fh, '>:encoding(UTF-8)', $path
        or die "Cannot write $path: $!";
    print $fh $content;
    close $fh;
}

write_file('index.html', <<"HTML");
<html>
  <frameset cols="25%,75%">
    <frameset rows="75%,25%">
      <frame name="nav"/>
      <frame src="feeds.html" name="feeds"/>
    </frameset>
    <frame name="main"/>
  </frameset>
</html>
HTML

use Cwd 'abs_path';

my $workprog = abs_path $ARGV[0];

my @jobs;
my @urls;

while (<STDIN>) {
  chomp;
  my $dir = $_;
  $dir =~ s/[^A-Za-z0-9._-]/_/g;
  make_path($dir);
  push @urls, $_;
  push @jobs, start(
      [ qw/curl -sL/, $_ ],
      "|",
      [ $workprog ],
      init => sub { chdir $dir });
}

use Time::Piece;
use Time::Seconds;

sub parse_date ($s) {
    for (
        "%Y-%m-%dT%H:%M:%S%z",
        "%a, %d %b %Y %H:%M:%S %z",
        "%Y-%m-%dT%H:%M%z",
    ) {
        $s =~ s/([+-]\d\d):(\d\d)/$1$2/;
        my $t = eval { Time::Piece->strptime($s, $_) };
        return $t if $t;
    }
    return;
}

sub human ($t) {
    my $d = gmtime() - $t;
    $d < ONE_HOUR ? int($d->minutes)."m ago" :
    $d < ONE_DAY  ? int($d->hours)."h ago"   :
                    int($d->days)."d ago";
}

open my $fh, '>:encoding(UTF-8)', 'feeds.html'
  or die "cannot write feeds.html: $!";


use Path::Tiny;

for (@jobs) {
  $_->finish;
  $_ = shift @urls;
  s/[^A-Za-z0-9._-]/_/g;
  my $title = path("$_/title.txt")->slurp_utf8;
  $title =~ s/[\p{Cf}\p{Mn}\p{So}\p{Sk}]//g;
  $title = substr($title, 0, 20);
  $title = trim $title;
  my $date = path("$_/date.txt")->slurp_utf8;
  $date = parse_date $date;
  $date = human $date;
  print {$fh} <<"HTML";
<p><a href="$_/nav.html" target=nav>$title ++ $date</a>
HTML
}

close $fh;

You execute them by running the second one with the path to the first one as an argument (~/feeds_many.pl ~/feeds_one.pl < ~/feeds), and redirect a list of bare URLs as lines to it β€” it creates you the directory structure and the HTML files; you then navigate with your browser to the index.html, which you of course can do all in a single shell command line.

Run once, read some, run once the next day again.

So, I hope to use it from now on πŸ™‚ and read feeds at last.

In my simplistic ideas, I nonetheless may have been influenced by the approach of Offpunk browser. https://offpunk.net

Smol Web Habits Tags:Atom|RSS_feeds, links2, perl, scripting, smolweb

Post navigation

Previous Post: Links 2, a graphical browser I wanna build upon. And a quick look at how ELinks is doing.

Comment (1) on “Atom/RSS feeds dish for a browser capable of framesets β€” with some Perl”

  1. nicole mikoΕ‚ajczyk says:
    February[²⁰26], Monday 16. at 10pm

    @shrimple works on my akkoma

    Reply as
    Reply on the Fediverse

    Remote Reply

    Original Comment URL

    Copy and paste the Comment URL into the search field of your favorite fediverse app or server.

    Your Profile

    Or, if you know your own profile, we can start things that way!

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Atom feed for this page

Atom feed for this blog

against-messy-software Atom|RSS_feeds bash big.ugly.git.patch. chromium-and-derivatives community fragment golang kde links2 linux microsoft-edge network offpunk offpunk:lists offpunk:redirections oss-contributing perl programming-tips scripting smolweb subscribe superuser window-decorations Wordpress_ActivityPub_plugin

  • February 2026 (5)
  • January 2026 (10)

Categories

  • Influencing Society

    (1)
  • Meta

    (2)
  • Oddities of alternate reality

    (1)
  • Programming Technologies

    (1)
  • Smol Web Habits

    (1)
  • Software Imposed On Us

    (1)
  • Wild Software Writing

    (8)
shrimple πŸ‡΅πŸ‡±  πŸ³οΈβ€βš§οΈ
shrimple πŸ‡΅πŸ‡± πŸ³οΈβ€βš§οΈ
@shrimple@www.shrimple.pl
Follow

shrimple mind. shrimple problems. complex solutions. she/her

15 posts
6 followers

Follow shrimple πŸ‡΅πŸ‡± πŸ³οΈβ€βš§οΈ

My Profile

Copy and paste my profile into the search field of your favorite fediverse app or server.

Your Profile

Or, if you know your own profile, we can start things that way!
  • Getting TLS1.3 Key Log from Go application with requests by a library, and using it in Wireshark Programming Technologies
  • Amending my Offpunk redirection implementation Wild Software Writing
  • Bugfix for list URI for my Offpunk redirections implementation draft Wild Software Writing
  • Forcing KWin decorations and MS Edge’s 1cm shadow gradient Software Imposed On Us
  • Links 2, a graphical browser I wanna build upon. And a quick look at how ELinks is doing. Wild Software Writing
  • when DMI info like Serial not in hostnamectl output Oddities of alternate reality
  • What if we organized a different kind of hackathon Influencing Society
  • Simplistic reconciliation of mostly-append text files like Offpunk lists: draft involving Kahn’s algorithm Wild Software Writing

shrimple@shrimple.pl

Copyright © 2026 shrimple πŸ‡΅πŸ‡± πŸ³οΈβ€βš§οΈ.

Powered by PressBook News WordPress theme