<commit_before># encoding: utf-8
# This file is part of the K5 bot project.
# See files README.md and COPYING for copyright and licensing information.

# Loader plugin loads or reloads plugins

require_relative '../../IRCPlugin'

class Loader < IRCPlugin
  Description = "Loads, reloads, and unloads plugins."
  Commands = {
    :load => "loads or reloads specified plugin",
    :unload => "unloads specified plugin",
    # :reload_core => "reloads core files"
  }

  def on_privmsg(msg)
    case msg.botcommand
    when :load
      return unless msg.tail
      plugins = msg.tail.split

      plugins_to_load = {}

      cycle_while_reducing(plugins) do |name|
        exists = !!(@plugin_manager.plugins[name.to_sym])
        unload_successful = !exists || !!(@plugin_manager.unload_plugin name)
        if unload_successful
          plugins_to_load[name] = exists
          true
        else
          false
        end
      end

      plugins.each do |name|
        msg.reply "Cannot unload '#{name}'."
      end

      # Since hash in Ruby 1.9 preserves order of addition,
      # reverse order would be the best one for loading
      plugins_to_load = plugins_to_load.to_a.reverse!

      cycle_while_reducing(plugins_to_load) do |name, existed|
        if @plugin_manager.load_plugin(name)
          msg.reply "'#{name}' #{'re' if existed}loaded."
          true
        else
          false
        end
      end

      plugins_to_load.each do |name, existed|
        msg.reply "Cannot #{'re' if existed}load '#{name}'."
      end
    when :unload
      return unless msg.tail
      msg.tail.split.each do |name|
        if name.eql? 'Loader'
          msg.reply "Refusing to unload the loader plugin."
          next
        end
        if @plugin_manager.unload_plugin name
          msg.reply "'#{name}' unloaded."
        else
          msg.reply "Cannot unload '#{name}'."
        end
      end
    end
  end

  def cycle_while_reducing(collection)
    old_size = collection.size + 1
    until collection.empty? || old_size <= collection.size
      old_size = collection.size
      collection.delete_if {|*v| yield(*v)}
    end
  end

end
<commit_msg>Guard against unloaded plugin access bugs in Loader<commit_after># encoding: utf-8
# This file is part of the K5 bot project.
# See files README.md and COPYING for copyright and licensing information.

# Loader plugin loads or reloads plugins

require_relative '../../IRCPlugin'

class Loader < IRCPlugin
  Description = "Loads, reloads, and unloads plugins."
  Commands = {
    :load => "loads or reloads specified plugin",
    :unload => "unloads specified plugin",
    # :reload_core => "reloads core files"
  }

  def on_privmsg(msg)
    case msg.botcommand
    when :load
      return unless msg.tail
      plugins = msg.tail.split

      plugins_to_load = {}

      cycle_while_reducing(plugins) do |name|
        exists = !!(@plugin_manager.plugins[name.to_sym])
        unload_successful = !exists || !!(@plugin_manager.unload_plugin name)
        if unload_successful
          plugins_to_load[name] = exists
          true
        else
          false
        end
      end

      plugins.each do |name|
        msg.reply "Cannot unload '#{name}'."
      end

      # Since hash in Ruby 1.9 preserves order of addition,
      # reverse order would be the best one for loading
      plugins_to_load = plugins_to_load.to_a.reverse!

      cycle_while_reducing(plugins_to_load) do |name, existed|
        if @plugin_manager.load_plugin(name)
          msg.reply "'#{name}' #{'re' if existed}loaded."
          true
        else
          false
        end
      end

      plugins_to_load.each do |name, existed|
        msg.reply "Cannot #{'re' if existed}load '#{name}'."
      end
    when :unload
      return unless msg.tail
      msg.tail.split.each do |name|
        if name.eql? 'Loader'
          msg.reply "Refusing to unload the loader plugin."
          next
        end
        if @plugin_manager.unload_plugin name
          msg.reply "'#{name}' unloaded."
        else
          msg.reply "Cannot unload '#{name}'."
        end
      end
    end
  end

  def cycle_while_reducing(collection)
    old_size = collection.size + 1
    until collection.empty? || old_size <= collection.size
      old_size = collection.size
      collection.delete_if {|*v| yield(*v)}
    end
  end

  # Make us process message after all other plugins,
  # because we may end up messing with them, leading to e.g.
  # Router attempting to send messages to plugins in their
  # uninitialized state with potentially bad consequences.
  LISTENER_PRIORITY = 16384

  def listener_priority
    LISTENER_PRIORITY
  end
end

{'fraction_non_alphanumeric': 0.08968609865470852, 'fraction_numerical': 0.0032030749519538757, 'mean_word_length': 2.3338078291814948, 'pattern_counts': {'":': 0, '<': 7, '<?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': '2946303', 'n_tokens_mistral': 1406, 'n_tokens_neox': 1382, 'n_words': 435}