<commit_before>"use strict";

let events = require( 'events' );
let jsonGate = require( 'json-gate' );
let mqtt = require( 'mqtt' );

let pem = require( './pem.js' );
let time = require( './time.js' );
let Source = require( './source.js' );
let Sink = require( './sink.js' );

let optionSchema = jsonGate.createSchema( {
	type: 'object',
	properties: {
		cert: {
			type: 'string',
			required: true
		},
		key: {
			type: 'string',
			required: true
		},
		ca: {
			type: 'string',
			required: true
		},
		broker: {
			type: 'string',
			required: true
		},
		prefix: {
			type: 'string',
			default: ''
		},
		ignoreTimedrift: {
			type: 'boolean',
			default: false
		},
		rejectUnauthorized: {
			type: 'boolean',
			default: true
		},
	},
	additionalProperties: false
} );

class EndocrineSystem extends events.EventEmitter {

	constructor( options ) {

		super();

		// Make sure all required options are stated
		if( options === undefined ) options = {};
		optionSchema.validate( options );

		// Extract URL
		let url = options.broker;
		delete options.broker;

		// Store option data
		this._key = options.key;
		this._cert = options.cert;
		this._ca = options.ca;
		this._prefix = options.prefix;
		this._ignoreTimedirft = options.ignoreTimedrift;

		// Prepare some stores
		this._online = false;
		this._subscriptions = {};
		this._glands = [];
		this._receptors = [];

		// Test all provided PKI data
		this._brokerPromise = Promise.all( [
			pem.getModulus( options.key ),
			pem.getModulus( options.cert ),
			pem.verifySigningChain( options.cert, options.ca )
		] ).then( ( result ) => {

			// Do key and cert match?
			if( result[0].modulus != result[1].modulus ) {
				throw new Error( "Key and certificate do not match" );
			}

			// Is cert signed by CA?
			if( ! result[2] ) {
				throw new Error( "Certificate has not been signed by CA" );
			}

			// Check the local time drift
			if( this._ignoreTimedirft ) return 0.0;
			else return time.getDrift();

		} ).then( ( drift ) => {

			// Make sure the drift is small enough
			if( drift < 0.0 ) drift *= -1;
			if( drift > 10000.0 ) {
				throw new Error( "The time drift is too large" );
			}

			// Connect to MQTT
			options.clean = true;
			let broker = mqtt.connect( url, options );

			// Set event handlers:
			// - Passthrough error events
			broker.on( 'error', ( err ) => {
				this.emit( 'error', err );
			} );
			// - Messages
			broker.on( 'message', ( topic, message ) => {
				// Find receipient
				for( let s in this._subscriptions ) {
					let sub = this._subscriptions[ s ];

					// Call handler if topic matches
					if( sub.topicRE.test( topic ) ) sub.handler( topic, message.toString() );
				}
			} );
			// - Connection
			//   The connect event may occurs several times, if the connection get
			//   get lost and the reconnection process cannot rerecognise this client
			broker.on( 'connect', () => {
				// Resubscribe topics
				for( let topic in this._subscriptions ) {
					broker.subscribe( topic, { qos: 1 } );
				}
				// Store the current state
				this._online = true;
				// Tell the user we are online
				this.emit( 'online' );
			} );
			// - Reconnect
			//   Try to reconnect to the broker
			broker.on( 'reconnect', () => {} );
			// - Close
			//   Fired if we lose connection to the broker
			broker.on( 'close', () => {
				// We are offline now
				if( this._online ) {
					// If we weren't offline before, tell the user
					this._online = false;
					this.emit( 'offline' );
				}
			} );
			// - Disconnect
			broker.on( 'offline', () => {} );

			// Wait for the first connection
			return new Promise( ( resolve, reject ) => {
				broker.once( 'connect', () => resolve( broker ) );
			} );

		} );

	}

	_publish( topic, message ) {

		// Get the broker handle
		return this._brokerPromise.then( ( broker ) => {

			return new Promise( ( resolve, reject ) => {
				// TODO: If the broker rejects the message, he will simply disconnect
				broker.publish(
					topic,
					message,
					{ qos: 1, retain: true },
					resolve
				);
			} );

		} );

	}

	_subscribe( topic, handler ) {

		// Get the broker handle
		return this._brokerPromise.then( ( broker ) => {

			// Do we know any subscriptions related to the topic
			if( this._subscriptions[ topic ] ) return Promise.reject(
				new Error( "Subscription duplicate" )
			);

			return new Promise( ( resolve, reject ) => {
				broker.subscribe( topic, { qos: 1 }, ( err ) => {
					if( err ) return reject( err );

					// Generate new regular expression
					let topicRE = topic.replace( /\+/g, '[^/]*' ).replace( /#/g, '.*' );

					// Store regular expression and handler
					this._subscriptions[ topic ] = {
						topicRE: new RegExp( '^' + topicRE + '$' ),
						handler: handler
					};

					resolve();

				} );
			} );

		} );

	}

	_unsubscribe( topic ) {

		// Get the broker handle
		return this._brokerPromise.then( ( broker ) => {

			// Do we know any subscriptions related to the topic
			if( ! this._subscriptions[ topic ] ) return Promise.reject(
				new Error( "Unknown subscription" )
			);

			return new Promise( ( resolve, reject ) => {
				broker.unsubscribe( topic, () => {
					// Remove subscription handler
					delete this._subscriptions[ topic ];
					resolve();
				} );
			} );

		} );

	}

	get online() { return this._online; }

	newGland( name, definition ) {

		let gland = new Source( this, this._prefix + name, definition );
		// Passthrough errors
		gland.on( 'error', ( err ) => {
			this.emit( 'error', err );
		} );
		this._glands.push( gland );
		return gland;

	}

	newReceptor( filter, certCheck ) {

		let receptor = new Sink( this, filter, certCheck );
		// Passthrough errors
		receptor.on( 'error', ( err ) => {
			this.emit( 'error', err );
		} );
		this._receptors.push( receptor );
		return receptor;

	}

	destroy() {

		let jobs = [];

		// Destory all glands and receptors
		this._glands.forEach( ( gland ) => {
			jobs.push( gland.destroy() );
		} );
		this._receptors.forEach( ( receptor ) => {
			jobs.push( receptor.destroy() );
		} );

		// Wait for all josb to be finished
		return Promise.all( jobs ).then( () => {
			return this._brokerPromise;
		} ).then( ( broker ) => {
			// Close connection to broker
			new Promise( ( resolve, reject ) => {
				broker.end( () => resolve() );
			} );
		} );

	}

}

module.exports = EndocrineSystem;
<commit_msg>First draft of mDNS broker discovery<commit_after>"use strict";

let events = require( 'events' );
let jsonGate = require( 'json-gate' );
let mqtt = require( 'mqtt' );

let pem = require( './pem.js' );
let discover = require( './discover.js' );
let time = require( './time.js' );
let Source = require( './source.js' );
let Sink = require( './sink.js' );

let optionSchema = jsonGate.createSchema( {
	type: 'object',
	properties: {
		cert: {
			type: 'string',
			required: true
		},
		key: {
			type: 'string',
			required: true
		},
		ca: {
			type: 'string',
			required: true
		},
		broker: {
			type: 'string',
			required: false
		},
		prefix: {
			type: 'string',
			default: ''
		},
		ignoreTimedrift: {
			type: 'boolean',
			default: false
		},
		rejectUnauthorized: {
			type: 'boolean',
			default: true
		},
	},
	additionalProperties: false
} );

class EndocrineSystem extends events.EventEmitter {

	constructor( options ) {

		super();

		// Make sure all required options are stated
		if( options === undefined ) options = {};
		optionSchema.validate( options );

		// Extract URL
		let url = options.broker || null;
		delete options.broker;

		// Store option data
		this._key = options.key;
		this._cert = options.cert;
		this._ca = options.ca;
		this._prefix = options.prefix;
		this._ignoreTimedirft = options.ignoreTimedrift;

		// Prepare some stores
		this._online = false;
		this._subscriptions = {};
		this._glands = [];
		this._receptors = [];

		// Test all provided PKI data
		this._brokerPromise = Promise.all( [
			pem.getModulus( options.key ),
			pem.getModulus( options.cert ),
			pem.verifySigningChain( options.cert, options.ca ),
			pem.getFingerprint( options.ca )
		] ).then( ( result ) => {

			// Do key and cert match?
			if( result[0].modulus != result[1].modulus ) {
				throw new Error( "Key and certificate do not match" );
			}

			// Is cert signed by CA?
			if( ! result[2] ) {
				throw new Error( "Certificate has not been signed by CA" );
			}

			if( typeof url == 'string' ) return;

			// If no broker has been give search in the local network via mdns
			let service = 'es-' + result[ 3 ].replace( /\:/g, '' ).substr( 0, 7 );
			return discover.mDNS( service ).then( ( server ) => {
				url = 'mqtts://' + server.host + ':' + server.port.toString();
			} );

		} ).then( () => {

			// Check the local time drift
			if( this._ignoreTimedirft ) return 0.0;
			else return time.getDrift();

		} ).then( ( drift ) => {

			// Make sure the drift is small enough
			if( drift < 0.0 ) drift *= -1;
			if( drift > 10000.0 ) {
				throw new Error( "The time drift is too large" );
			}

			// Connect to MQTT
			options.clean = true;
			options.checkServerIdentity = function() { return; };
			let broker = mqtt.connect( url, options );

			// Set event handlers:
			// - Passthrough error events
			broker.on( 'error', ( err ) => {
				this.emit( 'error', err );
			} );
			// - Messages
			broker.on( 'message', ( topic, message ) => {
				// Find receipient
				for( let s in this._subscriptions ) {
					let sub = this._subscriptions[ s ];

					// Call handler if topic matches
					if( sub.topicRE.test( topic ) ) sub.handler( topic, message.toString() );
				}
			} );
			// - Connection
			//   The connect event may occurs several times, if the connection get
			//   get lost and the reconnection process cannot rerecognise this client
			broker.on( 'connect', () => {
				// Resubscribe topics
				for( let topic in this._subscriptions ) {
					broker.subscribe( topic, { qos: 1 } );
				}
				// Store the current state
				this._online = true;
				// Tell the user we are online
				this.emit( 'online' );
			} );
			// - Reconnect
			//   Try to reconnect to the broker
			broker.on( 'reconnect', () => {} );
			// - Close
			//   Fired if we lose connection to the broker
			broker.on( 'close', () => {
				// We are offline now
				if( this._online ) {
					// If we weren't offline before, tell the user
					this._online = false;
					this.emit( 'offline' );
				}
			} );
			// - Disconnect
			broker.on( 'offline', () => {} );

			// Wait for the first connection
			return new Promise( ( resolve, reject ) => {
				broker.once( 'connect', () => resolve( broker ) );
			} );

		} );

	}

	_publish( topic, message ) {

		// Get the broker handle
		return this._brokerPromise.then( ( broker ) => {

			return new Promise( ( resolve, reject ) => {
				// TODO: If the broker rejects the message, he will simply disconnect
				broker.publish(
					topic,
					message,
					{ qos: 1, retain: true },
					resolve
				);
			} );

		} );

	}

	_subscribe( topic, handler ) {

		// Get the broker handle
		return this._brokerPromise.then( ( broker ) => {

			// Do we know any subscriptions related to the topic
			if( this._subscriptions[ topic ] ) return Promise.reject(
				new Error( "Subscription duplicate" )
			);

			return new Promise( ( resolve, reject ) => {
				broker.subscribe( topic, { qos: 1 }, ( err ) => {
					if( err ) return reject( err );

					// Generate new regular expression
					let topicRE = topic.replace( /\+/g, '[^/]*' ).replace( /#/g, '.*' );

					// Store regular expression and handler
					this._subscriptions[ topic ] = {
						topicRE: new RegExp( '^' + topicRE + '$' ),
						handler: handler
					};

					resolve();

				} );
			} );

		} );

	}

	_unsubscribe( topic ) {

		// Get the broker handle
		return this._brokerPromise.then( ( broker ) => {

			// Do we know any subscriptions related to the topic
			if( ! this._subscriptions[ topic ] ) return Promise.reject(
				new Error( "Unknown subscription" )
			);

			return new Promise( ( resolve, reject ) => {
				broker.unsubscribe( topic, () => {
					// Remove subscription handler
					delete this._subscriptions[ topic ];
					resolve();
				} );
			} );

		} );

	}

	get online() { return this._online; }

	newGland( name, definition ) {

		let gland = new Source( this, this._prefix + name, definition );
		// Passthrough errors
		gland.on( 'error', ( err ) => {
			this.emit( 'error', err );
		} );
		this._glands.push( gland );
		return gland;

	}

	newReceptor( filter, certCheck ) {

		let receptor = new Sink( this, filter, certCheck );
		// Passthrough errors
		receptor.on( 'error', ( err ) => {
			this.emit( 'error', err );
		} );
		this._receptors.push( receptor );
		return receptor;

	}

	destroy() {

		let jobs = [];

		// Destory all glands and receptors
		this._glands.forEach( ( gland ) => {
			jobs.push( gland.destroy() );
		} );
		this._receptors.forEach( ( receptor ) => {
			jobs.push( receptor.destroy() );
		} );

		// Wait for all josb to be finished
		return Promise.all( jobs ).then( () => {
			return this._brokerPromise;
		} ).then( ( broker ) => {
			// Close connection to broker
			new Promise( ( resolve, reject ) => {
				broker.end( () => resolve() );
			} );
		} );

	}

}

module.exports = EndocrineSystem;
<|endoftext|>


<commit_before>var exec = require('child_process').exec;
var Chalk = require('chalk');

// Force CLI coloring.
var chalk = new Chalk.constructor({enabled: true});

// Output styling.
var success = chalk.green;
var notice = chalk.yellow;
var whoops = chalk.bold.red; // Can't name error as exec uses error in callback.
var underline = chalk.underline;

exports.init = function() {
	exec('git init', function(error, stdout, stderr) {
		if (stdout.substring(0, 13) === 'Reinitialized') {
			console.log(notice('[~] Reinitialized existing Git repository.'));
		} else {
			console.log(success('[✔] Initialized new Git repository.'));
		}
	});
};

exports.cloneURL = function(repository) {
	// Nothing to clone.
	if (!repository) {
		console.log(notice('[~] You must enter a repository URL.'));
	} else {
		exec('git clone ' + repository, function(error, stdout, stderr) {
			if (stderr.substring(0, 7) === 'Cloning') {
				if (stderr.split('\n')[1].split(' ')[0] === 'fatal:') {
					console.log(whoops('[✖] Could not clone repository.'));
				} else {
					console.log(success('[✔] Cloned into ' + stderr.split('\n')[0].split(' ')[2]));
				}
			} else {
				if (stderr.substring(0, 23) === 'fatal: destination path') {
					console.log(notice('[~] Directory ' + stderr.split(' ')[3] + ' is not empty.'));
				} else {
					console.log(whoops('[✖] Could not clone repository.'));
				}
			}
		});
	}
};

exports.cloneGitHub = function(repository) {
	exports.cloneURL('https://github.com/' + repository + '.git');
};

exports.addAll = function() {
	exec('git add -A');

	console.log(success('[✔] Added everything!'));
};

exports.addSingle = function(target) {
	// TODO: Fix the Error handling! Currently just ignores warnings
	exec('git add ' + target, function(error, stdout, stderr) {
		if (typeof target === 'undefined') {// Then just add all.
			exports.addAll();
		} else {
			if (stderr.substring(0, 7) !== 'warning') {
				console.log(whoops('[✖] Could not add target specified. Are you sure the target exists?'));
			} else {
				console.log(success('[✔] Added ' + target + '!'));
			}
		}
	});
};

exports.addOptions = function(options, target) {
	// TODO: Fix the Error Handling! Currently just ignores warnings
	exec('git add -' + options + ' ' + target, function(error, stdout, stderr) {
		if (typeof target === 'undefined') {// Then just add all.
			exports.addAll();
		} else {
			if (stderr.substring(0, 7) !== 'warning') {
				console.log(whoops('[✖] Could not add target specified. Are you sure the target exists?'));
			} else {
				console.log(success('[✔] Added ' + target + '!'));
			}
		}
	});
};

exports.commit = function(message) {
	// No commit message.
	if (message === '' || typeof message === 'undefined') {
		console.log(notice('[~] You must enter a commit message.'));
	} else {
		exports.addAll();

		exec('git commit -m "' + message + '"', function(error, stdout, stderr) {
			if (stdout.substring(0, 1) === '[') {
				console.log(success('[✔] Committed!'));

				console.log();
				console.log('"' + underline(message) + '"');
			} else if (stdout.substring(0, 2) === 'On') {
				console.log(notice('[~] Everything already committed.'));
			} else {
				console.log(whoops('[✖] Could not commit changes.') + chalk.red(' Try again if there was a large amount of changes to add.'));
			}
		});
	}
};

exports.push = function() {
	exec('git push', function(error, stdout, stderr) {
		if (stderr.substring(0, 10) === 'Everything') {
			console.log(notice('[~] All commits already pushed.'));
		} else if (stderr.substring(0, 2) === 'To') {
			console.log(success('[✔] Pushed!'));
		} else {
			// TODO: add handling for non-fast-forwards (when you need to pull first, merge, and then push).
			console.log(whoops('[✖] Could not push commits.') + chalk.red(' (may have to pull first, merge any conflicts, and then attempt to push again using the ') + chalk.red.underline('standard git command') + chalk.red('.)'));
		}
	});
};

exports.pull = function() {
	exec('git pull', function(error, stdout, stderr) {
		if (stdout.substring(0, 7) === 'Already') {
			console.log(notice('[~] Everything is already up-to-date.'));
		} else if (stdout.substring(0, 8) === 'Updating') {
			console.log(success('[✔] Pulled!'));
		} else {
			// TODO: add handling for merge conflicts.
			console.log(whoops('[✖] Could not pull commits.') + chalk.red(' (may have to pull using the ') + chalk.red.underline('standard git command') + chalk.red(' to handle merge conflicts.)'));
		}
	});
};

exports.fetch = function() {
	exec('git fetch', function(error, stdout, stderr) {
		if (stdout === '' && stderr === '') {
			console.log(success('[✔] Fetched!'));
		} else {
			console.log(whoops('[✖] Could not fetch commits.'));
		}
	});
};

exports.fetchAll = function() {
	exec('git fetch --all', function(error, stdout, stderr) {
		if (stderr === '') {
			console.log(success('[✔] Fetched all!'));
		} else {
			console.log(whoops('[✖] Could not fetch commits.'));
		}
	});
};

exports.status = function() {
	exec('git status', function(error, stdout, stderr) {
		var status = stdout.split('\n');

		// Branch.
		var branch;
		if (status[0].split(' ')[0] === 'HEAD') { // If HEAD is at a commit.
			branch = chalk.dim('branch  ') + chalk.gray.dim(' | ') + success(status[0].split(' ')[3]);
		} else {
			branch = chalk.dim('branch  ') + chalk.gray.dim(' | ') + success(status[0].split(' ')[2]);
		}

		console.log(branch);

		// Position in branch.
		var position;
		switch (status[1].split(' ')[3]) {
			case 'behind':
				position = 'position' + chalk.gray.dim(' | ') + notice(status[1].split(' ')[7] + ' commits behind (fetch or pull)');
				break;

			case 'for': // Quick hack to get it working when changes are not staged.
			case 'working': // Quick hack to get it working with freshly initialized git repositories.
			case 'up-to-date':
				position = chalk.dim('position') + chalk.gray.dim(' | ') + success('up-to-date');
				break;

			case 'ahead':
				position = 'position' + chalk.gray.dim(' | ') + notice(status[1].split(' ')[7] + ' commits ahead (push commits)');
				break;

			default:
				// TODO: add proper handling for freshly initialized git repositories.
				if (typeof status[1].split(' ')[3] === 'undefined') { // Quick hack to get it working with freshly initialized git repositories.
					position = chalk.dim('position') + chalk.gray.dim(' | ') + success('up-to-date');
				} else if (status[1] === 'Changes to be committed:') {
					position = chalk.dim('position') + chalk.gray.dim(' | ') + success('up-to-date');
				} else {
					position = 'position' + chalk.gray.dim(' | ') + whoops('diverged');
				}
		}
		console.log(position);

		console.log();

		// Staging.
		var staging;
		if (status[2] === 'Initial commit') { // Fresh repository.
			switch (status[4]) {
				case 'Untracked files:':
					staging = 'staging ' + chalk.gray.dim(' | ') + notice('not all changes staged (add all changes)');
					break;

					case 'Changes to be committed:':
					case 'nothing to commit (create/copy files and use "git add" to track)':
						staging = chalk.dim('staging ') + chalk.gray.dim(' | ') + success('all changes staged');
						break;
			}
		} else {
			switch (status[1]) {
				case 'Changes not staged for commit:':
				case 'Untracked files:':
					staging = 'staging ' + chalk.gray.dim(' | ') + notice('not all changes staged (add all changes)');
					break;

				case 'Changes to be committed:':
				case 'nothing to commit, working directory clean':
					staging = chalk.dim('staging ') + chalk.gray.dim(' | ') + success('all changes staged');
					break;
			}
		}

		console.log(staging);

		// Commits.
		var commits;
		if (status[2] === 'Initial commit') {
			switch (status[4]) {
				case 'Changes to be committed:':
					commits = 'commits ' + chalk.gray.dim(' | ') + notice('not all changes committed (commit all changes)');
					break;

				case 'nothing to commit (create/copy files and use "git add" to track)':
					commits = chalk.dim('commits ') + chalk.gray.dim(' | ') + success('all changes committed');
					break;
			}
		} else {
			switch (status[1]) {
				case 'Changes not staged for commit:':
				case 'nothing to commit, working directory clean':
					commits = chalk.dim('commits ') + chalk.gray.dim(' | ') + success('all changes committed');
					break;

				case 'Changes to be committed:':
					commits = 'commits ' + chalk.gray.dim(' | ') + notice('not all changes committed (commit all changes)');
					break;
			}
		}

		console.log(commits);
	});
};

// https://gist.github.com/textarcana/1306223
exports.log = function() {
	exec('git log --pretty=format:\'{%n  "commit": "%H",%n  "author": "%an <%ae>",%n  "date": "%ad",%n  "message": "%f"%n},\' $@ | perl -pe \'BEGIN{print "["}; END{print "]\n"}\' | perl -pe \'s/},]/}]/\'', function(error, stdout, stderr) {
		var log = JSON.parse(stdout);
		log.reverse(); // Show newest at the bottom.

		for (var i = 0; i < log.length; i++) {
			console.log(); // New line between each commit.

			console.log(notice('[' + log[i].commit + ']'));

			var author = {
				name: log[i].author.split('<')[0].trim(),
				email: '<' + log[i].author.split('<')[1]
			};

			console.log('|- ' + chalk.gray(author.name) + " " + chalk.gray.dim(author.email));
			console.log('|- ' + chalk.gray.dim(log[i].date));
			console.log('|- ' + chalk.green(log[i].message.split('-').join(' ')));
		}
	});
};

exports.branch = function(name) {
	if (name === '' || typeof name === 'undefined') { // List branches.
		exec('git branch', function(error, stdout, stderr) {
			var branches = stdout.split('\n');
			for (var i = 0; i < branches.length - 1; i++) {
				if (branches[i].substring(0, 1) === '*') { // Current branch.
					console.log('|- ' + success.bold(branches[i].substring(2)));
				} else {
					console.log(chalk.dim('|- ' + branches[i].substring(2)));
				}
			}
		});
	} else { // Create branch and checkout.
		exec('git branch ' + name, function(error, stdout, stderr) {
			if (stderr.split(' ')[0] === 'fatal:') {
				if (stderr.substring(0, 21) === 'fatal: A branch named') { // Branch already exists.
					console.log(notice('[~] Branch \'' + name + '\' already exists.'));

					// TODO: add checkout branch here.
				} else if (stderr.slice(-24) === 'not a valid branch name.') { // Invalid branch name.
					console.log(notice('[~] \'' + name + '\' is an invalid branch name.'));
				} else { // Generic error.
					console.log(whoops('[✖] Could not create new branch \'' + name + '\'.'));
				}
			} else {
				console.log(success('[✔] Created new branch \'' + name + '\'!'));
			}
		});
	}
};

exports.checkout = function(branch) {
	if (branch === '' || typeof branch === 'undefined') { // If no branch provided, list the branches.
		exports.branch();
	} else {
		exec('git checkout ' + branch, function(error, stdout, stderr) {
			switch (stderr.split(' ')[0]) {
				case 'Already':
					console.log(notice('[~] Already on branch \'' + branch + '\'.'));
					break;

				case 'Switched':
					console.log(success('[✔] Switched to branch \'' + branch + '\'!'));
					break;

				case 'error:':
					console.log(whoops('[✖] Branch \'' + branch + '\' does not exist.'));
					break;
			}
		});
	}
};
<commit_msg>Checkout branch when you attempt to make an existing one.<commit_after>var exec = require('child_process').exec;
var Chalk = require('chalk');

// Force CLI coloring.
var chalk = new Chalk.constructor({enabled: true});

// Output styling.
var success = chalk.green;
var notice = chalk.yellow;
var whoops = chalk.bold.red; // Can't name error as exec uses error in callback.
var underline = chalk.underline;

exports.init = function() {
	exec('git init', function(error, stdout, stderr) {
		if (stdout.substring(0, 13) === 'Reinitialized') {
			console.log(notice('[~] Reinitialized existing Git repository.'));
		} else {
			console.log(success('[✔] Initialized new Git repository.'));
		}
	});
};

exports.cloneURL = function(repository) {
	// Nothing to clone.
	if (!repository) {
		console.log(notice('[~] You must enter a repository URL.'));
	} else {
		exec('git clone ' + repository, function(error, stdout, stderr) {
			if (stderr.substring(0, 7) === 'Cloning') {
				if (stderr.split('\n')[1].split(' ')[0] === 'fatal:') {
					console.log(whoops('[✖] Could not clone repository.'));
				} else {
					console.log(success('[✔] Cloned into ' + stderr.split('\n')[0].split(' ')[2]));
				}
			} else {
				if (stderr.substring(0, 23) === 'fatal: destination path') {
					console.log(notice('[~] Directory ' + stderr.split(' ')[3] + ' is not empty.'));
				} else {
					console.log(whoops('[✖] Could not clone repository.'));
				}
			}
		});
	}
};

exports.cloneGitHub = function(repository) {
	exports.cloneURL('https://github.com/' + repository + '.git');
};

exports.addAll = function() {
	exec('git add -A');

	console.log(success('[✔] Added everything!'));
};

exports.addSingle = function(target) {
	// TODO: Fix the Error handling! Currently just ignores warnings
	exec('git add ' + target, function(error, stdout, stderr) {
		if (typeof target === 'undefined') {// Then just add all.
			exports.addAll();
		} else {
			if (stderr.substring(0, 7) !== 'warning') {
				console.log(whoops('[✖] Could not add target specified. Are you sure the target exists?'));
			} else {
				console.log(success('[✔] Added ' + target + '!'));
			}
		}
	});
};

exports.addOptions = function(options, target) {
	// TODO: Fix the Error Handling! Currently just ignores warnings
	exec('git add -' + options + ' ' + target, function(error, stdout, stderr) {
		if (typeof target === 'undefined') {// Then just add all.
			exports.addAll();
		} else {
			if (stderr.substring(0, 7) !== 'warning') {
				console.log(whoops('[✖] Could not add target specified. Are you sure the target exists?'));
			} else {
				console.log(success('[✔] Added ' + target + '!'));
			}
		}
	});
};

exports.commit = function(message) {
	// No commit message.
	if (message === '' || typeof message === 'undefined') {
		console.log(notice('[~] You must enter a commit message.'));
	} else {
		exports.addAll();

		exec('git commit -m "' + message + '"', function(error, stdout, stderr) {
			if (stdout.substring(0, 1) === '[') {
				console.log(success('[✔] Committed!'));

				console.log();
				console.log('"' + underline(message) + '"');
			} else if (stdout.substring(0, 2) === 'On') {
				console.log(notice('[~] Everything already committed.'));
			} else {
				console.log(whoops('[✖] Could not commit changes.') + chalk.red(' Try again if there was a large amount of changes to add.'));
			}
		});
	}
};

exports.push = function() {
	exec('git push', function(error, stdout, stderr) {
		if (stderr.substring(0, 10) === 'Everything') {
			console.log(notice('[~] All commits already pushed.'));
		} else if (stderr.substring(0, 2) === 'To') {
			console.log(success('[✔] Pushed!'));
		} else {
			// TODO: add handling for non-fast-forwards (when you need to pull first, merge, and then push).
			console.log(whoops('[✖] Could not push commits.') + chalk.red(' (may have to pull first, merge any conflicts, and then attempt to push again using the ') + chalk.red.underline('standard git command') + chalk.red('.)'));
		}
	});
};

exports.pull = function() {
	exec('git pull', function(error, stdout, stderr) {
		if (stdout.substring(0, 7) === 'Already') {
			console.log(notice('[~] Everything is already up-to-date.'));
		} else if (stdout.substring(0, 8) === 'Updating') {
			console.log(success('[✔] Pulled!'));
		} else {
			// TODO: add handling for merge conflicts.
			console.log(whoops('[✖] Could not pull commits.') + chalk.red(' (may have to pull using the ') + chalk.red.underline('standard git command') + chalk.red(' to handle merge conflicts.)'));
		}
	});
};

exports.fetch = function() {
	exec('git fetch', function(error, stdout, stderr) {
		if (stdout === '' && stderr === '') {
			console.log(success('[✔] Fetched!'));
		} else {
			console.log(whoops('[✖] Could not fetch commits.'));
		}
	});
};

exports.fetchAll = function() {
	exec('git fetch --all', function(error, stdout, stderr) {
		if (stderr === '') {
			console.log(success('[✔] Fetched all!'));
		} else {
			console.log(whoops('[✖] Could not fetch commits.'));
		}
	});
};

exports.status = function() {
	exec('git status', function(error, stdout, stderr) {
		var status = stdout.split('\n');

		// Branch.
		var branch;
		if (status[0].split(' ')[0] === 'HEAD') { // If HEAD is at a commit.
			branch = chalk.dim('branch  ') + chalk.gray.dim(' | ') + success(status[0].split(' ')[3]);
		} else {
			branch = chalk.dim('branch  ') + chalk.gray.dim(' | ') + success(status[0].split(' ')[2]);
		}

		console.log(branch);

		// Position in branch.
		var position;
		switch (status[1].split(' ')[3]) {
			case 'behind':
				position = 'position' + chalk.gray.dim(' | ') + notice(status[1].split(' ')[7] + ' commits behind (fetch or pull)');
				break;

			case 'for': // Quick hack to get it working when changes are not staged.
			case 'working': // Quick hack to get it working with freshly initialized git repositories.
			case 'up-to-date':
				position = chalk.dim('position') + chalk.gray.dim(' | ') + success('up-to-date');
				break;

			case 'ahead':
				position = 'position' + chalk.gray.dim(' | ') + notice(status[1].split(' ')[7] + ' commits ahead (push commits)');
				break;

			default:
				// TODO: add proper handling for freshly initialized git repositories.
				if (typeof status[1].split(' ')[3] === 'undefined') { // Quick hack to get it working with freshly initialized git repositories.
					position = chalk.dim('position') + chalk.gray.dim(' | ') + success('up-to-date');
				} else if (status[1] === 'Changes to be committed:') {
					position = chalk.dim('position') + chalk.gray.dim(' | ') + success('up-to-date');
				} else {
					position = 'position' + chalk.gray.dim(' | ') + whoops('diverged');
				}
		}
		console.log(position);

		console.log();

		// Staging.
		var staging;
		if (status[2] === 'Initial commit') { // Fresh repository.
			switch (status[4]) {
				case 'Untracked files:':
					staging = 'staging ' + chalk.gray.dim(' | ') + notice('not all changes staged (add all changes)');
					break;

					case 'Changes to be committed:':
					case 'nothing to commit (create/copy files and use "git add" to track)':
						staging = chalk.dim('staging ') + chalk.gray.dim(' | ') + success('all changes staged');
						break;
			}
		} else {
			switch (status[1]) {
				case 'Changes not staged for commit:':
				case 'Untracked files:':
					staging = 'staging ' + chalk.gray.dim(' | ') + notice('not all changes staged (add all changes)');
					break;

				case 'Changes to be committed:':
				case 'nothing to commit, working directory clean':
					staging = chalk.dim('staging ') + chalk.gray.dim(' | ') + success('all changes staged');
					break;
			}
		}

		console.log(staging);

		// Commits.
		var commits;
		if (status[2] === 'Initial commit') {
			switch (status[4]) {
				case 'Changes to be committed:':
					commits = 'commits ' + chalk.gray.dim(' | ') + notice('not all changes committed (commit all changes)');
					break;

				case 'nothing to commit (create/copy files and use "git add" to track)':
					commits = chalk.dim('commits ') + chalk.gray.dim(' | ') + success('all changes committed');
					break;
			}
		} else {
			switch (status[1]) {
				case 'Changes not staged for commit:':
				case 'nothing to commit, working directory clean':
					commits = chalk.dim('commits ') + chalk.gray.dim(' | ') + success('all changes committed');
					break;

				case 'Changes to be committed:':
					commits = 'commits ' + chalk.gray.dim(' | ') + notice('not all changes committed (commit all changes)');
					break;
			}
		}

		console.log(commits);
	});
};

// https://gist.github.com/textarcana/1306223
exports.log = function() {
	exec('git log --pretty=format:\'{%n  "commit": "%H",%n  "author": "%an <%ae>",%n  "date": "%ad",%n  "message": "%f"%n},\' $@ | perl -pe \'BEGIN{print "["}; END{print "]\n"}\' | perl -pe \'s/},]/}]/\'', function(error, stdout, stderr) {
		var log = JSON.parse(stdout);
		log.reverse(); // Show newest at the bottom.

		for (var i = 0; i < log.length; i++) {
			console.log(); // New line between each commit.

			console.log(notice('[' + log[i].commit + ']'));

			var author = {
				name: log[i].author.split('<')[0].trim(),
				email: '<' + log[i].author.split('<')[1]
			};

			console.log('|- ' + chalk.gray(author.name) + " " + chalk.gray.dim(author.email));
			console.log('|- ' + chalk.gray.dim(log[i].date));
			console.log('|- ' + chalk.green(log[i].message.split('-').join(' ')));
		}
	});
};

exports.branch = function(name) {
	if (name === '' || typeof name === 'undefined') { // List branches.
		exec('git branch', function(error, stdout, stderr) {
			var branches = stdout.split('\n');
			for (var i = 0; i < branches.length - 1; i++) {
				if (branches[i].substring(0, 1) === '*') { // Current branch.
					console.log('|- ' + success.bold(branches[i].substring(2)));
				} else {
					console.log(chalk.dim('|- ' + branches[i].substring(2)));
				}
			}
		});
	} else { // Create branch and checkout.
		exec('git branch ' + name, function(error, stdout, stderr) {
			if (stderr.split(' ')[0] === 'fatal:') {
				if (stderr.substring(0, 21) === 'fatal: A branch named') { // Branch already exists.
					console.log(notice('[~] Branch \'' + name + '\' already exists.'));

					// Checkout branch.
					exports.checkout(name);
				} else if (stderr.slice(-24) === 'not a valid branch name.') { // Invalid branch name.
					console.log(notice('[~] \'' + name + '\' is an invalid branch name.'));
				} else { // Generic error.
					console.log(whoops('[✖] Could not create new branch \'' + name + '\'.'));
				}
			} else {
				console.log(success('[✔] Created new branch \'' + name + '\'!'));
			}
		});
	}
};

exports.checkout = function(branch) {
	if (branch === '' || typeof branch === 'undefined') { // If no branch provided, list the branches.
		exports.branch();
	} else {
		exec('git checkout ' + branch, function(error, stdout, stderr) {
			switch (stderr.split(' ')[0]) {
				case 'Already':
					console.log(notice('[~] Already on branch \'' + branch + '\'.'));
					break;

				case 'Switched':
					console.log(success('[✔] Switched to branch \'' + branch + '\'!'));
					break;

				case 'error:':
					console.log(whoops('[✖] Branch \'' + branch + '\' does not exist.'));
					break;
			}
		});
	}
};
<|endoftext|>


<commit_before>
// show the installed versions of packages
//
// --parseable creates output like this:
// <fullpath>:<name@ver>:<realpath>:<flags>
// Flags are a :-separated list of zero or more indicators

module.exports = exports = ls

var npm = require("../npm")
  , readInstalled = require("./utils/read-installed")
  , output = require("./utils/output")
  , log = require("./utils/log")
  , relativize = require("./utils/relativize")
  , path = require("path")

ls.usage = "npm ls"

function ls (args, cb) {
  if (args.length) {
    log.warn("ls doesn't take args. Try the 'search' command")
  }
  var dir = npm.prefix
  if (npm.config.get("global")) dir = path.resolve(dir, "lib")
  readInstalled(dir, function (er, data) {
    if (er) return cb(er)
    var long = npm.config.get("long")
    var out = makePretty(bfsify(data), long, dir).join("\n")
    output.write(out, cb)
  })
}

function bfsify (root, current, queue, seen) {
  // walk over the data, and turn it from this:
  // +-- a
  // |   `-- b
  // |       `-- a (truncated)
  // `--b (truncated)
  // into this:
  // +-- a
  // |   `-- b (truncated)
  // `-- b
  //     `-- a (truncated)
  // which looks nicer
  current = current || root
  queue = queue || []
  seen = seen || []
  var deps = current.dependencies = current.dependencies || {}
  Object.keys(deps).forEach(function (d) {
    var dep = deps[d]
    if (typeof dep !== "object") return
    if (seen.indexOf(dep) !== -1) {
      if (npm.config.get("parseable")) {
        delete deps[d]
        return
      }
      dep = deps[d] = Object.create(dep)
      dep.dependencies = {}
    }
    queue.push(dep)
    seen.push(dep)
  })
  if (!queue.length) return root
  return bfsify(root, queue.shift(), queue, seen)
}

function makePretty (data, long, dir, prefix, list) {
  list = list || []
  prefix = prefix || ""
  list.push(format(data, long, prefix, dir))
  var deps = data.dependencies || {}
    , childPref = prefix.split("├─").join("│ ")
                        .split("└─").join("  ")
    , depList = Object.keys(deps)
    , depLast = depList.length - 1
    , maxDepth = npm.config.get("depth")
  Object.keys(deps).sort(function (a, b) {
    return a > b ? 1 : -1
  }).forEach(function (d, i) {
    var depData = deps[d]
    if (typeof depData === "string") {
      if (data.depth < maxDepth) {
        var p = data.link || data.path
        log.warn("Unmet dependency in "+p, d+" "+deps[d])
        depData = npm.config.get("parseable")
                ? ( npm.config.get("long")
                    ? path.resolve(data.path, "node_modules", d)
                    + ":"+d+"@"+JSON.stringify(depData)+":INVALID:MISSING"
                    : "" )
                : "─ \033[31;40mUNMET DEPENDENCY\033[0m "+d+" "+depData
      } else {
        if (npm.config.get("parseable")) {
          depData = path.resolve(data.path, "node_modules", d)
                  + (npm.config.get("long")
                    ? ":" + d + "@" + JSON.stringify(depData)
                    + ":" // no realpath resolved
                    + ":MAXDEPTH"
                    : "")
        } else {
          depData = "─ "+d+"@'"+depData +"' (max depth reached)"
        }
      }
    }
    var c = i === depLast ? "└─" : "├─"
    makePretty(depData, long, dir, childPref + c, list)
  })
  return list.filter(function (l) { return l.trim() })
}

function ugly (data) {
  if (typeof data === "string") {
    return data
  }
  if (!npm.config.get("long")) return data.path

  return data.path
       + ":" + (data._id || "")
       + ":" + (data.realPath !== data.path ? data.realPath : "")
       + (data.extraneous ? ":EXTRANEOUS" : "")
       + (data.invalid ? ":INVALID" : "")
}

function format (data, long, prefix, dir) {
  if (npm.config.get("parseable")) return ugly(data)
  if (typeof data === "string") {
    return prefix + data
  }
//  console.log([data.path, dir], "relativize")
  var depLen = Object.keys(data.dependencies).length
    , space = prefix.split("├─").join("│ ")
                    .split("└─").join("  ")
            + (depLen ? "" : " ")
    , rel = relativize(data.path, dir)
    , l = prefix
        + (rel === "." ? "" : depLen ? "┬ " : "─ ")
        + (data._id ? data._id + " " : "")
        + (data.link ? "-> " + data.link : "") + ""
        + (rel === "." && !(long && data._id) ? dir : "")
  if (data.invalid) {
    if (data.realName !== data.name) l += " ("+data.realName+")"
    l += " \033[31;40minvalid\033[0m"
  }
  if (data.extraneous && rel !== ".") {
    l += " \033[32;40mextraneous\033[0m"
  }
  if (!long || !data._id) return l
  var extras = []
  if (rel !== ".") extras.push(rel)
  else extras.push(dir)
  if (data.description) extras.push(data.description)
  if (data.repository) extras.push(data.repository.url)
  if (data.homepage) extras.push(data.homepage)
  extras = extras.filter(function (e) { return e })
  var lastExtra = !depLen && extras.length - 1
  l += extras.map(function (e, i) {
    var indent = !depLen ? " " : "│ "
    return "\n" + space + indent + e
  }).join("")
  return l
}
<commit_msg>Close #807 Print (empty) if the dir is empty<commit_after>
// show the installed versions of packages
//
// --parseable creates output like this:
// <fullpath>:<name@ver>:<realpath>:<flags>
// Flags are a :-separated list of zero or more indicators

module.exports = exports = ls

var npm = require("../npm")
  , readInstalled = require("./utils/read-installed")
  , output = require("./utils/output")
  , log = require("./utils/log")
  , relativize = require("./utils/relativize")
  , path = require("path")

ls.usage = "npm ls"

function ls (args, cb) {
  if (args.length) {
    log.warn("ls doesn't take args. Try the 'search' command")
  }
  var dir = npm.prefix
  if (npm.config.get("global")) dir = path.resolve(dir, "lib")
  readInstalled(dir, function (er, data) {
    if (er) return cb(er)
    var long = npm.config.get("long")
    var out = makePretty(bfsify(data), long, dir).join("\n")
    output.write(out, cb)
  })
}

function bfsify (root, current, queue, seen) {
  // walk over the data, and turn it from this:
  // +-- a
  // |   `-- b
  // |       `-- a (truncated)
  // `--b (truncated)
  // into this:
  // +-- a
  // |   `-- b (truncated)
  // `-- b
  //     `-- a (truncated)
  // which looks nicer
  current = current || root
  queue = queue || []
  seen = seen || []
  var deps = current.dependencies = current.dependencies || {}
  Object.keys(deps).forEach(function (d) {
    var dep = deps[d]
    if (typeof dep !== "object") return
    if (seen.indexOf(dep) !== -1) {
      if (npm.config.get("parseable")) {
        delete deps[d]
        return
      }
      dep = deps[d] = Object.create(dep)
      dep.dependencies = {}
    }
    queue.push(dep)
    seen.push(dep)
  })
  if (!queue.length) return root
  return bfsify(root, queue.shift(), queue, seen)
}

function makePretty (data, long, dir, prefix, list) {
  var top = !list
  list = list || []
  prefix = prefix || ""
  list.push(format(data, long, prefix, dir))
  var deps = data.dependencies || {}
    , childPref = prefix.split("├─").join("│ ")
                        .split("└─").join("  ")
    , depList = Object.keys(deps)
    , depLast = depList.length - 1
    , maxDepth = npm.config.get("depth")
  Object.keys(deps).sort(function (a, b) {
    return a > b ? 1 : -1
  }).forEach(function (d, i) {
    var depData = deps[d]
    if (typeof depData === "string") {
      if (data.depth < maxDepth) {
        var p = data.link || data.path
        log.warn("Unmet dependency in "+p, d+" "+deps[d])
        depData = npm.config.get("parseable")
                ? ( npm.config.get("long")
                    ? path.resolve(data.path, "node_modules", d)
                    + ":"+d+"@"+JSON.stringify(depData)+":INVALID:MISSING"
                    : "" )
                : "─ \033[31;40mUNMET DEPENDENCY\033[0m "+d+" "+depData
      } else {
        if (npm.config.get("parseable")) {
          depData = path.resolve(data.path, "node_modules", d)
                  + (npm.config.get("long")
                    ? ":" + d + "@" + JSON.stringify(depData)
                    + ":" // no realpath resolved
                    + ":MAXDEPTH"
                    : "")
        } else {
          depData = "─ "+d+"@'"+depData +"' (max depth reached)"
        }
      }
    }
    var c = i === depLast ? "└─" : "├─"
    makePretty(depData, long, dir, childPref + c, list)
  })
  if (top && list.length === 1 && !data._id) {
    if (!npm.config.get("parseable")) list.push("(empty)")
    else if (npm.config.get("long")) list[0] += ":EMPTY"
  }
  return list.filter(function (l) { return l.trim() })
}

function ugly (data) {
  if (typeof data === "string") {
    return data
  }
  if (!npm.config.get("long")) return data.path

  return data.path
       + ":" + (data._id || "")
       + ":" + (data.realPath !== data.path ? data.realPath : "")
       + (data.extraneous ? ":EXTRANEOUS" : "")
       + (data.invalid ? ":INVALID" : "")
}

function format (data, long, prefix, dir) {
  if (npm.config.get("parseable")) return ugly(data)
  if (typeof data === "string") {
    return prefix + data
  }
//  console.log([data.path, dir], "relativize")
  var depLen = Object.keys(data.dependencies).length
    , space = prefix.split("├─").join("│ ")
                    .split("└─").join("  ")
            + (depLen ? "" : " ")
    , rel = relativize(data.path, dir)
    , l = prefix
        + (rel === "." ? "" : depLen ? "┬ " : "─ ")
        + (data._id ? data._id + " " : "")
        + (data.link ? "-> " + data.link : "") + ""
        + (rel === "." && !(long && data._id) ? dir : "")
  if (data.invalid) {
    if (data.realName !== data.name) l += " ("+data.realName+")"
    l += " \033[31;40minvalid\033[0m"
  }
  if (data.extraneous && rel !== ".") {
    l += " \033[32;40mextraneous\033[0m"
  }
  if (!long || !data._id) return l
  var extras = []
  if (rel !== ".") extras.push(rel)
  else extras.push(dir)
  if (data.description) extras.push(data.description)
  if (data.repository) extras.push(data.repository.url)
  if (data.homepage) extras.push(data.homepage)
  extras = extras.filter(function (e) { return e })
  var lastExtra = !depLen && extras.length - 1
  l += extras.map(function (e, i) {
    var indent = !depLen ? " " : "│ "
    return "\n" + space + indent + e
  }).join("")
  return l
}
<|endoftext|>


<commit_before>import _ from 'lodash';

import {countFirstPrefs} from './utils';

function irv (election, config) {
  var curBallots, curCandidates, voteCount, firstPrefs, ratios, winner, loser;

  config = _.defaults(config, {
    threshold: 0.5,
    tiebreak: _.sample,
    log: _.noop
  });

  curBallots = election.ballots;
  curCandidates = election.candidates;

  while (true) {
    // jshint loopfunc:true
    voteCount = _.sumBy(curBallots, 'count');
    firstPrefs = countFirstPrefs(curBallots, curCandidates);
    ratios = _.mapValues(firstPrefs, votes => votes / voteCount);

    winner = _.findKey(ratios, function (x) {return x > config.threshold; });
    if (!!winner) return winner;

    loser = _.map(_.orderBy(_.toPairs(firstPrefs), x => x[1], ['asc']), x => x[0])[0];
    config.log({
      type: 'eliminated',
      candidate: loser,
      state: firstPrefs
    });

    curBallots = _.compact(_.map(curBallots, function (ballot) {
      return ballot.eliminate(loser);
    }));
    curCandidates = _.without(curCandidates, loser);

  }
}

export default irv;
<commit_msg>Additional logging for IRV.<commit_after>import _ from 'lodash';

import {countFirstPrefs} from './utils';

function irv (election, config) {
  var curBallots, curCandidates, voteCount, firstPrefs, ratios, winner, loser;

  config = _.defaults(config, {
    threshold: 0.5,
    tiebreak: _.sample,
    log: _.noop
  });

  curBallots = election.ballots;
  curCandidates = election.candidates;

  while (true) {
    // jshint loopfunc:true
    voteCount = _.sumBy(curBallots, 'count');
    firstPrefs = countFirstPrefs(curBallots, curCandidates);
    ratios = _.mapValues(firstPrefs, votes => votes / voteCount);

    winner = _.findKey(ratios, function (x) {return x > config.threshold; });
    if (!!winner) {
      config.log({
        type: 'elected',
        candidate: winner,
        state: firstPrefs
      });
      return winner;
    }

    let lowestVote = _.min(_.values(firstPrefs));
    let lowestCandidates = _.keys(_.pickBy(firstPrefs, x => x === lowestVote));

    if (_.size(lowestCandidates) === 1) {
      loser = lowestCandidates[0];
    } else if (lowestVote === 0){
      // special case for candidates with no votes: they _all_ go
      // this can only happen in the first round
      loser = lowestCandidates;
    } else {
      loser = config.tiebreak(lowestCandidates);
      config.log({
        type: 'tiebreak',
        candidate: loser,
        tied: lowestCandidates,
        state: firstPrefs
      });
    }

    _.forEach(_.castArray(loser), loser => {
      config.log({
        type: 'eliminated',
        candidate: loser,
        state: firstPrefs
      });

      curCandidates = _.without(curCandidates, loser);
    });

    curBallots = _.compact(_.map(curBallots, function (ballot) {
      return ballot.eliminate(_.castArray(loser));
    }));

  }
}

export default irv;
<|endoftext|>


<commit_before>/**
 * lil lib to help you detect flash.
 * NOTE: I am not production ready yet.
 */
var lilswf = function(){
    var window,
        undefined,
        self = this;
        self.raw = "",
        self.version = [],
        self.installed = self.isCool = false;
    /**
     * A safe accessor for the native ActiveX GetVariable method.
     *
     * @param {Object} activeXObj A reference to an ActiveXObject.
     * @param {String} name The variable name for lookup.
     * @type {String}
     * @return The value of the AxtiveX if it is found or an empty string.
     */
    function activeXObjectGetVariable(activeXObj, name){
        try{
            return activeXObj.GetVariable(name);
        }catch(e){
            return "";
        }
    }
    /**
     * A safe accessor for creating a native ActiveX object
     *
     * @param {String} name The ActiveX object name lookup.
     * @type Object || undefined
     * @return One of an ActiveX object or undefined.
     */
    function newActiveObject(name){
        try{
            return new ActiveXObject(name);
        }catch(e){
            return undefined;
        }
    }
    /**
     * Parse an ActiveX $version variable into properly casted members:
     *
     * @param {String} raw The ActiveX Object GetVariable($version) string return value.
     * @type Array
     * @return An array of integers casted from the original raw version string. Values that can't be casted are returned as a -1 value.
     */
    function parseActiveXVersionVariable(raw){
        var parts = raw.match(/[0-9]+/g),
            i = 0,
            l = parts.length;
        for(; i < l; i++){
            parts[i] = parseInt(parts[i], 10);
        }
        return parts;
    }
    function argumentsToNumberArray(){
    	return Array.apply(null, arguments).join("").match(/[0-9]+/g);
    }
    self.eq = function(){

    };
    self.gt = function(){

    };
    self.gte = function(){

    };
    self.lt = function(){

    };
    self.lte = function(){

    };
    return self;
}();
<commit_msg>Add array compare method.<commit_after>/**
 * lil lib to help you detect flash.
 * NOTE: I am not production ready yet.
 */
var lilswf = function(){
    var window,
        undefined,
        self = this;
        self.raw = "",
        self.version = [],
        self.installed = self.isCool = false;
    /**
     * A safe accessor for the native ActiveX GetVariable method.
     *
     * @param {Object} activeXObj A reference to an ActiveXObject.
     * @param {String} name The variable name for lookup.
     * @type {String}
     * @return The value of the AxtiveX if it is found or an empty string.
     */
    function activeXObjectGetVariable(activeXObj, name){
        try{
            return activeXObj.GetVariable(name);
        }catch(e){
            return "";
        }
    }
    /**
     * A safe accessor for creating a native ActiveX object
     *
     * @param {String} name The ActiveX object name lookup.
     * @type Object || undefined
     * @return One of an ActiveX object or undefined.
     */
    function newActiveObject(name){
        try{
            return new ActiveXObject(name);
        }catch(e){
            return undefined;
        }
    }
    /**
     * Parse an ActiveX $version variable into properly casted members:
     *
     * @param {String} raw The ActiveX Object GetVariable($version) string return value.
     * @type Array
     * @return An array of integers casted from the original raw version string. Values that can't be casted are returned as a -1 value.
     */
    function parseActiveXVersionVariable(raw){
        var parts = raw.match(/[0-9]+/g),
            i = 0,
            l = parts.length;
        for(; i < l; i++){
            parts[i] = parseInt(parts[i], 10);
        }
        return parts;
    }
    /**
     * TBD.
     * @return
     */
    function argumentsToNumberArray(){
    	var parts = Array.apply(null, arguments).join("").match(/[0-9]+/g),
            i = 0,
            l = parts.length;
        for(; i < l; i++){
            parts[i] = parseInt(parts[i], 10);
        }
        return parts;
    }
    /**
     * TBD.
     *
     * @type Boolean
     * @return TBD.
     */
    function compare(arrayA, operator, arrayB){
        var sumA = sumB = 0,
            units = 1,
            i = 0,
            l = Math.min(arrayA.length, arrayB.length);
        for(; i < l; i++){
            sumA += arrayA[(l-i-1)] * units;
            sumB += arrayB[(l-i-1)] * units;
            units *= 10;
        }
        switch(operator){
            case "<":
                return sumA < sumB;
            case "<=":
                return sumA <= sumB;
            case ">":
                return sumA > sumB;
            case ">=":
                return sumA >= sumB;
            case "==":
                return sumA == sumB;
            default:
                throw new Error("Invalid comparison operator.");
        }
    }
    self.eq = function(){

    };
    self.gt = function(){

    };
    self.gte = function(){

    };
    self.lt = function(){

    };
    self.lte = function(){

    };
    return self;
}();
<|endoftext|>


<commit_before>/**
 * @module Keo
 * @author Adam Timberlake
 * @link https://github.com/Wildhoney/Keo
 */
import objectAssign from 'object-assign';
import {createClass} from 'react';
import WeakMap from 'es6-weak-map';
import {compose, composeDeferred} from 'funkel';
export {memoize, trace, partial} from 'funkel';
export {objectAssign, compose, composeDeferred};

/**
 * @method throwError
 * @param {String} message
 * @return {void}
 */
const throwError = message => console.error(`Keo: ${message}.`);

/**
 * @method isFunction
 * @param {*} fn
 * @return {Boolean}
 */
const isFunction = fn => typeof fn === 'function';

/**
 * @property resolving
 * @type {WeakMap}
*/
const resolving = new WeakMap();

/**
 * @method isPromise
 * @param {*} x
 * @return {Boolean}
 */
function isPromise(x) {
    return 'then' in Object(x);
}

/**
 * @method isObservable
 * @param {*} x
 * @return {Boolean}
 */
function isObservable(x) {
    return 'subscribe' in Object(x);
}

/**
 * @method isObject
 * @param {*} x
 * @return {Boolean}
 */
function isObject(x) {
    return typeof x === 'object';
}

/**
 * @method isFuture
 * @param {Object} cursor
 * @return {Boolean}
 */
const isFuture = cursor => {
    return isPromise(cursor) || isObservable(cursor);
};

/**
 * @method containsFuture
 * @param {*} cursor
 * @return {Boolean}
 */
const containsFuture = cursor => {

    return isFuture(cursor) || (() => {

        if (Array.isArray(cursor)) {

            // Determine if an array was passed, which includes a promise.
            return cursor.some(item => isFuture(item));

        }

        return false;

    })();

};

/**
 * @method createWithCompose
 * @param {Object} component
 * @return {createClass}
 */
export const createWithCompose = component => {

    /**
     * @method passArguments
     * @return {Object}
     */
    function passArguments() {

        if (!resolving.has(this)) {

            // Define the resolving as an object for adding items to.
            resolving.set(this, {});

        }

        /**
         * @method isResolving
         * @param {String} key
         * @param {Boolean} value
         * @return {void}
         */
        const isResolving = (key, value) => resolving.get(this)[key] = !!value;

        /**
         * @method orObject
         * @param {*} x
         * @return {Object}
         */
        const orObject = x => x || {};

        /**
         * @method element
         * @param {String} ref
         * @return {HTMLElement}
         */
        const element = ref => {
            return this.refs[ref];
        };

        /**
         * @method refs
         * @type {Proxy|Object}
         */
        const refs = typeof Proxy === 'undefined' ? {} : new Proxy(this.refs, {

            /**
             * @method get
             * @param {Object} target
             * @param {String} key
             */
            get: (target, key) => this.refs[key]

        });

        const props = orObject(this.props);
        const state = orObject(this.state);
        const context = orObject(this.context);
        const forceUpdate = this::this.forceUpdate;

        /**
         * @method dispatch
         * @param {*} model
         * @type {*|Function}
         */
        const dispatch = (...model) => {

            if (isPromise(model)) {
                return model.then(x => dispatch(model));
            }

            model != null && props.dispatch && props.dispatch(...model);
            return model;

        };

        /**
         * @method setState
         * @param {Object} state
         * @return {Object}
         */
        const setState = state => {

            if (isPromise(state)) {
                return state.then(x => setState(x));
            }

            /**
             * @method keyToState
             * @param {Object} accumulator
             * @param {String} key
             * @return {Object}
             */
            const keyToState = (accumulator, key) => {
                accumulator[key] = state[key];
                return accumulator;
            };

            /**
             * @property immediateState
             * @type {Object}
             */
            const immediateState = (() => {

                if (isPromise(state)) {

                    // Defer promise states until they have been resolved.
                    state.then(value => setState(value));
                    return null;

                }

                if (state && isObject(state)) {

                    const keys = Object.keys(state);

                    // Determine which state items yield promises, and which yield immediate values.
                    const futureStates = keys.filter(key => containsFuture(state[key])).reduce(keyToState, {});

                    // Iterate over each future state to apply the state once the promise has been resolved.
                    Object.keys(futureStates).map(key => {

                        const cursor = state[key];

                        if (isPromise(cursor)) {

                            isResolving(key, true);

                            // Resolve a simple promise contained within an object.
                            state[key].then(value => {

                                // Succeeded! We'll therefore update the value.
                                isResolving(key, false);
                                setState({ [key]: value });

                            }).catch(() => {

                                // Failed! In which case we'll simply re-render.
                                isResolving(key, false);
                                forceUpdate();

                            });

                        }

                        if (Array.isArray(cursor)) {

                            // Await all promises to resolve before setting the state.
                            const promises = cursor.filter(item => isPromise(item));
                            const items = cursor.filter(item => !isPromise(item));

                            isResolving(key, true);

                            Promise.all(promises).then(array => {

                                // Succeeded! Therefore we'll merge the new items in with the existing items.
                                isResolving(key, false);
                                setState({ [key]: [...items, ...array] });

                            }).catch(() => {

                                // Failed! We'll therefore display only the existing items.
                                isResolving(key, false);
                                setState({ [key]: items });

                            });

                        }

                    });

                    return keys.filter(key => !containsFuture(state[key])).reduce(keyToState, {});

                }

            })();

            const notEmpty = isObject(immediateState) ? Object.keys(immediateState).length : immediateState != null;
            notEmpty && this.setState(immediateState);
            return immediateState;

        };

        const args = { props, state, setState, dispatch, element, refs, context, forceUpdate };

        /**
         * @method debug
         * @return {void}
         */
        const debug = () => {

            console.table(Object.keys(args).reduce((accumulator, key) => {
                return [ ...accumulator, { name: key, type: typeof args[key] }];
            }, []));

        };

        return { ...args, debug };

    }

    /**
     * @method orFunction
     * @param {Function} fn
     * @return {Function}
     */
    function orFunction(fn) {
        return isFunction(fn) ? fn : () => {};
    }

    return createClass(objectAssign({}, component, {

        /**
         * @method componentWillMount
         * @return {*}
         */
        componentWillMount: pipe(passArguments, orFunction(component.componentWillMount)),

        /**
         * @method componentDidMount
         * @return {*}
         */
        componentDidMount: pipe(passArguments, orFunction(component.componentDidMount)),

        /**
         * @method componentWillReceiveProps
         * @param nextProps {Object}
         * @return {*}
         */
        componentWillReceiveProps(nextProps) {

            orFunction(component.componentWillReceiveProps)({
                ...passArguments.apply(this),
                nextProps
            });

        },

        /**
         * @method shouldComponentUpdate
         * @param nextProps {Object}
         * @param nextState {Object}
         * @return {*}
         */
        shouldComponentUpdate(nextProps, nextState) {

            return (component.shouldComponentUpdate || (() => true))({
                ...passArguments.apply(this),
                nextProps, nextState
            });

        },

        /**
         * @method componentWillUpdate
         * @param nextProps {Object}
         * @param nextState {Object}
         * @return {*}
         */
        componentWillUpdate(nextProps, nextState) {

            const args = { ...passArguments.apply(this), nextProps, nextState };
            delete args.setState;
            orFunction(component.componentWillUpdate)(args);

        },

        /**
         * @method componentDidUpdate
         * @param prevProps {Object}
         * @param prevState {Object}
         * @return {*}
         */
        componentDidUpdate(prevProps, prevState) {

            orFunction(component.componentDidUpdate)({
                ...passArguments.apply(this),
                prevProps, prevState
            });

        },

        /**
         * @method componentWillUnmount
         * @return {*}
         */
        componentWillUnmount: pipe(passArguments, orFunction(component.componentWillUnmount)),

        /**
         * @method render
         * @return {XML}
         */
        render: pipe(passArguments, component.render)

    }));

};

/**
 * @method stitch
 * @param {Object|Function} component
 * @return {createClass}
 */
export const stitch = component => {
    return createWithCompose(wrap(component));
};

/**
 * @method wrap
 * @param {Object|Function} object
 * @return {Object}
 */
export const wrap = object => {
    return isFunction(object) ? { render: object } : object;
};

/**
 * @method pipe
 * @param {Function} fns
 * @return {Function}
 */
export const pipe = (...fns) => {
    return compose(...fns.reverse());
};

/**
 * @method pipeDeferred
 * @param {Function} fns
 * @return {Promise}
 */
export const pipeDeferred = (...fns) => {
    return composeDeferred(...fns.reverse());
};

/**
 * @method resolutionMap
 * @param {Array} args
 * @return {Array}
 */
export const resolutionMap = function(args) {

    return objectAssign({}, args, {
        props: { ...args.props, resolving: resolving.get(this) }
    });

};
<commit_msg>Removed 'throwError' function<commit_after>/**
 * @module Keo
 * @author Adam Timberlake
 * @link https://github.com/Wildhoney/Keo
 */
import objectAssign from 'object-assign';
import {createClass} from 'react';
import WeakMap from 'es6-weak-map';
import {compose, composeDeferred} from 'funkel';
export {memoize, trace, partial} from 'funkel';
export {objectAssign, compose, composeDeferred};

/**
 * @method isFunction
 * @param {*} fn
 * @return {Boolean}
 */
const isFunction = fn => typeof fn === 'function';

/**
 * @property resolving
 * @type {WeakMap}
*/
const resolving = new WeakMap();

/**
 * @method isPromise
 * @param {*} x
 * @return {Boolean}
 */
function isPromise(x) {
    return 'then' in Object(x);
}

/**
 * @method isObservable
 * @param {*} x
 * @return {Boolean}
 */
function isObservable(x) {
    return 'subscribe' in Object(x);
}

/**
 * @method isObject
 * @param {*} x
 * @return {Boolean}
 */
function isObject(x) {
    return typeof x === 'object';
}

/**
 * @method isFuture
 * @param {Object} cursor
 * @return {Boolean}
 */
const isFuture = cursor => {
    return isPromise(cursor) || isObservable(cursor);
};

/**
 * @method containsFuture
 * @param {*} cursor
 * @return {Boolean}
 */
const containsFuture = cursor => {

    return isFuture(cursor) || (() => {

        if (Array.isArray(cursor)) {

            // Determine if an array was passed, which includes a promise.
            return cursor.some(item => isFuture(item));

        }

        return false;

    })();

};

/**
 * @method createWithCompose
 * @param {Object} component
 * @return {createClass}
 */
export const createWithCompose = component => {

    /**
     * @method passArguments
     * @return {Object}
     */
    function passArguments() {

        if (!resolving.has(this)) {

            // Define the resolving as an object for adding items to.
            resolving.set(this, {});

        }

        /**
         * @method isResolving
         * @param {String} key
         * @param {Boolean} value
         * @return {void}
         */
        const isResolving = (key, value) => resolving.get(this)[key] = !!value;

        /**
         * @method orObject
         * @param {*} x
         * @return {Object}
         */
        const orObject = x => x || {};

        /**
         * @method element
         * @param {String} ref
         * @return {HTMLElement}
         */
        const element = ref => {
            return this.refs[ref];
        };

        /**
         * @method refs
         * @type {Proxy|Object}
         */
        const refs = typeof Proxy === 'undefined' ? {} : new Proxy(this.refs, {

            /**
             * @method get
             * @param {Object} target
             * @param {String} key
             */
            get: (target, key) => this.refs[key]

        });

        const props = orObject(this.props);
        const state = orObject(this.state);
        const context = orObject(this.context);
        const forceUpdate = this::this.forceUpdate;

        /**
         * @method dispatch
         * @param {*} model
         * @type {*|Function}
         */
        const dispatch = (...model) => {

            if (isPromise(model)) {
                return model.then(x => dispatch(model));
            }

            model != null && props.dispatch && props.dispatch(...model);
            return model;

        };

        /**
         * @method setState
         * @param {Object} state
         * @return {Object}
         */
        const setState = state => {

            if (isPromise(state)) {
                return state.then(x => setState(x));
            }

            /**
             * @method keyToState
             * @param {Object} accumulator
             * @param {String} key
             * @return {Object}
             */
            const keyToState = (accumulator, key) => {
                accumulator[key] = state[key];
                return accumulator;
            };

            /**
             * @property immediateState
             * @type {Object}
             */
            const immediateState = (() => {

                if (isPromise(state)) {

                    // Defer promise states until they have been resolved.
                    state.then(value => setState(value));
                    return null;

                }

                if (state && isObject(state)) {

                    const keys = Object.keys(state);

                    // Determine which state items yield promises, and which yield immediate values.
                    const futureStates = keys.filter(key => containsFuture(state[key])).reduce(keyToState, {});

                    // Iterate over each future state to apply the state once the promise has been resolved.
                    Object.keys(futureStates).map(key => {

                        const cursor = state[key];

                        if (isPromise(cursor)) {

                            isResolving(key, true);

                            // Resolve a simple promise contained within an object.
                            state[key].then(value => {

                                // Succeeded! We'll therefore update the value.
                                isResolving(key, false);
                                setState({ [key]: value });

                            }).catch(() => {

                                // Failed! In which case we'll simply re-render.
                                isResolving(key, false);
                                forceUpdate();

                            });

                        }

                        if (Array.isArray(cursor)) {

                            // Await all promises to resolve before setting the state.
                            const promises = cursor.filter(item => isPromise(item));
                            const items = cursor.filter(item => !isPromise(item));

                            isResolving(key, true);

                            Promise.all(promises).then(array => {

                                // Succeeded! Therefore we'll merge the new items in with the existing items.
                                isResolving(key, false);
                                setState({ [key]: [...items, ...array] });

                            }).catch(() => {

                                // Failed! We'll therefore display only the existing items.
                                isResolving(key, false);
                                setState({ [key]: items });

                            });

                        }

                    });

                    return keys.filter(key => !containsFuture(state[key])).reduce(keyToState, {});

                }

            })();

            const notEmpty = isObject(immediateState) ? Object.keys(immediateState).length : immediateState != null;
            notEmpty && this.setState(immediateState);
            return immediateState;

        };

        const args = { props, state, setState, dispatch, element, refs, context, forceUpdate };

        /**
         * @method debug
         * @return {void}
         */
        const debug = () => {

            console.table(Object.keys(args).reduce((accumulator, key) => {
                return [ ...accumulator, { name: key, type: typeof args[key] }];
            }, []));

        };

        return { ...args, debug };

    }

    /**
     * @method orFunction
     * @param {Function} fn
     * @return {Function}
     */
    function orFunction(fn) {
        return isFunction(fn) ? fn : () => {};
    }

    return createClass(objectAssign({}, component, {

        /**
         * @method componentWillMount
         * @return {*}
         */
        componentWillMount: pipe(passArguments, orFunction(component.componentWillMount)),

        /**
         * @method componentDidMount
         * @return {*}
         */
        componentDidMount: pipe(passArguments, orFunction(component.componentDidMount)),

        /**
         * @method componentWillReceiveProps
         * @param nextProps {Object}
         * @return {*}
         */
        componentWillReceiveProps(nextProps) {

            orFunction(component.componentWillReceiveProps)({
                ...passArguments.apply(this),
                nextProps
            });

        },

        /**
         * @method shouldComponentUpdate
         * @param nextProps {Object}
         * @param nextState {Object}
         * @return {*}
         */
        shouldComponentUpdate(nextProps, nextState) {

            return (component.shouldComponentUpdate || (() => true))({
                ...passArguments.apply(this),
                nextProps, nextState
            });

        },

        /**
         * @method componentWillUpdate
         * @param nextProps {Object}
         * @param nextState {Object}
         * @return {*}
         */
        componentWillUpdate(nextProps, nextState) {

            const args = { ...passArguments.apply(this), nextProps, nextState };
            delete args.setState;
            orFunction(component.componentWillUpdate)(args);

        },

        /**
         * @method componentDidUpdate
         * @param prevProps {Object}
         * @param prevState {Object}
         * @return {*}
         */
        componentDidUpdate(prevProps, prevState) {

            orFunction(component.componentDidUpdate)({
                ...passArguments.apply(this),
                prevProps, prevState
            });

        },

        /**
         * @method componentWillUnmount
         * @return {*}
         */
        componentWillUnmount: pipe(passArguments, orFunction(component.componentWillUnmount)),

        /**
         * @method render
         * @return {XML}
         */
        render: pipe(passArguments, component.render)

    }));

};

/**
 * @method stitch
 * @param {Object|Function} component
 * @return {createClass}
 */
export const stitch = component => {
    return createWithCompose(wrap(component));
};

/**
 * @method wrap
 * @param {Object|Function} object
 * @return {Object}
 */
export const wrap = object => {
    return isFunction(object) ? { render: object } : object;
};

/**
 * @method pipe
 * @param {Function} fns
 * @return {Function}
 */
export const pipe = (...fns) => {
    return compose(...fns.reverse());
};

/**
 * @method pipeDeferred
 * @param {Function} fns
 * @return {Promise}
 */
export const pipeDeferred = (...fns) => {
    return composeDeferred(...fns.reverse());
};

/**
 * @method resolutionMap
 * @param {Array} args
 * @return {Array}
 */
export const resolutionMap = function(args) {

    return objectAssign({}, args, {
        props: { ...args.props, resolving: resolving.get(this) }
    });

};
<|endoftext|>


<commit_before>var Promise = require("bluebird");
var tokml = require('tokml');
var _ = require('lodash');
var fs = require('fs');
var et = require('elementtree');

var readFile = Promise.promisify(fs.readFile);
var writeFile = Promise.promisify(fs.writeFile);

exports.toGeoJson = function(fileName, options) {
    var promise = new Promise(function(resolve, reject) {
        if(!fs.statSync(fileName)) { reject('Give KML file does not exist.'); }

        var encoding = 'utf-8';
        if(_.has(options, 'encoding')) { encoding = options.encoding; }

        return readFile(fileName, encoding).then(function(data) {
            var etree = et.parse(data),
                geojson = {
                    'type': 'FeatureCollection',
                    'features': []
                };

            var placemarks = etree.findall('.//Placemark');
            _.forEach(placemarks, function(placemark) {
                geojson.features.push({
                    type: 'feature',
                    geometry: getGeometry(placemark),
                    properties: getProperties(placemark)
                });
            });

            resolve(geojson);
        });
    });

    return promise;
};

exports.fromGeoJson = function(geojson, fileName, options) {

    var promise = new Promise(function(resolve, reject) {
        try {
            var kmlGeoJson = JSON.parse(JSON.stringify(geojson));

            for(var feature in kmlGeoJson.feature) {
                var description = '';
                for(var key in feature.properties) {
                    description += key + '=' + feature.properties[key] + '\n';
                }
                feature.kmlDescription = description;
            }

            var kmlContent = tokml(kmlGeoJson, {
                name: 'Name',
                description: 'kmlDescription'
            });

            if(fileName) {
                var fileNameWithExt = fileName;
                if(!_.endsWith(fileNameWithExt, '.kml')) { fileNameWithExt += '.kml'; }

                writeFile(fileNameWithExt, kmlContent);
                resolve(fileNameWithExt);
            } else {
                resolve({
                    data: kmlContent,
                    format: 'kml'
                });
            }
        } catch(ex) {
            reject(ex);
        }
    });

    return promise;
};

function getGeometry(placemark) {
    var geomTag = placemark.find('./Point');
    if(geomTag) {
        return getGeom('POINT', geomTag.findtext('./coordinates'));
    }

    geomTag = placemark.find('./LineString');
    if(geomTag) {
        return getGeom('POLYLINE', geomTag.findtext('./coordinates'));
    }

    geomTag = placemark.find('./Polygon');
    if(geomTag) {
        var outRingCoors = geomTag.findtext('./outerBoundaryIs/LinearRing/coordinates');
        var inRingsCoors = _.map(geomTag.findall('./innerBoundaryIs/LinearRing/coordinates'),
                                function(innerRing) {
                                    return innerRing.text;
                                })

        return getGeom('POLYGON', outRingCoors, inRingsCoors);
    }
}

function getGeom(geomType, coordStr) {
    return {
        type: geomType,
        coordinates: getCoordinates(coordStr)
    };
}

function getCoordinates(outCoorsdStr, inCoordsStrs) {
    var pointStrs = outCoorsdStr.split(' ');

    if (pointStrs.length == 1) {
        var coors = pointStrs[0].split(',');
        return [parseFloat(coors[0]), parseFloat(coors[1])];
    } else {
         var outPoints = [];
        _.forEach(pointStrs, function(pointStr) {
            var coors = pointStr.split(',');
            outPoints.push([parseFloat(coors[0]), parseFloat(coors[1])]);
        });

        if (!inCoordsStrs) { return outPoints; }

        var allPoints = [outPoints];
        _.forEach(inCoordsStrs, function (coordsStr) {
            var inPoints = [],
                pointStrs = coordsStr.split(' ');

            _.forEach(pointStrs, function(coordsStr) {
                var coors = coordsStr.split(',');
                inPoints.push([parseFloat(coors[0]), parseFloat(coors[1])]);
            });

            allPoints.push(inPoints);
        });

        return allPoints;
    }
}

function findSchema(rootnode) {
    var schemaNode = rootnode.find('./kml/Document/Schema'),
        fields;

    if(schemaNode) {
        fields = {};
        _.forEach(schemaNode.findall('./'), function(fieldNode) {
            fields.
        });
    }

    return fields;
}
<commit_msg>parse kml data into geojson properties<commit_after>var Promise = require("bluebird");
var tokml = require('tokml');
var _ = require('lodash');
var fs = require('fs');
var et = require('elementtree');

var readFile = Promise.promisify(fs.readFile);
var writeFile = Promise.promisify(fs.writeFile);

exports.toGeoJson = function(fileName, options) {
    var promise = new Promise(function(resolve, reject) {
        if(!fs.statSync(fileName)) { reject('Give KML file does not exist.'); }

        var encoding = 'utf-8';
        if(_.has(options, 'encoding')) { encoding = options.encoding; }

        return readFile(fileName, encoding).then(function(data) {
            var etree = et.parse(data),
                geojson = {
                    'type': 'FeatureCollection',
                    'features': []
                };

            var schemas = findSchemas(etree);
            var placemarks = etree.findall('.//Placemark');
            _.forEach(placemarks, function(placemark) {
                geojson.features.push({
                    type: 'feature',
                    geometry: getGeometry(placemark),
                    properties: getProperties(placemark, schemas)
                });
            });

            resolve(geojson);
        });
    });

    return promise;
};

exports.fromGeoJson = function(geojson, fileName, options) {

    var promise = new Promise(function(resolve, reject) {
        try {
            var kmlGeoJson = JSON.parse(JSON.stringify(geojson));

            for(var feature in kmlGeoJson.feature) {
                var description = '';
                for(var key in feature.properties) {
                    description += key + '=' + feature.properties[key] + '\n';
                }
                feature.kmlDescription = description;
            }

            var kmlContent = tokml(kmlGeoJson, {
                name: 'Name',
                description: 'kmlDescription'
            });

            if(fileName) {
                var fileNameWithExt = fileName;
                if(!_.endsWith(fileNameWithExt, '.kml')) { fileNameWithExt += '.kml'; }

                writeFile(fileNameWithExt, kmlContent);
                resolve(fileNameWithExt);
            } else {
                resolve({
                    data: kmlContent,
                    format: 'kml'
                });
            }
        } catch(ex) {
            reject(ex);
        }
    });

    return promise;
};

function getGeometry(placemark) {
    var geomTag = placemark.find('./Point');
    if(geomTag) {
        return createGeometry('POINT', geomTag.findtext('./coordinates'));
    }

    geomTag = placemark.find('./LineString');
    if(geomTag) {
        return createGeometry('POLYLINE', geomTag.findtext('./coordinates'));
    }

    geomTag = placemark.find('./Polygon');
    if(geomTag) {
        var outRingCoors = geomTag.findtext('./outerBoundaryIs/LinearRing/coordinates');
        var inRingsCoors = _.map(geomTag.findall('./innerBoundaryIs/LinearRing/coordinates'),
                                function(innerRing) {
                                    return innerRing.text;
                                })

        return createGeometry('POLYGON', outRingCoors, inRingsCoors);
    }
}

function createGeometry(geomType, outerCoorStr, innerCoorStr) {
    return {
        type: geomType,
        coordinates: getCoordinates(outerCoorStr, innerCoorStr);
    };
}

function getCoordinates(outCoorsdStr, inCoordsStrs) {
    var pointStrs = outCoorsdStr.split(' ');

    if (pointStrs.length == 1) {
        var coors = pointStrs[0].split(',');
        return [parseFloat(coors[0]), parseFloat(coors[1])];
    } else {
         var outPoints = [];
        _.forEach(pointStrs, function(pointStr) {
            var coors = pointStr.split(',');
            outPoints.push([parseFloat(coors[0]), parseFloat(coors[1])]);
        });

        if (!inCoordsStrs) { return outPoints; }

        var allPoints = [outPoints];
        _.forEach(inCoordsStrs, function (coordsStr) {
            var inPoints = [],
                pointStrs = coordsStr.split(' ');

            _.forEach(pointStrs, function(coordsStr) {
                var coors = coordsStr.split(',');
                inPoints.push([parseFloat(coors[0]), parseFloat(coors[1])]);
            });

            allPoints.push(inPoints);
        });

        return allPoints;
    }
}

function findSchemas(rootnode) {
    var schemaNodes = rootnode.findall('./kml/Document/Schema');

    // considering if we have more than one schema
    if(schemaNodes.length > 0) {
        var schemas = {};
        _.forEach(schemaNodes, function(schemaNode) {
            var schema = {};

            // get the type of field
            _.forEach(schemaNode.findall('./SimpleField'), function(fieldNode) {
                schema[fieldNode.attrib.name] = fieldNode.attrib.type;
            });

            schemas[schemaNode.attrib.id] = schema;
        });

        return schemas;
    }
}

function getProperties(placemark, schemas) {
    var properties = {};

    // name
    var name = placemark.findtext('./name');
    if(name) { properties.name = name; }

    // description
    var description = placemark.findtext('./description');
    if(description) { properties.description = description; }

    // schema data
    if(schemas) {
        var schemaDatasets = placemark.findall('./ExtendedData/SchemaData');
        _.forEach(schemaDatasets, function(schemaDataset) {
            var schema = schemas[schemaDataset.attrib.schemaURl.replace('#', '')],
                fields = schemaDataset.findall('./SimpleData');
            _.forEach(fields, function(field) {
                properties[field.attrib.name] = convert(field.text, schema[field.attrib.name];)
            });
        });
    }

    // simple data
    var fields = placemark.findall('./ExtendedData/Data');
    _.forEach(fields, function(field) {
        properties[field.attrib.name] = field.findtext('./value');
    });

    return properties;
}

function convert(value, toType) {
    switch(toType) {
        case 'int':
        case 'uint':
        case 'short':
        case 'ushort'
            return parseInt(value);
        case 'float':
        case 'double':
            return parseFloat(value);
        case 'bool':
            return value.toLowerCase() === 'true';
        default:
            return value;
    };
}
<|endoftext|>