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
# 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
+ # Message (derived from BasicUserMessage) and a Hash containing
# {:option => "bar", :otheroption => "baz"}
# See the #map method for more details.
class MessageMapper
@templates = Array.new
@fallback = 'usage'
end
-
+
# args:: hash format containing arguments for this template
#
# map a template string to an action. example:
# (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'
# 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
+ # 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
+ # component (whitespace seperated), or a * to suck 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'}
# 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',
# plugin.map 'urls search :limit :string', :action => 'search',
# :defaults => {:limit => 4},
# :requirements => {:limit => /^\d+$/},
- # :private => false
+ # :private => false
#
- def map(*args)
- @templates << Template.new(*args)
+ def map(botmodule, *args)
+ @templates << Template.new(botmodule, *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
failures << [tmpl, failure]
else
action = tmpl.options[:action] ? tmpl.options[:action] : tmpl.items[0]
- next unless @parent.respond_to?(action)
- auth = tmpl.options[:auth] ? tmpl.options[:auth] : tmpl.items[0]
+ unless @parent.respond_to?(action)
+ failures << [tmpl, "class does not respond to action #{action}"]
+ next
+ end
+ auth = tmpl.options[:full_auth_path]
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}"
return false
end
end
- debug failures.inspect
+ 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)
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)
+
+ def initialize(botmodule, template, hash={})
+ raise ArgumentError, "Third 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
+ if hash.has_key?(:auth)
+ warning "Command #{template} in #{botmodule} uses old :auth syntax, please upgrade"
+ end
+ if hash.has_key?(:full_auth_path)
+ warning "Command #{template} in #{botmodule} sets :full_auth_path, please don't do this"
+ else
+ case botmodule
+ when String
+ pre = botmodule
+ when Plugins::BotModule
+ pre = botmodule.name
+ else
+ raise ArgumentError, "Can't find auth base in #{botmodule.inspect}"
+ end
+ words = items.reject{ |x|
+ x == pre || x.kind_of?(Symbol)
+ }
+ if words.empty?
+ post = nil
+ else
+ post = words.first
+ end
+ if hash.has_key?(:auth_path)
+ extra = hash[:auth_path]
+ if extra.sub!(/^:/, "")
+ pre += "::" + post
+ post = nil
+ end
+ if extra.sub!(/:$/, "")
+ post = [post,words[1]].compact.join("::") if words.length > 1
+ end
+ pre = nil if extra.sub!(/^!/, "")
+ post = nil if extra.sub!(/!$/, "")
+ else
+ extra = nil
+ end
+ hash[:full_auth_path] = [pre,extra,post].compact.join("::")
+ # TODO check if the full_auth_path is sane
+ end
+
@options = hash
+ # debug "Create template #{self.inspect}"
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 == ""
# 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
+ # We must remove the initial * when present,
+ # because the parameters hash will intern both :item and *item as :item
+ it = item.to_s.sub(/^\*/,"").intern
+ raise ArgumentError, "Illegal template -- duplicate item #{it} in #{str.inspect}" if seen.key? it
+ seen[it] = true
end
seen
end
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