engine.js

"use strict";
import _ from "lodash";
import async from "async";
import bag from "bagofcli";
import * as cheerio from "cheerio";
import { mkdirp } from "mkdirp";
import f from "file";
import fs from "fs";
import functions from "./functions.js";
import jazz from "jazz";
import p from "path";
import { fileURLToPath } from "url";
import util from "util";

const DIRNAME = p.dirname(fileURLToPath(import.meta.url));

/**
 * class Engine
 */
class Engine {
  /**
   * @param {String} opts: optional
   * - ext: template file extension, files without this extension will be ignored.
   */
  constructor(opts) {
    opts = opts || {};
    this.ext = opts.ext || "html";
    this.version =
      opts.version ||
      JSON.parse(fs.readFileSync(p.join(DIRNAME, "../package.json"))).version;
  }

  /**
   * Compile all template files (identified by file extension) in a directory.
   * This can be done in parallel because compiling a template does not depend on other templates.
   *
   * @param {String} dir: directory containing the templates
   * @param {Function} cb: standard cb(err, result) callback
   */
  compile(dir, cb) {
    const tasks = {},
      self = this;

    function _task(base, file) {
      const baseFile = p.join(base, file);
      const baseUrlPath = baseFile.replace(/\\/g, "/");
      tasks[baseUrlPath.substr(baseUrlPath.indexOf("/") + 1)] = function (cb) {
        fs.readFile(baseFile, "utf8", function (err, result) {
          cb(err, jazz.compile(result));
        });
      };
    }

    f.walkSync(dir, function (base, dirs, files) {
      files.forEach(function (file) {
        if (file.match(new RegExp("\\." + self.ext))) {
          _task(base, file);
        }
      });
    });

    async.parallel(tasks, cb);
  }

  /**
   * Compile all template files (identified by file extension) in a directory.
   * This can be done in parallel because compiling a template does not depend on other templates.
   *
   * @param {String} dir: output directory where website files will be written to
   * @param {Object} templates: compiled jazz templates
   * @param {Object} params: template parameters and functions
   * @param {Function} cb: standard cb(err, result) callback
   */
  merge(dir, templates, params, cb) {
    const self = this;

    // merge a set of params to a set of templates
    function _process(templates, params, cb) {
      const _templates = _.extend(templates, {}), // process template copies, not the originals
        tasks = {};

      _.keys(_templates).forEach(function (key) {
        tasks[key] = function (cb) {
          _templates[key].process(params, function (data) {
            cb(null, data);
          });
        };
      });

      async.parallel(tasks, function (err, results) {
        cb(results);
      });
    }

    const tasks = {};

    _.keys(templates.pages).forEach(function (page) {
      tasks[page] = function (cb) {
        let pageParams = _.extend(
          _.extend({}, params),
          functions(page, templates, params),
        );
        let pageContent;

        function _mergePartials(cb) {
          _process(templates.partials, pageParams, (result) => {
            pageParams.partials = result;
            pageParams = _.extend(
              pageParams,
              functions(page, templates, pageParams),
            );
            _process(templates.partials, pageParams, (result) => {
              pageParams.partials = result;
              cb();
            });
          });
        }

        function _mergePage(cb) {
          _process(
            { currpage: templates.pages[page] },
            pageParams,
            (result) => {
              pageParams.content = result.currpage;
              cb();
            },
          );
        }

        function _mergeLayout(cb) {
          const layout =
            pageParams.sitemap &&
            pageParams.sitemap[page] &&
            pageParams.sitemap[page].layout
              ? pageParams.sitemap[page].layout
              : "default.html";
          _process(
            { currlayout: templates.layouts[layout] },
            pageParams,
            (result) => {
              const $ = cheerio.load(result.currlayout);
              $("head").append(
                util.format(
                  '<meta name="generator" content="AE86 %s" />',
                  self.version,
                ),
              );
              pageContent = $.html();
              cb();
            },
          );
        }

        function _writePage(cb) {
          mkdirp.sync(p.join(dir, page).replace(/(\/[^\/]+$|\\[^\\]+$)/, ""));
          fs.writeFile(p.join(dir, page), pageContent, "utf8", function (err) {
            if (!err) {
              bag.logStepItemSuccess(
                util.format("creating %s", p.join(dir, page)),
              );
            }
            cb(err);
          });
        }

        async.series(
          [_mergePartials, _mergePage, _mergeLayout, _writePage],
          cb,
        );
      };
    });

    async.parallel(tasks, function (err, results) {
      cb(err, _.keys(results));
    });
  }
}

export { Engine as default };