@argdown/node
Version:
Async Argdown application for node.js
119 lines (116 loc) • 3.31 kB
text/typescript
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;
}
}