class Irc::Bot::MessageTemplate

MessageTemplate is the class that holds the actual message template map()'d by a BotModule and handled by a MessageMapper

Attributes

botmodule[R]
defaults[R]
items[R]
options[R]
regexp[R]
template[R]

Public Class Methods

initialize(botmodule, template, opts={}) click to toggle source

Create a new MessageTemplate associated to BotModule botmodule, with template template and options opts

# File lib/rbot/messagemapper.rb, line 411
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) : {}
  @template = template
  case botmodule
  when String
    @botmodule = botmodule
  when Plugins::BotModule
    @botmodule = botmodule.name
  else
    raise ArgumentError, "#{botmodule.inspect} is not a botmodule nor a botmodule name"
  end

  self.items = template
  # @dyn_items is an array of MessageParameters, except for the first entry
  # which is the template
  @dyn_items = @items.collect { |it|
    if it.kind_of?(Symbol)
      i = it.to_s
      opt = MessageParameter.new(i)
      if i.sub!(/^\*/,"")
        opt.name = i
        opt.multi = true
      end
      opt.default = @defaults[opt.name]
      opt.collector = @requirements[opt.name]
      opt
    else
      nil
    end
  }
  @dyn_items.unshift(template).compact!
  debug "Items: #{@items.inspect}; dyn items: #{@dyn_items.inspect}"

  self.regexp = template
  debug "Command #{template.inspect} in #{@botmodule} will match using #{@regexp}"

  set_auth_path(hash)

  unless hash.has_key?(:action)
    hash[:action] = items[0]
  end

  @options = hash

  # debug "Create template #{self.inspect}"
end

Public Instance Methods

inspect() click to toggle source
# File lib/rbot/messagemapper.rb, line 645
def inspect
  when_str = @requirements.empty? ? "" : " when #{@requirements.inspect}"
  default_str = @defaults.empty? ? "" : " || #{@defaults.inspect}"
  "<#{self.class.to_s} #{@items.map { |c| c.inspect }.join(' ').inspect}#{default_str}#{when_str}>"
end
items=(str) click to toggle source
# File lib/rbot/messagemapper.rb, line 499
def items=(str)
  raise ArgumentError, "template #{str.inspect} should be a String" unless str.kind_of?(String)

  # split and convert ':xyz' to symbols
  items = str.strip.split(/\]?\s+\[?|\]?$/).collect { |c|
    # there might be extra (non-alphanumeric) stuff (e.g. punctuation) after the symbol name
    if /^(:|\*)(\w+)(.*)/ =~ c
      sym = ($1 == ':' ) ? $2.intern : "*#{$2}".intern
      if $3.empty?
        sym
      else
        [sym, $3]
      end
    else
      c
    end
  }.flatten
  @items = items

  raise ArgumentError, "Illegal template -- first component cannot be dynamic: #{str.inspect}" if @items.first.kind_of? Symbol

  raise ArgumentError, "Illegal template -- first component cannot be optional: #{str.inspect}" if @items.first =~ /\[|\]/

  # Verify uniqueness of each component.
  @items.inject({}) do |seen, item|
    if item.kind_of? Symbol
      # 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
end
recognize(m) click to toggle source

Recognize the provided string components, returning a hash of recognized values, or [nil, reason] if the string isn't recognized.

# File lib/rbot/messagemapper.rb, line 581
def recognize(m)

  debug "Testing #{m.message.inspect} against #{self.inspect}"

  matching = @regexp.match(m.message)
  return MessageMapper::NoMatchFailure.new(self, m) unless matching
  return MessageMapper::PartialMatchFailure.new(self, m) unless matching[0] == m.message

  return MessageMapper::NotPrivateFailure.new(self, m) if @options.has_key?(:private) && !@options[:private] && m.private?
  return MessageMapper::NotPublicFailure.new(self, m) if @options.has_key?(:public) && !@options[:public] && !m.private?

  debug_match = matching[1..-1].collect{ |d| d.inspect}.join(', ')
  debug "#{m.message.inspect} matched #{@regexp} with #{debug_match}"
  debug "Associating #{debug_match} with dyn items #{@dyn_items.join(', ')}"

  options = @defaults.dup

  @dyn_items.each_with_index { |it, i|
    next if i == 0
    item = it.name
    debug "dyn item #{item} (multi-word: #{it.multi?.inspect})"
    if it.multi?
      if matching[i].nil?
        default = it.default
        case default
        when Array
          value = default.clone
        when String
          value = default.strip.split
        when nil, false, []
          value = []
        else
          warning "Unmanageable default #{default} detected for :*#{item.to_s}, using []"
          value = []
        end
        case default
        when String
          value.instance_variable_set(:@string_value, default)
        else
          value.instance_variable_set(:@string_value, value.join(' '))
        end
      else
        value = matching[i].split
        value.instance_variable_set(:@string_value, matching[i])
      end
      def value.to_s
        @string_value
      end
    else
      if matching[i].nil?
        warning "No default value for option #{item.inspect} specified" unless @defaults.has_key?(item)
        value = it.default
      else
        value = it.collect(matching[i])
      end
    end
    options[item] = value
    debug "set #{item} to #{options[item].inspect}"
  }

  options.delete_if {|k, v| v.nil?} # Remove nil values.
  return options
end
regexp=(str) click to toggle source
# File lib/rbot/messagemapper.rb, line 535
def regexp=(str)
  # debug "Original string: #{str.inspect}"
  rx = Regexp.escape(str)
  # debug "Escaped: #{rx.inspect}"
  rx.gsub!(/((?:\ )*)(:|\\*)(\w+)/) { |m|
    whites = $1
    is_single = $2 == ":"
    name = $3.intern

    not_needed = @defaults.has_key?(name)

    has_req = @requirements[name]
    debug "Requirements for #{name}: #{has_req.inspect}"
    case has_req
    when nil
      sub = is_single ? "\\S+" : ".*?"
    when Regexp
      # Remove captures and the ^ and $ that are sometimes placed in requirement regexps
      sub = has_req.mm_cleanup
    when String
      sub = Regexp.escape(has_req)
    when Array
      sub = has_req[0].mm_cleanup
    when Hash
      sub = has_req[:regexp].mm_cleanup
    else
      warning "Odd requirement #{has_req.inspect} of class #{has_req.class} for parameter '#{name}'"
      sub = Regexp.escape(has_req.to_s) rescue "\\S+"
    end
    debug "Regexp for #{name}: #{sub.inspect}"
    s = "#{not_needed ? "(?:" : ""}#{whites}(#{sub})#{ not_needed ? ")?" : ""}"
  }
  # debug "Replaced dyns: #{rx.inspect}"
  rx.gsub!(/((?:\ )*)((?:\\[)+)/, '\2\1')
  # debug "Corrected optionals spacing: #{rx.inspect}"
  rx.gsub!(/\\[/, "(?:")
  rx.gsub!(/\\]/, ")?")
  # debug "Delimited optionals: #{rx.inspect}"
  rx.gsub!(/(?:\ )+/, "\\s+")
  # debug "Corrected spaces: #{rx.inspect}"
  # Created message (such as by fake_message) can contain multiple lines
  @regexp = /\A#{rx}\z/m
end
requirements_for(name) click to toggle source
# File lib/rbot/messagemapper.rb, line 651
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
set_auth_path(hash) click to toggle source
# File lib/rbot/messagemapper.rb, line 460
def set_auth_path(hash)
  if hash.has_key?(:auth)
    warning "Command #{@template.inspect} in #{@botmodule} uses old :auth syntax, please upgrade"
  end
  if hash.has_key?(:full_auth_path)
    warning "Command #{@template.inspect} in #{@botmodule} sets :full_auth_path, please don't do this"
  else
    pre = @botmodule
    words = items.reject{ |x|
      x == pre || x.kind_of?(Symbol) || x =~ /\[|\]/
    }
    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!(/:$/, "")
        if words.length > 1
          post = [post,words[1]].compact.join("::")
        end
      end
      pre = nil if extra.sub!(/^!/, "")
      post = nil if extra.sub!(/!$/, "")
      extra = nil if extra.empty?
    else
      extra = nil
    end
    hash[:full_auth_path] = [pre,extra,post].compact.join("::")
    debug "Command #{@template} in #{botmodule} will use authPath #{hash[:full_auth_path]}"
    # TODO check if the full_auth_path is sane
  end
end