<commit_before>(function(name, factory) {

    if (typeof define === 'function') { // RequireJS
        define(function() { return factory(); });
    } else if (typeof module !== 'undefined' && module.exports) { // CommonJS
        module.exports = factory();
    } else { // Browser
        this[name] = factory();
    }

})('pledge', function(undefined) {

    var root = this,

        _invert = function(obj) {
            var result = {},
                key;
            for (key in obj) {
                result[obj[key]] = key;
            }
            return result;
        },

        _defer = root.process && root.process.nextTick ? function(callback) {
            root.process.nextTick(callback);
        } : function(callback) {
            setTimeout(callback, 0);
        },

        _idCounter = 0,

        _nativeToString = ({}).toString,
        _nativeSlide = ([]).slice,

        _slice = function(arr) {
            return _nativeSlide.call(arr);
        },

        _isArray = Array.isArray || function(obj) {
            return _nativeToString.call(obj) === '[object Array]';
        },

        _isNumber = function(obj) {
            return _nativeToString.call(obj) === '[object Number]';
        };

        /**
         * Status values, determines
         * what the promise's status is
         * @readonly
         * @enum {Number}
         * @alias _PROMISE_STATUS
         */
    var _PROMISE_STATUS = {
            idle:       0,
            progressed: 1,
            failed:     2,
            done:       3
        },
        /**
         * Call values, used to determine
         * what kind of functions to call
         * @readonly
         * @enum {Number}
         * @alias _PROMISE_CALL
         */
        _PROMISE_CALL = {
            done:     0,
            fail:     1,
            always:   2,
            progress: 3,
            pipe:     4
        },
        _PROMISE_CALL_NAME = _invert(_PROMISE_CALL);

    /**
     * A lightweight implementation of promises.
     * API based on {@link https://api.jquery.com/promise/ jQuery.promises}
     * @class Promise
     */
    var Promise = function(func) {
        var self = this;

        /**
         * @type {Id}
         * @private
         */
        self._id = ++_idCounter;

        /**
         * Registered functions organized by _PROMISE_CALL
         * @type {Object}
         * @private
         */
        self._calls = {};

        /**
         * Current status
         * @type {Number}
         * @private
         */
        self._status = _PROMISE_STATUS.idle;

        /**
         * Allow an ES6 style call
         * (only if the function is provided)
         */
        if (func) {
            func(self.resolve.bind(self), self.reject.bind(self));
        }
    };

    Promise.prototype = /** @lends Promise# */ {
        constructor: Promise,

        /**
         * Register a done call that is fired after a Promise is resolved
         * @param  {Function} func
         * @return {Promise}
         */
        done: function(func) { return this._pushCall(_PROMISE_CALL.done, func); },
        then: function(func) { return this._pushCall(_PROMISE_CALL.done, func); },
        /**
         * Register a fail call that is fired after a Promise is rejected
         * @param  {Function} func
         * @return {Promise}
         */
        fail: function(func) { return this._pushCall(_PROMISE_CALL.fail, func); },
        /**
         * Register a call that fires after done or fail
         * @param  {Function} func
         * @return {Promise}
         */
        always: function(func) { return this._pushCall(_PROMISE_CALL.always, func); },
        finally: function(func) { return this._pushCall(_PROMISE_CALL.always, func); },
        /**
         * Register a progress call that is fired after a Promise is notified
         * @param  {Function} func
         * @return {Promise}
         */
        progress: function(func) { return this._pushCall(_PROMISE_CALL.progress, func); },
        /**
         * Register a pipe call that is fired before done or fail and whose return value
         * is passed to the next pipe/done/fail call
         * @param  {Function} func
         * @return {Promise}
         */
        pipe: function(func) { return this._pushCall(_PROMISE_CALL.pipe, func); },

        /**
         * Pushes a function into a call array by type
         * @param  {_PROMISE_CALL} callType
         * @param  {Function} func
         * @return {Promise}
         * @private
         */
        _pushCall: function(callType, func) {
            var self = this,
                status = self._status;
            if (status !== _PROMISE_STATUS.idle) {
                if (
                    // done
                    (status === _PROMISE_STATUS.done   && callType === _PROMISE_CALL.done) ||
                    // fail
                    (status === _PROMISE_STATUS.failed && callType === _PROMISE_CALL.fail) ||
                    // always
                    ((status === _PROMISE_STATUS.done || status === _PROMISE_STATUS.failed) && callType === _PROMISE_CALL.always)
                ) {
                    _defer(function() { func.call(null, self._firedArgs); });
                    return self;
                }
            }

            self._getCalls(callType).push(func);
            return self;
        },

        /**
         * Notify the promise - calls any functions in
         * Promise.progress
         * @return {Promise}
         */
        notify: function() {
            var self = this;

            self._status = _PROMISE_STATUS.progressed;

            var args = self._runPipe(arguments);
            self._fire(_PROMISE_CALL.progress, args)._fire(_PROMISE_CALL.always, args);

            return self;
        },

        /**
         * Reject the promise - calls any functions in
         * Promise.fail, then calls any functions in
         * Promise.always
         * @return {Promise}
         */
        reject: function() {
            var self = this;

            // If we've already called failed or done, go no further
            if (self._status === _PROMISE_STATUS.failed || self._status === _PROMISE_STATUS.done) { return self; }

            self._status = _PROMISE_STATUS.failed;

            // Never run the pipe on fail. Simply fail.
            // Running the pipe after an unexpected failure may lead to
            // more failures
            self._fire(_PROMISE_CALL.fail, arguments)
                ._fire(_PROMISE_CALL.always, arguments);

            self._cleanup();

            return self;
        },

        /**
         * Resolve the promise - calls any functions in
         * Promise.done, then calls any functions in
         * Promise.always
         * @return {Promise}
         */
        resolve: function() {
            var self = this;

            // If we've already called failed or done, go no further
            if (self._status === _PROMISE_STATUS.failed || self._status === _PROMISE_STATUS.done) { return self; }

            self._status = _PROMISE_STATUS.done;

            var args = self._runPipe(arguments);
            self._fire(_PROMISE_CALL.done, args)
                ._fire(_PROMISE_CALL.always, args);

            self._cleanup();

            return self;
        },

        /**
         * Returns the status of the Promise
         * @return {Number} STATUS
         */
        status: function() {
            return this._status;
        },

        /**
         * Fires a _PROMISE_CALL type with the provided arguments
         * @param  {_PROMISE_CALL} callType
         * @param  {Array} args
         * @return {Promise}
         * @private
         */
        _fire: function(callType, args) {
            var self = this;

            self._firedArgs = args;

            var calls = self._getCalls(callType),
                idx = 0, length = calls.length;
            for (; idx < length; idx++) {
                calls[idx].apply(null, args);
            }
            return self;
        },

        /**
         * Runs the pipe, catching the return value
         * to pass to the next pipe. Returns the
         * arguments to used by the calling method
         * to proceed to call other methods (e.g. done/fail/always)
         * @param  {Array} args
         * @return {Array} args
         * @private
         */
        _runPipe: function(args) {
            var pipes = this._getCalls(_PROMISE_CALL.pipe),
                idx = 0, length = pipes.length, val;
            for (; idx < length; idx++) {
                val = pipes[idx].apply(null, args);
                if (val !== undefined) { args = [val]; }
            }

            return args;
        },

        /**
         * Lazy generate arrays based on type to
         * avoid creating disposable arrays for
         * methods that aren't going to be used/called
         * @param  {_PROMISE_CALL} type
         * @return {Array}
         * @private
         */
        _getCalls: function(type) {
            var self = this;
            return self._calls[_PROMISE_CALL_NAME[type]] || (self._calls[_PROMISE_CALL_NAME[type]] = []);
        },

        /**
         * Allows a promise to be called like a
         * Function.call() or Function.apply()
         *
         * Very useful for passing a promise as
         * a callback function to 3rd party code
         * (as long as the third party code doesn't
         * try to invoke the Promise directly)
         */
        call: function() {
            var self = this,
                args = _slice(arguments);
            args.splice(0, 1); // Throw away the context
            self.notify.apply(self, args);
        },
        apply: function(ctx, args) {
            var self = this;
            self.notify.apply(self, args);
        },

        /**
         * Cleanup references to functions stored in
         * arrays that are no longer able to be called
         * @private
         */
        _cleanup: function() {
            var self = this;
            self._getCalls(_PROMISE_CALL.done).length = 0;
            self._getCalls(_PROMISE_CALL.fail).length = 0;
            self._getCalls(_PROMISE_CALL.always).length = 0;
        },

        /**
         * Create a protected object that does not
         * allow promise resolution.
         * @return {Object}
         */
        promise: function() {
            var self = this;
            return {
                done:     self.done.bind(self),
                then:     self.then.bind(self),
                fail:     self.fail.bind(self),
                always:   self.always.bind(self),
                finally:  self.finally.bind(self),
                progress: self.progress.bind(self),
                pipe:     self.pipe.bind(self)
            };
        },

        /**
         * Debug string
         * @return {String}
         */
        toString: function() {
            var self = this;
            return 'promise-js - ' + [
                'id: '       + self._id,
                'status: '   + _invert(_PROMISE_STATUS)[self._status],
                'done: '     + self._getCalls(_PROMISE_CALL.done).length,
                'fail: '     + self._getCalls(_PROMISE_CALL.fail).length,
                'always: '   + self._getCalls(_PROMISE_CALL.always).length,
                'progress: ' + self._getCalls(_PROMISE_CALL.progress).length,
                'pipe: '     + self._getCalls(_PROMISE_CALL.pipe).length
            ].join(', ');
        }
    };

    /**
     * When to go with Promise. Used by calling `when()` and passing
     * promises to listen to. when can be chained with multiple calls
     * e.g. `when(p1, p2, p3).then(func).then(func).done(func).always(func);`
     * @function when
     * @param {...Promise} promises
     * @return {Promise} A new promise that resolves when all of the given promises resolve.
     */

    /**
     * The when object. It's not exposed to the user,
     * they only see a promise (with a .then() method),
     * but all the magic happens here
     */
    var When = function() {
        /**
         * Store our promise
         * @type {Promise}
         */
        // this._p;

        /**
         * Store the promises being listened to
         * @type {Array.<Promise>}
         */
        this._events = [];
    };

    When.prototype = {
        constructor: When,

        /**
         * Called by the public when function to initialize
         * the when object
         * @return {Promise}
         */
        init: function() {
            var self = this;

            self._events = _isArray(arguments[0]) ? arguments[0] : _slice(arguments);
            self._subscribe();

            var promise = new Promise();
            promise.then = function() { self.done.apply(self, arguments); };
            self._p = promise;
            return promise; // Return the promise so that it can be subscribed to
        },

        /**
         * Subscribe to the promises passed and react
         * when they fire events
         * @private
         */
        _subscribe: function() {
            var self = this,
                check = self._checkStatus.bind(self),
                fireProgress = self._fireProgress.bind(self),
                events = self._events,
                idx = events.length;
            while (idx--) {
                events[idx].done(check).fail(check).progress(fireProgress);
            }
        },

        /**
         * Check the status of all promises when
         * any one promise fires an event
         * @private
         */
        _checkStatus: function() {
            var self = this,
                events = self._events, evt,
                total = events.length,
                done = 0, failed = 0,
                idx = total;
            while (idx--) {
                evt = events[idx];
                // We're waiting for everything to complete
                // so if there's an item with no status, stop
                if (evt.status() === _PROMISE_STATUS.idle) { return; }
                if (evt.status() === _PROMISE_STATUS.done) { done += 1; continue; }
                if (evt.status() === _PROMISE_STATUS.failed) { failed += 1; continue; }
            }
            self._fire(total, done, failed, arguments);
        },

        /**
         * Based on the statuses of our promises, fire the
         * appropriate events
         * @param  {Number}    total  total number of promises
         * @param  {Number}    done   promises in a done state
         * @param  {Number}    failed promises in a failed state
         * @param  {Arguments} args   arguments to pass
         * @private
         */
        _fire: function(total, done, failed, args) {
            var promise = this._p; // Our promise

            // If everything completed, call done (this will call always)
            if (done === total) { return promise.resolve.apply(promise, args); }

            // If everything failed, call fail (this will call always)
            if (failed === total) { return promise.reject.apply(promise, args); }

            // If everything fired, but they're not all one thing, then just call always.
            // The only way to do that without exposing a public function in Promise is
            // to use the private _fire event
            if ((done + failed) === total) { return promise._fire(_PROMISE_CALL.always, args); }
        },

        /**
         * Handled separately from fire because we want to trigger
         * anytime any of the promises progress regardless of sate
         * @private
         */
        _fireProgress: function() {
            var promise = this._p;
            promise.notify.apply(promise, arguments);
        }
    };

    var api = function() {
        return new Promise();
    };
    api.when = function() {
        var w = new When();
        return w.init.apply(w, arguments);
    };

    return api;

});<commit_msg>add `catch`<commit_after>(function(name, factory) {

    if (typeof define === 'function') { // RequireJS
        define(function() { return factory(); });
    } else if (typeof module !== 'undefined' && module.exports) { // CommonJS
        module.exports = factory();
    } else { // Browser
        this[name] = factory();
    }

})('pledge', function(undefined) {

    var root = this,

        _invert = function(obj) {
            var result = {},
                key;
            for (key in obj) {
                result[obj[key]] = key;
            }
            return result;
        },

        _defer = root.process && root.process.nextTick ? function(callback) {
            root.process.nextTick(callback);
        } : function(callback) {
            setTimeout(callback, 0);
        },

        _idCounter = 0,

        _nativeToString = ({}).toString,
        _nativeSlide = ([]).slice,

        _slice = function(arr) {
            return _nativeSlide.call(arr);
        },

        _isArray = Array.isArray || function(obj) {
            return _nativeToString.call(obj) === '[object Array]';
        },

        _isNumber = function(obj) {
            return _nativeToString.call(obj) === '[object Number]';
        };

        /**
         * Status values, determines
         * what the promise's status is
         * @readonly
         * @enum {Number}
         * @alias _PROMISE_STATUS
         */
    var _PROMISE_STATUS = {
            idle:       0,
            progressed: 1,
            failed:     2,
            done:       3
        },
        /**
         * Call values, used to determine
         * what kind of functions to call
         * @readonly
         * @enum {Number}
         * @alias _PROMISE_CALL
         */
        _PROMISE_CALL = {
            done:     0,
            fail:     1,
            always:   2,
            progress: 3,
            pipe:     4
        },
        _PROMISE_CALL_NAME = _invert(_PROMISE_CALL);

    /**
     * A lightweight implementation of promises.
     * API based on {@link https://api.jquery.com/promise/ jQuery.promises}
     * @class Promise
     */
    var Promise = function(func) {
        var self = this;

        /**
         * @type {Id}
         * @private
         */
        self._id = ++_idCounter;

        /**
         * Registered functions organized by _PROMISE_CALL
         * @type {Object}
         * @private
         */
        self._calls = {};

        /**
         * Current status
         * @type {Number}
         * @private
         */
        self._status = _PROMISE_STATUS.idle;

        /**
         * Allow an ES6 style call
         * (only if the function is provided)
         */
        if (func) {
            func(self.resolve.bind(self), self.reject.bind(self));
        }
    };

    Promise.prototype = /** @lends Promise# */ {
        constructor: Promise,

        /**
         * Register a done call that is fired after a Promise is resolved
         * @param  {Function} func
         * @return {Promise}
         */
        done: function(func) { return this._pushCall(_PROMISE_CALL.done, func); },
        then: function(func) { return this._pushCall(_PROMISE_CALL.done, func); },
        /**
         * Register a fail call that is fired after a Promise is rejected
         * @param  {Function} func
         * @return {Promise}
         */
        fail: function(func) { return this._pushCall(_PROMISE_CALL.fail, func); },
        catch: function(func) { return this._pushCall(_PROMISE_CALL.fail, func); },
        /**
         * Register a call that fires after done or fail
         * @param  {Function} func
         * @return {Promise}
         */
        always: function(func) { return this._pushCall(_PROMISE_CALL.always, func); },
        finally: function(func) { return this._pushCall(_PROMISE_CALL.always, func); },
        /**
         * Register a progress call that is fired after a Promise is notified
         * @param  {Function} func
         * @return {Promise}
         */
        progress: function(func) { return this._pushCall(_PROMISE_CALL.progress, func); },
        /**
         * Register a pipe call that is fired before done or fail and whose return value
         * is passed to the next pipe/done/fail call
         * @param  {Function} func
         * @return {Promise}
         */
        pipe: function(func) { return this._pushCall(_PROMISE_CALL.pipe, func); },

        /**
         * Pushes a function into a call array by type
         * @param  {_PROMISE_CALL} callType
         * @param  {Function} func
         * @return {Promise}
         * @private
         */
        _pushCall: function(callType, func) {
            var self = this,
                status = self._status;
            if (status !== _PROMISE_STATUS.idle) {
                if (
                    // done
                    (status === _PROMISE_STATUS.done   && callType === _PROMISE_CALL.done) ||
                    // fail
                    (status === _PROMISE_STATUS.failed && callType === _PROMISE_CALL.fail) ||
                    // always
                    ((status === _PROMISE_STATUS.done || status === _PROMISE_STATUS.failed) && callType === _PROMISE_CALL.always)
                ) {
                    _defer(function() { func.call(null, self._firedArgs); });
                    return self;
                }
            }

            self._getCalls(callType).push(func);
            return self;
        },

        /**
         * Notify the promise - calls any functions in
         * Promise.progress
         * @return {Promise}
         */
        notify: function() {
            var self = this;

            self._status = _PROMISE_STATUS.progressed;

            var args = self._runPipe(arguments);
            self._fire(_PROMISE_CALL.progress, args)._fire(_PROMISE_CALL.always, args);

            return self;
        },

        /**
         * Reject the promise - calls any functions in
         * Promise.fail, then calls any functions in
         * Promise.always
         * @return {Promise}
         */
        reject: function() {
            var self = this;

            // If we've already called failed or done, go no further
            if (self._status === _PROMISE_STATUS.failed || self._status === _PROMISE_STATUS.done) { return self; }

            self._status = _PROMISE_STATUS.failed;

            // Never run the pipe on fail. Simply fail.
            // Running the pipe after an unexpected failure may lead to
            // more failures
            self._fire(_PROMISE_CALL.fail, arguments)
                ._fire(_PROMISE_CALL.always, arguments);

            self._cleanup();

            return self;
        },

        /**
         * Resolve the promise - calls any functions in
         * Promise.done, then calls any functions in
         * Promise.always
         * @return {Promise}
         */
        resolve: function() {
            var self = this;

            // If we've already called failed or done, go no further
            if (self._status === _PROMISE_STATUS.failed || self._status === _PROMISE_STATUS.done) { return self; }

            self._status = _PROMISE_STATUS.done;

            var args = self._runPipe(arguments);
            self._fire(_PROMISE_CALL.done, args)
                ._fire(_PROMISE_CALL.always, args);

            self._cleanup();

            return self;
        },

        /**
         * Returns the status of the Promise
         * @return {Number} STATUS
         */
        status: function() {
            return this._status;
        },

        /**
         * Fires a _PROMISE_CALL type with the provided arguments
         * @param  {_PROMISE_CALL} callType
         * @param  {Array} args
         * @return {Promise}
         * @private
         */
        _fire: function(callType, args) {
            var self = this;

            self._firedArgs = args;

            var calls = self._getCalls(callType),
                idx = 0, length = calls.length;
            for (; idx < length; idx++) {
                calls[idx].apply(null, args);
            }
            return self;
        },

        /**
         * Runs the pipe, catching the return value
         * to pass to the next pipe. Returns the
         * arguments to used by the calling method
         * to proceed to call other methods (e.g. done/fail/always)
         * @param  {Array} args
         * @return {Array} args
         * @private
         */
        _runPipe: function(args) {
            var pipes = this._getCalls(_PROMISE_CALL.pipe),
                idx = 0, length = pipes.length, val;
            for (; idx < length; idx++) {
                val = pipes[idx].apply(null, args);
                if (val !== undefined) { args = [val]; }
            }

            return args;
        },

        /**
         * Lazy generate arrays based on type to
         * avoid creating disposable arrays for
         * methods that aren't going to be used/called
         * @param  {_PROMISE_CALL} type
         * @return {Array}
         * @private
         */
        _getCalls: function(type) {
            var self = this;
            return self._calls[_PROMISE_CALL_NAME[type]] || (self._calls[_PROMISE_CALL_NAME[type]] = []);
        },

        /**
         * Allows a promise to be called like a
         * Function.call() or Function.apply()
         *
         * Very useful for passing a promise as
         * a callback function to 3rd party code
         * (as long as the third party code doesn't
         * try to invoke the Promise directly)
         */
        call: function() {
            var self = this,
                args = _slice(arguments);
            args.splice(0, 1); // Throw away the context
            self.notify.apply(self, args);
        },
        apply: function(ctx, args) {
            var self = this;
            self.notify.apply(self, args);
        },

        /**
         * Cleanup references to functions stored in
         * arrays that are no longer able to be called
         * @private
         */
        _cleanup: function() {
            var self = this;
            self._getCalls(_PROMISE_CALL.done).length = 0;
            self._getCalls(_PROMISE_CALL.fail).length = 0;
            self._getCalls(_PROMISE_CALL.always).length = 0;
        },

        /**
         * Create a protected object that does not
         * allow promise resolution.
         * @return {Object}
         */
        promise: function() {
            var self = this;
            return {
                done:     self.done.bind(self),
                then:     self.then.bind(self),
                fail:     self.fail.bind(self),
                catch:    self.catch.bind(self),
                always:   self.always.bind(self),
                finally:  self.finally.bind(self),
                progress: self.progress.bind(self),
                pipe:     self.pipe.bind(self)
            };
        },

        /**
         * Debug string
         * @return {String}
         */
        toString: function() {
            var self = this;
            return 'promise-js - ' + [
                'id: '       + self._id,
                'status: '   + _invert(_PROMISE_STATUS)[self._status],
                'done: '     + self._getCalls(_PROMISE_CALL.done).length,
                'fail: '     + self._getCalls(_PROMISE_CALL.fail).length,
                'always: '   + self._getCalls(_PROMISE_CALL.always).length,
                'progress: ' + self._getCalls(_PROMISE_CALL.progress).length,
                'pipe: '     + self._getCalls(_PROMISE_CALL.pipe).length
            ].join(', ');
        }
    };

    /**
     * When to go with Promise. Used by calling `when()` and passing
     * promises to listen to. when can be chained with multiple calls
     * e.g. `when(p1, p2, p3).then(func).then(func).done(func).always(func);`
     * @function when
     * @param {...Promise} promises
     * @return {Promise} A new promise that resolves when all of the given promises resolve.
     */

    /**
     * The when object. It's not exposed to the user,
     * they only see a promise (with a .then() method),
     * but all the magic happens here
     */
    var When = function() {
        /**
         * Store our promise
         * @type {Promise}
         */
        // this._p;

        /**
         * Store the promises being listened to
         * @type {Array.<Promise>}
         */
        this._events = [];
    };

    When.prototype = {
        constructor: When,

        /**
         * Called by the public when function to initialize
         * the when object
         * @return {Promise}
         */
        init: function() {
            var self = this;

            self._events = _isArray(arguments[0]) ? arguments[0] : _slice(arguments);
            self._subscribe();

            var promise = new Promise();
            promise.then = function() { self.done.apply(self, arguments); };
            self._p = promise;
            return promise; // Return the promise so that it can be subscribed to
        },

        /**
         * Subscribe to the promises passed and react
         * when they fire events
         * @private
         */
        _subscribe: function() {
            var self = this,
                check = self._checkStatus.bind(self),
                fireProgress = self._fireProgress.bind(self),
                events = self._events,
                idx = events.length;
            while (idx--) {
                events[idx].done(check).fail(check).progress(fireProgress);
            }
        },

        /**
         * Check the status of all promises when
         * any one promise fires an event
         * @private
         */
        _checkStatus: function() {
            var self = this,
                events = self._events, evt,
                total = events.length,
                done = 0, failed = 0,
                idx = total;
            while (idx--) {
                evt = events[idx];
                // We're waiting for everything to complete
                // so if there's an item with no status, stop
                if (evt.status() === _PROMISE_STATUS.idle) { return; }
                if (evt.status() === _PROMISE_STATUS.done) { done += 1; continue; }
                if (evt.status() === _PROMISE_STATUS.failed) { failed += 1; continue; }
            }
            self._fire(total, done, failed, arguments);
        },

        /**
         * Based on the statuses of our promises, fire the
         * appropriate events
         * @param  {Number}    total  total number of promises
         * @param  {Number}    done   promises in a done state
         * @param  {Number}    failed promises in a failed state
         * @param  {Arguments} args   arguments to pass
         * @private
         */
        _fire: function(total, done, failed, args) {
            var promise = this._p; // Our promise

            // If everything completed, call done (this will call always)
            if (done === total) { return promise.resolve.apply(promise, args); }

            // If everything failed, call fail (this will call always)
            if (failed === total) { return promise.reject.apply(promise, args); }

            // If everything fired, but they're not all one thing, then just call always.
            // The only way to do that without exposing a public function in Promise is
            // to use the private _fire event
            if ((done + failed) === total) { return promise._fire(_PROMISE_CALL.always, args); }
        },

        /**
         * Handled separately from fire because we want to trigger
         * anytime any of the promises progress regardless of sate
         * @private
         */
        _fireProgress: function() {
            var promise = this._p;
            promise.notify.apply(promise, arguments);
        }
    };

    var api = function() {
        return new Promise();
    };
    api.when = function() {
        var w = new When();
        return w.init.apply(w, arguments);
    };

    return api;

});<|endoftext|>


<commit_before>// Load in dependencies
var expect = require('chai').expect;
var spritesmithEngineTest = require('spritesmith-engine-test');
var phantomjssmith = require('../lib/phantomjssmith');

// Start the normal test
spritesmithEngineTest.run({
  engine: phantomjssmith,
  engineName: 'phantomjssmith'
});

// Define phantomjssmith specific tests
var testUtils = spritesmithEngineTest.spritesmithUtils;
describe('phantomjssmith', function () {
  describe.only('exporting a jpeg', function () {
    // Set up canvas for test case
    var multipleImages = spritesmithEngineTest.config.multipleImages;
    testUtils.interpretImages(phantomjssmith, multipleImages.filepaths);
    testUtils._createCanvas(phantomjssmith, multipleImages.width, multipleImages.height);
    testUtils._addImages(multipleImages.coordinateArr);

    // Run export for a jpeg
    before(function exportJpeg (done) {
      // Export canvas as a jpeg
      // https://github.com/twolfson/gulp.spritesmith/issues/19#issuecomment-57157408
      var that = this;
      this.canvas['export']({format: 'jpeg'}, function (err, result) {
        that.result = result;
        done(err);
      });
    });
    after(function cleanupExport () {
      delete this.result;
    });

    // Allow for debugging
    if (process.env.TEST_DEBUG) {
      testUtils.debugResult();
    }

    // Load in pixels for assertions
    testUtils.loadActualPixels('image/jpeg');
    testUtils.loadExpectedPixels(multipleImages.expectedImage, 'image/png');

    // Assert against our pixels
    it('returns an image', function () {
      expect(this.actualPixels.length).to.be.a('number');
      expect(this.actualPixels.length).to.equal(this.expectedPixels.length);
    });

    // TODO: Figure out this JPEG assertion madness
    it.skip('returns an accurate image', function () {
      // Localize pixel info
      var actualPixels = this.actualPixels;
      var expectedPixels = this.expectedPixels;

      // Compare pixels
      var i = 0;
      var len = actualPixels.length;
      for (; i < len; i++) {
        // If the pixels did not match, complain and throw
        var pixelsWithinThreshold = Math.abs(expectedPixels[i] - actualPixels[i]) <= 10;
        expect(pixelsWithinThreshold).to.equal(true,
          'Expected ' + expectedPixels[i] + ' and ' + actualPixels[i] + ' to be at most 10 apart. Index was ' + i);
      }
    });
  });

  describe('running against very long URLs', function () {
    // Set up canvas for test case
    var multipleImages = spritesmithEngineTest.config.multipleImages;
    testUtils.interpretImages(phantomjssmith, multipleImages.filepaths);
    testUtils._createCanvas(phantomjssmith, multipleImages.width, multipleImages.height);
    testUtils._addImages(multipleImages.coordinateArr);

    // Run export with excessive meta data
    before(function exportWithLongMetadata (done) {
      // Generate a long string (looooooong)
      // https://github.com/twolfson/phantomjssmith/issues/3
      // DEV: Unfortunately, this test doesn't reproduce the issue on Linux
      var longStr = 'l',
          i = 71663;
      while (i--) {
        longStr += 'o';
      }
      longStr += 'ng';

      // Export canvas with way too much meta data
      var that = this;
      this.canvas['export']({format: 'png', longStr: longStr}, function (err, result) {
        that.result = result;
        done(err);
      });
    });
    after(function cleanupExport () {
      delete this.result;
    });

    it('does not crash', function () {
      // Would have thrown
    });
    it('returns an image', function () {
      expect(this.result).to.not.equal('');
    });
  });

  describe('with a custom timeout', function () {
    // Set up canvas for test case
    var multipleImages = spritesmithEngineTest.config.multipleImages;
    testUtils.interpretImages(phantomjssmith, multipleImages.filepaths);
    testUtils._createCanvas(phantomjssmith, multipleImages.width, multipleImages.height);
    testUtils._addImages(multipleImages.coordinateArr);

    // Run export with short timeout
    before(function exportWithShortTimeout (done) {
      var that = this;
      this.canvas['export']({format: 'png', timeout: 1}, function (err, result) {
        that.err = err;
        that.result = result;
        done();
      });
    });
    after(function cleanupResults () {
      delete this.err;
      delete this.result;
    });

    it('times out very easily', function () {
      expect(this.err.message).to.contain('Timing out script.');
    });
  });
});
<commit_msg>Removed dev .only<commit_after>// Load in dependencies
var expect = require('chai').expect;
var spritesmithEngineTest = require('spritesmith-engine-test');
var phantomjssmith = require('../lib/phantomjssmith');

// Start the normal test
spritesmithEngineTest.run({
  engine: phantomjssmith,
  engineName: 'phantomjssmith'
});

// Define phantomjssmith specific tests
var testUtils = spritesmithEngineTest.spritesmithUtils;
describe('phantomjssmith', function () {
  describe('exporting a jpeg', function () {
    // Set up canvas for test case
    var multipleImages = spritesmithEngineTest.config.multipleImages;
    testUtils.interpretImages(phantomjssmith, multipleImages.filepaths);
    testUtils._createCanvas(phantomjssmith, multipleImages.width, multipleImages.height);
    testUtils._addImages(multipleImages.coordinateArr);

    // Run export for a jpeg
    before(function exportJpeg (done) {
      // Export canvas as a jpeg
      // https://github.com/twolfson/gulp.spritesmith/issues/19#issuecomment-57157408
      var that = this;
      this.canvas['export']({format: 'jpeg'}, function (err, result) {
        that.result = result;
        done(err);
      });
    });
    after(function cleanupExport () {
      delete this.result;
    });

    // Allow for debugging
    if (process.env.TEST_DEBUG) {
      testUtils.debugResult();
    }

    // Load in pixels for assertions
    testUtils.loadActualPixels('image/jpeg');
    testUtils.loadExpectedPixels(multipleImages.expectedImage, 'image/png');

    // Assert against our pixels
    it('returns an image', function () {
      expect(this.actualPixels.length).to.be.a('number');
      expect(this.actualPixels.length).to.equal(this.expectedPixels.length);
    });

    // TODO: Figure out this JPEG assertion madness
    it.skip('returns an accurate image', function () {
      // Localize pixel info
      var actualPixels = this.actualPixels;
      var expectedPixels = this.expectedPixels;

      // Compare pixels
      var i = 0;
      var len = actualPixels.length;
      for (; i < len; i++) {
        // If the pixels did not match, complain and throw
        var pixelsWithinThreshold = Math.abs(expectedPixels[i] - actualPixels[i]) <= 10;
        expect(pixelsWithinThreshold).to.equal(true,
          'Expected ' + expectedPixels[i] + ' and ' + actualPixels[i] + ' to be at most 10 apart. Index was ' + i);
      }
    });
  });

  describe('running against very long URLs', function () {
    // Set up canvas for test case
    var multipleImages = spritesmithEngineTest.config.multipleImages;
    testUtils.interpretImages(phantomjssmith, multipleImages.filepaths);
    testUtils._createCanvas(phantomjssmith, multipleImages.width, multipleImages.height);
    testUtils._addImages(multipleImages.coordinateArr);

    // Run export with excessive meta data
    before(function exportWithLongMetadata (done) {
      // Generate a long string (looooooong)
      // https://github.com/twolfson/phantomjssmith/issues/3
      // DEV: Unfortunately, this test doesn't reproduce the issue on Linux
      var longStr = 'l',
          i = 71663;
      while (i--) {
        longStr += 'o';
      }
      longStr += 'ng';

      // Export canvas with way too much meta data
      var that = this;
      this.canvas['export']({format: 'png', longStr: longStr}, function (err, result) {
        that.result = result;
        done(err);
      });
    });
    after(function cleanupExport () {
      delete this.result;
    });

    it('does not crash', function () {
      // Would have thrown
    });
    it('returns an image', function () {
      expect(this.result).to.not.equal('');
    });
  });

  describe('with a custom timeout', function () {
    // Set up canvas for test case
    var multipleImages = spritesmithEngineTest.config.multipleImages;
    testUtils.interpretImages(phantomjssmith, multipleImages.filepaths);
    testUtils._createCanvas(phantomjssmith, multipleImages.width, multipleImages.height);
    testUtils._addImages(multipleImages.coordinateArr);

    // Run export with short timeout
    before(function exportWithShortTimeout (done) {
      var that = this;
      this.canvas['export']({format: 'png', timeout: 1}, function (err, result) {
        that.err = err;
        that.result = result;
        done();
      });
    });
    after(function cleanupResults () {
      delete this.err;
      delete this.result;
    });

    it('times out very easily', function () {
      expect(this.err.message).to.contain('Timing out script.');
    });
  });
});
<|endoftext|>


<commit_before>/* This module uses a give ip to visit the whole urls in the bucket assigned to it
*/
'use strict';

module.exports = function(bucketExplorer , database) {
	//
    var ObjectId = require('mongodb').ObjectId;
	var Spooky = require('spooky');
    var EventEmitter = require('events').EventEmitter;
    var domain = require('domain');

	var bucket;
    var ipQueue = [];
    var ipQueueIndex = 0;
    var child_processes = 0;
    var exitFlag = false;

    //
    var Buckets = database.model('Buckets');

    //
    var setBucket =  function(bucketObj){
    	 bucket = bucketObj;
         if(bucket.urls.length>0){
            for(var i=0; i<bucket.urls.length; i++){
                bucket.urls[i].visited = 0;
                bucket.urls[i].statusText = 'No status yet';
            };
         }

    	 console.log(bucket);
    	 console.log('bucket set in visitor');
         startUpdateDaemon();
    };

    //
    var getBucket = function(){
    	return bucket;
    };

    //
    var updateBucket = function(bucketObj , meta){
    	console.log(meta);
    	if(bucket){
            if(bucketObj._id+'' == bucket._id){
	           if(meta.action == 0){
	           	   console.log('Deleting url in this bucket');
                   bucket.urls.splice(meta.index , 1);
	           }
	           else if(meta.action == 1) {
                   console.log('Updating url in this bucket');
                   bucket = bucketObj;
	           }
	    	}
	    	else{
	    	   console.log('Just passing by');
	    	}
    	}
    	else{
    		 console.log('Bucket empty here');
    	}
    }

    //
    var notifyDelete = function(_id){
         console.log('Bucket with'+ _id +'deleted');
         if(bucket){
             if(bucket._id == _id){
                console.log('This bucket was deleted');
                setTimeout(function(){
                    process.exit(0);
                } , 10000);
             }
             else{
                 console.log('Not this one 1..');
             }

         }
         else{
            console.log('Not this one..');
         }
    }

    //
    var visitWith = function(ip){
        console.log('Adding %s to queue' , ip);
        if(ipQueue.indexOf(ip)<0){
            ipQueue.push(ip);
        }
        else{
            console.log('ip alredy used or currently in use');
        }

        if(ipQueue.length == 1){
            startVisitingDaemon();
        }
    }

    //
    var exitWhenDone = function(){
         exitFlag = true;
    }

    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    var V_WORKER = function(){
        var workerEvents = new EventEmitter;

        var visit = function(ip , urlsArr){
            console.log('visit starting with '+ip+' and '+urlsArr.length+' urls');

            let spooky = new Spooky({
                child: {
                    transport: 'http',
                    proxy: ip
                },

                casper: {
                    logLevel: 'debug',
                    verbose: true,
                    options: {
                        pageSettings: {
                            webSecurityEnabled: false
                        }
                    }
                }
            },
            function(err){
            if (err) {
                    e = new Error('Failed to initialize SpookyJS');
                    e.details = err;
                    throw e;
                }

                let worker = function(url , selector , index){
                    //
                    spooky.start(url);
                    spooky.then([{url:url , selector:selector , urlIndex:index} , function(){
                        //General case
                        this.done = function(err , status){
                            if(err){
                                this.emit('done', {status:this.getCurrentUrl() , index:urlIndex});
                                return phantom.clearCookies();
                            }
                            else{
                                this.emit('done', {status:this.getCurrentUrl() , index:urlIndex});
                                return phantom.clearCookies();
                            }
                        }

                        if(selector=='none'){
                            this.then(function(){
                                this.wait(15000 , function(){
                                    this.done(null , true);
                                });
                            });
                        }

                        else{
                            this.then(function(){
                                this.waitForSelector(selector , function(){
                                    this.thenClick(selector , function() {
                                       this.done();
                                    });

                                } , function(){
                                      this.done(true , null);
                                } , 20000);
                            });
                        }
                    }]);
                }

                //
                for(let i=0; i < urlsArr.length; i++){
                    worker(urlsArr[i].urlName , urlsArr[i].selector , i);
                }

                //
                spooky.run();
            });

            //
            spooky.on('console', function (line) {
                console.log(line);
            });

            //
            spooky.on('error', function (e, stack) {
                console.error(stack||e);
                workerEvents.emit('done' , {status:stack?stack:e , index:0});
            });

            //
            spooky.on('done', function (status) {
                workerEvents.emit('done' , status);
            });

            //self destruct this instance in 15 minutes
            setTimeout(function(){
                console.log('Instance destroyed');
                spooky.destroy();
                child_processes--;
            } , 5*60000);
        };

        return {
            visit:visit,
            status:workerEvents
        }
    }
    //++++++++++++++++++++++++++++++++END++++++++++++++++++++++++++++++++++++++++++

    //updateBucket after every 120 secs of activity
    function startUpdateDaemon(){
        var timer = 0;
        console.log('Starting cron daemon');
        setInterval(function(){
            bucket.lastActive = Date.now()+'';
            Buckets.update(
                {_id : ObjectId(bucket._id)},
                bucket,
                function(err , result){
                    if(err){
                        console.log(err);
                        res.status(500).send('Database error during cron update in visitor');
                    }
                    else {
                        timer+=2;
                        if(timer >= 122){
                            console.log('Maximum uptime of two hours exceeded exiting....');
                            process.exit(0);
                        }
                        console.log('bucket updated in cron job');
                    }
                }
          );
        } , 120000);
    }

    //
    function startVisitingDaemon(){
       let visiting = 0;
       let limit =  20;

       let v_worker = V_WORKER();

       function fillVisiting(){
            console.log('fill visiting called');
            if(ipQueueIndex < ipQueue.length){
               child_processes++;
               console.log(child_processes+' child_processes currently running');
               v_worker.visit(ipQueue[ipQueueIndex] , bucket.urls);
               ipQueueIndex++;
               visiting+= bucket.urls.length;
            }
            else{
                if(limit < visiting){
                    console.log('Visiting urls have exceeded the limit');
                    limit+=100;
                }
                else{
                    console.log('Good ip shortage');
                }
            }

            //
            setTimeout(function(){
                return fillVisiting();
            } , 1000);
       }

      //
      fillVisiting();

      //
      v_worker.status.on('done' , function(status){
            console.log(status);
            bucket.urls[status.index].statusText = status.status;
            bucket.urls[status.index].visited++;
            visiting--;
            limit--;

            if(exitFlag && ipQueueIndex >= ipQueue.length){
                console.log('All ips have been visited exiting process...');
                process.exit(0);
            }//
        });
    }

	return {
	    visitWith:visitWith,
	    getBucket:getBucket,
	    setBucket:setBucket,
	    updateBucket:updateBucket,
        notifyDelete:notifyDelete,
	    exitWhenDone:exitWhenDone
	}
};<commit_msg>update<commit_after>/* This module uses a give ip to visit the whole urls in the bucket assigned to it
*/
'use strict';

module.exports = function(bucketExplorer , database) {
	//
    var ObjectId = require('mongodb').ObjectId;
	var Spooky = require('spooky');
    var EventEmitter = require('events').EventEmitter;
    var domain = require('domain');

	var bucket;
    var ipQueue = [];
    var ipQueueIndex = 0;
    var child_processes = 0;
    var exitFlag = false;

    //
    var Buckets = database.model('Buckets');

    //
    var setBucket =  function(bucketObj){
    	 bucket = bucketObj;
         if(bucket.urls.length>0){
            for(var i=0; i<bucket.urls.length; i++){
                bucket.urls[i].visited = 0;
                bucket.urls[i].statusText = 'No status yet';
            };
         }

    	 console.log(bucket);
    	 console.log('bucket set in visitor');
         startUpdateDaemon();
    };

    //
    var getBucket = function(){
    	return bucket;
    };

    //
    var updateBucket = function(bucketObj , meta){
    	console.log(meta);
    	if(bucket){
            if(bucketObj._id+'' == bucket._id){
	           if(meta.action == 0){
	           	   console.log('Deleting url in this bucket');
                   bucket.urls.splice(meta.index , 1);
	           }
	           else if(meta.action == 1) {
                   console.log('Updating url in this bucket');
                   bucket = bucketObj;
	           }
	    	}
	    	else{
	    	   console.log('Just passing by');
	    	}
    	}
    	else{
    		 console.log('Bucket empty here');
    	}
    }

    //
    var notifyDelete = function(_id){
         console.log('Bucket with'+ _id +'deleted');
         if(bucket){
             if(bucket._id == _id){
                console.log('This bucket was deleted');
                setTimeout(function(){
                    process.exit(0);
                } , 10000);
             }
             else{
                 console.log('Not this one 1..');
             }

         }
         else{
            console.log('Not this one..');
         }
    }

    //
    var visitWith = function(ip){
        console.log('Adding %s to queue' , ip);
        if(ipQueue.indexOf(ip)<0){
            ipQueue.push(ip);
        }
        else{
            console.log('ip alredy used or currently in use');
        }

        if(ipQueue.length == 1){
            startVisitingDaemon();
        }
    }

    //
    var exitWhenDone = function(){
         exitFlag = true;
    }

    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    var V_WORKER = function(){
        var workerEvents = new EventEmitter;

        var visit = function(ip , urlsArr){
            console.log('visit starting with '+ip+' and '+urlsArr.length+' urls');

            let spooky = new Spooky({
                child: {
                    transport: 'http',
                    proxy: ip
                },

                casper: {
                    logLevel: 'debug',
                    verbose: true,
                    options: {
                        pageSettings: {
                            webSecurityEnabled: false
                        }
                    }
                }
            },
            function(err){
            if (err) {
                    e = new Error('Failed to initialize SpookyJS');
                    e.details = err;
                    throw e;
                }

                let worker = function(url , selector , index){
                    //
                    spooky.start(url);
                    spooky.then([{url:url , selector:selector , urlIndex:index} , function(){
                        //General case
                        this.done = function(err , status){
                            if(err){
                                this.emit('done', {status:this.getCurrentUrl() , index:urlIndex});
                                return phantom.clearCookies();
                            }
                            else{
                                this.emit('done', {status:this.getCurrentUrl() , index:urlIndex});
                                return phantom.clearCookies();
                            }
                        }

                        if(selector=='none'){
                            this.then(function(){
                                this.wait(15000 , function(){
                                    this.done(null , true);
                                });
                            });
                        }

                        else{
                            this.then(function(){
                                this.waitForSelector(selector , function(){
                                    this.thenClick(selector , function() {
                                       this.done();
                                    });

                                } , function(){
                                      this.done(true , null);
                                } , 20000);
                            });
                        }
                    }]);
                }

                //
                for(let i=0; i < urlsArr.length; i++){
                    worker(urlsArr[i].urlName , urlsArr[i].selector , i);
                }

                //
                spooky.run();
            });

            //
            spooky.on('console', function (line) {
                console.log(line);
            });

            //
            spooky.on('error', function (e, stack) {
                console.error(stack||e);
                workerEvents.emit('done' , {status:stack?stack:e , index:0});
            });

            //
            spooky.on('done', function (status) {
                workerEvents.emit('done' , status);
            });

            //self destruct this instance in 15 minutes
            setTimeout(function(){
                console.log('Instance destroyed');
                spooky.destroy();
                child_processes--;
            } , 5*60000);
        };

        return {
            visit:visit,
            status:workerEvents
        }
    }
    //++++++++++++++++++++++++++++++++END++++++++++++++++++++++++++++++++++++++++++

    //updateBucket after every 120 secs of activity
    function startUpdateDaemon(){
        var timer = 0;
        console.log('Starting cron daemon');
        setInterval(function(){
            bucket.lastActive = Date.now()+'';
            Buckets.update(
                {_id : ObjectId(bucket._id)},
                bucket,
                function(err , result){
                    if(err){
                        console.log(err);
                        res.status(500).send('Database error during cron update in visitor');
                    }
                    else {
                        timer+=2;
                        if(timer >= 122){
                            console.log('Maximum uptime of two hours exceeded exiting....');
                            process.exit(0);
                        }
                        console.log('bucket updated in cron job');
                    }
                }
          );
        } , 120000);
    }

    //
    function startVisitingDaemon(){
       let visiting = 0;
       let limit =  20;

       let v_worker = V_WORKER();

       function fillVisiting(){
            console.log('fill visiting called');
            if(ipQueueIndex < ipQueue.length){
               child_processes++;
               console.log(child_processes+' child_processes currently running');
               v_worker.visit(ipQueue[ipQueueIndex] , bucket.urls);
               ipQueueIndex++;
               visiting+= bucket.urls.length;
            }
            else{
                if(limit < visiting){
                    console.log('Visiting urls have exceeded the limit');
                    limit+=100;
                }
                else{
                    console.log('Good ip shortage');
                }
            }

            //
            setTimeout(function(){
                return fillVisiting();
            } , 10000);
       }

      //
      fillVisiting();

      //
      v_worker.status.on('done' , function(status){
            console.log(status);
            bucket.urls[status.index].statusText = status.status;
            bucket.urls[status.index].visited++;
            visiting--;
            limit--;

            if(exitFlag && ipQueueIndex >= ipQueue.length){
                console.log('All ips have been visited exiting process...');
                process.exit(0);
            }//
        });
    }

	return {
	    visitWith:visitWith,
	    getBucket:getBucket,
	    setBucket:setBucket,
	    updateBucket:updateBucket,
        notifyDelete:notifyDelete,
	    exitWhenDone:exitWhenDone
	}
};<|endoftext|>


<commit_before>/*
 * grunt-concat-seajs
 * https://github.com/zero-g/grunt-concat-seajs
 *
 * Copyright (c) 2015 zero-g
 * Licensed under the MIT license.
 */

'use strict';

module.exports = function(grunt) {
    var crypto = require('crypto');
    var path = require('path');
    var fs = require('fs');
    var findup = require('findup-sync');

    var profile = '支持 指定的seajs模块化文件的合并';
    grunt.registerMultiTask('concat_seajs', profile, function() {
        var options = this.options({
            map_file_name: 'fetch.js',
            seajs_src: '',
            baseDir: '',
            externalFile: false //选择生成js文件，还是嵌入到html
        });

        var mapFileSrc = path.join(options.seajs_src, options.map_file_name); //生成map文件的路径
        var code = createMapFile(mapFileSrc, options.baseDir); //general

        if(options.externalFile === true) {
            grunt.file.write(mapFileSrc, code);
            var mapFileMd5Name = md5File(mapFileSrc);
            grunt.log.writeln('map file for md5：', mapFileMd5Name);

            this.files.forEach(function(filePair) {
                filePair.src.forEach(function(file) {
                    appendMapFileToView(file, mapFileMd5Name);
                });
            });
        } else {
            this.files.forEach(function(filePair) {
                filePair.src.forEach(function(file) {
                    appendMapSourceToView(file, code);
                });
            });
        }
    });

    function md5File(file) {
        var MD5_LENGTH = 8; //set hash length, todo be the filerev md5 length config
        var hash = crypto.createHash('md5').update(fs.readFileSync(file)).digest('hex');
        var suffix = hash.slice(0, MD5_LENGTH);
        var newName = [path.basename(file, path.extname(file)), suffix, path.extname(file).slice(1)].join('.');

        var dirname = path.dirname(file);
        var resultPath = path.resolve(dirname, newName);

        fs.renameSync(file, resultPath);
        return newName;
    }

    function appendMapSourceToView(viewSrc, source) {
        var code = grunt.file.read(viewSrc);

        var seajsReg = /<script.*\/(sea[^(js)]*js)[^<]*<\/script>/i;
        var m = code.match(seajsReg);
        if (!m) {
            return;
        }
        var seaScript = m[0];
        var fetchScript = '<script>' + source + '</script>';
        code = code.replace(seaScript, seaScript + '\n' + fetchScript);

        grunt.file.write(viewSrc, code);
        grunt.log.writeln('append fetch source to：', viewSrc);
    }

    function appendMapFileToView(viewSrc, mapFileName) {
        var code = grunt.file.read(viewSrc);

        var seajsReg = /<script.*\/(sea[^(js)]*js)[^<]*<\/script>/i;
        var m = code.match(seajsReg);
        if (!m) {
            return;
        }
        var seaScript = m[0];
        var fetchScript = seaScript.replace(m[1], mapFileName);
        code = code.replace(seaScript, seaScript + '\n' + fetchScript);

        grunt.file.write(viewSrc, code);
        grunt.log.writeln('append fetch file to：', viewSrc);
    }

    function createMapFile(baseDir) {
        var filesMatch = getConcatFilesMatch(); //获取合并文件的md5映射表
        filesMatch = resolvePath(filesMatch, baseDir); //更正路径

        //生成map_file_name文件
        var code = createMapCode(filesMatch);
        return code;

        function getConcatFilesMatch() {
            var concFilesMath = {}; //文件合并到哪个文件
            var concFiles = null; //合并的文件
            var concTaskConf = grunt.config.get('concat');
            if (!concTaskConf) {
                grunt.log.write('没有进行合并');
            } else {
                concFiles = {};
                for (var conf in concTaskConf) {
                    var f = concTaskConf[conf].files;
                    for (var concFile in f) {
                        f[concFile].forEach(function(beConfFile) {
                            var tempFileDir = findup(beConfFile, {cwd: './'});
                            console.log(tempFileDir);
                            if (!tempFileDir) {
                                console.log('待合并的文件', beConfFile, '找不到');
                            } else {
                                beConfFile = tempFileDir;
                                concFilesMath[beConfFile] = concFile;
                                console.log(concFilesMath);
                                concFiles[concFile] = true;
                            }
                        });
                    }
                }
            }

            var summary = grunt.filerev ? grunt.filerev.summary: null;

            if (!summary) {
                grunt.log.write('没有进行md5');
            } else if (!concFiles) {
                //如果没有合并文件，那么md5的作为match
                concFilesMath = grunt.filerev.summary;
            } else {
                for (var src in summary) {
                    if (concFiles[src]) {
                        //如果是合并的文件，合并的文件，不被引用，不需要加入math
                        continue;
                    }

                    var absuluteSrc = path.resolve(src);
                    if (concFilesMath[src]) {
                        //如果该文件是被合并的文件，则修改合并文件的url
                        concFilesMath[src] = summary[concFilesMath[src]];
                    }
                    if (concFilesMath[absuluteSrc]) {
                        concFilesMath[src] = summary[concFilesMath[absuluteSrc]];
                        delete concFilesMath[absuluteSrc];
                    } else {
                        concFilesMath[src] = summary[src];
                    }
                }
            }

            return concFilesMath;
        }
        function resolvePath(concFilesMath, baseDir) {
            var resultMap = {};
            baseDir = path.normalize(baseDir);
            for (var src in concFilesMath) {
                resultMap[src.substr(baseDir.length)] = concFilesMath[src].substr(baseDir.length);

            }
            return resultMap;
        }
        function createMapCode(concFilesMath) {
            var FETCH_TEMPLATE = 'seajs.on("fetch", function(data) {\n' + '\tvar cfm = concFilesMath;\n' + '\tfor(var beConfFile in cfm) {\n' + '\t\tdata.requestUri = data.uri.replace(beConfFile, cfm[beConfFile]);\n' + '\t\tif(data.uri !== data.requestUri) { break;}\n' + '\t};\n' + '});';
            var code = FETCH_TEMPLATE.replace('concFilesMath', JSON.stringify(concFilesMath));
            return code;
        }

    }

};
<commit_msg>跟npm包代码同步<commit_after>/*
 * grunt-concat-seajs
 * https://github.com/zero-g/grunt-concat-seajs
 *
 * Copyright (c) 2015 zero-g
 * Licensed under the MIT license.
 */

'use strict';

module.exports = function(grunt) {
    var crypto = require('crypto');
    var path = require('path');
    var fs = require('fs');
    var findup = require('findup-sync');

    var profile = '支持 指定的seajs模块化文件的合并';
    grunt.registerMultiTask('concat_seajs', profile, function() {
        var options = this.options({
            map_file_name: 'fetch.js',
            seajs_src: '',
            baseDir: '',
            externalFile: false //选择生成js文件，还是嵌入到html
        });

        var mapFileSrc = path.join(options.seajs_src, options.map_file_name); //生成map文件的路径
        var code = createMapFile(options.baseDir); //general

        if(options.externalFile === true) {
            grunt.file.write(mapFileSrc, code);
            var mapFileMd5Name = md5File(mapFileSrc);
            grunt.log.writeln('map file for md5：', mapFileMd5Name);

            this.files.forEach(function(filePair) {
                filePair.src.forEach(function(file) {
                    appendMapFileToView(file, mapFileMd5Name);
                });
            });
        } else {
            this.files.forEach(function(filePair) {
                filePair.src.forEach(function(file) {
                    appendMapSourceToView(file, code);
                });
            });
        }
    });

    function md5File(file) {
        var MD5_LENGTH = 8; //set hash length, todo be the filerev md5 length config
        var hash = crypto.createHash('md5').update(fs.readFileSync(file)).digest('hex');
        var suffix = hash.slice(0, MD5_LENGTH);
        var newName = [path.basename(file, path.extname(file)), suffix, path.extname(file).slice(1)].join('.');

        var dirname = path.dirname(file);
        var resultPath = path.resolve(dirname, newName);

        fs.renameSync(file, resultPath);
        return newName;
    }

    function appendMapSourceToView(viewSrc, source) {
        var code = grunt.file.read(viewSrc);

        var seajsReg = /<script.*\/(sea[^(js)]*js)[^<]*<\/script>/i;
        var m = code.match(seajsReg);
        if (!m) {
            return;
        }
        var seaScript = m[0];
        var fetchScript = '<script>' + source + '</script>';
        code = code.replace(seaScript, seaScript + '\n' + fetchScript);

        grunt.file.write(viewSrc, code);
        grunt.log.writeln('append fetch source to：', viewSrc);
    }

    function appendMapFileToView(viewSrc, mapFileName) {
        var code = grunt.file.read(viewSrc);

        var seajsReg = /<script.*\/(sea[^(js)]*js)[^<]*<\/script>/i;
        var m = code.match(seajsReg);
        if (!m) {
            return;
        }
        var seaScript = m[0];
        var fetchScript = seaScript.replace(m[1], mapFileName);
        code = code.replace(seaScript, seaScript + '\n' + fetchScript);

        grunt.file.write(viewSrc, code);
        grunt.log.writeln('append fetch file to：', viewSrc);
    }

    function createMapFile(baseDir) {
        var filesMatch = getConcatFilesMatch(); //获取合并文件的md5映射表
        filesMatch = resolvePath(filesMatch, baseDir); //更正路径

        //生成map_file_name文件
        var code = createMapCode(filesMatch);
        return code;

        function getConcatFilesMatch() {
            var concFilesMath = {}; //文件合并到哪个文件
            var concFiles = null; //合并的文件
            var concTaskConf = grunt.config.get('concat');
            if (!concTaskConf) {
                grunt.log.write('没有进行合并');
            } else {
                concFiles = {};
                for (var conf in concTaskConf) {
                    var f = concTaskConf[conf].files;
                    for (var concFile in f) {
                        f[concFile].forEach(function(beConfFile) {
                            var tempFileDir = findup(beConfFile, {cwd: './'});
                            console.log(tempFileDir);
                            if (!tempFileDir) {
                                console.log('待合并的文件', beConfFile, '找不到');
                            } else {
                                beConfFile = tempFileDir;
                                concFilesMath[beConfFile] = concFile;
                                console.log(concFilesMath);
                                concFiles[concFile] = true;
                            }
                        });
                    }
                }
            }

            var summary = grunt.filerev ? grunt.filerev.summary: null;

            if (!summary) {
                grunt.log.write('没有进行md5');
            } else if (!concFiles) {
                //如果没有合并文件，那么md5的作为match
                concFilesMath = grunt.filerev.summary;
            } else {
                for (var src in summary) {
                    if (concFiles[src]) {
                        //如果是合并的文件，合并的文件，不被引用，不需要加入math
                        continue;
                    }

                    var absuluteSrc = path.resolve(src);
                    if (concFilesMath[src]) {
                        //如果该文件是被合并的文件，则修改合并文件的url
                        concFilesMath[src] = summary[concFilesMath[src]];
                    }
                    if (concFilesMath[absuluteSrc]) {
                        concFilesMath[src] = summary[concFilesMath[absuluteSrc]];
                        delete concFilesMath[absuluteSrc];
                    } else {
                        if(/\.js$/.test(src)) {
                            concFilesMath[src] = summary[src];
                        }
                    }
                }
            }

            return concFilesMath;
        }
        function resolvePath(concFilesMath, baseDir) {
            var resultMap = {};
            baseDir = path.normalize(baseDir);
            for (var src in concFilesMath) {
                resultMap[src.substr(baseDir.length)] = concFilesMath[src].substr(baseDir.length);

            }
            return resultMap;
        }
        function createMapCode(concFilesMath) {
            var FETCH_TEMPLATE = 'seajs.on("fetch", function(data) {\n' + '\tvar cfm = concFilesMath;\n' + '\tfor(var beConfFile in cfm) {\n' + '\t\tdata.requestUri = data.uri.replace(beConfFile, cfm[beConfFile]);\n' + '\t\tif(data.uri !== data.requestUri) { break;}\n' + '\t};\n' + '});';
            var code = FETCH_TEMPLATE.replace('concFilesMath', JSON.stringify(concFilesMath));
            return code;
        }

    }

};
<|endoftext|>


<commit_before>'use strict';
import Observer from 'binary-common-utils/observer';
import _ from 'underscore';
import _Symbol from './symbol';
import StrategyCtrl from './strategyCtrl';
import {getUTCTime, asyncChain} from 'binary-common-utils/tools';
import CustomApi from 'binary-common-utils/customApi';
import config from 'const';
import Translator from 'translator';

var Bot = function Bot(api) {
	if (Bot.instance) {
		return Bot.instance;
	}
	Bot.instance = this;
	this.ticks = [];
	if ( typeof api === 'undefined' ) {
		this.api = new CustomApi(null, this.recoverFromDisconnect.bind(this));
	} else {
		this.api = api;
	}
	this.observer = new Observer();
	this.translator = new Translator();
	this.symbol = new _Symbol(this.api);
	this.initPromise = this.symbol.initPromise;
	this.running = false;
	this.firstCall = true;
	this.authorizedToken = '';
	this.balanceStr = '';
	this.symbolStr = '';
	this.runningObservations = [];
	this.totalProfit = 0;
	this.totalRuns = 0;
	this.totalStake = 0;
	this.totalPayout = 0;
	this.balance = 0;
};

Bot.prototype = Object.create(null, {
	startOver: {
		value: function startOver(__startTrading){
			var that = this;
			this.firstCall = true;
			this.symbolStr = '';
			this.balanceStr = '';
			this.setTheInitialConditions().then(function(){
				if ( __startTrading ) {
					that._startTrading();
				}
			});
		}
	},
	recoverFromDisconnect: {
		value: function recoverFromDisconnect(){
			this.observer.emit('ui.log.warn', this.translator.translateText('Connection lost, recovering...'));
			this.api._originalApi.connect();
			for ( var i in this.runningObservations ) {
				this.observer.unregisterAll.apply(this.observer, this.runningObservations[i]);
			}
			this.runningObservations = [];
			var that = this;
			this.authorizedToken = '';
			if ( this.running ) {
				if ( this.strategyCtrl ) {
					this.login().then(function(){
						var strategyRecovered = function(result){
							if ( !result.tradeWasRunning ){
								that.startOver(true);
							} else {
								that.strategyCtrl.destroy();
								that.observer.unregister('strategy.finish', strategyRecovered);
								that.startOver(false);
								that._finish(result.finishedContract);
							}
						};
						that.observer.register('strategy.recovered', strategyRecovered, true);
						that.runningObservations.push(['strategy.recovered', strategyRecovered]);
						that.strategyCtrl.recoverFromDisconnect();
					});
				} else {
					this.login().then(function(){
						that.startOver(true);
					});
				}
			}
		}
	},
	setTradeOptions: {
		value: function setTradeOptions() {
			if (!_.isEmpty(this.tradeOption)) {
				this.pip = this.symbol.activeSymbols.getSymbols()[this.tradeOption.symbol].pip;
				var opposites = config.opposites[this.tradeOption.condition];
				this.tradeOptions = [];
				for (var key in opposites) {
					this.tradeOptions.push( _.extend(_.clone(this.tradeOption), {
							contract_type: Object.keys(opposites[key])[0]
						}));
					this.tradeOptions.slice(-1)[0].duration_unit = 't';
					delete this.tradeOptions.slice(-1)[0].condition;
				}
			} else {
				this.tradeOptions = [];
			}
		}
	},
	login: {
		value: function login(){
			var that = this;
			return new Promise(function(resolve, reject){
				var apiAuthorize = function(){
					that.authorizedToken = that.token;
					resolve();
				};
				that.observer.register('api.authorize', apiAuthorize, true, {
					type: 'authorize',
					unregister: [['api.authorize', apiAuthorize]]
				});
				that.api.authorize(that.token);
			});
		}
	},
	setTheInitialConditions: {
		value: function setTheInitialConditions(){
			var that = this;
			return new Promise(function(resolve, reject){
				asyncChain()
				.pipe(function(chainDone){
					if ( that.firstCall ) {
						that.firstCall = false;
						that._observeOnceAndForever();
					}
					chainDone();
				})
				.pipe(function(chainDone){
					if ( _.isEmpty(that.tradeOption) || that.tradeOption.symbol === that.symbolStr ) {
						chainDone();
					} else {
						var apiHistory = function(history){
							that.symbolStr = that.tradeOption.symbol;
							that.ticks = history;
							chainDone();
						};
						that.observer.register('api.history', apiHistory, true, {
							type: 'history',
							unregister: [['api.history', apiHistory], 'api.tick', 'bot.tickUpdate']
						});
						if ( that.tradeOption.symbol !== that.symbolStr ) {
							that.api._originalApi.unsubscribeFromAllTicks().then(function(){
								that.api.history(that.tradeOption.symbol, {
									end: 'latest',
									count: 600,
									subscribe: 1
								});
							});
						} else {
							that.api.history(that.tradeOption.symbol, {
								end: 'latest',
								count: 600,
								subscribe: 1
							});
						}
					}
				})
				.pipe(function(chainDone){
					resolve();
				})
				.exec();
			});
		}
	},
	start: {
		value: function start(token, tradeOption, strategy, finish, again){
			if ( !this.running || again ) {
				this.running = true;
			} else {
				return;
			}
			this.strategy = strategy;
			this.finish = finish;
			this.tradeOption = tradeOption;
			if ( again ) {
				this._startTrading();
				return;
			}
			this.token = token;
			var that = this;
			if ( this.authorizedToken === this.token ) {
				this.setTheInitialConditions().then(function(){
					that._startTrading();
				});
				return;
			}
			this.login().then(function(){
				that.setTheInitialConditions().then(function(){
					that._startTrading();
				});
			});
		}
	},
	_observeOnceAndForever: {
		value: function _observeOnceAndForever(){
		}
	},
	_observeStreams: {
		value: function _observeStreams(){
			var that = this;
			var apiTick = function(tick){
				that.ticks = that.ticks.concat(tick);
				that.ticks.splice(0,1);
				that.strategyCtrl.updateTicks(that.ticks);
				that.observer.emit('bot.tickUpdate', {
					ticks: that.ticks,
					pip: that.pip
				});
			};
			this.observer.register('api.tick', apiTick);
			this.runningObservations.push(['api.tick', apiTick]);
			var apiBalance = function(balance){
				that.balance = balance.balance;
				that.balanceStr = Number(balance.balance).toFixed(2) + ' ' + balance.currency;
				that.observer.emit('bot.tradeInfo', {
					balance: that.balanceStr
				});
			};
			this.observer.register('api.balance', apiBalance, false, {
				type: 'balance',
				unregister: [['api.balance', apiBalance]]
			});
			this.api._originalApi.send({
				forget_all: 'balance'
			}).then(function(){
				that.api.balance();
			});
		}
	},
	_subscribeProposal: {
		value: function _subscribeProposal(tradeOption) {
			var that = this;
			var apiProposal = function(proposal){
				that.strategyCtrl.updateProposal(proposal);
			};
			this.observer.register('api.proposal', apiProposal, false, {
				type: 'proposal',
				unregister: [
					['api.proposal', apiProposal],
					'strategy.ready',
					'bot.waiting_for_purchase'
				]
			});
			this.runningObservations.push(['api.proposal', apiProposal]);
			this.api.proposal(tradeOption);
		}
	},
	_subscribeProposals: {
		value: function _subscribeProposals() {
			var that = this;
			var strategyReady = function(){
				that.observer.emit('bot.waiting_for_purchase');
			};
			this.observer.register('strategy.ready', strategyReady);
			this.runningObservations.push(['strategy.ready', strategyReady]);
			this.setTradeOptions();
			this.api._originalApi.unsubscribeFromAllProposals().then(function(response){
				for ( var i in that.tradeOptions ) {
					that._subscribeProposal(that.tradeOptions[i]);
				}
			});
		}
	},
	_startTrading: {
		value: function _startTrading() {
			var that = this;
			var strategyTradeUpdate = function(contract){
				that.observer.emit('bot.tradeUpdate', contract);
			};
			var strategyFinish = function(contract){
				that.strategyCtrl.destroy();
				that._finish(contract);
			};
			var tradePurchase = function(){
				that.totalRuns += 1;
				that.observer.emit('bot.tradeInfo', {
					totalRuns: that.totalRuns
				});
			};
			this.observer.register('strategy.tradeUpdate', strategyTradeUpdate);
			this.observer.register('strategy.finish', strategyFinish, true);
			this.observer.register('trade.purchase', tradePurchase, true);
			this.runningObservations.push(['strategy.tradeUpdate', strategyTradeUpdate]);
			this.runningObservations.push(['strategy.finish', strategyFinish]);
			this.runningObservations.push(['trade.purchase', tradePurchase]);
			this._observeStreams();
			this.strategyCtrl = new StrategyCtrl(this.api, this.strategy);
			this._subscribeProposals();
		}
	},
	_updateTotals: {
		value: function _updateTotals(contract){
			var profit = +(Number(contract.sell_price) - Number(contract.buy_price)).toFixed(2);
			this.totalProfit = +(this.totalProfit + profit).toFixed(2);
			this.totalStake = +(this.totalStake + Number(contract.buy_price)).toFixed(2);
			this.totalPayout = +(this.totalPayout + Number(contract.sell_price)).toFixed(2);
			var that = this;
			this.observer.emit('bot.tradeInfo', {
				totalProfit: that.totalProfit,
				totalStake: that.totalStake,
				totalPayout: that.totalPayout
			});
		}
	},
	_createDetails: {
		value: function _createDetails(contract) {
			var result = (+contract.sell_price === 0) ? 'loss' : 'win';
			var profit = +(Number(contract.sell_price) - Number(contract.buy_price)).toFixed(2);
			return [
				contract.transaction_ids.buy, +contract.buy_price, +contract.sell_price,
				profit, contract.contract_type,
				getUTCTime(new Date(parseInt(contract.entry_tick_time + '000'))), +contract.entry_tick,
				getUTCTime(new Date(parseInt(contract.exit_tick_time + '000'))), +contract.exit_tick,
				+((contract.barrier) ? contract.barrier : 0), result
			];
		}
	},
	_finish: {
		value: function _finish(contract){
			for ( var i in this.runningObservations ) {
				this.observer.unregisterAll.apply(this.observer, this.runningObservations[i]);
			}
			this.runningObservations = [];
			this._updateTotals(contract);
			this.observer.emit('bot.finish', contract);
			this.running = false;
			this.finish(contract, this._createDetails(contract));
		}
	},
	stop: {
		value: function stop(contract){
			if ( !this.running ) {
				this.observer.emit('bot.stop', contract);
				return;
			}
			for ( var i in this.runningObservations ) {
				this.observer.unregisterAll.apply(this.observer, this.runningObservations[i]);
			}
			this.runningObservations = [];
			var that = this;
			asyncChain()
			.pipe(function(done){
				if ( that.strategyCtrl ) {
					var promise = that.strategyCtrl.destroy();
					if ( promise ) {
						promise.then(function(){
							done();
						});
					} else {
						done();
					}
				} else {
					done();
				}
			})
			.pipe(function(done){
				that.api._originalApi.unsubscribeFromAllProposals().then(function(response){
					done();
				});
			})
			.pipe(function(done){
				that.running = false;
				that.observer.emit('bot.stop', contract);
			})
			.exec();
		}
	}
});

module.exports = Bot;
<commit_msg>Added candles to the bot<commit_after>'use strict';
import Observer from 'binary-common-utils/observer';
import _ from 'underscore';
import _Symbol from './symbol';
import StrategyCtrl from './strategyCtrl';
import {getUTCTime, asyncChain} from 'binary-common-utils/tools';
import CustomApi from 'binary-common-utils/customApi';
import config from 'const';
import Translator from 'translator';

var Bot = function Bot(api) {
	if (Bot.instance) {
		return Bot.instance;
	}
	Bot.instance = this;
	this.ticks = [];
	if ( typeof api === 'undefined' ) {
		this.api = new CustomApi(null, this.recoverFromDisconnect.bind(this));
	} else {
		this.api = api;
	}
	this.observer = new Observer();
	this.translator = new Translator();
	this.symbol = new _Symbol(this.api);
	this.initPromise = this.symbol.initPromise;
	this.running = false;
	this.firstCall = true;
	this.authorizedToken = '';
	this.balanceStr = '';
	this.symbolStr = '';
	this.runningObservations = [];
	this.totalProfit = 0;
	this.totalRuns = 0;
	this.totalStake = 0;
	this.totalPayout = 0;
	this.balance = 0;
};

Bot.prototype = Object.create(null, {
	startOver: {
		value: function startOver(__startTrading){
			var that = this;
			this.firstCall = true;
			this.symbolStr = '';
			this.balanceStr = '';
			this.setTheInitialConditions().then(function(){
				if ( __startTrading ) {
					that._startTrading();
				}
			});
		}
	},
	recoverFromDisconnect: {
		value: function recoverFromDisconnect(){
			this.observer.emit('ui.log.warn', this.translator.translateText('Connection lost, recovering...'));
			this.api._originalApi.connect();
			for ( var i in this.runningObservations ) {
				this.observer.unregisterAll.apply(this.observer, this.runningObservations[i]);
			}
			this.runningObservations = [];
			var that = this;
			this.authorizedToken = '';
			if ( this.running ) {
				if ( this.strategyCtrl ) {
					this.login().then(function(){
						var strategyRecovered = function(result){
							if ( !result.tradeWasRunning ){
								that.startOver(true);
							} else {
								that.strategyCtrl.destroy();
								that.observer.unregister('strategy.finish', strategyRecovered);
								that.startOver(false);
								that._finish(result.finishedContract);
							}
						};
						that.observer.register('strategy.recovered', strategyRecovered, true);
						that.runningObservations.push(['strategy.recovered', strategyRecovered]);
						that.strategyCtrl.recoverFromDisconnect();
					});
				} else {
					this.login().then(function(){
						that.startOver(true);
					});
				}
			}
		}
	},
	setTradeOptions: {
		value: function setTradeOptions() {
			if (!_.isEmpty(this.tradeOption)) {
				this.pip = this.symbol.activeSymbols.getSymbols()[this.tradeOption.symbol].pip;
				var opposites = config.opposites[this.tradeOption.condition];
				this.tradeOptions = [];
				for (var key in opposites) {
					this.tradeOptions.push( _.extend(_.clone(this.tradeOption), {
							contract_type: Object.keys(opposites[key])[0]
						}));
					this.tradeOptions.slice(-1)[0].duration_unit = 't';
					delete this.tradeOptions.slice(-1)[0].condition;
				}
			} else {
				this.tradeOptions = [];
			}
		}
	},
	login: {
		value: function login(){
			var that = this;
			return new Promise(function(resolve, reject){
				var apiAuthorize = function(){
					that.authorizedToken = that.token;
					resolve();
				};
				that.observer.register('api.authorize', apiAuthorize, true, {
					type: 'authorize',
					unregister: [['api.authorize', apiAuthorize]]
				});
				that.api.authorize(that.token);
			});
		}
	},
	subscribeToCandles: {
		value: function subscribeToCandles(){
			this.api.history(this.tradeOption.symbol, {
				end: 'latest',
				count: 600,
				granularity: 60,
				style: "candles",
				subscribe: 1
			});
		}
	},
	subscribeToTicks: {
		value: function subscribeToTicks(done){
			var that = this;
			if ( _.isEmpty(that.tradeOption) || that.tradeOption.symbol === that.symbolStr ) {
				done();
			} else {
				var apiHistory = function(history){
					that.symbolStr = that.tradeOption.symbol;
					that.ticks = history;
					that.subscribeToCandles();
					done();
				};
				that.observer.register('api.history', apiHistory, true, {
					type: 'history',
					unregister: [['api.history', apiHistory], 'api.tick', 'bot.tickUpdate']
				});
				if ( that.tradeOption.symbol !== that.symbolStr ) {
					that.api._originalApi.unsubscribeFromAllTicks().then(function(){
						that.api.history(that.tradeOption.symbol, {
							end: 'latest',
							count: 600,
							subscribe: 1
						});
					});
				} else {
					that.api.history(that.tradeOption.symbol, {
						end: 'latest',
						count: 600,
						subscribe: 1
					});
				}
			}
		}
	},
	setTheInitialConditions: {
		value: function setTheInitialConditions(){
			var that = this;
			return new Promise(function(resolve, reject){
				asyncChain()
				.pipe(function(chainDone){
					if ( that.firstCall ) {
						that.firstCall = false;
						that._observeOnceAndForever();
					}
					chainDone();
				})
				.pipe(function(chainDone){
					that.subscribeToTicks(chainDone);
				})
				.pipe(function(chainDone){
					resolve();
				})
				.exec();
			});
		}
	},
	start: {
		value: function start(token, tradeOption, strategy, finish, again){
			if ( !this.running || again ) {
				this.running = true;
			} else {
				return;
			}
			this.strategy = strategy;
			this.finish = finish;
			this.tradeOption = tradeOption;
			if ( again ) {
				this._startTrading();
				return;
			}
			this.token = token;
			var that = this;
			if ( this.authorizedToken === this.token ) {
				this.setTheInitialConditions().then(function(){
					that._startTrading();
				});
				return;
			}
			this.login().then(function(){
				that.setTheInitialConditions().then(function(){
					that._startTrading();
				});
			});
		}
	},
	_observeOnceAndForever: {
		value: function _observeOnceAndForever(){
		}
	},
	_observeTicks: {
		value: function _observeTicks(){
			var that = this;
			var apiTick = function(tick){
				that.ticks = that.ticks.concat(tick);
				that.ticks.splice(0,1);
				that.strategyCtrl.updateTicks(that.ticks);
				that.observer.emit('bot.tickUpdate', {
					ticks: that.ticks,
					pip: that.pip
				});
			};
			this.observer.register('api.tick', apiTick);
			this.runningObservations.push(['api.tick', apiTick]);
		}
	},
	_observeBalance: {
		value: function _observeBalance(){
			var that = this;
			var apiBalance = function(balance){
				that.balance = balance.balance;
				that.balanceStr = Number(balance.balance).toFixed(2) + ' ' + balance.currency;
				that.observer.emit('bot.tradeInfo', {
					balance: that.balanceStr
				});
			};
			this.observer.register('api.balance', apiBalance, false, {
				type: 'balance',
				unregister: [['api.balance', apiBalance]]
			});
			this.api._originalApi.send({
				forget_all: 'balance'
			}).then(function(){
				that.api.balance();
			});
		}
	},
	_observeCandles: {
		value: function _observeCandles(){
			var that = this;
			var apiCandles = function(candle){
				console.log(candle);
				that.candles = that.candles.concat(candle);
				that.candles.splice(0,1);
				//that.strategyCtrl.updateCandless(that.candles);
				that.observer.emit('bot.candleUpdate', {
					candles: that.candles,
					pip: that.pip
				});
			};
			this.observer.register('api.ohlc', apiCandles);
			this.runningObservations.push(['api.ohlc', apiCandles]);
		}
	},
	_observeStreams: {
		value: function _observeStreams(){
			this._observeTicks();
			this._observeCandles();
			this._observeBalance();
		}
	},
	_subscribeProposal: {
		value: function _subscribeProposal(tradeOption) {
			var that = this;
			var apiProposal = function(proposal){
				that.strategyCtrl.updateProposal(proposal);
			};
			this.observer.register('api.proposal', apiProposal, false, {
				type: 'proposal',
				unregister: [
					['api.proposal', apiProposal],
					'strategy.ready',
					'bot.waiting_for_purchase'
				]
			});
			this.runningObservations.push(['api.proposal', apiProposal]);
			this.api.proposal(tradeOption);
		}
	},
	_subscribeProposals: {
		value: function _subscribeProposals() {
			var that = this;
			var strategyReady = function(){
				that.observer.emit('bot.waiting_for_purchase');
			};
			this.observer.register('strategy.ready', strategyReady);
			this.runningObservations.push(['strategy.ready', strategyReady]);
			this.setTradeOptions();
			this.api._originalApi.unsubscribeFromAllProposals().then(function(response){
				for ( var i in that.tradeOptions ) {
					that._subscribeProposal(that.tradeOptions[i]);
				}
			});
		}
	},
	_startTrading: {
		value: function _startTrading() {
			var that = this;
			var strategyTradeUpdate = function(contract){
				that.observer.emit('bot.tradeUpdate', contract);
			};
			var strategyFinish = function(contract){
				that.strategyCtrl.destroy();
				that._finish(contract);
			};
			var tradePurchase = function(){
				that.totalRuns += 1;
				that.observer.emit('bot.tradeInfo', {
					totalRuns: that.totalRuns
				});
			};
			this.observer.register('strategy.tradeUpdate', strategyTradeUpdate);
			this.observer.register('strategy.finish', strategyFinish, true);
			this.observer.register('trade.purchase', tradePurchase, true);
			this.runningObservations.push(['strategy.tradeUpdate', strategyTradeUpdate]);
			this.runningObservations.push(['strategy.finish', strategyFinish]);
			this.runningObservations.push(['trade.purchase', tradePurchase]);
			this._observeStreams();
			this.strategyCtrl = new StrategyCtrl(this.api, this.strategy);
			this._subscribeProposals();
		}
	},
	_updateTotals: {
		value: function _updateTotals(contract){
			var profit = +(Number(contract.sell_price) - Number(contract.buy_price)).toFixed(2);
			this.totalProfit = +(this.totalProfit + profit).toFixed(2);
			this.totalStake = +(this.totalStake + Number(contract.buy_price)).toFixed(2);
			this.totalPayout = +(this.totalPayout + Number(contract.sell_price)).toFixed(2);
			var that = this;
			this.observer.emit('bot.tradeInfo', {
				totalProfit: that.totalProfit,
				totalStake: that.totalStake,
				totalPayout: that.totalPayout
			});
		}
	},
	_createDetails: {
		value: function _createDetails(contract) {
			var result = (+contract.sell_price === 0) ? 'loss' : 'win';
			var profit = +(Number(contract.sell_price) - Number(contract.buy_price)).toFixed(2);
			return [
				contract.transaction_ids.buy, +contract.buy_price, +contract.sell_price,
				profit, contract.contract_type,
				getUTCTime(new Date(parseInt(contract.entry_tick_time + '000'))), +contract.entry_tick,
				getUTCTime(new Date(parseInt(contract.exit_tick_time + '000'))), +contract.exit_tick,
				+((contract.barrier) ? contract.barrier : 0), result
			];
		}
	},
	_finish: {
		value: function _finish(contract){
			for ( var i in this.runningObservations ) {
				this.observer.unregisterAll.apply(this.observer, this.runningObservations[i]);
			}
			this.runningObservations = [];
			this._updateTotals(contract);
			this.observer.emit('bot.finish', contract);
			this.running = false;
			this.finish(contract, this._createDetails(contract));
		}
	},
	stop: {
		value: function stop(contract){
			if ( !this.running ) {
				this.observer.emit('bot.stop', contract);
				return;
			}
			for ( var i in this.runningObservations ) {
				this.observer.unregisterAll.apply(this.observer, this.runningObservations[i]);
			}
			this.runningObservations = [];
			var that = this;
			asyncChain()
			.pipe(function(done){
				if ( that.strategyCtrl ) {
					var promise = that.strategyCtrl.destroy();
					if ( promise ) {
						promise.then(function(){
							done();
						});
					} else {
						done();
					}
				} else {
					done();
				}
			})
			.pipe(function(done){
				that.api._originalApi.unsubscribeFromAllProposals().then(function(response){
					done();
				});
			})
			.pipe(function(done){
				that.running = false;
				that.observer.emit('bot.stop', contract);
			})
			.exec();
		}
	}
});

module.exports = Bot;
<|endoftext|>


<commit_before>var marked = require("marked")
var renderer = new marked.Renderer()
var options = require("../cache/options")
var accents = {
  "é" : "e",
  "è" : "e",
  "ê" : "e",
  "à" : "a",
  "ù" : "u",
  "ô" : "o"
}
var accentsRE = RegExp("(" + Object.keys(accents).join("|") + ")", "g")

marked.setOptions({
  highlight : function(code){
    return require("highlight.js").highlightAuto(code).value;
  },
  renderer : renderer,
  smartypants : true
})

/**
 * adjust image path to have by default path from images/posts
 */
renderer.image = function(href, title, text){
  if(href.indexOf("http") !== 0) {
    href = options.value.locals.uri("images/posts/" + href, options.value.locals.page)
  }
  var output = "" : ">"
  return output
}

renderer.heading = function(text, level){
  var escaped = text.toLowerCase()
    .replace(accentsRE, function(i){
      return accents[i]
    })
    .replace(/\W+/g, "-")

  return "<h" + level + " id=\"" + escaped + "\">" +
    "#" +
    text + "</h" + level + ">"
}

module.exports = marked
<commit_msg>moare accents (just au cas zou)<commit_after>var marked = require("marked")
var renderer = new marked.Renderer()
var options = require("../cache/options")
var accents = {
  "à" : "a",
  "á" : "a",
  "â" : "a",
  "ã" : "a",
  "ä" : "a",
  "å" : "a",
  "æ" : "ae",
  "ç" : "c",
  "è" : "e",
  "é" : "e",
  "ê" : "e",
  "ë" : "e",
  "ì" : "i",
  "í" : "i",
  "î" : "i",
  "ï" : "i",
  "ñ" : "n",
  "ò" : "o",
  "ó" : "o",
  "ô" : "o",
  "õ" : "o",
  "ö" : "o",
  "ø" : "o",
  "ù" : "u",
  "ú" : "u",
  "û" : "u",
  "ü" : "u",
  "ý" : "y"
}
var accentsRE = RegExp("(" + Object.keys(accents).join("|") + ")", "g")

marked.setOptions({
  highlight : function(code){
    return require("highlight.js").highlightAuto(code).value;
  },
  renderer : renderer,
  smartypants : true
})

/**
 * adjust image path to have by default path from images/posts
 */
renderer.image = function(href, title, text){
  if(href.indexOf("http") !== 0) {
    href = options.value.locals.uri("images/posts/" + href, options.value.locals.page)
  }
  var output = "" : ">"
  return output
}

renderer.heading = function(text, level){
  var escaped = text.toLowerCase()
    .replace(accentsRE, function(i){
      return accents[i]
    })
    .replace(/\W+/g, "-")

  return "<h" + level + " id=\"" + escaped + "\">" +
    "#" +
    text + "</h" + level + ">"
}

module.exports = marked
<|endoftext|>