UNPKG

@argdown/node

Version:

Async Argdown application for node.js

119 lines (116 loc) 3.31 kB
import * as fs from "fs"; import { promisify } from "util"; const readFileAsync = promisify(fs.readFile); let path = require("path"); import defaultsDeep from "lodash.defaultsdeep"; import includes from "lodash.includes"; import { IAsyncArgdownPlugin, IAsyncRequestHandler } from "../IAsyncArgdownPlugin"; import { IArgdownRequest, IRequestHandler, ArgdownPluginError } from "@argdown/core"; export interface IIncludeSettings { regEx?: RegExp; } declare module "@argdown/core" { interface IArgdownRequest { /** * Settings for the [[IncludePlugin]] */ 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 => { request.include = request.include || {}; return request.include; }; prepare: IRequestHandler = request => { defaultsDeep(this.getSettings(request), this.defaults); }; runAsync: IAsyncRequestHandler = async request => { if (!request.input) { throw new ArgdownPluginError( this.name, "missing-input-request-field", "Missing input." ); } if (!request.inputPath) { throw new ArgdownPluginError( this.name, "missing-inputPath-request-field", "Missing input path." ); } const settings = this.getSettings(request); settings.regEx!.lastIndex = 0; request.input = await this.replaceIncludesAsync( request.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); try { strToInclude = await readFileAsync(absoluteFilePath, "utf8"); } catch (e) { throw new ArgdownPluginError( this.name, "file-not-found", `'${absoluteFilePath}' not found.` ); } 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; } }