Today I Learned

A collection of snippets, thoughts and notes about stuff I learned.


Everything is available in a Git repository at


So far there are 26 TILs.


From build IDs to push log

via @chutten:

Found a regression? Here's how to get a pushlog:

  1. You have the build dates and you're gonna need revisions. Find the build before the regression and the build after the regression in this list: You want to record the Revision column someplace.

    May 10 final f44e64a61ed1
    May 11 final 61a83cc0b74b
  2. Put the revisions in this template:{}&tochange={}


Set the date in the emulator

To set the date & time of the running system:

adb shell su root date 061604052021.00

The datetime is in the format:



WebAssembly in BigQuery

So you can run WebAssembly code as part of a BigQuery SQL query.

Rust code:

fn main() {
extern "C" fn sum(a: i32, b: i32) -> i32 {
  a + b

Compiled using:

cargo build --target wasm32-unknown-unknown --release

with these compile settings in your Cargo.toml:

crate-type = ["cdylib"]

opt-level = "s"
debug = false
lto = true

Turn the Wasm file into a C-like array:

xxd -i target/wasm32-unknown-unknown/release/add.wasm

Then drop the output into the below query:

async function main() {
    const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 });
    const env = {
        'abortStackOverflow': _ => { throw new Error('overflow'); },
        'table': new WebAssembly.Table({ initial: 0, maximum: 0, element: 'anyfunc' }),
        'tableBase': 0,
        'memory': memory,
        'memoryBase': 1024,
        'STACKTOP': 0,
        'STACK_MAX': memory.buffer.byteLength,
    const imports = { env };
    const bytes = new Uint8Array([
      0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x60,
      0x02, 0x7f, 0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x05, 0x03, 0x01,
      0x00, 0x10, 0x06, 0x19, 0x03, 0x7f, 0x01, 0x41, 0x80, 0x80, 0xc0, 0x00,
      0x0b, 0x7f, 0x00, 0x41, 0x80, 0x80, 0xc0, 0x00, 0x0b, 0x7f, 0x00, 0x41,
      0x80, 0x80, 0xc0, 0x00, 0x0b, 0x07, 0x2b, 0x04, 0x06, 0x6d, 0x65, 0x6d,
      0x6f, 0x72, 0x79, 0x02, 0x00, 0x03, 0x73, 0x75, 0x6d, 0x00, 0x00, 0x0a,
      0x5f, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x65, 0x6e, 0x64, 0x03, 0x01,
      0x0b, 0x5f, 0x5f, 0x68, 0x65, 0x61, 0x70, 0x5f, 0x62, 0x61, 0x73, 0x65,
      0x03, 0x02, 0x0a, 0x09, 0x01, 0x07, 0x00, 0x20, 0x01, 0x20, 0x00, 0x6a,
      0x0b, 0x00, 0x0f, 0x0e, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x61,
      0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x00, 0x21, 0x04, 0x6e, 0x61, 0x6d,
      0x65, 0x01, 0x06, 0x01, 0x00, 0x03, 0x73, 0x75, 0x6d, 0x07, 0x12, 0x01,
      0x00, 0x0f, 0x5f, 0x5f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x70, 0x6f,
      0x69, 0x6e, 0x74, 0x65, 0x72, 0x00, 0x4d, 0x09, 0x70, 0x72, 0x6f, 0x64,
      0x75, 0x63, 0x65, 0x72, 0x73, 0x02, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75,
      0x61, 0x67, 0x65, 0x01, 0x04, 0x52, 0x75, 0x73, 0x74, 0x00, 0x0c, 0x70,
      0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x2d, 0x62, 0x79, 0x01,
      0x05, 0x72, 0x75, 0x73, 0x74, 0x63, 0x1d, 0x31, 0x2e, 0x35, 0x32, 0x2e,
      0x31, 0x20, 0x28, 0x39, 0x62, 0x63, 0x38, 0x63, 0x34, 0x32, 0x62, 0x62,
      0x20, 0x32, 0x30, 0x32, 0x31, 0x2d, 0x30, 0x35, 0x2d, 0x30, 0x39, 0x29
    return WebAssembly.instantiate(bytes, imports).then(wa => {
        const exports = wa.instance.exports;
        const sum = exports.sum;
        return sum(x, y);
return main();

WITH numbers AS
  (SELECT 1 AS x, 5 as y
  SELECT 2 AS x, 10 as y
  SELECT 3 as x, 15 as y)
SELECT x, y, sumInputs(x, y) as sum
FROM numbers;



The date of Easter

def easter(year):
    y = year;
    c = y//100;
    n = y-19*(y//19);
    k = (c-17)//25;
    i = c-c//4-(c-k)//3+19*n+15;
    i = i-30*(i//30);
    i = i-(i//28)*(1-(i//28)*(29//(i+1))*((21-n)//11));
    j = y+y//4+i+2-c+c//4;
    j = j-7*(j//7);
    l = i-j;
    m = 3+(l+40)//44;
    d = l+28-31*(m//4);

    return (m, d)

print(easter(2022))  # (4, 17)

Based on the wonderful explanation in §3. Calendrical. of the Inform7 documentation


Docker on a remote host

docker context create remote --docker "host=ssh://hostname"
docker context use remote

Is the docker daemon running?

If the Docker daemon is not running on the remote host, you might see this error message:

Cannot connect to the Docker daemon at Is the docker daemon running?

The host is of course nonsense. The solution: Start the Docker daemon on the remote host and it should work.

Run a shell with a Docker image

docker run -t -i --rm ubuntu:20.04 bash

Changing the platform, e.g. to use x86_64 when running on an M1 MacBook:

docker run -t -i --rm --platform linux/amd64 ubuntu:20.04 bash

SSH into the Docker VM on macOS

Run socat first:

socat -d -d ~/Library/Containers/com.docker.docker/Data/debug-shell.sock pty,rawer

This will print some lines, including the PTY device opened, like

PTY is /dev/ttys029

Use that to connect using screen:

screen /dev/ttys029


Fixup commits

Fixup commits are commits that build on top of an already existing commit. They can be squashed into the existing commit as a later fixup, e.g. to fix typos or formatting.

git commit comes with builtin support for that: git commit --fixup=<commit>, where <commit> is the existing commit to be modified. See the documentation for details.

See also git helpers.

Git helpers


git commit --fixup, but automatic

See See also Fixup commits.


A handy tool for doing efficient in-memory commit rebases & fixups


Last modification date of a file

Shows the date of the last commit that modified this file:

git log -1 --pretty="format:%ci" path/to/file

See PRETTY FORMATS in git-log(1) for all available formats.


GitHub Webhooks

GitHub can send webhooks to a configured server on events. By default this is done on any push event to the repository.

GitHub attaches an HMAC signature using the provided secret, which allows to verify that the content is really coming from GitHub. Documentation about this is available in Securing your webhooks.

In Rust one can verify the signature like this:

fn main() {
use hex::FromHex;
use hmac::{Hmac, Mac, NewMac};
use sha2::Sha256;

fn authenticate(key: &str, content: &[u8], signature: &str) -> bool {
    const SIG_PREFIX: &str = "sha256=";
    let sans_prefix = signature[SIG_PREFIX.len()..].as_bytes();
    match Vec::from_hex(sans_prefix) {
        Ok(sigbytes) => {
            let mut mac =
                HmacSha256::new_from_slice(key.as_bytes()).expect("HMAC can take key of any size");
        _ => false,


Run tests using Gradle

Run a single test:

./gradlew testDebugUnitTest --tests TestClassNameHere.testFunctionHere

Rerun tests when up-to-date



test.outputs.upToDateWhen {false} in the config


var vs. val - Difference


  • var: Mutable. Used to declare a mutable variable. It means the value of variable can be changed multiple times.
  • val: Immutable Used to declare a read only variable. It means once the value is assigned to variable, that can’t be changed later.

val is same as final in Java.


Runing parallel tasks from make

With the combination of multiple tools, you can serve static files over HTTP and rerun a build step whenever any input file changes.

I use these tools:

  • https - static file server
  • fd - a faster find
  • entr - run arbitrary commands when files change
  • make

With this Makefile:

	$(MAKE) MAKEFLAGS=--jobs=2 dev
.PHONY: default

dev: serve rerun
.PHONY: dev

	# Put your build task here.
	# I generate a book using
	mdbook build
.PHONY: build

serve: build
	@echo "Served on http://localhost:8000"
	# Change to the generate build directory, then serve it.
	cd _book && http
.PHONY: serve

	# fd respects your `.gitignore`
	fd | entr -s 'make build'
.PHONY: rerun

All it takes to continously serve and build the project is:


Symbols in shared libraries

List all exported symbols of a dynamic library:

nm -gD path/to/

To look at the largest objects/functions in libxul:

readelf -sW $NIGHTLY/ | sort -k 3 -g -r | head -n 100

To look at the disassembly:

objdump -dr $OBJ | c++filt

On macOS:

otool -tV $OBJ | c++filt


Check who holds SecureInput lock

Individual applications on macOS can request SecureInput mode, which disables some functionality that would otherwise allow to capture input. One can check if SecureInput is active and which process holds the lock:

$ ioreg -l -w 0 | grep SecureInput
  |   "IOConsoleUsers" = ({"kCGSSessionOnConsoleKey"=Yes,"kSCSecuritySessionID"=100024,"kCGSSessionSecureInputPID"=123,"kCGSSessionGroupIDKey"=20,

The kCGSSessionSecureInputPID holds the PID of the process that holds the SecureInput lock. Find that process with ps:

ps aux | grep $pid


Meta commands in psql

\lList databases
\cConnect to database
\dtList tables
\d $tableList schema of $table


Modify integer literals

Integer literals in Python refer to the same object every time they are used. One can modify those objects:

from sys import getsizeof
from ctypes import POINTER, c_void_p, c_char, cast

def read_int(obj: int, vv=True) -> bytes:
    size = getsizeof(obj)
    ptr = cast(c_void_p(id(obj)), POINTER(c_char))
    buf = ptr[0:size]
    if vv:
        print(f"int obj @ {hex(id(obj))}: {buf.hex(' ')}")
    return buf

def write_int(dst: int, src: int):
    raw_src = read_int(src, False)
    dst_ptr = cast(c_void_p(id(dst)), POINTER(c_char))

    for (idx, c) in enumerate(raw_src):
        dst_ptr[idx] = c

write_int(1, 2)

a = 1
b = 2
print(a + b)


pip - Install from Git

To install a Python package from Git instead of a PyPi-released version do this:

pip install git+ssh://

See also: Useful tricks with pip install URL and GitHub

Strip Markdown syntax

In order to strip Markdown syntax and leave only the plain text output one can patch the Markdown parser:

from markdown import Markdown
from io import StringIO

def unmark_element(element, stream=None):
    if stream is None:
        stream = StringIO()
    if element.text:
    for sub in element:
        unmark_element(sub, stream)
    if element.tail:
    return stream.getvalue()

Markdown.output_formats["plain"] = unmark_element

__md = Markdown(output_format="plain")
__md.stripTopLevelTags = False

def strip_markdown(text):
    return __md.convert(text)

Then call the strip_markdown function:

text = """
# Hello *World*!

[Today I learned](


This results in:

Hello World!
Today I learned



Not-equal types

// requires nightly!


use std::marker::PhantomData;

auto trait NotSame {}

impl<A> !NotSame for (A, A) {}

struct Is<S, T>(PhantomData<(S,T)>);

impl<S,T> Is<S,T> where (S,T): NotSame {
  fn absurd(&self) {

fn main() {
  let t : Is<u32, u32> = Is(PhantomData);
  let z : Is<u32, i32> = Is(PhantomData);

Random values using only libstd

fn main() {
use std::collections::hash_map::RandomState;
use std::hash::{BuildHasher, Hasher};

let random_value = RandomState::new().build_hasher().finish() as usize;
println!("Random: {}", random_value);

via ffi-support

Testing code blocks in the README


fn main() {
// Ensure code blocks in compile
macro_rules! readme {
    ($x:expr) => {
        #[doc = $x]
        mod readme {}
    () => {


Temporary values in SQLite

To select from some values:

WITH vals (k,v) AS (
    (1, 100)

To actually create a temporary table:

CREATE TEMP TABLE temp_table AS                                     
WITH t (k, v) AS (
 (0, -99999),
 (1, 100)

Working with dates

Full docs: Date And Time Functions

Datetime of now

SELECT datetime('now');

Timestamp to datetime

SELECT datetime(1092941466, 'unixepoch');

Datetime to timestamp

SELECT strftime('%s', 'now');


Exporting Twitter Spaces recording

For better or for worse people are using Twitter Spaces more and more: audio-only conversations on Twitter. Simon Willison recently hosted one and wrote a TIL how to download it. I helped because it's actually easier than his initial solution, so I'm copying that here:

Exporting the recording using youtube-dl

Open the Twitter Spaces page, open your Firefox developer tools console in the network tab, filter for "m3u", then hit "Play" on the page. The network tab will capture the URL to the playlist file. Copy that.

Then use youtube-dl (or one of its more recent forks like yt-dlp) to download the audio:

youtube-dl ""

This will result in a .mp4 file (media container):

$ mediainfo "playlist_16798763063413909336 [playlist_16798763063413909336].mp4"
Complete name                            : playlist_16798763063413909336 [playlist_16798763063413909336].mp4
Format                                   : ADTS
Format/Info                              : Audio Data Transport Stream
Format                                   : AAC LC
Format/Info                              : Advanced Audio Codec Low Complexity

To extract only the audio part you can use ffmpeg:

ffmpeg -i "playlist_16798763063413909336 [playlist_16798763063413909336].mp4" -vn -acodec copy twitter-spaces-recording.aac