roda-cj 0.9.3 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5360a34504fec1081e28143f1f2ce25c69747061
4
- data.tar.gz: a593de99e0868ce9478922e9fc1a775fd28afaf7
3
+ metadata.gz: 0381614904fadaf43af33f351a4edfc17d054c4d
4
+ data.tar.gz: a1f3866849f764552a2de2f987c636d20cf33099
5
5
  SHA512:
6
- metadata.gz: 7bf78f056f2b0d1e034e7fab2be1a9dfaad92261c72e692da8c9bcdae895fefa10c348cb8a950445c769b7f7e944e110481e8d066d7a517043a175e647e952be
7
- data.tar.gz: d963a3e7c9d02a9077d7f77678d7292b427b8d55ee21b614b7ebd1a401dccd83e9e4d037dd58d319b0490748cf3c33935206437e54ea908365ed8058d6892081
6
+ metadata.gz: d2930467e112ec168fd41c4f9005e21984b028ff45aa8fb58d843e4343bb098f888774c94b989b70ce0aea4f6f69e8eae2b2a62fc5c6626816f78eea9257fd58
7
+ data.tar.gz: 1f506448329172d622492c22167f39b8690ca812c50f734c3dec306fd537855b0d4767a8507c555471963a4f053e4b65874d277cc9d53e44fabe2471c3e9374e
data/CHANGELOG CHANGED
@@ -1,5 +1,17 @@
1
1
  = HEAD
2
2
 
3
+ * Add head plugin, for handling HEAD requests like GET requests with an empty body (jeremyevans)
4
+
5
+ * Optimize consuming patterns by using a positive lookahead assertion (jeremyevans)
6
+
7
+ * Add not_allowed plugin, for automatically returning 405 Method Not Allowed responses (jeremyevans)
8
+
9
+ * Optimize match blocks with no arguments (jeremyevans)
10
+
11
+ * Add content_for plugin, for storing content in one template and retrieving it in another (jeremyevans)
12
+
13
+ * Add render_each plugin, for rendering a template for each value in an enumerable (jeremyevans)
14
+
3
15
  * Add backtracking_array plugin, allowing array matchers to backtrack if later matchers do not match (jeremyevans)
4
16
 
5
17
  * Add :all hash matcher, allowing array matchers to include conditions where you want to match multiple conditions (jeremyevans)
@@ -28,7 +40,7 @@
28
40
 
29
41
  * Optimize matching by caching consume regexp for strings, regexp, symbol, and :extension matchers (jeremyevans)
30
42
 
31
- * Add r.root for matching root (path "/"), for easier to read version of r.is "" (jeremyevans)
43
+ * Add r.root for GET / requests, for easier to read version of r.get "" (jeremyevans)
32
44
 
33
45
  * Optimize r.is terminal matcher, remove :term hash matcher (jeremyevans)
34
46
 
data/README.rdoc CHANGED
@@ -434,18 +434,17 @@ hash matcher:
434
434
  == Root Method
435
435
 
436
436
  As displayed above, you can also use +r.root+ as a match method. This
437
- method matches only if the path at that point is exactly +/+. +r.root+
438
- is similar to <tt>r.is ""</tt>, except that it does not
439
- consume the +/+ from the path.
440
-
441
- Unlike the other matching methods, +r.root+ does not take multiple
442
- arguments and pass them to +r.on+. It only accepts an optional request
443
- method symbol, so <tt>r.root :get</tt> is similar to <tt>r.get ""</tt>,
437
+ method matches <tt>GET /</tt> requests. +r.root+ is similar to <tt>r.get ""</tt>,
444
438
  except that it does not consume the +/+ from the path.
445
439
 
440
+ Unlike the other matching methods, +r.root+ takes no arguments.
441
+
446
442
  Note that +r.root+ does not match if the path is empty, you should use
447
- +r.is+ with no arguments for that. If you want to match either the
448
- the empty path or +/+, you can use <tt>r.is ["", true]</tt>.
443
+ <tt>r.get true</tt> for that. If you want to match either the
444
+ the empty path or +/+, you can use <tt>r.get ["", true]</tt>.
445
+
446
+ Note that +r.root+ does not match non-GET requests, so to handle
447
+ <tt>POST /</tt> requests, use <tt>r.post ''</tt>.
449
448
 
450
449
  == Request and Response
451
450
 
@@ -638,6 +637,8 @@ These plugins ship with roda:
638
637
  all_verbs :: Adds routing methods to the request for all http verbs.
639
638
  backtracking_array :: Allows array matchers to backtrack if later matchers
640
639
  do not match.
640
+ content_for :: Allows storage of content in one template and retrieval of
641
+ that content in a different template.
641
642
  csrf :: Adds CSRF protection and helper methods using
642
643
  {rack_csrf}[https://github.com/baldowl/rack_csrf].
643
644
  default_headers :: Override the default response headers used.
@@ -647,6 +648,7 @@ flash :: Adds a flash handler.
647
648
  h :: Adds h method for html escaping.
648
649
  halt :: Augments request#halt method to take status and/or body or status,
649
650
  headers, and body.
651
+ head :: Treat HEAD requests like GET requests with an empty response body.
650
652
  header_matchers :: Adds host, header, and accept hash matchers.
651
653
  hooks :: Adds before and after methods to run code before and after requests.
652
654
  indifferent_params :: Adds params method with indifferent access to params,
@@ -657,6 +659,8 @@ middleware :: Allows the Roda app to be used as a rack middleware, calling the
657
659
  next middleware if no route matches.
658
660
  multi_route :: Adds the ability for multiple named route blocks, with the
659
661
  ability to dispatch to them add any point in the main route block.
662
+ not_allowed :: Adds support for automatically returning 405 Method Not Allowed
663
+ responses.
660
664
  not_found :: Adds a +not_found+ block that is called for all 404 responses
661
665
  without bodies.
662
666
  pass :: Adds a pass method allowing you to skip the current +r.on+ block as if
@@ -664,6 +668,7 @@ pass :: Adds a pass method allowing you to skip the current +r.on+ block as if
664
668
  per_thread_caching :: Switches the thread-safe cache from a shared cache to a
665
669
  per-thread cache.
666
670
  render :: Adds support for rendering templates via tilt, as described above.
671
+ render_each :: Render a template for each value in an enumerable.
667
672
  streaming :: Adds support for streaming responses.
668
673
  symbol_matchers :: Adds support for symbol-specific matching regexps.
669
674
  symbol_views :: Allows match blocks to return template name symbols, uses the
@@ -30,14 +30,12 @@ class Roda
30
30
  # The verb methods are defined via metaprogramming, so there
31
31
  # isn't documentation for the individual methods created.
32
32
  module AllVerbs
33
- module RequestMethods
34
- %w'delete head options link patch put trace unlink'.each do |t|
35
- if ::Rack::Request.method_defined?("#{t}?")
36
- class_eval(<<-END, __FILE__, __LINE__+1)
37
- def #{t}(*args, &block)
38
- _verb(args, &block) if #{t}?
39
- end
40
- END
33
+ def self.configure(app)
34
+ %w'delete head options link patch put trace unlink'.each do |v|
35
+ if ::Rack::Request.method_defined?("#{v}?")
36
+ app.request_module do
37
+ app::RodaRequest.def_verb_method(self, v)
38
+ end
41
39
  end
42
40
  end
43
41
  end
@@ -35,6 +35,7 @@ class Roda
35
35
  # entry in the array.
36
36
  def _match_array(arg, rest=nil)
37
37
  return super unless rest
38
+ env = @env
38
39
 
39
40
  script = env[SCRIPT_NAME]
40
41
  path = env[PATH_INFO]
@@ -0,0 +1,46 @@
1
+ class Roda
2
+ module RodaPlugins
3
+ # The content_for plugin is designed to be used with the
4
+ # render plugin, allowing you to store content inside one
5
+ # template, and retrieve that content inside a separate
6
+ # template. Most commonly, this is so view templates
7
+ # can set content for the layout template to display outside
8
+ # of the normal content pane.
9
+ #
10
+ # The content_for template probably only works with erb
11
+ # templates, and requires that you don't override the
12
+ # +:outvar+ render option. In the template in which you
13
+ # want to store content, call content_for with a block:
14
+ #
15
+ # <% content_for :foo do %>
16
+ # Some content here.
17
+ # <% end %>
18
+ #
19
+ # In the template in which you want to retrieve content,
20
+ # call content_for without the block:
21
+ #
22
+ # <%= content_for :foo %>
23
+ module ContentFor
24
+ module InstanceMethods
25
+ # If called with a block, store content enclosed by block
26
+ # under the given key. If called without a block, retrieve
27
+ # stored content with the given key, or return nil if there
28
+ # is no content stored with that key.
29
+ def content_for(key, &block)
30
+ if block
31
+ @_content_for ||= {}
32
+ buf_was = @_out_buf
33
+ @_out_buf = ''
34
+ yield
35
+ @_content_for[key] = @_out_buf
36
+ @_out_buf = buf_was
37
+ elsif @_content_for
38
+ @_content_for[key]
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ register_plugin(:content_for, ContentFor)
45
+ end
46
+ end
@@ -1,18 +1,12 @@
1
1
  class Roda
2
2
  module RodaPlugins
3
- # The halt plugin augments the standard request +halt+ method to handle more than
4
- # just rack response arrays.
3
+ # The halt plugin augments the standard request +halt+ method to allow the response
4
+ # status, body, or headers to be changed when halting.
5
5
  #
6
6
  # After loading the halt plugin:
7
7
  #
8
8
  # plugin :halt
9
9
  #
10
- # You can call halt with no arguments to immediately stop processing:
11
- #
12
- # route do |r|
13
- # r.halt
14
- # end
15
- #
16
10
  # You can call the halt method with an integer to set the response status and return:
17
11
  #
18
12
  # route do |r|
@@ -38,7 +32,7 @@ class Roda
38
32
  # end
39
33
  #
40
34
  # Note that there is a difference between provide status, headers, and body as separate
41
- # arguments and providing them as a rack response array. With a rack response array,
35
+ # arguments and providing them as a single rack response array. With a rack response array,
42
36
  # the values are used directly, while with 3 arguments, the headers given are merged into
43
37
  # the existing headers and the given body is written to the existing response body.
44
38
  module Halt
@@ -0,0 +1,56 @@
1
+ class Roda
2
+ module RodaPlugins
3
+ # The head plugin attempts to automatically handle HEAD requests,
4
+ # by treating them as GET requests and returning an empty body
5
+ # without modifying the response status or response headers.
6
+ #
7
+ # So for the following routes,
8
+ #
9
+ # route do |r|
10
+ # r.root do
11
+ # 'root'
12
+ # end
13
+ #
14
+ # r.get 'a' do
15
+ # 'a'
16
+ # end
17
+ #
18
+ # r.is 'b', :method=>[:get, :post] do
19
+ # 'b'
20
+ # end
21
+ # end
22
+ #
23
+ # HEAD requests for +/+, +/a+, and +/b+ will all return 200 status
24
+ # with an empty body.
25
+ module Head
26
+ module InstanceMethods
27
+ # Always use an empty response body for head requests, with a
28
+ # content length of 0.
29
+ def call(*)
30
+ res = super
31
+ if request.head?
32
+ res[2] = []
33
+ end
34
+ res
35
+ end
36
+ end
37
+
38
+ module RequestMethods
39
+ # Consider HEAD requests as GET requests.
40
+ def is_get?
41
+ super || head?
42
+ end
43
+
44
+ private
45
+
46
+ # If the current request is a HEAD request, match if one of
47
+ # the given methods is a GET request.
48
+ def match_method(method)
49
+ super || (!method.is_a?(Array) && head? && method.to_s.upcase == 'GET')
50
+ end
51
+ end
52
+ end
53
+
54
+ register_plugin(:head, Head)
55
+ end
56
+ end
@@ -35,14 +35,14 @@ class Roda
35
35
 
36
36
  # Match if the given mimetype is one of the accepted mimetypes.
37
37
  def match_accept(mimetype)
38
- if env["HTTP_ACCEPT"].to_s.split(',').any?{|s| s.strip == mimetype}
38
+ if @env["HTTP_ACCEPT"].to_s.split(',').any?{|s| s.strip == mimetype}
39
39
  response["Content-Type"] = mimetype
40
40
  end
41
41
  end
42
42
 
43
43
  # Match if the given uppercase key is present inside the environment.
44
44
  def match_header(key)
45
- env[key.upcase.tr("-","_")]
45
+ @env[key.upcase.tr("-","_")]
46
46
  end
47
47
 
48
48
  # Match if the host of the request is the same as the hostname.
@@ -2,7 +2,7 @@ require 'json'
2
2
 
3
3
  class Roda
4
4
  module RodaPlugins
5
- # The json plugin allows matching blocks to return
5
+ # The json plugin allows match blocks to return
6
6
  # arrays or hashes, and have those arrays or hashes be
7
7
  # converted to json which is used as the response body.
8
8
  # It also sets the response content type to application/json.
@@ -0,0 +1,133 @@
1
+ class Roda
2
+ module RodaPlugins
3
+ # The not_allowed plugin makes Roda attempt to automatically
4
+ # support the 405 Method Not Allowed response status. The plugin
5
+ # changes the +r.get+ and +r.post+ verb methods to automatically
6
+ # return a 405 status if they are called with any arguments, and
7
+ # the arguments match but the request method does not match. So
8
+ # this code:
9
+ #
10
+ # r.get '' do
11
+ # "a"
12
+ # end
13
+ #
14
+ # will return a 200 response for <tt>GET /</tt> and a 405
15
+ # response for <tt>POST /</tt>.
16
+ #
17
+ # This plugin also changes the +r.is+ method so that if you use
18
+ # a verb method inside +r.is+, it returns a 405 status if none
19
+ # of the verb methods match. So this code:
20
+ #
21
+ # r.is '' do
22
+ # r.get do
23
+ # "a"
24
+ # end
25
+ #
26
+ # r.post do
27
+ # "b"
28
+ # end
29
+ # end
30
+ #
31
+ # will return a 200 response for <tt>GET /</tt> and <tt>POST /</tt>,
32
+ # but a 405 response for <tt>PUT /</tt>.
33
+ #
34
+ # Note that this plugin will probably not do what you want for
35
+ # code such as:
36
+ #
37
+ # r.get '' do
38
+ # "a"
39
+ # end
40
+ #
41
+ # r.post '' do
42
+ # "b"
43
+ # end
44
+ #
45
+ # Since for a <tt>POST /</tt> request, when +r.get+ method matches
46
+ # the path but not the request method, it will return an immediate
47
+ # 405 response. You must DRY up this code for it work correctly,
48
+ # like this:
49
+ #
50
+ # r.is '' do
51
+ # r.get do
52
+ # "a"
53
+ # end
54
+ #
55
+ # r.post do
56
+ # "b"
57
+ # end
58
+ # end
59
+ #
60
+ # In all cases where it uses a 405 response, it also sets the +Allow+
61
+ # header in the response to contain the request methods supported.
62
+ #
63
+ # To make this affect the verb methods added by the all_verbs plugin,
64
+ # load this plugin first.
65
+ module NotAllowed
66
+ # Redefine the +r.get+ and +r.post+ methods when loading the plugin.
67
+ def self.configure(app)
68
+ app.request_module do
69
+ app::RodaRequest.def_verb_method(self, :get)
70
+ app::RodaRequest.def_verb_method(self, :post)
71
+ end
72
+ end
73
+
74
+ module RequestClassMethods
75
+ # Define a method named +verb+ in the given module which will
76
+ # return a 405 response if the method is called with any
77
+ # arguments and the arguments terminally match but the
78
+ # request method does not.
79
+ #
80
+ # If called without any arguments, check to see if the call
81
+ # is inside a terminal match, and in that case record the
82
+ # request method used.
83
+ def def_verb_method(mod, verb)
84
+ mod.class_eval(<<-END, __FILE__, __LINE__+1)
85
+ def #{verb}(*args, &block)
86
+ if args.empty?
87
+ @_is_verbs << "#{verb.to_s.upcase}" if @_is_verbs
88
+ always(&block) if #{verb == :get ? :is_get : verb}?
89
+ else
90
+ args << ::Roda::RodaPlugins::Base::RequestMethods::TERM
91
+ if_match(args) do
92
+ #{verb}(&block)
93
+ response.status = 405
94
+ response['Allow'] = '#{verb.to_s.upcase}'
95
+ nil
96
+ end
97
+ end
98
+ end
99
+ END
100
+ end
101
+ end
102
+
103
+ module RequestMethods
104
+ # Keep track of verb calls inside the block. If there are any
105
+ # verb calls inside the block, but the block returned, assume
106
+ # that the verb calls inside the block did not match, and
107
+ # since there was already a successful terminal match, the
108
+ # request method must not be allowed, so return a 405
109
+ # response in that case.
110
+ def is(*verbs)
111
+ super(*verbs) do
112
+ begin
113
+ @_is_verbs = []
114
+
115
+ ret = yield
116
+
117
+ unless @_is_verbs.empty?
118
+ response.status = 405
119
+ response['Allow'] = @_is_verbs.join(', ')
120
+ end
121
+
122
+ ret
123
+ ensure
124
+ @_is_verbs = nil
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ register_plugin(:not_allowed, NotAllowed)
132
+ end
133
+ end
@@ -1,6 +1,6 @@
1
1
  class Roda
2
2
  module RodaPlugins
3
- # The pass plugin adds a request +pass+ method to skip the current +on+
3
+ # The pass plugin adds a request +pass+ method to skip the current match
4
4
  # block as if it did not match.
5
5
  #
6
6
  # plugin :pass
@@ -17,14 +17,21 @@ class Roda
17
17
  # end
18
18
  module Pass
19
19
  module RequestMethods
20
- # Handle passing inside the current block.
21
- def _on(_)
20
+ # Skip the current match block as if it did not match.
21
+ def pass
22
+ throw :pass
23
+ end
24
+
25
+ private
26
+
27
+ # Handle passing inside the match block.
28
+ def always
22
29
  catch(:pass){super}
23
30
  end
24
31
 
25
- # Skip the current #on block as if it did not match.
26
- def pass
27
- throw :pass
32
+ # Handle passing inside the match block.
33
+ def if_match(_)
34
+ catch(:pass){super}
28
35
  end
29
36
  end
30
37
  end
@@ -0,0 +1,61 @@
1
+ class Roda
2
+ module RodaPlugins
3
+ # The render_each plugin allows you to render a template for each
4
+ # value in an enumerable, returning the concatention of all of the
5
+ # template renderings. For example:
6
+ #
7
+ # render_each([1,2,3], :foo)
8
+ #
9
+ # will render the +foo+ template 3 times. Each time the template
10
+ # is rendered, the local variable +foo+ will contain the given
11
+ # value (e.g. on the first rendering +foo+ is 1).
12
+ #
13
+ # You can pass additional render options via an options hash:
14
+ #
15
+ # render_each([1,2,3], :foo, :views=>'partials')
16
+ #
17
+ # One additional option supported by is +:local+, which sets the
18
+ # local variable containing the current value to use. So:
19
+ #
20
+ # render_each([1,2,3], :foo, :local=>:bar)
21
+ #
22
+ # Will render the +foo+ template, but the local variable used inside
23
+ # the template will be +bar+. You can use <tt>:local=>nil</tt> to
24
+ # not set a local variable inside the template.
25
+ module RenderEach
26
+ module InstanceMethods
27
+ EMPTY_STRING = ''.freeze
28
+
29
+ # For each value in enum, render the given template using the
30
+ # given opts. The template and options hash are passed to +render+.
31
+ # Additional options supported:
32
+ # :local :: The local variable to use for the current enum value
33
+ # inside the template. An explicit +nil+ value does not
34
+ # set a local variable. If not set, uses the template name.
35
+ def render_each(enum, template, opts={})
36
+ if as = opts.has_key?(:local)
37
+ as = opts[:local]
38
+ else
39
+ as = template.to_s.to_sym
40
+ end
41
+
42
+ if as
43
+ opts = opts.dup
44
+ if locals = opts[:locals]
45
+ locals = opts[:locals] = locals.dup
46
+ else
47
+ locals = opts[:locals] = {}
48
+ end
49
+ end
50
+
51
+ enum.map do |v|
52
+ locals[as] = v if as
53
+ render(template, opts)
54
+ end.join
55
+ end
56
+ end
57
+ end
58
+
59
+ register_plugin(:render_each, RenderEach)
60
+ end
61
+ end
@@ -7,7 +7,7 @@ class Roda
7
7
  # # ...
8
8
  # end
9
9
  #
10
- # By default this will match all segments. However, if your usernames
10
+ # By default this will match all nonempty segments. However, if your usernames
11
11
  # must be 6-20 characters, and can only contain +a-z+ and +0-9+, you can do:
12
12
  #
13
13
  # plugin :symbol_matchers
@@ -28,11 +28,12 @@ class Roda
28
28
  #
29
29
  # By default, this plugin sets up the following symbol matchers:
30
30
  #
31
- # :d :: <tt>/\d+/</tt>, a decimal segment
32
- # :format :: <tt>/(?:\.(\w+))?/</tt>, an optional format
31
+ # :d :: <tt>/(\d+)/</tt>, a decimal segment
32
+ # :format :: <tt>/(?:\.(\w+))?/</tt>, an optional format/extension
33
33
  # :opt :: <tt>/(?:\/([^\/]+))?</tt>, an optional segment
34
34
  # :optd :: <tt>/(?:\/(\d+))?</tt>, an optional decimal segment
35
- # :w :: <tt>/\w+/</tt>, a alphanumeric segment
35
+ # :rest :: <tt>/(.*)/</tt>, all remaining characters, if any
36
+ # :w :: <tt>/(\w+)/</tt>, a alphanumeric segment
36
37
  #
37
38
  # Note that because of how segment matching works, :format, :opt, and :optd
38
39
  # are only going to work inside of a string, like this:
@@ -46,6 +47,7 @@ class Roda
46
47
  app.symbol_matcher(:format, /(?:\.(\w+))?/)
47
48
  app.symbol_matcher(:opt, /(?:\/([^\/]+))?/)
48
49
  app.symbol_matcher(:optd, /(?:\/(\d+))?/)
50
+ app.symbol_matcher(:rest, /(.*)/)
49
51
  app.symbol_matcher(:w, /(\w+)/)
50
52
  end
51
53
 
@@ -57,6 +59,8 @@ class Roda
57
59
  end
58
60
 
59
61
  module RequestMethods
62
+ private
63
+
60
64
  # Allow for symbol specific regexps, by using match_symbol_#{s} if
61
65
  # defined. If not defined, calls super for the default behavior.
62
66
  def _match_symbol_regexp(s)
@@ -1,6 +1,6 @@
1
1
  class Roda
2
2
  module RodaPlugins
3
- # The symbol_views plugin allows matching blocks to return
3
+ # The symbol_views plugin allows match blocks to return
4
4
  # symbols, and consider those symbols as views to use for the
5
5
  # response body. So you can take code like:
6
6
  #
data/lib/roda/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Roda
2
- RodaVersion = '0.9.3'.freeze
2
+ RodaVersion = '0.9.4'.freeze
3
3
  end
data/lib/roda.rb CHANGED
@@ -301,6 +301,17 @@ class Roda
301
301
  pattern
302
302
  end
303
303
 
304
+ # Define a verb method in the given that will yield to the match block
305
+ # if the request method matches and there are either no arguments or
306
+ # there is a successful terminal match on the arguments.
307
+ def def_verb_method(mod, verb)
308
+ mod.class_eval(<<-END, __FILE__, __LINE__+1)
309
+ def #{verb}(*args, &block)
310
+ _verb(args, &block) if #{verb == :get ? :is_get : verb}?
311
+ end
312
+ END
313
+ end
314
+
304
315
  # Since RodaRequest is anonymously subclassed when Roda is subclassed,
305
316
  # and then assigned to a constant of the Roda subclass, make inspect
306
317
  # reflect the likely name for the class.
@@ -314,7 +325,7 @@ class Roda
314
325
  # pattern requires the path starts with a string and does not match partial
315
326
  # segments.
316
327
  def consume_pattern(pattern)
317
- /\A(\/(?:#{pattern}))(\/|\z)/
328
+ /\A(\/(?:#{pattern}))(?=\/|\z)/
318
329
  end
319
330
  end
320
331
 
@@ -327,8 +338,8 @@ class Roda
327
338
  EMPTY_STRING = "".freeze
328
339
  SLASH = "/".freeze
329
340
  SEGMENT = "([^\\/]+)".freeze
330
- EMPTY_ARRAY = [].freeze
331
341
  TERM_INSPECT = "TERM".freeze
342
+ GET_REQUEST_METHOD = 'GET'.freeze
332
343
 
333
344
  TERM = Object.new
334
345
  def TERM.inspect
@@ -354,14 +365,7 @@ class Roda
354
365
  # As request routing modifies SCRIPT_NAME and PATH_INFO, this exists
355
366
  # as a helper method to get the full request of the path info.
356
367
  def full_path_info
357
- "#{env[SCRIPT_NAME]}#{env[PATH_INFO]}"
358
- end
359
-
360
- # If this is not a GET method, returns immediately. Otherwise, if there
361
- # are arguments, do a terminal match on the arguments, otherwise do a
362
- # regular match.
363
- def get(*args, &block)
364
- _verb(args, &block) if get?
368
+ "#{@env[SCRIPT_NAME]}#{@env[PATH_INFO]}"
365
369
  end
366
370
 
367
371
  # Immediately stop execution of the route block and return the given
@@ -371,7 +375,14 @@ class Roda
371
375
  throw :halt, res
372
376
  end
373
377
 
374
- # Handle #on block return values. By default, if a string is given
378
+ # Whether this request is a get request. Similar to the default
379
+ # Rack::Request get? method, but can be overridden without changing
380
+ # rack's behavior.
381
+ def is_get?
382
+ @env[REQUEST_METHOD] == GET_REQUEST_METHOD
383
+ end
384
+
385
+ # Handle match block return values. By default, if a string is given
375
386
  # and the response is empty, use the string as the response body.
376
387
  def block_result(result)
377
388
  res = response
@@ -383,14 +394,20 @@ class Roda
383
394
  # Show information about current request, including request class,
384
395
  # request method and full path.
385
396
  def inspect
386
- "#<#{self.class.inspect} #{env[REQUEST_METHOD]} #{full_path_info}>"
397
+ "#<#{self.class.inspect} #{@env[REQUEST_METHOD]} #{full_path_info}>"
387
398
  end
388
399
 
389
- # Adds TERM as the final argument and passes to #on, ensuring that
390
- # there is only a match if #on has fully matched the path.
400
+ # Does a terminal match on the input, matching only if the arguments
401
+ # have fully matched the patch.
391
402
  def is(*args, &block)
392
- args << TERM
393
- _on(args, &block)
403
+ if args.empty?
404
+ if @env[PATH_INFO] == EMPTY_STRING
405
+ always(&block)
406
+ end
407
+ else
408
+ args << TERM
409
+ if_match(args, &block)
410
+ end
394
411
  end
395
412
 
396
413
  # Attempts to match on all of the arguments. If all of the
@@ -399,14 +416,11 @@ class Roda
399
416
  # If any of the arguments fails, ensures the request state is
400
417
  # returned to that before matches were attempted.
401
418
  def on(*args, &block)
402
- _on(args, &block)
403
- end
404
-
405
- # If this is not a GET method, returns immediately. Otherwise, if there
406
- # are arguments, do a terminal match on the arguments, otherwise do a
407
- # regular match.
408
- def post(*args, &block)
409
- _verb(args, &block) if post?
419
+ if args.empty?
420
+ always(&block)
421
+ else
422
+ if_match(args, &block)
423
+ end
410
424
  end
411
425
 
412
426
  # The response related to the current request.
@@ -420,19 +434,17 @@ class Roda
420
434
  throw :halt, response.finish
421
435
  end
422
436
 
423
- # If the current path is the root ("/"), match on the block. If a request
424
- # method is given, return immediately if the request does not use the given
425
- # method.
426
- def root(request_method=nil, &block)
427
- if env[PATH_INFO] == SLASH && (!request_method || send(:"#{request_method}?"))
428
- _on(EMPTY_ARRAY, &block)
437
+ # If this is a GET request for the root ("/"), yield to the match block.
438
+ def root(&block)
439
+ if @env[PATH_INFO] == SLASH && is_get?
440
+ always(&block)
429
441
  end
430
442
  end
431
443
 
432
444
  # Call the given rack app with the environment and immediately return
433
445
  # the response as the response for this request.
434
446
  def run(app)
435
- throw :halt, app.call(env)
447
+ throw :halt, app.call(@env)
436
448
  end
437
449
 
438
450
  private
@@ -480,31 +492,21 @@ class Roda
480
492
  SEGMENT
481
493
  end
482
494
 
483
- # Internal match method taking array of matchers instead of multiple
484
- # arguments.
485
- def _on(args)
486
- script = env[SCRIPT_NAME]
487
- path = env[PATH_INFO]
488
-
489
- # For every block, we make sure to reset captures so that
490
- # nesting matchers won't mess with each other's captures.
491
- captures.clear
492
-
493
- return unless match_all(args)
494
- block_result(yield(*captures))
495
- throw :halt, response.finish
496
- ensure
497
- env[SCRIPT_NAME] = script
498
- env[PATH_INFO] = path
499
- end
500
-
501
495
  # Backbone of the verb method support, using a terminal match if
502
496
  # args is not empty, or a regular match if it is empty.
503
497
  def _verb(args, &block)
504
- unless args.empty?
498
+ if args.empty?
499
+ always(&block)
500
+ else
505
501
  args << TERM
502
+ if_match(args, &block)
506
503
  end
507
- _on(args, &block)
504
+ end
505
+
506
+ # Yield to the match block and return rack response after the block returns.
507
+ def always
508
+ block_result(yield)
509
+ throw :halt, response.finish
508
510
  end
509
511
 
510
512
  # The body to use for the response if the response does not return
@@ -521,17 +523,38 @@ class Roda
521
523
  # SCRIPT_NAME to include the matched path, removes the matched
522
524
  # path from PATH_INFO, and updates captures with any regex captures.
523
525
  def consume(pattern)
526
+ env = @env
524
527
  return unless matchdata = env[PATH_INFO].match(pattern)
525
528
 
526
529
  vars = matchdata.captures
527
530
 
528
531
  # Don't mutate SCRIPT_NAME, breaks try
529
532
  env[SCRIPT_NAME] += vars.shift
530
- env[PATH_INFO] = "#{vars.pop}#{matchdata.post_match}"
533
+ env[PATH_INFO] = matchdata.post_match
531
534
 
532
535
  captures.concat(vars)
533
536
  end
534
537
 
538
+ # If all of the arguments match, yields to the match block and
539
+ # returns the rack response when the block returns. If any of
540
+ # the match arguments doesn't match, does nothing.
541
+ def if_match(args)
542
+ env = @env
543
+ script = env[SCRIPT_NAME]
544
+ path = env[PATH_INFO]
545
+
546
+ # For every block, we make sure to reset captures so that
547
+ # nesting matchers won't mess with each other's captures.
548
+ captures.clear
549
+
550
+ return unless match_all(args)
551
+ block_result(yield(*captures))
552
+ throw :halt, response.finish
553
+ ensure
554
+ env[SCRIPT_NAME] = script
555
+ env[PATH_INFO] = path
556
+ end
557
+
535
558
  # Attempt to match the argument to the given request, handling
536
559
  # common ruby types.
537
560
  def match(matcher)
@@ -543,7 +566,7 @@ class Roda
543
566
  when Symbol
544
567
  _match_symbol(matcher)
545
568
  when TERM
546
- env[PATH_INFO] == EMPTY_STRING
569
+ @env[PATH_INFO] == EMPTY_STRING
547
570
  when Hash
548
571
  _match_hash(matcher)
549
572
  when Array
@@ -563,7 +586,7 @@ class Roda
563
586
  # Match files with the given extension. Requires that the
564
587
  # request path end with the extension.
565
588
  def match_extension(ext)
566
- consume(self.class.cached_matcher(ext){"([^\\/]+?)\.#{ext}\\z"})
589
+ consume(self.class.cached_matcher([:extension, ext]){"([^\\/]+?)\.#{ext}\\z"})
567
590
  end
568
591
 
569
592
  # Match by request method. This can be an array if you want
@@ -572,7 +595,7 @@ class Roda
572
595
  if type.is_a?(Array)
573
596
  type.any?{|t| match_method(t)}
574
597
  else
575
- type.to_s.upcase == env[REQUEST_METHOD]
598
+ type.to_s.upcase == @env[REQUEST_METHOD]
576
599
  end
577
600
  end
578
601
 
@@ -698,4 +721,6 @@ class Roda
698
721
 
699
722
  extend RodaPlugins::Base::ClassMethods
700
723
  plugin RodaPlugins::Base
724
+ RodaRequest.def_verb_method(RodaPlugins::Base::RequestMethods, :get)
725
+ RodaRequest.def_verb_method(RodaPlugins::Base::RequestMethods, :post)
701
726
  end
@@ -487,21 +487,10 @@ describe "path matchers" do
487
487
  end
488
488
 
489
489
  body.should == 'Home'
490
+ status('REQUEST_METHOD'=>'POST').should == 404
490
491
  status("//").should == 404
491
492
  status("/foo").should == 404
492
493
  end
493
-
494
- it "matching the root with the root method and request method symbol" do
495
- app do |r|
496
- r.root(:get) do
497
- "Home"
498
- end
499
- end
500
-
501
- body.should == 'Home'
502
- status("//").should == 404
503
- status('REQUEST_METHOD'=>"POST").should == 404
504
- end
505
494
  end
506
495
 
507
496
  describe "root/empty segment matching" do
@@ -658,15 +647,15 @@ end
658
647
  describe "extension matcher" do
659
648
  it "should match given file extensions" do
660
649
  app do |r|
661
- r.on "styles" do
650
+ r.on "css" do
662
651
  r.on :extension=>"css" do |file|
663
652
  file
664
653
  end
665
654
  end
666
655
  end
667
656
 
668
- body("/styles/reset.css").should == 'reset'
669
- status("/styles/reset.bar").should == 404
657
+ body("/css/reset.css").should == 'reset'
658
+ status("/css/reset.bar").should == 404
670
659
  end
671
660
  end
672
661
 
@@ -0,0 +1,34 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ begin
4
+ require 'tilt/erb'
5
+ rescue LoadError
6
+ warn "tilt not installed, skipping content_for plugin test"
7
+ else
8
+ describe "content_for plugin" do
9
+ before do
10
+ app(:bare) do
11
+ plugin :render
12
+ render_opts[:views] = "./spec/views"
13
+ plugin :content_for
14
+
15
+ route do |r|
16
+ r.root do
17
+ view(:inline=>"<% content_for :foo do %>foo<% end %>bar", :layout=>{:inline=>'<%= yield %> <%= content_for(:foo) %>'})
18
+ end
19
+ r.get 'a' do
20
+ view(:inline=>"bar", :layout=>{:inline=>'<%= content_for(:foo) %> <%= yield %>'})
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ it "should be able to set content in template and get that content in the layout" do
27
+ body.strip.should == "bar foo"
28
+ end
29
+
30
+ it "should work if content is not set by the template" do
31
+ body('/a').strip.should == "bar"
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,35 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "head plugin" do
4
+ it "considers HEAD requests as GET requests which return no body" do
5
+ app(:head) do |r|
6
+ r.root do
7
+ 'root'
8
+ end
9
+
10
+ r.get 'a' do
11
+ 'a'
12
+ end
13
+
14
+ r.is 'b', :method=>[:get, :post] do
15
+ 'b'
16
+ end
17
+ end
18
+
19
+ s, h, b = req
20
+ s.should == 200
21
+ h['Content-Length'].should == '4'
22
+ b.should == ['root']
23
+
24
+ s, h, b = req('REQUEST_METHOD' => 'HEAD')
25
+ s.should == 200
26
+ h['Content-Length'].should == '4'
27
+ b.should == []
28
+
29
+ body('/a').should == 'a'
30
+ status('/a', 'REQUEST_METHOD' => 'HEAD').should == 200
31
+
32
+ body('/b').should == 'b'
33
+ status('/b', 'REQUEST_METHOD' => 'HEAD').should == 200
34
+ end
35
+ end
@@ -0,0 +1,42 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "not_allowed plugin" do
4
+ it "skips the current block if pass is called" do
5
+ app(:not_allowed) do |r|
6
+ r.get '' do
7
+ 'a'
8
+ end
9
+
10
+ r.is "c" do
11
+ r.get do
12
+ "cg"
13
+ end
14
+
15
+ r.post do
16
+ "cp"
17
+ end
18
+
19
+ "c"
20
+ end
21
+
22
+ r.get do
23
+ r.is 'b' do
24
+ 'b'
25
+ end
26
+ end
27
+ end
28
+
29
+ body.should == 'a'
30
+ status('REQUEST_METHOD'=>'POST').should == 405
31
+ header('Allow', 'REQUEST_METHOD'=>'POST').should == 'GET'
32
+
33
+ body('/b').should == 'b'
34
+ status('/b', 'REQUEST_METHOD'=>'POST').should == 404
35
+
36
+ body('/c').should == 'cg'
37
+ body('/c', 'REQUEST_METHOD'=>'POST').should == 'cp'
38
+ body('/c', 'REQUEST_METHOD'=>'PATCH').should == 'c'
39
+ status('/c', 'REQUEST_METHOD'=>'PATCH').should == 405
40
+ header('Allow', '/c', 'REQUEST_METHOD'=>'PATCH').should == 'GET, POST'
41
+ end
42
+ end
@@ -3,6 +3,11 @@ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
3
3
  describe "pass plugin" do
4
4
  it "skips the current block if pass is called" do
5
5
  app(:pass) do |r|
6
+ r.root do
7
+ r.pass if env['FOO']
8
+ 'root'
9
+ end
10
+
6
11
  r.on :id do |id|
7
12
  r.pass if id == 'foo'
8
13
  id
@@ -13,11 +18,12 @@ describe "pass plugin" do
13
18
  end
14
19
  end
15
20
 
21
+ body.should == 'root'
22
+ status('FOO'=>true).should == 404
16
23
  body("/a").should == 'a'
17
24
  body("/a/b").should == 'a'
18
25
  body("/foo/a").should == 'fooa'
19
26
  body("/foo/a/b").should == 'fooa'
20
27
  status("/foo").should == 404
21
- status.should == 404
22
28
  end
23
29
  end
@@ -0,0 +1,30 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "render_each plugin" do
4
+ it "calls render with each argument, returning joined string with all results" do
5
+ app(:bare) do
6
+ plugin :render_each
7
+ def render(t, opts)
8
+ "r#{t}#{opts[:locals][:foo] if opts[:locals]}#{opts[:bar]} "
9
+ end
10
+
11
+ route do |r|
12
+ r.root do
13
+ render_each([1,2,3], :foo)
14
+ end
15
+
16
+ r.is 'a' do
17
+ render_each([1,2,3], :bar, :local=>:foo, :bar=>4)
18
+ end
19
+
20
+ r.is 'b' do
21
+ render_each([1,2,3], :bar, :local=>nil)
22
+ end
23
+ end
24
+ end
25
+
26
+ body.should == 'rfoo1 rfoo2 rfoo3 '
27
+ body("/a").should == 'rbar14 rbar24 rbar34 '
28
+ body("/b").should == 'rbar rbar rbar '
29
+ end
30
+ end
@@ -27,6 +27,10 @@ describe "symbol_matchers plugin" do
27
27
  "f#{f}"
28
28
  end
29
29
 
30
+ r.is 'q:rest' do |r|
31
+ "rest#{r}"
32
+ end
33
+
30
34
  r.is :w do |w|
31
35
  "w#{w}"
32
36
  end
@@ -58,5 +62,7 @@ describe "symbol_matchers plugin" do
58
62
  body("/1/1a/f").should == 'dwf11af'
59
63
  body("/12/1azy/fffff").should == 'dwf121azyfffff'
60
64
  status("/1/f/a").should == 404
65
+ body("/qa/b/c/d//f/g").should == 'resta/b/c/d//f/g'
66
+ body('/q').should == 'rest'
61
67
  end
62
68
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda-cj
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.3
4
+ version: 0.9.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-14 00:00:00.000000000 Z
11
+ date: 2014-08-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -88,12 +88,16 @@ files:
88
88
  - lib/roda/plugins/flash.rb
89
89
  - lib/roda/plugins/csrf.rb
90
90
  - lib/roda/plugins/symbol_views.rb
91
+ - lib/roda/plugins/content_for.rb
91
92
  - lib/roda/plugins/halt.rb
93
+ - lib/roda/plugins/not_allowed.rb
92
94
  - lib/roda/plugins/header_matchers.rb
93
95
  - lib/roda/plugins/per_thread_caching.rb
94
96
  - lib/roda/plugins/error_handler.rb
95
97
  - lib/roda/plugins/indifferent_params.rb
98
+ - lib/roda/plugins/head.rb
96
99
  - lib/roda/plugins/json.rb
100
+ - lib/roda/plugins/render_each.rb
97
101
  - lib/roda/plugins/streaming.rb
98
102
  - lib/roda/plugins/all_verbs.rb
99
103
  - lib/roda/plugins/not_found.rb
@@ -118,6 +122,7 @@ files:
118
122
  - spec/plugin/error_handler_spec.rb
119
123
  - spec/plugin/json_spec.rb
120
124
  - spec/plugin/symbol_views_spec.rb
125
+ - spec/plugin/head_spec.rb
121
126
  - spec/plugin/all_verbs_spec.rb
122
127
  - spec/plugin/render_spec.rb
123
128
  - spec/plugin/symbol_matchers_spec.rb
@@ -126,12 +131,15 @@ files:
126
131
  - spec/plugin/default_headers_spec.rb
127
132
  - spec/plugin/backtracking_array_spec.rb
128
133
  - spec/plugin/per_thread_caching_spec.rb
134
+ - spec/plugin/content_for_spec.rb
129
135
  - spec/plugin/hooks_spec.rb
130
136
  - spec/plugin/streaming_spec.rb
131
137
  - spec/plugin/flash_spec.rb
132
138
  - spec/plugin/pass_spec.rb
139
+ - spec/plugin/render_each_spec.rb
133
140
  - spec/plugin/not_found_spec.rb
134
141
  - spec/plugin/middleware_spec.rb
142
+ - spec/plugin/not_allowed_spec.rb
135
143
  - spec/plugin/h_spec.rb
136
144
  - spec/plugin/halt_spec.rb
137
145
  - spec/plugin/view_subdirs_spec.rb