Source: index.js

'use strict';

/**
 * FUSE
 * FunctionSelect: Return a function based on a condition.
 * @version 0.10.0
 * @author Muthu Kumar (MKRhere)
 */

/**
 * @callback predicate
 * @param {any} value The selected Fuse value
 * @returns {boolean} The Boolean result of the test
 */

/**
 * @callback consequent
 * @param {any} value The selected Fuse value
 * @param {...any} args An arbitrary array of arguments
 * @returns {any}
 */

/**
 * Creates a FuseItem instance with a value and optional resolve function.
 * FuseIterable constructor uses it internally, and Fuse extends FuseItem.
 * Not exposed.
 * @class FuseItem
 */
class FuseItem {
	/**
	 * @param {any} value The input value
	 * @param {function} resolve Optional resolve function
	 * @constructs FuseItem
	 */
	constructor(value, resolve) {
		this.value = value;
		if (resolve) {
			this.resolve = (...args) => resolve(this.value, ...args);
			this.resolved = true;
		}
	}

	/**
	 * Fallback resolve prototype. Returns null when called.
	 * Used in case a resolve is never found.
	 * @returns {null} null
	 * @memberof FuseItem
	 */
	resolve() {
		return null;
	}
}

/**
 * Creates a FuseIterable instance from an array or iterable.
 * @class FuseIterable
 * @param {iterable} values An iterable expression as the switch
 * @param {Array<function>} tests Array of { test, consequent } objects
 * @param {function} tests[].predicate Test function
 * @param {function} tests[].consequent Consequent function
 * @constructs FuseIterable
 */
class FuseIterable {
	constructor(values, conditionals) {
		this.values = Array.from(values).map(value =>
			value instanceof FuseItem
				? value
				: new FuseItem(value));
		this.conditionals = conditionals || [];
	}

	/**
	 * Accepts a test and consequent function each and returns a new
	 * FuseIterable instance.
	 * FuseIterable.prototype.for works a little differently than
	 * Fuse.prototype.for, by lazy accumulating the tests and
	 * resolving all the values when .resolve() is called.
	 * @param {callback} predicate A test callback function
	 * @param {callback} consequent Consequent callback function
	 * @returns {FuseIterable} An instance of FuseIterable
	 * @memberof FuseIterable
	 */
	on(predicate, consequent) {
		return new FuseIterable(this.values, [
			...this.conditionals,
			{ predicate, consequent }
		]);
	}

	/**
	 * Accepts a list of tuples as arguments and returns a new
	 * FuseIterable instance. An alternative to chaining multiple
	 * FuseIterable.prototype.on methods.
	 * @memberOf FuseIterable
	 * @param {...Array.<function>} tuples -
	 * Array of [ predicate, consequent ] pairs
	 * @returns {FuseIterable} An instance of FuseIterable
	 */
	onField(...tuples) {
		const conditionals = tuples.map(conditional =>
			({
				predicate: conditional[0],
				consequent: conditional[1]
			}));
		return new FuseIterable(this.values, conditionals);
	}

	/**
	 * Accepts parameters during resolve time and passes them along with
	 * each value into the winning consequent function.
	 * @param {...any} args Any number of arguments
	 * @returns {(any|null)} Resolved value or null if it was unresolved
	 */
	resolve(...args) {
		return this.values.map(item => {
			const resolver = this.conditionals.find(conditional =>
				conditional.predicate(item.value)
					? conditional.consequent
					: null
			);
			return resolver
				? resolver.consequent(item.value, ...args)
				: null;
		});
	}
}

/**
 * Creates a new Fuse instance.
 * @class Fuse
 * @extends {FuseItem}
 */
class Fuse extends FuseItem {

	/**
	 * Accepts a test and consequent function each and returns a new
	 * Fuse or FuseIterable instance.
	 * @param {callback} predicate A test callback function
	 * @param {callback} consequent Consequent callback function
	 * @returns {Fuse} Returns a new Fuse instance
	 * @memberof Fuse
	 */
	on(predicate, consequent) {

		/* If a resolve exists, just pass on the instance
			until .resolve() is called */
		if (this.resolved) return this;

		if (predicate(this.value)) return new Fuse(this.value, consequent);

		/* If the test doesn't pass, just pass the Fuse
			instance along the chain until a test passes,
			or .resolve() is called */
		return this;
	}

	/**
	 * Accepts a consequent function which automatically becomes resolve.
	 * Does not return a Fuse instance as .on after .else would be useless.
	 * @param {callback} consequent Consequent callback function
	 * @returns {Fuse} Returns new FuseItem instance
	 */
	else(consequent) {
		return new FuseItem(this.value, consequent);
	}

	/**
	 * Accepts a value instead of a test function, and checks for strict
	 * equality with this.value.
	 * @param {any} value Any value to check against this.value
	 * @param {function} consequent Consequent callback function
	 * @returns {Fuse} An instance of Fuse
	 */
	is(value, consequent) {
		return this.on(() => value === this.value, consequent);
	}


	/**
	 * Accepts a value instead of a test function, and checks for strict
	 * inequality with this.value.
	 * @param {any} value Any value to check against this.value
	 * @param {function} consequent Consequent callback function
	 * @returns {Fuse} An instance of Fuse
	 */
	not(value, consequent) {
		return this.on(() => value !== this.value, consequent);
	}
}

module.exports = Fuse;
module.exports.FuseIterable = FuseIterable;