lib/runs/auto.js

const fnParams = require('../fn-params.js');

/**
 * @module pete/lib/runs/auto
 */
module.exports = createRunAuto;

/**
 * Map of supported function runners.
 * Every key is a "type name", every value is a factory function.
 *
 * @alias module:pete/lib/runs/auto.TYPES
 * @readonly
 * @constant
 * @enum {Function}
 */
const TYPES = module.exports.TYPES = {
	/* eslint-disable global-require */
	/**
	 * @type {module:pete/lib/runs/sync}
	 */
	sync     : require('./sync.js'),
	/**
	 * @type {module:pete/lib/runs/generator}
	 */
	generator: require('./generator.js'),
	/**
	 * @type {module:pete/lib/runs/callback}
	 */
	callback : require('./callback.js'),
	/**
	 * @type {module:pete/lib/runs/async}
	 */
	async    : require('./async.js'),
	/**
	 * @type {module:pete/lib/runs/fork}
	 */
	fork     : require('./fork.js'),
	/**
	 * @type {module:pete/lib/runs/skip}
	 */
	skip     : require('./skip.js'),
	/**
	 * @type {module:pete/lib/runs/echo}
	 */
	echo     : require('./echo.js')
	/* eslint-enable global-require */
};

/**
 * Try to find a best way to run target function.
 * If `fn` is a string, create and return a `fork` run.
 * If it is an `async` function, or it runs and return a Promise or an object with a `catch` method, create and return an `async` run.
 * If it can be parsed and last argument is named "cb", "callback", "done", or "next", create and return a `callback` run.
 * Otherwise create and return a `sync` run.
 *
 * @alias module:pete/lib/runs/auto
 * @param {Function} fn
 * @param {object}   options
 * @param {object}   state
 * @return {module:pete/lib/runner~run}
 */
function createRunAuto (fn, options, state) {
	if (typeof fn === 'string') {
		if (fn.endsWith('.json')) {
			return TYPES.echo(fn, options, state);
		}
		return TYPES.fork(fn, options, state);
	}

	const code = fn.toString();
	if (code.startsWith('async')) {
		return TYPES.async(fn, options, state);
	}
	else if (code.match(/^function[\s\n]*[*]/)) {
		return TYPES.generator(fn, options, state);
	}

	var params;
	try {
		params = fnParams(code);
	}
	catch (e) {
		// Unknown, so return function that just errors out.
		return function unknown (report) {
			report(e);
			report();
		};
	}

	if (!params || params.length < 1) {
		return tryRun(fn, options, state);
	}

	if (params.pop().match(/^cb|callback|done|next$/)) {
		return TYPES.callback(fn, options, state);
	}

	return TYPES.sync(fn, options, state);
}

/**
 * Run function.
 * If it returns an object with a `catch` function, create and return an `async` run.
 * If it throws an error, or returns anything else, create and return a `sync` run.
 *
 * @private
 * @param {Function} fn
 * @param {object}   options
 * @param {object}   state
 * @return {module:pete/lib/runner~run}
 */
function tryRun (fn, options, state) {
	var temp = null;
	try {
		temp = fn();
	}
	catch (e) { // eslint-disable-line no-unused-vars
		return TYPES.sync(fn, options, state);
	}

	if (temp && typeof temp.catch === 'function') {
		temp.catch(() => { /* Ignore */ });
		return TYPES.async(fn, options, state);
	}

	return TYPES.sync(fn, options, state);
}