]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blobdiff - lib/rbot/core/utils/extends.rb
config core botmodule: only show possible keys summary when more than one key was...
[user/henk/code/ruby/rbot.git] / lib / rbot / core / utils / extends.rb
index 31a348a94ff8594e756a5b29cc55c4d5bb7551a7..e62e2f21ea768ec846a7b607020ecaf6c4d9cf73 100644 (file)
@@ -4,8 +4,6 @@
 # :title: Standard classes extensions
 #
 # Author:: Giuseppe "Oblomov" Bilotta <giuseppe.bilotta@gmail.com>
-# Copyright:: (C) 2006,2007 Giuseppe Bilotta
-# License:: GPL v2
 #
 # This file collects extensions to standard Ruby classes and to some core rbot
 # classes to be used by the various plugins
@@ -38,6 +36,50 @@ class ::Module
 end
 
 
+# DottedIndex mixin: extend a Hash or Array class with this module
+# to achieve [] and []= methods that automatically split indices
+# at dots (indices are automatically converted to symbols, too)
+#
+# You have to define the single_retrieve(_key_) and
+# single_assign(_key_,_value_) methods (usually aliased at the
+# original :[] and :[]= methods)
+#
+module ::DottedIndex
+  def rbot_index_split(*ar)
+    keys = ([] << ar).flatten
+    keys.map! { |k|
+      k.to_s.split('.').map { |kk| kk.to_sym rescue nil }.compact
+    }.flatten
+  end
+
+  def [](*ar)
+    keys = self.rbot_index_split(ar)
+    return self.single_retrieve(keys.first) if keys.length == 1
+    h = self
+    while keys.length > 1
+      k = keys.shift
+      h[k] ||= self.class.new
+      h = h[k]
+    end
+    h[keys.last]
+  end
+
+  def []=(*arr)
+    val = arr.last
+    ar = arr[0..-2]
+    keys = self.rbot_index_split(ar)
+    return self.single_assign(keys.first, val) if keys.length == 1
+    h = self
+    while keys.length > 1
+      k = keys.shift
+      h[k] ||= self.class.new
+      h = h[k]
+    end
+    h[keys.last] = val
+  end
+end
+
+
 # Extensions to the Array class
 #
 class ::Array
@@ -49,6 +91,40 @@ class ::Array
     return nil if self.empty?
     self[rand(self.length)]
   end
+
+  # This method returns a given element from the array, deleting it from the
+  # array itself. The method returns nil if the element couldn't be found.
+  #
+  # If nil is specified, a random element is returned and deleted.
+  #
+  def delete_one(val=nil)
+    return nil if self.empty?
+    if val.nil?
+      index = rand(self.length)
+    else
+      index = self.index(val)
+      return nil unless index
+    end
+    self.delete_at(index)
+  end
+
+  # This method shuffles the items in the array
+  def shuffle!
+    base = self.dup
+    self.clear
+    while item = base.delete_one
+      self << item
+    end
+    self
+  end
+
+  # This method returns a new array with the same items as
+  # the receiver, but shuffled
+  def shuffle
+    ret = self.dup
+    ret.shuffle!
+  end
+
 end
 
 # Extensions to the Range class
@@ -137,6 +213,10 @@ class ::String
     txt.gsub!(/<sub>(.*?)<\/sub>/, '_{\1}')
     txt.gsub!(/(^|_)\{(.)\}/, '\1\2')
 
+    # List items are converted to *). We don't have special support for
+    # nested or ordered lists.
+    txt.gsub!(/<li>/, ' *) ')
+
     # All other tags are just removed
     txt.gsub!(/<[^>]+>/, '')
 
@@ -144,6 +224,14 @@ class ::String
     # such as &nbsp;
     txt = Utils.decode_html_entities(txt)
 
+    # Keep unbreakable spaces or conver them to plain spaces?
+    case val = opts[:nbsp]
+    when :space, ' '
+      txt.gsub!([160].pack('U'), ' ')
+    else
+      warning "unknown :nbsp option #{val} passed to ircify_html" if val
+    end
+
     # Remove double formatting options, since they only waste bytes
     txt.gsub!(/#{Bold}(\s*)#{Bold}/, '\1')
     txt.gsub!(/#{Underline}(\s*)#{Underline}/, '\1')
@@ -178,6 +266,23 @@ class ::String
   def riphtml
     self.gsub(/<[^>]+>/, '').gsub(/&amp;/,'&').gsub(/&quot;/,'"').gsub(/&lt;/,'<').gsub(/&gt;/,'>').gsub(/&ellip;/,'...').gsub(/&apos;/, "'").gsub("\n",'')
   end
+
+  # This method tries to find an HTML title in the string,
+  # and returns it if found
+  def get_html_title
+    if defined? ::Hpricot
+      Hpricot(self).at("title").inner_html
+    else
+      return unless Irc::Utils::TITLE_REGEX.match(self)
+      $1
+    end
+  end
+
+  # This method returns the IRC-formatted version of an
+  # HTML title found in the string
+  def ircify_html_title
+    self.get_html_title.ircify_html rescue nil
+  end
 end
 
 
@@ -227,79 +332,6 @@ end
 
 module ::Irc
 
-  # Define standard IRC attriubtes (not so standard actually,
-  # but the closest thing we have ...)
-  Bold = "\002"
-  Underline = "\037"
-  Reverse = "\026"
-  Italic = "\011"
-  NormalText = "\017"
-
-  # Color is prefixed by \003 and followed by optional
-  # foreground and background specifications, two-digits-max
-  # numbers separated by a comma. One of the two parts
-  # must be present.
-  Color = "\003"
-  ColorRx = /#{Color}\d?\d?(?:,\d\d?)?/
-
-  # Standard color codes
-  ColorCode = {
-    :black      => 1,
-    :blue       => 2,
-    :navyblue   => 2,
-    :navy_blue  => 2,
-    :green      => 3,
-    :red        => 4,
-    :brown      => 5,
-    :purple     => 6,
-    :olive      => 7,
-    :yellow     => 8,
-    :limegreen  => 9,
-    :lime_green => 9,
-    :teal       => 10,
-    :aqualight  => 11,
-    :aqua_light => 11,
-    :royal_blue => 12,
-    :hotpink    => 13,
-    :hot_pink   => 13,
-    :darkgray   => 14,
-    :dark_gray  => 14,
-    :lightgray  => 15,
-    :light_gray => 15,
-    :white      => 16
-  }
-
-  # Convert a String or Symbol into a color number
-  def Irc.find_color(data)
-    if Integer === data
-      data
-    else
-      f = if String === data
-            data.intern
-          else
-            data
-          end
-      if ColorCode.key?(f)
-        ColorCode[f] 
-      else
-        0
-      end
-    end
-  end
-
-  # Insert the full color code for a given
-  # foreground/background combination.
-  def Irc.color(fg=nil,bg=nil)
-    str = Color.dup
-    if fg
-     str << Irc.find_color(fg).to_s
-    end
-    if bg
-      str << "," << Irc.find_color(bg).to_s
-    end
-    return str
-  end
-
 
   class BasicUserMessage
 
@@ -331,5 +363,67 @@ module ::Irc
         end
       }.uniq
     end
+
+    # The recurse depth of a message, for fake messages. 0 means an original
+    # message
+    def recurse_depth
+      unless defined? @recurse_depth
+        @recurse_depth = 0
+      end
+      @recurse_depth
+    end
+
+    # Set the recurse depth of a message, for fake messages. 0 should only
+    # be used by original messages
+    def recurse_depth=(val)
+      @recurse_depth = val
+    end
+  end
+
+  class Bot
+    module Plugins
+
+      # Maximum fake message recursion
+      MAX_RECURSE_DEPTH = 10
+
+      class RecurseTooDeep < RuntimeError
+      end
+
+      class BotModule
+        # Sometimes plugins need to create a new fake message based on an existing
+        # message: for example, this is done by alias, linkbot, reaction and remotectl.
+        #
+        # This method simplifies the message creation, including a recursion depth
+        # check.
+        #
+        # In the options you can specify the :bot, the :server, the :source,
+        # the :target, the message :class and whether or not to :delegate. To
+        # initialize these entries from an existing message, you can use :from
+        #
+        # If you don't specify a :from you should specify a :source.
+        #
+        def fake_message(string, opts={})
+          if from = opts[:from]
+            o = {
+              :bot => from.bot, :server => from.server, :source => from.source,
+              :target => from.target, :class => from.class, :delegate => true,
+              :depth => from.recurse_depth + 1
+            }.merge(opts)
+          else
+            o = {
+              :bot => @bot, :server => @bot.server, :target => @bot.myself,
+              :class => PrivMessage, :delegate => true, :depth => 1
+            }.merge(opts)
+          end
+          raise RecurseTooDeep if o[:depth] > MAX_RECURSE_DEPTH
+          new_m = o[:class].new(o[:bot], o[:server], o[:source], o[:target], string)
+          new_m.recurse_depth = o[:depth]
+          return new_m unless o[:delegate]
+          method = o[:class].to_s.gsub(/^Irc::|Message$/,'').downcase
+          method = 'privmsg' if method == 'priv'
+          o[:bot].plugins.irc_delegate(method, new_m)
+        end
+      end
+    end
   end
 end