diff --git a/.gitignore b/.gitignore
index 427043b..fd66f45 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
 *.swp
 tests/*.diff
+spec-script
+spec-template
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..4729540
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "spec"]
+	path = spec
+	url = https://github.com/mustache/spec
diff --git a/README.md b/README.md
index 307c546..0101b49 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,8 @@ Requirements
 * The "coreutils" package (`basename` and `cat`)
 * ... that's it.  Why?  Because bash **can**!
 
+If you intend to develop this and run the official specs, you also need node.js.
+
 
 Concessions
 -----------
@@ -46,6 +48,8 @@ Developing
 
 Check out the code and hack away.  Please add tests to show off bugs before fixing them.  New functionality should also be covered by a test.
 
+To run against the official specs, you need to make sure you have the "spec" submodule.  If you see a `spec/` folder with stuff in it, you're already set.  Otherwise run `git submodule update --init`.  After that you need to install node.js and run `npm install async` (no, I didn't make a package.json to just list one dependency).  Finally, `./run-spec.js spec/specs/*.json` will run against the official tests - there's over 100 of them.
+
 
 License
 -------
diff --git a/run-spec.js b/run-spec.js
new file mode 100755
index 0000000..b44556e
--- /dev/null
+++ b/run-spec.js
@@ -0,0 +1,201 @@
+#!/usr/bin/env node
+
+var async, exec, fs, summary, specFiles;
+
+function makeShellString(value) {
+    if (typeof value === 'string') {
+        return JSON.stringify(value);
+    }
+
+    if (typeof value === 'number') {
+        return value;
+    }
+
+    return 'ERR_CONVERTING';
+}
+
+function addToEnvironment(name, value) {
+    var result;
+
+    if (Array.isArray(value)) {
+        result = [
+            '('
+        ];
+        value.forEach(function (subValue) {
+            result.push(makeShellString(subValue));
+        });
+        result.push(')');
+
+        return name + '=' + result.join(' ');
+    }
+
+    if (typeof value === 'object') {
+        return '# ' + name + ' is an object and will not work in bash';
+    }
+
+    if (typeof value === 'boolean') {
+        if (value) {
+            return name + '="true"';
+        }
+
+        return name + '=""';
+    }
+
+    return name + '=' + makeShellString(value);
+}
+
+function runTest(test, done) {
+    var output, script;
+
+    script = [
+        '#!/bin/bash'
+    ];
+
+    Object.keys(test.data).forEach(function (name) {
+        script.push(addToEnvironment(name, test.data[name]));
+    });
+    script.push('. mo spec-template');
+    test.script = script.join('\n');
+    async.series([
+        function (taskDone) {
+            fs.writeFile('spec-script', test.script, taskDone);
+        },
+        function (taskDone) {
+            fs.writeFile('spec-template', test.template, taskDone);
+        },
+        function (taskDone) {
+            exec('bash spec-script', function (err, stdout) {
+                if (err) {
+                    return taskDone(err);
+                }
+
+                output = stdout;
+                taskDone();
+            });
+        },
+        function (taskDone) {
+            fs.unlink('spec-script', taskDone);
+        },
+        function (taskDone) {
+            fs.unlink('spec-template', taskDone);
+        }
+    ], function (err) {
+        if (err) {
+            return done(err);
+        }
+        
+        done(null, output);
+    });
+    
+    return '';
+}
+
+function prepareAndRunTest(test, done) {
+    async.waterfall([
+        function (taskDone) {
+            console.log('### ' + test.name);
+            console.log('');
+            console.log(test.desc);
+            console.log('');
+            runTest(test, taskDone);
+        },
+        function (actual, taskDone) {
+            test.actual = actual;
+            test.pass = (test.actual === test.expected);
+
+            if (test.pass) {
+                console.log('Passed.');
+            } else {
+                console.log('Failed.');
+                console.log('');
+                console.log(test);
+            }
+
+            console.log('');
+            taskDone();
+        }
+    ], done);
+}
+
+function specFileToName(file) {
+    return file.replace(/.*\//, '').replace('.json', '').replace('~', '').replace(/(^|-)[a-z]/g, function (match) {
+        return match.toUpperCase();
+    });
+}
+
+function processSpecFile(specFile, done) {
+    fs.readFile(specFile, 'utf8', function (err, data) {
+        var name;
+
+        if (err) {
+            return done(err);
+        }
+
+        name = specFileToName(specFile);
+        data = JSON.parse(data);
+        console.log(name);
+        console.log('====================');
+        console.log('');
+        console.log(data.overview);
+        console.log('');
+        console.log('Tests');
+        console.log('-----');
+        console.log('');
+        async.series([
+            function (taskDone) {
+                async.eachSeries(data.tests, prepareAndRunTest, taskDone);
+            },
+            function (taskDone) {
+                summary[name] = {};
+                data.tests.forEach(function (test) {
+                    summary[name][test.name] = test.pass;
+                });
+                taskDone();
+            }
+        ], done);
+    });
+}
+
+// 0 = node, 1 = script, 2 = file
+if (process.argv.length < 3) {
+    console.log('Specify spec files on the command line');
+    process.exit();
+}
+
+async = require('async');
+fs = require('fs');
+exec = require('child_process').exec;
+summary = {};
+async.eachSeries(process.argv.slice(2), processSpecFile, function () {
+    var fail, pass;
+
+    console.log('');
+    console.log('Summary');
+    console.log('=======');
+    console.log('');
+    pass = 0;
+    fail = 0;
+    Object.keys(summary).forEach(function (name) {
+        var groupPass, groupFail, testResults;
+       
+        testResults = [];
+        groupPass = 0;
+        groupFail = 0;
+        Object.keys(summary[name]).forEach(function (testName) {
+            if (summary[name][testName]) {
+                testResults.push('    * pass - ' + testName);
+                groupPass += 1;
+                pass += 1;
+            } else {
+                testResults.push('    * FAIL - ' + testName);
+                groupFail += 1;
+                fail += 1;
+            }
+        });
+        testResults.unshift('* ' + name + ' (failed ' + groupFail + ' out of ' + (groupPass + groupFail) + ' tests)');
+        console.log(testResults.join('\n'));
+    });
+
+    console.log('');
+    console.log('Failed ' + fail + ' out of ' + (pass + fail) + ' tests');
+});
diff --git a/spec b/spec
new file mode 160000
index 0000000..934db98
--- /dev/null
+++ b/spec
@@ -0,0 +1 @@
+Subproject commit 934db98d8ba854574d18aa59a01d2c9aee41944d