<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var os = require('os');
var TimeMock = require('time-mock');

var allocCluster = require('./lib/alloc-cluster.js');
var nullStatsd = require('uber-statsd-client/null');
var TChannelStatsd = require('../lib/statsd');
var timers = TimeMock(Date.now());

allocCluster.test('emits stats on call success', {
    numPeers: 2,
    channelOptions: {
        timers: timers,
        emitConnectionMetrics: true
    }
}, function t(cluster, assert) {
    var server = cluster.channels[0];
    var client = cluster.channels[1];
    var serverHost = cluster.hosts[0]
        .split(':')[0]
        .replace(/\./g, '-');
    var statsd = nullStatsd(9);

    server.makeSubChannel({
        serviceName: 'reservoir'
    }).register('Reservoir::get', function get(req, res, h, b) {
        timers.setTimeout(function onSend() {
            res.headers.as = 'raw';
            res.sendOk(h, b);
        }, 500);
        timers.advance(500);
    });

    client.statTags = client.options.statTags = {
        app: 'pool',
        host: os.hostname(),
        cluster: 'c0',
        version: '1.0'
    };
    client.channelStatsd = new TChannelStatsd(client, statsd);
    var clientChan = client.makeSubChannel({
        serviceName: 'reservoir',
        peers: [server.hostPort],
        requestDefaults: {
            headers: {
                as: 'raw',
                cn: 'wat'
            },
            timeout: 1000
        }
    });

    clientChan.request({
        serviceName: 'reservoir',
        hasNoParent: true,
        headers: {
            cn: 'inPipe'
        }
    }).send('Reservoir::get', 'ton', '20', onResponse);

    function onResponse(err, res, arg2, arg3) {
        if (err) {
            return assert.end(err);
        }

        client.flushStats();

        assert.ok(res.ok, 'res should be ok');
        assert.deepEqual(statsd._buffer._elements, [{
            type: 'c',
            name: 'tchannel.outbound.calls.sent.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 1,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.connections.initiated.' + serverHost,
            value: null,
            delta: 1,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.outbound.request.size.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 109,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.connections.bytes-sent.127-0-0-1',
            value: null,
            delta: 109,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.inbound.response.size.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 67,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.connections.bytes-recvd.127-0-0-1',
            value: null,
            delta: 67,
            time: null
        }, {
            type: 'ms',
            name: 'tchannel.outbound.calls.per-attempt-latency.inPipe.reservoir.Reservoir--get.0',
            value: null,
            delta: null,
            time: 500
        }, {
            type: 'c',
            name: 'tchannel.outbound.calls.success.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 1,
            time: null
        }, {
            type: 'ms',
            name: 'tchannel.outbound.calls.latency.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: null,
            time: 500
        }], 'stats keys/values as expected');

        assert.end();
    }
});

allocCluster.test('emits stats on p2p call success', {
    numPeers: 2,
    channelOptions: {
        timers: timers,
        emitConnectionMetrics: true
    }
}, function t(cluster, assert) {
    var server = cluster.channels[0];
    var client = cluster.channels[1];
    var serverHost = cluster.hosts[0]
        .split(':')[0]
        .replace(/\./g, '-');
    var statsd = nullStatsd(8);

    server.makeSubChannel({
        serviceName: 'reservoir'
    }).register('Reservoir::get', function get(req, res, h, b) {
        timers.setTimeout(function onSend() {
            res.headers.as = 'raw';
            res.sendOk(h, b);
        }, 500);
        timers.advance(500);
    });

    client.statTags = client.options.statTags = {
        app: 'pool',
        host: os.hostname(),
        cluster: 'c0',
        version: '1.0'
    };
    client.channelStatsd = new TChannelStatsd(client, statsd);
    var clientChan = client.makeSubChannel({
        serviceName: 'reservoir',
        peers: [server.hostPort],
        requestDefaults: {
            headers: {
                as: 'raw',
                cn: 'wat'
            },
            timeout: 1000
        }
    });

    clientChan.waitForIdentified({
        host: server.hostPort
    }, function onIdentified(err) {
        assert.ifError(err);

        clientChan.request({
            host: server.hostPort,
            serviceName: 'reservoir',
            hasNoParent: true,
            headers: {
                cn: 'inPipe'
            }
        }).send('Reservoir::get', 'ton', '20', onResponse);
    });

    function onResponse(err, res, arg2, arg3) {
        if (err) {
            return assert.end(err);
        }

        client.flushStats();

        assert.ok(res.ok, 'res should be ok');
        assert.deepEqual(statsd._buffer._elements, [{
            type: 'c',
            name: 'tchannel.connections.initiated.' + serverHost,
            value: null,
            delta: 1,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.outbound.calls.sent.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 1,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.outbound.request.size.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 109,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.connections.bytes-sent.127-0-0-1',
            value: null,
            delta: 109,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.inbound.response.size.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 67,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.connections.bytes-recvd.127-0-0-1',
            value: null,
            delta: 67,
            time: null
        }, {
            type: 'ms',
            name: 'tchannel.outbound.calls.per-attempt-latency.inPipe.reservoir.Reservoir--get.0',
            value: null,
            delta: null,
            time: 500
        }, {
            type: 'c',
            name: 'tchannel.outbound.calls.success.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 1,
            time: null
        }], 'stats keys/values as expected');

        assert.end();
    }
});

allocCluster.test('emits stats with no connection metrics', {
    numPeers: 2,
    channelOptions: {
        timers: timers,
        emitConnectionMetrics: false
    }
}, function t(cluster, assert) {
    var server = cluster.channels[0];
    var client = cluster.channels[1];
    var statsd = nullStatsd(6);

    server.makeSubChannel({
        serviceName: 'reservoir'
    }).register('Reservoir::get', function get(req, res, h, b) {
        timers.setTimeout(function onSend() {
            res.headers.as = 'raw';
            res.sendOk(h, b);
        }, 500);
        timers.advance(500);
    });

    client.statTags = client.options.statTags = {
        app: 'pool',
        host: os.hostname(),
        cluster: 'c0',
        version: '1.0'
    };
    client.channelStatsd = new TChannelStatsd(client, statsd);
    var clientChan = client.makeSubChannel({
        serviceName: 'reservoir',
        peers: [server.hostPort],
        requestDefaults: {
            headers: {
                as: 'raw',
                cn: 'wat'
            },
            timeout: 1000
        }
    });

    clientChan.request({
        serviceName: 'reservoir',
        hasNoParent: true,
        headers: {
            cn: 'inPipe'
        }
    }).send('Reservoir::get', 'ton', '20', onResponse);

    function onResponse(err, res, arg2, arg3) {
        if (err) {
            return assert.end(err);
        }

        client.flushStats();

        assert.ok(res.ok, 'res should be ok');
        assert.deepEqual(statsd._buffer._elements, [{
            type: 'c',
            name: 'tchannel.outbound.calls.sent.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 1,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.outbound.request.size.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 109,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.inbound.response.size.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 67,
            time: null
        }, {
            type: 'ms',
            name: 'tchannel.outbound.calls.per-attempt-latency.inPipe.reservoir.Reservoir--get.0',
            value: null,
            delta: null,
            time: 500
        }, {
            type: 'c',
            name: 'tchannel.outbound.calls.success.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 1,
            time: null
        }, {
            type: 'ms',
            name: 'tchannel.outbound.calls.latency.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: null,
            time: 500
        }], 'stats keys/values as expected');

        assert.end();
    }
});

allocCluster.test('emits stats on call failure', {
    numPeers: 2,
    channelOptions: {
        timers: timers,
        emitConnectionMetrics: true
    }
}, function t(cluster, assert) {
    var server = cluster.channels[0];
    var client = cluster.channels[1];
    var serverHost = cluster.hosts[0]
        .split(':')[0]
        .replace(/\./g, '-');
    var statsd = nullStatsd(10);

    server.makeSubChannel({
        serviceName: 'reservoir'
    }).register('Reservoir::get', function get(req, res, h, b) {
        timers.setTimeout(function onSend() {
            res.headers.as = 'raw';
            res.sendNotOk(h, '0');
        }, 500);
        timers.advance(500);
    });

    client.statTags = client.options.statTags = {
        app: 'pool',
        host: os.hostname(),
        cluster: 'c0',
        version: '1.0'
    };
    client.channelStatsd = new TChannelStatsd(client, statsd);
    var clientChan = client.makeSubChannel({
        serviceName: 'reservoir',
        peers: [server.hostPort],
        requestDefaults: {
            headers: {
                as: 'raw',
                cn: 'wat'
            }
        }
    });

    clientChan.request({
        serviceName: 'reservoir',
        hasNoParent: true,
        headers: {
            cn: 'inPipe'
        },
        timeout: 1000
    }).send('Reservoir::get', 'ton', '20', onResponse);

    function onResponse(err, res, arg2, arg3) {
        if (err) {
            return assert.end(err);
        }

        client.flushStats();

        assert.ok(res.ok === false, 'res should be not ok');
        assert.deepEqual(statsd._buffer._elements, [{
            type: 'c',
            name: 'tchannel.outbound.calls.sent.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 1,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.connections.initiated.' + serverHost,
            value: null,
            delta: 1,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.outbound.request.size.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 109,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.connections.bytes-sent.127-0-0-1',
            value: null,
            delta: 109,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.inbound.response.size.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 66,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.connections.bytes-recvd.127-0-0-1',
            value: null,
            delta: 66,
            time: null
        }, {
            type: 'ms',
            name: 'tchannel.outbound.calls.per-attempt-latency.inPipe.reservoir.Reservoir--get.0',
            value: null,
            delta: null,
            time: 500
        }, {
            type: 'c',
            name: 'tchannel.outbound.calls.per-attempt.app-errors.inPipe.reservoir.Reservoir--get.unknown.0',
            value: null,
            delta: 1,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.outbound.calls.app-errors.inPipe.reservoir.Reservoir--get.unknown',
            value: null,
            delta: 1,
            time: null
        }, {
            type: 'ms',
            name: 'tchannel.outbound.calls.latency.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: null,
            time: 500
        }], 'stats keys/values as expected');

        assert.end();
    }
});
<commit_msg>test/request-with-statsd: more atomic assertions<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var os = require('os');
var TimeMock = require('time-mock');

var allocCluster = require('./lib/alloc-cluster.js');
var nullStatsd = require('uber-statsd-client/null');
var TChannelStatsd = require('../lib/statsd');
var timers = TimeMock(Date.now());

allocCluster.test('emits stats on call success', {
    numPeers: 2,
    channelOptions: {
        timers: timers,
        emitConnectionMetrics: true
    }
}, function t(cluster, assert) {
    var server = cluster.channels[0];
    var client = cluster.channels[1];
    var serverHost = cluster.hosts[0]
        .split(':')[0]
        .replace(/\./g, '-');
    var statsd = nullStatsd(9);

    server.makeSubChannel({
        serviceName: 'reservoir'
    }).register('Reservoir::get', function get(req, res, h, b) {
        timers.setTimeout(function onSend() {
            res.headers.as = 'raw';
            res.sendOk(h, b);
        }, 500);
        timers.advance(500);
    });

    client.statTags = client.options.statTags = {
        app: 'pool',
        host: os.hostname(),
        cluster: 'c0',
        version: '1.0'
    };
    client.channelStatsd = new TChannelStatsd(client, statsd);
    var clientChan = client.makeSubChannel({
        serviceName: 'reservoir',
        peers: [server.hostPort],
        requestDefaults: {
            headers: {
                as: 'raw',
                cn: 'wat'
            },
            timeout: 1000
        }
    });

    clientChan.request({
        serviceName: 'reservoir',
        hasNoParent: true,
        headers: {
            cn: 'inPipe'
        }
    }).send('Reservoir::get', 'ton', '20', onResponse);

    function onResponse(err, res, arg2, arg3) {
        if (err) {
            return assert.end(err);
        }

        client.flushStats();

        assert.ok(res.ok, 'res should be ok');
        [{
            type: 'c',
            name: 'tchannel.outbound.calls.sent.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 1,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.connections.initiated.' + serverHost,
            value: null,
            delta: 1,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.outbound.request.size.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 109,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.connections.bytes-sent.127-0-0-1',
            value: null,
            delta: 109,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.inbound.response.size.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 67,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.connections.bytes-recvd.127-0-0-1',
            value: null,
            delta: 67,
            time: null
        }, {
            type: 'ms',
            name: 'tchannel.outbound.calls.per-attempt-latency.inPipe.reservoir.Reservoir--get.0',
            value: null,
            delta: null,
            time: 500
        }, {
            type: 'c',
            name: 'tchannel.outbound.calls.success.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 1,
            time: null
        }, {
            type: 'ms',
            name: 'tchannel.outbound.calls.latency.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: null,
            time: 500
        }].forEach(function eachExpectedElement(elt, i, arr) {
            if (i === 0) {
                assert.equal(
                    statsd._buffer._elements.length, arr.length,
                    'expected number of stat elements');
            }
            assert.deepEqual(statsd._buffer._elements[i], elt,
                             'stat[' + i + '] keys/values as expected');
        });

        assert.end();
    }
});

allocCluster.test('emits stats on p2p call success', {
    numPeers: 2,
    channelOptions: {
        timers: timers,
        emitConnectionMetrics: true
    }
}, function t(cluster, assert) {
    var server = cluster.channels[0];
    var client = cluster.channels[1];
    var serverHost = cluster.hosts[0]
        .split(':')[0]
        .replace(/\./g, '-');
    var statsd = nullStatsd(8);

    server.makeSubChannel({
        serviceName: 'reservoir'
    }).register('Reservoir::get', function get(req, res, h, b) {
        timers.setTimeout(function onSend() {
            res.headers.as = 'raw';
            res.sendOk(h, b);
        }, 500);
        timers.advance(500);
    });

    client.statTags = client.options.statTags = {
        app: 'pool',
        host: os.hostname(),
        cluster: 'c0',
        version: '1.0'
    };
    client.channelStatsd = new TChannelStatsd(client, statsd);
    var clientChan = client.makeSubChannel({
        serviceName: 'reservoir',
        peers: [server.hostPort],
        requestDefaults: {
            headers: {
                as: 'raw',
                cn: 'wat'
            },
            timeout: 1000
        }
    });

    clientChan.waitForIdentified({
        host: server.hostPort
    }, function onIdentified(err) {
        assert.ifError(err);

        clientChan.request({
            host: server.hostPort,
            serviceName: 'reservoir',
            hasNoParent: true,
            headers: {
                cn: 'inPipe'
            }
        }).send('Reservoir::get', 'ton', '20', onResponse);
    });

    function onResponse(err, res, arg2, arg3) {
        if (err) {
            return assert.end(err);
        }

        client.flushStats();

        assert.ok(res.ok, 'res should be ok');
        [{
            type: 'c',
            name: 'tchannel.connections.initiated.' + serverHost,
            value: null,
            delta: 1,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.outbound.calls.sent.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 1,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.outbound.request.size.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 109,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.connections.bytes-sent.127-0-0-1',
            value: null,
            delta: 109,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.inbound.response.size.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 67,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.connections.bytes-recvd.127-0-0-1',
            value: null,
            delta: 67,
            time: null
        }, {
            type: 'ms',
            name: 'tchannel.outbound.calls.per-attempt-latency.inPipe.reservoir.Reservoir--get.0',
            value: null,
            delta: null,
            time: 500
        }, {
            type: 'c',
            name: 'tchannel.outbound.calls.success.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 1,
            time: null
        }].forEach(function eachExpectedElement(elt, i, arr) {
            if (i === 0) {
                assert.equal(
                    statsd._buffer._elements.length, arr.length,
                    'expected number of stat elements');
            }
            assert.deepEqual(statsd._buffer._elements[i], elt,
                             'stat[' + i + '] keys/values as expected');
        });

        assert.end();
    }
});

allocCluster.test('emits stats with no connection metrics', {
    numPeers: 2,
    channelOptions: {
        timers: timers,
        emitConnectionMetrics: false
    }
}, function t(cluster, assert) {
    var server = cluster.channels[0];
    var client = cluster.channels[1];
    var statsd = nullStatsd(6);

    server.makeSubChannel({
        serviceName: 'reservoir'
    }).register('Reservoir::get', function get(req, res, h, b) {
        timers.setTimeout(function onSend() {
            res.headers.as = 'raw';
            res.sendOk(h, b);
        }, 500);
        timers.advance(500);
    });

    client.statTags = client.options.statTags = {
        app: 'pool',
        host: os.hostname(),
        cluster: 'c0',
        version: '1.0'
    };
    client.channelStatsd = new TChannelStatsd(client, statsd);
    var clientChan = client.makeSubChannel({
        serviceName: 'reservoir',
        peers: [server.hostPort],
        requestDefaults: {
            headers: {
                as: 'raw',
                cn: 'wat'
            },
            timeout: 1000
        }
    });

    clientChan.request({
        serviceName: 'reservoir',
        hasNoParent: true,
        headers: {
            cn: 'inPipe'
        }
    }).send('Reservoir::get', 'ton', '20', onResponse);

    function onResponse(err, res, arg2, arg3) {
        if (err) {
            return assert.end(err);
        }

        client.flushStats();

        assert.ok(res.ok, 'res should be ok');
        [{
            type: 'c',
            name: 'tchannel.outbound.calls.sent.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 1,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.outbound.request.size.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 109,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.inbound.response.size.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 67,
            time: null
        }, {
            type: 'ms',
            name: 'tchannel.outbound.calls.per-attempt-latency.inPipe.reservoir.Reservoir--get.0',
            value: null,
            delta: null,
            time: 500
        }, {
            type: 'c',
            name: 'tchannel.outbound.calls.success.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 1,
            time: null
        }, {
            type: 'ms',
            name: 'tchannel.outbound.calls.latency.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: null,
            time: 500
        }].forEach(function eachExpectedElement(elt, i, arr) {
            if (i === 0) {
                assert.equal(
                    statsd._buffer._elements.length, arr.length,
                    'expected number of stat elements');
            }
            assert.deepEqual(statsd._buffer._elements[i], elt,
                             'stat[' + i + '] keys/values as expected');
        });

        assert.end();
    }
});

allocCluster.test('emits stats on call failure', {
    numPeers: 2,
    channelOptions: {
        timers: timers,
        emitConnectionMetrics: true
    }
}, function t(cluster, assert) {
    var server = cluster.channels[0];
    var client = cluster.channels[1];
    var serverHost = cluster.hosts[0]
        .split(':')[0]
        .replace(/\./g, '-');
    var statsd = nullStatsd(10);

    server.makeSubChannel({
        serviceName: 'reservoir'
    }).register('Reservoir::get', function get(req, res, h, b) {
        timers.setTimeout(function onSend() {
            res.headers.as = 'raw';
            res.sendNotOk(h, '0');
        }, 500);
        timers.advance(500);
    });

    client.statTags = client.options.statTags = {
        app: 'pool',
        host: os.hostname(),
        cluster: 'c0',
        version: '1.0'
    };
    client.channelStatsd = new TChannelStatsd(client, statsd);
    var clientChan = client.makeSubChannel({
        serviceName: 'reservoir',
        peers: [server.hostPort],
        requestDefaults: {
            headers: {
                as: 'raw',
                cn: 'wat'
            }
        }
    });

    clientChan.request({
        serviceName: 'reservoir',
        hasNoParent: true,
        headers: {
            cn: 'inPipe'
        },
        timeout: 1000
    }).send('Reservoir::get', 'ton', '20', onResponse);

    function onResponse(err, res, arg2, arg3) {
        if (err) {
            return assert.end(err);
        }

        client.flushStats();

        assert.ok(res.ok === false, 'res should be not ok');
        [{
            type: 'c',
            name: 'tchannel.outbound.calls.sent.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 1,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.connections.initiated.' + serverHost,
            value: null,
            delta: 1,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.outbound.request.size.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 109,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.connections.bytes-sent.127-0-0-1',
            value: null,
            delta: 109,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.inbound.response.size.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: 66,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.connections.bytes-recvd.127-0-0-1',
            value: null,
            delta: 66,
            time: null
        }, {
            type: 'ms',
            name: 'tchannel.outbound.calls.per-attempt-latency.inPipe.reservoir.Reservoir--get.0',
            value: null,
            delta: null,
            time: 500
        }, {
            type: 'c',
            name: 'tchannel.outbound.calls.per-attempt.app-errors.inPipe.reservoir.Reservoir--get.unknown.0',
            value: null,
            delta: 1,
            time: null
        }, {
            type: 'c',
            name: 'tchannel.outbound.calls.app-errors.inPipe.reservoir.Reservoir--get.unknown',
            value: null,
            delta: 1,
            time: null
        }, {
            type: 'ms',
            name: 'tchannel.outbound.calls.latency.inPipe.reservoir.Reservoir--get',
            value: null,
            delta: null,
            time: 500
        }].forEach(function eachExpectedElement(elt, i, arr) {
            if (i === 0) {
                assert.equal(
                    statsd._buffer._elements.length, arr.length,
                    'expected number of stat elements');
            }
            assert.deepEqual(statsd._buffer._elements[i], elt,
                             'stat[' + i + '] keys/values as expected');
        });

        assert.end();
    }
});
<|endoftext|>


<commit_before>var expect = require('chai').expect;
var runner = require('../runner');

describe('python runner', function() {
  // These specs are compatible with both Python 2 and 3
  ['2', '3', '3.6'].forEach(lv => {
    describe('.run', function() {
      runner.assertCodeExamples('python', lv);

      it('should handle basic code evaluation', function(done) {
        runner.run({
          language: 'python',
          languageVersion: lv,
          code: 'import sys; sys.stdout.write("42")'
        }, function(buffer) {
          expect(buffer.stdout).to.equal('42');
          done();
        });
      });
      it('stderr', function(done) {
        runner.run({
          language: 'python',
          languageVersion: lv,
          code: 'import sys; sys.stderr.write("Error!  Codewars cannot and will not accept any more Fibonacci kata.")'
        }, function(buffer) {
          expect(buffer.stderr).to.equal("Error!  Codewars cannot and will not accept any more Fibonacci kata.");
          done();
        });
      });
      it('stderr', function(done) {
        runner.run({
          language: 'python',
          languageVersion: lv,
          code: 'import sys; sys.stderr.write("florp"); sys.stdout.write("foop")'
        }, function(buffer) {
          expect(buffer.stderr).to.equal("florp");
          expect(buffer.stdout).to.equal("foop");
          done();
        });
      });
    });
    describe('cw-2', function() {
      it('should handle a basic assertion', function(done) {
        runner.run({
          language: 'python',
          languageVersion: lv,
          code: 'a = 1',
          fixture: 'test.expect(a == 1)',
          testFramework: 'cw-2'
        }, function(buffer) {
          expect(buffer.stdout).to.equal('\n<PASSED::>Test Passed\n');
          done();
        });
      });
      it('should handle a basic assert_equals', function(done) {
        runner.run({
          language: 'python',
          languageVersion: lv,
          code: 'a = 1',
          fixture: 'test.assert_equals(a, 1)',
          testFramework: 'cw-2'
        }, function(buffer) {
          expect(buffer.stdout).to.equal('\n<PASSED::>Test Passed\n');
          done();
        });
      });
      it('should handle a basic setup', function(done) {
        runner.run({
          language: 'python',
          languageVersion: lv,
          code: 'a = 1',
          setup: 'b = 2',
          fixture: 'test.assert_equals(b, 2)',
          testFramework: 'cw-2'
        }, function(buffer) {
          expect(buffer.stdout).to.equal('\n<PASSED::>Test Passed\n');
          done();
        });
      });
      it('should handle a failed assertion', function(done) {
        runner.run({
          language: 'python',
          languageVersion: lv,
          code: 'a = 1',
          fixture: 'test.expect(a == 2)',
          testFramework: 'cw-2'
        }, function(buffer) {
          expect(buffer.stdout).to.equal('\n<FAILED::>Value is not what was expected\n');
          done();
        });
      });

      it('should handle a failed assertion', function(done) {
        runner.run({
          language: 'python',
          languageVersion: lv,
          code: 'a.fail()',
          testFramework: 'cw-2'
        }, function(buffer) {
          expect(buffer.stderr).to.not.contain('File ');
          expect(buffer.stderr).to.not.contain(', line ');
          expect(buffer.stderr).to.not.contain('most recent call last');
          done();
        });
      });

      it('should support project mode', function(done) {
        runner.run({
          language: 'python',
          languageVersion: lv,
          testFramework: 'cw-2',
          files: {'spec.py': 'Test.expect(True)'}
        }, function(buffer) {
          expect(buffer.stdout).to.include('\n<PASSED::>');
          done();
        });
      });
    });
    describe('unittest', function() {
      it('should handle a basic assertion', function(done) {
        runner.run({
          language: 'python',
          languageVersion: lv,
          code: 'a = 1',
          fixture: [
            'class Test(unittest.TestCase):',
            '  def test_assert(self):',
            '    self.assertEqual(a, 1)'
          ].join('\n'),
          testFramework: 'unittest'
        }, function(buffer) {
          expect(buffer.stdout).to.contain('\n<PASSED::>Test Passed\n');
          done();
        });
      });
      it('should include test names', function(done) {
        runner.run({
          language: 'python',
          languageVersion: lv,
          code: 'a = 1',
          fixture: [
            'class Test(unittest.TestCase):',
            '  def test_assert(self):',
            '    self.assertEqual(a, 1)'
          ].join('\n'),
          testFramework: 'unittest'
        }, function(buffer) {
          expect(buffer.stdout).to.contain('\n<IT::>test_assert');
          done();
        });
      });
      it('should handle a failed assetion', function(done) {
        runner.run({
          language: 'python',
          languageVersion: lv,
          code: 'a = 1',
          fixture: [
            'class Test(unittest.TestCase):',
            '  def test_assert(self):',
            '    self.assertEqual(a, 2, "test failed")'
          ].join('\n'),
          testFramework: 'unittest'
        }, function(buffer) {
          expect(buffer.stdout).to.contain('\n<FAILED::>');
          expect(buffer.stdout).to.contain('test failed');
          done();
        });
      });
      it('should handle a failed assetion', function(done) {
        runner.run({
          language: 'python',
          code: 'a = 1',
          languageVersion: lv,
          fixture: [
            'class Test(unittest.TestCase):',
            '  def test_assert(self):',
            '    raise Exception("exceptions are my favorite, I always throw them")'
          ].join('\n'),
          testFramework: 'unittest'
        }, function(buffer) {
          expect(buffer.stdout).to.contain('\n<ERROR::>Unhandled Exception: exceptions are my favorite, I always throw them\n');
          done();
        });
      });
    });
  });
});

describe('Output format commands', function() {
  for (const v of ['2', '3', '3.6']) {
    it(`should be on independent lines (Python${v} cw-2)`, function(done) {
      runner.run({
        language: 'python',
        languageVersion: v,
        testFramework: 'cw-2',
        code: 'a = 1',
        fixture: [
          `import sys`,
          `sys.stdout.write('foo')`,
          `test.assert_equals(a, 2)`,
        ].join('\n'),
      }, function(buffer) {
        expect(buffer.stdout).to.include('\n<FAILED::>');
        done();
      });
    });

    it(`should be on independent lines (Python${v} unittest)`, function(done) {
      runner.run({
        language: 'python',
        languageVersion: v,
        testFramework: 'unittest',
        code: 'a = 1',
        fixture: [
          `import sys`,
          `class Test(unittest.TestCase):`,
          `  def test_assert(self):`,
          `    sys.stdout.write('foo')`,
          `    self.assertEqual(a, 2)`,
        ].join('\n'),
      }, function(buffer) {
        expect(buffer.stdout).to.include('\n<FAILED::>');
        done();
      });
    });
  }
});
<commit_msg>Add python cw-2 spec on fixed and new features<commit_after>var expect = require('chai').expect;
var runner = require('../runner');

describe('python runner', function() {
  // These specs are compatible with both Python 2 and 3
  ['2', '3', '3.6'].forEach(lv => {
    describe('.run', function() {
      runner.assertCodeExamples('python', lv);

      it('should handle basic code evaluation', function(done) {
        runner.run({
          language: 'python',
          languageVersion: lv,
          code: 'import sys; sys.stdout.write("42")'
        }, function(buffer) {
          expect(buffer.stdout).to.equal('42');
          done();
        });
      });
      it('stderr', function(done) {
        runner.run({
          language: 'python',
          languageVersion: lv,
          code: 'import sys; sys.stderr.write("Error!  Codewars cannot and will not accept any more Fibonacci kata.")'
        }, function(buffer) {
          expect(buffer.stderr).to.equal("Error!  Codewars cannot and will not accept any more Fibonacci kata.");
          done();
        });
      });
      it('stderr', function(done) {
        runner.run({
          language: 'python',
          languageVersion: lv,
          code: 'import sys; sys.stderr.write("florp"); sys.stdout.write("foop")'
        }, function(buffer) {
          expect(buffer.stderr).to.equal("florp");
          expect(buffer.stdout).to.equal("foop");
          done();
        });
      });
    });
    describe('cw-2', function() {
      it('should handle a basic assertion', function(done) {
        runner.run({
          language: 'python',
          languageVersion: lv,
          code: 'a = 1',
          fixture: 'test.expect(a == 1)',
          testFramework: 'cw-2'
        }, function(buffer) {
          expect(buffer.stdout).to.equal('\n<PASSED::>Test Passed\n');
          done();
        });
      });
      it('should handle a basic assert_equals', function(done) {
        runner.run({
          language: 'python',
          languageVersion: lv,
          code: 'a = 1',
          fixture: 'test.assert_equals(a, 1)',
          testFramework: 'cw-2'
        }, function(buffer) {
          expect(buffer.stdout).to.equal('\n<PASSED::>Test Passed\n');
          done();
        });
      });
      it('should handle a basic setup', function(done) {
        runner.run({
          language: 'python',
          languageVersion: lv,
          code: 'a = 1',
          setup: 'b = 2',
          fixture: 'test.assert_equals(b, 2)',
          testFramework: 'cw-2'
        }, function(buffer) {
          expect(buffer.stdout).to.equal('\n<PASSED::>Test Passed\n');
          done();
        });
      });
      it('should handle a failed assertion', function(done) {
        runner.run({
          language: 'python',
          languageVersion: lv,
          code: 'a = 1',
          fixture: 'test.expect(a == 2)',
          testFramework: 'cw-2'
        }, function(buffer) {
          expect(buffer.stdout).to.equal('\n<FAILED::>Value is not what was expected\n');
          done();
        });
      });

      it('should handle a failed assertion', function(done) {
        runner.run({
          language: 'python',
          languageVersion: lv,
          code: 'a.fail()',
          testFramework: 'cw-2'
        }, function(buffer) {
          expect(buffer.stderr).to.not.contain('File ');
          expect(buffer.stderr).to.not.contain(', line ');
          expect(buffer.stderr).to.not.contain('most recent call last');
          done();
        });
      });

      it('should support project mode', function(done) {
        runner.run({
          language: 'python',
          languageVersion: lv,
          testFramework: 'cw-2',
          files: {'spec.py': 'Test.expect(True)'}
        }, function(buffer) {
          expect(buffer.stdout).to.include('\n<PASSED::>');
          done();
        });
      });
    });
    describe('unittest', function() {
      it('should handle a basic assertion', function(done) {
        runner.run({
          language: 'python',
          languageVersion: lv,
          code: 'a = 1',
          fixture: [
            'class Test(unittest.TestCase):',
            '  def test_assert(self):',
            '    self.assertEqual(a, 1)'
          ].join('\n'),
          testFramework: 'unittest'
        }, function(buffer) {
          expect(buffer.stdout).to.contain('\n<PASSED::>Test Passed\n');
          done();
        });
      });
      it('should include test names', function(done) {
        runner.run({
          language: 'python',
          languageVersion: lv,
          code: 'a = 1',
          fixture: [
            'class Test(unittest.TestCase):',
            '  def test_assert(self):',
            '    self.assertEqual(a, 1)'
          ].join('\n'),
          testFramework: 'unittest'
        }, function(buffer) {
          expect(buffer.stdout).to.contain('\n<IT::>test_assert');
          done();
        });
      });
      it('should handle a failed assetion', function(done) {
        runner.run({
          language: 'python',
          languageVersion: lv,
          code: 'a = 1',
          fixture: [
            'class Test(unittest.TestCase):',
            '  def test_assert(self):',
            '    self.assertEqual(a, 2, "test failed")'
          ].join('\n'),
          testFramework: 'unittest'
        }, function(buffer) {
          expect(buffer.stdout).to.contain('\n<FAILED::>');
          expect(buffer.stdout).to.contain('test failed');
          done();
        });
      });
      it('should handle a failed assetion', function(done) {
        runner.run({
          language: 'python',
          code: 'a = 1',
          languageVersion: lv,
          fixture: [
            'class Test(unittest.TestCase):',
            '  def test_assert(self):',
            '    raise Exception("exceptions are my favorite, I always throw them")'
          ].join('\n'),
          testFramework: 'unittest'
        }, function(buffer) {
          expect(buffer.stdout).to.contain('\n<ERROR::>Unhandled Exception: exceptions are my favorite, I always throw them\n');
          done();
        });
      });
    });
  });
});

describe('Output format commands', function() {
  for (const v of ['2', '3', '3.6']) {
    it(`should be on independent lines (Python${v} cw-2)`, function(done) {
      runner.run({
        language: 'python',
        languageVersion: v,
        testFramework: 'cw-2',
        code: 'a = 1',
        fixture: [
          `import sys`,
          `sys.stdout.write('foo')`,
          `test.assert_equals(a, 2)`,
        ].join('\n'),
      }, function(buffer) {
        expect(buffer.stdout).to.include('\n<FAILED::>');
        done();
      });
    });

    it(`should be on independent lines (Python${v} unittest)`, function(done) {
      runner.run({
        language: 'python',
        languageVersion: v,
        testFramework: 'unittest',
        code: 'a = 1',
        fixture: [
          `import sys`,
          `class Test(unittest.TestCase):`,
          `  def test_assert(self):`,
          `    sys.stdout.write('foo')`,
          `    self.assertEqual(a, 2)`,
        ].join('\n'),
      }, function(buffer) {
        expect(buffer.stdout).to.include('\n<FAILED::>');
        done();
      });
    });
  }
});

describe('Fixed and new features', function() {
  for (const v of ['2', '3', '3.6']) {
    it(`should not block execution on failed test by default (Python${v} cw-2)`, function(done) {
      runner.run({
        language: 'python',
        languageVersion: v,
        testFramework: 'cw-2',
        code: 'a = 1',
        fixture: [
          `test.assert_equals(a, 2)`,
          `test.assert_equals(a, 1)`,
        ].join('\n'),
      }, function(buffer) {
        expect(buffer.stdout).to.include('\n<PASSED::>');
        done();
      });
    });

    it(`should support legacy style describe (Python${v} cw-2)`, function(done) {
      runner.run({
        language: 'python',
        languageVersion: v,
        testFramework: 'cw-2',
        code: 'a = 1',
        fixture: [
          `test.describe('describe')`,
          `test.assert_equals(a, 1)`,
        ].join('\n'),
      }, function(buffer) {
        expect(buffer.stdout).to.include('\n<DESCRIBE::>describe');
        expect(buffer.stdout).to.include('\n<PASSED::>');
        done();
      });
    });

    it(`should support new style describe (Python${v} cw-2)`, function(done) {
      runner.run({
        language: 'python',
        languageVersion: v,
        testFramework: 'cw-2',
        code: 'a = 1',
        fixture: [
          `@test.describe('describe')`,
          `def describe1():`,
          `  test.assert_equals(a, 1)`,
        ].join('\n'),
      }, function(buffer) {
        expect(buffer.stdout).to.include('\n<DESCRIBE::>describe');
        expect(buffer.stdout).to.include('\n<PASSED::>');
        expect(buffer.stdout).to.include('\n<COMPLETEDIN::>');
        done();
      });
    });

    it(`should support timeout (passing) (Python${v} cw-2)`, function(done) {
      runner.run({
        language: 'python',
        languageVersion: v,
        testFramework: 'cw-2',
        code: 'a = 1',
        fixture: [
          `@test.describe('describe')`,
          `def describe1():`,
          `  @test.timeout(0.01)`,
          `  def dummy(): test.pass_()`,
        ].join('\n'),
      }, function(buffer) {
        expect(buffer.stdout).to.include('\n<DESCRIBE::>describe');
        expect(buffer.stdout).to.include('\n<PASSED::>');
        expect(buffer.stdout).to.include('\n<COMPLETEDIN::>');
        done();
      });
    });

    it(`should support timeout (failing) (Python${v} cw-2)`, function(done) {
      runner.run({
        language: 'python',
        languageVersion: v,
        testFramework: 'cw-2',
        code: 'a = 1',
        fixture: [
          `@test.describe('describe')`,
          `def describe1():`,
          `  @test.timeout(0.01)`,
          `  def count():`,
          `    x = 0`,
          `    while x < 10 ** 9: x += 1`,
          `    test.pass_()`,
        ].join('\n'),
      }, function(buffer) {
        expect(buffer.stdout).to.include('\n<DESCRIBE::>describe');
        expect(buffer.stdout).to.include('\n<FAILED::>Exceeded time limit');
        expect(buffer.stdout).to.include('\n<COMPLETEDIN::>');
        done();
      });
    });
  }
});
<|endoftext|>


<commit_before>require('./autogenHelpers/globals');

const fs = require('fs');
const path = require('path');
const styleSpecJSON = require('../style-spec/v8.json');
const ejs = require('ejs');

const DocJSONBuilder = require('./autogenHelpers/DocJSONBuilder');
const MarkdownBuilder = require('./autogenHelpers/MarkdownBuilder');

if (!styleSpecJSON) {
  console.log('Could not find style spec, try running "yarn run fetch:style:spec"');
  process.exit(1);
}

const layers = [];
const androidVersion = "5.1.3";
const iosVersion = "3.6.1";

const TMPL_PATH = path.join(__dirname, 'templates');
const IOS_OUTPUT_PATH = path.join(__dirname, '..', 'example', 'node_modules', '@mapbox', 'react-native-mapbox-gl', 'ios', 'RCTMGL');
const ANDROID_OUTPUT_PATH = path.join(__dirname, '..', 'example', 'node_modules', '@mapbox', 'react-native-mapbox-gl', 'android', 'rctmgl', 'src', 'main', 'java', 'com', 'mapbox', 'rctmgl', 'components', 'styles');
const JS_OUTPUT_PATH = path.join(__dirname, '..', 'example', 'node_modules', '@mapbox', 'react-native-mapbox-gl', 'javascript', 'utils');

Object.keys(styleSpecJSON.layer.type.values).forEach((layerName) => {
  layers.push({
    name: layerName,
    properties: getPropertiesForLayer(layerName),
  });
});

// add light as a layer
layers.push({ name: 'light', properties: getPropertiesForLight() })

function getPropertiesForLight () {
  const lightAttributes = styleSpecJSON.light;

  const lightProps = getSupportedProperties(lightAttributes).map((attrName) => {
    return Object.assign({}, buildProperties(lightAttributes, attrName), {
      allowedFunctionTypes: [],
    });
  });

  return lightProps;
}

function getPropertiesForLayer (layerName) {
  const paintAttributes = styleSpecJSON[`paint_${layerName}`];
  const layoutAttributes = styleSpecJSON[`layout_${layerName}`];

  const paintProps = getSupportedProperties(paintAttributes).map((attrName) => {
    let prop = buildProperties(paintAttributes, attrName);

    // overrides
    if (['line-width'].includes(attrName)) {
      prop.allowedFunctionTypes = ['camera'];
    }

    return prop;
  });

  const layoutProps = getSupportedProperties(layoutAttributes).map((attrName) => {
    let prop = buildProperties(layoutAttributes, attrName);

    // overrides
    if (['line-join', 'text-max-width', 'text-letter-spacing', 'text-anchor', 'text-justify'].includes(attrName)) {
      prop.allowedFunctionTypes = ['camera'];
    }

    return prop;
  });

  return layoutProps.concat(paintProps);
}

function getSupportedProperties (attributes) {
  return Object.keys(attributes).filter((attrName) => isAttrSupported(attributes[attrName]));
}

function buildProperties (attributes, attrName) {
  return {
    name: camelCase(attrName),
    doc: {
      description: formatDescription(attributes[attrName].doc),
      requires: getRequires(attributes[attrName].requires),
      disabledBy: getDisables(attributes[attrName].requires),
      values: attributes[attrName].values,
    },
    type: attributes[attrName].type,
    value: attributes[attrName].value,
    image: isImage(attrName),
    translate: isTranslate(attrName),
    transition: attributes[attrName].transition,
    support: getAttributeSupport(attributes[attrName]['sdk-support']),
    allowedFunctionTypes: getAllowedFunctionTypes(attributes[attrName]),
  };
}

function formatDescription (description) {
  let words = description.split(' ');

  for (let i = 0; i < words.length; i++) {
    const word = words[i];

    if (word.includes('-')) {
      words[i] = camelCase(word);
    }
  }

  let formattedDescription = words.join(' ');
  return formattedDescription.replace(/\[x, y\]/g, '`{ x: number, y: number }`');
}

function getRequires (requiredItems) {
  let items = [];

  if (!requiredItems) {
    return items;
  }

  for (let item of requiredItems) {
    if (typeof item === 'string') {
      items.push(camelCase(item, '-'));
    }
  }

  return items;
}

function getDisables (disabledItems) {
  let items = [];

  if (!disabledItems) {
    return items;
  }

  for (let item of disabledItems) {
    if (item['!']) {
      items.push(camelCase(item['!'], '-'));
    }
  }

  return items;
}

function isImage (attrName) {
  return attrName.toLowerCase().indexOf('pattern') !== -1 || attrName.toLowerCase().indexOf('image') !== -1;
}

function isTranslate (attrName) {
  return attrName.toLowerCase().indexOf('translate') !== -1;
}

function isAttrSupported (attr) {
  const support = getAttributeSupport(attr['sdk-support']);
  return support.basic.android && support.basic.ios;
}

function getAttributeSupport (sdkSupport) {
  let support = {
    basic: { android: false, ios: false },
    data: { android: false, ios: false },
  };

  const basicSupport = sdkSupport['basic functionality'];
  if (basicSupport.android) {
    support.basic.android = isVersionGTE(androidVersion, basicSupport.android);
  }
  if (basicSupport.ios) {
    support.basic.ios = isVersionGTE(iosVersion, basicSupport.ios);
  }

  const dataDrivenSupport = sdkSupport['data-driven styling'];
  if (dataDrivenSupport && dataDrivenSupport.android) {
    support.data.android = isVersionGTE(androidVersion, dataDrivenSupport.android);
  }
  if (dataDrivenSupport && dataDrivenSupport.ios) {
    support.data.ios = isVersionGTE(iosVersion, dataDrivenSupport.ios);
  }

  if (support.data.ios !== true || support.data.android !== true) {
    support.data.ios = false;
    support.data.android = false;
  }

  return support;
}

function isVersionGTE(version, otherVersion) {
  const v = +version.split('.').join('');
  const ov = +otherVersion.split('.').join('');
  return v >= ov;
}

function getAllowedFunctionTypes (paintAttr) {
  const allowedFunctionTypes = [];

  if (paintAttr['zoom-function']) {
    allowedFunctionTypes.push('camera');
  }

  if (paintAttr['property-function']) {
    allowedFunctionTypes.push('source');
    allowedFunctionTypes.push('composite');
  }

  return allowedFunctionTypes;
}

// autogenerate code
[
  {
    input: path.join(TMPL_PATH, 'RCTMGLStyle.h.ejs'),
    output: path.join(IOS_OUTPUT_PATH, 'RCTMGLStyle.h'),
  },
  {
    input: path.join(TMPL_PATH, 'RCTMGLStyle.m.ejs'),
    output: path.join(IOS_OUTPUT_PATH, 'RCTMGLStyle.m'),
  },
  {
    input: path.join(TMPL_PATH, 'RCTMGLStyleFactory.java.ejs'),
    output: path.join(ANDROID_OUTPUT_PATH, 'RCTMGLStyleFactory.java'),
  },
  {
    input: path.join(TMPL_PATH, 'styleMap.js.ejs'),
    output: path.join(JS_OUTPUT_PATH, 'styleMap.js'),
  }
].forEach(({ input, output }) => {
  console.log(`Generating ${output.split('/').pop()}`);
  const tmpl = ejs.compile(fs.readFileSync(input, 'utf8'), { strict: true });
  fs.writeFileSync(output, tmpl({ layers: layers }));
})

// autogenerate docs
const docBuilder = new DocJSONBuilder(layers);
const markdownBuilder = new MarkdownBuilder();
docBuilder.generate().then(() => markdownBuilder.generate());
<commit_msg>Update supportedLayers function to autogen script<commit_after>require('./autogenHelpers/globals');

const fs = require('fs');
const path = require('path');
const styleSpecJSON = require('../style-spec/v8.json');
const ejs = require('ejs');

const DocJSONBuilder = require('./autogenHelpers/DocJSONBuilder');
const MarkdownBuilder = require('./autogenHelpers/MarkdownBuilder');

if (!styleSpecJSON) {
  console.log('Could not find style spec, try running "yarn run fetch:style:spec"');
  process.exit(1);
}

const layers = [];
const androidVersion = "5.2.0";
const iosVersion = "3.7.0";

const TMPL_PATH = path.join(__dirname, 'templates');
const IOS_OUTPUT_PATH = path.join(__dirname, '..', 'example', 'node_modules', '@mapbox', 'react-native-mapbox-gl', 'ios', 'RCTMGL');
const ANDROID_OUTPUT_PATH = path.join(__dirname, '..', 'example', 'node_modules', '@mapbox', 'react-native-mapbox-gl', 'android', 'rctmgl', 'src', 'main', 'java', 'com', 'mapbox', 'rctmgl', 'components', 'styles');
const JS_OUTPUT_PATH = path.join(__dirname, '..', 'example', 'node_modules', '@mapbox', 'react-native-mapbox-gl', 'javascript', 'utils');

getSupportedLayers(Object.keys(styleSpecJSON.layer.type.values)).forEach((layerName) => {
  layers.push({
    name: layerName,
    properties: getPropertiesForLayer(layerName),
  });
});

// add light as a layer
layers.push({ name: 'light', properties: getPropertiesForLight() })

function getPropertiesForLight () {
  const lightAttributes = styleSpecJSON.light;

  const lightProps = getSupportedProperties(lightAttributes).map((attrName) => {
    return Object.assign({}, buildProperties(lightAttributes, attrName), {
      allowedFunctionTypes: [],
    });
  });

  return lightProps;
}

function getPropertiesForLayer (layerName) {
  const paintAttributes = styleSpecJSON[`paint_${layerName}`];
  const layoutAttributes = styleSpecJSON[`layout_${layerName}`];

  const paintProps = getSupportedProperties(paintAttributes).map((attrName) => {
    let prop = buildProperties(paintAttributes, attrName);

    // overrides
    if (['line-width'].includes(attrName)) {
      prop.allowedFunctionTypes = ['camera'];
    }

    return prop;
  });

  const layoutProps = getSupportedProperties(layoutAttributes).map((attrName) => {
    let prop = buildProperties(layoutAttributes, attrName);

    // overrides
    if (['line-join', 'text-max-width', 'text-letter-spacing', 'text-anchor', 'text-justify', 'text-font'].includes(attrName)) {
      prop.allowedFunctionTypes = ['camera'];
    }

    return prop;
  });

  return layoutProps.concat(paintProps);
}

function getSupportedLayers (layerNames) {
  const layerMap = styleSpecJSON.layer.type.values;

  const supportedLayers = [];
  for (let layerName of layerNames) {
    const layer = layerMap[layerName];
    const support = getAttributeSupport(layer['sdk-support']);

    if (support.basic.android && support.basic.ios) {
      supportedLayers.push(layerName);
    }
  }

  return supportedLayers;
}

function getSupportedProperties (attributes) {
  return Object.keys(attributes).filter((attrName) => isAttrSupported(attributes[attrName]));
}

function buildProperties (attributes, attrName) {
  return {
    name: camelCase(attrName),
    doc: {
      description: formatDescription(attributes[attrName].doc),
      requires: getRequires(attributes[attrName].requires),
      disabledBy: getDisables(attributes[attrName].requires),
      values: attributes[attrName].values,
    },
    type: attributes[attrName].type,
    value: attributes[attrName].value,
    image: isImage(attrName),
    translate: isTranslate(attrName),
    transition: attributes[attrName].transition,
    support: getAttributeSupport(attributes[attrName]['sdk-support']),
    allowedFunctionTypes: getAllowedFunctionTypes(attributes[attrName]),
  };
}

function formatDescription (description) {
  let words = description.split(' ');

  for (let i = 0; i < words.length; i++) {
    const word = words[i];

    if (word.includes('-')) {
      words[i] = camelCase(word);
    }
  }

  let formattedDescription = words.join(' ');
  return formattedDescription.replace(/\[x, y\]/g, '`{ x: number, y: number }`');
}

function getRequires (requiredItems) {
  let items = [];

  if (!requiredItems) {
    return items;
  }

  for (let item of requiredItems) {
    if (typeof item === 'string') {
      items.push(camelCase(item, '-'));
    }
  }

  return items;
}

function getDisables (disabledItems) {
  let items = [];

  if (!disabledItems) {
    return items;
  }

  for (let item of disabledItems) {
    if (item['!']) {
      items.push(camelCase(item['!'], '-'));
    }
  }

  return items;
}

function isImage (attrName) {
  return attrName.toLowerCase().indexOf('pattern') !== -1 || attrName.toLowerCase().indexOf('image') !== -1;
}

function isTranslate (attrName) {
  return attrName.toLowerCase().indexOf('translate') !== -1;
}

function isAttrSupported (attr) {
  const support = getAttributeSupport(attr['sdk-support']);
  return support.basic.android && support.basic.ios;
}

function getAttributeSupport (sdkSupport) {
  let support = {
    basic: { android: false, ios: false },
    data: { android: false, ios: false },
  };

  const basicSupport = sdkSupport['basic functionality'];
  if (basicSupport.android) {
    support.basic.android = isVersionGTE(androidVersion, basicSupport.android);
  }
  if (basicSupport.ios) {
    support.basic.ios = isVersionGTE(iosVersion, basicSupport.ios);
  }

  const dataDrivenSupport = sdkSupport['data-driven styling'];
  if (dataDrivenSupport && dataDrivenSupport.android) {
    support.data.android = isVersionGTE(androidVersion, dataDrivenSupport.android);
  }
  if (dataDrivenSupport && dataDrivenSupport.ios) {
    support.data.ios = isVersionGTE(iosVersion, dataDrivenSupport.ios);
  }

  if (support.data.ios !== true || support.data.android !== true) {
    support.data.ios = false;
    support.data.android = false;
  }

  return support;
}

function isVersionGTE(version, otherVersion) {
  const v = +version.split('.').join('');
  const ov = +otherVersion.split('.').join('');
  return v >= ov;
}

function getAllowedFunctionTypes (paintAttr) {
  const allowedFunctionTypes = [];

  if (paintAttr['zoom-function']) {
    allowedFunctionTypes.push('camera');
  }

  if (paintAttr['property-function']) {
    allowedFunctionTypes.push('source');
    allowedFunctionTypes.push('composite');
  }

  return allowedFunctionTypes;
}

// autogenerate code
[
  {
    input: path.join(TMPL_PATH, 'RCTMGLStyle.h.ejs'),
    output: path.join(IOS_OUTPUT_PATH, 'RCTMGLStyle.h'),
  },
  {
    input: path.join(TMPL_PATH, 'RCTMGLStyle.m.ejs'),
    output: path.join(IOS_OUTPUT_PATH, 'RCTMGLStyle.m'),
  },
  {
    input: path.join(TMPL_PATH, 'RCTMGLStyleFactory.java.ejs'),
    output: path.join(ANDROID_OUTPUT_PATH, 'RCTMGLStyleFactory.java'),
  },
  {
    input: path.join(TMPL_PATH, 'styleMap.js.ejs'),
    output: path.join(JS_OUTPUT_PATH, 'styleMap.js'),
  }
].forEach(({ input, output }) => {
  console.log(`Generating ${output.split('/').pop()}`);
  const tmpl = ejs.compile(fs.readFileSync(input, 'utf8'), { strict: true });
  fs.writeFileSync(output, tmpl({ layers: layers }));
})

// autogenerate docs
const docBuilder = new DocJSONBuilder(layers);
const markdownBuilder = new MarkdownBuilder();
docBuilder.generate().then(() => markdownBuilder.generate());
<|endoftext|>


<commit_before>
'use strict';

var bunyan = require('bunyan');

var logShim = require('thehelp-log-shim');

var logger = logShim('test:scenarios:bunyan');

logger.verbose('this is the default verbose text');

logger.info('this is the info text', {
  data: {
    yes: 'no'
  }
});

logger.warn('this is the warn text', function(arg1, arg2, arg3, arg4) {
  /*jshint unused: false*/
  logger.warn('warn callback!');
});

logger.error('this is the error %s', 'interpolation');

logShim.loadLogger = function(moduleName) {
  var logger = bunyan.createLogger({
    name: moduleName,
    level: 'debug'
  });

  logger.verbose = logger.debug;
  return logger;
};

var second = logShim('test:scenarios:bunyan:custom');

second.verbose('this is the custom verbose text');
<commit_msg>Update bunyan integration test: show monkey-patch<commit_after>
'use strict';

var bunyan = require('bunyan');

var logShim = require('thehelp-log-shim');

var logger = logShim('test:scenarios:bunyan');

// this should not show up; the default level is info
logger.verbose('this is the default verbose text');

logger.info('this is the info text', {
  data: {
    yes: 'no'
  }
});

logger.warn('this is the warn text', function(arg1, arg2, arg3, arg4) {
  /*jshint unused: false*/
  logger.warn('warn callback!');
});

logger.error('this is the error %s', 'interpolation');

// monkey-patching to approximate global configuration
var create = bunyan.createLogger;
bunyan.createLogger = function(options) {
  options.level = 'debug';
  return create(options);
};

// this will show up
var second = logShim('test:scenarios:bunyan:custom');

second.verbose('this is the custom verbose text');
<|endoftext|>


<commit_before>"use strict";

$(function() {
  $("#show-author-books").click(function(e) {
    let authorId = $(this).attr("data-author-id");
    $.get(`/authors/${authorId}.json`, author => {
      let newAuthor = new Author(author)
      $("#author-books-list").html('')
      $("#author-books-list").html(unorderedList());
      let html = newAuthor.listBooks();
      $("#author-books-list ul").append(html);
      });
    });
  });

function Author(author) {
  this.id = author.id
  this.name = author.name
  this.books = alphabetize(author.books, "title")
}

Author.prototype.listBooks = function() {
  let html = ""
  let result = this.books.forEach(book => {
    html += `

${book.title}

    `
  })
  return html
}

$(function(){
  $("#new-author-book-form").hide()
  $("#add-author-book-button").click(function(e) {
    e.preventDefault(e)
    $("#new-author-book-form").toggle()
  })
})
<commit_msg>Refactor author.js<commit_after>"use strict";

// PAGE SETUP & EVENT LISTENERS

$(function() {
  setAuthorListeners();
});

function setAuthorListeners() {
  $("#show-author-books").click(function(e) {
    displayAuthorBooks(e);
  });
};

// AUTHOR MODEL

function Author(author) {
  this.id = author.id
  this.name = author.name
  this.books = alphabetize(author.books, "title")
}

Author.prototype.listBooks = function() {
  let html = ""
  let result = this.books.forEach(book => {
    html += `

${book.title}

    `
  })
  return html
}

// AUTHOR#SHOW FUNCTIONS

function displayAuthorBooks(e) {
  let authorId = parseInt($(e.currentTarget).attr("data-author-id"));
  $.get(`/authors/${authorId}.json`, author => {
    let newAuthor = new Author(author)
    $("#author-books-list").html(unorderedList());
    let html = newAuthor.listBooks();
    $("#author-books-list ul").append(html);
    });
  };
<|endoftext|>


<commit_before>require('./globals');

function preprocess ( input ) {
  var _included = [];

  function createOutput ( filename, data ) {
    var content = "";
    if ( data.trim() !== '' ) {
      content += "// @source: " + filename.replace(SRC_DIR, '.').replace(/\\/g, '/') + "\n\n  " + indent(data);
    }
    return content;
  }

  function commentedOut ( index, content ) {
    while ( (/( |\t)/).test(content.charAt(index - 1)) ) --index;
    return content.substring(index - 2, index) === '//';
  }

  function parseFile ( filename, rootname ) {
    if ( _included[filename] ) return '';
    _included[filename] = true;
    return readFile(filename).replace(/@import\s*?("|')(.*)("|')/g, function(m, openOp, request, closeOp, index, content){
      if ( commentedOut(index, content) ) return m;
      if ( openOp !== closeOp ) {
        throw new SyntaxError('import statement in "' + filename + '" at ' + index + ' has mismatched opening and closing characters.');
      }
      var path = join(rootname, request), data = "", isDirectory = false;
      if ( exists(path) && lstat(path).isDirectory() ) {
        path = join(path, 'index.pegjs');
        isDirectory = !exists(path);
      }
      if ( isDirectory ) {
        path = join(rootname, request);
        walk(path, function(filename, stat){
          data += createOutput(filename, parseFile(filename, stat.dirname)) + "\n\n";
        });
      } else {
        data = parseFile(path, dirname(path));
      }
      return createOutput(path, data.trim());
    });
  }

  return parseFile(input, dirname(input));
}

var parser = preprocess(join(SRC_DIR, 'parser', 'start.pegjs'));

writeFile(join(SRC_DIR, 'parser.pegjs'), parser);

if ( process.argv.indexOf('--no-peg-build') === -1 ) {
  writeFile(join(LIB_DIR, 'parser.js'), buildParser(parser));
}
<commit_msg>build-parser: edited outputting code<commit_after>require('./globals');

function preprocess ( input ) {
  var _included = [];

  function createOutput ( filename, data ) {
    var content = "";
    if ( data.trim() !== '' ) {
      content += "// @source: " + filename.replace(SRC_DIR, '.').replace(/\\/g, '/') + "\n\n  " + indent(data);
    }
    return content;
  }

  function commentedOut ( index, content ) {
    while ( (/( |\t)/).test(content.charAt(index - 1)) ) --index;
    return content.substring(index - 2, index) === '//';
  }

  function parseFile ( filename, rootname ) {
    if ( _included[filename] ) return '';
    _included[filename] = true;
    return readFile(filename).replace(/@import\s*?("|')(.*)("|')/g, function(m, openOp, request, closeOp, index, content){
      if ( commentedOut(index, content) ) return m;
      if ( openOp !== closeOp ) {
        throw new SyntaxError('import statement in "' + filename + '" at ' + index + ' has mismatched opening and closing characters.');
      }
      var path = join(rootname, request), data = "", isDirectory = false;
      if ( exists(path) && lstat(path).isDirectory() ) {
        path = join(path, 'index.pegjs');
        isDirectory = !exists(path);
      }
      if ( isDirectory ) {
        path = join(rootname, request);
        walk(path, function(filename, stat){
          data += createOutput(filename, parseFile(filename, stat.dirname)) + "\n";
        });
      } else {
        data = parseFile(path, dirname(path));
      }
      return createOutput(path, data.trim());
    });
  }

  return parseFile(input, dirname(input));
}

var parser = preprocess(join(SRC_DIR, 'parser', 'start.pegjs'));

writeFile(join(SRC_DIR, 'parser.pegjs'), parser);

if ( process.argv.indexOf('--no-peg-build') === -1 ) {
  writeFile(join(LIB_DIR, 'parser.js'), buildParser(parser));
}
<|endoftext|>


<commit_before>Mousetrap.stopCallback = function(e, element, combo) {
  // stop for input, select, and textarea
  return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA';
};

var Editor = function() {
  this.toolbar = $('#toolbar');
  this.article = $('#editarea article');

  this.connectEvents();
  this.connectShortcuts();

  this.article.focus();
  this.initParagraph();
};

Editor.prototype = {
  events: {
    'click #save-button': 'saveArticle',
    'submit #urlname-modal form':  'saveUrlname',
    'click #draft-button': 'draft',
    'click #publish-button': 'publish',
    'keyup #editarea article': 'keyup',
    'keydown #editarea article': 'keydown',
    'mouseup #editarea article': 'detectState',
    'paste #editarea article': 'paste',
    'click #toolbar [data-command]': 'toolbarCommand'
  },

  connectEvents: function() {
    var _this = this;
    $.each(this.events, function(key, method) {
      var actions = key.split(' ');
      var event = actions.shift();
      var selector = actions.join(' ');
      $(selector).on(event, function(event) {
        _this[method].call(_this, event, this);
      });
    });
  },

  shortcuts: {
    'ctrl+b': 'bold',
    'ctrl+i': 'italic',
    'ctrl+d': 'strikeThrough',
    'ctrl+u': 'underline',
    'ctrl+l': 'createLink',
    'ctrl+shift+l': 'insertUnorderedList',
    'ctrl+shift+o': 'insertOrderedList',
    'ctrl+p': 'p',
    'ctrl+1': 'h1',
    'ctrl+2': 'h2',
    'ctrl+3': 'h3',
    'ctrl+4': 'h4',
    'ctrl+k': 'code',
    'ctrl+q': 'blockquote',
    'ctrl+s': 'saveArticle'
  },

  connectShortcuts: function() {
    var _this = this;
    $.each(this.shortcuts, function(key, method) {
      Mousetrap.bind(key, function(event) {
        event.preventDefault();
        _this[method].call(_this);
      });
    });
  },

  toolbarCommand: function(event, element) {
    event.preventDefault();
    this[$(element).data('command')].call(this);
    this.detectButton();
    this.detectBlocks();
  },

  detectState: function() {
    this.detectButton();
    this.detectBlocks();
  },

  detectButton: function() {
    var _this = this;

    _this.toolbar.find('[data-command]').each(function(index, element) {
      var command = $(element).data('command');
      if (document.queryCommandValue(command) !== 'true') {
        $(element).removeClass('actived');
      } else {
        if (command === 'bold' && /^h/.test(document.queryCommandValue('formatBlock'))) {
          $(element).removeClass('actived');
        } else {
          $(element).addClass('actived');
        }
      }
    });
  },

  detectBlocks: function() {
    var type = document.queryCommandValue('formatBlock');
    var text = this.toolbar.find('#format-block [data-command=' + type + ']').text();
    if (text === '') {
      text = this.toolbar.find('#format-block [data-command]:first').text();
    }
    this.toolbar.find('#format-block .toolbar-botton').text(text);
  },

  paste: function(event) {
    this.dirty = true;
  },

  bold: function() {
    this.exec('bold');
  },

  italic: function() {
    this.exec('italic');
  },

  strikeThrough: function() {
    this.exec('strikeThrough');
  },

  underline: function() {
    this.exec('underline');
  },

  insertOrderedList: function() {
    this.exec('insertOrderedList');
  },

  insertUnorderedList: function() {
    this.exec('insertUnorderedList');
  },

  createLink: function() {
    var url = prompt('Link url:', 'http://');
    if (url !== null && url !== '') {
      this.exec('createLink', url);
    }
  },

  p: function() {
    this.formatBlock('p');
  },

  formatHeader: function(type) {
    if (document.queryCommandValue('formatBlock') === type) {
      this.p();
    } else {
      this.formatBlock(type);
    }
  },

  h1: function() {
    this.formatHeader('h1');
  },

  h2: function() {
    this.formatHeader('h2');
  },

  h3: function() {
    this.formatHeader('h3');
  },

  h4: function() {
    this.formatHeader('h4');
  },

  code: function() {
    var selection = window.getSelection();
    var range = selection.getRangeAt(0);
    var rangeAncestor = range.commonAncestorContainer;
    var start, end, $contents;

    var $code = $(rangeAncestor).closest('code');

    if ($code.length) {
      // remove code
      if ($code.closest('pre').length) {
        // pre code
        this.splitCode($code);
        $contents = $code.contents();
        $code.closest('pre').replaceWith($contents);
        this.selectContents($contents);
      } else {
        // inline code
        $contents = $code.contents();
        $code.replaceWith($code.contents());
        this.selectContents($contents);
      }
    } else {
      // wrap code
      var isEmptyRange = (range.toString() === '');
      var isWholeBlock = (range.toString() === $(range.startContainer).closest('p, h1, h2, h3, h4').text());
      var hasBlock = (range.cloneContents().querySelector('p, h1, h2, h3, h4'));
      if (isEmptyRange || isWholeBlock || hasBlock) {
        // pre code
        start = $(range.startContainer).closest('article > *')[0];
        end = $(range.endContainer).closest('article > *')[0];
        range.setStartBefore(start);
        range.setEndAfter(end);
        $code = $('').html(range.extractContents());
        $pre = $('
').html($code);
        range.insertNode($pre[0]);
        if ($pre.next().length === 0) {
          $('

').insertAfter($pre);
        }
      } else {
        // inline code
        $code = $('').html(range.extractContents());
        range.insertNode($code[0]);
      }
      this.striptCode($code);
      selection.selectAllChildren($code[0]);
    }
  },

  striptCode: function(code) {
    code.find('p, h1, h2, h3, h4').each(function() {
      $(this).replaceWith($(this).text() + "\n");
    }).children().each(function() {
      $(this).replaceWith($(this).text());
    });
  },

  splitCode: function(code) {
    code.html($.map(code.text().split("\n"), function(line) {
      if (line !== '') {
        return $('
').text(line);
      }
    }));
  },

  selectContents: function(contents) {
    var selection = window.getSelection();
    var range = selection.getRangeAt(0);
    var start = contents.first()[0];
    var end = contents.last()[0];
    range.setStart(start, 0);
    range.setEnd(end, end.childNodes.length || end.length); // text node don't have childNodes
    console.log(range);
    selection.removeAllRanges();
    selection.addRange(range);
  },

  blockquote: function() {
    var selection = window.getSelection();
    var range = selection.getRangeAt(0);
    var rangeAncestor = range.commonAncestorContainer;
    var start, end;

    var $blockquote = $(rangeAncestor).closest('blockquote');
    if ($blockquote.length) {
      // remmove blockquote
      var $contents = $blockquote.contents();
      $blockquote.replaceWith($contents);
      this.selectContents($contents);
    } else {
      // wrap blockquote
      start = $(range.startContainer).closest('article > *')[0];
      end = $(range.endContainer).closest('article > *')[0];
      range.setStartBefore(start);
      range.setEndAfter(end);
      $blockquote = $('
');
      $blockquote.html(range.extractContents()).find('blockquote').each(function() {
        $(this).replaceWith($(this).html());
      });
      range.insertNode($blockquote[0]);
      selection.selectAllChildren($blockquote[0]);
      if ($blockquote.next().length === 0) {
        $('

').insertAfter($blockquote);
      }
    }
  },

  formatBlock: function(type) {
    this.exec('formatBlock', type);
  },

  exec: function(command, arg) {
    document.execCommand(command, false, arg);
  },

  keyup: function() {
    this.initParagraph();
    this.detectState();
    this.sanitize();
  },

  keydown: function(event) {
    this.stopEmptyBackspace(event);
  },

  initParagraph: function() {
    // chrome is empty and firefox is

    if (this.article.html() === '' || this.article.html() === '
') {
      this.p();
    }

    // replace div to p
    if (document.queryCommandValue('formatBlock') === 'div') {
      this.p();
    }
  },

  tagWhiteList: ['p', 'br', 'img', 'a', 'b', 'i', 'strike', 'u', 'h1', 'h2', 'h3', 'h4', 'pre', 'code', 'ol', 'ul', 'li', 'blockquote'],

  attrWhiteList: {
    a: ['href', 'title'],
    img: ['src', 'title', 'alt']
  },

  sanitize: function() {
    var _this = this;
    if (this.dirty) {
      // replace div to p
      while(this.article.find('div').length) {
        this.convertDivToP();
      }

      // stript not allow tags
      while(this.article.find(':not(' + this.tagWhiteList.join() + ')').length) {
        this.striptNotAllowTags();
      }

      // flatten block element
      this.article.find('> :not(blockquote, pre)').each(function() {
        _this.flattenBlock(this);
      });
      // blockquote as a document
      this.article.find('> blockquote').find('> :not(blockquote, pre)').each(function() {
        _this.flattenBlock(this);
      });

      // remove all attribute not in attrWhiteList
      var tags = $.map(this.attrWhiteList, function(tag) { return tag; });
      this.article.find(':not(' + tags.join() + ')').each(function() {
        $element = $(this);
        $.each(this.attributes, function(i, attr) {
          if (attr) {
            $element.removeAttr(attr.name);
          }
        });
      });

      // remove attributes not in white list for attrWhiteList
      $.each(this.attrWhiteList, function(tag, attrList) {
        _this.article.find(tag).each(function() {
          $element = $(this);
          $.each(this.attributes, function(i, attr) {
            if (attr && ($.inArray(attr.name, attrList) == -1)) {
              $element.removeAttr(attr.name);
            }
          });
        });
      });
      this.dirty = false;
    }
  },

  convertDivToP: function() {
    this.article.find('div').each(function() {
      // don't use replaceWith, it flash cursor
      $(this).before($(this).html().wrap('
')).remove();
    });
  },

  striptNotAllowTags: function() {
    this.article.find(':not(' + this.tagWhiteList.join() + ')').each(function() {
      // don't use replaceWith, it flash cursor
      $(this).before($(this).html()).remove();
    });
  },

  blockElementSelector: '> p, > h1, > h2, > h3, > h4',

  flattenBlock: function(element) {
    var _this = this;
    var hasNoBlockContent = $(element).contents().filter(function() { return this.nodeType !== 1; }).length;
    if (hasNoBlockContent) {
      // stript block
      this.flattenBlockStript(element);
    } else {
      // split block

      // stript children
      $(element).find(this.blockElementSelector).each(function() {
        _this.flattenBlockStript.call(_this, this);
      });

      // don't replaceWith() for avoid cursor lose.
      $(element).before($(element).html()).remove();
    }
  },

  flattenBlockStript: function(element) {
    while($(element).find(this.blockElementSelector).length) {
      this.flattenBlockStriptExecute.call(this, element);
    }
  },

  flattenBlockStriptExecute: function(element) {
    $(element).find(this.blockElementSelector).each(function() {
      $(this).replaceWith($(this).html());
    });
  },

  stopEmptyBackspace: function(event) {
    // Stop Backspace when empty, avoid cursor flash
    if (event.keyCode === 8) {
      if (this.article.html() === '

') {
        event.preventDefault();
      }
    }
  },

  articleId: function() {
    return this.article.data('article-id');
  },

  saveArticle: function() {
    this.update({
      article: {
        title: this.extractTitle(),
        body: this.extractBody()
      }
    });
  },

  saveUrlname: function() {
    this.update($('#urlname-modal form').serializeArray(), function(data) {
      $('#topbar .urlname').text(data.urlname);
      Dialog.hide('#urlname-modal');
    });
  },

  publish: function() {
    this.update({
      article: {
        publish: true
      }
    }, function(data) {
      $('#draft-button').removeClass('button-actived');
      $('#publish-button').addClass('button-actived');
    });
  },

  draft: function() {
    this.update({
      article: {
        publish: false
      }
    }, function(data) {
      $('#publish-button').removeClass('button-actived');
      $('#draft-button').addClass('button-actived');
    });
  },

  update: function(data, success_callback, error_callback) {
    $.ajax({
      url: '/articles/' + this.articleId(),
      data: data,
      type: 'put',
      dataType: 'json'
    }).success(success_callback).error(error_callback);
  },

  extractTitle: function() {
    return this.article.find('h1').text();
  },

  extractBody: function() {
    return this.article.html();
  }
};

var editor;
$(function() {
  editor = new Editor();
});
<commit_msg>fix code.<commit_after>Mousetrap.stopCallback = function(e, element, combo) {
  // stop for input, select, and textarea
  return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA';
};

var Editor = function() {
  this.toolbar = $('#toolbar');
  this.article = $('#editarea article');

  this.connectEvents();
  this.connectShortcuts();

  this.article.focus();
  this.initParagraph();
};

Editor.prototype = {
  events: {
    'click #save-button': 'saveArticle',
    'submit #urlname-modal form':  'saveUrlname',
    'click #draft-button': 'draft',
    'click #publish-button': 'publish',
    'keyup #editarea article': 'keyup',
    'keydown #editarea article': 'keydown',
    'mouseup #editarea article': 'detectState',
    'paste #editarea article': 'paste',
    'click #toolbar [data-command]': 'toolbarCommand'
  },

  connectEvents: function() {
    var _this = this;
    $.each(this.events, function(key, method) {
      var actions = key.split(' ');
      var event = actions.shift();
      var selector = actions.join(' ');
      $(selector).on(event, function(event) {
        _this[method].call(_this, event, this);
      });
    });
  },

  shortcuts: {
    'ctrl+b': 'bold',
    'ctrl+i': 'italic',
    'ctrl+d': 'strikeThrough',
    'ctrl+u': 'underline',
    'ctrl+l': 'createLink',
    'ctrl+shift+l': 'insertUnorderedList',
    'ctrl+shift+o': 'insertOrderedList',
    'ctrl+p': 'p',
    'ctrl+1': 'h1',
    'ctrl+2': 'h2',
    'ctrl+3': 'h3',
    'ctrl+4': 'h4',
    'ctrl+k': 'code',
    'ctrl+q': 'blockquote',
    'ctrl+s': 'saveArticle'
  },

  connectShortcuts: function() {
    var _this = this;
    $.each(this.shortcuts, function(key, method) {
      Mousetrap.bind(key, function(event) {
        event.preventDefault();
        _this[method].call(_this);
      });
    });
  },

  toolbarCommand: function(event, element) {
    event.preventDefault();
    this[$(element).data('command')].call(this);
    this.detectButton();
    this.detectBlocks();
  },

  detectState: function() {
    this.detectButton();
    this.detectBlocks();
  },

  detectButton: function() {
    var _this = this;

    _this.toolbar.find('[data-command]').each(function(index, element) {
      var command = $(element).data('command');
      if (document.queryCommandValue(command) !== 'true') {
        $(element).removeClass('actived');
      } else {
        if (command === 'bold' && /^h/.test(document.queryCommandValue('formatBlock'))) {
          $(element).removeClass('actived');
        } else {
          $(element).addClass('actived');
        }
      }
    });
  },

  detectBlocks: function() {
    var type = document.queryCommandValue('formatBlock');
    var text = this.toolbar.find('#format-block [data-command=' + type + ']').text();
    if (text === '') {
      text = this.toolbar.find('#format-block [data-command]:first').text();
    }
    this.toolbar.find('#format-block .toolbar-botton').text(text);
  },

  paste: function(event) {
    this.dirty = true;
  },

  bold: function() {
    this.exec('bold');
  },

  italic: function() {
    this.exec('italic');
  },

  strikeThrough: function() {
    this.exec('strikeThrough');
  },

  underline: function() {
    this.exec('underline');
  },

  insertOrderedList: function() {
    this.exec('insertOrderedList');
  },

  insertUnorderedList: function() {
    this.exec('insertUnorderedList');
  },

  createLink: function() {
    var url = prompt('Link url:', 'http://');
    if (url !== null && url !== '') {
      this.exec('createLink', url);
    }
  },

  p: function() {
    this.formatBlock('p');
  },

  formatHeader: function(type) {
    if (document.queryCommandValue('formatBlock') === type) {
      this.p();
    } else {
      this.formatBlock(type);
    }
  },

  h1: function() {
    this.formatHeader('h1');
  },

  h2: function() {
    this.formatHeader('h2');
  },

  h3: function() {
    this.formatHeader('h3');
  },

  h4: function() {
    this.formatHeader('h4');
  },

  code: function() {
    var selection = window.getSelection();
    var range = selection.getRangeAt(0);
    var rangeAncestor = range.commonAncestorContainer;
    var start, end, $contents;

    var $code = $(rangeAncestor).closest('code');

    if ($code.length) {
      // remove code
      if ($code.closest('pre').length) {
        // pre code
        this.splitCode($code);
        $contents = $code.contents();
        if ($contents.length === 0) {
          $contents = $('

');
        }
        $code.closest('pre').replaceWith($contents);
        this.selectContents($contents);
      } else {
        // inline code
        $contents = $code.contents();
        $code.replaceWith($code.contents());
        this.selectContents($contents);
      }
    } else {
      // wrap code
      var isEmptyRange = (range.toString() === '');
      var isWholeBlock = (range.toString() === $(range.startContainer).closest('p, h1, h2, h3, h4').text());
      var hasBlock = (range.cloneContents().querySelector('p, h1, h2, h3, h4'));
      if (isEmptyRange || isWholeBlock || hasBlock) {
        // pre code
        start = $(range.startContainer).closest('article > *')[0];
        end = $(range.endContainer).closest('article > *')[0];
        range.setStartBefore(start);
        range.setEndAfter(end);
        $code = $('').html(range.extractContents());
        $pre = $('
').html($code);
        range.insertNode($pre[0]);
        if ($pre.next().length === 0) {
          $pre.after('

');
        }
      } else {
        // inline code
        $code = $('').html(range.extractContents());
        range.insertNode($code[0]);
      }
      this.striptCode($code);
      selection.selectAllChildren($code[0]);
    }
  },

  striptCode: function(code) {
    code.find('p, h1, h2, h3, h4').each(function() {
      $(this).replaceWith($(this).text() + "\n");
    }).children().each(function() {
      $(this).replaceWith($(this).text());
    });
  },

  splitCode: function(code) {
    code.html($.map(code.text().split("\n"), function(line) {
      if (line !== '') {
        return $('
').text(line);
      }
    }));
  },

  selectContents: function(contents) {
    var selection = window.getSelection();
    var range = selection.getRangeAt(0);
    var start = contents.first()[0];
    var end = contents.last()[0];
    range.setStart(start, 0);
    range.setEnd(end, end.childNodes.length || end.length); // text node don't have childNodes
    selection.removeAllRanges();
    selection.addRange(range);
  },

  blockquote: function() {
    var selection = window.getSelection();
    var range = selection.getRangeAt(0);
    var rangeAncestor = range.commonAncestorContainer;
    var start, end;

    var $blockquote = $(rangeAncestor).closest('blockquote');
    if ($blockquote.length) {
      // remmove blockquote
      var $contents = $blockquote.contents();
      $blockquote.replaceWith($contents);
      this.selectContents($contents);
    } else {
      // wrap blockquote
      start = $(range.startContainer).closest('article > *')[0];
      end = $(range.endContainer).closest('article > *')[0];
      range.setStartBefore(start);
      range.setEndAfter(end);
      $blockquote = $('
');
      $blockquote.html(range.extractContents()).find('blockquote').each(function() {
        $(this).replaceWith($(this).html());
      });
      range.insertNode($blockquote[0]);
      selection.selectAllChildren($blockquote[0]);
      if ($blockquote.next().length === 0) {
        $blockquote.after('

');
      }
    }
  },

  formatBlock: function(type) {
    this.exec('formatBlock', type);
  },

  exec: function(command, arg) {
    document.execCommand(command, false, arg);
  },

  keyup: function() {
    this.initParagraph();
    this.detectState();
    this.sanitize();
  },

  keydown: function(event) {
    this.stopEmptyBackspace(event);
  },

  initParagraph: function() {
    // chrome is empty and firefox is

    if (this.article.html() === '' || this.article.html() === '
') {
      this.p();
    }

    // replace div to p
    if (document.queryCommandValue('formatBlock') === 'div') {
      this.p();
    }
  },

  tagWhiteList: ['p', 'br', 'img', 'a', 'b', 'i', 'strike', 'u', 'h1', 'h2', 'h3', 'h4', 'pre', 'code', 'ol', 'ul', 'li', 'blockquote'],

  attrWhiteList: {
    a: ['href', 'title'],
    img: ['src', 'title', 'alt']
  },

  sanitize: function() {
    var _this = this;
    if (this.dirty) {
      // replace div to p
      while(this.article.find('div').length) {
        this.convertDivToP();
      }

      // stript not allow tags
      while(this.article.find(':not(' + this.tagWhiteList.join() + ')').length) {
        this.striptNotAllowTags();
      }

      // flatten block element
      this.article.find('> :not(blockquote, pre)').each(function() {
        _this.flattenBlock(this);
      });
      // blockquote as a document
      this.article.find('> blockquote').find('> :not(blockquote, pre)').each(function() {
        _this.flattenBlock(this);
      });

      // remove all attribute not in attrWhiteList
      var tags = $.map(this.attrWhiteList, function(tag) { return tag; });
      this.article.find(':not(' + tags.join() + ')').each(function() {
        $element = $(this);
        $.each(this.attributes, function(i, attr) {
          if (attr) {
            $element.removeAttr(attr.name);
          }
        });
      });

      // remove attributes not in white list for attrWhiteList
      $.each(this.attrWhiteList, function(tag, attrList) {
        _this.article.find(tag).each(function() {
          $element = $(this);
          $.each(this.attributes, function(i, attr) {
            if (attr && ($.inArray(attr.name, attrList) == -1)) {
              $element.removeAttr(attr.name);
            }
          });
        });
      });
      this.dirty = false;
    }
  },

  convertDivToP: function() {
    this.article.find('div').each(function() {
      // don't use replaceWith, it flash cursor
      $(this).before($(this).html().wrap('
')).remove();
    });
  },

  striptNotAllowTags: function() {
    this.article.find(':not(' + this.tagWhiteList.join() + ')').each(function() {
      // don't use replaceWith, it flash cursor
      $(this).before($(this).html()).remove();
    });
  },

  blockElementSelector: '> p, > h1, > h2, > h3, > h4',

  flattenBlock: function(element) {
    var _this = this;
    var hasNoBlockContent = $(element).contents().filter(function() { return this.nodeType !== 1; }).length;
    if (hasNoBlockContent) {
      // stript block
      this.flattenBlockStript(element);
    } else {
      // split block

      // stript children
      $(element).find(this.blockElementSelector).each(function() {
        _this.flattenBlockStript.call(_this, this);
      });

      // don't replaceWith() for avoid cursor lose.
      $(element).before($(element).html()).remove();
    }
  },

  flattenBlockStript: function(element) {
    while($(element).find(this.blockElementSelector).length) {
      this.flattenBlockStriptExecute.call(this, element);
    }
  },

  flattenBlockStriptExecute: function(element) {
    $(element).find(this.blockElementSelector).each(function() {
      $(this).replaceWith($(this).html());
    });
  },

  stopEmptyBackspace: function(event) {
    // Stop Backspace when empty, avoid cursor flash
    if (event.keyCode === 8) {
      if (this.article.html() === '

') {
        event.preventDefault();
      }
    }
  },

  articleId: function() {
    return this.article.data('article-id');
  },

  saveArticle: function() {
    this.update({
      article: {
        title: this.extractTitle(),
        body: this.extractBody()
      }
    });
  },

  saveUrlname: function() {
    this.update($('#urlname-modal form').serializeArray(), function(data) {
      $('#topbar .urlname').text(data.urlname);
      Dialog.hide('#urlname-modal');
    });
  },

  publish: function() {
    this.update({
      article: {
        publish: true
      }
    }, function(data) {
      $('#draft-button').removeClass('button-actived');
      $('#publish-button').addClass('button-actived');
    });
  },

  draft: function() {
    this.update({
      article: {
        publish: false
      }
    }, function(data) {
      $('#publish-button').removeClass('button-actived');
      $('#draft-button').addClass('button-actived');
    });
  },

  update: function(data, success_callback, error_callback) {
    $.ajax({
      url: '/articles/' + this.articleId(),
      data: data,
      type: 'put',
      dataType: 'json'
    }).success(success_callback).error(error_callback);
  },

  extractTitle: function() {
    return this.article.find('h1').text();
  },

  extractBody: function() {
    return this.article.html();
  }
};

var editor;
$(function() {
  editor = new Editor();
});
<|endoftext|>