[Pkg-javascript-commits] [node-jade] 01/02: Imported Upstream version 1.3.1+dfsg
Leo Iannacone
l3on-guest at moszumanska.debian.org
Wed May 7 17:47:15 UTC 2014
This is an automated email from the git hooks/post-receive script.
l3on-guest pushed a commit to branch master
in repository node-jade.
commit 1dedc3276b086e149e60eeb75c5975d09606ecdc
Author: Leo Iannacone <l3on at ubuntu.com>
Date: Wed May 7 18:57:02 2014 +0200
Imported Upstream version 1.3.1+dfsg
---
.gitmodules | 21 +
.npmignore | 14 +
.travis.yml | 3 +
History.md | 863 +++++++++++++
LICENSE | 22 +
README.md | 147 +++
Readme_zh-cn.md | 1285 ++++++++++++++++++++
bin/jade.js | 186 +++
component.json | 16 +
examples/README.md | 5 +
examples/attributes.jade | 8 +
examples/attributes.js | 11 +
examples/code.jade | 12 +
examples/code.js | 16 +
examples/csrf.jade | 5 +
examples/csrf.js | 42 +
examples/dynamicscript.jade | 5 +
examples/dynamicscript.js | 20 +
examples/each.jade | 3 +
examples/each.js | 16 +
examples/extend-layout.jade | 10 +
examples/extend.jade | 11 +
examples/extend.js | 18 +
examples/form.jade | 29 +
examples/form.js | 18 +
examples/includes.jade | 7 +
examples/includes.js | 11 +
examples/includes/foot.jade | 2 +
examples/includes/head.jade | 6 +
examples/includes/scripts.jade | 2 +
examples/includes/style.css | 5 +
examples/layout-debug.js | 11 +
examples/layout.jade | 14 +
examples/layout.js | 11 +
examples/mixins.jade | 16 +
examples/mixins.js | 16 +
examples/mixins/dialog.jade | 15 +
examples/mixins/profile.jade | 10 +
examples/pet.jade | 3 +
examples/rss.jade | 14 +
examples/rss.js | 17 +
examples/text.jade | 36 +
examples/text.js | 11 +
examples/whitespace.jade | 11 +
examples/whitespace.js | 11 +
index.js | 2 +
jade-language.md | 1023 ++++++++++++++++
jade.md | 510 ++++++++
lib/compiler.js | 703 +++++++++++
lib/doctypes.js | 12 +
lib/filters-client.js | 11 +
lib/filters.js | 15 +
lib/index.js | 1 +
lib/inline-tags.js | 23 +
lib/jade.js | 335 +++++
lib/lexer.js | 877 +++++++++++++
lib/nodes/attrs.js | 83 ++
lib/nodes/block-comment.js | 24 +
lib/nodes/block.js | 118 ++
lib/nodes/case.js | 33 +
lib/nodes/code.js | 26 +
lib/nodes/comment.js | 23 +
lib/nodes/doctype.js | 20 +
lib/nodes/each.js | 26 +
lib/nodes/filter.js | 24 +
lib/nodes/index.js | 16 +
lib/nodes/literal.js | 20 +
lib/nodes/mixin-block.js | 18 +
lib/nodes/mixin.js | 26 +
lib/nodes/node.js | 18 +
lib/nodes/tag.js | 89 ++
lib/nodes/text.js | 26 +
lib/parser.js | 809 ++++++++++++
lib/runtime.js | 203 ++++
lib/self-closing.js | 22 +
lib/utils.js | 16 +
package.json | 51 +
support/benchmark.js | 121 ++
test/README.md | 15 +
test/anti-cases/attrs.unescaped.jade | 3 +
test/anti-cases/case-when.jade | 4 +
test/anti-cases/case-without-with.jade | 2 +
test/anti-cases/doctype-5.jade | 2 +
test/anti-cases/else-condition.jade | 4 +
test/anti-cases/else-without-if.jade | 2 +
test/anti-cases/inlining-a-mixin-after-a-tag.jade | 1 +
test/anti-cases/key-char-ending-badly.jade | 1 +
test/anti-cases/key-ending-badly.jade | 1 +
test/anti-cases/mixins-blocks-with-bodies.jade | 3 +
.../multiple-non-nested-tags-on-a-line.jade | 1 +
test/anti-cases/non-existant-filter.jade | 2 +
test/anti-cases/non-mixin-block.jade | 2 +
test/anti-cases/open-brace-in-attributes.jade | 1 +
test/anti-cases/readme.md | 1 +
test/anti-cases/self-closing-tag-with-body.jade | 1 +
test/anti-cases/self-closing.jade | 2 +
test/anti-cases/tabs-and-spaces.jade | 3 +
test/browser/index.html | 10 +
test/browser/index.jade | 20 +
test/cases/attrs-data.html | 4 +
test/cases/attrs-data.jade | 5 +
test/cases/attrs.html | 13 +
test/cases/attrs.interpolation.html | 2 +
test/cases/attrs.interpolation.jade | 3 +
test/cases/attrs.jade | 26 +
test/cases/attrs.js.html | 5 +
test/cases/attrs.js.jade | 17 +
test/cases/attrs.unescaped.html | 5 +
test/cases/attrs.unescaped.jade | 3 +
test/cases/auxiliary/dialog.jade | 6 +
test/cases/auxiliary/empty-block.jade | 2 +
test/cases/auxiliary/escapes.html | 3 +
test/cases/auxiliary/extends-empty-block-1.jade | 5 +
test/cases/auxiliary/extends-empty-block-2.jade | 5 +
test/cases/auxiliary/extends-from-root.jade | 4 +
test/cases/auxiliary/includable.js | 7 +
test/cases/auxiliary/include-from-root.jade | 1 +
.../auxiliary/inheritance.extend.mixin.block.jade | 11 +
...ritance.extend.recursive-grand-grandparent.jade | 2 +
.../inheritance.extend.recursive-grandparent.jade | 6 +
.../inheritance.extend.recursive-parent.jade | 5 +
test/cases/auxiliary/layout.include.jade | 7 +
test/cases/auxiliary/layout.jade | 6 +
test/cases/auxiliary/mixins.jade | 3 +
test/cases/auxiliary/pet.jade | 3 +
test/cases/auxiliary/smile.html | 1 +
test/cases/auxiliary/window.jade | 4 +
test/cases/auxiliary/yield-nested.jade | 10 +
test/cases/basic.html | 5 +
test/cases/basic.jade | 3 +
test/cases/blanks.html | 5 +
test/cases/blanks.jade | 8 +
test/cases/block-expansion.html | 5 +
test/cases/block-expansion.jade | 5 +
test/cases/block-expansion.shorthands.html | 7 +
test/cases/block-expansion.shorthands.jade | 2 +
test/cases/blockquote.html | 4 +
test/cases/blockquote.jade | 4 +
test/cases/case-blocks.html | 5 +
test/cases/case-blocks.jade | 10 +
test/cases/case.html | 6 +
test/cases/case.jade | 14 +
test/cases/classes-empty.html | 1 +
test/cases/classes-empty.jade | 3 +
test/cases/classes.html | 1 +
test/cases/classes.jade | 9 +
test/cases/code.conditionals.html | 10 +
test/cases/code.conditionals.jade | 36 +
test/cases/code.escape.html | 2 +
test/cases/code.escape.jade | 2 +
test/cases/code.html | 10 +
test/cases/code.iteration.html | 36 +
test/cases/code.iteration.jade | 35 +
test/cases/code.jade | 10 +
test/cases/comments.html | 32 +
test/cases/comments.jade | 29 +
test/cases/comments.source.html | 0
test/cases/comments.source.jade | 9 +
test/cases/custom-filter.html | 1 +
test/cases/custom-filter.jade | 2 +
test/cases/doctype.custom.html | 1 +
test/cases/doctype.custom.jade | 1 +
test/cases/doctype.default.html | 6 +
test/cases/doctype.default.jade | 4 +
test/cases/doctype.keyword.html | 1 +
test/cases/doctype.keyword.jade | 1 +
test/cases/each.else.html | 20 +
test/cases/each.else.jade | 49 +
test/cases/escape-chars.html | 1 +
test/cases/escape-chars.jade | 2 +
test/cases/escape-test.html | 10 +
test/cases/escape-test.jade | 8 +
test/cases/escaping-class-attribute.html | 6 +
test/cases/escaping-class-attribute.jade | 6 +
test/cases/filters-empty.html | 4 +
test/cases/filters-empty.jade | 6 +
test/cases/filters.cdata.html | 4 +
test/cases/filters.cdata.jade | 8 +
test/cases/filters.coffeescript.html | 9 +
test/cases/filters.coffeescript.jade | 6 +
test/cases/filters.less.html | 7 +
test/cases/filters.less.jade | 8 +
test/cases/filters.markdown.html | 5 +
test/cases/filters.markdown.jade | 5 +
test/cases/filters.stylus.html | 8 +
test/cases/filters.stylus.jade | 7 +
test/cases/html.html | 6 +
test/cases/html.jade | 8 +
test/cases/html5.html | 4 +
test/cases/html5.jade | 4 +
test/cases/include-extends-from-root.html | 8 +
test/cases/include-extends-from-root.jade | 1 +
test/cases/include-extends-of-common-template.html | 2 +
test/cases/include-extends-of-common-template.jade | 2 +
test/cases/include-filter-stylus.html | 4 +
test/cases/include-filter-stylus.jade | 2 +
test/cases/include-filter.html | 6 +
test/cases/include-filter.jade | 3 +
test/cases/include-only-text-body.html | 1 +
test/cases/include-only-text-body.jade | 3 +
test/cases/include-only-text.html | 5 +
test/cases/include-only-text.jade | 5 +
test/cases/include-with-text-head.html | 3 +
test/cases/include-with-text-head.jade | 3 +
test/cases/include-with-text.html | 7 +
test/cases/include-with-text.jade | 4 +
test/cases/include.script.html | 6 +
test/cases/include.script.jade | 2 +
test/cases/include.yield.nested.html | 17 +
test/cases/include.yield.nested.jade | 4 +
test/cases/includes-with-ext-js.html | 2 +
test/cases/includes-with-ext-js.jade | 3 +
test/cases/includes.html | 13 +
test/cases/includes.jade | 10 +
test/cases/inheritance.alert-dialog.html | 6 +
test/cases/inheritance.alert-dialog.jade | 6 +
test/cases/inheritance.defaults.html | 7 +
test/cases/inheritance.defaults.jade | 6 +
test/cases/inheritance.extend.html | 10 +
test/cases/inheritance.extend.include.html | 14 +
test/cases/inheritance.extend.include.jade | 13 +
test/cases/inheritance.extend.jade | 9 +
test/cases/inheritance.extend.mixins.block.html | 10 +
test/cases/inheritance.extend.mixins.block.jade | 4 +
test/cases/inheritance.extend.mixins.html | 9 +
test/cases/inheritance.extend.mixins.jade | 11 +
test/cases/inheritance.extend.recursive.html | 4 +
test/cases/inheritance.extend.recursive.jade | 4 +
test/cases/inheritance.extend.whitespace.html | 10 +
test/cases/inheritance.extend.whitespace.jade | 13 +
test/cases/inheritance.html | 10 +
test/cases/inheritance.jade | 9 +
test/cases/inline-tag.html | 13 +
test/cases/inline-tag.jade | 15 +
test/cases/interpolation.escape.html | 6 +
test/cases/interpolation.escape.jade | 7 +
test/cases/javascript-new-lines.js | 1 +
test/cases/layout.append.html | 9 +
test/cases/layout.append.jade | 6 +
test/cases/layout.append.without-block.html | 9 +
test/cases/layout.append.without-block.jade | 6 +
test/cases/layout.multi.append.prepend.block.html | 8 +
test/cases/layout.multi.append.prepend.block.jade | 19 +
test/cases/layout.prepend.html | 9 +
test/cases/layout.prepend.jade | 6 +
test/cases/layout.prepend.without-block.html | 9 +
test/cases/layout.prepend.without-block.jade | 6 +
test/cases/mixin-hoist.html | 5 +
test/cases/mixin-hoist.jade | 7 +
test/cases/mixin-via-include.html | 1 +
test/cases/mixin-via-include.jade | 5 +
test/cases/mixin.attrs.html | 27 +
test/cases/mixin.attrs.jade | 41 +
test/cases/mixin.block-tag-behaviour.html | 22 +
test/cases/mixin.block-tag-behaviour.jade | 24 +
test/cases/mixin.blocks.html | 34 +
test/cases/mixin.blocks.jade | 44 +
test/cases/mixin.merge.html | 34 +
test/cases/mixin.merge.jade | 15 +
test/cases/mixins-unused.html | 1 +
test/cases/mixins-unused.jade | 3 +
test/cases/mixins.html | 17 +
test/cases/mixins.jade | 31 +
test/cases/namespaces.html | 2 +
test/cases/namespaces.jade | 2 +
test/cases/nesting.html | 11 +
test/cases/nesting.jade | 8 +
test/cases/pre.html | 7 +
test/cases/pre.jade | 10 +
test/cases/quotes.html | 2 +
test/cases/quotes.jade | 2 +
test/cases/regression.784.html | 1 +
test/cases/regression.784.jade | 2 +
test/cases/script.whitespace.html | 7 +
test/cases/script.whitespace.jade | 6 +
test/cases/scripts.html | 9 +
test/cases/scripts.jade | 8 +
test/cases/scripts.non-js.html | 11 +
test/cases/scripts.non-js.jade | 9 +
test/cases/single-period.html | 1 +
test/cases/single-period.jade | 1 +
test/cases/some-included.styl | 2 +
test/cases/some.md | 3 +
test/cases/some.styl | 1 +
test/cases/source.html | 6 +
test/cases/source.jade | 4 +
test/cases/styles.html | 9 +
test/cases/styles.jade | 6 +
test/cases/tag.interpolation.html | 9 +
test/cases/tag.interpolation.jade | 22 +
test/cases/tags.self-closing.html | 8 +
test/cases/tags.self-closing.jade | 12 +
test/cases/template.html | 11 +
test/cases/template.jade | 9 +
test/cases/text-block.html | 6 +
test/cases/text-block.jade | 6 +
test/cases/text.html | 22 +
test/cases/text.jade | 25 +
test/cases/utf8bom.html | 1 +
test/cases/utf8bom.jade | 1 +
test/cases/vars.html | 1 +
test/cases/vars.jade | 3 +
test/cases/while.html | 11 +
test/cases/while.jade | 5 +
test/cases/xml.html | 3 +
test/cases/xml.jade | 3 +
test/cases/yield-before-conditional-head.html | 3 +
test/cases/yield-before-conditional-head.jade | 5 +
test/cases/yield-before-conditional.html | 9 +
test/cases/yield-before-conditional.jade | 5 +
test/cases/yield-head.html | 4 +
test/cases/yield-head.jade | 4 +
test/cases/yield-title-head.html | 5 +
test/cases/yield-title-head.jade | 5 +
test/cases/yield-title.html | 9 +
test/cases/yield-title.jade | 4 +
test/cases/yield.html | 10 +
test/cases/yield.jade | 5 +
test/deprecated.js | 55 +
test/error.reporting.js | 185 +++
test/examples.js | 25 +
test/fixtures/append-without-block/app-layout.jade | 5 +
test/fixtures/append-without-block/layout.jade | 7 +
test/fixtures/append-without-block/page.jade | 6 +
test/fixtures/append/app-layout.jade | 5 +
test/fixtures/append/layout.jade | 7 +
test/fixtures/append/page.html | 9 +
test/fixtures/append/page.jade | 6 +
.../compile.with.include.locals.error.jade | 1 +
.../compile.with.include.syntax.error.jade | 1 +
.../fixtures/compile.with.layout.locals.error.jade | 1 +
.../fixtures/compile.with.layout.syntax.error.jade | 1 +
...pile.with.layout.with.include.locals.error.jade | 1 +
...pile.with.layout.with.include.syntax.error.jade | 1 +
.../fixtures/element-with-multiple-attributes.jade | 1 +
test/fixtures/include.locals.error.jade | 2 +
test/fixtures/include.syntax.error.jade | 2 +
test/fixtures/layout.locals.error.jade | 2 +
test/fixtures/layout.syntax.error.jade | 2 +
test/fixtures/layout.with.runtime.error.jade | 5 +
test/fixtures/mixin-include.jade | 5 +
test/fixtures/mixin.error.jade | 2 +
.../multi-append-prepend-block/redefine.jade | 5 +
test/fixtures/multi-append-prepend-block/root.jade | 5 +
test/fixtures/perf.jade | 32 +
.../fixtures/prepend-without-block/app-layout.jade | 5 +
test/fixtures/prepend-without-block/layout.jade | 7 +
test/fixtures/prepend-without-block/page.html | 9 +
test/fixtures/prepend-without-block/page.jade | 6 +
test/fixtures/prepend/app-layout.jade | 5 +
test/fixtures/prepend/layout.jade | 7 +
test/fixtures/prepend/page.html | 9 +
test/fixtures/prepend/page.jade | 6 +
test/fixtures/runtime.error.jade | 1 +
test/fixtures/runtime.layout.error.jade | 3 +
test/fixtures/runtime.with.mixin.error.jade | 3 +
test/fixtures/scripts.jade | 2 +
test/jade.test.js | 996 +++++++++++++++
test/mocha.opts | 1 +
test/run.js | 112 ++
test/unit.js | 40 +
361 files changed, 11778 insertions(+)
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..b5b4321
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,21 @@
+[submodule "support/expresso"]
+ path = support/expresso
+ url = git://github.com/visionmedia/expresso.git
+[submodule "support/sass"]
+ path = support/sass
+ url = git://github.com/visionmedia/sass.js.git
+[submodule "benchmarks/haml-js"]
+ path = benchmarks/haml-js
+ url = git://github.com/creationix/haml-js.git
+[submodule "benchmarks/ejs"]
+ path = benchmarks/ejs
+ url = git://github.com/visionmedia/ejs.git
+[submodule "benchmarks/haml"]
+ path = benchmarks/haml
+ url = git://github.com/visionmedia/haml.js.git
+[submodule "support/coffee-script"]
+ path = support/coffee-script
+ url = http://github.com/jashkenas/coffee-script.git
+[submodule "support/stylus"]
+ path = support/stylus
+ url = git://github.com/LearnBoost/stylus.git
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..5bb0b26
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,14 @@
+test
+support
+benchmarks
+examples
+lib-cov
+coverage
+.gitmodules
+.travis.yml
+History.md
+Makefile
+test/
+support/
+benchmarks/
+examples/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..6e5919d
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,3 @@
+language: node_js
+node_js:
+ - "0.10"
diff --git a/History.md b/History.md
new file mode 100644
index 0000000..d6f6164
--- /dev/null
+++ b/History.md
@@ -0,0 +1,863 @@
+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 message for inline tags with content ([@hiddentao](https://github.com/hiddentao))
+
+1.3.0 / 2014-03-02
+==================
+
+ * Fix a bug where sometimes mixins were removed by an optimisation even though they were being called ([@ForbesLindesay](http://www.forbeslindesay.co.uk/), reported by [@leider](https://github.com/leider))
+ * Updated with to support automatically detecting when a value is "global" and removed redundant `options.globals` option ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Improve warnings for tags with multiple attributes ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Deprecate (with a warning) `node.clone`, `block.replace`, `attrs.removeAttribute`, `attrs.getAttribute` - these are all internal APIs for the AST ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+
+1.2.0 / 2014-02-26
+==================
+
+ * Use variables instead of properties of jade, improving performance and reliability with nested templates ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Support compiling templates from stdin via a user typing ([@yorkie](https://github.com/yorkie))
+ * Lazily add mixins ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Fix case fall-through ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Earlier errors for `when` without `case` and `else` without `if` ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Allow `if`/`else` etc. to not have a block.
+ * Remove lib-cov legacy to make browserify work better ([@silver83](https://github.com/silver83))
+ * Add and improve test coverage using istanbul ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+
+1.1.5 / 2014-01-19
+==================
+
+ * Add filename to and fix line numbers for missing space before text warning (@ijin82)
+ * Fix filenames for some error reporting in extends/includes (@doublerebel)
+ * Fix a corner case where a mixin was called with `&attributes` but no other attributes and a block that was supposed to be fixed in 1.1.4 ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+
+1.1.4 / 2014-01-09
+==================
+
+ * Fix a corner case where a mixin was called with `&attributes` but no other attributes and a block ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+
+1.1.3 / 2014-01-09
+==================
+
+ * Fix failure of npm prepublish not running
+
+1.1.2 / 2014-01-09
+==================
+
+ * Fix same interaction of `&attributes` with `false` `null` or `undefined` but combined with dynamic attributes ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+
+1.1.1 / 2014-01-09
+==================
+
+ * Fix a bug when `&attributes` is combined with static attributes that evaluate to `false` or `null` or `undefined` ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+
+1.1.0 / 2014-01-07
+==================
+
+ * Fix class merging to work as documented ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Throw an error when the same attribute is duplicated multiple times ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Move more errors into the parser/lexer so they have more info about line numbers ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Support mixin blocks at the end of files ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+
+1.0.2 / 2013-12-31
+==================
+
+ * Fix a bug when `&attributes` is combined with dynamic attributes ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+
+1.0.1 / 2013-12-29
+==================
+
+ * Allow self closing tags to contian whitespace ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Allow tags to have a single white space after them ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Support text bodies of tags that begin with `//` rather than treating them as comments ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+
+1.0.0 / 2013-12-22
+==================
+
+ * No longer support node at 0.8 ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Fix error reporting in layouts & includes ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Allow a list of 'globals' to be passed as an array at compile time & don't automatically expose all globals ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Escape apostrophes in data attributes (@qualiabyte)
+ * Fix mixin/block interaction ([@ForbesLindesay](http://www.forbeslindesay.co.uk/) & [@paulyoung](https://github.com/paulyoung))
+ * Ignore trailing space after mixin declaration ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Make literal `.` work as expected ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Remove implicit text only for script/style ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Stop parsing comments and remove support for conditional comments ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Make filtering includes explicit ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Remove special assignment syntax ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Remove `!!!` shortcut for `doctype` ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Remove `5` shorcut for `html` doctype ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Remove `colons` option from the distant past ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Add a sepatate `compileClient` and `compileFileClient` to replace the `client` option ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Remove polyfills for supporting old browsers ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Allow interpolation for mixin names ([@jeromew](https://github.com/jeromew)
+ * Use `node.type` instead of `node.constructor.name` so it can be minified ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Allow hyphens in filter names ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Throw an error if a self closing tag has content ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Support inline tags ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Replace `attributes` magic attribute with `&attributes(attributes)` ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Remove automatic tag wrapping for filters, you can just put the tags in yourself now ([@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Remove whitespace from tags nested inside pre tags ([@markdalgleish](http://markdalgleish.com))
+
+0.35.0 / 2013-08-21
+===================
+
+ * Add support for space separated attributes (thanks to [@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Add earlier errors for invalid JavaScript expressions (thanks to [@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * Fix parsing files with UTF8 BOMs when they are includes or parent/layout templates (thanks to [@kiinoo](https://github.com/kiinoo))
+
+0.34.1 / 2013-07-26
+===================
+
+ * fix render file not working when called with callback (reported by [@xieren58](https://github.com/xieren58))
+
+0.34.0 / 2013-07-26
+===================
+
+ * callbacks only called once for async methods even if they throw (reported by [@davidcornu](https://github.com/davidcornu))
+ * HTML comments are pretty printed better (thanks to [@eddiemonge](https://github.com/eddiemonge))
+ * callbacks are optional and leaving them out results in synchronous operation (thanks to [@ForbesLindesay](http://www.forbeslindesay.co.uk/))
+ * empty filter nodes are now permitted (thanks to [@coderanger](https://github.com/coderanger))
+ * overhaul website and documentation (thanks to [@ForbesLindesay](http://www.forbeslindesay.co.uk/)), much more of this to come.
+
+0.33.0 / 2013-07-12
+===================
+
+ * Hugely more powerful error reporting (especially with `compileDebug` set explicitly to `true`)
+ * Add a warning for tags with multiple attributes
+ * be strict about requiring newlines after tags to fix some odd corner cases
+ * fix escaping of class to allow it to be unescaped (thanks to [@christiangenco](https://github.com/christiangenco))
+
+0.32.0 / 2013-06-28
+===================
+
+ * remove `jade.version` and fix `jade --version`
+ * add file name and line number to deprecation warnings
+ * use constantinople for better constant detection
+ * update `with` for a massive performance upgrade at compile time
+
+0.31.2 / 2013-06-07
+===================
+
+ * fix overzealous deprecation warnings
+
+0.31.1 / 2013-05-31
+===================
+
+ * fix line endings for executable command
+ * fix `locals` variable being undefined
+ * fix an obscure bug that could occur if multiple mixins interact badly (see [substack/lexical-scope#13](https://github.com/substack/lexical-scope/issues/13))
+
+0.31.0 / 2013-05-30
+==================
+
+ * deprecate implicit text-only `script` and `style` tags
+ * make `with` at compile time using `lexical-scope`
+ * add `options.parser` that behaves exactly like `options.compiler`
+ * add "component.json" for component (runtime) support
+ * removed `hasOwnProperty` check in each loops
+ * removed .min files from the repository (people can just generate these themselves)
+ * use browserify to compile client side libraries
+ * fix buggy block extending should now be fixed
+ * fix preserve case of custom doctypes
+ * fix regexps in attributes sometimes not being accepted
+ * fix allow `$` sign in each loop variable names
+ * fix mixins with buffered code on the same line
+ * fix separate class names by ` ` rather than `,` (was sometimes incorrect)
+
+0.30.0 / 2013-04-25
+==================
+
+ * add support for 'include' and 'extends' to use paths relative to basedir
+ * fix accidental calling of functions in iteration block. Closes #986
+ * fix: skip rethrow on client
+ * fix each/else prefixed with `-`
+ * fix multi-block prepend/append
+ * swap -o and -O, set -o to --out
+
+0.29.0 / 2013-04-16
+==================
+
+ * add "monocle" for watcher that actually works...
+ * fix interpolation in blocks of text
+ * fix attribute interpolation
+ * move filters to an external library
+ * fix JavaScript escaping corner cases
+
+0.28.2 / 2013-03-04
+==================
+
+ * wtf coffeescript is not a dep
+
+0.28.1 / 2013-01-10
+==================
+
+ * add passing of filename to include filters
+ * fix wrong new lines for include filters
+
+0.28.0 / 2013-01-08
+==================
+
+ * add .css and .js "filters". re #438
+ * add include filters. Closes #283
+ * fix "class:" within attribute escaping
+ * removing ast filters
+ * things I can't read:
+ * 反馈地址
+ * 样式
+ * 联系
+ * 初稿,翻译完
+ * 接受大鸟的建议
+ * 头晕,翻译一点点
+ * 到过滤器翻译完毕
+ * 翻译一部分
+ * 中文翻译单独放
+ * 特性部分
+ * 再翻
+ * 翻译一点点
+
+0.27.7 / 2012-11-05
+==================
+
+ * fix each/else clause for enumerated objects
+ * fix #764 (incorrect line number for error messages)
+ * fix double-escaping of interpolated js slashes. Closes #784
+
+0.27.6 / 2012-10-05
+==================
+
+ * Included templates can not override blocks of their parent. Closes #699
+
+0.27.5 / 2012-09-24
+==================
+
+ * fix attr interpolation escaping. Closes #771
+
+0.27.4 / 2012-09-18
+==================
+
+ * fix include yields. Closes #770
+
+0.27.3 / 2012-09-18
+==================
+
+ * fix escaping of interpolation. Closes #769
+ * loosen "mkdirp" version restriction [TooTallNate]
+
+0.27.2 / 2012-08-07
+==================
+
+ * Revert "fixing string interpolation escaping #731", problems reported
+
+0.27.1 / 2012-08-06
+==================
+
+ * fix attribute interpolation escaping #731
+ * fix string interpolation escaping #731
+
+0.27.0 / 2012-07-26
+==================
+
+ * added ability to pass in json file to `--obj`
+ * add preliminary `each` `else` support. Closes #716
+ * fix doctype bug overlooked in #712
+ * fix stripping of utf-8 BOMs
+
+0.26.3 / 2012-06-25
+==================
+
+ * Update version of commander that supports node v0.8.
+
+0.26.2 / 2012-06-22
+==================
+
+ * Added --options alias of --obj
+ * Added reserved word conflict prevention in Google's Closure Compiler
+ * Added tag interpolation. Closes #657
+ * Allow the compiled client to use it's own jade util functions [3rd-Eden]
+ * Fixed `attrs()` escape bug [caseywebdev]
+
+0.26.1 / 2012-05-27
+==================
+
+ * Changed default doctype to __html5__
+ * Performance: statically compile attrs when possible [chowey]
+ * Fixed some class attribute merging cases
+ * Fixed so `block` doesn't consume `blockquotes` tag [chowey]
+ * Fixed backslashes in text nodes [chowey]
+ * Fixed / in text. Closes #638
+
+0.26.0 / 2012-05-04
+==================
+
+ * Added package.json __component__ support
+ * Added explicit self-closing tag support. Closes #605
+ * Added `block` statement
+ * Added mixin tag-like behaviour [chowey]
+ * Fixed mixins with extends [chowey]
+
+0.25.0 / 2012-04-18
+==================
+
+ * Added preliminary mixin block support. Closes #310
+ * Fixed whitespace handling in various situations [chowey]
+ * Fixed indentation in various situations [chowey]
+
+0.24.0 / 2012-04-12
+==================
+
+ * Fixed unescaped attribute compilation
+ * Fixed pretty-printing of text-only tags (__Warning__: this may affect rendering) [chowey]
+
+0.23.0 / 2012-04-11
+==================
+
+ * Added data-attr json stringification support. Closes #572
+ * Added unescaped attr support. Closes #198
+ * Fixed #1070, reverted mixin function statements
+ * Fixed jade.1 typo
+
+0.22.1 / 2012-04-04
+==================
+
+ * Fixed source tags. now self-closing. Closes #308
+ * Fixed: escape backslashes in coffeescript filter
+
+0.22.0 / 2012-03-22
+==================
+
+ * Added jade manpage (`man jade` after installation for docs)
+ * Added `-D, --no-debug` to jade(1)
+ * Added `-p, --pretty` to jade(1)
+ * Added `-c, --client` option to jade(1)
+ * Fixed `-o { client: true }` with stdin
+ * Fixed: skip blank lines in lexer (unless within pipeless text). Closes #399
+
+0.21.0 / 2012-03-10
+==================
+
+ * Added new input/output test suite using Mocha's string diffing
+ * Added alias `extend` -> `extends`. Closes #527 [guillermo]
+ * Fixed include escapes. Closes #513
+ * Fixed block-expansion with .foo and #foo short-hands. Closes #498
+
+0.20.3 / 2012-02-16
+==================
+
+ * Changed: pass `.filename` to filters only
+
+0.20.2 / 2012-02-16
+==================
+
+ * Fixed `:stylus` import capabilities, pass .filename
+
+0.20.1 / 2012-02-02
+==================
+
+ * Fixed Block#includeBlock() with textOnly blocks
+
+0.20.0 / 2011-12-28
+==================
+
+ * Added a browser example
+ * Added `yield` for block `include`s
+ * Changed: replaced internal `__` var with `__jade` [chrisleishman]
+ * Fixed two globals. Closes #433
+
+0.19.0 / 2011-12-02
+==================
+
+ * Added block `append` / `prepend` support. Closes #355
+ * Added link in readme to jade-mode for Emacs
+ * Added link to python implementation
+
+0.18.0 / 2011-11-21
+==================
+
+ * Changed: only ['script', 'style'] are text-only. Closes #398'
+
+0.17.0 / 2011-11-10
+==================
+
+ * jade.renderFile() is back! (for express 3.x)
+ * Fixed `Object.keys()` failover bug
+
+0.16.4 / 2011-10-24
+==================
+
+ * Fixed a test due to reserved keyword
+ * Fixed: commander 0.1.x dep for 0.5.x
+
+0.16.3 / 2011-10-24
+==================
+
+ * Added: allow leading space for conditional comments
+ * Added quick implementation of a switch statement
+ * Fixed parens in mixin args. Closes #380
+ * Fixed: include files with a .jade extension as jade files
+
+0.16.2 / 2011-09-30
+==================
+
+ * Fixed include regression. Closes #354
+
+0.16.1 / 2011-09-29
+==================
+
+ * Fixed unexpected `else` bug when compileDebug: false
+ * Fixed attr state issue for balancing pairs. Closes #353
+
+0.16.0 / 2011-09-26
+==================
+
+ * Added `include` block support. Closes #303
+ * Added template inheritance via `block` and `extends`. Closes #242
+ * Added 'type="text/css"' to the style tags generated by filters.
+ * Added 'uglifyjs' as an explicit devDependency.
+ * Added -p, --path <path> flag to jade(1)
+ * Added support for any arbitrary doctype
+ * Added `jade.render(str[,options], fn)` back
+ * Added first-class `while` support
+ * Added first-class assignment support
+ * Fixed runtime.js `Array.isArray()` polyfill. Closes #345
+ * Fixed: set .filename option in jade(1) when passing filenames
+ * Fixed `Object.keys()` polyfill typo. Closes #331
+ * Fixed `include` error context
+ * Renamed magic "index" to "$index". Closes #350
+
+0.15.4 / 2011-09-05
+==================
+
+ * Fixed script template html. Closes #316
+ * Revert "Fixed script() tag with trailing ".". Closes #314"
+
+0.15.3 / 2011-08-30
+==================
+
+ * Added Makefile example. Closes #312
+ * Fixed script() tag with trailing ".". Closes #314
+
+0.15.2 / 2011-08-26
+==================
+
+ * Fixed new conditional boundaries. Closes #307
+
+0.15.1 / 2011-08-26
+==================
+
+ * Fixed jade(1) support due to `res.render()` removal
+ * Removed --watch support (use a makefile + watch...)
+
+0.15.0 / 2011-08-26
+==================
+
+ * Added `client` option to reference runtime helpers
+ * Added `Array.isArray()` for runtime.js as well
+ * Added `Object.keys()` for the client-side runtime
+ * Added first-class `if`, `unless`, `else` and `else if` support
+ * Added first-class `each` / `for` support
+ * Added `make benchmark` for continuous-bench
+ * Removed `inline` option, SS helpers are no longer inlined either
+ * Removed `Parser#debug()`
+ * Removed `jade.render()` and `jade.renderFile()`
+ * Fixed runtime.js `escape()` bug causing window.escape to be used
+ * Fixed a bunch of tests
+
+0.14.2 / 2011-08-16
+==================
+
+ * Added `include` support for non-jade files
+ * Fixed code indentation when followed by newline(s). Closes #295 [reported by masylum]
+
+0.14.1 / 2011-08-14
+==================
+
+ * Added `colons` option for everyone stuck with ":". Closes #231
+ * Optimization: consecutive lines are merged in compiled js
+
+0.14.0 / 2011-08-08
+==================
+
+ * Added array iteration with index example. Closes #276
+ * Added _runtime.js_
+ * Added `compileDebug` option to enable lineno instrumentation
+ * Added `inline` option to disable inlining of helpers (for client-side)
+
+0.13.0 / 2011-07-13
+==================
+
+ * Added `mixin` support
+ * Added `include` support
+ * Added array support for the class attribute
+
+0.12.4 / 2011-06-23
+==================
+
+ * Fixed filter indentation bug. Closes #243
+
+0.12.3 / 2011-06-21
+==================
+
+ * Fixed empty strings support. Closes #223
+ * Fixed conditional comments documentation. Closes #245
+
+0.12.2 / 2011-06-16
+==================
+
+ * Fixed `make test`
+ * Fixed block comments
+
+0.12.1 / 2011-06-04
+==================
+
+ * Fixed attribute interpolation with double quotes. Fixes #232 [topaxi]
+
+0.12.0 / 2011-06-03
+==================
+
+ * Added `doctype` as alias of `!!!`
+ * Added; doctype value is now case-insensitive
+ * Added attribute interpolation support
+ * Fixed; retain original indentation spaces in text blocks
+
+0.11.1 / 2011-06-01
+==================
+
+ * Fixed text block indentation [Laszlo Bacsi]
+ * Changed; utilizing devDependencies
+ * Fixed try/catch issue with renderFile(). Closes #227
+ * Removed attribute ":" support, use "=" (option for ':' coming soon)
+
+0.11.0 / 2011-05-14
+==================
+
+ * Added `self` object to avoid poor `with(){}` performance [masylum]
+ * Added `doctype` option [Jeremy Larkin]
+
+0.10.7 / 2011-05-04
+==================
+
+ * expose Parser
+
+0.10.6 / 2011-04-29
+==================
+
+ * Fixed CS `Object.keys()` [reported by robholland]
+
+0.10.5 / 2011-04-26
+==================
+
+ * Added error context after the lineno
+ * Added; indicate failing lineno with ">"
+ * Added `Object.keys()` for the client-side
+ * Fixed attr strings when containing the opposite quote. Closes 207
+ * Fixed attr issue with js expressions within strings
+ * Fixed single-quote filter escape bug. Closes #196
+
+
+0.10.4 / 2011-04-05
+==================
+
+ * Added `html` doctype, same as "5"
+ * Fixed `pre`, no longer text-only
+
+0.10.3 / 2011-03-30
+==================
+
+ * Fixed support for quoted attribute keys ex `rss("xmlns:atom"="atom")`
+
+0.10.2 / 2011-03-30
+==================
+
+ * Fixed pipeless text bug with missing outdent
+
+0.10.1 / 2011-03-28
+==================
+
+ * Fixed `support/compile.js` to exclude browser js in node
+ * Fixes for IE [Patrick Pfeiffer]
+
+0.10.0 / 2011-03-25
+==================
+
+ * Added AST-filter support back in the form of `<tag>[attrs]<:><block>`
+
+0.9.3 / 2011-03-24
+==================
+
+ * Added `Block#unshift(node)`
+ * Added `jade.js` for the client-side to the repo
+ * Added `jade.min.js` for the client-side to the repo
+ * Removed need for pipes in filters. Closes #185
+ Note that this _will_ break filters used to
+ manipulate the AST, until we have a different
+ syntax for doing so.
+
+0.9.2 / 2011-03-23
+==================
+
+ * Added jade `--version`
+ * Removed `${}` interpolation support, use `#{}`
+
+0.9.1 / 2011-03-16
+==================
+
+ * Fixed invalid `.map()` call due to recent changes
+
+0.9.0 / 2011-03-16
+==================
+
+ * Added client-side browser support via `make jade.js` and `make jade.min.js`.
+
+0.8.9 / 2011-03-15
+==================
+
+ * Fixed preservation of newlines in text blocks
+
+0.8.8 / 2011-03-14
+==================
+
+ * Fixed jade(1) stdio
+
+0.8.7 / 2011-03-14
+==================
+
+ * Added `mkdirs()` to jade(1)
+ * Added jade(1) stdio support
+ * Added new features to jade(1), `--watch`, recursive compilation etc [khingebjerg]
+ * Fixed pipe-less text newlines
+ * Removed jade(1) `--pipe` flag
+
+0.8.6 / 2011-03-11
+==================
+
+ * Fixed parenthesized expressions in attrs. Closes #170
+ * Changed; default interpolation values `== null` to ''. Closes #167
+
+0.8.5 / 2011-03-09
+==================
+
+ * Added pipe-less text support with immediate ".". Closes #157
+ * Fixed object support in attrs
+ * Fixed array support for attrs
+
+0.8.4 / 2011-03-08
+==================
+
+ * Fixed issue with expressions being evaluated several times. closes #162
+
+0.8.2 / 2011-03-07
+==================
+
+ * Added markdown, discount, and markdown-js support to `:markdown`. Closes #160
+ * Removed `:discount`
+
+0.8.1 / 2011-03-04
+==================
+
+ * Added `pre` pipe-less text support (and auto-escaping)
+
+0.8.0 / 2011-03-04
+==================
+
+ * Added block-expansion support. Closes #74
+ * Added support for multi-line attrs without commas. Closes #65
+
+0.7.1 / 2011-03-04
+==================
+
+ * Fixed `script()` etc pipe-less text with attrs
+
+0.7.0 / 2011-03-04
+==================
+
+ * Removed `:javascript` filter (it doesn't really do anything special, use `script` tags)
+ * Added pipe-less text support. Tags that only accept text nodes (`script`, `textarea`, etc) do not require `|`.
+ * Added `:text` filter for ad-hoc pipe-less
+ * Added flexible indentation. Tabs, arbitrary number of spaces etc
+ * Added conditional-comment support. Closes #146
+ * Added block comment support
+ * Added rss example
+ * Added `:stylus` filter
+ * Added `:discount` filter
+ * Fixed; auto-detect xml and do not self-close tags. Closes #147
+ * Fixed whitespace issue. Closes #118
+ * Fixed attrs. `,`, `=`, and `:` within attr value strings are valid Closes #133
+ * Fixed; only output "" when code == null. Ex: `span.name= user.name` when undefined or null will not output "undefined". Closes #130
+ * Fixed; throw on unexpected token instead of hanging
+
+0.6.3 / 2011-02-02
+==================
+
+ * Added `each` support for Array-like objects [guillermo]
+
+0.6.2 / 2011-02-02
+==================
+
+ * Added CSRF example, showing how you can transparently add inputs to a form
+ * Added link to vim-jade
+ * Fixed self-closing col support [guillermo]
+ * Fixed exception when getAttribute or removeAttribute run into removed attributes [Naitik Shah]
+
+0.6.0 / 2010-12-19
+==================
+
+ * Added unescaped interpolation variant `!{code}`. Closes #124
+ * Changed; escape interpolated code by default `#{code}`
+
+0.5.7 / 2010-12-08
+==================
+
+ * Fixed; hyphen in get `tag()`
+
+0.5.6 / 2010-11-24
+==================
+
+ * Added `exports.compile(str, options)`
+ * Renamed internal `_` to `__`, since `_()` is commonly used for translation
+
+0.5.5 / 2010-10-30
+==================
+
+ * Add _coffeescript_ filter [Michael Hampton]
+ * Added link to _slim_; a ruby implementation
+ * Fixed quoted attributes issue.
+
+ * Fixed attribute issue with over greedy regexp.
+ Previously "p(foo=(((('bar')))))= ((('baz')))"
+ would __fail__ for example since the regexp
+ would lookahead to far. Now we simply pair
+ the delimiters.
+
+0.5.4 / 2010-10-18
+==================
+
+ * Adding newline when using tag code when preceding text
+ * Assume newline in tag text when preceding text
+ * Changed; retain leading text whitespace
+ * Fixed code block support to prevent multiple buffer openings [Jake Luer]
+ * Fixed nested filter support
+
+0.5.3 / 2010-10-06
+==================
+
+ * Fixed bug when tags with code also have a block [reported by chrisirhc]
+
+0.5.2 / 2010-10-05
+==================
+
+ * Added; Text introduces newlines to mimic the grammar.
+ Whitespace handling is a little tricky with this sort of grammar.
+ Jade will now mimic the written grammar, meaning that text blocks
+ using the "|" margin character will introduce a literal newline,
+ where as immediate tag text (ex "a(href='#') Link") will not.
+
+ This may not be ideal, but it makes more sense than what Jade was
+ previously doing.
+
+ * Added `Tag#text` to disambiguate between immediate / block text
+ * Removed _pretty_ option (was kinda useless in the state it was in)
+ * Reverted ignoring of newlines. Closes #92.
+ * Fixed; `Parser#parse()` ignoring newlines
+
+0.5.1 / 2010-10-04
+==================
+
+ * Added many examples
+ * Added; compiler api is now public
+ * Added; filters can accept / manipulate the parse tree
+ * Added filter attribute support. Closes #79
+ * Added LL(*) capabilities
+ * Performance; wrapping code blocks in {} instead of `(function(){}).call(this)`
+ * Performance; Optimized attribute buffering
+ * Fixed trailing newlines in blocks
+
+0.5.0 / 2010-09-11
+==================
+
+ * __Major__ refactor. Logic now separated into lexer/parser/compiler for future extensibility.
+ * Added _pretty_ option
+ * Added parse tree output for _debug_ option
+ * Added new examples
+ * Removed _context_ option, use _scope_
+
+0.4.1 / 2010-09-09
+==================
+
+ * Added support for arbitrary indentation for single-line comments. Closes #71
+ * Only strip first space in text (ex '| foo' will buffer ' foo')
+
+0.4.0 / 2010-08-30
+==================
+
+ * Added tab naive support (tabs are converted to a single indent, aka two spaces). Closes #24
+ * Added unbuffered comment support. Closes #62
+ * Added hyphen support for tag names, ex: "fb:foo-bar"
+ * Fixed bug with single quotes in comments. Closes #61
+ * Fixed comment whitespace issue, previously padding. Closes #55
+
+0.3.0 / 2010-08-04
+==================
+
+ * Added single line comment support. Closes #25
+ * Removed CDATA from _:javascript_ filter. Closes #47
+ * Removed _sys_ local
+ * Fixed code following tag
+
+0.2.4 / 2010-08-02
+==================
+
+ * Added Buffer support to `render()`
+ * Fixed filter text block exception reporting
+ * Fixed tag exception reporting
+
+0.2.3 / 2010-07-27
+==================
+
+ * Fixed newlines before block
+ * Fixed; tag text allowing arbitrary trailing whitespace
+
+0.2.2 / 2010-07-16
+==================
+
+ * Added support for `jade.renderFile()` to utilize primed cache
+ * Added link to [textmate bundle](http://github.com/miksago/jade-tmbundle)
+ * Fixed filter issue with single quotes
+ * Fixed hyphenated attr bug
+ * Fixed interpolation single quotes. Closes #28
+ * Fixed issue with comma in attrs
+
+0.2.1 / 2010-07-09
+==================
+
+ * Added support for node-discount and markdown-js
+ depending on which is available.
+
+ * Added support for tags to have blocks _and_ text.
+ this kinda fucks with arbitrary whitespace unfortunately,
+ but also fixes trailing spaces after tags _with_ blocks.
+
+ * Caching generated functions. Closes #46
+
+0.2.0 / 2010-07-08
+==================
+
+ * Added `- each` support for readable iteration
+ * Added [markdown-js](http://github.com/evilstreak/markdown-js) support (no compilation required)
+ * Removed node-discount support
+
+0.1.0 / 2010-07-05
+==================
+
+ * Added `${}` support for interpolation. Closes #45
+ * Added support for quoted attr keys: `label("for": 'something')` is allowed (_although not required_) [Guillermo]
+ * Added `:less` filter [jakeluer]
+
+0.0.2 / 2010-07-03
+==================
+
+ * Added `context` as synonym for `scope` option [Guillermo]
+ * Fixed attr splitting: `div(style:"color: red")` is now allowed
+ * Fixed issue with `(` and `)` within attrs: `a(class: (a ? 'a' : 'b'))` is now allowed
+ * Fixed issue with leading / trailing spaces in attrs: `a( href="#" )` is now allowed [Guillermo]
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..0f3c767
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+(The MIT License)
+
+Copyright (c) 2009-2014 TJ Holowaychuk <tj at vision-media.ca>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5d63638
--- /dev/null
+++ b/README.md
@@ -0,0 +1,147 @@
+# [![Jade - template engine ](http://i.imgur.com/5zf2aVt.png)](http://jade-lang.com/)
+
+Full documentation is at [jade-lang.com](http://jade-lang.com/)
+
+ Jade is a high performance template engine heavily influenced by [Haml](http://haml-lang.com)
+ and implemented with JavaScript for [node](http://nodejs.org). For discussion join the [Google Group](http://groups.google.com/group/jadejs).
+
+ 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)
+
+## Installation
+
+via npm:
+
+```bash
+$ npm install jade
+```
+
+## Syntax
+
+Jade is a clean, whitespace sensitive syntax for writing html. Here is a simple example:
+
+```jade
+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.
+```
+
+becomes
+
+
+```html
+<!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>
+```
+
+The official [jade tutorial](http://jade-lang.com/tutorial/) is a great place to start. While that (and the syntax documentation) is being finished, you can view some of the old documentation [here](https://github.com/visionmedia/jade/blob/master/jade.md) and [here](https://github.com/visionmedia/jade/blob/master/jade-language.md)
+
+## API
+
+For full API, see [jade-lang.com/api](http://jade-lang.com/api/)
+
+```js
+var jade = require('jade');
+
+// compile
+var fn = jade.compile('string of jade', options);
+var html = fn(locals);
+
+// render
+var html = jade.render('string of jade', merge(options, locals));
+
+// renderFile
+var html = jade.renderFile('filename.jade', merge(options, locals));
+```
+
+### Options
+
+ - `filename` Used in exceptions, and required when using includes
+ - `compileDebug` When `false` no debug instrumentation is compiled
+ - `pretty` Add pretty-indentation whitespace to output _(false by default)_
+
+## Browser Support
+
+ The latest version of jade can be download for the browser in standalone form from [here](https://github.com/visionmedia/jade/raw/master/jade.js). It only supports the very latest browsers though, and is a large file. It is recommended that you pre-compile your jade templates to JavaScript and then just use the [runtime.js](https://github.com/visionmedia/jade/raw/master/runtime.js) library on the client.
+
+ To compile a template for use on the client using the command line, do:
+
+```console
+$ jade --client --no-debug filename.jade
+```
+
+which will produce `filename.js` containing the compiled template.
+
+## Command Line
+
+After installing the latest version of [node](http://nodejs.org/), install with:
+
+```console
+$ npm install jade -g
+```
+
+and run with
+
+```console
+$ jade --help
+```
+
+## Additional Resources
+
+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)
+
+
+Implementations in other languages:
+
+ - [php](http://github.com/everzet/jade.php)
+ - [scala](http://scalate.fusesource.org/versions/snapshot/documentation/scaml-reference.html)
+ - [ruby](https://github.com/slim-template/slim)
+ - [python](https://github.com/SyrusAkbary/pyjade)
+ - [java](https://github.com/neuland/jade4j)
+
+Other:
+
+ - [Emacs Mode](https://github.com/brianc/jade-mode)
+ - [Vim Syntax](https://github.com/digitaltoad/vim-jade)
+ - [TextMate Bundle](http://github.com/miksago/jade-tmbundle)
+ - [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
+
+## License
+
+MIT
diff --git a/Readme_zh-cn.md b/Readme_zh-cn.md
new file mode 100644
index 0000000..160ea0e
--- /dev/null
+++ b/Readme_zh-cn.md
@@ -0,0 +1,1285 @@
+# Jade - 模板引擎
+
+Jade 是一个高性能的模板引擎,它深受 [Haml](http://haml-lang.com) 影响,它是用 JavaScript 实现的, 并且可以供 [Node](http://nodejs.org) 使用.
+
+翻译: [草依山](http://jser.me) 等
+
+## 声明
+
+从 Jade `v0.31.0` 开始放弃了对于 `<script>` 和 `<style>` 标签的平文本支持. 这个问题你可以在 `<script> <style>` 标签后加上 `.` 来解决.
+
+希望这一点能让 Jade 对新手更友好, 同时也不影响到 Jade 本身的能力或者导致过度冗长.
+
+如果你有大量的文件需要转换你可以用下 [fix-jade](https://github.com/ForbesLindesay/fix-jade) 尝试自动完成这个过程.
+
+## Test drive
+
+你可以在网上[试玩 Jade](http://naltatis.github.com/jade-syntax-docs).
+
+## README 目录
+
+- [特性](#a1)
+- [其它实现](#a2)
+- [安装](#a3)
+- [浏览器支持](#a4)
+- [公开 API](#a5)
+- [语法](#a6)
+ - [行结束标志](#a6-1)
+ - [标签](#a6-2)
+ - [标签文本](#a6-3)
+ - [注释](#a6-4)
+ - [块注释](#a6-5)
+ - [内联](#a6-6)
+ - [块展开](#a6-7)
+ - [Case](#a6-8)
+ - [属性](#a6-9)
+ - [HTML](#a6-10)
+ - [Doctypes](#a6-11)
+- [过滤器](#a7)
+- [代码](#a8)
+- [循环](#a9)
+- [条件语句](#a10)
+- [模板继承](#a11)
+- [Block append/prepend](#a12)
+- [包含](#a13)
+- [Mixins](#a14)
+- [产生输出](#a15)
+- [Makefile 的一个例子](#a16)
+- [命令行的 Jade](#a17)
+- [教程](#a18)
+- [License](#a19)
+
+<a name="a1"/>
+## 特性
+
+ - 客户端支持
+ - 代码高可读
+ - 灵活的缩进
+ - 块展开
+ - Mixins
+ - 静态包含
+ - 属性改写
+ - 安全,默认代码是转义的
+ - 运行时和编译时上下文错误报告
+ - 命令行下编译jade模板
+ - HTML5 模式 (使用 `!!! 5` 文档类型)
+ - 在内存中缓存(可选)
+ - 合并动态和静态标签类
+ - 可以通过 `filters` 修改树
+ - 模板继承
+ - 原生支持 [Express JS](http://expressjs.com)
+ - 通过 `each` 枚举对象、数组甚至是不能枚举的对象
+ - 块注释
+ - 没有前缀的标签
+ - AST Filters
+ - 过滤器
+ - `:stylus` 必须已经安装 [stylus](http://github.com/LearnBoost/stylus)
+ - `:less` 必须已经安装 [less.js](http://github.com/cloudhead/less.js)
+ - `:markdown` 必须已经安装 [markdown-js](http://github.com/evilstreak/markdown-js) 或者 [node-discount](http://github.com/visionmedia/node-discount)
+ - `:cdata`
+ - `:coffeescript` 必须已经安装[coffee-script](http://jashkenas.github.com/coffee-script/)
+ - [Emacs Mode](https://github.com/brianc/jade-mode)
+ - [Vim Syntax](https://github.com/digitaltoad/vim-jade)
+ - [TextMate Bundle](http://github.com/miksago/jade-tmbundle)
+ - [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
+
+<a name="a2"/>
+## 其它实现
+
+ - [php](http://github.com/everzet/jade.php)
+ - [scala](http://scalate.fusesource.org/versions/snapshot/documentation/scaml-reference.html)
+ - [ruby](https://github.com/slim-template/slim)
+ - [python](https://github.com/SyrusAkbary/pyjade)
+ - [java](https://github.com/neuland/jade4j)
+
+<a name="a3"/>
+## 安装
+
+通过 NPM:
+
+```sh
+npm install jade
+```
+
+<a name="a4"/>
+## 浏览器支持
+
+把 Jade 编译为一个可供浏览器使用的单文件,只需要简单的执行:
+
+```sh
+$ make jade.js
+```
+
+如果你已经安装了 uglifyjs (`npm install uglify-js`),你可以执行下面的命令它会生成所有的文件。其实每一个正式版本里都帮你做了这事。
+
+```sh
+make jade.min.js
+```
+
+默认情况下,为了方便调试Jade会把模板组织成带有形如 `__.lineno = 3` 的行号的形式。
+在浏览器里使用的时候,你可以通过传递一个选项 `{ compileDebug: false }` 来去掉这个。
+下面的模板
+
+```sh
+p Hello #{name}
+```
+
+会被翻译成下面的函数:
+
+```js
+function anonymous(locals, attrs, escape, rethrow) {
+ var buf = [];
+ with (locals || {}) {
+ var interp;
+ buf.push('\n<p>Hello ' + escape((interp = name) == null ? '' : interp) + '\n</p>');
+ }
+ return buf.join("");
+}
+```
+
+通过使用 Jade 的 `./runtime.js`你可以在浏览器使用这些预编译的模板而不需要使用 Jade, 你只需要使用 `runtime.js` 里的工具函数, 它们会放在 `jade.attrs`, `jade.escape` 这些里。 把选项 `{ client: true }` 传递给 `jade.compile()`, Jade 会把这些帮助函数的引用放在`jade.attrs`, `jade.escape`.
+
+```js
+function anonymous(locals, attrs, escape, rethrow) {
+ var attrs = jade.attrs, escape = jade.escape, rethrow = jade.rethrow;
+ var buf = [];
+ with (locals || {}) {
+ var interp;
+ buf.push('\n<p>Hello ' + escape((interp = name) == null ? '' : interp) + '\n</p>');
+ }
+ return buf.join("");
+}
+```
+
+<a name="a5"/>
+## 公开 API
+
+```javascript
+var jade = require('jade');
+
+// Compile a function
+var fn = jade.compile('string of jade', options);
+fn(locals);
+```
+
+### 选项
+
+ - `self` 使用 `self` 命名空间来持有本地变量. _(默认为 `false`)_
+ - `locals` 本地变量对象
+ - `filename` 异常发生时使用,includes 时必需
+ - `debug` 输出 token 和翻译后的函数体
+ - `compiler` 替换掉 jade 默认的编译器
+ - `compileDebug` `false`的时候调试的结构不会被输出
+ - `pretty` 为输出加上了漂亮的空格缩进 _(默认为 `false`)_
+
+<a name="a6"/>
+## 语法
+
+<a name="a6-1"/>
+### 行结束标志
+
+**CRLF** 和 **CR** 会在编译之前被转换为 **LF**
+
+<a name="a6-2"/>
+### 标签
+
+标签就是一个简单的单词:
+
+```jade
+html
+```
+
+它会被转换为 `<html></html>`
+
+标签也是可以有 id 的:
+
+```jade
+div#container
+```
+
+它会被转换为 `<div id="container"></div>`
+
+怎么加 class 呢?
+
+```jade
+div.user-details
+```
+
+转换为 `<div class="user-details"></div>`
+
+多个 class 和 id? 也是可以搞定的:
+
+ div#foo.bar.baz
+
+转换为 `<div id="foo" class="bar baz"></div>`
+
+不停的 `div div div` 很讨厌啊 , 可以这样:
+
+```jade
+#foo
+.bar
+```
+
+这个算是我们的语法糖,它已经被很好的支持了,上面的会输出:
+
+```html
+<div id="foo"></div><div class="bar"></div>
+```
+
+<a name="a6-3"/>
+### 标签文本
+
+只需要简单的把内容放在标签之后:
+
+```jade
+p wahoo!
+```
+
+它会被渲染为 `<p>wahoo!</p>`.
+
+很帅吧,但是大段的文本怎么办呢:
+
+```jade
+p
+ | foo bar baz
+ | rawr rawr
+ | super cool
+ | go jade go
+```
+
+渲染为 `<p>foo bar baz rawr.....</p>`
+
+怎么和数据结合起来? 所有类型的文本展示都可以和数据结合起来,如果我们把 `{ name: 'tj', email: 'tj at vision-media.ca' }` 传给编译函数,下面是模板上的写法:
+
+```jade
+#user #{name} <#{email}>
+```
+
+它会被渲染为 `<div id="user">tj <tj at vision-media.ca></div>`
+
+当就是要输出 `#{}` 的时候怎么办? 转义一下!
+
+ p \#{something}
+
+它会输出 `<p>#{something}</p>`
+
+同样可以使用非转义的变量 `!{html}`, 下面的模板将直接输出一个 `<script>` 标签:
+
+```jade
+- var html = "<script></script>"
+| !{html}
+```
+
+内联标签同样可以使用文本块来包含文本:
+
+```jade
+label
+ | Username:
+ input(name='user[name]')
+```
+
+或者直接使用标签文本:
+
+```jade
+label Username:
+ input(name='user[name]')
+```
+
+_只_ 包含文本的标签,比如 `<script>`, `<style>`, 和 `<textarea>` 不需要前缀 `|` 字符, 比如:
+
+```jade
+html
+ head
+ title Example
+ script
+ if (foo) {
+ bar();
+ } else {
+ baz();
+ }
+```
+
+这里还有一种选择,可以使用 `.` 来开始一段文本块,比如:
+
+```jade
+p.
+ foo asdf
+ asdf
+ asdfasdfaf
+ asdf
+ asd.
+```
+
+会被渲染为:
+
+```jade
+<p>foo asdf
+asdf
+ asdfasdfaf
+ asdf
+asd
+.
+</p>
+```
+
+这和带一个空格的 `.` 是不一样的, 带空格的会被 Jade 的解析器忽略,当作一个普通的文字:
+
+```jade
+p .
+```
+
+渲染为:
+
+```jade
+<p>.</p>
+```
+
+需要注意的是文本块需要两次转义。比如想要输出下面的文本:
+
+```jade
+</p>foo\bar</p>
+```
+
+使用:
+
+```jade
+p.
+ foo\\bar
+```
+
+<a name="a6-4"/>
+### 注释
+
+单行注释和 JavaScript 里是一样的,通过 `//` 来开始,并且必须单独一行:
+
+```jade
+// just some paragraphs
+p foo
+p bar
+```
+
+渲染为:
+
+```html
+<!-- just some paragraphs -->
+<p>foo</p>
+<p>bar</p>
+```
+
+Jade 同样支持不输出的注释,加一个短横线就行了:
+
+```jade
+//- will not output within markup
+p foo
+p bar
+```
+
+渲染为:
+
+```html
+<p>foo</p>
+<p>bar</p>
+```
+
+<a name="a6-5"/>
+### 块注释
+
+块注释也是支持的:
+
+```jade
+body
+ //
+ #content
+ h1 Example
+```
+
+渲染为:
+
+```html
+<body>
+ <!--
+ <div id="content">
+ <h1>Example</h1>
+ </div>
+ -->
+</body>
+```
+
+Jade 同样很好的支持了条件注释:
+
+```jade
+body
+ //if IE
+ a(href='http://www.mozilla.com/en-US/firefox/') Get Firefox
+```
+
+渲染为:
+
+```html
+<body>
+ <!--[if IE]>
+ <a href="http://www.mozilla.com/en-US/firefox/">Get Firefox</a>
+ <![endif]-->
+</body>
+```
+
+<a name="a6-6"/>
+### 内联
+
+Jade 支持以自然的方式定义标签嵌套:
+
+```jade
+ul
+ li.first
+ a(href='#') foo
+ li
+ a(href='#') bar
+ li.last
+ a(href='#') baz
+```
+
+<a name="a6-7"/>
+### 块展开
+
+块展开可以帮助你在一行内创建嵌套的标签,下面的例子和上面的是一样的:
+
+```jade
+ul
+ li.first: a(href='#') foo
+ li: a(href='#') bar
+ li.last: a(href='#') baz
+```
+
+<a name="a6-8"/>
+### Case
+
+`case` 表达式按下面这样的形式写:
+
+```jade
+html
+ body
+ friends = 10
+ case friends
+ when 0
+ p you have no friends
+ when 1
+ p you have a friend
+ default
+ p you have #{friends} friends
+```
+
+块展开在这里也可以使用:
+
+```jade
+friends = 5
+
+html
+ body
+ case friends
+ when 0: p you have no friends
+ when 1: p you have a friend
+ default: p you have #{friends} friends
+```
+
+<a name="a6-9"/>
+### 属性
+
+Jade 现在支持使用 `(` 和 `)` 作为属性分隔符
+
+```jade
+a(href='/login', title='View login page') Login
+```
+
+当一个值是 `undefined` 或者 `null` 属性 _不_ 会被加上,
+所以呢,它不会编译出 'something="null"'.
+
+```jade
+div(something=null)
+```
+
+Boolean 属性也是支持的:
+
+```jade
+input(type="checkbox", checked)
+```
+
+使用代码的 Boolean 属性只有当属性为 `true` 时才会输出:
+
+```jade
+input(type="checkbox", checked=someValue)
+```
+
+多行同样也是可用的:
+
+```jade
+input(type='checkbox',
+ name='agreement',
+ checked)
+```
+
+多行的时候可以不加逗号:
+
+```jade
+input(type='checkbox'
+ name='agreement'
+ checked)
+```
+
+加点空格,格式好看一点?同样支持
+
+```jade
+input(
+ type='checkbox'
+ name='agreement'
+ checked)
+```
+
+冒号也是支持的:
+
+```jade
+rss(xmlns:atom="atom")
+```
+
+假如我有一个 `user` 对象 `{ id: 12, name: 'tobi' }`
+我们希望创建一个指向 `/user/12` 的链接 `href`, 我们可以使用普通的 JavaScript 字符串连接,如下:
+
+```jade
+a(href='/user/' + user.id)= user.name
+```
+
+或者我们使用 Jade 的修改方式, 这个我想很多使用 Ruby 或者 CoffeeScript 的人会看起来像普通的 JS..:
+
+```jade
+a(href='/user/#{user.id}')= user.name
+```
+
+`class` 属性是一个特殊的属性,你可以直接传递一个数组,比如 `bodyClasses = ['user', 'authenticated']` :
+
+```jade
+body(class=bodyClasses)
+```
+
+<a name="a6-10"/>
+### HTML
+
+内联的 HTML 是可以的,我们可以使用管道定义一段文本 :
+
+```jade
+html
+ body
+ | <h1>Title</h1>
+ | <p>foo bar baz</p>
+```
+
+或者我们可以使用 `.` 来告诉 Jade 我们需要一段文本:
+
+```jade
+html
+ body.
+ <h1>Title</h1>
+ <p>foo bar baz</p>
+```
+
+上面的两个例子都会渲染成相同的结果:
+
+```jade
+<html><body><h1>Title</h1>
+<p>foo bar baz</p>
+</body></html>
+```
+
+ 这条规则适应于在 Jade 里的任何文本:
+
+```
+html
+ body
+ h1 User <em>#{name}</em>
+```
+
+<a name="a6-11"/>
+### Doctypes
+
+添加文档类型只需要简单的使用 `!!!`, 或者 `doctype` 跟上下面的可选项:
+
+```jade
+!!!
+```
+
+会渲染出 _transitional_ 文档类型, 或者:
+
+```jade
+!!! 5
+```
+
+或
+
+```jade
+!!! html
+```
+
+或
+
+```jade
+doctype html
+```
+
+Doctype 是大小写不敏感的, 所以下面两个是一样的:
+
+```jade
+doctype Basic
+doctype basic
+```
+
+当然也是可以直接传递一段文档类型的文本:
+
+```jade
+doctype html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN"
+```
+
+渲染后:
+
+```html
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN">
+```
+
+会输出 _HTML5_ 文档类型. 下面的默认的文档类型,可以很简单的扩展:
+
+```javascript
+var doctypes = exports.doctypes = {
+ '5': '<!DOCTYPE html>',
+ 'xml': '<?xml version="1.0" encoding="utf-8" ?>',
+ 'default': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
+ 'transitional': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
+ 'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
+ 'frameset': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
+ '1.1': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
+ 'basic': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">',
+ 'mobile': '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
+};
+```
+
+通过下面的代码可以很简单的改变默认的文档类型:
+
+```javascript
+ jade.doctypes.default = 'whatever you want';
+```
+
+<a name="a7"/>
+## 过滤器
+
+过滤器前缀 `:`, 比如 `:markdown` 会把下面块里的文本交给专门的函数进行处理。查看顶部 _特性_ 里有哪些可用的过滤器。
+
+```jade
+body
+ :markdown
+ Woah! jade _and_ markdown, very **cool**
+ we can even link to [stuff](http://google.com)
+```
+
+渲染为:
+
+```html
+<body><p>Woah! jade <em>and</em> markdown, very <strong>cool</strong> we can even link to <a href="http://google.com">stuff</a></p></body>
+```
+
+<a name="a8"/>
+## 代码
+
+Jade 目前支持三种类型的可执行代码。第一种是前缀 `-`, 这是不会被输出的:
+
+```jade
+- var foo = 'bar';
+```
+
+这可以用在条件语句或者循环中:
+
+```jade
+- for (var key in obj)
+ p= obj[key]
+```
+
+由于 Jade 的缓存技术,下面的代码也是可以的:
+
+```jade
+- if (foo)
+ ul
+ li yay
+ li foo
+ li worked
+- else
+ p oh no! didnt work
+```
+
+哈哈,甚至是很长的循环也是可以的:
+
+ - if (items.length)
+ ul
+ - items.forEach(function(item){
+ li= item
+ - })
+
+所以你想要的!
+
+下一步我们要 _转义_ 输出的代码,比如我们返回一个值,只要前缀一个 `=`:
+
+```jade
+- var foo = 'bar'
+= foo
+h1= foo
+```
+
+它会渲染为 `bar<h1>bar</h1>`. 为了安全起见,使用 `=` 输出的代码默认是转义的,如果想直接输出不转义的值可以使用 `!=`:
+
+```jade
+p!= aVarContainingMoreHTML
+```
+
+Jade 同样是设计师友好的,它可以使 JavaScript 更直接更富表现力。比如下面的赋值语句是相等的,同时表达式还是通常的 JavaScript:
+
+```jade
+- var foo = 'foo ' + 'bar'
+foo = 'foo ' + 'bar'
+```
+
+Jade 会把 `if`, `else if`, `else`, `until`, `while`, `unless` 同别的优先对待, 但是你得记住它们还是普通的 JavaScript:
+
+```jade
+if foo == 'bar'
+ ul
+ li yay
+ li foo
+ li worked
+else
+ p oh no! didnt work
+```
+
+<a name="a9"/>
+## 循环
+
+尽管已经支持 JavaScript 原生代码,Jade 还是支持了一些特殊的标签,它们可以让模板更加易于理解,其中之一就是 `each`, 这种形式:
+
+```jade
+each VAL[, KEY] in OBJ
+```
+
+一个遍历数组的例子 :
+
+```jade
+- var items = ["one", "two", "three"]
+each item in items
+ li= item
+```
+
+渲染为:
+
+```html
+<li>one</li>
+<li>two</li>
+<li>three</li>
+```
+
+遍历一个数组同时带上索引:
+
+```jade
+items = ["one", "two", "three"]
+each item, i in items
+ li #{item}: #{i}
+```
+
+渲染为:
+
+```html
+<li>one: 0</li>
+<li>two: 1</li>
+<li>three: 2</li>
+```
+
+遍历一个数组的键值:
+
+```jade
+obj = { foo: 'bar' }
+each val, key in obj
+ li #{key}: #{val}
+```
+
+将会渲染为:`<li>foo: bar</li>`
+
+Jade 在内部会把这些语句转换成原生的 JavaScript 语句,就像使用 `users.forEach(function(user){`, 词法作用域和嵌套会像在普通的 JavaScript 中一样:
+
+```jade
+each user in users
+ each role in user.roles
+ li= role
+```
+
+如果你喜欢,也可以使用 `for` :
+
+```jade
+for user in users
+ for role in user.roles
+ li= role
+```
+
+<a name="a10"/>
+## 条件语句
+
+Jade 条件语句和使用了(`-`) 前缀的 JavaScript 语句是一致的,然后它允许你不使用圆括号,这样会看上去对设计师更友好一点,
+同时要在心里记住这个表达式渲染出的是 _常规_ JavaScript:
+
+```jade
+for user in users
+ if user.role == 'admin'
+ p #{user.name} is an admin
+ else
+ p= user.name
+```
+
+和下面的使用了常规 JavaScript 的代码是相等的:
+
+```jade
+for user in users
+ - if (user.role == 'admin')
+ p #{user.name} is an admin
+ - else
+ p= user.name
+```
+
+Jade 同时支持 `unless`, 这和 `if (!(expr))` 是等价的:
+
+```jade
+for user in users
+ unless user.isAnonymous
+ p
+ | Click to view
+ a(href='/users/' + user.id)= user.name
+```
+
+<a name="a11"/>
+## 模板继承
+
+Jade 支持通过 `block` 和 `extends` 关键字来实现模板继承。 一个块就是一个 Jade 的 block ,它将在子模板中实现,同时是支持递归的。
+
+Jade 块如果没有内容,Jade 会添加默认内容,下面的代码默认会输出 `block scripts`, `block content`, 和 `block foot`.
+
+```jade
+html
+ head
+ h1 My Site - #{title}
+ block scripts
+ script(src='/jquery.js')
+ body
+ block content
+ block foot
+ #footer
+ p some footer content
+```
+
+现在我们来继承这个布局,简单创建一个新文件,像下面那样直接使用 `extends`,给定路径(可以选择带 `.jade` 扩展名或者不带). 你可以定义一个或者更多的块来覆盖父级块内容, 注意到这里的 `foot` 块 _没有_ 定义,所以它还会输出父级的 "some footer content"。
+
+```jade
+extends extend-layout
+
+block scripts
+ script(src='/jquery.js')
+ script(src='/pets.js')
+
+block content
+ h1= title
+ each pet in pets
+ include pet
+```
+
+同样可以在一个子块里添加块,就像下面实现的块 `content` 里又定义了两个可以被实现的块 `sidebar` 和 `primary`,或者子模板直接实现 `content`。
+
+```jade
+extends regular-layout
+
+block content
+ .sidebar
+ block sidebar
+ p nothing
+ .primary
+ block primary
+ p nothing
+```
+
+<a name="a12"/>
+
+## 前置、追加代码块
+
+Jade允许你 _替换_ (默认)、 _前置_ 和 _追加_ blocks. 比如,假设你希望在 _所有_ 页面的头部都加上默认的脚本,你可以这么做:
+
+
+```jade
+html
+ head
+ block head
+ script(src='/vendor/jquery.js')
+ script(src='/vendor/caustic.js')
+ body
+ block content
+```
+
+现在假设你有一个Javascript游戏的页面,你希望在默认的脚本之外添加一些游戏相关的脚本,你可以直接`append`上代码块:
+
+
+```jade
+extends layout
+
+block append head
+ script(src='/vendor/three.js')
+ script(src='/game.js')
+```
+
+使用 `block append` 或 `block prepend` 时 `block` 是可选的:
+
+```jade
+extends layout
+
+append head
+ script(src='/vendor/three.js')
+ script(src='/game.js')
+```
+
+<a name="a13"/>
+## 包含
+
+Includes 允许你静态包含一段 Jade, 或者别的存放在单个文件中的东西比如 CSS, HTML 非常常见的例子是包含头部和页脚。 假设我们有一个下面目录结构的文件夹:
+
+```
+./layout.jade
+./includes/
+ ./head.jade
+ ./tail.jade
+```
+
+下面是 `layout.jade` 的内容:
+
+```jade
+html
+ include includes/head
+ body
+ h1 My Site
+ p Welcome to my super amazing site.
+ include includes/foot
+```
+
+这两个包含 `includes/head` 和 `includes/foot` 都会读取相对于给 `layout.jade` 参数`filename` 的路径的文件, 这是一个绝对路径,不用担心Express帮你搞定这些了。Include 会解析这些文件,并且插入到已经生成的语法树中,然后渲染为你期待的内容:
+
+```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>
+```
+
+前面已经提到,`include` 可以包含比如 HTML 或者 CSS 这样的内容。给定一个扩展名后,Jade 不会把这个文件当作一个 Jade 源代码,并且会把它当作一个普通文本包含进来:
+
+```
+html
+ head
+ //- css and js have simple filters that wrap them in
+ <style> and <script> tags, respectively
+ include stylesheet.css
+ include script.js
+ body
+ //- "markdown" files will use the "markdown" filter
+ to convert Markdown to HTML
+ include introduction.markdown
+ //- html files have no filter and are included verbatim
+ include content.html
+```
+
+Include 也可以接受块内容,给定的块将会附加到包含文件 _最后_ 的块里。 举个例子,`head.jade` 包含下面的内容:
+
+```jade
+head
+ script(src='/jquery.js')
+```
+
+我们可以像下面给`include head`添加内容, 这里是添加两个脚本.
+
+```
+html
+ include head
+ script(src='/foo.js')
+ script(src='/bar.js')
+ body
+ h1 test
+```
+
+
+在被包含的模板中,你也可以使用`yield`语句。`yield`语句允许你明确地标明`include`的代码块的放置位置。比如,假设你希望把代码块放在scripts之前,而不是附加在scripts之后:
+
+```jade
+head
+ yield
+ script(src='/jquery.js')
+ script(src='/jquery.ui.js')
+```
+
+由于被包含的Jade会按字面解析并合并到AST中,词法范围的变量的效果和直接写在同一个文件中的相同。这就意味着`include`可以用作partial的替代,例如,假设我们有一个引用了`user`变量的user.jade`文件:
+
+
+```jade
+h1= user.name
+p= user.occupation
+```
+
+接着,当我们迭代users的时候,只需简单地加上`include user`。因为在循环中`user`变量已经被定义了,被包含的模板可以访问它。
+
+
+
+```jade
+users = [{ name: 'Tobi', occupation: 'Ferret' }]
+
+each user in users
+ .user
+ include user
+```
+
+以上代码会生成:
+
+```html
+<div class="user">
+ <h1>Tobi</h1>
+ <p>Ferret</p>
+</div>
+```
+
+`user.jade`引用了`user`变量,如果我们希望使用一个不同的变量`user`,那么我们可以直接定义一个新变量`user = person`,如下所示:
+
+```jade
+each person in users
+ .user
+ user = person
+ include user
+```
+
+
+<a name="a14"/>
+## Mixins
+
+Mixins 在编译的模板里会被 Jade 转换为普通的 JavaScript 函数。 Mixins 可以还参数,但不是必需的:
+
+```jade
+mixin list
+ ul
+ li foo
+ li bar
+ li baz
+```
+
+使用不带参数的 mixin 看上去非常简单,在一个块外:
+
+```jade
+h2 Groceries
+mixin list
+```
+
+Mixins 也可以带一个或者多个参数,参数就是普通的 JavaScript 表达式,比如下面的例子:
+
+```jade
+mixin pets(pets)
+ ul.pets
+ - each pet in pets
+ li= pet
+
+mixin profile(user)
+ .user
+ h2= user.name
+ mixin pets(user.pets)
+```
+
+会输出像下面的 HTML:
+
+
+```html
+<div class="user">
+ <h2>tj</h2>
+ <ul class="pets">
+ <li>tobi</li>
+ <li>loki</li>
+ <li>jane</li>
+ <li>manny</li>
+ </ul>
+</div>
+```
+
+<a name="a15"/>
+## 产生输出
+
+假设我们有下面的 Jade 源码:
+
+```jade
+- var title = 'yay'
+h1.title #{title}
+p Just an example
+```
+
+当 `compileDebug` 选项不是 `false`, Jade 会编译时会把函数里加上 `__.lineno = n;`, 这个参数会在编译出错时传递给 `rethrow()`, 而这个函数会在 Jade 初始输出时给出一个有用的错误信息。
+
+```js
+function anonymous(locals) {
+ var __ = { lineno: 1, input: "- var title = 'yay'\nh1.title #{title}\np Just an example", filename: "testing/test.js" };
+ var rethrow = jade.rethrow;
+ try {
+ var attrs = jade.attrs, escape = jade.escape;
+ var buf = [];
+ with (locals || {}) {
+ var interp;
+ __.lineno = 1;
+ var title = 'yay'
+ __.lineno = 2;
+ buf.push('<h1');
+ buf.push(attrs({ "class": ('title') }));
+ buf.push('>');
+ buf.push('' + escape((interp = title) == null ? '' : interp) + '');
+ buf.push('</h1>');
+ __.lineno = 3;
+ buf.push('<p>');
+ buf.push('Just an example');
+ buf.push('</p>');
+ }
+ return buf.join("");
+ } catch (err) {
+ rethrow(err, __.input, __.filename, __.lineno);
+ }
+}
+```
+
+当 `compileDebug` 参数是 `false`, 这个参数会被去掉,这样对于轻量级的浏览器端模板是非常有用的。结合 Jade 的参数和当前源码库里的 `./runtime.js` 文件,你可以通过 `toString()` 来编译模板而不需要在浏览器端运行整个 Jade 库,这样可以提高性能,也可以减少载入的 JavaScript 数量。
+
+```js
+function anonymous(locals) {
+ var attrs = jade.attrs, escape = jade.escape;
+ var buf = [];
+ with (locals || {}) {
+ var interp;
+ var title = 'yay'
+ buf.push('<h1');
+ buf.push(attrs({ "class": ('title') }));
+ buf.push('>');
+ buf.push('' + escape((interp = title) == null ? '' : interp) + '');
+ buf.push('</h1>');
+ buf.push('<p>');
+ buf.push('Just an example');
+ buf.push('</p>');
+ }
+ return buf.join("");
+}
+```
+
+<a name="a16"/>
+## Makefile 的一个例子
+
+通过执行 `make`, 下面的 Makefile 例子可以把 `pages/*.jade` 编译为 `pages/*.html` 。
+
+```make
+JADE = $(shell find pages/*.jade)
+HTML = $(JADE:.jade=.html)
+
+all: $(HTML)
+
+%.html: %.jade
+ jade < $< --path $< > $@
+
+clean:
+ rm -f $(HTML)
+
+.PHONY: clean
+```
+
+这个可以和 `watch(1)` 命令起来产生像下面的行为:
+
+```sh
+$ watch make
+```
+
+<a name="a17"/>
+## 命令行的 Jade
+
+```
+
+使用: jade [options] [dir|file ...]
+
+选项:
+
+ -h, --help 输出帮助信息
+ -v, --version 输出版本号
+ -o, --out <dir> 输出编译后的 HTML 到 <dir>
+ -O, --obj <str> JavaScript 选项
+ -p, --path <path> 在处理 stdio 时,查找包含文件时的查找路径
+ -P, --pretty 格式化 HTML 输出
+ -c, --client 编译浏览器端可用的 runtime.js
+ -D, --no-debug 关闭编译的调试选项(函数会更小)
+ -w, --watch 监视文件改变自动刷新编译结果
+
+Examples:
+
+ # 编译整个目录
+ $ jade templates
+
+ # 生成 {foo,bar}.html
+ $ jade {foo,bar}.jade
+
+ # 在标准IO下使用jade
+ $ jade < my.jade > my.html
+
+ # 在标准IO下使用jade, 同时指定用于查找包含的文件
+ $ jade < my.jade -p my.jade > my.html
+
+ # 在标准IO下使用jade
+ $ echo "h1 Jade!" | jade
+
+ # foo, bar 目录渲染到 /tmp
+ $ jade foo bar --out /tmp
+
+```
+
+*注意: 从 `v0.31.0` 的 `-o` 选项已经指向 `--out`, `-O` 相应做了交换*
+
+<a name="a18"/>
+## 教程
+
+ - 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)
+ - in [Japanese](http://blog.craftgear.net/4f501e97c1347ec934000001/title/10%E5%88%86%E3%81%A7%E3%82%8F%E3%81%8B%E3%82%8Bjade%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%B3)
+
+<a name="a19"/>
+## License
+
+(The MIT License)
+
+Copyright (c) 2009-2010 TJ Holowaychuk <tj at vision-media.ca>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/bin/jade.js b/bin/jade.js
new file mode 100755
index 0000000..39ec040
--- /dev/null
+++ b/bin/jade.js
@@ -0,0 +1,186 @@
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var fs = require('fs')
+ , program = require('commander')
+ , path = require('path')
+ , basename = path.basename
+ , dirname = path.dirname
+ , resolve = path.resolve
+ , exists = fs.existsSync || path.existsSync
+ , join = path.join
+ , monocle = require('monocle')()
+ , mkdirp = require('mkdirp')
+ , jade = require('../');
+
+// jade options
+
+var options = {};
+
+// options
+
+program
+ .version(require('../package.json').version)
+ .usage('[options] [dir|file ...]')
+ .option('-O, --obj <str>', 'javascript options object')
+ .option('-o, --out <dir>', 'output the compiled html to <dir>')
+ .option('-p, --path <path>', 'filename used to resolve includes')
+ .option('-P, --pretty', 'compile pretty html output')
+ .option('-c, --client', 'compile function for client-side runtime.js')
+ .option('-D, --no-debug', 'compile without debugging (smaller functions)')
+ .option('-w, --watch', 'watch files for changes and automatically re-render')
+
+program.on('--help', function(){
+ console.log(' Examples:');
+ console.log('');
+ console.log(' # translate jade the templates dir');
+ console.log(' $ jade templates');
+ console.log('');
+ console.log(' # create {foo,bar}.html');
+ console.log(' $ jade {foo,bar}.jade');
+ console.log('');
+ console.log(' # jade over stdio');
+ console.log(' $ jade < my.jade > my.html');
+ console.log('');
+ console.log(' # jade over stdio');
+ console.log(' $ echo \'h1 Jade!\' | jade');
+ console.log('');
+ console.log(' # foo, bar dirs rendering to /tmp');
+ console.log(' $ jade foo bar --out /tmp ');
+ console.log('');
+});
+
+program.parse(process.argv);
+
+// options given, parse them
+
+if (program.obj) {
+ if (exists(program.obj)) {
+ options = JSON.parse(fs.readFileSync(program.obj));
+ } else {
+ options = eval('(' + program.obj + ')');
+ }
+}
+
+// --filename
+
+if (program.path) options.filename = program.path;
+
+// --no-debug
+
+options.compileDebug = program.debug;
+
+// --client
+
+options.client = program.client;
+
+// --pretty
+
+options.pretty = program.pretty;
+
+// --watch
+
+options.watch = program.watch;
+
+// left-over args are file paths
+
+var files = program.args;
+
+// compile files
+
+if (files.length) {
+ console.log();
+ files.forEach(renderFile);
+ if (options.watch) {
+ monocle.watchFiles({
+ files: files,
+ listener: function(file) {
+ renderFile(file.absolutePath);
+ }
+ });
+ }
+ process.on('exit', function () {
+ console.log();
+ });
+// stdio
+} else {
+ stdin();
+}
+
+/**
+ * Compile from stdin.
+ */
+
+function stdin() {
+ var buf = '';
+ process.stdin.setEncoding('utf8');
+ process.stdin.on('data', function(chunk){ buf += chunk; });
+ process.stdin.on('end', function(){
+ var output;
+ if (options.client) {
+ output = jade.compileClient(buf, options);
+ } else {
+ var fn = jade.compile(buf, options);
+ var output = fn(options);
+ }
+ process.stdout.write(output);
+ }).resume();
+
+ process.on('SIGINT', function() {
+ process.stdout.write('\n');
+ process.stdin.emit('end');
+ process.stdout.write('\n');
+ process.exit();
+ })
+}
+
+/**
+ * Process the given path, compiling the jade files found.
+ * Always walk the subdirectories.
+ */
+
+function renderFile(path) {
+ var re = /\.jade$/;
+ fs.lstat(path, function(err, stat) {
+ if (err) throw err;
+ // Found jade file
+ if (stat.isFile() && re.test(path)) {
+ fs.readFile(path, 'utf8', function(err, str){
+ if (err) throw err;
+ options.filename = path;
+ var fn = options.client ? jade.compileClient(str, options) : jade.compile(str, options);
+ var extname = options.client ? '.js' : '.html';
+ path = path.replace(re, extname);
+ if (program.out) path = join(program.out, basename(path));
+ var dir = resolve(dirname(path));
+ mkdirp(dir, 0755, function(err){
+ if (err) throw err;
+ try {
+ var output = options.client ? fn : fn(options);
+ fs.writeFile(path, output, function(err){
+ if (err) throw err;
+ console.log(' \033[90mrendered \033[36m%s\033[0m', path);
+ });
+ } catch (e) {
+ if (options.watch) {
+ console.error(e.stack || e.message || e);
+ } else {
+ throw e
+ }
+ }
+ });
+ });
+ // Found directory
+ } else if (stat.isDirectory()) {
+ fs.readdir(path, function(err, files) {
+ if (err) throw err;
+ files.map(function(filename) {
+ return path + '/' + filename;
+ }).forEach(renderFile);
+ });
+ }
+ });
+}
diff --git a/component.json b/component.json
new file mode 100644
index 0000000..392cc42
--- /dev/null
+++ b/component.json
@@ -0,0 +1,16 @@
+{
+ "name": "jade",
+ "repo": "visionmedia/jade",
+ "description": "Jade template runtime",
+ "version": "1.3.1",
+ "keywords": [
+ "template"
+ ],
+ "dependencies": {},
+ "development": {},
+ "license": "MIT",
+ "scripts": [
+ "lib/runtime.js"
+ ],
+ "main": "lib/runtime.js"
+}
\ No newline at end of file
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 0000000..265bce2
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,5 @@
+The examples in this directory can be run simply by something like.
+
+ node attributes.js
+
+You can also open `browser.html` in a browser.
diff --git a/examples/attributes.jade b/examples/attributes.jade
new file mode 100644
index 0000000..b8baa5f
--- /dev/null
+++ b/examples/attributes.jade
@@ -0,0 +1,8 @@
+div#id.left.container(class='user user-' + name)
+ h1.title= name
+ form
+ //- unbuffered comment :)
+ // An example of attributes.
+ input(type='text' name='user[name]' value=name)
+ input(checked, type='checkbox', name='user[blocked]')
+ input(type='submit', value='Update')
\ No newline at end of file
diff --git a/examples/attributes.js b/examples/attributes.js
new file mode 100644
index 0000000..f4dd730
--- /dev/null
+++ b/examples/attributes.js
@@ -0,0 +1,11 @@
+
+/**
+ * Module dependencies.
+ */
+
+var jade = require('./../')
+ , path = __dirname + '/attributes.jade'
+ , str = require('fs').readFileSync(path, 'utf8')
+ , fn = jade.compile(str, { filename: path, pretty: true });
+
+console.log(fn({ name: 'tj' }));
\ No newline at end of file
diff --git a/examples/code.jade b/examples/code.jade
new file mode 100644
index 0000000..8904c4d
--- /dev/null
+++ b/examples/code.jade
@@ -0,0 +1,12 @@
+
+- var title = "Things"
+
+h1= title
+
+ul#users
+ each user, name in users
+ // expands to if (user.isA == 'ferret')
+ if user.isA == 'ferret'
+ li(class='user-' + name) #{name} is just a ferret
+ else
+ li(class='user-' + name) #{name} #{user.email}
\ No newline at end of file
diff --git a/examples/code.js b/examples/code.js
new file mode 100644
index 0000000..9a35d1f
--- /dev/null
+++ b/examples/code.js
@@ -0,0 +1,16 @@
+
+/**
+ * Module dependencies.
+ */
+
+var jade = require('./../')
+ , path = __dirname + '/code.jade'
+ , str = require('fs').readFileSync(path, 'utf8')
+ , fn = jade.compile(str, { filename: path, pretty: true });
+
+var users = {
+ tj: { age: 23, email: 'tj at vision-media.ca', isA: 'human' },
+ tobi: { age: 1, email: 'tobi at is-amazing.com', isA: 'ferret' }
+};
+
+console.log(fn({ users: users }));
\ No newline at end of file
diff --git a/examples/csrf.jade b/examples/csrf.jade
new file mode 100644
index 0000000..283986b
--- /dev/null
+++ b/examples/csrf.jade
@@ -0,0 +1,5 @@
+
+form(method='post', action='/')
+ input(type='text', name='user[name]')
+ input(type='text', name='user[email]')
+ input(type='submit', value='Submit')
diff --git a/examples/csrf.js b/examples/csrf.js
new file mode 100644
index 0000000..21abf60
--- /dev/null
+++ b/examples/csrf.js
@@ -0,0 +1,42 @@
+
+
+/**
+ * Module dependencies.
+ */
+
+var jade = require('./../lib/jade'),
+ Compiler = jade.Compiler,
+ nodes = jade.nodes;
+
+var options = {
+ compiler: CSRF
+ , locals: {
+ csrf: 'WAHOOOOOO'
+ }
+};
+
+jade.renderFile(__dirname + '/csrf.jade', options, function(err, html){
+ if (err) throw err;
+ console.log(html);
+});
+
+function CSRF(node, options) {
+ Compiler.call(this, node, options);
+}
+
+CSRF.prototype.__proto__ = Compiler.prototype;
+
+CSRF.prototype.visitTag = function(node){
+ var parent = Compiler.prototype.visitTag;
+ switch (node.name) {
+ case 'form':
+ if ("'post'" == node.getAttribute('method')) {
+ var tok = new nodes.Tag('input');
+ tok.setAttribute('type', '"hidden"');
+ tok.setAttribute('name', '"csrf"');
+ tok.setAttribute('value', 'csrf');
+ node.block.unshift(tok);
+ }
+ }
+ parent.call(this, node);
+};
diff --git a/examples/dynamicscript.jade b/examples/dynamicscript.jade
new file mode 100644
index 0000000..4e1c895
--- /dev/null
+++ b/examples/dynamicscript.jade
@@ -0,0 +1,5 @@
+html
+ head
+ title Dynamic Inline JavaScript
+ script.
+ var users = !{JSON.stringify(users).replace(/<\//g, "<\\/")}
diff --git a/examples/dynamicscript.js b/examples/dynamicscript.js
new file mode 100644
index 0000000..d2b3a0d
--- /dev/null
+++ b/examples/dynamicscript.js
@@ -0,0 +1,20 @@
+
+/**
+ * Module dependencies.
+ */
+
+var jade = require('./../lib/jade');
+
+var options = {
+ locals: {
+ users: {
+ tj: { age: 23, email: 'tj at vision-media.ca', isA: 'human' },
+ tobi: { age: 1, email: 'tobi at is-amazing.com', isA: 'ferret' }
+ }
+ }
+};
+
+jade.renderFile(__dirname + '/dynamicscript.jade', options, function(err, html){
+ if (err) throw err;
+ console.log(html);
+});
\ No newline at end of file
diff --git a/examples/each.jade b/examples/each.jade
new file mode 100644
index 0000000..206c740
--- /dev/null
+++ b/examples/each.jade
@@ -0,0 +1,3 @@
+ul#users
+ each user, name in users
+ li(class='user-' + name) #{name} #{user.email}
\ No newline at end of file
diff --git a/examples/each.js b/examples/each.js
new file mode 100644
index 0000000..0f3cce5
--- /dev/null
+++ b/examples/each.js
@@ -0,0 +1,16 @@
+
+/**
+ * Module dependencies.
+ */
+
+var jade = require('./../')
+ , path = __dirname + '/each.jade'
+ , str = require('fs').readFileSync(path, 'utf8')
+ , fn = jade.compile(str, { filename: path, pretty: true });
+
+var users = {
+ tj: { age: 23, email: 'tj at vision-media.ca', isA: 'human' },
+ tobi: { age: 1, email: 'tobi at is-amazing.com', isA: 'ferret' }
+};
+
+console.log(fn({ users: users }));
\ No newline at end of file
diff --git a/examples/extend-layout.jade b/examples/extend-layout.jade
new file mode 100644
index 0000000..0767f5f
--- /dev/null
+++ b/examples/extend-layout.jade
@@ -0,0 +1,10 @@
+html
+ head
+ h1 My Site - #{title}
+ block scripts
+ script(src='/jquery.js')
+ body
+ block content
+ block foot
+ #footer
+ p some footer content
\ No newline at end of file
diff --git a/examples/extend.jade b/examples/extend.jade
new file mode 100644
index 0000000..74ea8d8
--- /dev/null
+++ b/examples/extend.jade
@@ -0,0 +1,11 @@
+
+extends extend-layout
+
+block scripts
+ script(src='/jquery.js')
+ script(src='/pets.js')
+
+block content
+ h1= title
+ each pet in pets
+ include pet
\ No newline at end of file
diff --git a/examples/extend.js b/examples/extend.js
new file mode 100644
index 0000000..604798d
--- /dev/null
+++ b/examples/extend.js
@@ -0,0 +1,18 @@
+
+/**
+ * Module dependencies.
+ */
+
+var jade = require('./../')
+ , path = __dirname + '/extend.jade'
+ , str = require('fs').readFileSync(path, 'utf8')
+ , fn = jade.compile(str, { filename: path, pretty: true });
+
+var tobi = { name: 'tobi', age: 2 };
+var loki = { name: 'loki', age: 1 };
+var jane = { name: 'jane', age: 5 };
+
+console.log(fn({
+ title: 'pets'
+ , pets: [tobi, loki, jane]
+}));
\ No newline at end of file
diff --git a/examples/form.jade b/examples/form.jade
new file mode 100644
index 0000000..afe3249
--- /dev/null
+++ b/examples/form.jade
@@ -0,0 +1,29 @@
+form(method="post")
+ fieldset
+ legend General
+ p
+ label(for="user[name]") Username:
+ input(type="text", name="user[name]", value=user.name)
+ p
+ label(for="user[email]") Email:
+ input(type="text", name="user[email]", value=user.email)
+ .tip.
+ Enter a valid
+ email address
+ such as <em>tj at vision-media.ca</em>.
+ fieldset
+ legend Location
+ p
+ label(for="user[city]") City:
+ input(type="text", name="user[city]", value=user.city)
+ p
+ select(name="user[province]")
+ option(value="") -- Select Province --
+ option(value="AB") Alberta
+ option(value="BC") British Columbia
+ option(value="SK") Saskatchewan
+ option(value="MB") Manitoba
+ option(value="ON") Ontario
+ option(value="QC") Quebec
+ p.buttons
+ input(type="submit", value="Save")
\ No newline at end of file
diff --git a/examples/form.js b/examples/form.js
new file mode 100644
index 0000000..dc2b8be
--- /dev/null
+++ b/examples/form.js
@@ -0,0 +1,18 @@
+
+/**
+ * Module dependencies.
+ */
+
+var jade = require('../')
+ , path = __dirname + '/form.jade'
+ , str = require('fs').readFileSync(path, 'utf8')
+ , fn = jade.compile(str, { filename: path, pretty: true });
+
+var user = {
+ name: 'TJ',
+ email: 'tj at vision-media.ca',
+ city: 'Victoria',
+ province: 'BC'
+};
+
+console.log(fn({ user: user }));
\ No newline at end of file
diff --git a/examples/includes.jade b/examples/includes.jade
new file mode 100644
index 0000000..93ce421
--- /dev/null
+++ b/examples/includes.jade
@@ -0,0 +1,7 @@
+
+html
+ include includes/head
+ body
+ h1 My Site
+ p Welcome to my super lame site.
+ include includes/foot
\ No newline at end of file
diff --git a/examples/includes.js b/examples/includes.js
new file mode 100644
index 0000000..6c062d6
--- /dev/null
+++ b/examples/includes.js
@@ -0,0 +1,11 @@
+
+/**
+ * Module dependencies.
+ */
+
+var jade = require('./../')
+ , path = __dirname + '/includes.jade'
+ , str = require('fs').readFileSync(path, 'utf8')
+ , fn = jade.compile(str, { filename: path, pretty: true });
+
+console.log(fn());
\ No newline at end of file
diff --git a/examples/includes/foot.jade b/examples/includes/foot.jade
new file mode 100644
index 0000000..b7f5889
--- /dev/null
+++ b/examples/includes/foot.jade
@@ -0,0 +1,2 @@
+#footer
+ p Copyright (c) foobar
\ No newline at end of file
diff --git a/examples/includes/head.jade b/examples/includes/head.jade
new file mode 100644
index 0000000..6b297fc
--- /dev/null
+++ b/examples/includes/head.jade
@@ -0,0 +1,6 @@
+head
+ title My Site
+ // including other jade works
+ include scripts
+ // including .html, .css, etc works
+ include style.css
\ No newline at end of file
diff --git a/examples/includes/scripts.jade b/examples/includes/scripts.jade
new file mode 100644
index 0000000..2f478fc
--- /dev/null
+++ b/examples/includes/scripts.jade
@@ -0,0 +1,2 @@
+script(src='/javascripts/jquery.js')
+script(src='/javascripts/app.js')
diff --git a/examples/includes/style.css b/examples/includes/style.css
new file mode 100644
index 0000000..950708e
--- /dev/null
+++ b/examples/includes/style.css
@@ -0,0 +1,5 @@
+<style>
+ body {
+ padding: 50px;
+ }
+</style>
\ No newline at end of file
diff --git a/examples/layout-debug.js b/examples/layout-debug.js
new file mode 100644
index 0000000..ca0d03b
--- /dev/null
+++ b/examples/layout-debug.js
@@ -0,0 +1,11 @@
+
+/**
+ * Module dependencies.
+ */
+
+var jade = require('./../lib/jade');
+
+jade.renderFile(__dirname + '/layout.jade', { debug: true }, function(err, html){
+ if (err) throw err;
+ console.log(html);
+});
\ No newline at end of file
diff --git a/examples/layout.jade b/examples/layout.jade
new file mode 100644
index 0000000..7affb91
--- /dev/null
+++ b/examples/layout.jade
@@ -0,0 +1,14 @@
+doctype html
+html(lang="en")
+ head
+ title Example
+ script.
+ if (foo) {
+ bar();
+ }
+ body
+ h1 Jade - node template engine
+ #container
+ :markdown
+ Jade is a _high performance_ template engine for [node](http://nodejs.org),
+ inspired by [haml](http://haml-lang.com/), and written by [TJ Holowaychuk](http://github.com/visionmedia).
\ No newline at end of file
diff --git a/examples/layout.js b/examples/layout.js
new file mode 100644
index 0000000..ab20635
--- /dev/null
+++ b/examples/layout.js
@@ -0,0 +1,11 @@
+
+/**
+ * Module dependencies.
+ */
+
+var jade = require('./../')
+ , path = __dirname + '/layout.jade'
+ , str = require('fs').readFileSync(path, 'utf8')
+ , fn = jade.compile(str, { filename: path, pretty: true });
+
+console.log(fn());
\ No newline at end of file
diff --git a/examples/mixins.jade b/examples/mixins.jade
new file mode 100644
index 0000000..49603a9
--- /dev/null
+++ b/examples/mixins.jade
@@ -0,0 +1,16 @@
+
+include mixins/dialog
+include mixins/profile
+
+.one
+ mixin dialog
+
+.two
+ mixin dialog-title('Whoop')
+
+.three
+ mixin dialog-title-desc('Whoop', 'Just a mixin')
+
+
+#profile
+ mixin profile(user)
diff --git a/examples/mixins.js b/examples/mixins.js
new file mode 100644
index 0000000..6b8d64d
--- /dev/null
+++ b/examples/mixins.js
@@ -0,0 +1,16 @@
+
+/**
+ * Module dependencies.
+ */
+
+var jade = require('./../')
+ , path = __dirname + '/mixins.jade'
+ , str = require('fs').readFileSync(path, 'utf8')
+ , fn = jade.compile(str, { filename: path, pretty: true });
+
+var user = {
+ name: 'tj'
+ , pets: ['tobi', 'loki', 'jane', 'manny']
+};
+
+console.log(fn({ user: user }));
\ No newline at end of file
diff --git a/examples/mixins/dialog.jade b/examples/mixins/dialog.jade
new file mode 100644
index 0000000..ba2ec93
--- /dev/null
+++ b/examples/mixins/dialog.jade
@@ -0,0 +1,15 @@
+
+mixin dialog
+ .dialog
+ h1 Whoop
+ p stuff
+
+mixin dialog-title(title)
+ .dialog
+ h1= title
+ p stuff
+
+mixin dialog-title-desc(title, desc)
+ .dialog
+ h1= title
+ p= desc
diff --git a/examples/mixins/profile.jade b/examples/mixins/profile.jade
new file mode 100644
index 0000000..b1542dd
--- /dev/null
+++ b/examples/mixins/profile.jade
@@ -0,0 +1,10 @@
+
+mixin pets(pets)
+ ul.pets
+ - each pet in pets
+ li= pet
+
+mixin profile(user)
+ .user
+ h2= user.name
+ mixin pets(user.pets)
\ No newline at end of file
diff --git a/examples/pet.jade b/examples/pet.jade
new file mode 100644
index 0000000..e5dbab9
--- /dev/null
+++ b/examples/pet.jade
@@ -0,0 +1,3 @@
+.pet
+ h2= pet.name
+ p #{pet.name} is <em>#{pet.age}</em> year(s) old.
\ No newline at end of file
diff --git a/examples/rss.jade b/examples/rss.jade
new file mode 100644
index 0000000..165dffb
--- /dev/null
+++ b/examples/rss.jade
@@ -0,0 +1,14 @@
+doctype xml
+rss(version='2.0')
+channel
+ title RSS Title
+ description Some description here
+ link http://google.com
+ lastBuildDate Mon, 06 Sep 2010 00:01:00 +0000
+ pubDate Mon, 06 Sep 2009 16:45:00 +0000
+
+ each item in items
+ item
+ title= item.title
+ description= item.description
+ link= item.link
diff --git a/examples/rss.js b/examples/rss.js
new file mode 100644
index 0000000..861986a
--- /dev/null
+++ b/examples/rss.js
@@ -0,0 +1,17 @@
+
+/**
+ * Module dependencies.
+ */
+
+var jade = require('./../')
+ , path = __dirname + '/rss.jade'
+ , str = require('fs').readFileSync(path, 'utf8')
+ , fn = jade.compile(str, { filename: path, pretty: true });
+
+var items = [];
+
+items.push({ title: 'Example', description: 'Something', link: 'http://google.com' });
+items.push({ title: 'LearnBoost', description: 'Cool', link: 'http://learnboost.com' });
+items.push({ title: 'Express', description: 'Cool', link: 'http://expressjs.com' });
+
+console.log(fn({ items: items }));
\ No newline at end of file
diff --git a/examples/text.jade b/examples/text.jade
new file mode 100644
index 0000000..c18e7e0
--- /dev/null
+++ b/examples/text.jade
@@ -0,0 +1,36 @@
+| An example of an
+a(href='#') inline
+| link.
+
+form
+ label Username:
+ input(type='text', name='user[name]')
+ p
+ | Just an example of some text usage.
+ | You can have <em>inline</em> html,
+ | as well as
+ strong tags
+ | .
+
+ | Interpolation is also supported. The
+ | username is currently "#{name}".
+
+ label Email:
+ input(type='text', name='user[email]')
+ p
+ | Email is currently
+ em= email
+ | .
+
+ // alternatively, if we plan on having only
+ // text or inline-html, we can use a trailing
+ // "." to let jade know we want to omit pipes
+
+ label Username:
+ input(type='text')
+ p.
+ Just an example, like before
+ however now we can omit those
+ annoying pipes!.
+
+ Wahoo.
\ No newline at end of file
diff --git a/examples/text.js b/examples/text.js
new file mode 100644
index 0000000..503d563
--- /dev/null
+++ b/examples/text.js
@@ -0,0 +1,11 @@
+
+/**
+ * Module dependencies.
+ */
+
+var jade = require('./../')
+ , path = __dirname + '/text.jade'
+ , str = require('fs').readFileSync(path, 'utf8')
+ , fn = jade.compile(str, { filename: path, pretty: true });
+
+console.log(fn({ name: 'tj', email: 'tj at vision-media.ca' }));
\ No newline at end of file
diff --git a/examples/whitespace.jade b/examples/whitespace.jade
new file mode 100644
index 0000000..ae7ebd9
--- /dev/null
+++ b/examples/whitespace.jade
@@ -0,0 +1,11 @@
+- var js = '<script></script>'
+doctype html
+html
+
+ head
+ title= "Some " + "JavaScript"
+ != js
+
+
+
+ body
\ No newline at end of file
diff --git a/examples/whitespace.js b/examples/whitespace.js
new file mode 100644
index 0000000..1651d13
--- /dev/null
+++ b/examples/whitespace.js
@@ -0,0 +1,11 @@
+
+/**
+ * Module dependencies.
+ */
+
+var jade = require('./../')
+ , path = __dirname + '/whitespace.jade'
+ , str = require('fs').readFileSync(path, 'utf8')
+ , fn = jade.compile(str, { filename: path, pretty: true });
+
+console.log(fn());
\ No newline at end of file
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..cf7d1a6
--- /dev/null
+++ b/index.js
@@ -0,0 +1,2 @@
+
+module.exports = require('./lib/jade');
diff --git a/jade-language.md b/jade-language.md
new file mode 100644
index 0000000..a5960fd
--- /dev/null
+++ b/jade-language.md
@@ -0,0 +1,1023 @@
+
+<a name="a6"/>
+## Syntax
+
+<a name="a6-2"/>
+### Tags
+
+A tag is simply a leading word:
+
+```jade
+html
+```
+
+for example is converted to `<html></html>`
+
+tags can also have ids:
+
+```jade
+div#container
+```
+
+which would render `<div id="container"></div>`
+
+how about some classes?
+
+```jade
+div.user-details
+```
+
+renders `<div class="user-details"></div>`
+
+multiple classes? _and_ an id? sure:
+
+```jade
+div#foo.bar.baz
+```
+
+renders `<div id="foo" class="bar baz"></div>`
+
+div div div sure is annoying, how about:
+
+```jade
+#foo
+.bar
+```
+
+which is syntactic sugar for what we have already been doing, and outputs:
+
+```html
+<div id="foo"></div><div class="bar"></div>
+```
+
+<a name="a6-3"/>
+### Tag Text
+
+Simply place some content after the tag:
+
+```jade
+p wahoo!
+```
+
+renders `<p>wahoo!</p>`.
+
+well cool, but how about large bodies of text:
+
+```jade
+p
+ | foo bar baz
+ | rawr rawr
+ | super cool
+ | go jade go
+```
+
+renders `<p>foo bar baz rawr.....</p>`
+
+interpolation? yup! both types of text can utilize interpolation,
+if we passed `{ name: 'tj', email: 'tj at vision-media.ca' }` to the compiled function we can do the following:
+
+```jade
+#user #{name} <#{email}>
+```
+
+outputs `<div id="user">tj <tj at vision-media.ca></div>`
+
+Actually want `#{}` for some reason? escape it!
+
+```jade
+p \#{something}
+```
+
+now we have `<p>#{something}</p>`
+
+We can also utilize the unescaped variant `!{html}`, so the following
+will result in a literal script tag:
+
+```jade
+- var html = "<script></script>"
+| !{html}
+```
+
+Nested tags that also contain text can optionally use a text block:
+
+```jade
+label
+ | Username:
+ input(name='user[name]')
+```
+
+or immediate tag text:
+
+```jade
+label Username:
+ input(name='user[name]')
+```
+
+As an alternative, we may use a trailing `.` to indicate a text block, for example:
+
+```jade
+p.
+ foo asdf
+ asdf
+ asdfasdfaf
+ asdf
+ asd.
+```
+
+outputs:
+
+```html
+<p>foo asdf
+asdf
+ asdfasdfaf
+ asdf
+asd.
+</p>
+```
+
+This however differs from a trailing `.` followed by a space, which although is ignored by the Jade parser, tells Jade that this period is a literal:
+
+```jade
+p .
+```
+
+outputs:
+
+```html
+<p>.</p>
+```
+
+It should be noted that text blocks should be doubled escaped. For example if you desire the following output.
+
+```html
+<p>foo\bar</p>
+```
+
+use:
+
+```jade
+p.
+ foo\\bar
+```
+
+<a name="a6-4"/>
+### Comments
+
+Single line comments currently look the same as JavaScript comments,
+aka `//` and must be placed on their own line:
+
+```jade
+// just some paragraphs
+p foo
+p bar
+```
+
+would output
+
+```html
+<!-- just some paragraphs -->
+<p>foo</p>
+<p>bar</p>
+```
+
+Jade also supports unbuffered comments, by simply adding a hyphen:
+
+```jade
+//- will not output within markup
+p foo
+p bar
+```
+
+outputting
+
+```html
+<p>foo</p>
+<p>bar</p>
+```
+
+<a name="a6-5"/>
+### Block Comments
+
+ A block comment is legal as well:
+
+```jade
+body
+ //
+ #content
+ h1 Example
+```
+
+outputting
+
+```html
+<body>
+ <!--
+ <div id="content">
+ <h1>Example</h1>
+ </div>
+ -->
+</body>
+```
+
+Jade supports conditional-comments as well, for example:
+
+```jade
+head
+ //if lt IE 8
+ script(src='/ie-sucks.js')
+```
+
+outputs:
+
+```html
+<head>
+ <!--[if lt IE 8]>
+ <script src="/ie-sucks.js"></script>
+ <![endif]-->
+</head>
+```
+
+<a name="a6-6"/>
+### Nesting
+
+ Jade supports nesting to define the tags in a natural way:
+
+```jade
+ul
+ li.first
+ a(href='#') foo
+ li
+ a(href='#') bar
+ li.last
+ a(href='#') baz
+```
+
+<a name="a6-7"/>
+### Block Expansion
+
+ Block expansion allows you to create terse single-line nested tags,
+ the following example is equivalent to the nesting example above.
+
+```jade
+ul
+ li.first: a(href='#') foo
+ li: a(href='#') bar
+ li.last: a(href='#') baz
+```
+
+<a name="a6-8"/>
+### Case
+
+ The case statement takes the following form:
+
+```jade
+html
+ body
+ friends = 10
+ case friends
+ when 0
+ p you have no friends
+ when 1
+ p you have a friend
+ default
+ p you have #{friends} friends
+```
+
+ Block expansion may also be used:
+
+```jade
+friends = 5
+
+html
+ body
+ case friends
+ when 0: p you have no friends
+ when 1: p you have a friend
+ default: p you have #{friends} friends
+```
+
+<a name="a6-9"/>
+### Attributes
+
+Jade currently supports `(` and `)` as attribute delimiters.
+
+```jade
+a(href='/login', title='View login page') Login
+```
+
+When a value is `undefined` or `null` the attribute is _not_ added,
+so this is fine, it will not compile `something="null"`.
+
+```jade
+div(something=null)
+```
+
+Boolean attributes are also supported:
+
+```jade
+input(type="checkbox", checked)
+```
+
+Boolean attributes with code will only output the attribute when `true`:
+
+```jade
+input(type="checkbox", checked=someValue)
+```
+
+Multiple lines work too:
+
+```jade
+input(type='checkbox',
+ name='agreement',
+ checked)
+```
+
+Multiple lines without the comma work fine:
+
+```jade
+input(type='checkbox'
+ name='agreement'
+ checked)
+```
+
+Funky whitespace? fine:
+
+```jade
+input(
+ type='checkbox'
+ name='agreement'
+ checked)
+```
+
+Colons work:
+
+```jade
+rss(xmlns:atom="atom")
+```
+
+Suppose we have the `user` local `{ id: 12, name: 'tobi' }`
+and we wish to create an anchor tag with `href` pointing to "/user/12"
+we could use regular javascript concatenation:
+
+```jade
+a(href='/user/' + user.id)= user.name
+```
+
+or we could use jade's interpolation, which I added because everyone
+using Ruby or CoffeeScript seems to think this is legal js..:
+
+```jade
+a(href='/user/#{user.id}')= user.name
+```
+
+The `class` attribute is special-cased when an array is given,
+allowing you to pass an array such as `bodyClasses = ['user', 'authenticated']` directly:
+
+```jade
+body(class=bodyClasses)
+```
+
+<a name="a6-10"/>
+### HTML
+
+ Inline html is fine, we can use the pipe syntax to
+ write arbitrary text, in this case some html:
+
+```jade
+html
+ body
+ | <h1>Title</h1>
+ | <p>foo bar baz</p>
+```
+
+ Or we can use the trailing `.` to indicate to Jade that we
+ only want text in this block, allowing us to omit the pipes:
+
+```jade
+html
+ body.
+ <h1>Title</h1>
+ <p>foo bar baz</p>
+```
+
+ Both of these examples yield the same result:
+
+```html
+<html><body><h1>Title</h1>
+<p>foo bar baz</p>
+</body></html>
+```
+
+ The same rule applies for anywhere you can have text
+ in jade, raw html is fine:
+
+```jade
+html
+ body
+ h1 User <em>#{name}</em>
+```
+
+<a name="a6-11"/>
+### Doctypes
+
+To add a doctype simply use `!!!`, or `doctype` followed by an optional value:
+
+```jade
+!!!
+```
+
+or
+
+```jade
+doctype
+```
+
+Will output the _html 5_ doctype, however:
+
+```jade
+!!! transitional
+```
+
+Will output the _transitional_ doctype.
+
+Doctypes are case-insensitive, so the following are equivalent:
+
+```jade
+doctype Basic
+doctype basic
+```
+
+it's also possible to simply pass a doctype literal:
+
+```jade
+doctype html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN"
+```
+
+yielding:
+
+```html
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN">
+```
+
+Below are the doctypes defined by default, which can easily be extended:
+
+```js
+var doctypes = exports.doctypes = {
+ '5': '<!DOCTYPE html>',
+ 'default': '<!DOCTYPE html>',
+ 'xml': '<?xml version="1.0" encoding="utf-8" ?>',
+ 'transitional': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
+ 'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
+ 'frameset': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
+ '1.1': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
+ 'basic': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">',
+ 'mobile': '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
+};
+```
+
+To alter the default simply change:
+
+```js
+jade.doctypes.default = 'whatever you want';
+```
+
+<a name="a7"/>
+## Filters
+
+Filters are prefixed with `:`, for example `:markdown` and
+pass the following block of text to an arbitrary function for processing. View the _features_
+at the top of this document for available filters.
+
+```jade
+body
+ :markdown
+ Woah! jade _and_ markdown, very **cool**
+ we can even link to [stuff](http://google.com)
+```
+
+Renders:
+
+```html
+<body><p>Woah! jade <em>and</em> markdown, very <strong>cool</strong> we can even link to <a href="http://google.com">stuff</a></p></body>
+```
+
+<a name="a8"/>
+## Code
+
+Jade currently supports three classifications of executable code. The first
+is prefixed by `-`, and is not buffered:
+
+```jade
+- var foo = 'bar';
+```
+
+This can be used for conditionals, or iteration:
+
+```jade
+- for (var key in obj)
+ p= obj[key]
+```
+
+Due to Jade's buffering techniques the following is valid as well:
+
+```jade
+- if (foo)
+ ul
+ li yay
+ li foo
+ li worked
+- else
+ p oh no! didnt work
+```
+
+Hell, even verbose iteration:
+
+```jade
+- if (items.length)
+ ul
+ - items.forEach(function(item){
+ li= item
+ - })
+```
+
+Anything you want!
+
+Next up we have _escaped_ buffered code, which is used to
+buffer a return value, which is prefixed by `=`:
+
+```jade
+- var foo = 'bar'
+= foo
+h1= foo
+```
+
+Which outputs `bar<h1>bar</h1>`. Code buffered by `=` is escaped
+by default for security, however to output unescaped return values
+you may use `!=`:
+
+```jade
+p!= aVarContainingMoreHTML
+```
+
+ Jade also has designer-friendly variants, making the literal JavaScript
+ more expressive and declarative. For example the following assignments
+ are equivalent, and the expression is still regular javascript:
+
+```jade
+- var foo = 'foo ' + 'bar'
+foo = 'foo ' + 'bar'
+```
+
+ Likewise Jade has first-class `if`, `else if`, `else`, `until`, `while`, `unless` among others, however you must remember that the expressions are still regular javascript:
+
+```jade
+if foo == 'bar'
+ ul
+ li yay
+ li foo
+ li worked
+else
+ p oh no! didnt work
+```
+
+<a name="a9"/>
+## Iteration
+
+ Along with vanilla JavaScript Jade also supports a subset of
+ constructs that allow you to create more designer-friendly templates,
+ one of these constructs is `each`, taking the form:
+
+```jade
+each VAL[, KEY] in OBJ
+```
+
+An example iterating over an array:
+
+```jade
+- var items = ["one", "two", "three"]
+each item in items
+ li= item
+```
+
+outputs:
+
+```html
+<li>one</li>
+<li>two</li>
+<li>three</li>
+```
+
+iterating an array with index:
+
+```jade
+items = ["one", "two", "three"]
+each item, i in items
+ li #{item}: #{i}
+```
+
+outputs:
+
+```html
+<li>one: 0</li>
+<li>two: 1</li>
+<li>three: 2</li>
+```
+
+iterating an object's keys and values:
+
+```jade
+obj = { foo: 'bar' }
+each val, key in obj
+ li #{key}: #{val}
+```
+
+would output `<li>foo: bar</li>`
+
+Internally Jade converts these statements to regular
+JavaScript loops such as `users.forEach(function(user){`,
+so lexical scope and nesting applies as it would with regular
+JavaScript:
+
+```jade
+each user in users
+ each role in user.roles
+ li= role
+```
+
+ You may also use `for` if you prefer:
+
+```jade
+for user in users
+ for role in user.roles
+ li= role
+```
+
+<a name="a10"/>
+## Conditionals
+
+ Jade conditionals are equivalent to those using the code (`-`) prefix,
+ however allow you to ditch parenthesis to become more designer friendly,
+ however keep in mind the expression given is _regular_ JavaScript:
+
+```jade
+for user in users
+ if user.role == 'admin'
+ p #{user.name} is an admin
+ else
+ p= user.name
+```
+
+ is equivalent to the following using vanilla JavaScript literals:
+
+```jade
+for user in users
+ - if (user.role == 'admin')
+ p #{user.name} is an admin
+ - else
+ p= user.name
+```
+
+ Jade also provides `unless` which is equivalent to `if (!(expr))`:
+
+```jade
+for user in users
+ unless user.isAnonymous
+ p
+ | Click to view
+ a(href='/users/' + user.id)= user.name
+```
+
+<a name="a11"/>
+## Template inheritance
+
+ Jade supports template inheritance via the `block` and `extends` keywords. A block is simply a "block" of Jade that may be replaced within a child template, this process is recursive. To activate template inheritance in Express 2.x you must add: `app.set('view options', { layout: false });`.
+
+ Jade blocks can provide default content if desired, however optional as shown below by `block scripts`, `block content`, and `block foot`.
+
+```jade
+html
+ head
+ title My Site - #{title}
+ block scripts
+ script(src='/jquery.js')
+ body
+ block content
+ block foot
+ #footer
+ p some footer content
+```
+
+ Now to extend the layout, simply create a new file and use the `extends` directive as shown below, giving the path (with or without the .jade extension). You may now define one or more blocks that will override the parent block content, note that here the `foot` block is _not_ redefined and will output "some footer content".
+
+```jade
+extends layout
+
+block scripts
+ script(src='/jquery.js')
+ script(src='/pets.js')
+
+block content
+ h1= title
+ each pet in pets
+ include pet
+```
+
+ It's also possible to override a block to provide additional blocks, as shown in the following example where `content` now exposes a `sidebar` and `primary` block for overriding, or the child template could override `content` all together.
+
+```jade
+extends regular-layout
+
+block content
+ .sidebar
+ block sidebar
+ p nothing
+ .primary
+ block primary
+ p nothing
+```
+
+<a name="a12"/>
+## Block append / prepend
+
+ Jade allows you to _replace_ (default), _prepend_, or _append_ blocks. Suppose for example you have default scripts in a "head" block that you wish to utilize on _every_ page, you might do this:
+
+```jade
+html
+ head
+ block head
+ script(src='/vendor/jquery.js')
+ script(src='/vendor/caustic.js')
+ body
+ block content
+```
+
+ Now suppose you have a page of your application for a JavaScript game, you want some game related scripts as well as these defaults, you can simply `append` the block:
+
+```jade
+extends layout
+
+block append head
+ script(src='/vendor/three.js')
+ script(src='/game.js')
+```
+
+ When using `block append` or `block prepend` the `block` is optional:
+
+```jade
+extends layout
+
+append head
+ script(src='/vendor/three.js')
+ script(src='/game.js')
+```
+
+<a name="a13"/>
+## Includes
+
+ Includes allow you to statically include chunks of Jade,
+ or other content like css, or html which lives in separate files. The classical example is including a header and footer. Suppose we have the following directory structure:
+
+ ./layout.jade
+ ./includes/
+ ./head.jade
+ ./foot.jade
+
+and the following _layout.jade_:
+
+```jade
+html
+ include includes/head
+ body
+ h1 My Site
+ p Welcome to my super amazing site.
+ include includes/foot
+```
+
+both includes _includes/head_ and _includes/foot_ are
+read relative to the `filename` option given to _layout.jade_,
+which should be an absolute path to this file, however Express
+does this for you. Include then parses these files, and injects
+the AST produced to render what you would expect:
+
+```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>
+```
+
+As mentioned `include` can be used to include other content
+such as html or css. By providing an explicit filter name
+with `include:`, Jade will read that file in, apply the
+[filter](#a7), and insert that content into the output.
+
+```jade
+html
+ head
+ //- css and js have simple filters that wrap them in
+ <style> and <script> tags, respectively
+ include stylesheet.css
+ include script.js
+ body
+ //- use the "markdown" filter to convert Markdown to HTML
+ include:markdown introduction.markdown
+ //- html files have no filter and are included verbatim
+ include content.html
+```
+
+ Include directives may also accept a block, in which case the
+ the given block will be appended to the _last_ block defined
+ in the file. For example if `head.jade` contains:
+
+```jade
+head
+ script(src='/jquery.js')
+```
+
+ We may append values by providing a block to `include head`
+ as shown below, adding the two scripts.
+
+```jade
+html
+ include head
+ script(src='/foo.js')
+ script(src='/bar.js')
+ body
+ h1 test
+```
+
+ You may also `yield` within an included template, allowing you to explicitly mark where the block given to `include` will be placed. Suppose for example you wish to prepend scripts rather than append, you might do the following:
+
+```jade
+head
+ yield
+ script(src='/jquery.js')
+ script(src='/jquery.ui.js')
+```
+
+ Since included Jade is parsed and literally merges the AST, lexically scoped variables function as if the included Jade was written right in the same file. This means `include` may be used as sort of partial, for example suppose we have `user.jade` which utilizes a `user` variable.
+
+```jade
+h1= user.name
+p= user.occupation
+```
+
+We could then simply `include user` while iterating users, and since the `user` variable is already defined within the loop the included template will have access to it.
+
+```jade
+users = [{ name: 'Tobi', occupation: 'Ferret' }]
+
+each user in users
+ .user
+ include user
+```
+
+yielding:
+
+```html
+<div class="user">
+ <h1>Tobi</h1>
+ <p>Ferret</p>
+</div>
+```
+
+If we wanted to expose a different variable name as `user` since `user.jade` references that name, we could simply define a new variable as shown here with `user = person`:
+
+```jade
+each person in users
+ .user
+ user = person
+ include user
+```
+
+<a name="a14"/>
+## Mixins
+
+ Mixins are converted to regular JavaScript functions in
+ the compiled template that Jade constructs. Mixins may
+ take arguments, though not required:
+
+```jade
+mixin list
+ ul
+ li foo
+ li bar
+ li baz
+```
+
+ Utilizing a mixin without args looks similar, just without a block:
+
+```jade
+h2 Groceries
+mixin list
+```
+
+ Mixins may take one or more arguments as well, the arguments
+ are regular javascripts expressions, so for example the following:
+
+```jade
+mixin pets(pets)
+ ul.pets
+ - each pet in pets
+ li= pet
+
+mixin profile(user)
+ .user
+ h2= user.name
+ mixin pets(user.pets)
+```
+
+ Would yield something similar to the following html:
+
+```html
+<div class="user">
+ <h2>tj</h2>
+ <ul class="pets">
+ <li>tobi</li>
+ <li>loki</li>
+ <li>jane</li>
+ <li>manny</li>
+ </ul>
+</div>
+```
+
+<a name="a15"/>
+## Generated Output
+
+ Suppose we have the following Jade:
+
+```jade
+- var title = 'yay'
+h1.title #{title}
+p Just an example
+```
+
+ When the `compileDebug` option is not explicitly `false`, Jade
+ will compile the function instrumented with `__.lineno = n;`, which
+ in the event of an exception is passed to `rethrow()` which constructs
+ a useful message relative to the initial Jade input.
+
+```js
+function anonymous(locals) {
+ var __ = { lineno: 1, input: "- var title = 'yay'\nh1.title #{title}\np Just an example", filename: "testing/test.js" };
+ var rethrow = jade.rethrow;
+ try {
+ var attrs = jade.attrs, escape = jade.escape;
+ var buf = [];
+ with (locals || {}) {
+ var interp;
+ __.lineno = 1;
+ var title = 'yay'
+ __.lineno = 2;
+ buf.push('<h1');
+ buf.push(attrs({ "class": ('title') }));
+ buf.push('>');
+ buf.push('' + escape((interp = title) == null ? '' : interp) + '');
+ buf.push('</h1>');
+ __.lineno = 3;
+ buf.push('<p>');
+ buf.push('Just an example');
+ buf.push('</p>');
+ }
+ return buf.join("");
+ } catch (err) {
+ rethrow(err, __.input, __.filename, __.lineno);
+ }
+}
+```
+
+When the `compileDebug` option _is_ explicitly `false`, this instrumentation
+is stripped, which is very helpful for light-weight client-side templates. Combining Jade's options with the `./runtime.js` file in this repo allows you
+to toString() compiled templates and avoid running the entire Jade library on
+the client, increasing performance, and decreasing the amount of JavaScript
+required.
+
+```js
+function anonymous(locals) {
+ var attrs = jade.attrs, escape = jade.escape;
+ var buf = [];
+ with (locals || {}) {
+ var interp;
+ var title = 'yay'
+ buf.push('<h1');
+ buf.push(attrs({ "class": ('title') }));
+ buf.push('>');
+ buf.push('' + escape((interp = title) == null ? '' : interp) + '');
+ buf.push('</h1>');
+ buf.push('<p>');
+ buf.push('Just an example');
+ buf.push('</p>');
+ }
+ return buf.join("");
+}
+```
\ No newline at end of file
diff --git a/jade.md b/jade.md
new file mode 100644
index 0000000..051dc03
--- /dev/null
+++ b/jade.md
@@ -0,0 +1,510 @@
+
+# Jade
+
+ The jade template engine for node.js
+
+## Synopsis
+
+ jade [-h|--help] [-v|--version] [-o|--obj STR]
+ [-O|--out DIR] [-p|--path PATH] [-P|--pretty]
+ [-c|--client] [-D|--no-debug]
+
+## 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 s
+
+ $ echo "h1 Jade!" | jade
+
+ foo, bar dirs rendering to /tmp
+
+ $ jade foo bar --out /tmp
+
+ compile client-side templates without debugging
+ instrumentation, making the output javascript
+ very light-weight. This requires runtime.js
+ in your projects.
+
+ $ jade --client --no-debug < my.jade
+
+## Tags
+
+ Tags are simply nested via whitespace, closing
+ tags defined for you. These indents are called "blocks".
+
+ ul
+ li
+ a Foo
+ li
+ a Bar
+
+ You may have several tags in one "block":
+
+ ul
+ li
+ a Foo
+ a Bar
+ a Baz
+
+## Self-closing Tags
+
+ Some tags are flagged as self-closing by default, such
+ as `meta`, `link`, and so on. To explicitly self-close
+ a tag simply append the `/` character:
+
+ foo/
+ foo(bar='baz')/
+
+ Would yield:
+
+ <foo/>
+ <foo bar="baz"/>
+
+## Attributes
+
+ Tag attributes look similar to HTML, however
+ the values are regular JavaScript, here are
+ some examples:
+
+ a(href='google.com') Google
+ a(class='button', href='google.com') Google
+
+ As mentioned the attribute values are just JavaScript,
+ this means ternary operations and other JavaScript expressions
+ work just fine:
+
+ body(class=user.authenticated ? 'authenticated' : 'anonymous')
+ a(href=user.website || 'http://google.com')
+
+ Multiple lines work too:
+
+ input(type='checkbox',
+ name='agreement',
+ checked)
+
+ Multiple lines without the comma work fine:
+
+ input(type='checkbox'
+ name='agreement'
+ checked)
+
+ Funky whitespace? fine:
+
+ input(
+ type='checkbox'
+ name='agreement'
+ checked)
+
+## Boolean attributes
+
+ Boolean attributes are mirrored by Jade, and accept
+ bools, aka _true_ or _false_. When no value is specified
+ _true_ is assumed. For example:
+
+ input(type="checkbox", checked)
+ // => "<input type="checkbox" checked="checked" />"
+
+ For example if the checkbox was for an agreement, perhaps `user.agreed`
+ was _true_ the following would also output 'checked="checked"':
+
+ input(type="checkbox", checked=user.agreed)
+
+## Class attributes
+
+ The _class_ attribute accepts an array of classes,
+ this can be handy when generated from a javascript
+ function etc:
+
+ classes = ['foo', 'bar', 'baz']
+ a(class=classes)
+ // => "<a class="foo bar baz"></a>"
+
+## Class literal
+
+ Classes may be defined using a ".CLASSNAME" syntax:
+
+ .button
+ // => "<div class="button"></div>"
+
+ Or chained:
+
+ .large.button
+ // => "<div class="large button"></div>"
+
+ The previous defaulted to divs, however you
+ may also specify the tag type:
+
+ h1.title My Title
+ // => "<h1 class="title">My Title</h1>"
+
+## Id literal
+
+ Much like the class literal there's an id literal:
+
+ #user-1
+ // => "<div id="user-1"></div>"
+
+ Again we may specify the tag as well:
+
+ ul#menu
+ li: a(href='/home') Home
+ li: a(href='/store') Store
+ li: a(href='/contact') Contact
+
+ Finally all of these may be used in any combination,
+ the following are all valid tags:
+
+ a.button#contact(style: 'color: red') Contact
+ a.button(style: 'color: red')#contact Contact
+ a(style: 'color: red').button#contact Contact
+
+## Block expansion
+
+ Jade supports the concept of "block expansion", in which
+ using a trailing ":" after a tag will inject a block:
+
+ ul
+ li: a Foo
+ li: a Bar
+ li: a Baz
+
+## Text
+
+ Arbitrary text may follow tags:
+
+ p Welcome to my site
+
+ yields:
+
+ <p>Welcome to my site</p>
+
+## Pipe text
+
+ Another form of text is "pipe" text. Pipes act
+ as the text margin for large bodies of text.
+
+ p
+ | This is a large
+ | body of text for
+ | this tag.
+ |
+ | Nothing too
+ | exciting.
+
+ yields:
+
+ <p>This is a large
+ body of text for
+ this tag.
+
+ Nothing too
+ exciting.
+ </p>
+
+ Using pipes we can also specify regular Jade tags
+ within the text:
+
+ p
+ | Click to visit
+ a(href='http://google.com') Google
+ | if you want.
+
+## Text only tags
+
+ As an alternative to pipe text you may add
+ a trailing "." to indicate that the block
+ contains nothing but plain-text, no tags:
+
+ p.
+ This is a large
+ body of text for
+ this tag.
+
+ Nothing too
+ exciting.
+
+ Some tags are text-only by default, for example
+ _script_, _textarea_, and _style_ tags do not
+ contain nested HTML so Jade implies the trailing ".":
+
+ script
+ if (foo) {
+ bar();
+ }
+
+ style
+ body {
+ padding: 50px;
+ font: 14px Helvetica;
+ }
+
+## Template script tags
+
+ Sometimes it's useful to define HTML in script
+ tags using Jade, typically for client-side templates.
+
+ To do this simply give the _script_ tag an arbitrary
+ _type_ attribute such as _text/x-template_:
+
+ script(type='text/template')
+ h1 Look!
+ p Jade still works in here!
+
+## Interpolation
+
+ Both plain-text and piped-text support interpolation,
+ which comes in two forms, escapes and non-escaped. The
+ following will output the _user.name_ in the paragraph
+ but HTML within it will be escaped to prevent XSS attacks:
+
+ p Welcome #{user.name}
+
+ The following syntax is identical however it will _not_ escape
+ HTML, and should only be used with strings that you trust:
+
+ p Welcome !{user.name}
+
+## Inline HTML
+
+ Sometimes constructing small inline snippets of HTML
+ in Jade can be annoying, luckily we can add plain
+ HTML as well:
+
+ p Welcome <em>#{user.name}</em>
+
+## Code
+
+ To buffer output with Jade simply use _=_ at the beginning
+ of a line or after a tag. This method escapes any HTML
+ present in the string.
+
+ p= user.description
+
+ To buffer output unescaped use the _!=_ variant, but again
+ be careful of XSS.
+
+ p!= user.description
+
+ The final way to mess with JavaScript code in Jade is the unbuffered
+ _-_, which can be used for conditionals, defining variables etc:
+
+ - var user = { description: 'foo bar baz' }
+ #user
+ - if (user.description) {
+ h2 Description
+ p.description= user.description
+ - }
+
+ When compiled blocks are wrapped in anonymous functions, so the
+ following is also valid, without braces:
+
+ - var user = { description: 'foo bar baz' }
+ #user
+ - if (user.description)
+ h2 Description
+ p.description= user.description
+
+ If you really want you could even use `.forEach()` and others:
+
+ - users.forEach(function(user){
+ .user
+ h2= user.name
+ p User #{user.name} is #{user.age} years old
+ - })
+
+ Taking this further Jade provides some syntax for conditionals,
+ iteration, switch statements etc. Let's look at those next!
+
+## Assignment
+
+ Jade's first-class assignment is simple, simply use the _=_
+ operator and Jade will _var_ it for you. The following are equivalent:
+
+ - var user = { name: 'tobi' }
+ user = { name: 'tobi' }
+
+## Conditionals
+
+ Jade's first-class conditional syntax allows for optional
+ parenthesis, and you may now omit the leading _-_ otherwise
+ it's identical, still just regular javascript:
+
+ user = { description: 'foo bar baz' }
+ #user
+ if user.description
+ h2 Description
+ p.description= user.description
+
+ Jade provides the negated version, _unless_ as well, the following
+ are equivalent:
+
+ - if (!(user.isAnonymous))
+ p You're logged in as #{user.name}
+
+ unless user.isAnonymous
+ p You're logged in as #{user.name}
+
+## Iteration
+
+ JavaScript's _for_ loops don't look very declarative, so Jade
+ also provides its own _for_ loop construct, aliased as _each_:
+
+ for user in users
+ .user
+ h2= user.name
+ p user #{user.name} is #{user.age} year old
+
+ As mentioned _each_ is identical:
+
+ each user in users
+ .user
+ h2= user.name
+
+ If necessary the index is available as well:
+
+ for user, i in users
+ .user(class='user-#{i}')
+ h2= user.name
+
+ Remember, it's just JavaScript:
+
+ ul#letters
+ for letter in ['a', 'b', 'c']
+ li= letter
+
+## Mixins
+
+ Mixins provide a way to define jade "functions" which "mix in"
+ their contents when called. This is useful for abstracting
+ out large fragments of Jade.
+
+ The simplest possible mixin which accepts no arguments might
+ look like this:
+
+ mixin hello
+ p Hello
+
+ You use a mixin by placing `+` before the name:
+
+ +hello
+
+ For something a little more dynamic, mixins can take
+ arguments, the mixin itself is converted to a javascript
+ function internally:
+
+ mixin hello(user)
+ p Hello #{user}
+
+ +hello('Tobi')
+
+ Yields:
+
+ <p>Hello Tobi</p>
+
+ Mixins may optionally take blocks, when a block is passed
+ its contents becomes the implicit `block` argument. For
+ example here is a mixin passed a block, and also invoked
+ without passing a block:
+
+ 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
+
+ yields:
+
+ <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>
+
+ Mixins can even take attributes, just like a tag. When
+ attributes are passed they become the implicit `attributes`
+ argument. Individual attributes can be accessed just like
+ normal object properties:
+
+ mixin centered
+ .centered(class=attributes.class)
+ block
+
+ +centered.bold Hello world
+
+ +centered.red
+ p This is my
+ p Amazing article
+
+ yields:
+
+ <div class="centered bold">Hello world</div>
+ <div class="centered red">
+ <p>This is my</p>
+ <p>Amazing article</p>
+ </div>
+
+ If you use `attributes` directly, *all* passed attributes
+ get used:
+
+ mixin link
+ a.menu(attributes)
+ block
+
+ +link.highlight(href='#top') Top
+ +link#sec1.plain(href='#section1') Section 1
+ +link#sec2.plain(href='#section2') Section 2
+
+ yields:
+
+ <a href="#top" class="highlight menu">Top</a>
+ <a id="sec1" href="#section1" class="plain menu">Section 1</a>
+ <a id="sec2" href="#section2" class="plain menu">Section 2</a>
+
+ If you pass arguments, they must directly follow the mixin:
+
+ mixin list(arr)
+ if block
+ .title
+ block
+ ul(attributes)
+ each item in arr
+ li= item
+
+ +list(['foo', 'bar', 'baz'])(id='myList', class='bold')
+
+ yields:
+
+ <ul id="myList" class="bold">
+ <li>foo</li>
+ <li>bar</li>
+ <li>baz</li>
+ </ul>
diff --git a/lib/compiler.js b/lib/compiler.js
new file mode 100644
index 0000000..263524f
--- /dev/null
+++ b/lib/compiler.js
@@ -0,0 +1,703 @@
+'use strict';
+
+var nodes = require('./nodes');
+var filters = require('./filters');
+var doctypes = require('./doctypes');
+var runtime = require('./runtime');
+var utils = require('./utils');
+var selfClosing = require('./self-closing');
+var parseJSExpression = require('character-parser').parseMax;
+var constantinople = require('constantinople');
+
+function isConstant(src) {
+ return constantinople(src, {jade: runtime, 'jade_interp': undefined});
+}
+function toConstant(src) {
+ return constantinople.toConstant(src, {jade: runtime, 'jade_interp': undefined});
+}
+function errorAtNode(node, error) {
+ error.line = node.line;
+ error.filename = node.filename;
+ return error;
+}
+
+/**
+ * Initialize `Compiler` with the given `node`.
+ *
+ * @param {Node} node
+ * @param {Object} options
+ * @api public
+ */
+
+var Compiler = module.exports = function Compiler(node, options) {
+ this.options = options = options || {};
+ this.node = node;
+ this.hasCompiledDoctype = false;
+ this.hasCompiledTag = false;
+ this.pp = options.pretty || false;
+ this.debug = false !== options.compileDebug;
+ this.indents = 0;
+ this.parentIndents = 0;
+ this.terse = false;
+ this.mixins = {};
+ this.dynamicMixins = false;
+ if (options.doctype) this.setDoctype(options.doctype);
+};
+
+/**
+ * Compiler prototype.
+ */
+
+Compiler.prototype = {
+
+ /**
+ * Compile parse tree to JavaScript.
+ *
+ * @api public
+ */
+
+ compile: function(){
+ this.buf = [];
+ if (this.pp) this.buf.push("var jade_indent = [];");
+ this.lastBufferedIdx = -1;
+ this.visit(this.node);
+ if (!this.dynamicMixins) {
+ // if there are no dynamic mixins we can remove any un-used mixins
+ var mixinNames = Object.keys(this.mixins);
+ for (var i = 0; i < mixinNames.length; i++) {
+ var mixin = this.mixins[mixinNames[i]];
+ if (!mixin.used) {
+ for (var x = 0; x < mixin.instances.length; x++) {
+ for (var y = mixin.instances[x].start; y < mixin.instances[x].end; y++) {
+ this.buf[y] = '';
+ }
+ }
+ }
+ }
+ }
+ return this.buf.join('\n');
+ },
+
+ /**
+ * Sets the default doctype `name`. Sets terse mode to `true` when
+ * html 5 is used, causing self-closing tags to end with ">" vs "/>",
+ * and boolean attributes are not mirrored.
+ *
+ * @param {string} name
+ * @api public
+ */
+
+ setDoctype: function(name){
+ this.doctype = doctypes[name.toLowerCase()] || '<!DOCTYPE ' + name + '>';
+ this.terse = this.doctype.toLowerCase() == '<!doctype html>';
+ this.xml = 0 == this.doctype.indexOf('<?xml');
+ },
+
+ /**
+ * Buffer the given `str` exactly as is or with interpolation
+ *
+ * @param {String} str
+ * @param {Boolean} interpolate
+ * @api public
+ */
+
+ buffer: function (str, interpolate) {
+ var self = this;
+ if (interpolate) {
+ var match = /(\\)?([#!]){((?:.|\n)*)$/.exec(str);
+ if (match) {
+ this.buffer(str.substr(0, match.index), false);
+ if (match[1]) { // escape
+ this.buffer(match[2] + '{', false);
+ this.buffer(match[3], true);
+ return;
+ } else {
+ var rest = match[3];
+ var range = parseJSExpression(rest);
+ var code = ('!' == match[2] ? '' : 'jade.escape') + "((jade_interp = " + range.src + ") == null ? '' : jade_interp)";
+ this.bufferExpression(code);
+ this.buffer(rest.substr(range.end + 1), true);
+ return;
+ }
+ }
+ }
+
+ str = JSON.stringify(str);
+ str = str.substr(1, str.length - 2);
+
+ if (this.lastBufferedIdx == this.buf.length) {
+ if (this.lastBufferedType === 'code') this.lastBuffered += ' + "';
+ this.lastBufferedType = 'text';
+ this.lastBuffered += str;
+ this.buf[this.lastBufferedIdx - 1] = 'buf.push(' + this.bufferStartChar + this.lastBuffered + '");'
+ } else {
+ this.buf.push('buf.push("' + str + '");');
+ this.lastBufferedType = 'text';
+ this.bufferStartChar = '"';
+ this.lastBuffered = str;
+ this.lastBufferedIdx = this.buf.length;
+ }
+ },
+
+ /**
+ * Buffer the given `src` so it is evaluated at run time
+ *
+ * @param {String} src
+ * @api public
+ */
+
+ bufferExpression: function (src) {
+ if (isConstant(src)) {
+ return this.buffer(toConstant(src) + '', false)
+ }
+ if (this.lastBufferedIdx == this.buf.length) {
+ if (this.lastBufferedType === 'text') this.lastBuffered += '"';
+ this.lastBufferedType = 'code';
+ this.lastBuffered += ' + (' + src + ')';
+ this.buf[this.lastBufferedIdx - 1] = 'buf.push(' + this.bufferStartChar + this.lastBuffered + ');'
+ } else {
+ this.buf.push('buf.push(' + src + ');');
+ this.lastBufferedType = 'code';
+ this.bufferStartChar = '';
+ this.lastBuffered = '(' + src + ')';
+ this.lastBufferedIdx = this.buf.length;
+ }
+ },
+
+ /**
+ * Buffer an indent based on the current `indent`
+ * property and an additional `offset`.
+ *
+ * @param {Number} offset
+ * @param {Boolean} newline
+ * @api public
+ */
+
+ prettyIndent: function(offset, newline){
+ offset = offset || 0;
+ newline = newline ? '\n' : '';
+ this.buffer(newline + Array(this.indents + offset).join(' '));
+ if (this.parentIndents)
+ this.buf.push("buf.push.apply(buf, jade_indent);");
+ },
+
+ /**
+ * Visit `node`.
+ *
+ * @param {Node} node
+ * @api public
+ */
+
+ visit: function(node){
+ var debug = this.debug;
+
+ if (debug) {
+ this.buf.push('jade_debug.unshift({ lineno: ' + node.line
+ + ', filename: ' + (node.filename
+ ? JSON.stringify(node.filename)
+ : 'jade_debug[0].filename')
+ + ' });');
+ }
+
+ // Massive hack to fix our context
+ // stack for - else[ if] etc
+ if (false === node.debug && this.debug) {
+ this.buf.pop();
+ this.buf.pop();
+ }
+
+ this.visitNode(node);
+
+ if (debug) this.buf.push('jade_debug.shift();');
+ },
+
+ /**
+ * Visit `node`.
+ *
+ * @param {Node} node
+ * @api public
+ */
+
+ visitNode: function(node){
+ return this['visit' + node.type](node);
+ },
+
+ /**
+ * Visit case `node`.
+ *
+ * @param {Literal} node
+ * @api public
+ */
+
+ visitCase: function(node){
+ var _ = this.withinCase;
+ this.withinCase = true;
+ this.buf.push('switch (' + node.expr + '){');
+ this.visit(node.block);
+ this.buf.push('}');
+ this.withinCase = _;
+ },
+
+ /**
+ * Visit when `node`.
+ *
+ * @param {Literal} node
+ * @api public
+ */
+
+ visitWhen: function(node){
+ if ('default' == node.expr) {
+ this.buf.push('default:');
+ } else {
+ this.buf.push('case ' + node.expr + ':');
+ }
+ if (node.block) {
+ this.visit(node.block);
+ this.buf.push(' break;');
+ }
+ },
+
+ /**
+ * Visit literal `node`.
+ *
+ * @param {Literal} node
+ * @api public
+ */
+
+ visitLiteral: function(node){
+ this.buffer(node.str);
+ },
+
+ /**
+ * Visit all nodes in `block`.
+ *
+ * @param {Block} block
+ * @api public
+ */
+
+ visitBlock: function(block){
+ var len = block.nodes.length
+ , escape = this.escape
+ , pp = this.pp
+
+ // Pretty print multi-line text
+ if (pp && len > 1 && !escape && block.nodes[0].isText && block.nodes[1].isText)
+ this.prettyIndent(1, true);
+
+ for (var i = 0; i < len; ++i) {
+ // Pretty print text
+ if (pp && i > 0 && !escape && block.nodes[i].isText && block.nodes[i-1].isText)
+ this.prettyIndent(1, false);
+
+ this.visit(block.nodes[i]);
+ // Multiple text nodes are separated by newlines
+ if (block.nodes[i+1] && block.nodes[i].isText && block.nodes[i+1].isText)
+ this.buffer('\n');
+ }
+ },
+
+ /**
+ * Visit a mixin's `block` keyword.
+ *
+ * @param {MixinBlock} block
+ * @api public
+ */
+
+ visitMixinBlock: function(block){
+ if (this.pp) this.buf.push("jade_indent.push('" + Array(this.indents + 1).join(' ') + "');");
+ this.buf.push('block && block();');
+ if (this.pp) this.buf.push("jade_indent.pop();");
+ },
+
+ /**
+ * Visit `doctype`. Sets terse mode to `true` when html 5
+ * is used, causing self-closing tags to end with ">" vs "/>",
+ * and boolean attributes are not mirrored.
+ *
+ * @param {Doctype} doctype
+ * @api public
+ */
+
+ visitDoctype: function(doctype){
+ if (doctype && (doctype.val || !this.doctype)) {
+ this.setDoctype(doctype.val || 'default');
+ }
+
+ if (this.doctype) this.buffer(this.doctype);
+ this.hasCompiledDoctype = true;
+ },
+
+ /**
+ * Visit `mixin`, generating a function that
+ * may be called within the template.
+ *
+ * @param {Mixin} mixin
+ * @api public
+ */
+
+ visitMixin: function(mixin){
+ var name = 'jade_mixins[';
+ var args = mixin.args || '';
+ var block = mixin.block;
+ var attrs = mixin.attrs;
+ var attrsBlocks = mixin.attributeBlocks;
+ var pp = this.pp;
+ var dynamic = mixin.name[0]==='#';
+ var key = mixin.name;
+ if (dynamic) this.dynamicMixins = true;
+ name += (dynamic ? mixin.name.substr(2,mixin.name.length-3):'"'+mixin.name+'"')+']';
+
+ this.mixins[key] = this.mixins[key] || {used: false, instances: []};
+ if (mixin.call) {
+ this.mixins[key].used = true;
+ if (pp) this.buf.push("jade_indent.push('" + Array(this.indents + 1).join(' ') + "');")
+ if (block || attrs.length || attrsBlocks.length) {
+
+ this.buf.push(name + '.call({');
+
+ if (block) {
+ this.buf.push('block: function(){');
+
+ // Render block with no indents, dynamically added when rendered
+ this.parentIndents++;
+ var _indents = this.indents;
+ this.indents = 0;
+ this.visit(mixin.block);
+ this.indents = _indents;
+ this.parentIndents--;
+
+ if (attrs.length || attrsBlocks.length) {
+ this.buf.push('},');
+ } else {
+ this.buf.push('}');
+ }
+ }
+
+ if (attrsBlocks.length) {
+ if (attrs.length) {
+ var val = this.attrs(attrs);
+ attrsBlocks.unshift(val);
+ }
+ this.buf.push('attributes: jade.merge([' + attrsBlocks.join(',') + '])');
+ } else if (attrs.length) {
+ var val = this.attrs(attrs);
+ this.buf.push('attributes: ' + val);
+ }
+
+ if (args) {
+ this.buf.push('}, ' + args + ');');
+ } else {
+ this.buf.push('});');
+ }
+
+ } else {
+ this.buf.push(name + '(' + args + ');');
+ }
+ if (pp) this.buf.push("jade_indent.pop();")
+ } else {
+ var mixin_start = this.buf.length;
+ this.buf.push(name + ' = function(' + args + '){');
+ this.buf.push('var block = (this && this.block), attributes = (this && this.attributes) || {};');
+ this.parentIndents++;
+ this.visit(block);
+ this.parentIndents--;
+ this.buf.push('};');
+ var mixin_end = this.buf.length;
+ this.mixins[key].instances.push({start: mixin_start, end: mixin_end});
+ }
+ },
+
+ /**
+ * Visit `tag` buffering tag markup, generating
+ * attributes, visiting the `tag`'s code and block.
+ *
+ * @param {Tag} tag
+ * @api public
+ */
+
+ visitTag: function(tag){
+ this.indents++;
+ var name = tag.name
+ , pp = this.pp
+ , self = this;
+
+ function bufferName() {
+ if (tag.buffer) self.bufferExpression(name);
+ else self.buffer(name);
+ }
+
+ if ('pre' == tag.name) this.escape = true;
+
+ if (!this.hasCompiledTag) {
+ if (!this.hasCompiledDoctype && 'html' == name) {
+ this.visitDoctype();
+ }
+ this.hasCompiledTag = true;
+ }
+
+ // pretty print
+ if (pp && !tag.isInline())
+ this.prettyIndent(0, true);
+
+ if (tag.selfClosing || (!this.xml && selfClosing.indexOf(tag.name) !== -1)) {
+ this.buffer('<');
+ bufferName();
+ this.visitAttributes(tag.attrs, tag.attributeBlocks);
+ this.terse
+ ? this.buffer('>')
+ : this.buffer('/>');
+ // if it is non-empty throw an error
+ if (tag.block &&
+ !(tag.block.type === 'Block' && tag.block.nodes.length === 0) &&
+ tag.block.nodes.some(function (tag) {
+ return tag.type !== 'Text' || !/^\s*$/.test(tag.val)
+ })) {
+ throw errorAtNode(tag, new Error(name + ' is self closing and should not have content.'));
+ }
+ } else {
+ // Optimize attributes buffering
+ this.buffer('<');
+ bufferName();
+ this.visitAttributes(tag.attrs, tag.attributeBlocks);
+ this.buffer('>');
+ if (tag.code) this.visitCode(tag.code);
+ this.visit(tag.block);
+
+ // pretty print
+ if (pp && !tag.isInline() && 'pre' != tag.name && !tag.canInline())
+ this.prettyIndent(0, true);
+
+ this.buffer('</');
+ bufferName();
+ this.buffer('>');
+ }
+
+ if ('pre' == tag.name) this.escape = false;
+
+ this.indents--;
+ },
+
+ /**
+ * Visit `filter`, throwing when the filter does not exist.
+ *
+ * @param {Filter} filter
+ * @api public
+ */
+
+ visitFilter: function(filter){
+ var text = filter.block.nodes.map(
+ function(node){ return node.val; }
+ ).join('\n');
+ filter.attrs.filename = this.options.filename;
+ try {
+ this.buffer(filters(filter.name, text, filter.attrs), true);
+ } catch (err) {
+ throw errorAtNode(filter, err);
+ }
+ },
+
+ /**
+ * Visit `text` node.
+ *
+ * @param {Text} text
+ * @api public
+ */
+
+ visitText: function(text){
+ this.buffer(text.val, true);
+ },
+
+ /**
+ * Visit a `comment`, only buffering when the buffer flag is set.
+ *
+ * @param {Comment} comment
+ * @api public
+ */
+
+ visitComment: function(comment){
+ if (!comment.buffer) return;
+ if (this.pp) this.prettyIndent(1, true);
+ this.buffer('<!--' + comment.val + '-->');
+ },
+
+ /**
+ * Visit a `BlockComment`.
+ *
+ * @param {Comment} comment
+ * @api public
+ */
+
+ visitBlockComment: function(comment){
+ if (!comment.buffer) return;
+ if (this.pp) this.prettyIndent(1, true);
+ this.buffer('<!--' + comment.val);
+ this.visit(comment.block);
+ if (this.pp) this.prettyIndent(1, true);
+ this.buffer('-->');
+ },
+
+ /**
+ * Visit `code`, respecting buffer / escape flags.
+ * If the code is followed by a block, wrap it in
+ * a self-calling function.
+ *
+ * @param {Code} code
+ * @api public
+ */
+
+ visitCode: function(code){
+ // Wrap code blocks with {}.
+ // we only wrap unbuffered code blocks ATM
+ // since they are usually flow control
+
+ // Buffer code
+ if (code.buffer) {
+ var val = code.val.trimLeft();
+ val = 'null == (jade_interp = '+val+') ? "" : jade_interp';
+ if (code.escape) val = 'jade.escape(' + val + ')';
+ this.bufferExpression(val);
+ } else {
+ this.buf.push(code.val);
+ }
+
+ // Block support
+ if (code.block) {
+ if (!code.buffer) this.buf.push('{');
+ this.visit(code.block);
+ if (!code.buffer) this.buf.push('}');
+ }
+ },
+
+ /**
+ * Visit `each` block.
+ *
+ * @param {Each} each
+ * @api public
+ */
+
+ visitEach: function(each){
+ this.buf.push(''
+ + '// iterate ' + each.obj + '\n'
+ + ';(function(){\n'
+ + ' var $$obj = ' + each.obj + ';\n'
+ + ' if (\'number\' == typeof $$obj.length) {\n');
+
+ if (each.alternative) {
+ this.buf.push(' if ($$obj.length) {');
+ }
+
+ this.buf.push(''
+ + ' for (var ' + each.key + ' = 0, $$l = $$obj.length; ' + each.key + ' < $$l; ' + each.key + '++) {\n'
+ + ' var ' + each.val + ' = $$obj[' + each.key + '];\n');
+
+ this.visit(each.block);
+
+ this.buf.push(' }\n');
+
+ if (each.alternative) {
+ this.buf.push(' } else {');
+ this.visit(each.alternative);
+ this.buf.push(' }');
+ }
+
+ this.buf.push(''
+ + ' } else {\n'
+ + ' var $$l = 0;\n'
+ + ' for (var ' + each.key + ' in $$obj) {\n'
+ + ' $$l++;'
+ + ' var ' + each.val + ' = $$obj[' + each.key + '];\n');
+
+ this.visit(each.block);
+
+ this.buf.push(' }\n');
+ if (each.alternative) {
+ this.buf.push(' if ($$l === 0) {');
+ this.visit(each.alternative);
+ this.buf.push(' }');
+ }
+ this.buf.push(' }\n}).call(this);\n');
+ },
+
+ /**
+ * Visit `attrs`.
+ *
+ * @param {Array} attrs
+ * @api public
+ */
+
+ visitAttributes: function(attrs, attributeBlocks){
+ if (attributeBlocks.length) {
+ if (attrs.length) {
+ var val = this.attrs(attrs);
+ attributeBlocks.unshift(val);
+ }
+ this.bufferExpression('jade.attrs(jade.merge([' + attributeBlocks.join(',') + ']), ' + JSON.stringify(this.terse) + ')');
+ } else if (attrs.length) {
+ this.attrs(attrs, true);
+ }
+ },
+
+ /**
+ * Compile attributes.
+ */
+
+ attrs: function(attrs, buffer){
+ var buf = [];
+ var classes = [];
+ var classEscaping = [];
+
+ attrs.forEach(function(attr){
+ var key = attr.name;
+ var escaped = attr.escaped;
+
+ if (key === 'class') {
+ classes.push(attr.val);
+ classEscaping.push(attr.escaped);
+ } else if (isConstant(attr.val)) {
+ if (buffer) {
+ this.buffer(runtime.attr(key, toConstant(attr.val), escaped, this.terse));
+ } else {
+ var val = toConstant(attr.val);
+ if (escaped && !(key.indexOf('data') === 0 && typeof val !== 'string')) {
+ val = runtime.escape(val);
+ }
+ buf.push(JSON.stringify(key) + ': ' + JSON.stringify(val));
+ }
+ } else {
+ if (buffer) {
+ this.bufferExpression('jade.attr("' + key + '", ' + attr.val + ', ' + JSON.stringify(escaped) + ', ' + JSON.stringify(this.terse) + ')');
+ } else {
+ var val = attr.val;
+ if (escaped && !(key.indexOf('data') === 0)) {
+ val = 'jade.escape(' + val + ')';
+ } else if (escaped) {
+ val = '(typeof (jade_interp = ' + val + ') == "string" ? jade.escape(jade_interp) : jade_interp)';
+ }
+ buf.push(JSON.stringify(key) + ': ' + val);
+ }
+ }
+ }.bind(this));
+ if (buffer) {
+ if (classes.every(isConstant)) {
+ this.buffer(runtime.cls(classes.map(toConstant), classEscaping));
+ } else {
+ this.bufferExpression('jade.cls([' + classes.join(',') + '], ' + JSON.stringify(classEscaping) + ')');
+ }
+ } else if (classes.length) {
+ if (classes.every(isConstant)) {
+ classes = JSON.stringify(runtime.joinClasses(classes.map(toConstant).map(runtime.joinClasses).map(function (cls, i) {
+ return classEscaping[i] ? runtime.escape(cls) : cls;
+ })));
+ } else {
+ classes = '(jade_interp = ' + JSON.stringify(classEscaping) + ',' +
+ ' jade.joinClasses([' + classes.join(',') + '].map(jade.joinClasses).map(function (cls, i) {' +
+ ' return jade_interp[i] ? jade.escape(cls) : cls' +
+ ' }))' +
+ ')';
+ }
+ if (classes.length)
+ buf.push('"class": ' + classes);
+ }
+ return '{' + buf.join(',') + '}';
+ }
+};
diff --git a/lib/doctypes.js b/lib/doctypes.js
new file mode 100644
index 0000000..1471d26
--- /dev/null
+++ b/lib/doctypes.js
@@ -0,0 +1,12 @@
+'use strict';
+
+module.exports = {
+ 'default': '<!DOCTYPE html>'
+ , 'xml': '<?xml version="1.0" encoding="utf-8" ?>'
+ , 'transitional': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
+ , 'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
+ , 'frameset': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
+ , '1.1': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
+ , 'basic': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
+ , 'mobile': '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
+};
\ No newline at end of file
diff --git a/lib/filters-client.js b/lib/filters-client.js
new file mode 100644
index 0000000..ef1b859
--- /dev/null
+++ b/lib/filters-client.js
@@ -0,0 +1,11 @@
+'use strict';
+
+module.exports = filter;
+function filter(name, str, options) {
+ if (typeof filter[name] === 'function') {
+ var res = filter[name](str, options);
+ } else {
+ throw new Error('unknown filter ":' + name + '"');
+ }
+ return res;
+}
diff --git a/lib/filters.js b/lib/filters.js
new file mode 100644
index 0000000..47d946e
--- /dev/null
+++ b/lib/filters.js
@@ -0,0 +1,15 @@
+'use strict';
+
+var transformers = require('transformers');
+
+module.exports = filter;
+function filter(name, str, options) {
+ if (typeof filter[name] === 'function') {
+ var res = filter[name](str, options);
+ } else if (transformers[name]) {
+ var res = transformers[name].renderSync(str, options);
+ } else {
+ throw new Error('unknown filter ":' + name + '"');
+ }
+ return res;
+}
diff --git a/lib/index.js b/lib/index.js
new file mode 120000
index 0000000..6a783c2
--- /dev/null
+++ b/lib/index.js
@@ -0,0 +1 @@
+jade.js
\ No newline at end of file
diff --git a/lib/inline-tags.js b/lib/inline-tags.js
new file mode 100644
index 0000000..a2345b1
--- /dev/null
+++ b/lib/inline-tags.js
@@ -0,0 +1,23 @@
+'use strict';
+
+module.exports = [
+ 'a'
+ , 'abbr'
+ , 'acronym'
+ , 'b'
+ , 'br'
+ , 'code'
+ , 'em'
+ , 'font'
+ , 'i'
+ , 'img'
+ , 'ins'
+ , 'kbd'
+ , 'map'
+ , 'samp'
+ , 'small'
+ , 'span'
+ , 'strong'
+ , 'sub'
+ , 'sup'
+];
\ No newline at end of file
diff --git a/lib/jade.js b/lib/jade.js
new file mode 100644
index 0000000..00b1a9d
--- /dev/null
+++ b/lib/jade.js
@@ -0,0 +1,335 @@
+'use strict';
+
+/*!
+ * Jade
+ * Copyright(c) 2010 TJ Holowaychuk <tj at vision-media.ca>
+ * MIT Licensed
+ */
+
+/**
+ * Module dependencies.
+ */
+
+var Parser = require('./parser')
+ , Lexer = require('./lexer')
+ , Compiler = require('./compiler')
+ , runtime = require('./runtime')
+ , addWith = require('with')
+ , fs = require('fs');
+
+/**
+ * Expose self closing tags.
+ */
+
+exports.selfClosing = require('./self-closing');
+
+/**
+ * Default supported doctypes.
+ */
+
+exports.doctypes = require('./doctypes');
+
+/**
+ * Text filters.
+ */
+
+exports.filters = require('./filters');
+
+/**
+ * Utilities.
+ */
+
+exports.utils = require('./utils');
+
+/**
+ * Expose `Compiler`.
+ */
+
+exports.Compiler = Compiler;
+
+/**
+ * Expose `Parser`.
+ */
+
+exports.Parser = Parser;
+
+/**
+ * Expose `Lexer`.
+ */
+
+exports.Lexer = Lexer;
+
+/**
+ * Nodes.
+ */
+
+exports.nodes = require('./nodes');
+
+/**
+ * Jade runtime helpers.
+ */
+
+exports.runtime = runtime;
+
+/**
+ * Template function cache.
+ */
+
+exports.cache = {};
+
+/**
+ * Parse the given `str` of jade and return a function body.
+ *
+ * @param {String} str
+ * @param {Object} options
+ * @return {String}
+ * @api private
+ */
+
+function parse(str, options){
+ // Parse
+ var parser = new (options.parser || Parser)(str, options.filename, options);
+ var tokens;
+ try {
+ // Parse
+ tokens = parser.parse();
+ } catch (err) {
+ parser = parser.context();
+ runtime.rethrow(err, parser.filename, parser.lexer.lineno, parser.input);
+ }
+
+ // Compile
+ var compiler = new (options.compiler || Compiler)(tokens, options);
+ var js;
+ try {
+ js = compiler.compile();
+ } catch (err) {
+ if (err.line && (err.filename || !options.filename)) {
+ runtime.rethrow(err, err.filename, err.line, parser.input);
+ }
+ }
+
+ // Debug compiler
+ if (options.debug) {
+ console.error('\nCompiled Function:\n\n\u001b[90m%s\u001b[0m', js.replace(/^/gm, ' '));
+ }
+
+ var globals = [];
+
+ globals.push('jade');
+ globals.push('jade_mixins');
+ globals.push('jade_interp');
+ globals.push('jade_debug');
+ globals.push('buf');
+
+ return ''
+ + 'var buf = [];\n'
+ + 'var jade_mixins = {};\n'
+ + 'var jade_interp;\n'
+ + (options.self
+ ? 'var self = locals || {};\n' + js
+ : addWith('locals || {}', '\n' + js, globals)) + ';'
+ + 'return buf.join("");';
+}
+
+/**
+ * Compile a `Function` representation of the given jade `str`.
+ *
+ * Options:
+ *
+ * - `compileDebug` when `false` debugging code is stripped from the compiled
+ template, when it is explicitly `true`, the source code is included in
+ the compiled template for better accuracy.
+ * - `filename` used to improve errors when `compileDebug` is not `false` and to resolve imports/extends
+ *
+ * @param {String} str
+ * @param {Options} options
+ * @return {Function}
+ * @api public
+ */
+
+exports.compile = function(str, options){
+ var options = options || {}
+ , filename = options.filename
+ ? JSON.stringify(options.filename)
+ : 'undefined'
+ , fn;
+
+ str = String(str);
+
+ if (options.compileDebug !== false) {
+ fn = [
+ 'var jade_debug = [{ lineno: 1, filename: ' + filename + ' }];'
+ , 'try {'
+ , parse(str, options)
+ , '} 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 = 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`');
+ console.error(err.stack || err.message);
+ return exports.compileClient(str, options);
+ };
+ }
+ return res;
+};
+
+/**
+ * Compile a JavaScript source representation of the given jade `str`.
+ *
+ * Options:
+ *
+ * - `compileDebug` When it is `true`, the source code is included in
+ the compiled template for better error messages.
+ * - `filename` used to improve errors when `compileDebug` is not `true` and to resolve imports/extends
+ *
+ * @param {String} str
+ * @param {Options} options
+ * @return {String}
+ * @api public
+ */
+
+exports.compileClient = function(str, options){
+ var options = options || {}
+ , filename = options.filename
+ ? JSON.stringify(options.filename)
+ : 'undefined'
+ , fn;
+
+ str = String(str);
+
+ if (options.compileDebug) {
+ options.compileDebug = true;
+ fn = [
+ 'var jade_debug = [{ lineno: 1, filename: ' + filename + ' }];'
+ , 'try {'
+ , parse(str, options)
+ , '} 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);
+ }
+
+ return 'function template(locals) {\n' + fn + '\n}';
+};
+
+
+
+/**
+ * Render the given `str` of jade.
+ *
+ * Options:
+ *
+ * - `cache` enable template caching
+ * - `filename` filename required for `include` / `extends` and caching
+ *
+ * @param {String} str
+ * @param {Object|Function} options or fn
+ * @param {Function|undefined} fn
+ * @returns {String}
+ * @api public
+ */
+
+exports.render = function(str, options, fn){
+ // support callback API
+ if ('function' == typeof options) {
+ fn = options, options = undefined;
+ }
+ if (typeof fn === 'function') {
+ var res
+ try {
+ res = exports.render(str, options);
+ } catch (ex) {
+ return fn(ex);
+ }
+ return fn(null, res);
+ }
+
+ options = options || {};
+
+ // cache requires .filename
+ if (options.cache && !options.filename) {
+ throw new Error('the "filename" option is required for caching');
+ }
+
+ var path = options.filename;
+ var tmpl = options.cache
+ ? exports.cache[path] || (exports.cache[path] = exports.compile(str, options))
+ : exports.compile(str, options);
+ return tmpl(options);
+};
+
+/**
+ * Render a Jade file at the given `path`.
+ *
+ * @param {String} path
+ * @param {Object|Function} options or callback
+ * @param {Function|undefined} fn
+ * @returns {String}
+ * @api public
+ */
+
+exports.renderFile = function(path, options, fn){
+ // support callback API
+ if ('function' == typeof options) {
+ fn = options, options = undefined;
+ }
+ if (typeof fn === 'function') {
+ var res
+ try {
+ res = exports.renderFile(path, options);
+ } catch (ex) {
+ return fn(ex);
+ }
+ return fn(null, res);
+ }
+
+ options = options || {};
+
+ var key = path + ':string';
+
+ options.filename = path;
+ var str = options.cache
+ ? exports.cache[key] || (exports.cache[key] = fs.readFileSync(path, 'utf8'))
+ : fs.readFileSync(path, 'utf8');
+ return exports.render(str, options);
+};
+
+
+/**
+ * Compile a Jade file at the given `path` for use on the client.
+ *
+ * @param {String} path
+ * @param {Object} options
+ * @returns {String}
+ * @api public
+ */
+
+exports.compileFileClient = function(path, options){
+ options = options || {};
+
+ var key = path + ':string';
+
+ options.filename = path;
+ var str = options.cache
+ ? exports.cache[key] || (exports.cache[key] = fs.readFileSync(path, 'utf8'))
+ : fs.readFileSync(path, 'utf8');
+
+ return exports.compileClient(str, options);
+};
+
+/**
+ * Express support.
+ */
+
+exports.__express = exports.renderFile;
diff --git a/lib/lexer.js b/lib/lexer.js
new file mode 100644
index 0000000..f16565f
--- /dev/null
+++ b/lib/lexer.js
@@ -0,0 +1,877 @@
+'use strict';
+
+var utils = require('./utils');
+var characterParser = require('character-parser');
+
+
+/**
+ * Initialize `Lexer` with the given `str`.
+ *
+ * @param {String} str
+ * @param {String} filename
+ * @api private
+ */
+
+var Lexer = module.exports = function Lexer(str, filename) {
+ this.input = str.replace(/\r\n|\r/g, '\n');
+ this.filename = filename;
+ this.deferredTokens = [];
+ this.lastIndents = 0;
+ this.lineno = 1;
+ this.stash = [];
+ this.indentStack = [];
+ this.indentRe = null;
+ this.pipeless = false;
+};
+
+
+function assertExpression(exp) {
+ //this verifies that a JavaScript expression is valid
+ Function('', 'return (' + exp + ')');
+}
+function assertNestingCorrect(exp) {
+ //this verifies that code is properly nested, but allows
+ //invalid JavaScript such as the contents of `attributes`
+ var res = characterParser(exp)
+ if (res.isNesting()) {
+ throw new Error('Nesting must match on expression `' + exp + '`')
+ }
+}
+
+/**
+ * Lexer prototype.
+ */
+
+Lexer.prototype = {
+
+ /**
+ * Construct a token with the given `type` and `val`.
+ *
+ * @param {String} type
+ * @param {String} val
+ * @return {Object}
+ * @api private
+ */
+
+ tok: function(type, val){
+ return {
+ type: type
+ , line: this.lineno
+ , val: val
+ }
+ },
+
+ /**
+ * Consume the given `len` of input.
+ *
+ * @param {Number} len
+ * @api private
+ */
+
+ consume: function(len){
+ this.input = this.input.substr(len);
+ },
+
+ /**
+ * Scan for `type` with the given `regexp`.
+ *
+ * @param {String} type
+ * @param {RegExp} regexp
+ * @return {Object}
+ * @api private
+ */
+
+ scan: function(regexp, type){
+ var captures;
+ if (captures = regexp.exec(this.input)) {
+ this.consume(captures[0].length);
+ return this.tok(type, captures[1]);
+ }
+ },
+
+ /**
+ * Defer the given `tok`.
+ *
+ * @param {Object} tok
+ * @api private
+ */
+
+ defer: function(tok){
+ this.deferredTokens.push(tok);
+ },
+
+ /**
+ * Lookahead `n` tokens.
+ *
+ * @param {Number} n
+ * @return {Object}
+ * @api private
+ */
+
+ lookahead: function(n){
+ var fetch = n - this.stash.length;
+ while (fetch-- > 0) this.stash.push(this.next());
+ return this.stash[--n];
+ },
+
+ /**
+ * Return the indexOf `(` or `{` or `[` / `)` or `}` or `]` delimiters.
+ *
+ * @return {Number}
+ * @api private
+ */
+
+ bracketExpression: function(skip){
+ skip = skip || 0;
+ var start = this.input[skip];
+ if (start != '(' && start != '{' && start != '[') throw new Error('unrecognized start character');
+ var end = ({'(': ')', '{': '}', '[': ']'})[start];
+ var range = characterParser.parseMax(this.input, {start: skip + 1});
+ if (this.input[range.end] !== end) throw new Error('start character ' + start + ' does not match end character ' + this.input[range.end]);
+ return range;
+ },
+
+ /**
+ * Stashed token.
+ */
+
+ stashed: function() {
+ return this.stash.length
+ && this.stash.shift();
+ },
+
+ /**
+ * Deferred token.
+ */
+
+ deferred: function() {
+ return this.deferredTokens.length
+ && this.deferredTokens.shift();
+ },
+
+ /**
+ * end-of-source.
+ */
+
+ eos: function() {
+ if (this.input.length) return;
+ if (this.indentStack.length) {
+ this.indentStack.shift();
+ return this.tok('outdent');
+ } else {
+ return this.tok('eos');
+ }
+ },
+
+ /**
+ * Blank line.
+ */
+
+ blank: function() {
+ var captures;
+ if (captures = /^\n *\n/.exec(this.input)) {
+ this.consume(captures[0].length - 1);
+ ++this.lineno;
+ if (this.pipeless) return this.tok('text', '');
+ return this.next();
+ }
+ },
+
+ /**
+ * Comment.
+ */
+
+ comment: function() {
+ var captures;
+ if (captures = /^\/\/(-)?([^\n]*)/.exec(this.input)) {
+ this.consume(captures[0].length);
+ var tok = this.tok('comment', captures[2]);
+ tok.buffer = '-' != captures[1];
+ return tok;
+ }
+ },
+
+ /**
+ * Interpolated tag.
+ */
+
+ interpolation: function() {
+ if (/^#\{/.test(this.input)) {
+ var match;
+ try {
+ match = this.bracketExpression(1);
+ } catch (ex) {
+ return;//not an interpolation expression, just an unmatched open interpolation
+ }
+
+ this.consume(match.end + 1);
+ return this.tok('interpolation', match.src);
+ }
+ },
+
+ /**
+ * Tag.
+ */
+
+ tag: function() {
+ var captures;
+ if (captures = /^(\w[-:\w]*)(\/?)/.exec(this.input)) {
+ this.consume(captures[0].length);
+ var tok, name = captures[1];
+ if (':' == name[name.length - 1]) {
+ name = name.slice(0, -1);
+ tok = this.tok('tag', name);
+ this.defer(this.tok(':'));
+ while (' ' == this.input[0]) this.input = this.input.substr(1);
+ } else {
+ tok = this.tok('tag', name);
+ }
+ tok.selfClosing = !!captures[2];
+ return tok;
+ }
+ },
+
+ /**
+ * Filter.
+ */
+
+ filter: function() {
+ return this.scan(/^:([\w\-]+)/, 'filter');
+ },
+
+ /**
+ * Doctype.
+ */
+
+ doctype: function() {
+ if (this.scan(/^!!! *([^\n]+)?/, 'doctype')) {
+ throw new Error('`!!!` is deprecated, you must now use `doctype`');
+ }
+ var node = this.scan(/^(?:doctype) *([^\n]+)?/, 'doctype');
+ if (node && node.val && node.val.trim() === '5') {
+ throw new Error('`doctype 5` is deprecated, you must now use `doctype html`');
+ }
+ return node;
+ },
+
+ /**
+ * Id.
+ */
+
+ id: function() {
+ return this.scan(/^#([\w-]+)/, 'id');
+ },
+
+ /**
+ * Class.
+ */
+
+ className: function() {
+ return this.scan(/^\.([\w-]+)/, 'class');
+ },
+
+ /**
+ * Text.
+ */
+
+ text: function() {
+ return this.scan(/^(?:\| ?| )([^\n]+)/, 'text') || this.scan(/^(<[^\n]*)/, 'text');
+ },
+
+ textFail: function () {
+ var tok;
+ if (tok = this.scan(/^([^\.\n][^\n]+)/, 'text')) {
+ console.warn('Warning: missing space before text for line ' + this.lineno +
+ ' of jade file "' + this.filename + '"');
+ return tok;
+ }
+ },
+
+ /**
+ * Dot.
+ */
+
+ dot: function() {
+ return this.scan(/^\./, 'dot');
+ },
+
+ /**
+ * Extends.
+ */
+
+ "extends": function() {
+ return this.scan(/^extends? +([^\n]+)/, 'extends');
+ },
+
+ /**
+ * Block prepend.
+ */
+
+ prepend: function() {
+ var captures;
+ if (captures = /^prepend +([^\n]+)/.exec(this.input)) {
+ this.consume(captures[0].length);
+ var mode = 'prepend'
+ , name = captures[1]
+ , tok = this.tok('block', name);
+ tok.mode = mode;
+ return tok;
+ }
+ },
+
+ /**
+ * Block append.
+ */
+
+ append: function() {
+ var captures;
+ if (captures = /^append +([^\n]+)/.exec(this.input)) {
+ this.consume(captures[0].length);
+ var mode = 'append'
+ , name = captures[1]
+ , tok = this.tok('block', name);
+ tok.mode = mode;
+ return tok;
+ }
+ },
+
+ /**
+ * Block.
+ */
+
+ block: function() {
+ var captures;
+ if (captures = /^block\b *(?:(prepend|append) +)?([^\n]+)/.exec(this.input)) {
+ this.consume(captures[0].length);
+ var mode = captures[1] || 'replace'
+ , name = captures[2]
+ , tok = this.tok('block', name);
+
+ tok.mode = mode;
+ return tok;
+ }
+ },
+
+ /**
+ * Mixin Block.
+ */
+
+ mixinBlock: function() {
+ var captures;
+ if (captures = /^block\s*(\n|$)/.exec(this.input)) {
+ this.consume(captures[0].length - 1);
+ return this.tok('mixin-block');
+ }
+ },
+
+ /**
+ * Yield.
+ */
+
+ yield: function() {
+ return this.scan(/^yield */, 'yield');
+ },
+
+ /**
+ * Include.
+ */
+
+ include: function() {
+ return this.scan(/^include +([^\n]+)/, 'include');
+ },
+
+ /**
+ * Include with filter
+ */
+
+ includeFiltered: function() {
+ var captures;
+ if (captures = /^include:([\w\-]+) +([^\n]+)/.exec(this.input)) {
+ this.consume(captures[0].length);
+ var filter = captures[1];
+ var path = captures[2];
+ var tok = this.tok('include', path);
+ tok.filter = filter;
+ return tok;
+ }
+ },
+
+ /**
+ * Case.
+ */
+
+ "case": function() {
+ return this.scan(/^case +([^\n]+)/, 'case');
+ },
+
+ /**
+ * When.
+ */
+
+ when: function() {
+ return this.scan(/^when +([^:\n]+)/, 'when');
+ },
+
+ /**
+ * Default.
+ */
+
+ "default": function() {
+ return this.scan(/^default */, 'default');
+ },
+
+ /**
+ * Call mixin.
+ */
+
+ call: function(){
+
+ var tok, captures;
+ if (captures = /^\+(([-\w]+)|(#\{))/.exec(this.input)) {
+ // try to consume simple or interpolated call
+ if (captures[2]) {
+ // simple call
+ this.consume(captures[0].length);
+ tok = this.tok('call', captures[2]);
+ } else {
+ // interpolated call
+ var match;
+ try {
+ match = this.bracketExpression(2);
+ } catch (ex) {
+ return;//not an interpolation expression, just an unmatched open interpolation
+ }
+ this.consume(match.end + 1);
+ assertExpression(match.src);
+ tok = this.tok('call', '#{'+match.src+'}');
+ }
+
+ // Check for args (not attributes)
+ if (captures = /^ *\(/.exec(this.input)) {
+ try {
+ var range = this.bracketExpression(captures[0].length - 1);
+ if (!/^ *[-\w]+ *=/.test(range.src)) { // not attributes
+ this.consume(range.end + 1);
+ tok.args = range.src;
+ }
+ } catch (ex) {
+ //not a bracket expcetion, just unmatched open parens
+ }
+ }
+
+ return tok;
+ }
+ },
+
+ /**
+ * Mixin.
+ */
+
+ mixin: function(){
+ var captures;
+ if (captures = /^mixin +([-\w]+)(?: *\((.*)\))? */.exec(this.input)) {
+ this.consume(captures[0].length);
+ var tok = this.tok('mixin', captures[1]);
+ tok.args = captures[2];
+ return tok;
+ }
+ },
+
+ /**
+ * Conditional.
+ */
+
+ conditional: function() {
+ var captures;
+ if (captures = /^(if|unless|else if|else)\b([^\n]*)/.exec(this.input)) {
+ this.consume(captures[0].length);
+ var type = captures[1]
+ var js = captures[2];
+ var isIf = false;
+ var isElse = false;
+
+ switch (type) {
+ case 'if':
+ assertExpression(js)
+ js = 'if (' + js + ')';
+ isIf = true;
+ break;
+ case 'unless':
+ assertExpression(js)
+ js = 'if (!(' + js + '))';
+ isIf = true;
+ break;
+ case 'else if':
+ assertExpression(js)
+ js = 'else if (' + js + ')';
+ isIf = true;
+ isElse = true;
+ break;
+ case 'else':
+ if (js && js.trim()) {
+ throw new Error('`else` cannot have a condition, perhaps you meant `else if`');
+ }
+ js = 'else';
+ isElse = true;
+ break;
+ }
+ var tok = this.tok('code', js);
+ tok.isElse = isElse;
+ tok.isIf = isIf;
+ tok.requiresBlock = true;
+ return tok;
+ }
+ },
+
+ /**
+ * While.
+ */
+
+ "while": function() {
+ var captures;
+ if (captures = /^while +([^\n]+)/.exec(this.input)) {
+ this.consume(captures[0].length);
+ assertExpression(captures[1])
+ var tok = this.tok('code', 'while (' + captures[1] + ')');
+ tok.requiresBlock = true;
+ return tok;
+ }
+ },
+
+ /**
+ * Each.
+ */
+
+ each: function() {
+ var captures;
+ if (captures = /^(?:- *)?(?:each|for) +([a-zA-Z_$][\w$]*)(?: *, *([a-zA-Z_$][\w$]*))? * in *([^\n]+)/.exec(this.input)) {
+ this.consume(captures[0].length);
+ var tok = this.tok('each', captures[1]);
+ tok.key = captures[2] || '$index';
+ assertExpression(captures[3])
+ tok.code = captures[3];
+ return tok;
+ }
+ },
+
+ /**
+ * Code.
+ */
+
+ code: function() {
+ var captures;
+ if (captures = /^(!?=|-)[ \t]*([^\n]+)/.exec(this.input)) {
+ this.consume(captures[0].length);
+ var flags = captures[1];
+ captures[1] = captures[2];
+ var tok = this.tok('code', captures[1]);
+ tok.escape = flags.charAt(0) === '=';
+ tok.buffer = flags.charAt(0) === '=' || flags.charAt(1) === '=';
+ if (tok.buffer) assertExpression(captures[1])
+ return tok;
+ }
+ },
+
+ /**
+ * Attributes.
+ */
+
+ attrs: function() {
+ if ('(' == this.input.charAt(0)) {
+ var index = this.bracketExpression().end
+ , str = this.input.substr(1, index-1)
+ , tok = this.tok('attrs');
+
+ assertNestingCorrect(str);
+
+ var quote = '';
+ var interpolate = function (attr) {
+ return attr.replace(/(\\)?#\{(.+)/g, function(_, escape, expr){
+ if (escape) return _;
+ try {
+ var range = characterParser.parseMax(expr);
+ if (expr[range.end] !== '}') return _.substr(0, 2) + interpolate(_.substr(2));
+ assertExpression(range.src)
+ return quote + " + (" + range.src + ") + " + quote + interpolate(expr.substr(range.end + 1));
+ } catch (ex) {
+ return _.substr(0, 2) + interpolate(_.substr(2));
+ }
+ });
+ }
+
+ this.consume(index + 1);
+ tok.attrs = [];
+
+ var escapedAttr = true
+ var key = '';
+ var val = '';
+ var interpolatable = '';
+ var state = characterParser.defaultState();
+ var loc = 'key';
+ var isEndOfAttribute = function (i) {
+ if (key.trim() === '') return false;
+ if (i === str.length) return true;
+ if (loc === 'key') {
+ if (str[i] === ' ' || str[i] === '\n') {
+ for (var x = i; x < str.length; x++) {
+ if (str[x] != ' ' && str[x] != '\n') {
+ if (str[x] === '=' || str[x] === '!' || str[x] === ',') return false;
+ else return true;
+ }
+ }
+ }
+ return str[i] === ','
+ } else if (loc === 'value' && !state.isNesting()) {
+ try {
+ Function('', 'return (' + val + ');');
+ if (str[i] === ' ' || str[i] === '\n') {
+ for (var x = i; x < str.length; x++) {
+ if (str[x] != ' ' && str[x] != '\n') {
+ if (characterParser.isPunctuator(str[x]) && str[x] != '"' && str[x] != "'") return false;
+ else return true;
+ }
+ }
+ }
+ return str[i] === ',';
+ } catch (ex) {
+ return false;
+ }
+ }
+ }
+
+ this.lineno += str.split("\n").length - 1;
+
+ for (var i = 0; i <= str.length; i++) {
+ if (isEndOfAttribute(i)) {
+ val = val.trim();
+ if (val) assertExpression(val)
+ key = key.trim();
+ key = key.replace(/^['"]|['"]$/g, '');
+ tok.attrs.push({
+ name: key,
+ val: '' == val ? true : val,
+ escaped: escapedAttr
+ });
+ key = val = '';
+ loc = 'key';
+ escapedAttr = false;
+ } else {
+ switch (loc) {
+ case 'key-char':
+ if (str[i] === quote) {
+ loc = 'key';
+ if (i + 1 < str.length && [' ', ',', '!', '=', '\n'].indexOf(str[i + 1]) === -1)
+ throw new Error('Unexpected character ' + str[i + 1] + ' expected ` `, `\\n`, `,`, `!` or `=`');
+ } else {
+ key += str[i];
+ }
+ break;
+ case 'key':
+ if (key === '' && (str[i] === '"' || str[i] === "'")) {
+ loc = 'key-char';
+ quote = str[i];
+ } else if (str[i] === '!' || str[i] === '=') {
+ escapedAttr = str[i] !== '!';
+ if (str[i] === '!') i++;
+ if (str[i] !== '=') throw new Error('Unexpected character ' + str[i] + ' expected `=`');
+ loc = 'value';
+ state = characterParser.defaultState();
+ } else {
+ key += str[i]
+ }
+ break;
+ case 'value':
+ state = characterParser.parseChar(str[i], state);
+ if (state.isString()) {
+ loc = 'string';
+ quote = str[i];
+ interpolatable = str[i];
+ } else {
+ val += str[i];
+ }
+ break;
+ case 'string':
+ state = characterParser.parseChar(str[i], state);
+ interpolatable += str[i];
+ if (!state.isString()) {
+ loc = 'value';
+ val += interpolate(interpolatable);
+ }
+ break;
+ }
+ }
+ }
+
+ if ('/' == this.input.charAt(0)) {
+ this.consume(1);
+ tok.selfClosing = true;
+ }
+
+ return tok;
+ }
+ },
+
+ /**
+ * &attributes block
+ */
+ attributesBlock: function () {
+ var captures;
+ if (/^&attributes\b/.test(this.input)) {
+ this.consume(11);
+ var args = this.bracketExpression();
+ this.consume(args.end + 1);
+ return this.tok('&attributes', args.src);
+ }
+ },
+
+ /**
+ * Indent | Outdent | Newline.
+ */
+
+ indent: function() {
+ 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;
+ }
+
+ if (captures) {
+ var tok
+ , indents = captures[1].length;
+
+ ++this.lineno;
+ this.consume(indents + 1);
+
+ if (' ' == this.input[0] || '\t' == this.input[0]) {
+ throw new Error('Invalid indentation, you can use tabs or spaces but not both');
+ }
+
+ // blank line
+ if ('\n' == this.input[0]) return this.tok('newline');
+
+ // outdent
+ if (this.indentStack.length && indents < this.indentStack[0]) {
+ while (this.indentStack.length && this.indentStack[0] > indents) {
+ this.stash.push(this.tok('outdent'));
+ this.indentStack.shift();
+ }
+ tok = this.stash.pop();
+ // indent
+ } else if (indents && indents != this.indentStack[0]) {
+ this.indentStack.unshift(indents);
+ tok = this.tok('indent', indents);
+ // newline
+ } else {
+ tok = this.tok('newline');
+ }
+
+ return tok;
+ }
+ },
+
+ /**
+ * Pipe-less text consumed only when
+ * pipeless is true;
+ */
+
+ 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);
+ }
+ },
+
+ /**
+ * ':'
+ */
+
+ colon: function() {
+ return this.scan(/^: */, ':');
+ },
+
+ fail: function () {
+ if (/^ ($|\n)/.test(this.input)) {
+ this.consume(1);
+ return this.next();
+ }
+ throw new Error('unexpected text ' + this.input.substr(0, 5));
+ },
+
+ /**
+ * Return the next token object, or those
+ * previously stashed by lookahead.
+ *
+ * @return {Object}
+ * @api private
+ */
+
+ advance: function(){
+ return this.stashed()
+ || this.next();
+ },
+
+ /**
+ * Return the next token object.
+ *
+ * @return {Object}
+ * @api private
+ */
+
+ next: function() {
+ return this.deferred()
+ || this.blank()
+ || this.eos()
+ || this.pipelessText()
+ || this.yield()
+ || this.doctype()
+ || this.interpolation()
+ || this["case"]()
+ || this.when()
+ || this["default"]()
+ || this["extends"]()
+ || this.append()
+ || this.prepend()
+ || this.block()
+ || this.mixinBlock()
+ || this.include()
+ || this.includeFiltered()
+ || this.mixin()
+ || this.call()
+ || this.conditional()
+ || this.each()
+ || this["while"]()
+ || this.tag()
+ || this.filter()
+ || this.code()
+ || this.id()
+ || this.className()
+ || this.attrs()
+ || this.attributesBlock()
+ || this.indent()
+ || this.text()
+ || this.comment()
+ || this.colon()
+ || this.dot()
+ || this.textFail()
+ || this.fail();
+ }
+};
diff --git a/lib/nodes/attrs.js b/lib/nodes/attrs.js
new file mode 100644
index 0000000..e5ced54
--- /dev/null
+++ b/lib/nodes/attrs.js
@@ -0,0 +1,83 @@
+'use strict';
+
+var Node = require('./node');
+
+/**
+ * Initialize a `Attrs` node.
+ *
+ * @api public
+ */
+
+var Attrs = module.exports = function Attrs() {
+ this.attributeNames = [];
+ this.attrs = [];
+ this.attributeBlocks = [];
+};
+
+// Inherit from `Node`.
+Attrs.prototype = Object.create(Node.prototype);
+Attrs.prototype.constructor = Attrs;
+
+Attrs.prototype.type = 'Attrs';
+
+/**
+ * Set attribute `name` to `val`, keep in mind these become
+ * part of a raw js object literal, so to quote a value you must
+ * '"quote me"', otherwise or example 'user.name' is literal JavaScript.
+ *
+ * @param {String} name
+ * @param {String} val
+ * @param {Boolean} escaped
+ * @return {Tag} for chaining
+ * @api public
+ */
+
+Attrs.prototype.setAttribute = function(name, val, escaped){
+ if (name !== 'class' && this.attributeNames.indexOf(name) !== -1) {
+ throw new Error('Duplicate attribute "' + name + '" is not allowed.');
+ }
+ this.attributeNames.push(name);
+ this.attrs.push({ name: name, val: val, escaped: escaped });
+ return this;
+};
+
+/**
+ * Remove attribute `name` when present.
+ *
+ * @param {String} name
+ * @api public
+ */
+
+Attrs.prototype.removeAttribute = function(name){
+ var err = new Error('attrs.removeAttribute is deprecated and will be removed in v2.0.0');
+ console.warn(err.stack);
+
+ for (var i = 0, len = this.attrs.length; i < len; ++i) {
+ if (this.attrs[i] && this.attrs[i].name == name) {
+ delete this.attrs[i];
+ }
+ }
+};
+
+/**
+ * Get attribute value by `name`.
+ *
+ * @param {String} name
+ * @return {String}
+ * @api public
+ */
+
+Attrs.prototype.getAttribute = function(name){
+ var err = new Error('attrs.getAttribute is deprecated and will be removed in v2.0.0');
+ console.warn(err.stack);
+
+ for (var i = 0, len = this.attrs.length; i < len; ++i) {
+ if (this.attrs[i] && this.attrs[i].name == name) {
+ return this.attrs[i].val;
+ }
+ }
+};
+
+Attrs.prototype.addAttributes = function (src) {
+ this.attributeBlocks.push(src);
+};
diff --git a/lib/nodes/block-comment.js b/lib/nodes/block-comment.js
new file mode 100644
index 0000000..d3c764c
--- /dev/null
+++ b/lib/nodes/block-comment.js
@@ -0,0 +1,24 @@
+'use strict';
+
+var Node = require('./node');
+
+/**
+ * Initialize a `BlockComment` with the given `block`.
+ *
+ * @param {String} val
+ * @param {Block} block
+ * @param {Boolean} buffer
+ * @api public
+ */
+
+var BlockComment = module.exports = function BlockComment(val, block, buffer) {
+ this.block = block;
+ this.val = val;
+ this.buffer = buffer;
+};
+
+// Inherit from `Node`.
+BlockComment.prototype = Object.create(Node.prototype);
+BlockComment.prototype.constructor = BlockComment;
+
+BlockComment.prototype.type = 'BlockComment';
diff --git a/lib/nodes/block.js b/lib/nodes/block.js
new file mode 100644
index 0000000..f50412c
--- /dev/null
+++ b/lib/nodes/block.js
@@ -0,0 +1,118 @@
+'use strict';
+
+var Node = require('./node');
+
+/**
+ * Initialize a new `Block` with an optional `node`.
+ *
+ * @param {Node} node
+ * @api public
+ */
+
+var Block = module.exports = function Block(node){
+ this.nodes = [];
+ if (node) this.push(node);
+};
+
+// Inherit from `Node`.
+Block.prototype = Object.create(Node.prototype);
+Block.prototype.constructor = Block;
+
+Block.prototype.type = 'Block';
+
+/**
+ * Block flag.
+ */
+
+Block.prototype.isBlock = true;
+
+/**
+ * Replace the nodes in `other` with the nodes
+ * in `this` block.
+ *
+ * @param {Block} other
+ * @api private
+ */
+
+Block.prototype.replace = function(other){
+ var err = new Error('block.replace is deprecated and will be removed in v2.0.0');
+ console.warn(err.stack);
+
+ other.nodes = this.nodes;
+};
+
+/**
+ * Push the given `node`.
+ *
+ * @param {Node} node
+ * @return {Number}
+ * @api public
+ */
+
+Block.prototype.push = function(node){
+ return this.nodes.push(node);
+};
+
+/**
+ * Check if this block is empty.
+ *
+ * @return {Boolean}
+ * @api public
+ */
+
+Block.prototype.isEmpty = function(){
+ return 0 == this.nodes.length;
+};
+
+/**
+ * Unshift the given `node`.
+ *
+ * @param {Node} node
+ * @return {Number}
+ * @api public
+ */
+
+Block.prototype.unshift = function(node){
+ return this.nodes.unshift(node);
+};
+
+/**
+ * Return the "last" block, or the first `yield` node.
+ *
+ * @return {Block}
+ * @api private
+ */
+
+Block.prototype.includeBlock = function(){
+ var ret = this
+ , node;
+
+ for (var i = 0, len = this.nodes.length; i < len; ++i) {
+ node = this.nodes[i];
+ if (node.yield) return node;
+ else if (node.textOnly) continue;
+ else if (node.includeBlock) ret = node.includeBlock();
+ else if (node.block && !node.block.isEmpty()) ret = node.block.includeBlock();
+ if (ret.yield) return ret;
+ }
+
+ return ret;
+};
+
+/**
+ * Return a clone of this block.
+ *
+ * @return {Block}
+ * @api private
+ */
+
+Block.prototype.clone = function(){
+ var err = new Error('block.clone is deprecated and will be removed in v2.0.0');
+ console.warn(err.stack);
+
+ var clone = new Block;
+ for (var i = 0, len = this.nodes.length; i < len; ++i) {
+ clone.push(this.nodes[i].clone());
+ }
+ return clone;
+};
diff --git a/lib/nodes/case.js b/lib/nodes/case.js
new file mode 100644
index 0000000..83e1e0e
--- /dev/null
+++ b/lib/nodes/case.js
@@ -0,0 +1,33 @@
+'use strict';
+
+var Node = require('./node');
+
+/**
+ * Initialize a new `Case` with `expr`.
+ *
+ * @param {String} expr
+ * @api public
+ */
+
+var Case = exports = module.exports = function Case(expr, block){
+ this.expr = expr;
+ this.block = block;
+};
+
+// Inherit from `Node`.
+Case.prototype = Object.create(Node.prototype);
+Case.prototype.constructor = Case;
+
+Case.prototype.type = 'Case';
+
+var When = exports.When = function When(expr, block){
+ this.expr = expr;
+ this.block = block;
+ this.debug = false;
+};
+
+// Inherit from `Node`.
+When.prototype = Object.create(Node.prototype);
+When.prototype.constructor = When;
+
+When.prototype.type = 'When';
diff --git a/lib/nodes/code.js b/lib/nodes/code.js
new file mode 100644
index 0000000..a49b3ee
--- /dev/null
+++ b/lib/nodes/code.js
@@ -0,0 +1,26 @@
+'use strict';
+
+var Node = require('./node');
+
+/**
+ * Initialize a `Code` node with the given code `val`.
+ * Code may also be optionally buffered and escaped.
+ *
+ * @param {String} val
+ * @param {Boolean} buffer
+ * @param {Boolean} escape
+ * @api public
+ */
+
+var Code = module.exports = function Code(val, buffer, escape) {
+ this.val = val;
+ this.buffer = buffer;
+ this.escape = escape;
+ if (val.match(/^ *else/)) this.debug = false;
+};
+
+// Inherit from `Node`.
+Code.prototype = Object.create(Node.prototype);
+Code.prototype.constructor = Code;
+
+Code.prototype.type = 'Code'; // prevent the minifiers removing this
\ No newline at end of file
diff --git a/lib/nodes/comment.js b/lib/nodes/comment.js
new file mode 100644
index 0000000..04108ef
--- /dev/null
+++ b/lib/nodes/comment.js
@@ -0,0 +1,23 @@
+'use strict';
+
+var Node = require('./node');
+
+/**
+ * Initialize a `Comment` with the given `val`, optionally `buffer`,
+ * otherwise the comment may render in the output.
+ *
+ * @param {String} val
+ * @param {Boolean} buffer
+ * @api public
+ */
+
+var Comment = module.exports = function Comment(val, buffer) {
+ this.val = val;
+ this.buffer = buffer;
+};
+
+// Inherit from `Node`.
+Comment.prototype = Object.create(Node.prototype);
+Comment.prototype.constructor = Comment;
+
+Comment.prototype.type = 'Comment';
diff --git a/lib/nodes/doctype.js b/lib/nodes/doctype.js
new file mode 100644
index 0000000..7ce1f34
--- /dev/null
+++ b/lib/nodes/doctype.js
@@ -0,0 +1,20 @@
+'use strict';
+
+var Node = require('./node');
+
+/**
+ * Initialize a `Doctype` with the given `val`.
+ *
+ * @param {String} val
+ * @api public
+ */
+
+var Doctype = module.exports = function Doctype(val) {
+ this.val = val;
+};
+
+// Inherit from `Node`.
+Doctype.prototype = Object.create(Node.prototype);
+Doctype.prototype.constructor = Doctype;
+
+Doctype.prototype.type = 'Doctype';
diff --git a/lib/nodes/each.js b/lib/nodes/each.js
new file mode 100644
index 0000000..23cc0dd
--- /dev/null
+++ b/lib/nodes/each.js
@@ -0,0 +1,26 @@
+'use strict';
+
+var Node = require('./node');
+
+/**
+ * Initialize an `Each` node, representing iteration
+ *
+ * @param {String} obj
+ * @param {String} val
+ * @param {String} key
+ * @param {Block} block
+ * @api public
+ */
+
+var Each = module.exports = function Each(obj, val, key, block) {
+ this.obj = obj;
+ this.val = val;
+ this.key = key;
+ this.block = block;
+};
+
+// Inherit from `Node`.
+Each.prototype = Object.create(Node.prototype);
+Each.prototype.constructor = Each;
+
+Each.prototype.type = 'Each';
diff --git a/lib/nodes/filter.js b/lib/nodes/filter.js
new file mode 100644
index 0000000..cc92a3a
--- /dev/null
+++ b/lib/nodes/filter.js
@@ -0,0 +1,24 @@
+'use strict';
+
+var Node = require('./node');
+
+/**
+ * Initialize a `Filter` node with the given
+ * filter `name` and `block`.
+ *
+ * @param {String} name
+ * @param {Block|Node} block
+ * @api public
+ */
+
+var Filter = module.exports = function Filter(name, block, attrs) {
+ this.name = name;
+ this.block = block;
+ this.attrs = attrs;
+};
+
+// Inherit from `Node`.
+Filter.prototype = Object.create(Node.prototype);
+Filter.prototype.constructor = Filter;
+
+Filter.prototype.type = 'Filter';
diff --git a/lib/nodes/index.js b/lib/nodes/index.js
new file mode 100644
index 0000000..22c111d
--- /dev/null
+++ b/lib/nodes/index.js
@@ -0,0 +1,16 @@
+'use strict';
+
+exports.Node = require('./node');
+exports.Tag = require('./tag');
+exports.Code = require('./code');
+exports.Each = require('./each');
+exports.Case = require('./case');
+exports.Text = require('./text');
+exports.Block = require('./block');
+exports.MixinBlock = require('./mixin-block');
+exports.Mixin = require('./mixin');
+exports.Filter = require('./filter');
+exports.Comment = require('./comment');
+exports.Literal = require('./literal');
+exports.BlockComment = require('./block-comment');
+exports.Doctype = require('./doctype');
diff --git a/lib/nodes/literal.js b/lib/nodes/literal.js
new file mode 100644
index 0000000..7559aaf
--- /dev/null
+++ b/lib/nodes/literal.js
@@ -0,0 +1,20 @@
+'use strict';
+
+var Node = require('./node');
+
+/**
+ * Initialize a `Literal` node with the given `str.
+ *
+ * @param {String} str
+ * @api public
+ */
+
+var Literal = module.exports = function Literal(str) {
+ this.str = str;
+};
+
+// Inherit from `Node`.
+Literal.prototype = Object.create(Node.prototype);
+Literal.prototype.constructor = Literal;
+
+Literal.prototype.type = 'Literal';
diff --git a/lib/nodes/mixin-block.js b/lib/nodes/mixin-block.js
new file mode 100644
index 0000000..20020e7
--- /dev/null
+++ b/lib/nodes/mixin-block.js
@@ -0,0 +1,18 @@
+'use strict';
+
+var Node = require('./node');
+
+/**
+ * Initialize a new `Block` with an optional `node`.
+ *
+ * @param {Node} node
+ * @api public
+ */
+
+var MixinBlock = module.exports = function MixinBlock(){};
+
+// Inherit from `Node`.
+MixinBlock.prototype = Object.create(Node.prototype);
+MixinBlock.prototype.constructor = MixinBlock;
+
+MixinBlock.prototype.type = 'MixinBlock';
diff --git a/lib/nodes/mixin.js b/lib/nodes/mixin.js
new file mode 100644
index 0000000..038df26
--- /dev/null
+++ b/lib/nodes/mixin.js
@@ -0,0 +1,26 @@
+'use strict';
+
+var Attrs = require('./attrs');
+
+/**
+ * Initialize a new `Mixin` with `name` and `block`.
+ *
+ * @param {String} name
+ * @param {String} args
+ * @param {Block} block
+ * @api public
+ */
+
+var Mixin = module.exports = function Mixin(name, args, block, call){
+ Attrs.call(this);
+ this.name = name;
+ this.args = args;
+ this.block = block;
+ this.call = call;
+};
+
+// Inherit from `Attrs`.
+Mixin.prototype = Object.create(Attrs.prototype);
+Mixin.prototype.constructor = Mixin;
+
+Mixin.prototype.type = 'Mixin';
diff --git a/lib/nodes/node.js b/lib/nodes/node.js
new file mode 100644
index 0000000..f739733
--- /dev/null
+++ b/lib/nodes/node.js
@@ -0,0 +1,18 @@
+'use strict';
+
+var Node = module.exports = function Node(){};
+
+/**
+ * Clone this node (return itself)
+ *
+ * @return {Node}
+ * @api private
+ */
+
+Node.prototype.clone = function(){
+ var err = new Error('node.clone is deprecated and will be removed in v2.0.0');
+ console.warn(err.stack);
+ return this;
+};
+
+Node.prototype.type = '';
diff --git a/lib/nodes/tag.js b/lib/nodes/tag.js
new file mode 100644
index 0000000..675b34c
--- /dev/null
+++ b/lib/nodes/tag.js
@@ -0,0 +1,89 @@
+'use strict';
+
+var Attrs = require('./attrs');
+var Block = require('./block');
+var inlineTags = require('../inline-tags');
+
+/**
+ * Initialize a `Tag` node with the given tag `name` and optional `block`.
+ *
+ * @param {String} name
+ * @param {Block} block
+ * @api public
+ */
+
+var Tag = module.exports = function Tag(name, block) {
+ Attrs.call(this);
+ this.name = name;
+ this.block = block || new Block;
+};
+
+// Inherit from `Attrs`.
+Tag.prototype = Object.create(Attrs.prototype);
+Tag.prototype.constructor = Tag;
+
+Tag.prototype.type = 'Tag';
+
+/**
+ * Clone this tag.
+ *
+ * @return {Tag}
+ * @api private
+ */
+
+Tag.prototype.clone = function(){
+ var err = new Error('tag.clone is deprecated and will be removed in v2.0.0');
+ console.warn(err.stack);
+
+ var clone = new Tag(this.name, this.block.clone());
+ clone.line = this.line;
+ clone.attrs = this.attrs;
+ clone.textOnly = this.textOnly;
+ return clone;
+};
+
+/**
+ * Check if this tag is an inline tag.
+ *
+ * @return {Boolean}
+ * @api private
+ */
+
+Tag.prototype.isInline = function(){
+ return ~inlineTags.indexOf(this.name);
+};
+
+/**
+ * Check if this tag's contents can be inlined. Used for pretty printing.
+ *
+ * @return {Boolean}
+ * @api private
+ */
+
+Tag.prototype.canInline = function(){
+ var nodes = this.block.nodes;
+
+ function isInline(node){
+ // Recurse if the node is a block
+ if (node.isBlock) return node.nodes.every(isInline);
+ return node.isText || (node.isInline && node.isInline());
+ }
+
+ // Empty tag
+ if (!nodes.length) return true;
+
+ // Text-only or inline-only tag
+ if (1 == nodes.length) return isInline(nodes[0]);
+
+ // Multi-line inline-only tag
+ if (this.block.nodes.every(isInline)) {
+ for (var i = 1, len = nodes.length; i < len; ++i) {
+ if (nodes[i-1].isText && nodes[i].isText)
+ return false;
+ }
+ return true;
+ }
+
+ // Mixed tag
+ return false;
+};
diff --git a/lib/nodes/text.js b/lib/nodes/text.js
new file mode 100644
index 0000000..c40ea85
--- /dev/null
+++ b/lib/nodes/text.js
@@ -0,0 +1,26 @@
+'use strict';
+
+var Node = require('./node');
+
+/**
+ * Initialize a `Text` node with optional `line`.
+ *
+ * @param {String} line
+ * @api public
+ */
+
+var Text = module.exports = function Text(line) {
+ this.val = line;
+};
+
+// Inherit from `Node`.
+Text.prototype = Object.create(Node.prototype);
+Text.prototype.constructor = Text;
+
+Text.prototype.type = 'Text';
+
+/**
+ * Flag as text.
+ */
+
+Text.prototype.isText = true;
\ No newline at end of file
diff --git a/lib/parser.js b/lib/parser.js
new file mode 100644
index 0000000..8f01e9f
--- /dev/null
+++ b/lib/parser.js
@@ -0,0 +1,809 @@
+'use strict';
+
+var Lexer = require('./lexer');
+var nodes = require('./nodes');
+var utils = require('./utils');
+var filters = require('./filters');
+var path = require('path');
+var constantinople = require('constantinople');
+var parseJSExpression = require('character-parser').parseMax;
+var extname = path.extname;
+
+/**
+ * Initialize `Parser` with the given input `str` and `filename`.
+ *
+ * @param {String} str
+ * @param {String} filename
+ * @param {Object} options
+ * @api public
+ */
+
+var Parser = exports = module.exports = function Parser(str, filename, options){
+ //Strip any UTF-8 BOM off of the start of `str`, if it exists.
+ this.input = str.replace(/^\uFEFF/, '');
+ this.lexer = new Lexer(this.input, filename);
+ this.filename = filename;
+ this.blocks = {};
+ this.mixins = {};
+ this.options = options;
+ this.contexts = [this];
+ this.inMixin = false;
+};
+
+/**
+ * Parser prototype.
+ */
+
+Parser.prototype = {
+
+ /**
+ * Save original constructor
+ */
+
+ constructor: Parser,
+
+ /**
+ * Push `parser` onto the context stack,
+ * or pop and return a `Parser`.
+ */
+
+ context: function(parser){
+ if (parser) {
+ this.contexts.push(parser);
+ } else {
+ return this.contexts.pop();
+ }
+ },
+
+ /**
+ * Return the next token object.
+ *
+ * @return {Object}
+ * @api private
+ */
+
+ advance: function(){
+ return this.lexer.advance();
+ },
+
+ /**
+ * Single token lookahead.
+ *
+ * @return {Object}
+ * @api private
+ */
+
+ peek: function() {
+ return this.lookahead(1);
+ },
+
+ /**
+ * Return lexer lineno.
+ *
+ * @return {Number}
+ * @api private
+ */
+
+ line: function() {
+ return this.lexer.lineno;
+ },
+
+ /**
+ * `n` token lookahead.
+ *
+ * @param {Number} n
+ * @return {Object}
+ * @api private
+ */
+
+ lookahead: function(n){
+ return this.lexer.lookahead(n);
+ },
+
+ /**
+ * Parse input returning a string of js for evaluation.
+ *
+ * @return {String}
+ * @api public
+ */
+
+ parse: function(){
+ var block = new nodes.Block, parser;
+ block.line = 0;
+ block.filename = this.filename;
+
+ while ('eos' != this.peek().type) {
+ if ('newline' == this.peek().type) {
+ this.advance();
+ } else {
+ var next = this.peek();
+ var expr = this.parseExpr();
+ expr.filename = expr.filename || this.filename;
+ expr.line = next.line;
+ block.push(expr);
+ }
+ }
+
+ if (parser = this.extending) {
+ this.context(parser);
+ var ast = parser.parse();
+ this.context();
+
+ // hoist mixins
+ for (var name in this.mixins)
+ ast.unshift(this.mixins[name]);
+ return ast;
+ }
+
+ return block;
+ },
+
+ /**
+ * Expect the given type, or throw an exception.
+ *
+ * @param {String} type
+ * @api private
+ */
+
+ expect: function(type){
+ if (this.peek().type === type) {
+ return this.advance();
+ } else {
+ throw new Error('expected "' + type + '", but got "' + this.peek().type + '"');
+ }
+ },
+
+ /**
+ * Accept the given `type`.
+ *
+ * @param {String} type
+ * @api private
+ */
+
+ accept: function(type){
+ if (this.peek().type === type) {
+ return this.advance();
+ }
+ },
+
+ /**
+ * tag
+ * | doctype
+ * | mixin
+ * | include
+ * | filter
+ * | comment
+ * | text
+ * | each
+ * | code
+ * | yield
+ * | id
+ * | class
+ * | interpolation
+ */
+
+ parseExpr: function(){
+ switch (this.peek().type) {
+ case 'tag':
+ return this.parseTag();
+ case 'mixin':
+ return this.parseMixin();
+ case 'block':
+ return this.parseBlock();
+ case 'mixin-block':
+ return this.parseMixinBlock();
+ case 'case':
+ return this.parseCase();
+ case 'extends':
+ return this.parseExtends();
+ case 'include':
+ return this.parseInclude();
+ case 'doctype':
+ return this.parseDoctype();
+ case 'filter':
+ return this.parseFilter();
+ case 'comment':
+ return this.parseComment();
+ case 'text':
+ return this.parseText();
+ case 'each':
+ return this.parseEach();
+ case 'code':
+ return this.parseCode();
+ case 'call':
+ return this.parseCall();
+ case 'interpolation':
+ return this.parseInterpolation();
+ case 'yield':
+ this.advance();
+ var block = new nodes.Block;
+ block.yield = true;
+ return block;
+ case 'id':
+ case 'class':
+ var tok = this.advance();
+ this.lexer.defer(this.lexer.tok('tag', 'div'));
+ this.lexer.defer(tok);
+ return this.parseExpr();
+ default:
+ throw new Error('unexpected token "' + this.peek().type + '"');
+ }
+ },
+
+ /**
+ * Text
+ */
+
+ parseText: function(){
+ var tok = this.expect('text');
+ var tokens = this.parseTextWithInlineTags(tok.val);
+ if (tokens.length === 1) return tokens[0];
+ var node = new nodes.Block;
+ for (var i = 0; i < tokens.length; i++) {
+ node.push(tokens[i]);
+ };
+ return node;
+ },
+
+ /**
+ * ':' expr
+ * | block
+ */
+
+ parseBlockExpansion: function(){
+ if (':' == this.peek().type) {
+ this.advance();
+ return new nodes.Block(this.parseExpr());
+ } else {
+ return this.block();
+ }
+ },
+
+ /**
+ * case
+ */
+
+ parseCase: function(){
+ var val = this.expect('case').val;
+ var node = new nodes.Case(val);
+ node.line = this.line();
+
+ var block = new nodes.Block;
+ block.line = this.line();
+ block.filename = this.filename;
+ this.expect('indent');
+ while ('outdent' != this.peek().type) {
+ switch (this.peek().type) {
+ case 'newline':
+ this.advance();
+ break;
+ case 'when':
+ block.push(this.parseWhen());
+ break;
+ case 'default':
+ block.push(this.parseDefault());
+ break;
+ default:
+ throw new Error('Unexpected token "' + this.peek().type
+ + '", expected "when", "default" or "newline"');
+ }
+ }
+ this.expect('outdent');
+
+ node.block = block;
+
+ return node;
+ },
+
+ /**
+ * when
+ */
+
+ parseWhen: function(){
+ var val = this.expect('when').val;
+ if (this.peek().type !== 'newline')
+ return new nodes.Case.When(val, this.parseBlockExpansion());
+ else
+ return new nodes.Case.When(val);
+ },
+
+ /**
+ * default
+ */
+
+ parseDefault: function(){
+ this.expect('default');
+ return new nodes.Case.When('default', this.parseBlockExpansion());
+ },
+
+ /**
+ * code
+ */
+
+ parseCode: function(afterIf){
+ var tok = this.expect('code');
+ var node = new nodes.Code(tok.val, tok.buffer, tok.escape);
+ var block;
+ node.line = this.line();
+
+ // throw an error if an else does not have an if
+ if (tok.isElse && !tok.hasIf) {
+ throw new Error('Unexpected else without if');
+ }
+
+ // handle block
+ block = 'indent' == this.peek().type;
+ if (block) {
+ node.block = this.block();
+ }
+
+ // handle missing block
+ if (tok.requiresBlock && !block) {
+ node.block = new nodes.Block();
+ }
+
+ // mark presense of if for future elses
+ if (tok.isIf && this.peek().isElse) {
+ this.peek().hasIf = true;
+ } else if (tok.isIf && this.peek().type === 'newline' && this.lookahead(2).isElse) {
+ this.lookahead(2).hasIf = true;
+ }
+
+ return node;
+ },
+
+ /**
+ * comment
+ */
+
+ parseComment: function(){
+ 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;
+ } else {
+ node = new nodes.Comment(tok.val, tok.buffer);
+ }
+
+ node.line = this.line();
+ return node;
+ },
+
+ /**
+ * doctype
+ */
+
+ parseDoctype: function(){
+ var tok = this.expect('doctype');
+ var node = new nodes.Doctype(tok.val);
+ node.line = this.line();
+ return node;
+ },
+
+ /**
+ * filter attrs? text-block
+ */
+
+ parseFilter: function(){
+ var tok = this.expect('filter');
+ 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;
+ }
+
+ var options = {};
+ if (attrs) {
+ attrs.attrs.forEach(function (attribute) {
+ options[attribute.name] = constantinople.toConstant(attribute.val);
+ });
+ }
+
+ var node = new nodes.Filter(tok.val, block, options);
+ node.line = this.line();
+ return node;
+ },
+
+ /**
+ * each block
+ */
+
+ parseEach: function(){
+ var tok = this.expect('each');
+ var node = new nodes.Each(tok.code, tok.val, tok.key);
+ node.line = this.line();
+ node.block = this.block();
+ if (this.peek().type == 'code' && this.peek().val == 'else') {
+ this.advance();
+ node.alternative = this.block();
+ }
+ return node;
+ },
+
+ /**
+ * Resolves a path relative to the template for use in
+ * includes and extends
+ *
+ * @param {String} path
+ * @param {String} purpose Used in error messages.
+ * @return {String}
+ * @api private
+ */
+
+ resolvePath: function (path, purpose) {
+ var p = require('path');
+ var dirname = p.dirname;
+ var basename = p.basename;
+ var join = p.join;
+
+ if (path[0] !== '/' && !this.filename)
+ throw new Error('the "filename" option is required to use "' + purpose + '" with "relative" paths');
+
+ if (path[0] === '/' && !this.options.basedir)
+ throw new Error('the "basedir" option is required to use "' + purpose + '" with "absolute" paths');
+
+ path = join(path[0] === '/' ? this.options.basedir : dirname(this.filename), path);
+
+ if (basename(path).indexOf('.') === -1) path += '.jade';
+
+ return path;
+ },
+
+ /**
+ * 'extends' name
+ */
+
+ parseExtends: function(){
+ var fs = require('fs');
+
+ var path = this.resolvePath(this.expect('extends').val.trim(), 'extends');
+ if ('.jade' != path.substr(-5)) path += '.jade';
+
+ var str = fs.readFileSync(path, 'utf8');
+ var parser = new this.constructor(str, path, this.options);
+
+ parser.blocks = this.blocks;
+ parser.contexts = this.contexts;
+ this.extending = parser;
+
+ // TODO: null node
+ return new nodes.Literal('');
+ },
+
+ /**
+ * 'block' name block
+ */
+
+ parseBlock: function(){
+ var block = this.expect('block');
+ var mode = block.mode;
+ var name = block.val.trim();
+
+ block = 'indent' == this.peek().type
+ ? this.block()
+ : new nodes.Block(new nodes.Literal(''));
+
+ var prev = this.blocks[name] || {prepended: [], appended: []}
+ if (prev.mode === 'replace') return this.blocks[name] = prev;
+
+ var allNodes = prev.prepended.concat(block.nodes).concat(prev.appended);
+
+ switch (mode) {
+ case 'append':
+ prev.appended = prev.parser === this ?
+ prev.appended.concat(block.nodes) :
+ block.nodes.concat(prev.appended);
+ break;
+ case 'prepend':
+ prev.prepended = prev.parser === this ?
+ block.nodes.concat(prev.prepended) :
+ prev.prepended.concat(block.nodes);
+ break;
+ }
+ block.nodes = allNodes;
+ block.appended = prev.appended;
+ block.prepended = prev.prepended;
+ block.mode = mode;
+ block.parser = this;
+
+ return this.blocks[name] = block;
+ },
+
+ parseMixinBlock: function () {
+ var block = this.expect('mixin-block');
+ if (!this.inMixin) {
+ throw new Error('Anonymous blocks are not allowed unless they are part of a mixin.');
+ }
+ return new nodes.MixinBlock();
+ },
+
+ /**
+ * include block?
+ */
+
+ parseInclude: function(){
+ var fs = require('fs');
+ var tok = this.expect('include');
+
+ var path = this.resolvePath(tok.val.trim(), 'include');
+
+ // has-filter
+ if (tok.filter) {
+ var str = fs.readFileSync(path, 'utf8').replace(/\r/g, '');
+ str = filters(tok.filter, str, { filename: path });
+ return new nodes.Literal(str);
+ }
+
+ // non-jade
+ if ('.jade' != path.substr(-5)) {
+ var str = fs.readFileSync(path, 'utf8').replace(/\r/g, '');
+ return new nodes.Literal(str);
+ }
+
+ var str = fs.readFileSync(path, 'utf8');
+ var parser = new this.constructor(str, path, this.options);
+ parser.blocks = utils.merge({}, this.blocks);
+
+ parser.mixins = this.mixins;
+
+ this.context(parser);
+ var ast = parser.parse();
+ this.context();
+ ast.filename = path;
+
+ if ('indent' == this.peek().type) {
+ ast.includeBlock().push(this.block());
+ }
+
+ return ast;
+ },
+
+ /**
+ * call ident block
+ */
+
+ parseCall: function(){
+ var tok = this.expect('call');
+ var name = tok.val;
+ var args = tok.args;
+ var mixin = new nodes.Mixin(name, args, new nodes.Block, true);
+
+ this.tag(mixin);
+ if (mixin.code) {
+ mixin.block.push(mixin.code);
+ mixin.code = null;
+ }
+ if (mixin.block.isEmpty()) mixin.block = null;
+ return mixin;
+ },
+
+ /**
+ * mixin block
+ */
+
+ parseMixin: function(){
+ var tok = this.expect('mixin');
+ var name = tok.val;
+ var args = tok.args;
+ var mixin;
+
+ // definition
+ if ('indent' == this.peek().type) {
+ this.inMixin = true;
+ mixin = new nodes.Mixin(name, args, this.block(), false);
+ this.mixins[name] = mixin;
+ this.inMixin = false;
+ return mixin;
+ // call
+ } else {
+ return new nodes.Mixin(name, args, null, true);
+ }
+ },
+
+ parseTextWithInlineTags: function (str) {
+ var line = this.line();
+
+ var match = /(\\)?#\[((?:.|\n)*)$/.exec(str);
+ if (match) {
+ if (match[1]) { // escape
+ var text = new nodes.Text(str.substr(0, match.index) + '#[');
+ text.line = line;
+ var rest = this.parseTextWithInlineTags(match[2]);
+ if (rest[0].type === 'Text') {
+ text.val += rest[0].val;
+ rest.shift();
+ }
+ return [text].concat(rest);
+ } else {
+ var text = new nodes.Text(str.substr(0, match.index));
+ text.line = line;
+ var buffer = [text];
+ var rest = match[2];
+ 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)));
+ }
+ } else {
+ var text = new nodes.Text(str);
+ text.line = line;
+ return [text];
+ }
+ },
+
+ /**
+ * indent (text | newline)* outdent
+ */
+
+ 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');
+
+ return block;
+ },
+
+ /**
+ * indent expr* outdent
+ */
+
+ block: function(){
+ var block = new nodes.Block;
+ block.line = this.line();
+ block.filename = this.filename;
+ this.expect('indent');
+ while ('outdent' != this.peek().type) {
+ if ('newline' == this.peek().type) {
+ this.advance();
+ } else {
+ var expr = this.parseExpr();
+ expr.filename = this.filename;
+ block.push(expr);
+ }
+ }
+ this.expect('outdent');
+ return block;
+ },
+
+ /**
+ * interpolation (attrs | class | id)* (text | code | ':')? newline* block?
+ */
+
+ parseInterpolation: function(){
+ var tok = this.advance();
+ var tag = new nodes.Tag(tok.val);
+ tag.buffer = true;
+ return this.tag(tag);
+ },
+
+ /**
+ * tag (attrs | class | id)* (text | code | ':')? newline* block?
+ */
+
+ parseTag: function(){
+ var tok = this.advance();
+ var tag = new nodes.Tag(tok.val);
+
+ tag.selfClosing = tok.selfClosing;
+
+ return this.tag(tag);
+ },
+
+ /**
+ * Parse tag.
+ */
+
+ tag: function(tag){
+ tag.line = this.line();
+
+ var seenAttrs = false;
+ // (attrs | class | id)*
+ out:
+ while (true) {
+ switch (this.peek().type) {
+ case 'id':
+ case 'class':
+ var tok = this.advance();
+ tag.setAttribute(tok.type, "'" + tok.val + "'");
+ continue;
+ case 'attrs':
+ if (seenAttrs) {
+ console.warn(this.filename + ', line ' + this.peek().line + ':\nYou should not have jade tags with multiple attributes.');
+ }
+ seenAttrs = true;
+ var tok = this.advance();
+ var attrs = tok.attrs;
+
+ if (tok.selfClosing) tag.selfClosing = true;
+
+ for (var i = 0; i < attrs.length; i++) {
+ tag.setAttribute(attrs[i].name, attrs[i].val, attrs[i].escaped);
+ }
+ continue;
+ case '&attributes':
+ var tok = this.advance();
+ tag.addAttributes(tok.val);
+ break;
+ default:
+ break out;
+ }
+ }
+
+ // check immediate '.'
+ if ('dot' == this.peek().type) {
+ tag.textOnly = true;
+ this.advance();
+ }
+
+ // (text | code | ':')?
+ switch (this.peek().type) {
+ case 'text':
+ tag.block.push(this.parseText());
+ break;
+ case 'code':
+ tag.code = this.parseCode();
+ break;
+ case ':':
+ this.advance();
+ tag.block = new nodes.Block;
+ tag.block.push(this.parseExpr());
+ break;
+ case 'newline':
+ case 'indent':
+ case 'outdent':
+ case 'eos':
+ break;
+ default:
+ throw new Error('Unexpected token `' + this.peek().type + '` expected `text`, `code`, `:`, `newline` or `eos`')
+ }
+
+ // newline*
+ 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]);
+ }
+ }
+ }
+
+ return tag;
+ }
+};
diff --git a/lib/runtime.js b/lib/runtime.js
new file mode 100644
index 0000000..237c33b
--- /dev/null
+++ b/lib/runtime.js
@@ -0,0 +1,203 @@
+'use strict';
+
+/**
+ * Merge two attribute objects giving precedence
+ * to values in object `b`. Classes are special-cased
+ * allowing for arrays and merging/joining appropriately
+ * resulting in a string.
+ *
+ * @param {Object} a
+ * @param {Object} b
+ * @return {Object} a
+ * @api private
+ */
+
+exports.merge = function merge(a, b) {
+ if (arguments.length === 1) {
+ var attrs = a[0];
+ for (var i = 1; i < a.length; i++) {
+ attrs = merge(attrs, a[i]);
+ }
+ return attrs;
+ }
+ var ac = a['class'];
+ var bc = b['class'];
+
+ if (ac || bc) {
+ ac = ac || [];
+ bc = bc || [];
+ if (!Array.isArray(ac)) ac = [ac];
+ if (!Array.isArray(bc)) bc = [bc];
+ a['class'] = ac.concat(bc).filter(nulls);
+ }
+
+ for (var key in b) {
+ if (key != 'class') {
+ a[key] = b[key];
+ }
+ }
+
+ return a;
+};
+
+/**
+ * Filter null `val`s.
+ *
+ * @param {*} val
+ * @return {Boolean}
+ * @api private
+ */
+
+function nulls(val) {
+ return val != null && val !== '';
+}
+
+/**
+ * join array as classes.
+ *
+ * @param {*} val
+ * @return {String}
+ */
+exports.joinClasses = joinClasses;
+function joinClasses(val) {
+ return Array.isArray(val) ? val.map(joinClasses).filter(nulls).join(' ') : val;
+}
+
+/**
+ * Render the given classes.
+ *
+ * @param {Array} classes
+ * @param {Array.<Boolean>} escaped
+ * @return {String}
+ */
+exports.cls = function cls(classes, escaped) {
+ var buf = [];
+ for (var i = 0; i < classes.length; i++) {
+ if (escaped && escaped[i]) {
+ buf.push(exports.escape(joinClasses([classes[i]])));
+ } else {
+ buf.push(joinClasses(classes[i]));
+ }
+ }
+ var text = joinClasses(buf);
+ if (text.length) {
+ return ' class="' + text + '"';
+ } else {
+ return '';
+ }
+};
+
+/**
+ * Render the given attribute.
+ *
+ * @param {String} key
+ * @param {String} val
+ * @param {Boolean} escaped
+ * @param {Boolean} terse
+ * @return {String}
+ */
+exports.attr = function attr(key, val, escaped, terse) {
+ if ('boolean' == typeof val || null == val) {
+ if (val) {
+ return ' ' + (terse ? key : key + '="' + key + '"');
+ } else {
+ return '';
+ }
+ } else if (0 == key.indexOf('data') && 'string' != typeof val) {
+ return ' ' + key + "='" + JSON.stringify(val).replace(/'/g, ''') + "'";
+ } else if (escaped) {
+ return ' ' + key + '="' + exports.escape(val) + '"';
+ } else {
+ return ' ' + key + '="' + val + '"';
+ }
+};
+
+/**
+ * Render the given attributes object.
+ *
+ * @param {Object} obj
+ * @param {Object} escaped
+ * @return {String}
+ */
+exports.attrs = function attrs(obj, terse){
+ var buf = [];
+
+ var keys = Object.keys(obj);
+
+ if (keys.length) {
+ for (var i = 0; i < keys.length; ++i) {
+ var key = keys[i]
+ , val = obj[key];
+
+ if ('class' == key) {
+ if (val = joinClasses(val)) {
+ buf.push(' ' + key + '="' + val + '"');
+ }
+ } else {
+ buf.push(exports.attr(key, val, false, terse));
+ }
+ }
+ }
+
+ return buf.join('');
+};
+
+/**
+ * Escape the given string of `html`.
+ *
+ * @param {String} html
+ * @return {String}
+ * @api private
+ */
+
+exports.escape = function escape(html){
+ var result = String(html)
+ .replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/"/g, '"');
+ if (result === '' + html) return html;
+ else return result;
+};
+
+/**
+ * Re-throw the given `err` in context to the
+ * the jade in `filename` at the given `lineno`.
+ *
+ * @param {Error} err
+ * @param {String} filename
+ * @param {String} lineno
+ * @api private
+ */
+
+exports.rethrow = function rethrow(err, filename, lineno, str){
+ if (!(err instanceof Error)) throw err;
+ if ((typeof window != 'undefined' || !filename) && !str) {
+ err.message += ' on line ' + lineno;
+ throw err;
+ }
+ try {
+ str = str || require('fs').readFileSync(filename, 'utf8')
+ } catch (ex) {
+ rethrow(err, null, lineno)
+ }
+ var context = 3
+ , lines = str.split('\n')
+ , start = Math.max(lineno - context, 0)
+ , end = Math.min(lines.length, lineno + context);
+
+ // Error context
+ var context = lines.slice(start, end).map(function(line, i){
+ var curr = i + start + 1;
+ return (curr == lineno ? ' > ' : ' ')
+ + curr
+ + '| '
+ + line;
+ }).join('\n');
+
+ // Alter exception message
+ err.path = filename;
+ err.message = (filename || 'Jade') + ':' + lineno
+ + '\n' + context + '\n\n' + err.message;
+ throw err;
+};
diff --git a/lib/self-closing.js b/lib/self-closing.js
new file mode 100644
index 0000000..3ab7de9
--- /dev/null
+++ b/lib/self-closing.js
@@ -0,0 +1,22 @@
+'use strict';
+
+// source: http://www.w3.org/html/wg/drafts/html/master/syntax.html#void-elements
+
+module.exports = [
+ 'area'
+ , 'base'
+ , 'br'
+ , 'col'
+ , 'embed'
+ , 'hr'
+ , 'img'
+ , 'input'
+ , 'keygen'
+ , 'link'
+ , 'menuitem'
+ , 'meta'
+ , 'param'
+ , 'source'
+ , 'track'
+ , 'wbr'
+];
diff --git a/lib/utils.js b/lib/utils.js
new file mode 100644
index 0000000..7c6782d
--- /dev/null
+++ b/lib/utils.js
@@ -0,0 +1,16 @@
+'use strict';
+
+/**
+ * Merge `b` into `a`.
+ *
+ * @param {Object} a
+ * @param {Object} b
+ * @return {Object}
+ * @api public
+ */
+
+exports.merge = function(a, b) {
+ for (var key in b) a[key] = b[key];
+ return a;
+};
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..64a3798
--- /dev/null
+++ b/package.json
@@ -0,0 +1,51 @@
+{
+ "name": "jade",
+ "description": "Jade template engine",
+ "version": "1.3.1",
+ "author": "TJ Holowaychuk <tj at vision-media.ca>",
+ "license": "MIT",
+ "repository": "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",
+ "transformers": "2.1.0",
+ "character-parser": "1.2.0",
+ "monocle": "1.1.51",
+ "with": "~3.0.0",
+ "constantinople": "~2.0.0"
+ },
+ "devDependencies": {
+ "coffee-script": "*",
+ "mocha": "*",
+ "istanbul": "*",
+ "markdown": "*",
+ "stylus": "*",
+ "should": "*",
+ "less": "*",
+ "uglify-js": "*",
+ "browserify": "*",
+ "linify": "*"
+ },
+ "component": {
+ "scripts": {
+ "jade": "runtime.js"
+ }
+ },
+ "scripts": {
+ "test": "mocha -R spec && npm run coverage",
+ "coverage": "istanbul cover node_modules/mocha/bin/_mocha",
+ "prepublish": "npm prune && linify transform bin && npm run build",
+ "build": "npm run compile",
+ "compile": "npm run compile-full && npm run compile-runtime",
+ "compile-full": "browserify ./lib/jade.js --standalone jade -x ./node_modules/transformers > jade.js",
+ "compile-runtime": "browserify ./lib/runtime.js --standalone jade > runtime.js"
+ },
+ "browser": {
+ "./lib/filters.js": "./lib/filters-client.js"
+ }
+}
\ No newline at end of file
diff --git a/support/benchmark.js b/support/benchmark.js
new file mode 100644
index 0000000..f362c5a
--- /dev/null
+++ b/support/benchmark.js
@@ -0,0 +1,121 @@
+
+/**
+ * Module dependencies.
+ */
+
+var uubench = require('uubench')
+ , jade = require('../');
+
+
+var suite = new uubench.Suite({
+ min: 200,
+ result: function(name, stats){
+ var persec = 1000 / stats.elapsed
+ , ops = stats.iterations * persec;
+ console.log('%s: %d', name, ops | 0);
+ }
+});
+
+function setup(self) {
+ var suffix = self ? ' (self)' : ''
+ , options = { self: self };
+
+ var str = 'html\n body\n h1 Title'
+ , fn = jade.compile(str, options);
+
+ suite.bench('tiny' + suffix, function(next){
+ fn();
+ next();
+ });
+
+str = '\
+html\n\
+ body\n\
+ h1 Title\n\
+ ul#menu\n\
+ li: a(href="#") Home\n\
+ li: a(href="#") About Us\n\
+ li: a(href="#") Store\n\
+ li: a(href="#") FAQ\n\
+ li: a(href="#") Contact\n\
+';
+
+ var fn2 = jade.compile(str, options);
+
+ suite.bench('small' + suffix, function(next){
+ fn2();
+ next();
+ });
+
+str = '\
+html\n\
+ body\n\
+ h1 #{title}\n\
+ ul#menu\n\
+ - each link in links\r\n\
+ li: a(href="#")= link\r\n\
+';
+
+ if (self) {
+str = '\
+html\n\
+ body\n\
+ h1 #{self.title}\n\
+ ul#menu\n\
+ - each link in self.links\r\n\
+ li: a(href="#")= link\r\n\
+';
+ }
+
+ var fn3 = jade.compile(str, options);
+
+ suite.bench('small locals' + suffix, function(next){
+ fn3({ title: 'Title', links: ['Home', 'About Us', 'Store', 'FAQ', 'Contact'] });
+ next();
+ });
+
+str = '\
+html\n\
+ body\n\
+ h1 Title\n\
+ ul#menu\n\
+ li: a(href="#") Home\n\
+ li: a(href="#") About Us\n\
+ li: a(href="#") Store\n\
+ li: a(href="#") FAQ\n\
+ li: a(href="#") Contact\n\
+';
+
+ str = Array(30).join(str);
+ var fn4 = jade.compile(str, options);
+
+ suite.bench('medium' + suffix, function(next){
+ fn4();
+ next();
+ });
+
+str = '\
+html\n\
+ body\n\
+ h1 Title\n\
+ ul#menu\n\
+ li: a(href="#") Home\n\
+ li: a(href="#") About Us\n\
+ li: a(href="#") Store\n\
+ li: a(href="#") FAQ\n\
+ li: a(href="#") Contact\n\
+';
+
+ str = Array(100).join(str);
+ var fn5 = jade.compile(str, options);
+
+ suite.bench('large' + suffix, function(next){
+ fn5();
+ next();
+ });
+}
+
+setup();
+setup(true);
+
+suite.run();
\ No newline at end of file
diff --git a/test/README.md b/test/README.md
new file mode 100644
index 0000000..0989992
--- /dev/null
+++ b/test/README.md
@@ -0,0 +1,15 @@
+# Running Tests
+
+To run tests (with node.js installed) you must complete 2 steps.
+
+## 1 Install dependencies
+
+```
+npm install
+```
+
+## 2 Run tests
+
+```
+npm test
+```
diff --git a/test/anti-cases/attrs.unescaped.jade b/test/anti-cases/attrs.unescaped.jade
new file mode 100644
index 0000000..ab47e09
--- /dev/null
+++ b/test/anti-cases/attrs.unescaped.jade
@@ -0,0 +1,3 @@
+script(type='text/x-template')
+ #user(id!='user-<%= user.id %>')
+ h1 <%= user.title %>
\ No newline at end of file
diff --git a/test/anti-cases/case-when.jade b/test/anti-cases/case-when.jade
new file mode 100644
index 0000000..74977d1
--- /dev/null
+++ b/test/anti-cases/case-when.jade
@@ -0,0 +1,4 @@
+when 5
+ .foo
+when 6
+ .bar
\ No newline at end of file
diff --git a/test/anti-cases/case-without-with.jade b/test/anti-cases/case-without-with.jade
new file mode 100644
index 0000000..3cbf016
--- /dev/null
+++ b/test/anti-cases/case-without-with.jade
@@ -0,0 +1,2 @@
+case foo
+ .div
\ No newline at end of file
diff --git a/test/anti-cases/doctype-5.jade b/test/anti-cases/doctype-5.jade
new file mode 100644
index 0000000..15069a4
--- /dev/null
+++ b/test/anti-cases/doctype-5.jade
@@ -0,0 +1,2 @@
+doctype 5
+//not a doctype
\ No newline at end of file
diff --git a/test/anti-cases/else-condition.jade b/test/anti-cases/else-condition.jade
new file mode 100644
index 0000000..93ff87e
--- /dev/null
+++ b/test/anti-cases/else-condition.jade
@@ -0,0 +1,4 @@
+if foo
+ div
+else bar
+ article
\ No newline at end of file
diff --git a/test/anti-cases/else-without-if.jade b/test/anti-cases/else-without-if.jade
new file mode 100644
index 0000000..3062364
--- /dev/null
+++ b/test/anti-cases/else-without-if.jade
@@ -0,0 +1,2 @@
+else
+ .foo
\ No newline at end of file
diff --git a/test/anti-cases/inlining-a-mixin-after-a-tag.jade b/test/anti-cases/inlining-a-mixin-after-a-tag.jade
new file mode 100644
index 0000000..3d01493
--- /dev/null
+++ b/test/anti-cases/inlining-a-mixin-after-a-tag.jade
@@ -0,0 +1 @@
+foo()+bar()
\ No newline at end of file
diff --git a/test/anti-cases/key-char-ending-badly.jade b/test/anti-cases/key-char-ending-badly.jade
new file mode 100644
index 0000000..45ca24a
--- /dev/null
+++ b/test/anti-cases/key-char-ending-badly.jade
@@ -0,0 +1 @@
+div("foo"abc)
diff --git a/test/anti-cases/key-ending-badly.jade b/test/anti-cases/key-ending-badly.jade
new file mode 100644
index 0000000..8e3c305
--- /dev/null
+++ b/test/anti-cases/key-ending-badly.jade
@@ -0,0 +1 @@
+div(foo!~abc)
diff --git a/test/anti-cases/mixins-blocks-with-bodies.jade b/test/anti-cases/mixins-blocks-with-bodies.jade
new file mode 100644
index 0000000..e7e6281
--- /dev/null
+++ b/test/anti-cases/mixins-blocks-with-bodies.jade
@@ -0,0 +1,3 @@
+mixin foo
+ block
+ bar
\ No newline at end of file
diff --git a/test/anti-cases/multiple-non-nested-tags-on-a-line.jade b/test/anti-cases/multiple-non-nested-tags-on-a-line.jade
new file mode 100644
index 0000000..fc9c884
--- /dev/null
+++ b/test/anti-cases/multiple-non-nested-tags-on-a-line.jade
@@ -0,0 +1 @@
+foo()bar
\ No newline at end of file
diff --git a/test/anti-cases/non-existant-filter.jade b/test/anti-cases/non-existant-filter.jade
new file mode 100644
index 0000000..8caa922
--- /dev/null
+++ b/test/anti-cases/non-existant-filter.jade
@@ -0,0 +1,2 @@
+:not-a-valid-filter
+ foo bar
\ No newline at end of file
diff --git a/test/anti-cases/non-mixin-block.jade b/test/anti-cases/non-mixin-block.jade
new file mode 100644
index 0000000..11ff9bc
--- /dev/null
+++ b/test/anti-cases/non-mixin-block.jade
@@ -0,0 +1,2 @@
+div
+ block
\ No newline at end of file
diff --git a/test/anti-cases/open-brace-in-attributes.jade b/test/anti-cases/open-brace-in-attributes.jade
new file mode 100644
index 0000000..7b5f21d
--- /dev/null
+++ b/test/anti-cases/open-brace-in-attributes.jade
@@ -0,0 +1 @@
+div(title=[)
\ No newline at end of file
diff --git a/test/anti-cases/readme.md b/test/anti-cases/readme.md
new file mode 100644
index 0000000..12678a5
--- /dev/null
+++ b/test/anti-cases/readme.md
@@ -0,0 +1 @@
+This folder collects examples of files that are not valid `jade`, but were at some point accepted by the parser without throwing an error. The tests ensure that all these cases now throw some form of error message (hopefully a helpful one).
\ No newline at end of file
diff --git a/test/anti-cases/self-closing-tag-with-body.jade b/test/anti-cases/self-closing-tag-with-body.jade
new file mode 100644
index 0000000..55fbed1
--- /dev/null
+++ b/test/anti-cases/self-closing-tag-with-body.jade
@@ -0,0 +1 @@
+input Input's can't have content
\ No newline at end of file
diff --git a/test/anti-cases/self-closing.jade b/test/anti-cases/self-closing.jade
new file mode 100644
index 0000000..e210f22
--- /dev/null
+++ b/test/anti-cases/self-closing.jade
@@ -0,0 +1,2 @@
+doctype html
+img Image with content?
\ No newline at end of file
diff --git a/test/anti-cases/tabs-and-spaces.jade b/test/anti-cases/tabs-and-spaces.jade
new file mode 100644
index 0000000..07868c2
--- /dev/null
+++ b/test/anti-cases/tabs-and-spaces.jade
@@ -0,0 +1,3 @@
+div
+ div
+ article
\ No newline at end of file
diff --git a/test/browser/index.html b/test/browser/index.html
new file mode 100644
index 0000000..2a8f257
--- /dev/null
+++ b/test/browser/index.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html><html><head></head><body><textarea id="input" placeholder="write jade here" style="width: 100%; min-height: 400px;">p
+ author
+ != myName</textarea><pre style="background: #ECECEC;width: 100%; min-height: 400px;"><code id="output"></code></pre><script src="../../jade.js"></script><script>var input = document.getElementById('input');
+var output = document.getElementById('output');
+setInterval(function () {
+ jade.render(input.value, {myName: 'Forbes Lindesay', pretty: true}, function (err, res) {
+ if (err) throw err;
+ output.textContent = res;
+ })
+}, 500)</script></body></html>
\ No newline at end of file
diff --git a/test/browser/index.jade b/test/browser/index.jade
new file mode 100644
index 0000000..e71e808
--- /dev/null
+++ b/test/browser/index.jade
@@ -0,0 +1,20 @@
+!!! 5
+html
+ head
+ body
+ textarea#input(placeholder='write jade here', style='width: 100%; min-height: 400px;').
+ p
+ author
+ != myName
+ pre(style='background: #ECECEC;width: 100%; min-height: 400px;')
+ code#output
+ script(src='../../jade.js')
+ script.
+ var input = document.getElementById('input');
+ var output = document.getElementById('output');
+ setInterval(function () {
+ jade.render(input.value, {myName: 'Forbes Lindesay', pretty: true}, function (err, res) {
+ if (err) throw err;
+ output.textContent = res;
+ })
+ }, 500)
\ No newline at end of file
diff --git a/test/cases/attrs-data.html b/test/cases/attrs-data.html
new file mode 100644
index 0000000..cbf661a
--- /dev/null
+++ b/test/cases/attrs-data.html
@@ -0,0 +1,4 @@
+<foo data-user='{"name":"tobi"}'></foo>
+<foo data-items='[1,2,3]'></foo>
+<foo data-username="tobi"></foo>
+<foo data-escaped='{"message":"Let's rock!"}'></foo>
diff --git a/test/cases/attrs-data.jade b/test/cases/attrs-data.jade
new file mode 100644
index 0000000..4e47db4
--- /dev/null
+++ b/test/cases/attrs-data.jade
@@ -0,0 +1,5 @@
+- var user = { name: 'tobi' }
+foo(data-user=user)
+foo(data-items=[1,2,3])
+foo(data-username='tobi')
+foo(data-escaped={message: "Let's rock!"})
diff --git a/test/cases/attrs.html b/test/cases/attrs.html
new file mode 100644
index 0000000..08edc72
--- /dev/null
+++ b/test/cases/attrs.html
@@ -0,0 +1,13 @@
+<a href="/contact">contact</a><a href="/save" class="button">save</a><a foo="foo" bar="bar" baz="baz"></a><a foo="foo, bar, baz" bar="1"></a><a foo="((foo))" bar="1"></a>
+<select>
+ <option value="foo" selected="selected">Foo</option>
+ <option selected="selected" value="bar">Bar</option>
+</select><a foo="class:"></a>
+<input pattern="\S+"/><a href="/contact">contact</a><a href="/save" class="button">save</a><a foo="foo" bar="bar" baz="baz"></a><a foo="foo, bar, baz" bar="1"></a><a foo="((foo))" bar="1"></a>
+<select>
+ <option value="foo" selected="selected">Foo</option>
+ <option selected="selected" value="bar">Bar</option>
+</select><a foo="class:"></a>
+<input pattern="\S+"/>
+<foo terse="true"></foo>
+<div foo="bar" bar="<baz>"></div>
\ No newline at end of file
diff --git a/test/cases/attrs.interpolation.html b/test/cases/attrs.interpolation.html
new file mode 100644
index 0000000..26c5530
--- /dev/null
+++ b/test/cases/attrs.interpolation.html
@@ -0,0 +1,2 @@
+<a href="/user/5"></a>
+<foo bar="stuff #{here} yup"></foo>
\ No newline at end of file
diff --git a/test/cases/attrs.interpolation.jade b/test/cases/attrs.interpolation.jade
new file mode 100644
index 0000000..ae1ea2b
--- /dev/null
+++ b/test/cases/attrs.interpolation.jade
@@ -0,0 +1,3 @@
+- var id = 5
+a(href='/user/#{id}')
+foo(bar='stuff \#{here} yup')
\ No newline at end of file
diff --git a/test/cases/attrs.jade b/test/cases/attrs.jade
new file mode 100644
index 0000000..7bd76c2
--- /dev/null
+++ b/test/cases/attrs.jade
@@ -0,0 +1,26 @@
+a(href='/contact') contact
+a(href='/save').button save
+a(foo, bar, baz)
+a(foo='foo, bar, baz', bar=1)
+a(foo='((foo))', bar= (1) ? 1 : 0 )
+select
+ option(value='foo', selected) Foo
+ option(selected, value='bar') Bar
+a(foo="class:")
+input(pattern='\\S+')
+
+a(href='/contact') contact
+a(href='/save').button save
+a(foo bar baz)
+a(foo='foo, bar, baz' bar=1)
+a(foo='((foo))' bar= (1) ? 1 : 0 )
+select
+ option(value='foo' selected) Foo
+ option(selected value='bar') Bar
+a(foo="class:")
+input(pattern='\\S+')
+foo(terse="true")
+
+- var attrs = {foo: 'bar', bar: '<baz>'}
+
+div&attributes(attrs)
diff --git a/test/cases/attrs.js.html b/test/cases/attrs.js.html
new file mode 100644
index 0000000..d5ea0c4
--- /dev/null
+++ b/test/cases/attrs.js.html
@@ -0,0 +1,5 @@
+<a href="/user/5" class="button"></a><a href="/user/5" class="button"></a>
+<meta key="answer" value="42"/><a class="class1 class2"></a><a class="tag-class class1 class2"></a><a href="/user/5" class="button"></a><a href="/user/5" class="button"></a>
+<meta key="answer" value="42"/><a class="class1 class2"></a><a class="tag-class class1 class2"></a>
+<div id="5" foo="bar"></div>
+<div baz="baz"></div>
\ No newline at end of file
diff --git a/test/cases/attrs.js.jade b/test/cases/attrs.js.jade
new file mode 100644
index 0000000..910c13a
--- /dev/null
+++ b/test/cases/attrs.js.jade
@@ -0,0 +1,17 @@
+- var id = 5
+- function answer() { return 42; }
+a(href='/user/' + id, class='button')
+a(href = '/user/' + id, class = 'button')
+meta(key='answer', value=answer())
+a(class = ['class1', 'class2'])
+a.tag-class(class = ['class1', 'class2'])
+
+a(href='/user/' + id class='button')
+a(href = '/user/' + id class = 'button')
+meta(key='answer' value=answer())
+a(class = ['class1', 'class2'])
+a.tag-class(class = ['class1', 'class2'])
+
+div(id=id)&attributes({foo: 'bar'})
+- var bar = null
+div(foo=null bar=bar)&attributes({baz: 'baz'})
diff --git a/test/cases/attrs.unescaped.html b/test/cases/attrs.unescaped.html
new file mode 100644
index 0000000..2c2f3f1
--- /dev/null
+++ b/test/cases/attrs.unescaped.html
@@ -0,0 +1,5 @@
+<script type="text/x-template">
+ <div id="user-<%= user.id %>">
+ <h1><%= user.title %></h1>
+ </div>
+</script>
\ No newline at end of file
diff --git a/test/cases/attrs.unescaped.jade b/test/cases/attrs.unescaped.jade
new file mode 100644
index 0000000..36a4e10
--- /dev/null
+++ b/test/cases/attrs.unescaped.jade
@@ -0,0 +1,3 @@
+script(type='text/x-template')
+ div(id!='user-<%= user.id %>')
+ h1 <%= user.title %>
\ No newline at end of file
diff --git a/test/cases/auxiliary/dialog.jade b/test/cases/auxiliary/dialog.jade
new file mode 100644
index 0000000..838025c
--- /dev/null
+++ b/test/cases/auxiliary/dialog.jade
@@ -0,0 +1,6 @@
+
+extends window
+
+block window-content
+ .dialog
+ block content
\ No newline at end of file
diff --git a/test/cases/auxiliary/empty-block.jade b/test/cases/auxiliary/empty-block.jade
new file mode 100644
index 0000000..776e5fe
--- /dev/null
+++ b/test/cases/auxiliary/empty-block.jade
@@ -0,0 +1,2 @@
+block test
+
diff --git a/test/cases/auxiliary/escapes.html b/test/cases/auxiliary/escapes.html
new file mode 100644
index 0000000..3b414f2
--- /dev/null
+++ b/test/cases/auxiliary/escapes.html
@@ -0,0 +1,3 @@
+<script>
+ console.log("foo\nbar")
+</script>
\ No newline at end of file
diff --git a/test/cases/auxiliary/extends-empty-block-1.jade b/test/cases/auxiliary/extends-empty-block-1.jade
new file mode 100644
index 0000000..5354a80
--- /dev/null
+++ b/test/cases/auxiliary/extends-empty-block-1.jade
@@ -0,0 +1,5 @@
+extends empty-block
+
+block test
+ div test1
+
diff --git a/test/cases/auxiliary/extends-empty-block-2.jade b/test/cases/auxiliary/extends-empty-block-2.jade
new file mode 100644
index 0000000..cbe65d8
--- /dev/null
+++ b/test/cases/auxiliary/extends-empty-block-2.jade
@@ -0,0 +1,5 @@
+extends empty-block
+
+block test
+ div test2
+
diff --git a/test/cases/auxiliary/extends-from-root.jade b/test/cases/auxiliary/extends-from-root.jade
new file mode 100644
index 0000000..0271858
--- /dev/null
+++ b/test/cases/auxiliary/extends-from-root.jade
@@ -0,0 +1,4 @@
+extends /auxiliary/layout
+
+block content
+ include /auxiliary/include-from-root
\ No newline at end of file
diff --git a/test/cases/auxiliary/includable.js b/test/cases/auxiliary/includable.js
new file mode 100644
index 0000000..f3efe1e
--- /dev/null
+++ b/test/cases/auxiliary/includable.js
@@ -0,0 +1,7 @@
+var STRING_SUBSTITUTIONS = { // table of character substitutions
+ '\t': '\\t',
+ '\r': '\\r',
+ '\n': '\\n',
+ '"' : '\\"',
+ '\\': '\\\\'
+};
\ No newline at end of file
diff --git a/test/cases/auxiliary/include-from-root.jade b/test/cases/auxiliary/include-from-root.jade
new file mode 100644
index 0000000..93c364b
--- /dev/null
+++ b/test/cases/auxiliary/include-from-root.jade
@@ -0,0 +1 @@
+h1 hello
\ No newline at end of file
diff --git a/test/cases/auxiliary/inheritance.extend.mixin.block.jade b/test/cases/auxiliary/inheritance.extend.mixin.block.jade
new file mode 100644
index 0000000..890febc
--- /dev/null
+++ b/test/cases/auxiliary/inheritance.extend.mixin.block.jade
@@ -0,0 +1,11 @@
+mixin article()
+ article
+ block
+
+html
+ head
+ title My Application
+ block head
+ body
+ +article
+ block content
diff --git a/test/cases/auxiliary/inheritance.extend.recursive-grand-grandparent.jade b/test/cases/auxiliary/inheritance.extend.recursive-grand-grandparent.jade
new file mode 100644
index 0000000..61033fa
--- /dev/null
+++ b/test/cases/auxiliary/inheritance.extend.recursive-grand-grandparent.jade
@@ -0,0 +1,2 @@
+h1 grand-grandparent
+block grand-grandparent
\ No newline at end of file
diff --git a/test/cases/auxiliary/inheritance.extend.recursive-grandparent.jade b/test/cases/auxiliary/inheritance.extend.recursive-grandparent.jade
new file mode 100644
index 0000000..0377a0c
--- /dev/null
+++ b/test/cases/auxiliary/inheritance.extend.recursive-grandparent.jade
@@ -0,0 +1,6 @@
+extends inheritance.extend.recursive-grand-grandparent.jade
+
+block grand-grandparent
+ h2 grandparent
+ block grandparent
+
diff --git a/test/cases/auxiliary/inheritance.extend.recursive-parent.jade b/test/cases/auxiliary/inheritance.extend.recursive-parent.jade
new file mode 100644
index 0000000..19ec928
--- /dev/null
+++ b/test/cases/auxiliary/inheritance.extend.recursive-parent.jade
@@ -0,0 +1,5 @@
+extends inheritance.extend.recursive-grandparent.jade
+
+block grandparent
+ h3 parent
+ block parent
\ No newline at end of file
diff --git a/test/cases/auxiliary/layout.include.jade b/test/cases/auxiliary/layout.include.jade
new file mode 100644
index 0000000..78c3b23
--- /dev/null
+++ b/test/cases/auxiliary/layout.include.jade
@@ -0,0 +1,7 @@
+html
+ head
+ title My Application
+ block head
+ body
+ block content
+ include window
\ No newline at end of file
diff --git a/test/cases/auxiliary/layout.jade b/test/cases/auxiliary/layout.jade
new file mode 100644
index 0000000..7d183b3
--- /dev/null
+++ b/test/cases/auxiliary/layout.jade
@@ -0,0 +1,6 @@
+html
+ head
+ title My Application
+ block head
+ body
+ block content
\ No newline at end of file
diff --git a/test/cases/auxiliary/mixins.jade b/test/cases/auxiliary/mixins.jade
new file mode 100644
index 0000000..0c14c1d
--- /dev/null
+++ b/test/cases/auxiliary/mixins.jade
@@ -0,0 +1,3 @@
+
+mixin foo()
+ p bar
\ No newline at end of file
diff --git a/test/cases/auxiliary/pet.jade b/test/cases/auxiliary/pet.jade
new file mode 100644
index 0000000..ebee3a8
--- /dev/null
+++ b/test/cases/auxiliary/pet.jade
@@ -0,0 +1,3 @@
+.pet
+ h1 {{name}}
+ p {{name}} is a {{species}} that is {{age}} old
\ No newline at end of file
diff --git a/test/cases/auxiliary/smile.html b/test/cases/auxiliary/smile.html
new file mode 100644
index 0000000..05a0c49
--- /dev/null
+++ b/test/cases/auxiliary/smile.html
@@ -0,0 +1 @@
+<p>:)</p>
\ No newline at end of file
diff --git a/test/cases/auxiliary/window.jade b/test/cases/auxiliary/window.jade
new file mode 100644
index 0000000..7ab7132
--- /dev/null
+++ b/test/cases/auxiliary/window.jade
@@ -0,0 +1,4 @@
+
+.window
+ a(href='#').close Close
+ block window-content
\ No newline at end of file
diff --git a/test/cases/auxiliary/yield-nested.jade b/test/cases/auxiliary/yield-nested.jade
new file mode 100644
index 0000000..0771c0a
--- /dev/null
+++ b/test/cases/auxiliary/yield-nested.jade
@@ -0,0 +1,10 @@
+html
+ head
+ title
+ body
+ h1 Page
+ #content
+ #content-wrapper
+ yield
+ #footer
+ stuff
\ No newline at end of file
diff --git a/test/cases/basic.html b/test/cases/basic.html
new file mode 100644
index 0000000..a01532a
--- /dev/null
+++ b/test/cases/basic.html
@@ -0,0 +1,5 @@
+<html>
+ <body>
+ <h1>Title</h1>
+ </body>
+</html>
\ No newline at end of file
diff --git a/test/cases/basic.jade b/test/cases/basic.jade
new file mode 100644
index 0000000..77066d1
--- /dev/null
+++ b/test/cases/basic.jade
@@ -0,0 +1,3 @@
+html
+ body
+ h1 Title
\ No newline at end of file
diff --git a/test/cases/blanks.html b/test/cases/blanks.html
new file mode 100644
index 0000000..d58268c
--- /dev/null
+++ b/test/cases/blanks.html
@@ -0,0 +1,5 @@
+<ul>
+ <li>foo</li>
+ <li>bar</li>
+ <li>baz</li>
+</ul>
\ No newline at end of file
diff --git a/test/cases/blanks.jade b/test/cases/blanks.jade
new file mode 100644
index 0000000..67b0697
--- /dev/null
+++ b/test/cases/blanks.jade
@@ -0,0 +1,8 @@
+
+
+ul
+ li foo
+
+ li bar
+
+ li baz
diff --git a/test/cases/block-expansion.html b/test/cases/block-expansion.html
new file mode 100644
index 0000000..3c24259
--- /dev/null
+++ b/test/cases/block-expansion.html
@@ -0,0 +1,5 @@
+<ul>
+ <li><a href="#">foo</a></li>
+ <li><a href="#">bar</a></li>
+</ul>
+<p>baz</p>
\ No newline at end of file
diff --git a/test/cases/block-expansion.jade b/test/cases/block-expansion.jade
new file mode 100644
index 0000000..fb40f9a
--- /dev/null
+++ b/test/cases/block-expansion.jade
@@ -0,0 +1,5 @@
+ul
+ li: a(href='#') foo
+ li: a(href='#') bar
+
+p baz
\ No newline at end of file
diff --git a/test/cases/block-expansion.shorthands.html b/test/cases/block-expansion.shorthands.html
new file mode 100644
index 0000000..96cf0e7
--- /dev/null
+++ b/test/cases/block-expansion.shorthands.html
@@ -0,0 +1,7 @@
+<ul>
+ <li class="list-item">
+ <div class="foo">
+ <div id="bar">baz</div>
+ </div>
+ </li>
+</ul>
\ No newline at end of file
diff --git a/test/cases/block-expansion.shorthands.jade b/test/cases/block-expansion.shorthands.jade
new file mode 100644
index 0000000..c52a126
--- /dev/null
+++ b/test/cases/block-expansion.shorthands.jade
@@ -0,0 +1,2 @@
+ul
+ li.list-item: .foo: #bar baz
\ No newline at end of file
diff --git a/test/cases/blockquote.html b/test/cases/blockquote.html
new file mode 100644
index 0000000..92b64de
--- /dev/null
+++ b/test/cases/blockquote.html
@@ -0,0 +1,4 @@
+<figure>
+ <blockquote>Try to define yourself by what you do, and you’ll burnout every time. You are. That is enough. I rest in that.</blockquote>
+ <figcaption>from @thefray at 1:43pm on May 10</figcaption>
+</figure>
\ No newline at end of file
diff --git a/test/cases/blockquote.jade b/test/cases/blockquote.jade
new file mode 100644
index 0000000..a23b70f
--- /dev/null
+++ b/test/cases/blockquote.jade
@@ -0,0 +1,4 @@
+figure
+ blockquote
+ | Try to define yourself by what you do, and you’ll burnout every time. You are. That is enough. I rest in that.
+ figcaption from @thefray at 1:43pm on May 10
\ No newline at end of file
diff --git a/test/cases/case-blocks.html b/test/cases/case-blocks.html
new file mode 100644
index 0000000..893b07d
--- /dev/null
+++ b/test/cases/case-blocks.html
@@ -0,0 +1,5 @@
+<html>
+ <body>
+ <p>you have a friend</p>
+ </body>
+</html>
\ No newline at end of file
diff --git a/test/cases/case-blocks.jade b/test/cases/case-blocks.jade
new file mode 100644
index 0000000..345cd41
--- /dev/null
+++ b/test/cases/case-blocks.jade
@@ -0,0 +1,10 @@
+html
+ body
+ - 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
\ No newline at end of file
diff --git a/test/cases/case.html b/test/cases/case.html
new file mode 100644
index 0000000..30fae96
--- /dev/null
+++ b/test/cases/case.html
@@ -0,0 +1,6 @@
+<html>
+ <body>
+ <p>you have a friend</p>
+ <p>you have very few friends</p>
+ </body>
+</html>
\ No newline at end of file
diff --git a/test/cases/case.jade b/test/cases/case.jade
new file mode 100644
index 0000000..a14a8c5
--- /dev/null
+++ b/test/cases/case.jade
@@ -0,0 +1,14 @@
+html
+ body
+ - 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
+ - var friends = 0
+ case friends
+ when 0
+ when 1
+ p you have very few friends
+ default
+ p you have #{friends} friends
\ No newline at end of file
diff --git a/test/cases/classes-empty.html b/test/cases/classes-empty.html
new file mode 100644
index 0000000..bcc28a9
--- /dev/null
+++ b/test/cases/classes-empty.html
@@ -0,0 +1 @@
+<a></a><a></a><a></a>
\ No newline at end of file
diff --git a/test/cases/classes-empty.jade b/test/cases/classes-empty.jade
new file mode 100644
index 0000000..5e66d84
--- /dev/null
+++ b/test/cases/classes-empty.jade
@@ -0,0 +1,3 @@
+a(class='')
+a(class=null)
+a(class=undefined)
\ No newline at end of file
diff --git a/test/cases/classes.html b/test/cases/classes.html
new file mode 100644
index 0000000..6faf6bc
--- /dev/null
+++ b/test/cases/classes.html
@@ -0,0 +1 @@
+<a class="foo bar baz"></a><a class="foo bar baz"></a><a class="foo-bar_baz"></a>
\ No newline at end of file
diff --git a/test/cases/classes.jade b/test/cases/classes.jade
new file mode 100644
index 0000000..496e791
--- /dev/null
+++ b/test/cases/classes.jade
@@ -0,0 +1,9 @@
+a(class=['foo', 'bar', 'baz'])
+
+
+
+a.foo(class='bar').baz
+
+
+
+a.foo-bar_baz
\ No newline at end of file
diff --git a/test/cases/code.conditionals.html b/test/cases/code.conditionals.html
new file mode 100644
index 0000000..72b0c3e
--- /dev/null
+++ b/test/cases/code.conditionals.html
@@ -0,0 +1,10 @@
+<p>foo</p>
+<p>foo</p>
+<p>foo</p>
+<p>bar</p>
+<p>baz</p>
+<p>bar</p>
+<p>yay</p>
+<div class="bar"></div>
+<div class="bar"></div>
+<div class="bing"></div>
\ No newline at end of file
diff --git a/test/cases/code.conditionals.jade b/test/cases/code.conditionals.jade
new file mode 100644
index 0000000..697d727
--- /dev/null
+++ b/test/cases/code.conditionals.jade
@@ -0,0 +1,36 @@
+
+- if (true)
+ p foo
+- else
+ p bar
+
+- if (true) {
+ p foo
+- } else {
+ p bar
+- }
+
+if true
+ p foo
+ p bar
+ p baz
+else
+ p bar
+
+unless true
+ p foo
+else
+ p bar
+
+if 'nested'
+ if 'works'
+ p yay
+
+//- allow empty blocks
+if false
+else
+ .bar
+if true
+ .bar
+else
+.bing
\ No newline at end of file
diff --git a/test/cases/code.escape.html b/test/cases/code.escape.html
new file mode 100644
index 0000000..c0e1758
--- /dev/null
+++ b/test/cases/code.escape.html
@@ -0,0 +1,2 @@
+<p><script></p>
+<p><script></p>
\ No newline at end of file
diff --git a/test/cases/code.escape.jade b/test/cases/code.escape.jade
new file mode 100644
index 0000000..762c089
--- /dev/null
+++ b/test/cases/code.escape.jade
@@ -0,0 +1,2 @@
+p= '<script>'
+p!= '<script>'
\ No newline at end of file
diff --git a/test/cases/code.html b/test/cases/code.html
new file mode 100644
index 0000000..e8ff07c
--- /dev/null
+++ b/test/cases/code.html
@@ -0,0 +1,10 @@
+<p></p>
+<p></p>
+<p></p>
+<p>0</p>
+<p>false</p>
+<p></p>
+<p></p>
+<p foo=""></p>
+<p foo="0"></p>
+<p></p>
\ No newline at end of file
diff --git a/test/cases/code.iteration.html b/test/cases/code.iteration.html
new file mode 100644
index 0000000..6c1bf69
--- /dev/null
+++ b/test/cases/code.iteration.html
@@ -0,0 +1,36 @@
+<ul>
+ <li>1</li>
+ <li>2</li>
+ <li>3</li>
+</ul>
+<ul>
+ <li class="item-0">1</li>
+ <li class="item-1">2</li>
+ <li class="item-2">3</li>
+</ul>
+<ul>
+ <li>1</li>
+ <li>2</li>
+ <li>3</li>
+</ul>
+<ul>
+ <li>1</li>
+ <li>2</li>
+ <li>3</li>
+</ul>
+<ul>
+ <li>1: a</li>
+ <li>2: a</li>
+ <li>3: a</li>
+ <li>1: b</li>
+ <li>2: b</li>
+ <li>3: b</li>
+ <li>1: c</li>
+ <li>2: c</li>
+ <li>3: c</li>
+</ul>
+<ul>
+ <li>1</li>
+ <li>2</li>
+ <li>3</li>
+</ul>
\ No newline at end of file
diff --git a/test/cases/code.iteration.jade b/test/cases/code.iteration.jade
new file mode 100644
index 0000000..6065ef1
--- /dev/null
+++ b/test/cases/code.iteration.jade
@@ -0,0 +1,35 @@
+
+- var items = [1,2,3]
+
+ul
+ - items.forEach(function(item){
+ li= item
+ - })
+
+- var items = [1,2,3]
+
+ul
+ for item, i in items
+ li(class='item-#{i}')= item
+
+ul
+ each item, i in items
+ li= item
+
+ul
+ each $item in items
+ li= $item
+
+- var nums = [1, 2, 3]
+- var letters = ['a', 'b', 'c']
+
+ul
+ for l in letters
+ for n in nums
+ li #{n}: #{l}
+
+- var count = 1
+- var counter = function() { return [count++, count++, count++] }
+ul
+ for n in counter()
+ li #{n}
\ No newline at end of file
diff --git a/test/cases/code.jade b/test/cases/code.jade
new file mode 100644
index 0000000..aa794a1
--- /dev/null
+++ b/test/cases/code.jade
@@ -0,0 +1,10 @@
+p= null
+p= undefined
+p= ''
+p= 0
+p= false
+p(foo=null)
+p(foo=undefined)
+p(foo='')
+p(foo=0)
+p(foo=false)
\ No newline at end of file
diff --git a/test/cases/comments.html b/test/cases/comments.html
new file mode 100644
index 0000000..fcf801f
--- /dev/null
+++ b/test/cases/comments.html
@@ -0,0 +1,32 @@
+
+<!-- foo-->
+<ul>
+ <!-- bar-->
+ <li>one</li>
+ <!-- baz-->
+ <li>two</li>
+</ul>
+<!--
+ul
+ li foo
+
+-->
+<!-- block
+// inline follow
+li three
+
+-->
+<!-- block
+// inline followed by tags
+ul
+ li four
+
+-->
+<!--if IE lt 9
+// inline
+script(src='/lame.js')
+// end-inline
+
+-->
+<p>five</p>
+<div class="foo">// not a comment</div>
\ No newline at end of file
diff --git a/test/cases/comments.jade b/test/cases/comments.jade
new file mode 100644
index 0000000..4fd9aa0
--- /dev/null
+++ b/test/cases/comments.jade
@@ -0,0 +1,29 @@
+
+// foo
+ul
+ // bar
+ li one
+ // baz
+ li two
+
+//
+ ul
+ li foo
+
+// block
+ // inline follow
+ li three
+
+// block
+ // inline followed by tags
+ ul
+ li four
+
+//if IE lt 9
+ // inline
+ script(src='/lame.js')
+ // end-inline
+
+p five
+
+.foo // not a comment
\ No newline at end of file
diff --git a/test/cases/comments.source.html b/test/cases/comments.source.html
new file mode 100644
index 0000000..e69de29
diff --git a/test/cases/comments.source.jade b/test/cases/comments.source.jade
new file mode 100644
index 0000000..934727d
--- /dev/null
+++ b/test/cases/comments.source.jade
@@ -0,0 +1,9 @@
+//-
+ s/s.
+
+//- test/cases/comments.source.jade
+
+//-
+ test/cases/comments.source.jade
+ when
+ ()
diff --git a/test/cases/custom-filter.html b/test/cases/custom-filter.html
new file mode 100644
index 0000000..1320970
--- /dev/null
+++ b/test/cases/custom-filter.html
@@ -0,0 +1 @@
+bar baz
\ No newline at end of file
diff --git a/test/cases/custom-filter.jade b/test/cases/custom-filter.jade
new file mode 100644
index 0000000..def880a
--- /dev/null
+++ b/test/cases/custom-filter.jade
@@ -0,0 +1,2 @@
+:custom-filter(foo='bar')
+ foo bar
\ No newline at end of file
diff --git a/test/cases/doctype.custom.html b/test/cases/doctype.custom.html
new file mode 100644
index 0000000..59a0e61
--- /dev/null
+++ b/test/cases/doctype.custom.html
@@ -0,0 +1 @@
+<!DOCTYPE custom stuff>
\ No newline at end of file
diff --git a/test/cases/doctype.custom.jade b/test/cases/doctype.custom.jade
new file mode 100644
index 0000000..9cb2605
--- /dev/null
+++ b/test/cases/doctype.custom.jade
@@ -0,0 +1 @@
+doctype custom stuff
\ No newline at end of file
diff --git a/test/cases/doctype.default.html b/test/cases/doctype.default.html
new file mode 100644
index 0000000..eb7d765
--- /dev/null
+++ b/test/cases/doctype.default.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <h1>Title</h1>
+ </body>
+</html>
\ No newline at end of file
diff --git a/test/cases/doctype.default.jade b/test/cases/doctype.default.jade
new file mode 100644
index 0000000..992885e
--- /dev/null
+++ b/test/cases/doctype.default.jade
@@ -0,0 +1,4 @@
+doctype
+html
+ body
+ h1 Title
\ No newline at end of file
diff --git a/test/cases/doctype.keyword.html b/test/cases/doctype.keyword.html
new file mode 100644
index 0000000..763b073
--- /dev/null
+++ b/test/cases/doctype.keyword.html
@@ -0,0 +1 @@
+<!DOCTYPE html>
\ No newline at end of file
diff --git a/test/cases/doctype.keyword.jade b/test/cases/doctype.keyword.jade
new file mode 100644
index 0000000..3dd566e
--- /dev/null
+++ b/test/cases/doctype.keyword.jade
@@ -0,0 +1 @@
+doctype html
\ No newline at end of file
diff --git a/test/cases/each.else.html b/test/cases/each.else.html
new file mode 100644
index 0000000..69d5f2e
--- /dev/null
+++ b/test/cases/each.else.html
@@ -0,0 +1,20 @@
+<ul>
+ <li>no users!</li>
+</ul>
+<ul>
+ <li>tobi</li>
+ <li>loki</li>
+</ul>
+<ul>
+ <li>name: tobi</li>
+ <li>age: 10</li>
+</ul>
+<ul>
+ <li>user has no details!</li>
+</ul>
+<ul>
+ <li>user has no details!</li>
+</ul>
+<ul>
+ <li>name: tobi</li>
+</ul>
diff --git a/test/cases/each.else.jade b/test/cases/each.else.jade
new file mode 100644
index 0000000..688b1ad
--- /dev/null
+++ b/test/cases/each.else.jade
@@ -0,0 +1,49 @@
+
+- var users = []
+
+ul
+ for user in users
+ li= user.name
+ else
+ li no users!
+
+
+- var users = [{ name: 'tobi', friends: ['loki'] }, { name: 'loki' }]
+
+if users
+ ul
+ for user in users
+ li= user.name
+ else
+ li no users!
+
+- var user = { name: 'tobi', age: 10 }
+
+ul
+ each val, key in user
+ li #{key}: #{val}
+ else
+ li user has no details!
+
+- var user = {}
+
+ul
+ each prop, key in user
+ li #{key}: #{val}
+ else
+ li user has no details!
+
+ul
+ - each prop, key in user
+ li #{key}: #{val}
+ - else
+ li user has no details!
+
+- var user = Object.create(null)
+- user.name = 'tobi'
+
+ul
+ each val, key in user
+ li #{key}: #{val}
+ else
+ li user has no details!
diff --git a/test/cases/escape-chars.html b/test/cases/escape-chars.html
new file mode 100644
index 0000000..2fb37ce
--- /dev/null
+++ b/test/cases/escape-chars.html
@@ -0,0 +1 @@
+<script>var re = /\d+/;</script>
\ No newline at end of file
diff --git a/test/cases/escape-chars.jade b/test/cases/escape-chars.jade
new file mode 100644
index 0000000..f7978d6
--- /dev/null
+++ b/test/cases/escape-chars.jade
@@ -0,0 +1,2 @@
+script.
+ var re = /\d+/;
\ No newline at end of file
diff --git a/test/cases/escape-test.html b/test/cases/escape-test.html
new file mode 100644
index 0000000..5df8727
--- /dev/null
+++ b/test/cases/escape-test.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>escape-test</title>
+ </head>
+ <body>
+ <textarea><param name="flashvars" value="a="value_a"&b="value_b"&c=3"/>
+ </textarea>
+ </body>
+</html>
diff --git a/test/cases/escape-test.jade b/test/cases/escape-test.jade
new file mode 100644
index 0000000..168c549
--- /dev/null
+++ b/test/cases/escape-test.jade
@@ -0,0 +1,8 @@
+doctype html
+html
+ head
+ title escape-test
+ body
+ textarea
+ - var txt = '<param name="flashvars" value="a="value_a"&b="value_b"&c=3"/>'
+ | #{txt}
diff --git a/test/cases/escaping-class-attribute.html b/test/cases/escaping-class-attribute.html
new file mode 100644
index 0000000..9563642
--- /dev/null
+++ b/test/cases/escaping-class-attribute.html
@@ -0,0 +1,6 @@
+<foo attr="<%= bar %>"></foo>
+<foo class="<%= bar %>"></foo>
+<foo attr="<%= bar %>"></foo>
+<foo class="<%= bar %>"></foo>
+<foo class="<%= bar %> lol rofl"></foo>
+<foo class="<%= bar %> lol rofl <%= lmao %>"></foo>
\ No newline at end of file
diff --git a/test/cases/escaping-class-attribute.jade b/test/cases/escaping-class-attribute.jade
new file mode 100644
index 0000000..dffbb8b
--- /dev/null
+++ b/test/cases/escaping-class-attribute.jade
@@ -0,0 +1,6 @@
+foo(attr="<%= bar %>")
+foo(class="<%= bar %>")
+foo(attr!="<%= bar %>")
+foo(class!="<%= bar %>")
+foo(class!="<%= bar %> lol rofl")
+foo(class!="<%= bar %> lol rofl <%= lmao %>")
diff --git a/test/cases/filters-empty.html b/test/cases/filters-empty.html
new file mode 100644
index 0000000..9ad128f
--- /dev/null
+++ b/test/cases/filters-empty.html
@@ -0,0 +1,4 @@
+<fb:users>
+ <fb:user age="2"><![CDATA[]]>
+ </fb:user>
+</fb:users>
diff --git a/test/cases/filters-empty.jade b/test/cases/filters-empty.jade
new file mode 100644
index 0000000..7aa64de
--- /dev/null
+++ b/test/cases/filters-empty.jade
@@ -0,0 +1,6 @@
+- var users = [{ name: 'tobi', age: 2 }]
+
+fb:users
+ for user in users
+ fb:user(age=user.age)
+ :cdata
diff --git a/test/cases/filters.cdata.html b/test/cases/filters.cdata.html
new file mode 100644
index 0000000..05c5bc1
--- /dev/null
+++ b/test/cases/filters.cdata.html
@@ -0,0 +1,4 @@
+<fb:users>
+ <fb:user age="2"><![CDATA[tobi]]>
+ </fb:user>
+</fb:users>
\ No newline at end of file
diff --git a/test/cases/filters.cdata.jade b/test/cases/filters.cdata.jade
new file mode 100644
index 0000000..a9f0921
--- /dev/null
+++ b/test/cases/filters.cdata.jade
@@ -0,0 +1,8 @@
+
+- users = [{ name: 'tobi', age: 2 }]
+
+fb:users
+ for user in users
+ fb:user(age=user.age)
+ :cdata
+ #{user.name}
\ No newline at end of file
diff --git a/test/cases/filters.coffeescript.html b/test/cases/filters.coffeescript.html
new file mode 100644
index 0000000..e14001a
--- /dev/null
+++ b/test/cases/filters.coffeescript.html
@@ -0,0 +1,9 @@
+<script type="text/javascript">
+(function() {
+ var regexp;
+
+ regexp = /\n/;
+
+}).call(this);
+(function(){var n;n={square:function(n){return n*n}}}).call(this);
+</script>
\ No newline at end of file
diff --git a/test/cases/filters.coffeescript.jade b/test/cases/filters.coffeescript.jade
new file mode 100644
index 0000000..3941b44
--- /dev/null
+++ b/test/cases/filters.coffeescript.jade
@@ -0,0 +1,6 @@
+script(type='text/javascript')
+ :coffeescript
+ regexp = /\n/
+ :coffeescript(minify=true)
+ math =
+ square: (value) -> value * value
\ No newline at end of file
diff --git a/test/cases/filters.less.html b/test/cases/filters.less.html
new file mode 100644
index 0000000..5cdb913
--- /dev/null
+++ b/test/cases/filters.less.html
@@ -0,0 +1,7 @@
+<html>
+ <head><style type="text/css">body {
+ padding: 15px;
+}
+</style>
+ </head>
+</html>
\ No newline at end of file
diff --git a/test/cases/filters.less.jade b/test/cases/filters.less.jade
new file mode 100644
index 0000000..a3df945
--- /dev/null
+++ b/test/cases/filters.less.jade
@@ -0,0 +1,8 @@
+html
+ head
+ style(type="text/css")
+ :less
+ @pad: 15px;
+ body {
+ padding: @pad;
+ }
diff --git a/test/cases/filters.markdown.html b/test/cases/filters.markdown.html
new file mode 100644
index 0000000..aa3d975
--- /dev/null
+++ b/test/cases/filters.markdown.html
@@ -0,0 +1,5 @@
+<html>
+ <body><p>This is <em>some</em> awesome <strong>markdown</strong>
+whoop.</p>
+ </body>
+</html>
\ No newline at end of file
diff --git a/test/cases/filters.markdown.jade b/test/cases/filters.markdown.jade
new file mode 100644
index 0000000..a36edf8
--- /dev/null
+++ b/test/cases/filters.markdown.jade
@@ -0,0 +1,5 @@
+html
+ body
+ :markdown
+ This is _some_ awesome **markdown**
+ whoop.
\ No newline at end of file
diff --git a/test/cases/filters.stylus.html b/test/cases/filters.stylus.html
new file mode 100644
index 0000000..d131a14
--- /dev/null
+++ b/test/cases/filters.stylus.html
@@ -0,0 +1,8 @@
+<html>
+ <head><style type="text/css">body {
+ padding: 50px;
+}
+</style>
+ </head>
+ <body></body>
+</html>
\ No newline at end of file
diff --git a/test/cases/filters.stylus.jade b/test/cases/filters.stylus.jade
new file mode 100644
index 0000000..323d29c
--- /dev/null
+++ b/test/cases/filters.stylus.jade
@@ -0,0 +1,7 @@
+html
+ head
+ style(type="text/css")
+ :stylus
+ body
+ padding: 50px
+ body
diff --git a/test/cases/html.html b/test/cases/html.html
new file mode 100644
index 0000000..1b942e9
--- /dev/null
+++ b/test/cases/html.html
@@ -0,0 +1,6 @@
+<ul>
+<li>foo</li>
+<li>bar</li>
+<li>baz</li>
+</ul>
+<p>You can <em>embed</em> html as well.</p>
\ No newline at end of file
diff --git a/test/cases/html.jade b/test/cases/html.jade
new file mode 100644
index 0000000..dff7aba
--- /dev/null
+++ b/test/cases/html.jade
@@ -0,0 +1,8 @@
+
+<ul>
+<li>foo</li>
+<li>bar</li>
+<li>baz</li>
+</ul>
+
+p You can <em>embed</em> html as well.
\ No newline at end of file
diff --git a/test/cases/html5.html b/test/cases/html5.html
new file mode 100644
index 0000000..83a553a
--- /dev/null
+++ b/test/cases/html5.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<input type="checkbox" checked>
+<input type="checkbox" checked>
+<input type="checkbox">
\ No newline at end of file
diff --git a/test/cases/html5.jade b/test/cases/html5.jade
new file mode 100644
index 0000000..8dc68e2
--- /dev/null
+++ b/test/cases/html5.jade
@@ -0,0 +1,4 @@
+doctype html
+input(type='checkbox', checked)
+input(type='checkbox', checked=true)
+input(type='checkbox', checked=false)
\ No newline at end of file
diff --git a/test/cases/include-extends-from-root.html b/test/cases/include-extends-from-root.html
new file mode 100644
index 0000000..3916f5d
--- /dev/null
+++ b/test/cases/include-extends-from-root.html
@@ -0,0 +1,8 @@
+<html>
+ <head>
+ <title>My Application</title>
+ </head>
+ <body>
+ <h1>hello</h1>
+ </body>
+</html>
\ No newline at end of file
diff --git a/test/cases/include-extends-from-root.jade b/test/cases/include-extends-from-root.jade
new file mode 100644
index 0000000..f682dc6
--- /dev/null
+++ b/test/cases/include-extends-from-root.jade
@@ -0,0 +1 @@
+include /auxiliary/extends-from-root
\ No newline at end of file
diff --git a/test/cases/include-extends-of-common-template.html b/test/cases/include-extends-of-common-template.html
new file mode 100644
index 0000000..dd04738
--- /dev/null
+++ b/test/cases/include-extends-of-common-template.html
@@ -0,0 +1,2 @@
+<div>test1</div>
+<div>test2</div>
diff --git a/test/cases/include-extends-of-common-template.jade b/test/cases/include-extends-of-common-template.jade
new file mode 100644
index 0000000..a918085
--- /dev/null
+++ b/test/cases/include-extends-of-common-template.jade
@@ -0,0 +1,2 @@
+include auxiliary/extends-empty-block-1
+include auxiliary/extends-empty-block-2
diff --git a/test/cases/include-filter-stylus.html b/test/cases/include-filter-stylus.html
new file mode 100644
index 0000000..d019c26
--- /dev/null
+++ b/test/cases/include-filter-stylus.html
@@ -0,0 +1,4 @@
+<style type="text/css">body {
+ padding: 10px;
+}
+</style>
diff --git a/test/cases/include-filter-stylus.jade b/test/cases/include-filter-stylus.jade
new file mode 100644
index 0000000..bf57710
--- /dev/null
+++ b/test/cases/include-filter-stylus.jade
@@ -0,0 +1,2 @@
+style(type="text/css")
+ include:styl some.styl
diff --git a/test/cases/include-filter.html b/test/cases/include-filter.html
new file mode 100644
index 0000000..b460b97
--- /dev/null
+++ b/test/cases/include-filter.html
@@ -0,0 +1,6 @@
+<html>
+ <body><p>Just <em>some</em> markdown <strong>tests</strong>.</p>
+
+<p>With new line.</p>
+ </body>
+</html>
diff --git a/test/cases/include-filter.jade b/test/cases/include-filter.jade
new file mode 100644
index 0000000..443f900
--- /dev/null
+++ b/test/cases/include-filter.jade
@@ -0,0 +1,3 @@
+html
+ body
+ include:md some.md
diff --git a/test/cases/include-only-text-body.html b/test/cases/include-only-text-body.html
new file mode 100644
index 0000000..f86b593
--- /dev/null
+++ b/test/cases/include-only-text-body.html
@@ -0,0 +1 @@
+The message is ""
\ No newline at end of file
diff --git a/test/cases/include-only-text-body.jade b/test/cases/include-only-text-body.jade
new file mode 100644
index 0000000..fdb080c
--- /dev/null
+++ b/test/cases/include-only-text-body.jade
@@ -0,0 +1,3 @@
+| The message is "
+yield
+| "
diff --git a/test/cases/include-only-text.html b/test/cases/include-only-text.html
new file mode 100644
index 0000000..6936ae4
--- /dev/null
+++ b/test/cases/include-only-text.html
@@ -0,0 +1,5 @@
+<html>
+ <body>
+ <p>The message is "<em>hello world</em>"</p>
+ </body>
+</html>
\ No newline at end of file
diff --git a/test/cases/include-only-text.jade b/test/cases/include-only-text.jade
new file mode 100644
index 0000000..497d419
--- /dev/null
+++ b/test/cases/include-only-text.jade
@@ -0,0 +1,5 @@
+html
+ body
+ p
+ include include-only-text-body
+ em hello world
diff --git a/test/cases/include-with-text-head.html b/test/cases/include-with-text-head.html
new file mode 100644
index 0000000..716f359
--- /dev/null
+++ b/test/cases/include-with-text-head.html
@@ -0,0 +1,3 @@
+<head>
+ <script type="text/javascript">alert('hello world');</script>
+</head>
\ No newline at end of file
diff --git a/test/cases/include-with-text-head.jade b/test/cases/include-with-text-head.jade
new file mode 100644
index 0000000..4e670c0
--- /dev/null
+++ b/test/cases/include-with-text-head.jade
@@ -0,0 +1,3 @@
+head
+ script(type='text/javascript').
+ alert('hello world');
diff --git a/test/cases/include-with-text.html b/test/cases/include-with-text.html
new file mode 100644
index 0000000..78386f7
--- /dev/null
+++ b/test/cases/include-with-text.html
@@ -0,0 +1,7 @@
+<html>
+ <head>
+ <script type="text/javascript">alert('hello world');</script>
+ <script src="/caustic.js"></script>
+ <script src="/app.js"></script>
+ </head>
+</html>
\ No newline at end of file
diff --git a/test/cases/include-with-text.jade b/test/cases/include-with-text.jade
new file mode 100644
index 0000000..b35c459
--- /dev/null
+++ b/test/cases/include-with-text.jade
@@ -0,0 +1,4 @@
+html
+ include include-with-text-head
+ script(src='/caustic.js')
+ script(src='/app.js')
diff --git a/test/cases/include.script.html b/test/cases/include.script.html
new file mode 100644
index 0000000..cdd37c2
--- /dev/null
+++ b/test/cases/include.script.html
@@ -0,0 +1,6 @@
+<script id="pet-template" type="text/x-template">
+ <div class="pet">
+ <h1>{{name}}</h1>
+ <p>{{name}} is a {{species}} that is {{age}} old</p>
+ </div>
+</script>
\ No newline at end of file
diff --git a/test/cases/include.script.jade b/test/cases/include.script.jade
new file mode 100644
index 0000000..c8a6c19
--- /dev/null
+++ b/test/cases/include.script.jade
@@ -0,0 +1,2 @@
+script#pet-template(type='text/x-template')
+ include auxiliary/pet
\ No newline at end of file
diff --git a/test/cases/include.yield.nested.html b/test/cases/include.yield.nested.html
new file mode 100644
index 0000000..947b615
--- /dev/null
+++ b/test/cases/include.yield.nested.html
@@ -0,0 +1,17 @@
+<html>
+ <head>
+ <title></title>
+ </head>
+ <body>
+ <h1>Page</h1>
+ <div id="content">
+ <div id="content-wrapper">
+ <p>some content</p>
+ <p>and some more</p>
+ </div>
+ </div>
+ <div id="footer">
+ <stuff></stuff>
+ </div>
+ </body>
+</html>
\ No newline at end of file
diff --git a/test/cases/include.yield.nested.jade b/test/cases/include.yield.nested.jade
new file mode 100644
index 0000000..b8d4ba2
--- /dev/null
+++ b/test/cases/include.yield.nested.jade
@@ -0,0 +1,4 @@
+
+include auxiliary/yield-nested
+ p some content
+ p and some more
\ No newline at end of file
diff --git a/test/cases/includes-with-ext-js.html b/test/cases/includes-with-ext-js.html
new file mode 100644
index 0000000..c36c363
--- /dev/null
+++ b/test/cases/includes-with-ext-js.html
@@ -0,0 +1,2 @@
+<pre><code>var x = "\n here is some \n new lined text";
+</code></pre>
diff --git a/test/cases/includes-with-ext-js.jade b/test/cases/includes-with-ext-js.jade
new file mode 100644
index 0000000..65bfa8a
--- /dev/null
+++ b/test/cases/includes-with-ext-js.jade
@@ -0,0 +1,3 @@
+pre
+ code
+ include javascript-new-lines.js
diff --git a/test/cases/includes.html b/test/cases/includes.html
new file mode 100644
index 0000000..aa4b192
--- /dev/null
+++ b/test/cases/includes.html
@@ -0,0 +1,13 @@
+<p>bar</p>
+<body><p>:)</p><script>
+ console.log("foo\nbar")
+</script>
+ <script type="text/javascript">var STRING_SUBSTITUTIONS = { // table of character substitutions
+ '\t': '\\t',
+ '\r': '\\r',
+ '\n': '\\n',
+ '"' : '\\"',
+ '\\': '\\\\'
+};
+ </script>
+</body>
diff --git a/test/cases/includes.jade b/test/cases/includes.jade
new file mode 100644
index 0000000..df2592e
--- /dev/null
+++ b/test/cases/includes.jade
@@ -0,0 +1,10 @@
+
+include auxiliary/mixins
+
++foo
+
+body
+ include auxiliary/smile.html
+ include auxiliary/escapes.html
+ script(type="text/javascript")
+ include:js auxiliary/includable.js
diff --git a/test/cases/inheritance.alert-dialog.html b/test/cases/inheritance.alert-dialog.html
new file mode 100644
index 0000000..f028c56
--- /dev/null
+++ b/test/cases/inheritance.alert-dialog.html
@@ -0,0 +1,6 @@
+<div class="window"><a href="#" class="close">Close</a>
+ <div class="dialog">
+ <h1>Alert!</h1>
+ <p>I'm an alert!</p>
+ </div>
+</div>
\ No newline at end of file
diff --git a/test/cases/inheritance.alert-dialog.jade b/test/cases/inheritance.alert-dialog.jade
new file mode 100644
index 0000000..0d02d76
--- /dev/null
+++ b/test/cases/inheritance.alert-dialog.jade
@@ -0,0 +1,6 @@
+
+extends auxiliary/dialog
+
+block content
+ h1 Alert!
+ p I'm an alert!
\ No newline at end of file
diff --git a/test/cases/inheritance.defaults.html b/test/cases/inheritance.defaults.html
new file mode 100644
index 0000000..e6878d1
--- /dev/null
+++ b/test/cases/inheritance.defaults.html
@@ -0,0 +1,7 @@
+<html>
+ <head>
+ <script src="jquery.js"></script>
+ <script src="keymaster.js"></script>
+ <script src="caustic.js"></script>
+ </head>
+</html>
\ No newline at end of file
diff --git a/test/cases/inheritance.defaults.jade b/test/cases/inheritance.defaults.jade
new file mode 100644
index 0000000..aaead83
--- /dev/null
+++ b/test/cases/inheritance.defaults.jade
@@ -0,0 +1,6 @@
+html
+ head
+ block head
+ script(src='jquery.js')
+ script(src='keymaster.js')
+ script(src='caustic.js')
\ No newline at end of file
diff --git a/test/cases/inheritance.extend.html b/test/cases/inheritance.extend.html
new file mode 100644
index 0000000..1f4eae4
--- /dev/null
+++ b/test/cases/inheritance.extend.html
@@ -0,0 +1,10 @@
+<html>
+ <head>
+ <title>My Application</title>
+ <script src="jquery.js"></script>
+ </head>
+ <body>
+ <h2>Page</h2>
+ <p>Some content</p>
+ </body>
+</html>
\ No newline at end of file
diff --git a/test/cases/inheritance.extend.include.html b/test/cases/inheritance.extend.include.html
new file mode 100644
index 0000000..cf965b4
--- /dev/null
+++ b/test/cases/inheritance.extend.include.html
@@ -0,0 +1,14 @@
+<html>
+ <head>
+ <title>My Application</title>
+ <script src="jquery.js"></script>
+ </head>
+ <body>
+ <h2>Page</h2>
+ <p>Some content</p>
+ <div class="window"><a href="#" class="close">Close</a>
+ <h2>Awesome</h2>
+ <p>Now we can extend included blocks!</p>
+ </div>
+ </body>
+</html>
\ No newline at end of file
diff --git a/test/cases/inheritance.extend.include.jade b/test/cases/inheritance.extend.include.jade
new file mode 100644
index 0000000..efa29f0
--- /dev/null
+++ b/test/cases/inheritance.extend.include.jade
@@ -0,0 +1,13 @@
+
+extend auxiliary/layout.include
+
+block head
+ script(src='jquery.js')
+
+block content
+ h2 Page
+ p Some content
+
+block window-content
+ h2 Awesome
+ p Now we can extend included blocks!
\ No newline at end of file
diff --git a/test/cases/inheritance.extend.jade b/test/cases/inheritance.extend.jade
new file mode 100644
index 0000000..1fabb55
--- /dev/null
+++ b/test/cases/inheritance.extend.jade
@@ -0,0 +1,9 @@
+
+extend auxiliary/layout
+
+block head
+ script(src='jquery.js')
+
+block content
+ h2 Page
+ p Some content
\ No newline at end of file
diff --git a/test/cases/inheritance.extend.mixins.block.html b/test/cases/inheritance.extend.mixins.block.html
new file mode 100644
index 0000000..0ea5d94
--- /dev/null
+++ b/test/cases/inheritance.extend.mixins.block.html
@@ -0,0 +1,10 @@
+<html>
+ <head>
+ <title>My Application</title>
+ </head>
+ <body>
+ <article>
+ <p>Hello World!</p>
+ </article>
+ </body>
+</html>
diff --git a/test/cases/inheritance.extend.mixins.block.jade b/test/cases/inheritance.extend.mixins.block.jade
new file mode 100644
index 0000000..398627c
--- /dev/null
+++ b/test/cases/inheritance.extend.mixins.block.jade
@@ -0,0 +1,4 @@
+extend auxiliary/inheritance.extend.mixin.block
+
+block content
+ p Hello World!
diff --git a/test/cases/inheritance.extend.mixins.html b/test/cases/inheritance.extend.mixins.html
new file mode 100644
index 0000000..618e2b1
--- /dev/null
+++ b/test/cases/inheritance.extend.mixins.html
@@ -0,0 +1,9 @@
+<html>
+ <head>
+ <title>My Application</title>
+ </head>
+ <body>
+ <h1>The meaning of life</h1>
+ <p>Foo bar baz!</p>
+ </body>
+</html>
\ No newline at end of file
diff --git a/test/cases/inheritance.extend.mixins.jade b/test/cases/inheritance.extend.mixins.jade
new file mode 100644
index 0000000..e369e76
--- /dev/null
+++ b/test/cases/inheritance.extend.mixins.jade
@@ -0,0 +1,11 @@
+
+extend auxiliary/layout
+
+mixin article(title)
+ if title
+ h1= title
+ block
+
+block content
+ +article("The meaning of life")
+ p Foo bar baz!
\ No newline at end of file
diff --git a/test/cases/inheritance.extend.recursive.html b/test/cases/inheritance.extend.recursive.html
new file mode 100644
index 0000000..d5d0522
--- /dev/null
+++ b/test/cases/inheritance.extend.recursive.html
@@ -0,0 +1,4 @@
+<h1>grand-grandparent</h1>
+<h2>grandparent</h2>
+<h3>parent</h3>
+<h4>child</h4>
\ No newline at end of file
diff --git a/test/cases/inheritance.extend.recursive.jade b/test/cases/inheritance.extend.recursive.jade
new file mode 100644
index 0000000..2b14e21
--- /dev/null
+++ b/test/cases/inheritance.extend.recursive.jade
@@ -0,0 +1,4 @@
+extends /auxiliary/inheritance.extend.recursive-parent.jade
+
+block parent
+ h4 child
\ No newline at end of file
diff --git a/test/cases/inheritance.extend.whitespace.html b/test/cases/inheritance.extend.whitespace.html
new file mode 100644
index 0000000..1f4eae4
--- /dev/null
+++ b/test/cases/inheritance.extend.whitespace.html
@@ -0,0 +1,10 @@
+<html>
+ <head>
+ <title>My Application</title>
+ <script src="jquery.js"></script>
+ </head>
+ <body>
+ <h2>Page</h2>
+ <p>Some content</p>
+ </body>
+</html>
\ No newline at end of file
diff --git a/test/cases/inheritance.extend.whitespace.jade b/test/cases/inheritance.extend.whitespace.jade
new file mode 100644
index 0000000..ebdb8e4
--- /dev/null
+++ b/test/cases/inheritance.extend.whitespace.jade
@@ -0,0 +1,13 @@
+
+extend auxiliary/layout
+
+block head
+
+ script(src='jquery.js')
+
+block content
+
+
+
+ h2 Page
+ p Some content
\ No newline at end of file
diff --git a/test/cases/inheritance.html b/test/cases/inheritance.html
new file mode 100644
index 0000000..1f4eae4
--- /dev/null
+++ b/test/cases/inheritance.html
@@ -0,0 +1,10 @@
+<html>
+ <head>
+ <title>My Application</title>
+ <script src="jquery.js"></script>
+ </head>
+ <body>
+ <h2>Page</h2>
+ <p>Some content</p>
+ </body>
+</html>
\ No newline at end of file
diff --git a/test/cases/inheritance.jade b/test/cases/inheritance.jade
new file mode 100644
index 0000000..567408f
--- /dev/null
+++ b/test/cases/inheritance.jade
@@ -0,0 +1,9 @@
+
+extends auxiliary/layout
+
+block head
+ script(src='jquery.js')
+
+block content
+ h2 Page
+ p Some content
\ No newline at end of file
diff --git a/test/cases/inline-tag.html b/test/cases/inline-tag.html
new file mode 100644
index 0000000..43c1cb1
--- /dev/null
+++ b/test/cases/inline-tag.html
@@ -0,0 +1,13 @@
+
+<p>bing <strong>foo</strong> bong</p>
+<p>
+ bing
+ <strong>foo</strong>
+ bong
+
+</p>
+<p>bing<strong>foo</strong>bong</p>
+<p>
+ #[strong escaped]
+ #[<strong>escaped</strong>
+</p>
\ No newline at end of file
diff --git a/test/cases/inline-tag.jade b/test/cases/inline-tag.jade
new file mode 100644
index 0000000..9712123
--- /dev/null
+++ b/test/cases/inline-tag.jade
@@ -0,0 +1,15 @@
+p bing #[strong foo] bong
+
+p.
+ bing
+ #[strong foo]
+ bong
+
+p
+ | bing
+ | #[strong foo]
+ | bong
+
+p.
+ \#[strong escaped]
+ \#[#[strong escaped]
\ No newline at end of file
diff --git a/test/cases/interpolation.escape.html b/test/cases/interpolation.escape.html
new file mode 100644
index 0000000..8dd546b
--- /dev/null
+++ b/test/cases/interpolation.escape.html
@@ -0,0 +1,6 @@
+<foo>
+ some
+ #{text}
+ here
+ My ID is {42}
+</foo>
\ No newline at end of file
diff --git a/test/cases/interpolation.escape.jade b/test/cases/interpolation.escape.jade
new file mode 100644
index 0000000..cff251b
--- /dev/null
+++ b/test/cases/interpolation.escape.jade
@@ -0,0 +1,7 @@
+
+- var id = 42;
+foo
+ | some
+ | \#{text}
+ | here
+ | My ID #{"is {" + id + "}"}
\ No newline at end of file
diff --git a/test/cases/javascript-new-lines.js b/test/cases/javascript-new-lines.js
new file mode 100644
index 0000000..6893341
--- /dev/null
+++ b/test/cases/javascript-new-lines.js
@@ -0,0 +1 @@
+var x = "\n here is some \n new lined text";
diff --git a/test/cases/layout.append.html b/test/cases/layout.append.html
new file mode 100644
index 0000000..bc5e126
--- /dev/null
+++ b/test/cases/layout.append.html
@@ -0,0 +1,9 @@
+<html>
+ <script src="vendor/jquery.js"></script>
+ <script src="vendor/caustic.js"></script>
+ <script src="app.js"></script>
+ <script src="foo.js"></script>
+ <script src="bar.js"></script>
+ <body>
+ </body>
+</html>
diff --git a/test/cases/layout.append.jade b/test/cases/layout.append.jade
new file mode 100644
index 0000000..ec2a21f
--- /dev/null
+++ b/test/cases/layout.append.jade
@@ -0,0 +1,6 @@
+
+extends ../fixtures/append/app-layout
+
+block append head
+ script(src='foo.js')
+ script(src='bar.js')
diff --git a/test/cases/layout.append.without-block.html b/test/cases/layout.append.without-block.html
new file mode 100644
index 0000000..bc5e126
--- /dev/null
+++ b/test/cases/layout.append.without-block.html
@@ -0,0 +1,9 @@
+<html>
+ <script src="vendor/jquery.js"></script>
+ <script src="vendor/caustic.js"></script>
+ <script src="app.js"></script>
+ <script src="foo.js"></script>
+ <script src="bar.js"></script>
+ <body>
+ </body>
+</html>
diff --git a/test/cases/layout.append.without-block.jade b/test/cases/layout.append.without-block.jade
new file mode 100644
index 0000000..ce0c56d
--- /dev/null
+++ b/test/cases/layout.append.without-block.jade
@@ -0,0 +1,6 @@
+
+extends ../fixtures/append-without-block/app-layout
+
+append head
+ script(src='foo.js')
+ script(src='bar.js')
diff --git a/test/cases/layout.multi.append.prepend.block.html b/test/cases/layout.multi.append.prepend.block.html
new file mode 100644
index 0000000..107fbea
--- /dev/null
+++ b/test/cases/layout.multi.append.prepend.block.html
@@ -0,0 +1,8 @@
+<p class="first prepend">Last prepend must appear at top</p>
+<p class="first prepend">Something prepended to content</p>
+<div class="content">Defined content</div>
+<p class="first append">Something appended to content</p>
+<p class="last append">Last append must be most last</p>
+<script src="foo.js"></script>
+<script src="/app.js"></script>
+<script src="jquery.js"></script>
\ No newline at end of file
diff --git a/test/cases/layout.multi.append.prepend.block.jade b/test/cases/layout.multi.append.prepend.block.jade
new file mode 100644
index 0000000..05f3960
--- /dev/null
+++ b/test/cases/layout.multi.append.prepend.block.jade
@@ -0,0 +1,19 @@
+extends ../fixtures/multi-append-prepend-block/redefine
+
+append content
+ p.first.append Something appended to content
+
+prepend content
+ p.first.prepend Something prepended to content
+
+append content
+ p.last.append Last append must be most last
+
+prepend content
+ p.first.prepend Last prepend must appear at top
+
+append head
+ script(src='jquery.js')
+
+prepend head
+ script(src='foo.js')
diff --git a/test/cases/layout.prepend.html b/test/cases/layout.prepend.html
new file mode 100644
index 0000000..8753a42
--- /dev/null
+++ b/test/cases/layout.prepend.html
@@ -0,0 +1,9 @@
+<html>
+ <script src="foo.js"></script>
+ <script src="bar.js"></script>
+ <script src="app.js"></script>
+ <script src="vendor/jquery.js"></script>
+ <script src="vendor/caustic.js"></script>
+ <body>
+ </body>
+</html>
diff --git a/test/cases/layout.prepend.jade b/test/cases/layout.prepend.jade
new file mode 100644
index 0000000..f593c09
--- /dev/null
+++ b/test/cases/layout.prepend.jade
@@ -0,0 +1,6 @@
+
+extends ../fixtures/prepend/app-layout
+
+block prepend head
+ script(src='foo.js')
+ script(src='bar.js')
diff --git a/test/cases/layout.prepend.without-block.html b/test/cases/layout.prepend.without-block.html
new file mode 100644
index 0000000..8753a42
--- /dev/null
+++ b/test/cases/layout.prepend.without-block.html
@@ -0,0 +1,9 @@
+<html>
+ <script src="foo.js"></script>
+ <script src="bar.js"></script>
+ <script src="app.js"></script>
+ <script src="vendor/jquery.js"></script>
+ <script src="vendor/caustic.js"></script>
+ <body>
+ </body>
+</html>
diff --git a/test/cases/layout.prepend.without-block.jade b/test/cases/layout.prepend.without-block.jade
new file mode 100644
index 0000000..79310da
--- /dev/null
+++ b/test/cases/layout.prepend.without-block.jade
@@ -0,0 +1,6 @@
+
+extends ../fixtures/prepend-without-block/app-layout
+
+prepend head
+ script(src='foo.js')
+ script(src='bar.js')
diff --git a/test/cases/mixin-hoist.html b/test/cases/mixin-hoist.html
new file mode 100644
index 0000000..268813a
--- /dev/null
+++ b/test/cases/mixin-hoist.html
@@ -0,0 +1,5 @@
+<html>
+ <body>
+ <h1>Jade</h1>
+ </body>
+</html>
\ No newline at end of file
diff --git a/test/cases/mixin-hoist.jade b/test/cases/mixin-hoist.jade
new file mode 100644
index 0000000..64601b5
--- /dev/null
+++ b/test/cases/mixin-hoist.jade
@@ -0,0 +1,7 @@
+
+mixin foo()
+ h1= title
+
+html
+ body
+ mixin foo
diff --git a/test/cases/mixin-via-include.html b/test/cases/mixin-via-include.html
new file mode 100644
index 0000000..8124337
--- /dev/null
+++ b/test/cases/mixin-via-include.html
@@ -0,0 +1 @@
+<p>bar</p>
\ No newline at end of file
diff --git a/test/cases/mixin-via-include.jade b/test/cases/mixin-via-include.jade
new file mode 100644
index 0000000..53c9d02
--- /dev/null
+++ b/test/cases/mixin-via-include.jade
@@ -0,0 +1,5 @@
+//- regression test for https://github.com/visionmedia/jade/issues/1435
+
+include ../fixtures/mixin-include.jade
+
++bang
\ No newline at end of file
diff --git a/test/cases/mixin.attrs.html b/test/cases/mixin.attrs.html
new file mode 100644
index 0000000..a767cf5
--- /dev/null
+++ b/test/cases/mixin.attrs.html
@@ -0,0 +1,27 @@
+<body>
+ <div id="First" class="centered">Hello World
+ </div>
+ <div id="Second" class="centered">
+ <h1>Section 1</h1>
+ <p>Some important content.</p>
+ </div>
+ <div id="Third" class="centered">
+ <h1 class="foo bar">Section 2</h1>
+ <p>Even more important content.</p>
+ <div class="footer"><a href="menu.html">Back</a></div>
+ </div>
+ <div class="stretch">
+ <div class="centered">
+ <h1 class="highlight">Section 3</h1>
+ <p>Last content.</p>
+ <div class="footer"><a href="#">Back</a></div>
+ </div>
+ </div>
+ <div class="bottom foo bar" name="end" id="Last" data-attr="baz">
+ <p>Some final words.</p>
+ </div>
+ <div class="bottom class1 class2">
+ </div>
+</body>
+<div attr1="foo" attr2="bar" class="thing foo bar thunk" attr3="baz" data-foo="<biz>" data-bar="<biz>"></div>
+<div data-profile="profile" data-creator-name="name">work</div>
\ No newline at end of file
diff --git a/test/cases/mixin.attrs.jade b/test/cases/mixin.attrs.jade
new file mode 100644
index 0000000..804d58b
--- /dev/null
+++ b/test/cases/mixin.attrs.jade
@@ -0,0 +1,41 @@
+mixin centered(title)
+ div.centered(id=attributes.id)
+ - if (title)
+ h1(class=attributes.class)= title
+ block
+ - if (attributes.href)
+ .footer
+ a(href=attributes.href) Back
+
+mixin main(title)
+ div.stretch
+ +centered(title).highlight&attributes(attributes)
+ block
+
+mixin bottom
+ div.bottom&attributes(attributes)
+ block
+
+body
+ +centered#First Hello World
+ +centered('Section 1')#Second
+ p Some important content.
+ +centered('Section 2')#Third.foo(href='menu.html', class='bar')
+ p Even more important content.
+ +main('Section 3')(href='#')
+ p Last content.
+ +bottom.foo(class='bar', name='end', id='Last', data-attr='baz')
+ p Some final words.
+ +bottom(class=['class1', 'class2'])
+
+mixin foo
+ div.thing(attr1='foo', attr2='bar')&attributes(attributes)
+
+- var val = '<biz>'
+- var classes = ['foo', 'bar']
++foo(attr3='baz' data-foo=val data-bar!=val class=classes).thunk
+
+//- Regression test for #1424
+mixin work_filmstrip_item(work)
+ div&attributes(attributes)= work
++work_filmstrip_item('work')("data-profile"='profile', "data-creator-name"='name')
diff --git a/test/cases/mixin.block-tag-behaviour.html b/test/cases/mixin.block-tag-behaviour.html
new file mode 100644
index 0000000..580dbe0
--- /dev/null
+++ b/test/cases/mixin.block-tag-behaviour.html
@@ -0,0 +1,22 @@
+<html>
+ <body>
+ <section class="article">
+ <h1>Foo</h1>
+ <p>I'm article foo</p>
+ </section>
+ </body>
+</html>
+<html>
+ <body>
+ <section class="article">
+ <h1>Something</h1>
+ <p>
+ I'm a much longer
+ text-only article,
+ but you can still
+ inline html tags
+ in me if you want.
+ </p>
+ </section>
+ </body>
+</html>
\ No newline at end of file
diff --git a/test/cases/mixin.block-tag-behaviour.jade b/test/cases/mixin.block-tag-behaviour.jade
new file mode 100644
index 0000000..1d2d2d3
--- /dev/null
+++ b/test/cases/mixin.block-tag-behaviour.jade
@@ -0,0 +1,24 @@
+
+mixin article(name)
+ section.article
+ h1= name
+ block
+
+html
+ body
+ +article('Foo'): p I'm article foo
+
+mixin article(name)
+ section.article
+ h1= name
+ p
+ block
+
+html
+ body
+ +article('Something').
+ I'm a much longer
+ text-only article,
+ but you can still
+ inline html tags
+ in me if you want.
\ No newline at end of file
diff --git a/test/cases/mixin.blocks.html b/test/cases/mixin.blocks.html
new file mode 100644
index 0000000..def5c6f
--- /dev/null
+++ b/test/cases/mixin.blocks.html
@@ -0,0 +1,34 @@
+<html>
+ <body>
+ <form method="GET" action="/search">
+ <input type="hidden" name="_csrf" value="hey"/>
+ <input type="text" name="query" placeholder="Search"/>
+ <input type="submit" value="Search"/>
+ </form>
+ </body>
+</html>
+<html>
+ <body>
+ <form method="POST" action="/search">
+ <input type="hidden" name="_csrf" value="hey"/>
+ <input type="text" name="query" placeholder="Search"/>
+ <input type="submit" value="Search"/>
+ </form>
+ </body>
+</html>
+<html>
+ <body>
+ <form method="POST" action="/search">
+ <input type="hidden" name="_csrf" value="hey"/>
+ </form>
+ </body>
+</html>
+<div id="foo">
+ <div id="bar">
+ <p>one</p>
+ <p>two</p>
+ <p>three</p>
+ </div>
+</div>
+<div id="baz">123
+</div>
\ No newline at end of file
diff --git a/test/cases/mixin.blocks.jade b/test/cases/mixin.blocks.jade
new file mode 100644
index 0000000..30c9990
--- /dev/null
+++ b/test/cases/mixin.blocks.jade
@@ -0,0 +1,44 @@
+
+
+mixin form(method, action)
+ form(method=method, action=action)
+ - var csrf_token_from_somewhere = 'hey'
+ input(type='hidden', name='_csrf', value=csrf_token_from_somewhere)
+ block
+
+html
+ body
+ +form('GET', '/search')
+ input(type='text', name='query', placeholder='Search')
+ input(type='submit', value='Search')
+
+html
+ body
+ +form('POST', '/search')
+ input(type='text', name='query', placeholder='Search')
+ input(type='submit', value='Search')
+
+html
+ body
+ +form('POST', '/search')
+
+mixin bar()
+ #bar
+ block
+
+mixin foo()
+ #foo
+ +bar
+ block
+
++foo
+ p one
+ p two
+ p three
+
+
+mixin baz
+ #baz
+ block
+
++baz()= '123'
\ No newline at end of file
diff --git a/test/cases/mixin.merge.html b/test/cases/mixin.merge.html
new file mode 100644
index 0000000..c116856
--- /dev/null
+++ b/test/cases/mixin.merge.html
@@ -0,0 +1,34 @@
+<body>
+ <p class="bar hello">One</p>
+ <p class="baz quux hello">Two</p>
+ <p class="hello">Three</p>
+ <p class="bar baz hello">Four</p>
+ <p class="bar" id="world">One</p>
+ <p class="baz quux" id="world">Two</p>
+ <p id="world">Three</p>
+ <p class="bar baz" id="world">Four</p>
+ <p class="bar hello" id="world">One</p>
+ <p class="baz quux hello" id="world">Two</p>
+ <p id="world" class="hello">Three</p>
+ <p class="bar baz hello" id="world">Four</p>
+ <p class="bar hello world">One</p>
+ <p class="baz quux hello world">Two</p>
+ <p class="hello world">Three</p>
+ <p class="bar baz hello world">Four</p>
+ <p class="bar hello">One</p>
+ <p class="baz quux hello">Two</p>
+ <p class="hello">Three</p>
+ <p class="bar baz hello">Four</p>
+ <p class="bar hello world">One</p>
+ <p class="baz quux hello world">Two</p>
+ <p class="hello world">Three</p>
+ <p class="bar baz hello world">Four</p>
+ <p class="bar">One</p>
+ <p class="baz quux">Two</p>
+ <p>Three</p>
+ <p class="bar baz">Four</p>
+ <p class="bar hello">One</p>
+ <p class="baz quux hello">Two</p>
+ <p class="hello">Three</p>
+ <p class="bar baz hello">Four</p>
+</body>
diff --git a/test/cases/mixin.merge.jade b/test/cases/mixin.merge.jade
new file mode 100644
index 0000000..f0d217d
--- /dev/null
+++ b/test/cases/mixin.merge.jade
@@ -0,0 +1,15 @@
+mixin foo
+ p.bar&attributes(attributes) One
+ p.baz.quux&attributes(attributes) Two
+ p&attributes(attributes) Three
+ p.bar&attributes(attributes)(class="baz") Four
+
+body
+ +foo.hello
+ +foo#world
+ +foo.hello#world
+ +foo.hello.world
+ +foo(class="hello")
+ +foo.hello(class="world")
+ +foo
+ +foo&attributes({class: "hello"})
\ No newline at end of file
diff --git a/test/cases/mixins-unused.html b/test/cases/mixins-unused.html
new file mode 100644
index 0000000..5db7bc1
--- /dev/null
+++ b/test/cases/mixins-unused.html
@@ -0,0 +1 @@
+<body></body>
\ No newline at end of file
diff --git a/test/cases/mixins-unused.jade b/test/cases/mixins-unused.jade
new file mode 100644
index 0000000..b0af6cc
--- /dev/null
+++ b/test/cases/mixins-unused.jade
@@ -0,0 +1,3 @@
+mixin never-called
+ .wtf This isn't something we ever want to output
+body
\ No newline at end of file
diff --git a/test/cases/mixins.html b/test/cases/mixins.html
new file mode 100644
index 0000000..fbf6df0
--- /dev/null
+++ b/test/cases/mixins.html
@@ -0,0 +1,17 @@
+<div id="user">
+ <h1>Tobi</h1>
+ <div class="comments">
+ <div class="comment">
+ <h2>This</h2>
+ <p class="body">is regular, javascript</p>
+ </div>
+ </div>
+</div>
+<body>
+ <ul>
+ <li>foo</li>
+ <li>bar</li>
+ <li>baz</li>
+ </ul>
+</body>
+<div id="interpolation">This is interpolated</div>
diff --git a/test/cases/mixins.jade b/test/cases/mixins.jade
new file mode 100644
index 0000000..0ee7106
--- /dev/null
+++ b/test/cases/mixins.jade
@@ -0,0 +1,31 @@
+mixin comment(title, str)
+ .comment
+ h2= title
+ p.body= str
+
+
+mixin comment (title, str)
+ .comment
+ h2= title
+ p.body= str
+
+#user
+ h1 Tobi
+ .comments
+ +comment('This',
+ (('is regular, javascript')))
+
+mixin list
+ ul
+ li foo
+ li bar
+ li baz
+
+body
+ mixin list()
+
+mixin foobar(str)
+ div#interpolation= str + 'interpolated'
+
+- var suffix = "bar"
++#{'foo' + suffix}('This is ')
diff --git a/test/cases/namespaces.html b/test/cases/namespaces.html
new file mode 100644
index 0000000..90522ac
--- /dev/null
+++ b/test/cases/namespaces.html
@@ -0,0 +1,2 @@
+<fb:user:role>Something</fb:user:role>
+<foo fb:foo="bar"></foo>
\ No newline at end of file
diff --git a/test/cases/namespaces.jade b/test/cases/namespaces.jade
new file mode 100644
index 0000000..0694677
--- /dev/null
+++ b/test/cases/namespaces.jade
@@ -0,0 +1,2 @@
+fb:user:role Something
+foo(fb:foo='bar')
\ No newline at end of file
diff --git a/test/cases/nesting.html b/test/cases/nesting.html
new file mode 100644
index 0000000..56c15cb
--- /dev/null
+++ b/test/cases/nesting.html
@@ -0,0 +1,11 @@
+<ul>
+ <li>a</li>
+ <li>b</li>
+ <li>
+ <ul>
+ <li>c</li>
+ <li>d</li>
+ </ul>
+ </li>
+ <li>e</li>
+</ul>
\ No newline at end of file
diff --git a/test/cases/nesting.jade b/test/cases/nesting.jade
new file mode 100644
index 0000000..f8cab4d
--- /dev/null
+++ b/test/cases/nesting.jade
@@ -0,0 +1,8 @@
+ul
+ li a
+ li b
+ li
+ ul
+ li c
+ li d
+ li e
\ No newline at end of file
diff --git a/test/cases/pre.html b/test/cases/pre.html
new file mode 100644
index 0000000..33bab4e
--- /dev/null
+++ b/test/cases/pre.html
@@ -0,0 +1,7 @@
+<pre>foo
+bar
+baz
+</pre>
+<pre><code>foo
+bar
+baz</code></pre>
\ No newline at end of file
diff --git a/test/cases/pre.jade b/test/cases/pre.jade
new file mode 100644
index 0000000..75673c5
--- /dev/null
+++ b/test/cases/pre.jade
@@ -0,0 +1,10 @@
+pre.
+ foo
+ bar
+ baz
+
+pre
+ code.
+ foo
+ bar
+ baz
\ No newline at end of file
diff --git a/test/cases/quotes.html b/test/cases/quotes.html
new file mode 100644
index 0000000..592b136
--- /dev/null
+++ b/test/cases/quotes.html
@@ -0,0 +1,2 @@
+<p>"foo"</p>
+<p>'foo'</p>
\ No newline at end of file
diff --git a/test/cases/quotes.jade b/test/cases/quotes.jade
new file mode 100644
index 0000000..499c835
--- /dev/null
+++ b/test/cases/quotes.jade
@@ -0,0 +1,2 @@
+p "foo"
+p 'foo'
\ No newline at end of file
diff --git a/test/cases/regression.784.html b/test/cases/regression.784.html
new file mode 100644
index 0000000..933e986
--- /dev/null
+++ b/test/cases/regression.784.html
@@ -0,0 +1 @@
+<div class="url">google.com</div>
\ No newline at end of file
diff --git a/test/cases/regression.784.jade b/test/cases/regression.784.jade
new file mode 100644
index 0000000..bab7540
--- /dev/null
+++ b/test/cases/regression.784.jade
@@ -0,0 +1,2 @@
+- var url = 'http://www.google.com'
+.url #{url.replace('http://', '').replace(/^www\./, '')}
\ No newline at end of file
diff --git a/test/cases/script.whitespace.html b/test/cases/script.whitespace.html
new file mode 100644
index 0000000..a8f49e5
--- /dev/null
+++ b/test/cases/script.whitespace.html
@@ -0,0 +1,7 @@
+<script>
+ if (foo) {
+
+ bar();
+
+ }
+</script>
\ No newline at end of file
diff --git a/test/cases/script.whitespace.jade b/test/cases/script.whitespace.jade
new file mode 100644
index 0000000..e0afc3a
--- /dev/null
+++ b/test/cases/script.whitespace.jade
@@ -0,0 +1,6 @@
+script.
+ if (foo) {
+
+ bar();
+
+ }
\ No newline at end of file
diff --git a/test/cases/scripts.html b/test/cases/scripts.html
new file mode 100644
index 0000000..e3dc48b
--- /dev/null
+++ b/test/cases/scripts.html
@@ -0,0 +1,9 @@
+<script>
+ if (foo) {
+ bar();
+ }
+</script>
+<script>foo()</script>
+<script>foo()</script>
+<script></script>
+<div></div>
\ No newline at end of file
diff --git a/test/cases/scripts.jade b/test/cases/scripts.jade
new file mode 100644
index 0000000..d28887f
--- /dev/null
+++ b/test/cases/scripts.jade
@@ -0,0 +1,8 @@
+script.
+ if (foo) {
+ bar();
+ }
+script!= 'foo()'
+script foo()
+script
+div
\ No newline at end of file
diff --git a/test/cases/scripts.non-js.html b/test/cases/scripts.non-js.html
new file mode 100644
index 0000000..9daff38
--- /dev/null
+++ b/test/cases/scripts.non-js.html
@@ -0,0 +1,11 @@
+<script id="user-template" type="text/template">
+ <div id="user">
+ <h1><%= user.name %></h1>
+ <p><%= user.description %></p>
+ </div>
+</script>
+<script id="user-template" type="text/template">
+ if (foo) {
+ bar();
+ }
+</script>
\ No newline at end of file
diff --git a/test/cases/scripts.non-js.jade b/test/cases/scripts.non-js.jade
new file mode 100644
index 0000000..9f9a408
--- /dev/null
+++ b/test/cases/scripts.non-js.jade
@@ -0,0 +1,9 @@
+script#user-template(type='text/template')
+ #user
+ h1 <%= user.name %>
+ p <%= user.description %>
+
+script#user-template(type='text/template').
+ if (foo) {
+ bar();
+ }
\ No newline at end of file
diff --git a/test/cases/single-period.html b/test/cases/single-period.html
new file mode 100644
index 0000000..430944c
--- /dev/null
+++ b/test/cases/single-period.html
@@ -0,0 +1 @@
+<span>.</span>
\ No newline at end of file
diff --git a/test/cases/single-period.jade b/test/cases/single-period.jade
new file mode 100644
index 0000000..f3d734c
--- /dev/null
+++ b/test/cases/single-period.jade
@@ -0,0 +1 @@
+span .
\ No newline at end of file
diff --git a/test/cases/some-included.styl b/test/cases/some-included.styl
new file mode 100644
index 0000000..7458543
--- /dev/null
+++ b/test/cases/some-included.styl
@@ -0,0 +1,2 @@
+body
+ padding 10px
diff --git a/test/cases/some.md b/test/cases/some.md
new file mode 100644
index 0000000..8ea3e54
--- /dev/null
+++ b/test/cases/some.md
@@ -0,0 +1,3 @@
+Just _some_ markdown **tests**.
+
+With new line.
diff --git a/test/cases/some.styl b/test/cases/some.styl
new file mode 100644
index 0000000..f77222d
--- /dev/null
+++ b/test/cases/some.styl
@@ -0,0 +1 @@
+ at import "some-included"
diff --git a/test/cases/source.html b/test/cases/source.html
new file mode 100644
index 0000000..1881c0f
--- /dev/null
+++ b/test/cases/source.html
@@ -0,0 +1,6 @@
+<html>
+ <audio preload="auto" autobuffer="autobuffer" controls="controls">
+ <source src="foo"/>
+ <source src="bar"/>
+ </audio>
+</html>
\ No newline at end of file
diff --git a/test/cases/source.jade b/test/cases/source.jade
new file mode 100644
index 0000000..db22b80
--- /dev/null
+++ b/test/cases/source.jade
@@ -0,0 +1,4 @@
+html
+ audio(preload='auto', autobuffer, controls)
+ source(src='foo')
+ source(src='bar')
\ No newline at end of file
diff --git a/test/cases/styles.html b/test/cases/styles.html
new file mode 100644
index 0000000..1397780
--- /dev/null
+++ b/test/cases/styles.html
@@ -0,0 +1,9 @@
+<html>
+ <head>
+ <style>
+ body {
+ padding: 50px;
+ }
+ </style>
+ </head>
+</html>
\ No newline at end of file
diff --git a/test/cases/styles.jade b/test/cases/styles.jade
new file mode 100644
index 0000000..f087518
--- /dev/null
+++ b/test/cases/styles.jade
@@ -0,0 +1,6 @@
+html
+ head
+ style.
+ body {
+ padding: 50px;
+ }
\ No newline at end of file
diff --git a/test/cases/tag.interpolation.html b/test/cases/tag.interpolation.html
new file mode 100644
index 0000000..7b45195
--- /dev/null
+++ b/test/cases/tag.interpolation.html
@@ -0,0 +1,9 @@
+<p>value</p>
+<p foo="bar">value</p>
+<a something="something">here</a>
+<ul>
+ <li><span><img src="contact" class="icon"/>Contact</span>
+ </li>
+ <li><a href="/contact"><img class="icon"/>Contact</a>
+ </li>
+</ul>
\ No newline at end of file
diff --git a/test/cases/tag.interpolation.jade b/test/cases/tag.interpolation.jade
new file mode 100644
index 0000000..d923ddb
--- /dev/null
+++ b/test/cases/tag.interpolation.jade
@@ -0,0 +1,22 @@
+
+- var tag = 'p'
+- var foo = 'bar'
+
+#{tag} value
+#{tag}(foo='bar') value
+#{foo ? 'a' : 'li'}(something) here
+
+mixin item(icon)
+ li
+ if attributes.href
+ a&attributes(attributes)
+ img.icon(src=icon)
+ block
+ else
+ span&attributes(attributes)
+ img.icon(src=icon)
+ block
+
+ul
+ +item('contact') Contact
+ +item(href='/contact') Contact
diff --git a/test/cases/tags.self-closing.html b/test/cases/tags.self-closing.html
new file mode 100644
index 0000000..997103d
--- /dev/null
+++ b/test/cases/tags.self-closing.html
@@ -0,0 +1,8 @@
+<body>
+ <foo></foo>
+ <foo bar="baz"></foo>
+ <foo/>
+ <foo bar="baz"/>
+ <foo>/</foo>
+ <foo bar="baz">/</foo><img/><img/>
+</body>
\ No newline at end of file
diff --git a/test/cases/tags.self-closing.jade b/test/cases/tags.self-closing.jade
new file mode 100644
index 0000000..de327cc
--- /dev/null
+++ b/test/cases/tags.self-closing.jade
@@ -0,0 +1,12 @@
+
+body
+ foo
+ foo(bar='baz')
+ foo/
+ foo(bar='baz')/
+ foo /
+ foo(bar='baz') /
+ //- can have a single space after them
+ img
+ //- can have lots of white space after them
+ img
\ No newline at end of file
diff --git a/test/cases/template.html b/test/cases/template.html
new file mode 100644
index 0000000..2054e05
--- /dev/null
+++ b/test/cases/template.html
@@ -0,0 +1,11 @@
+<script type="text/x-template">
+ <article>
+ <h2>{{title}}</h2>
+ <p>{{description}}</p>
+ </article>
+</script>
+<script type="text/x-template">
+ article
+ h2 {{title}}
+ p {{description}}
+</script>
\ No newline at end of file
diff --git a/test/cases/template.jade b/test/cases/template.jade
new file mode 100644
index 0000000..20e086b
--- /dev/null
+++ b/test/cases/template.jade
@@ -0,0 +1,9 @@
+script(type='text/x-template')
+ article
+ h2 {{title}}
+ p {{description}}
+
+script(type='text/x-template').
+ article
+ h2 {{title}}
+ p {{description}}
diff --git a/test/cases/text-block.html b/test/cases/text-block.html
new file mode 100644
index 0000000..fae8caa
--- /dev/null
+++ b/test/cases/text-block.html
@@ -0,0 +1,6 @@
+<label>Username:
+ <input type="text" name="user[name]"/>
+</label>
+<label>Password:
+ <input type="text" name="user[pass]"/>
+</label>
\ No newline at end of file
diff --git a/test/cases/text-block.jade b/test/cases/text-block.jade
new file mode 100644
index 0000000..a032fa7
--- /dev/null
+++ b/test/cases/text-block.jade
@@ -0,0 +1,6 @@
+
+label Username:
+ input(type='text', name='user[name]')
+
+label Password:
+ input(type='text', name='user[pass]')
\ No newline at end of file
diff --git a/test/cases/text.html b/test/cases/text.html
new file mode 100644
index 0000000..d8e37cc
--- /dev/null
+++ b/test/cases/text.html
@@ -0,0 +1,22 @@
+<option value="">-- (selected) --</option>
+<p>
+ foo
+ bar
+ baz
+</p>
+<p>
+ foo
+
+
+ bar
+ baz
+
+</p>
+<pre>foo
+ bar
+ baz
+.</pre>
+<pre>foo
+ bar
+ baz
+.</pre>
\ No newline at end of file
diff --git a/test/cases/text.jade b/test/cases/text.jade
new file mode 100644
index 0000000..a26387e
--- /dev/null
+++ b/test/cases/text.jade
@@ -0,0 +1,25 @@
+option(value='') -- (selected) --
+
+p
+ | foo
+ | bar
+ | baz
+
+p.
+ foo
+
+
+ bar
+ baz
+
+pre
+ | foo
+ | bar
+ | baz
+ | .
+
+pre.
+ foo
+ bar
+ baz
+ .
\ No newline at end of file
diff --git a/test/cases/utf8bom.html b/test/cases/utf8bom.html
new file mode 100644
index 0000000..e3e18f0
--- /dev/null
+++ b/test/cases/utf8bom.html
@@ -0,0 +1 @@
+<p>"foo"</p>
diff --git a/test/cases/utf8bom.jade b/test/cases/utf8bom.jade
new file mode 100644
index 0000000..9a32814
--- /dev/null
+++ b/test/cases/utf8bom.jade
@@ -0,0 +1 @@
+p "foo"
diff --git a/test/cases/vars.html b/test/cases/vars.html
new file mode 100644
index 0000000..892e7e7
--- /dev/null
+++ b/test/cases/vars.html
@@ -0,0 +1 @@
+<a id="bar" class="1 2 3"></a>
\ No newline at end of file
diff --git a/test/cases/vars.jade b/test/cases/vars.jade
new file mode 100644
index 0000000..46451a9
--- /dev/null
+++ b/test/cases/vars.jade
@@ -0,0 +1,3 @@
+- var foo = 'bar'
+- var list = [1,2,3]
+a(class=list, id=foo)
\ No newline at end of file
diff --git a/test/cases/while.html b/test/cases/while.html
new file mode 100644
index 0000000..dff7ff6
--- /dev/null
+++ b/test/cases/while.html
@@ -0,0 +1,11 @@
+<ul>
+ <li>2</li>
+ <li>3</li>
+ <li>4</li>
+ <li>5</li>
+ <li>6</li>
+ <li>7</li>
+ <li>8</li>
+ <li>9</li>
+ <li>10</li>
+</ul>
diff --git a/test/cases/while.jade b/test/cases/while.jade
new file mode 100644
index 0000000..059b54b
--- /dev/null
+++ b/test/cases/while.jade
@@ -0,0 +1,5 @@
+- var x = 1;
+ul
+ while x < 10
+ - x++;
+ li= x
diff --git a/test/cases/xml.html b/test/cases/xml.html
new file mode 100644
index 0000000..5fd9f1a
--- /dev/null
+++ b/test/cases/xml.html
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<category term="some term"/>
+<link>http://google.com</link>
\ No newline at end of file
diff --git a/test/cases/xml.jade b/test/cases/xml.jade
new file mode 100644
index 0000000..2b21fa4
--- /dev/null
+++ b/test/cases/xml.jade
@@ -0,0 +1,3 @@
+doctype xml
+category(term='some term')/
+link http://google.com
\ No newline at end of file
diff --git a/test/cases/yield-before-conditional-head.html b/test/cases/yield-before-conditional-head.html
new file mode 100644
index 0000000..35ace64
--- /dev/null
+++ b/test/cases/yield-before-conditional-head.html
@@ -0,0 +1,3 @@
+<head>
+ <script src="/jquery.js"></script>
+</head>
\ No newline at end of file
diff --git a/test/cases/yield-before-conditional-head.jade b/test/cases/yield-before-conditional-head.jade
new file mode 100644
index 0000000..8515406
--- /dev/null
+++ b/test/cases/yield-before-conditional-head.jade
@@ -0,0 +1,5 @@
+head
+ script(src='/jquery.js')
+ yield
+ if false
+ script(src='/jquery.ui.js')
diff --git a/test/cases/yield-before-conditional.html b/test/cases/yield-before-conditional.html
new file mode 100644
index 0000000..7a3f184
--- /dev/null
+++ b/test/cases/yield-before-conditional.html
@@ -0,0 +1,9 @@
+<html>
+ <body>
+ <head>
+ <script src="/jquery.js"></script>
+ <script src="/caustic.js"></script>
+ <script src="/app.js"></script>
+ </head>
+ </body>
+</html>
\ No newline at end of file
diff --git a/test/cases/yield-before-conditional.jade b/test/cases/yield-before-conditional.jade
new file mode 100644
index 0000000..eedc33c
--- /dev/null
+++ b/test/cases/yield-before-conditional.jade
@@ -0,0 +1,5 @@
+html
+ body
+ include yield-before-conditional-head
+ script(src='/caustic.js')
+ script(src='/app.js')
diff --git a/test/cases/yield-head.html b/test/cases/yield-head.html
new file mode 100644
index 0000000..83f92b5
--- /dev/null
+++ b/test/cases/yield-head.html
@@ -0,0 +1,4 @@
+<head>
+ <script src="/jquery.js"></script>
+ <script src="/jquery.ui.js"></script>
+</head>
\ No newline at end of file
diff --git a/test/cases/yield-head.jade b/test/cases/yield-head.jade
new file mode 100644
index 0000000..1428be6
--- /dev/null
+++ b/test/cases/yield-head.jade
@@ -0,0 +1,4 @@
+head
+ script(src='/jquery.js')
+ yield
+ script(src='/jquery.ui.js')
diff --git a/test/cases/yield-title-head.html b/test/cases/yield-title-head.html
new file mode 100644
index 0000000..ae62c27
--- /dev/null
+++ b/test/cases/yield-title-head.html
@@ -0,0 +1,5 @@
+<head>
+ <title></title>
+ <script src="/jquery.js"></script>
+ <script src="/jquery.ui.js"></script>
+</head>
\ No newline at end of file
diff --git a/test/cases/yield-title-head.jade b/test/cases/yield-title-head.jade
new file mode 100644
index 0000000..5ec7d32
--- /dev/null
+++ b/test/cases/yield-title-head.jade
@@ -0,0 +1,5 @@
+head
+ title
+ yield
+ script(src='/jquery.js')
+ script(src='/jquery.ui.js')
diff --git a/test/cases/yield-title.html b/test/cases/yield-title.html
new file mode 100644
index 0000000..83ef1fb
--- /dev/null
+++ b/test/cases/yield-title.html
@@ -0,0 +1,9 @@
+<html>
+ <body>
+ <head>
+ <title>My Title</title>
+ <script src="/jquery.js"></script>
+ <script src="/jquery.ui.js"></script>
+ </head>
+ </body>
+</html>
\ No newline at end of file
diff --git a/test/cases/yield-title.jade b/test/cases/yield-title.jade
new file mode 100644
index 0000000..0754f52
--- /dev/null
+++ b/test/cases/yield-title.jade
@@ -0,0 +1,4 @@
+html
+ body
+ include yield-title-head
+ | My Title
\ No newline at end of file
diff --git a/test/cases/yield.html b/test/cases/yield.html
new file mode 100644
index 0000000..b16459d
--- /dev/null
+++ b/test/cases/yield.html
@@ -0,0 +1,10 @@
+<html>
+ <body>
+ <head>
+ <script src="/jquery.js"></script>
+ <script src="/caustic.js"></script>
+ <script src="/app.js"></script>
+ <script src="/jquery.ui.js"></script>
+ </head>
+ </body>
+</html>
\ No newline at end of file
diff --git a/test/cases/yield.jade b/test/cases/yield.jade
new file mode 100644
index 0000000..bc6a679
--- /dev/null
+++ b/test/cases/yield.jade
@@ -0,0 +1,5 @@
+html
+ body
+ include yield-head
+ script(src='/caustic.js')
+ script(src='/app.js')
diff --git a/test/deprecated.js b/test/deprecated.js
new file mode 100644
index 0000000..95841cc
--- /dev/null
+++ b/test/deprecated.js
@@ -0,0 +1,55 @@
+'use strict';
+
+var assert = require('assert');
+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; };
+ fn();
+ assert((regex || new RegExp(name + ' is deprecated and will be removed in v2.0.0')).test(log));
+ console.error = consoleError;
+ console.warn = consoleWarn;
+ });
+ }
+ deprecate('tag.clone', function () {
+ var tag = new jade.nodes.Tag();
+ tag.clone();
+ });
+ deprecate('node.clone', function () {
+ var code = new jade.nodes.Code('var x = 10');
+ code.clone();
+ });
+ deprecate('block.clone', function () {
+ var block = new jade.nodes.Block(new jade.nodes.Code('var x = 10'));
+ block.clone();
+ });
+ deprecate('block.replace', function () {
+ var block = new jade.nodes.Block(new jade.nodes.Code('var x = 10'));
+ block.replace({});
+ });
+ deprecate('attrs.removeAttribute', function () {
+ var tag = new jade.nodes.Tag('a');
+ tag.setAttribute('foo', 'value');
+ tag.removeAttribute('href');
+ assert(!tag.getAttribute('href'));
+ tag.setAttribute('href', 'value');
+ tag.removeAttribute('href');
+ assert(!tag.getAttribute('href'));
+ });
+ deprecate('attrs.getAttribute', function () {
+ var tag = new jade.nodes.Tag('a');
+ tag.setAttribute('href', 'value');
+ assert(tag.getAttribute('href') === 'value');
+ });
+ deprecate('jade.compile(str, {client: true})', function () {
+ var fn = jade.compile('div', {client: true});
+ var fn = Function('jade', fn.toString() + '\nreturn template;')(jade.runtime);
+ assert(fn() === '<div></div>');
+ }, /The `client` option is deprecated/);
+});
\ No newline at end of file
diff --git a/test/error.reporting.js b/test/error.reporting.js
new file mode 100644
index 0000000..6fbe685
--- /dev/null
+++ b/test/error.reporting.js
@@ -0,0 +1,185 @@
+
+/**
+ * Module dependencies.
+ */
+
+var jade = require('../')
+ , assert = require('assert')
+ , fs = require('fs');
+
+// Shortcut
+
+function getError(str, options){
+ try {
+ jade.render(str, options);
+ } catch (ex) {
+ return ex;
+ }
+ throw new Error('Input was supposed to result in an error.');
+}
+function getFileError(name, options){
+ try {
+ jade.renderFile(name, options);
+ } catch (ex) {
+ return ex;
+ }
+ throw new Error('Input was supposed to result in an error.');
+}
+
+
+describe('error reporting', function () {
+ describe('compile time errors', function () {
+ describe('with no filename', function () {
+ it('includes detail of where the error was thrown', function () {
+ var err = getError('foo(')
+ assert(/Jade:1/.test(err.message))
+ assert(/foo\(/.test(err.message))
+ });
+ });
+ describe('with a filename', function () {
+ it('includes detail of where the error was thrown including the filename', function () {
+ var err = getError('foo(', {filename: 'test.jade'})
+ assert(/test\.jade:1/.test(err.message))
+ assert(/foo\(/.test(err.message))
+ });
+ });
+ describe('with a layout without block declaration (syntax)', function () {
+ it('includes detail of where the error was thrown including the filename', function () {
+ var err = getFileError(__dirname + '/fixtures/compile.with.layout.syntax.error.jade', {})
+ assert(/[\\\/]layout.syntax.error.jade:2/.test(err.message))
+ assert(/foo\(/.test(err.message))
+ });
+ });
+ describe('with a layout without block declaration (locals)', function () {
+ it('includes detail of where the error was thrown including the filename', function () {
+ var err = getFileError(__dirname + '/fixtures/compile.with.layout.locals.error.jade', {})
+ assert(/[\\\/]layout.locals.error.jade:2/.test(err.message))
+ assert(/undefined is not a function/.test(err.message))
+ });
+ });
+ describe('with a include (syntax)', function () {
+ it('includes detail of where the error was thrown including the filename', function () {
+ var err = getFileError(__dirname + '/fixtures/compile.with.include.syntax.error.jade', {})
+ assert(/[\\\/]include.syntax.error.jade:2/.test(err.message))
+ assert(/foo\(/.test(err.message))
+ });
+ });
+ describe('with a include (locals)', function () {
+ it('includes detail of where the error was thrown including the filename', function () {
+ var err = getFileError(__dirname + '/fixtures/compile.with.include.locals.error.jade', {})
+ assert(/[\\\/]include.locals.error.jade:2/.test(err.message))
+ assert(/foo\(/.test(err.message))
+ });
+ });
+ describe('with a layout (without block) with an include (syntax)', function () {
+ it('includes detail of where the error was thrown including the filename', function () {
+ var err = getFileError(__dirname + '/fixtures/compile.with.layout.with.include.syntax.error.jade', {})
+ assert(/[\\\/]include.syntax.error.jade:2/.test(err.message))
+ assert(/foo\(/.test(err.message))
+ });
+ });
+ describe('with a layout (without block) with an include (locals)', function () {
+ it('includes detail of where the error was thrown including the filename', function () {
+ var err = getFileError(__dirname + '/fixtures/compile.with.layout.with.include.locals.error.jade', {})
+ assert(/[\\\/]include.locals.error.jade:2/.test(err.message))
+ assert(/foo\(/.test(err.message))
+ });
+ });
+ });
+ describe('runtime errors', function () {
+ describe('with no filename and `compileDebug` left undefined', function () {
+ it('just reports the line number', function () {
+ var sentinel = new Error('sentinel');
+ var err = getError('-foo()', {foo: function () { throw sentinel; }})
+ assert(/on line 1/.test(err.message))
+ });
+ });
+ describe('with no filename and `compileDebug` set to `true`', function () {
+ it('includes detail of where the error was thrown', function () {
+ var sentinel = new Error('sentinel');
+ var err = getError('-foo()', {foo: function () { throw sentinel; }, compileDebug: true})
+ assert(/Jade:1/.test(err.message))
+ assert(/-foo\(\)/.test(err.message))
+ });
+ });
+ describe('with a filename that does not correspond to a real file and `compileDebug` left undefined', function () {
+ it('just reports the line number', function () {
+ var sentinel = new Error('sentinel');
+ var err = getError('-foo()', {foo: function () { throw sentinel; }, filename: 'fake.jade'})
+ assert(/on line 1/.test(err.message))
+ });
+ });
+ describe('with a filename that corresponds to a real file and `compileDebug` left undefined', function () {
+ it('includes detail of where the error was thrown including the filename', function () {
+ var sentinel = new Error('sentinel');
+ var path = __dirname + '/fixtures/runtime.error.jade'
+ var err = getError(fs.readFileSync(path, 'utf8'), {foo: function () { throw sentinel; }, filename: path})
+ assert(/fixtures[\\\/]runtime\.error\.jade:1/.test(err.message))
+ assert(/-foo\(\)/.test(err.message))
+ });
+ });
+ describe('in a mixin', function () {
+ it('includes detail of where the error was thrown including the filename', function () {
+ var err = getFileError(__dirname + '/fixtures/runtime.with.mixin.error.jade', {})
+ assert(/mixin.error.jade:2/.test(err.message))
+ assert(/Cannot read property 'length' of null/.test(err.message))
+ });
+ });
+ describe('in a layout', function () {
+ it('includes detail of where the error was thrown including the filename', function () {
+ var err = getFileError(__dirname + '/fixtures/runtime.layout.error.jade', {})
+ assert(/layout.with.runtime.error.jade:3/.test(err.message))
+ assert(/Cannot read property 'length' of undefined/.test(err.message))
+ });
+ });
+ });
+ describe('deprecated features', function () {
+ it('deprecates `!!!` in favour of `doctype`', function () {
+ var err = getError('!!! 5', {filename: 'test.jade'})
+ assert(/test\.jade:1/.test(err.message))
+ assert(/`!!!` is deprecated, you must now use `doctype`/.test(err.message))
+ });
+ it('deprecates `doctype 5` in favour of `doctype html`', function () {
+ var err = getError('doctype 5', {filename: 'test.jade'})
+ assert(/test\.jade:1/.test(err.message))
+ assert(/`doctype 5` is deprecated, you must now use `doctype html`/.test(err.message))
+ });
+ it('warns about element-with-multiple-attributes', function () {
+ var consoleWarn = console.warn;
+ var log = '';
+ console.warn = function (str) {
+ log += str;
+ };
+ var res = jade.renderFile(__dirname + '/fixtures/element-with-multiple-attributes.jade');
+ console.warn = consoleWarn;
+ assert(/element-with-multiple-attributes.jade, line 1:/.test(log));
+ assert(/You should not have jade tags with multiple attributes/.test(log));
+ assert(res === '<div attr="val" foo="bar"></div>');
+ });
+ it('warns about missing space at the start of a line', function () {
+ var consoleWarn = console.warn;
+ var log = '';
+ console.warn = function (str) {
+ log += str;
+ };
+ var res = jade.render('%This line is plain text, but it should not be', {filename: 'foo.jade'});
+ console.warn = consoleWarn;
+ assert(log === 'Warning: missing space before text for line 1 of jade file "foo.jade"');
+ assert(res === '%This line is plain text, but it should not be');
+ });
+ });
+ describe('if you throw something that isn\'t an error', function () {
+ it('just rethrows without modification', function () {
+ var err = getError('- throw "foo"');
+ assert(err === 'foo');
+ });
+ });
+ describe('import without a filename for a basedir', function () {
+ it('throws an error', function () {
+ var err = getError('include foo.jade');
+ assert(/the "filename" option is required to use/.test(err.message));
+ var err = getError('include /foo.jade');
+ assert(/the "basedir" option is required to use/.test(err.message));
+ })
+ });
+});
diff --git a/test/examples.js b/test/examples.js
new file mode 100644
index 0000000..2738b2d
--- /dev/null
+++ b/test/examples.js
@@ -0,0 +1,25 @@
+'use strict';
+
+var fs = require('fs');
+var jade = require('../');
+
+describe('examples', function () {
+ it('none of them throw any errors', function () {
+ var log = console.log;
+ var err = console.error;
+ console.log = function () {};
+ console.error = function () {};
+ try {
+ fs.readdirSync(__dirname + '/../examples').forEach(function (example) {
+ if (/\.js$/.test(example)) {
+ require('../examples/' + example);
+ }
+ });
+ } catch (ex) {
+ console.log = log;
+ console.error = err;
+ }
+ console.log = log;
+ console.error = err;
+ });
+});
\ No newline at end of file
diff --git a/test/fixtures/append-without-block/app-layout.jade b/test/fixtures/append-without-block/app-layout.jade
new file mode 100644
index 0000000..50984bd
--- /dev/null
+++ b/test/fixtures/append-without-block/app-layout.jade
@@ -0,0 +1,5 @@
+
+extends layout
+
+append head
+ script(src='app.js')
\ No newline at end of file
diff --git a/test/fixtures/append-without-block/layout.jade b/test/fixtures/append-without-block/layout.jade
new file mode 100644
index 0000000..671b3c9
--- /dev/null
+++ b/test/fixtures/append-without-block/layout.jade
@@ -0,0 +1,7 @@
+
+html
+ block head
+ script(src='vendor/jquery.js')
+ script(src='vendor/caustic.js')
+ body
+ block body
\ No newline at end of file
diff --git a/test/fixtures/append-without-block/page.jade b/test/fixtures/append-without-block/page.jade
new file mode 100644
index 0000000..5a6e640
--- /dev/null
+++ b/test/fixtures/append-without-block/page.jade
@@ -0,0 +1,6 @@
+
+extends app-layout
+
+append head
+ script(src='foo.js')
+ script(src='bar.js')
diff --git a/test/fixtures/append/app-layout.jade b/test/fixtures/append/app-layout.jade
new file mode 100644
index 0000000..48bf886
--- /dev/null
+++ b/test/fixtures/append/app-layout.jade
@@ -0,0 +1,5 @@
+
+extends layout
+
+block append head
+ script(src='app.js')
\ No newline at end of file
diff --git a/test/fixtures/append/layout.jade b/test/fixtures/append/layout.jade
new file mode 100644
index 0000000..671b3c9
--- /dev/null
+++ b/test/fixtures/append/layout.jade
@@ -0,0 +1,7 @@
+
+html
+ block head
+ script(src='vendor/jquery.js')
+ script(src='vendor/caustic.js')
+ body
+ block body
\ No newline at end of file
diff --git a/test/fixtures/append/page.html b/test/fixtures/append/page.html
new file mode 100644
index 0000000..bc5e126
--- /dev/null
+++ b/test/fixtures/append/page.html
@@ -0,0 +1,9 @@
+<html>
+ <script src="vendor/jquery.js"></script>
+ <script src="vendor/caustic.js"></script>
+ <script src="app.js"></script>
+ <script src="foo.js"></script>
+ <script src="bar.js"></script>
+ <body>
+ </body>
+</html>
diff --git a/test/fixtures/append/page.jade b/test/fixtures/append/page.jade
new file mode 100644
index 0000000..1ae9909
--- /dev/null
+++ b/test/fixtures/append/page.jade
@@ -0,0 +1,6 @@
+
+extends app-layout
+
+block append head
+ script(src='foo.js')
+ script(src='bar.js')
diff --git a/test/fixtures/compile.with.include.locals.error.jade b/test/fixtures/compile.with.include.locals.error.jade
new file mode 100644
index 0000000..fc333b0
--- /dev/null
+++ b/test/fixtures/compile.with.include.locals.error.jade
@@ -0,0 +1 @@
+include include.locals.error.jade
\ No newline at end of file
diff --git a/test/fixtures/compile.with.include.syntax.error.jade b/test/fixtures/compile.with.include.syntax.error.jade
new file mode 100644
index 0000000..9beb67d
--- /dev/null
+++ b/test/fixtures/compile.with.include.syntax.error.jade
@@ -0,0 +1 @@
+include include.syntax.error.jade
\ No newline at end of file
diff --git a/test/fixtures/compile.with.layout.locals.error.jade b/test/fixtures/compile.with.layout.locals.error.jade
new file mode 100644
index 0000000..8069c8e
--- /dev/null
+++ b/test/fixtures/compile.with.layout.locals.error.jade
@@ -0,0 +1 @@
+extends layout.locals.error.jade
\ No newline at end of file
diff --git a/test/fixtures/compile.with.layout.syntax.error.jade b/test/fixtures/compile.with.layout.syntax.error.jade
new file mode 100644
index 0000000..691bc3a
--- /dev/null
+++ b/test/fixtures/compile.with.layout.syntax.error.jade
@@ -0,0 +1 @@
+extends layout.syntax.error.jade
\ No newline at end of file
diff --git a/test/fixtures/compile.with.layout.with.include.locals.error.jade b/test/fixtures/compile.with.layout.with.include.locals.error.jade
new file mode 100644
index 0000000..1bea1fd
--- /dev/null
+++ b/test/fixtures/compile.with.layout.with.include.locals.error.jade
@@ -0,0 +1 @@
+extends compile.with.include.locals.error.jade
\ No newline at end of file
diff --git a/test/fixtures/compile.with.layout.with.include.syntax.error.jade b/test/fixtures/compile.with.layout.with.include.syntax.error.jade
new file mode 100644
index 0000000..6e93fe6
--- /dev/null
+++ b/test/fixtures/compile.with.layout.with.include.syntax.error.jade
@@ -0,0 +1 @@
+extends compile.with.include.syntax.error.jade
\ No newline at end of file
diff --git a/test/fixtures/element-with-multiple-attributes.jade b/test/fixtures/element-with-multiple-attributes.jade
new file mode 100644
index 0000000..e76f560
--- /dev/null
+++ b/test/fixtures/element-with-multiple-attributes.jade
@@ -0,0 +1 @@
+div(attr='val')(foo='bar')
\ No newline at end of file
diff --git a/test/fixtures/include.locals.error.jade b/test/fixtures/include.locals.error.jade
new file mode 100644
index 0000000..bd604a9
--- /dev/null
+++ b/test/fixtures/include.locals.error.jade
@@ -0,0 +1,2 @@
+
+= foo()
\ No newline at end of file
diff --git a/test/fixtures/include.syntax.error.jade b/test/fixtures/include.syntax.error.jade
new file mode 100644
index 0000000..8b0542a
--- /dev/null
+++ b/test/fixtures/include.syntax.error.jade
@@ -0,0 +1,2 @@
+
+= foo(
\ No newline at end of file
diff --git a/test/fixtures/layout.locals.error.jade b/test/fixtures/layout.locals.error.jade
new file mode 100644
index 0000000..bd604a9
--- /dev/null
+++ b/test/fixtures/layout.locals.error.jade
@@ -0,0 +1,2 @@
+
+= foo()
\ No newline at end of file
diff --git a/test/fixtures/layout.syntax.error.jade b/test/fixtures/layout.syntax.error.jade
new file mode 100644
index 0000000..8b0542a
--- /dev/null
+++ b/test/fixtures/layout.syntax.error.jade
@@ -0,0 +1,2 @@
+
+= foo(
\ No newline at end of file
diff --git a/test/fixtures/layout.with.runtime.error.jade b/test/fixtures/layout.with.runtime.error.jade
new file mode 100644
index 0000000..73d3a0d
--- /dev/null
+++ b/test/fixtures/layout.with.runtime.error.jade
@@ -0,0 +1,5 @@
+html
+ body
+ = foo.length
+ block content
+
diff --git a/test/fixtures/mixin-include.jade b/test/fixtures/mixin-include.jade
new file mode 100644
index 0000000..491fc70
--- /dev/null
+++ b/test/fixtures/mixin-include.jade
@@ -0,0 +1,5 @@
+mixin bang
+ +foo
+
+mixin foo
+ p bar
\ No newline at end of file
diff --git a/test/fixtures/mixin.error.jade b/test/fixtures/mixin.error.jade
new file mode 100644
index 0000000..5a4fdf4
--- /dev/null
+++ b/test/fixtures/mixin.error.jade
@@ -0,0 +1,2 @@
+mixin mixin-with-error(foo)
+ - foo.length
diff --git a/test/fixtures/multi-append-prepend-block/redefine.jade b/test/fixtures/multi-append-prepend-block/redefine.jade
new file mode 100644
index 0000000..b0e3449
--- /dev/null
+++ b/test/fixtures/multi-append-prepend-block/redefine.jade
@@ -0,0 +1,5 @@
+extends root
+
+block content
+ .content
+ | Defined content
\ No newline at end of file
diff --git a/test/fixtures/multi-append-prepend-block/root.jade b/test/fixtures/multi-append-prepend-block/root.jade
new file mode 100644
index 0000000..8e3334a
--- /dev/null
+++ b/test/fixtures/multi-append-prepend-block/root.jade
@@ -0,0 +1,5 @@
+block content
+ | default content
+
+block head
+ script(src='/app.js')
\ No newline at end of file
diff --git a/test/fixtures/perf.jade b/test/fixtures/perf.jade
new file mode 100644
index 0000000..9aa454b
--- /dev/null
+++ b/test/fixtures/perf.jade
@@ -0,0 +1,32 @@
+.data
+ ol.sortable#contents
+ each item in report
+ if (!item.parent)
+ div
+ li.chapter(data-ref= item.id)
+ a(href='/admin/report/detail/' + item.id)
+ = item.name
+ - var chp = item.id
+ ol.sortable
+ each item in report
+ if (item.parent === chp && item.type === 'section')
+ div
+ li.section(data-ref= item.id)
+ a(href='/admin/report/detail/' + item.id)
+ = item.name
+ - var sec = item.id
+ ol.sortable
+ each item in report
+ if (item.parent === sec && item.type === 'page')
+ div
+ li.page(data-ref= item.id)
+ a(href='/admin/report/detail/' + item.id)
+ = item.name
+ - var page = item.id
+ ol.sortable
+ each item in report
+ if (item.parent === page && item.type === 'subpage')
+ div
+ li.subpage(data-ref= item.id)
+ a(href='/admin/report/detail/' + item.id)
+ = item.name
\ No newline at end of file
diff --git a/test/fixtures/prepend-without-block/app-layout.jade b/test/fixtures/prepend-without-block/app-layout.jade
new file mode 100644
index 0000000..711801f
--- /dev/null
+++ b/test/fixtures/prepend-without-block/app-layout.jade
@@ -0,0 +1,5 @@
+
+extends layout
+
+prepend head
+ script(src='app.js')
\ No newline at end of file
diff --git a/test/fixtures/prepend-without-block/layout.jade b/test/fixtures/prepend-without-block/layout.jade
new file mode 100644
index 0000000..671b3c9
--- /dev/null
+++ b/test/fixtures/prepend-without-block/layout.jade
@@ -0,0 +1,7 @@
+
+html
+ block head
+ script(src='vendor/jquery.js')
+ script(src='vendor/caustic.js')
+ body
+ block body
\ No newline at end of file
diff --git a/test/fixtures/prepend-without-block/page.html b/test/fixtures/prepend-without-block/page.html
new file mode 100644
index 0000000..8753a42
--- /dev/null
+++ b/test/fixtures/prepend-without-block/page.html
@@ -0,0 +1,9 @@
+<html>
+ <script src="foo.js"></script>
+ <script src="bar.js"></script>
+ <script src="app.js"></script>
+ <script src="vendor/jquery.js"></script>
+ <script src="vendor/caustic.js"></script>
+ <body>
+ </body>
+</html>
diff --git a/test/fixtures/prepend-without-block/page.jade b/test/fixtures/prepend-without-block/page.jade
new file mode 100644
index 0000000..d8c3d3d
--- /dev/null
+++ b/test/fixtures/prepend-without-block/page.jade
@@ -0,0 +1,6 @@
+
+extends app-layout
+
+prepend head
+ script(src='foo.js')
+ script(src='bar.js')
diff --git a/test/fixtures/prepend/app-layout.jade b/test/fixtures/prepend/app-layout.jade
new file mode 100644
index 0000000..fcadc04
--- /dev/null
+++ b/test/fixtures/prepend/app-layout.jade
@@ -0,0 +1,5 @@
+
+extends layout
+
+block prepend head
+ script(src='app.js')
\ No newline at end of file
diff --git a/test/fixtures/prepend/layout.jade b/test/fixtures/prepend/layout.jade
new file mode 100644
index 0000000..671b3c9
--- /dev/null
+++ b/test/fixtures/prepend/layout.jade
@@ -0,0 +1,7 @@
+
+html
+ block head
+ script(src='vendor/jquery.js')
+ script(src='vendor/caustic.js')
+ body
+ block body
\ No newline at end of file
diff --git a/test/fixtures/prepend/page.html b/test/fixtures/prepend/page.html
new file mode 100644
index 0000000..8753a42
--- /dev/null
+++ b/test/fixtures/prepend/page.html
@@ -0,0 +1,9 @@
+<html>
+ <script src="foo.js"></script>
+ <script src="bar.js"></script>
+ <script src="app.js"></script>
+ <script src="vendor/jquery.js"></script>
+ <script src="vendor/caustic.js"></script>
+ <body>
+ </body>
+</html>
diff --git a/test/fixtures/prepend/page.jade b/test/fixtures/prepend/page.jade
new file mode 100644
index 0000000..a359645
--- /dev/null
+++ b/test/fixtures/prepend/page.jade
@@ -0,0 +1,6 @@
+
+extends app-layout
+
+block prepend head
+ script(src='foo.js')
+ script(src='bar.js')
diff --git a/test/fixtures/runtime.error.jade b/test/fixtures/runtime.error.jade
new file mode 100644
index 0000000..27794a4
--- /dev/null
+++ b/test/fixtures/runtime.error.jade
@@ -0,0 +1 @@
+-foo()
\ No newline at end of file
diff --git a/test/fixtures/runtime.layout.error.jade b/test/fixtures/runtime.layout.error.jade
new file mode 100644
index 0000000..ca0954e
--- /dev/null
+++ b/test/fixtures/runtime.layout.error.jade
@@ -0,0 +1,3 @@
+extends layout.with.runtime.error.jade
+block content
+ | some content
diff --git a/test/fixtures/runtime.with.mixin.error.jade b/test/fixtures/runtime.with.mixin.error.jade
new file mode 100644
index 0000000..69cdf97
--- /dev/null
+++ b/test/fixtures/runtime.with.mixin.error.jade
@@ -0,0 +1,3 @@
+include mixin.error.jade
+
+mixin mixin-with-error(null)
diff --git a/test/fixtures/scripts.jade b/test/fixtures/scripts.jade
new file mode 100644
index 0000000..30fabcf
--- /dev/null
+++ b/test/fixtures/scripts.jade
@@ -0,0 +1,2 @@
+script(src='/jquery.js')
+script(src='/caustic.js')
\ No newline at end of file
diff --git a/test/jade.test.js b/test/jade.test.js
new file mode 100644
index 0000000..23311b9
--- /dev/null
+++ b/test/jade.test.js
@@ -0,0 +1,996 @@
+'use strict';
+
+var jade = require('../');
+var assert = require('assert');
+var fs = require('fs');
+
+var perfTest = fs.readFileSync(__dirname + '/fixtures/perf.jade', 'utf8')
+
+describe('jade', function(){
+
+ describe('.properties', function(){
+ it('should have exports', function(){
+ assert.equal('object', typeof jade.selfClosing, 'exports.selfClosing missing');
+ assert.equal('object', typeof jade.doctypes, 'exports.doctypes missing');
+ assert.equal('function', typeof jade.filters, 'exports.filters missing');
+ assert.equal('object', typeof jade.utils, 'exports.utils missing');
+ assert.equal('function', typeof jade.Compiler, 'exports.Compiler missing');
+ });
+ });
+
+ describe('.compile()', function(){
+ it('should support doctypes', function(){
+ assert.equal('<?xml version="1.0" encoding="utf-8" ?>', jade.render('doctype xml'));
+ assert.equal('<!DOCTYPE html>', jade.render('doctype html'));
+ assert.equal('<!DOCTYPE foo bar baz>', jade.render('doctype foo bar baz'));
+ assert.equal('<!DOCTYPE html>', jade.render('doctype html'));
+ assert.equal('<!DOCTYPE html>', jade.render('doctype', { doctype:'html' }));
+ assert.equal('<!DOCTYPE html>', jade.render('doctype html', { doctype:'xml' }));
+ assert.equal('<html></html>', jade.render('html'));
+ assert.equal('<!DOCTYPE html><html></html>', jade.render('html', { doctype:'html' }));
+ assert.equal('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN>', jade.render('doctype html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN'));
+ });
+
+ it('should support Buffers', function(){
+ assert.equal('<p>foo</p>', jade.render(new Buffer('p foo')));
+ });
+
+ 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 = [
+ 'p',
+ 'div',
+ 'img'
+ ].join('\r');
+
+ var html = [
+ '<p></p>',
+ '<div></div>',
+ '<img/>'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+
+ var str = [
+ 'p',
+ 'div',
+ 'img'
+ ].join('\r\n');
+
+ var html = [
+ '<p></p>',
+ '<div></div>',
+ '<img>'
+ ].join('');
+
+ assert.equal(html, jade.render(str, { doctype:'html' }));
+ });
+
+ it('should support single quotes', function(){
+ assert.equal("<p>'foo'</p>", jade.render("p 'foo'"));
+ assert.equal("<p>'foo'</p>", jade.render("p\n | 'foo'"));
+ assert.equal('<a href="/foo"></a>', jade.render("- var path = 'foo';\na(href='/' + path)"));
+ });
+
+ it('should support block-expansion', function(){
+ assert.equal("<li><a>foo</a></li><li><a>bar</a></li><li><a>baz</a></li>", jade.render("li: a foo\nli: a bar\nli: a baz"));
+ assert.equal("<li class=\"first\"><a>foo</a></li><li><a>bar</a></li><li><a>baz</a></li>", jade.render("li.first: a foo\nli: a bar\nli: a baz"));
+ assert.equal('<div class="foo"><div class="bar">baz</div></div>', jade.render(".foo: .bar baz"));
+ });
+
+ it('should support tags', function(){
+ var str = [
+ 'p',
+ 'div',
+ 'img'
+ ].join('\n');
+
+ var html = [
+ '<p></p>',
+ '<div></div>',
+ '<img/>'
+ ].join('');
+
+ assert.equal(html, jade.render(str), 'Test basic tags');
+ assert.equal('<fb:foo-bar></fb:foo-bar>', jade.render('fb:foo-bar'), 'Test hyphens');
+ assert.equal('<div class="something"></div>', jade.render('div.something'), 'Test classes');
+ assert.equal('<div id="something"></div>', jade.render('div#something'), 'Test ids');
+ assert.equal('<div class="something"></div>', jade.render('.something'), 'Test stand-alone classes');
+ assert.equal('<div id="something"></div>', jade.render('#something'), 'Test stand-alone ids');
+ assert.equal('<div id="foo" class="bar"></div>', jade.render('#foo.bar'));
+ assert.equal('<div id="foo" class="bar"></div>', jade.render('.bar#foo'));
+ assert.equal('<div id="foo" class="bar"></div>', jade.render('div#foo(class="bar")'));
+ assert.equal('<div id="foo" class="bar"></div>', jade.render('div(class="bar")#foo'));
+ assert.equal('<div id="bar" class="foo"></div>', jade.render('div(id="bar").foo'));
+ assert.equal('<div class="foo bar baz"></div>', jade.render('div.foo.bar.baz'));
+ assert.equal('<div class="foo bar baz"></div>', jade.render('div(class="foo").bar.baz'));
+ assert.equal('<div class="foo bar baz"></div>', jade.render('div.foo(class="bar").baz'));
+ assert.equal('<div class="foo bar baz"></div>', jade.render('div.foo.bar(class="baz")'));
+ assert.equal('<div class="a-b2"></div>', jade.render('div.a-b2'));
+ assert.equal('<div class="a_b2"></div>', jade.render('div.a_b2'));
+ assert.equal('<fb:user></fb:user>', jade.render('fb:user'));
+ assert.equal('<fb:user:role></fb:user:role>', jade.render('fb:user:role'));
+ assert.equal('<colgroup><col class="test"/></colgroup>', jade.render('colgroup\n col.test'));
+ });
+
+ it('should support nested tags', function(){
+ var str = [
+ 'ul',
+ ' li a',
+ ' li b',
+ ' li',
+ ' ul',
+ ' li c',
+ ' li d',
+ ' li e',
+ ].join('\n');
+
+ var html = [
+ '<ul>',
+ '<li>a</li>',
+ '<li>b</li>',
+ '<li><ul><li>c</li><li>d</li></ul></li>',
+ '<li>e</li>',
+ '</ul>'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+
+ var str = [
+ 'a(href="#")',
+ ' | foo ',
+ ' | bar ',
+ ' | baz'
+ ].join('\n');
+
+ assert.equal('<a href="#">foo \nbar \nbaz</a>', jade.render(str));
+
+ var str = [
+ 'ul',
+ ' li one',
+ ' ul',
+ ' | two',
+ ' li three'
+ ].join('\n');
+
+ var html = [
+ '<ul>',
+ '<li>one</li>',
+ '<ul>two',
+ '<li>three</li>',
+ '</ul>',
+ '</ul>'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+ });
+
+ it('should support variable length newlines', function(){
+ var str = [
+ 'ul',
+ ' li a',
+ ' ',
+ ' li b',
+ ' ',
+ ' ',
+ ' li',
+ ' ul',
+ ' li c',
+ '',
+ ' li d',
+ ' li e',
+ ].join('\n');
+
+ var html = [
+ '<ul>',
+ '<li>a</li>',
+ '<li>b</li>',
+ '<li><ul><li>c</li><li>d</li></ul></li>',
+ '<li>e</li>',
+ '</ul>'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+ });
+
+ it('should support tab conversion', function(){
+ var str = [
+ 'ul',
+ '\tli a',
+ '\t',
+ '\tli b',
+ '\t\t',
+ '\t\t\t\t\t\t',
+ '\tli',
+ '\t\tul',
+ '\t\t\tli c',
+ '',
+ '\t\t\tli d',
+ '\tli e',
+ ].join('\n');
+
+ var html = [
+ '<ul>',
+ '<li>a</li>',
+ '<li>b</li>',
+ '<li><ul><li>c</li><li>d</li></ul></li>',
+ '<li>e</li>',
+ '</ul>'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+ });
+
+ it('should support newlines', function(){
+ var str = [
+ 'ul',
+ ' li a',
+ ' ',
+ ' ',
+ '',
+ ' ',
+ ' li b',
+ ' li',
+ ' ',
+ ' ',
+ ' ',
+ ' ul',
+ ' ',
+ ' li c',
+ ' li d',
+ ' li e',
+ ].join('\n');
+
+ var html = [
+ '<ul>',
+ '<li>a</li>',
+ '<li>b</li>',
+ '<li><ul><li>c</li><li>d</li></ul></li>',
+ '<li>e</li>',
+ '</ul>'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+
+ var str = [
+ 'html',
+ ' ',
+ ' head',
+ ' != "test"',
+ ' ',
+ ' ',
+ ' ',
+ ' body'
+ ].join('\n');
+
+ var html = [
+ '<html>',
+ '<head>',
+ 'test',
+ '</head>',
+ '<body></body>',
+ '</html>'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+ assert.equal('<foo></foo>something<bar></bar>', jade.render('foo\n= "something"\nbar'));
+ assert.equal('<foo></foo>something<bar></bar>else', jade.render('foo\n= "something"\nbar\n= "else"'));
+ });
+
+ it('should support text', function(){
+ assert.equal('foo\nbar\nbaz', jade.render('| foo\n| bar\n| baz'));
+ assert.equal('foo \nbar \nbaz', jade.render('| foo \n| bar \n| baz'));
+ assert.equal('(hey)', jade.render('| (hey)'));
+ assert.equal('some random text', jade.render('| some random text'));
+ assert.equal(' foo', jade.render('| foo'));
+ assert.equal(' foo ', jade.render('| foo '));
+ assert.equal(' foo \n bar ', jade.render('| foo \n| bar '));
+ });
+
+ it('should support pipe-less text', function(){
+ assert.equal('<pre><code><foo></foo><bar></bar></code></pre>', jade.render('pre\n code\n foo\n\n bar'));
+ assert.equal('<p>foo\n\nbar</p>', jade.render('p.\n foo\n\n bar'));
+ assert.equal('<p>foo\n\n\n\nbar</p>', jade.render('p.\n foo\n\n\n\n bar'));
+ assert.equal('<p>foo\n bar\nfoo</p>', jade.render('p.\n foo\n bar\n foo'));
+ assert.equal('<script>s.parentNode.insertBefore(g,s)</script>', jade.render('script.\n s.parentNode.insertBefore(g,s)\n'));
+ assert.equal('<script>s.parentNode.insertBefore(g,s)</script>', jade.render('script.\n s.parentNode.insertBefore(g,s)'));
+ });
+
+ it('should support tag text', function(){
+ assert.equal('<p>some random text</p>', jade.render('p some random text'));
+ assert.equal('<p>click<a>Google</a>.</p>', jade.render('p\n | click\n a Google\n | .'));
+ assert.equal('<p>(parens)</p>', jade.render('p (parens)'));
+ assert.equal('<p foo="bar">(parens)</p>', jade.render('p(foo="bar") (parens)'));
+ assert.equal('<option value="">-- (optional) foo --</option>', jade.render('option(value="") -- (optional) foo --'));
+ });
+
+ it('should support tag text block', function(){
+ assert.equal('<p>foo \nbar \nbaz</p>', jade.render('p\n | foo \n | bar \n | baz'));
+ assert.equal('<label>Password:<input/></label>', jade.render('label\n | Password:\n input'));
+ assert.equal('<label>Password:<input/></label>', jade.render('label Password:\n input'));
+ });
+
+ it('should support tag text interpolation', function(){
+ assert.equal('yo, jade is cool', jade.render('| yo, #{name} is cool\n', { name: 'jade' }));
+ assert.equal('<p>yo, jade is cool</p>', jade.render('p yo, #{name} is cool', { name: 'jade' }));
+ assert.equal('yo, jade is cool', jade.render('| yo, #{name || "jade"} is cool', { name: null }));
+ assert.equal('yo, \'jade\' is cool', jade.render('| yo, #{name || "\'jade\'"} is cool', { name: null }));
+ assert.equal('foo <script> bar', jade.render('| foo #{code} bar', { code: '<script>' }));
+ assert.equal('foo <script> bar', jade.render('| foo !{code} bar', { code: '<script>' }));
+ });
+
+ it('should support flexible indentation', function(){
+ assert.equal('<html><body><h1>Wahoo</h1><p>test</p></body></html>', jade.render('html\n body\n h1 Wahoo\n p test'));
+ });
+
+ it('should support interpolation values', function(){
+ assert.equal('<p>Users: 15</p>', jade.render('p Users: #{15}'));
+ assert.equal('<p>Users: </p>', jade.render('p Users: #{null}'));
+ assert.equal('<p>Users: </p>', jade.render('p Users: #{undefined}'));
+ assert.equal('<p>Users: none</p>', jade.render('p Users: #{undefined || "none"}'));
+ assert.equal('<p>Users: 0</p>', jade.render('p Users: #{0}'));
+ assert.equal('<p>Users: false</p>', jade.render('p Users: #{false}'));
+ });
+
+ it('should support test html 5 mode', function(){
+ assert.equal('<!DOCTYPE html><input type="checkbox" checked>', jade.render('doctype html\ninput(type="checkbox", checked)'));
+ assert.equal('<!DOCTYPE html><input type="checkbox" checked>', jade.render('doctype html\ninput(type="checkbox", checked=true)'));
+ assert.equal('<!DOCTYPE html><input type="checkbox">', jade.render('doctype html\ninput(type="checkbox", checked= false)'));
+ });
+
+ it('should support multi-line attrs', function(){
+ assert.equal('<a foo="bar" bar="baz" checked="checked">foo</a>', jade.render('a(foo="bar"\n bar="baz"\n checked) foo'));
+ assert.equal('<a foo="bar" bar="baz" checked="checked">foo</a>', jade.render('a(foo="bar"\nbar="baz"\nchecked) foo'));
+ assert.equal('<a foo="bar" bar="baz" checked="checked">foo</a>', jade.render('a(foo="bar"\n,bar="baz"\n,checked) foo'));
+ assert.equal('<a foo="bar" bar="baz" checked="checked">foo</a>', jade.render('a(foo="bar",\nbar="baz",\nchecked) foo'));
+ });
+
+ it('should support attrs', function(){
+ assert.equal('<img src="<script>"/>', jade.render('img(src="<script>")'), 'Test attr escaping');
+
+ assert.equal('<a data-attr="bar"></a>', jade.render('a(data-attr="bar")'));
+ assert.equal('<a data-attr="bar" data-attr-2="baz"></a>', jade.render('a(data-attr="bar", data-attr-2="baz")'));
+
+ assert.equal('<a title="foo,bar"></a>', jade.render('a(title= "foo,bar")'));
+ assert.equal('<a title="foo,bar" href="#"></a>', jade.render('a(title= "foo,bar", href="#")'));
+
+ assert.equal('<p class="foo"></p>', jade.render("p(class='foo')"), 'Test single quoted attrs');
+ assert.equal('<input type="checkbox" checked="checked"/>', jade.render('input( type="checkbox", checked )'));
+ assert.equal('<input type="checkbox" checked="checked"/>', jade.render('input( type="checkbox", checked = true )'));
+ assert.equal('<input type="checkbox"/>', jade.render('input(type="checkbox", checked= false)'));
+ assert.equal('<input type="checkbox"/>', jade.render('input(type="checkbox", checked= null)'));
+ assert.equal('<input type="checkbox"/>', jade.render('input(type="checkbox", checked= undefined)'));
+
+ assert.equal('<img src="/foo.png"/>', jade.render('img(src="/foo.png")'), 'Test attr =');
+ assert.equal('<img src="/foo.png"/>', jade.render('img(src = "/foo.png")'), 'Test attr = whitespace');
+ assert.equal('<img src="/foo.png"/>', jade.render('img(src="/foo.png")'), 'Test attr :');
+ assert.equal('<img src="/foo.png"/>', jade.render('img(src = "/foo.png")'), 'Test attr : whitespace');
+
+ assert.equal('<img src="/foo.png" alt="just some foo"/>', jade.render('img(src="/foo.png", alt="just some foo")'));
+ assert.equal('<img src="/foo.png" alt="just some foo"/>', jade.render('img(src = "/foo.png", alt = "just some foo")'));
+
+ assert.equal('<p class="foo,bar,baz"></p>', jade.render('p(class="foo,bar,baz")'));
+ assert.equal('<a href="http://google.com" title="Some : weird = title"></a>', jade.render('a(href= "http://google.com", title= "Some : weird = title")'));
+ assert.equal('<label for="name"></label>', jade.render('label(for="name")'));
+ assert.equal('<meta name="viewport" content="width=device-width"/>', jade.render("meta(name= 'viewport', content='width=device-width')"), 'Test attrs that contain attr separators');
+ assert.equal('<div style="color= white"></div>', jade.render("div(style='color= white')"));
+ assert.equal('<div style="color: white"></div>', jade.render("div(style='color: white')"));
+ assert.equal('<p class="foo"></p>', jade.render("p('class'='foo')"), 'Test keys with single quotes');
+ assert.equal('<p class="foo"></p>', jade.render("p(\"class\"= 'foo')"), 'Test keys with double quotes');
+
+ assert.equal('<p data-lang="en"></p>', jade.render('p(data-lang = "en")'));
+ assert.equal('<p data-dynamic="true"></p>', jade.render('p("data-dynamic"= "true")'));
+ assert.equal('<p data-dynamic="true" class="name"></p>', jade.render('p("class"= "name", "data-dynamic"= "true")'));
+ assert.equal('<p data-dynamic="true"></p>', jade.render('p(\'data-dynamic\'= "true")'));
+ assert.equal('<p data-dynamic="true" class="name"></p>', jade.render('p(\'class\'= "name", \'data-dynamic\'= "true")'));
+ assert.equal('<p data-dynamic="true" yay="yay" class="name"></p>', jade.render('p(\'class\'= "name", \'data-dynamic\'= "true", yay)'));
+
+ assert.equal('<input checked="checked" type="checkbox"/>', jade.render('input(checked, type="checkbox")'));
+
+ assert.equal('<a data-foo="{ foo: \'bar\', bar= \'baz\' }"></a>', jade.render('a(data-foo = "{ foo: \'bar\', bar= \'baz\' }")'));
+
+ assert.equal('<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>', jade.render('meta(http-equiv="X-UA-Compatible", content="IE=edge,chrome=1")'));
+
+ assert.equal('<div style="background: url(/images/test.png)">Foo</div>', jade.render("div(style= 'background: url(/images/test.png)') Foo"));
+ assert.equal('<div style="background = url(/images/test.png)">Foo</div>', jade.render("div(style= 'background = url(/images/test.png)') Foo"));
+ assert.equal('<div style="foo">Foo</div>', jade.render("div(style= ['foo', 'bar'][0]) Foo"));
+ assert.equal('<div style="bar">Foo</div>', jade.render("div(style= { foo: 'bar', baz: 'raz' }['foo']) Foo"));
+ assert.equal('<a href="def">Foo</a>', jade.render("a(href='abcdefg'.substr(3,3)) Foo"));
+ assert.equal('<a href="def">Foo</a>', jade.render("a(href={test: 'abcdefg'}.test.substr(3,3)) Foo"));
+ assert.equal('<a href="def">Foo</a>', jade.render("a(href={test: 'abcdefg'}.test.substr(3,[0,3][1])) Foo"));
+
+ assert.equal('<rss xmlns:atom="atom"></rss>', jade.render("rss(xmlns:atom=\"atom\")"));
+ assert.equal('<rss xmlns:atom="atom"></rss>', jade.render("rss('xmlns:atom'=\"atom\")"));
+ assert.equal('<rss xmlns:atom="atom"></rss>', jade.render("rss(\"xmlns:atom\"='atom')"));
+ assert.equal('<rss xmlns:atom="atom" foo="bar"></rss>', jade.render("rss('xmlns:atom'=\"atom\", 'foo'= 'bar')"));
+ assert.equal('<a data-obj="{ foo: \'bar\' }"></a>', jade.render("a(data-obj= \"{ foo: 'bar' }\")"));
+
+ assert.equal('<meta content="what\'s up? \'weee\'"/>', jade.render('meta(content="what\'s up? \'weee\'")'));
+ });
+
+ it('should support class attr array', function(){
+ assert.equal('<body class="foo bar baz"></body>', jade.render('body(class=["foo", "bar", "baz"])'));
+ });
+
+ it('should support attr interpolation', function(){
+ // Test single quote interpolation
+ assert.equal('<a href="/user/12">tj</a>'
+ , jade.render("a(href='/user/#{id}') #{name}", { name: 'tj', id: 12 }));
+
+ assert.equal('<a href="/user/12-tj">tj</a>'
+ , jade.render("a(href='/user/#{id}-#{name}') #{name}", { name: 'tj', id: 12 }));
+
+ assert.equal('<a href="/user/<script>">tj</a>'
+ , jade.render("a(href='/user/#{id}') #{name}", { name: 'tj', id: '<script>' }));
+
+ // Test double quote interpolation
+ assert.equal('<a href="/user/13">ds</a>'
+ , jade.render('a(href="/user/#{id}") #{name}', { name: 'ds', id: 13 }));
+
+ assert.equal('<a href="/user/13-ds">ds</a>'
+ , jade.render('a(href="/user/#{id}-#{name}") #{name}', { name: 'ds', id: 13 }));
+
+ assert.equal('<a href="/user/<script>">ds</a>'
+ , jade.render('a(href="/user/#{id}") #{name}', { name: 'ds', id: '<script>' }));
+
+ // Test escaping the interpolation
+ assert.equal('<a href="/user/#{id}">#{name}</a>'
+ , jade.render('a(href="/user/\\#{id}") \\#{name}', {}));
+ assert.equal('<a href="/user/#{id}">ds</a>'
+ , jade.render('a(href="/user/\\#{id}") #{name}', {name: 'ds'}));
+ });
+
+ it('should support attr parens', function(){
+ assert.equal('<p foo="bar">baz</p>', jade.render('p(foo=((("bar"))))= ((("baz")))'));
+ });
+
+ it('should support code attrs', function(){
+ assert.equal('<p></p>', jade.render('p(id= name)', { name: undefined }));
+ assert.equal('<p></p>', jade.render('p(id= name)', { name: null }));
+ assert.equal('<p></p>', jade.render('p(id= name)', { name: false }));
+ assert.equal('<p id=""></p>', jade.render('p(id= name)', { name: '' }));
+ assert.equal('<p id="tj"></p>', jade.render('p(id= name)', { name: 'tj' }));
+ assert.equal('<p id="default"></p>', jade.render('p(id= name || "default")', { name: null }));
+ assert.equal('<p id="something"></p>', jade.render("p(id= 'something')", { name: null }));
+ assert.equal('<p id="something"></p>', jade.render("p(id = 'something')", { name: null }));
+ assert.equal('<p id="foo"></p>', jade.render("p(id= (true ? 'foo' : 'bar'))"));
+ assert.equal('<option value="">Foo</option>', jade.render("option(value='') Foo"));
+ });
+
+ it('should support code attrs class', function(){
+ assert.equal('<p class="tj"></p>', jade.render('p(class= name)', { name: 'tj' }));
+ assert.equal('<p class="tj"></p>', jade.render('p( class= name )', { name: 'tj' }));
+ assert.equal('<p class="default"></p>', jade.render('p(class= name || "default")', { name: null }));
+ assert.equal('<p class="foo default"></p>', jade.render('p.foo(class= name || "default")', { name: null }));
+ assert.equal('<p class="default foo"></p>', jade.render('p(class= name || "default").foo', { name: null }));
+ assert.equal('<p id="default"></p>', jade.render('p(id = name || "default")', { name: null }));
+ assert.equal('<p id="user-1"></p>', jade.render('p(id = "user-" + 1)'));
+ assert.equal('<p class="user-1"></p>', jade.render('p(class = "user-" + 1)'));
+ });
+
+ it('should support code buffering', function(){
+ assert.equal('<p></p>', jade.render('p= null'));
+ assert.equal('<p></p>', jade.render('p= undefined'));
+ assert.equal('<p>0</p>', jade.render('p= 0'));
+ assert.equal('<p>false</p>', jade.render('p= false'));
+ });
+
+ it('should support script text', function(){
+ var str = [
+ 'script.',
+ ' p foo',
+ '',
+ 'script(type="text/template")',
+ ' p foo',
+ '',
+ 'script(type="text/template").',
+ ' p foo'
+ ].join('\n');
+
+ var html = [
+ '<script>p foo\n</script>',
+ '<script type="text/template"><p>foo</p></script>',
+ '<script type="text/template">p foo</script>'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+ });
+
+ it('should support comments', function(){
+ // Regular
+ var str = [
+ '//foo',
+ 'p bar'
+ ].join('\n');
+
+ var html = [
+ '<!--foo-->',
+ '<p>bar</p>'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+
+ // Between tags
+
+ var str = [
+ 'p foo',
+ '// bar ',
+ 'p baz'
+ ].join('\n');
+
+ var html = [
+ '<p>foo</p>',
+ '<!-- bar -->',
+ '<p>baz</p>'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+
+ // Quotes
+
+ var str = "<!-- script(src: '/js/validate.js') -->",
+ js = "// script(src: '/js/validate.js') ";
+ assert.equal(str, jade.render(js));
+ });
+
+ it('should support unbuffered comments', function(){
+ var str = [
+ '//- foo',
+ 'p bar'
+ ].join('\n');
+
+ var html = [
+ '<p>bar</p>'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+
+ var str = [
+ 'p foo',
+ '//- bar ',
+ 'p baz'
+ ].join('\n');
+
+ var html = [
+ '<p>foo</p>',
+ '<p>baz</p>'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+ });
+
+ it('should support literal html', function(){
+ assert.equal('<!--[if IE lt 9]>weeee<![endif]-->', jade.render('<!--[if IE lt 9]>weeee<![endif]-->'));
+ });
+
+ it('should support code', function(){
+ assert.equal('test', jade.render('!= "test"'));
+ assert.equal('test', jade.render('= "test"'));
+ assert.equal('test', jade.render('- var foo = "test"\n=foo'));
+ assert.equal('foo<em>test</em>bar', jade.render('- var foo = "test"\n| foo\nem= foo\n| bar'));
+ assert.equal('test<h2>something</h2>', jade.render('!= "test"\nh2 something'));
+
+ var str = [
+ '- var foo = "<script>";',
+ '= foo',
+ '!= foo'
+ ].join('\n');
+
+ var html = [
+ '<script>',
+ '<script>'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+
+ var str = [
+ '- var foo = "<script>";',
+ '- if (foo)',
+ ' p= foo'
+ ].join('\n');
+
+ var html = [
+ '<p><script></p>'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+
+ var str = [
+ '- var foo = "<script>";',
+ '- if (foo)',
+ ' p!= foo'
+ ].join('\n');
+
+ var html = [
+ '<p><script></p>'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+
+ var str = [
+ '- var foo;',
+ '- if (foo)',
+ ' p.hasFoo= foo',
+ '- else',
+ ' p.noFoo no foo'
+ ].join('\n');
+
+ var html = [
+ '<p class="noFoo">no foo</p>'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+
+ var str = [
+ '- var foo;',
+ '- if (foo)',
+ ' p.hasFoo= foo',
+ '- else if (true)',
+ ' p kinda foo',
+ '- else',
+ ' p.noFoo no foo'
+ ].join('\n');
+
+ var html = [
+ '<p>kinda foo</p>'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+
+ var str = [
+ 'p foo',
+ '= "bar"',
+ ].join('\n');
+
+ var html = [
+ '<p>foo</p>bar'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+
+ var str = [
+ 'title foo',
+ '- if (true)',
+ ' p something',
+ ].join('\n');
+
+ var html = [
+ '<title>foo</title><p>something</p>'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+
+ var str = [
+ 'foo',
+ ' bar= "bar"',
+ ' baz= "baz"',
+ ].join('\n');
+
+ var html = [
+ '<foo>',
+ '<bar>bar',
+ '<baz>baz</baz>',
+ '</bar>',
+ '</foo>'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+ });
+
+ it('should support - each', function(){
+ // Array
+ var str = [
+ '- var items = ["one", "two", "three"];',
+ '- each item in items',
+ ' li= item'
+ ].join('\n');
+
+ var html = [
+ '<li>one</li>',
+ '<li>two</li>',
+ '<li>three</li>'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+
+ // Any enumerable (length property)
+ var str = [
+ '- var jQuery = { length: 3, 0: 1, 1: 2, 2: 3 };',
+ '- each item in jQuery',
+ ' li= item'
+ ].join('\n');
+
+ var html = [
+ '<li>1</li>',
+ '<li>2</li>',
+ '<li>3</li>'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+
+ // Empty array
+ var str = [
+ '- var items = [];',
+ '- each item in items',
+ ' li= item'
+ ].join('\n');
+
+ assert.equal('', jade.render(str));
+
+ // Object
+ var str = [
+ '- var obj = { foo: "bar", baz: "raz" };',
+ '- each val in obj',
+ ' li= val'
+ ].join('\n');
+
+ var html = [
+ '<li>bar</li>',
+ '<li>raz</li>'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+
+ // Complex
+ var str = [
+ '- var obj = { foo: "bar", baz: "raz" };',
+ '- each key in Object.keys(obj)',
+ ' li= key'
+ ].join('\n');
+
+ var html = [
+ '<li>foo</li>',
+ '<li>baz</li>'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+
+ // Keys
+ var str = [
+ '- var obj = { foo: "bar", baz: "raz" };',
+ '- each val, key in obj',
+ ' li #{key}: #{val}'
+ ].join('\n');
+
+ var html = [
+ '<li>foo: bar</li>',
+ '<li>baz: raz</li>'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+
+ // Nested
+ var str = [
+ '- var users = [{ name: "tj" }]',
+ '- each user in users',
+ ' - each val, key in user',
+ ' li #{key} #{val}',
+ ].join('\n');
+
+ var html = [
+ '<li>name tj</li>'
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+
+ var str = [
+ '- var users = ["tobi", "loki", "jane"]',
+ 'each user in users',
+ ' li= user',
+ ].join('\n');
+
+ var html = [
+ '<li>tobi</li>',
+ '<li>loki</li>',
+ '<li>jane</li>',
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+
+ var str = [
+ '- var users = ["tobi", "loki", "jane"]',
+ 'for user in users',
+ ' li= user',
+ ].join('\n');
+
+ var html = [
+ '<li>tobi</li>',
+ '<li>loki</li>',
+ '<li>jane</li>',
+ ].join('');
+
+ assert.equal(html, jade.render(str));
+ });
+
+ it('should support if', function(){
+ var str = [
+ '- var users = ["tobi", "loki", "jane"]',
+ 'if users.length',
+ ' p users: #{users.length}',
+ ].join('\n');
+
+ assert.equal('<p>users: 3</p>', jade.render(str));
+
+ assert.equal('<iframe foo="bar"></iframe>', jade.render('iframe(foo="bar")'));
+ });
+
+ it('should support unless', function(){
+ var str = [
+ '- var users = ["tobi", "loki", "jane"]',
+ 'unless users.length',
+ ' p no users',
+ ].join('\n');
+
+ assert.equal('', jade.render(str));
+
+ var str = [
+ '- var users = []',
+ 'unless users.length',
+ ' p no users',
+ ].join('\n');
+
+ assert.equal('<p>no users</p>', jade.render(str));
+ });
+
+ it('should support else', function(){
+ var str = [
+ '- var users = []',
+ 'if users.length',
+ ' p users: #{users.length}',
+ 'else',
+ ' p users: none',
+ ].join('\n');
+
+ assert.equal('<p>users: none</p>', jade.render(str));
+ });
+
+ it('should else if', function(){
+ var str = [
+ '- var users = ["tobi", "jane", "loki"]',
+ 'for user in users',
+ ' if user == "tobi"',
+ ' p awesome #{user}',
+ ' else if user == "jane"',
+ ' p lame #{user}',
+ ' else',
+ ' p #{user}',
+ ].join('\n');
+
+ assert.equal('<p>awesome tobi</p><p>lame jane</p><p>loki</p>', jade.render(str));
+ });
+
+ it('should include block', function(){
+ var str = [
+ 'html',
+ ' head',
+ ' include fixtures/scripts',
+ ' scripts(src="/app.js")',
+ ].join('\n');
+
+ assert.equal('<html><head><script src=\"/jquery.js\"></script><script src=\"/caustic.js\"></script><scripts src=\"/app.js\"></scripts></head></html>'
+ , jade.render(str, { filename: __dirname + '/jade.test.js' }));
+ });
+ });
+
+ describe('.render()', function(){
+ it('should support .jade.render(str, fn)', function(){
+ jade.render('p foo bar', function(err, str){
+ assert.ok(!err);
+ assert.equal('<p>foo bar</p>', str);
+ });
+ });
+
+ it('should support .jade.render(str, options, fn)', function(){
+ jade.render('p #{foo}', { foo: 'bar' }, function(err, str){
+ assert.ok(!err);
+ assert.equal('<p>bar</p>', str);
+ });
+ });
+
+ it('should support .jade.render(str, options, fn) cache', function(){
+ jade.render('p bar', { cache: true }, function(err, str){
+ assert.ok(/the "filename" option is required for caching/.test(err.message));
+ });
+
+ jade.render('p foo bar', { cache: true, filename: 'test' }, function(err, str){
+ assert.ok(!err);
+ assert.equal('<p>foo bar</p>', str);
+ });
+ });
+
+ it('should support .compile()', function(){
+ var fn = jade.compile('p foo');
+ assert.equal('<p>foo</p>', fn());
+ });
+
+ it('should support .compile() locals', function(){
+ var fn = jade.compile('p= foo');
+ assert.equal('<p>bar</p>', fn({ foo: 'bar' }));
+ });
+
+ it('should support .compile() no debug', function(){
+ var fn = jade.compile('p foo\np #{bar}', {compileDebug: false});
+ assert.equal('<p>foo</p><p>baz</p>', fn({bar: 'baz'}));
+ });
+
+ it('should support .compile() no debug and global helpers', function(){
+ var fn = jade.compile('p foo\np #{bar}', {compileDebug: false, helpers: 'global'});
+ assert.equal('<p>foo</p><p>baz</p>', fn({bar: 'baz'}));
+ });
+
+ it('should be reasonably fast', function(){
+ jade.compile(perfTest, {})
+ })
+ });
+
+ describe('.renderFile()', function () {
+ it('will synchronously return a string', function () {
+ var expected = fs.readFileSync(__dirname + '/cases/basic.html', 'utf8').replace(/\s/g, '');
+ var actual = jade.renderFile(__dirname + '/cases/basic.jade', {name: 'foo'}).replace(/\s/g, '');
+ assert(actual === expected);
+ });
+ it('when given a callback, it calls that rather than returning', function (done) {
+ var expected = fs.readFileSync(__dirname + '/cases/basic.html', 'utf8').replace(/\s/g, '');
+ jade.renderFile(__dirname + '/cases/basic.jade', {name: 'foo'}, function (err, actual) {
+ if (err) return done(err);
+ assert(actual.replace(/\s/g, '') === expected);
+ done();
+ });
+ });
+ it('when given a callback, it calls that rather than returning even if there are no options', function (done) {
+ var expected = fs.readFileSync(__dirname + '/cases/basic.html', 'utf8').replace(/\s/g, '');
+ jade.renderFile(__dirname + '/cases/basic.jade', function (err, actual) {
+ if (err) return done(err);
+ assert(actual.replace(/\s/g, '') === expected);
+ done();
+ });
+ });
+ it('when given a callback, it calls that with any errors', function (done) {
+ jade.renderFile(__dirname + '/fixtures/runtime.error.jade', function (err, actual) {
+ assert.ok(err);
+ done();
+ });
+ });
+ });
+
+ describe('.compileFileClient(path, options)', function () {
+ it('returns a string form of a function called `template`', function () {
+ var src = jade.compileFileClient(__dirname + '/cases/basic.jade');
+ var expected = fs.readFileSync(__dirname + '/cases/basic.html', 'utf8').replace(/\s/g, '');
+ var fn = Function('jade', src + '\nreturn template;')(jade.runtime);
+ var actual = fn({name: 'foo'}).replace(/\s/g, '');
+ assert(actual === expected);
+ });
+ });
+
+ describe('.runtime', function () {
+ describe('.merge', function () {
+ it('merges two attribute objects, giving precedensce to the second object', function () {
+ assert.deepEqual(jade.runtime.merge({}, {'class': ['foo', 'bar'], 'foo': 'bar'}), {'class': ['foo', 'bar'], 'foo': 'bar'});
+ assert.deepEqual(jade.runtime.merge({'class': ['foo'], 'foo': 'baz'}, {'class': ['bar'], 'foo': 'bar'}), {'class': ['foo', 'bar'], 'foo': 'bar'});
+ assert.deepEqual(jade.runtime.merge({'class': ['foo', 'bar'], 'foo': 'bar'}, {}), {'class': ['foo', 'bar'], 'foo': 'bar'});
+ });
+ });
+ describe('.attrs', function () {
+ it('Renders the given attributes object', function () {
+ assert.equal(jade.runtime.attrs({}), '');
+ assert.equal(jade.runtime.attrs({'class': []}), '');
+ assert.equal(jade.runtime.attrs({'class': ['foo']}), ' class="foo"');
+ assert.equal(jade.runtime.attrs({'class': ['foo'], 'id': 'bar'}), ' class="foo" id="bar"');
+ });
+ });
+ });
+});
diff --git a/test/mocha.opts b/test/mocha.opts
new file mode 100644
index 0000000..e305201
--- /dev/null
+++ b/test/mocha.opts
@@ -0,0 +1 @@
+--require should
diff --git a/test/run.js b/test/run.js
new file mode 100644
index 0000000..630cf8f
--- /dev/null
+++ b/test/run.js
@@ -0,0 +1,112 @@
+
+/**
+ * Module dependencies.
+ */
+
+var fs = require('fs');
+var assert = require('assert');
+var jade = require('../');
+var uglify = require('uglify-js');
+
+jade.filters['custom-filter'] = function (str, options) {
+ assert(str === 'foo bar');
+ assert(options.foo === 'bar');
+ return 'bar baz';
+};
+
+// test cases
+
+var cases = fs.readdirSync('test/cases').filter(function(file){
+ return ~file.indexOf('.jade');
+}).map(function(file){
+ return file.replace('.jade', '');
+});
+try {
+ fs.mkdirSync(__dirname + '/output');
+} catch (ex) {
+ if (ex.code !== 'EEXIST') {
+ throw ex;
+ }
+}
+
+var mixinsUnusedTestRan = false;
+cases.forEach(function(test){
+ var name = test.replace(/[-.]/g, ' ');
+ it(name, function(){
+ var path = 'test/cases/' + test + '.jade';
+ var str = fs.readFileSync(path, 'utf8');
+ var html = fs.readFileSync('test/cases/' + test + '.html', 'utf8').trim().replace(/\r/g, '');
+ var fn = jade.compile(str, { filename: path, pretty: true, basedir: 'test/cases' });
+ var actual = fn({ title: 'Jade' });
+
+ fs.writeFileSync(__dirname + '/output/' + test + '.html', actual);
+ var clientCode = uglify.minify(jade.compileClient(str, {
+ filename: path,
+ pretty: true,
+ compileDebug: false,
+ basedir: 'test/cases'
+ }), {output: {beautify: true}, mangle: false, compress: false, fromString: true}).code;
+ var clientCodeDebug = uglify.minify(jade.compileClient(str, {
+ filename: path,
+ pretty: true,
+ compileDebug: true,
+ basedir: 'test/cases'
+ }), {output: {beautify: true}, mangle: false, compress: false, fromString: true}).code;
+ fs.writeFileSync(__dirname + '/output/' + test + '.js', uglify.minify(jade.compileClient(str, {
+ filename: path,
+ pretty: false,
+ compileDebug: false,
+ basedir: 'test/cases'
+ }), {output: {beautify: true}, mangle: false, compress: false, fromString: true}).code);
+ if (/filter/.test(test)) {
+ actual = actual.replace(/\n| /g, '');
+ html = html.replace(/\n| /g, '');
+ }
+ if (/mixins-unused/.test(test)) {
+ mixinsUnusedTestRan = true;
+ assert(/never-called/.test(str), 'never-called is in the jade file for mixins-unused');
+ assert(!/never-called/.test(clientCode), 'never-called should be removed from the code');
+ }
+ JSON.stringify(actual.trim()).should.equal(JSON.stringify(html));
+ actual = Function('jade', clientCode + '\nreturn template;')(jade.runtime)({ title: 'Jade' });
+ if (/filter/.test(test)) {
+ actual = actual.replace(/\n| /g, '');
+ }
+ JSON.stringify(actual.trim()).should.equal(JSON.stringify(html));
+ actual = Function('jade', clientCodeDebug + '\nreturn template;')(jade.runtime)({ title: 'Jade' });
+ if (/filter/.test(test)) {
+ actual = actual.replace(/\n| /g, '');
+ }
+ JSON.stringify(actual.trim()).should.equal(JSON.stringify(html));
+ })
+});
+after(function () {
+ assert(mixinsUnusedTestRan, 'mixins-unused test should run');
+})
+
+// test cases
+
+var anti = fs.readdirSync('test/anti-cases').filter(function(file){
+ return ~file.indexOf('.jade');
+}).map(function(file){
+ return file.replace('.jade', '');
+});
+
+describe('certain syntax is not allowed and will throw a compile time error', function () {
+ anti.forEach(function(test){
+ var name = test.replace(/[-.]/g, ' ');
+ it(name, function(){
+ var path = 'test/anti-cases/' + test + '.jade';
+ var str = fs.readFileSync(path, 'utf8');
+ try {
+ var fn = jade.compile(str, { filename: path, pretty: true, basedir: 'test/anti-cases' });
+ } catch (ex) {
+ ex.should.be.an.instanceof(Error);
+ ex.message.replace(/\\/g, '/').should.startWith(path);
+ ex.message.replace(/\\/g, '/').should.match(/:\d+$/m);
+ return;
+ }
+ throw new Error(test + ' should have thrown an error');
+ })
+ });
+});
\ No newline at end of file
diff --git a/test/unit.js b/test/unit.js
new file mode 100644
index 0000000..24b2db8
--- /dev/null
+++ b/test/unit.js
@@ -0,0 +1,40 @@
+
+var runtime = require('../lib/runtime')
+ , merge = runtime.merge;
+
+describe('merge(a, b, escaped)', function(){
+ it('should merge classes into strings', function(){
+ merge({ foo: 'bar' }, { bar: 'baz' })
+ .should.eql({ foo: 'bar', bar: 'baz' });
+
+ merge({ class: [] }, {})
+ .should.eql({ class: [] });
+
+ merge({ class: [] }, { class: [] })
+ .should.eql({ class: [] });
+
+ merge({ class: [] }, { class: ['foo'] })
+ .should.eql({ class: ['foo'] });
+
+ merge({ class: ['foo'] }, {})
+ .should.eql({ class: ['foo'] });
+
+ merge({ class: ['foo'] }, { class: ['bar'] })
+ .should.eql({ class: ['foo','bar'] });
+
+ merge({ class: ['foo', 'raz'] }, { class: ['bar', 'baz'] })
+ .should.eql({ class: ['foo', 'raz', 'bar', 'baz'] });
+
+ merge({ class: 'foo' }, { class: 'bar' })
+ .should.eql({ class: ['foo', 'bar'] });
+
+ merge({ class: 'foo' }, { class: ['bar', 'baz'] })
+ .should.eql({ class: ['foo', 'bar', 'baz'] });
+
+ merge({ class: ['foo', 'bar'] }, { class: 'baz' })
+ .should.eql({ class: ['foo', 'bar', 'baz'] });
+
+ merge({ class: ['foo', null, 'bar'] }, { class: [undefined, null, 0, 'baz'] })
+ .should.eql({ class: ['foo', 'bar', 0, 'baz'] });
+ })
+})
\ No newline at end of file
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-jade.git
More information about the Pkg-javascript-commits
mailing list