roda 2.1.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +20 -0
- data/MIT-LICENSE +2 -1
- data/README.rdoc +41 -0
- data/Rakefile +6 -5
- data/doc/release_notes/2.1.0.txt +1 -1
- data/doc/release_notes/2.2.0.txt +97 -0
- data/lib/roda/plugins/_erubis_escaping.rb +37 -10
- data/lib/roda/plugins/chunked.rb +42 -6
- data/lib/roda/plugins/default_headers.rb +1 -1
- data/lib/roda/plugins/multi_route.rb +1 -1
- data/lib/roda/plugins/padrino_render.rb +5 -24
- data/lib/roda/plugins/partials.rb +47 -0
- data/lib/roda/plugins/path.rb +29 -12
- data/lib/roda/plugins/render.rb +11 -0
- data/lib/roda/plugins/shared_vars.rb +85 -0
- data/lib/roda/plugins/streaming.rb +1 -1
- data/lib/roda/version.rb +1 -1
- data/spec/plugin/_erubis_escaping_spec.rb +31 -2
- data/spec/plugin/chunked_spec.rb +21 -3
- data/spec/plugin/default_headers_spec.rb +14 -0
- data/spec/plugin/padrino_render_spec.rb +1 -24
- data/spec/plugin/partials_spec.rb +43 -0
- data/spec/plugin/path_spec.rb +43 -0
- data/spec/plugin/shared_vars_spec.rb +45 -0
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9bbbb565f4b5766e8e11f78d322a5446da6358bd
|
4
|
+
data.tar.gz: 4e7b782d3d4a2292e9461c47053156a18d56bec2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 864fd659136483c2867b91e6512df778fd9f331c60e30b8166f1146afda0810cb1f08d56b38f2c01f50d7fcd477b9914def3d0eb1479de2533f0db8f94dc3dc3
|
7
|
+
data.tar.gz: 79dcb04899e305ed875f439d3ebb57cbaf9092bba1614e8fcbbb4b15da55c9b10c74086f3bf24e7b543b9dcaaf9f3042009ab611a9e7b7f0aa0b0077a91a32b0
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,23 @@
|
|
1
|
+
= 2.2.0 (2015-04-13)
|
2
|
+
|
3
|
+
* Add :escaper render plugin option to support custom escaping of <%= %> tags when :escape is used (jeremyevans)
|
4
|
+
|
5
|
+
* Add :escape_safe_classes render plugin option, to not escape certain string subclasses when :escape is used (jeremyevans)
|
6
|
+
|
7
|
+
* Split partials method from padrino_render plugin into partials plugin (kematzy) (#19)
|
8
|
+
|
9
|
+
* Add shared_vars plugin, for sharing variables between multiple Roda apps (jeremyevans)
|
10
|
+
|
11
|
+
* Add delay method to chunked plugin, for delaying a block execution until right before content template rendering (jeremyevans)
|
12
|
+
|
13
|
+
* Have default Content-Type header when using the default_headers plugin (jeremyevans)
|
14
|
+
|
15
|
+
* Add :by_name option to the path plugin, for registering classes by name, useful when reloading code (jeremyevans)
|
16
|
+
|
17
|
+
* Add Roda.path_block to get the block related to the given class used for Road#path (jeremyevans)
|
18
|
+
|
19
|
+
* Make Roda#path work correctly in subclasses (jeremyevans)
|
20
|
+
|
1
21
|
= 2.1.0 (2015-03-13)
|
2
22
|
|
3
23
|
* Have add_file in the mailer plugin support blocks, which are called after the file has been added (jeremyevans)
|
data/MIT-LICENSE
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
Copyright (c) 2014-2015 Jeremy Evans
|
2
|
-
Copyright (c) 2010
|
2
|
+
Copyright (c) 2010-2014 Michel Martens, Damian Janowski and Cyril David
|
3
|
+
Copyright (c) 2008-2009 Christian Neukirchen
|
3
4
|
|
4
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
6
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.rdoc
CHANGED
@@ -744,8 +744,49 @@ to escape by default, so that in your templates:
|
|
744
744
|
<%= '<>' %> # outputs <>
|
745
745
|
<%== '<>' %> # outputs <>
|
746
746
|
|
747
|
+
You can also provide a +:escape_safe_classes+ option, which will
|
748
|
+
make <tt><%= %></tt> not escape certain string subclasses, useful
|
749
|
+
if you have helpers that already return escaped output using a
|
750
|
+
string subclass instance.
|
751
|
+
|
747
752
|
This support requires {Erubis}[http://www.kuwata-lab.com/erubis/].
|
748
753
|
|
754
|
+
=== Security Related HTTP Headers
|
755
|
+
|
756
|
+
You may want to look into setting the following HTTP headers, which
|
757
|
+
can be done at the web server level, but can also be done at the
|
758
|
+
application level using using the +default_headers+ plugin:
|
759
|
+
|
760
|
+
Content-Security-Policy/X-Content-Security-Policy :: Defines policy for how javascript and other
|
761
|
+
types of content can be used on the page.
|
762
|
+
Frame-Options/X-Frame-Options :: Provides click-jacking projection by not allowing usage inside
|
763
|
+
a frame.
|
764
|
+
Strict-Transport-Security :: Enforces SSL/TLS Connections to the application.
|
765
|
+
X-Content-Type-Options :: Forces some browsers to respect a declared Content-Type header.
|
766
|
+
X-XSS-Protection :: Enables an XSS mitigation filter in some browsers.
|
767
|
+
|
768
|
+
Example:
|
769
|
+
|
770
|
+
class App < Roda
|
771
|
+
plugin :default_headers,
|
772
|
+
'Content-Type'=>'text/html',
|
773
|
+
'Content-Security-Policy'=>"default-src 'self'",
|
774
|
+
'Strict-Transport-Security'=>'max-age=16070400;',
|
775
|
+
'X-Frame-Options'=>'deny',
|
776
|
+
'X-Content-Type-Options'=>'nosniff',
|
777
|
+
'X-XSS-Protection'=>'1; mode=block'
|
778
|
+
end
|
779
|
+
|
780
|
+
== Reloading
|
781
|
+
|
782
|
+
Most rack-based reloaders will work with Roda, including:
|
783
|
+
|
784
|
+
* {rack-unreloader}[https://github.com/jeremyevans/rack-unreloader]
|
785
|
+
* {rerun}[https://github.com/alexch/rerun]
|
786
|
+
* {shotgun}[https://github.com/rtomayko/shotgun]
|
787
|
+
* {Rack::Reloader}[https://github.com/rack/rack/blob/master/lib/rack/reloader.rb]
|
788
|
+
* {pistol}[https://github.com/monk/pistol]
|
789
|
+
|
749
790
|
== Plugins
|
750
791
|
|
751
792
|
By design, Roda has a very small core, providing only the essentials.
|
data/Rakefile
CHANGED
@@ -68,15 +68,16 @@ end
|
|
68
68
|
|
69
69
|
begin
|
70
70
|
begin
|
71
|
-
|
72
|
-
require "spec/rake/spectask"
|
73
|
-
spec_class = Spec::Rake::SpecTask
|
74
|
-
spec_files_meth = :spec_files=
|
75
|
-
rescue LoadError
|
71
|
+
raise LoadError if ENV['RSPEC1']
|
76
72
|
# RSpec 2
|
77
73
|
require "rspec/core/rake_task"
|
78
74
|
spec_class = RSpec::Core::RakeTask
|
79
75
|
spec_files_meth = :pattern=
|
76
|
+
rescue LoadError
|
77
|
+
# RSpec 1
|
78
|
+
require "spec/rake/spectask"
|
79
|
+
spec_class = Spec::Rake::SpecTask
|
80
|
+
spec_files_meth = :spec_files=
|
80
81
|
end
|
81
82
|
|
82
83
|
spec = lambda do |name, files, d|
|
data/doc/release_notes/2.1.0.txt
CHANGED
@@ -0,0 +1,97 @@
|
|
1
|
+
= New Plugins
|
2
|
+
|
3
|
+
* A shared_vars plugin has been added, for sharing variables between
|
4
|
+
multiple Roda apps. Example:
|
5
|
+
|
6
|
+
class API < Roda
|
7
|
+
plugin :shared_vars
|
8
|
+
route do |r|
|
9
|
+
user = shared[:user]
|
10
|
+
# ...
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class App < Roda
|
15
|
+
plugin :shared_vars
|
16
|
+
|
17
|
+
route do |r|
|
18
|
+
r.on :user_id do |user_id|
|
19
|
+
shared[:user] = User[user_id]
|
20
|
+
r.run API
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
If you pass a hash to shared, it will update the shared
|
26
|
+
variables with the content of the hash:
|
27
|
+
|
28
|
+
route do |r|
|
29
|
+
r.on :user_id do |user_id|
|
30
|
+
shared(:user => User[user_id])
|
31
|
+
r.run API
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
You can also pass a block to shared, which will set the
|
36
|
+
shared variables only for the given block, restoring the
|
37
|
+
previous shared variables afterward:
|
38
|
+
|
39
|
+
route do |r|
|
40
|
+
r.on :user_id do |user_id|
|
41
|
+
shared(:user => User[user_id]) do
|
42
|
+
r.run API
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
* The partials method added by the padrino_render plugin has been
|
48
|
+
extracted into a partials plugin, for users who want to use the
|
49
|
+
partials method without having render use a layout by default.
|
50
|
+
|
51
|
+
= Other New Features
|
52
|
+
|
53
|
+
* The render plugin when using the :escape option now additionally
|
54
|
+
supports an :escape_safe_classes option, which accepts a class or
|
55
|
+
array of classes that should not be automatically escaped when using
|
56
|
+
<%= %> tags. This makes easier to integrate with helpers or
|
57
|
+
external libraries that return already html escaped strings, without
|
58
|
+
using <%== %> tags.
|
59
|
+
|
60
|
+
For complete control, an :escaper option is also supported, which
|
61
|
+
will be used for escaping <%= %> strings. The value of this option
|
62
|
+
should be an object that responds to escape_xml with a single
|
63
|
+
argument and returns an output string.
|
64
|
+
|
65
|
+
* A Roda#delay method has been added to the chunked plugin, which
|
66
|
+
delays execution of the given block until right before the content
|
67
|
+
template is rendered. This allows you to continue to use the
|
68
|
+
routing tree to DRY up your code even when streaming template
|
69
|
+
rendering. Example:
|
70
|
+
|
71
|
+
r.on 'albums/:d' do |album_id|
|
72
|
+
delay do
|
73
|
+
@album = Album[album_id]
|
74
|
+
end
|
75
|
+
r.get 'info' do
|
76
|
+
chunked(:info)
|
77
|
+
end
|
78
|
+
r.get 'tracks' do
|
79
|
+
chunked(:tracks)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
* The path plugin now supports a :by_name option, which makes lookup
|
84
|
+
of the class be by name as opposed to by reference. This is
|
85
|
+
designed for use in development when doing code reloading.
|
86
|
+
|
87
|
+
* Roda.path_block has been added to the path plugin for returning the
|
88
|
+
block associated with the given class.
|
89
|
+
|
90
|
+
= Other Improvements
|
91
|
+
|
92
|
+
* The default_headers plugin now defaults to the same headers that
|
93
|
+
Roda uses by default (just the Content-Type header with text/html
|
94
|
+
value). Previously, it did not set a Content-Type header if you
|
95
|
+
didn't specify one.
|
96
|
+
|
97
|
+
* In the path plugin, Roda#path now works correctly in subclasses.
|
@@ -2,23 +2,50 @@ require 'erubis'
|
|
2
2
|
|
3
3
|
class Roda
|
4
4
|
module RodaPlugins
|
5
|
-
# The _erubis_escaping plugin
|
6
|
-
#
|
5
|
+
# The _erubis_escaping plugin handles escaping of <tt><%= %></tt> inside
|
6
|
+
# ERB templates. It is an internal plugin that should not be loaded
|
7
|
+
# directlyn by user code.
|
7
8
|
module ErubisEscaping
|
8
|
-
#
|
9
|
+
# Escaper which escapes by default, but does not escape instances of
|
10
|
+
# classes marked as safe.
|
11
|
+
class UnsafeClassEscaper
|
12
|
+
# Default escaper if the string needs to be escaped.
|
13
|
+
Escaper = Erubis::XmlHelper
|
14
|
+
|
15
|
+
# Record the classes to consider safe.
|
16
|
+
def initialize(safe_classes)
|
17
|
+
@safe_classes = Array(safe_classes).freeze
|
18
|
+
freeze
|
19
|
+
end
|
20
|
+
|
21
|
+
# If the string given is not an instance of one of the safe
|
22
|
+
# classes, escape it, otherwise return it verbatim. If the
|
23
|
+
# given object is not already a string, convert it to a string first.
|
24
|
+
def escape_xml(string)
|
25
|
+
unless string.is_a?(String)
|
26
|
+
string = string.to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
if @safe_classes.any?{|c| string.is_a?(c)}
|
30
|
+
string
|
31
|
+
else
|
32
|
+
Escaper.escape_xml(string)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Subclass that works with specified escaper, also handling
|
38
|
+
# postfix conditionals inside <tt><%= %></tt> tags.
|
9
39
|
class Eruby < Erubis::EscapedEruby
|
10
|
-
# Set escaping
|
11
|
-
# constant lookup per escape.
|
40
|
+
# Set escaping object to a local variable
|
12
41
|
def convert_input(codebuf, input)
|
13
|
-
codebuf << '
|
42
|
+
codebuf << '_erubis_escaper = render_opts[:escaper];'
|
14
43
|
super
|
15
44
|
end
|
16
45
|
|
17
|
-
#
|
18
|
-
# <%= %> are broken (e.g. <%= foo if bar %> ), and optimize by using
|
19
|
-
# a local variable instead of a constant lookup.
|
46
|
+
# Use escaping object to escape the code, and handle postfix conditionals.
|
20
47
|
def add_expr_escaped(src, code)
|
21
|
-
src << "
|
48
|
+
src << " " << @bufvar << " << _erubis_escaper.escape_xml((" << code << "));"
|
22
49
|
end
|
23
50
|
end
|
24
51
|
end
|
data/lib/roda/plugins/chunked.rb
CHANGED
@@ -37,6 +37,23 @@ class Roda
|
|
37
37
|
# end
|
38
38
|
# end
|
39
39
|
#
|
40
|
+
# You can also call delay manually with a block, and the execution of the
|
41
|
+
# block will be delayed until rendering the content template. This is
|
42
|
+
# useful if you want to delay execution for all routes under a branch:
|
43
|
+
#
|
44
|
+
# r.on 'albums/:d' do |album_id|
|
45
|
+
# delay do
|
46
|
+
# @album = Album[album_id]
|
47
|
+
# end
|
48
|
+
# r.get 'info' do
|
49
|
+
# chunked(:info)
|
50
|
+
# end
|
51
|
+
# r.get 'tracks' do
|
52
|
+
# chunked(:tracks)
|
53
|
+
# end
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
#
|
40
57
|
# If you want to chunk all responses, pass the :chunk_by_default option
|
41
58
|
# when loading the plugin:
|
42
59
|
#
|
@@ -192,15 +209,19 @@ class Roda
|
|
192
209
|
end
|
193
210
|
|
194
211
|
# Render a response to the user in chunks. See Chunked for
|
195
|
-
# an overview.
|
212
|
+
# an overview. If a block is given, it is passed to #delay.
|
196
213
|
def chunked(template, opts=OPTS, &block)
|
197
214
|
unless defined?(@_chunked)
|
198
215
|
@_chunked = env[HTTP_VERSION] == HTTP11
|
199
216
|
end
|
200
217
|
|
218
|
+
if block
|
219
|
+
delay(&block)
|
220
|
+
end
|
221
|
+
|
201
222
|
unless @_chunked
|
202
223
|
# If chunking is disabled, do a normal rendering of the view.
|
203
|
-
|
224
|
+
run_delayed_blocks
|
204
225
|
return view(template, opts)
|
205
226
|
end
|
206
227
|
|
@@ -214,7 +235,7 @@ class Roda
|
|
214
235
|
|
215
236
|
# Hack so that the arguments don't need to be passed
|
216
237
|
# through the response and body objects.
|
217
|
-
@_each_chunk_args = [template, opts
|
238
|
+
@_each_chunk_args = [template, opts]
|
218
239
|
|
219
240
|
res = response
|
220
241
|
headers = res.headers
|
@@ -226,11 +247,18 @@ class Roda
|
|
226
247
|
throw :halt, res.finish_with_body(Body.new(self))
|
227
248
|
end
|
228
249
|
|
250
|
+
# Delay the execution of the block until right before the
|
251
|
+
# content template is to be rendered.
|
252
|
+
def delay(&block)
|
253
|
+
raise RodaError, "must pass a block to Roda#delay" unless block
|
254
|
+
(@_delays ||= []) << block
|
255
|
+
end
|
256
|
+
|
229
257
|
# Yield each chunk of the template rendering separately.
|
230
258
|
def each_chunk
|
231
259
|
response.body.each{|s| yield s}
|
232
260
|
|
233
|
-
template, opts
|
261
|
+
template, opts = @_each_chunk_args
|
234
262
|
|
235
263
|
# Use a lambda for the flusher, so that a call to flush
|
236
264
|
# by a template can result in this method yielding a chunk
|
@@ -243,12 +271,12 @@ class Roda
|
|
243
271
|
if layout_opts = view_layout_opts(opts)
|
244
272
|
@_out_buf = render_template(layout_opts) do
|
245
273
|
flush
|
246
|
-
|
274
|
+
run_delayed_blocks
|
247
275
|
yield opts[:content] || render_template(template, opts)
|
248
276
|
nil
|
249
277
|
end
|
250
278
|
else
|
251
|
-
|
279
|
+
run_delayed_blocks
|
252
280
|
yield view(template, opts)
|
253
281
|
end
|
254
282
|
|
@@ -268,6 +296,14 @@ class Roda
|
|
268
296
|
def flush
|
269
297
|
@_flusher.call if @_flusher
|
270
298
|
end
|
299
|
+
|
300
|
+
private
|
301
|
+
|
302
|
+
# Run all delayed blocks
|
303
|
+
def run_delayed_blocks
|
304
|
+
return unless @_delays
|
305
|
+
@_delays.each(&:call)
|
306
|
+
end
|
271
307
|
end
|
272
308
|
end
|
273
309
|
|
@@ -20,7 +20,7 @@ class Roda
|
|
20
20
|
module DefaultHeaders
|
21
21
|
# Merge the given headers into the existing default headers, if any.
|
22
22
|
def self.configure(app, headers={})
|
23
|
-
app.opts[:default_headers] = (app.
|
23
|
+
app.opts[:default_headers] = (app.default_headers || app::RodaResponse::DEFAULT_HEADERS).merge(headers).freeze
|
24
24
|
end
|
25
25
|
|
26
26
|
module ClassMethods
|
@@ -133,7 +133,7 @@ class Roda
|
|
133
133
|
# Freeze the namespaced routes so that there can be no thread safety issues at runtime.
|
134
134
|
def freeze
|
135
135
|
opts[:namespaced_routes].freeze
|
136
|
-
opts[:namespaced_routes].each_value
|
136
|
+
opts[:namespaced_routes].each_value(&:freeze)
|
137
137
|
super
|
138
138
|
end
|
139
139
|
|
@@ -5,7 +5,9 @@ class Roda
|
|
5
5
|
# rendering supports is supported by this plugin (yet), it
|
6
6
|
# currently handles enough to be a drop in replacement for
|
7
7
|
# some applications.
|
8
|
-
#
|
8
|
+
#
|
9
|
+
# plugin :padrino_render, :views => 'path/2/views'
|
10
|
+
#
|
9
11
|
# Most notably, this makes the +render+ method default to
|
10
12
|
# using the layout, similar to how the +view+ method works
|
11
13
|
# in the render plugin. If you want to call render and not
|
@@ -15,22 +17,14 @@ class Roda
|
|
15
17
|
# render('test') # layout
|
16
18
|
# render('test', :layout=>false) # no layout
|
17
19
|
#
|
18
|
-
#
|
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
|
-
#
|
20
|
+
# Note that this plugin loads the :partials plugin.
|
26
21
|
module PadrinoRender
|
27
22
|
OPTS = {}.freeze
|
28
|
-
SLASH = '/'.freeze
|
29
23
|
|
30
24
|
# Depend on the render plugin, since this overrides
|
31
25
|
# some of its methods.
|
32
26
|
def self.load_dependencies(app, opts=OPTS)
|
33
|
-
app.plugin :
|
27
|
+
app.plugin :partials, opts
|
34
28
|
end
|
35
29
|
|
36
30
|
module InstanceMethods
|
@@ -39,19 +33,6 @@ class Roda
|
|
39
33
|
def render(template, opts=OPTS)
|
40
34
|
view(template, opts)
|
41
35
|
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
36
|
end
|
56
37
|
end
|
57
38
|
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Roda
|
2
|
+
module RodaPlugins
|
3
|
+
# The partials plugin adds a +partial+ method, which renders
|
4
|
+
# templates without the layout.
|
5
|
+
#
|
6
|
+
# plugin :partials, :views => 'path/2/views'
|
7
|
+
#
|
8
|
+
# Template files are prefixed with an underscore:
|
9
|
+
#
|
10
|
+
# partial('test') # uses _test.erb
|
11
|
+
# partial('dir/test') # uses dir/_test.erb
|
12
|
+
#
|
13
|
+
# This is basically equivalent to:
|
14
|
+
#
|
15
|
+
# render('_test')
|
16
|
+
# render('dir/_test')
|
17
|
+
#
|
18
|
+
# Note that this plugin automatically loads the :render plugin.
|
19
|
+
module Partials
|
20
|
+
OPTS = {}.freeze
|
21
|
+
SLASH = '/'.freeze
|
22
|
+
|
23
|
+
# Depend on the render plugin, since this overrides
|
24
|
+
# some of its methods.
|
25
|
+
def self.load_dependencies(app, opts=OPTS)
|
26
|
+
app.plugin :render, opts
|
27
|
+
end
|
28
|
+
|
29
|
+
module InstanceMethods
|
30
|
+
# Renders the given template without a layout, but
|
31
|
+
# prefixes the template filename to use with an
|
32
|
+
# underscore.
|
33
|
+
def partial(template, opts=OPTS)
|
34
|
+
opts = parse_template_opts(template, opts)
|
35
|
+
if opts[:template]
|
36
|
+
template = opts[:template].split(SLASH)
|
37
|
+
template[-1] = "_#{template[-1]}"
|
38
|
+
opts[:template] = template.join(SLASH)
|
39
|
+
end
|
40
|
+
render_template(opts)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
register_plugin(:partials, Partials)
|
46
|
+
end
|
47
|
+
end
|
data/lib/roda/plugins/path.rb
CHANGED
@@ -61,11 +61,15 @@ class Roda
|
|
61
61
|
DEFAULT_PORTS = {'http' => 80, 'https' => 443}.freeze
|
62
62
|
OPTS = {}.freeze
|
63
63
|
|
64
|
-
# Initialize the path classes when loading the plugin
|
65
|
-
|
64
|
+
# Initialize the path classes when loading the plugin. Options:
|
65
|
+
# :by_name :: Register classes by name, which is friendlier when reloading code.
|
66
|
+
def self.configure(app, opts=OPTS)
|
66
67
|
app.instance_eval do
|
67
|
-
|
68
|
-
|
68
|
+
if opts.has_key?(:by_name)
|
69
|
+
self.opts[:path_class_by_name] = opts[:by_name]
|
70
|
+
end
|
71
|
+
self.opts[:path_classes] ||= {}
|
72
|
+
unless path_block(String)
|
69
73
|
path(String){|str| str}
|
70
74
|
end
|
71
75
|
end
|
@@ -73,11 +77,13 @@ class Roda
|
|
73
77
|
|
74
78
|
module ClassMethods
|
75
79
|
# Hash of recognizes classes for path instance method. Keys are classes, values are procs.
|
76
|
-
|
80
|
+
def path_classes
|
81
|
+
opts[:path_classes]
|
82
|
+
end
|
77
83
|
|
78
84
|
# Freeze the path classes when freezing the app.
|
79
85
|
def freeze
|
80
|
-
|
86
|
+
path_classes.freeze
|
81
87
|
super
|
82
88
|
end
|
83
89
|
|
@@ -86,7 +92,10 @@ class Roda
|
|
86
92
|
if name.is_a?(Class)
|
87
93
|
raise RodaError, "can't provide path or options when calling path with a class" unless path.nil? && opts.empty?
|
88
94
|
raise RodaError, "must provide a block when calling path with a class" unless block
|
89
|
-
|
95
|
+
if self.opts[:path_class_by_name]
|
96
|
+
name = name.name
|
97
|
+
end
|
98
|
+
path_classes[name] = block
|
90
99
|
return
|
91
100
|
end
|
92
101
|
|
@@ -145,6 +154,14 @@ class Roda
|
|
145
154
|
|
146
155
|
nil
|
147
156
|
end
|
157
|
+
|
158
|
+
# Return the block related to the given class, or nil if there is no block.
|
159
|
+
def path_block(klass)
|
160
|
+
if opts[:path_class_by_name]
|
161
|
+
klass = klass.name
|
162
|
+
end
|
163
|
+
path_classes[klass]
|
164
|
+
end
|
148
165
|
end
|
149
166
|
|
150
167
|
module InstanceMethods
|
@@ -153,13 +170,13 @@ class Roda
|
|
153
170
|
# :add_script_name option is true, this prepends the SCRIPT_NAME to the path.
|
154
171
|
def path(obj, *args)
|
155
172
|
app = self.class
|
156
|
-
|
157
|
-
path = instance_exec(obj, *args, &blk)
|
158
|
-
path = request.script_name.to_s + path if app.opts[:add_script_name]
|
159
|
-
path
|
160
|
-
else
|
173
|
+
unless blk = app.path_block(obj.class)
|
161
174
|
raise RodaError, "unrecognized object given to Roda#path: #{obj.inspect}"
|
162
175
|
end
|
176
|
+
|
177
|
+
path = instance_exec(obj, *args, &blk)
|
178
|
+
path = request.script_name.to_s + path if app.opts[:add_script_name]
|
179
|
+
path
|
163
180
|
end
|
164
181
|
end
|
165
182
|
end
|
data/lib/roda/plugins/render.rb
CHANGED
@@ -31,6 +31,11 @@ class Roda
|
|
31
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
|
+
# :escape_safe_classes :: String subclasses that should not be HTML escaped when used in
|
35
|
+
# <tt><%= %></tt> tags, when :escape is used. Can be an array for multiple classes.
|
36
|
+
# :escaper :: Object used for escaping output of <tt><%= %></tt>, when :escape is used,
|
37
|
+
# overriding the default. If given, object should respond to +escape_xml+ with
|
38
|
+
# a single argument and return an output string.
|
34
39
|
# :ext :: The file extension to assume for view files, defaults to the :engine
|
35
40
|
# option.
|
36
41
|
# :layout :: The base name of the layout file, defaults to 'layout'.
|
@@ -119,6 +124,12 @@ class Roda
|
|
119
124
|
end
|
120
125
|
if opts[:escape]
|
121
126
|
template_opts[:engine_class] = ErubisEscaping::Eruby
|
127
|
+
|
128
|
+
opts[:escaper] ||= if opts[:escape_safe_classes]
|
129
|
+
ErubisEscaping::UnsafeClassEscaper.new(opts[:escape_safe_classes])
|
130
|
+
else
|
131
|
+
::Erubis::XmlHelper
|
132
|
+
end
|
122
133
|
end
|
123
134
|
opts[:cache] = app.thread_safe_cache if opts.fetch(:cache, ENV['RACK_ENV'] != 'development')
|
124
135
|
opts[:layout_opts].freeze
|
@@ -0,0 +1,85 @@
|
|
1
|
+
class Roda
|
2
|
+
module RodaPlugins
|
3
|
+
# The shared_vars plugin adds a shared method for storing
|
4
|
+
# shared variables across nested Roda apps.
|
5
|
+
#
|
6
|
+
# class API < Roda
|
7
|
+
# plugin :shared_vars
|
8
|
+
# route do |r|
|
9
|
+
# user = shared[:user]
|
10
|
+
# # ...
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# class App < Roda
|
15
|
+
# plugin :shared_vars
|
16
|
+
#
|
17
|
+
# route do |r|
|
18
|
+
# r.on :user_id do |user_id|
|
19
|
+
# shared[:user] = User[user_id]
|
20
|
+
# r.run API
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# If you pass a hash to shared, it will update the shared
|
26
|
+
# vars with the content of the hash:
|
27
|
+
#
|
28
|
+
# route do |r|
|
29
|
+
# r.on :user_id do |user_id|
|
30
|
+
# shared(:user => User[user_id])
|
31
|
+
# r.run API
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# You can also pass a block to shared, which will set the
|
36
|
+
# shared variables only for the given block, restoring the
|
37
|
+
# previous shared variables afterward:
|
38
|
+
#
|
39
|
+
# route do |r|
|
40
|
+
# r.on :user_id do |user_id|
|
41
|
+
# shared(:user => User[user_id]) do
|
42
|
+
# r.run API
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
module SharedVars
|
47
|
+
KEY = 'roda.shared'.freeze
|
48
|
+
|
49
|
+
module InstanceMethods
|
50
|
+
# Returns the current shared vars for the request. These are
|
51
|
+
# stored in the request's environment, so they will be implicitly
|
52
|
+
# shared with other apps using this plugin.
|
53
|
+
#
|
54
|
+
# If the +vars+ argument is given, it should be a hash that will be
|
55
|
+
# merged into the current shared vars.
|
56
|
+
#
|
57
|
+
# If a block is given, a +vars+ argument must be provided, and it will
|
58
|
+
# only make the changes to the shared vars for the duration of the
|
59
|
+
# block, restoring the previous shared vars before the block returns.
|
60
|
+
def shared(vars=nil)
|
61
|
+
h = env[KEY] ||= {}
|
62
|
+
|
63
|
+
if block_given?
|
64
|
+
if vars
|
65
|
+
begin
|
66
|
+
env[KEY] = h.merge(vars)
|
67
|
+
yield
|
68
|
+
ensure
|
69
|
+
env[KEY] = h
|
70
|
+
end
|
71
|
+
else
|
72
|
+
raise RodaError, "must pass a vars hash when calling shared with a block"
|
73
|
+
end
|
74
|
+
elsif vars
|
75
|
+
h.merge!(vars)
|
76
|
+
end
|
77
|
+
|
78
|
+
h
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
register_plugin(:shared_vars, SharedVars)
|
84
|
+
end
|
85
|
+
end
|
data/lib/roda/version.rb
CHANGED
@@ -12,7 +12,7 @@ rescue LoadError
|
|
12
12
|
warn "tilt or erubis not installed, skipping _erubis_escaping plugin test"
|
13
13
|
else
|
14
14
|
describe "_erubis_escaping plugin" do
|
15
|
-
|
15
|
+
it "should escape inside <%= %> and not inside <%== %>, and handle postfix conditionals" do
|
16
16
|
app(:bare) do
|
17
17
|
plugin :render, :escape=>true
|
18
18
|
|
@@ -20,10 +20,39 @@ describe "_erubis_escaping plugin" do
|
|
20
20
|
render(:inline=>'<%= "<>" %> <%== "<>" %><%= "<>" if false %>')
|
21
21
|
end
|
22
22
|
end
|
23
|
+
|
24
|
+
body.should == '<> <>'
|
23
25
|
end
|
24
26
|
|
25
|
-
it "should
|
27
|
+
it "should consider classes in :escape_safe_classes as safe" do
|
28
|
+
c = Class.new(String)
|
29
|
+
c2 = Class.new(String)
|
30
|
+
app(:bare) do
|
31
|
+
plugin :render, :escape=>true, :escape_safe_classes=>c
|
32
|
+
|
33
|
+
route do |r|
|
34
|
+
@c, @c2 = c, c2
|
35
|
+
render(:inline=>'<%= @c2.new("<>") %> <%= @c.new("<>") %>')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
26
39
|
body.should == '<> <>'
|
27
40
|
end
|
41
|
+
|
42
|
+
it "should allow use of custom :escaper" do
|
43
|
+
escaper = Object.new
|
44
|
+
def escaper.escape_xml(s)
|
45
|
+
s.gsub("'", "''")
|
46
|
+
end
|
47
|
+
app(:bare) do
|
48
|
+
plugin :render, :escape=>true, :escaper=>escaper
|
49
|
+
|
50
|
+
route do |r|
|
51
|
+
render(:inline=>'<%= "ab\'1" %> <%== "ab\'1" %>')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
body.should == "ab''1 ab'1"
|
56
|
+
end
|
28
57
|
end
|
29
58
|
end
|
data/spec/plugin/chunked_spec.rb
CHANGED
@@ -7,9 +7,7 @@ rescue LoadError
|
|
7
7
|
else
|
8
8
|
describe "chunked plugin" do
|
9
9
|
def cbody(env={})
|
10
|
-
|
11
|
-
req({'HTTP_VERSION'=>'HTTP/1.1'}.merge(env))[2].each{|s| b << s}
|
12
|
-
b
|
10
|
+
body({'HTTP_VERSION'=>'HTTP/1.1'}.merge(env))
|
13
11
|
end
|
14
12
|
|
15
13
|
it "streams templates in chunked encoding only if HTTP 1.1 is used" do
|
@@ -42,6 +40,26 @@ describe "chunked plugin" do
|
|
42
40
|
body.should == "hmht"
|
43
41
|
end
|
44
42
|
|
43
|
+
it "has delay accept block that is called after layout yielding but before content when streaming" do
|
44
|
+
app(:chunked) do |r|
|
45
|
+
delay do
|
46
|
+
@h << 'i'
|
47
|
+
end
|
48
|
+
@h = 'h'
|
49
|
+
chunked(:inline=>'m<%= @h %>', :layout=>{:inline=>'<%= @h %><%= yield %>t'}) do
|
50
|
+
@h << 'j'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
cbody.should == "1\r\nh\r\n4\r\nmhij\r\n1\r\nt\r\n0\r\n\r\n"
|
55
|
+
body.should == "hijmhijt"
|
56
|
+
end
|
57
|
+
|
58
|
+
it "has delay raise if not given a block" do
|
59
|
+
app(:chunked){|r| delay}
|
60
|
+
proc{body}.should raise_error(Roda::RodaError)
|
61
|
+
end
|
62
|
+
|
45
63
|
it "works when a layout is not used" do
|
46
64
|
app(:chunked) do |r|
|
47
65
|
chunked(:inline=>'m', :layout=>nil)
|
@@ -43,6 +43,20 @@ describe "default_headers plugin" do
|
|
43
43
|
req[1].should == {'Content-Type'=>'text/json', 'Foo'=>'baz'}
|
44
44
|
end
|
45
45
|
|
46
|
+
it "should have a default Content-Type header" do
|
47
|
+
h = {'Foo'=>'bar'}
|
48
|
+
|
49
|
+
app(:bare) do
|
50
|
+
plugin :default_headers, h
|
51
|
+
|
52
|
+
route do |r|
|
53
|
+
r.halt response.finish_with_body([])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
req[1].should == {'Content-Type'=>'text/html', 'Foo'=>'bar'}
|
58
|
+
end
|
59
|
+
|
46
60
|
it "should work correctly in subclasses" do
|
47
61
|
h = {'Content-Type'=>'text/json', 'Foo'=>'bar'}
|
48
62
|
|
@@ -11,18 +11,7 @@ describe "padrino_render plugin" do
|
|
11
11
|
plugin :padrino_render, :views=>"./spec/views"
|
12
12
|
|
13
13
|
route do |r|
|
14
|
-
|
15
|
-
partial("test", :locals=>{:title => "About Roda"})
|
16
|
-
end
|
17
|
-
|
18
|
-
r.is "partial/subdir" do
|
19
|
-
partial("about/test", :locals=>{:title => "About Roda"})
|
20
|
-
end
|
21
|
-
|
22
|
-
r.is "partial/inline" do
|
23
|
-
partial(:inline=>"Hello <%= name %>", :locals=>{:name => "Agent Smith"})
|
24
|
-
end
|
25
|
-
|
14
|
+
|
26
15
|
r.is "render" do
|
27
16
|
render(:content=>'bar', :layout_opts=>{:locals=>{:title=>"Home"}})
|
28
17
|
end
|
@@ -34,18 +23,6 @@ describe "padrino_render plugin" do
|
|
34
23
|
end
|
35
24
|
end
|
36
25
|
|
37
|
-
it "partial renders without layout, and prepends _ to template" do
|
38
|
-
body("/partial").strip.should == "<h1>About Roda</h1>"
|
39
|
-
end
|
40
|
-
|
41
|
-
it "partial renders without layout, and prepends _ to template" do
|
42
|
-
body("/partial/subdir").strip.should == "<h1>Subdir: About Roda</h1>"
|
43
|
-
end
|
44
|
-
|
45
|
-
it "partial handles inline partials" do
|
46
|
-
body("/partial/inline").strip.should == "Hello Agent Smith"
|
47
|
-
end
|
48
|
-
|
49
26
|
it "render uses layout by default" do
|
50
27
|
body("/render").strip.should == "<title>Roda: Home</title>\nbar"
|
51
28
|
end
|
@@ -0,0 +1,43 @@
|
|
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 partials plugin test"
|
7
|
+
else
|
8
|
+
describe "partials plugin" do
|
9
|
+
before do
|
10
|
+
app(:bare) do
|
11
|
+
plugin :partials, :views=>"./spec/views"
|
12
|
+
|
13
|
+
route do |r|
|
14
|
+
r.is "partial" do
|
15
|
+
partial("test", :locals=>{:title => "About Roda"})
|
16
|
+
end
|
17
|
+
|
18
|
+
r.is "partial/subdir" do
|
19
|
+
partial("about/test", :locals=>{:title => "About Roda"})
|
20
|
+
end
|
21
|
+
|
22
|
+
r.is "partial/inline" do
|
23
|
+
partial(:inline=>"Hello <%= name %>", :locals=>{:name => "Agent Smith"})
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it "partial renders without layout, and prepends _ to template" do
|
31
|
+
body("/partial").strip.should == "<h1>About Roda</h1>"
|
32
|
+
end
|
33
|
+
|
34
|
+
it "partial renders without layout, and prepends _ to template" do
|
35
|
+
body("/partial/subdir").strip.should == "<h1>Subdir: About Roda</h1>"
|
36
|
+
end
|
37
|
+
|
38
|
+
it "partial handles inline partials" do
|
39
|
+
body("/partial/inline").strip.should == "Hello Agent Smith"
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
data/spec/plugin/path_spec.rb
CHANGED
@@ -152,6 +152,49 @@ describe "path plugin" do
|
|
152
152
|
body('path'=>[@obj, 'foo', 'bar'], 'SCRIPT_NAME'=>'/baz').should == '/baz/d/1/foo/bar'
|
153
153
|
end
|
154
154
|
|
155
|
+
it "Roda#path works in subclasses" do
|
156
|
+
old_app = @app
|
157
|
+
@app = Class.new(@app)
|
158
|
+
@app.route{|r| path('/a')}
|
159
|
+
body.should == '/a'
|
160
|
+
|
161
|
+
@app.path(String){|b| "/foo#{b}"}
|
162
|
+
body.should == '/foo/a'
|
163
|
+
|
164
|
+
@app = old_app
|
165
|
+
body('path'=>'/a').should == '/a'
|
166
|
+
end
|
167
|
+
|
168
|
+
it "registers classes by reference by default" do
|
169
|
+
c1 = Class.new
|
170
|
+
def c1.name; 'C'; end
|
171
|
+
c2 = Class.new
|
172
|
+
def c2.name; 'C'; end
|
173
|
+
@app.path(c1){'/c'}
|
174
|
+
@app.route{|r| path(r.env['c'])}
|
175
|
+
body('c'=>c1.new).should == '/c'
|
176
|
+
proc{body('c'=>c2.new)}.should raise_error(Roda::RodaError)
|
177
|
+
end
|
178
|
+
|
179
|
+
it ":by_name => option registers classes by name" do
|
180
|
+
c1 = Class.new
|
181
|
+
def c1.name; 'C'; end
|
182
|
+
c2 = Class.new
|
183
|
+
def c2.name; 'C'; end
|
184
|
+
@app.plugin :path, :by_name=>true
|
185
|
+
@app.path(c1){'/c'}
|
186
|
+
@app.route{|r| path(r.env['c'])}
|
187
|
+
body('c'=>c1.new).should == '/c'
|
188
|
+
body('c'=>c2.new).should == '/c'
|
189
|
+
end
|
190
|
+
|
191
|
+
it "Roda.path_block returns the block used" do
|
192
|
+
c = Class.new
|
193
|
+
b = proc{|x| x.to_s}
|
194
|
+
@app.path(c, &b)
|
195
|
+
@app.path_block(c).should == b
|
196
|
+
end
|
197
|
+
|
155
198
|
it "Roda.path doesn't work with classes without blocks" do
|
156
199
|
proc{app.path(Class.new)}.should raise_error(Roda::RodaError)
|
157
200
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
describe "shared_vars plugin" do
|
4
|
+
it "adds shared method for sharing variables across multiple apps" do
|
5
|
+
app(:shared_vars) {|r| shared[:c]}
|
6
|
+
old_app = app
|
7
|
+
app(:shared_vars) do |r|
|
8
|
+
shared[:c] = 'c'
|
9
|
+
r.run old_app
|
10
|
+
end
|
11
|
+
|
12
|
+
body.should == 'c'
|
13
|
+
end
|
14
|
+
|
15
|
+
it "adds shared with hash merges the hash into the shared vars" do
|
16
|
+
app(:shared_vars) do |r|
|
17
|
+
shared(:c=>'c')
|
18
|
+
shared[:c]
|
19
|
+
end
|
20
|
+
|
21
|
+
body.should == 'c'
|
22
|
+
end
|
23
|
+
|
24
|
+
it "calling shared with hash and a block sets shared variables only for that block" do
|
25
|
+
app(:shared_vars) do |r|
|
26
|
+
c = nil
|
27
|
+
d = nil
|
28
|
+
shared[:c] = 'b'
|
29
|
+
shared(:c=>'c', :d=>'d') do
|
30
|
+
c = shared[:c]
|
31
|
+
d = shared[:d]
|
32
|
+
end
|
33
|
+
"#{shared[:c]}:#{shared[:d]}:#{c}:#{d}"
|
34
|
+
end
|
35
|
+
|
36
|
+
body.should == 'b::c:d'
|
37
|
+
end
|
38
|
+
|
39
|
+
it "calling shared with no arguments and a block raises an error" do
|
40
|
+
app(:shared_vars) do |r|
|
41
|
+
shared{}
|
42
|
+
end
|
43
|
+
proc{body}.should raise_error(Roda::RodaError)
|
44
|
+
end
|
45
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: roda
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Evans
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-04-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -138,6 +138,7 @@ extra_rdoc_files:
|
|
138
138
|
- doc/release_notes/1.3.0.txt
|
139
139
|
- doc/release_notes/2.0.0.txt
|
140
140
|
- doc/release_notes/2.1.0.txt
|
141
|
+
- doc/release_notes/2.2.0.txt
|
141
142
|
files:
|
142
143
|
- CHANGELOG
|
143
144
|
- MIT-LICENSE
|
@@ -150,6 +151,7 @@ files:
|
|
150
151
|
- doc/release_notes/1.3.0.txt
|
151
152
|
- doc/release_notes/2.0.0.txt
|
152
153
|
- doc/release_notes/2.1.0.txt
|
154
|
+
- doc/release_notes/2.2.0.txt
|
153
155
|
- lib/roda.rb
|
154
156
|
- lib/roda/plugins/_erubis_escaping.rb
|
155
157
|
- lib/roda/plugins/all_verbs.rb
|
@@ -190,12 +192,14 @@ files:
|
|
190
192
|
- lib/roda/plugins/not_found.rb
|
191
193
|
- lib/roda/plugins/padrino_render.rb
|
192
194
|
- lib/roda/plugins/param_matchers.rb
|
195
|
+
- lib/roda/plugins/partials.rb
|
193
196
|
- lib/roda/plugins/pass.rb
|
194
197
|
- lib/roda/plugins/path.rb
|
195
198
|
- lib/roda/plugins/path_matchers.rb
|
196
199
|
- lib/roda/plugins/per_thread_caching.rb
|
197
200
|
- lib/roda/plugins/render.rb
|
198
201
|
- lib/roda/plugins/render_each.rb
|
202
|
+
- lib/roda/plugins/shared_vars.rb
|
199
203
|
- lib/roda/plugins/sinatra_helpers.rb
|
200
204
|
- lib/roda/plugins/slash_path_empty.rb
|
201
205
|
- lib/roda/plugins/static.rb
|
@@ -255,12 +259,14 @@ files:
|
|
255
259
|
- spec/plugin/not_found_spec.rb
|
256
260
|
- spec/plugin/padrino_render_spec.rb
|
257
261
|
- spec/plugin/param_matchers_spec.rb
|
262
|
+
- spec/plugin/partials_spec.rb
|
258
263
|
- spec/plugin/pass_spec.rb
|
259
264
|
- spec/plugin/path_matchers_spec.rb
|
260
265
|
- spec/plugin/path_spec.rb
|
261
266
|
- spec/plugin/per_thread_caching_spec.rb
|
262
267
|
- spec/plugin/render_each_spec.rb
|
263
268
|
- spec/plugin/render_spec.rb
|
269
|
+
- spec/plugin/shared_vars_spec.rb
|
264
270
|
- spec/plugin/sinatra_helpers_spec.rb
|
265
271
|
- spec/plugin/slash_path_empty_spec.rb
|
266
272
|
- spec/plugin/static_spec.rb
|