TIL
Adding a label to multiple Github repos
04.10.2024for 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" fidonee
Custom errors in rust
02.04.2024On 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.2024I 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.2024I 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.2024In 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.2024I 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.2024Bookmarklets 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.2023Neovim 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.2023zshenv
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.2023In 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.2023I 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.2023I 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.2023In 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.2023I 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.2023I 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.2023Creating 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=glowalias -s html=openalias -s txt=lessalias -s json=jq
Recent branches with fzf
21.01.2023Not 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.2023Learned today that in normal mode in vim typing zz
will center the active line in the middle of the screen.