render.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. 'use strict';
  2. const { extname } = require('path');
  3. const Promise = require('bluebird');
  4. const { readFile, readFileSync } = require('hexo-fs');
  5. const getExtname = str => {
  6. if (typeof str !== 'string') return '';
  7. const ext = extname(str);
  8. return ext.startsWith('.') ? ext.slice(1) : ext;
  9. };
  10. const toString = (result, options) => {
  11. if (!Object.prototype.hasOwnProperty.call(options, 'toString') || typeof result === 'string') return result;
  12. if (typeof options.toString === 'function') {
  13. return options.toString(result);
  14. } else if (typeof result === 'object') {
  15. return JSON.stringify(result);
  16. } else if (result.toString) {
  17. return result.toString();
  18. }
  19. return result;
  20. };
  21. class Render {
  22. constructor(ctx) {
  23. this.context = ctx;
  24. this.renderer = ctx.extend.renderer;
  25. }
  26. isRenderable(path) {
  27. return this.renderer.isRenderable(path);
  28. }
  29. isRenderableSync(path) {
  30. return this.renderer.isRenderableSync(path);
  31. }
  32. getOutput(path) {
  33. return this.renderer.getOutput(path);
  34. }
  35. getRenderer(ext, sync) {
  36. return this.renderer.get(ext, sync);
  37. }
  38. getRendererSync(ext) {
  39. return this.getRenderer(ext, true);
  40. }
  41. render(data, options, callback) {
  42. if (!callback && typeof options === 'function') {
  43. callback = options;
  44. options = {};
  45. }
  46. const ctx = this.context;
  47. let ext = '';
  48. return new Promise((resolve, reject) => {
  49. if (!data) return reject(new TypeError('No input file or string!'));
  50. if (data.text != null) return resolve(data.text);
  51. if (!data.path) return reject(new TypeError('No input file or string!'));
  52. readFile(data.path).then(resolve, reject);
  53. }).then(text => {
  54. data.text = text;
  55. ext = data.engine || getExtname(data.path);
  56. if (!ext || !this.isRenderable(ext)) return text;
  57. const renderer = this.getRenderer(ext);
  58. return Reflect.apply(renderer, ctx, [data, options]);
  59. }).then(result => {
  60. result = toString(result, data);
  61. if (data.onRenderEnd) {
  62. return data.onRenderEnd(result);
  63. }
  64. return result;
  65. }).then(result => {
  66. const output = this.getOutput(ext) || ext;
  67. return ctx.execFilter(`after_render:${output}`, result, {
  68. context: ctx,
  69. args: [data]
  70. });
  71. }).asCallback(callback);
  72. }
  73. renderSync(data, options = {}) {
  74. if (!data) throw new TypeError('No input file or string!');
  75. const ctx = this.context;
  76. if (data.text == null) {
  77. if (!data.path) throw new TypeError('No input file or string!');
  78. data.text = readFileSync(data.path);
  79. }
  80. if (data.text == null) throw new TypeError('No input file or string!');
  81. const ext = data.engine || getExtname(data.path);
  82. let result;
  83. if (ext && this.isRenderableSync(ext)) {
  84. const renderer = this.getRendererSync(ext);
  85. result = Reflect.apply(renderer, ctx, [data, options]);
  86. } else {
  87. result = data.text;
  88. }
  89. const output = this.getOutput(ext) || ext;
  90. result = toString(result, data);
  91. if (data.onRenderEnd) {
  92. result = data.onRenderEnd(result);
  93. }
  94. return ctx.execFilterSync(`after_render:${output}`, result, {
  95. context: ctx,
  96. args: [data]
  97. });
  98. }
  99. }
  100. module.exports = Render;