add https://domm.plix.at/index.xml - sfeed_tests - sfeed tests and RSS and Atom files
(HTM) git clone git://git.codemadness.org/sfeed_tests
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
(DIR) commit 8bcd79044038d3ae53ec3c3483a45149197a2095
(DIR) parent 867de918bd38122d0410482621516f1ce442cd1e
(HTM) Author: Hiltjo Posthuma <hiltjo@codemadness.org>
Date: Fri, 10 Oct 2025 14:20:03 +0200
add https://domm.plix.at/index.xml
generator: XML::Atom::SimpleFeed
Diffstat:
A input/sfeed/realworld/domm.plix.at… | 367 ++++++++++++++++++++++++++++++
1 file changed, 367 insertions(+), 0 deletions(-)
---
(DIR) diff --git a/input/sfeed/realworld/domm.plix.at.atom.xml b/input/sfeed/realworld/domm.plix.at.atom.xml
@@ -0,0 +1,366 @@
+<?xml version="1.0" encoding="us-ascii"?>
+<feed xmlns="http://www.w3.org/2005/Atom"><title>domm.plix.at</title><author><name>Thomas Klausner</name></author><link href="https://domm.plix.at/index.xml" rel="self"/><id>https://domm.plix.at/index.xml</id><updated>2025-10-06T15:00:00+00:00</updated><generator uri="https://metacpan.org/pod/XML::Atom::SimpleFeed" version="0.905">XML::Atom::SimpleFeed</generator><entry><title>I brain coded a static image gallery in a few hours: snig.pl</title><link href="https://domm.plix.at/perl/2025_10_braincoded_static_image_gallery.html"/><id>https://domm.plix.at/perl/2025_10_braincoded_static_image_gallery.html</id><updated>2025-10-06T15:00:00+00:00</updated><category term="perl"/><summary>For quite some I wanted to write a small static image gallery so I can share my pictures with friends and family. Of course there are a gazillion tools like this, but, well, sometimes I just want to ...</summary><content type="html"><p>For quite some I wanted to write a small static image gallery so I can share my pictures with friends and family. Of course there are a gazillion tools like this, but, well, sometimes I just want to roll my own.</p>
+
+<p>I took the opportunity during our <a href="/reisen/2025_schwaz.html">stay in Schwaz</a> to take a few hours and hack together <a href="https://snig.plix.at/">snig</a>, the (small | static | simple | stupid | ...) image gallery. <a href="https://snig.plix.at/pub/202509_a_few_days_in_tyrol/">Here</a> you can see the example gallery (showing some of the pictures I took in Schwaz).</p>
+
+<p>I used the old, well tested technique I call <b>brain coding</b><sup class="footnote"><a href="#fn0">0</a></sup>, where you start with an empty vim buffer and type some code (Perl, <span class="caps">HTML, CSS</span>) until you're happy with the result. It helps to think a bit (aka use your brain) during this process.</p>
+
+<p>According to my <a href="http://timetracker.plix.at/">timetracker</a> I spend 8h 15min (probably half of it spend fiddling with <span class="caps">CSS...</span>).</p>
+
+<h3>Installation</h3>
+
+<p>I used the new <a href="https://perldoc.perl.org/perlclass">Perl class</a> feature, so you'll need at least Perl 5.40 which was released last year and is included in current Debian.</p>
+
+<p>I prefer <a href="https://metacpan.org/pod/App::cpm">cpm</a> to install <span class="caps">CPAN </span>modules:</p>
+
+<pre><code>cpm install -g Snig</code></pre>
+
+<p>I haven't provided a <code>Containerfile</code> yet, but if somebody is interested, drop me a line.</p>
+
+<p>You can get the raw source code from <a href="https://git.sr.ht/~domm/snig">Source Hut</a> (as I don't want to support the big <span class="caps">LLM </span>vacuum machines formerly known as Git(Hub|Lab)).</p>
+
+<h3>Example usage</h3>
+
+<p>You need a folder filled with images (eg <code>some-pictures</code>) and some place where you can host a folder and a bunch of <span class="caps">HTML </span>files.</p>
+
+<pre><code>ls -la some-pictures/
+7156 -rw------- 1 domm domm 7322112 Oct 6 09:14 P1370198.JPG
+7188 -rw------- 1 domm domm 7354880 Oct 6 09:14 P1370208.JPG
+7204 -rw------- 1 domm domm 7369728 Oct 6 09:14 P1370257.JPG</code></pre>
+
+<p>Then you do</p>
+
+<pre><code>snig.pl --input some-pictures --output /var/web/my-server-net/gallery/2025-10-some-pictures --name &quot;Some nice pictures&quot;</code></pre>
+
+<p>This will:</p>
+
+<ul>
+<li>find all <code>jpgs</code> in the folder <code>some-pictures</code></li>
+<li>copy them into the output folder</li>
+<li>generate a thumbnail (for use in the list)</li>
+<li>generate a preview (for use in the detail page)</li>
+<li>generate a <span class="caps">HTML </span>overview page</li>
+<li>generate a <span class="caps">HTML </span>detail page for each image, linked to the next/prev image</li>
+<li>generate a zip archive of the images</li>
+<li><span class="caps">EXIF </span>rotation hints are used to properly orient the previews</li>
+<li>images are sorted by the <span class="caps">EXIF </span>timestamp (per default, you could also use mtime)</li>
+</ul>
+
+<pre><code>ls -la /var/web/my-server-net/gallery/2025-10-some-pictures
+-rw-rw-r-- 1 domm domm 1370 Sep 26 21:15 20250914_p1370198.html
+-rw-rw-r-- 1 domm domm 1370 Sep 26 21:15 20250914_p1370208.html
+-rw-rw-r-- 1 domm domm 1370 Sep 26 21:15 20250915_p1370253.html
+-rw-rw-r-- 1 domm domm 171738640 Sep 26 21:12 2025-10-some-pictures.zip
+-rw-rw-r-- 1 domm domm 3977 Sep 26 21:15 index.html
+-rw-rw-r-- 1 domm domm 7322112 Sep 26 21:12 orig_20250914_P1370198.JPG
+-rw-rw-r-- 1 domm domm 7354880 Sep 26 21:12 orig_20250914_P1370208.JPG
+-rw-rw-r-- 1 domm domm 7686656 Sep 26 21:12 orig_20250915_P1370253.JPG
+-rw-rw-r-- 1 domm domm 106382 Sep 26 21:12 preview_20250914_P1370198.JPG
+-rw-rw-r-- 1 domm domm 170346 Sep 26 21:12 preview_20250914_P1370208.JPG
+-rw-rw-r-- 1 domm domm 133342 Sep 26 21:12 preview_20250915_P1370253.JPG
+-rw-rw-r-- 1 domm domm 1434 Sep 26 21:15 snig.css
+-rw-rw-r-- 1 domm domm 5128 Sep 26 21:12 thumbnail_20250914_P1370198.JPG
+-rw-rw-r-- 1 domm domm 9495 Sep 26 21:12 thumbnail_20250914_P1370208.JPG
+-rw-rw-r-- 1 domm domm 6408 Sep 26 21:12 thumbnail_20250915_P1370253.JPG</code></pre>
+
+<p>You can then take the output folder and rsync it onto a static file server. Or install <code>snig</code> on your web server and do the conversion there..</p>
+
+<p>Again, take a look at the <a href="https://snig.plix.at/pub/202509_a_few_days_in_tyrol/">example gallery</a>.</p>
+
+<h3>Limitations</h3>
+
+<p>For now, <code>snig</code> is very simple and stupid, so quite a few things are still hard coded (like copyright and license). If somebody else wants to use it, I will add some more command line flags and/or a config file. But for now this is not needed, so I did not add it.</p>
+
+<h3>Have fun</h3>
+
+<p>I had quite some fun hacking <code>snig</code> and also used it as an opportunity to learn the new <code>class</code> syntax (and fresh up on subroutine / method signatures). So not only did I use my brain, I actually learned something new!</p>
+
+<p>Please feel free to give <code>snig</code> a try. Or just use one of the countless other static image gallery generators. Or just write your own!</p>
+
+<p><a href="https://lobste.rs/s/bu1a84/i_brain_coded_static_image_gallery_few">lobste.rs</a>, <a href="https://news.ycombinator.com/item?id=45492242">hacker news</a>, <a href="https://www.reddit.com/r/perl/comments/1nzls1e/i_brain_coded_a_static_image_gallery_in_a_few/">reddit</a></p>
+
+<h3>Footnotes</h3>
+
+<p class="footnote" id="fn0"><sup>0</sup> As opposed to <a href="https://en.wikipedia.org/wiki/Vibe_coding">vibe coding</a>.</p></content><category term="Perl"/><category term="CPAN"/><category term="fun"/><category term="release"/><category term="open source"/><category term="~/bin"/></entry><entry><title>Bike trip from Schwaz to Wels</title><link href="https://domm.plix.at/reisen/2025_biketrip_schwaz_wels.html"/><id>https://domm.plix.at/reisen/2025_biketrip_schwaz_wels.html</id><updated>2025-09-27T15:46:00+02:00</updated><category term="reisen"/><summary type="html">After spending 1.5 weeks in Schwaz with the family looking after a friends cats and house, I cycled back (not the complete distance to Vienna..)
+
+Day 1: Schwaz - Waidring
+
+96.75km, 5:40, ⌀ 17.03, ...</summary><content type="html"><p>After spending 1.5 weeks in Schwaz with the family looking after a friends cats and house, I cycled back (not the complete distance to Vienna..)</p>
+
+<h3>Day 1: Schwaz - Waidring</h3>
+
+<p>96.75km, 5:40, ⌀ 17.03, from 11:00 - 17:30</p>
+
+<p>Along the Inn to Wörgel, then into the "Wilder Kaiser" area, very touristy. I once had to stop a bit because a movie shoot (well, actually for some <span class="caps">ORF</span> Krimi) needed a clean view of the small mountain road I was climbing. Stayed in a nice <span class="caps">B&amp;B </span>and had Blunzn for dinner.</p>
+
+<h3>Day 2: Waidring - Nußdorf / Attersse</h3>
+
+<p>112km, 5:56, ⌀ 18.86, from 9:45 to 17:30</p>
+
+<p>A bit more downhill today, through the very nice area around Lofer and Bad Reichenhall. Got some "fake" Mozartkugeln at a Reber factory shop right before the "border" back into Austria. Drachenwand impressive as always, as is riding through the bike tunnel along Mondsee. Did extra 10km because I could not find a place to sleep. All the <span class="caps">B&amp;B</span>s were "full", but I think they just did not want to prepare a single room for one night...</p>
+
+<h3>Attersse - Wels</h3>
+
+<p>72.74km, 3:44, ⌀ 19,41, from 10:00 to 14:15</p>
+
+<p>Mostly flat, except some detours along Attersee so the "slow" bikes don't block the main road running along the shore (I skipped a few, esp the one to "Berg im Attergau"...). Not so nice landscape between Attersee an Schwanstadt (lots of industry), but then some nice parts along Ager and Traun and through small villages. For some weird reason all three Kebab shops <b>and</b> the big Spar Supermarket next to Wels Railway Station are closed on Saturday, but I found some "Chinese" noodles.</p>
+
+<h3>Now sitting in the train back to Vienna.</h3>
+
+<p>A nice trip, though I feel my ass and my left knee.</p>
+
+<p>I think the next time I do some multi-day long distance cycling I will target ~80km / 4:30h stages, so I can spend some time hacking at fun projects (or work..) for a few hours after cycling.</p>
+
+<p>I had my <span class="caps">GPS </span>tracker set to 15sec interval, which proved to be quite inaccurate. For example for day 2 the <span class="caps">GPS </span>tracker recorded 104km, but my old-school simple wired bike computer 112km. Next time I'll either not record at all, or use a shorter interval (and hope that this won't kill the battery).</p>
+
+<p>Much of this route was actually the same as <a href="/reisen/2016_rad_wien_innsbruck.html">this route</a>, only in the other direction...</p></content><category term="bicycle"/></entry><entry><title>Cat and house sitting in Schwaz</title><link href="https://domm.plix.at/reisen/2025_schwaz.html"/><id>https://domm.plix.at/reisen/2025_schwaz.html</id><updated>2025-09-13T12:00:00+02:00</updated><category term="reisen"/><summary>A friend of a friend was doing a lot of long bike trips this year and was looking for people to sit their cats and house. We happily volunteered! We (me, BaHo, Older and Younger Son plus partner) ...</summary><content type="html"><p>A friend of a friend was doing a lot of long bike trips this year and was looking for people to sit their cats and house. We happily volunteered!</p>
+
+<p>We (me, BaHo, Older and Younger Son plus partner) spend a nice week there, with some hiking (Kellerjoch, Wolfsklamm, Achensee) and even more relaxing. And tending to the cats...</p>
+
+<p><a href="https://snig.plix.at/pub/202509_a_few_days_in_tyrol/">Here</a> are some more pictures.</p></content></entry><entry><title>Installing DarkPAN Perl modules via GitLab</title><link href="https://domm.plix.at/perl/2025_09_install_darkpan_gitlab.html"/><id>https://domm.plix.at/perl/2025_09_install_darkpan_gitlab.html</id><updated>2025-09-07T15:00:00+00:00</updated><category term="perl"/><summary>This week Farhad and me finally found some time to improve a part of our build pipeline that was nagging me for years. We can now release our DarkPAN modules via CI/CD into a GitLab generic packages ...</summary><content type="html"><p>This week <a href="https://farhad.shahbazi.at/">Farhad</a> and me finally found some time to improve a part of our build pipeline that was nagging me for years. We can now release our DarkPAN modules via CI/CD into a <a href="https://docs.gitlab.com/user/packages/generic_packages/">GitLab generic packages repository</a> and install them from there into our app containers, also via CI/CD pipelines.</p>
+
+<p>But before we start with the details, a little background:</p>
+
+<h3>DarkPAN</h3>
+
+<p><a href="https://www.perl.com/">Perl</a> modules are published to <a href="https://metacpan.org/"><span class="caps">CPAN</span></a>. But not all Perl code goes to <span class="caps">CPAN, </span>and this "dark Perl matter" is called "DarkPAN". You especially don't want to publish the internal code that's running your company, or the set of various helper code that you collect over the years that's not really tied to one app, and also (for whatever reasons) not suitable to be properly release to <span class="caps">CPAN.</span> If you still want to use all the best practices and tools established in the last 30+ years, but in private, you can set up an internal <span class="caps">CPAN</span>-like repository, for example using tools like <a href="https://metacpan.org/dist/Pinto/view/bin/pinto">Pinto</a>. Then you can use standard tools to install your internal dependencies from your internal <span class="caps">CPAN</span>-like repo (which I also call DarkPAN).</p>
+
+<p>So this is what we did in the last ~10 years:</p>
+
+<ul>
+<li>We have a bunch of applications</li>
+<li>We have a bunch of libraries that are used by those applications</li>
+<li>When we push a library repo, GitLab will run a CI job that<br />
+ * uses <a href="http://Dist::Zilla">Dist::Zilla</a> to build a proper package for the library<br />
+ * also uses Dist::Zilla to release the package to our custom Pinto</li>
+<li>When an app needs to use such a library (or a new version), we add it to the apps <code>cpanfile</code></li>
+<li>And when a new release of the app is built (again via GitLab CI/CD pipeline), we use <a href="https://metacpan.org/dist/App-cpm/view/script/cpm">cpm</a> with a custom <code>resolver</code> to install the dependency from our DarkPAN</li>
+</ul>
+
+<p><code>cpm install -g --resolver metadb --resolver 02packages,https://pinto.internal.example.com/stacks/darkpan</code></p>
+
+<p>This worked quite well.</p>
+
+<h3>Shared Libs</h3>
+
+<p>It worked so well that we also used this to release and install what I now call "shared libs" inside monorepos: We have a few monorepos for different projects, where each monorepo contains multiple apps (different <span class="caps">API </span>backends, frontends and anything in between). Inside a monorepo we have code that we want to share between all apps, for example the database abstraction (DBIx::Class). Deploying these shared libs via the above method was working, but not very smoothly, especially when there's a lot of development happening: You had to push a new version of the lib, wait for it to be released, and then bump the version in all the <code>cpanfiles</code> using this library.</p>
+
+<p>So a few weeks ago I changed our handling of these shared libs. Instead of properly installing them via <code>cpm</code>, I can copy the source code directly into the app container and set <code>PERL5LIB</code> accordingly. This is possible because all of the code is in the same monorepo and thus available in the CI/CD pipeline. (material for another blog post..)</p>
+
+<p>This hack is not an (easy) option for code that has to be shared between projects. But I wanted to get rid of maintaining a server to host Pinto, especially as we already have GitLab running, which supports a large range of <a href="https://docs.gitlab.com/user/packages/package_registry/">language specific repositories</a>. Unfortunately, Perl/CPAN is not implemented. But they have a "generic" repository, so I tried to use it to solve our problem.</p>
+
+<h3>Publishing a Perl package to into a GitLab generic packages repository</h3>
+
+<p>The first step is to publish the freshly build Perl distribution into the GitLab repo. This is easy to do via the GitLab <span class="caps">API </span>and <code>curl</code>. The <span class="caps">API </span>endpoint is not very nice <span class="caps">IMO</span>: <code>api/v4/projects/{project-id}/packages/generic/{name}/{version}/{file}</code>, and I find it a bit weird that you set up a <code>name</code> and <code>version</code> and then add files to it (instead of just uploading a tarball), but whatever.</p>
+
+<p>In our <code>Makefile</code> we added:</p>
+
+<pre><code>package_registry := &quot;https://gitlab.example.com/api/v4/projects/foo%2Fbar/packages/generic/$(application_name)&quot;
+get_version = $(shell ls $(application_name)-*.tar.gz | sed &quot;s/$(application_name)-//; s/\.tar\.gz//&quot;)
+get_filename = $(shell ls $(application_name)-*.tar.gz)
+private_token ?= ${CI_JOB_TOKEN}
+
+.PHONY: build
+build:
+ dzil authordeps --missing | xargs -r cpm install --global
+ cpm install --global
+ dzil build
+
+.PHONY: release
+release:
+ $(eval VERSION := $(get_version))
+ $(eval FILENAME := $(get_filename))
+ curl -L -H &quot;JOB-TOKEN: $(private_token)&quot; \
+ --upload-file &quot;$(FILENAME)&quot; \
+ &quot;$(package_registry)/$(VERSION)/$(FILENAME)&quot;</code></pre>
+
+<p>We set the <code>package_registry</code> base <span class="caps">URL </span>(note that I use the <span class="caps">URI</span>-escaped string <code>foo%2Fbar</code> to address the project <code>bar</code> in group <code>foo</code>, because I find using the project ID even uglier that escaping the <code>/</code> as <code>%2F</code>). Then we use some shell / sed to "parse" the filename and get the version number (which is set automatically via Dist::Zilla) in <code>build</code>.</p>
+
+<p>In <code>release</code>, we construct the final <span class="caps">URL, </span>authorize using the <code>CI_JOB_TOKEN</code> and call <code>curl</code> to upload the file.</p>
+
+<h4>Why are we using a <code>Makefile</code> instead of defining the steps in <code>.gitlab-ci.yml</code>?</h4>
+
+<ul>
+<li>I can use the <code>Makefile</code> locally, so I can run whatever steps that are triggered by the pipeline without having to push a change to gitlab (no more <code>force pipeline</code> commits!)</li>
+<li>We prefer writing <code>Makefile</code> to "programming" in <span class="caps">YAML</span></li>
+<li>The CI stages are very short: <code>script: - make build release</code></li>
+</ul>
+
+<h3>Installing from GitLab</h3>
+
+<p>Actually installing the Perl distribution from GitLab seemed easy, but GitLab threw a few stumbling blocks in our way.</p>
+
+<p>My plan was to just take the <span class="caps">URL </span>of the distribution in the GitLab Generic Registry and use that in <code>cpanfile</code> via the nice <code>url</code> addon which allows you to install a <span class="caps">CPAN </span>distribution directly from the given <span class="caps">URL</span>:</p>
+
+<pre><code>requires 'Internal::Package' =&gt; '1.42', url =&gt; 'https://gitlab.example.com/foo/bar/packages/generic/Internal-Package-1.42.tar.gz';</code></pre>
+
+<p>But for weird reasons, GitLab does not provide a simple <span class="caps">URL </span>like that, esp not for unauthorized users. Instead you have to call the <span class="caps">API, </span>provide a token and only then you can download the tarball. And there is no way to add a custom header to pass the token in <code>cpanfile</code>.</p>
+
+<p>After some more reading of the docs, we found that instead of using a header, we can also stuff a deploy token into basic auth using <code>https://user:password@url</code> format, and thus can specify the install <span class="caps">URL </span>like this:</p>
+
+<pre><code>requires 'Internal::Package' =&gt; '1.42', url =&gt; 'https://deployer:gldt-234lkndfg@gitlab.example.com/foo/bar/packages/generic/Internal-Package-1.42.tar.gz';</code></pre>
+
+<p>And this works!!</p>
+
+<p>Well, the <span class="caps">URL </span>(using the actual <span class="caps">API </span>call) in fact looks like this:</p>
+
+<pre><code>requires 'Internal::Package' =&gt; '1.42', url =&gt;
+'https://deployer:gldt-234lkndfg@gitlab.example.com/api/v4/projects/foo%2Fbar/packages/generic/Internal-Package/1.42/Internal-Package-1.42.tar.gz/';</code></pre>
+
+<p>This is not very nice:</p>
+
+<ul>
+<li>The token is embedded in the <code>cpanfile</code></li>
+<li>We have to define the package name three times</li>
+<li>And we also have to define the version three times, and adapt it correctly every time we release a new version</li>
+</ul>
+
+<p>So we continued to improve this in a maybe crazy but perlish way:</p>
+
+<h3>Dynamic cpanfile</h3>
+
+<p>One of the nice things of <code>cpanfile</code> (and Perl in general) is that instead of inventing some stupid <span class="caps">DSL </span>to specify your requirements, we just use code:</p>
+
+<pre><code>requires 'Foo::Bar';</code></pre>
+
+<p>is actually calling a function somewhere that does stuff.</p>
+
+<p>So we can run code in <code>cpanfile</code>:</p>
+
+<pre><code>my @DB = qw(Pg mysql DB2 CSV);
+my $yolo_db = 'DB::' . $DB[rand @DB];
+requires $yolo_db;</code></pre>
+
+<p>The above code is of course crazy, but we can use this power for good and write a nice little wrapper to make depending on our DarkPAN easier.</p>
+
+<h3>Validad::InstallFromGitlab</h3>
+
+<p>I wrote a small tool, <code>Validad::InstallFromGitlab</code>, which is configured with the GitLab base <span class="caps">URL, </span>the project name and the token. It exports a function <code>from_gitlab</code>, which takes the name and the version of the distribution and returns the long line that <code>requires</code> needs.</p>
+
+<p>And because <code>cpanfile</code> is just Perl, we can easily use this module there:</p>
+
+<pre><code>use Validad::InstallFromGitlab (gitlab =&gt; 'https://gitlab.example.com', project =&gt; 'validad%2fcontainer', auth =&gt; $ENV{DARKPAN_ACCESS});
+requires from_gitlab( 'Validad::Mailer' =&gt; '1.20250904.144530');
+requires from_gitlab( 'Accounts::Client' =&gt; '1.20250904.144543');</code></pre>
+
+<p>I decided to use some rather old but very powerful method to make <code>Validad::InstallFromGitlab</code> easy to use: A custom <code>import()</code> function:</p>
+
+<pre><code>package Validad::InstallFromGitlab;
+use v5.40;
+use Carp qw(croak);
+
+sub import {
+ my ($class, %args) = @_;
+ if (!$args{gitlab} || !$args{project}) {
+ croak(&quot;gitlab and/or project missing&quot;);
+ }
+
+ my $registry_url = sprintf('%s/api/v4/projects/%s/packages/generic', $args{gitlab}, $args{project});
+ if (my $auth = $args{auth}) {
+ $registry_url =~s{://}{'://'.$auth.'@'}e;
+ }
+
+ my $caller=caller();
+ no strict 'refs';
+ *{&quot;$caller\::from_gitlab&quot;} = sub {
+ my ($module, $version) = @_;
+
+ my $package = $module;
+ $package =~s/::/-/g;
+ my $tarball = $package .'-' . $version . '.tar.gz' ;
+ my $url = join('/',$registry_url, $package, $version, $tarball);
+ return ($module, url =&gt; $url);
+ };
+}
+
+1;</code></pre>
+
+<p><code>import()</code> is called when you use the module in the calling code or <code>cpanfile</code> :-)</p>
+
+<pre><code>use Validad::InstallFromGitlab (
+ gitlab =&gt; 'https://gitlab.example.com',
+ project =&gt; 'validad%2fcontainer',
+ auth =&gt; $ENV{DARKPAN_ACCESS}
+);</code></pre>
+
+<p>The parameters passed to <code>use</code> are passed on to <code>import</code>, where I do some light checking and build the long and cumbersome GitLab url.</p>
+
+<p>Then I use <code>caller()</code> to get the name of the calling namespace and use a typoglob (been some time since I used that..) to install a function named <code>from_gitlab</code> into the caller.</p>
+
+<p>This function takes two params, the module name and version, and finally constructs the data needed by require, i.e. the module name, version and the whole gitlab <span class="caps">URL.</span></p>
+
+<p>So I can now specify my requirements very easily in the apps <code>cpanfiles</code> and still use the GitLab generic package registry to distribute my DarkPAN modules!</p>
+
+<h3>Installing Validad::InstallFromGitlab</h3>
+
+<p>But how do I install <code>Validad::InstallFromGitlab</code>?</p>
+
+<p>I don't. But all of our apps use a shared base container (which also helps to keep container size down). And in the Containerfile of the base container, I copy <code>Validad::InstallFromGitlab</code> to a well-known location <code>/opt/perl/darkpan</code> and load it from there via <code>PERL5LIB</code>:</p>
+
+<pre><code>RUN mkdir -p /opt/perl/darkpan/Validad/
+COPY Validad-InstallFromGitlab.pm /opt/perl/darkpan/Validad/InstallFromGitlab.pm
+
+ONBUILD ARG DARKPAN_ACCESS
+ONBUILD RUN PERL5LIB=/opt/perl/darkpan/ \
+ /opt/perl/bin/cpm install --cpanfile cpanfile \
+ --show-build-log-on-failure -g</code></pre>
+
+<p>But again that's material for another blog post...</p></content><category term="Perl"/><category term="gitlab"/><category term="DevOps"/><category term="CI"/><category term="container"/><category term="CPAN"/></entry><entry><title>Koha Hackfest 2025 in Marseille</title><link href="https://domm.plix.at/perl/2025_04_koha_hackfest.html"/><id>https://domm.plix.at/perl/2025_04_koha_hackfest.html</id><updated>2025-04-04T10:00:00+00:00</updated><category term="perl"/><summary type="html">I'm currently sitting in a TGV doing 300km/h from Marseille to Paris, traveling back home from the Koha Hackfest, hosted by BibLibre.
+
+Results
+
+This year I did a lot of QA, which means reviewing ...</summary><content type="html"><p>I'm currently sitting in a <span class="caps">TGV </span>doing 300km/h from Marseille to Paris, traveling back home from the <a href="https://koha-community.org/">Koha</a> Hackfest, hosted by <a href="https://www.biblibre.com/">BibLibre</a>.</p>
+
+<h3>Results</h3>
+
+<p>This year I did a lot of <span class="caps">QA, </span>which means reviewing patches, running their test plan, verifying that everything works and finally signing off the patches and marking the bug as "Passed QA". The process is documented in <a href="https://wiki.koha-community.org/wiki/How_to_QA">the wiki</a>. According to the <a href="https://scoreboard.koha-community.org/">scoreboard</a> I <span class="caps">QA'</span>ed 8 bugs (the second highest number!). After the third or fourth time I did not even have to look up all the steps anymore.</p>
+
+<p>I moderated a short panel on ElasticSearch, because I found some weird behaviors on which I needed feedback from the experts. This resulted in a bunch of new "bugs" (Koha speak for issues, in this case a mix of actual bugs an feature requests): <a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=39493">39494</a>, <a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=39548">39548</a>, <a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=39549">39549</a>, <a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=39551">39551</a>, <a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=39552">39552</a>.</p>
+
+<p>I did a rather detailed review of <a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=37020">37020 - bulkmarcimport gets killed when inserting large files</a>. The problem here is that the current code uses <span class="caps">MARC</span>::Batch, which does some horrible regex "parsing" of <span class="caps">XML </span>to implement a stream parser (so it can handle large files without using <span class="caps">ALL </span>the <span class="caps">RAM</span>) (see more details at the end of this post). But a <a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=37478">recent change</a> added a check-step which validates the records and puts the valid ones onto an Perl array. Which now again takes up <span class="caps">ALL </span>the <span class="caps">RAM.</span> I reviewed the two proposed patches, but I think we should use <span class="caps">XML</span>::LibXML::Reader directly, which should result in cleaner, faster, less-RAM-using and correct code.</p>
+
+<p>I also participated in various other discussions and hope to have provided some helpful ideas &amp; feedback from my still semi-external Koha perspective and semi-extensive knowledge of other environments and projects (I have been doing this "web dev" stuff for quite some time now..).</p>
+
+<p>After help Clemens setup <span class="caps">L10N </span>on his <span class="caps">KTD </span>setup, I submitted a <a href="https://gitlab.com/koha-community/koha-testing-docker/-/merge_requests/540">doc patch</a> to <span class="caps">KTD </span>explaining the <code>SKIP_L10N</code> setup and hopefully making the general <span class="caps">L10N </span>setup a bit clearer. I generally try to improve the docs if I hit a problem and was able to fix it. Give it a try the next time, it's very rewarding!</p>
+
+<p>I could also provide some Perl help to various other attendees. But I still failed most of the questions of joubus <a href="https://2025-hackfest-perl-quizz-e8dbca.gitlab.io/perl_quizz.html">Perl quiz</a>. My excuse is that I trained my brain on writing only good/sane/nice Perl so that I forgot how to parse all the <a href="https://domm.plix.at/perl/space_invaders.pl.txt">weird corner cases</a>...</p>
+
+<h3>Social</h3>
+
+<p>But the Hackfest is not only about hacking, there's also the "fest" part (or party?). I really enjoyed hanging out with the other attendees on the terrace during lunch in the sun. The food was as usual excellent and not too unhealthy (of course depending on how much cheese one is able to stack onto his plate). The evenings at various bars and restaurants where fun and entertaining (even though I did manage to go to bed early enough this year, and hardly had any alcohol).</p>
+
+<p>I did not do any sightseeing or even just walking around Marseille this year. I blame the fact that our hotel was very near to the venue and most of the after-hack locations. And I didn't bring my swimming trunks so I was not motivated to go to the beach (but I've ticked that off last year..)</p>
+
+<p>I had a lot of nice chats with old and new friends on topics ranging from the obvious (A.I., the sorry state of the world, Koha, Perl) to the obscure (US garbage collection trucks, the lifetime of ropes for hand-pulled elevators up to Greek monasteries, sweet potato heritage of Aotearoa, chicken egg sizes, anarcho-syndicalism, ...)</p>
+
+<h3>Thanks</h3>
+
+<p>Thanks to BibLibre and Paul Poulain for organizing the event, and to all the attendees for making it such a wonderful 4 days!</p>
+
+
+<h4>Postscript: The horrors of <span class="caps">MARC</span>::Batch</h4>
+
+<p>So, how does <a href="https://metacpan.org/pod/MARC::Batch"><span class="caps">MARC</span>::Batch</a> handle importing huge <span class="caps">XML </span>files without using too much <span class="caps">RAM</span>?</p>
+
+<p>By breaking the first rule of <span class="caps">XML </span>handling: It "parses" the <span class="caps">XML </span>via regex!</p>
+
+<p>This is actually implemented in <a href="https://metacpan.org/pod/MARC::File::XML"><span class="caps">MARC</span>::File::XML</a>, namely <a href="https://metacpan.org/dist/MARC-File-XML/source/lib/MARC/File/XML.pm#L385">here</a>. If you have a strong stomach I'll wait for you to take a look at that code.</p>
+
+<p>Here are some "highlights":</p>
+
+<pre><code> ## get a chunk of xml for a record
+ local $/ = 'record&gt;';
+ my $xml = &lt;$fh&gt;;</code></pre>
+
+<p>Set the <code>input record separator</code> (usually newline <code>\n</code>, and telling Perl what it should consider a line) to the string <code>record&gt;</code> so, basically something which looks like and <span class="caps">XML </span>tag ending with record. It is <span class="caps">NOT </span>including the start <code>&lt;</code> because the code wants to ignore <span class="caps">XML </span>namespaces.</p>
+
+<p>The it uses <code>&lt;$fh&gt;</code> to read the next "line" from the record, which isn't a line in any usual sense, but all bytes up to the next occurrence of <code>record&gt;</code>.</p>
+
+<pre><code> ## do we have enough?
+ $xml .= &lt;$fh&gt; if $xml !~ m!&lt;/([^:]+:){0,1}record&gt;$!;</code></pre>
+
+<p>It continues reading until it find something that looks like a closing <code>&lt;/record&gt;</code> tag (which might contain a namespace). Then some more "cleanup", and finally the xml chunk is returned.</p>
+
+<p>Obviously this works, as it is used by thousands of libraries around the world on millions of records all the time.</p>
+
+<p>But still: Uff!</p></content><category term="Perl"/><category term="Koha"/><category term="event"/></entry></feed>
+\ No newline at end of file