Source: renderer.js

"use strict";

/**
 * @module renderer
 */

const path = require("path");
const {File} = require("./types");
const {isFunction} = require("./utils");

/**
 * @description File renderer.
 */
class Renderer {
  /**
   * @param {Logger} logger
   * @param {String[]} [skipRenderList] File that won't be rendered.
   * @return {Renderer}
   */
  constructor(logger, skipRenderList = []) {
    this.logger = logger;
    this._ = {};
    this.skipRenderList = skipRenderList;
  }

  /**
   * @callback renderCallback
   * @param {File} input
   * @return {File}
   */
  /**
   * @description Register a renderer function.
   * @param {String} srcExt Source file's extend name starts with `.`.
   * @param {String} [docExt=null] Doc file's extend name starts with `.`.
   * @param {renderCallback} fn
   */
  register(srcExt, docExt, fn) {
    if (isFunction(docExt) && fn == null) {
      // This renderer does not change extname.
      fn = docExt;
      docExt = srcExt;
    } else if (!isFunction(fn)) {
      throw new TypeError("fn must be a Function");
    }
    if (this._[srcExt] == null) {
      this._[srcExt] = {};
    }
    // Use another object for docExt,
    // so renderer for the same src and doc in plugin
    // can replace internal renderer.
    this._[srcExt][docExt] = {srcExt, docExt, fn};
  }

  /**
   * @description Render file with renderer function.
   * @param {File} input
   * @return {File} Rendered file.
   */
  async render(input) {
    const srcExt = path.extname(input["srcPath"]);
    const results = [];
    // It might be OK to allow renderer for binary file, but why?
    // It has no `raw`, `text` and why you want to handle binary with a SSG?
    // So better to just skip binary here.
    if (
      this._[srcExt] != null && !input["binary"] &&
      !this.skipRenderList.includes(input["srcPath"])
    ) {
      for (const handler of Object.values(this._[srcExt])) {
        const output = new File(input);
        const docExt = handler["docExt"];
        if (docExt !== srcExt) {
          const dirname = path.dirname(output["srcPath"]);
          const basename = path.basename(output["srcPath"], srcExt);
          output["docPath"] = path.join(dirname, `${basename}${docExt}`);
          this.logger.debug(`Hikaru is rendering \`${
            this.logger.cyan(output["srcPath"])
          }\` to \`${
            this.logger.cyan(output["docPath"])
          }\`...`);
        } else {
          output["docPath"] = output["srcPath"];
          this.logger.debug(`Hikaru is rendering \`${
            this.logger.cyan(output["srcPath"])
          }\`...`);
        }
        results.push(await handler["fn"](output));
      }
    } else {
      const output = new File(input);
      output["docPath"] = output["srcPath"];
      // this.logger.debug(`Hikaru is rendering \`${
      //   this.logger.cyan(output['srcPath'])
      // }\`...`)
      // For binary files, this is useless,
      // but we have to keep this line for other text files without renderer.
      output["content"] = output["text"];
      results.push(output);
    }
    return results;
  }
}

module.exports = Renderer;