ae86.js

"use strict";
import async from "async";
import bag from "bagofcli";
import cpr from "cpr";
import dateformat from "dateformat";
import Engine from "./engine.js";
import fs from "fs";
import p from "path";
import { fileURLToPath } from "url";
import watch from "@cnakazawa/watch";
import wrench from "wrench";

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

/**
 * class AE86
 */
class AE86 {
  constructor(opts) {
    opts = opts || {};
    this.outDir = opts.outDir || "out";
  }

  /**
   * Create example AE86 project files in current directory.
   *
   * @param {Function} cb: standard cb(err, result) callback
   */
  init(cb) {
    cpr.cpr(p.join(DIRNAME, "../examples"), ".", cb);
  }

  /**
   * Generate website based on the templates and params.
   *
   * @param {Function} cb: standard cb(err, result) callback
   */
  generate(cb) {
    const self = this;

    function _static(cb) {
      // copy static files as-is
      cpr.cpr("static", self.outDir, cb);
    }

    function _pages(cb) {
      function _params(cb) {
        function prepParams(result) {
          const params = result.params;
          // add website info
          params.sitemap = params.sitemap || {};
          params.__genId = dateformat("yyyymmddHHMMss");
          cb(null, params);
        }

        // initialise userland params
        const paramsFile = p.join(process.cwd(), "params.js");
        if (fs.existsSync(paramsFile)) {
          import(paramsFile)
            .then((result) => prepParams(result))
            .catch((err) => cb(err));
        } else {
          cb(null, null);
        }
      }

      const engine = new Engine(),
        tasks = {};

      ["partials", "layouts", "pages"].forEach((dir) => {
        tasks[dir] = function (cb) {
          engine.compile(dir, cb);
        };
      });

      async.parallel(tasks, (err, results) => {
        function prepParams(err, result) {
          if (err) {
            cb(err);
          } else {
            engine.merge(self.outDir, results, result || {}, cb);
          }
        }
        _params(prepParams);
      });
    }

    async.parallel([_static, _pages], cb);
  }

  /**
   * Watch for any file changes in AE86 project files.
   * A change means the project website will automatically be regenerated.
   */
  watch() {
    const self = this;

    function _listener() {
      // no callback because the process shouldn't exit
      self.generate();
    }

    function _watch(file) {
      watch.watchTree(file, function (f, curr, prev) {
        if (typeof f === "object" && prev === null && curr === null) {
          bag.logStepItemSuccess("Watching for file changes at %s...", file);
        } else if (prev === null) {
          bag.logStepItemSuccess("%s was created", file);
          _listener();
        } else if (curr.nlink === 0) {
          bag.logStepItemSuccess("%s was deleted", file);
          self.clean();
          _listener();
        } else {
          bag.logStepItemSuccess("%s was modified", file);
          _listener();
        }
      });
    }

    ["static", "partials", "layouts", "pages", "params.js"].forEach(_watch);
  }

  /**
   * Remove the generated website.
   *
   * @param {Function} cb: standard cb(err, result) callback
   */
  clean(cb) {
    wrench.rmdirRecursive(this.outDir, cb);
  }
}

export { AE86 as default };