lib/roda.rb in roda-1.3.0 vs lib/roda.rb in roda-2.0.0

- old
+ new

@@ -57,22 +57,10 @@ @inherit_middleware = true @middleware = [] @opts = {} @route_block = nil - module RodaDeprecateMutation - [:[]=, :clear, :compare_by_identity, :default=, :default_proc=, :delete, :delete_if, - :keep_if, :merge!, :reject!, :replace, :select!, :shift, :store, :update].each do |m| - class_eval(<<-END, __FILE__, __LINE__+1) - def #{m}(*) - RodaPlugins.deprecate("Mutating this hash (\#{inspect}) via the #{m} method is deprecated, this hash will be frozen in Roda 2.") - super - end - END - end - end - # Module in which all Roda plugins should be stored. Also contains logic for # registering and loading plugins. module RodaPlugins # Stores registered plugins @plugins = RodaCache.new @@ -96,16 +84,10 @@ # Roda::RodaPlugins.register_plugin(:plugin_name, PluginModule) def self.register_plugin(name, mod) @plugins[name] = mod end - # Emit a deprecation message. By default this just calls warn. You can override this - # method to log deprecation messages to a file or include backtraces (or something else). - def self.deprecate(msg) - warn(msg) - end - # The base plugin for Roda, implementing all default functionality. # Methods are put into a plugin so future plugins can easily override # them and call super to get the default behavior. module Base # Class methods for the Roda class. @@ -149,15 +131,10 @@ @opts.freeze @middleware.freeze super end - def hash_matcher(key, &block) - RodaPlugins.deprecate("Roda.hash_matcher is deprecated and will be removed in Roda 2. It has been moved to the hash_matcher plugin.") - self::RodaRequest.send(:define_method, :"match_#{key}", &block) - end - # When inheriting Roda, copy the shared data into the subclass, # and setup the request and response subclasses. def inherited(subclass) raise RodaError, "Cannot subclass a frozen Roda class" if frozen? super @@ -165,13 +142,10 @@ subclass.instance_variable_set(:@middleware, @inherit_middleware ? @middleware.dup : []) subclass.instance_variable_set(:@opts, opts.dup) subclass.opts.to_a.each do |k,v| if (v.is_a?(Array) || v.is_a?(Hash)) && !v.frozen? subclass.opts[k] = v.dup - if v.is_a?(RodaDeprecateMutation) - subclass.opts[k].extend(RodaDeprecateMutation) - end end end subclass.instance_variable_set(:@route_block, @route_block) subclass.send(:build_rack_app) @@ -202,20 +176,10 @@ self::RodaResponse.send(:include, plugin::ResponseMethods) if defined?(plugin::ResponseMethods) self::RodaResponse.extend(plugin::ResponseClassMethods) if defined?(plugin::ResponseClassMethods) plugin.configure(self, *args, &block) if plugin.respond_to?(:configure) end - def request_module(mod = nil, &block) - RodaPlugins.deprecate("Roda.request_module is deprecated and will be removed in Roda 2. It has been moved to the module_include plugin.") - module_include(:request, mod, &block) - end - - def response_module(mod = nil, &block) - RodaPlugins.deprecate("Roda.response_module is deprecated and will be removed in Roda 2. It has been moved to the module_include plugin.") - module_include(:response, mod, &block) - end - # Setup routing tree for the current Roda application, and build the # underlying rack application using the stored middleware. Requires # a block, which is yielded the request. By convention, the block # argument should be named +r+. Example: # @@ -252,55 +216,43 @@ # Build the rack app to use def build_rack_app if block = @route_block builder = Rack::Builder.new @middleware.each{|a, b| builder.use(*a, &b)} - builder.run lambda{|env| allocate.call(env, &block)} + builder.run lambda{|env| new(env).call(&block)} @app = builder.to_app end end - - # REMOVE20 - def module_include(type, mod) - if type == :response - klass = self::RodaResponse - iv = :@response_module - else - klass = self::RodaRequest - iv = :@request_module - end - - if mod - raise RodaError, "can't provide both argument and block to response_module" if block_given? - klass.send(:include, mod) - else - if instance_variable_defined?(iv) - mod = instance_variable_get(iv) - else - mod = instance_variable_set(iv, Module.new) - klass.send(:include, mod) - end - - mod.module_eval(&Proc.new) if block_given? - end - - mod - end end # Instance methods for the Roda class. + # + # In addition to the listed methods, the following two methods are available: + # + # request :: The instance of the request class related to this request. + # This is the same object yielded by Roda.route. + # response :: The instance of the response class related to this request. module InstanceMethods - # Create a request and response of the appopriate - # class, the instance_exec the route block with - # the request, handling any halts. This is not usually - # called directly. - def call(env, &block) - @_request = self.class::RodaRequest.new(self, env) - @_response = self.class::RodaResponse.new - _route(&block) + # Create a request and response of the appopriate class + def initialize(env) + klass = self.class + @_request = klass::RodaRequest.new(self, env) + @_response = klass::RodaResponse.new end + # instance_exec the route block in the scope of the + # receiver, with the related request. Catch :halt so that + # the route block can throw :halt at any point with the + # rack response to use. + def call(&block) + catch(:halt) do + r = @_request + r.block_result(instance_exec(r, &block)) + @_response.finish + end + end + # The environment hash for the current request. Example: # # env['REQUEST_METHOD'] # => 'GET' def env @_request.env @@ -315,40 +267,25 @@ # end def opts self.class.opts end - # The instance of the request class related to this request. - # This is the same object yielded by Roda.route. - def request - @_request - end + attr_reader :_request # :nodoc: + alias request _request + remove_method :_request - # The instance of the response class related to this request. - def response - @_response - end + attr_reader :_response # :nodoc: + alias response _response + remove_method :_response # The session hash for the current request. Raises RodaError # if no session exists. Example: # # session # => {} def session @_request.session end - - private - - # Internals of #call, extracted so that plugins can override - # behavior after the request and response have been setup. - def _route(&block) - catch(:halt) do - r = @_request - r.block_result(instance_exec(r, &block)) - @_response.finish - end - end end # Class methods for RodaRequest module RequestClassMethods # Reference to the Roda class related to this request class. @@ -416,10 +353,11 @@ # Store the roda instance and environment. def initialize(scope, env) @scope = scope @captures = [] + @remaining_path = env[PATH_INFO] super(env) end # Handle match block return values. By default, if a string is given # and the response is empty, use the string as the response body. @@ -463,11 +401,11 @@ # Does a terminal match on the current path, matching only if the arguments # have fully matched the path. If it matches, the match block is # executed, and when the match block returns, the rack response is # returned. # - # r.path_info + # r.remaining_path # # => "/foo/bar" # # r.is 'foo' do # # does not match, as path isn't fully matched (/bar remaining) # end @@ -485,11 +423,11 @@ # end # # Note that this matches only if the path after matching the arguments # is empty, not if it still contains a trailing slash: # - # r.path_info + # r.remaining_path # # => "/foo/bar/" # # r.is 'foo/bar' do # # does not match, as path isn't fully matched (/ remaining) # end @@ -524,11 +462,11 @@ # Does a match on the path, matching only if the arguments # have matched the path. Because this doesn't fully match the # path, this is usually used to setup branches of the routing tree, # not for final handling of the request. # - # r.path_info + # r.remaining_path # # => "/foo/bar" # # r.on 'foo' do # # matches, path is /bar after matching # end @@ -556,11 +494,12 @@ end end # The already matched part of the path, including the original SCRIPT_NAME. def matched_path - @env[SCRIPT_NAME] + e = @env + e[SCRIPT_NAME] + e[PATH_INFO].chomp(@remaining_path) end # This an an optimized version of Rack::Request#path. # # r.env['SCRIPT_NAME'] = '/foo' @@ -570,21 +509,13 @@ def path e = @env "#{e[SCRIPT_NAME]}#{e[PATH_INFO]}" end - def full_path_info - RodaPlugins.deprecate("RodaRequest#full_path_info is deprecated and will be removed in Roda 2. Switch to using #path.") - path - end + # The current path to match requests against. + attr_reader :remaining_path - # The current path to match requests against. This is the same as PATH_INFO - # in the environment, which gets updated as the request is being routed. - def remaining_path - @env[PATH_INFO] - end - # Match POST requests. If no arguments are provided, matches all POST # requests, otherwise, matches only POST requests where the arguments # given fully consume the path. def post(*args, &block) _verb(args, &block) if post? @@ -635,31 +566,31 @@ # Routing matches that only matches +GET+ requests where the current # path is +/+. If it matches, the match block is executed, and when # the match block returns, the rack response is returned. # - # [r.request_method, r.path_info] + # [r.request_method, r.remaining_path] # # => ['GET', '/'] # # r.root do # # matches # end # # This is usuable inside other match blocks: # - # [r.request_method, r.path_info] + # [r.request_method, r.remaining_path] # # => ['GET', '/foo/'] # # r.on 'foo' do # r.root do # # matches # end # end # # Note that this does not match non-+GET+ requests: # - # [r.request_method, r.path_info] + # [r.request_method, r.remaining_path] # # => ['POST', '/'] # # r.root do # # does not match # end @@ -667,11 +598,11 @@ # Use <tt>r.post ""</tt> for +POST+ requests where the current path # is +/+. # # Nor does it match empty paths: # - # [r.request_method, r.path_info] + # [r.request_method, r.remaining_path] # # => ['GET', '/foo'] # # r.on 'foo' do # r.root do # # does not match @@ -691,12 +622,29 @@ # the processing of the request: # # r.run(proc{[403, {}, []]}) unless r['letmein'] == '1' # r.run(proc{[404, {}, []]}) # response.status = 404 # not reached + # + # This updates SCRIPT_NAME/PATH_INFO based on the current remaining_path + # before dispatching to another rack app, so the app still works as + # a URL mapper. def run(app) - throw :halt, app.call(@env) + e = @env + path = @remaining_path + sn = SCRIPT_NAME + pi = PATH_INFO + script_name = e[sn] + path_info = e[pi] + begin + e[sn] += path_info.chomp(path) + e[pi] = path + throw :halt, app.call(e) + ensure + e[sn] = script_name + e[pi] = path_info + end end # The session for the current request. Raises a RodaError if # a session handler has not been loaded. def session @@ -778,11 +726,11 @@ # match, returns false without changes. Otherwise, modifies # SCRIPT_NAME to include the matched path, removes the matched # path from PATH_INFO, and updates captures with any regex captures. def consume(pattern) if matchdata = remaining_path.match(pattern) - update_remaining_path(matchdata.post_match) + @remaining_path = matchdata.post_match @captures.concat(matchdata.captures) end end # The default path to use for redirects when a path is not given. @@ -811,33 +759,22 @@ # If all of the arguments match, yields to the match block and # returns the rack response when the block returns. If any of # the match arguments doesn't match, does nothing. def if_match(args) - keep_remaining_path do - # For every block, we make sure to reset captures so that - # nesting matchers won't mess with each other's captures. - @captures.clear + path = @remaining_path + # For every block, we make sure to reset captures so that + # nesting matchers won't mess with each other's captures. + @captures.clear - return unless match_all(args) - block_result(yield(*captures)) - throw :halt, response.finish - end - end - - # Yield to the block, restoring SCRIPT_NAME and PATH_INFO to - # their initial values before returning from the block. - def keep_remaining_path - env = @env - script = env[sn = SCRIPT_NAME] - path = env[pi = PATH_INFO] - yield + return unless match_all(args) + block_result(yield(*captures)) + throw :halt, response.finish ensure - env[sn] = script - env[pi] = path + @remaining_path = path end - + # Attempt to match the argument to the given request, handling # common ruby types. def match(matcher) case matcher when String @@ -862,47 +799,19 @@ # Match only if all of the arguments in the given array match. def match_all(args) args.all?{|arg| match(arg)} end - def match_extension(ext) - RodaPlugins.deprecate("The :extension matcher is deprecated and will be removed in Roda 2. It has been moved to the path_matchers plugin.") - consume(self.class.cached_matcher([:extension, ext]){/([^\\\/]+)\.#{ext}/}) - end - # Match by request method. This can be an array if you want # to match on multiple methods. def match_method(type) if type.is_a?(Array) type.any?{|t| match_method(t)} else type.to_s.upcase == @env[REQUEST_METHOD] end end - - def match_param(key) - RodaPlugins.deprecate("The :param matcher is deprecated and will be removed in Roda 2. It has been moved to the param_matchers plugin.") - if v = self[key] - @captures << v - end - end - - def match_param!(key) - RodaPlugins.deprecate("The :param! matcher is deprecated and will be removed in Roda 2. It has been moved to the param_matchers plugin.") - if (v = self[key]) && !v.empty? - @captures << v - end - end - - # Update PATH_INFO and SCRIPT_NAME based on the matchend and remaining variables. - def update_remaining_path(remaining) - e = @env - - # Don't mutate SCRIPT_NAME, breaks try - e[SCRIPT_NAME] += e[pi = PATH_INFO].chomp(remaining) - e[pi] = remaining - end end # Class methods for RodaResponse module ResponseClassMethods # Reference to the Roda class related to this response class. @@ -957,15 +866,10 @@ # The default headers to use for responses. def default_headers DEFAULT_HEADERS end - def delete_cookie(key, value = {}) - RodaPlugins.deprecate("RodaResponse#delete_cookie is deprecated and will be removed in Roda 2. It has been moved to the cookies plugin.") - ::Rack::Utils.delete_cookie_header!(@headers, key, value) - end - # Whether the response body has been written to yet. Note # that writing an empty string to the response body marks # the response as not empty. Example: # # response.empty? # => true @@ -1021,14 +925,9 @@ end # Return the Roda class related to this response. def roda_class self.class.roda_class - end - - def set_cookie(key, value) - RodaPlugins.deprecate("RodaResponse#set_cookie is deprecated and will be removed in Roda 2. It has been moved to the cookies plugin.") - ::Rack::Utils.set_cookie_header!(@headers, key, value) end # Write to the response body. Returns nil. # # response.write('foo')