<commit_before>var Promise = require('es6-promise').Promise;

/**
 * Main CrispHooks constructor.  This creates an object to which hooks can be added, and
 * on which hooks can be called.
 *
 * @class CrispHooks
 * @constructor
 * @param {Object} options - Options changing the behavior of the hooks object
 * @param {Boolean} options.eventEmitter - Add EventEmitter-style aliases
 */
function CrispHooks(options) {
	if (options && options.eventEmitter) {
		this.on = CrispHooks.prototype.hookSync;
		this.emit = CrispHooks.prototype.triggerSync;
	}
	this._hooks = {};
}

CrispHooks.prototype._addHook = function(args) {
	var name = args.shift();
	var priority = (typeof args[0] === 'number') ? args.shift() : 0;
	var handler = args.shift();
	var errorHandler = args.shift();

	if(args.length !== 0) throwError('Too many arguments to addHook()');

	var nameHooks;
	if(this._hooks[name]) {
		nameHooks = this._hooks[name];
	} else {
		nameHooks = this._hooks[name] = {
			hooks: [],
			sorted: true,
			hookCtr: 0
		};
	}

	nameHooks.hooks.push({
		priority: priority,
		hookNumber: nameHooks.hookCtr++,
		handler: handler,
		errorHandler: errorHandler
	});
	if(nameHooks.hooks.length > 1) {
		nameHooks.sorted = false;
	}

	return this;
};

/**
 * Add a synchronous hook.
 *
 * @param {String} name - The name of the event to add a hook to
 * @param {Number} [priority] - Numeric priority for the order of the hook execution.  Lower priorities are executed first.
 * @param {Function} handler - Handler for the synchronous hook.  Should throw on error.
 * @param {Mixed} [handler.arg1...] - Arguments passed to trigger.
 * @param {Function} [errorHandler] - Handler in case of error.  Is only called if an error occurs after handler is called, and should
 * clean up if necessary.  They are called in reverse order of handler.
 * @param {Mixed} errorHandler.error - Error supplied to the error handler
 * @return {CrispHooks} - this
 */
CrispHooks.prototype.hookSync = function() {
	return this._addHook(toArray(arguments));
};

/**
 * Add an asynchronous hook utilizing a callback instead a promise to indicate asynchronous
 * behavior.
 *
 * @param {String} name - The name of the event to add a hook to
 * @param {Number} [priority] - Numeric priority for the order of the hook execution.  Lower priorities are executed first.
 * @param {Function} handler - Handler for the synchronous hook.  Should throw on error.
 * @param {Function} handler.next - Callback given to handler
 * @param {Mixed} [handler.arg1...] - Arguments passed to trigger.
 * @param {Function} [errorHandler] - Handler in case of error.  Is only called if an error occurs after handler is called, and should
 * clean up if necessary.  They are called in reverse order of handler.
 * @param {Function} errorHandler.next - Callback given to error handler
 * @param {Mixed} errorHandler.error - Error supplied to the error handler
 * @return {CrispHooks} - this
 */
CrispHooks.prototype.hookAsync = function() {
	var args = toArray(arguments);
	var name = args.shift();
	var priority = (typeof args[0] === 'number') ? args.shift() : 0;
	var handler = args.shift();
	var errorHandler = args.shift();
	this._addHook([
		name,
		priority,
		handler && function() {
			var self = this;
			var args = toArray(arguments);
			return new Promise(function(resolve, reject) {
				handler.apply(self, [function(error, result) {
					if(error) reject(error);
					else resolve(result);
				}].concat(args));
			});
		},
		errorHandler && function() {
			var self = this;
			var args = toArray(arguments);
			return new Promise(function(resolve, reject) {
				errorHandler.apply(self, [function(error) {
					if(error) reject(error);
					else resolve();
				}].concat(args));
			});
		}
	]);
};

/**
 * Add a hook that may either be synchronous or asynchronous.  Both the handler and the error handler
 * may optionally return a promise, indicating that the result will be available asynchronously.  If
 * they return a non-promise or throw an exception, it is treated as a synchronous hook.
 *
 * @param {String} name - The name of the event to add a hook to
 * @param {Number} [priority] - Numeric priority for the order of the hook execution.  Lower priorities are executed first.
 * @param {Function} handler - Handler for the synchronous hook.  Should throw on error.
 * @param {Mixed} [handler.arg1...] - Arguments passed to trigger.
 * @param {Function} [errorHandler] - Handler in case of error.  Is only called if an error occurs after handler is called, and should
 * clean up if necessary.  They are called in reverse order of handler.
 * @param {Mixed} errorHandler.error - Error supplied to the error handler
 * @return {CrispHooks} - this
 */
CrispHooks.prototype.hook = function() {
	return this._addHook(toArray(arguments));
};

CrispHooks.prototype._sortHooks = function(name) {
	var nameHooks = this._hooks[name];
	if(!nameHooks || !nameHooks.hooks || nameHooks.hooks.length <= 1 || nameHooks.sorted) return;
	nameHooks.hooks.sort(function(a, b) {
		if(a.priority > b.priority) return 1;
		if(b.priority > a.priority) return -1;
		return a.hookCtr - b.hookCtr;
	});
	nameHooks.sorted = true;
};

CrispHooks.prototype._trigger = function(args, triggerWithError) {
	var thisPtr = (typeof args[0] == 'string') ? this : args.shift();
	var name = args.shift();

	var nameHooks = this._hooks[name];
	if(!nameHooks) return Promise.resolve([]);

	this._sortHooks(name);
	// Duplicate hook array to prevent weird behavior if hooks are modified
	// while being run.
	var hookArray = nameHooks.hooks.slice(0);<commit_msg>Fix hook order priority tie breaker<commit_after>var Promise = require('es6-promise').Promise;

/**
 * Main CrispHooks constructor.  This creates an object to which hooks can be added, and
 * on which hooks can be called.
 *
 * @class CrispHooks
 * @constructor
 * @param {Object} options - Options changing the behavior of the hooks object
 * @param {Boolean} options.eventEmitter - Add EventEmitter-style aliases
 */
function CrispHooks(options) {
	if (options && options.eventEmitter) {
		this.on = CrispHooks.prototype.hookSync;
		this.emit = CrispHooks.prototype.triggerSync;
	}
	this._hooks = {};
}

CrispHooks.prototype._addHook = function(args) {
	var name = args.shift();
	var priority = (typeof args[0] === 'number') ? args.shift() : 0;
	var handler = args.shift();
	var errorHandler = args.shift();

	if(args.length !== 0) throwError('Too many arguments to addHook()');

	var nameHooks;
	if(this._hooks[name]) {
		nameHooks = this._hooks[name];
	} else {
		nameHooks = this._hooks[name] = {
			hooks: [],
			sorted: true,
			hookCtr: 0
		};
	}

	nameHooks.hooks.push({
		priority: priority,
		hookNumber: nameHooks.hookCtr++,
		handler: handler,
		errorHandler: errorHandler
	});
	if(nameHooks.hooks.length > 1) {
		nameHooks.sorted = false;
	}

	return this;
};

/**
 * Add a synchronous hook.
 *
 * @param {String} name - The name of the event to add a hook to
 * @param {Number} [priority] - Numeric priority for the order of the hook execution.  Lower priorities are executed first.
 * @param {Function} handler - Handler for the synchronous hook.  Should throw on error.
 * @param {Mixed} [handler.arg1...] - Arguments passed to trigger.
 * @param {Function} [errorHandler] - Handler in case of error.  Is only called if an error occurs after handler is called, and should
 * clean up if necessary.  They are called in reverse order of handler.
 * @param {Mixed} errorHandler.error - Error supplied to the error handler
 * @return {CrispHooks} - this
 */
CrispHooks.prototype.hookSync = function() {
	return this._addHook(toArray(arguments));
};

/**
 * Add an asynchronous hook utilizing a callback instead a promise to indicate asynchronous
 * behavior.
 *
 * @param {String} name - The name of the event to add a hook to
 * @param {Number} [priority] - Numeric priority for the order of the hook execution.  Lower priorities are executed first.
 * @param {Function} handler - Handler for the synchronous hook.  Should throw on error.
 * @param {Function} handler.next - Callback given to handler
 * @param {Mixed} [handler.arg1...] - Arguments passed to trigger.
 * @param {Function} [errorHandler] - Handler in case of error.  Is only called if an error occurs after handler is called, and should
 * clean up if necessary.  They are called in reverse order of handler.
 * @param {Function} errorHandler.next - Callback given to error handler
 * @param {Mixed} errorHandler.error - Error supplied to the error handler
 * @return {CrispHooks} - this
 */
CrispHooks.prototype.hookAsync = function() {
	var args = toArray(arguments);
	var name = args.shift();
	var priority = (typeof args[0] === 'number') ? args.shift() : 0;
	var handler = args.shift();
	var errorHandler = args.shift();
	this._addHook([
		name,
		priority,
		handler && function() {
			var self = this;
			var args = toArray(arguments);
			return new Promise(function(resolve, reject) {
				handler.apply(self, [function(error, result) {
					if(error) reject(error);
					else resolve(result);
				}].concat(args));
			});
		},
		errorHandler && function() {
			var self = this;
			var args = toArray(arguments);
			return new Promise(function(resolve, reject) {
				errorHandler.apply(self, [function(error) {
					if(error) reject(error);
					else resolve();
				}].concat(args));
			});
		}
	]);
};

/**
 * Add a hook that may either be synchronous or asynchronous.  Both the handler and the error handler
 * may optionally return a promise, indicating that the result will be available asynchronously.  If
 * they return a non-promise or throw an exception, it is treated as a synchronous hook.
 *
 * @param {String} name - The name of the event to add a hook to
 * @param {Number} [priority] - Numeric priority for the order of the hook execution.  Lower priorities are executed first.
 * @param {Function} handler - Handler for the synchronous hook.  Should throw on error.
 * @param {Mixed} [handler.arg1...] - Arguments passed to trigger.
 * @param {Function} [errorHandler] - Handler in case of error.  Is only called if an error occurs after handler is called, and should
 * clean up if necessary.  They are called in reverse order of handler.
 * @param {Mixed} errorHandler.error - Error supplied to the error handler
 * @return {CrispHooks} - this
 */
CrispHooks.prototype.hook = function() {
	return this._addHook(toArray(arguments));
};

CrispHooks.prototype._sortHooks = function(name) {
	var nameHooks = this._hooks[name];
	if(!nameHooks || !nameHooks.hooks || nameHooks.hooks.length <= 1 || nameHooks.sorted) return;
	nameHooks.hooks.sort(function(a, b) {
		if(a.priority > b.priority) return 1;
		if(b.priority > a.priority) return -1;
		return a.hookNumber - b.hookNumber;
	});
	nameHooks.sorted = true;
};

CrispHooks.prototype._trigger = function(args, triggerWithError) {
	var thisPtr = (typeof args[0] == 'string') ? this : args.shift();
	var name = args.shift();

	var nameHooks = this._hooks[name];
	if(!nameHooks) return Promise.resolve([]);

	this._sortHooks(name);
	// Duplicate hook array to prevent weird behavior if hooks are modified
	// while being run.
	var hookArray = nameHooks.hooks.slice(0);
{'fraction_non_alphanumeric': 0.11906461977865049, 'fraction_numerical': 0.002856122813280971, 'mean_word_length': 3.86328125, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1592053', 'n_tokens_mistral': 3561, 'n_tokens_neox': 3380, 'n_words': 1206}