<commit_before>var memoizeAsync = require('../lib/memoizeAsync'),
    LRUCache = require('lru-cache'),
    expect = require('unexpected').clone().installPlugin(require('unexpected-sinon')),
    passError = require('passerror'),
    async = require('async'),
    sinon = require('sinon');

describe('memoizeAsync', function () {
    it('on a zero-param function should keep returning the same result', function (done) {
        var nextNumber = 1,
            memoizedGetNextNumber = memoizeAsync(function getNextNumber(cb) {
                process.nextTick(function () {
                    cb(null, nextNumber++);
                });
            });

        memoizedGetNextNumber(function (err, nextNumber) {
            expect(nextNumber, 'to equal', 1);
            memoizedGetNextNumber(function (err, nextNextNumber) {
                expect(nextNextNumber, 'to equal', 1);
                done();
            });
        });
    });

    it('on a multi-param function should only return the same result when the parameters are the same', function (done) {
        var nextNumber = 1,
            memoizedSumOfOperandsPlusNextNumber = memoizeAsync(function sumOfOperandsPlusNextNumber(op1, op2, cb) {
                process.nextTick(function () {
                    cb(null, op1 + op2 + nextNumber++);
                });
            });

        memoizedSumOfOperandsPlusNextNumber(10, 10, passError(done, function (sumPlusNextNumber) {
            expect(sumPlusNextNumber, 'to equal', 21);
            expect(memoizedSumOfOperandsPlusNextNumber.peek(10, 10)[1], 'to equal', 21);
            memoizedSumOfOperandsPlusNextNumber(10, 10, passError(done, function (sumPlusNextNextNumber) {
                expect(sumPlusNextNextNumber, 'to equal', 21);
                memoizedSumOfOperandsPlusNextNumber(10, 20, passError(done, function (sumPlusNextNextNextNumber) {
                    expect(sumPlusNextNextNextNumber, 'to equal', 32);
                    memoizedSumOfOperandsPlusNextNumber.purge(10, 20);
                    memoizedSumOfOperandsPlusNextNumber(10, 20, passError(done, function (number) {
                        expect(number, 'to equal', 33);
                        memoizedSumOfOperandsPlusNextNumber(10, 10, passError(done, function (number) {
                            expect(number, 'to equal', 21);
                            memoizedSumOfOperandsPlusNextNumber.purgeAll();
                            memoizedSumOfOperandsPlusNextNumber(10, 20, passError(done, function (number) {
                                expect(number, 'to equal', 34);
                                memoizedSumOfOperandsPlusNextNumber(10, 10, passError(done, function (number) {
                                    expect(number, 'to equal', 25);
                                    done();
                                }));
                            }));
                        }));
                    }));
                }));
            }));
        }));
    });

    it('should produce a function that works as a method', function (done) {
        function Counter() {
            this.nextNumber = 1;
        }

        Counter.prototype.getNextNumber = memoizeAsync(function (cb) {
            var that = this;
            process.nextTick(function () {
                cb(null, that.nextNumber++);
            });
        });

        var counter = new Counter();

        counter.getNextNumber(passError(done, function (nextNumber) {
            expect(nextNumber, 'to equal', 1);
            expect(counter.nextNumber, 'to equal', 2);
            counter.getNextNumber(passError(done, function (nextNextNumber) {
                expect(nextNextNumber, 'to equal', 1);
                expect(counter.nextNumber, 'to equal', 2);
                done();
            }));
        }));
    });

    it('should deliver the same result to multiple callbacks that are queued before the result is available', function (done) {
        var nextNumber = 1,
            memoizedGetNextNumber = memoizeAsync(function getNextNumber(cb) {
                process.nextTick(function () {
                    cb(null, nextNumber++);
                });
            });

        var results = [];
        function receiveResultAndProceedIfReady(err, number) {
            results.push(number);
            if (results.length === 2) {
                expect(results[0], 'to equal', 1);
                expect(results[1], 'to equal', 1);
                done();
            }
        }
        memoizedGetNextNumber(receiveResultAndProceedIfReady);
        memoizedGetNextNumber(receiveResultAndProceedIfReady);
    });

    it('should work with a custom argumentsStringifier', function (done) {
        function toCanonicalJson(obj) {
            return JSON.stringify(function traverseAndSortKeys(obj) {
                if (Array.isArray(obj)) {
                    return obj.map(traverseAndSortKeys);
                } else if (typeof obj === 'object' && obj !== null) {
                    var resultObj = {};
                    Object.keys(obj).sort().forEach(function (key) {
                        resultObj[key] = traverseAndSortKeys(obj[key]);
                    });
                    return resultObj;
                } else {
                    return obj;
                }
            }(obj));
        }

        var nextNumber = 1,
            memoizedGetNextNumber = memoizeAsync(function getNextNumber(obj, cb) {
                process.nextTick(function () {
                    cb(null, nextNumber++);
                });
            }, {
                argumentsStringifier: function (args) {
                    return args.map(toCanonicalJson).join('\x1d');
                }
            });

        memoizedGetNextNumber({foo: 'bar', quux: 'baz'}, passError(done, function (nextNumber) {
            expect(nextNumber, 'to equal', 1);
            memoizedGetNextNumber({quux: 'baz', foo: 'bar'}, passError(done, function (nextNumber) {
                expect(nextNumber, 'to equal', 1);
                memoizedGetNextNumber({barf: 'baz'}, passError(done, function (nextNumber) {
                    expect(nextNumber, 'to equal', 2);
                    done();
                }));
            }));
        }));
    });

    it('with a maxAge should recompute the value after an item has become stale', function (done) {
        var nextNumber = 1,
            memoizedGetNextNumber = memoizeAsync(function getNextNumber(cb) {
                process.nextTick(function () {
                    cb(null, nextNumber++);
                });
            }, {maxAge: 10});

        memoizedGetNextNumber(passError(done, function (nextNumber) {
            expect(nextNumber, 'to equal', 1);
            memoizedGetNextNumber(passError(done, function (nextNumber) {
                expect(nextNumber, 'to equal', 1);
                setTimeout(function () {
                    memoizedGetNextNumber(passError(done, function (nextNumber) {
                        expect(nextNumber, 'to equal', 2);
                        done();
                    }));
                }, 15);
            }));
        }));
    });

    it('with a max limit should purge the least recently used result', function (done) {
        var nextNumber = 1,
            memoizedGetNextNumberPlusOtherNumber = memoizeAsync(function getNextNumber(otherNumber, cb) {
                process.nextTick(function () {
                    cb(null, otherNumber + (nextNumber++));
                });
            }, {max: 2});

        memoizedGetNextNumberPlusOtherNumber(1, passError(done, function (nextNumberPlusOne) {
            expect(nextNumberPlusOne, 'to equal', 2);
            memoizedGetNextNumberPlusOtherNumber(2, passError(done, function (nextNumberPlusTwo) {
                expect(nextNumberPlusTwo, 'to equal', 4);
                memoizedGetNextNumberPlusOtherNumber(1, passError(done, function (nextNumberPlusOneAgain) {
                    expect(nextNumberPlusOne, 'to equal', 2);
                    // This will purge memoizedGetNextNumberPlusOtherNumber(2, ...):
                    memoizedGetNextNumberPlusOtherNumber(3, passError(done, function (nextNumberPlusThree) {
                        expect(nextNumberPlusThree, 'to equal', 6);
                        memoizedGetNextNumberPlusOtherNumber(2, passError(done, function (nextNumberPlusTwoAgain) {
                            expect(nextNumberPlusTwoAgain, 'to equal', 6);
                            done();
                        }));
                    }));
                }));
            }));
        }));
    });

    it('with a length function should call the length function with the result callback parameters as regular arguments', function (done) {
        var shouldErrorNextTime = false,
            functionThatErrorsEverySecondTime = function (number, cb) {
                process.nextTick(function () {
                    if (shouldErrorNextTime) {
                        cb(new Error());
                    } else {
                        cb(null, 'the result');
                    }
                    shouldErrorNextTime = !shouldErrorNextTime;
                });
            },
            memoizedFunctionThatErrorsEverySecondTime = memoizeAsync(functionThatErrorsEverySecondTime, {
                length: function (err, result) {
                    return err ? 1 : result.length;
                },
                errors: true
            });
        memoizedFunctionThatErrorsEverySecondTime(1, passError(done, function (result) {
            expect(result, 'to equal', 'the result');
            expect(memoizedFunctionThatErrorsEverySecondTime.cache.length, 'to equal', 10);
            memoizedFunctionThatErrorsEverySecondTime(2, function (err, result2) {
                expect(err, 'to be an', Error);
                expect(result2, 'to be undefined');
                expect(memoizedFunctionThatErrorsEverySecondTime.cache.length, 'to equal', 11);
                done();
            });
        }));
    });

    it('should leave unrelated values in the cache when purgeAll is called', function (done) {
        var memoizedAsyncSum = memoizeAsync(function asyncSum(a, b, cb) {
            process.nextTick(function () {
                cb(null, a + b);
            });
        });
        var cache = memoizedAsyncSum.cache;
        memoizedAsyncSum(1, 2, passError(done, function (sum) {
            expect(sum, 'to equal', 3);
            expect(cache.keys().length, 'to equal', 1);
            cache.set('foo', 'bar');
            expect(cache.keys().length, 'to equal', 2);
            memoizedAsyncSum.purgeAll();
            expect(cache.keys().length, 'to equal', 1);
            expect(cache.get('foo'), 'to equal', 'bar');
            done();
        }));
    });

    it('should allow passing an existing lru-cache instance in the options object', function (done) {
        function asyncSum(a, b, cb) {
            process.nextTick(function () {
                cb(null, a + b);
            });
        }
        var cache = new LRUCache(),
            memoizedAsyncSum1 = memoizeAsync(asyncSum, {cache: cache});
        expect(memoizedAsyncSum1.cache, 'to be', cache);

        var memoizedAsyncSum2 = memoizeAsync(asyncSum, {cache: cache});
        expect(memoizedAsyncSum2.cache, 'to be', cache);
        memoizedAsyncSum1(1, 2, passError(done, function (sum) {
            expect(sum, 'to equal', 3);
            expect(cache.keys().length, 'to equal', 1);
            expect(cache.get(memoizedAsyncSum1.cacheKeyPrefix + memoizedAsyncSum1.argumentsStringifier([1, 2])), 'to equal', [null, 3]);
            memoizedAsyncSum2(1, 2, passError(done, function (sum) {
                expect(sum, 'to equal', 3);
                expect(cache.keys().length, 'to equal', 2);
                done();
            }));
        }));
    });

    it('should allow specifying a custom cacheKeyPrefix', function (done) {
        var memoizedAsyncSum = memoizeAsync(function (a, b, cb) {
            process.nextTick(function () {
                cb(null, a + b);
            });
        }, {
            cacheKeyPrefix: 999
        });

        expect(memoizedAsyncSum.cacheKeyPrefix, 'to equal', '999');

        memoizedAsyncSum(1, 2, passError(done, function (sum) {
            expect(sum, 'to equal', 3);
            expect(memoizedAsyncSum.cache.get('999' + memoizedAsyncSum.argumentsStringifier([1, 2])), 'to equal', [null, 3]);
            done();
        }));
    });

    it('should call the memoized function in options.context if specified', function (done) {
        var memoizedFunction = memoizeAsync(function (a, cb) {
            var sum = this.foo + a;
            process.nextTick(function () {
                cb(null, sum);
            });
        }, {context: {foo: 4}});
        memoizedFunction(8, passError(done, function (sum) {
            expect(sum, 'to equal', 12);
            done();
        }));
    });

    it('should not memoize errors per default', function (done) {
        var memoizedFunction = memoizeAsync(function (cb) {
            process.nextTick(function () {
                cb(new Error('foo'));
            });
        });
        memoizedFunction(function (err) {
            expect(err, 'to be an', Error);
            expect(err.message, 'to equal', 'foo');
            memoizedFunction(function (err2) {
                expect(err2, 'to be an', Error);
                expect(err2.message, 'to equal', 'foo');
                expect(err2, 'not to be', err);
                done();
            });
        });
    });

    it('should memoize errors if the `errors` option is set to true', function (done) {
        var memoizedFunction = memoizeAsync(function (cb) {
            process.nextTick(function () {
                cb(new Error('foo'));
            });
        }, {errors: true});
        memoizedFunction(function (err) {
            expect(err, 'to be an', Error);
            expect(err.message, 'to equal', 'foo');
            memoizedFunction(function (err2) {
                expect(err2, 'to be an', Error);
                expect(err2.message, 'to equal', 'foo');
                expect(err2, 'to be', err);
                done();
            });
        });
    });

    describe('refreshTime', function () {
        var clock;
        var currentTick = 0;

        function gotoTick(n) {
            var toTick = n - currentTick;
            currentTick = n;
            clock.tick(toTick);
        }

        beforeEach(function () {
            currentTick = 0;
            clock = sinon.useFakeTimers();
        });
        afterEach(function () {
            clock.restore();
        });
        it('should refresh async but return stale value immediately', function (done) {
            var nextNumber = 1,
                method = sinon.spy(function getNextNumber(cb) {
                    setTimeout(function () {
                        cb(null, nextNumber);
                        nextNumber += 1;
                    }, 5);
                }),
                memoizedGetNextNumber = memoizeAsync(method, { maxAge: 20, refreshAge: 10 });

            async.series([
                function (callback) {
                    // initial call
                    memoizedGetNextNumber(passError(callback, function (nextNumber) {
                        expect(nextNumber, 'to be', 1);
                        expect(method, 'was called once');
                        gotoTick(10);
                        callback();
                    }));
                    // going to tick 6 to allow it to finish
                    gotoTick(6);
                },
                function (callback) {
                    // still below refreshAge
                    memoizedGetNextNumber(passError(callback, function (nextNumber) {
                        expect(nextNumber, 'to be', 1);
                        expect(method, 'was called once');
                        gotoTick(17);
                        callback();
                    }));
                },
                function (callback) {
                    // above refreshAge below maxAge immediate result
                    memoizedGetNextNumber(passError(callback, function (nextNumber) {
                        expect(nextNumber, 'to be', 1);
                        expect(method, 'was called twice');
                        gotoTick(27);
                        callback();
                    }));
                },
                function (callback) {
                    // above initial maxAge but immediate result because refresh
                    memoizedGetNextNumber(passError(callback, function (nextNumber) {
                        expect(nextNumber, 'to be', 2);
                        expect(method, 'was called twice');
                        callback();
                    }));
                }
            ], done);
        });
    });
});
<commit_msg>Test: Avoid ++<commit_after>var memoizeAsync = require('../lib/memoizeAsync'),
    LRUCache = require('lru-cache'),
    expect = require('unexpected').clone().installPlugin(require('unexpected-sinon')),
    passError = require('passerror'),
    async = require('async'),
    sinon = require('sinon');

describe('memoizeAsync', function () {
    it('on a zero-param function should keep returning the same result', function (done) {
        var nextNumber = 1,
            memoizedGetNextNumber = memoizeAsync(function getNextNumber(cb) {
                process.nextTick(function () {
                    cb(null, nextNumber);
                    nextNumber += 1;
                });
            });

        memoizedGetNextNumber(function (err, nextNumber) {
            expect(nextNumber, 'to equal', 1);
            memoizedGetNextNumber(function (err, nextNextNumber) {
                expect(nextNextNumber, 'to equal', 1);
                done();
            });
        });
    });

    it('on a multi-param function should only return the same result when the parameters are the same', function (done) {
        var nextNumber = 1,
            memoizedSumOfOperandsPlusNextNumber = memoizeAsync(function sumOfOperandsPlusNextNumber(op1, op2, cb) {
                process.nextTick(function () {
                    cb(null, op1 + op2 + nextNumber);
                    nextNumber += 1;
                });
            });

        memoizedSumOfOperandsPlusNextNumber(10, 10, passError(done, function (sumPlusNextNumber) {
            expect(sumPlusNextNumber, 'to equal', 21);
            expect(memoizedSumOfOperandsPlusNextNumber.peek(10, 10)[1], 'to equal', 21);
            memoizedSumOfOperandsPlusNextNumber(10, 10, passError(done, function (sumPlusNextNextNumber) {
                expect(sumPlusNextNextNumber, 'to equal', 21);
                memoizedSumOfOperandsPlusNextNumber(10, 20, passError(done, function (sumPlusNextNextNextNumber) {
                    expect(sumPlusNextNextNextNumber, 'to equal', 32);
                    memoizedSumOfOperandsPlusNextNumber.purge(10, 20);
                    memoizedSumOfOperandsPlusNextNumber(10, 20, passError(done, function (number) {
                        expect(number, 'to equal', 33);
                        memoizedSumOfOperandsPlusNextNumber(10, 10, passError(done, function (number) {
                            expect(number, 'to equal', 21);
                            memoizedSumOfOperandsPlusNextNumber.purgeAll();
                            memoizedSumOfOperandsPlusNextNumber(10, 20, passError(done, function (number) {
                                expect(number, 'to equal', 34);
                                memoizedSumOfOperandsPlusNextNumber(10, 10, passError(done, function (number) {
                                    expect(number, 'to equal', 25);
                                    done();
                                }));
                            }));
                        }));
                    }));
                }));
            }));
        }));
    });

    it('should produce a function that works as a method', function (done) {
        function Counter() {
            this.nextNumber = 1;
        }

        Counter.prototype.getNextNumber = memoizeAsync(function (cb) {
            var that = this;
            process.nextTick(function () {
                cb(null, that.nextNumber);
                that.nextNumber += 1;
            });
        });

        var counter = new Counter();

        counter.getNextNumber(passError(done, function (nextNumber) {
            expect(nextNumber, 'to equal', 1);
            expect(counter.nextNumber, 'to equal', 2);
            counter.getNextNumber(passError(done, function (nextNextNumber) {
                expect(nextNextNumber, 'to equal', 1);
                expect(counter.nextNumber, 'to equal', 2);
                done();
            }));
        }));
    });

    it('should deliver the same result to multiple callbacks that are queued before the result is available', function (done) {
        var nextNumber = 1,
            memoizedGetNextNumber = memoizeAsync(function getNextNumber(cb) {
                process.nextTick(function () {
                    cb(null, nextNumber);
                    nextNumber += 1;
                });
            });

        var results = [];
        function receiveResultAndProceedIfReady(err, number) {
            results.push(number);
            if (results.length === 2) {
                expect(results[0], 'to equal', 1);
                expect(results[1], 'to equal', 1);
                done();
            }
        }
        memoizedGetNextNumber(receiveResultAndProceedIfReady);
        memoizedGetNextNumber(receiveResultAndProceedIfReady);
    });

    it('should work with a custom argumentsStringifier', function (done) {
        function toCanonicalJson(obj) {
            return JSON.stringify(function traverseAndSortKeys(obj) {
                if (Array.isArray(obj)) {
                    return obj.map(traverseAndSortKeys);
                } else if (typeof obj === 'object' && obj !== null) {
                    var resultObj = {};
                    Object.keys(obj).sort().forEach(function (key) {
                        resultObj[key] = traverseAndSortKeys(obj[key]);
                    });
                    return resultObj;
                } else {
                    return obj;
                }
            }(obj));
        }

        var nextNumber = 1,
            memoizedGetNextNumber = memoizeAsync(function getNextNumber(obj, cb) {
                process.nextTick(function () {
                    cb(null, nextNumber);
                    nextNumber += 1;
                });
            }, {
                argumentsStringifier: function (args) {
                    return args.map(toCanonicalJson).join('\x1d');
                }
            });

        memoizedGetNextNumber({foo: 'bar', quux: 'baz'}, passError(done, function (nextNumber) {
            expect(nextNumber, 'to equal', 1);
            memoizedGetNextNumber({quux: 'baz', foo: 'bar'}, passError(done, function (nextNumber) {
                expect(nextNumber, 'to equal', 1);
                memoizedGetNextNumber({barf: 'baz'}, passError(done, function (nextNumber) {
                    expect(nextNumber, 'to equal', 2);
                    done();
                }));
            }));
        }));
    });

    it('with a maxAge should recompute the value after an item has become stale', function (done) {
        var nextNumber = 1,
            memoizedGetNextNumber = memoizeAsync(function getNextNumber(cb) {
                process.nextTick(function () {
                    cb(null, nextNumber);
                    nextNumber += 1;
                });
            }, {maxAge: 10});

        memoizedGetNextNumber(passError(done, function (nextNumber) {
            expect(nextNumber, 'to equal', 1);
            memoizedGetNextNumber(passError(done, function (nextNumber) {
                expect(nextNumber, 'to equal', 1);
                setTimeout(function () {
                    memoizedGetNextNumber(passError(done, function (nextNumber) {
                        expect(nextNumber, 'to equal', 2);
                        done();
                    }));
                }, 15);
            }));
        }));
    });

    it('with a max limit should purge the least recently used result', function (done) {
        var nextNumber = 1,
            memoizedGetNextNumberPlusOtherNumber = memoizeAsync(function getNextNumber(otherNumber, cb) {
                process.nextTick(function () {
                    cb(null, otherNumber + nextNumber);
                    nextNumber += 1;
                });
            }, {max: 2});

        memoizedGetNextNumberPlusOtherNumber(1, passError(done, function (nextNumberPlusOne) {
            expect(nextNumberPlusOne, 'to equal', 2);
            memoizedGetNextNumberPlusOtherNumber(2, passError(done, function (nextNumberPlusTwo) {
                expect(nextNumberPlusTwo, 'to equal', 4);
                memoizedGetNextNumberPlusOtherNumber(1, passError(done, function (nextNumberPlusOneAgain) {
                    expect(nextNumberPlusOne, 'to equal', 2);
                    // This will purge memoizedGetNextNumberPlusOtherNumber(2, ...):
                    memoizedGetNextNumberPlusOtherNumber(3, passError(done, function (nextNumberPlusThree) {
                        expect(nextNumberPlusThree, 'to equal', 6);
                        memoizedGetNextNumberPlusOtherNumber(2, passError(done, function (nextNumberPlusTwoAgain) {
                            expect(nextNumberPlusTwoAgain, 'to equal', 6);
                            done();
                        }));
                    }));
                }));
            }));
        }));
    });

    it('with a length function should call the length function with the result callback parameters as regular arguments', function (done) {
        var shouldErrorNextTime = false,
            functionThatErrorsEverySecondTime = function (number, cb) {
                process.nextTick(function () {
                    if (shouldErrorNextTime) {
                        cb(new Error());
                    } else {
                        cb(null, 'the result');
                    }
                    shouldErrorNextTime = !shouldErrorNextTime;
                });
            },
            memoizedFunctionThatErrorsEverySecondTime = memoizeAsync(functionThatErrorsEverySecondTime, {
                length: function (err, result) {
                    return err ? 1 : result.length;
                },
                errors: true
            });
        memoizedFunctionThatErrorsEverySecondTime(1, passError(done, function (result) {
            expect(result, 'to equal', 'the result');
            expect(memoizedFunctionThatErrorsEverySecondTime.cache.length, 'to equal', 10);
            memoizedFunctionThatErrorsEverySecondTime(2, function (err, result2) {
                expect(err, 'to be an', Error);
                expect(result2, 'to be undefined');
                expect(memoizedFunctionThatErrorsEverySecondTime.cache.length, 'to equal', 11);
                done();
            });
        }));
    });

    it('should leave unrelated values in the cache when purgeAll is called', function (done) {
        var memoizedAsyncSum = memoizeAsync(function asyncSum(a, b, cb) {
            process.nextTick(function () {
                cb(null, a + b);
            });
        });
        var cache = memoizedAsyncSum.cache;
        memoizedAsyncSum(1, 2, passError(done, function (sum) {
            expect(sum, 'to equal', 3);
            expect(cache.keys().length, 'to equal', 1);
            cache.set('foo', 'bar');
            expect(cache.keys().length, 'to equal', 2);
            memoizedAsyncSum.purgeAll();
            expect(cache.keys().length, 'to equal', 1);
            expect(cache.get('foo'), 'to equal', 'bar');
            done();
        }));
    });

    it('should allow passing an existing lru-cache instance in the options object', function (done) {
        function asyncSum(a, b, cb) {
            process.nextTick(function () {
                cb(null, a + b);
            });
        }
        var cache = new LRUCache(),
            memoizedAsyncSum1 = memoizeAsync(asyncSum, {cache: cache});
        expect(memoizedAsyncSum1.cache, 'to be', cache);

        var memoizedAsyncSum2 = memoizeAsync(asyncSum, {cache: cache});
        expect(memoizedAsyncSum2.cache, 'to be', cache);
        memoizedAsyncSum1(1, 2, passError(done, function (sum) {
            expect(sum, 'to equal', 3);
            expect(cache.keys().length, 'to equal', 1);
            expect(cache.get(memoizedAsyncSum1.cacheKeyPrefix + memoizedAsyncSum1.argumentsStringifier([1, 2])), 'to equal', [null, 3]);
            memoizedAsyncSum2(1, 2, passError(done, function (sum) {
                expect(sum, 'to equal', 3);
                expect(cache.keys().length, 'to equal', 2);
                done();
            }));
        }));
    });

    it('should allow specifying a custom cacheKeyPrefix', function (done) {
        var memoizedAsyncSum = memoizeAsync(function (a, b, cb) {
            process.nextTick(function () {
                cb(null, a + b);
            });
        }, {
            cacheKeyPrefix: 999
        });

        expect(memoizedAsyncSum.cacheKeyPrefix, 'to equal', '999');

        memoizedAsyncSum(1, 2, passError(done, function (sum) {
            expect(sum, 'to equal', 3);
            expect(memoizedAsyncSum.cache.get('999' + memoizedAsyncSum.argumentsStringifier([1, 2])), 'to equal', [null, 3]);
            done();
        }));
    });

    it('should call the memoized function in options.context if specified', function (done) {
        var memoizedFunction = memoizeAsync(function (a, cb) {
            var sum = this.foo + a;
            process.nextTick(function () {
                cb(null, sum);
            });
        }, {context: {foo: 4}});
        memoizedFunction(8, passError(done, function (sum) {
            expect(sum, 'to equal', 12);
            done();
        }));
    });

    it('should not memoize errors per default', function (done) {
        var memoizedFunction = memoizeAsync(function (cb) {
            process.nextTick(function () {
                cb(new Error('foo'));
            });
        });
        memoizedFunction(function (err) {
            expect(err, 'to be an', Error);
            expect(err.message, 'to equal', 'foo');
            memoizedFunction(function (err2) {
                expect(err2, 'to be an', Error);
                expect(err2.message, 'to equal', 'foo');
                expect(err2, 'not to be', err);
                done();
            });
        });
    });

    it('should memoize errors if the `errors` option is set to true', function (done) {
        var memoizedFunction = memoizeAsync(function (cb) {
            process.nextTick(function () {
                cb(new Error('foo'));
            });
        }, {errors: true});
        memoizedFunction(function (err) {
            expect(err, 'to be an', Error);
            expect(err.message, 'to equal', 'foo');
            memoizedFunction(function (err2) {
                expect(err2, 'to be an', Error);
                expect(err2.message, 'to equal', 'foo');
                expect(err2, 'to be', err);
                done();
            });
        });
    });

    describe('refreshTime', function () {
        var clock;
        var currentTick = 0;

        function gotoTick(n) {
            var toTick = n - currentTick;
            currentTick = n;
            clock.tick(toTick);
        }

        beforeEach(function () {
            currentTick = 0;
            clock = sinon.useFakeTimers();
        });
        afterEach(function () {
            clock.restore();
        });
        it('should refresh async but return stale value immediately', function (done) {
            var nextNumber = 1,
                method = sinon.spy(function getNextNumber(cb) {
                    setTimeout(function () {
                        cb(null, nextNumber);
                        nextNumber += 1;
                    }, 5);
                }),
                memoizedGetNextNumber = memoizeAsync(method, { maxAge: 20, refreshAge: 10 });

            async.series([
                function (callback) {
                    // initial call
                    memoizedGetNextNumber(passError(callback, function (nextNumber) {
                        expect(nextNumber, 'to be', 1);
                        expect(method, 'was called once');
                        gotoTick(10);
                        callback();
                    }));
                    // going to tick 6 to allow it to finish
                    gotoTick(6);
                },
                function (callback) {
                    // still below refreshAge
                    memoizedGetNextNumber(passError(callback, function (nextNumber) {
                        expect(nextNumber, 'to be', 1);
                        expect(method, 'was called once');
                        gotoTick(17);
                        callback();
                    }));
                },
                function (callback) {
                    // above refreshAge below maxAge immediate result
                    memoizedGetNextNumber(passError(callback, function (nextNumber) {
                        expect(nextNumber, 'to be', 1);
                        expect(method, 'was called twice');
                        gotoTick(27);
                        callback();
                    }));
                },
                function (callback) {
                    // above initial maxAge but immediate result because refresh
                    memoizedGetNextNumber(passError(callback, function (nextNumber) {
                        expect(nextNumber, 'to be', 2);
                        expect(method, 'was called twice');
                        callback();
                    }));
                }
            ], done);
        });
    });
});
<|endoftext|>


<commit_before>'use strict';

const Generator = require('yeoman-generator');
const chalk = require('chalk');
const yosay = require('yosay');
const path = require('path');
const to = require('to-case');
const mkdirp = require('mkdirp');

module.exports = class extends Generator {
  constructor(args, opts) {
    super(args, opts);

    //
    // Get command-line argument, if any
    //
    this.argument('arg',
      {
        type: String,
        required: false,
        desc: 'Name of the service'
      });
  }

  prompting() {
    this.log(yosay(
      'Welcome to the ' + chalk.red('angular-modsmith') + ' generator!'
    ));

    if (typeof this.options.arg === 'undefined') {
      this.log(chalk.blue.bold('Creating new service...') + '\n');
    } else {
      this.log(chalk.blue.bold('Creating service') + ' ' +
               chalk.magenta.bold(this.options.arg) + chalk.blue.bold('...') + '\n');
    }

    const prompts = [
      {
        type: 'input',
        name: 'argName',
        message: chalk.yellow('Service name :'),
        default: this.options.arg ? to.slug(this.options.arg) : undefined
      },
      {
        type: 'input',
        name: 'argDesc',
        message: chalk.yellow('Service desc :'),
        default: 'TODO: Add service description.'
      }
    ];

    return this.prompt(prompts).then(props => {
      //
      // Read package configuration.
      //
      var pkgconf = this.config.getAll();

      //
      // Add package configuration to service options
      //
      this.props = {
        pkg     : JSON.parse(JSON.stringify(pkgconf.pkg)),
        author  : JSON.parse(JSON.stringify(pkgconf.author)),
        svc : {
          name  : {
            orig    : props.argName,
            slug    : to.slug(props.argName),
            camel   : to.camel(props.argName)
          },
          desc      : props.argDesc
        }
      };
    });
  }

  /**
   * Private function to create directory
   */
  _createDir(dst) {
    var dirOk = true;

    try {
      mkdirp.sync(dst);
    } catch (e) {
      dirOk = false;

      this.log('\n' + chalk.red.bold('Couldn\'t create directory:') + ' ' +
               chalk.magenta.bold(dst));
      this.log(chalk.yellow(e.message) + '\n');
    }

    if (!dirOk) {
      return this.error(new Error('Failed to create directory: ' + dst));
    }
  }

  /**
   * Private function to copy source files
   */
  _copySourceFiles(dst) {
    this.fs.copyTpl(
      this.templatePath('_service.js'),
      this.destinationPath(path.join(dst, this.props.svc.name.camel + '.service.js')),
      this.props);

    this.fs.copyTpl(
      this.templatePath('_service.spec.js'),
      this.destinationPath(path.join(dst, this.props.svc.name.camel + '.service.spec.js')),
      this.props);
  }

  writing() {
    var dstDir = path.join('src', 'services', this.props.svc.name.camel);

    this._createDir(dstDir);
    this._copySourceFiles(dstDir);
  }
};
<commit_msg>subgen: service: Commit generated sources<commit_after>'use strict';

const Generator = require('yeoman-generator');
const chalk = require('chalk');
const yosay = require('yosay');
const path = require('path');
const to = require('to-case');
const mkdirp = require('mkdirp');

module.exports = class extends Generator {
  constructor(args, opts) {
    super(args, opts);

    //
    // Get command-line argument, if any
    //
    this.argument('arg',
      {
        type: String,
        required: false,
        desc: 'Name of the service'
      });
  }

  prompting() {
    this.log(yosay(
      'Welcome to the ' + chalk.red('angular-modsmith') + ' generator!'
    ));

    if (typeof this.options.arg === 'undefined') {
      this.log(chalk.blue.bold('Creating new service...') + '\n');
    } else {
      this.log(chalk.blue.bold('Creating service') + ' ' +
               chalk.magenta.bold(this.options.arg) + chalk.blue.bold('...') + '\n');
    }

    const prompts = [
      {
        type: 'input',
        name: 'argName',
        message: chalk.yellow('Service name :'),
        default: this.options.arg ? to.slug(this.options.arg) : undefined
      },
      {
        type: 'input',
        name: 'argDesc',
        message: chalk.yellow('Service desc :'),
        default: 'TODO: Add service description.'
      }
    ];

    return this.prompt(prompts).then(props => {
      //
      // Read package configuration.
      //
      var pkgconf = this.config.getAll();

      //
      // Add package configuration to service options
      //
      this.props = {
        pkg     : JSON.parse(JSON.stringify(pkgconf.pkg)),
        author  : JSON.parse(JSON.stringify(pkgconf.author)),
        svc : {
          name  : {
            orig    : props.argName,
            slug    : to.slug(props.argName),
            camel   : to.camel(props.argName)
          },
          desc      : props.argDesc
        }
      };
    });
  }

  /**
   * Private function to create directory
   */
  _createDir(dst) {
    var dirOk = true;

    try {
      mkdirp.sync(dst);
    } catch (e) {
      dirOk = false;

      this.log('\n' + chalk.red.bold('Couldn\'t create directory:') + ' ' +
               chalk.magenta.bold(dst));
      this.log(chalk.yellow(e.message) + '\n');
    }

    if (!dirOk) {
      return this.error(new Error('Failed to create directory: ' + dst));
    }
  }

  /**
   * Private function to copy source files
   */
  _copySourceFiles(dst) {
    this.fs.copyTpl(
      this.templatePath('_service.js'),
      this.destinationPath(path.join(dst, this.props.svc.name.camel + '.service.js')),
      this.props);

    this.fs.copyTpl(
      this.templatePath('_service.spec.js'),
      this.destinationPath(path.join(dst, this.props.svc.name.camel + '.service.spec.js')),
      this.props);
  }

  /**
   * Private function to add sources to git repo and commit
   */
  _gitCommit(dst) {
    const sgit = require('simple-git')(this.destinationRoot());

    var msg = 'chore: Add service \'' + this.props.svc.name.slug + '\'\n' +
              '\n' +
              'Created by generator-angular-modsmith v' +
              this.rootGeneratorVersion() + '.\n';

    sgit.add(dst).commit(msg);

    this.log(chalk.white.bold('\nCommitted to repo.\n'));
  }

  writing() {
    var dstDir = path.join('src', 'services', this.props.svc.name.camel);

    this._createDir(dstDir);
    this._copySourceFiles(dstDir);
  }

  end() {
    var dstDir = path.join('src', 'services', this.props.svc.name.camel);

    this._gitCommit(dstDir);
  }
};
<|endoftext|>


<commit_before>(function(root, factory) {
    if (typeof exports === 'object') { // Node.js
        module.exports = factory(
            require('./events.js')
        );
    } else if (typeof define === 'function' && define.amd) { // Require.JS
        define(['jef/events'], factory);
    } else { // Browser globals
        root.jef = root.jef || {};
        root.jef.stream = factory(
            root.jef.events
        );
    }
})(this, function(events, undefined) {
    'use strict';

    // Helper functions
    var slice =  Function.prototype.call.bind(Array.prototype.slice);

    /**
     * Drain the stream
     *
     * @this Stream
     */
    function drain() {
        var value,
        drain = this.options.drain;

        // You can drain once given stream
        this.options.drain = null;

        // Drain untill will be empty
        while (typeof (value = drain()) !== 'undefined') {
            this.push(value);
        }

        // Close stream
        this.destroy();
    }

    /**
     * Represents a stream
     *
     * @constructor
     * @param {Object} options - Options object
     */
    function Stream(options) {
        events.call(this);

        this.options = options || {};
        this.chain = [];
        this.buffer = [];
        this.filtered = false;
        this.lastValue = [];
        this.closed = false;
    }
    Stream.constructor = Stream;
    Stream.prototype = new events();

    /**
     * Check if stream is closed
     *
     * @return {Boolean}
     */
    Stream.prototype.isClosed = function() {
        return this.closed;
    }

    /**
     * Check if stream has readers
     *
     * @return {Boolean}
     */
    Stream.prototype.hasReaders = function() {
        return this.eventsCallbacks > 0;
    };

    /**
     * Check wether we can drain data on not
     *
     * @return {Boolean}
     */
    Stream.prototype.canDrain = function() {
        return typeof this.options.drain === 'function';
    };

    /**
     * Register new event handler, and trigger the 'drain' event that stream is ready to datin.
     *
     * @param {String} name     An event to which attach listener
     * @param {Function} func   An listener to listne to the event
     * @return {Stream}
     */
    Stream.prototype.on = function(name, func) {
        var result = events.prototype.on.call(this, name, func);
        this.trigger('drain');
        return result;
    };

    /**
     * Pipe one stream to another
     *
     * @param {Stream} stream   An stream to pipe
     */
    Stream.prototype.pipe = function(stream) {
        // Accept only streams
        if (!stream instanceof Stream) {
            throw new Error('You can pipe to another stream, but given ' + stream);
        }
        if (this.isClosed()) {
            throw new Error('Stream is closed, can\'t pipe to another stream');
        }
        if (stream.isClosed()) {
            throw new Error('You can\t pipe to closed streams');
        }

        // Connect two streams
        this.on('data', stream.push.bind(stream));

        // If we can drain this stream then do it,
        // Otherwise just connect streams
        if (!this.canDrain()) {
            return stream;
        }

        // If next stream dont have readers,
        // and we have drain function postpone drain
        // until this stream or next will have stream readers
        if (!stream.hasReaders()) {
            stream.on('drain', drain.bind(this));
            return stream;
        }

        // Drain data from stream
        drain.call(this);

        return stream;
    };

    /**
     * Create new stream based on current.
     *
     * Stream will buffer values and push them only when reach given {size}.
     * If flag {allowEmpty} is set this stream wont waint till
     * buffer size will be equal {size}, and will push data along the stream
     * even if doesn't have enough size.
     *
     * @param {Integer} size
     * @param {Integer} skip
     * @param {Boolean} allowEmpty
     * @return {Stream}
     */
    Stream.prototype.take = function (size, skip, allowEmpty) {
        // Create new stream that will reject values that don't filter test
        this.chain.push(new Stream({
            // apply: true,
            take: {
                limit: size,
                skip: skip || 0,
                allowEmpty: !! allowEmpty
            }
        }));
        return this.chain[this.chain.length - 1];
    }

    /**
     * Create new stream which will map values in to new representation.
     *
     * @param {Function} func
     * @return {Stream}
     */
    Stream.prototype.map = function(func) {
        // Create new stream with that maps values to new form
        this.chain.push(new Stream({
            apply: this.options.apply,
            map: func
        }));
        return this.chain[this.chain.length - 1];
    };

    /**
     * Create new stream which will accept only values
     * that test by given function {func}
     *
     * @param {Function} func
     * @return {Stream}
     */
    Stream.prototype.accept = function(func) {
        // Create new stream that will accept values that pass filter test
        this.chain.push(new Stream({
            apply: this.options.apply,
            filter: func
        }));
        return this.chain[this.chain.length - 1];
    };

    /**
     * Create new stream which wont accept values
     * when given text function {func} wont accept them.
     *
     * @param {Function} func
     * @return {Stream}
     */
    Stream.prototype.reject = function(func) {
        // Create new stream that will reject values that don't filter test
        this.chain.push(new Stream({
            apply: this.options.apply,
            filter: function() {
                return !func.apply(func, arguments);
            }
        }));
        return this.chain[this.chain.length - 1];
    };

    /**
     * Create new stream that will take all incomming values
     * And reduce them to the {base} by applying given function {func}
     *
     * @param {Function} func
     * @param {Function} base
     * @return {Stream}
     */
    Stream.prototype.reduce = function(func, base) {
        this.chain.push(new Stream({
            reduce: {
                func: func,
                base: base
            }
        }));
        return this.chain[this.chain.length - 1];
    };

    /**
     * Push data along the stream
     *
     * @param {Object} data
     * @return {Stream}
     */
    Stream.prototype.push = function(data) {
        if (this.isClosed()) {
            throw new Error('Stream is closed, can\'t push data');
        }

        if (this.canDrain()) {
            throw new Error('Stream can\'t push data because it has drain function');
        }

        // Arguments to array
        data = slice(arguments);

        // On new data mark stream as not filtered
        this.filtered = false;

        // Clear last value in case filter function wont accept this input
        // Experimental.
        this.lastValue = [];

        // Test data if this part of the stream would like to accept it
        if (this.options.filter && !this.options.filter.apply(this.options.filter, data)) {
            this.filtered = true;
            return this.trigger('out', data, this);
        }

        // Lets map our result
        if (this.options.map) {
            if (typeof this.options.map === 'function')  {
                data = this.options.map.apply(this, data);
            } else {
                data = this.options.map;
            }
            data = Array.isArray(data) ? data : [data];
        }

        // Lets reduce our stream
        if (this.options.reduce) {
            data = this.options.reduce.func.apply(
                this,
                data.concat(
                    this.options.reduce.base
                )
            );
            this.options.reduce.base = data;
            data = Array.isArray(data) ? data : [data];
        }

        // Lets buffer some values in stream
        if (this.options.take) {
            // Collect streamed data
            this.buffer.push(data);

            var length = this.buffer.length;
            var end = length - this.options.take.skip;
            var start = end - this.options.take.limit;

            start = start < 0 ? 0 : start;

            data = this.buffer.slice(start, end);

            // Will take only when will have given limit in buffer
            if (!this.options.allowEmpty
                && data.length !== this.options.take.limit
            ) {
                return this;
            }
        }

        this.lastValue = data;

        // Notify that data was set
        this.trigger('data', data, this);

        // Notify children nodes
        this.chain.forEach(function(stream) {
            stream.push.apply(stream, data);
        });

        return this;
    };

    /**
     * Function to be called on recent value from stream and on future values
     *
     * @param {Function} func
     * @return this
     */
    Stream.prototype.last = function(func) {
        // Call function with last value in stream
        func.apply(func, this.lastValue);
        // Subscribe function to new data
        this.on('data', func);

        return this;
    };

    /**
     * Crate new stream that merge two stream into one
     *
     * @param {Stream} next
     * @return {Stream}
     */
    Stream.prototype.merge = function(next) {
        return Stream.merge(this, next);
    };

    /**
     * Destroy stream
     */
    Stream.prototype.destroy = function() {
        this.trigger('closed');
        // Custom destroy function
        this.options.destroy && this.options.destroy();
        // Destroy childs
        this.chain.forEach(function(stream) {
            stream.destroy();
        });
        // Invoking constructor clean properties
        Stream.call(this);

        this.closed = true;
    };

    Stream.merge = function () {
        var streams = slice(arguments);
        var refs = [];
        var merged = new Stream({
            destroy: function() {
                streams.forEach(function(item, index) {
                    item.off('data', refs[index]);
                });
            }
        });
        streams.forEach(function (stream, index) {
            stream.on('data', refs[index] = function () {
                merged.push.apply(merged, arguments)
            });
        });

        return merged;
    }

    Stream.when = function() {
        var streams = slice(arguments);
        var refs = [];
        var buffer = new Array(streams.length);
        var called = new Array(streams.length);
        var result = new Stream({
            filter: function(streams) {
                // if true then is valid
                return -1 === streams.called.indexOf(false);
            },
            map: function(streams) {
                return streams.arguments;
            },
            destroy: function() {
                streams.forEach(function(item, index) {
                    item.off('out', refs[index]);
                    item.off('data', refs[index]);
                });
            }
        });

        streams.forEach(function(item, index) {
            called[index] = false;
            refs[index] = function(value) {
                called[index] = !this.filtered;
                buffer[index] = value;
                result.push({arguments: buffer, called: called });
            }
            item.on('out', refs[index]);
            item.on('data', refs[index]);

            // If stream have last value then set it
            result.lastValue.push(item.lastValue);
        });

        return result;
    };

    Stream.fromArray = function(array) {
        var index = -1, length = array.length;
        var result = new Stream({
            drain: function() {
                return ++index < length ? array[index] : undefined;
            }
        });
        return result;
    };

    Stream.fromPromise = function(promise, stream) {
        var result = stream instanceof Stream ? stream : new Stream();

        promise.then(function(value) {
            result.push(value);
        }, function(error) {
            result.trigger('error', arguments);
        });

        return result;
    };

    return Stream;
});
<commit_msg>Stream.fromPromise closing stream after promise is fulfilled or rejected<commit_after>(function(root, factory) {
    if (typeof exports === 'object') { // Node.js
        module.exports = factory(
            require('./events.js')
        );
    } else if (typeof define === 'function' && define.amd) { // Require.JS
        define(['jef/events'], factory);
    } else { // Browser globals
        root.jef = root.jef || {};
        root.jef.stream = factory(
            root.jef.events
        );
    }
})(this, function(events, undefined) {
    'use strict';

    // Helper functions
    var slice =  Function.prototype.call.bind(Array.prototype.slice);

    /**
     * Drain the stream
     *
     * @this Stream
     */
    function drain() {
        var value,
        drain = this.options.drain;

        // You can drain once given stream
        this.options.drain = null;

        // Drain untill will be empty
        while (typeof (value = drain()) !== 'undefined') {
            this.push(value);
        }

        // Close stream
        this.destroy();
    }

    /**
     * Represents a stream
     *
     * @constructor
     * @param {Object} options - Options object
     */
    function Stream(options) {
        events.call(this);

        this.options = options || {};
        this.chain = [];
        this.buffer = [];
        this.filtered = false;
        this.lastValue = [];
        this.closed = false;
    }
    Stream.constructor = Stream;
    Stream.prototype = new events();

    /**
     * Check if stream is closed
     *
     * @return {Boolean}
     */
    Stream.prototype.isClosed = function() {
        return this.closed;
    }

    /**
     * Check if stream has readers
     *
     * @return {Boolean}
     */
    Stream.prototype.hasReaders = function() {
        return this.eventsCallbacks > 0;
    };

    /**
     * Check wether we can drain data on not
     *
     * @return {Boolean}
     */
    Stream.prototype.canDrain = function() {
        return typeof this.options.drain === 'function';
    };

    /**
     * Register new event handler, and trigger the 'drain' event that stream is ready to datin.
     *
     * @param {String} name     An event to which attach listener
     * @param {Function} func   An listener to listne to the event
     * @return {Stream}
     */
    Stream.prototype.on = function(name, func) {
        var result = events.prototype.on.call(this, name, func);
        this.trigger('drain');
        return result;
    };

    /**
     * Pipe one stream to another
     *
     * @param {Stream} stream   An stream to pipe
     */
    Stream.prototype.pipe = function(stream) {
        // Accept only streams
        if (!stream instanceof Stream) {
            throw new Error('You can pipe to another stream, but given ' + stream);
        }
        if (this.isClosed()) {
            throw new Error('Stream is closed, can\'t pipe to another stream');
        }
        if (stream.isClosed()) {
            throw new Error('You can\t pipe to closed streams');
        }

        // Connect two streams
        this.on('data', stream.push.bind(stream));

        // If we can drain this stream then do it,
        // Otherwise just connect streams
        if (!this.canDrain()) {
            return stream;
        }

        // If next stream dont have readers,
        // and we have drain function postpone drain
        // until this stream or next will have stream readers
        if (!stream.hasReaders()) {
            stream.on('drain', drain.bind(this));
            return stream;
        }

        // Drain data from stream
        drain.call(this);

        return stream;
    };

    /**
     * Create new stream based on current.
     *
     * Stream will buffer values and push them only when reach given {size}.
     * If flag {allowEmpty} is set this stream wont waint till
     * buffer size will be equal {size}, and will push data along the stream
     * even if doesn't have enough size.
     *
     * @param {Integer} size
     * @param {Integer} skip
     * @param {Boolean} allowEmpty
     * @return {Stream}
     */
    Stream.prototype.take = function (size, skip, allowEmpty) {
        // Create new stream that will reject values that don't filter test
        this.chain.push(new Stream({
            // apply: true,
            take: {
                limit: size,
                skip: skip || 0,
                allowEmpty: !! allowEmpty
            }
        }));
        return this.chain[this.chain.length - 1];
    }

    /**
     * Create new stream which will map values in to new representation.
     *
     * @param {Function} func
     * @return {Stream}
     */
    Stream.prototype.map = function(func) {
        // Create new stream with that maps values to new form
        this.chain.push(new Stream({
            apply: this.options.apply,
            map: func
        }));
        return this.chain[this.chain.length - 1];
    };

    /**
     * Create new stream which will accept only values
     * that test by given function {func}
     *
     * @param {Function} func
     * @return {Stream}
     */
    Stream.prototype.accept = function(func) {
        // Create new stream that will accept values that pass filter test
        this.chain.push(new Stream({
            apply: this.options.apply,
            filter: func
        }));
        return this.chain[this.chain.length - 1];
    };

    /**
     * Create new stream which wont accept values
     * when given text function {func} wont accept them.
     *
     * @param {Function} func
     * @return {Stream}
     */
    Stream.prototype.reject = function(func) {
        // Create new stream that will reject values that don't filter test
        this.chain.push(new Stream({
            apply: this.options.apply,
            filter: function() {
                return !func.apply(func, arguments);
            }
        }));
        return this.chain[this.chain.length - 1];
    };

    /**
     * Create new stream that will take all incomming values
     * And reduce them to the {base} by applying given function {func}
     *
     * @param {Function} func
     * @param {Function} base
     * @return {Stream}
     */
    Stream.prototype.reduce = function(func, base) {
        this.chain.push(new Stream({
            reduce: {
                func: func,
                base: base
            }
        }));
        return this.chain[this.chain.length - 1];
    };

    /**
     * Push data along the stream
     *
     * @param {Object} data
     * @return {Stream}
     */
    Stream.prototype.push = function(data) {
        if (this.isClosed()) {
            throw new Error('Stream is closed, can\'t push data');
        }

        if (this.canDrain()) {
            throw new Error('Stream can\'t push data because it has drain function');
        }

        // Arguments to array
        data = slice(arguments);

        // On new data mark stream as not filtered
        this.filtered = false;

        // Clear last value in case filter function wont accept this input
        // Experimental.
        this.lastValue = [];

        // Test data if this part of the stream would like to accept it
        if (this.options.filter && !this.options.filter.apply(this.options.filter, data)) {
            this.filtered = true;
            return this.trigger('out', data, this);
        }

        // Lets map our result
        if (this.options.map) {
            if (typeof this.options.map === 'function')  {
                data = this.options.map.apply(this, data);
            } else {
                data = this.options.map;
            }
            data = Array.isArray(data) ? data : [data];
        }

        // Lets reduce our stream
        if (this.options.reduce) {
            data = this.options.reduce.func.apply(
                this,
                data.concat(
                    this.options.reduce.base
                )
            );
            this.options.reduce.base = data;
            data = Array.isArray(data) ? data : [data];
        }

        // Lets buffer some values in stream
        if (this.options.take) {
            // Collect streamed data
            this.buffer.push(data);

            var length = this.buffer.length;
            var end = length - this.options.take.skip;
            var start = end - this.options.take.limit;

            start = start < 0 ? 0 : start;

            data = this.buffer.slice(start, end);

            // Will take only when will have given limit in buffer
            if (!this.options.allowEmpty
                && data.length !== this.options.take.limit
            ) {
                return this;
            }
        }

        this.lastValue = data;

        // Notify that data was set
        this.trigger('data', data, this);

        // Notify children nodes
        this.chain.forEach(function(stream) {
            stream.push.apply(stream, data);
        });

        return this;
    };

    /**
     * Function to be called on recent value from stream and on future values
     *
     * @param {Function} func
     * @return this
     */
    Stream.prototype.last = function(func) {
        // Call function with last value in stream
        func.apply(func, this.lastValue);
        // Subscribe function to new data
        this.on('data', func);

        return this;
    };

    /**
     * Crate new stream that merge two stream into one
     *
     * @param {Stream} next
     * @return {Stream}
     */
    Stream.prototype.merge = function(next) {
        return Stream.merge(this, next);
    };

    /**
     * Destroy stream
     */
    Stream.prototype.destroy = function() {
        this.trigger('closed');
        // Custom destroy function
        this.options.destroy && this.options.destroy();
        // Destroy childs
        this.chain.forEach(function(stream) {
            stream.destroy();
        });
        // Invoking constructor clean properties
        Stream.call(this);

        this.closed = true;
    };

    Stream.merge = function () {
        var streams = slice(arguments);
        var refs = [];
        var merged = new Stream({
            destroy: function() {
                streams.forEach(function(item, index) {
                    item.off('data', refs[index]);
                });
            }
        });
        streams.forEach(function (stream, index) {
            stream.on('data', refs[index] = function () {
                merged.push.apply(merged, arguments)
            });
        });

        return merged;
    }

    Stream.when = function() {
        var streams = slice(arguments);
        var refs = [];
        var buffer = new Array(streams.length);
        var called = new Array(streams.length);
        var result = new Stream({
            filter: function(streams) {
                // if true then is valid
                return -1 === streams.called.indexOf(false);
            },
            map: function(streams) {
                return streams.arguments;
            },
            destroy: function() {
                streams.forEach(function(item, index) {
                    item.off('out', refs[index]);
                    item.off('data', refs[index]);
                });
            }
        });

        streams.forEach(function(item, index) {
            called[index] = false;
            refs[index] = function(value) {
                called[index] = !this.filtered;
                buffer[index] = value;
                result.push({arguments: buffer, called: called });
            }
            item.on('out', refs[index]);
            item.on('data', refs[index]);

            // If stream have last value then set it
            result.lastValue.push(item.lastValue);
        });

        return result;
    };

    Stream.fromArray = function(array) {
        var index = -1, length = array.length;
        var result = new Stream({
            drain: function() {
                return ++index < length ? array[index] : undefined;
            }
        });
        return result;
    };

    Stream.fromPromise = function(promise, stream) {
        var result = stream instanceof Stream ? stream : new Stream();

        promise.then(function(value) {
            result.push(value);
            result.destroy();
        }, function(error) {
            result.trigger('error', arguments);
            result.destroy();
        });

        return result;
    };

    return Stream;
});
<|endoftext|>


<commit_before>'use strict'
var ethers = require('ethers')

module.exports = {
  /**
    * deploy the given contract
    *
    * @param {String} from    - sender address
    * @param {String} data    - data to send with the transaction ( return of txFormat.buildData(...) ).
    * @param {String} value    - decimal representation of value.
    * @param {String} gasLimit    - decimal representation of gas limit.
    * @param {Object} txRunner    - TxRunner.js instance
    * @param {Object} callbacks    - { confirmationCb, gasEstimationForceSend, promptCb }
    *     [validate transaction] confirmationCb (network, tx, gasEstimation, continueTxExecution, cancelCb)
    *     [transaction failed, force send] gasEstimationForceSend (error, continueTxExecution, cancelCb)
    *     [personal mode enabled, need password to continue] promptCb (okCb, cancelCb)
    * @param {Function} finalCallback    - last callback.
    */
  createContract: function (from, data, value, gasLimit, txRunner, callbacks, finalCallback) {
    if (!callbacks.confirmationCb || !callbacks.gasEstimationForceSend || !callbacks.promptCb) {
      return finalCallback('all the callbacks must have been defined')
    }
    var tx = { from: from, to: null, data: data, useCall: false, value: value, gasLimit: gasLimit }
    txRunner.rawRun(tx, callbacks.confirmationCb, callbacks.gasEstimationForceSend, callbacks.promptCb, (error, txResult) => {
      // see universaldapp.js line 660 => 700 to check possible values of txResult (error case)
      finalCallback(error, txResult)
    })
  },

  /**
    * call the current given contract ! that will create a transaction !
    *
    * @param {String} from    - sender address
    * @param {String} to    - recipient address
    * @param {String} data    - data to send with the transaction ( return of txFormat.buildData(...) ).
    * @param {String} value    - decimal representation of value.
    * @param {String} gasLimit    - decimal representation of gas limit.
    * @param {Object} txRunner    - TxRunner.js instance
    * @param {Object} callbacks    - { confirmationCb, gasEstimationForceSend, promptCb }
    *     [validate transaction] confirmationCb (network, tx, gasEstimation, continueTxExecution, cancelCb)
    *     [transaction failed, force send] gasEstimationForceSend (error, continueTxExecution, cancelCb)
    *     [personal mode enabled, need password to continue] promptCb (okCb, cancelCb)
    * @param {Function} finalCallback    - last callback.
    */
  callFunction: function (from, to, data, value, gasLimit, funAbi, txRunner, callbacks, finalCallback) {
    var tx = { from: from, to: to, data: data, useCall: false, value: value, gasLimit: gasLimit }
    txRunner.rawRun(tx, callbacks.confirmationCb, callbacks.gasEstimationForceSend, callbacks.promptCb, (error, txResult) => {
      // see universaldapp.js line 660 => 700 to check possible values of txResult (error case)
      finalCallback(error, txResult)
    })
  },

  /**
    * check if the vm has errored
    *
    * @param {Object} txResult    - the value returned by the vm
    * @return {Object} -  { error: true/false, message: DOMNode }
    */
  checkVMError: function (txResult) {
    var errorCode = {
      OUT_OF_GAS: 'out of gas',
      STACK_UNDERFLOW: 'stack underflow',
      STACK_OVERFLOW: 'stack overflow',
      INVALID_JUMP: 'invalid JUMP',
      INVALID_OPCODE: 'invalid opcode',
      REVERT: 'revert',
      STATIC_STATE_CHANGE: 'static state change'
    }
    var ret = {
      error: false,
      message: ''
    }
    if (!txResult.result.vm.exceptionError) {
      return ret
    }
    var exceptionError = txResult.result.vm.exceptionError.error || ''
    var error = `VM error: ${exceptionError}.\n`
    var msg
    if (exceptionError === errorCode.INVALID_OPCODE) {
      msg = `\t\n\tThe execution might have thrown.\n`
      ret.error = true
    } else if (exceptionError === errorCode.OUT_OF_GAS) {
      msg = `\tThe transaction ran out of gas. Please increase the Gas Limit.\n`
      ret.error = true
    } else if (exceptionError === errorCode.REVERT) {
      var returnData = txResult.result.vm.return
      // It is the hash of Error(string)
      if (returnData && (returnData.slice(0, 4).toString('hex') === '08c379a0')) {
        var abiCoder = new ethers.utils.AbiCoder()
        var reason = abiCoder.decode(['string'], returnData.slice(4))[0]
        msg = `\tThe transaction has been reverted to the initial state.\nReason provided by the contract: "${reason}".`
      } else {
        msg = `\tThe transaction has been reverted to the initial state.\nNote: The constructor should be payable if you send value.`
      }
      ret.error = true
    } else if (exceptionError === errorCode.STATIC_STATE_CHANGE) {
      msg = `\tState changes is not allowed in Static Call context\n`
      ret.error = true
    }
    ret.message = `${error}${exceptionError}${msg}\tDebug the transaction to get more information.`
    return ret
  }
}

<commit_msg>Update txExecution.js<commit_after>'use strict'
var ethers = require('ethers')

module.exports = {
  /**
    * deploy the given contract
    *
    * @param {String} from    - sender address
    * @param {String} data    - data to send with the transaction ( return of txFormat.buildData(...) ).
    * @param {String} value    - decimal representation of value.
    * @param {String} gasLimit    - decimal representation of gas limit.
    * @param {Object} txRunner    - TxRunner.js instance
    * @param {Object} callbacks    - { confirmationCb, gasEstimationForceSend, promptCb }
    *     [validate transaction] confirmationCb (network, tx, gasEstimation, continueTxExecution, cancelCb)
    *     [transaction failed, force send] gasEstimationForceSend (error, continueTxExecution, cancelCb)
    *     [personal mode enabled, need password to continue] promptCb (okCb, cancelCb)
    * @param {Function} finalCallback    - last callback.
    */
  createContract: function (from, data, value, gasLimit, txRunner, callbacks, finalCallback) {
    if (!callbacks.confirmationCb || !callbacks.gasEstimationForceSend || !callbacks.promptCb) {
      return finalCallback('all the callbacks must have been defined')
    }
    var tx = { from: from, to: null, data: data, useCall: false, value: value, gasLimit: gasLimit }
    txRunner.rawRun(tx, callbacks.confirmationCb, callbacks.gasEstimationForceSend, callbacks.promptCb, (error, txResult) => {
      // see universaldapp.js line 660 => 700 to check possible values of txResult (error case)
      finalCallback(error, txResult)
    })
  },

  /**
    * call the current given contract ! that will create a transaction !
    *
    * @param {String} from    - sender address
    * @param {String} to    - recipient address
    * @param {String} data    - data to send with the transaction ( return of txFormat.buildData(...) ).
    * @param {String} value    - decimal representation of value.
    * @param {String} gasLimit    - decimal representation of gas limit.
    * @param {Object} txRunner    - TxRunner.js instance
    * @param {Object} callbacks    - { confirmationCb, gasEstimationForceSend, promptCb }
    *     [validate transaction] confirmationCb (network, tx, gasEstimation, continueTxExecution, cancelCb)
    *     [transaction failed, force send] gasEstimationForceSend (error, continueTxExecution, cancelCb)
    *     [personal mode enabled, need password to continue] promptCb (okCb, cancelCb)
    * @param {Function} finalCallback    - last callback.
    */
  callFunction: function (from, to, data, value, gasLimit, funAbi, txRunner, callbacks, finalCallback) {
    var tx = { from: from, to: to, data: data, useCall: false, value: value, gasLimit: gasLimit }
    txRunner.rawRun(tx, callbacks.confirmationCb, callbacks.gasEstimationForceSend, callbacks.promptCb, (error, txResult) => {
      // see universaldapp.js line 660 => 700 to check possible values of txResult (error case)
      finalCallback(error, txResult)
    })
  },

  /**
    * check if the vm has errored
    *
    * @param {Object} txResult    - the value returned by the vm
    * @return {Object} -  { error: true/false, message: DOMNode }
    */
  checkVMError: function (txResult) {
    var errorCode = {
      OUT_OF_GAS: 'out of gas',
      STACK_UNDERFLOW: 'stack underflow',
      STACK_OVERFLOW: 'stack overflow',
      INVALID_JUMP: 'invalid JUMP',
      INVALID_OPCODE: 'invalid opcode',
      REVERT: 'revert',
      STATIC_STATE_CHANGE: 'static state change'
    }
    var ret = {
      error: false,
      message: ''
    }
    if (!txResult.result.vm.exceptionError) {
      return ret
    }
    var exceptionError = txResult.result.vm.exceptionError.error || ''
    var error = `VM error: ${exceptionError}.\n`
    var msg
    if (exceptionError === errorCode.INVALID_OPCODE) {
      msg = `\t\n\tThe execution might have thrown.\n`
      ret.error = true
    } else if (exceptionError === errorCode.OUT_OF_GAS) {
      msg = `\tThe transaction ran out of gas. Please increase the Gas Limit.\n`
      ret.error = true
    } else if (exceptionError === errorCode.REVERT) {
      var returnData = txResult.result.vm.return
      // It is the hash of Error(string)
      if (returnData && (returnData.slice(0, 4).toString('hex') === '08c379a0')) {
        var abiCoder = new ethers.utils.AbiCoder()
        var reason = abiCoder.decode(['string'], returnData.slice(4))[0]
        msg = `\tThe transaction has been reverted to the initial state.\nReason provided by the contract: "${reason}".`
      } else {
        msg = `\tThe transaction has been reverted to the initial state.\nNote: The constructor should be payable if you send value and the value you send should be less than your current balance.`
      }
      ret.error = true
    } else if (exceptionError === errorCode.STATIC_STATE_CHANGE) {
      msg = `\tState changes is not allowed in Static Call context\n`
      ret.error = true
    }
    ret.message = `${error}${exceptionError}${msg}\tDebug the transaction to get more information.`
    return ret
  }
}

<|endoftext|>


<commit_before>var StampView = function(stamp){
  this.stamp = stamp;
  this.$el = $("
<form><input class='commitBox' type='textbox' placeholder='Random Commit Message'></input><button class='randomCommit'>Get Random Commit!</button></form>
")
}

StampView.prototype = {
  getDate:function(){
    var $el = $("
")
    var date = new Date(this.stamp.createdAt)
    var month = date.getMonth() + 1
    var display = month + "/" + date.getDate() + "/" + date.getFullYear()
    $el.html(display)
    console.log(this.stamp.createdAt)
    console.log(date)
    this.$el.append($el);
  },
  getLangData:function(){
    var langSum = 0;
    var langArray = d3.entries(this.stamp.data.langTotals)
      .map(function(d){
        langSum += d.value;
        return d;
      })
    langArray.map(function(d){
      var v = d.value;
      d.value = {
        total: v,
        avg: v/langSum*100
      }
      return d;
    })
    langArray.sort(function(b,a){return a.value.total-b.value.total})
    return {langArray:langArray,langSum:langSum};
  },
  makeScale:function(domainMax,rangeMax,direction){
    var scale = d3.scale.linear();
    if (direction == 'asc'){
      scale.domain([0,domainMax]).range([0,rangeMax])
    }else{
      scale.domain([0,domainMax]).range([rangeMax,0])
    }
    return scale;
  },
  makeStampSvg:function(stampsDiv){
    return d3.select(stampsDiv[0]).append('svg').attr('height',400).attr('width',500);
  },
  makeLangComposite:function(svg,langArray,langSum){
    var xScale = this.makeScale(langSum,500,'asc');
    var g = svg.append('g').attr('transform','translate(0,20)')
    g.selectAll('rect').data(langArray).enter().append('rect')
      .attr('transform',function(d,i){
        var offset = 0;
        for (var b=0;b<i;b++){
          offset += xScale(langArray[b].value.total);
        }
        return 'translate('+offset+',0)'
      })
      .style('height',20)
      .style('width',function(d){
        var w = xScale(d.value.total);
        return w;
      })
      .style('fill',function(d){
        return githubColors[d.key];
      })
      .on("mouseover",langHover)
      .on("mousemove",langHover)
      .on("mouseleave",langUnhover);
  },
  getRepoLangData:function(){
    var data = d3.entries(this.stamp.data.languages)
      .map(function(d){
        var values = d3.entries(d.value).filter(function(a){return a.key!=='meta'})
          .sort(function(a,b){return a.value - b.value})
        return {
          key: d.key,
          meta: d.value.meta,
          value: values
        }
      });
    var repoMax = 0;
    var repoLangs = data
      .filter(function(d){return d.value.length>0})
      .sort(function(a,b){
        return Date.parse(a.meta['last-modified']) - Date.parse(b.meta['last-modified'])
      })
    repoLangs.forEach(function(repo){
      var thisSum = 0;
      for (var i=0;i<repo.value.length;i++){
        thisSum+=repo.value[i].value;
      }
      if(thisSum > repoMax){repoMax = thisSum}
    });
    return {repoLangs:repoLangs, repoMax:repoMax};
  },
  makeRepoComposite:function(svg,data,repoMax){
    var xScale = this.makeScale(data.length,450,'asc');
    var yScale = this.makeScale(repoMax,300,'asc');
    var yAxisScale = this.makeScale(repoMax,300,'desc')
    var w = 450/data.length;

    var yAxis = d3.svg.axis().orient('left').scale(yAxisScale)
      .tickFormat(d3.format('s'));
    svg.append('svg:g').attr('class','yaxis').call(yAxis)
      .attr('transform','translate(45,50)')

    var repos = svg.selectAll('.repo').data(data).enter().append('g').attr('class','repo')
      .attr('transform',function(d,i){return 'translate('+(xScale(i)+50)+',0)'});
    repos.selectAll('rect').data(function(d){return d.value}).enter().append('rect')
      .attr('height',function(d){
        return yScale(d.value)
      })
      .attr('width',w)
      .attr('transform',function(d,i){
        var vals = d3.select(this.parentNode).datum().value;
        var offset = (-50);
        for (var b=0;b<i;b++){
          offset += yScale(vals[b].value);
        }
        return 'translate(0,'+(300-offset-yScale(d.value))+')'
      })
      .style('fill',function(d){return githubColors[d.key]})
      .on("mouseover",repoHover)
      .on("mousemove",repoHover)
      .on("mouseleave",repoUnhover);
  },
  render: function(stampsDiv){
    var self = this;
    this.getDate();
    var langSummary = this.getLangData();
    var langArray = langSummary.langArray;
    var langSum = langSummary.langSum;
    var langSvg = d3.select(stampsDiv[0]).append('svg').attr('class','languageComposite')
      .attr('height',60).attr('width',500);

    var repoSummary = this.getRepoLangData();
    var repoLangs = repoSummary.repoLangs;
    var repoMax = repoSummary.repoMax;
    var repoSvg = d3.select(stampsDiv[0]).append('svg').attr('class','languageRepos')
      .attr('height',400).attr('width',500);

    this.makeLangComposite(langSvg,langArray,langSum);
    this.makeRepoComposite(repoSvg,repoLangs,repoMax);
    this.makeChartButtons(stampsDiv);

    var randomCommitButton = this.$el.find('.randomCommit');
    randomCommitButton.on("click", function(e){
      e.preventDefault();
      self.getRandomCommit();
    });

    var chartInputs = this.$el.find('input[name=chartFields]');
    chartInputs.on("change", function(e){
      e.preventDefault();
      self.transitionRepoComposite(this.value, repoSvg,repoMax);
    });
  },
  transitionRepoComposite:function(field, svg, repoMax){
    var max = (field=='percent')?1:repoMax;
    var yScale = this.makeScale(max,300,'asc');
    var yAxisScale = this.makeScale(max,300,'desc');
    var tickFormat = (field=='percent')?'p':'s';
    var yAxis = d3.svg.axis().orient('left').scale(yAxisScale)
      .tickFormat(d3.format(tickFormat));
    svg.select('.yaxis').transition().duration(500).call(yAxis);
    var repos = svg.selectAll('.repo');
    repos.selectAll('rect').transition().duration(500)
      .style('height',function(d){
        var vals = d3.select(this.parentNode).datum().value;
        var sum = d3.sum(vals, function(d) { return d.value; });
        var val = (field=='percent')?d.value/sum:d.value;
        var h = (field=='percent')?yScale(d.value/sum):yScale(d.value);
        return h;
      })
      .attr('transform',function(d,i){
        var vals = d3.select(this.parentNode).datum().value;
        var sum = d3.sum(vals, function(d) { return d.value; });
        var offset = (-50);
        for (var b=0;b<i;b++){
          var off = (field=='percent')?yScale(vals[b].value/sum):yScale(vals[b].value);
          offset += off;
        }
        var h = (field=='percent')?yScale(d.value/sum):yScale(d.value);
        return 'translate(0,'+(300-offset-h)+')'
      })

  },
  getRandomCommit:function(){
    repos = this.stamp.data.commitMessages;
    var commits = [];
    for (repo in repos){
      array = repos[repo];
      for (var i=0;i<array.length;i++){
        commits.push(array[i]);
      }
    };
    randomCommit = commits[Math.round(commits.length*Math.random(),0)];
    this.$el.find('.commitBox').val(randomCommit);
  },
  makeChartButtons:function(stampsDiv){
    var btnForm = $("<form/>");
    var btnFieldset = $("<fieldset/>");
    btnFieldset.append('<input type="radio" value="bytes" name="chartFields" checked>');
    btnFieldset.append('Bytes');
    btnFieldset.append('
')
    btnFieldset.append('<input type="radio" value="percent" name="chartFields">')
    btnFieldset.append('Percentage');
    btnForm.append(btnFieldset);
    stampsDiv.append(btnForm);
  }
}
function langHover(d){
  d3.select('.tooltip')
    .style('top',(d3.event.pageY+10)+"px")
    .style('left',(d3.event.pageX+10)+"px")
    .text(d.key+": "+d.value.total+ " bytes, "+d3.round(d.value.avg,2)+"%")
    .style('opacity',1)
}
function langUnhover(d){
  d3.select('.tooltip').style('opacity',0)
}
function repoHover(d){
  var parentNode = d3.select(this.parentNode).datum();
  var repo = parentNode.key;
  var sum = d3.sum(parentNode.value, function(d) { return d.value; });
  d3.select('.tooltip')
    .style('top',(d3.event.pageY+10)+"px")
    .style('left',(d3.event.pageX+10)+"px")
    .text(repo+": "+d.key+": "+d.value+" bytes, "+d3.round((d.value/sum*100),2)+"%")
    .style('opacity',1)
}
function repoUnhover(d){
  d3.select('.tooltip').style('opacity',0)
}
<commit_msg>remove unwanted console.logs<commit_after>var StampView = function(stamp){
  this.stamp = stamp;
  this.$el = $("
<form><input class='commitBox' type='textbox' placeholder='Random Commit Message'></input><button class='randomCommit'>Get Random Commit!</button></form>
")
}

StampView.prototype = {
  getDate:function(){
    var $el = $("
")
    var date = new Date(this.stamp.createdAt)
    var month = date.getMonth() + 1
    var display = month + "/" + date.getDate() + "/" + date.getFullYear()
    $el.html(display)
    this.$el.append($el);
  },
  getLangData:function(){
    var langSum = 0;
    var langArray = d3.entries(this.stamp.data.langTotals)
      .map(function(d){
        langSum += d.value;
        return d;
      })
    langArray.map(function(d){
      var v = d.value;
      d.value = {
        total: v,
        avg: v/langSum*100
      }
      return d;
    })
    langArray.sort(function(b,a){return a.value.total-b.value.total})
    return {langArray:langArray,langSum:langSum};
  },
  makeScale:function(domainMax,rangeMax,direction){
    var scale = d3.scale.linear();
    if (direction == 'asc'){
      scale.domain([0,domainMax]).range([0,rangeMax])
    }else{
      scale.domain([0,domainMax]).range([rangeMax,0])
    }
    return scale;
  },
  makeStampSvg:function(stampsDiv){
    return d3.select(stampsDiv[0]).append('svg').attr('height',400).attr('width',500);
  },
  makeLangComposite:function(svg,langArray,langSum){
    var xScale = this.makeScale(langSum,500,'asc');
    var g = svg.append('g').attr('transform','translate(0,20)')
    g.selectAll('rect').data(langArray).enter().append('rect')
      .attr('transform',function(d,i){
        var offset = 0;
        for (var b=0;b<i;b++){
          offset += xScale(langArray[b].value.total);
        }
        return 'translate('+offset+',0)'
      })
      .style('height',20)
      .style('width',function(d){
        var w = xScale(d.value.total);
        return w;
      })
      .style('fill',function(d){
        return githubColors[d.key];
      })
      .on("mouseover",langHover)
      .on("mousemove",langHover)
      .on("mouseleave",langUnhover);
  },
  getRepoLangData:function(){
    var data = d3.entries(this.stamp.data.languages)
      .map(function(d){
        var values = d3.entries(d.value).filter(function(a){return a.key!=='meta'})
          .sort(function(a,b){return a.value - b.value})
        return {
          key: d.key,
          meta: d.value.meta,
          value: values
        }
      });
    var repoMax = 0;
    var repoLangs = data
      .filter(function(d){return d.value.length>0})
      .sort(function(a,b){
        return Date.parse(a.meta['last-modified']) - Date.parse(b.meta['last-modified'])
      })
    repoLangs.forEach(function(repo){
      var thisSum = 0;
      for (var i=0;i<repo.value.length;i++){
        thisSum+=repo.value[i].value;
      }
      if(thisSum > repoMax){repoMax = thisSum}
    });
    return {repoLangs:repoLangs, repoMax:repoMax};
  },
  makeRepoComposite:function(svg,data,repoMax){
    var xScale = this.makeScale(data.length,450,'asc');
    var yScale = this.makeScale(repoMax,300,'asc');
    var yAxisScale = this.makeScale(repoMax,300,'desc')
    var w = 450/data.length;

    var yAxis = d3.svg.axis().orient('left').scale(yAxisScale)
      .tickFormat(d3.format('s'));
    svg.append('svg:g').attr('class','yaxis').call(yAxis)
      .attr('transform','translate(45,50)')

    var repos = svg.selectAll('.repo').data(data).enter().append('g').attr('class','repo')
      .attr('transform',function(d,i){return 'translate('+(xScale(i)+50)+',0)'});
    repos.selectAll('rect').data(function(d){return d.value}).enter().append('rect')
      .attr('height',function(d){
        return yScale(d.value)
      })
      .attr('width',w)
      .attr('transform',function(d,i){
        var vals = d3.select(this.parentNode).datum().value;
        var offset = (-50);
        for (var b=0;b<i;b++){
          offset += yScale(vals[b].value);
        }
        return 'translate(0,'+(300-offset-yScale(d.value))+')'
      })
      .style('fill',function(d){return githubColors[d.key]})
      .on("mouseover",repoHover)
      .on("mousemove",repoHover)
      .on("mouseleave",repoUnhover);
  },
  render: function(stampsDiv){
    var self = this;
    this.getDate();
    var langSummary = this.getLangData();
    var langArray = langSummary.langArray;
    var langSum = langSummary.langSum;
    var langSvg = d3.select(stampsDiv[0]).append('svg').attr('class','languageComposite')
      .attr('height',60).attr('width',500);

    var repoSummary = this.getRepoLangData();
    var repoLangs = repoSummary.repoLangs;
    var repoMax = repoSummary.repoMax;
    var repoSvg = d3.select(stampsDiv[0]).append('svg').attr('class','languageRepos')
      .attr('height',400).attr('width',500);

    this.makeLangComposite(langSvg,langArray,langSum);
    this.makeRepoComposite(repoSvg,repoLangs,repoMax);
    this.makeChartButtons(stampsDiv);

    var randomCommitButton = this.$el.find('.randomCommit');
    randomCommitButton.on("click", function(e){
      e.preventDefault();
      self.getRandomCommit();
    });

    var chartInputs = this.$el.find('input[name=chartFields]');
    chartInputs.on("change", function(e){
      e.preventDefault();
      self.transitionRepoComposite(this.value, repoSvg,repoMax);
    });
  },
  transitionRepoComposite:function(field, svg, repoMax){
    var max = (field=='percent')?1:repoMax;
    var yScale = this.makeScale(max,300,'asc');
    var yAxisScale = this.makeScale(max,300,'desc');
    var tickFormat = (field=='percent')?'p':'s';
    var yAxis = d3.svg.axis().orient('left').scale(yAxisScale)
      .tickFormat(d3.format(tickFormat));
    svg.select('.yaxis').transition().duration(500).call(yAxis);
    var repos = svg.selectAll('.repo');
    repos.selectAll('rect').transition().duration(500)
      .style('height',function(d){
        var vals = d3.select(this.parentNode).datum().value;
        var sum = d3.sum(vals, function(d) { return d.value; });
        var val = (field=='percent')?d.value/sum:d.value;
        var h = (field=='percent')?yScale(d.value/sum):yScale(d.value);
        return h;
      })
      .attr('transform',function(d,i){
        var vals = d3.select(this.parentNode).datum().value;
        var sum = d3.sum(vals, function(d) { return d.value; });
        var offset = (-50);
        for (var b=0;b<i;b++){
          var off = (field=='percent')?yScale(vals[b].value/sum):yScale(vals[b].value);
          offset += off;
        }
        var h = (field=='percent')?yScale(d.value/sum):yScale(d.value);
        return 'translate(0,'+(300-offset-h)+')'
      })

  },
  getRandomCommit:function(){
    repos = this.stamp.data.commitMessages;
    var commits = [];
    for (repo in repos){
      array = repos[repo];
      for (var i=0;i<array.length;i++){
        commits.push(array[i]);
      }
    };
    randomCommit = commits[Math.round(commits.length*Math.random(),0)];
    this.$el.find('.commitBox').val(randomCommit);
  },
  makeChartButtons:function(stampsDiv){
    var btnForm = $("<form/>");
    var btnFieldset = $("<fieldset/>");
    btnFieldset.append('<input type="radio" value="bytes" name="chartFields" checked>');
    btnFieldset.append('Bytes');
    btnFieldset.append('
')
    btnFieldset.append('<input type="radio" value="percent" name="chartFields">')
    btnFieldset.append('Percentage');
    btnForm.append(btnFieldset);
    stampsDiv.append(btnForm);
  }
}
function langHover(d){
  d3.select('.tooltip')
    .style('top',(d3.event.pageY+10)+"px")
    .style('left',(d3.event.pageX+10)+"px")
    .text(d.key+": "+d.value.total+ " bytes, "+d3.round(d.value.avg,2)+"%")
    .style('opacity',1)
}
function langUnhover(d){
  d3.select('.tooltip').style('opacity',0)
}
function repoHover(d){
  var parentNode = d3.select(this.parentNode).datum();
  var repo = parentNode.key;
  var sum = d3.sum(parentNode.value, function(d) { return d.value; });
  d3.select('.tooltip')
    .style('top',(d3.event.pageY+10)+"px")
    .style('left',(d3.event.pageX+10)+"px")
    .text(repo+": "+d.key+": "+d.value+" bytes, "+d3.round((d.value/sum*100),2)+"%")
    .style('opacity',1)
}
function repoUnhover(d){
  d3.select('.tooltip').style('opacity',0)
}
<|endoftext|>


<commit_before>'use strict';

var Promise = require('es6-promise').Promise;
var promiseSeries = require('promise-series-node');

module.exports = function(array, testCallback) {
  testCallback = testCallback || function() {}

  return new Promise(function(resolve, reject) {
    if(!(array instanceof Array) || !array.length) { //empty/non arrays can't pass any test
      return resolve(false);
    }
    promiseSeries(array, testCallback).then(function(results) {
      resolve(results.every(testCallback));
    });
  });
};<commit_msg>Optimization - only run re-callback on the final result from every-series<commit_after>'use strict';

var Promise = require('es6-promise').Promise;
var promiseSeries = require('promise-series-node');

module.exports = function(array, testCallback) {
  return new Promise(function(resolve, reject) {
    if(!(testCallback instanceof Function)) {
      return resolve(false);
    }
    if(!(array instanceof Array) || !array.length) { //empty/non arrays can't pass any test
      return resolve(false);
    }
    promiseSeries(array, testCallback).then(function(results) {
      //have to double check last result, since promise-series will return
      //the halt-condition's failure value in the results.
      resolve(testCallback(results[results.length - 1]));
    });
  });
};<|endoftext|>