<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>TIL by Rolf Erik Lekang</title>
        <link>https://rolflekang.com/til</link>
        <description>Today I Learned is a series of mini blogposts, about small things I learn every day. Partly for my own reference, but also in the hope that it will help someone else.</description>
        <lastBuildDate>Sat, 11 Apr 2026 19:43:24 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <atom:link href="https://rolflekang.com/api/feeds/til" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[LazyVim restore session]]></title>
            <link>https://rolflekang.com/til/lazyvim-restore-session</link>
            <guid>https://rolflekang.com/til/lazyvim-restore-session</guid>
            <pubDate>Tue, 23 Sep 2025 07:47:00 GMT</pubDate>
            <description><![CDATA[<p>My vim setup is based on LazyVim, which has way more features than I use on a daily basis. Today I discovered that it has a way to restore the previous session. It has been there in the splash screen all the time, but I haven’t really noticed it. When looking at the splash screen press <code>s</code> to open the buffers that where open the last time neovim quit.</p>
]]></description>
            <category>vim</category>
            <category>neovim</category>
        </item>
        <item>
            <title><![CDATA[uv based scripts with dependencies]]></title>
            <link>https://rolflekang.com/til/uv-based-scripts-with-dependencies</link>
            <guid>https://rolflekang.com/til/uv-based-scripts-with-dependencies</guid>
            <pubDate>Sun, 23 Feb 2025 23:00:00 GMT</pubDate>
            <description><![CDATA[<p>I came over a blogpost <a href="https://treyhunner.com/2024/12/lazy-self-installing-python-scripts-with-uv/"><strong>Lazy self-installing Python scripts with uv</strong></a> which described how you can use uv to declare dependencies for a script with a shebang. Thus, you can for instance use typer to create some scripts with validation and <code>--help</code> functionality. Like this example:</p>
<pre><code>#!/usr/bin/env -S uv run --script
# /// script
# dependencies = ["typer"]
# ///
import typer


def main(name: str):
    print(f"Hello {name}")


if __name__ == "__main__":
    typer.run(main)
</code></pre>
<p>This is pretty neat for those scripts you need something more than shell or even just python standard lib. One use case that comes to mind is <a href="https://xbarapp.com/">xbar</a> scripts. The <a href="https://treyhunner.com/2024/12/lazy-self-installing-python-scripts-with-uv/">linked blogpost</a> has more details on how it works if you are interested.</p>
]]></description>
            <category>python</category>
        </item>
        <item>
            <title><![CDATA[Adding a label to multiple Github repos ]]></title>
            <link>https://rolflekang.com/til/adding-a-label-to-multiple-github-repos </link>
            <guid>https://rolflekang.com/til/adding-a-label-to-multiple-github-repos </guid>
            <pubDate>Fri, 04 Oct 2024 07:00:00 GMT</pubDate>
            <description><![CDATA[<pre><code>for dir in */; do
    if [ -d "$dir/.git" ]; then
        echo "Setting $dir as default repository"
        (cd "$dir" &amp;&amp; gh repo set-default)
        
        echo "Adding label to repository in $dir"
        (cd "$dir" &amp;&amp; gh label create "security" --color "BD55B7" --description "Security issue, will be escalated on slack every day the pull-request is open.")
    else
        echo "Skipping $dir as it is not a git repository"
    fi
donee
</code></pre>
]]></description>
            <category>shell</category>
        </item>
        <item>
            <title><![CDATA[Custom errors  in rust]]></title>
            <link>https://rolflekang.com/til/custom-errors-in-rust</link>
            <guid>https://rolflekang.com/til/custom-errors-in-rust</guid>
            <pubDate>Tue, 02 Apr 2024 12:00:00 GMT</pubDate>
            <description><![CDATA[<p>On my journey to learn rust I have stumbled on the mismatch error type when using the question mark operator. I recently learned that a nice way to handle that is to have custom errors and implement the <code>From</code> trait for each of the errors that we need to handle. The question mark operator calls from implicitly.</p>
<p>In one of my side projects I have a function that fetches a RSS feed and then stores the items from the feed in a database with sqlx. To use the question mark operator in that function with all the results it gets from different libraries I use a <code>FetchError</code>. The possible errors in the function is <code>sqlx::Error</code>, <code>reqwest::Error</code>  and <code>feed_rs::parser::ParseFeedError</code>.</p>
<pre><code>use std::{error::Error, fmt::Display};
use feed_rs::parser::ParseFeedError;

#[derive(Debug, Clone)]
pub struct FetchError {
    pub message: String,
}

impl Display for FetchError {
    fn fmt(&amp;self, f: &amp;mut std::fmt::Formatter&lt;'_&gt;) -&gt; std::fmt::Result {
        write!(f, "{}", self.message)
    }
}

impl Error for FetchError {}

impl From&lt;reqwest::Error&gt; for FetchError {
    fn from(err: reqwest::Error) -&gt; Self {
        FetchError { message: err.to_string() }
    }
}

impl From&lt;sqlx::Error&gt; for FetchError {
    fn from(err: sqlx::Error) -&gt; Self {
        FetchError { message: err.to_string() }
    }
}

impl From&lt;ParseFeedError&gt; for FetchError {
    fn from(err: ParseFeedError) -&gt; Self {
        FetchError { message: err.to_string() }
    }
}
</code></pre>
]]></description>
            <category>rust</category>
        </item>
        <item>
            <title><![CDATA[if let]]></title>
            <link>https://rolflekang.com/til/if-let</link>
            <guid>https://rolflekang.com/til/if-let</guid>
            <pubDate>Sun, 17 Mar 2024 14:31:00 GMT</pubDate>
            <description><![CDATA[<p>I recently read about <code>if let</code> in rust and thought it was neat. I don’t think it has been something like this in any of the other languages I have worked with that had pattern matching and ADTs.</p>
<p>Instead of </p>
<pre><code>match optionalValue {
    Some(value) =&gt; println!(value)
    _ =&gt; ()
}
</code></pre>
<p>it is possible to do</p>
<pre><code>if let Some(value) {
    println!(value)
}
</code></pre>
]]></description>
            <category>rust</category>
        </item>
        <item>
            <title><![CDATA[Long poll a SSR Next.js page]]></title>
            <link>https://rolflekang.com/til/long-poll-a-nextjs-page</link>
            <guid>https://rolflekang.com/til/long-poll-a-nextjs-page</guid>
            <pubDate>Sat, 17 Feb 2024 12:21:00 GMT</pubDate>
            <description><![CDATA[<p>I figured out that doing long polling with Next.js is pretty straightforward. I came over <a href="https://www.joshwcomeau.com/nextjs/refreshing-server-side-props/">a great post on how to reload the data from </a><a href="https://www.joshwcomeau.com/nextjs/refreshing-server-side-props/"><code>getServerSideProps</code></a><a href="https://www.joshwcomeau.com/nextjs/refreshing-server-side-props/"> from the client</a> by Josh Comeau. In short calling <code>router.replace(router.asPath)</code> on will reload the data of the current page in the same manner as clicking a next link would. Thus, we can put this in an effect with setInterval and get periodic reloads of the props. I put this in a reusable react hook below.</p>
<pre><code>import { useRouter } from "next/router";
import { useEffect } from "react";

export function useLongPollRefresh(wait: number = 5000) {
    const router = useRouter()

    useEffect(() =&gt; {
    const id = setInterval(() =&gt; {
      if (/^\d+$/.test(router.query.slug as string)) {
        router.replace(router.asPath);
      }
    }, wait);
    return () =&gt; clearInterval(id);
  }, [router]);
}
</code></pre>
<p>I use this technique in a few places were the user creates a new entity that triggers background jobs that adds more data to the entity. It makes the user action seem snappier and then without having to add new api endpoints to long poll the extra data will show up when it s ready.</p>
]]></description>
            <category>nextjs</category>
            <category>web</category>
        </item>
        <item>
            <title><![CDATA[Using for_each to loop lists in terraform]]></title>
            <link>https://rolflekang.com/til/for-each-terraform-lists</link>
            <guid>https://rolflekang.com/til/for-each-terraform-lists</guid>
            <pubDate>Fri, 02 Feb 2024 14:31:00 GMT</pubDate>
            <description><![CDATA[<p>In terraform it is quite common to have lists of things that should result in a list of resources. It is also common to use count to loop over those lists to create the similar resources. It would look something like this the code below if we wanted to create a few cloud storage buckets with the same config. Not the most likely thing to do, but a resource that will give a concise example.</p>
<pre><code>locals {
  buckets = ["bucket-one", "bucket-two"]
}
resource "google_storage_bucket" "generic-buckets" {
  count         = length(local.buckets)
  name          = local.buckets[count.index]
  location      = "EU"
}
</code></pre>
<p>This will generate two buckets with these state labels in terraform:</p>
<ul>
<li><code>resource.google_storage_bucket.generic-buckets[0]</code></li>
<li><code>resource.google_storage_bucket.generic-buckets[1]</code></li>
</ul>
<p>The problem with this is that if <code>bucket-one</code> is no longer needed and we remove it from the list then terraform will destroy both buckets and recreate <code>bucket-two</code>. That problem will disappear if <code>for_each</code> is used instead of count. </p>
<pre><code>locals {
  buckets = ["bucket-one", "bucket-two"]
}
resource "google_storage_bucket" "generic-buckets" {
  for_each      = { for i, k in toset(local.buckets) : k =&gt; i }
  name          = each.key
  location      = "EU"
}
</code></pre>
<p>The code above creates the same buckets, but the state labels will now be using the value as the index:</p>
<ul>
<li><code>resource.google_storage_bucket.generic-buckets[”bucket-one”]</code></li>
<li><code>resource.google_storage_bucket.generic-buckets[”bucket-two”]</code></li>
</ul>
<p>This makes it possible to both remove items from the list and reorder them without destroying and recreating the buckets that has not been removed from the list.</p>
]]></description>
            <category>terraform</category>
        </item>
        <item>
            <title><![CDATA[vim-visual-multi]]></title>
            <link>https://rolflekang.com/til/vim-visual-multi</link>
            <guid>https://rolflekang.com/til/vim-visual-multi</guid>
            <pubDate>Thu, 01 Feb 2024 07:57:00 GMT</pubDate>
            <description><![CDATA[<p>I recently discovered a vim plugin called <a href="https://github.com/mg979/vim-visual-multi">vim-visual-multi</a>. It enables matching words for multiple cursors in a similar fashion as cmd/ctrl+d from Sublime Text, Atom and VS Code. It is one of the few things I have really been missing since moving to neovim.</p>
]]></description>
            <category>vim</category>
        </item>
        <item>
            <title><![CDATA[Scrolling bookmarklets]]></title>
            <link>https://rolflekang.com/til/scrolling-bookmarklets</link>
            <guid>https://rolflekang.com/til/scrolling-bookmarklets</guid>
            <pubDate>Thu, 11 Jan 2024 11:52:00 GMT</pubDate>
            <description><![CDATA[<p>Bookmarklets are nice to make small tiny utilities. They are bookmarks that contain javascript code prefixed with <code>javascript:</code>. The javascript code will run every time the bookmarklet is clicked.</p>
<p>These bookmarklets will scroll 500px up and down and are nice to have when some page disables the scrolling for some reason. Typical example is when showing ad blocker warning modal.</p>
<p>Up: <code>javascript:window.scrollTo(0, window.scrollY - 500);</code>
Down: <code>javascript:window.scrollTo(0, window.scrollY + 500);</code></p>
]]></description>
            <category>bookmarklets</category>
        </item>
        <item>
            <title><![CDATA[Switch configs in neovim]]></title>
            <link>https://rolflekang.com/til/switch-configs-in-neovim</link>
            <guid>https://rolflekang.com/til/switch-configs-in-neovim</guid>
            <pubDate>Wed, 08 Nov 2023 11:40:00 GMT</pubDate>
            <description><![CDATA[<p>Neovim recently got a feature for selecting different neovim configs based on an environment variable. The variable is <code>NVIM_APPNAME</code> and it is set to <code>“nvim"</code> as default. It is used in the path when looking up configuration like this <code>~/.config/{NVIM_APPNAME}</code>. Thus, if it is set to lazyvim the config will be loaded from <code>~/.config/lazyvim</code>. </p>
<p>To use it with vim, prefix the nvim command with the environment variable.</p>
<pre><code>NVIM_APPNAME=lazyvim nvim
</code></pre>
<p>This is super useful when testing out different premade configs, but it can also be useful when making changes to the config without disrupting the daily workflow by having two clones of your own config into <code>~/.config/nvim</code> and <code>~/.config/nvim-test</code></p>
]]></description>
            <category>vim</category>
        </item>
        <item>
            <title><![CDATA[zshenv]]></title>
            <link>https://rolflekang.com/til/zshenv</link>
            <guid>https://rolflekang.com/til/zshenv</guid>
            <pubDate>Tue, 24 Oct 2023 06:38:00 GMT</pubDate>
            <description><![CDATA[<p><code>zshenv</code> is similar to <code>zshrc</code>, but it is loaded for all kinds of shells. <code>zshrc</code> on the other hand is only sourced for interactive shells. I discovered this while trying to use mosh-server on a mac. mosh-server is installed with brew so the binary is in <code>/opt/homebrew/bin/</code> and since I had only setup the path for homebrew in <code>zshrc</code> it was not accessible through non-interactive ssh connections that the mosh clients use to start the mosh-server. Now my <code>zshenv</code> looks like this:</p>
<pre><code>PATH=/opt/homebrew/bin/:$PATH
</code></pre>
]]></description>
            <category>shell</category>
        </item>
        <item>
            <title><![CDATA[Always use os clipboard in neovim]]></title>
            <link>https://rolflekang.com/til/clipboard-neovim</link>
            <guid>https://rolflekang.com/til/clipboard-neovim</guid>
            <pubDate>Mon, 23 Oct 2023 09:00:00 GMT</pubDate>
            <description><![CDATA[<p>In neovim it is possible to use the os clipboard manually, but it is also possible to make it the default behaviour with:</p>
<h3>Viml</h3>
<pre><code>set clipboard+=unnamedplus
</code></pre>
<h3>Lua</h3>
<pre><code>vim.opt.clipboard = 'unnamedplus'
</code></pre>
<p>See <code>:help clipboard</code> for more.</p>
]]></description>
            <category>vim</category>
        </item>
        <item>
            <title><![CDATA[Pipe in neovim]]></title>
            <link>https://rolflekang.com/til/pipe-in-neovim</link>
            <guid>https://rolflekang.com/til/pipe-in-neovim</guid>
            <pubDate>Tue, 10 Oct 2023 13:15:00 GMT</pubDate>
            <description><![CDATA[<p>I wish I new about this way sooner. It is possible to chain tasks with pipe(<code>|</code>) in neovim. It can be superuseful for running tasks on the current file. I often use it for running tests in a python project now:</p>
<pre><code>:w | !pytest %
</code></pre>
<p>This will first write the file and then run tests on the current path.</p>
]]></description>
            <category>vim</category>
        </item>
        <item>
            <title><![CDATA[sudo -k and sudo -K]]></title>
            <link>https://rolflekang.com/til/sudo-k</link>
            <guid>https://rolflekang.com/til/sudo-k</guid>
            <pubDate>Fri, 06 Oct 2023 12:00:00 GMT</pubDate>
            <description><![CDATA[<p>I recently found something pretty useful in sudo that I wish I new about ages ago. This  is especially useful for dotfiles scripts and similar things where you need sudo to do that one thing, like disable the sound effect on boot of an apple device. The problem with regular sudo is that evrything that happens after that will have a sudo session so if some thing that is downloaded from the internet like a package tries sudo in its install script it will just work.</p>
<p>From <code>sudo --help</code> the following is printed:</p>
<pre><code>-K, --remove-timestamp        remove timestamp file completely
-k, --reset-timestamp         invalidate timestamp file
</code></pre>
<h3><code>sudo -K</code></h3>
<p>This will completely remove the timestamp of the current sudo session. Thus, it is not possible to use together with a command since the command will not be able to run before the timestamp is deleted.</p>
<h3><code>sudo -k</code></h3>
<p>This will invalidate the timestamp by changing it to the finish time of the last command. This means that it can both be used standalone <code>sudo -k</code> and with a command like <code>sudo -k apt install sl</code>.</p>
]]></description>
            <category>shell</category>
        </item>
        <item>
            <title><![CDATA[To globally mock functions in python]]></title>
            <link>https://rolflekang.com/til/to-globally-mock-functions-in-python</link>
            <guid>https://rolflekang.com/til/to-globally-mock-functions-in-python</guid>
            <pubDate>Thu, 05 Oct 2023 13:00:00 GMT</pubDate>
            <description><![CDATA[<p>In one of the python projects I am working on we have this central method to publish events to pubsub. We wanted to have a global mock of pubsub to avoid having to explicitly mocking it many places. Turns out this is possible with a wrapper function and pytest fixtures:</p>
<pre><code>def _publish_pubsub_event(topic: str, message: PubSubMessage) -&gt; None:
    PubSubPublisher(settings.GCLOUD_PROJECT, topic).publish(message)


def publish_pubsub_event(topic: str, message: PubSubMessage) -&gt; None:
    return _publish_pubsub_event(topic, message)
</code></pre>
<p>The <code>publish_pubsub_event</code> gets imported everywhere so to mock it we need to mock it in the context is being used, but the <code>_publish_pubsub_event</code> is not being used anywhere else than here so we can mock it in the context of this file.</p>
<p>In the module level <code>conftest.py</code> there is a fixture with <code>autouse=True</code> and it mocks the <code>_publish_pubsub_event</code> and returns the mock so that the fixture can be loaded explicitly to assert that the mock was called in the cases we want to verify the messages sent.</p>
<pre><code>@pytest.fixture(autouse=True)
def mock_pubsub_publish(mocker: MockerFixture):
    return mocker.patch("app.pubsub._publish_pubsub_event")
</code></pre>
]]></description>
            <category>python</category>
        </item>
        <item>
            <title><![CDATA[Switch to previous session in tmux]]></title>
            <link>https://rolflekang.com/til/switch-to-previous-session-in-tmux</link>
            <guid>https://rolflekang.com/til/switch-to-previous-session-in-tmux</guid>
            <pubDate>Tue, 14 Feb 2023 08:00:00 GMT</pubDate>
            <description><![CDATA[<p>I recently setup <code>&lt;prefix&gt; +</code> to switch to the previous tmux session. Super handy to switch fast between two projects, for example backend and frontend.</p>
<pre><code>bind + switch-client -l
</code></pre>
]]></description>
            <category>tmux</category>
        </item>
        <item>
            <title><![CDATA[tx]]></title>
            <link>https://rolflekang.com/til/tx-the-tmux-helper</link>
            <guid>https://rolflekang.com/til/tx-the-tmux-helper</guid>
            <pubDate>Fri, 03 Feb 2023 12:00:00 GMT</pubDate>
            <description><![CDATA[<p>I have recently been trying to do a proper setup of tmux and as a part of it i made this little shell function to make re-attaching sessions a bit easier. It will give fzf prompt with available sessions if called without a session name. On the other hand when called with a session name it will check if it exists and attach or create based on the result. </p>
<pre><code>tx() {
  if [ -z ${1+x} ]; then
    session=$(tmux list-sessions | fzf | awk '{print $1}' | sed 's/:$//' )
    tmux a -t $session
  else
    if tmux list-sessions | grep $1 2&gt;&amp;1 &gt; /dev/null; then
      tmux a -t $1
    else
      tmux new -s $1
    fi
  fi
}

_tx() {
  sessions=$(tmux list-sessions 2&gt;/dev/null)
  if [[ -z "$1" ]]; then
     echo $sessions | awk '{split($0,a,":"); print a[1]}'
  else
    echo $sessions | awk '{split($0,a,":"); print a[1]}' | grep --color=never $1
  fi
}

complete -C _tx tx
</code></pre>
]]></description>
            <category>tmux</category>
            <category>shell</category>
        </item>
        <item>
            <title><![CDATA[Alias filetypes]]></title>
            <link>https://rolflekang.com/til/alias-filetypes</link>
            <guid>https://rolflekang.com/til/alias-filetypes</guid>
            <pubDate>Sun, 29 Jan 2023 19:00:00 GMT</pubDate>
            <description><![CDATA[<p>Creating aliases with <code>-s</code> enables aliasing commands too files types. Thus, with the alias <code>alias -s txt=less</code> turns <code>./file.txt</code> into <code>less ./file.txt</code></p>
<p>Other examples:</p>
<pre><code>alias -s md=glow
alias -s html=open
alias -s txt=less
alias -s json=jq
</code></pre>
]]></description>
            <category>shell</category>
        </item>
        <item>
            <title><![CDATA[Recent branches with fzf]]></title>
            <link>https://rolflekang.com/til/recent-branches-with-fzf</link>
            <guid>https://rolflekang.com/til/recent-branches-with-fzf</guid>
            <pubDate>Sat, 21 Jan 2023 12:30:00 GMT</pubDate>
            <description><![CDATA[<p>Not something I learned today, but i recently configured a alias for recent branches with fuzzy search with <code>fzf</code>. It will show the branches sorted by the date of the head commit when doing <code>git recent</code>. Selecting one with fzf will then do a git checkout of that branch.</p>
<p>Add the following under aliases in gitconfig:</p>
<pre><code>recent = "!git branch --sort=-committerdate --format=\"%(committerdate:relative)%09%(refname:short)\" | fzf --ansi | awk 'BEGIN{FS=\"\t\"} {print $2}' | xargs git checkout"
</code></pre>
]]></description>
            <category>git</category>
            <category>fzf</category>
        </item>
        <item>
            <title><![CDATA[zz in vim centers line on screen]]></title>
            <link>https://rolflekang.com/til/zz-in-vim</link>
            <guid>https://rolflekang.com/til/zz-in-vim</guid>
            <pubDate>Sun, 15 Jan 2023 11:00:00 GMT</pubDate>
            <description><![CDATA[<p>Learned today that in normal mode in vim typing <code>zz</code> will center the active line in the middle of the screen.</p>
<p></p>
]]></description>
            <category>vim</category>
        </item>
    </channel>
</rss>