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')