]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blobdiff - lib/rbot/plugins.rb
Merge pull request #4 from ahpook/rename_karma
[user/henk/code/ruby/rbot.git] / lib / rbot / plugins.rb
index c499fd40ccef26c21ea2e24c4a966541b2c24dcf..8621fe45341456e485894a4768c8d8c298ae8257 100644 (file)
@@ -4,6 +4,7 @@
 # :title: rbot plugin management
 
 require 'singleton'
+require_relative './core/utils/where_is.rb'
 
 module Irc
 class Bot
@@ -37,36 +38,36 @@ module Plugins
 
      Examples:
 
-       plugin.map 'karmastats', :action => 'karma_stats'
+       plugin.map 'pointstats', :action => 'point_stats'
 
        # while in the plugin...
-       def karma_stats(m, params)
+       def point_stats(m, params)
          m.reply "..."
        end
 
        # the default action is the first component
-       plugin.map 'karma'
+       plugin.map 'points'
 
        # attributes can be pulled out of the match string
-       plugin.map 'karma for :key'
-       plugin.map 'karma :key'
+       plugin.map 'points for :key'
+       plugin.map 'points :key'
 
        # while in the plugin...
-       def karma(m, params)
+       def points(m, params)
          item = params[:key]
-         m.reply 'karma for #{item}'
+         m.reply 'points for #{item}'
        end
 
        # you can setup defaults, to make parameters optional
-       plugin.map 'karma :key', :defaults => {:key => 'defaultvalue'}
+       plugin.map 'points :key', :defaults => {:key => 'defaultvalue'}
 
        # the default auth check is also against the first component
        # but that can be changed
-       plugin.map 'karmastats', :auth => 'karma'
+       plugin.map 'pointstats', :auth => 'points'
 
        # maps can be restricted to public or private message:
-       plugin.map 'karmastats', :private => false
-       plugin.map 'karmastats', :public => false
+       plugin.map 'pointstats', :private => false
+       plugin.map 'pointstats', :public => false
 
      See MessageMapper#map for more information on the template format and the
      allowed options.
@@ -409,6 +410,9 @@ module Plugins
     attr_reader :botmodules
     attr_reader :maps
 
+    attr_reader :core_module_dirs
+    attr_reader :plugin_dirs
+
     # This is the list of patterns commonly delegated to plugins.
     # A fast delegation lookup is enabled for them.
     DEFAULT_DELEGATE_PATTERNS = %r{^(?:
@@ -459,13 +463,31 @@ module Plugins
     end
 
     # Reset lists of botmodules
-    def reset_botmodule_lists
-      @botmodules[:CoreBotModule].clear
-      @botmodules[:Plugin].clear
-      @names_hash.clear
-      @commandmappers.clear
-      @maps.clear
-      @failures_shown = false
+    #
+    # :botmodule ::
+    #   optional instance of a botmodule to remove from the lists
+    def reset_botmodule_lists(botmodule=nil)
+      if botmodule
+        # deletes only references of the botmodule
+        @botmodules[:CoreBotModule].delete botmodule
+        @botmodules[:Plugin].delete botmodule
+        @names_hash.delete_if {|key, value| value == botmodule}
+        @commandmappers.delete_if {|key, value| value[:botmodule] == botmodule }
+        @delegate_list.each_pair { |cmd, list|
+          list.delete botmodule
+        }
+        @delegate_list.delete_if {|key, value| value.empty?}
+        @maps.delete_if {|key, value| value[:botmodule] == botmodule }
+        @failures_shown = false
+      else
+        @botmodules[:CoreBotModule].clear
+        @botmodules[:Plugin].clear
+        @names_hash.clear
+        @commandmappers.clear
+        @delegate_list.clear
+        @maps.clear
+        @failures_shown = false
+      end
       mark_priorities_dirty
     end
 
@@ -477,9 +499,16 @@ module Plugins
 
     # Returns the botmodule with the given _name_
     def [](name)
+      return if not name
       @names_hash[name.to_sym]
     end
 
+    # Returns +true+ if a botmodule named _name_ exists.
+    def has_key?(name)
+      return if not name
+      @names_hash.has_key?(name.to_sym)
+    end
+
     # Returns +true+ if _cmd_ has already been registered as a command
     def who_handles?(cmd)
       return nil unless @commandmappers.has_key?(cmd.to_sym)
@@ -518,6 +547,11 @@ module Plugins
       end
       @botmodules[kl] << botmodule
       @names_hash[botmodule.to_sym] = botmodule
+      # add itself to the delegate list for the fast-delegation
+      # of methods like cleanup or privmsg, etc..
+      botmodule.methods.grep(DEFAULT_DELEGATE_PATTERNS).each { |m|
+        @delegate_list[m.intern] << botmodule
+      }
       mark_priorities_dirty
     end
 
@@ -551,7 +585,8 @@ module Plugins
     # This method is the one that actually loads a module from the
     # file _fname_
     #
-    # _desc_ is a simple description of what we are loading (plugin/botmodule/whatever)
+    # _desc_ is a simple description of what we are loading
+    # (plugin/botmodule/whatever) for error reporting
     #
     # It returns the Symbol :loaded on success, and an Exception
     # on failure
@@ -561,6 +596,7 @@ module Plugins
       # the idea here is to prevent namespace pollution. perhaps there
       # is another way?
       plugin_module = Module.new
+      
       # each plugin uses its own textdomain, we bind it automatically here
       bindtextdomain_to(plugin_module, "rbot-#{File.basename(fname, '.rb')}")
 
@@ -570,6 +606,7 @@ module Plugins
         plugin_string = IO.read(fname)
         debug "loading #{desc}#{fname}"
         plugin_module.module_eval(plugin_string, fname)
+
         return :loaded
       rescue Exception => err
         # rescue TimeoutError, StandardError, NameError, LoadError, SyntaxError => err
@@ -585,6 +622,7 @@ module Plugins
         msg = err.to_s.gsub(/^\(eval\)(:\d+)(:in `.*')?(:.*)?/) { |m|
           "#{fname}#{$1}#{$3}"
         }
+        msg.gsub!(fname, File.basename(fname))
         begin
           newerr = err.class.new(msg)
         rescue ArgumentError => aerr_in_err
@@ -712,32 +750,58 @@ module Plugins
       scan_botmodules(:type => :plugins)
 
       debug "finished loading plugins: #{status(true)}"
-      (core_modules + plugins).each { |p|
-       p.methods.grep(DEFAULT_DELEGATE_PATTERNS).each { |m|
-         @delegate_list[m.intern] << p
-       }
-      }
       mark_priorities_dirty
     end
 
     # call the save method for each active plugin
-    def save
-      delegate 'flush_registry'
-      delegate 'save'
+    #
+    # :botmodule ::
+    #   optional instance of a botmodule to save
+    def save(botmodule=nil)
+      if botmodule
+        botmodule.flush_registry
+        botmodule.save if botmodule.respond_to? 'save'
+      else
+        delegate 'flush_registry'
+        delegate 'save'
+      end
     end
 
     # call the cleanup method for each active plugin
-    def cleanup
-      delegate 'cleanup'
-      reset_botmodule_lists
+    #
+    # :botmodule ::
+    #   optional instance of a botmodule to cleanup
+    def cleanup(botmodule=nil)
+      if botmodule
+        botmodule.cleanup
+      else
+        delegate 'cleanup'
+      end
+      reset_botmodule_lists(botmodule)
     end
 
-    # drop all plugins and rescan plugins on disk
-    # calls save and cleanup for each plugin before dropping them
-    def rescan
-      save
-      cleanup
-      scan
+    # drops botmodules and rescan botmodules on disk
+    # calls save and cleanup for each botmodule before dropping them
+    # a optional _botmodule_ argument might specify a botmodule 
+    # instance that should be reloaded
+    #
+    # :botmodule ::
+    #   instance of the botmodule to rescan
+    def rescan(botmodule=nil)
+      save(botmodule)
+      cleanup(botmodule)
+      if botmodule
+        @failed.clear
+        @ignored.clear
+        filename = where_is(botmodule.class)
+        err = load_botmodule_file(filename, "plugin")
+        if err.is_a? Exception
+          @failed << { :name => botmodule.to_s,
+                       :dir => File.dirname(filename), :reason => err }
+        end
+      else
+        scan
+      end
     end
 
     def status(short=false)
@@ -802,6 +866,20 @@ module Plugins
       output.join '; '
     end
 
+    # returns the last logged failure (if present) of a botmodule
+    #
+    # :name ::
+    #   name of the botmodule
+    def botmodule_failure(name)
+      failure = @failed.find { |f| f[:name] == name }
+      if failure
+        "%{exception}: %{reason}" % {
+          :exception => failure[:reason].class,
+          :reason => failure[:reason]
+        }
+      end
+    end
+
     # return list of help topics (plugin names)
     def helptopics
       rv = status
@@ -889,8 +967,7 @@ module Plugins
       end
     end
 
-    # call-seq: delegate</span><span class="method-args">(method, m, opts={})</span>
-    # <span class="method-name">delegate</span><span class="method-args">(method, opts={})
+    # delegate(method, [m,] opts={})
     #
     # see if each plugin handles _method_, and if so, call it, passing
     # _m_ as a parameter (if present). BotModules are called in order of