Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Add functionality for dynamic includes #93 #156

Open
wants to merge 1 commit into from

7 participants

@jasper-lyons

As per the issue raise #93 and with some amendments to corrects the suggest fix. Variables can now be used with includes so that the contents of the variable is evaluated to a string and then the include statement is evaluated as normal with the value of the string as the file name (without extension or path) to search for.

@phplego

+1 Why this very old issue still has not solved by this solution?
And, yes, this pull request has an issue. Variable "path" is declared twice. You need just to remove "var" keyword inside the "if" statement

@tj
Owner
tj commented

you'd have to pass it via options? seems a little weird, unless I'm misreading that, but it's not that simple, need caching etc, I haven't seen a good solid solution yet that's why there's no merge

@Vadorequest

The var path isn't declared twice, only once since the code goes either in the if or else statement. That's not an issue. To make things look better we also could declare the var path; before the if statement.

Edit: Seems like I'm wrong:

This would be because of javascript variables all being hoisted to the top of the scope and, as an if statement doesn't create a new scope, it is in fact defined twice.

@jjackoway

Is there a reason this can't be as simple as changing
var path = resolveInclude(name, filename);
to
var path = resolveInclude(options[name] ? options[name] : name, filename);
and leave off the options change? Am I missing something obvious?

@nikmartin

so, without this merge or one like it, is there a way to get dynamic include paths in ejs? I need to do includes based on a portion of the path being dynamic, and have wired ejs DEEP into my app's architecture.

@mde
Collaborator

I'd be happy to merge this PR or one like it if it doesn't break existing tests. I'm taking a look at it now, buit the merge might go faster if the authors of the PR get tests passing before submitting it.

@jasper-lyons jasper-lyons Add functionality for dynamic includes #93
Replaced path with templatePath as that is more semmantic and
lifted the templatePath definition out of the if statement so as to
prvent future jshint errors.
0b69844
@jasper-lyons

@mde I've fixed this PR so that existing test pass while still providing the required functionality. If there are any other changes you'd like me to make, just let me know. :)

@mde
Collaborator

@jasper-lyons I'm probably missing something, but I'm not clear on what that block of code with the switch is for in this last commit.

I made a simpler change yesterday in the master branch which passes all the tests, and added a test for the variable-based include. Can you take a look at it and see if it handles what you'd say the uses-cases are for this?

@mde
Collaborator

@jasper-lyons Ah, I was looking at the changes to the built file. Looks like your change is pretty much what I implemented in master. Can you verify if it works for you?

@jasper-lyons

I can verify that it does and passes all test for me.

I added a simple test case here at line 304-308 with some fixtures, simply outlining an automated test for the use-case.

I would be happy to work on future extensions to this functionality if required.

@mde
Collaborator
mde commented

@jasper-lyons, I am doing a version 2.0 (using my preexisting EJS implementation from Geddy and previous) that handles includes as a simple function call at runtime: https://github.com/mde/ejs Could you take a look at it and let me know if it works for you? I believe I've gotten parity with all the existing functionality -- except filters, which I'd really like to deprecate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 1, 2015
  1. @jasper-lyons

    Add functionality for dynamic includes #93

    jasper-lyons authored Jasper Lyons committed
    Replaced path with templatePath as that is more semmantic and
    lifted the templatePath definition out of the if statement so as to
    prvent future jshint errors.
This page is out of date. Refresh to see the latest.
View
25 ejs.js
@@ -50,7 +50,6 @@ require.relative = function (parent) {
require.register("ejs.js", function(module, exports, require){
-
/*!
* EJS
* Copyright(c) 2012 TJ Holowaychuk <tj@vision-media.ca>
@@ -209,18 +208,34 @@ var parse = exports.parse = function(str, options){
consumeEOL = true;
}
- if (0 == js.trim().indexOf('include')) {
- var name = js.trim().slice(7).trim();
+ if (0 == js.trim().indexOf('include')) {
+ var optionName = js.trim().slice(7).trim();
if (!filename) throw new Error('filename option is required for includes');
+
+ // If the options provided to include is a variable in scope
+ // use the value of that variable.
+ var name = options[optionName] || optionName;
var path = resolveInclude(name, filename);
include = read(path, 'utf8');
- include = exports.parse(include, { filename: path, _with: false, open: open, close: close, compileDebug: compileDebug });
+ include = exports.parse(include, { filename: path, _with: false, open: open, close: close, compileDebug: compileDebug });
buf += "' + (function(){" + include + "})() + '";
js = '';
}
while (~(n = js.indexOf("\n", n))) n++, lineno++;
- if (js.substr(0, 1) == ':') js = filtered(js);
+
+ switch(js.substr(0, 1)) {
+ case ':':
+ js = filtered(js);
+ break;
+ case '%':
+ js = " buf.push('<%" + js.substring(1).replace(/'/g, "\\'") + "%>');";
+ break;
+ case '#':
+ js = "";
+ break;
+ }
+
if (js) {
if (js.lastIndexOf('//') > js.lastIndexOf('\n')) js += '\n';
buf += prefix;
View
2  ejs.min.js
@@ -1 +1 @@
-ejs=function(){function require(p){if("fs"==p)return{};if("path"==p)return{};var path=require.resolve(p),mod=require.modules[path];if(!mod)throw new Error('failed to require "'+p+'"');if(!mod.exports){mod.exports={};mod.call(mod.exports,mod,mod.exports,require.relative(path))}return mod.exports}require.modules={};require.resolve=function(path){var orig=path,reg=path+".js",index=path+"/index.js";return require.modules[reg]&&reg||require.modules[index]&&index||orig};require.register=function(path,fn){require.modules[path]=fn};require.relative=function(parent){return function(p){if("."!=p.substr(0,1))return require(p);var path=parent.split("/"),segs=p.split("/");path.pop();for(var i=0;i<segs.length;i++){var seg=segs[i];if(".."==seg)path.pop();else if("."!=seg)path.push(seg)}return require(path.join("/"))}};require.register("ejs.js",function(module,exports,require){var utils=require("./utils"),path=require("path"),dirname=path.dirname,extname=path.extname,join=path.join,fs=require("fs"),read=fs.readFileSync;var filters=exports.filters=require("./filters");var cache={};exports.clearCache=function(){cache={}};function filtered(js){return js.substr(1).split("|").reduce(function(js,filter){var parts=filter.split(":"),name=parts.shift(),args=parts.join(":")||"";if(args)args=", "+args;return"filters."+name+"("+js+args+")"})}function rethrow(err,str,filename,lineno){var lines=str.split("\n"),start=Math.max(lineno-3,0),end=Math.min(lines.length,lineno+3);var context=lines.slice(start,end).map(function(line,i){var curr=i+start+1;return(curr==lineno?" >> ":" ")+curr+"| "+line}).join("\n");err.path=filename;err.message=(filename||"ejs")+":"+lineno+"\n"+context+"\n\n"+err.message;throw err}var parse=exports.parse=function(str,options){var options=options||{},open=options.open||exports.open||"<%",close=options.close||exports.close||"%>",filename=options.filename,compileDebug=options.compileDebug!==false,buf="";buf+="var buf = [];";if(false!==options._with)buf+="\nwith (locals || {}) { (function(){ ";buf+="\n buf.push('";var lineno=1;var consumeEOL=false;for(var i=0,len=str.length;i<len;++i){var stri=str[i];if(str.slice(i,open.length+i)==open){i+=open.length;var prefix,postfix,line=(compileDebug?"__stack.lineno=":"")+lineno;switch(str[i]){case"=":prefix="', escape(("+line+", ";postfix=")), '";++i;break;case"-":prefix="', ("+line+", ";postfix="), '";++i;break;default:prefix="');"+line+";";postfix="; buf.push('"}var end=str.indexOf(close,i);if(end<0){throw new Error('Could not find matching close tag "'+close+'".')}var js=str.substring(i,end),start=i,include=null,n=0;if("-"==js[js.length-1]){js=js.substring(0,js.length-2);consumeEOL=true}if(0==js.trim().indexOf("include")){var name=js.trim().slice(7).trim();if(!filename)throw new Error("filename option is required for includes");var path=resolveInclude(name,filename);include=read(path,"utf8");include=exports.parse(include,{filename:path,_with:false,open:open,close:close,compileDebug:compileDebug});buf+="' + (function(){"+include+"})() + '";js=""}while(~(n=js.indexOf("\n",n)))n++,lineno++;if(js.substr(0,1)==":")js=filtered(js);if(js){if(js.lastIndexOf("//")>js.lastIndexOf("\n"))js+="\n";buf+=prefix;buf+=js;buf+=postfix}i+=end-start+close.length-1}else if(stri=="\\"){buf+="\\\\"}else if(stri=="'"){buf+="\\'"}else if(stri=="\r"){}else if(stri=="\n"){if(consumeEOL){consumeEOL=false}else{buf+="\\n";lineno++}}else{buf+=stri}}if(false!==options._with)buf+="'); })();\n} \nreturn buf.join('');";else buf+="');\nreturn buf.join('');";return buf};var compile=exports.compile=function(str,options){options=options||{};var escape=options.escape||utils.escape;var input=JSON.stringify(str),compileDebug=options.compileDebug!==false,client=options.client,filename=options.filename?JSON.stringify(options.filename):"undefined";if(compileDebug){str=["var __stack = { lineno: 1, input: "+input+", filename: "+filename+" };",rethrow.toString(),"try {",exports.parse(str,options),"} catch (err) {"," rethrow(err, __stack.input, __stack.filename, __stack.lineno);","}"].join("\n")}else{str=exports.parse(str,options)}if(options.debug)console.log(str);if(client)str="escape = escape || "+escape.toString()+";\n"+str;try{var fn=new Function("locals, filters, escape, rethrow",str)}catch(err){if("SyntaxError"==err.name){err.message+=options.filename?" in "+filename:" while compiling ejs"}throw err}if(client)return fn;return function(locals){return fn.call(this,locals,filters,escape,rethrow)}};exports.render=function(str,options){var fn,options=options||{};if(options.cache){if(options.filename){fn=cache[options.filename]||(cache[options.filename]=compile(str,options))}else{throw new Error('"cache" option requires "filename".')}}else{fn=compile(str,options)}options.__proto__=options.locals;return fn.call(options.scope,options)};exports.renderFile=function(path,options,fn){var key=path+":string";if("function"==typeof options){fn=options,options={}}options.filename=path;var str;try{str=options.cache?cache[key]||(cache[key]=read(path,"utf8")):read(path,"utf8")}catch(err){fn(err);return}fn(null,exports.render(str,options))};function resolveInclude(name,filename){var path=join(dirname(filename),name);var ext=extname(name);if(!ext)path+=".ejs";return path}exports.__express=exports.renderFile;if(require.extensions){require.extensions[".ejs"]=function(module,filename){filename=filename||module.filename;var options={filename:filename,client:true},template=fs.readFileSync(filename).toString(),fn=compile(template,options);module._compile("module.exports = "+fn.toString()+";",filename)}}else if(require.registerExtension){require.registerExtension(".ejs",function(src){return compile(src,{})})}});require.register("filters.js",function(module,exports,require){exports.first=function(obj){return obj[0]};exports.last=function(obj){return obj[obj.length-1]};exports.capitalize=function(str){str=String(str);return str[0].toUpperCase()+str.substr(1,str.length)};exports.downcase=function(str){return String(str).toLowerCase()};exports.upcase=function(str){return String(str).toUpperCase()};exports.sort=function(obj){return Object.create(obj).sort()};exports.sort_by=function(obj,prop){return Object.create(obj).sort(function(a,b){a=a[prop],b=b[prop];if(a>b)return 1;if(a<b)return-1;return 0})};exports.size=exports.length=function(obj){return obj.length};exports.plus=function(a,b){return Number(a)+Number(b)};exports.minus=function(a,b){return Number(a)-Number(b)};exports.times=function(a,b){return Number(a)*Number(b)};exports.divided_by=function(a,b){return Number(a)/Number(b)};exports.join=function(obj,str){return obj.join(str||", ")};exports.truncate=function(str,len,append){str=String(str);if(str.length>len){str=str.slice(0,len);if(append)str+=append}return str};exports.truncate_words=function(str,n){var str=String(str),words=str.split(/ +/);return words.slice(0,n).join(" ")};exports.replace=function(str,pattern,substitution){return String(str).replace(pattern,substitution||"")};exports.prepend=function(obj,val){return Array.isArray(obj)?[val].concat(obj):val+obj};exports.append=function(obj,val){return Array.isArray(obj)?obj.concat(val):obj+val};exports.map=function(arr,prop){return arr.map(function(obj){return obj[prop]})};exports.reverse=function(obj){return Array.isArray(obj)?obj.reverse():String(obj).split("").reverse().join("")};exports.get=function(obj,prop){return obj[prop]};exports.json=function(obj){return JSON.stringify(obj)}});require.register("utils.js",function(module,exports,require){exports.escape=function(html){return String(html).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/'/g,"&#39;").replace(/"/g,"&quot;")}});return require("ejs")}();
+ejs=function(){function require(p){if("fs"==p)return{};if("path"==p)return{};var path=require.resolve(p),mod=require.modules[path];if(!mod)throw new Error('failed to require "'+p+'"');if(!mod.exports){mod.exports={};mod.call(mod.exports,mod,mod.exports,require.relative(path))}return mod.exports}require.modules={};require.resolve=function(path){var orig=path,reg=path+".js",index=path+"/index.js";return require.modules[reg]&&reg||require.modules[index]&&index||orig};require.register=function(path,fn){require.modules[path]=fn};require.relative=function(parent){return function(p){if("."!=p.substr(0,1))return require(p);var path=parent.split("/"),segs=p.split("/");path.pop();for(var i=0;i<segs.length;i++){var seg=segs[i];if(".."==seg)path.pop();else if("."!=seg)path.push(seg)}return require(path.join("/"))}};require.register("ejs.js",function(module,exports,require){var utils=require("./utils"),path=require("path"),dirname=path.dirname,extname=path.extname,join=path.join,fs=require("fs"),read=fs.readFileSync;var filters=exports.filters=require("./filters");var cache={};exports.clearCache=function(){cache={}};function filtered(js){return js.substr(1).split("|").reduce(function(js,filter){var parts=filter.split(":"),name=parts.shift(),args=parts.join(":")||"";if(args)args=", "+args;return"filters."+name+"("+js+args+")"})}function rethrow(err,str,filename,lineno){var lines=str.split("\n"),start=Math.max(lineno-3,0),end=Math.min(lines.length,lineno+3);var context=lines.slice(start,end).map(function(line,i){var curr=i+start+1;return(curr==lineno?" >> ":" ")+curr+"| "+line}).join("\n");err.path=filename;err.message=(filename||"ejs")+":"+lineno+"\n"+context+"\n\n"+err.message;throw err}var parse=exports.parse=function(str,options){var options=options||{},open=options.open||exports.open||"<%",close=options.close||exports.close||"%>",filename=options.filename,compileDebug=options.compileDebug!==false,buf="";buf+="var buf = [];";if(false!==options._with)buf+="\nwith (locals || {}) { (function(){ ";buf+="\n buf.push('";var lineno=1;var consumeEOL=false;for(var i=0,len=str.length;i<len;++i){var stri=str[i];if(str.slice(i,open.length+i)==open){i+=open.length;var prefix,postfix,line=(compileDebug?"__stack.lineno=":"")+lineno;switch(str[i]){case"=":prefix="', escape(("+line+", ";postfix=")), '";++i;break;case"-":prefix="', ("+line+", ";postfix="), '";++i;break;default:prefix="');"+line+";";postfix="; buf.push('"}var end=str.indexOf(close,i);if(end<0){throw new Error('Could not find matching close tag "'+close+'".')}var js=str.substring(i,end),start=i,include=null,n=0;if("-"==js[js.length-1]){js=js.substring(0,js.length-2);consumeEOL=true}if(0==js.trim().indexOf("include")){var optionName=js.trim().slice(7).trim();if(!filename)throw new Error("filename option is required for includes");var name=options[optionName]||optionName;var path=resolveInclude(name,filename);include=read(path,"utf8");include=exports.parse(include,{filename:path,_with:false,open:open,close:close,compileDebug:compileDebug});buf+="' + (function(){"+include+"})() + '";js=""}while(~(n=js.indexOf("\n",n)))n++,lineno++;switch(js.substr(0,1)){case":":js=filtered(js);break;case"%":js=" buf.push('<%"+js.substring(1).replace(/'/g,"\\'")+"%>');";break;case"#":js="";break}if(js){if(js.lastIndexOf("//")>js.lastIndexOf("\n"))js+="\n";buf+=prefix;buf+=js;buf+=postfix}i+=end-start+close.length-1}else if(stri=="\\"){buf+="\\\\"}else if(stri=="'"){buf+="\\'"}else if(stri=="\r"){}else if(stri=="\n"){if(consumeEOL){consumeEOL=false}else{buf+="\\n";lineno++}}else{buf+=stri}}if(false!==options._with)buf+="'); })();\n} \nreturn buf.join('');";else buf+="');\nreturn buf.join('');";return buf};var compile=exports.compile=function(str,options){options=options||{};var escape=options.escape||utils.escape;var input=JSON.stringify(str),compileDebug=options.compileDebug!==false,client=options.client,filename=options.filename?JSON.stringify(options.filename):"undefined";if(compileDebug){str=["var __stack = { lineno: 1, input: "+input+", filename: "+filename+" };",rethrow.toString(),"try {",exports.parse(str,options),"} catch (err) {"," rethrow(err, __stack.input, __stack.filename, __stack.lineno);","}"].join("\n")}else{str=exports.parse(str,options)}if(options.debug)console.log(str);if(client)str="escape = escape || "+escape.toString()+";\n"+str;try{var fn=new Function("locals, filters, escape, rethrow",str)}catch(err){if("SyntaxError"==err.name){err.message+=options.filename?" in "+filename:" while compiling ejs"}throw err}if(client)return fn;return function(locals){return fn.call(this,locals,filters,escape,rethrow)}};exports.render=function(str,options){var fn,options=options||{};if(options.cache){if(options.filename){fn=cache[options.filename]||(cache[options.filename]=compile(str,options))}else{throw new Error('"cache" option requires "filename".')}}else{fn=compile(str,options)}options.__proto__=options.locals;return fn.call(options.scope,options)};exports.renderFile=function(path,options,fn){var key=path+":string";if("function"==typeof options){fn=options,options={}}options.filename=path;var str;try{str=options.cache?cache[key]||(cache[key]=read(path,"utf8")):read(path,"utf8")}catch(err){fn(err);return}fn(null,exports.render(str,options))};function resolveInclude(name,filename){var path=join(dirname(filename),name);var ext=extname(name);if(!ext)path+=".ejs";return path}exports.__express=exports.renderFile;if(require.extensions){require.extensions[".ejs"]=function(module,filename){filename=filename||module.filename;var options={filename:filename,client:true},template=fs.readFileSync(filename).toString(),fn=compile(template,options);module._compile("module.exports = "+fn.toString()+";",filename)}}else if(require.registerExtension){require.registerExtension(".ejs",function(src){return compile(src,{})})}});require.register("filters.js",function(module,exports,require){exports.first=function(obj){return obj[0]};exports.last=function(obj){return obj[obj.length-1]};exports.capitalize=function(str){str=String(str);return str[0].toUpperCase()+str.substr(1,str.length)};exports.downcase=function(str){return String(str).toLowerCase()};exports.upcase=function(str){return String(str).toUpperCase()};exports.sort=function(obj){return Object.create(obj).sort()};exports.sort_by=function(obj,prop){return Object.create(obj).sort(function(a,b){a=a[prop],b=b[prop];if(a>b)return 1;if(a<b)return-1;return 0})};exports.size=exports.length=function(obj){return obj.length};exports.plus=function(a,b){return Number(a)+Number(b)};exports.minus=function(a,b){return Number(a)-Number(b)};exports.times=function(a,b){return Number(a)*Number(b)};exports.divided_by=function(a,b){return Number(a)/Number(b)};exports.join=function(obj,str){return obj.join(str||", ")};exports.truncate=function(str,len,append){str=String(str);if(str.length>len){str=str.slice(0,len);if(append)str+=append}return str};exports.truncate_words=function(str,n){var str=String(str),words=str.split(/ +/);return words.slice(0,n).join(" ")};exports.replace=function(str,pattern,substitution){return String(str).replace(pattern,substitution||"")};exports.prepend=function(obj,val){return Array.isArray(obj)?[val].concat(obj):val+obj};exports.append=function(obj,val){return Array.isArray(obj)?obj.concat(val):obj+val};exports.map=function(arr,prop){return arr.map(function(obj){return obj[prop]})};exports.reverse=function(obj){return Array.isArray(obj)?obj.reverse():String(obj).split("").reverse().join("")};exports.get=function(obj,prop){return obj[prop]};exports.json=function(obj){return JSON.stringify(obj)}});require.register("utils.js",function(module,exports,require){exports.escape=function(html){return String(html).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/'/g,"&#39;").replace(/"/g,"&quot;")}});return require("ejs")}();
View
10 lib/ejs.js
@@ -156,12 +156,16 @@ var parse = exports.parse = function(str, options){
consumeEOL = true;
}
- if (0 == js.trim().indexOf('include')) {
- var name = js.trim().slice(7).trim();
+ if (0 == js.trim().indexOf('include')) {
+ var optionName = js.trim().slice(7).trim();
if (!filename) throw new Error('filename option is required for includes');
+
+ // If the options provided to include is a variable in scope
+ // use the value of that variable.
+ var name = options[optionName] || optionName;
var path = resolveInclude(name, filename);
include = read(path, 'utf8');
- include = exports.parse(include, { filename: path, _with: false, open: open, close: close, compileDebug: compileDebug });
+ include = exports.parse(include, { filename: path, _with: false, open: open, close: close, compileDebug: compileDebug });
buf += "' + (function(){" + include + "})() + '";
js = '';
}
View
6 test/ejs.js
@@ -301,6 +301,12 @@ describe('includes', function(){
.should.equal(fixture('include.css.html'));
})
+ it('should allow dynamic includes', function (){
+ var file = 'test/fixtures/dynamic1.ejs';
+ ejs.render(fixture('dynamic1.ejs'), {filename : file, dynamicTemplate : 'dynamic2'})
+ .should.equal(fixture('dynamic.html'));
+ });
+
it('should pass compileDebug to include', function(){
var file = 'test/fixtures/include.ejs';
var fn = ejs.compile(fixture('include.ejs'), { filename: file, open: '[[', close: ']]', compileDebug: false, client: true })
View
3  test/fixtures/dynamic.html
@@ -0,0 +1,3 @@
+<span>An ejs template generated this</span>
+<span>This was also generated by an ejs template</span>
+
View
2  test/fixtures/dynamic1.ejs
@@ -0,0 +1,2 @@
+<span>An ejs template generated this</span>
+<% include dynamicTemplate %>
View
1  test/fixtures/dynamic2.ejs
@@ -0,0 +1 @@
+<span>This was also generated by an ejs template</span>
Something went wrong with that request. Please try again.