<commit_before>= Roda

Roda is a routing tree web framework.

= Installation

  $ gem install roda

== Resources

Website :: http://roda.jeremyevans.net
Source :: http://github.com/jeremyevans/roda
Bugs :: http://github.com/jeremyevans/roda/issues
Google Group :: http://groups.google.com/group/ruby-roda
IRC :: irc://chat.freenode.net/#roda

== Inspiration

Roda was inspired by {Sinatra}[http://www.sinatrarb.com] and {Cuba}[http://cuba.is],
two other Ruby web frameworks.  It started out as a fork of Cuba, from which it borrows
the idea of using a routing tree (which Cuba in turn took from
{Rum}[https://github.com/chneukirchen/rum]).  From Sinatra it takes the ideas that
route blocks should return the request bodies and that routes should be canonical.
It pilfers the idea for an extensible plugin system from the Ruby database library
{Sequel}[http://sequel.jeremyevans.net].

== Usage

Here's a simple application, showing how the routing tree works:

  # cat config.ru
  require "roda"

  class App < Roda
    use Rack::Session::Cookie, :secret => ENV['SECRET']

    route do |r|
      # GET / request
      r.root do
        r.redirect "/hello"
      end

      # /hello branch
      r.on "hello" do

        # GET /hello/world request
        r.get "world" do
          "Hello world!"
        end

        # /hello request
        r.is do
          # GET /hello request
          r.get do
            "Hello!"
          end

          # POST /hello request
          r.post do
            puts "Someone said hello!"
            r.redirect
          end
        end
      end
    end
  end

  run App.app

Here's a breakdown of what is going on in the above block:

After requiring the library and subclassing Roda, the +use+ method
is called, which loads a rack middleware into the current
application.

The +route+ block is called whenever a new request comes in, 
and it is yielded an instance of a subclass of <tt>Rack::Request</tt>
with some additional methods for matching routes.  By
convention, this argument should be named +r+.

The primary way routes are matched in Roda is by calling
+r.on+, +r.is+, +r.root+, +r.get+, or +r.post+.  These methods are
calling the routing methods, and each of them takes a block. The
block is referred to as a match block.

Each routing method takes each of the arguments (called matchers)
given and tries to match them to the current request.  If it is
able to match all of the arguments, it yields to the match block,
otherwise the block is skipped and execution continues.

+r.on+ matches if all of the arguments match.
+r.is+ matches if all of the arguments match, and there are no
further entries in the path after matching.
+r.get+ when called without arguments matches any +GET+ request.
+r.get+ when called with any arguments matches only if the
current request is a +GET+ request and there are no further entries
in the path after matching.
+r.root+ only matches a +GET+ request where the current path is +/+.

If a routing method matches and control is yielded to the match
block, whenever the match block returns, Roda will return the
rack response array of status, headers, and body, to the caller.

If the match block returns a string and the response body hasn't
already been written to, the block return value will interpreted
as the body for the response.  If none of the routing methods match
and the route block returns a string, it will be interpreted as the
body for the response.

+r.redirect+ immediately returns the response, allowing for
code such as <tt>r.redirect(path) if some_condition</tt>. If
called without arguments, it redirects to the current path if
the current request method is not +GET+.

The +.app+ at the end is an optimization, which you can leave
off, but which saves a few methods call for every response.

== The Routing Tree

Roda is called a routing tree web framework because the way most
sites are structured, routing takes the form of a tree based on the
URL structure of the site.  In general, +r.on+ is used to split the
tree into different branches, and +r.is+ is finalizes the routing,
where the request is actually handled.

So a simple routing tree may look something like this:

  r.on "a" do           # /a branch
    r.on "b" do         # /a/b branch
      r.is "c" do       # /a/b/c request
        r.get do end    # GET /a/b/c request
        r.post do end   # POST /a/b/c request
      end
      r.get "d" do end  # GET /a/b/d request
      r.post "e" do end # POST /a/b/e request
    end
  end

It's also possible to handling the same requests, but structure the
routing tree by first branching on the request method:

  r.get do              # GET 
    r.on "a" do         # GET /a branch
      r.on "b" do       # GET /a/b branch
        r.is "c" do end # GET /a/b/c request
        r.is "d" do end # GET /a/b/d request
      end
    end
  end

  r.post do             # POST
    r.on "a" do         # POST /a branch
      r.on "b" do       # POST /a/b branch
        r.is "c" do end # POST /a/b/c request
        r.is "e" do end # POST /a/b/e request
      end
    end
  end

This allows you to easily separate your GET request handling from
your POST request handling.  If you only have a small number of
POST request URLs and a large number of GET request URLs, this
may make things easier.

However, in general routing first by the path and last by the
request method is likely to lead to simpler and DRYer code. This
is because at any point during the routing, you can act on the
request.  For example, if all requests in the +/a+ branch need
need access permission +A+ and all requests in the +/a/b+ branch
need access permission +B+, you can easily handle this in the
routing tree:

  r.on "a" do           # /a branch
    check_perm(:A)  
    r.on "b" do         # /a/b branch
      check_perm(:B)  
      r.is "c" do       # /a/b/c request
        r.get do end    # GET /a/b/c request
        r.post do end   # POST /a/b/c request
      end
      r.get "d" do end  # GET /a/b/d request
      r.post "e" do end # POST /a/b/e request
    end
  end

Being able to operate on the request at any point during the
the routing is one of the major advantages of Roda compared
to other web frameworks that do not use a routing tree.

== Matchers

Other than +r.root+, the routing methods all take arguments called
matchers.  If all of the matchers match, the match block is yielded
to.  Here's an example showcasing how different matchers work.

  class App < Roda
    route do |r|
      # only GET requests
      r.get do

        # /
        r.root do
          "Home"
        end

        # /about
        r.is "about" do
          "About"
        end

        # /styles/basic.css
        r.is "styles", :extension => "css" do |file|
          "Filename: #{file}" #=> "Filename: basic"
        end

        # /post/2011/02/16/hello
        r.is "post/:y/:m/:d/:slug" do |y, m, d, slug|
          "#{y}-#{m}-#{d} #{slug}" #=> "2011-02-16 hello"
        end

        # /username/foobar
        r.on "username/:username" do |username|
          user = User.find_by_username(username) # username == "foobar"

          # /username/foobar/posts
          r.is "posts" do
            # You can access user here, because the blocks are closures.
            "Total Posts: #{user.posts.size}" #=> "Total Posts: 6"
          end

          # /username/foobar/following
          r.is "following" do
            user.following.size.to_s #=> "1301"
          end
        end

        # /search?q=barbaz
        r.is "search", :param=>"q" do |query|
          "Searched for #{query}" #=> "Searched for barbaz"
        end
      end

      # only POST requests
      r.post do
        r.is "login" do

          # POST /login, user: foo, pass: <PASSWORD>
          r.on({:param=>"user"}, {:param=>"pass"}) do |user, pass|
            "#{user}:#{pass}" #=> "foo:baz"
          end

          # If the params user and pass are not provided, this
          # will get executed.
          "You need to provide user and pass!"
        end
      end
    end
  end

Here's a description of the matchers.  Note that segment as used
here means one part of the path preceeded by a +/+.  So a path such
as +/foo/bar//baz+ has 4 segments, +/foo+, +/bar+, +/+ and +/baz+.
The +/+ here is considered the empty segment.

=== String

If it does not contain a colon or slash, it matches single segment
with the text of the string, preceeded by a slash.

  ""    # matches "/"
  "foo" # matches "/foo"
  "foo" # does not match "/food"

If it contains any slashes, it matches one additional segment for
each slash:

  "foo/bar" # matches "/foo/bar"
  "foo/bar" # does not match "/foo/bard"

If it contains a colon followed by any <tt>\\w</tt> characters, the colon and
remaing <tt>\\w</tt> characters matches any nonempty segment that contains at
least one character:

  "foo/:id" # matches "/foo/bar", "/foo/baz", etc.
  "foo/:id" # does not match "/fo/bar"

You can use multiple colons in a string:

  ":x/:y" # matches "/foo/bar", "/bar/foo" etc.
  ":x/:y" # does not match "/foo", "/bar/"

You can prefix colons:

  "foo:x/bar:y" # matches "/food/bard", "/fool/bart", etc.
  "foo:x/bar:y" # does not match "/foo/bart", "/fool/bar", etc.

If any colons are used, the block will yield one argument for
each segment matched containing the matched text.  So:

  "foo:x/:y" # matching "/fool/bar" yields "l", "bar"

Colons that are not followed by a <tt>\\w</tt> character are matched literally:

  ":/a" # matches "/:/a"

Note that strings are regexp escaped before being used in a regular
expression, so:

  "\\d+(/\\w+)?" # matches "/\d+(/\w+)?"
  "\\d+(/\\w+)?" # does not match "/123/abc"

=== Regexp

Regexps match one or more segments by looking for the pattern preceeded by a
slash:

  /foo\w+/ # matches "/foobar"
  /foo\w+/ # does not match "/foo/bar"

If any patterns are captured by the regexp, they are yielded:

  /foo\w+/   # matches "/foobar", yields nothing
  /foo(\w+)/ # matches "/foobar", yields "bar" 

=== Symbol

Symbols match any nonempty segment, yielding the segment except for the
preceeding slash:

  :id # matches "/foo" yields "foo"
  :id # does not match "/"

=== Proc

Procs match unless they return false or nil:

  proc{true}  # matches anything
  proc{false} # does not match anything

Procs don't capture anything by default, but they can if you add
the captured text to +r.captures+.

=== Arrays

Arrays match when any of their elements matches.  If multiple matchers
are given to +r.on+, they all must match (an AND condition), while
if an array of matchers is given, only one needs to match (an OR
condition).  Evaluation stops at the first matcher that matches.

Additionally, if the matched object is a String, the string is yielded.
This makes it easy to handle multiple strings without a Regexp:

  %w'page1 page2' # matches "/page1", "/page2"
  []              # does not match anything

=== Hash

Hashes allow easily calling specialized match methods on the request.
The default registered matchers included with Roda are documented below.
You can add your own hash matchers using the +hash_matcher+ class method,
which creates an appropriate request match method.  The +hash_matcher+
block will be called with the value of the hash.

  class App < Roda
    hash_matcher(:foo) do |v|
      # ...
    end
    
    route do |r|
      r.on :foo=>'bar' do
        # ...
      end
    end
  end

==== :all

The :all matcher matches if all of the entries in the given array matches. So

  r.on :all=>[:a, :b] do
    # ...
  end

is the same as:

  r.on :a, :b do
    # ...
  end

The reason it also exists as a separate hash matcher is so you can use it inside
an array matcher. so:

  r.on ['foo', {:all=>['foos', :id]}] do
  end

Would match +/foo+ and +/foos/10+, but not +/foos+.

==== :extension

The :extension matcher matches any nonempty path ending with the given extension:

  {:extension => "css"} # matches "/foo.css", "/bar.css"
  {:extension => "css"} # does not match "/foo.css/x", "/foo.bar", "/.css"

This matcher yields the part before the extension.

==== :method

This matches the method of the request.  You can provide an array to specify multiple
request methods and match on any of them:

  {:method => :post}          # matches POST
  {:method => %w'post patch'} # matches POST and PATCH

==== :param

The :param matcher matches if the given parameter is present, even if empty.

  {:param => "user"} # matches "/foo?user=bar", "/foo?user="
  {:param => "user"} # does not matches "/foo"

==== :param!

The :param! matcher matches if the given parameter is present and not empty.

  {:param! => "user"} # matches "/foo?user=bar"
  {:param! => "user"} # does not matches "/foo", "/foo?user="

=== false, nil

If false or nil is given directly as a matcher, it doesn't match anything.

=== Everything else

Everything else matches anything.

== Status codes

When it comes time to finalize a response, if a status code has not
been set manually, it will use a 200 status code if anything has been
written to the response, otherwise it will use a 404 status code.
This enables the principle of least surprise to work, where if you
don't handle an action, a 404 response is assumed.

You can always set the status code manually via the status attribute
for the response.

  route do |r|
    r.get "hello" do
      response.status = 200
    end
  end

== Verb Methods

The main match method is +r.on+, but as displayed above, you can also
use +r.get+ or +r.post+.  When called without any arguments, these
match as long as the request has the appropriate method, so:

  r.get do end

matches any +GET+ request, and

  r.post do end

matches any +POST+ request

If any arguments are given to the method, these match only
if the request method matches, all arguments match, and
only the path has been fully matched by the arguments.  So:

  r.post "" do end

matches only +POST+ requests where the current path is +/+.

  r.get "a/b" do end

matches only +GET+ requests where the current path is +/a/b+.

The reason for this difference in behavior is that if you are not
providing any arguments, you probably don't want to to also test
for an exact match with the current path.  If that is something
you do want, you can provide true as an argument:

  r.on "foo" do
    r.get true do # Matches GET /foo, not GET /foo/.*
    end
  end

If you want to match the request method and do a partial match
on the request path instead of a full match, you need to use
+r.on+ with the <tt>:method</tt> hash matcher:

  r.on "foo", :method=>:get do # Matches GET /foo(/.*)?
  end

== Root Method

As displayed above, you can also use +r.root+ as a match method.  This
method matches +GET+ requests where the current path +/+.  +r.root+ is
similar to <tt>r.get ""</tt>, except that it does not consume the +/+ from the path.

Unlike the other matching methods, +r.root+ takes no arguments.

Note that +r.root+ does not match if the path is empty, you should use
<tt>r.get true</tt> for that.  If you want to match either the
the empty path or +/+, you can use <tt>r.get ["", true]</tt>.

Note that +r.root+ does not match non-GET requests, so to handle
<tt>POST /</tt> requests, use <tt>r.post ''</tt>.

== Request and Response

While the request object is yielded to the route block, it is also
available via the +request+ method.  Likewise, the response object
is available via the +response+ method.

The request object is an instance of a subclass of <tt>Rack::Request</tt>
with some additional methods, and the response object is an
instance of a subclass of <tt>Rack::Response</tt> with some additional
methods.

If you want to extend the request and response objects with additional
modules, you can do so via the +request_module+ or +response_module+
methods, or via plugins.

== Pollution

Roda tries very hard to avoid polluting the scope in which the +route+
block operates.  The only instance variables defined by default in the scope of
the +route+ block are <tt>@_request</tt> and <tt>@_response</tt>.  The only methods defined
(beyond the default methods for +Object+) are: +env+, +opts+, +request+,
+response+, +call+, +session+, and +_route+ (private). Constants inside the
Roda namespace are all prefixed with +Roda+ (e.g. <tt>Roda::RodaRequest</tt>).  This
should make it unlikely that Roda will cause a namespace issue with your
application code.

== Captures

You may have noticed that some matchers yield a value to the block. The rules
for determining if a matcher will yield a value are simple:

1. Regexp captures: <tt>/posts\/(\d+)-(.*)/</tt> will yield two values, corresponding to each capture.
2. String placeholders: <tt>"users/:id"</tt> will yield the value in the position of +:id+.
3. Symbols: +:foobar+ will yield if a segment is available.
4. File extensions: <tt>:extension=>"css"</tt> will yield the basename of the matched file.
5. Parameters: <tt>:param=>"user"</tt> will yield the value of the parameter user, if present.

The first case is important because it shows the underlying effect of regex
captures.

In the second case, the substring +:id+ gets replaced by <tt>([^\\/]+)</tt> and the
regexp becomes <tt>/users\/([^\/]+)/</tt> before performing the match, thus it reverts
to the first form we saw.

In the third case, the symbol, no matter what it says, gets replaced
by <tt>/([^\\/]+)/</tt>, and again we are in presence of case 1.

The fourth case, again, reverts to the basic matcher: it generates the string
<tt>/([^\/]+?)\.#{ext}\z/</tt> before performing the match.

The fifth case is different: it checks if the the parameter supplied is present
in the request (via POST or QUERY_STRING) and it pushes the value as a capture.

== Composition

You can mount any Rack app (including another Roda app), with its own middlewares,
inside a Roda app, using +r.run+:

  class API < Roda
    use SomeMiddleware

    route do |r|
      r.is do
        # ...
      end
    end
  end

  class App < Roda
    route do |r|
      r.on "api" do
        r.run API
      end
    end
  end

  run App.app

This will take any path starting with +/api+ and send it to +API+.  In this
example, +API+ is a Roda app, but it could easily be a Sinatra, Rails, or
other Rack app.

When you use +r.run+, it is going to call the given Rack app (+API+ in this
case), and whatever the Rack app returns will be returned as the response
for the current application.

=== multi_route plugin

If you are just looking to split up the main route block up by branches, you
should use the +multi_route+ plugin, which keeps the current scope of
the route block:

  class App < Roda
    plugin :multi_route

    route "api" do |r|
      r.is do
        # ...
      end
    end

    route do |r|
      r.on "api" do
        r.route "api"
      end
    end
  end

  run App.app

This allows you so set instance variables in the main route block, and still
have access to them inside the +api+ route block.

== Testing

It is very easy to test Roda with {Rack::Test}[https://github.com/brynary/rack-test]
or {Capybara}[https://github.com/jnicklas/capybara]. Roda's own tests use
{RSpec}[http://rspec.info].  The default rake task will run the specs for Roda, if
RSpec is installed.

== Settings<commit_msg>Small tweaks to the README<commit_after>= Roda

Roda is a routing tree web framework.

= Installation

  $ gem install roda

== Resources

Website :: http://roda.jeremyevans.net
Source :: http://github.com/jeremyevans/roda
Bugs :: http://github.com/jeremyevans/roda/issues
Google Group :: http://groups.google.com/group/ruby-roda
IRC :: irc://chat.freenode.net/#roda

== Inspiration

Roda was inspired by {Sinatra}[http://www.sinatrarb.com] and {Cuba}[http://cuba.is],
two other Ruby web frameworks.  It started out as a fork of Cuba, from which it borrows
the idea of using a routing tree (which Cuba in turn took from
{Rum}[https://github.com/chneukirchen/rum]).  From Sinatra it takes the ideas that
route blocks should return the request bodies and that routes should be canonical.
It pilfers the idea for an extensible plugin system from the Ruby database library
{Sequel}[http://sequel.jeremyevans.net].

== Usage

Here's a simple application, showing how the routing tree works:

  # cat config.ru
  require "roda"

  class App < Roda
    use Rack::Session::Cookie, :secret => ENV['SECRET']

    route do |r|
      # GET / request
      r.root do
        r.redirect "/hello"
      end

      # /hello branch
      r.on "hello" do

        # GET /hello/world request
        r.get "world" do
          "Hello world!"
        end

        # /hello request
        r.is do
          # GET /hello request
          r.get do
            "Hello!"
          end

          # POST /hello request
          r.post do
            puts "Someone said hello!"
            r.redirect
          end
        end
      end
    end
  end

  run App.app

Here's a breakdown of what is going on in the above block:

After requiring the library and subclassing Roda, the +use+ method
is called, which loads a rack middleware into the current
application.

The +route+ block is called whenever a new request comes in, 
and it is yielded an instance of a subclass of <tt>Rack::Request</tt>
with some additional methods for matching routes.  By
convention, this argument should be named +r+.

The primary way routes are matched in Roda is by calling
+r.on+, +r.is+, +r.root+, +r.get+, or +r.post+.  These methods are
calling the routing methods, and each of them takes a block. The
block is referred to as a match block.

Each routing method takes each of the arguments (called matchers)
given and tries to match them to the current request.  If it is
able to match all of the arguments, it yields to the match block,
otherwise the block is skipped and execution continues.

+r.on+ matches if all of the arguments match.
+r.is+ matches if all of the arguments match, and there are no
further entries in the path after matching.
+r.get+ when called without arguments matches any +GET+ request.
+r.get+ when called with any arguments matches only if the
current request is a +GET+ request and there are no further entries
in the path after matching.
+r.root+ only matches a +GET+ request where the current path is +/+.

If a routing method matches and control is yielded to the match
block, whenever the match block returns, Roda will return the
rack response array of status, headers, and body, to the caller.

If the match block returns a string and the response body hasn't
already been written to, the block return value will interpreted
as the body for the response.  If none of the routing methods match
and the route block returns a string, it will be interpreted as the
body for the response.

+r.redirect+ immediately returns the response, allowing for
code such as <tt>r.redirect(path) if some_condition</tt>. If
called without arguments, it redirects to the current path if
the current request method is not +GET+.

The +.app+ at the end is an optimization, which you can leave
off, but which saves a few methods call for every response.

== The Routing Tree

Roda is called a routing tree web framework because the way most
sites are structured, routing takes the form of a tree based on the
URL structure of the site.  In general, +r.on+ is used to split the
tree into different branches, and +r.is+ is finalizes the routing,
where the request is actually handled.

So a simple routing tree may look something like this:

  r.on "a" do           # /a branch
    r.on "b" do         # /a/b branch
      r.is "c" do       # /a/b/c request
        r.get do end    # GET /a/b/c request
        r.post do end   # POST /a/b/c request
      end
      r.get "d" do end  # GET /a/b/d request
      r.post "e" do end # POST /a/b/e request
    end
  end

It's also possible to handle the same requests, but structure the
routing tree by first branching on the request method:

  r.get do              # GET 
    r.on "a" do         # GET /a branch
      r.on "b" do       # GET /a/b branch
        r.is "c" do end # GET /a/b/c request
        r.is "d" do end # GET /a/b/d request
      end
    end
  end

  r.post do             # POST
    r.on "a" do         # POST /a branch
      r.on "b" do       # POST /a/b branch
        r.is "c" do end # POST /a/b/c request
        r.is "e" do end # POST /a/b/e request
      end
    end
  end

This allows you to easily separate your +GET+ request handling from
your +POST+ request handling.  If you only have a small number of
+POST+ request URLs and a large number of +GET+ request URLs, this
may make things easier.

However, in general routing first by the path and last by the
request method is likely to lead to simpler and DRYer code. This
is because at any point during the routing, you can act on the
request.  For example, if all requests in the +/a+ branch need
need access permission +A+ and all requests in the +/a/b+ branch
need access permission +B+, you can easily handle this in the
routing tree:

  r.on "a" do           # /a branch
    check_perm(:A)  
    r.on "b" do         # /a/b branch
      check_perm(:B)  
      r.is "c" do       # /a/b/c request
        r.get do end    # GET /a/b/c request
        r.post do end   # POST /a/b/c request
      end
      r.get "d" do end  # GET /a/b/d request
      r.post "e" do end # POST /a/b/e request
    end
  end

Being able to operate on the request at any point during the
the routing is one of the major advantages of Roda compared
to other web frameworks that do not use a routing tree.

== Matchers

Other than +r.root+, the routing methods all take arguments called
matchers.  If all of the matchers match, the routing method yields to
the match block.  Here's an example showcasing how different
matchers work:

  class App < Roda
    route do |r|
      # only GET requests
      r.get do

        # /
        r.root do
          "Home"
        end

        # /about
        r.is "about" do
          "About"
        end

        # /styles/basic.css
        r.is "styles", :extension => "css" do |file|
          "Filename: #{file}" #=> "Filename: basic"
        end

        # /post/2011/02/16/hello
        r.is "post/:y/:m/:d/:slug" do |y, m, d, slug|
          "#{y}-#{m}-#{d} #{slug}" #=> "2011-02-16 hello"
        end

        # /username/foobar
        r.on "username/:username" do |username|
          user = User.find_by_username(username) # username == "foobar"

          # /username/foobar/posts
          r.is "posts" do
            # You can access user here, because the blocks are closures.
            "Total Posts: #{user.posts.size}" #=> "Total Posts: 6"
          end

          # /username/foobar/following
          r.is "following" do
            user.following.size.to_s #=> "1301"
          end
        end

        # /search?q=barbaz
        r.is "search", :param=>"q" do |query|
          "Searched for #{query}" #=> "Searched for barbaz"
        end
      end

      # only POST requests
      r.post do
        r.is "login" do

          # POST /login, user: foo, pass: <PASSWORD>
          r.on({:param=>"user"}, {:param=>"pass"}) do |user, pass|
            "#{user}:#{pass}" #=> "foo:baz"
          end

          # If the params user and pass are not provided, this
          # will get executed.
          "You need to provide user and pass!"
        end
      end
    end
  end

Here's a description of the matchers.  Note that segment as used
here means one part of the path preceeded by a +/+.  So a path such
as +/foo/bar//baz+ has 4 segments, +/foo+, +/bar+, +/+ and +/baz+.
The +/+ here is considered the empty segment.

=== String

If it does not contain a colon or slash, it matches single segment
with the text of the string, preceeded by a slash.

  ""    # matches "/"
  "foo" # matches "/foo"
  "foo" # does not match "/food"

If it contains any slashes, it matches one additional segment for
each slash:

  "foo/bar" # matches "/foo/bar"
  "foo/bar" # does not match "/foo/bard"

If it contains a colon followed by any <tt>\\w</tt> characters, the colon and
remaing <tt>\\w</tt> characters matches any nonempty segment that contains at
least one character:

  "foo/:id" # matches "/foo/bar", "/foo/baz", etc.
  "foo/:id" # does not match "/fo/bar"

You can use multiple colons in a string:

  ":x/:y" # matches "/foo/bar", "/bar/foo" etc.
  ":x/:y" # does not match "/foo", "/bar/"

You can prefix colons:

  "foo:x/bar:y" # matches "/food/bard", "/fool/bart", etc.
  "foo:x/bar:y" # does not match "/foo/bart", "/fool/bar", etc.

If any colons are used, the block will yield one argument for
each segment matched containing the matched text.  So:

  "foo:x/:y" # matching "/fool/bar" yields "l", "bar"

Colons that are not followed by a <tt>\\w</tt> character are matched literally:

  ":/a" # matches "/:/a"

Note that strings are regexp escaped before being used in a regular
expression, so:

  "\\d+(/\\w+)?" # matches "/\d+(/\w+)?"
  "\\d+(/\\w+)?" # does not match "/123/abc"

=== Regexp

Regexps match one or more segments by looking for the pattern preceeded by a
slash:

  /foo\w+/ # matches "/foobar"
  /foo\w+/ # does not match "/foo/bar"

If any patterns are captured by the regexp, they are yielded:

  /foo\w+/   # matches "/foobar", yields nothing
  /foo(\w+)/ # matches "/foobar", yields "bar" 

=== Symbol

Symbols match any nonempty segment, yielding the segment except for the
preceeding slash:

  :id # matches "/foo" yields "foo"
  :id # does not match "/"

=== Proc

Procs match unless they return false or nil:

  proc{true}  # matches anything
  proc{false} # does not match anything

Procs don't capture anything by default, but they can if you add
the captured text to +r.captures+.

=== Arrays

Arrays match when any of their elements matches.  If multiple matchers
are given to +r.on+, they all must match (an AND condition), while
if an array of matchers is given, only one needs to match (an OR
condition).  Evaluation stops at the first matcher that matches.

Additionally, if the matched object is a String, the string is yielded.
This makes it easy to handle multiple strings without a Regexp:

  ['page1', 'page2'] # matches "/page1", "/page2"
  []                 # does not match anything

=== Hash

Hashes allow easily calling specialized match methods on the request.
The default registered matchers included with Roda are documented below.
You can add your own hash matchers using the +hash_matcher+ class method,
which creates an appropriate request match method.  The +hash_matcher+
block will be called with the value of the hash.

  class App < Roda
    hash_matcher(:foo) do |v|
      # ...
    end
    
    route do |r|
      r.on :foo=>'bar' do
        # ...
      end
    end
  end

==== :all

The :all matcher matches if all of the entries in the given array matches. So

  r.on :all=>[:a, :b] do
    # ...
  end

is the same as:

  r.on :a, :b do
    # ...
  end

The reason it also exists as a separate hash matcher is so you can use it inside
an array matcher. so:

  r.on ['foo', {:all=>['foos', :id]}] do
  end

Would match +/foo+ and +/foos/10+, but not +/foos+.

==== :extension

The :extension matcher matches any nonempty path ending with the given extension:

  {:extension => "css"} # matches "/foo.css", "/bar.css"
  {:extension => "css"} # does not match "/foo.css/x", "/foo.bar", "/.css"

This matcher yields the part before the extension.

==== :method

This matches the method of the request.  You can provide an array to specify multiple
request methods and match on any of them:

  {:method => :post}             # matches POST
  {:method => ['post', 'patch']} # matches POST and PATCH

==== :param

The :param matcher matches if the given parameter is present, even if empty.

  {:param => "user"} # matches "/foo?user=bar", "/foo?user="
  {:param => "user"} # does not matches "/foo"

==== :param!

The :param! matcher matches if the given parameter is present and not empty.

  {:param! => "user"} # matches "/foo?user=bar"
  {:param! => "user"} # does not matches "/foo", "/foo?user="

=== false, nil

If false or nil is given directly as a matcher, it doesn't match anything.

=== Everything else

Everything else matches anything.

== Status codes

When it comes time to finalize a response, if a status code has not
been set manually, it will use a 200 status code if anything has been
written to the response, otherwise it will use a 404 status code.
This enables the principle of least surprise to work, where if you
don't handle an action, a 404 response is assumed.

You can always set the status code manually via the status attribute
for the response.

  route do |r|
    r.get "hello" do
      response.status = 200
    end
  end

== Verb Methods

The main match method is +r.on+, but as displayed above, you can also
use +r.get+ or +r.post+.  When called without any arguments, these
match as long as the request has the appropriate method, so:

  r.get do end

matches any +GET+ request, and

  r.post do end

matches any +POST+ request

If any arguments are given to the method, these match only
if the request method matches, all arguments match, and
only the path has been fully matched by the arguments.  So:

  r.post "" do end

matches only +POST+ requests where the current path is +/+.

  r.get "a/b" do end

matches only +GET+ requests where the current path is +/a/b+.

The reason for this difference in behavior is that if you are not
providing any arguments, you probably don't want to to also test
for an exact match with the current path.  If that is something
you do want, you can provide true as an argument:

  r.on "foo" do
    r.get true do # Matches GET /foo, not GET /foo/.*
    end
  end

If you want to match the request method and do a partial match
on the request path instead of a full match, you need to use
+r.on+ with the <tt>:method</tt> hash matcher:

  r.on "foo", :method=>:get do # Matches GET /foo(/.*)?
  end

== Root Method

As displayed above, you can also use +r.root+ as a match method.  This
method matches +GET+ requests where the current path +/+.  +r.root+ is
similar to <tt>r.get ""</tt>, except that it does not consume the +/+ from the path.

Unlike the other matching methods, +r.root+ takes no arguments.

Note that +r.root+ does not match if the path is empty, you should use
<tt>r.get true</tt> for that.  If you want to match either the
the empty path or +/+, you can use <tt>r.get ["", true]</tt>.

Note that +r.root+ does not match non-GET requests, so to handle
<tt>POST /</tt> requests, use <tt>r.post ''</tt>.

== Request and Response

While the request object is yielded to the route block, it is also
available via the +request+ method.  Likewise, the response object
is available via the +response+ method.

The request object is an instance of a subclass of <tt>Rack::Request</tt>
with some additional methods, and the response object is an
instance of a subclass of <tt>Rack::Response</tt> with some additional
methods.

If you want to extend the request and response objects with additional
modules, you can do so via the +request_module+ or +response_module+
methods, or via plugins.

== Pollution

Roda tries very hard to avoid polluting the scope of the +route+
block.  The only instance variables defined by default in the scope of
the +route+ block are <tt>@_request</tt> and <tt>@_response</tt>.  The only methods defined
(beyond the default methods for +Object+) are: +env+, +opts+, +request+,
+response+, +call+, +session+, and +_route+ (private). Constants inside the
Roda namespace are all prefixed with +Roda+ (e.g. <tt>Roda::RodaRequest</tt>).  This
should make it unlikely that Roda will cause a namespace issue with your
application code.

== Captures

You may have noticed that some matchers yield a value to the block. The rules
for determining if a matcher will yield a value are simple:

1. Regexp captures: <tt>/posts\/(\d+)-(.*)/</tt> will yield two values, corresponding to each capture.
2. String placeholders: <tt>"users/:id"</tt> will yield the value in the position of +:id+.
3. Symbols: +:foobar+ will yield if a segment is available.
4. File extensions: <tt>:extension=>"css"</tt> will yield the basename of the matched file.
5. Parameters: <tt>:param=>"user"</tt> will yield the value of the parameter user, if present.

The first case is important because it shows the underlying effect of regex
captures.

In the second case, the substring +:id+ gets replaced by <tt>([^\\/]+)</tt> and the
regexp becomes <tt>/users\/([^\/]+)/</tt> before performing the match, thus it reverts
to the first form we saw.

In the third case, the symbol, no matter what it says, gets replaced
by <tt>/([^\\/]+)/</tt>, and again we are in presence of case 1.

The fourth case, again, reverts to the basic matcher: it generates the string
<tt>/([^\/]+?)\.#{ext}\z/</tt> before performing the match.

The fifth case is different: it checks if the the parameter supplied is present
in the request (via POST or QUERY_STRING) and it pushes the value as a capture.

== Composition

You can mount any Rack app (including another Roda app), with its own middlewares,
inside a Roda app, using +r.run+:

  class API < Roda
    use SomeMiddleware

    route do |r|
      r.is do
        # ...
      end
    end
  end

  class App < Roda
    route do |r|
      r.on "api" do
        r.run API
      end
    end
  end

  run App.app

This will take any path starting with +/api+ and send it to +API+.  In this
example, +API+ is a Roda app, but it could easily be a Sinatra, Rails, or
other Rack app.

When you use +r.run+, Roda calls the given Rack app (+API+ in this
case), and whatever the Rack app returns will be returned as the response
for the current application.

=== multi_route plugin

If you are just looking to split up the main route block up by branches, you
should use the +multi_route+ plugin, which keeps the current scope of
the route block:

  class App < Roda
    plugin :multi_route

    route "api" do |r|
      r.is do
        # ...
      end
    end

    route do |r|
      r.on "api" do
        r.route "api"
      end
    end
  end

  run App.app

This allows you to set instance variables in the main route block, and still
have access to them inside the +api+ route block.

== Testing

It is very easy to test Roda with {Rack::Test}[https://github.com/brynary/rack-test]
or {Capybara}[https://github.com/jnicklas/capybara]. Roda's own tests use
{RSpec}[http://rspec.info].  The default rake task will run the specs for Roda, if
RSpec is installed.

== Settings
{'fraction_non_alphanumeric': 0.09893075884978719, 'fraction_numerical': 0.002543340600020762, 'mean_word_length': 2.8208230044620723, 'pattern_counts': {'":': 6, '<': 113, '<?xml version=': 0, '>': 151, 'https://': 6, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7074255', 'n_tokens_mistral': 11980, 'n_tokens_neox': 11492, 'n_words': 5562}