module Roda::RodaPlugins::Base::RequestMethods

def _match_array(matcher)

if no elements in the array match.
first match without evaluating future matches. Returns false
Match any of the elements in the given array. Return at the
def _match_array(matcher)
  matcher.any? do |m|
    if matched = match(m)
      if m.is_a?(String)
        @captures.push(m)
      end
    end
    matched
  end
end

def _match_hash(hash)

Match the given hash if all hash matchers match.
def _match_hash(hash)
  hash.all?{|k,v| send("match_#{k}", v)}
end

def _match_regexp(re)

Match the given regexp exactly if it matches a full segment.
def _match_regexp(re)
  consume(self.class.cached_matcher(re){re})
end

def _match_string(str)

colon tokens for placeholders.
string so that regexp metacharacters are not matched, and recognizes
Match the given string to the request path. Regexp escapes the
def _match_string(str)
  consume(self.class.cached_matcher(str){Regexp.escape(str).gsub(/:(\w+)/){|m| _match_symbol_regexp($1)}})
end

def _match_symbol(sym)

Match the given symbol if any segment matches.
def _match_symbol(sym)
  consume(self.class.cached_matcher(sym){_match_symbol_regexp(sym)})
end

def _match_symbol_regexp(s)

segment matches.
The regular expression to use for matching symbols. By default, any non-empty
def _match_symbol_regexp(s)
  SEGMENT
end

def _verb(args, &block)

args is not empty, or a regular match if it is empty.
Backbone of the verb method support, using a terminal match if
def _verb(args, &block)
  if args.empty?
    always(&block)
  else
    args << TERM
    if_match(args, &block)
  end
end

def always

Yield to the match block and return rack response after the block returns.
def always
  block_result(yield)
  throw :halt, response.finish
end

def block_result(result)

and the response is empty, use the string as the response body.
Handle match block return values. By default, if a string is given
def block_result(result)
  res = response
  if res.empty? && (body = block_result_body(result))
    res.write(body)
  end
end

def block_result_body(result)

returned otherwise.
a body. By default, a String is returned directly, and nil is
The body to use for the response if the response does not return
def block_result_body(result)
  if result.is_a?(String)
    result
  end
end

def consume(pattern)

path from PATH_INFO, and updates captures with any regex captures.
SCRIPT_NAME to include the matched path, removes the matched
match, returns false without changes. Otherwise, modifies
Attempts to match the pattern to the current path. If there is no
def consume(pattern)
  if matchdata = remaining_path.match(pattern)
    update_remaining_path(matchdata.post_match)
    @captures.concat(matchdata.captures)
  end
end

def default_redirect_path

it is easy to create an infinite redirect.
If the current request is a GET request, raise an error, as otherwise

will work fine.
a POST request will redirect to a GET request at the same location
trigger a GET request. This is to make the common case where
For non-GET requests, redirects to the current path, which will
The default path to use for redirects when a path is not given.
def default_redirect_path
  raise RodaError, "must provide path argument to redirect for get requests" if is_get?
  path
end

def default_redirect_status

302 by default.
The default status to use for redirects if a status is not provided,
def default_redirect_status
  302
end

def empty_path?

Whether the current path is considered empty.
def empty_path?
  remaining_path == EMPTY_STRING
end

def get(*args, &block)

given fully consume the path.
requests, otherwise, matches only GET requests where the arguments
Match GET requests. If no arguments are provided, matches all GET
def get(*args, &block)
  _verb(args, &block) if is_get?
end

def halt(res=response.finish)

r.halt
response.write 'Hello World!'
response['Content-Type'] = 'text/html'
response.status = 200

r.halt [200, {'Content-Type'=>'text/html'}, ['Hello World!']]

is given, uses the current response.
rack response array of status, headers, and body. If no argument
Immediately stop execution of the route block and return the given
def halt(res=response.finish)
  throw :halt, res
end

def if_match(args)

the match arguments doesn't match, does nothing.
returns the rack response when the block returns. If any of
If all of the arguments match, yields to the match block and
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
    return unless match_all(args)
    block_result(yield(*captures))
    throw :halt, response.finish
  end
end

def initialize(scope, env)

Store the roda instance and environment.
def initialize(scope, env)
  @scope = scope
  @captures = []
  super(env)
end

def inspect

# => '#'
r.inspect

request method and full path.
Show information about current request, including request class,
def inspect
  "#<#{self.class.inspect} #{@env[REQUEST_METHOD]} #{path}>"
end

def is(*args, &block)

end
end
# matches as path is empty after matching
r.is "" do
r.on 'foo/bar' do

end
# matches as path is empty after matching
r.is 'foo/bar/' do

end
# does not match, as path isn't fully matched (/ remaining)
r.is 'foo/bar' do

# => "/foo/bar/"
r.path_info

is empty, not if it still contains a trailing slash:
Note that this matches only if the path after matching the arguments

end
end
# matches as path is already empty
r.is do
r.on 'foo/bar' do

If no arguments are given, matches if the path is already fully matched.

end
# matches as path is empty after matching
r.is 'foo/bar' do

end
# does not match, as path isn't fully matched (/bar remaining)
r.is 'foo' do

# => "/foo/bar"
r.path_info

returned.
executed, and when the match block returns, the rack response is
have fully matched the path. If it matches, the match block is
Does a terminal match on the current path, matching only if the arguments
def is(*args, &block)
  if args.empty?
    if empty_path?
      always(&block)
    end
  else
    args << TERM
    if_match(args, &block)
  end
end

def is_get?

overridden without changing rack's behavior.
Similar to the default Rack::Request get? method, but can be
Optimized method for whether this request is a +GET+ request.
def is_get?
  @env[REQUEST_METHOD] == GET_REQUEST_METHOD
end

def keep_remaining_path

their initial values before returning from the block.
Yield to the block, restoring SCRIPT_NAME and PATH_INFO to
def keep_remaining_path
  env = @env
  script = env[sn = SCRIPT_NAME]
  path = env[pi = PATH_INFO]
  yield
ensure
  env[sn] = script
  env[pi] = path
end

def match(matcher)

common ruby types.
Attempt to match the argument to the given request, handling
def match(matcher)
  case matcher
  when String
    _match_string(matcher)
  when Regexp
    _match_regexp(matcher)
  when Symbol
    _match_symbol(matcher)
  when TERM
    empty_path?
  when Hash
    _match_hash(matcher)
  when Array
    _match_array(matcher)
  when Proc
    matcher.call
  else
    matcher
  end
end

def match_all(args)

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)

request path end with the extension.
Match files with the given extension. Requires that the
def match_extension(ext)
  consume(self.class.cached_matcher([:extension, ext]){/([^\\\/]+)\.#{ext}/})
end

def match_method(type)

to match on multiple methods.
Match by request method. This can be an array if you want
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)

Adds any match to the captures.
Match the given parameter if present, even if the parameter is empty.
def match_param(key)
  if v = self[key]
    @captures << v
  end
end

def match_param!(key)

Adds any match to the captures.
Match the given parameter if present and not empty.
def match_param!(key)
  if (v = self[key]) && !v.empty?
    @captures << v
  end
end

def matched_path

The already matched part of the path, including the original SCRIPT_NAME.
def matched_path
  @env[SCRIPT_NAME]
end

def on(*args, &block)

end
end
# handle /foo/bar request
r.is 'bar' do
r.on 'foo' do

final handling for the request:
inside the match block that fully matches the path and does the
returned. However, in general you will call another routing method
executed, and when the match block returns, the rack response is
Like other routing methods, If it matches, the match block is

end
# does not match
r.on 'bar' do

end
# matches, path is /bar after matching
r.on 'foo' do

# => "/foo/bar"
r.path_info

not for final handling of the request.
path, this is usually used to setup branches of the routing tree,
have matched the path. Because this doesn't fully match the
Does a match on the path, matching only if the arguments
def on(*args, &block)
  if args.empty?
    always(&block)
  else
    if_match(args, &block)
  end
end

def path

# => '/foo/bar'
r.path
r.env['PATH_INFO'] = '/bar'
r.env['SCRIPT_NAME'] = '/foo'

This an an optimized version of Rack::Request#path.
def path
  e = @env
  "#{e[SCRIPT_NAME]}#{e[PATH_INFO]}"
end

def post(*args, &block)

given fully consume the path.
requests, otherwise, matches only POST requests where the arguments
Match POST requests. If no arguments are provided, matches all POST
def post(*args, &block)
  _verb(args, &block) if post?
end

def redirect(path=default_redirect_path, status=default_redirect_status)

end
end
r.redirect
# change state
r.post do

end
# show state
r.get do
r.is "foo" do

after changing:
returns the current state, and you want to show the current state
it easy to use where a +POST+ request to a URL changes state, +GET+
path if the request is not a +GET+ request. This is designed to make
If you do not provide a path, by default it will redirect to the same

response.status = 404 # not reached
r.redirect '/page2' # uses 302 status code
r.redirect '/page1', 301 if r['param'] == 'value1'

the processing of the request:
Immediately redirect to the path using the status code. This ends
def redirect(path=default_redirect_path, status=default_redirect_status)
  response.redirect(path, status)
  throw :halt, response.finish
end

def remaining_path

in the environment, which gets updated as the request is being routed.
The current path to match requests against. This is the same as PATH_INFO
def remaining_path
  @env[PATH_INFO]
end

def response

response['Header-Name'] = 'Header value'
response.status = 200

is to override the response status and headers:
instance methods for the response, but in general the most common usage
The response related to the current request. See ResponseMethods for
def response
  scope.response
end

def roda_class

Return the Roda class related to this request.
def roda_class
  self.class.roda_class
end

def root(&block)

path is empty.
Use r.get true to handle +GET+ requests where the current

end
end
# does not match
r.root do
r.on 'foo' do

# => ['GET', '/foo']
[r.request_method, r.path_info]

Nor does it match empty paths:

is +/+.
Use r.post "" for +POST+ requests where the current path

end
# does not match
r.root do

# => ['POST', '/']
[r.request_method, r.path_info]

Note that this does not match non-+GET+ requests:

end
end
# matches
r.root do
r.on 'foo' do

# => ['GET', '/foo/']
[r.request_method, r.path_info]

This is usuable inside other match blocks:

end
# matches
r.root do

# => ['GET', '/']
[r.request_method, r.path_info]

the match block returns, the rack response is returned.
path is +/+. If it matches, the match block is executed, and when
Routing matches that only matches +GET+ requests where the current
def root(&block)
  if remaining_path == SLASH && is_get?
    always(&block)
  end
end

def run(app)

response.status = 404 # not reached
r.run(proc{[404, {}, []]})
r.run(proc{[403, {}, []]}) unless r['letmein'] == '1'

the processing of the request:
from the rack app as the response for this request. This ends
Call the given rack app with the environment and return the response
def run(app)
  throw :halt, app.call(@env)
end

def session

a session handler has not been loaded.
The session for the current request. Raises a RodaError if
def session
  @env[SESSION_KEY] || raise(RodaError, "You're missing a session handler. You can get started by adding use Rack::Session::Cookie")
end

def update_remaining_path(remaining)

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