• Spreadsheet Jeopardy!

    My girlfriend and I often watch Jeopardy! together, and we play along and keep score to make it more fun.

    To avoid having to pause the show to wager, we have a house rule that “Daily Double”s are just worth twice their marked value. E.g., a D.D. on a $600 clue would be worth $1200. And we yell the answers (questions?) instead of buzzing in.

    Scoring Jeopardy! in Google Sheets

    After tallying a number of games by hand, I decided to make a custom function in Google Sheets to keep score. Our scoring notation is:

    • correct: first initial of first name
    • incorrect: first initial of last name
    • daily double: initial followed by asterisk
    function score(results, level) {
      mScore = 0;
      kScore = 0;
      results[0].forEach( function(cell) {
        cell = cell.toLowerCase()
        
        if (cell.match(/.*m\*.*/)) {
          mScore += 2 * level;
        } else if (cell.match(/.*m.*/) !== null) {
          mScore += level;
        } else if (cell.match(/.*b\*.*/)) {
          mScore -= 2 * level;
        } else if (cell.match(/.*b.*/)) {
          mScore -= level;
        }
        
        if (cell.match(/.*k\*.*/)) {
          kScore += 2 * level;
        } else if (cell.match(/.*k.*/)) {
          kScore += level;
        } else if (cell.match(/.*c\*.*/)) {
          kScore -= 2 * level;
        } else if (cell.match(/.*c.*/)) {
          kScore -= level;
        }
        
      })
      return [[mScore, kScore]];
    }
    

    (Yes, I’m aware this could be refactored)

    It just occurred to me that I could probably modify this function to gift me a stray hundred dollars every now and then. She probably wouldn’t notice… 😈


  • Setting Emacs Theme Based on Ambient Light

    I sit next to a window at work. On sunny days it’s easier to see a light editor theme, and when the sky is dark, a dark theme is easier on my eyes. So I decided to try to have my MacBook automatically switch the Emacs theme based on readings from the ambient light sensor.

    Demonstration of the theme switching in action

    There are two parts to this solution: a command-line executable to read data from the sensor, and then a small elisp function to do the theme switching.

    The program below is from StackOverflow and slightly modified. It gets the AppleLMUController IO service, then, when the service is ready, prints the light sensor data to stdout and exits.

    // lmutracker.mm
    //
    // clang -o lmutracker lmutracker.mm -framework IOKit -framework CoreFoundation
    
    #include <mach/mach.h>
    #import <IOKit/IOKitLib.h>
    #import <CoreFoundation/CoreFoundation.h>
    
    static double updateInterval = 0.1;
    static io_connect_t dataPort = 0;
    
    void updateTimerCallBack(CFRunLoopTimerRef timer, void *info) {
      kern_return_t kr;
      uint32_t outputs = 2;
      uint64_t values[outputs];
    
      kr = IOConnectCallMethod(dataPort, 0, nil, 0, nil, 0, values, &outputs, nil, 0);
      if (kr == KERN_SUCCESS) {
        printf("%8lld", values[0]);
        exit(0);
      }
    
      if (kr == kIOReturnBusy) {
        return;
      }
    
      mach_error("I/O Kit error:", kr);
      exit(kr);
    }
    
    int main(void) {
      kern_return_t kr;
      io_service_t serviceObject;
      CFRunLoopTimerRef updateTimer;
    
      serviceObject = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleLMUController"));
      if (!serviceObject) {
        fprintf(stderr, "failed to find ambient light sensors\n");
        exit(1);
      }
    
      kr = IOServiceOpen(serviceObject, mach_task_self(), 0, &dataPort);
      IOObjectRelease(serviceObject);
      if (kr != KERN_SUCCESS) {
        mach_error("IOServiceOpen:", kr);
        exit(kr);
      }
    
      setbuf(stdout, NULL);
    
      updateTimer = CFRunLoopTimerCreate(kCFAllocatorDefault,
                      CFAbsoluteTimeGetCurrent() + updateInterval, updateInterval,
                      0, 0, updateTimerCallBack, NULL);
      CFRunLoopAddTimer(CFRunLoopGetCurrent(), updateTimer, kCFRunLoopDefaultMode);
      CFRunLoopRun();
    
      exit(0);
    }
    

    The accompanying elisp code will invoke that executable on a timer and change the theme based on the light reading.

    (setq current-theme "dark")
    (defconst light-theme 'majapahit-light)
    (defconst dark-theme 'majapahit-dark)
    
    ;; will apply a dark theme if the room is dark, and a light theme if the room is
    ;; bright
    (defun change-theme-for-lighting ()
      (let* ((current-light-sensor-reading
              (string-to-number
               (shell-command-to-string "./lmutracker"))))
        (if (< current-light-sensor-reading 100000)
            (when (not (string-equal current-theme "dark"))
              (load-theme dark-theme 1)
              (setq current-theme "dark"))
          (when (not (string-equal current-theme "light"))
            (load-theme light-theme 1)
            (setq current-theme "light")))))
    
    ;; probably want to run this less frequently than every second
    (run-with-timer 0 1 #'change-theme-for-lighting)
    

    Edit 4/7/2019: Read a Russian translation of this post here.


  • Creating a new blog post in Emacs

    Now that my blog is based on markdown text files, some new tooling options have opened!

    • entire blog is under source control (done)
    • make targets for common actions (e.g. create new post, deploy, serve dev version) (done)
    • git hooks for publishing (need to think about this more)
    • blogging via emacs! (done!)

    I just wrote this function to create a new posts file and open a buffer for editing it. In fact, it’s how I’m editing this post right now!

    (defun new-blog-post ()
      (interactive)
      (let ((post-title (read-string "Enter new post title: ")))
        (let* ((posts-dir "/ssh:vps:~/projects/blog/_posts/")
               (clean-title (replace-regexp-in-string
                             "[^[:alpha:][:digit:]_-]"
                             ""
                             (s-replace " " "-" (downcase post-title))))
               (new-post-filename (concat
                                   (format-time-string "%Y-%m-%d")
                                   "-"
                                   clean-title
                                   ".md"))
               (frontmatter-template "---\nlayout: post\ntitle: {title}\ndate: {date}\n---\n\n")
               (frontmatter (s-replace "{date}"
                                       (format-time-string "%Y-%m-%d %H:%m %z")
                                       (s-replace "{title}"
                                                  post-title
                                                  frontmatter-template)))
               (new-post-file (expand-file-name new-post-filename posts-dir)))
          (if (file-exists-p new-post-file)
              (message "A post with that name already exists.")
            (write-region frontmatter nil new-post-file)
            (find-file new-post-file)))))
    

    This was also the first elisp function I wrote :)


  • under construction

    I was having some headaches with my existing shared hosting provider, so I decided to move this site to a VPS. I also didn’t want to run a LAMP stack, so I’ve moved this blog from WordPress to Jekyll. Please pardon the appearance as I find time to iron things out.


  • How to set up free SSL on shared-hosting with Let&#8217;s Encrypt

    I just updated this domain to use HTTPS with Let’s Encrypt as a certificate authority. Presently this site is on a shared-hosting provider and I had to generate a cert manually and then upload it. Here are instructions for doing that.

    Note: some shared-hosting providers may offer a way to automatically generate and install a Let’s Encrypt (or other CA) certificate directly through the cPanel. I’d recommend doing that if it’s an option :)

    First, download and install certbot. On a separate computer (i.e., not the website host), run certbot to generate a certificate:

    brew install certbot
    mkdir ~/letstencrypt && cd ~/letstencrypt/
    certbot --config-dir . --work-dir . --logs-dir . certonly --manual
    

    After displaying some prompts, certbot will produce a challenge string and ask you to upload a file to your host containing that content (using the http challenge). This is to prove control of the website.

    On the host, create the file as instructed. E.g. copy the challenge text, then:

    pbpaste > challengefile
    ssh myhost 'mkdir -p ~/public_html/.well-know/acme-challenge/'
    scp challengefile myhost:~/public_html/.well-known/acme-challenge/rPs-CyPusl...
    

    Then, confirm you’ve uploaded the file and complete the certbot setup to create the certificate. There will be a live directory containing the generated certificate and secret.

    live
    └── my-website.com
        ├── README
        ├── cert.pem -> ../../archive/my-website.com/cert1.pem
        ├── chain.pem -> ../../archive/my-website.com/chain1.pem
        ├── fullchain.pem -> ../../archive/my-website.com/fullchain1.pem
        └── privkey.pem -> ../../archive/my-website.com/privkey1.pem
    

    Copy the contents of fullchain.pem and paste them into the certificate text box of your cPanel’s SSL configuration settings or upload the certificate file directly.

    Finally, install the certificate and upload privkey.pem.

    Once the process is complete the challenge file can be removed from the server. You should now be able to access your domain over https.