<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
	<title>Karl Bartel's Website</title>
	<link href="https://www.karl.berlin/atom.xml" rel="self" />
	<link href="https://www.karl.berlin/" rel="alternate" />
	<updated>2026-03-01T10:56:02+01:00</updated>
	<author>
		<name>Karl Bartel</name>
	</author>
	<id>tag:www.karl.berlin,2020-05-23:default-atom-feed</id>
	<entry>
		<title>Can We Make Simpler Software With LLMs?</title>
		<content type="html">&lt;h1&gt;Can We Make Simpler Software With LLMs?&lt;/h1&gt;
&lt;p&gt;Growing numbers of abstraction layers, vast amounts of dependencies and general complexity are what annoy me most about modern software development. LLMs are powerful tools, but in practice they often lead to even larger, more complex projects with even more dependencies. Can we use them for the opposite: small, simple software without dependencies? I tried this recently when I wanted a specific piece of software to do simple calculations inside short texts. Not all the approaches I tried are ones I&#39;m convinced are a good idea, but I intentionally wanted to experiment a bit.&lt;/p&gt;
&lt;h2&gt;The Project&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/karlb/calced&quot;&gt;calced&lt;/a&gt; is a notepad calculator. You write natural-looking text with math in it, and calced evaluates the expressions and appends results. It supports things like variables, percentages (&lt;code&gt;200 + 15%&lt;/code&gt;), unit conversions (&lt;code&gt;100 km in miles&lt;/code&gt;), date arithmetic (&lt;code&gt;2025-01-15 + 3 weeks&lt;/code&gt;), different number formats and custom conversion rates.&lt;/p&gt;
&lt;p&gt;I wanted two implementations: a Python CLI to work on local files and a web version. The typical approach today would involve TypeScript, a bundler and lots of npm dependencies for the web version; maybe Pyodide to run the Python version in the browser and some React UI around it. Instead, I tried to use the LLM to do it in a more primitive way without spending a lot of time handcrafting code I don&#39;t care too much about.&lt;/p&gt;
&lt;h2&gt;No Dependency Management&lt;/h2&gt;
&lt;p&gt;The web version uses &lt;a href=&quot;https://github.com/MikeMcl/big.js/&quot;&gt;big.js&lt;/a&gt; for arbitrary-precision math. Instead of using npm and a bundler, I had the LLM inline the library directly into the HTML file. Same for the logo SVG. The result is a single 52KB HTML file that works offline.&lt;/p&gt;
&lt;p&gt;The Python CLI is even simpler: a single 47KB file using only the standard library. Although I prefer &lt;code&gt;click&lt;/code&gt; for the nicer API, I used &lt;code&gt;argparse&lt;/code&gt; instead, since the LLM can easily generate the more verbose but dependency-free version. The same is true for many similar decisions.&lt;/p&gt;
&lt;h2&gt;No Build System&lt;/h2&gt;
&lt;p&gt;With dependencies either inlined or avoided, there is nothing left that needs a build system. No TypeScript, no bundler, no transpiler, no node_modules, no minifier. Just a single HTML file with plain JavaScript for the web version. For small projects, TypeScript is not necessary anyway, but when the LLM writes most of the code, the benefit becomes tiny (at least for the user, it might help the LLM). So let&#39;s just use JS directly! And without big dependencies the code is small enough to serve as is.&lt;/p&gt;
&lt;p&gt;Both the JS and the Python versions can be run exactly as they are stored in version control, even when downloading just that single file.&lt;/p&gt;
&lt;h2&gt;Two Implementations Without Transpilation&lt;/h2&gt;
&lt;p&gt;Instead of running the Python implementation in the browser via Pyodide or transpiling one version into the other, I simply asked the LLM to write and maintain both versions. To make this work, I needed a proper test setup: 18 markdown files serve as &lt;a href=&quot;testing-with-diff.html&quot;&gt;integration tests using &lt;code&gt;git diff&lt;/code&gt;&lt;/a&gt;, where calced processes them and the test verifies the output has not changed. Both implementations run against the same files, so any behavioral difference between Python and JavaScript is caught immediately. Shared JSON fixtures cover additional cases that don&#39;t fit this specific testing pattern. As long as the test fixtures are shared and cover enough of the functionality, I can use the LLM to keep both versions in sync.&lt;/p&gt;
&lt;p&gt;Another (probably more sane) approach would have been to use JS for the CLI version too. But&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I personally prefer python&lt;/li&gt;
&lt;li&gt;Python has a better stdlib, requiring fewer dependencies&lt;/li&gt;
&lt;li&gt;the UI/CLI part needs to be done separately anyway&lt;/li&gt;
&lt;li&gt;having implementations in different languages prevents me from accidentally relying on that language&#39;s specifics in my math syntax&lt;/li&gt;
&lt;li&gt;it wouldn&#39;t allow me to try out this &quot;keep two implementations in sync with LLM&quot; approach (let me have some fun!)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Avoid Needless Inconsistency&lt;/h2&gt;
&lt;p&gt;When you write something from scratch, it is easy to accidentally deviate from established conventions. I had the LLM check the CLI against the excellent &lt;a href=&quot;https://clig.dev/&quot;&gt;clig.dev&lt;/a&gt; and compare behavior with existing tools like &lt;a href=&quot;https://soulver.app/&quot;&gt;Soulver&lt;/a&gt;, &lt;a href=&quot;https://numi.app/&quot;&gt;Numi&lt;/a&gt; and &lt;a href=&quot;https://numbr.dev/&quot;&gt;Numbr&lt;/a&gt;, adopting the same conventions where I had no strong preference. Better consistency is not simpler in a direct technical way, but usually makes the user&#39;s life simpler, so I&#39;ll still count it towards this goal.&lt;/p&gt;
&lt;h2&gt;Verdict&lt;/h2&gt;
&lt;p&gt;As intended, the result is small and simple compared to similar projects: a 52KB HTML file, a 47KB Python file, a 62-line Makefile and no trace of the npm ecosystem. The really interesting part will be to see how maintainable this is over a longer time frame. But I&#39;m optimistic that at least some of the approaches are genuinely good ideas (e.g. letting the LLM check against clig.dev and comparing with similar projects).&lt;/p&gt;
&lt;p&gt;I might have created something technically better if I wrote it by hand, but realistically, I just would not have written it at all. And when most people use LLMs to quickly generate vast amounts of code, it is nice to see that you can also use them to generate less in some cases.&lt;/p&gt;</content>
		<link href="https://www.karl.berlin/simplicity-by-llm.html"/>
		<id>tag:www.karl.berlin,2026-02-28:posts/simplicity-by-llm.md</id>
		<published>2026-02-28T19:20:48+01:00</published>
		<updated>2026-02-28T19:20:48+01:00</updated>
	</entry>
	<entry>
		<title>Raising Notifications From Terminal</title>
		<content type="html">&lt;h1&gt;Raising Notifications From Terminal&lt;/h1&gt;
&lt;p&gt;When executing long-running jobs in the terminal, it&#39;s useful to get notified when they complete so you can do other things while waiting. Here are a few ways to achieve this.&lt;/p&gt;
&lt;h2&gt;Using notify-send (Linux)&lt;/h2&gt;
&lt;p&gt;The simplest approach is to chain your command with &lt;code&gt;notify-send&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;slow-job; notify-send &amp;quot;done&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;If You Already Started the Job&lt;/h2&gt;
&lt;p&gt;If you&#39;ve already started a long-running job and forgot to add a notification, you can still do it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Press &lt;code&gt;Ctrl-Z&lt;/code&gt; to suspend the job and put it in the background&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;fg; notify-send &amp;quot;done&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The job will resume in the foreground, and you&#39;ll get notified when it finishes.&lt;/p&gt;
&lt;h2&gt;Notify on Success or Failure&lt;/h2&gt;
&lt;p&gt;Using &lt;code&gt;;&lt;/code&gt; to chain commands will always raise the notification, regardless of whether the job succeeded or failed. You can use &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; and &lt;code&gt;||&lt;/code&gt; to be more selective:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;slow-job &amp;amp;&amp;amp; notify-send &amp;quot;success&amp;quot;   # only on success
slow-job || notify-send &amp;quot;failed&amp;quot;    # only on failure
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This also works when adding the notification after backgrounding the process with Ctrl-Z:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;fg || notify-send &amp;quot;failed&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Using osascript (macOS)&lt;/h2&gt;
&lt;p&gt;On macOS, you can use &lt;code&gt;osascript&lt;/code&gt; instead:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;slow-job; osascript -e &#39;display notification &amp;quot;done&amp;quot; with title &amp;quot;Terminal&amp;quot;&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&#39;ve put this osascript command (with a &lt;code&gt;$1&lt;/code&gt; instead of &lt;code&gt;done&lt;/code&gt;) into a small script called &lt;code&gt;notify-send&lt;/code&gt;, so that I can use the same command on both Linux and macOS. Since I don&#39;t use more than a single text parameter for my notifications, that works well for me.&lt;/p&gt;
&lt;h2&gt;The Terminal Bell&lt;/h2&gt;
&lt;p&gt;An interesting alternative is ringing the terminal bell:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;slow-job; echo -e &#39;\a&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Many terminal emulators show some kind of notification or badge when the bell rings while the terminal is in the background. The great thing about this approach is that it works across many platforms and even through SSH sessions!&lt;/p&gt;
&lt;h2&gt;OSC 777&lt;/h2&gt;
&lt;p&gt;Some terminals support the (highly underdocumented) OSC 777 escape sequence for desktop notifications:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;slow-job; printf &#39;\x1b]777;notify;Command;Job finished\x1b\\&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just like the terminal bell, this emits an escape sequence that is interpreted by the terminal emulator, but is made specifically for raising desktop notifications. So it keeps the terminal bell&#39;s advantages of not requiring external tools and working through SSH. Terminals that support this include foot, Ghostty, WezTerm and some others.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://sw.kovidgoyal.net/kitty/&quot;&gt;Kitty&lt;/a&gt; has its own &lt;a href=&quot;https://sw.kovidgoyal.net/kitty/desktop-notifications/&quot;&gt;notification protocol&lt;/a&gt; with advanced features like icons, buttons, and urgency levels. It comes with a convenient &lt;a href=&quot;https://sw.kovidgoyal.net/kitty/kittens/notify/&quot;&gt;notify kitten&lt;/a&gt; that wraps this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;slow-job; kitten notify &amp;quot;Done&amp;quot; &amp;quot;Job finished&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Closing Thoughts&lt;/h2&gt;
&lt;p&gt;For quick local work, &lt;code&gt;notify-send&lt;/code&gt; or &lt;code&gt;osascript&lt;/code&gt; are straightforward and provided by the system. The terminal bell is the most portable option. OSC 777 gives you proper notifications without external tools. The last two also work through SSH. In the long run, I&#39;m hoping for proper standardization, documentation and widespread support of OSC 777, but for now I can&#39;t blindly recommend a single approach, so this blog post seems necessary.&lt;/p&gt;</content>
		<link href="https://www.karl.berlin/terminal-notifications.html"/>
		<id>tag:www.karl.berlin,2026-01-17:posts/terminal-notifications.md</id>
		<published>2026-01-17T14:01:35+01:00</published>
		<updated>2026-01-17T14:01:35+01:00</updated>
	</entry>
	<entry>
		<title>Stack Traces are Underrated</title>
		<content type="html">&lt;h1&gt;Stack Traces are Underrated&lt;/h1&gt;
&lt;h2&gt;I Love Stack Traces&lt;/h2&gt;
&lt;p&gt;When something goes wrong in a program, many languages will spit out a stack trace with lots of useful information. Here&#39;s an example from one of my Python programs:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Traceback (most recent call last):
  File &amp;quot;/home/piku/.piku/envs/wikdict/lib/python3.11/site-packages/flask/app.py&amp;quot;, line 1511, in wsgi_app
    response = self.full_dispatch_request()
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &amp;quot;/home/piku/.piku/envs/wikdict/lib/python3.11/site-packages/flask/app.py&amp;quot;, line 919, in full_dispatch_request
    rv = self.handle_user_exception(e)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &amp;quot;/home/piku/.piku/envs/wikdict/lib/python3.11/site-packages/flask/app.py&amp;quot;, line 917, in full_dispatch_request
    rv = self.dispatch_request()
         ^^^^^^^^^^^^^^^^^^^^^^^
  File &amp;quot;/home/piku/.piku/envs/wikdict/lib/python3.11/site-packages/flask/app.py&amp;quot;, line 902, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &amp;quot;/home/piku/.piku/apps/wikdict/wikdict_web/lookup.py&amp;quot;, line 119, in lookup
    if r := get_combined_result(lang, other_lang, query):
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &amp;quot;/home/piku/.piku/apps/wikdict/wikdict_web/lookup.py&amp;quot;, line 91, in get_combined_result
    conn = get_conn(lang + &amp;quot;-&amp;quot; + other_lang)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &amp;quot;/home/piku/.piku/apps/wikdict/wikdict_web/base.py&amp;quot;, line 103, in get_conn
    raise sqlite3.OperationalError(
sqlite3.OperationalError: Database file &amp;quot;/home/piku/.piku/data/wikdict/dict/en-ko.sqlite3&amp;quot; does not exist
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Beautiful! In addition to the error, I immediately see the line that
caused the error, the full call stack and I have the file paths and line
numbers at hand to &lt;a href=&quot;https://github.com/karlb/vim-pytest-traceback&quot;&gt;quickly jump to each of the locations&lt;/a&gt;.
Often, this is all I need to fix the problem.&lt;/p&gt;
&lt;h2&gt;The Case of the Missing Trace&lt;/h2&gt;
&lt;p&gt;Stack traces are already a common debugging tool for decades, but unfortunately they are not ubiquitous and in some cases, they even disappear where we used to have them.
Let&#39;s take a look at a few of these cases.&lt;/p&gt;
&lt;h3&gt;Modern Error Handling&lt;/h3&gt;
&lt;p&gt;Many people dislike exceptions since they break the usual control flow and make it easy to skip proper error handling. So instead of raising exceptions, they return errors as special return values. This was always the case in C because it doesn&#39;t have exceptions, but also more modern languages like Go and Rust do this, although in a nicer way. But this approach does not yield stack traces since the functions return normally, just with different values. So instead of the beautiful stack trace above, Go will give you&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wrong number of elements
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or, if the author was diligent and added context (preferably with &lt;code&gt;fmt.Errorf&lt;/code&gt;&#39;s &lt;code&gt;%w&lt;/code&gt; format string) each time the error is passed along, something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;can&#39;t load data: failed to parse header: wrong number of elements
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While these prefixes resemble a poor man&#39;s stack trace, they&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;don&#39;t show the file and line number&lt;/li&gt;
&lt;li&gt;don&#39;t show the code&lt;/li&gt;
&lt;li&gt;can be ambiguous (the same error message or prefix could be used in multiple places in the source)&lt;/li&gt;
&lt;li&gt;can&#39;t be trusted to be constructed consistently&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The situation in Rust is similar, where errors are also passed as return values, just with more interesting typing than in Go. But Rust has a better workaround to create stack traces: the &lt;a href=&quot;https://doc.rust-lang.org/std/backtrace/index.html&quot;&gt;&lt;code&gt;backtrace&lt;/code&gt; module&lt;/a&gt;, which allows capturing stack traces that you can then add to the errors you return. The main problem with this approach is that you still have to add the stack trace to each error and also trust library authors to do so.&lt;/p&gt;
&lt;h3&gt;More Complicated Architectures&lt;/h3&gt;
&lt;p&gt;Not only modern programming languages, but also modern architectures can be a threat to good error traces.
More and more often, a single system is split up across many programs, often under labels like microservices, containers or serverless functions.
This allows using different programming languages between components, better isolation and scalability.
But in addition to making everything a lot more complex, it breaks stack traces!
If you have an HTTP call between your functions, you will have to put a large amount of work into your tooling to get something nearly as useful and consistent as Python&#39;s default stack traces. Most people don&#39;t and even fewer succeed at doing that.&lt;/p&gt;
&lt;h2&gt;Closing Thoughts&lt;/h2&gt;
&lt;p&gt;So while I can understand that not everybody likes exceptions, I don&#39;t see any reasons against having stack traces. Collecting the traces can cost some performance, but that is a small price to pay for the advantages and could even be turned off in production builds.
I&#39;m really surprised that so few people complain about the lack of stack traces in some modern programming languages and ecosystems. Are they just not used to having them so that they don&#39;t miss them? Or are they heavily relying on other tools that mitigate the problem (e.g. debuggers)?
Please let me know your thoughts on this!&lt;/p&gt;</content>
		<link href="https://www.karl.berlin/stacktraces.html"/>
		<id>tag:www.karl.berlin,2025-03-08:posts/stacktraces.md</id>
		<published>2025-03-08T09:41:27+01:00</published>
		<updated>2025-03-08T09:41:27+01:00</updated>
	</entry>
	<entry>
		<title>Easily Entering Umlauts With a US Keyboard Layout</title>
		<content type="html">&lt;h1&gt;Easily Entering Umlauts With a US Keyboard Layout&lt;/h1&gt;
&lt;p&gt;As a software developer, a US keyboard layout is great for entering all the special characters you need while coding and for using certain applications like vim. But as a German speaker, I also frequently need to enter German characters that don&#39;t exist in English: the Umlauts äÄöÖüÜ and ß. I want to do this without giving up the US layout and without having to press complicated key combinations (no dead keys, no unintuitive combinations).&lt;/p&gt;
&lt;p&gt;My goal is to get the German characters by holding right alt (&quot;Alt Gr&quot;) while pressing the respective Latin character. E.g. ralt+a =&amp;gt; ä.&lt;/p&gt;
&lt;h2&gt;Previous Approach: xmodmap&lt;/h2&gt;
&lt;p&gt;For the last years, I used the following &lt;code&gt;.Xmodmap&lt;/code&gt; file, which could be applied by running &lt;code&gt;xmodmap .Xmodmap&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-keycode 108 = Mode_switch Alt_R&quot;&gt;keycode 39 = s S ssharp
keycode 38 = a A adiaeresis Adiaeresis
keycode 30 = u U udiaeresis Udiaeresis
keycode 32 = o O odiaeresis Odiaeresis
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This worked, but had two downsides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It only works with Xorg, but not with Wayland.&lt;/li&gt;
&lt;li&gt;The applied modmap got lost regularly (related to suspend or screen locking?), so I had to rerun &lt;code&gt;xmodmap&lt;/code&gt; frequently.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;New approach: xkb&lt;/h2&gt;
&lt;p&gt;Fortunately, there is a way that (despite its name) works for both Wayland and Xorg: setting up a user specific xkb configuration. Based on a &lt;a href=&quot;http://who-t.blogspot.com/2020/09/user-specific-xkb-configuration-putting.html&quot;&gt;detailed blog post&lt;/a&gt; and a &lt;a href=&quot;http://who-t.blogspot.com/2020/09/user-specific-xkb-configuration-putting.html&quot;&gt;github comment&lt;/a&gt;, I created the following configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;File: .config/xkb/rules/evdev.xml
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;
&amp;lt;!DOCTYPE xkbConfigRegistry SYSTEM &amp;quot;xkb.dtd&amp;quot;&amp;gt;
&amp;lt;xkbConfigRegistry version=&amp;quot;1.1&amp;quot;&amp;gt;
  &amp;lt;layoutList&amp;gt;
    &amp;lt;layout&amp;gt;
      &amp;lt;configItem&amp;gt;
        &amp;lt;name&amp;gt;us&amp;lt;/name&amp;gt;
      &amp;lt;/configItem&amp;gt;
      &amp;lt;variantList&amp;gt;
        &amp;lt;variant&amp;gt;
          &amp;lt;configItem&amp;gt;
            &amp;lt;name&amp;gt;umlaut&amp;lt;/name&amp;gt;
            &amp;lt;shortDescription&amp;gt;umlaut&amp;lt;/shortDescription&amp;gt;
            &amp;lt;description&amp;gt;English (US, international with German umlauts)&amp;lt;/description&amp;gt;
          &amp;lt;/configItem&amp;gt;
        &amp;lt;/variant&amp;gt;
      &amp;lt;/variantList&amp;gt;
    &amp;lt;/layout&amp;gt;
  &amp;lt;/layoutList&amp;gt;
&amp;lt;/xkbConfigRegistry&amp;gt;

File: .config/xkb/symbols/us
partial alphanumeric_keys
xkb_symbols &amp;quot;umlaut&amp;quot; {
    include &amp;quot;us(altgr-intl)&amp;quot;
    include &amp;quot;level3(caps_switch)&amp;quot;
    name[Group1] = &amp;quot;English (US, international with German umlaut)&amp;quot;;
    key &amp;lt;AD03&amp;gt; { [ e, E, EuroSign, cent ] };
    key &amp;lt;AD07&amp;gt; { [ u, U, udiaeresis, Udiaeresis ] };
    key &amp;lt;AD09&amp;gt; { [ o, O, odiaeresis, Odiaeresis ] };
    key &amp;lt;AC01&amp;gt; { [ a, A, adiaeresis, Adiaeresis ] };
    key &amp;lt;AC02&amp;gt; { [ s, S, ssharp ] };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you prefer, you can also find the files in &lt;a href=&quot;https://github.com/karlb/dotfiles/tree/master/.config/xkb&quot;&gt;my dotfiles repo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After restarting your desktop environment, you can select the keyboard layout and start using it. Under GNOME, you&#39;ll find it under Settings -&amp;gt; Keyboard -&amp;gt; Input Sources -&amp;gt; + -&amp;gt; English.&lt;/p&gt;
&lt;h2&gt;Alternatives&lt;/h2&gt;
&lt;p&gt;Recent versions of xkeyboard-config include a &lt;a href=&quot;https://gitlab.freedesktop.org/xkeyboard-config/xkeyboard-config/-/blob/master/symbols/us#L2241-2262&quot;&gt;&quot;de_se_fi&quot; variant for the us layout&lt;/a&gt;, that should give you the same results as my approach above.&lt;/p&gt;
&lt;p&gt;There&#39;s also the &lt;a href=&quot;https://altgr-weur.eu/&quot;&gt;AltGr-wEur&lt;/a&gt; keyboard layout, which fits the Danish, Dutch, Finnish, French, German, Italian, Norwegian, Portuguese, Spanish and Swedish characters all into a single keyboard layout without using dead keys. Combining all of these languages requires compromises, which results in the German ß not being on the S key but on number 8 key. If that&#39;s ok for you, this layout is a good choice.&lt;/p&gt;</content>
		<link href="https://www.karl.berlin/umlauts.html"/>
		<id>tag:www.karl.berlin,2024-08-29:posts/umlauts.md</id>
		<published>2024-08-29T11:58:59+02:00</published>
		<updated>2026-02-15T14:33:18+01:00</updated>
	</entry>
	<entry>
		<title>Consistent Handling of Git Repositories With Different Default Branches</title>
		<content type="html">&lt;h1&gt;Consistent Handling of Git Repositories With Different Default Branches&lt;/h1&gt;
&lt;h2&gt;Why Do I Care About Default Branches?&lt;/h2&gt;
&lt;p&gt;Typically, I develop on a feature branch, and when the feature is ready, there is an obvious branch into which I want to merge the branch. This branch is often called &lt;code&gt;master&lt;/code&gt;, &lt;code&gt;main&lt;/code&gt; or &lt;code&gt;develop&lt;/code&gt;. During development, I will occasionally rebase my feature branch on top of this branch, to reduce future merge conflicts, and to avoid falling behind on changes merged by other developers. I will call this branch the default branch from here on.&lt;/p&gt;
&lt;p&gt;To make common operations consistent across repositories, I want to use a &lt;code&gt;git default-branch&lt;/code&gt; command that returns the name of the default branch, so that I can use commands like&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Rebase on latest version of the default branch
git fetch origin $(git default-branch) &amp;amp;&amp;amp; git rebase origin/$(git default-branch)

# Switch to the default branch and bring it up to date
git switch $(git default-branch) &amp;amp;&amp;amp; pull

# Absorb staged changes into the respective commits since branching off of the default branch
git absorb --base (git merge-base HEAD $(git default-branch))
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;How to Determine the Default Branch&lt;/h2&gt;
&lt;p&gt;What I consider the default branch in the examples above is the HEAD of the remote repository (usually on GitHub). I use the remote name &lt;code&gt;origin&lt;/code&gt; for the remote containing the latest version of the default branch. If your naming is different, adapt the commands accordingly.&lt;/p&gt;
&lt;p&gt;The command &lt;code&gt;remote show origin&lt;/code&gt; provides all relevant information about the remote, including the HEAD branch which can be filtered out with &lt;code&gt;git remote show origin | sed -n &#39;/HEAD branch/s/.*: //p&#39;&lt;/code&gt;. The downside of this approach is that it queries the remote, so it will take quite a long time due to network latency, and will only work if you are online.&lt;/p&gt;
&lt;p&gt;Fortunately there is a local representation of this information available via &lt;code&gt;git symbolic-ref refs/remotes/origin/HEAD&lt;/code&gt;, but it may be out of date or unavailable (in which case you will see the error &lt;code&gt;fatal: ref refs/remotes/origin/HEAD is not a symbolic ref&lt;/code&gt;). To update it, run &lt;code&gt;git remote set-head origin --auto&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Implementing the &lt;code&gt;default-branch&lt;/code&gt; Command&lt;/h2&gt;
&lt;p&gt;Since I want to neither memorize nor type out these commands all the time, I package them up in two git aliases in my &lt;code&gt;.gitconfig&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[alias]
	default-branch = &amp;quot;!git symbolic-ref refs/remotes/origin/HEAD --short | sed &#39;s|origin/||&#39;&amp;quot;
	update-default-branch = remote set-head origin --auto
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can update the information with &lt;code&gt;git update-default-branch&lt;/code&gt; and then use the &lt;code&gt;git default-branch&lt;/code&gt; as shown in examples at the top or however else you like.&lt;/p&gt;
&lt;p&gt;If you prefer the slow, always up-to-date version with online requirement, use the following alias instead:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[alias]
	default-branch = &amp;quot;!git remote show origin | sed -n &#39;/HEAD branch/s/.*: //p&#39;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Q&amp;amp;A&lt;/h2&gt;
&lt;blockquote&gt;&lt;p&gt;The remote names differ across my repositories, so I can&#39;t put the alias with an &lt;code&gt;origin&lt;/code&gt; remote into my gitconfig?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you want the same alias to work across all repos, you will have to set up consistent branch names.&lt;/p&gt;
&lt;p&gt;Let&#39;s assume &lt;code&gt;origin&lt;/code&gt; always points to your personal github repo, but for some repos your default branch lives in you repo while in other cases, your repo is a fork and your default branch lives in the &lt;code&gt;upstream&lt;/code&gt; remote. In that case I suggest to add an &lt;code&gt;upstream&lt;/code&gt; remote to all repos (even if it will be identical to &lt;code&gt;origin&lt;/code&gt; when your repo contains the default branch). This allows you to use &lt;code&gt;upstream&lt;/code&gt; in the alias and have your alias work across all repos again.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Why don&#39;t you create a local branch called &lt;code&gt;default&lt;/code&gt; that points to the default branch on the remote?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This would also be a valid solution, but I am easily confused when local branch names don&#39;t match remote branch names.&lt;/p&gt;</content>
		<link href="https://www.karl.berlin/git-default-branch.html"/>
		<id>tag:www.karl.berlin,2023-09-17:posts/git-default-branch.md</id>
		<published>2023-09-17T09:32:14+02:00</published>
		<updated>2024-01-18T14:25:04+01:00</updated>
	</entry>
	<entry>
		<title>Simple Testing with `git diff`</title>
		<content type="html">&lt;h1&gt;Simple Testing with &lt;code&gt;git diff&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;Automatic tests are great, but creating and maintaining them is time consuming and sometimes really complicated. Often, getting a moderate test coverage to prevent regressions is important, but written a &quot;proper&quot; test suite is more work than you can justify. Let me show you how to solve this in a way that is easy to apply and does not rely on any specific programming language or ecosystem.&lt;/p&gt;
&lt;h2&gt;Snapshot Testing&lt;/h2&gt;
&lt;p&gt;What is a quick way to get test cases? You take a well defined run of your program (usually something you did during manual testing before) and save the output. After changing your code, you run the program again and compare the outputs. This can yield one of three results:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The output stays the same. You now know that you did not add a regression for this test case.&lt;/li&gt;
&lt;li&gt;The output differs in a wrong way. Go fix your code!&lt;/li&gt;
&lt;li&gt;The output differs in just the way you intended when changing the code. You should mark the new output as correct.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This approach is called &quot;Snapshot Testing&quot; and is especially useful when your program&#39;s output is so large that it is hard to describe in a test, or when you need to guarantee backwards compatibility across a huge number of test cases. But it is also great for creating high level tests quickly.&lt;/p&gt;
&lt;h2&gt;Testing snapshots with &lt;code&gt;git diff&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;There are libraries that can help you do snapshot testing. But those usually rely on a specific programming language, test runner or other infrastructure and take some time to learn. I prefer to go with tools I and most other developers already know and use: &lt;code&gt;git&lt;/code&gt;, &lt;code&gt;sh&lt;/code&gt; and optionally &lt;code&gt;make&lt;/code&gt;. What do we actually need? We need a way to&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generate the output&lt;/li&gt;
&lt;li&gt;compare the differences to the accepted output&lt;/li&gt;
&lt;li&gt;mark output as accepted&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Generate Output&lt;/h3&gt;
&lt;p&gt;This is the hardest part and the part that depends on your specific program the most. If it is a simple unix tool, you can just create a directory with program inputs and write a small script to generate and save the corresponding outputs. I like to put such things in a Makefile like&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# We want to generate one .out file for each .in file in `tests`
test: $(patsubst %.in,%.out,$(wildcard tests/*.in)) 

# An .out file can be created by calling myprogram on the corresponding .in file
%.out: %.in myprogram
		./myprogram $&amp;lt; &amp;gt; $@
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Mark Output as Accepted&lt;/h3&gt;
&lt;p&gt;It is good practice to store the test cases along with the code in you git repository. So let&#39;s just add and commit the output to the git repo! I know the general rule is to not add generated files to git, but this is no random output, it is our test suite!&lt;/p&gt;
&lt;h3&gt;Compare the Differences to the Accepted Output&lt;/h3&gt;
&lt;p&gt;Now that we have our accepted output in git, it is trivial to check for differences and show them in a nice way:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git diff -- tests
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is a good idea to add &lt;code&gt;--exit-code&lt;/code&gt; so that a difference (and thus a failing test) will yield a non-zero exit code. Adding that to the &lt;code&gt;test&lt;/code&gt; make target above results in&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# We want to generate one .out file for each .in file in `tests`
test: $(patsubst %.in,%.out,$(wildcard tests/*.in)) 
		git diff --exit-code -- tests

# An .out file can be created by calling myprogram on the corresponding .in file
%.out: %.in myprogram
		./myprogram $&amp;lt; &amp;gt; $@
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;which will run you code on all test cases and compare the results with just a run of &lt;code&gt;make test&lt;/code&gt;. A complete test runner, implemented in just four lines of &lt;code&gt;make&lt;/code&gt;!&lt;/p&gt;
&lt;h2&gt;Common Objections&lt;/h2&gt;
&lt;h3&gt;My Output Is Not Diffable&lt;/h3&gt;
&lt;p&gt;If your output is not very readable in the default diff format, there are ways to improve the situation (in order of preference):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;change your output to be more diffable (e.g. sort objects by keys if your output is JSON)&lt;/li&gt;
&lt;li&gt;postprocess your output (run PDFs through &lt;code&gt;pdftotext&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;use a custom diff tool with git (you can configure different diff drivers per file type via git attributes)&lt;/li&gt;
&lt;li&gt;write a custom script to compare the outputs instead of using &lt;code&gt;git diff&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;My Program Does Not Create Output Files&lt;/h3&gt;
&lt;p&gt;Make it do so!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write a thin wrapper around core parts of you logic and serialize the results&lt;/li&gt;
&lt;li&gt;If it outputs user interfaces, you could create screenshots of them&lt;/li&gt;
&lt;li&gt;If it creates network traffic, capture it&lt;/li&gt;
&lt;li&gt;Get creative!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This will not only help with automatic testing, but allow you to script you program for one-off tests or other tasks.&lt;/p&gt;
&lt;h3&gt;This Does Not Create Unit Tests&lt;/h3&gt;
&lt;p&gt;No, it doesn&#39;t. Oftentimes, you don&#39;t really need unit tests if your overall testing exercises your code well. You can still add unit tests later if you realize you need them. This is mostly about getting working tests with low effort, not about creating the perfect test suite.&lt;/p&gt;
&lt;h3&gt;Does This Work for Real Projects&lt;/h3&gt;
&lt;p&gt;Yes, I have used it in multiple commercial projects, as well as for the &lt;a href=&quot;https://github.com/karlb/smu&quot;&gt;smu markdown parser&lt;/a&gt;.&lt;/p&gt;</content>
		<link href="https://www.karl.berlin/testing-with-diff.html"/>
		<id>tag:www.karl.berlin,2022-07-30:posts/testing-with-diff.md</id>
		<published>2022-07-30T16:14:20+02:00</published>
		<updated>2022-07-30T16:14:20+02:00</updated>
	</entry>
	<entry>
		<title>`make` as a Static Site Generator</title>
		<content type="html">&lt;h1&gt;&lt;code&gt;make&lt;/code&gt; as a Static Site Generator&lt;/h1&gt;
&lt;p&gt;Static site generators are in fashion for good reasons. The resulting pages are easy to host, fast and extremely low on maintenance while being sufficient for many use cases.
As I learned when &lt;a href=&quot;blog.html&quot;&gt;setting up my blog&lt;/a&gt;, writing a simple script myself is faster and more satisfying than learning one of the other site builders and customizing it to my needs.
This time, I only need a plain site without automatically updated timestamps or an RSS-feed, so I can go even simpler than by blog script.&lt;/p&gt;
&lt;h2&gt;Basic setup&lt;/h2&gt;
&lt;p&gt;To get the site into a working state, I require the following functionality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All input files reside in the &lt;code&gt;source&lt;/code&gt; directory, in the same layout as I want them in the output.&lt;/li&gt;
&lt;li&gt;During processing, add a header to all HTML files.&lt;/li&gt;
&lt;li&gt;Copy all other files to the &lt;code&gt;build&lt;/code&gt; directory as they are.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each of these points results in one rule in the Makefile:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-make&quot;&gt;# The `build` target depends on all output files in the `build` directory. It
# does not do anything by itself, but causes one of the following rules to be
# applied for each file.
build: $(patsubst source/%,build/%,$(shell find source -type f))

# For each .html file do `cat header.html $input &amp;gt; $output`.
build/%.html: source/%.html header.html Makefile
	@mkdir -p $(dir $@)
	cat header.html $&amp;lt; &amp;gt; $@

# Copy all other files without changes.
build/%: source/%
	cp $&amp;lt; $@
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With a corresponding &lt;code&gt;header.html&lt;/code&gt; and these rules in place, calling &lt;code&gt;make build&lt;/code&gt; will create a &lt;code&gt;build&lt;/code&gt; directory that can be browsed locally or uploaded to any web server.&lt;/p&gt;
&lt;h2&gt;Variations&lt;/h2&gt;
&lt;p&gt;This is really all you need, but the real strength of this approach is that it is so simple, that you can trivially extend it to fit different needs. Let me show you a few examples!&lt;/p&gt;
&lt;h3&gt;Mark Current Page&lt;/h3&gt;
&lt;p&gt;It is helpful to highlight the current page in the navigation so that the visitor sees where he is within the site at a glance. To do this, we search for the link within the navigation and replace the link with a highlighted version. The specifics vary depending on your markup. I&#39;m using the following code to add the &lt;code&gt;current&lt;/code&gt; class to the link tag:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-make&quot;&gt;build/%.html: source/%.html header.html Makefile
	@mkdir -p $(dir $@)
	sed -E &#39;s|(href=&amp;quot;$(subst source,,$&amp;lt;))|class=&amp;quot;current&amp;quot; \1|&#39; header.html | cat - $&amp;lt; &amp;gt; $@
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Generate Page From Markdown&lt;/h3&gt;
&lt;p&gt;If you dislike writing HTML or if you have existing content in markdown format, you can pipe your markdown content through a markdown-to-HTML converter of you choice (I like &lt;a href=&quot;https://github.com/karlb/smu&quot;&gt;smu&lt;/a&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-make&quot;&gt;build/%.html: source/%.html header.html Makefile
	@mkdir -p $(dir $@)
	smu $&amp;lt; | cat header.html - &amp;gt; $@
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since we still assume that &lt;code&gt;build/foo.html&lt;/code&gt; is built from &lt;code&gt;source/foo.html&lt;/code&gt;, you should keep the &lt;code&gt;.html&lt;/code&gt; suffix for the markdown files or modify the rules to look for &lt;code&gt;.md&lt;/code&gt; files as input.&lt;/p&gt;
&lt;h2&gt;Little Helpers&lt;/h2&gt;
&lt;p&gt;You can not only modify the site generation itself. Convenience features can also be added as additional make targets.&lt;/p&gt;
&lt;h3&gt;Serve Site Locally&lt;/h3&gt;
&lt;p&gt;Not all sites can be accurately previewed by opening the local files in your browser.
The most common reason for this is using absolute links instead of relative ones.
In those cases, you will want to run a small test web server locally to preview your site.
Python is already installed on many systems and comes with a web server this is suitable for the task.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-make&quot;&gt;serve:
	python -m http.server -d build
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Rebuild on Change&lt;/h3&gt;
&lt;p&gt;If you work a lot on your site, manually rebuilding after each change is a hassle.
Just use &lt;a href=&quot;https://eradman.com/entrproject/&quot;&gt;&lt;code&gt;entr&lt;/code&gt;&lt;/a&gt; (or &lt;a href=&quot;https://linux.die.net/man/1/inotifywait&quot;&gt;&lt;code&gt;inotifywait&lt;/code&gt;&lt;/a&gt; if you want to avoid the dependency) to rebuild automatically when a file in the source directory changes.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-make&quot;&gt;watch:
	find source header.html Makefile | entr make build
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Upload to GitHub Pages&lt;/h3&gt;
&lt;p&gt;I store my repositories on GitHub, so using GitHub Pages to host the resulting HTML is a natural choice.
Getting the commands just right so that you don&#39;t have to care about git details when publishing is a bit tricky, but easy enough in the end.
The approach is based on &lt;a href=&quot;https://sangsoonam.github.io/2019/02/08/using-git-worktree-to-deploy-github-pages.html&quot;&gt;Sangsoo Nam&#39;s post&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-make&quot;&gt;deploy:
	git worktree add public_html gh-pages
	cp -rf build/* public_html
	cd public_html &amp;amp;&amp;amp; \
	  git add --all &amp;amp;&amp;amp; \
	  git commit -m &amp;quot;Deploy to github pages&amp;quot; &amp;amp;&amp;amp; \
	  git push origin gh-pages
	git worktree remove public_html
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Having your own static site generator in only six simple lines in a Makefile is great!
There are no exotic dependencies, nothing to maintain and you can quickly adapt it to your needs.
A page I built using this approach is available at &lt;a href=&quot;https://github.com/karlb/astridbartel.de&quot;&gt;https://github.com/karlb/astridbartel.de&lt;/a&gt; and can serve as a real world example.&lt;/p&gt;</content>
		<link href="https://www.karl.berlin/static-site.html"/>
		<id>tag:www.karl.berlin,2022-06-14:posts/static-site.md</id>
		<published>2022-06-14T14:30:11+02:00</published>
		<updated>2022-06-14T14:30:11+02:00</updated>
	</entry>
	<entry>
		<title>When Is Complexity OK?</title>
		<content type="html">&lt;h1&gt;When Is Complexity OK?&lt;/h1&gt;
&lt;p&gt;We all known complexity is bad. It slows down development, makes bugs more likely, increases the maintenance burden and makes it harder to onboard new developers. So why do we still have complex software everywhere? Legacy code, misaligned incentives, unclear goals, bad development practices or sheer incompetence can be causes. But even well run projects often get very complex because it is required to achieve the desired outcome (&quot;essential complexity&quot;). Distinguishing between essential and accidental complexity is hard and even if I could identify and remove all accidental complexity, one question would remain: Is the complexity level of this project healthy, or do I have to fundamentally change the overall approach (or declare the project a failure)?&lt;/p&gt;
&lt;!--Is the essential complexity so high that the project is not viable within my development budget?--&gt;
&lt;!--When planning the next development steps I often wonder &quot;Is the complexity level of this project healthy, or do I have to fundamentally change the approach (or declare the project a failure)?&quot;.--&gt;
&lt;p&gt;If I manage to keep the project very simple the answer is obvious, so let&#39;s ignore that case. Just looking at the complexity level is not sufficient, since there are many projects of significant complexity which are very health and have a bright future (e.g. Linux, SQLite, PostgreSQL). But these projects are mature enough to carry the weight of their complexity. But projects don&#39;t start out incredibly mature, so I need to know if they are on the trajectory to get into the realm of complex but healthy and mature, or whether they are set up for failure. Looking back, the best way to find out was to ask myself&lt;/p&gt;
&lt;p&gt;&quot;Is the project getting more mature faster than it is getting more complex?&quot;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Complexity                    Complexity                
^                             ^                /
|                             |               /
|            ____             |             _/    
|        ___/                 |            /
|     __/                     |          _/
|   _/                        |         /
|  /                          |       _/
| /                           |    __/
|/                            |___/
+--------------&amp;gt; Maturity     +--------------&amp;gt; Maturity
  healthy project               unhealthy project
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When I&#39;m trying to get an early version mostly stable and bug-free, but each time I fix a problem, I have to add new concepts to handle those cases, that is an indication that I can&#39;t improve the maturity faster than the complexity. After all, when even fixing bugs does not improve the maturity/complexity ratio, then adding new features will be even worse. Continuing to work in the same won&#39;t be sustainable and I should look for a different approach of declare the project a failure. On the other hand, if I am able to fix bugs and fine-tune the program behavior without increasing the code size or making it harder to read, then I&#39;m right on my way into the healthy &quot;more mature than complex&quot;.&lt;/p&gt;
&lt;p&gt;So now that you know my complexity assessment heuristic, let me point out some details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I used the term &quot;project&quot;, but the same approach works on different levels. E.g. a complexity and maturity of a single feature can be compared in the same way.&lt;/li&gt;
&lt;li&gt;When the result is unclear, assume that you are in the unhealthy case. We tend to overestimate maturity until we had enough time to observe many edge cases.&lt;/li&gt;
&lt;li&gt;There certainly are other approaches which are better in specific cases, but this one is the most generic approach that yielded good results for me.&lt;/li&gt;
&lt;li&gt;Time is mostly orthogonal, since it can be used to move in any direction in the complexity/maturity space.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As always, if you have any feedback, don&#39;t hesitate to &lt;a href=&quot;mailto:karl@karl.berlin&quot;&gt;let me know&lt;/a&gt;!&lt;/p&gt;</content>
		<link href="https://www.karl.berlin/complexity.html"/>
		<id>tag:www.karl.berlin,2022-04-03:posts/complexity.md</id>
		<published>2022-04-03T09:54:28+02:00</published>
		<updated>2022-04-03T09:54:28+02:00</updated>
	</entry>
	<entry>
		<title>Formatting Numbers of Unknown Order of Magnitude</title>
		<content type="html">&lt;h1&gt;Formatting Numbers of Unknown Order of Magnitude&lt;/h1&gt;
&lt;p&gt;Sometimes, I write programs that need to display numbers where I don&#39;t know how big or small they will be. The most common approaches of printing numbers won&#39;t be very helpful in such a case.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Default print: more decimal places than useful
&amp;gt;&amp;gt;&amp;gt; print(1324325425.3254435363463)
1324325425.3254435

# Fixed amount of decimal places: better, but ...
&amp;gt;&amp;gt;&amp;gt; print(&amp;quot;{:.0f}&amp;quot;.format(1324325425.3254435363463))
1324325425
# ...useless results for small numbers
&amp;gt;&amp;gt;&amp;gt; print(&amp;quot;{:.0f}&amp;quot;.format(0.3254435363463))
0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A good way to deal with this problem is to use the &lt;a href=&quot;https://en.wikipedia.org/wiki/Scientific_notation&quot;&gt;scientific notation&lt;/a&gt;, which displays the mantissa and exponent separately.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;gt;&amp;gt;&amp;gt; print(&amp;quot;{:e}&amp;quot;.format(1324325425.3254435363463))
1.324325e+09
&amp;gt;&amp;gt;&amp;gt; print(&amp;quot;{:e}&amp;quot;.format(0.3254435363463))
3.254435e-01
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, not everyone is used to reading the scientific notation and many programs don&#39;t accept it as input. What I often want to have is a format that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;does not irritate humans&lt;/li&gt;
&lt;li&gt;is understood by all programs&lt;/li&gt;
&lt;li&gt;is not unnecessarily verbose&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One way to reach this is to round to a fixed number of &lt;a href=&quot;https://en.wikipedia.org/wiki/Significant_figures&quot;&gt;significant figures&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;gt;&amp;gt;&amp;gt; def fmt_sig(x, sig_figures=3):
...     show_dec = -floor(log10(abs(x)) + 1) + sig_figures
...     return round(x, show_dec)
...
&amp;gt;&amp;gt;&amp;gt; print(fmt_sig(1324325425.3254435363463))
1320000000.0
&amp;gt;&amp;gt;&amp;gt; print(fmt_sig(0.3254435363463))
0.325
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But rounding the big number can be confusing because it only has an accuracy of three digits while showing eleven digits. My suggested solution is to format numbers with a &lt;em&gt;minimum&lt;/em&gt; number of significant figures and to print all places before the decimal point for big numbers.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;gt;&amp;gt;&amp;gt; def fmt_min_sig(x, min_sig_figures=3):
...     show_dec = max(-floor(log10(abs(x)) + 1) + min_sig_figures, 0)
...     return (&amp;quot;{:.&amp;quot; + str(show_dec) + &amp;quot;f}&amp;quot;).format(x)
... 
&amp;gt;&amp;gt;&amp;gt; print(fmt_min_sig(1324325425.3254435363463))
1324325425
&amp;gt;&amp;gt;&amp;gt; print(fmt_min_sig(0.3254435363463))
0.325
&amp;gt;&amp;gt;&amp;gt; print(fmt_min_sig(12.3254435363463))
12.3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This approach has worked well for me, but I have not seen it any other code bases. That makes me wonder: Did I miss any downsides? Are others doing the same and I just didn&#39;t notice? Or is there a better way to do the same? If you have the answer to one of these questions, please &lt;a href=&quot;mailto:karl@karl.berlin&quot;&gt;let me know&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;Update (2026)&lt;/h2&gt;
&lt;p&gt;It turns out that JavaScript&#39;s &lt;code&gt;Intl.NumberFormat&lt;/code&gt; supports this formatting
strategy since the &lt;a href=&quot;https://github.com/tc39/proposal-intl-numberformat-v3&quot;&gt;V3 proposal&lt;/a&gt;
shipped in browsers. The ICU library that powers it calls the underlying concept
&quot;relaxed precision&quot;. You can get the same behavior as &lt;code&gt;fmt_min_sig&lt;/code&gt; with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const fmt = new Intl.NumberFormat(&amp;quot;en&amp;quot;, {
  minimumSignificantDigits: 3,
  maximumSignificantDigits: 3,
  maximumFractionDigits: 0,
  roundingPriority: &amp;quot;morePrecision&amp;quot;
});
fmt.format(0.0123456)  // &#39;0.0123&#39;
fmt.format(123456.78)  // &#39;123,457&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;morePrecision&lt;/code&gt; mode resolves conflicts between the two rounding strategies
by picking whichever produces more digits. For large numbers,
&lt;code&gt;maximumFractionDigits: 0&lt;/code&gt; wins (keeping all integer digits). For small numbers,
&lt;code&gt;minimumSignificantDigits: 3&lt;/code&gt; wins (keeping three significant figures). That is
exactly the &lt;code&gt;max()&lt;/code&gt; in &lt;code&gt;fmt_min_sig&lt;/code&gt;.&lt;/p&gt;</content>
		<link href="https://www.karl.berlin/formatting-numbers.html"/>
		<id>tag:www.karl.berlin,2021-05-01:posts/formatting-numbers.md</id>
		<published>2021-05-01T15:07:39+02:00</published>
		<updated>2026-02-19T14:28:26+01:00</updated>
	</entry>
	<entry>
		<title>Adding Gemini Support With Just a Few Lines of Code</title>
		<content type="html">&lt;h1&gt;Adding Gemini Support With Just a Few Lines of Code&lt;/h1&gt;
&lt;p&gt;The Gemini protocol nicely matches my preference for simplicity, so I want to start providing content for it to show my support. The first step for doing that is providing this blog via Gemini. I&#39;ll quickly summarize the steps that were necessary to do this in this post.&lt;/p&gt;
&lt;h2&gt;What is Gemini?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://gemini.circumlunar.space/&quot;&gt;Gemini&lt;/a&gt; is an extremely simplified version of the web. It doesn&#39;t have Javascript, CSS (or any other kind of styling) and it&#39;s markup language Gemtext is much simpler than Markdown. It doesn&#39;t use HTTP, but works in a similar way. Don&#39;t be irritated by the lack of content about Gemini on the web. You&#39;ll see most of the content about it only after installing a Gemini client. My recommendation is &lt;a href=&quot;https://gmi.skyjake.fi/lagrange/&quot;&gt;LaGrange&lt;/a&gt;. The great thing about Gemini is that both creating and consuming content is pretty easy due to the low complexity in every regard.&lt;/p&gt;
&lt;h2&gt;Generating Gemtext&lt;/h2&gt;
&lt;p&gt;I don&#39;t want to write each block post twice, so I&#39;d like to convert the existing posts (written in Markdown) to Gemtext automatically. Fortunately, there&#39;s &lt;a href=&quot;https://github.com/makeworld-the-better-one/md2gemini&quot;&gt;md2gemini&lt;/a&gt;, which can handle the conversion process. Since Gemtext does not support inline links, the links in my posts have to be moved out of the paragraphs into separate lines. md2gemini offers multiple ways to that. Putting the links at the end of each paragraph and numbering the references inside the paragraph (like this &lt;code&gt;[1]&lt;/code&gt;) seemed like the most readable way to me, so I chose that one.&lt;/p&gt;
&lt;p&gt;The resulting Gemini pages worked, but had two problems left:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The comments I left for myself in some posts suddenly became visible&lt;/li&gt;
&lt;li&gt;The HTML-links I use to include the &lt;code&gt;rel=&amp;quot;me&amp;quot;&lt;/code&gt; attribute on the index page were not converted&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both of these issues arise because md2gemini does not try to interpret HTML (which is allowed in Markdown documents). The easy fix was to replace the HTML markup before passing it to md2gemini with regular expressions:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;s/&amp;lt;a href=&amp;quot;([^&amp;quot;]*)&amp;quot;.*&amp;gt;(.*)&amp;lt;\/a&amp;gt;/[\2](\1)/g
s/^&amp;lt;!--.*--&amp;gt;//gsm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Combined with the call to md2gemini, this led to the following shell function to process Markdown to Gemtext.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GEMINI() {
	&amp;lt;&amp;quot;$1&amp;quot; perl -0pe &#39;s/&amp;lt;a href=&amp;quot;([^&amp;quot;]*)&amp;quot;.*&amp;gt;(.*)&amp;lt;\/a&amp;gt;/[\2](\1)/g;s/^&amp;lt;!--.*--&amp;gt;//gsm&#39; | md2gemini --links paragraph;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now I can use this inside my page generation loop, I just have to replace the target suffix and append the last modification date.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GEMINI &amp;quot;$filename&amp;quot; | \
	   sed &amp;quot;$ s/$/\\n\\n$dates_text/&amp;quot; \
	   &amp;gt; &amp;quot;$(echo &amp;quot;$target&amp;quot; | sed s/.html/.gmi/)&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the posts working, I&#39;m still missing a proper index page. It should consist of an introductory text and a list of blog posts, ideally prefixed by the ISO date of the post, so that it matches the optional &lt;a href=&quot;gemini://gemini.circumlunar.space/docs/companion/subscription.gmi&quot;&gt;Gemini feed format&lt;/a&gt;. Instead of doing any abstractions, I just copied the index generation code for the HTML version and adapted it to Gemtext.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;index_gmi() {
       # Intro text
       GEMINI index.md

       # Posts
       while read -r f title created updated; do
               if [ &amp;quot;$created&amp;quot; = &amp;quot;draft&amp;quot; ] &amp;amp;&amp;amp; [ &amp;quot;$2&amp;quot; = &amp;quot;hide-drafts&amp;quot; ]; then continue; fi
               link=$(echo &amp;quot;$f&amp;quot; | sed -E &#39;s|.*/(.*).md|\1.gmi|&#39;)
               created=$(echo &amp;quot;$created&amp;quot; | sed -E &#39;s/T.*//&#39;)
               echo &amp;quot;=&amp;gt; $link $created - $title&amp;quot;
       done &amp;lt; &amp;quot;$1&amp;quot;
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&#39;s all that was necessary to create Gemtext pages for my blog. Right now, there are only two things I plan to improve in the future:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add the &lt;a href=&quot;projects.html&quot;&gt;list of my projects&lt;/a&gt;. That page is not a blog post and thus slightly special.&lt;/li&gt;
&lt;li&gt;Keep links between blog pages consistent. Right now, they all link to the HTML version, even in the Gemini version.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Setting Up the Server&lt;/h2&gt;
&lt;p&gt;Since Gemini doesn&#39;t use HTTP, I can&#39;t just copy the generated pages to my normal webspace. Fortunately, setting up a Gemini server is pretty easy.&lt;/p&gt;
&lt;h3&gt;Choosing a Server&lt;/h3&gt;
&lt;p&gt;There are plenty of Gemini servers. I chose &lt;a href=&quot;https://git.sr.ht/~sircmpwn/gmnisrv&quot;&gt;gmnisrv&lt;/a&gt; for the following reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Few dependencies (written in plain C)&lt;/li&gt;
&lt;li&gt;Automatic certificate handling (Gemini always uses TLS and thus needs certificates)&lt;/li&gt;
&lt;li&gt;Support for virtual hosts, which will allow me to add more Gemini services on the same host in the future&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Installing gmnisrv&lt;/h3&gt;
&lt;p&gt;The compilation and installation was dead simple and done in a minute, just by following the instructions in gmnisrv&#39;s readme. Writing the configuration was also straightforward and resulted in this small config.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;listen=0.0.0.0:1965 [::]:1965

[:tls]
store=/var/lib/gemini/certs

[gmi.karl.berlin]
root=/home/karl/hosts/karl.berlin/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Gemini server itself worked now, but it was not reachable from the outside, yet. The two missing steps were&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configure the DNS setting for gmi.karl.berlin.&lt;/li&gt;
&lt;li&gt;Open the Gemini ports in the firewall: &lt;code&gt;sudo ufw allow 1965/tcp&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As a final touch, I added a systemd unit to automatically start gmnisrv.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Unit]
Description=gmnisrv Gemini server (see /usr/local/etc/gmnisrv.ini for config)

[Service]
Type=simple
Restart=always
RestartSec=5
ExecStart=gmnisrv

[Install]
WantedBy=default.target
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Please note that you want to use a non-root user if there are other important services on the machine.&lt;/p&gt;
&lt;h2&gt;Result&lt;/h2&gt;
&lt;p&gt;That&#39;s it! If you are still reading this in your web browser, fire up your Gemini client and look at &lt;a href=&quot;gemini://gmi.karl.berlin/&quot;&gt;the result&lt;/a&gt;! Considering that this was a low effort way to create a presence in Gemini space, I&#39;m quite pleased with the results.&lt;/p&gt;
&lt;p&gt;My next goal regarding Gemini will be to bring &lt;a href=&quot;http://www.wikdict.com&quot;&gt;WikDict&lt;/a&gt; to Gemini, which should be a bit more challenging.&lt;/p&gt;</content>
		<link href="https://www.karl.berlin/gemini-blog.html"/>
		<id>tag:www.karl.berlin,2021-01-31:posts/gemini-blog.md</id>
		<published>2021-01-31T11:33:01+01:00</published>
		<updated>2021-01-31T11:37:39+01:00</updated>
	</entry>
	<entry>
		<title>Tcl as a Shell Scripting Replacement</title>
		<content type="html">&lt;h1&gt;Tcl as a Shell Scripting Replacement&lt;/h1&gt;
&lt;p&gt;Summary: I got interested in the old scripting language &lt;a href=&quot;https://www.tcl.tk/&quot;&gt;Tcl&lt;/a&gt;, rewrote my blog generator in it as an exercise and compare it to shell scripting.&lt;/p&gt;
&lt;h2&gt;Motivation&lt;/h2&gt;
&lt;p&gt;Every once in a while, the &quot;&lt;a href=&quot;http://antirez.com/articoli/tclmisunderstood.html&quot;&gt;Tcl the Misunderstood&lt;/a&gt;&quot; article by &lt;a href=&quot;http://invece.org/&quot;&gt;antirez&lt;/a&gt; gets to the front page of &lt;a href=&quot;https://news.ycombinator.com/&quot;&gt;Hacker News&lt;/a&gt;. I&#39;ve read it at least two of those times, since I appreciate antirez&#39; opinions and I&#39;m generally interested in programming languages. I&#39;ve also often heard of Tk, one of the few simple cross-platform GUI libraries, which comes bundled with your Tcl install. Tcl is also close to the awesome SQLite project, which started out as a Tcl extension.&lt;/p&gt;
&lt;p&gt;All of this provided a base interest in the language, but what really pushed me into action was reading the well-written &quot;&lt;a href=&quot;https://www.goodreads.com/en/book/show/39996759&quot;&gt;A Philosophy of Software Design&lt;/a&gt;&quot; book by John Ousterhout, the creator of Tcl. When his general ideas about software development resonate well with me, the language he created might do so, too. So I got his other book &lt;a href=&quot;https://www.oreilly.com/library/view/tcl-and-the/9780321601766/&quot;&gt;Tcl and the Tk Toolkit&lt;/a&gt; and read through most of it, playing with small code samples along the way. But to get a real feel for the language, I needed to write a real project in it, even if it is a tiny one.&lt;/p&gt;
&lt;h2&gt;Rewriting My Blog Engine&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/karlb/karl.berlin&quot;&gt;simple blog engine&lt;/a&gt; I wrote recently is small enough that I could rewrite it in Tcl within a few hours. Rewriting an existing project also gave me the opportunity to compare both versions afterwards, which shows the differences caused by the change of programming language in a nice way. The comparison won&#39;t be totally fair for two reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I&#39;m writing the code for a second time, so I already know exactly how I want the program to behave.&lt;/li&gt;
&lt;li&gt;I&#39;m biased by the existing implementation, so the code will be less idiomatic in the new language.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I chose to use the existing code as a starting point and port that to Tcl line by line, so the latter point is the more serious one. Even more so, since I&#39;m new to Tcl but have used shell scripting before.&lt;/p&gt;
&lt;h3&gt;Rewriting Line by Line&lt;/h3&gt;
&lt;p&gt;Rewriting the first few lines already made one thing obvious: using Tcl in to call external commands is very easy and works mostly like it does in the shell. The code I use to get the time of the first git commit hardly changed:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-	$(git log --pretty=&#39;format:%aI&#39; &amp;quot;$f&amp;quot; 2&amp;gt; /dev/null | tail -1)
+	[exec git log --pretty=format:%aI $f 2&amp;gt; /dev/null | tail -1]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The only notable difference is the lack of quoting required in the Tcl version. &lt;a href=&quot;https://wiki.tcl-lang.org/page/Tcl+Quoting&quot;&gt;Tcl&#39;s quoting rules&lt;/a&gt; are a bit unusual, but actually quite simple and more regular and robust than the ones in traditional Unix shells. I won&#39;t go into the details here, but I want to point out one example of increased robustness of the Tcl way. Although list elements are separated by spaces, just like in sh, having spaces in your variables will never make the variable accidentally split into multiple variables. Let&#39;s assume you have the following variables:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;a=&amp;quot;eggs&amp;quot;
b=&amp;quot;bacon spam&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you call &lt;code&gt;myfunc $a $b&lt;/code&gt; in sh, you will pass three parameters to myfunc, because b gets split up at the space. In contrast, Tcl will always pass two parameters, irregardless of the content of those two variables. There are ways to avoid this in sh (and even more ways in bash), but I still run into edge cases from time to time and find Tcl&#39;s approach more pleasant and sane.&lt;/p&gt;
&lt;p&gt;Other changes compared to the shell version were&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use of a nested list instead of writing temporary data to a TSV, because Tcl made it easier to deal with the nested data&lt;/li&gt;
&lt;li&gt;Some syntactic sugar like &lt;code&gt;lassign&lt;/code&gt; and explicitly named function parameters&lt;/li&gt;
&lt;li&gt;Replace some &lt;code&gt;sed&lt;/code&gt; usages with built-in string functions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This led me to my first working Tcl version of the blog engine (&lt;a href=&quot;tcl/blog.1.tcl&quot;&gt;blog.1.tcl&lt;/a&gt;, compare with &lt;a href=&quot;tcl/blog.sh&quot;&gt;blog.sh&lt;/a&gt;) which resulted in the same output as my original version, except for some insignificant whitespace differences.&lt;/p&gt;
&lt;h3&gt;Cleaning Things Up&lt;/h3&gt;
&lt;p&gt;The next step was to go through the code a second time and look for opportunities to simplify and clean up parts that felt out of place in a Tcl program. Calls to &lt;code&gt;sed&lt;/code&gt; can be replaced by using Tcl&#39;s regex support:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-	set host [exec echo $uri | sed -r {s|.*//([^/]+).*|\1|}]
+	regexp {.*//([^/]+).*} $uri -&amp;gt; host
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One use of &lt;code&gt;sed&lt;/code&gt; was not as straightforward to replace as I expected. I didn&#39;t find a compact way to return a regexp match from a file, so I wrote a small helper function.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tcl&quot;&gt;proc from_file {re filename} {
	set f [open $filename]
	regexp -line -- $re [read $f] -&amp;gt; match
	close $f
	return $match
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allowed me to replace four lines lines like &lt;code&gt;set title [exec sed -n &amp;quot;/^# /{s/# //p; q}&amp;quot; $f]&lt;/code&gt; with &lt;code&gt;set title [from_file {^# (.*)} $f]&lt;/code&gt;. The regexp is easier to read for me (the curly braces are just part of Tcl&#39;s quoting), which somewhat offsets the downside of requiring an extra function. Still, I don&#39;t see this as a clear improvement and would like to hear about better solutions.&lt;/p&gt;
&lt;p&gt;The simple access to proper data structures also made me replace two calls to git (for getting first and last commit) by a single one and using the list functions get the desired parts from the result.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-diff&quot;&gt;-       set created [exec git log --pretty=format:%aI $f 2&amp;gt; /dev/null | tail -1]
-       set updated [exec git log --pretty=format:%aI $f 2&amp;gt; /dev/null | head -1]
+       set commit_times [exec git log --pretty=format:%aI $f 2&amp;gt; /dev/null]
+       set created [lindex $commit_times end]
+       set updated [lindex $commit_times 0]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The new code is a bit longer, but less redundant and clearly displays the relationship between &lt;code&gt;created&lt;/code&gt; and &lt;code&gt;updated&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Result&lt;/h3&gt;
&lt;p&gt;Together, these changes resulted in the final version of my blog script (&lt;a href=&quot;tcl/blog.tcl&quot;&gt;blog.tcl&lt;/a&gt;). Let&#39;s compare the size and run times as a last discipline.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ wc blog.*
 105  354 2934 blog.sh
 105  409 3099 blog.1.tcl
 113  406 3143 blog.tcl
 323 1169 9176 total

$ time ./blog.sh
real    0m0,280s
user    0m0,263s
sys     0m0,098s

$ time ./blog.1.tcl 
real    0m0,125s
user    0m0,078s
sys     0m0,071s

$ time ./blog.tcl 
real    0m0,111s
user    0m0,085s
sys     0m0,035s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So the script got marginally longer but significantly faster, although all versions are more than fast enough for my case. I could probably have kept the file size the same if I wanted, but I went for readability in more cases in the Tcl script. E.g. I used &lt;code&gt;$filename&lt;/code&gt; instead of &lt;code&gt;$f&lt;/code&gt; and used more lines breaks (with the additional bytes for indentation that come with it) in cases like this escaping of HTML characters:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-	sed &#39;s/&amp;amp;/\&amp;amp;amp;/g; s/&amp;lt;/\&amp;amp;lt;/g; s/&amp;gt;/\&amp;amp;gt;/g; s/&amp;quot;/\&amp;amp;quot;/g; s/&#39;&amp;quot;&#39;&amp;quot;&#39;/\&amp;amp;#39;/g&#39;
+	string map {
+		&amp;amp; &amp;amp;amp;
+		&amp;lt; &amp;amp;lt;
+		&amp;gt; &amp;amp;gt;
+		\&amp;quot; &amp;amp;quot;
+		&#39; &amp;amp;#39;
+	}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Overall, the effort for porting was low and I&#39;m happy with the result, especially the improvement in readability.&lt;/p&gt;
&lt;h2&gt;What Now?&lt;/h2&gt;
&lt;p&gt;So what do I do with it? Do I use it instead of my old shell version? Do I discard the code just as part of an experiment? I&#39;m not sure, yet. I like the new code, but should I add another language to the zoo of code bases I&#39;m maintaining? I guess it will depend on whether I find Tcl suitable for other projects. I currently see three areas that seem interesting:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;shell scripting replacement, as described in this post&lt;/li&gt;
&lt;li&gt;python alternative in certain use cases (lots of external programs or heavy meta programming)&lt;/li&gt;
&lt;li&gt;as language for interactive shells&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Originally, I wanted to write about the latter two points in this article, too. But it got long enough as it is and writing separate posts will keep me more focused. If those topics sound interesting, &lt;a href=&quot;mailto:mail@karl.berlin&quot;&gt;let me&lt;/a&gt; know to make sure that I actually get around to writing those posts!&lt;/p&gt;</content>
		<link href="https://www.karl.berlin/tcl-blog.html"/>
		<id>tag:www.karl.berlin,2021-01-06:posts/tcl-blog.md</id>
		<published>2021-01-06T13:23:57+01:00</published>
		<updated>2021-01-06T13:23:57+01:00</updated>
	</entry>
	<entry>
		<title>My Simple Custom Blog Software</title>
		<content type="html">&lt;h1&gt;My Simple Custom Blog Software&lt;/h1&gt;
&lt;p&gt;The pages on this site are generated by my own primitive blog engine. This post summarizes why I wrote my own, what my priorities for it were and how I went about achieving them.&lt;/p&gt;
&lt;h2&gt;Why Something Custom?&lt;/h2&gt;
&lt;p&gt;Originally I wanted to use &lt;a href=&quot;https://gohugo.io/&quot;&gt;Hugo&lt;/a&gt; for this blog. I started to look for a simple theme that did not involve many dependencies and provided clean output. This quickly frustrated me because the themes were either not minimal at all or I could not get them to work with my version of Hugo. This made me take a step back and think about what I actually want as blog output. I wrote a tiny HTML header, a small test blog article in markdown and did
&lt;code&gt;cat header.html &amp;gt; post.html &amp;amp;&amp;amp; smu post.md &amp;gt;&amp;gt; post.html&lt;/code&gt;. The result looked good and that convinced me that writing a small blog creation script was more rewarding and maybe even faster than configuring something existing the way I like it.&lt;/p&gt;
&lt;h2&gt;Requirements&lt;/h2&gt;
&lt;p&gt;Before jumping deeper into it, I made a list of my basic requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Low maintenance&lt;/li&gt;
&lt;li&gt;Low barrier to create content (markdown)&lt;/li&gt;
&lt;li&gt;Low requirements on the client (web browser, internet connection, RAM, CPU)&lt;/li&gt;
&lt;li&gt;Shows creation and update timestamps of posts&lt;/li&gt;
&lt;li&gt;RSS feed&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;How I Achieved These  Constraints&lt;/h2&gt;
&lt;h3&gt;Low Maintenance&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Avoid dependencies: only a POSIX shell and a markdown converter are required. Dependencies always increase the maintenance burden.&lt;/li&gt;
&lt;li&gt;Static files: hosting static files is much easier to do reliably than hosting dynamic content&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Low Barrier to Create Content&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Use markdown: when writing markdown I can mostly avoid thinking about the syntax and focus on the content&lt;/li&gt;
&lt;li&gt;Automatically generate timestamps: generating the timestamps from the git history allows me to just create posts without needing any templates or front matter to provide this data. I also can&#39;t forget to update these timestamps.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Low Requirements on the Client&lt;/h3&gt;
&lt;p&gt;This is mostly solved by not doing anything complicated. Not adding any JS and CSS frameworks keeps the size small. Not setting a font size and not disallowing zooming makes the text readable on a wide variety of devices and by people of bad eye sight. Just using basic HTML and a few lines of CSS allows old or limited browsers and slow computers to display the page easily. I could go on for a long time here, but I&#39;m sure you get the concept.&lt;/p&gt;
&lt;h3&gt;RSS Feed&lt;/h3&gt;
&lt;p&gt;Adding an RSS feed (an &lt;a href=&quot;https://en.wikipedia.org/wiki/Atom_(Web_standard)&quot;&gt;Atom&lt;/a&gt; feed to be precise) was the most complicated part of the script. But I really love RSS and in my opinion RSS support is an essential part of a blog. I was shocked to learn that nowadays some blog engines need plugins to get RSS support!
Writing something that is nearly a valid Atom feed is pretty easy. Just take the &lt;a href=&quot;https://validator.w3.org/feed/docs/atom.html#sampleFeed&quot;&gt;example Atom feed&lt;/a&gt; and fill in your own values. But to make it standard compliant and work well with different clients, I also needed to&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use an absolute feed URL. I usually avoid absolute URLs, as that makes local testing easier.&lt;/li&gt;
&lt;li&gt;Generate good IDs for the posts. This was the hardest part, see &lt;a href=&quot;http://web.archive.org/web/20110514113830/http://diveintomark.org/archives/2004/05/28/howto-atom-id&quot;&gt;Mark Pilgram&#39;s recommendations&lt;/a&gt; to see the challenges in detail.&lt;/li&gt;
&lt;li&gt;Include the post content while escaping the HTML tags&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;h3&gt;The HTML Output&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;No JS, no tracking, no custom fonts&lt;/li&gt;
&lt;li&gt;No external CSS&lt;/li&gt;
&lt;li&gt;Minimal styling&lt;/li&gt;
&lt;li&gt;This page weighs just 7K, most of it the text you are reading right&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The largest part of the CSS is the styling of the navigation bar. I could have went with a line of text links separated by spaces, but I wanted to have a clearly visible navigation at the top.
Using such a small amount of CSS also removed the need for any CSS preprocessors like &lt;a href=&quot;https://sass-lang.com/&quot;&gt;Sass&lt;/a&gt;. &lt;/p&gt;
&lt;h3&gt;The Blog Script&lt;/h3&gt;
&lt;p&gt;You can find the &lt;a href=&quot;https://github.com/karlb/karl.berlin/blob/master/blog.sh&quot;&gt;code of the resulting script&lt;/a&gt; on github. The blog you are currently viewing is part of the same repository and serves as example content.&lt;/p&gt;
&lt;p&gt;To make the script work as intended, you should adhere to the following rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use git and don&#39;t edit history after publishing. This is required for the automatic timestamp generation.&lt;/li&gt;
&lt;li&gt;All posts and the &lt;code&gt;index.md&lt;/code&gt; have a title using the &lt;code&gt;# &lt;/code&gt; syntax. This title is used for the HTMl title tag and for the post titles in the article listing and RSS feed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;header.html&lt;/code&gt; contains an absolute link to your &lt;code&gt;atom.xml&lt;/code&gt;, including a hostname you control. This is necessary to build a correct atom feed.&lt;/li&gt;
&lt;li&gt;Uncommitted files are drafts and won&#39;t show up in the normal article list, but are shown in &lt;code&gt;index-with-drafts.html&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Feel free to contact me if you have any questions. If you want to use it for your own site, I can give some guidance. Since I don&#39;t expect many users, this is less effort than writing and maintaining good documentation.&lt;/p&gt;</content>
		<link href="https://www.karl.berlin/blog.html"/>
		<id>tag:www.karl.berlin,2020-08-30:posts/blog.md</id>
		<published>2020-08-30T16:35:19+02:00</published>
		<updated>2020-08-30T16:35:19+02:00</updated>
	</entry>
	<entry>
		<title>Hacking on "smu", a Minimal Markdown Parser</title>
		<content type="html">&lt;h1&gt;Hacking on &quot;smu&quot;, a Minimal Markdown Parser&lt;/h1&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I wanted to get my hands dirty and improve a &lt;a href=&quot;https://suckless.org/&quot;&gt;suckless&lt;/a&gt; related program. So I had a look at the &lt;a href=&quot;https://suckless.org/project_ideas/&quot;&gt;project ideas page&lt;/a&gt; and picked out something simple:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Improve the Markdown parser used by the suckless wiki called &quot;smu&quot; to conform more to Markdown. for example for nested codeblocks. Difficulty: trivial-medium.&lt;br /&gt;
Specs: http://daringfireball.net/projects/markdown/syntax.text and http://commonmark.org/.&lt;br /&gt;
smu: https://github.com/Gottox/smu  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;First Impressions&lt;/h2&gt;
&lt;p&gt;While C is pleasantly simple in many regards, writing good code can be quite cumbersome if you&#39;re not used to it. I didn&#39;t write any serious amount of C for many years and I tend to think that C is generally a bad choice for anything that does large amounts of string manipulation. I was expecting many regular expressions and some hard-to-get-right memory allocation code. To my surprise, I didn&#39;t find a single regex and hardly any memory management code. Instead of regexes, the code relied on basic string functions like &lt;code&gt;strstr&lt;/code&gt; and lots of manually-iterating-through-char-arrays.&lt;/p&gt;
&lt;p&gt;Like most suckless programs, smu consists of a single C file with only a few hundred lines (624 in this case), had no dependencies and compiled instantly without the need to run a &lt;code&gt;configure&lt;/code&gt; script.&lt;/p&gt;
&lt;h2&gt;How smu Manages to Stay Simple&lt;/h2&gt;
&lt;h3&gt;Put Logic Into Data instead of Code&lt;/h3&gt;
&lt;p&gt;Markdown has a large amount of different syntax elements, but many of them behave in similar ways. Smu takes advantage of this by declaring the different syntax elements in data structures that can be processed by a small amount of different functions. Here&#39;s one example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;static Tag lineprefix[] = {
    { &amp;quot;   &amp;quot;,        0,  &amp;quot;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&amp;quot;, &amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;quot; },
    { &amp;quot;\t&amp;quot;,         0,  &amp;quot;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&amp;quot;, &amp;quot;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;quot; },
    { &amp;quot;&amp;gt; &amp;quot;,         2,  &amp;quot;&amp;lt;blockquote&amp;gt;&amp;quot;, &amp;quot;&amp;lt;/blockquote&amp;gt;&amp;quot; },
    { &amp;quot;###### &amp;quot;,    1,  &amp;quot;&amp;lt;h6&amp;gt;&amp;quot;,         &amp;quot;&amp;lt;/h6&amp;gt;&amp;quot; },
    { &amp;quot;##### &amp;quot;,     1,  &amp;quot;&amp;lt;h5&amp;gt;&amp;quot;,         &amp;quot;&amp;lt;/h5&amp;gt;&amp;quot; },
    { &amp;quot;#### &amp;quot;,      1,  &amp;quot;&amp;lt;h4&amp;gt;&amp;quot;,         &amp;quot;&amp;lt;/h4&amp;gt;&amp;quot; },
    { &amp;quot;### &amp;quot;,       1,  &amp;quot;&amp;lt;h3&amp;gt;&amp;quot;,         &amp;quot;&amp;lt;/h3&amp;gt;&amp;quot; },
    { &amp;quot;## &amp;quot;,        1,  &amp;quot;&amp;lt;h2&amp;gt;&amp;quot;,         &amp;quot;&amp;lt;/h2&amp;gt;&amp;quot; },
    { &amp;quot;# &amp;quot;,         1,  &amp;quot;&amp;lt;h1&amp;gt;&amp;quot;,         &amp;quot;&amp;lt;/h1&amp;gt;&amp;quot; },
    { &amp;quot;- - -\n&amp;quot;,    1,  &amp;quot;&amp;lt;hr /&amp;gt;&amp;quot;,       &amp;quot;&amp;quot;},
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These declarations provide the basis of handling code indents (both with spaces and with tabs), blockquotes, headings of different levels and horizontal rules. All of these different syntax elements can now be processed by a single small functions (~45 lines). Not only does this reduce the amount of code, but it also makes it a lot easier to get an overview of the possible markup constructs and how they relate to each other.&lt;/p&gt;
&lt;h3&gt;Avoid Memory Management&lt;/h3&gt;
&lt;p&gt;Since these syntax elements have to be parsed into different parts (markdown syntax, content, link title, link target, etc.), I was expecting many strings allocations to hold these parts. But smu gets around this in most cases by doing one of these two things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Instead of saving the parsed data structures, the corresponding output is generated immediately, so that the parsed strings don&#39;t have to be saved at all&lt;/li&gt;
&lt;li&gt;Instead of allocation a new string, two pointers are used to mark the desired substring in smu&#39;s input.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Simplify Spec&lt;/h3&gt;
&lt;p&gt;Markdown is not something that falls out of a beautiful mathematical model, rather it is grown over the time, driven by what people &quot;mean&quot; when they write pseudo-plaintext. This led to a bunch of weird edge case handling rules and syntax oddities. Smu took the liberty to ignore parts of markdown (reference style links) and handle some details differently (escaping, white space handling).&lt;/p&gt;
&lt;h2&gt;Changing smu&lt;/h2&gt;
&lt;h3&gt;Test Suite&lt;/h3&gt;
&lt;p&gt;As mentioned above, I didn&#39;t write C for a long time. I also was not sure how all Markdown should be interpreted in detail. So to prevent me from breaking everything, I needed some way to spot regressions or other bugs. When looking for Markdown test cases, I found &lt;a href=&quot;https://github.com/michelf/mdtest/&quot;&gt;mdtest&lt;/a&gt; and took a set of basis tests from it that mostly worked with smu. Then I looked through the remaining differences and tried to understand why smu delivered different results. That way, the tests did not only provide some safety while hacking on smu, but also showed me where it differed from other markdown implementations.&lt;/p&gt;
&lt;p&gt;To turn the &lt;em&gt;(input, expected output)&lt;/em&gt; pairs into automated test cases, I committed both to git and added a make target that regenerates the output and runs &lt;code&gt;git diff&lt;/code&gt; on the test directory. When the diff shows no output, the tests have passed! This will give more false positives than mdtest&#39;s algorithm that accounts for insignificant white space, but it is much simpler.&lt;/p&gt;
&lt;h3&gt;CommonMark Compatibility&lt;/h3&gt;
&lt;p&gt;These differences provided a nice starting ground for the first steps towards improved markdown compatibility. I could pick some minor differences that could easily be adjusted by modifying just a few line of code. At the same time, I collected a list of differences to CommonMark that I noticed but couldn&#39;t (or didn&#39;t want to) fix right away in order to add that to the documentation.&lt;/p&gt;
&lt;p&gt;By doing a few of these changes I got more confident when changing the code and felt ready to start bigger changes. For my personal usage, one feature omitted by smu proved to be a annoyance: code fences. Without code fences, copy/pasting code to and from your markdown code blocks requires changes in indentation, which is error prone and a bit of a hassle. To implement code fences, extending one of the lists of syntax elements was not enough, since they work neither by prefixing every line of the block nor are the surrounding markers for other elements sufficient to accurately capture their block behavior. So I unfortunately had to add another parsing function to handle them correctly. That however, worked without much surprises and yielded results to my satisfaction.&lt;/p&gt;
&lt;p&gt;One other conceptual difference I introduced was a different escaping rules. Originally, smu had the simple rule of escaping the characters &lt;code&gt;\ ` * _ { } [ ] ( ) # + - . !&lt;/code&gt;. This worked fine in most cases, but brought downsides with it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;There is not way to escape text parts that look like HTML but aren&#39;t&lt;/li&gt;
&lt;li&gt;Some code parts containing backslashes lost their backslashes unless you escape them (bad for copy/paste).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To remedy the first problem, I looked up the &lt;a href=&quot;https://spec.commonmark.org/0.29/#backslash-escapes&quot;&gt;CommonMark escaping rules&lt;/a&gt; and added all characters from the CommomMark list to smu&#39;s list of escaped characters. Inside code spans, &lt;code&gt;\`&lt;/code&gt; was the only escape needed to be allowed, since it would mean the end of a code span without escape and all other characters have no special meaning inside code spans/blocks. But this was an ugly special case in the code and would also lead to silently broken code samples when you add code that does contain a literal &lt;code&gt;\`&lt;/code&gt;. How does CommonMark deal with this? The rules for code spans and code blocks allow an unlimited amount of different start and end markers, so markers can be chosen that are not present in the code itself. I chose a subset of these rules that could be expressed with smu&#39;s existing matching declarations, so that you can write &lt;code&gt;`` ` ``&lt;/code&gt; if you want a code span that contains a single backtick (The pair of single white spaces is ignored). When first reading the CommonMark specs, I felt that these rules were strange and arbitrary, but after trying to find simpler alternative, I started to appreciate the spec more and more, even though I still consider parts of it to allow an unnecessarily amount of different syntaxes.&lt;/p&gt;
&lt;!--
### Different approaches

* used regex
* more comments
--&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Overall, the small and well structured code base allowed me to dive into the project quickly and get my first small success within the first hour of coding. This is a pleasant contrast to bigger software projects where I&#39;m sometimes happy to have it build successfully during that time frame. The only thing that really slowed me down was the lack of tests, since I didn&#39;t dare to change most parts before I added some test coverage.&lt;/p&gt;
&lt;p&gt;The resulting program is very usable for my markdown use cases (it rendered this page), but I would not dare to run it on potentially malicious input. I&#39;m also not sure how well the HTML pass-through works, since I haven&#39;t used it myself, yet.&lt;/p&gt;
&lt;p&gt;The increased CommonMark compliance should make it easier for new users to start working with smu, but it did come at a slight increase of complexity. My version is ~100 lines longer, but about half of that is comments, blank lines and additional escaping declarations. This feels like a good trade off to me.&lt;/p&gt;
&lt;p&gt;The result can be seen on &lt;a href=&quot;https://github.com/karlb/smu&quot;&gt;my smu fork&#39;s github page&lt;/a&gt;. Feedback welcome!&lt;/p&gt;</content>
		<link href="https://www.karl.berlin/smu.html"/>
		<id>tag:www.karl.berlin,2020-05-06:posts/smu.md</id>
		<published>2020-05-06T17:26:23+02:00</published>
		<updated>2020-05-06T17:26:23+02:00</updated>
	</entry>
	<entry>
		<title>Suckless Software on My Desktop</title>
		<content type="html">&lt;h1&gt;Suckless Software on My Desktop&lt;/h1&gt;
&lt;p&gt;I took a look at which simple programs I could use as a desktop environment instead of Gnome and picked out the following ones&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://st.suckless.org/&quot;&gt;st&lt;/a&gt; as a terminal&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dwm.suckless.org/&quot;&gt;dwm&lt;/a&gt; as a window manager, along with &lt;a href=&quot;https://tools.suckless.org/slstatus/&quot;&gt;slstatus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tools.suckless.org/slock/&quot;&gt;slock&lt;/a&gt; for screen locking&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Initial Impression&lt;/h2&gt;
&lt;p&gt;The suckless tools are configured by editing C header files before compilation. This is pretty straightforward to do in most cases, but it requires dealing with source changes in git repos instead of using config files. For now these changes stay outside my dotfiles repo and I&#39;m not sure about the best way to version and sync this type of configuration.&lt;/p&gt;
&lt;p&gt;One very pleasant aspect is how easily and fast the programs compile. They have hardly any dependencies and compilation is finished nearly at the same moment as you hit the enter key to call make.&lt;/p&gt;
&lt;p&gt;Since each of the programs handles only a single concern, you have to collect more of them than you&#39;re used to when you want to cover the usual amount of use cases. This allows freely mixing and matching different tools as you like. I like the general approach, but would have preferred a way to get a selection of recommended programs and configs and start using a known-good setup before fiddling with the details.&lt;/p&gt;
&lt;h2&gt;Additional Configuration&lt;/h2&gt;
&lt;h3&gt;xbindkeys&lt;/h3&gt;
&lt;p&gt;I want to be able to change the volume and screen brightness with the media keys and have a keybinding to turn off and lock my screen. This can be configured with &lt;a href=&quot;https://www.nongnu.org/xbindkeys/&quot;&gt;xbindkeys&lt;/a&gt; by assigning shell commands to certain keys. This has a nice unix feel to it, but needs some fiddling to get right. I also miss the visual feedback when changing brightness and volume.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Brightness +
&amp;quot;lux -a 100&amp;quot;
    XF86MonBrightnessUp 

# Brightness -
&amp;quot;lux -s 100&amp;quot;
    XF86MonBrightnessDown 


# Increase volume
&amp;quot;pactl set-sink-volume @DEFAULT_SINK@ $(pacmd list-sinks | awk &#39;/volume:/ {print int($3 * 1.2); exit}&#39;); pacmd list-sinks | awk &#39;/volume:/ {print $5; exit}&#39; | dzen2 -p 2&amp;quot;
   XF86AudioRaiseVolume
# Decrease volume
&amp;quot;pactl set-sink-volume @DEFAULT_SINK@ $(pacmd list-sinks | awk &#39;/volume:/ {print int($3 * 0.8); exit}&#39;); pacmd list-sinks | awk &#39;/volume:/ {print $5; exit}&#39; | dzen2 -p 2&amp;quot;
   XF86AudioLowerVolume
# Mute volume
&amp;quot;pactl set-sink-mute @DEFAULT_SINK@ toggle&amp;quot;
   XF86AudioMute

# Shift + super + L locks screen and turns display off
&amp;quot;slock &amp;amp; (sleep 1 &amp;amp;&amp;amp; xset dpms force off)&amp;quot;
   Shift + Mod4 + l

# turn screen off
&amp;quot;sleep 1.0 &amp;amp;&amp;amp; xset dpms force off&amp;quot;
   shift + mod4 + s

&amp;quot;systemctl suspend&amp;quot;
   shift + mod4 + z

&amp;quot;autorandr -c&amp;quot;
   control+shift + a

# toggle redshift
&amp;quot;killall redshift || redshift -l 52.5200:13.4050 -b 0.7:0.3 &amp;amp;&amp;quot;
   shift + mod4 + r
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Problems&lt;/h2&gt;
&lt;h3&gt;autolock&lt;/h3&gt;
&lt;p&gt;I didn&#39;t find an autolock setup that worked as well as I would like. My goal would be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lock screen when sleeping or after X minutes&lt;/li&gt;
&lt;li&gt;Turn screen off when locking&lt;/li&gt;
&lt;li&gt;Don&#39;t lock when watching fullscreen videos in Firefox&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I could not get the last point working reliably for me with xautolock or &lt;a href=&quot;https://github.com/jD91mZM2/xidlehook&quot;&gt;xidlehook&lt;/a&gt; (&lt;a href=&quot;https://github.com/jD91mZM2/xidlehook/issues/23&quot;&gt;bug report&lt;/a&gt;). I stopped using autolock and just manually lock my screen.&lt;/p&gt;
&lt;h3&gt;Versioning My Setup&lt;/h3&gt;
&lt;p&gt;As noted above, the suckless tools don&#39;t use config files, so they I can&#39;t put their configuration in version control as I am used to.&lt;/p&gt;
&lt;h3&gt;Lack of Good Predefined Setup&lt;/h3&gt;
&lt;p&gt;While the programs work well and reliably, using them is not just matter of installing and running them. Getting to a usable setup involves quite some time of choosing the right set of programs and configuration that works for you. I would have liked a known-good setup as a starting point.&lt;/p&gt;
&lt;!--
## Other simple tools

* tmux
* cmus

## Not relevant because not desktop

* smu
* entr

## Big software

* Web browser (Firefox, Chromium)
* Games (steam, etc)

## Web Apps

* GMail
* Spotify

--&gt;</content>
		<link href="https://www.karl.berlin/suckless-desktop.html"/>
		<id>tag:www.karl.berlin,2020-04-28:posts/suckless-desktop.md</id>
		<published>2020-04-28T19:37:01+02:00</published>
		<updated>2021-01-20T16:04:17+01:00</updated>
	</entry>
	<entry>
		<title>Exercises in Simplicity</title>
		<content type="html">&lt;h1&gt;Exercises in Simplicity&lt;/h1&gt;
&lt;p&gt;The more time I spent on software development, the more I come to appreciate simplicity. When software is buggy, slow or lacking features, that is annoying. But as long as it is simple, I can still understand the limitations and work around them or even fix them. When something goes wrong in a complex program, it is often not worth spending the huge amount of time necessary to even understand what is happening. Instead of learning about the problem your software solves, you are drowned in build tools, pre- and post-processors, too many layers of abstractions, many different APIs and huge amounts of dependencies.&lt;/p&gt;
&lt;p&gt;Dealing with simpler software is more satisfying for me and provides a faster way to deep understanding. I want to write a few posts about the different ways I approach simple software. Sometimes, I want to replace a program I use by something simpler, in other cases I just want to try something out in order to learn how much of the complexity of common solutions is really necessary.&lt;/p&gt;
&lt;h2&gt;Guidelines&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Simplicity is more important than features.&lt;/li&gt;
&lt;li&gt;Usability must not suffer too much.&lt;/li&gt;
&lt;li&gt;If I start with something minimal, some growth over time is ok. The important thing is a high ratio between benefit and increase of complexity.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Exercises&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;blog.html&quot;&gt;This web site itself&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Vim instead of IDE (see &lt;a href=&quot;https://github.com/karlb/dotfiles/tree/master/.vim&quot;&gt;my vim config&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;suckless-desktop.html&quot;&gt;Use suckless software on my desktop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;smu.html&quot;&gt;Hacking on a minimal markdown-like parser&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Inspiration&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://inf.ethz.ch/personal/wirth/Articles/LeanSoftware.pdf&quot;&gt;Niklaus Wirth - A Plea for Lean Software&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://suckless.org/philosophy/&quot;&gt;Suckless Philosophy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.goodreads.com/en/book/show/39996759&quot;&gt;John Ousterhout - A Philosophy of Software Design&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://youtu.be/pW-SOdj4Kkk&quot;&gt;Jonathan Blow - Preventing the Collapse of Civilization&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Quotes&lt;/h2&gt;
&lt;p&gt;Controlling complexity is the essence of computer programming.&lt;/p&gt;
&lt;p&gt;-- Brian Kernighan&lt;/p&gt;
&lt;p&gt;Simplicity is prerequisite for reliability.&lt;/p&gt;
&lt;p&gt;-- Edsger W. Dijkstra&lt;/p&gt;
&lt;p&gt;A program is like a poem: you cannot write a poem without writing it. Yet people talk about programming as if it were a production process and measure &quot;programmer productivity&quot; in terms of &quot;number of lines of code produced&quot;. In so doing they book that number on the wrong side of the ledger: We should always refer to &quot;the number of lines of code spent&quot;.&lt;/p&gt;
&lt;p&gt;-- Edsger W. Dijkstra&lt;/p&gt;</content>
		<link href="https://www.karl.berlin/simplicity.html"/>
		<id>tag:www.karl.berlin,2020-04-19:posts/simplicity.md</id>
		<published>2020-04-19T14:00:55+02:00</published>
		<updated>2021-01-06T13:28:35+01:00</updated>
	</entry>
</feed>
