@argdown/node
Version:
Async Argdown application for node.js
285 lines • 11.3 kB
JavaScript
"use strict";
import defaultsDeep from "lodash.defaultsdeep";
import isFunction from "lodash.isfunction";
import isString from "lodash.isstring";
import isEmpty from "lodash.isempty";
import isObject from "lodash.isobject";
import cloneDeep from "lodash.clonedeep";
import { ArgdownApplication, ArgdownPluginError } from "@argdown/core";
import { isAsyncPlugin } from "./IAsyncArgdownPlugin";
import path from "path";
import chokidar from "chokidar";
import glob from "glob";
import { promisify } from "util";
import importFresh from "import-fresh";
import { readFile } from "fs";
const readFileAsync = promisify(readFile);
export class AsyncArgdownApplication extends ArgdownApplication {
constructor() {
super(...arguments);
this.load = async (request) => {
const processObj = request.processes && isString(request.process)
? request.processes[request.process]
: undefined;
let req = request;
if (processObj) {
req = defaultsDeep({}, processObj, req);
}
let inputGlob = req.inputPath || "./*.argdown";
const ignoreFiles = req.ignore || [
"**/_*",
"**/_*/**"
];
if (req.logger &&
isFunction(req.logger.log) &&
isFunction(req.logger.setLevel)) {
if (!this.defaultLogger) {
this.defaultLogger = this.logger;
}
this.logger = req.logger;
}
else if (this.defaultLogger) {
this.logger = this.defaultLogger;
}
if (!req.rootPath) {
req.rootPath = process.cwd();
}
if (req.logLevel) {
this.logger.setLevel(req.logLevel);
}
if (req.plugins) {
for (let pluginData of req.plugins) {
if (isObject(pluginData.plugin) && isString(pluginData.processor)) {
this.addPlugin(pluginData.plugin, pluginData.processor);
}
}
}
if (req.input && !req.inputPath) {
await this.runAsync(cloneDeep(request));
return;
}
const $ = this;
let absoluteInputGlob = path.resolve(req.rootPath, inputGlob);
const loadOptions = {};
if (ignoreFiles) {
loadOptions.ignore = ignoreFiles;
}
if (req.watch) {
const watcher = chokidar.watch(absoluteInputGlob, loadOptions);
const watcherRequest = cloneDeep(req);
watcherRequest.watch = false;
watcher
.on("add", path => {
this.logger.log("verbose", `File ${path} has been added.`);
watcherRequest.inputPath = path;
$.load(watcherRequest);
})
.on("change", path => {
this.logger.log("verbose", `File ${path} has been changed.`);
watcherRequest.inputPath = path;
$.load(watcherRequest);
})
.on("unlink", path => {
this.logger.log("verbose", `File ${path} has been removed.`);
});
}
else {
let files = await new Promise((resolve, reject) => {
glob(absoluteInputGlob, loadOptions, (er, files) => {
if (er) {
reject(er);
}
resolve(files);
});
});
const promises = [];
if (files.length == 0) {
throw new ArgdownPluginError("AsyncArgdownApplication.load", "no-files-found", `No Argdown files found at: '${absoluteInputGlob}'`);
}
for (let file of files) {
const requestForFile = cloneDeep(request);
requestForFile.inputPath = file;
promises.push(this.runAsync(requestForFile));
}
if (req.plugins) {
for (let pluginData of req.plugins) {
this.removePlugin(pluginData.plugin, pluginData.processor);
}
}
return await Promise.all(promises);
}
return;
};
this.loadConfig = async (filePath) => {
let config = {};
filePath = filePath || "argdown.config.json";
filePath = path.resolve(process.cwd(), filePath);
const extension = path.extname(filePath);
if (extension === ".json") {
try {
const buffer = await readFileAsync(filePath, "utf8");
config = JSON.parse(buffer);
}
catch (e) {
if (e instanceof Error) {
this.logger.log("verbose", "[AsyncArgdownApplication]: No config found: " + e.toString());
}
}
}
else if (extension === ".js") {
try {
let jsModuleExports = loadJSFile(filePath);
if (jsModuleExports.config) {
config = jsModuleExports.config;
}
else {
config = jsModuleExports;
}
}
catch (e) {
if (e instanceof Error) {
this.logger.log("verbose", "[AsyncArgdownApplication]: No config found: " + e.toString());
}
}
}
return config;
};
}
async runAsync(request, response) {
let processorsToRun = [];
this.logger.setLevel("error");
let resp = response || {};
let req = request;
if (req) {
if (req.logLevel) {
this.logger.setLevel(req.logLevel);
}
if (req.process) {
if (Array.isArray(req.process)) {
processorsToRun = req.process;
}
else if (isString(req.process) &&
req.processes &&
req.processes[req.process]) {
const processObj = req.processes[req.process];
req = defaultsDeep({}, processObj, req);
if (isString(req.process)) {
processorsToRun = this.defaultProcesses[req.process];
}
else if (req.process && req.process.constructor === Array) {
processorsToRun = req.process;
}
}
else if (isString(req.process)) {
processorsToRun = this.defaultProcesses[req.process];
}
}
}
if (isEmpty(processorsToRun)) {
this.logger.log("error", "[AsyncArgdownApplication]: No processors to run.");
return resp;
}
const exceptions = [];
resp.exceptions = exceptions;
for (let processorId of processorsToRun) {
let cancelProcessor = false;
let processor = this.processors[processorId];
if (!processor) {
this.logger.log("error", "[AsyncArgdownApplication]: Processor not found: " + processorId);
continue;
}
this.logger.log("verbose", "[AsyncArgdownApplication]: Running processor: " + processorId);
for (let plugin of processor.plugins) {
if (isFunction(plugin.prepare)) {
this.logger.log("verbose", "[AsyncArgdownApplication]: Preparing plugin: " + plugin.name);
try {
plugin.prepare(req, resp, this.logger);
}
catch (e) {
if (req.throwExceptions) {
throw e;
}
else if (e instanceof ArgdownPluginError) {
e.processor = processorId;
exceptions.push(e);
cancelProcessor = true;
this.logger.log("warning", `Processor ${processorId} canceled.`);
break;
}
}
}
}
if (cancelProcessor) {
break;
}
if (resp.ast && processor.walker) {
try {
processor.walker.walk(req, resp, this.logger);
}
catch (e) {
if (req.throwExceptions) {
throw e;
}
else if (e instanceof ArgdownPluginError) {
e.processor = processorId;
cancelProcessor = true;
exceptions.push(e);
this.logger.log("warning", `[ArgdownApplication]: Processor ${processorId} canceled.`);
break;
}
}
}
if (cancelProcessor) {
break;
}
for (let plugin of processor.plugins) {
this.logger.log("verbose", "[AsyncArgdownApplication]: Running plugin: " + plugin.name);
try {
if (isAsyncPlugin(plugin)) {
await plugin.runAsync(req, resp, this.logger);
}
else if (isFunction(plugin.run)) {
plugin.run(req, resp, this.logger);
}
}
catch (e) {
if (req.throwExceptions) {
throw e;
}
else if (e instanceof ArgdownPluginError) {
e.processor = processorId;
cancelProcessor = true;
this.logger.log("warning", `Processor ${processorId} canceled.`);
exceptions.push(e);
break;
}
}
}
if (cancelProcessor) {
break;
}
}
if (req.logExceptions === undefined || req.logExceptions) {
for (let exception of exceptions) {
let msg = exception.stack || exception.message;
if (exception instanceof ArgdownPluginError) {
msg = `[${exception.processor}/${exception.plugin}]: ${msg}`;
}
this.logger.log("error", msg);
}
}
return resp;
}
}
const loadJSFile = (filePath) => {
try {
return importFresh(filePath);
}
catch (e) {
if (e instanceof Error) {
e.message = `Cannot read file: ${filePath}\nError: ${e.message}`;
}
throw e;
}
};
//# sourceMappingURL=AsyncArgdownApplication.js.map