A place where I capture raw, quick notes worth remembering.

June 29, 2024

linux emacs

Sync Fastmail with isync

As an Emacs enthusiast, I’ve found Mu4e to be an excellent email client. While Emacs can send emails, it can’t fetch and sync them efficiently. Enter isync, the perfect companion to fill this gap.

Setting up this duo took some digging, so I’m sharing my configuration to save you time and headaches.

Here’s what my setup accomplishes:

  1. Syncs all mailboxes, excluding junk and notes.
  2. Renames “Sent Items” to “Sent” for simplicity.
  3. Encrypts your password on disk using GPG.

First, I recommend to install the latest version of isync, if you are on the Mac, use brew install isync --HEAD. Then, you’ll need to create an encrypted password file. In Emacs, run the epa-encrypt-file command.

Now, let’s dive into the heart of the setup. Here’s my .mbsyncrc configuration file:

# Petar account on Fastmail.
IMAPAccount petar
Host imap.fastmail.com
Port 993
AuthMechs LOGIN
User [email protected]
PassCmd "gpg -q --for-your-eyes-only --no-tty -d ~/.mbsync-password-petar.gpg"
TLSType IMAPS
TLSVersions +1.2

IMAPStore petar-remote
Account petar

# Petar Local.
MaildirStore petar-local
Path ~/Mail/Petar/
Inbox ~/Mail/Petar/Inbox
Trash ~/Mail/Petar/Trash/
SubFolders Verbatim

# Sync everything besides sent and junk.
Channel sync-petar-all
Far :petar-remote:
Near :petar-local:
Patterns * !"Sent Items" !"Junk Mail" !"Notes"
Expunge None
CopyArrivalDate yes
Sync All
Create Near
SyncState *

# Sync and rename the sent items
Channel sync-petar-sent
Far :petar-remote:"Sent Items"
Near :petar-local:Sent
Expunge None
CopyArrivalDate yes
Sync All
Create Near
SyncState *

Group petar
Channel sync-petar-all
Channel sync-petar-sent

With this setup, you’ll have all your email on disk, ready to use it in Emacs and Mu4e.

Permalink

June 28, 2024

emacs lisp

From Python to Emacs Lisp

Ever feel like your brilliant blog ideas evaporate faster than spilled coffee on a hot keyboard?

If you’re like me, the lower the barrier to creating a note for your blog, the better. I started with a Python script to streamline my blog writing process, but it wasn’t quite hitting the mark. It required me to open the terminal, run the command, and then open the file in Emacs. The experience felt clunky.

Then it hit me: I’m using Emacs, why the heck am I doing this in Python.

I decided to port my Python script to Emacs Lisp, and not only was it easier to write, but the overall experience also improved significantly. It prompts you with a few questions and opens a buffer for you to start writing.

For reference, here’s the code that I ended up writing.

(defun pet/write-blog ()
  "Create a new blog note or post."
  (interactive)
  (let* ((is-note (y-or-n-p "Do you want to write a note? "))
         (title (read-string "What is the title? "))
         (tags (read-string "Do you want to tag it? "))
         (filename (concat (pet--slugify title) ".md"))
         (dir-type (if is-note "note" "post"))
         (path (expand-file-name
                filename
                (expand-file-name
                 (concat "content/" dir-type "s") pet/blog-directory)))
         (date (format-time-string "%Y-%m-%d"))
         (replacements
          `(("${title}" . ,title)
            ("${date}" . ,date)
            ("${type}" . ,dir-type)
            ("${taxonomies}" . ,(format (if is-note "tags: [%s]" "categories: [%s]") tags))
            ("${tags}" . ,tags))))
    (find-file path)
    (insert pet/blog-frontmatter) ;; insert template for frontmatter
    (goto-char (point-min)) ;; beginning of buffer, so we can replace the title
    (pet--replace-strings replacements)
    (goto-char (point-max))))

(defun pet--slugify (s)
  "Create a slug from string S."
  (replace-regexp-in-string "^-\\|-$" ""
    (replace-regexp-in-string
     "[^a-z0-9]+" "-" (downcase s))))

(defun pet--replace-strings (replacement-list)
  "Replace multiple strings in the current buffer.

REPLACEMENT-LIST is an alist where each element is a cons cell (SEARCH
. REPLACE).  For each pair, all occurrences of SEARCH are replaced with
REPLACE."
  (save-excursion
    (dolist (rep replacement-list)
      (goto-char (point-min))
      (while (search-forward (car rep) nil t)
        (replace-match (cdr rep) t t)))))

Now it’s just one command pet/write-blog to start writing a new note, just like the one you are reading right now.

Permalink

June 27, 2024

life

Embrace the Challenge of Learning: Insights from Andrej Karpathy

Recently, I came across a quote from Andrej Karpathy, whose videos and teaching style on YouTube I greatly admire. His words resonated deeply with me, serving as a powerful reminder that genuine learning and progress are supposed to be challenging.

In it he outlines three steps to mastering a subject:

  • Depth Over Breadth: Focus on completing concrete projects and learning what you need as you go, rather than trying to cover everything superficially.
  • Teach What You Learn: Summarize and teach the material in your own words to reinforce your understanding.
  • Personal Progress: Measure your progress against your past self, not others.

Here’s the full quote:

There are a lot of videos on YouTube/TikTok etc. that give the appearance of education, but if you look closely they are really just entertainment. This is very convenient for everyone involved : the people watching enjoy thinking they are learning (but actually they are just having fun).

Learning is not supposed to be fun. The primary feeling should be that of effort. It should look a lot less like that “10 minute full body” workout from your local digital media creator and a lot more like a serious session at the gym. You want the mental equivalent of sweating.

I find it helpful to explicitly declare your intent up front as a sharp, binary variable in your mind. If you are consuming content: are you trying to be entertained or are you trying to learn? And if you are creating content: are you trying to entertain or are you trying to teach? You’ll go down a different path in each case. Attempts to seek the stuff in between actually clamp to zero.

So for those who actually want to learn. Unless you are trying to learn something narrow and specific, close those tabs with quick blog posts. Close those tabs of “Learn XYZ in 10 minutes”. Consider the opportunity cost of snacking and seek the meal - the textbooks, docs, papers, manuals, longform. Allocate a 4 hour window. Don’t just read, take notes, re-read, re-phrase, process, manipulate, learn.

How to become an expert at a thing:

  1. Iteratively take on concrete projects and accomplish them depth wise, learning “on demand” (ie don’t learn bottom up breadth wise).
  2. Teach/summarize everything you learn in your own words.
  3. Only compare yourself to younger you, never to others.

Permalink

April 20, 2024

elixir exercism

Piping arithmetic in Elixir

For an exercism exercise I wanted to do multiplication with the pipe operator, and simply using * does not work, neither does (*).

Now, it turns out the arithmetic operations are functions part of the Kernel module, and you can also call them as an actual function with Kernel.*(2, 2), which is equal to writing 2 * 2.

This enables you do do arithmetic as part of the pipe operation, see:

def monthly_rate(hourly_rate, discount) do
  hourly_rate
  |> daily_rate
  |> apply_discount(discount)
  |> Kernel.*(22)
  |> ceil()
end

Permalink

April 20, 2024

testing elixir

Shorter feedback loops with Elixir tests

In Ruby, you have the --fail-fast flag, which stops running the test suite at the first failed test. Convenient to get shorter feedback loops, with a long running test suite.

In Elixir, you can achieve the same with:

mix test --max-failures 1

And then when a test fails, you fix it, and make sure that it works with:

mix test --failed

That makes for shorter feedback loops!

Permalink

Hello World, strings in Zig

I am doing the Exercism exercises for Zig and decided to document my progress since, with every exercise, I learn new and interesting information.

The first exercise is doing a “Hello World” and the end solution is simple:

pub fn hello() []const u8 {
    return "Hello, World!";
}

The interesting part is that Zig represents string literals as []const u8, which is an array of bytes.

const foo = "Hello" is (almost) the same as const foo = [_]u8{ 'H', 'e', 'l', 'l', 'o' }; Let’s break it down:

  • const means it’s a constant value or immutable.
  • [_]u8 means it’s an array of bytes.
  • The compiler will infer the [_] to [5].

Where the string uses double quotes (“H”), and the individual characters (‘H’) use single quotes.

But there is more because a string also contains a sentinel value. A sentinel value is something that indicates the end of a sequence. A popular choice of a sentinel value is the value 0, also called the null character.

Zig supports sentinel-terminated arrays, slices, and pointers:

//.    This is taken from Ziglings, exercise 76.
//     const a: [4:0]u32       =  [4:0]u32{1, 2, 3, 4};
//     const b: [:0]const u32  = &[4:0]u32{1, 2, 3, 4};

Array a stores five values, the last of which is a 0. Array b is only allowed to point to zero-terminated arrays.

Now we can determine the actual type of a string in Zig:

@TypeOf("foo") == *const [3:0]u8

Translated to English, a string is a “constant pointer to a null-terminated fixed-size array of u8”.

Now, why would you still have a 0 at the end when you know the size of the array? That’s because Zig strings are compatible with C strings, which are also null-terminated.

If you want to learn more about strings, I can recommend the Zig / Strings in 5 minutes article.

Permalink

April 15, 2024

life

Kaizen Principle

Kaizen is a Japanese term for “continuous improvement”, the Kaizen principle is the idea that small, continuous changes can lead to significant improvements.

I’m often reminded of this quote by one of my heroes Joe Armstrong, who phrased it as follows:

Make it work, then make it beautiful. Then, if you really, really have to, make it fast. 90 percent of the time, if you make it beautiful, it will already be fast. So, really, just make it beautiful!

Permalink

April 14, 2024

elixir asdf

Install Elixir with asdf

The following is what I use to setup Elixir on my machines, where I want to make sure that I can jump to all definitions of Elixir library functions.

First, I install asdf. I go the Homebrew and Fish route, but you can also do it from the source and use your shell of choice.

Before I install Erlang, I make sure to set the right flags, because I don’t want Java or build any GUI stuff:

KERL_BUILD_DOCS="yes"
KERL_CONFIGURE_OPTIONS="--disable-debug --without-javac --without-wx"

After that, I go to the asdf-erlang repository, check that I have all the build requirements and install Erlang with:

asdf plugin add erlang https://github.com/asdf-vm/asdf-erlang.git

# check available Erlang versions
asdf list all erlang

# install the latest version
asdf install erlang 26.2.4

# enable it globally
asdf global erlang 26.2.4

Now, let’s install Elixir. Go to the asdf-elixir repository, make sure you have unzip installed, and do the following.

asdf plugin-add elixir https://github.com/asdf-vm/asdf-elixir.git

# check all available versions
asdf list all elixir

# install the latest version, but! Install by source, so we can jump to the definition of Elixir functions
asdf install elixir ref:v1.16.2

# enable elixir globally
asdf global elixir ref:v1.16.2

Now, it says it in the comments, but the important difference is that I install Elixir by reference instead of the pre-compiled versions because I also like to be able to jump to definitions of Elixir library functions.

Permalink

Switching between devices with Syncthing

I have a desktop machine running Linux and a Macbook running MacOS, and I often switch between devices while working on a project.

Now, I can sync states using Git, but I often find that doing so makes me create pointless commits just to sync states.

That’s where I started using Syncthing on all my devices. I’ll leave installing Syncthing for you, the reader, but there is one handy trick I think you need to know.

If you create a .stignore file in your project root, and you add:

#include .gitignore

It will take your .gitnore and exclude the items in it from being synced. This is helpful for reducing the size of the sync but also means that you won’t sync build artifacts, which may not be compatible across devices.

Try it out!

Permalink

March 25, 2024

zig testing

Test selection with Zig

With Zig, I usually run zig build test to run all the tests. However, sometimes, I need to run a single test, and that option is not available through the build command.

In this case, I learned you can use the zig test command along with the --test-filter option.

For example, to test a single test named “lexer initialization” located in src/lexer/lexer.zig, you can run zig test --test-filter “lexer initialization” src/lexer/lexer.zig. It’s worth noting that the test filter doesn’t have to be the full name of the test to be able to find it.

Permalink