JavaScript Templates

While playing around with autotest-style QUnit, I needed something to work on that actually needed some tests. Naturally, I settled on the same problem that they did in the video: Given a string with $-placeholders, replace the placeholders with values from a context object. Here's the code I came up with:

var template = (function(){
    return {
        render: function(source, context){
            return source.replace(/\$\w+/g, function(match){
                var key = match.substring(1);
                return context[key] || "";
            }) ;
        }
    };
})();

Dead simple, right? Four actual lines of code and a bunch of setup to put it in a fake module. Here are the tests:

$(function(){
    function replace(source, expected, context, msg){
        equals(template.render(source, context), expected, msg);
    }

    module("template.render");
    test("with no tokens", function() {
        replace("", "", {},
            "returns blank string if the given string is blank"
        );

        replace("hello, world", "hello, world", {},
            "returns a non-blank string unchanged"
        );
    });

    test("with one token", function(){
        replace("$name", "", {},
            "replaces token with empty string if no value in context"
        );

        replace("$name", "Aaron", {name: "Aaron"},
            "replaces token with value from the context"
        );

        replace("Hello $name!", "Hello Aaron!", {name: "Aaron"},
            "leaves the rest of the string unchanged"
        );
    });

    test("with two tokens", function(){
        replace(
            "Hello $name! It is a $attitude day.",
            "Hello Aaron! It is a wonderful day.",
            {name: "Aaron", attitude: "wonderful"},
            "replaces each token with the value from the context"
        );

        replace(
            "Hello $name! It is a $attitude day.",
            "Hello Aaron! It is a  day.",
            {name: "Aaron"},
            "replaces each token without a value in the context with the blank string"
        );

        replace("$me$you", "AaronJohn", {me: "Aaron", you: "John"},
            "replaces adjacent tokens with their respective values"
        );
    });
});

Kind of verbose, but they're actually kind of interesting. They're written in a BDD style, or as much of a BDD style as QUnit will allow. You can easily imagine a tool that extracts:

template.render
    with no tokens
        - returns blank string if the given string is blank
        - returns a non-blank string unchanged

... from tests like these. Indeed, such tools exists for RSpec and there is a BDD JavaScript testing framework, imaginatively named: JSpec, which I expect provides similar tools for it's tests.

It was an interesting experience to work my way through the tests one at a time, implementing that simplest thing that could possibly work. Though, It is kind of discouraging that the test suite is so much longer than the implementation, especially considering this code is so easily tested.

Autotesting QUnit

I was watching a video on performance code kata and I was intrigued by the use of autotest. Basically, it's just a tool that watches the filesystem for changes and re-runs your test-suite when stuff changes. I thought it was a neat idea and wanted to try it out, but I don't really write any Ruby. Instead I turned to the world's most popular dynamic language: JavaScript. I've used QUnit to do some test driven development in JavScript, so tried the simplest thing that could possibly work, a meta-refresh:

<meta http-equiv="refresh" content="5"/>

... and that kind of worked. It was kind of cool, but the constant refreshing gets in the way of of using the JavaScript console to poke at things. Which, when you're writing JavaScript, is kind of an important thing to do. So... meta-refresh with a pause button?

$(function(){
    function startPaused(){
        return location.hash.indexOf("autotest=true") == -1;
    }

    function enable(){
        button.removeAttr("disabled");
        $("body").unbind("mousemove", enable);
    }

    var paused = startPaused();
    var button = $('<input type="button" value="{title}" id="auto-refresh">'.replace("{title}",
        (paused ? "autotest" : "pause")
    ));
    $("body").prepend(button);

    button.click(function(){
        paused = !paused;
        button.attr("value", (paused ? "resume" : "pause"));
        button.attr("disabled", "disabled");

        if(paused){
            location.hash = "";
        }

        $("body").mousemove(enable);
    });


    setInterval(function(){
        if(!paused){
            location.hash = "#autotest=true"
            location.reload(true);
        }
    }, 5000);
});
#auto-refresh {
    float: right;
    background-color: #D2E0E6;
    border: 2px solid #2B81AF;
    color: #2B81AF;
    margin-right: 1em;
    margin-top: 1em;
    border-radius: 5px;
}

#auto-refresh[disabled] {
    opacity: 0.75;
}

There you go. Thirty-odd lines to implement meta-refresh a pause button sigh.

It is actually pretty neat. If I used QUnit more and was in a better position to judge the actually usefulness of such a feature I might try hacking this into core QUnit and seeing if I could get it merged upstream. I mean, there are entire projects built around adding this kind of functionality to other tools, and it's so easy to do in a browser. I'm not planning on it though, because I've only actually used QUnit for fun-zies, and can't really judge how useful this kind of functionality would be on an actual test suite.

May I Bring Your Attention To...

One of the cool things I ended up implementing was Chrome-style disable-enable behaviour for the pause/resume button. Basically, as part of the click handler, it disabled the button and attached a mousemove event handler on the body. The handler then enables the button and unbinds itself when a mousemove event fires. The cool part is that while the button is disabled and your cursor is over it, moving your mouse doesn't fire any mousemove events, but the second your cursor leaves the button, a mousemove event fires and the button gets enabled. You get to safely reuse the pause/resume button without worrying about double clicks doing the wrong thing. Pretty spiffy, huh?

Grayvatar 2.0

In my post about Grayvatar, I warned everybody away from actually using it for anything as it was a complete toy. The major reason I cited was that it did not make any attempt to respect cache headers. Enter Grayvatar 2.0, which is slightly less of a toy as it, at least, makes a half-hearted attempt to respect cache headers.

The key addition was httplib2, which maintains it's own cache, so that we're not hitting Gravatar's servers for every single request we receive. The other big change was to copy cache headers from the client to the server and vice-versa. This means that we'll pass a long the cache-control: max-age=300 that we get from Gravatar or the cache-control: max-age=0 we get from the client. All of this adds about 20 lines on top of the existing implementation. You still shouldn't use it for anything, it's still a toy, but at least it's slightly less naive one.

import StringIO
import tempfile
import urllib, urllib2

import cherrypy
import httplib2
import Image

from paver.easy import path

URL = "http://www.gravatar.com/avatar/%s?%s"
cache_headers = ("cache-control",)


class Grayvatar(object):
    def __init__(self):
        tmp_dir = tempfile.mkdtemp(".cache")
        self.http = http = httplib2.Http(tmp_dir)

    @cherrypy.expose
    def default(self, *bits, **kwargs):
        if bits:
            cherrypy.response.headers["Content-Type"] = "image/png"
            url = URL % (bits[0], urllib.urlencode(kwargs))
            headers, img = self.get_img(url)

            for header in cache_headers:
                if header in headers:
                    cherrypy.response.headers[header] = headers[header]

            return img_as_str(img.convert("L"))

        pwd = path(__file__).abspath().parent
        return (pwd / "how_it_works.html").text()

    def get_img(self, url):
        headers, body = self.http.request(url,
            headers = project(cherrypy.request.headers, cache_headers)
        )
        return headers, Image.open(StringIO.StringIO(body))


def project(d, keys):
    return dict((k, d[k]) for k in keys if k in d)

def img_as_str(img):
    sock = StringIO.StringIO()
    img.save(sock, "png")
    return sock.getvalue()

if __name__ == "__main__":
    cherrypy.quickstart(Grayvatar())

die Frequenz der Blog

Post Frequency by Month

/blog/entries/2010/12/07/die_frequenz_der_blog/by_month-6c41c7b46f47.min.png

This nicely illustrates that my blogging attention span is kind of bursty. There is a cluster of posts during the first half of 2008, another one in the first half of 2009 and then a giant dead spot from the middle of 2009 until last month. It's actually kind of funny that pretty much all of my blog posts have been written in tree bursts of attention separated by big swaths of neglect.

Histogram of Post Frequency

/blog/entries/2010/12/07/die_frequenz_der_blog/by_count-103df219d0cf.min.png

... and the runaway winner is zero! Mostly explained by the fact that there have been streches of five or six months where I haven't posted a single thing (like, for instance, the first half of 2010). November 2010 deserves special mention as it is a crazy outlier on the the complete other end of the scale with four more posts then then next most prolific month.

Making Tufte Proud

I used flot to generate the histograms and while it does a good job at producing nice looking graphs it has the annoying habit of adding lots of stuff to your plots which reduces the percentage of pixels in your plot that convey data (data ink). It took a surprising amount of fiddling to turn off all of the exta stuff, most of which was accomplished by setting the color of the grid lines and frame to white:

$.plot($("#plot"), [data], {
    bars: {show: true},
    grid: {
        backgroundColor: "#ffffff",
        borderColor: "#ffffff",
        tickColor: "#ffffff"
    },
    yaxis: { ticks: 0 }
});

Grayvatar

http://aaron.maenpaa.ca/grayvatar/634329486bd326c395c8b3539c035139?s=196&d=identicon&r=PG

The week before last I got to spend some time working on a prototype as part of a sekrit project at work. In order to give the prototype the air of not-quite-done, I did the whole thing in black and white. This worked great up until I added in Identicons to identify users, which added a bunch of brightly colored triangles and ruined the whole black-and-white aesthetic. I eventually ended up pulling a bunch of Identicons from Gravatar with wget and desaturated them with ImageMagik. It worked, but it was kind of gross.

Teh Codez

It occurred to me that it should be pretty straightforward to build a web app that implemented the Gravatar API and desaturate real Gravatars, so I took a crack at it:

import StringIO
import urllib, urllib2

import cherrypy
import Image

from paver.easy import path

URL = "http://www.gravatar.com/avatar/%s?%s"

class Grayvatar(object):
    @cherrypy.expose
    def default(self, *bits, **kwargs):
        if bits:
            cherrypy.response.headers["Content-Type"] = "image/png"
            url = URL % (bits[0], urllib.urlencode(kwargs))
            img = get_img(url).convert("L")
            return img_as_str(img)

        pwd = path(__file__).abspath().parent
        return (pwd / "how_it_works.html").text()

def get_img(url):
    sock = urllib2.urlopen(url)
    return Image.open(StringIO.StringIO(sock.read()))

def img_as_str(img):
    sock = StringIO.StringIO()
    img.save(sock, "png")
    return sock.getvalue()

if __name__ == "__main__":
    cherrypy.quickstart(Grayvatar())

I think it's awesome that, thanks to CherrPy and PIL, I could put together a web app that does all that 30 odd lines (including imports), but it's less awesome that so many of those lines are shuffling data in and out of a StringIO.

mod_wsgi

I set up an instance at: http://aaron.maenpaa.ca/grayvatar using Apache and mod_wsgi. I used a lightly adapted version of the the wsgi config from the CherryPy wiki.

import sys
sys.stdout = sys.stderr

import atexit
import threading
import cherrypy

sys.path = ['/home/aaron/code/grayvatar'] + sys.path

import grayvatar

cherrypy.config.update({
    'environment': 'embedded',
    'log.screen': True,
})

if cherrypy.__version__.startswith('3.0') and cherrypy.engine.state == 0:
    cherrypy.engine.start(blocking=False)
    atexit.register(cherrypy.engine.stop)

application = cherrypy.Application(grayvatar.Grayvatar(), script_name=None)

The most important change was adding 'log.screen': True to the config. This sends errors to stderr, which eventually makes it's way to /var/log/apache2/error.log which makes it possible to figure out what's going on when things don't go right.

How It Works

Just like Gravatar. The base URL is http://aaron.maenpaa.ca/grayvatar/. You calculate the MD5 hash of your email address, append it to the base URL and then add any query parameters that Gravatar supports. That gets you a URL like this: http://aaron.maenpaa.ca/grayvatar/634329486bd326c395c8b3539c035139?s=256&d=identicon&r=PG

It fetchs the appropriate Gravatar, desaturates and returns it. Keep in mind, this is a toy: It doesn't actually respect the cache headers Gravatar sends and worse, it doesn't even pass them along to the client.