roda 2.16.0 → 2.17.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 +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
|