<commit_before>import fs from "fs";
import path from "path";

import most from "most";
import { builders as b } from "ast-types";
import estemplate from "estemplate";

import cache from "./cache";
import { programTmpl, bodyTmpl } from "../../ast/template";
import { toArray } from "../../util/stream";
import { fromObject } from "../../util/ast";


function getTemplate (templateName) {
  const absPath = path.join(__dirname, "templates", templateName + ".jst");
  const templateStr = fs.readFileSync(absPath, "utf-8");
  return estemplate.compile(templateStr, {attachComment: true});
}

const commonModuleTmpl = getTemplate("common-module");
const moduleSetTmpl = getTemplate("module-set");
const runtimeTmpl = getTemplate("runtime");
const loadModuleWhenReadyTmpl = getTemplate("load-module-when-ready");

const registerUrlsTmplPath = path.join(__dirname, "templates/register-urls.jst");
const registerUrlsTmplStr = fs.readFileSync(registerUrlsTmplPath, "utf-8");
const registerUrlsTmpl = bodyTmpl(registerUrlsTmplStr);

const iifeTmplPath = path.join(__dirname, "templates/iife.jst");
const iifeTmplStr = fs.readFileSync(iifeTmplPath, "utf-8");
const iifeTmpl = programTmpl(iifeTmplStr);

/**
 * Given an array of AST nodes and their dependencies (possibly originating from
 * a common JS module), construct an object expression that represents its run-time
 * equivalent.
 *
 * @param  {Array}  moduleBody  Array of ECMAscript AST nodes.
 * @param  {Array}  deps        Array of modules upon which origin module is
 *                              dependent.
 *
 * @return {ASTnode}            Object expression AST node.
 */
export function constructCommonModule (moduleBody, deps) {
  const depsHashes = deps.map(dependency => b.literal(dependency.hash));

  // Parser infers an expression-statement from the template, whereas the common module
  // construction should only return an object expression (not a statement).
  return commonModuleTmpl({ body: moduleBody, deps: depsHashes }).body[0].expression;
}

/**
 * Given an array of CJS modules (in the form of object expression AST nodes),
 * construct the AST of a file that would register those modules for consumpsion
 * by the Interlock run-time.
 *
 * @param  {Array}  cjsModules  Array of object expressions.
 * @param  {String} globalName     Global variable name of the Interlock run-time.
 *
 * @return {Array}                 Array of AST nodes to be emitted as JavaScript.
 */
export function constructModuleSet (cjsModules, globalName) {
  // Construct an object whose keys are the module hashes and values are AST expression
  // nodes - expected values are those returned from `constructCommonModule`.
  return toArray(cjsModules)
    .then(modules =>
      modules.map(([hash, ast]) => b.property("init", b.literal(hash), ast)))
    .then(modulesAst =>
      b.objectExpression(modulesAst))
    .then(modulesHashAst =>
      moduleSetTmpl({ globalName: b.literal(globalName), modulesHashAst }).body);
}

/**
 * Construct the guts of the Interlock run-time for inclusion in file output.
 *
 * @param  {String} globalName  Global variable name of Interlock run-time.
 * @param  {String} cacheMode   (DEPRECATED) cache mode enum.
 *
 * @return {Array}              Array of AST nodes.
 */
export function constructRuntime (globalName, cacheMode) {
  const cacheRuntime = cache[cacheMode];
  const cacheGet = cacheRuntime && cacheRuntime.get({}) || [];
  const cacheSave = cacheRuntime && cacheRuntime.save({}) || [];

  return runtimeTmpl({
    globalName: b.literal(globalName),
    cacheGet,
    cacheSave
  }).body;
}

/**
 * Construct a require statement invoking the given module.
 *
 * @param  {String} moduleHash  Hash of the module to be required.
 * @param  {String} globalName  Global variable name of Interlock run-time.
 *
 * @return {ASTnode}            Single AST node.
 */
export function constructRequireStmt (moduleHash, globalName) {
  return loadModuleWhenReadyTmpl({
    globalName: b.literal(globalName),
    moduleHash: b.literal(moduleHash)
  }).body[0];
}

/**
 * Transforms a map of module-hashes-to-URLs to the AST equivalent.
 *
 * @param  {Object} urls        Keys are module hashes, values are URL strings.
 * @param  {String} globalName  Global variable name of Interlock run-time.
 *
 * @return {ASTnode}            Single AST node.
 */
export function constructUrlRegistration (urls, globalName) {
  return registerUrlsTmpl({
    identifier: {
      "GLOBAL_NAME": b.literal(globalName),
      "URLS": fromObject(urls)
    }
  });
}

/**
 * The primary constructor.  Given a set of options, construct Program AST
 * to be emitted as JavaScript.
 *
 * @param  {Object}  opts                 Options.
 * @param  {Boolean} opts.includeRuntime  Indicates whether Interlock run-time should be emitted.
 * @param  {Object}  opts.urls            Optional. If included, map of module hashes to URLs
 *                                        will be emitted.
 * @param  {Array}   opts.modules         Optional. If included, the module objects will be
 *                                        transformed into output module AST and emitted.
 * @param  {String}  opts.initialRequire  Optional. If included, the statement in the AST output
 *                                        will be to require the specified module.
 *
 * @return {ASTnode}                      Single program AST node.
 */
export function constructBundle (opts) {
  const globalName = opts.globalName || "__interlock__";

  const runtimeP = opts.includeRuntime ?
    constructRuntime(globalName, opts.cacheMode) :
    null;

  const urlsP = opts.urls ?
    constructUrlRegistration(opts.urls, globalName) :
    null;

  const modules = most.from(opts.modules)
    .map(module => Promise.all([
      module.hash,
      constructCommonModule(module.ast.body, module.dependencies)
    ]))
    .flatMap(most.fromPromise);

  const moduleSetP = opts.modules ? constructModuleSet(modules, globalName) : null;

  const initialRequireP = opts.initialRequire ?
    constructRequireStmt(opts.initialRequire, globalName) :
    null;

  return Promise.all([runtimeP, urlsP, moduleSetP, initialRequireP])
    .then(([runtime, urls, moduleSet, initialRequire]) => iifeTmpl({
      body: { "BODY": [].concat(runtime, urls, moduleSet, initialRequire).filter(x => x) }
    }));
}

<commit_msg>Abstract async template creation into helper function.<commit_after>import fs from "fs";
import path from "path";

import most from "most";
import { builders as b } from "ast-types";
import estemplate from "estemplate";

import cache from "./cache";
import { programTmpl, bodyTmpl } from "../../ast/template";
import { toArray } from "../../util/stream";
import { fromObject } from "../../util/ast";


function getTemplate (templateName) {
  const absPath = path.join(__dirname, "templates", templateName + ".jst");
  const templateStr = fs.readFileSync(absPath, "utf-8");
  return estemplate.compile(templateStr, {attachComment: true});
}

function getAsyncTemplate (templateName, tmplType) {
  const absPath = path.join(__dirname, `templates/${templateName}.jst`);
  const templateStr = fs.readFileSync(absPath, "utf-8");
  return tmplType(templateStr);
}

const commonModuleTmpl = getTemplate("common-module");
const moduleSetTmpl = getTemplate("module-set");
const runtimeTmpl = getTemplate("runtime");
const loadModuleWhenReadyTmpl = getTemplate("load-module-when-ready");
const registerUrlsTmpl = getAsyncTemplate("register-urls", bodyTmpl);
const iifeTmpl = getAsyncTemplate("iife", programTmpl);

/**
 * Given an array of AST nodes and their dependencies (possibly originating from
 * a common JS module), construct an object expression that represents its run-time
 * equivalent.
 *
 * @param  {Array}  moduleBody  Array of ECMAscript AST nodes.
 * @param  {Array}  deps        Array of modules upon which origin module is
 *                              dependent.
 *
 * @return {ASTnode}            Object expression AST node.
 */
export function constructCommonModule (moduleBody, deps) {
  const depsHashes = deps.map(dependency => b.literal(dependency.hash));

  // Parser infers an expression-statement from the template, whereas the common module
  // construction should only return an object expression (not a statement).
  return commonModuleTmpl({ body: moduleBody, deps: depsHashes }).body[0].expression;
}

/**
 * Given an array of CJS modules (in the form of object expression AST nodes),
 * construct the AST of a file that would register those modules for consumpsion
 * by the Interlock run-time.
 *
 * @param  {Array}  cjsModules  Array of object expressions.
 * @param  {String} globalName     Global variable name of the Interlock run-time.
 *
 * @return {Array}                 Array of AST nodes to be emitted as JavaScript.
 */
export function constructModuleSet (cjsModules, globalName) {
  // Construct an object whose keys are the module hashes and values are AST expression
  // nodes - expected values are those returned from `constructCommonModule`.
  return toArray(cjsModules)
    .then(modules =>
      modules.map(([hash, ast]) => b.property("init", b.literal(hash), ast)))
    .then(modulesAst =>
      b.objectExpression(modulesAst))
    .then(modulesHashAst =>
      moduleSetTmpl({ globalName: b.literal(globalName), modulesHashAst }).body);
}

/**
 * Construct the guts of the Interlock run-time for inclusion in file output.
 *
 * @param  {String} globalName  Global variable name of Interlock run-time.
 * @param  {String} cacheMode   (DEPRECATED) cache mode enum.
 *
 * @return {Array}              Array of AST nodes.
 */
export function constructRuntime (globalName, cacheMode) {
  const cacheRuntime = cache[cacheMode];
  const cacheGet = cacheRuntime && cacheRuntime.get({}) || [];
  const cacheSave = cacheRuntime && cacheRuntime.save({}) || [];

  return runtimeTmpl({
    globalName: b.literal(globalName),
    cacheGet,
    cacheSave
  }).body;
}

/**
 * Construct a require statement invoking the given module.
 *
 * @param  {String} moduleHash  Hash of the module to be required.
 * @param  {String} globalName  Global variable name of Interlock run-time.
 *
 * @return {ASTnode}            Single AST node.
 */
export function constructRequireStmt (moduleHash, globalName) {
  return loadModuleWhenReadyTmpl({
    globalName: b.literal(globalName),
    moduleHash: b.literal(moduleHash)
  }).body[0];
}

/**
 * Transforms a map of module-hashes-to-URLs to the AST equivalent.
 *
 * @param  {Object} urls        Keys are module hashes, values are URL strings.
 * @param  {String} globalName  Global variable name of Interlock run-time.
 *
 * @return {ASTnode}            Single AST node.
 */
export function constructUrlRegistration (urls, globalName) {
  return registerUrlsTmpl({
    identifier: {
      "GLOBAL_NAME": b.literal(globalName),
      "URLS": fromObject(urls)
    }
  });
}

/**
 * The primary constructor.  Given a set of options, construct Program AST
 * to be emitted as JavaScript.
 *
 * @param  {Object}  opts                 Options.
 * @param  {Boolean} opts.includeRuntime  Indicates whether Interlock run-time should be emitted.
 * @param  {Object}  opts.urls            Optional. If included, map of module hashes to URLs
 *                                        will be emitted.
 * @param  {Array}   opts.modules         Optional. If included, the module objects will be
 *                                        transformed into output module AST and emitted.
 * @param  {String}  opts.initialRequire  Optional. If included, the statement in the AST output
 *                                        will be to require the specified module.
 *
 * @return {ASTnode}                      Single program AST node.
 */
export function constructBundle (opts) {
  const globalName = opts.globalName || "__interlock__";

  const runtimeP = opts.includeRuntime ?
    constructRuntime(globalName, opts.cacheMode) :
    null;

  const urlsP = opts.urls ?
    constructUrlRegistration(opts.urls, globalName) :
    null;

  const modules = most.from(opts.modules)
    .map(module => Promise.all([
      module.hash,
      constructCommonModule(module.ast.body, module.dependencies)
    ]))
    .flatMap(most.fromPromise);

  const moduleSetP = opts.modules ? constructModuleSet(modules, globalName) : null;

  const initialRequireP = opts.initialRequire ?
    constructRequireStmt(opts.initialRequire, globalName) :
    null;

  return Promise.all([runtimeP, urlsP, moduleSetP, initialRequireP])
    .then(([runtime, urls, moduleSet, initialRequire]) => iifeTmpl({
      body: { "BODY": [].concat(runtime, urls, moduleSet, initialRequire).filter(x => x) }
    }));
}


{'fraction_non_alphanumeric': 0.10808040993299173, 'fraction_numerical': 0.0007094994087504927, 'mean_word_length': 3.368457300275482, 'pattern_counts': {'":': 6, '<': 3, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4867518', 'n_tokens_mistral': 3599, 'n_tokens_neox': 3507, 'n_words': 1152}