All files / src/plugins IncludePlugin.ts

30% Statements 12/40
0% Branches 0/10
20% Functions 1/5
30% Lines 12/40

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 721x 1x   1x 1x 1x   1x                 1x 1x     1x       1x         1x     1x                                                                              
import * as fs from "fs";
import { promisify } from "util";
 
const readFileAsync = promisify(fs.readFile);
let path = require("path");
import * as _ from "lodash";
import { IAsyncArgdownPlugin, IAsyncRequestHandler } from "../IAsyncArgdownPlugin";
import { IArgdownRequest, IRequestHandler, ArgdownPluginError } from "@argdown/core";
import { IFileRequest } from "../IFileRequest";
 
export interface IIncludeSettings {
  regEx?: RegExp;
}
export interface IIncludeRequest {
  include?: IIncludeSettings;
}
export class IncludePlugin implements IAsyncArgdownPlugin {
  name = "IncludePlugin";
  defaults: IIncludeSettings;
  constructor(config?: IIncludeSettings) {
    this.defaults = _.defaultsDeep({}, config, {
      regEx: /@include\(([^\)]+)\)/g
    });
  }
  getSettings = (request: IArgdownRequest): IIncludeSettings => {
    const r = <IIncludeRequest>request;
    r.include = r.include || {};
    return r.include;
  };
  prepare: IRequestHandler = request => {
    _.defaultsDeep(this.getSettings(request), this.defaults);
  };
  runAsync: IAsyncRequestHandler = async request => {
    if (!request.input) {
      throw new ArgdownPluginError(this.name, "Missing input.");
    }
    if (!(<IFileRequest>request).inputPath) {
      throw new ArgdownPluginError(this.name, "Missing input path.");
    }
    const fileRequest = <IFileRequest>request;
    const settings = this.getSettings(request);
    settings.regEx!.lastIndex = 0;
    request.input = await this.replaceIncludesAsync(fileRequest.inputPath!, request.input!, settings.regEx!, []);
  };
  async replaceIncludesAsync(currentFilePath: string, str: string, regEx: RegExp, filesAlreadyIncluded: string[]) {
    let match = null;
    const directoryPath = path.dirname(currentFilePath);
    regEx.lastIndex = 0;
    while ((match = regEx.exec(str))) {
      const absoluteFilePath = path.resolve(directoryPath, match[1]);
      let strToInclude = "";
      if (_.includes(filesAlreadyIncluded, absoluteFilePath)) {
        strToInclude =
          "<!-- Include failed: File '" +
          absoluteFilePath +
          "' already included. To avoid infinite loops, each file can only be included once. -->";
      } else {
        filesAlreadyIncluded.push(absoluteFilePath);
        strToInclude = await readFileAsync(absoluteFilePath, "utf8");
        if (strToInclude == null) {
          strToInclude = "<!-- Include failed: File '" + absoluteFilePath + "' not found. -->\n";
        } else {
          strToInclude = await this.replaceIncludesAsync(absoluteFilePath, strToInclude, regEx, filesAlreadyIncluded);
        }
      }
      str = str.substr(0, match.index) + strToInclude + str.substr(match.index + match[0].length);
      regEx.lastIndex = match.index + strToInclude.length;
    }
    return str;
  }
}