lib/roda.rb in roda-3.18.0 vs lib/roda.rb in roda-3.19.0
- old
+ new
@@ -116,11 +116,13 @@
# them and call super to get the default behavior.
module Base
# Class methods for the Roda class.
module ClassMethods
# The rack application that this class uses.
- attr_reader :app
+ def app
+ @app || build_rack_app
+ end
# Whether middleware from the current class should be inherited by subclasses.
# True by default, should be set to false when using a design where the parent
# class accepts requests and uses run to dispatch the request to a subclass.
attr_accessor :inherit_middleware
@@ -140,11 +142,11 @@
end
# Clear the middleware stack
def clear_middleware!
@middleware.clear
- build_rack_app
+ @app = nil
end
# Define an instance method using the block with the provided name and
# expected arity. If the name is given as a Symbol, it is used directly.
# If the name is given as a String, a unique name will be generated using
@@ -170,10 +172,11 @@
# expected arity is 0 or 1.
def define_roda_method(meth, expected_arity, &block)
if meth.is_a?(String)
meth = roda_method_name(meth)
end
+ call_meth = meth
if (check_arity = opts.fetch(:check_arity, true)) && !block.lambda?
required_args, optional_args, rest, keyword = _define_roda_method_arg_numbers(block)
if keyword == :required && (expected_arity == 0 || expected_arity == 1)
@@ -192,12 +195,19 @@
when 1
if required_args == 0 && optional_args == 0 && !rest
if check_arity == :warn
RodaPlugins.warn "Arity mismatch in block passed to define_roda_method. Expected Arity 1, but no arguments accepted for #{block.inspect}"
end
+ temp_method = roda_method_name("temp")
+ class_eval("def #{temp_method}(_) #{meth =~ /\A\w+\z/ ? "#{meth}_arity" : "send(:\"#{meth}_arity\")"} end", __FILE__, __LINE__)
+ alias_method meth, temp_method
+ undef_method temp_method
+ private meth
+ meth = :"#{meth}_arity"
+ elsif required_args > 1
b = block
- block = lambda{|_| instance_exec(&b)} # Fallback
+ block = lambda{|r| instance_exec(r, &b)} # Fallback
end
when :any
if check_dynamic_arity = opts.fetch(:check_dynamic_arity, check_arity)
if keyword
# Complexity of handling keyword arguments using define_method is too high,
@@ -237,14 +247,13 @@
end
send(meth, *a)
end
private arity_meth
- arity_meth
- else
- meth
end
+
+ call_meth
end
# Expand the given path, using the root argument as the base directory.
def expand_path(path, root=opts[:root])
::File.expand_path(path, root)
@@ -256,12 +265,11 @@
# be raised if you try to modify the internal state after calling this.
#
# Note that freezing the class prevents you from subclassing it, mostly because
# it would cause some plugins to break.
def freeze
- @opts.freeze
- @middleware.freeze
+ return self if frozen?
unless opts[:subclassed]
# If the _roda_run_main_route instance method has not been overridden,
# make it an alias to _roda_main_route for performance
if instance_method(:_roda_run_main_route).owner == InstanceMethods
@@ -274,12 +282,20 @@
def set_default_headers
@headers['Content-Type'] ||= 'text/html'
end
end
end
+
+ if @middleware.empty? && use_new_dispatch_api?
+ plugin :direct_call
+ end
end
+ build_rack_app
+ @opts.freeze
+ @middleware.freeze
+
super
end
# Rebuild the _roda_before and _roda_after methods whenever a plugin might
# have added a _roda_before_* or _roda_after_* method.
@@ -338,11 +354,11 @@
self::RodaRequest.send(:include, plugin::RequestMethods) if defined?(plugin::RequestMethods)
self::RodaRequest.extend(plugin::RequestClassMethods) if defined?(plugin::RequestClassMethods)
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)
- nil
+ @app = nil
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
@@ -364,20 +380,20 @@
@raw_route_block = block
@route_block = block = convert_route_block(block)
@rack_app_route_block = block = rack_app_route_block(block)
public define_roda_method(:_roda_main_route, 1, &block)
- build_rack_app
+ @app = nil
end
# Add a middleware to use for the rack application. Must be
# called before calling #route to have an effect. Example:
#
# Roda.use Rack::ShowExceptions
def use(*args, &block)
@middleware << [args, block].freeze
- build_rack_app
+ @app = nil
end
private
# Return the number of required argument, optional arguments,
@@ -424,33 +440,19 @@
end
end
# Build the rack app to use
def build_rack_app
- if @rack_app_route_block
- # RODA4: Assume optimize is true
- optimize = ancestors.each do |mod|
- break true if mod == InstanceMethods
- meths = mod.instance_methods(false)
- if meths.include?(:call) && !(meths.include?(:_roda_handle_main_route) || meths.include?(:_roda_run_main_route))
- RodaPlugins.warn <<WARNING
-Falling back to using #call for dispatching for #{self}, due to #call override in #{mod}.
-#{mod} should be fixed to adjust to Roda's new dispatch API, and override _roda_handle_main_route or _roda_run_main_route
-WARNING
- break false
- end
- end
+ app = base_rack_app_callable(use_new_dispatch_api?)
- app = base_rack_app_callable(optimize)
-
- @middleware.reverse_each do |args, bl|
- mid, *args = args
- app = mid.new(app, *args, &bl)
- app.freeze if opts[:freeze_middleware]
- end
- @app = app
+ @middleware.reverse_each do |args, bl|
+ mid, *args = args
+ app = mid.new(app, *args, &bl)
+ app.freeze if opts[:freeze_middleware]
end
+
+ @app = app
end
# Modify the route block to use for any route block provided as input,
# which can include route blocks that are delegated to by the main route block.
# Can be modified by plugins.
@@ -497,10 +499,28 @@
# Can be modified by plugins.
def rack_app_route_block(block)
block
end
+ # Whether the new dispatch API should be used.
+ def use_new_dispatch_api?
+ # RODA4: remove this method
+ ancestors.each do |mod|
+ break if mod == InstanceMethods
+ meths = mod.instance_methods(false)
+ if meths.include?(:call) && !(meths.include?(:_roda_handle_main_route) || meths.include?(:_roda_run_main_route))
+ RodaPlugins.warn <<WARNING
+Falling back to using #call for dispatching for #{self}, due to #call override in #{mod}.
+#{mod} should be fixed to adjust to Roda's new dispatch API, and override _roda_handle_main_route or _roda_run_main_route
+WARNING
+ return false
+ end
+ end
+
+ true
+ end
+
method_num = 0
method_num_mutex = Mutex.new
# Return a unique method name symbol for the given suffix.
define_method(:roda_method_name) do |suffix|
:"_roda_#{suffix}_#{method_num_mutex.synchronize{method_num += 1}}"
@@ -1015,25 +1035,46 @@
# Match the given string to the request path. Matches only if the
# request path ends with the string or if the next character in the
# request path is a slash (indicating a new segment).
def _match_string(str)
rp = @remaining_path
- if rp.start_with?("/#{str}")
- last = str.length + 1
- case rp[last]
- when "/"
- @remaining_path = rp[last, rp.length]
+ length = str.length
+
+ match = case rp.rindex(str, length)
+ when nil
+ # segment does not match, most common case
+ return
+ when 1
+ # segment matches, check first character is /
+ rp.getbyte(0) == 47
+ else # must be 0
+ # segment matches at first character, only a match if
+ # empty string given and first character is /
+ length == 0 && rp.getbyte(0) == 47
+ end
+
+ if match
+ length += 1
+ case rp.getbyte(length)
+ when 47
+ # next character is /, update remaining path to rest of string
+ @remaining_path = rp[length, 100000000]
when nil
+ # end of string, so remaining path is empty
@remaining_path = ""
+ # else
+ # Any other value means this was partial segment match,
+ # so we return nil in that case without updating the
+ # remaining_path. No need for explicit else clause.
end
end
end
# Match the given symbol if any segment matches.
def _match_symbol(sym=nil)
rp = @remaining_path
- if rp[0, 1] == "/"
+ if rp.getbyte(0) == 47
if last = rp.index('/', 1)
if last > 1
@captures << rp[1, last-1]
@remaining_path = rp[last, rp.length]
end
@@ -1115,11 +1156,11 @@
302
end
# Whether the current path is considered empty.
def empty_path?
- remaining_path == ""
+ remaining_path.empty?
end
# 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.
@@ -1146,21 +1187,23 @@
_match_string(matcher)
when Class
_match_class(matcher)
when TERM
empty_path?
- when Symbol
- _match_symbol(matcher)
when Regexp
_match_regexp(matcher)
- when Hash
- _match_hash(matcher)
+ when true
+ matcher
when Array
_match_array(matcher)
+ when Hash
+ _match_hash(matcher)
+ when Symbol
+ _match_symbol(matcher)
+ when false, nil
+ matcher
when Proc
matcher.call
- when true, false, nil
- matcher
else
unsupported_matcher(matcher)
end
end