~sumner/home-manager-config

0f276539fc2383567b81333b5698ed2c1f25e32f — Sumner Evans 1 year, 9 months ago 29a0e45 master
migrate to GitHub
135 files changed, 1 insertions(+), 6729 deletions(-)

D .envrc
D .gitattributes
D .gitignore
D .vim/coc-settings.json
D LICENSE
M README.md
D config.nix
D home.nix
D host-configurations/coruscant/audio-control-scripts.nix
D host-configurations/coruscant/default.nix
D host-configurations/jedha.nix
D host-configurations/mustafar.nix
D host-configurations/tatooine.nix
D modules/alacritty.nix
D modules/browsers.nix
D modules/calendar/default.nix
D modules/calendar/ics-subscription-import.nix
D modules/calendar/khal.nix
D modules/calendar/vdirsyncer.nix
D modules/communication.nix
D modules/default.nix
D modules/devtools.nix
D modules/email/accounts/account-config-helper.nix
D modules/email/accounts/admin-accounts.nix
D modules/email/accounts/default.nix
D modules/email/accounts/gmail.nix
D modules/email/accounts/personal.nix
D modules/email/accounts/summation-tech.nix
D modules/email/accounts/work.nix
D modules/email/bin/contact-query.py
D modules/email/bin/contact-search.py
D modules/email/bin/mutt-display-filter.py
D modules/email/contact-query.nix
D modules/email/default.nix
D modules/email/icalview.py
D modules/email/mailcap.nix
D modules/email/mailnotify.nix
D modules/email/mbsync.nix
D modules/email/neomutt.nix
D modules/email/offlinemsmtp.nix
D modules/email/quotes
D modules/fzf.nix
D modules/git.nix
D modules/gpg.nix
D modules/multimedia.nix
D modules/neovim/clipboard.nix
D modules/neovim/coc-settings.json
D modules/neovim/default.nix
D modules/neovim/filetype-specific-configs.vim
D modules/neovim/init.vim
D modules/neovim/plugins/ale.nix
D modules/neovim/plugins/blamer.nix
D modules/neovim/plugins/coc.nix
D modules/neovim/plugins/ctrlsf.nix
D modules/neovim/plugins/default.nix
D modules/neovim/plugins/deoplete.nix
D modules/neovim/plugins/editorconfig-vim.nix
D modules/neovim/plugins/fzf-vim.nix
D modules/neovim/plugins/nerdtree.nix
D modules/neovim/plugins/rainbow.nix
D modules/neovim/plugins/supertab.nix
D modules/neovim/plugins/vim-airline.nix
D modules/neovim/plugins/vim-autoswap.nix
D modules/neovim/plugins/vim-commentary.nix
D modules/neovim/plugins/vim-markdown-composer.nix
D modules/neovim/plugins/vim-multiple-cursors.nix
D modules/neovim/plugins/vim-rooter.nix
D modules/neovim/plugins/vim-signify.nix
D modules/neovim/plugins/vim-template.nix
D modules/neovim/plugins/vim-tmux-navigator.nix
D modules/neovim/plugins/vim-togglelist.nix
D modules/neovim/shortcuts.nix
D modules/neovim/theme.nix
D modules/newsboat.nix
D modules/scripts/bin/set-contact-photo.py
D modules/scripts/default.nix
D modules/scripts/projectsync.nix
D modules/scripts/sl.nix
D modules/ssh.nix
D modules/symlinks.nix
D modules/syncthing.nix
D modules/tmux.nix
D modules/tracktime.nix
D modules/udiskie.nix
D modules/window-manager/autorandr.nix
D modules/window-manager/bin/inactive-windows-transparency.py
D modules/window-manager/common.nix
D modules/window-manager/default.nix
D modules/window-manager/fuzzy-lock.nix
D modules/window-manager/i3status-rust.nix
D modules/window-manager/kanshi.nix
D modules/window-manager/rofi.nix
D modules/window-manager/wallpaper.nix
D modules/window-manager/wayland.nix
D modules/window-manager/writeping.nix
D modules/window-manager/xorg.nix
D modules/xdg.nix
D modules/yubikey.nix
D modules/zsh/aliases.nix
D modules/zsh/completion.nix
D modules/zsh/default.nix
D modules/zsh/dir-hashes.nix
D modules/zsh/functions.nix
D modules/zsh/key-widgets.zsh
D modules/zsh/plugins.nix
D modules/zsh/prompt.zsh
D pkgs/beeper-desktop.nix
D pkgs/mailnotify.nix
D pkgs/menucalc.nix
D pkgs/offlinemsmtp.nix
D pkgs/python-csmdirsearch.nix
D pkgs/python-gitlab.nix
D pkgs/sublime-music.nix
D pkgs/tracktime.nix
D programs/default.nix
D secrets/github-tracktime-access-token
D secrets/gitlab-api-key
D secrets/gitlab-email-key
D secrets/linear-personal-api-key
D secrets/mail/admin@bookmyti.me
D secrets/mail/admin@nevarro.space
D secrets/mail/admin@summation.tech
D secrets/mail/admin@sumnerevans.com
D secrets/mail/comments@sumnerevans.com
D secrets/mail/inquiries@sumnerevans.com
D secrets/mail/junk@sumnerevans.com
D secrets/mail/me@sumnerevans.com
D secrets/mail/sumner.evans98@gmail.com
D secrets/mail/sumner@beeper.com
D secrets/mail/sumner@summation.tech
D secrets/sourcehut-access-token
D secrets/vdirsyncer/gcp_client_id
D secrets/vdirsyncer/gcp_client_secret
D secrets/vdirsyncer/xandikos
D shell.nix
D .envrc => .envrc +0 -1
@@ 1,1 0,0 @@
use nix

D .gitattributes => .gitattributes +0 -1
@@ 1,1 0,0 @@
secrets/** filter=git-crypt diff=git-crypt

D .gitignore => .gitignore +0 -3
@@ 1,3 0,0 @@
.direnv
.secrets_password_file
host-config.nix

D .vim/coc-settings.json => .vim/coc-settings.json +0 -4
@@ 1,4 0,0 @@
{
  "python.formatting.provider": "black",
  "python.linting.enabled": true
}

D LICENSE => LICENSE +0 -21
@@ 1,21 0,0 @@
MIT License

Copyright (c) 2020 Sumner Evans

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

M README.md => README.md +1 -2
@@ 1,4 1,3 @@
# Home Manager Configuration

I use [Home Manager](https://github.com/nix-community/home-manager) to manage my
home directory.
MIGRATED TO GITHUB: https://github.com/sumnerevans/home-manager-config/

D config.nix => config.nix +0 -9
@@ 1,9 0,0 @@
{
  packageOverrides = pkgs: {
    nur = import (builtins.fetchTarball "https://github.com/nix-community/NUR/archive/master.tar.gz") {
      inherit pkgs;
    };
  };
  allowUnfree = true;
  android_sdk.accept_license = true;
}

D home.nix => home.nix +0 -16
@@ 1,16 0,0 @@
{ config, pkgs, ... }: {
  imports = [
    ./modules
    ./programs
    ./host-config.nix
  ];

  home = {
    stateVersion = "21.03";
    username = "sumner";
    homeDirectory = "/home/sumner";
  };

  # Always restart/start/stop systemd services on home manager switch.
  systemd.user.startServices = "sd-switch";
}

D host-configurations/coruscant/audio-control-scripts.nix => host-configurations/coruscant/audio-control-scripts.nix +0 -92
@@ 1,92 0,0 @@
{ config, pkgs, runtimeShell, ... }: let
  grep = "${pkgs.gnugrep}/bin/grep";
  pacmd = "${pkgs.pulseaudio}/bin/pacmd";
  sed = "${pkgs.gnused}/bin/sed";
  mkBashScript = scriptText: {
    executable = true;
    text = ''
      #!${pkgs.bash}/bin/bash
      ${scriptText}
    '';
  };
  binDir = "${config.home.homeDirectory}/bin";
in
{
  home.file."bin/change-audio-sink" = mkBashScript ''
    device_description=$1

    if [[ $device_description == "" ]]; then
        echo "Usage: ./change-audio-sink 'DESCRIPTION OF SINK'"
        exit 1
    fi

    # Find the index of the device with the given description.
    sinks="$(${pacmd} list-sinks | ${grep} -e 'index:' -e 'device.description')"
    i=0
    dev_index=0
    activate=0
    while IFS= read -r line; do
        if [ $(( $i % 2 )) -eq 0 ]; then
            dev_index="$(echo "$line" | ${sed} -n 's/^.*index: \(.*\)$/\1/p')"
        else
            description="$(echo "$line" | ${sed} -n 's/^.*device.description = "\(.*\)"$/\1/p')"

            if [[ $description =~ $device_description ]]; then
                break
            fi
        fi
        i=$(($i + 1))
    done <<< "$sinks"

    echo "Switching default sink to: $dev_index"
    ${pacmd} set-default-sink $dev_index

    sink_inputs="$(${pacmd} list-sink-inputs | ${grep} -e 'index:')"
    while IFS= read -r line; do
        input_id="$(echo "$line" | ${sed} -n 's/^.*index: \(.*\)$/\1/p')"
        echo "Switching input $input_id to $dev_index"
        ${pacmd} move-sink-input $input_id $dev_index
    done <<< "$sink_inputs"

    exit 2
  '';

  home.file."bin/current-audio-device" = mkBashScript ''
    # Find the description of the active audio sink.
    sinks="$(${pacmd} list-sinks | ${grep} -e 'index:' -e 'device.description')"
    i=0
    has_star=0
    activate=0
    while IFS= read -r line; do
        if [ $(( $i % 2 )) -eq 0 ]; then
            has_star="$(echo "$line" | ${sed} -n 's/^.*\(\*\) index: .*$/\1/p')"
        else
            description="$(echo "$line" | ${sed} -n 's/^.*device.description = "\(.*\)"$/\1/p')"

            if [[ $has_star == "*" ]]; then
                echo $description
                exit 0
            fi
        fi
        i=$(($i + 1))
    done <<< "$sinks"
  '';

  home.file."bin/toggle-audio" = mkBashScript ''
    # If it's on the headphones, toggle to speakers. Otherwise, toggle to the
    # headphones by default.
    if [[ $(${binDir}/current-audio-device) =~ "Family 17h (Models 00h-0fh)" ]]; then
        ${binDir}/change-audio-sink 'PCM2704 16-bit stereo audio'
    else
        ${binDir}/change-audio-sink 'Family 17h \(Models 00h-0fh\)'
    fi
  '';

  home.file."bin/headphones" = mkBashScript ''
    ${binDir}/change-audio-sink 'Family 17h \(Models 00h-0fh\)'
  '';

  home.file."bin/speakers" = mkBashScript ''
    ${binDir}/change-audio-sink 'PCM2704 16-bit stereo audio DAC'
  '';
}

D host-configurations/coruscant/default.nix => host-configurations/coruscant/default.nix +0 -18
@@ 1,18 0,0 @@
{ config, ... }: {
  imports = [
    ./audio-control-scripts.nix
  ];

  xorg.enable = true;
  xorg.remapEscToCaps = false;
  networking.interfaces = [ "enp37s0" "wlp35s0" ];
  programs.i3status-rust.extraBlocks = [
    {
      block = "custom";
      command = ''[[ $(${config.home.homeDirectory}/bin/current-audio-device) =~ 'PCM2704 16-bit stereo audio DAC' ]] && echo  ||  echo  '';
      on_click = "${config.home.homeDirectory}/bin/toggle-audio";
      interval = 5;
      priority = 89;
    }
  ];
}

D host-configurations/jedha.nix => host-configurations/jedha.nix +0 -14
@@ 1,14 0,0 @@
{
  wayland.enable = true;
  laptop.enable = true;
  networking.interfaces = [ "enp0s31f6" "wlp4s0" ];
  programs.i3status-rust.extraBlocks = [
    {
      block = "battery";
      interval = 30;
      format = "{percentage} {time}";
      device = "BAT1";
      priority = 111;
    }
  ];
}

D host-configurations/mustafar.nix => host-configurations/mustafar.nix +0 -35
@@ 1,35 0,0 @@
{ lib, ... }: with lib; rec {
  wayland.enable = true;
  laptop.enable = true;
  networking.interfaces = [ "wlp0s20f3" ];
  windowManager.modKey = "Mod1"; # use Alt as modifier on mustafar

  programs.alacritty.settings.font.size = 11;

  wayland.extraSwayConfig.config = {
    # Scale to 1.8 instead of 2.
    output.eDP-1.scale = "1.75";
    input = {
      "1:1:AT_Translated_Set_2_keyboard" = {
        xkb_layout = "us";
        xkb_variant = "3l-cros";
      };

      # Don't scroll so fast
      "1739:52731:Synaptics_TM3579-001".scroll_factor = "0.75";

      # Map the inputs so rotation works.
      "11551:157:WCOM50C1:00_2D1F:009D".map_to_output = "eDP-1";
      "0:0:Elan_Touchscreen".map_to_output = "eDP-1";
    };

    keybindings = {
      # F6/F7 are the brightness up/down keys. Without mod, change screen
      # brightness. With mod, change the keyboard.
      F6 = "exec brightnessctl s 5%-";
      F7 = "exec brightnessctl s 5%+";
      "${windowManager.modKey}+F6" = "exec brightnessctl -d chromeos::kbd_backlight s 1%-";
      "${windowManager.modKey}+F7" = "exec brightnessctl -d chromeos::kbd_backlight s 1%+";
    };
  };
}

D host-configurations/tatooine.nix => host-configurations/tatooine.nix +0 -1
@@ 1,1 0,0 @@
{ }

D modules/alacritty.nix => modules/alacritty.nix +0 -43
@@ 1,43 0,0 @@
{ lib, ... }: {
  programs.alacritty = {
    enable = true;
    settings = {
      window.title = "Terminal";
      font.size = lib.mkDefault 10;

      # Colors (One Dark)
      # https://github.com/eendroroy/alacritty-theme/blob/master/themes/one_dark.yaml
      colors = {
        # Default colors
        primary = {
          background = "0x1e2127";
          foreground = "0xabb2bf";
        };

        # Normal colors
        normal = {
          black = "0x1e2127";
          red = "0xe06c75";
          green = "0x98c379";
          yellow = "0xd19a66";
          blue = "0x3e82d6";
          magenta = "0xc678dd";
          cyan = "0x56b6c2";
          white = "0xabb2bf";
        };

        # Bright colors
        bright = {
          black = "0x5c6370";
          red = "0xe06c75";
          green = "0x98c379";
          yellow = "0xd19a66";
          blue = "0x61afef";
          magenta = "0xc678dd";
          cyan = "0x56b6c2";
          white = "0xffffff";
        };
      };
    };
  };
}

D modules/browsers.nix => modules/browsers.nix +0 -22
@@ 1,22 0,0 @@
{ config, lib, pkgs, ... }: with lib;
let
  chromeCommandLineArgs = "-high-dpi-support=0 -force-device-scale-factor=1";
  hasGui = config.wayland.enable || config.xorg.enable;
in
{
  home.packages = with pkgs; [
    elinks
    w3m
  ] ++ optionals hasGui [
    (google-chrome.override { commandLineArgs = chromeCommandLineArgs; })
  ];

  programs.chromium.enable = hasGui;
  programs.firefox.enable = hasGui;

  home.sessionVariables = mkIf hasGui {
    # Enable touchscreen in Firefox
    MOZ_USE_XINPUT2 = "1";
    MOZ_DBUS_REMOTE = "1";
  };
}

D modules/calendar/default.nix => modules/calendar/default.nix +0 -7
@@ 1,7 0,0 @@
{
  imports = [
    ./ics-subscription-import.nix
    ./khal.nix
    ./vdirsyncer.nix
  ];
}

D modules/calendar/ics-subscription-import.nix => modules/calendar/ics-subscription-import.nix +0 -36
@@ 1,36 0,0 @@
{ lib, config, pkgs, ... }: with lib; let
  # ICS import settings
  icsSubscriptions = [
    { uri = "https://acm.mines.edu/schedule/ical.ics"; importTo = "ACM"; }
    { uri = "https://lug.mines.edu/schedule/ical.ics"; importTo = "LUG"; }
  ];

  icsImportCurl = { uri, importTo }:
    "${pkgs.curl}/bin/curl '${uri}' | ${pkgs.khal}/bin/khal import --batch -a ${importTo}";

  icsSubscriptionImport = pkgs.writeShellScript "ics-subscription-import" ''
    set -xe
    ${concatMapStringsSep "\n" icsImportCurl icsSubscriptions}
  '';
in
{
  systemd.user.services."ics-subscription-import" = {
    Unit.Description = "Download the icsSubscriptions and import using khal.";

    Service = {
      Type = "oneshot";
      ExecStart = "${icsSubscriptionImport}";
    };
  };

  systemd.user.timers."ics-subscription-import" = {
    Unit.Description = "Download the icsSubscriptions and import using khal.";

    Timer = {
      OnCalendar = "*:0"; # Every hour
      Unit = "ics-subscription-import.service";
    };

    Install = { WantedBy = [ "timers.target" ]; };
  };
}

D modules/calendar/khal.nix => modules/calendar/khal.nix +0 -26
@@ 1,26 0,0 @@
{ lib, config, pkgs, ... }: {
  home.packages = [ pkgs.khal ];
  xdg.configFile."khal/config".text = ''
    [calendars]

    [[xandikos_calendar_local]]
    path = ${config.xdg.dataHome}/vdirsyncer/calendars/*
    type = discover

    [[xandikos_contacts_local]]
    path = ${config.xdg.dataHome}/vdirsyncer/contacts/addressbook
    type = birthdays

    [[beeper_calendar_local]]
    path = ${config.xdg.dataHome}/vdirsyncer/work-calendars/*
    type = discover

    [locale]
    timeformat = %H:%M
    dateformat = %Y-%m-%d
    longdateformat = %Y-%m-%d
    datetimeformat = %Y-%m-%d %H:%M
    longdatetimeformat = %Y-%m-%d %H:%M
    firstweekday = 6
  '';
}

D modules/calendar/vdirsyncer.nix => modules/calendar/vdirsyncer.nix +0 -87
@@ 1,87 0,0 @@
{ lib, config, pkgs, ... }: with lib; let
  vdirsyncer = "${pkgs.vdirsyncer}/bin/vdirsyncer";
  vdirsyncerScript = pkgs.writeShellScript "vdirsyncer" ''
    ${vdirsyncer} discover
    ${vdirsyncer} sync
    ${vdirsyncer} metasync
  '';

  passwordFetchCommand = passwordName:
    ''["command", "${pkgs.coreutils}/bin/cat", "${config.xdg.configHome}/nixpkgs/secrets/vdirsyncer/${passwordName}"]'';
in
{
  home.packages = [ pkgs.vdirsyncer ];
  systemd.user.services.vdirsyncer = {
    Unit.Description = "Synchronize Calendar and Contacts";

    Service = {
      Type = "oneshot";
      ExecStart = "${vdirsyncerScript}";
    };
  };

  systemd.user.timers.vdirsyncer = {
    Unit.Description = "Synchronize Calendar and Contacts";

    Timer = {
      OnCalendar = "*:0/15"; # Every 15 minutes
      Unit = "vdirsyncer.service";
    };

    Install = { WantedBy = [ "timers.target" ]; };
  };

  xdg.configFile."vdirsyncer/config".text = let
    typeToFileExt = type: if type == "contacts" then ".vcf" else ".ics";
    typeToRemoteType = type: if type == "contacts" then "carddav" else "caldav";
    mkPair = { name, metadata ? [ "displayname" ] }: ''
      [pair xandikos_${name}]
      a = "xandikos_${name}_local"
      b = "xandikos_${name}_remote"
      collections = ["from a", "from b"]
      conflict_resolution = "b wins"
      metadata = [${concatMapStringsSep ", " (x: ''"${x}"'') metadata}]

      [storage xandikos_${name}_local]
      type = "filesystem"
      path = "${config.xdg.dataHome}/vdirsyncer/${name}/"
      fileext = "${typeToFileExt name}"

      [storage xandikos_${name}_remote]
      type = "${typeToRemoteType name}"
      url = "https://dav.sumnerevans.com/"
      username = "sumner"
      password.fetch = ${passwordFetchCommand "xandikos"}
    '';
  in
    ''
      [general]
      # A folder where vdirsyncer can store some metadata about each pair.
      status_path = "${config.xdg.dataHome}/vdirsyncer/status/"

      # Contacts
      ${mkPair { name = "contacts"; }}

      # Calendar
      ${mkPair { name = "calendars"; metadata = [ "displayname" "color" ]; }}

      # Work Calendar
      [pair beeper_google_calendar]
      a = "beeper_google_calendar_local"
      b = "beeper_google_calendar_remote"
      collections = ["from a", "from b"]
      conflict_resolution = "b wins"
      metadata = [ "displayname", "color" ]

      [storage beeper_google_calendar_local]
      type = "filesystem"
      path = "${config.xdg.dataHome}/vdirsyncer/work-calendars/"
      fileext = ".ics"

      [storage beeper_google_calendar_remote]
      type = "google_calendar"
      token_file = "${config.xdg.dataHome}/vdirsyncer/beeper_google_calendar_token_file"
      client_id.fetch = ${passwordFetchCommand "gcp_client_id"}
      client_secret.fetch = ${passwordFetchCommand "gcp_client_secret"}
    '';
}

D modules/communication.nix => modules/communication.nix +0 -21
@@ 1,21 0,0 @@
{ config, lib, pkgs, ... }: with lib; with pkgs; let
  beeper-desktop = pkgs.callPackage ../pkgs/beeper-desktop.nix { };
  element-desktop = pkgs.element-desktop.overrideAttrs (old: {
    fixupPhase = ''
      sed -i "s#exec#LD_PRELOAD=${pkgs.sqlcipher}/lib/libsqlcipher.so exec#g" "$out/bin/element-desktop"
    '';
  });
in
{
  home.packages = optionals (config.wayland.enable || config.xorg.enable)
    [
      beeper-desktop
      discord
      element-desktop
      fractal
      mumble
      neochat
      nheko
      zoom-us
    ];
}

D modules/default.nix => modules/default.nix +0 -37
@@ 1,37 0,0 @@
{ pkgs, ... }:
let
  editor = "${pkgs.neovim}/bin/nvim";
in
{
  imports = [
    ./alacritty.nix
    ./browsers.nix
    ./calendar
    ./communication.nix
    ./devtools.nix
    ./email
    ./fzf.nix
    ./git.nix
    ./gpg.nix
    ./multimedia.nix
    ./neovim
    ./newsboat.nix
    ./scripts
    ./ssh.nix
    ./symlinks.nix
    ./syncthing.nix
    ./tmux.nix
    ./tracktime.nix
    ./udiskie.nix
    ./window-manager
    ./xdg.nix
    ./yubikey.nix
    ./zsh
  ];

  home.sessionVariables = {
    COLORTERM = "truecolor";
    VISUAL = "${editor}";
    EDITOR = "${editor}";
  };
}

D modules/devtools.nix => modules/devtools.nix +0 -59
@@ 1,59 0,0 @@
{ config, lib, pkgs, ... }: with lib; let
  tomlFormat = pkgs.formats.toml { };
  hasGui = config.wayland.enable || config.xorg.enable;
  exposePort = pkgs.writeShellScriptBin "exposeport" ''
    sudo ssh -L $2:localhost:$2 $1
  '';
in
{
  options = {
    devTools.enable = mkEnableOption "developer tools and applications" // {
      default = true;
    };
  };

  config = mkIf config.devTools.enable {
    home.packages = with pkgs; [
      # Shell Utilities
      delta
      eternal-terminal
      mosh
      watchexec

      # SQL Terminal GUI
      litecli
      pgcli
    ] ++ (
      # GUI Tools
      optionals hasGui [
        android-studio
        dfeet
        jetbrains.idea-community
        openjdk11
        postman
        sqlitebrowser
        wireshark
      ]
    );

    programs.zsh.shellAliases = {
      tat = "${pkgs.eternal-terminal}/bin/et tatooine";
      tat-synapse = "${exposePort}/bin/exposeport tatooine.sumnerevans.com 8008";
    };

    # Enable developer programs
    programs.direnv.enable = true;
    programs.direnv.nix-direnv.enable = true;
    programs.jq.enable = true;
    programs.opam.enable = true;
    programs.vscode.enable = hasGui;

    xdg.configFile."pypoetry/config.toml".source = tomlFormat.generate "config.toml" {
      virtualenvs.in-project = true;
    };

    home.file.".ideavimrc".text = ''
      set clipboard+=unnamed
    '';
  };
}

D modules/email/accounts/account-config-helper.nix => modules/email/accounts/account-config-helper.nix +0 -77
@@ 1,77 0,0 @@
{ config, pkgs, lib, ... }: with lib; let
  offlinemsmtp = pkgs.callPackage ../../../pkgs/offlinemsmtp.nix { };

  # Create a signature script that gets a quote.
  mkSignatureScript = signatureLines: pkgs.writeScript "signature" ''
    #!${pkgs.python3}/bin/python
    import subprocess

    ${concatMapStringsSep "\n" (l: ''print("${l}")'') (splitString "\n" signatureLines)}

    # Quote
    quotes_cmd = ["${pkgs.fortune}/bin/fortune", "${config.xdg.dataHome}/fortune/quotes"]
    quote = ""
    # Not sure why, but sometimes fortune returns an empty fortune.
    while len(quote) == 0:
        quote = subprocess.check_output(quotes_cmd).decode("utf-8").strip()
    print(quote)
  '';
in
{
  # Common configuration
  commonConfig = { address, name, color ? "", ... }: {
    inherit address;

    realName = mkDefault "Sumner Evans";
    userName = mkDefault address;
    passwordCommand = mkDefault "${pkgs.coreutils}/bin/cat ${config.xdg.configHome}/nixpkgs/secrets/mail/${address}";

    mbsync = {
      enable = true;
      create = "both";
      remove = "both";
    };

    msmtp.enable = true;

    neomutt = {
      enable = true;
      sendMailCommand = "${offlinemsmtp}/bin/offlinemsmtp -a ${name}";

      extraConfig = concatStringsSep "\n" (
        [
          ''set folder="${config.accounts.email.maildirBasePath}"''
          ''set pgp_default_key = "B50022FD"''
        ]
        ++ (optional (color != "") "color status ${color} default")
      );
    };

    folders.inbox = "INBOX";
  };

  gpgConfig.gpg = {
    encryptByDefault = true;
    key = "3F15C22BFD125095F9C072758904527AB50022FD";
    signByDefault = true;
  };

  imapnotifyConfig = { name, ... }: {
    imapnotify = {
      enable = true;
      boxes = [ "INBOX" ];
      onNotify = "${pkgs.isync}/bin/mbsync ${name}:%s";
    };
  };

  migaduConfig = {
    imap.host = "imap.migadu.com";
    smtp.host = "smtp.migadu.com";
  };

  signatureConfig = { signatureLines, ... }: {
    neomutt.extraConfig = ''
      set signature="${mkSignatureScript signatureLines}|"
    '';
  };
}

D modules/email/accounts/admin-accounts.nix => modules/email/accounts/admin-accounts.nix +0 -79
@@ 1,79 0,0 @@
{ config, pkgs, lib, ... }: with lib; let
  adminConfig = {
    name = "Admin";
    address = "admin@sumnerevans.com";
    color = "green";
  };

  commentsConfig = {
    name = "Comments";
    address = "comments@sumnerevans.com";
    color = "green";
  };

  inquiriesConfig = {
    name = "Inquiries";
    address = "inquiries@sumnerevans.com";
    color = "green";
  };

  junkConfig = {
    name = "Junk";
    address = "junk@sumnerevans.com";
    color = "red";
  };

  bookMyTimeAdmin = {
    name = "BMT-Admin";
    address = "admin@bookmyti.me";
    color = "green";
  };

  nevarroAdmin = {
    name = "Nevarro-Admin";
    address = "admin@nevarro.space";
    color = "green";
  };

  helper = import ./account-config-helper.nix { inherit config pkgs lib; };
in
{
  accounts.email.accounts = {
    Admin = mkMerge [
      (helper.commonConfig adminConfig)
      helper.migaduConfig
      {
        aliases = [
          "abuse@sumnerevans.com"
          "hostmaster@sumnerevans.com"
          "postmaster@sumnerevans.com"
        ];
      }
    ];

    Comments = mkMerge [
      (helper.commonConfig commentsConfig)
      helper.migaduConfig
    ];

    Inquiries = mkMerge [
      (helper.commonConfig inquiriesConfig)
      helper.migaduConfig
    ];

    Junk = mkMerge [
      (helper.commonConfig junkConfig)
      helper.migaduConfig
    ];

    BMT-Admin = mkMerge [
      (helper.commonConfig bookMyTimeAdmin)
      helper.migaduConfig
    ];

    Nevarro-Admin= mkMerge [
      (helper.commonConfig nevarroAdmin)
      helper.migaduConfig
    ];
  };
}

D modules/email/accounts/default.nix => modules/email/accounts/default.nix +0 -11
@@ 1,11 0,0 @@
{ config, ... }: {
  imports = [
    ./admin-accounts.nix
    ./gmail.nix
    ./personal.nix
    ./summation-tech.nix
    ./work.nix
  ];

  accounts.email.maildirBasePath = "${config.home.homeDirectory}/Mail";
}

D modules/email/accounts/gmail.nix => modules/email/accounts/gmail.nix +0 -35
@@ 1,35 0,0 @@
{ config, pkgs, lib, ... }: with lib; let
  accountConfig = {
    address = "sumner.evans98@gmail.com";
    name = "Gmail";
    color = "yellow";
    signatureLines = ''
      Sumner Evans
      Software Engineer at Beeper
      2 Chronicles 7:14

      https://sumnerevans.com | @sumner:nevarro.space | GPG: B50022FD

      Note, this is not my main email, please update your contact information
      for me to my new email: me@sumnerevans.com.
    '';
  };

  helper = import ./account-config-helper.nix { inherit config pkgs lib; };
in
{
  accounts.email.accounts.Gmail = mkMerge [
    (helper.commonConfig accountConfig)
    (helper.imapnotifyConfig accountConfig)
    (helper.signatureConfig accountConfig)
    helper.gpgConfig
    {
      flavor = "gmail.com";
      folders = {
        drafts = "[Gmail]/Drafts";
        sent = "[Gmail]/Sent Mail";
        trash = "[Gmail]/Trash";
      };
    }
  ];
}

D modules/email/accounts/personal.nix => modules/email/accounts/personal.nix +0 -29
@@ 1,29 0,0 @@
{ config, pkgs, lib, ... }: with lib; let
  accountConfig = {
    address = "me@sumnerevans.com";
    name = "Personal";
    color = "green";
    signatureLines = ''
      Sumner Evans
      Software Engineer at Beeper
      2 Chronicles 7:14

      https://sumnerevans.com | @sumner:nevarro.space | GPG: B50022FD
    '';
  };

  helper = import ./account-config-helper.nix { inherit config pkgs lib; };
in
{
  accounts.email.accounts.Personal = mkMerge [
    (helper.commonConfig accountConfig)
    (helper.imapnotifyConfig accountConfig)
    (helper.signatureConfig accountConfig)
    helper.gpgConfig
    helper.migaduConfig
    {
      primary = true;
      aliases = [ "alerts@sumnerevans.com" "resume@sumnerevans.com" ];
    }
  ];
}

D modules/email/accounts/summation-tech.nix => modules/email/accounts/summation-tech.nix +0 -28
@@ 1,28 0,0 @@
{ config, pkgs, lib, ... }: with lib; let
  summationTechAdmin = {
    name = "ST-Admin";
    address = "admin@summation.tech";
    color = "red";
  };

  summationTechSumner = {
    name = "ST-Sumner";
    address = "sumner@summation.tech";
    color = "red";
  };

  helper = import ./account-config-helper.nix { inherit config pkgs lib; };
in
{
  accounts.email.accounts = {
    ST-Admin = mkMerge [
      (helper.commonConfig summationTechAdmin)
      helper.migaduConfig
    ];

    ST-Sumner = mkMerge [
      (helper.commonConfig summationTechSumner)
      helper.migaduConfig
    ];
  };
}

D modules/email/accounts/work.nix => modules/email/accounts/work.nix +0 -29
@@ 1,29 0,0 @@
{ config, pkgs, lib, ... }: with lib; let
  accountConfig = {
    address = "sumner@beeper.com";
    name = "Work";
    color = "blue";
    signatureLines = ''
      Sumner Evans | Software Engineer at Beeper
      https://sumnerevans.com | @sumner:nevarro.space | GPG: B50022FD
    '';
  };

  helper = import ./account-config-helper.nix { inherit config pkgs lib; };
in
{
  accounts.email.accounts.Work = mkMerge [
    (helper.commonConfig accountConfig)
    (helper.imapnotifyConfig accountConfig)
    (helper.signatureConfig accountConfig)
    helper.gpgConfig
    {
      flavor = "gmail.com";
      folders = {
        drafts = "[Gmail]/Drafts";
        sent = "[Gmail]/Sent Mail";
        trash = "[Gmail]/Trash";
      };
    }
  ];
}

D modules/email/bin/contact-query.py => modules/email/bin/contact-query.py +0 -120
@@ 1,120 0,0 @@
#! /usr/bin/env python3
# Depends on:
#  - python-vobject
#    (https://www.archlinux.org/packages/community/any/python-vobject/) for
#    querying the synced contacts from Xandikos
#  - csmdirsearch (https://github.com/jackrosenthal/csmdirsearch/) by Jack
#    Rosenthal for searching for Colorado School of Mines students and faculty
#    email addresses.
#  - python-gitlab (https://github.com/python-gitlab/python-gitlab to enable
#    GitLab issue creation auto-complete.
#  - python-fuzzywuzzy (https://github.com/seatgeek/fuzzywuzzy) fuzzy search
#    for GitLab projects
#  - python-levenshtein

import os
import sys
import concurrent

from subprocess import PIPE, run
from pathlib import Path

import csmdirsearch
import gitlab
import vobject

from fuzzywuzzy import fuzz

# Read from the config file
with open(os.path.expanduser('~/.config/contact-query/config')) as f:
    emailkey = f.readline().strip()
    groups = []
    for line in f:
        groups.append(line.strip())


def test_internet():
    """
    Tests whether or not the computer is currently connected to the internet.
    """
    command = ['ping', '-c', '1', '8.8.8.8']
    return run(command, stdout=PIPE, stderr=PIPE).returncode == 0


if len(sys.argv) < 2:
    print('Enter something, you moron.')
    sys.exit(1)


def query_vdirsyncer(query):
    query = query.lower()

    contacts_dir = Path(
        '~/.local/share/vdirsyncer/contacts/addressbook').expanduser()
    for contact_file in contacts_dir.glob('*.vcf'):
        with open(contact_file) as cf:
            contact = vobject.readOne(cf.read())
            fullname = contact.fn.value

            emails = (
                c.value for c in contact.getChildren()
                if c.name.lower() == 'email' and c.value and c.value != '')
            for email in emails:
                fullname_email = f'{contact.fn.value} <{email}>'
                if fuzz.partial_ratio(query, fullname_email.lower()) > 70:
                    yield f'{email}\t{fullname}'


def query_gitlab(query):
    if not test_internet():
        return
    query = query.lower()

    # GitLab Projects
    gl = gitlab.Gitlab.from_config()
    gl.auth()
    gl_username = gl.user.username
    me = gl.users.list(username=gl_username)[0]
    projects = [(gl_username, p.attributes['name'])
                for p in me.projects.list(all=True)]

    for g in groups:
        projects.extend([(g, p.attributes['name'])
                         for p in gl.groups.get(g).projects.list(all=True)])

    for g, p in projects:
        if fuzz.partial_ratio(query, p.lower()) > 70:
            yield f'incoming+{g}/{p}+{emailkey}@incoming.gitlab.com\t{g}/{p}'


def query_csmdirsearch(query):
    if not test_internet():
        return

    for result in csmdirsearch.search(query):
        if not hasattr(result, 'business_email'):
            continue
        yield '{}\t{}\t{}'.format(
            result.business_email,
            result.name.strfname('{pfirst} {last}'),
            result.desc,
        )


with concurrent.futures.ThreadPoolExecutor(max_workers=30) as executor:
    query = " ".join(sys.argv[1:])
    futures = [
        executor.submit(fn, query) for fn in (
            query_vdirsyncer,
            query_gitlab,
            query_csmdirsearch,
        )
    ]
    print()  # Mutt ignores the first line
    for future in concurrent.futures.as_completed(futures):
        try:
            for r in (future.result() or []):
                print(r)
        except Exception:
            # Who cares
            pass

D modules/email/bin/contact-search.py => modules/email/bin/contact-search.py +0 -80
@@ 1,80 0,0 @@
#! /usr/bin/env python3
# Depends on:
#  - python-vobject
#    (https://www.archlinux.org/packages/community/any/python-vobject/) for
#    querying the synced contacts from Xandikos
#  - csmdirsearch (https://github.com/jackrosenthal/csmdirsearch/) by Jack
#    Rosenthal for searching for Colorado School of Mines students and faculty
#    email addresses.
#  - goobook (https://gitlab.com/goobook/goobook/) to search Google contacts.
#  - python-gitlab (https://github.com/python-gitlab/python-gitlab to enable
#    GitLab issue creation auto-complete.
#  - python-fuzzywuzzy (https://github.com/seatgeek/fuzzywuzzy) fuzzy search
#    for GitLab projects
#  - python-levenshtein

import sys
from pathlib import Path
from typing import List

import tabulate
import vobject

from fuzzywuzzy import fuzz, process

query = " ".join(sys.argv[1:])
contacts_dir = Path("~/.local/share/vdirsyncer/contacts/addressbook").expanduser()
contacts = []

for contact_file in contacts_dir.glob("*.vcf"):
    with open(contact_file) as cf:
        contact = vobject.readOne(cf.read())
        fullname = contact.fn.value
        if (score := process.extractOne(query, fullname.lower().split())[1]) > 70:
            contacts.append((fuzz.ratio(query, fullname.lower()), contact))


def parse_type(typestrs: List[str]):
    if len(typestrs) == 0:
        return ""
    typestr = typestrs[0]
    return "(" + (
        (typestr[2:] if typestr.startswith("x-") else typestr).replace("_", " ").lower()
    ) + ")"


def pluralize(word: str, items: int):
    return (
        word if items == 1 else (word + "S" if not word.endswith("S") else word + "ES")
    )


for i, (score, contact) in enumerate(
    sorted(contacts, key=lambda x: (x[0], x[1].fn.value), reverse=True)
):
    if i > 0:
        print()
        print("=" * 80)
        print()

    emails = []
    phone_numbers = []
    addresses = []
    for c in contact.getChildren():
        name = c.name.lower()
        if name == "email":
            if c.value and c.value != "":
                emails.append(f"{c.value} {parse_type(c.params.get('TYPE', []))}")
        elif name == "adr":
            addresses.append(f"{c.value} {parse_type(c.params.get('TYPE', []))}")
        elif name == "tel":
            phone_numbers.append(f"{c.value} {parse_type(c.params.get('TYPE', []))}")

    rows = [
        ("NAME", contact.fn.value),
        (pluralize("EMAIL", len(emails)), "\n".join(emails)),
        (pluralize("PHONE NUMBER", len(phone_numbers)), "\n".join(phone_numbers)),
        (pluralize("ADDRESS", len(addresses)), "\n\n".join(addresses)),
    ]

    print(tabulate.tabulate(rows, tablefmt="plain"))

D modules/email/bin/mutt-display-filter.py => modules/email/bin/mutt-display-filter.py +0 -162
@@ 1,162 0,0 @@
#! /usr/bin/env python3
import os
import pathlib
import re
import sys
from datetime import datetime
from functools import lru_cache

import pytz
from dateutil import parser


class bcolors:
    BLUE = "\x1B[34m"
    GREEN = "\x1B[32m"
    YELLOW = "\x1B[33m"
    RED = "\x1B[31m"
    ENDC = "\033[0m"
    BOLD = "\x1B[1m"


@lru_cache(maxsize=None)
def create_url_verify_page(url):
    tmp_dir = pathlib.Path(os.path.expanduser("~/tmp/mdf/"))
    tmp_dir.mkdir(parents=True, exist_ok=True)
    if len(os.listdir(tmp_dir)) == 0:
        next_num = 1
    else:
        next_num = max(int(x, 16) for x in os.listdir(tmp_dir)) + 1

    filename = os.path.expanduser(f"~/tmp/mdf/{hex(next_num)}")

    with open(filename, "w+") as f:
        f.write(
            f"""
            <!doctype html>
            <html>
            <body>
                <textarea id="url_edit"
                          rows="10"
                          style="width: 100%;">{url}</textarea>
                <br />
                <input id="go" type="button" value="Go" />
                <script>
                    const redirect = () => {{
                        const url = document.getElementById('url_edit').value;
                        window.location.replace(url);
                    }}
                    document.getElementById('go').addEventListener('click', redirect);
                    document.onkeypress = e => {{
                        if (e.keyCode === 13) {{
                            redirect();
                            return false;
                        }}
                    }};
                    document.getElementById('url_edit').focus();
                </script>
            </body>
            </html>
            """
        )

    return f"<file://{filename}>"


def parse_datetime(datetime_string):
    # https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior
    alternative_formats = []
    try:
        return parser.parse(datetime_string, fuzzy_with_tokens=True)[0]
    except ValueError:
        for f in alternative_formats:
            try:
                return datetime.strptime(datetime_string, f)
            except ValueError:
                pass


# Git diff state
hit_diff = False
hit_diff_footer = False
git_summary_file_re = re.compile(r" .* \| +\d+ [\+-]")
git_summary_re = re.compile(
    r" (\d+) (files?) changed(?:, (\d+) (insertions?)\(\+\))(?:, (\d+) (deletions?)\(-\))"
)
diff_re = re.compile("diff --git")
diff_desc_re = re.compile(r"@@ (-\d+,\d+ \+\d+,\d+) @@")

date_re = re.compile("Date: (.*)")
email_re = re.compile("<mailto:(.*?)>")
url_re = re.compile(r"(https?://[^\s]*)")


for line in sys.stdin.readlines():
    # Git handling
    if diff_re.match(line):
        hit_diff = True
        line = f"{bcolors.BOLD}{line}{bcolors.ENDC}"

    elif match := git_summary_re.match(line):
        line = f" {bcolors.BOLD}{match.group(1)} {match.group(2)} changed"
        if match.group(3) and match.group(4):
            line += f", {bcolors.GREEN}{match.group(3)} {match.group(4)}(+)"
        if match.group(5) and match.group(6):
            line += f", {bcolors.RED}{match.group(5)} {match.group(6)}(-){bcolors.ENDC}"
        line += "\n"

    elif git_summary_file_re.match(line):
        parts = line.split(" ")
        adds, removes = parts[-1].count("+"), parts[-1].count("-")
        parts[-2] = f"{bcolors.BOLD}{parts[-2]}{bcolors.ENDC}"
        parts[-1] = (
            f"{bcolors.GREEN}{'+' * adds}{bcolors.ENDC}"
            + f"{bcolors.RED}{'-' * removes}{bcolors.ENDC}"
        )
        line = " ".join(parts) + "\n"

    elif hit_diff_footer:
        line = f"{bcolors.YELLOW}{line}{bcolors.ENDC}"

    elif hit_diff:
        if line.startswith("--") and not line.startswith("---"):
            hit_diff_footer = True
            line = f"{bcolors.YELLOW}{line}{bcolors.ENDC}"
        elif line.startswith("-"):
            line = f"{bcolors.RED}{line}{bcolors.ENDC}"
        elif line.startswith("+"):
            line = f"{bcolors.GREEN}{line}{bcolors.ENDC}"
        elif diff_desc_re.match(line):
            line = f"{bcolors.BLUE}{line}{bcolors.ENDC}"
        elif line.startswith(" "):
            pass
        else:
            line = f"{bcolors.BOLD}{line}{bcolors.ENDC}"

    else:
        date_match = date_re.match(line)
        if date_match:
            # Fix Date to be in the local time
            date_str = date_match.groups()[0]
            dt = parse_datetime(date_str)
            if not dt:
                print("Date Parse Fail")
            else:
                tz_name = "/".join(os.readlink("/etc/localtime").split("/")[-2:])
                dt = dt.astimezone(pytz.timezone(tz_name))
                line = "Date: {}\n".format(dt.strftime("%a, %b %d %H:%M:%S %Y (%Z)"))
        elif email_re.findall(line):
            # Remove redundant <mailto:{email}>.
            for email in email_re.findall(line):
                if f"{email}<mailto:{email}>" in line:
                    line = line.replace(f"<mailto:{email}>", "")
        elif url_re.findall(line):
            # Create a HTML page that has a link to the actual URL, and give a
            # local address instead of a long URL.
            for url in url_re.findall(line):
                if len(url) > 30:
                    url_verify_page_filename = create_url_verify_page(url)
                    if len(url_verify_page_filename) < len(url):
                        line = line.replace(url, url_verify_page_filename)

    print(line, end="")

D modules/email/contact-query.nix => modules/email/contact-query.nix +0 -19
@@ 1,19 0,0 @@
{ lib, pkgs, ... }: let
  contact-query = pkgs.writeScriptBin "contact-query" (builtins.readFile ./bin/contact-query.py);
  contact-search = pkgs.writeScriptBin "contact-search" (builtins.readFile ./bin/contact-search.py);
in
{
  home.packages = [
    contact-query
    contact-search
  ];

  programs.neomutt.extraConfig = ''
    set query_command="${contact-query}/bin/contact-query %s"
  '';

  xdg.configFile."contact-query/config".text = ''
    ${lib.removeSuffix "\n" (builtins.readFile ../../secrets/gitlab-email-key)}
    ColoradoSchoolOfMines
  '';
}

D modules/email/default.nix => modules/email/default.nix +0 -24
@@ 1,24 0,0 @@
{ config, pkgs, ... }: with pkgs; let
  quotesPath = "${config.xdg.dataHome}/fortune/quotes";
in
{
  imports = [
    ./accounts
    ./contact-query.nix
    ./mailcap.nix
    ./mailnotify.nix
    ./mbsync.nix
    ./neomutt.nix
    ./offlinemsmtp.nix
  ];

  services.imapnotify.enable = true;
  programs.msmtp.enable = true;

  home.file = {
    "${quotesPath}" = {
      source = ./quotes;
      onChange = "${pkgs.fortune}/bin/strfile -r ${quotesPath}";
    };
  };
}

D modules/email/icalview.py => modules/email/icalview.py +0 -37
@@ 1,37 0,0 @@
#! /usr/bin/env python3

import sys

import html2text
import pytz
from icalendar import Calendar

calendar = Calendar.from_ical(sys.stdin.read())

for event in calendar.walk('vevent'):
    tz = pytz.timezone('America/Denver')
    summary = event.get('SUMMARY')
    start = event.decoded('DTSTART').astimezone(tz)
    end = event.decoded('DTEND').astimezone(tz)

    attendees = None
    if 'ATTENDEE' in event:
        if type(event.decoded('ATTENDEE')) == list:
            attendees = ',\n               '.join(
                [a[7:] for a in event.decoded('ATTENDEE')])
        else:
            attendees = event.decoded('ATTENDEE')[7:]

    description = event.get('DESCRIPTION') or ''
    description = html2text.html2text(description)
    description = '    ' + '\n    '.join(description.split('\n'))

    print('=' * len(summary))
    print(summary)
    print('=' * len(summary))
    print()
    print('  Start:      ', start)
    print('  End:        ', end)
    print('  Attendee(s):', attendees)
    print('  Description:')
    print(description)

D modules/email/mailcap.nix => modules/email/mailcap.nix +0 -37
@@ 1,37 0,0 @@
{ pkgs, ... }: let
in
{
  xdg.configFile."neomutt/mailcap".text = let
    feh = "${pkgs.feh}/bin/feh";
    libreoffice = "${pkgs.libreoffice}/bin/libreoffice";
    icalviewScript = pkgs.writeScript "icalview" (builtins.readFile ./icalview.py);
  in
    ''
      # HTML
      text/html; ${pkgs.elinks}/bin/elinks -dump %s; copiousoutput;

      # PDF documents
      application/pdf; ${pkgs.zathura}/bin/zathura %s

      # Images
      image/jpg; ${feh} %s
      image/jpeg; ${feh} %s
      image/pjpeg; ${feh} %s
      image/png; ${feh} %s
      image/gif; ${feh} %s

      # iCal
      text/calendar; ${icalviewScript}; copiousoutput
      application/calendar; ${icalviewScript}; copiousoutput
      application/ics; ${icalviewScript}; copiousoutput

      # Office Suites
      application/msword; ${libreoffice} %s;
      application/vnd.ms-word.document.12; ${libreoffice} %s;
      application/vnd.openxmlformats-officedocument.wordprocessingml.document; ${libreoffice} %s;
      application/vnd.oasis.opendocument.text; ${libreoffice} %s;

      # Microsoft LookOut
      application/ms-tnef; ${pkgs.tnef}/bin/tnef -w -C /home/sumner/tmp %s;
    '';
}

D modules/email/mailnotify.nix => modules/email/mailnotify.nix +0 -28
@@ 1,28 0,0 @@
{ config, lib, pkgs, ... }:
let
  mailnotify = pkgs.callPackage ../../pkgs/mailnotify.nix { };
  hasGui = config.wayland.enable || config.xorg.enable;
in
{
  systemd.user.services.mailnotify = lib.mkIf hasGui {
    Unit = {
      Description = "mailnotify daemon";
      PartOf = [ "graphical-session.target" ];
    };

    Service = {
      ExecStart = ''
        ${mailnotify}/bin/mailnotify ${config.accounts.email.maildirBasePath}
      '';
      Environment = [
        "ICON_PATH=${pkgs.gnome-icon-theme}/share/icons/gnome/48x48/status/mail-unread.png"
      ];
      Restart = "always";
      RestartSec = 5;
    };

    Install = {
      WantedBy = [ "graphical-session.target" ];
    };
  };
}

D modules/email/mbsync.nix => modules/email/mbsync.nix +0 -24
@@ 1,24 0,0 @@
{ pkgs, ... }: {
  programs.mbsync.enable = true;

  services.mbsync = let
    checkNetworkOrAlreadyRunningScript = pkgs.writeShellScript "cknetpgrep" ''
      # Check that the network is up.
      ${pkgs.iputils}/bin/ping -c 1 8.8.8.8
      if [[ "$?" != "0" ]]; then
          echo "Couldn't contact the network. Exiting..."
          exit 1
      fi

      # Chcek to see if we are already syncing.
      if ${pkgs.procps}/bin/pgrep mbsync &>/dev/null; then
          echo "Process $pid already running. Exiting..." >&2
          exit 1
      fi
    '';
  in {
    enable = true;
    preExec = "${checkNetworkOrAlreadyRunningScript}";
    frequency = "*:0/10";
  };
}

D modules/email/neomutt.nix => modules/email/neomutt.nix +0 -163
@@ 1,163 0,0 @@
{ config, lib, pkgs, ... }: with pkgs; let
  aliasfile = "${config.xdg.configHome}/neomutt/aliases";
  mailboxfile = "${config.xdg.configHome}/neomutt/mailboxes";
  bindir = "${config.home.homeDirectory}/bin";
  mutt-display-filter = pkgs.writeScriptBin "mdf" (builtins.readFile ./bin/mutt-display-filter.py);

  syncthingdir = "${config.home.homeDirectory}/Syncthing";
in
{
  home.file."bin/mutt_helper" = lib.mkIf (config.wayland.enable || config.xorg.enable) {
    text = ''
      #!/usr/bin/env sh
      set -xe
      if [[ $# == 0 ]]; then
        ${config.home.sessionVariables.TERMINAL} -t Mutt -e \
          zsh -c "export FOR_MUTT_HELPER=1 && source ${config.home.homeDirectory}/.zshrc && neomutt"
      else
        ${config.home.sessionVariables.TERMINAL} -t Mutt -e \
          zsh -c "export FOR_MUTT_HELPER=1 && source ${config.home.homeDirectory}/.zshrc && neomutt \"$@\""
      fi
    '';
    executable = true;
  };

  home.symlinks."${aliasfile}" = "${syncthingdir}/.config/neomutt/aliases";
  home.symlinks."${mailboxfile}" = "${syncthingdir}/.config/neomutt/mailboxes";

  programs.neomutt = {
    enable = true;
    vimKeys = true;
    binds = [
      { action = "complete-query"; key = "<Tab>"; map = [ "editor" ]; }
      { action = "group-reply"; key = "R"; map = [ "index" "pager" ]; }
      { action = "sidebar-prev"; key = "\\Cp"; map = [ "index" "pager" ]; }
      { action = "sidebar-next"; key = "\\Cn"; map = [ "index" "pager" ]; }
      { action = "sidebar-open"; key = "\\Co"; map = [ "index" "pager" ]; }
    ];
    macros = [
      {
        action = "!systemctl --user start mbsync &^M";
        key = "<F5>";
        map = [ "index" ];
      }
      {
        action = "<change-folder>${config.accounts.email.accounts.Personal.maildir.absPath}/INBOX<enter>";
        key = "P";
        map = [ "index" ];
      }
      {
        action = "<change-folder>${config.accounts.email.accounts.Work.maildir.absPath}/INBOX<enter>";
        key = "W";
        map = [ "index" ];
      }
      {
        action = "<change-folder>${config.accounts.email.accounts.Gmail.maildir.absPath}/INBOX<enter>";
        key = "A";
        map = [ "index" ];
      }
      {
        action = "<change-folder>?<change-dir><home>^K=<enter><tab>";
        key = "c";
        map = [ "index" ];
      }
      {
        action = "<save-message>?<tab>";
        key = "s";
        map = [ "index" ];
      }
    ];

    sidebar = {
      enable = true;
      width = 40;
      format = "%B%?F? [%F]?%* %?N?%N/?%S";
      shortPath = false;
    };

    settings = {
      alias_file = aliasfile;
      confirmappend = "no";
      edit_headers = "yes";
      fast_reply = "yes";
      folder = "${config.home.homeDirectory}/Mail";
      imap_check_subscribed = "yes";
      include = "yes";
      mail_check = "0";
      mail_check_stats = "yes";
      mailcap_path = "${config.xdg.configHome}/neomutt/mailcap";
      mark_old = "no";
      markers = "no";
      pager_context = "3";
      pager_index_lines = "10";
      pager_stop = "yes";
      sort_aux = "reverse-last-date-received";
      sort_re = "yes";
      tmpdir = "${config.home.homeDirectory}/tmp";
    };

    extraConfig = ''
      source ${aliasfile}
      source ${mailboxfile}

      set allow_ansi
      set display_filter="${mutt-display-filter}/bin/mdf"

      # Use return to open message because I'm not a savage
      unbind index <return>
      bind index <return> display-message

      # Use N to toggle new
      unbind index N
      bind index N toggle-new

      # Status Bar
      set status_chars  = " *%A"
      set status_format = "───[ Folder: %f (%l %s/%S)]───[%r%m messages%?n? (%n new)?%?d? (%d to delete)?%?t? (%t tagged)?%?F? (%F flagged)?]───%>─%?p?( %p postponed)?───"

      lists .*@lists.sr.ht

      # ====== COLORS ======
      color attachment        yellow          black
      color prompt            yellow          black
      color message           white           black
      color error             red             black
      color indicator         black           yellow
      color status            brightwhite     blue
      color tree              magenta         black
      color normal            white           black
      color markers           brightyellow    black
      color search            white           black

      # Index
      color index             brightwhite     black   ~N # unread
      color index             white           black   ~O # read
      color index             brightgreen     black   ~F # flagged
      color index             red             black   ~D # deleted

      # Header
      color hdrdefault        white           black
      color header            brightgreen     black   (^Subject\:)

      # Color Links blue
      color body              brightblue      black "(ftp|http|https)://[^ ]+"
      color body              brightblue      black [-a-z_0-9.]+@[-a-z_0-9.]+

      # Color signature verification
      color body              brightgreen     black "^(gpg: )?Good signature"
      color body              brightgreen     black "^(gpg: )?Encrypted"
      color body              brightred       black "^(gpg: )?Bad signature"
      color body              red             black "^(gpg: )?Problem signature from:.*"
      color body              red             black "^(gpg: )?warning:"
      color body              red             black "^(gpg: ).*failed:"

      # Body
      color quoted            cyan            black
      color signature         cyan            black

      # Sidebar
      color sidebar_highlight white           color8
      color sidebar_new       cyan            black
    '';
  };
}

D modules/email/offlinemsmtp.nix => modules/email/offlinemsmtp.nix +0 -25
@@ 1,25 0,0 @@
{ config, pkgs, ... }: with pkgs; let
  offlinemsmtp = callPackage ../../pkgs/offlinemsmtp.nix {};
in
{
  systemd.user.services.offlinemsmtp = {
    Unit = {
      Description = "offlinemsmtp daemon";
      PartOf = [ "graphical-session.target" ];
    };

    Service = {
      ExecStart = ''
        ${offlinemsmtp}/bin/offlinemsmtp --daemon \
          --send-mail-file ${config.home.homeDirectory}/tmp/offlinemsmtp-sendmail \
          --file ${config.xdg.configHome}/msmtp/config
      '';
      Restart = "always";
      RestartSec = 5;
    };

    Install = {
      WantedBy = [ "graphical-session.target" ];
    };
  };
}

D modules/email/quotes => modules/email/quotes +0 -1345
@@ 1,1345 0,0 @@
Never follow your passion, but always bring it with you.
    -- Mike Rowe
%
It is very much better sometimes to have a panic feeling beforehand, and
then be quite calm when things happen, than to be extremely calm
beforehand and to get into a panic when things happen.
    -- Winston Churchill
%
It is wonderful how well men can keep secrets they have never been told.
    -- Winston Churchill
%
The best evidence of the fairness of any settlement is the fact that it
fully satisfies neither party.
    -- Winston Churchill
%
All the greatest things are simple, and many can be expressed in a
single word: freedom; justice; honour; duty; mercy; hope.
    -- Winston Churchill
%
Never look down on anybody unless you're helping them up.
    -- Jesse Jackson
%
In politics, when you are in doubt what to do, do nothing... when you
are in doubt what to say, say what you really think.
    -- Winston Churchill
%
The best thing for the government to do is nothing, because whatever
they do will likely be insufficient and ineffective.
    -- Me
%
The inherent vice of capitalism is the unequal sharing of blessings. The
inherent virtue of socialism is the equal sharing of miseries.
    -- Winston Churchill
%
One can always consult a man and ask him "would you like your head cut
off tomorrow?" and after he has said "I would rather not," cut it off.
"Consultation" is a vague and elastic term.
    -- Winston Churchill
%
There is only one thing worse than fighting with allies, and that is
fighting without them.
    -- Winston Churchill
%
One always measures friendships by how they show up in bad weather.
    -- Winston Churchill
%
The glory of light cannot exist without its shadows.
    -- Winston Churchill
%
I have derived continued benefit from criticism at all periods in my
life and I do not remember any time when I was ever short of it.
    -- Winston Churchill
%
Better to remain silent and be thought a fool than speak and remove all
doubt.
    -- Abraham Lincoln
%
The highest calling of leadership is to challenge the status quo and
unlock the potential of others.
    -- Carly Fiorina
%
Success is determined not by whether or not you face obstacles, but by
your reaction to them.
    -- Ben Carson (in Gifted Hands)
%
Successful people don't have fewer problems. They have determined that
nothing will stop them from going forward.
    -- Ben Carson (in Gifted Hands)
%
There are two ways to make a deterministic machine behave randomly. One
is to use a pseudo-random number generator. The second way is to install
Windows on it.
    -- Keith Hellman
%
Quite frankly, HFS+ is probably the worst file-system ever.
    -- Linus Torvalds (on Apple's proprietary filesystem)
%
Testing can show the presence of bugs, but not their absence.
    -- Edsger Dijkstra
%
Once you run Windows in a VM, it's obvious that that operating system
just needs a little belts and suspenders, and maybe a diaper, and it's
not that bad to use.
    -- Chris Fisher (in a podcast)
%
I knew Bitcoin had gone mainstream when my dad asked me about it. I knew
Bitcoin was a bubble when my grandpa asked me about it.
    -- Patrick Nichols
%
I am an inherently visual person; it takes a lot of brain power to
process human language.
    -- Sam Sartor
%
Social Liberalism says that we are all different, but we have to be
tolerant of those differences.

Social Conservatism says that we are all equal, so our differences don't
matter.
    -- Me
%
Bubbleware (n.): Software designed for hipsters designing software.
Typically delivered "as a service" since hipsters would not know how to
use it otherwise.
    -- Jack Rosenthal (in an email signature)
%
Machine Learning is quite the buzzword. There's probably hipsters at
Galvanize right now sipping on bubbly drinks learning about it.
    -- Jack Rosenthal
%
You really need to monitor him while he's coding, you can't just let him
go off on his own.
    -- John Cornish (referring to Sam Sartor)
%
"Mutability" and "Immutability" are actually a words in English, but we
use them more than anybody.
    -- Paul Christopher (referring to Software Engineers)
%
Defense is the service industry of the tech industry.
    -- Paul Christopher
%
All models are wrong, some are useful.
    -- Sam Sartor
%
Don't call me a professor. That's an insult to professors everywhere.
    -- Jack Rosenthal (Programming Languages, Spring 2018)
%
A "pacman -Syu" fixed it! G** damn it Arch Linux!
    -- Sam Sartor
%
The concept of a programming language in which the possibility of inline
assembly is an entirely foreign concept hurts my brain.
    -- Sam Sartor (referring to Idris)
%
Where do I put it? Do I put it in the type?
    -- Sam Sartor (referring to Idris)
%
People should have never given set theorists computers.
    -- Derek Foundoulis
%
JavaScript is like marijuana. Some people really like it and talk about
it a lot. Everyone else thinks its a stinky weed that is corrupting our
youth.
    -- Robby Zampino
%
A virtual filesystem? I don't know what you are trying to achieve, but
there's probably a better way.
    -- Jack Rosenthal (referring to Angle Defense 2.0)
%
All good things in Software Engineering can be boiled down to the
concept of being "loosely coupled and highly cohesive".
    -- Chad Woolley
%
When TV shows began putting hashtags in the corner, I thought that they
were IRC rooms.
    -- Robby Zampino
%
Some people call it sarcasm. I call it talking.
    -- Chad Woolley
%
You start lying. Well... you call it marketing.
    -- James Vasile
%
Why is Jack IoT-ing his stove?!? This is what I get for giving him a
microcontroller.
    -- Robby Zampino
%
Well, that's how you do it in 5 minutes, there's nothing there!
    -- A Pivotal Labs client (referring to Pivotal's main standup)
%
It's easy to parallelize your program if you don't care about
performance.
    -- Bo Wu (Advanced High Performance Computing, Fall 2018)
%
They're great!
    -- Paul Christopher (referring to Google's "golden handcuffs")
%
The nice thing about proprietary software is that you don't have to
build it when you install it via the AUR.
    -- Jack Garner
%
First they ignore you, then they laugh at you, then they fight you, then
you win.
    -- Mahatma Gandhi
%
Phys 2 is Phys 1 that you can't see.
    -- Johnny Reiley
%
Virtual Memory is a way that the operating system cheats programmers.
    -- Bo Wu
%
Are the unicorns a group?
    -- Sam Sartor (when we were taking Abstract Algebra)
%
How do you do it in serial? It's easy! You use something called "for
loop".
    -- Bo Wu (referring to summing an array)
%
All government does is investigate itself, all it does is throw money at
things. Like, it doesn't *do* anything!
    -- Dave Rubin
%
[Sandboxing X] is reimplementing Wayland in the worst possible way.
    -- Alexander Larsson (LAS 2018)
%
I came for the memes, stayed for the symbols layer.
    -- Robby Zampino (referring to 3l)
%
PPAs were the worst thing we've had in Linux in the last ten years.
    -- Aleix Pol (LAS 2018)
%
There's C++14 which is what 11 should have been. And there's 17, which
is kinda weird.
    -- Albert Astals Cid (LAS 2018)
%
Design is not just, like, your opinion, man.
    -- The elementary OS HIG
%
Nothing is so permanent as a temporary government program.
    -- Milton Friedman
%
Nothing is so permanent as a temporary name.
    -- Sam Sartor (referring to Visplay)
%
Sam: "You took discrete math?"
Someone: "Yes"
Sam: "Did you use LaTeX?"
Someone: "Not that I recall"
Sam: "I thought that was the whole point of discrete math"
%
It better work for all groups or else we can't prove it.
    -- David Florness
%
It's a nice time to be good at writing code.
    -- Matt Baldin
%
It's in telescopic view of a good language.
    -- Sam Sartor (referring to C++ after he found clangd)
%
ACM and LUG pizza allow me to eat breakfast.
    -- Fisher Darling
%
That's why I did it in bash, you just add an ampersand to the end of the
line.
    -- Sam Warfield (on why he used bash for parallel programming)
%
Anytime you can use defaultdict, you and me kick butt.
    -- Sam Sartor
%
See Sumner, telling people to use Rust has job benefits!
    -- Sam Sartor
%
Many people think that Java is faster than Python... Unfortunately that
is true.
    -- Bo Wu
%
I am deeply ashamed. I have been trying to set the church back 2000
years.
    -- Billy Graham (in response to criticism that he set the church
       back 50 years by visiting the USSR)
%
If you think you are not conceited, it means you are very conceited
indeed.
    -- C.S. Lewis
%
Humility is not thinking less of yourself, it's thinking of yourself
less.
    -- Rick Warren
%
I'm not going to say that it's a Snow Leopard, but it's definitely a
Slush Leopard.
    -- Michael Dominic (Referring to elementary OS, Coder Radio 326)
%
Failure is always an option.
    -- Adam Savage
%
Creation is a set of failures which end with success.
    -- Adam Savage (Homecoming Distinguished Lecture 2018)
%
Facebook and Google and Apple are like a bear in a car, they aren't
going to eventually drive it, but they will do a lot of damage looking
for food.
    -- Adam Savage (Homecoming Distinguished Lecture 2018)
%
I love coming to a place where you can major in blowing s**t up!
    -- Adam Savage (Homecoming Distinguished Lecture 2018)
%
ACM Member: Are there any abstractions on top of the Vulcan API?
Sam Sartor: There are: it's called Unity.
%
You have to know a lot about Windows to be able to convince it not to
boot.
    -- Jack Garner
%
Idris is not a language; it's a rogue type system.
    -- Sam Sartor
%
Anxiety in a man's heart weighs him down, but a good word makes him
glad.
    -- Proverbs 12:25 (ESV)
%
The people who run things competently are often, but not always,
interested, very interested, in finding young people that they can
mentor and move forward. It's actually one of the pleasures of being
competent in something.
    -- Jordan Peterson
%
Competence is the acceptance of vulnerability.
    -- Jordan Peterson
%
It's not procrastination, it's lazy evaluation.
    -- Daichi Jameson
%
Your problem doesn't exist when the universe doesn't exist.
    -- Sam Sartor
%
What does the hat mean?
    -- Matt Clayton (Theory of Computation, Fall 2018)
%
Most people's lives are pretty nicely saturated with tragedy.
    -- Jordan Peterson
%
And now the obligatory vote for the tower defense game...
    -- A student in Software Engineering (referring to Angle Defense)
%
Cost function! Simulated annealing!
    -- Fisher Darling
%
Chemistry is an abstraction on top of physics.
    -- Me
%
Learning about deep neural networks is like Toto pulling back the
curtain to unveil the Wizard of Oz.
    -- Me
%
Whenever we see for loops, we are happy.
    -- Bo Wu (Advanced HPC, Fall 2018)
%
[Some] people call *JavaScript* modern.
    -- Jake Vossen (MHacks 2018)
%
Everything is proceeding as I have foreseen.
    -- Emperor Palpatine (Star Wars Episode VI: Return of the Jedi)
%
If you saw this code, you would kick us out of ACM.
    -- Jake Vossen (MHacks 2018)
%
Sam: It's kinda like coding by instinct instead of reason.
Carl: That's called writing JavaScript.
%
I'm working out my brain, and I'm sitting, which must take *some*
strength.
    -- Kristin Farris (MHacks 2018)
%
Flight attendant: Did you like take a sleeping pill or something?
Ben Perkins: No, we just didn't have any sleep all weekend.
Flight attendant to other flight attendant: he was super out, we had to
        like shake him to wake him up!
    -- Disembarking after flying home from MHacks
%
Let's pair program so that we can double the stupidity.
    -- Sam Sartor (00:45 at MHacks 2018)
%
All they would have had to do to wake me up is say "we're going to do
our project in Node.JS".
    -- Jake Vossen (referring to sleeping on the flight home from MHacks
       2018)
%
LUG just kinda happens because we are all in ALAMODE at 6:00 PM on
Thursday.
    -- Jordan Newport
%
We are built to explore; and when you're not exploring, what's left?
    -- Glenn Beck
%
I'm an optimistic catastrophist.
    -- Glenn Beck
%
You smoke a lot of LISP. Then, you have become enlightened.
    -- Jack Rosenthal
%
Actually, do you know what's inefficient? Not shipping.
    -- Michael Dominic (Coder Radio 330)
%
You realize that *everything* can be done with function composition?...
It's called *lambda calculus*.
    -- Joseph McKinsey
%
Me: Stupid, I am.
David: About time you admit that...
%
A cyclic group is a baguette.
    -- Sam Sartor
%
It probably doesn't exist for LISP.
    -- Jo (referring to the Balmer Peak)
%
It's a heuristic, which means that they can just make something up and
say it works.
    -- Alan Champion
%
Is it OK if I don't handle two-way roundabouts? Otherwise it would be
called a car super-collider.
    -- Sam Sartor
%
Those who treasure Freedom would never exchange even the mayhem of a
major election for the peace that would come by the rule of the
intellectual elites.
    -- Albert Mohler (The Briefing 2018/11/07)
%
When you are spouting policy, you are no longer a child, you are an
adult.
    -- Kyle Kashuv (on The Rubin Report)
%
Oh, this isn't tedious; it's just the most pointless thing ever.
    -- Jordan Newport
%
His task allocator is DFS.
    -- Matt Baldin (referring to Sam Sartor)
%
If everything is broken, nothing is broken.
    -- Billy Brickner
%
We aren't sweaty all the time, that would imply that we do physical
activity.
    -- Liam Warfield
%
Now it looks like it was made for humans, not a bunch of sweaty nerds.
    -- Ross Starritt (after cleaning ALAMODE)
%
We've thought about you!
    -- Nicole Greczyn (Gogo Tech Talk 2018)
%
Don't ask, just see if it runs.
    -- Me (at 00:23 at Facebook Hackathon Finals)
%
Just make it a View and we can cast it.
    -- Fisher Darling (doing Android dev in Kotlin)
%
Today I learned that a big bag of nuts next to a box of electronics
looks like a bomb.
    -- Sam Sartor (after getting through TSA security)
%
Don't trampolines jump for you?
    -- Fisher Darling
%
It's fundamentally a vulnerability with if statements.
    -- Sam Sartor (referring to speculative execution attacks)
%
Remember when we thought this project would be boring?
    -- Jack Garner (after winning Facebook Global Hackathon Finals)
%
When people say "Oh, you're a computer scientist, does that mean you can
help me with my computer problems?" I just say: "No, I just have
computer problems professionally."
    -- Sam Sartor
%
I would help, if I knew how to type.
    -- David Florness (while he was learning 3l)
%
The political discussion is the Left and the Right constantly eying each
other to make sure that the hierarchical structures maintain their good
health.
    -- Jordan Peterson (The Rubin Report, Nov. 30, 2018)
%
What makes human beings "in the image of God" is our creative capacity;
and the creative capacity is the ability to transform, through an act of
will, something that was not into something that is.
    -- Ben Shapiro (The Rubin Report, Nov. 30, 2018)
%
Rules are like pants: they are fundamentally constraining, but they
allow you to walk through brambles.
    -- Ben Shapiro (The Rubin Report, Nov. 30, 2018)
%
If human beings can find meaning living in a concentration camp where
people are getting gassed to death every day, then you certainly should
be able to find meaning in the freeest, most rich, most prosperous human
society in the history of humanity.
    -- Ben Shapiro (The Rubin Report, Nov. 30, 2018)
%
Don't sleep with your students; but if you are already sleeping with one
of your students, don't grade for them.
    -- CPW (on what not to do as TAs)
%
The only Republicans that the Left thinks are wonderful are ones that
are out of office or dead.
    -- Ben Shapiro (The Ben Shapiro Show, Ep. 671)
%
Sam: That's just impolite, using all the cores [on Isengard]!
Daichi: Isn't that what we were going to do?
%
A SQL query goes into a bar, walks up to two tables and asks "can I join
you"
    -- Jesus Nuñez (Mines LUG Matrix Chat)
%
Guys guys we should all program in REXX! It's like if Python and FORTRAN
had a baby that ended up with cmd.exe's extra chromosomes. And you can
run it on the JVM if you want.
    -- Jo (Mines LUG Matrix Chat)
%
A story, of course, does not have to be true to be interesting; but it
does have to be true, to be true.
    -- Albert Mohler (The Briefing 2019/01/10)
%
[Every] divorce is the death of a small civilization.
    -- Pat Conroy
%
Students say that my class is one of the easiest ones in the
department... They take my class to improve their GPA.
    -- Dr. Dinesh Mehta (C-MAPP Party 2019)
%
Once you accept that every human being has value, you can no longer be
quote, unquote "pro choice", using their euphemism; you must be pro
life.
    -- Ben Shapiro (March for Life 2019)
%
If every human being has value, then that means that every human being
deserves to be protected, no matter how small, no matter how early, all
of them deserve to be protected because they are innately valuable.
    -- Ben Shapiro (March for Life 2019)
%
What God demands of you is decency and justice. What God demands of us
is to choose life.
    -- Ben Shapiro (March for Life 2019)
%
Righteousness doesn't have to be popular, it just has to be righteous.
    -- Ben Shapiro (March for Life 2019)
%
This is the funny thing about talking to engineers, they study tree
structures in school, like, you know that tree structures grow very
efficiently. And yet when we come to design organizations we tend to
shun them in favour of some complex three dimensional chess.
    -- Jonathan Nightingale (SE-Radio Ep. 352)
%
They were spouting all these things, and like throwing "therefore"
around everywhere.
    -- Dom (referring to the Apostles after Pentecost)
%
Me: Does anyone know what a Kardashian is?
Liam: Yes, it's a special kind of wave transformation.
%
Does an encrypted message still have meaning if you lose the key?
    -- Me
%
I treat it as write-only memory.
    -- Sam Sartor (referring to his INBOX)
%
All of this was a mistake, we should go live in nuclear bunkers and use
TempleOS... Except MBR still exists.
    -- Jo (LUG Officers group chat)
%
Noah: Oh! I just got that XKCD about loosing the presents under the
    Christmas tree!
Jack: That's the entire point of a CS degree.
%
Hopefully you can't inject a virus into a TTF file.
    -- Jake Vossen (HackCU, 2019)
%
This sort of idolatry... allows people to come here and hide from who
they are, behind the belief of who they could be. But they won't
actually take any steps to cross the distance between, because that's
where your actual life is, that's where the truth about you is going to
be revealed.
    -- Jeremy Boreing
%
When a man stops believing in God, he doesn't believe in nothing - he
believes in anything.
    -- G.K. Chesterton
%
Never underestimate the bandwidth of a station wagon full of tapes
hurtling down the highway.
    -- Andrew S. Tanenbaum
%
A politician needs the ability to foretell what is going to happen
tomorrow, next week, next month, and next year. And to have the ability
afterwards to explain why it didn't happen.
    -- Winston Churchill
%
History will be kind to me for I intend to write it.
    -- Winston Churchill
%
You only know what I want you to know; you only see what I want you to
see.
    -- Art3mis (Ready Player One)
%
I ask Jack, "What is a monad transformer?" and he's like "First, imagine
the entire world is a data type."
    -- Joseph McKinsey (on talking to Jack Garner about Haskell)
%
Ok, you have to admit that gender is at least quantized.
    -- Me
%
Jack Garner: We should make Assembly Macros. That would be...
Me: A programming language!?!
%
When 900 years old, you reach; look as good, you will not.
    -- Yoda (Star Wars Episode VI: Return of the Jedi)
%
The Dark Side of the Force is a pathway to many abilities *some*
consider to be... unnatural.
    -- Chancellor Palpatine (Star Wars Episode III: Revenge of the Sith)
%
I find your lack of faith disturbing.
    -- Darth Vader (Star Wars Episode IV: A New Hope)
%
Be careful not to *choke* on your aspirations, Director.
    -- Darth Vader (Rouge One)
%
Never tell me the odds.
    -- Han Solo (Star Wars Episode V: The Empire Strikes Back)
%
It's a trap!
    -- Admiral Ackbar (Star Wars Episode VI: Return of the Jedi)
%
Fear is the path to the dark side. Fear leads to anger, anger leads to
hate, hate leads to suffering. I sense much fear in you.
    -- Yoda (Star Wars Episode I: The Phantom Menace)
%
Ahh, hard to see, the Dark Side is.
    -- Yoda (Star Wars Episode I: The Phantom Menace)
%
I've got a bad feeling about this.
%
Always in motion is the future.
    -- Yoda (Star Wars Episode V: The Empire Strikes Back)
%
I'm beginning to understand why people thought Idris was a good idea,
and that scares me.
    -- Joseph McKinsey
%
This is 2019, and I had a merge conflict with a floppy disk driver.
    -- Jack Rosenthal
%
"Frontend" is everything from the Linux kernel up.
    -- Jack Rosenthal (about the Chrome OS team)
%
This isn't how it's supposed to be!
    -- Viceroy Nute Gunray (Star Wars Episode II: Attack of the Clones)
%
AI is basically "Problems for which to use Lisp, the class".
    -- Jordan Newport
%
Being ignorant is not so much a shame, as being unwilling to learn.
    -- Benjamin Franklin
%
The only thing worse than a Christian who is judgemental, is a Christian
who does not really make Biblical judgements.
    -- Albert Mohler (The Briefing, 2019/05/02)
%
Democracy is the worst form of Government except for all those
other forms that have been tried from time to time.
    -- Winston Churchill
%
If one big government is bad, imagine how bad two big governments would
be... that's the European Union.
    -- Nigel Farage
%
A leader is not about your position or your title, it's about whether
you are solving a problem, and changing the order of things for the
better.
    -- Carly Fiorina (on the Ben Shapiro Sunday Special Ep. 51)
%
Dreams shed light on the dim places where reason itself has yet to
voyage.
    -- Jordan Peterson (12 Rules for Life, An Antidote to Chaos)
%
Do. Or do not. There is no try.
    -- Yoda (Star Wars Episode V: The Empire Strikes Back)
%
This is the sort of nonsense up with which I will not put.
    -- Winston Churchill
%
Who controls the past controls the future: who controls the present
controls the past.
    -- George Orwell (Nineteen Eighty-Four)
%
Ruby is what happens when a Java developer tries to make Python.
    -- Liam Warfield
%
The emotion of a government power point is the environment of this
company.
    -- Jo
%
When people say "Oh, you're a computer scientist, does that mean you are
in IT?" I say: "No, IT *solves* computer problems. I *make* computer
problems."
    -- Sam Sartor
%
Working has done something terrible to me... I've become pragmatic!
    -- Sam Sartor
%
Success is not final, failure is not fatal. It is the courage to
continue that counts.
    -- Winston Churchill
%
We often think Jesus Christ came into this world to make bad people
good. That's wrong. It's not got anything to do with making bad people
good, it's coming into the world to make dead people live.
    -- Ravi Zacharias on the Rubin Report
%
WebSockets is just TCP sockets in a for loop.
    -- Robby Zampino
%
Violence... is no mystery. It's peace that's the mystery.
    -- Jordan Peterson (12 Rules for Life, Rule 5)
%
They went to a transatlantic cable [and] when that was laid down,
suddenly we had more bandwidth than we ever knew what to do with. Until
about two months later when fully utilized it all.
    -- Dr. Julian Onions (First Contact (Internet at 50) -
       Computerphile)
%
Me: Oh, I give up. This [LaTeX document formatting] looks good enough.
Keith Hellman: If you don't care about formatting, why are you using
    LaTeX?
%
If you want to change a culture, change the culture, not the rules.
    -- Me
%
There are going to be a lot of good people in Hell, because God doesn't
allow good people to go to heaven, He allows *redeemed* people to go to
heaven.
    -- Me
%
If your faith does not motivate you towards works, you should not
question your works, you should question your faith.
    -- Me
%
Hot Take: Admiral Holdo is worse than Jar Jar.
Proof: Holdo is incompetent, but thinks she's competent.
       Jar Jar is incompetent, but doesn't think.
    -- Me
%
I think I now understand the appeal of a language like Go. There is one
and only one canonical way to solve this problem. Rust feels like you
are car shopping. There are 50 different libraries with infinite
combinations and the differences between them are all kinda pedantic.
    -- Sam Sartor (talking to Robby about Rust libraries)
%
We had a lot of scary LaTeX so it must be true.
    -- Matthew Hodgson
%
Or you could have no types, but then you would have LISP, and nobody
wants that.
    -- Joseph McKinsey
%
You are very good at making people vote again; but what we've proved is
that the British are too big to bully, thank goodness.
    -- Nigel Farage (in his final speech in the EU Parliament)
%
We love Europe, we just hate the European Union.
    -- Nigel Farage (in his final speech in the EU Parliament)
%
There is an historic battle going on across the West... it is Globalism
against Populism. And you may loathe Populism, but I tell you a funny
thing: it's becoming very popular!
    -- Nigel Farage (in his final speech in the EU Parliament)
%
If you dedicate yourself to a cause, no matter what it is... there's a
big price to pay.
    -- Nigel Farage
%
This generation needs to know that the Gospel is good more than they
need to know that the Gospel is true.
    -- Lawrence Koo (paraphrase)
%
Whenever you see a stupid feature in an app and wonder why it's there,
just remember that that feature was the highlight of somebody's
performance review.
    -- Me
%
Somebody smoked too much IntelliJ.
    -- Jared Lincenberg (referring to the creation of Kotlin)
%
I thought I fixed something but then I realized I was in the regular
shell.
    -- Jared Lincenberg (while working on the OS shell project)
%
Me: This just works!
Robby: I know, that's why I'm suspicious.
%
When the Bible says that stars fall to earth, it doesn't mean that
literally because otherwise the next line would have to say "and then
earth was gone".
    -- David Brown (Anchor, 2020-02-21)
%
If science and the Bible seem to disagree, just wait 100 years.
    -- David Brown (Anchor, 2020-02-21)
%
Most of HPC research is like "when the full moon is directly over head
and the cache buffers are perfectly aligned, you can get 3% performance
improvement 10% of the time".
    -- Liam Warfield
%
Someone turned on the microwave, and my computer disconnected from the
internet.
    -- Jack Garner
%
*SQLite backend*
Because comments are not Big Data.
    -- Isso homepage (https://posativ.org/isso/)
%
...the vast majority of people who are into maths, they don't find it
easy. They are just the people who enjoy how difficult it is.
    -- Matt Parker (Talks at Google)
%
If large sums of money entice you to sell out on something that you care
about, maybe you don't actually care about what you are building as much
as you think.
    -- Me
%
You aren't working from home; you are living at work.
    -- A coworker during a Zoom Bar Cart
%
No matter how it is being used, what is being built is the architecture
of oppression.
    -- Edward Snowden (referring to Coronavirus emergency powers)
%
The plural of anecdote is data.
    -- Ray Wolfinger
%
All languages fall into two categories: "just-in-time complexity
languages" and "ahead-of-time complexity languages".
    -- Sam Sartor
%
It seems pretty straightforward, let's see what problems we can invent
on the way!
    -- Adam Savage (One Day Builds: Little Thwacker Hammer!)
%
That was very Alpha-Zero-like. Just YOLO, and push the A-pawn.
    -- IM Eric Rosen (analysing a game on a Twitch stream)
%
It is byte inefficient, but brain cell efficient.
    -- Me (referring to using JSON over HTTP for IPC)
%
Schalke, in terms of goals, is socially distanced from Dortmund.
    -- Match Commentator (first game back after Coronavirus Lockdown
       with Dortmund up 4:0)
%
I call it *fattening* the curve.
    -- Match Commentator (Dortmund vs. Schalke, referring to people
       gaining weight during the Coronavirus Lockdown)
%
Trying is the first step to failure.
    -- GM Ben Finegold
%
Emotions give us a glimpse into the incomprehensible Will of God.
    -- Me
%
I don't even know what to calculate, every move just looks bad.
    -- IM Eric Rosen (during a Twitch stream)
%
If something is too easy, it generally doesn't interest me too much.
    -- GM Hikaru Nakamura (during a Twitch stream)
%
...then you get confused, and when you are confused, bad stuff happens.
    -- GM Hikaru Nakamura (analysing Pogchamps games on YouTube)
%
So this is how democracy dies, with thunderous applause.
    -- Padmé Amidala (Star Wars Episode III: Revenge of the Sith)
%
Hypocrisy only exists if religion exists. So when you see a hypocrite,
look for their religion.
    -- Me
%
You know what, this is Sparta, I'm going to run with my king and pick up
all your pawns.
    -- GM Hikaru Nakamura (analysing Pogchamps 2020: Forsen vs. Nym)
%
We have a distributed IQ system.
    -- Jo (on Mumble, referring to Microsoft)
%
Feminism is cancer, and we shouldn't be waiting until stage 3 to start
chemo.
    -- John Doyle (4 Liberal Things Conservatives Should STOP Saying)
%
You know, as engineers, it's easy to say "oh damn all the financial
market people, they make huge problems and contribute nothing to
society", but we invented Node.JS, so I don't think we should really
talk on this subject.
    -- Sam Sartor
%
Programming isn't hard. Programming well is very hard.
    -- Functional Programming in OCaml (2019 Edition, Cornell)
%
A language that doesn't affect the way you think about programming is
not worth knowing.
    -- Alan J. Perlis (1922-1990), first recipient of the Turing Award
%
By studying functional programming, you get a taste of what might be
coming down the pipe next.
    -- Functional Programming in OCaml (2019 Edition, Cornell)
%
Education is what remains after one has forgotten everything one learned
in school.
    -- Albert Einstein
%
We did enough lockdown to really damage the economy; but we didn't do
enough lockdown to damage the Big-'Rona.
    -- Chris Fisher (Unfilter 316: Catastrophic Cascading Consequences)
%
...these neocons and the left are so distracted by Russia that the
United States and the rest of the West are going to get rolled by China.
We'll be staring at Russia, meanwhile, China will just be buying us up
bit by bit, getting more and more power and more control while we are
sitting here yelling Russia, Russia, Russia, China will walk in the back
door.
    -- Chris Fisher (Unfilter 317: Gaslighting America)
%
I think [people who are proposing terminology changes] are injecting
racism where there previously was none.
    -- Bryan Lunduke (YouTube: Linux Kernel blacklists "blacklist")
%
Ultimately, this is just a high stakes hash collision.
    -- Ross Starritt (referring to Lady Antebellum changing their name
       to Lady A)
%
That doesn't work when the files are classified.
    -- Jo (referring to using online PDF concatenation websites)
%
This is what the desperate computer science students do when they really
want to get in to Google.
    -- Ben Awad (referring to LeetCode)
%
[Matrix] is usable for anything if you squint hard enough.
    -- Michael (t3chguy) (#matrix-dev:matrix.org)
%
But we have survived. And on that terrible disappointment... for Top
Gear... it's time to say "goodbye".
    -- Jeremy Clarkson (The Grand Tour: Seamen)
%
What the crisis has revealed to us is that there's been a huge
percentage of the populous who never really understood what freedom
meant; they believed that their freedoms and rights weren't inalienable.
    -- Computing Forever (The Rising Resistance to the New Abnormal)
%
I really appreciate the effort LUG makes to cultivate an environment
where it is ok to have no clue what is going on and ask lots of
questions. When I walked in to the penguin-itis talk with a windows
laptop, Jack was only too happy to explain WTF an "XOrg" is, how to
bash, or (later when I was running Arch) why one should bother to
install a bootloader.
    -- Sam Sartor (hekn dank memes)
%
People need to be able to self-direct, to be creative and clever. But
being spoon feed for years and years by older clever people is usually
how younger clever people get to be so clever.
    -- Sam Sartor (hekn dank memes)
%
I never thought that gambling was sinful until today...
    -- Joseph McKinsey (during a discussion about derivatives markets)
%
QED and Checkmate are synonyms: change my mind.
    -- Jack Garner (hekn dank memes)
%
Salaries rent you an apartment. RSUs buy you a house.
    -- Robby Zampino
%
I felt a great disturbance in Online Learning... as if millions of
servers suddenly cried out in terror, and were suddenly silenced. I fear
the semester has begun.
    -- Fisher Darling (referring to online service outages at the start
       of Fall 2020)
%
The problem with VR is that your eyes are 2D.
    -- Joseph McKinsey (referring to VR graph visualization)
%
In a modern environment, memorizing the minutia of an API should be as
relevant as memorizing times tables.
    -- Bret Victor (Learnable Programming on worrydream.com)
%
Working in the head doesn't scale. The head is a hardware platform that
hasn't been updated in millions of years.
    -- Bret Victor (Learnable Programming on worrydream.com)
%
A language that discourages decomposition is a language that cripples a
programmer's most valuable way of thinking.
    -- Bret Victor (Learnable Programming on worrydream.com)
%
A language must be parsed by people, not just compilers.
    -- Bret Victor (Learnable Programming on worrydream.com)
%
Does anyone here know Rust?
    -- Fisher Darling (entering ALAMODE for the first time)
%
I like machine learning, I just don't like people who like machine
learning.
    -- Jo
%
Everything is a chat app, or a DAG, or both.
    -- Me
%
Everything is information theory.
    -- Me
%
After the first exam all of that motivation becomes a fine powder that
the professors snort off the table.
    -- Jesus Nuñez (referring to freshmen motivation in Physics I)
%
khal should run on all major operating systems[1]
[1]: except for Microsoft Windows
    -- The khal README
%
Random person from Mines at another table at Winter Park: It isn't even
    a coding class!
Jonathon Robel: 101
%
Recursion is an exercise in delegation.
    -- Me
%
...the reality is the rest of us are sitting here disgusted by the
entire thing.
    -- Chris Fisher (Unfilter Presidential Debate Live Stream, 2020)
%
I'm gonna sit here in the studio and think about what the hell I just
watched.
    -- Chris Fisher (Unfilter Presidential Debate Live Stream, 2020)
%
[Idris is] a language whose defining characteristic is being based
around features almost no mainstream language has.
    -- Jack Garner (Mines LUG)
%
Jesus is not a Republican. He is a theocratic fascist.
    -- Adam Hebener
%
Who would Jesus vote for? Let me tell you, Jesus is not a Capitalist.
Jesus is not a Republican. He is a theocratic fascist, and he us coming
back.
    -- Adam Hebener
%
It looks a little bit like plagiarism.
    -- Mike Pence (2020 VP debate, referring to the Biden COVID-19 plan)
%
Lost the trade war with China? Joe Biden never fought it!
    -- Mike Pence (2020 VP debate)
%
See, what Pence is doing tonight is what the Trump administration would
look like without Twitter. And it is GOOD. VERY GOOD.
    -- Ben Shapiro (referring to the 2020 VP debate)
%
I'm pro-life, and I'm unapologetic about it.
    -- Mike Pence (2020 VP debate)
%
Are you and Joe Biden going to pack the court?
    -- Mike Pence (2020 VP debate)
%
I demand an SNL skit about what it would be like if people in everyday
life answered questions like politicians answer debate questions.
    -- Maggie Koerth (FiveThirtyEight live blog for 2020 VP debate)
%
Jesus' economy doesn't use money, it uses blood.
    -- Adam Hebener
%
When I heard that you guys were running out of [i3wm] workspaces, I was
like "no wonder you are running out of RAM all the time".
    -- Jonathon Robel
%
I would rather be right and depressed than wrong and disappointed.
    -- Me
%
Poetry's unnat'ral; no man ever talked poetry 'cept a beadle on boxin'
day, or Warren's blackin', or Rowland's oil, or some o' them low
fellows; never let yourself down to talk poetry, my boy.
    -- The Pickwick Papers
%
If you *assume* that God is absent, you will not *perceive* God's
presence.
    -- George Towers
%
By listening, one will learn truths. By hearing, one will only learn
half-truths.
    -- A Fortune Cookie
%
Every blunder is a sacrifice if you have the right perspective on life.
    -- IM Eric Rosen
%
File sync is not a backup strategy! It's an catastrophe distribution
system.
    -- Me
%
Do you think you are going to be booting off of SSHFS?
    -- Jack Rosenthal
%
The thing about computer simulations is that one of the things that is
most powerful and useful to simulate is time itself.
    -- Me
%
Quinnipiac is having a bad night.
    -- Pundit on Fox News (Election Night 2020)
%
I must study politics and war, that our sons may have liberty to study
... mathematics and philosophy, geography, natural history and naval
architecture, navigation, commerce and agriculture in order to give
their children a right to study painting, poetry, music, architecture,
statuary, tapestry and porcelain.
    -- John Adams
%
You can use 3l on pretty much every computer you can think of, except
for a lot of the ones that are in my basement.
    -- Jack Rosenthal (On Keyboards and Things, Fall 2020)
%
Robby: Sam, are you drunk?
Sam: No, I'm just incompetent.
    -- WAN Party Fall 2020
%
The current COVID-19 hysteria is a natural result of decades of
helicopter parenting.
    -- Me
%
Sometimes you get the elevator, sometimes you get the shaft.
    -- joshuawise (Advent of Code 2020: day 3)
%
This is not algorithmically interesting; computers are fast.
    -- joshuawise (Advent of Code 2020: day 15)
%
I save that time by not hitting RETURN.
    -- McPqndq (referring to how he's so fast at Advent of Code 2020)
%
The competent programmer is fully aware of the strictly limited size of
his own skull; therefore he approaches the programming task in full
humility, and among other things he avoids clever tricks like the
plague.
    -- Edsger Dijkstra, 1972
%
Part of democracy is being able to say no to what everybody else is
doing, even when it's not necessarily even for the [common] good.
     -- Chris Fisher (Unfilter 339)
%
We should be super-spreaders of the Gospel.
    -- Dave Anderson (6 December 2020, Mark 16)
%
Geophys is outdoor CS funded by the petroleum industry.
    -- jjaro (hekn dank memes)
%
When it comes to sex, our youth are being discipled by someone. It's up
to the church to have a more compelling voice in their lives. Silence is
not an option.
    -- Preston Sprinkle
%
If you don't level with the public and make clear what the actual
problems are, and lay out *several* different kinds of scenarios, ones
that are perhaps not the worst case, but more likely as to where you are
going to end up, then, the public can look at that data, reflect on that
data, and perhaps change their behaviour accordingly. But I think when
you set out one scenario, especially one that is clearly designed to
frighten people, they might not believe it in [the] future.
    -- Kate Andrews (This Week in 60 Minutes #6 on SpectatorTV,
       referring to COVID-19 reporting)
%
If we're now telling people to double mask up, we are implicitly saying
that the single mask was not effective enough. Which is *also* saying:
if you questioned the effectiveness of masks, you were correct!
    -- Chris Fisher (Unfilter 346: Spotlight on Extremism)
%
If we're now telling people to double mask up, we are implicitly saying
that the single mask was not effective enough... [And] we were told the
reason why Corona is spreading is because *you* didn't wear a mask
enough and you... *you're* part of the problem if you ever *dared* to
*question* the all holy face-diaper, and here we are, now we are told:
"not good enough". And it's not like Corona just got smaller.
    -- Chris Fisher (Unfilter 346: Spotlight on Extremism)
%
If we're now telling people to double mask up, we are implicitly saying
that the single mask was not effective enough. Which is *also* saying:
if you questioned the effectiveness of masks, you were correct! [That]
doesn't mean you shouldn't wear a mask, but it means if you went "hmm,
is this thin piece of cloth really doing me any good?", you were correct
to question that. And every time we were told the reason why Corona is
spreading is because *you* didn't wear a mask enough (even though here
in Washington and paces like California, everyone has a mask on) and
you... *you're* part of the problem if you ever *dared* to *question*
the all holy face-diaper, and here we are, now we are told: "not good
enough". And it's not like Corona just got smaller.
    -- Chris Fisher (Unfilter 346: Spotlight on Extremism)
%
Anything that is dogma should be questioned. Questioning is not
rejecting.
    -- Chris Fisher (Unfilter 346: Spotlight on Extremism)
%
ALAMODE is not a place, it's a people.
    -- Oden (probably)
%
[It's] like a peer-to-peer hedgefund.
    -- Chris Fisher (Unfilter 346, referring to GameStop)
%
I'm not an anti-mask person, but man am I anti-bullsh*t
    -- Chris Fisher (Unfilter 347)
%
There's not a demon behind all of your spiritual problems. You are quite
efficient at screwing yourself up. We all are, and Scripture is very
clear about that.
    -- Dr. Michael S. Heiser
%
Could a sword [decapitate somebody]? Yeah, you could do it if you were
fighting an army of naked people.
    -- Mike Loades (Military Historian Breaks Down Medieval Weapons in
       Video Games | WIRED)
%
[Unit testing is] no silver bullet: your software is not going to crash
and burn if you don’t write your tests first, and the presence of tests
alone does not mean you won't have maintainability issues.
    -- Roberta Arcoverde and Ryan Donovan (Best practices can slow your
    application down. The StackOverflow Blog)
%
If the Lord delays, we will also be judged by successive Generations.
Now, you have people on the left who say, "that means you have to get on
the right side of history", but of course that is where Christians look
at it and say, "no, we gotta get on the right side of scripture!"
    -- Albert Mohler (The Briefing 2021/03/04)
%
What has happened is the mask has become a symbol of compliance... When
you put it on, you are complying; and when you refuse to wear it, you
refuse to comply... That's what the mask has become in the United
States. Instead of making it something effective that could improve
society for years to come, we screwed the pooch at the beginning. We
made it a toxic conversation, we couldn't be bothered to answer people's
questions. Instead we labelled them as Trump supporters to shut them up,
and then we just told everybody to put two more masks on. What a
failure! What an unbelievable failure! And the Biden Administration is
doing nothing to fix this.
    -- Chris Fisher (Unfilter 352: America is Back)
%
I don't put projects on the back burner. I put them under the
floorboards, where the ever-louder beating of their hearts drives me
slowly to madness.
    -- @celestelabedz on Twitter
%
I don't find taxes much different than the most tedious coding exercise
ever.
    - Carl Johnson (#fun-finance, TTD Slack)
%
Censoring and purging political viewpoints will not make the ideas
suddenly disappear. It will only make these viewpoints go underground,
create resentments, and make wounds fester.
    -- Patrice Onwuka
%
Our difficulties and dangers will not be removed by closing our eyes to
them.
    -- Winston Churchill (The Sinews of Peace [The "Iron Curtain"
    Speech])
%
If your language stabilizes features based on theoretical discussion,
without seeing how they play out in the ecosystem, you get C++.
    -- Sam Sartor (hekn dank memes)
%
NixOS is an operating system for people who like to read code.
    -- Me
%
We hope we do well, but we want to be sure we don't do terribly.
    -- Warren Buffet (2021 Berkshire Hathaway Annual Shareholders
    Meeting)
%
Trillions don't mean anything to anybody, and $1400 does mean something
to 'em.
    -- Warren Buffet (2021 Berkshire Hathaway Annual Shareholders
    Meeting)
%
I was that obnoxious kid not very long ago, now I'm just obnoxious.
    -- grahamc (#nix:nixos.org, 2021-05-19 20:03)
%
Me teaching ACA: LRU isn't that hard to implement, why are these
    students having such a hard time?
Me writing quotesfilebot: just use random eviction because LRU is too
    hard.
%
Checklist for staying at a job:
1. How do you feel coming to work in the morning?
2. Do you like the people you work with? 
3. Are you compensated competitively?
4. Are you proud of your contributions?
5. Do you learn new things?
6. Can the company be successful? 
    -- samsartor (Alamode r/politics)
%
I will no longer value contributors by the code they crank out. The code
doesn't write itself, and the person writing the code needs even more
maintenance than the "open source" itself.
    -- jackpot51 (after the death of jD91mZM2)
       https://www.redox-os.org/news/open-source-mental-health/
%
We can't be concerned about everything all the time, or we would fail to
be operational.
    -- Albert Mohler (The Briefing 2021/06/28, on the Surfside Building
    Collapse in Florida)
%
IM Eric Rosen: this is really bad.
WGM Nemo Zhou: It's only as bad as you let your mind think it is...
               yeah, it's awful.
IM Eric Rosen: yeah, it's really bad.
    -- "OH NO MY QUEEN in real life" on Eric Rosen's YouTube channel
%
It's never a blunder; it's a dynamic sacrifice.
    -- WGM Nemo "Nemsko" Zhou
%
There's a million things that can go wrong, and none of them are "I
can't write a 'for' loop fast enough".
    -- Michael Dominic (Referring to GitHub Copilot, Coder Radio 422)
%
The fun thing about block based programming languages is your code looks
like actual spaghetti.
    -- Jack Garner (hekn denk memes)
%
Me: I can't ping IPv6 addresses from my VPS. Where should I start
    debugging?
Jo: I'm suddenly having this problem on my laptop, you must have cursed
    me
Me: Yes, that is one side effect of using IPv6. It's as infectious as
    COVID. Don't play around with IPs with colons in them. They may
    cause a pandemic.
Jo: Better wear your subnet mask
%
Whenever Joseph says anything, I understand like 90% of the words, but
like 20% of the terms.
    -- Me (referring to Joseph McKinsey)
%
The people who read the man page for getline did the project in a single
while loop. The people who didn't read the man page for getline are
still doing the project.
    -- Jesus Nuñez
%
Compilation is the first step to a runtime error.
    -- Me
%
I think it's safest to just go backwards in time.
    -- Tulir Asokan, 2021
%
Slow does not mean controlled, slow is just slow!
    -- Master Dante James
%
I don't really find incredible, anymore, the stuff that people say. What
I find incredible is that people believe it.
    -- Dr. Michael Heiser
%

We all want software to solve real world problems, right? So you have to
be in the real world. The more time you spend at the computer, the more
you're gonna be creating software to solve computer problems.
    -- Ryan Donovan (StackOverflow Podcast, ep. 388)
%
euclid:    what's the Rule of Zero?
samsartor: You should strive to write exactly 0 C++.
    -- hekn dank memes (2021-12-03)
%
Thinking is bad. Thinking is very slow, and you might make a mistake.
    -- Jonathan Paulson (Advent of Code 2021 - Day 7)
%

D modules/fzf.nix => modules/fzf.nix +0 -10
@@ 1,10 0,0 @@
{ config, lib, pkgs, ... }: with lib;
{
  programs.fzf = {
    enable = true;
    changeDirWidgetOptions = [ "--preview 'tree -C {} | head -200'" ];
    defaultCommand = "fd --type f --hidden --follow";
    defaultOptions = [ "--border" "--height 40%" ];
    fileWidgetOptions = [ "--preview 'bat --style=numbers --color=always --line-range :500 {}'" ];
  };
}

D modules/git.nix => modules/git.nix +0 -86
@@ 1,86 0,0 @@
{ lib, pkgs, ... }:
let
  offlinemsmtp = pkgs.callPackage ../pkgs/offlinemsmtp.nix { };
in
{
  home.packages = with pkgs.gitAndTools; [ gh hub lab ];

  programs.git = {
    enable = true;
    package = pkgs.gitAndTools.gitFull;
    lfs.enable = true;

    userEmail = "me@sumnerevans.com";
    userName = "Sumner Evans";

    attributes = [ "*.pdf diff=pdf" ];
    delta.enable = true;

    signing = {
      key = "8904527AB50022FD";
      signByDefault = true;
    };

    extraConfig = {
      core.editor = "${pkgs.neovim}/bin/nvim";
      diff.colorMoved = "default";
      init.defaultBranch = "master";
      pull.rebase = false;
      tag.gpgsign = true;

      sendemail = {
        annotate = "yes";
        smtpserver = "${offlinemsmtp}/bin/offlinemsmtp";
        smtpserveroption = [ "-a" "Personal" "--" ];
      };
    };

    ignores = [
      "*.orig"
      "*_BACKUP_*"
      "*_BASE_*"
      "*_LOCAL_*"
      "*_REMOTE_*"
      ".ccls-cache"
      ".mypy_cache"
      ".rooter_root"
      ".ropeproject"
      ".stylelintrc"
      ".venv"
      "__pycache__"
      ".direnv"
    ];
  };

  home.file.".python-gitlab.cfg".text = ''
    [global]
    default = gitlab
    ssl_verify = true
    timeout = 5

    [gitlab]
    url = https://gitlab.com
    private_token = ${lib.removeSuffix "\n" (builtins.readFile ../secrets/gitlab-api-key)}
  '';

  programs.zsh.shellAliases = {
    ga = "git add";
    gaa = "git add -A";
    gap = "git add -p";
    gc = "git commit";
    gca = "gc -a";
    gcaa = "gca --amend";
    gcan = "gc --amend --no-edit";
    gcaan = "gcaa --no-edit";
    gch = "git checkout";
    gd = "git diff";
    gdc = "git diff --cached";
    gfetch = "git fetch";
    gl = "git log --pretty=format:'%C(auto)%h %ad %C(green)%s%Creset %C(auto)%d [%an (%G? %GK)]' --graph --date=format:'%Y-%m-%d %H:%M' --all";
    gpull = "git pull";
    gpush = "git push";
    grhh = "git reset --hard HEAD";
    gs = "git status";
    gst = "git stash";
  };
}

D modules/gpg.nix => modules/gpg.nix +0 -33
@@ 1,33 0,0 @@
{ config, lib, pkgs, ... }: let
  agentTTL = 60 * 60 * 4; # 4 hours
  waylandCfg = config.wayland;
  xorgCfg = config.xorg;
in
{
  programs.gpg.enable = true;

  # Make the gpg-agent work
  services.gpg-agent = {
    enable = true;
    defaultCacheTtl = agentTTL;
    maxCacheTtl = agentTTL;
    pinentryFlavor = "gnome3";
    verbose = true;
  };


  systemd.user.services.yubikey-touch-detector = lib.mkIf (waylandCfg.enable || xorgCfg.enable) {
    Unit = {
      Description = "YubiKey touch detector";
      PartOf = [ "graphical-session.target" ];
    };

    Service = {
      ExecStart = "${pkgs.yubikey-touch-detector}/bin/yubikey-touch-detector --libnotify";
      Restart = "always";
      RestartSec = 5;
    };

    Install.WantedBy = [ "graphical-session.target" ];
  };
}

D modules/multimedia.nix => modules/multimedia.nix +0 -54
@@ 1,54 0,0 @@
{ config, lib, pkgs, ... }: with lib; with pkgs; let
  sublime-music = callPackage ../pkgs/sublime-music.nix {
    chromecastSupport = true;
    serverSupport = true;
  };
  cfg = config.gaming;
  hasGui = config.wayland.enable || config.xorg.enable;
in
{
  options.gaming.enable = mkEnableOption "gaming programs";

  config = {
    home.packages = [
      # Shell Utilities
      ffmpeg-full
      youtube-dl
    ] ++ (
      # GUI Tools
      optionals hasGui [
        pavucontrol
        playerctl

        # Multimedia
        fbida
        gimp
        guvcview
        imagemagick
        inkscape
        kdenlive
        libreoffice-fresh
        spotify
        sublime-music
      ]
    );

    programs.feh.enable = true;

    programs.mpv.enable = hasGui;
    programs.mpv.config = {
      force-window = "yes";
      hwdec = "auto-safe";
      profile = "gpu-hq";
      vo = "gpu";
      ytdl-format = "bestvideo+bestaudio";
    };

    programs.obs-studio.enable = hasGui;
    programs.obs-studio.plugins = with pkgs; [
      obs-studio-plugins.wlrobs
    ];

    programs.zathura.enable = hasGui;
  };
}

D modules/neovim/clipboard.nix => modules/neovim/clipboard.nix +0 -5
@@ 1,5 0,0 @@
{ config, pkgs, lib, ... }: with lib; {
  programs.neovim.extraConfig = ''
    set clipboard+=unnamed${if config.isLinux then "plus" else ""}
  '';
}

D modules/neovim/coc-settings.json => modules/neovim/coc-settings.json +0 -56
@@ 1,56 0,0 @@
{
  "explorer.keyMappings.global": {
    "<cr>": [
      "expandable?",
      [
        "expanded?",
        "collapse",
        "expand"
      ],
      "open"
    ]
  },
  "coc.preferences.rootPatterns": [
    ".rooter_root",
    ".git",
    ".hg",
    ".projections.json"
  ],
  "explorer.file.showHiddenFiles": true,
  "explorer.git.icon.status.added": "✚",
  "explorer.git.icon.status.deleted": "✖",
  "explorer.git.icon.status.renamed": "➜",
  "explorer.git.icon.status.copied": "➜",
  "explorer.git.icon.status.unmerged": "═",
  "explorer.git.icon.status.untracked": "?",
  "explorer.git.icon.status.ignored": "☒",
  "explorer.git.icon.status.mixed": "✹",
  "explorer.git.icon.status.modified": "✹",
  "explorer.icon.enableNerdfont": true,
  "explorer.git.showIgnored": true,
  "languageserver": {
    "nix": {
      "command": "rnix-lsp",
      "filetypes": [
        "nix"
      ]
    },
    "racket": {
      "command": "racket",
      "args": [
        "--lib",
        "racket-langserver"
      ],
      "filetypes": [
        "racket"
      ]
    },
    "vala": {
      "command": "vala-language-server",
      "filetypes": [
        "vala",
        "genie"
      ]
    }
  }
}

D modules/neovim/default.nix => modules/neovim/default.nix +0 -59
@@ 1,59 0,0 @@
# Cool other configs to look at
# - http://www.lukesmith.xyz/conf/.vimrc
# - https://github.com/thoughtstream/Damian-Conway-s-Vim-Setup
# - https://github.com/jschomay/.vim/blob/master/vimrc
# - https://github.com/jhgarner/DotFiles
#
# Dependencies
# - bat
# - fzf
# - python-neovim
# - ripgrep
# - wmctrl
# - probably others

{ config, pkgs, lib, ... }: with lib; {
  imports = [
    ./clipboard.nix
    ./plugins
    ./shortcuts.nix
    ./theme.nix
  ];

  programs.neovim = {
    enable = true;
    extraConfig = concatMapStringsSep "\n\n" builtins.readFile [
      ./init.vim
      ./filetype-specific-configs.vim
    ];

    extraPackages = with pkgs; [
      bat
      clang
      openjdk11_headless
      ripgrep
      rnix-lsp
      texlab
    ];

    extraPython3Packages = (
      ps: with ps; [
        black
        flake8
        pynvim
      ]
    );

    viAlias = true;
    vimAlias = true;
    vimdiffAlias = true;

    withNodeJs = true;
    withPython3 = true;
    withRuby = true;
  };

  home.symlinks."${config.xdg.configHome}/nvim/spell/en.utf-8.add" = "${config.home.homeDirectory}/Syncthing/.config/nvim/spell/en.utf-8.add";

  xdg.configFile."nvim/coc-settings.json".source = ./coc-settings.json;
}

D modules/neovim/filetype-specific-configs.vim => modules/neovim/filetype-specific-configs.vim +0 -35
@@ 1,35 0,0 @@
" FILETYPE SPECIFIC CONFIGURATIONS
" =============================================================================
set modeline " allow stuff like vim: set spelllang=en_us at the top of files

" Automatically break lines at 80 characters on TeX/LaTeX, Markdown, and text
" files
" Enable spell check on TeX/LaTeX, Markdown, and text files
autocmd BufNewFile,BufRead *.tex,*.md,*.txt,*.rst setlocal tw=80
autocmd BufNewFile,BufRead *.tex,*.md,*.txt,*.rst setlocal linebreak breakindent
autocmd BufNewFile,BufRead *.tex,*.md,*.txt,*.rst setlocal spell spelllang=en_gb
autocmd BufNewFile,BufRead *.tex,*.md,*.txt,*.rst match Over100Length /\%81v.\+/

" Automatically break lines at 100 characters when writing HTML files
" Enable spell check on HTML files
autocmd BufNewFile,BufRead *.html setlocal tw=100
autocmd BufNewFile,BufRead *.html setlocal spell spelllang=en_gb

" Automatically break lines at 80 characters when writing emails in mutt
" Enable spell check for emails in mutt and quotes file
autocmd BufRead *.email,$HOME/tmp/mutt-*,$HOME/tmp/neomutt-*,$HOME/.config/nixpkgs/modules/email/quotes setlocal tw=72
autocmd BufRead *.email,$HOME/tmp/mutt-*,$HOME/tmp/neomutt-*,$HOME/.config/nixpkgs/modules/email/quotes setlocal spell spelllang=en_gb
autocmd BufRead *.email,$HOME/tmp/mutt-*,$HOME/tmp/neomutt-*,$HOME/.config/nixpkgs/modules/email/quotes setlocal colorcolumn=72,80
autocmd BufRead *.email,$HOME/tmp/mutt-*,$HOME/tmp/neomutt-*,$HOME/.config/nixpkgs/modules/email/quotes match Over100Length /\%73v.\+/

" Use TAB = 2 spaces for a few file types
autocmd FileType javascript,json,xhtml,html,htmldjango,scss,less,yaml,css,markdown,rst,lisp,nix setlocal shiftwidth=2

" Make spelling a top level syntax element
autocmd FileType * syntax spell toplevel

" Disable folding on RST
autocmd FileType rst setlocal nofoldenable

" Remove unused imports on save for Go
autocmd BufWritePre *.go :silent call CocAction('runCommand', 'editor.action.organizeImport') 

D modules/neovim/init.vim => modules/neovim/init.vim +0 -57
@@ 1,57 0,0 @@
" VIM SETTINGS
" =============================================================================
set colorcolumn=80,100,120                              " Column guides
set hidden                                              " Don't close when switching buffers
set list listchars=tab:\▶\ ,trail:·,nbsp:·,space:·      " Highlight whitespace
set mouse=a                                             " Enable mouse scrolling
set number                                              " Show the current line number
set pastetoggle=<F2>                                    " Make C-S-V paste work better
set scrolloff=5                                         " Always have 5 lines above/below the current line
set showbreak=↩\ 
set signcolumn=yes                                      " Always show the sign column for git gutter
set title                                               " Override the terminal title
set virtualedit=onemore                                 " Allow the cursor to go one past the EOL
set nojoinspaces                                        " One space after period when doing gq

" Tabs
set expandtab                                           " Insert spaces instead of tabs
set shiftwidth=4                                        " 4 is the only way
set tabstop=8

" Search
set ignorecase  " ignore case...
set smartcase   " unless the search string has uppercase letters

" Tabs and Buffers ------------------------------------------------------------
set showtabline=2       " Always show the tab bar
set splitbelow          " Default split below, rather than above
set splitright          " Default split to the right, rather than the left

" Buffer navigation
map <C-H> :bp<CR>
map <C-L> :bn<CR>
map <C-W> :bdelete<CR>

" Quickfix --------------------------------------------------------------------
au FileType qf call AdjustWindowHeight(5, 5)
function! AdjustWindowHeight(minheight, maxheight)
  exe max([min([line("$"), a:maxheight]), a:minheight]) . "wincmd _"
endfunction

" Custom keybindings ----------------------------------------------------------
" Make j and k behave nicer when the line wraps
noremap j gj
noremap k gk

" Run make
nnoremap <F5> :make<CR>
nnoremap <S-F5> :make run<CR>

" Clean up paragraph
noremap <C-c> vipgq

" Undo and Swap ---------------------------------------------------------------
" Note: ~/tmp is a tmpfs, so writing to it does not use disk)
set undofile                    " Use an undo file
set undodir=~/tmp/nvim/undo//   " Store undo files in ~/tmp to avoid disk I/O
set directory=~/tmp/nvim/swap// " Store swap files in ~/tmp to aviod disk I/O

D modules/neovim/plugins/ale.nix => modules/neovim/plugins/ale.nix +0 -91
@@ 1,91 0,0 @@
{ lib, pkgs, ... }: with lib;
let
  aleFixers = {
    c = [ [ "clang-format" pkgs.clang ] ];
    cpp = [ [ "clang-format" pkgs.clang ] ];
    go = [ [ "gofmt" pkgs.go ] [ "goimports" pkgs.goimports ] ];
    nix = [ [ "nixpkgs-fmt" pkgs.nixpkgs-fmt ] ];
    python = [ [ "black" pkgs.black ] ];
    rust = [ [ "rustfmt" pkgs.rustfmt ] ];
  };
  aleLinters = {
    go = [ [ "gopls" pkgs.gopls ] ];
    python = [
      [ "pyright" pkgs.pyright ]
      [ "flake8" pkgs.python3Packages.flake8 ]
    ];
    rust = [ [ "analyzer" [ pkgs.rust-analyzer pkgs.rustc ] ] ];
  };
  mapListForLang = lang: l: "'${lang}': [${concatMapStringsSep ", " (f: "'${elemAt f 0}'") l}]";
  extractPackages = mapAttrsToList (lang: x: map (f: elemAt f 1) x);
in
{
  programs.neovim = {
    extraPackages = (flatten (map extractPackages [ aleFixers aleLinters ]));

    extraConfig = ''
      let g:ale_set_balloons = 1
      let g:ale_hover_to_floating_preview = 1
    '';

    plugins = [{
      plugin = pkgs.vimPlugins.ale;
      config = ''
        let g:ale_open_list = 1                 " Auto open the error list
        let g:ale_set_loclist = 0               " Limit the size of the ALE output to 5 lines
        let g:ale_set_quickfix = 1              " Limit the size of the ALE output to 5 lines
        let g:ale_list_window_size = 5          " Limit the size of the ALE output to 5 lines

        let g:ale_sign_error = '✖'              " Consistent sign column with Language Client
        let g:ale_sign_warning = '⚠'
        let g:ale_sign_info = '➤'

        let g:ale_fixers = {${concatStringsSep ", " (mapAttrsToList mapListForLang aleFixers)}}
        let g:ale_linters = {${concatStringsSep ", " (mapAttrsToList mapListForLang aleLinters)}}

        " Remap for format (selected region|document)
        xmap <C-S-F> <Plug>(ale_fix)
        nmap <C-S-F> <Plug>(ale_fix)

        " Remap keys for gotos
        nmap <silent> gd <Plug>(ale_go_to_definition)
        nmap <silent> gy <Plug>(ale_go_to_type_definition)
        nmap <silent> gr <Plug>(ale_find_references)

        " Hover and rename
        " TODO figure out how to make this work
        nnoremap <silent> K <Plug>(ale_hover)

        " Get fancy symbols for all of the different completion types.
        let g:ale_completion_symbols = {
          \ 'text': ' ',
          \ 'method': ' ',
          \ 'function': ' ',
          \ 'constructor': ' ',
          \ 'field': ' ',
          \ 'variable': ' ',
          \ 'class': ' ',
          \ 'interface': ' ',
          \ 'module': ' ',
          \ 'property': ' ',
          \ 'unit': 'unit ',
          \ 'value': 'val ',
          \ 'enum': ' ',
          \ 'keyword': 'keyword ',
          \ 'snippet': ' ',
          \ 'color': 'color ',
          \ 'file': ' ',
          \ 'reference': 'ref ',
          \ 'folder': ' ',
          \ 'enum member': ' ',
          \ 'constant': ' ',
          \ 'struct': ' ',
          \ 'event': 'event ',
          \ 'operator': ' ',
          \ 'type_parameter': 'type param ',
          \ '<default>': 'v '
          \ }
      '';
    }];
  };
}

D modules/neovim/plugins/blamer.nix => modules/neovim/plugins/blamer.nix +0 -25
@@ 1,25 0,0 @@
# Enable git blame on the line.
{ pkgs, ... }: with pkgs; let
  blamer = pkgs.vimUtils.buildVimPluginFrom2Nix rec {
    pname = "blamer";
    version = "1.3.0";
    src = pkgs.fetchFromGitHub {
      owner = "APZelos";
      repo = "blamer.nvim";
      rev = "v${version}";
      sha256 = "sha256-uIrbnoS2llGjn/mLMftO4F6gss0xnPCE39yICd0N51Y=";
    };
    meta.homepage = "https://github.com/APZelos/blamer.nvim";
  };
in
{
  programs.neovim.plugins = [
    {
      plugin = blamer;
      config = ''
        let g:blamer_enabled = 1
        let g:blamer_date_format = '%Y-%m-%d'
      '';
    }
  ];
}

D modules/neovim/plugins/coc.nix => modules/neovim/plugins/coc.nix +0 -79
@@ 1,79 0,0 @@
{ pkgs, ... }: {
  programs.neovim = {
    extraPackages = with pkgs; [
      gopls
    ];
    plugins = with pkgs.vimPlugins; [
      {
        plugin = coc-nvim;
        config = ''
          " CoC Extensions
          let g:coc_global_extensions=[
              \ 'coc-clangd',
              \ 'coc-css',
              \ 'coc-dictionary',
              \ 'coc-docker',
              \ 'coc-elixir',
              \ 'coc-eslint',
              \ 'coc-explorer',
              \ 'coc-go',
              \ 'coc-html',
              \ 'coc-java',
              \ 'coc-json',
              \ 'coc-lists',
              \ 'coc-marketplace',
              \ 'coc-omnisharp',
              \ 'coc-prettier',
              \ 'coc-pyright',
              \ 'coc-rust-analyzer',
              \ 'coc-sh',
              \ 'coc-texlab',
              \ 'coc-tsserver',
              \ 'coc-word',
              \ 'coc-yaml',
              \ ]

          set updatetime=300
          set shortmess+=c

          " Use tab for trigger completion with characters ahead and navigate.
          inoremap <silent><expr> <TAB>
                \ pumvisible() ? "\<C-n>" :
                \ <SID>check_back_space() ? "\<TAB>" :
                \ coc#refresh()
          inoremap <expr><S-TAB> pumvisible() ? "\<C-p>" : "\<C-h>"

          function! s:check_back_space() abort
            let col = col('.') - 1
            return !col || getline('.')[col - 1]  =~# '\s'
          endfunction

          " Remap keys for gotos
          nmap <silent> gd <Plug>(coc-definition)
          nmap <silent> gy <Plug>(coc-type-definition)
          nmap <silent> gi <Plug>(coc-implementation)
          nmap <silent> gr <Plug>(coc-references)

          " Formatting
          vmap <C-F> <Plug>(coc-format-selected)
          xmap <C-F> :call CocAction('format')<CR>
          nmap <C-F> :call CocAction('format')<CR>

          " Hover and rename
          nmap <silent> <F6> <Plug>(coc-rename)
          nnoremap <silent> K :call CocAction('doHover')<CR>

          " Go to symbol in (document|project)
          nmap <silent> S :CocList symbols<CR>

          " CoC Explorer
          function! CocExploreCwd()
              let cwd = substitute(execute(":pwd"), '\n', "", "")
              exe 'CocCommand explorer ' . cwd
          endfunction
          map <S-T> :call CocExploreCwd()<CR>
        '';
      }
    ];
  };
}

D modules/neovim/plugins/ctrlsf.nix => modules/neovim/plugins/ctrlsf.nix +0 -34
@@ 1,34 0,0 @@
# Sublime Text-like search
{ pkgs, ... }: let
  ctrlsf = pkgs.vimUtils.buildVimPluginFrom2Nix rec {
    pname = "ctrlsf";
    version = "2.1.2";
    src = pkgs.fetchFromGitHub {
      owner = "dyng";
      repo = "ctrlsf.vim";
      rev = "v${version}";
      sha256 = "sha256-h1/cbhdB8usyaPoV+P32+2Iml6x/iEbpKCWe3tqEgLI=";
    };
    meta.homepage = "https://github.com/dyng/ctrlsf.vim";
  };
in
{
  programs.neovim.plugins = [
    {
      plugin = ctrlsf;
      config = ''
        nmap <C-S> <Plug>CtrlSFPrompt
        vmap <C-S> <Plug>CtrlSFVwordPath
        let g:ctrlsf_ackprg = 'rg'
        let g:ctrlsf_auto_focus = { "at": "start" }
        let g:ctrlsf_default_root = 'cwd'
        let g:ctrlsf_mapping = {
            \ "next": "n",
            \ "prev": "N",
            \ }
        let g:ctrlsf_position = 'bottom'
        let g:ctrlsf_winsize = '70%'
      '';
    }
  ];
}

D modules/neovim/plugins/default.nix => modules/neovim/plugins/default.nix +0 -47
@@ 1,47 0,0 @@
{ pkgs, ... }: with pkgs; {
  imports = [
    # Project Navigation and Configuration
    ./ctrlsf.nix
    ./editorconfig-vim.nix
    ./vim-rooter.nix

    # UI Chrome
    ./fzf-vim.nix
    # ./nerdtree.nix
    ./vim-airline.nix
    ./vim-togglelist.nix

    # Editor
    ./blamer.nix
    # ./deoplete.nix
    ./rainbow.nix
    # ./supertab.nix
    ./vim-commentary.nix
    ./vim-multiple-cursors.nix
    ./vim-signify.nix
    ./vim-template.nix

    # Integration with environment
    ./vim-autoswap.nix
    ./vim-tmux-navigator.nix

    # Language Support
    # ./ale.nix
    ./coc.nix
    ./vim-markdown-composer.nix
  ];

  programs.neovim.plugins = with vimPlugins; [
    # Project Navigation and Configuration
    direnv-vim
    vim-fugitive

    # Editor
    auto-pairs
    vim-closetag
    vim-surround

    # Language Support
    vim-polyglot # Syntax support for basically all of the languages
  ];
}

D modules/neovim/plugins/deoplete.nix => modules/neovim/plugins/deoplete.nix +0 -13
@@ 1,13 0,0 @@
# Autocompletion
{ pkgs, ... }: with pkgs; {
  programs.neovim.plugins = [
    {
      plugin = vimPlugins.deoplete-nvim;
      config = ''
        let g:deoplete#enable_at_startup = 1

        autocmd BufNewFile,BufRead call deoplete#custom#option('sources', {'_':['ale', 'buffer','file']})
      '';
    }
  ];
}

D modules/neovim/plugins/editorconfig-vim.nix => modules/neovim/plugins/editorconfig-vim.nix +0 -12
@@ 1,12 0,0 @@
# Enable .editorconfig support.
{ pkgs, ... }: with pkgs;{
  programs.neovim.plugins = [
    {
      plugin = vimPlugins.editorconfig-vim;
      config = ''
        let g:EditorConfig_preserve_formatoptions = 1
        let g:EditorConfig_exclude_patterns = ['fugitive://.*', 'scp://.*']
      '';
    }
  ];
}

D modules/neovim/plugins/fzf-vim.nix => modules/neovim/plugins/fzf-vim.nix +0 -15
@@ 1,15 0,0 @@
# Fuzzy finder with preview window
{ pkgs, ... }: with pkgs;{
  programs.neovim = {
    extraPackages = [ fzf ];
    plugins = [
      {
        plugin = vimPlugins.fzf-vim;
        config = ''
          nnoremap <C-p> :Files<CR>
          let g:fzf_preview_window = 'right:60%'
        '';
      }
    ];
  };
}

D modules/neovim/plugins/nerdtree.nix => modules/neovim/plugins/nerdtree.nix +0 -14
@@ 1,14 0,0 @@
# Directory tree on the left
{ pkgs, ... }: with pkgs;{
  programs.neovim = {
    plugins = [
      { plugin = vimPlugins.nerdtree-git-plugin; }
      {
        plugin = vimPlugins.nerdtree;
        config = ''
          map <S-T> :NERDTreeToggle<CR>
        '';
      }
    ];
  };
}

D modules/neovim/plugins/rainbow.nix => modules/neovim/plugins/rainbow.nix +0 -31
@@ 1,31 0,0 @@
{ pkgs, ... }: let
  rainbow = pkgs.vimUtils.buildVimPluginFrom2Nix rec {
    pname = "rainbow";
    version = "3.3.1";
    src = pkgs.fetchFromGitHub {
      owner = "luochen1990";
      repo = "rainbow";
      rev = "v${version}";
      sha256 = "sha256-mKdgDQisD2w6dstTY+FUKh0vQeg57w39+Ga4Z/KKZPk=";
    };
    meta.homepage = "https://github.com/luochen1990/rainbow/";
  };
in
{
  programs.neovim.plugins = [
    {
      plugin = rainbow;
      config = ''
        let g:rainbow_active = 1
        let g:rainbow_conf = {
            \   'separately': {
            \       'nerdtree': 0,
            \       'tex': {
            \           'parentheses': ['start=/(/ end=/)/', 'start=/\[/ end=/\]/'],
            \       },
            \   }
            \}
      '';
    }
  ];
}

D modules/neovim/plugins/supertab.nix => modules/neovim/plugins/supertab.nix +0 -13
@@ 1,13 0,0 @@
# Tab complete sanely
{ pkgs, ... }: with pkgs;{
  programs.neovim = {
    plugins = [
      {
        plugin = vimPlugins.supertab;
        config = ''
          let g:SuperTabDefaultCompletionType = "<c-n>"
        '';
      }
    ];
  };
}

D modules/neovim/plugins/vim-airline.nix => modules/neovim/plugins/vim-airline.nix +0 -19
@@ 1,19 0,0 @@
# Cool status bar
{ pkgs, ... }: with pkgs; {
  programs.neovim.plugins = [
    {
      plugin = vimPlugins.vim-airline;
      config = ''
        let g:airline_powerline_fonts = 1                       " Enable fancy chars
        let g:airline#extensions#coc#enabled = 1                " Code competion/linting
        let g:airline#extensions#fugitiveline#enabled = 1       " Git branch, etc
        let g:airline#extensions#tabline#enabled = 1            " Show the tabline
        let g:airline#extensions#tabline#show_tabs = 0          " Don't show tabs, just buffers
      '';
    }
    {
      plugin = vimPlugins.vim-airline-themes;
      config = "let g:airline_theme='one'";
    }
  ];
}

D modules/neovim/plugins/vim-autoswap.nix => modules/neovim/plugins/vim-autoswap.nix +0 -12
@@ 1,12 0,0 @@
# Swap to the already opened file
{ lib, pkgs, ... }: with pkgs; {
  programs.neovim = {
    extraPackages = [ wmctrl ];
    plugins = [
      {
        plugin = vimPlugins.vim-autoswap;
        config = "let g:autoswap_detect_tmux = 1";
      }
    ];
  };
}

D modules/neovim/plugins/vim-commentary.nix => modules/neovim/plugins/vim-commentary.nix +0 -10
@@ 1,10 0,0 @@
{ pkgs, ... }: {
  programs.neovim.plugins = [
    {
      plugin = pkgs.vimPlugins.vim-commentary;
      config = ''
        noremap <F8> :Commentary<CR>
      '';
    }
  ];
}

D modules/neovim/plugins/vim-markdown-composer.nix => modules/neovim/plugins/vim-markdown-composer.nix +0 -10
@@ 1,10 0,0 @@
{ pkgs, ... }: {
  programs.neovim.plugins = [
    {
      plugin = pkgs.vimPlugins.vim-markdown-composer;
      config = ''
        let g:markdown_composer_autostart = 0
      '';
    }
  ];
}

D modules/neovim/plugins/vim-multiple-cursors.nix => modules/neovim/plugins/vim-multiple-cursors.nix +0 -25
@@ 1,25 0,0 @@
# Multiple cursor support
{ pkgs, ... }: with pkgs;{
  programs.neovim = {
    plugins = [
      {
        plugin = vimPlugins.vim-multiple-cursors;
        # config = ''
        #   func! Multiple_cursors_before()
        #     if deoplete#is_enabled()
        #       call deoplete#disable()
        #       let g:deoplete_is_enable_before_multi_cursors = 1
        #     else
        #       let g:deoplete_is_enable_before_multi_cursors = 0
        #     endif
        #   endfunc
        #   func! Multiple_cursors_after()
        #     if g:deoplete_is_enable_before_multi_cursors
        #       call deoplete#enable()
        #     endif
        #   endfunc
        # '';
      }
    ];
  };
}

D modules/neovim/plugins/vim-rooter.nix => modules/neovim/plugins/vim-rooter.nix +0 -12
@@ 1,12 0,0 @@
# Always change the CWD to the project root.
{ lib, pkgs, ... }: with lib; with pkgs; let
  rooter_patterns = [ ".rooter_root" ".git" ];
in
{
  programs.neovim.plugins = [
    {
      plugin = vimPlugins.vim-rooter;
      config = "let g:rooter_patterns = [${concatMapStringsSep ", " (p: "'${p}'") rooter_patterns}]";
    }
  ];
}

D modules/neovim/plugins/vim-signify.nix => modules/neovim/plugins/vim-signify.nix +0 -10
@@ 1,10 0,0 @@
{ pkgs, ... }: {
  programs.neovim.plugins = [
    {
      plugin = pkgs.vimPlugins.vim-signify;
      config = ''
        let g:signify_sign_delete = '-' " Make delete use - rather than _
      '';
    }
  ];
}

D modules/neovim/plugins/vim-template.nix => modules/neovim/plugins/vim-template.nix +0 -27
@@ 1,27 0,0 @@
# Sublime Text-like search
{ config, pkgs, ... }: let
  vim-template = pkgs.vimUtils.buildVimPluginFrom2Nix rec {
    pname = "vim-template";
    version = "0.0.1";
    src = pkgs.fetchFromGitHub {
      owner = "aperezdc";
      repo = "vim-template";
      rev = "618d3f2713ab300b9d8e755fd9fbc43d4f394d7e";
      sha256 = "sha256-zPWmIzo4HBgdyeXPkF5NWjt3ISzSdpzBX5gmyr1m08M=";
    };
    meta.homepage = "https://github.com/aperezdc/vim-template";
  };
in
{
  programs.neovim.plugins = [
    {
      plugin = vim-template;
      config = ''
        let g:templates_no_builtin_templates = 1
        let g:templates_directory = [
            \'${config.xdg.configHome}/nvim/templates',
            \]
      '';
    }
  ];
}

D modules/neovim/plugins/vim-tmux-navigator.nix => modules/neovim/plugins/vim-tmux-navigator.nix +0 -17
@@ 1,17 0,0 @@
# Always change the CWD to the project root.
{ pkgs, ... }: with pkgs; {
  programs.neovim.plugins = [
    {
      plugin = vimPlugins.vim-tmux-navigator;
      config = ''
        let g:tmux_navigator_no_mappings = 1
        let g:tmux_navigator_disable_when_zoomed = 1

        nnoremap <silent> <A-h> :TmuxNavigateLeft<cr>
        nnoremap <silent> <A-j> :TmuxNavigateDown<cr>
        nnoremap <silent> <A-k> :TmuxNavigateUp<cr>
        nnoremap <silent> <A-l> :TmuxNavigateRight<cr>
      '';
    }
  ];
}

D modules/neovim/plugins/vim-togglelist.nix => modules/neovim/plugins/vim-togglelist.nix +0 -10
@@ 1,10 0,0 @@
{ lib, pkgs, ... }: with pkgs; {
  programs.neovim.plugins = [
    {
      plugin = vimPlugins.vim-togglelist;
      config = ''
        nmap <script> <silent> E :call ToggleLocationList()<CR>
      '';
    }
  ];
}

D modules/neovim/shortcuts.nix => modules/neovim/shortcuts.nix +0 -115
@@ 1,115 0,0 @@
{ config, pkgs, lib, ... }: with lib; let
  mkAuGroup = { name, commands, filetype }: ''
    augroup ${name}
      autocmd!
      ${concatMapStringsSep "\n  " mkAutocmd (map (c: { filetype = filetype; } // c) commands)}
    augroup END
  '';
  mkAutocmd = { filetype, shortcut, action }:
    "autocmd FileType ${filetype} inoremap ;${shortcut} ${action}";

  groups = [
    {
      name = "LaTeX";
      filetype = "tex";
      commands = [
        { shortcut = "bf"; action = "\\textbf{}<Esc>i"; }
        { shortcut = "it"; action = "\\textit{}<Esc>i"; }
        { shortcut = "tt"; action = "\\texttt{}<Esc>i"; }
        { shortcut = "bb"; action = "\\mathbb{}<Esc>i"; }
        { shortcut = "ta"; action = "\\begin{tabular}<Enter>(<>)<Enter>\\end{tabular}<Esc>2kA{}<Esc>i"; }
        { shortcut = "ol"; action = "\\begin{enumerate}<Enter><Enter>\\end{enumerate}<Esc>kA\\item<Space>"; }
        { shortcut = "ul"; action = "\\begin{itemize}<Enter><Enter>\\end{itemize}<Esc>kA\\item<Space>"; }
        { shortcut = "li"; action = "\\item"; }
        { shortcut = "sec"; action = "\\section{}<Esc>i"; }
        { shortcut = "ssec"; action = "\\subsection{}<Esc>i"; }
        { shortcut = "sssec"; action = "\\subsubsection{}<Esc>i"; }
        { shortcut = "be"; action = "\\begin{DELRN}(<>)<Enter>(<>)<Enter>\\end{DELRN}<Esc>2k0fR:MultipleCursorsFind<Space>DELRN<Enter>c"; }
        { shortcut = "min"; action = "\\begin{minted}{(<>)}<Enter>(<>)<Enter>\\end{minted}<Esc>2kA{}<Esc>i"; }
        { shortcut = "int"; action = "\\int_{(<>)}^{(<>)}<Space>(<>)<Esc>T{i"; }
        { shortcut = "sum"; action = "\\sum_{(<>)}^{(<>)}<Space>(<>)<Esc>T{i"; }
        { shortcut = "verb"; action = "\\verb\\|\\|<Esc>i"; }
      ];
    }
    {
      name = "HTML";
      filetype = "html,xhtml,javascript.jsx";
      commands = [
        { shortcut = "s"; action = "<strong></strong><Space>(<>)<Esc>FbT>i"; }
        { shortcut = "em"; action = "<em></em><Space>(<>)<Esc>FeT>i"; }
        { shortcut = "h1"; action = "<h1></h1><Esc>F>a"; }
        { shortcut = "h2"; action = "<h2></h2><Esc>F>a"; }
        { shortcut = "h3"; action = "<h3></h3><Esc>F>a"; }
        { shortcut = "p"; action = "<p></p><Esc>F<i"; }
        { shortcut = "a"; action = ''<a<Space>href="">(<>)</a><Space>(<>)<Esc>14hi''; }
        { shortcut = "ul"; action = "<ul><Enter><li></li><Enter></ul><Esc>0k2f<i"; }
        { shortcut = "li"; action = "<li></li><Esc>F>a"; }
        { shortcut = "ol"; action = "<ol><Enter><li></li><Enter></ol><Esc>0k2f<i"; }
        { shortcut = "div"; action = ''<div class=""><Enter>(<>)<Enter></div><Esc>02kf"a''; }
        { shortcut = "tg"; action = "<DELRN>(<>)</DELRN><Esc>0fR:MultipleCursorsFind<Space>DELRN<Enter>c"; }
        { shortcut = "tr"; action = "<tr></tr><Esc>F<i"; }
        { shortcut = "td"; action = "<td></td><Esc>F<i"; }
        { shortcut = "th"; action = "<th></th><Esc>F<i"; }
      ];
    }
    {
      name = "Python";
      filetype = "python";
      commands = [
        { shortcut = "def"; action = "def ():<Enter><Space><Space><Space><Space>(<>)<Esc>0kf(i"; }
        { shortcut = "pr"; action = "print()<Esc>F(a"; }
        { shortcut = "for"; action = "for  in (<>):<Enter>(<>)<Esc>0kfrla"; }
      ];
    }
    {
      name = "JavaScript";
      filetype = "javascript";
      commands = [
        { shortcut = "$"; action = "$().(<>)<Esc>2F(a"; }
        { shortcut = "af"; action = "<Space>=> (<>)<Esc>F=hi"; }
        { shortcut = "cf"; action = "((<>)) {<Enter>(<>)<Enter>}<Esc>02kf(i"; }
        { shortcut = "cl"; action = "console.log();<Esc>F(a"; }
        { shortcut = "cs"; action = "class  {<Enter>(<>)<Enter>}<Esc>2k$Fsla"; }
        { shortcut = "fn"; action = "function() {<Enter>(<>)<Enter>}<Esc>2k$F(a"; }
        { shortcut = "for"; action = "for () {<Enter>(<>)<Enter>}<Esc>02kf(a"; }
        { shortcut = "if"; action = "if () {<Enter>(<>)<Enter>}<Esc>02kf(a"; }
        { shortcut = "let"; action = "let  = (<>);<Esc>F=hi"; }
        { shortcut = "x"; action = "extends<Space>"; }
        { shortcut = "im"; action = "import  from '(<>)';<Esc>Ftla"; }
        { shortcut = "var"; action = "var  = (<>);<Esc>F=hi"; }
      ];
    }
    {
      name = "Rust";
      filetype = "rust";
      commands = [
        { shortcut = "pr"; action = ''println!("{}", );<Esc>hi''; }
      ];
    }
    {
      name = "CPP";
      filetype = "cpp";
      commands = [
        { shortcut = "pr"; action = ''cout <<  << endl;<Esc>2bhi''; }
      ];
    }
    {
      name = "C_CUDA";
      filetype = "c,cuda";
      commands = [
        { shortcut = "pr"; action = ''printf();<Esc>hi''; }
      ];
    }
  ];
in
{
  programs.neovim.extraConfig = ''
    inoremap ;g (<>)

    " Navigating with guides
    map <Space><Backspace> a(<>) <Esc>
    map <Space><Space> <Esc>/(<>)<Enter>"_c4l

    ${concatMapStringsSep "\n\n" mkAuGroup groups}
  '';
}

D modules/neovim/theme.nix => modules/neovim/theme.nix +0 -21
@@ 1,21 0,0 @@
{ pkgs, lib, ... }: with lib; {
  programs.neovim = {
    plugins = with pkgs.vimPlugins; [
      vim-devicons
      vim-one
    ];

    extraConfig = ''
      if ($TERM == 'alacritty' || $TERM == 'tmux-256color' || $TERM == 'xterm-256color' || $TERM == 'screen-256color') && !has('gui_running')
          set termguicolors
      endif

      colorscheme one
      set background=dark

      " Highlight past 100 characters
      highlight Over100Length ctermbg=red ctermfg=white guibg=#BD4F4F guifg=#cccccc
      match Over100Length /\%101v.\+/
    '';
  };
}

D modules/newsboat.nix => modules/newsboat.nix +0 -66
@@ 1,66 0,0 @@
{ pkgs, ... }: {
  # See other configs:
  # * https://github.com/gpakosz/.newsboat/blob/master/config
  programs.newsboat = {
    enable = true;
    autoReload = true;
    browser = "${pkgs.xdg-utils}/bin/xdg-open";
    extraConfig = ''
      # Key navigation
      goto-next-feed no

      bind-key j down feedlist
      bind-key j next articlelist
      bind-key j down article
      bind-key J next-feed articlelist
      bind-key k up feedlist
      bind-key k prev articlelist
      bind-key k up article
      bind-key K prev-feed articlelist

      bind-key g home feedlist
      bind-key g home articlelist
      bind-key g home article
      bind-key G end feedlist
      bind-key G end articlelist
      bind-key G end article

      # View
      text-width 80

      # Newsboat colour scheme to work with the Nord palette
      # from Arctic Studios - https://github.com/arcticicestudio/nord
      # Tested with the iTerm2 Nord terminal colour scheme
      # https://github.com/arcticicestudio/nord-iterm2
      # though should work with any terminal using the palette

      color background          color236   default
      color listnormal          color248   default
      color listnormal_unread   color6     default
      color listfocus           color236   color12
      color listfocus_unread    color15    color12
      color info                color248   color236
      color article             color248   default

      # highlights
      highlight article "^(Feed|Link):.*$" color6 default bold
      highlight article "^(Title|Date|Author):.*$" color6 default bold
      highlight article "https?://[^ ]+" color10 default underline
      highlight article "\\[[0-9]+\\]" color10 default bold
      highlight article "\\[image\\ [0-9]+\\]" color10 default bold
      highlight feedlist "^─.*$" color6 color236 bold
    '';

    urls = [
      { url = "https://drewdevault.com/blog/index.xml"; }
      { url = "https://sourcehut.org/blog/index.xml"; }
      { url = "https://www.thedroneely.com/posts/rss.xml"; }
      { url = "https://stackoverflow.blog/feed/"; }
      { url = "https://jakevossen.libsyn.com/rss"; }
      { url = "https://emersion.fr/blog/rss.xml"; }
      { url = "https://www.joelonsoftware.com/feed/"; }
      { url = "https://weekly.nixos.org/feeds/all.rss.xml"; }
      { url = "https://veronneau.org/feeds/atom.xml"; }
    ];
  };
}

D modules/scripts/bin/set-contact-photo.py => modules/scripts/bin/set-contact-photo.py +0 -60
@@ 1,60 0,0 @@
#! /usr/bin/env python3

import io
import sys

import vobject
from PIL import Image

if len(sys.argv) != 3:
    print("Usage:")
    print("    set-contact-photo <vcf file> <photo file>")
    sys.exit(1)

with open(sys.argv[1]) as cf:
    vcard = vobject.readOne(cf.read())

with open(sys.argv[2], 'rb') as imagefile:
    image_data = imagefile.read()
    image = Image.open(io.BytesIO(image_data))

photos = [c for c in vcard.getChildren() if c.name.lower() == "photo"]

if len(photos) == 1:
    # Check if the photo is the same
    if image_data == photos[0].value:
        print("Photos are the same")
        sys.exit(0)

    while True:
        print(f"Already have a photo for {vcard.fn.value}. Replace? [Yn]: ", end="")
        response = input().lower()
        if response == "y":
            break
        if response == "n":
            sys.exit(0)

if len(photos) > 1:
    while True:
        print(
            f"Multiple photos found for {vcard.fn.value}. Remove all and replace? [Yn]: ",
            end="",
        )
        response = input().lower()
        if response == "y":
            break
        if response == "n":
            sys.exit(0)


# Remove all old photos:
for child in photos:
    vcard.remove(child)

photo = vcard.add('photo')
photo.type_param = image.format
photo.encoding_param = 'b'
photo.value = image_data

with open(sys.argv[1], 'w+') as cf:
    cf.write(vcard.serialize())

D modules/scripts/default.nix => modules/scripts/default.nix +0 -14
@@ 1,14 0,0 @@
{ lib, pkgs, ... }: let
  set-contact-photo = pkgs.writeScriptBin "set-contact-photo"
    (builtins.readFile ./bin/set-contact-photo.py);
in
{
  imports = [
    ./projectsync.nix
    ./sl.nix
  ];

  home.packages = [
    set-contact-photo
  ];
}

D modules/scripts/projectsync.nix => modules/scripts/projectsync.nix +0 -42
@@ 1,42 0,0 @@
{ config, pkgs, ... }: with pkgs; let
  # Clones projects into the ~/projects directory.
  tput = "${ncurses}/bin/tput";
  projectsyncScript = pkgs.writeShellScriptBin "projectsync" ''
    set -e

    BOLD=$(${tput} bold)
    RED=$(${tput} setaf 1)
    CLR=$(${tput} sgr 0)

    while read -ra project; do
      url=''${project[0]}
      parentdir=''${project[1]}
      rename=''${project[2]}

      if [[ ''${rename} ]]; then
        dir="$HOME/projects/''${parentdir}/''${rename}"
      else
        IFS='/' read -ra parts <<< "''${url}"
        dir="$HOME/projects/''${parentdir}/''${parts[-1]}"
      fi

      if [[ ''${dir} =~ ^.*.git$ ]]; then
        dir=''${dir::-4}
      fi

      echo "''${BOLD}Checking $dir...''${CLR}"
      if [[ -d $dir ]]; then
        echo "$dir already exists. Will not override."
      elif [[ -f $dir ]]; then
        echo "''${BOLD}''${RED}''${dir} is a file! Aborting!''${CLR}" && exit 1
      else
        echo "Cloning ''${url} into ''${dir}."
        ${git}/bin/git clone --recurse-submodules -j8 ''${url} ''${dir}
      fi
      echo
    done <"${config.home.homeDirectory}/Syncthing/projectlist"
  '';
in
{
  home.packages = [ projectsyncScript ];
}

D modules/scripts/sl.nix => modules/scripts/sl.nix +0 -26
@@ 1,26 0,0 @@
{ config, pkgs, ... }: with pkgs; let
  # Reverse ls when you type sl
  ls = "${coreutils}/bin/ls";
  slScript = pkgs.writeScriptBin "sl" ''
    #! ${python3}/bin/python
    import subprocess

    p = subprocess.Popen(
        ['${ls}', '-lah'],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        universal_newlines=True)
    output, errors = p.communicate()

    lines = output.splitlines()

    max_line_len = max(len(l) for l in lines)

    for line in output.splitlines():
        print(' ' * (max_line_len - len(line)), end="")
        print(line[::-1])
  '';
in
{
  home.packages = [ slScript ];
}

D modules/ssh.nix => modules/ssh.nix +0 -7
@@ 1,7 0,0 @@
{ config, ... }:
let
  homedir = config.home.homeDirectory;
in
{
  home.symlinks."${homedir}/.ssh/config" = "${homedir}/Syncthing/.ssh/config";
}

D modules/symlinks.nix => modules/symlinks.nix +0 -26
@@ 1,26 0,0 @@
{ config, lib, pkgs, ... }: with lib; let
  cu = "${pkgs.coreutils}/bin";
  cfg = config.home.symlinks;

  toSymlinkCmd = destination: target: ''
    $DRY_RUN_CMD ${cu}/mkdir -p $(${cu}/dirname ${destination})
    $DRY_RUN_CMD ${cu}/ln -sf $VERBOSE_ARG \
      ${target} ${destination}
  '';
in
{
  options = {
    home.symlinks = mkOption {
      type = types.attrsOf (types.str);
      description = "A list of symlinks to create.";
      default = { };
    };
  };

  # TODO Convert to config.lib.file.mkOutOfStoreSymlink ./path/to/file/to/link;
  # https://github.com/nix-community/home-manager/issues/257#issuecomment-831300021
  config = {
    home.activation.symlinks = hm.dag.entryAfter [ "writeBoundary" ]
      (concatStringsSep "\n" (mapAttrsToList toSymlinkCmd cfg));
  };
}

D modules/syncthing.nix => modules/syncthing.nix +0 -3
@@ 1,3 0,0 @@
{
  services.syncthing.enable = true;
}

D modules/tmux.nix => modules/tmux.nix +0 -74
@@ 1,74 0,0 @@
{ config, pkgs, ... }:
let
  mkModBind = key: command: ''
    bind -n M-C-${key} ${command}
    bind -n M-${key} ${command}
  '';
  configFilePath = "${config.xdg.configHome}/tmux/tmux.conf";
in
{
  programs.tmux = {
    enable = true;
    baseIndex = 1;
    keyMode = "vi";
    clock24 = true;
    escapeTime = 0;
    historyLimit = 10000;
    newSession = true;
    shell = "${pkgs.zsh}/bin/zsh";
    terminal = "tmux-256color";

    plugins = with pkgs.tmuxPlugins; [
      yank
      resurrect
      {
        # Resurrect tmux sessions (https://github.com/tmux-plugins/tmux-continuum)
        plugin = continuum;
        extraConfig = ''
          set -g @continuum-restore 'on'
        '';
      }
    ];

    extraConfig = ''
      # Border color
      set -g pane-active-border-style fg=red,bg=default 

      # Use Alt[-Ctrl]-HJKL to move around between vim panes and tmux windows.
      is_vim="ps -o state= -o comm= -t '#{pane_tty}' \
          | grep -iqE '^[^TXZ ]+ +(\\S+\\/)?g?(view|n?vim?x?)(diff)?$'"
      ${mkModBind "h" "if-shell \"$is_vim\" 'send-keys M-h' 'select-pane -L'"}
      ${mkModBind "j" "if-shell \"$is_vim\" 'send-keys M-j' 'select-pane -D'"}
      ${mkModBind "k" "if-shell \"$is_vim\" 'send-keys M-k' 'select-pane -U'"}
      ${mkModBind "l" "if-shell \"$is_vim\" 'send-keys M-l' 'select-pane -R'"}

      # Open a new window with Alt[-Ctrl]-Enter
      ${mkModBind "Enter" "split-window -h"}

      # Right status
      set -g status-interval 1
      set -g status-right '%Y-%m-%d %H:%M:%S'  

      # Use the mouse
      set -gq mouse on

      # Improve copy mode
      bind-key -T copy-mode-vi 'v' send-keys -X begin-selection
      bind-key -T copy-mode-vi 'y' send-keys -X copy-selection-and-cancel

      # renumber windows sequentially after closing any of them
      set -g renumber-windows on

      set -ga terminal-overrides ',*256col*:Tc'

      # new window by right click on status line
      bind-key -n MouseDown3Status new-window -a -t= -c '#{pane_current_path}'

      # new window in background by middle click on status line
      bind-key -n MouseDown2Status new-window -ad -t= -c '#{pane_current_path}'

      bind-key R run-shell 'tmux source-file ${configFilePath} > /dev/null; \
                            tmux display-message "Sourced ${configFilePath}!"'
    '';
  };
}

D modules/tracktime.nix => modules/tracktime.nix +0 -82
@@ 1,82 0,0 @@
{ config, lib, pkgs, ... }: with lib; with pkgs; let
  secretsDir = "${config.xdg.configHome}/nixpkgs/secrets";
  tracktime = callPackage ../pkgs/tracktime.nix { };
  yamlFormat = pkgs.formats.yaml { };
in
{
  home.packages = [ tracktime ];

  xdg.configFile."tracktime/tracktimerc".source = yamlFormat.generate "tracktimerc" {
    fullname = "Sumner Evans";
    github = {
      access_token = "${pkgs.coreutils}/bin/cat ${secretsDir}/github-tracktime-access-token|";
      username = "sumnerevans";
    };

    gitlab = {
      api_root = "https://gitlab.com/api/v4/";
      api_key = "${pkgs.coreutils}/bin/cat ${secretsDir}/gitlab-api-key|";
    };

    linear = {
      default_org = "beeper";
      api_key = "${pkgs.coreutils}/bin/cat ${secretsDir}/linear-personal-api-key|";
    };

    sourcehut = {
      api_root = "https://todo.sr.ht/api/";
      access_token = "${pkgs.coreutils}/bin/cat ${secretsDir}/sourcehut-access-token|";
      username = "~sumner";
    };

    sync_time = true;
    tableformat = "fancy_grid";

    day_worked_min_threshold = 120;

    project_rates = {
      "CSCI 400" = 40;
      "CSCI 406" = 40;
      "Grading 101" = 15;
      "teaching/aca" = 22;
    };

    customer_rates = {
      Beeper = 57.5;
      TTD = 51.92;
    };

    customer_addresses = {
      Beeper = ''
        207 High Street
        Palo Alto, CA 94301
      '';
      "Tracy Camp" = ''
        Computer Sceince Department
        Colorado School of Mines
      '';
      TTD = ''
        42 N. Chestnut St
        Ventura, CA 93001
        United States of America
      '';
    };

    customer_aliases = {
      Beeper = "Beeper Inc.";
      "Tracy Camp" = "Dr. Tracy Camp";
      TTD = "The Trade Desk";
    };
  };

  # Aliases
  programs.zsh.shellAliases = {
    tt-beeper = "tt start -c Beeper";
    tt-bri = "tt start -t linear -c Beeper -p BRI";
    tt-element = "tt start -c Beeper 'Element catchup'";
    tt-hspc = "tt start -t gl -p ColoradoSchoolOfMines/hspc-problems";
    tt-tea = "tt start -p teaching/ppl";
    tt-issues = "tt start -t gl -p 'beeper/issues' -c Beeper";
    tt-standup = "tt start -c Beeper 'Standup'";
  };
}

D modules/udiskie.nix => modules/udiskie.nix +0 -3
@@ 1,3 0,0 @@
{
  services.udiskie.enable = true;
}

D modules/window-manager/autorandr.nix => modules/window-manager/autorandr.nix +0 -106
@@ 1,106 0,0 @@
{ config, lib, pkgs, ... }: with lib; let
  cfg = config.xorg;
  i3msg = "${pkgs.i3-gaps}/bin/i3-msg";
  fingerprints = {
    ThinkPad_Internal = "00ffffffffffff0030aeba4000000000001c0104a5221378e238d5975e598e271c505400000001010101010101010101010101010101243680a070381f403020350058c210000019502b80a070381f403020350058c2100000190000000f00d10930d10930190a0030e4e705000000fe004c503135365746432d535044420094";
    ThinkVision = "00ffffffffffff0030aedd61000000002a1e0104a51f12783aee95a3544c99260f5054bdcf84a94081008180818c9500950fa94ab300023a801871382d40582c450035ae1000001e000000fc004d31340a202020202020202020000000fd00324b1e5a14000a202020202020000000ff0056393036503548410affffffff0101020314b4499011040302011f121365030c0010007c2e90a0601a1e4030203600dc0b1100001cab22a0a050841a3030203600dc0b1100001c662156aa51001e30468f3300dc0b1100001e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022";

    # Dell Monitors
    Dell_S2417DG = "00ffffffffffff0010ace7a055464330241c0104a5351e7806ee91a3544c99260f505421080001010101010101010101010101010101565e00a0a0a02950302035000f282100001a000000ff002341534e6d632f6446756a7664000000fd001e9022de3b010a202020202020000000fc0044656c6c20533234313744470a011e020312412309070183010000654b040001015a8700a0a0a03b50302035000f282100001a5aa000a0a0a04650302035000f282100001a6fc200a0a0a05550302035000f282100001a22e50050a0a0675008203a000f282100001e1c2500a0a0a01150302035000f282100001a0000000000000000000000000000000000000044";
    Dell_P2421D = "00ffffffffffff0010acfed042343330231e0104a5351e783aad75a9544d9d260f5054a54b008100b300d100714fa9408180d1c00101565e00a0a0a02950302035000f282100001a000000ff00443634335835330a2020202020000000fc0044454c4c205032343231440a20000000fd00314b1d711c010a202020202020019e020314b14f90050403020716010611121513141f023a801871382d40582c45000f282100001e011d8018711c1620582c25000f282100009e7e3900a080381f4030203a000f282100001a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fc";

    # School projectors
    MZ108 = "00ffffffffffff00170e000000000000001b0103800000782e0000000000000000000021080081c08100818090409500a9c0b3000101023a801871382d40582c450080387400001e000000fd00173d0e4c11000a202020202020000000fc00457874726f6e2048444d490a2000000010000000000000000000000000000001b1020322c04a90202205040203060701230907078301000067030c0000008021e200ea00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040";
  };

  configs = {
    ThinkPad_Internal = {
      enable = true;
      primary = true;
      position = "0x0";
      mode = "1920x1080";
      rate = "60.00";
    };
    ThinkVision = {
      enable = true;
      mode = "1920x1080";
      rate = "60.00";
    };
    Dell_S2417DG = {
      enable = true;
      primary = true;
      mode = "2560x1440";
      rate = "144.00";
    };
    Dell_P2421D = {
      enable = true;
      mode = "2560x1440";
      rate = "60.00";
    };
    MZ108 = {
      enable = true;
      mode = "1920x1080";
      rate = "60.00";
    };
  };
in
{
  config = mkIf cfg.enable {
    programs.autorandr = {
      enable = true;
      hooks = {
        postswitch = {
          "change-background" = "systemctl restart --user wallpaper";
          "notify-dbus" = ''${pkgs.libnotify}/bin/notify-send "Profile changed to $AUTORANDR_CURRENT_PROFILE"'';
        };
      };
      profiles = {
        ThinkPad = {
          fingerprint = {
            eDP-1 = fingerprints.ThinkPad_Internal;
          };
          config = {
            eDP-1 = configs.ThinkPad_Internal;
          };
        };
        ThinkVision = {
          fingerprint = {
            eDP-1 = fingerprints.ThinkPad_Internal;
            DP-1 = fingerprints.ThinkVision;
          };
          config = {
            eDP-1 = configs.ThinkPad_Internal;
            DP-1 = configs.ThinkVision // { position = "1920x0"; };
          };
          hooks = {
            postswitch = ''
              ${i3msg} '[workspace="10"]' move workspace to output DP-1
              ${i3msg} '[workspace="11: "]' move workspace to output DP-1
              ${i3msg} '[workspace="12: "]' move workspace to output DP-1
            '';
          };
        };
        Courscant_DoubleDell = {
          fingerprint = {
            DP-0 = fingerprints.Dell_S2417DG;
            DP-4 = fingerprints.Dell_P2421D;
          };
          config = {
            DP-0 = configs.Dell_S2417DG // { position = "0x0"; };
            DP-4 = configs.Dell_P2421D // { position = "2560x0"; };
          };
        };
        MZ108 = {
          fingerprint = {
            eDP-1 = fingerprints.ThinkPad_Internal;
            HDMI-2 = fingerprints.MZ108;
          };
          config = {
            eDP-1 = configs.ThinkPad_Internal;
            HDMI-2 = configs.MZ108;
          };
        };
      };
    };
  };
}

D modules/window-manager/bin/inactive-windows-transparency.py => modules/window-manager/bin/inactive-windows-transparency.py +0 -59
@@ 1,59 0,0 @@
#!/usr/bin/env python3

# This script requires i3ipc-python package (install it from a system package manager
# or pip).
# It makes inactive windows transparent. Use `transparency_val` variable to control
# transparency strength in range of 0…1 or use the command line argument -o.

import argparse
import i3ipc
import signal
import sys
from functools import partial

def on_window_focus(inactive_opacity, ipc, event):
    global prev_focused

    focused = event.container

    if focused.id != prev_focused.id:  # https://github.com/swaywm/sway/issues/2859
        focused.command("opacity 1")
        prev_focused.command("opacity " + inactive_opacity)
        prev_focused = focused


def remove_opacity(ipc):
    for workspace in ipc.get_tree().workspaces():
        for w in workspace:
            w.command("opacity 1")
    ipc.main_quit()
    sys.exit(0)


if __name__ == "__main__":
    transparency_val = "0.85"

    parser = argparse.ArgumentParser(
        description="This script allows you to set the transparency of unfocused windows in sway."
    )
    parser.add_argument(
        "--opacity",
        "-o",
        type=str,
        default=transparency_val,
        help="set opacity value in range 0...1",
    )