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.

142 lines
2.7 KiB

7 years ago
  1. /**
  2. * Module dependencies.
  3. */
  4. var debug = require('debug')('express:view');
  5. var path = require('path');
  6. var fs = require('fs');
  7. var utils = require('./utils');
  8. /**
  9. * Module variables.
  10. * @private
  11. */
  12. var dirname = path.dirname;
  13. var basename = path.basename;
  14. var extname = path.extname;
  15. var join = path.join;
  16. var resolve = path.resolve;
  17. /**
  18. * Expose `View`.
  19. */
  20. module.exports = View;
  21. /**
  22. * Initialize a new `View` with the given `name`.
  23. *
  24. * Options:
  25. *
  26. * - `defaultEngine` the default template engine name
  27. * - `engines` template engine require() cache
  28. * - `root` root path for view lookup
  29. *
  30. * @param {String} name
  31. * @param {Object} options
  32. * @api private
  33. */
  34. function View(name, options) {
  35. options = options || {};
  36. this.name = name;
  37. this.root = options.root;
  38. var engines = options.engines;
  39. this.defaultEngine = options.defaultEngine;
  40. var ext = this.ext = extname(name);
  41. if (!ext && !this.defaultEngine) throw new Error('No default engine was specified and no extension was provided.');
  42. if (!ext) name += (ext = this.ext = ('.' != this.defaultEngine[0] ? '.' : '') + this.defaultEngine);
  43. this.engine = engines[ext] || (engines[ext] = require(ext.slice(1)).__express);
  44. this.path = this.lookup(name);
  45. }
  46. /**
  47. * Lookup view by the given `name`
  48. *
  49. * @param {String} name
  50. * @return {String}
  51. * @api private
  52. */
  53. View.prototype.lookup = function lookup(name) {
  54. var path;
  55. var roots = [].concat(this.root);
  56. debug('lookup "%s"', name);
  57. for (var i = 0; i < roots.length && !path; i++) {
  58. var root = roots[i];
  59. // resolve the path
  60. var loc = resolve(root, name);
  61. var dir = dirname(loc);
  62. var file = basename(loc);
  63. // resolve the file
  64. path = this.resolve(dir, file);
  65. }
  66. return path;
  67. };
  68. /**
  69. * Render with the given `options` and callback `fn(err, str)`.
  70. *
  71. * @param {Object} options
  72. * @param {Function} fn
  73. * @api private
  74. */
  75. View.prototype.render = function render(options, fn) {
  76. debug('render "%s"', this.path);
  77. this.engine(this.path, options, fn);
  78. };
  79. /**
  80. * Resolve the file within the given directory.
  81. *
  82. * @param {string} dir
  83. * @param {string} file
  84. * @private
  85. */
  86. View.prototype.resolve = function resolve(dir, file) {
  87. var ext = this.ext;
  88. var path;
  89. var stat;
  90. // <path>.<ext>
  91. path = join(dir, file);
  92. stat = tryStat(path);
  93. if (stat && stat.isFile()) {
  94. return path;
  95. }
  96. // <path>/index.<ext>
  97. path = join(dir, basename(file, ext), 'index' + ext);
  98. stat = tryStat(path);
  99. if (stat && stat.isFile()) {
  100. return path;
  101. }
  102. };
  103. /**
  104. * Return a stat, maybe.
  105. *
  106. * @param {string} path
  107. * @return {fs.Stats}
  108. * @private
  109. */
  110. function tryStat(path) {
  111. debug('stat "%s"', path);
  112. try {
  113. return fs.statSync(path);
  114. } catch (e) {
  115. return undefined;
  116. }
  117. }