Skip to main content

Vim Knowledge Management

Another installment of @hyde's "Vim Carnival", this time asking, "How do you use Neovim/Vim to build your knowledge management?"

Unstructured Data

While boring, I dump all my unstructured "knowledge" data in a simple notes.txt file within my "personal data/text files" repo stored in git for syncing between machines.

This file contains a host of random thoughts and web-clippings. Disambiguations between words/spellings, URLs to interesting things with minor annotations, notes about VPS specs/pricing, book recommendations, some favorite jokes, short poetry-snippets I've composed, whatever…all tossed together with timestamps.

Typically I'll either grep -3 for things in the file (including 3 lines of context) or I'll open it in any $EDITOR to search for things.

I also have a keyboard shortcut in my window-manager (Fluxbox) to launch a shell-script that uses zenity to prompt me for a description, then appends the current date+time, the one-line description, and the contents of the clipboard (using xsel) to this file.

Structured Data

Depending on the nature of the structured usually specific tooling gets used to manipulate it.

Address book

I keep my personal address book in GNU RecUtils format. This plain-text database format easily lets me add new fields as I have need.

# people
id: davesmith
household: smith
last: Smith
first: David
nickname: Dave
mother: alicesmith
father: bobsmith
dob: Apr 1 2015
note: in 5th grade class with S

id: alicesmith
household: smith
last: Smith
first: Alice
spouse: bobsmith
phone: 972.555.1212 (c)
phone: 817.555.1234 (h)
email: alice@example.net

⋮

# households
hid: smith
anniversary: Jun 10 2006
street: 3141 Oak St.
city: Anytown
state: PA
zip: 31415
a sample addressbook.txt excerpt

Most of the time, I find it easiest to just open the file in my $EDITOR and search for things there when I need to look someone up. But I have a few canned queries for doing things like generating the email contact list for our Christmas email letter.

Calendar

I use Remind for calendar-related data. Thanks to recsel and a little awk scripting, I can generate remind entries for birthdays, anniversaries, adoptions, memorials, etc. Then remind can INCLUDE the resulting files.

For other appointments, they go directly into the corresponding reminder files &emdash; one for me, one for my wife, one for each kid individually, one for the kids combined, one for the whole household, one for work, one for church, …

I have a cron job that sends each day's remind output to my email, so I have a copy of the day's reminders and todo items.

Finances

I track these as plain-text accounting plain-text which allows me to manage my finances using ledger.

Todos

For a long time I kept todo items in todo.txt format, and had a symlink for my ~/.plan so I could use finger(1) from other machines to fetch my current todo list.

That said, since remind has grown todo-tracking functionality, most of my todo items have moved into a todo.rem reminder file. And I can express repeating todo-items a lot more easily now.

Closing

But the best part of all of this? It's all plain text. I store it in git and can edit it with any editor, whether vim, vi, ed(1), or even (as noted above) generated by shell commands.


If interested, here's the shell-script

#!/bin/sh
SAVE_FILE="$HOME/notes.txt"
DESC="$(zenity --title 'Enter description' --entry --text 'Enter description of clipboard contents')"
if [ -n "${DESC}" ]
then
 echo  $SAVE_FILE
 date +"%Y-%m-%d %H:%M"  $SAVE_FILE
 echo "${DESC}"  $SAVE_FILE
 xclip -o -selection clipboard  $SAVE_FILE
 echo  $SAVE_FILE
fi

Vim Carnival: Motion

@hyde over on Mastodon issued a call for "Vim Carnival" posts, with this month's topic suggesting the vim motion that changed everything.

Buckle up, because you get not one but multiple motions that changed things.

The Boring Motion(s)

While boring, you get 6 motions for the price of one: f/F/t/T jump forward/backward landing on (or immediately short of) a target character; and ,/;. to repeat that motion in either the same or opposite direction. I use these all. the. time. And when I find myself using other $EDITORs that lack this functionality, it feels so slow moving around horizontally.

The Epiphany Motion

While simple, the H/L motions hold a special place in my vim-learning path. Not so much the raw motions themselves (which I had used quite regularly), but because they represent a step in vim intuition. At one point I wanted to jump a couple lines below the top line on the screen. Without knowing whether they accepted a {count} I instinctively issued something like 4H and to my delight, it worked exactly as I had hoped/expected. So rather than search or use relative line-numbers (which tend to get a bit laggy over a slow or high-latency SSH connection) I often find it easier to make a coarse jump to an estimated line near the top/bottom of the screen and adjust up/down a line or two if needed.

Unlocking secondary disks on OpenBSD

In a recent conversation, the topic of encrypted disks on OpenBSD came up, and how to have multiple FDE disks while only needing to enter a passphrase once. If you install to a FDE root, and then follow the official instructions for setting up FDE on your additional drive(s), you'll likely end up needing to enter your passphrase multiple times during boot, once for each drive or provide a key-disk. Thus began the quest to document how to unlock the root disk with a single passphrase and then have the other disk(s) unlock automatically.

This assumes you've already configured the first boot-drive with FDE using the installer.

Get a quiesced system

To begin with, make sure that all drives are attached and you have rebooted into the system to ensure drive-naming quiesces. I found that using a USB drive as my $ENCRYPTED_DISK led to it being "sd2" after initially plugging it in, but "sd0" upon rebooting. You want to ensure stable naming.

Initial setup

To make the following instructions easy to adapt, we'll set some initial variables that you can change to suit your needs:
KEYFILE=/root/keyfile
ENCRYPTED_DISK=sd2
DEST=/mnt/data
Helpful environment variables
The $KEYFILE points to the password/passphrase file owned by the "root" user, and with 0600 permissions ("-rwx------", which can be done by temporarily setting a umask of "077") that we need to generate with high-quality random gibberish:
OLD_UMASK=$(umask)
umask 077
tr -dc a-zA-Z0-9 < /dev/urandom |
fold -256 |
head -1 > $KEYFILE
umask $OLD_UMASK
Creating the keyfile

Disk setup

WARNING: this will destroy the data on $ENCRYPTED_DISK!

Use fdisk and disklabel to create a full-disk-spanning "a" partition of type "RAID":

dd if=/dev/urandom of=/dev/r${ENCRYPTED_DISK}c bs=1m
fdisk -iy $ENCRYPTED_DISK
disklabel -E $ENCRYPTED_DISK
 a



 RAID 
 q

Setting up the target disk

Next, create the encrypted drive ("sd3" here as reported from bioctl output):

bioctl -c C -p ${KEYFILE} -l ${ENCRYPTED_DISK}a softraid0
softraid0: CRYPTO volume attached as sd3
Setting up the encrypted device

Now let's prepare that resulting disk for usage. You can use disklabel to partition it however you want, but for the example here, we'll just create a single big "a" partition.

DECRYPTED_DISK=sd3
dd if=/dev/zero of=/dev/r${DECRYPTED_DISK}c bs=1m count=1
fdisk -iy $DECRYPTED_DISK
disklabel -E $DECRYPTED_DISK
a




 q

newfs ${DECRYPTED_DISK}a
Formatting the encrypted disk

Repeat the newfs for other partitions if you created any.

Reproducing it on reboot

To re-engage things upon rebooting, we need to obtain the disk-id:

DUID="$(disklabel $DECRYPTED_DISK | awk '$1 == "duid:"{print $2}')"
echo $DUID
c001ba1dc0dedad5
Determining the Disk ID

With that, we can create entries in /etc/fstab:

echo "${DUID}.a $DEST ffs rw,noauto,nodev,nosuid 0 0" >> /etc/fstab
Creating a /etc/fstab entry

You can remove the "nodev" and "nosuid" if you need those. Add additional entries if you created and formatted other partitions. At this point, you should be able to mount the decrypted drive:

mount $DEST
Mounting the encrypted partition

Finally, add commands in /etc/rc.local to create the crypto device, and mount it at boot:

cat >> /etc/rc.local <<EOF
bioctl -c C -p ${KEYFILE} -l ${ENCRYPTED_DISK}a softraid0
mount "$DEST"
EOF
Adding commands to /etc/rc.local

If you have other partitions and fstab(5) entries, you can add additional mount(8) directives.

Finally you should be able to reboot into the new system, enter your main/root drive's password/passphrase, and yet have your second drive also unlocked during the boot process without needing to provide a second password/passphrase.

You can back up the $KEYFILE file by copying it to a USB drive, sending it to a remote machine, or using whatever backup methods you're currently using. Just ensure that you keep it secure. Otherwise, if your main/root drive goes down, you'll lose access to the second drive, too.

Why BSDs?

Every week or two, a post seems to pop up on one of the BSD-related sub-reddits where I hang out (FreeBSD, OpenBSD, NetBSD, & BSD) asking why folks would choose a BSD over a Linux distribution. Having answered the same question multiple times, I figured the time had come to post my "why" here.

Skip the history and jump to the push/pull

Early Unix experiences (1987–1994)

I'd grown up on Apple DOS 3.3 & ProDOS, then moved to MS-DOS 4.x and following. But then I had taste of Unix via our local college's dial-in shell servers and Unix labs. Using a Unix command-line felt similar, but as if crafted with design and intent. Pieces played together nicely. Programs multitasked in ways that made sense (and I could never do on DOS). Countless late nights via 1200 baud dial-up. I became hooked.

Initial installation & exploration (1995–1996 school year)

After downloading umpty-gazillion 3½" floppies and schlepping them back to my dorm, an older CompSci classmate helped me install Slackware on my computer. That powerhouse 486DX/100 with 32MB of RAM and two 512MB hard-drives (a whole GIGABYTE, baby!). To this day, I still have a dislike of Emacs that might stem from the E-series Slackware floppies getting corrupted, driving me back to the lab to download and write fresh disks just so Emacs would install.

Meanwhile, my favorite campus computer lab held networked DEC Ultrix workstations running X at mind-blowing resolutions and color-depth compared to the 800x600 SVGA graphics on my personal computer. A real Unix.

While I enjoyed having Slackware, the lack of internet access in the dorm room made it a challenge to make productive use of it. DOS (with Windows 3.1 on demand) at least let me write papers in WordPerfect 5.1 and take the files to labs to print them out. But then I discovered HTML. It felt like WordPerfect's Reveal Codes functionality, letting me clearly see what my document contained under the hood. No hidden stray markup, causing images to misalign or change the spacing or bold/italicize/underline the wrong thing. I could also write it in a normal text editor, whether on DOS, in Windows, or in my Slackware install (modulo line-ending concerns), letting it become more useful. In addition to those benefits, while the computer labs charged $0.07 per page to print, I discovered that the college library let me print "web resources" for free. Guess who could point the web-browser at his paper on the floppy drive, open it as a "web resource" and print it for free?

College experimentation

As college progressed, I tried installing and reinstalling other flavors of Linux. Red Hat 8 ("Psyche") & Mandrake had a great install experience, but RPM had issues that required me to manually install package dependencies. Download the .rpm file, try to install it, discover missing dependencies, download those .rpm files, install, rinse, repeat until things finally installed.

I also tried installing FreeBSD (Walnut Creek CD-ROMs, around version 2.x) in this time-frame. However I failed to figure out the installer. With the benefit of hindsight, I know the primary issue stemmed from "partition" meaning one thing in DOS & Linux, while meaning a different thing in FreeBSD (where "slices" referred to what I knew as MBR "partitions" in DOS, while FreeBSD referred to subdivisions of one of those "slices" as "partitions"). Had I understood the differing terminology at the time, I might have successfully installed FreeBSD.

Post college Linux

Now out of college and married, we ordered a new Gateway Solo 1200 laptop. An 800MHz Celeron processor, 128MB of RAM, and 10GB of disk-space! It came with Windows ME, a horrible OS, but with WinModem drivers that worked where Knoppix and other attempts at Linux refused to get online. Once we had a DSL connection in the apartment, I maxed out the RAM to 320MB, swapped in a 120GB hard-drive, and installed Debian which served us well for many years.

Curious about Apple hardware & operating systems, we picked up an Apple iBook G4, the last of their PPC line, running at 1.3GHz, maxed out with 1.5GB of RAM, and a 30GB hard-drive.

The Mac drove me bonkers.

I imagine that folks exist who think the way Apple does. And I imagine that OSX works fabulously for them. I am not such a person. Without support for new OS versions (PPC support stopped with 10.4 or 10.5), the laptop rapidly became a useless brick, unsafe to use on the open internet.

I found a good deal on some Lenovo laptops at (the now-defunct) Fry's. I installed Debian on mine, and Ubuntu on my sweetheart's. They served us fairly well for years of web-browsing, email, light gaming, remoting into machines at work, and software development for me.

The Push: the slow decline of Linux

However, grey clouds started rolling in. Debian started introducing changes that drifted farther and farther from the Unix I started with, and I did not care for it.

Sound systems churned leading to frustration. I lived through OSS, libao, ESD, aRTS, ALSA, Pulse, Jack, and now Pipewire. Each one promised to fix all the problems of the previous standards. Similarly, I lived through a plurality of firewall tech.

Then programs I'd used for years/decades began to issue deprecation warnings. "Don't use ifconfig to manage your wireless connection use iwconfig, and to manage your bridge devices use brctl, except don't use either of those, use ip instead. Fresh installs of most popular distributions lacked ed(1) in the base system, so when things went sideways, you might not have a $EDITOR to rescue your system.

I would open a man page to read documentation, only to find a useless placeholder stub redirecting me to a GNU info page where you had to navigate to different parts to see them, and you couldn't read the whole thing in one place. I've since learned to use info program | less to force info to render the entire document into less where I can comfortably read the whole thing.

I'd reach for netstat only to hear that I should use ss instead. Or that I should stop using nslookup that I'd used for years on *nix and Windows and instead use host or dig/drill. EDIT: A note from author Michael W. Lucas informs me that the developers of nslookup deprecated their own application, not Linux distro-builders mandating change.

Then came the big ones: systemd and the threat of Wayland replacing Xorg. systemd broke detaching in tmux and rudely expected the tmux team to fix the problems that systemd had caused. I would issue shutdown/reboot commands with the power of root (sudo shutdown -r now) only to have systemd balk and refuse to actually shutdown/reboot, hanging indefinitely on some process over which I had no control. Thank goodness for the ability to hold down the power-button and kill it properly. No choice whether I wanted systemd vs some other init system. I could no longer grep my log files because systemd kept them in binary formats.

Meanwhile, I've heard strong rumblings that Xorg will get supplanted by Wayland in most Linux distributions. My window-manager of preference (fluxbox) does not work in Wayland. Many of the GUI applications that I use do not work in Wayland. I continue to hear that a lot of things don't currently work in Wayland. Maybe it will meet my needs someday, but not any time in the imminent future.

The Pull: the BSDs (2012–)

Meanwhile, amid all those pushes in the Linux world I found myself drawn to features that FreeBSD & OpenBSD offered.

While FreeBSD does offer several firewalls, those choices include pf which quickly became my favorite firewall syntax. Originating in OpenBSD, it gives me one sensible syntax to manage my firewalls on all my machines.

ZFS frees me from getting locked into a particular partition layout, pooling my available storage and making it available to all datasets. It checksums my data before writing and after reading to ensure that my data hasn't bitrotted. It gives me transparent compression. I can do instant snapshots of my data. I can clone datasets and send/receive that data efficiently across to other machines for backup. Copy-on-write meant that even if my system experienced abrupt power-loss the file-system remained consistent and didn't require a fsck upon reboot.

FreeBSD gives me jails which make a lot more sense to me than containerization in Linux-land. As an added benefit, FreeBSD has offered jails far longer (March of 2000) than Linux containers, giving them more time to bake.

Meanwhile, OpenBSD gives me a system that feels good. Applications and services play well together like dhcpd talking to pf, unbound, or relayd. And it includes xenodm/xenocara with a base install, and includes three window managers: fvwm (the default), twm (old school), and cwm (my favorite of the three).

Finally switching from Debian to FreeBSD/OpenBSD (2019)

The final nail in the coffin came from a Debian upgrade where systemd took down my audio subsystem completely and started having problems booting reliably.

I backed up my data to an external drive along with a list of the major software I use, sent a copy to my VPS, and installed FreeBSD on my daily driver. I copied all my data back, installed the major software on FreeBSD, and went on with life largely as I had before.

I also set up some other junker laptops with OpenBSD, including that now-dead Gateway Solo 1200, and that iBook G4, as well as my writer-deck, a Dell Mini10 netbook. Additionally, my VPS instances run a mix of FreeBSD & OpenBSD.

Epilogue

Is everything perfect? I still experience minor issues, most notably the audio on FreeBSD doesn't automatically cut over between speakers and headphones when I plug/unplug headphones.

But it feels like the Unix I grew up using.

It feels like home.


A few such sample posts: here, here, here, here, here, here, here, here, here, here, here, here, and here.

Assorted less(1) tips

In a recent discussion on Reddit I shared a number of tips about the common utility less(1) that others found helpful so I figured I'd aggregate some of those tips here.

Operating on multiple files

While most folks invoke less at the tail of a pipeline like

 command | less
Invoking less in a pipeline
you can directly provide one or more files to open
 less README.txt file.c *.md
Invoking less directly

Adding files after starting

When reading a document, sometimes you want to view another file, adding it to the file list. Perhaps while reading some C source code you want to also look over the corresponding header-file. You can add that header-file to the argument list with :e file.h

You can navigate between multiple files using :n to go to the next file in the argument-list, and :p for the previous file. You can also use :x to rewind to the first file in the argument-list similar to how :rewind behaves in vi/vim.

Removing files after starting

While I rarely feel the need to, if you have finished with a file and want to keep your argument list clean, you can use :d to delete the current file from the argument-list.

Jumping to a particular line-number

Use «count»G to jump to a particular line-number. So using 3141G will jump to line 3141. It helps to display line numbers.

Jumping to a particular percentage-offset

Similarly, using «count»% jumps to that percentage-offset of the file. So if you want to go to ¾ of the way through the file, you can type 75% to jump right there.

Searching

While many folks know you can search forward with /«pattern» and some people know you can use ?«pattern» to search backwards, or use n/N to search again for the next/previous match, less provides modifiers you can specify before the pattern to modify its behavior:

!
Find the next line that doesn't match the pattern
*
search across multiple files, starting from the current location in the current file
@
rewind to the first file and search from there
@*
rewind to the first file and search from there across multiple files

Thus you would use /@*«pattern» to search for "pattern" starting with the first file.

Filtering lines

Using & lets you specify a pattern and filter the displayed lines to only those matching the pattern, much like an internal grep command. If you modify it with !, so it will display only those lines that do not match the pattern, like &!«pattern». I find this particularly helpful for browsing log-files.

Bookmarking

You can bookmark points in a file with m followed by a letter, then jump back to that bookmark with ' followed by the same letter. These apply globally across all open files, so if you ma in the third file, then navigate away to other files, using 'a will take you back to the marked location in that third file. I use marks most when reading man-pages, dropping one mark at the OPTIONS section such as mo, and another at the EXAMPLES section, such as me, then bounce back and forth between them with 'o and 'e. While you can use any of the 26 lowercase or uppercase letters (for a total of 52 marks), I rarely use more than two or three either in alphabetical order ("a", "b", "c"), or assigning mnemonics like in the man-page example above.

Bracket matching

If the first line on the screen contains a (, [, or {, typing that character will jump to the matching/closing character, putting it on the bottom line of the screen. Similarly, if a closing ), ], or }, character appears on the last line, typing that closing character will jump to the matching/opening character, putting it at the top of the screen. I find it a little disorienting if they fall less than a screen-height apart because what feels like a forward motion to find the next matching close-bracket might actually result in shifting the screen down rather than up which feels backwards.

While I don't use it much, you can also specify match-pairs using alt+ctrl+f or alt+ctrl+b followed by the opening/closing pair of characters such as alt+ctrl+f<> to define a "<"…">" pair and jump between them in a manner similar to the (/), [/], and {/) motions.

Toggling options without restarting

While the man-page documents many flags you can pass on the command-line, you can also toggle boolean options from inside less. I find this particularly helpful when I've fed the output of a long-running process to less and don't want to re-run it because it will take a long time. Instead of quitting, you can type a literal - followed by the option you want to change. I most commonly want to toggle word-wrap for long lines, so instead of quitting and adding -S at the end of my pipeline, I can type -S directly in less. Options I commonly toggle:

-S
word-wrap (mnemonic "splitting long lines")
-G
search-highlighting
-i/-I
smart-case/case-sensitivity for searches
-R
ANSI-color escaping
-N/-n
show/hide line-numbers

Running external commands

The ! lets you invoke an external command. I don't do this often, but occasionally I want some simple reference like the current date (!date) or to do some simple math (!bc).

Default options with $LESS

You might find yourself regularly setting a common group of options so you can put those in your environment (usually in your shell startup file like ~/.bashrc) like LESS="-RNe" if you want to show ANSI colors, show line-numbers, and exit automatically when you reach the end of the file.

Other misc

less has a few other corners that I've never really used, but figured I'd document here:

Tags

While I've used tags in vi/vim to easily jump between definitions. However, even though less provides support for tags generated by ctags. I've never found cause to use them.

Editing the current document

The v command will open your $VISUAL editor on the current document.

"Log" output

less lets you redirect the output it has gathered from stdin to a file using the o command (or the O command to overwrite an existing file). This might come in handy because less won't let you edit stdin in an external editor but you can write it directly to a file.

Blog Question Challenge 2025

Why did you start blogging in the first place?

Part of me wants to just record my thought-process and solidify my thinking.

Another part like sharing things I learn in case they help others.

And sometimes I just want to rant.

What platform are you using to manage your blog, and why do you use it

In the late 90s, I wrote raw HTML files and just posted them on the RedHat web-server that my college CS department offered. Good ol' table-based layout and all that.

Once I got my own domain, I looked around at Static Site Generators (SSGs) and Nikola, topped my list. I wanted an SSG that

  • could ingest pure HTML fragments (which I prefer) rather than forcing me to use something like Markdown or AsciiDoc. I don't mind using them for casual prose (like this) but for technical work, I prefer the markup-control that I get from raw HTML

  • was written in Python (which I use as part of $DAYJOB, so I felt more comfortable poking under the hood)

  • ran on all the platforms I used (Linux at the time, now FreeBSD, and OpenBSD

  • had metadata features that fit my requirements

  • had a built-in web-server for viewing the site locally

  • was easy to create an rsync deploy hook to send my files up to my web-server

However, Nikola has a lot of churn, meaning I frequently have to revisit my configuration files or regularly suffer the wrath of warnings and errors at every upgrade. It also processes my HTML source through lxml which in turn mangles certain cromulent markup, meaning my input doesn't show up in output. And system upgrades of Python seem to throw my virtual-env into a bit of a frenzy.

So I've started a Makefile-based SSG that largely sticks to BSD make and POSIX awk. Stay tuned when that comes to fruition.

How do you write your posts?

A plain ol' text editor.

Sometimes vi/vim, sometimes ed(1).

Do you normally publish immediately after writing, or do you let it simmer a bit?

Most posts take me a while to compose, so those get set to Draft status, and the publishing process uploads the static files but doesn't include them in indexing or the RSS feed. This allows me to read them on my site (optionally sharing the pre-release URL with others) and revise them accordingly. However, once I feel it's done, I go ahead and remove the Draft status to publish it.

Have you blogged on other platforms before?

If you count "microblogging", then I've blogged on Twitter/X and have largely moved my microblogging participation to Mastodon.

However, for the most part, it's just been my own site/blog.

When do you feel most inspired to write?

A couple triggers:

  • I'm working through something complex and want to document it for myself

  • I've found a fun little shell trick and want to share it

  • I've experienced some frustration and just need to rant

What’s your favorite post on your blog?

Probably my post on using remind. However, it's starting to show its age, so I should revisit the article and update it.

Any future plans for the blog?

Keep on emitting a steady trickle of whatever interests me.

The fall of the User Agent

In the beginning

Similar to some Mail User Agents (MUAs) and NNTP clients, the specification for HTTP since at least version 0.9 have included User-Agent headers that still exist in modern HTTP standards.

This header lets the server know what software made the request. But it also provides a reminder that the software existed to act on behalf of the user.

Abuse

Sadly, server-side software started to abuse the User-Agent header. Based on the value, a web-server would respond with different output depending on what it thought the other side could handle. By accommodating a broken client and making presumptions about how it would behave, this User-Agent sniffing led to a fractured web.

Lowe's rant

Sigh. Lowe's doesn't seem to be able to competently ship a part that still shows as in-stock on their site. Rant documenting the drama follows.

tl;dr order online, they cancel. order online again, they cancel. Call support, get told to place the order with the local store. Call local store, get told they can't. Call support, get told to physically go into the store to order. Order gets placed. Order arrives, installer notes it's missing parts. Call support, get told they'll rush out a replacement. Replacement arrives. Check box, still missing same parts. Call support, rush delivery of another replacement with notes for the shipper to check the contents. Third order arrives with the same contents as the previous two. To be continued…

Read more…

Planning the day on the CLI with tsort

I had a bunch of items on the todo list for the day and wanted to arrange them in order, but I needed to certain items before others:

  • Drop donations off at the resale store, but can't do that before 10:00 when they open
  • Get the daughter ready for soccer camp before dropping her off
  • Drop the daughter off at camp before taking paperwork up to her school
  • cut the son's hair before he takes a shower and cleans his bathroom
  • pick up daughter at camp after she's started camp (duh) and before 10:45
  • take the kids to the library after picking up daughter at camp
  • for obvious reasons, 8:30 → 8:45 → 9:00 → 10:00 → 10:30 → 10:45
So I expressed this in a file where each row contains "X needs to come before Y":
cat todo.txt
10:00 resale
resale 10:30
sunblock take_to_camp
pack_snack take_to_camp
pack_water take_to_camp
pack_towel take_to_camp
take_to_camp 8:30
8:30 soccer_camp
soccer_camp 8:45 
soccer_camp paperwork
8:45 home
home 9:00
paperwork home
home cut_hair
home clean_bathroom
home shower
cut_hair shower
cut_hair pick_up
shower pick_up
clean_bathroom pick_up
cut_hair clean_bathroom
clean_bathroom 10:00
10:45 pick_up
8:30 8:45 
8:45 9:00 
9:00 10:00 
10:00 10:30 
10:30 10:45 
pick_up library

Now it just became a matter of passing these requirements to tsort

tsort todo.txt
pack_towel
pack_water
pack_snack
sunblock
take_to_camp
8:30
soccer_camp
paperwork
8:45
home
cut_hair
9:00
shower
clean_bathroom
10:00
resale
10:30
10:45
pick_up
library
to sift them into the required order of my todo list for the day.