roda 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|