[Pkg-javascript-commits] [node-jade] 01/04: Imported Upstream version 1.5.0+dfsg

Jelmer Vernooij jelmer at moszumanska.debian.org
Thu Aug 7 23:52:50 UTC 2014


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

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

commit e8070eb8827236d58b2f412d9173543c01ec6820
Author: Jelmer Vernooij <jelmer at samba.org>
Date:   Tue Aug 5 02:19:43 2014 +0200

    Imported Upstream version 1.5.0+dfsg
---
 .gitignore                                        |  12 ++
 .npmignore                                        |   1 +
 History.md                                        |  30 +++-
 README.md                                         |   9 +-
 Readme_zh-cn.md                                   |   2 +-
 bin/jade.js                                       |  33 ++++-
 component.json                                    |   2 +-
 docs/client/index.js                              | 108 ++++++++++++++
 docs/server.js                                    | 115 +++++++++++++++
 docs/stop.js                                      |  34 +++++
 docs/style/footer.less                            |  32 +++++
 docs/style/highlighting.less                      |  37 +++++
 docs/style/index.less                             |  47 ++++++
 docs/style/logo.png                               | Bin 0 -> 6431 bytes
 docs/style/parameter-lists.less                   |  58 ++++++++
 docs/versions.json                                |   7 +
 docs/views/api.jade                               | 165 ++++++++++++++++++++++
 docs/views/command-line.jade                      |  53 +++++++
 docs/views/history.jade                           |   5 +
 docs/views/home.jade                              |  51 +++++++
 docs/views/includes/ga.jade                       |  10 ++
 docs/views/includes/mixins.jade                   |  12 ++
 docs/views/layout.jade                            |  55 ++++++++
 docs/views/reference.jade                         |  35 +++++
 docs/views/reference/attributes.jade              | 143 +++++++++++++++++++
 docs/views/reference/case.jade                    |  61 ++++++++
 docs/views/reference/code.jade                    |  86 +++++++++++
 docs/views/reference/comments.jade                |  84 +++++++++++
 docs/views/reference/conditionals.jade            |  46 ++++++
 docs/views/reference/doctype.jade                 |  34 +++++
 docs/views/reference/extends.jade                 |  50 +++++++
 docs/views/reference/filters.jade                 |  33 +++++
 docs/views/reference/includes.jade                | 133 +++++++++++++++++
 docs/views/reference/iteration.jade               |  90 ++++++++++++
 docs/views/reference/mixins.jade                  | 117 +++++++++++++++
 docs/views/reference/plain-text.jade              |  60 ++++++++
 docs/views/reference/tags.jade                    |  49 +++++++
 lib/jade.js                                       |  59 ++++++--
 lib/lexer.js                                      |  96 ++++++++++---
 lib/parser.js                                     | 116 ++++++++-------
 lib/runtime.js                                    |   2 +-
 lib/utils.js                                      |  31 ++++
 package.json                                      |  36 ++++-
 release.js                                        |  88 ++++++++++++
 test/cases/auxiliary/blocks-in-blocks-layout.jade |   8 ++
 test/cases/blocks-in-blocks.html                  |   9 ++
 test/cases/blocks-in-blocks.jade                  |   4 +
 test/cases/blocks-in-if.html                      |   1 +
 test/cases/blocks-in-if.jade                      |  19 +++
 test/cases/comments.html                          |   4 +-
 test/cases/include-filter-coffee.coffee           |   2 +
 test/cases/include-filter.html                    |  14 ++
 test/cases/include-filter.jade                    |   4 +
 test/cases/script.whitespace.html                 |   4 +-
 test/command-line.js                              |  43 ++++++
 test/dependencies/dependency1.jade                |   1 +
 test/dependencies/dependency2.jade                |   1 +
 test/dependencies/dependency3.jade                |   1 +
 test/dependencies/extends1.jade                   |   1 +
 test/dependencies/extends2.jade                   |   1 +
 test/dependencies/include1.jade                   |   1 +
 test/dependencies/include2.jade                   |   1 +
 test/deprecated.js                                |  36 +++--
 test/error.reporting.js                           |  14 ++
 test/fixtures/invalid-block-in-extends.jade       |   7 +
 test/fixtures/layout.jade                         |   6 +
 test/jade.test.js                                 | 116 +++++++++++----
 67 files changed, 2482 insertions(+), 143 deletions(-)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a85fb8f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+.DS_Store
+.idea
+lib-cov
+testing
+node_modules
+lib-cov
+coverage
+npm-debug.log
+/test/output
+/test/temp
+/docs/out
+.release.json
diff --git a/.npmignore b/.npmignore
index 5bb0b26..f53cdc5 100644
--- a/.npmignore
+++ b/.npmignore
@@ -12,3 +12,4 @@ test/
 support/
 benchmarks/
 examples/
+docs/
diff --git a/History.md b/History.md
index d6f6164..b97a158 100644
--- a/History.md
+++ b/History.md
@@ -1,7 +1,35 @@
+1.5.0 / 2014-07-23
+==================
+
+  * Added compileFile API ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+  * Fix line number in un-used blocks warning ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+  * Fix a warning that eroniously warned for un-used blocks if they were under another block (Reported by [@pesho](https://github.com/pesho))
+
+1.4.2 / 2014-07-16
+==================
+
+  * Fix a warning that eroniously warned for un-used blocks if they were under a "Code" element (Reported by [@narirou](https://github.com/narirou))
+
+1.4.1 / 2014-07-16
+==================
+
+  * Fix an error that sometimes resulted in 'unexpected token "pipless-text"' being erroniously thrown (Reported by [@Artazor](https://github.com/Artazor) and [@thenitai](https://github.com/thenitai))
+
+1.4.0 / 2014-07-15
+==================
+
+  * Fix CLI so it keeps watching when errors occur ([@AndrewTsao](https://github.com/AndrewTsao))
+  * Support custom names for client side templates ([@ForbesLindesay](http://www.forbeslindesay.co.uk/) and [@dscape](https://github.com/dscape))
+  * Allow whitepsace other than "space" before attributes passed to mixins (N.B. there is a small chance this could be a breaking change for you) ([@regular](https://github.com/regular))
+  * Track dependencies so file watchers can be more clever ([@ForbesLindesay](http://www.forbeslindesay.co.uk/) and [@sdether](https://github.com/sdether))
+  * Allow passing options to filtered includes ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+  * Fix bugs with indentation in filters ([@ForbesLindesay](http://www.forbeslindesay.co.uk/) and [@lackac](https://github.com/lackac))
+  * Warn on block names that are never used ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+
 1.3.1 / 2014-04-04
 ==================
 
-  * Fix error with tags in xml that are self-closing in html ([@ForbesLindesay](http://www.forbeslindesay.co.uk/)
+  * Fix error with tags in xml that are self-closing in html ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
   * Fix error message for inline tags with content ([@hiddentao](https://github.com/hiddentao))
 
 1.3.0 / 2014-03-02
diff --git a/README.md b/README.md
index 5d63638..666446b 100644
--- a/README.md
+++ b/README.md
@@ -7,9 +7,9 @@ Full documentation is at [jade-lang.com](http://jade-lang.com/)
 
  You can test drive Jade online [here](http://naltatis.github.com/jade-syntax-docs).
 
- [![Build Status](https://travis-ci.org/visionmedia/jade.png?branch=master)](https://travis-ci.org/visionmedia/jade)
- [![Dependency Status](https://gemnasium.com/visionmedia/jade.png)](https://gemnasium.com/visionmedia/jade)
- [![NPM version](https://badge.fury.io/js/jade.png)](http://badge.fury.io/js/jade)
+ [![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)
 
 ## Installation
 
@@ -123,7 +123,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)
 
 Implementations in other languages:
 
@@ -141,6 +141,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
+  - [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 160ea0e..3adde19 100644
--- a/Readme_zh-cn.md
+++ b/Readme_zh-cn.md
@@ -62,7 +62,7 @@ Jade 是一个高性能的模板引擎,它深受 [Haml](http://haml-lang.com)
   - 安全,默认代码是转义的
   - 运行时和编译时上下文错误报告 
   - 命令行下编译jade模板
-  - HTML5 模式 (使用 `!!! 5` 文档类型)
+  - HTML5 模式 (使用 ~~`!!! 5`~~ `doctype html` 文档类型)
   - 在内存中缓存(可选)
   - 合并动态和静态标签类
   - 可以通过 `filters` 修改树
diff --git a/bin/jade.js b/bin/jade.js
index 39ec040..6411066 100755
--- a/bin/jade.js
+++ b/bin/jade.js
@@ -30,8 +30,11 @@ program
   .option('-p, --path <path>', 'filename used to resolve includes')
   .option('-P, --pretty', 'compile pretty html output')
   .option('-c, --client', 'compile function for client-side runtime.js')
+  .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('--name-after-file', 'Name the template after the last section of the file path (requires --client and overriden by --name)')
+
 
 program.on('--help', function(){
   console.log('  Examples:');
@@ -85,6 +88,10 @@ options.pretty = program.pretty;
 
 options.watch = program.watch;
 
+// --name
+
+options.name = program.name;
+
 // left-over args are file paths
 
 var files = program.args;
@@ -93,14 +100,20 @@ var files = program.args;
 
 if (files.length) {
   console.log();
-  files.forEach(renderFile);
   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);
       }
     });
+  } else {
+    files.forEach(renderFile);
   }
   process.on('exit', function () {
     console.log();
@@ -128,7 +141,7 @@ function stdin() {
     }
     process.stdout.write(output);
   }).resume();
-  
+
   process.on('SIGINT', function() {
     process.stdout.write('\n');
     process.stdin.emit('end');
@@ -151,6 +164,9 @@ function renderFile(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);
@@ -184,3 +200,16 @@ function renderFile(path) {
     }
   });
 }
+
+/**
+ * Get a sensible name for a template function from a file path
+ *
+ * @param {String} filename
+ * @returns {String}
+ */
+function getNameFromFileName(filename) {
+  var file = path.basename(filename, '.jade');
+  return file.toLowerCase().replace(/[^a-z0-9]+([a-z])/g, function (_, character) {
+    return character.toUpperCase();
+  }) + 'Template';
+}
diff --git a/component.json b/component.json
index 392cc42..c43dfb4 100644
--- a/component.json
+++ b/component.json
@@ -2,7 +2,7 @@
   "name": "jade",
   "repo": "visionmedia/jade",
   "description": "Jade template runtime",
-  "version": "1.3.1",
+  "version": "1.5.0",
   "keywords": [
     "template"
   ],
diff --git a/docs/client/index.js b/docs/client/index.js
new file mode 100644
index 0000000..123db7f
--- /dev/null
+++ b/docs/client/index.js
@@ -0,0 +1,108 @@
+'use strict';
+
+if (!(typeof Function.prototype.bind === 'function' && typeof Array.isArray === 'function') ||
+    /no-interactivity/.test(location.search)) {
+  return;
+}
+
+var CodeMirror = require('code-mirror');
+require('code-mirror/mode/htmlmixed');
+require('jade-code-mirror');
+var uglify = require('uglify-js')
+var jade = require('../../')
+
+function $(selector, parent) {
+  return Array.prototype.slice.call((parent || document).querySelectorAll(selector));
+}
+function _(selector, parent) {
+  return (parent || document).querySelector(selector)
+}
+
+function betterTab(cm) {
+  if (cm.somethingSelected()) {
+    cm.indentSelection('add');
+  } else {
+    cm.replaceSelection('  ', 'end', '+input');
+  }
+}
+
+function cm(el, mode, readonly) {
+  if (el.parentNode) {
+    var div = document.createElement('div');
+    if (readonly) {
+      div.setAttribute('class', 'read-only-code-mirror');
+    }
+    el.parentNode.replaceChild(div, el);
+    return CodeMirror(div, {
+      value: el.textContent,
+      mode: mode,
+      readOnly: readonly || false,
+      viewportMargin: Infinity,
+      indentWithTabs: false,
+      extraKeys: {
+        'Tab': betterTab,
+        'Shift-Tab': function (cm) { CodeMirror.commands.indentLess(cm) }
+      }
+    });
+  } else {
+    return {
+      on: function (event, handler) {},
+      getValue: function () { return el.textContent; },
+      setValue: function (src) { el.textContent = ''; }
+    };
+  }
+}
+
+$('[data-control="interactive"]').forEach(function (control) {
+  var jade = _('[data-control="input-jade"]', control);
+  var js = _('[data-control="input-js"]', control) || {textContent: '{ pageTitle: "Jade", youAreUsingJade: true }'};
+  var html = _('[data-control="output-html"]', control) || {};
+  var jsOut = _('[data-control="output-js"]', control) || {};
+  var editable = false;
+  if (editable) return;
+  editable = true;
+  handleChanges(cm(jade, 'jade'), cm(js, 'javascript'), cm(html, 'htmlmixed', true), cm(jsOut, 'javascript', true));
+});
+
+function handleChanges(jadeInput, js, html, jsOut) {
+  jadeInput.on('change', update);
+  js.on('change', update);
+  update();
+  function update() {
+    var jadeSrc = jadeInput.getValue();
+    var jsSrc = js.getValue();
+    
+    var jsObjA, jsObjB, jsObjC;
+    try {
+      jsObjA = Function('', 'return ' + js.getValue())() || {};
+      jsObjB = Function('', 'return ' + js.getValue())() || {};
+      jsObjC = Function('', 'return ' + js.getValue())() || {};
+      if (jsObjA.compileDebug === undefined) jsObjA.compileDebug = true;
+      jade.compileClient(jadeSrc, jsObjA);
+      if (jsObjB.compileDebug === undefined) jsObjB.compileDebug = false;
+      var jsOutSrc = jade.compileClient(jadeSrc, jsObjB);
+      try {
+        jsOutSrc = uglify.minify(jsOutSrc, {
+          fromString: true,
+          mangle: false,
+          output: {beautify: true},
+          compress: false
+        }).code
+      } catch (ex) {}
+      jsOut.setValue(jsOutSrc.trim());
+    } catch (ex) {
+      jsOut.setValue(ex.message || ex);
+      html.setValue(ex.message || ex);
+      return;
+    }
+    try {
+      if (jsObjC.compileDebug === undefined) jsObjC.compileDebug = true;
+      if (jsObjC.pretty === undefined) jsObjC.pretty = true;
+      var htmlOutSrc = jade.render(jadeSrc, jsObjC);
+      html.setValue(htmlOutSrc.trim());
+    } catch (ex) {
+      html.setValue(ex.message || ex);
+      return;
+    }
+  }
+}
\ No newline at end of file
diff --git a/docs/server.js b/docs/server.js
new file mode 100644
index 0000000..f69714e
--- /dev/null
+++ b/docs/server.js
@@ -0,0 +1,115 @@
+'use strict';
+
+var path = require('path');
+var fs = require('fs');
+var marked = require('marked');
+var express = require('express');
+var less = require('less-file');
+var browserify = require('browserify-middleware');
+var CodeMirror = require('highlight-codemirror');
+var highlightJade = require('jade-highlighter');
+var jade = require('../');
+
+
+var version = require('../package.json').version;
+var app = express();
+
+var filters = jade.filters;
+
+CodeMirror.loadMode('xml');//dep of htmlmixed
+CodeMirror.loadMode('htmlmixed');
+CodeMirror.loadMode('javascript');
+CodeMirror.loadMode('css');
+
+filters.jadesrc = highlightJade
+filters.htmlsrc = function (html) {
+  return CodeMirror.highlight(html, {name: 'htmlmixed'});
+};
+filters.jssrc = function (js) {
+  return CodeMirror.highlight(js, {name: 'javascript'});
+};
+filters.csssrc = function (css) {
+  return CodeMirror.highlight(css, {name: 'css'});
+};
+
+app.engine('jade', jade.renderFile);
+app.set('views', __dirname + '/views');
+
+app.locals.doctypes = jade.doctypes;
+
+app.use(function (req, res, next) {
+  if (req.url.substr(0, version.length + 2) === '/' + version + '/') {
+    req.url = req.url.substr(version.length + 1);
+    res.locals.path = function (path) {
+      return '/' + version + path;
+    };
+  } else if (/^\/\d+\.\d+\.\d+\//.test(req.url)) {
+    res.send(404, 'This page only exists on the live website');
+    return;
+  } else {
+    res.locals.path = function (path) {
+      return path;
+    };
+  }
+  next();
+});
+
+app.get('/', function (req, res, next) {
+  res.render('home.jade');
+});
+app.get('/reference', function (req, res, next) {
+  res.render('reference.jade', {section: 'reference'});
+});
+app.get('/reference/:name', function (req, res, next) {
+  res.render('reference/' + req.params.name + '.jade', {
+    section: 'reference',
+    currentDocumentation: req.params.name
+  });
+});
+app.get('/api', function (req, res, next) {
+  res.render('api.jade', {section: 'api'});
+});
+app.get('/command-line', function (req, res, next) {
+  res.render('command-line.jade', {section: 'command-line'});
+});
+app.get('/history', function (req, res, next) {
+  var versionHeader = /(\d+\.\d+\.\d+) *\/ *\d\d\d\d\-\d\d\-\d\d\<\/h2\>/g;
+  var versions = JSON.parse(fs.readFileSync(__dirname + '/versions.json', 'utf8'));
+  if (versions.indexOf(version) === -1) {
+    versions.push(version);
+    fs.writeFileSync(__dirname + '/versions.json', JSON.stringify(versions, null, '  '));
+  }
+  var history = marked(fs.readFileSync(__dirname + '/../History.md', 'utf8'))
+    .replace(/h1/g, 'h2')
+    .replace(versionHeader, function (_, version) {
+      if (versions.indexOf(version) !== -1) {
+        return _ + '<p><a href="/' + version +
+          '/reference" rel="nofollow">Documentation</a></p>';
+      } else {
+        return _;
+      }
+    });
+  res.render('history.jade', {
+    section: 'history',
+    history: history
+  });
+});
+
+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();
+  console.error(msg);
+  if (res.statusCode < 400) res.statusCode = 500;
+  if (err.status) res.statusCode = err.status;
+  res.setHeader('Content-Type', 'text/plain');
+  res.setHeader('Content-Length', Buffer.byteLength(msg));
+  if ('HEAD' == req.method) return res.end();
+  res.end(msg);
+});
+
+module.exports = app.listen(3000);
+module.exports.version = version;
diff --git a/docs/stop.js b/docs/stop.js
new file mode 100644
index 0000000..d4a08ab
--- /dev/null
+++ b/docs/stop.js
@@ -0,0 +1,34 @@
+'use strict';
+
+var url = require('url');
+var stop = require('stop');
+var rimraf = require('rimraf').sync;
+
+var server = require('./server.js');
+var version = require('../package.json').version;
+
+rimraf(__dirname + '/out');
+
+module.exports = stop.getWebsiteStream('http://localhost:3000', {
+  filter: function (currentURL) {
+    var u = url.parse(currentURL);
+    return u.hostname === 'localhost' &&
+      (!/^\/\d+\.\d+\.\d+\//.test(u.pathname) ||
+       u.pathname.substr(0, version.length + 2) === '/' + version + '/');
+  },
+  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) {
+    throw new Error('Unexpected status code ' + page.statusCode +
+                    ' for ' + page.url);
+  }
+  console.log(page.statusCode + ' - ' + page.url);
+})
+.syphon(stop.writeFileSystem(__dirname + '/out'))
+.wait().then(function () {
+  server.close();
+  console.log('successfuly compiled website');
+});
diff --git a/docs/style/footer.less b/docs/style/footer.less
new file mode 100644
index 0000000..d1b00cc
--- /dev/null
+++ b/docs/style/footer.less
@@ -0,0 +1,32 @@
+
+ at footer-height: 40px;
+ at footer-height-narrow: 60px;
+ at footer-padding: 10px;
+
+/* Sticky footer styles */
+html, body {
+  height: 100%;
+  padding: 0;
+  margin: 0;
+}
+
+// Wrapper for page content to push down footer
+#wrap {
+  min-height: 100%; height: auto !important; height: 100%;
+  // Negative indent footer by it's height
+  margin: 0 auto - at footer-height;
+}
+
+// Set the fixed height of the footer here
+#push, #footer-wrapper { height: @footer-height; }
+#footer-wrapper { background-color: #f5f5f5; }
+
+footer { padding: @footer-padding 0; border-top: 1px solid #e5e5e5; background-color: #f5f5f5; }
+
+footer p { margin-bottom: 0; color: #777; }
+
+// Lastly, apply responsive CSS fixes as necessary
+ at media (max-width: 767px) {
+  #push, #footer-wrapper { height: @footer-height-narrow; }
+  #wrap { margin: 0 auto - at footer-height-narrow; }
+}
\ No newline at end of file
diff --git a/docs/style/highlighting.less b/docs/style/highlighting.less
new file mode 100644
index 0000000..ef4bb61
--- /dev/null
+++ b/docs/style/highlighting.less
@@ -0,0 +1,37 @@
+ at import (npm) "inconsolata";
+ at import (npm) "code-mirror/codemirror";
+
+pre code, pre, pre span {
+  font-family: 'Inconsolata' monospace;
+  font-size: 17px;
+  overflow: auto;
+  word-wrap: normal;
+  white-space: pre;
+  line-height: 1.2;
+}
+pre {
+  padding: 4px;
+}
+.CodeMirror {
+  font-size: 17px;
+  font-family: 'Inconsolata' monospace;
+}
+
+.CodeMirror {
+  height: auto;
+}
+.CodeMirror-scroll {
+  overflow-y: hidden;
+  overflow-x: auto;
+}
+
+pre, .CodeMirror, .code {
+  border: 2px solid grey;
+  background: white;
+  border-radius: 0;
+  margin-bottom: 10px;
+}
+
+.read-only-code-mirror .CodeMirror-cursor {
+  visibility: hidden !important;
+}
diff --git a/docs/style/index.less b/docs/style/index.less
new file mode 100644
index 0000000..028407e
--- /dev/null
+++ b/docs/style/index.less
@@ -0,0 +1,47 @@
+ at import (npm) "twbs";
+
+ at jade: #00a86b;
+ at brand-primary: @jade;
+ at grid-float-breakpoint: @screen-md-min;
+
+ at media (min-width: 1500px) {
+  .container {
+    width: 1450px + @grid-gutter-width;
+  }
+}
+ at media (max-width: @grid-float-breakpoint) {
+  .navbar .container {
+    width: auto;
+  }
+  .navbar.navbar-default .navbar-header {
+    background: @jade;
+    .navbar-brand {
+      color: white;
+    }
+    .icon-bar {
+      background-color: white;
+    }
+    .navbar-toggle:hover, .navbar-toggle:focus {
+      background: inherit;
+    }
+  }
+  .logo-container {
+    display: none;
+  }
+}
+
+ at import "./footer.less";
+ at import "./highlighting.less";
+ at import "./parameter-lists.less";
+
+
+.logo-container {
+  text-align: center;
+  background-color: @jade;
+  padding-top: 50px;
+  padding-bottom: 50px;
+}
+
+p {
+  font-size: 1.2em;
+}
diff --git a/docs/style/logo.png b/docs/style/logo.png
new file mode 100644
index 0000000..1ce783e
Binary files /dev/null and b/docs/style/logo.png differ
diff --git a/docs/style/parameter-lists.less b/docs/style/parameter-lists.less
new file mode 100644
index 0000000..c29af47
--- /dev/null
+++ b/docs/style/parameter-lists.less
@@ -0,0 +1,58 @@
+dl.parameter-list, dl.returns {
+    margin-left: 1em;
+    margin-bottom: 0;
+}
+
+dl.returns {
+    margin-top: 1em;
+}
+dl.parameter-list, dl.returns {
+  dt {
+      color: #666;
+      float: left;
+      margin-right: 16px;
+      min-width: 110px;
+  }
+  dd.type {
+      margin: 0;
+      float: left;
+      -moz-border-radius: 5px;
+      -webkit-border-radius: 5px;
+      border-radius: 5px;
+      background: #CCC;
+      font-size: .75em;
+      min-width: 80px;
+      margin: 0 8px 0 0;
+      padding: 2px 5px;
+      text-align: center;
+  }
+  dd.description {
+      display: table;
+      min-height: 24px;
+  }
+
+  dd.type.string {
+      background: #e1edb1;
+      color: #3d4c00;
+  }
+  dd.type.object {
+      background: #edb1b1;
+      color: #4c0000;
+  }
+  dd.type.function {
+      background: #cfb1ed;
+      color: #26004c;
+  }
+  dd.type.number {
+      background: #b1c9ed;
+      color: #001e4c;
+  }
+  dd.type.boolean {
+      background: #b1edc9;
+      color: #004c1e;
+  }
+  dd.type.array {
+      background: #edd5b1;
+      color: #4c2d00;
+  }
+}
diff --git a/docs/versions.json b/docs/versions.json
new file mode 100644
index 0000000..9dfc49e
--- /dev/null
+++ b/docs/versions.json
@@ -0,0 +1,7 @@
+[
+  "1.3.1",
+  "1.4.0",
+  "1.4.1",
+  "1.4.2",
+  "1.5.0"
+]
\ No newline at end of file
diff --git a/docs/views/api.jade b/docs/views/api.jade
new file mode 100644
index 0000000..d4a50b9
--- /dev/null
+++ b/docs/views/api.jade
@@ -0,0 +1,165 @@
+extends ./layout.jade
+
+block content
+  h1 API Documentation
+
+  p This page details how to render jade using the JavaScript API in node.js
+
+  h2 Installation
+
+  p via npm:
+
+  pre
+    code npm install jade
+
+  h2 Usage
+
+  h3 options
+
+  p All API methods take the following set of options:
+
+  .code
+    div {
+    dl.parameter-list
+      dt filename:
+      dd.type.string string
+      dd.description Used in exceptions, and required for relative includes and extends
+      dt pretty:
+      dd.type.boolean boolean
+      dd.description Adds whitespace to the resulting html to make it easier for a human to read
+      dt self:
+      dd.type.boolean boolean
+      dd.description Use a #[code self] namespace to hold the locals (false by default)
+      dt debug:
+      dd.type.boolean boolean
+      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).
+      dt compiler:
+      dd.type.function class
+      dd.description Override the default compiler
+    div }
+
+  h3 jade.compile(source, options)
+
+  p Compile some jade source to a function which can be rendered multiple times with different locals.
+
+  dl.parameter-list
+    dt source
+    dd.type.string string
+    dd.description The source jade to compile
+    dt options
+    dd.type.object options
+    dd.description An options object (see above)
+  dl.returns
+    dt returns
+    dd.type.function function
+    dd.description A function to generate the html from an object containing locals
+
+  +js
+    :jssrc
+      var jade = require('jade');
+
+      // Compile a function
+      var fn = jade.compile('string of jade', options);
+
+      // Render the function
+      var html = fn(locals);
+      // => '<string>of jade</string>'
+
+  h3 jade.compileFile(path, options)
+
+  p Compile some jade source from a file to a function which can be rendered multiple times with different locals.
+
+  dl.parameter-list
+    dt source
+    dd.type.string path
+    dd.description The path to a jade file
+    dt options
+    dd.type.object options
+    dd.description An options object (see above)
+  dl.returns
+    dt returns
+    dd.type.function function
+    dd.description A function to generate the html from an object containing locals
+
+  +js
+    :jssrc
+      var jade = require('jade');
+
+      // Compile a function
+      var fn = jade.compileFile('path to jade file', options);
+
+      // Render the function
+      var html = fn(locals);
+      // => '<string>of jade</string>'
+
+  h3 jade.compileClient(source, options)
+
+  p Compile some jade source to a string of JavaScript that can be used client side along with the jade runtime.
+
+  dl.parameter-list
+    dt source
+    dd.type.string string
+    dd.description The source jade to compile
+    dt options
+    dd.type.object options
+    dd.description An options object (see above)
+  dl.returns
+    dt returns
+    dd.type.string string
+    dd.description A string of JavaScript representing a function
+
+  +js
+    :jssrc
+      var jade = require('jade');
+
+      // Compile a function
+      var fn = jade.compileClient('string of jade', options);
+
+      // Render the function
+      var html = fn(locals);
+      // => 'function template(locals) { return "<string>of jade</string>"; }'
+
+  h3 jade.render(source, options)
+
+  dl.parameter-list
+    dt source
+    dd.type.string string
+    dd.description The source jade to render
+    dt options
+    dd.type.object options
+    dd.description An options object (see above), also used as the locals object
+  dl.returns
+    dt returns
+    dd.type.string string
+    dd.description The resulting html string
+
+  +js
+    :jssrc
+      var jade = require('jade');
+
+      var html = jade.render('string of jade', options);
+      // => '<string>of jade</string>'
+
+  h3 jade.renderFile(filename, options)
+
+  dl.parameter-list
+    dt filename
+    dd.type.string string
+    dd.description The path to the jade file to render
+    dt options
+    dd.type.object options
+    dd.description An options object (see above), also used as the locals object
+  dl.returns
+    dt returns
+    dd.type.string string
+    dd.description The resulting html string
+
+  +js
+    :jssrc
+      var jade = require('jade');
+
+      var html = jade.renderFile('path/to/file.jade', options);
+      // ...
diff --git a/docs/views/command-line.jade b/docs/views/command-line.jade
new file mode 100644
index 0000000..59b5360
--- /dev/null
+++ b/docs/views/command-line.jade
@@ -0,0 +1,53 @@
+extends ./layout.jade
+
+block content
+  .container
+    #body
+      :markdown
+        # Command Line
+
+        ## Installation
+
+        via npm:
+
+            $ npm install jade --global
+
+        ## Usage
+
+            $ jade [options] [dir|file ...]
+
+        ### 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
+        ```
+
+        ### Examples
+
+        Translate jade the templates dir:
+
+            $ jade templates
+
+        Create {foo,bar}.html
+
+            $ jade {foo,bar}.jade
+
+        Jade over stdio
+
+            $ jade < my.jade > my.html
+
+        Jade over stdio
+
+            $ echo "h1 Jade!" | jade
+
+        foo, bar dirs rendering to /tmp
+
+            $ jade foo bar --out /tmp
diff --git a/docs/views/history.jade b/docs/views/history.jade
new file mode 100644
index 0000000..aa189dc
--- /dev/null
+++ b/docs/views/history.jade
@@ -0,0 +1,5 @@
+extends ./layout.jade
+
+block content
+  h1 Change Log
+  != history
diff --git a/docs/views/home.jade b/docs/views/home.jade
new file mode 100644
index 0000000..cbad0a6
--- /dev/null
+++ b/docs/views/home.jade
@@ -0,0 +1,51 @@
+extends ./layout.jade
+
+block content
+  .row(data-control='interactive')
+      .col-md-6
+        +jade
+          :jadesrc
+            doctype html
+            html(lang="en")
+              head
+                title= pageTitle
+                script(type='text/javascript').
+                  if (foo) {
+                     bar(1 + 5)
+                  }
+              body
+                h1 Jade - node template engine
+                #container.col
+                  if youAreUsingJade
+                    p You are amazing
+                  else
+                    p Get on it!
+                  p.
+                    Jade is a terse and simple
+                    templating language with a
+                    strong focus on performance
+                    and powerful features.
+      .col-md-6
+        +html
+          :htmlsrc
+            <!DOCTYPE html>
+            <html lang="en">
+              <head>
+                <title>Jade</title>
+                <script type="text/javascript">
+                  if (foo) {
+                    bar(1 + 5)
+                  }
+                </script>
+              </head>
+              <body>
+                <h1>Jade - node template engine</h1>
+                <div id="container" class="col">
+                  <p>You are amazing</p>
+                  <p>Jade is a terse and simple
+                     templating language with a
+                     strong focus on performance
+                     and powerful features.</p>
+                </div>
+              </body>
+            </html>
\ No newline at end of file
diff --git a/docs/views/includes/ga.jade b/docs/views/includes/ga.jade
new file mode 100644
index 0000000..51c4434
--- /dev/null
+++ b/docs/views/includes/ga.jade
@@ -0,0 +1,10 @@
+script.
+  var _gaq = _gaq || [];
+  _gaq.push(['_setAccount', 'UA-25261536-1']);
+  _gaq.push(['_trackPageview']);
+
+  (function() {
+    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+  })();
\ No newline at end of file
diff --git a/docs/views/includes/mixins.jade b/docs/views/includes/mixins.jade
new file mode 100644
index 0000000..9db514f
--- /dev/null
+++ b/docs/views/includes/mixins.jade
@@ -0,0 +1,12 @@
+mixin jade
+  pre.cm-s-default(data-control='input-jade')
+    code
+      block
+mixin html
+  pre.cm-s-default(data-control='output-html')
+    code
+      block
+mixin js
+  pre.cm-s-default(data-control='input-js')
+    code
+      block
\ No newline at end of file
diff --git a/docs/views/layout.jade b/docs/views/layout.jade
new file mode 100644
index 0000000..5123cf7
--- /dev/null
+++ b/docs/views/layout.jade
@@ -0,0 +1,55 @@
+doctype html
+html
+  head
+    meta(charset='utf-8')
+    title Jade - Template Engine
+    meta(name='viewport', content='width=device-width, initial-scale=1.0')
+    link(rel='stylesheet', href="/style/bundle.css")
+    include ./includes/mixins.jade
+  body
+  body
+    #wrap
+      #content
+        .logo-container
+          a(href=path('/'))
+            img(src="/style/logo.png")
+            .sr-only Jade - Node Template Engine
+        .navbar.navbar-default.navbar-static-top
+          .container
+            .navbar-header.visible-sm.visible-xs
+              button.navbar-toggle(type='button' data-toggle='collapse' data-target='.navbar-collapse')
+                span.sr-only Toggle navigation
+                span.icon-bar
+                span.icon-bar
+                span.icon-bar
+              a.navbar-brand(href=path('/')) JADE
+            .collapse.navbar-collapse
+              ul.nav.navbar-nav
+                block navsections
+                  li(class=section === 'reference' ? 'active' : false)
+                    a(href=path('/reference')) Language Reference
+                  li(class=section === 'api' ? 'active' : false)
+                    a(href=path('/api')) API
+                  li(class=section === 'command-line' ? 'active' : false)
+                    a(href=path('/command-line')) Command Line
+              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
+              block navigation
+        .container
+          block content
+      #push
+    #footer-wrapper
+      footer
+        .container
+          p
+            | Jade is a template language maintained by 
+            a(href='http://www.forbeslindesay.co.uk') @ForbesLindesay
+            |.
+    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")
diff --git a/docs/views/reference.jade b/docs/views/reference.jade
new file mode 100644
index 0000000..280fbe6
--- /dev/null
+++ b/docs/views/reference.jade
@@ -0,0 +1,35 @@
+extends ./layout.jade
+
+block content
+  mixin link(name)
+    - var id = name.replace(/ /g, '-')
+    li(class=(id === currentDocumentation ? 'active' : false))
+      a(href=path('/reference/' + id))= name
+  .row
+    .col-md-2
+      ul.nav.nav-pills.nav-stacked
+        +link('attributes')
+        +link('case')
+        +link('code')
+        +link('comments')
+        +link('conditionals')
+        +link('doctype')
+        +link('extends')
+        +link('filters')
+        +link('includes')
+        +link('iteration')
+        +link('mixins')
+        +link('plain text')
+        +link('tags')
+    .col-md-10
+      block documentation
+        h1 Language Reference
+        p.
+          Jade is a terse language for writing HTML templates.
+        ul
+          li Produces HTML
+          li Supports dynamic code
+          li Supports reusability (DRY)
+        p.
+          To read about the features of the language, select them from
+          the navigation menu on the right.
diff --git a/docs/views/reference/attributes.jade b/docs/views/reference/attributes.jade
new file mode 100644
index 0000000..7b8f557
--- /dev/null
+++ b/docs/views/reference/attributes.jade
@@ -0,0 +1,143 @@
+extends ../reference.jade
+
+block documentation
+  h1 Attributes
+
+  p Tag attributes look similar to html, however their values are just regular JavaScript.
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          a(href='google.com') Google
+          a(class='button', href='google.com') Google
+    .col-lg-6
+      +html
+        :htmlsrc
+          <a href="google.com">Google</a>
+          <a class="button" href="google.com">Google</a>
+
+  p All the normal JavaScript expressions work fine too:
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          - var authenticated = true
+          body(class=authenticated?'authed':'anon')
+    .col-lg-6
+      +html
+        :htmlsrc
+          <body class="authed"></body>
+
+  h4#booleanattribs Boolean Attributes
+
+  p Boolean attributes are mirrored by Jade, and accept bools, aka #[code true] or #[code false]. When no value is specified true is assumed.
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          input(type='checkbox', checked)
+          input(type='checkbox', checked=true)
+          input(type='checkbox', checked=false)
+          input(type='checkbox', checked=true.toString())
+    .col-lg-6
+      +html
+        :htmlsrc
+          <input type="checkbox" checked="checked" />
+          <input type="checkbox" checked="checked" />
+          <input type="checkbox" />
+          <input type="checkbox" checked="true" />
+
+  p If the doctype is #[code html] jade knows not to mirror the attribute and uses the terse style (understood by all browsers).
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          doctype html
+          input(type='checkbox', checked)
+          input(type='checkbox', checked=true)
+          input(type='checkbox', checked=false)
+          input(type='checkbox', checked=true && 'checked')
+    .col-lg-6
+      +html
+        :htmlsrc
+          <!DOCTYPE html>
+          <input type="checkbox" checked>
+          <input type="checkbox" checked>
+          <input type="checkbox">
+          <input type="checkbox" checked="checked">
+
+  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.
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          - var classes = ['foo', 'bar', 'baz']
+          a(class=classes)
+          //- the class attribute may also be repeated to merge arrays
+          a.bing(class=classes class=['bing'])
+    .col-lg-6
+      +html
+        :htmlsrc
+          <a class="foo bar baz"></a>
+          <a class="foo bar baz bing"></a>
+
+  h2 Class Literal
+
+  p Classes may be defined using a #[code .classname] syntax:
+
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          a.button
+    .col-lg-6
+      +html
+        :htmlsrc
+          <a class="button"></a>
+
+  p Since div's are such a common choice of tag, it is the default if you omit the tag name:
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          .content
+    .col-lg-6
+      +html
+        :htmlsrc
+          <div class="content"></div>
+
+  h2 ID Literal
+
+  p IDs may be defined using a <code>#idname</code> syntax:
+
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          a#main-link
+    .col-lg-6
+      +html
+        :htmlsrc
+          <a id="main-link"></a>
+
+  p Since div's are such a common choice of tag, it is the default if you omit the tag name:
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          #content
+    .col-lg-6
+      +html
+        :htmlsrc
+          <div id="content"></div>
diff --git a/docs/views/reference/case.jade b/docs/views/reference/case.jade
new file mode 100644
index 0000000..4c94e8d
--- /dev/null
+++ b/docs/views/reference/case.jade
@@ -0,0 +1,61 @@
+extends ../reference.jade
+
+block documentation
+  h1 Case
+
+  p The case statement is a shorthand for JavaScript's <code>switch</code> statement and takes the following form:
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          - var friends = 10
+          case friends
+            when 0
+              p you have no friends
+            when 1
+              p you have a friend
+            default
+              p you have \#{friends} friends
+    .col-lg-6
+      +html
+        :htmlsrc
+          <p>you have 10 friends</p>
+
+  h2 Case Fall Through
+
+  p You can use fall through just like in a select statement in JavaScript
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          - var friends = 0
+          case friends
+            when 0
+            when 1
+              p you have very few friends
+            default
+              p you have \#{friends} friends
+    .col-lg-6
+      +html
+        :htmlsrc
+          <p>you have very few friends</p>
+
+  h2 Block Expansion
+
+  p Block expansion may also be used:
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          - var friends = 1
+          case friends
+            when 0: p you have no friends
+            when 1: p you have a friend
+            default: p you have \#{friends} friends
+    .col-lg-6
+      +html
+        :htmlsrc
+          <p>you have a friend</p>
diff --git a/docs/views/reference/code.jade b/docs/views/reference/code.jade
new file mode 100644
index 0000000..9c8e193
--- /dev/null
+++ b/docs/views/reference/code.jade
@@ -0,0 +1,86 @@
+extends ../reference.jade
+
+block documentation
+  h1 Code
+
+  p Jade makes it possible to write inline JavaScript code in your templates.  There are three types of code.
+
+  h2 Unbuffered Code
+
+  p Unbuffered code starts with <code>-</code> does not add any output directly, e.g.
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          - for (var x = 0; x < 3; x++)
+            li item
+    .col-lg-6
+      +html
+        :htmlsrc
+          <li>item</li>
+          <li>item</li>
+          <li>item</li>
+
+  h2 Buffered Code
+
+  p Buffered code starts with <code>=</code> and outputs the result of evaluating the JavaScript expression in the template.  For security, it is first HTML escaped:
+
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          p
+            = 'This code is <escaped>!'
+    .col-lg-6
+      +html
+        :htmlsrc
+          <p>This code is <escaped>!</p>
+
+  p It can also be written inline with attributes, and supports the full range of JavaScript expressions:
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          p= 'This code is' + ' <escaped>!'
+    .col-lg-6
+      +html
+        :htmlsrc
+          <p>This code is <escaped>!</p>
+
+  h2 Unescaped Buffered Code
+
+  p Unescaped buffered code starts with <code>!=</code> and outputs the result of evaluating the JavaScript expression in the template.  This does not do any escaping, so is not safe for user input:
+
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          p
+            != 'This code is <strong>not</strong> escaped!'
+    .col-lg-6
+      +html
+        :htmlsrc
+          <p>This code is <strong>not</strong> escaped!</p>
+
+  p It can also be written inline with attributes, and supports the full range of JavaScript expressions:
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          p!= 'This code is <strong>not</strong> escaped!'
+    .col-lg-6
+      +html
+        :htmlsrc
+          <p>This code is <strong>not</strong> escaped!</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]
diff --git a/docs/views/reference/comments.jade b/docs/views/reference/comments.jade
new file mode 100644
index 0000000..9d54362
--- /dev/null
+++ b/docs/views/reference/comments.jade
@@ -0,0 +1,84 @@
+extends ../reference.jade
+
+block documentation
+  h1 Comments
+
+  p Single line comments look the same as JavaScript comments and must be placed on their own line:
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          // just some paragraphs
+          p foo
+          p bar
+    .col-lg-6
+      +html
+        :htmlsrc
+          <!-- just some paragraphs -->
+          <p>foo</p>
+          <p>bar</p>
+
+  p Jade also supports unbuffered comments, by simply adding a hyphen
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          //- will not output within markup
+          p foo
+          p bar
+    .col-lg-6
+      +html
+        :htmlsrc
+          <p>foo</p>
+          <p>bar</p>
+
+  h2 Block Comments
+
+  p A block comment is legal as well:
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          body
+            //
+              As much text as you want
+              can go here.
+    .col-lg-6
+      +html
+        :htmlsrc
+          <body>
+            <!--
+            As much text as you want
+            can go here.
+            -->
+          </body>
+
+  h2 Conditional Comments
+
+  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:
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :htmlsrc
+          <!--[if IE 8]>
+          <html lang="en" class="lt-ie9">
+          <![endif]-->
+          <!--[if gt IE 8]><!-->
+          <html lang="en">
+          <!--<![endif]-->
+    .col-lg-6
+      +html
+        :htmlsrc
+          <!--[if IE 8]>
+          <html lang="en" class="lt-ie9">
+          <![endif]-->
+          <!--[if gt IE 8]><!-->
+          <html lang="en">
+          <!--<![endif]-->
diff --git a/docs/views/reference/conditionals.jade b/docs/views/reference/conditionals.jade
new file mode 100644
index 0000000..0d885f0
--- /dev/null
+++ b/docs/views/reference/conditionals.jade
@@ -0,0 +1,46 @@
+extends ../reference.jade
+
+block documentation
+  h2 Conditionals
+
+  p Jade's first-class conditional syntax allows for optional parenthesis, and you may now omit the leading <code>-</code> otherwise it's identical, still just regular javascript:
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          - var user = { description: 'foo bar baz' }
+          - var authorised = false
+          #user
+            if user.description
+              h2 Description
+              p.description= user.description
+            else if authorised
+              h2 Description
+              p.description.
+                User has no description,
+                why not add one...
+            else
+              h1 Description
+              p.description User has no description
+    .col-lg-6
+      +html
+        :htmlsrc
+          <div id="user">
+            <h2>Description</h2>
+            <p class="description">foo bar baz</p>
+          </div>
+
+  p Jade also provides a negated version <code>unless</code> (the following are therefore equivalent):
+
+  .row
+    .col-lg-6
+      +jade
+        :jadesrc
+          unless user.isAnonymous
+            p You're logged in as \#{user.name}
+    .col-lg-6
+      +jade
+        :jadesrc
+          if !user.isAnonymous
+            p You're logged in as \#{user.name}
diff --git a/docs/views/reference/doctype.jade b/docs/views/reference/doctype.jade
new file mode 100644
index 0000000..17b5de7
--- /dev/null
+++ b/docs/views/reference/doctype.jade
@@ -0,0 +1,34 @@
+extends ../reference.jade
+
+block documentation
+  h1 doctype
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          doctype html
+    .col-lg-6
+      +html
+        :htmlsrc
+          <!DOCTYPE html>
+  p There are shortcuts for commonly used doctypes:
+
+  dl
+    each doctype, key in doctypes
+      if key != '5' && key != 'default'
+        dt= key
+        dd
+          pre
+            code= doctype
+
+  p You can also use your own literal custom doctype:
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          doctype html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN"
+    .col-lg-6
+      +html
+        :htmlsrc
+          <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN">
diff --git a/docs/views/reference/extends.jade b/docs/views/reference/extends.jade
new file mode 100644
index 0000000..cc08f3b
--- /dev/null
+++ b/docs/views/reference/extends.jade
@@ -0,0 +1,50 @@
+extends ../reference.jade
+
+block documentation
+  h1 Extends - Template Inheritance
+
+  p.
+    The #[code extends] keyword allows a template to extend a layout or parent template.
+    It can then override certain pre-defined blocks of content.
+
+  .row
+    .col-lg-6
+      +jade
+        :jadesrc
+          //- layout.jade
+          doctype html
+          html
+            head
+              block title
+                title Default title
+            body
+              block content
+      +jade
+        :jadesrc
+          //- index.jade
+          extends ./layout.jade
+
+          block title
+            title Article Title
+
+          block content
+            h1 My Article
+
+    .col-lg-6
+      +html
+        :htmlsrc
+          <!doctype html>
+          <html>
+            <head>
+              <title>Article Title</title>
+            </head>
+            <body>
+              <h1>My Article</h1>
+            </body>
+          </html>
+
+  .panel.panel-info
+    .panel-heading Note
+    .panel-body.
+      You can have multiple levels of inheritance, allowing you to create
+      powerful higherachies of templates.
diff --git a/docs/views/reference/filters.jade b/docs/views/reference/filters.jade
new file mode 100644
index 0000000..80d74f7
--- /dev/null
+++ b/docs/views/reference/filters.jade
@@ -0,0 +1,33 @@
+extends ../reference.jade
+
+block documentation
+  h2 Filters
+
+  p.
+    Filters let you use other languages within a jade template.
+    They take a block of plain text as an input.
+
+  .row
+    .col-lg-6
+      +jade
+        :jadesrc
+          :markdown
+            # Markdown
+
+            I often like including markdown documents.
+          script
+            :coffee
+              console.log 'This is coffee script'
+    .col-lg-6
+      +html
+        :htmlsrc
+          <h1>Markdown</h1>
+          <p>I often like including markdown documents.</p>
+          <script>console.log('This is coffee script')</script>
+  .panel.panel-warning
+    .panel-heading Warning
+    .panel-body
+      p Filters are compile time.  This makes them fast but means they cannot support dynamic content.
+      p.
+        Built in filters are not available in the browser as they would not all work.
+        Providing you compile your templates server side, they will still work fine though.
diff --git a/docs/views/reference/includes.jade b/docs/views/reference/includes.jade
new file mode 100644
index 0000000..cddadbe
--- /dev/null
+++ b/docs/views/reference/includes.jade
@@ -0,0 +1,133 @@
+extends ../reference.jade
+
+block documentation
+  h1 Includes
+
+  p Includes allow you to insert the contents of one jade file into another.
+
+  .row
+    .col-lg-6
+      +jade
+        :jadesrc
+          //- index.jade
+          doctype html
+          html
+            include ./includes/head.jade
+            body
+              h1 My Site
+              p Welcome to my super lame site.
+              include ./includes/foot.jade
+      +jade
+        :jadesrc
+          //- includes/head.jade
+          head
+            title My Site
+            script(src='/javascripts/jquery.js')
+            script(src='/javascripts/app.js')
+
+      +jade
+        :jadesrc
+          //- includes/foot.jade
+          #footer
+            p Copyright (c) foobar
+    .col-lg-6
+      +html
+        :htmlsrc
+          <!doctype html>
+          <html>
+            <head>
+              <title>My Site</title>
+              <script src='/javascripts/jquery.js'></script>
+              <script src='/javascripts/app.js'></script>
+            </head>
+            <body>
+              <h1>My Site</h1>
+              <p>Welcome to my super lame site.</p>
+              <div id="footer">
+                <p>Copyright (c) foobar</p>
+              </div>
+            </body>
+          </html>
+  h2 Including Plain Text
+
+  p Including files that are not jade just includes the raw text.
+
+  .row
+    .col-lg-6
+      +jade
+        :jadesrc
+          //- index.jade
+          doctype html
+          html
+            head
+              style
+                include style.css
+            body
+              h1 My Site
+              p Welcome to my super lame site.
+              script
+                include script.js
+      +jade
+        :csssrc
+          /* style.css */
+          h1 { color: red; }
+
+      +jade
+        :jssrc
+          // script.js
+          console.log('You are awesome');
+    .col-lg-6
+      +html
+        :htmlsrc
+          <!doctype html>
+          <html>
+            <head>
+              <style>
+                /* style.css */
+                h1 { color: red; }
+              </style>
+            </head>
+            <body>
+              <h1>My Site</h1>
+              <p>Welcome to my super lame site.</p>
+              <script>
+                // script.js
+                console.log('You are awesome');
+              </script>
+            </body>
+          </html>
+
+  h2 Including Filtered Text
+
+  p You can combine filters with includes to filter things as you include them.
+
+  .row
+    .col-lg-6
+      +jade
+        :jadesrc
+          //- index.jade
+          doctype html
+          html
+            head
+              title An Article
+            body
+              include:markdown article.md
+      +jade
+        :verbatim
+          # article.md
+
+          This is an article written in markdown.
+
+    .col-lg-6
+      +html
+        :htmlsrc
+          <!doctype html>
+          <html>
+            <head>
+              <title>An Article</title>
+            </head>
+            <body>
+              <h1>article.md</h1>
+              <p>This is an article written in markdown.</p>
+            </body>
+          </html>
diff --git a/docs/views/reference/iteration.jade b/docs/views/reference/iteration.jade
new file mode 100644
index 0000000..1c79e68
--- /dev/null
+++ b/docs/views/reference/iteration.jade
@@ -0,0 +1,90 @@
+extends ../reference.jade
+
+block documentation
+  h1 Iteration
+
+  p Jade supports two primary methods of iteration, #[code each] and #[code while].
+
+  h2 each
+
+  p.
+    Jade's first-class iteration syntax makes it easier to iterate over
+    arrays and objects within a template:
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          ul
+            each val in [1, 2, 3, 4, 5]
+              li= val
+    .col-lg-6
+      +html
+        :htmlsrc
+          <ul>
+            <li>1</li>
+            <li>2</li>
+            <li>3</li>
+            <li>4</li>
+            <li>5</li>
+          </ul>
+
+  p You can also get the index as you iterate:
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          ul
+            each val, index in ['zero', 'one', 'two']
+              li= index + ': ' + val
+    .col-lg-6
+      +html
+        :htmlsrc
+          <ul>
+            <li>0: zero</li>
+            <li>1: one</li>
+            <li>2: two</li>
+          </ul>
+
+  p Jade also lets you iterate over the keys in an object:
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          ul
+            each val, index in {1:'one',2:'two',3:'three'}
+              li= index + ': ' + val
+    .col-lg-6
+      +html
+        :htmlsrc
+          <ul>
+            <li>1: one</li>
+            <li>2: two</li>
+            <li>3: three</li>
+          </ul>
+
+  p The object or array to iterate over is just plain JavaScript so it can be a variable or the result of a function call as well.
+
+  h2 while
+
+  p You can also use #[code while] to create a loop:
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          - var n = 0
+          ul
+            while n < 4
+              li= n++
+    .col-lg-6
+      +html
+        :htmlsrc
+          <ul>
+            <li>0</li>
+            <li>1</li>
+            <li>2</li>
+            <li>3</li>
+          </ul>
diff --git a/docs/views/reference/mixins.jade b/docs/views/reference/mixins.jade
new file mode 100644
index 0000000..0553365
--- /dev/null
+++ b/docs/views/reference/mixins.jade
@@ -0,0 +1,117 @@
+extends ../reference.jade
+
+block documentation
+  h1 Mixins
+
+  p Mixins allow you to create reusable blocks of jade.
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          //- Declaration
+          mixin list
+            ul
+              li foo
+              li bar
+              li baz
+          //- Use
+          +list
+          +list
+    .col-lg-6
+      +html
+        :htmlsrc
+          <ul>
+            <li>foo</li>
+            <li>bar</li>
+            <li>baz</li>
+          </ul>
+          <ul>
+            <li>foo</li>
+            <li>bar</li>
+            <li>baz</li>
+          </ul>
+
+  p They are compiled to functions and can take arguments:
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          mixin pet(name)
+            li.pet= name
+          ul
+            +pet('cat')
+            +pet('dog')
+            +pet('pig')
+    .col-lg-6
+      +html
+        :htmlsrc
+          <ul>
+            <li class="pet">cat</li>
+            <li class="pet">dog</li>
+            <li class="pet">pig</li>
+          </ul>
+
+  h2 Mixin Blocks
+
+  p Mixins can also take a block of jade to act as the content:
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          mixin article(title)
+            .article
+              .article-wrapper
+                h1= title
+                if block
+                  block
+                else
+                  p No content provided
+
+          +article('Hello world')
+
+          +article('Hello world')
+            p This is my
+            p Amazing article
+    .col-lg-6
+      +html
+        :htmlsrc
+          <div class="article">
+            <div class="article-wrapper">
+              <h1>Hello world</h1>
+              <p>No content provided</p>
+            </div>
+          </div>
+
+          <div class="article">
+            <div class="article-wrapper">
+              <h1>Hello world</h1>
+              <p>This is my</p>
+              <p>Amazing article</p>
+            </div>
+          </div>
+
+  h2 Mixin Attributes
+
+  p Mixins also get an implicit attributes argument taken from the attributes passed to the mixin:
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          mixin link(href, name)
+            //- attributes == {class: "btn"}
+            a(class!=attributes.class, href=href)= name
+
+          +link('/foo', 'foo')(class="btn")
+    .col-lg-6
+      +html
+        :htmlsrc
+          <a class="btn" href="/foo">foo</a>
+  .panel.panel-info
+    .panel-heading Note
+    .panel-body.
+      The values in #[code attributes] are already escaped so you should
+      use #[code !=] to avoid escaping them a second time.
diff --git a/docs/views/reference/plain-text.jade b/docs/views/reference/plain-text.jade
new file mode 100644
index 0000000..9151b86
--- /dev/null
+++ b/docs/views/reference/plain-text.jade
@@ -0,0 +1,60 @@
+extends ../reference.jade
+
+block documentation
+  h1 Plain Text
+
+  p Jade provides three common ways of getting plain text.  They are useful in different situations
+
+  h2 Piped Text
+
+  p The simplest way of adding plain text to templates is to prefix the line with a <code>|</code> character (pronounced "pipe").
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          | Plain text can include <strong>html</strong>
+          p
+            | It must always be on its own line
+    .col-lg-6
+      +html
+        :htmlsrc
+          Plain text can include <strong>html</strong>
+          <p>It must always be on its own line</p>
+
+  h2 Inline in a Tag
+
+  p Since it's a common use case, you can put text in a tag just by adding it inline after a space.
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          p Plain text can include <strong>html</strong>
+    .col-lg-6
+      +html
+        :htmlsrc
+          <p>Plain text can include <strong>html</strong></p>
+
+  h2 Block in a Tag
+
+  p Often you might want large blocks of text within a tag.  A good example is with inline scripts or styles.  To do this, just add a <code>.</code> after the tag (with no preceding space):
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          script.
+            if (usingJade)
+              console.log('you are awesome')
+            else
+              console.log('use jade')
+    .col-lg-6
+      +html
+        :htmlsrc
+          <script>
+            if (usingJade)
+              console.log('you are awesome')
+            else
+              console.log('use jade')
+          </script>
diff --git a/docs/views/reference/tags.jade b/docs/views/reference/tags.jade
new file mode 100644
index 0000000..a43679f
--- /dev/null
+++ b/docs/views/reference/tags.jade
@@ -0,0 +1,49 @@
+extends ../reference.jade
+
+block documentation
+  h1 Tags
+
+  p By default, text at the start of a line (or after only white space) represents an html tag.  Indented tags are nested, creating the tree like structure of html.
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          ul
+            li Item A
+            li Item B
+            li Item C
+    .col-lg-6
+      +html
+        :htmlsrc
+          <ul>
+            <li>Item A</li>
+            <li>Item B</li>
+            <li>Item C</li>
+          </ul>
+
+  p Jade also knows which elements are self closing:
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          img
+    .col-lg-6
+      +html
+        :htmlsrc
+          <img/>
+
+  h2 Block Expansion
+
+  p To save space, jade provides an inline syntax for nested tags.
+
+  .row(data-control='interactive')
+    .col-lg-6
+      +jade
+        :jadesrc
+          a: img
+    .col-lg-6
+      +html
+        :htmlsrc
+          <a><img/></a>
diff --git a/lib/jade.js b/lib/jade.js
index 00b1a9d..e43f306 100644
--- a/lib/jade.js
+++ b/lib/jade.js
@@ -82,7 +82,7 @@ exports.cache = {};
  *
  * @param {String} str
  * @param {Object} options
- * @return {String}
+ * @return {Object}
  * @api private
  */
 
@@ -122,7 +122,7 @@ function parse(str, options){
   globals.push('jade_debug');
   globals.push('buf');
 
-  return ''
+  var body = ''
     + 'var buf = [];\n'
     + 'var jade_mixins = {};\n'
     + 'var jade_interp;\n'
@@ -130,6 +130,7 @@ function parse(str, options){
       ? 'var self = locals || {};\n' + js
       : addWith('locals || {}', '\n' + js, globals)) + ';'
     + 'return buf.join("");';
+  return {body: body, dependencies: parser.dependencies};
 }
 
 /**
@@ -157,27 +158,30 @@ exports.compile = function(str, options){
 
   str = String(str);
 
+  var parsed = parse(str, options);
   if (options.compileDebug !== false) {
     fn = [
         'var jade_debug = [{ lineno: 1, filename: ' + filename + ' }];'
       , 'try {'
-      , parse(str, options)
+      , parsed.body
       , '} catch (err) {'
       , '  jade.rethrow(err, jade_debug[0].filename, jade_debug[0].lineno' + (options.compileDebug === true ? ',' + JSON.stringify(str) : '') + ');'
       , '}'
     ].join('\n');
   } else {
-    fn = parse(str, options);
+    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 `jade.compileClient`');
+      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;
 };
 
@@ -187,8 +191,9 @@ exports.compile = function(str, options){
  * Options:
  *
  *   - `compileDebug` When it is `true`, the source code is included in
-       the compiled template for better error messages.
+ *     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
@@ -197,11 +202,10 @@ exports.compile = function(str, options){
  */
 
 exports.compileClient = function(str, options){
-  var options = options || {}
-    , filename = options.filename
-      ? JSON.stringify(options.filename)
-      : 'undefined'
-    , fn;
+  var options = options || {};
+  var name = options.name || 'template';
+  var filename = options.filename ? JSON.stringify(options.filename) : 'undefined';
+  var fn;
 
   str = String(str);
 
@@ -210,20 +214,47 @@ exports.compileClient = function(str, options){
     fn = [
         'var jade_debug = [{ lineno: 1, filename: ' + filename + ' }];'
       , 'try {'
-      , parse(str, options)
+      , parse(str, options).body
       , '} catch (err) {'
       , '  jade.rethrow(err, jade_debug[0].filename, jade_debug[0].lineno, ' + JSON.stringify(str) + ');'
       , '}'
     ].join('\n');
   } else {
     options.compileDebug = false;
-    fn = parse(str, options);
+    fn = parse(str, options).body;
   }
 
-  return 'function template(locals) {\n' + fn + '\n}';
+  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.
diff --git a/lib/lexer.js b/lib/lexer.js
index f16565f..1ec04c9 100644
--- a/lib/lexer.js
+++ b/lib/lexer.js
@@ -187,6 +187,7 @@ Lexer.prototype = {
       this.consume(captures[0].length);
       var tok = this.tok('comment', captures[2]);
       tok.buffer = '-' != captures[1];
+      this.pipeless = true;
       return tok;
     }
   },
@@ -236,7 +237,11 @@ Lexer.prototype = {
    */
 
   filter: function() {
-    return this.scan(/^:([\w\-]+)/, 'filter');
+    var tok = this.scan(/^:([\w\-]+)/, 'filter');
+    if (tok) {
+      this.pipeless = true;
+      return tok;
+    }
   },
 
   /**
@@ -275,7 +280,9 @@ Lexer.prototype = {
    */
 
   text: function() {
-    return this.scan(/^(?:\| ?| )([^\n]+)/, 'text') || this.scan(/^(<[^\n]*)/, 'text');
+    return this.scan(/^(?:\| ?| )([^\n]+)/, 'text') ||
+      this.scan(/^\|?( )/, 'text') ||
+      this.scan(/^(<[^\n]*)/, 'text');
   },
 
   textFail: function () {
@@ -292,7 +299,11 @@ Lexer.prototype = {
    */
 
   dot: function() {
-    return this.scan(/^\./, 'dot');
+    var match;
+    if (match = this.scan(/^\./, 'dot')) {
+      this.pipeless = true;
+      return match;
+    }
   },
 
   /**
@@ -368,7 +379,7 @@ Lexer.prototype = {
    * Yield.
    */
 
-  yield: function() {
+  'yield': function() {
     return this.scan(/^yield */, 'yield');
   },
 
@@ -386,12 +397,22 @@ Lexer.prototype = {
 
   includeFiltered: function() {
     var captures;
-    if (captures = /^include:([\w\-]+) +([^\n]+)/.exec(this.input)) {
-      this.consume(captures[0].length);
+    if (captures = /^include:([\w\-]+)([\( ])/.exec(this.input)) {
+      this.consume(captures[0].length - 1);
       var filter = captures[1];
-      var path = captures[2];
+      var attrs = captures[2] === '(' ? this.attrs() : null;
+      if (!(captures[2] === ' ' || this.input[0] === ' ')) {
+        throw new Error('expected space after include:filter but got ' + JSON.stringify(this.input[0]));
+      }
+      captures = /^ *([^\n]+)/.exec(this.input);
+      if (!captures || captures[1].trim() === '') {
+        throw new Error('missing path for include:filter');
+      }
+      this.consume(captures[0].length);
+      var path = captures[1];
       var tok = this.tok('include', path);
       tok.filter = filter;
+      tok.attrs = attrs;
       return tok;
     }
   },
@@ -450,7 +471,7 @@ Lexer.prototype = {
       if (captures = /^ *\(/.exec(this.input)) {
         try {
           var range = this.bracketExpression(captures[0].length - 1);
-          if (!/^ *[-\w]+ *=/.test(range.src)) { // not attributes
+          if (!/^\s*[-\w]+ *=/.test(range.src)) { // not attributes
             this.consume(range.end + 1);
             tok.args = range.src;
           }
@@ -762,7 +783,10 @@ Lexer.prototype = {
       }
 
       // blank line
-      if ('\n' == this.input[0]) return this.tok('newline');
+      if ('\n' == this.input[0]) {
+        this.pipeless = false;
+        return this.tok('newline');
+      }
 
       // outdent
       if (this.indentStack.length && indents < this.indentStack[0]) {
@@ -780,6 +804,7 @@ Lexer.prototype = {
         tok = this.tok('newline');
       }
 
+      this.pipeless = false;
       return tok;
     }
   },
@@ -790,13 +815,48 @@ Lexer.prototype = {
    */
 
   pipelessText: function() {
-    if (this.pipeless) {
-      if ('\n' == this.input[0]) return;
-      var i = this.input.indexOf('\n');
-      if (-1 == i) i = this.input.length;
-      var str = this.input.substr(0, i);
-      this.consume(str.length);
-      return this.tok('text', str);
+    if (!this.pipeless) return;
+    var captures, re;
+
+    // established regexp
+    if (this.indentRe) {
+      captures = this.indentRe.exec(this.input);
+    // determine regexp
+    } else {
+      // tabs
+      re = /^\n(\t*) */;
+      captures = re.exec(this.input);
+
+      // spaces
+      if (captures && !captures[1].length) {
+        re = /^\n( *)/;
+        captures = re.exec(this.input);
+      }
+
+      // established
+      if (captures && captures[1].length) this.indentRe = re;
+    }
+
+    var indents = captures && captures[1].length;
+    if (indents && (this.indentStack.length === 0 || indents > this.indentStack[0])) {
+      var indent = captures[1];
+      var line;
+      var tokens = [];
+      var isMatch;
+      do {
+        // text has `\n` as a prefix
+        var i = this.input.substr(1).indexOf('\n');
+        if (-1 == i) i = this.input.length - 1;
+        var str = this.input.substr(1, i);
+        isMatch = str.substr(0, indent.length) === indent || !str.trim();
+        if (isMatch) {
+          // consume test along with `\n` prefix if match
+          this.consume(str.length + 1);
+          tokens.push(str.substr(indent.length));
+        }
+      } while(this.input.length && isMatch);
+      while (this.input.length === 0 && tokens[tokens.length - 1] === '') tokens.pop();
+      return this.tok('pipeless-text', tokens);
     }
   },
 
@@ -809,10 +869,6 @@ Lexer.prototype = {
   },
 
   fail: function () {
-    if (/^ ($|\n)/.test(this.input)) {
-      this.consume(1);
-      return this.next();
-    }
     throw new Error('unexpected text ' + this.input.substr(0, 5));
   },
 
diff --git a/lib/parser.js b/lib/parser.js
index 8f01e9f..93c7836 100644
--- a/lib/parser.js
+++ b/lib/parser.js
@@ -28,6 +28,8 @@ var Parser = exports = module.exports = function Parser(str, filename, options){
   this.options = options;
   this.contexts = [this];
   this.inMixin = false;
+  this.dependencies = [];
+  this.inBlock = 0;
 };
 
 /**
@@ -135,6 +137,27 @@ Parser.prototype = {
       return ast;
     }
 
+    if (!this.extending && !this.included && Object.keys(this.blocks).length){
+      var blocks = [];
+      utils.walkAST(block, function (node) {
+        if (node.type === 'Block' && node.name) {
+          blocks.push(node.name);
+        }
+      });
+      Object.keys(this.blocks).forEach(function (name) {
+        if (blocks.indexOf(name) === -1 && !this.blocks[name].isSubBlock) {
+          console.warn('Warning: Unexpected block "'
+                       + name
+                       + '" '
+                       + ' on line '
+                       + this.blocks[name].line
+                       + ' of '
+                       + (this.blocks[name].filename)
+                       + '. This block is never used. This warning will be an error in v2.0.0');
+        }
+      }.bind(this));
+    }
+
     return block;
   },
 
@@ -236,7 +259,7 @@ Parser.prototype = {
 
   parseText: function(){
     var tok = this.expect('text');
-    var tokens = this.parseTextWithInlineTags(tok.val);
+    var tokens = this.parseInlineTagsInText(tok.val);
     if (tokens.length === 1) return tokens[0];
     var node = new nodes.Block;
     for (var i = 0; i < tokens.length; i++) {
@@ -360,10 +383,9 @@ Parser.prototype = {
     var tok = this.expect('comment');
     var node;
 
-    if ('indent' == this.peek().type) {
-      this.lexer.pipeless = true;
-      node = new nodes.BlockComment(tok.val, this.parseTextBlock(), tok.buffer);
-      this.lexer.pipeless = false;
+    var block;
+    if (block = this.parseTextBlock()) {
+      node = new nodes.BlockComment(tok.val, block, tok.buffer);
     } else {
       node = new nodes.Comment(tok.val, tok.buffer);
     }
@@ -392,13 +414,7 @@ Parser.prototype = {
     var attrs = this.accept('attrs');
     var block;
 
-    if ('indent' == this.peek().type) {
-      this.lexer.pipeless = true;
-      block = this.parseTextBlock();
-      this.lexer.pipeless = false;
-    } else {
-      block = new nodes.Block;
-    }
+    block = this.parseTextBlock() || new nodes.Block();
 
     var options = {};
     if (attrs) {
@@ -467,8 +483,10 @@ Parser.prototype = {
     var path = this.resolvePath(this.expect('extends').val.trim(), 'extends');
     if ('.jade' != path.substr(-5)) path += '.jade';
 
+    this.dependencies.push(path);
     var str = fs.readFileSync(path, 'utf8');
     var parser = new this.constructor(str, path, this.options);
+    parser.dependencies = this.dependencies;
 
     parser.blocks = this.blocks;
     parser.contexts = this.contexts;
@@ -487,9 +505,15 @@ Parser.prototype = {
     var mode = block.mode;
     var name = block.val.trim();
 
+    var line = block.line;
+
+    this.inBlock++;
     block = 'indent' == this.peek().type
       ? this.block()
       : new nodes.Block(new nodes.Literal(''));
+    this.inBlock--;
+    block.name = name;
+    block.line = line;
 
     var prev = this.blocks[name] || {prepended: [], appended: []}
     if (prev.mode === 'replace') return this.blocks[name] = prev;
@@ -514,6 +538,8 @@ Parser.prototype = {
     block.mode = mode;
     block.parser = this;
 
+    block.isSubBlock = this.inBlock > 0;
+
     return this.blocks[name] = block;
   },
 
@@ -534,11 +560,17 @@ Parser.prototype = {
     var tok = this.expect('include');
 
     var path = this.resolvePath(tok.val.trim(), 'include');
-
+    this.dependencies.push(path);
     // has-filter
     if (tok.filter) {
       var str = fs.readFileSync(path, 'utf8').replace(/\r/g, '');
-      str = filters(tok.filter, str, { filename: path });
+      var options = {filename: path};
+      if (tok.attrs) {
+        tok.attrs.attrs.forEach(function (attribute) {
+          options[attribute.name] = constantinople.toConstant(attribute.val);
+        });
+      }
+      str = filters(tok.filter, str, options);
       return new nodes.Literal(str);
     }
 
@@ -550,7 +582,10 @@ Parser.prototype = {
 
     var str = fs.readFileSync(path, 'utf8');
     var parser = new this.constructor(str, path, this.options);
+    parser.dependencies = this.dependencies;
+
     parser.blocks = utils.merge({}, this.blocks);
+    parser.included = true;
 
     parser.mixins = this.mixins;
 
@@ -608,7 +643,7 @@ Parser.prototype = {
     }
   },
 
-  parseTextWithInlineTags: function (str) {
+  parseInlineTagsInText: function (str) {
     var line = this.line();
 
     var match = /(\\)?#\[((?:.|\n)*)$/.exec(str);
@@ -616,7 +651,7 @@ Parser.prototype = {
       if (match[1]) { // escape
         var text = new nodes.Text(str.substr(0, match.index) + '#[');
         text.line = line;
-        var rest = this.parseTextWithInlineTags(match[2]);
+        var rest = this.parseInlineTagsInText(match[2]);
         if (rest[0].type === 'Text') {
           text.val += rest[0].val;
           rest.shift();
@@ -630,7 +665,7 @@ Parser.prototype = {
         var range = parseJSExpression(rest);
         var inner = new Parser(range.src, this.filename, this.options);
         buffer.push(inner.parse());
-        return buffer.concat(this.parseTextWithInlineTags(rest.substr(range.end + 1)));
+        return buffer.concat(this.parseInlineTagsInText(rest.substr(range.end + 1)));
       }
     } else {
       var text = new nodes.Text(str);
@@ -646,30 +681,12 @@ Parser.prototype = {
   parseTextBlock: function(){
     var block = new nodes.Block;
     block.line = this.line();
-    var spaces = this.expect('indent').val;
-    if (null == this._spaces) this._spaces = spaces;
-    var indent = Array(spaces - this._spaces + 1).join(' ');
-    while ('outdent' != this.peek().type) {
-      switch (this.peek().type) {
-        case 'newline':
-          this.advance();
-          break;
-        case 'indent':
-          this.parseTextBlock(true).nodes.forEach(function(node){
-            block.push(node);
-          });
-          break;
-        default:
-          var texts = this.parseTextWithInlineTags(indent + this.advance().val);
-          texts.forEach(function (text) {
-            block.push(text);
-          });
-      }
-    }
-
-    if (spaces == this._spaces) this._spaces = null;
-    this.expect('outdent');
-
+    var body = this.peek();
+    if (body.type !== 'pipeless-text') return;
+    this.advance();
+    block.nodes = body.val.reduce(function (accumulator, text) {
+      return accumulator.concat(this.parseInlineTagsInText(text));
+    }.bind(this), []);
     return block;
   },
 
@@ -782,6 +799,7 @@ Parser.prototype = {
       case 'indent':
       case 'outdent':
       case 'eos':
+      case 'pipeless-text':
         break;
       default:
         throw new Error('Unexpected token `' + this.peek().type + '` expected `text`, `code`, `:`, `newline` or `eos`')
@@ -791,16 +809,12 @@ Parser.prototype = {
     while ('newline' == this.peek().type) this.advance();
 
     // block?
-    if ('indent' == this.peek().type) {
-      if (tag.textOnly) {
-        this.lexer.pipeless = true;
-        tag.block = this.parseTextBlock();
-        this.lexer.pipeless = false;
-      } else {
-        var block = this.block();
-        for (var i = 0, len = block.nodes.length; i < len; ++i) {
-          tag.block.push(block.nodes[i]);
-        }
+    if (tag.textOnly) {
+      tag.block = this.parseTextBlock();
+    } else if ('indent' == this.peek().type) {
+      var block = this.block();
+      for (var i = 0, len = block.nodes.length; i < len; ++i) {
+        tag.block.push(block.nodes[i]);
       }
     }
 
diff --git a/lib/runtime.js b/lib/runtime.js
index 237c33b..8fd22fd 100644
--- a/lib/runtime.js
+++ b/lib/runtime.js
@@ -177,7 +177,7 @@ exports.rethrow = function rethrow(err, filename, lineno, str){
     throw err;
   }
   try {
-    str =  str || require('fs').readFileSync(filename, 'utf8')
+    str = str || require('fs').readFileSync(filename, 'utf8')
   } catch (ex) {
     rethrow(err, null, lineno)
   }
diff --git a/lib/utils.js b/lib/utils.js
index 7c6782d..813a55a 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -14,3 +14,34 @@ exports.merge = function(a, b) {
   return a;
 };
 
+exports.walkAST = function walkAST(ast, before, after) {
+  before && before(ast);
+  switch (ast.type) {
+    case 'Block':
+      ast.nodes.forEach(function (node) {
+        walkAST(node, before, after);
+      });
+      break;
+    case 'Case':
+    case 'Each':
+    case 'Mixin':
+    case 'Tag':
+    case 'When':
+    case 'Code':
+      ast.block && walkAST(ast.block, before, after);
+      break;
+    case 'Attrs':
+    case 'BlockComment':
+    case 'Comment':
+    case 'Doctype':
+    case 'Filter':
+    case 'Literal':
+    case 'MixinBlock':
+    case 'Text':
+      break;
+    default:
+      throw new Error('Unexpected node type ' + ast.type);
+      break;
+  }
+  after && after(ast);
+};
diff --git a/package.json b/package.json
index 64a3798..16511af 100644
--- a/package.json
+++ b/package.json
@@ -1,18 +1,25 @@
 {
   "name": "jade",
   "description": "Jade template engine",
-  "version": "1.3.1",
+  "version": "1.5.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>"
+  ],
   "license": "MIT",
-  "repository": "git://github.com/visionmedia/jade",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/visionmedia/jade"
+  },
   "main": "./index.js",
   "bin": {
     "jade": "./bin/jade.js"
   },
-  "man": "./jade.1",
   "dependencies": {
     "commander": "2.1.0",
-    "mkdirp": "~0.3.5",
+    "mkdirp": "~0.5.0",
     "transformers": "2.1.0",
     "character-parser": "1.2.0",
     "monocle": "1.1.51",
@@ -29,7 +36,24 @@
     "less": "*",
     "uglify-js": "*",
     "browserify": "*",
-    "linify": "*"
+    "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",
+    "code-mirror": "~3.22.0",
+    "handle": "~1.0.0",
+    "jade-highlighter": "~1.0.5",
+    "marked": "~0.3.2",
+    "stop": "^3.0.0-rc1",
+    "opener": "^1.3.0",
+    "github-basic": "^3.0.0",
+    "pull-request": "^3.0.0",
+    "lsr": "^1.0.0",
+    "rimraf": "^2.2.8"
   },
   "component": {
     "scripts": {
@@ -48,4 +72,4 @@
   "browser": {
     "./lib/filters.js": "./lib/filters-client.js"
   }
-}
\ No newline at end of file
+}
diff --git a/release.js b/release.js
new file mode 100644
index 0000000..d7e52e3
--- /dev/null
+++ b/release.js
@@ -0,0 +1,88 @@
+'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;
+
+
+// todo: check that the version is a new un-released version
+// todo: check the user has commit access to the github repo
+// todo: check the user is an owner in npm
+// todo: check History.md has been updated
+
+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);
+  });
+  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();
+});
diff --git a/test/cases/auxiliary/blocks-in-blocks-layout.jade b/test/cases/auxiliary/blocks-in-blocks-layout.jade
new file mode 100644
index 0000000..17ca8a0
--- /dev/null
+++ b/test/cases/auxiliary/blocks-in-blocks-layout.jade
@@ -0,0 +1,8 @@
+doctype html
+html
+  head
+    title Default title
+  body
+    block body
+      .container
+        block content
diff --git a/test/cases/blocks-in-blocks.html b/test/cases/blocks-in-blocks.html
new file mode 100644
index 0000000..d7955ab
--- /dev/null
+++ b/test/cases/blocks-in-blocks.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Default title</title>
+  </head>
+  <body>
+    <h1>Page 2</h1>
+  </body>
+</html>
diff --git a/test/cases/blocks-in-blocks.jade b/test/cases/blocks-in-blocks.jade
new file mode 100644
index 0000000..3e9a4ad
--- /dev/null
+++ b/test/cases/blocks-in-blocks.jade
@@ -0,0 +1,4 @@
+extends ./auxiliary/blocks-in-blocks-layout.jade
+
+block body
+  h1 Page 2
diff --git a/test/cases/blocks-in-if.html b/test/cases/blocks-in-if.html
new file mode 100644
index 0000000..c3b9107
--- /dev/null
+++ b/test/cases/blocks-in-if.html
@@ -0,0 +1 @@
+<p>ajax contents</p>
diff --git a/test/cases/blocks-in-if.jade b/test/cases/blocks-in-if.jade
new file mode 100644
index 0000000..80bc4b6
--- /dev/null
+++ b/test/cases/blocks-in-if.jade
@@ -0,0 +1,19 @@
+//- see https://github.com/visionmedia/jade/issues/1589
+
+-var ajax = true
+
+-if( ajax )
+    //- return only contents if ajax requests
+    block contents
+        p ajax contents
+
+-else
+    //- return all html
+    doctype html
+    html
+        head
+            meta( charset='utf8' )
+            title sample
+            body
+                block contents
+                    p all contetns
diff --git a/test/cases/comments.html b/test/cases/comments.html
index fcf801f..522ecbe 100644
--- a/test/cases/comments.html
+++ b/test/cases/comments.html
@@ -9,7 +9,7 @@
 <!--
 ul
   li foo
-  
+
 -->
 <!-- block
 // inline follow
@@ -20,7 +20,7 @@ li three
 // inline followed by tags
 ul
   li four
-  
+
 -->
 <!--if IE lt 9
 // inline
diff --git a/test/cases/include-filter-coffee.coffee b/test/cases/include-filter-coffee.coffee
new file mode 100644
index 0000000..9723cd7
--- /dev/null
+++ b/test/cases/include-filter-coffee.coffee
@@ -0,0 +1,2 @@
+math =
+  square: (value) -> value * value
diff --git a/test/cases/include-filter.html b/test/cases/include-filter.html
index b460b97..cc74456 100644
--- a/test/cases/include-filter.html
+++ b/test/cases/include-filter.html
@@ -2,5 +2,19 @@
   <body><p>Just <em>some</em> markdown <strong>tests</strong>.</p>
 
 <p>With new line.</p>
+    <script>(function(){var n;n={square:function(n){return n*n}}}).call(this);
+    </script>
+    <script>(function() {
+  var math;
+
+  math = {
+    square: function(value) {
+      return value * value;
+    }
+  };
+
+}).call(this);
+
+    </script>
   </body>
 </html>
diff --git a/test/cases/include-filter.jade b/test/cases/include-filter.jade
index 443f900..7e4edd7 100644
--- a/test/cases/include-filter.jade
+++ b/test/cases/include-filter.jade
@@ -1,3 +1,7 @@
 html
   body
     include:md some.md
+    script
+      include:coffee(minify=true) include-filter-coffee.coffee
+    script
+      include:coffee(minify=false) include-filter-coffee.coffee
diff --git a/test/cases/script.whitespace.html b/test/cases/script.whitespace.html
index a8f49e5..45b7ced 100644
--- a/test/cases/script.whitespace.html
+++ b/test/cases/script.whitespace.html
@@ -1,7 +1,7 @@
 <script>
   if (foo) {
-  
-    bar();
     
+    bar();
+  
   }
 </script>
\ No newline at end of file
diff --git a/test/command-line.js b/test/command-line.js
new file mode 100644
index 0000000..29192ec
--- /dev/null
+++ b/test/command-line.js
@@ -0,0 +1,43 @@
+'use strict';
+
+var fs = require('fs');
+var path = require('path');
+var assert = require('assert');
+var exec = require('child_process').exec;
+
+function run(args, callback) {
+  exec('node ' + JSON.stringify(path.resolve(__dirname + '/../bin/jade.js')) + ' ' + args, {
+    cwd: __dirname + '/temp'
+  }, callback);
+}
+
+try {
+  fs.mkdirSync(__dirname + '/temp');
+} catch (ex) {
+  if (ex.code !== 'EEXIST') {
+    throw ex;
+  }
+}
+
+describe('command line', function () {
+  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");');
+    run('--no-debug --client --name myTemplate input.jade', function (err) {
+      if (err) return done(err);
+      var template = Function('', fs.readFileSync(__dirname + '/temp/input.js', 'utf8') + ';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");');
+    run('--no-debug --client --name-after-file input-file.jade', function (err, stdout, stderr) {
+      if (err) return done(err);
+      var template = Function('', fs.readFileSync(__dirname + '/temp/input-file.js', 'utf8') + ';return inputFileTemplate;')();
+      assert(template() === '<div class="foo">bar</div>');
+      return done();
+    });
+  });
+});
diff --git a/test/dependencies/dependency1.jade b/test/dependencies/dependency1.jade
new file mode 100644
index 0000000..5c24ab5
--- /dev/null
+++ b/test/dependencies/dependency1.jade
@@ -0,0 +1 @@
+strong dependency1
diff --git a/test/dependencies/dependency2.jade b/test/dependencies/dependency2.jade
new file mode 100644
index 0000000..28931c3
--- /dev/null
+++ b/test/dependencies/dependency2.jade
@@ -0,0 +1 @@
+include dependency3
diff --git a/test/dependencies/dependency3.jade b/test/dependencies/dependency3.jade
new file mode 100644
index 0000000..8cb467c
--- /dev/null
+++ b/test/dependencies/dependency3.jade
@@ -0,0 +1 @@
+strong dependency3
diff --git a/test/dependencies/extends1.jade b/test/dependencies/extends1.jade
new file mode 100644
index 0000000..cbe4ab9
--- /dev/null
+++ b/test/dependencies/extends1.jade
@@ -0,0 +1 @@
+extends dependency1
diff --git a/test/dependencies/extends2.jade b/test/dependencies/extends2.jade
new file mode 100644
index 0000000..49eb98f
--- /dev/null
+++ b/test/dependencies/extends2.jade
@@ -0,0 +1 @@
+extends dependency2
diff --git a/test/dependencies/include1.jade b/test/dependencies/include1.jade
new file mode 100644
index 0000000..2ff1b4a
--- /dev/null
+++ b/test/dependencies/include1.jade
@@ -0,0 +1 @@
+include dependency1
diff --git a/test/dependencies/include2.jade b/test/dependencies/include2.jade
new file mode 100644
index 0000000..8bf3ca3
--- /dev/null
+++ b/test/dependencies/include2.jade
@@ -0,0 +1 @@
+include dependency2
diff --git a/test/deprecated.js b/test/deprecated.js
index 95841cc..6d7dbea 100644
--- a/test/deprecated.js
+++ b/test/deprecated.js
@@ -1,22 +1,30 @@
 'use strict';
 
 var assert = require('assert');
+var util = require('util');
 var jade = require('../');
 
-describe('deprecated functions', function () {
-  function deprecate(name, fn, regex) {
-    it(name, function () {
-      var consoleError = console.error;
-      var consoleWarn = console.warn;
-      var log = '';
-      console.warn = function (msg) { log += msg; };
-      console.error = function (msg) { log += msg; };
+function deprecate(name, fn, regex) {
+  it(name, function () {
+    var consoleError = console.error;
+    var consoleWarn = console.warn;
+    var log = '';
+    console.warn = function (msg) { log += msg; };
+    console.error = function (msg) { log += msg; };
+    try {
       fn();
-      assert((regex || new RegExp(name + ' is deprecated and will be removed in v2.0.0')).test(log));
+      regex = regex || new RegExp(name + ' is deprecated and will be removed in v2.0.0');
+      assert(regex.test(log), 'Expected ' + JSON.stringify(log) + ' to match ' + util.inspect(regex));
+    } catch (ex) {
       console.error = consoleError;
       console.warn = consoleWarn;
-    });
-  }
+      throw ex;
+    }
+    console.error = consoleError;
+    console.warn = consoleWarn;
+  });
+}
+describe('deprecated functions', function () {
   deprecate('tag.clone', function () {
     var tag = new jade.nodes.Tag();
     tag.clone();
@@ -52,4 +60,10 @@ describe('deprecated functions', function () {
     var fn = Function('jade', fn.toString() + '\nreturn template;')(jade.runtime);
     assert(fn() === '<div></div>');
   }, /The `client` option is deprecated/);
+});
+
+describe('warnings that will become errors', function () {
+  deprecate('block that is never actually used', function () {
+    jade.renderFile(__dirname + '/fixtures/invalid-block-in-extends.jade');
+  }, /Warning\: Unexpected block .* on line.*of.*This warning will be an error in v2\.0\.0/);
 });
\ No newline at end of file
diff --git a/test/error.reporting.js b/test/error.reporting.js
index 6fbe685..2048e1b 100644
--- a/test/error.reporting.js
+++ b/test/error.reporting.js
@@ -85,6 +85,20 @@ describe('error reporting', function () {
         assert(/foo\(/.test(err.message))
       });
     });
+    describe('Unexpected character', function () {
+      it('includes details of where the error was thrown', function () {
+        var err = getError('ul?', {});
+        assert(err.message.indexOf('unexpected text ?') !== -1);
+      });
+    });
+    describe('Include filtered', function () {
+      it('includes details of where the error was thrown', function () {
+        var err = getError('include:js()!', {});
+        assert(err.message.indexOf('expected space after include:filter but got "!"') !== -1);
+        var err = getError('include:js ', {});
+        assert(err.message.indexOf('missing path for include:filter') !== -1);
+      });
+    });
   });
   describe('runtime errors', function () {
     describe('with no filename and `compileDebug` left undefined', function () {
diff --git a/test/fixtures/invalid-block-in-extends.jade b/test/fixtures/invalid-block-in-extends.jade
new file mode 100644
index 0000000..dd55bca
--- /dev/null
+++ b/test/fixtures/invalid-block-in-extends.jade
@@ -0,0 +1,7 @@
+extends ./layout.jade
+
+block title
+  title My Article
+
+block contents
+  // oops, that's not a block
\ No newline at end of file
diff --git a/test/fixtures/layout.jade b/test/fixtures/layout.jade
new file mode 100644
index 0000000..87518e5
--- /dev/null
+++ b/test/fixtures/layout.jade
@@ -0,0 +1,6 @@
+doctype html
+html
+  head
+    block title
+  body
+    block body
\ No newline at end of file
diff --git a/test/jade.test.js b/test/jade.test.js
index 23311b9..218b26b 100644
--- a/test/jade.test.js
+++ b/test/jade.test.js
@@ -1,8 +1,9 @@
 'use strict';
 
-var jade = require('../');
 var assert = require('assert');
 var fs = require('fs');
+var path = require('path');
+var jade = require('../');
 
 var perfTest = fs.readFileSync(__dirname + '/fixtures/perf.jade', 'utf8')
 
@@ -36,25 +37,11 @@ describe('jade', function(){
     });
 
     it('should support line endings', function(){
-      var str = [
-          'p',
-          'div',
-          'img'
-      ].join('\r\n');
-
-      var html = [
-          '<p></p>',
-          '<div></div>',
-          '<img/>'
-      ].join('');
-
-      assert.equal(html, jade.render(str));
-
-      var str = [
+      var src = [
           'p',
           'div',
           'img'
-      ].join('\r');
+      ];
 
       var html = [
           '<p></p>',
@@ -62,21 +49,19 @@ describe('jade', function(){
           '<img/>'
       ].join('');
 
-      assert.equal(html, jade.render(str));
-
-      var str = [
-          'p',
-          'div',
-          'img'
-      ].join('\r\n');
+      assert.equal(html, jade.render(src.join('\n')));
+      assert.equal(html, jade.render(src.join('\r')));
+      assert.equal(html, jade.render(src.join('\r\n')));
 
-      var html = [
+      html = [
           '<p></p>',
           '<div></div>',
           '<img>'
       ].join('');
 
-      assert.equal(html, jade.render(str, { doctype:'html' }));
+      assert.equal(html, jade.render(src.join('\n'), { doctype:'html' }));
+      assert.equal(html, jade.render(src.join('\r'), { doctype:'html' }));
+      assert.equal(html, jade.render(src.join('\r\n'), { doctype:'html' }));
     });
 
     it('should support single quotes', function(){
@@ -933,7 +918,11 @@ describe('jade', function(){
 
     it('should be reasonably fast', function(){
       jade.compile(perfTest, {})
-    })
+    });
+    it('allows trailing space (see #1586)', function () {
+      var res = jade.render('ul \n  li An Item');
+      assert.equal('<ul> <li>An Item</li></ul>', res);
+    });
   });
 
   describe('.renderFile()', function () {
@@ -974,6 +963,13 @@ describe('jade', function(){
       var actual = fn({name: 'foo'}).replace(/\s/g, '');
       assert(actual === expected);
     });
+    it('accepts the `name` option to rename the resulting function', function () {
+      var src = jade.compileFileClient(__dirname + '/cases/basic.jade', {name: 'myTemplateName'});
+      var expected = fs.readFileSync(__dirname + '/cases/basic.html', 'utf8').replace(/\s/g, '');
+      var fn = Function('jade', src + '\nreturn myTemplateName;')(jade.runtime);
+      var actual = fn({name: 'foo'}).replace(/\s/g, '');
+      assert(actual === expected);
+    });
   });
 
   describe('.runtime', function () {
@@ -993,4 +989,70 @@ describe('jade', function(){
       });
     });
   });
+
+  describe('filter indentation', function () {
+    it('is maintained', function () {
+      jade.filters.indents = function(str){
+        return str.split(/\n/).map(function (line) { return line.match(/^ */)[0].length; }).join(",");
+      };
+
+      var indents = [
+        ':indents',
+        '  x',
+        '   x',
+        '    x',
+        '     x',
+        '  x',
+        '      x',
+        '      x',
+        '     x',
+        '     x',
+        '      x',
+        '    x',
+        '  x',
+        '    x',
+        '  x',
+        '   x'
+      ].join('\n');
+
+      assert.equal(jade.render(indents), '0,1,2,3,0,4,4,3,3,4,2,0,2,0,1');
+    });
+  });
+
+  describe('.compile().dependencies', function() {
+    it('should list the filename of the template referenced by extends', function(){
+      var filename = __dirname + '/dependencies/extends1.jade';
+      var str = fs.readFileSync(filename, 'utf8');
+      var info = jade.compile(str, {filename: filename});
+      assert.deepEqual([
+        path.resolve(__dirname + '/dependencies/dependency1.jade')
+      ], info.dependencies);
+    });
+    it('should list the filename of the template referenced by an include', function() {
+      var filename = __dirname + '/dependencies/include1.jade';
+      var str = fs.readFileSync(filename, 'utf8');
+      var info = jade.compile(str, {filename: filename});
+      assert.deepEqual([
+        path.resolve(__dirname + '/dependencies/dependency1.jade')
+      ], info.dependencies);
+    });
+    it('should list the dependencies of extends dependencies', function() {
+      var filename = __dirname + '/dependencies/extends2.jade';
+      var str = fs.readFileSync(filename, 'utf8');
+      var info = jade.compile(str, {filename: filename});
+      assert.deepEqual([
+        path.resolve(__dirname + '/dependencies/dependency2.jade'),
+        path.resolve(__dirname + '/dependencies/dependency3.jade')
+      ], info.dependencies);
+    });
+    it('should list the dependencies of include dependencies', function() {
+      var filename = __dirname + '/dependencies/include2.jade';
+      var str = fs.readFileSync(filename, 'utf8');
+      var info = jade.compile(str, {filename: filename});
+      assert.deepEqual([
+        path.resolve(__dirname + '/dependencies/dependency2.jade'),
+        path.resolve(__dirname + '/dependencies/dependency3.jade')
+      ],info.dependencies);
+    });
+  });
 });

-- 
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