lib/roda/plugins/path_rewriter.rb



class Roda
  module RodaPlugins
    # The path_rewriter plugin allows you to rewrite the remaining path
    # or the path info for requests.  This is useful if you want to
    # transparently treat some paths the same as other paths.
    #
    # By default, +rewrite_path+ will rewrite just the remaining path.  So
    # only routing in the current Roda app will be affected.  This is useful
    # if you have other code in your app that uses PATH_INFO and needs to
    # see the original PATH_INFO (for example, when using relative links).
    #
    #   rewrite_path '/a', '/b'
    #   # PATH_INFO '/a' => remaining_path '/b'
    #   # PATH_INFO '/a/c' => remaining_path '/b/c'
    #
    # In some cases, you may want to override PATH_INFO for the rewritten
    # paths, such as when you are passing the request to another Rack app.
    # For those cases, you can use the <tt>:path_info => true</tt> option to
    # +rewrite_path+.
    #
    #   rewrite_path '/a', '/b', :path_info => true
    #   # PATH_INFO '/a' => PATH_INFO '/b'
    #   # PATH_INFO '/a/c' => PATH_INFO '/b/c'
    #
    # If you pass a string to +rewrite_path+, it will rewrite all paths starting
    # with that string.  You can provide a regexp if you want more complete control,
    # such as only matching exact paths.
    #
    #   rewrite_path /\A\/a\z/, '/b'
    #   # PATH_INFO '/a' => remaining_path '/b'
    #   # PATH_INFO '/a/c' => remaining_path '/a/c', no change
    #
    # All path rewrites are applied in order, so if a path is rewritten by one rewrite,
    # it can be rewritten again by a later rewrite.  Note that PATH_INFO rewrites are
    # processed before remaining_path rewrites.
    module PathRewriter
      PATH_INFO = 'PATH_INFO'.freeze
      OPTS={}.freeze

      def self.configure(app)
        app.instance_exec do
          app.opts[:remaining_path_rewrites] ||= []
          app.opts[:path_info_rewrites] ||= []
        end
      end

      module ClassMethods
        # Freeze the path rewrite metadata.
        def freeze
          opts[:remaining_path_rewrites].freeze
          opts[:path_info_rewrites].freeze
          super
        end

        # Record a path rewrite from path +was+ to path +is+.  Options:
        # :path_info :: Modify PATH_INFO, not just remaining path.
        def rewrite_path(was, is, opts=OPTS)
          was = /\A#{Regexp.escape(was)}/ unless was.is_a?(Regexp)
          array = @opts[opts[:path_info] ? :path_info_rewrites : :remaining_path_rewrites]
          array << [was, is.dup.freeze].freeze
        end
      end

      module RequestMethods
        # Rewrite remaining_path and/or PATH_INFO based on the path rewrites.
        def initialize(scope, env)
          path_info = env[PATH_INFO]
          scope.class.opts[:path_info_rewrites].each do |was, is|
            path_info.sub!(was, is)
          end
          super
          remaining_path = @remaining_path = @remaining_path.dup
          scope.class.opts[:remaining_path_rewrites].each do |was, is|
            remaining_path.sub!(was, is)
          end
        end
      end
    end

    register_plugin(:path_rewriter, PathRewriter)
  end
end