You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1679 lines
37 KiB

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