module Boty # Public: Wrap the idea of something that should happen when a regex matches. # Actions are the underlying mecanism for store and execute the blocks passed # to `Bot#match` and `Bot#respond` invocations. # # Maybe one of the more important things to remember here is that an Action is # executed within the scope of a `Boty::DSL` instance already binded with the # `Bot` of the current `Session` (via `#instance_exec`). # # Examples: # # # some_script.rb # desc "omg! whoo!" # hear(/omg/i) do # im "whoo!" # end # # # Will generate an `Action` like this: # action = Action.new(bot, /omg/i, "omg! whoo!") { im "whoo!" } # # # Then this can be executed, by `#execute`. # action.execute "omg! this string triggers the hear block." # # # If the regex matches the parameter passed to execute, it'll invoke the # # block passed to `#hear`. class Action include Boty::Logger attr_reader :regex, :desc, :action # Public: Initialize an `Action` associated with a specific `Bot`. # # bot - A Bot that will react to the matches on the regex. # regex - A Regexp to match against messages received in the bot session. # action - A Proc that will be executed everytime the regex matches. def initialize(bot, regex, description, &action) @dsl = DSL.new bot @regex = regex @action = action self.desc = description end # Public: Check if a given Regexp and an optional block were the same used # to build this Action. # # Examples: # # send_im = -> { im "whoo!" } # action = Action.new(bot, /omg/i, "omg! whoo!", &send_im) # action.this? /omg/i, send_im # # => return true # # regex - A Regexp to be compared against `regex`. # block - An optional Proc to be checked against `action`. # # Returns True or False. def this?(regex, block) same_regex = regex == self.regex block ? same_regex && block == action : same_regex end # Public: Creates a `Regexp::Match` based on the internal `regex` against a # Slack::Message. # Then pass this match to be used as argument to the action per se. # # message - A Slack::Message object to be matched against the `regex`. # # Returns nothing. def execute(message) action_call regex.match(message.text) rescue => e logger.error e.message raise e end private # Internal: Store the ActionDescription for this action (create a new for it # if a nil value is the parameter). # # To make it possible to create handlers (that will became actions) without # a description, this method can work with a nil parameter. If a nil value # is passed, it creates an `ActionDescription` with only a regex (without a # description text). # # Returns an ActionDescription with the regex associated with this Action. def desc=(description) if description description.regex = regex else description = ActionDescription.new(nil, regex: regex) end @desc = description end # Internal: Effectively executes the action. # The regex match created on the `#execute` method will be exploded, so the # matches can be passed as arguments to the block. # Important to remember: the block passed to `Bot#match` or `Bot#respond` # will be executed within the scope of a `Boty::DSL`. # # Examples: # # # some_script.rb # hear(/(omg)\s(lol)/i) do |first_match, second_match| # im "whoo!" # end # # # will cause an invocation like this: # @dsl.instance_exec("omg", "lol") do |first_match, second_match| # im "whoo!" # end # # Returns nothing. def action_call(match) return unless match matches = Array(match) matches.shift @dsl.instance_exec(*matches, &action) end end end