<commit_before>/*!
 * Node - Clam
 * Copyright(c) 2013 Kyle Farris <kyle@chomponllc.com>
 * MIT Licensed
 */

// Module dependencies.
var __ = require('underscore');
var fs = require('fs');
var exec = require('child_process').exec;

// ****************************************************************************
// Return a new NodeClam object.
// -----
// @param	Object 	options		Supplied to the NodeClam object for configuration
// @return	Function / Class
// @api 	Public
// ****************************************************************************
module.exports = function(options){

	this.default_scanner = 'clamdscan';

	// Configuration Settings
	this.settings = {
		remove_infected: false,
		quarantine_infected: false,
		scan_log: null,
		debug_mode: false,
		file_list: null,
		scan_recursively: true,
		clamscan: {
			path: '/usr/bin/clamscan',
			scan_archives: true,
			db: null,
			active: true
		},
		clamdscan: {
			path: '/usr/bin/clamdscan',
			config_file: '/etc/clamd.conf',
			multiscan: true,
			reload_db: false,
			active: true
		},
		preference: this.default_scanner
	};

	// Override defaults with user preferences
	this.settings = __.extend(this.settings,options);

	// Backwards compatibilty
	if (this.settings.quarantine_path && !__.isEmpty(this.settings.quarantine_path)) {
		this.settings.quarantine_infected = this.settings.quarantine_path;
	}

	// Determine whether to use clamdscan or clamscan
	this.scanner = this.default_scanner;
	if (this.settings.preference == 'clamscan' && this.settings.clamscan.active === true) {
		this.scanner = 'clamscan';
	}

	// Check to make sure preferred scanner exists
	if (!fs.existsSync(this.settings[this.scanner].path)) {
		// Fall back to other option:
		if (this.scanner == 'clamdscan' && this.settings.clamscan.active === true) {
			this.scanner == 'clamscan';
		} else if (this.scanner == 'clamscan' && this.settings.clamdscan.active === true {
			this.scanner == 'clamdscan';
		} else {
			throw new Error("No valid virus scanning binaries are active and available!");
		}

		// Neither scanners are available!
		if (!fs.existsSync(this.settings[this.scanner].path)) {
			throw new Error("No valid virus scanning binaries have been found in the paths provided!");
		}
	}

	// Make sure quarantine path exists at specified location
	if (!__.isEmpty(this.settings.quarantine_infected) && !fs.existsSync(this.settings.quarantine_infected)) {
		this.quarantine_infected = false;
		throw new Error("Quarantine path (" + this.quarantine_infected + ") is invalid.");

		if (this.settings.debug_mode)
			console.log("node-clam: Quarantine path (" + this.quarantine_infected + ") is invalid.");
	}

	// Make sure scan_log exists at specified location
	if (!__.isEmpty(this.settings.scan_log) && fs.existsSync(this.settings.scan_log)) {
		this.scan_log = null;

		if (this.settings.debug_mode)
			console.log("node-clam: Scan Log path (" + this.scan_log + ") is invalid.");
	}

	// If using clamscan, make sure definition db exists at specified location
	if (this.scanner == 'clamscan') {
		if (!__.isEmpty(this.settings.clamscan.db) && !fs.existsSync(this.settings.db)) {
			this.db = null;
			if(this.settings.debug_mode)
				console.log("node-clam: Definitions DB path (" + this.db + ") is invalid.");
		}
	}

	// Build clam flags
	this.clam_flags = build_clam_flags(this.scanner, this.settings);

	// ****************************************************************************
	// Checks if a particular file is infected.
	// -----
	// @param	String		file		Path to the file to check
	// @param	Function	callback	What to do after the scan
	// ****************************************************************************
	NodeClam.prototype.is_infected = function(file, callback) {
		if(this.settings.debug_mode)
			console.log("node-clam: Scanning " + file);

		var command = this.settings[this.scanner].path + this.clam_flags + file;

		if(this.settings.debug_mode === true)
			console.log('node-clam: Configured clam command: ' + command);

		// Execute the clam binary with the proper flags
		exec(command, function(err, stdout, stderr) {
			if (err || stderr) {
				if (err) {
					if(err.hasOwnProperty('code') && err.code === 1) {
	    				callback(null, file, true);
		    		} else {
			    		if(self.settings.debug_mode)
				    		console.log(err);
					    callback(err, file, null);
				    }
				} else {
					console.error(stderr);
					callback(err, file, null);
				}
			} else {
				var result = stdout.trim();

				if(result.match(/OK$/)) {
					if(self.settings.debug_mode)
						console.log(file + ' is OK!');
					callback(null, file, false);
				} else {
					if(self.settings.debug_mode)
						console.log(file + ' is INFECTED!');
					callback(null, file, true);
				}
			}
		}
	}

	// ****************************************************************************
	// Scans an array of files or paths. You must provide the full paths of the
	// files and/or paths.
	// -----
	// @param	Array		files		A list of files or paths (full paths) to be scanned.
	// @param	Function	end_cb		What to do after the scan
	// @param	Function	file_cb		What to do after each file has been scanned
	// ****************************************************************************
	NodeClam.prototype.scan_files = function(files, end_cb, file_cb) {
		files = files || [];
		end_cb = end_cb || null;
		files_cb = file_cb || null;

		var bad_files = [];
		var good_files = [];
		var completed_files = 0;
		var self = this;
		var file;

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

		// Slower but more verbose way...
		if (typeof file_cb === 'function') {
			(function scan_file() {
				file = files.shift();
				self.is_infected(file, function(err, file, infected) {
					var completed_files++;

					if(!infected) {
						var good_files.push(file);
					} else if(infected || err) {
						var bad_files.push(file);
					}

					if(__.isFunction(file_cb)) file_cb(err, file, infected);

					if(completed_files >= files.length) {
						if(self.settings.debug_mode) {
							console.log('node-clam: Scan Complete!');
							console.log("node-clam: Bad Files: ");
							console.dir(self.bad_files);
							console.log("node-clam: Good Files: ");
							console.dir(self.good_files);
						}
						if(__.isFunction(end_cb)) end_cb(null, good_files, bad_files);
					}
					// All files have not been scanned yet, scan next item.
					else {
						// Using setTimeout to avoid crazy stack trace madness.
						setTimeout(scan_file, 0);
					}
				});
			})();
		}

		// The MUCH quicker but less-verbose way
		else {
			var all_files = [];
			if (this.scanner === 'clamdscan' && this.scan_recursively === false) {
				for(var i in files) {
					if (!fs.statSync(files[i]).isFile()) {
						all_files = _.uniq(all_files.concat(fs.readdirSync(files[i])));
					} else {
						all_files.push(files[i]);
					}
				}
			} else {
				all_files = files;
			}

			// Make sure there are no dupes... just cause we can
			all_files = __.uniq(all_files);

			// List files by space and escape
			var items = files.map(function(file) {
				return file.replace(/ /g,'\\ ');
			}).join(' ');

			var command = this.settings.clamscan.path + this.clam_flags + items;
			if(this.settings.debug_mode === true)
				console.log('node-clam: Configured clam command: ' + command);

			// Execute the clam binary with the proper flags
			exec(command, function(err, stdout, stderr) {
				if (err || stderr) {
					if(this.settings.debug_mode === true)
						console.error(stderr);
					return end_cb(err, path, null);
				} else {
					var result = stdout.trim();

					if(result.match(/OK$/)) {
						if(self.settings.debug_mode)
							console.log(path + ' is OK!');
						return end_cb(null, path, false);
					} else {
						if(self.settings.debug_mode)
							console.log(path + ' is INFECTED!');
						return end_cb(null, path, true);
					}
				}
			});
		}
	}

	// ****************************************************************************
	// Scans an entire directory. Provides 3 params to end callback: Error, path
	// scanned, and whether its infected or not. To scan multiple directories, pass
	// them as an array to the scan_files method.
	// -----
	// NOTE: While possible, it is NOT advisable to use the file_cb parameter when
	// using the clamscan binary. Doing so with clamdscan is okay, however. This
	// method also allows for non-recursive scanning with the clamdscan binary.
	// -----
	// @param	String		path		The directory to scan files of
	// @param	Function	en_cb	    What to do when all files have been scanned
	// @param   Function    file_cb     What to do after each file has been scanned
	// ****************************************************************************
	NodeClam.prototype.scan_dir = function(path,end_cb,file_cb) {
		path = path || '';
		end_cb = end_cb || null;
		files_cb = file_cb || null;

		if (typeof path !== 'string' || path.length <= 0) {
			return end_cb(new Error("Invalid path provided! Path must be a string!"));
		}

		// Trim trailing slash
		path = path.replace(/\/$/, '');

		if(this.settings.debug_mode)
			console.log("node-clam: Scanning Directory: " + path);

		// Get all files recursively
		if (this.settings.scan_recursively && typeof file_cb == 'function') {
			exec('find ' + path. function(err, stdout, stderr) {
				if (err || stderr) {
					if(this.settings.debug_mode === true)
						console.error(stderr);
					return end_cb(err, path, null);
				} else {
					var files = stdout.split("\n").map(function(path) { return path.replace(/ /g,'\\ '); });
					self.scan_files(files, end_cb, file_cb);
				}
			});
		}
		// Clamdscan always does recursive, so, here's a way to avoid that if you want...
		else if (this.settings.scan_recursively === false && this.scanner === 'clamdscan') {
			fs.readdir(path, function(err, files) {
				files.filter(function (file) {
					return fs.statSync(file).isFile();
				}

				self.scan_files(files, end_file, file_cb);
			});
		}

		// If you don't care about individual file progress (which is very slow for clamscan but fine for clamdscan...)
		else if (this.settings.scan_recursively && typeof file_cb !== 'function') {
			var command = this.settings.clamscan.path + this.clam_flags + path;

			if(this.settings.debug_mode === true)
				console.log('node-clam: Configured clam command: ' + command);

			// Execute the clam binary with the proper flags
			exec(command, function(err, stdout, stderr) {
				if (err || stderr) {
					if(this.settings.debug_mode === true)
						console.error(stderr);
					return end_cb(err, null, null);
				} else {
					var result = stdout.trim();

					if(result.match(/OK$/)) {
						if(self.settings.debug_mode)
							console.log(path + ' is OK!');
						return end_cb(null, [path], null);
					} else {
						if(self.settings.debug_mode)
							console.log(path + ' is INFECTED!');
						return end_cb(null, null, [path]);
					}
				}
			});
		}
	}

	return new NodeClam(options);
}

// *****************************************************************************
// Builds out the flags based on the configuration the user provided
// -----
// @param	String	scanner		The scanner to use (clamscan or clamdscan)
// @param	Object	settings	The settings used to build the flags
// @return	String				The concatenated clamav flags
// @api		Private
// *****************************************************************************
function build_clam_flags(scanner, settings) {
	var flags_array = ['--no-summary'];

	// Flags specific to clamscan
	if (scanner == 'clamscan') {
		flags_array.push('--stdout');
		// Remove infected files
		if (settings.remove_infected === true) flags_array.push('--remove=yes');
		// Database file
		if (!__.isEmpty(settings.clamscan.db)) flags_array.push('--database=' + settings.clamscan.db);
		// Scan archives
		if (settings.clamscan.scan_archives === true) {
			flags_array.push('--scan-archive=yes');
		} else {
			flags_array.push('--scan-archive=no');
		}
		// Recursive scanning (flag is specific, feature is not)
		if (settings.scan_recursively === true) {
			flags_array.push('-r');
		} else {
			flags_array.push('--recursive=no');
		}
	}

	// Flags specific to clamdscan
	else if (scanner == 'clamdscan') {
		// Remove infected files
		if (settings.remove_infected === true) flags_array.push('--remove');
		// Specify a config file
		if (!__.isEmpty(settings.clamdscan.config_file)) flags_array.push('--config-file=' + settings.clamdscan.config_file);
		// Turn on multi-threaded scanning
		if (settings.clamdscan.multiscan === true) flags_array.push('--multiscan');
		// Reload the virus DB
		if (settings.clamdscan.reload_db === true) flags_array.push('--reload');
	}

	// ***************
	// Common flags
	// ***************

	// Remove infected files
	if (settings.remove_infected !== true) {
		if (!__.isEmpty(settings.quarantine_infected))
			flags_array.push('--move=' + settings.quarantine_infected);
	}
	// Write info to a log
	if (!__.isEmpty(settings.scan_log)) flags_array.push('--log=' + settings.scan_log);
	// Read list of files to scan from a file
	if (!__.isEmpty(settings.file_list)) flags_array.push('--file-list=' + settings.file_list);

	// Build the String
	return ' ' + flags_array.join(' ') + ' ';
}<commit_msg>Fixed some additional glaring bugs. Almost ready to release to NPM.<commit_after>/*!
 * Node - Clam
 * Copyright(c) 2013 Kyle Farris <kyle@chomponllc.com>
 * MIT Licensed
 */

// Module dependencies.
var __ = require('underscore');
var fs = require('fs');
var exec = require('child_process').exec;

// ****************************************************************************
// Return a new NodeClam object.
// -----
// @param	Object 	options		Supplied to the NodeClam object for configuration
// @return	Function / Class
// @api 	Public
// ****************************************************************************
module.exports = function(options){

	// ****************************************************************************
	// NodeClam class definition
	// -----
	// @param	Object	options		Key => Value pairs to override default settings
	// ****************************************************************************
	function NodeClam(options) {
		this.default_scanner = 'clamdscan';

		// Configuration Settings
		this.settings = {
			remove_infected: false,
			quarantine_infected: false,
			scan_log: null,
			debug_mode: false,
			file_list: null,
			scan_recursively: true,
			clamscan: {
				path: '/usr/bin/clamscan',
				scan_archives: true,
				db: null,
				active: true
			},
			clamdscan: {
				path: '/usr/bin/clamdscan',
				config_file: '/etc/clamd.conf',
				multiscan: true,
				reload_db: false,
				active: true
			},
			preference: this.default_scanner
		};

		// Override defaults with user preferences
		this.settings = __.extend(this.settings,options);

		// Backwards compatibilty
		if (this.settings.quarantine_path && !__.isEmpty(this.settings.quarantine_path)) {
			this.settings.quarantine_infected = this.settings.quarantine_path;
		}

		// Determine whether to use clamdscan or clamscan
		this.scanner = this.default_scanner;
		if (this.settings.preference == 'clamscan' && this.settings.clamscan.active === true) {
			this.scanner = 'clamscan';
		}

		// Check to make sure preferred scanner exists
		if (!fs.existsSync(this.settings[this.scanner].path)) {
			// Fall back to other option:
			if (this.scanner == 'clamdscan' && this.settings.clamscan.active === true) {
				this.scanner == 'clamscan';
			} else if (this.scanner == 'clamscan' && this.settings.clamdscan.active === true) {
				this.scanner == 'clamdscan';
			} else {
				throw new Error("No valid virus scanning binaries are active and available!");
			}

			// Neither scanners are available!
			if (!fs.existsSync(this.settings[this.scanner].path)) {
				throw new Error("No valid virus scanning binaries have been found in the paths provided!");
			}
		}

		// Make sure quarantine path exists at specified location
		if (!__.isEmpty(this.settings.quarantine_infected) && !fs.existsSync(this.settings.quarantine_infected)) {
			this.quarantine_infected = false;
			throw new Error("Quarantine path (" + this.quarantine_infected + ") is invalid.");

			if (this.settings.debug_mode)
				console.log("node-clam: Quarantine path (" + this.quarantine_infected + ") is invalid.");
		}

		// Make sure scan_log exists at specified location
		if (!__.isEmpty(this.settings.scan_log) && fs.existsSync(this.settings.scan_log)) {
			this.scan_log = null;

			if (this.settings.debug_mode)
				console.log("node-clam: Scan Log path (" + this.scan_log + ") is invalid.");
		}

		// If using clamscan, make sure definition db exists at specified location
		if (this.scanner == 'clamscan') {
			if (!__.isEmpty(this.settings.clamscan.db) && !fs.existsSync(this.settings.db)) {
				this.db = null;
				if(this.settings.debug_mode)
					console.log("node-clam: Definitions DB path (" + this.db + ") is invalid.");
			}
		}

		// Build clam flags
		this.clam_flags = build_clam_flags(this.scanner, this.settings);
	}

	// ****************************************************************************
	// Checks if a particular file is infected.
	// -----
	// @param	String		file		Path to the file to check
	// @param	Function	callback	What to do after the scan
	// ****************************************************************************
	NodeClam.prototype.is_infected = function(file, callback) {
		if(this.settings.debug_mode)
			console.log("node-clam: Scanning " + file);

		var command = this.settings[this.scanner].path + this.clam_flags + file;

		if(this.settings.debug_mode === true)
			console.log('node-clam: Configured clam command: ' + command);

		// Execute the clam binary with the proper flags
		exec(command, function(err, stdout, stderr) {
			if (err || stderr) {
				if (err) {
					if(err.hasOwnProperty('code') && err.code === 1) {
	    				callback(null, file, true);
		    		} else {
			    		if(self.settings.debug_mode)
				    		console.log(err);
					    callback(err, file, null);
				    }
				} else {
					console.error(stderr);
					callback(err, file, null);
				}
			} else {
				var result = stdout.trim();

				if(result.match(/OK$/)) {
					if(self.settings.debug_mode)
						console.log(file + ' is OK!');
					callback(null, file, false);
				} else {
					if(self.settings.debug_mode)
						console.log(file + ' is INFECTED!');
					callback(null, file, true);
				}
			}
		});
	}

	// ****************************************************************************
	// Scans an array of files or paths. You must provide the full paths of the
	// files and/or paths.
	// -----
	// @param	Array		files		A list of files or paths (full paths) to be scanned.
	// @param	Function	end_cb		What to do after the scan
	// @param	Function	file_cb		What to do after each file has been scanned
	// ****************************************************************************
	NodeClam.prototype.scan_files = function(files, end_cb, file_cb) {
		files = files || [];
		end_cb = end_cb || null;
		files_cb = file_cb || null;

		var bad_files = [];
		var good_files = [];
		var completed_files = 0;
		var self = this;
		var file;

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

		// Slower but more verbose way...
		if (typeof file_cb === 'function') {
			(function scan_file() {
				file = files.shift();
				self.is_infected(file, function(err, file, infected) {
					completed_files++;

					if(!infected) {
						good_files.push(file);
					} else if(infected || err) {
						bad_files.push(file);
					}

					if(__.isFunction(file_cb)) file_cb(err, file, infected);

					if(completed_files >= files.length) {
						if(self.settings.debug_mode) {
							console.log('node-clam: Scan Complete!');
							console.log("node-clam: Bad Files: ");
							console.dir(self.bad_files);
							console.log("node-clam: Good Files: ");
							console.dir(self.good_files);
						}
						if(__.isFunction(end_cb)) end_cb(null, good_files, bad_files);
					}
					// All files have not been scanned yet, scan next item.
					else {
						// Using setTimeout to avoid crazy stack trace madness.
						setTimeout(scan_file, 0);
					}
				});
			})();
		}

		// The MUCH quicker but less-verbose way
		else {
			var all_files = [];
			if (this.scanner === 'clamdscan' && this.scan_recursively === false) {
				for(var i in files) {
					if (!fs.statSync(files[i]).isFile()) {
						all_files = _.uniq(all_files.concat(fs.readdirSync(files[i])));
					} else {
						all_files.push(files[i]);
					}
				}
			} else {
				all_files = files;
			}

			// Make sure there are no dupes... just cause we can
			all_files = __.uniq(all_files);

			// List files by space and escape
			var items = files.map(function(file) {
				return file.replace(/ /g,'\\ ');
			}).join(' ');

			var command = this.settings.clamscan.path + this.clam_flags + items;
			if(this.settings.debug_mode === true)
				console.log('node-clam: Configured clam command: ' + command);

			// Execute the clam binary with the proper flags
			exec(command, function(err, stdout, stderr) {
				if (err || stderr) {
					if(this.settings.debug_mode === true)
						console.error(stderr);
					return end_cb(err, path, null);
				} else {
					var result = stdout.trim();

					if(result.match(/OK$/)) {
						if(self.settings.debug_mode)
							console.log(path + ' is OK!');
						return end_cb(null, path, false);
					} else {
						if(self.settings.debug_mode)
							console.log(path + ' is INFECTED!');
						return end_cb(null, path, true);
					}
				}
			});
		}
	}

	// ****************************************************************************
	// Scans an entire directory. Provides 3 params to end callback: Error, path
	// scanned, and whether its infected or not. To scan multiple directories, pass
	// them as an array to the scan_files method.
	// -----
	// NOTE: While possible, it is NOT advisable to use the file_cb parameter when
	// using the clamscan binary. Doing so with clamdscan is okay, however. This
	// method also allows for non-recursive scanning with the clamdscan binary.
	// -----
	// @param	String		path		The directory to scan files of
	// @param	Function	en_cb	    What to do when all files have been scanned
	// @param   Function    file_cb     What to do after each file has been scanned
	// ****************************************************************************
	NodeClam.prototype.scan_dir = function(path,end_cb,file_cb) {
		var self = this;

		path = path || '';
		end_cb = end_cb || null;
		files_cb = file_cb || null;

		if (typeof path !== 'string' || path.length <= 0) {
			return end_cb(new Error("Invalid path provided! Path must be a string!"));
		}

		// Trim trailing slash
		path = path.replace(/\/$/, '');

		if(this.settings.debug_mode)
			console.log("node-clam: Scanning Directory: " + path);

		// Get all files recursively
		if (this.settings.scan_recursively && typeof file_cb == 'function') {
			exec('find ' + path, function(err, stdout, stderr) {
				if (err || stderr) {
					if(this.settings.debug_mode === true)
						console.error(stderr);
					return end_cb(err, path, null);
				} else {
					var files = stdout.split("\n").map(function(path) { return path.replace(/ /g,'\\ '); });
					self.scan_files(files, end_cb, file_cb);
				}
			});
		}
		// Clamdscan always does recursive, so, here's a way to avoid that if you want...
		else if (this.settings.scan_recursively === false && this.scanner === 'clamdscan') {
			fs.readdir(path, function(err, files) {
				files.filter(function (file) {
					return fs.statSync(file).isFile();
				});

				self.scan_files(files, end_file, file_cb);
			});
		}

		// If you don't care about individual file progress (which is very slow for clamscan but fine for clamdscan...)
		else if (this.settings.scan_recursively && typeof file_cb !== 'function') {
			var command = this.settings.clamscan.path + this.clam_flags + path;

			if(this.settings.debug_mode === true)
				console.log('node-clam: Configured clam command: ' + command);

			// Execute the clam binary with the proper flags
			exec(command, function(err, stdout, stderr) {
				if (err || stderr) {
					if(this.settings.debug_mode === true)
						console.error(stderr);
					return end_cb(err, [], []);
				} else {
					var result = stdout.trim();

					if(result.match(/OK$/)) {
						if(self.settings.debug_mode)
							console.log(path + ' is OK!');
						return end_cb(null, [path], []);
					} else {
						if(self.settings.debug_mode)
							console.log(path + ' is INFECTED!');
						return end_cb(null, [], [path]);
					}
				}
			});
		}
	}

	return new NodeClam(options);
};

// *****************************************************************************
// Builds out the flags based on the configuration the user provided
// -----
// @param	String	scanner		The scanner to use (clamscan or clamdscan)
// @param	Object	settings	The settings used to build the flags
// @return	String				The concatenated clamav flags
// @api		Private
// *****************************************************************************
function build_clam_flags(scanner, settings) {
	var flags_array = ['--no-summary'];

	// Flags specific to clamscan
	if (scanner == 'clamscan') {
		flags_array.push('--stdout');
		// Remove infected files
		if (settings.remove_infected === true) flags_array.push('--remove=yes');
		// Database file
		if (!__.isEmpty(settings.clamscan.db)) flags_array.push('--database=' + settings.clamscan.db);
		// Scan archives
		if (settings.clamscan.scan_archives === true) {
			flags_array.push('--scan-archive=yes');
		} else {
			flags_array.push('--scan-archive=no');
		}
		// Recursive scanning (flag is specific, feature is not)
		if (settings.scan_recursively === true) {
			flags_array.push('-r');
		} else {
			flags_array.push('--recursive=no');
		}
	}

	// Flags specific to clamdscan
	else if (scanner == 'clamdscan') {
		// Remove infected files
		if (settings.remove_infected === true) flags_array.push('--remove');
		// Specify a config file
		if (!__.isEmpty(settings.clamdscan.config_file)) flags_array.push('--config-file=' + settings.clamdscan.config_file);
		// Turn on multi-threaded scanning
		if (settings.clamdscan.multiscan === true) flags_array.push('--multiscan');
		// Reload the virus DB
		if (settings.clamdscan.reload_db === true) flags_array.push('--reload');
	}

	// ***************
	// Common flags
	// ***************

	// Remove infected files
	if (settings.remove_infected !== true) {
		if (!__.isEmpty(settings.quarantine_infected))
			flags_array.push('--move=' + settings.quarantine_infected);
	}
	// Write info to a log
	if (!__.isEmpty(settings.scan_log)) flags_array.push('--log=' + settings.scan_log);
	// Read list of files to scan from a file
	if (!__.isEmpty(settings.file_list)) flags_array.push('--file-list=' + settings.file_list);

	// Build the String
	return ' ' + flags_array.join(' ') + ' ';
}<|endoftext|>


<commit_before>/**
 * Module dependencies
 */
var request = require('superagent')
  , _ = require('underscore');

// API endpoint
var endpoint = 'http://api.tradedoubler.com/1.0/products.json'

var Tradedoubler = module.exports = function Tradedoubler (opts) {
  if (!(this instanceof Tradedoubler)) return new Tradedoubler(opts);

  this._keywords = opts.keywords;
};

Tradedoubler.prototype.id = function (id) {
  return this._id = id, this;
};

Tradedoubler.prototype.done = function (cb) {
  return request
    .get(endpoint)
    .query({token: this._id})
    .end(function (err, res) {
      return cb(err, res.text);
    });
};
<commit_msg>Format errors and results<commit_after>/**
 * Module dependencies
 */
var request = require('superagent')
  , _ = require('underscore');

// API endpoint
var endpoint = 'http://api.tradedoubler.com/1.0/products.json'

var Tradedoubler = module.exports = function Tradedoubler (opts) {
  if (!(this instanceof Tradedoubler)) return new Tradedoubler(opts);

  this._keywords = opts.keywords;
};

Tradedoubler.prototype.id = function (id) {
  return this._id = id, this;
};

Tradedoubler.prototype.done = function (cb) {
  return request
    .get(endpoint)
    .query({token: this._id})
    .query({q: this._keywords})
    .end(function (err, res) {
      // HTTP errors
      if (err) return cb(err);

      // Text errors
      if (res.type === 'text/plain') return cb(new Error(res.text));

      // API errors
      if (err = formatErrors(res.body.errors)) return cb(err);

      // Format results
      var result = format(res.body.products);

      return cb(null, result);
    });
};

var formatErrors = function (errors) {
  var first = _.first(errors);

  if (!first) return null;

  return new Error(first.message);
};

var format = function (products) {
  return products.map(function (p) {
    var offer = formatOffers(p.offers)
      , name = p.name;

    if (!offer || !name) return null;

    return _.extend(offer, {
      name: name
    });
  })
  .filter(function (p) {
    return p !== null;
  });
};

var formatOffers = function (offers) {
  var first = _.first(offers)
    , price = formatPrices(first.priceHistory);

  if (!first || !price) return null;

  return _.extend(price, {
    url: first.productUrl,
    id: first.id
  })
};

var formatPrices = function (prices) {
  var first = _.first(prices);

  if (!first) return null;

  return {
    listPrice: first.price.value,
    currency: first.price.currency
  }
};
<|endoftext|>


<commit_before>var es = require("event-stream");
var power = require("power-doctest");
var gutil = require('gulp-util');
module.exports = function (options) {
    options = options || {};
    return es.map(function (file, callback) {
        var contents = String(file.contents);
        if (!contents || contents.length === 0) {
            return callback(null, file);
        }
        options.filePath = file.path;
        power.runDocTestAsPromise(contents, options)
            .then(function (result) {
                gutil.log(result + " PASSED");
                callback(null, file);
            }).catch(function (error) {
                callback(power.printTestResult(error), file);
            });
    });
};
module.exports.runDocTestAsPromise = power.runDocTestAsPromise;<commit_msg>feat(output): add filename to output<commit_after>var es = require("event-stream");
var power = require("power-doctest");
var gutil = require('gulp-util');
var pather = require("path");
module.exports = function (options) {
    options = options || {};
    return es.map(function (file, callback) {
        var contents = String(file.contents);
        if (!contents || contents.length === 0) {
            return callback(null, file);
        }
        options.filePath = file.path;
        power.runDocTestAsPromise(contents, options)
            .then(function (result) {
                gutil.log(pather.basename(file.path) + " " + result + " PASSED");
                callback(null, file);
            }).catch(function (error) {
                callback(power.printTestResult(error), file);
            });
    });
};
module.exports.runDocTestAsPromise = power.runDocTestAsPromise;<|endoftext|>


<commit_before>var koa = require('koa');
var app = koa();

app.use(function *(){
  this.body = 'Hello Netlight!';
});

app.listen(process.env.PORT || 3000);
<commit_msg>Made it better<commit_after>var koa = require('koa');
var app = koa();

app.use(function *(){
  this.body = 'Hello Netlight!!';
});

app.listen(process.env.PORT || 3000);
<|endoftext|>


<commit_before>var Promise = require('rsvp').Promise;

var System = exports.System = require('systemjs');
var fs = require('fs');

var asp = require('rsvp').denodeify;

var es6Compiler = require('./compilers/es6');
var registerCompiler = require('./compilers/register');
var amdCompiler = require('./compilers/amd');
var cjsCompiler = require('./compilers/cjs');
var globalCompiler = require('./compilers/global');
var builder = require('./lib/builder');

var path = require('path');

var loader, pluginLoader;

function reset() {
  loader = new Loader(System);
  loader.baseURL = System.baseURL;
  loader.paths = { '*': '*.js' };
  loader.config = System.config;

  pluginLoader = new Loader(System);
  pluginLoader.baseURL = System.baseURL;
  pluginLoader.paths = { '*': '*.js' };
  pluginLoader.config = System.config;
  pluginLoader.trace = true;

  pluginLoader._nodeRequire = loader._nodeRequire = require;

  loader.trace = true;
  loader.execute = false;
  loader.pluginLoader = pluginLoader;

  loader.set('@empty', loader.newModule({}));
  pluginLoader.set('@empty', loader.newModule({}));

  amdCompiler.attach(loader);
  amdCompiler.attach(pluginLoader);
  exports.loader = loader;
}
exports.reset = reset;

reset();

exports.build = function(moduleName, outFile, opts) {
  loader.buildOptions = opts;
  opts = opts || {};
  return exports.trace(moduleName, opts.config)
  .then(function(trace) {
    return exports.buildTree(trace.tree, outFile, opts);
  });
};

function compileLoad(load, opts, compilers) {
  return Promise.resolve()
  .then(function() {
    // note which compilers we used
    compilers = compilers || {};
    if (load.metadata.build == false) {
      return {};
    }
    else if (load.metadata.format == 'es6') {
      compilers['es6'] = true;
      return es6Compiler.compile(load, opts, loader);
    }
    else if (load.metadata.format == 'register') {
      compilers['register'] = true;
      return registerCompiler.compile(load, opts, loader);
    }
    else if (load.metadata.format == 'amd') {
      compilers['amd'] = true;
      return amdCompiler.compile(load, opts, loader);
    }
    else if (load.metadata.format == 'cjs') {
      compilers['cjs'] = true;
      return cjsCompiler.compile(load, opts, loader);
    }
    else if (load.metadata.format == 'global') {
      compilers['global'] = true;
      return globalCompiler.compile(load, opts, loader);
    }
    else if (load.metadata.format == 'defined') {
      return {source: ''};
    }
    else {
      throw "unknown format " + load.metadata.format;
    }
  });
}

function buildOutputs(tree, opts, sfx) {
  var names = Object.keys(tree);

  // store plugins with a bundle hook to allow post-processing
  var plugins = {};

  var outputs = [];

  return Promise.all(names.map(function(name) {
    var load = tree[name];

    if (sfx && load.metadata.plugin && (load.metadata.build === false || load.metadata.plugin.build === false))
      outputs.push('System.register("' + load.name + '", [], false, function() { console.log("SystemJS Builder - Plugin for ' + load.name + ' does not support sfx builds"); });\n');

    // support plugin "bundle" reduction hook
    var plugin = load.metadata.plugin;
    if (plugin && load.metadata.build !== false) {
      var entry = plugins[load.metadata.pluginName] = plugins[load.metadata.pluginName] || {
        loads: [],
        bundle: plugin.bundle
      };
      entry.loads.push(load);
    }

    return Promise.resolve(compileLoad(load, opts))
    .then(outputs.push.bind(outputs));
  }))
  .then(function() {
    // apply plugin "bundle" hook
    return Promise.all(Object.keys(pluginBundlers).map(function(pluginName) {
      var entry = pluginBundlers[pluginName];
      return Promise.resolve(entry.bundle(entry.loads, opts))
      .then(outputs.push.bind(outputs));
    }));
  })
  .then(function() {
    return outputs;
  });
}

exports.buildTree = function(tree, outFile, opts) {
  opts = opts || {};
  opts.outFile = outFile;

  loader.buildOptions = opts;

  return buildOutputs(tree, opts, false)
  .then(function(outputs) {
    outputs.unshift('"format register";\n');
    return builder.writeOutput(opts, outputs, loader.baseURL);
  });
};

exports.buildSFX = function(moduleName, outFile, opts) {
  opts = opts || {};
  var config = opts.config;

  opts.outFile = outFile;

  var outputs;

  loader.buildOptions = opts;

  var compilers = {};
  opts.normalize = true;
  return exports.trace(moduleName, config)
  .then(function(trace) {
    moduleName = trace.moduleName;
    return buildOutputs(trace.tree, opts, true);
  })
  .then(function(_outputs) {
    outputs = _outputs;
  })
  // next add sfx headers for formats at the beginning
  .then(function() {
    if (compilers.register && registerCompiler.sfx)
      return registerCompiler.sfx(loader);
  })
  .then(function(result) {
    outputs.unshift(result || '');
    if (compilers.amd && amdCompiler.sfx)
      return amdCompiler.sfx(loader);
  })
  .then(function(result) {
    outputs.unshift(result || '');
    if (compilers.cjs && cjsCompiler.sfx)
      return cjsCompiler.sfx(loader);
  })
  .then(function(result) {
    outputs.unshift(result || '');
    if (compilers.global && globalCompiler.sfx)
      return globalCompiler.sfx(loader);
  })
  // next wrap with the core code
  .then(function(result) {
    outputs.push(result || '');
    return asp(fs.readFile)(path.resolve(__dirname, './sfx/sfx-core.js'));
  })
  .then(function(sfxcore) {
    outputs.unshift("('" + moduleName + "', function(System) {\n");
    outputs.unshift(sfxcore.toString());
  })
  .then(function(result) {
    outputs.push("});");
  })
  .then(function() {
    return builder.writeOutput(opts, outputs, loader.baseURL);
  });
};

exports.loadConfig = function(configFile) {
  return Promise.resolve()
  .then(function() {
    return asp(fs.readFile)(path.resolve(process.cwd(), configFile))
  })
  .then(function(source) {
    var curSystem = global.System;
    global.System = {
      config: function(cfg) {
        loader.config(cfg);
        pluginLoader.config(cfg);
      }
    };
    new Function(source.toString()).call(global);
    global.System = curSystem;
  });
}

exports.config = function(config) {
  var cfg = {};
  for (var p in config) {
    if (p != 'bundles')
      cfg[p] = config[p];
  }
  loader.config(cfg);
  pluginLoader.config(cfg);
};

// returns a new tree containing tree1 n tree2
exports.intersectTrees = function(tree1, tree2) {
  var intersectTree = {};

  var tree1Names = [];
  for (var name in tree1)
    tree1Names.push(name);

  for (var name in tree2) {
    if (tree1Names.indexOf(name) == -1)
      continue;

    intersectTree[name] = tree1[name];
  }

  return intersectTree;
};

// returns a new tree containing tree1 + tree2
exports.addTrees = function(tree1, tree2) {
  var unionTree = {};

  for (var name in tree2)
    unionTree[name] = tree2[name];

  for (var name in tree1)
    unionTree[name] = tree1[name];

  return unionTree;
};

// returns a new tree containing tree1 - tree2
exports.subtractTrees = function(tree1, tree2) {
  var subtractTree = {};

  for (var name in tree1)
    subtractTree[name] = tree1[name];

  for (var name in tree2)
    delete subtractTree[name];

  return subtractTree;
};

// copies a subtree out of the tree
exports.extractTree = function(tree, moduleName) {
  var outTree = {};
  return visitTree(tree, moduleName, null, function(load) {
    outTree[load.name] = load;
  })
  .then(function() {
    return outTree;
  });
};

exports.trace = function(moduleName, config, includePlugins) {
  if (config) {
    exports.config(config);
  }

  var System = loader.global.System;
  loader.global.System = loader;

  var traceTree = {};

  return loader.import(moduleName)
  .then(function() {
    return loader.normalize(moduleName);
  })
  .then(function(_moduleName) {
    moduleName = _moduleName;
    loader.global.System = System;
    return visitTree(loader.loads, moduleName, includePlugins && loader.pluginLoader, function(load) {
      traceTree[load.name] = load;
    });
  })
  .then(function() {
    return {
      moduleName: moduleName,
      tree: traceTree
    };
  })
  .catch(function(e) {
    loader.global.System = System;
    throw e;
  });
}

function visitTree(tree, moduleName, pluginLoader, visit, seen) {
  seen = seen || [];

  if (seen.indexOf(moduleName) != -1)
    return;

  seen.push(moduleName);

  var load = tree[moduleName];

  if (!load)
    return Promise.resolve()

  // visit the deps first
  return Promise.all(load.deps.map(function(dep) {
    return visitTree(tree, load.depMap[dep], pluginLoader, visit, seen);
  })).then(function() {
    if (!pluginLoader)
      return;

    var pluginName = load.metadata.pluginName;
    if (pluginName) {
      return visitTree(pluginLoader.loads, pluginName, pluginLoader, visit, seen);
    }
  })
  .then(function() {
    // if we are the bottom of the tree, visit
    return visit(load);
  });
}
<commit_msg>revert buildOptions, bug fix<commit_after>var Promise = require('rsvp').Promise;

var System = exports.System = require('systemjs');
var fs = require('fs');

var asp = require('rsvp').denodeify;

var es6Compiler = require('./compilers/es6');
var registerCompiler = require('./compilers/register');
var amdCompiler = require('./compilers/amd');
var cjsCompiler = require('./compilers/cjs');
var globalCompiler = require('./compilers/global');
var builder = require('./lib/builder');

var path = require('path');

var loader, pluginLoader;

function reset() {
  loader = new Loader(System);
  loader.baseURL = System.baseURL;
  loader.paths = { '*': '*.js' };
  loader.config = System.config;

  pluginLoader = new Loader(System);
  pluginLoader.baseURL = System.baseURL;
  pluginLoader.paths = { '*': '*.js' };
  pluginLoader.config = System.config;
  pluginLoader.trace = true;
  pluginLoader.import = loader.import = System.import;

  pluginLoader._nodeRequire = loader._nodeRequire = require;

  loader.trace = true;
  loader.execute = false;
  loader.pluginLoader = pluginLoader;

  loader.set('@empty', loader.newModule({}));
  pluginLoader.set('@empty', loader.newModule({}));

  amdCompiler.attach(loader);
  amdCompiler.attach(pluginLoader);
  exports.loader = loader;
}
exports.reset = reset;

reset();

exports.build = function(moduleName, outFile, opts) {
  opts = opts || {};

  return exports.trace(moduleName, opts.config)
  .then(function(trace) {
    return exports.buildTree(trace.tree, outFile, opts);
  });
};

function compileLoad(load, opts, compilers) {
  return Promise.resolve()
  .then(function() {
    // note which compilers we used
    compilers = compilers || {};
    if (load.metadata.build == false) {
      return {};
    }
    else if (load.metadata.format == 'es6') {
      compilers['es6'] = true;
      return es6Compiler.compile(load, opts, loader);
    }
    else if (load.metadata.format == 'register') {
      compilers['register'] = true;
      return registerCompiler.compile(load, opts, loader);
    }
    else if (load.metadata.format == 'amd') {
      compilers['amd'] = true;
      return amdCompiler.compile(load, opts, loader);
    }
    else if (load.metadata.format == 'cjs') {
      compilers['cjs'] = true;
      return cjsCompiler.compile(load, opts, loader);
    }
    else if (load.metadata.format == 'global') {
      compilers['global'] = true;
      return globalCompiler.compile(load, opts, loader);
    }
    else if (load.metadata.format == 'defined') {
      return {source: ''};
    }
    else {
      throw "unknown format " + load.metadata.format;
    }
  });
}

function buildOutputs(tree, opts, sfx) {
  var names = Object.keys(tree);

  // store plugins with a bundle hook to allow post-processing
  var plugins = {};

  var outputs = [];

  return Promise.all(names.map(function(name) {
    var load = tree[name];

    if (sfx && load.metadata.plugin && (load.metadata.build === false || load.metadata.plugin.build === false))
      outputs.push('System.register("' + load.name + '", [], false, function() { console.log("SystemJS Builder - Plugin for ' + load.name + ' does not support sfx builds"); });\n');

    // support plugin "bundle" reduction hook
    var plugin = load.metadata.plugin;
    if (plugin && load.metadata.build !== false) {
      var entry = plugins[load.metadata.pluginName] = plugins[load.metadata.pluginName] || {
        loads: [],
        bundle: plugin.bundle
      };
      entry.loads.push(load);
    }

    return Promise.resolve(compileLoad(load, opts))
    .then(outputs.push.bind(outputs));
  }))
  .then(function() {
    // apply plugin "bundle" hook
    return Promise.all(Object.keys(plugins).map(function(pluginName) {
      var entry = plugins[pluginName];
      return Promise.resolve(entry.bundle.call(pluginLoader, entry.loads, opts))
      .then(outputs.push.bind(outputs));
    }));
  })
  .then(function() {
    return outputs;
  });
}

exports.buildTree = function(tree, outFile, opts) {

  opts = opts || {};
  opts.outFile = outFile;

  return buildOutputs(tree, opts, false)
  .then(function(outputs) {
    outputs.unshift('"format register";\n');
    return builder.writeOutput(opts, outputs, loader.baseURL);
  });
};

exports.buildSFX = function(moduleName, outFile, opts) {

  opts = opts || {};
  var config = opts.config;

  opts.outFile = outFile;

  var outputs;

  var compilers = {};
  opts.normalize = true;
  return exports.trace(moduleName, config)
  .then(function(trace) {
    moduleName = trace.moduleName;
    return buildOutputs(trace.tree, opts, true);
  })
  .then(function(_outputs) {
    outputs = _outputs;
  })
  // next add sfx headers for formats at the beginning
  .then(function() {
    if (compilers.register && registerCompiler.sfx)
      return registerCompiler.sfx(loader);
  })
  .then(function(result) {
    outputs.unshift(result || '');
    if (compilers.amd && amdCompiler.sfx)
      return amdCompiler.sfx(loader);
  })
  .then(function(result) {
    outputs.unshift(result || '');
    if (compilers.cjs && cjsCompiler.sfx)
      return cjsCompiler.sfx(loader);
  })
  .then(function(result) {
    outputs.unshift(result || '');
    if (compilers.global && globalCompiler.sfx)
      return globalCompiler.sfx(loader);
  })
  // next wrap with the core code
  .then(function(result) {
    outputs.push(result || '');
    return asp(fs.readFile)(path.resolve(__dirname, './sfx/sfx-core.js'));
  })
  .then(function(sfxcore) {
    outputs.unshift("('" + moduleName + "', function(System) {\n");
    outputs.unshift(sfxcore.toString());
  })
  .then(function(result) {
    outputs.push("});");
  })
  .then(function() {
    return builder.writeOutput(opts, outputs, loader.baseURL);
  });
};

exports.loadConfig = function(configFile) {
  return Promise.resolve()
  .then(function() {
    return asp(fs.readFile)(path.resolve(process.cwd(), configFile))
  })
  .then(function(source) {
    var curSystem = global.System;
    global.System = {
      config: function(cfg) {
        loader.config(cfg);
        pluginLoader.config(cfg);
      }
    };
    new Function(source.toString()).call(global);
    global.System = curSystem;
  });
}

exports.config = function(config) {
  var cfg = {};
  for (var p in config) {
    if (p != 'bundles')
      cfg[p] = config[p];
  }
  loader.config(cfg);
  pluginLoader.config(cfg);
};

// returns a new tree containing tree1 n tree2
exports.intersectTrees = function(tree1, tree2) {
  var intersectTree = {};

  var tree1Names = [];
  for (var name in tree1)
    tree1Names.push(name);

  for (var name in tree2) {
    if (tree1Names.indexOf(name) == -1)
      continue;

    intersectTree[name] = tree1[name];
  }

  return intersectTree;
};

// returns a new tree containing tree1 + tree2
exports.addTrees = function(tree1, tree2) {
  var unionTree = {};

  for (var name in tree2)
    unionTree[name] = tree2[name];

  for (var name in tree1)
    unionTree[name] = tree1[name];

  return unionTree;
};

// returns a new tree containing tree1 - tree2
exports.subtractTrees = function(tree1, tree2) {
  var subtractTree = {};

  for (var name in tree1)
    subtractTree[name] = tree1[name];

  for (var name in tree2)
    delete subtractTree[name];

  return subtractTree;
};

// copies a subtree out of the tree
exports.extractTree = function(tree, moduleName) {
  var outTree = {};
  return visitTree(tree, moduleName, null, function(load) {
    outTree[load.name] = load;
  })
  .then(function() {
    return outTree;
  });
};

exports.trace = function(moduleName, config, includePlugins) {
  if (config) {
    exports.config(config);
  }

  var System = loader.global.System;
  loader.global.System = loader;

  var traceTree = {};

  return loader.import(moduleName)
  .then(function() {
    return loader.normalize(moduleName);
  })
  .then(function(_moduleName) {
    moduleName = _moduleName;
    loader.global.System = System;
    return visitTree(loader.loads, moduleName, includePlugins && loader.pluginLoader, function(load) {
      traceTree[load.name] = load;
    });
  })
  .then(function() {
    return {
      moduleName: moduleName,
      tree: traceTree
    };
  })
  .catch(function(e) {
    loader.global.System = System;
    throw e;
  });
}

function visitTree(tree, moduleName, pluginLoader, visit, seen) {
  seen = seen || [];

  if (seen.indexOf(moduleName) != -1)
    return;

  seen.push(moduleName);

  var load = tree[moduleName];

  if (!load)
    return Promise.resolve()

  // visit the deps first
  return Promise.all(load.deps.map(function(dep) {
    return visitTree(tree, load.depMap[dep], pluginLoader, visit, seen);
  })).then(function() {
    if (!pluginLoader)
      return;

    var pluginName = load.metadata.pluginName;
    if (pluginName) {
      return visitTree(pluginLoader.loads, pluginName, pluginLoader, visit, seen);
    }
  })
  .then(function() {
    // if we are the bottom of the tree, visit
    return visit(load);
  });
}
<|endoftext|>


<commit_before>'use strict';

function useImmediately(rootNode, use) {
    return function (plugin) {
        var self = this,
            length = self.plugins.length;

        use.apply(self, arguments);

        if (length !== self.plugins.length) {
            plugin(rootNode, self);
        }

        return self;
    };
}

/**
 * Define `Retext`. Exported above, and used to instantiate a new
 * `Retext`.
 *
 * Note that, when parser is a string or not given, the to-require module
 * is first removed from the Require cache. This results in a completly
 * new parser module, and a new TextOM object, thus clearing any changes
 * made to TextOM or the parser and that, for example, the following is
 * true:
 *
 *   !(new Retext().parse() instanceof new Retext().parse().constructor);
 *
 * The following however, is also true:
 *
 *   var retext = new Retext();
 *   retext.parse() instanceof retext.parse().constructor;
 *
 *
 * @param {(Object|String)?} parser - the parser, or its name, to use.
 *                                    Defaults to "parse-english".
 * @api public
 * @constructor
 */
function Retext(parser) {
    var self = this;

    if (!parser) {
        parser = 'parse-english';
    }

    if (typeof parser === 'string') {
        /* istanbul ignore else: TODO / TOSPEC */
        /* Load the parser for vendors without dynamic-require's */
        if (parser === 'parse-english') {
            parser = require('parse-english');
        } else {
            parser = require(parser);
        }

        parser = parser();
    }

    self.parser = parser;
    self.plugins = [];
}

/**
 * `Retext#use` takes a plugin—a humble function—and when the parse
 * method of the Retext instance is called, the plugin will be called
 * with the parsed tree, and the retext instance as arguments.
 *
 * Note that, during the parsing stage, when the `use` method is called
 * by a plugin, the nested plugin is immediately called, before continuing
 * on with its parent plugin.
 *
 * @param {Function} plugin - the plugin to call when parsing.
 * @param {Function?} plugin.attach - called only once with a Retext
 *                                    instance. If you're planning on
 *                                    modifying TextOM or a parser, do it
 *                                    in this method.
 * @return this
 * @api public
 */
Retext.prototype.use = function (plugin) {
    if (typeof plugin !== 'function') {
        throw new TypeError('Illegal invocation: \'' + plugin +
            '\' is not a valid argument for \'Retext.prototype.use\'');
    }

    var self = this,
        plugins = self.plugins;

    if (plugins.indexOf(plugin) === -1) {
        if (plugin.attach) {
            plugin.attach(self);
        }

        plugins.push(plugin);
    }

    return self;
};

/**
 * `Retext#parse` takes a source to be given (and parsed) by the parser.
 * Then, `parse` iterates over all plugins, and allows them to modify the
 * TextOM tree created by the parser.
 *
 * Note that, during the parsing stage, when the `use` plugin is called
 * by a plugin, the nested plugin is immediately called, before continuing
 * on with its parent plugin.
 *
 * @param {(String|Node)?} source - The source to convert.
 * @return {Node} - A RootNode containing the tokenised source.
 * @api public
 */
Retext.prototype.parse = function (source) {
    var self = this,
        parser = self.parser,
        plugins = self.plugins.concat(),
        iterator = -1,
        use = self.use,
        rootNode = parser(source);

    self.use = useImmediately(rootNode, use);

    while (plugins[++iterator]) {
        plugins[iterator](rootNode, this);
    }

    self.use = use;

    return rootNode;
};

/**
 * Expose `Retext`. Used to instantiate a new Retext object.
 */
exports = module.exports = Retext;
<commit_msg>Removed some docs about the previous sandbox “fix”<commit_after>'use strict';

function useImmediately(rootNode, use) {
    return function (plugin) {
        var self = this,
            length = self.plugins.length;

        use.apply(self, arguments);

        if (length !== self.plugins.length) {
            plugin(rootNode, self);
        }

        return self;
    };
}

/**
 * Define `Retext`. Exported above, and used to instantiate a new
 * `Retext`.
 *
 * @param {(Object|String)?} parser - the parser, or its name, to use.
 *                                    Defaults to "parse-english".
 * @api public
 * @constructor
 */
function Retext(parser) {
    var self = this;

    if (!parser) {
        parser = 'parse-english';
    }

    if (typeof parser === 'string') {
        /* istanbul ignore else: TODO / TOSPEC */
        /* Load the parser for vendors without dynamic-require's */
        if (parser === 'parse-english') {
            parser = require('parse-english');
        } else {
            parser = require(parser);
        }

        parser = parser();
    }

    self.parser = parser;
    self.plugins = [];
}

/**
 * `Retext#use` takes a plugin—a humble function—and when the parse
 * method of the Retext instance is called, the plugin will be called
 * with the parsed tree, and the retext instance as arguments.
 *
 * Note that, during the parsing stage, when the `use` method is called
 * by a plugin, the nested plugin is immediately called, before continuing
 * on with its parent plugin.
 *
 * @param {Function} plugin - the plugin to call when parsing.
 * @param {Function?} plugin.attach - called only once with a Retext
 *                                    instance. If you're planning on
 *                                    modifying TextOM or a parser, do it
 *                                    in this method.
 * @return this
 * @api public
 */
Retext.prototype.use = function (plugin) {
    if (typeof plugin !== 'function') {
        throw new TypeError('Illegal invocation: \'' + plugin +
            '\' is not a valid argument for \'Retext.prototype.use\'');
    }

    var self = this,
        plugins = self.plugins;

    if (plugins.indexOf(plugin) === -1) {
        if (plugin.attach) {
            plugin.attach(self);
        }

        plugins.push(plugin);
    }

    return self;
};

/**
 * `Retext#parse` takes a source to be given (and parsed) by the parser.
 * Then, `parse` iterates over all plugins, and allows them to modify the
 * TextOM tree created by the parser.
 *
 * Note that, during the parsing stage, when the `use` plugin is called
 * by a plugin, the nested plugin is immediately called, before continuing
 * on with its parent plugin.
 *
 * @param {(String|Node)?} source - The source to convert.
 * @return {Node} - A RootNode containing the tokenised source.
 * @api public
 */
Retext.prototype.parse = function (source) {
    var self = this,
        parser = self.parser,
        plugins = self.plugins.concat(),
        iterator = -1,
        use = self.use,
        rootNode = parser(source);

    self.use = useImmediately(rootNode, use);

    while (plugins[++iterator]) {
        plugins[iterator](rootNode, this);
    }

    self.use = use;

    return rootNode;
};

/**
 * Expose `Retext`. Used to instantiate a new Retext object.
 */
exports = module.exports = Retext;
<|endoftext|>


<commit_before>'use strict';

var fs         = require('fs');
var Lazy       = require('lazy');
var slidercalc = require('./lib/slidercalc.js');

function beatmapParser() {
  var beatmap = {
    nbCircles: 0,
    nbSliders: 0,
    nbSpinners: 0,
    timingPoints: [],
    breakTimes: [],
    hitObjects: []
  };

  var osuSection;
  var bpmMin;
  var bpmMax;
  var members;

  var timingLines    = [];
  var objectLines    = [];
  var eventsLines    = [];
  var sectionReg     = /^\[([a-zA-Z0-9]+)\]$/;
  var keyValReg      = /^([a-zA-Z0-9]+)[ ]*:[ ]*(.+)$/;
  var curveTypes     = {
    C: "catmull",
    B: "bezier",
    L: "linear",
    P: "pass-through"
  };

  /**
   * Parse additions member
   * @param  {String} str         additions member (sample:add:customSampleIndex:Volume:hitsound)
   * @return {Object} additions   a list of additions
   */
  var parseAdditions = function (str) {
    if (!str) return {};

    var additions = {};
    var adds      = str.split(':');

    if (adds[0] && adds[0] !== '0') {
      var sample;
      switch (adds[0]) {
        case '1':
          sample = 'normal';
          break;
        case '2':
          sample = 'soft';
          break;
        case '3':
          sample = 'drum';
          break;
      }
      additions.sample = sample;
    }

    if (adds[1] && adds[1] !== '0') {
      var addSample;
      switch (adds[1]) {
        case '1':
          addSample = 'normal';
          break;
        case '2':
          addSample = 'soft';
          break;
        case '3':
          addSample = 'drum';
          break;
      }
      additions.additionalSample = addSample;
    }

    if (adds[2] && adds[2] !== '0') { additions.customSampleIndex = parseInt(adds[2]); }
    if (adds[3] && adds[3] !== '0') { additions.hitsoundVolume    = parseInt(adds[3]); }
    if (adds[4])                    { additions.hitsound          = adds[4]; }

    return additions;
  };

  /**
   * Parse a timing line
   * @param  {String} line
   */
  var parseTimingPoint = function (line) {
    members = line.split(',');

    var timingPoint = {
      offset:           parseInt(members[0]),
      beatLength:       parseFloat(members[1]),
      velocity:         1,
      timingSignature:  parseInt(members[2]),
      sampleSetId:      parseInt(members[3]),
      useCustomSamples: (members[4] == 1),
      sampleVolume:     parseInt(members[5]),
      timingChange:     (members[6] == 1),
      kiaiTimeActive:   (members[7] == 1)
    };

    if (!isNaN(timingPoint.beatLength) && timingPoint.beatLength !== 0) {
      if (timingPoint.beatLength > 0) {
        // If positive, beatLength is the length of a beat in milliseconds
        var bpm        = Math.round(60000 / timingPoint.beatLength);
        beatmap.bpmMin = beatmap.bpmMin ? Math.min(beatmap.bpmMin, bpm) : bpm;
        beatmap.bpmMax = beatmap.bpmMax ? Math.max(beatmap.bpmMax, bpm) : bpm;
        timingPoint.bpm    = bpm;
      } else {
        // If negative, beatLength is a velocity factor
        timingPoint.velocity = Math.abs(100 / timingPoint.beatLength);
      }
    }

    beatmap.timingPoints.push(timingPoint);
  };

  /**
   * Parse an object line
   * @param  {String} line
   */
  var parseHitObject = function (line) {
    members = line.split(',');

    var soundType  = members[4];
    var objectType = members[3];

    var hitobject = {
      startTime:  parseInt(members[2]),
      newCombo:   ((objectType & 4) == 4),
      soundTypes: [],
      position: [
        parseInt(members[0]),
        parseInt(members[1])
      ]
    };

    /**
     * sound type is a bitwise flag enum
     * 0 : normal
     * 2 : whistle
     * 4 : finish
     * 8 : clap
     */
    if ((soundType & 2) == 2)              { hitobject.soundTypes.push('whistle'); }
    if ((soundType & 4) == 4)              { hitobject.soundTypes.push('finish');  }
    if ((soundType & 8) == 8)              { hitobject.soundTypes.push('clap');    }
    if (hitobject.soundTypes.length === 0) { hitobject.soundTypes.push('normal'); }

    /**
     * object type is a bitwise flag enum
     * 1: circle
     * 2: slider
     * 8: spinner
     */
    if ((objectType & 1) == 1) {
      // Circle
      beatmap.nbCircles++;
      hitobject.objectName = 'circle';
      hitobject.additions  = parseAdditions(members[5]);
    } else if ((objectType & 8) == 8) {
      // Spinner
      beatmap.nbSpinners++;
      hitobject.objectName = 'spinner';
      hitobject.endTime    = parseInt(members[5]);
      hitobject.additions  = parseAdditions(members[6]);
    } else if ((objectType & 2) == 2) {
      // Slider
      beatmap.nbSliders++;
      hitobject.objectName  = 'slider';
      hitobject.repeatCount = parseInt(members[6]);
      hitobject.pixelLength = parseInt(members[7]);
      hitobject.additions   = parseAdditions(members[10]);
      hitobject.edges       = [];
      hitobject.points      = [
        [hitobject.position[0], hitobject.position[1]]
      ];

      /**
       * Parse slider points
       */
      var points = (members[5] || '').split('|');
      if (points.length) {
        hitobject.curveType = curveTypes[points[0]] || 'unknown';

        for (var i = 1, l = points.length; i < l; i++) {
          var coordinates = points[i].split(':');
          hitobject.points.push([
            parseInt(coordinates[0]),
            parseInt(coordinates[1])
          ]);
        }
      }

      var edgeSounds    = [];
      var edgeAdditions = [];
      if (members[8]) { edgeSounds    = members[8].split('|'); }
      if (members[9]) { edgeAdditions = members[9].split('|'); }

      /**
       Get soundTypes and additions for each slider edge
       */
      for (var j = 0, lgt = hitobject.repeatCount + 1; j < lgt; j++) {
        var edge = {
          soundTypes: [],
          additions: parseAdditions(edgeAdditions[j])
        };

        if (edgeSounds[j]) {
          var sound = edgeSounds[j];
          if ((sound & 2) == 2)             { edge.soundTypes.push('whistle'); }
          if ((sound & 4) == 4)             { edge.soundTypes.push('finish');  }
          if ((sound & 8) == 8)             { edge.soundTypes.push('clap');    }
          if (edge.soundTypes.length === 0) { edge.soundTypes.push('normal');  }
        } else {
          edge.soundTypes.push('normal');
        }

        hitobject.edges.push(edge);
      }

      // get coordinates of the slider endpoint
      var endPoint = slidercalc.getEndPoint(hitobject.curveType, hitobject.pixelLength, hitobject.points);
      if (endPoint && endPoint[0] && endPoint[1]) {
        hitobject.endPosition = [
          Math.round(endPoint[0]),
          Math.round(endPoint[1])
        ];
      }
    } else {
      // Unknown
      hitobject.objectName = 'unknown';
    }

    beatmap.hitObjects.push(hitobject);
  };

  /**
   * Parse an event line
   * @param  {String} line
   */
  var parseEvent = function (line) {
    /**
     * Background line : 0,0,"bg.jpg"
     * TODO: confirm that the second member is always zero
     *
     * Breaktimes lines : 2,1000,2000
     * second integer is start offset
     * third integer is end offset
     */
    members = line.split(',');

    if (members[0] == '0' && members[1] == '0' && members[2]) {
      var bgName = members[2].trim();

      if (bgName.charAt(0) == '"' && bgName.charAt(bgName.length - 1) == '"') {
        beatmap.bgFilename = bgName.substring(1, bgName.length - 1);
      } else {
        beatmap.bgFilename = bgName;
      }
    } else if (members[0] == '2' && /^[0-9]+$/.test(members[1]) && /^[0-9]+$/.test(members[2])) {
      beatmap.breakTimes.push({
        startTime: parseInt(members[1]),
        endTime: parseInt(members[2])
      });
    }
  };

  /**
   * Compute the total time and the draining time of the beatmap
   */
  var computeDuration = function () {
    var firstObject = beatmap.hitObjects[0];
    var lastObject  = beatmap.hitObjects[beatmap.hitObjects.length - 1];

    var totalBreakTime = 0;

    beatmap.breakTimes.forEach(function (breakTime) {
      totalBreakTime += (breakTime.endTime - breakTime.startTime);
    });

    if (firstObject && lastObject) {
      beatmap.totalTime    = Math.floor(lastObject.startTime / 1000);
      beatmap.drainingTime = Math.floor((lastObject.startTime - firstObject.startTime - totalBreakTime) / 1000);
    } else {
      beatmap.totalTime    = 0;
      beatmap.drainingTime = 0;
    }
  };

  /**
   * Browse objects and compute max combo
   */
  var computeMaxCombo = function () {
    if (beatmap.timingPoints.length === 0) { return; }

    var maxCombo         = 0;
    var sliderMultiplier = parseFloat(beatmap.SliderMultiplier);
    var sliderTickRate   = parseInt(beatmap.SliderTickRate, 10);

    var timingPoints  = beatmap.timingPoints;
    var currentTiming = timingPoints[0];
    var nextOffset    = timingPoints[1] ? timingPoints[1].offset : Infinity;
    var i = 1;

    beatmap.hitObjects.forEach(function (hitObject) {
      if (hitObject.startTime >= nextOffset) {
        currentTiming = timingPoints[i++];
        nextOffset = timingPoints[i] ? timingPoints[i].offset : Infinity;
      }

      var osupxPerBeat = sliderMultiplier * 100 * currentTiming.velocity;
      var tickLength   = osupxPerBeat / sliderTickRate;

      switch (hitObject.objectName) {
        case 'spinner':
        case 'circle':
          maxCombo++;
          break;
        case 'slider':
          var tickPerSide = Math.ceil((Math.floor(hitObject.pixelLength / tickLength * 100) / 100) - 1);
          maxCombo += (hitObject.edges.length - 1) * (tickPerSide + 1) + 1;  // 1 combo for each tick and endpoint
      }
    });

    beatmap.maxCombo = maxCombo;
  };

  /**
   * Read a single line, parse when key/value, store when further parsing needed
   * @param  {String|Buffer} line
   */
  var readLine = function (line) {
    line = line.toString().trim();
    if (!line) { return; }

    var match = sectionReg.exec(line);
    if (match) {
      osuSection = match[1].toLowerCase();
      return;
    }

    switch (osuSection) {
    case 'timingpoints':
      timingLines.push(line);
      break;
    case 'hitobjects':
      objectLines.push(line);
      break;
    case 'events':
      eventsLines.push(line);
      break;
    default:
      if (!osuSection) {
        match = /^osu file format (v[0-9]+)$/.exec(line);
        if (match) {
          beatmap.fileFormat = match[1];
          return;
        }
      }

      /**
       * Apart from events, timingpoints and hitobjects sections, lines are "key: value"
       */
      match = keyValReg.exec(line);
      if (match) { beatmap[match[1]] = match[2]; }
    }
  };

  /**
   * Compute everything that require the file to be completely parsed and return the beatmap
   * @return {Object} beatmap
   */
  var buildBeatmap = function () {
    if (beatmap.Tags) {
      beatmap.tagsArray = beatmap.Tags.split(' ');
    }

    eventsLines.forEach(parseEvent);
    timingLines.forEach(parseTimingPoint);
    objectLines.forEach(parseHitObject);

    beatmap.breakTimes.sort(function (a, b) { return (a.startTime > b.startTime ? 1 : -1); });
    beatmap.timingPoints.sort(function (a, b) { return (a.offset > b.offset ? 1 : -1); });
    beatmap.hitObjects.sort(function (a, b) { return (a.startTime > b.startTime ? 1 : -1); });

    var timingPoints = beatmap.timingPoints;

    for (var i = 1, l = timingPoints.length; i < l; i++) {
      if (!timingPoints[i].hasOwnProperty('bpm')) {
        timingPoints[i].bpm = timingPoints[i - 1].bpm;
      }
    }

    computeMaxCombo();
    computeDuration();

    return beatmap;
  };

  return {
    readLine: readLine,
    buildBeatmap: buildBeatmap
  };
}

/**
 * Parse a .osu file
 * @param  {String}   file  path to the file
 * @param  {Function} callback(err, beatmap)
 */
exports.parseFile = function (file, callback) {
  fs.exists(file, function (exists) {
    if (!exists) {
      callback(new Error('file does not exist'));
      return;
    }

    var parser = beatmapParser();

    var lazy = new Lazy(fs.createReadStream(file));
    lazy
    .map(String)
    .lines
    .forEach(function (line) { parser.readLine(line); });

    lazy.on('error', function (err) {
      callback(err);
    });

    lazy.on('end', function () {
      var beatmap = parser.buildBeatmap();
      callback(null, beatmap);
    });
  });
};

/**
 * Parse a stream containing .osu content
 * @param  {Stream}   stream
 * @param  {Function} callback(err, beatmap)
 */
exports.parseStream = function (stream, callback) {
  var parser = beatmapParser();
  var lazy   = new Lazy(stream);
  lazy
  .map(String)
  .lines
  .forEach(function (line) { parser.readLine(line); });

  lazy.on('error', function (err) {
    callback(err);
  });

  lazy.on('end', function () {
    var beatmap = parser.buildBeatmap();
    callback(null, beatmap);
  });
};

/**
 * Parse the content of a .osu
 * @param  {String|Buffer} content
 * @return {Object} beatmap
 */
exports.parseContent = function (content) {
  var parser = beatmapParser();
  content.toString().split(/[\n\r]+/).forEach(function (line) {
    parser.readLine(line);
  });

  return parser.buildBeatmap();
};<commit_msg>if no endPosition approximate with last point<commit_after>'use strict';

var fs         = require('fs');
var Lazy       = require('lazy');
var slidercalc = require('./lib/slidercalc.js');

function beatmapParser() {
  var beatmap = {
    nbCircles: 0,
    nbSliders: 0,
    nbSpinners: 0,
    timingPoints: [],
    breakTimes: [],
    hitObjects: []
  };

  var osuSection;
  var bpmMin;
  var bpmMax;
  var members;

  var timingLines    = [];
  var objectLines    = [];
  var eventsLines    = [];
  var sectionReg     = /^\[([a-zA-Z0-9]+)\]$/;
  var keyValReg      = /^([a-zA-Z0-9]+)[ ]*:[ ]*(.+)$/;
  var curveTypes     = {
    C: "catmull",
    B: "bezier",
    L: "linear",
    P: "pass-through"
  };

  /**
   * Parse additions member
   * @param  {String} str         additions member (sample:add:customSampleIndex:Volume:hitsound)
   * @return {Object} additions   a list of additions
   */
  var parseAdditions = function (str) {
    if (!str) return {};

    var additions = {};
    var adds      = str.split(':');

    if (adds[0] && adds[0] !== '0') {
      var sample;
      switch (adds[0]) {
        case '1':
          sample = 'normal';
          break;
        case '2':
          sample = 'soft';
          break;
        case '3':
          sample = 'drum';
          break;
      }
      additions.sample = sample;
    }

    if (adds[1] && adds[1] !== '0') {
      var addSample;
      switch (adds[1]) {
        case '1':
          addSample = 'normal';
          break;
        case '2':
          addSample = 'soft';
          break;
        case '3':
          addSample = 'drum';
          break;
      }
      additions.additionalSample = addSample;
    }

    if (adds[2] && adds[2] !== '0') { additions.customSampleIndex = parseInt(adds[2]); }
    if (adds[3] && adds[3] !== '0') { additions.hitsoundVolume    = parseInt(adds[3]); }
    if (adds[4])                    { additions.hitsound          = adds[4]; }

    return additions;
  };

  /**
   * Parse a timing line
   * @param  {String} line
   */
  var parseTimingPoint = function (line) {
    members = line.split(',');

    var timingPoint = {
      offset:           parseInt(members[0]),
      beatLength:       parseFloat(members[1]),
      velocity:         1,
      timingSignature:  parseInt(members[2]),
      sampleSetId:      parseInt(members[3]),
      useCustomSamples: (members[4] == 1),
      sampleVolume:     parseInt(members[5]),
      timingChange:     (members[6] == 1),
      kiaiTimeActive:   (members[7] == 1)
    };

    if (!isNaN(timingPoint.beatLength) && timingPoint.beatLength !== 0) {
      if (timingPoint.beatLength > 0) {
        // If positive, beatLength is the length of a beat in milliseconds
        var bpm        = Math.round(60000 / timingPoint.beatLength);
        beatmap.bpmMin = beatmap.bpmMin ? Math.min(beatmap.bpmMin, bpm) : bpm;
        beatmap.bpmMax = beatmap.bpmMax ? Math.max(beatmap.bpmMax, bpm) : bpm;
        timingPoint.bpm    = bpm;
      } else {
        // If negative, beatLength is a velocity factor
        timingPoint.velocity = Math.abs(100 / timingPoint.beatLength);
      }
    }

    beatmap.timingPoints.push(timingPoint);
  };

  /**
   * Parse an object line
   * @param  {String} line
   */
  var parseHitObject = function (line) {
    members = line.split(',');

    var soundType  = members[4];
    var objectType = members[3];

    var hitObject = {
      startTime:  parseInt(members[2]),
      newCombo:   ((objectType & 4) == 4),
      soundTypes: [],
      position: [
        parseInt(members[0]),
        parseInt(members[1])
      ]
    };

    /**
     * sound type is a bitwise flag enum
     * 0 : normal
     * 2 : whistle
     * 4 : finish
     * 8 : clap
     */
    if ((soundType & 2) == 2)              { hitObject.soundTypes.push('whistle'); }
    if ((soundType & 4) == 4)              { hitObject.soundTypes.push('finish');  }
    if ((soundType & 8) == 8)              { hitObject.soundTypes.push('clap');    }
    if (hitObject.soundTypes.length === 0) { hitObject.soundTypes.push('normal'); }

    /**
     * object type is a bitwise flag enum
     * 1: circle
     * 2: slider
     * 8: spinner
     */
    if ((objectType & 1) == 1) {
      // Circle
      beatmap.nbCircles++;
      hitObject.objectName = 'circle';
      hitObject.additions  = parseAdditions(members[5]);
    } else if ((objectType & 8) == 8) {
      // Spinner
      beatmap.nbSpinners++;
      hitObject.objectName = 'spinner';
      hitObject.endTime    = parseInt(members[5]);
      hitObject.additions  = parseAdditions(members[6]);
    } else if ((objectType & 2) == 2) {
      // Slider
      beatmap.nbSliders++;
      hitObject.objectName  = 'slider';
      hitObject.repeatCount = parseInt(members[6]);
      hitObject.pixelLength = parseInt(members[7]);
      hitObject.additions   = parseAdditions(members[10]);
      hitObject.edges       = [];
      hitObject.points      = [
        [hitObject.position[0], hitObject.position[1]]
      ];

      /**
       * Parse slider points
       */
      var points = (members[5] || '').split('|');
      if (points.length) {
        hitObject.curveType = curveTypes[points[0]] || 'unknown';

        for (var i = 1, l = points.length; i < l; i++) {
          var coordinates = points[i].split(':');
          hitObject.points.push([
            parseInt(coordinates[0]),
            parseInt(coordinates[1])
          ]);
        }
      }

      var edgeSounds    = [];
      var edgeAdditions = [];
      if (members[8]) { edgeSounds    = members[8].split('|'); }
      if (members[9]) { edgeAdditions = members[9].split('|'); }

      /**
       Get soundTypes and additions for each slider edge
       */
      for (var j = 0, lgt = hitObject.repeatCount + 1; j < lgt; j++) {
        var edge = {
          soundTypes: [],
          additions: parseAdditions(edgeAdditions[j])
        };

        if (edgeSounds[j]) {
          var sound = edgeSounds[j];
          if ((sound & 2) == 2)             { edge.soundTypes.push('whistle'); }
          if ((sound & 4) == 4)             { edge.soundTypes.push('finish');  }
          if ((sound & 8) == 8)             { edge.soundTypes.push('clap');    }
          if (edge.soundTypes.length === 0) { edge.soundTypes.push('normal');  }
        } else {
          edge.soundTypes.push('normal');
        }

        hitObject.edges.push(edge);
      }

      // get coordinates of the slider endpoint
      var endPoint = slidercalc.getEndPoint(hitObject.curveType, hitObject.pixelLength, hitObject.points);
      if (endPoint && endPoint[0] && endPoint[1]) {
        hitObject.endPosition = [
          Math.round(endPoint[0]),
          Math.round(endPoint[1])
        ];
      } else {
        // If endPosition could not be calculated, approximate it by setting it to the last point
        hitObject.endPosition = hitObject.points[hitObjects.points.length - 1];
      }
    } else {
      // Unknown
      hitObject.objectName = 'unknown';
    }

    beatmap.hitObjects.push(hitObject);
  };

  /**
   * Parse an event line
   * @param  {String} line
   */
  var parseEvent = function (line) {
    /**
     * Background line : 0,0,"bg.jpg"
     * TODO: confirm that the second member is always zero
     *
     * Breaktimes lines : 2,1000,2000
     * second integer is start offset
     * third integer is end offset
     */
    members = line.split(',');

    if (members[0] == '0' && members[1] == '0' && members[2]) {
      var bgName = members[2].trim();

      if (bgName.charAt(0) == '"' && bgName.charAt(bgName.length - 1) == '"') {
        beatmap.bgFilename = bgName.substring(1, bgName.length - 1);
      } else {
        beatmap.bgFilename = bgName;
      }
    } else if (members[0] == '2' && /^[0-9]+$/.test(members[1]) && /^[0-9]+$/.test(members[2])) {
      beatmap.breakTimes.push({
        startTime: parseInt(members[1]),
        endTime: parseInt(members[2])
      });
    }
  };

  /**
   * Compute the total time and the draining time of the beatmap
   */
  var computeDuration = function () {
    var firstObject = beatmap.hitObjects[0];
    var lastObject  = beatmap.hitObjects[beatmap.hitObjects.length - 1];

    var totalBreakTime = 0;

    beatmap.breakTimes.forEach(function (breakTime) {
      totalBreakTime += (breakTime.endTime - breakTime.startTime);
    });

    if (firstObject && lastObject) {
      beatmap.totalTime    = Math.floor(lastObject.startTime / 1000);
      beatmap.drainingTime = Math.floor((lastObject.startTime - firstObject.startTime - totalBreakTime) / 1000);
    } else {
      beatmap.totalTime    = 0;
      beatmap.drainingTime = 0;
    }
  };

  /**
   * Browse objects and compute max combo
   */
  var computeMaxCombo = function () {
    if (beatmap.timingPoints.length === 0) { return; }

    var maxCombo         = 0;
    var sliderMultiplier = parseFloat(beatmap.SliderMultiplier);
    var sliderTickRate   = parseInt(beatmap.SliderTickRate, 10);

    var timingPoints  = beatmap.timingPoints;
    var currentTiming = timingPoints[0];
    var nextOffset    = timingPoints[1] ? timingPoints[1].offset : Infinity;
    var i = 1;

    beatmap.hitObjects.forEach(function (hitObject) {
      if (hitObject.startTime >= nextOffset) {
        currentTiming = timingPoints[i++];
        nextOffset = timingPoints[i] ? timingPoints[i].offset : Infinity;
      }

      var osupxPerBeat = sliderMultiplier * 100 * currentTiming.velocity;
      var tickLength   = osupxPerBeat / sliderTickRate;

      switch (hitObject.objectName) {
        case 'spinner':
        case 'circle':
          maxCombo++;
          break;
        case 'slider':
          var tickPerSide = Math.ceil((Math.floor(hitObject.pixelLength / tickLength * 100) / 100) - 1);
          maxCombo += (hitObject.edges.length - 1) * (tickPerSide + 1) + 1;  // 1 combo for each tick and endpoint
      }
    });

    beatmap.maxCombo = maxCombo;
  };

  /**
   * Read a single line, parse when key/value, store when further parsing needed
   * @param  {String|Buffer} line
   */
  var readLine = function (line) {
    line = line.toString().trim();
    if (!line) { return; }

    var match = sectionReg.exec(line);
    if (match) {
      osuSection = match[1].toLowerCase();
      return;
    }

    switch (osuSection) {
    case 'timingpoints':
      timingLines.push(line);
      break;
    case 'hitobjects':
      objectLines.push(line);
      break;
    case 'events':
      eventsLines.push(line);
      break;
    default:
      if (!osuSection) {
        match = /^osu file format (v[0-9]+)$/.exec(line);
        if (match) {
          beatmap.fileFormat = match[1];
          return;
        }
      }

      /**
       * Apart from events, timingpoints and hitobjects sections, lines are "key: value"
       */
      match = keyValReg.exec(line);
      if (match) { beatmap[match[1]] = match[2]; }
    }
  };

  /**
   * Compute everything that require the file to be completely parsed and return the beatmap
   * @return {Object} beatmap
   */
  var buildBeatmap = function () {
    if (beatmap.Tags) {
      beatmap.tagsArray = beatmap.Tags.split(' ');
    }

    eventsLines.forEach(parseEvent);
    timingLines.forEach(parseTimingPoint);
    objectLines.forEach(parseHitObject);

    beatmap.breakTimes.sort(function (a, b) { return (a.startTime > b.startTime ? 1 : -1); });
    beatmap.timingPoints.sort(function (a, b) { return (a.offset > b.offset ? 1 : -1); });
    beatmap.hitObjects.sort(function (a, b) { return (a.startTime > b.startTime ? 1 : -1); });

    var timingPoints = beatmap.timingPoints;

    for (var i = 1, l = timingPoints.length; i < l; i++) {
      if (!timingPoints[i].hasOwnProperty('bpm')) {
        timingPoints[i].bpm = timingPoints[i - 1].bpm;
      }
    }

    computeMaxCombo();
    computeDuration();

    return beatmap;
  };

  return {
    readLine: readLine,
    buildBeatmap: buildBeatmap
  };
}

/**
 * Parse a .osu file
 * @param  {String}   file  path to the file
 * @param  {Function} callback(err, beatmap)
 */
exports.parseFile = function (file, callback) {
  fs.exists(file, function (exists) {
    if (!exists) {
      callback(new Error('file does not exist'));
      return;
    }

    var parser = beatmapParser();

    var lazy = new Lazy(fs.createReadStream(file));
    lazy
    .map(String)
    .lines
    .forEach(function (line) { parser.readLine(line); });

    lazy.on('error', function (err) {
      callback(err);
    });

    lazy.on('end', function () {
      var beatmap = parser.buildBeatmap();
      callback(null, beatmap);
    });
  });
};

/**
 * Parse a stream containing .osu content
 * @param  {Stream}   stream
 * @param  {Function} callback(err, beatmap)
 */
exports.parseStream = function (stream, callback) {
  var parser = beatmapParser();
  var lazy   = new Lazy(stream);
  lazy
  .map(String)
  .lines
  .forEach(function (line) { parser.readLine(line); });

  lazy.on('error', function (err) {
    callback(err);
  });

  lazy.on('end', function () {
    var beatmap = parser.buildBeatmap();
    callback(null, beatmap);
  });
};

/**
 * Parse the content of a .osu
 * @param  {String|Buffer} content
 * @return {Object} beatmap
 */
exports.parseContent = function (content) {
  var parser = beatmapParser();
  content.toString().split(/[\n\r]+/).forEach(function (line) {
    parser.readLine(line);
  });

  return parser.buildBeatmap();
};<|endoftext|>


<commit_before>var mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/mydb/testData');

//schema//
var locationSchema = mongoose.Schema({
        city : String,
        country : String,
        position: {
            latitude: Number,
            longitude: Number
        }
    });
//Model//
var Locations = mongoose.model('Locations',locationSchema);

var db = mongoose.connection;
db.on('error',console.error.bind(console,'connection error'));
db.once('open',function(callback){
    console.log('Mongodb is connected');
});

module.exports.create = function (newCity,newCountry,newPosX,newPosY){
    var cityLocation = new Locations({
                                        city:newCity,
                                        country:newCountry,
                                        position:{
                                            latitude:newPosX,
                                            longitude:newPosY
                                        }
                                    })
    console.log(cityLocation.city +' '+ cityLocation.country+' '+cityLocation.latitude+' '+cityLocation.longitude)
    cityLocation.save(function(err,cityLocation){

        if(!err) return ('The object has been added to the db ...');
    });
}

module.exports.update = function(nc){
}

module.exports.find = function(cityName,callback){
    Locations.find({city:cityName},function(err,resultData){
        if(err){
            console.log("Damn! We got an error retrieving your data!");
            return;
        }
        callback(resultData);
    });
}

module.exports.delete = function(nc){
}
<commit_msg>module update<commit_after>var mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/mydb/testData');

//schema//
var locationSchema = mongoose.Schema({
        city : String,
        country : String,
        position: {
            latitude: Number,
            longitude: Number
        }
    });
//Model//
var Locations = mongoose.model('Locations',locationSchema);

var db = mongoose.connection;
db.on('error',console.error.bind(console,'connection error'));
db.once('open',function(callback){
    console.log('Mongodb is connected');
});

module.exports.create = function (newCity,newCountry,newPosX,newPosY){
    var cityLocation = new Locations({
                                        city:newCity,
                                        country:newCountry,
                                        position:{
                                            latitude:newPosX,
                                            longitude:newPosY
                                        }
                                    })
    console.log(cityLocation.city +' '+ cityLocation.country+' '+cityLocation.latitude+' '+cityLocation.longitude)
    cityLocation.save(function(err,cityLocation){

        if(!err) return ('The object has been added to the db ...');
    });
}

module.exports.update = function(cityUpdate,callback){
    Locations.remove({city:new cityUpdate},function(err,resultData){
        if(err){
            console.log("Update succesful!"),
            return;
        }
        callback(resultData);
    });
}

module.exports.find = function(cityName,callback){
    Locations.find({city:cityName},function(err,resultData){
        if(err){
            console.log("Damn! We got an error retrieving your data!");
            return;
        }
        callback(resultData);
    });
}

module.exports.delete = function(nc){
}
<|endoftext|>


<commit_before>var asArray = require('as-array')
var minimist = require('minimist')

var omit = require('ramda/src/omit')
var pipe = require('ramda/src/pipe')
var map = require('ramda/src/map')
var tail = require('ramda/src/tail')
var flatten = require('ramda/src/flatten')
var forEach = require('ramda/src/forEach')
var path = require('ramda/src/path')
var not = require('ramda/src/not')
var prop = require('ramda/src/prop')
var curryN = require('ramda/src/curryN')
var flip = require('ramda/src/flip')
var apply = require('ramda/src/apply')
var contains = require('ramda/src/contains')
var head = require('ramda/src/head')
var or = require('ramda/src/or')
var filter = require('ramda/src/filter')
var keys = require('ramda/src/keys')
var is = require('ramda/src/is')
var contains = require('ramda/src/contains')

var typeAsArray = pipe(prop('type'), asArray)
var getAlias = path(['value', 'alias'])
var valueDoesNotHaveAlias = pipe(getAlias, not)
var isCommand = pipe(typeAsArray, contains('command'))
var isFlag = pipe(typeAsArray, contains('flag'))

var applyFunctions = curryN(4, function applyFunctions (data, flags, tree, context) {

  var computedArgs = is(Function, context.args)
    ? context.args(data, flags, tree)
    : context.args;

  var args = asArray(computedArgs).concat({
    data: data,
    flags: flags,
    options: context.options,
    tree: tree
  })

  return pipe(
    path(['value', 'handler']),
    map(flip(apply)(args))
  )(context)
})

var containsAlias =	 function containsAlias (data, def) {

  return pipe(
    getAlias,
    flip(or)([]),
    contains(head(data))
  )(def)
}

var matchAlias = curryN(2, function matchAlias (data, def) {

  return or(valueDoesNotHaveAlias(def), containsAlias(data, def))
})

function getCommands (data) {

  return pipe(
  	prop('command'),
    filter(matchAlias(data))
  )
}

function getFlags (flags) {

  var matchFlagAliases = pipe(
    keys,
    matchAlias
  )

  return pipe(
    prop('flag'),
    filter(matchFlagAliases(flags)) // TODO: need to format flags input
  )
}

function normalizeTree (raw) {

	var tree = {}

	forEach(branch => {

		var meta = {}

		if (path(['value', 'meta'])(branch)) {

			forEach(m => {

				meta[m.type] = meta[m.type] || []
				meta[m.type].push(m)
			})(branch.value.meta)

			branch.value.meta = meta
		}

		tree[branch.type] = tree[branch.type] || []
		tree[branch.type].push(branch)
	})(flatten(raw))

	return tree
}

module.exports = function run (argv) {

	var input = minimist(argv)
	var data = input._
	var flags = omit('_')(input)

	var tree = normalizeTree(tail(arguments))
	var runner = applyFunctions(data)(flags)(tree)
	var runCommands = pipe(
		getCommands(data),
		map(runner)
	)

	var runFlags = pipe(
		getFlags(flags, tree),
		map(runner)
	)

	runFlags(tree)
	runCommands(tree)
}
<commit_msg>run command flags<commit_after>var asArray = require('as-array')
var minimist = require('minimist')

var omit = require('ramda/src/omit')
var pipe = require('ramda/src/pipe')
var map = require('ramda/src/map')
var tail = require('ramda/src/tail')
var flatten = require('ramda/src/flatten')
var forEach = require('ramda/src/forEach')
var path = require('ramda/src/path')
var pluck = require('ramda/src/pluck')
var not = require('ramda/src/not')
var prop = require('ramda/src/prop')
var curryN = require('ramda/src/curryN')
var flip = require('ramda/src/flip')
var apply = require('ramda/src/apply')
var contains = require('ramda/src/contains')
var or = require('ramda/src/or')
var filter = require('ramda/src/filter')
var keys = require('ramda/src/keys')
var is = require('ramda/src/is')
var defaultTo = require('ramda/src/defaultTo')
var intersection = require('ramda/src/intersection')
var length = require('ramda/src/length')
var gt = require('ramda/src/gt')

var typeAsArray = pipe(prop('type'), asArray)
var getAlias = path(['value', 'alias'])
var valueDoesNotHaveAlias = pipe(getAlias, not)
var isCommand = pipe(typeAsArray, contains('command'))
var isFlag = pipe(typeAsArray, contains('flag'))

var applyFunctions = curryN(4, function applyFunctions (data, flags, tree, context) {

  var computedArgs = is(Function, context.args)
    ? context.args(data, flags, tree)
    : context.args;

  var args = asArray(computedArgs).concat({
    data: data,
    flags: flags,
    options: context.options,
    tree: tree
  })

  return pipe(
    path(['value', 'handler']),
    map(flip(apply)(args))
  )(context)
})

var containsAlias =	 function containsAlias (data, def) {

  return pipe(
    getAlias,
    defaultTo([]),
    intersection(data),
    length,
    flip(gt)(0)
  )(def)
}

var matchAlias = curryN(2, function matchAlias (data, def) {

  return or(
    valueDoesNotHaveAlias(def),
    containsAlias(data, def)
  )
})
var matchFlagAliases = pipe(
  keys,
  matchAlias
)

function getCommands (data) {

  return pipe(
  	prop('command'),
    defaultTo([]),
    filter(matchAlias(data))
  )
}

function getCommandFlags (data, flags) {

  return pipe(
    prop('command'),
    defaultTo([]),
    filter(matchAlias(data)),
    defaultTo([]),
    map(
      pipe(
        path(['value', 'flag']),
        defaultTo([]),
        map(function (value) {

          return {
            type: 'flag',
            value: value
          }
        })
      )
    ),
    flatten,
    filter(matchFlagAliases(flags))
  )
}

function getFlags (flags) {

  return pipe(
    prop('flag'),
    defaultTo([]),
    filter(matchFlagAliases(flags))
  )
}

function normalizeTree (raw) {

	var tree = {}

	forEach(branch => {

		var meta = {}

		if (path(['value', 'meta'])(branch)) {

			forEach(m => {

				meta[m.type] = meta[m.type] || []
				meta[m.type].push(m)
			})(branch.value.meta)

			branch.value.meta = meta
		}

		tree[branch.type] = tree[branch.type] || []
		tree[branch.type].push(branch)
	})(flatten(raw))

	return tree
}

module.exports = function run (argv) {

	var input = minimist(argv)
	var data = input._
	var flags = omit('_')(input)

	var tree = normalizeTree(tail(arguments))
	var runner = applyFunctions(data)(flags)(tree)
	var runCommands = pipe(
		getCommands(data),
		map(runner)
	)
  var runCommandFlags = pipe(
    getCommandFlags(data, flags),
    map(runner)
  )
	var runFlags = pipe(
		getFlags(flags, tree),
		map(runner)
	)

	runFlags(tree)
  runCommandFlags(tree)
	runCommands(tree)
}
<|endoftext|>