Repository: mozilla/nunjucks
Pull request #581 (2015-11-10 07:22:47)

Allows compiled templates to be imported, included and extended.
This is a simple enhancement (with tests) to add functionality described in #562.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4194a8c..c6888ea 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,8 @@ Changelog
 master (unreleased)
 -------------------
 
+* Allows compiled templates to be imported, included and extended. Thanks 
+  Luis Gutierrez-Sheris. Merge of [#581](https://github.com/mozilla/nunjucks/pull/581).
 * Fix issue with different nunjucks environments sharing same globals. Each
   environment is now independent.  Thanks Paul Pechin. Merge of
   [#574](https://github.com/mozilla/nunjucks/pull/574).
diff --git a/docs/templating.md b/docs/templating.md
index ba52a59..f8aa68d 100644
--- a/docs/templating.md
+++ b/docs/templating.md
@@ -110,8 +110,10 @@ This is the default content
 ```
 
 You can store the template to inherit in a variable and use it by
-omitting quotes. That way you can dynamically change which template is
-inherited when rendering by setting it in the context.
+omitting quotes. This variable can contain a string that points to a 
+template file, or it can contain a compiled Template object that has
+been added to the context. That way you can dynamically change which 
+template is inherited when rendering by setting it in the context.
 
 ```jinja
 {% extends parentTemplate %}
@@ -357,7 +359,9 @@ Inheritance](#template-inheritance).
 ```
 
 You can store the template to inherit in a variable and use it by
-omitting quotes. That way you can dynamically change which template is
+omitting quotes. This variable can contain a string that points to a 
+template file, or it can contain a compiled Template object that has
+been added to the context. That way you can dynamically change which template is
 inherited when rendering by setting it in the context.
 
 ```jinja
@@ -365,7 +369,12 @@ inherited when rendering by setting it in the context.
 ```
 
 In fact, `extends` accepts any arbitrary expression, so you can pass
-anything into it: `{% extends name + ".html" %}`.
+anything into it, as long as that expression evaluates to a string or 
+a compiled Template object: 
+
+```jinja
+{% extends name + ".html" %}`.
+```
 
 ### block
 
@@ -419,7 +428,7 @@ You can even include templates in the middle of loops:
 
 This is especially useful for cutting up templates into pieces so that the browser-side environment can render the small chunks when it needs to change the page.
 
-`include` actually accepts any arbitrary expression, so you can pass anything into it: `{% include name + ".html" %}`.
+`include` actually accepts any arbitrary expression, so you can pass anything into it, as long as the expression evaluates to a string or a compiled Template object: `{% include name + ".html" %}`.
 
 It might be useful to not throw an error if a template does not exist. Use the `ignore missing` option to suppress such errors.
 
@@ -472,7 +481,7 @@ You can also import specific values from a template into the current namespace w
 {{ field('pass', type='password') }}
 ```
 
-`import` actually accepts any arbitrary expression, so you can pass anything into it: `{% import name + ".html" as obj %}`.
+`import` actually accepts any arbitrary expression, so you can pass anything into it, as long as the expression evaluates to a string or a compiled Template object: `{% import name + ".html" as obj %}`.
 
 ### raw
 
diff --git a/src/environment.js b/src/environment.js
index 331fd6e..b208ab7 100644
--- a/src/environment.js
+++ b/src/environment.js
@@ -171,14 +171,18 @@ var Environment = Obj.extend({
             eagerCompile = false;
         }
 
-        if(typeof name !== 'string') {
+        if (name instanceof Template) {
+             tmpl = name;
+        }
+        else if(typeof name !== 'string') {
             throw new Error('template names must be a string: ' + name);
         }
-
-        for (var i = 0; i < this.loaders.length; i++) {
-            var _name = this.resolveTemplate(this.loaders[i], parentName, name);
-            tmpl = this.loaders[i].cache[_name];
-            if (tmpl) break;
+        else {
+            for (var i = 0; i < this.loaders.length; i++) {
+                var _name = this.resolveTemplate(this.loaders[i], parentName, name);
+                tmpl = this.loaders[i].cache[_name];
+                if (tmpl) break;
+            }
         }
 
         if(tmpl) {


Test patch:
diff --git a/tests/compiler.js b/tests/compiler.js
index 0c78ae2..d3959e5 100644
--- a/tests/compiler.js
+++ b/tests/compiler.js
@@ -540,6 +540,23 @@
             finish(done);
         });
 
+        it('should import template objects', function(done) {
+            var tmpl = new Template('{% macro foo() %}Inside a macro{% endmacro %}' +
+                                    '{% set bar = "BAZ" %}');
+
+            equal('{% import tmpl as imp %}' +
+                  '{{ imp.foo() }} {{ imp.bar }}',
+                  { tmpl : tmpl },
+                  'Inside a macro BAZ');
+
+            equal('{% from tmpl import foo as baz, bar %}' +
+                  '{{ bar }} {{ baz() }}',
+                  { tmpl : tmpl },
+                  'BAZ Inside a macro');
+
+            finish(done);
+        });
+
         it('should import templates with context', function(done) {
             equal('{% set bar = "BAR" %}' +
                   '{% import "import-context.html" as imp with context %}' +
@@ -629,6 +646,23 @@
             finish(done);
         });
 
+        it('should inherit template objects', function(done) {
+            var tmpl = new Template('Foo{% block block1 %}Bar{% endblock %}' +
+                                    '{% block block2 %}Baz{% endblock %}Whizzle');
+
+            equal('hola {% extends tmpl %} fizzle mumble',
+                  { tmpl: tmpl },
+                  'FooBarBazWhizzle');
+
+            equal('{% extends tmpl %}' +
+                  '{% block block1 %}BAR{% endblock %}' +
+                  '{% block block2 %}BAZ{% endblock %}',
+                  { tmpl: tmpl },
+                  'FooBARBAZWhizzle');
+
+            finish(done);
+        });
+
         it('should conditionally inherit templates', function(done) {
             equal('{% if false %}{% extends "base.html" %}{% endif %}' +
                   '{% block block1 %}BAR{% endblock %}',
@@ -697,6 +731,16 @@
             finish(done);
         });
 
+        it('should include template objects', function(done) {
+            var tmpl = new Template('FooInclude {{ name }}');
+
+            equal('hello world {% include tmpl %}',
+                  { name: 'thedude', tmpl: tmpl },
+                  'hello world FooInclude thedude');
+
+            finish(done);
+        });
+
         it('should throw an error when including a file that does not exist', function(done) {
             render(
                 '{% include "missing.html" %}',