Mimsy: Hacks

icalBuddy and eventsFrom/to—Wednesday, August 19th, 2015

I use icalBuddy extensively along with GeekTool to display events on my desktop. I have the fairly standard sort of “here are your upcoming events”:

  • icalBuddy --excludeCals Television --excludeEventProps url --dateFormat "%A" --includeOnlyEventsFromNowOn eventsToday+4

This shows everything from the rest of today through four days from now. But you’ll notice it excludes one calendar: I really don’t need to see a list of the television shows I sometimes watch. They’re not that important to me.

Up until about a week ago, I showed only television shows for today:

  • icalBuddy --includeCals Television --excludeEventProps url --dateFormat "%A" --includeOnlyEventsFromNowOn eventsToday

And it worked fine, until the local old movies television station had William Castle’s classic 13 Ghosts on at 1:20 AM a few weeks ago. I missed it, because 1:20 AM isn’t today, it’s tomorrow. But by the time I look at it tomorrow morning, 1:20 AM is long gone.

I initially changed it to “eventsToday+1”, but that clutters up my desktop with events for tomorrow night, which I don’t need to know about now. I don’t plan my life around television shows, I just want to know if there’s something interesting right now. What I really want is for today’s list to include until tomorrow morning. The icalBuddy man page indicates that it’s possible to specify a range that includes an hour on a relative end date, but the documentation is currently wrong. For the option “eventsFrom:START to:END”, it says:

Print events occurring between the two specified dates. The dates (START and END) may be specified in a natural language form (such as "tomorrow at noon" or "june 10 at 6 pm") or as relative dates (such as "today+3" or "yesterday-2") but the safest format is "YYYY-MM-DD HH:MM:SS +HHMM"

Specifying tomorrow at noon, or tomorrow at 8 am, or tomorrow at anything just shows everything from tomorrow. A quick use of --debug confirmed it: icalBuddy interprets “tomorrow at” anything to be “[tomorrow] at 11:59:59 PM Central Daylight Time”.

I verified, however, that the to: option can accept partial days, by using the actual date (August 20 at 7 pm, for example), so I messed around until I found a format that works. Rather than “tomorrow at time”, use “time at tomorrow”:

  • icalBuddy --includeCals Television --excludeEventProps url -f --dateFormat "%A" --includeOnlyEventsFromNowOn eventsFrom:today to:"noon tomorrow"
  • icalBuddy --includeCals Television --excludeEventProps url -f --dateFormat "%A" --includeOnlyEventsFromNowOn eventsFrom:today to:"9 am tomorrow"

The first is interpreted as noon tomorrow, and the second as 9 am tomorrow, only showing things in the morning from tomorrow instead of all day.

Retry SSH connections after transient error—Monday, October 20th, 2014

The Timeout class works great for retrying connections after they timeout, but what about more prosaic errors? I’ve been getting a bunch of AuthenticationException errors in my Python/Paramiko connection attempts lately. I’d been just capturing all SSHExceptions (of which AuthenticationException is a subclass) and reporting the error, but this is just a transient error that almost always goes away on the very next upload.

That makes it a perfect candidate for retrying the connection. I renamed the class from Timeout to Persistence, because this more generic class is going to be more persistent at making connections.1

[toggle code]

  • from paramiko import SSHException, AuthenticationException
  • class Persistence(object):
    • def __init__(self, function=None, seconds=30, tries=3, errorMessage='Timeout'):
      • self.seconds = seconds
      • self.tryLimit = tries
      • self.tries = 1
      • self.function = function
      • self.errorMessage = errorMessage
    • def act(self):
      • signal.signal(signal.SIGALRM, self.handleTimeout)
      • signal.alarm(self.seconds)
      • try:
        • self.function()
      • except AuthenticationException, error:
        • self.tryAgain(AuthenticationException(error), 'Authentication exception')
      • signal.alarm(0)
    • def tryAgain(self, exception, message):
      • if self.tries >= self.tryLimit:
        • raise exception
      • else:
        • print message, 'try', self.tries, self.errorMessage
        • sleep(2*self.tries)
        • self.tries = self.tries + 1
        • self.act()
        • print 'Succeeded on try', self.tries
    • def handleTimeout(self, signum, frame):
      • self.tryAgain(TimeoutError(self.errorMessage), 'Timed out')

All it really does is add a tryAgain method that can be called both by the handleTimeout method and any exceptions in try/except. If the failure continues more than three times, the exception is passed back up as normal.

AppleScript, variables, and dropped filenames in Automator—Monday, October 6th, 2014
Prepend Disclaimer workflow

Simple workflow to prepend a known PDF to an arbitrary dropped PDF.

Over on Stack Overflow the other day, someone asked an intriguing question about wanting to prepend a disclaimer page to hundreds of PDF files. Now, merging multiple PDFs into a single PDF is the kind of task Automator excels at, and it’s very easy to do. Appending a single page to multiple PDF files, however, is a classic loop. This is not something Automator excels at. It requires thinking outside the workflow.

The basic workflow is an application—application workflows take dropped items and do things with them.

  1. Get Specified Finder Items (the disclaimer)
  2. Combine PDF Pages (appending pages)
  3. Move Finder Items (to a special folder created for the combined items)
Disclaimer Loop workflow

Automator can loop through dropped items one at a time using Run Workflow.

However, this only works for one file at a time. How to prepend the disclaimer to hundreds of files? Automator has a loop workflow action, but all it does is go back to the beginning of the workflow. It doesn’t do anything to incrementally loop through the list of dropped files. For that, we need the Run Workflow action. This runs a different workflow, and can send its own dropped items one at a time to that second workflow.

But, there’s a second problem: the Combine PDF Pages action creates files with a random filename. There is no option to tell it to take one of the dropped files and use that as its new filename.

Automator has some very impressive variable options: there are variables that are really AppleScripts, variables that are really shell scripts, variables containing many system names and common system paths. There is no variable that contains the dropped filename, however, and the AppleScript/Shell Script variables don’t take any arguments. As far as I can tell, the variables are all, basically, static, even if in a dynamic way. There is, thus, no way to modify the dropped file’s path using either an AppleScript variable or a Shell Script variable in order to get the dropped file’s base name.

Two search bookmarklets for Django—Tuesday, August 19th, 2014

I run this site through a Django CMS. I often find myself looking for the most recent pages. Now, by default, the most recently edited pages show up at the top, and of course I can link a URL that specifically orders by when pages were added, but there is no way to limit it just to, say, the pages I’ve created this month. Even when I create only a few pages a month, which is normal, those views will still show, in my case, the hundred most recent pages. I’m not aware of a query parameter that will limit the number of results, nor of a query parameter that will only show within the last x days of a date field.

It is possible to show only a single month, but not to show only the current month. Any such bookmark will be out of date once the next month comes around. A JavaScript bookmarklet, however, can do this:

  • javascript:today=new%20Date();recentURL='https://django.localhost/admin/cms/page/?created__month='+(today.getMonth()+1)+'&created__year='+today.getFullYear();window.location=recentURL;

I also find myself going to the main list of pages and then doing a search. A bookmarklet can go directly to the search:

  • javascript:searchTerm=window.prompt('Search%20for?');searchURL='https://django.localhost/admin/cms/page/?q='+searchTerm;window.location=searchURL;

This will prompt for the search term and then construct a search URL with that term. It does not specifically encode the term, but I’ve tested it in Safari, Firefox, and Chrome and it works.

BASIC video decoder ring—Sunday, August 10th, 2014

I’ve been fooling around with BASIC a bit after getting HotPaw BASIC for iOS. First things were getting Mastermind (80 Microcomputing, October 1981, p. 122) and the infamous Super Star Trek BASIC programs working. I can now use my 64-gigabyte iPad 2 as a 1978 TRS-80 Model I.

One of the things I always had in mind back in the day but never got around to1 was a cipher decoder assistant. Over time, a cipher decoder seemed more and more pointless—who uses ciphers in the age of public keys?

Here’s a simple encipher program in Python for Editorial:

  • #coding: utf-8
  • import workflow
  • from random import shuffle
  • from string import ascii_lowercase
  • originalText = workflow.get_input().lower()
  • letters = list(ascii_lowercase)
  • cipher = list(ascii_lowercase)
  • shuffle(cipher)
  • cipher = dict(zip(letters, cipher))
  • cipheredText = ''
  • for letter in list(originalText):
  • if letter in cipher:
  • cipheredText = cipheredText + cipher[letter]
  • else:
  • cipheredText = cipheredText + letter
  • workflow.set_output(cipheredText)

This will shuffle the letters of the alphabet and use the new shuffling as the cipher. This puts it one step above a video decoder ring, which only had 25 possible ciphers.

The BASIC code acts as it would on any old computer of the era. It waits for text commands and prints out the currently-guessed cipher letter by letter and then the currently-guessed deciphered text.

HotPaw Basic on iOS—Sunday, July 13th, 2014

While looking at Chipmunk Basic yesterday, I noticed that Ron Nicholson also has a BASIC for iOS, HotPaw Basic. I’m not sure how useful it is, but it’s insanely fun. It appears to be based off of the Chipmunk code, and so works similarly. It also includes functions for accessing the touchscreen, the accelerometer, and the GPS location.

This is very simple code for drawing random circles wherever you’re touching the screen.

Compared to the other programming environment I have on my iPad, Pythonista, the built-in editor is archaic. It’s one-line-at-a-time editing pretty much just like I remember it from the early eighties—but even toward the late eighties, I was using a full-screen BASIC editor on OS-9. This is more like the old interactive BASICs where you replaced a line by retyping that line number; you can also edit a line by typing “edit” and the line’s number.

Type just the line’s number to delete that line.

Just like in the old days, it is very easy to program an endless loop; however, even these tiny computers we carry in our pockets are so fast that an endless loop can be impossible to break out of. My initial circles program just drew random circles at random locations on the screen; it locked up HotPaw Basic—the Stop button failed to work. Unlike the old days, however, one rogue program is less likely to lock up the entire system. The iPad itself experienced no slow-down and I was able to easily exit HotPaw Basic and restart it.

HotPaw automatically saves your current program in “tmp.bas”. You can also save it using the “save” command, load new ones using the “load” command, and view all programs using “dir”.

It does not support iCloud, which means that programs written on the iPad do not automatically transfer to the iPhone or other devices.

If you’re one of those people who look back fondly on BASIC, or if you have some BASIC programs you’d like to run on your iPhone or iPad, take a look at it. Most of the other BASIC languages I’m seeing on the app store are recreations of specific computers of the past, such as Commodore BASIC, or don’t even look like BASIC any more. Chipmunk Basic, while hardly modern, has been and continues to be updated for use today.

Apple goes to the Swift—Saturday, June 7th, 2014
photo for Swift Apple

The race may not always go to the swift, but that is the way to place your bets.

The most exciting part of the WWDC keynote last Monday wasn‘t the new operating systems for the Macintosh and iDevices. It was the announcement of the new Swift programming language for MacOS and iOS. A new programming language is my equivalent of “one more thing…”

It took a few seconds for the Swift Language Guide to appear on the iBookstore after the announcement, but the reference makes me even more excited. Swift appears to combine some of the best features of Objective-C/C++ and Python.1

The book, which is also available online, hooked me. I have not yet programmed in Swift, but I’m going to, and soon. I dabbled in iOS app creation for work, but so far all of my personal iPhone/iPad apps have been XHTML5. Swift makes me want to find some use for it.

It’s pretty rare for me to read a programming book all the way through; good ones, like the Swift book, make me want to hit the keyboard. In this case, even though I don’t have access to my iMac, I still wanted to, and did, finish the guide.

Swift will definitely be familiar to Python programmers. For example, it contains the for…in control statement that I first saw in Python.

In addition to the traditional for-condition-increment loop found in C, Swift adds a for-in loop that makes it easy to iterate over arrays, dictionaries, ranges, strings, and other sequences.

Swift contains both a while and a while… do loop, as does C++. It also includes C++’s switch statement, however, there are major differences:

The cases of a switch statement do not “fall through” to the next case in Swift, avoiding common C errors caused by missing break statements.

You can specify that any particular case does fall through, but that’s not the default.

A switch statement must handle all possible cases or the code will not compile. This is especially useful for enumeration types.

…a switch statement must be exhaustive when considering an enumeration’s members. If the case for .West is omitted, this code does not compile, because it does not consider the complete list of CompassPoint members. Requiring exhaustiveness ensures that enumeration members are not accidentally omitted.

Timeout class with retry in Python—Wednesday, April 16th, 2014

In Paramiko, the SSHClient’s connect method has a timeout parameter, but it rarely causes a timeout in some common instances. Since moving from San Diego’s Cox Cable to Round Rock’s Time-Warner, I’ve been seeing stuck connections much more often.

The fix appears to be to use signals: set an alarm before running the line/function that might get stuck, and then remove the alarm afterward. If the alarm has time to go off, it will generate an exception that can then be handled.

Since the inability to connect or fail to connect appears to be extremely random, I decided to combine it with the option to retry the connection:

[toggle code]

  • import signal
  • class TimeoutError(Exception):
    • pass
  • #Paramiko timeout does not work often, if at all
  • # So create a timeout class
  • class Timer(object):
    • def __init__(self, function=None, seconds=30, tries=3, errorMessage='Timeout'):
      • self.seconds = seconds
      • self.tryLimit = tries
      • self.tries = 1
      • self.function = function
      • self.errorMessage = errorMessage
    • def act(self):
      • signal.signal(signal.SIGALRM, self.handleTimeout)
      • signal.alarm(self.seconds)
      • self.function()
      • signal.alarm(0)
    • def handleTimeout(self, signum, frame):
      • if self.tries >= self.tryLimit:
        • raise TimeoutError(self.errorMessage)
      • else:
        • print 'Timed out on try', self.tries, self.errorMessage
        • self.tries = self.tries + 1
        • self.act()
        • print 'Succeeded on try', self.tries

The first class, a subclass of Exception, doesn’t do anything except give us an appropriately-named exception. The second class will raise that exception if the passed function does not complete before the given number of seconds.

First, instantiate the timer; then run the “act” method. If it times out, it will (by default) try again twice; that is, it will try three times. The function is retried simply by calling the act method again.

For example:

Older posts.