<commit_before>/*
 *  MQTTTransport.js
 *
 *  David Janes
 *  IOTDB.org
 *  2015-03-27
 *
 *  Copyright [2013-2015] [David P. Janes]
 *
 *  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.
 */

"use strict";

var iotdb = require('iotdb');
var iotdb_transport = require('iotdb-transport');
var errors = iotdb_transport.errors;
var _ = iotdb._;

var url_join = require('url-join');
var path = require('path');
var mqtt = require('mqtt');
var fs = require('fs');

var util = require('util');
var url = require('url');

var logger = iotdb.logger({
    name: 'iotdb-transport-mqtt',
    module: 'MQTTTransport',
});

/* --- forward definitions --- */
var _encode;
var _decode;
var _unpack;
var _pack;

/* --- constructor --- */

/**
 *  See {iotdb_transport.Transport#Transport} for documentation.
 *
 *  @param {dictionary} initd
 *
 *  @param {string} initd.prefix
 *  MQTT topic prefix
 *
 *  @param {string} initd.host
 *  MQTT server host
 *
 *  @param {integer} initd.port
 *  MQTT server port, by default 1883
 *
 *  @param {boolean} initd.retain
 *  Make messages retained, by default false
 *
 *  @param {boolean} initd.qos
 *  MQTT QOS. Either 0, 1 or 2. By default 0.
 *
 *  @param {boolean|array} initd.add_timestamp
 *  Add a @timestamp to outgoing records.
 *  If an array, the band must be in the array.
 *
 *  @param {boolean} initd.allow_updated
 *  If True, allow MQTT to update data (e.g. by receiving
 *  it over MQTT). Default false
 *
 *  @param {function} initd.channel
 *  @param {function} initd.unchannel
 *
 *  @param {MQTTClient|undefined} native
 *  If defined, this will be used for the MQTT client / connection.
 *  Otherwise we will make our own.
 */
var MQTTTransport = function (initd, native) {
    var self = this;

    self.initd = _.defaults(
        initd, {
            channel: iotdb_transport.channel,
            unchannel: iotdb_transport.unchannel,
            encode: _encode,
            decode: _decode,
            pack: _pack,
            unpack: _unpack,
            allow_updated: false,
            client_id: "tr-mqtt-" + _.uid(10),
            user: null,
        },
        iotdb.keystore().get("/transports/MQTTTransport/initd"), {
            prefix: "",
            host: "",
            retain: false,
            qos: 0,
            add_timestamp: false,

            // secure conections
            protocol: null,
            port: null,
            ca: null,
            cert: null,
            key: null,
        }
    );

    if (native) {
        self.native = native;
    } else {
        /*
        self.native = mqtt.createClient(self.initd.port, self.initd.host, {
            clientId: self.initd.client_id,
        });
        */

        if (!self.initd.host) {
            throw new Error("MQTTTransport: expected initd.host");
        }

        var connectd = {
            clientId: self.initd.client_id,
        };

        if (self.initd.key) {
            connectd.key = fs.readFileSync(self.initd.key);
        }

        if (self.initd.cert) {
            connectd.cert = fs.readFileSync(self.initd.cert);
        }

        if (self.initd.ca) {
            connectd.ca = fs.readFileSync(self.initd.ca);
        }

        if (!self.initd.protocol) {
            if (connectd.key && connectd.cert) {
                self.initd.protocol = 'mqtts';
            } else {
                self.initd.protocol = 'mqtt';
            }
        }

        if (!self.initd.port) {
            if (self.initd.protocol === 'mqtts') {
                self.initd.port = 8883;
            } else {
                self.initd.port = 1883;
            }
        }

        var url = util.format("%s://%s:%s", self.initd.protocol || "mqtt", self.initd.host, self.initd.port);

        self.native = mqtt.connect(url, connectd);
        self.native.on('connect', function () {
            logger.info({
                method: "publish/on(connect)",
                url: url,
            }, "connected");

            console.log("===============================");
            console.log("=== MQTT Server Up");
            console.log("=== ");
            console.log("=== Connect at:");
            console.log("=== " + url);
            console.log("===============================");
        });
    }

    self.native.on('error', function () {
        logger.error({
            method: "publish/on(error)",
            arguments: arguments,
            cause: "likely MQTT issue - will automatically reconnect soon",
        }, "unexpected error");
    });
    self.native.on('close', function () {
        logger.error({
            method: "publish/on(close)",
            arguments: arguments,
            cause: "likely MQTT issue - will automatically reconnect soon",
        }, "unexpected close");
    });

    self._subscribed = false;
};

MQTTTransport.prototype = new iotdb_transport.Transport();
MQTTTransport.prototype._class = "MQTTTransport";

/* --- methods --- */

/**
 *  See {iotdb_transport.Transport#list} for documentation.
 *

 *  MQTT: this does nothing, as we don't have
 *  a concept of a databse.
 */
MQTTTransport.prototype.list = function (paramd, callback) {
    var self = this;

    self._validate_list(paramd, callback);

    callback({
        end: true,
        error: new Error("N/A"),
    });
};

/**
 *  See {iotdb_transport.Transport#added} for documentation.
 *

 *  NOT FINISHED
 */
MQTTTransport.prototype.added = function (paramd, callback) {
    var self = this;

    self._validate_added(paramd, callback);
};

/**
 *  See {iotdb_transport.Transport#bands} for documentation.
 *

 *  MQTT: this does nothing, as we don't have
 *  a concept of a databse
 */
MQTTTransport.prototype.bands = function (paramd, callback) {
    var self = this;

    self._validate_bands(paramd, callback);

    // don't know (and never will)
    callback({
        id: paramd.id,
        band: paramd.band,
        value: null,
        error: new errors.NotImplemented(),
    });
};

/**
 *  See {iotdb_transport.Transport#get} for documentation.
 *

 *  MQTT: this does nothing, as we don't have
 *  a concept of a databse
 */
MQTTTransport.prototype.get = function (paramd, callback) {
    var self = this;

    self._validate_get(paramd, callback);

    var gd = _.shallowCopy(paramd);
    gd.value = null;

    callback(new errors.NotImplemented(), gd);
};

/**
 *  See {iotdb_transport.Transport#update} for documentation.
 */
MQTTTransport.prototype.put = function (paramd, callback) {
    var self = this;

    self._validate_update(paramd, callback);

    paramd = _.shallowCopy(paramd);

    var value = paramd.value;
    var timestamp = value["@timestamp"];
    if (!timestamp && _.is.Boolean(self.initd.add_timestamp)) {
        value = _.shallowCopy(value);
        value["@timestamp"] = _.timestamp.make();
    } else if (!timestamp && _.is.Array(self.initd.add_timestamp) && (self.init.add_timestamp.indexOf(paramd.band) > -1)) {
        value = _.shallowCopy(value);
        value["@timestamp"] = _.timestamp.make();
    }

    var channel = self.initd.channel(self.initd, paramd.id, paramd.band);
    var d = self.initd.pack(value, paramd.id, paramd.band);

    self.native.publish(channel, d, {
        retain: self.initd.retain,
        qos: self.initd.qos,
    }, function() {
        callback(paramd);
    });

};

/**
 *  See {iotdb_transport.Transport#updated} for documentation.
 */
MQTTTransport.prototype.updated = function (paramd, callback) {
    var self = this;

    self._validate_updated(paramd, callback);

    if (!self.initd.allow_updated) {
        return;
    }

    if (!self._subscribed) {
        var channel = url_join(self.initd.prefix, "#");
        self.native.subscribe(channel, function (error) {
            /* maybe reset _subscribed on mqtt.open? */
            logger.error({
                method: "publish/on(close)",
                arguments: arguments,
                cause: "likely MQTT issue - this is probably very bad",
            }, "unexpected error subscribing");
        });
    }

    self.native.on("message", function (topic, message, packet) {
        var parts = self.initd.unchannel(self.initd, topic);
        if (!parts) {
            return;
        }

        var topic_id = parts[0];
        var topic_band = parts[1];

        if (paramd.id && (topic_id !== paramd.id)) {
            return;
        }
        if (paramd.band && (topic_band !== paramd.band)) {
            return;
        }

        var d = self.initd.unpack(message, topic_id, topic_band);
        callback({
            id: topic_id,
            band: topic_band,
            value: d,
        });
    });
};

/**
 *  See {iotdb_transport.Transport#remove} for documentation.
 *

 *  MQTT - do nothing
 */
MQTTTransport.prototype.remove = function (paramd, callback) {
    var self = this;

    self._validate_remove(paramd, callback);
};

/* -- internals -- */
var _encode = function (s) {
    return s.replace(/[\/#+]/g, function (c) {
        return '%' + c.charCodeAt(0).toString(16);
    });
};

var _decode = function (s) {
    return decodeURIComponent(s);
};

var _unpack = function (d) {
    if (d.toString) {
        d = d.toString();
    }

    return JSON.parse(d);
};

var _pack = function (d) {
    return JSON.stringify(d);
};

/**
 *  API
 */
exports.MQTTTransport = MQTTTransport;
<commit_msg>put() modernized<commit_after>/*
 *  MQTTTransport.js
 *
 *  David Janes
 *  IOTDB.org
 *  2015-03-27
 *
 *  Copyright [2013-2015] [David P. Janes]
 *
 *  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.
 */

"use strict";

var iotdb = require('iotdb');
var iotdb_transport = require('iotdb-transport');
var errors = iotdb_transport.errors;
var _ = iotdb._;

var url_join = require('url-join');
var path = require('path');
var mqtt = require('mqtt');
var fs = require('fs');

var util = require('util');
var url = require('url');

var logger = iotdb.logger({
    name: 'iotdb-transport-mqtt',
    module: 'MQTTTransport',
});

/* --- forward definitions --- */
var _encode;
var _decode;
var _unpack;
var _pack;

/* --- constructor --- */

/**
 *  See {iotdb_transport.Transport#Transport} for documentation.
 *
 *  @param {dictionary} initd
 *
 *  @param {string} initd.prefix
 *  MQTT topic prefix
 *
 *  @param {string} initd.host
 *  MQTT server host
 *
 *  @param {integer} initd.port
 *  MQTT server port, by default 1883
 *
 *  @param {boolean} initd.retain
 *  Make messages retained, by default false
 *
 *  @param {boolean} initd.qos
 *  MQTT QOS. Either 0, 1 or 2. By default 0.
 *
 *  @param {boolean|array} initd.add_timestamp
 *  Add a @timestamp to outgoing records.
 *  If an array, the band must be in the array.
 *
 *  @param {boolean} initd.allow_updated
 *  If True, allow MQTT to update data (e.g. by receiving
 *  it over MQTT). Default false
 *
 *  @param {function} initd.channel
 *  @param {function} initd.unchannel
 *
 *  @param {MQTTClient|undefined} native
 *  If defined, this will be used for the MQTT client / connection.
 *  Otherwise we will make our own.
 */
var MQTTTransport = function (initd, native) {
    var self = this;

    self.initd = _.defaults(
        initd, {
            channel: iotdb_transport.channel,
            unchannel: iotdb_transport.unchannel,
            encode: _encode,
            decode: _decode,
            pack: _pack,
            unpack: _unpack,
            allow_updated: false,
            client_id: "tr-mqtt-" + _.uid(10),
            user: null,
        },
        iotdb.keystore().get("/transports/MQTTTransport/initd"), {
            prefix: "",
            host: "",
            retain: false,
            qos: 0,
            add_timestamp: false,

            // secure conections
            protocol: null,
            port: null,
            ca: null,
            cert: null,
            key: null,
        }
    );

    if (native) {
        self.native = native;
    } else {
        /*
        self.native = mqtt.createClient(self.initd.port, self.initd.host, {
            clientId: self.initd.client_id,
        });
        */

        if (!self.initd.host) {
            throw new Error("MQTTTransport: expected initd.host");
        }

        var connectd = {
            clientId: self.initd.client_id,
        };

        if (self.initd.key) {
            connectd.key = fs.readFileSync(self.initd.key);
        }

        if (self.initd.cert) {
            connectd.cert = fs.readFileSync(self.initd.cert);
        }

        if (self.initd.ca) {
            connectd.ca = fs.readFileSync(self.initd.ca);
        }

        if (!self.initd.protocol) {
            if (connectd.key && connectd.cert) {
                self.initd.protocol = 'mqtts';
            } else {
                self.initd.protocol = 'mqtt';
            }
        }

        if (!self.initd.port) {
            if (self.initd.protocol === 'mqtts') {
                self.initd.port = 8883;
            } else {
                self.initd.port = 1883;
            }
        }

        var url = util.format("%s://%s:%s", self.initd.protocol || "mqtt", self.initd.host, self.initd.port);

        self.native = mqtt.connect(url, connectd);
        self.native.on('connect', function () {
            logger.info({
                method: "publish/on(connect)",
                url: url,
            }, "connected");

            console.log("===============================");
            console.log("=== MQTT Server Up");
            console.log("=== ");
            console.log("=== Connect at:");
            console.log("=== " + url);
            console.log("===============================");
        });
    }

    self.native.on('error', function () {
        logger.error({
            method: "publish/on(error)",
            arguments: arguments,
            cause: "likely MQTT issue - will automatically reconnect soon",
        }, "unexpected error");
    });
    self.native.on('close', function () {
        logger.error({
            method: "publish/on(close)",
            arguments: arguments,
            cause: "likely MQTT issue - will automatically reconnect soon",
        }, "unexpected close");
    });

    self._subscribed = false;
};

MQTTTransport.prototype = new iotdb_transport.Transport();
MQTTTransport.prototype._class = "MQTTTransport";

/* --- methods --- */

/**
 *  See {iotdb_transport.Transport#list} for documentation.
 *

 *  MQTT: this does nothing, as we don't have
 *  a concept of a databse.
 */
MQTTTransport.prototype.list = function (paramd, callback) {
    var self = this;

    self._validate_list(paramd, callback);

    callback({
        end: true,
        error: new Error("N/A"),
    });
};

/**
 *  See {iotdb_transport.Transport#added} for documentation.
 *

 *  NOT FINISHED
 */
MQTTTransport.prototype.added = function (paramd, callback) {
    var self = this;

    self._validate_added(paramd, callback);
};

/**
 *  See {iotdb_transport.Transport#bands} for documentation.
 *

 *  MQTT: this does nothing, as we don't have
 *  a concept of a databse
 */
MQTTTransport.prototype.bands = function (paramd, callback) {
    var self = this;

    self._validate_bands(paramd, callback);

    // don't know (and never will)
    callback({
        id: paramd.id,
        band: paramd.band,
        value: null,
        error: new errors.NotImplemented(),
    });
};

/**
 *  See {iotdb_transport.Transport#get} for documentation.
 *

 *  MQTT: this does nothing, as we don't have
 *  a concept of a databse
 */
MQTTTransport.prototype.get = function (paramd, callback) {
    var self = this;

    self._validate_get(paramd, callback);

    var gd = _.shallowCopy(paramd);
    gd.value = null;

    callback(new errors.NotImplemented(), gd);
};

/**
 *  See {iotdb_transport.Transport#update} for documentation.
 */
MQTTTransport.prototype.put = function (paramd, callback) {
    var self = this;

    self._validate_update(paramd, callback);

    var pd = _.shallowCopy(paramd);

    var value = paramd.value;
    var timestamp = value["@timestamp"];
    if (!timestamp && _.is.Boolean(self.initd.add_timestamp)) {
        value = _.shallowCopy(value);
        value["@timestamp"] = _.timestamp.make();
    } else if (!timestamp && _.is.Array(self.initd.add_timestamp) && (self.init.add_timestamp.indexOf(paramd.band) > -1)) {
        value = _.shallowCopy(value);
        value["@timestamp"] = _.timestamp.make();
    }

    var channel = self.initd.channel(self.initd, paramd.id, paramd.band);
    var d = self.initd.pack(value, paramd.id, paramd.band);

    self.native.publish(channel, d, {
        retain: self.initd.retain,
        qos: self.initd.qos,
    }, function() {
        // TD: is there an error we can use?
        callback(null, pd);
    });

};

/**
 *  See {iotdb_transport.Transport#updated} for documentation.
 */
MQTTTransport.prototype.updated = function (paramd, callback) {
    var self = this;

    self._validate_updated(paramd, callback);

    if (!self.initd.allow_updated) {
        return;
    }

    if (!self._subscribed) {
        var channel = url_join(self.initd.prefix, "#");
        self.native.subscribe(channel, function (error) {
            /* maybe reset _subscribed on mqtt.open? */
            logger.error({
                method: "publish/on(close)",
                arguments: arguments,
                cause: "likely MQTT issue - this is probably very bad",
            }, "unexpected error subscribing");
        });
    }

    self.native.on("message", function (topic, message, packet) {
        var parts = self.initd.unchannel(self.initd, topic);
        if (!parts) {
            return;
        }

        var topic_id = parts[0];
        var topic_band = parts[1];

        if (paramd.id && (topic_id !== paramd.id)) {
            return;
        }
        if (paramd.band && (topic_band !== paramd.band)) {
            return;
        }

        var d = self.initd.unpack(message, topic_id, topic_band);
        callback({
            id: topic_id,
            band: topic_band,
            value: d,
        });
    });
};

/**
 *  See {iotdb_transport.Transport#remove} for documentation.
 *

 *  MQTT - do nothing
 */
MQTTTransport.prototype.remove = function (paramd, callback) {
    var self = this;

    self._validate_remove(paramd, callback);
};

/* -- internals -- */
var _encode = function (s) {
    return s.replace(/[\/#+]/g, function (c) {
        return '%' + c.charCodeAt(0).toString(16);
    });
};

var _decode = function (s) {
    return decodeURIComponent(s);
};

var _unpack = function (d) {
    if (d.toString) {
        d = d.toString();
    }

    return JSON.parse(d);
};

var _pack = function (d) {
    return JSON.stringify(d);
};

/**
 *  API
 */
exports.MQTTTransport = MQTTTransport;
<|endoftext|>


<commit_before>var _ = require('lodash'),
	async = require('async'),
	camelCase = require('camel-case'),
	fs = require('fs'),
	path = require('path'),
	prompt = require('prompt'),
	wrench = require('wrench');

var HOME = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE,
	CONFIG = '.node-bootstrap.json';

function bootstrap(opts, callback) {
	callback = maybeCallback(arguments[arguments.length-1]);
	if (!opts || _.isFunction(opts)) {
		opts = {};
	}

	async.waterfall([

		// feed opts in waterfall
		function(cb) { return cb(null, opts); },

		// load default values
		// loadDefaults,

		// load any relevant config files
		// loadConfig,

		// prompt for any missing required values
		promptForConfig,

		// write completed config to HOME
		// writeConfig,

		// generate the bootstrapped project
		generateProject

	], function(err, results) {
		if (err) { return callback(err); }
		return callback(null, results);
	});
}

module.exports = bootstrap;

/*
function loadDefaults(opts, callback) {
	opts = opts || {};

	async.parallel([

		function(cb) {

		},

		function(cb) {

		}
	], function(err, results) {

	});

	return callback(null, opts);
}

function loadConfig(opts, callback) {
	opts = opts || {};

	try {
		// explicit config file
		if (opts.config) {
			fs.readFile(opts.config, function(err, data) {
				return callback(null, JSON.parse(data));
			});

		// try to load local config file
		} else {
			var config = findConfig();
			return callback(null, config ? require(config) : null);
		}
	} catch (e) {
		return callback(e);
	}
}
*/

function promptForConfig(config, callback) {
	config = config || {};

	prompt.message = '⚙'.green + '⚙'.white;
	prompt.delimiter = '  ';
	prompt.override = config;

	var schema = {
		properties: {
			project: {
				description: 'project name',
				required: true
			},
			description: {
				description: 'description',
				required: true
			},
			name: {
				description: 'author name',
				required: true
			},
			email: {
				description: 'author email',
				required: true
			},
			github: {
				description: 'github username',
				required: true
			}
		}
	};

	prompt.start();
	prompt.get(schema, function (err, result) {
		config.projectCamelCase = camelCase(result.project);
		config.devDependencies = JSON.stringify({
			'grunt': '~0.4.5',
			'grunt-mocha-test': '~0.10.2',
			'grunt-contrib-jshint': '~0.10.0',
			'grunt-contrib-clean': '~0.5.0',
			'istanbul': '~0.2.10',
			'mocha': '~1.19.0',
			'should': '~3.3.1'
		}, null, '\t');
		return callback(null, _.extend(config, result));
	});
}

/*
function writeConfig(config, callback) {
	config = config || {};
	var configFile = path.join(HOME, CONFIG);
	fs.writeFile(configFile, JSON.stringify(config, null, 2), function(err) {
		return callback(err, config, configFile);
	});
}
*/

function generateProject(config, callback) {
	config = config || {};

	var dest = path.resolve(config.project);
	if (fs.existsSync(dest) && !config.force) {
		return callback('"' + config.project + '" already exists. Use --force to overwrite.');
	}
	//wrench.mkdirSyncRecursive(dest, 0755);

	// copy all files in
	wrench.copyDirSyncRecursive(path.join(__dirname, '..', 'src'), dest, {
		forceDelete: true
	});
	wrench.mkdirSyncRecursive(path.join(dest, 'node_modules'));

	// iterate through all files and update them with templates
	wrench.readdirSyncRecursive(dest).forEach(function(file) {
		var fullpath = path.join(dest, file);
		if (fs.statSync(fullpath).isFile()) {
			console.log('processing ' + file + '...');
			fs.writeFileSync(fullpath, _.template(fs.readFileSync(fullpath, 'utf8'), config));
		}
	});

	return callback(null, config);
}

// helpers
function findConfig() {
	var result = null,
		configs = [
			path.resolve(CONFIG),
			path.join(HOME, CONFIG)
		];

	// see if we have a config saved in either CWD or HOME
	configs.every(function(config) {
		if (fs.existsSync(config)) {
			result = config;
			return false;
		}
		return true;
	});

	return result;
}

function maybeCallback(cb) {
	return _.isFunction(cb) ? cb : function(err) { if (err) { throw err; } };
}<commit_msg>rename source files to match project<commit_after>var _ = require('lodash'),
	async = require('async'),
	camelCase = require('camel-case'),
	fs = require('fs'),
	path = require('path'),
	prompt = require('prompt'),
	wrench = require('wrench');

var HOME = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE,
	CONFIG = '.node-bootstrap.json';

function bootstrap(opts, callback) {
	callback = maybeCallback(arguments[arguments.length-1]);
	if (!opts || _.isFunction(opts)) {
		opts = {};
	}

	async.waterfall([

		// feed opts in waterfall
		function(cb) { return cb(null, opts); },

		// load default values
		// loadDefaults,

		// load any relevant config files
		// loadConfig,

		// prompt for any missing required values
		promptForConfig,

		// write completed config to HOME
		// writeConfig,

		// generate the bootstrapped project
		generateProject

	], function(err, results) {
		if (err) { return callback(err); }
		return callback(null, results);
	});
}

module.exports = bootstrap;

/*
function loadDefaults(opts, callback) {
	opts = opts || {};

	async.parallel([

		function(cb) {

		},

		function(cb) {

		}
	], function(err, results) {

	});

	return callback(null, opts);
}

function loadConfig(opts, callback) {
	opts = opts || {};

	try {
		// explicit config file
		if (opts.config) {
			fs.readFile(opts.config, function(err, data) {
				return callback(null, JSON.parse(data));
			});

		// try to load local config file
		} else {
			var config = findConfig();
			return callback(null, config ? require(config) : null);
		}
	} catch (e) {
		return callback(e);
	}
}
*/

function promptForConfig(config, callback) {
	config = config || {};

	prompt.message = '⚙'.green + '⚙'.white;
	prompt.delimiter = '  ';
	prompt.override = config;

	var schema = {
		properties: {
			project: {
				description: 'project name',
				required: true
			},
			description: {
				description: 'description',
				required: true
			},
			name: {
				description: 'author name',
				required: true
			},
			email: {
				description: 'author email',
				required: true
			},
			github: {
				description: 'github username',
				required: true
			}
		}
	};

	prompt.start();
	prompt.get(schema, function (err, result) {
		config.projectCamelCase = camelCase(result.project);
		config.devDependencies = JSON.stringify({
			'grunt': '~0.4.5',
			'grunt-mocha-test': '~0.10.2',
			'grunt-contrib-jshint': '~0.10.0',
			'grunt-contrib-clean': '~0.5.0',
			'istanbul': '~0.2.10',
			'mocha': '~1.19.0',
			'should': '~3.3.1'
		}, null, '\t');
		return callback(null, _.extend(config, result));
	});
}

/*
function writeConfig(config, callback) {
	config = config || {};
	var configFile = path.join(HOME, CONFIG);
	fs.writeFile(configFile, JSON.stringify(config, null, 2), function(err) {
		return callback(err, config, configFile);
	});
}
*/

function generateProject(config, callback) {
	config = config || {};

	var dest = path.resolve(config.project);
	if (fs.existsSync(dest) && !config.force) {
		return callback('"' + config.project + '" already exists. Use --force to overwrite.');
	}
	//wrench.mkdirSyncRecursive(dest, 0755);

	// copy all files in
	wrench.copyDirSyncRecursive(path.join(__dirname, '..', 'src'), dest, {
		forceDelete: true
	});
	wrench.mkdirSyncRecursive(path.join(dest, 'node_modules'));

	// iterate through all files and update them with templates
	wrench.readdirSyncRecursive(dest).forEach(function(file) {
		var fullpath = path.join(dest, file);
		if (fs.statSync(fullpath).isFile()) {
			console.log('processing ' + file + '...');
			fs.writeFileSync(fullpath, _.template(fs.readFileSync(fullpath, 'utf8'), config));
		}
	});

	fs.renameSync(path.join(dest, 'lib', 'module.js'), path.join(dest, 'lib', config.project + '.js'));
	fs.renameSync(path.join(dest, 'test', 'module_test.js'), path.join(dest, 'test', config.project + '_test.js'));

	return callback(null, config);
}

// helpers
function findConfig() {
	var result = null,
		configs = [
			path.resolve(CONFIG),
			path.join(HOME, CONFIG)
		];

	// see if we have a config saved in either CWD or HOME
	configs.every(function(config) {
		if (fs.existsSync(config)) {
			result = config;
			return false;
		}
		return true;
	});

	return result;
}

function maybeCallback(cb) {
	return _.isFunction(cb) ? cb : function(err) { if (err) { throw err; } };
}<|endoftext|>


<commit_before>"use strict";

var Boom = require('boom');

module.exports = function (err) {
  var error;

  switch (err.code) {
    case '42P01': // undefined_table
      error = Boom.notFound('Table not found');
      break;
    case '42703': // undefined_column
    case '22026': // string_data_length_mismatch
    case '22001': // string_data_right_truncation
    case '23502': // not_null_violation
      error = Boom.badData(err.message);
      break;
    case '22P02': // invalid_text_representation
      error = Boom.badRequest('Invalid value for operation');
      break;
    case '23503': // foreign_key_violation
      error = Boom.badData('Foreign key is not respected');
      break;
    case 'ECONNREFUSED':
      error = Boom.badGateway('Connection refused');
      break;
    default:
      error = Boom.wrap(error, 'An unhandled error occurred, contact an administrator for more information', 500);
      break;
  }

  return error;
};<commit_msg>Fixed typo when wrapping error<commit_after>"use strict";

var Boom = require('boom');

module.exports = function (err) {
  var error;

  switch (err.code) {
    case '42P01': // undefined_table
      error = Boom.notFound('Table not found');
      break;
    case '42703': // undefined_column
    case '22026': // string_data_length_mismatch
    case '22001': // string_data_right_truncation
    case '23502': // not_null_violation
      error = Boom.badData(err.message);
      break;
    case '22P02': // invalid_text_representation
      error = Boom.badRequest('Invalid value for operation');
      break;
    case '23503': // foreign_key_violation
      error = Boom.badData('Foreign key is not respected');
      break;
    case 'ECONNREFUSED':
      error = Boom.badGateway('Connection refused');
      break;
    default:
      error = Boom.wrap(err, 'An unhandled error occurred, contact an administrator for more information', 500);
      break;
  }

  return error;
};<|endoftext|>


<commit_before>'use strict'

var cssAstFormatter = require('css'),
  embeddedbase64Remover = require('./embedded-base64-remover'),
  ffRemover = require('./unused-fontface-remover'),
  fs = require('fs'),
  nonMatchingMediaQueryRemover = require('./non-matching-media-query-remover'),
  system = require('system'),
  stdout = system.stdout, // for using this as a file
  webpage = require('webpage'),
  page // initialised in prepareNewPage

var NORMALIZATION_DONE = 'NORMALIZATION_DONE'
var GENERATION_DONE = 'GENERATION_DONE'
// for debugging
var START_TIME

var args = system.args
if (args.length < 4) {
  errorlog('Not enough arguments.')
  phantomExit(1)
}

var criticalCssOptions = {
  url: args[1],
  css: args[2],
  width: args[3],
  height: args[4],
  // always forceInclude '*' selector
  forceInclude: [{ value: '*' }].concat(JSON.parse(args[5]) || []),
  strict: args[6] === 'true',
  maxEmbeddedBase64Length: args[7],
  userAgent: args[8],
  debugMode: args[9] === 'true'
}

// monkey patch for directing errors to stderr
// https://github.com/ariya/phantomjs/issues/10150#issuecomment-28707859
var errorlog = function (msg) {
  if (criticalCssOptions.debugMode) {
    debuglog(msg, true)
  } else {
    system.stderr.write(msg)
  }
}

var debuglog = function (msg, isError) {
  if (criticalCssOptions.debugMode) {
    system.stderr.write('time: ' + (new Date().getTime() - START_TIME) + ' | ' + (isError ? 'ERR: ' : '') + msg)
  }
}

function prepareNewPage () {
  page = webpage.create()
  // don't confuse analytics more than necessary when visiting websites
  page.settings.userAgent = criticalCssOptions.userAgent

  /* prevent page JS errors from being output to final CSS */
  page.onError = function (msg, trace) {
    // do nothing
  }

  page.onConsoleMessage = function(msg) {
    debuglog(msg)
  }

  page.onResourceRequested = function (requestData, request) {
    if (/\.js(\?.*)?$/.test(requestData.url)) {
      request.abort()
    }
  }

  page.onResourceError = function (resourceError) {
    page.reason = resourceError.errorString
    page.reason_url = resourceError.url
  }
  page.onCallback = function (callbackObject) {
    if (callbackObject.status === NORMALIZATION_DONE) {
      debuglog('NORMALIZATION_DONE')
      // because otherwise the actual url we want to open a page with will open with
      // content set in normalizeCss..
      prepareNewPage()
      debuglog('NORMALIZATION_DONE, prepareNewPage done')
      criticalCssOptions.ast = cssAstFormatter.parse(callbackObject.css, { silent: true })
      debuglog('NORMALIZATION_DONE, got ast')
      getCriticalPathCss(criticalCssOptions)
      return
    }

    if (callbackObject.status === GENERATION_DONE) {
      debuglog('GENERATION_DONE')
      returnCssFromAstRules(callbackObject.rules)
    }
  }
}

function returnCssFromAstRules (criticalRules) {
  debuglog('returnCssFromAstRules')
  try {
    if (criticalRules && criticalRules.length > 0) {
      var finalCss = cssAstFormatter.stringify({
        stylesheet: {
          rules: criticalRules
        }
      })
      debuglog('finalCss: stringify from ast')

      // remove data-uris that are too long
      // ..faster if this removal can be combined with @font-face one into same iteration..
      finalCss = embeddedbase64Remover(finalCss, criticalCssOptions.maxEmbeddedBase64Length)
      debuglog('finalCss: embeddedbase64Remover')

      // remove unused @fontface rules
      finalCss = ffRemover(finalCss)
      debuglog('finalCss: ffRemover')

      // TODO: bring handleRuleSelectorCase func back, was removed because was too slow
      // previous version of Penthouse ensured selector case in return critiacl css was the same.
      // (it gets modified (transformed to lowercase) when normalized)

      if (finalCss.trim().length === 0) {
        errorlog('Note: Generated critical css was empty for URL: ' + criticalCssOptions.url)
      }

      // return the critical css!
      stdout.write(finalCss)
      debuglog('finalCss: write - DONE!')
      phantomExit(0)
    } else {
      // No css. This is not an error on our part
      // but still safer to warn the end user, in case they made a mistake
      errorlog('Note: Generated critical css was empty for URL: ' + criticalCssOptions.url)
      // for consisteny, still generate output (will be empty)
      stdout.write('')
      phantomExit(0)
    }
  } catch (ex) {
    errorlog('error: ' + ex)
    phantomExit(1)
  }
}

// discard stdout from phantom exit
function phantomExit (code) {
  if (page) {
    page.close()
  }
  setTimeout(function () {
    phantom.exit(code)
  }, 0)
}

function extractFullCssFromPage (doneStatus, originalCss) {
  console.log('extractFullCssFromPage')
  var getOriginalSelectorCase = function (selector) {
    var sanitizedSelector = selector.replace(/[#-.]|[[-^]|[?|{}]/g, '\\$&')
    var pattern = new RegExp('(' + sanitizedSelector + ')[ ,{]?', 'i') // }
    var match = originalCss.match(pattern)
    if (match && match[1]) {
      return match[1]
    }
    return selector
  }

  var handleCssRule = function (rule) {
    if (!rule.selectorText) {
      if (!rule.media) {
        return rule.cssText
      }
      var mediaContent = handleCssRules(rule.cssRules)
      return '@media ' + rule.media.mediaText + '{' + mediaContent + '}'
    }
    return rule.cssText
  }
  var handleCssRules = function (cssRulesList) {
    return Array.prototype.map.call(cssRulesList || [], handleCssRule).join(' ')
  }

  originalCss = decodeURIComponent(originalCss)
  console.log('extractFullCssFromPage :' + originalCss.length)
  var css = Array.prototype.map.call(document.styleSheets, function (stylesheet, idx) {
    return handleCssRules(stylesheet.cssRules)
  }).join(' ')

  console.log('extractFullCssFromPage, css extracted :' + css.length)

  // these (case 0) @-rules are not part of document.styleSheets, so need to be preserved manually
  var metaMatches = originalCss.match(/(@(import|namespace)[^;]*;)/g)
  if (metaMatches) {
    console.log('extractFullCssFromPage, metamatches')
    // preserve order
    var metaCss = ''
    metaMatches.forEach(function (metaMatch) {
      metaCss += metaMatch
    })
    css = metaCss + css
  }
  console.log('extractFullCssFromPage, metamatches DONE, callPhantom')
  window.callPhantom({
    status: doneStatus,
    css: css
  })
}

function normalizeCss (css) {
  debuglog('normalizeCss: ' + css.length)
  page.content = ''
  page.evaluate(extractFullCssFromPage, NORMALIZATION_DONE, encodeURIComponent(css))
}

// called inside a sandboxed environment inside phantomjs - no outside references
// arguments and return value must be primitives
// @see http://phantomjs.org/api/webpage/method/evaluate.html
function pruneNonCriticalCss (astRules, forceInclude, doneStatus) {
  console.log('pruneNonCriticalCss')
  var h = window.innerHeight
  var renderWaitTime = 100 // ms TODO: user specifiable through options object

  var isElementAboveFold = function (element) {
    // temporarily force clear none in order to catch elements that clear previous content themselves and who w/o their styles could show up unstyled in above the fold content (if they rely on f.e. 'clear:both;' to clear some main content)
    var originalClearStyle = element.style.clear || ''
    element.style.clear = 'none'
    var aboveFold = element.getBoundingClientRect().top < h

    // set clear style back to what it was
    element.style.clear = originalClearStyle
    return aboveFold
  }

  var matchesForceInclude = function (selector) {
    return forceInclude.some(function (includeSelector) {
      if (includeSelector.type === 'RegExp') {
        var pattern = new RegExp(includeSelector.value)
        return pattern.test(selector)
      }
      return includeSelector.value === selector
    })
  }

  var isSelectorCritical = function (selector) {
    if (matchesForceInclude(selector.trim())) {
      return true
    }

    // Case 3: @-rule with full CSS (rules) inside [REMAIN]
    // @viewport, @-ms-viewport. AST parser classifies these as "regular" rules
    if (/^@/.test(selector)) {
      return true
    }

    // some selectors can't be matched on page.
    // In these cases we test a slightly modified selectors instead, modifiedSelector.
    var modifiedSelector = selector
    if (modifiedSelector.indexOf(':') > -1) {
      // handle special case selectors, the ones that contain a semi colon (:)
      // many of these selectors can't be matched to anything on page via JS,
      // but that still might affect the above the fold styling

      // these psuedo selectors depend on an element, so test element instead
      // (:hover, :focus, :active would be treated same
      // IF we wanted to keep them for critical path css, but we don't)
      modifiedSelector = modifiedSelector.replace(/(:?:before|:?:after)*/g, '')

      // if selector is purely psuedo (f.e. ::-moz-placeholder), just keep as is.
      // we can't match it to anything on page, but it can impact above the fold styles
      if (modifiedSelector.replace(/:[:]?([a-zA-Z0-9\-\_])*/g, '').trim().length === 0) {
        return true
      }

      // handle browser specific psuedo selectors bound to elements,
      // Example, button::-moz-focus-inner, input[type=number]::-webkit-inner-spin-button
      // remove browser specific pseudo and test for element
      modifiedSelector = modifiedSelector.replace(/:?:-[a-z-]*/g, '')
    }

    // now we have a selector to test, first grab any matching elements
    var elements
    try {
      elements = document.querySelectorAll(modifiedSelector)
    } catch (e) {
      // not a valid selector, remove it.
      return false
    }

    // some is not supported on Arrays in this version of QT browser,
    // meaning have to write much less terse code here.
    var elementIndex = 0
    var aboveFold = false
    while (!aboveFold && elementIndex < elements.length) {
      aboveFold = isElementAboveFold(elements[elementIndex])
      elementIndex++
    }
    return aboveFold
  }

  var isCssRuleCritical = function (rule) {
    if (rule.type === 'rule') {
      // check what, if any selectors are found above fold
      rule.selectors = rule.selectors.filter(isSelectorCritical)
      return rule.selectors.length > 0
    }
    /* ==@-rule handling==*/
    /* - Case 0 : Non nested @-rule [REMAIN]
     (@charset, @import, @namespace)
     */
    if (
      rule.type === 'charset' ||
      rule.type === 'import' ||
      rule.type === 'namespace'
    ) {
      return true
    }

    /* Case 1: @-rule with CSS properties inside [REMAIN]
    	@font-face - keep here, but remove later in code, unless it is used.
    */
    if (rule.type === 'font-face') {
      return true
    }

    /* Case 3: @-rule with full CSS (rules) inside [REMAIN]
    */
    if (
      // non matching media queries are stripped out in non-matching-media-query-remover.js
      rule.type === 'media' ||
      rule.type === 'document' ||
      rule.type === 'supports'
    ) {
      rule.rules = rule.rules.filter(isCssRuleCritical)
      return rule.rules.length > 0
    }

    return false
  }

  var processCssRules = function () {
    console.log('processCssRules BEFORE')
    var criticalRules = astRules.filter(isCssRuleCritical)
    console.log('processCssRules AFTER')

    // we're done - call final function to exit outside of phantom evaluate scope
    window.callPhantom({
      status: doneStatus,
      rules: criticalRules
    })
  }

  // give some time (renderWaitTime) for sites like facebook that build their page dynamically,
  // otherwise we can miss some selectors (and therefor rules)
  // --tradeoff here: if site is too slow with dynamic content,
  //	it doesn't deserve to be in critical path.
  setTimeout(processCssRules, renderWaitTime)
}

/*
 * Tests each selector in css file at specified resolution,
 * to see if any such elements appears above the fold on the page
 * calls callPhantom when done, with an updated AST rules list
 *
 * @param options.url the url as a string
 * @param options.ast the css as an AST object
 * @param options.width the width of viewport
 * @param options.height the height of viewport
 ---------------------------------------------------------*/
function getCriticalPathCss (options) {
  debuglog('getCriticalPathCss')
  page.viewportSize = {
    width: options.width,
    height: options.height
  }
  // first strip out non matching media queries
  var astRules = nonMatchingMediaQueryRemover(options.ast.stylesheet.rules, options.width, options.height)
  debuglog('stripped out non matching media queries')

  page.open(options.url, function (status) {
    if (status !== 'success') {
      errorlog("Error opening url '" + page.reason_url + "': " + page.reason)
      phantomExit(1)
    } else {
      debuglog('page opened')
      page.evaluate(pruneNonCriticalCss, astRules, options.forceInclude, GENERATION_DONE)
    }
  })
}

START_TIME = new Date().getTime()
var css
try {
  var f = fs.open(criticalCssOptions.css, 'r')
  css = f.read()
  debuglog('opened css')
} catch (e) {
  errorlog(e)
  phantomExit(1)
}

prepareNewPage()
debuglog('prepared new page')
var ast
try {
  ast = cssAstFormatter.parse(css)
  debuglog('parsed ast (without errors)')
} catch (e) {
  if (criticalCssOptions.strict) {
    errorlog(e.message)
    phantomExit(1)
  }
  debuglog("Failed ast formatting css '" + e.message + "': ")
  normalizeCss(css)
}

if (ast) {
  criticalCssOptions.ast = ast
  getCriticalPathCss(criticalCssOptions)
}
<commit_msg>syntax and dead code cleanup<commit_after>'use strict'

var cssAstFormatter = require('css'),
  embeddedbase64Remover = require('./embedded-base64-remover'),
  ffRemover = require('./unused-fontface-remover'),
  fs = require('fs'),
  nonMatchingMediaQueryRemover = require('./non-matching-media-query-remover'),
  system = require('system'),
  stdout = system.stdout, // for using this as a file
  webpage = require('webpage'),
  page // initialised in prepareNewPage

var NORMALIZATION_DONE = 'NORMALIZATION_DONE'
var GENERATION_DONE = 'GENERATION_DONE'
// for debugging
var START_TIME

var args = system.args
if (args.length < 4) {
  errorlog('Not enough arguments.')
  phantomExit(1)
}

var criticalCssOptions = {
  url: args[1],
  css: args[2],
  width: args[3],
  height: args[4],
  // always forceInclude '*' selector
  forceInclude: [{ value: '*' }].concat(JSON.parse(args[5]) || []),
  strict: args[6] === 'true',
  maxEmbeddedBase64Length: args[7],
  userAgent: args[8],
  debugMode: args[9] === 'true'
}

// monkey patch for directing errors to stderr
// https://github.com/ariya/phantomjs/issues/10150#issuecomment-28707859
var errorlog = function (msg) {
  if (criticalCssOptions.debugMode) {
    debuglog(msg, true)
  } else {
    system.stderr.write(msg)
  }
}

var debuglog = function (msg, isError) {
  if (criticalCssOptions.debugMode) {
    system.stderr.write('time: ' + (new Date().getTime() - START_TIME) + ' | ' + (isError ? 'ERR: ' : '') + msg)
  }
}

function prepareNewPage () {
  page = webpage.create()
  // don't confuse analytics more than necessary when visiting websites
  page.settings.userAgent = criticalCssOptions.userAgent

  /* prevent page JS errors from being output to final CSS */
  page.onError = function (msg, trace) {
    // do nothing
  }

  page.onConsoleMessage = function (msg) {
    debuglog(msg)
  }

  page.onResourceRequested = function (requestData, request) {
    if (/\.js(\?.*)?$/.test(requestData.url)) {
      request.abort()
    }
  }

  page.onResourceError = function (resourceError) {
    page.reason = resourceError.errorString
    page.reason_url = resourceError.url
  }
  page.onCallback = function (callbackObject) {
    if (callbackObject.status === NORMALIZATION_DONE) {
      debuglog('NORMALIZATION_DONE')
      // because otherwise the actual url we want to open a page with will open with
      // content set in normalizeCss..
      prepareNewPage()
      debuglog('NORMALIZATION_DONE, prepareNewPage done')
      criticalCssOptions.ast = cssAstFormatter.parse(callbackObject.css, { silent: true })
      debuglog('NORMALIZATION_DONE, got ast')
      getCriticalPathCss(criticalCssOptions)
      return
    }

    if (callbackObject.status === GENERATION_DONE) {
      debuglog('GENERATION_DONE')
      returnCssFromAstRules(callbackObject.rules)
    }
  }
}

function returnCssFromAstRules (criticalRules) {
  debuglog('returnCssFromAstRules')
  try {
    if (criticalRules && criticalRules.length > 0) {
      var finalCss = cssAstFormatter.stringify({
        stylesheet: {
          rules: criticalRules
        }
      })
      debuglog('finalCss: stringify from ast')

      // remove data-uris that are too long
      // ..faster if this removal can be combined with @font-face one into same iteration..
      finalCss = embeddedbase64Remover(finalCss, criticalCssOptions.maxEmbeddedBase64Length)
      debuglog('finalCss: embeddedbase64Remover')

      // remove unused @fontface rules
      finalCss = ffRemover(finalCss)
      debuglog('finalCss: ffRemover')

      // TODO: bring handleRuleSelectorCase func back, was removed because was too slow
      // previous version of Penthouse ensured selector case in return critiacl css was the same.
      // (it gets modified (transformed to lowercase) when normalized)

      if (finalCss.trim().length === 0) {
        errorlog('Note: Generated critical css was empty for URL: ' + criticalCssOptions.url)
      }

      // return the critical css!
      stdout.write(finalCss)
      debuglog('finalCss: write - DONE!')
      phantomExit(0)
    } else {
      // No css. This is not an error on our part
      // but still safer to warn the end user, in case they made a mistake
      errorlog('Note: Generated critical css was empty for URL: ' + criticalCssOptions.url)
      // for consisteny, still generate output (will be empty)
      stdout.write('')
      phantomExit(0)
    }
  } catch (ex) {
    errorlog('error: ' + ex)
    phantomExit(1)
  }
}

// discard stdout from phantom exit
function phantomExit (code) {
  if (page) {
    page.close()
  }
  setTimeout(function () {
    phantom.exit(code)
  }, 0)
}

function extractFullCssFromPage (doneStatus, originalCss) {
  console.log('extractFullCssFromPage')

  var handleCssRule = function (rule) {
    if (!rule.selectorText) {
      if (!rule.media) {
        return rule.cssText
      }
      var mediaContent = handleCssRules(rule.cssRules)
      return '@media ' + rule.media.mediaText + '{' + mediaContent + '}'
    }
    return rule.cssText
  }
  var handleCssRules = function (cssRulesList) {
    return Array.prototype.map.call(cssRulesList || [], handleCssRule).join(' ')
  }

  originalCss = decodeURIComponent(originalCss)
  console.log('extractFullCssFromPage :' + originalCss.length)
  var css = Array.prototype.map.call(document.styleSheets, function (stylesheet, idx) {
    return handleCssRules(stylesheet.cssRules)
  }).join(' ')

  console.log('extractFullCssFromPage, css extracted :' + css.length)

  // these (case 0) @-rules are not part of document.styleSheets, so need to be preserved manually
  var metaMatches = originalCss.match(/(@(import|namespace)[^;]*;)/g)
  if (metaMatches) {
    console.log('extractFullCssFromPage, metamatches')
    // preserve order
    var metaCss = ''
    metaMatches.forEach(function (metaMatch) {
      metaCss += metaMatch
    })
    css = metaCss + css
  }
  console.log('extractFullCssFromPage, metamatches DONE, callPhantom')
  window.callPhantom({
    status: doneStatus,
    css: css
  })
}

function normalizeCss (css) {
  debuglog('normalizeCss: ' + css.length)
  page.content = ''
  page.evaluate(extractFullCssFromPage, NORMALIZATION_DONE, encodeURIComponent(css))
}

// called inside a sandboxed environment inside phantomjs - no outside references
// arguments and return value must be primitives
// @see http://phantomjs.org/api/webpage/method/evaluate.html
function pruneNonCriticalCss (astRules, forceInclude, doneStatus) {
  console.log('pruneNonCriticalCss')
  var h = window.innerHeight
  var renderWaitTime = 100 // ms TODO: user specifiable through options object

  var isElementAboveFold = function (element) {
    // temporarily force clear none in order to catch elements that clear previous content themselves and who w/o their styles could show up unstyled in above the fold content (if they rely on f.e. 'clear:both;' to clear some main content)
    var originalClearStyle = element.style.clear || ''
    element.style.clear = 'none'
    var aboveFold = element.getBoundingClientRect().top < h

    // set clear style back to what it was
    element.style.clear = originalClearStyle
    return aboveFold
  }

  var matchesForceInclude = function (selector) {
    return forceInclude.some(function (includeSelector) {
      if (includeSelector.type === 'RegExp') {
        var pattern = new RegExp(includeSelector.value)
        return pattern.test(selector)
      }
      return includeSelector.value === selector
    })
  }

  var isSelectorCritical = function (selector) {
    if (matchesForceInclude(selector.trim())) {
      return true
    }

    // Case 3: @-rule with full CSS (rules) inside [REMAIN]
    // @viewport, @-ms-viewport. AST parser classifies these as "regular" rules
    if (/^@/.test(selector)) {
      return true
    }

    // some selectors can't be matched on page.
    // In these cases we test a slightly modified selectors instead, modifiedSelector.
    var modifiedSelector = selector
    if (modifiedSelector.indexOf(':') > -1) {
      // handle special case selectors, the ones that contain a semi colon (:)
      // many of these selectors can't be matched to anything on page via JS,
      // but that still might affect the above the fold styling

      // these psuedo selectors depend on an element, so test element instead
      // (:hover, :focus, :active would be treated same
      // IF we wanted to keep them for critical path css, but we don't)
      modifiedSelector = modifiedSelector.replace(/(:?:before|:?:after)*/g, '')

      // if selector is purely psuedo (f.e. ::-moz-placeholder), just keep as is.
      // we can't match it to anything on page, but it can impact above the fold styles
      if (modifiedSelector.replace(/:[:]?([a-zA-Z0-9\-\_])*/g, '').trim().length === 0) {
        return true
      }

      // handle browser specific psuedo selectors bound to elements,
      // Example, button::-moz-focus-inner, input[type=number]::-webkit-inner-spin-button
      // remove browser specific pseudo and test for element
      modifiedSelector = modifiedSelector.replace(/:?:-[a-z-]*/g, '')
    }

    // now we have a selector to test, first grab any matching elements
    var elements
    try {
      elements = document.querySelectorAll(modifiedSelector)
    } catch (e) {
      // not a valid selector, remove it.
      return false
    }

    // some is not supported on Arrays in this version of QT browser,
    // meaning have to write much less terse code here.
    var elementIndex = 0
    var aboveFold = false
    while (!aboveFold && elementIndex < elements.length) {
      aboveFold = isElementAboveFold(elements[elementIndex])
      elementIndex++
    }
    return aboveFold
  }

  var isCssRuleCritical = function (rule) {
    if (rule.type === 'rule') {
      // check what, if any selectors are found above fold
      rule.selectors = rule.selectors.filter(isSelectorCritical)
      return rule.selectors.length > 0
    }
    /* ==@-rule handling==*/
    /* - Case 0 : Non nested @-rule [REMAIN]
     (@charset, @import, @namespace)
     */
    if (
      rule.type === 'charset' ||
      rule.type === 'import' ||
      rule.type === 'namespace'
    ) {
      return true
    }

    /* Case 1: @-rule with CSS properties inside [REMAIN]
    	@font-face - keep here, but remove later in code, unless it is used.
    */
    if (rule.type === 'font-face') {
      return true
    }

    /* Case 3: @-rule with full CSS (rules) inside [REMAIN]
    */
    if (
      // non matching media queries are stripped out in non-matching-media-query-remover.js
      rule.type === 'media' ||
      rule.type === 'document' ||
      rule.type === 'supports'
    ) {
      rule.rules = rule.rules.filter(isCssRuleCritical)
      return rule.rules.length > 0
    }

    return false
  }

  var processCssRules = function () {
    console.log('processCssRules BEFORE')
    var criticalRules = astRules.filter(isCssRuleCritical)
    console.log('processCssRules AFTER')

    // we're done - call final function to exit outside of phantom evaluate scope
    window.callPhantom({
      status: doneStatus,
      rules: criticalRules
    })
  }

  // give some time (renderWaitTime) for sites like facebook that build their page dynamically,
  // otherwise we can miss some selectors (and therefor rules)
  // --tradeoff here: if site is too slow with dynamic content,
  //	it doesn't deserve to be in critical path.
  setTimeout(processCssRules, renderWaitTime)
}

/*
 * Tests each selector in css file at specified resolution,
 * to see if any such elements appears above the fold on the page
 * calls callPhantom when done, with an updated AST rules list
 *
 * @param options.url the url as a string
 * @param options.ast the css as an AST object
 * @param options.width the width of viewport
 * @param options.height the height of viewport
 ---------------------------------------------------------*/
function getCriticalPathCss (options) {
  debuglog('getCriticalPathCss')
  page.viewportSize = {
    width: options.width,
    height: options.height
  }
  // first strip out non matching media queries
  var astRules = nonMatchingMediaQueryRemover(options.ast.stylesheet.rules, options.width, options.height)
  debuglog('stripped out non matching media queries')

  page.open(options.url, function (status) {
    if (status !== 'success') {
      errorlog("Error opening url '" + page.reason_url + "': " + page.reason)
      phantomExit(1)
    } else {
      debuglog('page opened')
      page.evaluate(pruneNonCriticalCss, astRules, options.forceInclude, GENERATION_DONE)
    }
  })
}

START_TIME = new Date().getTime()
var css
try {
  var f = fs.open(criticalCssOptions.css, 'r')
  css = f.read()
  debuglog('opened css')
} catch (e) {
  errorlog(e)
  phantomExit(1)
}

prepareNewPage()
debuglog('prepared new page')
var ast
try {
  ast = cssAstFormatter.parse(css)
  debuglog('parsed ast (without errors)')
} catch (e) {
  if (criticalCssOptions.strict) {
    errorlog(e.message)
    phantomExit(1)
  }
  debuglog("Failed ast formatting css '" + e.message + "': ")
  normalizeCss(css)
}

if (ast) {
  criticalCssOptions.ast = ast
  getCriticalPathCss(criticalCssOptions)
}
<|endoftext|>


<commit_before>(function(window){

	var WORKER_PATH = 'js/recorderWorker.js';
	var encoderWorker = new Worker('js/mp3Worker.js');

	var Recorder = function(source, cfg) {
		var config = cfg || {};
		var bufferLen = config.bufferLen || 4096;
		this.context = source.context;
		this.node = (this.context.createScriptProcessor || this.context.createJavaScriptNode)
						.call(this.context,bufferLen, 2, 2);

		var worker = new Worker(config.workerPath || WORKER_PATH);
		worker.postMessage({
			command: 'init',
			config: {
				sampleRate: this.context.sampleRate
			}
		});
		var recording = false,
			currCallback;

		this.node.onaudioprocess = function(e){
			if (!recording) return;
			worker.postMessage({
				command: 'record',
				buffer: [
					e.inputBuffer.getChannelData(0),
					//e.inputBuffer.getChannelData(1)
				]
			});
		}

		this.configure = function(cfg){
			for (var prop in cfg){
				if (cfg.hasOwnProperty(prop)){
					config[prop] = cfg[prop];
				}
			}
		}

		this.record = function(){
			recording = true;
		}

		this.stop = function(){
			recording = false;
		}

		this.clear = function(){
			worker.postMessage({ command: 'clear' });
		}

		this.getBuffer = function(cb) {
			currCallback = cb || config.callback;
			worker.postMessage({ command: 'getBuffer' })
		}

		this.exportWAV = function(cb, type){
			currCallback = cb || config.callback;
			type = type || config.type || 'audio/wav';
			if (!currCallback) throw new Error('Callback not set');
			worker.postMessage({
				command: 'exportWAV',
				type: type
			});
		}

		//Mp3 conversion
		worker.onmessage = function(e){
			var blob = e.data;

			var arrayBuffer;

			var fileReader = new FileReader();
			fileReader.onload = function () {
				arrayBuffer = this.result;

				var buffer = new Uint8Array(arrayBuffer),
					data = parseWav(buffer);

				encoderWorker.postMessage({ cmd: 'init', config:{
					mode : 3,
					channels:1,
					samplerate: data.sampleRate,
					bitrate: data.bitsPerSample
				}});

				encoderWorker.postMessage({ cmd: 'encode', buf: Uint8ArrayToFloat32Array(data.samples) });
				encoderWorker.postMessage({ cmd: 'finish'});

				encoderWorker.onmessage = function(e) {
					if (e.data.cmd == 'data') {
						/*var audio = new Audio();
						audio.src = 'data:audio/mp3;base64,'+encode64(e.data.buf);
						audio.play();*/

						var mp3Blob = new Blob([new Uint8Array(e.data.buf)], {type: 'audio/mp3'});
						uploadAudio(mp3Blob);

						var url = 'data:audio/mp3;base64,'+encode64(e.data.buf);
						var li = document.createElement('li');
						var au = document.createElement('audio');
						var hf = document.createElement('a');

						au.controls = true;
						au.src = url;
						hf.href = url;
						hf.download = 'audio_recording_' + new Date().getTime() + '.mp3';
						hf.innerHTML = hf.download;
						li.appendChild(au);
						li.appendChild(hf);
						recordingslist.appendChild(li);

					}
				};
			};

			fileReader.readAsArrayBuffer(blob);

			currCallback(blob);
		};

		function encode64(buffer) {
			var binary = '',
				bytes = new Uint8Array( buffer ),
				len = bytes.byteLength;

			for (var i = 0; i < len; i++) {
				binary += String.fromCharCode( bytes[ i ] );
			}

			return window.btoa( binary );
		}

		function parseWav(wav) {
			function readInt(i, bytes) {
				var ret = 0,
					shft = 0;

				while (bytes) {
					ret += wav[i] << shft;
					shft += 8;
					i++;
					bytes--;
				}
				return ret;
			}
			if (readInt(20, 2) != 1) throw 'Invalid compression code, not PCM';
			if (readInt(22, 2) != 1) throw 'Invalid number of channels, not 1';
			return {
				sampleRate: readInt(24, 4),
				bitsPerSample: readInt(34, 2),
				samples: wav.subarray(44)
			};
		}

		function Uint8ArrayToFloat32Array(u8a){
			var f32Buffer = new Float32Array(u8a.length);
			for (var i = 0; i < u8a.length; i++) {
				var value = u8a[i<<1] + (u8a[(i<<1)+1]<<8);
				if (value >= 0x8000) value |= ~0x7FFF;
				f32Buffer[i] = value / 0x8000;
			}
			return f32Buffer;
		}

		function uploadAudio(mp3Data){
			var reader = new FileReader();
			reader.onload = function(event){
				var fd = new FormData();
				var mp3Name = encodeURIComponent('audio_recording_' + new Date().getTime() + '.mp3');
				fd.append('fname', mp3Name);
				fd.append('data', event.target.result);
				$.ajax({
					type: 'POST',
					url: 'upload.php',
					data: fd,
					processData: false,
					contentType: false
				}).done(function(data) {
					//console.log(data);
				});
			};
			reader.readAsDataURL(mp3Data);
		}

		source.connect(this.node);
		this.node.connect(this.context.destination);    //this should not be necessary
	};

	/*Recorder.forceDownload = function(blob, filename){
	console.log("Force download");
		var url = (window.URL || window.webkitURL).createObjectURL(blob);
		var link = window.document.createElement('a');
		link.href = url;
		link.download = filename || 'output.wav';
		var click = document.createEvent("Event");
		click.initEvent("click", true, true);
		link.dispatchEvent(click);
	}*/

	window.Recorder = Recorder;

})(window);
<commit_msg>Formated and reordered class.<commit_after>(function (window) {

	var WORKER_PATH = 'js/recorderWorker.js';
	var encoderWorker = new Worker('js/mp3Worker.js');

	var Recorder = function (source, cfg) {
		var config = cfg || {},
			currCallback;

		var recording = false;

		var bufferLen = config.bufferLen || 4096;

		this.context = source.context;

		this.node = (this.context.createScriptProcessor || this.context.createJavaScriptNode).call(this.context,bufferLen, 2, 2);
		this.node.onaudioprocess = function (e) {
			if (!recording) return;
			worker.postMessage({
				command: 'record',
				buffer: [
					e.inputBuffer.getChannelData(0),
					//e.inputBuffer.getChannelData(1)
				]
			});
		}

		source.connect(this.node);

		var worker = new Worker(config.workerPath || WORKER_PATH);
		worker.postMessage({
			command: 'init',
			config: {
				sampleRate: this.context.sampleRate
			}
		});

		// MP3 conversion
		worker.onmessage = function (e) {
			var blob = e.data;

			var arrayBuffer;

			var fileReader = new FileReader();
			fileReader.onload = function () {
				arrayBuffer = this.result;

				var buffer = new Uint8Array(arrayBuffer),
					data = parseWav(buffer);

				encoderWorker.postMessage({ cmd: 'init', config:{
					mode : 3,
					channels:1,
					samplerate: data.sampleRate,
					bitrate: data.bitsPerSample
				}});

				encoderWorker.postMessage({ cmd: 'encode', buf: Uint8ArrayToFloat32Array(data.samples) });
				encoderWorker.postMessage({ cmd: 'finish'});

				encoderWorker.onmessage = function (e) {
					if (e.data.cmd == 'data') {
						/*var audio = new Audio();
						audio.src = 'data:audio/mp3;base64,'+encode64(e.data.buf);
						audio.play();*/

						var mp3Blob = new Blob([new Uint8Array(e.data.buf)], {type: 'audio/mp3'});
						uploadAudio(mp3Blob);

						var url = 'data:audio/mp3;base64,'+encode64(e.data.buf);
						var li = document.createElement('li');
						var au = document.createElement('audio');
						var hf = document.createElement('a');

						au.controls = true;
						au.src = url;
						hf.href = url;
						hf.download = 'audio_recording_' + new Date().getTime() + '.mp3';
						hf.innerHTML = hf.download;
						li.appendChild(au);
						li.appendChild(hf);
						recordingslist.appendChild(li);

					}
				};
			};

			fileReader.readAsArrayBuffer(blob);

			currCallback(blob);
		};

		this.configure = function (cfg) {
			for (var prop in cfg) {
				if (cfg.hasOwnProperty(prop)) {
					config[prop] = cfg[prop];
				}
			}
		}

		this.record = function () {
			recording = true;
		}

		this.stop = function () {
			recording = false;
		}

		this.clear = function () {
			worker.postMessage({ command: 'clear' });
		}

		this.getBuffer = function (cb) {
			currCallback = cb || config.callback;
			worker.postMessage({ command: 'getBuffer' })
		}

		this.exportWAV = function (cb, type) {
			currCallback = cb || config.callback;
			type = type || config.type || 'audio/wav';
			if (!currCallback) throw new Error('Callback not set');
			worker.postMessage({
				command: 'exportWAV',
				type: type
			});
		}

		function encode64 (buffer) {
			var binary = '',
				bytes = new Uint8Array( buffer ),
				len = bytes.byteLength;

			for (var i = 0; i < len; i++) {
				binary += String.fromCharCode( bytes[ i ] );
			}

			return window.btoa( binary );
		}

		function parseWav (wav) {
			function readInt(i, bytes) {
				var ret = 0,
					shft = 0;

				while (bytes) {
					ret += wav[i] << shft;
					shft += 8;
					i++;
					bytes--;
				}
				return ret;
			}
			if (readInt(20, 2) != 1) throw 'Invalid compression code, not PCM';
			if (readInt(22, 2) != 1) throw 'Invalid number of channels, not 1';
			return {
				sampleRate: readInt(24, 4),
				bitsPerSample: readInt(34, 2),
				samples: wav.subarray(44)
			};
		}

		function Uint8ArrayToFloat32Array (u8a) {
			var f32Buffer = new Float32Array(u8a.length);
			for (var i = 0; i < u8a.length; i++) {
				var value = u8a[i<<1] + (u8a[(i<<1)+1]<<8);
				if (value >= 0x8000) value |= ~0x7FFF;
				f32Buffer[i] = value / 0x8000;
			}
			return f32Buffer;
		}

		function uploadAudio (mp3Data) {
			var reader = new FileReader();
			reader.onload = function (event) {
				var fd = new FormData();
				var mp3Name = encodeURIComponent('audio_recording_' + new Date().getTime() + '.mp3');
				fd.append('fname', mp3Name);
				fd.append('data', event.target.result);
				$.ajax({
					type: 'POST',
					url: 'upload.php',
					data: fd,
					processData: false,
					contentType: false
				}).done(function (data) {
					//console.log(data);
				});
			};
			reader.readAsDataURL(mp3Data);
		}
	};

	/*
	Recorder.forceDownload = function (blob, filename) {
		var clickEvent = document.createEvent("Event");
		clickEvent.initEvent("click", true, true);

		var url = (window.URL || window.webkitURL).createObjectURL(blob);

		var link = window.document.createElement('a');
		link.href = url;
		link.download = filename || 'output.wav';
		link.dispatchEvent(clickEvent);
	}
	*/

	window.Recorder = Recorder;

})(window);
<|endoftext|>


<commit_before>/**!
 * red dwarf v1.0.5
 * https://github.com/rviscomi/red-dwarf
 *
 * Copyright 2012 Rick Viscomi
 * Released under the MIT License.
 *
 * Date: October 12, 2012
 */

(function () {
	'use strict';

	function RedDwarf(options) {
		/* Validate options. */
		if (!options.user ||
				!options.repository ||
				!options.map_id) {
			throw 'RedDwarf requires an object with properties "user", "repository", and "map_id".';
		}

		/* Require jQuery. */
		if (!jQuery) {
			throw 'RedDwarf requires jQuery.';
		}

		/* Require JSON. */
		if (!JSON) {
			throw 'RedDwarf requires JSON. Check your browser support.';
		}

		this.user = options.user;
		this.repository = options.repository;
		this.map_id = options.map_id;
		this.cache_location = options.cache_location || '';
		this.num_stargazers = 0;
		this.num_cached_stargazers = 0;
		this.stargazers = {};
		this.points = [];
		this.locations = [];
		this.geocodes = {};

		/* Require Google Maps API. */
		if (this.hasMapsAPI()) {
			this.drawMap(options.map_zoom, options.map_lat, options.map_lng, options.map_type);
		}
		else {
			throw 'RedDwarf requires the Google Maps JavaScript API.';
		}

		/* Event hooks. */
		this.onRepositoryError = options.onRepositoryError || function () {};
		this.onRepositoryLoaded = options.onRepositoryLoaded || function () {};
		this.onCacheLoaded = options.onCacheLoaded || function () {};
		this.onStargazersUpdated = options.onStargazersUpdated || function () {};
		this.onStargazersLoaded = options.onStargazersLoaded || function () {};
		this.onLocationUpdated = options.onLocationUpdated || function () {};
		this.onLocationLoaded = options.onLocationLoaded || function () {};
		this.onPointsUpdated = options.onPointsUpdated || function () {};
		this.onPointsLoaded = options.onPointsLoaded || function () {};

		this.start();
	}

	RedDwarf.prototype.start = function () {
		var cache_loaded = false,
			repo_loaded = false,
			that = this;

		/* Load the JSON cache file. */
		if (this.cache_location) {
			this.loadCache(function () {
				/* After cache load and repo load are completed. */
				if (repo_loaded) {
					that.onInitialized();
				}

				cache_loaded = true;
			});
		}
		else {
			cache_loaded = true;
		}

		/* Load the repository info.*/
		this.loadRepository(function () {
			/* After cache load and repo load are completed. */
			if (cache_loaded) {
				that.onInitialized();
			}

			repo_loaded = true;
		});
	};

	RedDwarf.prototype.loadCache = function (onComplete) {
		var that = this;

		$.ajax({
			url: this.cache_location,
			dataType: 'json',
			success: function (data) {
				var location = null,
					geocode;

				if (data) {
					that.num_cached_stargazers = data.num_stargazers || 0;
					that.stargazers = data.stargazers || {};

					/* Convert geocodes to pairs of locations to LatLngs. */
					for (location in data.geocodes) {
						if (data.geocodes.hasOwnProperty(location)) {
							geocode = data.geocodes[location];
							that.geocodes[location] = geocode;
							that.points.push(new google.maps.LatLng(geocode.Xa, geocode.Ya));
						}
					}
				}

				that.onCacheLoaded();
			},
			error: function () {
				/* Show no errors. */
				return false;
			},
			complete: onComplete
		});
	};

	RedDwarf.prototype.loadRepository = function (onComplete) {
		var that = this;

		$.ajax({
			url: 'https://api.github.com/repos/' + this.user + '/' +
					this.repository,
			dataType: 'jsonp',
			success: function (data) {
				if (data.meta.status === 200) {
					that.num_stargazers = data.data.watchers; // TODO data.stargazers?

					that.onRepositoryLoaded(data.data);
				}
				else {
					that.onRepositoryError(data.data.message);
				}
			},
			complete: onComplete
		});
	};

	RedDwarf.prototype.onInitialized = function () {
		if (this.num_stargazers <= this.num_cached_stargazers) {
			this.drawHeatmap();
		}
		else {
			this.appendStargazers();
		}
	};

	RedDwarf.prototype.appendStargazers = function () {
		var that = this,
			page = Math.ceil(this.num_cached_stargazers / 100) || 1,
			num_stargazers = this.num_cached_stargazers,
			stargazers = [];

		function loadStargazers() {
			$.ajax({
				url: 'https://api.github.com/repos/' + that.user + '/' +
						that.repository + '/stargazers',
				data: {
					page: page,
					per_page: 100
				},
				dataType: 'jsonp',
				success: function (data, textStatus, xhr) {
					var i, n, stargazer;

					if (textStatus === 'success' && data.meta.status === 200) {
						n = data.data.length;

						/* Add new stargazers to instance property. */
						for (i = 0; i < n; i++) {
							stargazer = data.data[i];

							/* If this stargazer is not in cache, do lookup. */
							if (!that.stargazers[stargazer.login]) {
								that.stargazers[stargazer.login] = {
									login: stargazer.login,
									url: stargazer.url
								};

								stargazers.push({
									login: stargazer.login,
									url: stargazer.url
								});

								num_stargazers++;
							}
						}

						that.onStargazersUpdated(num_stargazers);
					}

					if (num_stargazers < that.num_stargazers) {
						page++;
						loadStargazers();
					}
					else {
						that.onStargazersLoaded();

						that.getStargazersLocation(stargazers);
					}
				}
			});
		}

		loadStargazers();
	};

	RedDwarf.prototype.getStargazersLocation = function (stargazers) {
		var that = this,
			n = stargazers.length,
			num_resolved_stargazers = 0;

		timedChunk(stargazers, function (stargazer) {
			/* Query the GitHub API for stargazer profile. */
			$.ajax({
				url: stargazer.url,
				dataType: 'jsonp',
				success: function (data, textStatus, xhr) {
					var location = '';

					if (textStatus === 'success' && data.meta.status === 200) {
						location = data.data.location;
					}

					if (location && !that.geocodes[location]) {
						that.locations.push(location);
					}
				},
				error: function () {
					console.warn('Error loading', stargazer.login);
				},
				complete: function () {
					num_resolved_stargazers++;

					that.onLocationUpdated(num_resolved_stargazers, n);

					if (num_resolved_stargazers === n) {
						that.onLocationLoaded();

						that.getGeoCoordinates();
					}
				}
			});
		}, null, function () {}, 7e2, 1); // load one every 700 ms

		if (!n) {
			that.onLocationLoaded();

			that.getGeoCoordinates();
		}
	};

	RedDwarf.prototype.getGeoCoordinates = function () {
		var that = this,
			geo = new google.maps.Geocoder(),
			OK = google.maps.GeocoderStatus.OK,
			locations = this.locations,
			n = locations.length;

		/* Delay to avoid maxing out Google API. */
		timedChunk(locations, function (location) {
			geo.geocode({address: location}, function (results, status) {
				if (status === OK) {
					that.points.push(results[0].geometry.location);
					that.geocodes[location] = results[0].geometry.location;
				}

				that.onPointsUpdated(that.points.length, that.num_stargazers);

				/* After all locations have been converted. */
				if (!--n) {
					that.drawHeatmap();
				}
			});
		}, this, function () {}, 15e3, 10);

		/* Passing an empty callback function to timedChunk because the */
		/* process function in turn makes an async request. So the callback */
		/* may be invoked before the async request of the final process. */
		/* Do nothing on callback, let the process function handle next step. */

		if (!locations.length) {
			that.drawHeatmap();
		}
	};

	RedDwarf.prototype.drawMap = function (zoom, lat, lng, type) {
		zoom = zoom || 2;
		lat = lat || 20;
		lng = lng || 0;
		type = type || google.maps.MapTypeId.ROADMAP;

		this.map = new google.maps.Map(document.getElementById(this.map_id), {
			zoom: zoom,
			center: new google.maps.LatLng(lat, lng),
			mapTypeId: type
		});
	};

	RedDwarf.prototype.drawHeatmap = function () {
		new google.maps.visualization.HeatmapLayer({
			data: this.points,
			map: this.map,
			radius: 25
		});

		this.onPointsLoaded();
	};

	RedDwarf.prototype.hasMapsAPI = function () {
		/* Check all Google API dependencies. */
		return (google &&
				google.maps &&
				google.maps.Map &&
				google.maps.MapTypeId &&
				google.maps.MapTypeId.ROADMAP &&
				google.maps.Geocoder &&
				google.maps.GeocoderStatus &&
				google.maps.GeocoderStatus.OK &&
				google.maps.LatLng &&
				google.maps.visualization &&
				google.maps.visualization.HeatmapLayer);
	};

	RedDwarf.prototype.toJSON = function () {
		return JSON.stringify({
			num_stargazers: this.num_stargazers,
			stargazers: this.stargazers,
			geocodes: this.geocodes
		});
	};

	/*! Copyright 2009 Nicholas C. Zakas. All rights reserved. MIT Licensed */
	function timedChunk(items, process, context, callback, delay, maxItems) {
		var n = items.length,
			delay = delay || 25,
			maxItems = maxItems || n,
			i = 0;

		setTimeout(function chunkTimer(){
			var start = +new Date(),
				j = i;

			while (i < n && (i - j) < maxItems && (new Date() - start < 50)) {
				process.call(context, items[i]);
				i += 1;
			}

			if (i < n) {
				setTimeout(chunkTimer, delay);
			}
			else {
				callback.call(context, items);
			}
		}, 25);
	}

	/* Expose as a global object. */
	window.RedDwarf = RedDwarf;
})();<commit_msg>2.0<commit_after>/**!
 * red dwarf v2.0
 * https://github.com/rviscomi/red-dwarf
 *
 * Copyright 2012 Rick Viscomi
 * Released under the MIT License.
 *
 * Date: October 18, 2012
 */

(function () {
	'use strict';

	var STATUS_OK = '200 OK';

	function RedDwarf(options) {
		var noop;

		/* Validate options. */
		if (!options.user ||
				!options.repository ||
				!options.map_id) {
			throw 'RedDwarf requires an object with properties "user", "repository", and "map_id".';
		}

		/* Require jQuery. */
		if (!jQuery) {
			throw 'RedDwarf requires jQuery.';
		}

		/* Require JSON. */
		if (!JSON) {
			throw 'RedDwarf requires JSON. Check your browser support.';
		}

		this.user = options.user;
		this.repository = options.repository;
		this.map_id = options.map_id;
		this.cache_location = options.cache_location || '';
		this.api_location = options.api_location || 'https://api.github.com/';
		this.num_stargazers = 0;
		this.num_cached_stargazers = 0;
		this.stargazers = {};
		this.points = [];
		this.locations = [];
		this.geocodes = {};

		/* Require Google Maps API. */
		if (this.hasMapsAPI()) {
			this.drawMap(options.map_zoom, options.map_lat, options.map_lng, options.map_type);
		}
		else {
			throw 'RedDwarf requires the Google Maps JavaScript API.';
		}

		/* Event hooks. */
		noop = function () {};
		this.onRepositoryError = options.onRepositoryError || noop;
		this.onRepositoryLoaded = options.onRepositoryLoaded || noop;
		this.onCacheLoaded = options.onCacheLoaded || noop;
		this.onStargazersUpdated = options.onStargazersUpdated || noop;
		this.onStargazersLoaded = options.onStargazersLoaded || noop;
		this.onLocationUpdated = options.onLocationUpdated || noop;
		this.onLocationLoaded = options.onLocationLoaded || noop;
		this.onPointsUpdated = options.onPointsUpdated || noop;
		this.onPointsLoaded = options.onPointsLoaded || noop;

		this.start();
	}

	RedDwarf.prototype.start = function () {
		var cache_loaded = false,
			repo_loaded = false,
			that = this;

		/* Load the JSON cache file. */
		if (this.cache_location) {
			this.loadCache(function () {
				/* After cache load and repo load are completed. */
				if (repo_loaded) {
					that.onInitialized();
				}

				cache_loaded = true;
			});
		}
		else {
			cache_loaded = true;
		}

		/* Load the repository info.*/
		this.loadRepository(function () {
			/* After cache load and repo load are completed. */
			if (cache_loaded) {
				that.onInitialized();
			}

			repo_loaded = true;
		});
	};

	RedDwarf.prototype.loadCache = function (onComplete) {
		var that = this;

		$.ajax({
			url: this.cache_location,
			dataType: 'json',
			success: function (data) {
				var location = null,
					geocode;

				if (data) {
					that.num_cached_stargazers = data.num_stargazers || 0;
					that.stargazers = data.stargazers || {};

					/* Convert geocodes to pairs of locations to LatLngs. */
					for (location in data.geocodes) {
						if (data.geocodes.hasOwnProperty(location)) {
							geocode = data.geocodes[location];

							if (geocode.latitude && geocode.longitude) {
								that.geocodes[location] = geocode;
								that.points.push(new google.maps.LatLng(geocode.latitude, geocode.longitude));
							}
							else if (geocode.Xa) {
								that.geocodes[location] = {latitude: geocode.Xa, longitude: geocode.Ya};
								that.points.push(new google.maps.LatLng(geocode.Xa, geocode.Ya));
							}
							else if (geocode.Za) { // ughh i hate this code FIXME
								that.geocodes[location] = {latitude: geocode.Ya, longitude: geocode.Za};
								that.points.push(new google.maps.LatLng(geocode.Ya, geocode.Za));
							}
							else if (geocode.$a) { // FIXME
								that.geocodes[location] = {latitude: geocode.$a, longitude: geocode.ab};
								that.points.push(new google.maps.LatLng(geocode.$a, geocode.ab));
							}
						}
					}
				}

				that.onCacheLoaded();
			},
			error: function () {
				/* Show no errors. */
				return false;
			},
			complete: onComplete
		});
	};

	RedDwarf.prototype.loadRepository = function (onComplete) {
		var that = this;

		$.ajax({
			url: this.api_location + 'repos/' + this.user + '/' +
					this.repository,
			dataType: 'json',
			success: function (data) {
				if (data.status === STATUS_OK) {
					that.num_stargazers = data.data.watchers; // TODO data.stargazers?

					that.onRepositoryLoaded(data.data);
				}
				else {
					that.onRepositoryError(data.data.message);
				}
			},
			complete: onComplete
		});
	};

	RedDwarf.prototype.onInitialized = function () {
		if (this.num_stargazers <= this.num_cached_stargazers) {
			this.drawHeatmap(this.onPointsLoaded);
		}
		else {
			if (this.points.length > 0) {
				/* Draw what we have so far */
				this.drawHeatmap();
			}

			this.appendStargazers();
		}
	};

	RedDwarf.prototype.appendStargazers = function () {
		var that = this,
			page = Math.ceil(this.num_cached_stargazers / 100) || 1,
			num_stargazers = this.num_cached_stargazers,
			stargazers = [];

		function loadStargazers() {
			$.ajax({
				url: that.api_location + 'users/' + that.user + '/' +
						that.repository,
				data: {
					page: page,
					per_page: 100
				},
				dataType: 'json',
				success: function (data, textStatus, xhr) {
					var n = 0, i, stargazer;

					if (textStatus === 'success' && data.status === STATUS_OK) {
						n = data.data.length;

						/* Add new stargazers to instance property. */
						for (i = 0; i < n; i++) {
							stargazer = data.data[i];

							/* If this stargazer is not in cache, do lookup. */
							if (!that.stargazers[stargazer.login]) {
								that.stargazers[stargazer.login] = {
									login: stargazer.login,
									url: stargazer.url
								};

								stargazers.push(stargazer.login);

								num_stargazers++;
							}
						}

						that.onStargazersUpdated(num_stargazers);
					}

					if (num_stargazers < that.num_stargazers && n) {
						page++;

						/* Wait 400 ms. */
						setTimeout(loadStargazers, 2e2);
					}
					else {
						that.onStargazersLoaded();

						that.getStargazersLocation(stargazers);
					}
				}
			});
		}

		loadStargazers();
	};

	RedDwarf.prototype.getStargazersLocation = function (stargazers) {
		var that = this,
			n = stargazers.length,
			num_resolved_stargazers = 0;

		timedChunk(stargazers, function (stargazer) {
			/* Query the GitHub API for stargazer profile. */
			$.ajax({
				url: that.api_location + 'users/' + stargazer,
				dataType: 'json',
				success: function (data, textStatus, xhr) {
					var location = '',
						geocode;

					if (textStatus === 'success' && data.status === STATUS_OK) {
						location = data.data.location;
					}

					if (location && !that.geocodes[location]) {
						that.locations.push(location);
					}
					else if (location) {
						/* If it's in cache, add this instance. */
						geocode = that.geocodes[location];
						that.points.push(new google.maps.LatLng(geocode.latitude, geocode.longitude));
					}
				},
				error: function () {
					console.warn('Error loading', stargazer);
				},
				complete: function () {
					num_resolved_stargazers++;

					that.onLocationUpdated(num_resolved_stargazers, n);

					if (num_resolved_stargazers === n) {
						that.onLocationLoaded();

						that.getGeoCoordinates();
					}
				}
			});
		}, null, function () {}, 4e2, 1); // load 2.5 per second

		if (!n) {
			that.onLocationLoaded();

			that.getGeoCoordinates();
		}
	};

	RedDwarf.prototype.getGeoCoordinates = function () {
		var that = this,
			geo = new google.maps.Geocoder(),
			OK = google.maps.GeocoderStatus.OK,
			locations = this.locations,
			n = locations.length;

		/* Delay to avoid maxing out Google API. */
		timedChunk(locations, function (location) {
			geo.geocode({address: location}, function (results, status) {
				if (status === OK) {
					that.points.push(results[0].geometry.location);
					that.geocodes[location] = results[0].geometry.location;
				}

				that.onPointsUpdated(that.points.length, that.num_stargazers);

				/* After all locations have been converted. */
				if (!--n) {
					that.drawHeatmap(that.onPointsLoaded);
				}
				else {
					that.drawHeatmap();
				}
			});
		}, this, function () {}, 15e2, 1);

		/* Passing an empty callback function to timedChunk because the */
		/* process function in turn makes an async request. So the callback */
		/* may be invoked before the async request of the final process. */
		/* Do nothing on callback, let the process function handle next step. */

		if (!locations.length) {
			that.drawHeatmap(this.onPointsLoaded);
		}
	};

	RedDwarf.prototype.drawMap = function (zoom, lat, lng, type, styles) {
		zoom = zoom || 2;
		lat = lat || 20;
		lng = lng || 0;
		type = type || google.maps.MapTypeId.ROADMAP;
		styles = styles || [];

		this.map = this.map || new google.maps.Map(document.getElementById(this.map_id));

		this.map.setOptions({
			zoom: zoom,
			center: new google.maps.LatLng(lat, lng),
			mapTypeId: type,
			styles: styles
		});

		this.map_layer = this.map_layer || new google.maps.visualization.HeatmapLayer({
			map: this.map,
			radius: 25
		});
	};

	RedDwarf.prototype.drawHeatmap = function (callback) {
		this.map_layer.setData(this.points);

		if (callback) {
			callback();
		}
	};

	RedDwarf.prototype.hasMapsAPI = function () {
		/* Check all Google API dependencies. */
		return (google &&
				google.maps &&
				google.maps.Map &&
				google.maps.MapTypeId &&
				google.maps.MapTypeId.ROADMAP &&
				google.maps.Geocoder &&
				google.maps.GeocoderStatus &&
				google.maps.GeocoderStatus.OK &&
				google.maps.LatLng &&
				google.maps.visualization &&
				google.maps.visualization.HeatmapLayer);
	};

	RedDwarf.prototype.toJSON = function () {
		return JSON.stringify({
			num_stargazers: this.num_stargazers,
			stargazers: this.stargazers,
			geocodes: this.geocodes
		});
	};

	/*! Copyright 2009 Nicholas C. Zakas. All rights reserved. MIT Licensed */
	function timedChunk(items, process, context, callback, delay, maxItems) {
		var n = items.length,
			delay = delay || 25,
			maxItems = maxItems || n,
			i = 0;

		setTimeout(function chunkTimer(){
			var start = +new Date(),
				j = i;

			while (i < n && (i - j) < maxItems && (new Date() - start < 50)) {
				process.call(context, items[i]);
				i += 1;
			}

			if (i < n) {
				setTimeout(chunkTimer, delay);
			}
			else {
				callback.call(context, items);
			}
		}, 25);
	}

	/* Expose as a global object. */
	window.RedDwarf = RedDwarf;
})();<|endoftext|>


<commit_before>/**
 * @package @cyrhla/tester
 * @license MIT
 * @copyright Krystian Pietruszka <kpietru@cyrhla.com>
 * @see http://www.cyrhla.com
 */

'use strict'

const InvalidTypeError = require('@cyrhla/tester/Error/InvalidTypeError')

/**
 * The Mocker generates a mock object is a simulated object
 * that mimics the behavior of a real object in controlled ways.
 *
 * @author Krystian Pietruszka <kpietru@cyrhla.com>
 */
module.exports = class Mocker
{
    /**
     * Initializes this class with the given options.
     *
     * @param function baseClass
     * @param mixed[]  args      Default empty array
     *
     * @throws InvalidTypeError
     */
    constructor(baseClass, args = [])
    {
        if (typeof baseClass !== 'function') {
            throw new InvalidTypeError('@param baseClass invalid type, must be a function.')
        }
        if (!Array.isArray(args)) {
            throw new InvalidTypeError('@param args invalid type, must be an array.')
        }

        /** @type function */
        this._baseClass = baseClass

        /** @type array */
        this._arguments = args

        /** @type object */
        this._properties = {}

        /** @type object */
        this._methods = {}
    }

    /**
     * Sets the property.
     *
     * @param mixed key
     * @param mixed value
     *
     * @return self The invoked object.
     */
    setProperty(key, value)
    {
        this._properties[key] = value

        return this
    }

    /**
     * Sets the method.
     *
     * @param mixed    key
     * @param function callback
     *
     * @return self The invoked object.
     *
     * @throws InvalidTypeError
     */
    setMethod(key, callback)
    {
        if (typeof callback !== 'function') {
            throw new InvalidTypeError('@param callback invalid type, must be a function.')
        }

        this._methods[key] = callback

        return this
    }

    /**
     * Compiles a new class.
     *
     * @return object
     */
    compile()
    {
        var newClass = new (this._baseClass.bind.apply(this._baseClass, this._arguments))

        for (let key in this._properties) {
            var obj = {}
            obj[key] = this._properties[key]
            newClass = Object.assign(newClass, obj)
        }

        for (let key in this._methods) {
            var obj = {}
            obj[key] = this._methods[key]
            newClass = Object.assign(newClass, obj)
        }

        return newClass
    }
}
<commit_msg>Update Mocker.js<commit_after>/**
 * @package @cyrhla/tester
 * @license MIT
 * @copyright Krystian Pietruszka <kpietru@cyrhla.com>
 * @see http://www.cyrhla.com
 */

'use strict'

const InvalidTypeError = require('@cyrhla/tester/Error/InvalidTypeError')

/**
 * The Mocker generates a mock object is a simulated object
 * that mimics the behavior of a real object in controlled ways.
 *
 * @author Krystian Pietruszka <kpietru@cyrhla.com>
 */
module.exports = class Mocker
{
    /**
     * Initializes this class with the given options.
     *
     * @param function baseClass
     * @param mixed[]  args      Default empty array
     *
     * @throws InvalidTypeError
     */
    constructor(baseClass, args = [])
    {
        if (typeof baseClass !== 'function') {
            throw new InvalidTypeError('@param baseClass invalid type, must be a function.')
        }
        if (!Array.isArray(args)) {
            throw new InvalidTypeError('@param args invalid type, must be an array.')
        }

        /** @type function */
        this._baseClass = baseClass

        /** @type array */
        this._arguments = args

        /** @type object */
        this._properties = {}

        /** @type object */
        this._methods = {}
    }

    /**
     * Sets the property.
     *
     * @param mixed key
     * @param mixed value
     *
     * @return self The invoked object.
     */
    setProperty(key, value)
    {
        this._properties[key] = value

        return this
    }

    /**
     * Sets the method.
     *
     * @param mixed    key
     * @param function callback
     *
     * @return self The invoked object.
     *
     * @throws InvalidTypeError
     */
    setMethod(key, callback)
    {
        if (typeof callback !== 'function') {
            throw new InvalidTypeError('@param callback invalid type, must be a function.')
        }

        this._methods[key] = callback

        return this
    }

    /**
     * Compiles a new class.
     *
     * @return object
     */
    compile()
    {
        this._arguments.unshift(this._baseClass)
        var newClass = new (this._baseClass.bind.apply(this._baseClass, this._arguments))

        for (let key in this._properties) {
            var obj = {}
            obj[key] = this._properties[key]
            newClass = Object.assign(newClass, obj)
        }

        for (let key in this._methods) {
            var obj = {}
            obj[key] = this._methods[key]
            newClass = Object.assign(newClass, obj)
        }

        return newClass
    }
}
<|endoftext|>