[Pkg-javascript-commits] [node-jade] 01/10: Imported Upstream version 1.11.0

Jelmer Vernooij jelmer at moszumanska.debian.org
Sun Jul 3 18:02:39 UTC 2016


This is an automated email from the git hooks/post-receive script.

jelmer pushed a commit to branch 0.11-packaging
in repository node-jade.

commit f95cc52751db89b7c5efb82514602c83d0764547
Author: Jelmer Vernooij <jelmer at jelmer.uk>
Date:   Sun Jul 3 16:36:28 2016 +0000

    Imported Upstream version 1.11.0
---
 .gitignore                                      |  12 +
 .gitmodules                                     |  21 --
 .travis.yml                                     |  16 +
 History.md                                      |  81 +++++
 README.md                                       |  22 +-
 Readme_zh-cn.md                                 |   2 +-
 bin/jade.js                                     | 201 ++++++++----
 block-code.html                                 |   0
 component.json                                  |   2 +-
 docs/server.js                                  |   1 -
 docs/stop.js                                    |   4 +-
 docs/style/bg.jpg                               | Bin 0 -> 5349 bytes
 docs/style/bg at 2X.jpg                            | Bin 0 -> 27855 bytes
 docs/style/index.less                           |  17 +-
 docs/style/jade-logo-header.svg                 | 108 ++++++
 docs/style/jade-logo.svg                        |   1 +
 docs/style/logo.png                             | Bin 6431 -> 0 bytes
 docs/versions.json                              |  11 +-
 docs/views/api.jade                             | 120 ++++++-
 docs/views/command-line.jade                    |  25 +-
 docs/views/layout.jade                          |  15 +-
 docs/views/reference.jade                       |   3 +-
 docs/views/reference/attributes.jade            |  43 ++-
 docs/views/reference/code.jade                  |  71 ++--
 docs/views/reference/comments.jade              |   2 +-
 docs/views/reference/doctype.jade               |   2 +-
 docs/views/reference/filters.jade               |   2 +-
 docs/views/reference/inheritance.jade           |  12 +-
 docs/views/reference/interpolation.jade         | 124 +++++++
 examples/attributes.js                          |   2 +-
 examples/client-compilation.jade                | 145 ++++++++
 examples/client-compilation.js                  |  22 ++
 examples/code.jade                              |   5 +
 examples/code.js                                |   2 +-
 examples/csrf.jade                              |   5 -
 examples/csrf.js                                |  42 ---
 examples/dynamicscript.js                       |  20 +-
 examples/each.js                                |   2 +-
 examples/extend.js                              |   2 +-
 examples/includes.js                            |   2 +-
 examples/layout-debug.js                        |   4 +-
 examples/layout.js                              |   2 +-
 examples/mixins.jade                            |  10 +-
 examples/mixins.js                              |   2 +-
 examples/rss.js                                 |   2 +-
 examples/text.js                                |   2 +-
 examples/whitespace.js                          |   2 +-
 index.js                                        |   2 -
 lib/compiler.js                                 |  33 +-
 lib/filters.js                                  |  88 ++++-
 lib/index.js                                    | 419 +++++++++++++++++++++++-
 lib/jade.js                                     | 371 ---------------------
 lib/lexer.js                                    |  60 ++--
 lib/parser.js                                   |  30 +-
 lib/runtime.js                                  |  57 +++-
 package.json                                    |  94 +++---
 release.js                                      |  89 +----
 test/anti-cases/mixin-args-syntax-error.jade    |   2 +
 test/anti-cases/unclosed-interpolated-call.jade |   1 +
 test/anti-cases/unclosed-interpolated-tag.jade  |   4 +
 test/anti-cases/unclosed-interpolation.jade     |   1 +
 test/cases/auxiliary/1794-extends.jade          |   1 +
 test/cases/auxiliary/1794-include.jade          |   4 +
 test/cases/block-code.html                      |   7 +
 test/cases/block-code.jade                      |  12 +
 test/cases/blocks-in-if.jade                    |   2 +-
 test/cases/classes.html                         |   2 +-
 test/cases/classes.jade                         |   6 +-
 test/cases/comments-in-case.html                |   6 +
 test/cases/comments-in-case.jade                |  10 +
 test/cases/filters.coffeescript.html            |   3 +-
 test/cases/filters.coffeescript.jade            |   4 +-
 test/cases/filters.markdown.jade                |   2 +-
 test/cases/include-filter-stylus.jade           |   2 +-
 test/cases/include-filter.jade                  |   6 +-
 test/cases/includes.html                        |   6 +-
 test/cases/includes.jade                        |   2 +-
 test/cases/mixin-via-include.jade               |   2 +-
 test/cases/regression.1794.html                 |   1 +
 test/cases/regression.1794.jade                 |   4 +
 test/cases/styles.html                          |  13 +-
 test/cases/styles.jade                          |  15 +-
 test/cases/text.html                            |   2 +
 test/cases/text.jade                            |   4 +
 test/command-line.js                            | 331 ++++++++++++++++++-
 test/deprecated.js                              |   7 +
 test/error.reporting.js                         |   6 +-
 test/examples.js                                |  29 +-
 test/jade.test.js                               | 138 +++++++-
 test/run.js                                     |  15 +-
 test/unit.js                                    |  12 +-
 91 files changed, 2240 insertions(+), 856 deletions(-)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..24fdab8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+.DS_Store
+.idea
+testing
+node_modules
+lib-cov
+coverage
+cov-pt*
+npm-debug.log
+/test/output
+/test/temp
+/docs/out
+.release.json
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index b5b4321..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,21 +0,0 @@
-[submodule "support/expresso"]
-	path = support/expresso
-	url = git://github.com/visionmedia/expresso.git
-[submodule "support/sass"]
-	path = support/sass
-	url = git://github.com/visionmedia/sass.js.git
-[submodule "benchmarks/haml-js"]
-	path = benchmarks/haml-js
-	url = git://github.com/creationix/haml-js.git
-[submodule "benchmarks/ejs"]
-	path = benchmarks/ejs
-	url = git://github.com/visionmedia/ejs.git
-[submodule "benchmarks/haml"]
-	path = benchmarks/haml
-	url = git://github.com/visionmedia/haml.js.git
-[submodule "support/coffee-script"]
-	path = support/coffee-script
-	url = http://github.com/jashkenas/coffee-script.git
-[submodule "support/stylus"]
-	path = support/stylus
-	url = git://github.com/LearnBoost/stylus.git
diff --git a/.travis.yml b/.travis.yml
index 6e5919d..541afc2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,3 +1,19 @@
 language: node_js
 node_js:
   - "0.10"
+  - "0.12"
+  - "iojs"
+
+# Use faster Docker architecture on Travis.
+sudo: false
+
+script:        npm test
+after_success: npm run coveralls
+
+notifications:
+  webhooks:
+    urls:
+      - https://webhooks.gitter.im/e/dd893be75c6b9908987d
+    on_success: change
+    on_failure: always
+    on_start:   never
diff --git a/History.md b/History.md
index 2f387ae..ec54631 100644
--- a/History.md
+++ b/History.md
@@ -1,3 +1,84 @@
+1.11.0 / 2015-06-12
+==================
+
+  * Added block code support ([@alephyud](https://github.com/alephyud))
+  * Improved runtime performance of mixins significantly ([Andreas Lubbe](https://github.com/alubbe))
+  * Improved runtime performance of jade's string escaping ([Andreas Lubbe](https://github.com/alubbe)) and ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+  * Better line number counting for pipeless text ([@alephyud](https://github.com/alephyud))
+
+
+1.10.0 / 2015-05-25
+==================
+
+  * Now supports jstransformers, which allows improved handling of embedded languages such as Coffee-Script, and deprecated Transformers support in filters - to be removed in 2.0.0 ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+  * CLI: added a flag to keep directory hierarchy when a directory is specified - this behavior will be the default in 2.0.0 ([@TimothyGu](https://github.com/TimothyGu))
+  * disabled 'compileDebug' flag by default when used with express in production mode ([Andreas Lubbe](https://github.com/alubbe))
+  * Fixed a memory leak on modern versions of Chrome as well as node 0.12 and iojs ([Andreas Lubbe](https://github.com/alubbe))
+  * update website ([@GarthDB](https://github.com/GarthDB))
+
+1.9.2 / 2015-01-18
+==================
+
+  * Do not ignore some parser errors for mismatched parenthesis ([@TimothyGu](https://github.com/TimothyGu))
+  * Warn for `:` that is not followed by a space ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+  * Fix #1794 (a bizzare bug with a certain combination of inheritance, mixins and &attributes) ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+  * Add `compileClientWithDependenciesTracked` ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+  * Support comments in `case` blocks ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+  * Fix blocks in nested mixins ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+  * Lots more documentation ([@enlore](https://github.com/enlore))
+  * Fix watching in CLI ([@pavel](https://github.com/pavel))
+
+1.9.1 / 2015-01-17
+==================
+
+  * Clean up path/fs functions in CLI as we no longer support node at 0.6 ([@TimothyGu](https://github.com/TimothyGu))
+  * Update commander ([@TimothyGu](https://github.com/TimothyGu))
+  * Document `cache` and `parser` options ([@TimothyGu](https://github.com/TimothyGu))
+  * Fix bug in 1.9.0 where we read the file if cache was enabled, even if a string was provided ([@TimothyGu](https://github.com/TimothyGu))
+  * Fix year in changelog ([@tomByrer](https://github.com/tomByrer))
+
+1.9.0 / 2015-01-13
+==================
+
+  * Fix `--watch` sometimes dying when there were file-system errors ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+  * Fix `--watch` by using `fs.watchFile` correctly ([@TimothyGu](https://github.com/TimothyGu))
+  * Fix errors with using the CLI to compile from stdin
+  * Better looking badges ([@TimothyGu](https://github.com/TimothyGu))
+  * Added `--extension` to CLI([@nicocedron](https://github.com/nicocedron) and [@TimothyGu](https://github.com/TimothyGu))
+  * Refactor and improve internal cache handling ([@TimothyGu](https://github.com/TimothyGu))
+  * Loads more tests ([@TimothyGu](https://github.com/TimothyGu))
+
+1.8.2 / 2014-12-16
+==================
+
+  * Use `-` as the default filename when using stdin on CLI ([@TimothyGu](https://github.com/TimothyGu))
+  * Prevent some compiler errors being silenced ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+  * Remove use of non-standard `string.trimLeft()` ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+  * Fix bug in CLI when no name was provided for child template ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+  * Remove dependency on monocle (hopefully fixing installation on 0.8) ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+  * Add gitter chat room ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+
+1.8.1 / 2014-11-30
+==================
+
+  * Fix corner case when the pretty option was passed a non-string truthy value ([@TimothyGu](https://github.com/TimothyGu))
+  * Warn when `lexer` is given as an option ([@TimothyGu](https://github.com/TimothyGu))
+  * Update dependencies ([@TimothyGu](https://github.com/TimothyGu))
+
+1.8.0 / 2014-11-28
+==================
+
+  * Fix empty text-only block ([@rlidwka](https://github.com/rlidwka))
+  * Warn about future change to ISO 8601 style dates ([@TimothyGu](https://github.com/TimothyGu) and [@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+  * Add warnings when data-attributes contain ampersands ([@TimothyGu](https://github.com/TimothyGu))
+  * Allow custom pretty indentation ([@bfred-it](https://github.com/bfred-it))
+  * Add support for an object in the style attribute ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+  * Add support for an object in the class attribute ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+  * Ignore fs module in browser builds ([@sokra](https://github.com/sokra))
+  * Update dependencies ([@hildjj](https://github.com/hildjj))
+  * Check mixin arguments are valid JavaScript expressions ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+  * Remove symlink ([@slang800](https://github.com/slang800))
+
 1.7.0 / 2014-09-17
 ==================
 
diff --git a/README.md b/README.md
index 666446b..5ff6d97 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,20 @@
-# [![Jade - template engine ](http://i.imgur.com/5zf2aVt.png)](http://jade-lang.com/)
+# [![Jade - Node Template Engine](http://garthdb.com/img/jade_branding/jade-01.svg)](http://jade-lang.com/)
 
 Full documentation is at [jade-lang.com](http://jade-lang.com/)
 
  Jade is a high performance template engine heavily influenced by [Haml](http://haml-lang.com)
- and implemented with JavaScript for [node](http://nodejs.org). For discussion join the [Google Group](http://groups.google.com/group/jadejs).
+ and implemented with JavaScript for [node](http://nodejs.org) and browsers. For bug reports,
+ feature requests and questions, [open an issue](https://github.com/jadejs/jade/issues/new).
+ For discussion join the [chat room](https://gitter.im/jadejs/jade).
 
  You can test drive Jade online [here](http://naltatis.github.com/jade-syntax-docs).
 
- [![Build Status](https://img.shields.io/travis/visionmedia/jade/master.svg)](https://travis-ci.org/visionmedia/jade)
- [![Dependency Status](https://img.shields.io/gemnasium/visionmedia/jade.svg)](https://gemnasium.com/visionmedia/jade)
- [![NPM version](https://img.shields.io/npm/v/jade.svg)](http://badge.fury.io/js/jade)
+ [![Build Status](https://img.shields.io/travis/jadejs/jade/master.svg?style=flat)](https://travis-ci.org/jadejs/jade)
+ [![Coverage Status](https://img.shields.io/coveralls/jadejs/jade/master.svg?style=flat)](https://coveralls.io/r/jadejs/jade?branch=master)
+ [![Dependency Status](https://img.shields.io/david/jadejs/jade.svg?style=flat)](https://david-dm.org/jadejs/jade)
+ [![devDependencies Status](https://img.shields.io/david/dev/jadejs/jade.svg?style=flat)](https://david-dm.org/jadejs/jade#info=devDependencies)
+ [![NPM version](https://img.shields.io/npm/v/jade.svg?style=flat)](http://badge.fury.io/js/jade)
+ [![Join Gitter Chat](https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg?style=flat)](https://gitter.im/jadejs/jade?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
 
 ## Installation
 
@@ -64,7 +69,7 @@ becomes
 </html>
 ```
 
-The official [jade tutorial](http://jade-lang.com/tutorial/) is a great place to start.  While that (and the syntax documentation) is being finished, you can view some of the old documentation [here](https://github.com/visionmedia/jade/blob/master/jade.md) and [here](https://github.com/visionmedia/jade/blob/master/jade-language.md)
+The official [jade tutorial](http://jade-lang.com/tutorial/) is a great place to start.  While that (and the syntax documentation) is being finished, you can view some of the old documentation [here](https://github.com/jadejs/jade/blob/master/jade.md) and [here](https://github.com/jadejs/jade/blob/master/jade-language.md)
 
 ## API
 
@@ -92,7 +97,7 @@ var html = jade.renderFile('filename.jade', merge(options, locals));
 
 ## Browser Support
 
- The latest version of jade can be download for the browser in standalone form from [here](https://github.com/visionmedia/jade/raw/master/jade.js).  It only supports the very latest browsers though, and is a large file.  It is recommended that you pre-compile your jade templates to JavaScript and then just use the [runtime.js](https://github.com/visionmedia/jade/raw/master/runtime.js) library on the client.
+ The latest version of jade can be download for the browser in standalone form from [here](https://github.com/jadejs/jade/raw/master/jade.js).  It only supports the very latest browsers though, and is a large file.  It is recommended that you pre-compile your jade templates to JavaScript and then just use the [runtime.js](https://github.com/jadejs/jade/raw/master/runtime.js) library on the client.
 
  To compile a template for use on the client using the command line, do:
 
@@ -123,7 +128,7 @@ Tutorials:
   - cssdeck interactive [Jade syntax tutorial](http://cssdeck.com/labs/learning-the-jade-templating-engine-syntax)
   - cssdeck interactive [Jade logic tutorial](http://cssdeck.com/labs/jade-templating-tutorial-codecast-part-2)
   - [Jade について。](https://gist.github.com/japboy/5402844) (A Japanese Tutorial)
-  - [Jade - 模板引擎](https://github.com/visionmedia/jade/blob/master/Readme_zh-cn.md)
+  - [Jade - 模板引擎](https://github.com/jadejs/jade/blob/master/Readme_zh-cn.md)
 
 Implementations in other languages:
 
@@ -141,6 +146,7 @@ Other:
   - [Coda/SubEtha syntax Mode](https://github.com/aaronmccall/jade.mode)
   - [Screencasts](http://tjholowaychuk.com/post/1004255394/jade-screencast-template-engine-for-nodejs)
   - [html2jade](https://github.com/donpark/html2jade) converter
+  - [jade2php](https://github.com/SE7ENSKY/jade2php) converter
   - [Jade Server](https://github.com/ded/jade-server)  Ideal for building local prototypes apart from any application
 
 ## License
diff --git a/Readme_zh-cn.md b/Readme_zh-cn.md
index 3adde19..da54583 100644
--- a/Readme_zh-cn.md
+++ b/Readme_zh-cn.md
@@ -339,7 +339,7 @@ p .
 需要注意的是文本块需要两次转义。比如想要输出下面的文本:
 
 ```jade
-</p>foo\bar</p>
+<p>foo\bar</p>
 ```
 
 使用:
diff --git a/bin/jade.js b/bin/jade.js
index d7552ef..a2c509f 100755
--- a/bin/jade.js
+++ b/bin/jade.js
@@ -10,9 +10,8 @@ var fs = require('fs')
   , basename = path.basename
   , dirname = path.dirname
   , resolve = path.resolve
-  , exists = fs.existsSync || path.existsSync
+  , normalize = path.normalize
   , join = path.join
-  , monocle = require('monocle')()
   , mkdirp = require('mkdirp')
   , jade = require('../');
 
@@ -25,7 +24,7 @@ var options = {};
 program
   .version(require('../package.json').version)
   .usage('[options] [dir|file ...]')
-  .option('-O, --obj <str>', 'javascript options object')
+  .option('-O, --obj <str|path>', 'JavaScript options object or JSON file containing it')
   .option('-o, --out <dir>', 'output the compiled html to <dir>')
   .option('-p, --path <path>', 'filename used to resolve includes')
   .option('-P, --pretty', 'compile pretty html output')
@@ -33,6 +32,8 @@ program
   .option('-n, --name <str>', 'The name of the compiled template (requires --client)')
   .option('-D, --no-debug', 'compile without debugging (smaller functions)')
   .option('-w, --watch', 'watch files for changes and automatically re-render')
+  .option('-E, --extension <ext>', 'specify the output file extension')
+  .option('-H, --hierarchy', 'keep directory hierarchy when a directory is specified')
   .option('--name-after-file', 'Name the template after the last section of the file path (requires --client and overriden by --name)')
   .option('--doctype <str>', 'Specify the doctype on the command line (useful if it is not specified by the template)')
 
@@ -62,14 +63,26 @@ program.parse(process.argv);
 // options given, parse them
 
 if (program.obj) {
-  if (exists(program.obj)) {
-    options = JSON.parse(fs.readFileSync(program.obj));
-  } else {
-    options = eval('(' + program.obj + ')');
+  options = parseObj(program.obj);
+}
+
+/**
+ * Parse object either in `input` or in the file called `input`. The latter is
+ * searched first.
+ */
+function parseObj (input) {
+  var str, out;
+  try {
+    str = fs.readFileSync(program.obj);
+  } catch (e) {
+    return eval('(' + program.obj + ')');
   }
+  // We don't want to catch exceptions thrown in JSON.parse() so have to
+  // use this two-step approach.
+  return JSON.parse(str);
 }
 
-// --filename
+// --path
 
 if (program.path) options.filename = program.path;
 
@@ -91,7 +104,9 @@ options.watch = program.watch;
 
 // --name
 
-options.name = program.name;
+if (typeof program.name === 'string') {
+  options.name = program.name;
+}
 
 // --doctype
 
@@ -101,25 +116,25 @@ options.doctype = program.doctype;
 
 var files = program.args;
 
+// array of paths that are being watched
+
+var watchList = [];
+
+// function for rendering
+var render = program.watch ? tryRender : renderFile;
+
 // compile files
 
 if (files.length) {
   console.log();
   if (options.watch) {
-    // keep watching when error occured.
-    process.on('uncaughtException', function(err) {
-      console.error(err);
-    });
-    files.forEach(renderFile);
-    monocle.watchFiles({
-      files: files,
-      listener: function(file) {
-        renderFile(file.absolutePath);
-      }
+    process.on('SIGINT', function() {
+      process.exit(1);
     });
-  } else {
-    files.forEach(renderFile);
   }
+  files.forEach(function (file) {
+    render(file);
+  });
   process.on('exit', function () {
     console.log();
   });
@@ -129,6 +144,48 @@ if (files.length) {
 }
 
 /**
+ * Watch for changes on path
+ *
+ * Renders `base` if specified, otherwise renders `path`.
+ */
+function watchFile(path, base, rootPath) {
+  path = normalize(path);
+  if (watchList.indexOf(path) !== -1) return;
+  console.log("  \033[90mwatching \033[36m%s\033[0m", path);
+  fs.watchFile(path, {persistent: true, interval: 200},
+               function (curr, prev) {
+    // File doesn't exist anymore. Keep watching.
+    if (curr.mtime.getTime() === 0) return;
+    // istanbul ignore if
+    if (curr.mtime.getTime() === prev.mtime.getTime()) return;
+    tryRender(base || path, rootPath);
+  });
+  watchList.push(path);
+}
+
+/**
+ * Convert error to string
+ */
+function errorToString(e) {
+  return e.stack || /* istanbul ignore next */ (e.message || e);
+}
+
+/**
+ * Try to render `path`; if an exception is thrown it is printed to stderr and
+ * otherwise ignored.
+ *
+ * This is used in watch mode.
+ */
+function tryRender(path, rootPath) {
+  try {
+    renderFile(path, rootPath);
+  } catch (e) {
+    // keep watching when error occured.
+    console.error(errorToString(e));
+  }
+}
+
+/**
  * Compile from stdin.
  */
 
@@ -155,55 +212,73 @@ function stdin() {
   })
 }
 
+var hierarchyWarned = false;
+
 /**
  * Process the given path, compiling the jade files found.
  * Always walk the subdirectories.
+ *
+ * @param path      path of the file, might be relative
+ * @param rootPath  path relative to the directory specified in the command
  */
 
-function renderFile(path) {
+function renderFile(path, rootPath) {
   var re = /\.jade$/;
-  fs.lstat(path, function(err, stat) {
-    if (err) throw err;
-    // Found jade file
-    if (stat.isFile() && re.test(path)) {
-      fs.readFile(path, 'utf8', function(err, str){
-        if (err) throw err;
-        options.filename = path;
-        if (program.nameAfterFile) {
-          options.name = getNameFromFileName(path);
-        }
-        var fn = options.client ? jade.compileClient(str, options) : jade.compile(str, options);
-        var extname = options.client ? '.js' : '.html';
-        path = path.replace(re, extname);
-        if (program.out) path = join(program.out, basename(path));
-        var dir = resolve(dirname(path));
-        mkdirp(dir, 0755, function(err){
-          if (err) throw err;
-          try {
-            var output = options.client ? fn : fn(options);
-            fs.writeFile(path, output, function(err){
-              if (err) throw err;
-              console.log('  \033[90mrendered \033[36m%s\033[0m', path);
-            });
-          } catch (e) {
-            if (options.watch) {
-              console.error(e.stack || e.message || e);
-            } else {
-              throw e
-            }
-          }
-        });
-      });
-    // Found directory
-    } else if (stat.isDirectory()) {
-      fs.readdir(path, function(err, files) {
-        if (err) throw err;
-        files.map(function(filename) {
-          return path + '/' + filename;
-        }).forEach(renderFile);
+  var stat = fs.lstatSync(path);
+  // Found jade file/\.jade$/
+  if (stat.isFile() && re.test(path)) {
+    // Try to watch the file if needed. watchFile takes care of duplicates.
+    if (options.watch) watchFile(path, null, rootPath);
+    if (program.nameAfterFile) {
+      options.name = getNameFromFileName(path);
+    }
+    var fn = options.client
+           ? jade.compileFileClient(path, options)
+           : jade.compileFile(path, options);
+    if (options.watch && fn.dependencies) {
+      // watch dependencies, and recompile the base
+      fn.dependencies.forEach(function (dep) {
+        watchFile(dep, path, rootPath);
       });
     }
-  });
+
+    // --extension
+    var extname;
+    if (program.extension)   extname = '.' + program.extension;
+    else if (options.client) extname = '.js';
+    else                     extname = '.html';
+
+    // path: foo.jade -> foo.<ext>
+    path = path.replace(re, extname);
+    if (program.out) {
+      // prepend output directory
+      if (rootPath && program.hierarchy) {
+        // replace the rootPath of the resolved path with output directory
+        path = resolve(path).replace(new RegExp('^' + resolve(rootPath)), '');
+        path = join(program.out, path);
+      } else {
+        if (rootPath && !hierarchyWarned) {
+          console.warn('In Jade 2.0.0 --hierarchy will become the default.');
+          hierarchyWarned = true;
+        }
+        // old behavior or if no rootPath handling is needed
+        path = join(program.out, basename(path));
+      }
+    }
+    var dir = resolve(dirname(path));
+    mkdirp.sync(dir, 0755);
+    var output = options.client ? fn : fn(options);
+    fs.writeFileSync(path, output);
+    console.log('  \033[90mrendered \033[36m%s\033[0m', normalize(path));
+  // Found directory
+  } else if (stat.isDirectory()) {
+    var files = fs.readdirSync(path);
+    files.map(function(filename) {
+      return path + '/' + filename;
+    }).forEach(function (file) {
+      render(file, rootPath || path);
+    });
+  }
 }
 
 /**
@@ -213,7 +288,7 @@ function renderFile(path) {
  * @returns {String}
  */
 function getNameFromFileName(filename) {
-  var file = path.basename(filename, '.jade');
+  var file = basename(filename, '.jade');
   return file.toLowerCase().replace(/[^a-z0-9]+([a-z])/g, function (_, character) {
     return character.toUpperCase();
   }) + 'Template';
diff --git a/block-code.html b/block-code.html
new file mode 100644
index 0000000..e69de29
diff --git a/component.json b/component.json
index a3befff..785ff08 100644
--- a/component.json
+++ b/component.json
@@ -2,7 +2,7 @@
   "name": "jade",
   "repo": "visionmedia/jade",
   "description": "Jade template runtime",
-  "version": "1.7.0",
+  "version": "1.11.0",
   "keywords": [
     "template"
   ],
diff --git a/docs/server.js b/docs/server.js
index f69714e..5bbee05 100644
--- a/docs/server.js
+++ b/docs/server.js
@@ -98,7 +98,6 @@ app.get('/history', function (req, res, next) {
 app.get('/client.js', browserify(__dirname + '/client/index.js'));
 app.use('/style', less(__dirname + '/style/index.less'));
 app.use('/style', express.static(__dirname + '/style'));
-app.use('/coverage', express.static(path.resolve(__dirname + '/../coverage/lcov-report')));
 
 app.use(function (err, req, res, next) {
   var msg = err.stack || err.toString();
diff --git a/docs/stop.js b/docs/stop.js
index d4a08ab..884725f 100644
--- a/docs/stop.js
+++ b/docs/stop.js
@@ -19,9 +19,7 @@ module.exports = stop.getWebsiteStream('http://localhost:3000', {
   parallel: 1
 })
 .on('data', function (page) {
-  if (page.url === 'http://localhost:3000/style/files/1/glyphicons-halflings-regular.eot?' && page.statusCode === 404) {
-    //todo: fix this
-  } else if (page.statusCode !== 200) {
+  if (page.statusCode !== 200) {
     throw new Error('Unexpected status code ' + page.statusCode +
                     ' for ' + page.url);
   }
diff --git a/docs/style/bg.jpg b/docs/style/bg.jpg
new file mode 100644
index 0000000..45e3ebb
Binary files /dev/null and b/docs/style/bg.jpg differ
diff --git a/docs/style/bg at 2X.jpg b/docs/style/bg at 2X.jpg
new file mode 100644
index 0000000..3997ca2
Binary files /dev/null and b/docs/style/bg at 2X.jpg differ
diff --git a/docs/style/index.less b/docs/style/index.less
index 028407e..090f0c9 100644
--- a/docs/style/index.less
+++ b/docs/style/index.less
@@ -1,6 +1,6 @@
 @import (npm) "twbs";
 
- at jade: #00a86b;
+ at jade: #589983;
 @brand-primary: @jade;
 @grid-float-breakpoint: @screen-md-min;
 
@@ -37,11 +37,24 @@
 
 .logo-container {
   text-align: center;
-  background-color: @jade;
+  background: url('./bg.jpg');
   padding-top: 50px;
   padding-bottom: 50px;
+  img {
+    width: 100%;
+    height: auto;
+    max-width: 300px
+  }
+}
+
+ at media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
+  .logo-container {
+    background: url('./bg at 2X.jpg');
+    background-size: 250px 250px;
+  }
 }
 
+
 p {
   font-size: 1.2em;
 }
diff --git a/docs/style/jade-logo-header.svg b/docs/style/jade-logo-header.svg
new file mode 100644
index 0000000..0fd1a3d
--- /dev/null
+++ b/docs/style/jade-logo-header.svg
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 800 316" enable-background="new 0 0 800 316" xml:space="preserve">
+<path fill="#569A83" d="M118.9,23.8c3.5,0,5.1,1.7,5.1,5.1v171.5c0,6.3-1.2,12.4-3.7,18s-5.7,10.5-9.9,14.7
+	c-4.2,4.2-9.1,7.5-14.7,9.9s-11.6,3.7-18,3.7h-14c-6.3,0-12.4-1.2-18-3.7c-5.6-2.4-10.5-5.7-14.7-9.9s-7.5-9.1-9.9-14.7
+	c-2.5-5.6-3.7-11.6-3.7-18v-39.3c0-3.5,1.6-5.6,4.8-6.6l33.4-8.8c3.2-1,4.8,0.2,4.8,3.7v49.2c0,2.5,0.8,4.5,2.6,6.2
+	c1.7,1.7,3.8,2.6,6.2,2.6h3c2.4,0,4.5-0.8,6.2-2.6c1.7-1.7,2.6-3.8,2.6-6.2V29c0-3.5,1.7-5.1,5.1-5.1
+	C86.2,23.8,118.9,23.8,118.9,23.8L118.9,23.8z M261.7,239c0.5,3.5-1,5.1-4.4,5.1h-33.4c-3.2,0-5-1.7-5.5-5.1l-3-35.9h-22.8l-3,35.9
+	c-0.5,3.5-2.4,5.1-5.5,5.1h-33.4c-3.5,0-4.9-1.7-4.4-5.1l27.6-210c0.5-3.5,2.4-5.1,5.9-5.1h48.5c3.5,0,5.4,1.7,5.9,5.1L261.7,239z
+	 M212.5,161.9l-6.9-81.2h-3l-6.9,81.2H212.5L212.5,161.9z M346.1,23.8c6.3,0,12.3,1.2,18,3.7c5.6,2.5,10.5,5.7,14.7,9.9
+	s7.5,9.1,9.9,14.7s3.7,11.6,3.7,18v127.8c0,6.3-1.2,12.4-3.7,18s-5.7,10.5-9.9,14.7c-4.2,4.2-9.1,7.5-14.7,9.9
+	c-5.6,2.5-11.6,3.7-18,3.7h-55c-3.5,0-5.1-1.7-5.1-5.1V29c0-3.5,1.7-5.1,5.1-5.1L346.1,23.8L346.1,23.8z M349.5,71.9
+	c0-2.4-0.8-4.5-2.6-6.2c-1.7-1.7-3.8-2.6-6.2-2.6H329v141.7h11.7c2.4,0,4.5-0.8,6.2-2.6c1.7-1.7,2.6-3.8,2.6-6.2V71.9z M511.8,206.3
+	V239c0,3.5-1.7,5.1-5.1,5.1H427c-3.5,0-5.1-1.7-5.1-5.1V29c0-3.5,1.7-5.1,5.1-5.1h79.6c3.5,0,5.1,1.7,5.1,5.1v32.6
+	c0,3.5-1.7,5.1-5.1,5.1h-41.9v41.9H501c3.5,0,5.1,1.7,5.1,5.1v32.7c0,3.5-1.7,5.1-5.1,5.1h-36.3v49.6h41.9
+	C510.1,201.1,511.8,202.8,511.8,206.3L511.8,206.3z"/>
+<path fill="#C1272D" d="M723.7,144.1c1.1,1.1,1.2,2.9-0.1,3.8c-3.2,2.1-7.3,1.8-11.1,1.4c-6.4-0.7-14-1-17.3-7
+	c-0.1,0-0.1-0.1-0.2-0.2c-4.2-2.3,2.9-3.9,4.9-4.2c4.5-0.6,9.3,0.6,13.7,1.6C717.3,140.2,720.8,141.2,723.7,144.1L723.7,144.1z
+	 M782.1,239.5c-3,2.1-1.7,5.7-3.8,8.2c-2.1,2.4-7.2,2.4-9.9,2.4c-11.5,0.1-22.7-0.8-34,0.1c-2.9,0.2-5.5,0.6-8.2,1.3
+	c-0.8,0.2-2.1,0.7-3,0.2c-2.3-1.2-4.4-5-7.4-3.8c-2.5,1.1-4.4,4.7-7.4,2.5c-5.5-4.1-10.1-0.4-15.9,0.7c-6,1.2-11.7-2.1-17.6-0.5
+	c-4.4,1.3-8.6,2.6-13.1,1.1c-4.5-1.6-5.7-5.1-1.8-8.4c6.2-5.1,13.9-5.9,21.6-5.3c11.7,1,23.3,0.1,35-0.5c0.4,0,0.6,0.2,0.6,0.5
+	c5,2,8.2-2,11.1-5.5c0.4-0.5,1.1-0.2,1.3,0.1c4.2,6.2,9.3-0.8,14.6-0.2c1.4,0.1,2.7,0.5,3.6,1.8c0.4,0.6,0.5,1.3,0.5,2
+	c0.2,3.2,4.3,2.5,4.2-0.7c0-0.5,0.6-1,1.1-0.8c4.2,1.7,9.6,3.9,13-0.5c2.9-3.7,3.3-9,3.6-13.5c0.6-12.1-0.2-24.5-1.1-36.5
+	c-0.7-11.7-2-23.6-0.6-35.3c1.4-11.6,1.3-22.9,1.1-34.6c-0.1-6.1-0.4-12.2,0-18.3c0.2-5.6,1.2-11.6,0.6-17.2
+	c-0.6-5-3.6-9.3-4.2-14.2c-0.6-4.5,0-9.2,1.1-13.6c0.6-2.7,2-6,1.9-8.8c-0.1-2.4-0.2-4.9-0.1-7.3c0-1.2-0.2-3.1,0.7-4.1
+	c1.4-1.7,2.1-4.2-1.2-4.5c-7.9-1-16-6-23.5-1.1c0.1,1.7,0.2,3.6-0.2,5.3l1.1,1.4c1.9,0.8,3.8,1.1,5.9,1.2c2.4,0,5-0.6,7.4-0.4
+	c2,0.2,3.7,2,3,4.3c-0.5,1.7-2,3.1-2.9,4.5c-1.6,2.5-1.3,6.1-1.3,9c-0.1,7.3,0,14.6,0.2,21.7c0.2,6,0.8,12.1,1.2,18.1
+	c0.1,0.1,0.5,0.2,0.5,0.5c0.2,6.8,2.5,13.5,2.6,20.3c0.1,0.1-1.8,8.4-1.8,8.4c-0.1,8.8-0.8,25.1-0.8,25.1c-0.1,15-2.7,29.9-0.1,44.8
+	c1.1,6-1.2,11.8-1.4,17.8c-0.2,6.6,0.6,13.1-0.4,19.7c0.1,0.4,0,0.8-0.5,1c-13.7,2.6-27.6,3.1-41.6,2.3c-3.5-0.2-7.6-0.5-11,0.8
+	c-1.3,0.6-3.1,1.4-4.7,1.3c-2-0.2-3-1.8-4.9-2.1c-2.4-0.5-4.7-1-7-1.2c-6.2-0.8-12.1-1.2-17.9,1.3c-3.2,1.4-6.6,0.8-9.8-0.5
+	c-3.8-1.6-6.7-3.5-10.9-1.4c-1.7,0.8-3.2,1.7-5.1,2.1c-1.6,0.4-3,0-4.4-0.6c-3.2-1.2-5.7-1.6-9.1-1c0.4,0.5,0.1,1.4-0.6,1.4
+	c-20.3-1.1-40.7,1.3-61,1.6c-1,0.2-1.7,1.3-2.3,1.3h-0.6c-0.5,0-0.6-1.7-0.5-2c-2.7-3-2-12.9-1.9-15.8c0.8-18.9-1.6-37.9-2.1-56.7
+	c-0.2-9.4-0.2-18.9,1.3-28.2c1.8-10.5,1.8-20.7,1.6-31.2c-0.5-20.9,1.6-41.7,1-62.6c0-0.4,0.4-0.5,0.6-0.4l0.2-0.1
+	c5.6-0.5,11.7-2,17.2-0.5c0.1,0,0.2,0.1,0.2,0.2l0.8,0.1h6.2c0.2,0,0.5,0.1,0.5,0.2c11,0.1,22-0.8,33-1.3c3.8-0.1,7.8,0,11.6-1.2
+	c1.9-0.6,4.2-1.6,6.2-1.4c5,0.4,10.1,1.9,15,3c3.5,0.7,5.9-5.3,9-3.2c4.7,3,10.1,1.9,15.5,2c2.9,0.1,5.6-0.5,8.5-0.5
+	c1.6,0,2.9,0.4,4.3,0.7c1.4-0.2,2.6-0.1,4.2,0.5l25.6-0.8c5.1,0.1,10.7,0.5,15.6-1.4c1.3-0.5,2-1.2,2.4-2c-0.2-0.4-0.4-0.8-0.6-1.3
+	c-0.4-0.7-0.8-1.6-1.3-2.1c-5.5-1.8-11.8-1.3-17.6-1.6c-7.9-0.4-15.6,0.7-23.5,1.3c-7.3,0.6-14.9,1.1-22.2,0.5
+	c-4.3-0.4-12.4-2.3-16.1,0.7c-0.4,0.2-0.7,0.2-1.1,0c-7-5.7-16.2,2.9-24.2,0.5c-4.8-1.4-9.3-2.4-14.4-1.8c-4.9,0.5-9.7,1.2-14.7,1.2
+	c-10.9-0.1-21.7,0.6-32.6,0.2c-2.4-0.1-4.7-0.1-6.9-0.4c-1.9-0.2-4.4-1.2-6.3-0.4c-3,1.1-3.6,2.1-3.3,3c0.8,0.6-2,4.4-2,6.3l-0.2,5
+	c-0.1,7.4-0.6,14.2-2.3,21.4c-0.8,3.8-2.7,9.2-1.3,13c0.8,2.5,2.7,4.2,3.8,6.4c1.9,4.1,2,9.1,1.8,13.5l-1.2,43
+	c-0.5,14.3-1.4,28.7-0.8,43.1c0.5,13.3,2.6,26.9,0.8,40.1c-0.6,4.4-5.9,12.2-0.8,15.3c2.9,1.7,6.3,2.1,9.6,2.4
+	c8.1,0.5,16.2-0.4,24.4-1.3c3-0.4,6-1,9-1c2.3-0.1,4.4,0.2,6.6,1c1.9,0.7,4.8,2,7,2c0.1,0.1,3.6-0.1,3.8-1.1c3-1.3,5.6-2.3,9.4-1.6
+	c3.7,0.7,7,1.2,10.9,0.5c2-0.4,5-0.6,7-0.1l0.8,0.6c0.8-0.4-0.5,1-1.3,1.3c-4.5,1.8-3.1,7.6-3.6,11.5c0,0.4-0.5,0.5-0.7,0.5
+	c-1.8,0-4.3,0-5.3-1.9c-2.1-3.9-7.2-6.6-7.2,1.6c0,0.5-0.4,0.7-0.7,0.7c-0.4,0-0.7-0.2-0.7-0.7s-5.3-3.3-5.9-3.6
+	c-2.6-1.4-5.6-3.1-8.7-2c-1.9,0.6-3.5,1.7-5.3,2.5c-3.2,3.9-6.6,8.1-11.3,5.6c-1-0.5-4.9-3.9-5.7-3.2c-1.1,1.2-2.3,2.4-3.8,3
+	c-4.3,1.7-8.8,1.4-13.4,1.3c-0.2,0-0.7-0.2-0.7-0.6c-0.2-1.8-1.2-5.4-3.7-3.2c-0.6,0.5-1.1,1-1.7,1.6c-0.7,0.6-1.6,1.4-2.5,1.8
+	c-3,1.2-12.2-0.4-13.4-2.5c-1-1.6-1.3-3.2-1.7-5c-0.7-4.4-0.2-9.2-0.4-13.7c-0.4-11.3-1.2-22.4-2.1-33.7c-0.5-5.7-1-11.6-0.5-17.4
+	c0.2-3.7,0.4-8.8,2.3-12.2c1.6-2.7,0.5-4.9-1.6-6.9c-0.5-0.6-0.7-1.7-0.7-2.4c-0.4-4.2,0.4-8.6,0.6-12.8c0.2-3.1,0.7-6.2,0.7-9.3
+	c0-2.4-0.5-4.7-0.7-7c-0.1-1.2-0.2-2.4,0.5-3.5c1.4-2,1.2-3.1,0.4-5.4c-1.6-4.4,0-9.7,0.2-14.3c0.4-5.4,0.6-10.7,0-16.2
+	c-0.6-6-1.8-11.8-1.4-17.9c0.4-5.5,0.7-10.5,1-15.9c0.5-9.8,0.5-19.7,0.2-29.5v2.3c0,0-0.8-12.5,4.4-15.5c8-4.4,17.7-3.7,26.4-4.1
+	c11.3-0.5,22.7,0.6,34,0.6c6.6,0,13.1-0.1,19.5-1.6c4.5-1,9-1.4,13.6-1.2c5.6,0.4,11,1.8,16.5,2.3s10.9,0.2,16.4-0.1
+	c11.9-0.8,23.9-0.5,35.8,0l18.6,0.8c2.9,0.1,6.2,0.8,9,0.1l5.3-1.8c3.9-1.1,6.6-0.1,10.4,0.8l14,3.3c1.6-0.4,3.1-0.5,4.3,0.7
+	c2.6,2.4,0.1,5.4,2,8.1c2.3,3.2,4.4,5.3,4.9,9.4c0.5,4.4,0.7,9.2-0.7,13.5c-1,2.9-2.3,6-1.1,9.1c0.7,1.9,2,3.7,2.5,5.7
+	c1,4.3,1,8.6,0.7,13c-0.2,5-0.5,10.1-1.1,15c-0.4,3.2-1.7,6.3-1.8,9.4c-0.1,2.6,0.7,4.8,1.2,7.3c1.7,8.2-5.5,13.4-6.8,20.9
+	c-0.7,4.1,3.9,2.3,6,4.7c1.3,1.6,0.6,4.2,0.5,5.9c-0.5,4.4,0.8,8.7,0.6,13.1c-0.2,5.5-1.9,10-0.6,15.5c0.7,3.1,1.3,6.2,0.6,9.3
+	s-4.3,10.9,0.5,12.3c0.2,0,0.4,0.2,0.4,0.5c0,1.4-0.2,2.4-0.8,3.7c-2.1,4.8,1.2,3.8,1.8,6.9c0.6,2.9-1.1,3.9-1.6,6.6
+	c-0.7,4.3,1.6,7.6,1.4,11.8c-0.1,3.9-2.3,6.1-1.6,9.7c0.2,0,0.5,0.2,0.5,0.5c0.4,1.9,0.6,4.1-0.1,6c-1,2.7-2.1,3.5,1,5.1
+	C782.6,238.5,782.6,239.2,782.1,239.5L782.1,239.5z M729.8,106.8c-1.9-11.7-3-23.9-6.9-35.1c-1.7-4.7-2.5-9.8-8-10.7
+	c-7.2-1.2-4.8,4.8-3.8,10.5c1.4,8.8,2,17.8,4.3,26.5c1.6,6,4.5,13.1-3.5,8.5c-13.4-8.1-18-25.8-23.4-39c-2.4-6-9.4-20.3-14.2-23.9
+	c-2.3-1.7-13.6-4.2-15.8-2.7c-4.7,3.2,9.8,25.7,11.8,29.7c4.4,9,9.1,17.9,14.8,26c4.1,5.7,10.5,11,1.4,14.4
+	c-9.4,3.6-14-1.7-20.4-6.6c-5.1-3.8-11.6-4.4-18-5.1c-17.6-2.1-34.3-0.1-48.4,11.6c-6.8,5.7-11.1,13.5-14.7,21.5
+	c-1,2-6.8,19.7-6,19.8c-3.6-0.6-1.4-9-6-4.1c-4.1,4.3,1.2,16.7,3,21c4.1,9.6,8.4,19,11.6,29c3.2,10,3.9,9.8,13.1,10.6
+	c11.7,1.1,23.2-2.5,34.7-3.9c0,0-20.4-12.7,19.9,1.2c9,3.1,18.5,2.3,27.9,2.6c6.7-1.6,6.9-13.1,10.4-18.5c1.3-2,7.8-12.2,9.8-13
+	c2.9-1.1,7,1.9,9.9,2.3c16.8,2.6,22-11.2,19.3-24.7c-2.3-11.8-1-25,0.7-34.1C734,117.2,730.4,109.9,729.8,106.8L729.8,106.8z
+	 M654.7,250.4c-1.8-1.7-4.5-2.1-6.3-3.9c-1-1.1-1.3-2.1-1.1-3.3c0,0-0.1,0-0.1-0.1c-1.3-2.4,1.7-4.4,3.8-3.6
+	c1.1,0.5,2.7,1.2,2.9,2.5c0.1,1.7-1,2.9-1.3,4.4c1.2,0.7,2.4,2.4,3,3.2C656,250.3,655.2,250.9,654.7,250.4L654.7,250.4z"/>
+<g>
+	<path fill="#333333" d="M113.4,273.5l10.7,17.2h0.1v-17.2h5.3v25.7h-5.7L113.1,282H113v17.2h-5.3v-25.7H113.4z"/>
+	<path fill="#333333" d="M141.4,281.1c0.6-1.6,1.4-3.1,2.5-4.3c1.1-1.2,2.4-2.2,4-2.9s3.3-1,5.3-1c2,0,3.8,0.3,5.3,1
+		c1.6,0.7,2.9,1.7,4,2.9c1.1,1.2,1.9,2.7,2.5,4.3c0.6,1.6,0.9,3.4,0.9,5.3c0,1.9-0.3,3.6-0.9,5.2c-0.6,1.6-1.4,3-2.5,4.2
+		s-2.4,2.1-4,2.8c-1.6,0.7-3.3,1-5.3,1c-2,0-3.7-0.3-5.3-1c-1.6-0.7-2.9-1.6-4-2.8s-1.9-2.6-2.5-4.2s-0.9-3.4-0.9-5.2
+		C140.5,284.5,140.8,282.7,141.4,281.1z M146.5,289.6c0.3,1,0.7,1.9,1.2,2.8c0.6,0.8,1.3,1.5,2.2,2c0.9,0.5,2,0.7,3.2,0.7
+		c1.3,0,2.4-0.2,3.2-0.7c0.9-0.5,1.6-1.1,2.2-2c0.6-0.8,1-1.7,1.2-2.8c0.3-1,0.4-2.1,0.4-3.2c0-1.1-0.1-2.2-0.4-3.3s-0.7-2-1.2-2.8
+		c-0.6-0.8-1.3-1.5-2.2-2c-0.9-0.5-2-0.7-3.2-0.7c-1.3,0-2.4,0.2-3.2,0.7c-0.9,0.5-1.6,1.2-2.2,2s-1,1.8-1.2,2.8s-0.4,2.1-0.4,3.3
+		C146.2,287.5,146.3,288.6,146.5,289.6z"/>
+	<path fill="#333333" d="M187.9,273.5c1.7,0,3.2,0.3,4.6,0.8c1.4,0.5,2.7,1.3,3.7,2.4c1,1.1,1.9,2.4,2.4,4s0.9,3.4,0.9,5.6
+		c0,1.9-0.2,3.6-0.7,5.2c-0.5,1.6-1.2,3-2.2,4.1c-1,1.2-2.2,2.1-3.6,2.7c-1.5,0.7-3.2,1-5.1,1h-11.1v-25.7H187.9z M187.5,294.4
+		c0.8,0,1.6-0.1,2.4-0.4c0.8-0.3,1.5-0.7,2.1-1.3s1.1-1.4,1.4-2.4c0.4-1,0.5-2.2,0.5-3.6c0-1.3-0.1-2.5-0.4-3.5s-0.7-1.9-1.2-2.7
+		s-1.3-1.3-2.3-1.7c-0.9-0.4-2.1-0.6-3.5-0.6h-4v16.2H187.5z"/>
+	<path fill="#333333" d="M229.9,273.5v4.8h-13.6v5.5h12.5v4.4h-12.5v6.3h13.9v4.8h-19.5v-25.7H229.9z"/>
+	<path fill="#333333" d="M256.4,278.2v-4.8h21.1v4.8h-7.7v21h-5.7v-21H256.4z"/>
+	<path fill="#333333" d="M306.9,273.5v4.8h-13.6v5.5h12.5v4.4h-12.5v6.3h13.9v4.8h-19.5v-25.7H306.9z"/>
+	<path fill="#333333" d="M326.1,273.5l6,17.7h0.1l5.7-17.7h8v25.7h-5.3V281h-0.1l-6.3,18.2h-4.4l-6.3-18h-0.1v18h-5.3v-25.7H326.1z"
+		/>
+	<path fill="#333333" d="M369.6,273.5c1.6,0,3,0.2,4.1,0.7c1.1,0.5,2,1.1,2.8,1.9c0.7,0.8,1.2,1.6,1.5,2.6s0.5,2,0.5,3.1
+		c0,1-0.2,2-0.5,3c-0.3,1-0.8,1.9-1.5,2.6c-0.7,0.8-1.6,1.4-2.8,1.9c-1.1,0.5-2.5,0.7-4.1,0.7h-5.9v9.2H358v-25.7H369.6z
+		 M368.1,285.6c0.6,0,1.3,0,1.9-0.1c0.6-0.1,1.1-0.3,1.6-0.6c0.5-0.3,0.8-0.7,1.1-1.2c0.3-0.5,0.4-1.2,0.4-2c0-0.8-0.1-1.5-0.4-2
+		c-0.3-0.5-0.6-0.9-1.1-1.2c-0.5-0.3-1-0.5-1.6-0.6c-0.6-0.1-1.2-0.1-1.9-0.1h-4.4v7.7H368.1z"/>
+	<path fill="#333333" d="M394.9,273.5v21h12.5v4.8h-18.2v-25.7H394.9z"/>
+	<path fill="#333333" d="M430.6,273.5l9.6,25.7h-5.9l-1.9-5.7h-9.6l-2,5.7h-5.7l9.7-25.7H430.6z M430.9,289.2l-3.2-9.4h-0.1
+		l-3.3,9.4H430.9z"/>
+	<path fill="#333333" d="M444.3,278.2v-4.8h21.1v4.8h-7.7v21H452v-21H444.3z"/>
+	<path fill="#333333" d="M494.8,273.5v4.8h-13.6v5.5h12.5v4.4h-12.5v6.3H495v4.8h-19.5v-25.7H494.8z"/>
+	<path fill="#333333" d="M542.5,273.5v4.8h-13.6v5.5h12.5v4.4h-12.5v6.3h13.9v4.8h-19.5v-25.7H542.5z"/>
+	<path fill="#333333" d="M559.4,273.5l10.7,17.2h0.1v-17.2h5.3v25.7h-5.7L559.2,282h-0.1v17.2h-5.3v-25.7H559.4z"/>
+	<path fill="#333333" d="M602.9,299c-1.2,0.5-2.5,0.8-3.7,0.8c-2,0-3.7-0.3-5.3-1c-1.6-0.7-2.9-1.6-4-2.8c-1.1-1.2-1.9-2.6-2.5-4.2
+		s-0.9-3.4-0.9-5.2c0-1.9,0.3-3.7,0.9-5.3c0.6-1.6,1.4-3.1,2.5-4.3c1.1-1.2,2.4-2.2,4-2.9c1.6-0.7,3.3-1,5.3-1
+		c1.3,0,2.6,0.2,3.8,0.6c1.2,0.4,2.4,1,3.3,1.7c1,0.8,1.8,1.7,2.5,2.8c0.6,1.1,1,2.4,1.2,3.9h-5.4c-0.3-1.4-1-2.5-1.9-3.2
+		c-1-0.7-2.1-1.1-3.5-1.1c-1.3,0-2.4,0.2-3.2,0.7c-0.9,0.5-1.6,1.2-2.2,2c-0.6,0.8-1,1.8-1.2,2.8s-0.4,2.1-0.4,3.3
+		c0,1.1,0.1,2.1,0.4,3.2c0.3,1,0.7,1.9,1.2,2.8c0.6,0.8,1.3,1.5,2.2,2c0.9,0.5,2,0.7,3.2,0.7c1.9,0,3.3-0.5,4.3-1.4
+		c1-0.9,1.6-2.3,1.8-4.1h-5.7v-4.2h10.8v13.9h-3.6l-0.6-2.9C605.2,297.5,604.1,298.5,602.9,299z"/>
+	<path fill="#333333" d="M627.8,273.5v25.7h-5.7v-25.7H627.8z"/>
+	<path fill="#333333" d="M645.6,273.5l10.7,17.2h0.1v-17.2h5.3v25.7h-5.7L645.4,282h-0.1v17.2H640v-25.7H645.6z"/>
+	<path fill="#333333" d="M693.1,273.5v4.8h-13.6v5.5H692v4.4h-12.5v6.3h13.9v4.8h-19.5v-25.7H693.1z"/>
+</g>
+</svg>
diff --git a/docs/style/jade-logo.svg b/docs/style/jade-logo.svg
new file mode 100644
index 0000000..bf1da6d
--- /dev/null
+++ b/docs/style/jade-logo.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 260" enable-background="new 0 0 800 260"><path fill="#569A83" d="M118.9 21.8c3.5 0 5.1 1.7 5.1 5.1v171.5c0 6.3-1.2 12.4-3.7 18s-5.7 10.5-9.9 14.7c-4.2 4.2-9.1 7.5-14.7 9.9s-11.6 3.7-18 3.7h-14c-6.3 0-12.4-1.2-18-3.7-5.6-2.4-10.5-5.7-14.7-9.9s-7.5-9.1-9.9-14.7c-2.5-5.6-3.7-11.6-3.7-18v-39.3c0-3.5 1.6-5.6 4.8-6.6l33.4-8.8c3.2-1 4.8.2 4.8 3.7v49.2c0 2.5.8 4.5 2.6 6.2 1.7 1.7 3.8 2.6 6.2 2.6h3c2.4 0 4.5-.8 6.2-2.6 1.7-1.7 2.6-3.8 2.6-6 [...]
\ No newline at end of file
diff --git a/docs/style/logo.png b/docs/style/logo.png
deleted file mode 100644
index 1ce783e..0000000
Binary files a/docs/style/logo.png and /dev/null differ
diff --git a/docs/versions.json b/docs/versions.json
index 4cc33fc..9f433a5 100644
--- a/docs/versions.json
+++ b/docs/versions.json
@@ -5,5 +5,12 @@
   "1.4.2",
   "1.5.0",
   "1.6.0",
-  "1.7.0"
-]
\ No newline at end of file
+  "1.7.0",
+  "1.8.0",
+  "1.8.1",
+  "1.8.2",
+  "1.9.0",
+  "1.9.1",
+  "1.9.2",
+  "1.10.0"
+]
diff --git a/docs/views/api.jade b/docs/views/api.jade
index 8934979..98b9255 100644
--- a/docs/views/api.jade
+++ b/docs/views/api.jade
@@ -28,8 +28,8 @@ block content
       dd.type.string string
       dd.description If the doctype is not specified as part of the template, you can specify it here.  It is sometimes useful to get self-closing tags and remove mirroring of boolean attributes.
       dt pretty:
-      dd.type.boolean boolean
-      dd.description Adds whitespace to the resulting html to make it easier for a human to read
+      dd.type boolean | string
+      dd.description Adds whitespace to the resulting html to make it easier for a human to read using #[code '  '] as indentation. If a string is specified, that will be used as indentation instead (e.g. #[code '\t']).
       dt self:
       dd.type.boolean boolean
       dd.description Use a #[code self] namespace to hold the locals (false by default)
@@ -38,10 +38,19 @@ block content
       dd.description If set to true, the tokens and function body is logged to stdout
       dt compileDebug:
       dd.type.boolean boolean
-      dd.description Set this to false to disable debugging instrumentation (recommended in production).  Set it to true to include the function source in the compiled template for better error messages (sometimes useful in development).
+      dd.description If set to true, the function source will be included in the compiled template for better error messages (sometimes useful in development). It is enabled by default unless used with express in production mode.
+      dt cache:
+      dd.type.boolean boolean
+      dd.description If set to true, compiled functions are cached. #[code filename] must be set as the cache key.
       dt compiler:
       dd.type.function class
       dd.description Override the default compiler
+      dt parser:
+      dd.type.function class
+      dd.description Override the default parser
+      dt globals:
+      dd.type.array= 'Array.<string>'
+      dd.description Add a list of global names to make accessible in templates
     div }
 
   h3 jade.compile(source, options)
@@ -125,6 +134,111 @@ block content
       var html = fn(locals);
       // => 'function template(locals) { return "<string>of jade</string>"; }'
 
+  h3 jade.compileClientWithDependenciesTracked(source, options)
+
+  p See #[code jade.compileClient] except that this method returns an object of the form:
+
+  +js
+    :jssrc
+      {
+        'body': 'function (locals) {...}',
+        'dependencies': ['filename.jade']
+      }
+
+  p.
+    You should only use this method if you need to implement something
+    like watching for changes to the jade files.
+
+  h3 jade.compileFileClient(path, options)
+
+  p Compile a jade template file to a string of Javascript that can be used client side along with the jade runtime.
+
+  dl.parameter-list
+    dt path
+    dd.type.string string
+    dd.description The path to a jade file
+    dt options
+    dd.type.object options
+    dd.description An options object (see above)
+    dt options.name
+    dd.type.string string
+    dd.description.
+      If you pass a #[code .name] property on the options object, it will be
+      used as the function name for your client side template function.
+  dl.returns
+    dt returns
+    dd.type.string string
+    dd.description A Javascript function body.
+
+  p First, our template file.
+
+  +jade
+    :jadesrc
+      h1 This is a Jade template
+      h2 By \#{author}
+
+  p Then, we compile the jade file into a function string.
+
+  +js
+    :jssrc
+      var fs   = require('fs');
+      var jade = require('jade');
+
+      // Compile the template to a function string
+      var jsFunctionString = jade.compileFileClient('/path/to/jadeFile.jade', {name: "fancyTemplateFun"});
+
+      // Maybe you want to compile all of your templates to a templates.js file and serve it to the client
+      fs.writeFileSync("templates.js", jsFunctionString);
+
+  p Here's what the output function string looks like (written to #[code templates.js]).
+
+  +js
+    :jssrc
+      function fancyTemplateFun(locals) {
+        var buf = [];
+        var jade_mixins = {};
+        var jade_interp;
+
+        var locals_for_with = (locals || {});
+
+        (function (author) {
+          buf.push("<h1>This is a Jade template</h1><h2>By "
+            + (jade.escape((jade_interp = author) == null ? '' : jade_interp))
+            + "</h2>");
+        }.call(this, "author" in locals_for_with ?
+          locals_for_with.author : typeof author !== "undefined" ?
+            author : undefined)
+        );
+
+        return buf.join("");
+      }
+
+
+  p.
+    Be sure to send the Jade runtime (#[code node_modules/jade/runtime.js]) to
+    the client in addition to the template that you just compiled.
+
+  +html
+    :htmlsrc
+      <!DOCTYPE html>
+      <html>
+        <head>
+          <script src="/runtime.js"></script>
+          <script src="/templates.js"></script>
+        </head>
+
+        <body>
+          <h1>This is one fancy template.</h1>
+
+          <script type="text/javascript">
+            var html = window.fancyTemplateFun({author: "enlore"});
+            var div = document.createElement("div");
+            div.innerHTML = html;
+            document.body.appendChild(div);
+          </script>
+        </body>
+      </html>
+
   h3 jade.render(source, options)
 
   dl.parameter-list
diff --git a/docs/views/command-line.jade b/docs/views/command-line.jade
index d94f09c..c266815 100644
--- a/docs/views/command-line.jade
+++ b/docs/views/command-line.jade
@@ -19,16 +19,21 @@ block content
         ### Options
 
         ```
-        -h, --help         output usage information
-        -V, --version      output the version number
-        -O, --obj <str>    javascript options object
-        -o, --out <dir>    output the compiled html to <dir>
-        -p, --path <path>  filename used to resolve includes
-        -P, --pretty       compile pretty html output
-        -c, --client       compile function for client-side runtime.js
-        -D, --no-debug     compile without debugging (smaller functions)
-        -w, --watch        watch files for changes and automatically re-render
-        --doctype          specify the doctype if it is not already specified by the template
+        -h, --help             output usage information
+        -V, --version          output the version number
+        -O, --obj <path|str>   JavaScript options object or JSON file containing it
+        -o, --out <dir>        output the compiled html to <dir>
+        -p, --path <path>      filename used to resolve includes
+        -P, --pretty           compile pretty html output
+        -c, --client           compile function for client-side runtime.js
+        -n, --name <str>       the name of the compiled template (requires --client)
+        -D, --no-debug         compile without debugging (smaller functions)
+        -w, --watch            watch files for changes and automatically re-render
+        -E, --extension <ext>  specify the output file extension
+        --name-after-file      name the template after the last section of the file path
+                               (requires --client and overriden by --name)
+        --doctype <str>        specify the doctype on the command line (useful if it
+                               is not specified by the template)
         ```
 
         ### Examples
diff --git a/docs/views/layout.jade b/docs/views/layout.jade
index 5123cf7..12295db 100644
--- a/docs/views/layout.jade
+++ b/docs/views/layout.jade
@@ -12,8 +12,7 @@ html
       #content
         .logo-container
           a(href=path('/'))
-            img(src="/style/logo.png")
-            .sr-only Jade - Node Template Engine
+            img(src="/style/jade-logo-header.svg",alt="Jade - Node Template Engine")
         .navbar.navbar-default.navbar-static-top
           .container
             .navbar-header.visible-sm.visible-xs
@@ -35,9 +34,9 @@ html
               ul.nav.navbar-nav.navbar-right
                 li(class=section === 'history' ? 'active' : false)
                   a(href='/history') Change Log
-                li: a(href=path('/coverage/')) Code Coverage
-                li: a(href='https://travis-ci.org/visionmedia/jade') Test Results
-                li: a(href='https://github.com/visionmedia/jade') GitHub Repository
+                li: a(href='https://coveralls.io/r/jadejs/jade?branch=master') Code Coverage
+                li: a(href='https://travis-ci.org/jadejs/jade') Test Results
+                li: a(href='https://github.com/jadejs/jade') GitHub Repository
               block navigation
         .container
           block content
@@ -48,8 +47,10 @@ html
           p
             | Jade is a template language maintained by 
             a(href='http://www.forbeslindesay.co.uk') @ForbesLindesay
+            |  and contributed by 
+            a(href='https://github.com/jadejs/jade/graphs/contributors') many others
             |.
     include ./includes/ga.jade
     script(src=path("/client.js"))
-    script(src="http://code.jquery.com/jquery-2.1.1.min.js")
-    script(src="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js")
+    script(src="http://code.jquery.com/jquery-2.1.3.min.js")
+    script(src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js")
diff --git a/docs/views/reference.jade b/docs/views/reference.jade
index 9ddc36f..561e092 100644
--- a/docs/views/reference.jade
+++ b/docs/views/reference.jade
@@ -18,6 +18,7 @@ block content
         +link('filters')
         +link('includes')
         +link('inheritance')
+        +link('interpolation')
         +link('iteration')
         +link('mixins')
         +link('plain text')
@@ -33,4 +34,4 @@ block content
           li Supports reusability (DRY)
         p.
           To read about the features of the language, select them from
-          the navigation menu on the right.
+          the navigation menu on the left.
diff --git a/docs/views/reference/attributes.jade b/docs/views/reference/attributes.jade
index f037238..d7ec4c5 100644
--- a/docs/views/reference/attributes.jade
+++ b/docs/views/reference/attributes.jade
@@ -14,8 +14,7 @@ block documentation
     .col-lg-6
       +html
         :htmlsrc
-          <a href="google.com">Google</a>
-          <a class="button" href="google.com">Google</a>
+          <a href="google.com">Google</a><a class="button" href="google.com">Google</a>
 
   p All the normal JavaScript expressions work fine too:
 
@@ -67,8 +66,8 @@ block documentation
     .panel-body
       p.
         Unescaped buffered code can be dangerous.
-        You must be sure to sanatize any user inputs to avoid
-        #[a(href='http://en.wikipedia.org/wiki/Cross-site_scripting') Cross Site Scripting]
+        You must be sure to sanitize any user inputs to avoid
+        #[a(href='https://en.wikipedia.org/wiki/Cross-site_scripting') cross-site scripting].
 
   h2#booleanattribs Boolean Attributes
 
@@ -110,6 +109,21 @@ block documentation
           <input type="checkbox">
           <input type="checkbox" checked="checked">
 
+  h2 Style Attributes
+
+  p The #[code style] attribute can be a string (like any normal attribute) but it can also be an object, which is handy when parts of the style are generated by JavaScript.
+
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          a(style={color: 'red', background: 'green'})
+    .col-lg-6
+      +html
+        :htmlsrc
+          <a style="color:red;background:green"></a>
+
   h2 Class Attributes
 
   p The <code>class</code> attribute can be a string (like any normal attribute) but it can also be an array of class names, which is handy when generated from JavaScript.
@@ -125,8 +139,21 @@ block documentation
     .col-lg-6
       +html
         :htmlsrc
-          <a class="foo bar baz"></a>
-          <a class="foo bar baz bing"></a>
+          <a class="foo bar baz"></a><a class="foo bar baz bing"></a>
+
+  p It can also be an object mapping class names to true or false values, which is useful for applying conditional classes
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          - var currentUrl = '/about'
+          a(class={active: currentUrl === '/'} href='/') Home
+          a(class={active: currentUrl === '/about'} href='/about') About
+    .col-lg-6
+      +html
+        :htmlsrc
+          <a href="/">Home</a><a href="/about" class="active">About</a>
 
   h2 Class Literal
 
@@ -212,6 +239,6 @@ block documentation
     .panel-body
       p.
         Attributes applied using #[code &attributes] are not automatically escaped.
-        You must be sure to sanatize any user inputs to avoid
-        #[a(href='http://en.wikipedia.org/wiki/Cross-site_scripting') Cross Site Scripting].
+        You must be sure to sanitize any user inputs to avoid
+        #[a(href='https://en.wikipedia.org/wiki/Cross-site_scripting') cross-site scripting].
         This is done for you if you are passing in #[code attributes] from a mixin call.
diff --git a/docs/views/reference/code.jade b/docs/views/reference/code.jade
index a68d6a8..c133797 100644
--- a/docs/views/reference/code.jade
+++ b/docs/views/reference/code.jade
@@ -1,7 +1,7 @@
 extends ../reference.jade
 
 block documentation
-  h1 Code & Interpolation
+  h1 Code
 
   p Jade makes it possible to write inline JavaScript code in your templates.  There are three types of code.
 
@@ -21,6 +21,27 @@ block documentation
           <li>item</li>
           <li>item</li>
           <li>item</li>
+  
+  p Jade also supports block unbuffered code:
+  
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          -
+            list = ["Uno", "Dos", "Tres",
+                    "Cuatro", "Cinco", "Seis"]
+          each item in list
+            li= item
+    .col-lg-6
+      +html
+        :htmlsrc
+          <li>Uno</li>
+          <li>Dos</li>
+          <li>Tres</li>
+          <li>Cuatro</li>
+          <li>Cinco</li>
+          <li>Seis</li>
 
   h2 Buffered Code
 
@@ -72,7 +93,7 @@ block documentation
     .col-lg-6
       +jade
         :jadesrc
-          p!= 'This code is <strong>not</strong> escaped!'
+          p!= 'This code is' + ' <strong>not</strong> escaped!'
     .col-lg-6
       +html
         :htmlsrc
@@ -82,47 +103,5 @@ block documentation
     .panel-body
       p.
         Unescaped buffered code can be dangerous.
-        You must be sure to sanatize any user inputs to avoid
-        #[a(href='http://en.wikipedia.org/wiki/Cross-site_scripting') Cross Site Scripting]
-
-  h2 Interpolation
-
-  p.
-    Both plain-text and piped-text support interpolation. The following will output the user.name in the paragraph:
-
-  .row(data-control='interactive')
-    .col-lg-6
-      +jade
-        :jadesrc
-          - var user = {name: 'Forbes Lindesay'}
-          p Welcome \#{user.name}
-    .col-lg-6
-      +html
-        :htmlsrc
-          <p>Welcome Forbes Lindesay</p>
-
-  p It is generally recommended that you use #[code =] for buffering code unless it is a small amount of code in a large block.
-
-  h2 Unescaped Interpolation
-
-  p You can also use unescaped interpolation to output HTML:
-
-  .row(data-control='interactive')
-    .col-lg-6
-      +jade
-        :jadesrc
-          - var user = {name: '<strong>Forbes Lindesay</strong>'}
-          p Welcome \#{user.name}
-          p Welcome \!{user.name}
-    .col-lg-6
-      +html
-        :htmlsrc
-          <p>Welcome <strong>Forbes Lindesay</strong></p>
-          <p>Welcome <strong>Forbes Lindesay</strong></p>
-  .panel.panel-danger
-    .panel-heading Danger
-    .panel-body
-      p.
-        Unescaped buffered code can be dangerous.
-        You must be sure to sanatize any user inputs to avoid
-        #[a(href='http://en.wikipedia.org/wiki/Cross-site_scripting') Cross Site Scripting]
+        You must be sure to sanitize any user inputs to avoid
+        #[a(href='http://en.wikipedia.org/wiki/Cross-site_scripting') cross-site scripting].
diff --git a/docs/views/reference/comments.jade b/docs/views/reference/comments.jade
index fb57c10..0d72c31 100644
--- a/docs/views/reference/comments.jade
+++ b/docs/views/reference/comments.jade
@@ -61,7 +61,7 @@ block documentation
   p.
     Jade does not have any special syntax for conditional comments.
     If your line begins with #[code <] then it is treated as plain text.
-    So just use normal HTML syle conditional comments:
+    So just use normal HTML style conditional comments:
 
   .row(data-control='interactive')
     .col-lg-6
diff --git a/docs/views/reference/doctype.jade b/docs/views/reference/doctype.jade
index 4ac2b93..013d62f 100644
--- a/docs/views/reference/doctype.jade
+++ b/docs/views/reference/doctype.jade
@@ -46,7 +46,7 @@ block documentation
 
   p.
     The doctype affects compilation in some other cases, for example self closing tags
-    and #[a(href="http://localhost:3000/reference/attributes#booleanattribs") boolean attributes]).
+    and #[a(href="/reference/attributes#booleanattribs") boolean attributes]).
     For this reason, you might sometimes want to specify it manually. You can do this via the
     #[code doctype] option.  e.g.
 
diff --git a/docs/views/reference/filters.jade b/docs/views/reference/filters.jade
index 80d74f7..6145f74 100644
--- a/docs/views/reference/filters.jade
+++ b/docs/views/reference/filters.jade
@@ -16,7 +16,7 @@ block documentation
 
             I often like including markdown documents.
           script
-            :coffee
+            :coffee-script
               console.log 'This is coffee script'
     .col-lg-6
       +html
diff --git a/docs/views/reference/inheritance.jade b/docs/views/reference/inheritance.jade
index 83050e8..7c3fc09 100644
--- a/docs/views/reference/inheritance.jade
+++ b/docs/views/reference/inheritance.jade
@@ -5,10 +5,10 @@ block documentation
 
   p.
     Jade supports template inheritance via the #[code block] and #[code extends] keywords. A block is simply a "block"
-    of Jade that may be replaced within a child template, this process is recursive.
+    of Jade that may be replaced within a child template. This process is recursive.
 
   p.
-    Jade blocks can provide default content if desired, however optional as shown below by `block scripts`, `block content`, and `block foot`.
+    Jade blocks can provide default content if desired, however optional as shown below by #[code block scripts], #[code block content], and #[code block foot].
 
   p layout.jade
   +jade
@@ -24,7 +24,7 @@ block documentation
             #footer
               p some footer content
 
-  p Now to extend the layout, simply create a new file and use the `extends` directive as shown below, giving the path (with or without the .jade extension). You may now define one or more blocks that will override the parent block content, note that here the `foot` block is _not_ redefined and will output "some footer content".
+  p Now to extend the layout, simply create a new file and use the #[code extends] directive as shown below, giving the path (with or without the .jade extension). You may now define one or more blocks that will override the parent block content, note that here the #[code foot] block is #[em not] redefined and will output "some footer content".
 
   p page-a.jade
   +jade
@@ -41,7 +41,7 @@ block documentation
           include pet
 
   p.
-    It's also possible to override a block to provide additional blocks, as shown in the following example where `content` now exposes a `sidebar` and `primary` block for overriding, or the child template could override `content` all together.
+    It's also possible to override a block to provide additional blocks, as shown in the following example where #[code content] now exposes a #[code sidebar] and #[code primary] block for overriding, or the child template could override #[code content] all together.
 
   p sub-layout.jade
   +jade
@@ -83,7 +83,7 @@ block documentation
         body
           block content
 
-  p Now suppose you have a page of your application for a JavaScript game, you want some game related scripts as well as these defaults, you can simply `append` the block:
+  p Now suppose you have a page of your application for a JavaScript game, you want some game related scripts as well as these defaults, you can simply #[code append] the block:
 
   +jade
     :jadesrc
@@ -93,7 +93,7 @@ block documentation
         script(src='/vendor/three.js')
         script(src='/game.js')
 
-  p When using `block append` or `block prepend` the `block` is optional:
+  p When using #[code block append] or #[code block prepend] the #[code block] is optional:
 
   +jade
     :jadesrc
diff --git a/docs/views/reference/interpolation.jade b/docs/views/reference/interpolation.jade
new file mode 100644
index 0000000..ca4c60b
--- /dev/null
+++ b/docs/views/reference/interpolation.jade
@@ -0,0 +1,124 @@
+extends ../reference.jade
+
+block documentation
+  .row
+    .col-lg-8.col-lg-offset-2
+      .panel.panel-info
+        .panel-heading Hey!
+        .panel-body
+          p.
+            The Jade source code displayed in this, and many of the other pages
+            in these docs, is interactive. Edit it and see what happens!
+
+  h1 Interpolation
+
+  p.
+    Jade provides operators for a variety of your different interpolative
+    needs.
+
+  h2 String Interpolation, Escaped
+
+  p.
+    Consider the placement of the template locals #[code title], #[code author],
+    and #[code theGreat] in the following template.
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          - var title = "On Dogs: Man's Best Friend";
+          - var author = "enlore";
+          - var theGreat = "<span>escape!</span>";
+
+          h1= title
+          p Written with love by \#{author}
+          p This will be safe: \#{theGreat}
+
+    .col-lg-6
+      +html
+        :htmlsrc
+          <h1>On Dogs: Man's Best Friend</h1>
+          <p>Written with love by enlore</p>
+          <p>This will be safe: <span>escape!</span></p>
+
+  p.
+    #[code title] follows the basic pattern for evaluating a template local,
+    but the code in between <code>\#{</code> and <code>}</code> is evaluated,
+    escaped, and the result buffered into the output of the template being
+    rendered.
+
+  p.
+    This can be any valid Javascript expression, so you can do whatever feels good.
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          - var msg = "not my inside voice";
+          p This is \#{msg.toUpperCase()}
+
+    .col-lg-6
+      +html
+        :htmlsrc
+          <p>This is NOT MY INSIDE VOICE</p>
+
+  h2 String Interpolation, Unescaped
+
+  p.
+    You don't have to play it safe. You can buffer unescaped values into your
+    templates, too.
+
+  .row(data-control="interactive")
+    .col-lg-6
+      +jade
+        :jadesrc
+          - var riskyBusiness = "<em>Some of the girls are wearing my mother's clothing.</em>";
+          .quote
+            p Joel: \!{riskyBusiness}
+
+    .col-lg-6
+      +html
+        :htmlsrc
+          <div class="quote">
+            <p>Joel: <em>Some of the girls are wearing my mother's clothing.</em></p>
+          </div>
+
+  .row
+    .col-lg-8.col-lg-offset-2
+      .panel.panel-danger
+        .panel-heading Danger
+        .panel-body
+          p.
+            Keep in mind that buffering unescaped content into your templates
+            can be mighty risky if that content comes fresh from your users.
+            Never trust user input!
+
+  h2 Tag Interpolation
+
+  p.
+    If you take a look at this page's source #[a(target="_blank", href="https://github.com/jadejs/jade/blob/master/docs/views/reference/interpolation.jade") on GitHub], you'll see
+    several places where the tag interpolation operator is used, like so.
+
+  .row(data-control="interactive")
+    .col-lg-6
+      +jade
+        :jadesrc
+          p.
+            If you take a look at this page's source \#[a(target="_blank", href="https://github.com/jadejs/jade/blob/master/docs/views/reference/interpolation.jade") on GitHub],
+            you'll see several places where the tag interpolation operator is
+            used, like so.
+
+
+    .col-lg-6
+      +html
+        :htmlsrc
+          <p>If you take a look at this page's source <a href="https://github.com/jadejs/jade/blob/master/docs/views/reference/interpolation.jade">on GitHub</a>,
+            you'll see several places where the tag interpolation operator is
+            used, like so.
+          </p>
+
+  p.
+    You could accomplish the same thing by writing an HTML tag inline with your
+    Jade, but then what's the point of writing the Jade? Wrap an inline Jade
+    tag declaration in <code>\#[</code> and <code>]</code> and it'll be evaluated
+    and buffered into the content of its containing tag.
diff --git a/examples/attributes.js b/examples/attributes.js
index f4dd730..7e90a89 100644
--- a/examples/attributes.js
+++ b/examples/attributes.js
@@ -3,7 +3,7 @@
  * Module dependencies.
  */
 
-var jade = require('./../')
+var jade = require('../')
   , path = __dirname + '/attributes.jade'
   , str = require('fs').readFileSync(path, 'utf8')
   , fn = jade.compile(str, { filename: path, pretty: true });
diff --git a/examples/client-compilation.jade b/examples/client-compilation.jade
new file mode 100644
index 0000000..ac674ea
--- /dev/null
+++ b/examples/client-compilation.jade
@@ -0,0 +1,145 @@
+//-
+  You can serve this file using a simple Express.js server
+  examples/client-compilation.js.
+
+doctype html
+html
+  head
+    title Jade compilation demo
+    style.
+      * {
+        box-sizing: border-box;
+      }
+      iframe, pre, textarea {
+        height: auto;
+        border: 1px solid #a9a9a9;
+        width: 100%;
+        max-height: 600px;
+        min-height: 120px;
+      }
+      /* Preformatted <textarea> */
+      textarea.pre {
+        font-family: monospace;
+        font-size: 1em;
+      }
+      pre {
+        padding: 2px;
+        overflow: auto;
+        width: auto;
+      }
+      /* Prettier button (half-)stolen from Bootstrap */
+      button {
+        padding: 8px 16px;
+        font-size: 19px;
+        border: 1px solid transparent;
+        border-radius: 5px;
+        background-color: #fff;
+        border-color: #ccc;
+        cursor: pointer;
+      }
+      button:hover,
+      button:focus,
+      button:active {
+        color: #333;
+        background-color: #e6e6e6;
+        border-color: #adadad;
+      }
+
+  body
+    h1
+      | Client Compilation Example 
+      button(type='submit', onclick='comp()') Submit
+
+    p.
+      This example shows how to compile and render Jade templates in the
+      browser.
+    p.
+      Note that although it is supported, we do not #[em recommend] compiling
+      in the browser; rather, you should pre-compile the template using
+      #[code jade.compileClient] or #[code jade.compileFileClient].
+
+    h2 Input template
+    textarea#input.pre(rows='8').
+      doctype html
+
+      html
+        body
+          p I love you.
+          p: small From \#[em= name].
+
+    h2 Compilation options (JSON)
+    textarea#options.pre(rows='8').
+      {
+        "pretty": true
+      }
+
+    h2 Locals (JSON)
+    textarea#locals.pre(rows='8').
+      {
+        "name": "Timothy"
+      }
+
+    h2 Raw HTML
+    pre#output
+
+    h2 Rendered HTML
+    iframe#output-html
+
+    script(src='jade.js')
+    script.
+      comp();
+
+      // Handle compilation and injection of Jade scripts
+      function comp() {
+        // Get the elements
+        var // input Jade template
+            templ   = document.getElementById('input')
+            // JSON locals
+          , locals  = document.getElementById('locals')
+            // JSON options
+          , options = document.getElementById('options')
+            // output text area
+          , output  = document.getElementById('output')
+            // rendered HTML
+          , html    = document.getElementById('output-html')
+          , fn;
+
+        // Handle errors by catching it, print the message to the output HTML
+        // box, and throwing it again so that it's visible on the JS console.
+        // `stage` sets the corresponding error context.
+        function handleError(func, stage) {
+          try {
+            // Try executing callback
+            func();
+          } catch (e) {
+            // If there is an exception:
+            // 1. Change output heading to "Error when ..."
+            outputTitle.innerHTML = 'Error ' + stage;
+            // 2. Put the stack trace into the output text area
+            output.textContent = e.stack || e.message || e.toString();
+            // 3. Rethrow it
+            throw e;
+          }
+        }
+
+        // Parse options as JSON
+        handleError(function () {
+          options = JSON.parse(options.value || '{}');
+        }, 'when parsing options');
+
+        // Parse locals as JSON
+        handleError(function () {
+          locals = JSON.parse(locals.value || '{}');
+        }, 'when parsing locals');
+
+        // Compile template with specified template and options
+        handleError(function () {
+          fn = jade.compile(templ.value, options);
+        }, 'when compiling');
+
+        // Put the rendered HTML into the text area
+        handleError(function () {
+          output.textContent = fn(locals);
+          html.srcdoc = fn(locals);
+        }, 'when executing template function');
+      }
diff --git a/examples/client-compilation.js b/examples/client-compilation.js
new file mode 100644
index 0000000..aa61b26
--- /dev/null
+++ b/examples/client-compilation.js
@@ -0,0 +1,22 @@
+// Miniserver for client-compilation.jade
+
+'use strict';
+
+var PORT = 3000;
+
+var express = require('express');
+var jade = require('../');
+
+var app = express();
+
+app.set('view engine', 'jade');
+app.engine('jade', jade.renderFile);
+app.set('views', __dirname);
+
+app.get('/', function (req, res, next) {
+  res.render('client-compilation');
+});
+app.use('/jade.js', express.static(__dirname + '/../jade.js'));
+
+app.listen(PORT);
+console.log('Server started at http://127.0.0.1:' + PORT + '/');
diff --git a/examples/code.jade b/examples/code.jade
index 8904c4d..d0cab66 100644
--- a/examples/code.jade
+++ b/examples/code.jade
@@ -1,7 +1,12 @@
 
 - var title = "Things"
 
+-
+  var subtitle = ["Really",  "long",
+                  "list", "of",
+                  "words"]
 h1= title
+h2= subtitle.join(" ")
 
 ul#users
   each user, name in users
diff --git a/examples/code.js b/examples/code.js
index 9a35d1f..75fbb79 100644
--- a/examples/code.js
+++ b/examples/code.js
@@ -3,7 +3,7 @@
  * Module dependencies.
  */
 
-var jade = require('./../')
+var jade = require('../')
   , path = __dirname + '/code.jade'
   , str = require('fs').readFileSync(path, 'utf8')
   , fn = jade.compile(str, { filename: path, pretty: true });
diff --git a/examples/csrf.jade b/examples/csrf.jade
deleted file mode 100644
index 283986b..0000000
--- a/examples/csrf.jade
+++ /dev/null
@@ -1,5 +0,0 @@
-
-form(method='post', action='/')
-  input(type='text', name='user[name]')
-  input(type='text', name='user[email]')
-  input(type='submit', value='Submit')
diff --git a/examples/csrf.js b/examples/csrf.js
deleted file mode 100644
index 21abf60..0000000
--- a/examples/csrf.js
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-/**
- * Module dependencies.
- */
-
-var jade = require('./../lib/jade'),
-    Compiler = jade.Compiler,
-    nodes = jade.nodes;
-
-var options = {
-    compiler: CSRF
-  , locals: {
-    csrf: 'WAHOOOOOO'
-  }
-};
-
-jade.renderFile(__dirname + '/csrf.jade', options, function(err, html){
-    if (err) throw err;
-    console.log(html);
-});
-
-function CSRF(node, options) {
-  Compiler.call(this, node, options);
-}
-
-CSRF.prototype.__proto__ = Compiler.prototype;
-
-CSRF.prototype.visitTag = function(node){
-  var parent = Compiler.prototype.visitTag;
-  switch (node.name) {
-    case 'form':
-      if ("'post'" == node.getAttribute('method')) {
-        var tok = new nodes.Tag('input');
-        tok.setAttribute('type', '"hidden"');
-        tok.setAttribute('name', '"csrf"');
-        tok.setAttribute('value', 'csrf');
-        node.block.unshift(tok);
-      }
-  }
-  parent.call(this, node);
-};
diff --git a/examples/dynamicscript.js b/examples/dynamicscript.js
index d2b3a0d..a0e48ce 100644
--- a/examples/dynamicscript.js
+++ b/examples/dynamicscript.js
@@ -3,18 +3,14 @@
  * Module dependencies.
  */
 
-var jade = require('./../lib/jade');
+var jade = require('../');
 
-var options = {
-    locals: {
-        users: {
-            tj: { age: 23, email: 'tj at vision-media.ca', isA: 'human' },
-            tobi: { age: 1, email: 'tobi at is-amazing.com', isA: 'ferret' }
-        }
-    }
+var locals = {
+  users: {
+    tj: { age: 23, email: 'tj at vision-media.ca', isA: 'human' },
+    tobi: { age: 1, email: 'tobi at is-amazing.com', isA: 'ferret' }
+  }
 };
 
-jade.renderFile(__dirname + '/dynamicscript.jade', options, function(err, html){
-    if (err) throw err;
-    console.log(html);
-});
\ No newline at end of file
+var fn = jade.compileFile(__dirname + '/dynamicscript.jade');
+console.log(fn(locals));
diff --git a/examples/each.js b/examples/each.js
index 0f3cce5..c083377 100644
--- a/examples/each.js
+++ b/examples/each.js
@@ -3,7 +3,7 @@
  * Module dependencies.
  */
 
-var jade = require('./../')
+var jade = require('../')
   , path = __dirname + '/each.jade'
   , str = require('fs').readFileSync(path, 'utf8')
   , fn = jade.compile(str, { filename: path, pretty: true });
diff --git a/examples/extend.js b/examples/extend.js
index 604798d..8609be0 100644
--- a/examples/extend.js
+++ b/examples/extend.js
@@ -3,7 +3,7 @@
  * Module dependencies.
  */
 
-var jade = require('./../')
+var jade = require('../')
   , path = __dirname + '/extend.jade'
   , str = require('fs').readFileSync(path, 'utf8')
   , fn = jade.compile(str, { filename: path, pretty: true });
diff --git a/examples/includes.js b/examples/includes.js
index 6c062d6..671f0cb 100644
--- a/examples/includes.js
+++ b/examples/includes.js
@@ -3,7 +3,7 @@
  * Module dependencies.
  */
 
-var jade = require('./../')
+var jade = require('../')
   , path = __dirname + '/includes.jade'
   , str = require('fs').readFileSync(path, 'utf8')
   , fn = jade.compile(str, { filename: path, pretty: true });
diff --git a/examples/layout-debug.js b/examples/layout-debug.js
index ca0d03b..a08a8f3 100644
--- a/examples/layout-debug.js
+++ b/examples/layout-debug.js
@@ -3,9 +3,9 @@
  * Module dependencies.
  */
 
-var jade = require('./../lib/jade');
+var jade = require('../');
 
 jade.renderFile(__dirname + '/layout.jade', { debug: true }, function(err, html){
     if (err) throw err;
     console.log(html);
-});
\ No newline at end of file
+});
diff --git a/examples/layout.js b/examples/layout.js
index ab20635..3a99db9 100644
--- a/examples/layout.js
+++ b/examples/layout.js
@@ -3,7 +3,7 @@
  * Module dependencies.
  */
 
-var jade = require('./../')
+var jade = require('../')
   , path = __dirname + '/layout.jade'
   , str = require('fs').readFileSync(path, 'utf8')
   , fn = jade.compile(str, { filename: path, pretty: true });
diff --git a/examples/mixins.jade b/examples/mixins.jade
index 49603a9..5861ce1 100644
--- a/examples/mixins.jade
+++ b/examples/mixins.jade
@@ -1,16 +1,14 @@
-
 include mixins/dialog
 include mixins/profile
 
 .one
-  mixin dialog
+  +dialog
 
 .two
-  mixin dialog-title('Whoop')
+  +dialog-title('Whoop')
 
 .three
-  mixin dialog-title-desc('Whoop', 'Just a mixin')
-
+  +dialog-title-desc('Whoop', 'Just a mixin')
 
 #profile
-  mixin profile(user)
+  +profile(user)
diff --git a/examples/mixins.js b/examples/mixins.js
index 6b8d64d..15a80fb 100644
--- a/examples/mixins.js
+++ b/examples/mixins.js
@@ -3,7 +3,7 @@
  * Module dependencies.
  */
 
-var jade = require('./../')
+var jade = require('../')
   , path = __dirname + '/mixins.jade'
   , str = require('fs').readFileSync(path, 'utf8')
   , fn = jade.compile(str, { filename: path, pretty: true });
diff --git a/examples/rss.js b/examples/rss.js
index 861986a..e59cbed 100644
--- a/examples/rss.js
+++ b/examples/rss.js
@@ -3,7 +3,7 @@
  * Module dependencies.
  */
 
-var jade = require('./../')
+var jade = require('../')
   , path = __dirname + '/rss.jade'
   , str = require('fs').readFileSync(path, 'utf8')
   , fn = jade.compile(str, { filename: path, pretty: true });
diff --git a/examples/text.js b/examples/text.js
index 503d563..1bd8113 100644
--- a/examples/text.js
+++ b/examples/text.js
@@ -3,7 +3,7 @@
  * Module dependencies.
  */
 
-var jade = require('./../')
+var jade = require('../')
   , path = __dirname + '/text.jade'
   , str = require('fs').readFileSync(path, 'utf8')
   , fn = jade.compile(str, { filename: path, pretty: true });
diff --git a/examples/whitespace.js b/examples/whitespace.js
index 1651d13..dd027e1 100644
--- a/examples/whitespace.js
+++ b/examples/whitespace.js
@@ -3,7 +3,7 @@
  * Module dependencies.
  */
 
-var jade = require('./../')
+var jade = require('../')
   , path = __dirname + '/whitespace.jade'
   , str = require('fs').readFileSync(path, 'utf8')
   , fn = jade.compile(str, { filename: path, pretty: true });
diff --git a/index.js b/index.js
deleted file mode 100644
index cf7d1a6..0000000
--- a/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-
-module.exports = require('./lib/jade');
diff --git a/lib/compiler.js b/lib/compiler.js
index 83e5299..6dff5e3 100644
--- a/lib/compiler.js
+++ b/lib/compiler.js
@@ -35,6 +35,9 @@ var Compiler = module.exports = function Compiler(node, options) {
   this.hasCompiledDoctype = false;
   this.hasCompiledTag = false;
   this.pp = options.pretty || false;
+  if (this.pp && typeof this.pp !== 'string') {
+    this.pp = '  ';
+  }
   this.debug = false !== options.compileDebug;
   this.indents = 0;
   this.parentIndents = 0;
@@ -176,7 +179,7 @@ Compiler.prototype = {
   prettyIndent: function(offset, newline){
     offset = offset || 0;
     newline = newline ? '\n' : '';
-    this.buffer(newline + Array(this.indents + offset).join('  '));
+    this.buffer(newline + Array(this.indents + offset).join(this.pp));
     if (this.parentIndents)
       this.buf.push("buf.push.apply(buf, jade_indent);");
   },
@@ -192,11 +195,11 @@ Compiler.prototype = {
     var debug = this.debug;
 
     if (debug) {
-      this.buf.push('jade_debug.unshift({ lineno: ' + node.line
-        + ', filename: ' + (node.filename
+      this.buf.push('jade_debug.unshift(new jade.DebugItem( ' + node.line
+        + ', ' + (node.filename
           ? utils.stringify(node.filename)
           : 'jade_debug[0].filename')
-        + ' });');
+        + ' ));');
     }
 
     // Massive hack to fix our context
@@ -304,7 +307,7 @@ Compiler.prototype = {
    */
 
   visitMixinBlock: function(block){
-    if (this.pp) this.buf.push("jade_indent.push('" + Array(this.indents + 1).join('  ') + "');");
+    if (this.pp) this.buf.push("jade_indent.push('" + Array(this.indents + 1).join(this.pp) + "');");
     this.buf.push('block && block();');
     if (this.pp) this.buf.push("jade_indent.pop();");
   },
@@ -340,7 +343,7 @@ Compiler.prototype = {
     var args = mixin.args || '';
     var block = mixin.block;
     var attrs = mixin.attrs;
-    var attrsBlocks = mixin.attributeBlocks;
+    var attrsBlocks = mixin.attributeBlocks.slice();
     var pp = this.pp;
     var dynamic = mixin.name[0]==='#';
     var key = mixin.name;
@@ -350,7 +353,7 @@ Compiler.prototype = {
     this.mixins[key] = this.mixins[key] || {used: false, instances: []};
     if (mixin.call) {
       this.mixins[key].used = true;
-      if (pp) this.buf.push("jade_indent.push('" + Array(this.indents + 1).join('  ') + "');")
+      if (pp) this.buf.push("jade_indent.push('" + Array(this.indents + 1).join(pp) + "');")
       if (block || attrs.length || attrsBlocks.length) {
 
         this.buf.push(name + '.call({');
@@ -401,7 +404,9 @@ Compiler.prototype = {
       if (args.length && /^\.\.\./.test(args[args.length - 1].trim())) {
         rest = args.pop().trim().replace(/^\.\.\./, '');
       }
-      this.buf.push(name + ' = function(' + args.join(',') + '){');
+      // we need use jade_interp here for v8: https://code.google.com/p/v8/issues/detail?id=4165
+      // once fixed, use this: this.buf.push(name + ' = function(' + args.join(',') + '){');
+      this.buf.push(name + ' = jade_interp = function(' + args.join(',') + '){');
       this.buf.push('var block = (this && this.block), attributes = (this && this.attributes) || {};');
       if (rest) {
         this.buf.push('var ' + rest + ' = [];');
@@ -450,10 +455,10 @@ Compiler.prototype = {
     if (pp && !tag.isInline())
       this.prettyIndent(0, true);
 
-    if (tag.selfClosing || (!this.xml && selfClosing.indexOf(tag.name) !== -1)) {
+    if (tag.selfClosing || (!this.xml && selfClosing[tag.name])) {
       this.buffer('<');
       bufferName();
-      this.visitAttributes(tag.attrs, tag.attributeBlocks);
+      this.visitAttributes(tag.attrs, tag.attributeBlocks.slice());
       this.terse
         ? this.buffer('>')
         : this.buffer('/>');
@@ -469,7 +474,7 @@ Compiler.prototype = {
       // Optimize attributes buffering
       this.buffer('<');
       bufferName();
-      this.visitAttributes(tag.attrs, tag.attributeBlocks);
+      this.visitAttributes(tag.attrs, tag.attributeBlocks.slice());
       this.buffer('>');
       if (tag.code) this.visitCode(tag.code);
       this.visit(tag.block);
@@ -563,7 +568,7 @@ Compiler.prototype = {
 
     // Buffer code
     if (code.buffer) {
-      var val = code.val.trimLeft();
+      var val = code.val.trim();
       val = 'null == (jade_interp = '+val+') ? "" : jade_interp';
       if (code.escape) val = 'jade.escape(' + val + ')';
       this.bufferExpression(val);
@@ -669,6 +674,7 @@ Compiler.prototype = {
           this.buffer(runtime.attr(key, toConstant(attr.val), escaped, this.terse));
         } else {
           var val = toConstant(attr.val);
+          if (key === 'style') val = runtime.style(val);
           if (escaped && !(key.indexOf('data') === 0 && typeof val !== 'string')) {
             val = runtime.escape(val);
           }
@@ -679,6 +685,9 @@ Compiler.prototype = {
           this.bufferExpression('jade.attr("' + key + '", ' + attr.val + ', ' + utils.stringify(escaped) + ', ' + utils.stringify(this.terse) + ')');
         } else {
           var val = attr.val;
+          if (key === 'style') {
+            val = 'jade.style(' + val + ')';
+          }
           if (escaped && !(key.indexOf('data') === 0)) {
             val = 'jade.escape(' + val + ')';
           } else if (escaped) {
diff --git a/lib/filters.js b/lib/filters.js
index 5a8c468..f89be4a 100644
--- a/lib/filters.js
+++ b/lib/filters.js
@@ -1,14 +1,96 @@
 'use strict';
 
 var transformers = require('transformers');
+var jstransformer = require('jstransformer');
+var uglify = require('uglify-js');
+var CleanCSS = require('clean-css');
+
+var warned = {};
+var alternatives = {
+  uglifyJS: 'uglify-js',
+  uglify: 'uglify-js',
+  uglifyCSS: 'clean-css',
+  'uglify-css': 'clean-css' ,
+  uglifyJSON: 'json',
+  'uglify-json': 'json',
+  live: 'livescript',
+  LiveScript: 'livescript',
+  ls: 'livescript',
+  // TODO: remove if we add support for coffeekup
+  coffeekup: 'coffeecup',
+  // The `style` transformer is not the same as the `stylus` jstransformer
+  styl: 'stylus',
+  coffee: 'coffee-script',
+  coffeescript: 'coffee-script',
+  coffeeScript: 'coffee-script',
+  // these marker transformers haven't made sense in a long time
+  css: 'verbatim',
+  js: 'verbatim',
+};
+var deprecated = ['jqtpl', 'jazz'];
+function getMarkdownImplementation() {
+  var implementations = ['marked', 'supermarked', 'markdown-js', 'markdown'];
+  while (implementations.length) {
+    try {
+      require(implementations[0]);
+      return implementations[0];
+    } catch (ex) {
+      implementations.shift();
+    }
+  }
+  return 'markdown-it';
+}
 
 module.exports = filter;
 function filter(name, str, options) {
   if (typeof filter[name] === 'function') {
     return filter[name](str, options);
-  } else if (transformers[name]) {
-    return transformers[name].renderSync(str, options);
   } else {
-    throw new Error('unknown filter ":' + name + '"');
+    var tr;
+    try {
+      tr = jstransformer(require('jstransformer-' + name));
+    } catch (ex) {}
+    if (tr) {
+      // TODO: we may want to add a way for people to separately specify "locals"
+      var result = tr.render(str, options, options).body;
+      if (options && options.minify) {
+        try {
+          switch (tr.outputFormat) {
+            case 'js':
+              result = uglify.minify(result, {fromString: true}).code;
+              break;
+            case 'css':
+              result = new CleanCSS().minify(result).styles;
+              break;
+          }
+        } catch (ex) {
+          // better to fail to minify than output nothing
+        }
+      }
+      return result;
+    } else if (transformers[name]) {
+      if (!warned[name]) {
+        warned[name] = true;
+        if (name === 'md' || name === 'markdown') {
+          var implementation = getMarkdownImplementation();
+          console.log('Transformers.' + name + ' is deprecated, you must replace the :' +
+                      name + ' jade filter, with :' +
+                      implementation + ' and install jstransformer-' +
+                      implementation + ' before you update to jade at 2.0.0.');
+        } else if (alternatives[name]) {
+          console.log('Transformers.' + name + ' is deprecated, you must replace the :' +
+                      name + ' jade filter, with :' +
+                      alternatives[name] + ' and install jstransformer-' +
+                      alternatives[name] + ' before you update to jade at 2.0.0.');
+        } else {
+          console.log('Transformers.' + name + ' is deprecated, to continue using the :' +
+                      name + ' jade filter after jade at 2.0.0, you will need to install jstransformer-' +
+                      name.toLowerCase() + '.');
+        }
+      }
+      return transformers[name].renderSync(str, options);
+    } else {
+      throw new Error('unknown filter ":' + name + '"');
+    }
   }
 }
diff --git a/lib/index.js b/lib/index.js
deleted file mode 120000
index 6a783c2..0000000
--- a/lib/index.js
+++ /dev/null
@@ -1 +0,0 @@
-jade.js
\ No newline at end of file
diff --git a/lib/index.js b/lib/index.js
new file mode 100644
index 0000000..7d734e5
--- /dev/null
+++ b/lib/index.js
@@ -0,0 +1,418 @@
+'use strict';
+
+/*!
+ * Jade
+ * Copyright(c) 2010 TJ Holowaychuk <tj at vision-media.ca>
+ * MIT Licensed
+ */
+
+/**
+ * Module dependencies.
+ */
+
+var Parser = require('./parser')
+  , Lexer = require('./lexer')
+  , Compiler = require('./compiler')
+  , runtime = require('./runtime')
+  , addWith = require('with')
+  , fs = require('fs')
+  , utils = require('./utils');
+
+/**
+ * Expose self closing tags.
+ */
+
+// FIXME: either stop exporting selfClosing in v2 or export the new object
+// form
+exports.selfClosing = Object.keys(require('void-elements'));
+
+/**
+ * Default supported doctypes.
+ */
+
+exports.doctypes = require('./doctypes');
+
+/**
+ * Text filters.
+ */
+
+exports.filters = require('./filters');
+
+/**
+ * Utilities.
+ */
+
+exports.utils = utils;
+
+/**
+ * Expose `Compiler`.
+ */
+
+exports.Compiler = Compiler;
+
+/**
+ * Expose `Parser`.
+ */
+
+exports.Parser = Parser;
+
+/**
+ * Expose `Lexer`.
+ */
+
+exports.Lexer = Lexer;
+
+/**
+ * Nodes.
+ */
+
+exports.nodes = require('./nodes');
+
+/**
+ * Jade runtime helpers.
+ */
+
+exports.runtime = runtime;
+
+/**
+ * Template function cache.
+ */
+
+exports.cache = {};
+
+/**
+ * Parse the given `str` of jade and return a function body.
+ *
+ * @param {String} str
+ * @param {Object} options
+ * @return {Object}
+ * @api private
+ */
+
+function parse(str, options){
+
+  if (options.lexer) {
+    console.warn('Using `lexer` as a local in render() is deprecated and '
+               + 'will be interpreted as an option in Jade 2.0.0');
+  }
+
+  // Parse
+  var parser = new (options.parser || Parser)(str, options.filename, options);
+  var tokens;
+  try {
+    // Parse
+    tokens = parser.parse();
+  } catch (err) {
+    parser = parser.context();
+    runtime.rethrow(err, parser.filename, parser.lexer.lineno, parser.input);
+  }
+
+  // Compile
+  var compiler = new (options.compiler || Compiler)(tokens, options);
+  var js;
+  try {
+    js = compiler.compile();
+  } catch (err) {
+    if (err.line && (err.filename || !options.filename)) {
+      runtime.rethrow(err, err.filename, err.line, parser.input);
+    } else {
+      if (err instanceof Error) {
+        err.message += '\n\nPlease report this entire error and stack trace to https://github.com/jadejs/jade/issues';
+      }
+      throw err;
+    }
+  }
+
+  // Debug compiler
+  if (options.debug) {
+    console.error('\nCompiled Function:\n\n\u001b[90m%s\u001b[0m', js.replace(/^/gm, '  '));
+  }
+
+  var globals = [];
+
+  if (options.globals) {
+    globals = options.globals.slice();
+  }
+
+  globals.push('jade');
+  globals.push('jade_mixins');
+  globals.push('jade_interp');
+  globals.push('jade_debug');
+  globals.push('buf');
+
+  var body = ''
+    + 'var buf = [];\n'
+    + 'var jade_mixins = {};\n'
+    + 'var jade_interp;\n'
+    + (options.self
+      ? 'var self = locals || {};\n' + js
+      : addWith('locals || {}', '\n' + js, globals)) + ';'
+    + 'return buf.join("");';
+  return {body: body, dependencies: parser.dependencies};
+}
+
+/**
+ * Get the template from a string or a file, either compiled on-the-fly or
+ * read from cache (if enabled), and cache the template if needed.
+ *
+ * If `str` is not set, the file specified in `options.filename` will be read.
+ *
+ * If `options.cache` is true, this function reads the file from
+ * `options.filename` so it must be set prior to calling this function.
+ *
+ * @param {Object} options
+ * @param {String=} str
+ * @return {Function}
+ * @api private
+ */
+function handleTemplateCache (options, str) {
+  var key = options.filename;
+  if (options.cache && exports.cache[key]) {
+    return exports.cache[key];
+  } else {
+    if (str === undefined) str = fs.readFileSync(options.filename, 'utf8');
+    var templ = exports.compile(str, options);
+    if (options.cache) exports.cache[key] = templ;
+    return templ;
+  }
+}
+
+/**
+ * Compile a `Function` representation of the given jade `str`.
+ *
+ * Options:
+ *
+ *   - `compileDebug` when `false` debugging code is stripped from the compiled
+       template, when it is explicitly `true`, the source code is included in
+       the compiled template for better accuracy.
+ *   - `filename` used to improve errors when `compileDebug` is not `false` and to resolve imports/extends
+ *
+ * @param {String} str
+ * @param {Options} options
+ * @return {Function}
+ * @api public
+ */
+
+exports.compile = function(str, options){
+  var options = options || {}
+    , filename = options.filename
+      ? utils.stringify(options.filename)
+      : 'undefined'
+    , fn;
+
+  str = String(str);
+
+  var parsed = parse(str, options);
+  if (options.compileDebug !== false) {
+    fn = [
+        'var jade_debug = [ new jade.DebugItem( 1, ' + filename + ' ) ];'
+      , 'try {'
+      , parsed.body
+      , '} catch (err) {'
+      , '  jade.rethrow(err, jade_debug[0].filename, jade_debug[0].lineno' + (options.compileDebug === true ? ',' + utils.stringify(str) : '') + ');'
+      , '}'
+    ].join('\n');
+  } else {
+    fn = parsed.body;
+  }
+  fn = new Function('locals, jade', fn)
+  var res = function(locals){ return fn(locals, Object.create(runtime)) };
+  if (options.client) {
+    res.toString = function () {
+      var err = new Error('The `client` option is deprecated, use the `jade.compileClient` method instead');
+      err.name = 'Warning';
+      console.error(err.stack || /* istanbul ignore next */ err.message);
+      return exports.compileClient(str, options);
+    };
+  }
+  res.dependencies = parsed.dependencies;
+  return res;
+};
+
+/**
+ * Compile a JavaScript source representation of the given jade `str`.
+ *
+ * Options:
+ *
+ *   - `compileDebug` When it is `true`, the source code is included in
+ *     the compiled template for better error messages.
+ *   - `filename` used to improve errors when `compileDebug` is not `true` and to resolve imports/extends
+ *   - `name` the name of the resulting function (defaults to "template")
+ *
+ * @param {String} str
+ * @param {Options} options
+ * @return {Object}
+ * @api public
+ */
+
+exports.compileClientWithDependenciesTracked = function(str, options){
+  var options = options || {};
+  var name = options.name || 'template';
+  var filename = options.filename ? utils.stringify(options.filename) : 'undefined';
+  var fn;
+
+  str = String(str);
+  options.compileDebug = options.compileDebug ? true : false;
+  var parsed = parse(str, options);
+  if (options.compileDebug) {
+    fn = [
+        'var jade_debug = [ new jade.DebugItem( 1, ' + filename + ' ) ];'
+      , 'try {'
+      , parsed.body
+      , '} catch (err) {'
+      , '  jade.rethrow(err, jade_debug[0].filename, jade_debug[0].lineno, ' + utils.stringify(str) + ');'
+      , '}'
+    ].join('\n');
+  } else {
+    fn = parsed.body;
+  }
+
+  return {body: 'function ' + name + '(locals) {\n' + fn + '\n}', dependencies: parsed.dependencies};
+};
+
+/**
+ * Compile a JavaScript source representation of the given jade `str`.
+ *
+ * Options:
+ *
+ *   - `compileDebug` When it is `true`, the source code is included in
+ *     the compiled template for better error messages.
+ *   - `filename` used to improve errors when `compileDebug` is not `true` and to resolve imports/extends
+ *   - `name` the name of the resulting function (defaults to "template")
+ *
+ * @param {String} str
+ * @param {Options} options
+ * @return {String}
+ * @api public
+ */
+exports.compileClient = function (str, options) {
+  return exports.compileClientWithDependenciesTracked(str, options).body;
+};
+
+/**
+ * Compile a `Function` representation of the given jade file.
+ *
+ * Options:
+ *
+ *   - `compileDebug` when `false` debugging code is stripped from the compiled
+       template, when it is explicitly `true`, the source code is included in
+       the compiled template for better accuracy.
+ *
+ * @param {String} path
+ * @param {Options} options
+ * @return {Function}
+ * @api public
+ */
+exports.compileFile = function (path, options) {
+  options = options || {};
+  options.filename = path;
+  return handleTemplateCache(options);
+};
+
+/**
+ * Render the given `str` of jade.
+ *
+ * Options:
+ *
+ *   - `cache` enable template caching
+ *   - `filename` filename required for `include` / `extends` and caching
+ *
+ * @param {String} str
+ * @param {Object|Function} options or fn
+ * @param {Function|undefined} fn
+ * @returns {String}
+ * @api public
+ */
+
+exports.render = function(str, options, fn){
+  // support callback API
+  if ('function' == typeof options) {
+    fn = options, options = undefined;
+  }
+  if (typeof fn === 'function') {
+    var res
+    try {
+      res = exports.render(str, options);
+    } catch (ex) {
+      return fn(ex);
+    }
+    return fn(null, res);
+  }
+
+  options = options || {};
+
+  // cache requires .filename
+  if (options.cache && !options.filename) {
+    throw new Error('the "filename" option is required for caching');
+  }
+
+  return handleTemplateCache(options, str)(options);
+};
+
+/**
+ * Render a Jade file at the given `path`.
+ *
+ * @param {String} path
+ * @param {Object|Function} options or callback
+ * @param {Function|undefined} fn
+ * @returns {String}
+ * @api public
+ */
+
+exports.renderFile = function(path, options, fn){
+  // support callback API
+  if ('function' == typeof options) {
+    fn = options, options = undefined;
+  }
+  if (typeof fn === 'function') {
+    var res
+    try {
+      res = exports.renderFile(path, options);
+    } catch (ex) {
+      return fn(ex);
+    }
+    return fn(null, res);
+  }
+
+  options = options || {};
+
+  options.filename = path;
+  return handleTemplateCache(options)(options);
+};
+
+
+/**
+ * Compile a Jade file at the given `path` for use on the client.
+ *
+ * @param {String} path
+ * @param {Object} options
+ * @returns {String}
+ * @api public
+ */
+
+exports.compileFileClient = function(path, options){
+  var key = path + ':client';
+  options = options || {};
+
+  options.filename = path;
+
+  if (options.cache && exports.cache[key]) {
+    return exports.cache[key];
+  }
+
+  var str = fs.readFileSync(options.filename, 'utf8');
+  var out = exports.compileClient(str, options);
+  if (options.cache) exports.cache[key] = out;
+  return out;
+};
+
+/**
+ * Express support.
+ */
+
+exports.__express = function(path, options, fn) {
+  if(options.compileDebug == undefined && process.env.NODE_ENV === 'production') {
+    options.compileDebug = false;
+  }
+  exports.renderFile(path, options, fn);
+}
diff --git a/lib/jade.js b/lib/jade.js
deleted file mode 100644
index 2281679..0000000
--- a/lib/jade.js
+++ /dev/null
@@ -1,371 +0,0 @@
-'use strict';
-
-/*!
- * Jade
- * Copyright(c) 2010 TJ Holowaychuk <tj at vision-media.ca>
- * MIT Licensed
- */
-
-/**
- * Module dependencies.
- */
-
-var Parser = require('./parser')
-  , Lexer = require('./lexer')
-  , Compiler = require('./compiler')
-  , runtime = require('./runtime')
-  , addWith = require('with')
-  , fs = require('fs')
-  , utils = require('./utils');
-
-/**
- * Expose self closing tags.
- */
-
-exports.selfClosing = require('void-elements');
-
-/**
- * Default supported doctypes.
- */
-
-exports.doctypes = require('./doctypes');
-
-/**
- * Text filters.
- */
-
-exports.filters = require('./filters');
-
-/**
- * Utilities.
- */
-
-exports.utils = require('./utils');
-
-/**
- * Expose `Compiler`.
- */
-
-exports.Compiler = Compiler;
-
-/**
- * Expose `Parser`.
- */
-
-exports.Parser = Parser;
-
-/**
- * Expose `Lexer`.
- */
-
-exports.Lexer = Lexer;
-
-/**
- * Nodes.
- */
-
-exports.nodes = require('./nodes');
-
-/**
- * Jade runtime helpers.
- */
-
-exports.runtime = runtime;
-
-/**
- * Template function cache.
- */
-
-exports.cache = {};
-
-/**
- * Parse the given `str` of jade and return a function body.
- *
- * @param {String} str
- * @param {Object} options
- * @return {Object}
- * @api private
- */
-
-function parse(str, options){
-  // Parse
-  var parser = new (options.parser || Parser)(str, options.filename, options);
-  var tokens;
-  try {
-    // Parse
-    tokens = parser.parse();
-  } catch (err) {
-    parser = parser.context();
-    runtime.rethrow(err, parser.filename, parser.lexer.lineno, parser.input);
-  }
-
-  // Compile
-  var compiler = new (options.compiler || Compiler)(tokens, options);
-  var js;
-  try {
-    js = compiler.compile();
-  } catch (err) {
-    if (err.line && (err.filename || !options.filename)) {
-      runtime.rethrow(err, err.filename, err.line, parser.input);
-    }
-  }
-
-  // Debug compiler
-  if (options.debug) {
-    console.error('\nCompiled Function:\n\n\u001b[90m%s\u001b[0m', js.replace(/^/gm, '  '));
-  }
-
-  var globals = [];
-
-  if (options.globals) {
-    globals = options.globals.slice();
-  }
-
-  globals.push('jade');
-  globals.push('jade_mixins');
-  globals.push('jade_interp');
-  globals.push('jade_debug');
-  globals.push('buf');
-
-  var body = ''
-    + 'var buf = [];\n'
-    + 'var jade_mixins = {};\n'
-    + 'var jade_interp;\n'
-    + (options.self
-      ? 'var self = locals || {};\n' + js
-      : addWith('locals || {}', '\n' + js, globals)) + ';'
-    + 'return buf.join("");';
-  return {body: body, dependencies: parser.dependencies};
-}
-
-/**
- * Compile a `Function` representation of the given jade `str`.
- *
- * Options:
- *
- *   - `compileDebug` when `false` debugging code is stripped from the compiled
-       template, when it is explicitly `true`, the source code is included in
-       the compiled template for better accuracy.
- *   - `filename` used to improve errors when `compileDebug` is not `false` and to resolve imports/extends
- *
- * @param {String} str
- * @param {Options} options
- * @return {Function}
- * @api public
- */
-
-exports.compile = function(str, options){
-  var options = options || {}
-    , filename = options.filename
-      ? utils.stringify(options.filename)
-      : 'undefined'
-    , fn;
-
-  str = String(str);
-
-  var parsed = parse(str, options);
-  if (options.compileDebug !== false) {
-    fn = [
-        'var jade_debug = [{ lineno: 1, filename: ' + filename + ' }];'
-      , 'try {'
-      , parsed.body
-      , '} catch (err) {'
-      , '  jade.rethrow(err, jade_debug[0].filename, jade_debug[0].lineno' + (options.compileDebug === true ? ',' + utils.stringify(str) : '') + ');'
-      , '}'
-    ].join('\n');
-  } else {
-    fn = parsed.body;
-  }
-  fn = new Function('locals, jade', fn)
-  var res = function(locals){ return fn(locals, Object.create(runtime)) };
-  if (options.client) {
-    res.toString = function () {
-      var err = new Error('The `client` option is deprecated, use the `jade.compileClient` method instead');
-      err.name = 'Warning';
-      console.error(err.stack || err.message);
-      return exports.compileClient(str, options);
-    };
-  }
-  res.dependencies = parsed.dependencies;
-  return res;
-};
-
-/**
- * Compile a JavaScript source representation of the given jade `str`.
- *
- * Options:
- *
- *   - `compileDebug` When it is `true`, the source code is included in
- *     the compiled template for better error messages.
- *   - `filename` used to improve errors when `compileDebug` is not `true` and to resolve imports/extends
- *   - `name` the name of the resulting function (defaults to "template")
- *
- * @param {String} str
- * @param {Options} options
- * @return {String}
- * @api public
- */
-
-exports.compileClient = function(str, options){
-  var options = options || {};
-  var name = options.name || 'template';
-  var filename = options.filename ? utils.stringify(options.filename) : 'undefined';
-  var fn;
-
-  str = String(str);
-
-  if (options.compileDebug) {
-    options.compileDebug = true;
-    fn = [
-        'var jade_debug = [{ lineno: 1, filename: ' + filename + ' }];'
-      , 'try {'
-      , parse(str, options).body
-      , '} catch (err) {'
-      , '  jade.rethrow(err, jade_debug[0].filename, jade_debug[0].lineno, ' + utils.stringify(str) + ');'
-      , '}'
-    ].join('\n');
-  } else {
-    options.compileDebug = false;
-    fn = parse(str, options).body;
-  }
-
-  return 'function ' + name + '(locals) {\n' + fn + '\n}';
-};
-
-/**
- * Compile a `Function` representation of the given jade file.
- *
- * Options:
- *
- *   - `compileDebug` when `false` debugging code is stripped from the compiled
-       template, when it is explicitly `true`, the source code is included in
-       the compiled template for better accuracy.
- *
- * @param {String} path
- * @param {Options} options
- * @return {Function}
- * @api public
- */
-exports.compileFile = function (path, options) {
-  options = options || {};
-
-  var key = path + ':string';
-
-  options.filename = path;
-  var str = options.cache
-    ? exports.cache[key] || (exports.cache[key] = fs.readFileSync(path, 'utf8'))
-    : fs.readFileSync(path, 'utf8');
-
-  return options.cache
-    ? exports.cache[path] || (exports.cache[path] = exports.compile(str, options))
-    : exports.compile(str, options);
-};
-
-/**
- * Render the given `str` of jade.
- *
- * Options:
- *
- *   - `cache` enable template caching
- *   - `filename` filename required for `include` / `extends` and caching
- *
- * @param {String} str
- * @param {Object|Function} options or fn
- * @param {Function|undefined} fn
- * @returns {String}
- * @api public
- */
-
-exports.render = function(str, options, fn){
-  // support callback API
-  if ('function' == typeof options) {
-    fn = options, options = undefined;
-  }
-  if (typeof fn === 'function') {
-    var res
-    try {
-      res = exports.render(str, options);
-    } catch (ex) {
-      return fn(ex);
-    }
-    return fn(null, res);
-  }
-
-  options = options || {};
-
-  // cache requires .filename
-  if (options.cache && !options.filename) {
-    throw new Error('the "filename" option is required for caching');
-  }
-
-  var path = options.filename;
-  var tmpl = options.cache
-    ? exports.cache[path] || (exports.cache[path] = exports.compile(str, options))
-    : exports.compile(str, options);
-  return tmpl(options);
-};
-
-/**
- * Render a Jade file at the given `path`.
- *
- * @param {String} path
- * @param {Object|Function} options or callback
- * @param {Function|undefined} fn
- * @returns {String}
- * @api public
- */
-
-exports.renderFile = function(path, options, fn){
-  // support callback API
-  if ('function' == typeof options) {
-    fn = options, options = undefined;
-  }
-  if (typeof fn === 'function') {
-    var res
-    try {
-      res = exports.renderFile(path, options);
-    } catch (ex) {
-      return fn(ex);
-    }
-    return fn(null, res);
-  }
-
-  options = options || {};
-
-  var key = path + ':string';
-
-  options.filename = path;
-  var str = options.cache
-    ? exports.cache[key] || (exports.cache[key] = fs.readFileSync(path, 'utf8'))
-    : fs.readFileSync(path, 'utf8');
-  return exports.render(str, options);
-};
-
-
-/**
- * Compile a Jade file at the given `path` for use on the client.
- *
- * @param {String} path
- * @param {Object} options
- * @returns {String}
- * @api public
- */
-
-exports.compileFileClient = function(path, options){
-  options = options || {};
-
-  var key = path + ':string';
-
-  options.filename = path;
-  var str = options.cache
-    ? exports.cache[key] || (exports.cache[key] = fs.readFileSync(path, 'utf8'))
-    : fs.readFileSync(path, 'utf8');
-
-  return exports.compileClient(str, options);
-};
-
-/**
- * Express support.
- */
-
-exports.__express = exports.renderFile;
diff --git a/lib/lexer.js b/lib/lexer.js
index f4b3551..061fdef 100644
--- a/lib/lexer.js
+++ b/lib/lexer.js
@@ -198,12 +198,7 @@ Lexer.prototype = {
 
   interpolation: function() {
     if (/^#\{/.test(this.input)) {
-      var match;
-      try {
-        match = this.bracketExpression(1);
-      } catch (ex) {
-        return;//not an interpolation expression, just an unmatched open interpolation
-      }
+      var match = this.bracketExpression(1);
 
       this.consume(match.end + 1);
       return this.tok('interpolation', match.src);
@@ -223,6 +218,10 @@ Lexer.prototype = {
         name = name.slice(0, -1);
         tok = this.tok('tag', name);
         this.defer(this.tok(':'));
+        if (this.input[0] !== ' ') {
+          console.warn('Warning: space required after `:` on line ' + this.lineno +
+              ' of jade file "' + this.filename + '"');
+        }
         while (' ' == this.input[0]) this.input = this.input.substr(1);
       } else {
         tok = this.tok('tag', name);
@@ -456,12 +455,7 @@ Lexer.prototype = {
         tok = this.tok('call', captures[3]);
       } else {
         // interpolated call
-        var match;
-        try {
-          match = this.bracketExpression(2 + captures[1].length);
-        } catch (ex) {
-          return;//not an interpolation expression, just an unmatched open interpolation
-        }
+        var match = this.bracketExpression(2 + captures[1].length);
         this.consume(match.end + 1);
         assertExpression(match.src);
         tok = this.tok('call', '#{'+match.src+'}');
@@ -469,14 +463,13 @@ Lexer.prototype = {
 
       // Check for args (not attributes)
       if (captures = /^ *\(/.exec(this.input)) {
-        try {
-          var range = this.bracketExpression(captures[0].length - 1);
-          if (!/^\s*[-\w]+ *=/.test(range.src)) { // not attributes
-            this.consume(range.end + 1);
-            tok.args = range.src;
-          }
-        } catch (ex) {
-          //not a bracket expcetion, just unmatched open parens
+        var range = this.bracketExpression(captures[0].length - 1);
+        if (!/^\s*[-\w]+ *=/.test(range.src)) { // not attributes
+          this.consume(range.end + 1);
+          tok.args = range.src;
+        }
+        if (tok.args) {
+          assertExpression('[' + tok.args + ']');
         }
       }
 
@@ -593,6 +586,21 @@ Lexer.prototype = {
     }
   },
 
+
+  /**
+   * Block code.
+   */
+
+  blockCode: function() {
+    var captures;
+    if (captures = /^-\n/.exec(this.input)) {
+      this.consume(captures[0].length - 1);
+      var tok = this.tok('blockCode');
+      this.pipeless = true;
+      return tok;
+    }
+  },
+
   /**
    * Attributes.
    */
@@ -644,7 +652,7 @@ Lexer.prototype = {
           return str[i] === ','
         } else if (loc === 'value' && !state.isNesting()) {
           try {
-            Function('', 'return (' + val + ');');
+            assertExpression(val);
             if (str[i] === ' ' || str[i] === '\n') {
               for (var x = i; x < str.length; x++) {
                 if (str[x] != ' ' && str[x] != '\n') {
@@ -852,6 +860,7 @@ Lexer.prototype = {
         if (isMatch) {
           // consume test along with `\n` prefix if match
           this.consume(str.length + 1);
+          ++this.lineno;
           tokens.push(str.substr(indent.length));
         }
       } while(this.input.length && isMatch);
@@ -865,7 +874,13 @@ Lexer.prototype = {
    */
 
   colon: function() {
-    return this.scan(/^: */, ':');
+    var good = /^: +/.test(this.input);
+    var res = this.scan(/^: */, ':');
+    if (res && !good) {
+      console.warn('Warning: space required after `:` on line ' + this.lineno +
+          ' of jade file "' + this.filename + '"');
+    }
+    return res;
   },
 
   fail: function () {
@@ -917,6 +932,7 @@ Lexer.prototype = {
       || this["while"]()
       || this.tag()
       || this.filter()
+      || this.blockCode()
       || this.code()
       || this.id()
       || this.className()
diff --git a/lib/parser.js b/lib/parser.js
index b479f83..ff79571 100644
--- a/lib/parser.js
+++ b/lib/parser.js
@@ -27,7 +27,7 @@ var Parser = exports = module.exports = function Parser(str, filename, options){
   this.mixins = {};
   this.options = options;
   this.contexts = [this];
-  this.inMixin = false;
+  this.inMixin = 0;
   this.dependencies = [];
   this.inBlock = 0;
 };
@@ -233,6 +233,8 @@ Parser.prototype = {
         return this.parseEach();
       case 'code':
         return this.parseCode();
+      case 'blockCode':
+        return this.parseBlockCode();
       case 'call':
         return this.parseCall();
       case 'interpolation':
@@ -297,6 +299,7 @@ Parser.prototype = {
     this.expect('indent');
     while ('outdent' != this.peek().type) {
       switch (this.peek().type) {
+        case 'comment':
         case 'newline':
           this.advance();
           break;
@@ -376,6 +379,25 @@ Parser.prototype = {
   },
 
   /**
+   * block code
+   */
+
+  parseBlockCode: function(){
+    var tok = this.expect('blockCode');
+    var node;
+    var body = this.peek();
+    var text;
+    if (body.type === 'pipeless-text') {
+      this.advance();
+      text = body.val.join('\n');
+    } else {
+      text = '';
+    }
+      node = new nodes.Code(text, false, false);
+      return node;
+  },
+
+  /**
    * comment
    */
 
@@ -633,10 +655,10 @@ Parser.prototype = {
 
     // definition
     if ('indent' == this.peek().type) {
-      this.inMixin = true;
+      this.inMixin++;
       mixin = new nodes.Mixin(name, args, this.block(), false);
       this.mixins[name] = mixin;
-      this.inMixin = false;
+      this.inMixin--;
       return mixin;
     // call
     } else {
@@ -811,7 +833,7 @@ Parser.prototype = {
 
     // block?
     if (tag.textOnly) {
-      tag.block = this.parseTextBlock();
+      tag.block = this.parseTextBlock() || new nodes.Block();
     } else if ('indent' == this.peek().type) {
       var block = this.block();
       for (var i = 0, len = block.nodes.length; i < len; ++i) {
diff --git a/lib/runtime.js b/lib/runtime.js
index 8fd22fd..8b4fa25 100644
--- a/lib/runtime.js
+++ b/lib/runtime.js
@@ -60,7 +60,9 @@ function nulls(val) {
  */
 exports.joinClasses = joinClasses;
 function joinClasses(val) {
-  return Array.isArray(val) ? val.map(joinClasses).filter(nulls).join(' ') : val;
+  return (Array.isArray(val) ? val.map(joinClasses) :
+    (val && typeof val === 'object') ? Object.keys(val).filter(function (key) { return val[key]; }) :
+    [val]).filter(nulls).join(' ');
 }
 
 /**
@@ -87,6 +89,16 @@ exports.cls = function cls(classes, escaped) {
   }
 };
 
+
+exports.style = function (val) {
+  if (val && typeof val === 'object') {
+    return Object.keys(val).map(function (style) {
+      return style + ':' + val[style];
+    }).join(';');
+  } else {
+    return val;
+  }
+};
 /**
  * Render the given attribute.
  *
@@ -97,6 +109,9 @@ exports.cls = function cls(classes, escaped) {
  * @return {String}
  */
 exports.attr = function attr(key, val, escaped, terse) {
+  if (key === 'style') {
+    val = exports.style(val);
+  }
   if ('boolean' == typeof val || null == val) {
     if (val) {
       return ' ' + (terse ? key : key + '="' + key + '"');
@@ -104,10 +119,24 @@ exports.attr = function attr(key, val, escaped, terse) {
       return '';
     }
   } else if (0 == key.indexOf('data') && 'string' != typeof val) {
+    if (JSON.stringify(val).indexOf('&') !== -1) {
+      console.warn('Since Jade 2.0.0, ampersands (`&`) in data attributes ' +
+                   'will be escaped to `&`');
+    };
+    if (val && typeof val.toISOString === 'function') {
+      console.warn('Jade will eliminate the double quotes around dates in ' +
+                   'ISO form after 2.0.0');
+    }
     return ' ' + key + "='" + JSON.stringify(val).replace(/'/g, ''') + "'";
   } else if (escaped) {
+    if (val && typeof val.toISOString === 'function') {
+      console.warn('Jade will stringify dates in ISO form after 2.0.0');
+    }
     return ' ' + key + '="' + exports.escape(val) + '"';
   } else {
+    if (val && typeof val.toISOString === 'function') {
+      console.warn('Jade will stringify dates in ISO form after 2.0.0');
+    }
     return ' ' + key + '="' + val + '"';
   }
 };
@@ -150,12 +179,21 @@ exports.attrs = function attrs(obj, terse){
  * @api private
  */
 
-exports.escape = function escape(html){
-  var result = String(html)
-    .replace(/&/g, '&')
-    .replace(/</g, '<')
-    .replace(/>/g, '>')
-    .replace(/"/g, '"');
+var jade_encode_html_rules = {
+  '&': '&',
+  '<': '<',
+  '>': '>',
+  '"': '"'
+};
+var jade_match_html = /[&<>"]/g;
+
+function jade_encode_char(c) {
+  return jade_encode_html_rules[c] || c;
+}
+
+exports.escape = jade_escape;
+function jade_escape(html){
+  var result = String(html).replace(jade_match_html, jade_encode_char);
   if (result === '' + html) return html;
   else return result;
 };
@@ -201,3 +239,8 @@ exports.rethrow = function rethrow(err, filename, lineno, str){
     + '\n' + context + '\n\n' + err.message;
   throw err;
 };
+
+exports.DebugItem = function DebugItem(lineno, filename) {
+  this.lineno = lineno;
+  this.filename = filename;
+}
diff --git a/package.json b/package.json
index 9a72d0e..a94f582 100644
--- a/package.json
+++ b/package.json
@@ -1,60 +1,73 @@
 {
   "name": "jade",
-  "description": "Jade template engine",
-  "version": "1.7.0",
+  "description": "A clean, whitespace-sensitive template language for writing HTML",
+  "version": "1.11.0",
   "author": "TJ Holowaychuk <tj at vision-media.ca>",
   "maintainers": [
-    "forbeslindesay <forbes at lindesay.co.uk>",
-    "bloodyowl <mlbli at me.com",
-    "jbnicolai <joshua at jbna.nl>"
+    "Forbes Lindesay <forbes at lindesay.co.uk>",
+    "Matthias Le Brun <mlbli at me.com>",
+    "Joshua Appelman <joshua at jbna.nl>",
+    "Jonathan Ong <jonathanrichardong at gmail.com>",
+    "Alex Kocharin <alex at kocharin.ru>",
+    "Hemanth <hemanth.hm at gmail.com>",
+    "Timothy Gu <timothygu99 at gmail.com>",
+    "Andreas Lubbe <git at lubbe.org>"
   ],
   "license": "MIT",
   "repository": {
     "type": "git",
-    "url": "git://github.com/visionmedia/jade"
+    "url": "git://github.com/jadejs/jade"
   },
-  "main": "./index.js",
+  "main": "lib",
   "bin": {
     "jade": "./bin/jade.js"
   },
   "dependencies": {
-    "character-parser": "1.2.0",
-    "commander": "2.1.0",
-    "constantinople": "~2.0.0",
+    "character-parser": "1.2.1",
+    "clean-css": "^3.1.9",
+    "commander": "~2.6.0",
+    "constantinople": "~3.0.1",
+    "jstransformer": "0.0.2",
     "mkdirp": "~0.5.0",
-    "monocle": "1.1.51",
     "transformers": "2.1.0",
-    "void-elements": "~1.0.0",
-    "with": "~3.0.0"
+    "uglify-js": "^2.4.19",
+    "void-elements": "~2.0.1",
+    "with": "~4.0.0"
   },
   "devDependencies": {
-    "coffee-script": "*",
-    "mocha": "*",
-    "istanbul": "*",
-    "markdown": "*",
-    "stylus": "*",
-    "should": "*",
-    "less": "*",
-    "uglify-js": "*",
     "browserify": "*",
-    "linify": "*",
-    "less-file": "0.0.8",
-    "express": "~3.4.8",
-    "browserify-middleware": "~2.4.0",
-    "twbs": "0.0.6",
-    "highlight-codemirror": "~3.20.0",
-    "inconsolata": "0.0.2",
-    "jade-code-mirror": "~1.0.5",
+    "browserify-middleware": "~4.1.0",
     "code-mirror": "~3.22.0",
+    "coffee-script": "*",
+    "coveralls": "^2.11.2",
+    "express": "~4.10.4",
+    "github-basic": "^4.1.2",
     "handle": "~1.0.0",
+    "highlight-codemirror": "~4.1.0",
+    "inconsolata": "0.0.2",
+    "istanbul": "*",
+    "jade-code-mirror": "~1.0.5",
     "jade-highlighter": "~1.0.5",
-    "marked": "~0.3.2",
-    "stop": "^3.0.0-rc1",
+    "jstransformer-cdata": "0.0.3",
+    "jstransformer-coffee-script": "0.0.2",
+    "jstransformer-less": "^1.0.0",
+    "jstransformer-marked": "0.0.1",
+    "jstransformer-stylus": "0.0.1",
+    "jstransformer-verbatim": "0.0.2",
+    "less": "<2.0.0",
+    "less-file": "0.0.9",
+    "linify": "*",
+    "lsr": "^1.0.0",
+    "marked": "~0.3.3",
+    "mocha": "*",
     "opener": "^1.3.0",
-    "github-basic": "^3.0.0",
     "pull-request": "^3.0.0",
-    "lsr": "^1.0.0",
-    "rimraf": "^2.2.8"
+    "rimraf": "^2.2.8",
+    "should": "*",
+    "stop": "^3.0.0-rc1",
+    "stylus": "*",
+    "twbs": "0.0.6",
+    "uglify-js": "*"
   },
   "component": {
     "scripts": {
@@ -62,15 +75,20 @@
     }
   },
   "scripts": {
-    "test": "mocha -R spec && npm run coverage",
-    "coverage": "istanbul cover node_modules/mocha/bin/_mocha",
+    "test": "mocha -R spec",
+    "precoverage": "rimraf coverage && rimraf cov-pt*",
+    "coverage": "istanbul cover --report none --dir cov-pt0 node_modules/mocha/bin/_mocha -- -R dot",
+    "postcoverage": "istanbul report --include cov-pt\\*/coverage.json && rimraf cov-pt*",
+    "coveralls": "npm run coverage && cat ./coverage/lcov.info | coveralls",
     "prepublish": "npm prune && linify transform bin && npm run build",
     "build": "npm run compile",
     "compile": "npm run compile-full && npm run compile-runtime",
-    "compile-full": "browserify ./lib/jade.js --standalone jade -x ./node_modules/transformers > jade.js",
+    "compile-full": "browserify ./lib/index.js --standalone jade -x ./node_modules/transformers > jade.js",
     "compile-runtime": "browserify ./lib/runtime.js --standalone jade > runtime.js"
   },
   "browser": {
+    "fs": false,
     "./lib/filters.js": "./lib/filters-client.js"
-  }
+  },
+  "homepage": "http://jade-lang.com"
 }
diff --git a/release.js b/release.js
index d7e52e3..2ef23b4 100644
--- a/release.js
+++ b/release.js
@@ -1,18 +1,10 @@
 'use strict';
 
-var GITHUB_CLIENT_ID = '2031dbe958e741775201';
-var GITHUB_CLIENT_SECRET = 'd509a1e5e89248ce5d4211cb06995edcd979667d';
-var SCOPE = 'public_repo';
-
 var fs = require('fs');
-var qs = require('querystring');
-var crypto = require('crypto');
-var express = require('express');
-var opener = require('opener');
-var github = require('github-basic');
 var pr = require('pull-request');
 var readdirp = require('lsr').sync;
 
+var TOKEN = JSON.parse(fs.readFileSync(__dirname + '/.release.json', 'utf8'));
 
 // todo: check that the version is a new un-released version
 // todo: check the user has commit access to the github repo
@@ -22,67 +14,22 @@ var readdirp = require('lsr').sync;
 var version = require('./package.json').version;
 var compiledWebsite = require('./docs/stop.js');
 
-function getToken(gotToken) {
-  try {
-    var settings = JSON.parse(fs.readFileSync(__dirname + '/.release.json', 'utf8'));
-    return gotToken(settings.token);
-  } catch (ex) {
-    // use server to initialize config
-  }
-
-  var app = express();
-
-  var state = crypto.randomBytes(8).toString('hex');
-  var server = null;
-
-  app.get('/', function (req, res, next) {
-    if (req.query.code) return next();
-    res.redirect('https://github.com/login/oauth/authorize?client_id=' + GITHUB_CLIENT_ID
-        + '&scope=' + SCOPE
-        + '&redirect_uri=http://localhost:1337/'
-        + '&state=' + state);
+compiledWebsite.then(function () {
+  var fileUpdates = readdirp(__dirname + '/docs/out').filter(function (info) {
+    return info.isFile();
+  }).map(function (info) {
+    return {
+      path: info.path.replace(/^\.\//, ''),
+      content: fs.readFileSync(info.fullPath)
+    };
   });
-  app.get('/', function (req, res, next) {
-    var code = req.query.code;
-    var u = 'https://github.com/login/oauth/access_token'
-         + '?client_id=' + GITHUB_CLIENT_ID
-         + '&client_secret=' + GITHUB_CLIENT_SECRET
-         + '&code=' + code
-         + '&state=' + state;
-    github.buffer('GET', u, {}, {}, function (err, response) {
-      if (err) return next(err);
-      req.token = qs.parse(response.body);
-      next();
-    });
-  });
-  app.get('/', function (req, res, next) {
-    res.send('got token, return to terminal');
-    server.close();
-    fs.writeFileSync(__dirname + '/.release.json', JSON.stringify({token: req.token}));
-    gotToken(req.token);
-  });
-
-  server = app.listen(1337);
-  server.setTimeout(3000);
-  opener('http://localhost:1337');
-}
-
-getToken(function (token) {
-  compiledWebsite.then(function () {
-    var fileUpdates = readdirp(__dirname + '/docs/out').filter(function (info) {
-      return info.isFile();
-    }).map(function (info) {
-      return {
-        path: info.path.replace(/^\.\//, ''),
-        content: fs.readFileSync(info.fullPath)
-      };
-    });
-    return pr.commit('visionmedia', 'jade', {
-      branch: 'gh-pages',
-      message: 'Update website for ' + version,
-      updates: fileUpdates
-    }, {auth: {type: 'oauth', token: token.access_token}});
-  }).then(function () {
-    // todo: release the new npm package, set the tag and commit etc.
-  }).done();
+  return pr.commit('jadejs', 'jade', {
+    branch: 'gh-pages',
+    message: 'Update website for ' + version,
+    updates: fileUpdates
+  }, {auth: {type: 'oauth', token: TOKEN}});
+}).then(function () {
+  // todo: release the new npm package, set the tag and commit etc.
+}).done(function () {
+  console.log('website published');
 });
diff --git a/test/anti-cases/mixin-args-syntax-error.jade b/test/anti-cases/mixin-args-syntax-error.jade
new file mode 100644
index 0000000..d0b725b
--- /dev/null
+++ b/test/anti-cases/mixin-args-syntax-error.jade
@@ -0,0 +1,2 @@
+mixin foo(a, b)
++foo('a'b'b')
diff --git a/test/anti-cases/unclosed-interpolated-call.jade b/test/anti-cases/unclosed-interpolated-call.jade
new file mode 100644
index 0000000..63d02db
--- /dev/null
+++ b/test/anti-cases/unclosed-interpolated-call.jade
@@ -0,0 +1 @@
++#{myMixin
\ No newline at end of file
diff --git a/test/anti-cases/unclosed-interpolated-tag.jade b/test/anti-cases/unclosed-interpolated-tag.jade
new file mode 100644
index 0000000..be66079
--- /dev/null
+++ b/test/anti-cases/unclosed-interpolated-tag.jade
@@ -0,0 +1,4 @@
+mixin item
+  block
+
++item( Contact
\ No newline at end of file
diff --git a/test/anti-cases/unclosed-interpolation.jade b/test/anti-cases/unclosed-interpolation.jade
new file mode 100644
index 0000000..4698dd9
--- /dev/null
+++ b/test/anti-cases/unclosed-interpolation.jade
@@ -0,0 +1 @@
+#{myMixin
\ No newline at end of file
diff --git a/test/cases/auxiliary/1794-extends.jade b/test/cases/auxiliary/1794-extends.jade
new file mode 100644
index 0000000..99649d6
--- /dev/null
+++ b/test/cases/auxiliary/1794-extends.jade
@@ -0,0 +1 @@
+block content
\ No newline at end of file
diff --git a/test/cases/auxiliary/1794-include.jade b/test/cases/auxiliary/1794-include.jade
new file mode 100644
index 0000000..b9c03b4
--- /dev/null
+++ b/test/cases/auxiliary/1794-include.jade
@@ -0,0 +1,4 @@
+mixin test()
+  .test&attributes(attributes)
+ 
++test()
\ No newline at end of file
diff --git a/test/cases/block-code.html b/test/cases/block-code.html
new file mode 100644
index 0000000..489fe5d
--- /dev/null
+++ b/test/cases/block-code.html
@@ -0,0 +1,7 @@
+
+<li>Uno</li>
+<li>Dos</li>
+<li>Tres</li>
+<li>Cuatro</li>
+<li>Cinco</li>
+<li>Seis</li>
diff --git a/test/cases/block-code.jade b/test/cases/block-code.jade
new file mode 100644
index 0000000..9ab6854
--- /dev/null
+++ b/test/cases/block-code.jade
@@ -0,0 +1,12 @@
+-
+  list = ["uno", "dos", "tres",
+          "cuatro", "cinco", "seis"];
+//- Without a block, the element is accepted and no code is generated
+-
+each item in list
+  -
+    string = item.charAt(0)
+    
+      .toUpperCase() +
+    item.slice(1);
+  li= string
diff --git a/test/cases/blocks-in-if.jade b/test/cases/blocks-in-if.jade
index 80bc4b6..b1107b6 100644
--- a/test/cases/blocks-in-if.jade
+++ b/test/cases/blocks-in-if.jade
@@ -1,4 +1,4 @@
-//- see https://github.com/visionmedia/jade/issues/1589
+//- see https://github.com/jadejs/jade/issues/1589
 
 -var ajax = true
 
diff --git a/test/cases/classes.html b/test/cases/classes.html
index 6faf6bc..07da8c5 100644
--- a/test/cases/classes.html
+++ b/test/cases/classes.html
@@ -1 +1 @@
-<a class="foo bar baz"></a><a class="foo bar baz"></a><a class="foo-bar_baz"></a>
\ No newline at end of file
+<a class="foo bar baz"></a><a class="foo bar baz"></a><a class="foo-bar_baz"></a><a class="foo baz"></a>
diff --git a/test/cases/classes.jade b/test/cases/classes.jade
index 496e791..67e1a1b 100644
--- a/test/cases/classes.jade
+++ b/test/cases/classes.jade
@@ -4,6 +4,8 @@ a(class=['foo', 'bar', 'baz'])
 
 a.foo(class='bar').baz
 
-   
 
-a.foo-bar_baz
\ No newline at end of file
+
+a.foo-bar_baz
+
+a(class={foo: true, bar: false, baz: true})
diff --git a/test/cases/comments-in-case.html b/test/cases/comments-in-case.html
new file mode 100644
index 0000000..0efe935
--- /dev/null
+++ b/test/cases/comments-in-case.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <p>It's this!</p>
+  </body>
+</html>
\ No newline at end of file
diff --git a/test/cases/comments-in-case.jade b/test/cases/comments-in-case.jade
new file mode 100644
index 0000000..2439cfe
--- /dev/null
+++ b/test/cases/comments-in-case.jade
@@ -0,0 +1,10 @@
+doctype html
+html
+  body
+   - var s = 'this'
+   case s
+     //- Comment
+     when 'this'
+       p It's this!
+     when 'that'
+       p It's that!
\ No newline at end of file
diff --git a/test/cases/filters.coffeescript.html b/test/cases/filters.coffeescript.html
index e14001a..ceddb0c 100644
--- a/test/cases/filters.coffeescript.html
+++ b/test/cases/filters.coffeescript.html
@@ -1,5 +1,4 @@
-<script type="text/javascript">
-(function() {
+<script type="text/javascript">(function() {
   var regexp;
 
   regexp = /\n/;
diff --git a/test/cases/filters.coffeescript.jade b/test/cases/filters.coffeescript.jade
index 3941b44..34fe542 100644
--- a/test/cases/filters.coffeescript.jade
+++ b/test/cases/filters.coffeescript.jade
@@ -1,6 +1,6 @@
 script(type='text/javascript')
-  :coffeescript
+  :coffee-script
     regexp = /\n/
-  :coffeescript(minify=true)
+  :coffee-script(minify=true)
     math =
       square: (value) -> value * value
\ No newline at end of file
diff --git a/test/cases/filters.markdown.jade b/test/cases/filters.markdown.jade
index a36edf8..419340a 100644
--- a/test/cases/filters.markdown.jade
+++ b/test/cases/filters.markdown.jade
@@ -1,5 +1,5 @@
 html
   body
-    :markdown
+    :marked
       This is _some_ awesome **markdown**
       whoop.
\ No newline at end of file
diff --git a/test/cases/include-filter-stylus.jade b/test/cases/include-filter-stylus.jade
index bf57710..eefd3c1 100644
--- a/test/cases/include-filter-stylus.jade
+++ b/test/cases/include-filter-stylus.jade
@@ -1,2 +1,2 @@
 style(type="text/css")
-  include:styl some.styl
+  include:stylus some.styl
diff --git a/test/cases/include-filter.jade b/test/cases/include-filter.jade
index 7e4edd7..2f8079e 100644
--- a/test/cases/include-filter.jade
+++ b/test/cases/include-filter.jade
@@ -1,7 +1,7 @@
 html
   body
-    include:md some.md
+    include:marked some.md
     script
-      include:coffee(minify=true) include-filter-coffee.coffee
+      include:coffee-script(minify=true) include-filter-coffee.coffee
     script
-      include:coffee(minify=false) include-filter-coffee.coffee
+      include:coffee-script(minify=false) include-filter-coffee.coffee
diff --git a/test/cases/includes.html b/test/cases/includes.html
index aa4b192..d61de76 100644
--- a/test/cases/includes.html
+++ b/test/cases/includes.html
@@ -2,12 +2,14 @@
 <body><p>:)</p><script>
   console.log("foo\nbar")
 </script>
-  <script type="text/javascript">var STRING_SUBSTITUTIONS = {    // table of character substitutions
+  <script type="text/javascript">
+var STRING_SUBSTITUTIONS = {    // table of character substitutions
   '\t': '\\t',
   '\r': '\\r',
   '\n': '\\n',
   '"' : '\\"',
   '\\': '\\\\'
 };
+
   </script>
-</body>
+</body>
\ No newline at end of file
diff --git a/test/cases/includes.jade b/test/cases/includes.jade
index df2592e..6eb43b5 100644
--- a/test/cases/includes.jade
+++ b/test/cases/includes.jade
@@ -7,4 +7,4 @@ body
   include auxiliary/smile.html
   include auxiliary/escapes.html
   script(type="text/javascript")
-    include:js auxiliary/includable.js
+    include:verbatim auxiliary/includable.js
diff --git a/test/cases/mixin-via-include.jade b/test/cases/mixin-via-include.jade
index 53c9d02..a7bd0cd 100644
--- a/test/cases/mixin-via-include.jade
+++ b/test/cases/mixin-via-include.jade
@@ -1,4 +1,4 @@
-//- regression test for https://github.com/visionmedia/jade/issues/1435
+//- regression test for https://github.com/jadejs/jade/issues/1435
 
 include ../fixtures/mixin-include.jade
 
diff --git a/test/cases/regression.1794.html b/test/cases/regression.1794.html
new file mode 100644
index 0000000..b322cca
--- /dev/null
+++ b/test/cases/regression.1794.html
@@ -0,0 +1 @@
+<div class="test"></div>
\ No newline at end of file
diff --git a/test/cases/regression.1794.jade b/test/cases/regression.1794.jade
new file mode 100644
index 0000000..0143350
--- /dev/null
+++ b/test/cases/regression.1794.jade
@@ -0,0 +1,4 @@
+extends ./auxiliary/1794-extends.jade
+ 
+block content
+  include ./auxiliary/1794-include.jade
\ No newline at end of file
diff --git a/test/cases/styles.html b/test/cases/styles.html
index 1397780..a557983 100644
--- a/test/cases/styles.html
+++ b/test/cases/styles.html
@@ -6,4 +6,15 @@
       }
     </style>
   </head>
-</html>
\ No newline at end of file
+  <body>
+    <div style="color:red;background:green"></div>
+    <div style="color:red;background:green"></div>
+    <div style="color:red;background:green"></div>
+    <div style="color:red;background:green"></div>
+        <div style="color:red;background:green"></div>
+        <div style="color:red;background:green"></div>
+    <div style="color:red;background:green"></div>
+    <div style="color:red;background:green"></div>
+        <div style="color:red;background:green"></div>
+  </body>
+</html>
diff --git a/test/cases/styles.jade b/test/cases/styles.jade
index f087518..9618353 100644
--- a/test/cases/styles.jade
+++ b/test/cases/styles.jade
@@ -3,4 +3,17 @@ html
     style.
       body {
         padding: 50px;
-      }
\ No newline at end of file
+      }
+  body
+    div(style='color:red;background:green')
+    div(style={color: 'red', background: 'green'})
+    div&attributes({style: 'color:red;background:green'})
+    div&attributes({style: {color: 'red', background: 'green'}})
+    mixin div()
+      div&attributes(attributes)
+    +div(style='color:red;background:green')
+    +div(style={color: 'red', background: 'green'})
+    - var bg = 'green';
+    div(style={color: 'red', background: bg})
+    div&attributes({style: {color: 'red', background: bg}})
+    +div(style={color: 'red', background: bg})
diff --git a/test/cases/text.html b/test/cases/text.html
index d8e37cc..b7ac508 100644
--- a/test/cases/text.html
+++ b/test/cases/text.html
@@ -1,4 +1,6 @@
 <option value="">-- (selected) --</option>
+<p></p>
+<p></p>
 <p>
   foo
   bar
diff --git a/test/cases/text.jade b/test/cases/text.jade
index a26387e..8680bac 100644
--- a/test/cases/text.jade
+++ b/test/cases/text.jade
@@ -1,6 +1,10 @@
 option(value='') -- (selected) --
 
 p
+
+p.
+
+p
   | foo
   | bar
   | baz
diff --git a/test/command-line.js b/test/command-line.js
index 29192ec..ba62457 100644
--- a/test/command-line.js
+++ b/test/command-line.js
@@ -1,25 +1,198 @@
 'use strict';
 
 var fs = require('fs');
+var mkdirp = require('mkdirp');
+var rimraf = require('rimraf');
 var path = require('path');
 var assert = require('assert');
-var exec = require('child_process').exec;
+var cp = require('child_process');
 
-function run(args, callback) {
-  exec('node ' + JSON.stringify(path.resolve(__dirname + '/../bin/jade.js')) + ' ' + args, {
+// Sets directory to output coverage data to
+// Incremented every time getRunner() is called.
+var covCount = 1;
+var isIstanbul = process.env.running_under_istanbul;
+
+/**
+ * Gets an array containing the routine to run the jade CLI. If this file is
+ * being processed with istanbul then this function will return a routine
+ * asking istanbul to store coverage data to a unique directory
+ * (cov-pt<covCount>/).
+ */
+function getRunner() {
+  var jadeExe = __dirname + '/../bin/jade.js';
+
+  if (!isIstanbul) return ['node', jadeExe];
+  else {
+    return ['istanbul', 'cover',
+            '--print',  'none',
+            '--report', 'none',
+            '--root',   process.cwd(),
+            '--dir',    process.cwd() + '/cov-pt' + (covCount++),
+            jadeExe,
+            '--'];
+  }
+}
+
+function run(args, stdin, callback) {
+  if (arguments.length === 2) {
+    callback = stdin;
+    stdin    = null;
+  }
+  var runner = getRunner().join(' ');
+  cp.exec((stdin || '') + runner + ' ' + args, {
     cwd: __dirname + '/temp'
   }, callback);
 }
 
-try {
-  fs.mkdirSync(__dirname + '/temp');
-} catch (ex) {
-  if (ex.code !== 'EEXIST') {
-    throw ex;
+rimraf.sync(__dirname + '/temp');
+mkdirp.sync(__dirname + '/temp/inputs/level-1-1');
+mkdirp.sync(__dirname + '/temp/inputs/level-1-2');
+mkdirp.sync(__dirname + '/temp/outputs/level-1-1');
+mkdirp.sync(__dirname + '/temp/outputs/level-1-2');
+
+/**
+ * Set timing limits for a test case
+ */
+function timing(testCase) {
+  if (isIstanbul) {
+    testCase.timeout(20000);
+    testCase.slow(3000);
+  } else {
+    testCase.timeout(12500);
+    testCase.slow(2000);
   }
 }
 
 describe('command line', function () {
+  timing(this);
+  it('jade --version', function (done) {
+    run('-V', function (err, stdout) {
+      if (err) done(err);
+      assert.equal(stdout.trim(), require('../package.json').version);
+      run('--version', function (err, stdout) {
+        if (err) done(err);
+        assert.equal(stdout.trim(), require('../package.json').version);
+        done()
+      });
+    });
+  });
+  it('jade --help', function (done) {
+    // only check that it doesn't crash
+    run('-h', function (err, stdout) {
+      if (err) done(err);
+      run('--help', function (err, stdout) {
+        if (err) done(err);
+        done()
+      });
+    });
+  });
+});
+
+describe('command line with HTML output', function () {
+  timing(this);
+  it('jade --no-debug input.jade', function (done) {
+    fs.writeFileSync(__dirname + '/temp/input.jade', '.foo bar');
+    fs.writeFileSync(__dirname + '/temp/input.html', '<p>output not written</p>');
+    run('--no-debug input.jade', function (err) {
+      if (err) return done(err);
+      var html = fs.readFileSync(__dirname + '/temp/input.html', 'utf8');
+      assert(html === '<div class="foo">bar</div>');
+      done();
+    });
+  });
+  it('jade --no-debug -E special-html input.jade', function (done) {
+    fs.writeFileSync(__dirname + '/temp/input.jade', '.foo bar');
+    fs.writeFileSync(__dirname + '/temp/input.special-html', '<p>output not written</p>');
+    run('--no-debug -E special-html input.jade', function (err) {
+      if (err) return done(err);
+      var html = fs.readFileSync(__dirname + '/temp/input.special-html', 'utf8');
+      assert(html === '<div class="foo">bar</div>');
+      done();
+    });
+  });
+  it('jade --no-debug --obj "{\'loc\':\'str\'}" input.jade', function (done) {
+    fs.writeFileSync(__dirname + '/temp/input.jade', '.foo= loc');
+    fs.writeFileSync(__dirname + '/temp/input.html', '<p>output not written</p>');
+    run('--no-debug --obj "{\'loc\':\'str\'}" input.jade', function (err) {
+      if (err) return done(err);
+      var html = fs.readFileSync(__dirname + '/temp/input.html', 'utf8');
+      assert(html === '<div class="foo">str</div>');
+      done();
+    });
+  });
+  it('jade --no-debug --obj "obj.json" input.jade', function (done) {
+    fs.writeFileSync(__dirname + '/temp/obj.json', '{"loc":"str"}');
+    fs.writeFileSync(__dirname + '/temp/input.jade', '.foo= loc');
+    fs.writeFileSync(__dirname + '/temp/input.html', '<p>output not written</p>');
+    run('--no-debug --obj "'+__dirname+'/temp/obj.json" input.jade', function (err) {
+      if (err) return done(err);
+      var html = fs.readFileSync(__dirname + '/temp/input.html', 'utf8');
+      assert(html === '<div class="foo">str</div>');
+      done();
+    });
+  });
+  it('cat input.jade | jade --no-debug', function (done) {
+    fs.writeFileSync(__dirname + '/temp/input.jade', '.foo bar');
+    run('--no-debug', 'cat input.jade | ', function (err, stdout, stderr) {
+      if (err) return done(err);
+      assert(stdout === '<div class="foo">bar</div>');
+      done();
+    });
+  });
+  it('jade --no-debug --out outputs input.jade', function (done) {
+    fs.writeFileSync(__dirname + '/temp/input.jade', '.foo bar');
+    fs.writeFileSync(__dirname + '/temp/input.html', '<p>output not written</p>');
+    run('--no-debug --out outputs input.jade', function (err) {
+      if (err) return done(err);
+      var html = fs.readFileSync(__dirname + '/temp/outputs/input.html', 'utf8');
+      assert(html === '<div class="foo">bar</div>');
+      done();
+    });
+  });
+  context('when input is directory', function () {
+    it('jade --no-debug --out outputs inputs', function (done) {
+      fs.writeFileSync(__dirname + '/temp/inputs/input.jade', '.foo bar 1');
+      fs.writeFileSync(__dirname + '/temp/inputs/level-1-1/input1-1.jade', '.foo bar 1-1');
+      fs.writeFileSync(__dirname + '/temp/inputs/level-1-2/input1-2.jade', '.foo bar 1-2');
+      fs.writeFileSync(__dirname + '/temp/outputs/input.html', 'BIG FAT HEN 1');
+      fs.writeFileSync(__dirname + '/temp/outputs/input1-1.html', 'BIG FAT HEN 1-1');
+      fs.writeFileSync(__dirname + '/temp/outputs/input1-2.html', 'BIG FAT HEN 1-2');
+      run('--no-debug --out outputs inputs', function (err, stdout, stderr) {
+        if (err) return done(err);
+        var html = fs.readFileSync(__dirname + '/temp/outputs/input.html', 'utf8');
+        assert(html === '<div class="foo">bar 1</div>');
+        var html = fs.readFileSync(__dirname + '/temp/outputs/input1-1.html', 'utf8');
+        assert(html === '<div class="foo">bar 1-1</div>');
+        var html = fs.readFileSync(__dirname + '/temp/outputs/input1-2.html', 'utf8');
+        assert(html === '<div class="foo">bar 1-2</div>');
+        assert(stderr.indexOf('--hierarchy will become the default') !== -1,
+               '--hierarchy default warning not shown');
+        done();
+      });
+    });
+    it('jade --no-debug --hierarchy --out outputs inputs', function (done) {
+      fs.writeFileSync(__dirname + '/temp/inputs/input.jade', '.foo bar 1');
+      fs.writeFileSync(__dirname + '/temp/inputs/level-1-1/input.jade', '.foo bar 1-1');
+      fs.writeFileSync(__dirname + '/temp/inputs/level-1-2/input.jade', '.foo bar 1-2');
+      fs.writeFileSync(__dirname + '/temp/outputs/input.html', 'BIG FAT HEN 1');
+      fs.writeFileSync(__dirname + '/temp/outputs/level-1-1/input.html', 'BIG FAT HEN 1-1');
+      fs.writeFileSync(__dirname + '/temp/outputs/level-1-2/input.html', 'BIG FAT HEN 1-2');
+      run('--no-debug --hierarchy --out outputs inputs', function (err) {
+        if (err) return done(err);
+        var html = fs.readFileSync(__dirname + '/temp/outputs/input.html', 'utf8');
+        assert(html === '<div class="foo">bar 1</div>');
+        var html = fs.readFileSync(__dirname + '/temp/outputs/level-1-1/input.html', 'utf8');
+        assert(html === '<div class="foo">bar 1-1</div>');
+        var html = fs.readFileSync(__dirname + '/temp/outputs/level-1-2/input.html', 'utf8');
+        assert(html === '<div class="foo">bar 1-2</div>');
+        done();
+      });
+    });
+  });
+});
+
+describe('command line with client JS output', function () {
+  timing(this);
   it('jade --no-debug --client --name myTemplate input.jade', function (done) {
     fs.writeFileSync(__dirname + '/temp/input.jade', '.foo bar');
     fs.writeFileSync(__dirname + '/temp/input.js', 'throw new Error("output not written");');
@@ -30,6 +203,26 @@ describe('command line', function () {
       done();
     });
   });
+  it('jade --no-debug --client -E special-js --name myTemplate input.jade', function (done) {
+    fs.writeFileSync(__dirname + '/temp/input.jade', '.foo bar');
+    fs.writeFileSync(__dirname + '/temp/input.special-js', 'throw new Error("output not written");');
+    run('--no-debug --client -E special-js --name myTemplate input.jade', function (err) {
+      if (err) return done(err);
+      var template = Function('', fs.readFileSync(__dirname + '/temp/input.special-js', 'utf8') + ';return myTemplate;')();
+      assert(template() === '<div class="foo">bar</div>');
+      done();
+    });
+  });
+  it('cat input.jade | jade --no-debug --client --name myTemplate', function (done) {
+    fs.writeFileSync(__dirname + '/temp/input.jade', '.foo bar');
+    fs.writeFileSync(__dirname + '/temp/input.js', 'throw new Error("output not written");');
+    run('--no-debug --client --name myTemplate', 'cat input.jade | ', function (err, stdout) {
+      if (err) return done(err);
+      var template = Function('', stdout + ';return myTemplate;')();
+      assert(template() === '<div class="foo">bar</div>');
+      done();
+    });
+  });
   it('jade --no-debug --client --name-after-file input-file.jade', function (done) {
     fs.writeFileSync(__dirname + '/temp/input-file.jade', '.foo bar');
     fs.writeFileSync(__dirname + '/temp/input-file.js', 'throw new Error("output not written");');
@@ -40,4 +233,126 @@ describe('command line', function () {
       return done();
     });
   });
+  it('jade --no-debug --client --name-after-file _InPuTwIthWEiRdNaMME.jade', function (done) {
+    fs.writeFileSync(__dirname + '/temp/_InPuTwIthWEiRdNaMME.jade', '.foo bar');
+    fs.writeFileSync(__dirname + '/temp/_InPuTwIthWEiRdNaMME.js', 'throw new Error("output not written");');
+    run('--no-debug --client --name-after-file _InPuTwIthWEiRdNaMME.jade', function (err, stdout, stderr) {
+      if (err) return done(err);
+      var template = Function('', fs.readFileSync(__dirname + '/temp/_InPuTwIthWEiRdNaMME.js', 'utf8') + ';return InputwithweirdnammeTemplate;')();
+      assert(template() === '<div class="foo">bar</div>');
+      return done();
+    });
+  });
+});
+
+describe('command line watch mode', function () {
+  var watchProc;
+  var stdout = '';
+  after(function() {
+    if (!watchProc) return
+    // Just to be sure
+    watchProc.stderr.removeAllListeners('data');
+    watchProc.stdout.removeAllListeners('data');
+    watchProc.removeAllListeners('error');
+    watchProc.removeAllListeners('close');
+
+    watchProc.kill('SIGINT');
+  });
+  afterEach(function (done) {
+    // jade --watch can only detect changes that are at least 1 second apart
+    setTimeout(done, 1000);
+  });
+  it('jade --no-debug --client --name-after-file --watch input-file.jade (pass 1)', function (done) {
+    timing(this);
+    fs.writeFileSync(__dirname + '/temp/input-file.jade', '.foo bar');
+    fs.writeFileSync(__dirname + '/temp/input-file.js', 'throw new Error("output not written (pass 1)");');
+    var cmd = getRunner();
+    cmd.push.apply(cmd,
+      ['--no-debug', '--client', '--name-after-file', '--watch', 'input-file.jade']);
+    watchProc = cp.spawn(cmd[0], cmd.slice(1),  {
+      cwd: __dirname + '/temp'
+    });
+
+    watchProc.stdout.setEncoding('utf8');
+    watchProc.stderr.setEncoding('utf8');
+    watchProc
+      .on('error', done)
+      .stdout.on('data', function(buf) {
+        stdout += buf;
+        if (/.*rendered.*/.test(stdout)) {
+          stdout = '';
+          var template = Function('', fs.readFileSync(__dirname + '/temp/input-file.js', 'utf8') + ';return inputFileTemplate;')();
+          assert(template() === '<div class="foo">bar</div>');
+
+          watchProc.stdout.removeAllListeners('data');
+          watchProc.removeAllListeners('error');
+          return done();
+        }
+      });
+  });
+  it('jade --no-debug --client --name-after-file --watch input-file.jade (pass 2)', function (done) {
+    // Just to be sure
+    watchProc.stdout.removeAllListeners('data');
+    watchProc.removeAllListeners('error');
+
+    fs.writeFileSync(__dirname + '/temp/input-file.js', 'throw new Error("output not written (pass 2)");');
+    fs.writeFileSync(__dirname + '/temp/input-file.jade', '.foo baz');
+
+    watchProc
+      .on('error', done)
+      .stdout.on('data', function(buf) {
+        stdout += buf;
+        if (/.*rendered.*/.test(stdout)) {
+          stdout = '';
+          var template = Function('', fs.readFileSync(__dirname + '/temp/input-file.js', 'utf8') + ';return inputFileTemplate;')();
+          assert(template() === '<div class="foo">baz</div>');
+
+          watchProc.stdout.removeAllListeners('data');
+          watchProc.removeAllListeners('error');
+          return done();
+        }
+      });
+  });
+  it('jade --no-debug --client --name-after-file --watch input-file.jade (intentional errors in the jade file)', function (done) {
+    // Just to be sure
+    watchProc.stdout.removeAllListeners('data');
+    watchProc.removeAllListeners('error');
+
+    var stderr = '';
+    var errored = false;
+    watchProc
+      .on('error', done)
+      .on('close', function() {
+        errored = true;
+        return done(new Error('Jade should not terminate in watch mode'));
+      })
+      .stdout.on('data', function(buf) {
+        stdout += buf;
+        if (/.*rendered.*/.test(stdout)) {
+          stdout = '';
+          return done(new Error('Jade compiles an erroneous file w/o error'));
+        }
+      })
+    watchProc
+      .stderr.on('data', function(buf) {
+        stderr += buf;
+        if (!/.*Invalid indentation.*/.test(stderr)) return;
+        stderr = '';
+        var template = Function('', fs.readFileSync(__dirname + '/temp/input-file.js', 'utf8') + ';return inputFileTemplate;')();
+        assert(template() === '<div class="foo">baz</div>');
+
+        watchProc.stderr.removeAllListeners('data');
+        watchProc.stdout.removeAllListeners('data');
+        watchProc.removeAllListeners('error');
+        watchProc.removeAllListeners('exit');
+        // The stderr event will always fire sooner than the close event.
+        // Wait for it.
+        setTimeout(function() {
+          if (!errored) done();
+        }, 100);
+      });
+    fs.writeFileSync(__dirname + '/temp/input-file.jade',
+                     fs.readFileSync(__dirname
+                                     + '/anti-cases/tabs-and-spaces.jade'));
+  });
 });
diff --git a/test/deprecated.js b/test/deprecated.js
index 6d7dbea..54ba1db 100644
--- a/test/deprecated.js
+++ b/test/deprecated.js
@@ -55,11 +55,18 @@ describe('deprecated functions', function () {
     tag.setAttribute('href', 'value');
     assert(tag.getAttribute('href') === 'value');
   });
+});
+
+describe('deprecated options or local names', function () {
   deprecate('jade.compile(str, {client: true})', function () {
     var fn = jade.compile('div', {client: true});
     var fn = Function('jade', fn.toString() + '\nreturn template;')(jade.runtime);
     assert(fn() === '<div></div>');
   }, /The `client` option is deprecated/);
+  deprecate('jade.render(str, {lexer: \'this is a local\'})', function () {
+    var str = jade.render('div', {lexer: 'this is a local'});
+    assert(str === '<div></div>');
+  }, /Using `lexer` as a local in render\(\) is deprecated/);
 });
 
 describe('warnings that will become errors', function () {
diff --git a/test/error.reporting.js b/test/error.reporting.js
index fc21837..402139a 100644
--- a/test/error.reporting.js
+++ b/test/error.reporting.js
@@ -54,7 +54,7 @@ describe('error reporting', function () {
       it('includes detail of where the error was thrown including the filename', function () {
         var err = getFileError(__dirname + '/fixtures/compile.with.layout.locals.error.jade', {})
         assert(/[\\\/]layout.locals.error.jade:2/.test(err.message))
-        assert(/undefined is not a function/.test(err.message))
+        assert(/is not a function/.test(err.message))
       });
     });
     describe('with a include (syntax)', function () {
@@ -93,9 +93,9 @@ describe('error reporting', function () {
     });
     describe('Include filtered', function () {
       it('includes details of where the error was thrown', function () {
-        var err = getError('include:js()!', {});
+        var err = getError('include:verbatim()!', {});
         assert(err.message.indexOf('expected space after include:filter but got "!"') !== -1);
-        var err = getError('include:js ', {});
+        var err = getError('include:verbatim ', {});
         assert(err.message.indexOf('missing path for include:filter') !== -1);
       });
     });
diff --git a/test/examples.js b/test/examples.js
index 2738b2d..5c45860 100644
--- a/test/examples.js
+++ b/test/examples.js
@@ -4,22 +4,23 @@ var fs = require('fs');
 var jade = require('../');
 
 describe('examples', function () {
-  it('none of them throw any errors', function () {
-    var log = console.log;
-    var err = console.error;
-    console.log = function () {};
-    console.error = function () {};
-    try {
-      fs.readdirSync(__dirname + '/../examples').forEach(function (example) {
-        if (/\.js$/.test(example)) {
+  fs.readdirSync(__dirname + '/../examples').forEach(function (example) {
+    if (/\.js$/.test(example)) {
+      it(example + ' does not throw any error', function () {
+        var log = console.log;
+        var err = console.error;
+        console.log = function () {};
+        console.error = function () {};
+        try {
           require('../examples/' + example);
+        } catch (ex) {
+          console.log = log;
+          console.error = err;
+          throw ex;
         }
+        console.log = log;
+        console.error = err;
       });
-    } catch (ex) {
-      console.log = log;
-      console.error = err;
     }
-    console.log = log;
-    console.error = err;
   });
-});
\ No newline at end of file
+});
diff --git a/test/jade.test.js b/test/jade.test.js
index a7016ad..8873c98 100644
--- a/test/jade.test.js
+++ b/test/jade.test.js
@@ -7,6 +7,14 @@ var jade = require('../');
 
 var perfTest = fs.readFileSync(__dirname + '/fixtures/perf.jade', 'utf8')
 
+try {
+  fs.mkdirSync(__dirname + '/temp');
+} catch (ex) {
+  if (ex.code !== 'EEXIST') {
+    throw ex;
+  }
+}
+
 describe('jade', function(){
 
   describe('.properties', function(){
@@ -19,7 +27,7 @@ describe('jade', function(){
     });
   });
 
-  describe('.compile()', function(){
+  describe('unit tests with .render()', function(){
     it('should support doctypes', function(){
       assert.equal('<?xml version="1.0" encoding="utf-8" ?>', jade.render('doctype xml'));
       assert.equal('<!DOCTYPE html>', jade.render('doctype html'));
@@ -673,6 +681,19 @@ describe('jade', function(){
       ].join('');
 
       assert.equal(html, jade.render(str));
+      
+      var str = [
+          '-',
+          '  var a =',
+          '    5;',
+          'p= a'
+      ].join('\n')
+
+      var html = [
+          '<p>5</p>'
+      ].join('');
+
+      assert.equal(html, jade.render(str));
     });
 
     it('should support - each', function(){
@@ -869,12 +890,60 @@ describe('jade', function(){
       , jade.render(str, { filename: __dirname + '/jade.test.js' }));
     });
 
+    it('should not fail on js newlines', function(){
+      assert.equal("<p>foo\u2028bar</p>", jade.render("p foo\u2028bar"));
+      assert.equal("<p>foo\u2029bar</p>", jade.render("p foo\u2029bar"));
+    });
+    
+    it('should display error line number correctly up to token level', function() {
+      var str = [
+        'p.',
+        '  Lorem ipsum dolor sit amet, consectetur',
+        '  adipisicing elit, sed do eiusmod tempor',
+        '  incididunt ut labore et dolore magna aliqua.',
+        'p.',
+        '  Ut enim ad minim veniam, quis nostrud',
+        '  exercitation ullamco laboris nisi ut aliquip',
+        '  ex ea commodo consequat.',
+        'p.',
+        '  Duis aute irure dolor in reprehenderit',
+        '  in voluptate velit esse cillum dolore eu',
+        '  fugiat nulla pariatur.',
+        'a(href="#" Next',
+      ].join('\n');
+      var errorLocation = function(str) {
+        try {
+          jade.render(str);
+        } catch (err) {
+          return err.message.split('\n')[0];
+        }
+      };
+      assert.equal(errorLocation(str), "Jade:13");
+    });
+  });
+
+  describe('.compileFile()', function () {
     it('does not produce warnings for issue-1593', function () {
       jade.compileFile(__dirname + '/fixtures/issue-1593/index.jade');
     });
+    it('should support caching (pass 1)', function () {
+      fs.writeFileSync(__dirname + '/temp/input-compileFile.jade', '.foo bar');
+      var fn = jade.compileFile(__dirname + '/temp/input-compileFile.jade',
+                                { cache: true });
+      var expected = '<div class="foo">bar</div>';
+      assert(fn() === expected);
+    });
+    it('should support caching (pass 2)', function () {
+      // Poison the input file
+      fs.writeFileSync(__dirname + '/temp/input-compileFile.jade', '.big fat hen');
+      var fn = jade.compileFile(__dirname + '/temp/input-compileFile.jade',
+                                { cache: true });
+      var expected = '<div class="foo">bar</div>';
+      assert(fn() === expected);
+    });
   });
 
-  describe('.render()', function(){
+  describe('.render()', function () {
     it('should support .jade.render(str, fn)', function(){
       jade.render('p foo bar', function(err, str){
         assert.ok(!err);
@@ -899,7 +968,9 @@ describe('jade', function(){
         assert.equal('<p>foo bar</p>', str);
       });
     });
+  })
 
+  describe('.compile()', function(){
     it('should support .compile()', function(){
       var fn = jade.compile('p foo');
       assert.equal('<p>foo</p>', fn());
@@ -910,6 +981,11 @@ describe('jade', function(){
       assert.equal('<p>bar</p>', fn({ foo: 'bar' }));
     });
 
+    it('should support .compile() locals in \'self\' hash', function(){
+      var fn = jade.compile('p= self.foo', {self: true});
+      assert.equal('<p>bar</p>', fn({ foo: 'bar' }));
+    });
+
     it('should support .compile() no debug', function(){
       var fn = jade.compile('p foo\np #{bar}', {compileDebug: false});
       assert.equal('<p>foo</p><p>baz</p>', fn({bar: 'baz'}));
@@ -929,6 +1005,24 @@ describe('jade', function(){
     });
   });
 
+  describe('.compileClient()', function () {
+    it('should support .jade.compileClient(str)', function () {
+      var src = fs.readFileSync(__dirname + '/cases/basic.jade');
+      var expected = fs.readFileSync(__dirname + '/cases/basic.html', 'utf8').replace(/\s/g, '');
+      var fn = jade.compileClient(src);
+      fn = Function('jade', fn.toString() + '\nreturn template;')(jade.runtime);
+      var actual = fn({name: 'foo'}).replace(/\s/g, '');
+      assert(actual === expected);
+    });
+    it('should support .jade.compileClient(str, options)', function () {
+      var src = '.bar= self.foo'
+      var fn = jade.compileClient(src, {self: true});
+      fn = Function('jade', fn.toString() + '\nreturn template;')(jade.runtime);
+      var actual = fn({foo: 'baz'});
+      assert(actual === '<div class="bar">baz</div>');
+    });
+  });
+
   describe('.renderFile()', function () {
     it('will synchronously return a string', function () {
       var expected = fs.readFileSync(__dirname + '/cases/basic.html', 'utf8').replace(/\s/g, '');
@@ -957,6 +1051,23 @@ describe('jade', function(){
         done();
       });
     });
+    it('should support caching (pass 1)', function (done) {
+      fs.writeFileSync(__dirname + '/temp/input-renderFile.jade', '.foo bar');
+      jade.renderFile(__dirname + '/temp/input-renderFile.jade', { cache: true }, function (err, actual) {
+        if (err) return done(err);
+        assert.equal('<div class="foo">bar</div>', actual);
+        done();
+      });
+    });
+    it('should support caching (pass 2)', function (done) {
+      // Poison the input file
+      fs.writeFileSync(__dirname + '/temp/input-renderFile.jade', '.big fat hen');
+      jade.renderFile(__dirname + '/temp/input-renderFile.jade', { cache: true }, function (err, actual) {
+        if (err) return done(err);
+        assert.equal('<div class="foo">bar</div>', actual);
+        done();
+      });
+    });
   });
 
   describe('.compileFileClient(path, options)', function () {
@@ -974,6 +1085,25 @@ describe('jade', function(){
       var actual = fn({name: 'foo'}).replace(/\s/g, '');
       assert(actual === expected);
     });
+    it('should support caching (pass 1)', function () {
+      fs.writeFileSync(__dirname + '/temp/input-compileFileClient.jade', '.foo bar');
+      var src = jade.compileFileClient(__dirname + '/temp/input-compileFileClient.jade',
+                                        { name: 'myTemplateName',
+                                          cache: true });
+      var expected = '<div class="foo">bar</div>';
+      var fn = Function('jade', src + '\nreturn myTemplateName;')(jade.runtime);
+      assert(fn() === expected);
+    });
+    it('should support caching (pass 2)', function () {
+      // Poison the input file
+      fs.writeFileSync(__dirname + '/temp/input-compileFileClient.jade', '.big fat hen');
+      var src = jade.compileFileClient(__dirname + '/temp/input-compileFileClient.jade',
+                                        { name: 'myTemplateName',
+                                          cache: true });
+      var expected = '<div class="foo">bar</div>';
+      var fn = Function('jade', src + '\nreturn myTemplateName;')(jade.runtime);
+      assert(fn() === expected);
+    });
   });
 
   describe('.runtime', function () {
@@ -1058,9 +1188,5 @@ describe('jade', function(){
         path.resolve(__dirname + '/dependencies/dependency3.jade')
       ],info.dependencies);
     });
-    it('should not fail on js newlines', function(){
-      assert.equal("<p>foo\u2028bar</p>", jade.render("p foo\u2028bar"));
-      assert.equal("<p>foo\u2029bar</p>", jade.render("p foo\u2029bar"));
-    });
   });
 });
diff --git a/test/run.js b/test/run.js
index 630cf8f..2f93c40 100644
--- a/test/run.js
+++ b/test/run.js
@@ -29,7 +29,6 @@ try {
   }
 }
 
-var mixinsUnusedTestRan = false;
 cases.forEach(function(test){
   var name = test.replace(/[-.]/g, ' ');
   it(name, function(){
@@ -63,7 +62,6 @@ cases.forEach(function(test){
       html = html.replace(/\n| /g, '');
     }
     if (/mixins-unused/.test(test)) {
-      mixinsUnusedTestRan = true;
       assert(/never-called/.test(str), 'never-called is in the jade file for mixins-unused');
       assert(!/never-called/.test(clientCode), 'never-called should be removed from the code');
     }
@@ -78,11 +76,8 @@ cases.forEach(function(test){
       actual = actual.replace(/\n| /g, '');
     }
     JSON.stringify(actual.trim()).should.equal(JSON.stringify(html));
-  })
+  });
 });
-after(function () {
-  assert(mixinsUnusedTestRan, 'mixins-unused test should run');
-})
 
 // test cases
 
@@ -101,12 +96,12 @@ describe('certain syntax is not allowed and will throw a compile time error', fu
       try {
         var fn = jade.compile(str, { filename: path, pretty: true, basedir: 'test/anti-cases' });
       } catch (ex) {
-        ex.should.be.an.instanceof(Error);
-        ex.message.replace(/\\/g, '/').should.startWith(path);
-        ex.message.replace(/\\/g, '/').should.match(/:\d+$/m);
+        assert(ex instanceof Error, 'Should throw a real Error');
+        assert(ex.message.replace(/\\/g, '/').indexOf(path) === 0, 'it should start with the path');
+        assert(/:\d+$/m.test(ex.message.replace(/\\/g, '/')), 'it should include a line number.');
         return;
       }
       throw new Error(test + ' should have thrown an error');
     })
   });
-});
\ No newline at end of file
+});
diff --git a/test/unit.js b/test/unit.js
index 24b2db8..a719554 100644
--- a/test/unit.js
+++ b/test/unit.js
@@ -1,6 +1,7 @@
 
 var runtime = require('../lib/runtime')
-  , merge = runtime.merge;
+  , merge = runtime.merge
+  , DebugItem = runtime.DebugItem;
 
 describe('merge(a, b, escaped)', function(){
   it('should merge classes into strings', function(){
@@ -37,4 +38,11 @@ describe('merge(a, b, escaped)', function(){
     merge({ class: ['foo', null, 'bar'] }, { class: [undefined, null, 0, 'baz'] })
       .should.eql({ class: ['foo', 'bar', 0, 'baz'] });
   })
-})
\ No newline at end of file
+})
+
+describe('DebugItem', function(){
+  it('should instantiate objects with lineno and filename properties', function(){
+    new DebugItem(42, "/path/to/file")
+      .should.eql({ lineno: 42, filename: "/path/to/file" });
+  })
+})

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-jade.git



More information about the Pkg-javascript-commits mailing list