You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by jv...@apache.org on 2005/12/16 23:30:52 UTC

svn commit: r357245 [2/3] - in /maven/sandbox/issue/rbot/dist: ./ bin/ lib/ lib/site_ruby/ lib/site_ruby/1.8/ lib/site_ruby/1.8/rbot/ plugins/ share/ share/rbot/ share/rbot/languages/ share/rbot/templates/ share/rbot/templates/lart/

Added: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/keywords.rb
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/keywords.rb?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/keywords.rb (added)
+++ maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/keywords.rb Fri Dec 16 14:30:38 2005
@@ -0,0 +1,437 @@
+require 'pp'
+
+module Irc
+
+  # Keyword class
+  #
+  # Encapsulates a keyword ("foo is bar" is a keyword called foo, with type
+  # is, and has a single value of bar).
+  # Keywords can have multiple values, to_s() will choose one at random
+  class Keyword
+
+    # type of keyword (e.g. "is" or "are")
+    attr_reader :type
+    
+    # type::   type of keyword (e.g "is" or "are")
+    # values:: array of values
+    # 
+    # create a keyword of type +type+ with values +values+
+    def initialize(type, values)
+      @type = type.downcase
+      @values = values
+    end
+
+    # pick a random value for this keyword and return it
+    def to_s
+      if(@values.length > 1)
+        Keyword.unescape(@values[rand(@values.length)])
+      else
+        Keyword.unescape(@values[0])
+      end
+    end
+
+    # describe the keyword (show all values without interpolation)
+    def desc
+      @values.join(" | ")
+    end
+
+    # return the keyword in a stringified form ready for storage
+    def dump
+      @type + "/" + Keyword.unescape(@values.join("<=or=>"))
+    end
+
+    # deserialize the stringified form to an object
+    def Keyword.restore(str)
+      if str =~ /^(\S+?)\/(.*)$/
+        type = $1
+        vals = $2.split("<=or=>")
+        return Keyword.new(type, vals)
+      end
+      return nil
+    end
+
+    # values:: array of values to add
+    # add values to a keyword
+    def <<(values)
+      if(@values.length > 1 || values.length > 1)
+        values.each {|v|
+          @values << v
+        }
+      else
+        @values[0] += " or " + values[0]
+      end
+    end
+
+    # unescape special words/characters in a keyword
+    def Keyword.unescape(str)
+      str.gsub(/\\\|/, "|").gsub(/ \\is /, " is ").gsub(/ \\are /, " are ").gsub(/\\\?(\s*)$/, "?\1")
+    end
+
+    # escape special words/characters in a keyword
+    def Keyword.escape(str)
+      str.gsub(/\|/, "\\|").gsub(/ is /, " \\is ").gsub(/ are /, " \\are ").gsub(/\?(\s*)$/, "\\?\1")
+    end
+  end
+
+  # keywords class. 
+  #
+  # Handles all that stuff like "bot: foo is bar", "bot: foo?"
+  #
+  # Fallback after core and auth have had a look at a message and refused to
+  # handle it, checks for a keyword command or lookup, otherwise the message
+  # is delegated to plugins
+  class Keywords
+    BotConfig.register BotConfigBooleanValue.new('keyword.listen',
+      :default => false,
+      :desc => "Should the bot listen to all chat and attempt to automatically detect keywords? (e.g. by spotting someone say 'foo is bar')")
+    BotConfig.register BotConfigBooleanValue.new('keyword.address',
+      :default => true,
+      :desc => "Should the bot require that keyword lookups are addressed to it? If not, the bot will attempt to lookup foo if someone says 'foo?' in channel")
+    
+    # create a new Keywords instance, associated to bot +bot+
+    def initialize(bot)
+      @bot = bot
+      @statickeywords = Hash.new
+      upgrade_data
+      @keywords = DBTree.new bot, "keyword"
+
+      scan
+      
+      # import old format keywords into DBHash
+      if(File.exist?("#{@bot.botclass}/keywords.rbot"))
+        puts "auto importing old keywords.rbot"
+        IO.foreach("#{@bot.botclass}/keywords.rbot") do |line|
+          if(line =~ /^(.*?)\s*<=(is|are)?=?>\s*(.*)$/)
+            lhs = $1
+            mhs = $2
+            rhs = $3
+            mhs = "is" unless mhs
+            rhs = Keyword.escape rhs
+            values = rhs.split("<=or=>")
+            @keywords[lhs] = Keyword.new(mhs, values).dump
+          end
+        end
+        File.delete("#{@bot.botclass}/keywords.rbot")
+      end
+    end
+    
+    # drop static keywords and reload them from files, picking up any new
+    # keyword files that have been added
+    def rescan
+      @statickeywords = Hash.new
+      scan
+    end
+
+    # load static keywords from files, picking up any new keyword files that
+    # have been added
+    def scan
+      # first scan for old DBHash files, and convert them
+      Dir["#{@bot.botclass}/keywords/*"].each {|f|
+        next unless f =~ /\.db$/
+        puts "upgrading keyword db #{f} (rbot 0.9.5 or prior) database format"
+        newname = f.gsub(/\.db$/, ".kdb")
+        old = BDB::Hash.open f, nil, 
+                             "r+", 0600, "set_pagesize" => 1024,
+                             "set_cachesize" => [0, 32 * 1024, 0]
+        new = BDB::CIBtree.open newname, nil, 
+                                BDB::CREATE | BDB::EXCL | BDB::TRUNCATE,
+                                0600, "set_pagesize" => 1024,
+                                "set_cachesize" => [0, 32 * 1024, 0]
+        old.each {|k,v|
+          new[k] = v
+        }
+        old.close
+        new.close
+        File.delete(f)
+      }
+      
+      # then scan for current DBTree files, and load them
+      Dir["#{@bot.botclass}/keywords/*"].each {|f|
+        next unless f =~ /\.kdb$/
+        hsh = DBTree.new @bot, f, true
+        key = File.basename(f).gsub(/\.kdb$/, "")
+        debug "keywords module: loading DBTree file #{f}, key #{key}"
+        @statickeywords[key] = hsh
+      }
+      
+      # then scan for non DB files, and convert/import them and delete
+      Dir["#{@bot.botclass}/keywords/*"].each {|f|
+        next if f =~ /\.kdb$/
+        next if f =~ /CVS$/
+        puts "auto converting keywords from #{f}"
+        key = File.basename(f)
+        unless @statickeywords.has_key?(key)
+          @statickeywords[key] = DBHash.new @bot, "#{f}.db", true
+        end
+        IO.foreach(f) {|line|
+          if(line =~ /^(.*?)\s*<?=(is|are)?=?>\s*(.*)$/)
+            lhs = $1
+            mhs = $2
+            rhs = $3
+            # support infobot style factfiles, by fixing them up here
+            rhs.gsub!(/\$who/, "<who>")
+            mhs = "is" unless mhs
+            rhs = Keyword.escape rhs
+            values = rhs.split("<=or=>")
+            @statickeywords[key][lhs] = Keyword.new(mhs, values).dump
+          end
+        }
+        File.delete(f)
+        @statickeywords[key].flush
+      }
+    end
+
+    # upgrade data files found in old rbot formats to current
+    def upgrade_data
+      if File.exist?("#{@bot.botclass}/keywords.db")
+        puts "upgrading old keywords (rbot 0.9.5 or prior) database format"
+        old = BDB::Hash.open "#{@bot.botclass}/keywords.db", nil, 
+                             "r+", 0600, "set_pagesize" => 1024,
+                             "set_cachesize" => [0, 32 * 1024, 0]
+        new = BDB::CIBtree.open "#{@bot.botclass}/keyword.db", nil, 
+                                BDB::CREATE | BDB::EXCL | BDB::TRUNCATE,
+                                0600, "set_pagesize" => 1024,
+                                "set_cachesize" => [0, 32 * 1024, 0]
+        old.each {|k,v|
+          new[k] = v
+        }
+        old.close
+        new.close
+        File.delete("#{@bot.botclass}/keywords.db")
+      end
+    end
+
+    # save dynamic keywords to file
+    def save
+      @keywords.flush
+    end
+    def oldsave
+      File.open("#{@bot.botclass}/keywords.rbot", "w") do |file|
+        @keywords.each do |key, value|
+          file.puts "#{key}<=#{value.type}=>#{value.dump}"
+        end
+      end
+    end
+    
+    # lookup keyword +key+, return it or nil
+    def [](key)
+      return nil if key.nil?
+      debug "keywords module: looking up key #{key}"
+      if(@keywords.has_key?(key))
+        return Keyword.restore(@keywords[key])
+      else
+        # key name order for the lookup through these
+        @statickeywords.keys.sort.each {|k|
+          v = @statickeywords[k]
+          if v.has_key?(key)
+            return Keyword.restore(v[key])
+          end
+        }
+      end
+      return nil
+    end
+
+    # does +key+ exist as a keyword?
+    def has_key?(key)
+      if @keywords.has_key?(key) && Keyword.restore(@keywords[key]) != nil
+        return true
+      end
+      @statickeywords.each {|k,v|
+        if v.has_key?(key) && Keyword.restore(v[key]) != nil
+          return true
+        end
+      }
+      return false
+    end
+
+    # m::     PrivMessage containing message info
+    # key::   key being queried
+    # dunno:: optional, if true, reply "dunno" if +key+ not found
+    # 
+    # handle a message asking about a keyword
+    def keyword(m, key, dunno=true)
+      return if key.nil?
+       unless(kw = self[key])
+         m.reply @bot.lang.get("dunno") if (dunno)
+         return
+       end
+       response = kw.to_s
+       response.gsub!(/<who>/, m.sourcenick)
+       if(response =~ /^<reply>\s*(.*)/)
+         m.reply "#$1"
+       elsif(response =~ /^<action>\s*(.*)/)
+         @bot.action m.replyto, "#$1"
+       elsif(m.public? && response =~ /^<topic>\s*(.*)/)
+         topic = $1
+         @bot.topic m.target, topic
+       else
+         m.reply "#{key} #{kw.type} #{response}"
+       end
+    end
+
+    
+    # m::      PrivMessage containing message info
+    # target:: channel/nick to tell about the keyword
+    # key::    key being queried
+    # 
+    # handle a message asking the bot to tell someone about a keyword
+    def keyword_tell(m, target, key)
+      unless(kw = self[key])
+        @bot.say m.sourcenick, @bot.lang.get("dunno_about_X") % key
+        return
+      end
+      response = kw.to_s
+      response.gsub!(/<who>/, m.sourcenick)
+      if(response =~ /^<reply>\s*(.*)/)
+        @bot.say target, "#{m.sourcenick} wanted me to tell you: (#{key}) #$1"
+        m.reply "okay, I told #{target}: (#{key}) #$1"
+      elsif(response =~ /^<action>\s*(.*)/)
+        @bot.action target, "#$1 (#{m.sourcenick} wanted me to tell you)"
+        m.reply "okay, I told #{target}: * #$1"
+      else
+        @bot.say target, "#{m.sourcenick} wanted me to tell you that #{key} #{kw.type} #{response}"
+        m.reply "okay, I told #{target} that #{key} #{kw.type} #{response}"
+      end
+    end
+
+    # handle a message which alters a keyword
+    # like "foo is bar", or "no, foo is baz", or "foo is also qux"
+    def keyword_command(sourcenick, target, lhs, mhs, rhs, quiet=false)
+      debug "got keyword command #{lhs}, #{mhs}, #{rhs}"
+      overwrite = false
+      overwrite = true if(lhs.gsub!(/^no,\s*/, ""))
+      also = true if(rhs.gsub!(/^also\s+/, ""))
+      values = rhs.split(/\s+\|\s+/)
+      lhs = Keyword.unescape lhs
+      if(overwrite || also || !has_key?(lhs))
+        if(also && has_key?(lhs))
+          kw = self[lhs]
+          kw << values
+          @keywords[lhs] = kw.dump
+        else
+          @keywords[lhs] = Keyword.new(mhs, values).dump
+        end
+        @bot.okay target if !quiet
+      elsif(has_key?(lhs))
+        kw = self[lhs]
+        @bot.say target, "but #{lhs} #{kw.type} #{kw.desc}" if kw && !quiet
+      end
+    end
+
+    # return help string for Keywords with option topic +topic+
+    def help(topic="")
+      case topic
+        when "overview"
+          return "set: <keyword> is <definition>, overide: no, <keyword> is <definition>, add to definition: <keyword> is also <definition>, random responses: <keyword> is <definition> | <definition> [| ...], plurals: <keyword> are <definition>, escaping: \\is, \\are, \\|, specials: <reply>, <action>, <who>"
+        when "set"
+          return "set => <keyword> is <definition>"
+        when "plurals"
+          return "plurals => <keywords> are <definition>"
+        when "override"
+          return "overide => no, <keyword> is <definition>"
+        when "also"
+          return "also => <keyword> is also <definition>"
+        when "random"
+          return "random responses => <keyword> is <definition> | <definition> [| ...]"
+        when "get"
+          return "asking for keywords => (with addressing) \"<keyword>?\", (without addressing) \"'<keyword>\""
+        when "tell"
+          return "tell <nick> about <keyword> => if <keyword> is known, tell <nick>, via /msg, its definition"
+        when "forget"
+          return "forget <keyword> => forget fact <keyword>"
+        when "keywords"
+          return "keywords => show current keyword counts"
+        when "<reply>"
+          return "<reply> => normal response is \"<keyword> is <definition>\", but if <definition> begins with <reply>, the response will be \"<definition>\""
+        when "<action>"
+          return "<action> => makes keyword respnse \"/me <definition>\""
+        when "<who>"
+          return "<who> => replaced with questioner in reply"
+        when "<topic>"
+          return "<topic> => respond by setting the topic to the rest of the definition"
+        when "search"
+          return "keywords search [--all] [--full] <regexp> => search keywords for <regexp>. If --all is set, search static keywords too, if --full is set, search definitions too."
+        else
+          return "Keyword module (Fact learning and regurgitation) topics: overview, set, plurals, override, also, random, get, tell, forget, keywords, keywords search, <reply>, <action>, <who>, <topic>"
+      end
+    end
+
+    # privmsg handler
+    def privmsg(m)
+      return if m.replied?
+      if(m.address?)
+        if(!(m.message =~ /\\\?\s*$/) && m.message =~ /^(.*\S)\s*\?\s*$/)
+          keyword m, $1 if(@bot.auth.allow?("keyword", m.source, m.replyto))
+        elsif(m.message =~ /^(.*?)\s+(is|are)\s+(.*)$/)
+          keyword_command(m.sourcenick, m.replyto, $1, $2, $3) if(@bot.auth.allow?("keycmd", m.source, m.replyto))
+        elsif (m.message =~ /^tell\s+(\S+)\s+about\s+(.+)$/)
+          keyword_tell(m, $1, $2) if(@bot.auth.allow?("keyword", m.source, m.replyto))
+        elsif (m.message =~ /^forget\s+(.*)$/)
+          key = $1
+          if((@bot.auth.allow?("keycmd", m.source, m.replyto)) && @keywords.has_key?(key))
+            @keywords.delete(key)
+            @bot.okay m.replyto
+          end
+        elsif (m.message =~ /^keywords$/)
+          if(@bot.auth.allow?("keyword", m.source, m.replyto))
+            length = 0
+            @statickeywords.each {|k,v|
+              length += v.length
+            }
+            m.reply "There are currently #{@keywords.length} keywords, #{length} static facts defined."
+          end
+        elsif (m.message =~ /^keywords search\s+(.*)$/)
+          str = $1
+          all = false
+          all = true if str.gsub!(/--all\s+/, "")
+          full = false
+          full = true if str.gsub!(/--full\s+/, "")
+
+          re = Regexp.new(str, Regexp::IGNORECASE)
+          if(@bot.auth.allow?("keyword", m.source, m.replyto))
+            matches = Array.new
+            @keywords.each {|k,v|
+              kw = Keyword.restore(v)
+              if re.match(k) || (full && re.match(kw.desc))
+                matches << [k,kw]
+              end
+            }
+            if all
+              @statickeywords.each {|k,v|
+                v.each {|kk,vv|
+                  kw = Keyword.restore(vv)
+                  if re.match(kk) || (full && re.match(kw.desc))
+                    matches << [kk,kw]
+                  end
+                }
+              }
+            end
+            if matches.length == 1
+              rkw = matches[0]
+              m.reply "#{rkw[0]} #{rkw[1].type} #{rkw[1].desc}"
+            elsif matches.length > 0
+              i = 0
+              matches.each {|rkw|
+                m.reply "[#{i+1}/#{matches.length}] #{rkw[0]} #{rkw[1].type} #{rkw[1].desc}"
+                i += 1
+                break if i == 3
+              }
+            else
+              m.reply "no keywords match #{str}"
+            end
+          end
+        end
+      else
+        # in channel message, not to me
+        # TODO option to do if(m.message =~ /^(.*)$/, ie try any line as a
+        # keyword lookup.
+        if(m.message =~ /^'(.*)$/ || (!@bot.config["keyword.address"] && m.message =~ /^(.*\S)\s*\?\s*$/))
+          keyword m, $1, false if(@bot.auth.allow?("keyword", m.source))
+        elsif(@bot.config["keyword.listen"] == true && (m.message =~ /^(.*?)\s+(is|are)\s+(.*)$/))
+          # TODO MUCH more selective on what's allowed here
+          keyword_command(m.sourcenick, m.replyto, $1, $2, $3, true) if(@bot.auth.allow?("keycmd", m.source))
+        end
+      end
+    end
+  end
+end

Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/keywords.rb
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/keywords.rb
------------------------------------------------------------------------------
    svn:keywords = "Author Date Id Revision"

Added: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/language.rb
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/language.rb?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/language.rb (added)
+++ maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/language.rb Fri Dec 16 14:30:38 2005
@@ -0,0 +1,72 @@
+module Irc
+module Language
+
+  class Language
+    @@datadir = ""
+  
+    BotConfig.register BotConfigEnumValue.new('core.language',
+      :default => "english", :wizard => true,
+      :values => Proc.new{|bot|
+            Dir.new(Config::datadir + "/languages").collect {|f|
+              f =~ /\.lang$/ ? f.gsub(/\.lang$/, "") : nil
+            }.compact
+          },   
+      :on_change => Proc.new {|bot, v| bot.lang.set_language v},
+      :desc => "Which language file the bot should use")
+    
+    def initialize(language,datadir)
+      @@datadir = datadir      
+      set_language language
+    end
+
+    def set_language(language)
+      file = @@datadir + "/languages/#{language}.lang"
+      unless(FileTest.exist?(file))
+        raise "no such language: #{language} (no such file #{file})"
+      end
+      @language = language
+      @file = file
+      scan
+    end
+
+    def scan
+      @strings = Hash.new
+      current_key = nil
+      IO.foreach(@file) {|l|
+        next if l =~ /^$/
+        next if l =~ /^\s*#/
+        if(l =~ /^(\S+):$/)
+          @strings[$1] = Array.new
+          current_key = $1
+        elsif(l =~ /^\s*(.*)$/)
+          @strings[current_key] << $1
+        end
+      }
+    end
+
+    def rescan
+      scan
+    end
+
+    def get(key)
+      if(@strings.has_key?(key))
+        return @strings[key][rand(@strings[key].length)]
+      else
+        raise "undefined language key"
+      end
+    end
+
+    def save
+      File.open(@file, "w") {|file|
+        @strings.each {|key,val|
+          file.puts "#{key}:"
+          val.each_value {|v|
+            file.puts "   #{v}"
+          }
+        }
+      }
+    end
+  end
+
+end
+end

Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/language.rb
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/language.rb
------------------------------------------------------------------------------
    svn:keywords = "Author Date Id Revision"

Added: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/message.rb
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/message.rb?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/message.rb (added)
+++ maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/message.rb Fri Dec 16 14:30:38 2005
@@ -0,0 +1,260 @@
+module Irc
+  BotConfig.register BotConfigArrayValue.new('core.address_prefix',
+    :default => [], :wizard => true,
+    :desc => "what non nick-matching prefixes should the bot respond to as if addressed (e.g !, so that '!foo' is treated like 'rbot: foo')"
+  )
+
+  # base user message class, all user messages derive from this
+  # (a user message is defined as having a source hostmask, a target
+  # nick/channel and a message part)
+  class BasicUserMessage
+    
+    # associated bot
+    attr_reader :bot
+    
+    # when the message was received
+    attr_reader :time
+
+    # hostmask of message source
+    attr_reader :source
+    
+    # nick of message source
+    attr_reader :sourcenick
+    
+    # url part of message source
+    attr_reader :sourceaddress
+    
+    # nick/channel message was sent to
+    attr_reader :target
+    
+    # contents of the message
+    attr_accessor :message
+
+    # has the message been replied to/handled by a plugin?
+    attr_accessor :replied
+
+    # instantiate a new Message
+    # bot::      associated bot class
+    # source::   hostmask of the message source
+    # target::   nick/channel message is destined for
+    # message::  message part
+    def initialize(bot, source, target, message)
+      @time = Time.now
+      @bot = bot
+      @source = source
+      @address = false
+      @target = target
+      @message = BasicUserMessage.stripcolour message
+      @replied = false
+
+      # split source into consituent parts
+      if source =~ /^((\S+)!(\S+))$/
+        @sourcenick = $2
+        @sourceaddress = $3
+      end
+      
+      if target && target.downcase == @bot.nick.downcase
+        @address = true
+      end
+      
+    end
+    
+    # returns true if the message was addressed to the bot.
+    # This includes any private message to the bot, or any public message
+    # which looks like it's addressed to the bot, e.g. "bot: foo", "bot, foo",
+    # a kick message when bot was kicked etc.
+    def address?
+      return @address
+    end
+
+    # has this message been replied to by a plugin?
+    def replied?
+      return @replied
+    end
+
+    # strip mIRC colour escapes from a string
+    def BasicUserMessage.stripcolour(string)
+      return "" unless string
+      ret = string.gsub(/\cC\d\d?(?:,\d\d?)?/, "")
+      #ret.tr!("\x00-\x1f", "")
+      ret
+    end
+
+  end
+
+  # class for handling IRC user messages. Includes some utilities for handling
+  # the message, for example in plugins.
+  # The +message+ member will have any bot addressing "^bot: " removed
+  # (address? will return true in this case)
+  class UserMessage < BasicUserMessage
+    
+    # for plugin messages, the name of the plugin invoked by the message
+    attr_reader :plugin
+    
+    # for plugin messages, the rest of the message, with the plugin name
+    # removed
+    attr_reader :params
+
+    # convenience member. Who to reply to (i.e. would be sourcenick for a
+    # privately addressed message, or target (the channel) for a publicly
+    # addressed message
+    attr_reader :replyto
+
+    # channel the message was in, nil for privately addressed messages
+    attr_reader :channel
+    
+    # for PRIVMSGs, true if the message was a CTCP ACTION (CTCP stuff
+    # will be stripped from the message)
+    attr_reader :action
+    
+    # instantiate a new UserMessage
+    # bot::      associated bot class
+    # source::   hostmask of the message source
+    # target::   nick/channel message is destined for
+    # message::  message part
+    def initialize(bot, source, target, message)
+      super(bot, source, target, message)
+      @target = target
+      @private = false
+      @plugin = nil
+      @action = false
+      
+      if target.downcase == @bot.nick.downcase
+        @private = true
+        @address = true
+        @channel = nil
+        @replyto = @sourcenick
+      else
+        @replyto = @target
+        @channel = @target
+      end
+
+      # check for option extra addressing prefixes, e.g "|search foo", or
+      # "!version" - first match wins
+      bot.config['core.address_prefix'].each {|mprefix|
+        if @message.gsub!(/^#{Regexp.escape(mprefix)}\s*/, "")
+          @address = true
+          break
+        end
+      }
+      
+      # even if they used above prefixes, we allow for silly people who
+      # combine all possible types, e.g. "|rbot: hello", or 
+      # "/msg rbot rbot: hello", etc
+      if @message.gsub!(/^\s*#{Regexp.escape(bot.nick)}\s*([:;,>]|\s)\s*/i, "")
+        @address = true
+      end
+      
+      if(@message =~ /^\001ACTION\s(.+)\001/)
+        @message = $1
+        @action = true
+      end
+      
+      # free splitting for plugins
+      @params = @message.dup
+      if @params.gsub!(/^\s*(\S+)[\s$]*/, "")
+        @plugin = $1.downcase
+        @params = nil unless @params.length > 0
+      end
+    end
+
+    # returns true for private messages, e.g. "/msg bot hello"
+    def private?
+      return @private
+    end
+
+    # returns true if the message was in a channel
+    def public?
+      return !@private
+    end
+
+    def action?
+      return @action
+    end
+
+    # convenience method to reply to a message, useful in plugins. It's the
+    # same as doing:
+    # <tt>@bot.say m.replyto, string</tt>
+    # So if the message is private, it will reply to the user. If it was
+    # in a channel, it will reply in the channel.
+    def reply(string)
+      @bot.say @replyto, string
+      @replied = true
+    end
+
+    # convenience method to reply "okay" in the current language to the
+    # message
+    def okay
+      @bot.say @replyto, @bot.lang.get("okay")
+    end
+
+  end
+
+  # class to manage IRC PRIVMSGs
+  class PrivMessage < UserMessage
+  end
+  
+  # class to manage IRC NOTICEs
+  class NoticeMessage < UserMessage
+  end
+
+  # class to manage IRC KICKs
+  # +address?+ can be used as a shortcut to see if the bot was kicked,
+  # basically, +target+ was kicked from +channel+ by +source+ with +message+
+  class KickMessage < BasicUserMessage
+    # channel user was kicked from
+    attr_reader :channel
+    
+    def initialize(bot, source, target, channel, message="")
+      super(bot, source, target, message)
+      @channel = channel
+    end
+  end
+
+  # class to pass IRC Nick changes in. @message contains the old nickame,
+  # @sourcenick contains the new one.
+  class NickMessage < BasicUserMessage
+    def initialize(bot, source, oldnick, newnick)
+      super(bot, source, oldnick, newnick)
+    end
+  end
+
+  class QuitMessage < BasicUserMessage
+    def initialize(bot, source, target, message="")
+      super(bot, source, target, message)
+    end
+  end
+
+  class TopicMessage < BasicUserMessage
+    # channel topic
+    attr_reader :topic
+    # topic set at (unixtime)
+    attr_reader :timestamp
+    # topic set on channel
+    attr_reader :channel
+
+    def initialize(bot, source, channel, timestamp, topic="")
+      super(bot, source, channel, topic)
+      @topic = topic
+      @timestamp = timestamp
+      @channel = channel
+    end
+  end
+
+  # class to manage channel joins
+  class JoinMessage < BasicUserMessage
+    # channel joined
+    attr_reader :channel
+    def initialize(bot, source, channel, message="")
+      super(bot, source, channel, message)
+      @channel = channel
+      # in this case sourcenick is the nick that could be the bot
+      @address = (sourcenick.downcase == @bot.nick.downcase)
+    end
+  end
+  
+  # class to manage channel parts
+  # same as a join, but can have a message too
+  class PartMessage < JoinMessage
+  end
+end

Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/message.rb
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/message.rb
------------------------------------------------------------------------------
    svn:keywords = "Author Date Id Revision"

Added: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/messagemapper.rb
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/messagemapper.rb?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/messagemapper.rb (added)
+++ maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/messagemapper.rb Fri Dec 16 14:30:38 2005
@@ -0,0 +1,267 @@
+module Irc
+  
+  # +MessageMapper+ is a class designed to reduce the amount of regexps and
+  # string parsing plugins and bot modules need to do, in order to process
+  # and respond to messages.
+  # 
+  # You add templates to the MessageMapper which are examined by the handle
+  # method when handling a message. The templates tell the mapper which
+  # method in its parent class (your class) to invoke for that message. The
+  # string is split, optionally defaulted and validated before being passed
+  # to the matched method.
+  #
+  # A template such as "foo :option :otheroption" will match the string "foo
+  # bar baz" and, by default, result in method +foo+ being called, if
+  # present, in the parent class. It will receive two parameters, the
+  # Message (derived from BasicUserMessage) and a Hash containing 
+  #   {:option => "bar", :otheroption => "baz"}
+  # See the #map method for more details.
+  class MessageMapper
+    # used to set the method name used as a fallback for unmatched messages.
+    # The default fallback is a method called "usage".
+    attr_writer :fallback
+
+    # parent::   parent class which will receive mapped messages
+    #
+    # create a new MessageMapper with parent class +parent+. This class will
+    # receive messages from the mapper via the handle() method.
+    def initialize(parent)
+      @parent = parent
+      @templates = Array.new
+      @fallback = 'usage'
+    end
+    
+    # args:: hash format containing arguments for this template
+    #
+    # map a template string to an action. example:
+    #   map 'myplugin :parameter1 :parameter2'
+    # (other examples follow). By default, maps a matched string to an
+    # action with the name of the first word in the template. The action is
+    # a method which takes a message and a parameter hash for arguments.
+    # 
+    # The :action => 'method_name' option can be used to override this
+    # default behaviour. Example:
+    #   map 'myplugin :parameter1 :parameter2', :action => 'mymethod'
+    #
+    # By default whether a handler is fired depends on an auth check. The
+    # first component of the string is used for the auth check, unless
+    # overridden via the :auth => 'auth_name' option.
+    #
+    # Static parameters (not prefixed with ':' or '*') must match the
+    # respective component of the message exactly. Example:
+    #   map 'myplugin :foo is :bar'
+    # will only match messages of the form "myplugin something is 
+    # somethingelse"
+    #
+    # Dynamic parameters can be specified by a colon ':' to match a single
+    # component (whitespace seperated), or a * to such up all following
+    # parameters into an array. Example:
+    #   map 'myplugin :parameter1 *rest'
+    # 
+    # You can provide defaults for dynamic components using the :defaults
+    # parameter. If a component has a default, then it is optional. e.g:
+    #   map 'myplugin :foo :bar', :defaults => {:bar => 'qux'}
+    # would match 'myplugin param param2' and also 'myplugin param'. In the
+    # latter case, :bar would be provided from the default.
+    #
+    # Components can be validated before being allowed to match, for
+    # example if you need a component to be a number:
+    #   map 'myplugin :param', :requirements => {:param => /^\d+$/}
+    # will only match strings of the form 'myplugin 1234' or some other
+    # number.
+    #
+    # Templates can be set not to match public or private messages using the
+    # :public or :private boolean options.
+    #
+    # Further examples:
+    #
+    #   # match 'karmastats' and call my stats() method
+    #   map 'karmastats', :action => 'stats'
+    #   # match 'karma' with an optional 'key' and call my karma() method
+    #   map 'karma :key', :defaults => {:key => false}
+    #   # match 'karma for something' and call my karma() method
+    #   map 'karma for :key'
+    #   
+    #   # two matches, one for public messages in a channel, one for
+    #   # private messages which therefore require a channel argument
+    #   map 'urls search :channel :limit :string', :action => 'search',
+    #             :defaults => {:limit => 4},
+    #             :requirements => {:limit => /^\d+$/},
+    #             :public => false
+    #   plugin.map 'urls search :limit :string', :action => 'search',
+    #             :defaults => {:limit => 4},
+    #             :requirements => {:limit => /^\d+$/},
+    #             :private => false                                     
+    #
+    def map(*args)
+      @templates << Template.new(*args)
+    end
+    
+    def each
+      @templates.each {|tmpl| yield tmpl}
+    end
+    def last
+      @templates.last
+    end
+    
+    # m::  derived from BasicUserMessage
+    #
+    # examine the message +m+, comparing it with each map()'d template to
+    # find and process a match. Templates are examined in the order they
+    # were map()'d - first match wins.
+    #
+    # returns +true+ if a match is found including fallbacks, +false+
+    # otherwise.
+    def handle(m)
+      return false if @templates.empty?
+      failures = []
+      @templates.each do |tmpl|
+        options, failure = tmpl.recognize(m)
+        if options.nil?
+          failures << [tmpl, failure]
+        else
+          action = tmpl.options[:action] ? tmpl.options[:action] : tmpl.items[0]
+          unless @parent.respond_to?(action)
+            failures << [tmpl, "class does not respond to action #{action}"]
+            next
+          end
+          auth = tmpl.options[:auth] ? tmpl.options[:auth] : tmpl.items[0]
+          debug "checking auth for #{auth}"
+          if m.bot.auth.allow?(auth, m.source, m.replyto)
+            debug "template match found and auth'd: #{action.inspect} #{options.inspect}"
+            @parent.send(action, m, options)
+            return true
+          end
+          debug "auth failed for #{auth}"
+          # if it's just an auth failure but otherwise the match is good,
+          # don't try any more handlers
+          return false
+        end
+      end
+      failures.each {|f, r|
+        debug "#{f.inspect} => #{r}"
+      }
+      debug "no handler found, trying fallback"
+      if @fallback != nil && @parent.respond_to?(@fallback)
+        if m.bot.auth.allow?(@fallback, m.source, m.replyto)
+          @parent.send(@fallback, m, {})
+          return true
+        end
+      end
+      return false
+    end
+
+  end
+
+  class Template
+    attr_reader :defaults # The defaults hash
+    attr_reader :options  # The options hash
+    attr_reader :items
+    def initialize(template, hash={})
+      raise ArgumentError, "Second argument must be a hash!" unless hash.kind_of?(Hash)
+      @defaults = hash[:defaults].kind_of?(Hash) ? hash.delete(:defaults) : {}
+      @requirements = hash[:requirements].kind_of?(Hash) ? hash.delete(:requirements) : {}
+      self.items = template
+      @options = hash
+    end
+    def items=(str)
+      items = str.split(/\s+/).collect {|c| (/^(:|\*)(\w+)$/ =~ c) ? (($1 == ':' ) ? $2.intern : "*#{$2}".intern) : c} if str.kind_of?(String) # split and convert ':xyz' to symbols
+      items.shift if items.first == ""
+      items.pop if items.last == ""
+      @items = items
+
+      if @items.first.kind_of? Symbol
+        raise ArgumentError, "Illegal template -- first component cannot be dynamic\n   #{str.inspect}"
+      end
+
+      # Verify uniqueness of each component.
+      @items.inject({}) do |seen, item|
+        if item.kind_of? Symbol
+          raise ArgumentError, "Illegal template -- duplicate item #{item}\n   #{str.inspect}" if seen.key? item
+          seen[item] = true
+        end
+        seen
+      end
+    end
+
+    # Recognize the provided string components, returning a hash of
+    # recognized values, or [nil, reason] if the string isn't recognized.
+    def recognize(m)
+      components = m.message.split(/\s+/)
+      options = {}
+
+      @items.each do |item|
+        if /^\*/ =~ item.to_s
+          if components.empty?
+            value = @defaults.has_key?(item) ? @defaults[item].clone : []
+          else
+            value = components.clone
+          end
+          components = []
+          def value.to_s() self.join(' ') end
+          options[item.to_s.sub(/^\*/,"").intern] = value
+        elsif item.kind_of? Symbol
+          value = components.shift || @defaults[item]
+          if passes_requirements?(item, value)
+            options[item] = value
+          else
+            if @defaults.has_key?(item)
+              options[item] = @defaults[item]
+              # push the test-failed component back on the stack
+              components.unshift value
+            else
+              return nil, requirements_for(item)
+            end
+          end
+        else
+          return nil, "No value available for component #{item.inspect}" if components.empty?
+          component = components.shift
+          return nil, "Value for component #{item.inspect} doesn't match #{component}" if component != item
+        end
+      end
+
+      return nil, "Unused components were left: #{components.join '/'}" unless components.empty?
+
+      return nil, "template is not configured for private messages" if @options.has_key?(:private) && !@options[:private] && m.private?
+      return nil, "template is not configured for public messages" if @options.has_key?(:public) && !@options[:public] && !m.private?
+      
+      options.delete_if {|k, v| v.nil?} # Remove nil values.
+      return options, nil
+    end
+
+    def inspect
+      when_str = @requirements.empty? ? "" : " when #{@requirements.inspect}"
+      default_str = @defaults.empty? ? "" : " || #{@defaults.inspect}"
+      "<#{self.class.to_s} #{@items.collect{|c| c.kind_of?(String) ? c : c.inspect}.join(' ').inspect}#{default_str}#{when_str}>"
+    end
+
+    # Verify that the given value passes this template's requirements
+    def passes_requirements?(name, value)
+      return @defaults.key?(name) && @defaults[name].nil? if value.nil? # Make sure it's there if it should be
+
+      case @requirements[name]
+        when nil then true
+        when Regexp then
+          value = value.to_s
+          match = @requirements[name].match(value)
+          match && match[0].length == value.length
+        else
+          @requirements[name] == value.to_s
+      end
+    end
+
+    def requirements_for(name)
+      name = name.to_s.sub(/^\*/,"").intern if (/^\*/ =~ name.inspect)
+      presence = (@defaults.key?(name) && @defaults[name].nil?)
+      requirement = case @requirements[name]
+        when nil then nil
+        when Regexp then "match #{@requirements[name].inspect}"
+        else "be equal to #{@requirements[name].inspect}"
+      end
+      if presence && requirement then "#{name} must be present and #{requirement}"
+      elsif presence || requirement then "#{name} must #{requirement || 'be present'}"
+      else "#{name} has no requirements"
+      end
+    end
+  end
+end

Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/messagemapper.rb
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/messagemapper.rb
------------------------------------------------------------------------------
    svn:keywords = "Author Date Id Revision"

Added: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/pkgconfig.rb
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/pkgconfig.rb?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/pkgconfig.rb (added)
+++ maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/pkgconfig.rb Fri Dec 16 14:30:38 2005
@@ -0,0 +1,5 @@
+module Irc
+  module PKGConfig
+    DATADIR = '/usr/share/rbot'
+  end
+end

Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/pkgconfig.rb
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/pkgconfig.rb
------------------------------------------------------------------------------
    svn:keywords = "Author Date Id Revision"

Added: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/plugins.rb
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/plugins.rb?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/plugins.rb (added)
+++ maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/plugins.rb Fri Dec 16 14:30:38 2005
@@ -0,0 +1,299 @@
+module Irc
+module Plugins
+  require 'rbot/messagemapper'
+
+  # base class for all rbot plugins
+  # certain methods will be called if they are provided, if you define one of
+  # the following methods, it will be called as appropriate:
+  #
+  # map(template, options)::
+  #    map is the new, cleaner way to respond to specific message formats
+  #    without littering your plugin code with regexps. examples:
+  #
+  #      plugin.map 'karmastats', :action => 'karma_stats'
+  #
+  #      # while in the plugin...
+  #      def karma_stats(m, params)
+  #        m.reply "..."
+  #      end
+  #      
+  #      # the default action is the first component
+  #      plugin.map 'karma'
+  #
+  #      # attributes can be pulled out of the match string
+  #      plugin.map 'karma for :key'
+  #      plugin.map 'karma :key'
+  #
+  #      # while in the plugin...
+  #      def karma(m, params)
+  #        item = params[:key]
+  #        m.reply 'karma for #{item}'
+  #      end
+  #      
+  #      # you can setup defaults, to make parameters optional
+  #      plugin.map 'karma :key', :defaults => {:key => 'defaultvalue'}
+  #      
+  #      # the default auth check is also against the first component
+  #      # but that can be changed
+  #      plugin.map 'karmastats', :auth => 'karma'
+  #
+  #      # maps can be restricted to public or private message:
+  #      plugin.map 'karmastats', :private false,
+  #      plugin.map 'karmastats', :public false,
+  #    end
+  #
+  #    To activate your maps, you simply register them
+  #    plugin.register_maps
+  #    This also sets the privmsg handler to use the map lookups for
+  #    handling messages. You can still use listen(), kick() etc methods
+  # 
+  # listen(UserMessage)::
+  #                        Called for all messages of any type. To
+  #                        differentiate them, use message.kind_of? It'll be
+  #                        either a PrivMessage, NoticeMessage, KickMessage,
+  #                        QuitMessage, PartMessage, JoinMessage, NickMessage,
+  #                        etc.
+  #                              
+  # privmsg(PrivMessage)::
+  #                        called for a PRIVMSG if the first word matches one
+  #                        the plugin register()d for. Use m.plugin to get
+  #                        that word and m.params for the rest of the message,
+  #                        if applicable.
+  #
+  # kick(KickMessage)::
+  #                        Called when a user (or the bot) is kicked from a
+  #                        channel the bot is in.
+  #
+  # join(JoinMessage)::
+  #                        Called when a user (or the bot) joins a channel
+  #
+  # part(PartMessage)::
+  #                        Called when a user (or the bot) parts a channel
+  #
+  # quit(QuitMessage)::    
+  #                        Called when a user (or the bot) quits IRC
+  #
+  # nick(NickMessage)::
+  #                        Called when a user (or the bot) changes Nick
+  # topic(TopicMessage)::
+  #                        Called when a user (or the bot) changes a channel
+  #                        topic
+  #
+  # connect()::            Called when a server is joined successfully, but
+  #                        before autojoin channels are joined (no params)
+  # 
+  # save::                 Called when you are required to save your plugin's
+  #                        state, if you maintain data between sessions
+  #
+  # cleanup::              called before your plugin is "unloaded", prior to a
+  #                        plugin reload or bot quit - close any open
+  #                        files/connections or flush caches here
+  class Plugin
+    attr_reader :bot   # the associated bot
+    # initialise your plugin. Always call super if you override this method,
+    # as important variables are set up for you
+    def initialize
+      @bot = Plugins.bot
+      @names = Array.new
+      @handler = MessageMapper.new(self)
+      @registry = BotRegistryAccessor.new(@bot, self.class.to_s.gsub(/^.*::/, ""))
+    end
+
+    def map(*args)
+      @handler.map(*args)
+      # register this map
+      name = @handler.last.items[0]
+      self.register name
+      unless self.respond_to?('privmsg')
+        def self.privmsg(m)
+          @handler.handle(m)
+        end
+      end
+    end
+
+    # return an identifier for this plugin, defaults to a list of the message
+    # prefixes handled (used for error messages etc)
+    def name
+      @names.join("|")
+    end
+    
+    # return a help string for your module. for complex modules, you may wish
+    # to break your help into topics, and return a list of available topics if
+    # +topic+ is nil. +plugin+ is passed containing the matching prefix for
+    # this message - if your plugin handles multiple prefixes, make sure your
+    # return the correct help for the prefix requested
+    def help(plugin, topic)
+      "no help"
+    end
+    
+    # register the plugin as a handler for messages prefixed +name+
+    # this can be called multiple times for a plugin to handle multiple
+    # message prefixes
+    def register(name)
+      return if Plugins.plugins.has_key?(name)
+      Plugins.plugins[name] = self
+      @names << name
+    end
+
+    # default usage method provided as a utility for simple plugins. The
+    # MessageMapper uses 'usage' as its default fallback method.
+    def usage(m, params = {})
+      m.reply "incorrect usage, ask for help using '#{@bot.nick}: help #{m.plugin}'"
+    end
+
+  end
+
+  # class to manage multiple plugins and delegate messages to them for
+  # handling
+  class Plugins
+    # hash of registered message prefixes and associated plugins
+    @@plugins = Hash.new
+    # associated IrcBot class
+    @@bot = nil
+
+    # bot::     associated IrcBot class
+    # dirlist:: array of directories to scan (in order) for plugins
+    #
+    # create a new plugin handler, scanning for plugins in +dirlist+
+    def initialize(bot, dirlist, datadir )
+      @@datadir = datadir
+      @@bot = bot
+      @dirs = dirlist
+      scan
+    end
+    
+    # access to associated bot
+    def Plugins.bot
+      @@bot
+    end
+
+    # access to list of plugins
+    def Plugins.plugins
+      @@plugins
+    end
+
+    # load plugins from pre-assigned list of directories
+    def scan
+      processed = Array.new
+      dirs = Array.new
+      dirs << @@datadir + "/plugins"
+      dirs += @dirs
+      dirs.reverse.each {|dir|
+        if(FileTest.directory?(dir))
+          d = Dir.new(dir)
+          d.sort.each {|file|
+            next if(file =~ /^\./)
+            next if(processed.include?(file))
+            if(file =~ /^(.+\.rb)\.disabled$/)
+              processed << $1
+              next
+            end
+            next unless(file =~ /\.rb$/)
+            tmpfilename = "#{dir}/#{file}"
+
+            # create a new, anonymous module to "house" the plugin
+            # the idea here is to prevent namespace pollution. perhaps there
+            # is another way?
+            plugin_module = Module.new
+            
+            begin
+              plugin_string = IO.readlines(tmpfilename).join("")
+              debug "loading plugin #{tmpfilename}"
+              plugin_module.module_eval(plugin_string)
+              processed << file
+            rescue TimeoutError, StandardError, NameError, LoadError, SyntaxError => err
+              puts "warning: plugin #{tmpfilename} load failed: " + err
+              puts err.backtrace.join("\n")
+            end
+          }
+        end
+      }
+    end
+
+    # call the save method for each active plugin
+    def save
+      delegate 'save'
+    end
+
+    # call the cleanup method for each active plugin
+    def cleanup
+      delegate 'cleanup'
+    end
+
+    # drop all plugins and rescan plugins on disk
+    # calls save and cleanup for each plugin before dropping them
+    def rescan
+      save
+      cleanup
+      @@plugins = Hash.new
+      scan
+    end
+
+    # return list of help topics (plugin names)
+    def helptopics
+      if(@@plugins.length > 0)
+        # return " [plugins: " + @@plugins.keys.sort.join(", ") + "]"
+        return " [#{length} plugins: " + @@plugins.values.uniq.collect{|p| p.name}.sort.join(", ") + "]"
+      else
+        return " [no plugins active]" 
+      end
+    end
+
+    def length
+      @@plugins.values.uniq.length
+    end
+
+    # return help for +topic+ (call associated plugin's help method)
+    def help(topic="")
+      if(topic =~ /^(\S+)\s*(.*)$/)
+        key = $1
+        params = $2
+        if(@@plugins.has_key?(key))
+          begin
+            return @@plugins[key].help(key, params)
+          rescue TimeoutError, StandardError, NameError, SyntaxError => err
+            puts "plugin #{@@plugins[key].name} help() failed: " + err
+            puts err.backtrace.join("\n")
+          end
+        else
+          return false
+        end
+      end
+    end
+    
+    # see if each plugin handles +method+, and if so, call it, passing
+    # +message+ as a parameter
+    def delegate(method, *args)
+      @@plugins.values.uniq.each {|p|
+        if(p.respond_to? method)
+          begin
+            p.send method, *args
+          rescue TimeoutError, StandardError, NameError, SyntaxError => err
+            puts "plugin #{p.name} #{method}() failed: " + err
+            puts err.backtrace.join("\n")
+          end
+        end
+      }
+    end
+
+    # see if we have a plugin that wants to handle this message, if so, pass
+    # it to the plugin and return true, otherwise false
+    def privmsg(m)
+      return unless(m.plugin)
+      if (@@plugins.has_key?(m.plugin) &&
+          @@plugins[m.plugin].respond_to?("privmsg") &&
+          @@bot.auth.allow?(m.plugin, m.source, m.replyto))
+        begin
+          @@plugins[m.plugin].privmsg(m)
+        rescue TimeoutError, StandardError, NameError, SyntaxError => err
+          puts "plugin #{@@plugins[m.plugin].name} privmsg() failed: " + err
+          puts err.backtrace.join("\n")
+        end
+        return true
+      end
+      return false
+    end
+  end
+
+end
+end

Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/plugins.rb
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/plugins.rb
------------------------------------------------------------------------------
    svn:keywords = "Author Date Id Revision"

Added: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/rbotconfig.rb
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/rbotconfig.rb?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/rbotconfig.rb (added)
+++ maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/rbotconfig.rb Fri Dec 16 14:30:38 2005
@@ -0,0 +1,36 @@
+module Irc
+  module Config
+    @@datadir = nil
+    # setup pkg-based configuration - i.e. where were we installed to, where
+    # are our data files, etc.
+    begin
+      debug "trying to load rubygems"
+      require 'rubygems'
+      debug "loaded rubygems, looking for rbot-#$version"
+      gemname, gem = Gem.source_index.find{|name, spec| spec.name == 'rbot' && spec.version.version == $version}
+      debug "got gem #{gem}"
+      if gem && path = gem.full_gem_path
+        debug "installed via rubygems to #{path}"
+        @@datadir = "#{path}/data/rbot"
+      else
+        debug "not installed via rubygems"
+      end
+    rescue LoadError
+      debug "no rubygems installed"
+    end
+
+    if @@datadir.nil?
+      begin
+        require 'rbot/pkgconfig'
+        @@datadir = PKGConfig::DATADIR
+      rescue LoadError
+        puts "fatal - no way to determine data dir"
+        exit 2
+      end
+    end
+    
+    def Config.datadir
+      @@datadir
+    end
+  end
+end

Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/rbotconfig.rb
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/rbotconfig.rb
------------------------------------------------------------------------------
    svn:keywords = "Author Date Id Revision"

Added: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/registry.rb
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/registry.rb?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/registry.rb (added)
+++ maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/registry.rb Fri Dec 16 14:30:38 2005
@@ -0,0 +1,271 @@
+require 'rbot/dbhash'
+
+module Irc
+
+  # this is the backend of the RegistryAccessor class, which ties it to a
+  # DBHash object called plugin_registry(.db). All methods are delegated to
+  # the DBHash.
+  class BotRegistry
+    def initialize(bot)
+      @bot = bot
+      upgrade_data
+      @db = DBTree.new @bot, "plugin_registry"
+    end
+
+    # delegation hack
+    def method_missing(method, *args, &block)
+      @db.send(method, *args, &block)
+    end
+
+    # check for older versions of rbot with data formats that require updating
+    # NB this function is called _early_ in init(), pretty much all you have to
+    # work with is @bot.botclass.
+    def upgrade_data
+      if File.exist?("#{@bot.botclass}/registry.db")
+        puts "upgrading old-style (rbot 0.9.5 or earlier) plugin registry to new format"
+        old = BDB::Hash.open "#{@bot.botclass}/registry.db", nil, 
+                             "r+", 0600, "set_pagesize" => 1024,
+                             "set_cachesize" => [0, 32 * 1024, 0]
+        new = BDB::CIBtree.open "#{@bot.botclass}/plugin_registry.db", nil, 
+                                BDB::CREATE | BDB::EXCL | BDB::TRUNCATE,
+                                0600, "set_pagesize" => 1024,
+                                "set_cachesize" => [0, 32 * 1024, 0]
+        old.each {|k,v|
+          new[k] = v
+        }
+        old.close
+        new.close
+        File.delete("#{@bot.botclass}/registry.db")
+      end
+    end
+  end
+
+  # This class provides persistent storage for plugins via a hash interface.
+  # The default mode is an object store, so you can store ruby objects and
+  # reference them with hash keys. This is because the default store/restore
+  # methods of the plugins' RegistryAccessor are calls to Marshal.dump and
+  # Marshal.restore,
+  # for example:
+  #   blah = Hash.new
+  #   blah[:foo] = "fum"
+  #   @registry[:blah] = blah
+  # then, even after the bot is shut down and disconnected, on the next run you
+  # can access the blah object as it was, with:
+  #   blah = @registry[:blah]
+  # The registry can of course be used to store simple strings, fixnums, etc as
+  # well, and should be useful to store or cache plugin data or dynamic plugin
+  # configuration. 
+  #
+  # WARNING:
+  # in object store mode, don't make the mistake of treating it like a live
+  # object, e.g. (using the example above)
+  #   @registry[:blah][:foo] = "flump"
+  # will NOT modify the object in the registry - remember that BotRegistry#[]
+  # returns a Marshal.restore'd object, the object you just modified in place
+  # will disappear. You would need to:
+  #   blah = @registry[:blah]
+  #   blah[:foo] = "flump"
+  #   @registry[:blah] = blah
+
+  # If you don't need to store objects, and strictly want a persistant hash of
+  # strings, you can override the store/restore methods to suit your needs, for
+  # example (in your plugin):
+  #   def initialize
+  #     class << @registry
+  #       def store(val)
+  #         val
+  #       end
+  #       def restore(val)
+  #         val
+  #       end
+  #     end
+  #   end
+  # Your plugins section of the registry is private, it has its own namespace
+  # (derived from the plugin's class name, so change it and lose your data).
+  # Calls to registry.each etc, will only iterate over your namespace.
+  class BotRegistryAccessor
+    # plugins don't call this - a BotRegistryAccessor is created for them and
+    # is accessible via @registry.
+    def initialize(bot, prefix)
+      @bot = bot
+      @registry = @bot.registry
+      @orig_prefix = prefix
+      @prefix = prefix + "/"
+      @default = nil
+      # debug "initializing registry accessor with prefix #{@prefix}"
+    end
+
+    # use this to chop up your namespace into bits, so you can keep and
+    # reference separate object stores under the same registry
+    def sub_registry(prefix)
+      return BotRegistryAccessor.new(@bot, @orig_prefix + "+" + prefix)
+    end
+
+    # convert value to string form for storing in the registry
+    # defaults to Marshal.dump(val) but you can override this in your module's
+    # registry object to use any method you like.
+    # For example, if you always just handle strings use:
+    #   def store(val)
+    #     val
+    #   end
+    def store(val)
+      Marshal.dump(val)
+    end
+
+    # restores object from string form, restore(store(val)) must return val.
+    # If you override store, you should override restore to reverse the
+    # action.
+    # For example, if you always just handle strings use:
+    #   def restore(val)
+    #     val
+    #   end
+    def restore(val)
+      Marshal.restore(val)
+    end
+
+    # lookup a key in the registry
+    def [](key)
+      if @registry.has_key?(@prefix + key)
+        return restore(@registry[@prefix + key])
+      elsif @default != nil
+        return restore(@default)
+      else
+        return nil
+      end
+    end
+
+    # set a key in the registry
+    def []=(key,value)
+      @registry[@prefix + key] = store(value)
+    end
+
+    # set the default value for registry lookups, if the key sought is not
+    # found, the default will be returned. The default default (har) is nil.
+    def set_default (default)
+      @default = store(default)
+    end
+
+    # just like Hash#each
+    def each(&block)
+      @registry.each {|key,value|
+        if key.gsub!(/^#{Regexp.escape(@prefix)}/, "")
+          block.call(key, restore(value))
+        end
+      }
+    end
+    
+    # just like Hash#each_key
+    def each_key(&block)
+      @registry.each {|key, value|
+        if key.gsub!(/^#{Regexp.escape(@prefix)}/, "")
+          block.call(key)
+        end
+      }
+    end
+    
+    # just like Hash#each_value
+    def each_value(&block)
+      @registry.each {|key, value|
+        if key =~ /^#{Regexp.escape(@prefix)}/
+          block.call(restore(value))
+        end
+      }
+    end
+
+    # just like Hash#has_key?
+    def has_key?(key)
+      return @registry.has_key?(@prefix + key)
+    end
+    alias include? has_key?
+    alias member? has_key?
+
+    # just like Hash#has_both?
+    def has_both?(key, value)
+      return @registry.has_both?(@prefix + key, store(value))
+    end
+    
+    # just like Hash#has_value?
+    def has_value?(value)
+      return @registry.has_value?(store(value))
+    end
+
+    # just like Hash#index?
+    def index(value)
+      ind = @registry.index(store(value))
+      if ind && ind.gsub!(/^#{Regexp.escape(@prefix)}/, "")
+        return ind
+      else
+        return nil
+      end
+    end
+    
+    # delete a key from the registry
+    def delete(key)
+      return @registry.delete(@prefix + key)
+    end
+
+    # returns a list of your keys
+    def keys
+      return @registry.keys.collect {|key|
+        if key.gsub!(/^#{Regexp.escape(@prefix)}/, "")  
+          key
+        else
+          nil
+        end
+      }.compact
+    end
+
+    # Return an array of all associations [key, value] in your namespace
+    def to_a
+      ret = Array.new
+      @registry.each {|key, value|
+        if key.gsub!(/^#{Regexp.escape(@prefix)}/, "")
+          ret << [key, restore(value)]
+        end
+      }
+      return ret
+    end
+    
+    # Return an hash of all associations {key => value} in your namespace
+    def to_hash
+      ret = Hash.new
+      @registry.each {|key, value|
+        if key.gsub!(/^#{Regexp.escape(@prefix)}/, "")
+          ret[key] = restore(value)
+        end
+      }
+      return ret
+    end
+
+    # empties the registry (restricted to your namespace)
+    def clear
+      @registry.each_key {|key|
+        if key =~ /^#{Regexp.escape(@prefix)}/
+          @registry.delete(key)
+        end
+      }
+    end
+    alias truncate clear
+
+    # returns an array of the values in your namespace of the registry
+    def values
+      ret = Array.new
+      self.each {|k,v|
+        ret << restore(v)
+      }
+      return ret
+    end
+
+    # returns the number of keys in your registry namespace
+    def length
+      self.keys.length
+    end
+    alias size length
+
+    def flush
+      @registry.flush
+    end
+    
+  end
+
+end

Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/registry.rb
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/registry.rb
------------------------------------------------------------------------------
    svn:keywords = "Author Date Id Revision"