<commit_before>/*
 * patron - The cleanest command framework for discord.js and eris.
 * Copyright (C) 2019  patron contributors
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
"use strict";
const Registry = require("./Registry.js");
const ArgumentDefault = require("../enums/ArgumentDefault.js");
const ResultType = require("../enums/ResultType.js");
const CommandResult = require("../results/CommandResult.js");
const CooldownResult = require("../results/CooldownResult.js");
const ErrorResult = require("../results/ErrorResult.js");
const ExecutionResult = require("../results/ExecutionResult.js");
const Result = require("../results/Result.js");
const {regexes} = require("../utils/constants.js");
const InvalidOptions = require("../utils/InvalidOptions.js");
const lib = require("../utils/libraryHandler.js");

/**
 * The Handler responsible for executing Commands.
 * @prop {Boolean} defaultRegex Whether or not a custom RegExp for parsing argument was provided.
 * @prop {RegExp} argumentRegex The RegExp used to parse arguments from Messages.
 * @prop {String} separator The separator used to join parts of a remainder argument together.
 * @prop {Registry} registry The Registry that stores Commands and other items.
 */
class Handler {
  /**
   * @arg {Object} options The Handler options.
   * @arg {RegExp} [options.argumentRegex=/"[\S\s]+?"|[\S\n]+/g] The RegExp used to parse arguments from Messages.
   * @arg {String} [options.separator=" "] The separator used to join parts of a remainder argument together.
   * @arg {Registry} options.registry The Registry that stores Commands and other items.
   */
  constructor(options) {
    if(InvalidOptions(options))
      throw ReferenceError("The Handler constructor requires an options object.");

    this.defaultRegex = options.argumentRegex == null;
    this.argumentRegex = this.defaultRegex ? regexes.argument : options.argumentRegex;
    this.separator = options.separator == null ? " " : options.separator;
    this.registry = options.registry;
    Handler.validate(this);
  }

  parseCommand(message, prefixLength) {
    let args = message.content.slice(prefixLength).match(this.argumentRegex);

    if(args == null)
      return CommandResult.fromUnknown("");

    const command = this.registry.getCommand(args[0]);

    if(command == null)
      return CommandResult.fromUnknown(args[0]);
    else if(this.defaultRegex)
      args = args.slice(1).map(arg => arg.replace(regexes.quotes, ""));
    else
      args = args.slice(1);

    return {args, command};
  }

  validateCommand(message, command) {
    const result = lib.validateContext(message, command);

    if(result != null || message.channel.guild == null)
      return result;

    return lib.validatePermissions(message, command);
  }

  async useCooldown(message, command) {
    const guildId = message.channel.guild == null ? undefined : message.channel.guild.id;
    let cooldown = await command.useCooldown(message.author.id, guildId);
    let group;

    if(!cooldown) {
      if(command.group == null)
        return;

      group = this.registry.getGroup(command.group);
      cooldown = await group.useCooldown(message.author.id, guildId);

      if(!cooldown)
        return;
    }

    cooldown = await (group == null ? command : group).getCooldown(message.author.id, guildId);

    return CooldownResult.fromFailure(command, cooldown.resets - Date.now(), group);
  }

  async revertCooldown(message, command) {
    const guildId = message.channel.guild == null ? undefined : message.channel.guild.id;

    await command.revertCooldown(message.author.id, guildId);

    if(command.group != null)
      await this.registry.getGroup(command.group).revertCooldown(message.author.id, guildId);
  }

  async runPreconditions(message, command) {
    for(let i = 0; i < command.preconditions.length; i++) {
      const result = await this.registry.getPrecondition(command.preconditions[i])
        .run(command, message, command.preconditionOptions[i]);

      if(result.type !== ResultType.Success)
        return result;
    }

    if(command.group == null)
      return;

    const group = this.registry.getGroup(command.group);

    for(let i = 0; i < group.preconditions.length; i++) {
      const result = await this.registry.getPrecondition(group.preconditions[i])
        .run(command, message, group.preconditionOptions[i]);

      if(!result.success)
        return result;
    }
  }

  async validateArgument(message, command, info, parsed, input) {
    let result = await this.registry.getTypeReader(info.type).read(input, command, message, info, parsed);

    if(result.type !== ResultType.Success)
      return result;

    const {value} = result;

    for(let i = 0; i < info.preconditions.length; i++) {
      result = await this.registry.getArgumentPrecondition(info.preconditions[i])
        .run(value, command, message, info, parsed, info.preconditionOptions[i]);

      if(result.type !== ResultType.Success)
        return result;
    }

    return value;
  }
<commit_msg>Fix Handler bug with preconditions<commit_after>/*
 * patron - The cleanest command framework for discord.js and eris.
 * Copyright (C) 2019  patron contributors
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
"use strict";
const Registry = require("./Registry.js");
const ArgumentDefault = require("../enums/ArgumentDefault.js");
const ResultType = require("../enums/ResultType.js");
const CommandResult = require("../results/CommandResult.js");
const CooldownResult = require("../results/CooldownResult.js");
const ErrorResult = require("../results/ErrorResult.js");
const ExecutionResult = require("../results/ExecutionResult.js");
const Result = require("../results/Result.js");
const {regexes} = require("../utils/constants.js");
const InvalidOptions = require("../utils/InvalidOptions.js");
const lib = require("../utils/libraryHandler.js");

/**
 * The Handler responsible for executing Commands.
 * @prop {Boolean} defaultRegex Whether or not a custom RegExp for parsing argument was provided.
 * @prop {RegExp} argumentRegex The RegExp used to parse arguments from Messages.
 * @prop {String} separator The separator used to join parts of a remainder argument together.
 * @prop {Registry} registry The Registry that stores Commands and other items.
 */
class Handler {
  /**
   * @arg {Object} options The Handler options.
   * @arg {RegExp} [options.argumentRegex=/"[\S\s]+?"|[\S\n]+/g] The RegExp used to parse arguments from Messages.
   * @arg {String} [options.separator=" "] The separator used to join parts of a remainder argument together.
   * @arg {Registry} options.registry The Registry that stores Commands and other items.
   */
  constructor(options) {
    if(InvalidOptions(options))
      throw ReferenceError("The Handler constructor requires an options object.");

    this.defaultRegex = options.argumentRegex == null;
    this.argumentRegex = this.defaultRegex ? regexes.argument : options.argumentRegex;
    this.separator = options.separator == null ? " " : options.separator;
    this.registry = options.registry;
    Handler.validate(this);
  }

  parseCommand(message, prefixLength) {
    let args = message.content.slice(prefixLength).match(this.argumentRegex);

    if(args == null)
      return CommandResult.fromUnknown("");

    const command = this.registry.getCommand(args[0]);

    if(command == null)
      return CommandResult.fromUnknown(args[0]);
    else if(this.defaultRegex)
      args = args.slice(1).map(arg => arg.replace(regexes.quotes, ""));
    else
      args = args.slice(1);

    return {args, command};
  }

  validateCommand(message, command) {
    const result = lib.validateContext(message, command);

    if(result != null || message.channel.guild == null)
      return result;

    return lib.validatePermissions(message, command);
  }

  async useCooldown(message, command) {
    const guildId = message.channel.guild == null ? undefined : message.channel.guild.id;
    let cooldown = await command.useCooldown(message.author.id, guildId);
    let group;

    if(!cooldown) {
      if(command.group == null)
        return;

      group = this.registry.getGroup(command.group);
      cooldown = await group.useCooldown(message.author.id, guildId);

      if(!cooldown)
        return;
    }

    cooldown = await (group == null ? command : group).getCooldown(message.author.id, guildId);

    return CooldownResult.fromFailure(command, cooldown.resets - Date.now(), group);
  }

  async revertCooldown(message, command) {
    const guildId = message.channel.guild == null ? undefined : message.channel.guild.id;

    await command.revertCooldown(message.author.id, guildId);

    if(command.group != null)
      await this.registry.getGroup(command.group).revertCooldown(message.author.id, guildId);
  }

  async runPreconditions(message, command) {
    for(let i = 0; i < command.preconditions.length; i++) {
      const result = await this.registry.getPrecondition(command.preconditions[i])
        .run(command, message, command.preconditionOptions[i]);

      if(result.type !== ResultType.Success)
        return result;
    }

    if(command.group == null)
      return;

    const group = this.registry.getGroup(command.group);

    for(let i = 0; i < group.preconditions.length; i++) {
      const result = await this.registry.getPrecondition(group.preconditions[i])
        .run(command, message, group.preconditionOptions[i]);

      if(result.type !== ResultType.Success)
        return result;
    }
  }

  async validateArgument(message, command, info, parsed, input) {
    let result = await this.registry.getTypeReader(info.type).read(input, command, message, info, parsed);

    if(result.type !== ResultType.Success)
      return result;

    const {value} = result;

    for(let i = 0; i < info.preconditions.length; i++) {
      result = await this.registry.getArgumentPrecondition(info.preconditions[i])
        .run(value, command, message, info, parsed, info.preconditionOptions[i]);

      if(result.type !== ResultType.Success)
        return result;
    }

    return value;
  }

{'fraction_non_alphanumeric': 0.12117730245713275, 'fraction_numerical': 0.0021212656885274883, 'mean_word_length': 3.675619834710744, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 7, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, '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': '2851682', 'n_tokens_mistral': 3250, 'n_tokens_neox': 3132, 'n_words': 917}