TIL

Adding a label to multiple Github repos

04.10.2024
for dir in */; do
if [ -d "$dir/.git" ]; then
echo "Setting $dir as default repository"
(cd "$dir" && gh repo set-default)
echo "Adding label to repository in $dir"
(cd "$dir" && 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

Custom errors in rust

02.04.2024

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 From trait for each of the errors that we need to handle. The question mark operator calls from implicitly.

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 FetchError. The possible errors in the function is sqlx::Error, reqwest::Error and feed_rs::parser::ParseFeedError.

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(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
impl Error for FetchError {}
impl From<reqwest::Error> for FetchError {
fn from(err: reqwest::Error) -> Self {
FetchError { message: err.to_string() }
}
}
impl From<sqlx::Error> for FetchError {
fn from(err: sqlx::Error) -> Self {
FetchError { message: err.to_string() }
}
}
impl From<ParseFeedError> for FetchError {
fn from(err: ParseFeedError) -> Self {
FetchError { message: err.to_string() }
}
}

if let

17.03.2024

I recently read about if let 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.

Instead of

match optionalValue {
Some(value) => println!(value)
_ => ()
}

it is possible to do

if let Some(value) {
println!(value)
}

Long poll a SSR Next.js page

17.02.2024

I figured out that doing long polling with Next.js is pretty straightforward. I came over a great post on how to reload the data from getServerSideProps from the client by Josh Comeau. In short calling router.replace(router.asPath) 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.

import { useRouter } from "next/router";
import { useEffect } from "react";
export function useLongPollRefresh(wait: number = 5000) {
const router = useRouter()
useEffect(() => {
const id = setInterval(() => {
if (/^\d+$/.test(router.query.slug as string)) {
router.replace(router.asPath);
}
}, wait);
return () => clearInterval(id);
}, [router]);
}

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.

Using for_each to loop lists in terraform

02.02.2024

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.

locals {
buckets = ["bucket-one", "bucket-two"]
}
resource "google_storage_bucket" "generic-buckets" {
count = length(local.buckets)
name = local.buckets[count.index]
location = "EU"
}

This will generate two buckets with these state labels in terraform:

  • resource.google_storage_bucket.generic-buckets[0]
  • resource.google_storage_bucket.generic-buckets[1]

The problem with this is that if bucket-one is no longer needed and we remove it from the list then terraform will destroy both buckets and recreate bucket-two. That problem will disappear if for_each is used instead of count.

locals {
buckets = ["bucket-one", "bucket-two"]
}
resource "google_storage_bucket" "generic-buckets" {
for_each = { for i, k in toset(local.buckets) : k => i }
name = each.key
location = "EU"
}

The code above creates the same buckets, but the state labels will now be using the value as the index:

  • resource.google_storage_bucket.generic-buckets[”bucket-one”]
  • resource.google_storage_bucket.generic-buckets[”bucket-two”]

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.

vim-visual-multi

01.02.2024

I recently discovered a vim plugin called vim-visual-multi. 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.

Scrolling bookmarklets

11.01.2024

Bookmarklets are nice to make small tiny utilities. They are bookmarks that contain javascript code prefixed with javascript:. The javascript code will run every time the bookmarklet is clicked.

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.

Up: javascript:window.scrollTo(0, window.scrollY - 500); Down: javascript:window.scrollTo(0, window.scrollY + 500);

Switch configs in neovim

08.11.2023

Neovim recently got a feature for selecting different neovim configs based on an environment variable. The variable is NVIM_APPNAME and it is set to “nvim" as default. It is used in the path when looking up configuration like this ~/.config/{NVIM_APPNAME}. Thus, if it is set to lazyvim the config will be loaded from ~/.config/lazyvim.

To use it with vim, prefix the nvim command with the environment variable.

NVIM_APPNAME=lazyvim nvim

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 ~/.config/nvim and ~/.config/nvim-test

zshenv

24.10.2023

zshenv is similar to zshrc, but it is loaded for all kinds of shells. zshrc 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 /opt/homebrew/bin/ and since I had only setup the path for homebrew in zshrc it was not accessible through non-interactive ssh connections that the mosh clients use to start the mosh-server. Now my zshenv looks like this:

PATH=/opt/homebrew/bin/:$PATH

Always use os clipboard in neovim

23.10.2023

In neovim it is possible to use the os clipboard manually, but it is also possible to make it the default behaviour with:

Viml

set clipboard+=unnamedplus

Lua

vim.opt.clipboard = 'unnamedplus'

See :help clipboard for more.

Pipe in neovim

10.10.2023

I wish I new about this way sooner. It is possible to chain tasks with pipe(|) 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:

:w | !pytest %

This will first write the file and then run tests on the current path.

sudo -k and sudo -K

06.10.2023

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.

From sudo --help the following is printed:

-K, --remove-timestamp remove timestamp file completely
-k, --reset-timestamp invalidate timestamp file

sudo -K

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.

sudo -k

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 sudo -k and with a command like sudo -k apt install sl.

To globally mock functions in python

05.10.2023

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:

def _publish_pubsub_event(topic: str, message: PubSubMessage) -> None:
PubSubPublisher(settings.GCLOUD_PROJECT, topic).publish(message)
def publish_pubsub_event(topic: str, message: PubSubMessage) -> None:
return _publish_pubsub_event(topic, message)

The publish_pubsub_event gets imported everywhere so to mock it we need to mock it in the context is being used, but the _publish_pubsub_event is not being used anywhere else than here so we can mock it in the context of this file.

In the module level conftest.py there is a fixture with autouse=True and it mocks the _publish_pubsub_event 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.

@pytest.fixture(autouse=True)
def mock_pubsub_publish(mocker: MockerFixture):
return mocker.patch("app.pubsub._publish_pubsub_event")

Switch to previous session in tmux

14.02.2023

I recently setup <prefix> + to switch to the previous tmux session. Super handy to switch fast between two projects, for example backend and frontend.

bind + switch-client -l

tx

03.02.2023

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.

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>&1 > /dev/null; then
tmux a -t $1
else
tmux new -s $1
fi
fi
}
_tx() {
sessions=$(tmux list-sessions 2>/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

Alias filetypes

29.01.2023

Creating aliases with -s enables aliasing commands too files types. Thus, with the alias alias -s txt=less turns ./file.txt into less ./file.txt

Other examples:

alias -s md=glow
alias -s html=open
alias -s txt=less
alias -s json=jq

Recent branches with fzf

21.01.2023

Not something I learned today, but i recently configured a alias for recent branches with fuzzy search with fzf. It will show the branches sorted by the date of the head commit when doing git recent. Selecting one with fzf will then do a git checkout of that branch.

Add the following under aliases in gitconfig:

recent = "!git branch --sort=-committerdate --format=\"%(committerdate:relative)%09%(refname:short)\" | fzf --ansi | awk 'BEGIN{FS=\"\t\"} {print $2}' | xargs git checkout"

zz in vim centers line on screen

15.01.2023

Learned today that in normal mode in vim typing zz will center the active line in the middle of the screen.

zz2.gif