roda 2.16.0 → 2.17.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +14 -0
- data/README.rdoc +57 -13
- data/doc/release_notes/2.17.0.txt +62 -0
- data/lib/roda/plugins/assets.rb +51 -9
- data/lib/roda/plugins/error_handler.rb +13 -2
- data/lib/roda/plugins/match_affix.rb +4 -3
- data/lib/roda/plugins/path_rewriter.rb +2 -2
- data/lib/roda/plugins/render.rb +51 -10
- data/lib/roda/plugins/run_append_slash.rb +52 -0
- data/lib/roda/plugins/type_routing.rb +11 -1
- data/lib/roda/version.rb +1 -1
- data/spec/plugin/assets_spec.rb +51 -1
- data/spec/plugin/error_handler_spec.rb +25 -0
- data/spec/plugin/match_affix_spec.rb +21 -0
- data/spec/plugin/render_spec.rb +28 -0
- data/spec/plugin/run_append_slash_spec.rb +77 -0
- data/spec/plugin/type_routing_spec.rb +12 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f728447f69ff9f50896fb6dca7a0e709f2df26e1
|
4
|
+
data.tar.gz: 521dd8fa68fe85421eab4d410619991da0655cb8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 150c34299dd44322d1d36523f83aa948a61740b8b0bc5374de85ce94feb67ab9ced97262b7982330aa91d1433362ace6bab7a1924689a3dd99b3576db478fdc2
|
7
|
+
data.tar.gz: 68d485ef5b650992d224ac98af823153d25bc7c4ff9fb71d087a44f7c792f9a2431d2d93722433ebf553ad3f387d74848125f88144ef88dad52dfdaf50dab13b
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
= 2.17.0 (2016-08-13)
|
2
|
+
|
3
|
+
* Add :postprocessor option to assets plugin, for postprocessing assets (e.g. autoprefixing CSS) (celsworth) (#86)
|
4
|
+
|
5
|
+
* Fix path passed to rack apps when using r.run and the type_routing plugin (jeremyevans) (#82)
|
6
|
+
|
7
|
+
* Support :classes option to error_handler plugin for overriding which exception classes to rescue (jeremyevans)
|
8
|
+
|
9
|
+
* Support :layout_opts=>:merge_locals option in render plugin for merging view template locals into layout template locals (jeremyevans) (#80)
|
10
|
+
|
11
|
+
* Support :sri option to assets plugin to enable subresource integrity (jeremyevans)
|
12
|
+
|
13
|
+
* Add run_append_slash plugin, so r.run uses "/" instead of "" for app's PATH_INFO (kenaniah) (#77)
|
14
|
+
|
1
15
|
= 2.16.0 (2016-07-13)
|
2
16
|
|
3
17
|
* Add type_routing plugin, for routing based on path extensions and Accept headers (Papierkorb, jeremyevans) (#75)
|
data/README.rdoc
CHANGED
@@ -46,7 +46,7 @@ to get the default behavior.
|
|
46
46
|
|
47
47
|
Roda has low per-request overhead, and the use of a routing tree
|
48
48
|
and intelligent caching of internal datastructures makes it
|
49
|
-
significantly faster than popular ruby web frameworks.
|
49
|
+
significantly faster than other popular ruby web frameworks.
|
50
50
|
|
51
51
|
== Usage
|
52
52
|
|
@@ -443,6 +443,15 @@ via the +status+ attribute for the response.
|
|
443
443
|
end
|
444
444
|
end
|
445
445
|
|
446
|
+
When redirecting, the response will use a 302 status code by default.
|
447
|
+
You can change this by passing a second argument to +r.redirect+:
|
448
|
+
|
449
|
+
route do |r|
|
450
|
+
r.get "hello" do
|
451
|
+
r.redirect "/other", 301 # use 301 Moved Permanently
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
446
455
|
== Verb Methods
|
447
456
|
|
448
457
|
As displayed above, Roda has +r.get+ and +r.post+ methods
|
@@ -601,8 +610,8 @@ and still have access to them inside the +api+ +route+ block.
|
|
601
610
|
|
602
611
|
It is very easy to test Roda with {Rack::Test}[https://github.com/brynary/rack-test]
|
603
612
|
or {Capybara}[https://github.com/jnicklas/capybara].
|
604
|
-
Roda's own tests use {
|
605
|
-
The default Rake task will run the specs for Roda
|
613
|
+
Roda's own tests use {minitest/spec}[https://github.com/seattlerb/minitest].
|
614
|
+
The default Rake task will run the specs for Roda.
|
606
615
|
|
607
616
|
== Settings
|
608
617
|
|
@@ -743,7 +752,7 @@ to escape by default, so that in your templates:
|
|
743
752
|
<%= '<>' %> # outputs <>
|
744
753
|
<%== '<>' %> # outputs <>
|
745
754
|
|
746
|
-
When using the +:
|
755
|
+
When using the +:escape+ option, you will need to ensure that your layouts
|
747
756
|
are not escaping the output of the content template:
|
748
757
|
|
749
758
|
<%== yield %> # not <%= yield %>
|
@@ -809,15 +818,50 @@ Roda 3. Note that when specifying the +:path+ option when rendering a template,
|
|
809
818
|
not check paths, as it assumes that users and libraries that use this option will be checking
|
810
819
|
such paths manually.
|
811
820
|
|
812
|
-
== Reloading
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
+
== Code Reloading
|
822
|
+
|
823
|
+
Roda does not ship with integrated support for code reloading, as it is a toolkit and not a
|
824
|
+
framework, but there are rack-based reloaders that will work with Roda apps.
|
825
|
+
|
826
|
+
For most applications, {rack-unreloader}[https://github.com/jeremyevans/rack-unreloader]
|
827
|
+
is probably the fastest approach to reloading while still being fairly safe, as it
|
828
|
+
reloads just files that have been modified, and unloads constants defined in the files
|
829
|
+
before reloading them. However, it requires modifying your application code to use
|
830
|
+
rack-unreloader specific APIs.
|
831
|
+
|
832
|
+
A similar solution that reloads files and unloads constants is ActiveSupport::Dependencies.
|
833
|
+
ActiveSupport::Dependencies doesn't require modifying your application code, but it modifies
|
834
|
+
some core methods, including +require+ and +const_missing+. It requires less configuration,
|
835
|
+
but depends that you follow Rails' file and class naming conventions. It also provides
|
836
|
+
autoloading (on the fly) of files when a missing constant is accessed. If your application
|
837
|
+
does not rely on autoloading then +require_dependency+ must be used to require the dependencies
|
838
|
+
or they won't be reloaded.
|
839
|
+
|
840
|
+
{AutoReloader}[https://github.com/rosenfeld/auto_reloader] provides transparent reloading for
|
841
|
+
all files reached from one of the +reloadable_paths+ option entries, by detecting new top-level
|
842
|
+
constants and removing them when any of the reloadable loaded files changes. It overrides
|
843
|
+
+require+ and +require_relative+ when activated (usually in the development environment). No
|
844
|
+
configurations other than +reloadable_paths+ are required.
|
845
|
+
|
846
|
+
Both {rerun}[https://github.com/alexch/rerun] and
|
847
|
+
{shotgun}[https://github.com/rtomayko/shotgun] use a fork/exec approach for loading new
|
848
|
+
versions of your app. rerun is faster as it only reloads the app on changes, whereas
|
849
|
+
shotgun reloads the app on every request. Both work without any changes to application
|
850
|
+
code, but may be slower as they have to reload the entire application on every change.
|
851
|
+
However, for small apps that load quickly, either may be a good approach.
|
852
|
+
|
853
|
+
{Rack::Reloader}[https://github.com/rack/rack/blob/master/lib/rack/reloader.rb] ships
|
854
|
+
with rack and just reloads monitored files when they change, without unloading constants.
|
855
|
+
It's fast but may cause issues in cases where you remove classes, constants, or methods,
|
856
|
+
or when you are not clearing out cached data manually when files are reloaded.
|
857
|
+
|
858
|
+
There is no one reloading solution that is the best for all applications and development
|
859
|
+
approaches. Consider your needs and the the tradeoffs of each of the reloading approaches,
|
860
|
+
and pick the one you think will work best.
|
861
|
+
|
862
|
+
If you are unsure where to start, it may be best to start with rerun or shotgun
|
863
|
+
(unless you're running on JRuby or Windows), and only consider other options if rerun or
|
864
|
+
shotgun are not fast enough.
|
821
865
|
|
822
866
|
== Plugins
|
823
867
|
|
@@ -0,0 +1,62 @@
|
|
1
|
+
= New Plugins
|
2
|
+
|
3
|
+
* A run_append_slash plugin has been added, which automatically uses
|
4
|
+
"/" as the path instead of an empty path when calling rack
|
5
|
+
applications with r.run:
|
6
|
+
|
7
|
+
route do |r|
|
8
|
+
r.on "a" do
|
9
|
+
r.run App
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# without run_append_slash:
|
14
|
+
# GET /a => App gets "" as PATH_INFO
|
15
|
+
# GET /a/ => App gets "/" as PATH_INFO
|
16
|
+
|
17
|
+
# with run_append_slash:
|
18
|
+
# GET /a => App gets "/" as PATH_INFO
|
19
|
+
# GET /a/ => App gets "/" as PATH_INFO
|
20
|
+
|
21
|
+
By default, the path is modified directly, but if you want to
|
22
|
+
redirect instead, you can pass a :use_redirects option when loading
|
23
|
+
the plugin.
|
24
|
+
|
25
|
+
= New Features
|
26
|
+
|
27
|
+
* The assets plugin now supports an :sri option to enable subresource
|
28
|
+
integrity on the link/script tags generated by the assets helper.
|
29
|
+
This option takes either :sha256, :sha384, or :sha512 values,
|
30
|
+
specifying the hash algorithm to use. Roda 3 will default to using
|
31
|
+
:sri => :sha256.
|
32
|
+
|
33
|
+
* The assets plugin now supports a :postprocessor option, which should
|
34
|
+
be a callable object. If the option is given, it will be called with
|
35
|
+
the filename, type, and rendered asset output of the file (CSS/JS),
|
36
|
+
and should return the postprocessed content to use. This allows any
|
37
|
+
type of custom postprocessing to be done, such as CSS autoprefixing.
|
38
|
+
|
39
|
+
* The render plugin now supports a :layout_opts=>:merge_locals option,
|
40
|
+
which will automatically merge view template locals into layout
|
41
|
+
template locals. This is useful if you want to use the same local
|
42
|
+
variable for both templates.
|
43
|
+
|
44
|
+
* The error_handler plugin now supports a :classes option, allowing
|
45
|
+
you to override which exception classes to handle. This allows it
|
46
|
+
to be used with libraries which use exception classes that
|
47
|
+
subclass from Exception instead of StandardError.
|
48
|
+
|
49
|
+
= Other Improvements
|
50
|
+
|
51
|
+
* The type_routing plugin now works correctly when using r.run.
|
52
|
+
Previously, if the type routing plugin recognized and removed
|
53
|
+
the file extension used in the requested path, it would not
|
54
|
+
add the file extension back to the path when passing the request
|
55
|
+
to another rack app.
|
56
|
+
|
57
|
+
= Backwards Compatibility
|
58
|
+
|
59
|
+
* In the assets plugin, Roda::RodaRequest.assets_matchers now uses
|
60
|
+
symbols instead of strings as the first argument in each entry.
|
61
|
+
This should not affect you unless you were accessing the values
|
62
|
+
directly.
|
data/lib/roda/plugins/assets.rb
CHANGED
@@ -182,6 +182,26 @@ class Roda
|
|
182
182
|
# and it will run in compiled mode, assuming that the compiled asset files
|
183
183
|
# already exist.
|
184
184
|
#
|
185
|
+
# ==== Postprocessing
|
186
|
+
#
|
187
|
+
# If you pass a callable object to the :postprocessor option, it will be called
|
188
|
+
# before an asset is served.
|
189
|
+
# If the assets are to be compiled, the object will be called at compilation time.
|
190
|
+
#
|
191
|
+
# It is passed three arguments; the name of the asset file, the type of the
|
192
|
+
# asset file (which is a symbol, either :css or :js), and the asset contents.
|
193
|
+
#
|
194
|
+
# It should return the new content for the asset.
|
195
|
+
#
|
196
|
+
# You can use this to call Autoprefixer on your CSS:
|
197
|
+
#
|
198
|
+
# plugin :assets, {
|
199
|
+
# :css => [ 'style.scss' ],
|
200
|
+
# :postprocessor => lambda do |file, type, content|
|
201
|
+
# type == :css ? AutoprefixerRails.process(content).css : content
|
202
|
+
# end
|
203
|
+
# }
|
204
|
+
#
|
185
205
|
# ==== On Heroku
|
186
206
|
#
|
187
207
|
# Heroku supports precompiling the assets when using Roda. You just need to
|
@@ -245,12 +265,19 @@ class Roda
|
|
245
265
|
# :js_route :: Route under :prefix for javascript assets (default: :js_dir)
|
246
266
|
# :path :: Path to your asset source directory (default: 'assets'). Relative
|
247
267
|
# paths will be considered relative to the application's :root option.
|
268
|
+
# :postprocessor :: A block which should accept three arguments (asset name, asset type,
|
269
|
+
# content). This block can be used to hook into the asset system and
|
270
|
+
# make your own modifications before the asset is served. If the asset
|
271
|
+
# is to be compiled, the block is called at compile time.
|
248
272
|
# :prefix :: Prefix for assets path in your URL/routes (default: 'assets')
|
249
273
|
# :precompiled :: Path to the compiled asset metadata file. If the file exists, will use compiled
|
250
274
|
# mode using the metadata in the file. If the file does not exist, will use
|
251
275
|
# non-compiled mode, but will write the metadata to the file if compile_assets is called.
|
252
276
|
# :public :: Path to your public folder, in which compiled files are placed (default: 'public'). Relative
|
253
277
|
# paths will be considered relative to the application's :root option.
|
278
|
+
# :sri :: Enables subresource integrity when setting up references to compiled assets. The value should be
|
279
|
+
# :sha256, :sha384, or :sha512 depending on which hash algorithm you want to use. This changes the
|
280
|
+
# hash algorithm that Roda will use when naming compiled asset files.
|
254
281
|
module Assets
|
255
282
|
DEFAULTS = {
|
256
283
|
:compiled_name => 'app'.freeze,
|
@@ -546,8 +573,15 @@ class Roda
|
|
546
573
|
# a different digest type or to return a static string if you don't
|
547
574
|
# want to use a unique value.
|
548
575
|
def asset_digest(content)
|
549
|
-
|
550
|
-
|
576
|
+
klass = if algo = assets_opts[:sri]
|
577
|
+
require 'digest/sha2'
|
578
|
+
::Digest.const_get(algo.to_s.upcase)
|
579
|
+
else
|
580
|
+
require 'digest/sha1'
|
581
|
+
::Digest::SHA1
|
582
|
+
end
|
583
|
+
|
584
|
+
klass.hexdigest(content)
|
551
585
|
end
|
552
586
|
end
|
553
587
|
|
@@ -566,9 +600,9 @@ class Roda
|
|
566
600
|
o = self.class.assets_opts
|
567
601
|
type, *dirs = type if type.is_a?(Array)
|
568
602
|
stype = type.to_s
|
603
|
+
ru = Rack::Utils
|
569
604
|
|
570
605
|
attrs = if attrs
|
571
|
-
ru = Rack::Utils
|
572
606
|
attrs.map{|k,v| "#{k}=\"#{ru.escape_html(v.to_s)}\""}.join(SPACE)
|
573
607
|
else
|
574
608
|
EMPTY_STRING
|
@@ -590,15 +624,19 @@ class Roda
|
|
590
624
|
if dirs && !dirs.empty?
|
591
625
|
key = dirs.join(DOT)
|
592
626
|
ckey = "#{stype}.#{key}"
|
593
|
-
if ukey = compiled[ckey]
|
627
|
+
if hash = ukey = compiled[ckey]
|
594
628
|
ukey = "#{key}.#{ukey}"
|
595
629
|
end
|
596
630
|
else
|
597
|
-
ukey = compiled[stype]
|
631
|
+
hash = ukey = compiled[stype]
|
598
632
|
end
|
599
633
|
|
600
634
|
if ukey
|
601
|
-
|
635
|
+
if algo = o[:sri]
|
636
|
+
integrity = "\" integrity=\"#{algo}-#{ru.escape_html([[hash].pack('H*')].pack('m').tr("\n", EMPTY_STRING))}"
|
637
|
+
end
|
638
|
+
|
639
|
+
"#{tag_start}#{asset_host}#{url_prefix}/#{o[:"compiled_#{stype}_prefix"]}.#{ukey}.#{stype}#{integrity}#{tag_end}"
|
602
640
|
end
|
603
641
|
else
|
604
642
|
asset_dir = o[type]
|
@@ -640,11 +678,15 @@ class Roda
|
|
640
678
|
# Otherwise, render the file using the render plugin. +file+ should be
|
641
679
|
# the relative path to the file from the current directory.
|
642
680
|
def read_asset_file(file, type)
|
643
|
-
|
681
|
+
o = self.class.assets_opts
|
682
|
+
|
683
|
+
content = if file.end_with?(".#{type}")
|
644
684
|
::File.read(file)
|
645
685
|
else
|
646
|
-
render_asset_file(file, :template_opts=>
|
686
|
+
render_asset_file(file, :template_opts=>o[:"#{type}_opts"])
|
647
687
|
end
|
688
|
+
|
689
|
+
o[:postprocessor] ? o[:postprocessor].call(file, type, content) : content
|
648
690
|
end
|
649
691
|
|
650
692
|
private
|
@@ -680,7 +722,7 @@ class Roda
|
|
680
722
|
# handled.
|
681
723
|
def assets_matchers
|
682
724
|
@assets_matchers ||= [:css, :js].map do |t|
|
683
|
-
[t
|
725
|
+
[t, assets_regexp(t)].freeze if roda_class.assets_opts[t]
|
684
726
|
end.compact.freeze
|
685
727
|
end
|
686
728
|
|
@@ -29,10 +29,21 @@ class Roda
|
|
29
29
|
# The error handler can change the response status if necessary,
|
30
30
|
# as well set headers and/or write to the body, just like a regular
|
31
31
|
# request.
|
32
|
+
#
|
33
|
+
#
|
34
|
+
# By default, this plugin handles StandardError and ScriptError.
|
35
|
+
# To override the exception classes it will handle, pass a :classes
|
36
|
+
# option to the plugin:
|
37
|
+
#
|
38
|
+
# plugin :error_handler, :classes=>[StandardError, ScriptError, NoMemoryError]
|
32
39
|
module ErrorHandler
|
40
|
+
DEFAULT_ERROR_HANDLER_CLASSES = [StandardError, ScriptError].freeze
|
41
|
+
|
33
42
|
# If a block is given, automatically call the +error+ method on
|
34
43
|
# the Roda class with it.
|
35
|
-
def self.configure(app, &block)
|
44
|
+
def self.configure(app, opts={}, &block)
|
45
|
+
app.opts[:error_handler_classes] = (opts[:classes] || app.opts[:error_handler_classes] || DEFAULT_ERROR_HANDLER_CLASSES).dup.freeze
|
46
|
+
|
36
47
|
if block
|
37
48
|
app.error(&block)
|
38
49
|
end
|
@@ -53,7 +64,7 @@ class Roda
|
|
53
64
|
# the error handler.
|
54
65
|
def call
|
55
66
|
super
|
56
|
-
rescue
|
67
|
+
rescue *opts[:error_handler_classes] => e
|
57
68
|
res = @_response = self.class::RodaResponse.new
|
58
69
|
res.status = 500
|
59
70
|
super{handle_error(e)}
|
@@ -15,15 +15,16 @@ class Roda
|
|
15
15
|
#
|
16
16
|
# plugin :match_affix, ""
|
17
17
|
#
|
18
|
-
# will load the plugin and use an empty prefix.
|
18
|
+
# will load the plugin and use an empty prefix (instead of a slash).
|
19
19
|
#
|
20
20
|
# plugin :match_affix, "", /(\/|\z)/
|
21
21
|
#
|
22
22
|
# will use an empty prefix and change the suffix to consume a trailing slash.
|
23
23
|
#
|
24
|
-
# plugin :match_affix, nil, /(
|
24
|
+
# plugin :match_affix, nil, /(?:\/\z|(?=\/|\z))/
|
25
25
|
#
|
26
|
-
# will not modify the prefix and will change the suffix
|
26
|
+
# will not modify the prefix and will change the suffix so that it consumes a trailing slash
|
27
|
+
# at the end of the path only.
|
27
28
|
module MatchAffix
|
28
29
|
PREFIX = "/".freeze
|
29
30
|
SUFFIX = "(?=\/|\z)".freeze
|
@@ -36,9 +36,9 @@ class Roda
|
|
36
36
|
# Patterns can be rewritten dynamically by providing a block accepting a MatchData
|
37
37
|
# object and evaluating to the replacement.
|
38
38
|
#
|
39
|
-
# rewrite_path(/\A\/a/(\w+)/){|match| match[1].capitalize}
|
39
|
+
# rewrite_path(/\A\/a/(\w+)/){|match| "/a/#{match[1].capitalize}"}
|
40
40
|
# # PATH_INFO '/a/moo' => remaining_path '/a/Moo'
|
41
|
-
# rewrite_path(/\A\/a/(\w+)/, :path_info => true){|match| match[1].capitalize}
|
41
|
+
# rewrite_path(/\A\/a/(\w+)/, :path_info => true){|match| "/a/#{match[1].capitalize}"}
|
42
42
|
# # PATH_INFO '/a/moo' => PATH_INFO '/a/Moo'
|
43
43
|
#
|
44
44
|
# All path rewrites are applied in order, so if a path is rewritten by one rewrite,
|
data/lib/roda/plugins/render.rb
CHANGED
@@ -20,6 +20,24 @@ class Roda
|
|
20
20
|
# end
|
21
21
|
# end
|
22
22
|
#
|
23
|
+
# The +render+ and +view+ methods just return strings, they do not have
|
24
|
+
# side effects (unless the templates themselves have side effects).
|
25
|
+
# As Roda uses the routing block return value as the body of the response,
|
26
|
+
# in most cases you will call these methods as the last expression in a
|
27
|
+
# routing block # to have the response body be the result of the template
|
28
|
+
# rendering.
|
29
|
+
#
|
30
|
+
# Because +render+ and +view+ just return strings, you can call them inside
|
31
|
+
# templates (i.e. for subtemplates/partials), or multiple times in the
|
32
|
+
# same route and combine the results together:
|
33
|
+
#
|
34
|
+
# route do |r|
|
35
|
+
# r.is 'foo-bars' do
|
36
|
+
# @bars = Bar.where(:foo).map{|b| render(:bar, :locals=>{:bar=>b})}.join
|
37
|
+
# view('foo')
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
#
|
23
41
|
# You can provide options to the plugin method:
|
24
42
|
#
|
25
43
|
# plugin :render, :engine=>'haml', :views=>'admin_views'
|
@@ -46,9 +64,12 @@ class Roda
|
|
46
64
|
# :escaper :: Object used for escaping output of <tt><%= %></tt>, when :escape is used,
|
47
65
|
# overriding the default. If given, object should respond to +escape_xml+ with
|
48
66
|
# a single argument and return an output string.
|
49
|
-
# :layout :: The base name of the layout file, defaults to 'layout'.
|
67
|
+
# :layout :: The base name of the layout file, defaults to 'layout'. This can be provided as a hash
|
68
|
+
# with the :template or :inline options.
|
50
69
|
# :layout_opts :: The options to use when rendering the layout, if different
|
51
|
-
# from the default options.
|
70
|
+
# from the default options. To pass local variables to the layout, include a :locals
|
71
|
+
# option inside :layout_opts. To automatically merge the view template locals into
|
72
|
+
# the layout template locals, include a :merge_locals option inside :layout_opts.
|
52
73
|
# :template_opts :: The tilt options used when rendering all templates. defaults to:
|
53
74
|
# <tt>{:outvar=>'@_out_buf', :default_encoding=>Encoding.default_external}</tt>.
|
54
75
|
# :engine_opts :: The tilt options to use per template engine. Keys are
|
@@ -154,6 +175,11 @@ class Roda
|
|
154
175
|
|
155
176
|
opts[:layout_opts] = (opts[:layout_opts] || {}).dup
|
156
177
|
opts[:layout_opts][:_is_layout] = true
|
178
|
+
|
179
|
+
if opts[:layout_opts][:merge_locals] && opts[:locals]
|
180
|
+
opts[:layout_opts][:locals] = opts[:locals].merge(opts[:layout_opts][:locals] || {})
|
181
|
+
end
|
182
|
+
|
157
183
|
if layout = opts.fetch(:layout, true)
|
158
184
|
opts[:layout] = true unless opts.has_key?(:layout)
|
159
185
|
|
@@ -373,16 +399,31 @@ class Roda
|
|
373
399
|
# used, return nil.
|
374
400
|
def view_layout_opts(opts)
|
375
401
|
if layout = opts.fetch(:layout, render_opts[:layout])
|
402
|
+
|
376
403
|
layout_opts = render_layout_opts
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
404
|
+
merge_locals = layout_opts[:merge_locals]
|
405
|
+
|
406
|
+
if method_layout_opts = opts[:layout_opts]
|
407
|
+
method_layout_locals = method_layout_opts[:locals]
|
408
|
+
merge_locals = method_layout_opts[:merge_locals] if method_layout_opts.has_key?(:merge_locals)
|
409
|
+
end
|
410
|
+
|
411
|
+
locals = {}
|
412
|
+
if merge_locals && (plugin_locals = render_opts[:locals])
|
413
|
+
locals.merge!(plugin_locals)
|
385
414
|
end
|
415
|
+
if layout_locals = layout_opts[:locals]
|
416
|
+
locals.merge!(layout_locals)
|
417
|
+
end
|
418
|
+
if merge_locals && (method_locals = opts[:locals])
|
419
|
+
locals.merge!(method_locals)
|
420
|
+
end
|
421
|
+
if method_layout_locals
|
422
|
+
locals.merge!(method_layout_locals)
|
423
|
+
end
|
424
|
+
|
425
|
+
layout_opts.merge!(method_layout_opts) if method_layout_opts
|
426
|
+
layout_opts[:locals] = locals unless locals.empty?
|
386
427
|
|
387
428
|
case layout
|
388
429
|
when Hash
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
class Roda
|
5
|
+
module RodaPlugins
|
6
|
+
# The run_append_slash plugin makes +r.run+ use +/+ as the +PATH_INFO+
|
7
|
+
# when calling the rack application if +PATH_INFO+ would be empty.
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# route do |r|
|
11
|
+
# r.on "a" do
|
12
|
+
# r.run App
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# # without run_append_slash:
|
17
|
+
# # GET /a => App gets "" as PATH_INFO
|
18
|
+
# # GET /a/ => App gets "/" as PATH_INFO
|
19
|
+
#
|
20
|
+
# # with run_append_slash:
|
21
|
+
# # GET /a => App gets "/" as PATH_INFO
|
22
|
+
# # GET /a/ => App gets "/" as PATH_INFO
|
23
|
+
module RunAppendSlash
|
24
|
+
OPTS = {}.freeze
|
25
|
+
# Set plugin specific options. Options:
|
26
|
+
# :use_redirects :: Whether to issue 302 redirects when appending the
|
27
|
+
# trailing slash.
|
28
|
+
def self.configure(app, opts=OPTS)
|
29
|
+
app.opts[:run_append_slash_redirect] = !!opts[:use_redirects]
|
30
|
+
end
|
31
|
+
|
32
|
+
module RequestMethods
|
33
|
+
# Calls the given rack app. If the path matches the root of the app but
|
34
|
+
# does not contain a trailing slash, a trailing slash is appended to the
|
35
|
+
# path internally, or a redirect is issued when configured with
|
36
|
+
# <tt>use_redirects: true</tt>.
|
37
|
+
def run(app)
|
38
|
+
if remaining_path.empty?
|
39
|
+
if scope.opts[:run_append_slash_redirect]
|
40
|
+
redirect("#{path}/")
|
41
|
+
else
|
42
|
+
@remaining_path += '/'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
super
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
register_plugin(:run_append_slash, RunAppendSlash)
|
51
|
+
end
|
52
|
+
end
|
@@ -159,6 +159,16 @@ class Roda
|
|
159
159
|
@requested_type ||= opts[:default_type]
|
160
160
|
end
|
161
161
|
|
162
|
+
# Add the type routing extension back to the remaining path
|
163
|
+
# if it was removed from the path when the application was
|
164
|
+
# initialized.
|
165
|
+
def run(_)
|
166
|
+
if defined?(@type_routing_extension)
|
167
|
+
@remaining_path += ".#{@type_routing_extension}"
|
168
|
+
end
|
169
|
+
super
|
170
|
+
end
|
171
|
+
|
162
172
|
private
|
163
173
|
|
164
174
|
# Removes a trailing file extension from the path, and sets
|
@@ -169,7 +179,7 @@ class Roda
|
|
169
179
|
|
170
180
|
if opts[:use_extension]
|
171
181
|
if m = path.match(opts[:extension_regexp])
|
172
|
-
@requested_type = m[2].to_sym
|
182
|
+
@type_routing_extension = @requested_type = m[2].to_sym
|
173
183
|
path = m[1]
|
174
184
|
end
|
175
185
|
end
|
data/lib/roda/version.rb
CHANGED
data/spec/plugin/assets_spec.rb
CHANGED
@@ -54,6 +54,7 @@ if run_tests
|
|
54
54
|
after(:all) do
|
55
55
|
FileUtils.rm_r('spec/assets/tmp') if File.directory?('spec/assets/tmp')
|
56
56
|
FileUtils.rm_r('spec/public') if File.directory?('spec/public')
|
57
|
+
FileUtils.rm(Dir['spec/assets/app.*.{js,css}*'])
|
57
58
|
end
|
58
59
|
|
59
60
|
def gunzip(body)
|
@@ -266,6 +267,31 @@ if run_tests
|
|
266
267
|
js.must_include('console.log')
|
267
268
|
end
|
268
269
|
|
270
|
+
[[:sha256, 64, 44], [:sha384, 96, 64], [:sha512, 128, 88]].each do |algo, hex_length, base64_length|
|
271
|
+
it 'should handle :sri option for subresource integrity when compiling assets' do
|
272
|
+
app.plugin :assets, :sri=>algo
|
273
|
+
app.compile_assets
|
274
|
+
html = body('/test')
|
275
|
+
html.scan(/<link/).length.must_equal 1
|
276
|
+
html =~ %r|integrity="#{algo}-([^"]+)" />|
|
277
|
+
css_hash = $1.gsub('/', '/')
|
278
|
+
css_hash.length.must_equal base64_length
|
279
|
+
html =~ %r|href="(/assets/app\.[a-f0-9]{#{hex_length}}\.css)"|
|
280
|
+
css = body($1)
|
281
|
+
[Digest.const_get(algo.to_s.upcase).digest(css)].pack('m').tr("\n", "").must_equal css_hash
|
282
|
+
html.scan(/<script/).length.must_equal 1
|
283
|
+
html =~ %r|integrity="#{algo}-([^"]+)"></script>|
|
284
|
+
js_hash = $1.gsub('/', '/')
|
285
|
+
js_hash.length.must_equal base64_length
|
286
|
+
html =~ %r|src="(/assets/app\.head\.[a-f0-9]{#{hex_length}}\.js)"|
|
287
|
+
js = body($1)
|
288
|
+
[Digest.const_get(algo.to_s.upcase).digest(js)].pack('m').tr("\n", "").must_equal js_hash
|
289
|
+
css.must_match(/color: ?red/)
|
290
|
+
css.must_match(/color: ?blue/)
|
291
|
+
js.must_include('console.log')
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
269
295
|
it 'should handle linking to compiled assets when a compiled asset host is used' do
|
270
296
|
app.plugin :assets, :compiled_asset_host=>'https://cdn.example.com'
|
271
297
|
app.compile_assets
|
@@ -519,7 +545,7 @@ if run_tests
|
|
519
545
|
a = app::RodaRequest.assets_matchers
|
520
546
|
a.length.must_equal 1
|
521
547
|
a.first.length.must_equal 2
|
522
|
-
a.first.first.must_equal
|
548
|
+
a.first.first.must_equal :js
|
523
549
|
'assets/js/head/app.js'.must_match a.first.last
|
524
550
|
'assets/js/head/app2.js'.wont_match a.first.last
|
525
551
|
end
|
@@ -529,6 +555,30 @@ if run_tests
|
|
529
555
|
app::RodaRequest.assets_matchers.must_equal []
|
530
556
|
end
|
531
557
|
|
558
|
+
it 'should support :postprocessor option' do
|
559
|
+
postprocessor = lambda do |file, type, content|
|
560
|
+
"file=#{file} type=#{type} tc=#{type.class} #{content.sub('color', 'font')}"
|
561
|
+
end
|
562
|
+
|
563
|
+
app.plugin :assets, :path=>'spec', :js_dir=>nil, :css_dir=>nil, :prefix=>nil,
|
564
|
+
:postprocessor=>postprocessor,
|
565
|
+
:css=>{:assets=>{:css=>%w'app.scss'}}
|
566
|
+
app.route do |r|
|
567
|
+
r.assets
|
568
|
+
r.is 'test' do
|
569
|
+
"#{assets([:css, :assets, :css])}"
|
570
|
+
end
|
571
|
+
end
|
572
|
+
html = body('/test')
|
573
|
+
html.scan(/<link/).length.must_equal 1
|
574
|
+
html =~ %r{href="(/assets/css/app\.scss)"}
|
575
|
+
css = body($1)
|
576
|
+
css.must_match(/app\.scss/)
|
577
|
+
css.must_match(/type=css/)
|
578
|
+
css.must_match(/tc=Symbol/)
|
579
|
+
css.must_match(/font: red;/)
|
580
|
+
end
|
581
|
+
|
532
582
|
it 'should support :precompiled option' do
|
533
583
|
app.plugin :assets, :precompiled=>metadata_file
|
534
584
|
File.exist?(metadata_file).must_equal false
|
@@ -47,6 +47,31 @@ describe "error_handler plugin" do
|
|
47
47
|
status.must_equal 500
|
48
48
|
end
|
49
49
|
|
50
|
+
it "executes on custom exception classes" do
|
51
|
+
app(:bare) do
|
52
|
+
plugin :error_handler, :classes=>[StandardError]
|
53
|
+
|
54
|
+
error do |e|
|
55
|
+
e.message
|
56
|
+
end
|
57
|
+
|
58
|
+
route do |r|
|
59
|
+
r.on "a" do
|
60
|
+
raise 'foo'
|
61
|
+
end
|
62
|
+
|
63
|
+
raise SyntaxError, 'bad idea'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
proc{body}.must_raise SyntaxError
|
68
|
+
body("/a").must_equal 'foo'
|
69
|
+
|
70
|
+
@app = Class.new(@app)
|
71
|
+
proc{body}.must_raise SyntaxError
|
72
|
+
body("/a").must_equal 'foo'
|
73
|
+
end
|
74
|
+
|
50
75
|
it "can override status inside error block" do
|
51
76
|
app(:bare) do
|
52
77
|
plugin :error_handler do |e|
|
@@ -19,4 +19,25 @@ describe "match_affix plugin" do
|
|
19
19
|
body("/albums/a/1").must_equal 'albums-/'
|
20
20
|
body("/albums/b/1").must_equal 'b-/-1-""'
|
21
21
|
end
|
22
|
+
|
23
|
+
it "handles extra trailing slash only" do
|
24
|
+
app(:bare) do
|
25
|
+
plugin :match_affix, nil, /(?:\/\z|(?=\/|\z))/
|
26
|
+
|
27
|
+
route do |r|
|
28
|
+
r.on "albums" do
|
29
|
+
r.on "b" do
|
30
|
+
"albums/b:#{r.remaining_path}"
|
31
|
+
end
|
32
|
+
|
33
|
+
"albums:#{r.remaining_path}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
body("/albums/a").must_equal 'albums:/a'
|
39
|
+
body("/albums/a/").must_equal 'albums:/a/'
|
40
|
+
body("/albums/b").must_equal 'albums/b:'
|
41
|
+
body("/albums/b/").must_equal 'albums/b:'
|
42
|
+
end
|
22
43
|
end
|
data/spec/plugin/render_spec.rb
CHANGED
@@ -68,6 +68,34 @@ describe "render plugin" do
|
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
|
+
describe "render plugin with :layout_opts=>{:merge_locals=>true}" do
|
72
|
+
h = {:a=>1, :b=>2, :c=>3, :d=>4}
|
73
|
+
|
74
|
+
before do
|
75
|
+
app(:bare) do
|
76
|
+
plugin :render, :views=>"./spec/views", :check_paths=>true, :locals=>{:a=>1, :b=>2, :c=>3, :d=>4, :e=>5}, :layout_opts=>{:inline=>'<%= a %>|<%= b %>|<%= c %>|<%= d %>|<%= e %>|<%= f %>|<%= yield %>', :merge_locals=>true, :locals=>{:a=>-1, :f=>6}}
|
77
|
+
|
78
|
+
route do |r|
|
79
|
+
r.on "base" do
|
80
|
+
view(:inline=>'(<%= a %>|<%= b %>|<%= c %>|<%= d %>|<%= e %>)')
|
81
|
+
end
|
82
|
+
r.on "override" do
|
83
|
+
view(:inline=>'(<%= a %>|<%= b %>|<%= c %>|<%= d %>|<%= e %>)', :locals=>{:b=>-2, :d=>-4, :f=>-6}, :layout_opts=>{:locals=>{:d=>0, :c=>-3, :e=>-5}})
|
84
|
+
end
|
85
|
+
r.on "no_merge" do
|
86
|
+
view(:inline=>'(<%= a %>|<%= b %>|<%= c %>|<%= d %>|<%= e %>)', :locals=>{:b=>-2, :d=>-4, :f=>-6}, :layout_opts=>{:merge_locals=>false, :locals=>{:d=>0, :c=>-3, :e=>-5}})
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should choose method opts before plugin opts, and layout specific before locals" do
|
93
|
+
body("/base").must_equal '-1|2|3|4|5|6|(1|2|3|4|5)'
|
94
|
+
body("/override").must_equal '-1|-2|-3|0|-5|-6|(1|-2|3|-4|5)'
|
95
|
+
body("/no_merge").must_equal '-1|2|-3|0|-5|6|(1|-2|3|-4|5)'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
71
99
|
describe "render plugin" do
|
72
100
|
it "simple layout support" do
|
73
101
|
app(:bare) do
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
describe "run_append_slash plugin" do
|
4
|
+
before do
|
5
|
+
sub2 = app do |r|
|
6
|
+
r.root do
|
7
|
+
'sub-bar-root'
|
8
|
+
end
|
9
|
+
|
10
|
+
r.get 'baz' do
|
11
|
+
'sub-bar-baz'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
sub1 = app(:run_append_slash) do |r|
|
16
|
+
r.root do
|
17
|
+
'sub-root'
|
18
|
+
end
|
19
|
+
|
20
|
+
r.get 'foo' do
|
21
|
+
'sub-foo'
|
22
|
+
end
|
23
|
+
|
24
|
+
r.on 'bar' do
|
25
|
+
r.run sub2
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
app(:bare) do
|
30
|
+
route do |r|
|
31
|
+
r.root do
|
32
|
+
'root'
|
33
|
+
end
|
34
|
+
|
35
|
+
r.on 'sub' do
|
36
|
+
r.run sub1
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it "internally appends a missing trailing slash to #run sub apps" do
|
43
|
+
# Without append slash
|
44
|
+
body.must_equal 'root'
|
45
|
+
status('/sub').must_equal 404
|
46
|
+
body('/sub/').must_equal 'sub-root'
|
47
|
+
body('/sub/foo').must_equal 'sub-foo'
|
48
|
+
status('/sub/foo/').must_equal 404
|
49
|
+
body('/sub/bar/').must_equal 'sub-bar-root'
|
50
|
+
body('/sub/bar/baz').must_equal 'sub-bar-baz'
|
51
|
+
status('/sub/bar/baz/').must_equal 404
|
52
|
+
|
53
|
+
# With append slash
|
54
|
+
app.plugin :run_append_slash
|
55
|
+
body('/sub').must_equal 'sub-root'
|
56
|
+
body('/sub/').must_equal 'sub-root'
|
57
|
+
body('/sub/foo').must_equal 'sub-foo'
|
58
|
+
status('/sub/foo/').must_equal 404
|
59
|
+
body('/sub/bar').must_equal 'sub-bar-root'
|
60
|
+
body('/sub/bar/').must_equal 'sub-bar-root'
|
61
|
+
body('/sub/bar/baz').must_equal 'sub-bar-baz'
|
62
|
+
status('/sub/bar/baz/').must_equal 404
|
63
|
+
end
|
64
|
+
|
65
|
+
it "redirects #run sub apps when trailing slash is missing" do
|
66
|
+
app.plugin :run_append_slash, :use_redirects => true
|
67
|
+
status('/sub').must_equal 302
|
68
|
+
header('Location', '/sub').must_equal '/sub/'
|
69
|
+
body('/sub/').must_equal 'sub-root'
|
70
|
+
body('/sub/foo').must_equal 'sub-foo'
|
71
|
+
status('/sub/foo/').must_equal 404
|
72
|
+
body('/sub/bar').must_equal 'sub-bar-root'
|
73
|
+
body('/sub/bar/').must_equal 'sub-bar-root'
|
74
|
+
body('/sub/bar/baz').must_equal 'sub-bar-baz'
|
75
|
+
status('/sub/bar/baz/').must_equal 404
|
76
|
+
end
|
77
|
+
end
|
@@ -50,6 +50,18 @@ describe "type_routing plugin" do
|
|
50
50
|
body('/a.html', 'HTTP_ACCEPT' => 'application/xml').must_equal 'HTML: html'
|
51
51
|
end
|
52
52
|
|
53
|
+
it "works correctly in sub apps" do
|
54
|
+
sup_app = app
|
55
|
+
b = sup_app.route_block
|
56
|
+
@app = Class.new(sup_app)
|
57
|
+
app.route do |r|
|
58
|
+
r.run(sup_app)
|
59
|
+
end
|
60
|
+
|
61
|
+
body('/a.json', 'HTTP_ACCEPT' => 'text/html').must_equal 'JSON: json'
|
62
|
+
body('/a.xml', 'HTTP_ACCEPT' => 'application/json').must_equal 'XML: xml'
|
63
|
+
body('/a.html', 'HTTP_ACCEPT' => 'application/xml').must_equal 'HTML: html'
|
64
|
+
end
|
53
65
|
|
54
66
|
it "uses the default if neither file extension nor Accept header are given" do
|
55
67
|
body('/a').must_equal 'HTML: html'
|
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.17.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: 2016-
|
11
|
+
date: 2016-08-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -182,6 +182,7 @@ extra_rdoc_files:
|
|
182
182
|
- doc/release_notes/2.14.0.txt
|
183
183
|
- doc/release_notes/2.15.0.txt
|
184
184
|
- doc/release_notes/2.16.0.txt
|
185
|
+
- doc/release_notes/2.17.0.txt
|
185
186
|
files:
|
186
187
|
- CHANGELOG
|
187
188
|
- MIT-LICENSE
|
@@ -201,6 +202,7 @@ files:
|
|
201
202
|
- doc/release_notes/2.14.0.txt
|
202
203
|
- doc/release_notes/2.15.0.txt
|
203
204
|
- doc/release_notes/2.16.0.txt
|
205
|
+
- doc/release_notes/2.17.0.txt
|
204
206
|
- doc/release_notes/2.2.0.txt
|
205
207
|
- doc/release_notes/2.3.0.txt
|
206
208
|
- doc/release_notes/2.4.0.txt
|
@@ -268,6 +270,7 @@ files:
|
|
268
270
|
- lib/roda/plugins/render_each.rb
|
269
271
|
- lib/roda/plugins/request_headers.rb
|
270
272
|
- lib/roda/plugins/response_request.rb
|
273
|
+
- lib/roda/plugins/run_append_slash.rb
|
271
274
|
- lib/roda/plugins/run_handler.rb
|
272
275
|
- lib/roda/plugins/shared_vars.rb
|
273
276
|
- lib/roda/plugins/sinatra_helpers.rb
|
@@ -353,6 +356,7 @@ files:
|
|
353
356
|
- spec/plugin/render_spec.rb
|
354
357
|
- spec/plugin/request_headers_spec.rb
|
355
358
|
- spec/plugin/response_request_spec.rb
|
359
|
+
- spec/plugin/run_append_slash_spec.rb
|
356
360
|
- spec/plugin/run_handler_spec.rb
|
357
361
|
- spec/plugin/shared_vars_spec.rb
|
358
362
|
- spec/plugin/sinatra_helpers_spec.rb
|