roda 2.17.0 → 2.18.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 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