<commit_before>///<reference path="../../.d.ts"/>
"use strict";

var jaroWinklerDistance = require("../vendor/jaro-winkler_distance");
import helpers = require("../helpers");
import util = require("util");
import os = require("os");
var options: any = require("../options");

class CommandArgumentsValidationHelper {
	constructor(public isValid: boolean, public remainingArguments: string[]) { }
}

export class CommandsService implements ICommandsService {
	private static HIERARCHICAL_COMMANDS_DELIMITER = "|";
	private static HIERARCHICAL_COMMANDS_DEFAULT_COMMAND_DELIMITER = "|*";
	private static HOOKS_COMMANDS_DELIMITER = "-";
	private areDynamicSubcommandsRegistered = false;

	constructor(private $errors: IErrors,
		private $logger: ILogger,
		private $injector: IInjector,
		private $staticConfig: Config.IStaticConfig,
		private $hooksService: IHooksService,
		private $commandsServiceProvider: ICommandsServiceProvider) {
	}

	public allCommands(includeDev: boolean): string[] {
		var commands = this.$injector.getRegisteredCommandsNames(includeDev);
		return _.reject(commands, (command) => _.contains(command, '|'));
	}

	public executeCommandUnchecked(commandName: string, commandArguments: string[]): IFuture<boolean> {
		return (() => {
			var command = this.$injector.resolveCommand(commandName);

			if(command) {
				if(!this.$staticConfig.disableAnalytics && !command.disableAnalytics) {
					var analyticsService = this.$injector.resolve("analyticsService"); // This should be resolved here due to cyclic dependency
					analyticsService.checkConsent(commandName).wait();
					analyticsService.trackFeature(commandName).wait();
				}
				if(!this.$staticConfig.disableHooks && (command.enableHooks === undefined || command.enableHooks === true)) {
					// Handle correctly hierarchical commands
					var hierarchicalCommandName = this.$injector.buildHierarchicalCommand(commandName, commandArguments);
					if(hierarchicalCommandName) {
						commandName = helpers.stringReplaceAll(hierarchicalCommandName.commandName, CommandsService.HIERARCHICAL_COMMANDS_DEFAULT_COMMAND_DELIMITER, CommandsService.HOOKS_COMMANDS_DELIMITER);
						commandName = helpers.stringReplaceAll(commandName, CommandsService.HIERARCHICAL_COMMANDS_DELIMITER, CommandsService.HOOKS_COMMANDS_DELIMITER); 
					}

					this.$hooksService.initialize(commandName);
					this.$hooksService.executeBeforeHooks().wait();
					command.execute(commandArguments).wait();
					this.$hooksService.executeAfterHooks().wait();

				} else {
					command.execute(commandArguments).wait();
				}
				return true;
			}
			return false;
		}).future<boolean>()();
	}

	private printHelp(commandName: string): IFuture<boolean> {
		options.help = true;
		return this.executeCommandUnchecked("help", [this.beautifyCommandName(commandName)]);
	}

	private executeCommandAction(commandName: string, commandArguments: string[], action: (commandName: string, commandArguments: string[]) => IFuture<boolean>): IFuture<boolean> {
		return this.$errors.beginCommand(
			() => action.apply(this, [commandName, commandArguments]),
			() => this.printHelp(commandName));
	}

	public tryExecuteCommand(commandName: string, commandArguments: string[]): IFuture<void> {
		return (() => {
			if(!this.areDynamicSubcommandsRegistered) {
				this.$commandsServiceProvider.registerDynamicSubCommands();
				this.areDynamicSubcommandsRegistered = true;
			}
			if(this.executeCommandAction(commandName, commandArguments, this.canExecuteCommand).wait()) {
				this.executeCommandAction(commandName, commandArguments, this.executeCommandUnchecked).wait();
			} else {
				// If canExecuteCommand returns false, the command cannot be executed or there's no such command at all.
				var command = this.$injector.resolveCommand(commandName);
				if(command) {
					// If command cannot be executed we should print its help.
					this.printHelp(commandName).wait();
				}
			}
		}).future<void>()();
	}

	private canExecuteCommand(commandName: string, commandArguments: string[], isDynamicCommand?: boolean): IFuture<boolean> {
		return (() => {
			var command = this.$injector.resolveCommand(commandName);
			var beautifiedName = helpers.stringReplaceAll(commandName, "|", " ");
			if(command) {
				// If command wants to handle canExecute logic on its own.
				if(command.canExecute) {
					return command.canExecute(commandArguments).wait();
				}

				// First part of hierarchical commands should be validated in specific way.
				if(this.$injector.isValidHierarchicalCommand(commandName, commandArguments)) {
					return true;
				}

				if(this.validateCommandArguments(command, commandArguments).wait()) {
					return true;
				}

				this.$errors.fail("Unable to execute command '%s'. Use '$ %s %s --help' for help.", beautifiedName, this.$staticConfig.CLIENT_NAME.toLowerCase(), beautifiedName);
				return false;
			} else if(!isDynamicCommand){
				if(_.any(this.$commandsServiceProvider.getDynamicCommands().wait())) {
					this.$commandsServiceProvider.generateDynamicCommands().wait();
					return this.canExecuteCommand(commandName, commandArguments, true).wait();
				}
			}

			this.$logger.fatal("Unknown command '%s'. Use '%s help' for help.", beautifiedName, this.$staticConfig.CLIENT_NAME.toLowerCase());
			this.tryMatchCommand(commandName);

			return false;
		}).future<boolean>()();
	}

	private validateMandatoryParams(commandArguments: string[], mandatoryParams: ICommandParameter[]): IFuture<CommandArgumentsValidationHelper> {
		return (() => {
			var commandArgsHelper = new CommandArgumentsValidationHelper(true, commandArguments);

			if(mandatoryParams.length > 0) {
				// If command has more mandatory params than the passed ones, we shouldn't execute it
				if(mandatoryParams.length > commandArguments.length) {
					var customErrorMessages = _.map(mandatoryParams, mp => mp.errorMessage);
					customErrorMessages.splice(0, 0, "You need to provide all the required parameters.");
					this.$errors.failWithoutHelp(customErrorMessages.join(os.EOL));
				}

				// If we reach here, the commandArguments are at least as much as mandatoryParams. Now we should verify that we have each of them.
				_.each(mandatoryParams, (mandatoryParam) => {
					var argument = _.find(commandArgsHelper.remainingArguments, c => mandatoryParam.validate(c).wait());

					if(argument) {
						commandArgsHelper.remainingArguments = _.without(commandArgsHelper.remainingArguments, argument);
					}
					else {
						this.$errors.fail("Missing mandatory parameter.");
					}
				});
			}
			
			return commandArgsHelper;
		}).future<CommandArgumentsValidationHelper>()();
	}

	private validateCommandArguments(command: ICommand, commandArguments: string[]): IFuture<boolean> {
		return (() => {
			var mandatoryParams: ICommandParameter[] = _.filter(command.allowedParameters, (param) => param.mandatory);
			var commandArgsHelper = this.validateMandatoryParams(commandArguments, mandatoryParams).wait();
			if(!commandArgsHelper.isValid) {<commit_msg>Fix help not displayed if mandatory parameter not provided<commit_after>///<reference path="../../.d.ts"/>
"use strict";

var jaroWinklerDistance = require("../vendor/jaro-winkler_distance");
import helpers = require("../helpers");
import util = require("util");
import os = require("os");
var options: any = require("../options");

class CommandArgumentsValidationHelper {
	constructor(public isValid: boolean, public remainingArguments: string[]) { }
}

export class CommandsService implements ICommandsService {
	private static HIERARCHICAL_COMMANDS_DELIMITER = "|";
	private static HIERARCHICAL_COMMANDS_DEFAULT_COMMAND_DELIMITER = "|*";
	private static HOOKS_COMMANDS_DELIMITER = "-";
	private areDynamicSubcommandsRegistered = false;

	constructor(private $errors: IErrors,
		private $logger: ILogger,
		private $injector: IInjector,
		private $staticConfig: Config.IStaticConfig,
		private $hooksService: IHooksService,
		private $commandsServiceProvider: ICommandsServiceProvider) {
	}

	public allCommands(includeDev: boolean): string[] {
		var commands = this.$injector.getRegisteredCommandsNames(includeDev);
		return _.reject(commands, (command) => _.contains(command, '|'));
	}

	public executeCommandUnchecked(commandName: string, commandArguments: string[]): IFuture<boolean> {
		return (() => {
			var command = this.$injector.resolveCommand(commandName);

			if(command) {
				if(!this.$staticConfig.disableAnalytics && !command.disableAnalytics) {
					var analyticsService = this.$injector.resolve("analyticsService"); // This should be resolved here due to cyclic dependency
					analyticsService.checkConsent(commandName).wait();
					analyticsService.trackFeature(commandName).wait();
				}
				if(!this.$staticConfig.disableHooks && (command.enableHooks === undefined || command.enableHooks === true)) {
					// Handle correctly hierarchical commands
					var hierarchicalCommandName = this.$injector.buildHierarchicalCommand(commandName, commandArguments);
					if(hierarchicalCommandName) {
						commandName = helpers.stringReplaceAll(hierarchicalCommandName.commandName, CommandsService.HIERARCHICAL_COMMANDS_DEFAULT_COMMAND_DELIMITER, CommandsService.HOOKS_COMMANDS_DELIMITER);
						commandName = helpers.stringReplaceAll(commandName, CommandsService.HIERARCHICAL_COMMANDS_DELIMITER, CommandsService.HOOKS_COMMANDS_DELIMITER); 
					}

					this.$hooksService.initialize(commandName);
					this.$hooksService.executeBeforeHooks().wait();
					command.execute(commandArguments).wait();
					this.$hooksService.executeAfterHooks().wait();

				} else {
					command.execute(commandArguments).wait();
				}
				return true;
			}
			return false;
		}).future<boolean>()();
	}

	private printHelp(commandName: string): IFuture<boolean> {
		options.help = true;
		return this.executeCommandUnchecked("help", [this.beautifyCommandName(commandName)]);
	}

	private executeCommandAction(commandName: string, commandArguments: string[], action: (commandName: string, commandArguments: string[]) => IFuture<boolean>): IFuture<boolean> {
		return this.$errors.beginCommand(
			() => action.apply(this, [commandName, commandArguments]),
			() => this.printHelp(commandName));
	}

	public tryExecuteCommand(commandName: string, commandArguments: string[]): IFuture<void> {
		return (() => {
			if(!this.areDynamicSubcommandsRegistered) {
				this.$commandsServiceProvider.registerDynamicSubCommands();
				this.areDynamicSubcommandsRegistered = true;
			}
			if(this.executeCommandAction(commandName, commandArguments, this.canExecuteCommand).wait()) {
				this.executeCommandAction(commandName, commandArguments, this.executeCommandUnchecked).wait();
			} else {
				// If canExecuteCommand returns false, the command cannot be executed or there's no such command at all.
				var command = this.$injector.resolveCommand(commandName);
				if(command) {
					// If command cannot be executed we should print its help.
					this.printHelp(commandName).wait();
				}
			}
		}).future<void>()();
	}

	private canExecuteCommand(commandName: string, commandArguments: string[], isDynamicCommand?: boolean): IFuture<boolean> {
		return (() => {
			var command = this.$injector.resolveCommand(commandName);
			var beautifiedName = helpers.stringReplaceAll(commandName, "|", " ");
			if(command) {
				// If command wants to handle canExecute logic on its own.
				if(command.canExecute) {
					return command.canExecute(commandArguments).wait();
				}

				// First part of hierarchical commands should be validated in specific way.
				if(this.$injector.isValidHierarchicalCommand(commandName, commandArguments)) {
					return true;
				}

				if(this.validateCommandArguments(command, commandArguments).wait()) {
					return true;
				}

				this.$errors.fail("Unable to execute command '%s'. Use '$ %s %s --help' for help.", beautifiedName, this.$staticConfig.CLIENT_NAME.toLowerCase(), beautifiedName);
				return false;
			} else if(!isDynamicCommand){
				if(_.any(this.$commandsServiceProvider.getDynamicCommands().wait())) {
					this.$commandsServiceProvider.generateDynamicCommands().wait();
					return this.canExecuteCommand(commandName, commandArguments, true).wait();
				}
			}

			this.$logger.fatal("Unknown command '%s'. Use '%s help' for help.", beautifiedName, this.$staticConfig.CLIENT_NAME.toLowerCase());
			this.tryMatchCommand(commandName);

			return false;
		}).future<boolean>()();
	}

	private validateMandatoryParams(commandArguments: string[], mandatoryParams: ICommandParameter[]): IFuture<CommandArgumentsValidationHelper> {
		return (() => {
			var commandArgsHelper = new CommandArgumentsValidationHelper(true, commandArguments);

			if(mandatoryParams.length > 0) {
				// If command has more mandatory params than the passed ones, we shouldn't execute it
				if(mandatoryParams.length > commandArguments.length) {
					var customErrorMessages = _.map(mandatoryParams, mp => mp.errorMessage);
					customErrorMessages.splice(0, 0, "You need to provide all the required parameters.");
					this.$errors.fail(customErrorMessages.join(os.EOL));
				}

				// If we reach here, the commandArguments are at least as much as mandatoryParams. Now we should verify that we have each of them.
				_.each(mandatoryParams, (mandatoryParam) => {
					var argument = _.find(commandArgsHelper.remainingArguments, c => mandatoryParam.validate(c).wait());

					if(argument) {
						commandArgsHelper.remainingArguments = _.without(commandArgsHelper.remainingArguments, argument);
					}
					else {
						this.$errors.fail("Missing mandatory parameter.");
					}
				});
			}
			
			return commandArgsHelper;
		}).future<CommandArgumentsValidationHelper>()();
	}

	private validateCommandArguments(command: ICommand, commandArguments: string[]): IFuture<boolean> {
		return (() => {
			var mandatoryParams: ICommandParameter[] = _.filter(command.allowedParameters, (param) => param.mandatory);
			var commandArgsHelper = this.validateMandatoryParams(commandArguments, mandatoryParams).wait();
			if(!commandArgsHelper.isValid) {
{'fraction_non_alphanumeric': 0.1223185111521523, 'fraction_numerical': 0.00042619690296917176, 'mean_word_length': 5.0347192456065155, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 59, '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': '2334422', 'n_tokens_mistral': 4441, 'n_tokens_neox': 3848, 'n_words': 816}