Programming

Here are some of my programming projects I like the most

Booty

Booty is a language and command line utility for bootstrapping the setup of personal OS installs. Its goal is to execute all of the things that you do after your first boot, like install packages through your package manager, configure your shell, setup your terminal tools/IDE, and install SDKs for various programming languages, without you having to download everything manually and run various scripts in the right order.

This is what Booty looks like.

essentials: apt(wget git vim autokey-gtk silversearcher-ag gawk xclip
    gnome-disk-utility cryptsetup build-essential dconf-editor ripgrep xdotool
    luarocks cmake libterm-readkey-perl expect ssh curl fzf)

files.git -> essentials
files.git: git(naddeo@do.naddeo.org:~/git/files, ~/files)

notes.git -> essentials
notes.git: git(naddeo@do.naddeo.org:~/git/notes, ~/notes)

ssh_conf -> essentials
ssh_conf:
    setup: ssh-keygen
    is_setup:
        test -f ~/.ssh/id_rsa.pub
        test -f ~/.ssh/id_rsa

## Terminal stuff

nvim -> essentials
nvim:
    setup:
        curl -L https://github.com/neovim/neovim/releases/latest/download/nvim-linux64.tar.gz -o ~/nvim-linux64.tar.gz
        tar -xzf ~/nvim-linux64.tar.gz -C ~
    is_setup: test -f ~/nvim-linux64/bin/nvim

This is the spiritual successor to Systemconf (below). I’ve learned a lot since I made Systemconf and decided it was time to take another stab at the problem because I found myself really only depending on Systemconf for symlinking things. I actually use Booty for my entire system setup now and I like the languge a lot more than Systemcof (aesthetically). It’s primarily inspired by make (which I also love) with a few additions to adapt it to system setups. Lots of info in the README if you want more.

Here’s a demo of Booty in action.

Strokes

Strokes is a Windows application that lets you map keys and key combinations to other keys and key combinations. It’s inspired by Karabiner, a Mac application that lets you do the same thing.

I made this because I can’t find any good Windows solutions for doing these things. On OSX, I have Karabiner. On Linux, a I have AutoKey. On Windows, all I have is strange 10 year old applications, registry hacks that only work for single key presses, and AutoHotKey which is way overkill.

This is what a Strokes configuration looks like.

{
  "rules": [
    {
      "description": "Windows DE switching",
      "manipulators": [
        {
          "description": "Next desktop",
          "from": {
            "key": "l",
            "modifiers": ["VK_LSHIFT", "VK_LCONTROL"]
          },
          "to": [
            {
              "key": "VK_RIGHT",
              "modifiers": ["VK_LCONTROL", "VK_LWIN"],
              "type": "keys"
            }
          ]
        },
        {
          "description": "Previous desktop",
          "from": {
            "key": "h",
            "modifiers": ["VK_LSHIFT", "VK_LCONTROL"]
          },
          "to": [
            {
              "key": "VK_LEFT",
              "modifiers": ["VK_LCONTROL", "VK_LWIN"],
              "type": "keys"
            }
          ]
        }
      ]
    },
    {
      "description": "Firefox VK_TAB switching",
      "manipulators": [
        {
          "conditions": [
            {
              "description": "firefox",
              "type": "focused_application"
            }
          ],
          "description": "tab next",
          "from": {
            "key": "l",
            "modifiers": ["VK_LALT", "VK_LCONTROL"]
          },
          "to": [
            {
              "key": "VK_TAB",
              "modifiers": ["VK_LCONTROL"],
              "type": "keys"
            }
          ]
        },
        {
          "conditions": [
            {
              "description": "firefox",
              "type": "focused_applicatnextion"
            }
          ],
          "description": "tab previous",
          "from": {
            "key": "h",
            "modifiers": ["VK_LALT", "VK_LCONTROL"]
          },
          "to": [
            {
              "key": "VK_TAB",
              "modifiers": ["VK_LCONTROL", "VK_LSHIFT"],
              "type": "keys"
            }
          ]
        }
      ]
    }
  ]
}

Stream

Functional, infinite streams of data in Elm that won’t overflow the stack.

 let
    actual =
        Stream.value "a"
            |> Stream.zip (Stream.range 1 5 1)
            |> Stream.toList

    expected =
        [ ( 1, "a" ), ( 2, "a" ), ( 3, "a" ), ( 4, "a" ), ( 5, "a" ) ]
in
    Expect.equalLists expected actual

I started this while playing around with some Project Euler problems using Elm. I had also just seen a YouTube talk on a JVM Haskell called Frege where the author of the language talks about using functional programming and streams to solve Fizz Buzz so I wanted to use the popular Elm lazy-list. Pretty fast I started running into stack overflows using lazy collections. I ended up attempting to fix the issue with a pull request only to conclude that it would take a pretty big rearchitecture. I decided to just write this instead with the goal of enabling Elm to use tail call optimization. It was definitely tricky to implement but it worked out well. I like it and at least a few people have gotten some use out of it.

Git Bulk

Perform operations on git repositories in bulk.

$ git bulk status
git-js [master  origin/master  1]

vim-visual-page-percent [HEAD  ]
 M README.md

onion-oled [master  origin/master  2]
 M README.md
 M main.js

It has an optional JavaScript configuration file named .gitbulkconfig that look like this.

module.exports = {
  repositories: [
    "./vim-visual-page-percent",
    {
      path: "./git-js",
      group: "js",
    },
    {
      name: "onion-oled",
      path: "./onion-oled-js",
      group: "js",
    },
  ],
};

I started this project because I really needed it. I’ve been on team that have a lot of code and its usually split into small repositories. After one day I just had enough with cd and decided to make this. Its a crucial part of my workflow now on all multi-package projects that I do, in or out of work. You can use it too with npm install -g git-bulk.

Quick Cache

A simple, least recently used (LRU) cache in Elm with O(1) get, put and remove operations.

myCache =
    Cache.newCache 3
        |> Cache.put "a" 1
        |> Cache.put "b" 2
        |> Cache.put "c" 3
        |> Cache.put "d" 4

Cache.get "a" => (newCacheState, Nothing)
Cache.get "b" => (newCacheState, Just 2)
Cache.get "c" => (newCacheState, Just 3)
Cache.get "d" => (newCacheState, Just 4)

I was brushing up on some esoteric computer science for an interview and came across this LRU cache question. My naive attempt was to use a heap as the cache structure, but that would have yielded O(logn) insertions. My second approach was to use a linked list and a map, pulling the linked list node to the front every time its accessed and getting a O(1) reference to it through the map. I implemented it in Java and it worked but it was pretty gross. I reimplemented it in Elm with immutable data structures and quickly found that I couldn’t use my linked list mutation implementation. Instead, I implemented the linked list with a Map<string, Node> where Node is a record that contains a previous, next, and value. Saving a reference to head and tail of the linked list makes for a pretty complete implementation for the purposes of this problem. This was useful because I can’t change the previous or next values of the Node, but I can insert new entries in a map. I think it performs better than the other options available in Elm right now.

Systemconf

Utility to help in automating the installation of packages on fresh systems. It is based around a simple Domain Specific Language (DSL) written in PegJS that lets you specify what you need on your computer, and when and how to put it there. This is a sample configuration.

# These lines say that they only apply if the current operating system is ubuntu.
# This is the primitive statement in sysc that others build on. It says how to
# install, uninstall, and test for install status. Here, brew is being installed.
cmd({"os": "ubuntu"}) brew: install -- echo | ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Linuxbrew/install/master/install)"
cmd({"os": "ubuntu"}) brew: uninstall -- sudo rm -f $(which brew)
cmd({"os": "ubuntu"}) brew: isInstalled -- which brew


# This is a convenience around installing brew packages
brew toilet: toilet

# Same with git. This will clone from the first argument to the second arguments location
git oh-my-zsh: https://github.com/robbyrussell/oh-my-zsh.git ~/.oh-my-zsh

# And same with apt for debian distributions.
apt({"os":"ubuntu"}) essentials: git vim vim-gnome zsh mosh

# And for symlinks
symlink vimrc: ~/linux-dotfiles/files/.vimrc ~/.vimrc

And this is sample output using that configuration file. You can actually clone the repository and run this.

$ node ./lib/cli/systemconf.js install ./sample.sysc
Parsing ./sample.sysc...
Seeing which scripts are already installed...
All known schemas:
    brew
    toilet
    oh-my-zsh
    vundle
    powerlevel9k
    essentials
    ui_stuff
    tools
    yubikey
    build
    vimrc
    gitconfig
    gitignore
    tmux.conf
    vim_ftplugin
    vim_syntax
    xbindkeysrc
    zshrc
    platform_zshrc
    ssh_config

These are already installed:
oh-my-zsh
vundle
powerlevel9k
essentials
ui_stuff
tools
build
vimrc
gitconfig
gitignore
tmux.conf
vim_ftplugin
vim_syntax
xbindkeysrc
zshrc
ssh_config

These encountered errors while parsing the config file and will not be installed:
iiii
⤷ Couldn't parse a line in the config file: "iiii"
foobar
⤷ Wrong args supplied to a git config line for foobar. Each line should have included two args. Got https://no.second.arg.com
toprc
⤷ Target of the symlink doesn't exist: ~/linux-dotfiles/files/.toprcf

These will be installed with the following commands:
brew
⤷ echo | ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Linuxbrew/install/master/install)"
toilet
⤷ brew install toilet
yubikey
⤷ sudo apt-get install -y yubikey-neo-manager yubikey-personalization libpam-yubico
platform_zshrc
⤷ ln -fs ~/linux-dotfiles/files/.zshrc_ubuntu ~/.zshrc_platform

proceed? [y/N]:

I got into computer science through Linux and I’ve been a bit of a distro-hopper ever since. Reinstalling my OS constantly meant that I had to reinstall all of my software as well, and a lot of it wasn’t on the distribution repositories. I made a version of this that was essentially a big Makefile. That worked for a while but it was too easy for errors to leave my system in an awkward state that rerunning the script wouldn’t fix. I tried to rewrite it in Haskell as an excuse to use Haskell and learned a lot about IO. That implementation forced me to quarantine my side effects to a small portion of the program. Side effects here are installing software, testing if software is installed and reporting to the user. I ended up dropping Haskell for this project in part because of the tooling but also because I really want people to npm install my software. Systemconf is now written in TypeScript. Its mostly the same logic with a bit less rigor. You can run it and it won’t actually do anything but you’ll be able to see ahead of time exactly what happens. You also get a wrap-up summary where it confirms what went right and what went wrong.

Clojure Elm Parser

A Clojure and ClojureScript parser for Elm written in Instaparse.

(deftest test-function-call
  (let [input "Just \"string\""
        actual (parser/parser input :start :function_call)
        parses (insta/parses parser/parser input :start :function_call)
        parse-count (count parses)
        expected [:function_call
                  [:function_name [:Name "Just"]]
                  [:arguments [:expression [:value [:string "string"]]]]]]
    (is (= expected actual))
(is (= parse-count 1))))

This was really fun. I’ve always loved programming languages. My mentor in college showed me Lex/Yac (and their derivatives) and I’ve ben making little DSLs ever since. I started this project because I wanted to improve Elm tooling for Vim. I also wanted an excuse to use Clojure and Instaparse seemed like the go-to parsing library. Instaparse is pretty amazing. This was by far the quickest I’ve written a grammar for a language. I was able to focus entirely on the grammar instead of making stupid little helper classes and fighting with the lexer like I was used in with JFlex and Cup from my previous GraphQL parsing attempt. Instaparse outputs data from a parse, which was also new to me. I had a function that took in a string and gave me an AST without me having to do anything. Clojure also has a ton of useful stuff for manipulating data once you have it so this ended up being a really good introduction project. You can see the grammar here.

Pokeboard

Simple little app that I made while playing Pokemon during the pandemic. Full page here