lib/roda/plugins/match_hook_args.rb



# frozen-string-literal: true

#
class Roda
  module RodaPlugins
    # The match_hook_args plugin adds hooks that are called upon a successful match
    # by any of the matchers. It is similar to the match_hook plugin, but it allows
    # for passing the matchers and block arguments for each match method.
    #
    #   plugin :match_hook_args
    #
    #   add_match_hook do |matchers, block_args|
    #     logger.debug("matchers: #{matchers.inspect}. #{block_args.inspect} yielded.")
    #   end
    #
    #   # Term is an implicit matcher used for terminating matches, and
    #   # will be included in the array of matchers yielded to the match hook 
    #   # if a terminating match is used.
    #   term = self.class::RodaRequest::TERM
    #
    #   route do |r|
    #     r.root do
    #       # for a request for /
    #       # matchers: nil, block_args: nil
    #     end
    #
    #     r.on 'a', ['b', 'c'], Integer do |segment, id|
    #       # for a request for /a/b/1
    #       # matchers: ["a", ["b", "c"], Integer], block_args: ["b", 1]
    #     end
    #
    #     r.get 'd' do
    #       # for a request for /d
    #       # matchers: ["d", term], block_args: []
    #     end
    #   end
    module MatchHookArgs
      def self.configure(app)
        app.opts[:match_hook_args] ||= []
      end

      module ClassMethods
        # Freeze the array of hook methods when freezing the app
        def freeze
          opts[:match_hook_args].freeze
          super
        end

        # Add a match hook that will be called with matchers and block args.
        def add_match_hook(&block)
          opts[:match_hook_args] << define_roda_method("match_hook_args", :any, &block)

          if opts[:match_hook_args].length == 1
            class_eval("alias _match_hook_args #{opts[:match_hook_args].first}", __FILE__, __LINE__)
          else
            class_eval("def _match_hook_args(v, a); #{opts[:match_hook_args].map{|m| "#{m}(v, a)"}.join(';')} end", __FILE__, __LINE__)
          end

          public :_match_hook_args

          nil
        end
      end

      module InstanceMethods
        # Default empty method if no match hooks are defined.
        def _match_hook_args(matchers, block_args)
        end
      end

      module RequestMethods
        private

        # Call the match hook with matchers and block args if yielding to the block before yielding to the block.
        def if_match(v)
          super do |*a|
            scope._match_hook_args(v, a)
            yield(*a)
          end
        end

        # Call the match hook with nil matchers and blocks before yielding to the block
        def always
          scope._match_hook_args(nil, nil)
          super
        end
      end
    end

    register_plugin :match_hook_args, MatchHookArgs
  end
end