<commit_before>/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

/*
 * Copyright (c) 2017, Joyent, Inc.
 */

var util = require('util');
var EventEmitter = require('events').EventEmitter;

var assert = require('assert-plus');
var libuuid = require('libuuid');
var mod_fast = require('fast');
var mod_kang = require('kang');
var mod_artedi = require('artedi');
var mod_net = require('net');
var mod_os = require('os');
var mod_restify = require('restify');
var VError = require('verror').VError;
var LRU = require('lru-cache');
var vasync = require('vasync');

var control = require('./control');
var buckets = require('./buckets');
var objects = require('./objects');
var ping = require('./ping');
var sql = require('./sql');

///--- Globals

/*
 * API version, increment for depended-upon changes.
 *
 * This should not be incremented lightly: Moray consumers don't currently
 * have a great way to deal with Moray backends of different versions. Work
 * will need to be done there before this can be bumped. See RFD 33 for a
 * longer discussion on this issue.
 */
var API_VERSION = 2;

/*
 * This is the Kang version identifier. For now, we set it to 1.0.0. Someday,
 * we'll hopefully have a better way of grabbing information about the image
 * version and exporting that here.
 */
var KANG_VERSION = '1.0.0';

///--- API

function MorayServer(options) {
    var self = this;

    EventEmitter.call(this);

    var log = options.log;

    // Configure the metric collector.
    var labels = {
        datacenter: options.datacenter,
        server: options.server_uuid,
        zonename: mod_os.hostname(),
        pid: process.pid
    };

    var collector = mod_artedi.createCollector({
        labels: labels
    });

    var db;
    if (options.standalone) {
        options.standalone.log = log;
        options.standalone.collector = collector;
        db = require('./standalone').createClient(options.standalone);
    } else {
        options.manatee.log = log;
        options.manatee.collector = collector;
        db = require('./manatee').createClient(options.manatee);
    }

    this.ms_bucketCache = new LRU({
        name: 'BucketCache',
        max: 100,
        maxAge: (300 * 1000)
    });
    this.ms_objectCache = new LRU({
        name: 'ObjectCache',
        max: 1000,
        maxAge: (30 * 1000)
    });

    var opts = {
        log: options.log,
        manatee: db,
        bucketCache: this.ms_bucketCache,
        objectCache: this.ms_objectCache
    };

    var socket = mod_net.createServer({ 'allowHalfOpen': true });
    var server = new mod_fast.FastServer({
        log: log.child({ component: 'fast' }),
        collector: collector,
        server: socket
    });

    var methods = [
        { rpcmethod: 'createBucket', rpchandler: buckets.creat(opts) },
        { rpcmethod: 'getBucket', rpchandler: buckets.get(opts) },
        { rpcmethod: 'listBuckets', rpchandler: buckets.list(opts) },
        { rpcmethod: 'updateBucket', rpchandler: buckets.update(opts) },
        { rpcmethod: 'delBucket', rpchandler: buckets.del(opts) },
        { rpcmethod: 'putObject', rpchandler: objects.put(opts) },
        { rpcmethod: 'batch', rpchandler: objects.batch(opts) },
        { rpcmethod: 'getObject', rpchandler: objects.get(opts) },
        { rpcmethod: 'delObject', rpchandler: objects.del(opts) },
        { rpcmethod: 'findObjects', rpchandler: objects.find(opts) },
        { rpcmethod: 'updateObjects', rpchandler: objects.update(opts) },
        { rpcmethod: 'reindexObjects', rpchandler: objects.reindex(opts) },
        { rpcmethod: 'deleteMany', rpchandler: objects.deleteMany(opts) },
        { rpcmethod: 'getTokens', rpchandler: getTokens(opts) },
        { rpcmethod: 'sql', rpchandler: sql.sql(opts) },
        { rpcmethod: 'ping', rpchandler: ping.ping(opts) },
        { rpcmethod: 'version', rpchandler: ping.version(opts, API_VERSION) }
    ];

    methods.forEach(function (rpc) {
        server.registerRpcMethod(rpc);
    });

    this.port = options.port;
    this.ip = options.bindip;

    this.fast_socket = socket;
    this.fast_server = server;
    this.monitor_server = null;
    this.db_conn = db;
    this.log = options.log;

    if (options.monitorPort) {
        /*
         * Set up the monitoring server. This exposes a kang monitoring listener
         * and an artedi-based metric collector. Both are exposed on the
         * `monitorPort` port on the `bindip` network.
         *
         * Since we are using the same restify server for both kang and artedi,
         * we will not be using the kang.knStartServer() convenience function.
         */
        this.monitor_server = mod_restify.createServer({
            name: 'Monitor'
        });

        var kangOpts = {
            service_name: 'moray',
            version: KANG_VERSION,
            uri_base: '/kang',
            ident: mod_os.hostname() + '/' + process.pid,
            list_types: server.kangListTypes.bind(server),
            list_objects: server.kangListObjects.bind(server),
            get: server.kangGetObject.bind(server),
            stats: server.kangStats.bind(server)
        };

        this.monitor_server.get('/kang/.*',
                mod_kang.knRestifyHandler(kangOpts));
        this.monitor_server.get('/metrics',
            function getMetricsHandler(req, res, next) {
            req.on('end', function () {
                collector.collect(mod_artedi.FMT_PROM, function (err, metrics) {
                    if (err) {
                        next(new VError(err));
                        return;
                    }
                    res.setHeader('Content-Type', 'text/plain; version=0.0.4');
                    res.send(metrics);
                });
                next();
            });
            req.resume();
        });

        this.monitor_server.listen(options.monitorPort, options.bindip,
            function () {
            self.log.info('monitoring server started on port %d',
                options.monitorPort);
        });
    }

    /*
     * Once both the server socket and the database connection are up,
     * emit the "ready" event to indicate that the database is good to
     * use now.
     *
     * Failure to listen() will be re-emitted on the MorayServer. In a
     * "moray" zone, no one is listening, so the error will be thrown,
     * and SMF will either restart us or take the service into
     * maintenance.
     */
    var sock_ready = false;
    var dbc_ready = false;

    function emitReady() {
        if (sock_ready && dbc_ready) {
            self.emit('ready');
        }
    }

    self.fast_socket.on('listening', function () {
        self.log.info({ address: self.fast_socket.address() },
            'Moray listening on port %d', self.port);
        sock_ready = true;
        emitReady();
    });

    self.fast_socket.on('error', function (err) {
        self.fast_socket = null;
        self.emit('error', err);
    });

    self.db_conn.on('ready', function () {
        dbc_ready = true;
        emitReady();
    });
}
util.inherits(MorayServer, EventEmitter);

MorayServer.prototype.listen = function listen() {
    this.fast_socket.listen(this.port, this.ip);
};

MorayServer.prototype.close = function close() {
    var barrier = vasync.barrier();
    var self = this;

    /*
     * Wait until both the server socket and the DB connection
     * have been shutdown before closing Fast and Kang.
     */
    barrier.on('drain', function () {
        self.fast_server.close();

        if (self.kang_server !== null) {
            self.kang_server.close();
        }

        self.emit('close');
    });

    if (self.fast_socket !== null) {
        barrier.start('close-server-socket');
        self.fast_socket.on('close', function () {
            barrier.done('close-server-socket');
        });
        self.fast_socket.close();
    }

    barrier.start('close-db-conn');
    self.db_conn.on('close', function () {
        barrier.done('close-db-conn');
    });
    self.db_conn.close();
};

function createServer(options) {
    assert.object(options, 'options');
    assert.object(options.log, 'options.log');

    return new MorayServer(options);
}

///--- Exports

module.exports = {
    createServer: createServer
};

///--- Privates

var GET_TOKENS_ARGS = [
    { name: 'options', type: 'options' }
];

function getTokens(options) {
    assert.object(options, 'options');
    assert.object(options.log, 'options.log');

    function _getTokens(rpc) {
        var argv = rpc.argv();
        if (control.invalidArgs(rpc, argv, GET_TOKENS_ARGS)) {
            return;
        }

        var opts = argv[0];
        var id = opts.req_id || libuuid.create();
        var log = options.log.child({
            req_id: id
        });

        log.debug('getTokens: entered');

        rpc.fail(new Error('Operation not supported'));
    }

    return _getTokens;
}
<commit_msg>MORAY-443 Clean shutdown broken by MORAY-421 Reviewed by: Dan McDonald <danmcd@joyent.com> Reviewed by: Jason King <jason.king@joyent.com> Reviewed by: Kody A Kantor <kody.kantor@gmail.com> Approved by: Kody A Kantor <kody.kantor@gmail.com><commit_after>/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

/*
 * Copyright (c) 2017, Joyent, Inc.
 */

var util = require('util');
var EventEmitter = require('events').EventEmitter;

var assert = require('assert-plus');
var libuuid = require('libuuid');
var mod_fast = require('fast');
var mod_kang = require('kang');
var mod_artedi = require('artedi');
var mod_net = require('net');
var mod_os = require('os');
var mod_restify = require('restify');
var VError = require('verror').VError;
var LRU = require('lru-cache');
var vasync = require('vasync');

var control = require('./control');
var buckets = require('./buckets');
var objects = require('./objects');
var ping = require('./ping');
var sql = require('./sql');

///--- Globals

/*
 * API version, increment for depended-upon changes.
 *
 * This should not be incremented lightly: Moray consumers don't currently
 * have a great way to deal with Moray backends of different versions. Work
 * will need to be done there before this can be bumped. See RFD 33 for a
 * longer discussion on this issue.
 */
var API_VERSION = 2;

/*
 * This is the Kang version identifier. For now, we set it to 1.0.0. Someday,
 * we'll hopefully have a better way of grabbing information about the image
 * version and exporting that here.
 */
var KANG_VERSION = '1.0.0';

///--- API

function MorayServer(options) {
    var self = this;

    EventEmitter.call(this);

    var log = options.log;

    // Configure the metric collector.
    var labels = {
        datacenter: options.datacenter,
        server: options.server_uuid,
        zonename: mod_os.hostname(),
        pid: process.pid
    };

    var collector = mod_artedi.createCollector({
        labels: labels
    });

    var db;
    if (options.standalone) {
        options.standalone.log = log;
        options.standalone.collector = collector;
        db = require('./standalone').createClient(options.standalone);
    } else {
        options.manatee.log = log;
        options.manatee.collector = collector;
        db = require('./manatee').createClient(options.manatee);
    }

    this.ms_bucketCache = new LRU({
        name: 'BucketCache',
        max: 100,
        maxAge: (300 * 1000)
    });
    this.ms_objectCache = new LRU({
        name: 'ObjectCache',
        max: 1000,
        maxAge: (30 * 1000)
    });

    var opts = {
        log: options.log,
        manatee: db,
        bucketCache: this.ms_bucketCache,
        objectCache: this.ms_objectCache
    };

    var socket = mod_net.createServer({ 'allowHalfOpen': true });
    var server = new mod_fast.FastServer({
        log: log.child({ component: 'fast' }),
        collector: collector,
        server: socket
    });

    var methods = [
        { rpcmethod: 'createBucket', rpchandler: buckets.creat(opts) },
        { rpcmethod: 'getBucket', rpchandler: buckets.get(opts) },
        { rpcmethod: 'listBuckets', rpchandler: buckets.list(opts) },
        { rpcmethod: 'updateBucket', rpchandler: buckets.update(opts) },
        { rpcmethod: 'delBucket', rpchandler: buckets.del(opts) },
        { rpcmethod: 'putObject', rpchandler: objects.put(opts) },
        { rpcmethod: 'batch', rpchandler: objects.batch(opts) },
        { rpcmethod: 'getObject', rpchandler: objects.get(opts) },
        { rpcmethod: 'delObject', rpchandler: objects.del(opts) },
        { rpcmethod: 'findObjects', rpchandler: objects.find(opts) },
        { rpcmethod: 'updateObjects', rpchandler: objects.update(opts) },
        { rpcmethod: 'reindexObjects', rpchandler: objects.reindex(opts) },
        { rpcmethod: 'deleteMany', rpchandler: objects.deleteMany(opts) },
        { rpcmethod: 'getTokens', rpchandler: getTokens(opts) },
        { rpcmethod: 'sql', rpchandler: sql.sql(opts) },
        { rpcmethod: 'ping', rpchandler: ping.ping(opts) },
        { rpcmethod: 'version', rpchandler: ping.version(opts, API_VERSION) }
    ];

    methods.forEach(function (rpc) {
        server.registerRpcMethod(rpc);
    });

    this.port = options.port;
    this.ip = options.bindip;

    this.fast_socket = socket;
    this.fast_server = server;
    this.monitor_server = null;
    this.db_conn = db;
    this.log = options.log;

    if (options.monitorPort) {
        /*
         * Set up the monitoring server. This exposes a kang monitoring listener
         * and an artedi-based metric collector. Both are exposed on the
         * `monitorPort` port on the `bindip` network.
         *
         * Since we are using the same restify server for both kang and artedi,
         * we will not be using the kang.knStartServer() convenience function.
         */
        this.monitor_server = mod_restify.createServer({
            name: 'Monitor'
        });

        var kangOpts = {
            service_name: 'moray',
            version: KANG_VERSION,
            uri_base: '/kang',
            ident: mod_os.hostname() + '/' + process.pid,
            list_types: server.kangListTypes.bind(server),
            list_objects: server.kangListObjects.bind(server),
            get: server.kangGetObject.bind(server),
            stats: server.kangStats.bind(server)
        };

        this.monitor_server.get('/kang/.*',
                mod_kang.knRestifyHandler(kangOpts));
        this.monitor_server.get('/metrics',
            function getMetricsHandler(req, res, next) {
            req.on('end', function () {
                collector.collect(mod_artedi.FMT_PROM, function (err, metrics) {
                    if (err) {
                        next(new VError(err));
                        return;
                    }
                    res.setHeader('Content-Type', 'text/plain; version=0.0.4');
                    res.send(metrics);
                });
                next();
            });
            req.resume();
        });

        this.monitor_server.listen(options.monitorPort, options.bindip,
            function () {
            self.log.info('monitoring server started on port %d',
                options.monitorPort);
        });
    }

    /*
     * Once both the server socket and the database connection are up,
     * emit the "ready" event to indicate that the database is good to
     * use now.
     *
     * Failure to listen() will be re-emitted on the MorayServer. In a
     * "moray" zone, no one is listening, so the error will be thrown,
     * and SMF will either restart us or take the service into
     * maintenance.
     */
    var sock_ready = false;
    var dbc_ready = false;

    function emitReady() {
        if (sock_ready && dbc_ready) {
            self.emit('ready');
        }
    }

    self.fast_socket.on('listening', function () {
        self.log.info({ address: self.fast_socket.address() },
            'Moray listening on port %d', self.port);
        sock_ready = true;
        emitReady();
    });

    self.fast_socket.on('error', function (err) {
        self.fast_socket = null;
        self.emit('error', err);
    });

    self.db_conn.on('ready', function () {
        dbc_ready = true;
        emitReady();
    });
}
util.inherits(MorayServer, EventEmitter);

MorayServer.prototype.listen = function listen() {
    this.fast_socket.listen(this.port, this.ip);
};

MorayServer.prototype.close = function close() {
    var barrier = vasync.barrier();
    var self = this;

    /*
     * Wait until both the server socket and the DB connection
     * have been shutdown before closing Fast and Kang.
     */
    barrier.on('drain', function () {
        self.fast_server.close();

        if (self.monitor_server !== null) {
            self.monitor_server.close();
        }

        self.emit('close');
    });

    if (self.fast_socket !== null) {
        barrier.start('close-server-socket');
        self.fast_socket.on('close', function () {
            barrier.done('close-server-socket');
        });
        self.fast_socket.close();
    }

    barrier.start('close-db-conn');
    self.db_conn.on('close', function () {
        barrier.done('close-db-conn');
    });
    self.db_conn.close();
};

function createServer(options) {
    assert.object(options, 'options');
    assert.object(options.log, 'options.log');

    return new MorayServer(options);
}

///--- Exports

module.exports = {
    createServer: createServer
};

///--- Privates

var GET_TOKENS_ARGS = [
    { name: 'options', type: 'options' }
];

function getTokens(options) {
    assert.object(options, 'options');
    assert.object(options.log, 'options.log');

    function _getTokens(rpc) {
        var argv = rpc.argv();
        if (control.invalidArgs(rpc, argv, GET_TOKENS_ARGS)) {
            return;
        }

        var opts = argv[0];
        var id = opts.req_id || libuuid.create();
        var log = options.log.child({
            req_id: id
        });

        log.debug('getTokens: entered');

        rpc.fail(new Error('Operation not supported'));
    }

    return _getTokens;
}
<|endoftext|>


<commit_before>var xmpp        = require('node-xmpp');

// Loading all modules needed
var Logger              = require('../modules/logger');
var Router              = require('../modules/router').Router;
var RecipientOffline    = require('../modules/offline');
var SoftwareVersion     = require('../modules/software_version');
var Presence            = require('../modules/presence');
var Roster              = require('../modules/roster');
var DiscoInfo           = require('../modules/disco_info');
var VCard               = require('../modules/vcard');
var Websocket           = require('../modules/websocket');

// Loading non -xmpp libraries
var User = require('../lib/users.js').User;

exports.run = function(config) {

    // That's the way you add mods to a given server.
    SoftwareVersion.name = "Node XMPP server example";
    SoftwareVersion.version = "0.0.0.1";
    SoftwareVersion.os = "Unknown";

    // Creates the server.
    var c2s = new xmpp.C2SServer(config);
    c2s.router = new Router(c2s); // Using the right Router.
    var s2s = new xmpp.Router();

    // Configure the mods at the server level!
    Logger.configure(c2s, s2s);
    RecipientOffline.configure(c2s, s2s);
    SoftwareVersion.configure(c2s, s2s);
    Presence.configure(c2s, s2s);
    Roster.configure(c2s, s2s);
    DiscoInfo.configure(c2s, s2s);
    VCard.configure(c2s, s2s);
    Websocket.configure(c2s, s2s);

    /* ======================================================================================== */
    /* THIS SYNTAX WILL PROBABLY BE DEPRECATED BY ASTRO, BUT HE NEEDS TO SUGGEST A REPLACEMENT! */
    s2s.register(config.domain, function(stanza) {
        c2s.router.route(stanza);
    });
    c2s.s2s = s2s;

    c2s.on('inStanza', function(client, stanza) {
        client.emit('inStanza', stanza);
        // We need to find a way to not call this when any other signal called it!
        // Also, it is a problem as one of the callback may alter the stanza... and we may want to _not_ forward that changed stanza!
        // We have the problem for several examples, like this one: when susbcribing to another user, some clients (Psi) will send <presence type="subscribe" from="julien@zipline.local/res" to="friend@zipline.local" /> the problem is that the we need to clean the from attribute to remove the resource. How can we do that?
        s2s.send(stanza); // Allow for the incoming (from C2SStream) stanzas to be routed... we first route to the S2S component, which will then maybe forward to the C2SServer.. and this one may push it down to the right C2SStream
    });
    /* /THIS SYNTAX WILL PROBABLY BE DEPRECATED BY ASTRO. */
    /* ======================================================================================== */

    // Allows the developer to authenticate users against anything they want.
    c2s.on("authenticate", function(jid, password, client, cb) {
        User.find(jid, function(user) {
            cb(user && user.attrs.password === password);
        });
    });

    // Allows the developer to register the jid against anything they want
    c2s.on("register", function(jid, password, client, cb) {
        User.register(jid, password, {
            success: function() {
                cb(true);
            },
            error: function() {
                cb(false, {code: "409", type: "cancel", text: "conflict"});
            }
        });
    });

    // On Connect event. When a client connects.
    c2s.on("connect", function(client) {
        client.addMixin(Logger.mod);
        client.addMixin(RecipientOffline.mod);
        client.addMixin(SoftwareVersion.mod);
        client.addMixin(Presence.mod);
        client.addMixin(Roster.mod);
        client.addMixin(DiscoInfo.mod);
        client.addMixin(VCard.mod);
    });

    // On Disconnect event. When a client disconnects
    c2s.on("disconnect", function(client) {

    });
}

<commit_msg>server.js: make authenticate & register adhere to node conventions<commit_after>var xmpp        = require('node-xmpp');

// Loading all modules needed
var Logger              = require('../modules/logger');
var Router              = require('../modules/router').Router;
var RecipientOffline    = require('../modules/offline');
var SoftwareVersion     = require('../modules/software_version');
var Presence            = require('../modules/presence');
var Roster              = require('../modules/roster');
var DiscoInfo           = require('../modules/disco_info');
var VCard               = require('../modules/vcard');
var Websocket           = require('../modules/websocket');

// Loading non -xmpp libraries
var User = require('../lib/users.js').User;

exports.run = function(config) {

    // That's the way you add mods to a given server.
    SoftwareVersion.name = "Node XMPP server example";
    SoftwareVersion.version = "0.0.0.1";
    SoftwareVersion.os = "Unknown";

    // Creates the server.
    var c2s = new xmpp.C2SServer(config);
    c2s.router = new Router(c2s); // Using the right Router.
    var s2s = new xmpp.Router();

    // Configure the mods at the server level!
    Logger.configure(c2s, s2s);
    RecipientOffline.configure(c2s, s2s);
    SoftwareVersion.configure(c2s, s2s);
    Presence.configure(c2s, s2s);
    Roster.configure(c2s, s2s);
    DiscoInfo.configure(c2s, s2s);
    VCard.configure(c2s, s2s);
    Websocket.configure(c2s, s2s);

    /* ======================================================================================== */
    /* THIS SYNTAX WILL PROBABLY BE DEPRECATED BY ASTRO, BUT HE NEEDS TO SUGGEST A REPLACEMENT! */
    s2s.register(config.domain, function(stanza) {
        c2s.router.route(stanza);
    });
    c2s.s2s = s2s;

    c2s.on('inStanza', function(client, stanza) {
        client.emit('inStanza', stanza);
        // We need to find a way to not call this when any other signal called it!
        // Also, it is a problem as one of the callback may alter the stanza... and we may want to _not_ forward that changed stanza!
        // We have the problem for several examples, like this one: when susbcribing to another user, some clients (Psi) will send <presence type="subscribe" from="julien@zipline.local/res" to="friend@zipline.local" /> the problem is that the we need to clean the from attribute to remove the resource. How can we do that?
        s2s.send(stanza); // Allow for the incoming (from C2SStream) stanzas to be routed... we first route to the S2S component, which will then maybe forward to the C2SServer.. and this one may push it down to the right C2SStream
    });
    /* /THIS SYNTAX WILL PROBABLY BE DEPRECATED BY ASTRO. */
    /* ======================================================================================== */

    // Allows the developer to authenticate users against anything they want.
    c2s.on("authenticate", function(opts, cb) {
        User.find(opts.jid, function(user) {
            if (user && user.attrs.password === opts.password)
		cb();
	    else
		cb(new Error("Authentication failure"));
        });
    });

    // Allows the developer to register the jid against anything they want
    c2s.on("register", function(opts, cb) {
        User.register(opts.jid, opts.password, {
            success: function() {
                cb(true);
            },
            error: function() {
		var err = new Error("conflict");
		err.code = 409;
		err.type = "cancel";
                cb(err);
            }
        });
    });

    // On Connect event. When a client connects.
    c2s.on("connect", function(client) {
        client.addMixin(Logger.mod);
        client.addMixin(RecipientOffline.mod);
        client.addMixin(SoftwareVersion.mod);
        client.addMixin(Presence.mod);
        client.addMixin(Roster.mod);
        client.addMixin(DiscoInfo.mod);
        client.addMixin(VCard.mod);
    });

    // On Disconnect event. When a client disconnects
    c2s.on("disconnect", function(client) {

    });
}

<|endoftext|>


<commit_before>/*
 * Copyright (c) 2011-2012, Yahoo! Inc.  All rights reserved.
 * Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */
var path = require('path'),
    mojito,
    fs = require('fs'),
    gear = require('gear'),
    async = require('async'),
    mkdirp = require('mkdirp'),
    mime = require('mime'),
    ShakerCore = require('./core').ShakerCore,
    logger = require('./core').logger,
    utils = require('./utils');

/*
 * Buildy wrapper for images.
 *
 * @param name {string} Name of image to build
 * @param file {string} Filename of image
 */
function Image(name, file) {
    this._name = name;
    this._file = file;
}

Image.prototype = {
    push: function (registry, options, callback) {
        var config = utils.simpleClone(options.config);
        config.file = this._name;

        var queue = gear.queue({registry: registry})
            .load([this._file]);

        queue.task(options.task, config, function(err, results) {
            callback(null, results[0].meta.name);
        }).run();
    }
};

/*
 * Buildy wrapper for Javascript/CSS.
 *
 * @param name {string} Name of rollup to build
 * @param files {array} Filenames to rollup
 */
function Rollup(name, files) {
    this._name = name;
    this._files = files;
}

Rollup.prototype = {
    push: function (registry, options, callback) {
        var config = utils.simpleClone(options.config);
        config.file = this._name;

        var queue = gear.queue({registry: registry})
            .load(this._files)
            .concat();

        if (options.minify) {
            queue.task(mime.lookup(this._name) === 'application/javascript' ? 'jsminify' : 'cssminify');
        }

        queue.task(options.task, config, function(err, results) {
            callback(null, results[0].meta.name);
        }).run();
    }
};

/*
 * Buildy wrapper for templates.
 *
 * @param name {string} Name of template to build
 * @param files {array} Filenames to rollup
 */
function ClientRollup(name, base, files) {
    this._name = name;
    this._base = base;
    this._files = files;
}

ClientRollup.prototype = {
    push: function (registry, options, callback) {
        var config = utils.simpleClone(options.config);
        config.file = this._name;

        var queue = gear.queue({registry: registry})
            .load(this._files)
            .concat({
                callback: function (message) {
                    var filename = message.meta.name;
                    var content = message.body;

                    if (mime.lookup(filename) === 'text/html') {
                        var parts = filename.match(/.*\/?mojits\/(.*)\/views.*\/(.*)\.(.*)\.html/),
                            mojit = parts[1],
                            action = parts[2],
                            renderer = parts[3],
                            json = JSON.stringify(content);

                        content = 'YUI.add("views/' + mojit + '/' + action + '", function (Y, NAME) {\n';
                        content += '\tYUI.namespace("_mojito._cache.compiled.' + mojit + '.views");\n';
                        content += '\tYUI._mojito._cache.compiled.' + mojit + '.views.' + action + ' = ' + json + ';\n';
                        content += '});';
                    }

                    return content;
                }
            });

        if (options.minify) {
            queue.jsminify();
        }

        queue.task(options.task, config, function(err, results) {
            callback(null, results[0].meta.name);
        }).run();
    }
};

/**
 * Shaker compiler.
 *
 * Compiler takes metadata generated by the core and depending on the environment context
 * will run tasks against the assets. Tasks including concatenating, minifying, and deploying to
 * a variety of locations (local, cdn).
 *
 * Sample application.json:
 *   ...
 *   {
 *       "settings": ["environment:test"],
 *       "shaker": {
 *           "task": "local"
 *       }
 *   },
 *   ...
 *
 * Example:
 * new Shaker({store: store}).run(function (metadata) {
 *    console.log(metadata);
 * )});
 *
 * @param core {Object} A Shaker core object for a Mojito app.
 */
function Shaker (options) {
    this._core = new ShakerCore(options);

    var shaker = this._core.getConfig();
    this._task = Shaker.DEFAULT_TASK;
    if (shaker.task) { // Task to run when compiling assets
        this._task = shaker.task === 'local' ? 'write' : shaker.task;
    }
    this._compiled_dir = shaker.compiled_dir || Shaker.COMPILED_DIR;
    this._images = shaker.images !== undefined ? shaker.images : false; // Deploy images
    this._parallel = shaker.parallel !== undefined ? shaker.parallel : 20; // How many tasks the async queue runs
    this._delay = shaker.delay !== undefined ? shaker.delay : 0; // Add some network delay for slow connections
    this._lint = shaker.lint !== undefined ? shaker.lint : true; // lint or not
    this._minify = shaker.minify !== undefined ? shaker.minify : true; // Uglify or not
    this._config = shaker.config || {}; // Config object passed through to task
    this._config.root = this._core.getStaticRoot();

    this._registry = gear.registry({module: 'gear-lib'});
    this._registry.load(Shaker.TASKS_DIR);
}

Shaker.DEFAULT_TASK = 'raw';
Shaker.TASKS_DIR = __dirname + '/tasks/'; // Tasks in this directory can be directly referenced in application.json
Shaker.ASSETS_DIR = 'assets/';
Shaker.COMPILED_DIR = Shaker.ASSETS_DIR + 'compiled/'; // Where we write the rollups
Shaker.IMAGES_DIR = Shaker.ASSETS_DIR + 'images/';

Shaker.prototype = {
    /*
     * Start compiling assets.
     *
     * @param callback {function} Callback when compiling is complete.
     */
    run: function (callback) {
        logger.log('[SHAKER] - Analizying application assets to Shake... ');
        var metadata = this._core.run({ignore_dir: this._compiled_dir});

        if (this._lint) {
            this._lintFiles(this._runTask.bind(this, metadata, callback));
        } else {
            this._runTask(metadata, callback);
        }
    },

    _runTask: function(metadata, callback) {
        if (this._task === Shaker.DEFAULT_TASK) {
            this._rename(metadata);
            this._writeMeta(metadata);
            callback(metadata);
        }
        else {
            this._compileRollups(metadata, callback);
        }
    },

    /*
     * Rename assets from local filename to URL.
     *
     * @param metadata {Object} Core metadata to augment.
     */
    _rename: function (metadata) {
        logger.log('[SHAKER] - Processing assets for development env.');
        var mojit, action, dim, item, list, renamed;

        // Mojit assets
        for (mojit in metadata.mojits) {
            for (action in metadata.mojits[mojit]) {
                for (dim in metadata.mojits[mojit][action].shaken) {
                    for (item in metadata.mojits[mojit][action].shaken[dim]) {
                        list = metadata.mojits[mojit][action].shaken[dim];
                        list[item] = this._core.getURL(list[item]);
                    }
                }
                for(item in (list = metadata.mojits[mojit][action].client)){
                    list[item] = this._core.getURL(list[item]);
                }
            }
        }

        // App level assets
        for (action in metadata.app) {
            for (dim in metadata.app[action].shaken) {
                for (item in (list = metadata.app[action].shaken[dim])) {
                    list[item] = this._core.getURL(list[item]);
                }
            }
            for(item in (list = list = metadata.app[action].client)){
                    list[item] = this._core.getURL(list[item]);
            }
        }
        //Core
        for (item in (list = metadata.core)) {
            list[item] = this._core.getURL(list[item]);
        }
    },

    _lintFiles: function (callback) {
        logger.log('[SHAKER] - Linting files...');

        var files = [],
            file,
            filtered = {},
            queue;

        for (file in this._core.getFiles()) {
            files.push(file);
        }

        filtered = this._filterFiles(files);

        if (filtered.css.length > 0) {
            gear.queue({registry: this._registry})
                .load(filtered.css)
                .csslint({}, function(err, results) {
                    /*
                    if (results.meta.lint) {
                        logger.log(results.meta.lint);
                    }*/
                })
                .run(function(err, results) {
                    if (!err) {
                        callback();
                    }
                });
        }
    },

    /*
     * The workhorse of compiler. Loads the queue and processes the results. We modify the original metadata
     * lists for simplicity.
     *
     * @param metadata {Object} Core metadata to augment.
     * @param callback {function} Callback when rollups are compiled.
     */
    _compileRollups: function (metadata, callback) {
        logger.log('[SHAKER] - Compiling rollups...');
        var queue,
            self = this;

        queue = async.queue(function (item, taskCallback) {
            setTimeout(function () {
                var options = {
                    task: self._task,
                    minify: self._minify,
                    config: self._config
                };
                item.object.push(self._registry, options, function (err, url) {
                    logger.log('[SHAKER] - Pushed file ' + url);
                    item.files.push(url);
                    taskCallback();
                });
            }, self._delay);
        }, this._parallel);

        queue.drain = function () {
            self._writeMeta(metadata);
            callback(metadata);
        };

        this._queueRollups(queue, metadata);
    },

    /*
     * Add all assets referenced by metadata to an async queue.
     *
     * @param queue {Object} Queue to append tasks to.
     * @param metadata {Object} Core metadata to look through.
     */
    _queueRollups: function (queue, metadata) {
        var mojit, action, dim, files, name, client, filtered;

        // We need to add images so they can be referenced by relative URLs in the deployed CSS.
        if (this._images) {
            metadata.images.forEach(function (image) {
                queue.push({
                    object: new Image(Shaker.IMAGES_DIR + path.basename(image), image),
                    files: metadata.images
                });
            });
            metadata.images.length = 0;
        }

        // Mojito core assets
        queue.push({
            object: new Rollup(this._compiled_dir + 'core_{checksum}.js', metadata.core.slice() /* Clone */),
            files: metadata.core
        });
        metadata.core.length = 0;

        // Mojit assets
        for (mojit in metadata.mojits) {
            for (action in metadata.mojits[mojit]) {
                // Mojit client assets
                client = metadata.mojits[mojit][action].client;
                if (client.length) {
                    queue.push({
                        object: new ClientRollup(this._compiled_dir + 'client_' + mojit + '_{checksum}.js', this._core.getAppRoot(), client.slice() /* Clone */),
                        files: client
                    });
                    client.length = 0;
                }

                for (dim in (files = metadata.mojits[mojit][action].shaken)) {
                    if (files[dim].length) {
                        name = mojit + '_' + action.replace('*', 'default') + '_{checksum}';
                        filtered = this._filterFiles(files[dim]);

                        if (filtered.js.length) {
                            queue.push({
                                object: new Rollup(this._compiled_dir + name + '.js', filtered.js),
                                files: files[dim]
                            });
                        }
                        if (filtered.css.length) {
                            queue.push({
                                object: new Rollup(this._compiled_dir + name + '.css', filtered.css),
                                files: files[dim]
                            });
                        }
                        files[dim].length = 0;
                    }
                }
            }
        }

        // App level assets
        for (action in metadata.app) {
            // App client assets
            client = metadata.app[action].client;
            if (client.length) {
                queue.push({
                    object: new ClientRollup(this._compiled_dir + 'appclient_' + action + '_{checksum}.js', this._core.getAppRoot(), client.slice() /* Clone */ ),
                    files: client
                });
                client.length = 0;
            }

            for (dim in (files = metadata.app[action].shaken)) {
                if (files[dim].length) {
                    name = 'app_' + action.replace('*', 'default') + '_{checksum}';
                    filtered = this._filterFiles(files[dim]);

                    if (filtered.js.length) {
                        queue.push({
                            object: new Rollup(this._compiled_dir + name + '.js', filtered.js),
                            files: files[dim]
                        });
                    }
                    if (filtered.css.length) {
                        queue.push({
                            object: new Rollup(this._compiled_dir + name + '.css', filtered.css),
                            files: files[dim]
                        });
                    }
                    files[dim].length = 0;
                }
            }
        }
    },

    /*
     * Separate JS and CSS files into separate arrays.
     *
     * @param files {array} List of JS/CSS filenames.
     */
    _filterFiles: function (files) {
        var js = [],
            css = [];

        files.forEach(function (file) {
            var type = mime.lookup(file);

            if (type === 'application/javascript') {
                js.push(file);
            } else if (type === 'text/css') {
                css.push(file);
            }
        });

        return {'js': js, 'css': css};
    },

    /*
     * Write the modified metadata.
     *
     * @param metadata {Object} Core metadata to write to file.
     */
    _writeMeta: function (metadata) {
        var self = this,
            content = "";

        content += 'YUI.add("shaker/metaMojits", function (Y, NAME) {\n';
        content += 'YUI.namespace("_mojito._cache.shaker");\n';
        content += 'YUI._mojito._cache.shaker.meta = \n';
        content += JSON.stringify(metadata, null, '\t');
        content += '});';

        logger.log('[SHAKER] - Writting addon metadata file');
        mkdirp.sync(self._core.getAppRoot() + '/autoload/compiled', 0777 & (~process.umask()));
        fs.writeFileSync(self._core.getAppRoot() + '/autoload/compiled/shaker-meta.common.js', content);
    }
};

exports.Shaker = Shaker;
<commit_msg>adding root url to rollups on completion<commit_after>/*
 * Copyright (c) 2011-2012, Yahoo! Inc.  All rights reserved.
 * Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */
var path = require('path'),
    mojito,
    fs = require('fs'),
    gear = require('gear'),
    async = require('async'),
    mkdirp = require('mkdirp'),
    mime = require('mime'),
    ShakerCore = require('./core').ShakerCore,
    logger = require('./core').logger,
    utils = require('./utils');

/*
 * Buildy wrapper for images.
 *
 * @param name {string} Name of image to build
 * @param file {string} Filename of image
 */
function Image(name, file) {
    this._name = name;
    this._file = file;
}

Image.prototype = {
    push: function (registry, options, callback) {
        var config = utils.simpleClone(options.config);
        config.file = this._name;

        var queue = gear.queue({registry: registry})
            .load([this._file]);

        queue.task(options.task, config, function(err, results) {
            callback(null, results[0].meta.url ? results[0].meta.url : config.root + results[0].meta.name);
        }).run();
    }
};

/*
 * Buildy wrapper for Javascript/CSS.
 *
 * @param name {string} Name of rollup to build
 * @param files {array} Filenames to rollup
 */
function Rollup(name, files) {
    this._name = name;
    this._files = files;
}

Rollup.prototype = {
    push: function (registry, options, callback) {
        var config = utils.simpleClone(options.config);
        config.file = this._name;

        var queue = gear.queue({registry: registry})
            .load(this._files)
            .concat();

        if (options.minify) {
            queue.task(mime.lookup(this._name) === 'application/javascript' ? 'jsminify' : 'cssminify');
        }

        queue.task(options.task, config, function(err, results) {
            callback(null, results[0].meta.url ? results[0].meta.url : config.root + results[0].meta.name);
        }).run();
    }
};

/*
 * Buildy wrapper for templates.
 *
 * @param name {string} Name of template to build
 * @param files {array} Filenames to rollup
 */
function ClientRollup(name, base, files) {
    this._name = name;
    this._base = base;
    this._files = files;
}

ClientRollup.prototype = {
    push: function (registry, options, callback) {
        var config = utils.simpleClone(options.config);
        config.file = this._name;

        var queue = gear.queue({registry: registry})
            .load(this._files)
            .concat({
                callback: function (message) {
                    var filename = message.meta.name;
                    var content = message.body;

                    if (mime.lookup(filename) === 'text/html') {
                        var parts = filename.match(/.*\/?mojits\/(.*)\/views.*\/(.*)\.(.*)\.html/),
                            mojit = parts[1],
                            action = parts[2],
                            renderer = parts[3],
                            json = JSON.stringify(content);

                        content = 'YUI.add("views/' + mojit + '/' + action + '", function (Y, NAME) {\n';
                        content += '\tYUI.namespace("_mojito._cache.compiled.' + mojit + '.views");\n';
                        content += '\tYUI._mojito._cache.compiled.' + mojit + '.views.' + action + ' = ' + json + ';\n';
                        content += '});';
                    }

                    return content;
                }
            });

        if (options.minify) {
            queue.jsminify();
        }

        queue.task(options.task, config, function(err, results) {
            callback(null, results[0].meta.url ? results[0].meta.url : config.root + results[0].meta.name);
        }).run();
    }
};

/**
 * Shaker compiler.
 *
 * Compiler takes metadata generated by the core and depending on the environment context
 * will run tasks against the assets. Tasks including concatenating, minifying, and deploying to
 * a variety of locations (local, cdn).
 *
 * Sample application.json:
 *   ...
 *   {
 *       "settings": ["environment:test"],
 *       "shaker": {
 *           "task": "local"
 *       }
 *   },
 *   ...
 *
 * Example:
 * new Shaker({store: store}).run(function (metadata) {
 *    console.log(metadata);
 * )});
 *
 * @param core {Object} A Shaker core object for a Mojito app.
 */
function Shaker (options) {
    this._core = new ShakerCore(options);

    var shaker = this._core.getConfig();
    this._task = Shaker.DEFAULT_TASK;
    if (shaker.task) { // Task to run when compiling assets
        this._task = shaker.task === 'local' ? 'write' : shaker.task;
    }
    this._compiled_dir = shaker.compiled_dir || Shaker.COMPILED_DIR;
    this._images = shaker.images !== undefined ? shaker.images : false; // Deploy images
    this._parallel = shaker.parallel !== undefined ? shaker.parallel : 20; // How many tasks the async queue runs
    this._delay = shaker.delay !== undefined ? shaker.delay : 0; // Add some network delay for slow connections
    this._lint = shaker.lint !== undefined ? shaker.lint : true; // lint or not
    this._minify = shaker.minify !== undefined ? shaker.minify : true; // Uglify or not
    this._config = shaker.config || {}; // Config object passed through to task
    this._config.root = this._core.getStaticRoot();

    this._registry = gear.registry({module: 'gear-lib'});
    this._registry.load(Shaker.TASKS_DIR);
}

Shaker.DEFAULT_TASK = 'raw';
Shaker.TASKS_DIR = __dirname + '/tasks/'; // Tasks in this directory can be directly referenced in application.json
Shaker.ASSETS_DIR = 'assets/';
Shaker.COMPILED_DIR = Shaker.ASSETS_DIR + 'compiled/'; // Where we write the rollups
Shaker.IMAGES_DIR = Shaker.ASSETS_DIR + 'images/';

Shaker.prototype = {
    /*
     * Start compiling assets.
     *
     * @param callback {function} Callback when compiling is complete.
     */
    run: function (callback) {
        logger.log('[SHAKER] - Analizying application assets to Shake... ');
        var metadata = this._core.run({ignore_dir: this._compiled_dir});

        if (this._lint) {
            this._lintFiles(this._runTask.bind(this, metadata, callback));
        } else {
            this._runTask(metadata, callback);
        }
    },

    _runTask: function(metadata, callback) {
        if (this._task === Shaker.DEFAULT_TASK) {
            this._rename(metadata);
            this._writeMeta(metadata);
            callback(metadata);
        }
        else {
            this._compileRollups(metadata, callback);
        }
    },

    /*
     * Rename assets from local filename to URL.
     *
     * @param metadata {Object} Core metadata to augment.
     */
    _rename: function (metadata) {
        logger.log('[SHAKER] - Processing assets for development env.');
        var mojit, action, dim, item, list, renamed;

        // Mojit assets
        for (mojit in metadata.mojits) {
            for (action in metadata.mojits[mojit]) {
                for (dim in metadata.mojits[mojit][action].shaken) {
                    for (item in metadata.mojits[mojit][action].shaken[dim]) {
                        list = metadata.mojits[mojit][action].shaken[dim];
                        list[item] = this._core.getURL(list[item]);
                    }
                }
                for(item in (list = metadata.mojits[mojit][action].client)){
                    list[item] = this._core.getURL(list[item]);
                }
            }
        }

        // App level assets
        for (action in metadata.app) {
            for (dim in metadata.app[action].shaken) {
                for (item in (list = metadata.app[action].shaken[dim])) {
                    list[item] = this._core.getURL(list[item]);
                }
            }
            for(item in (list = list = metadata.app[action].client)){
                    list[item] = this._core.getURL(list[item]);
            }
        }
        //Core
        for (item in (list = metadata.core)) {
            list[item] = this._core.getURL(list[item]);
        }
    },

    _lintFiles: function (callback) {
        logger.log('[SHAKER] - Linting files...');

        var files = [],
            file,
            filtered = {},
            queue;

        for (file in this._core.getFiles()) {
            files.push(file);
        }

        filtered = this._filterFiles(files);

        if (filtered.css.length > 0) {
            gear.queue({registry: this._registry})
                .load(filtered.css)
                .csslint({}, function(err, results) {
                    /*
                    if (results.meta.lint) {
                        logger.log(results.meta.lint);
                    }*/
                })
                .run(function(err, results) {
                    if (!err) {
                        callback();
                    }
                });
        }
    },

    /*
     * The workhorse of compiler. Loads the queue and processes the results. We modify the original metadata
     * lists for simplicity.
     *
     * @param metadata {Object} Core metadata to augment.
     * @param callback {function} Callback when rollups are compiled.
     */
    _compileRollups: function (metadata, callback) {
        logger.log('[SHAKER] - Compiling rollups...');
        var queue,
            self = this;

        queue = async.queue(function (item, taskCallback) {
            setTimeout(function () {
                var options = {
                    task: self._task,
                    minify: self._minify,
                    config: self._config
                };
                item.object.push(self._registry, options, function (err, url) {
                    logger.log('[SHAKER] - Pushed file ' + url);
                    item.files.push(url);
                    taskCallback();
                });
            }, self._delay);
        }, this._parallel);

        queue.drain = function () {
            self._writeMeta(metadata);
            callback(metadata);
        };

        this._queueRollups(queue, metadata);
    },

    /*
     * Add all assets referenced by metadata to an async queue.
     *
     * @param queue {Object} Queue to append tasks to.
     * @param metadata {Object} Core metadata to look through.
     */
    _queueRollups: function (queue, metadata) {
        var mojit, action, dim, files, name, client, filtered;

        // We need to add images so they can be referenced by relative URLs in the deployed CSS.
        if (this._images) {
            metadata.images.forEach(function (image) {
                queue.push({
                    object: new Image(Shaker.IMAGES_DIR + path.basename(image), image),
                    files: metadata.images
                });
            });
            metadata.images.length = 0;
        }

        // Mojito core assets
        queue.push({
            object: new Rollup(this._compiled_dir + 'core_{checksum}.js', metadata.core.slice() /* Clone */),
            files: metadata.core
        });
        metadata.core.length = 0;

        // Mojit assets
        for (mojit in metadata.mojits) {
            for (action in metadata.mojits[mojit]) {
                // Mojit client assets
                client = metadata.mojits[mojit][action].client;
                if (client.length) {
                    queue.push({
                        object: new ClientRollup(this._compiled_dir + 'client_' + mojit + '_{checksum}.js', this._core.getAppRoot(), client.slice() /* Clone */),
                        files: client
                    });
                    client.length = 0;
                }

                for (dim in (files = metadata.mojits[mojit][action].shaken)) {
                    if (files[dim].length) {
                        name = mojit + '_' + action.replace('*', 'default') + '_{checksum}';
                        filtered = this._filterFiles(files[dim]);

                        if (filtered.js.length) {
                            queue.push({
                                object: new Rollup(this._compiled_dir + name + '.js', filtered.js),
                                files: files[dim]
                            });
                        }
                        if (filtered.css.length) {
                            queue.push({
                                object: new Rollup(this._compiled_dir + name + '.css', filtered.css),
                                files: files[dim]
                            });
                        }
                        files[dim].length = 0;
                    }
                }
            }
        }

        // App level assets
        for (action in metadata.app) {
            // App client assets
            client = metadata.app[action].client;
            if (client.length) {
                queue.push({
                    object: new ClientRollup(this._compiled_dir + 'appclient_' + action + '_{checksum}.js', this._core.getAppRoot(), client.slice() /* Clone */ ),
                    files: client
                });
                client.length = 0;
            }

            for (dim in (files = metadata.app[action].shaken)) {
                if (files[dim].length) {
                    name = 'app_' + action.replace('*', 'default') + '_{checksum}';
                    filtered = this._filterFiles(files[dim]);

                    if (filtered.js.length) {
                        queue.push({
                            object: new Rollup(this._compiled_dir + name + '.js', filtered.js),
                            files: files[dim]
                        });
                    }
                    if (filtered.css.length) {
                        queue.push({
                            object: new Rollup(this._compiled_dir + name + '.css', filtered.css),
                            files: files[dim]
                        });
                    }
                    files[dim].length = 0;
                }
            }
        }
    },

    /*
     * Separate JS and CSS files into separate arrays.
     *
     * @param files {array} List of JS/CSS filenames.
     */
    _filterFiles: function (files) {
        var js = [],
            css = [];

        files.forEach(function (file) {
            var type = mime.lookup(file);

            if (type === 'application/javascript') {
                js.push(file);
            } else if (type === 'text/css') {
                css.push(file);
            }
        });

        return {'js': js, 'css': css};
    },

    /*
     * Write the modified metadata.
     *
     * @param metadata {Object} Core metadata to write to file.
     */
    _writeMeta: function (metadata) {
        var self = this,
            content = "";

        content += 'YUI.add("shaker/metaMojits", function (Y, NAME) {\n';
        content += 'YUI.namespace("_mojito._cache.shaker");\n';
        content += 'YUI._mojito._cache.shaker.meta = \n';
        content += JSON.stringify(metadata, null, '\t');
        content += '});';

        logger.log('[SHAKER] - Writting addon metadata file');
        mkdirp.sync(self._core.getAppRoot() + '/autoload/compiled', 0777 & (~process.umask()));
        fs.writeFileSync(self._core.getAppRoot() + '/autoload/compiled/shaker-meta.common.js', content);
    }
};

exports.Shaker = Shaker;
<|endoftext|>


<commit_before>/*!
 * Should
 * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var util = require('util')
  , http = require('http')
  , assert = require('assert')
  , AssertionError = assert.AssertionError
  , statusCodes = http.STATUS_CODES
  , eql = require('./eql')
  , i = util.inspect;

/**
 * Expose assert as should.
 *
 * This allows you to do things like below
 * without require()ing the assert module.
 *
 *    should.equal(foo.bar, undefined);
 *
 */

exports = module.exports = assert;

/**
 * Library version.
 */

exports.version = '0.4.0';

/**
 * Assert _obj_ exists, with optional message.
 *
 * @param {Mixed} obj
 * @param {String} msg
 * @api public
 */

exports.exist = function(obj, msg){
  if (null == obj) {
    throw new AssertionError({
        message: msg || ('expected ' + i(obj) + ' to exist')
      , stackStartFunction: should.exist
    });
  }
};

/**
 * Asserts _obj_ does not exist, with optional message.
 *
 * @param {Mixed} obj
 * @param {String} msg
 * @api public
 */

exports.not = {};
exports.not.exist = function(obj, msg){
  if (null != obj) {
    throw new AssertionError({
        message: msg || ('expected ' + i(obj) + ' to not exist')
      , stackStartFunction: should.not.exist
    });
  }
};

/**
 * Expose api via `Object#should`.
 *
 * @api public
 */

Object.defineProperty(Object.prototype, 'should', {
  set: function(){},
  get: function(){
    return new Assertion(this);
  },
  configurable: true
});

/**
 * Initialize a new `Assertion` with the given _obj_.
 *
 * @param {Mixed} obj
 * @api private
 */

var Assertion = exports.Assertion = function Assertion(obj) {
  this.obj = obj;
};

/**
 * Prototype.
 */

Assertion.prototype = {

  /**
   * HACK: prevents double require() from failing.
   */

  exports: exports,

  /**
   * Assert _expr_ with the given _msg_ and _negatedMsg_.
   *
   * @param {Boolean} expr
   * @param {String} msg
   * @param {String} negatedMsg
   * @api private
   */

  assert: function(expr, msg, negatedMsg){
    var msg = this.negate ? negatedMsg : msg
      , ok = this.negate ? !expr : expr;
    if (!ok) {
      throw new AssertionError({
          message: msg
        , stackStartFunction: this.assert
      });
    }
  },

  /**
   * Dummy getter.
   *
   * @api public
   */

  get an() {
    return this;
  },

  /**
   * Dummy getter.
   *
   * @api public
   */

  get and() {
    return this;
  },

  /**
   * Dummy getter.
   *
   * @api public
   */

  get be() {
    return this;
  },

  /**
   * Dummy getter.
   *
   * @api public
   */

  get have() {
    return this;
  },

  /**
   * Dummy getter.
   *
   * @api public
   */

  get with() {
    return this;
  },

  /**
   * Negation modifier.
   *
   * @api public
   */

  get not() {
    this.negate = true;
    return this;
  },

  /**
   * Get object inspection string.
   *
   * @return {String}
   * @api private
   */

  get inspect() {
    return i(this.obj);
  },

  /**
   * Assert instanceof `Arguments`.
   *
   * @api public
   */

  get arguments() {
    this.assert(
        '[object Arguments]' == Object.prototype.toString.call(this.obj)
      , 'expected ' + this.inspect + ' to be arguments'
      , 'expected ' + this.inspect + ' to not be arguments');
    return this;
  },

  /**
   * Assert that an object is empty aka length of 0.
   *
   * @api public
   */

  get empty() {
    this.obj.should.have.property('length');
    this.assert(
        0 === this.obj.length
      , 'expected ' + this.inspect + ' to be empty'
      , 'expected ' + this.inspect + ' not to be empty');
    return this;
  },

  /**
   * Assert ok.
   *
   * @api public
   */

  get ok() {
    this.assert(
        this.obj
      , 'expected ' + this.inspect + ' to be truthy'
      , 'expected ' + this.inspect + ' to be falsey');
    return this;
  },

  /**
   * Assert true.
   *
   * @api public
   */

  get true() {
    this.assert(
        true === this.obj
      , 'expected ' + this.inspect + ' to be true'
      , 'expected ' + this.inspect + ' not to be true');
    return this;
  },

  /**
   * Assert false.
   *
   * @api public
   */

  get false() {
    this.assert(
        false === this.obj
      , 'expected ' + this.inspect + ' to be false'
      , 'expected ' + this.inspect + ' not to be false');
    return this;
  },

  /**
   * Assert equal.
   *
   * @param {Mixed} val
   * @api public
   */

  eql: function(val){
    this.assert(
        eql(val, this.obj)
      , 'expected ' + this.inspect + ' to equal ' + i(val)
      , 'expected ' + this.inspect + ' to not equal ' + i(val));
    return this;
  },

  /**
   * Assert strict equal.
   *
   * @param {Mixed} val
   * @api public
   */

  equal: function(val){
    this.assert(
        val === this.obj
      , 'expected ' + this.inspect + ' to equal ' + i(val)
      , 'expected ' + this.inspect + ' to not equal ' + i(val));
    return this;
  },

  /**
   * Assert within start to finish (inclusive).
   *
   * @param {Number} start
   * @param {Number} finish
   * @api public
   */

  within: function(start, finish){
    var range = start + '..' + finish;
    this.assert(
        this.obj >= start && this.obj <= finish
      , 'expected ' + this.inspect + ' to be within ' + range
      , 'expected ' + this.inspect + ' to not be within ' + range);
    return this;
  },

  /**
   * Assert typeof.
   *
   * @api public
   */

  a: function(type){
    this.assert(
        type == typeof this.obj
      , 'expected ' + this.inspect + ' to be a ' + type
      , 'expected ' + this.inspect + ' not to be a ' + type);
    return this;
  },

  /**
   * Assert instanceof.
   *
   * @api public
   */

  instanceof: function(constructor){
    var name = constructor.name;
    this.assert(
        this.obj instanceof constructor
      , 'expected ' + this.inspect + ' to be an instance of ' + name
      , 'expected ' + this.inspect + ' not to be an instance of ' + name);
    return this;
  },

  /**
   * Assert numeric value above _n_.
   *
   * @param {Number} n
   * @api public
   */

  above: function(n){
    this.assert(
        this.obj > n
      , 'expected ' + this.inspect + ' to be above ' + n
      , 'expected ' + this.inspect + ' to be below ' + n);
    return this;
  },

  /**
   * Assert numeric value below _n_.
   *
   * @param {Number} n
   * @api public
   */

  below: function(n){
    this.assert(
        this.obj < n
      , 'expected ' + this.inspect + ' to be below ' + n
      , 'expected ' + this.inspect + ' to be above ' + n);
    return this;
  },

  /**
   * Assert string value matches _regexp_.
   *
   * @param {RegExp} regexp
   * @api public
   */

  match: function(regexp){
    this.assert(
        regexp.exec(this.obj)
      , 'expected ' + this.inspect + ' to match ' + regexp
      , 'expected ' + this.inspect + ' not to match ' + regexp);
    return this;
  },

  /**
   * Assert property "length" exists and has value of _n_.
   *
   * @param {Number} n
   * @api public
   */

  length: function(n){
    this.obj.should.have.property('length');
    var len = this.obj.length;
    this.assert(
        n == len
      , 'expected ' + this.inspect + ' to have a length of ' + n + ' but got ' + len
      , 'expected ' + this.inspect + ' to not have a length of ' + len);
    return this;
  },

  /**
   * Assert substring.
   *
   * @param {String} str
   * @api public
   */

  string: function(str){
    this.obj.should.be.a('string');
    this.assert(
        ~this.obj.indexOf(str)
      , 'expected ' + this.inspect + ' to include ' + i(str)
      , 'expected ' + this.inspect + ' to not include ' + i(str));
    return this;
  },

  /**
   * Assert property _name_ exists, with optional _val_.
   *
   * @param {String} name
   * @param {Mixed} val
   * @api public
   */

  property: function(name, val){
    if (this.negate && undefined !== val) {
      if (undefined === this.obj[name]) {
        throw new Error(this.inspect + ' has no property ' + i(name));
      }
    } else {
      this.assert(
          undefined !== this.obj[name]
        , 'expected ' + this.inspect + ' to have a property ' + i(name)
        , 'expected ' + this.inspect + ' to not have a property ' + i(name));
    }

    if (undefined !== val) {
      this.assert(
          val === this.obj[name]
        , 'expected ' + this.inspect + ' to have a property ' + i(name)
          + ' of ' + i(val) + ', but got ' + i(this.obj[name])
        , 'expected ' + this.inspect + ' to not have a property ' + i(name) + ' of ' + i(val));
    }

    this.obj = this.obj[name];
    return this;
  },

  /**
   * Assert own property _name_ exists.
   *
   * @param {String} name
   * @api public
   */

  ownProperty: function(name){
    this.assert(
        this.obj.hasOwnProperty(name)
      , 'expected ' + this.inspect + ' to have own property ' + i(name)
      , 'expected ' + this.inspect + ' to not have own property ' + i(name));
    return this;
  },

  /**
   * Assert that `obj` is present via `.indexOf()`.
   *
   * @param {Mixed} obj
   * @api public
   */

  include: function(obj){
    this.assert(
        ~this.obj.indexOf(obj)
      , 'expected ' + this.inspect + ' to include ' + i(obj)
      , 'expected ' + this.inspect + ' to not include ' + i(obj));

    return this;
  },

  /**
   * Assert that the array contains _obj_.
   *
   * @param {Mixed} obj
   * @api public
   */

  contain: function(obj){
    console.warn('should.contain() is deprecated, use should.include()');
    this.obj.should.be.an.instanceof(Array);
    this.assert(
        ~this.obj.indexOf(obj)
      , 'expected ' + this.inspect + ' to contain ' + i(obj)
      , 'expected ' + this.inspect + ' to not contain ' + i(obj));
    return this;
  },

  /**
   * Assert exact keys or inclusion of keys by using
   * the `.include` modifier.
   *
   * @param {Array|String ...} keys
   * @api public
   */

  keys: function(keys){
    var str
      , ok = true;

    keys = keys instanceof Array
      ? keys
      : Array.prototype.slice.call(arguments);

    if (!keys.length) throw new Error('keys required');

    var actual = Object.keys(this.obj)
      , len = keys.length;

    // make sure they're all present
    ok = keys.every(function(key){
      return ~actual.indexOf(key);
    });

    // matching length
    ok = ok && keys.length == actual.length;

    // key string
    if (len > 1) {
      keys = keys.map(function(key){
        return i(key);
      });
      var last = keys.pop();
      str = keys.join(', ') + ', and ' + last;
    } else {
      str = i(keys[0]);
    }

    // message
    str = 'have ' + (len > 1 ? 'keys ' : 'key ') + str;

    this.assert(
        ok
      , 'expected ' + this.inspect + ' to ' + str
      , 'expected ' + this.inspect + ' to not ' + str);

    return this;
  },

  /**
   * Assert that header `field` has the given `val`.
   *
   * @param {String} field
   * @param {String} val
   * @return {Assertion} for chaining
   * @api public
   */

  header: function(field, val){
    this.obj.should.have.property('headers');
    this.obj.headers.should.have.property(field.toLowerCase(), val);
    return this;
  },

  /**
   * Assert `.statusCode` of `code`.
   *
   * @param {Number} code
   * @return {Assertion} for chaining
   * @api public
   */

  status:  function(code){
    this.obj.should.have.property('statusCode');
    var status = this.obj.statusCode;

    this.assert(
        code == status
      , 'expected response code of ' + code + ' ' + i(statusCodes[code])
        + ', but got ' + status + ' ' + i(statusCodes[status])
      , 'expected to not respond with ' + code + ' ' + i(statusCodes[code]));

    return this;
  },

  /**
   * Assert that this function will or will not
   * throw an exception.
   *
   * @return {Assertion} for chaining
   * @api public
   */

  throw: function(){
    var fn = this.obj
      , err = {}
      , ok = true;

    try {
      fn();
      ok = false;
    } catch (e) {
      err = e;
    }

    this.assert(
        ok
      , 'expected an exception to be thrown'
      , 'expected no exception to be thrown, got "' + err.message + '"');

    return this;
  }
};

/**
 * Aliases.
 */

(function alias(name, as){
  Assertion.prototype[as] = Assertion.prototype[name];
  return alias;
})
('length', 'lengthOf')
('keys', 'key')
('ownProperty', 'haveOwnProperty')
('above', 'greaterThan')
('below', 'lessThan');

<commit_msg>Fixed: chain .header() to retain negation<commit_after>/*!
 * Should
 * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var util = require('util')
  , http = require('http')
  , assert = require('assert')
  , AssertionError = assert.AssertionError
  , statusCodes = http.STATUS_CODES
  , eql = require('./eql')
  , i = util.inspect;

/**
 * Expose assert as should.
 *
 * This allows you to do things like below
 * without require()ing the assert module.
 *
 *    should.equal(foo.bar, undefined);
 *
 */

exports = module.exports = assert;

/**
 * Library version.
 */

exports.version = '0.4.0';

/**
 * Assert _obj_ exists, with optional message.
 *
 * @param {Mixed} obj
 * @param {String} msg
 * @api public
 */

exports.exist = function(obj, msg){
  if (null == obj) {
    throw new AssertionError({
        message: msg || ('expected ' + i(obj) + ' to exist')
      , stackStartFunction: should.exist
    });
  }
};

/**
 * Asserts _obj_ does not exist, with optional message.
 *
 * @param {Mixed} obj
 * @param {String} msg
 * @api public
 */

exports.not = {};
exports.not.exist = function(obj, msg){
  if (null != obj) {
    throw new AssertionError({
        message: msg || ('expected ' + i(obj) + ' to not exist')
      , stackStartFunction: should.not.exist
    });
  }
};

/**
 * Expose api via `Object#should`.
 *
 * @api public
 */

Object.defineProperty(Object.prototype, 'should', {
  set: function(){},
  get: function(){
    return new Assertion(this);
  },
  configurable: true
});

/**
 * Initialize a new `Assertion` with the given _obj_.
 *
 * @param {Mixed} obj
 * @api private
 */

var Assertion = exports.Assertion = function Assertion(obj) {
  this.obj = obj;
};

/**
 * Prototype.
 */

Assertion.prototype = {

  /**
   * HACK: prevents double require() from failing.
   */

  exports: exports,

  /**
   * Assert _expr_ with the given _msg_ and _negatedMsg_.
   *
   * @param {Boolean} expr
   * @param {String} msg
   * @param {String} negatedMsg
   * @api private
   */

  assert: function(expr, msg, negatedMsg){
    var msg = this.negate ? negatedMsg : msg
      , ok = this.negate ? !expr : expr;
    if (!ok) {
      throw new AssertionError({
          message: msg
        , stackStartFunction: this.assert
      });
    }
  },

  /**
   * Dummy getter.
   *
   * @api public
   */

  get an() {
    return this;
  },

  /**
   * Dummy getter.
   *
   * @api public
   */

  get and() {
    return this;
  },

  /**
   * Dummy getter.
   *
   * @api public
   */

  get be() {
    return this;
  },

  /**
   * Dummy getter.
   *
   * @api public
   */

  get have() {
    return this;
  },

  /**
   * Dummy getter.
   *
   * @api public
   */

  get with() {
    return this;
  },

  /**
   * Negation modifier.
   *
   * @api public
   */

  get not() {
    this.negate = true;
    return this;
  },

  /**
   * Get object inspection string.
   *
   * @return {String}
   * @api private
   */

  get inspect() {
    return i(this.obj);
  },

  /**
   * Assert instanceof `Arguments`.
   *
   * @api public
   */

  get arguments() {
    this.assert(
        '[object Arguments]' == Object.prototype.toString.call(this.obj)
      , 'expected ' + this.inspect + ' to be arguments'
      , 'expected ' + this.inspect + ' to not be arguments');
    return this;
  },

  /**
   * Assert that an object is empty aka length of 0.
   *
   * @api public
   */

  get empty() {
    this.obj.should.have.property('length');
    this.assert(
        0 === this.obj.length
      , 'expected ' + this.inspect + ' to be empty'
      , 'expected ' + this.inspect + ' not to be empty');
    return this;
  },

  /**
   * Assert ok.
   *
   * @api public
   */

  get ok() {
    this.assert(
        this.obj
      , 'expected ' + this.inspect + ' to be truthy'
      , 'expected ' + this.inspect + ' to be falsey');
    return this;
  },

  /**
   * Assert true.
   *
   * @api public
   */

  get true() {
    this.assert(
        true === this.obj
      , 'expected ' + this.inspect + ' to be true'
      , 'expected ' + this.inspect + ' not to be true');
    return this;
  },

  /**
   * Assert false.
   *
   * @api public
   */

  get false() {
    this.assert(
        false === this.obj
      , 'expected ' + this.inspect + ' to be false'
      , 'expected ' + this.inspect + ' not to be false');
    return this;
  },

  /**
   * Assert equal.
   *
   * @param {Mixed} val
   * @api public
   */

  eql: function(val){
    this.assert(
        eql(val, this.obj)
      , 'expected ' + this.inspect + ' to equal ' + i(val)
      , 'expected ' + this.inspect + ' to not equal ' + i(val));
    return this;
  },

  /**
   * Assert strict equal.
   *
   * @param {Mixed} val
   * @api public
   */

  equal: function(val){
    this.assert(
        val === this.obj
      , 'expected ' + this.inspect + ' to equal ' + i(val)
      , 'expected ' + this.inspect + ' to not equal ' + i(val));
    return this;
  },

  /**
   * Assert within start to finish (inclusive).
   *
   * @param {Number} start
   * @param {Number} finish
   * @api public
   */

  within: function(start, finish){
    var range = start + '..' + finish;
    this.assert(
        this.obj >= start && this.obj <= finish
      , 'expected ' + this.inspect + ' to be within ' + range
      , 'expected ' + this.inspect + ' to not be within ' + range);
    return this;
  },

  /**
   * Assert typeof.
   *
   * @api public
   */

  a: function(type){
    this.assert(
        type == typeof this.obj
      , 'expected ' + this.inspect + ' to be a ' + type
      , 'expected ' + this.inspect + ' not to be a ' + type);
    return this;
  },

  /**
   * Assert instanceof.
   *
   * @api public
   */

  instanceof: function(constructor){
    var name = constructor.name;
    this.assert(
        this.obj instanceof constructor
      , 'expected ' + this.inspect + ' to be an instance of ' + name
      , 'expected ' + this.inspect + ' not to be an instance of ' + name);
    return this;
  },

  /**
   * Assert numeric value above _n_.
   *
   * @param {Number} n
   * @api public
   */

  above: function(n){
    this.assert(
        this.obj > n
      , 'expected ' + this.inspect + ' to be above ' + n
      , 'expected ' + this.inspect + ' to be below ' + n);
    return this;
  },

  /**
   * Assert numeric value below _n_.
   *
   * @param {Number} n
   * @api public
   */

  below: function(n){
    this.assert(
        this.obj < n
      , 'expected ' + this.inspect + ' to be below ' + n
      , 'expected ' + this.inspect + ' to be above ' + n);
    return this;
  },

  /**
   * Assert string value matches _regexp_.
   *
   * @param {RegExp} regexp
   * @api public
   */

  match: function(regexp){
    this.assert(
        regexp.exec(this.obj)
      , 'expected ' + this.inspect + ' to match ' + regexp
      , 'expected ' + this.inspect + ' not to match ' + regexp);
    return this;
  },

  /**
   * Assert property "length" exists and has value of _n_.
   *
   * @param {Number} n
   * @api public
   */

  length: function(n){
    this.obj.should.have.property('length');
    var len = this.obj.length;
    this.assert(
        n == len
      , 'expected ' + this.inspect + ' to have a length of ' + n + ' but got ' + len
      , 'expected ' + this.inspect + ' to not have a length of ' + len);
    return this;
  },

  /**
   * Assert substring.
   *
   * @param {String} str
   * @api public
   */

  string: function(str){
    this.obj.should.be.a('string');
    this.assert(
        ~this.obj.indexOf(str)
      , 'expected ' + this.inspect + ' to include ' + i(str)
      , 'expected ' + this.inspect + ' to not include ' + i(str));
    return this;
  },

  /**
   * Assert property _name_ exists, with optional _val_.
   *
   * @param {String} name
   * @param {Mixed} val
   * @api public
   */

  property: function(name, val){
    if (this.negate && undefined !== val) {
      if (undefined === this.obj[name]) {
        throw new Error(this.inspect + ' has no property ' + i(name));
      }
    } else {
      this.assert(
          undefined !== this.obj[name]
        , 'expected ' + this.inspect + ' to have a property ' + i(name)
        , 'expected ' + this.inspect + ' to not have a property ' + i(name));
    }

    if (undefined !== val) {
      this.assert(
          val === this.obj[name]
        , 'expected ' + this.inspect + ' to have a property ' + i(name)
          + ' of ' + i(val) + ', but got ' + i(this.obj[name])
        , 'expected ' + this.inspect + ' to not have a property ' + i(name) + ' of ' + i(val));
    }

    this.obj = this.obj[name];
    return this;
  },

  /**
   * Assert own property _name_ exists.
   *
   * @param {String} name
   * @api public
   */

  ownProperty: function(name){
    this.assert(
        this.obj.hasOwnProperty(name)
      , 'expected ' + this.inspect + ' to have own property ' + i(name)
      , 'expected ' + this.inspect + ' to not have own property ' + i(name));
    return this;
  },

  /**
   * Assert that `obj` is present via `.indexOf()`.
   *
   * @param {Mixed} obj
   * @api public
   */

  include: function(obj){
    this.assert(
        ~this.obj.indexOf(obj)
      , 'expected ' + this.inspect + ' to include ' + i(obj)
      , 'expected ' + this.inspect + ' to not include ' + i(obj));

    return this;
  },

  /**
   * Assert that the array contains _obj_.
   *
   * @param {Mixed} obj
   * @api public
   */

  contain: function(obj){
    console.warn('should.contain() is deprecated, use should.include()');
    this.obj.should.be.an.instanceof(Array);
    this.assert(
        ~this.obj.indexOf(obj)
      , 'expected ' + this.inspect + ' to contain ' + i(obj)
      , 'expected ' + this.inspect + ' to not contain ' + i(obj));
    return this;
  },

  /**
   * Assert exact keys or inclusion of keys by using
   * the `.include` modifier.
   *
   * @param {Array|String ...} keys
   * @api public
   */

  keys: function(keys){
    var str
      , ok = true;

    keys = keys instanceof Array
      ? keys
      : Array.prototype.slice.call(arguments);

    if (!keys.length) throw new Error('keys required');

    var actual = Object.keys(this.obj)
      , len = keys.length;

    // make sure they're all present
    ok = keys.every(function(key){
      return ~actual.indexOf(key);
    });

    // matching length
    ok = ok && keys.length == actual.length;

    // key string
    if (len > 1) {
      keys = keys.map(function(key){
        return i(key);
      });
      var last = keys.pop();
      str = keys.join(', ') + ', and ' + last;
    } else {
      str = i(keys[0]);
    }

    // message
    str = 'have ' + (len > 1 ? 'keys ' : 'key ') + str;

    this.assert(
        ok
      , 'expected ' + this.inspect + ' to ' + str
      , 'expected ' + this.inspect + ' to not ' + str);

    return this;
  },

  /**
   * Assert that header `field` has the given `val`.
   *
   * @param {String} field
   * @param {String} val
   * @return {Assertion} for chaining
   * @api public
   */

  header: function(field, val){
    this.obj.should.have.property('headers')
      .and.obj.headers.should.have.property(field.toLowerCase(), val);
    return this;
  },

  /**
   * Assert `.statusCode` of `code`.
   *
   * @param {Number} code
   * @return {Assertion} for chaining
   * @api public
   */

  status:  function(code){
    this.obj.should.have.property('statusCode');
    var status = this.obj.statusCode;

    this.assert(
        code == status
      , 'expected response code of ' + code + ' ' + i(statusCodes[code])
        + ', but got ' + status + ' ' + i(statusCodes[status])
      , 'expected to not respond with ' + code + ' ' + i(statusCodes[code]));

    return this;
  },

  /**
   * Assert that this function will or will not
   * throw an exception.
   *
   * @return {Assertion} for chaining
   * @api public
   */

  throw: function(){
    var fn = this.obj
      , err = {}
      , ok = true;

    try {
      fn();
      ok = false;
    } catch (e) {
      err = e;
    }

    this.assert(
        ok
      , 'expected an exception to be thrown'
      , 'expected no exception to be thrown, got "' + err.message + '"');

    return this;
  }
};

/**
 * Aliases.
 */

(function alias(name, as){
  Assertion.prototype[as] = Assertion.prototype[name];
  return alias;
})
('length', 'lengthOf')
('keys', 'key')
('ownProperty', 'haveOwnProperty')
('above', 'greaterThan')
('below', 'lessThan');

<|endoftext|>


<commit_before>module.exports = (function(io) {
  io.on('connection', function(err, socket, session) {
    if (!session.passport.user) {
      return;
    }
    console.log('> user: ', session);
  });
});<commit_msg>Allowing users to subscribe to devices and send messages<commit_after>var db = require('mongoose');

module.exports = (function(io, ascoltatore) {
  io.on('connection', function(err, socket, session) {
    var User = db.model('User'), user;

    if (!(user = session.passport.user)) {
      return;
    }

    socket.on('subscribe', function(key, fn) {
      fn = fn || function() {};

      if (typeof key !== 'string') {
	fn('invalid device key');
	return;
      }

      if (key == 'master') {
	ascoltatore.subscribe('~user+'+user, function(user, packet) {
	  socket.emit('message', packet);
	});
	return;
      }

      User.findOne(
	{_id: user, devices: {$all: key}},  function(err, _user) {
	  if (err) {
	    fn('something went wrong');
	    return;
	  }

	  if (!_user) {
	    fn('sorry, not authorized');
	    return;
	  }

	  ascoltatore.subscribe(key, function(key, packet) {
	    socket.emit('message', packet);
	  });
	}
      );
    });

    socket.on('message', function(packet, fn) {
      var topics, payload, type;

      type = typeof packet.devices;
      fn = fn || function() {};

      if (type != 'object' && type != 'string') {
	fn('invalid devices especified');
	return;
      }

      if (type == 'object' && !packet.devices.length) {
	fn('no devices specified');
	return;
      }

      if (typeof packet.payload == 'undefined') {
	fn('invalid playload');
	return;
      }

      topics = packet.devices;
      payload = packet.payload;

      if (typeof topics == 'string') {
	topics = [topics];
      }

      User.findOne(
	{_id: user, devices: {$all: topics}},  function(err, _user) {
	  if (err) {
	    fn('something went wrong');
	    return;
	  }

	  if (!_user) {
	    fn('not authorized, sorry');
	    return;
	  }

	  topics.forEach(function(topic) {
	    ascoltatore.publish(
	      topic,
	      payload,
	      {},
	      function(a, b, c) {
		console.log('> packet send?');
	      }
	    );
	  });
	}
      );
    });
  });
});
<|endoftext|>


<commit_before>
/**
 * Module exports.
 */

module.exports = exports = Socket;

/**
 * Export Transport.
 */

exports.Transport = require('./transport');

/**
 * Export transports
 */

var transports = exports.transports = require('./transports');

/**
 * Export utils.
 */

var util = exports.util = require('./util')

/**
 * Socket constructor.
 *
 * @param {Object} options
 * @api public
 */

function Socket (opts) {
  if ('string' == typeof opts) {
    var uri = util.parseUri(opts);
    opts.host = uri.host;
    opts.secure = uri.scheme == 'wss'
    opts.port = uri.port || (opts.secure ? 443 : 80);
  }

  opts = opts || {};

  this.host = opts.host || opts.hostname || 'localhost';
  this.port = opts.port || 80;
  this.upgrade = false !== opts.upgrade;
  this.path = opts.path || '/engine.io'
  this.forceJSONP = !!opts.forceJSONP;
  this.transports = opts.transports || ['polling', 'websocket', 'flashsocket'];
  this.readyState = '';
  this.writeBuffer = [];

  this.open();
};

/**
 * Creates transport of the given type.
 *
 * @api {String} transport name
 * @return {Transport}
 * @api private
 */

Socket.prototype.createTransport = function (name) {
  var transport = new transports[name]({
      host: self.host
    , port: self.port
    , secure: self.secure
    , path: self.path + '/' + name
    , query: self.query ? '&' + self.query : ''
    , forceJSONP: self.forceJSONP
  }, engine);

  return transport;
};

/**
 * Initializes transport to use and starts probe.
 *
 * @api private
 */

Socket.prototype.open = function () {
  var self = this;

  this.readyState = 'opening';

  // use the first transport always for the first try
  var transport = this.createTransport(this.transports[0]);
  transport.open();
  transport.once('open', function () {
    this.setTransport(transport);
  });

  // if the engine is closed before transport opened, abort it
  this.once('close', function () {
    transport.close();
  });

  // whether we should perform a probe
  if (this.upgrade && this.transports.length > 1 && transport.pause) {
    var probeTransports = this.transports.slice(1);

    for (var i = 0, l = probeTransports.length; i < l; i++) {
      this.probe(probeTransports[i]);
    }
  }
};

/**
 * Sets the current transport. Disables the existing one (if any).
 *
 * @api private
 */

Socket.prototype.setTransport = function (transport) {
  var self = this;

  // set up transport
  this.transport = transport;

  // set up transport listeners
  transport
    .on('message', function (data) {
      self.onMessage(msg);
    })
    .on('close', function () {
      self.onClose();
    })
};

/**
 * Probes a transport.
 *
 * @param {String} transport name
 * @api private
 */

Socket.prototype.probe = function (name) {
  this.log.debug('probing transport "%s"', name);

  var transport = this.createTransport(name)
    , self = this

  transport.once('open', function () {
    transport.write(parser.encodePacket('probe'));
    transport.once('message', function (message) {
      if ('probe' == message.type) {
        self.upgrading = true;
        self.emit('upgrading', name);

        self.transport.pause(function () {
          self.setTransport(self.transport);
          self.upgrading = false;
          self.emit('upgrade', name);
        });
      } else {
        var err = new Error('probe error');
        err.transport = transport.name;
        self.emit('error', err);
      }
    });
  });

  transport.open();

  // if closed prematurely, abort probe
  this.once('close', function () {
    transport.close();
  });

  // if another probe suceeds, abort this one
  this.once('upgrading', function (to) {
    if (to != name) {
      transport.close();
    }
  });
};

/**
 * Opens the connection
 *
 * @api public
 */

Socket.prototype.open = function () {
  if ('' == this.readyState || 'closed' == this.readyState) {
    this.transport.open()
  }

  return this;
};

/**
 * Called when connection is deemed open.
 *
 * @api public
 */

Socket.prototype.onOpen = function () {
  this.readyState = 'open';
  this.emit('open');
  this.onopen && this.onopen.call(this);
};

/**
 * Handles a message.
 *
 * @api private
 */

Socket.prototype.onMessage = function (msg) {
  switch (msg.type) {
    case 'noop':
      break;

    case 'open':
      this.onOpen();
      break;

    case 'ping':
      this.writePacket('pong');
      break;

    case 'error':
      var err = new Error('server error');
      err.code = msg.data;
      this.emit('error', err);
      break;

    case 'message':
      this.emit('message', msg.data);
      this.onmessage && this.onmessage.call(this, msg.data);
      break;
  }
};

/**
 * Flush write buffers.
 *
 * @api private
 */

Socket.prototype.flush = function () {
  if (this.writeBuffer.length) {
    // make sure to transfer the buffer to the transport
    this.transport.buffer = true;

    for (var i = 0, l = this.writeBuffer.length; i < l; i++) {
      this.transport.send(this.writeBuffer[i]);
    }

    // force transport flush
    this.transport.flush();
  }
};

/**
 * Sends a message.
 *
 * @param {String} message.
 * @return {Socket} for chaining.
 * @api public
 */

Socket.prototype.send = function (msg) {
  this.writePacket('message', msg);
  return this;
};

/**
 * Encodes a packet and writes it out.
 *
 * @param {String} packet type.
 * @param {String} data.
 * @api private
 */

Socket.prototype.writePacket = function (type, data) {
  this.write(parser.encodePacket(type, data));
};

/**
 * Writes data.
 *
 * @api private
 */

Socket.prototype.write = function (data) {
  if ('open' != this.readyState || this.upgrading) {
    this.writeBuffer.push(data);
  } else {
    this.transport.send(data);
  }
};

/**
 * Closes the connection.
 *
 * @api private
 */

Socket.prototype.close = function () {
  if ('opening' == this.readyState || 'open' == this.readyState) {
    if (this.transport) {
      this.transport.close();
    }

    this.onClose();
  }

  return this;
};

/**
 * Called upon transport close.
 *
 * @api private
 */

Socket.prototype.onClose = function () {
  this.readyState = 'closed';
  this.emit('close');
  this.onclose && this.onclose.call(this);
};
<commit_msg>Switched probe to ping/pong.<commit_after>
/**
 * Module exports.
 */

module.exports = exports = Socket;

/**
 * Export Transport.
 */

exports.Transport = require('./transport');

/**
 * Export transports
 */

var transports = exports.transports = require('./transports');

/**
 * Export utils.
 */

var util = exports.util = require('./util')

/**
 * Socket constructor.
 *
 * @param {Object} options
 * @api public
 */

function Socket (opts) {
  if ('string' == typeof opts) {
    var uri = util.parseUri(opts);
    opts.host = uri.host;
    opts.secure = uri.scheme == 'wss'
    opts.port = uri.port || (opts.secure ? 443 : 80);
  }

  opts = opts || {};

  this.host = opts.host || opts.hostname || 'localhost';
  this.port = opts.port || 80;
  this.upgrade = false !== opts.upgrade;
  this.path = opts.path || '/engine.io'
  this.forceJSONP = !!opts.forceJSONP;
  this.transports = opts.transports || ['polling', 'websocket', 'flashsocket'];
  this.readyState = '';
  this.writeBuffer = [];

  this.open();
};

/**
 * Creates transport of the given type.
 *
 * @api {String} transport name
 * @return {Transport}
 * @api private
 */

Socket.prototype.createTransport = function (name) {
  var transport = new transports[name]({
      host: self.host
    , port: self.port
    , secure: self.secure
    , path: self.path + '/' + name
    , query: self.query ? '&' + self.query : ''
    , forceJSONP: self.forceJSONP
  }, engine);

  return transport;
};

/**
 * Initializes transport to use and starts probe.
 *
 * @api private
 */

Socket.prototype.open = function () {
  var self = this;

  this.readyState = 'opening';

  // use the first transport always for the first try
  var transport = this.createTransport(this.transports[0]);
  transport.open();
  transport.once('open', function () {
    this.setTransport(transport);
  });

  // if the engine is closed before transport opened, abort it
  this.once('close', function () {
    transport.close();
  });

  // whether we should perform a probe
  if (this.upgrade && this.transports.length > 1 && transport.pause) {
    var probeTransports = this.transports.slice(1);

    for (var i = 0, l = probeTransports.length; i < l; i++) {
      this.probe(probeTransports[i]);
    }
  }
};

/**
 * Sets the current transport. Disables the existing one (if any).
 *
 * @api private
 */

Socket.prototype.setTransport = function (transport) {
  var self = this;

  // set up transport
  this.transport = transport;

  // set up transport listeners
  transport
    .on('message', function (data) {
      self.onMessage(msg);
    })
    .on('close', function () {
      self.onClose();
    })
};

/**
 * Probes a transport.
 *
 * @param {String} transport name
 * @api private
 */

Socket.prototype.probe = function (name) {
  this.log.debug('probing transport "%s"', name);

  var transport = this.createTransport(name)
    , self = this

  transport.once('open', function () {
    transport.write(parser.encodePacket('ping'));
    transport.once('message', function (message) {
      if ('pong' == message.type) {
        self.upgrading = true;
        self.emit('upgrading', name);

        self.transport.pause(function () {
          self.setTransport(self.transport);
          self.upgrading = false;
          self.emit('upgrade', name);
        });
      } else {
        var err = new Error('probe error');
        err.transport = transport.name;
        self.emit('error', err);
      }
    });
  });

  transport.open();

  // if closed prematurely, abort probe
  this.once('close', function () {
    transport.close();
  });

  // if another probe suceeds, abort this one
  this.once('upgrading', function (to) {
    if (to != name) {
      transport.close();
    }
  });
};

/**
 * Opens the connection
 *
 * @api public
 */

Socket.prototype.open = function () {
  if ('' == this.readyState || 'closed' == this.readyState) {
    this.transport.open()
  }

  return this;
};

/**
 * Called when connection is deemed open.
 *
 * @api public
 */

Socket.prototype.onOpen = function () {
  this.readyState = 'open';
  this.emit('open');
  this.onopen && this.onopen.call(this);
};

/**
 * Handles a message.
 *
 * @api private
 */

Socket.prototype.onMessage = function (msg) {
  switch (msg.type) {
    case 'noop':
      break;

    case 'open':
      this.onOpen();
      break;

    case 'ping':
      this.write('pong');
      break;

    case 'error':
      var err = new Error('server error');
      err.code = msg.data;
      this.emit('error', err);
      break;

    case 'message':
      this.emit('message', msg.data);
      this.onmessage && this.onmessage.call(this, msg.data);
      break;
  }
};

/**
 * Flush write buffers.
 *
 * @api private
 */

Socket.prototype.flush = function () {
  if (this.writeBuffer.length) {
    // make sure to transfer the buffer to the transport
    this.transport.buffer = true;

    for (var i = 0, l = this.writeBuffer.length; i < l; i++) {
      this.transport.send(this.writeBuffer[i]);
    }

    // force transport flush
    this.transport.flush();
  }
};

/**
 * Sends a message.
 *
 * @param {String} message.
 * @return {Socket} for chaining.
 * @api public
 */

Socket.prototype.send = function (msg) {
  this.writePacket('message', msg);
  return this;
};

/**
 * Encodes a packet and writes it out.
 *
 * @param {String} packet type.
 * @param {String} data.
 * @api private
 */

Socket.prototype.writePacket = function (type, data) {
  this.write(parser.encodePacket(type, data));
};

/**
 * Writes data.
 *
 * @api private
 */

Socket.prototype.write = function (data) {
  if ('open' != this.readyState || this.upgrading) {
    this.writeBuffer.push(data);
  } else {
    this.transport.send(data);
  }
};

/**
 * Closes the connection.
 *
 * @api private
 */

Socket.prototype.close = function () {
  if ('opening' == this.readyState || 'open' == this.readyState) {
    if (this.transport) {
      this.transport.close();
    }

    this.onClose();
  }

  return this;
};

/**
 * Called upon transport close.
 *
 * @api private
 */

Socket.prototype.onClose = function () {
  this.readyState = 'closed';
  this.emit('close');
  this.onclose && this.onclose.call(this);
};
<|endoftext|>