URI:
       add gopher versions for feeds - www.codemadness.org - www.codemadness.org saait content files
  HTML git clone git://git.codemadness.org/www.codemadness.org
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit aa794d3b1909cb60c010583a2da9a3582cdc6b16
   DIR parent 1af725a4591bf8242badc4baffec0bfc7fd038b0
  HTML Author: Hiltjo Posthuma <hiltjo@codemadness.org>
       Date:   Fri, 16 May 2025 12:23:14 +0200
       
       add gopher versions for feeds
       
       Requested by kroovy, thanks.
       
       Diffstat:
         M Makefile                            |       5 +++++
         M config.cfg                          |       5 ++++-
         M feeds/pages/atom.md                 |      19 ++++++++++++++-----
         M output/atom.html                    |      15 +++++++++++++--
         M output/atom.md                      |      19 ++++++++++++++-----
         M output/atom.xml                     |       2 +-
         M output/atom_content.xml             |       2 +-
         A output/atom_content_gopher.xml      |    3663 ++++++++++++++++++++++++++++++
         A output/atom_gopher.xml              |     429 +++++++++++++++++++++++++++++++
         A output/jsonfeed.json                |     285 +++++++++++++++++++++++++++++++
         A output/jsonfeed_content.json        |     285 +++++++++++++++++++++++++++++++
         A output/jsonfeed_content_gopher.json |     285 +++++++++++++++++++++++++++++++
         A output/jsonfeed_gopher.json         |     285 +++++++++++++++++++++++++++++++
         A output/mailservice.html             |     184 +++++++++++++++++++++++++++++++
         A output/mailservice.md               |     150 +++++++++++++++++++++++++++++++
         M output/phlog/atom                   |      13 +++++++++++--
         A output/phlog/mailservice            |     158 +++++++++++++++++++++++++++++++
         A output/phlog/webdump                |     151 +++++++++++++++++++++++++++++++
         A output/phlog/xargs                  |     247 +++++++++++++++++++++++++++++++
         A output/phlog/xml                    |     334 +++++++++++++++++++++++++++++++
         A output/phlog/youtube-feed-improved  |     167 +++++++++++++++++++++++++++++++
         A output/rss_content_gopher.xml       |    3489 +++++++++++++++++++++++++++++++
         A output/rss_gopher.xml               |     290 +++++++++++++++++++++++++++++++
         A output/sfeed.tsv                    |      35 +++++++++++++++++++++++++++++++
         A output/sfeed_content.tsv            |      35 +++++++++++++++++++++++++++++++
         A output/sfeed_content_gopher.tsv     |      35 +++++++++++++++++++++++++++++++
         A output/sfeed_gopher.tsv             |      35 +++++++++++++++++++++++++++++++
         A output/twtxt_gopher.txt             |      35 +++++++++++++++++++++++++++++++
         A output/webdump.html                 |     164 +++++++++++++++++++++++++++++++
         A output/webdump.md                   |     143 +++++++++++++++++++++++++++++++
         A output/xml.html                     |     347 +++++++++++++++++++++++++++++++
         A output/xml.md                       |     326 +++++++++++++++++++++++++++++++
         A templates/atom_content_gopher.xml/… |       1 +
         A templates/atom_content_gopher.xml/… |       8 ++++++++
         A templates/atom_content_gopher.xml/… |      15 +++++++++++++++
         A templates/atom_gopher.xml/footer.x… |       1 +
         A templates/atom_gopher.xml/header.x… |       8 ++++++++
         A templates/atom_gopher.xml/item.xml  |      12 ++++++++++++
         A templates/rss_content_gopher.xml/f… |       2 ++
         A templates/rss_content_gopher.xml/h… |       8 ++++++++
         A templates/rss_content_gopher.xml/i… |      10 ++++++++++
         A templates/rss_gopher.xml/footer.xml |       2 ++
         A templates/rss_gopher.xml/header.xml |       8 ++++++++
         A templates/rss_gopher.xml/item.xml   |       8 ++++++++
         A templates/twtxt_gopher.txt/item.txt |       1 +
       
       45 files changed, 11704 insertions(+), 17 deletions(-)
       ---
   DIR diff --git a/Makefile b/Makefile
       @@ -14,9 +14,14 @@ generate: markdown sfeeddoc
                # Atom to JSON Feed 1.1
                sfeed < output/atom.xml | sfeed_json > output/jsonfeed.json
                sfeed < output/atom_content.xml | sfeed_json > output/jsonfeed_content.json
       +        sfeed < output/atom_gopher.xml | sfeed_json > output/jsonfeed_gopher.json
       +        sfeed < output/atom_content_gopher.xml | sfeed_json > output/jsonfeed_content_gopher.json
                # raw sfeed
                sfeed < output/atom.xml > output/sfeed.tsv
                sfeed < output/atom_content.xml > output/sfeed_content.tsv
       +        sfeed < output/atom_gopher.xml > output/sfeed_gopher.tsv
       +        sfeed < output/atom_content_gopher.xml > output/sfeed_content_gopher.tsv
       +
                # Atom feed page
                saait -t feeds/templates feeds/pages/atom.cfg
                # move index.gopher to index.
   DIR diff --git a/config.cfg b/config.cfg
       @@ -1,10 +1,13 @@
        # last updated the site.
       -siteupdated = 2025-05-03
       +siteupdated = 2025-05-16
        
        sitetitle = Codemadness
        siteurl = https://www.codemadness.org
        sitemail = hiltjo@AT@codemadness.DOT.org
        sitegenerator = Static content generated using saait: https://codemadness.org/saait.html
       +gopherurl = gopher://codemadness.org
       +gophersurl = gophers://codemadness.org
       +gopherphlog = /phlog/
        
        lang = en
        author = Hiltjo
   DIR diff --git a/feeds/pages/atom.md b/feeds/pages/atom.md
       @@ -1,24 +1,31 @@
        ## Atom
        
        * Atom feed, smaller filesize with only a summary: [atom.xml](atom.xml).
       -* Atom feed with content: [atom_content.xml](atom_content.xml).
       +* Atom feed with content: [atom\_content.xml](atom_content.xml).
       +* Atom feed, smaller filesize with only a summary: [atom\_gopher.xml](atom_gopher.xml).
       +* Atom feed with content: [atom\_content\_gopher.xml](atom_content_gopher.xml).
        
        
        ## RSS
        
        * RSS feed, smaller filesize with only a summary: [rss.xml](rss.xml).
       -* RSS feed with content: [rss_content.xml](rss_content.xml).
       +* RSS feed with content: [rss\_content.xml](rss_content.xml).
       +* RSS feed, smaller filesize with only a summary, gopher: [rss\_gopher.xml](rss_gopher.xml).
       +* RSS feed with content, gopher: [rss\_content\_gopher.xml](rss_content_gopher.xml).
        
        
        ## [Twtxt](https://twtxt.readthedocs.io/en/stable/)
        
       -Twtxt feed: [twtxt.txt](twtxt.txt).
       +* Twtxt feed: [twtxt.txt](twtxt.txt).
       +* Twtxt feed, gopher: [twtxt\_gopher.txt](twtxt_gopher.txt).
        
        
        ## [sfeed TAB-separated format](sfeed.1.txt)
        
        * Feed, smaller filesize with only a summary: [sfeed.tsv](sfeed.tsv).
       -* Feed with content: [sfeed_content.tsv](sfeed_content.tsv).
       +* Feed with content: [sfeed\_content.tsv](sfeed_content.tsv).
       +* Feed, smaller filesize with only a summary, gopher: [sfeed\_gopher.tsv](sfeed_gopher.tsv).
       +* Feed with content, gopher: [sfeed\_content\_gopher.tsv](sfeed_content_gopher.tsv).
        
        Documentation:
        * [sfeed TAB-separated data format documentation](sfeed.1.txt).
       @@ -28,7 +35,9 @@ Documentation:
        ## [JSONfeed 1.1](https://www.jsonfeed.org/version/1.1/)
        
        * JSONfeed 1.1 feed, smaller filesize with only a summary: [jsonfeed.json](jsonfeed.json).
       -* JSONfeed 1.1 feed with content: [jsonfeed_content.json](jsonfeed_content.json).
       +* JSONfeed 1.1 feed with content: [jsonfeed\_content.json](jsonfeed\_content.json).
       +* JSONfeed 1.1 feed, smaller filesize with only a summary, gopher: [jsonfeed\_gopher.json](jsonfeed_gopher.json).
       +* JSONfeed 1.1 feed with content, gopher: [jsonfeed\_content\_gopher.json](jsonfeed_content_gopher.json).
        
        
        ## Atom feeds for the git repositories
   DIR diff --git a/output/atom.html b/output/atom.html
       @@ -47,18 +47,27 @@
        <ul>
        <li>Atom feed, smaller filesize with only a summary: <a href="atom.xml">atom.xml</a>.</li>
        <li>Atom feed with content: <a href="atom_content.xml">atom_content.xml</a>.</li>
       +<li>Atom feed, smaller filesize with only a summary: <a href="atom_gopher.xml">atom_gopher.xml</a>.</li>
       +<li>Atom feed with content: <a href="atom_content_gopher.xml">atom_content_gopher.xml</a>.</li>
        </ul>
        <h2>RSS</h2>
        <ul>
        <li>RSS feed, smaller filesize with only a summary: <a href="rss.xml">rss.xml</a>.</li>
        <li>RSS feed with content: <a href="rss_content.xml">rss_content.xml</a>.</li>
       +<li>RSS feed, smaller filesize with only a summary, gopher: <a href="rss_gopher.xml">rss_gopher.xml</a>.</li>
       +<li>RSS feed with content, gopher: <a href="rss_content_gopher.xml">rss_content_gopher.xml</a>.</li>
        </ul>
        <h2><a href="https://twtxt.readthedocs.io/en/stable/">Twtxt</a></h2>
       -<p>Twtxt feed: <a href="twtxt.txt">twtxt.txt</a>.</p>
       +<ul>
       +<li>Twtxt feed: <a href="twtxt.txt">twtxt.txt</a>.</li>
       +<li>Twtxt feed, gopher: <a href="twtxt_gopher.txt">twtxt_gopher.txt</a>.</li>
       +</ul>
        <h2><a href="sfeed.1.txt">sfeed TAB-separated format</a></h2>
        <ul>
        <li>Feed, smaller filesize with only a summary: <a href="sfeed.tsv">sfeed.tsv</a>.</li>
        <li>Feed with content: <a href="sfeed_content.tsv">sfeed_content.tsv</a>.</li>
       +<li>Feed, smaller filesize with only a summary, gopher: <a href="sfeed_gopher.tsv">sfeed_gopher.tsv</a>.</li>
       +<li>Feed with content, gopher: <a href="sfeed_content_gopher.tsv">sfeed_content_gopher.tsv</a>.</li>
        </ul>
        <p>Documentation:
        <ul>
       @@ -69,7 +78,9 @@
        <h2><a href="https://www.jsonfeed.org/version/1.1/">JSONfeed 1.1</a></h2>
        <ul>
        <li>JSONfeed 1.1 feed, smaller filesize with only a summary: <a href="jsonfeed.json">jsonfeed.json</a>.</li>
       -<li>JSONfeed 1.1 feed with content: <a href="jsonfeed_content.json">jsonfeed_content.json</a>.</li>
       +<li>JSONfeed 1.1 feed with content: <a href="jsonfeed\_content.json">jsonfeed_content.json</a>.</li>
       +<li>JSONfeed 1.1 feed, smaller filesize with only a summary, gopher: <a href="jsonfeed_gopher.json">jsonfeed_gopher.json</a>.</li>
       +<li>JSONfeed 1.1 feed with content, gopher: <a href="jsonfeed_content_gopher.json">jsonfeed_content_gopher.json</a>.</li>
        </ul>
        <h2>Atom feeds for the git repositories</h2>
        <p>The git <a href="/git/">repositories</a> each have an Atom feed for the commit log (atom.xml) and
   DIR diff --git a/output/atom.md b/output/atom.md
       @@ -1,24 +1,31 @@
        ## Atom
        
        * Atom feed, smaller filesize with only a summary: [atom.xml](https://codemadness.org/atom.xml).
       -* Atom feed with content: [atom_content.xml](https://codemadness.org/atom_content.xml).
       +* Atom feed with content: [atom\_content.xml](https://codemadness.org/atom_content.xml).
       +* Atom feed, smaller filesize with only a summary: [atom\_gopher.xml](https://codemadness.org/atom_gopher.xml).
       +* Atom feed with content: [atom\_content\_gopher.xml](https://codemadness.org/atom_content_gopher.xml).
        
        
        ## RSS
        
        * RSS feed, smaller filesize with only a summary: [rss.xml](https://codemadness.org/rss.xml).
       -* RSS feed with content: [rss_content.xml](https://codemadness.org/rss_content.xml).
       +* RSS feed with content: [rss\_content.xml](https://codemadness.org/rss_content.xml).
       +* RSS feed, smaller filesize with only a summary, gopher: [rss\_gopher.xml](https://codemadness.org/rss_gopher.xml).
       +* RSS feed with content, gopher: [rss\_content\_gopher.xml](https://codemadness.org/rss_content_gopher.xml).
        
        
        ## [Twtxt](https://twtxt.readthedocs.io/en/stable/)
        
       -Twtxt feed: [twtxt.txt](https://codemadness.org/twtxt.txt).
       +* Twtxt feed: [twtxt.txt](https://codemadness.org/twtxt.txt).
       +* Twtxt feed, gopher: [twtxt\_gopher.txt](https://codemadness.org/twtxt_gopher.txt).
        
        
        ## [sfeed TAB-separated format](https://codemadness.org/sfeed.1.txt)
        
        * Feed, smaller filesize with only a summary: [sfeed.tsv](https://codemadness.org/sfeed.tsv).
       -* Feed with content: [sfeed_content.tsv](https://codemadness.org/sfeed_content.tsv).
       +* Feed with content: [sfeed\_content.tsv](https://codemadness.org/sfeed_content.tsv).
       +* Feed, smaller filesize with only a summary, gopher: [sfeed\_gopher.tsv](https://codemadness.org/sfeed_gopher.tsv).
       +* Feed with content, gopher: [sfeed\_content\_gopher.tsv](https://codemadness.org/sfeed_content_gopher.tsv).
        
        Documentation:
        * [sfeed TAB-separated data format documentation](https://codemadness.org/sfeed.1.txt).
       @@ -28,7 +35,9 @@ Documentation:
        ## [JSONfeed 1.1](https://www.jsonfeed.org/version/1.1/)
        
        * JSONfeed 1.1 feed, smaller filesize with only a summary: [jsonfeed.json](https://codemadness.org/jsonfeed.json).
       -* JSONfeed 1.1 feed with content: [jsonfeed_content.json](https://codemadness.org/jsonfeed_content.json).
       +* JSONfeed 1.1 feed with content: [jsonfeed\_content.json](jsonfeed\_content.json).
       +* JSONfeed 1.1 feed, smaller filesize with only a summary, gopher: [jsonfeed\_gopher.json](https://codemadness.org/jsonfeed_gopher.json).
       +* JSONfeed 1.1 feed with content, gopher: [jsonfeed\_content\_gopher.json](https://codemadness.org/jsonfeed_content_gopher.json).
        
        
        ## Atom feeds for the git repositories
   DIR diff --git a/output/atom.xml b/output/atom.xml
       @@ -2,7 +2,7 @@
        <feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
                <title>Codemadness</title>
                <subtitle>blog with various projects and articles about computer-related things</subtitle>
       -        <updated>2025-05-03T00:00:00Z</updated>
       +        <updated>2025-05-16T00:00:00Z</updated>
                <link rel="alternate" type="text/html" href="https://www.codemadness.org" />
                <id>https://www.codemadness.org/atom.xml</id>
                <link rel="self" type="application/atom+xml" href="https://www.codemadness.org/atom.xml" />
   DIR diff --git a/output/atom_content.xml b/output/atom_content.xml
       @@ -2,7 +2,7 @@
        <feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
                <title>Codemadness</title>
                <subtitle>blog with various projects and articles about computer-related things</subtitle>
       -        <updated>2025-05-03T00:00:00Z</updated>
       +        <updated>2025-05-16T00:00:00Z</updated>
                <link rel="alternate" type="text/html" href="https://www.codemadness.org" />
                <id>https://www.codemadness.org/atom_content.xml</id>
                <link rel="self" type="application/atom+xml" href="https://www.codemadness.org/atom_content.xml" />
   DIR diff --git a/output/atom_content_gopher.xml b/output/atom_content_gopher.xml
       @@ -0,0 +1,3663 @@
       +<?xml version="1.0" encoding="UTF-8"?>
       +<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
       +        <title>Codemadness</title>
       +        <subtitle>blog with various projects and articles about computer-related things</subtitle>
       +        <updated>2025-05-16T00:00:00Z</updated>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org" />
       +        <id>gopher://codemadness.org/0/atom_content_gopher.xml</id>
       +        <link rel="self" type="application/atom+xml" href="gopher://codemadness.org/0/atom_content_gopher.xml" />
       +<entry>
       +        <title>Chess puzzle book generator</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/chess-puzzles" />
       +        <id>gopher://codemadness.org/1/phlog/chess-puzzles</id>
       +        <updated>2025-05-03T00:00:00Z</updated>
       +        <published>2024-02-02T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Chess puzzle book generator</summary>
       +        <content type="html"><![CDATA[<h1>Chess puzzle book generator</h1>
       +        <p><strong>Last modification on </strong> <time>2025-05-03</time></p>
       +        <p>This was a christmas hack for fun and non-profit.
       +I wanted to write a chess puzzle book generator.
       +Inspired by <a href="https://archive.org/details/1001deadlycheckm0000nunn">1001 Deadly Checkmates by John Nunn, ISBN-13: 978-1906454258</a>,
       +<a href="https://www.stappenmethode.nl/en/">Steps Method workbooks</a> and other puzzle books.</p>
       +<h1>Example output</h1>
       +<ul>
       +<li>English version: <a href="https://codemadness.org/downloads/puzzles/">https://codemadness.org/downloads/puzzles/</a></li>
       +<li>Dutch version: <a href="https://hiltjo.nl/puzzles/">https://hiltjo.nl/puzzles/</a></li>
       +</ul>
       +<p>Terminal version:</p>
       +<pre><code>curl -s 'https://codemadness.org/downloads/puzzles/index.vt' | less -R
       +</code></pre>
       +<p>I may or may not periodially update this page :)</p>
       +<p>Time flies (since Christmas), here is a valentine edition with <a href="https://lichess.org/practice/intermediate-tactics/attraction/">attraction</a>
       +puzzles (not only checkmates) using the red "love" theme.
       +It is optimized for his and her pleasure:</p>
       +<p><a href="https://codemadness.org/downloads/puzzles-valentine/">https://codemadness.org/downloads/puzzles-valentine/</a></p>
       +<h2>Clone</h2>
       +<pre><code>git clone git://git.codemadness.org/chess-puzzles
       +</code></pre>
       +<h2>Browse</h2>
       +<p>You can browse the source-code at:</p>
       +<ul>
       +<li><a href="https://git.codemadness.org/chess-puzzles/">https://git.codemadness.org/chess-puzzles/</a></li>
       +<li><a href="gopher://codemadness.org/1/git/chess-puzzles">gopher://codemadness.org/1/git/chess-puzzles</a></li>
       +</ul>
       +<h1>Quick overview of how it works</h1>
       +<p>The generate.sh shellscript generates the output and files for the puzzles.</p>
       +<p>The puzzles used are from the lichess.org puzzle database:
       +<a href="https://database.lichess.org/#puzzles">https://database.lichess.org/#puzzles</a></p>
       +<p>This database is a big CSV file containing the initial board state in the
       +Forsyth-Edwards Notation (FEN) format and the moves in Universal Chess
       +Interface (UCI) format. Each line contains the board state and the initial and
       +solution moves.</p>
       +<p>The generated index page is a HTML page, it lists the puzzles.  Each puzzle on
       +this page is an SVG image. This scalable image format looks good in all
       +resolutions.</p>
       +<h1>Open puzzle data</h1>
       +<p>Lichess is an <a href="https://lichess.org/source">open-source</a> and gratis website to play on-line chess. There are
       +no paid levels to unlock features.  All the software hosting Lichess is
       +open-source and anyone can register and play chess on it for free. Most of the
       +data about the games played is also open.</p>
       +<p>However, the website depends on your donations or contributions. If you can,
       +<a href="https://lichess.org/about">please do so</a>.</p>
       +<h1>generate.sh</h1>
       +<p>Reads puzzles from the database and shuffle them. Do some rough sorting and
       +categorization based on difficulty and assign score points.</p>
       +<p>The random shuffling is done using a hard-coded <a href="https://en.wikipedia.org/wiki/Random_seed">random seed</a>. This means on the
       +same machine with the same puzzle database it will regenerate the same sequence
       +of random puzzles in a deterministic manner.</p>
       +<p>It outputs HTML, with support for CSS dark mode and does not require Javascript.
       +It includes a plain-text listing of the solutions in PGN notation for the
       +puzzles.
       +It also outputs .vt files suitable for the terminal. It uses unicode symbols
       +for the chess pieces and RGB color sequence for the board theme</p>
       +<h1>fen.c</h1>
       +<p>This is a program written in C to read and parse the board state in FEN format
       +and read the UCI moves. It can output to various formats.</p>
       +<p>See the man page for detailed usage information.</p>
       +<p>fen.c supports the following output formats:</p>
       +<ul>
       +<li>ascii - very simple ASCII mode.</li>
       +<li><a href="https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation">fen</a> - output FEN of the board state (from FEN and optional played moves).</li>
       +<li><a href="https://en.wikipedia.org/wiki/Portable_Game_Notation">pgn</a> - Portable Game Notation.</li>
       +<li>speak - mode to output a description of the moves in words.</li>
       +<li><a href="https://en.wikipedia.org/wiki/SVG">SVG</a> - Scalable Vector Graphics image.</li>
       +<li>tty - Terminal output with some markup using escape codes.</li>
       +</ul>
       +<p>fen.c can also run in <a href="https://en.wikipedia.org/wiki/Common_Gateway_Interface">CGI</a> mode. This can be used on a HTTP server:</p>
       +<p><img src="https://codemadness.org/onlyfens?fen=6k1/ppq3bp/2n2np1/5p2/2P2P2/4rBN1/PP3K1P/RQ6%20w%20-%20-%200%2023&amp;moves=f2e3&amp;flip=1" alt="Position from game: Rene Letelier Martner - Robert James Fischer, 1960-10-24" /></p>
       +<ul>
       +<li><a href="https://codemadness.org/onlyfens">https://codemadness.org/onlyfens</a></li>
       +<li><a href="https://codemadness.org/onlyfens?fen=6k1/ppq3bp/2n2np1/5p2/2P2P2/4rBN1/PP3K1P/RQ6%20w%20-%20-%200%2023&amp;moves=f2e3&amp;flip=1">https://codemadness.org/onlyfens?fen=6k1/ppq3bp/2n2np1/5p2/2P2P2/4rBN1/PP3K1P/RQ6%20w%20-%20-%200%2023&amp;moves=f2e3&amp;flip=1</a></li>
       +<li><a href="https://codemadness.org/onlyfens?moves=e2e4%20e7e5&amp;flip=1&amp;theme=green&amp;output=svg">https://codemadness.org/onlyfens?moves=e2e4%20e7e5&amp;flip=1&amp;theme=green&amp;output=svg</a></li>
       +<li><a href="https://codemadness.org/onlyfens?moves=e2e4%20e7e5&amp;output=pgn">https://codemadness.org/onlyfens?moves=e2e4%20e7e5&amp;output=pgn</a></li>
       +<li><a href="https://codemadness.org/onlyfens?moves=e2e4%20e7e5&amp;output=speak">https://codemadness.org/onlyfens?moves=e2e4%20e7e5&amp;output=speak</a></li>
       +<li><a href="https://codemadness.org/onlyfens?moves=e2e4%20e7e5&amp;output=ascii">https://codemadness.org/onlyfens?moves=e2e4%20e7e5&amp;output=ascii</a></li>
       +<li><a href="https://codemadness.org/onlyfens?moves=e2e4%20e7e5&amp;output=fen">https://codemadness.org/onlyfens?moves=e2e4%20e7e5&amp;output=fen</a></li>
       +</ul>
       +<p>Terminal output:</p>
       +<pre><code>curl -s 'https://codemadness.org/onlyfens?moves=e2e4%20e7e5&amp;output=tty'
       +</code></pre>
       +<h1>Support for Dutch notated PGN and output</h1>
       +<p>For pgn and "speak mode" it has an option to output Dutch notated PGN or speech
       +too.</p>
       +<p>For example:</p>
       +<ul>
       +<li>Queen = Dame (Q -&gt; D), translated: lady.</li>
       +<li>Rook = Toren (R -&gt; T), translated: tower.</li>
       +<li>Bishop = Loper (B -&gt; L), translated: walker.</li>
       +<li>Knight = Paard (N -&gt; P), translated: horse.</li>
       +</ul>
       +<h1>Example script to stream games from Lichess</h1>
       +<p>There is an included example script that can stream Lichess games to the
       +terminal. It uses the <a href="https://lichess.org/api">Lichess API</a>.  It will display the board using terminal
       +escape codes. The games are automatically annotated with PGN notation and with
       +text how a human would say the notation. This can also be piped to a speech
       +synthesizer like <a href="https://github.com/espeak-ng/espeak-ng/">espeak</a> as audio.</p>
       +<p>pgn-extract is a useful tool to convert Portable Game Notation (PGN) to
       +Universal Chess Interface (UCI) moves (or do many other useful chess related
       +things!).</p>
       +<h1>Example script to generate an animated gif from PGN</h1>
       +<p>Theres also an example script included that can generate an animated gif from
       +PGN using <a href="https://ffmpeg.org/">ffmpeg</a>.</p>
       +<p>It creates an optimal color palette from the input images and generates an
       +optimized animated gif. The last move (typically some checkmate) is displayed
       +slightly longer.</p>
       +<h1>References and chess related links</h1>
       +<ul>
       +<li><p>chess-puzzles source-code:<br />  
       +<a href="https://www.codemadness.org/git/chess-puzzles/file/README.html">https://www.codemadness.org/git/chess-puzzles/file/README.html</a></p>
       +</li>
       +<li><p>Lichess FEN puzzle database:<br />  
       +<a href="https://database.lichess.org/#puzzles">https://database.lichess.org/#puzzles</a></p>
       +</li>
       +<li><p>lichess.org:<br />  
       +<a href="https://lichess.org/">https://lichess.org/</a></p>
       +</li>
       +<li><p>SVG of the individual pieces used in fen.c:<br />  
       +<a href="https://github.com/lichess-org/lila/tree/master/public/piece/cburnett">https://github.com/lichess-org/lila/tree/master/public/piece/cburnett</a></p>
       +</li>
       +<li><p>pgn-extract:<br />  
       +A great multi-purpose PGN manipulation program with many options:<br />  
       +<a href="https://www.cs.kent.ac.uk/people/staff/djb/pgn-extract/">https://www.cs.kent.ac.uk/people/staff/djb/pgn-extract/</a></p>
       +<p>An example to convert PGN games to UCI moves:<br />  
       +<code>pgn-extract --notags -Wuc</code></p>
       +</li>
       +<li><p>Lichess API:<br />  
       +<a href="https://lichess.org/api">https://lichess.org/api</a></p>
       +</li>
       +<li><p>Stockfish:<br />  
       +Strong open-source chess engine and analysis tool:<br />  
       +<a href="https://stockfishchess.org/">https://stockfishchess.org/</a></p>
       +</li>
       +</ul>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>xargs: an example for parallel batch jobs</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/xargs" />
       +        <id>gopher://codemadness.org/1/phlog/xargs</id>
       +        <updated>2023-12-17T00:00:00Z</updated>
       +        <published>2023-11-22T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>xargs: an example for parallel batch jobs</summary>
       +        <content type="html"><![CDATA[<h1>xargs: an example for parallel batch jobs</h1>
       +        <p><strong>Last modification on </strong> <time>2023-12-17</time></p>
       +        <p>This describes a simple shellscript programming pattern to process a list of
       +jobs in parallel. This script example is contained in one file.</p>
       +<h1>Simple but less optimal example</h1>
       +<pre><code>#!/bin/sh
       +maxjobs=4
       +
       +# fake program for example purposes.
       +someprogram() {
       +        echo "Yep yep, I'm totally a real program!"
       +        sleep "$1"
       +}
       +
       +# run(arg1, arg2)
       +run() {
       +        echo "[$1] $2 started" &gt;&amp;2
       +        someprogram "$1" &gt;/dev/null
       +        status="$?"
       +        echo "[$1] $2 done" &gt;&amp;2
       +        return "$status"
       +}
       +
       +# process the jobs.
       +j=1
       +for f in 1 2 3 4 5 6 7 8 9 10; do
       +        run "$f" "something" &amp;
       +
       +        jm=$((j % maxjobs)) # shell arithmetic: modulo
       +        test "$jm" = "0" &amp;&amp; wait
       +        j=$((j+1))
       +done
       +wait
       +</code></pre>
       +<h1>Why is this less optimal</h1>
       +<p>This is less optimal because it waits until all jobs in the same batch are finished
       +(each batch contain $maxjobs items).</p>
       +<p>For example with 2 items per batch and 4 total jobs it could be:</p>
       +<ul>
       +<li>Job 1 is started.</li>
       +<li>Job 2 is started.</li>
       +<li>Job 2 is done.</li>
       +<li>Job 1 is done.</li>
       +<li>Wait: wait on process status of all background processes.</li>
       +<li>Job 3 in new batch is started.</li>
       +</ul>
       +<p>This could be optimized to:</p>
       +<ul>
       +<li>Job 1 is started.</li>
       +<li>Job 2 is started.</li>
       +<li>Job 2 is done.</li>
       +<li>Job 3 in new batch is started (immediately).</li>
       +<li>Job 1 is done.</li>
       +<li>...</li>
       +</ul>
       +<p>It also does not handle signals such as SIGINT (^C). However the xargs example
       +below does:</p>
       +<h1>Example</h1>
       +<pre><code>#!/bin/sh
       +maxjobs=4
       +
       +# fake program for example purposes.
       +someprogram() {
       +        echo "Yep yep, I'm totally a real program!"
       +        sleep "$1"
       +}
       +
       +# run(arg1, arg2)
       +run() {
       +        echo "[$1] $2 started" &gt;&amp;2
       +        someprogram "$1" &gt;/dev/null
       +        status="$?"
       +        echo "[$1] $2 done" &gt;&amp;2
       +        return "$status"
       +}
       +
       +# child process job.
       +if test "$CHILD_MODE" = "1"; then
       +        run "$1" "$2"
       +        exit "$?"
       +fi
       +
       +# generate a list of jobs for processing.
       +list() {
       +        for f in 1 2 3 4 5 6 7 8 9 10; do
       +                printf '%s\0%s\0' "$f" "something"
       +        done
       +}
       +
       +# process jobs in parallel.
       +list | CHILD_MODE="1" xargs -r -0 -P "${maxjobs}" -L 2 "$(readlink -f "$0")"
       +</code></pre>
       +<h1>Run and timings</h1>
       +<p>Although the above example is kindof stupid, it already shows the queueing of
       +jobs is more efficient.</p>
       +<p>Script 1:</p>
       +<pre><code>time ./script1.sh
       +[...snip snip...]
       +real    0m22.095s
       +</code></pre>
       +<p>Script 2:</p>
       +<pre><code>time ./script2.sh
       +[...snip snip...]
       +real    0m18.120s
       +</code></pre>
       +<h1>How it works</h1>
       +<p>The parent process:</p>
       +<ul>
       +<li>The parent, using xargs, handles the queue of jobs and schedules the jobs to
       +execute as a child process.</li>
       +<li>The list function writes the parameters to stdout. These parameters are
       +separated by the NUL byte separator. The NUL byte separator is used because
       +this character cannot be used in filenames (which can contain spaces or even
       +newlines) and cannot be used in text (the NUL byte terminates the buffer for
       +a string).</li>
       +<li>The -L option must match the amount of arguments that are specified for the
       +job. It will split the specified parameters per job.</li>
       +<li>The expression "$(readlink -f "$0")" gets the absolute path to the
       +shellscript itself. This is passed as the executable to run for xargs.</li>
       +<li>xargs calls the script itself with the specified parameters it is being fed.
       +The environment variable $CHILD_MODE is set to indicate to the script itself
       +it is run as a child process of the script.</li>
       +</ul>
       +<p>The child process:</p>
       +<ul>
       +<li><p>The command-line arguments are passed by the parent using xargs.</p>
       +</li>
       +<li><p>The environment variable $CHILD_MODE is set to indicate to the script itself
       +it is run as a child process of the script.</p>
       +</li>
       +<li><p>The script itself (ran in child-mode process) only executes the task and
       +signals its status back to xargs and the parent.</p>
       +</li>
       +<li><p>The exit status of the child program is signaled to xargs. This could be
       +handled, for example to stop on the first failure (in this example it is not).
       +For example if the program is killed, stopped or the exit status is 255 then
       +xargs stops running also.</p>
       +</li>
       +</ul>
       +<h1>Description of used xargs options</h1>
       +<p>From the OpenBSD man page: <a href="https://man.openbsd.org/xargs">https://man.openbsd.org/xargs</a></p>
       +<pre><code>xargs - construct argument list(s) and execute utility
       +</code></pre>
       +<p>Options explained:</p>
       +<ul>
       +<li>-r: Do not run the command if there are no arguments. Normally the command
       +is executed at least once even if there are no arguments.</li>
       +<li>-0: Change xargs to expect NUL ('\0') characters as separators, instead of
       +spaces and newlines.</li>
       +<li>-P maxprocs: Parallel mode: run at most maxprocs invocations of utility
       +at once.</li>
       +<li>-L number: Call utility for every number of non-empty lines read. A line
       +ending in unescaped white space and the next non-empty line are considered
       +to form one single line. If EOF is reached and fewer than number lines have
       +been read then utility will be called with the available lines.</li>
       +</ul>
       +<h1>xargs options -0 and -P, portability and historic context</h1>
       +<p>Some of the options, like -P are as of writing (2023) non-POSIX:
       +<a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/xargs.html">https://pubs.opengroup.org/onlinepubs/9699919799/utilities/xargs.html</a>.
       +However many systems support this useful extension for many years now.</p>
       +<p>The specification even mentions implementations which support parallel
       +operations:</p>
       +<p>"The version of xargs required by this volume of POSIX.1-2017 is required to
       +wait for the completion of the invoked command before invoking another command.
       +This was done because historical scripts using xargs assumed sequential
       +execution. Implementations wanting to provide parallel operation of the invoked
       +utilities are encouraged to add an option enabling parallel invocation, but
       +should still wait for termination of all of the children before xargs
       +terminates normally."</p>
       +<p>Some historic context:</p>
       +<p>The xargs -0 option was added on 1996-06-11 by Theo de Raadt, about a year
       +after the NetBSD import (over 27 years ago at the time of writing):</p>
       +<p><a href="http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/xargs/xargs.c?rev=1.2&amp;content-type=text/x-cvsweb-markup">CVS log</a></p>
       +<p>On OpenBSD the xargs -P option was added on 2003-12-06 by syncing the FreeBSD
       +code:</p>
       +<p><a href="http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/xargs/xargs.c?rev=1.14&amp;content-type=text/x-cvsweb-markup">CVS log</a></p>
       +<p>Looking at the imported git history log of GNU findutils (which has xargs), the
       +very first commit already had the -0 and -P option:</p>
       +<p><a href="https://savannah.gnu.org/git/?group=findutils">git log</a></p>
       +<pre><code>commit c030b5ee33bbec3c93cddc3ca9ebec14c24dbe07
       +Author: Kevin Dalley &lt;kevin@seti.org&gt;
       +Date:   Sun Feb 4 20:35:16 1996 +0000
       +
       +    Initial revision
       +</code></pre>
       +<h1>xargs: some incompatibilities found</h1>
       +<ul>
       +<li>Using the -0 option empty fields are handled differently in different
       +implementations.</li>
       +<li>The -n and -L option doesn't work correctly in many of the BSD implementations.
       +Some count empty fields, some don't.  In early implementations in FreeBSD and
       +OpenBSD it only processed the first line.  In OpenBSD it has been improved
       +around 2017.</li>
       +</ul>
       +<p>Depending on what you want to do a workaround could be to use the -0 option
       +with a single field and use the -n flag.  Then in each child program invocation
       +split the field by a separator.</p>
       +<h1>References</h1>
       +<ul>
       +<li>xargs: <a href="https://man.openbsd.org/xargs">https://man.openbsd.org/xargs</a></li>
       +<li>printf: <a href="https://man.openbsd.org/printf">https://man.openbsd.org/printf</a></li>
       +<li>ksh, wait: <a href="https://man.openbsd.org/ksh#wait">https://man.openbsd.org/ksh#wait</a></li>
       +<li>wait(2): <a href="https://man.openbsd.org/wait">https://man.openbsd.org/wait</a></li>
       +</ul>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>Improved Youtube RSS/Atom feed</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/youtube-feed" />
       +        <id>gopher://codemadness.org/1/phlog/youtube-feed</id>
       +        <updated>2023-11-20T00:00:00Z</updated>
       +        <published>2023-11-20T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Improved Youtube Atom feed by adding video duration and filtering away shorts</summary>
       +        <content type="html"><![CDATA[<h1>Improved Youtube RSS/Atom feed</h1>
       +        <p><strong>Last modification on </strong> <time>2023-11-20</time></p>
       +        <p>... improved at least for my preferences ;)</p>
       +<p>It scrapes the channel data from Youtube and combines it with the parsed Atom
       +feed from the channel on Youtube.</p>
       +<p>The Atom parser is based on sfeed, with some of the code removed because it is
       +not needed by this program.  It scrapes the metadata of the videos from the
       +channel its HTML page and uses my custom JSON parser to convert the
       +Javascript/JSON structure.</p>
       +<p>This parser is also used by the <a href="json2tsv.html">json2tsv</a> tool. It has few dependencies.</p>
       +<h2>Features</h2>
       +<ul>
       +<li>Add the video duration to the title to quickly see how long the video is.</li>
       +<li>Filter away Youtube shorts and upcoming videos / announcements: only videos are shown.</li>
       +<li>Supports more output formats: Atom, <a href="https://www.jsonfeed.org/version/1.1/">JSON Feed</a> or
       +<a href="sfeed.1.txt">sfeed</a> Tab-Separated-Value format.</li>
       +<li>Easy to build and deploy: can be run as a CGI program as a static-linked
       +binary in a chroot.</li>
       +<li>Secure: additionally to running in a chroot it can use pledge(2) and unveil(2)
       +on OpenBSD to restrict system calls and access to the filesystem.</li>
       +</ul>
       +<h2>How to use</h2>
       +<p>There is an option to run directly from the command-line or in CGI-mode.  When
       +the environment variable $REQUEST_URI is set then it is automatically run in
       +CGI mode.</p>
       +<p>Command-line usage:</p>
       +<pre><code>youtube_feed channelid atom
       +youtube_feed channelid gph
       +youtube_feed channelid html
       +youtube_feed channelid json
       +youtube_feed channelid tsv
       +youtube_feed channelid txt
       +</code></pre>
       +<p>CGI program usage:</p>
       +<p>The last basename part of the URL should be the channelid + the output format
       +extension. It defaults to TSV if there is no extension.
       +The CGI program can be used with a HTTPd or a Gopher daemon such as geomyidae.</p>
       +<p>For example:</p>
       +<pre><code>Atom XML:     https://codemadness.org/yt-chan/UCrbvoMC0zUvPL8vjswhLOSw.xml
       +HTML:         https://codemadness.org/yt-chan/UCrbvoMC0zUvPL8vjswhLOSw.html
       +JSON:         https://codemadness.org/yt-chan/UCrbvoMC0zUvPL8vjswhLOSw.json
       +TSV:          https://codemadness.org/yt-chan/UCrbvoMC0zUvPL8vjswhLOSw.tsv
       +twtxt:        https://codemadness.org/yt-chan/UCrbvoMC0zUvPL8vjswhLOSw.txt
       +TSV, default: https://codemadness.org/yt-chan/UCrbvoMC0zUvPL8vjswhLOSw
       +
       +Gopher dir:   gopher://codemadness.org/1/feed.cgi/UCrbvoMC0zUvPL8vjswhLOSw.gph
       +Gopher TSV:   gopher://codemadness.org/0/feed.cgi/UCrbvoMC0zUvPL8vjswhLOSw
       +</code></pre>
       +<p>An OpenBSD httpd.conf using slowcgi as an example:</p>
       +<pre><code>server "codemadness.org" {
       +        location "/yt-chan/*" {
       +                request strip 1
       +                root "/cgi-bin/yt-chan"
       +                fastcgi socket "/run/slowcgi.sock"
       +        }
       +}
       +</code></pre>
       +<h2>Using it with <a href="sfeed.html">sfeed</a></h2>
       +<p>sfeedrc example of an existing Youtube RSS/Atom feed:</p>
       +<pre><code># list of feeds to fetch:
       +feeds() {
       +        # feed &lt;name&gt; &lt;feedurl&gt; [basesiteurl] [encoding]
       +        # normal Youtube Atom feed.
       +        feed "yt IM" "https://www.youtube.com/feeds/videos.xml?channel_id=UCrbvoMC0zUvPL8vjswhLOSw"
       +}
       +</code></pre>
       +<p>Use the new Atom feed directly using the CGI-mode and Atom output format:</p>
       +<pre><code># list of feeds to fetch:
       +feeds() {
       +        # feed &lt;name&gt; &lt;feedurl&gt; [basesiteurl] [encoding]
       +        # new Youtube Atom feed.
       +        feed "idiotbox IM" "https://codemadness.org/yt-chan/UCrbvoMC0zUvPL8vjswhLOSw.xml"
       +}
       +</code></pre>
       +<p>... or convert directly using a custom connector program on the local system via the command-line:</p>
       +<pre><code># fetch(name, url, feedfile)
       +fetch() {
       +        case "$1" in
       +        "connector example")
       +                youtube_feed "$2";;
       +        *)
       +                curl -L --max-redirs 0 -H "User-Agent:" -f -s -m 15 \
       +                        "$2" 2&gt;/dev/null;;
       +        esac
       +}
       +
       +# parse and convert input, by default XML to the sfeed(5) TSV format.
       +# parse(name, feedurl, basesiteurl)
       +parse() {
       +        case "$1" in
       +        "connector example")
       +                cat;;
       +        *)
       +                sfeed "$3";;
       +        esac
       +}
       +
       +# list of feeds to fetch:
       +feeds() {
       +        # feed &lt;name&gt; &lt;feedurl&gt; [basesiteurl] [encoding]
       +        feed "connector example" "UCrbvoMC0zUvPL8vjswhLOSw"
       +}
       +</code></pre>
       +<h2>Screenshot using sfeed_curses</h2>
       +<p><a href="downloads/screenshots/sfeed_curses_youtube.png"><img src="downloads/screenshots/sfeed_curses_youtube.png" alt="Screenshot showing the improved Youtube feed" width="480" height="270" loading="lazy" /></a></p>
       +<h2>Clone</h2>
       +<pre><code>git clone git://git.codemadness.org/frontends
       +</code></pre>
       +<h2>Browse</h2>
       +<p>You can browse the source-code at:</p>
       +<ul>
       +<li><a href="https://git.codemadness.org/frontends/file/youtube/feed.c.html">https://git.codemadness.org/frontends/file/youtube/feed.c.html</a></li>
       +<li><a href="gopher://codemadness.org/1/git/frontends/file/youtube/feed.c.gph">gopher://codemadness.org/1/git/frontends/file/youtube/feed.c.gph</a></li>
       +</ul>
       +<p>The program is: youtube/feed</p>
       +<h2>Dependencies</h2>
       +<ul>
       +<li>C compiler.</li>
       +<li>LibreSSL + libtls.</li>
       +</ul>
       +<h2>Build and install</h2>
       +<pre><code>$ make
       +# make install
       +</code></pre>
       +<h2>That's all</h2>
       +<p>I hope by sharing this it is useful to someone other than me as well.</p>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>webdump HTML to plain-text converter</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/webdump" />
       +        <id>gopher://codemadness.org/1/phlog/webdump</id>
       +        <updated>2025-04-25T00:00:00Z</updated>
       +        <published>2023-11-20T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>webdump HTML to plain-text converter</summary>
       +        <content type="html"><![CDATA[<h1>webdump HTML to plain-text converter</h1>
       +        <p><strong>Last modification on </strong> <time>2025-04-25</time></p>
       +        <p>webdump is (yet another) HTML to plain-text converter tool.</p>
       +<p>It reads HTML in UTF-8 from stdin and writes plain-text to stdout.</p>
       +<h2>Goals and scope</h2>
       +<p>The main goal of this tool for me is to use it for converting HTML mails to
       +plain-text and to convert HTML content in RSS feeds to plain-text.</p>
       +<p>The tool will only convert HTML to stdout, similarly to links -dump or lynx
       +-dump but simpler and more secure.</p>
       +<ul>
       +<li>HTML and XHTML will be supported.</li>
       +<li>There will be some workarounds and quirks for broken and legacy HTML code.</li>
       +<li>It will be usable and secure for reading HTML from mails and RSS/Atom feeds.</li>
       +<li>No remote resources which are part of the HTML will be downloaded:
       +images, video, audio, etc. But these may be visible as a link reference.</li>
       +<li>Data will be written to stdout. Intended for plain-text or a text terminal.</li>
       +<li>No support for Javascript, CSS, frame rendering or form processing.</li>
       +<li>No HTTP or network protocol handling: HTML data is read from stdin.</li>
       +<li>Listings for references and some options to extract them in a list that is
       +usable for scripting. Some references are: link anchors, images, audio, video,
       +HTML (i)frames, etc.</li>
       +<li>Security: on OpenBSD it uses pledge("stdio", NULL).</li>
       +<li>Keep the code relatively small, simple and hackable.</li>
       +</ul>
       +<h2>Features</h2>
       +<ul>
       +<li>Support for word-wrapping.</li>
       +<li>A mode to enable basic markup: bold, underline, italic and blink ;)</li>
       +<li>Indentation of headers, paragraphs, pre and list items.</li>
       +<li>Basic support to query elements or hide them.</li>
       +<li>Show link references.</li>
       +<li>Show link references and resources such as img, video, audio, subtitles.</li>
       +<li>Export link references and resources to a TAB-separated format.</li>
       +</ul>
       +<h2>Usage examples</h2>
       +<pre><code>url='https://codemadness.org/sfeed.html'
       +
       +curl -s "$url" | webdump -r -b "$url" | less
       +
       +curl -s "$url" | webdump -8 -a -i -l -r -b "$url" | less -R
       +
       +curl -s "$url" | webdump -s 'main' -8 -a -i -l -r -b "$url" | less -R
       +</code></pre>
       +<p>Yes, all these option flags look ugly, a shellscript wrapper could be used :)</p>
       +<h2>Practical examples</h2>
       +<p>To use webdump as a HTML to text filter for example in the mutt mail client,
       +change in ~/.mailcap:</p>
       +<pre><code>text/html; webdump -i -l -r &lt; %s; needsterminal; copiousoutput
       +</code></pre>
       +<p>In mutt you should then add:</p>
       +<pre><code>auto_view text/html
       +</code></pre>
       +<p>Using webdump as a HTML to text filter for sfeed_curses (otherwise the default is lynx):</p>
       +<pre><code>SFEED_HTMLCONV="webdump -d -8 -r -i -l -a" sfeed_curses ~/.sfeed/feeds/*
       +</code></pre>
       +<h1>Query/selector examples</h1>
       +<p>The query syntax using the -s option is a bit inspired by CSS (but much more limited).</p>
       +<p>To get the title from a HTML page:</p>
       +<pre><code>url='https://codemadness.org/sfeed.html'
       +
       +title=$(curl -s "$url" | webdump -s 'title')
       +printf '%s\n' "$title"
       +</code></pre>
       +<p>List audio and video-related content from a HTML page, redirect fd 3 to fd 1 (stdout):</p>
       +<pre><code>url="https://media.ccc.de/v/051_Recent_features_to_OpenBSD-ntpd_and_bgpd"
       +curl -s "$url" | webdump -x -s 'audio,video' -b "$url" 3&gt;&amp;1 &gt;/dev/null | cut -f 2
       +</code></pre>
       +<h2>Clone</h2>
       +<pre><code>git clone git://git.codemadness.org/webdump
       +</code></pre>
       +<h2>Browse</h2>
       +<p>You can browse the source-code at:</p>
       +<ul>
       +<li><a href="https://git.codemadness.org/webdump/">https://git.codemadness.org/webdump/</a></li>
       +<li><a href="gopher://codemadness.org/1/git/webdump">gopher://codemadness.org/1/git/webdump</a></li>
       +</ul>
       +<h2>Download releases</h2>
       +<p>Releases are available at:</p>
       +<ul>
       +<li><a href="https://codemadness.org/releases/webdump/">https://codemadness.org/releases/webdump/</a></li>
       +<li><a href="gopher://codemadness.org/1/releases/webdump">gopher://codemadness.org/1/releases/webdump</a></li>
       +</ul>
       +<h2>Build and install</h2>
       +<pre><code>$ make
       +# make install
       +</code></pre>
       +<h2>Dependencies</h2>
       +<ul>
       +<li>C compiler.</li>
       +<li>libc + some BSDisms.</li>
       +</ul>
       +<h2>Trade-offs</h2>
       +<p>All software has trade-offs.</p>
       +<p>webdump processes HTML in a single-pass. It does not buffer the full DOM tree.
       +Although due to the nature of HTML/XML some parts like attributes need to be
       +buffered.</p>
       +<p>Rendering tables in webdump is very limited. Twibright Links has really nice
       +table rendering. However implementing a similar feature in the current design of
       +webdump would make the code much more complex. Twibright links
       +processes a full DOM tree and processes the tables in multiple passes (to
       +measure the table cells) etc.  Of course tables can be nested also, or HTML tables
       +that are used for creating layouts (these are mostly older webpages).</p>
       +<p>These trade-offs and preferences are chosen for now. It may change in the
       +future.  Fortunately there are the usual good suspects for HTML to plain-text
       +conversion, each with their own chosen trade-offs of course:</p>
       +<ul>
       +<li>twibright links: <a href="http://links.twibright.com/">http://links.twibright.com/</a></li>
       +<li>lynx: <a href="https://lynx.invisible-island.net/">https://lynx.invisible-island.net/</a></li>
       +<li>w3m: <a href="https://w3m.sourceforge.net/">https://w3m.sourceforge.net/</a></li>
       +<li>xmllint (part of libxml2): <a href="https://gitlab.gnome.org/GNOME/libxml2/-/wikis/home">https://gitlab.gnome.org/GNOME/libxml2/-/wikis/home</a></li>
       +<li>xmlstarlet: <a href="https://xmlstar.sourceforge.net/">https://xmlstar.sourceforge.net/</a></li>
       +</ul>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>Setup your own mail paste service</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/mailservice" />
       +        <id>gopher://codemadness.org/1/phlog/mailservice</id>
       +        <updated>2024-02-10T00:00:00Z</updated>
       +        <published>2023-10-25T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Setup your own mail paste service using mblaze</summary>
       +        <content type="html"><![CDATA[<h1>Setup your own mail paste service</h1>
       +        <p><strong>Last modification on </strong> <time>2024-02-10</time></p>
       +        <h2>How it works</h2>
       +<ul>
       +<li>The user sends a mail with an attachment to a certain mail address, for
       +example: paste@somehost.org</li>
       +<li>The mail daemon configuration has an mail alias to pipe the raw mail to a
       +shellscript.</li>
       +<li>This shellscript processes the raw mail contents from stdin.</li>
       +</ul>
       +<h2>What it does</h2>
       +<ul>
       +<li>Process a mail with the attachments automatically.</li>
       +<li>The script processes the attachments in the mail and stores them.</li>
       +<li>It will mail (back) the URL where the file(s) are stored.</li>
       +</ul>
       +<p>This script is tested on OpenBSD using OpenBSD smtpd and OpenBSD httpd and the
       +gopher daemon geomyidae.</p>
       +<h2>Install dependencies</h2>
       +<p>On OpenBSD:</p>
       +<pre><code>pkg_add mblaze
       +</code></pre>
       +<h2>smtpd mail configuration</h2>
       +<p>In your mail aliases (for example /etc/mail/aliases) put:</p>
       +<pre><code>paste: |/usr/local/bin/paste-mail
       +</code></pre>
       +<p>This pipes the mail to the script paste-mail for processing, this script is
       +described below. Copy the below contents in /usr/local/bin/paste-mail</p>
       +<p>Script:</p>
       +<pre><code>#!/bin/sh
       +
       +d="/home/www/domains/www.codemadness.org/htdocs/mailpaste"
       +tmpmsg=$(mktemp)
       +tmpmail=$(mktemp)
       +
       +cleanup() {
       +        rm -f "$tmpmail" "$tmpmsg"
       +}
       +
       +# store whole mail from stdin temporarily, on exit remove temporary file.
       +trap "cleanup" EXIT
       +cat &gt; "$tmpmail"
       +
       +# mblaze: don't store mail sequence.
       +MAILSEQ=/dev/null
       +export MAILSEQ
       +
       +# get from address (without display name).
       +from=$(maddr -a -h 'From' /dev/stdin &lt; "$tmpmail")
       +
       +# check if allowed or not.
       +case "$from" in
       +"hiltjo@codemadness.org")
       +        ;;
       +*)
       +        exit 0;;
       +esac
       +
       +# prevent mail loop.
       +if printf '%s' "$from" | grep -q "paste@"; then
       +        exit 0
       +fi
       +
       +echo "Thank you for using the enterprise paste service." &gt; "$tmpmsg"
       +echo "" &gt;&gt; "$tmpmsg"
       +echo "Your file(s) are available at:" &gt;&gt; "$tmpmsg"
       +echo "" &gt;&gt; "$tmpmsg"
       +
       +# process each attachment.
       +mshow -n -q -t /dev/stdin &lt; "$tmpmail" | sed -nE 's@.*name="(.*)".*@\1@p' | while read -r name; do
       +        test "$name" = "" &amp;&amp; continue
       +
       +        # extract attachment.
       +        tmpfile=$(mktemp -p "$d" XXXXXXXXXXXX)
       +        mshow -n -O /dev/stdin "$name" &lt; "$tmpmail" &gt; "$tmpfile"
       +
       +        # use file extension.
       +        ext="${name##*/}"
       +        case "$ext" in
       +        *.tar.*)
       +                # special case: support .tar.gz, tar.bz2, etc.
       +                ext="tar.${ext##*.}";;
       +        *.*)
       +                ext="${ext##*.}";;
       +        *)
       +                ext="";;
       +        esac
       +        ext="${ext%%*.}"
       +
       +        # use file extension if it is set.
       +        outputfile="$tmpfile"
       +        if test "$ext" != ""; then
       +                outputfile="$tmpfile.$ext"
       +        fi
       +        mv "$tmpfile" "$outputfile"
       +        b=$(basename "$outputfile")
       +
       +        chmod 666 "$outputfile"
       +        url="gopher://codemadness.org/9/mailpaste/$b"
       +
       +        echo "$name:" &gt;&gt; "$tmpmsg"
       +        echo "        Text   file: gopher://codemadness.org/0/mailpaste/$b" &gt;&gt; "$tmpmsg"
       +        echo "        Image  file: gopher://codemadness.org/I/mailpaste/$b" &gt;&gt; "$tmpmsg"
       +        echo "        Binary file: gopher://codemadness.org/9/mailpaste/$b" &gt;&gt; "$tmpmsg"
       +        echo "" &gt;&gt; "$tmpmsg"
       +done
       +
       +echo "" &gt;&gt; "$tmpmsg"
       +echo "Sincerely," &gt;&gt; "$tmpmsg"
       +echo "Your friendly paste_bot" &gt;&gt; "$tmpmsg"
       +
       +# mail back the user.
       +mail -r "$from" -s "Your files" "$from" &lt; "$tmpmsg"
       +
       +cleanup
       +</code></pre>
       +<p>The mail daemon processing the mail needs of course to be able to have
       +permissions to write to the specified directory. The user who received the mail
       +needs to be able to read it from a location they can access and have
       +permissions for it also.</p>
       +<h2>Room for improvements</h2>
       +<p>This is just an example script. There is room for many improvements.
       +Feel free to change it in any way you like.</p>
       +<h2>References</h2>
       +<ul>
       +<li><a href="https://man.openbsd.org/aliases">https://man.openbsd.org/aliases</a></li>
       +<li><a href="https://man.openbsd.org/smtpd">https://man.openbsd.org/smtpd</a></li>
       +<li><a href="https://man.openbsd.org/httpd">https://man.openbsd.org/httpd</a></li>
       +<li><a href="https://github.com/leahneukirchen/mblaze">https://github.com/leahneukirchen/mblaze</a></li>
       +</ul>
       +<h2>Bye bye</h2>
       +<p>I hope this enterprise(tm) mail service is inspirational or something ;)</p>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>A simple TODO application</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/todo" />
       +        <id>gopher://codemadness.org/1/phlog/todo</id>
       +        <updated>2022-07-01T00:00:00Z</updated>
       +        <published>2022-07-01T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>A simple TODO application workflow</summary>
       +        <content type="html"><![CDATA[<h1>A simple TODO application</h1>
       +        <p><strong>Last modification on </strong> <time>2022-07-01</time></p>
       +        <p>This article describes a TODO application or workflow.</p>
       +<h2>Workflow</h2>
       +<p>It works like this:</p>
       +<ul>
       +<li>Open any text editor.</li>
       +<li>Edit the text.</li>
       +<li>Save it in a file (probably named "TODO").</li>
       +<li>Feel happy about it.</li>
       +</ul>
       +<h2>The text format</h2>
       +<p>The text format I use is this:</p>
       +<pre><code>[indendations]&lt;symbol&gt;&lt;SPACE&gt;&lt;item text&gt;&lt;NEWLINE&gt;
       +</code></pre>
       +<p>Most of the time an item is just one line.
       +This format is just a general guideline to keep the items somewhat structured.</p>
       +<h2>Symbols</h2>
       +<p>Items are prefixed with a symbol.</p>
       +<ul>
       +<li>- is an item which is planned to be done at some point.</li>
       +<li>x is an item which is done.</li>
       +<li>? is an item which I'm not (yet) sure about. It can also be an idea.</li>
       +</ul>
       +<p>I use an indendation with a TAB before an item to indicate item dependencies.
       +The items can be nested.</p>
       +<p>For the prioritization I put the most important items and sections from the top
       +to the bottom. These can be reshuffled as you wish of course.</p>
       +<p>To delete an item you remove the line. To archive an item you keep the line.</p>
       +<h2>Sections</h2>
       +<p>A section is a line which has no symbol. This is like a header to group items.</p>
       +<h2>Example</h2>
       +<pre><code>Checklist for releasing project 0.1:
       +- Test project with different compilers and check for warnings.
       +- Documentation:
       +        - Proofread and make sure it matches all program behaviour.
       +        - Run mandoc -Tlint on the man pages.
       +        ? Copy useful examples from the README file to the man page?
       +- Run testsuite and check for failures before release.
       +
       +
       +project 0.2:
       +? Investigate if feature mentioned by some user is worth adding.
       +</code></pre>
       +<h1>Example: secure remote cloud-encrypted edit session(tm)</h1>
       +<pre><code>ssh -t host 'ed TODO'
       +</code></pre>
       +<h1>Example: multi-user secure remote cloud-encrypted edit session(tm)</h1>
       +<pre><code>ssh host
       +tmux or tmux a
       +ed TODO
       +</code></pre>
       +<h1>Example: version-controlled multi-user secure remote cloud-encrypted edit session(tm)</h1>
       +<pre><code>ssh host
       +tmux or tmux a
       +ed TODO
       +git add TODO
       +git commit -m 'TODO: update'
       +</code></pre>
       +<h2>Pros</h2>
       +<ul>
       +<li>When you open the TODO file the most important items are at the top.</li>
       +<li>The items are easy to read and modify with any text editor.</li>
       +<li>It is easy to extend the format and use with other text tools.</li>
       +<li>The format is portable: it works on sticky-notes on your CRT monitor too!</li>
       +<li>No monthly online subscription needed and full NO-money-back guarantee.</li>
       +</ul>
       +<h2>Cons</h2>
       +<ul>
       +<li>Complex lists with interconnected dependencies might not work, maybe.</li>
       +<li>It's assumed there is one person maintaining the TODO file. Merging items
       +from multiple people at the same time in this workflow is not recommended.</li>
       +<li>It is too simple: noone will be impressed by it.</li>
       +</ul>
       +<p>I hope this is inspirational or something,</p>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>2FA TOTP without crappy authenticator apps</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/totp" />
       +        <id>gopher://codemadness.org/1/phlog/totp</id>
       +        <updated>2022-10-29T00:00:00Z</updated>
       +        <published>2022-03-23T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Using 2FA TOTP without crappy authenticator apps</summary>
       +        <content type="html"><![CDATA[<h1>2FA TOTP without crappy authenticator apps</h1>
       +        <p><strong>Last modification on </strong> <time>2022-10-29</time></p>
       +        <p>This describes how to use 2FA without using crappy authenticator "apps" or a
       +mobile device.</p>
       +<h2>Install</h2>
       +<p>On OpenBSD:</p>
       +<pre><code>pkg_add oath-toolkit zbar
       +</code></pre>
       +<p>On Void Linux:</p>
       +<pre><code>xbps-install oath-toolkit zbar
       +</code></pre>
       +<p>There is probably a package for your operating system.</p>
       +<ul>
       +<li>oath-toolkit is used to generate the digits based on the secret key.</li>
       +<li>zbar is used to scan the QR barcode text from the image.</li>
       +</ul>
       +<h2>Steps</h2>
       +<p>Save the QR code image from the authenticator app, website to an image file.
       +Scan the QR code text from the image:</p>
       +<pre><code>zbarimg image.png
       +</code></pre>
       +<p>An example QR code:</p>
       +<p><img src="downloads/2fa/qr.png" alt="QR code example" /></p>
       +<p>The output is typically something like:</p>
       +<pre><code>QR-Code:otpauth://totp/Example:someuser@codemadness.org?secret=SECRETKEY&amp;issuer=Codemadness
       +</code></pre>
       +<p>You only need to scan this QR-code for the secret key once.
       +Make sure to store the secret key in a private safe place and don't show it to
       +anyone else.</p>
       +<p>Using the secret key the following command outputs a 6-digit code by default.
       +In this example we also assume the key is base32-encoded.
       +There can be other parameters and options, this is documented in the Yubico URI
       +string format reference below.</p>
       +<p>Command:</p>
       +<pre><code>oathtool --totp -b SOMEKEY
       +</code></pre>
       +<ul>
       +<li>The --totp option uses the time-variant TOTP mode, by default it uses HMAC SHA1.</li>
       +<li>The -b option uses base32 encoding of KEY instead of hex.</li>
       +</ul>
       +<p>Tip: you can create a script that automatically puts the digits in the
       +clipboard, for example:</p>
       +<pre><code>oathtool --totp -b SOMEKEY | xclip
       +</code></pre>
       +<h2>References</h2>
       +<ul>
       +<li><a href="https://linux.die.net/man/1/zbarimg">zbarimg(1) man page</a></li>
       +<li><a href="https://www.nongnu.org/oath-toolkit/man-oathtool.html">oathtool(1) man page</a></li>
       +<li><a href="https://datatracker.ietf.org/doc/html/rfc6238">RFC6238 - TOTP: Time-Based One-Time Password Algorithm</a></li>
       +<li><a href="https://docs.yubico.com/yesdk/users-manual/application-oath/uri-string-format.html">Yubico.com - otpauth URI string format</a></li>
       +</ul>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>Setup an OpenBSD RISCV64 VM in QEMU</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/openbsd-riscv64-vm" />
       +        <id>gopher://codemadness.org/1/phlog/openbsd-riscv64-vm</id>
       +        <updated>2021-10-26T00:00:00Z</updated>
       +        <published>2021-10-23T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Setup an OpenBSD RISCV-64 VM in QEMU</summary>
       +        <content type="html"><![CDATA[<h1>Setup an OpenBSD RISCV64 VM in QEMU</h1>
       +        <p><strong>Last modification on </strong> <time>2021-10-26</time></p>
       +        <p>This describes how to setup an OpenBSD RISCV64 VM in QEMU.</p>
       +<p>The shellscript below does the following:</p>
       +<ul>
       +<li>Set up the disk image (raw format).</li>
       +<li>Patch the disk image with the OpenBSD miniroot file for the installation.</li>
       +<li>Downloads the opensbi and u-boot firmware files for qemu.</li>
       +<li>Run the VM with the supported settings.</li>
       +</ul>
       +<p>The script is tested on the host GNU/Void Linux and OpenBSD-current.</p>
       +<p><strong>IMPORTANT!: The signature and checksum for the miniroot, u-boot and opensbi
       +files are not verified. If the host is OpenBSD make sure to instead install the
       +packages (pkg_add u-boot-riscv64 opensbi) and adjust the firmware path for the
       +qemu -bios and -kernel options. </strong></p>
       +<h2>Shellscript</h2>
       +<pre><code>#!/bin/sh
       +# mirror list: https://www.openbsd.org/ftp.html
       +mirror="https://ftp.bit.nl/pub/OpenBSD/"
       +release="7.0"
       +minirootname="miniroot70.img"
       +
       +miniroot() {
       +        test -f "${minirootname}" &amp;&amp; return # download once
       +
       +        url="${mirror}/${release}/riscv64/${minirootname}"
       +        curl -o "${minirootname}" "${url}"
       +}
       +
       +createrootdisk() {
       +        test -f disk.raw &amp;&amp; return # create once
       +        qemu-img create disk.raw 10G # create 10 GB disk
       +        dd conv=notrunc if=${minirootname} of=disk.raw # write miniroot to disk
       +}
       +
       +opensbi() {
       +        f="opensbi.tgz"
       +        test -f "${f}" &amp;&amp; return # download and extract once.
       +
       +        url="${mirror}/${release}/packages/amd64/opensbi-0.9p0.tgz"
       +        curl -o "${f}" "${url}"
       +
       +        tar -xzf "${f}" share/opensbi/generic/fw_jump.bin
       +}
       +
       +uboot() {
       +        f="uboot.tgz"
       +        test -f "${f}" &amp;&amp; return # download and extract once.
       +
       +        url="${mirror}/${release}/packages/amd64/u-boot-riscv64-2021.07p0.tgz"
       +        curl -o "${f}" "${url}"
       +
       +        tar -xzf "${f}" share/u-boot/qemu-riscv64_smode/u-boot.bin
       +}
       +
       +setup() {
       +        miniroot
       +        createrootdisk
       +        opensbi
       +        uboot
       +}
       +
       +run() {
       +        qemu-system-riscv64 \
       +                -machine virt \
       +                -nographic \
       +                -m 2048M \
       +                -smp 2 \
       +                -bios share/opensbi/generic/fw_jump.bin \
       +                -kernel share/u-boot/qemu-riscv64_smode/u-boot.bin \
       +                -drive file=disk.raw,format=raw,id=hd0 -device virtio-blk-device,drive=hd0 \
       +                -netdev user,id=net0,ipv6=off -device virtio-net-device,netdev=net0
       +}
       +
       +setup
       +run
       +</code></pre>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>Sfeed_curses: a curses UI front-end for sfeed</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/sfeed_curses" />
       +        <id>gopher://codemadness.org/1/phlog/sfeed_curses</id>
       +        <updated>2022-05-08T00:00:00Z</updated>
       +        <published>2020-06-25T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Sfeed_curses is a curses UI front-end for the sfeed RSS/Atom parser</summary>
       +        <content type="html"><![CDATA[<h1>Sfeed_curses: a curses UI front-end for sfeed</h1>
       +        <p><strong>Last modification on </strong> <time>2022-05-08</time></p>
       +        <p>sfeed_curses is a curses UI front-end for <a href="sfeed.html">sfeed</a>.
       +It is now part of sfeed.</p>
       +<p>It shows the TAB-separated feed items in a graphical command-line UI.  The
       +interface has a look inspired by the <a href="http://www.mutt.org/">mutt mail client</a>. It has a sidebar
       +panel for the feeds, a panel with a listing of the items and a small statusbar
       +for the selected item/URL. Some functions like searching and scrolling are
       +integrated in the interface itself.</p>
       +<h2>Features</h2>
       +<ul>
       +<li>Relatively few LOC, about 2.5K lines of C.</li>
       +<li>Few dependencies: a C compiler and a curses library (typically ncurses).
       +It also requires a terminal (emulator) supporting UTF-8.</li>
       +<li>Easy to customize by modifying the small source-code and shellscripts.</li>
       +<li>Quite fast.</li>
       +<li>Plumb support: open the URL or an enclosure URL directly with any program.</li>
       +<li>Pipe support: pipe the selected Tab-Separated Value line to a program for
       +scripting purposes. Like viewing the content in any way you like.</li>
       +<li>Yank support: copy the URL or an enclosure URL to the clipboard.</li>
       +<li>Familiar keybinds: supports both vi-like, emacs-like and arrow keys for
       +actions.</li>
       +<li>Mouse support: it supports xterm X10 and extended SGR encoding.</li>
       +<li>Support two ways of managing read/unread items.
       +By default sfeed_curses marks the feed items of the last day as new/bold.
       +Alternatively a simple plain-text list with the read URLs can be used.</li>
       +<li>UI layouts: supports vertical, horizontal and monocle (full-screen) layouts.
       +Useful for different kind of screen sizes.</li>
       +<li>Auto-execute keybind commands at startup to automate setting a preferred
       +layout, toggle showing new items or other actions.</li>
       +</ul>
       +<p>Like the format programs included in sfeed you can run it by giving the feed
       +files as arguments like this:</p>
       +<pre><code>sfeed_curses ~/.sfeed/feeds/*
       +</code></pre>
       +<p>... or by reading directly from stdin:</p>
       +<pre><code>sfeed_curses &lt; ~/.sfeed/feeds/xkcd
       +</code></pre>
       +<p>It will show a sidebar if one or more files are specified as parameters. It
       +will not show the sidebar by default when reading from stdin.</p>
       +<p><a href="downloads/screenshots/sfeed_curses_screenshot.png"><img src="downloads/screenshots/sfeed_curses_screenshot.png" alt="Screenshot showing what the UI looks" width="480" height="270" loading="lazy" /></a></p>
       +<p>On pressing the 'o' or ENTER keybind it will open the link URL of an item with
       +the plumb program.  On pressing the 'a', 'e' or '@' keybind it will open the
       +enclosure URL if there is one.  The default plumb program is set to <a href="https://portland.freedesktop.org/doc/xdg-open.html">xdg-open</a>,
       +but can be modified by setting the environment variable $SFEED_PLUMBER.  The
       +plumb program receives the URL as a command-line argument.</p>
       +<p>The TAB-Separated-Value line of the current selected item in the feed file can
       +be piped to a program by pressing the 'c', 'p' or '|' keybind. This allows much
       +flexibility to make a content formatter or write other custom actions or views.
       +This line is in the exact same format as described in the sfeed(5) man page.</p>
       +<p>The pipe program can be changed by setting the environment variable
       +$SFEED_PIPER.</p>
       +<p><a href="downloads/screenshots/sfeed_curses_pipe_screenshot.png"><img src="downloads/screenshots/sfeed_curses_pipe_screenshot.png" alt="Screenshot showing the output of the pipe content script" width="480" height="270" loading="lazy" /></a></p>
       +<p>The above screenshot shows the included <a href="https://git.codemadness.org/sfeed_curses/file/sfeed_content.html">sfeed_content</a> shellscript which uses
       +the <a href="https://invisible-island.net/lynx/">lynx text-browser</a> to convert HTML to plain-text.  It pipes the formatted
       +plain-text to the user $PAGER (or "less").</p>
       +<p>Of course the script can be easily changed to use a different browser or
       +HTML-to-text converter like:</p>
       +<ul>
       +<li><a href="https://www.dillo.org/">dillo</a></li>
       +<li><a href="http://www.jikos.cz/~mikulas/links/">links</a></li>
       +<li><a href="http://w3m.sourceforge.net/">w3m</a></li>
       +<li><a href="https://git.codemadness.org/webdump/file/README.html">webdump</a></li>
       +</ul>
       +<p>It's easy to modify the color-theme by changing the macros in the source-code
       +or set a predefined theme at compile-time. The README file contains information
       +how to set a theme.  On the left a <a href="https://templeos.org/">TempleOS</a>-like color-theme on the right a
       +<a href="https://newsboat.org/">newsboat</a>-like colorscheme.</p>
       +<p><a href="downloads/screenshots/sfeed_curses_theme_screenshot.png"><img src="downloads/screenshots/sfeed_curses_theme_screenshot.png" alt="Screenshot showing a custom colorscheme" width="480" height="270" loading="lazy" /></a></p>
       +<p>It supports a vertical layout, horizontal and monocle (full-screen) layout.
       +This can be useful for different kind of screen sizes.  The keybinds '1', '2'
       +and '3' can be used to switch between these layouts.</p>
       +<p><a href="downloads/screenshots/sfeed_curses_horizontal_screenshot.png"><img src="downloads/screenshots/sfeed_curses_horizontal_screenshot.png" alt="Screenshot showing the horizontal layout" width="480" height="270" loading="lazy" /></a></p>
       +<h2>Clone</h2>
       +<pre><code>git clone git://git.codemadness.org/sfeed
       +</code></pre>
       +<h2>Browse</h2>
       +<p>You can browse the source-code at:</p>
       +<ul>
       +<li><a href="https://git.codemadness.org/sfeed/">https://git.codemadness.org/sfeed/</a></li>
       +<li><a href="gopher://codemadness.org/1/git/sfeed">gopher://codemadness.org/1/git/sfeed</a></li>
       +</ul>
       +<h2>Download releases</h2>
       +<p>Releases are available at:</p>
       +<ul>
       +<li><a href="https://codemadness.org/releases/sfeed/">https://codemadness.org/releases/sfeed/</a></li>
       +<li><a href="gopher://codemadness.org/1/releases/sfeed">gopher://codemadness.org/1/releases/sfeed</a></li>
       +</ul>
       +<h2>Build and install</h2>
       +<pre><code>$ make
       +# make install
       +</code></pre>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>hurl: HTTP, HTTPS and Gopher file grabber</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/hurl" />
       +        <id>gopher://codemadness.org/1/phlog/hurl</id>
       +        <updated>2020-07-20T00:00:00Z</updated>
       +        <published>2019-11-10T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>hurl: HTTP, HTTPS and Gopher file grabber</summary>
       +        <content type="html"><![CDATA[<h1>hurl: HTTP, HTTPS and Gopher file grabber</h1>
       +        <p><strong>Last modification on </strong> <time>2020-07-20</time></p>
       +        <p>hurl is a relatively simple HTTP, HTTPS and Gopher client/file grabber.</p>
       +<h2>Why?</h2>
       +<p>Sometimes (or most of the time?) you just want to fetch a file via the HTTP,
       +HTTPS or Gopher protocol.</p>
       +<p>The focus of this tool is only this.</p>
       +<h2>Features</h2>
       +<ul>
       +<li>Uses OpenBSD pledge(2) and unveil(2). Allow no filesystem access (writes to
       +stdout).</li>
       +<li>Impose time-out and maximum size limits.</li>
       +<li>Use well-defined exitcodes for reliable scripting (curl sucks at this).</li>
       +<li>Send as little information as possible (no User-Agent etc by default).</li>
       +</ul>
       +<h2>Anti-features</h2>
       +<ul>
       +<li>No HTTP byte range support.</li>
       +<li>No HTTP User-Agent.</li>
       +<li>No HTTP If-Modified-Since/If-* support.</li>
       +<li>No HTTP auth support.</li>
       +<li>No HTTP/2+ support.</li>
       +<li>No HTTP keep-alive.</li>
       +<li>No HTTP chunked-encoding support.</li>
       +<li>No HTTP redirect support.</li>
       +<li>No (GZIP) compression support.</li>
       +<li>No cookie-jar or cookie parsing support.</li>
       +<li>No Gopher text handling (".\r\n").</li>
       +<li>... etc...</li>
       +</ul>
       +<h2>Dependencies</h2>
       +<ul>
       +<li>C compiler (C99).</li>
       +<li>libc + some BSD functions like err() and strlcat().</li>
       +<li>LibreSSL(-portable)</li>
       +<li>libtls (part of LibreSSL).</li>
       +</ul>
       +<h2>Optional dependencies</h2>
       +<ul>
       +<li>POSIX make(1) (for Makefile).</li>
       +<li>mandoc for documentation: <a href="https://mdocml.bsd.lv/">https://mdocml.bsd.lv/</a></li>
       +</ul>
       +<h2>Clone</h2>
       +<pre><code>git clone git://git.codemadness.org/hurl
       +</code></pre>
       +<h2>Browse</h2>
       +<p>You can browse the source-code at:</p>
       +<ul>
       +<li><a href="https://git.codemadness.org/hurl/">https://git.codemadness.org/hurl/</a></li>
       +<li><a href="gopher://codemadness.org/1/git/hurl">gopher://codemadness.org/1/git/hurl</a></li>
       +</ul>
       +<h2>Download releases</h2>
       +<p>Releases are available at:</p>
       +<ul>
       +<li><a href="https://codemadness.org/releases/hurl/">https://codemadness.org/releases/hurl/</a></li>
       +<li><a href="gopher://codemadness.org/1/releases/hurl">gopher://codemadness.org/1/releases/hurl</a></li>
       +</ul>
       +<h2>Build and install</h2>
       +<pre><code>$ make
       +# make install
       +</code></pre>
       +<h2>Examples</h2>
       +<p>Fetch the Atom feed from this site using a maximum filesize limit of 1MB and
       +a time-out limit of 15 seconds:</p>
       +<pre><code>hurl -m 1048576 -t 15 "https://codemadness.org/atom.xml"
       +</code></pre>
       +<p>There is an -H option to add custom headers. This way some of the anti-features
       +listed above are supported. For example some CDNs like Cloudflare are known to
       +block empty or certain User-Agents.</p>
       +<p>User-Agent:</p>
       +<pre><code>hurl -H 'User-Agent: some browser' 'https://codemadness.org/atom.xml'
       +</code></pre>
       +<p>HTTP Basic Auth (base64-encoded username:password):</p>
       +<pre><code>hurl -H 'Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=' \
       +        'https://codemadness.org/atom.xml'
       +</code></pre>
       +<p>GZIP (this assumes the served response Content-Type is gzip):</p>
       +<pre><code>hurl -H 'Accept-Encoding: gzip' 'https://somesite/' | gzip -d
       +</code></pre>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>json2tsv: a JSON to TSV converter</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/json2tsv" />
       +        <id>gopher://codemadness.org/1/phlog/json2tsv</id>
       +        <updated>2021-09-25T00:00:00Z</updated>
       +        <published>2019-10-13T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>json2tsv: a JSON to TAB-Separated Value converter</summary>
       +        <content type="html"><![CDATA[<h1>json2tsv: a JSON to TSV converter</h1>
       +        <p><strong>Last modification on </strong> <time>2021-09-25</time></p>
       +        <p>Convert JSON to TSV or separated output.</p>
       +<p>json2tsv reads JSON data from stdin.  It outputs each JSON type to a TAB-
       +Separated Value format per line by default.</p>
       +<h2>TAB-Separated Value format</h2>
       +<p>The output format per line is:</p>
       +<pre><code>nodename&lt;TAB&gt;type&lt;TAB&gt;value&lt;LF&gt;
       +</code></pre>
       +<p>Control-characters such as a newline, TAB and backslash (\n, \t and \) are
       +escaped in the nodename and value fields.  Other control-characters are
       +removed.</p>
       +<p>The type field is a single byte and can be:</p>
       +<ul>
       +<li>a for array</li>
       +<li>b for bool</li>
       +<li>n for number</li>
       +<li>o for object</li>
       +<li>s for string</li>
       +<li>? for null</li>
       +</ul>
       +<p>Filtering on the first field "nodename" is easy using awk for example.</p>
       +<h2>Features</h2>
       +<ul>
       +<li>Accepts all <strong>valid</strong> JSON.</li>
       +<li>Designed to work well with existing UNIX programs like awk and grep.</li>
       +<li>Straightforward and not much lines of code: about 475 lines of C.</li>
       +<li>Few dependencies: C compiler (C99), libc.</li>
       +<li>No need to learn a new (meta-)language for processing data.</li>
       +<li>The parser supports code point decoding and UTF-16 surrogates to UTF-8.</li>
       +<li>It does not output control-characters to the terminal for security reasons by
       +default (but it has a -r option if needed).</li>
       +<li>On OpenBSD it supports <a href="https://man.openbsd.org/pledge">pledge(2)</a> for syscall restriction:
       +pledge("stdio", NULL).</li>
       +<li>Supports setting a different field separator and record separator with the -F
       +and -R option.</li>
       +</ul>
       +<h2>Cons</h2>
       +<ul>
       +<li>For the tool there is additional overhead by processing and filtering data
       +from stdin after parsing.</li>
       +<li>The parser does not do complete validation on numbers.</li>
       +<li>The parser accepts some bad input such as invalid UTF-8
       +(see <a href="https://tools.ietf.org/html/rfc8259#section-8.1">RFC8259 - 8.1. Character Encoding</a>).
       +json2tsv reads from stdin and does not do assumptions about a "closed
       +ecosystem" as described in the RFC.</li>
       +<li>The parser accepts some bad JSON input and "extensions"
       +(see <a href="https://tools.ietf.org/html/rfc8259#section-9">RFC8259 - 9. Parsers</a>).</li>
       +<li>Encoded NUL bytes (\u0000) in strings are ignored.
       +(see <a href="https://tools.ietf.org/html/rfc8259#section-9">RFC8259 - 9. Parsers</a>).
       +"An implementation may set limits on the length and character contents of
       +strings."</li>
       +<li>The parser is not the fastest possible JSON parser (but also not the
       +slowest).  For example: for ease of use, at the cost of performance all
       +strings are decoded, even though they may be unused.</li>
       +</ul>
       +<h2>Why Yet Another JSON parser?</h2>
       +<p>I wanted a tool that makes parsing JSON easier and work well from the shell,
       +similar to <a href="https://stedolan.github.io/jq/">jq</a>.</p>
       +<p>sed and grep often work well enough for matching some value using some regex
       +pattern, but it is not good enough to parse JSON correctly or to extract all
       +information: just like parsing HTML/XML using some regex is not good (enough)
       +or a good idea :P.</p>
       +<p>I didn't want to learn a new specific <a href="https://stedolan.github.io/jq/manual/#Builtinoperatorsandfunctions">meta-language</a> which jq has and wanted
       +something simpler.</p>
       +<p>While it is more efficient to embed this query language for data aggregation,
       +it is also less simple. In my opinion it is simpler to separate this and use
       +pattern-processing by awk or an other filtering/aggregating program.</p>
       +<p>For the parser, there are many JSON parsers out there, like the efficient
       +<a href="https://github.com/zserge/jsmn">jsmn parser</a>, however a few parser behaviours I want to have are:</p>
       +<ul>
       +<li>jsmn buffers data as tokens, which is very efficient, but also a bit
       +annoying as an API as it requires another layer of code to interpret the
       +tokens.</li>
       +<li>jsmn does not handle decoding strings by default. Which is very efficient
       +if you don't need parts of the data though.</li>
       +<li>jsmn does not keep context of nested structures by default, so may require
       +writing custom utility functions for nested data.</li>
       +</ul>
       +<p>This is why I went for a parser design that uses a single callback per "node"
       +type and keeps track of the current nested structure in a single array and
       +emits that.</p>
       +<h2>Clone</h2>
       +<pre><code>git clone git://git.codemadness.org/json2tsv
       +</code></pre>
       +<h2>Browse</h2>
       +<p>You can browse the source-code at:</p>
       +<ul>
       +<li><a href="https://git.codemadness.org/json2tsv/">https://git.codemadness.org/json2tsv/</a></li>
       +<li><a href="gopher://codemadness.org/1/git/json2tsv">gopher://codemadness.org/1/git/json2tsv</a></li>
       +</ul>
       +<h2>Download releases</h2>
       +<p>Releases are available at:</p>
       +<ul>
       +<li><a href="https://codemadness.org/releases/json2tsv/">https://codemadness.org/releases/json2tsv/</a></li>
       +<li><a href="gopher://codemadness.org/1/releases/json2tsv">gopher://codemadness.org/1/releases/json2tsv</a></li>
       +</ul>
       +<h2>Build and install</h2>
       +<pre><code>$ make
       +# make install
       +</code></pre>
       +<h2>Examples</h2>
       +<p>An usage example to parse posts of the JSON API of <a href="https://www.reddit.com/">reddit.com</a> and format them
       +to a plain-text list using awk:</p>
       +<pre><code>#!/bin/sh
       +curl -s -H 'User-Agent:' 'https://old.reddit.com/.json?raw_json=1&amp;limit=100' | \
       +json2tsv | \
       +awk -F '\t' '
       +function show() {
       +        if (length(o["title"]) == 0)
       +                return;
       +        print n ". " o["title"] " by " o["author"] " in r/" o["subreddit"];
       +        print o["url"];
       +        print "";
       +}
       +$1 == ".data.children[].data" {
       +        show();
       +        n++;
       +        delete o;
       +}
       +$1 ~ /^\.data\.children\[\]\.data\.[a-zA-Z0-9_]*$/ {
       +        o[substr($1, 23)] = $3;
       +}
       +END {
       +        show();
       +}'
       +</code></pre>
       +<h2>References</h2>
       +<ul>
       +<li>Sites:
       +<ul>
       +<li><a href="http://seriot.ch/parsing_json.php">seriot.ch - Parsing JSON is a Minefield</a></li>
       +<li><a href="https://github.com/nst/JSONTestSuite">A comprehensive test suite for RFC 8259 compliant JSON parsers</a></li>
       +<li><a href="https://json.org/">json.org</a></li>
       +</ul>
       +</li>
       +<li>Current standard:
       +<ul>
       +<li><a href="https://tools.ietf.org/html/rfc8259">RFC8259 - The JavaScript Object Notation (JSON) Data Interchange Format</a></li>
       +<li><a href="https://www.ecma-international.org/publications/standards/Ecma-404.htm">Standard ECMA-404 - The JSON Data Interchange Syntax (2nd edition (December 2017)</a></li>
       +</ul>
       +</li>
       +<li>Historic standards:
       +<ul>
       +<li><a href="https://tools.ietf.org/html/rfc7159">RFC7159 - The JavaScript Object Notation (JSON) Data Interchange Format (obsolete)</a></li>
       +<li><a href="https://tools.ietf.org/html/rfc7158">RFC7158 - The JavaScript Object Notation (JSON) Data Interchange Format (obsolete)</a></li>
       +<li><a href="https://tools.ietf.org/html/rfc4627">RFC4627 - The JavaScript Object Notation (JSON) Data Interchange Format (obsolete, original)</a></li>
       +</ul>
       +</li>
       +</ul>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>OpenBSD: setup a local auto-installation server</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/openbsd-autoinstall" />
       +        <id>gopher://codemadness.org/1/phlog/openbsd-autoinstall</id>
       +        <updated>2020-04-30T00:00:00Z</updated>
       +        <published>2019-04-24T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>OpenBSD: setup a local auto-installation server</summary>
       +        <content type="html"><![CDATA[<h1>OpenBSD: setup a local auto-installation server</h1>
       +        <p><strong>Last modification on </strong> <time>2020-04-30</time></p>
       +        <p>This guide describes how to setup a local mirror and installation/upgrade
       +server that requires little or no input interaction.</p>
       +<h2>Setup a local HTTP mirror</h2>
       +<p>The HTTP mirror will be used to fetch the base sets and (optional) custom sets.
       +In this guide we will assume <strong>192.168.0.2</strong> is the local installation server
       +and mirror, the CPU architecture is amd64 and the OpenBSD release version is
       +6.5.  We will store the files in the directory with the structure:</p>
       +<pre><code>http://192.168.0.2/pub/OpenBSD/6.5/amd64/
       +</code></pre>
       +<p>Create the www serve directory and fetch all sets and install files
       +(if needed to save space *.iso and install65.fs can be skipped):</p>
       +<pre><code>$ cd /var/www/htdocs
       +$ mkdir -p pub/OpenBSD/6.5/amd64/
       +$ cd pub/OpenBSD/6.5/amd64/
       +$ ftp 'ftp://ftp.nluug.nl/pub/OpenBSD/6.5/amd64/*'
       +</code></pre>
       +<p>Verify signature and check some checksums:</p>
       +<pre><code>$ signify -C -p /etc/signify/openbsd-65-base.pub -x SHA256.sig
       +</code></pre>
       +<p>Setup <a href="https://man.openbsd.org/httpd.8">httpd(8)</a> for simple file serving:</p>
       +<pre><code># $FAVORITE_EDITOR /etc/httpd.conf
       +</code></pre>
       +<p>A minimal example config for <a href="https://man.openbsd.org/httpd.conf.5">httpd.conf(5)</a>:</p>
       +<pre><code>server "*" {
       +        listen on * port 80
       +}
       +</code></pre>
       +<p>The default www root directory is: /var/www/htdocs/</p>
       +<p>Enable the httpd daemon to start by default and start it now:</p>
       +<pre><code># rcctl enable httpd
       +# rcctl start httpd
       +</code></pre>
       +<h2>Creating an installation response/answer file</h2>
       +<p>The installer supports loading responses to the installation/upgrade questions
       +from a simple text file. We can do a regular installation and copy the answers
       +from the saved file to make an automated version of it.</p>
       +<p>Do a test installation, at the end of the installation or upgrade when asked the
       +question:</p>
       +<pre><code>Exit to (S)hell, (H)alt or (R)eboot?
       +</code></pre>
       +<p>Type S to go to the shell. Find the response file for an installation and copy
       +it to some USB stick or write down the response answers:</p>
       +<pre><code>cp /tmp/i/install.resp /mnt/usbstick/
       +</code></pre>
       +<p>A response file could be for example:</p>
       +<pre><code>System hostname = testvm
       +Which network interface do you wish to configure = em0
       +IPv4 address for em0 = dhcp
       +IPv6 address for em0 = none
       +Which network interface do you wish to configure = done
       +Password for root account = $2b$10$IqI43aXjgD55Q3nLbRakRO/UAG6SAClL9pyk0vIUpHZSAcLx8fWk.
       +Password for user testuser = $2b$10$IqI43aXjgD55Q3nLbRakRO/UAG6SAClL9pyk0vIUpHZSAcLx8fWk.
       +Start sshd(8) by default = no
       +Do you expect to run the X Window System = no
       +Setup a user = testuser
       +Full name for user testuser = testuser
       +What timezone are you in = Europe/Amsterdam
       +Which disk is the root disk = wd0
       +Use (W)hole disk MBR, whole disk (G)PT, (O)penBSD area or (E)dit = OpenBSD
       +Use (A)uto layout, (E)dit auto layout, or create (C)ustom layout = a
       +Location of sets = http
       +HTTP proxy URL = none
       +HTTP Server = 192.168.0.2
       +Server directory = pub/OpenBSD/6.5/amd64
       +Unable to connect using https. Use http instead = yes
       +Location of sets = http
       +Set name(s) = done
       +Location of sets = done
       +Exit to (S)hell, (H)alt or (R)eboot = R
       +</code></pre>
       +<p>Get custom encrypted password for response file:</p>
       +<pre><code>$ printf '%s' 'yourpassword' | encrypt
       +</code></pre>
       +<h2>Changing the RAMDISK kernel disk image</h2>
       +<p><a href="https://man.openbsd.org/rdsetroot.8">rdsetroot(8)</a> is publicly exposed now in base since 6.5. Before 6.5 it is
       +available in the /usr/src/ tree as elfrdsetroot, see also the <a href="https://man.openbsd.org/rd.4">rd(4)</a> man page.</p>
       +<pre><code>$ mkdir auto
       +$ cd auto
       +$ cp pubdir/bsd.rd .
       +$ rdsetroot -x bsd.rd disk.fs
       +# vnconfig vnd0 disk.fs
       +# mkdir mount
       +# mount /dev/vnd0a mount
       +</code></pre>
       +<p>Copy the response file (install.resp) to: mount/auto_install.conf
       +(installation) <strong>or</strong> mount/auto_upgrade.conf (upgrade), but not both. In this
       +guide we will do an auto-installation.</p>
       +<p>Unmount, detach and patch RAMDISK:</p>
       +<pre><code># umount mount
       +# vnconfig -u vnd0
       +$ rdsetroot bsd.rd disk.fs
       +</code></pre>
       +<p>To test copy bsd.rd to the root of some testmachine like /bsd.test.rd then
       +(re)boot and type:</p>
       +<pre><code>boot /bsd.test.rd
       +</code></pre>
       +<p>In the future (6.5+) it will be possible to copy to a file named "/bsd.upgrade"
       +in the root of a current system and automatically load the kernel:
       +<a href="https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/sys/stand/boot/boot.c?rev=1.46&amp;content-type=text/x-cvsweb-markup">See the script bsd.upgrade in CVS.</a>
       +Of course this is possible with PXE boot or some custom USB/ISO also.
       +As explained in the <a href="https://man.openbsd.org/autoinstall.8">autoinstall(8)</a> man page: create either an
       +auto_upgrade.conf <strong>or</strong> an auto_install.conf, but not both.</p>
       +<h2>Create bootable miniroot</h2>
       +<p>In this example the miniroot will boot the custom kernel, but fetch all the
       +sets from the local network.</p>
       +<p>We will base our miniroot of the official version: miniroot65.fs.</p>
       +<p>We will create a 16MB miniroot to boot from (in this guide it is assumed the
       +original miniroot is about 4MB and the modified kernel image fits in the new
       +allocated space):</p>
       +<pre><code>$ dd if=/dev/zero of=new.fs bs=512 count=32768
       +</code></pre>
       +<p>Copy first part of the original image to the new disk (no truncation):</p>
       +<pre><code>$ dd conv=notrunc if=miniroot65.fs of=new.fs
       +# vnconfig vnd0 new.fs
       +</code></pre>
       +<p>Expand disk OpenBSD boundaries:</p>
       +<pre><code># disklabel -E vnd0
       +&gt; b
       +Starting sector: [1024]
       +Size ('*' for entire disk): [8576] *
       +&gt; r
       +Total free sectors: 1168.
       +&gt; c a
       +Partition a is currently 8576 sectors in size, and can have a maximum
       +size of 9744 sectors.
       +size: [8576] *
       +&gt; w
       +&gt; q
       +</code></pre>
       +<p>or:</p>
       +<pre><code># printf 'b\n\n*\nc a\n*\nw\n' | disklabel -E vnd0
       +</code></pre>
       +<p>Grow filesystem and check it and mark as clean:</p>
       +<pre><code># growfs -y /dev/vnd0a
       +# fsck -y /dev/vnd0a
       +</code></pre>
       +<p>Mount filesystem:</p>
       +<pre><code># mount /dev/vnd0a mount/
       +</code></pre>
       +<p>The kernel on the miniroot is GZIP compressed. Compress our modified bsd.rd and
       +overwrite the original kernel:</p>
       +<pre><code># gzip -c9n bsd.rd &gt; mount/bsd
       +</code></pre>
       +<p>Or to save space (+- 500KB) by stripping debug symbols, taken from bsd.gz target
       +<a href="https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/distrib/amd64/iso/Makefile">in this Makefile</a>.</p>
       +<pre><code>$ cp bsd.rd bsd.strip
       +$ strip bsd.strip
       +$ strip -R .comment -R .SUNW_ctf bsd.strip
       +$ gzip -c9n bsd.strip &gt; bsd.gz
       +$ cp bsd.gz mount/bsd
       +</code></pre>
       +<p>Now unmount and detach:</p>
       +<pre><code># umount mount/
       +# vnconfig -u vnd0
       +</code></pre>
       +<p>Now you can <a href="https://man.openbsd.org/dd.1">dd(1)</a> the image new.fs to your bootable (USB) medium.</p>
       +<h2>Adding custom sets (optional)</h2>
       +<p>For patching <a href="https://man.openbsd.org/rc.firsttime.8">/etc/rc.firsttime</a> and other system files it is useful to use a
       +customized installation set like siteVERSION.tgz, for example: site65.tgz.  The
       +sets can even be specified per host/MAC address like
       +siteVERSION-$(hostname -s).tgz so for example: site65-testvm.tgz</p>
       +<p>When the installer checks the base sets of the mirror it looks for a file
       +index.txt.  To add custom sets the site entries have to be added.</p>
       +<p>For example:</p>
       +<pre><code>-rw-r--r--  1 1001  0    4538975 Oct 11 13:58:26 2018 site65-testvm.tgz
       +</code></pre>
       +<p>The filesize, permissions etc do not matter and are not checked by the
       +installer.  Only the filename is matched by a regular expression.</p>
       +<h2>Sign custom site* tarball sets (optional)</h2>
       +<p>If you have custom sets without creating a signed custom release you will be
       +prompted for the messages:</p>
       +<pre><code>checksum test failed
       +</code></pre>
       +<p>and:</p>
       +<pre><code>unverified sets: continue without verification
       +</code></pre>
       +<p>OpenBSD uses the program <a href="https://man.openbsd.org/signify.1">signify(1)</a> to cryptographically sign and
       +verify filesets.</p>
       +<p>To create a custom public/private keypair (ofcourse make sure to store the
       +private key privately):</p>
       +<pre><code>$ signify -G -n -c "Custom 6.5 install" -p custom-65-base.pub -s custom-65-base.sec
       +</code></pre>
       +<p>Create new checksum file with filelist of the current directory (except SHA256*
       +files):</p>
       +<pre><code>$ printf '%s\n' * | grep -v SHA256 | xargs sha256 &gt; SHA256
       +</code></pre>
       +<p>Sign SHA256 and store as SHA256.sig, embed signature:</p>
       +<pre><code>$ signify -S -e -s /privatedir/custom-65-base.sec -m SHA256 -x SHA256.sig
       +</code></pre>
       +<p>Verify the created signature and data is correct:</p>
       +<pre><code>$ signify -C -p /somelocation/custom-65-base.pub -x SHA256.sig
       +</code></pre>
       +<p>Copy <strong>only</strong> the <strong>public</strong> key to the RAMDISK:</p>
       +<pre><code>$ cp custom-65-base.pub mount/etc/signify/custom-65-base.pub
       +</code></pre>
       +<p>Now we have to patch the install.sub file to check our public key.  If you know
       +a better way without having to patch this script, please let me know.</p>
       +<p>Change the variable PUB_KEY in the shellscript mount/install.sub from:</p>
       +<pre><code>PUB_KEY=/etc/signify/openbsd-${VERSION}-base.pub
       +</code></pre>
       +<p>To:</p>
       +<pre><code>PUB_KEY=/etc/signify/custom-${VERSION}-base.pub
       +</code></pre>
       +<p>And for upgrades from:</p>
       +<pre><code>$UPGRADE_BSDRD &amp;&amp;
       +        PUB_KEY=/mnt/etc/signify/openbsd-$((VERSION + 1))-base.pub
       +</code></pre>
       +<p>To:</p>
       +<pre><code>$UPGRADE_BSDRD &amp;&amp;
       +        PUB_KEY=/mnt/etc/signify/custom-$((VERSION + 1))-base.pub
       +</code></pre>
       +<h2>Ideas</h2>
       +<ul>
       +<li>Patch <a href="https://man.openbsd.org/rc.firsttime.8">rc.firsttime(8)</a>: and run syspatch, add ports, setup xenodm etc.</li>
       +<li>Custom partitioning scheme, see <a href="https://man.openbsd.org/autoinstall.8">autoinstall(8)</a> "URL to autopartitioning
       +template for disklabel = url".</li>
       +<li>Setup <a href="https://man.openbsd.org/pxeboot.8">pxeboot(8)</a> to boot and install over the network using
       +<a href="https://man.openbsd.org/dhcpd.8">dhcpd(8)</a> and
       +<a href="https://man.openbsd.org/tftpd.8">tftpd(8)</a> then not even some USB stick is required.</li>
       +</ul>
       +<h2>References</h2>
       +<ul>
       +<li>Main OpenBSD installation and upgrade shellscript:
       +<a href="https://cvsweb.openbsd.org/src/distrib/miniroot/install.sub">/usr/src/distrib/miniroot/install.sub</a></li>
       +</ul>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>Idiotbox: Youtube interface</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/idiotbox" />
       +        <id>gopher://codemadness.org/1/phlog/idiotbox</id>
       +        <updated>2021-12-25T00:00:00Z</updated>
       +        <published>2019-02-10T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Idiotbox: Youtube interface</summary>
       +        <content type="html"><![CDATA[<h1>Idiotbox: Youtube interface</h1>
       +        <p><strong>Last modification on </strong> <time>2021-12-25</time></p>
       +        <p>Idiotbox is a less resource-heavy Youtube interface.  For viewing videos it is
       +recommended to use it with <a href="https://mpv.io/">mpv</a> or
       +<a href="https://mplayerhq.hu/">mplayer</a> with
       +<a href="https://youtube-dl.org/">youtube-dl</a> or
       +<a href="https://github.com/yt-dlp/yt-dlp">yt-dlp</a>.</p>
       +<p>For more (up-to-date) information see the <a href="/git/frontends/file/youtube/README.html">README</a> file.</p>
       +<h2>Why</h2>
       +<p>In my opinion the standard Youtube web interface is:</p>
       +<ul>
       +<li>Non-intuitive, too much visual crap.</li>
       +<li>Too resource-hungry, both in CPU and bandwidth.</li>
       +<li>Doesn't work well on simpler (text-based) browsers such as netsurf and links.</li>
       +</ul>
       +<h2>Features</h2>
       +<ul>
       +<li>Doesn't use JavaScript.</li>
       +<li>Doesn't use (tracking) cookies.</li>
       +<li>CSS is optional.</li>
       +<li>Multiple interfaces available: HTTP CGI, command-line, Gopher CGI (gph),
       +this is a work-in-progress.</li>
       +<li>Doesn't use or require the Google API.</li>
       +<li>CGI interface works nice in most browsers, including text-based ones.</li>
       +<li>On OpenBSD it runs "sandboxed" and it can be compiled as a static-linked
       +binary with <a href="https://man.openbsd.org/pledge">pledge(2)</a>,
       +<a href="https://man.openbsd.org/unveil">unveil(2)</a> in a chroot.</li>
       +</ul>
       +<h2>Cons</h2>
       +<ul>
       +<li>Order by upload date is incorrect (same as on Youtube).</li>
       +<li>Some Youtube features are not supported.</li>
       +<li>Uses scraping so might break at any point.</li>
       +</ul>
       +<h2>Clone</h2>
       +<pre><code>git clone git://git.codemadness.org/frontends
       +</code></pre>
       +<h2>Browse</h2>
       +<p>You can browse the source-code at:</p>
       +<ul>
       +<li><a href="https://git.codemadness.org/frontends/">https://git.codemadness.org/frontends/</a></li>
       +<li><a href="gopher://codemadness.org/1/git/frontends">gopher://codemadness.org/1/git/frontends</a></li>
       +</ul>
       +<h2>Download releases</h2>
       +<p>Releases are available at:</p>
       +<ul>
       +<li><a href="https://codemadness.org/releases/frontends/">https://codemadness.org/releases/frontends/</a></li>
       +<li><a href="gopher://codemadness.org/1/releases/frontends">gopher://codemadness.org/1/releases/frontends</a></li>
       +</ul>
       +<h2>View</h2>
       +<p>You can view it here: <a href="https://codemadness.org/idiotbox/">https://codemadness.org/idiotbox/</a></p>
       +<p>For example you can search using the query string parameter "q":
       +<a href="https://codemadness.org/idiotbox/?q=gunther+tralala">https://codemadness.org/idiotbox/?q=gunther+tralala</a></p>
       +<p>The gopher version is here: <a href="gopher://codemadness.org/7/idiotbox.cgi">gopher://codemadness.org/7/idiotbox.cgi</a></p>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>Gopher HTTP proxy</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/gopher-proxy" />
       +        <id>gopher://codemadness.org/1/phlog/gopher-proxy</id>
       +        <updated>2020-08-30T00:00:00Z</updated>
       +        <published>2018-08-17T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Gopher HTTP proxy</summary>
       +        <content type="html"><![CDATA[<h1>Gopher HTTP proxy</h1>
       +        <p><strong>Last modification on </strong> <time>2020-08-30</time></p>
       +        <p>For fun I wrote a small HTTP Gopher proxy CGI program in C. It only supports
       +the basic Gopher types and has some restrictions to prevent some abuse.</p>
       +<p>For your regular Gopher browsing I recommend the simple Gopher client <a href="https://git.fifth.space/sacc/">sacc</a>.</p>
       +<p>For more information about Gopher check out <a href="http://gopherproject.org/">gopherproject.org</a>.</p>
       +<h2>Clone</h2>
       +<pre><code>git clone git://git.codemadness.org/gopherproxy-c
       +</code></pre>
       +<h2>Browse</h2>
       +<p>You can browse the source-code at:</p>
       +<ul>
       +<li><a href="https://git.codemadness.org/gopherproxy-c/">https://git.codemadness.org/gopherproxy-c/</a></li>
       +<li><a href="gopher://codemadness.org/1/git/gopherproxy-c">gopher://codemadness.org/1/git/gopherproxy-c</a></li>
       +</ul>
       +<h2>View</h2>
       +<p>You can view it here:
       +<a href="https://codemadness.org/gopherproxy/">https://codemadness.org/gopherproxy/</a></p>
       +<p>For example you can also view my gopherhole using the proxy, the query string
       +parameter "q" reads the URI:
       +<a href="https://codemadness.org/gopherproxy/?q=codemadness.org">https://codemadness.org/gopherproxy/?q=codemadness.org</a></p>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>Setup your own file paste service</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/paste-service" />
       +        <id>gopher://codemadness.org/1/phlog/paste-service</id>
       +        <updated>2018-03-10T00:00:00Z</updated>
       +        <published>2018-03-10T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Howto setup your own secure file paste service</summary>
       +        <content type="html"><![CDATA[<h1>Setup your own file paste service</h1>
       +        <p><strong>Last modification on </strong> <time>2018-03-10</time></p>
       +        <h2>Setup SSH authentication</h2>
       +<p>Make sure to setup SSH public key authentication so you don't need to enter a
       +password each time and have a more secure authentication.</p>
       +<p>For example in the file $HOME/.ssh/config:</p>
       +<pre><code>Host codemadness
       +        Hostname codemadness.org
       +        Port 22
       +        IdentityFile ~/.ssh/codemadness/id_rsa
       +</code></pre>
       +<p>Of course also make sure to generate the private and public keys.</p>
       +<h2>Shell alias</h2>
       +<p>Make an alias or function in your shell config:</p>
       +<pre><code>pastesrv() {
       +        ssh user@codemadness "cat &gt; /your/www/publicdir/paste/$1"
       +        echo "https://codemadness.org/paste/$1"
       +}
       +</code></pre>
       +<p>This function reads any data from stdin and transfers the output securely via
       +SSH and writes it to a file at the specified path. This path can be visible via
       +HTTP, gopher or an other protocol. Then it writes the absolute URL to stdout,
       +this URL can be copied to the clipboard and pasted anywhere like to an e-mail,
       +IRC etc.</p>
       +<h2>Usage and examples</h2>
       +<p>To use it, here are some examples:</p>
       +<p>Create a patch of the last commit in the git repo and store it:</p>
       +<pre><code>git format-patch --stdout HEAD^ | pastesrv 'somepatch.diff'
       +</code></pre>
       +<p>Create a screenshot of your current desktop and paste it:</p>
       +<pre><code>xscreenshot | ff2png | pastesrv 'screenshot.png'
       +</code></pre>
       +<p>There are many other uses of course, use your imagination :)</p>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>Setup your own git hosting service</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/setup-git-hosting" />
       +        <id>gopher://codemadness.org/1/phlog/setup-git-hosting</id>
       +        <updated>2022-08-07T00:00:00Z</updated>
       +        <published>2018-02-25T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Howto setup your own git hosting service</summary>
       +        <content type="html"><![CDATA[<h1>Setup your own git hosting service</h1>
       +        <p><strong>Last modification on </strong> <time>2022-08-07</time></p>
       +        <p><strong>This article assumes you use OpenBSD for the service files and OS-specific
       +examples.</strong></p>
       +<h2>Why</h2>
       +<p>A good reason to host your own git repositories is because of having and
       +keeping control over your own computing infrastructure.</p>
       +<p>Some bad examples:</p>
       +<ul>
       +<li><a href="https://en.wikipedia.org/wiki/SourceForge#Controversies">The SourceForge ads/malware/hijack controversies. Injecting malware into projects</a>.</li>
       +<li><a href="https://gitlab.com/gitlab-org/gitaly/issues/2113">As of 2019-10-23 Gitlab added telemetry to their software</a>.</li>
       +<li><a href="https://about.gitlab.com/blog/2019/10/10/update-free-software-and-telemetry/">On 2019-10-24 Gitlab reverted it again because many people complained</a>.</li>
       +<li><a href="https://github.blog/2020-11-16-standing-up-for-developers-youtube-dl-is-back/">On 2020-11-16 Github reinstated youtube-dl, to reverse a Digital Millennium Copyright Act (DMCA) takedown</a>.</li>
       +<li><a href="https://arstechnica.com/gadgets/2021/03/critics-fume-after-github-removes-exploit-code-for-exchange-vulnerabilities/">On 2021-03-11 Github (owned by Microsoft) removes exploit code for Microsoft Exchange vulnerabilities</a>.</li>
       +<li><a href="https://www.bleepingcomputer.com/news/security/github-suspends-accounts-of-russian-devs-at-sanctioned-companies/">On 2022-04-16 Russian software developers are reporting that their GitHub accounts are being suspended without warning if they work for or previously worked for companies under US sanctions</a>.</li>
       +<li><a href="https://www.theregister.com/2022/08/04/gitlab_data_retention_policy/">On 2022-08-04 GitLab plans to delete dormant projects in free accounts</a>.</li>
       +<li><a href="https://www.theregister.com/2022/08/05/gitlab_reverses_deletion_policy/">On 2022-08-05 GitLab U-turns on deleting dormant projects after backlash</a>.</li>
       +</ul>
       +<p>The same thing can happen with Github, Atlassian Bitbucket or other similar
       +services.  After all: they are just a company with commercial interests.  These
       +online services also have different pricing plans and various (arbitrary)
       +restrictions.  When you host it yourself the restrictions are the resource
       +limits of the system and your connection, therefore it is a much more flexible
       +solution.</p>
       +<p>Always make sure you own the software (which is <a href="https://www.gnu.org/philosophy/free-sw.html">Free</a> or open-source) and you
       +can host it yourself, so you will be in control of it.</p>
       +<h2>Creating repositories</h2>
       +<p>For the hosting it is recommended to use a so-called "bare" repository.  A bare
       +repository means no files are checked out in the folder itself.  To create a
       +bare repository use git init with the --bare argument:</p>
       +<pre><code>$ git init --bare
       +</code></pre>
       +<p>I recommend to create a separate user and group for the source-code
       +repositories.  In the examples we will assume the user is called "src".</p>
       +<p>Login as the src user and create the files. To create a directory for the
       +repos, in this example /home/src/src:</p>
       +<pre><code>$ mkdir -p /home/src/src
       +$ cd /home/src/src
       +$ git init --bare someproject
       +$ $EDITOR someproject/description
       +</code></pre>
       +<p>Make sure the git-daemon process has access permissions to these repositories.</p>
       +<h2>Install git-daemon (optional)</h2>
       +<p>Using git-daemon you can clone the repositories publicly using the efficient
       +git:// protocol. An alternative without having to use git-daemon is by using
       +(anonymous) SSH, HTTPS or any public shared filesystem.</p>
       +<p>When you use a private-only repository I recommend to just use SSH without
       +git-daemon because it is secure.</p>
       +<p>Install the git package. The package should contain "git daemon":</p>
       +<pre><code># pkg_add git
       +</code></pre>
       +<p>Enable the daemon:</p>
       +<pre><code># rcctl enable gitdaemon
       +</code></pre>
       +<p>Set the gitdaemon service flags to use the src directory and use all the
       +available repositories in this directory. The command-line flags "--export-all"
       +exports all repositories in the base path. Alternatively you can use the
       +"git-daemon-export-ok" file (see the git-daemon man page).</p>
       +<pre><code># rcctl set gitdaemon flags --export-all --base-path="/home/src/src"
       +</code></pre>
       +<p>To configure the service to run as the user _gitdaemon:</p>
       +<pre><code># rcctl set gitdaemon user _gitdaemon
       +</code></pre>
       +<p>To run the daemon directly as the user _gitdaemon (without dropping privileges
       +from root to the user) set the following flags in /etc/rc.d/gitdaemon:</p>
       +<pre><code>daemon_flags="--user=_gitdaemon"
       +</code></pre>
       +<p>Which will also avoid this warning while cloning:</p>
       +<pre><code>"can't access /root/.git/config"
       +</code></pre>
       +<p>Now start the daemon:</p>
       +<pre><code># rcctl start gitdaemon
       +</code></pre>
       +<h2>Cloning and fetching changes</h2>
       +<p>To test and clone the repository do:</p>
       +<pre><code>$ git clone git://yourdomain/someproject
       +</code></pre>
       +<p>if you skipped the optional git-daemon installation then just clone via SSH:</p>
       +<pre><code>$ git clone ssh://youraccount@yourdomain:/home/src/src/someproject
       +</code></pre>
       +<p>When cloning via SSH make sure to setup private/public key authentication for
       +security and convenience.</p>
       +<p>You should also make sure the firewall allows connections to the services like
       +the git daemon, HTTPd or SSH, for example using OpenBSD pf something like this
       +can be set in <a href="https://man.openbsd.org/pf.conf">/etc/pf.conf</a>:</p>
       +<pre><code>tcp_services="{ ssh, gopher, http, https, git }"
       +pass in on egress proto tcp from any to (egress) port $tcp_services
       +</code></pre>
       +<h2>Pushing changes</h2>
       +<p>Add the repository as a remote:</p>
       +<pre><code>$ git remote add myremote ssh://youraccount@yourdomain:/home/src/src/someproject
       +</code></pre>
       +<p>Then push the changes:</p>
       +<pre><code>$ git push myremote master:master
       +</code></pre>
       +<h2>Git history web browsing (optional)</h2>
       +<p>Sometimes it's nice to browse the git history log of the repository in a web
       +browser or some other program without having to look at the local repository.</p>
       +<ul>
       +<li><a href="stagit.html">Stagit</a> is a static HTML page generator for git.</li>
       +<li><a href="stagit-gopher.html">Stagit-gopher</a> is a static page generator for
       +<a href="http://gopherproject.org/">gopher</a> and
       +<a href="gopher://bitreich.org/1/scm/geomyidae">geomyidae</a>.</li>
       +<li>cgit is a CGI-based program which shows HTML views of your repository, see
       +also the page: <a href="openbsd-httpd-and-cgit.html">OpenBSD httpd, slowcgi and cgit</a>.</li>
       +</ul>
       +<p>It's also possible with these tools to generate an Atom feed and then use a
       +RSS/Atom reader to track the git history:</p>
       +<ul>
       +<li>An example url from cgit: <a href="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/atom/?h=master">Linux kernel tree</a>.</li>
       +<li>An example url from stagit for the <a href="/git/stagit/atom.xml">commit log</a>.</li>
       +<li>An example url from stagit for the <a href="/git/stagit/tags.xml">releases</a>.</li>
       +</ul>
       +<p>My <a href="sfeed.html">sfeed</a> program can be used as a RSS/Atom reader.</p>
       +<h2>Setting up git hooks (optional)</h2>
       +<p>Using git hooks you can setup automated triggers, for example when pushing to a
       +repository.  Some useful examples can be:</p>
       +<ul>
       +<li><a href="/git/stagit/file/example_post-receive.sh.html">For stagit: update the repo files (example post-receive hook).</a></li>
       +<li>Send an e-mail with the commit subject and message.</li>
       +<li>Log/notify commits and changes to an IRC channel using a fifo: <a href="https://tools.suckless.org/ii/">ii</a>.</li>
       +<li>Create a release tarball and checksum file on a tag push/change.</li>
       +<li>Checkout files for website content.</li>
       +</ul>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>Setup an OpenBSD SPARC64 VM in QEMU</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/openbsd-sparc64-vm" />
       +        <id>gopher://codemadness.org/1/phlog/openbsd-sparc64-vm</id>
       +        <updated>2020-04-18T00:00:00Z</updated>
       +        <published>2017-12-11T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Setup an OpenBSD SPARC64 VM in QEMU</summary>
       +        <content type="html"><![CDATA[<h1>Setup an OpenBSD SPARC64 VM in QEMU</h1>
       +        <p><strong>Last modification on </strong> <time>2020-04-18</time></p>
       +        <p>This describes how to setup an OpenBSD SPARC64 VM in QEMU.</p>
       +<h2>Create a disk image</h2>
       +<p>To create a 5GB disk image:</p>
       +<pre><code>qemu-img create -f qcow2 fs.qcow2 5G
       +</code></pre>
       +<h2>Install</h2>
       +<p>In this guide we'll use the installation ISO to install OpenBSD. Make sure to
       +download the latest (stable) OpenBSD ISO, for example install62.iso.</p>
       +<ul>
       +<li>Change -boot c to -boot d to boot from the CD-ROM and do a clean install.</li>
       +<li>Change -cdrom install62.iso to the location of your ISO file.</li>
       +<li>When the install is done type: halt -p</li>
       +<li>Change -boot d back to -boot c.</li>
       +</ul>
       +<p>Start the VM:</p>
       +<pre><code>#!/bin/sh
       +LC_ALL=C QEMU_AUDIO_DRV=none \
       +qemu-system-sparc64 \
       +        -machine sun4u,usb=off \
       +        -realtime mlock=off \
       +        -smp 1,sockets=1,cores=1,threads=1 \
       +        -rtc base=utc \
       +        -m 1024 \
       +        -boot c \
       +        -drive file=fs.qcow2,if=none,id=drive-ide0-0-1,format=qcow2,cache=none \
       +        -cdrom install62.iso \
       +        -device ide-hd,bus=ide.0,unit=0,drive=drive-ide0-0-1,id=ide0-0-1 \
       +        -msg timestamp=on \
       +        -serial pty -nographic \
       +        -net nic,model=ne2k_pci -net user
       +</code></pre>
       +<p>The VM has the following properties:</p>
       +<ul>
       +<li>No audio.</li>
       +<li>No USB.</li>
       +<li>No VGA graphics: serial console.</li>
       +<li>Netdev is ne0 (Realtek 8029).</li>
       +<li>1024MB memory.</li>
       +</ul>
       +<p>From your host connect to the serial device indicated by QEMU, for example:</p>
       +<pre><code>(qemu) 2017-11-19T15:14:20.884312Z qemu-system-sparc64: -serial pty: char device redirected to /dev/ttyp0 (label serial0)
       +</code></pre>
       +<p>Then you can use the serial terminal emulator <strong>cu</strong> to attach:</p>
       +<pre><code>cu -l /dev/ttyp0
       +</code></pre>
       +<p>Another option could be using the <a href="https://git.suckless.org/st/">simple terminal(st)</a> from suckless.</p>
       +<pre><code>st -l /dev/ttyp0
       +</code></pre>
       +<p>using cu to detach the <a href="https://man.openbsd.org/cu#~^D">cu(1) man page</a> says:</p>
       +<pre><code>Typed characters are normally transmitted directly to the remote machine (which
       +does the echoing as well).  A tilde ('~') appearing as the first character of a
       +line is an escape signal; the following are recognized:
       +
       +    ~^D or ~.  Drop the connection and exit.  Only the connection is
       +               the login session is not terminated.
       +</code></pre>
       +<p>On boot you have to type:</p>
       +<pre><code>root device: wd0a
       +for swap use the default (wd0b) Press enter
       +</code></pre>
       +<h2>Initial settings on first boot (optional)</h2>
       +<p>Automatic network configuration using DHCP</p>
       +<pre><code>echo "dhcp" &gt; /etc/hostname.ne0
       +</code></pre>
       +<p>To bring up the interface (will be automatic on the next boot):</p>
       +<pre><code>sh /etc/netstart
       +</code></pre>
       +<p>Add a mirror to /etc/installurl for package installation. Make sure to lookup
       +the most efficient/nearby mirror site on the OpenBSD mirror page.</p>
       +<pre><code>echo "https://ftp.hostserver.de/pub/OpenBSD" &gt; /etc/installurl
       +</code></pre>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>Tscrape: a Twitter scraper</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/tscrape" />
       +        <id>gopher://codemadness.org/1/phlog/tscrape</id>
       +        <updated>2020-07-20T00:00:00Z</updated>
       +        <published>2017-09-24T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Tscrape: a Twitter scraper</summary>
       +        <content type="html"><![CDATA[<h1>Tscrape: a Twitter scraper</h1>
       +        <p><strong>Last modification on </strong> <time>2020-07-20</time></p>
       +        <p>Tscrape is a Twitter web scraper and archiver.</p>
       +<p>Twitter removed the functionality to follow users using a RSS feed without
       +authenticating or using their API. With this program you can format tweets in
       +any way you like relatively anonymously.</p>
       +<p>For more (up-to-date) information see the <a href="/git/tscrape/file/README.html">README</a> file.</p>
       +<h2>Clone</h2>
       +<pre><code>git clone git://git.codemadness.org/tscrape
       +</code></pre>
       +<h2>Browse</h2>
       +<p>You can browse the source-code at:</p>
       +<ul>
       +<li><a href="https://git.codemadness.org/tscrape/">https://git.codemadness.org/tscrape/</a></li>
       +<li><a href="gopher://codemadness.org/1/git/tscrape">gopher://codemadness.org/1/git/tscrape</a></li>
       +</ul>
       +<h2>Download releases</h2>
       +<p>Releases are available at:</p>
       +<ul>
       +<li><a href="https://codemadness.org/releases/tscrape/">https://codemadness.org/releases/tscrape/</a></li>
       +<li><a href="gopher://codemadness.org/1/releases/tscrape">gopher://codemadness.org/1/releases/tscrape</a></li>
       +</ul>
       +<h2>Examples</h2>
       +<p>Output format examples:</p>
       +<ul>
       +<li><a href="tscrape/tscrape_html.html">tscrape_html: HTML</a></li>
       +<li><a href="tscrape/tscrape_plain.txt">tscrape_plain: Text</a></li>
       +</ul>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>jsdatatable: a small datatable Javascript</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/datatable" />
       +        <id>gopher://codemadness.org/1/phlog/datatable</id>
       +        <updated>2020-07-20T00:00:00Z</updated>
       +        <published>2017-09-24T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>jsdatatable: a small datatable Javascript</summary>
       +        <content type="html"><![CDATA[<h1>jsdatatable: a small datatable Javascript</h1>
       +        <p><strong>Last modification on </strong> <time>2020-07-20</time></p>
       +        <p>This is a small datatable Javascript with no dependencies.</p>
       +<h2>Features</h2>
       +<ul>
       +<li>Small:
       +<ul>
       +<li>Filesize: +- 9.1KB.</li>
       +<li>Lines: +- 300, not much code, so hopefully easy to understand.</li>
       +<li>No dependencies on other libraries like jQuery.</li>
       +</ul>
       +</li>
       +<li>Sorting on columns, multi-column support with shift-click.</li>
       +<li>Filtering values: case-insensitively, tokenized (separated by space).</li>
       +<li>Able to add custom filtering, parsing and sorting functions.</li>
       +<li>Helper function for delayed (150ms) filtering, so filtering feels more
       +responsive for big datasets.</li>
       +<li>Permissive ISC license, see LICENSE file.</li>
       +<li>"Lazy scroll" mode:
       +<ul>
       +<li>fixed column headers and renders only visible rows, this allows you to
       +"lazily" render millions of rows.</li>
       +</ul>
       +</li>
       +<li>Officially supported browsers are:
       +<ul>
       +<li>Firefox and Firefox ESR.</li>
       +<li>Chrome and most recent webkit-based browsers.</li>
       +<li>IE10+.</li>
       +</ul>
       +</li>
       +</ul>
       +<h2>Why? and a comparison</h2>
       +<p>It was created because all the other datatable scripts suck balls.</p>
       +<p>Most Javascripts nowadays have a default dependency on jQuery, Bootstrap or
       +other frameworks.</p>
       +<p>jQuery adds about 97KB and Bootstrap adds about 100KB to your scripts and CSS
       +as a dependency.  This increases the CPU, memory and bandwidth consumption and
       +latency. It also adds complexity to your scripts.</p>
       +<p>jQuery was mostly used for backwards-compatibility in the Internet Explorer
       +days, but is most often not needed anymore. It contains functionality to query
       +the DOM using CSS-like selectors, but this is now supported with for example
       +document.querySelectorAll.  Functionality like a JSON parser is standard
       +available now: JSON.parse().</p>
       +<h3>Size comparison</h3>
       +<p>All sizes are not "minified" or gzipped.</p>
       +<pre><code>Name                             |   Total |      JS |   CSS | Images | jQuery
       +---------------------------------+---------+---------+-------+--------+-------
       +jsdatatable                      |  12.9KB |   9.1KB | 2.5KB |  1.3KB |      -
       +datatables.net (without plugins) | 563.4KB | 449.3KB |  16KB |  0.8KB | 97.3KB
       +jdatatable                       | 154.6KB |    53KB |   1KB |  3.3KB | 97.3KB
       +</code></pre>
       +<ul>
       +<li><a href="https://datatables.net/">datatables.net</a> (without plugins).</li>
       +<li><a href="https://plugins.jquery.com/jdatatable/">jdatatable</a></li>
       +</ul>
       +<p>Of course jsdatatable has less features (less is more!), but it does 90% of
       +what's needed.  Because it is so small it is also much simpler to understand and
       +extend with required features if needed.</p>
       +<p>See also:
       +<a href="https://idlewords.com/talks/website_obesity.htm">The website obesity crisis</a></p>
       +<h2>Clone</h2>
       +<pre><code>git clone git://git.codemadness.org/jscancer
       +</code></pre>
       +<h2>Browse</h2>
       +<p>You can browse the source-code at:</p>
       +<ul>
       +<li><a href="https://git.codemadness.org/jscancer/">https://git.codemadness.org/jscancer/</a></li>
       +<li><a href="gopher://codemadness.org/1/git/jscancer">gopher://codemadness.org/1/git/jscancer</a></li>
       +</ul>
       +<p>It is in the datatable directory.</p>
       +<h2>Download releases</h2>
       +<p>Releases are available at:</p>
       +<ul>
       +<li><a href="https://codemadness.org/releases/jscancer/">https://codemadness.org/releases/jscancer/</a></li>
       +<li><a href="gopher://codemadness.org/1/releases/jscancer">gopher://codemadness.org/1/releases/jscancer</a></li>
       +</ul>
       +<h2>Usage</h2>
       +<h3>Examples</h3>
       +<p>See example.html for an example. A stylesheet file datatable.css is also
       +included, it contains the icons as embedded images.</p>
       +<p>A table should have the classname "datatable" set, it must contain a &lt;thead&gt;
       +for the column headers (&lt;td&gt; or &lt;th&gt;) and &lt;tbody&gt; element for the data. The
       +minimal code needed for a working datatable:</p>
       +<pre><code>&lt;html&gt;
       +&lt;body&gt;
       +&lt;input class="filter-text" /&gt;&lt;!-- optional --&gt;
       +&lt;table class="datatable"&gt;
       +        &lt;thead&gt;&lt;!-- columns --&gt;
       +                &lt;tr&gt;&lt;td&gt;Click me&lt;/td&gt;&lt;/tr&gt;
       +        &lt;/thead&gt;
       +        &lt;tbody&gt;&lt;!-- data --&gt;
       +                &lt;tr&gt;&lt;td&gt;a&lt;/td&gt;&lt;/tr&gt;
       +                &lt;tr&gt;&lt;td&gt;b&lt;/td&gt;&lt;/tr&gt;
       +        &lt;/tbody&gt;
       +&lt;/table&gt;
       +&lt;script type="text/javascript" src="datatable.js"&gt;&lt;/script&gt;
       +&lt;script type="text/javascript"&gt;var datatables = datatable_autoload();&lt;/script&gt;
       +&lt;/body&gt;
       +&lt;/html&gt;
       +</code></pre>
       +<h3>Column attributes</h3>
       +<p>The following column attributes are supported:</p>
       +<ul>
       +<li>data-filterable: if "1" or "true" specifies if the column can be filtered,
       +default: "true".</li>
       +<li>data-parse: specifies how to parse the values, default: "string", which is
       +datatable_parse_string(). See PARSING section below.</li>
       +<li>data-sort: specifies how to sort the values: default: "default", which is
       +datatable_sort_default(). See SORTING section below.</li>
       +<li>data-sortable: if "1" or "true" specifies if the column can be sorted,
       +default: "true".</li>
       +</ul>
       +<h3>Parsing</h3>
       +<p>By default only parsing for the types: date, float, int and string are
       +supported, but other types can be easily added as a function with the name:
       +datatable_parse_&lt;typename&gt;(). The parse functions parse the data-value
       +attribute when set or else the cell content (in order). Because of this
       +behaviour you can set the actual values as the data-value attribute and use the
       +cell content for display. This is useful to display and properly sort
       +locale-aware currency, datetimes etc.</p>
       +<h3>Filtering</h3>
       +<p>Filtering will be done case-insensitively on the cell content and when set also
       +on the data-value attribute. The filter string is split up as tokens separated
       +by space. Each token must match at least once per row to display it.</p>
       +<h3>Sorting</h3>
       +<p>Sorting is done on the parsed values by default with the function:
       +datatable_sort_default(). To change this you can set a customname string on
       +the data-sort attribute on the column which translates to the function:
       +datatable_sort_&lt;customname&gt;().</p>
       +<p>In some applications locale values are used, like for currency, decimal numbers
       +datetimes. Some people also like to use icons or extended HTML elements inside
       +the cell. Because jsdatatable sorts on the parsed value (see section PARSING)
       +it is possible to sort on the data-value attribute values and use the cell
       +content for display.</p>
       +<p>For example:</p>
       +<ul>
       +<li>currency, decimal numbers: use data-value attribute with floating-point
       +number, set data-parse column to "float".</li>
       +<li>date/datetimes: use data-value attribute with UNIX timestamps (type int), set
       +data-parse on column to "int" or set the data-parse attribute on column to
       +"date" which is datatable_parse_date(), then make sure to use Zulu times, like:
       +"2016-01-01T01:02:03Z" or other time strings that are parsable as the
       +data-value attribute.</li>
       +<li>icons: generally use data-value attribute with integer as weight value to
       +sort on, set data-parse column to "int".</li>
       +</ul>
       +<h3>Dynamically update data</h3>
       +<p>To update data dynamically see example-ajax.html for an example how to do this.</p>
       +<h3>Caveats</h3>
       +<ul>
       +<li>A date, integer, float or other values must be able to parse properly, when
       +the parse function returns NaN, null or undefined etc. the sorting behaviour is
       +also undefined. It is recommended to always set a zero value for each type.</li>
       +<li>&lt;tfoot&gt; is not supported in datatables in "lazy" mode.</li>
       +</ul>
       +<h2>Demo / example</h2>
       +<p><strong>For the below example to work you need to have Javascript enabled.</strong></p>
       +<p><a href="datatable-example.html">datatable-example.html</a></p>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>Stagit-gopher: a static git page generator for gopher</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/stagit-gopher" />
       +        <id>gopher://codemadness.org/1/phlog/stagit-gopher</id>
       +        <updated>2021-04-11T00:00:00Z</updated>
       +        <published>2017-08-04T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>a static git page generator for gopher</summary>
       +        <content type="html"><![CDATA[<h1>Stagit-gopher: a static git page generator for gopher</h1>
       +        <p><strong>Last modification on </strong> <time>2021-04-11</time></p>
       +        <p>stagit-gopher is a static page generator for Gopher.  It creates the pages as
       +static <a href="http://git.r-36.net/geomyidae/">geomyidae</a> .gph files.  stagit-gopher is a modified version from the
       +HTML version of stagit.</p>
       +<p><a href="/git/stagit-gopher/file/README.html">Read the README for more information about it.</a></p>
       +<p>I also run a gopherhole and stagit-gopher, you can see how it looks here:
       +<a href="gopher://codemadness.org/1/git/">gopher://codemadness.org/1/git/</a></p>
       +<p><a href="https://git.fifth.space/sacc/log.html">sacc</a> is a good Gopher client to view it.</p>
       +<h2>Features</h2>
       +<ul>
       +<li>Log of all commits from HEAD.</li>
       +<li>Log and diffstat per commit.</li>
       +<li>Show file tree with line numbers.</li>
       +<li>Show references: local branches and tags.</li>
       +<li>Detect README and LICENSE file from HEAD and link it as a webpage.</li>
       +<li>Detect submodules (.gitmodules file) from HEAD and link it as a webpage.</li>
       +<li>Atom feed of the commit log (atom.xml).</li>
       +<li>Atom feed of the tags/refs (tags.xml).</li>
       +<li>Make index page for multiple repositories with stagit-gopher-index.</li>
       +<li>After generating the pages (relatively slow) serving the files is very fast,
       +simple and requires little resources (because the content is static), a
       +geomyidae Gopher server is required.</li>
       +<li>Security: all pages are static. No CGI or dynamic code is run for the
       +interface.  Using it with a secure Gopher server such as geomyidae it is
       +privilege-dropped and chroot(2)'d.</li>
       +<li>Simple to setup: the content generation is clearly separated from serving it.
       +This makes configuration as simple as copying a few directories and scripts.</li>
       +<li>Usable with Gopher clients such as lynx and <a href="https://git.fifth.space/sacc/log.html">sacc</a>.</li>
       +</ul>
       +<h2>Cons</h2>
       +<ul>
       +<li>Not suitable for large repositories (2000+ commits), because diffstats are
       +an expensive operation, the cache (-c flag) is a workaround for this in
       +some cases.</li>
       +<li>Not suitable for large repositories with many files, because all files are
       +written for each execution of stagit. This is because stagit shows the lines
       +of textfiles and there is no "cache" for file metadata (this would add more
       +complexity to the code).</li>
       +<li>Not suitable for repositories with many branches, a quite linear history is
       +assumed (from HEAD).</li>
       +<li>Relatively slow to run the first time (about 3 seconds for sbase,
       +1500+ commits), incremental updates are faster.</li>
       +<li>Does not support some of the dynamic features cgit has (for HTTP), like:
       +<ul>
       +<li>Snapshot tarballs per commit.</li>
       +<li>File tree per commit.</li>
       +<li>History log of branches diverged from HEAD.</li>
       +<li>Stats (git shortlog -s).</li>
       +</ul>
       +</li>
       +</ul>
       +<p>This is by design, just use git locally.</p>
       +<h2>Clone</h2>
       +<pre><code>git clone git://git.codemadness.org/stagit-gopher
       +</code></pre>
       +<h2>Browse</h2>
       +<p>You can browse the source-code at:</p>
       +<ul>
       +<li><a href="https://git.codemadness.org/stagit-gopher/">https://git.codemadness.org/stagit-gopher/</a></li>
       +<li><a href="gopher://codemadness.org/1/git/stagit-gopher">gopher://codemadness.org/1/git/stagit-gopher</a></li>
       +</ul>
       +<h2>Download releases</h2>
       +<p>Releases are available at:</p>
       +<ul>
       +<li><a href="https://codemadness.org/releases/stagit-gopher/">https://codemadness.org/releases/stagit-gopher/</a></li>
       +<li><a href="gopher://codemadness.org/1/releases/stagit-gopher">gopher://codemadness.org/1/releases/stagit-gopher</a></li>
       +</ul>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>Saait: a boring HTML page generator</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/saait" />
       +        <id>gopher://codemadness.org/1/phlog/saait</id>
       +        <updated>2020-07-20T00:00:00Z</updated>
       +        <published>2017-06-10T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Saait: a boring HTML page generator</summary>
       +        <content type="html"><![CDATA[<h1>Saait: a boring HTML page generator</h1>
       +        <p><strong>Last modification on </strong> <time>2020-07-20</time></p>
       +        <p>Saait is the most boring static HTML page generator.</p>
       +<p>Meaning of saai (dutch): boring. Pronunciation: site</p>
       +<p><a href="/git/saait/file/README.html">Read the README for more information about it.</a></p>
       +<p>I used to use <a href="/git/static-site-scripts/files.html">shellscripts</a> to generate the static pages, but realised I
       +wanted a small program that works on each platform consistently.  There are
       +many incompatibilities or unimplemented features in base tools across different
       +platforms: Linux, UNIX, Windows.</p>
       +<p>This site is created using saait.</p>
       +<h2>Features</h2>
       +<ul>
       +<li>Single small binary that handles all the things. At run-time no dependency on
       +other tools.</li>
       +<li>Few lines of code (about 575 lines of C) and no dependencies except: a C
       +compiler and libc.</li>
       +<li>Works on most platforms: tested on Linux, *BSD, Windows.</li>
       +<li>Simple template syntax.</li>
       +<li>Uses HTML output by default, but can easily be modified to generate any
       +textual content, like gopher pages, wiki pages or other kinds of documents.</li>
       +<li>Out-of-the-box supports: creating an index page of all pages, Atom feed,
       +twtxt.txt feed, sitemap.xml and urllist.txt.</li>
       +</ul>
       +<h2>Cons</h2>
       +<ul>
       +<li>Simple template syntax, but very basic. Requires C knowledge to extend it if
       +needed.</li>
       +<li>Only basic (no nested) template blocks supported.</li>
       +</ul>
       +<h2>Clone</h2>
       +<pre><code>git clone git://git.codemadness.org/saait
       +</code></pre>
       +<h2>Browse</h2>
       +<p>You can browse the source-code at:</p>
       +<ul>
       +<li><a href="https://git.codemadness.org/saait/">https://git.codemadness.org/saait/</a></li>
       +<li><a href="gopher://codemadness.org/1/git/saait">gopher://codemadness.org/1/git/saait</a></li>
       +</ul>
       +<h2>Download releases</h2>
       +<p>Releases are available at:</p>
       +<ul>
       +<li><a href="https://codemadness.org/releases/saait/">https://codemadness.org/releases/saait/</a></li>
       +<li><a href="gopher://codemadness.org/1/releases/saait">gopher://codemadness.org/1/releases/saait</a></li>
       +</ul>
       +<h2>Documentation / man page</h2>
       +<p>Below is the saait(1) man page, which includes usage examples.</p>
       +<pre><code>
       +SAAIT(1)                    General Commands Manual                      SAAIT(1)
       +
       +NAME
       +     saait  the most boring static page generator
       +
       +SYNOPSIS
       +     saait [-c configfile] [-o outputdir] [-t templatesdir] pages...
       +
       +DESCRIPTION
       +     saait writes HTML pages to the output directory.
       +
       +     The arguments pages are page config files, which are processed in the
       +     given order.
       +
       +     The options are as follows:
       +
       +     -c configfile
       +             The global configuration file, the default is "config.cfg". Each
       +             page configuration file inherits variables from this file. These
       +             variables can be overwritten per page.
       +
       +     -o outputdir
       +             The output directory, the default is "output".
       +
       +     -t templatesdir
       +             The templates directory, the default is "templates".
       +
       +DIRECTORY AND FILE STRUCTURE
       +     A recommended directory structure for pages, although the names can be
       +     anything:
       +     pages/001-page.cfg
       +     pages/001-page.html
       +     pages/002-page.cfg
       +     pages/002-page.html
       +
       +     The directory and file structure for templates must be:
       +     templates/&lt;templatename&gt;/header.ext
       +     templates/&lt;templatename&gt;/item.ext
       +     templates/&lt;templatename&gt;/footer.ext
       +
       +     The following filename prefixes are detected for template blocks and
       +     processed in this order:
       +
       +     "header."
       +             Header block.
       +
       +     "item."
       +             Item block.
       +
       +     "footer."
       +             Footer block.
       +
       +     The files are saved as output/&lt;templatename&gt;, for example
       +     templates/atom.xml/* will become: output/atom.xml. If a template block
       +     file does not exist then it is treated as if it was empty.
       +
       +     Template directories starting with a dot (".") are ignored.
       +
       +     The "page" templatename is special and will be used per page.
       +
       +CONFIG FILE
       +     A config file has a simple key=value configuration syntax, for example:
       +
       +     # this is a comment line.
       +     filename = example.html
       +     title = Example page
       +     description = This is an example page
       +     created = 2009-04-12
       +     updated = 2009-04-14
       +
       +     The following variable names are special with their respective defaults:
       +
       +     contentfile
       +             Path to the input content filename, by default this is the path
       +             of the config file with the last extension replaced to ".html".
       +
       +     filename
       +             The filename or relative file path for the output file for this
       +             page.  By default the value is the basename of the contentfile.
       +             The path of the written output file is the value of filename
       +             appended to the outputdir path.
       +
       +     A line starting with # is a comment and is ignored.
       +
       +     TABs and spaces before and after a variable name are ignored.  TABs and
       +     spaces before a value are ignored.
       +
       +TEMPLATES
       +     A template (block) is text.  Variables are replaced with the values set
       +     in the config files.
       +
       +     The possible operators for variables are:
       +
       +     $             Escapes a XML string, for example: &lt; to the entity &amp;lt;.
       +
       +     #             Literal raw string value.
       +
       +     %             Insert contents of file of the value of the variable.
       +
       +     For example in a HTML item template:
       +
       +     &lt;article&gt;
       +             &lt;header&gt;
       +                     &lt;h1&gt;&lt;a href=""&gt;${title}&lt;/a&gt;&lt;/h1&gt;
       +                     &lt;p&gt;
       +                             &lt;strong&gt;Last modification on &lt;/strong&gt;
       +                             &lt;time datetime="${updated}"&gt;${updated}&lt;/time&gt;
       +                     &lt;/p&gt;
       +             &lt;/header&gt;
       +             %{contentfile}
       +     &lt;/article&gt;
       +
       +EXIT STATUS
       +     The saait utility exits 0 on success, and &gt;0 if an error occurs.
       +
       +EXAMPLES
       +     A basic usage example:
       +
       +     1.   Create a directory for a new site:
       +
       +          mkdir newsite
       +
       +     2.   Copy the example pages, templates, global config file and example
       +          stylesheets to a directory:
       +
       +          cp -r pages templates config.cfg style.css print.css newsite/
       +
       +     3.   Change the current directory to the created directory.
       +
       +          cd newsite/
       +
       +     4.   Change the values in the global config.cfg file.
       +
       +     5.   If you want to modify parts of the header, like the navigation menu
       +          items, you can change the following two template files:
       +          templates/page/header.html
       +          templates/index.html/header.html
       +
       +     6.   Create any new pages in the pages directory. For each config file
       +          there has to be a corresponding HTML file.  By default this HTML
       +          file has the path of the config file, but with the last extension
       +          (".cfg" in this case) replaced to ".html".
       +
       +     7.   Create an output directory:
       +
       +          mkdir -p output
       +
       +     8.   After any modifications the following commands can be used to
       +          generate the output and process the pages in descending order:
       +
       +          find pages -type f -name '*.cfg' -print0 | sort -zr | xargs -0 saait
       +
       +     9.   Copy the modified stylesheets to the output directory also:
       +
       +          cp style.css print.css output/
       +
       +     10.  Open output/index.html locally in your webbrowser to review the
       +          changes.
       +
       +     11.  To synchronize files, you can securely transfer them via SSH using
       +          rsync:
       +
       +          rsync -av output/ user@somehost:/var/www/htdocs/
       +
       +TRIVIA
       +     The most boring static page generator.
       +
       +     Meaning of saai (dutch): boring, pronunciation of saait: site
       +
       +SEE ALSO
       +     find(1), sort(1), xargs(1)
       +
       +AUTHORS
       +     Hiltjo Posthuma &lt;hiltjo@codemadness.org&gt;
       +</code></pre>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>Stagit: a static git page generator</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/stagit" />
       +        <id>gopher://codemadness.org/1/phlog/stagit</id>
       +        <updated>2021-04-11T00:00:00Z</updated>
       +        <published>2017-05-10T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>a static git page generator</summary>
       +        <content type="html"><![CDATA[<h1>Stagit: a static git page generator</h1>
       +        <p><strong>Last modification on </strong> <time>2021-04-11</time></p>
       +        <p>stagit is a static page generator for git.</p>
       +<p><a href="/git/stagit/file/README.html">Read the README for more information about it.</a></p>
       +<p>My git repository uses stagit, you can see how it looks here:
       +<a href="https://codemadness.org/git/">https://codemadness.org/git/</a></p>
       +<h2>Features</h2>
       +<ul>
       +<li>Log of all commits from HEAD.</li>
       +<li>Log and diffstat per commit.</li>
       +<li>Show file tree with linkable line numbers.</li>
       +<li>Show references: local branches and tags.</li>
       +<li>Detect README and LICENSE file from HEAD and link it as a webpage.</li>
       +<li>Detect submodules (.gitmodules file) from HEAD and link it as a webpage.</li>
       +<li>Atom feed of the commit log (atom.xml).</li>
       +<li>Atom feed of the tags/refs (tags.xml).</li>
       +<li>Make index page for multiple repositories with stagit-index.</li>
       +<li>After generating the pages (relatively slow) serving the files is very fast,
       +simple and requires little resources (because the content is static), only
       +a HTTP file server is required.</li>
       +<li>Security: all pages are static. No CGI or dynamic code is run for the
       +interface. Using it with a secure httpd such as OpenBSD httpd it is
       +privilege-separated, chroot(2)'d and pledge(2)'d.</li>
       +<li>Simple to setup: the content generation is clearly separated from serving
       +it. This makes configuration as simple as copying a few directories and
       +scripts.</li>
       +<li>Usable with text-browsers such as dillo, links, lynx and w3m.</li>
       +</ul>
       +<h2>Cons</h2>
       +<ul>
       +<li>Not suitable for large repositories (2000+ commits), because diffstats are
       +an expensive operation, the cache (-c flag) or (-l maxlimit) is a workaround
       +for this in some cases.</li>
       +<li>Not suitable for large repositories with many files, because all files are
       +written for each execution of stagit. This is because stagit shows the lines
       +of textfiles and there is no "cache" for file metadata (this would add more
       +complexity to the code).</li>
       +<li>Not suitable for repositories with many branches, a quite linear history is
       +assumed (from HEAD).</li>
       +</ul>
       +<p>In these cases it is better to use <a href="https://git.zx2c4.com/cgit/">cgit</a> or
       +possibly change stagit to run as a CGI program.</p>
       +<ul>
       +<li>Relatively slow to run the first time (about 3 seconds for sbase,
       +1500+ commits), incremental updates are faster.</li>
       +<li>Does not support some of the dynamic features cgit has, like:
       +<ul>
       +<li>Snapshot tarballs per commit.</li>
       +<li>File tree per commit.</li>
       +<li>History log of branches diverged from HEAD.</li>
       +<li>Stats (git shortlog -s).</li>
       +</ul>
       +</li>
       +</ul>
       +<p>This is by design, just use git locally.</p>
       +<h2>Clone</h2>
       +<pre><code>git clone git://git.codemadness.org/stagit
       +</code></pre>
       +<h2>Browse</h2>
       +<p>You can browse the source-code at:</p>
       +<ul>
       +<li><a href="https://git.codemadness.org/stagit/">https://git.codemadness.org/stagit/</a></li>
       +<li><a href="gopher://codemadness.org/1/git/stagit">gopher://codemadness.org/1/git/stagit</a></li>
       +</ul>
       +<h2>Download releases</h2>
       +<p>Releases are available at:</p>
       +<ul>
       +<li><a href="https://codemadness.org/releases/stagit/">https://codemadness.org/releases/stagit/</a></li>
       +<li><a href="gopher://codemadness.org/1/releases/stagit">gopher://codemadness.org/1/releases/stagit</a></li>
       +</ul>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>OpenBSD httpd, slowcgi and cgit</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/openbsd-httpd-and-cgit" />
       +        <id>gopher://codemadness.org/1/phlog/openbsd-httpd-and-cgit</id>
       +        <updated>2021-04-11T00:00:00Z</updated>
       +        <published>2015-07-05T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>OpenBSD httpd, slowcgi and cgit</summary>
       +        <content type="html"><![CDATA[<h1>OpenBSD httpd, slowcgi and cgit</h1>
       +        <p><strong>Last modification on </strong> <time>2021-04-11</time></p>
       +        <p>This is a guide to get <a href="https://git.zx2c4.com/cgit/">cgit</a> working with
       +<a href="https://man.openbsd.org/httpd.8">OpenBSD httpd(8)</a> and
       +<a href="https://man.openbsd.org/slowcgi.8">slowcgi(8)</a> in base.  OpenBSD httpd is very simple to setup, but nevertheless
       +this guide might help someone out there.</p>
       +<h2>Installation</h2>
       +<p>Install the cgit package:</p>
       +<pre><code># pkg_add cgit
       +</code></pre>
       +<p>or build it from ports:</p>
       +<pre><code># cd /usr/ports/www/cgit &amp;&amp; make &amp;&amp; make install
       +</code></pre>
       +<h2>Configuration</h2>
       +<h3>httpd</h3>
       +<p>An example of <a href="https://man.openbsd.org/httpd.conf.5">httpd.conf(5)</a>:
       +<a href="downloads/openbsd-httpd/httpd.conf">httpd.conf</a>.</p>
       +<h3>slowcgi</h3>
       +<p>By default the slowcgi UNIX domain socket is located at:
       +/var/www/run/slowcgi.sock.  For this example we use the defaults.</p>
       +<h3>cgit</h3>
       +<p>The cgit binary should be located at: /var/www/cgi-bin/cgit.cgi (default).</p>
       +<p>cgit uses the $CGIT_CONFIG environment variable to locate its config.  By
       +default on OpenBSD this is set to /conf/cgitrc (chroot), which is
       +/var/www/conf/cgitrc.  An example of the cgitrc file is here: <a href="downloads/openbsd-httpd/cgitrc">cgitrc</a>.</p>
       +<p>In this example the cgit cache directory is set to /cgit/cache (chroot), which
       +is /var/www/cgit/cache.  Make sure to give this path read and write permissions
       +for cgit (www:daemon).</p>
       +<p>In the example the repository path (scan-path) is set to /htdocs/src (chroot),
       +which is /var/www/htdocs/src.</p>
       +<p>The footer file is set to /conf/cgit.footer. Make sure this file exists or you
       +will get warnings:</p>
       +<pre><code># &gt;/var/www/conf/cgit.footer
       +</code></pre>
       +<p>Make sure cgit.css (stylesheet) and cgit.png (logo) are accessible, by default:
       +/var/www/cgit/cgit.{css,png} (location can be changed in httpd.conf).</p>
       +<p>To support .tar.gz snapshots a static gzip binary is required in the chroot
       +/bin directory:</p>
       +<pre><code>cd /usr/src/usr.bin/compress
       +make clean &amp;&amp; make LDFLAGS="-static -pie"
       +cp obj/compress /var/www/bin/gzip
       +</code></pre>
       +<h2>Running the services</h2>
       +<p>Enable the httpd and slowcgi services to automatically start them at boot:</p>
       +<pre><code># rcctl enable httpd slowcgi
       +</code></pre>
       +<p>Start the services:</p>
       +<pre><code># rcctl start httpd slowcgi
       +</code></pre>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>twitch: application to watch Twitch streams</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/twitch-interface" />
       +        <id>gopher://codemadness.org/1/phlog/twitch-interface</id>
       +        <updated>2020-12-14T00:00:00Z</updated>
       +        <published>2014-11-23T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>twitch: application to watch Twitch streams</summary>
       +        <content type="html"><![CDATA[<h1>twitch: application to watch Twitch streams</h1>
       +        <p><strong>Last modification on </strong> <time>2020-12-14</time></p>
       +        <p><strong>Update: as of 2020-05-06:</strong> I stopped maintaining it.
       +Twitch now requires OAUTH and 2-factor authentication. It requires me to expose
       +personal information such as my phone number.</p>
       +<p><strong>Update: as of ~2020-01-03:</strong> I rewrote this application from Golang to C.
       +The Twitch Kraken API used by the Golang version was deprecated.  It was
       +rewritten to use the Helix API.</p>
       +<p>This program/script allows to view streams in your own video player like so the
       +bloated Twitch interface is not needed.  It is written in C.</p>
       +<h2>Features</h2>
       +<ul>
       +<li>No Javascript, cookies, CSS optional.</li>
       +<li>Works well in all browsers, including text-based ones.</li>
       +<li>Has a HTTP CGI and Gopher CGI version.</li>
       +<li>Atom feed for VODs.</li>
       +</ul>
       +<h2>Clone</h2>
       +<pre><code>git clone git://git.codemadness.org/frontends
       +</code></pre>
       +<h2>Browse</h2>
       +<p>You can browse the source-code at:</p>
       +<ul>
       +<li><a href="https://git.codemadness.org/frontends/">https://git.codemadness.org/frontends/</a></li>
       +<li><a href="gopher://codemadness.org/1/git/frontends">gopher://codemadness.org/1/git/frontends</a></li>
       +</ul>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>Userscript: focus input field</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/userscript-focus-input-field" />
       +        <id>gopher://codemadness.org/1/phlog/userscript-focus-input-field</id>
       +        <updated>2014-03-02T00:00:00Z</updated>
       +        <published>2014-03-02T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Userscript to focus the first input field on a page with a hotkey</summary>
       +        <content type="html"><![CDATA[<h1>Userscript: focus input field</h1>
       +        <p><strong>Last modification on </strong> <time>2014-03-02</time></p>
       +        <p>This is an userscript I wrote a while ago which allows to focus the first input
       +field on a page with ctrl+space.  This is useful if a site doesn't specify the
       +autofocus attribute for an input field and you don't want to switch to it using
       +the mouse.</p>
       +<h2>Download</h2>
       +<p><a href="downloads/input_focus.user.js">Download userscript input_focus.user.js</a></p>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>Userscript: Youtube circumvent age verification</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/userscript-youtube-circumvent-age-verification" />
       +        <id>gopher://codemadness.org/1/phlog/userscript-youtube-circumvent-age-verification</id>
       +        <updated>2020-12-27T00:00:00Z</updated>
       +        <published>2013-02-21T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Userscript to circumvent Youtube age verification and redirect to the video</summary>
       +        <content type="html"><![CDATA[<h1>Userscript: Youtube circumvent age verification</h1>
       +        <p><strong>Last modification on </strong> <time>2020-12-27</time></p>
       +        <p>This is an userscript I wrote a while ago which circumvents requiring to login
       +with an account on Youtube if a video requires age verification.</p>
       +<p><strong>Note: this is an old script and does not work anymore.</strong></p>
       +<h2>Download</h2>
       +<p><a href="downloads/youtube_circumvent_sign_in.user.js">Download userscript Youtube_circumvent_sign_in.user.js</a></p>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>Userscript: block stupid fonts</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/userscript-block-stupid-fonts" />
       +        <id>gopher://codemadness.org/1/phlog/userscript-block-stupid-fonts</id>
       +        <updated>2020-03-10T00:00:00Z</updated>
       +        <published>2012-10-21T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Userscript to whitelist your favorite fonts and block the rest</summary>
       +        <content type="html"><![CDATA[<h1>Userscript: block stupid fonts</h1>
       +        <p><strong>Last modification on </strong> <time>2020-03-10</time></p>
       +        <p>This is an userscript I wrote a while ago which white-lists fonts I like and
       +blocks the rest.  The reason I made this is because I don't like the
       +inconsistency of custom fonts used on a lot of websites.</p>
       +<h2>Download</h2>
       +<p><a href="downloads/block_stupid_fonts_v1.2.user.js">Download userscript Block_stupid_fonts_v1.2.user.js</a></p>
       +<p>Old version: <a href="downloads/block_stupid_fonts.user.js">Download userscript Block_stupid_fonts.user.js</a></p>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>Sfeed: simple RSS and Atom parser</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/sfeed" />
       +        <id>gopher://codemadness.org/1/phlog/sfeed</id>
       +        <updated>2022-11-05T00:00:00Z</updated>
       +        <published>2011-04-01T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Sfeed is a simple RSS and Atom parser (and format programs to add reader functionality)</summary>
       +        <content type="html"><![CDATA[<h1>Sfeed: simple RSS and Atom parser</h1>
       +        <p><strong>Last modification on </strong> <time>2022-11-05</time></p>
       +        <p>Sfeed is a RSS and Atom parser (and some format programs).</p>
       +<p>It converts RSS or Atom feeds from XML to a TAB-separated file. There are
       +formatting programs included to convert this TAB-separated format to various
       +other formats. There are also some programs and scripts included to import and
       +export OPML and to fetch, filter, merge and order feed items.</p>
       +<p>For the most (up-to-date) information see the <a href="/git/sfeed/file/README.html">README</a>.</p>
       +<h2>Clone</h2>
       +<pre><code>git clone git://git.codemadness.org/sfeed
       +</code></pre>
       +<h2>Browse</h2>
       +<p>You can browse the source-code at:</p>
       +<ul>
       +<li><a href="https://git.codemadness.org/sfeed/">https://git.codemadness.org/sfeed/</a></li>
       +<li><a href="gopher://codemadness.org/1/git/sfeed">gopher://codemadness.org/1/git/sfeed</a></li>
       +</ul>
       +<h2>Download releases</h2>
       +<p>Releases are available at:</p>
       +<ul>
       +<li><a href="https://codemadness.org/releases/sfeed/">https://codemadness.org/releases/sfeed/</a></li>
       +<li><a href="gopher://codemadness.org/1/releases/sfeed">gopher://codemadness.org/1/releases/sfeed</a></li>
       +</ul>
       +<h2>Build and install</h2>
       +<pre><code>$ make
       +# make install
       +</code></pre>
       +<h2>Screenshot and examples</h2>
       +<p><a href="downloads/screenshots/sfeed-screenshot.png"><img src="downloads/screenshots/sfeed-thumb.png" alt="Screenshot of sfeed piped to sfeed_plain using dmenu in vertical-list mode" width="400" height="232" loading="lazy" /></a></p>
       +<p>The above screenshot uses the sfeed_plain format program with <a href="https://tools.suckless.org/dmenu/">dmenu</a>.  This
       +program outputs the feed items in a compact way per line as plain-text to
       +stdout.  The dmenu program reads these lines from stdin and displays them as a
       +X11 list menu. When an item is selected in dmenu it prints this item to stdout.
       +A simple written script can then filter for the URL in this output and do some
       +action, like opening it in some browser or open a podcast in your music player.</p>
       +<p>For example:</p>
       +<pre><code>#!/bin/sh
       +url=$(sfeed_plain "$HOME/.sfeed/feeds/"* | dmenu -l 35 -i | \
       +        sed -n 's@^.* \([a-zA-Z]*://\)\(.*\)$@\1\2@p')
       +test -n "${url}" &amp;&amp; $BROWSER "${url}"
       +</code></pre>
       +<p>However this is just one way to format and interact with feed items.
       +See also the README for other practical examples.</p>
       +<p>Below are some examples of output that are supported by the included format
       +programs:</p>
       +<ul>
       +<li><a href="downloads/sfeed/plain/feeds.txt">plain text (UTF-8)</a></li>
       +<li><a href="downloads/sfeed/atom/feeds.xml">atom</a></li>
       +<li>gopher</li>
       +<li><a href="downloads/sfeed/html/feeds.html">HTML (CSS)</a></li>
       +<li><a href="downloads/sfeed/frames/index.html">HTML frames</a></li>
       +<li><a href="jsonfeed_content.json">JSON Feed</a></li>
       +<li><a href="downloads/sfeed/mbox/feeds.mbox">mbox</a></li>
       +<li><a href="downloads/sfeed/twtxt/twtxt.txt">twtxt</a></li>
       +</ul>
       +<p>There is also a curses UI front-end, see the page <a href="sfeed_curses.html">sfeed_curses</a>.
       +It is now part of sfeed.</p>
       +<h2>Videos</h2>
       +<p>Here are some videos of other people showcasing some of the functionalities of
       +sfeed, sfeed_plain and sfeed_curses.  To the creators: thanks for making these!</p>
       +<ul>
       +<li><a href="https://www.youtube.com/watch?v=RnuY32DP9jU">sfeed: RSS/Atom Feeds without the Suck (Youtube)</a><br />  
       +by <a href="https://www.youtube.com/channel/UCQQB104oMOos758GTOdx_kQ">noocsharp</a>
       +<a href="downloads/sfeed/videos/sfeed_without_the_suck.mp4">(mirror)</a><br />  
       +Video published on March 8 2020.</li>
       +<li><a href="https://www.youtube.com/watch?v=ok8k639GoRU">Sfeed - news in the terminal with minimalism (Youtube)</a><br />  
       +by <a href="https://www.youtube.com/channel/UCJetJ7nDNLlEzDLXv7KIo0w">Gavin Freeborn</a>
       +<a href="downloads/sfeed/videos/sfeed_news_in_terminal.mp4">(mirror)</a><br />  
       +Video published on January 15 2021.</li>
       +<li><a href="https://www.youtube.com/watch?v=xMkW4iJzot0">Sfeed - Peak Minimal RSS Feed Reader (Youtube)</a><br />  
       +by <a href="https://www.youtube.com/channel/UCld68syR8Wi-GY_n4CaoJGA">Brodie Robertson</a>
       +<a href="downloads/sfeed/videos/sfeed_minimalism.mp4">(mirror)</a><br />  
       +Video published on February 23 2021.</li>
       +<li><a href="https://www.youtube.com/watch?v=O8x0MAyqvt0">RSS with sfeed, fdm, and mblaze! (Youtube)</a><br />  
       +by <a href="https://www.youtube.com/channel/UCz_u0h4usMbnFsIHSVdjUQw">Joseph Choe</a>
       +<a href="downloads/sfeed/videos/rss_with_sfeed_fdm_and_mblaze.mp4">(mirror)</a><br />  
       +Website: <a href="https://josephchoe.com/rss-terminal">https://josephchoe.com/rss-terminal</a><br />  
       +Video published on 4 November 2022.</li>
       +</ul>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>Vim theme: relaxed</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/vim-theme-relaxed" />
       +        <id>gopher://codemadness.org/1/phlog/vim-theme-relaxed</id>
       +        <updated>2011-01-07T00:00:00Z</updated>
       +        <published>2011-01-07T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>a dark VIM theme I made and use on a daily basis</summary>
       +        <content type="html"><![CDATA[<h1>Vim theme: relaxed</h1>
       +        <p><strong>Last modification on </strong> <time>2011-01-07</time></p>
       +        <p>This is a dark theme I made for <a href="https://www.vim.org/">vim</a>.  This is a theme I personally used for
       +quite a while now and over time tweaked to my liking.  It is made for gvim, but
       +also works for 16-colour terminals (with small visual differences).  The
       +relaxed.vim file also has my .Xdefaults file colours listed at the top for
       +16+-colour terminals on X11.</p>
       +<p>It is inspired by the "desert" theme available at
       +<a href="https://www.vim.org/scripts/script.php?script_id=105">https://www.vim.org/scripts/script.php?script_id=105</a>, although I removed the
       +cursive and bold styles and changed some colours I didn't like.</p>
       +<h2>Download</h2>
       +<p><a href="downloads/themes/vim/relaxed.vim">relaxed.vim</a></p>
       +<h2>Screenshot</h2>
       +<p><a href="downloads/themes/vim/vim_relaxed_theme.png"><img src="downloads/themes/vim/vim_relaxed_theme_thumb.png" alt="Screenshot of VIM theme relaxed on the left is gvim (GUI), on the right is vim in urxvt (terminal)" width="480" height="300" loading="lazy" /></a></p>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>Seturgent: set urgency hints for X applications</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/seturgent" />
       +        <id>gopher://codemadness.org/1/phlog/seturgent</id>
       +        <updated>2020-07-20T00:00:00Z</updated>
       +        <published>2010-10-31T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Seturgent is a small utility to set an application it&#39;s urgency hint</summary>
       +        <content type="html"><![CDATA[<h1>Seturgent: set urgency hints for X applications</h1>
       +        <p><strong>Last modification on </strong> <time>2020-07-20</time></p>
       +        <p>Seturgent is a small utility to set an application its urgency hint.  For most
       +windowmanager's and panel applications this will highlight the application and
       +will allow special actions.</p>
       +<h2>Clone</h2>
       +<pre><code>    git clone git://git.codemadness.org/seturgent
       +</code></pre>
       +<h2>Browse</h2>
       +<p>You can browse the source-code at:</p>
       +<ul>
       +<li><a href="https://git.codemadness.org/seturgent/">https://git.codemadness.org/seturgent/</a></li>
       +<li><a href="gopher://codemadness.org/1/git/seturgent">gopher://codemadness.org/1/git/seturgent</a></li>
       +</ul>
       +<h2>Download releases</h2>
       +<p>Releases are available at:</p>
       +<ul>
       +<li><a href="https://codemadness.org/releases/seturgent/">https://codemadness.org/releases/seturgent/</a></li>
       +<li><a href="gopher://codemadness.org/1/releases/seturgent">gopher://codemadness.org/1/releases/seturgent</a></li>
       +</ul>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>DWM-hiltjo: my windowmanager configuration</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/dwm" />
       +        <id>gopher://codemadness.org/1/phlog/dwm</id>
       +        <updated>2020-07-20T00:00:00Z</updated>
       +        <published>2010-08-12T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>My DWM configuration; a few added features to suit my needs</summary>
       +        <content type="html"><![CDATA[<h1>DWM-hiltjo: my windowmanager configuration</h1>
       +        <p><strong>Last modification on </strong> <time>2020-07-20</time></p>
       +        <p><a href="https://dwm.suckless.org/">DWM</a> is a very minimal windowmanager. It has the most essential features I
       +need, everything else is "do-it-yourself" or extending it with the many
       +available <a href="https://dwm.suckless.org/patches/">patches</a>. The vanilla version is less than 2000 SLOC. This makes it
       +easy to understand and modify it.</p>
       +<p>I really like my configuration at the moment and want to share my changes. Some
       +of the features listed below are patches from suckless.org I applied, but there
       +are also some changes I made.</p>
       +<p>This configuration is entirely tailored for my preferences of course.</p>
       +<h2>Features</h2>
       +<ul>
       +<li>Titlebar:
       +<ul>
       +<li>Shows all clients of the selected / active tags.</li>
       +<li>Divide application titlebars evenly among available space.</li>
       +<li>Colour urgent clients in the taskbar on active tags.</li>
       +<li>Left-click focuses clicked client.</li>
       +<li>Right-click toggles monocle layout.</li>
       +<li>Middle-click kills the clicked client.</li>
       +</ul>
       +</li>
       +<li>Tagbar:
       +<ul>
       +<li>Only show active tags.</li>
       +<li>Colour inactive tags with urgent clients.</li>
       +</ul>
       +</li>
       +<li>Layouts:
       +<ul>
       +<li>Cycle layouts with Modkey + Space (next) and Modkey + Control + Space
       +(previous).</li>
       +<li>Fullscreen layout (hides topbar and removes borders).</li>
       +</ul>
       +</li>
       +<li>Other:
       +<ul>
       +<li>Move tiled clients around with the mouse (drag-move), awesomewm-like.</li>
       +<li>Add some keybinds for multimedia keyboards (audio play / pause, mute, www,
       +volume buttons, etc).</li>
       +</ul>
       +</li>
       +<li>... and more ;) ...</li>
       +</ul>
       +<h2>Clone</h2>
       +<pre><code>git clone -b hiltjo git://git.codemadness.org/dwm
       +</code></pre>
       +<h2>Screenshot</h2>
       +<p><a href="downloads/screenshots/dwm-screenshot.png"><img src="downloads/screenshots/dwm-screenshot-thumb.png" alt="Screenshot showing what dwm-hiltjo looks like" width="480" height="300" loading="lazy" /></a></p>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>Query unused CSS rules on current document state</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/query-unused-css-rules-on-current-document-state" />
       +        <id>gopher://codemadness.org/1/phlog/query-unused-css-rules-on-current-document-state</id>
       +        <updated>2010-04-21T00:00:00Z</updated>
       +        <published>2010-04-21T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>How to see all the rules in a stylesheet (CSS) that are not used for the current document</summary>
       +        <content type="html"><![CDATA[<h1>Query unused CSS rules on current document state</h1>
       +        <p><strong>Last modification on </strong> <time>2010-04-21</time></p>
       +        <p>Today I was doing some web development and wanted to see all the rules in a
       +stylesheet (CSS) that were not used for the current document. I wrote the
       +following Javascript code which you can paste in the Firebug console and run:</p>
       +<pre><code>(function() {
       +        for (var i=0;i&lt;document.styleSheets.length;i++) {
       +                var rules = document.styleSheets[i].cssRules || [];
       +                var sheethref = document.styleSheets[i].href || 'inline';
       +                for (var r=0;r&lt;rules.length;r++)
       +                        if (!document.querySelectorAll(rules[r].selectorText).length)
       +                                console.log(sheethref + ': "' + rules[r].selectorText + '" not found.');
       +        }
       +})();
       +</code></pre>
       +<p>This will output all the (currently) unused CSS rules per selector, the output can be for example:</p>
       +<pre><code>http://www.codemadness.nl/blog/wp-content/themes/codemadness/style.css: "fieldset, a img" not found.
       +http://www.codemadness.nl/blog/wp-content/themes/codemadness/style.css: "#headerimg" not found.
       +http://www.codemadness.nl/blog/wp-content/themes/codemadness/style.css: "a:hover" not found.
       +http://www.codemadness.nl/blog/wp-content/themes/codemadness/style.css: "h2 a:hover, h3 a:hover" not found.
       +http://www.codemadness.nl/blog/wp-content/themes/codemadness/style.css: ".postmetadata-center" not found.
       +http://www.codemadness.nl/blog/wp-content/themes/codemadness/style.css: ".thread-alt" not found.
       +</code></pre>
       +<p>Just a trick I wanted to share, I hope someone finds this useful :)</p>
       +<p>For webkit-based browsers you can use "Developer Tools" and use "Audits" under
       +"Web Page Performance" it says "Remove unused CSS rules". For Firefox there is
       +also Google Page Speed: <a href="https://code.google.com/speed/page-speed/">https://code.google.com/speed/page-speed/</a> this adds
       +an extra section under Firebug.</p>
       +<p>Tested on Chrome and Firefox.</p>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>Driconf: enabling S3 texture compression on Linux</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/driconf" />
       +        <id>gopher://codemadness.org/1/phlog/driconf</id>
       +        <updated>2020-08-21T00:00:00Z</updated>
       +        <published>2009-07-05T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>driconf: enabling S3 texture compression</summary>
       +        <content type="html"><![CDATA[<h1>Driconf: enabling S3 texture compression on Linux</h1>
       +        <p><strong>Last modification on </strong> <time>2020-08-21</time></p>
       +        <p><strong>Update: the DXTC patent expired on 2018-03-16, many distros enable this by
       +default now.</strong></p>
       +<p>S3TC (also known as DXTn or DXTC) is a patented lossy texture compression
       +algorithm.  See: <a href="https://en.wikipedia.org/wiki/S3TC">https://en.wikipedia.org/wiki/S3TC</a> for more detailed
       +information.  Many games use S3TC and if you use Wine to play games you
       +definitely want to enable it if your graphics card supports it.</p>
       +<p>Because this algorithm was <a href="https://dri.freedesktop.org/wiki/S3TC/">patented it is disabled by default on many Linux
       +distributions</a>.</p>
       +<p>To enable it you can install the library "libtxc" if your favorite OS has not
       +installed it already.</p>
       +<p>For easy configuration you can install the optional utility DRIconf, which you
       +can find at: <a href="https://dri.freedesktop.org/wiki/DriConf">https://dri.freedesktop.org/wiki/DriConf</a>.  DriConf can safely be
       +removed after configuration.</p>
       +<h2>Steps to enable it</h2>
       +<p>Install libtxc_dxtn:</p>
       +<p>ArchLinux:
       +<pre><code># pacman -S libtxc_dxtn
       +</code></pre>
       +<p>Debian:
       +<pre><code># aptitude install libtxc-dxtn-s2tc0
       +</code></pre>
       +</p>
       +</p>
       +<p>Install driconf (optional):</p>
       +<p>ArchLinux:</p>
       +<pre><code># pacman -S driconf
       +</code></pre>
       +<p>Debian:</p>
       +<pre><code># aptitude install driconf
       +</code></pre>
       +<p>Run driconf and enable S3TC:</p>
       +<p><a href="downloads/screenshots/driconf.png"><img src="downloads/screenshots/driconf-thumb.png" alt="Screenshot of DRIconf window and its options" width="300" height="266" loading="lazy" /></a></p>
       +<h2>Additional links</h2>
       +<ul>
       +<li>S3TC: <a href="https://dri.freedesktop.org/wiki/S3TC/">https://dri.freedesktop.org/wiki/S3TC/</a></li>
       +<li>DriConf: <a href="https://dri.freedesktop.org/wiki/DriConf">https://dri.freedesktop.org/wiki/DriConf</a></li>
       +</ul>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>Getting the USB-powerline bridge to work on Linux</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/getting-the-usb-powerline-bridge-to-work-on-linux" />
       +        <id>gopher://codemadness.org/1/phlog/getting-the-usb-powerline-bridge-to-work-on-linux</id>
       +        <updated>2019-12-06T00:00:00Z</updated>
       +        <published>2009-04-13T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>A guide to get a USB-powerline bridge with the Intellon 51x1 chipset working on Linux</summary>
       +        <content type="html"><![CDATA[<h1>Getting the USB-powerline bridge to work on Linux</h1>
       +        <p><strong>Last modification on </strong> <time>2019-12-06</time></p>
       +        <p><strong>NOTE: this guide is obsolete, a working driver is now included in the Linux
       +kernel tree (<a href="https://lkml.org/lkml/2009/4/18/121">since Linux 2.6.31</a>)</strong></p>
       +<h2>Introduction</h2>
       +<p>A USB to powerline bridge is a network device that instead of using an ordinary
       +Ethernet cable (CAT5 for example) or wireless LAN it uses the powerlines as a
       +network to communicate with similar devices.  A more comprehensive explanation
       +of what it is and how it works you can find here:
       +<a href="https://en.wikipedia.org/wiki/IEEE_1901">https://en.wikipedia.org/wiki/IEEE_1901</a>.</p>
       +<p>Known products that use the Intellon 51x1 chipset:</p>
       +<ul>
       +<li>MicroLink dLAN USB</li>
       +<li>"Digitus network"</li>
       +<li>Intellon USB Ethernet powerline adapter</li>
       +<li>Lots of other USB-powerline adapters...</li>
       +</ul>
       +<p>To check if your device is supported:</p>
       +<pre><code>$ lsusb | grep -i 09e1
       +Bus 001 Device 003: ID 09e1:5121 Intellon Corp.
       +</code></pre>
       +<p>If the vendor (09e1) and product (5121) ID match then it's probably supported.</p>
       +<h2>Installation</h2>
       +<p>Get drivers from the official site:
       +<a href="http://www.devolo.co.uk/consumer/downloads-44-microlink-dlan-usb.html?l=en">http://www.devolo.co.uk/consumer/downloads-44-microlink-dlan-usb.html?l=en</a> or
       +<a href="downloads/int51x1/dLAN-linux-package-v4.tar.gz">mirrored here</a>.
       +The drivers from the official site were/are more up-to-date.</p>
       +<p>Extract them:</p>
       +<pre><code>$ tar -xzvf dLAN-linux-package-v4.tar.gz
       +</code></pre>
       +<p>Go to the extracted directory and compile them:</p>
       +<pre><code>$ ./configure
       +$ make
       +</code></pre>
       +<p>Depending on the errors you got you might need to <a href="downloads/int51x1/int51x1.patch">download</a> and apply
       +my patch:</p>
       +<pre><code>$ cd dLAN-linux-package-v4/     (or other path to the source code)
       +$ patch &lt; int51x1.patch
       +</code></pre>
       +<p>Try again:</p>
       +<pre><code>$ ./configure
       +$ make
       +</code></pre>
       +<p>If that failed try:</p>
       +<pre><code>$ ./configure
       +$ KBUILD_NOPEDANTIC=1 make
       +</code></pre>
       +<p>If that went OK install the drivers (as root):</p>
       +<pre><code># make install
       +</code></pre>
       +<p>Check if the "devolo_usb" module is loaded:</p>
       +<pre><code>$ lsmod | grep -i devolo_usb
       +</code></pre>
       +<p>If it shows up then it's loaded. Now check if the interface is added:</p>
       +<pre><code>$ ifconfig -a | grep -i dlanusb
       +dlanusb0 Link encap:Ethernet HWaddr 00:12:34:56:78:9A
       +</code></pre>
       +<h2>Configuration</h2>
       +<p>It is assumed you use a static IP, otherwise you can just use your DHCP client
       +to get an unused IP address from your DHCP server. Setting up the interface is
       +done like this (change the IP address and netmask accordingly if it's
       +different):</p>
       +<pre><code># ifconfig dlanusb0 192.168.2.12 netmask 255.255.255.0
       +</code></pre>
       +<h2>Checking if the network works</h2>
       +<p>Try to ping an IP address on your network to test for a working connection:</p>
       +<pre><code>$ ping 192.168.2.1
       +PING 192.168.2.1 (192.168.2.1) 56(84) bytes of data.
       +64 bytes from 192.168.2.1: icmp_seq=1 ttl=30 time=2.49 ms
       +64 bytes from 192.168.2.1: icmp_seq=2 ttl=30 time=3.37 ms
       +64 bytes from 192.168.2.1: icmp_seq=3 ttl=30 time=2.80 ms
       +--- 192.168.2.1 ping statistics ---
       +3 packets transmitted, 3 received, 0% packet loss, time 2005ms
       +rtt min/avg/max/mdev = 2.497/2.891/3.374/0.368 ms
       +</code></pre>
       +<p>You can now set up a network connection like you normally do with any Ethernet
       +device.  The route can be added like this for example:</p>
       +<pre><code># route add -net 0.0.0.0 netmask 0.0.0.0 gw 192.168.2.1 dlanusb0
       +</code></pre>
       +<p>Change the IP address of your local gateway accordingly. Also make sure your
       +nameserver is set in /etc/resolv.conf, something like:</p>
       +<pre><code>nameserver 192.168.2.1
       +</code></pre>
       +<p>Test your internet connection by doing for example:</p>
       +<pre><code>$ ping codemadness.org
       +PING codemadness.org (64.13.232.151) 56(84) bytes of data.
       +64 bytes from acmkoieeei.gs02.gridserver.com (64.13.232.151): icmp_seq=1 ttl=52 time=156 ms
       +64 bytes from acmkoieeei.gs02.gridserver.com (64.13.232.151): icmp_seq=2 ttl=52 time=156 ms
       +64 bytes from acmkoieeei.gs02.gridserver.com (64.13.232.151): icmp_seq=3 ttl=52 time=155 ms
       +--- codemadness.org ping statistics ---
       +3 packets transmitted, 3 received, 0% packet loss, time 1999ms
       +rtt min/avg/max/mdev = 155.986/156.312/156.731/0.552 ms
       +</code></pre>
       +<p>If this command failed you probably have not setup your DNS/gateway properly.
       +If it worked then good for you :)</p>
       +<h2>References</h2>
       +<ul>
       +<li><a href="http://www.devolo.co.uk/consumer/downloads-44-microlink-dlan-usb.html?l=en">Devolo download page with drivers (USB version).</a></li>
       +<li><a href="downloads/int51x1/dLAN-linux-package-v4.tar.gz">dLAN-linux-package-v4.tar.gz</a></li>
       +<li><a href="downloads/int51x1/int51x1.patch">Patch for recent 2.6.x kernels</a></li>
       +<li><a href="downloads/int51x1/INT51X1_datasheet.pdf">INT51X1 datasheet</a></li>
       +</ul>
       +]]></content>
       +</entry>
       +<entry>
       +        <title>Gothic 1 game guide</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/gothic" />
       +        <id>gopher://codemadness.org/1/phlog/gothic</id>
       +        <updated>2025-01-05T00:00:00Z</updated>
       +        <published>2009-04-12T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Gothic 1 game guide with some useful tips</summary>
       +        <content type="html"><![CDATA[<h1>Gothic 1 game guide</h1>
       +        <p><strong>Last modification on </strong> <time>2025-01-05</time></p>
       +        <p><strong>Disclaimer:</strong>
       +Some (including myself) may find some of these hints/exploits cheating. This
       +guide is just for educational and fun purposes. Some of these hints/tips apply
       +to Gothic 2 as well. I got the meat exploit from a guide somewhere on the
       +internet I can't recall where, anyway kudos to that person. Some of the
       +exploits I discovered myself.</p>
       +<h2>Configuration</h2>
       +<h3>Widescreen resolution</h3>
       +<p>Gothic supports widescreen resolutions with a small tweak, add the following
       +text string as a command-line argument:</p>
       +<pre><code>-zRes:1920,1200,32
       +</code></pre>
       +<p>This also works for Gothic 2. Here 1920 is the width, 1200 the height and 32
       +the bits per pixel, change this to your preferred resolution.</p>
       +<h3>Fix crash with Steam version</h3>
       +<p>Disable steam overlay. If that doesn't work rename GameOverlayRenderer.dll in
       +your steam folder to _GameOverlayRenderer.dll.  I strongly recommend to buy the
       +better version from <a href="https://www.gog.com/game/gothic">GOG.com</a>.  The GOG version has no DRM and allows easier
       +modding, it also allows playing in most published languages: German, English,
       +Polish, furthermore it has some original artwork and soundtrack included.</p>
       +<h3>Upgrade Steam version to stand-alone version and remove Steam DRM (Gothic 1 and 2)</h3>
       +<p>You can install the Gothic playerkit and patches to remove the Steam DRM.</p>
       +<p><a href="https://www.worldofgothic.de/">WorldOfGothic</a> playerkit patches:</p>
       +<ul>
       +<li>Gothic 1 (EN):    <a href="https://www.worldofgothic.com/dl/?go=dlfile&amp;fileid=28">https://www.worldofgothic.com/dl/?go=dlfile&amp;fileid=28</a></li>
       +<li>Gothic 1 (DE):    <a href="https://www.worldofgothic.de/dl/download_34.htm">https://www.worldofgothic.de/dl/download_34.htm</a></li>
       +<li>Gothic 2 (EN/DE): <a href="https://www.worldofgothic.de/dl/download_168.htm">https://www.worldofgothic.de/dl/download_168.htm</a></li>
       +</ul>
       +<h3>Play Gothic in a different language with English subtitles</h3>
       +<p>If you're like me and have played the English version many times, but would
       +like to hear the (original) German voice audio or if you would like to play
       +with different audio than you're used to, then you can copy the speech.vdf file
       +of your preferred version to your game files. Optionally turn on subtitles.
       +I've used this to play the English version of Gothic with the original German
       +voice audio and English subtitles.
       +This works best with the version from GOG as it allows easier modding.</p>
       +<h2>Easy money/weapons/armour/other items</h2>
       +<h3>Steal from Huno</h3>
       +<p>At night attack Huno the smith in the Old Camp and steal all his steel. Then
       +make some weapons and sell them with a merchant.  When you ask Huno about
       +blacksmith equipment it will respawn with 5 of each kind of steel. This is also
       +a fairly good starting weapon (requires 20 strength).  Also his chest located
       +near the sharpening stone and fire contains some steel as well, lock-pick it.
       +The combination is: RRLRLL. The chest contains at least 20 raw steel, forge it
       +to get 20 crude swords which you can sell for 50 ore each to a merchant.  This
       +will generate some nice starting money (1000+ ore) :)</p>
       +<h3>Steal weapons from the castle in the Old Camp</h3>
       +<p>This tip is useful for getting pretty good starting weapons.</p>
       +<p>Before entering the castle itself drop your ore (Left control + down for me)
       +in front of it. This will ensure when you get caught (and you probably will ;))
       +no ore will get stolen by the guards. Now use the "slip past guard" technique
       +described below and you should be able to get into Gomez his castle. Run to the
       +left where some weapons are stored. Now make sure you at least steal the best
       +weapon (battle sword) and steal as much as you can until you get whacked.  I
       +usually stand in the corner since that's where the best weapons are (battle
       +sword, judgement sword, etc). You'll now have some nice starting weapon(s) and
       +the good thing is they require very little attributes (about 13 strength).</p>
       +<p>Location: <a href="downloads/gothic1/old_camp_swords.png">screenshot</a></p>
       +<h3>Free scraper armour the New Camp</h3>
       +<p>In the New Camp go to the mine and talk to Swiney at the bottom of "The
       +Hollow". Ask who he is and then ask to join the scrapers.  He will give you a
       +"Diggers dress" worth 250 ore. It has the following stats: + 10 against
       +weapons. + 5 against fire.  This will also give you free entrance to the bar in
       +the New Camp.</p>
       +<h3>Unlimited water bottles in the New Camp</h3>
       +<p>In the quest from Lefty you will be assigned to get water bottles from the
       +rice lord.  He will give you infinite amounts of water bottles, in batches of
       +12.</p>
       +<h3>Armour amulet and increase HP potion</h3>
       +<p>In the Old Camp in the main castle there are at least 3 chests with valuable
       +items that don't require a key:</p>
       +<ul>
       +<li><p>Middle right side (looking from the entrance), 1 chest:
       +<ul>
       +<li>lock combination: LLLLRLRL</li>
       +<li>loot:
       +<ul>
       +<li>+15 against weapons, +15 against arrows (amulet of stone skin)
       +(worth: 1000 ore)</li>
       +</ul>
       +</li>
       +<li>additionally there are 2 locked doors at the right side in this room. In
       +the final room there are 3 floors with lots of chests.<br />  
       +<a href="downloads/gothic1/video/amulet.mp4">Video of the location</a></li>
       +</ul>
       +</p>
       +</li>
       +<li><p>Left side, 1 chest:
       +<ul>
       +<li>lock combination: RLLLLLRR</li>
       +<li>loot:
       +<ul>
       +<li>+8 mana amulet (worth: 600 ore)</li>
       +<li>2 potions (+70 hp)</li>
       +<li>dreamcall (weed)</li>
       +<li>120 coins (worth: nothing)</li>
       +</ul>
       +</li>
       +</ul>
       +</p>
       +</li>
       +<li><p>Right side, 2 chests with:
       +<ul>
       +<li>lock combination: RLLLRLLR</li>
       +<li>loot:
       +<ul>
       +<li>armour amulets, +15 against weapons (worth: 600 ore)</li>
       +<li>maximum life potion, +10 maximum life (worth: 1000 ore)</li>
       +<li>speed potion (1 minute duration)</li>
       +<li>4 potions (+70 hp)</li>
       +</ul>
       +</li>
       +</ul>
       +</p>
       +</li>
       +</ul>
       +<h3>Swamp/Sect Camp harvest twice</h3>
       +<p>In the swamp-weed harvest quest you must get swamp-weed for a guru. After this
       +quest you can get the harvest again, but you can keep the harvest without
       +consequences.</p>
       +<h2>Exploits</h2>
       +<h3>Slip past guards</h3>
       +<p>This exploit is really simple, just draw your weapon before you're "targeted"
       +by the guard and run past them, this bypasses the dialog sequence.  When you're
       +just out of their range holster your weapon again, so the people around won't
       +get pissed off.</p>
       +<p>Works really well on the guards in front of the Old camp's castle, Y'Berrion
       +templars and New Camp mercenaries near the Water magicians, just to name a few.</p>
       +<p><a href="downloads/gothic1/video/amulet.mp4">Video</a></p>
       +<h3>Meat duplication</h3>
       +<p>Go to a pan and focus / target it so it says "frying pan" or similar. Now open
       +your inventory and select the meat. Now cook the meat (for me Left Control +
       +Arrow up). The inventory should remain open. You'll now have twice as much meat
       +as you had before. Do this a few times and you'll have a lot of meat, easy for
       +trading with ore/other items as well. This exploit does not work with the
       +community patch applied.</p>
       +<h3>Glitch through (locked) doors and walls</h3>
       +<p>You can glitch through walls by strafing into them. Then when the player is
       +partially collided into a door or wall you can jump forward to glitch through
       +it.</p>
       +<p><a href="downloads/gothic1/video/bloodsword.mp4">Video</a></p>
       +<h3>Fall from great heights</h3>
       +<p>When you fall or jump from where you usually get fall damage you can do the
       +following trick: slightly before the ground use left or right strafe.  This
       +works because it resets the falling animation. There are also other ways to
       +achieve the same thing cancelling the falling animation, such as attacking with
       +a weapon in the air.</p>
       +<p><a href="downloads/gothic1/video/fall.mp4">Video</a></p>
       +<h2>Experience / level up tips</h2>
       +<h3>Test of faith (extra exp)</h3>
       +<p>You get an additional 750 exp (from Lares) when you forge the letter in the new
       +camp and then give it to Diego. You can still join both camps after this.</p>
       +<h3>Fighting skeleton mages and their skeletons</h3>
       +<p>An easy way to get more experience is to let the skeleton mages summon as much
       +skeletons as they can, instead of rushing to kill the summoner immediately.
       +After you have defeated all of them: kill the skeleton mage.</p>
       +<h3>Permanent str/dex/mana/hp potions/items and teachers</h3>
       +<p>When you want to get the maximum power at the end of the game you should save
       +up the items that give you a permanent boost. Teachers of strength, dexterity
       +and mana won't train over 100 of each skill.  However using potions and quest
       +rewards you can increase this over 100.</p>
       +<p>You should also look out for the following:</p>
       +<ul>
       +<li><p>Learn to get extra force into your punch from Horatio (strength +5, this
       +can't be done after level 100 strength). Talking to Jeremiah in the New Camp
       +bar unlocks the dialog option to train strength at Horatio.</p>
       +</li>
       +<li><p>Smoke the strongest non-quest joint (+2 mana).</p>
       +</li>
       +</ul>
       +<h3>Permanent potions in Sleeper temple</h3>
       +<p>This one is really obvious, but I would like to point out the mummy's on each
       +side where Xardas is located have lots and I mean lots of permanent potions.
       +This will give you a nice boost before the end battle.</p>
       +<p>Location, left and right corridor in the Sleeper temple: <a href="downloads/gothic1/sleeper_temple_potions.png">screenshot</a><br />  
       +Mummies, you can loot them: <a href="downloads/gothic1/sleeper_temple_potions_mummies.png">screenshot</a><br />  </p>
       +<h3>Permanent potions as reward in quests</h3>
       +<p>Always pick the permanent potion as a reward for quests when you can, for
       +example the quest for delivering the message to the High Fire magicians (mana
       +potion) or the one for fetching the almanac for the Sect Camp.  Don't forget to
       +pick up the potions from Riordian the water magician when you're doing the
       +focus stones quest, it contains a strength and dexterity potion (+3).</p>
       +<h3>Improve ancient ore armour further</h3>
       +<p>In the last chapters the blacksmith Stone from the Old Camp is captured If you
       +save him from the prison cell in the Old Camp the reward will have a few
       +options.  One of the options is improving the Ancient Ore armour.</p>
       +<h2>Good early game weapons available in chapter 1</h2>
       +<h3>Orc Hammer</h3>
       +<p>Location: in a cave near bloodhounds near the mountain fort.<br />  
       +It can be reached from a path from the swamp camp up to the mountain.
       +Watch out for the bloodhounds. They can instantly kill you in the early game.</p>
       +<p>Location: <a href="downloads/gothic1/early_weapon_old_orc_hammer_location.png">screenshot</a><br />  
       +Stats: <a href="downloads/gothic1/early_weapon_old_orc_hammer_stats.png">screenshot</a><br />  </p>
       +<p>Stats:
       +<ul>
       +<li>Type: one-handed</li>
       +<li>Damage: 50</li>
       +<li>Required strength: 22</li>
       +<li>Worth: 1000 ore</li>
       +</ul>
       +</p>
       +<p>It has very low strength stat requirement and has high damage for the early
       +game chapters.  A downside is the lower weapon swing range.
       +It is also a decent weapon against stone golems.</p>
       +<h3>Old Battle Axe</h3>
       +<p>Location: near Xardas his tower.<br />  
       +Watch out for a group of Biters lurking there.</p>
       +<p>Location: <a href="downloads/gothic1/early_weapon_old_battle_axe_location.png">screenshot</a><br />  
       +Stats: <a href="downloads/gothic1/early_weapon_old_battle_axe_stats.png">screenshot</a><br />  </p>
       +<p>Stats:
       +<ul>
       +<li>Type: two-handed</li>
       +<li>Damage: 67</li>
       +<li>Required strength: 36</li>
       +<li>Worth: 1800 ore</li>
       +</ul>
       +</p>
       +<p>It has a relatively low strength requirements and is available in game chapter
       +1 or could be sold for a decent amount.</p>
       +<h3>Random/beginner tips</h3>
       +<ul>
       +<li><p>If you want to talk to a NPC, but some animation of them takes too long (like
       +eating, drinking, smoking) you can sometimes force them out of it by quickly
       +unsheathing/sheathing your weapon.</p>
       +</li>
       +<li><p>When in the Old Camp: Baal Parvez can take you to the Sect Camp, he can be
       +found near the campfire near Fisk and Dexter.
       +Mordrag can take you to the New Camp, he can be found near the south gate,
       +slightly after the campfire near Baal Parvez.</p>
       +<p>When you follow them and when they kill monsters then you also get the
       +experience.</p>
       +</li>
       +<li><p>The NPC Wolf in the New Camp sells "The Bloodflies" book for 150 ore. When
       +you read this book you learn how to remove bloodflies parts (without having to
       +spend learning points). After you read the book and learned its skill then you
       +can sell the book back for 75 ore. This investment quickly pays back: Per
       +bloodfly: sting: 25 ore (unsold value), 2x wings (15 ore each unsold value).</p>
       +</li>
       +<li><p>The templar Gor Na Drak (usually near the old mine and walks around with
       +another templar): talking to him teaches you how to learn to get secretion from
       +minecrawlers for free.</p>
       +</li>
       +<li><p>The spell scroll "Transform into bloodfly" is very useful:
       +<ul>
       +<li>A bloodfly is very fast.</li>
       +<li>Can also fly over water.</li>
       +<li>The scroll costs 100 ore. Its the same price as a potion of speed, but it
       +has no duration (just until you transform back).</li>
       +<li>You have no fall damage.</li>
       +<li>You can climb some steep mountains this way.</li>
       +<li>Some monsters won't attack you, but some NPCs will attack you.</li>
       +<li>Your attribute stats will temporary change.</li>
       +<li>It requires 10 mana to cast (low requirement).</li>
       +</ul>
       +</p>
       +</li>
       +<li><p>Almost all mummies that are lootable in the game (Orc temple and The Sleeper
       +temple) have really good loot: permanent and regular potions and amulets and
       +rings.<br />  </p>
       +</li>
       +<li><p>Skill investments:
       +<ul>
       +<li>For melee skills:
       +<ul>
       +<li>Strength</li>
       +<li>One-handed weapons have a bit lower weapon damage but are less clunky and
       +faster. You can also interrupt enemy attacks.</li>
       +<li>Two-handed weapons have the highest damage, but are slower.</li>
       +<li>Get at least the first tier of one-handed training. It will change the
       +combat animations and make combat less slow and clunky.</li>
       +</ul>
       +</li>
       +<li>For ranged skills:
       +<ul>
       +<li>Dexterity</li>
       +<li>Cross-bows have high damage and are very good.
       +<ul>
       +<li>Cross-bow: the path for cross-bow training is easier in the old camp.
       +When you become the Old Camp guard Scorpio can train you. Later in the
       +game in chapter 4 after some story progression he will train everyone.</li>
       +</ul>
       +</li>
       +</ul>
       +</li>
       +<li>For mage characters:
       +<ul>
       +<li>Investing a little bit into strength, lets say 30 STR is OK.</li>
       +<li>Magic skills are powerful but are a bit clunky and slow.</li>
       +<li>Joining the Old Camp (fire mage) or New Camp (water mage) for the magician
       +path is probably easier.</li>
       +</ul>
       +</li>
       +<li>Harvest animals:
       +<ul>
       +<li>Early investments of a few skill points into getting skins, teeth and claws
       +from animals is OK (it is easy to get a lot of ore if you loot everything
       +though).</li>
       +</ul>
       +</li>
       +<li>Lockpicking: training in lockpicking only reduces the chance to break locks
       +when you fail the combination. Investing in it is OK but not necessary.
       +A small cheat: the lock pick combination stays the same, you can save and
       +reload the game to avoid losing lockpicks.</li>
       +<li>Bad skill investments to avoid:
       +<ul>
       +<li>Sneak and pickpocket are nearly useless.</li>
       +</ul>
       +</li>
       +</ul>
       +</p>
       +</li>
       +</ul>
       +<p>Overall recommendation: I'd recommend a hybrid of melee/magic or melee/range.
       +Early game for melee: get max strength to 100 and get at least the first tier
       +of one-handed training.<br />  
       +In the later game focus more on ranged combat or learning the magic circles.</p>
       +<h1>Side-quest Chromanin / The Stranger</h1>
       +<p>This describes an interesting side quest in the Gothic 1 game, which is not too
       +obvious to find and may be overlooked.</p>
       +<p>The first Chromanin book is found by defeating the skeleton mage in the Fog
       +Tower. On its bones you can find the Chromanin book. Reading the book starts
       +the Chromanin / The Stranger quest.  The books contain some typos, being
       +demonicly possesed could be an excuse for that :)</p>
       +<p>Note that the Old books only spawn in a specific order after reading each found
       +book.  So they have to be done in this specific order.</p>
       +<p><a href="downloads/gothic1/chromanin/0_mage_fog_tower.png">Fog tower mage</a><br />  
       +<a href="downloads/gothic1/chromanin/0_fog_tower.png">Location</a><br />  
       +<a href="downloads/gothic1/chromanin/0_map.png">Map</a><br />  </p>
       +<p>Text:</p>
       +<pre><code>"He who is willing to
       +renounce all depravity
       +and wanders on the path
       +of righteousness, shall
       +know where the source
       +of my power lies
       +hidden. So that he might
       +use it to break the chains
       +of this world and prove
       +worthy to receive Chromanin."
       +
       +"The Wise One sees to
       +having a general overview before he
       +dedicates himself to his
       +next mission."
       +</code></pre>
       +<h2>Chromanin</h2>
       +<p>The clue is in the words "general overview" on the second page.
       +One of the highest points on the map is the tower where you find and free the orc Ur-Shak
       +from being attacked by other orcs.</p>
       +<p>The Wise One sees to having a general overview before he dedicates himself to
       +his next mission".<br />  
       +Location: on top of the tower near where the orc Ur-Shak was.<br />  
       +Item: Old Book.</p>
       +<p><a href="downloads/gothic1/chromanin/1_book.png">Item</a><br />  
       +<a href="downloads/gothic1/chromanin/1_tower.png">Location</a><br />  
       +<a href="downloads/gothic1/chromanin/1_map.png">Map</a><br />  </p>
       +<h2>Chromanin 2</h2>
       +<p>Text:</p>
       +<pre><code>"Carried from the tides
       +of time, Chromanin's
       +visions have opened my
       +eyes. No price could be
       +high enough to ever
       +renounce my faith in
       +them, for it touched my
       +heart too insensely."
       +
       +"What is devided will be
       +reunited, after being
       +massively separated for
       +a short time."
       +</code></pre>
       +<p>Clue: "What is devided (sic) will be reunited, after being massively separated for a short time".
       +Location: small island near the (divided) river near the Old Camp.</p>
       +<p><a href="downloads/gothic1/chromanin/2_book.png">Item</a><br />  
       +<a href="downloads/gothic1/chromanin/2_river.png">Location</a><br />  
       +<a href="downloads/gothic1/chromanin/2_map.png">Map</a><br />  </p>
       +<h2>Chromanin 3</h2>
       +<p>Text:</p>
       +<pre><code>"Oh, Ancient Gods. How
       +can it be that a man like
       +me, simple and unworthy,
       +may receive such great a
       +legacy. I feel great
       +fear to lose all of it
       +again by a slight
       +faltering in word or
       +deed."
       +
       +"The wise fisherman
       +occasionally tries to get
       +lucky on the other side
       +of the lake."
       +</code></pre>
       +<p>Clue: a fisherman lake and (partially sunken hut) can be found close the the entrance of the New Camp.
       +At the other side is the Old Book.</p>
       +<p><a href="downloads/gothic1/chromanin/3_book.png">Item</a><br />  
       +<a href="downloads/gothic1/chromanin/3_lake_new_camp.png">Location</a><br />  
       +<a href="downloads/gothic1/chromanin/3_map.png">Map</a><br />  </p>
       +<h2>Chromanin 4</h2>
       +<p>Text:</p>
       +<pre><code>"I dare not to be in
       +the presence of
       +Chromanin one day. Gone
       +are the days of wasting
       +and wailing. So easy it
       +will be to acheive
       +absolute perfection. I'm
       +not far from it!"
       +
       +"Long forgotten are the
       +deeds of those who once
       +were aboard."
       +</code></pre>
       +<p>Clue: "Long forgotten are the deeds of those who once were aboard."
       +A broken ship can be found near the beach at the entrance of the Fog Tower.</p>
       +<p><a href="downloads/gothic1/chromanin/4_book.png">Item</a><br />  
       +<a href="downloads/gothic1/chromanin/4_aboard.png">Location</a><br />  
       +<a href="downloads/gothic1/chromanin/4_map.png">Map</a><br />  </p>
       +<h2>Chromanin 5</h2>
       +<p>Text:</p>
       +<pre><code>"But I shall not walk this
       +path alone. This honor is
       +mine. I must accept to
       +share the power within
       +myself with the worthy
       +ones who are to come and
       +find me. I hope they're
       +coming soon..."
       +
       +"You will find me where it all began."
       +</code></pre>
       +<p>Clue: "You will find me where it all began."
       +Very obvious it is the same location as were the first book was found.</p>
       +<p><a href="downloads/gothic1/chromanin/5_book.png">Item</a><br />  
       +<a href="downloads/gothic1/chromanin/5_begin.png">Location</a><br />  
       +<a href="downloads/gothic1/chromanin/5_map.png">Map</a><br />  </p>
       +<h2>Chromanin 6</h2>
       +<p>Text:</p>
       +<pre><code>"Empty pages"
       +</code></pre>
       +<p><a href="downloads/gothic1/chromanin/6_book.png">Item</a></p>
       +<p>On the corpse is the last chromanin book.
       +When reading this last book the book is empty.
       +Then there is evil laugh and 2 skeleton mages and skeleton minions will spawn.</p>
       +<h2>Chromanin quest log</h2>
       +<p>Here are the texts in the quest log:</p>
       +<p><a href="downloads/gothic1/chromanin/quest_log_part_1.png">Quest log part 1</a><br />  
       +<a href="downloads/gothic1/chromanin/quest_log_part_2.png">Quest log part 2</a><br />  </p>
       +<h3>The End</h3>
       +<p>When you use the tips described above Gothic should be an easier game and you
       +should be able to get at a high(er) level with lots of mana/strength/hp.</p>
       +<p>Have fun!</p>
       +]]></content>
       +</entry>
       +</feed>
   DIR diff --git a/output/atom_gopher.xml b/output/atom_gopher.xml
       @@ -0,0 +1,429 @@
       +<?xml version="1.0" encoding="UTF-8"?>
       +<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
       +        <title>Codemadness</title>
       +        <subtitle>blog with various projects and articles about computer-related things</subtitle>
       +        <updated>2025-05-16T00:00:00Z</updated>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org" />
       +        <id>gopher://codemadness.org/0/atom_gopher.xml</id>
       +        <link rel="self" type="application/atom+xml" href="gopher://codemadness.org/0/atom_gopher.xml" />
       +<entry>
       +        <title>Chess puzzle book generator</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/chess-puzzles" />
       +        <id>gopher://codemadness.org/1/phlog/chess-puzzles</id>
       +        <updated>2025-05-03T00:00:00Z</updated>
       +        <published>2024-02-02T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Chess puzzle book generator</summary>
       +</entry>
       +<entry>
       +        <title>xargs: an example for parallel batch jobs</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/xargs" />
       +        <id>gopher://codemadness.org/1/phlog/xargs</id>
       +        <updated>2023-12-17T00:00:00Z</updated>
       +        <published>2023-11-22T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>xargs: an example for parallel batch jobs</summary>
       +</entry>
       +<entry>
       +        <title>Improved Youtube RSS/Atom feed</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/youtube-feed" />
       +        <id>gopher://codemadness.org/1/phlog/youtube-feed</id>
       +        <updated>2023-11-20T00:00:00Z</updated>
       +        <published>2023-11-20T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Improved Youtube Atom feed by adding video duration and filtering away shorts</summary>
       +</entry>
       +<entry>
       +        <title>webdump HTML to plain-text converter</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/webdump" />
       +        <id>gopher://codemadness.org/1/phlog/webdump</id>
       +        <updated>2025-04-25T00:00:00Z</updated>
       +        <published>2023-11-20T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>webdump HTML to plain-text converter</summary>
       +</entry>
       +<entry>
       +        <title>Setup your own mail paste service</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/mailservice" />
       +        <id>gopher://codemadness.org/1/phlog/mailservice</id>
       +        <updated>2024-02-10T00:00:00Z</updated>
       +        <published>2023-10-25T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Setup your own mail paste service using mblaze</summary>
       +</entry>
       +<entry>
       +        <title>A simple TODO application</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/todo" />
       +        <id>gopher://codemadness.org/1/phlog/todo</id>
       +        <updated>2022-07-01T00:00:00Z</updated>
       +        <published>2022-07-01T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>A simple TODO application workflow</summary>
       +</entry>
       +<entry>
       +        <title>2FA TOTP without crappy authenticator apps</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/totp" />
       +        <id>gopher://codemadness.org/1/phlog/totp</id>
       +        <updated>2022-10-29T00:00:00Z</updated>
       +        <published>2022-03-23T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Using 2FA TOTP without crappy authenticator apps</summary>
       +</entry>
       +<entry>
       +        <title>Setup an OpenBSD RISCV64 VM in QEMU</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/openbsd-riscv64-vm" />
       +        <id>gopher://codemadness.org/1/phlog/openbsd-riscv64-vm</id>
       +        <updated>2021-10-26T00:00:00Z</updated>
       +        <published>2021-10-23T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Setup an OpenBSD RISCV-64 VM in QEMU</summary>
       +</entry>
       +<entry>
       +        <title>Sfeed_curses: a curses UI front-end for sfeed</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/sfeed_curses" />
       +        <id>gopher://codemadness.org/1/phlog/sfeed_curses</id>
       +        <updated>2022-05-08T00:00:00Z</updated>
       +        <published>2020-06-25T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Sfeed_curses is a curses UI front-end for the sfeed RSS/Atom parser</summary>
       +</entry>
       +<entry>
       +        <title>hurl: HTTP, HTTPS and Gopher file grabber</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/hurl" />
       +        <id>gopher://codemadness.org/1/phlog/hurl</id>
       +        <updated>2020-07-20T00:00:00Z</updated>
       +        <published>2019-11-10T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>hurl: HTTP, HTTPS and Gopher file grabber</summary>
       +</entry>
       +<entry>
       +        <title>json2tsv: a JSON to TSV converter</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/json2tsv" />
       +        <id>gopher://codemadness.org/1/phlog/json2tsv</id>
       +        <updated>2021-09-25T00:00:00Z</updated>
       +        <published>2019-10-13T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>json2tsv: a JSON to TAB-Separated Value converter</summary>
       +</entry>
       +<entry>
       +        <title>OpenBSD: setup a local auto-installation server</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/openbsd-autoinstall" />
       +        <id>gopher://codemadness.org/1/phlog/openbsd-autoinstall</id>
       +        <updated>2020-04-30T00:00:00Z</updated>
       +        <published>2019-04-24T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>OpenBSD: setup a local auto-installation server</summary>
       +</entry>
       +<entry>
       +        <title>Idiotbox: Youtube interface</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/idiotbox" />
       +        <id>gopher://codemadness.org/1/phlog/idiotbox</id>
       +        <updated>2021-12-25T00:00:00Z</updated>
       +        <published>2019-02-10T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Idiotbox: Youtube interface</summary>
       +</entry>
       +<entry>
       +        <title>Gopher HTTP proxy</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/gopher-proxy" />
       +        <id>gopher://codemadness.org/1/phlog/gopher-proxy</id>
       +        <updated>2020-08-30T00:00:00Z</updated>
       +        <published>2018-08-17T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Gopher HTTP proxy</summary>
       +</entry>
       +<entry>
       +        <title>Setup your own file paste service</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/paste-service" />
       +        <id>gopher://codemadness.org/1/phlog/paste-service</id>
       +        <updated>2018-03-10T00:00:00Z</updated>
       +        <published>2018-03-10T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Howto setup your own secure file paste service</summary>
       +</entry>
       +<entry>
       +        <title>Setup your own git hosting service</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/setup-git-hosting" />
       +        <id>gopher://codemadness.org/1/phlog/setup-git-hosting</id>
       +        <updated>2022-08-07T00:00:00Z</updated>
       +        <published>2018-02-25T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Howto setup your own git hosting service</summary>
       +</entry>
       +<entry>
       +        <title>Setup an OpenBSD SPARC64 VM in QEMU</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/openbsd-sparc64-vm" />
       +        <id>gopher://codemadness.org/1/phlog/openbsd-sparc64-vm</id>
       +        <updated>2020-04-18T00:00:00Z</updated>
       +        <published>2017-12-11T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Setup an OpenBSD SPARC64 VM in QEMU</summary>
       +</entry>
       +<entry>
       +        <title>Tscrape: a Twitter scraper</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/tscrape" />
       +        <id>gopher://codemadness.org/1/phlog/tscrape</id>
       +        <updated>2020-07-20T00:00:00Z</updated>
       +        <published>2017-09-24T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Tscrape: a Twitter scraper</summary>
       +</entry>
       +<entry>
       +        <title>jsdatatable: a small datatable Javascript</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/datatable" />
       +        <id>gopher://codemadness.org/1/phlog/datatable</id>
       +        <updated>2020-07-20T00:00:00Z</updated>
       +        <published>2017-09-24T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>jsdatatable: a small datatable Javascript</summary>
       +</entry>
       +<entry>
       +        <title>Stagit-gopher: a static git page generator for gopher</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/stagit-gopher" />
       +        <id>gopher://codemadness.org/1/phlog/stagit-gopher</id>
       +        <updated>2021-04-11T00:00:00Z</updated>
       +        <published>2017-08-04T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>a static git page generator for gopher</summary>
       +</entry>
       +<entry>
       +        <title>Saait: a boring HTML page generator</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/saait" />
       +        <id>gopher://codemadness.org/1/phlog/saait</id>
       +        <updated>2020-07-20T00:00:00Z</updated>
       +        <published>2017-06-10T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Saait: a boring HTML page generator</summary>
       +</entry>
       +<entry>
       +        <title>Stagit: a static git page generator</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/stagit" />
       +        <id>gopher://codemadness.org/1/phlog/stagit</id>
       +        <updated>2021-04-11T00:00:00Z</updated>
       +        <published>2017-05-10T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>a static git page generator</summary>
       +</entry>
       +<entry>
       +        <title>OpenBSD httpd, slowcgi and cgit</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/openbsd-httpd-and-cgit" />
       +        <id>gopher://codemadness.org/1/phlog/openbsd-httpd-and-cgit</id>
       +        <updated>2021-04-11T00:00:00Z</updated>
       +        <published>2015-07-05T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>OpenBSD httpd, slowcgi and cgit</summary>
       +</entry>
       +<entry>
       +        <title>twitch: application to watch Twitch streams</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/twitch-interface" />
       +        <id>gopher://codemadness.org/1/phlog/twitch-interface</id>
       +        <updated>2020-12-14T00:00:00Z</updated>
       +        <published>2014-11-23T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>twitch: application to watch Twitch streams</summary>
       +</entry>
       +<entry>
       +        <title>Userscript: focus input field</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/userscript-focus-input-field" />
       +        <id>gopher://codemadness.org/1/phlog/userscript-focus-input-field</id>
       +        <updated>2014-03-02T00:00:00Z</updated>
       +        <published>2014-03-02T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Userscript to focus the first input field on a page with a hotkey</summary>
       +</entry>
       +<entry>
       +        <title>Userscript: Youtube circumvent age verification</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/userscript-youtube-circumvent-age-verification" />
       +        <id>gopher://codemadness.org/1/phlog/userscript-youtube-circumvent-age-verification</id>
       +        <updated>2020-12-27T00:00:00Z</updated>
       +        <published>2013-02-21T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Userscript to circumvent Youtube age verification and redirect to the video</summary>
       +</entry>
       +<entry>
       +        <title>Userscript: block stupid fonts</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/userscript-block-stupid-fonts" />
       +        <id>gopher://codemadness.org/1/phlog/userscript-block-stupid-fonts</id>
       +        <updated>2020-03-10T00:00:00Z</updated>
       +        <published>2012-10-21T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Userscript to whitelist your favorite fonts and block the rest</summary>
       +</entry>
       +<entry>
       +        <title>Sfeed: simple RSS and Atom parser</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/sfeed" />
       +        <id>gopher://codemadness.org/1/phlog/sfeed</id>
       +        <updated>2022-11-05T00:00:00Z</updated>
       +        <published>2011-04-01T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Sfeed is a simple RSS and Atom parser (and format programs to add reader functionality)</summary>
       +</entry>
       +<entry>
       +        <title>Vim theme: relaxed</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/vim-theme-relaxed" />
       +        <id>gopher://codemadness.org/1/phlog/vim-theme-relaxed</id>
       +        <updated>2011-01-07T00:00:00Z</updated>
       +        <published>2011-01-07T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>a dark VIM theme I made and use on a daily basis</summary>
       +</entry>
       +<entry>
       +        <title>Seturgent: set urgency hints for X applications</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/seturgent" />
       +        <id>gopher://codemadness.org/1/phlog/seturgent</id>
       +        <updated>2020-07-20T00:00:00Z</updated>
       +        <published>2010-10-31T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Seturgent is a small utility to set an application it&#39;s urgency hint</summary>
       +</entry>
       +<entry>
       +        <title>DWM-hiltjo: my windowmanager configuration</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/dwm" />
       +        <id>gopher://codemadness.org/1/phlog/dwm</id>
       +        <updated>2020-07-20T00:00:00Z</updated>
       +        <published>2010-08-12T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>My DWM configuration; a few added features to suit my needs</summary>
       +</entry>
       +<entry>
       +        <title>Query unused CSS rules on current document state</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/query-unused-css-rules-on-current-document-state" />
       +        <id>gopher://codemadness.org/1/phlog/query-unused-css-rules-on-current-document-state</id>
       +        <updated>2010-04-21T00:00:00Z</updated>
       +        <published>2010-04-21T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>How to see all the rules in a stylesheet (CSS) that are not used for the current document</summary>
       +</entry>
       +<entry>
       +        <title>Driconf: enabling S3 texture compression on Linux</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/driconf" />
       +        <id>gopher://codemadness.org/1/phlog/driconf</id>
       +        <updated>2020-08-21T00:00:00Z</updated>
       +        <published>2009-07-05T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>driconf: enabling S3 texture compression</summary>
       +</entry>
       +<entry>
       +        <title>Getting the USB-powerline bridge to work on Linux</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/getting-the-usb-powerline-bridge-to-work-on-linux" />
       +        <id>gopher://codemadness.org/1/phlog/getting-the-usb-powerline-bridge-to-work-on-linux</id>
       +        <updated>2019-12-06T00:00:00Z</updated>
       +        <published>2009-04-13T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>A guide to get a USB-powerline bridge with the Intellon 51x1 chipset working on Linux</summary>
       +</entry>
       +<entry>
       +        <title>Gothic 1 game guide</title>
       +        <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/gothic" />
       +        <id>gopher://codemadness.org/1/phlog/gothic</id>
       +        <updated>2025-01-05T00:00:00Z</updated>
       +        <published>2009-04-12T00:00:00Z</published>
       +        <author>
       +                <name>Hiltjo</name>
       +                <uri>gopher://codemadness.org</uri>
       +        </author>
       +        <summary>Gothic 1 game guide with some useful tips</summary>
       +</entry>
       +</feed>
   DIR diff --git a/output/jsonfeed.json b/output/jsonfeed.json
       @@ -0,0 +1,285 @@
       +{
       +"version": "https://jsonfeed.org/version/1.1",
       +"title": "Newsfeed",
       +"items": [
       +{
       +        "id": "https://www.codemadness.org/chess-puzzles.html",
       +        "date_published": "2024-02-02T00:00:00Z",
       +        "title": "Chess puzzle book generator",
       +        "url": "https://www.codemadness.org/chess-puzzles.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "Chess puzzle book generator"
       +},
       +{
       +        "id": "https://www.codemadness.org/xargs.html",
       +        "date_published": "2023-11-22T00:00:00Z",
       +        "title": "xargs: an example for parallel batch jobs",
       +        "url": "https://www.codemadness.org/xargs.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "xargs: an example for parallel batch jobs"
       +},
       +{
       +        "id": "https://www.codemadness.org/youtube-feed.html",
       +        "date_published": "2023-11-20T00:00:00Z",
       +        "title": "Improved Youtube RSS/Atom feed",
       +        "url": "https://www.codemadness.org/youtube-feed.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "Improved Youtube Atom feed by adding video duration and filtering away shorts"
       +},
       +{
       +        "id": "https://www.codemadness.org/webdump.html",
       +        "date_published": "2023-11-20T00:00:00Z",
       +        "title": "webdump HTML to plain-text converter",
       +        "url": "https://www.codemadness.org/webdump.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "webdump HTML to plain-text converter"
       +},
       +{
       +        "id": "https://www.codemadness.org/mailservice.html",
       +        "date_published": "2023-10-25T00:00:00Z",
       +        "title": "Setup your own mail paste service",
       +        "url": "https://www.codemadness.org/mailservice.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "Setup your own mail paste service using mblaze"
       +},
       +{
       +        "id": "https://www.codemadness.org/todo-application.html",
       +        "date_published": "2022-07-01T00:00:00Z",
       +        "title": "A simple TODO application",
       +        "url": "https://www.codemadness.org/todo-application.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "A simple TODO application workflow"
       +},
       +{
       +        "id": "https://www.codemadness.org/totp.html",
       +        "date_published": "2022-03-23T00:00:00Z",
       +        "title": "2FA TOTP without crappy authenticator apps",
       +        "url": "https://www.codemadness.org/totp.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "Using 2FA TOTP without crappy authenticator apps"
       +},
       +{
       +        "id": "https://www.codemadness.org/openbsd-riscv64-vm.html",
       +        "date_published": "2021-10-23T00:00:00Z",
       +        "title": "Setup an OpenBSD RISCV64 VM in QEMU",
       +        "url": "https://www.codemadness.org/openbsd-riscv64-vm.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "Setup an OpenBSD RISCV-64 VM in QEMU"
       +},
       +{
       +        "id": "https://www.codemadness.org/sfeed_curses-ui.html",
       +        "date_published": "2020-06-25T00:00:00Z",
       +        "title": "Sfeed_curses: a curses UI front-end for sfeed",
       +        "url": "https://www.codemadness.org/sfeed_curses-ui.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "Sfeed_curses is a curses UI front-end for the sfeed RSS/Atom parser"
       +},
       +{
       +        "id": "https://www.codemadness.org/hurl.html",
       +        "date_published": "2019-11-10T00:00:00Z",
       +        "title": "hurl: HTTP, HTTPS and Gopher file grabber",
       +        "url": "https://www.codemadness.org/hurl.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "hurl: HTTP, HTTPS and Gopher file grabber"
       +},
       +{
       +        "id": "https://www.codemadness.org/json2tsv.html",
       +        "date_published": "2019-10-13T00:00:00Z",
       +        "title": "json2tsv: a JSON to TSV converter",
       +        "url": "https://www.codemadness.org/json2tsv.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "json2tsv: a JSON to TAB-Separated Value converter"
       +},
       +{
       +        "id": "https://www.codemadness.org/openbsd-autoinstall.html",
       +        "date_published": "2019-04-24T00:00:00Z",
       +        "title": "OpenBSD: setup a local auto-installation server",
       +        "url": "https://www.codemadness.org/openbsd-autoinstall.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "OpenBSD: setup a local auto-installation server"
       +},
       +{
       +        "id": "https://www.codemadness.org/idiotbox.html",
       +        "date_published": "2019-02-10T00:00:00Z",
       +        "title": "Idiotbox: Youtube interface",
       +        "url": "https://www.codemadness.org/idiotbox.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "Idiotbox: Youtube interface"
       +},
       +{
       +        "id": "https://www.codemadness.org/gopher-proxy.html",
       +        "date_published": "2018-08-17T00:00:00Z",
       +        "title": "Gopher HTTP proxy",
       +        "url": "https://www.codemadness.org/gopher-proxy.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "Gopher HTTP proxy"
       +},
       +{
       +        "id": "https://www.codemadness.org/paste-service.html",
       +        "date_published": "2018-03-10T00:00:00Z",
       +        "title": "Setup your own file paste service",
       +        "url": "https://www.codemadness.org/paste-service.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "Howto setup your own secure file paste service"
       +},
       +{
       +        "id": "https://www.codemadness.org/setup-git-hosting.html",
       +        "date_published": "2018-02-25T00:00:00Z",
       +        "title": "Setup your own git hosting service",
       +        "url": "https://www.codemadness.org/setup-git-hosting.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "Howto setup your own git hosting service"
       +},
       +{
       +        "id": "https://www.codemadness.org/openbsd-sparc64-vm.html",
       +        "date_published": "2017-12-11T00:00:00Z",
       +        "title": "Setup an OpenBSD SPARC64 VM in QEMU",
       +        "url": "https://www.codemadness.org/openbsd-sparc64-vm.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "Setup an OpenBSD SPARC64 VM in QEMU"
       +},
       +{
       +        "id": "https://www.codemadness.org/tscrape.html",
       +        "date_published": "2017-09-24T00:00:00Z",
       +        "title": "Tscrape: a Twitter scraper",
       +        "url": "https://www.codemadness.org/tscrape.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "Tscrape: a Twitter scraper"
       +},
       +{
       +        "id": "https://www.codemadness.org/datatable.html",
       +        "date_published": "2017-09-24T00:00:00Z",
       +        "title": "jsdatatable: a small datatable Javascript",
       +        "url": "https://www.codemadness.org/datatable.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "jsdatatable: a small datatable Javascript"
       +},
       +{
       +        "id": "https://www.codemadness.org/stagit-gopher.html",
       +        "date_published": "2017-08-04T00:00:00Z",
       +        "title": "Stagit-gopher: a static git page generator for gopher",
       +        "url": "https://www.codemadness.org/stagit-gopher.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "a static git page generator for gopher"
       +},
       +{
       +        "id": "https://www.codemadness.org/saait.html",
       +        "date_published": "2017-06-10T00:00:00Z",
       +        "title": "Saait: a boring HTML page generator",
       +        "url": "https://www.codemadness.org/saait.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "Saait: a boring HTML page generator"
       +},
       +{
       +        "id": "https://www.codemadness.org/stagit.html",
       +        "date_published": "2017-05-10T00:00:00Z",
       +        "title": "Stagit: a static git page generator",
       +        "url": "https://www.codemadness.org/stagit.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "a static git page generator"
       +},
       +{
       +        "id": "https://www.codemadness.org/openbsd-httpd-and-cgit.html",
       +        "date_published": "2015-07-05T00:00:00Z",
       +        "title": "OpenBSD httpd, slowcgi and cgit",
       +        "url": "https://www.codemadness.org/openbsd-httpd-and-cgit.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "OpenBSD httpd, slowcgi and cgit"
       +},
       +{
       +        "id": "https://www.codemadness.org/twitch-interface.html",
       +        "date_published": "2014-11-23T00:00:00Z",
       +        "title": "twitch: application to watch Twitch streams",
       +        "url": "https://www.codemadness.org/twitch-interface.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "twitch: application to watch Twitch streams"
       +},
       +{
       +        "id": "https://www.codemadness.org/userscript-focus-input-field.html",
       +        "date_published": "2014-03-02T00:00:00Z",
       +        "title": "Userscript: focus input field",
       +        "url": "https://www.codemadness.org/userscript-focus-input-field.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "Userscript to focus the first input field on a page with a hotkey"
       +},
       +{
       +        "id": "https://www.codemadness.org/userscript-youtube-circumvent-age-verification.html",
       +        "date_published": "2013-02-21T00:00:00Z",
       +        "title": "Userscript: Youtube circumvent age verification",
       +        "url": "https://www.codemadness.org/userscript-youtube-circumvent-age-verification.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "Userscript to circumvent Youtube age verification and redirect to the video"
       +},
       +{
       +        "id": "https://www.codemadness.org/userscript-block-stupid-fonts.html",
       +        "date_published": "2012-10-21T00:00:00Z",
       +        "title": "Userscript: block stupid fonts",
       +        "url": "https://www.codemadness.org/userscript-block-stupid-fonts.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "Userscript to whitelist your favorite fonts and block the rest"
       +},
       +{
       +        "id": "https://www.codemadness.org/sfeed-simple-feed-parser.html",
       +        "date_published": "2011-04-01T00:00:00Z",
       +        "title": "Sfeed: simple RSS and Atom parser",
       +        "url": "https://www.codemadness.org/sfeed-simple-feed-parser.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "Sfeed is a simple RSS and Atom parser (and format programs to add reader functionality)"
       +},
       +{
       +        "id": "https://www.codemadness.org/vim-theme-relaxed.html",
       +        "date_published": "2011-01-07T00:00:00Z",
       +        "title": "Vim theme: relaxed",
       +        "url": "https://www.codemadness.org/vim-theme-relaxed.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "a dark VIM theme I made and use on a daily basis"
       +},
       +{
       +        "id": "https://www.codemadness.org/seturgent-set-urgency-hints-for-x-applications.html",
       +        "date_published": "2010-10-31T00:00:00Z",
       +        "title": "Seturgent: set urgency hints for X applications",
       +        "url": "https://www.codemadness.org/seturgent-set-urgency-hints-for-x-applications.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "Seturgent is a small utility to set an application it's urgency hint"
       +},
       +{
       +        "id": "https://www.codemadness.org/dwm-hiltjo-my-windowmanager-configuration.html",
       +        "date_published": "2010-08-12T00:00:00Z",
       +        "title": "DWM-hiltjo: my windowmanager configuration",
       +        "url": "https://www.codemadness.org/dwm-hiltjo-my-windowmanager-configuration.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "My DWM configuration; a few added features to suit my needs"
       +},
       +{
       +        "id": "https://www.codemadness.org/query-unused-css-rules-on-current-document-state.html",
       +        "date_published": "2010-04-21T00:00:00Z",
       +        "title": "Query unused CSS rules on current document state",
       +        "url": "https://www.codemadness.org/query-unused-css-rules-on-current-document-state.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "How to see all the rules in a stylesheet (CSS) that are not used for the current document"
       +},
       +{
       +        "id": "https://www.codemadness.org/driconf-enabling-s3-texture-compression-on-linux.html",
       +        "date_published": "2009-07-05T00:00:00Z",
       +        "title": "Driconf: enabling S3 texture compression on Linux",
       +        "url": "https://www.codemadness.org/driconf-enabling-s3-texture-compression-on-linux.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "driconf: enabling S3 texture compression"
       +},
       +{
       +        "id": "https://www.codemadness.org/getting-the-usb-powerline-bridge-to-work-on-linux.html",
       +        "date_published": "2009-04-13T00:00:00Z",
       +        "title": "Getting the USB-powerline bridge to work on Linux",
       +        "url": "https://www.codemadness.org/getting-the-usb-powerline-bridge-to-work-on-linux.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "A guide to get a USB-powerline bridge with the Intellon 51x1 chipset working on Linux"
       +},
       +{
       +        "id": "https://www.codemadness.org/gothic-1-guide.html",
       +        "date_published": "2009-04-12T00:00:00Z",
       +        "title": "Gothic 1 game guide",
       +        "url": "https://www.codemadness.org/gothic-1-guide.html",
       +        "authors": [{"name": "Hiltjo"}],
       +        "content_text": "Gothic 1 game guide with some useful tips"
       +}]
       +}
   DIR diff --git a/output/jsonfeed_content.json b/output/jsonfeed_content.json
       @@ -0,0 +1,285 @@
       +{
       +"version": "https://jsonfeed.org/version/1.1",
       +"title": "Newsfeed",
       +"items": [
       +{
       +        "id": "https://www.codemadness.org/chess-puzzles.html",
       +        "date_published": "2024-02-02T00:00:00Z",
       +        "title": "Chess puzzle book generator",
       +        "url": "https://www.codemadness.org/chess-puzzles.html",
       +        "authors": [{"name": "Hiltjo"}],
codemadness.org:70 /git/www.codemadness.org/commit/aa794d3b1909cb60c010583a2da9a3582cdc6b16.gph:4651: line too long