Repository: mozilla/nunjucks
Pull request #559 (2015-10-21 20:42:31)

Added feature to ignore missing template files for include
This feature belongs to #437.

I will add documentation etc. when anyone of you confirm this as a possible solution. Thank you.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0bda946..d26c80c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,8 @@ master (unreleased)
   [#534](https://github.com/mozilla/nunjucks/pull/534).
 * trimBlocks now also trims windows style line endings. Thanks Magnus Tovslid. Merge of
   [#548](https://github.com/mozilla/nunjucks/pull/548)
+* `include` now supports an option to suppress errors if the template does not exist. Thanks Mathias Nestler. Merge of
+  [#559](https://github.com/mozilla/nunjucks/pull/559)
 
 
 v2.1.0 (Sep 21 2015)
diff --git a/docs/templating.md b/docs/templating.md
index e33c148..3fb2273 100644
--- a/docs/templating.md
+++ b/docs/templating.md
@@ -421,6 +421,12 @@ This is especially useful for cutting up templates into pieces so that the brows
 
 `include` actually accepts any arbitrary expression, so you can pass anything into it: `{% 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.
+
+```jinja
+{% include "missing.html" ignore missing %}
+```
+
 ### import
 
 `import` loads a different template and allows you to access its exported values. Macros and top-level assignments (done with [`set`](#set)) are exported from templates, allowing you to access them in a different template.
diff --git a/src/compiler.js b/src/compiler.js
index a19dd34..e0a0526 100644
--- a/src/compiler.js
+++ b/src/compiler.js
@@ -896,7 +896,7 @@ var Compiler = Object.extend({
 
         this.emit('env.getTemplate(');
         this._compileExpression(node.template, frame);
-        this.emitLine(', false, '+this._templateName()+', ' + this.makeCallback(id));
+        this.emitLine(', false, '+this._templateName()+', false, ' + this.makeCallback(id));
         this.addScopeLevel();
 
         this.emitLine(id + '.getExported(' +
@@ -919,7 +919,7 @@ var Compiler = Object.extend({
 
         this.emit('env.getTemplate(');
         this._compileExpression(node.template, frame);
-        this.emitLine(', false, '+this._templateName()+', ' + this.makeCallback(importedId));
+        this.emitLine(', false, '+this._templateName()+', false, ' + this.makeCallback(importedId));
         this.addScopeLevel();
 
         this.emitLine(importedId + '.getExported(' +
@@ -1002,7 +1002,7 @@ var Compiler = Object.extend({
 
         this.emit('env.getTemplate(');
         this._compileExpression(node.template, frame);
-        this.emitLine(', true, '+this._templateName()+', ' + this.makeCallback('_parentTemplate'));
+        this.emitLine(', true, '+this._templateName()+', false, ' + this.makeCallback('_parentTemplate'));
 
         // extends is a dynamic tag and can occur within a block like
         // `if`, so if this happens we need to capture the parent
@@ -1023,7 +1023,7 @@ var Compiler = Object.extend({
 
         this.emit('env.getTemplate(');
         this._compileExpression(node.template, frame);
-        this.emitLine(', false, '+this._templateName()+', '+ this.makeCallback(id));
+        this.emitLine(', false, '+this._templateName()+', ' + node.ignoreMissing + ', ' + this.makeCallback(id));
         this.addScopeLevel();
 
         this.emitLine(id + '.render(' +
diff --git a/src/environment.js b/src/environment.js
index d681ed4..f2ce52c 100644
--- a/src/environment.js
+++ b/src/environment.js
@@ -150,7 +150,7 @@ var Environment = Obj.extend({
         return (isRelative && loader.resolve)? loader.resolve(parentName, filename) : filename;
     },
 
-    getTemplate: function(name, eagerCompile, parentName, cb) {
+    getTemplate: function(name, eagerCompile, parentName, ignoreMissing, cb) {
         var that = this;
         var tmpl = null;
         if(name && name.raw) {
@@ -196,7 +196,9 @@ var Environment = Obj.extend({
 
             var createTemplate = function(err, info) {
                 if(!info && !err) {
-                    err = new Error('template not found: ' + name);
+                    if(!ignoreMissing) {
+                        err = new Error('template not found: ' + name);
+                    }
                 }
 
                 if (err) {
@@ -208,11 +210,18 @@ var Environment = Obj.extend({
                     }
                 }
                 else {
-                    var tmpl = new Template(info.src, _this,
+                    var tmpl;
+                    if(info) {
+                        tmpl = new Template(info.src, _this,
                                             info.path, eagerCompile);
 
-                    if(!info.noCache) {
-                        info.loader.cache[name] = tmpl;
+                        if(!info.noCache) {
+                            info.loader.cache[name] = tmpl;
+                        }
+                    }
+                    else {
+                        tmpl = new Template('', _this,
+                                            '', eagerCompile);
                     }
 
                     if(cb) {
diff --git a/src/nodes.js b/src/nodes.js
index 47ae613..53c384a 100644
--- a/src/nodes.js
+++ b/src/nodes.js
@@ -117,7 +117,7 @@ var Block = Node.extend('Block', { fields: ['name', 'body'] });
 var Super = Node.extend('Super', { fields: ['blockName', 'symbol'] });
 var TemplateRef = Node.extend('TemplateRef', { fields: ['template'] });
 var Extends = TemplateRef.extend('Extends');
-var Include = TemplateRef.extend('Include');
+var Include = Node.extend('Include', { fields: ['template', 'ignoreMissing'] });
 var Set = Node.extend('Set', { fields: ['targets', 'value'] });
 var Output = NodeList.extend('Output');
 var TemplateData = Literal.extend('TemplateData');
diff --git a/src/parser.js b/src/parser.js
index 06e911d..ba4f047 100644
--- a/src/parser.js
+++ b/src/parser.js
@@ -409,25 +409,36 @@ var Parser = Object.extend({
         return node;
     },
 
-    parseTemplateRef: function(tagName, NodeType) {
+    parseExtends: function() {
+        var tagName = 'extends';
         var tag = this.peekToken();
         if(!this.skipSymbol(tagName)) {
             this.fail('parseTemplateRef: expected '+ tagName);
         }
 
-        var node = new NodeType(tag.lineno, tag.colno);
+        var node = new nodes.Extends(tag.lineno, tag.colno);
         node.template = this.parseExpression();
 
         this.advanceAfterBlockEnd(tag.value);
         return node;
     },
 
-    parseExtends: function() {
-        return this.parseTemplateRef('extends', nodes.Extends);
-    },
-
     parseInclude: function() {
-        return this.parseTemplateRef('include', nodes.Include);
+        var tagName = 'include';
+        var tag = this.peekToken();
+        if(!this.skipSymbol(tagName)) {
+            this.fail('parseInclude: expected '+ tagName);
+        }
+
+        var node = new nodes.Include(tag.lineno, tag.colno);
+        node.template = this.parseExpression();
+
+        if(this.skipSymbol('ignore') && this.skipSymbol('missing')) {
+            node.ignoreMissing = true;
+        }
+
+        this.advanceAfterBlockEnd(tag.value);
+        return node;
     },
 
     parseIf: function() {


Test patch:
diff --git a/tests/compiler.js b/tests/compiler.js
index 47840b6..0c78ae2 100644
--- a/tests/compiler.js
+++ b/tests/compiler.js
@@ -697,6 +697,31 @@
             finish(done);
         });
 
+        it('should throw an error when including a file that does not exist', function(done) {
+            render(
+                '{% include "missing.html" %}',
+                {},
+                { noThrow: true },
+                function(err, res) {
+                    expect(res).to.be(undefined);
+                    expect(err).to.match(/template not found: missing.html/);
+                }
+            );
+
+            finish(done);
+        });
+
+        it('should fail silently on missing templates if requested', function(done) {
+            equal('hello world {% include "missing.html" ignore missing %}',
+                  'hello world ');
+
+            equal('hello world {% include "missing.html" ignore missing %}',
+                  { name: 'thedude' },
+                  'hello world ');
+
+            finish(done);
+        });
+
         /**
          * This test checks that this issue is resolved: http://stackoverflow.com/questions/21777058/loop-index-in-included-nunjucks-file
          */