<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>coding &amp;mdash; Jerry of the Week</title>
    <link>https://write.in0rdr.ch/tag:coding</link>
    <description>ˈdʒɛri - Individual who sends life against the grain no matter the consequences</description>
    <pubDate>Mon, 18 May 2026 06:13:19 +0000</pubDate>
    <item>
      <title>Jenkins works</title>
      <link>https://write.in0rdr.ch/jenkins-works</link>
      <description>&lt;![CDATA[I was dabbling around with Jenkins in my Nomad cluster lately. In this post I quickly share my experiences and what I learned along the way.&#xA;&#xA;#cicd #coding #jenkins #nomad&#xA;!--more--&#xA;&#xA;You may ask why I spend time to setup a good old Jenkins while everyone else seems to jump on some newer CI systems (GitLab, Forgejo, etc.)? Well, as said, it&#39;s still good old Jenkins and I assume it will be around for some more time.&#xA;&#xA;To run Jenkins agents on a Nomad cluster I followed Ola Ogunseghas instructions and example code here:&#xA;&#xA;https://faun.pub/jenkins-build-agents-on-nomad-workers-626b0df4fc57&#xA;https://github.com/GastroGee/jenkins-nomad/blob/main/jenkins-controller/nomad.yaml&#xA;&#xA;It involves installing the Nomad plugin for Jenkins and configuring this &#34;Nomad cloud&#34; (that&#39;s how the integration is called in Jenkins) with a template for the Jenkins agents.&#xA;&#xA;Obviously, I wanted to integrate Jenkins with my Git repos. The most straight forward way seemed to use post-receive hooks to nudge Jenkins on every push. This has worked fabulously so far.&#xA;&#xA;Even though the runners are spawned as Nomad jobs, I still wanted to run other Docker containers in the pipeline. This is where it got very confusing for me, because of the different Docker plugins for Jenkins. Most notably, there exist at least these two plugins:&#xA;&#xA;docker-workflow, runs the Docker container from the Jenkins agent&#xA;docker-plugin, runs Jenkins agents as Docker containers&#xA;&#xA;I decided to go with the former &#34;docker-workflow&#34; plugin because I already deployed the Jenkins agents as Nomad jobs. docker-workflow can run arbitrary containers from any Docker image, whereas the docker-plugin needs to be based on the Jenkins inbound-agent image to be able to connect to the Jenkins server.&#xA;&#xA;I wanted the containers that are launched from Jenkins to be contained in a another users namespace. There exists the option to rebuild the inbound-agent with user-supplied attributes for uid/gid, but since I wanted to modify the image anyways I simply forked and built my own agent images, mostly inspired by the example for running Docker inside the agent.&#xA;&#xA;At this stage, my tooling was sophisticated enough I could run a simple gitleaks container on each push to scan for secrets. I&#39;m always afraid to publish secrets accidentally (it happened to me before).&#xA;&#xA;Furthermore, I needed to establish some kind of build process, so I can also build images with Jenkins and directly push them to my local image registry. After some reading, I discarded the thought to use Kaniko because it still requires nodes in the native architecture of the respective build for multi-architecture builds.&#xA;&#xA;Therefore, I followed RedHat best-practices to integrate Buildah, which is also the tool I use to build multi-arch container images locally on my laptop.&#xA;&#xA;If you are interested in more example code, here the link to some of the key components:&#xA;&#xA;jenkins.yaml.tmpl.html Jenkins infrastructure code&#xA;jenkins.nomad.html Jenkins nomad job&#xA;docker-agent Modified Jenkins inbound-agent with Docker and Buildah&#xA;&#xA;I&#39;m not finished with playing around, because I still haven&#39;t figured out some things fully yet (e.g., how to properly use jlink in multi-arch builds) and because I&#39;m also curious how other members of the community use Jenkins on Nomad.&#xA;&#xA;div style=&#34;text-align:center; font-size: 0.8em&#34;&#xD;&#xA;a href=&#34;https://write.in0rdr.ch/feed&#34;&amp;#128732; RSS/a | a href=&#34;https://m.in0rdr.ch/in0rdr&#34;&amp;#128024; Fediverse/a | a href=&#34;https://chat.in0rdr.ch/#/guest?join=p0c@conference.in0rdr.ch&#34;&amp;#128172; XMPP/a&#xD;&#xA;/div]]&gt;</description>
      <content:encoded><![CDATA[<p>I was dabbling around with Jenkins in my Nomad cluster lately. In this post I quickly share my experiences and what I learned along the way.</p>

<p><a href="https://write.in0rdr.ch/tag:cicd" class="hashtag"><span>#</span><span class="p-category">cicd</span></a> <a href="https://write.in0rdr.ch/tag:coding" class="hashtag"><span>#</span><span class="p-category">coding</span></a> <a href="https://write.in0rdr.ch/tag:jenkins" class="hashtag"><span>#</span><span class="p-category">jenkins</span></a> <a href="https://write.in0rdr.ch/tag:nomad" class="hashtag"><span>#</span><span class="p-category">nomad</span></a>
</p>

<p>You may ask why I spend time to setup a good old Jenkins while everyone else seems to jump on some newer CI systems (GitLab, Forgejo, etc.)? Well, as said, it&#39;s still good old Jenkins and I assume it will be around for some more time.</p>

<p>To run Jenkins agents on a Nomad cluster I followed Ola Ogunseghas instructions and example code here:</p>
<ul><li><a href="https://faun.pub/jenkins-build-agents-on-nomad-workers-626b0df4fc57">https://faun.pub/jenkins-build-agents-on-nomad-workers-626b0df4fc57</a></li>
<li><a href="https://github.com/GastroGee/jenkins-nomad/blob/main/jenkins-controller/nomad.yaml">https://github.com/GastroGee/jenkins-nomad/blob/main/jenkins-controller/nomad.yaml</a></li></ul>

<p>It involves installing the <a href="https://plugins.jenkins.io/nomad">Nomad plugin for Jenkins</a> and configuring this “Nomad cloud” (that&#39;s how the integration is called in Jenkins) with a template for the Jenkins agents.</p>

<p>Obviously, I wanted to integrate Jenkins with my <a href="https://code.in0rdr.ch">Git repos</a>. The most straight forward way seemed to use <a href="https://plugins.jenkins.io/git/#plugin-content-push-notification-from-repository">post-receive hooks</a> to nudge Jenkins on every push. This has worked fabulously so far.</p>

<p>Even though the runners are spawned as Nomad jobs, I still wanted to run other Docker containers in the pipeline. This is where it got very confusing for me, because of the different Docker plugins for Jenkins. Most notably, there exist at least these two plugins:</p>
<ul><li><a href="https://plugins.jenkins.io/docker-workflow">docker-workflow</a>, runs the Docker container from the Jenkins agent</li>
<li><a href="https://plugins.jenkins.io/docker-plugin">docker-plugin</a>, runs Jenkins agents as Docker containers</li></ul>

<p>I decided to go with the former “docker-workflow” plugin because I already deployed the Jenkins agents as Nomad jobs. <code>docker-workflow</code> can run arbitrary containers from any Docker image, whereas the <code>docker-plugin</code> needs to be based on the <a href="https://hub.docker.com/r/jenkins/inbound-agent">Jenkins <code>inbound-agent</code> image</a> to be able to connect to the Jenkins server.</p>

<p>I wanted the containers that are launched from Jenkins to be contained in a another users namespace. There exists the option to rebuild the <code>inbound-agent</code> with user-supplied attributes for uid/gid, but since I wanted to modify the image anyways I simply forked and <a href="https://github.com/jenkinsci/docker-agent/compare/master...in0rdr:docker-agent:debug/podman_x86_64?diff=unified">built my own agent</a> images, mostly inspired by the <a href="https://github.com/jenkinsci/docker-inbound-agents/blob/master/docker/Dockerfile">example for running Docker</a> inside the agent.</p>

<p>At this stage, my tooling was sophisticated enough I could run a simple <a href="https://gitleaks.io/">gitleaks</a> container on each push to scan for secrets. I&#39;m always afraid to publish secrets accidentally (it happened to me before).</p>

<p>Furthermore, I needed to establish some kind of build process, so I can also build images with Jenkins and directly push them to my local image registry. After some reading, I discarded the thought to use Kaniko because it still requires nodes in the native architecture of the respective build for <a href="https://github.com/GoogleContainerTools/kaniko?tab=readme-ov-file#creating-multi-arch-container-manifests-using-kaniko-and-manifest-tool">multi-architecture builds</a>.</p>

<p>Therefore, I followed RedHat <a href="https://developers.redhat.com/blog/2019/08/14/best-practices-for-running-buildah-in-a-container">best-practices</a> to integrate Buildah, which is also the tool I use to build multi-arch container images locally on my laptop.</p>

<p>If you are interested in more example code, here the link to some of the key components:</p>
<ul><li><a href="https://code.in0rdr.ch/nomad/file/hcl/default/jenkins/templates/jenkins.yaml.tmpl.html">jenkins.yaml.tmpl.html</a> Jenkins infrastructure code</li>
<li><a href="https://code.in0rdr.ch/nomad/file/hcl/default/jenkins/jenkins.nomad.html">jenkins.nomad.html</a> Jenkins nomad job</li>
<li><a href="https://code.in0rdr.ch/nomad/file/docker/docker-jenkins-inbound-agent/Dockerfile.html">docker-agent</a> Modified Jenkins inbound-agent with Docker and Buildah</li></ul>

<p>I&#39;m not finished with playing around, because I still haven&#39;t figured out some things fully yet (e.g., how to properly use <a href="https://community.jenkins.io/t/usage-of-jlink-in-jenkinsci-docker-agent/15456">jlink in multi-arch builds</a>) and because I&#39;m also curious <a href="https://discuss.hashicorp.com/t/jenkins-nomad-plugin/67020">how other members of the community use Jenkins on Nomad</a>.</p>

<div style="text-align:center; font-size: 0.8em">
<a href="https://write.in0rdr.ch/feed">🛜 RSS</a> | <a href="https://m.in0rdr.ch/in0rdr">🐘 Fediverse</a> | <a href="https://chat.in0rdr.ch/#/guest?join=p0c@conference.in0rdr.ch">💬 XMPP</a>
</div>
]]></content:encoded>
      <guid>https://write.in0rdr.ch/jenkins-works</guid>
      <pubDate>Sun, 09 Jun 2024 19:32:05 +0000</pubDate>
    </item>
    <item>
      <title>Building Haskell projects for Nix</title>
      <link>https://write.in0rdr.ch/building-haskell-projects-for-nix</link>
      <description>&lt;![CDATA[Similar to the last post on building a C project, today I quickly cover how to build a Haskell project.&#xA;&#xA;You might ask why? I got asked about that stuff by my friendly office colleague Samuel. By the way, without him I would have never installed/used Nixos in the first place. Good fella, go checkout his blog.&#xA;&#xA;#packaging #coding #nix #haskell&#xA;!--more--&#xA;&#xA;The goal was to package oama. Oama helps you to renew OAuth2 tokens from the command line. To be honest, I haven&#39;t checked the details on how it works yet, I was only interested in packaging it for Nix.&#xA;&#xA;I was reading a bit on how it can be used with my favorite msmtp client.&#xA;&#xA;Then I read some of the Nix documentation on how to build and use Haskell packages. They describe how to quickly bootstrap development environments (i.e., a Nix shell) using the handy cabal2nix utility.&#xA;&#xA;The first step for packaging was also to convert the cabal description of the package into a valid Nix expression which already proved useful for development purposes.&#xA;&#xA;git clone https://github.com/pdobsan/oama.git oama.git&#xA;cd oama.git&#xA;cabal2nix .   oama.nix&#xA;&#xA;Create default.nix:&#xA;Retrieve nixpkgs impurely from NIXPATH for now, you can pin it instead, of course.&#xA;{ pkgs ? import nixpkgs {} }:&#xA;&#xA;use the nixpkgs default haskell package set&#xA;pkgs.haskellPackages.callPackage ./oama.nix { }&#xA;&#xA;If you have a look at oama.nix it contains a lot of references to Haskell packages (.e.g, haskellPackages.aeson). The callPackage takes care of the name resolution in this case. haskellPackages.mkDerivation is a wrapper around stdenv.mkDerivation (see last post building a C project), but works without the standard Haskell build tool cabal-install.&#xA;&#xA;To build oama and quickly run it in a shell:&#xA;nix-shell -p cabal-install&#xA;nix-build default.nix&#xA;./result/bin/oama&#xA;&#xA;I did not look further into shellFor at this point, because I was interested in creating a Nix package that I can install globally.&#xA;&#xA;For this, I took the &#34;stdenv template&#34; from the last post) and merged 😻 it with the oama.nix output from cabal2nix. The result:&#xA;&#xA;let&#xA;  pkgs = import nixpkgs { };&#xA;  hpkgs = pkgs.haskellPackages;&#xA;in&#xA;  hpkgs.mkDerivation {&#xA;    pname = &#34;oama&#34;;&#xA;    version = &#34;0.10.1&#34;;&#xA;    src = pkgs.fetchgit {&#xA;      url = &#34;https://github.com/pdobsan/oama.git&#34;;&#xA;      rev = &#34;refs/tags/0.10.1&#34;;&#xA;      hash = &#34;sha256-mQBlCrF9rvFOfSOxhMi6JUKDJocFmO4Hhc3Zy7AqiXk=&#34;;&#xA;    };&#xA;&#xA;    isLibrary = true;&#xA;    isExecutable = true;&#xA;    libraryHaskellDepends = with hpkgs; [&#xA;      aeson base bytestring containers directory hsyslog http-conduit&#xA;      network-uri optparse-applicative pretty-simple process string-qq&#xA;      strings text time twain unix utf8-string warp yaml&#xA;    ];&#xA;    executableHaskellDepends = with hpkgs; [&#xA;      aeson base bytestring containers directory hsyslog http-conduit&#xA;      network-uri optparse-applicative pretty-simple process string-qq&#xA;      strings text time twain unix utf8-string warp yaml&#xA;    ];&#xA;&#xA;    mainProgram = &#34;oama&#34;;&#xA;    license = pkgs.lib.licenses.bsd3;&#xA;  }&#xA;&#xA;Essentially, I only had to use nix-prefetch-git to get the shasum of the git revision and make sure I use the pkgs and hpkgs (reference to pkgs.haskellPackages) correctly in the right places.&#xA;&#xA;Afterwards, again, building and installing oama globally was a peace of cake 🧁:&#xA;&#xA;nix-build oama.nix&#xA;nix-env -i -f oama.nix&#xA;&#xA;Now I only have to figure out what to do with this tool 🤔 but this is left for another post.&#xA;&#xA;div style=&#34;text-align:center; font-size: 0.8em&#34;&#xD;&#xA;a href=&#34;https://write.in0rdr.ch/feed&#34;&amp;#128732; RSS/a | a href=&#34;https://m.in0rdr.ch/in0rdr&#34;&amp;#128024; Fediverse/a | a href=&#34;https://chat.in0rdr.ch/#/guest?join=p0c@conference.in0rdr.ch&#34;&amp;#128172; XMPP/a&#xD;&#xA;/div]]&gt;</description>
      <content:encoded><![CDATA[<p>Similar to the last post on <a href="https://write.in0rdr.ch/building-and-installing-a-c-project-with-nix">building a C project</a>, today I quickly cover how to build a Haskell project.</p>

<p>You might ask why? I got asked about that stuff by my friendly office colleague Samuel. By the way, without him I would have never installed/used Nixos in the first place. Good fella, go checkout his <a href="https://blog.yosemitesam.ch">blog</a>.</p>

<p><a href="https://write.in0rdr.ch/tag:packaging" class="hashtag"><span>#</span><span class="p-category">packaging</span></a> <a href="https://write.in0rdr.ch/tag:coding" class="hashtag"><span>#</span><span class="p-category">coding</span></a> <a href="https://write.in0rdr.ch/tag:nix" class="hashtag"><span>#</span><span class="p-category">nix</span></a> <a href="https://write.in0rdr.ch/tag:haskell" class="hashtag"><span>#</span><span class="p-category">haskell</span></a>
</p>

<p>The goal was to package <a href="https://github.com/pdobsan/oama"><code>oama</code></a>. Oama helps you to renew OAuth2 tokens from the command line. To be honest, I haven&#39;t checked the details on how it works yet, I was only interested in packaging it for Nix.</p>

<p>I was reading a bit on <a href="https://wiki.archlinux.org/title/msmtp#OAuth2_Setup">how it can be used with my favorite <code>msmtp</code> client</a>.</p>

<p>Then I read some of the <a href="https://nixos.org/manual/nixpkgs/stable/#haskell">Nix documentation on how to build and use Haskell packages</a>. They describe how to quickly bootstrap development environments (i.e., a Nix shell) using the handy <a href="https://hackage.haskell.org/package/cabal2nix#readme"><code>cabal2nix</code> utility</a>.</p>

<p>The first step for packaging was also to convert the <code>cabal</code> description of the package into a valid Nix expression which already proved useful for development purposes.</p>

<pre><code class="language-bash">git clone https://github.com/pdobsan/oama.git oama.git
cd oama.git
cabal2nix . &gt; oama.nix
</code></pre>

<p>Create <code>default.nix</code>:</p>

<pre><code># Retrieve nixpkgs impurely from NIX_PATH for now, you can pin it instead, of course.
{ pkgs ? import &lt;nixpkgs&gt; {} }:

# use the nixpkgs default haskell package set
pkgs.haskellPackages.callPackage ./oama.nix { }
</code></pre>

<p>If you have a look at <code>oama.nix</code> it contains a lot of references to Haskell packages (.e.g, <a href="https://search.nixos.org/packages?channel=23.11&amp;show=haskellPackages.aeson"><code>haskellPackages.aeson</code></a>). The <code>callPackage</code> takes care of the name resolution in this case. <code>haskellPackages.mkDerivation</code> is a wrapper around <code>stdenv.mkDerivation</code> (see last post <a href="https://write.in0rdr.ch/building-and-installing-a-c-project-with-nix">building a C project</a>), but works without the standard Haskell build tool <code>cabal-install</code>.</p>

<p>To build <code>oama</code> and quickly run it in a shell:</p>

<pre><code class="language-bash">nix-shell -p cabal-install
nix-build default.nix
./result/bin/oama
</code></pre>

<p>I did not look further into <code>shellFor</code> at this point, because I was interested in creating a Nix package that I can install globally.</p>

<p>For this, I took the “stdenv template” from the last post and merged 😻 it with the <code>oama.nix</code> output from <code>cabal2nix</code>. The result:</p>

<pre><code class="language-nix">let
  pkgs = import &lt;nixpkgs&gt; { };
  hpkgs = pkgs.haskellPackages;
in
  hpkgs.mkDerivation {
    pname = &#34;oama&#34;;
    version = &#34;0.10.1&#34;;
    src = pkgs.fetchgit {
      url = &#34;https://github.com/pdobsan/oama.git&#34;;
      rev = &#34;refs/tags/0.10.1&#34;;
      hash = &#34;sha256-mQBlCrF9rvFOfSOxhMi6JUKDJocFmO4Hhc3Zy7AqiXk=&#34;;
    };

    isLibrary = true;
    isExecutable = true;
    libraryHaskellDepends = with hpkgs; [
      aeson base bytestring containers directory hsyslog http-conduit
      network-uri optparse-applicative pretty-simple process string-qq
      strings text time twain unix utf8-string warp yaml
    ];
    executableHaskellDepends = with hpkgs; [
      aeson base bytestring containers directory hsyslog http-conduit
      network-uri optparse-applicative pretty-simple process string-qq
      strings text time twain unix utf8-string warp yaml
    ];

    mainProgram = &#34;oama&#34;;
    license = pkgs.lib.licenses.bsd3;
  }
</code></pre>

<p>Essentially, I only had to use <code>nix-prefetch-git</code> to get the shasum of the git revision and make sure I use the <code>pkgs</code> and <code>hpkgs</code> (reference to <code>pkgs.haskellPackages</code>) correctly in the right places.</p>

<p>Afterwards, again, building and installing <code>oama</code> globally was a peace of cake 🧁:</p>

<pre><code class="language-bash">nix-build oama.nix
nix-env -i -f oama.nix
</code></pre>

<p>Now I only have to figure out what to do with this tool 🤔 but this is left for another post.</p>

<div style="text-align:center; font-size: 0.8em">
<a href="https://write.in0rdr.ch/feed">🛜 RSS</a> | <a href="https://m.in0rdr.ch/in0rdr">🐘 Fediverse</a> | <a href="https://chat.in0rdr.ch/#/guest?join=p0c@conference.in0rdr.ch">💬 XMPP</a>
</div>
]]></content:encoded>
      <guid>https://write.in0rdr.ch/building-haskell-projects-for-nix</guid>
      <pubDate>Fri, 17 May 2024 17:50:48 +0000</pubDate>
    </item>
    <item>
      <title>Building and installing a C project with Nix</title>
      <link>https://write.in0rdr.ch/building-and-installing-a-c-project-with-nix</link>
      <description>&lt;![CDATA[Today I learned how to build and install a simple C project on Nix(OS).&#xA;&#xA;After some reading on the Internet 📚, I finally understood the basics of building and installing a package from C sources using a Nix expression file (the .nix file in the following examples).&#xA;&#xA;#packaging #coding #nix #diary #c&#xA;&#xA;!--more--&#xA;&#xA;The expression file uses the standard build environment 🧰. Essentially, this environment abstracts the traditional Makefile based build proces (i.e., make &amp;&amp; make install).&#xA;&#xA;For learning purposes, I decided to package my hobby project, the text-based journaling program diary 📓.&#xA;&#xA;Writing the Nix expression file&#xA;&#xA;I built the expression file based on a an example from the Nix book.&#xA;&#xA;The Nix expression file diary.nix to install the stable version of the diary program fetches the source by tag from the Git repository:&#xA;&#xA;let&#xA;  pkgs = import nixpkgs { };&#xA;in&#xA;  pkgs.stdenv.mkDerivation {&#xA;    name = &#34;diary&#34;;&#xA;    version = &#34;v0.10&#34;;&#xA;    buildInputs = with pkgs; [&#xA;      ncurses&#xA;      curlFull&#xA;      lttng-ust&#xA;    ];&#xA;&#xA;    src = pkgs.fetchgit {&#xA;      url = &#34;https://git.in0rdr.ch/diary.git&#34;;&#xA;      rev = &#34;refs/tags/v0.10&#34;;&#xA;      hash = &#34;sha256-jDPfqUjf0ZETYqPxvEJ2/YFFjNsF7yvVQiThc5yMvjc=&#34;;&#xA;    };&#xA;&#xA;    installPhase = &#39;&#39;&#xA;      install -d $out/bin&#xA;      install -d $out/share/man/man1&#xA;      install -m755 diary $out/bin/&#xA;      install -m644 man1/diary.1 $out/share/man/man1/&#xA;    &#39;&#39;;&#xA;  }&#xA;&#xA;The mkDerivation call does most of the work. There we define the package name and version, as well all of the dependencies required to build the project. In this case, the diary program depends on the curl and ncurses libraries as well as the lttng tracing library.&#xA;&#xA;The source code src can be fetched conveniently from Git (using the fetchgit &#34;builder&#34;) or from other remote sources (see related &#34;fetch&#34; builtin functions).&#xA;&#xA;To build a nightly release from the master branch, I only had to slightly tune the src of the unstable Nix expression:&#xA;&#xA;    src = pkgs.fetchgit {&#xA;      url = &#34;https://git.in0rdr.ch/diary.git&#34;;&#xA;      hash = &#34;sha256-laKIVxfqJqBkB2d0F8tOOnpAXXRzHFfpjhAzZT8MX8k=&#34;;&#xA;    };&#xA;&#xA;The sha256 hash can be obtained using nix-prefetch-git:&#xA;&#xA;get hash for the specific tag/release&#xA;nix-prefetch-git https://git.in0rdr.ch/diary.git --rev refs/tags/v0.10&#xA;&#xA;get hash for latest nightly build from master branch&#xA;nix-prefetch-git https://git.in0rdr.ch/diary.git&#xA;&#xA;Finally, the installPhase needed a bit of tuning and has a small difference to what is commonly used in a Makefile on a traditional Linux distribution. But that was not too hard either, I simply had to translate my install target from the already existing Makefile.&#xA;&#xA;The  install target:&#xA;install: $(TARGET)&#xA;        install -m755 $(TARGET) $(BINDIR)/$(TARGET)&#xA;        install -d $(MANDIR)/man1&#xA;        install -m644 $(MAN1) $(MANDIR)/$(MAN1)&#xA;&#xA;The corresponding Nix installPhase:&#xA;      install -d $out/bin&#xA;      install -d $out/share/man/man1&#xA;      install -m755 diary $out/bin/&#xA;      install -m644 man1/diary.1 $out/share/man/man1/&#xA;&#xA;Very similar indeed. One point to note here is that the fixup phase will automatically install the man pages to the correct location.&#xA;&#xA;Building and installing the C project&#xA;&#xA;Once the expression file is written, building and installing the program is a peace of cake 🍰:&#xA;&#xA;Build stable package&#xA;nix-build nix/diary.nix&#xA;&#xA;Build latest nightly version&#xA;nix-build nix/diary-unstable.nix&#xA;&#xA;To install the package from the expression file to the user environment:&#xA;Install stable package&#xA;nix-env -i -f nix/diary.nix&#xA;&#xA;Install latest nightly version&#xA;nix-env -i -f nix/diary-unstable.nix&#xA;&#xA;You can even verify the location of the man page by using manpath -q or query the installed package with nix-env -q.&#xA;&#xA;Next steps&#xA;&#xA;Now that we have drafted the basic expression file, we might also add it to the nixpkgs repository on GitHub. I haven&#39;t checked the contributor requirements in detail yet.&#xA;&#xA;div style=&#34;text-align:center; font-size: 0.8em&#34;&#xD;&#xA;a href=&#34;https://write.in0rdr.ch/feed&#34;&amp;#128732; RSS/a | a href=&#34;https://m.in0rdr.ch/in0rdr&#34;&amp;#128024; Fediverse/a | a href=&#34;https://chat.in0rdr.ch/#/guest?join=p0c@conference.in0rdr.ch&#34;&amp;#128172; XMPP/a&#xD;&#xA;/div]]&gt;</description>
      <content:encoded><![CDATA[<p>Today I learned how to build and install a simple C project on Nix(OS).</p>

<p>After some reading on the Internet 📚, I finally understood the basics of building and installing a package from C sources using a Nix expression file (the <code>.nix</code> file in the following examples).</p>

<p><a href="https://write.in0rdr.ch/tag:packaging" class="hashtag"><span>#</span><span class="p-category">packaging</span></a> <a href="https://write.in0rdr.ch/tag:coding" class="hashtag"><span>#</span><span class="p-category">coding</span></a> <a href="https://write.in0rdr.ch/tag:nix" class="hashtag"><span>#</span><span class="p-category">nix</span></a> <a href="https://write.in0rdr.ch/tag:diary" class="hashtag"><span>#</span><span class="p-category">diary</span></a> <a href="https://write.in0rdr.ch/tag:c" class="hashtag"><span>#</span><span class="p-category">c</span></a></p>



<p>The expression file uses the <strong><a href="https://nixos.org/manual/nixpkgs/stable/#part-stdenv">standard build environment</a></strong> 🧰. Essentially, this environment abstracts the traditional <code>Makefile</code> based build proces (i.e., <code>make &amp;&amp; make install</code>).</p>

<p>For learning purposes, I decided to package my hobby project, the <a href="https://diary.p0c.ch">text-based journaling program <code>diary</code></a> 📓.</p>

<h2 id="writing-the-nix-expression-file">Writing the Nix expression file</h2>

<p>I built the expression file based on a an <strong><a href="https://book.divnix.com/ch06-01-simple-c-program.html">example from the Nix book</a></strong>.</p>

<p>The Nix expression file <a href="https://code.in0rdr.ch/diary/file/nix/diary.nix.html"><code>diary.nix</code></a> to install the stable version of the <code>diary</code> program fetches the source by tag from the Git repository:</p>

<pre><code class="language-nix">let
  pkgs = import &lt;nixpkgs&gt; { };
in
  pkgs.stdenv.mkDerivation {
    name = &#34;diary&#34;;
    version = &#34;v0.10&#34;;
    buildInputs = with pkgs; [
      ncurses
      curlFull
      lttng-ust
    ];

    src = pkgs.fetchgit {
      url = &#34;https://git.in0rdr.ch/diary.git&#34;;
      rev = &#34;refs/tags/v0.10&#34;;
      hash = &#34;sha256-jDPfqUjf0ZETYqPxvEJ2/YFFjNsF7yvVQiThc5yMvjc=&#34;;
    };

    installPhase = &#39;&#39;
      install -d $out/bin
      install -d $out/share/man/man1
      install -m755 diary $out/bin/
      install -m644 man1/diary.1 $out/share/man/man1/
    &#39;&#39;;
  }
</code></pre>

<p>The <code>mkDerivation</code> call does most of the work. There we define the package <code>name</code> and <code>version</code>, as well all of the dependencies required to build the project. In this case, the <code>diary</code> program depends on the <code>curl</code> and <code>ncurses</code> libraries as well as the <code>lttng</code> tracing library.</p>

<p>The source code <code>src</code> can be fetched conveniently from Git (using the <a href="https://nixos.org/manual/nix/stable/language/builtins.html#builtins-fetchGit"><code>fetchgit</code> “builder”</a>) or from other remote sources (see related <a href="https://nixos.org/manual/nix/stable/language/builtins.html">“fetch” builtin functions</a>).</p>

<p>To build a nightly release from the master branch, I only had to slightly tune the <code>src</code> of the <a href="https://code.in0rdr.ch/diary/file/nix/diary-unstable.nix.html">unstable Nix expression</a>:</p>

<pre><code class="language-nix">    src = pkgs.fetchgit {
      url = &#34;https://git.in0rdr.ch/diary.git&#34;;
      hash = &#34;sha256-laKIVxfqJqBkB2d0F8tOOnpAXXRzHFfpjhAzZT8MX8k=&#34;;
    };
</code></pre>

<p>The sha256 hash can be obtained using <a href="https://search.nixos.org/packages?channel=23.11&amp;show=nix-prefetch-git"><code>nix-prefetch-git</code></a>:</p>

<pre><code class="language-bash"># get hash for the specific tag/release
nix-prefetch-git https://git.in0rdr.ch/diary.git --rev refs/tags/v0.10

# get hash for latest nightly build from master branch
nix-prefetch-git https://git.in0rdr.ch/diary.git
</code></pre>

<p>Finally, the <a href="https://nixos.org/manual/nixpkgs/stable/#ssec-install-phase"><code>installPhase</code></a> needed a bit of tuning and has a small difference to what is commonly used in a <code>Makefile</code> on a traditional Linux distribution. But that was not too hard either, I simply had to translate my <code>install</code> target from the <a href="https://code.in0rdr.ch/diary/file/Makefile.html">already existing <code>Makefile</code></a>.</p>

<p>The  <code>install</code> target:</p>

<pre><code class="language-Makefile">install: $(TARGET)
        install -m755 $(TARGET) $(BINDIR)/$(TARGET)
        install -d $(MANDIR)/man1
        install -m644 $(MAN1) $(MANDIR)/$(MAN1)
</code></pre>

<p>The corresponding Nix <code>installPhase</code>:</p>

<pre><code class="language-nix">      install -d $out/bin
      install -d $out/share/man/man1
      install -m755 diary $out/bin/
      install -m644 man1/diary.1 $out/share/man/man1/
</code></pre>

<p>Very similar indeed. One point to note here is that the <a href="https://nixos.org/manual/nixpkgs/stable/#ssec-fixup-phase"><code>fixup</code> phase</a> will automatically install the man pages to the correct location.</p>

<h2 id="building-and-installing-the-c-project">Building and installing the C project</h2>

<p>Once the expression file is written, <a href="https://code.in0rdr.ch/diary/file/docs/NIX.md.html">building and installing</a> the program is a peace of cake 🍰:</p>

<pre><code class="language-bash"># Build stable package
nix-build nix/diary.nix

# Build latest nightly version
nix-build nix/diary-unstable.nix
</code></pre>

<p>To install the package from the expression file to the user environment:</p>

<pre><code class="language-bash"># Install stable package
nix-env -i -f nix/diary.nix

# Install latest nightly version
nix-env -i -f nix/diary-unstable.nix
</code></pre>

<p>You can even verify the location of the man page by using <code>manpath -q</code> or query the installed package with <code>nix-env -q</code>.</p>

<h2 id="next-steps">Next steps</h2>

<p>Now that we have drafted the basic expression file, we might also add it to the <code>nixpkgs</code> repository on GitHub. I haven&#39;t checked the <a href="https://github.com/NixOS/nixpkgs/blob/master/CONTRIBUTING.md">contributor requirements</a> in detail yet.</p>

<div style="text-align:center; font-size: 0.8em">
<a href="https://write.in0rdr.ch/feed">🛜 RSS</a> | <a href="https://m.in0rdr.ch/in0rdr">🐘 Fediverse</a> | <a href="https://chat.in0rdr.ch/#/guest?join=p0c@conference.in0rdr.ch">💬 XMPP</a>
</div>
]]></content:encoded>
      <guid>https://write.in0rdr.ch/building-and-installing-a-c-project-with-nix</guid>
      <pubDate>Mon, 15 Apr 2024 18:20:17 +0000</pubDate>
    </item>
    <item>
      <title>SRF Teletext Parsing with txtv</title>
      <link>https://write.in0rdr.ch/h1srf-teletext-parsing-with-txtv-h1</link>
      <description>&lt;![CDATA[I spent some time with a neat little CLI application: txtv&#xA;&#xA;This app written in Python let&#39;s you easily read latest Swedish a href=&#34;https://en.wikipedia.org/wiki/Teletext&#34;Teletext/a news. It&#39;s &#34;a client for reading swedish text tv in the terminal&#34;.&#xA;&#xA;I slightly modified the code so it reads from Swiss 🇨🇭 Teletext source (SRF):&#xA;&#xA;https://github.com/voidcase/txtv/pull/15&#xA;&#xA;#coding #python #txtv&#xA;&#xA;!--more--&#xA;&#xA;I could figure out the URL for the API by observing how the .gif pictures for the teletext.ch website are generated from the API while browsing through the txt pages:&#xA;&#xA;img src=&#34;https://code.in0rdr.ch/pub/blog/txt-api.png&#34; alt=&#34;gif page created from api.teletext.ch&#34; /&#xA;&#xA;https://www.teletext.ch&#xA;https://api.teletext.ch/channels/SRF1/pages/{num}&#xA;&#xA;Unfortunately, I did not find a proper documentation for this API 😢.&#xA;&#xA;Reading a bit of Javascript in the browser debugging console helped me to figure out the path structure of the https://api.teletext.ch API a bit better.&#xA;&#xA;By the way, on my mobile I typically read https://m.txt.ch/SRF1 (but this one does not really work well for reading on the web browser / Laptop).&#xA;&#xA;After playing a while with the API I realized, that there is not really an easy way to parse the structure of such a txt page from specific API fields.&#xA;&#xA;img src=&#34;https://code.in0rdr.ch/pub/blog/txt-structure.png&#34; alt=&#34;content and structure of a txt page from the API&#34; /&#xA;&#xA;I decided to only use the content field, because it already displays the Swiss German special characters and Umlauts quite well. Also, the header and other fields like commandRow where not really helpful to parse the content on a page (e.g., to substract title text):&#xA;&#xA;curl -s https://api.teletext.ch/channels/SRF1/pages/130 | jq -r &#39;.subpages[0].ep1Info.data.ep1Format.header&#39;&#xA;ICAgICAgICAgICAgICBQMDEgICAgICAgICAgICAgICAgICAgICAgRA==&#xA;&#xA;curl -s https://api.teletext.ch/channels/SRF1/pages/130 | jq -r &#39;.subpages[0].ep1Info.data.ep1Format.header&#39; | base64 -d&#xA;              P01                      D&#xA;&#xA;In Teletext the pages generally follow a specific structure, most of the time.&#xA;&#xA;Generally, there are some overview pages with links to subpages. The actual content can then be explored on these subpages.&#xA;&#xA;The txtv CLI also roughly supports these two types of pages.&#xA;&#xA;By default, txtv starts in the &#34;interactive mode&#34;, when not started with a specific argument (e.g., to read a specific page nr):&#xA;&#xA;[andi@nixos:~]$ txtv&#xA;WHO: Kontakt nach Gaza gestört.......133&#xA;UNO bestätigt Gaza-Resolution........134&#xA;Maine: Mutmasslicher Schütze tot.....140&#xA;Frauen-Nati unterliegt Schweden......183&#xA;Lugano siegt bei ZSC Lions...........187&#xA;05:05 Gredig direkt/UT...............734&#xA;05:40 Hoch hinaus/UT.................735&#xA;06:25 News-Schlagzeilen 07:30 Wetterkanal KURZÜBERSICHT101&#xA;INLAND...............................104&#xA;SPORT................................180&#xA;AUSLAND..............................130&#xA;METEO................................500&#xA;WIRTSCHAFT...........................150&#xA;TV&amp;RADIO.............................700&#xA;&#xA;Very nice 😎 Not perfect, but still useful.&#xA;&#xA;Now, for my patch I concentrated on displaying the information on two pages:&#xA;&#xA;INLAND 104&#xA;AUSLAND 130&#xA;&#xA;I also did some parsing regarding the &#34;TV&amp;RADIO&#34; section but gave up on over-engineering that part after some time 🤓.&#xA;&#xA;[andi@nixos:~]$ txtv 104&#xA;Sie folgt der Schwesterkommission....107&#xA;Aktionsplan Schweiz und Frankreich...108&#xA;Piste in Zermatt.....................109&#xA;Eine Stunde länger einkaufen.........110&#xA;&#xA;[andi@nixos:~]$ txtv 130&#xA;WHO: Kein Kontakt zu Mitarbeitenden..133&#xA;UNO-Vollversammlung Gaza-Resolution..134&#xA;Reaktionen auf UNO-Resolution........135&#xA;Gaza: Kein Internet nach Angriffen...136&#xA;Weitere Hilfsgüter für Gazastreifen..137&#xA;Israel greift........................250&#xA;Hamas-Ziele an.......................138&#xA;Unesco fordert Schutz der Schulen.. 139MAINE/USA: Polizei findet Leiche140&#xA;&#xA;As you can see from the examples above, the parsing rules still have some issues. This is due to the fact that I can only rely on a few basic assumptions on how the overview txt pages (e.g., 104 and 130) are structured. For instance, the current parsing is flawed when a page title includes a three digit number. For example &#34;Israel greift 250 Hamas-Ziele an&#34;:&#xA;&#xA;Israel greift........................250&#xA;Hamas-Ziele an.......................138&#xA;&#xA;The problem here is that 250 is interpreted as a page number, where it should not.&#xA;&#xA;img src=&#34;https://code.in0rdr.ch/pub/blog/txt-pages.png&#34; alt=&#34;picture of txt pages 138 and 150&#34; /&#xA;&#xA;Unfortunately, I&#39;m not aware of a solution for this, except for finding a smarter parsing rule:&#xA;&#xA;Find all three digit numbers, most probably these are page numbers&#xA;pagenrs = re.findall(r&#39;\s(\d{3})-\/(|$)&#39;, stories)&#xA;allpagenrs = []&#xA;&#xA;for p in pagenrs:&#xA;    try:&#xA;        n = int(p[0])&#xA;        allpagenrs.append(n)&#xA;    except:&#xA;        pass&#xA;    try:&#xA;        n = int(p[1])&#xA;        allpagenrs.append(n)&#xA;    except:&#xA;        pass&#xA;&#xA;allpagenrs = [str(p) for p in allpagenrs]&#xA;&#xA;I experimented with quite a few variations, but gave up after some time, because each has it&#39;s benefits and pitfalls, where the rule would help in some cases but perform worse in another edge case.&#xA;&#xA;So, if you have any suggestions on how to solve that problem of &#34;missing intent&#34; while parsing basically &#34;generic text&#34; let me know. I heard that in this age of AI and machine learning we should be able to do better, shouldn&#39;t we?&#xA;&#xA;The same problem also applies when parsing the titles and subtitles on the overview pages. The txtv ls command is my favorite command. It simply lists all the subpages of the pages 104 (INLAND) and 130 (AUSLAND):&#xA;&#xA;img src=&#34;https://code.in0rdr.ch/pub/blog/txt-output.png&#34; alt=&#34;txtv ls command output&#34; /&#xA;&#xA;But here, as well, my parsing rule is flawed when it comes to actual page titles containing more than a few capital letters in a row (3-4 capital chars are typically used for abbreviations):&#xA;&#xA;if self.num == 100 or self.num == 700:&#xA;    # Remove actual titles&#xA;    stories = re.sub(&#34;Jetzt auf SRF 1&#34;, &#34;&#34;, stories)&#xA;    stories = re.sub(&#34;JETZT AUF SRF 1&#34;, &#34;&#34;, stories)&#xA;    stories = re.sub(&#34;TELETEXT SRF 1&#34;, &#34;&#34;, stories)&#xA;else:&#xA;    # Remove all uppercase subtitles. There can be multiple&#xA;    # subtitles on a page (subtitle, stories, subtitle, stories, etc)&#xA;    stories = regex.sub(r&#39;[\p{Lu}\s-]{9,}[\s:]&#39;, &#39;&#39;, self.content)&#xA;&#xA;So in generally I can be quite happy with the outcome. It gives me a fast and smart way to quickly read the news on any terminal enabled device with Internet access 🤓. Also, the parsing rules on the individual detail/content/sub pages to extract the category and date work flawlessly (except maybe for the more special pages/categories like SPORT and TV&amp;RADIO).&#xA;&#xA;For future improvements I&#39;m right now thinking to also use the links of the API output for extracting the set of real page numbers, which would at least help me with the first problem presented in this article (the titles that contain three digits):&#xA;&#xA;curl -s https://api.teletext.ch/channels/SRF1/pages/130 | jq -r &#39;.subpages[0].ep1Info.links&#39;&#xA;&#xA;For any page without links, I could simply apply the &#34;content page&#34; rendering which has shown to work pretty well already.&#xA;&#xA;div style=&#34;text-align:center; font-size: 0.8em&#34;&#xD;&#xA;a href=&#34;https://write.in0rdr.ch/feed&#34;&amp;#128732; RSS/a | a href=&#34;https://m.in0rdr.ch/in0rdr&#34;&amp;#128024; Fediverse/a | a href=&#34;https://chat.in0rdr.ch/#/guest?join=p0c@conference.in0rdr.ch&#34;&amp;#128172; XMPP/a&#xD;&#xA;/div]]&gt;</description>
      <content:encoded><![CDATA[<p>I spent some time with a neat little CLI application: <code>txtv</code></p>

<p>This app written in Python let&#39;s you easily read latest Swedish <a href="https://en.wikipedia.org/wiki/Teletext">Teletext</a> news. It&#39;s “a client for reading swedish text tv in the terminal”.</p>

<p>I slightly modified the code so it reads from Swiss 🇨🇭 Teletext source (SRF):</p>

<p><a href="https://github.com/voidcase/txtv/pull/15">https://github.com/voidcase/txtv/pull/15</a></p>

<p><a href="https://write.in0rdr.ch/tag:coding" class="hashtag"><span>#</span><span class="p-category">coding</span></a> <a href="https://write.in0rdr.ch/tag:python" class="hashtag"><span>#</span><span class="p-category">python</span></a> <a href="https://write.in0rdr.ch/tag:txtv" class="hashtag"><span>#</span><span class="p-category">txtv</span></a></p>



<p>I could figure out the URL for the API by observing how the <code>.gif</code> pictures for the teletext.ch website are generated from the API while browsing through the txt pages:</p>

<p><img src="https://code.in0rdr.ch/pub/blog/txt-api.png" alt="gif page created from api.teletext.ch"/></p>
<ul><li><a href="https://www.teletext.ch">https://www.teletext.ch</a></li>
<li><code>https://api.teletext.ch/channels/SRF1/pages/{num}</code></li></ul>

<p>Unfortunately, I did not find a proper documentation for this API 😢.</p>

<p>Reading a bit of Javascript in the browser debugging console helped me to figure out the path structure of the <a href="https://api.teletext.ch">https://api.teletext.ch</a> API a bit better.</p>

<p>By the way, on my mobile I typically read <a href="https://m.txt.ch/SRF1">https://m.txt.ch/SRF1</a> (but this one does not really work well for reading on the web browser / Laptop).</p>

<p>After playing a while with the API I realized, that there is not really an easy way to parse the structure of such a txt page from specific API fields.</p>

<p><img src="https://code.in0rdr.ch/pub/blog/txt-structure.png" alt="content and structure of a txt page from the API"/></p>

<p>I decided to only use the <code>content</code> field, because it already displays the Swiss German special characters and Umlauts quite well. Also, the <code>header</code> and other fields like <code>commandRow</code> where not really helpful to parse the content on a page (e.g., to substract title text):</p>

<pre><code class="language-bash">curl -s https://api.teletext.ch/channels/SRF1/pages/130 | jq -r &#39;.subpages[0].ep1Info.data.ep1Format.header&#39;
ICAgICAgICAgICAgICBQMDEgICAgICAgICAgICAgICAgICAgICAgRA==

curl -s https://api.teletext.ch/channels/SRF1/pages/130 | jq -r &#39;.subpages[0].ep1Info.data.ep1Format.header&#39; | base64 -d
              P01                      D
</code></pre>

<p>In Teletext the pages generally follow a specific structure, most of the time.</p>

<p>Generally, there are some overview pages with links to subpages. The actual content can then be explored on these subpages.</p>

<p>The <code>txtv</code> CLI also roughly supports these two types of pages.</p>

<p>By default, <code>txtv</code> starts in the “interactive mode”, when not started with a specific argument (e.g., to read a specific page nr):</p>

<pre><code class="language-bash">[andi@nixos:~]$ txtv
WHO: Kontakt nach Gaza gestört.......133
UNO bestätigt Gaza-Resolution........134
Maine: Mutmasslicher Schütze tot.....140
Frauen-Nati unterliegt Schweden......183
Lugano siegt bei ZSC Lions...........187
05:05 Gredig direkt/UT...............734
05:40 Hoch hinaus/UT.................735
06:25 News-Schlagzeilen 07:30 Wetterkanal KURZÜBERSICHT101
INLAND...............................104
SPORT................................180
AUSLAND..............................130
METEO................................500
WIRTSCHAFT...........................150
TV&amp;RADIO.............................700
</code></pre>

<p>Very nice 😎 Not perfect, but still useful.</p>

<p>Now, for my patch I concentrated on displaying the information on two pages:</p>
<ul><li><code>INLAND 104</code></li>
<li><code>AUSLAND 130</code></li></ul>

<p>I also did some parsing regarding the “TV&amp;RADIO” section but gave up on over-engineering that part after some time 🤓.</p>

<pre><code class="language-bash">[andi@nixos:~]$ txtv 104
Sie folgt der Schwesterkommission....107
Aktionsplan Schweiz und Frankreich...108
Piste in Zermatt.....................109
Eine Stunde länger einkaufen.........110

[andi@nixos:~]$ txtv 130
WHO: Kein Kontakt zu Mitarbeitenden..133
UNO-Vollversammlung Gaza-Resolution..134
Reaktionen auf UNO-Resolution........135
Gaza: Kein Internet nach Angriffen...136
Weitere Hilfsgüter für Gazastreifen..137
Israel greift........................250
Hamas-Ziele an.......................138
Unesco fordert Schutz der Schulen.. 139MAINE/USA: Polizei findet Leiche140
</code></pre>

<p>As you can see from the examples above, the parsing rules still have some issues. This is due to the fact that I can only rely on a few basic assumptions on how the overview txt pages (e.g., 104 and 130) are structured. For instance, the current parsing is flawed when a page title includes a three digit number. For example “Israel greift 250 Hamas-Ziele an”:</p>

<pre><code>Israel greift........................250
Hamas-Ziele an.......................138
</code></pre>

<p>The problem here is that 250 is interpreted as a page number, where it should not.</p>

<p><img src="https://code.in0rdr.ch/pub/blog/txt-pages.png" alt="picture of txt pages 138 and 150"/></p>

<p>Unfortunately, I&#39;m not aware of a solution for this, except for finding a smarter parsing rule:</p>

<pre><code class="language-python"># Find all three digit numbers, most probably these are page numbers
page_nrs = re.findall(r&#39;\s(\d{3})*[-\/]*(\d{3})([^\d]|$)&#39;, stories)
all_page_nrs = []

for p in page_nrs:
    try:
        n = int(p[0])
        all_page_nrs.append(n)
    except:
        pass
    try:
        n = int(p[1])
        all_page_nrs.append(n)
    except:
        pass

all_page_nrs = [str(p) for p in all_page_nrs]
</code></pre>

<p>I experimented with quite a few variations, but gave up after some time, because each has it&#39;s benefits and pitfalls, where the rule would help in some cases but perform worse in another edge case.</p>

<p>So, if you have any suggestions on how to solve that problem of “missing intent” while parsing basically “generic text” let me know. I heard that in this age of AI and machine learning we should be able to do better, shouldn&#39;t we?</p>

<p>The same problem also applies when parsing the titles and subtitles on the overview pages. The <code>txtv ls</code> command is my favorite command. It simply lists all the subpages of the pages 104 (INLAND) and 130 (AUSLAND):</p>

<p><img src="https://code.in0rdr.ch/pub/blog/txt-output.png" alt="txtv ls command output"/></p>

<p>But here, as well, my parsing rule is flawed when it comes to actual page titles containing more than a few capital letters in a row (3-4 capital chars are typically used for abbreviations):</p>

<pre><code class="language-python">if self.num == 100 or self.num == 700:
    # Remove actual titles
    stories = re.sub(&#34;Jetzt auf SRF 1&#34;, &#34;&#34;, stories)
    stories = re.sub(&#34;JETZT AUF SRF 1&#34;, &#34;&#34;, stories)
    stories = re.sub(&#34;TELETEXT SRF 1&#34;, &#34;&#34;, stories)
else:
    # Remove all uppercase subtitles. There can be multiple
    # subtitles on a page (subtitle, stories, subtitle, stories, etc)
    stories = regex.sub(r&#39;[\p{Lu}\s-]{9,}[\s:]&#39;, &#39;&#39;, self.content)
</code></pre>

<p>So in generally I can be quite happy with the outcome. It gives me a fast and smart way to quickly read the news on any terminal enabled device with Internet access 🤓. Also, the parsing rules on the individual detail/content/sub pages to extract the category and date work flawlessly (except maybe for the more special pages/categories like SPORT and TV&amp;RADIO).</p>

<p>For future improvements I&#39;m right now thinking to also use the <code>links</code> of the API output for extracting the set of real page numbers, which would at least help me with the first problem presented in this article (the titles that contain three digits):</p>

<pre><code class="language-bash">curl -s https://api.teletext.ch/channels/SRF1/pages/130 | jq -r &#39;.subpages[0].ep1Info.links&#39;
</code></pre>

<p>For any page without links, I could simply apply the “content page” rendering which has shown to work pretty well already.</p>

<div style="text-align:center; font-size: 0.8em">
<a href="https://write.in0rdr.ch/feed">🛜 RSS</a> | <a href="https://m.in0rdr.ch/in0rdr">🐘 Fediverse</a> | <a href="https://chat.in0rdr.ch/#/guest?join=p0c@conference.in0rdr.ch">💬 XMPP</a>
</div>
]]></content:encoded>
      <guid>https://write.in0rdr.ch/h1srf-teletext-parsing-with-txtv-h1</guid>
      <pubDate>Sat, 28 Oct 2023 02:34:09 +0000</pubDate>
    </item>
    <item>
      <title>How-To Test Nixpkg Perl Module Additions</title>
      <link>https://write.in0rdr.ch/h1how-to-test-nixpkg-perl-module-addition-h1</link>
      <description>&lt;![CDATA[I recently installed NixOS on my laptop.&#xA;&#xA;So far, I&#39;m very happy with how things work and can do most of my tasks as on any other Linux distribution. Some tasks, however, require some more reading and thinking.&#xA;&#xA;Today I wanted to jot down a quick how-to on testing an addition to the Perl packages in the Nix Packages collection (Nixpkgs).&#xA;&#xA;#coding #perl #nixos&#xA;&#xA;!--more--&#xA;&#xA;This how-to is based on:&#xA;&#xA;https://nixos.wiki/wiki/Perl#AddingsomethingfromCPANtonixpkgs&#xA;https://dataswamp.org/~solene/2021-09-18-nix-cpan-pip.html#Perl&#xA;https://nixos.org/manual/nixpkgs/stable/#how-to-develop&#xA;https://nixos.org/manual/nixpkgs/stable/#submitting-changes-making-patches&#xA;&#xA;First, download nix-generate-from-cpan by adding it to /etc/nixos/configuration.nix and running sudo nixos-rebuild switch.&#xA;&#xA;I won&#39;t go into the details on the first step here, because this is a task you should already be familiar with after having spent the first few hours a href=&#34;https://nixos.org/manual/nixos/stable/#sec-changing-config&#34;configuring your NixOS installation/a.&#xA;&#xA;The next steps involve patching the a href=&#34;https://nixos.org/manual/nixpkgs&#34;nixpkgs/a &#34;collection&#34; (which is part of the default nixpkgs &#34;channel&#34;) .&#xA;&#xA;git clone --depth=1 https://github.com/NixOS/nixpkgs nixpkgs.git&#xA;cd nixpkgs.git&#xA;&#xA;Then you create the patch for the a href=&#34;https://nixos.org/manual/nixpkgs/stable/#sec-language-perl&#34;pkgs/top-level/perl-packages.nix/a file as follows:&#xA;&#xA;nix-generate-from-cpan Cal::DAV&#xA;attribute name: CalDAV&#xA;module: Cal::DAV&#xA;version: 0.6&#xA;package: Cal-DAV-0.6.tar.gz (Cal-DAV-0.6, CalDAV)&#xA;path: authors/id/S/SI/SIMONW&#xA;downloaded to: /home/andi/.cpanplus/authors/id/S/SI/SIMONW/Cal-DAV-0.6.tar.gz&#xA;sha-256: e436ecb61db9a2fa3ba4b2111149dfd31bdf7aa6acbc12cc49584e50db63e79b&#xA;unpacked to: /home/andi/.cpanplus/5.36.0/build/CM40IbZK/Cal-DAV-0.6&#xA;runtime deps: DataICal HTTPDAV LWP&#xA;build deps: &#xA;description: A CalDAV client&#xA;license: perl5&#xA;RSS feed: https://metacpan.org/feed/distribution/Cal-DAV&#xA;&#xA;  CalDAV = buildPerlPackage {&#xA;    pname = &#34;Cal-DAV&#34;;&#xA;    version = &#34;0.6&#34;;&#xA;    src = fetchurl {&#xA;      url = &#34;mirror://cpan/authors/id/S/SI/SIMONW/Cal-DAV-0.6.tar.gz&#34;;&#xA;      sha256 = &#34;e436ecb61db9a2fa3ba4b2111149dfd31bdf7aa6acbc12cc49584e50db63e79b&#34;;&#xA;    };&#xA;    propagatedBuildInputs = [ DataICal HTTPDAV LWP ];&#xA;    meta = {&#xA;      description = &#34;A CalDAV client&#34;;&#xA;      license = with lib.licenses; [ artistic1 gpl1Plus ];&#xA;    };&#xA;  };&#xA;&#xA;Add the expression in alphabetical order to a href=&#34;https://github.com/NixOS/nixpkgs/blob/master/pkgs/top-level/perl-packages.nix&#34;pkgs/top-level/perl-packages.nix/a. Only copy/paste the &#34;Nix expression&#34; after the === characters into the file.&#xA;&#xA;To test the patch, spin-up a nix-shell which includes the new Perl module. Create the default.nix expression file in any working directory:&#xA;&#xA;cat &lt;EOF  default.nix &#xA;with (import nixpkgs {});&#xA;let&#xA;  CalDAV = perlPackages.buildPerlPackage {&#xA;    pname = &#34;Cal-DAV&#34;;&#xA;    version = &#34;0.6&#34;;&#xA;    src = fetchurl {&#xA;      url = &#34;mirror://cpan/authors/id/S/SI/SIMONW/Cal-DAV-0.6.tar.gz&#34;;&#xA;      sha256 = &#34;e436ecb61db9a2fa3ba4b2111149dfd31bdf7aa6acbc12cc49584e50db63e79b&#34;;&#xA;    };&#xA;    propagatedBuildInputs = [ perlPackages.DataICal perlPackages.HTTPDAV perlPackages.LWP ];&#xA;    meta = {&#xA;      description = &#34;A CalDAV client&#34;;&#xA;      license = with lib.licenses; [ artistic1 gpl1Plus ];&#xA;    };&#xA;  };&#xA;&#xA;in&#xA;mkShell {&#xA;  buildInputs = [ CalDAV perl ];&#xA;  # putting perl here is only required when not using NixOS, this tell you want Nix perl binary&#xA;}&#xA;EOF&#xA;&#xA;The expression includes the result of the previous nix-generate-from-cpan Cal::DAV with slight modifications.&#xA;&#xA;Notice that propagatedBuildInputs needs to refer to already installed packages prefixed by perlPackages.&#xA;&#xA;More information on a href=&#34;https://nixos.org/manual/nixpkgs/stable/#how-to-develop&#34;how to develop with nix-shell/a.&#xA;&#xA;a href=&#34;https://dataswamp.org/~solene/2021-09-18-nix-cpan-pip.html#Perl&#34;Solènes article/a nicely shows how the a href=&#34;https://nixos.org/guides/nix-pills/basics-of-language&#34;let expression/a can also include an entirely new block of code to define a variable. In this example, the let expression uses the a href=&#34;https://nixos.org/manual/nixpkgs/stable/#ssec-perl-packaging&#34;buildPerlPackage function/a, which is a a href=&#34;https://nixos.org/guides/nix-pills/generic-builders&#34;&#34;builder&#34;/a for building Perl packages (as the name indicates).&#xA;&#xA;With that said, you can launch the development shell to test any Perl script with the newly added Perl library as follows:&#xA;&#xA;nix-shell&#xA;&#xA;If you don&#39;t want to create a &#34;descriptive&#34; shell expression, you can also launch the shell more in an ad-hoc fashion using the following command:&#xA;&#xA;nix-shell -p perlPackages.CalDAV perl -I nixpkgs=~/Downloads/nixpkgs.git&#xA;&#xA;See also the example for Perl in the a href=&#34;https://nixos.org/manual/nix/stable/command-ref/nix-shell#examples&#34;nix-shell Manual/a.&#xA;&#xA;If you are happy with how the module works in the nix-shell, you can a href=&#34;https://nixos.org/manual/nixpkgs/stable/#submitting-changes-making-patches&#34;add it to your system/profile/a (using nix-env or nixos-rebuild) and even a href=&#34;https://github.com/NixOS/nixpkgs/blob/master/pkgs/README.md&#34;contribute it to the upstream nixpkgs repository/a.&#xA;&#xA;This post mostly reminds myself of all the new terminology I learned the past few days.&#xA;&#xA;div style=&#34;text-align:center; font-size: 0.8em&#34;&#xD;&#xA;a href=&#34;https://write.in0rdr.ch/feed&#34;&amp;#128732; RSS/a | a href=&#34;https://m.in0rdr.ch/in0rdr&#34;&amp;#128024; Fediverse/a | a href=&#34;https://chat.in0rdr.ch/#/guest?join=p0c@conference.in0rdr.ch&#34;&amp;#128172; XMPP/a&#xD;&#xA;/div]]&gt;</description>
      <content:encoded><![CDATA[<p>I recently installed NixOS on my laptop.</p>

<p>So far, I&#39;m very happy with how things work and can do most of my tasks as on any other Linux distribution. Some tasks, however, require some more reading and thinking.</p>

<p>Today I wanted to jot down a quick how-to on testing an addition to the Perl packages in the Nix Packages collection (Nixpkgs).</p>

<p><a href="https://write.in0rdr.ch/tag:coding" class="hashtag"><span>#</span><span class="p-category">coding</span></a> <a href="https://write.in0rdr.ch/tag:perl" class="hashtag"><span>#</span><span class="p-category">perl</span></a> <a href="https://write.in0rdr.ch/tag:nixos" class="hashtag"><span>#</span><span class="p-category">nixos</span></a></p>



<p>This how-to is based on:</p>
<ul><li><a href="https://nixos.wiki/wiki/Perl#Adding_something_from_CPAN_to_nixpkgs">https://nixos.wiki/wiki/Perl#Adding_something_from_CPAN_to_nixpkgs</a></li>
<li><a href="https://dataswamp.org/~solene/2021-09-18-nix-cpan-pip.html#_Perl">https://dataswamp.org/~solene/2021-09-18-nix-cpan-pip.html#_Perl</a></li>
<li><a href="https://nixos.org/manual/nixpkgs/stable/#how-to-develop">https://nixos.org/manual/nixpkgs/stable/#how-to-develop</a></li>
<li><a href="https://nixos.org/manual/nixpkgs/stable/#submitting-changes-making-patches">https://nixos.org/manual/nixpkgs/stable/#submitting-changes-making-patches</a></li></ul>

<p>First, download <code>nix-generate-from-cpan</code> by adding it to <code>/etc/nixos/configuration.nix</code> and running <code>sudo nixos-rebuild switch</code>.</p>

<p>I won&#39;t go into the details on the first step here, because this is a task you should already be familiar with after having spent the first few hours <a href="https://nixos.org/manual/nixos/stable/#sec-changing-config">configuring your NixOS installation</a>.</p>

<p>The next steps involve patching the <a href="https://nixos.org/manual/nixpkgs"><code>&lt;nixpkgs&gt;</code></a> “collection” (which is part of the default <code>nixpkgs</code> “channel”) .</p>

<pre><code class="language-bash">git clone --depth=1 https://github.com/NixOS/nixpkgs nixpkgs.git
cd nixpkgs.git
</code></pre>

<p>Then you create the patch for the <a href="https://nixos.org/manual/nixpkgs/stable/#sec-language-perl"><code>pkgs/top-level/perl-packages.nix</code></a> file as follows:</p>

<pre><code class="language-bash">nix-generate-from-cpan Cal::DAV
attribute name: CalDAV
module: Cal::DAV
version: 0.6
package: Cal-DAV-0.6.tar.gz (Cal-DAV-0.6, CalDAV)
path: authors/id/S/SI/SIMONW
downloaded to: /home/andi/.cpanplus/authors/id/S/SI/SIMONW/Cal-DAV-0.6.tar.gz
sha-256: e436ecb61db9a2fa3ba4b2111149dfd31bdf7aa6acbc12cc49584e50db63e79b
unpacked to: /home/andi/.cpanplus/5.36.0/build/CM_4_0IbZK/Cal-DAV-0.6
runtime deps: DataICal HTTPDAV LWP
build deps: 
description: A CalDAV client
license: perl_5
RSS feed: https://metacpan.org/feed/distribution/Cal-DAV
===
  CalDAV = buildPerlPackage {
    pname = &#34;Cal-DAV&#34;;
    version = &#34;0.6&#34;;
    src = fetchurl {
      url = &#34;mirror://cpan/authors/id/S/SI/SIMONW/Cal-DAV-0.6.tar.gz&#34;;
      sha256 = &#34;e436ecb61db9a2fa3ba4b2111149dfd31bdf7aa6acbc12cc49584e50db63e79b&#34;;
    };
    propagatedBuildInputs = [ DataICal HTTPDAV LWP ];
    meta = {
      description = &#34;A CalDAV client&#34;;
      license = with lib.licenses; [ artistic1 gpl1Plus ];
    };
  };
</code></pre>

<p>Add the expression in alphabetical order to <a href="https://github.com/NixOS/nixpkgs/blob/master/pkgs/top-level/perl-packages.nix">pkgs/top-level/perl-packages.nix</a>. Only copy/paste the “Nix expression” after the <code>===</code> characters into the file.</p>

<p>To test the patch, spin-up a <code>nix-shell</code> which includes the new Perl module. Create the <code>default.nix</code> expression file in any working directory:</p>

<pre><code class="language-bash">cat &lt;&lt;EOF &gt; default.nix 
with (import &lt;nixpkgs&gt; {});
let
  CalDAV = perlPackages.buildPerlPackage {
    pname = &#34;Cal-DAV&#34;;
    version = &#34;0.6&#34;;
    src = fetchurl {
      url = &#34;mirror://cpan/authors/id/S/SI/SIMONW/Cal-DAV-0.6.tar.gz&#34;;
      sha256 = &#34;e436ecb61db9a2fa3ba4b2111149dfd31bdf7aa6acbc12cc49584e50db63e79b&#34;;
    };
    propagatedBuildInputs = [ perlPackages.DataICal perlPackages.HTTPDAV perlPackages.LWP ];
    meta = {
      description = &#34;A CalDAV client&#34;;
      license = with lib.licenses; [ artistic1 gpl1Plus ];
    };
  };

in
mkShell {
  buildInputs = [ CalDAV perl ];
  # putting perl here is only required when not using NixOS, this tell you want Nix perl binary
}
EOF
</code></pre>

<p>The expression includes the result of the previous <code>nix-generate-from-cpan Cal::DAV</code> with slight modifications.</p>

<p>Notice that <code>propagatedBuildInputs</code> needs to refer to already installed packages prefixed by <code>perlPackages</code>.</p>

<p>More information on <a href="https://nixos.org/manual/nixpkgs/stable/#how-to-develop">how to develop with nix-shell</a>.</p>

<p><a href="https://dataswamp.org/~solene/2021-09-18-nix-cpan-pip.html#_Perl">Solènes article</a> nicely shows how the <a href="https://nixos.org/guides/nix-pills/basics-of-language"><code>let</code> expression</a> can also include an entirely new block of code to define a variable. In this example, the let expression uses the <a href="https://nixos.org/manual/nixpkgs/stable/#ssec-perl-packaging"><code>buildPerlPackage</code> function</a>, which is a <a href="https://nixos.org/guides/nix-pills/generic-builders">“builder”</a> for building Perl packages (as the name indicates).</p>

<p>With that said, you can launch the development shell to test any Perl script with the newly added Perl library as follows:</p>

<pre><code class="language-bash">nix-shell
</code></pre>

<p>If you don&#39;t want to create a “descriptive” shell expression, you can also launch the shell more in an ad-hoc fashion using the following command:</p>

<pre><code class="language-bash">nix-shell -p perlPackages.CalDAV perl -I nixpkgs=~/Downloads/nixpkgs.git
</code></pre>

<p>See also the example for Perl in the <a href="https://nixos.org/manual/nix/stable/command-ref/nix-shell#examples"><code>nix-shell</code> Manual</a>.</p>

<p>If you are happy with how the module works in the <code>nix-shell</code>, you can <a href="https://nixos.org/manual/nixpkgs/stable/#submitting-changes-making-patches">add it to your system/profile</a> (using <code>nix-env</code> or <code>nixos-rebuild</code>) and even <a href="https://github.com/NixOS/nixpkgs/blob/master/pkgs/README.md">contribute it to the upstream <code>&lt;nixpkgs&gt;</code> repository</a>.</p>

<p>This post mostly reminds myself of all the new terminology I learned the past few days.</p>

<div style="text-align:center; font-size: 0.8em">
<a href="https://write.in0rdr.ch/feed">🛜 RSS</a> | <a href="https://m.in0rdr.ch/in0rdr">🐘 Fediverse</a> | <a href="https://chat.in0rdr.ch/#/guest?join=p0c@conference.in0rdr.ch">💬 XMPP</a>
</div>
]]></content:encoded>
      <guid>https://write.in0rdr.ch/h1how-to-test-nixpkg-perl-module-addition-h1</guid>
      <pubDate>Sun, 10 Sep 2023 18:18:10 +0000</pubDate>
    </item>
    <item>
      <title>MyHeats v0.4</title>
      <link>https://write.in0rdr.ch/h1myheats-v0-4-h1</link>
      <description>&lt;![CDATA[I was hacking on a new version of the &#34;live-updating leaderboard&#34; with some bug fixes and a fresh new design (a href=&#34;https://code.in0rdr.ch/myheats/file/CHANGELOG.md.html&#34;CHANGELOG.md/a).&#xA;&#xA;a href=&#34;https://myheats.p0c.ch&#34;Project site/a&#xA;a href=&#34;https://myheats-demo.p0c.ch&#34;Demo instance/a&#xA;a href=&#34;https://code.in0rdr.ch/myheats&#34;Code/a&#xA;&#xA;#coding #sports #myheats&#xA;&#xA;!--more--&#xA;&#xA;I took advantage of the CSS flexbox layout to improve the design on mobile devices. The main idea was to reduce the table size to a minimum on small screen sizes. The individual cells of the table are shown as small boxes, with labels to indicate the content type.&#xA;&#xA;img src=&#34;https://code.in0rdr.ch/pub/blog/myheats1.jpg&#34; alt=&#34;myheats leaderboard mobile layout&#34; /&#xA;&#xA;On larger displays, for instance, a big screen at the finish line of a sport event/run, the table is displayed in a traditional fashion.&#xA;&#xA;img src=&#34;https://code.in0rdr.ch/pub/blog/myheats2.jpg&#34; alt=&#34;myheats leaderboard large display&#34; /&#xA;&#xA;Here some more screenshots of the admin section with the new design.&#xA;&#xA;The live-updating leaderboard:&#xA;img src=&#34;https://code.in0rdr.ch/pub/blog/myheats3.png&#34; alt=&#34;myheats leaderboard mobile view&#34; /&#xA;&#xA;The scoring section where judges can enter the scores. In this section, I found it important to indicate the loading status whenever the score is changed and to make the input field big enough for usage on small smartphone screens:&#xA;img src=&#34;https://code.in0rdr.ch/pub/blog/myheats4.png&#34; alt=&#34;myheats leaderboard mobile view scoring&#34; /&#xA;&#xA;Friends told me that similar software has a feature to &#34;lock&#34; the scores once the scoring is done. Currently, there exists no such feature in MyHeats, but that&#39;s a good idea for my next hacking sess.&#xA;&#xA;The section where event admins can create heats and manage the startlists for each run:&#xA;img src=&#34;https://code.in0rdr.ch/pub/blog/myheats5.png&#34; alt=&#34;myheats leaderboard mobile view startlist&#34; /&#xA;&#xA;The administration of the athletes:&#xA;img src=&#34;https://code.in0rdr.ch/pub/blog/myheats6.png&#34; alt=&#34;myheats leaderboard mobile view athletes&#34; /&#xA;&#xA;Athletes are not required to have a lastname. In this way, the leaderboard can equally well be used to score entire teams.&#xA;&#xA;And last but not least, if you are reading until down here and you are interested in the technical implementation and the arrangement of the data in the database, here the database schema:&#xA;img src=&#34;https://code.in0rdr.ch/pub/blog/myheats7.png&#34; alt=&#34;myheats leaderboard supabase database schema&#34; /&#xA;&#xA;Interested in test-driving the scoring backend for judges and event admins? Drop me a line (see footer).&#xA;&#xA;div style=&#34;text-align:center; font-size: 0.8em&#34;&#xD;&#xA;a href=&#34;https://write.in0rdr.ch/feed&#34;&amp;#128732; RSS/a | a href=&#34;https://m.in0rdr.ch/in0rdr&#34;&amp;#128024; Fediverse/a | a href=&#34;https://chat.in0rdr.ch/#/guest?join=p0c@conference.in0rdr.ch&#34;&amp;#128172; XMPP/a&#xD;&#xA;/div]]&gt;</description>
      <content:encoded><![CDATA[<p>I was hacking on a new version of the “live-updating leaderboard” with some bug fixes and a fresh new design (<a href="https://code.in0rdr.ch/myheats/file/CHANGELOG.md.html">CHANGELOG.md</a>).</p>
<ul><li><a href="https://myheats.p0c.ch">Project site</a></li>
<li><a href="https://myheats-demo.p0c.ch">Demo instance</a></li>
<li><a href="https://code.in0rdr.ch/myheats">Code</a></li></ul>

<p><a href="https://write.in0rdr.ch/tag:coding" class="hashtag"><span>#</span><span class="p-category">coding</span></a> <a href="https://write.in0rdr.ch/tag:sports" class="hashtag"><span>#</span><span class="p-category">sports</span></a> <a href="https://write.in0rdr.ch/tag:myheats" class="hashtag"><span>#</span><span class="p-category">myheats</span></a></p>



<p>I took advantage of the CSS flexbox layout to improve the design on mobile devices. The main idea was to reduce the table size to a minimum on small screen sizes. The individual cells of the table are shown as small boxes, with labels to indicate the content type.</p>

<p><img src="https://code.in0rdr.ch/pub/blog/myheats1.jpg" alt="myheats leaderboard mobile layout"/></p>

<p>On larger displays, for instance, a big screen at the finish line of a sport event/run, the table is displayed in a traditional fashion.</p>

<p><img src="https://code.in0rdr.ch/pub/blog/myheats2.jpg" alt="myheats leaderboard large display"/></p>

<p>Here some more screenshots of the admin section with the new design.</p>

<p>The live-updating leaderboard:
<img src="https://code.in0rdr.ch/pub/blog/myheats3.png" alt="myheats leaderboard mobile view"/></p>

<p>The scoring section where judges can enter the scores. In this section, I found it important to indicate the loading status whenever the score is changed and to make the input field big enough for usage on small smartphone screens:
<img src="https://code.in0rdr.ch/pub/blog/myheats4.png" alt="myheats leaderboard mobile view scoring"/></p>

<p>Friends told me that similar software has a feature to “lock” the scores once the scoring is done. Currently, there exists no such feature in MyHeats, but that&#39;s a good idea for my next hacking sess.</p>

<p>The section where event admins can create heats and manage the startlists for each run:
<img src="https://code.in0rdr.ch/pub/blog/myheats5.png" alt="myheats leaderboard mobile view startlist"/></p>

<p>The administration of the athletes:
<img src="https://code.in0rdr.ch/pub/blog/myheats6.png" alt="myheats leaderboard mobile view athletes"/></p>

<p>Athletes are not required to have a lastname. In this way, the leaderboard can equally well be used to score entire teams.</p>

<p>And last but not least, if you are reading until down here and you are interested in the technical implementation and the arrangement of the data in the database, here the database schema:
<img src="https://code.in0rdr.ch/pub/blog/myheats7.png" alt="myheats leaderboard supabase database schema"/></p>

<p>Interested in test-driving the scoring backend for judges and event admins? Drop me a line (see footer).</p>

<div style="text-align:center; font-size: 0.8em">
<a href="https://write.in0rdr.ch/feed">🛜 RSS</a> | <a href="https://m.in0rdr.ch/in0rdr">🐘 Fediverse</a> | <a href="https://chat.in0rdr.ch/#/guest?join=p0c@conference.in0rdr.ch">💬 XMPP</a>
</div>
]]></content:encoded>
      <guid>https://write.in0rdr.ch/h1myheats-v0-4-h1</guid>
      <pubDate>Sun, 02 Jul 2023 17:09:26 +0000</pubDate>
    </item>
    <item>
      <title>MyHeats Update: Manage Heats</title>
      <link>https://write.in0rdr.ch/myheats-update-manage-heats</link>
      <description>&lt;![CDATA[Extended my newest coding pet project in React with the ability to delete heats.&#xA;&#xA;🚀 Demo instance: https://myheats.in0rdr.ch&#xA;🧬 Code: https://code.in0rdr.ch/myheats&#xA;&#xA;Plan to work on some features the next few days:&#xA;Add/remove athletes (bulk)&#xA;Improve UI&#xA;&#xA;img src=&#34;https://code.in0rdr.ch/pub/blog/myheats-manage.png&#34; alt=&#34;manage-heats-myheats&#34; width=&#34;50%&#34; /&#xA;&#xA;#coding #sports #myheats&#xA;&#xA;!--more--&#xA;&#xA;For more background on the app, the idea and the problem(s) it could solve have a look at the original post a href=&#34;https://write.in0rdr.ch/live-heats-scoring-and-leaderboard-for-sport-events&#34;Live heats, Scoring and Leaderboard for Sport Events/a (March 11, 2023).&#xA;&#xA;hr /&#xA;&#xA;bedit 2023-04-04:/b Bug a href=&#34;https://code.in0rdr.ch/myheats/commit/6d58d104a4827c3fa9510f847d10d596c2286a75.html&#34;fixed/a in v0.2, thanks a href=&#34;https://github.com/JonnyBurger&#34;@JonnyBurger/a and @PatrickH!&#xA;&#xA;oh.. and, I might need your 🥺 help with a little 🐛 bug I encounter.&#xA;&#xA;⁉️ Anyone knows where this flickering comes from? Reproduce as follows:&#xA;ol&#xA;liAdd one heats to display/li&#xA;liRemove the heat again/li&#xA;liWait for the screen to update. Can you see that flickering?/li&#xA;/ol&#xA;&#xA;img src=&#34;https://code.in0rdr.ch/pub/blog/myheats-bug.gif&#34; alt=&#34;myheats-bug-flickering&#34; /&#xA;&#xA;How can I fix that? I&#39;m pretty new in that JavaScript business. If you know the fix, please shoot me a mail, ping me (see footer).&#xA;&#xA;The bug should be in a href=&#34;https://code.in0rdr.ch/myheats/file/src/Leaderboard.js.html&#34;Leaderboard.js/a somewhere.. Thanks for any hints!&#xA;&#xA;img src=&#34;https://media2.giphy.com/media/XjlNyeZp5lDri/giphy.gif?cid=ecf05e47bogzg24umo1f9ctjv19knprueeyzkr83vjfwypxn&amp;rid=giphy.gif&amp;ct=g&#34; /&#xA;&#xA;div style=&#34;text-align:center; font-size: 0.8em&#34;&#xD;&#xA;a href=&#34;https://write.in0rdr.ch/feed&#34;&amp;#128732; RSS/a | a href=&#34;https://m.in0rdr.ch/in0rdr&#34;&amp;#128024; Fediverse/a | a href=&#34;https://chat.in0rdr.ch/#/guest?join=p0c@conference.in0rdr.ch&#34;&amp;#128172; XMPP/a&#xD;&#xA;/div]]&gt;</description>
      <content:encoded><![CDATA[<p>Extended my newest coding pet project in React with the ability to delete heats.</p>
<ul><li>🚀 Demo instance: <a href="https://myheats.in0rdr.ch">https://myheats.in0rdr.ch</a></li>
<li>🧬 Code: <a href="https://code.in0rdr.ch/myheats">https://code.in0rdr.ch/myheats</a></li></ul>

<p>Plan to work on some features the next few days:
* Add/remove athletes (bulk)
* Improve UI</p>

<p><img src="https://code.in0rdr.ch/pub/blog/myheats-manage.png" alt="manage-heats-myheats" width="50%"/></p>

<p><a href="https://write.in0rdr.ch/tag:coding" class="hashtag"><span>#</span><span class="p-category">coding</span></a> <a href="https://write.in0rdr.ch/tag:sports" class="hashtag"><span>#</span><span class="p-category">sports</span></a> <a href="https://write.in0rdr.ch/tag:myheats" class="hashtag"><span>#</span><span class="p-category">myheats</span></a></p>



<p>For more background on the app, the idea and the problem(s) it could solve have a look at the original post <a href="https://write.in0rdr.ch/live-heats-scoring-and-leaderboard-for-sport-events">Live heats, Scoring and Leaderboard for Sport Events</a> (March 11, 2023).</p>

<hr/>

<p><b>edit 2023-04-04:</b> Bug <a href="https://code.in0rdr.ch/myheats/commit/6d58d104a4827c3fa9510f847d10d596c2286a75.html">fixed</a> in v0.2, thanks <a href="https://github.com/JonnyBurger">@JonnyBurger</a> and @PatrickH!</p>

<p>oh.. and, I might need your 🥺 help with a little 🐛 bug I encounter.</p>

<p>⁉️ Anyone knows where this flickering comes from? Reproduce as follows:
<ol><li>Add one heats to display</li>
<li>Remove the heat again</li>
<li>Wait for the screen to update. Can you see that flickering?</li></ol></p>

<p><img src="https://code.in0rdr.ch/pub/blog/myheats-bug.gif" alt="myheats-bug-flickering"/></p>

<p>How can I fix that? I&#39;m pretty new in that JavaScript business. If you know the fix, please shoot me a mail, ping me (see footer).</p>

<p>The bug should be in <a href="https://code.in0rdr.ch/myheats/file/src/Leaderboard.js.html">Leaderboard.js</a> somewhere.. Thanks for any hints!</p>

<p><img src="https://media2.giphy.com/media/XjlNyeZp5lDri/giphy.gif?cid=ecf05e47bogzg24umo1f9ctjv19knprueeyzkr83vjfwypxn&amp;rid=giphy.gif&amp;ct=g"/></p>

<div style="text-align:center; font-size: 0.8em">
<a href="https://write.in0rdr.ch/feed">🛜 RSS</a> | <a href="https://m.in0rdr.ch/in0rdr">🐘 Fediverse</a> | <a href="https://chat.in0rdr.ch/#/guest?join=p0c@conference.in0rdr.ch">💬 XMPP</a>
</div>
]]></content:encoded>
      <guid>https://write.in0rdr.ch/myheats-update-manage-heats</guid>
      <pubDate>Fri, 31 Mar 2023 23:00:47 +0000</pubDate>
    </item>
    <item>
      <title>Live heats, Scoring and Leaderboard for Sport Events</title>
      <link>https://write.in0rdr.ch/live-heats-scoring-and-leaderboard-for-sport-events</link>
      <description>&lt;![CDATA[After last weeks a href=&#34;https://www.rtr.ch/play/tv/redirect/detail/d4c565ac-ad0e-4a02-825d-f8f02b55529e&#34;Bündner Championship/a I found start list generation and ranking process to be error prone, especially for the disciplines that are judged by several experts, such as slopestyle (Ski or Snowboard) or the formation skiing teams. We were editing Excel sheets (stored on OneDrive) and printing the new start lists for the next runs. Just imagine the printer 🖨️ on the mountain 🏔️🤣. The grades of the experts/judges needed to be transferred from paper into Excel after every completed run or heat.&#xA;&#xA;#coding #sports #myheats&#xA;&#xA;!--more--&#xA;&#xA;I detected the following deficiencies which could be improved with a more digital approach (digital/mobile from the start):&#xA;ul&#xA;liMistakes can occur while copying ratings and numbers from paper into Excel/li&#xA;liSorting each Excel list (for every competition and run) manually is error prone, especially, with multiple competitions going on simultaneously (slopestyle ski or snowboard, formation skiing, men, women, several runs, etc.)/li&#xA;liThe results need to be printed on paper and published on the a href=&#34;https://skischule-savognin.ch/DE/winter/bm.html&#34;website (PDF)/a/li&#xA;liFormatting and printing these sheets (for each competition) takes quite some time and the result is not always consistent (in terms of formatting)/li&#xA;liThere are no live results. This is also not convenient for the athletes that want to know right after the run how they scored and how they rank in the competition. Ideally, the athletes get to know their scores right after they cross the finish line./li&#xA;liOneDrive syncing did not always work reliably (depending on the end users devices) and mailing the sheets was a pain/li&#xA;/ul&#xA;&#xA;I was looking into some existing solutions for systems to process the data, such as a href=&#34;https://liveheats.com&#34;liveheats/a. However, since most of these systems are finished products and there is no open source code available that I know of, I decided to quickly draft my own version of a live leaderboard for these kind of sport competitions.&#xA;&#xA;🚀 Demo instance: https://myheats.p0c.ch&#xA;&#xA;🧬 Code: https://code.in0rdr.ch/myheats&#xA;&#xA;Right now, there is only the leaderboard which automatically updates as soon as new data arrives in a href=&#34;https://supabase.com&#34;Supabase/a. During the process of creating this first draft, I became a huge fan of Supabase because it works really well. It is the open source Firebase alternative with Postgres database in the backend. I can highly recommend it for your next project, because it is extremely easy to get started with.&#xA;&#xA;For the purpose of the leaderboard, I was especially interested in the a href=&#34;https://supabase.com/realtime&#34;realtime aspects of Supabase/a. This allows me to update the table as soon as new data is entered in the backend (by the judges of the comptition).&#xA;&#xA;I&#39;m planning to update the code with some more features soonish, among others:&#xA;&#xA;ul&#xA;liAuthenticaiton for the experts that perform the ranking and rating/li&#xA;liA very simple user-interface or view for the experts to edit the score for a single athlete of a heat or run/li&#xA;liBetter import/export functionality for start list and rankings/li&#xA;/ul&#xA;&#xA;Stay tuned. If you have some input or tips feel free to drop me a line (see footer).&#xA;&#xA;div style=&#34;text-align:center; font-size: 0.8em&#34;&#xD;&#xA;a href=&#34;https://write.in0rdr.ch/feed&#34;&amp;#128732; RSS/a | a href=&#34;https://m.in0rdr.ch/in0rdr&#34;&amp;#128024; Fediverse/a | a href=&#34;https://chat.in0rdr.ch/#/guest?join=p0c@conference.in0rdr.ch&#34;&amp;#128172; XMPP/a&#xD;&#xA;/div]]&gt;</description>
      <content:encoded><![CDATA[<p>After last weeks <a href="https://www.rtr.ch/play/tv/redirect/detail/d4c565ac-ad0e-4a02-825d-f8f02b55529e">Bündner Championship</a> I found start list generation and ranking process to be error prone, especially for the disciplines that are judged by several experts, such as slopestyle (Ski or Snowboard) or the formation skiing teams. We were editing Excel sheets (stored on OneDrive) and printing the new start lists for the next runs. Just imagine the printer 🖨️ on the mountain 🏔️🤣. The grades of the experts/judges needed to be transferred from paper into Excel after every completed run or heat.</p>

<p><a href="https://write.in0rdr.ch/tag:coding" class="hashtag"><span>#</span><span class="p-category">coding</span></a> <a href="https://write.in0rdr.ch/tag:sports" class="hashtag"><span>#</span><span class="p-category">sports</span></a> <a href="https://write.in0rdr.ch/tag:myheats" class="hashtag"><span>#</span><span class="p-category">myheats</span></a></p>



<p>I detected the following deficiencies which could be improved with a more digital approach (digital/mobile from the start):
<ul><li>Mistakes can occur while copying ratings and numbers from paper into Excel</li>
<li>Sorting each Excel list (for every competition and run) manually is error prone, especially, with multiple competitions going on simultaneously (slopestyle ski or snowboard, formation skiing, men, women, several runs, etc.)</li>
<li>The results need to be printed on paper and published on the <a href="https://skischule-savognin.ch/DE/winter/bm.html">website (PDF)</a></li>
<li>Formatting and printing these sheets (for each competition) takes quite some time and the result is not always consistent (in terms of formatting)</li>
<li>There are no live results. This is also not convenient for the athletes that want to know right after the run how they scored and how they rank in the competition. Ideally, the athletes get to know their scores right after they cross the finish line.</li>
<li>OneDrive syncing did not always work reliably (depending on the end users devices) and mailing the sheets was a pain</li></ul></p>

<p>I was looking into some existing solutions for systems to process the data, such as <a href="https://liveheats.com">liveheats</a>. However, since most of these systems are finished products and there is no open source code available that I know of, I decided to quickly draft my own version of a live leaderboard for these kind of sport competitions.</p>

<p>🚀 Demo instance: <a href="https://myheats.p0c.ch">https://myheats.p0c.ch</a></p>

<p>🧬 Code: <a href="https://code.in0rdr.ch/myheats">https://code.in0rdr.ch/myheats</a></p>

<p>Right now, there is only the leaderboard which automatically updates as soon as new data arrives in <a href="https://supabase.com">Supabase</a>. During the process of creating this first draft, I became a huge fan of Supabase because it works really well. It is the open source Firebase alternative with Postgres database in the backend. I can highly recommend it for your next project, because it is extremely easy to get started with.</p>

<p>For the purpose of the leaderboard, I was especially interested in the <a href="https://supabase.com/realtime">realtime aspects of Supabase</a>. This allows me to update the table as soon as new data is entered in the backend (by the judges of the comptition).</p>

<p>I&#39;m planning to update the code with some more features soonish, among others:</p>

<ul><li>Authenticaiton for the experts that perform the ranking and rating</li>
<li>A very simple user-interface or view for the experts to edit the score for a single athlete of a heat or run</li>
<li>Better import/export functionality for start list and rankings</li></ul>

<p>Stay tuned. If you have some input or tips feel free to drop me a line (see footer).</p>

<div style="text-align:center; font-size: 0.8em">
<a href="https://write.in0rdr.ch/feed">🛜 RSS</a> | <a href="https://m.in0rdr.ch/in0rdr">🐘 Fediverse</a> | <a href="https://chat.in0rdr.ch/#/guest?join=p0c@conference.in0rdr.ch">💬 XMPP</a>
</div>
]]></content:encoded>
      <guid>https://write.in0rdr.ch/live-heats-scoring-and-leaderboard-for-sport-events</guid>
      <pubDate>Sat, 11 Mar 2023 08:40:19 +0000</pubDate>
    </item>
  </channel>
</rss>