roda 2.17.0 → 2.18.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f728447f69ff9f50896fb6dca7a0e709f2df26e1
4
- data.tar.gz: 521dd8fa68fe85421eab4d410619991da0655cb8
3
+ metadata.gz: b5e850d352a98dff085d020e04fa52c55894edaf
4
+ data.tar.gz: 0fb277bfbfd64699d15d6e56325464e5ba3cb5cb
5
5
  SHA512:
6
- metadata.gz: 150c34299dd44322d1d36523f83aa948a61740b8b0bc5374de85ce94feb67ab9ced97262b7982330aa91d1433362ace6bab7a1924689a3dd99b3576db478fdc2
7
- data.tar.gz: 68d485ef5b650992d224ac98af823153d25bc7c4ff9fb71d087a44f7c792f9a2431d2d93722433ebf553ad3f387d74848125f88144ef88dad52dfdaf50dab13b
6
+ metadata.gz: a779f1e2cb695dab260f457798582014dd7bee889304de79cea0c05c43f3252b350cddb33e9d17d601f6844eaedc241290b48a2daf7ede4a89133113a2c2f353
7
+ data.tar.gz: 8594f7058bd36a6cfba5cd47831b0e5267927f09e740c4c2e3b14f237a98432aaa5dde2c15b5a9d9ecb4944de9d35125adc548382383e366795ca8d4678dff1c
data/CHANGELOG CHANGED
@@ -1,3 +1,15 @@
1
+ = 2.18.0 (2016-09-13)
2
+
3
+ * Add assets_preloading plugin, for creating link tags or Link header for preloading assets (celsworth, jeremyevans) (#98)
4
+
5
+ * Add assets_paths method to assets plugin, for just the paths to the assets, instead of the full tags (celsworth) (#96)
6
+
7
+ * Make type_routing plugin work correctly with public plugin (celsworth, jeremyevans) (#95)
8
+
9
+ * Add static_routing plugin for 3-4x increase in performance for large numbers of static routes (jeremyevans)
10
+
11
+ * Make head plugin work with not_allowed plugin if loaded after (jeremyevans) (#92)
12
+
1
13
  = 2.17.0 (2016-08-13)
2
14
 
3
15
  * Add :postprocessor option to assets plugin, for postprocessing assets (e.g. autoprefixing CSS) (celsworth) (#86)
@@ -0,0 +1,69 @@
1
+ = New Plugins
2
+
3
+ * A static_routing plugin has been added, which can give a 3-4x
4
+ increase in performance for large number of static routes, and
5
+ makes routing O(1) for static routes. Static routes are routes
6
+ that match full paths, with no placeholders, and are checked before
7
+ using the normal routing tree.
8
+
9
+ Static routes are defined via class-level static_* routing methods.
10
+ There is a static_* routing method for each HTTP verb (e.g.
11
+ static_get), as well as a static_route method, which will work
12
+ for any HTTP verb, with the verb-specific method taking priority.
13
+ By using static_route, you can get significantly faster performance
14
+ while retaining some of the benefits of Roda's routing tree design
15
+ (simple shared logic with verb specific behavior). Example:
16
+
17
+ plugin :static_routing
18
+
19
+ static_route '/foo' do |r|
20
+ @var = :foo
21
+
22
+ r.get do
23
+ 'Not actually reached'
24
+ end
25
+
26
+ r.post{'static POST /#{@var}'}
27
+ end
28
+
29
+ static_get '/foo' do |r|
30
+ 'static GET /foo'
31
+ end
32
+
33
+ route do |r|
34
+ 'Not a static route'
35
+ end
36
+
37
+ Because static routing routes on the full path instead of by
38
+ path segment, the methods takes the full path as a string,
39
+ including the leading slash.
40
+
41
+ * An assets_preloading plugin has been added, which makes it simple
42
+ to generate HTML link tags or a Link header value to tell the
43
+ browser to preload assets for performance reasons.
44
+
45
+ # In routes, using the Link header:
46
+ response.headers['Link'] = preload_assets_link_header(:css)
47
+
48
+ # In templates, using a link tag:
49
+ <%= preload_assets_link_tags(:css) %>
50
+
51
+ = New Features
52
+
53
+ * RodaRequest#real_remaining_path has been added. This is designed
54
+ to be overridden by plugins that modify remaining_path for internal
55
+ routing purposes. RodaRequest#run now uses real_remaining_path
56
+ when passing requests to other rack applications.
57
+
58
+ * An assets_paths method has been added to the assets plugin. This
59
+ is similar to the assets method, but it returns an array of paths
60
+ to the assets, instead of a HTML link/script tag.
61
+
62
+ = Other Improvements
63
+
64
+ * The public plugin now works correctly when used with the
65
+ type_routing plugin, for paths ending in extensions that
66
+ type_routing is configured to handle.
67
+
68
+ * The head plugin now works with the not_allowed plugin if it is
69
+ loaded after the not_allowed plugin.
data/lib/roda.rb CHANGED
@@ -509,6 +509,12 @@ class Roda
509
509
  # The current path to match requests against.
510
510
  attr_reader :remaining_path
511
511
 
512
+ # An alias of remaining_path. If a plugin changes remaining_path then
513
+ # it should override this method to return the untouched original.
514
+ def real_remaining_path
515
+ remaining_path
516
+ end
517
+
512
518
  # Match POST requests. If no arguments are provided, matches all POST
513
519
  # requests, otherwise, matches only POST requests where the arguments
514
520
  # given fully consume the path.
@@ -625,7 +631,7 @@ class Roda
625
631
  # a URL mapper.
626
632
  def run(app)
627
633
  e = @env
628
- path = @remaining_path
634
+ path = real_remaining_path
629
635
  sn = SCRIPT_NAME
630
636
  pi = PATH_INFO
631
637
  script_name = e[sn]
@@ -63,6 +63,21 @@ class Roda
63
63
  # if it set it will automatically prefix the path with the SCRIPT_NAME for
64
64
  # the request.
65
65
  #
66
+ # == Asset Paths
67
+ #
68
+ # If you just want the paths rather than the full tags, you can use
69
+ # assets_paths instead. This will return an array of the sources that
70
+ # the assets function would have put into tags:
71
+ #
72
+ # assets_paths(:css)
73
+ # # => ["/assets/css/foo.css", "/assets/css/app.css"]
74
+ #
75
+ # If compilation is turned on, it will return the path to the compiled
76
+ # asset:
77
+ #
78
+ # assets_paths(:css)
79
+ # # => ["/assets/app.5e7b06baa1a514d8473b0eca514b806c201073b9.css"]
80
+ #
66
81
  # == Asset Groups
67
82
  #
68
83
  # The asset plugin supports groups for the cases where you have different
@@ -182,7 +197,20 @@ class Roda
182
197
  # and it will run in compiled mode, assuming that the compiled asset files
183
198
  # already exist.
184
199
  #
185
- # ==== Postprocessing
200
+ # ==== On Heroku
201
+ #
202
+ # Heroku supports precompiling the assets when using Roda. You just need to
203
+ # add an assets:precompile task, similar to this:
204
+ #
205
+ # namespace :assets do
206
+ # desc "Precompile the assets"
207
+ # task :precompile do
208
+ # require './app'
209
+ # App.compile_assets
210
+ # end
211
+ # end
212
+ #
213
+ # == Postprocessing
186
214
  #
187
215
  # If you pass a callable object to the :postprocessor option, it will be called
188
216
  # before an asset is served.
@@ -202,19 +230,6 @@ class Roda
202
230
  # end
203
231
  # }
204
232
  #
205
- # ==== On Heroku
206
- #
207
- # Heroku supports precompiling the assets when using Roda. You just need to
208
- # add an assets:precompile task, similar to this:
209
- #
210
- # namespace :assets do
211
- # desc "Precompile the assets"
212
- # task :precompile do
213
- # require './app'
214
- # App.compile_assets
215
- # end
216
- # end
217
- #
218
233
  # == External Assets/Assets from Gems
219
234
  #
220
235
  # The assets plugin only supports loading assets files underneath the assets
@@ -586,39 +601,16 @@ class Roda
586
601
  end
587
602
 
588
603
  module InstanceMethods
589
- # Return a string containing html tags for the given asset type.
590
- # This will use a script tag for the :js type and a link tag for
591
- # the :css type.
592
- #
593
- # To return the tags for a specific asset group, use an array for
594
- # the type, such as [:css, :frontend].
595
- #
596
- # When the assets are not compiled, this will result in a separate
597
- # tag for each asset file. When the assets are compiled, this will
598
- # result in a single tag to the compiled asset file.
599
- def assets(type, attrs = nil)
604
+ # Return an array of paths for the given asset type and optionally
605
+ # asset group. See the assets function documentation for details.
606
+ def assets_paths(type)
600
607
  o = self.class.assets_opts
601
608
  type, *dirs = type if type.is_a?(Array)
602
609
  stype = type.to_s
603
610
  ru = Rack::Utils
604
611
 
605
- attrs = if attrs
606
- attrs.map{|k,v| "#{k}=\"#{ru.escape_html(v.to_s)}\""}.join(SPACE)
607
- else
608
- EMPTY_STRING
609
- end
610
-
611
- if type == :js
612
- tag_start = "<script type=\"text/javascript\" #{attrs} src=\""
613
- tag_end = JS_END
614
- else
615
- tag_start = "<link rel=\"stylesheet\" #{attrs} href=\""
616
- tag_end = CSS_END
617
- end
618
-
619
612
  url_prefix = request.script_name if self.class.opts[:add_script_name]
620
613
 
621
- # Create a tag for each individual file
622
614
  if compiled = o[:compiled]
623
615
  asset_host = o[:compiled_asset_host]
624
616
  if dirs && !dirs.empty?
@@ -636,7 +628,9 @@ class Roda
636
628
  integrity = "\" integrity=\"#{algo}-#{ru.escape_html([[hash].pack('H*')].pack('m').tr("\n", EMPTY_STRING))}"
637
629
  end
638
630
 
639
- "#{tag_start}#{asset_host}#{url_prefix}/#{o[:"compiled_#{stype}_prefix"]}.#{ukey}.#{stype}#{integrity}#{tag_end}"
631
+ [ "#{asset_host}#{url_prefix}/#{o[:"compiled_#{stype}_prefix"]}.#{ukey}.#{stype}#{integrity}" ]
632
+ else
633
+ []
640
634
  end
641
635
  else
642
636
  asset_dir = o[type]
@@ -644,8 +638,42 @@ class Roda
644
638
  dirs.each{|f| asset_dir = asset_dir[f]}
645
639
  prefix = "#{dirs.join(SLASH)}/" if o[:group_subdirs]
646
640
  end
647
- Array(asset_dir).map{|f| "#{tag_start}#{url_prefix}/#{o[:"#{stype}_prefix"]}#{prefix}#{f}#{o[:"#{stype}_suffix"]}#{tag_end}"}.join(NEWLINE)
641
+ Array(asset_dir).map{|f| "#{url_prefix}/#{o[:"#{stype}_prefix"]}#{prefix}#{f}#{o[:"#{stype}_suffix"]}"}
642
+ end
643
+ end
644
+
645
+ # Return a string containing html tags for the given asset type.
646
+ # This will use a script tag for the :js type and a link tag for
647
+ # the :css type.
648
+ #
649
+ # To return the tags for a specific asset group, use an array for
650
+ # the type, such as [:css, :frontend].
651
+ #
652
+ # When the assets are not compiled, this will result in a separate
653
+ # tag for each asset file. When the assets are compiled, this will
654
+ # result in a single tag to the compiled asset file.
655
+ def assets(type, attrs = nil)
656
+ ru = Rack::Utils
657
+ attrs = if attrs
658
+ attrs.map{|k,v| "#{k}=\"#{ru.escape_html(v.to_s)}\""}.join(SPACE)
659
+ else
660
+ EMPTY_STRING
648
661
  end
662
+
663
+ ltype = if type.is_a?(Array)
664
+ type[0]
665
+ else
666
+ type
667
+ end
668
+ if ltype == :js
669
+ tag_start = "<script type=\"text/javascript\" #{attrs} src=\""
670
+ tag_end = JS_END
671
+ else
672
+ tag_start = "<link rel=\"stylesheet\" #{attrs} href=\""
673
+ tag_end = CSS_END
674
+ end
675
+
676
+ assets_paths(type).map{|p| "#{tag_start}#{p}#{tag_end}"}.join(NEWLINE)
649
677
  end
650
678
 
651
679
  # Render the asset with the given filename. When assets are compiled,
@@ -0,0 +1,91 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The assets_preloading plugin generates html tags or a header value
7
+ # to facilitate browser preloading of your assets. This allows
8
+ # compatible browsers to fetch assets before they are required,
9
+ # streamlining page rendering.
10
+ #
11
+ # For a list of compatible browsers, see
12
+ # http://caniuse.com/#search=link-rel-preload
13
+ #
14
+ # The plugin provides two functions - preload_assets_link_header and
15
+ # preload_assets_link_tags. The resulting preloading should be
16
+ # identical, it is up to you which system you prefer.
17
+ #
18
+ # preload_assets_link_header returns a string suitable for populating
19
+ # the response Link header:
20
+ #
21
+ # response.headers['Link'] = preload_assets_link_header(:css)
22
+ # # Link header will now contain something like:
23
+ # # </assets/app.css>;rel="preload";as="style"
24
+ #
25
+ # preload_assets_link_tags returns a string to drop into your
26
+ # templates containing link tags:
27
+ #
28
+ # preload_assets_link_tags(:css)
29
+ # # returns <link href="/assets/app.css" rel="preload" as="style">
30
+ #
31
+ # Note that these link tags are different to the usual asset
32
+ # declarations in markup; this will only instruct a compatible browser
33
+ # to fetch the file and cache it for later; the browser will not parse
34
+ # the asset until it encounters a traditional link or script tag.
35
+ #
36
+ # You must still setup and link to your assets as you did previously.
37
+ #
38
+ # Both functions can be passed any combination of asset types or
39
+ # asset groups, as multiple arguments:
40
+ #
41
+ # # generate tags for css assets and the app js asset group
42
+ # preload_assets_link_tags(:css, [:js, :app], [:js, :bar])
43
+ #
44
+ # # generate Link header for css assets and js asset groups app and bar
45
+ # preload_assets_link_header(:css, [:js, :app])
46
+ #
47
+ module AssetsPreloading
48
+ TYPE_AS = {
49
+ :css => 'style'.freeze,
50
+ :js => 'script'.freeze,
51
+ }.freeze
52
+ COMMA = ",".freeze
53
+ NEWLINE= "\n".freeze
54
+
55
+ # Depend on the assets plugin, as we'll be calling some functions in it.
56
+ def self.load_dependencies(app)
57
+ app.plugin :assets
58
+ end
59
+
60
+ module InstanceMethods
61
+ # Return a string of <link> tags for the given asset
62
+ # types/groups.
63
+ def preload_assets_link_tags(*args)
64
+ _preload_assets_array(args).map{|path, as| "<link href=\"#{path}\" rel=\"preload\" as=\"#{as}\">"}.join(NEWLINE)
65
+ end
66
+
67
+ # Return a string suitable for a Link header for the
68
+ # given asset types/groups.
69
+ def preload_assets_link_header(*args)
70
+ _preload_assets_array(args).map{|path, as| "<#{path}>;rel=preload;as=#{as}"}.join(COMMA)
71
+ end
72
+
73
+ private
74
+
75
+ # Return an array of paths/as pairs for the given asset
76
+ # types and/or groups.
77
+ def _preload_assets_array(assets)
78
+ assets.map do |type|
79
+ paths = assets_paths(type)
80
+ type = type[0] if type.is_a?(Array)
81
+ as = TYPE_AS[type]
82
+
83
+ paths.map{|path| [path, as]}
84
+ end.flatten(1)
85
+ end
86
+ end
87
+ end
88
+
89
+ register_plugin(:assets_preloading, AssetsPreloading)
90
+ end
91
+ end
@@ -26,6 +26,10 @@ class Roda
26
26
  # HEAD requests for +/+, +/a+, and +/b+ will all return 200 status
27
27
  # with an empty body.
28
28
  #
29
+ # This plugin also works with the not_allowed plugin if it is loaded
30
+ # after the not_allowed plugin. In that case, if GET is one of Allow
31
+ # header options, then HEAD will be as well.
32
+ #
29
33
  # NOTE: if you have a public facing website it is recommended that
30
34
  # you enable this plugin. Search engines and other bots may send a
31
35
  # HEAD request prior to crawling a page with a GET request. Without
@@ -59,6 +63,13 @@ class Roda
59
63
  def match_method(method)
60
64
  super || (!method.is_a?(Array) && head? && method.to_s.upcase == 'GET')
61
65
  end
66
+
67
+ # Work with the not_allowed plugin so that if GET is one
68
+ # of the Allow header options, then HEAD is as well.
69
+ def method_not_allowed(verbs)
70
+ verbs = verbs.sub('GET', 'HEAD, GET')
71
+ super
72
+ end
62
73
  end
63
74
  end
64
75
 
@@ -102,7 +102,7 @@ class Roda
102
102
 
103
103
  # Setup methods for all verbs. If inside an is block and not given
104
104
  # arguments, record the verb used. If given an argument, add an is
105
- # check with the argu
105
+ # check with the arguments.
106
106
  %w'get post delete head options link patch put trace unlink'.each do |verb|
107
107
  if ::Rack::Request.method_defined?("#{verb}?")
108
108
  class_eval(<<-END, __FILE__, __LINE__+1)
@@ -41,7 +41,7 @@ class Roda
41
41
  # Serve files from the public directory if the file exists and this is a GET request.
42
42
  def public
43
43
  if is_get?
44
- path = PARSER.unescape(remaining_path)
44
+ path = PARSER.unescape(real_remaining_path)
45
45
  return if path.include?(NULL_BYTE)
46
46
 
47
47
  roda_opts = roda_class.opts
@@ -89,7 +89,9 @@ class Roda
89
89
  def public_file_readable?(path)
90
90
  ::File.file?(path) && ::File.readable?(path)
91
91
  rescue SystemCallError
92
+ # :nocov:
92
93
  false
94
+ # :nocov:
93
95
  end
94
96
 
95
97
  if ::Rack.release > '2'
@@ -0,0 +1,134 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The static_routing plugin adds static_* routing class methods for handling
7
+ # static routes (i.e. routes with static paths, no placeholders). These
8
+ # routes are processed before the normal routing tree and designed for
9
+ # maximum performance. This can be substantially faster than Roda's normal
10
+ # tree based routing if you have large numbers of static routes, about 3-4x
11
+ # for 100-10000 static routes. Example:
12
+ #
13
+ # plugin :static_routing
14
+ #
15
+ # static_route '/foo' do |r|
16
+ # @var = :foo
17
+ #
18
+ # r.get do
19
+ # 'Not actually reached'
20
+ # end
21
+ #
22
+ # r.post{'static POST /#{@var}'}
23
+ # end
24
+ #
25
+ # static_get '/foo' do |r|
26
+ # 'static GET /foo'
27
+ # end
28
+ #
29
+ # route do |r|
30
+ # 'Not a static route'
31
+ # end
32
+ #
33
+ # A few things to note in the above example. First, unlike most other
34
+ # routing methods in Roda, these take the full path of the request, and only
35
+ # match if r.path_info matches exactly. This is why you need to include the
36
+ # leading slash in the path argument.
37
+ #
38
+ # Second, the static_* routing methods only take a single string argument for
39
+ # the path, they do not acccept other options, and do not handle placeholders
40
+ # in strings. For any routes needing placeholders, you should use Roda's
41
+ # routing tree.
42
+ #
43
+ # There are separate static_* methods for each type of request method, and these
44
+ # request method specific routes are tried first. There is also a static_route
45
+ # method that will match regardless of the request method, if there is no
46
+ # matching request methods specific route. This is why the static_get
47
+ # method call takes precedence over the static_route method call for /foo.
48
+ # As shown above, you can use Roda's routing tree methods inside the
49
+ # static_route block to have shared behavior for different request methods,
50
+ # while still having handling the request methods differently.
51
+ module StaticRouting
52
+ def self.configure(app)
53
+ app.opts[:static_routes] = {}
54
+ end
55
+
56
+ module ClassMethods
57
+ # Freeze the static route metadata when freezing the app.
58
+ def freeze
59
+ super
60
+ opts[:static_routes].freeze
61
+ opts[:static_routes].each_value(&:freeze)
62
+ end
63
+
64
+ # Duplicate static route metadata in subclass.
65
+ def inherited(subclass)
66
+ super
67
+ static_routes = subclass.opts[:static_routes]
68
+ opts[:static_routes].each do |k, v|
69
+ static_routes[k] = v.dup
70
+ end
71
+ end
72
+
73
+ # Add a static route for any request method. These are
74
+ # tried after the request method specific static routes (e.g.
75
+ # static_get), but allow you to use Roda's routing tree
76
+ # methods inside the route for handling shared behavior while
77
+ # still allowing request method specific handling.
78
+ def static_route(path, &block)
79
+ add_static_route(nil, path, &block)
80
+ end
81
+
82
+ # Return the static route for the given request method and path.
83
+ def static_route_for(method, path)
84
+ if h = opts[:static_routes][path]
85
+ h[method] || h[nil]
86
+ end
87
+ end
88
+
89
+ [:get, :post, :delete, :head, :options, :link, :patch, :put, :trace, :unlink].each do |meth|
90
+ request_method = meth.to_s.upcase
91
+ define_method("static_#{meth}") do |path, &block|
92
+ add_static_route(request_method, path, &block)
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ # Add a static route for the given method.
99
+ def add_static_route(method, path, &block)
100
+ (opts[:static_routes][path] ||= {})[method] = block
101
+ end
102
+ end
103
+
104
+ module InstanceMethods
105
+ # If there is a static routing method for the given path, call it
106
+ # instead having the routing tree handle the request.
107
+ def call
108
+ r = @_request
109
+ path_info = r.env['PATH_INFO']
110
+ if route = self.class.static_route_for(r.request_method, r.path_info)
111
+ catch(:halt){r.static_route(&route)}
112
+ else
113
+ super
114
+ end
115
+ end
116
+ end
117
+
118
+ module RequestMethods
119
+ # Assume that this request matches a static route, setting
120
+ # the remaining path to the emptry string and passing
121
+ # control to the given block.
122
+ def static_route(&block)
123
+ @remaining_path = ''
124
+
125
+ always do
126
+ scope.instance_exec(self, &block)
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ register_plugin(:static_routing, StaticRouting)
133
+ end
134
+ end
@@ -159,14 +159,14 @@ 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(_)
162
+ # Append the type routing extension back to the path if it was
163
+ # removed before routing.
164
+ def real_remaining_path
166
165
  if defined?(@type_routing_extension)
167
- @remaining_path += ".#{@type_routing_extension}"
166
+ "#{super}.#{@type_routing_extension}"
167
+ else
168
+ super
168
169
  end
169
- super
170
170
  end
171
171
 
172
172
  private
data/lib/roda/version.rb CHANGED
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 2
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 17
7
+ RodaMinorVersion = 18
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
@@ -131,10 +131,15 @@ describe "matchers" do
131
131
  r.on "posts/:id" do |id|
132
132
  id
133
133
  end
134
+
135
+ r.on "responses-:id" do |id|
136
+ id
137
+ end
134
138
  end
135
139
 
136
140
  body('/posts/123').must_equal '123'
137
141
  status('/post/123').must_equal 404
142
+ body('/responses-123').must_equal '123'
138
143
  end
139
144
 
140
145
  it "should handle multiple params in single string" do
@@ -0,0 +1,82 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "assets_preloading plugin" do
4
+ before do
5
+ app(:bare) do
6
+ plugin :assets, {
7
+ :css => ['app.scss'],
8
+ :js => { :head => ['app.js'] },
9
+ :path => 'spec/assets',
10
+ :public => 'spec',
11
+ }
12
+ plugin :assets_preloading
13
+
14
+ route do |r|
15
+ r.is 'header-css' do
16
+ preload_assets_link_header :css
17
+ end
18
+ r.is 'header-js' do
19
+ preload_assets_link_header [:js, :head]
20
+ end
21
+ r.is 'header-multiple' do
22
+ preload_assets_link_header :css, [:js, :head]
23
+ end
24
+
25
+ r.is 'tags-css' do
26
+ preload_assets_link_tags :css
27
+ end
28
+ r.is 'tags-js' do
29
+ preload_assets_link_tags [:js, :head]
30
+ end
31
+ r.is 'tags-multiple' do
32
+ preload_assets_link_tags :css, [:js, :head]
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ it "preload_assets_link_header returns a well-formed header" do
39
+ html = body('/header-multiple')
40
+
41
+ assets = html.split(',')
42
+ assets.count.must_equal 2
43
+
44
+ assets.each do |asset|
45
+ parts = asset.split(';')
46
+ parts.count.must_equal 3
47
+
48
+ parts[0].scan(/^<.*>$/).count.must_equal 1
49
+ parts.select{|c| c == 'rel=preload' }.count.must_equal 1
50
+ parts.select{|c| c.match(/^as=\w+$/) }.count.must_equal 1
51
+ end
52
+ end
53
+
54
+ it "preload_assets_link_header returns the correct 'as' for an asset type" do
55
+ html = body('/header-css')
56
+ html.scan('as=style').count.must_equal 1
57
+
58
+ html = body('/header-js')
59
+ html.scan('as=script').count.must_equal 1
60
+ end
61
+
62
+ it "preload_assets_link_tags returns well-formed tags" do
63
+ html = body('/tags-multiple')
64
+
65
+ tags = html.scan(/<link.+?>/)
66
+ tags.count.must_equal 2
67
+
68
+ tags.each do |tag|
69
+ tag.scan(' rel="preload"').count.must_equal 1
70
+ tag.scan(/ href=".+"/).count.must_equal 1
71
+ tag.scan(/ as=".+"/).count.must_equal 1
72
+ end
73
+ end
74
+
75
+ it "preload_assets_link_tags returns the correct 'as' for an asset type" do
76
+ html = body('/tags-css')
77
+ html.scan('as="style"').count.must_equal 1
78
+
79
+ html = body('/tags-js')
80
+ html.scan('as="script"').count.must_equal 1
81
+ end
82
+ end
@@ -43,6 +43,15 @@ if run_tests
43
43
  r.is 'test' do
44
44
  "#{assets(:css)}\n#{assets([:js, :head])}"
45
45
  end
46
+
47
+ r.is 'paths_test' do
48
+ css_paths = assets_paths(:css)
49
+ js_paths = assets_paths([:js, :head])
50
+ empty_paths = assets_paths(:empty)
51
+ { 'css' => css_paths, 'js' => js_paths, 'empty' => empty_paths }.map do |k, a|
52
+ "#{k}:#{a.class}:#{a.length}:#{a.join(',')}"
53
+ end.join("\n")
54
+ end
46
55
  end
47
56
  end
48
57
  end
@@ -139,6 +148,23 @@ if run_tests
139
148
  app.assets_opts.values_at(*keys).must_equal [{'Content-Type'=>"C", 'A'=>'B1', 'C'=>'D', 'E'=>'G'}, {'Content-Type'=>"C", 'A'=>'B', 'E'=>'F1'}, {'a'=>'b', 'c'=>'d1'}]
140
149
  end
141
150
 
151
+ it 'assets_paths should return arrays of source paths' do
152
+ html = body('/paths_test')
153
+ html.scan('css:Array:2:/assets/css/app.scss,/assets/css/raw.css').length.must_equal 1
154
+ html.scan('js:Array:1:/assets/js/head/app.js').length.must_equal 1
155
+ html.scan('empty:Array:0').length.must_equal 1
156
+ end
157
+
158
+ it 'assets_paths should return the compiled path in an array' do
159
+ app.compile_assets
160
+ html = body('/paths_test')
161
+ css_hash = app.assets_opts[:compiled]['css']
162
+ js_hash = app.assets_opts[:compiled]['js.head']
163
+ html.scan("css:Array:1:/assets/app.#{css_hash}.css").length.must_equal 1
164
+ html.scan("js:Array:1:/assets/app.head.#{js_hash}.js").length.must_equal 1
165
+ html.scan('empty:Array:0').length.must_equal 1
166
+ end
167
+
142
168
  it 'should handle rendering assets, linking to them, and accepting requests for them when not compiling' do
143
169
  html = body('/test')
144
170
  html.scan(/<link/).length.must_equal 2
@@ -62,5 +62,8 @@ describe "not_allowed plugin" do
62
62
  body('/c', 'REQUEST_METHOD'=>'PATCH').must_equal 'c'
63
63
  status('/c', 'REQUEST_METHOD'=>'PATCH').must_equal 405
64
64
  header('Allow', '/c', 'REQUEST_METHOD'=>'PATCH').must_equal 'GET, POST'
65
+
66
+ @app.plugin :head
67
+ header('Allow', '/c', 'REQUEST_METHOD'=>'PATCH').must_equal 'HEAD, GET, POST'
65
68
  end
66
69
  end
@@ -0,0 +1,108 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "static_routing plugin" do
4
+ it "adds support for static routes that are taken before normal routes" do
5
+ app(:bare) do
6
+ plugin :static_routing
7
+ static_route "/foo" do |r|
8
+ "#{r.path}:#{r.remaining_path}"
9
+ end
10
+ static_route "/bar" do |r|
11
+ r.get{"GET:#{r.path}:#{r.remaining_path}"}
12
+ r.post{"POST:#{r.path}:#{r.remaining_path}"}
13
+ end
14
+ static_get "/bar" do |r|
15
+ r.get{"GET2:#{r.path}:#{r.remaining_path}"}
16
+ end
17
+ static_route "/quux" do |r|
18
+ r.halt [500, {}, []]
19
+ end
20
+
21
+ route do |r|
22
+ r.on 'foo' do
23
+ r.get true do
24
+ 'foo1'
25
+ end
26
+ r.root do
27
+ 'foo2'
28
+ end
29
+ end
30
+
31
+ r.get 'baz' do
32
+ 'baz'
33
+ end
34
+ end
35
+ end
36
+
37
+ 2.times do
38
+ body('/foo').must_equal '/foo:'
39
+ body('/foo/').must_equal 'foo2'
40
+ body('/bar').must_equal 'GET2:/bar:'
41
+ body('/bar', 'REQUEST_METHOD'=>'POST').must_equal 'POST:/bar:'
42
+ status('/bar', 'REQUEST_METHOD'=>'PATCH').must_equal 404
43
+ body('/baz').must_equal 'baz'
44
+ status('/quux').must_equal 500
45
+ @app = Class.new(@app)
46
+ end
47
+ end
48
+
49
+ it "does not allow placeholders in static routes" do
50
+ app(:bare) do
51
+ plugin :static_routing
52
+ static_route "/:foo" do |r|
53
+ "#{r.path}:#{r.remaining_path}"
54
+ end
55
+
56
+ route{}
57
+ end
58
+ body('/:foo').must_equal '/:foo:'
59
+ status('/a').must_equal 404
60
+ end
61
+
62
+ it "duplicates data structures in subclasses" do
63
+ app(:bare) do
64
+ plugin :static_routing
65
+ static_route "/foo" do |r|
66
+ 'foo'
67
+ end
68
+
69
+ route{}
70
+ end
71
+
72
+ old_app = @app
73
+ @app = Class.new(old_app)
74
+ old_app.static_route '/bar' do |r|
75
+ 'bar1'
76
+ end
77
+ old_app.static_get '/foo' do |r|
78
+ 'foop'
79
+ end
80
+ @app.static_route '/bar' do |r|
81
+ 'bar2'
82
+ end
83
+
84
+ body('/foo').must_equal 'foo'
85
+ body('/bar').must_equal 'bar2'
86
+ body('/foo', 'REQUEST_METHOD'=>'POST').must_equal 'foo'
87
+ @app = old_app
88
+ body('/foo').must_equal 'foop'
89
+ body('/bar').must_equal 'bar1'
90
+ body('/foo', 'REQUEST_METHOD'=>'POST').must_equal 'foo'
91
+ end
92
+
93
+ it "freezes static routes when app is frozen" do
94
+ app(:bare) do
95
+ plugin :static_routing
96
+ static_route "/foo"
97
+ freeze
98
+
99
+ proc do
100
+ static_get "/foo"
101
+ end.must_raise
102
+
103
+ proc do
104
+ static_route "/bar"
105
+ end.must_raise
106
+ end
107
+ end
108
+ end
@@ -58,6 +58,7 @@ describe "type_routing plugin" do
58
58
  r.run(sup_app)
59
59
  end
60
60
 
61
+ body('/a', 'HTTP_ACCEPT' => 'text/html').must_equal 'HTML: html'
61
62
  body('/a.json', 'HTTP_ACCEPT' => 'text/html').must_equal 'JSON: json'
62
63
  body('/a.xml', 'HTTP_ACCEPT' => 'application/json').must_equal 'XML: xml'
63
64
  body('/a.html', 'HTTP_ACCEPT' => 'application/xml').must_equal 'HTML: html'
@@ -264,4 +265,32 @@ describe "type_routing plugin" do
264
265
  body('/a.html').must_equal 'HTML'
265
266
  body('/a.json').must_equal 'JSON'
266
267
  end
268
+
269
+ it "removes the handled part from r.remaining_path" do
270
+ app(:bare) do
271
+ plugin :type_routing
272
+
273
+ route do |r|
274
+ r.is 'a' do
275
+ r.html{ r.remaining_path }
276
+ end
277
+ end
278
+ end
279
+
280
+ body('/a.html').must_equal ''
281
+ end
282
+
283
+ it "overrides r.real_remaining_path correctly" do
284
+ app(:bare) do
285
+ plugin :type_routing
286
+
287
+ route do |r|
288
+ r.is 'a' do
289
+ r.html{ r.real_remaining_path }
290
+ end
291
+ end
292
+ end
293
+
294
+ body('/a.html').must_equal '.html'
295
+ end
267
296
  end
data/spec/request_spec.rb CHANGED
@@ -12,6 +12,18 @@ describe "request.path, .remaining_path, and .matched_path" do
12
12
  end
13
13
  end
14
14
 
15
+ describe "request.real_remaining_path" do
16
+ it "should be an alias of remaining_path" do
17
+ app do |r|
18
+ r.on "foo" do
19
+ "#{r.remaining_path}:#{r.real_remaining_path}"
20
+ end
21
+ end
22
+
23
+ body("/foo/bar").must_equal "/bar:/bar"
24
+ end
25
+ end
26
+
15
27
  describe "request.halt" do
16
28
  it "should return rack response as argument given it as argument" do
17
29
  app do |r|
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.17.0
4
+ version: 2.18.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-08-13 00:00:00.000000000 Z
11
+ date: 2016-09-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -183,6 +183,7 @@ extra_rdoc_files:
183
183
  - doc/release_notes/2.15.0.txt
184
184
  - doc/release_notes/2.16.0.txt
185
185
  - doc/release_notes/2.17.0.txt
186
+ - doc/release_notes/2.18.0.txt
186
187
  files:
187
188
  - CHANGELOG
188
189
  - MIT-LICENSE
@@ -203,6 +204,7 @@ files:
203
204
  - doc/release_notes/2.15.0.txt
204
205
  - doc/release_notes/2.16.0.txt
205
206
  - doc/release_notes/2.17.0.txt
207
+ - doc/release_notes/2.18.0.txt
206
208
  - doc/release_notes/2.2.0.txt
207
209
  - doc/release_notes/2.3.0.txt
208
210
  - doc/release_notes/2.4.0.txt
@@ -216,6 +218,7 @@ files:
216
218
  - lib/roda/plugins/_erubis_escaping.rb
217
219
  - lib/roda/plugins/all_verbs.rb
218
220
  - lib/roda/plugins/assets.rb
221
+ - lib/roda/plugins/assets_preloading.rb
219
222
  - lib/roda/plugins/backtracking_array.rb
220
223
  - lib/roda/plugins/caching.rb
221
224
  - lib/roda/plugins/chunked.rb
@@ -277,6 +280,7 @@ files:
277
280
  - lib/roda/plugins/slash_path_empty.rb
278
281
  - lib/roda/plugins/static.rb
279
282
  - lib/roda/plugins/static_path_info.rb
283
+ - lib/roda/plugins/static_routing.rb
280
284
  - lib/roda/plugins/status_handler.rb
281
285
  - lib/roda/plugins/streaming.rb
282
286
  - lib/roda/plugins/symbol_matchers.rb
@@ -301,6 +305,7 @@ files:
301
305
  - spec/opts_spec.rb
302
306
  - spec/plugin/_erubis_escaping_spec.rb
303
307
  - spec/plugin/all_verbs_spec.rb
308
+ - spec/plugin/assets_preloading_spec.rb
304
309
  - spec/plugin/assets_spec.rb
305
310
  - spec/plugin/backtracking_array_spec.rb
306
311
  - spec/plugin/caching_spec.rb
@@ -361,6 +366,7 @@ files:
361
366
  - spec/plugin/shared_vars_spec.rb
362
367
  - spec/plugin/sinatra_helpers_spec.rb
363
368
  - spec/plugin/slash_path_empty_spec.rb
369
+ - spec/plugin/static_routing_spec.rb
364
370
  - spec/plugin/static_spec.rb
365
371
  - spec/plugin/status_handler_spec.rb
366
372
  - spec/plugin/streaming_spec.rb