Saturday, December 22, 2007

New blog URL

If you're seeing this, you're looking at my old blog. My new blog is at blog.extracheese.org.

Wednesday, December 19, 2007

Blog woes

Last night, I switched my blog to tumblr; this morning, I switched it back to Blogger. Over the last two days, I've spent a ridiculous amount of time and effort trying to make the switch without breaking any links. I'll spare the details, but it involved a lot of mod_rewrite, a PHP script written by Henrik Nyh that proxies all requests to tumblr, and a huge list of URLs mapping the old Blogger ones to the new tumblr ones.

I found the proxy-with-a-PHP-script thing distasteful, but the lack of decent tag support was the thing that ultimately made me give up. I have a Python-specific feed that gets aggregated by the unofficial planet python, so whatever I switch to needs to be able to generate tag-specific feeds. Tumblr does let you add tags to your posts, but it doesn't seem to actually do anything with them.

So, I'm in search of blogging software. I want something that:

  • Is written in Python or Ruby. Python because I know it very well; Ruby because I want to know it better.
  • Is simple. I'm not interested in anything built in Django or TurboGears or Rails. Preferably, it would be something that runs on my local machine, generating the static files that make up the live blog.
  • Allows custom URLs but has sane defaults. (I.e., no exposed serial integer keys.)
  • Supports tags and can generate subfeeds based on them, as well as human-readable post lists that are filtered by them. (For example, with my current blog you can look at only Python posts if you want to.)
  • Does not involve a database. (Not even SQLite.)
  • Reads the posts out of plain HTML files, which I will write by hand.

I've looked around for something like this, but everything seems to be either big (e.g., Pyblosxom and Typo) or someone's weekend project that never got touched again. I'm sure that the bigger ones are quite good at what they do, but I just want something that takes my plaintext files and generates an appropriate URL structure. It can do it offline or online - I don't care - but it's got to be simple and require no complicated installation or configuration.

So, any ideas, or am I starting a new project? :)

Tuesday, October 09, 2007

Tubes by proxy

On Friday, I am bored and want video games. But no Internet service! How to download new games to Wii?!

Assets:

  • One VX8300 cellular telephone.
  • One Macintosh Book Professional.
  • One Nintendo Wii.

Solution:

  1. Connect VX8300 to MacBook via Bluetooth.
  2. Configure MacBook to use VX8300 as a modem for free. This is an undocumented Verizon feature!
  3. Put MacBook in ad-hoc wireless mode.
  4. Enable internet sharing on MacBook.
  5. Inform Wii of wireless network.
  6. Access Wii Store!

Full data path: Internet -> Verizon Wireless -> CDMA 2000 (1x) -> VX8300 -> Bluetooth -> MacBook -> 802.11g -> Wii.

Operation was a success. Breath of Fire II acquired.

Thursday, September 13, 2007

Undeletable zombie files on OS X

Somehow, one of Bitbacker's system tests occasionally creates an undeletable file. It happens within a test that generates random filenames, then tries to back up and restore them. Unfortunately, there are apparently some character sequences that make HFS+ choke. I've only had this happen twice ever - once on my 17" MacBook Pro, and just now on my brand new 15" MacBook Pro. Here's what the file looks like:

A broken file

It can't be deleted from Finder:

Trying to delete a broken file from Finder

It can't be deleted from the console:

Trying to delete a broken file from the console

And it can't even be "ls"ed:

Trying to ls a broken file

If I manually move the containing folder to the trash and try to empty it, I get this, which is even more ridiculous than the rest:

Trying to delete a broken file from the trash

I've googled for undeletable files on OS X, as well as for the specific error messages Finder throws, and tried every suggested fix I found. And, of course, I've tried to delete the file from within Python:

>>> os.unlink(os.listdir('.')[0])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  OSError: [Errno 2] No such file or directory:
  '\\xe4\\xad\\xa9\\xe1\\x84\\x84\\xe1\\x85\\xaf\\[...truncated]'

Dear Apple: please replace HFS+. Seriously, it sucks.

Friday, September 07, 2007

Globals and cargo culting

Matt Wilson wants a module's functions to log to one logger, but he can't change their interface and he doesn't want to use a global variable. This is the kind of thing that decorators are very good at, for better or worse. Here's a decorator that will do the job:

def with_logger(fn):
    def new_fn(*args, **kwargs):
        logger = get_singleton_logger()
        return fn(logger=logger, *args, **kwargs)
    return new_fn

And here's how to use it to define a function that gets a logger instance without the caller passing it in:

>>> @with_logger
... def add(x, y, logger):
...     logger.warning('x + y = %i' % (x + y))
...
>>> add(1, 2)
WARNING:foo:x + y = 3

This seems to be exactly what Matt was looking for: there are no globals, a logger gets injected, and the function's interface hasn't changed. But is it a good idea?

No, it's a ridiculous idea! It's just a reimplementation of global variables! All I've done is come up with a complicated scheme for injecting a single logger instance into every function in the module. But that's exactly what a global does! This is something that programmers have done over and over again in the name of OO. Everyone wants globals, but they go to great lengths to hide it.

Here are three possible ways to solve the original logger problem:

  1. Use a singleton, and have each function retrieve the instance that way.
  2. Use a decorator that injects the instance into the function's argument list each time. In with_logger, I combined this with a singleton.
  3. Just use a global.

If you choose (1) or (2), the joke's on you. You're still using a global, but now you have two problems: global state and a complicated method for managing it. There's no need for that, because we already have a simple method for injecting instances into a module's functions: globals!

Of course, sometimes singletons or decorators like with_logging make sense, but only when they actually do something. If all they do is allow multiple functions to access a single long-living instance, they're dangerous and needlessly complex. In almost all cases, singleton and related techniques are nothing more than cargo cult programming.

Monday, July 23, 2007

When JSON isn't JSON

JSON is so simple that you can specify it on an index card, but we still can't get it right. For example, here's what happens when simplejson and python-cjson talk about slashes:

# simplejson correctly decodes cjson's data
>>> print simplejson.loads(cjson.encode('/'))
/

# cjson fails to decode simplejson's data
>>> print cjson.decode(simplejson.dumps('/'))
\/

In this case, the problem is that cjson doesn't handle backslashes correctly. There are two ways to say "/" in JSON: "/" and "\/". When encoding, simplejson always escapes slashes, but cjson never does:

>>> print simplejson.dumps('/')
"\/"
>>> print cjson.encode('/')
"/"

The reverse is also true: simplejson knows how to decode "\/", but cjson decodes it incorrectly:

>>> print simplejson.loads('"\/"')
/
>>> print cjson.decode('"\/"')
\/

So there you go: simplejson and cjson don't interoperate. This bit me when I tried to move BitBacker from simplejson to cjson for performance reasons. The live alpha server had a few thousand records encoded with simplejson, all of which included slashes. When I switched to cjson, everything broke because every "/foo/bar" entry in the database came back as "\/foo\/bar".

As far as I'm concerned, this problem with JSON is actually an argument for simple data formats like JSON. If we can't get full interoperability between something as stupidly simple as JSON, how did anyone ever expect WS-* to work?

Sunday, May 27, 2007

Adobe, master of the painful install

After using Macs for about two years, I've become accustomed to the painless installation process there: open the dmg, drag the app to the Applications folder, and you're done. Unfortunately, I recently had to install Acrobat Reader to electronically sign some insurance documents. They have managed to come up with a downright pathological installation process:

  1. Download AdbeRdr80_DLM_en_US_i386.dmg.
  2. Open the dmg.
  3. DownloadReader.pkg is inside; open it.
  4. Click through some stuff.
  5. Wait for the "Adobe Reader Download Manager" to download another 23 MB dmg.
  6. The "Download Manager" has silently quit (what?), but the dmg is now mounted. Open "Adobe Reader 8 Installer.app" and wait for it to finish.
  7. The "Installer" has silently quit (what?), but it must be done, because Acrobat is now running.

This is amazing. When I saw the very first pkg file, I was annoyed: I hate installers and they're rare in the Mac world. As I kept installing and installing, it was almost surreal. In the above list, there are three times where something is being installed. Working back from Acrobat itself,

  1. "Adobe Reader 8 Installer.app" installs "Adobe reader.app", so it's the installer.
  2. The "Download Manager" installs that, so it's the installer installer.
  3. DownloadReader.pkg installed that, so it's the installer installer installer.

So, while most Mac programs have shed the archaic notion of an installer altogether, Adobe Acrobat Reader has a program that installs a program that installs a program that installs the software you actually need.