• Getting URL and Tab Title from Firefox with AppleScript

    A long time ago, a coworker shared an AppleScript function that gets the title and URL from the current tab in Chrome and formats the result as an Emacs org mode link.

    Org mode links have the format [[url][title]], e.g.:

    [[https://matthewbilyeu.com/blog/][· Matt's programming blog]]
    

    Invoked via Alfred, this script was helpful for getting links from Github pull requests, Trello cards, etc. into my org TODO list. When I began using Firefox Quantum, however, the Chrome-specific script was of no use.

    Firefox does not have AppleScript support, which means that any automated interaction with the application must be clumsily achieved through simulating GUI events. I found out that such GUI-based automation is prone to failure. It took some trial and error and cobbling various online snippets together, but so far this script has been fairly robust:

    use scripting additions
    use framework "Foundation"
    
    tell application "Firefox" to activate
    
    -- get the tab title from FF
    tell application "System Events" to tell process "firefox"
    	set frontmost to true
    	set the_title to name of windows's item 1
    	set the_title to (do shell script "echo " & quoted form of the_title & " | tr '[' ' '")
    	set the_title to (do shell script "echo " & quoted form of the_title & " | tr ']' ' '")
    end tell
    
    set thePasteboard to current application's NSPasteboard's generalPasteboard()
    set theCount to thePasteboard's changeCount()
    
    -- send cmd+l and cmd+c keystrokes to FF to highlight and copy the URL
    tell application "System Events"
    	keystroke "l" using {command down}
    	delay 0.2
    	keystroke "c" using {command down}
    end tell
    
    -- wait for the clipboard content change to have been detected
    repeat 20 times
    	if thePasteboard's changeCount() is not theCount then exit repeat
    	delay 0.1
    end repeat
    
    -- get the clipboard contents
    set the_url to the clipboard
    
    return "[[" & the_url & "][" & the_title & "]]" as text
    

    References:


  • Tech debt

    For a long time I’d considered technical debt to be just a bad thing. That it was a mess left behind by imprudent coders of the past. I hadn’t paused to reflect on the idea of it.

    Only recently did I come to appreciate that tech debt can be a tool, just like monetary debt. Monetary debt is a (hopefully) temporary suboptimal state that’s intentionally entered with an eye toward a long-term goal. E.g., you take on debt to pay for a university degree that you think will result in you having a higher salary in the future. The debt is useful in this way. Tech debt, too, can be useful.

    This became clear to me while working on a prototype to test market fit for a potential new feature at work. By making tradeoffs that sacrificed design robustness for speed of implementation, we intentionally introduced tech debt into the system. Designing and implementing a polished, scalable system before knowing whether the offering was valuable to our customers could have been an expensive mistake. Tech debt is allowing us to explore an idea and quickly react to evolving requirements.

    Of course, just as with monetary debt, too much tech debt can be disastrous. Tech debt is also harder than monetary debt to measure. Still, it’s a useful tool in some scenarios, and I’m glad I have a more nuanced understanding of it.


  • Command Lining

    I’ve slowly accumulated knowledge of a few tricks that help me to be productive at the terminal prompt. These are things that I somehow missed during years of computer science schooling and my early career. I often learn things, then immediately forget the time before I learned them, and then take that new information for granted. I’m trying to reflect on such learning and distill the best parts. And so hopefully someone who happens upon this list may find a new tip or two.

    TLDR

    TLDR - Simplified and community-driven man pages.

    tldr-pages usage screenshot

    This isn’t a built-in bash command, but I’ve found it to be very handy. It’s one of the first programs I install on a new system. Type tldr <command> to see usage examples of the most common options of command line programs.

    Tree

    Another not-built-in program. Imagine if ls recursively displayed a hierarchical directory listing. I like visual information, so I like tree.

    tree -L 3
    .
    └── uploads
        ├── 2013
        │   ├── 11
        │   └── 12
        ├── 2014
        │   ├── 03
        │   ├── 06
        │   ├── 07
        ├── 2015
        │   └── 01
        ├── 2016
        │   ├── 01
        │   └── 04
        ├── 2017
        │   └── 07
        └── 2018
            ├── 01
            └── 02
    

    Return to previous directory

    I happened to be reading an intro to bash commands once, and I was surprised that one of the first lessons showed a use of cd that I somehow wasn’t familiar with. This is how you can change the working directory back to the previous working directory. Great for hopping between a couple related projects.

    cd -
    

    Curly brace expansion

    This is a really cool feature of bash and one I wished I’d learned about earlier. Read more about it here.

    for i in {0..4}; do echo $i; done
    0
    1
    2
    3
    4
    

    My favorite use of brace expansion is to expand two long path names. E.g.

    cp ~/my/long/path/to/a/file.txt ~/my/long/path/to/a/file.txt.bak
    

    can be rewritten as

    cp ~/my/long/path/to/a/file.txt{,.bak}
    

    Previous command

    !! will give you access to the previous command. Of course you can also press the up arrow, but this is useful for modifying the previous command. 99% of the time I use this it’s because I forgot sudo.

    touch somefile.txt
    touch: somefile.txt: Permission denied
    
    sudo !!
    
    sudo touch somefile.txt
    

    Search command history

    Press ctrl + r to start searching backwards through the command history. This is another one I wish I’d learned sooner.

    (reverse-i-search)`touc': touch somefile.txt
    

    And the entire command history is available via history:

    history | tail -n3
    13637  ls
    13638  ll
    13639  history | tail -n3
    

  • User Feedback

    I spent some of my free time over the past three weeks rebuilding a small single page app I maintain: Tumblr Top (code here).

    The original incarnation of this app was written circa 2015 in CoffeeScript and AngularJS 1.4. The new version is in React and Semantic UI. There are also a few simple charts to visualize blog post and tag popularity that were coded using the Victory charting library.

    Tumblr Top post view

    I rewrote the app as an excuse to use React on a personal project and to make the site easier to build and maintain going forward. But the original version was running smoothly with something like 2-3k users per month. I didn’t want to upset the regular users by fixing what wasn’t broken.

    So I built out the updated site to what I thought was a reduced but perhaps satisfactory level of functionality. I hosted the new app at a different url, and added a callout to the old site asking users to try it out.

    While the new app was in-progress, I invited the early adopters to leave comments and criticism. Google Forms made soliciting and collecting user feedback dead simple (alternatives: SurveyMonkey or Typeform). The feedback survey was to the point. It only had four questions:

    • Do you like the redesign?
    • Do you miss the [missing feature]?
    • Were there any errors or bugs with the redesign? (If so, please provide your browser / operating system)
    • Any other feedback about the redesign or the app in general?

    Tumblr Top user feedback survey

    Over the course of a couple weeks the survey received 19 responses. This was enough to help me massively improve the first cut. The answers to those four questions gave me a lot of insight:

    • The performance improvements I’d added had come at the cost of my API access often being rate-limited
    • There was a critical display bug in a browser I hadn’t tested
    • Other small bugs were highlighted
    • Users had become very accustomed to what I thought were unimportant design details of the original version
    • A feature I thought was useless was meaningful to a large percentage of users
    • Strangers on the internet are willing to fill out a survey
    • You can’t please everybody

    I’m thankful to the handful of power users who took time out of their lives to provide valuable feedback for the app. Without that survey I may have shipped a shoddy update and degraded my users’ experience.


  • 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… 😈