lib/roda.rb in roda-cj-0.9.3 vs lib/roda.rb in roda-cj-0.9.4

- old
+ new

@@ -299,10 +299,21 @@ end pattern end + # Define a verb method in the given that will yield to the match block + # if the request method matches and there are either no arguments or + # there is a successful terminal match on the arguments. + def def_verb_method(mod, verb) + mod.class_eval(<<-END, __FILE__, __LINE__+1) + def #{verb}(*args, &block) + _verb(args, &block) if #{verb == :get ? :is_get : verb}? + end + END + end + # Since RodaRequest is anonymously subclassed when Roda is subclassed, # and then assigned to a constant of the Roda subclass, make inspect # reflect the likely name for the class. def inspect "#{roda_class.inspect}::RodaRequest" @@ -312,11 +323,11 @@ # The pattern to use for consuming, based on the given argument. The returned # pattern requires the path starts with a string and does not match partial # segments. def consume_pattern(pattern) - /\A(\/(?:#{pattern}))(\/|\z)/ + /\A(\/(?:#{pattern}))(?=\/|\z)/ end end # Instance methods for RodaRequest, mostly related to handling routing # for the request. @@ -325,12 +336,12 @@ SCRIPT_NAME = "SCRIPT_NAME".freeze REQUEST_METHOD = "REQUEST_METHOD".freeze EMPTY_STRING = "".freeze SLASH = "/".freeze SEGMENT = "([^\\/]+)".freeze - EMPTY_ARRAY = [].freeze TERM_INSPECT = "TERM".freeze + GET_REQUEST_METHOD = 'GET'.freeze TERM = Object.new def TERM.inspect TERM_INSPECT end @@ -352,28 +363,28 @@ end # As request routing modifies SCRIPT_NAME and PATH_INFO, this exists # as a helper method to get the full request of the path info. def full_path_info - "#{env[SCRIPT_NAME]}#{env[PATH_INFO]}" + "#{@env[SCRIPT_NAME]}#{@env[PATH_INFO]}" end - # If this is not a GET method, returns immediately. Otherwise, if there - # are arguments, do a terminal match on the arguments, otherwise do a - # regular match. - def get(*args, &block) - _verb(args, &block) if get? - end - # Immediately stop execution of the route block and return the given # rack response array of status, headers, and body. If no argument # is given, uses the current response. def halt(res=response.finish) throw :halt, res end - # Handle #on block return values. By default, if a string is given + # Whether this request is a get request. Similar to the default + # Rack::Request get? method, but can be overridden without changing + # rack's behavior. + def is_get? + @env[REQUEST_METHOD] == GET_REQUEST_METHOD + 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. def block_result(result) res = response if res.empty? && (body = block_result_body(result)) res.write(body) @@ -381,36 +392,39 @@ end # Show information about current request, including request class, # request method and full path. def inspect - "#<#{self.class.inspect} #{env[REQUEST_METHOD]} #{full_path_info}>" + "#<#{self.class.inspect} #{@env[REQUEST_METHOD]} #{full_path_info}>" end - # Adds TERM as the final argument and passes to #on, ensuring that - # there is only a match if #on has fully matched the path. + # Does a terminal match on the input, matching only if the arguments + # have fully matched the patch. def is(*args, &block) - args << TERM - _on(args, &block) + if args.empty? + if @env[PATH_INFO] == EMPTY_STRING + always(&block) + end + else + args << TERM + if_match(args, &block) + end end # Attempts to match on all of the arguments. If all of the # arguments match, control is yielded to the block, and after # the block returns, the rack response will be returned. # If any of the arguments fails, ensures the request state is # returned to that before matches were attempted. def on(*args, &block) - _on(args, &block) + if args.empty? + always(&block) + else + if_match(args, &block) + end end - # If this is not a GET method, returns immediately. Otherwise, if there - # are arguments, do a terminal match on the arguments, otherwise do a - # regular match. - def post(*args, &block) - _verb(args, &block) if post? - end - # The response related to the current request. def response scope.response end @@ -418,23 +432,21 @@ def redirect(path, status=302) response.redirect(path, status) throw :halt, response.finish end - # If the current path is the root ("/"), match on the block. If a request - # method is given, return immediately if the request does not use the given - # method. - def root(request_method=nil, &block) - if env[PATH_INFO] == SLASH && (!request_method || send(:"#{request_method}?")) - _on(EMPTY_ARRAY, &block) + # If this is a GET request for the root ("/"), yield to the match block. + def root(&block) + if @env[PATH_INFO] == SLASH && is_get? + always(&block) end end # Call the given rack app with the environment and immediately return # the response as the response for this request. def run(app) - throw :halt, app.call(env) + throw :halt, app.call(@env) end private # Match any of the elements in the given array. Return at the @@ -478,37 +490,27 @@ # segment matches. def _match_symbol_regexp(s) SEGMENT end - # Internal match method taking array of matchers instead of multiple - # arguments. - def _on(args) - script = env[SCRIPT_NAME] - path = env[PATH_INFO] - - # 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 - ensure - env[SCRIPT_NAME] = script - env[PATH_INFO] = path - end - # Backbone of the verb method support, using a terminal match if # args is not empty, or a regular match if it is empty. def _verb(args, &block) - unless args.empty? + if args.empty? + always(&block) + else args << TERM + if_match(args, &block) end - _on(args, &block) end + # Yield to the match block and return rack response after the block returns. + def always + block_result(yield) + throw :halt, response.finish + end + # The body to use for the response if the response does not return # a body. By default, a String is returned directly, and nil is # returned otherwise. def block_result_body(result) if result.is_a?(String) @@ -519,21 +521,42 @@ # Attempts to match the pattern to the current path. If there is no # 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) + env = @env return unless matchdata = env[PATH_INFO].match(pattern) vars = matchdata.captures # Don't mutate SCRIPT_NAME, breaks try env[SCRIPT_NAME] += vars.shift - env[PATH_INFO] = "#{vars.pop}#{matchdata.post_match}" + env[PATH_INFO] = matchdata.post_match captures.concat(vars) 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. + def if_match(args) + env = @env + script = env[SCRIPT_NAME] + path = env[PATH_INFO] + + # 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 + ensure + env[SCRIPT_NAME] = script + env[PATH_INFO] = path + end + # Attempt to match the argument to the given request, handling # common ruby types. def match(matcher) case matcher when String @@ -541,11 +564,11 @@ when Regexp _match_regexp(matcher) when Symbol _match_symbol(matcher) when TERM - env[PATH_INFO] == EMPTY_STRING + @env[PATH_INFO] == EMPTY_STRING when Hash _match_hash(matcher) when Array _match_array(matcher) when Proc @@ -561,20 +584,20 @@ end # Match files with the given extension. Requires that the # request path end with the extension. def match_extension(ext) - consume(self.class.cached_matcher(ext){"([^\\/]+?)\.#{ext}\\z"}) + consume(self.class.cached_matcher([:extension, ext]){"([^\\/]+?)\.#{ext}\\z"}) 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] + type.to_s.upcase == @env[REQUEST_METHOD] end end # Match the given parameter if present, even if the parameter is empty. # Adds any match to the captures. @@ -696,6 +719,8 @@ end end extend RodaPlugins::Base::ClassMethods plugin RodaPlugins::Base + RodaRequest.def_verb_method(RodaPlugins::Base::RequestMethods, :get) + RodaRequest.def_verb_method(RodaPlugins::Base::RequestMethods, :post) end