From 08fed54ce99f0e55aad78ab50b83c03ed3493c5f Mon Sep 17 00:00:00 2001 From: Griffin Smith Date: Sun, 31 Jan 2016 19:11:52 -0500 Subject: [PATCH] Add support for passing a Proc to routes Add support for passing a Proc to routes instead of a regular expression for matching - this proc will then be called with the message in the context of the handler object and should return a boolean indicating whether the route matches or not. This allows for dynamically defined routing, plus support for configuring things like command names and prefixes, which is useful as plugin configuration is only available on a handler instance, not the singleton. Probably addresses most of what #100 is asking for --- lib/lita/handler/chat_router.rb | 20 ++++++++++++-------- lib/lita/route_validator.rb | 15 ++++++++++++--- spec/lita/handler/chat_router_spec.rb | 14 ++++++++++++++ 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/lib/lita/handler/chat_router.rb b/lib/lita/handler/chat_router.rb index 1080cdb8..7a5a5aa1 100644 --- a/lib/lita/handler/chat_router.rb +++ b/lib/lita/handler/chat_router.rb @@ -31,7 +31,9 @@ class Route # @overload route(pattern, method_name, **options) # Creates a chat route. - # @param pattern [Regexp] A regular expression to match incoming messages against. + # @param pattern [Regexp, Proc] Either a regular expression to match incoming messages against, or a + # Proc that will be called in the context of the handler with the message, and should return a + # boolean indicating whether that message matches the route # @param method_name [Symbol, String] The name of the instance method to trigger. # @param command [Boolean] Whether or not the message must be directed at the robot. # @param restrict_to [Array, nil] An optional list of authorization @@ -41,7 +43,9 @@ class Route # @return [void] # @overload route(pattern, **options) # Creates a chat route. - # @param pattern [Regexp] A regular expression to match incoming messages against. + # @param pattern [Regexp, Proc] Either a regular expression to match incoming messages against, or a + # Proc that will be called in the context of the handler with the message, and should return a + # boolean indicating whether that message matches the route # @param command [Boolean] Whether or not the message must be directed at the robot. # @param restrict_to [Array, nil] An optional list of authorization # groups the user must be in to trigger the route. @@ -76,8 +80,9 @@ def routes # @param message [Message] The incoming message. # @return [Boolean] Whether or not the message matched any routes. def dispatch(robot, message) + handler = new(robot) routes.map do |route| - next unless route_applies?(route, message, robot) + next unless route_applies?(handler, route, message, robot) log_dispatch(robot, route) robot.trigger( :message_dispatched, @@ -86,7 +91,7 @@ def dispatch(robot, message) message: message, robot: robot ) - dispatch_to_route(route, robot, message) + dispatch_to_route(handler, route, robot, message) true end.any? end @@ -97,10 +102,9 @@ def dispatch(robot, message) # @param message [Message] The incoming message. # @return [void] # @since 3.3.0 - def dispatch_to_route(route, robot, message) + def dispatch_to_route(handler, route, robot, message) response = Response.new(message, route.pattern) robot.hooks[:trigger_route].each { |hook| hook.call(response: response, route: route) } - handler = new(robot) route.callback.call(handler, response) rescue => error log_error(robot, error) @@ -118,8 +122,8 @@ def default_route_options end # Determines whether or not an incoming messages should trigger a route. - def route_applies?(route, message, robot) - RouteValidator.new(self, route, message, robot).call + def route_applies?(handler, route, message, robot) + RouteValidator.new(handler, route, message, robot).call end # Logs the dispatch of message. diff --git a/lib/lita/route_validator.rb b/lib/lita/route_validator.rb index 5363dc15..d719366a 100644 --- a/lib/lita/route_validator.rb +++ b/lib/lita/route_validator.rb @@ -2,7 +2,7 @@ module Lita # Determines if an incoming message should trigger a route. # @api private class RouteValidator - # The handler class the route belongs to. + # The handler the route belongs to. attr_reader :handler # The incoming message. @@ -25,6 +25,11 @@ def initialize(handler, route, message, robot) @robot = robot end + # The handler class the route belongs to. + def handler_class + @handler_class ||= handler.class + end + # Returns a boolean indicating whether or not the route should be triggered. # @return [Boolean] Whether or not the route should be triggered. def call @@ -59,13 +64,17 @@ def from_self?(message, robot) # Message must match the pattern def matches_pattern?(route, message) - route.pattern === message.body + if route.pattern.is_a?(Proc) || route.pattern.is_a?(Symbol) + instance_exec(handler, message, &route.pattern) + else + route.pattern === message.body + end end # Allow custom route hooks to reject the route def passes_route_hooks?(route, message, robot) robot.hooks[:validate_route].all? do |hook| - hook.call(handler: handler, route: route, message: message, robot: robot) + hook.call(handler: handler_class, route: route, message: message, robot: robot) end end diff --git a/spec/lita/handler/chat_router_spec.rb b/spec/lita/handler/chat_router_spec.rb index 3d31cd14..6f75a0c8 100644 --- a/spec/lita/handler/chat_router_spec.rb +++ b/spec/lita/handler/chat_router_spec.rb @@ -11,6 +11,7 @@ def self.name route(/command/, :command, command: true) route(/admin/, :admin, restrict_to: :admins) route(/error/, :error) + route(:proc_route?, :trigger_proc_route) route(/validate route hook/, :validate_route_hook, code_word: true) route(/trigger route hook/, :trigger_route_hook, custom_data: "trigger route hook") @@ -30,6 +31,14 @@ def error(_response) raise end + def proc_route?(message) + message.body == "proc route" + end + + def trigger_proc_route(response) + response.reply("trigger proc route") + end + def validate_route_hook(response) response.reply("validate route hook") end @@ -50,6 +59,11 @@ def trigger_route_hook(response) expect(replies.last).to eq("message") end + it "routes messages satisfying a proc to the supplied method" do + send_message("proc route") + expect(replies.last).to eq("trigger proc route") + end + it "routes a matching message even if addressed to the robot" do send_command("message") expect(replies.last).to eq("message")