diff options
Diffstat (limited to 'lib/rbot/messagemapper.rb')
-rw-r--r-- | lib/rbot/messagemapper.rb | 121 |
1 files changed, 107 insertions, 14 deletions
diff --git a/lib/rbot/messagemapper.rb b/lib/rbot/messagemapper.rb index c8e2b6ba..b079acd6 100644 --- a/lib/rbot/messagemapper.rb +++ b/lib/rbot/messagemapper.rb @@ -1,38 +1,131 @@ 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 - @routes = Array.new + @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) - @routes << Template.new(*args) + @templates << Template.new(*args) end def each - @routes.each {|route| yield route} + @templates.each {|tmpl| yield tmpl} end def last - @routes.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 @routes.empty? + return false if @templates.empty? failures = [] - @routes.each do |route| - options, failure = route.recognize(m) + @templates.each do |tmpl| + options, failure = tmpl.recognize(m) if options.nil? - failures << [route, failure] + failures << [tmpl, failure] else - action = route.options[:action] ? route.options[:action] : route.items[0] + action = tmpl.options[:action] ? tmpl.options[:action] : tmpl.items[0] next unless @parent.respond_to?(action) - auth = route.options[:auth] ? route.options[:auth] : route.items[0] + 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 "route found and auth'd: #{action.inspect} #{options.inspect}" + debug "template match found and auth'd: #{action.inspect} #{options.inspect}" @parent.send(action, m, options) return true end @@ -124,8 +217,8 @@ module Irc return nil, "Unused components were left: #{components.join '/'}" unless components.empty? - return nil, "route is not configured for private messages" if @options.has_key?(:private) && !@options[:private] && m.private? - return nil, "route is not configured for public messages" if @options.has_key?(:public) && !@options[:public] && !m.private? + 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 @@ -137,7 +230,7 @@ module Irc "<#{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 route's requirements + # 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 |