module Rack class Rewrite class RuleSet attr_reader :rules def initialize #:nodoc: @rules = [] end protected # We're explicitly defining private functions for our DSL rather than # using method_missing # Creates a rewrite rule that will simply rewrite the REQUEST_URI, # PATH_INFO, and QUERYSTRING headers of the Rack environment. The # user's browser will continue to show the initially requested URL. # # rewrite '/wiki/John_Trupiano', '/john' # rewrite %r{/wiki/(\w+)_\w+}, '/$1' def rewrite(from, to) @rules << Rule.new(:rewrite, from, to) end # Creates a redirect rule that will send a 301 when matching. # # r301 '/wiki/John_Trupiano', '/john' # r301 '/contact-us.php', '/contact-us' def r301(from, to) @rules << Rule.new(:r301, from, to) end # Creates a redirect rule that will send a 302 when matching. # # r302 '/wiki/John_Trupiano', '/john' # r302 '/wiki/(.*)', 'http://www.google.com/?q=$1' def r302(from, to) @rules << Rule.new(:r302, from, to) end end # TODO: Break rules into subclasses class Rule #:nodoc: attr_reader :rule_type, :from, :to def initialize(rule_type, from, to) #:nodoc: @rule_type, @from, @to = rule_type, from, to end def matches?(path) #:nodoc: case self.from when Regexp path =~ self.from when String path == self.from else false end end # Either (a) return a Rack response (short-circuiting the Rack stack), or # (b) alter env as necessary and return true def apply!(env) #:nodoc: interpreted_to = self.send(:interpret_to, env['REQUEST_URI']) case self.rule_type when :r301 [301, {'Location' => interpreted_to}, ['Redirecting...']] when :r302 [302, {'Location' => interpreted_to}, ['Redirecting...']] when :rewrite # return [200, {}, {:content => env.inspect}] env['REQUEST_URI'] = interpreted_to if q_index = interpreted_to.index('?') env['PATH_INFO'] = interpreted_to[0..q_index-1] env['QUERYSTRING'] = interpreted_to[q_index+1..interpreted_to.size-1] else env['PATH_INFO'] = interpreted_to env['QUERYSTRING'] = '' end true else raise Exception.new("Unsupported rule: #{self.rule_type}") end end private # is there a better way to do this? def interpret_to(path) #:nodoc: if self.from.is_a?(Regexp) if from_match_data = self.from.match(path) computed_to = self.to.dup (from_match_data.size - 1).downto(1) do |num| computed_to.gsub!("$#{num}", from_match_data[num]) end return computed_to end end self.to end end end end