Archive for January, 2007

ROT13 in Python

Wednesday, January 31st, 2007

Patr1ck has posted instructions on performing ROT13 in Ruby. Colin has responded with the Perl version. Here's the Python version.

import codecs rot13ed_data = codecs.getencoder('rot13')(data_to_rot13)[0]

That is all.

Free stuff on iTunes: iPod game

Tuesday, January 30th, 2007

5G iPod users, listen up: Apple is offering a demo of its Vortex game.

More interesting Technorati search results

Thursday, January 25th, 2007

A different brand of weird this time:

The Technorati search for my blog, showing both “20 links from 12 blogs” and “Sorry! No posts link to that URL yet.”.

Make up your mind?

My blog has been renamed, unbeknownst to me

Wednesday, January 24th, 2007

Screenshot of Technorati:

Technorati search for my blog, showing its title as “500 Internal Server Error” as opposed to “Domain of the Bored”.

Hmmm…

UPDATE 2007-01-25 00:08 PST: I just checked. It's fixed now.

SpotlightFS quickie

Wednesday, January 24th, 2007

Just to save you the time of trying this yourself, the answer is yes, you can use arbitrary queries with SpotlightFS.

mkdir 'kMDItemTitle = "A Day In The Life"'             %/Volumes/SpotlightFS(0)
___
ls 'kMDItemTitle = "A Day In The Life"'                %/Volumes/SpotlightFS(0)
:Users:prh:Music:iTunes:iTunes Music:The Beatles:Love:23 A Day In The Life.m4a@
:Users:prh:Music:iTunes:iTunes Music:The Beatles:Sgt. Pepper's Lonely Hearts Club Band:13 A Day In The Life.m4a@
:Users:prh:Music:iTunes:iTunes Music:The Beatles:The Beatles 1967–1970:1-06 A Day In The Life.m4a@
Icon?*
___
mkdir 'kMDItemTitle = "A Day In The Life" && kMDItemAlbum = "Sgt.*"'
___
ls 'kMDItemTitle = "A Day In The Life" && kMDItemAlbum = "Sgt.*"'   
:Users:prh:Music:iTunes:iTunes Music:The Beatles:Sgt. Pepper's Lonely Hearts Club Band:13 A Day In The Life.m4a@
Icon?*

I also would suggest defining a shell function that does the mkdir-ls-rmdir dance in one step.

How to quickly change several lines at once

Wednesday, January 24th, 2007

It's 130.87 K, and just over four seconds, and will play under any version of QuickTime.

It shows me using vim to replace an old variable name (“fullScreenOverlayWindow”) with a new one (“panel”). The sequence of events:

  1. I move into position.
  2. I enter Visual Block mode by pressing ctrl-v.
  3. I jump to the end of the current word with e.
  4. I jump 7 lines down with 7j. This gives me a total of 8 lines selected (including the one I started on).
  5. I enter Change mode by pressing c.
  6. I type my shiny new variable name. (Notice that it only displays on the line where my cursor is; all the others stay blank for the duration of my Change.)
  7. I exit Change mode by pressing escape.
  8. I wait a beat.
  9. Voila!

Cocoa text editors like TextEdit and Xcode have rectangular selection, but not rectangular editing; you will obliterate all the lines, but only insert your new text into the first line. This is not useful.

Please do not use %x for pointers

Tuesday, January 23rd, 2007

I often see code like this:

printf("Address of foo: %x\n", &foo);

The intent here is to print the address in hexadecimal format. Good plan; bad execution.

First, here's one possible output:

Address of foo: 123456

Is this decimal (%u), hex (%x), or octal (%o)? I can't tell from the output; for all I know, the person who wrote that line is a real newbie and used %i or %d. (That would be even worse for really high addresses, as they will then be formatted as negative numbers.)

So the author changes the line to:

printf("Address of foo: 0x%x\n", &foo);

Now the output has the 0x prefix, making clear that it's hex, but there's still a bug here. The bug is that %x is not the correct formatter for pointers.

The type expected by %x is unsigned int. For the past decade or so, this has not been a problem because on all PCs, including Macs, the size of a pointer has been equal to the size of an int.

But over the past couple years and the next couple years, there's a transition underway to LP64, wherein long ints and pointers are 64-bit. Plain ints won't be; they'll still be 32 bits. This means that you'll get funky results, and possibly crashes (with %s, %n, and %@), if you're using %x for your pointers.

You could use %lx (unsigned long int), but it's still the wrong type. There is a formatter specifically for pointers: %p. It even provides the “0x” prefix for you.

printf("Address of foo: %p\n", &foo);

Address of foo: 0x1e240

So, in order to make your code both more stable and more future-proof, please use %p, not %x or %lx, for formatting your pointers.

Why you should use #pragma mark

Saturday, January 20th, 2007

It's a minute and a half, and it's 200 K, and you'll need QuickTime 7 to watch it.


pragma mark at work in Xcode's editor.

Useful as #pragma mark is, I wouldn't recommend it for cross-platform code. GCC's documentation of #pragma mark seems to suggest that it's Darwin-only.

Weekly Cocoa app challenge #3 solution

Thursday, January 18th, 2007

HERE THERE BE SPOILERS. If you intend to participate in the challenge, read no further because my solution follows.

DudeMenu-PRH-1.0-source.tbz

Source and executable are both included. It took me about an hour and 19 minutes to complete. Source-control-wise, this is revision 7 of its SVN repository.

Things used therein:

  • Acquisition of and intake from a plist
  • Definition of custom symbols for the auto-documentation of dictionary keys
  • Dynamically building a menu
  • Validation of input, both with alerts and assertions
  • Many, many comments
  • String localization
  • String localization using a custom table
  • Primitive token-replacement (primitive because there is no provision for escaping)
  • Inspection of the current locale
  • A status item
  • Menu items representing other objects
  • An app without a main menu (as in NSApp's mainMenu)
  • LSUIElement
  • A menu item that messages NSApp directly
  • Retrieval of images from the search path by name
  • Walking arrays
  • Auto-incrementation

A Core-Image-less Image Unit

Wednesday, January 17th, 2007

Can you imagine an Image Unit that didn't actually use Core Image?

I just wrote one.

Well, OK, so I did use CIFilter and CIImage — you can't get away without those. But I did not use a CIKernel. That's right: This simple filter does its work without a kernel.

For the uninitiated, a kernel is what QuartzCore compiles to either a pixel shader or a series of vector (AltiVec or SSE) instructions. All Image Units (as far as I know) use one — not only because it's faster than any other way, but because that's all you see in the documentation.

But I was curious. Could an Image Unit be written that didn't use a kernel? I saw nothing to prevent it, and indeed, it does work just fine.

The image unit that I wrote simply scales the image by a multiplier, using AppKit. I call it the AppKit-scaling Image Unit. Feel free to try it out or peek at the source code; my usual BSD license applies.

Obviously, this Image Unit shouldn't require a Core Image-capable GPU.

Why the iPhone is closed to developers

Sunday, January 14th, 2007

In MacBreak episode 56, Merlin Mann talks to (among other people) Dan Moren of MacUser, who I think inadvertently states the reason why Apple has not released an SDK for the iPhone. From 1m25s:

Merlin Mann: And what I wanna know from you is, if you had a software development kit today, and you could walk home and, I don't know, go learn Xcode, and make the application of your choice, what would you put on an iPhone?

Dan Moren: I think that the most compelling thing is to take on Cisco with their iPhone, with the VoIP capabilities? I mean, I'd like to see some Skype on the iPhone. If you got the WiFi in there, you got some, you know, 3G or something, but for those of us who don't wanna switch to Cingular, you know, and you still want some voice capabilities, why not be able to develop a Skype, put in some voice communication application in there, … it'd be great to have some kind of AIM functionality too.

I think that's it. The iPhone is closed because if it wasn't, you'd be able to use the iPhone without continually paying for Cingular phone calls or SMS (by using Skype and $IM_SERVICE instead).

That means that if they do make an SDK, it won't be available until at least two years from June.

The first working version of Negative Turing Test

Friday, January 12th, 2007

I just committed revision 44 of Negative Turing Test, and am running it now on this blog (and I've turned off the Comment Authorization plug-in, which is what used to email you when you commented, prompting you to approve your own comment). It now correctly blocks spam and allows ham; these being the minimum requirements, I call r44 the first working version of NTT. Feel free to try it out on your own blog — or on mine — and report any problems (preferably using the Google Code issue tracker, but email's fine too).

I'm not quite done with it. My next step is to add an option for it to outright delete spam instead of simply stamping it “spam” and saving it for some plug-in that I don't use to study. I'm confident that I will never see a false positive, and if I ever see a false negative, I can simply change the problem that it poses in order to avoid future false negatives.

The plug-in comes with no default challenge: All fields are empty. This means that if you want to block spam with it, you'll need to think of a challenge to put in there. Please don't borrow mine, as I put in no default for a reason: If there's a default (or a really popular challenge), the spammers will pre-program their bots with the correct response and the plug-in will be defeated (and all NTT users who've used that challenge will have to change it, and/or will send me a bunch of email). I recommend searching a book of easy jokes or logic riddles.

The Cocoa Memory Management Regular Expression

Thursday, January 11th, 2007

Any method whose selector matches this regular expression gives you a reference to its return value, which you must later release.

/^retain$|^(alloc|new)|[cC]opy/

As an added extra bonus, here's the CF equivalent.

/Create|Copy|Retain/

Carbon adds several exceptions, such as GetControlData (which returns a reference to CF things like the string value of a text field). Use Cocoa instead and your memory-management life will be simpler. ;)

How the Preferences have grown

Wednesday, January 10th, 2007

BBEdit Lite 6.1.2 vs TextWrangler 2.2:

BBEdit Lite's Preferences window is 461 by 348 points. TextWrangler's Preferences window is 750 by 572 points.

An iPhone SDK guess

Wednesday, January 10th, 2007

Maybe it will come with Xcode 3?

UPDATE 20:55: Never mind.

All known Keynote Bingoes for MWSF 2007

Tuesday, January 9th, 2007

iTunes smart playlists: Recent podcasts

Sunday, January 7th, 2007

This is a response to Daniel Jalkut's iTunes Script: Recent Podcasts.

It is possible to do what he does with scripts with smart playlists. The result requires no user action to update, since the playlists will update automatically. You can even use them to sync recent podcasts to your iPod.

We start with a playlist for all podcasts:

The rules in the “Recent podcasts (played or not)” playlist are: Category is not (empty); Kind does not contain “stream”; Date Added is in the last 3 days.

This smart playlist provides the same function as the script with its kIgnoreAlreadyPlayedPodcasts property set to its default value of false. If you like kIgnoreAlreadyPlayedPodcasts set to true:

The rules in the “Recent podcasts (never played)” playlist are: Playlist is “Podcasts (played or not)”; Play Count is 0.

This playlist derives from the first one, and you can replace the Playlist rule with the three rules of the other playlist if you don't want a played-or-not playlist hanging around.

UPDATE 2007-01-08: Daniel Jalkut pointed out that there's a Podcast criterion that I could have used. I completely missed it. This makes the solution much simpler:

The rules in the “Recent podcasts (never played), Daniel Jalkut edition” playlist are: Podcast is true; Play Count is 0.

How to detect a click

Sunday, January 7th, 2007

If you've ever written something that needs to simulate menu behavior, you've probably noticed that it would be nice to know, portably to future OS versions, how long you should wait before rejecting a mouse-down/mouse-up pair as not a click, as well as how far the user must move the mouse for it to not be a click.

The way to do this is:

EventTime clickTimeout; HISize clickMaxDistance; OSStatus err = HIMouseTrackingGetParameters(kMouseParamsSticky, &clickTimeout, &clickMaxDistance);

There is no Cocoa API for this.

Now, 4% more bingo!

Saturday, January 6th, 2007

I've just posted version 1.0.1 of Keynote Bingo MWSF 2007 Edition. This adds nine strings, and at Patrick Gibson's suggestion, removes one (“Intel Core 2 Duo”, which has already happened).

The added strings, all by me, are:

  • Name of Apple phone: iChat mobile
  • Apple phone is CDMA
  • Apple phone is GSM
  • Apple is now a mobile-phone service provider
  • Apple announces agreement with Cingular
  • Apple announces agreement with Verizon
  • Apple announces agreement with Sprint
  • Apple announces agreement with T-Mobile
  • Lucida Grande replaced by Myriad in Leopard

This brings the count to 186, from 178.

Finding the longest common prefix of an array of strings in Python, part 2

Saturday, January 6th, 2007

In case you found my previous post unsatisfactory, and wanted to know how one would actually implement os.path.commonprefix in Python, here's my answer.

(And yes, I started writing this before I found os.path.commonprefix in the documentation. ☺)

This is in response to wootest's post on finding the longest common prefix of an array of strings in Ruby.

files = ['/foo/bar/xyzzy_one', '/foo/bar/xyzzy_two', '/foo/bar/xyzzy_three/four']

No change here. For relative paths, use os.path.join(os.getcwd(), path) to convert each path to an absolute path.

Zeroth, we don't set up an array to hold guesses. We'll extract exactly one prefix, so no array is necessary.

Let's put this whole thing in a function for easy access. (It also makes the code clearer later.)

def longest_common_prefix(*files): "This function returns the longest common prefix of one or more sequences. Raises an exception when zero sequences are provided."

The * means that this function is variadic: You use it with the syntax longest_common_prefix(foo, bar, baz). If you want to pass an array of paths, use the apply syntax: longest_common_prefix(*files) (does that asterisk look familiar?).

The first thing our function should do is assert that we actually have some strings. There are two reasons for this:

  1. My algorithm assumes that there is at least one string.
  2. Trying to get the longest common prefix of no strings is definitely unusual, and should be suspected of being a bug.

assert files, 'Longest common prefix of no strings requested. Such behavior is highly irrational and is not tolerated by this program.'

Next is an if to cover the case of exactly one string. The algorithm would handle this just fine, but inefficiently: One string is its own longest common prefix, so we can simply return that. This if would also cover the case of no strings, but I like having an assertion for that (see #2 above). I'm paranoid that way.

if len(files) == 1: return files[0]

Next we put the filenames in order by length. Having the shortest string first is necessary everywhere you see files[0] mentioned below.

files = [pair[1] for pair in sorted((len(fi), fi) for fi in files)]

This is the “decorate-sort-undecorate” pattern, used in Python because built-in comparison is much faster than a pure-Python comparison function.

Next is the outer loop, in which we start iterating the characters of the first (shortest) string.

for i, comparison_ch in enumerate(files[0]):

The built-in enumerate yields a pair (idx, element) for every item in the sequence.

The inner loop iterates through all our other paths.

for fi in files[1:]:

Get character i of this path.

ch = fi[i]

Compare it to the same character in the shortest path.

if ch != comparison_ch:

If the equality test fails, then we have found the first character that is not in the longest common prefix. This means that i is the length of said prefix. We extract the prefix by slicing the shortest path.

return fi[:i]

The stop index i is not included in the slice, just as with ranges). This gets us the first i characters.

If the equality test succeeds, then we haven't gone past the longest common prefix, and we continue iterating.

Outside the loops (notice that we've gone down several indentation levels):

return ''

If we've run out of loop, there must not be a common prefix; our common prefix, therefore, is the empty string. We return that as a literal.

Now, despite the variable names, that function is suited for arbitrary sequences, and does not properly handle the case of a partial directory name. There are two solutions.

Ugly solution

Split the pathnames into components, pass these lists to longest_common_prefix, and join the output and prepend a '/' to make it back into a path.

'/' + path.join(*longest_common_prefix(*(fi.split('/') for fi in files)))

Robust solution

def longest_common_pathname_prefix(*files): "This function returns the longest common prefix of one or more pathnames. Raises an exception when zero pathnames are provided." lcp = longest_common_prefix(*files) if not lcp.endswith('/'): import os.path lcp = os.path.dirname(lcp) return lcp

Alternatively, I could use:

if lcp[-1:] != '/':

which mirrors wootest's solution. (It would extract characters -1 through the end — i.e. the last character, or the empty string if lcp is also the empty string.) But str.endswith is more Pythonic.

Here's all the code at once, implemented as a complete combination Python module and self-test:

#!/usr/bin/env python def longest_common_prefix(*files): "This function returns the longest common prefix of one or more sequences. Raises an exception when zero sequences are provided." assert files, 'Longest common prefix of no strings requested. Such behavior is highly irrational and is not tolerated by this program.' if len(files) == 1: return files[0] files = [pair[1] for pair in sorted((len(fi), fi) for fi in files)] for i, comparison_ch in enumerate(files[0]): for fi in files[1:]: ch = fi[i] if ch != comparison_ch: return fi[:i] return '' def longest_common_pathname_prefix(*files): "This function returns the longest common prefix of one or more pathnames. Raises an exception when zero pathnames are provided." lcp = longest_common_prefix(*files) if not lcp.endswith('/'): import os.path lcp = os.path.dirname(lcp) return lcp if __name__ == '__main__': files = ['/foo/bar/xyzzy_one', '/foo/bar/xyzzy_two', '/foo/bar/xyzzy_three/four'] print longest_common_prefix(*files) print longest_common_pathname_prefix(*files)