12 @routes << Route.new(*args)
16 @routes.each {|route| yield route}
23 return false if @routes.empty?
25 @routes.each do |route|
26 options, failure = route.recognize(m)
28 failures << [route, failure]
30 action = route.options[:action] ? route.options[:action] : route.items[0]
31 next unless @parent.respond_to?(action)
32 auth = route.options[:auth] ? route.options[:auth] : action
33 if m.bot.auth.allow?(auth, m.source, m.replyto)
34 debug "route found and auth'd: #{action.inspect} #{options.inspect}"
35 @parent.send(action, m, options)
38 # if it's just an auth failure but otherwise the match is good,
39 # don't try any more handlers
43 debug failures.inspect
44 debug "no handler found, trying fallback"
45 if @fallback != nil && @parent.respond_to?(@fallback)
46 if m.bot.auth.allow?(@fallback, m.source, m.replyto)
47 @parent.send(@fallback, m, {})
57 attr_reader :defaults # The defaults hash
58 attr_reader :options # The options hash
60 def initialize(template, hash={})
61 raise ArgumentError, "Second argument must be a hash!" unless hash.kind_of?(Hash)
62 @defaults = hash[:defaults].kind_of?(Hash) ? hash.delete(:defaults) : {}
63 @requirements = hash[:requirements].kind_of?(Hash) ? hash.delete(:requirements) : {}
68 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
69 items.shift if items.first == ""
70 items.pop if items.last == ""
73 if @items.first.kind_of? Symbol
74 raise ArgumentError, "Illegal template -- first component cannot be dynamic\n #{str.inspect}"
77 # Verify uniqueness of each component.
78 @items.inject({}) do |seen, item|
79 if item.kind_of? Symbol
80 raise ArgumentError, "Illegal template -- duplicate item #{item}\n #{str.inspect}" if seen.key? item
87 # Recognize the provided string components, returning a hash of
88 # recognized values, or [nil, reason] if the string isn't recognized.
90 components = m.message.split(/\s+/)
96 value = @defaults.has_key?(item) ? @defaults[item].clone : []
98 value = components.clone
101 def value.to_s() self.join(' ') end
102 options[item.to_s.sub(/^\*/,"").intern] = value
103 elsif item.kind_of? Symbol
104 value = components.shift || @defaults[item]
105 if passes_requirements?(item, value)
106 options[item] = value
108 if @defaults.has_key?(item)
109 debug "item #{item} doesn't pass reqs but has a default of #{@defaults[item]}"
110 options[item] = @defaults[item].clone
111 # push the test-failed component back on the stack
112 components.unshift value
114 return nil, requirements_for(item)
118 return nil, "No value available for component #{item.inspect}" if components.empty?
119 component = components.shift
120 return nil, "Value for component #{item.inspect} doesn't match #{component}" if component != item
124 return nil, "Unused components were left: #{components.join '/'}" unless components.empty?
126 return nil, "route is not configured for private messages" if @options.has_key?(:private) && !@options[:private] && m.private?
127 return nil, "route is not configured for public messages" if @options.has_key?(:public) && !@options[:public] && !m.private?
129 options.delete_if {|k, v| v.nil?} # Remove nil values.
134 when_str = @requirements.empty? ? "" : " when #{@requirements.inspect}"
135 default_str = @defaults.empty? ? "" : " || #{@defaults.inspect}"
136 "<#{self.class.to_s} #{@items.collect{|c| c.kind_of?(String) ? c : c.inspect}.join(' ').inspect}#{default_str}#{when_str}>"
139 # Verify that the given value passes this route's requirements
140 def passes_requirements?(name, value)
141 return @defaults.key?(name) && @defaults[name].nil? if value.nil? # Make sure it's there if it should be
143 case @requirements[name]
147 match = @requirements[name].match(value)
148 match && match[0].length == value.length
150 @requirements[name] == value.to_s
154 def requirements_for(name)
155 name = name.to_s.sub(/^\*/,"").intern if (/^\*/ =~ name.inspect)
156 presence = (@defaults.key?(name) && @defaults[name].nil?)
157 requirement = case @requirements[name]
159 when Regexp then "match #{@requirements[name].inspect}"
160 else "be equal to #{@requirements[name].inspect}"
162 if presence && requirement then "#{name} must be present and #{requirement}"
163 elsif presence || requirement then "#{name} must #{requirement || 'be present'}"
164 else "#{name} has no requirements"