mcfly-io/gulp-mux

View on GitHub
lib/mux.js

Summary

Maintainability
A
3 hrs
Test Coverage
/**
 * @module gulp-mux
 */

'use strict';
var _ = require('lodash');
var randomstring = require('randomstring');
var runSequence = require('run-sequence');
var targets = require('./targets');

var changeObject = function(object, cb) {
    _.each(object, function(value, key) {
        if (_.isObject(value)) {
            changeObject(value, cb);
        } else {
            if (!_.isString(value)) {
                object[key] = value;
            } else {
                object[key] = cb(value);
            }
        }
    });
};

/**
 * Create a function pointer attached to the arguments provided
 * accept besides fn, an optional list of arguments following the signature of fn
 * @method createSingleFn
 * @param {Function} fn - The function the pointer should point to
 *
 * @returns {Function} - The pointer
 */
var createSingleFn = function(fn) {
    //var args = Array.prototype.slice.call(arguments, 0);
    //assert.ok(_.isFunction(fn), 'fn must be a function');
    if (!_.isFunction(fn)) {
        throw new Error('fn must be a function');
    }
    return Function.bind.apply(fn, _(arguments).flatten().value());
};

/**
 * Create an array of function pointers, attached one each to the arguments
 * provided in args, and array of single arguments or arrays for the function pointers.
 * @method createArrayFn
 * @param {Function} fn - The function the pointer should point to
 * @param {Array} args - The array of arguments-or-arrays to be bound to the pointers
 *
 * @returns {Function[]} - The array of function pointers
 */
var createArrayFn = function(fn, args) {
    var fns = [];

    if (!_.isFunction(fn)) {
        throw new Error('fn must be a function');
    }

    if (!args) {
        fns.push(createSingleFn(fn));
        return fns;
    }
    if (!_.isArray(args)) {
        throw new Error('args must be an array of arrays');
    }
    args.forEach(function(arg) {
        fns.push(createSingleFn(fn, arg));
    });
    return fns;
};

// jscs:disable
/**
 * Resolve the passed constants object with the target
 * @method resolveConstants
 * @param {Object} constants - The constants object
 * @param {Object} templateObj - The object of target names and file suffixes for a target to replace in the constants object
 * @returns {Object} - The resolved constants object
 */
var resolveConstants = function(constants, templateObj) {
    if (constants === null || constants === undefined) {
        return null;
    }

    templateObj = templateObj || {
        targetName: '',
        targetSuffix: '',
        mode: ''
    };
    // we clone the object to avoid touching the original
    var clone = _.cloneDeep(constants);
    changeObject(clone, function(value) {
        var compiled = _.template(value, {
            'interpolate': /{{([\s\S]+?)}}/g
        });
        return compiled(templateObj);
    });
    return clone;
};

var targetToSuffix = targets.targetToSuffix;

var targetToTemplateData = function(target, mode) {
    return {
        targetName: target,
        targetSuffix: targetToSuffix(target),
        mode: mode
    };
};
// jscs:enable

/**
 * Create gulp tasks from the provided function for the desired targets and the
 * mode as specified and using the provided constants template.
 * @method createTasks
 * @param {Object} gulp - The instance of gulp to use for the muxed tasks
 * @param {Function} fn - The function with the operations to be run based on the provided constants
 * @param {String} taskname - The name of the underlying task that fn performs
 * @param {String|String[]} targets - An array of target names or the single target for the created task(s)
 * @param {String} mode - the desired mode, either 'dev' or 'prod'
 * @param {Object} constants - The constants object
 *
 * @returns {String[]} - The array of created tasks
 */
var createTasks = function(gulp, fn, taskname, targets, mode, constants) {
    if (!fn) {
        throw new Error('The passed fn cannot be null or undefined');
    }
    var tasks = [];

    taskname = taskname || '';
    targets = [].concat(targets);
    targets.forEach(function(target, index) {
        var targetConstants = resolveConstants(constants, targetToTemplateData(target, mode));
        var fulltaskname = taskname.length === 0 ? randomstring.generate(5) : taskname + ':' + randomstring.generate(5);
        gulp.task(fulltaskname, createSingleFn(fn, targetConstants));
        tasks.push(fulltaskname);
    });
    return tasks;
};

/**
 * Runs all of the of gulp tasks provided in parallel
 * @method runTasks
 * @param {Object} gulp - The instance of gulp to use for the muxed tasks
 * @param {String[]} tasks - The tasks to run as an array of tasknames
 * @param {Function} cb - The callback to to run-sequence run after all tasks are complete.
 *
 * @returns {*} - Returns the returns of any of the subtasks, so chaining works.
 */
var runTasks = function(gulp, tasks, cb) {
    var runSeq = runSequence.use(gulp);

    return runSeq(tasks, function() {
        //tasks.forEach(function(task) {
        //    gulp.seq = _(gulp.seq).remove(task).value();
        //    delete gulp.tasks[task];
        //});
        if (cb) {
            cb();
        }
    });
};

var runTasksSequential = function(gulp, tasks, cb) {
    var runSeq = runSequence.use(gulp);
    if (cb) {
        tasks.push(cb);
    }
    return runSeq.apply(this, tasks);
};

/**
 * Composes the {@link createTask} and {@link runTask} functions
 * @method createAndRunTasks
 * @param {Object} gulp - The instance of gulp to use for the muxed tasks
 * @param {Function} fn - The function with the operations to be run based on the provided constants
 * @param {String} taskname - The name of the underlying task that fn performs
 * @param {String|String[]} targets - An array of target names or the single target for the created task(s)
 * @param {String} mode - the desired mode, either 'dev' or 'prod'
 * @param {Object} constants - The constants object
 * @param {Function} cb - The callback to to run-sequence run after all tasks are complete.
 *
 * @returns {*} - Returns the returns of any of the subtasks, so chaining works.
 */
var createAndRunTasks = function(gulp, fn, taskname, targets, mode, constants, cb) {
    var tasks = createTasks(gulp, fn, taskname, targets, mode, constants);
    return runTasks(gulp, tasks, cb);
};

var createAndRunTasksSequential = function(gulp, fn, taskname, targets, mode, constants, cb) {
    var tasks = createTasks(gulp, fn, taskname, targets, mode, constants);
    return runTasksSequential(gulp, tasks, cb);
};

/**
 * Sanitize a list of watchable folders removing './' from the path as it generates a bug that makes the gulp.watch miss changes on new or deleted files
 * @param  {Array|string} folders - An array of folders or a single folder
 *
 * @returns {Array|string} - Returns the adjusted list of folders or single folder.
 */
var sanitizeWatchFolders = function(folders) {
    var sanitizeFolder = function(folder) {
        if (folder.indexOf('./') === 0) {
            return folder.substring(2);
        } else {
            return folder;
        }
    };

    if (!folders) {
        return folders;
    }
    if (!_.isArray(folders)) {
        return sanitizeFolder(folders);
    }

    return _(folders)
        .map(sanitizeFolder)
        .value();
};

module.exports = {
    createSingleFn: createSingleFn,
    createArrayFn: createArrayFn,
    resolveConstants: resolveConstants,
    createTasks: createTasks,
    runTasks: runTasks,
    runTasksSequential: runTasksSequential,
    createAndRunTasks: createAndRunTasks,
    createAndRunTasksSequential: createAndRunTasksSequential,
    sanitizeWatchFolders: sanitizeWatchFolders
};