<commit_before>// host.js
//
// data object representing a remote host
//
// Copyright 2013, StatusNet Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

var _ = require("underscore"),
    wf = require("webfinger"),
    qs = require("querystring"),
    Step = require("step"),
    Schlock = require("schlock"),
    OAuth = require("oauth").OAuth,
    DatabankObject = require("databank").DatabankObject,
    URLMaker = require("../urlmaker").URLMaker,
    RemoteRequestToken = require("./remoterequesttoken").RemoteRequestToken,
    Credentials = require("./credentials").Credentials;

var Host = DatabankObject.subClass("host");

var OAUTH_RT = "http://apinamespace.org/oauth/request_token",
    OAUTH_AT = "http://apinamespace.org/oauth/access_token",
    OAUTH_AUTHZ = "http://apinamespace.org/oauth/authorize",
    WHOAMI = "http://apinamespace.org/activitypub/whoami",
    OAUTH_CRED = "registration_endpoint";

Host.schema = {
    pkey: "hostname",
    fields: ["registration_endpoint",
             "request_token_endpoint",
             "access_token_endpoint",
             "authorization_endpoint",
             "whoami_endpoint",
             "created",
             "updated"]
};

Host.beforeCreate = function(props, callback) {
    if (!props.hostname) {
        callback(new Error("Hostname is required"), null);
        return;
    }
    props.created = Date.now();
    props.modified = props.created;
    callback(null, props);
};

Host.prototype.beforeUpdate = function(props, callback) {
    props.modified = Date.now();
    callback(null, props);
};

Host.prototype.beforeSave = function(callback) {
    var host = this;

    if (!host.hostname) {
        callback(new Error("Hostname is required"));
        return;
    }
    host.modified = Date.now();
    if (!host.created) {
        host.created = host.modified;
    }
    callback(null);
};

// prevent clashes for same host

Host.schlock = new Schlock();

Host.ensureHost = function(hostname, callback) {

    var host;

    Step(
        function() {
            Host.schlock.writeLock(hostname, this);
        },
        function(err) {
            if (err) throw err;
            Host.get(hostname, this);
        },
        function(err, results) {
            if (err && err.name == "NoSuchThingError") {
                Host.discover(hostname, this);
            } else if (err) {
                throw err;
            } else {
                // XXX: update endpoints?
                this(null, results);
            }
        },
        function(err, results) {
            if (err) throw err;
            host = results;
            Host.schlock.writeUnlock(hostname, this);
        },
        function(err) {
            if (err) {
                callback(err, null);
            } else {
                callback(null, host);
            }
        }
    );
};

Host.discover = function(hostname, callback) {

    var props = {
        hostname: hostname
    };

    Step(
        function() {
            wf.hostmeta(hostname, this);
        },
        function(err, jrd) {
            if (err) throw err;
            var prop,
                rel,
                rels = {
                    registration_endpoint: OAUTH_CRED,
                    request_token_endpoint: OAUTH_RT,
                    access_token_endpoint: OAUTH_AT,
                    authorization_endpoint: OAUTH_AUTHZ,
                    whoami_endpoint: WHOAMI
                };

            for (prop in rels) {
                rel = rels[prop];
                var links = _.where(jrd.links, {rel: rel});
                if (links.length === 0) {
                    callback(new Error(hostname + " does not implement " + rel), null);
                    return;
                } else {
                    props[prop] = links[0].href;
                }
            }

            Host.create(props, this);
        },
        callback
    );
};

Host.prototype.getRequestToken = function(callback) {

    var host = this,
        oa;

    Step(
        function() {
            host.getOAuth(this);
        },
        function(err, results) {
            if (err) throw err;
            oa = results;
            oa.getOAuthRequestToken(this);
        },
        function(err, token, secret, other) {
            if (err) throw err;
            RemoteRequestToken.create({token: token,
                                       secret: secret,
                                       hostname: host.hostname},
                                      this);
        },
        callback
    );
};

Host.prototype.authorizeURL = function(rt) {
    var host = this,
        separator;

    if (_.contains(host.authorization_endpoint, "?")) {
        separator = "&";
    } else {
        separator = "?";
    }

    return host.authorization_endpoint + separator + "oauth_token=" + rt.token;
};

Host.prototype.getAccessToken = function(rt, verifier, callback) {
    var host = this,
        oa;

    Step(
        function() {
            host.getOAuth(this);
        },
        function(err, results) {
            if (err) throw err;
            oa = results;
            oa.getOAuthAccessToken(rt.token, rt.secret, verifier, this);
        },
        function(err, token, secret, res) {
            if (err) {
                callback(err, null);
            } else {
                // XXX: Mark rt as used?
                // XXX: Save the verifier somewhere?
                callback(null, {token: token, secret: secret});
            }
        }
    );
};

Host.prototype.whoami = function(token, secret, callback) {
    var host = this,
        oa;

    Step(
        function() {
            host.getOAuth(this);
        },
        function(err, results) {
            if (err) throw err;
            oa = results;
            oa.get(host.whoami_endpoint, token, secret, this);
        },
        function(err, doc, response) {
            var obj;
            if (err) throw err;
            obj = JSON.parse(doc);
            this(null, obj);
        },
        callback
    );
};

Host.prototype.getOAuth = function(callback) {

    var host = this;

    Step(
        function() {
            Credentials.getForHost(URLMaker.hostname, host, this);
        },
        function(err, cred) {
            var oa;
            if (err) throw err;
            oa = new OAuth(host.request_token_endpoint,
                           host.access_token_endpoint,
                           cred.client_id,
                           cred.client_secret,
                           "1.0",
                           URLMaker.makeURL("/main/authorized/"+host.hostname),
                           "HMAC-SHA1",
                           null, // nonce size; use default
                           {"User-Agent": "pump.io/0.2.0"});
            this(null, oa);
        },
        callback
    );
};

exports.Host = Host;
<commit_msg>Use the version module for User-Agent<commit_after>// host.js
//
// data object representing a remote host
//
// Copyright 2013, StatusNet Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

var _ = require("underscore"),
    wf = require("webfinger"),
    qs = require("querystring"),
    Step = require("step"),
    Schlock = require("schlock"),
    OAuth = require("oauth").OAuth,
    DatabankObject = require("databank").DatabankObject,
    URLMaker = require("../urlmaker").URLMaker,
    version = require("../version").version,
    RemoteRequestToken = require("./remoterequesttoken").RemoteRequestToken,
    Credentials = require("./credentials").Credentials;

var Host = DatabankObject.subClass("host");

var OAUTH_RT = "http://apinamespace.org/oauth/request_token",
    OAUTH_AT = "http://apinamespace.org/oauth/access_token",
    OAUTH_AUTHZ = "http://apinamespace.org/oauth/authorize",
    WHOAMI = "http://apinamespace.org/activitypub/whoami",
    OAUTH_CRED = "registration_endpoint";

Host.schema = {
    pkey: "hostname",
    fields: ["registration_endpoint",
             "request_token_endpoint",
             "access_token_endpoint",
             "authorization_endpoint",
             "whoami_endpoint",
             "created",
             "updated"]
};

Host.beforeCreate = function(props, callback) {
    if (!props.hostname) {
        callback(new Error("Hostname is required"), null);
        return;
    }
    props.created = Date.now();
    props.modified = props.created;
    callback(null, props);
};

Host.prototype.beforeUpdate = function(props, callback) {
    props.modified = Date.now();
    callback(null, props);
};

Host.prototype.beforeSave = function(callback) {
    var host = this;

    if (!host.hostname) {
        callback(new Error("Hostname is required"));
        return;
    }
    host.modified = Date.now();
    if (!host.created) {
        host.created = host.modified;
    }
    callback(null);
};

// prevent clashes for same host

Host.schlock = new Schlock();

Host.ensureHost = function(hostname, callback) {

    var host;

    Step(
        function() {
            Host.schlock.writeLock(hostname, this);
        },
        function(err) {
            if (err) throw err;
            Host.get(hostname, this);
        },
        function(err, results) {
            if (err && err.name == "NoSuchThingError") {
                Host.discover(hostname, this);
            } else if (err) {
                throw err;
            } else {
                // XXX: update endpoints?
                this(null, results);
            }
        },
        function(err, results) {
            if (err) throw err;
            host = results;
            Host.schlock.writeUnlock(hostname, this);
        },
        function(err) {
            if (err) {
                callback(err, null);
            } else {
                callback(null, host);
            }
        }
    );
};

Host.discover = function(hostname, callback) {

    var props = {
        hostname: hostname
    };

    Step(
        function() {
            wf.hostmeta(hostname, this);
        },
        function(err, jrd) {
            if (err) throw err;
            var prop,
                rel,
                rels = {
                    registration_endpoint: OAUTH_CRED,
                    request_token_endpoint: OAUTH_RT,
                    access_token_endpoint: OAUTH_AT,
                    authorization_endpoint: OAUTH_AUTHZ,
                    whoami_endpoint: WHOAMI
                };

            for (prop in rels) {
                rel = rels[prop];
                var links = _.where(jrd.links, {rel: rel});
                if (links.length === 0) {
                    callback(new Error(hostname + " does not implement " + rel), null);
                    return;
                } else {
                    props[prop] = links[0].href;
                }
            }

            Host.create(props, this);
        },
        callback
    );
};

Host.prototype.getRequestToken = function(callback) {

    var host = this,
        oa;

    Step(
        function() {
            host.getOAuth(this);
        },
        function(err, results) {
            if (err) throw err;
            oa = results;
            oa.getOAuthRequestToken(this);
        },
        function(err, token, secret, other) {
            if (err) throw err;
            RemoteRequestToken.create({token: token,
                                       secret: secret,
                                       hostname: host.hostname},
                                      this);
        },
        callback
    );
};

Host.prototype.authorizeURL = function(rt) {
    var host = this,
        separator;

    if (_.contains(host.authorization_endpoint, "?")) {
        separator = "&";
    } else {
        separator = "?";
    }

    return host.authorization_endpoint + separator + "oauth_token=" + rt.token;
};

Host.prototype.getAccessToken = function(rt, verifier, callback) {
    var host = this,
        oa;

    Step(
        function() {
            host.getOAuth(this);
        },
        function(err, results) {
            if (err) throw err;
            oa = results;
            oa.getOAuthAccessToken(rt.token, rt.secret, verifier, this);
        },
        function(err, token, secret, res) {
            if (err) {
                callback(err, null);
            } else {
                // XXX: Mark rt as used?
                // XXX: Save the verifier somewhere?
                callback(null, {token: token, secret: secret});
            }
        }
    );
};

Host.prototype.whoami = function(token, secret, callback) {
    var host = this,
        oa;

    Step(
        function() {
            host.getOAuth(this);
        },
        function(err, results) {
            if (err) throw err;
            oa = results;
            oa.get(host.whoami_endpoint, token, secret, this);
        },
        function(err, doc, response) {
            var obj;
            if (err) throw err;
            obj = JSON.parse(doc);
            this(null, obj);
        },
        callback
    );
};

Host.prototype.getOAuth = function(callback) {

    var host = this;

    Step(
        function() {
            Credentials.getForHost(URLMaker.hostname, host, this);
        },
        function(err, cred) {
            var oa;
            if (err) throw err;
            oa = new OAuth(host.request_token_endpoint,
                           host.access_token_endpoint,
                           cred.client_id,
                           cred.client_secret,
                           "1.0",
                           URLMaker.makeURL("/main/authorized/"+host.hostname),
                           "HMAC-SHA1",
                           null, // nonce size; use default
                           {"User-Agent": "pump.io/"+version});
            this(null, oa);
        },
        callback
    );
};

exports.Host = Host;
<|endoftext|>


<commit_before>'use strict';

function User(apiUser) {
    this._id = apiUser.userId;
    this._login = apiUser.userLogin;
    this._rating = apiUser.userRating;
    this._createdAt = new Date(apiUser.userCreateDate * 1000);
    this._isAllegroStandard = (apiUser.userIsAllegroStandard === 1) ? true : false;
}

User.prototype = {
    get id() {
        return this._id;
    },
    get login() {
        return this._login;
    },
    get rating() {
        return this._rating;
    },
    get createdAt() {
        return this._createdAt;
    },
    get isAllegroStandard() {
        return this._isAllegroStandard;
    }
};

module.exports = User;
<commit_msg>Simplier<commit_after>'use strict';

function User(apiUser) {
    this._id = apiUser.userId;
    this._login = apiUser.userLogin;
    this._rating = apiUser.userRating;
    this._createdAt = new Date(apiUser.userCreateDate * 1000);
    this._isAllegroStandard = apiUser.userIsAllegroStandard === 1;
}

User.prototype = {
    get id() {
        return this._id;
    },
    get login() {
        return this._login;
    },
    get rating() {
        return this._rating;
    },
    get createdAt() {
        return this._createdAt;
    },
    get isAllegroStandard() {
        return this._isAllegroStandard;
    }
};

module.exports = User;
<|endoftext|>


<commit_before>
var uuid = require('node-uuid'),
    util = require('util'),
    events = require('events');

function Node() {
	events.EventEmitter.call(this);

    var id = uuid.v4();
    var services = [];
    var nodes = [];

    this.getDescription = function () {
        return {
            id: id,
            services: services
        };
    };

    this.registerService = function (name, service, description) {
        if (!description) {
            if (service && service.getDescription)
                description = service.getDescription();
            else
                description = { };
        }

        services[name] = { name: name, service: service, description: description };
    };

    this.process = function (msg) {
        this.emit('data', msg);

        if (msg.service) {
            var serviceinfo = services[msg.service];
            if (serviceinfo)
                serviceinfo.service.process(msg.message);
            else {
                var ns = this.getNodes(msg.service);
                if (ns.length) {
                    var position = Math.floor(Math.random() * ns.length);
                    ns[position].node.process(msg);
                }
            }
            return;
        }
        else if (msg.id) {
            if (!nodes[msg.id])
                nodes[msg.id] = { id: msg.id };

            nodes[msg.id].info = msg;
        }
    };

    this.registerNode = function (node) {
        var description = node.getDescription();
        var id = description.id;
        if (!nodes[id])
            nodes[id] = { id: id };
        nodes[id].info = description;
        nodes[id].node = node;
        this.emit('data', description);
    };

    this.connect = function (node) {
        node.registerNode(this);
        this.registerNode(node);
    };

    this.getNodes = function (name, fn) {
        if (!name && !fn)
            return nodes;

        var result = [];

        for (var n in nodes) {
            var node = nodes[n];
            if (!name || node.info.services && node.info.services[name])
                if (!fn || fn(node.info.services[name].description))
                    result.push(node);
        };

        return result;
    };
}

util.inherits(Node, events.EventEmitter);

exports.createNode = function () {
	return new Node();
};

<commit_msg>Refactor node.process<commit_after>
var uuid = require('node-uuid'),
    util = require('util'),
    events = require('events');

function Node() {
	events.EventEmitter.call(this);

    var id = uuid.v4();
    var services = [];
    var nodes = [];

    this.getDescription = function () {
        return {
            id: id,
            services: services
        };
    };

    this.registerService = function (name, service, description) {
        if (!description) {
            if (service && service.getDescription)
                description = service.getDescription();
            else
                description = { };
        }

        services[name] = { name: name, service: service, description: description };
    };

    this.process = function (msg) {
        this.emit('data', msg);

        if (msg.service) {
            var serviceinfo = services[msg.service];
            if (serviceinfo)
                serviceinfo.service.process(msg.message);
            else {
                var ns = this.getNodes(msg.service);
                if (ns.length) {
                    var position = Math.floor(Math.random() * ns.length);
                    ns[position].node.process(msg);
                }
            }
            return;
        }
    };

    this.registerNode = function (node) {
        var description = node.getDescription();
        var id = description.id;
        if (!nodes[id])
            nodes[id] = { id: id };
        nodes[id].info = description;
        nodes[id].node = node;
        this.emit('data', description);
    };

    this.connect = function (node) {
        node.registerNode(this);
        this.registerNode(node);
    };

    this.getNodes = function (name, fn) {
        if (!name && !fn)
            return nodes;

        var result = [];

        for (var n in nodes) {
            var node = nodes[n];
            if (!name || node.info.services && node.info.services[name])
                if (!fn || fn(node.info.services[name].description))
                    result.push(node);
        };

        return result;
    };
}

util.inherits(Node, events.EventEmitter);

exports.createNode = function () {
	return new Node();
};

<|endoftext|>


<commit_before>var Transport = require("./transport").Transport,
    MailComposer = require("mailcomposer").MailComposer,
    XOAuthGenerator = require("./xoauth").XOAuthGenerator,
    helpers = require("./helpers");

/*
 * Version constants
 */
var X_MAILER_NAME = "Nodemailer",
    X_MAILER_VERSION = "0.3.14; +https://github.com/andris9/Nodemailer";

module.exports.X_MAILER_NAME = X_MAILER_NAME;
module.exports.X_MAILER_VERSION = X_MAILER_VERSION;

// Export createTransport method
module.exports.createTransport = function(type, options){
    var transport = new Transport(type, options);
    transport.sendMail = function(options, callback){
        options = options || {};
        options.transport = options.transport || transport;
        sendMail(options, callback);
    };
    return transport;
};

//Export createXOAuthGenerator function
module.exports.createXOAuthGenerator = function(options){
	return new XOAuthGenerator(options);
};

// Export Transport constructor
module.exports.Transport = Transport;

// Export Nodemailer constructor
module.exports.Nodemailer = Nodemailer;

// Export sendMail function (and the alias send_mail for legacy)
module.exports.sendMail = module.exports.send_mail = sendMail;

function sendMail(options, callback){
    var mailer = new Nodemailer(options);

    mailer.validateSettings(function(err){
        if(err){
            // report validation error back to the client
            return callback(err);
        }else{
            // try to send the e-mail message
            mailer.sendMail(callback);
        }
    });

    return mailer;
}

/**
 *
Generates a Nodemailer object which is the main 'hub' for managing the
 * send process

 *
 * @constructor
 * @param {Object} options Message options object, see README for the complete list of possible options
 */
function Nodemailer(options){
    this.options = options || {};

    this.transport = this.options.transport;

    this.mailcomposer = new MailComposer();

    if(!this.transport){
        this.transport = this.getGlobalTransport();
    }
}

/**
 *
Add support for legacy transport settings by checking for global
 * variables SMTP, sendmail and SES

 *
 * @return {Object} {@link Transport} object
 */
Nodemailer.prototype.getGlobalTransport = function(){
    if(this.options.SMTP){
        // cache the transport for SMTP as it is actually a connection pool
        if(!this.options.SMTP._smtp_transport){
            this.options.SMTP._smtp_transport = new Transport("SMTP", this.options.SMTP);
        }
        return this.options.SMTP._smtp_transport;
    }else if(this.options.sendmail){
        return new Transport("sendmail", this.options.sendmail);
    }else if(this.options.SES){
        return new Transport("SES", this.options.SES);
    }else if(module.exports.SMTP){
        // cache the transport for SMTP as it is actually a connection pool
        if(!module.exports._smtp_transport){
            module.exports._smtp_transport = new Transport("SMTP", module.exports.SMTP);
        }
        return module.exports._smtp_transport;
    }else if(module.exports.sendmail){
        return new Transport("sendmail", module.exports.sendmail);
    }else if(module.exports.SES){
        return new Transport("SES", module.exports.SES);
    }
    return false;
};

/**
 *
Doesn't do much currently, if the future should link to transport
 * validation methods. For example in case of SES should check that AWS
 * keys are set up etc.

 *
 * @param {Function} callback Callback function to run after validation
 */
Nodemailer.prototype.validateSettings = function(callback){
    if(!this.transport || !this.transport.transport){
        return callback(new Error("No transport method defined"));
    }
    callback(null);
};

/**
 *
Send the e-mail message by using data from the original options object
 * and selected transport

 *
 * @param {Function} callback Callback function to run when the e-mail has been sent (or it failed)
 */
Nodemailer.prototype.sendMail = function(callback){
    // compose the e-mail
    this.generateMailObject();
    // send the message using preselected transport method
    this.transport.sendMailWithTransport(this.mailcomposer, callback);
};

/**
 *
Uses the data from the original options object to compose a mailcomposer
 * e-mail message that can be later streamed to the selected transport

 */
Nodemailer.prototype.generateMailObject = function(){

    // set envelope data, subject etc.
    this.setGeneralOptions();

    // set module defined headers (date, message-id, etc.)
    this.setModuleHeaders();

    // set user defined headers (if any)
    this.setUserHeaders();

    // set attachments (if any)
    this.setAttachments();
};

/**
 *
Uses the general options (message sender and receiver, subject body, etc.)
 * to set mailcomposer properties. Includes support for legacy properties.

 */
Nodemailer.prototype.setGeneralOptions = function(){

    // generate plaintext if only HTML exists and generateTextFromHTML is true
    if(!(this.options.text || this.options.body) && (this.options.html) &&
      this.options.generateTextFromHTML){
        this.options.text = helpers.stripHTML(this.options.html);
    }

    var acceptedFields = ["from", "sender", "to", "subject", "replyTo", "debug",
                          "reply_to", "cc", "bcc", "body", "text", "html",
                          "envelope"],
        mailOptions = {},
        keys = Object.keys(this.options),
        key;

    for(var i=0, len=keys.length; i<len; i++){
        key = keys[i];
        if(acceptedFields.indexOf(key) >=0 && this.options[key]){
            mailOptions[key] = this.options[key];
        }
    }

    if(this.options.debug){
        console.log(mailOptions);
    }

    this.mailcomposer.setMessageOption(mailOptions);
};

/**
 *
If the 'headers' property was set on the options, add the values to the
 * header of the e-mail message

 */
Nodemailer.prototype.setUserHeaders = function(){
    if(typeof this.options.headers != "object"){
        return;
    }
    var keys = Object.keys(this.options.headers),
        key;

    for(var i=0, len=keys.length; i<len; i++){
        key = keys[i];
        if(this.options.headers[key]){
            this.mailcomposer.addHeader(key, this.options.headers[key]);
        }
    }
};

/**
 *
Add some required headers to the message, such as Date: and Message-Id:

 */
Nodemailer.prototype.setModuleHeaders = function(){

    // Mailer name + version
    this.mailcomposer.addHeader("X-Mailer", X_MAILER_NAME+
        (X_MAILER_VERSION?" ("+X_MAILER_VERSION+")":""));

    // Date
    this.mailcomposer.addHeader("Date", new Date().toUTCString());

    // Message ID
    if(this.options.messageId){
        this.mailcomposer.addHeader("Message-Id", "<"+this.options.messageId+">");
    }else{
        this.mailcomposer.addHeader("Message-Id", "<"+
                Date.now()+Math.random().toString(16).substr(1)+"@"+
                X_MAILER_NAME+">");
    }
};

/**
 *
If attachment array is set on the options object, add these attachments
 * to the mailcomposer object

 */
Nodemailer.prototype.setAttachments = function(){
    if(!Array.isArray(this.options.attachments)){
        return;
    }
    var attachment;
    for(var i=0, len=this.options.attachments.length; i<len; i++){
        attachment = this.options.attachments[i];
        this.mailcomposer.addAttachment(attachment);
    }
};<commit_msg>Include version nr. for user agent from package.json<commit_after>var Transport = require("./transport").Transport,
    MailComposer = require("mailcomposer").MailComposer,
    XOAuthGenerator = require("./xoauth").XOAuthGenerator,
    helpers = require("./helpers"),
    packageData = require("../package.json");

/*
 * Version constants
 */
var X_MAILER_NAME = "Nodemailer",
    X_MAILER_HOMEPAGE = "http://andris9.github.com/Nodemailer/";

module.exports.X_MAILER_NAME = X_MAILER_NAME;
module.exports.X_MAILER_HOMEPAGE = X_MAILER_HOMEPAGE;

// Export createTransport method
module.exports.createTransport = function(type, options){
    var transport = new Transport(type, options);
    transport.sendMail = function(options, callback){
        options = options || {};
        options.transport = options.transport || transport;
        sendMail(options, callback);
    };
    return transport;
};

//Export createXOAuthGenerator function
module.exports.createXOAuthGenerator = function(options){
	return new XOAuthGenerator(options);
};

// Export Transport constructor
module.exports.Transport = Transport;

// Export Nodemailer constructor
module.exports.Nodemailer = Nodemailer;

// Export sendMail function (and the alias send_mail for legacy)
module.exports.sendMail = module.exports.send_mail = sendMail;

function sendMail(options, callback){
    var mailer = new Nodemailer(options);

    mailer.validateSettings(function(err){
        if(err){
            // report validation error back to the client
            return callback(err);
        }else{
            // try to send the e-mail message
            mailer.sendMail(callback);
        }
    });

    return mailer;
}

/**
 *
Generates a Nodemailer object which is the main 'hub' for managing the
 * send process

 *
 * @constructor
 * @param {Object} options Message options object, see README for the complete list of possible options
 */
function Nodemailer(options){
    this.options = options || {};

    this.transport = this.options.transport;

    this.mailcomposer = new MailComposer();

    if(!this.transport){
        this.transport = this.getGlobalTransport();
    }
}

/**
 *
Generates an user agent string for Nodemailer with homepage, version etc.

 *
 * @return {String} user agent string for X-Mailer value
 */
Nodemailer.prototype.generateUserAgentString = function(){
	var details = [];

	if(packageData.version){
		details.push(packageData.version);
	}

	if(X_MAILER_HOMEPAGE){
		details.push("+"+X_MAILER_HOMEPAGE);
	}

	return X_MAILER_NAME+ (details.length?" ("+details.join("; ")+")":"");
}

/**
 *
Add support for legacy transport settings by checking for global
 * variables SMTP, sendmail and SES

 *
 * @return {Object} {@link Transport} object
 */
Nodemailer.prototype.getGlobalTransport = function(){
    if(this.options.SMTP){
        // cache the transport for SMTP as it is actually a connection pool
        if(!this.options.SMTP._smtp_transport){
            this.options.SMTP._smtp_transport = new Transport("SMTP", this.options.SMTP);
        }
        return this.options.SMTP._smtp_transport;
    }else if(this.options.sendmail){
        return new Transport("sendmail", this.options.sendmail);
    }else if(this.options.SES){
        return new Transport("SES", this.options.SES);
    }else if(module.exports.SMTP){
        // cache the transport for SMTP as it is actually a connection pool
        if(!module.exports._smtp_transport){
            module.exports._smtp_transport = new Transport("SMTP", module.exports.SMTP);
        }
        return module.exports._smtp_transport;
    }else if(module.exports.sendmail){
        return new Transport("sendmail", module.exports.sendmail);
    }else if(module.exports.SES){
        return new Transport("SES", module.exports.SES);
    }
    return false;
};

/**
 *
Doesn't do much currently, if the future should link to transport
 * validation methods. For example in case of SES should check that AWS
 * keys are set up etc.

 *
 * @param {Function} callback Callback function to run after validation
 */
Nodemailer.prototype.validateSettings = function(callback){
    if(!this.transport || !this.transport.transport){
        return callback(new Error("No transport method defined"));
    }
    callback(null);
};

/**
 *
Send the e-mail message by using data from the original options object
 * and selected transport

 *
 * @param {Function} callback Callback function to run when the e-mail has been sent (or it failed)
 */
Nodemailer.prototype.sendMail = function(callback){
    // compose the e-mail
    this.generateMailObject();
    // send the message using preselected transport method
    this.transport.sendMailWithTransport(this.mailcomposer, callback);
};

/**
 *
Uses the data from the original options object to compose a mailcomposer
 * e-mail message that can be later streamed to the selected transport

 */
Nodemailer.prototype.generateMailObject = function(){

    // set envelope data, subject etc.
    this.setGeneralOptions();

    // set module defined headers (date, message-id, etc.)
    this.setModuleHeaders();

    // set user defined headers (if any)
    this.setUserHeaders();

    // set attachments (if any)
    this.setAttachments();
};

/**
 *
Uses the general options (message sender and receiver, subject body, etc.)
 * to set mailcomposer properties. Includes support for legacy properties.

 */
Nodemailer.prototype.setGeneralOptions = function(){

    // generate plaintext if only HTML exists and generateTextFromHTML is true
    if(!(this.options.text || this.options.body) && (this.options.html) &&
      this.options.generateTextFromHTML){
        this.options.text = helpers.stripHTML(this.options.html);
    }

    var acceptedFields = ["from", "sender", "to", "subject", "replyTo", "debug",
                          "reply_to", "cc", "bcc", "body", "text", "html",
                          "envelope"],
        mailOptions = {},
        keys = Object.keys(this.options),
        key;

    for(var i=0, len=keys.length; i<len; i++){
        key = keys[i];
        if(acceptedFields.indexOf(key) >=0 && this.options[key]){
            mailOptions[key] = this.options[key];
        }
    }

    if(this.options.debug){
        console.log(mailOptions);
    }

    this.mailcomposer.setMessageOption(mailOptions);
};

/**
 *
If the 'headers' property was set on the options, add the values to the
 * header of the e-mail message

 */
Nodemailer.prototype.setUserHeaders = function(){
    if(typeof this.options.headers != "object"){
        return;
    }
    var keys = Object.keys(this.options.headers),
        key;

    for(var i=0, len=keys.length; i<len; i++){
        key = keys[i];
        if(this.options.headers[key]){
            this.mailcomposer.addHeader(key, this.options.headers[key]);
        }
    }
};

/**
 *
Add some required headers to the message, such as Date: and Message-Id:

 */
Nodemailer.prototype.setModuleHeaders = function(){

    // Mailer name + version
    this.mailcomposer.addHeader("X-Mailer", this.generateUserAgentString);

    // Date
    this.mailcomposer.addHeader("Date", new Date().toUTCString());

    // Message ID
    if(this.options.messageId){
        this.mailcomposer.addHeader("Message-Id", "<"+this.options.messageId+">");
    }else{
        this.mailcomposer.addHeader("Message-Id", "<"+
                Date.now()+Math.random().toString(16).substr(1)+"@"+
                X_MAILER_NAME+">");
    }
};

/**
 *
If attachment array is set on the options object, add these attachments
 * to the mailcomposer object

 */
Nodemailer.prototype.setAttachments = function(){
    if(!Array.isArray(this.options.attachments)){
        return;
    }
    var attachment;
    for(var i=0, len=this.options.attachments.length; i<len; i++){
        attachment = this.options.attachments[i];
        this.mailcomposer.addAttachment(attachment);
    }
};<|endoftext|>


<commit_before>var utils = require('./utils')
  , Model = require('./model')
  , implode = require('./implode')
  , Group = exports.Group = Model.define('Group', function() {
  this.property('collection')
})

exports.Array = function(values, model) {
  if (!model && !Array.isArray(values)) {
    model = values
    values = null
  }

  var array = values || new Array
    , byId = {}
    , positionModifier = 0

  utils.eventify(array)
  var contract = ['model', 'events']
  contract.id = 'ObservableArray'
  Object.defineProperty(array, '$contract', {
    value: contract,
    writable: true
  })
  array.model = model
  array.compareFunction = null

  function adding(element) {
    if (typeof element !== 'object') return element
    if (model && !(element instanceof model)) {
      element = new model(element)
      element.isNew = false
    }
    if (!('_id' in element)) return element
    if (element._id) {
      byId[element._id] = element
    } else {
      if (gotEventified(element)) {
        function reassign() {
          byId[element._id] = element
          if (element._changedValues._id !== undefined
            ||element._changedValues._id !== null)
            delete byId[element._changedValues._id]
        }
        element.on('changed._id', reassign)
        element.once('removed', element.off.bind(element,'changed._id', reassign))
      }
    }
    return element
  }

  function added(element) {
    if (typeof element === 'object') {
      if (gotEventified(element)) {
        element.on('changed', array, 'emit', array, 'changed')
        element.once('destroy', array, 'remove', array, element)
      }
    }

    array.emit('added', element)
  }

  function removed(element) {
    if (byId[element._id] === element)
      delete byId[element._id]
    array.emit('removed', element)
    if (gotEventified(element)) {
      element.emit('removed', array)
      element.off('changed', array)
      element.off('destroy', array)
      if (array.compareFunction && array.compareFunction.affected) {
        array.compareFunction.affected.forEach(function(key) {
          element.off('changed.' + key, array)
        })
      }
    }
  }

  array.find = function(id) {
    return byId[id]
  }

  array.pop = function() {
    var result = Array.prototype.pop.apply(this, arguments)
    removed(result)
    this.emit('changed')
    return result
  }

  array.push = function() {
    var that = this
      , elements = Array.prototype.slice.call(arguments)
      , result

    if (!this.compareFunction) {
      elements.forEach(function(element) {
        result = Array.prototype.push.call(that, (element = adding(element)))
        added(element)
      })
    } else {
      elements.forEach(function(element) {
        var idx = binarySearch(that, element, true, that.compareFunction)
        Array.prototype.splice.call(that, idx, 0, (element = adding(element)))
        added(element)
        if (that.compareFunction.affected) {
          that.compareFunction.affected.forEach(function(key) {
            element.on('changed.' + key, that, 'reinsert', that, element)
          })
        }
      })
    }

    this.emit('changed')
    return result
  }

  array.add = function(element) {
    return this[this.push(element) - 1]
  }

  var remove = array.remove = function(element) {
    var index = -1
    if ((index = this.indexOf(element)) === -1)
      return false
    return this.splice(index, 1)
  }

  array.reverse = function() {
    this.unsort()
    Array.prototype.reverse.apply(this, arguments)
    this.emit('changed')
  }

  array.shift = function() {
    var result = Array.prototype.shift.apply(this, arguments)
    removed(result)
    this.emit('changed')
    return result
  }

  array.sort = function(compareFunction) {
    var that = this

    this.compareFunction = compareFunction || function(lhs, rhs) {
      if (lhs < rhs) return -1
      if (lhs === rhs) return 0
      else return 1
    }
    var sorted = mergeSort(this, this.compareFunction)
    for (var i = 0; i < sorted.length; ++i) {
      this[i] = sorted[i]
    }

    if (model) {
      var tmp1 = new model
        , tmp2 = new model

      this.compareFunction.affected = []
      this.compareFunction.fragment = {
        observe: function(_, key) {
          if (that.compareFunction.affected.indexOf(key) === -1)
            that.compareFunction.affected.push(key)
        }
      }
      this.compareFunction(tmp1, tmp2)
      delete this.compareFunction.fragment

      this.forEach(function(element) {
        that.compareFunction.affected.forEach(function(key) {
          element.on('changed.' + key, that, 'reinsert', that, element)
        })
      })
    }

    array.emit('changed')
  }

  array.unsort = function() {
    var that = this
    if (this.compareFunction && this.compareFunction.affected) {
      this.forEach(function(element) {
        that.compareFunction.affected.forEach(function(key) {
          element.off('changed.' + key, that, 'reinsert')
        })
      })
    }
    this.compareFunction = null
  }

  array.reinsert = function(element) {
    var index = this.indexOf(element)
    if (index === -1) return

    this.splice(index, 1)
    this.push(element)

    if (this.compareFunction && index !== this.indexOf(element)) {
      // throw moved event
    }
  }

  array.splice = function() {
    var args = Array.prototype.slice.call(arguments)
      , index = args.shift()
      , howMany = args.shift()
      , newElements = []
      , that = this

    args.forEach(function(element) {
      newElements.push(element)
    })

    if (!this.compareFunction) {
      for (var i = 0; i < newElements.length; ++i)
        newElements[i] = adding(newElements[i])
      args = [index, howMany].concat(newElements)
    } else {
      args = [index, howMany]
    }

    var result = Array.prototype.splice.apply(this, args)
    result.forEach(function(element) {
      removed(element)
    })

    if (this.compareFunction) {
      this.push.apply(this, newElements)
    }

    newElements.slice(2).forEach(function(element) {
      added(element)
    })

    array.emit('changed')
    return result
  }

  array.unshift = function() {
    var that = this
      , elements = Array.prototype.slice.call(arguments)
      , result

    if (!this.compareFunction) {
      elements.forEach(function(element) {
        result = Array.prototype.unshift.call(that, (element = adding(element)))
        added(element)
      })
    } else {
      this.push.apply(this, elements)
    }

    this.emit('changed')
    return result
  }

  array.reset = function(elements) {
    this.unsort()
    if (typeof elements === 'undefined') elements = []
    elements.unshift(0, array.length)
    array.splice.apply(array, elements)
    return array.length
  }

  Object.defineProperty(array, 'size', {
    get: function() {
      if (typeof arguments.callee.caller.fragment != 'undefined')
        arguments.callee.caller.fragment.observe(array)

      return array.length
    }
  })

  array.groupBy = exports.GroupedArray.bind(array, model)

  array.forEach(function(item) {
    added(item)
  })

  return array
}

exports.GroupedArray = function(model, prop, sub) {
  var grouped = exports.Array(Group)
  grouped.prop = prop
  grouped.$contract.id = 'ObservableGroupedArray'
  grouped.$contract.push('prop')

  grouped.check = function(group) {
    if (group.collection.length === 0) {
      group.off('changed', this)
      group.destroy()
    }
  }

  grouped.pivotChanged = function(item) {
    this.remove(item, true)
    this.add(item)
  }

  grouped.add = function(item) {
    var group = this.get(item[this.prop])
    if (!group) {
      group = new Group({ collection: (sub ? new sub : exports.Array(model)) })
      group._id = item[this.prop]
      group.isNew = true
      group.collection.on('removed', this, 'check', this, group)
      group.collection.on('changed', this, 'emit', this, 'changed')
      this.push(group)
    }
    item = group.collection.add(item)
    if (gotEventified(item))
      item.once('changed.' + this.prop, this, 'pivotChanged', this, item)
  }

  grouped.get = grouped.find

  var remove = function(element) {
    var index = -1
    if ((index = this.indexOf(element)) === -1)
      return false
    return this.splice(index, 1)
  }

  grouped.remove = function(item, move) {
    if (typeof item === 'undefined') return
    if (item instanceof Group)
      return remove.call(this, item)
    if (gotEventified(item))
      item.off('changed.' + this.prop, this)
    var group = this.get(move ? item._changedValues[this.prop] : item[this.prop])
    if (!group) return
    group.collection.remove(item)
  }

  grouped.find = function(id) {
    for (var i = 0; i < this.length; ++i) {
      var result = this[i].collection.find(id)
      if (result) return result
    }
    return null
  }

  if (this.length > 0)
    this.forEach(grouped.add.bind(grouped))

  return grouped
}

implode.register('ObservableArray', exports.Array, ['model', 'events'])
implode.register('ObservableGroupedArray', exports.GroupedArray, ['model', 'events'])

function gotEventified(obj) {
  return obj && typeof obj.emit === 'function'
}

var merge = function(lhs, rhs, compareFunction) {
  var result = []

  while (lhs.length && rhs.length) {
    if (compareFunction(lhs[0], rhs[0]) < 1)
      result.push(lhs.shift())
    else
      result.push(rhs.shift())
  }

  while (lhs.length)
    result.push(lhs.shift())

  while (rhs.length)
    result.push(rhs.shift())

  return result
}

function mergeSort(arr, compareFunction) {
  if (arr.length < 2)
    return arr

  var middle = parseInt(arr.length / 2)
  var left   = arr.slice(0, middle)
  var right  = arr.slice(middle, arr.length)

  return merge(mergeSort(left, compareFunction),
               mergeSort(right, compareFunction),
               compareFunction)
}

// modified version of
//+ Carlos R. L. Rodrigues
//@ http://jsfromhell.com/array/search [rev. #2]
// o: array that will be looked up
// v: object that will be searched
// b: if true, the function will return the index where the value should be inserted to keep the array ordered, otherwise returns the index where the value was found or -1 if it wasn't found
function binarySearch(o, v, i, compareFunction){
    var h = o.length, l = -1, m
    while(h - l > 1)
        if(compareFunction(o[m = h + l >> 1], v) < 1) l = m
        else h = m
    return o[h] != v ? i ? h : -1 : h
}<commit_msg>add model `moved` event<commit_after>var utils = require('./utils')
  , Model = require('./model')
  , implode = require('./implode')
  , Group = exports.Group = Model.define('Group', function() {
  this.property('collection')
})

exports.Array = function(values, model) {
  if (!model && !Array.isArray(values)) {
    model = values
    values = null
  }

  var array = values || new Array
    , byId = {}
    , positionModifier = 0

  utils.eventify(array)
  var contract = ['model', 'events']
  contract.id = 'ObservableArray'
  Object.defineProperty(array, '$contract', {
    value: contract,
    writable: true
  })
  array.model = model
  array.compareFunction = null

  function adding(element) {
    if (typeof element !== 'object') return element
    if (model && !(element instanceof model)) {
      element = new model(element)
      element.isNew = false
    }
    if (!('_id' in element)) return element
    if (element._id) {
      byId[element._id] = element
    } else {
      if (gotEventified(element)) {
        function reassign() {
          byId[element._id] = element
          if (element._changedValues._id !== undefined
            ||element._changedValues._id !== null)
            delete byId[element._changedValues._id]
        }
        element.on('changed._id', reassign)
        element.once('removed', element.off.bind(element,'changed._id', reassign))
      }
    }
    return element
  }

  function added(element, index) {
    if (typeof element === 'object') {
      if (gotEventified(element)) {
        element.on('changed', array, 'emit', array, 'changed')
        element.once('destroy', array, 'remove', array, element)
      }
    }

    array.emit('added', element, index)
  }

  function removed(element) {
    if (byId[element._id] === element)
      delete byId[element._id]
    array.emit('removed', element)
    if (gotEventified(element)) {
      element.emit('removed', array)
      element.off('changed', array)
      element.off('destroy', array)
      if (array.compareFunction && array.compareFunction.affected) {
        array.compareFunction.affected.forEach(function(key) {
          element.off('changed.' + key, array)
        })
      }
    }
  }

  array.find = function(id) {
    return byId[id]
  }

  array.pop = function() {
    var result = Array.prototype.pop.apply(this, arguments)
    removed(result)
    this.emit('changed')
    return result
  }

  array.push = function() {
    var that = this
      , elements = Array.prototype.slice.call(arguments)
      , result

    if (!this.compareFunction) {
      elements.forEach(function(element) {
        result = Array.prototype.push.call(that, (element = adding(element)))
        added(element)
      })
    } else {
      elements.forEach(function(element) {
        var idx = binarySearch(that, element, true, that.compareFunction)
        Array.prototype.splice.call(that, idx, 0, (element = adding(element)))
        added(element, idx)
        if (that.compareFunction.affected) {
          that.compareFunction.affected.forEach(function(key) {
            element.on('changed.' + key, that, 'reinsert', that, element)
          })
        }
      })
    }

    this.emit('changed')
    return result
  }

  array.add = function(element) {
    return this[this.push(element) - 1]
  }

  var remove = array.remove = function(element) {
    var index = -1
    if ((index = this.indexOf(element)) === -1)
      return false
    return this.splice(index, 1)
  }

  array.reverse = function() {
    this.unsort()
    Array.prototype.reverse.apply(this, arguments)
    this.emit('changed')
  }

  array.shift = function() {
    var result = Array.prototype.shift.apply(this, arguments)
    removed(result)
    this.emit('changed')
    return result
  }

  array.sort = function(compareFunction) {
    var that = this

    this.compareFunction = compareFunction || function(lhs, rhs) {
      if (lhs < rhs) return -1
      if (lhs === rhs) return 0
      else return 1
    }
    var sorted = mergeSort(this, this.compareFunction)
    for (var i = 0; i < sorted.length; ++i) {
      this[i] = sorted[i]
      if (gotEventified(this[i]))
        this[i].emit('moved', i)
    }

    if (model) {
      var tmp1 = new model
        , tmp2 = new model

      this.compareFunction.affected = []
      this.compareFunction.fragment = {
        observe: function(_, key) {
          if (that.compareFunction.affected.indexOf(key) === -1)
            that.compareFunction.affected.push(key)
        }
      }
      this.compareFunction(tmp1, tmp2)
      delete this.compareFunction.fragment

      this.forEach(function(element) {
        that.compareFunction.affected.forEach(function(key) {
          element.on('changed.' + key, that, 'reinsert', that, element)
        })
      })
    }

    array.emit('changed')
  }

  array.unsort = function() {
    var that = this
    if (this.compareFunction && this.compareFunction.affected) {
      this.forEach(function(element) {
        that.compareFunction.affected.forEach(function(key) {
          element.off('changed.' + key, that, 'reinsert')
        })
      })
    }
    this.compareFunction = null
  }

  array.reinsert = function(element) {
    var index = this.indexOf(element)
    if (index === -1) return

    this.splice(index, 1)
    this.push(element)

    if (this.compareFunction && index !== this.indexOf(element)) {
      if (gotEventified(element))
        element.emit('moved', index)
    }
  }

  array.splice = function() {
    var args = Array.prototype.slice.call(arguments)
      , index = args.shift()
      , howMany = args.shift()
      , newElements = []
      , that = this

    args.forEach(function(element) {
      newElements.push(element)
    })

    if (!this.compareFunction) {
      for (var i = 0; i < newElements.length; ++i)
        newElements[i] = adding(newElements[i])
      args = [index, howMany].concat(newElements)
    } else {
      args = [index, howMany]
    }

    var result = Array.prototype.splice.apply(this, args)
    result.forEach(function(element) {
      removed(element)
    })

    if (this.compareFunction) {
      this.push.apply(this, newElements)
    }

    newElements.slice(2).forEach(function(element) {
      added(element)
    })

    array.emit('changed')
    return result
  }

  array.unshift = function() {
    var that = this
      , elements = Array.prototype.slice.call(arguments)
      , result

    if (!this.compareFunction) {
      elements.forEach(function(element) {
        result = Array.prototype.unshift.call(that, (element = adding(element)))
        added(element)
      })
    } else {
      this.push.apply(this, elements)
    }

    this.emit('changed')
    return result
  }

  array.reset = function(elements) {
    this.unsort()
    if (typeof elements === 'undefined') elements = []
    elements.unshift(0, array.length)
    array.splice.apply(array, elements)
    return array.length
  }

  Object.defineProperty(array, 'size', {
    get: function() {
      if (typeof arguments.callee.caller.fragment != 'undefined')
        arguments.callee.caller.fragment.observe(array)

      return array.length
    }
  })

  array.groupBy = exports.GroupedArray.bind(array, model)

  array.forEach(function(item) {
    added(item)
  })

  return array
}

exports.GroupedArray = function(model, prop, sub) {
  var grouped = exports.Array(Group)
  grouped.prop = prop
  grouped.$contract.id = 'ObservableGroupedArray'
  grouped.$contract.push('prop')

  grouped.check = function(group) {
    if (group.collection.length === 0) {
      group.off('changed', this)
      group.destroy()
    }
  }

  grouped.pivotChanged = function(item) {
    this.remove(item, true)
    this.add(item)
  }

  grouped.add = function(item) {
    var group = this.get(item[this.prop])
    if (!group) {
      group = new Group({ collection: (sub ? new sub : exports.Array(model)) })
      group._id = item[this.prop]
      group.isNew = true
      group.collection.on('removed', this, 'check', this, group)
      group.collection.on('changed', this, 'emit', this, 'changed')
      this.push(group)
    }
    item = group.collection.add(item)
    if (gotEventified(item))
      item.once('changed.' + this.prop, this, 'pivotChanged', this, item)
  }

  grouped.get = grouped.find

  var remove = function(element) {
    var index = -1
    if ((index = this.indexOf(element)) === -1)
      return false
    return this.splice(index, 1)
  }

  grouped.remove = function(item, move) {
    if (typeof item === 'undefined') return
    if (item instanceof Group)
      return remove.call(this, item)
    if (gotEventified(item))
      item.off('changed.' + this.prop, this)
    var group = this.get(move ? item._changedValues[this.prop] : item[this.prop])
    if (!group) return
    group.collection.remove(item)
  }

  grouped.find = function(id) {
    for (var i = 0; i < this.length; ++i) {
      var result = this[i].collection.find(id)
      if (result) return result
    }
    return null
  }

  if (this.length > 0)
    this.forEach(grouped.add.bind(grouped))

  return grouped
}

implode.register('ObservableArray', exports.Array, ['model', 'events'])
implode.register('ObservableGroupedArray', exports.GroupedArray, ['model', 'events'])

function gotEventified(obj) {
  return obj && typeof obj.emit === 'function'
}

var merge = function(lhs, rhs, compareFunction) {
  var result = []

  while (lhs.length && rhs.length) {
    if (compareFunction(lhs[0], rhs[0]) < 1)
      result.push(lhs.shift())
    else
      result.push(rhs.shift())
  }

  while (lhs.length)
    result.push(lhs.shift())

  while (rhs.length)
    result.push(rhs.shift())

  return result
}

function mergeSort(arr, compareFunction) {
  if (arr.length < 2)
    return arr

  var middle = parseInt(arr.length / 2)
  var left   = arr.slice(0, middle)
  var right  = arr.slice(middle, arr.length)

  return merge(mergeSort(left, compareFunction),
               mergeSort(right, compareFunction),
               compareFunction)
}

// modified version of
//+ Carlos R. L. Rodrigues
//@ http://jsfromhell.com/array/search [rev. #2]
// o: array that will be looked up
// v: object that will be searched
// b: if true, the function will return the index where the value should be inserted to keep the array ordered, otherwise returns the index where the value was found or -1 if it wasn't found
function binarySearch(o, v, i, compareFunction){
    var h = o.length, l = -1, m
    while(h - l > 1)
        if(compareFunction(o[m = h + l >> 1], v) < 1) l = m
        else h = m
    return o[h] != v ? i ? h : -1 : h
}<|endoftext|>


<commit_before>const _ = require('lodash');

// module tools
const tools = require('./tools');

const {
  toBool, toNumber, isObjectID, parseDateCustom
} = tools;

function parseQuery(query, options = {}) {
  /**

  reserved keys: q,t,f,s,sk,l,p

  [q=<query>][&t=<type>][&f=<fields>][&s=<order>][&sk=<skip>][&l=<limit>][&p=<populate>]
  q=<query> - restrict results by the specified JSON query
  t=<type> - find|findOne|count|aggregate|distinct..
  f=<set of fields> - specify the set of fields to include or exclude in
    each document (1 - include; 0 - exclude)
  s=<sort order> - specify the order in which to sort each specified
    field (1- ascending; -1 - descending)
  sk=<num results to skip> - specify the number of results to skip in
    the result set; useful for paging
  l=<limit> - specify the limit for the number of results (default is 1000)
  p=<populate> - specify the fields for populate

  alternative search conditions:
  "key={in}a,b"
  "At least one of these is in array"

  "key={nin}a,b"
  "Any of these values is not in array"

  "key={all}a,b"
  "All of these contains in array"

  "key={empty}-"
   "Field is empty or not exists"

  "key={!empty}-"
   "Field exists and is not empty"

  "key={mod}a,b"
  "Docs where key mod a is b"

  "key={gt}a"
  "Docs key is greater than a"

  "key={lt}a"
  "Docs key is lower than a"

  "key=a|b|c"
  "Docs where key type is Array, contains at least one of given value
  */
  _.defaults(options, { includeAllParams: true });

  const qy = {
    q: {}, //  query
    map: '',
    reduce: '',
    t: 'find', //  count
    f: false, // fields
    s: false, //  sort
    sk: false, //  skip
    l: 1000, //  limit
    p: false, // populate
    fl: false // flat
  };

  const arrayOperators = ['in', 'nin', 'all', 'mod'];

  function addCondition(key, val) {
    if (['$or', '$nor', '$and'].indexOf(key) !== -1) {
      if (!_.has(qy.q, key)) {
        qy.q[key] = [];
      }
      qy.q[key].push(val);
    } else {
      qy.q[key] = val;
    }
  }

  function parseParam(key, val) {
    const lcKey = key;
    let operator = false;
    if (typeof val === 'string') {
      operator = val.match(/\{(.*)\}/);
      if (operator) {
        // eslint-disable-next-line no-param-reassign
        val = val.replace(/\{(.*)\}/, '');
        [, operator] = operator;
      }

      if (/^\/.*\/\S*?$/.test(val)) {
        const matches = val.match(/\/(.*)\/(\S*)?$/);
        // eslint-disable-next-line no-param-reassign
        val = new RegExp(matches[1], matches[2]);
      }

      // there is problem when/if value is valid objectid but it
      // should be handled as a number, e.g. "123456789012345678901234"
      // anyway - collision propability is quite low.
      if (!isObjectID(val)) {
        const num = toNumber(val);
        if (!Number.isNaN(num)) {
          // eslint-disable-next-line no-param-reassign
          val = num;
        } else {
          const date = parseDateCustom(val);
          if (!Number.isNaN(date)
              && operator !== 'size'
              && arrayOperators.indexOf(operator) === -1
              && ['i', 'e', 'b', 'm'].indexOf(operator) === -1) {
            // eslint-disable-next-line no-param-reassign
            val = date;
          }
        }
      }
    }
    if (key.startsWith('$')) {
      // bypass $ characters for security reasons
      throw new Error(`$ is not allowed to be beginning of the key (${key})`);
    }
    if (val === '' && operator !== 'empty' && operator !== '!empty') {
      // do nothing
    } else if (lcKey === 'sort_by') {
      if (!_.isString(val) && val.length > 0) {
        throw new Error('sort_by value should contains string');
      }
      const parts = val.match('(.*),([-1]{1})?');
      if (parts) {
        qy.s = {};
        qy.s[parts[1]] = parts.length > 2 ? parseInt(parts[2], 10) : 1;
      } else {
        throw new Error('invalid sort_by value');
      }
    } else if (toBool(val) !== -1) {
      const b = toBool(val);
      if (b === false) {
        const orCond = {};
        orCond[lcKey] = { $exists: false };
        qy.q.$or = [];
        qy.q.$or.push(orCond);
        orCond[lcKey] = b;
        qy.q.$or.push(orCond);
      } else addCondition(lcKey, b);
    } else if (['gt', 'gte',
      'lt', 'lte',
      'in', 'nin', 'ne',
      'size'].indexOf(operator) !== -1) {
      const tmp = {};
      if (arrayOperators.indexOf(operator) !== -1) {
        // eslint-disable-next-line no-param-reassign
        val = val.split(',');
      }
      tmp[`$${operator}`] = val;
      addCondition(lcKey, tmp);
    } else if (operator === 'i') {
      // key={i}<value>
      addCondition(lcKey, new RegExp(`^${val}$`, 'i')); // http://scriptular.com/
    } else if (operator === 'e') {
      // key={e}<value>
      addCondition(lcKey, new RegExp(`${val}$`));
    } else if (operator === 'b') {
      addCondition(lcKey, new RegExp(`^${val}`));
    } else if (operator === 'm') {
      // key={m}<key>,<value>
      if (_.isString(val)) {
        const match = val.match(/(.*),(.*)/);
        if (match) {
          qy.q[key] = {};
          qy.q[key].$elemMatch = {};
          // eslint-disable-next-line prefer-destructuring
          qy.q[key].$elemMatch[match[1]] = match[2];
        }
      }
    } else if (operator === 'empty') {
      const empty = {};
      empty[lcKey] = '';
      const unexists = {};
      unexists[lcKey] = { $exists: false };
      addCondition('$or', empty);
      addCondition('$or', unexists);
    } else if (operator === '!empty') {
      const empty = {};
      empty[lcKey] = '';
      const unexists = {};
      unexists[lcKey] = { $exists: false };
      addCondition('$nor', empty);
      addCondition('$nor', unexists);
    } else if (operator === 'c') {
      const tmp = val.match(/(.*)\/(.*)/);
      if (tmp) {
        addCondition(lcKey, new RegExp(tmp[1], tmp[2]));
      } else {
        throw new Error('Invalid options for operator "c"');
      }
    } else {
      if ((options.ignoreKeys === true)
          || (Array.isArray(options.ignoreKeys)
          && (options.ignoreKeys.indexOf(key) !== -1))) {
        return;
      }
      if (_.isString(val)
          && /.*\|.*/.test(val)) {
        const parts = val.split('|');
        for (let i = 0; i < parts.length; i += 1) {
          const tmp = {};
          tmp[lcKey] = parts[i];
          addCondition('$or', tmp);
        }
      } else {
        addCondition(lcKey, val);
      }
    }
  }

  /**
   * Internal function to convert plain json
   * values which cannot represent in json api like:
   * RexExp, date and
   * @param value
   * @param key
   * @param obj
   */
  function jsonConverter(value, key, obj) {
    if (value !== null && typeof value === 'object') {
      // Recurse into children
      _.each(value, jsonConverter);
    } else if (typeof value === 'string') {
      // handle special cases (regex/number/date)
      if (key === '$regex') {
        const m = value.match(/\/(.*)\//);
        if (m) {
          if (obj.$options) {
            m[2] = obj.$options;
            // eslint-disable-next-line no-param-reassign
            delete obj.$options;
          }
          // eslint-disable-next-line no-param-reassign
          obj[key] = new RegExp(m[1], m[2]);
        }
      } else if (/^oid:[a-fA-F0-9]{24}$/.test(value)) {
        // eslint-disable-next-line no-param-reassign, prefer-destructuring
        obj[key] = value.split(':')[1];
      } else {
        // check if date to convert it
        const date = parseDateCustom(value);
        if (!Number.isNaN(date)) {
          // eslint-disable-next-line no-param-reassign
          obj[key] = date;
        }
      }
    }
  }

  // eslint-disable-next-line guard-for-in, no-restricted-syntax
  for (const key in query) {
    const value = decodeURIComponent(query[key]);
    const someOf = function someOf(...args) {
      return args.indexOf(key) !== -1;
    };
    if (key === 'q') {
      qy.q = JSON.parse(value);
      _.each(qy.q, jsonConverter);
    } else if (key === 't') {
      qy.t = value;
    } else if (someOf('f', 'select')) {
      qy.f = query[key];
    } else if (someOf('s', 'sort')) {
      qy.s = JSON.parse(value);
    } else if (someOf('sk', 'skip', 'skips')) {
      qy.sk = parseInt(value, 10);
    } else if (someOf('l', 'limit')) {
      qy.l = parseInt(value, 10);
    } else if (someOf('p')) {
      if (typeof value === 'string') {
        if (/^{.*}$/.test(value)
            || /^\[.*\]$/.test(value)) {
          // eslint-disable-next-line no-param-reassign
          query[key] = JSON.parse(value);
        } else if (value.indexOf(',') !== -1) {
          // eslint-disable-next-line no-param-reassign
          query[key] = value.split(',');
        }
      }
      qy.p = query[key];
    } else if (key === 'map') {
      qy.map = value;
    } else if (key === 'reduce') {
      qy.reduce = value;
    } else if (key === 'fl') {
      qy.fl = toBool(value);
    } else if (options.includeAllParams) {
      parseParam(key, value);
    }
  }
  return qy;
}

module.exports = parseQuery;
<commit_msg>allow query.q to be an object (#54)<commit_after>const _ = require('lodash');

// module tools
const tools = require('./tools');

const {
  toBool, toNumber, isObjectID, parseDateCustom
} = tools;

function parseQuery(query, options = {}) {
  /**

  reserved keys: q,t,f,s,sk,l,p

  [q=<query>][&t=<type>][&f=<fields>][&s=<order>][&sk=<skip>][&l=<limit>][&p=<populate>]
  q=<query> - restrict results by the specified JSON query
  t=<type> - find|findOne|count|aggregate|distinct..
  f=<set of fields> - specify the set of fields to include or exclude in
    each document (1 - include; 0 - exclude)
  s=<sort order> - specify the order in which to sort each specified
    field (1- ascending; -1 - descending)
  sk=<num results to skip> - specify the number of results to skip in
    the result set; useful for paging
  l=<limit> - specify the limit for the number of results (default is 1000)
  p=<populate> - specify the fields for populate

  alternative search conditions:
  "key={in}a,b"
  "At least one of these is in array"

  "key={nin}a,b"
  "Any of these values is not in array"

  "key={all}a,b"
  "All of these contains in array"

  "key={empty}-"
   "Field is empty or not exists"

  "key={!empty}-"
   "Field exists and is not empty"

  "key={mod}a,b"
  "Docs where key mod a is b"

  "key={gt}a"
  "Docs key is greater than a"

  "key={lt}a"
  "Docs key is lower than a"

  "key=a|b|c"
  "Docs where key type is Array, contains at least one of given value
  */
  _.defaults(options, { includeAllParams: true });

  const qy = {
    q: {}, //  query
    map: '',
    reduce: '',
    t: 'find', //  count
    f: false, // fields
    s: false, //  sort
    sk: false, //  skip
    l: 1000, //  limit
    p: false, // populate
    fl: false // flat
  };

  const arrayOperators = ['in', 'nin', 'all', 'mod'];

  function addCondition(key, val) {
    if (['$or', '$nor', '$and'].indexOf(key) !== -1) {
      if (!_.has(qy.q, key)) {
        qy.q[key] = [];
      }
      qy.q[key].push(val);
    } else {
      qy.q[key] = val;
    }
  }

  function parseParam(key, val) {
    const lcKey = key;
    let operator = false;
    if (typeof val === 'string') {
      operator = val.match(/\{(.*)\}/);
      if (operator) {
        // eslint-disable-next-line no-param-reassign
        val = val.replace(/\{(.*)\}/, '');
        [, operator] = operator;
      }

      if (/^\/.*\/\S*?$/.test(val)) {
        const matches = val.match(/\/(.*)\/(\S*)?$/);
        // eslint-disable-next-line no-param-reassign
        val = new RegExp(matches[1], matches[2]);
      }

      // there is problem when/if value is valid objectid but it
      // should be handled as a number, e.g. "123456789012345678901234"
      // anyway - collision propability is quite low.
      if (!isObjectID(val)) {
        const num = toNumber(val);
        if (!Number.isNaN(num)) {
          // eslint-disable-next-line no-param-reassign
          val = num;
        } else {
          const date = parseDateCustom(val);
          if (!Number.isNaN(date)
              && operator !== 'size'
              && arrayOperators.indexOf(operator) === -1
              && ['i', 'e', 'b', 'm'].indexOf(operator) === -1) {
            // eslint-disable-next-line no-param-reassign
            val = date;
          }
        }
      }
    }
    if (key.startsWith('$')) {
      // bypass $ characters for security reasons
      throw new Error(`$ is not allowed to be beginning of the key (${key})`);
    }
    if (val === '' && operator !== 'empty' && operator !== '!empty') {
      // do nothing
    } else if (lcKey === 'sort_by') {
      if (!_.isString(val) && val.length > 0) {
        throw new Error('sort_by value should contains string');
      }
      const parts = val.match('(.*),([-1]{1})?');
      if (parts) {
        qy.s = {};
        qy.s[parts[1]] = parts.length > 2 ? parseInt(parts[2], 10) : 1;
      } else {
        throw new Error('invalid sort_by value');
      }
    } else if (toBool(val) !== -1) {
      const b = toBool(val);
      if (b === false) {
        const orCond = {};
        orCond[lcKey] = { $exists: false };
        qy.q.$or = [];
        qy.q.$or.push(orCond);
        orCond[lcKey] = b;
        qy.q.$or.push(orCond);
      } else addCondition(lcKey, b);
    } else if (['gt', 'gte',
      'lt', 'lte',
      'in', 'nin', 'ne',
      'size'].indexOf(operator) !== -1) {
      const tmp = {};
      if (arrayOperators.indexOf(operator) !== -1) {
        // eslint-disable-next-line no-param-reassign
        val = val.split(',');
      }
      tmp[`$${operator}`] = val;
      addCondition(lcKey, tmp);
    } else if (operator === 'i') {
      // key={i}<value>
      addCondition(lcKey, new RegExp(`^${val}$`, 'i')); // http://scriptular.com/
    } else if (operator === 'e') {
      // key={e}<value>
      addCondition(lcKey, new RegExp(`${val}$`));
    } else if (operator === 'b') {
      addCondition(lcKey, new RegExp(`^${val}`));
    } else if (operator === 'm') {
      // key={m}<key>,<value>
      if (_.isString(val)) {
        const match = val.match(/(.*),(.*)/);
        if (match) {
          qy.q[key] = {};
          qy.q[key].$elemMatch = {};
          // eslint-disable-next-line prefer-destructuring
          qy.q[key].$elemMatch[match[1]] = match[2];
        }
      }
    } else if (operator === 'empty') {
      const empty = {};
      empty[lcKey] = '';
      const unexists = {};
      unexists[lcKey] = { $exists: false };
      addCondition('$or', empty);
      addCondition('$or', unexists);
    } else if (operator === '!empty') {
      const empty = {};
      empty[lcKey] = '';
      const unexists = {};
      unexists[lcKey] = { $exists: false };
      addCondition('$nor', empty);
      addCondition('$nor', unexists);
    } else if (operator === 'c') {
      const tmp = val.match(/(.*)\/(.*)/);
      if (tmp) {
        addCondition(lcKey, new RegExp(tmp[1], tmp[2]));
      } else {
        throw new Error('Invalid options for operator "c"');
      }
    } else {
      if ((options.ignoreKeys === true)
          || (Array.isArray(options.ignoreKeys)
          && (options.ignoreKeys.indexOf(key) !== -1))) {
        return;
      }
      if (_.isString(val)
          && /.*\|.*/.test(val)) {
        const parts = val.split('|');
        for (let i = 0; i < parts.length; i += 1) {
          const tmp = {};
          tmp[lcKey] = parts[i];
          addCondition('$or', tmp);
        }
      } else {
        addCondition(lcKey, val);
      }
    }
  }

  /**
   * Internal function to convert plain json
   * values which cannot represent in json api like:
   * RexExp, date and
   * @param value
   * @param key
   * @param obj
   */
  function jsonConverter(value, key, obj) {
    if (value !== null && typeof value === 'object') {
      // Recurse into children
      _.each(value, jsonConverter);
    } else if (typeof value === 'string') {
      // handle special cases (regex/number/date)
      if (key === '$regex') {
        const m = value.match(/\/(.*)\//);
        if (m) {
          if (obj.$options) {
            m[2] = obj.$options;
            // eslint-disable-next-line no-param-reassign
            delete obj.$options;
          }
          // eslint-disable-next-line no-param-reassign
          obj[key] = new RegExp(m[1], m[2]);
        }
      } else if (/^oid:[a-fA-F0-9]{24}$/.test(value)) {
        // eslint-disable-next-line no-param-reassign, prefer-destructuring
        obj[key] = value.split(':')[1];
      } else {
        // check if date to convert it
        const date = parseDateCustom(value);
        if (!Number.isNaN(date)) {
          // eslint-disable-next-line no-param-reassign
          obj[key] = date;
        }
      }
    }
  }

  // eslint-disable-next-line guard-for-in, no-restricted-syntax
  for (const key in query) {
    const value = decodeURIComponent(query[key]);
    const someOf = function someOf(...args) {
      return args.indexOf(key) !== -1;
    };
    if (key === 'q') {
      qy.q = _.isString(value) ? JSON.parse(value) : value;
      _.each(qy.q, jsonConverter);
    } else if (key === 't') {
      qy.t = value;
    } else if (someOf('f', 'select')) {
      qy.f = query[key];
    } else if (someOf('s', 'sort')) {
      qy.s = JSON.parse(value);
    } else if (someOf('sk', 'skip', 'skips')) {
      qy.sk = parseInt(value, 10);
    } else if (someOf('l', 'limit')) {
      qy.l = parseInt(value, 10);
    } else if (someOf('p')) {
      if (typeof value === 'string') {
        if (/^{.*}$/.test(value)
            || /^\[.*\]$/.test(value)) {
          // eslint-disable-next-line no-param-reassign
          query[key] = JSON.parse(value);
        } else if (value.indexOf(',') !== -1) {
          // eslint-disable-next-line no-param-reassign
          query[key] = value.split(',');
        }
      }
      qy.p = query[key];
    } else if (key === 'map') {
      qy.map = value;
    } else if (key === 'reduce') {
      qy.reduce = value;
    } else if (key === 'fl') {
      qy.fl = toBool(value);
    } else if (options.includeAllParams) {
      parseParam(key, value);
    }
  }
  return qy;
}

module.exports = parseQuery;
<|endoftext|>


<commit_before>(function (stylecow) {
	stylecow.prototypes = {};

	/**
	 * Collection class, to filter and traverse into the css tree
	 */
	var Collection = function () {};

	Collection.prototype = Object.create(Array.prototype, {
		getChildren: {
			value: function (match) {
				var result = new Collection();

				for (var i = 0, t = this.length; i < t; ++i) {
					if (this[i].is(match)) {
						result.push(this[i]);
					}
				};

				return result;
			}
		},

		getChild: {
			value: function (match) {
				for (var i = 0, t = this.length; i < t; ++i) {
					if (this[i].is(match)) {
						return this[i];
					}
				};
			}
		},

		hasChild: {
			value: function (match) {
				for (var i = 0, t = this.length; i < t; i++) {
					if (this[i].is(match)) {
						return true;
					}
				}

				return false;
			}
		},

		get: {
			value: function (match) {
				for (var i = 0, t = this.length; i < t; i++) {
					if (this[i].is(match)) {
						return this[i];
					}

					if (this[i] instanceof Collection) {
						var found = this[i].get(match);

						if (found) {
							return found;
						}
					}
				}
			}
		},

		getAll: {
			value: function (match, result) {
				result = result || new Collection();

				for (var i = 0, t = this.length; i < t; i++) {
					if (this[i].is(match)) {
						result.push(this[i]);
					}

					if (this[i] instanceof Collection) {
						this[i].getAll(match, result);
					}
				}

				return result;
			}
		},

		has: {
			value: function (match) {
				for (var i = 0, t = this.length; i < t; i++) {
					if (this[i].is(match) || (this[i] instanceof Collection && this[i].has(match))) {
						return true;
					}
				}

				return false;
			}
		},

		toArray: {
			value: function () {
				return this.map(function (child) {
					return child.toString();
				});
			}
		}
	});

	/**
	 * Base method inherited by all css nodes
	 */
	var BasicPrototype = {
		cloneBefore: function () {
			var clone = this.clone();
			this.before(clone);
			return clone;
		},

		cloneAfter: function () {
			var clone = this.clone();
			this.after(clone);
			return clone;
		},

		is: function (match) {
			if (!match) {
				return true;
			}

			if (typeof match === 'string') {
				return equals(this.type, match);
			}

			if (match instanceof Function) {
				return match.call(this);
			}

			for (var k in match) {
				if (k === 'string') {
					if (!equals(this.toString(), match.string)) {
						return false;
					}
				} else if ((this[k] !== undefined) && !equals(this[k], match[k])) {
					return false;
				}
			}

			return true;
		},

		getParent: function (match) {
			if (this.parent) {
				if (!match || this.parent.is(match)) {
					return this.parent;
				}

				return this.parent.getParent(match);
			}
		},

		setSource: function (token) {
			this.data.line = token.currToken[1];
			this.data.col = token.currToken[2];
			this.data.file = token.file;

			return this;
		},

		getData: function (key) {
			if (key in this.data) {
				return this.data[key];
			}

			if (this.parent) {
				return this.parent.getData(key);
			}
		},

		getAllData: function (key, result) {
			result = result || [];

			if (key in this.data) {
				result.push(this.data[key]);
			}

			if (this.parent) {
				this.parent.getAllData(key, result);
			}

			return result;
		},

		setData: function (key, value) {
			this.data[key] = value;

			return this;
		},

		index: function () {
			if (this.parent) {
				return this.parent.indexOf(this);
			}

			return -1;
		},

		next: function () {
			var index = this.index();

			if (index !== -1) {
				return this.parent[index + 1];
			}
		},

		prev: function () {
			var index = this.index();

			if (index > 0) {
				return this.parent[index - 1];
			}
		},

		before: function (child) {
			var index = this.index();

			if (index !== -1) {
				return this.parent.splice(index, 0, child);
			}
		},

		after: function (child) {
			var index = this.index();

			if (index !== -1) {
				if (index === this.parent.length) {
					return this.parent.push(child);
				}

				return this.parent.splice(index + 1, 0, child);
			}
		},

		replaceWith: function (child) {
			var index = this.index();

			if (index !== -1) {
				var parent = this.parent;
				this.remove();

				return parent.splice(index, 0, child);
			}
		},

		remove: function () {
			return this.detach();
		},

		detach: function () {
			if (this.parent) {
				var index = this.index();

				if (index !== -1) {
					this.parent.splice(index, 1);
					this.parent = null;
				}
			}

			return this;
		},

		toAst: function () {
			var object = {
				class: this.class
			};

			if ('name' in this) {
				object.name = (this.name === undefined) ? null : this.name;
			}

			if ('vendor' in this) {
				object.vendor = (this.vendor === undefined) ? null : this.vendor;
			}

			if (this instanceof Array) {
				object.children = [];

				this.forEach(function (child) {
					object.children.push(child.toAst());
				});
			}

			return object;
		},

		getContextVendor: function () {
			if (('vendor' in this) && this.vendor) {
				return this.vendor;
			}

			if (this instanceof Collection) {
				var vendor;

				for (var i = 0, t = this.length; i < t; i++) {
					if (this[i].type !== 'Block' && this[i].type !== 'Comment') {
						vendor = this[i].getContextVendor();

						if (vendor) {
							return vendor;
						}
					}
				}
			}
		},

		normalizeVendors: function (vendor) {
			if (vendor === undefined) {
				vendor = this.getContextVendor() || null;
			}

			var k = 0;

			while (this[k]) {
				var child = this[k];

				if (child.type === 'Block') {
					child.normalizeVendors(vendor);
				} else if (child.type !== 'Comment') {
					var contextVendor = child.getContextVendor();

					if (contextVendor && vendor && (contextVendor !== vendor)) {
						child.detach();
						continue;
					}

					child.normalizeVendors(contextVendor || vendor);
				}

				++k;
			}

			return this;
		}
	};

	/**
	 * Node prototype used by all basic elements
	 */
	stylecow.prototypes.Node = mix({}, BasicPrototype, {
		clone: function (data) {
			var clone = new stylecow[this.class]();

			if (data !== true) {
				clone.data = this.data;
			}

			return clone;
		}
	});

	/**
	 * Node prototype used by all elements with a name
	 */
	stylecow.prototypes.NodeWithName = mix({}, BasicPrototype, {
		clone: function (data) {
			var clone = new stylecow[this.class]();

			clone.name = this.name;

			if (data !== true) {
				clone.data = this.data;
			}

			return clone;
		},

		setName: function (name) {
			this.name = name;

			return this;
		},

		toString: function () {
			return this.name;
		}
	});

	/**
	 * Node prototype used by all elements with a name and vendor prefix
	 */
	stylecow.prototypes.NodeWithNameAndVendor = mix({}, BasicPrototype, {
		setNameWithVendor: function (name) {
			if (name[0] === '-') {
				var match = name.match(/^(-(\w+)-)?(.+)$/);
				this.vendor = match[2] || null;
				this.name = match[3];
			} else {
				this.vendor = null;
				this.name = name;
			}

			return this;
		},

		getNameWithVendor: function (name) {
			if (this.vendor) {
				return '-' + this.vendor + '-' + this.name;
			}

			return this.name;
		},

		setName: function (name) {
			this.name = name;

			return this;
		},

		setVendor: function (vendor) {
			this.vendor = vendor;

			return this;
		},

		clone: function (data) {
			var clone = new stylecow[this.class]();

			clone.name = this.name;
			clone.vendor = this.vendor;

			if (data !== true) {
				clone.data = this.data;
			}

			return clone;
		},

		toString: function () {
			return this.getNameWithVendor();
		}
	});

	/**
	 * Node prototype used by all nodes with children
	 */
	stylecow.prototypes.NodeCollection = Object.create(Collection.prototype);
	mix(stylecow.prototypes.NodeCollection, BasicPrototype, {
		empty: function () {
			this.splice(0);

			return this;
		},

		push: function (child) {
			if (child.parent) {
				child.detach();
			}

			child.parent = this;

			return Array.prototype.push.call(this, child);
		},

		add: function (child) {
			this.push(child);

			return this;
		},

		unshift: function (child) {
			if (child.parent) {
				child.detach();
			}

			child.parent = this;

			return Array.prototype.unshift.call(this, child);
		},

		splice: function (start, deleteCount, child) {
			if (!child) {
				return Array.prototype.splice.call(this, start, deleteCount);
			}

			if (child.parent) {
				child.detach();
			}

			child.parent = this;

			return Array.prototype.splice.call(this, start, deleteCount, child);
		},

		remove: function () {
			this.detach();

			for (var i = 0, t = this.length; i < t; ++i) {
				this[i].parent = null;
				this[i].remove();
			};

			this.splice(0);

			return this;
		},

		clone: function (data) {
			var clone = new stylecow[this.class]();

			if (data !== true) {
				clone.data = this.data;
			}

			for (var i = 0, t = this.length; i < t; ++i) {
				clone.push(this[i].clone(data));
			};

			return clone;
		}
	});

	/**
	 * Node prototype used by all nodes with children and a name
	 */
	stylecow.prototypes.NodeCollectionWithName = Object.create(stylecow.prototypes.NodeCollection);
	mix(stylecow.prototypes.NodeCollectionWithName, BasicPrototype, {
		setName: stylecow.prototypes.NodeWithNameAndVendor.setName,

		clone: function (data) {
			var clone = new stylecow[this.class]();

			if (data !== true) {
				clone.data = this.data;
			}

			clone.name = this.name;

			for (var i = 0, t = this.length; i < t; ++i) {
				clone.push(this[i].clone(data));
			};

			return clone;
		}
	});

	/**
	 * Node prototype used by all nodes with children, a name and a vendor prefix
	 */
	stylecow.prototypes.NodeCollectionWithNameAndVendor = Object.create(stylecow.prototypes.NodeCollection);
	mix(stylecow.prototypes.NodeCollectionWithNameAndVendor, BasicPrototype, {
		setNameWithVendor: stylecow.prototypes.NodeWithNameAndVendor.setNameWithVendor,
		getNameWithVendor: stylecow.prototypes.NodeWithNameAndVendor.getNameWithVendor,
		setName: stylecow.prototypes.NodeWithNameAndVendor.setName,
		setVendor: stylecow.prototypes.NodeWithNameAndVendor.setVendor,

		clone: function (data) {
			var clone = new stylecow[this.class]();

			if (data !== true) {
				clone.data = this.data;
			}

			clone.name = this.name;
			clone.vendor = this.vendor;

			for (var i = 0, t = this.length; i < t; ++i) {
				clone.push(this[i].clone(data));
			};

			return clone;
		}
	});

	function mix (target) {
		Array.prototype.slice.call(arguments, 1).forEach(function (object) {
			Object.keys(object).forEach(function (i) {
				target[i] = object[i];
			});
		});

		return target;
	}

	function equals (value, needle) {
		if (needle === true) {
			return value ? true : false;
		}

		if (needle === false) {
			return value ? false : true;
		}

		if (typeof needle === 'string') {
			return (needle === value);
		}

		if (needle instanceof Array) {
			return (needle.indexOf(value) !== -1);
		}

		if (needle instanceof RegExp) {
			return needle.test(value);
		}

		return true;
	}

})(require('./index'));
<commit_msg>added getSibling, getSiblings, hasSibling methods<commit_after>(function (stylecow) {
	stylecow.prototypes = {};

	/**
	 * Collection class, to filter and traverse into the css tree
	 */
	var Collection = function () {};

	Collection.prototype = Object.create(Array.prototype, {
		getChildren: {
			value: function (match) {
				var result = new Collection();

				for (var i = 0, t = this.length; i < t; ++i) {
					if (this[i].is(match)) {
						result.push(this[i]);
					}
				};

				return result;
			}
		},

		getChild: {
			value: function (match) {
				for (var i = 0, t = this.length; i < t; ++i) {
					if (this[i].is(match)) {
						return this[i];
					}
				};
			}
		},

		hasChild: {
			value: function (match) {
				for (var i = 0, t = this.length; i < t; i++) {
					if (this[i].is(match)) {
						return true;
					}
				}

				return false;
			}
		},

		get: {
			value: function (match) {
				for (var i = 0, t = this.length; i < t; i++) {
					if (this[i].is(match)) {
						return this[i];
					}

					if (this[i] instanceof Collection) {
						var found = this[i].get(match);

						if (found) {
							return found;
						}
					}
				}
			}
		},

		getAll: {
			value: function (match, result) {
				result = result || new Collection();

				for (var i = 0, t = this.length; i < t; i++) {
					if (this[i].is(match)) {
						result.push(this[i]);
					}

					if (this[i] instanceof Collection) {
						this[i].getAll(match, result);
					}
				}

				return result;
			}
		},

		has: {
			value: function (match) {
				for (var i = 0, t = this.length; i < t; i++) {
					if (this[i].is(match) || (this[i] instanceof Collection && this[i].has(match))) {
						return true;
					}
				}

				return false;
			}
		},

		toArray: {
			value: function () {
				return this.map(function (child) {
					return child.toString();
				});
			}
		}
	});

	/**
	 * Base method inherited by all css nodes
	 */
	var BasicPrototype = {
		cloneBefore: function () {
			var clone = this.clone();
			this.before(clone);
			return clone;
		},

		cloneAfter: function () {
			var clone = this.clone();
			this.after(clone);
			return clone;
		},

		is: function (match) {
			if (!match) {
				return true;
			}

			if (typeof match === 'string') {
				return equals(this.type, match);
			}

			if (match instanceof Function) {
				return match.call(this);
			}

			for (var k in match) {
				if (k === 'string') {
					if (!equals(this.toString(), match.string)) {
						return false;
					}
				} else if ((this[k] !== undefined) && !equals(this[k], match[k])) {
					return false;
				}
			}

			return true;
		},

		getParent: function (match) {
			if (this.parent) {
				if (!match || this.parent.is(match)) {
					return this.parent;
				}

				return this.parent.getParent(match);
			}
		},

		getSiblings: function (match) {
			var parent = this.parent;
			var result = new Collection();

			if (!parent) {
				return result;
			}

			for (var i = 0, t = parent.length; i < t; ++i) {
				if (parent[i] !== this && parent[i].is(match)) {
					result.push(this[i]);
				}
			};

			return result;
		},

		hasSibling: function (match) {
			var parent = this.parent;

			if (!parent) {
				return false;
			}

			for (var i = 0, t = parent.length; i < t; ++i) {
				if (parent[i] !== this && parent[i].is(match)) {
					return true;
				}
			}

			return false;
		},

		getSibling: function (match) {
			var parent = this.parent;

			if (parent) {
				for (var i = 0, t = parent.length; i < t; ++i) {
					if (parent[i] !== this && parent[i].is(match)) {
						return parent[i];
					}
				}
			}
		},

		setSource: function (token) {
			this.data.line = token.currToken[1];
			this.data.col = token.currToken[2];
			this.data.file = token.file;

			return this;
		},

		getData: function (key) {
			if (key in this.data) {
				return this.data[key];
			}

			if (this.parent) {
				return this.parent.getData(key);
			}
		},

		getAllData: function (key, result) {
			result = result || [];

			if (key in this.data) {
				result.push(this.data[key]);
			}

			if (this.parent) {
				this.parent.getAllData(key, result);
			}

			return result;
		},

		setData: function (key, value) {
			this.data[key] = value;

			return this;
		},

		index: function () {
			if (this.parent) {
				return this.parent.indexOf(this);
			}

			return -1;
		},

		next: function () {
			var index = this.index();

			if (index !== -1) {
				return this.parent[index + 1];
			}
		},

		prev: function () {
			var index = this.index();

			if (index > 0) {
				return this.parent[index - 1];
			}
		},

		before: function (child) {
			var index = this.index();

			if (index !== -1) {
				return this.parent.splice(index, 0, child);
			}
		},

		after: function (child) {
			var index = this.index();

			if (index !== -1) {
				if (index === this.parent.length) {
					return this.parent.push(child);
				}

				return this.parent.splice(index + 1, 0, child);
			}
		},

		replaceWith: function (child) {
			var index = this.index();

			if (index !== -1) {
				var parent = this.parent;
				this.remove();

				return parent.splice(index, 0, child);
			}
		},

		remove: function () {
			return this.detach();
		},

		detach: function () {
			if (this.parent) {
				var index = this.index();

				if (index !== -1) {
					this.parent.splice(index, 1);
					this.parent = null;
				}
			}

			return this;
		},

		toAst: function () {
			var object = {
				class: this.class
			};

			if ('name' in this) {
				object.name = (this.name === undefined) ? null : this.name;
			}

			if ('vendor' in this) {
				object.vendor = (this.vendor === undefined) ? null : this.vendor;
			}

			if (this instanceof Array) {
				object.children = [];

				this.forEach(function (child) {
					object.children.push(child.toAst());
				});
			}

			return object;
		},

		getContextVendor: function () {
			if (('vendor' in this) && this.vendor) {
				return this.vendor;
			}

			if (this instanceof Collection) {
				var vendor;

				for (var i = 0, t = this.length; i < t; i++) {
					if (this[i].type !== 'Block' && this[i].type !== 'Comment') {
						vendor = this[i].getContextVendor();

						if (vendor) {
							return vendor;
						}
					}
				}
			}
		},

		normalizeVendors: function (vendor) {
			if (vendor === undefined) {
				vendor = this.getContextVendor() || null;
			}

			var k = 0;

			while (this[k]) {
				var child = this[k];

				if (child.type === 'Block') {
					child.normalizeVendors(vendor);
				} else if (child.type !== 'Comment') {
					var contextVendor = child.getContextVendor();

					if (contextVendor && vendor && (contextVendor !== vendor)) {
						child.detach();
						continue;
					}

					child.normalizeVendors(contextVendor || vendor);
				}

				++k;
			}

			return this;
		}
	};

	/**
	 * Node prototype used by all basic elements
	 */
	stylecow.prototypes.Node = mix({}, BasicPrototype, {
		clone: function (data) {
			var clone = new stylecow[this.class]();

			if (data !== true) {
				clone.data = this.data;
			}

			return clone;
		}
	});

	/**
	 * Node prototype used by all elements with a name
	 */
	stylecow.prototypes.NodeWithName = mix({}, BasicPrototype, {
		clone: function (data) {
			var clone = new stylecow[this.class]();

			clone.name = this.name;

			if (data !== true) {
				clone.data = this.data;
			}

			return clone;
		},

		setName: function (name) {
			this.name = name;

			return this;
		},

		toString: function () {
			return this.name;
		}
	});

	/**
	 * Node prototype used by all elements with a name and vendor prefix
	 */
	stylecow.prototypes.NodeWithNameAndVendor = mix({}, BasicPrototype, {
		setNameWithVendor: function (name) {
			if (name[0] === '-') {
				var match = name.match(/^(-(\w+)-)?(.+)$/);
				this.vendor = match[2] || null;
				this.name = match[3];
			} else {
				this.vendor = null;
				this.name = name;
			}

			return this;
		},

		getNameWithVendor: function (name) {
			if (this.vendor) {
				return '-' + this.vendor + '-' + this.name;
			}

			return this.name;
		},

		setName: function (name) {
			this.name = name;

			return this;
		},

		setVendor: function (vendor) {
			this.vendor = vendor;

			return this;
		},

		clone: function (data) {
			var clone = new stylecow[this.class]();

			clone.name = this.name;
			clone.vendor = this.vendor;

			if (data !== true) {
				clone.data = this.data;
			}

			return clone;
		},

		toString: function () {
			return this.getNameWithVendor();
		}
	});

	/**
	 * Node prototype used by all nodes with children
	 */
	stylecow.prototypes.NodeCollection = Object.create(Collection.prototype);
	mix(stylecow.prototypes.NodeCollection, BasicPrototype, {
		empty: function () {
			this.splice(0);

			return this;
		},

		push: function (child) {
			if (child.parent) {
				child.detach();
			}

			child.parent = this;

			return Array.prototype.push.call(this, child);
		},

		add: function (child) {
			this.push(child);

			return this;
		},

		unshift: function (child) {
			if (child.parent) {
				child.detach();
			}

			child.parent = this;

			return Array.prototype.unshift.call(this, child);
		},

		splice: function (start, deleteCount, child) {
			if (!child) {
				return Array.prototype.splice.call(this, start, deleteCount);
			}

			if (child.parent) {
				child.detach();
			}

			child.parent = this;

			return Array.prototype.splice.call(this, start, deleteCount, child);
		},

		remove: function () {
			this.detach();

			for (var i = 0, t = this.length; i < t; ++i) {
				this[i].parent = null;
				this[i].remove();
			};

			this.splice(0);

			return this;
		},

		clone: function (data) {
			var clone = new stylecow[this.class]();

			if (data !== true) {
				clone.data = this.data;
			}

			for (var i = 0, t = this.length; i < t; ++i) {
				clone.push(this[i].clone(data));
			};

			return clone;
		}
	});

	/**
	 * Node prototype used by all nodes with children and a name
	 */
	stylecow.prototypes.NodeCollectionWithName = Object.create(stylecow.prototypes.NodeCollection);
	mix(stylecow.prototypes.NodeCollectionWithName, BasicPrototype, {
		setName: stylecow.prototypes.NodeWithNameAndVendor.setName,

		clone: function (data) {
			var clone = new stylecow[this.class]();

			if (data !== true) {
				clone.data = this.data;
			}

			clone.name = this.name;

			for (var i = 0, t = this.length; i < t; ++i) {
				clone.push(this[i].clone(data));
			};

			return clone;
		}
	});

	/**
	 * Node prototype used by all nodes with children, a name and a vendor prefix
	 */
	stylecow.prototypes.NodeCollectionWithNameAndVendor = Object.create(stylecow.prototypes.NodeCollection);
	mix(stylecow.prototypes.NodeCollectionWithNameAndVendor, BasicPrototype, {
		setNameWithVendor: stylecow.prototypes.NodeWithNameAndVendor.setNameWithVendor,
		getNameWithVendor: stylecow.prototypes.NodeWithNameAndVendor.getNameWithVendor,
		setName: stylecow.prototypes.NodeWithNameAndVendor.setName,
		setVendor: stylecow.prototypes.NodeWithNameAndVendor.setVendor,

		clone: function (data) {
			var clone = new stylecow[this.class]();

			if (data !== true) {
				clone.data = this.data;
			}

			clone.name = this.name;
			clone.vendor = this.vendor;

			for (var i = 0, t = this.length; i < t; ++i) {
				clone.push(this[i].clone(data));
			};

			return clone;
		}
	});

	function mix (target) {
		Array.prototype.slice.call(arguments, 1).forEach(function (object) {
			Object.keys(object).forEach(function (i) {
				target[i] = object[i];
			});
		});

		return target;
	}

	function equals (value, needle) {
		if (needle === true) {
			return value ? true : false;
		}

		if (needle === false) {
			return value ? false : true;
		}

		if (typeof needle === 'string') {
			return (needle === value);
		}

		if (needle instanceof Array) {
			return (needle.indexOf(value) !== -1);
		}

		if (needle instanceof RegExp) {
			return needle.test(value);
		}

		return true;
	}

})(require('./index'));
<|endoftext|>