summaryrefslogtreecommitdiff
path: root/lib/rbot/messagemapper.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rbot/messagemapper.rb')
-rw-r--r--lib/rbot/messagemapper.rb121
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