Templates, Paths, and Cartesian Products

Was working on a CLI application at work that required a lot of ‘deep’ interpolation. Was pretty satisfied with this result.

// Use this...
const variables = {
  environments: ["production", "development", "testing"],
  regions: ["us-east-1", "sa-east-1"],
  stages: ["alpha", "beta", "rc", "live"],
  appName: "foo",
};

// ... to interpolate this...
const templateName = `config.{{stages}}.LOLZILLA.{{appName}}__{{environments}}--{{regions}}.json`;

// ... to yield this:
const expectedOutput = [
  "config.alpha.LOLZILLA.foo__production--us-east-1.json",
  "config.alpha.LOLZILLA.foo__development--us-east-1.json",
  "config.alpha.LOLZILLA.foo__testing--us-east-1.json",
  "config.alpha.LOLZILLA.foo__production--sa-east-1.json",
  "config.alpha.LOLZILLA.foo__development--sa-east-1.json",
  "config.alpha.LOLZILLA.foo__testing--sa-east-1.json",
  "config.beta.LOLZILLA.foo__production--us-east-1.json",
  "config.beta.LOLZILLA.foo__development--us-east-1.json",
  "config.beta.LOLZILLA.foo__testing--us-east-1.json",
  "config.beta.LOLZILLA.foo__production--sa-east-1.json",
  "config.beta.LOLZILLA.foo__development--sa-east-1.json",
  "config.beta.LOLZILLA.foo__testing--sa-east-1.json",
  "config.rc.LOLZILLA.foo__production--us-east-1.json",
  "config.rc.LOLZILLA.foo__development--us-east-1.json",
  "config.rc.LOLZILLA.foo__testing--us-east-1.json",
  "config.rc.LOLZILLA.foo__production--sa-east-1.json",
  "config.rc.LOLZILLA.foo__development--sa-east-1.json",
  "config.rc.LOLZILLA.foo__testing--sa-east-1.json",
  "config.live.LOLZILLA.foo__production--us-east-1.json",
  "config.live.LOLZILLA.foo__development--us-east-1.json",
  "config.live.LOLZILLA.foo__testing--us-east-1.json",
  "config.live.LOLZILLA.foo__production--sa-east-1.json",
  "config.live.LOLZILLA.foo__development--sa-east-1.json",
  "config.live.LOLZILLA.foo__testing--sa-east-1.json",
];

const createPaths = (variables, templateName) => {
  // This is the magic! Everything else is pretty trivial.
  const cartesianProduct = (...a) =>
    a.reduce((a, b) => a.flatMap((d) => b.map((e) => [d, e].flat())));

  const templateVars = [...templateName.matchAll(/\{\{(\w+)\}\}/g)].map(
    (m) => m[1]
  );

  let order = [];
  let varData = [];

  for (v of templateVars) {
    order.push(v);

    if (Array.isArray(variables[v])) {
      varData.push(variables[v]);
    } else if (typeof variables[v] === "string") {
      varData.push([variables[v]]);
    } else {
      throw new Error(
        `Invalid type in variables. Must be a string or list of strings.`
      );
    }
  }

  if (varData.length === 0) {
    return [templateName];
  }

  return cartesianProduct(...varData)
    .map((c) => {
      let _ = [];

      order.map((o, idx) => {
        _.push([o, varData.length === 1 ? c : c[idx]]);
      });

      return _;
    })
    .map((c) => {
      let _ = templateName;

      c.map(([variable, value]) => {
        _ = _.replace(`{{${variable}}}`, value);
      });

      return _;
    });
};

(() => {
  // Some other cases...s
  [
    templateName,
    `config.{{environments}}-{{regions}}-{{appName}}---{{environments}}.json`,
    `config.{{environments}}--{{environments}}.json`,
    `config.{{environments}}.json`,
    `config.json`,
  ].map((_) => {
    console.log(`Template: ${_}`);
    console.log(createPaths(variables, _));
    console.log();
  });
})();