<commit_before>'use strict';

/**
 * Holds connection info configuration.
 *
 * @typedef {Object} ConnectionInfo
 * @param {String} host - The redis host
 * @param {Number} port - The redis port
 */

//load external modules
var util = require('util');
var EventEmitterEnhancer = require('event-emitter-enhancer');
var debug = require('debuglog')('multiple-redis');
var redis = require('redis');
var redisCommands = require('redis-commands');
var asyncLib = require('async');

/**
 * Proxies requests to one or more redis clients.
 *
 * @author Sagie Gur-Ari
 * @class MultiRedisClient
 * @extends {EventEmitter}
 * @public
 * @param {Object} params - The client init params
 * @param {redis[]|redis} [params.clients] - The redis client/s (if not provided, the connection info must be provided instead)
 * @param {ConnectionInfo[]|ConnectionInfo} [params.connectionInfo] - The redis client/s connection info (if not provided, the redis clients must be provided)
 * @param {Object} [params.options] - Used when this client creates the redis clients (see redis module for more details)
 * @param {Number} [params.options.childCommandTimeout=10000] - The per client command timeout
 * @param {Boolean} [params.options.mergeDuplicateEndpoints=true] - True to merge duplicate endpoint configurations and prevent needless redis client connections
 */
function MultiRedisClient(params) {
    var self = this;

    //call super constructor
    EventEmitterEnhancer.EnhancedEventEmitter.call(self);

    var options = params.options || {};
    self.childCommandTimeout = options.childCommandTimeout || 10000;
    if (self.childCommandTimeout <= 0) {
        self.childCommandTimeout = 10000;
    }

    /*jshint camelcase: false*/
    //jscs:disable requireCamelCaseOrUpperCaseIdentifiers
    /*eslint-disable camelcase*/
    //disable offline queue to prevent old data in redis clients from being sent
    if (options.enable_offline_queue === undefined) {
        options.enable_offline_queue = false;
    }
    /*eslint-enable camelcase*/
    //jscs:enable requireCamelCaseOrUpperCaseIdentifiers
    /*jshint camelcase: true*/

    self.initClients(params, options);

    self.resetState();

    //proxy events
    self.setupEventProxy();

    self.setupStateChangeEvents();
}

//setup MultiRedisClient as an event emitter
util.inherits(MultiRedisClient, EventEmitterEnhancer.EnhancedEventEmitter);

/**
 * True when at least one internal redis client is connected.
 *
 * @member {Boolean}
 * @alias MultiRedisClient.connected
 * @memberof! MultiRedisClient
 * @public
 */
MultiRedisClient.prototype.connected = false;

/*jshint camelcase: false*/
//jscs:disable requireCamelCaseOrUpperCaseIdentifiers
/*eslint-disable camelcase*/
/**
 * After the ready probe completes, the results from the INFO command are saved in this attribute.
 *
 * @member {Object}
 * @alias MultiRedisClient.server_info
 * @memberof! MultiRedisClient
 * @public
 */
MultiRedisClient.prototype.server_info = null;
/*eslint-enable camelcase*/
//jscs:enable requireCamelCaseOrUpperCaseIdentifiers
/*jshint camelcase: true*/

/**
 * Internal function to init the multiple redis clients used by this multi client.
 *
 * @function
 * @memberof! MultiRedisClient
 * @private
 * @param {Object} params - The client init params
 * @param {Object} [options] - Used when this client creates the redis clients (see redis module for more details)
 */
MultiRedisClient.prototype.initClients = function (params, options) {
    var self = this;

    if (params.clients) {
        if (Array.isArray(params.clients)) {
            self.clients = params.clients;
        } else {
            self.clients = [params.clients];
        }
    } else {
        if (options.mergeDuplicateEndpoints === undefined) {
            options.mergeDuplicateEndpoints = true;
        }

        var connectionInfo = params.connectionInfo;
        if (!Array.isArray(connectionInfo)) {
            connectionInfo = [connectionInfo];
        } else if (options.mergeDuplicateEndpoints) {
            connectionInfo = self.getUniqueEndpoints(connectionInfo);
        }

        self.clients = [];
        connectionInfo.forEach(function createSingleClient(info) {
            self.clients.push(redis.createClient(info.port, info.host, options));
        });
    }
};

/**
 * Internal function to setup the events proxy from the underlining clients.
 *
 * @function
 * @memberof! MultiRedisClient
 * @private
 */
MultiRedisClient.prototype.setupEventProxy = function () {
    this.proxyEvents(this.clients, [
        'ready',
        'connect',
        'reconnecting',
        'error',
        'end',
        'warning',
        'message',
        'pmessage',
        'message_buffer',
        'pmessage_buffer',
        'subscribe',
        'psubscribe',
        'unsubscribe',
        'punsubscribe'
    ]);
};

/**
 * Internal function to listen to events which can change the client state.
 *
 * @function
 * @memberof! MultiRedisClient
 * @private
 */
MultiRedisClient.prototype.setupStateChangeEvents = function () {
    var self = this;

    self.onAny([
        'connect',
        'ready',
        'error',
        'end'
    ], function runResetState() {
        self.resetState();
    });
};

/**
 * Returns the connection info array without duplicates.
 *
 * @function
 * @memberof! MultiRedisClient
 * @private
 * @param {ConnectionInfo[]} connectionInfo - The redis client/s connection info
 * @returns {ConnectionInfo[]} The connection info array without duplicates
 */
MultiRedisClient.prototype.getUniqueEndpoints = function (connectionInfo) {
    var endpoints = [];

    var keys = {};
    var index;
    var key;
    for (index = 0; index < connectionInfo.length; index++) {
        key = connectionInfo[index].host + '-' + connectionInfo[index].port;
        if (!keys[key]) {
            keys[key] = true;

            endpoints.push(connectionInfo[index]);
        }
    }

    return endpoints;
};

/**
 * Resets the redis attributes based on the status of the internal redis clients.
 *
 * @function
 * @memberof! MultiRedisClient
 * @private
 */
MultiRedisClient.prototype.resetState = function () {
    this.connected = false;

    var index;
    for (index = 0; index < this.clients.length; index++) {
        if (this.clients[index].connected) {
            this.connected = true;

            /*jshint camelcase: false*/
            //jscs:disable requireCamelCaseOrUpperCaseIdentifiers
            /*eslint-disable camelcase*/
            this.server_info = this.clients[index].server_info;
            /*eslint-enable camelcase*/
            //jscs:enable requireCamelCaseOrUpperCaseIdentifiers
            /*jshint camelcase: true*/

            break;
        }
    }
};

/**
 * Returns true if not null and not undefined.
 *
 * @function
 * @memberof! MultiRedisClient
 * @alias MultiRedisClient.setupPrototype
 * @private
 * @param {Object} [value] - The value to validate
 * @returns {Boolean} True if not null and not undefined
 */
function isDefined(value) {
    return ((value !== undefined) && (value !== null));
}

/**
 * Creates and returns a handler function for the given command.
 *
 * @function
 * @memberof! MultiRedisClient
 * @alias MultiRedisClient.setupPrototype
 * @private
 * @param {String} command - The command to create a handler function for
 * @returns {function} The handler function
 */
function createCommandFunction(command) {
    var getCommand = ((command !== 'getset') && (command.indexOf('get') !== -1));
    var runInSequence = getCommand || (command.indexOf('setnx') !== -1);

    return function invokeCommand(args, callback) {
        /*eslint-disable no-invalid-this*/
        var self = this;
        /*eslint-enable no-invalid-this*/

        if ((!Array.isArray(args)) || (typeof callback !== 'function')) {
            args = Array.prototype.slice.call(arguments, 0);
            if (args.length && (typeof args[args.length - 1] === 'function')) {
                callback = args.pop();
            } else {
                callback = null;
            }
        }

        var redisOutput;
        var redisError;
        var onRedisFlowEnd = function onRedisFlowEnd() {
            if (callback) {
                if (isDefined(redisOutput)) {
                    return callback(null, redisOutput);
                }

                return callback(redisError);
            }
        };

        var actions = [];
        var done = false;
        self.clients.forEach(function runCommandForClient(client) {
            actions.push(function runRedisCommand(asyncCallback) {
                if (done) {
                    return asyncCallback();
                }

                var originalAsyncCallback = asyncCallback;
                var ignore = false;
                var timeoutID;

                /*eslint-disable func-name-matching*/
                asyncCallback = function onWrappedCallback(error, output) {
                    /*istanbul ignore else*/
                    if (timeoutID) {
                        clearTimeout(timeoutID);
                        timeoutID = null;
                    }

                    /*istanbul ignore else*/
                    if (!ignore) {
                        ignore = true;
                        originalAsyncCallback(error, output);
                        originalAsyncCallback = null;
                    }
                };
                /*eslint-enable func-name-matching*/

                timeoutID = setTimeout(function timeoutCommand() {
                    /*istanbul ignore else*/
                    if (!ignore) {
                        redisError = new Error('Timeout on running command.');

                        asyncCallback();
                    }
                }, self.childCommandTimeout);

                try {
                    /*jshint camelcase: false*/
                    //jscs:disable requireCamelCaseOrUpperCaseIdentifiers
                    /*eslint-disable camelcase*/
                    client.send_command(command, args, function onCommandEnd(error, output) {
                        if (error) {
                            debug('Error while redis command: %s, args: %s, ', command, args, error.stack);
                        }

                        /*istanbul ignore else*/
                        if (!ignore) {
                            /*istanbul ignore else*/
                            if (error) {
                                redisError = error;
                            } else if (isDefined(output)) {
                                if (getCommand) {
                                    done = true;
                                }

                                if (!isDefined(redisOutput)) {
                                    redisOutput = output;
                                }
                            }

                            asyncCallback(null, redisOutput);
                        }
                    });
                    /*eslint-enable camelcase*/
                    //jscs:enable requireCamelCaseOrUpperCaseIdentifiers
                    /*jshint camelcase: true*/
                } catch (unhandledError) {
                    /*istanbul ignore else*/
                    if (!ignore) {
                        redisError = unhandledError;
                        asyncCallback();
                    }
                }
            });
        });

        if (runInSequence) {
            asyncLib.series(actions, onRedisFlowEnd);
        } else {
            asyncLib.parallel(actions, onRedisFlowEnd);
        }
    };
}

/**
 * Adds all functions with proxy capabilities.
 *
 * @function
 * @memberof! MultiRedisClient
 * @alias MultiRedisClient.setupPrototype
 * @private
 */
function setupPrototype() {
    var commands = redisCommands.list;

    commands.forEach(function addCommandToPrototype(command) {
        MultiRedisClient.prototype[command] = createCommandFunction(command);

        MultiRedisClient.prototype[command.toUpperCase()] = MultiRedisClient.prototype[command];
    });
}

setupPrototype();

/**
 * Creates and returns a new MultiRedisClient instance.
 *
 * @function
 * @memberof! MultiRedisClient
 * @private
 * @returns {MultiRedisClient} The multiple redis client instance
 */
function create() {
    var args = Array.prototype.slice.call(arguments);

    var params = {};

    if (args.length > 2) {
        throw new Error('Wrong arguments count provided.');
    }

    if (args.length > 0) {
        if (args.length === 2) {
            params.options = args[1];
        }

        var arg = args[0];
        if (Array.isArray(arg)) {
            arg = arg[0];
        }

        if (arg.on && (typeof arg.on === 'function')) { //redis client extends event emitter
            params.clients = args[0];
        } else {
            params.connectionInfo = args[0];
        }
    } else {
        throw new Error('Redis clients/Connection info not provided.');
    }

    return new MultiRedisClient(params);
}

/*eslint-disable valid-jsdoc*/
module.exports = {
    /**
     * Creates and returns a new MultiRedisClient instance.
     *
     * @function
     * @memberof! MultiRedisClient
     * @public
     * @param {redis[]|redis} clients - The redis client/s
     * @param {Object} [options] - Various options
     * @param {Number} [options.childCommandTimeout=10000] - The per client command timeout
     * @returns {MultiRedisClient} The multiple redis client instance
     * @example
     * ```js
     * //create multiple redis clients
     * var redis = require('redis');
     * var client1 = redis.createClient(...);
     * var client2 = redis.createClient(...);
     *
     * //create the wrapper client
     * var MultipleRedis = require('multiple-redis');
     * var multiClient = MultipleRedis.createClient([client1, client2]);
     *
     * multiClient.once('ready', function onReady() {
     *   //run any command on the multi client instead of the original clients
     *   multiClient.set('string key', 'string val', callback);
     * });
     * ```
     *
     * @also
     *
     * @function
     * @memberof! MultiRedisClient
     * @public
     * @param {ConnectionInfo[]|ConnectionInfo} connectionInfo - The redis client/s connection info
     * @param {Object} [options] - Used when this client creates the redis clients (see redis module for more details)
     * @param {Number} [options.childCommandTimeout=10000] - The per client command timeout
     * @param {Boolean} [options.mergeDuplicateEndpoints=true] - True to merge duplicate endpoint configurations and prevent needless redis client connections
     * @returns {MultiRedisClient} The multiple redis client instance
     * @example
     * ```js
     * //create the wrapper client with connection info
     * var MultipleRedis = require('multiple-redis');
     * var multiClient = MultipleRedis.createClient([{
     *   host: 'host1',
     *   port: 6379
     * }, {
     *   host: 'host2',
     *   port: 6379
     * }], options);
     *
     * multiClient.once('ready', function onReady() {
     *   //run any command on the multi client instead of the original clients
     *   multiClient.set('string key', 'string val', callback);
     * });
     * ```
     */
    createClient: create
};
/*eslint-enable valid-jsdoc*/
<commit_msg>jsdocs update<commit_after>'use strict';

/**
 * Holds connection info configuration.
 *
 * @typedef {Object} ConnectionInfo
 * @param {String} host - The redis host
 * @param {Number} port - The redis port
 */

//load external modules
var util = require('util');
var EventEmitterEnhancer = require('event-emitter-enhancer');
var debug = require('debuglog')('multiple-redis');
var redis = require('redis');
var redisCommands = require('redis-commands');
var asyncLib = require('async');

/**
 * Proxies requests to one or more redis clients.
 *
 * @author Sagie Gur-Ari
 * @class MultiRedisClient
 * @extends {EventEmitter}
 * @public
 * @param {Object} params - The client init params
 * @param {redis[]|redis} [params.clients] - The redis client/s (if not provided, the connection info must be provided instead)
 * @param {ConnectionInfo[]|ConnectionInfo} [params.connectionInfo] - The redis client/s connection info (if not provided, the redis clients must be provided)
 * @param {Object} [params.options] - Used when this client creates the redis clients (see redis module for more details)
 * @param {Number} [params.options.childCommandTimeout=10000] - The per client command timeout
 * @param {Boolean} [params.options.mergeDuplicateEndpoints=true] - True to merge duplicate endpoint configurations and prevent needless redis client connections
 */
function MultiRedisClient(params) {
    var self = this;

    //call super constructor
    EventEmitterEnhancer.EnhancedEventEmitter.call(self);

    var options = params.options || {};
    self.childCommandTimeout = options.childCommandTimeout || 10000;
    if (self.childCommandTimeout <= 0) {
        self.childCommandTimeout = 10000;
    }

    /*jshint camelcase: false*/
    //jscs:disable requireCamelCaseOrUpperCaseIdentifiers
    /*eslint-disable camelcase*/
    //disable offline queue to prevent old data in redis clients from being sent
    if (options.enable_offline_queue === undefined) {
        options.enable_offline_queue = false;
    }
    /*eslint-enable camelcase*/
    //jscs:enable requireCamelCaseOrUpperCaseIdentifiers
    /*jshint camelcase: true*/

    self.initClients(params, options);

    self.resetState();

    //proxy events
    self.setupEventProxy();

    self.setupStateChangeEvents();
}

//setup MultiRedisClient as an event emitter
util.inherits(MultiRedisClient, EventEmitterEnhancer.EnhancedEventEmitter);

/**
 * True when at least one internal redis client is connected.
 *
 * @member {Boolean}
 * @alias MultiRedisClient.connected
 * @memberof! MultiRedisClient
 * @public
 */
MultiRedisClient.prototype.connected = false;

/*jshint camelcase: false*/
//jscs:disable requireCamelCaseOrUpperCaseIdentifiers
/*eslint-disable camelcase*/
/**
 * After the ready probe completes, the results from the INFO command are saved in this attribute.
 *
 * @member {Object}
 * @alias MultiRedisClient.server_info
 * @memberof! MultiRedisClient
 * @public
 */
MultiRedisClient.prototype.server_info = null;
/*eslint-enable camelcase*/
//jscs:enable requireCamelCaseOrUpperCaseIdentifiers
/*jshint camelcase: true*/

/**
 * Internal function to init the multiple redis clients used by this multi client.
 *
 * @function
 * @memberof! MultiRedisClient
 * @private
 * @param {Object} params - The client init params
 * @param {Object} [options] - Used when this client creates the redis clients (see redis module for more details)
 */
MultiRedisClient.prototype.initClients = function (params, options) {
    var self = this;

    if (params.clients) {
        if (Array.isArray(params.clients)) {
            self.clients = params.clients;
        } else {
            self.clients = [params.clients];
        }
    } else {
        if (options.mergeDuplicateEndpoints === undefined) {
            options.mergeDuplicateEndpoints = true;
        }

        var connectionInfo = params.connectionInfo;
        if (!Array.isArray(connectionInfo)) {
            connectionInfo = [connectionInfo];
        } else if (options.mergeDuplicateEndpoints) {
            connectionInfo = self.getUniqueEndpoints(connectionInfo);
        }

        self.clients = [];
        connectionInfo.forEach(function createSingleClient(info) {
            self.clients.push(redis.createClient(info.port, info.host, options));
        });
    }
};

/**
 * Internal function to setup the events proxy from the underlining clients.
 *
 * @function
 * @memberof! MultiRedisClient
 * @private
 */
MultiRedisClient.prototype.setupEventProxy = function () {
    this.proxyEvents(this.clients, [
        'ready',
        'connect',
        'reconnecting',
        'error',
        'end',
        'warning',
        'message',
        'pmessage',
        'message_buffer',
        'pmessage_buffer',
        'subscribe',
        'psubscribe',
        'unsubscribe',
        'punsubscribe'
    ]);
};

/**
 * Internal function to listen to events which can change the client state.
 *
 * @function
 * @memberof! MultiRedisClient
 * @private
 */
MultiRedisClient.prototype.setupStateChangeEvents = function () {
    var self = this;

    self.onAny([
        'connect',
        'ready',
        'error',
        'end'
    ], function runResetState() {
        self.resetState();
    });
};

/**
 * Returns the connection info array without duplicates.
 *
 * @function
 * @memberof! MultiRedisClient
 * @private
 * @param {ConnectionInfo[]} connectionInfo - The redis client/s connection info
 * @returns {ConnectionInfo[]} The connection info array without duplicates
 */
MultiRedisClient.prototype.getUniqueEndpoints = function (connectionInfo) {
    var endpoints = [];

    var keys = {};
    var index;
    var key;
    for (index = 0; index < connectionInfo.length; index++) {
        key = connectionInfo[index].host + '-' + connectionInfo[index].port;
        if (!keys[key]) {
            keys[key] = true;

            endpoints.push(connectionInfo[index]);
        }
    }

    return endpoints;
};

/**
 * Resets the redis attributes based on the status of the internal redis clients.
 *
 * @function
 * @memberof! MultiRedisClient
 * @private
 */
MultiRedisClient.prototype.resetState = function () {
    this.connected = false;

    var index;
    for (index = 0; index < this.clients.length; index++) {
        if (this.clients[index].connected) {
            this.connected = true;

            /*jshint camelcase: false*/
            //jscs:disable requireCamelCaseOrUpperCaseIdentifiers
            /*eslint-disable camelcase*/
            this.server_info = this.clients[index].server_info;
            /*eslint-enable camelcase*/
            //jscs:enable requireCamelCaseOrUpperCaseIdentifiers
            /*jshint camelcase: true*/

            break;
        }
    }
};

/**
 * Returns true if not null and not undefined.
 *
 * @function
 * @memberof! MultiRedisClient
 * @alias MultiRedisClient.isDefined
 * @private
 * @param {Object} [value] - The value to validate
 * @returns {Boolean} True if not null and not undefined
 */
function isDefined(value) {
    return ((value !== undefined) && (value !== null));
}

/**
 * Creates and returns a handler function for the given command.
 *
 * @function
 * @memberof! MultiRedisClient
 * @alias MultiRedisClient.setupPrototype
 * @private
 * @param {String} command - The command to create a handler function for
 * @returns {function} The handler function
 * @example
 * ```js
 * MultiRedisClient.prototype.set = createCommandFunction('set');
 * ```
 */
function createCommandFunction(command) {
    var getCommand = ((command !== 'getset') && (command.indexOf('get') !== -1));
    var runInSequence = getCommand || (command.indexOf('setnx') !== -1);

    return function invokeCommand(args, callback) {
        /*eslint-disable no-invalid-this*/
        var self = this;
        /*eslint-enable no-invalid-this*/

        if ((!Array.isArray(args)) || (typeof callback !== 'function')) {
            args = Array.prototype.slice.call(arguments, 0);
            if (args.length && (typeof args[args.length - 1] === 'function')) {
                callback = args.pop();
            } else {
                callback = null;
            }
        }

        var redisOutput;
        var redisError;
        var onRedisFlowEnd = function onRedisFlowEnd() {
            if (callback) {
                if (isDefined(redisOutput)) {
                    return callback(null, redisOutput);
                }

                return callback(redisError);
            }
        };

        var actions = [];
        var done = false;
        self.clients.forEach(function runCommandForClient(client) {
            actions.push(function runRedisCommand(asyncCallback) {
                if (done) {
                    return asyncCallback();
                }

                var originalAsyncCallback = asyncCallback;
                var ignore = false;
                var timeoutID;

                /*eslint-disable func-name-matching*/
                asyncCallback = function onWrappedCallback(error, output) {
                    /*istanbul ignore else*/
                    if (timeoutID) {
                        clearTimeout(timeoutID);
                        timeoutID = null;
                    }

                    /*istanbul ignore else*/
                    if (!ignore) {
                        ignore = true;
                        originalAsyncCallback(error, output);
                        originalAsyncCallback = null;
                    }
                };
                /*eslint-enable func-name-matching*/

                timeoutID = setTimeout(function timeoutCommand() {
                    /*istanbul ignore else*/
                    if (!ignore) {
                        redisError = new Error('Timeout on running command.');

                        asyncCallback();
                    }
                }, self.childCommandTimeout);

                try {
                    /*jshint camelcase: false*/
                    //jscs:disable requireCamelCaseOrUpperCaseIdentifiers
                    /*eslint-disable camelcase*/
                    client.send_command(command, args, function onCommandEnd(error, output) {
                        if (error) {
                            debug('Error while redis command: %s, args: %s, ', command, args, error.stack);
                        }

                        /*istanbul ignore else*/
                        if (!ignore) {
                            /*istanbul ignore else*/
                            if (error) {
                                redisError = error;
                            } else if (isDefined(output)) {
                                if (getCommand) {
                                    done = true;
                                }

                                if (!isDefined(redisOutput)) {
                                    redisOutput = output;
                                }
                            }

                            asyncCallback(null, redisOutput);
                        }
                    });
                    /*eslint-enable camelcase*/
                    //jscs:enable requireCamelCaseOrUpperCaseIdentifiers
                    /*jshint camelcase: true*/
                } catch (unhandledError) {
                    /*istanbul ignore else*/
                    if (!ignore) {
                        redisError = unhandledError;
                        asyncCallback();
                    }
                }
            });
        });

        if (runInSequence) {
            asyncLib.series(actions, onRedisFlowEnd);
        } else {
            asyncLib.parallel(actions, onRedisFlowEnd);
        }
    };
}

/**
 * Adds all functions with proxy capabilities.
 *
 * @function
 * @memberof! MultiRedisClient
 * @alias MultiRedisClient.setupPrototype
 * @private
 */
function setupPrototype() {
    var commands = redisCommands.list;

    commands.forEach(function addCommandToPrototype(command) {
        MultiRedisClient.prototype[command] = createCommandFunction(command);

        MultiRedisClient.prototype[command.toUpperCase()] = MultiRedisClient.prototype[command];
    });
}

setupPrototype();

/**
 * Creates and returns a new MultiRedisClient instance.
 *
 * @function
 * @memberof! MultiRedisClient
 * @private
 * @returns {MultiRedisClient} The multiple redis client instance
 */
function create() {
    var args = Array.prototype.slice.call(arguments);

    var params = {};

    if (args.length > 2) {
        throw new Error('Wrong arguments count provided.');
    }

    if (args.length > 0) {
        if (args.length === 2) {
            params.options = args[1];
        }

        var arg = args[0];
        if (Array.isArray(arg)) {
            arg = arg[0];
        }

        if (arg.on && (typeof arg.on === 'function')) { //redis client extends event emitter
            params.clients = args[0];
        } else {
            params.connectionInfo = args[0];
        }
    } else {
        throw new Error('Redis clients/Connection info not provided.');
    }

    return new MultiRedisClient(params);
}

/*eslint-disable valid-jsdoc*/
module.exports = {
    /**
     * Creates and returns a new MultiRedisClient instance.
     *
     * @function
     * @memberof! MultiRedisClient
     * @public
     * @param {redis[]|redis} clients - The redis client/s
     * @param {Object} [options] - Various options
     * @param {Number} [options.childCommandTimeout=10000] - The per client command timeout
     * @returns {MultiRedisClient} The multiple redis client instance
     * @example
     * ```js
     * //create multiple redis clients
     * var redis = require('redis');
     * var client1 = redis.createClient(...);
     * var client2 = redis.createClient(...);
     *
     * //create the wrapper client
     * var MultipleRedis = require('multiple-redis');
     * var multiClient = MultipleRedis.createClient([client1, client2]);
     *
     * multiClient.once('ready', function onReady() {
     *   //run any command on the multi client instead of the original clients
     *   multiClient.set('string key', 'string val', callback);
     * });
     * ```
     *
     * @also
     *
     * @function
     * @memberof! MultiRedisClient
     * @public
     * @param {ConnectionInfo[]|ConnectionInfo} connectionInfo - The redis client/s connection info
     * @param {Object} [options] - Used when this client creates the redis clients (see redis module for more details)
     * @param {Number} [options.childCommandTimeout=10000] - The per client command timeout
     * @param {Boolean} [options.mergeDuplicateEndpoints=true] - True to merge duplicate endpoint configurations and prevent needless redis client connections
     * @returns {MultiRedisClient} The multiple redis client instance
     * @example
     * ```js
     * //create the wrapper client with connection info
     * var MultipleRedis = require('multiple-redis');
     * var multiClient = MultipleRedis.createClient([{
     *   host: 'host1',
     *   port: 6379
     * }, {
     *   host: 'host2',
     *   port: 6379
     * }], options);
     *
     * multiClient.once('ready', function onReady() {
     *   //run any command on the multi client instead of the original clients
     *   multiClient.set('string key', 'string val', callback);
     * });
     * ```
     */
    createClient: create
};
/*eslint-enable valid-jsdoc*/
<|endoftext|>


<commit_before>var express = require('express');
var app = express();
var http = require('http');
var server = http.Server(app);
var session = require("express-session");
var Util = require('./util');
var bodyParser = require("body-parser");
var expressValidator = require('express-validator')
var fileUpload = require('express-fileupload');
var mongoose = require("mongoose");
var cookieParser = require("cookie-parser");
var Cookies = require("cookies");
var extend = require('util')._extend
var restClient = new (require("node-rest-client")).Client()
var unirest = require("unirest");
var crypto = require('crypto');
var fs = require('fs')
var morgan = require('morgan')
var rfs = require("rotating-file-stream");
var rateLimit = require("express-rate-limit");
var Optimus = require("optimus-js");
var puller = require("express-git-puller");
var path = require('path')
var colors = require("colors");
var config = require("./config");
var port = process.env.PORT || config.port || 3014;

console.log("\n" +
    "  ==== STARTING UP ==== " +
    "\n");

require("rootpath")();
require('console-stamp')(console, 'HH:MM:ss.l');

// require("./statusMonitor")(app, config);

app.use(function (req, res, next) {
    res.header('Access-Control-Allow-Origin', '*');
    if (req.method === 'OPTIONS') {
        res.header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
        res.header("Access-Control-Allow-Headers", "X-Requested-With, Accept, Content-Type, Origin");
        res.header("Access-Control-Request-Headers", "X-Requested-With, Accept, Content-Type, Origin");
        return res.sendStatus(200);
    } else {
        return next();
    }
});
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json({extended: true}));
app.use(expressValidator());
app.use(fileUpload());
app.use(function (req, res, next) {
    req.realAddress = req.header("x-real-ip") || req.realAddress;
    res.header("X-Mineskin-Server", config.server || "default");
    next();
});

app.use("/.well-known",express.static(".well-known"));

var swStats = require('swagger-stats');
app.use(swStats.getMiddleware( config.swagger));

// create a rotating write stream
var accessLogStream = rfs('access.log', {
    interval: '1d', // rotate daily
    path: path.join(__dirname, 'log'),
    compress: "gzip"
})

// setup the logger
app.use(morgan('combined', {stream: accessLogStream}))
morgan.token('remote-addr', function (req) {
    return req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || req.connection.remoteAddress;
});

var updatingApp = false;
config.puller.beforeRun = function(req, res){
    updatingApp = true;
};
config.puller.afterRun = function(req, res){
    updatingApp = false;
};
app.use(function (req, res, next) {
    if (updatingApp) {
        res.status(503).send({err: "app is updating"});
        return;
    }
    next();
});
// Git Puller
app.use(config.puller.endpoint, new puller(config.puller));

colors.setTheme({
    silly: 'rainbow',
    input: 'grey',
    prompt: 'grey',
    info: 'green',
    data: 'grey',
    help: 'cyan',
    warn: 'yellow',
    debug: 'cyan',
    error: 'red'
});

// Databse
require("./db/db")(mongoose, config);
var Skin = require("./db/schemas/skin").Skin;

// API methods
app.get("/", function (req, res) {
    res.json({msg: "Hi!"});
});

app.get("/encrypt/:text", function (req, res) {
    if (!config.crypto.reqToken || req.query.key !== config.crypto.reqToken) return;
    res.json({enc: Util.crypto.encrypt(req.params.text)});
});

app.get("/decrypt/:text", function (req, res) {
    if (!config.crypto.reqToken || req.query.key !== config.crypto.reqToken) return;
    res.json({dec: Util.crypto.decrypt(req.params.text)});
});

app.post("/testing/upload_tester_result", function (req, res) {
    if (!config.testing.testerToken || req.body.token !== config.testing.testerToken) return;
    if (!req.body.data) return;
    if (req.headers["user-agent"] !== "mineskin-tester") return;

    if (req.body.data.r === "success") {
        Util.increaseStat("mineskintester.success")
        res.sendStatus(202);

        if (req.body.data.m > 0) {
            Util.postDiscordMessage("🛑 mineskin-tester generated data with " + req.body.data.m + " image mismatches! ID: " + req.body.data.i);
        }

        if (req.body.data.i) {
            Skin.updateOne({id: req.body.data.i, server: req.body.data.s}, {testerRequest: true, testerMismatchCounter: req.body.data.m || 0});
        }
    } else if (req.body.data.r === "fail") {
        Util.increaseStat("mineskintester.fail")
        res.sendStatus(202);
    } else {
        res.sendStatus(400)
    }
});

var optimus = new Optimus(config.optimus.prime, config.optimus.inverse, config.optimus.random);
console.log("Optimus Test:", optimus.encode(Math.floor(Date.now() / 10)));

var limiter = rateLimit({
    windowMs: 2*60*1000, // 2 minutes,
    max:6,
    message:JSON.stringify({error:"Too many requests"}),
    keyGenerator:function (req) {
        return  req.headers['cf-connecting-ip'] || req.headers['x-forwarded-for'] || req.realAddress || req.connection.remoteAddress
    }
})

/// Routes
require("./routes/generate")(app, config, optimus, limiter);
require("./routes/get")(app);
require("./routes/render")(app);
require("./routes/util")(app);
require("./routes/admin")(app);
require("./routes/accountManager")(app, config);

function exitHandler(err) {
    if (err) {
        console.log("\n\n\n\n\n\n\n\n");
        console.log(err);
        console.log("\n\n\n");
    }
    process.exit();
}

server.listen(port, function () {
    console.log(' ==> listening on *:' + port + "\n");
});

process.on("exit", exitHandler);
process.on("SIGINT", exitHandler);
process.on("uncaughtException", exitHandler);
<commit_msg>try a different method, since updateOne didn't seem to do anything<commit_after>var express = require('express');
var app = express();
var http = require('http');
var server = http.Server(app);
var session = require("express-session");
var Util = require('./util');
var bodyParser = require("body-parser");
var expressValidator = require('express-validator')
var fileUpload = require('express-fileupload');
var mongoose = require("mongoose");
var cookieParser = require("cookie-parser");
var Cookies = require("cookies");
var extend = require('util')._extend
var restClient = new (require("node-rest-client")).Client()
var unirest = require("unirest");
var crypto = require('crypto');
var fs = require('fs')
var morgan = require('morgan')
var rfs = require("rotating-file-stream");
var rateLimit = require("express-rate-limit");
var Optimus = require("optimus-js");
var puller = require("express-git-puller");
var path = require('path')
var colors = require("colors");
var config = require("./config");
var port = process.env.PORT || config.port || 3014;

console.log("\n" +
    "  ==== STARTING UP ==== " +
    "\n");

require("rootpath")();
require('console-stamp')(console, 'HH:MM:ss.l');

// require("./statusMonitor")(app, config);

app.use(function (req, res, next) {
    res.header('Access-Control-Allow-Origin', '*');
    if (req.method === 'OPTIONS') {
        res.header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
        res.header("Access-Control-Allow-Headers", "X-Requested-With, Accept, Content-Type, Origin");
        res.header("Access-Control-Request-Headers", "X-Requested-With, Accept, Content-Type, Origin");
        return res.sendStatus(200);
    } else {
        return next();
    }
});
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json({extended: true}));
app.use(expressValidator());
app.use(fileUpload());
app.use(function (req, res, next) {
    req.realAddress = req.header("x-real-ip") || req.realAddress;
    res.header("X-Mineskin-Server", config.server || "default");
    next();
});

app.use("/.well-known",express.static(".well-known"));

var swStats = require('swagger-stats');
app.use(swStats.getMiddleware( config.swagger));

// create a rotating write stream
var accessLogStream = rfs('access.log', {
    interval: '1d', // rotate daily
    path: path.join(__dirname, 'log'),
    compress: "gzip"
})

// setup the logger
app.use(morgan('combined', {stream: accessLogStream}))
morgan.token('remote-addr', function (req) {
    return req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || req.connection.remoteAddress;
});

var updatingApp = false;
config.puller.beforeRun = function(req, res){
    updatingApp = true;
};
config.puller.afterRun = function(req, res){
    updatingApp = false;
};
app.use(function (req, res, next) {
    if (updatingApp) {
        res.status(503).send({err: "app is updating"});
        return;
    }
    next();
});
// Git Puller
app.use(config.puller.endpoint, new puller(config.puller));

colors.setTheme({
    silly: 'rainbow',
    input: 'grey',
    prompt: 'grey',
    info: 'green',
    data: 'grey',
    help: 'cyan',
    warn: 'yellow',
    debug: 'cyan',
    error: 'red'
});

// Databse
require("./db/db")(mongoose, config);
var Skin = require("./db/schemas/skin").Skin;

// API methods
app.get("/", function (req, res) {
    res.json({msg: "Hi!"});
});

app.get("/encrypt/:text", function (req, res) {
    if (!config.crypto.reqToken || req.query.key !== config.crypto.reqToken) return;
    res.json({enc: Util.crypto.encrypt(req.params.text)});
});

app.get("/decrypt/:text", function (req, res) {
    if (!config.crypto.reqToken || req.query.key !== config.crypto.reqToken) return;
    res.json({dec: Util.crypto.decrypt(req.params.text)});
});

app.post("/testing/upload_tester_result", function (req, res) {
    if (!config.testing.testerToken || req.body.token !== config.testing.testerToken) return;
    if (!req.body.data) return;
    if (req.headers["user-agent"] !== "mineskin-tester") return;

    if (req.body.data.r === "success") {
        Util.increaseStat("mineskintester.success")
        res.sendStatus(202);

        if (req.body.data.m > 0) {
            Util.postDiscordMessage("🛑 mineskin-tester generated data with " + req.body.data.m + " image mismatches! ID: " + req.body.data.i);
        }

        if (req.body.data.i) {
            Skin.findOneAndUpdate({id: req.body.data.i, server: req.body.data.s}, {testerRequest: true, testerMismatchCounter: req.body.data.m || 0});
        }
    } else if (req.body.data.r === "fail") {
        Util.increaseStat("mineskintester.fail")
        res.sendStatus(202);
    } else {
        res.sendStatus(400)
    }
});

var optimus = new Optimus(config.optimus.prime, config.optimus.inverse, config.optimus.random);
console.log("Optimus Test:", optimus.encode(Math.floor(Date.now() / 10)));

var limiter = rateLimit({
    windowMs: 2*60*1000, // 2 minutes,
    max:6,
    message:JSON.stringify({error:"Too many requests"}),
    keyGenerator:function (req) {
        return  req.headers['cf-connecting-ip'] || req.headers['x-forwarded-for'] || req.realAddress || req.connection.remoteAddress
    }
})

/// Routes
require("./routes/generate")(app, config, optimus, limiter);
require("./routes/get")(app);
require("./routes/render")(app);
require("./routes/util")(app);
require("./routes/admin")(app);
require("./routes/accountManager")(app, config);

function exitHandler(err) {
    if (err) {
        console.log("\n\n\n\n\n\n\n\n");
        console.log(err);
        console.log("\n\n\n");
    }
    process.exit();
}

server.listen(port, function () {
    console.log(' ==> listening on *:' + port + "\n");
});

process.on("exit", exitHandler);
process.on("SIGINT", exitHandler);
process.on("uncaughtException", exitHandler);
<|endoftext|>


<commit_before>/*
 * provider.js: Abstraction providing an interface into pluggable configuration storage.
 *
 * (C) 2011, Nodejitsu Inc.
 *
 */

var async = require('async'),
    common = require('./common');

//
// ### function Provider (options)
// #### @options {Object} Options for this instance.
// Constructor function for the Provider object responsible
// for exposing the pluggable storage features of `nconf`.
//
var Provider = exports.Provider = function (options) {
  //
  // Setup default options for working with `stores`,
  // `overrides`, `process.env` and `process.argv`.
  //
  options       = options || {};
  this.stores  = {};
  this.sources = [];
  this.init(options);
};

//
// Define wrapper functions for using basic stores
// in this instance
//
['argv', 'env', 'file'].forEach(function (type) {
  Provider.prototype[type] = function (options) {
    return this.add(type, options);
  };
});

//
// Define wrapper functions for using
// overrides and defaults
//
['defaults', 'overrides'].forEach(function (type) {
  Provider.prototype[type] = function (options) {
    options = options || {};
    if(!options.type) {
      options.type = 'literal';
    }
    return this.add(type, options);
  };
});

//
// ### function use (name, options)
// #### @type {string} Type of the nconf store to use.
// #### @options {Object} Options for the store instance.
// Adds (or replaces) a new store with the specified `name`
// and `options`. If `options.type` is not set, then `name`
// will be used instead:
//
//    provider.use('file');
//    provider.use('file', { type: 'file', filename: '/path/to/userconf' })
//
Provider.prototype.use = function (name, options) {
  options  = options      || {};
  var type = options.type || name;

  function sameOptions (store) {
    return Object.keys(options).every(function (key) {
      return options[key] === store[key];
    });
  }

  var store = this.stores[name],
      update = store && !sameOptions(store);

  if (!store || update) {
    if (update) {
      this.remove(name);
    }

    this.add(name, options);
  }

  return this;
};

//
// ### function add (name, options)
// #### @name {string} Name of the store to add to this instance
// #### @options {Object} Options for the store to create
// Adds a new store with the specified `name` and `options`. If `options.type`
// is not set, then `name` will be used instead:
//
//    provider.add('memory');
//    provider.add('userconf', { type: 'file', filename: '/path/to/userconf' })
//
Provider.prototype.add = function (name, options) {
  options  = options      || {};
  var type = options.type || name;

  if (!require('../nconf')[common.capitalize(type)]) {
    throw new Error('Cannot add store with unknown type: ' + type);
  }

  this.stores[name] = this.create(type, options);

  if (this.stores[name].loadSync) {
    this.stores[name].loadSync();
  }

  return this;
};

//
// ### function remove (name)
// #### @name {string} Name of the store to remove from this instance
// Removes a store with the specified `name` from this instance. Users
// are allowed to pass in a type argument (e.g. `memory`) as name if
// this was used in the call to `.add()`.
//
Provider.prototype.remove = function (name) {
  delete this.stores[name];
  return this;
};

//
// ### function create (type, options)
// #### @type {string} Type of the nconf store to use.
// #### @options {Object} Options for the store instance.
// Creates a store of the specified `type` using the
// specified `options`.
//
Provider.prototype.create = function (type, options) {
  return new (require('../nconf')[common.capitalize(type.toLowerCase())])(options);
};

//
// ### function init (options)
// #### @options {Object} Options to initialize this instance with.
// Initializes this instance with additional `stores` or `sources` in the
// `options` supplied.
//
Provider.prototype.init = function (options) {
  var self = this;

  //
  // Add any stores passed in through the options
  // to this instance.
  //
  if (options.type) {
    this.add(options.type, options);
  }
  else if (options.store) {
    this.add(options.store.name || options.store.type, options.store);
  }
  else if (options.stores) {
    Object.keys(options.stores).forEach(function (name) {
      var store = options.stores[name];
      self.add(store.name || name || store.type, store);
    });
  }

  //
  // Add any read-only sources to this instance
  //
  if (options.source) {
    this.sources.push(this.create(options.source.type || options.source.name, options.source));
  }
  else if (options.sources) {
    Object.keys(options.sources).forEach(function (name) {
      var source = options.sources[name];
      self.sources.push(self.create(source.type || source.name || name, source));
    });
  }
};

//
// ### function get (key, callback)
// #### @key {string} Key to retrieve for this instance.
// #### @callback {function} **Optional** Continuation to respond to when complete.
// Retrieves the value for the specified key (if any).
//
Provider.prototype.get = function (key, callback) {
  //
  // If there is no callback we can short-circuit into the default
  // logic for traversing stores.
  //
  if (!callback) {
    return this._execute('get', 1, key, callback);
  }

  //
  // Otherwise the asynchronous, hierarchical `get` is
  // slightly more complicated because we do not need to traverse
  // the entire set of stores, but up until there is a defined value.
  //
  var current = 0,
      names = Object.keys(this.stores),
      self = this,
      response;

  async.whilst(function () {
    return typeof response === 'undefined' && current < names.length;
  }, function (next) {
    var store = self.stores[names[current]];
    current++;

    if (store.get.length >= 2) {
      return store.get(key, function (err, value) {
        if (err) {
          return next(err);
        }

        response = value;
        next();
      });
    }

    response = store.get(key);
    next();
  }, function (err) {
    return err ? callback(err) : callback(null, response);
  });
};

//
// ### function set (key, value, callback)
// #### @key {string} Key to set in this instance
// #### @value {literal|Object} Value for the specified key
// #### @callback {function} **Optional** Continuation to respond to when complete.
// Sets the `value` for the specified `key` in this instance.
//
Provider.prototype.set = function (key, value, callback) {
  return this._execute('set', 2, key, value, callback);
};

//
// ### function reset (callback)
// #### @callback {function} **Optional** Continuation to respond to when complete.
// Clears all keys associated with this instance.
//
Provider.prototype.reset = function (callback) {
  return this._execute('reset', 0, callback);
};

//
// ### function clear (key, callback)
// #### @key {string} Key to remove from this instance
// #### @callback {function} **Optional** Continuation to respond to when complete.
// Removes the value for the specified `key` from this instance.
//
Provider.prototype.clear = function (key, callback) {
  return this._execute('clear', 1, key, callback);
};

//
// ### function merge ([key,] value [, callback])
// #### @key {string} Key to merge the value into
// #### @value {literal|Object} Value to merge into the key
// #### @callback {function} **Optional** Continuation to respond to when complete.
// Merges the properties in `value` into the existing object value at `key`.
//
// 1. If the existing value `key` is not an Object, it will be completely overwritten.
// 2. If `key` is not supplied, then the `value` will be merged into the root.
//
Provider.prototype.merge = function () {
  var self = this,
      args = Array.prototype.slice.call(arguments),
      callback = typeof args[args.length - 1] === 'function' && args.pop(),
      value = args.pop(),
      key = args.pop();

  function mergeProperty (prop, next) {
    return self._execute('merge', 2, prop, value[prop], next);
  }

  if (!key) {
    if (Array.isArray(value) || typeof value !== 'object') {
      return onError(new Error('Cannot merge non-Object into top-level.'), callback);
    }

    return async.forEach(Object.keys(value), mergeProperty, callback || function () { })
  }

  return this._execute('merge', 2, key, value, callback);
};

//
// ### function load (callback)
// #### @callback {function} Continuation to respond to when complete.
// Responds with an Object representing all keys associated in this instance.
//
Provider.prototype.load = function (callback) {
  var self = this;

  function getStores () {
    var stores = Object.keys(self.stores);
    stores.reverse();
    return stores.map(function (name) {
      return self.stores[name];
    });
  }

  function loadStoreSync(store) {
    if (!store.loadSync) {
      throw new Error('nconf store ' + store.type + ' has no loadSync() method');
    }

    return store.loadSync();
  }

  function loadStore(store, next) {
    if (!store.load && !store.loadSync) {
      return next(new Error('nconf store ' + store.type + ' has no load() method'));
    }

    return store.loadSync
      ? next(null, store.loadSync())
      : store.load(next);
  }

  function loadBatch (targets, done) {
    if (!done) {
      return common.merge(targets.map(loadStoreSync));
    }

    async.map(targets, loadStore, function (err, objs) {
      return err ? done(err) : done(null, common.merge(objs));
    });
  }

  function mergeSources (data) {
    //
    // If `data` was returned then merge it into
    // the system store.
    //
    if (data && typeof data === 'object') {
      self.use('sources', {
        type: 'literal',
        store: data
      });
    }
  }

  function loadSources () {
    //
    // If we don't have a callback and the current
    // store is capable of loading synchronously
    // then do so.
    //
    if (!callback) {
      mergeSources(loadBatch(self.sources));
      return loadBatch(getStores());
    }

    loadBatch(self.sources, function (err, data) {
      if (err) {
        return callback(err);
      }

      mergeSources(data);
      return loadBatch(getStores(), callback);
    });
  }

  return self.sources.length
    ? loadSources()
    : loadBatch(getStores(), callback);
};

//
// ### function save (value, callback)
// #### @value {Object} **Optional** Config object to set for this instance
// #### @callback {function} Continuation to respond to when complete.
// Removes any existing configuration settings that may exist in this
// instance and then adds all key-value pairs in `value`.
//
Provider.prototype.save = function (value, callback) {
  if (!callback && typeof value === 'function') {
    callback = value;
    value = null;
  }

  var self = this,
      names = Object.keys(this.stores);

  function saveStoreSync(name) {
    var store = self.stores[name];

    //
    // If the `store` doesn't have a `saveSync` method,
    // just ignore it and continue.
    //
    return store.saveSync
      ? store.saveSync()
      : null;
  }

  function saveStore(name, next) {
    var store = self.stores[name];

    //
    // If the `store` doesn't have a `save` or saveSync`
    // method(s), just ignore it and continue.
    //
    if (!store.save && !store.saveSync) {
      return next();
    }

    return store.saveSync
      ? next(null, store.saveSync())
      : store.save(next);
  }

  //
  // If we don't have a callback and the current
  // store is capable of saving synchronously
  // then do so.
  //
  if (!callback) {
    return common.merge(names.map(saveStoreSync));
  }

  async.map(names, saveStore, function (err, objs) {
    return err ? callback(err) : callback();
  });
};

//
// ### @private function _execute (action, syncLength, [arguments])
// #### @action {string} Action to execute on `this.store`.
// #### @syncLength {number} Function length of the sync version.
// #### @arguments {Array} Arguments array to apply to the action
// Executes the specified `action` on all stores for this instance, ensuring a callback supplied
// to a synchronous store function is still invoked.
//
Provider.prototype._execute = function (action, syncLength /* [arguments] */) {
  var args = Array.prototype.slice.call(arguments, 2),
      callback = typeof args[args.length - 1] === 'function' && args.pop(),
      destructive = ['set', 'clear', 'merge'].indexOf(action) !== -1,
      self = this,
      response;

  function runAction (name, next) {
    var store = self.stores[name];

    if (destructive && store.readOnly) {
      return next();
    }

    return store[action].length > syncLength
      ? store[action].apply(store, args.concat(next))
      : next(null, store[action].apply(store, args));
  }

  if (callback) {
    return async.forEach(Object.keys(this.stores), runAction, function (err) {
      return err ? callback(err) : callback();
    });
  }

  Object.keys(this.stores).forEach(function (name) {
    if (typeof response === 'undefined') {
      var store = self.stores[name];

      if (destructive && store.readOnly) {
        return;
      }

      response = store[action].apply(store, args);
    }
  });

  return response;
}

//
// Throw the `err` if a callback is not supplied
//
function onError(err, callback) {
  if (callback) {
    return callback(err);
  }

  throw err;
}<commit_msg>updated Provider.load to respect sources hierarchy<commit_after>/*
 * provider.js: Abstraction providing an interface into pluggable configuration storage.
 *
 * (C) 2011, Nodejitsu Inc.
 *
 */

var async = require('async'),
    common = require('./common');

//
// ### function Provider (options)
// #### @options {Object} Options for this instance.
// Constructor function for the Provider object responsible
// for exposing the pluggable storage features of `nconf`.
//
var Provider = exports.Provider = function (options) {
  //
  // Setup default options for working with `stores`,
  // `overrides`, `process.env` and `process.argv`.
  //
  options       = options || {};
  this.stores  = {};
  this.sources = [];
  this.init(options);
};

//
// Define wrapper functions for using basic stores
// in this instance
//
['argv', 'env', 'file'].forEach(function (type) {
  Provider.prototype[type] = function (options) {
    return this.add(type, options);
  };
});

//
// Define wrapper functions for using
// overrides and defaults
//
['defaults', 'overrides'].forEach(function (type) {
  Provider.prototype[type] = function (options) {
    options = options || {};
    if(!options.type) {
      options.type = 'literal';
    }
    return this.add(type, options);
  };
});

//
// ### function use (name, options)
// #### @type {string} Type of the nconf store to use.
// #### @options {Object} Options for the store instance.
// Adds (or replaces) a new store with the specified `name`
// and `options`. If `options.type` is not set, then `name`
// will be used instead:
//
//    provider.use('file');
//    provider.use('file', { type: 'file', filename: '/path/to/userconf' })
//
Provider.prototype.use = function (name, options) {
  options  = options      || {};
  var type = options.type || name;

  function sameOptions (store) {
    return Object.keys(options).every(function (key) {
      return options[key] === store[key];
    });
  }

  var store = this.stores[name],
      update = store && !sameOptions(store);

  if (!store || update) {
    if (update) {
      this.remove(name);
    }

    this.add(name, options);
  }

  return this;
};

//
// ### function add (name, options)
// #### @name {string} Name of the store to add to this instance
// #### @options {Object} Options for the store to create
// Adds a new store with the specified `name` and `options`. If `options.type`
// is not set, then `name` will be used instead:
//
//    provider.add('memory');
//    provider.add('userconf', { type: 'file', filename: '/path/to/userconf' })
//
Provider.prototype.add = function (name, options) {
  options  = options      || {};
  var type = options.type || name;

  if (!require('../nconf')[common.capitalize(type)]) {
    throw new Error('Cannot add store with unknown type: ' + type);
  }

  this.stores[name] = this.create(type, options);

  if (this.stores[name].loadSync) {
    this.stores[name].loadSync();
  }

  return this;
};

//
// ### function remove (name)
// #### @name {string} Name of the store to remove from this instance
// Removes a store with the specified `name` from this instance. Users
// are allowed to pass in a type argument (e.g. `memory`) as name if
// this was used in the call to `.add()`.
//
Provider.prototype.remove = function (name) {
  delete this.stores[name];
  return this;
};

//
// ### function create (type, options)
// #### @type {string} Type of the nconf store to use.
// #### @options {Object} Options for the store instance.
// Creates a store of the specified `type` using the
// specified `options`.
//
Provider.prototype.create = function (type, options) {
  return new (require('../nconf')[common.capitalize(type.toLowerCase())])(options);
};

//
// ### function init (options)
// #### @options {Object} Options to initialize this instance with.
// Initializes this instance with additional `stores` or `sources` in the
// `options` supplied.
//
Provider.prototype.init = function (options) {
  var self = this;

  //
  // Add any stores passed in through the options
  // to this instance.
  //
  if (options.type) {
    this.add(options.type, options);
  }
  else if (options.store) {
    this.add(options.store.name || options.store.type, options.store);
  }
  else if (options.stores) {
    Object.keys(options.stores).forEach(function (name) {
      var store = options.stores[name];
      self.add(store.name || name || store.type, store);
    });
  }

  //
  // Add any read-only sources to this instance
  //
  if (options.source) {
    this.sources.push(this.create(options.source.type || options.source.name, options.source));
  }
  else if (options.sources) {
    Object.keys(options.sources).forEach(function (name) {
      var source = options.sources[name];
      self.sources.push(self.create(source.type || source.name || name, source));
    });
  }
};

//
// ### function get (key, callback)
// #### @key {string} Key to retrieve for this instance.
// #### @callback {function} **Optional** Continuation to respond to when complete.
// Retrieves the value for the specified key (if any).
//
Provider.prototype.get = function (key, callback) {
  //
  // If there is no callback we can short-circuit into the default
  // logic for traversing stores.
  //
  if (!callback) {
    return this._execute('get', 1, key, callback);
  }

  //
  // Otherwise the asynchronous, hierarchical `get` is
  // slightly more complicated because we do not need to traverse
  // the entire set of stores, but up until there is a defined value.
  //
  var current = 0,
      names = Object.keys(this.stores),
      self = this,
      response;

  async.whilst(function () {
    return typeof response === 'undefined' && current < names.length;
  }, function (next) {
    var store = self.stores[names[current]];
    current++;

    if (store.get.length >= 2) {
      return store.get(key, function (err, value) {
        if (err) {
          return next(err);
        }

        response = value;
        next();
      });
    }

    response = store.get(key);
    next();
  }, function (err) {
    return err ? callback(err) : callback(null, response);
  });
};

//
// ### function set (key, value, callback)
// #### @key {string} Key to set in this instance
// #### @value {literal|Object} Value for the specified key
// #### @callback {function} **Optional** Continuation to respond to when complete.
// Sets the `value` for the specified `key` in this instance.
//
Provider.prototype.set = function (key, value, callback) {
  return this._execute('set', 2, key, value, callback);
};

//
// ### function reset (callback)
// #### @callback {function} **Optional** Continuation to respond to when complete.
// Clears all keys associated with this instance.
//
Provider.prototype.reset = function (callback) {
  return this._execute('reset', 0, callback);
};

//
// ### function clear (key, callback)
// #### @key {string} Key to remove from this instance
// #### @callback {function} **Optional** Continuation to respond to when complete.
// Removes the value for the specified `key` from this instance.
//
Provider.prototype.clear = function (key, callback) {
  return this._execute('clear', 1, key, callback);
};

//
// ### function merge ([key,] value [, callback])
// #### @key {string} Key to merge the value into
// #### @value {literal|Object} Value to merge into the key
// #### @callback {function} **Optional** Continuation to respond to when complete.
// Merges the properties in `value` into the existing object value at `key`.
//
// 1. If the existing value `key` is not an Object, it will be completely overwritten.
// 2. If `key` is not supplied, then the `value` will be merged into the root.
//
Provider.prototype.merge = function () {
  var self = this,
      args = Array.prototype.slice.call(arguments),
      callback = typeof args[args.length - 1] === 'function' && args.pop(),
      value = args.pop(),
      key = args.pop();

  function mergeProperty (prop, next) {
    return self._execute('merge', 2, prop, value[prop], next);
  }

  if (!key) {
    if (Array.isArray(value) || typeof value !== 'object') {
      return onError(new Error('Cannot merge non-Object into top-level.'), callback);
    }

    return async.forEach(Object.keys(value), mergeProperty, callback || function () { })
  }

  return this._execute('merge', 2, key, value, callback);
};

//
// ### function load (callback)
// #### @callback {function} Continuation to respond to when complete.
// Responds with an Object representing all keys associated in this instance.
//
Provider.prototype.load = function (callback) {
  var self = this;

  function getStores () {
    var stores = Object.keys(self.stores);
    stores.reverse();
    return stores.map(function (name) {
      return self.stores[name];
    });
  }

  function loadStoreSync(store) {
    if (!store.loadSync) {
      throw new Error('nconf store ' + store.type + ' has no loadSync() method');
    }

    return store.loadSync();
  }

  function loadStore(store, next) {
    if (!store.load && !store.loadSync) {
      return next(new Error('nconf store ' + store.type + ' has no load() method'));
    }

    return store.loadSync
      ? next(null, store.loadSync())
      : store.load(next);
  }

  function loadBatch (targets, done) {
    if (!done) {
      return common.merge(targets.map(loadStoreSync));
    }

    async.map(targets, loadStore, function (err, objs) {
      return err ? done(err) : done(null, common.merge(objs));
    });
  }

  function mergeSources (data) {
    //
    // If `data` was returned then merge it into
    // the system store.
    //
    if (data && typeof data === 'object') {
      self.use('sources', {
        type: 'literal',
        store: data
      });
    }
  }

  function loadSources () {
    var sourceHierarchy = self.sources.splice(0);
    sourceHierarchy.reverse();

    //
    // If we don't have a callback and the current
    // store is capable of loading synchronously
    // then do so.
    //
    if (!callback) {
      mergeSources(loadBatch(sourceHierarchy));
      return loadBatch(getStores());
    }

    loadBatch(sourceHierarchy, function (err, data) {
      if (err) {
        return callback(err);
      }

      mergeSources(data);
      return loadBatch(getStores(), callback);
    });
  }

  return self.sources.length
    ? loadSources()
    : loadBatch(getStores(), callback);
};

//
// ### function save (value, callback)
// #### @value {Object} **Optional** Config object to set for this instance
// #### @callback {function} Continuation to respond to when complete.
// Removes any existing configuration settings that may exist in this
// instance and then adds all key-value pairs in `value`.
//
Provider.prototype.save = function (value, callback) {
  if (!callback && typeof value === 'function') {
    callback = value;
    value = null;
  }

  var self = this,
      names = Object.keys(this.stores);

  function saveStoreSync(name) {
    var store = self.stores[name];

    //
    // If the `store` doesn't have a `saveSync` method,
    // just ignore it and continue.
    //
    return store.saveSync
      ? store.saveSync()
      : null;
  }

  function saveStore(name, next) {
    var store = self.stores[name];

    //
    // If the `store` doesn't have a `save` or saveSync`
    // method(s), just ignore it and continue.
    //
    if (!store.save && !store.saveSync) {
      return next();
    }

    return store.saveSync
      ? next(null, store.saveSync())
      : store.save(next);
  }

  //
  // If we don't have a callback and the current
  // store is capable of saving synchronously
  // then do so.
  //
  if (!callback) {
    return common.merge(names.map(saveStoreSync));
  }

  async.map(names, saveStore, function (err, objs) {
    return err ? callback(err) : callback();
  });
};

//
// ### @private function _execute (action, syncLength, [arguments])
// #### @action {string} Action to execute on `this.store`.
// #### @syncLength {number} Function length of the sync version.
// #### @arguments {Array} Arguments array to apply to the action
// Executes the specified `action` on all stores for this instance, ensuring a callback supplied
// to a synchronous store function is still invoked.
//
Provider.prototype._execute = function (action, syncLength /* [arguments] */) {
  var args = Array.prototype.slice.call(arguments, 2),
      callback = typeof args[args.length - 1] === 'function' && args.pop(),
      destructive = ['set', 'clear', 'merge'].indexOf(action) !== -1,
      self = this,
      response;

  function runAction (name, next) {
    var store = self.stores[name];

    if (destructive && store.readOnly) {
      return next();
    }

    return store[action].length > syncLength
      ? store[action].apply(store, args.concat(next))
      : next(null, store[action].apply(store, args));
  }

  if (callback) {
    return async.forEach(Object.keys(this.stores), runAction, function (err) {
      return err ? callback(err) : callback();
    });
  }

  Object.keys(this.stores).forEach(function (name) {
    if (typeof response === 'undefined') {
      var store = self.stores[name];

      if (destructive && store.readOnly) {
        return;
      }

      response = store[action].apply(store, args);
    }
  });

  return response;
}

//
// Throw the `err` if a callback is not supplied
//
function onError(err, callback) {
  if (callback) {
    return callback(err);
  }

  throw err;
}<|endoftext|>


<commit_before>var mongo = require('zefti-mongo');
var redis = require('zefti-redis');
var remote = require('zefti-remote');

var originSources = {
    mongo : mongo
  , redis : redis
  , remote : remote
};

var readySources = {};

module.exports = function(dataSources){
  for (var dataSource in dataSources) {
    if (!readySources[dataSource]) {
      readySources[dataSource] = originSources[dataSources[dataSource].type](dataSources[dataSource])
    }
  }
  return readySources;
}<commit_msg>stuff<commit_after>var mongo = require('zefti-mongo');
var redis = require('zefti-redis');
var remote = require('zefti-remote');
var dynamo = require('zefti-dynamo');

var originSources = {
    dynamo : dynamo
  , mongo : mongo
  , redis : redis
  , remote : remote
};

var readySources = {};

module.exports = function(dataSources){
  for (var dataSource in dataSources) {
    if (!readySources[dataSource]) {
      readySources[dataSource] = originSources[dataSources[dataSource].type](dataSources[dataSource])
    }
  }
  return readySources;
}<|endoftext|>


<commit_before>const NODE_ENV = process.env.NODE_ENV;
const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

function htmlPack(sets, extractSet)
{
    var extract = new ExtractTextPlugin(extractSet);
    var extractLess = new ExtractTextPlugin("css/[name].css");
    this.entry = cleanEntry(sets);
    this.output = sets.output;
    this.resolve = sets.resolve || {};
    this.module = {
        rules: [
        {
            test: /\.(html|ejs)$/,
            use: extract.extract(
            {
                use: [
                {
                    loader: "html-loader",
                    query:
                    {
                        interpolate: 'require',
                        attrs: ['img:src', 'audio:src', 'source:src', 'video:src']
                    }
                }]
            })
        },
        {
            test: /\.(png|jpg|JPG|gif|svg|eot|ttf|woff|woff2)$/,
            loader: 'url-loader',
            options:
            {
                limit: sets.limitValue,
                name: '/public/imgs/[name].[ext]',
            }
        },
        {
            test: /\.(mp3|ogg|wav|mp4)$/,
            loader: 'file-loader',
            options:
            {
                limit: sets.limitValue,
                name: '/public/audio/[name].[ext]',
            }
        },
        {
            test: /\.gthtml$/,
            use: ["html-loader"]
        },
        {
            test: /\.less$/,
            use: ["css-loader", "less-loader"]
        }]
    };
    this.plugins = [
        extract,
    ];
}

function jsPack(sets)
{
    this.entry = cleanEntry(sets);
    this.output = sets.output;
    this.resolve = sets.resolve || {};
    this.plugins = [
        new webpack.BannerPlugin(''),
    ];
    //可能之後會用到圖片的LOADER 還有ES6 紀錄 TODO
    this.module = {
        rules: [
        { // ES6
            test: /\.js$/,
            exclude: function(modulePath)
            {
                return /node_modules/.test(modulePath) &&
                    !/node_modules\/gt-games/.test(modulePath);
            },
            use:
            {
                loader: 'babel-loader',
                options:
                {
                    presets: ['es2015'],
                    plugins: ['transform-runtime']
                }
            }
        },
        {
            test: /\.vue$/,
            loader: "vue-loader"
        }]
    };
}

function cssPack(sets, extractLessSet)
{
    var extractLess = new ExtractTextPlugin(extractLessSet);
    this.entry = cleanEntry(sets);
    this.output = sets.output;
    this.module = {
        rules: [
        {
            test: /\.(png|jpg|JPG|gif|svg|eot|ttf|woff|woff2)$/,
            loader: 'url-loader',
            options:
            {
                limit: sets.limitValue,
                name: '/public/imgs/[name].[ext]',
            }
        },
        {
            test: /\.less$/,
            use: extractLess.extract(
            {
                use: [
                {
                    loader: "css-loader"
                },
                {
                    loader: "less-loader"
                }],
                // use style-loader in development
                fallback: "style-loader"
            }),
        }]
    };
    this.resolve = sets.resolve || {};
    this.plugins = [
        extractLess,
        new webpack.BannerPlugin(''),
    ];
}

class tsPack
{
    constructor(sets)
    {
        this.entry = cleanEntry(sets),
            this.output = sets.output;
        this.resolve = {
            // Add '.ts' and '.tsx' as a resolvable extension.
            extensions: ['.ts', '.tsx', '.js']
        };
        this.module = {
            loaders: [
                // all files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'
                {
                    test: /\.tsx?$/,
                    loader: "ts-loader?" + JSON.stringify(
                    {
                        configFile: process.cwd() + '/tsconfig.webpack.json'
                    })
                }
            ]
        };
    }
}

var check = {
    html: function(gtSet)
    {
        return new htmlPack(
        {
            entry: gtSet.html,
            output:
            {
                filename: '[name].html',
                path: gtSet.html.dirName || gtSet.dirName,
            },
            limitValue: gtSet.limitValue || 10000
        },
        {
            filename: '[name].html',
        });
    },
    ejs: function(gtSet)
    {
        return new htmlPack(
        {
            entry: gtSet.ejs,
            output:
            {
                filename: '[name].ejs',
                path: gtSet.ejs.dirName || gtSet.dirName,
            }
        },
        {
            filename: '[name].ejs',
        });
    },
    css: function(gtSet)
    {
        return new cssPack(
        {
            entry: gtSet.css,
            output:
            {
                filename: 'css/[name].css',
                path: gtSet.css.dirName || gtSet.dirName,
            },
            limitValue: gtSet.limitValue || 10000
        },
        {
            filename: 'css/[name].css',
        });
    },
    js: function(gtSet)
    {
        return new jsPack(
        {
            entry: gtSet.js,
            output:
            {
                filename: 'js/[name].js',
                path: gtSet.js.dirName || gtSet.dirName,
            },
            resolve: gtSet.js.resolve
        });
    },
    ts: function(gtSet)
    {
        return new tsPack(
        {
            entry: gtSet.ts,
            output:
            {
                filename: 'js/[name].js',
                path: gtSet.ts.dirName || gtSet.dirName,
            }
        });
    }
};

function GuanTing(gtSet)
{
    var out = [];
    for (var x in gtSet)
    {
        try
        {
            if (x != 'dirName' && x != 'limitValue')
            {
                console.log(x);
                out.push(check[x](gtSet));
            }
        }
        catch (e)
        {
            console.log('e04: ' + e);
            continue;
        }
    }
    return out;
}

function readEntries (viewsPath, dirName, ext='.html')
{
    const read = (dir) =>
    fs.readdirSync(dir)
        .reduce((files, file) => {
            let isDirectory = fs.statSync(path.join(dir, file)).isDirectory();
            if (isDirectory) {
                return files.concat(read(path.join(dir, file)));
            } else if (file.search(ext) >= 0) {
                return files.concat(path.join(dir.split(viewsPath)[1], file));
            } else {
                return files;
            }
        }, []);

    const list = read(viewsPath);

    let views = {};

    for (let i=0; i<list.length; i++) {
        let name = (list[i].split(ext)[0]).toString();
        views[name] = viewsPath + list[i];
    }

    views.dirName = dirName;

    return views;
}

function cleanEntry(sets)
{
    let set = {};
    for (let s in sets.entry)
    {
        if (s === 'dirName' || s === 'resolve' || s === 'ext') continue;
        set[s] = sets.entry[s];
    }
    return set;
}

function noder(packs)
{
    let output = [];
    for (var i = 0; i < packs.length; i++)
    {
        output = output.concat(packs[i]);
    }
    return output;
}

module.exports = GuanTing;
module.exports.css = cssPack;
module.exports.js = jsPack;
module.exports.html = htmlPack;
module.exports.readEntries = readEntries;
module.exports.GuanTing = GuanTing;
module.exports.noder = noder;<commit_msg>fix `readEntries` ext.<commit_after>const NODE_ENV = process.env.NODE_ENV;
const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

function htmlPack(sets, extractSet)
{
    var extract = new ExtractTextPlugin(extractSet);
    var extractLess = new ExtractTextPlugin("css/[name].css");
    this.entry = cleanEntry(sets);
    this.output = sets.output;
    this.resolve = sets.resolve || {};
    this.module = {
        rules: [
        {
            test: /\.(html|ejs)$/,
            use: extract.extract(
            {
                use: [
                {
                    loader: "html-loader",
                    query:
                    {
                        interpolate: 'require',
                        attrs: ['img:src', 'audio:src', 'source:src', 'video:src']
                    }
                }]
            })
        },
        {
            test: /\.(png|jpg|JPG|gif|svg|eot|ttf|woff|woff2)$/,
            loader: 'url-loader',
            options:
            {
                limit: sets.limitValue,
                name: '/public/imgs/[name].[ext]',
            }
        },
        {
            test: /\.(mp3|ogg|wav|mp4)$/,
            loader: 'file-loader',
            options:
            {
                limit: sets.limitValue,
                name: '/public/audio/[name].[ext]',
            }
        },
        {
            test: /\.gthtml$/,
            use: ["html-loader"]
        },
        {
            test: /\.less$/,
            use: ["css-loader", "less-loader"]
        }]
    };
    this.plugins = [
        extract,
    ];
}

function jsPack(sets)
{
    this.entry = cleanEntry(sets);
    this.output = sets.output;
    this.resolve = sets.resolve || {};
    this.plugins = [
        new webpack.BannerPlugin(''),
    ];
    //可能之後會用到圖片的LOADER 還有ES6 紀錄 TODO
    this.module = {
        rules: [
        { // ES6
            test: /\.js$/,
            exclude: function(modulePath)
            {
                return /node_modules/.test(modulePath) &&
                    !/node_modules\/gt-games/.test(modulePath);
            },
            use:
            {
                loader: 'babel-loader',
                options:
                {
                    presets: ['es2015'],
                    plugins: ['transform-runtime']
                }
            }
        },
        {
            test: /\.vue$/,
            loader: "vue-loader"
        }]
    };
}

function cssPack(sets, extractLessSet)
{
    var extractLess = new ExtractTextPlugin(extractLessSet);
    this.entry = cleanEntry(sets);
    this.output = sets.output;
    this.module = {
        rules: [
        {
            test: /\.(png|jpg|JPG|gif|svg|eot|ttf|woff|woff2)$/,
            loader: 'url-loader',
            options:
            {
                limit: sets.limitValue,
                name: '/public/imgs/[name].[ext]',
            }
        },
        {
            test: /\.less$/,
            use: extractLess.extract(
            {
                use: [
                {
                    loader: "css-loader"
                },
                {
                    loader: "less-loader"
                }],
                // use style-loader in development
                fallback: "style-loader"
            }),
        }]
    };
    this.resolve = sets.resolve || {};
    this.plugins = [
        extractLess,
        new webpack.BannerPlugin(''),
    ];
}

class tsPack
{
    constructor(sets)
    {
        this.entry = cleanEntry(sets),
            this.output = sets.output;
        this.resolve = {
            // Add '.ts' and '.tsx' as a resolvable extension.
            extensions: ['.ts', '.tsx', '.js']
        };
        this.module = {
            loaders: [
                // all files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'
                {
                    test: /\.tsx?$/,
                    loader: "ts-loader?" + JSON.stringify(
                    {
                        configFile: process.cwd() + '/tsconfig.webpack.json'
                    })
                }
            ]
        };
    }
}

var check = {
    html: function(gtSet)
    {
        return new htmlPack(
        {
            entry: gtSet.html,
            output:
            {
                filename: '[name].html',
                path: gtSet.html.dirName || gtSet.dirName,
            },
            limitValue: gtSet.limitValue || 10000
        },
        {
            filename: '[name].html',
        });
    },
    ejs: function(gtSet)
    {
        return new htmlPack(
        {
            entry: gtSet.ejs,
            output:
            {
                filename: '[name].ejs',
                path: gtSet.ejs.dirName || gtSet.dirName,
            }
        },
        {
            filename: '[name].ejs',
        });
    },
    css: function(gtSet)
    {
        return new cssPack(
        {
            entry: gtSet.css,
            output:
            {
                filename: 'css/[name].css',
                path: gtSet.css.dirName || gtSet.dirName,
            },
            limitValue: gtSet.limitValue || 10000
        },
        {
            filename: 'css/[name].css',
        });
    },
    js: function(gtSet)
    {
        return new jsPack(
        {
            entry: gtSet.js,
            output:
            {
                filename: 'js/[name].js',
                path: gtSet.js.dirName || gtSet.dirName,
            },
            resolve: gtSet.js.resolve
        });
    },
    ts: function(gtSet)
    {
        return new tsPack(
        {
            entry: gtSet.ts,
            output:
            {
                filename: 'js/[name].js',
                path: gtSet.ts.dirName || gtSet.dirName,
            }
        });
    }
};

function GuanTing(gtSet)
{
    var out = [];
    for (var x in gtSet)
    {
        try
        {
            if (x != 'dirName' && x != 'limitValue')
            {
                console.log(x);
                out.push(check[x](gtSet));
            }
        }
        catch (e)
        {
            console.log('e04: ' + e);
            continue;
        }
    }
    return out;
}

function readEntries (viewsPath, dirName, ext='html')
{
    ext = '.' + ext;
    const read = (dir) =>
    fs.readdirSync(dir)
        .reduce((files, file) => {
            let isDirectory = fs.statSync(path.join(dir, file)).isDirectory();
            if (isDirectory) {
                return files.concat(read(path.join(dir, file)));
            } else if (file.search(ext) >= 0) {
                return files.concat(path.join(dir.split(viewsPath)[1], file));
            } else {
                return files;
            }
        }, []);

    const list = read(viewsPath);

    let views = {};

    for (let i=0; i<list.length; i++) {
        let name = (list[i].split(ext)[0]).toString();
        views[name] = viewsPath + list[i];
    }

    views.dirName = dirName;

    return views;
}

function cleanEntry(sets)
{
    let set = {};
    for (let s in sets.entry)
    {
        if (s === 'dirName' || s === 'resolve' || s === 'ext') continue;
        set[s] = sets.entry[s];
    }
    return set;
}

function noder(packs)
{
    let output = [];
    for (var i = 0; i < packs.length; i++)
    {
        output = output.concat(packs[i]);
    }
    return output;
}

module.exports = GuanTing;
module.exports.css = cssPack;
module.exports.js = jsPack;
module.exports.html = htmlPack;
module.exports.readEntries = readEntries;
module.exports.GuanTing = GuanTing;
module.exports.noder = noder;<|endoftext|>


<commit_before>var ejs = require('ejs'),
    path = require('path');

var createJstEjsPreprocessor = function(args, config, logger, helper) {
  config = config || {};

  var log = logger.create('preprocessor.jst-ejs');
  var defaultOptions = {
    logicalMountPoint: "src"
  };

  var options = helper.merge(defaultOptions, args.options || {}, config.options || {});

  var transformPath = function(filepath) {
    return filepath.replace(/\.jst.ejs$/, '.js');
  };

  // Removes base and sprockets untill sprockets mountPath with options.logicalPathStrip
  var transformLogicalPath = function(filepath) {
    var logicalPath = filepath.replace(/\.jst.ejs$/, '');
    // Poor mans solutions here, its somewhat more complex
    return logicalPath.substring(logicalPath.indexOf(options.logicalMountPoint));
  };

  // Internal (private) namespace storage
  var namespace = 'this.JST';

  var makeJST = function (data, file) {
    log.debug(file)

    return "(function () { " +
      namespace + " || (" + namespace + " = {}); " +
      namespace + "[" + JSON.stringify(file.logicalPath) + "] = " +
      data.replace(/$(.)/mg, '$1  ').trimLeft().trimRight() +
      " }).call(this);";
  };

  return function(content, file, done) {
    var result = null;

    log.debug('Processing "%s".', file.originalPath);
    file.path = transformPath(file.originalPath);
    file.logicalPath = transformLogicalPath(file.originalPath);

    try {
      log.debug(content);
      result = ejs.compile(content, {client: true}).toString();
      result = makeJST(result, file);
      log.debug(result);
    } catch (e) {
      log.error('%s\n  at %s', e.message, file.originalPath);
      return;
    }
    // We are done!
    done(result);
  };
};

createJstEjsPreprocessor.$inject = ['args', 'config.JstEjsPreprocessor', 'logger', 'helper'];

// PUBLISH DI MODULE
module.exports = {
  'preprocessor:jst-ejs': ['factory', createJstEjsPreprocessor]
};<commit_msg>Somewhat easier on debug messages<commit_after>var ejs = require('ejs'),
    path = require('path');

var createJstEjsPreprocessor = function(args, config, logger, helper) {
  config = config || {};

  var log = logger.create('preprocessor.jst-ejs');
  var defaultOptions = {
    logicalMountPoint: "src"
  };

  var options = helper.merge(defaultOptions, args.options || {}, config.options || {});

  var transformPath = function(filepath) {
    return filepath.replace(/\.jst.ejs$/, '.js');
  };

  // Removes base and sprockets untill sprockets mountPath with options.logicalPathStrip
  var transformLogicalPath = function(filepath) {
    var logicalPath = filepath.replace(/\.jst.ejs$/, '');
    // Poor mans solutions here, its somewhat more complex
    return logicalPath.substring(logicalPath.indexOf(options.logicalMountPoint));
  };

  // Internal (private) namespace storage
  var namespace = 'this.JST';

  var makeJST = function (data, file) {
    return "(function () { " +
      namespace + " || (" + namespace + " = {}); " +
      namespace + "[" + JSON.stringify(file.logicalPath) + "] = " +
      data.replace(/$(.)/mg, '$1  ').trimLeft().trimRight() +
      " }).call(this);";
  };

  return function(content, file, done) {
    var result = null;

    log.debug('Processing "%s".', file.originalPath);
    file.path = transformPath(file.originalPath);
    file.logicalPath = transformLogicalPath(file.originalPath);

    try {
      result = ejs.compile(content, {client: true}).toString();
      result = makeJST(result, file);
    } catch (e) {
      log.error('%s\n  at %s', e.message, file.originalPath);
      return;
    }
    // We are done!
    done(result);
  };
};

createJstEjsPreprocessor.$inject = ['args', 'config.JstEjsPreprocessor', 'logger', 'helper'];

// PUBLISH DI MODULE
module.exports = {
  'preprocessor:jst-ejs': ['factory', createJstEjsPreprocessor]
};<|endoftext|>


<commit_before>var through = require('through2');
var speedometer = require('speedometer');

module.exports = function(options, onprogress) {
	if (typeof options === 'function') return module.exports(null, options);
	options = options || {};

	var length = options.length || 0;
	var time = options.time || 0;
	var drain = options.drain || false;
	var transferred = 0;
	var nextUpdate = Date.now()+time;
	var delta = 0;
	var speed = speedometer(options.speed || 5000);
	var startTime = Date.now();

	var update = {
		percentage: 0,
		transferred: transferred,
		length: length,
		remaining: length,
		eta: 0,
		runtime: 0
	};

	var emit = function(ended) {
		update.delta = delta;
		update.percentage = ended ? 100 : (length ? transferred/length*100 : 0);
		update.speed = speed(delta);
		update.eta = Math.round(update.remaining / update.speed);
		update.runtime = parseInt((Date.now() - startTime)/1000);
		nextUpdate += time;

		delta = 0;

		tr.emit('progress', update);
	};
	var write = function(chunk, enc, callback) {
		var len = options.objectMode ? 1 : chunk.length;
		transferred += len;
		delta += len;
		update.transferred = transferred;
		update.remaining = length >= transferred ? length - transferred : 0;

		if (Date.now() >= nextUpdate) emit(false);
		callback(null, chunk);
	};
	var end = function(callback) {
		emit(true);
		callback();
	};

	var tr = through(options.objectMode ? {objectMode:true, highWaterMark:16} : {}, write, end);
	var onlength = function(newLength) {
		length = newLength;
		update.length = length;
		update.remaining = length - update.transferred;
		tr.emit('length', length);
	};

	// Expose `onlength()` handler as `setLength()` to support custom use cases where length
	// is not known until after a few chunks have already been pumped, or is
	// calculated on the fly.
	tr.setLength = onlength;

	tr.on('pipe', function(stream) {
		// Support http module
		if (stream.readable && !stream.writable && stream.headers) {
			return onlength(parseInt(stream.headers['content-length'] || 0));
		}

		// Support streams with a length property
		if (typeof stream.length === 'number') {
			return onlength(stream.length);
		}

		// Support request module
		stream.on('response', function(res) {
			if (res && res.headers['content-encoding'] === 'gzip') return;
			if (res && res.headers && res.headers['content-length']) {
				return onlength(parseInt(res.headers['content-length']));
			}
		});
	});

	if (drain) tr.resume();
	if (onprogress) tr.on('progress', onprogress);

	tr.progress = function() {
		update.speed = speed(0);
		update.eta = Math.round(update.remaining / update.speed);

		return update;
	};
	return tr;
};
<commit_msg>Better error handling<commit_after>var through = require('through2');
var speedometer = require('speedometer');

module.exports = function(options, onprogress) {
	if (typeof options === 'function') return module.exports(null, options);
	options = options || {};

	var length = options.length || 0;
	var time = options.time || 0;
	var drain = options.drain || false;
	var transferred = 0;
	var nextUpdate = Date.now()+time;
	var delta = 0;
	var speed = speedometer(options.speed || 5000);
	var startTime = Date.now();

	var update = {
		percentage: 0,
		transferred: transferred,
		length: length,
		remaining: length,
		eta: 0,
		runtime: 0
	};

	var emit = function(ended) {
		update.delta = delta;
		update.percentage = ended ? 100 : (length ? transferred/length*100 : 0);
		update.speed = speed(delta);
		update.eta = Math.round(update.remaining / update.speed);
		update.runtime = parseInt((Date.now() - startTime)/1000);
		nextUpdate += time;

		delta = 0;

		tr.emit('progress', update);
	};
	var write = function(chunk, enc, callback) {
		var len = options.objectMode ? 1 : chunk.length;
		transferred += len;
		delta += len;
		update.transferred = transferred;
		update.remaining = length >= transferred ? length - transferred : 0;

		if (Date.now() >= nextUpdate) emit(false);
		callback(null, chunk);
	};
	var end = function(callback) {
		emit(true);
		callback();
	};

	var tr = through(options.objectMode ? {objectMode:true, highWaterMark:16} : {}, write, end);
	var onlength = function(newLength) {
		length = newLength;
		update.length = length;
		update.remaining = length - update.transferred;
		tr.emit('length', length);
	};

	// Expose `onlength()` handler as `setLength()` to support custom use cases where length
	// is not known until after a few chunks have already been pumped, or is
	// calculated on the fly.
	tr.setLength = onlength;

	tr.on('pipe', function(stream) {
		// Support http module
		if (stream.readable && !stream.writable && stream.headers) {
			return onlength(parseInt(stream.headers['content-length'] || 0));
		}

		// Support streams with a length property
		if (typeof stream.length === 'number') {
			return onlength(stream.length);
		}

		// Support request module
		stream.on('response', function(res) {
			if (!res || !res.headers) return;
			if (res.headers['content-encoding'] === 'gzip') return;
			if (res.headers['content-length']) {
				return onlength(parseInt(res.headers['content-length']));
			}
		});
	});

	if (drain) tr.resume();
	if (onprogress) tr.on('progress', onprogress);

	tr.progress = function() {
		update.speed = speed(0);
		update.eta = Math.round(update.remaining / update.speed);

		return update;
	};
	return tr;
};
<|endoftext|>


<commit_before>$ = Awesomplete.$;
$$ = Awesomplete.$$;

document.addEventListener("DOMContentLoaded", function() {
	var nav = $("nav")
	$$("section > h1").forEach(function (h1) {
		if (h1.parentNode.id) {
			$.create("a", {
				href: "#" + h1.parentNode.id,
				textContent: h1.textContent.replace(/\(.+?\)/g, ""),
				inside: nav
			});
		}
	});
});<commit_msg>Tell eslint about valid globals<commit_after>/* global Awesomplete, $:true, $$:true */
$ = Awesomplete.$;
$$ = Awesomplete.$$;

document.addEventListener("DOMContentLoaded", function() {
	var nav = $("nav")
	$$("section > h1").forEach(function (h1) {
		if (h1.parentNode.id) {
			$.create("a", {
				href: "#" + h1.parentNode.id,
				textContent: h1.textContent.replace(/\(.+?\)/g, ""),
				inside: nav
			});
		}
	});
});<|endoftext|>