roda-cj 0.9.3 → 0.9.4

Sign up to get free protection for your applications and to get access to all the features.
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