Scripting your scripting
July 23, 2014 at 11:22 PM by Dr. Drang
A couple of days ago, Rob Trew tweeted a link to a shell script he wrote that prints out an emoji clock face based on the time itβs given.
π.sh
Simple bash .sh gets emoji clockface (stdout|clipboard) with specified time (to nearest preceding half hour)
github.com/RobTrew/txtqueβ¦
β ComplexPoint (@ComplexPoint) Jul 22 2014 10:31 AM
It sounded like a fun little programming project and possibly even useful.
I knew immediately that the key feature of my script would be a dictionary that mapped a time written as text, like β8:30,β to the corresponding emoji, π£. The rest of the script would just be input, output, and rounding. Hereβs the script:
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 from sys import argv
5 from time import strftime
6
7 clocks = {'12:00': 'π', '12:30': 'π§', '1:00': 'π', '1:30': 'π',
8 '2:00': 'π', '2:30': 'π', '3:00': 'π', '3:30': 'π',
9 '4:00': 'π', '4:30': 'π', '5:00': 'π', '5:30': 'π ',
10 '6:00': 'π', '6:30': 'π‘', '7:00': 'π', '7:30': 'π’',
11 '8:00': 'π', '8:30': 'π£', '9:00': 'π', '9:30': 'π€',
12 '10:00': 'π', '10:30': 'π₯', '11:00': 'π', '11:30': 'π¦'}
13
14 # Get the argument, which should be a time string, like "8:10."
15 # If there's no argument, use the current time.
16 try:
17 oclock = argv[1]
18 except IndexError:
19 oclock = strftime('%I:%M')
20
21 hour, minute = [ int(x) for x in oclock.split(':') ]
22
23 # Round to the nearest half-hour, because there are no
24 # emoji clockfaces for other times.
25 rminute = int(round(float(minute)/30)*30)
26 if rminute == 60:
27 rminute = 0
28 hour = 1 if hour == 12 else hour + 1
29
30 print clocks['%d:%02d' % (hour, rminute)]
The dictionary is defined in Lines 7β12. By including the encoding line at the top of the script, I was able to include the clock faces directly in the source code instead of messing around with hex character codes.
But the script itself isnβt the point of this post. What I really want to talk about is how I wrote it. I knew what Lines 7β12 were going to look like, and I really didnβt want to type it out. Not only would it be tedious, Iβd almost certainly make an error somewhere. So I used a combination of IPython and BBEditβs search-and-replace tools to build Lines 7β12 step by step.
Step 1 was generating a list of times. I used these commands in an IPython session to print a list of times, one per line:
for h in range(1, 13):
print "'%d:00'\n'%d:30'" % (h, h)
I copied this into a BBEdit window, moved the last two lines, 12:00 and 12:30, to the top, and did this find-and-replace:
This gave me a single line that looked like this,
12:00', '12:30', '1:00', '1:30', '2:00', '2:30', '3:00', '3:30',
'4:00', '4:30', '5:00', '5:30', '6:00', '6:30', '7:00', '7:30',
'8:00', '8:30', '9:00', '9:30', '10:00', '10:30', '11:00', '11:30', '
which Iβve broken up here for ease of reading. I cleaned up the front and rear to turn it into a Python list assignment,
times = ['12:00', '12:30', '1:00', '1:30', '2:00', '2:30', '3:00',
'3:30', '4:00', '4:30', '5:00', '5:30', '6:00', '6:30', '7:00',
'7:30', '8:00', '8:30', '9:00', '9:30', '10:00', '10:30', '11:00', '11:30']
which I entered into my IPython session.
Step 2 was to do a similar thing for the clock faces. I βtypedβ them in, one per line, using the Character Viewer,
and then converted them into a list assignment that I entered into IPython.
faces = ['π', 'π§', 'π', 'π', 'π', 'π', 'π', 'π', 'π',
'π', 'π', 'π ', 'π', 'π‘', 'π', 'π’', 'π', 'π£', 'π', 'π€',
'π', 'π₯', 'π', 'π¦']
Now comes the fun part. With times
and faces
defined, it was easy to generate most of the dictionary assignment statement with
print ", ".join("'%s': '%s'" % (t, f) for t, f in zip(times, faces) )
That gave me this string
'12:00': 'π', '12:30': 'π§', '1:00': 'π', '1:30': 'π',
'2:00': 'π', '2:30': 'π', '3:00': 'π', '3:30': 'π',
'4:00': 'π', '4:30': 'π', '5:00': 'π', '5:30': 'π ',
'6:00': 'π', '6:30': 'π‘', '7:00': 'π', '7:30': 'π’',
'8:00': 'π', '8:30': 'π£', '9:00': 'π', '9:30': 'π€',
'10:00': 'π', '10:30': 'π₯', '11:00': 'π', '11:30': 'π¦'
where, again, Iβve added line breaks here to make it easier to read.
A few quick edits turned that string into
clocks = {'12:00': 'π', '12:30': 'π§', '1:00': 'π', '1:30': 'π',
'2:00': 'π', '2:30': 'π', '3:00': 'π', '3:30': 'π',
'4:00': 'π', '4:30': 'π', '5:00': 'π', '5:30': 'π ',
'6:00': 'π', '6:30': 'π‘', '7:00': 'π', '7:30': 'π’',
'8:00': 'π', '8:30': 'π£', '9:00': 'π', '9:30': 'π€',
'10:00': 'π', '10:30': 'π₯', '11:00': 'π', '11:30': 'π¦'}
which is the nicely formatted dictionary assignment statement you see in Lines 7β12.
Does this seem like more work than just typing it out? It wasnβt. One of the great things about writing scripts frequently is that you become fluent enough to know instinctively how cobble together little bits of code like this. You also learn that some things, like the beginning and end of a list assignment statement, are more efficiently done by typing than by programming, so you donβt waste time trying to script those parts.
Sometimes your first instinct is a little off. I initially wrote the line that built the dictionary string this way:
print ", ".join("'%s': '%s'" % (t, f) for t, f in zip(times, faces) )
Do you see the difference? Thereβs only one space after the comma in the join
ing string instead of two. When I saw the output of this statement, I realized that to get the times lined up nicely Iβd need a mixture of one and two spaces, depending on whether the hour was a one- or two-digit number. Since there are nine one-digit hours and only three two-digit hours, it would be faster to put two spaces everywhere and delete one in front of the two-digit hours. I reran the line with two spaces after the comma and saved myself twelve edits.
Iβm sure there are still better ways to do this, further tricks that would have saved me even more typing. But even though I didnβt save as much time as I might have, I still saved a lot, mainly because I automated all the quotes, colons, and commas that I tend to mistype.
-
It is useful for Rob. He makes links in FoldingText to his timed Reminders and includes the appropriate clock face in the link text. β©