class Discordrb::Commands::CommandChain

Command chain, may have multiple commands, nested and commands

Public Class Methods

new(chain, bot, subchain = false) click to toggle source

@param chain [String] The string the chain should be parsed from. @param bot [CommandBot] The bot that executes this command chain. @param subchain [true, false] Whether this chain is a sub chain of another chain.

# File lib/discordrb/commands/parser.rb, line 143
def initialize(chain, bot, subchain = false)
  @attributes = bot.attributes
  @chain = chain
  @bot = bot
  @subchain = subchain
end

Public Instance Methods

execute(event) click to toggle source

Divides the command chain into chain arguments and command chain, then executes them both. @param event [CommandEvent] The event to execute the command with. @return [String] the result of the command chain execution.

# File lib/discordrb/commands/parser.rb, line 278
def execute(event)
  old_chain = @chain
  @bot.debug 'Executing bare chain'
  result = execute_bare(event)

  @chain_args ||= []

  @bot.debug "Found chain args #{@chain_args}, preliminary result #{result}"

  @chain_args.each do |arg|
    case arg.first
    when 'repeat'
      new_result = ''
      executed_chain = divide_chain(old_chain).last

      arg[1].to_i.times do
        chain_result = CommandChain.new(executed_chain, @bot).execute(event)
        new_result += chain_result if chain_result
      end

      result = new_result
      # TODO: more chain arguments
    end
  end

  result
end
execute_bare(event) click to toggle source

Parses the command chain itself, including sub-chains, and executes it. Executes only the command chain, without its chain arguments. @param event [CommandEvent] The event to execute the chain with. @return [String] the result of the execution.

# File lib/discordrb/commands/parser.rb, line 154
def execute_bare(event)
  b_start = -1
  b_level = 0
  result = ''
  quoted = false
  escaped = false
  hacky_delim, hacky_space, hacky_prev, hacky_newline = [0xe001, 0xe002, 0xe003, 0xe004].pack('U*').chars

  @chain.each_char.each_with_index do |char, index|
    # Escape character
    if char == '\\' && !escaped
      escaped = true
      next
    elsif escaped && b_level <= 0
      result += char
      escaped = false
      next
    end

    if quoted
      # Quote end
      if char == @attributes[:quote_end]
        quoted = false
        next
      end

      if b_level <= 0
        case char
        when @attributes[:chain_delimiter]
          result += hacky_delim
          next
        when @attributes[:previous]
          result += hacky_prev
          next
        when ' '
          result += hacky_space
          next
        when "\n"
          result += hacky_newline
          next
        end
      end
    else
      case char
      when @attributes[:quote_start] # Quote begin
        quoted = true
        next
      when @attributes[:sub_chain_start]
        b_start = index if b_level.zero?
        b_level += 1
      end
    end

    result += char if b_level <= 0

    next unless char == @attributes[:sub_chain_end] && !quoted

    b_level -= 1
    next unless b_level.zero?

    nested = @chain[b_start + 1..index - 1]
    subchain = CommandChain.new(nested, @bot, true)
    result += subchain.execute(event)
  end

  event.respond("Your subchains are mismatched! Make sure you don't have any extra #{@attributes[:sub_chain_start]}'s or #{@attributes[:sub_chain_end]}'s") unless b_level.zero?

  @chain = result

  @chain_args, @chain = divide_chain(@chain)

  prev = ''

  chain_to_split = @chain

  # Don't break if a command is called the same thing as the chain delimiter
  chain_to_split = chain_to_split.slice(1..-1) if !@attributes[:chain_delimiter].empty? && chain_to_split.start_with?(@attributes[:chain_delimiter])

  first = true
  split_chain = if @attributes[:chain_delimiter].empty?
                  [chain_to_split]
                else
                  chain_to_split.split(@attributes[:chain_delimiter])
                end
  split_chain.each do |command|
    command = @attributes[:chain_delimiter] + command if first && @chain.start_with?(@attributes[:chain_delimiter])
    first = false

    command = command.strip

    # Replace the hacky delimiter that was used inside quotes with actual delimiters
    command = command.gsub hacky_delim, @attributes[:chain_delimiter]

    first_space = command.index ' '
    command_name = first_space ? command[0..first_space - 1] : command
    arguments = first_space ? command[first_space + 1..] : ''

    # Append a previous sign if none is present
    arguments += @attributes[:previous] unless arguments.include? @attributes[:previous]
    arguments = arguments.gsub @attributes[:previous], prev

    # Replace hacky previous signs with actual ones
    arguments = arguments.gsub hacky_prev, @attributes[:previous]

    arguments = arguments.split ' '

    # Replace the hacky spaces/newlines with actual ones
    arguments.map! do |elem|
      elem.gsub(hacky_space, ' ').gsub(hacky_newline, "\n")
    end

    # Finally execute the command
    prev = @bot.execute_command(command_name.to_sym, event, arguments, split_chain.length > 1 || @subchain)

    # Stop chain if execute_command failed (maybe the command doesn't exist, or permissions failed, etc.)
    break unless prev
  end

  prev
end

Private Instance Methods

divide_chain(chain) click to toggle source
# File lib/discordrb/commands/parser.rb, line 308
def divide_chain(chain)
  chain_args_index = chain.index(@attributes[:chain_args_delim]) unless @attributes[:chain_args_delim].empty?
  chain_args = []

  if chain_args_index
    chain_args = chain[0..chain_args_index].split ','

    # Split up the arguments

    chain_args.map! do |arg|
      arg.split ' '
    end

    chain = chain[chain_args_index + 1..]
  end

  [chain_args, chain]
end