UNPKG

37.7 kBJavaScriptView Raw
1'use strict';
2/*
3 * Engines which do not support caching of their file contents
4 * should use the `read()` function defined in consolidate.js
5 * On top of this, when an engine compiles to a `Function`,
6 * these functions should either be cached within consolidate.js
7 * or the engine itself via `options.cache`. This will allow
8 * users and frameworks to pass `options.cache = true` for
9 * `NODE_ENV=production`, however edit the file(s) without
10 * re-loading the application in development.
11 */
12
13/**
14 * Module dependencies.
15 */
16
17var fs = require('fs');
18var path = require('path');
19var Promise = require('bluebird');
20
21var join = path.join;
22var resolve = path.resolve;
23var extname = path.extname;
24var dirname = path.dirname;
25var isAbsolute = path.isAbsolute;
26
27var readCache = {};
28
29/**
30 * Require cache.
31 */
32
33var cacheStore = {};
34
35/**
36 * Require cache.
37 */
38
39var requires = {};
40
41/**
42 * Clear the cache.
43 *
44 * @api public
45 */
46
47exports.clearCache = function() {
48 readCache = {};
49 cacheStore = {};
50};
51
52/**
53 * Conditionally cache `compiled` template based
54 * on the `options` filename and `.cache` boolean.
55 *
56 * @param {Object} options
57 * @param {Function} compiled
58 * @return {Function}
59 * @api private
60 */
61
62function cache(options, compiled) {
63 // cachable
64 if (compiled && options.filename && options.cache) {
65 delete readCache[options.filename];
66 cacheStore[options.filename] = compiled;
67 return compiled;
68 }
69
70 // check cache
71 if (options.filename && options.cache) {
72 return cacheStore[options.filename];
73 }
74
75 return compiled;
76}
77
78/**
79 * Read `path` with `options` with
80 * callback `(err, str)`. When `options.cache`
81 * is true the template string will be cached.
82 *
83 * @param {String} options
84 * @param {Function} cb
85 * @api private
86 */
87
88function read(path, options, cb) {
89 var str = readCache[path];
90 var cached = options.cache && str && typeof str === 'string';
91
92 // cached (only if cached is a string and not a compiled template function)
93 if (cached) return cb(null, str);
94
95 // read
96 fs.readFile(path, 'utf8', function(err, str) {
97 if (err) return cb(err);
98 // remove extraneous utf8 BOM marker
99 str = str.replace(/^\uFEFF/, '');
100 if (options.cache) readCache[path] = str;
101 cb(null, str);
102 });
103}
104
105/**
106 * Read `path` with `options` with
107 * callback `(err, str)`. When `options.cache`
108 * is true the partial string will be cached.
109 *
110 * @param {String} options
111 * @param {Function} fn
112 * @api private
113 */
114
115function readPartials(path, options, cb) {
116 if (!options.partials) return cb();
117 var partials = options.partials;
118 var keys = Object.keys(partials);
119
120 function next(index) {
121 if (index === keys.length) return cb(null);
122 var key = keys[index];
123 var partialPath = partials[key];
124
125 if (partialPath === undefined || partialPath === null || partialPath === false) {
126 return next(++index);
127 }
128
129 var file;
130 if (isAbsolute(partialPath)) {
131 if (extname(partialPath) !== '') {
132 file = partialPath;
133 } else {
134 file = join(partialPath + extname(path));
135 }
136 } else {
137 file = join(dirname(path), partialPath + extname(path));
138 }
139
140 read(file, options, function(err, str) {
141 if (err) return cb(err);
142 options.partials[key] = str;
143 next(++index);
144 });
145 }
146
147 next(0);
148}
149
150/**
151 * promisify
152 */
153function promisify(cb, fn) {
154 return new Promise(function(resolve, reject) {
155 cb = cb || function(err, html) {
156 if (err) {
157 return reject(err);
158 }
159 resolve(html);
160 };
161 fn(cb);
162 });
163}
164
165/**
166 * fromStringRenderer
167 */
168
169function fromStringRenderer(name) {
170 return function(path, options, cb) {
171 options.filename = path;
172
173 return promisify(cb, function(cb) {
174 readPartials(path, options, function(err) {
175 if (err) return cb(err);
176 if (cache(options)) {
177 exports[name].render('', options, cb);
178 } else {
179 read(path, options, function(err, str) {
180 if (err) return cb(err);
181 exports[name].render(str, options, cb);
182 });
183 }
184 });
185 });
186 };
187}
188
189/**
190 * velocity support.
191 */
192
193exports.velocityjs = fromStringRenderer('velocityjs');
194
195/**
196 * velocity string support.
197 */
198
199exports.velocityjs.render = function(str, options, cb) {
200 return promisify(cb, function(cb) {
201 var engine = requires.velocityjs || (requires.velocityjs = require('velocityjs'));
202 try {
203 options.locals = options;
204 cb(null, engine.render(str, options).trimLeft());
205 } catch (err) {
206 cb(err);
207 }
208 });
209};
210
211/**
212 * Liquid support.
213 */
214
215exports.liquid = fromStringRenderer('liquid');
216
217/**
218 * Liquid string support.
219 */
220
221/**
222 * Note that in order to get filters and custom tags we've had to push
223 * all user-defined locals down into @locals. However, just to make things
224 * backwards-compatible, any property of `options` that is left after
225 * processing and removing `locals`, `meta`, `filters`, `customTags` and
226 * `includeDir` will also become a local.
227 */
228
229function _renderTinyliquid(engine, str, options, cb) {
230 var context = engine.newContext();
231 var k;
232
233 /**
234 * Note that there's a bug in the library that doesn't allow us to pass
235 * the locals to newContext(), hence looping through the keys:
236 */
237
238 if (options.locals) {
239 for (k in options.locals) {
240 context.setLocals(k, options.locals[k]);
241 }
242 delete options.locals;
243 }
244
245 if (options.meta) {
246 context.setLocals('page', options.meta);
247 delete options.meta;
248 }
249
250 /**
251 * Add any defined filters:
252 */
253
254 if (options.filters) {
255 for (k in options.filters) {
256 context.setFilter(k, options.filters[k]);
257 }
258 delete options.filters;
259 }
260
261 /**
262 * Set up a callback for the include directory:
263 */
264
265 var includeDir = options.includeDir || process.cwd();
266
267 context.onInclude(function(name, callback) {
268 var extname = path.extname(name) ? '' : '.liquid';
269 var filename = path.resolve(includeDir, name + extname);
270
271 fs.readFile(filename, {encoding: 'utf8'}, function(err, data) {
272 if (err) return callback(err);
273 callback(null, engine.parse(data));
274 });
275 });
276 delete options.includeDir;
277
278 /**
279 * The custom tag functions need to have their results pushed back
280 * through the parser, so set up a shim before calling the provided
281 * callback:
282 */
283
284 var compileOptions = {
285 customTags: {}
286 };
287
288 if (options.customTags) {
289 var tagFunctions = options.customTags;
290
291 for (k in options.customTags) {
292 /*Tell jshint there's no problem with having this function in the loop */
293 /*jshint -W083 */
294 compileOptions.customTags[k] = function(context, name, body) {
295 var tpl = tagFunctions[name](body.trim());
296 context.astStack.push(engine.parse(tpl));
297 };
298 /*jshint +W083 */
299 }
300 delete options.customTags;
301 }
302
303 /**
304 * Now anything left in `options` becomes a local:
305 */
306
307 for (k in options) {
308 context.setLocals(k, options[k]);
309 }
310
311 /**
312 * Finally, execute the template:
313 */
314
315 var tmpl = cache(context) || cache(context, engine.compile(str, compileOptions));
316 tmpl(context, cb);
317}
318
319exports.liquid.render = function(str, options, cb) {
320 return promisify(cb, function(cb) {
321 var engine = requires.liquid;
322 var Liquid;
323
324 try {
325 // set up tinyliquid engine
326 engine = requires.liquid = require('tinyliquid');
327
328 // use tinyliquid engine
329 _renderTinyliquid(engine, str, options, cb);
330
331 return;
332
333 } catch (err) {
334
335 // set up liquid-node engine
336 try {
337 Liquid = requires.liquid = require('liquid-node');
338 engine = new Liquid.Engine();
339 } catch (err) {
340 throw err;
341 }
342
343 }
344
345 // use liquid-node engine
346 try {
347 var locals = options.locals || {};
348
349 if (options.meta) {
350 locals.pages = options.meta;
351 delete options.meta;
352 }
353
354 /**
355 * Add any defined filters:
356 */
357
358 if (options.filters) {
359 engine.registerFilters(options.filters);
360 delete options.filters;
361 }
362
363 /**
364 * Set up a callback for the include directory:
365 */
366
367 var includeDir = options.includeDir || process.cwd();
368 engine.fileSystem = new Liquid.LocalFileSystem(includeDir, 'liquid');
369 delete options.includeDir;
370
371 /**
372 * The custom tag functions need to have their results pushed back
373 * through the parser, so set up a shim before calling the provided
374 * callback:
375 */
376
377 if (options.customTags) {
378 var tagFunctions = options.customTags;
379
380 for (k in options.customTags) {
381 engine.registerTag(k, tagFunctions[k]);
382 }
383 delete options.customTags;
384 }
385
386 /**
387 * Now anything left in `options` becomes a local:
388 */
389
390 for (var k in options) {
391 locals[k] = options[k];
392 }
393
394 /**
395 * Finally, execute the template:
396 */
397
398 return engine
399 .parseAndRender(str, locals)
400 .nodeify(function(err, result) {
401 if (err) {
402 throw new Error(err);
403 } else {
404 return cb(null, result);
405 }
406 });
407
408 } catch (err) {
409 cb(err);
410 }
411 });
412};
413
414/**
415 * Jade support.
416 */
417
418exports.jade = function(path, options, cb) {
419 return promisify(cb, function(cb) {
420 var engine = requires.jade;
421 if (!engine) {
422 try {
423 engine = requires.jade = require('jade');
424 } catch (err) {
425 try {
426 engine = requires.jade = require('then-jade');
427 } catch (otherError) {
428 throw err;
429 }
430 }
431 }
432
433 try {
434 var tmpl = cache(options) || cache(options, engine.compileFile(path, options));
435 cb(null, tmpl(options));
436 } catch (err) {
437 cb(err);
438 }
439 });
440};
441
442/**
443 * Jade string support.
444 */
445
446exports.jade.render = function(str, options, cb) {
447 return promisify(cb, function(cb) {
448 var engine = requires.jade;
449 if (!engine) {
450 try {
451 engine = requires.jade = require('jade');
452 } catch (err) {
453 try {
454 engine = requires.jade = require('then-jade');
455 } catch (otherError) {
456 throw err;
457 }
458 }
459 }
460
461 try {
462 var tmpl = cache(options) || cache(options, engine.compile(str, options));
463 cb(null, tmpl(options));
464 } catch (err) {
465 cb(err);
466 }
467 });
468};
469
470/**
471 * Dust support.
472 */
473
474exports.dust = fromStringRenderer('dust');
475
476/**
477 * Dust string support.
478 */
479
480exports.dust.render = function(str, options, cb) {
481 return promisify(cb, function(cb) {
482 var engine = requires.dust;
483 if (!engine) {
484 try {
485 engine = requires.dust = require('dust');
486 } catch (err) {
487 try {
488 engine = requires.dust = require('dustjs-helpers');
489 } catch (err) {
490 engine = requires.dust = require('dustjs-linkedin');
491 }
492 }
493 }
494
495 var ext = 'dust';
496 var views = '.';
497
498 if (options) {
499 if (options.ext) ext = options.ext;
500 if (options.views) views = options.views;
501 if (options.settings && options.settings.views) views = options.settings.views;
502 }
503 if (!options || (options && !options.cache)) engine.cache = {};
504
505 engine.onLoad = function(path, callback) {
506 if (extname(path) === '') path += '.' + ext;
507 if (path[0] !== '/') path = views + '/' + path;
508 read(path, options, callback);
509 };
510
511 try {
512 var templateName;
513 if (options.filename) {
514 templateName = options.filename.replace(new RegExp('^' + views + '/'), '').replace(new RegExp('\\.' + ext), '');
515 }
516
517 var tmpl = cache(options) || cache(options, engine.compileFn(str, templateName));
518 tmpl(options, cb);
519 } catch (err) {
520 cb(err);
521 }
522 });
523};
524
525/**
526 * Swig support.
527 */
528
529exports.swig = fromStringRenderer('swig');
530
531/**
532 * Swig string support.
533 */
534
535exports.swig.render = function(str, options, cb) {
536 return promisify(cb, function(cb) {
537 var engine = requires.swig;
538 if (!engine) {
539 try {
540 engine = requires.swig = require('swig');
541 } catch (err) {
542 try {
543 engine = requires.swig = require('swig-templates');
544 } catch (otherError) {
545 throw err;
546 }
547 }
548 }
549
550 try {
551 if (options.cache === true) options.cache = 'memory';
552 engine.setDefaults({ cache: options.cache });
553 var tmpl = cache(options) || cache(options, engine.compile(str, options));
554 cb(null, tmpl(options));
555 } catch (err) {
556 cb(err);
557 }
558 });
559};
560
561/**
562 * Atpl support.
563 */
564
565exports.atpl = fromStringRenderer('atpl');
566
567/**
568 * Atpl string support.
569 */
570
571exports.atpl.render = function(str, options, cb) {
572 return promisify(cb, function(cb) {
573 var engine = requires.atpl || (requires.atpl = require('atpl'));
574 try {
575 var tmpl = cache(options) || cache(options, engine.compile(str, options));
576 cb(null, tmpl(options));
577 } catch (err) {
578 cb(err);
579 }
580 });
581};
582
583/**
584 * Liquor support,
585 */
586
587exports.liquor = fromStringRenderer('liquor');
588
589/**
590 * Liquor string support.
591 */
592
593exports.liquor.render = function(str, options, cb) {
594 return promisify(cb, function(cb) {
595 var engine = requires.liquor || (requires.liquor = require('liquor'));
596 try {
597 var tmpl = cache(options) || cache(options, engine.compile(str, options));
598 cb(null, tmpl(options));
599 } catch (err) {
600 cb(err);
601 }
602 });
603};
604
605/**
606 * Twig support.
607 */
608
609exports.twig = fromStringRenderer('twig');
610
611/**
612 * Twig string support.
613 */
614
615exports.twig.render = function(str, options, cb) {
616 return promisify(cb, function(cb) {
617 var engine = requires.twig || (requires.twig = require('twig').twig);
618 var templateData = {
619 data: str
620 };
621 try {
622 var tmpl = cache(templateData) || cache(templateData, engine(templateData));
623 cb(null, tmpl.render(options));
624 } catch (err) {
625 cb(err);
626 }
627 });
628};
629
630/**
631 * EJS support.
632 */
633
634exports.ejs = fromStringRenderer('ejs');
635
636/**
637 * EJS string support.
638 */
639
640exports.ejs.render = function(str, options, cb) {
641 return promisify(cb, function(cb) {
642 var engine = requires.ejs || (requires.ejs = require('ejs'));
643 try {
644 var tmpl = cache(options) || cache(options, engine.compile(str, options));
645 cb(null, tmpl(options));
646 } catch (err) {
647 cb(err);
648 }
649 });
650};
651
652/**
653 * Eco support.
654 */
655
656exports.eco = fromStringRenderer('eco');
657
658/**
659 * Eco string support.
660 */
661
662exports.eco.render = function(str, options, cb) {
663 return promisify(cb, function(cb) {
664 var engine = requires.eco || (requires.eco = require('eco'));
665 try {
666 cb(null, engine.render(str, options));
667 } catch (err) {
668 cb(err);
669 }
670 });
671};
672
673/**
674 * Jazz support.
675 */
676
677exports.jazz = fromStringRenderer('jazz');
678
679/**
680 * Jazz string support.
681 */
682
683exports.jazz.render = function(str, options, cb) {
684 return promisify(cb, function(cb) {
685 var engine = requires.jazz || (requires.jazz = require('jazz'));
686 try {
687 var tmpl = cache(options) || cache(options, engine.compile(str, options));
688 tmpl.eval(options, function(str) {
689 cb(null, str);
690 });
691 } catch (err) {
692 cb(err);
693 }
694 });
695};
696
697/**
698 * JQTPL support.
699 */
700
701exports.jqtpl = fromStringRenderer('jqtpl');
702
703/**
704 * JQTPL string support.
705 */
706
707exports.jqtpl.render = function(str, options, cb) {
708 return promisify(cb, function(cb) {
709 var engine = requires.jqtpl || (requires.jqtpl = require('jqtpl'));
710 try {
711 engine.template(str, str);
712 cb(null, engine.tmpl(str, options));
713 } catch (err) {
714 cb(err);
715 }
716 });
717};
718
719/**
720 * Haml support.
721 */
722
723exports.haml = fromStringRenderer('haml');
724
725/**
726 * Haml string support.
727 */
728
729exports.haml.render = function(str, options, cb) {
730 return promisify(cb, function(cb) {
731 var engine = requires.haml || (requires.haml = require('hamljs'));
732 try {
733 options.locals = options;
734 cb(null, engine.render(str, options).trimLeft());
735 } catch (err) {
736 cb(err);
737 }
738 });
739};
740
741/**
742 * Hamlet support.
743 */
744
745exports.hamlet = fromStringRenderer('hamlet');
746
747/**
748 * Hamlet string support.
749 */
750
751exports.hamlet.render = function(str, options, cb) {
752 return promisify(cb, function(cb) {
753 var engine = requires.hamlet || (requires.hamlet = require('hamlet'));
754 try {
755 options.locals = options;
756 cb(null, engine.render(str, options).trimLeft());
757 } catch (err) {
758 cb(err);
759 }
760 });
761};
762
763/**
764 * Whiskers support.
765 */
766
767exports.whiskers = function(path, options, cb) {
768 return promisify(cb, function(cb) {
769 var engine = requires.whiskers || (requires.whiskers = require('whiskers'));
770 engine.__express(path, options, cb);
771 });
772};
773
774/**
775 * Whiskers string support.
776 */
777
778exports.whiskers.render = function(str, options, cb) {
779 return promisify(cb, function(cb) {
780 var engine = requires.whiskers || (requires.whiskers = require('whiskers'));
781 try {
782 cb(null, engine.render(str, options));
783 } catch (err) {
784 cb(err);
785 }
786 });
787};
788
789/**
790 * Coffee-HAML support.
791 */
792
793exports['haml-coffee'] = fromStringRenderer('haml-coffee');
794
795/**
796 * Coffee-HAML string support.
797 */
798
799exports['haml-coffee'].render = function(str, options, cb) {
800 return promisify(cb, function(cb) {
801 var engine = requires['haml-coffee'] || (requires['haml-coffee'] = require('haml-coffee'));
802 try {
803 var tmpl = cache(options) || cache(options, engine.compile(str, options));
804 cb(null, tmpl(options));
805 } catch (err) {
806 cb(err);
807 }
808 });
809};
810
811/**
812 * Hogan support.
813 */
814
815exports.hogan = fromStringRenderer('hogan');
816
817/**
818 * Hogan string support.
819 */
820
821exports.hogan.render = function(str, options, cb) {
822 return promisify(cb, function(cb) {
823 var engine = requires.hogan || (requires.hogan = require('hogan.js'));
824 try {
825 var tmpl = cache(options) || cache(options, engine.compile(str, options));
826 cb(null, tmpl.render(options, options.partials));
827 } catch (err) {
828 cb(err);
829 }
830 });
831};
832
833/**
834 * templayed.js support.
835 */
836
837exports.templayed = fromStringRenderer('templayed');
838
839/**
840 * templayed.js string support.
841 */
842
843exports.templayed.render = function(str, options, cb) {
844 return promisify(cb, function(cb) {
845 var engine = requires.templayed || (requires.templayed = require('templayed'));
846 try {
847 var tmpl = cache(options) || cache(options, engine(str));
848 cb(null, tmpl(options));
849 } catch (err) {
850 cb(err);
851 }
852 });
853};
854
855/**
856 * Handlebars support.
857 */
858
859exports.handlebars = fromStringRenderer('handlebars');
860
861/**
862 * Handlebars string support.
863 */
864
865exports.handlebars.render = function(str, options, cb) {
866 return promisify(cb, function(cb) {
867 var engine = requires.handlebars || (requires.handlebars = require('handlebars'));
868 try {
869 for (var partial in options.partials) {
870 engine.registerPartial(partial, options.partials[partial]);
871 }
872 for (var helper in options.helpers) {
873 engine.registerHelper(helper, options.helpers[helper]);
874 }
875 var tmpl = cache(options) || cache(options, engine.compile(str, options));
876 cb(null, tmpl(options));
877 } catch (err) {
878 cb(err);
879 }
880 });
881};
882
883/**
884 * Underscore support.
885 */
886
887exports.underscore = fromStringRenderer('underscore');
888
889/**
890 * Underscore string support.
891 */
892
893exports.underscore.render = function(str, options, cb) {
894 return promisify(cb, function(cb) {
895 var engine = requires.underscore || (requires.underscore = require('underscore'));
896 try {
897 for (var partial in options.partials) {
898 options.partials[partial] = engine.template(options.partials[partial]);
899 }
900 var tmpl = cache(options) || cache(options, engine.template(str, null, options));
901 cb(null, tmpl(options).replace(/\n$/, ''));
902 } catch (err) {
903 cb(err);
904 }
905 });
906};
907
908/**
909 * Lodash support.
910 */
911
912exports.lodash = fromStringRenderer('lodash');
913
914/**
915 * Lodash string support.
916 */
917
918exports.lodash.render = function(str, options, cb) {
919 return promisify(cb, function(cb) {
920 var engine = requires.lodash || (requires.lodash = require('lodash'));
921 try {
922 var tmpl = cache(options) || cache(options, engine.template(str, options));
923 cb(null, tmpl(options).replace(/\n$/, ''));
924 } catch (err) {
925 cb(err);
926 }
927 });
928};
929
930/**
931 * Pug support. (formerly Jade)
932 */
933
934exports.pug = function(path, options, cb) {
935 return promisify(cb, function(cb) {
936 var engine = requires.pug;
937 if (!engine) {
938 try {
939 engine = requires.pug = require('pug');
940 } catch (err) {
941 try {
942 engine = requires.pug = require('then-pug');
943 } catch (otherError) {
944 throw err;
945 }
946 }
947 }
948
949 try {
950 var tmpl = cache(options) || cache(options, engine.compileFile(path, options));
951 cb(null, tmpl(options));
952 } catch (err) {
953 cb(err);
954 }
955 });
956};
957
958/**
959 * Pug string support.
960 */
961
962exports.pug.render = function(str, options, cb) {
963 return promisify(cb, function(cb) {
964 var engine = requires.pug;
965 if (!engine) {
966 try {
967 engine = requires.pug = require('pug');
968 } catch (err) {
969 try {
970 engine = requires.pug = require('then-pug');
971 } catch (otherError) {
972 throw err;
973 }
974 }
975 }
976
977 try {
978 var tmpl = cache(options) || cache(options, engine.compile(str, options));
979 cb(null, tmpl(options));
980 } catch (err) {
981 cb(err);
982 }
983 });
984};
985
986/**
987 * QEJS support.
988 */
989
990exports.qejs = fromStringRenderer('qejs');
991
992/**
993 * QEJS string support.
994 */
995
996exports.qejs.render = function(str, options, cb) {
997 return promisify(cb, function(cb) {
998 try {
999 var engine = requires.qejs || (requires.qejs = require('qejs'));
1000 engine.render(str, options).then(function(result) {
1001 cb(null, result);
1002 }, function(err) {
1003 cb(err);
1004 }).done();
1005 } catch (err) {
1006 cb(err);
1007 }
1008 });
1009};
1010
1011/**
1012 * Walrus support.
1013 */
1014
1015exports.walrus = fromStringRenderer('walrus');
1016
1017/**
1018 * Walrus string support.
1019 */
1020
1021exports.walrus.render = function(str, options, cb) {
1022 return promisify(cb, function(cb) {
1023 var engine = requires.walrus || (requires.walrus = require('walrus'));
1024 try {
1025 var tmpl = cache(options) || cache(options, engine.parse(str));
1026 cb(null, tmpl.compile(options));
1027 } catch (err) {
1028 cb(err);
1029 }
1030 });
1031};
1032
1033/**
1034 * Mustache support.
1035 */
1036
1037exports.mustache = fromStringRenderer('mustache');
1038
1039/**
1040 * Mustache string support.
1041 */
1042
1043exports.mustache.render = function(str, options, cb) {
1044 return promisify(cb, function(cb) {
1045 var engine = requires.mustache || (requires.mustache = require('mustache'));
1046 try {
1047 cb(null, engine.to_html(str, options, options.partials));
1048 } catch (err) {
1049 cb(err);
1050 }
1051 });
1052};
1053
1054/**
1055 * Just support.
1056 */
1057
1058exports.just = function(path, options, cb) {
1059 return promisify(cb, function(cb) {
1060 var engine = requires.just;
1061 if (!engine) {
1062 var JUST = require('just');
1063 engine = requires.just = new JUST();
1064 }
1065 engine.configure({ useCache: options.cache });
1066 engine.render(path, options, cb);
1067 });
1068};
1069
1070/**
1071 * Just string support.
1072 */
1073
1074exports.just.render = function(str, options, cb) {
1075 return promisify(cb, function(cb) {
1076 var JUST = require('just');
1077 var engine = new JUST({ root: { page: str }});
1078 engine.render('page', options, cb);
1079 });
1080};
1081
1082/**
1083 * ECT support.
1084 */
1085
1086exports.ect = function(path, options, cb) {
1087 return promisify(cb, function(cb) {
1088 var engine = requires.ect;
1089 if (!engine) {
1090 var ECT = require('ect');
1091 engine = requires.ect = new ECT(options);
1092 }
1093 engine.configure({ cache: options.cache });
1094 engine.render(path, options, cb);
1095 });
1096};
1097
1098/**
1099 * ECT string support.
1100 */
1101
1102exports.ect.render = function(str, options, cb) {
1103 return promisify(cb, function(cb) {
1104 var ECT = require('ect');
1105 var engine = new ECT({ root: { page: str }});
1106 engine.render('page', options, cb);
1107 });
1108};
1109
1110/**
1111 * mote support.
1112 */
1113
1114exports.mote = fromStringRenderer('mote');
1115
1116/**
1117 * mote string support.
1118 */
1119
1120exports.mote.render = function(str, options, cb) {
1121 return promisify(cb, function(cb) {
1122 var engine = requires.mote || (requires.mote = require('mote'));
1123 try {
1124 var tmpl = cache(options) || cache(options, engine.compile(str));
1125 cb(null, tmpl(options));
1126 } catch (err) {
1127 cb(err);
1128 }
1129 });
1130};
1131
1132/**
1133 * Toffee support.
1134 */
1135
1136exports.toffee = function(path, options, cb) {
1137 return promisify(cb, function(cb) {
1138 var toffee = requires.toffee || (requires.toffee = require('toffee'));
1139 toffee.__consolidate_engine_render(path, options, cb);
1140 });
1141};
1142
1143/**
1144 * Toffee string support.
1145 */
1146
1147exports.toffee.render = function(str, options, cb) {
1148 return promisify(cb, function(cb) {
1149 var engine = requires.toffee || (requires.toffee = require('toffee'));
1150 try {
1151 engine.str_render(str, options, cb);
1152 } catch (err) {
1153 cb(err);
1154 }
1155 });
1156};
1157
1158/**
1159 * doT support.
1160 */
1161
1162exports.dot = fromStringRenderer('dot');
1163
1164/**
1165 * doT string support.
1166 */
1167
1168exports.dot.render = function(str, options, cb) {
1169 return promisify(cb, function(cb) {
1170 var engine = requires.dot || (requires.dot = require('dot'));
1171 var extend = (requires.extend || (requires.extend = require('util')._extend));
1172 try {
1173 var settings = {};
1174 settings = extend(settings, engine.templateSettings);
1175 settings = extend(settings, options ? options.dot : {});
1176 var tmpl = cache(options) || cache(options, engine.template(str, settings, options));
1177 cb(null, tmpl(options));
1178 } catch (err) {
1179 cb(err);
1180 }
1181 });
1182};
1183
1184/**
1185 * bracket support.
1186 */
1187
1188exports.bracket = fromStringRenderer('bracket');
1189
1190/**
1191 * bracket string support.
1192 */
1193
1194exports.bracket.render = function(str, options, cb) {
1195 return promisify(cb, function(cb) {
1196 var engine = requires.bracket || (requires.bracket = require('bracket-template'));
1197 try {
1198 var tmpl = cache(options) || cache(options, engine.default.compile(str, options));
1199 cb(null, tmpl(options));
1200 } catch (err) {
1201 cb(err);
1202 }
1203 });
1204};
1205
1206/**
1207 * Ractive support.
1208 */
1209
1210exports.ractive = fromStringRenderer('ractive');
1211
1212/**
1213 * Ractive string support.
1214 */
1215
1216exports.ractive.render = function(str, options, cb) {
1217 return promisify(cb, function(cb) {
1218 var Engine = requires.ractive || (requires.ractive = require('ractive'));
1219
1220 var template = cache(options) || cache(options, Engine.parse(str));
1221 options.template = template;
1222
1223 if (options.data === null || options.data === undefined) {
1224 var extend = (requires.extend || (requires.extend = require('util')._extend));
1225
1226 // Shallow clone the options object
1227 options.data = extend({}, options);
1228
1229 // Remove consolidate-specific properties from the clone
1230 var i;
1231 var length;
1232 var properties = ['template', 'filename', 'cache', 'partials'];
1233 for (i = 0, length = properties.length; i < length; i++) {
1234 var property = properties[i];
1235 delete options.data[property];
1236 }
1237 }
1238
1239 try {
1240 cb(null, new Engine(options).toHTML());
1241 } catch (err) {
1242 cb(err);
1243 }
1244 });
1245};
1246
1247/**
1248 * Nunjucks support.
1249 */
1250
1251exports.nunjucks = fromStringRenderer('nunjucks');
1252
1253/**
1254 * Nunjucks string support.
1255 */
1256
1257exports.nunjucks.render = function(str, options, cb) {
1258 return promisify(cb, function(cb) {
1259
1260 try {
1261
1262 var engine = options.nunjucksEnv || requires.nunjucks || (requires.nunjucks = require('nunjucks'));
1263
1264 var env = engine;
1265
1266 // deprecated fallback support for express
1267 // <https://github.com/tj/consolidate.js/pull/152>
1268 // <https://github.com/tj/consolidate.js/pull/224>
1269 if (options.settings && options.settings.views) {
1270 env = engine.configure(options.settings.views);
1271 } else if (options.nunjucks && options.nunjucks.configure) {
1272 env = engine.configure.apply(engine, options.nunjucks.configure);
1273 }
1274
1275 //
1276 // because `renderString` does not initiate loaders
1277 // we must manually create a loader for it based off
1278 // either `options.settings.views` or `options.nunjucks` or `options.nunjucks.root`
1279 //
1280 // <https://github.com/mozilla/nunjucks/issues/730>
1281 // <https://github.com/crocodilejs/node-email-templates/issues/182>
1282 //
1283
1284 // so instead we simply check if we passed a custom loader
1285 // otherwise we create a simple file based loader
1286 if (options.loader) {
1287 env = new engine.Environment(options.loader);
1288 } else if (options.settings && options.settings.views) {
1289 env = new engine.Environment(
1290 new engine.FileSystemLoader(options.settings.views)
1291 );
1292 } else if (options.nunjucks && options.nunjucks.loader) {
1293 if (typeof options.nunjucks.loader === 'string') {
1294 env = new engine.Environment(new engine.FileSystemLoader(options.nunjucks.loader));
1295 } else {
1296 env = new engine.Environment(
1297 new engine.FileSystemLoader(
1298 options.nunjucks.loader[0],
1299 options.nunjucks.loader[1]
1300 )
1301 );
1302 }
1303 }
1304
1305 env.renderString(str, options, cb);
1306 } catch (err) {
1307 throw cb(err);
1308 }
1309 });
1310};
1311
1312/**
1313 * HTMLing support.
1314 */
1315
1316exports.htmling = fromStringRenderer('htmling');
1317
1318/**
1319 * HTMLing string support.
1320 */
1321
1322exports.htmling.render = function(str, options, cb) {
1323 return promisify(cb, function(cb) {
1324 var engine = requires.htmling || (requires.htmling = require('htmling'));
1325 try {
1326 var tmpl = cache(options) || cache(options, engine.string(str));
1327 cb(null, tmpl.render(options));
1328 } catch (err) {
1329 cb(err);
1330 }
1331 });
1332};
1333
1334/**
1335 * Rendering function
1336 */
1337function requireReact(module, filename) {
1338 var babel = requires.babel || (requires.babel = require('babel-core'));
1339
1340 var compiled = babel.transformFileSync(filename, { presets: [ 'react' ] }).code;
1341
1342 return module._compile(compiled, filename);
1343}
1344
1345exports.requireReact = requireReact;
1346
1347/**
1348 * Converting a string into a node module.
1349 */
1350function requireReactString(src, filename) {
1351 var babel = requires.babel || (requires.babel = require('babel-core'));
1352
1353 if (!filename) filename = '';
1354 var m = new module.constructor();
1355 filename = filename || '';
1356
1357 // Compile Using React
1358 var compiled = babel.transform(src, { presets: [ 'react' ] }).code;
1359
1360 // Compile as a module
1361 m.paths = module.paths;
1362 m._compile(compiled, filename);
1363
1364 return m.exports;
1365}
1366
1367/**
1368 * A naive helper to replace {{tags}} with options.tags content
1369 */
1370function reactBaseTmpl(data, options) {
1371
1372 var exp;
1373 var regex;
1374
1375 // Iterates through the keys in file object
1376 // and interpolate / replace {{key}} with it's value
1377 for (var k in options) {
1378 if (options.hasOwnProperty(k)) {
1379 exp = '{{' + k + '}}';
1380 regex = new RegExp(exp, 'g');
1381 if (data.match(regex)) {
1382 data = data.replace(regex, options[k]);
1383 }
1384 }
1385 }
1386
1387 return data;
1388}
1389
1390/**
1391* Plates Support.
1392*/
1393
1394exports.plates = fromStringRenderer('plates');
1395
1396/**
1397* Plates string support.
1398*/
1399
1400exports.plates.render = function(str, options, cb) {
1401 return promisify(cb, function(cb) {
1402 var engine = requires.plates || (requires.plates = require('plates'));
1403 var map = options.map || undefined;
1404 try {
1405 var tmpl = engine.bind(str, options, map);
1406 cb(null, tmpl);
1407 } catch (err) {
1408 cb(err);
1409 }
1410 });
1411};
1412
1413/**
1414 * The main render parser for React bsaed templates
1415 */
1416function reactRenderer(type) {
1417
1418 if (require.extensions) {
1419
1420 // Ensure JSX is transformed on require
1421 if (!require.extensions['.jsx']) {
1422 require.extensions['.jsx'] = requireReact;
1423 }
1424
1425 // Supporting .react extension as well as test cases
1426 // Using .react extension is not recommended.
1427 if (!require.extensions['.react']) {
1428 require.extensions['.react'] = requireReact;
1429 }
1430
1431 }
1432
1433 // Return rendering fx
1434 return function(str, options, cb) {
1435 return promisify(cb, function(cb) {
1436 // React Import
1437 var ReactDOM = requires.ReactDOM || (requires.ReactDOM = require('react-dom/server'));
1438 var react = requires.react || (requires.react = require('react'));
1439
1440 // Assign HTML Base
1441 var base = options.base;
1442 delete options.base;
1443
1444 var enableCache = options.cache;
1445 delete options.cache;
1446
1447 var isNonStatic = options.isNonStatic;
1448 delete options.isNonStatic;
1449
1450 // Start Conversion
1451 try {
1452
1453 var Code;
1454 var Factory;
1455
1456 var baseStr;
1457 var content;
1458 var parsed;
1459
1460 if (!cache(options)) {
1461 // Parsing
1462 if (type === 'path') {
1463 var path = resolve(str);
1464 delete require.cache[path];
1465 Code = require(path);
1466 } else {
1467 Code = requireReactString(str);
1468 }
1469 Factory = cache(options, react.createFactory(Code));
1470
1471 } else {
1472 Factory = cache(options);
1473 }
1474
1475 parsed = new Factory(options);
1476 content = (isNonStatic) ? ReactDOM.renderToString(parsed) : ReactDOM.renderToStaticMarkup(parsed);
1477
1478 if (base) {
1479 baseStr = readCache[str] || fs.readFileSync(resolve(base), 'utf8');
1480
1481 if (enableCache) {
1482 readCache[str] = baseStr;
1483 }
1484
1485 options.content = content;
1486 content = reactBaseTmpl(baseStr, options);
1487 }
1488
1489 cb(null, content);
1490
1491 } catch (err) {
1492 cb(err);
1493 }
1494 });
1495 };
1496}
1497
1498/**
1499 * React JS Support
1500 */
1501exports.react = reactRenderer('path');
1502
1503/**
1504 * React JS string support.
1505 */
1506exports.react.render = reactRenderer('string');
1507
1508/**
1509 * ARC-templates support.
1510 */
1511
1512exports['arc-templates'] = fromStringRenderer('arc-templates');
1513
1514/**
1515 * ARC-templates string support.
1516 */
1517
1518exports['arc-templates'].render = function(str, options, cb) {
1519 var readFileWithOptions = Promise.promisify(read);
1520 var consolidateFileSystem = {};
1521 consolidateFileSystem.readFile = function(path) {
1522 return readFileWithOptions(path, options);
1523 };
1524
1525 return promisify(cb, function(cb) {
1526 try {
1527 var engine = requires['arc-templates'];
1528 if (!engine) {
1529 var Engine = require('arc-templates/dist/es5');
1530 engine = requires['arc-templates'] = new Engine({ filesystem: consolidateFileSystem });
1531 }
1532
1533 var compiler = cache(options) || cache(options, engine.compileString(str, options.filename));
1534 compiler.then(function(func) { return func(options); })
1535 .then(function(result) { cb(null, result.content); })
1536 .catch(cb);
1537 } catch (err) {
1538 cb(err);
1539 }
1540 });
1541};
1542
1543/**
1544 * Vash support
1545 */
1546exports.vash = fromStringRenderer('vash');
1547
1548/**
1549 * Vash string support
1550 */
1551exports.vash.render = function(str, options, cb) {
1552 return promisify(cb, function(cb) {
1553 var engine = requires.vash || (requires.vash = require('vash'));
1554
1555 try {
1556 // helper system : https://github.com/kirbysayshi/vash#helper-system
1557 if (options.helpers) {
1558 for (var key in options.helpers) {
1559 if (!options.helpers.hasOwnProperty(key) || typeof options.helpers[key] !== 'function') {
1560 continue;
1561 }
1562 engine.helpers[key] = options.helpers[key];
1563 }
1564 }
1565
1566 var tmpl = cache(options) || cache(options, engine.compile(str, options));
1567 tmpl(options, function sealLayout(err, ctx) {
1568 if (err) cb(err);
1569 ctx.finishLayout();
1570 cb(null, ctx.toString().replace(/\n$/, ''));
1571 });
1572 } catch (err) {
1573 cb(err);
1574 }
1575 });
1576};
1577
1578/**
1579 * Slm support.
1580 */
1581
1582exports.slm = fromStringRenderer('slm');
1583
1584/**
1585 * Slm string support.
1586 */
1587
1588exports.slm.render = function(str, options, cb) {
1589 return promisify(cb, function(cb) {
1590 var engine = requires.slm || (requires.slm = require('slm'));
1591
1592 try {
1593 var tmpl = cache(options) || cache(options, engine.compile(str, options));
1594 cb(null, tmpl(options));
1595 } catch (err) {
1596 cb(err);
1597 }
1598 });
1599};
1600
1601/**
1602 * Marko support.
1603 */
1604
1605exports.marko = function(path, options, cb) {
1606 return promisify(cb, function(cb) {
1607 var engine = requires.marko || (requires.marko = require('marko'));
1608 options.writeToDisk = !!options.cache;
1609
1610 try {
1611 var tmpl = cache(options) || cache(options, engine.load(path, options));
1612 tmpl.renderToString(options, cb);
1613 } catch (err) {
1614 cb(err);
1615 }
1616 });
1617};
1618
1619/**
1620 * Marko string support.
1621 */
1622
1623exports.marko.render = function(str, options, cb) {
1624 return promisify(cb, function(cb) {
1625 var engine = requires.marko || (requires.marko = require('marko'));
1626 options.writeToDisk = !!options.cache;
1627 options.filename = options.filename || 'string.marko';
1628
1629 try {
1630 var tmpl = cache(options) || cache(options, engine.load(options.filename, str, options));
1631 tmpl.renderToString(options, cb);
1632 } catch (err) {
1633 cb(err);
1634 }
1635 });
1636};
1637
1638/**
1639 * Teacup support.
1640 */
1641exports.teacup = function(path, options, cb) {
1642 return promisify(cb, function(cb) {
1643 var engine = requires.teacup || (requires.teacup = require('teacup/lib/express'));
1644 require.extensions['.teacup'] = require.extensions['.coffee'];
1645 if (path[0] !== '/') {
1646 path = join(process.cwd(), path);
1647 }
1648 if (!options.cache) {
1649 var callback = cb;
1650 cb = function() {
1651 delete require.cache[path];
1652 callback.apply(this, arguments);
1653 };
1654 }
1655 engine.renderFile(path, options, cb);
1656 });
1657};
1658
1659/**
1660 * Teacup string support.
1661 */
1662exports.teacup.render = function(str, options, cb) {
1663 var coffee = require('coffee-script');
1664 var vm = require('vm');
1665 var sandbox = {
1666 module: {exports: {}},
1667 require: require
1668 };
1669 return promisify(cb, function(cb) {
1670 vm.runInNewContext(coffee.compile(str), sandbox);
1671 var tmpl = sandbox.module.exports;
1672 cb(null, tmpl(options));
1673 });
1674};
1675
1676/**
1677 * expose the instance of the engine
1678 */
1679exports.requires = requires;