Posts Tagged ‘productivity’
Checkcards on Github
August 30th, 2010 at 11:41 pm
My library loan tracking scripts, checkcards and checkcards.py, stopped working this weekend. After a bit of debugging, I came to learn that the library—or, more accurately, its software vendor—had snuck in a new <span> tag at the ends of magazine titles. My Python/BeautifulSoup code wasn’t expecting this and choked on it.1
The fix was fairly simple, but as I was editing checkcards.py, the Python script that does all the heavy lifting, I got a little too aggressive in tearing out the old code and had to reconstruct some of it from memory. It was then that I realized that I’d never put the checkcards scripts under version control. Now I have.
In addition to the README, the GitHub repository has three files:
checkcards.py, which communicates with the library’s website and prepares the email with the list of items checked out and on hold.checkcards, which runscheckcards.pyand pipes the result tosendmailfor emailing.com.leancrew.checkcards.plist, which tellslaunchdto runcheckcardsevery morning.
My login information has been redacted, and the README tells you how to customize it for yourself. Frankly, I doubt that this will be useful to anyone who doesn’t use the Naperville Public Library, but who knows? I think it makes pretty good use of the mechanize library to interact with web pages.
-
I swear this paragraph makes perfect sense. ↩
Updated Flickr URL script for TextExpander
July 26th, 2010 at 11:50 am
Last week I wrote a little Python script that printed out the URL of a Flickr image when that image’s page is currently showing in Safari. I used that script with TextExpander to automatically type out the URL when I needed it without having to dig in a couple of levels to get the image URL by hand. I’ve since improved the script to be more flexible and easier to modify.
I won’t go through my motivation for writing the script; it’s laid out in last week’s post. I’ll just point out that there were two problems with the script as it was originally written:
- “Special” strings, like URLs, were buried in the code instead of defined at the beginning.
- It worked only when the current page in Safari was the main page for the photo. It failed when the current page was, for example, one of the “sized” pages for the image.
Both of these problems have been fixed with this new version.
1: #!/usr/bin/python
2:
3: import appscript
4: import re
5: import sys
6: from urllib import urlopen
7:
8: # The basic URL format for photos.
9: baseURL = 'http://www.flickr.com/photos/%s/%s/'
10:
11: # The regex for extracting user and photo info.
12: infoRE = r'flickr\.com/photos/(.*)/(\d+)/?'
13:
14: # The various image URL suffixes.
15: suffixes = {'master': '_m.jpg',
16: 'original': '_o_d.jpg',
17: 'large': '_b_d.jpg',
18: 'medium640': '_z_d.jpg',
19: 'medium500': '_d.jpg',
20: 'small': '_m_d.jpg',
21: 'thumbnail': '_t_d.jpg',
22: 'square': '_s_d.jpg'}
23:
24: # Get the URL of the frontmost Safari tab and extract the photo info.
25: thisURL = appscript.app('Safari').documents[0].URL.get()
26: info = re.findall(infoRE, thisURL)
27:
28: # Download the main page for that photo and get its "master URL."
29: # Use the master to generate the URL for the medium500 image
30: # and print it.
31: try:
32: user = info[0][0]
33: id = info[0][1]
34: pageURL = baseURL % (user, id)
35: html = urlopen(pageURL).read()
36: imageURL = re.search(r'<link\s+rel="image_src"\s+href="([^"]+)"', html).group(1)
37: imageURL = imageURL.replace(suffixes['master'], suffixes['medium500'])
38: sys.stdout.write(imageURL)
39:
40: # Print an error message if there's any problem.
41: except:
42: sys.stdout.write("wrongpagewrongpage")
Lines 8-22 pull all the special strings out to the top of the code, where they can be seen (and adjusted if Flickr changes its URL format). The new suffixes dictionary included all the size possibilities, so it would be a simple matter to change the code to return, say, the Thumbnail URL; just change medium500 in Line 37 to thumbnail.
In the previous version of this script, the URL of the current Safari page would be downloaded and searched for the special <link rel="image_src" > tag. The problem with this was that some Flickr image pages—in particular, the pages associated with “sized” images—didn’t have this tag, so the search would fail. This version defines the baseURL for the photo, and downloads it instead of the current Safari page, insuring that the <link> tag will be present.
Errors are now handled through exceptions instead of an if/else test. This allows us to handle a multitude of errors with a single error message.
As before, I have this script saved as a Shell Script in TextExpander and tied to an abbreviation of ;500. Now it’s a snap to enter Flickr image URLs wherever I need them.
A quick script for Avery 5160 labels
July 24th, 2010 at 11:26 pm
An occasional theme of these blog posts is the value of scripting as a way of showing your computer who’s boss. If you can’t write scripts—and here I’m adopting a broad definition of scripts to include macros and other customizations—you become a slave to your computer, forced to do things the way it wants you to instead of the way that’s most natural and efficient for you.
Tonight I helped my wife and a friend who needed to print up a bunch of labels for the neighborhood swim team. The computer program the swim team uses to run the meets and keep track of times can print labels, but not the type of label they needed. What they needed was a label for each swimmer who swam faster than the “city time”1 in one or more events this year. The labels were to be stuck to the backs of ribbons given out at tomorrow morning’s awards ceremony. (Oh yes, I forgot to mention: there was a looming deadline for these labels.)
What we had was a printed list of all the swimmers who were to get ribbons and the events in which they’d made city times. What we needed was to get that information printed on a set of Avery 5160 labels. Some division of labor was in order.
The list went to our daughter, the fastest non-professional typist I know. I asked her to retype the list of names and events into a text file with a simple format and email it to me. Yes, we could have transferred the file over the network here in the house, but it’s usually fastest to use the tools you’re most familiar with.
While she was typing, I pulled up my ancient Perl code for printing file folder labels and started modifying it. That script was written for Avery 5161 label sheets, which have two columns of ten labels each. The 5160 sheets have three columns of ten labels each, so the necessary changes were obvious:
- The logic needs to expand to accommodate three horizontal positions instead of two.
- The various margins need to be adjusted to reflect the narrower labels.
These changes went smoothly, and after a few syntax errors—programming in Python has gotten me out of the habit of ending statements with a semicolon—the script was up and running. It takes a text file that looks like this:2
#Irene Hartnett|2010
50 Free, 50 Back, 50 Breast
50 Fly, 100 IM
#Rosalina Reial|2010
50 Fly
#Darrin Schrick|2010
50 Back, 50 Fly
#Douglas Dunnam|2010
50 Free, 50 Back, 50 Breast
50 Fly, 100 IM
#Carmina Jaworsky|2010
50 Fly
#Elliott Oland|2010
50 Free, 50 Back, 50 Breast
50 Fly, 100 IM
#Salena Angel|2010
25 Back
#Elroy Tigert|2010
50 Free
#Robin Turiano|2010
50 Back
#Jolyn Mcclerkin|2010
50 Free, 50 Breast
50 Fly, 100 IM
#James Wolf|2010
50 Free, 50 Back, 50 Breast
50 Fly, 100 IM
#Renea Addison|2010
50 Free, 50 Back, 50 Breast
50 Fly, 100 IM
#Jesse Bautista|2010
25 Free, 50 Free, 25 Back
25 Breast, 25 Fly
#Gail Tanous|2010
50 Free, 50 Breast, 50 Fly
#Miguel Loarca|2010
25 Free, 50 Free, 25 Back
25 Breast, 25 Fly
and generates a PDF that looks like this:

I didn’t ask my daughter to type in the hash marks, vertical bars, or the year; I added those to the file with a couple of search-and-replace commands. I used the same formatting rules as my file folder label program:
- Blank lines separate labels.
- Lines that start with a hash (#) are in bold.
- A vertical bar acts as a tab to a right-justified tab stop at the right margin.
- All other lines are centered.
Here’s the script that does it:
1: #!/usr/bin/perl
2:
3: use Getopt::Std;
4:
5: # Usage/help message.
6: $usage = <<USAGE;
7: Usage: prlabels [options] [filename]
8: Print file folder labels on Avery 5160 sheets
9:
10: -r m : start at row m (range: 1..10; default: 1)
11: -c n : start at column n (range 1..3; default: 1)
12: -h : print this message
13:
14: If no filename is given, use STDIN. A label entry is a plain text
15: series of non-blank lines. Blank lines separate entries.
16:
17: The first line of an entry is special. If it starts with a #, then it's
18: considered a header line. Everything in the header line up to the | is
19: printed flush left in bold and everything after the | is printed flush
20: right in bold. Subsequent lines are printed centered in normal weight.
21: If the first line of an entry doesn't start with #, it uses the header
22: of the previous entry.
23: USAGE
24:
25: # Set up geometry constants for Avery 5160.
26: $topmargin = 0.60;
27: $poleft = 0.4;
28: $pomiddle = 3.20;
29: $poright = 5.95;
30: $lheight = 1;
31:
32: # get starting point from command line if present
33: getopts('hr:c:', \%opt);
34: die $usage if ($opt{h});
35:
36: $row = int($opt{r}) || 1; # chop off any fractional parts and
37: $col = int($opt{c}) || 1;
38:
39: # Bail out if position options are out of bounds
40: die $usage unless (($row >= 1 and $row <= 10) and
41: ($col >= 1 and $col <= 3));
42:
43: # Set initial horizontal and vertical positions.
44: if ($col == 1) {
45: $po = $poleft;
46: } elsif ($col == 2) {
47: $po = $pomiddle;
48: } else {
49: $po = $poright;
50: }
51: $sp = ($topmargin + ($row - 1)*$lheight);
52:
53: # Pipe output through groff and ps2pdf.
54: open OUT, "| groff | ps2pdf -";
55: # open OUT, "> labels.rf"; # for debugging
56: select OUT;
57:
58: # Set up document.
59: print <<SETUP;
60: .ps 11
61: .vs 15
62: .ll 2.20i
63: .ta 2.20iR
64:
65: SETUP
66:
67: # The troff code for formatting a single entry, with placeholders for
68: # positioning on the page. The magic numbers embedded in the formatting
69: # commands make the layout look nice.
70: $label = <<ENTRY;
71: .sp |%.2fi
72: .po %.2fi
73: .ft HB
74: %s
75: .ft H
76: .ce 3
77: %s
78: .ce 0
79: ENTRY
80:
81: # Slurp all the input into an array of entries.
82: $/ = "";
83: @entries = <>;
84:
85: $bp = 0; # we don't want to start with a page break
86:
87: foreach $body (@entries) {
88: # Parse and transform the header and body.
89: if ($body =~ /^#/) { # it's a header line
90: ($header, $body) = split(/\n/, $body, 2);
91: $header = substr($header, 1);
92: $header =~ s/\|/\t/;
93: }
94: $body =~ s/\s+$//;
95:
96: # Break page if we ran off the end.
97: if ($bp) {
98: print "\n.bp\n"; # issue the page break command
99: $bp = 0; # reset flag
100: }
101:
102: # Print the label.
103: printf $label, $sp, $po, $header, $body;
104:
105: # Now we set up for the next entry.
106: if ($col == 1){ # last entry was in the left column
107: $col = 2; # so the next will be in
108: $po = $pomiddle; # the middle column
109: } elsif ($col == 2) { # last entry was in the middle column
110: $col = 3; # so the next will be in
111: $po = $poright; # the right column
112: } else { # last was in the right column
113: $col = 1; # so the next will be in
114: $po = $poleft; # the left column
115: $row++; # of the next row
116: if ($row > 10) { # we're at the end of the page
117: $bp = 1; # page break flag
118: $row = 1; # new page starts at top row
119: }
120: $sp = ($topmargin + ($row - 1)*$lheight);
121: }
122: }
I fed the input file into this script and sent the resulting PDF to the printer. Lots of labels in very little time.
More important, we now have a tool for doing this again and again. Maybe next year we’ll be able to save a step by getting the swim team’s program to spit out a text file instead of a printed list that has to be retyped.
-
This is a somewhat arbitrary “wheat from chaff” separator. It’s called a city time because those who beat it get to compete in a city-wide meet at the end of the season. ↩
-
By the way, if you ever need to create some fake names, I suggest The Name Generator. ↩
Absolute elsewhere
July 9th, 2010 at 7:52 pm
Way back when the Earth was young and Merlin Mann regularly updated 43 folders, he wrote this:
As the administrative grunt for a Netflix household, I’m responsible for hoofing it down to the big blue mailbox every couple days. To make sure I don’t forget anything on my epic, two-block trek to 23rd Avenue, I hang all our outgoing mail on a large binder clip on the front door. When I somehow have managed to miss that subtle clue, I hang the mail off the actual dead bolt; I literally can’t leave the house without being reminded that Napoleon Dynamite needs to go home now. (“Dang!”)
Merlin’s actually written several posts on the weird and often blunt ways we have to remind ourselves of what needs doing, but it was the imagery of this example, with the mail clipped together and hanging on the door, that came to mind as I went on a similar reminder odyssey.
As I’ve mentioned before, I have a prescription for Simvastatin: one pill per day. When I started the prescription, I decided the best time to take the pill was in the morning, so I kept the bottle on the bathroom counter next to my sink, where I couldn’t help but see it every morning.
This worked pretty well. I almost always took the pill. But quite often I’d find myself at my sink before going to bed, brushing my teeth and looking at the bottle of pills. Did I take one this morning? I think I remember taking one, but maybe that was yesterday.
Usually, the problem was not remembering to take the pill—it was remembering whether I had remembered to take the pill.
So I printed up a set of little monthly calendars using pcal1 and taped the current month to the mirror behind my sink. The plan was to cross out the day when I took the pill. That way, I’d know that I had remembered.
This worked really well…except when I forgot to mark the calender. On those days, the problem was remembering to make the reminder that would help me remember that I had remembered to take my pill.
My solution has been to use one particular pen to mark the calendar and to keep that pen on top of the pill bottle. I can’t open the pill bottle without picking up the pen, and picking up the pen is the perfect reminder to mark the calendar. So far, this has worked.

But is this the ultimate solution? I wouldn’t count on it.
-
No, not PCalc, but I can understand your confusion. ↩
TaskPaper update
June 29th, 2010 at 11:24 am
A couple of months ago, I complained about TaskPaper’s handling of the user’s password when syncing. The problem was that TaskPaper wouldn’t allow me to paste my Google ID from 1Password into the password field of the dialog box.

I later changed my Google password from a 1Password-generated random string to something I could remember and type in directly.
With the latest version of TaskPaper for iPhone, this bug is fixed. It’s still a hassle that TaskPaper can’t just remember the password itself, but at least there’s now a way to login with credentials stored in 1Password.
(I should mention here that TaskPaper doesn’t require you to log in every time you need to sync. I suspect the login requirement is coming from Google; it’s similar to the way GMail asks for your password every few weeks.)
The future of TaskPaper syncing looks bright. In this blog post Jesse Grosjean describes not just a new note-taking product for iOS (one that might give Simplenote a run for its money), but also an upcoming switchover of all his apps from his bespoke syncing system to syncing via Dropbox. This will be an enormous improvement on both the iPhone and the Mac; we’ll get not just automatic syncing, but also the ability to revert to earlier versions.
I’m kind of surprised how thoroughly TaskPaper has won me over. Its simplicity is a big plus; because it’s just a little more structured than a blank sheet of paper, I can adapt it to many purposes. That its native format is easily converted to Markdown—and, therefore, to HTML—has allowed me to a get great-looking printed task lists to slip into my planner.1
Ack
June 18th, 2010 at 11:03 pm
At the end of last week’s post about trusses, I mentioned that I had found an old photo of a roof truss by greping for the word “truss” in a folder of project reports on my work computer. I should not have grep’d; I should have ack’d.
Ack is a Perl script that’s meant to be a replacement of and improvement on the venerable Unix search tool grep. I’d read about ack quite a while ago—there’s a TextMate bundle called Ack in Project that’s well thought of—but kept forgetting to install it. Now that I have it installed, I see some obvious improvements.
- Speed. I haven’t done any formal timing tests, but
ackis clearly faster. - Presentation.
Ackshows its results in a much clearer format. I must say, though, that I’m not thrilled with some of the colors it uses in the output—maybe those can be changed in the.ackrcconfiguration file. - Better regular expressions. And by that I mean Perl’s regular expressions.
- Subdirectory search by default. I usually want my searches to walk down into the subdirectories but am always forgetting to turn on
grep’s recursive search option (-r), so having recursive search as the default is a big help. - No searching in revision control subdirectories. For me, that means the default is to avoid my
.gitfolders, butackalso knows to avoid folders named.svn,.hg,.bzr,CVS,RCS, and so on.
A couple of ack’s defaults are not to my liking.
- It doesn’t search in files that it considers “text” files. Since a lot of my searching is in just these kinds of files—mostly Markdown—this default works against me.
- It’s searches are case-sensitive. This is no different than
grep’s default, but that doesn’t mean I like having to always use the-iswitch.
These defaults betray ack’s origins as a programmer’s tool. Programmers usually want to search through their source code and avoid extraneous hits in, say, the documentation files. And most of today’s languages are case-sensitive, so it only makes sense for the default searches to be so as well.
Luckily for me, ack’s defaults can be changed through an .ackrc file in my home directory. Right now my .ackrc has just two lines:
--type=text
--smart-case
The first line tells ack to go ahead and search the text files. The second tells it to do case-insensitive searches unless the search term has a capital letter. The format of the .ackrc is simple: each line represents a command line switch and is written just as if you were adding it to an ack command—there’s no special dotfile syntax.
I’ll probably add more lines to my .ackrc as I learn more about it. For now, I’m just wondering why I didn’t switch from grep earlier.





