lib/getReporters.js

const qs = require('node:querystring');
const path = require('node:path');
const include = require('./include.js');

/**
 * @module pete/lib/getReporters
 */
module.exports = getReporters;

/**
 * Cache of currently open reporters.
 * Every key is a name passed to load reporter (name may contain file path and optional querystring).
 * Every value is a report function created by specific reporter's factory function.
 *
 * @private
 */
const CACHE = new Map();

/**
 * Build and output data to all reporters.
 * If input data already contains a `.report`, just pass that through, without building new one.
 *
 * @callback report
 * @param {Error|null}                          err
 * @param {module:pete/lib/createReport~Report} data   to report
 */

/**
 * Load reporters and return a function that will pass every report to each of them.
 *
 * @alias module:pete/lib/getReporters
 * @param {string|Array<string>} names
 * @param {object}               [options]
 * @param {boolean}              [options.close]   set to close specified reporters
 * @return {module:pete/lib/getReporters~report|null}
 */
function getReporters (names, options) {
	if (!names) {
		names = Array.from(CACHE.keys());
	}
	else if (typeof names === 'string') {
		names = [names];
	}
	else if (!Array.isArray(names)) {
		return null;
	}

	var list = names.reduce((result, name) => {
		var reporter = loadReporter(name, options);
		if (reporter) {
			result.push(reporter);
		}
		return result;
	}, []);

	/**
	 * @type {module:pete/lib/getReporters~report}
	 */
	return function report (err, data) {
		for (var i = 0, max = list.length; i < max; i++) {
			list[i](err, data);
		}

		if (!err && !data && list.length) {
			// Closed reporters are not needed anymore
			list = [];
		}
	};
}

/**
 * @private
 * @param {string|Function} name
 * @param {object}          options
 * @return {Function|boolean}
 */
function loadReporter (name, options) {
	var filename;

	var cached = CACHE.get(name);
	if (cached) {
		if (options && options.close) {
			cached();
			CACHE.delete(name);
		}

		return cached;
	}

	if (typeof name === 'function') {
		CACHE.set(name, name);
		return name;
	}

	[filename, options] = name.split('?');

	if (options) {
		options = qs.parse(options);
	}

	var factory = include(`./reporters/${path.basename(filename)}`) || include(filename);

	if (!factory) {
		console.error(`Reporter not found: ${name}`);
		return false;
	}

	if (typeof factory !== 'function') {
		console.error(`Reporter "${name}" does not export a function and will be ignored`);
		return false;
	}

	var report = factory(options);
	if (typeof report !== 'function') {
		console.error(`Reporter "${name}" did not create reporting function and will be ignored`);
		return false;
	}

	CACHE.set(name, report);
	return report;
}