roda 1.3.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +24 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +7 -4
- data/doc/release_notes/2.0.0.txt +75 -0
- data/lib/roda/plugins/assets.rb +2 -2
- data/lib/roda/plugins/backtracking_array.rb +2 -11
- data/lib/roda/plugins/caching.rb +4 -2
- data/lib/roda/plugins/chunked.rb +4 -9
- data/lib/roda/plugins/class_level_routing.rb +1 -3
- data/lib/roda/plugins/default_headers.rb +1 -2
- data/lib/roda/plugins/error_email.rb +4 -14
- data/lib/roda/plugins/error_handler.rb +4 -4
- data/lib/roda/plugins/flash.rb +1 -3
- data/lib/roda/plugins/halt.rb +24 -5
- data/lib/roda/plugins/header_matchers.rb +2 -7
- data/lib/roda/plugins/hooks.rb +1 -3
- data/lib/roda/plugins/json.rb +4 -2
- data/lib/roda/plugins/mailer.rb +8 -7
- data/lib/roda/plugins/middleware.rb +21 -9
- data/lib/roda/plugins/not_found.rb +3 -3
- data/lib/roda/plugins/padrino_render.rb +60 -0
- data/lib/roda/plugins/param_matchers.rb +3 -3
- data/lib/roda/plugins/path.rb +2 -1
- data/lib/roda/plugins/render.rb +55 -37
- data/lib/roda/plugins/render_each.rb +4 -2
- data/lib/roda/plugins/static_path_info.rb +2 -63
- data/lib/roda/plugins/streaming.rb +4 -2
- data/lib/roda/version.rb +2 -2
- data/lib/roda.rb +71 -172
- data/spec/matchers_spec.rb +31 -82
- data/spec/plugin/assets_spec.rb +6 -6
- data/spec/plugin/error_handler_spec.rb +23 -0
- data/spec/plugin/halt_spec.rb +39 -0
- data/spec/plugin/middleware_spec.rb +7 -0
- data/spec/plugin/padrino_render_spec.rb +57 -0
- data/spec/plugin/render_each_spec.rb +1 -1
- data/spec/plugin/render_spec.rb +59 -5
- data/spec/request_spec.rb +0 -12
- data/spec/response_spec.rb +0 -24
- data/spec/views/_test.erb +1 -0
- metadata +7 -4
- data/lib/roda/plugins/delete_nil_headers.rb +0 -34
- data/spec/module_spec.rb +0 -29
@@ -0,0 +1,60 @@
|
|
1
|
+
class Roda
|
2
|
+
module RodaPlugins
|
3
|
+
# The padrino_render plugin adds rendering support that is
|
4
|
+
# similar to Padrino's. While not everything Padrino's
|
5
|
+
# rendering supports is supported by this plugin (yet), it
|
6
|
+
# currently handles enough to be a drop in replacement for
|
7
|
+
# some applications.
|
8
|
+
#
|
9
|
+
# Most notably, this makes the +render+ method default to
|
10
|
+
# using the layout, similar to how the +view+ method works
|
11
|
+
# in the render plugin. If you want to call render and not
|
12
|
+
# use a layout, you can use the <tt>:layout=>false</tt>
|
13
|
+
# option:
|
14
|
+
#
|
15
|
+
# render('test') # layout
|
16
|
+
# render('test', :layout=>false) # no layout
|
17
|
+
#
|
18
|
+
# This also adds a +partial+ method, which renders templates
|
19
|
+
# without the layout, but prefixes the template filename to
|
20
|
+
# use with an underscore:
|
21
|
+
#
|
22
|
+
# partial('test') # uses _test.erb
|
23
|
+
# partial('dir/test') # uses dir/_test.erb
|
24
|
+
#
|
25
|
+
#
|
26
|
+
module PadrinoRender
|
27
|
+
OPTS = {}.freeze
|
28
|
+
SLASH = '/'.freeze
|
29
|
+
|
30
|
+
# Depend on the render plugin, since this overrides
|
31
|
+
# some of its methods.
|
32
|
+
def self.load_dependencies(app, opts=OPTS)
|
33
|
+
app.plugin :render, opts
|
34
|
+
end
|
35
|
+
|
36
|
+
module InstanceMethods
|
37
|
+
# Call view with the given arguments, so that render
|
38
|
+
# uses a layout by default.
|
39
|
+
def render(template, opts=OPTS)
|
40
|
+
view(template, opts)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Renders the given template without a layout, but
|
44
|
+
# prefixes the template filename to use with an
|
45
|
+
# underscore.
|
46
|
+
def partial(template, opts=OPTS)
|
47
|
+
opts = parse_template_opts(template, opts)
|
48
|
+
if opts[:template]
|
49
|
+
template = opts[:template].split(SLASH)
|
50
|
+
template[-1] = "_#{template[-1]}"
|
51
|
+
opts[:template] = template.join(SLASH)
|
52
|
+
end
|
53
|
+
render_template(opts)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
register_plugin(:padrino_render, PadrinoRender)
|
59
|
+
end
|
60
|
+
end
|
@@ -6,15 +6,15 @@ class Roda
|
|
6
6
|
# It adds a :param matcher for matching on any param with the
|
7
7
|
# same name, yielding the value of the param.
|
8
8
|
#
|
9
|
-
# r.on :param=>'foo' do |value|
|
9
|
+
# r.on :param => 'foo' do |value|
|
10
10
|
# # Matches '?foo=bar', '?foo='
|
11
11
|
# # Doesn't match '?bar=foo'
|
12
12
|
# end
|
13
13
|
#
|
14
14
|
# It adds a :param! matcher for matching on any non-empty param
|
15
|
-
# with the same name, yielding the value of the param
|
15
|
+
# with the same name, yielding the value of the param.
|
16
16
|
#
|
17
|
-
# r.on
|
17
|
+
# r.on(:param! => 'foo') do |value|
|
18
18
|
# # Matches '?foo=bar'
|
19
19
|
# # Doesn't match '?foo=', '?bar=foo'
|
20
20
|
# end
|
data/lib/roda/plugins/path.rb
CHANGED
@@ -32,10 +32,11 @@ class Roda
|
|
32
32
|
# a block to a block that is instance_execed.
|
33
33
|
module Path
|
34
34
|
DEFAULT_PORTS = {'http' => 80, 'https' => 443}.freeze
|
35
|
+
OPTS = {}.freeze
|
35
36
|
|
36
37
|
module ClassMethods
|
37
38
|
# Create a new instance method for the named path. See plugin module documentation for options.
|
38
|
-
def path(name, path=nil, opts=
|
39
|
+
def path(name, path=nil, opts=OPTS, &block)
|
39
40
|
if path.is_a?(Hash)
|
40
41
|
raise RodaError, "cannot provide two option hashses to Roda.path" unless opts.empty?
|
41
42
|
opts = path
|
data/lib/roda/plugins/render.rb
CHANGED
@@ -28,7 +28,7 @@ class Roda
|
|
28
28
|
# to true unless RACK_ENV is development to automatically use the
|
29
29
|
# default template cache.
|
30
30
|
# :engine :: The tilt engine to use for rendering, defaults to 'erb'.
|
31
|
-
# :escape :: Use Roda's Erubis escaping support, which makes
|
31
|
+
# :escape :: Use Roda's Erubis escaping support, which makes <tt><%= %></tt> escape output,
|
32
32
|
# <tt><%== %></tt> not escape output, and handles postfix conditions inside
|
33
33
|
# <tt><%= %></tt> tags.
|
34
34
|
# :ext :: The file extension to assume for view files, defaults to the :engine
|
@@ -65,7 +65,7 @@ class Roda
|
|
65
65
|
# :template_block :: Pass this block when creating the underlying template,
|
66
66
|
# ignored when using :inline.
|
67
67
|
# :template_class :: Provides the template class to use, inside of using
|
68
|
-
# Tilt or
|
68
|
+
# Tilt or <tt>Tilt[:engine]</tt>.
|
69
69
|
#
|
70
70
|
# Here's how those options are used:
|
71
71
|
#
|
@@ -73,7 +73,8 @@ class Roda
|
|
73
73
|
# render(:path=>'/path/to/template.erb')
|
74
74
|
#
|
75
75
|
# If you pass a hash as the first argument to +view+ or +render+, it should
|
76
|
-
# have either +:inline
|
76
|
+
# have either +:template+, +:inline+, +:path+, or +:content+ (for +view+) as
|
77
|
+
# one of the keys.
|
77
78
|
module Render
|
78
79
|
OPTS={}.freeze
|
79
80
|
|
@@ -85,27 +86,30 @@ class Roda
|
|
85
86
|
|
86
87
|
# Setup default rendering options. See Render for details.
|
87
88
|
def self.configure(app, opts=OPTS)
|
89
|
+
orig_opts = opts
|
88
90
|
if app.opts[:render]
|
89
91
|
app.opts[:render] = app.opts[:render].merge(opts)
|
90
92
|
else
|
91
93
|
app.opts[:render] = opts.dup
|
92
94
|
end
|
93
95
|
|
94
|
-
if opts[:opts] && !opts[:template_opts]
|
95
|
-
RodaPlugins.deprecate("The render plugin :opts option is deprecated and will be removed in Roda 2. Switch to using the :template_opts option")
|
96
|
-
app.opts[:render][:template_opts] = opts[:opts]
|
97
|
-
end
|
98
|
-
|
99
96
|
opts = app.opts[:render]
|
100
97
|
opts[:engine] ||= "erb"
|
101
98
|
opts[:ext] = nil unless opts.has_key?(:ext)
|
102
99
|
opts[:views] ||= File.expand_path("views", Dir.pwd)
|
103
|
-
opts[:
|
104
|
-
|
100
|
+
opts[:layout_opts] = (opts[:layout_opts] || {}).dup
|
101
|
+
|
102
|
+
if layout = orig_opts.fetch(:layout, true)
|
103
|
+
opts[:layout] = true unless opts.has_key?(:layout)
|
105
104
|
|
106
|
-
|
107
|
-
|
108
|
-
|
105
|
+
case layout
|
106
|
+
when Hash
|
107
|
+
opts[:layout_opts].merge!(layout)
|
108
|
+
when true
|
109
|
+
opts[:layout_opts][:template] ||= 'layout'
|
110
|
+
else
|
111
|
+
opts[:layout_opts][:template] = layout
|
112
|
+
end
|
109
113
|
end
|
110
114
|
|
111
115
|
template_opts = opts[:template_opts] = (opts[:template_opts] || {}).dup
|
@@ -117,9 +121,9 @@ class Roda
|
|
117
121
|
template_opts[:engine_class] = ErubisEscaping::Eruby
|
118
122
|
end
|
119
123
|
opts[:cache] = app.thread_safe_cache if opts.fetch(:cache, ENV['RACK_ENV'] != 'development')
|
120
|
-
opts.
|
121
|
-
opts[:
|
122
|
-
opts
|
124
|
+
opts[:layout_opts].freeze
|
125
|
+
opts[:template_opts].freeze
|
126
|
+
opts.freeze
|
123
127
|
end
|
124
128
|
|
125
129
|
module ClassMethods
|
@@ -128,11 +132,9 @@ class Roda
|
|
128
132
|
# affecting the parent class.
|
129
133
|
def inherited(subclass)
|
130
134
|
super
|
131
|
-
opts = subclass.opts[:render].dup
|
132
|
-
opts[:layout_opts] = opts[:layout_opts].dup.extend(RodaDeprecateMutation)
|
133
|
-
opts[:template_opts] = opts[:template_opts].dup.extend(RodaDeprecateMutation)
|
135
|
+
opts = subclass.opts[:render] = subclass.opts[:render].dup
|
134
136
|
opts[:cache] = thread_safe_cache if opts[:cache]
|
135
|
-
|
137
|
+
opts.freeze
|
136
138
|
end
|
137
139
|
|
138
140
|
# Return the render options for this class.
|
@@ -148,10 +150,6 @@ class Roda
|
|
148
150
|
cached_template(opts) do
|
149
151
|
template_opts = render_opts[:template_opts]
|
150
152
|
current_template_opts = opts[:template_opts]
|
151
|
-
if opts[:opts] && !current_template_opts
|
152
|
-
RodaPlugins.deprecate("The render method :opts option is deprecated and will be removed in Roda 2. Switch to using the :template_opts option")
|
153
|
-
current_template_opts = opts[:opts]
|
154
|
-
end
|
155
153
|
template_opts = template_opts.merge(current_template_opts) if current_template_opts
|
156
154
|
opts[:template_class].new(opts[:path], 1, template_opts, &opts[:template_block])
|
157
155
|
end.render(self, (opts[:locals]||OPTS), &block)
|
@@ -169,15 +167,10 @@ class Roda
|
|
169
167
|
# and render it inside the layout. See Render for details.
|
170
168
|
def view(template, opts=OPTS)
|
171
169
|
opts = parse_template_opts(template, opts)
|
172
|
-
content = opts[:content] ||
|
173
|
-
|
174
|
-
if layout = opts.fetch(:layout, (OPTS if render_opts[:layout]))
|
175
|
-
layout_opts = render_opts[:layout_opts]
|
176
|
-
if opts[:layout_opts]
|
177
|
-
layout_opts = opts[:layout_opts].merge(layout_opts)
|
178
|
-
end
|
170
|
+
content = opts[:content] || render_template(opts)
|
179
171
|
|
180
|
-
|
172
|
+
if layout_opts = view_layout_opts(opts)
|
173
|
+
content = render_template(layout_opts){content}
|
181
174
|
end
|
182
175
|
|
183
176
|
content
|
@@ -185,6 +178,10 @@ class Roda
|
|
185
178
|
|
186
179
|
private
|
187
180
|
|
181
|
+
# Private alias for render. Should be used by other plugins when they want to render a template
|
182
|
+
# without a layout, as plugins can override render to use a layout.
|
183
|
+
alias render_template render
|
184
|
+
|
188
185
|
# If caching templates, attempt to retrieve the template from the cache. Otherwise, just yield
|
189
186
|
# to get the template.
|
190
187
|
def cached_template(opts, &block)
|
@@ -214,10 +211,6 @@ class Roda
|
|
214
211
|
|
215
212
|
if render_opts[:cache]
|
216
213
|
template_opts = opts[:template_opts]
|
217
|
-
if opts[:opts] && !template_opts
|
218
|
-
RodaPlugins.deprecate("The render method :opts option is deprecated and will be removed in Roda 2. Switch to using the :template_opts option")
|
219
|
-
template_opts = opts[:opts]
|
220
|
-
end
|
221
214
|
template_block = opts[:template_block] if !content
|
222
215
|
|
223
216
|
key = if template_class || template_opts || template_block
|
@@ -242,11 +235,36 @@ class Roda
|
|
242
235
|
opts[:template].to_s
|
243
236
|
end
|
244
237
|
|
245
|
-
# The path for the given
|
238
|
+
# The template path for the given options.
|
246
239
|
def template_path(opts)
|
247
240
|
render_opts = render_opts()
|
248
241
|
"#{opts[:views] || render_opts[:views]}/#{template_name(opts)}.#{opts[:ext] || render_opts[:ext] || render_opts[:engine]}"
|
249
242
|
end
|
243
|
+
|
244
|
+
# If a layout should be used, return a hash of options for
|
245
|
+
# rendering the layout template. If a layout should not be
|
246
|
+
# used, return nil.
|
247
|
+
def view_layout_opts(opts)
|
248
|
+
if layout = opts.fetch(:layout, render_opts[:layout])
|
249
|
+
layout_opts = if opts[:layout_opts]
|
250
|
+
opts[:layout_opts].merge(render_opts[:layout_opts])
|
251
|
+
else
|
252
|
+
render_opts[:layout_opts].dup
|
253
|
+
end
|
254
|
+
|
255
|
+
case layout
|
256
|
+
when Hash
|
257
|
+
layout_opts.merge!(layout)
|
258
|
+
when true
|
259
|
+
# use default layout
|
260
|
+
else
|
261
|
+
layout_opts[:template] = layout
|
262
|
+
end
|
263
|
+
|
264
|
+
layout_opts
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
250
268
|
end
|
251
269
|
end
|
252
270
|
|
@@ -23,6 +23,8 @@ class Roda
|
|
23
23
|
# the template will be +bar+. You can use <tt>:local=>nil</tt> to
|
24
24
|
# not set a local variable inside the template.
|
25
25
|
module RenderEach
|
26
|
+
OPTS = {}.freeze
|
27
|
+
|
26
28
|
# Load the render plugin before this plugin, since this plugin
|
27
29
|
# calls the render method.
|
28
30
|
def self.load_dependencies(app)
|
@@ -36,7 +38,7 @@ class Roda
|
|
36
38
|
# :local :: The local variable to use for the current enum value
|
37
39
|
# inside the template. An explicit +nil+ value does not
|
38
40
|
# set a local variable. If not set, uses the template name.
|
39
|
-
def render_each(enum, template, opts=
|
41
|
+
def render_each(enum, template, opts=OPTS)
|
40
42
|
if as = opts.has_key?(:local)
|
41
43
|
as = opts[:local]
|
42
44
|
else
|
@@ -54,7 +56,7 @@ class Roda
|
|
54
56
|
|
55
57
|
enum.map do |v|
|
56
58
|
locals[as] = v if as
|
57
|
-
|
59
|
+
render_template(template, opts)
|
58
60
|
end.join
|
59
61
|
end
|
60
62
|
end
|
@@ -1,71 +1,10 @@
|
|
1
1
|
class Roda
|
2
2
|
module RodaPlugins
|
3
|
-
# The static_path_info plugin changes Roda's behavior so that the
|
4
|
-
# SCRIPT_NAME/PATH_INFO environment settings are not modified
|
5
|
-
# while the request is beind routed, improving performance. If
|
6
|
-
# you have any helpers that operate on PATH_INFO or SCRIPT_NAME,
|
7
|
-
# their behavior will not change depending on where they are
|
8
|
-
# called in the routing tree.
|
9
|
-
#
|
10
|
-
# This still updates SCRIPT_NAME/PATH_INFO before dispatching to
|
11
|
-
# another rack app via +r.run+.
|
12
3
|
module StaticPathInfo
|
13
|
-
module RequestMethods
|
14
|
-
PATH_INFO = "PATH_INFO".freeze
|
15
|
-
SCRIPT_NAME = "SCRIPT_NAME".freeze
|
16
|
-
|
17
|
-
# The current path to match requests against. This is initialized
|
18
|
-
# to PATH_INFO when the request is created.
|
19
|
-
attr_reader :remaining_path
|
20
|
-
|
21
|
-
# Set remaining_path when initializing.
|
22
|
-
def initialize(*)
|
23
|
-
super
|
24
|
-
@remaining_path = @env[PATH_INFO]
|
25
|
-
end
|
26
|
-
|
27
|
-
# The already matched part of the path, including the original SCRIPT_NAME.
|
28
|
-
def matched_path
|
29
|
-
e = @env
|
30
|
-
e[SCRIPT_NAME] + e[PATH_INFO].chomp(@remaining_path)
|
31
|
-
end
|
32
|
-
|
33
|
-
# Update SCRIPT_NAME/PATH_INFO based on the current remaining_path
|
34
|
-
# before dispatching to another rack app, so the app still works as
|
35
|
-
# a URL mapper.
|
36
|
-
def run(_)
|
37
|
-
e = @env
|
38
|
-
path = @remaining_path
|
39
|
-
begin
|
40
|
-
script_name = e[SCRIPT_NAME]
|
41
|
-
path_info = e[PATH_INFO]
|
42
|
-
e[SCRIPT_NAME] += path_info.chomp(path)
|
43
|
-
e[PATH_INFO] = path
|
44
|
-
super
|
45
|
-
ensure
|
46
|
-
e[SCRIPT_NAME] = script_name
|
47
|
-
e[PATH_INFO] = path_info
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
private
|
52
|
-
|
53
|
-
# Update remaining_path with the remaining characters
|
54
|
-
def update_remaining_path(remaining)
|
55
|
-
@remaining_path = remaining
|
56
|
-
end
|
57
|
-
|
58
|
-
# Yield to the block, restoring the remaining_path before
|
59
|
-
# the method returns.
|
60
|
-
def keep_remaining_path
|
61
|
-
path = @remaining_path
|
62
|
-
yield
|
63
|
-
ensure
|
64
|
-
@remaining_path = path
|
65
|
-
end
|
66
|
-
end
|
67
4
|
end
|
68
5
|
|
6
|
+
# For backwards compatibilty, don't raise an error
|
7
|
+
# if trying to load the plugin
|
69
8
|
register_plugin(:static_path_info, StaticPathInfo)
|
70
9
|
end
|
71
10
|
end
|
@@ -50,6 +50,8 @@ class Roda
|
|
50
50
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
51
51
|
# OTHER DEALINGS IN THE SOFTWARE.
|
52
52
|
module Streaming
|
53
|
+
OPTS = {}.freeze
|
54
|
+
|
53
55
|
# Class of the response body in case you use #stream.
|
54
56
|
#
|
55
57
|
# Three things really matter: The front and back block (back being the
|
@@ -85,7 +87,7 @@ class Roda
|
|
85
87
|
end
|
86
88
|
|
87
89
|
# Handle streaming options, see Streaming for details.
|
88
|
-
def initialize(opts=
|
90
|
+
def initialize(opts=OPTS, &back)
|
89
91
|
@scheduler = opts[:scheduler] || Scheduler.new(self)
|
90
92
|
@back = back.to_proc
|
91
93
|
@keep_open = opts[:keep_open]
|
@@ -143,7 +145,7 @@ class Roda
|
|
143
145
|
# Immediately return a streaming response using the current response
|
144
146
|
# status and headers, calling the block to get the streaming response.
|
145
147
|
# See Streaming for details.
|
146
|
-
def stream(opts=
|
148
|
+
def stream(opts=OPTS, &block)
|
147
149
|
opts = opts.merge(:scheduler=>EventMachine) if !opts.has_key?(:scheduler) && env['async.callback']
|
148
150
|
|
149
151
|
if opts[:loop]
|
data/lib/roda/version.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
class Roda
|
2
2
|
# The major version of Roda, updated only for major changes that are
|
3
3
|
# likely to require modification to Roda apps.
|
4
|
-
RodaMajorVersion =
|
4
|
+
RodaMajorVersion = 2
|
5
5
|
|
6
6
|
# The minor version of Roda, updated for new feature releases of Roda.
|
7
|
-
RodaMinorVersion =
|
7
|
+
RodaMinorVersion = 0
|
8
8
|
|
9
9
|
# The patch version of Roda, updated only for bug fixes from the last
|
10
10
|
# feature release.
|