roda 3.29.0 → 3.34.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +38 -0
  3. data/README.rdoc +5 -1
  4. data/doc/conventions.rdoc +17 -8
  5. data/doc/release_notes/3.30.0.txt +14 -0
  6. data/doc/release_notes/3.31.0.txt +11 -0
  7. data/doc/release_notes/3.32.0.txt +42 -0
  8. data/doc/release_notes/3.33.0.txt +8 -0
  9. data/doc/release_notes/3.34.0.txt +17 -0
  10. data/lib/roda.rb +2 -0
  11. data/lib/roda/plugins/_symbol_regexp_matchers.rb +2 -0
  12. data/lib/roda/plugins/all_verbs.rb +2 -0
  13. data/lib/roda/plugins/assets.rb +26 -5
  14. data/lib/roda/plugins/backtracking_array.rb +1 -3
  15. data/lib/roda/plugins/class_level_routing.rb +0 -1
  16. data/lib/roda/plugins/content_security_policy.rb +2 -0
  17. data/lib/roda/plugins/default_headers.rb +2 -0
  18. data/lib/roda/plugins/disallow_file_uploads.rb +2 -0
  19. data/lib/roda/plugins/exception_page.rb +17 -9
  20. data/lib/roda/plugins/hash_matcher.rb +1 -1
  21. data/lib/roda/plugins/header_matchers.rb +5 -1
  22. data/lib/roda/plugins/indifferent_params.rb +2 -0
  23. data/lib/roda/plugins/match_affix.rb +1 -1
  24. data/lib/roda/plugins/not_allowed.rb +2 -0
  25. data/lib/roda/plugins/partials.rb +33 -6
  26. data/lib/roda/plugins/path.rb +42 -15
  27. data/lib/roda/plugins/placeholder_string_matchers.rb +2 -0
  28. data/lib/roda/plugins/public.rb +23 -17
  29. data/lib/roda/plugins/relative_path.rb +73 -0
  30. data/lib/roda/plugins/render.rb +21 -5
  31. data/lib/roda/plugins/render_each.rb +11 -3
  32. data/lib/roda/plugins/render_locals.rb +2 -0
  33. data/lib/roda/plugins/run_append_slash.rb +1 -1
  34. data/lib/roda/plugins/sinatra_helpers.rb +4 -4
  35. data/lib/roda/plugins/symbol_matchers.rb +2 -2
  36. data/lib/roda/plugins/typecast_params.rb +4 -6
  37. data/lib/roda/plugins/view_options.rb +2 -0
  38. data/lib/roda/request.rb +2 -4
  39. data/lib/roda/version.rb +1 -1
  40. metadata +13 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 47d34c142084b4dc11049861f97621177bd1071744caff3b18ad113a79308139
4
- data.tar.gz: 7021cfd179b0bd159f93ff31d86b9128686686d2f63c2068863b3ffc5150b44a
3
+ metadata.gz: 4ea0324d5667f9dcde48ff8fd1025b15e0e06861eea776019c0be9be8c46b097
4
+ data.tar.gz: 1712241233e21e7863101192227797976a79f6a3f0895da907a9e2b19242e1ed
5
5
  SHA512:
6
- metadata.gz: 48fa41c207a4e37aaa3deb8eb773480bd015875e8f2c9ef7d4489ef6b2538ad98001afa9ab1adf7555ebc100929a5cd0b7eb1b333cc331255818583c1d1dfb25
7
- data.tar.gz: 4f7103b4730ed2f51309704c8f991c5c3f7fc3c865e07ba917219b54ef9019e0bafddbdeca280bd8343c8fd1e496ca5e3444e674eb59c93e98f5bdf7eab09647
6
+ metadata.gz: 6b8d1b37ed94ee40c0b1f12cc20ab9f734b276805044b059c9ea10375cc64d7d2cc8b0b36049bd21e29d3e45eeefcc8aeaffe0b4bbaf2d36043c16838270e2eb
7
+ data.tar.gz: 1670e3cde892c8cd42e2856d4e938e6da359d0818ee09f7c28e8ec5f4bc4bd627f3aaa8c48aeac8f6d03ab80994078a5cbce69d99595214f288eb9ab1f860c23
data/CHANGELOG CHANGED
@@ -1,3 +1,41 @@
1
+ = 3.34.0 (2020-07-14)
2
+
3
+ * Remove unnecessary conditionals (jeremyevans)
4
+
5
+ * Allow loading the match_affix plugin with a single argument (jeremyevans)
6
+
7
+ * Do not include pre/post context sections if empty in the exception_page plugin (jeremyevans)
8
+
9
+ = 3.33.0 (2020-06-16)
10
+
11
+ * Add :brotli option to public plugin to supplement it to serve brotli-compressed files like :gzip does for gzipped files (hmdne) (#194)
12
+
13
+ * Add url method to path plugin, similar to path but returning the entire URL (jeremyevans)
14
+
15
+ = 3.32.0 (2020-05-15)
16
+
17
+ * Make :dependencies option in assets plugin work correctly with render plugin template caching (jeremyevans) (#191)
18
+
19
+ * Support render method :dependencies option for specifying which files to check for modification (jgarth, jeremyevans) (#192)
20
+
21
+ * Add each_partial to the partials plugin for rendering a partial for each element in an enumerable (jeremyevans)
22
+
23
+ * Make render_each in render_each plugin handle template names with directories and extensions (jeremyevans)
24
+
25
+ = 3.31.0 (2020-04-15)
26
+
27
+ * Add :relative option to path method in path plugin, for generating a method returning relative paths (jeremyevans)
28
+
29
+ * Add relative_path plugin, for turning absolute paths to paths relative to the current request (jeremyevans)
30
+
31
+ = 3.30.0 (2020-03-13)
32
+
33
+ * Support :relative_paths assets plugin option to use relative paths for the assets (jeremyevans)
34
+
35
+ * Make run_append_slash and run_handler plugins work when used together (janko) (#185)
36
+
37
+ * Make :header matcher in header_matchers plugin work for Content-Type and Content-Length (jeremyevans) (#184)
38
+
1
39
  = 3.29.0 (2020-02-14)
2
40
 
3
41
  * Remove specs and old release notes from the gem to reduce gem size by over 35% (jeremyevans)
@@ -418,6 +418,10 @@ You can provide an array to specify multiple request methods and match on any of
418
418
  {method: :post} # matches POST
419
419
  {method: ['post', 'patch']} # matches POST and PATCH
420
420
 
421
+ === true
422
+
423
+ If +true+ is given directly as a matcher, it always matches.
424
+
421
425
  === false, nil
422
426
 
423
427
  If +false+ or +nil+ is given directly as a matcher, it doesn't match anything.
@@ -1107,7 +1111,7 @@ Roda's plugin system is based on the plugin system used by
1107
1111
 
1108
1112
  Roda fully supports the currently supported versions of Ruby (MRI) and JRuby. It may
1109
1113
  support unsupported versions of Ruby or JRuby, but such support may be dropped in any
1110
- minor version of keeping it becomes a support issue. The minimum Ruby version
1114
+ minor version if keeping it becomes a support issue. The minimum Ruby version
1111
1115
  required to run the current version of Roda is 1.9.2.
1112
1116
 
1113
1117
  == License
@@ -15,6 +15,8 @@ For a small application, the following directory layout is recommended:
15
15
  Rakefile
16
16
  app_name.rb
17
17
  assets/
18
+ config.ru
19
+ db.rb
18
20
  migrate/
19
21
  models.rb
20
22
  models/
@@ -25,6 +27,8 @@ For a small application, the following directory layout is recommended:
25
27
  +app_name.rb+ should contain the Roda application, and should reflect the name of your application.
26
28
  So, if your application is named +FooBar+, you should use +foo_bar.rb+.
27
29
 
30
+ +config.ru+ should contain the code the webserver uses to determine which application to run.
31
+
28
32
  +views/+ should contain your template files. This assumes you are using the +render+ plugin
29
33
  and server-side rendering. If you are creating a single page application and just serving
30
34
  JSON, then you won't need a +views+ directory. For small applications, all view files should be
@@ -36,7 +40,11 @@ Again, for pure JSON applications, you won't need a +public+ directory.
36
40
  +assets/+ should contain the source files for your CSS and javascript assets. If you are
37
41
  not using the +assets+ plugin, you won't need an +assets+ directory.
38
42
 
39
- +models.rb+ should contain all code related to your database/ORM. This file should be required
43
+ +db.rb+ should contain the minimum code to setup a database connection, without loading any of
44
+ the applications models. This can be required in cases where you don't want the models loaded,
45
+ such as when running migrations. This file should be required by +models.rb+.
46
+
47
+ +models.rb+ should contain all code related to your ORM. This file should be required
40
48
  by +app_name.rb+. This keeps your model code separate from your web code, making it easier
41
49
  to use outside of your web code. It allows you to get an IRB shell for accessing your models
42
50
  via <tt>irb -r ./models</tt>, without loading the Roda application.
@@ -46,7 +54,7 @@ via irb -r ./models, without loading the Roda application.
46
54
  +migrate/+ should create your database migration files, if you are using an ORM that uses
47
55
  migrations.
48
56
 
49
- +spec/+ should contain your specifications/tests. For a small application, it's recommended
57
+ +spec/+ (or +test/+ should contain your specifications/tests. For a small application, it's recommended
50
58
  to a have a single file for your model tests, and a single file for your web/integration tests.
51
59
 
52
60
  +Rakefile+ should contain the rake tasks for the application. The convention is that the
@@ -83,7 +91,8 @@ The routes used by the +hash_routes+ or +multi_run+ should be stored in routing
83
91
  directory, with one file per prefix.
84
92
 
85
93
  For specs/tests, you should have +spec/models/+ and +spec/web/+, with one file per model in +spec/models/+
86
- and one file per prefix in +spec/web/+.
94
+ and one file per prefix in +spec/web/+. Substitute +spec+ with +test+ if that is what you are using as the
95
+ name of the directory.
87
96
 
88
97
  You should have a separate view subdirectory per prefix. If you are using +hash_routes+ and +view_options+ plugins,
89
98
  use +set_view_subdir+ in your routing files to specify the subdirectory to use, so it doesn't need to be
@@ -105,7 +114,7 @@ subdirectories in the +routes/+ directory, and nested subdirectories in the +vie
105
114
  For a small application, the convention in Roda is to layout your Roda application file (+app_name.rb+) like this:
106
115
 
107
116
  require 'roda'
108
- require './models'
117
+ require_relative 'models'
109
118
 
110
119
  class AppName < Roda
111
120
  SOME_CONSTANT = 1
@@ -138,24 +147,24 @@ used in your route block or views.
138
147
  For larger applications, there are some slight changes to the Roda application file layout:
139
148
 
140
149
  require 'roda'
141
- require './models'
150
+ require_relative 'models'
142
151
 
143
152
  class AppName < Roda
144
153
  SOME_CONSTANT = 1
145
154
 
146
155
  use SomeMiddleware
147
156
 
148
- plugin :render, escape: true
157
+ plugin :render, escape: true, layout: './layout'
149
158
  plugin :assets
150
159
  plugin :view_options
151
160
  plugin :hash_routes
152
- Dir['./routes/*.rb'].each{|f| require f}
161
+ Dir['routes/*.rb'].each{|f| require_relative f}
153
162
 
154
163
  route do |r|
155
164
  r.hash_routes
156
165
  end
157
166
 
158
- Dir['./helpers/*.rb'].each{|f| require f}
167
+ Dir['helpers/*.rb'].each{|f| require_relative f}
159
168
  end
160
169
 
161
170
  After loading the +view_options+ and +hash_routes+ plugin, you require all of your
@@ -0,0 +1,14 @@
1
+ = New Features
2
+
3
+ * A :relative_paths plugin option has been added to the assets
4
+ plugin. This option makes the paths to the asset files in the
5
+ link and script tags relative paths instead of absolute paths.
6
+
7
+ = Other Improvements
8
+
9
+ * The :header matcher in the header_matchers plugin now works
10
+ correctly for the Content-Type and Content-Length headers, which
11
+ are not prefixed with HTTP_ in the rack environment.
12
+
13
+ * The run_append_slash and run_handler plugins now work correctly
14
+ when used together.
@@ -0,0 +1,11 @@
1
+ = New Features
2
+
3
+ * A relative_path plugin has been added, adding a relative_path
4
+ method that will take an absolute path and make it relative to the
5
+ current request by prepending an appropriate prefix. This is
6
+ helpful when using Roda as a static site generator to generate a
7
+ site that can be hosted at any subpath or directly from the
8
+ filesystem.
9
+
10
+ * In the path plugin, the path method now accepts a :relative
11
+ option for generating relative paths instead of absolute paths.
@@ -0,0 +1,42 @@
1
+ = New Features
2
+
3
+ * render_each in the render_each plugin now automatically handles
4
+ template names with subdirectories and extensions. Previously, these
5
+ caused issues unless the :local option was provided. So now you
6
+ can use:
7
+
8
+ render_each(foos, "items/foo")
9
+
10
+ instead of:
11
+
12
+ render_each(foos, "items/foo", :local=>:foo)
13
+
14
+ * each_partial has been added to the partials plugin. It operates
15
+ similarly to render_each, but uses the convention for partial
16
+ template naming. So this:
17
+
18
+ each_partial(foos, "items/foo")
19
+
20
+ is the same as:
21
+
22
+ render_each(foos, "items/_foo", :local=>:foo)
23
+
24
+ = Other Improvements
25
+
26
+ * The :dependencies option in the assets plugin now works correctly
27
+ with compiled templates in the render plugin in uncached mode
28
+ (the default in development). Previously, modifying a dependency
29
+ file would not result in recompiling the asset template when
30
+ requesting the main file.
31
+
32
+ * Method visibility issues in the following plugins have been fixed:
33
+
34
+ * content_security_policy
35
+ * default_headers
36
+ * indifferent_params
37
+ * placeholder_string_matchers
38
+ * symbol_matchers
39
+
40
+ Previously, these plugins made private methods public by mistake
41
+ when overriding them. Additionally, Roda.freeze no longer changes
42
+ the visibility of the set_default_headers private method.
@@ -0,0 +1,8 @@
1
+ = New Features
2
+
3
+ * The path plugin now supports a url method, allowing for returning
4
+ the entire URL instead of just the path for class-based paths.
5
+
6
+ * The public plugin now supports a :brotli option that will directly
7
+ serve brotli-compressed files (with .br extension) similar to how the
8
+ :gzip option directly serves gzipped files (with the .gz extension).
@@ -0,0 +1,17 @@
1
+ = Improvements
2
+
3
+ * Multiple unneeded conditionals have been removed.
4
+
5
+ * pre_content and post_context sections in backtraces are no longer
6
+ included in the exception_page plugin output if they would be
7
+ empty.
8
+
9
+ * The match_affix plugin can be loaded again with a single argument.
10
+ It was originally designed to accept a single argument, but a bug
11
+ introduced in 2.29.0 made it require two arguments.
12
+
13
+ * Core Roda and all plugins that ship with Roda now have 100% branch
14
+ coverage.
15
+
16
+ * The sinatra_helpers plugin no longer emits statement not reached
17
+ warnings in verbose mode.
@@ -197,6 +197,8 @@ class Roda
197
197
  if instance_method(:set_default_headers).owner == ResponseMethods &&
198
198
  instance_method(:default_headers).owner == ResponseMethods
199
199
 
200
+ private
201
+
200
202
  def set_default_headers
201
203
  @headers['Content-Type'] ||= 'text/html'
202
204
  end
@@ -8,6 +8,8 @@ class Roda
8
8
  # using a regexp.
9
9
  module SymbolRegexpMatchers
10
10
  module RequestMethods
11
+ private
12
+
11
13
  # The regular expression to use for matching symbols. By default, any non-empty
12
14
  # segment matches.
13
15
  def _match_symbol_regexp(s)
@@ -34,7 +34,9 @@ class Roda
34
34
  module AllVerbs
35
35
  module RequestMethods
36
36
  %w'delete head options link patch put trace unlink'.each do |verb|
37
+ # :nocov:
37
38
  if ::Rack::Request.method_defined?("#{verb}?")
39
+ # :nocov:
38
40
  class_eval(<<-END, __FILE__, __LINE__+1)
39
41
  def #{verb}(*args, &block)
40
42
  _verb(args, &block) if #{verb}?
@@ -291,6 +291,8 @@ class Roda
291
291
  # non-compiled mode, but will write the metadata to the file if compile_assets is called.
292
292
  # :public :: Path to your public folder, in which compiled files are placed (default: 'public'). Relative
293
293
  # paths will be considered relative to the application's :root option.
294
+ # :relative_paths :: Use relative paths instead of absolute paths when setting up link and script tags for
295
+ # assets.
294
296
  # :sri :: Enables subresource integrity when setting up references to compiled assets. The value should be
295
297
  # :sha256, :sha384, or :sha512 depending on which hash algorithm you want to use. This changes the
296
298
  # hash algorithm that Roda will use when naming compiled asset files. The default is :sha256, you
@@ -323,10 +325,14 @@ class Roda
323
325
 
324
326
  # Load the render, caching, and h plugins, since the assets plugin
325
327
  # depends on them.
326
- def self.load_dependencies(app, _opts = nil)
328
+ def self.load_dependencies(app, opts = OPTS)
327
329
  app.plugin :render
328
330
  app.plugin :caching
329
331
  app.plugin :h
332
+
333
+ if opts[:relative_paths]
334
+ app.plugin :relative_path
335
+ end
330
336
  end
331
337
 
332
338
  # Setup the options for the plugin. See the Assets module RDoc
@@ -399,6 +405,11 @@ class Roda
399
405
  opts[s] ||= {}
400
406
  end
401
407
 
408
+ expanded_deps = opts[:expanded_dependencies] = {}
409
+ opts[:dependencies].each do |file, deps|
410
+ expanded_deps[File.expand_path(file)] = Array(deps)
411
+ end
412
+
402
413
  if headers = opts[:headers]
403
414
  opts[:css_headers] = headers.merge(opts[:css_headers])
404
415
  opts[:js_headers] = headers.merge(opts[:js_headers])
@@ -406,7 +417,7 @@ class Roda
406
417
  opts[:css_headers]['Content-Type'] ||= "text/css; charset=UTF-8".freeze
407
418
  opts[:js_headers]['Content-Type'] ||= "application/javascript; charset=UTF-8".freeze
408
419
 
409
- [:css_headers, :js_headers, :css_opts, :js_opts, :dependencies].each do |s|
420
+ [:css_headers, :js_headers, :css_opts, :js_opts, :dependencies, :expanded_dependencies].each do |s|
410
421
  opts[s].freeze
411
422
  end
412
423
  [:headers, :css, :js].each do |s|
@@ -621,8 +632,10 @@ class Roda
621
632
  stype = ltype.to_s
622
633
 
623
634
  url_prefix = request.script_name if self.class.opts[:add_script_name]
635
+ relative_paths = o[:relative_paths]
624
636
 
625
- if o[:compiled]
637
+ paths = if o[:compiled]
638
+ relative_paths = false if o[:compiled_asset_host]
626
639
  if ukey = _compiled_assets_hash(type, true)
627
640
  ["#{o[:compiled_asset_host]}#{url_prefix}/#{o[:"compiled_#{stype}_prefix"]}.#{ukey}.#{stype}"]
628
641
  else
@@ -642,6 +655,14 @@ class Roda
642
655
  "#{url_prefix}/#{o[:"#{stype}_prefix"]}#{mtime}#{prefix}#{f}#{o[:"#{stype}_suffix"]}"
643
656
  end
644
657
  end
658
+
659
+ if relative_paths
660
+ paths.map! do |path|
661
+ "#{relative_prefix}#{path}"
662
+ end
663
+ end
664
+
665
+ paths
645
666
  end
646
667
 
647
668
  # Return a string containing html tags for the given asset type.
@@ -716,7 +737,7 @@ class Roda
716
737
  content = if file.end_with?(".#{type}")
717
738
  ::File.read(file)
718
739
  else
719
- render_asset_file(file, :template_opts=>o[:"#{type}_opts"])
740
+ render_asset_file(file, :template_opts=>o[:"#{type}_opts"], :dependencies=>o[:expanded_dependencies][file])
720
741
  end
721
742
 
722
743
  o[:postprocessor] ? o[:postprocessor].call(file, type, content) : content
@@ -746,7 +767,7 @@ class Roda
746
767
  # other files, check the modification times of all dependencies and
747
768
  # return the maximum.
748
769
  def asset_last_modified(file)
749
- if deps = self.class.assets_opts[:dependencies][file]
770
+ if deps = self.class.assets_opts[:expanded_dependencies][file]
750
771
  ([file] + Array(deps)).map{|f| ::File.stat(f).mtime}.max
751
772
  else
752
773
  ::File.stat(file).mtime
@@ -33,9 +33,7 @@ class Roda
33
33
  # elements. If the remaining elements could not be
34
34
  # matched, reset the state and continue to the next
35
35
  # entry in the array.
36
- def _match_array(arg, rest=nil)
37
- return super unless rest
38
-
36
+ def _match_array(arg, rest)
39
37
  path = @remaining_path
40
38
  captures = @captures
41
39
  caps = captures.dup
@@ -56,7 +56,6 @@ class Roda
56
56
  # currently have a routing block, setup an empty routing block so that things will still work if
57
57
  # a routing block isn't added.
58
58
  def self.configure(app)
59
- app.route{} unless app.route_block
60
59
  app.opts[:class_level_routes] ||= []
61
60
  end
62
61
 
@@ -304,6 +304,8 @@ class Roda
304
304
  @content_security_policy ||= roda_class.opts[:content_security_policy].dup
305
305
  end
306
306
 
307
+ private
308
+
307
309
  # Set the appropriate content security policy header.
308
310
  def set_default_headers
309
311
  super
@@ -31,6 +31,8 @@ class Roda
31
31
  if owner == Base::ResponseMethods || (owner == response_class && app.opts[:set_default_headers_overridder] == response_class)
32
32
  app.opts[:set_default_headers_overridder] = response_class
33
33
  response_class.class_eval(<<-END, __FILE__, __LINE__+1)
34
+ private
35
+
34
36
  def set_default_headers
35
37
  h = @headers
36
38
  #{headers.map{|k,v| "h[#{k.inspect}] ||= #{v.inspect}"}.join('; ')}
@@ -1,6 +1,8 @@
1
1
  # frozen-string-literal: true
2
2
 
3
+ # :nocov:
3
4
  raise LoadError, "disallow_file_uploads plugin not supported on Rack <1.6" if Rack.release < '1.6'
5
+ # :nocov:
4
6
 
5
7
  #
6
8
  class Roda
@@ -244,25 +244,33 @@ END
244
244
 
245
245
  frames = exception.backtrace.map.with_index do |line, i|
246
246
  frame = {:id=>i}
247
- if line =~ /(.*?):(\d+)(:in `(.*)')?/
247
+ if line =~ /\A(.*?):(\d+)(?::in `(.*)')?\Z/
248
248
  filename = frame[:filename] = $1
249
249
  lineno = frame[:lineno] = $2.to_i
250
- frame[:function] = $4
250
+ frame[:function] = $3
251
251
 
252
252
  begin
253
253
  lineno -= 1
254
254
  lines = ::File.readlines(filename)
255
- pre_lineno = frame[:pre_context_lineno] = [lineno-context, 0].max
256
- frame[:pre_context] = lines[pre_lineno...lineno]
257
- frame[:context_line] = lines[lineno].chomp
258
- post_lineno = frame[:post_context_lineno] = [lineno+context, lines.size].min
259
- frame[:post_context] = lines[lineno+1..post_lineno]
255
+ if line = lines[lineno]
256
+ pre_lineno = [lineno-context, 0].max
257
+ if (pre_context = lines[pre_lineno...lineno]) && !pre_context.empty?
258
+ frame[:pre_context_lineno] = pre_lineno
259
+ frame[:pre_context] = pre_context
260
+ end
261
+
262
+ post_lineno = [lineno+context, lines.size].min
263
+ if (post_context = lines[lineno+1..post_lineno]) && !post_context.empty?
264
+ frame[:post_context_lineno] = post_lineno
265
+ frame[:post_context] = post_context
266
+ end
267
+
268
+ frame[:context_line] = line.chomp
269
+ end
260
270
  rescue
261
271
  end
262
272
 
263
273
  frame
264
- else
265
- nil
266
274
  end
267
275
  end.compact
268
276
 
@@ -8,7 +8,7 @@ class Roda
8
8
  #
9
9
  # class App < Roda
10
10
  # hash_matcher(:foo) do |v|
11
- # self['foo'] == v
11
+ # params['foo'] == v
12
12
  # end
13
13
  #
14
14
  # route do
@@ -51,7 +51,11 @@ class Roda
51
51
 
52
52
  # Match if the given uppercase key is present inside the environment.
53
53
  def match_header(key)
54
- if v = @env["HTTP_#{key.upcase.tr("-","_")}"]
54
+ key = key.upcase.tr("-","_")
55
+ unless key == "CONTENT_TYPE" || key == "CONTENT_LENGTH"
56
+ key = "HTTP_#{key}"
57
+ end
58
+ if v = @env[key]
55
59
  @captures << v
56
60
  end
57
61
  end
@@ -64,6 +64,8 @@ class Roda
64
64
  module RequestMethods
65
65
  QUERY_PARSER = Rack::Utils.default_query_parser = QueryParser.new(QueryParser::Params, 65536, 100)
66
66
 
67
+ private
68
+
67
69
  def query_parser
68
70
  QUERY_PARSER
69
71
  end
@@ -28,7 +28,7 @@ class Roda
28
28
  #
29
29
  # This plugin automatically loads the placeholder_string_matchers plugin.
30
30
  module MatchAffix
31
- def self.load_dependencies(app, _prefix, _suffix)
31
+ def self.load_dependencies(app, _prefix, _suffix=nil)
32
32
  app.plugin :placeholder_string_matchers
33
33
  end
34
34
 
@@ -104,7 +104,9 @@ class Roda
104
104
  # arguments, record the verb used. If given an argument, add an is
105
105
  # check with the arguments.
106
106
  %w'get post delete head options link patch put trace unlink'.each do |verb|
107
+ # :nocov:
107
108
  if ::Rack::Request.method_defined?("#{verb}?")
109
+ # :nocov:
108
110
  class_eval(<<-END, __FILE__, __LINE__+1)
109
111
  def #{verb}(*args, &block)
110
112
  if (empty = args.empty?) && @_is_verbs
@@ -18,27 +18,54 @@ class Roda
18
18
  # render('_test')
19
19
  # render('dir/_test')
20
20
  #
21
- # Note that this plugin automatically loads the :render plugin.
21
+ # To render the same template once for each object in an enumerable,
22
+ # you can use the +render_partials+ method:
23
+ #
24
+ # each_partial([1,2,3], :foo) # uses _foo.erb
25
+ #
26
+ # This is basically equivalent to:
27
+ #
28
+ # render_each([1,2,3], "_foo", local: :foo)
29
+ #
30
+ # This plugin depends on the render and render_each plugins.
22
31
  module Partials
23
- # Depend on the render plugin, since this overrides
24
- # some of its methods.
32
+ # Depend on the render plugin, passing received options to it.
33
+ # Also depend on the render_each plugin.
25
34
  def self.load_dependencies(app, opts=OPTS)
26
35
  app.plugin :render, opts
36
+ app.plugin :render_each
27
37
  end
28
38
 
29
39
  module InstanceMethods
40
+ # For each object in the given enumerable, render the given
41
+ # template (prefixing the template filename with an underscore).
42
+ def each_partial(enum, template, opts=OPTS)
43
+ unless opts.has_key?(:local)
44
+ opts = Hash[opts]
45
+ opts[:local] = render_each_default_local(template)
46
+ end
47
+ render_each(enum, partial_template_name(template.to_s), opts)
48
+ end
49
+
30
50
  # Renders the given template without a layout, but
31
51
  # prefixes the template filename to use with an
32
52
  # underscore.
33
53
  def partial(template, opts=OPTS)
34
54
  opts = parse_template_opts(template, opts)
35
55
  if opts[:template]
36
- template = opts[:template].split('/')
37
- template[-1] = "_#{template[-1]}"
38
- opts[:template] = template.join('/')
56
+ opts[:template] = partial_template_name(opts[:template])
39
57
  end
40
58
  render_template(opts)
41
59
  end
60
+
61
+ private
62
+
63
+ # Prefix the template base filename with an underscore.
64
+ def partial_template_name(template)
65
+ segments = template.split('/')
66
+ segments[-1] = "_#{segments[-1]}"
67
+ segments.join('/')
68
+ end
42
69
  end
43
70
  end
44
71
 
@@ -10,7 +10,8 @@ class Roda
10
10
  #
11
11
  # Additionally, you can call the +path+ class method with a class and a block, and it will register
12
12
  # the class. You can then call the +path+ instance method with an instance of that class, and it will
13
- # execute the block in the context of the route block scope with the arguments provided to path.
13
+ # execute the block in the context of the route block scope with the arguments provided to path. You
14
+ # can call the +url+ instance method with the same arguments as the +path+ method to get the full URL.
14
15
  #
15
16
  # Example:
16
17
  #
@@ -46,21 +47,23 @@ class Roda
46
47
  #
47
48
  # r.post 'quux' do
48
49
  # bar = Quux[1]
49
- # r.redirect path(quux, '/bar') # /quux/1/bar
50
+ # r.redirect url(quux, '/bar') # http://example.com/quux/1/bar
50
51
  # end
51
52
  # end
52
53
  #
53
- # The path method accepts the following options when not called with a class:
54
+ # The path class method accepts the following options when not called with a class:
54
55
  #
55
56
  # :add_script_name :: Prefix the path generated with SCRIPT_NAME. This defaults to the app's
56
57
  # :add_script_name option.
57
58
  # :name :: Provide a different name for the method, instead of using <tt>*_path</tt>.
59
+ # :relative :: Generate paths relative to the current request instead of absolute paths by prepending
60
+ # an appropriate prefix. This implies :add_script_name.
58
61
  # :url :: Create a url method in addition to the path method, which will prefix the string generated
59
62
  # with the appropriate scheme, host, and port. If true, creates a <tt>*_url</tt>
60
63
  # method. If a Symbol or String, uses the value as the url method name.
61
64
  # :url_only :: Do not create a path method, just a url method.
62
65
  #
63
- # Note that if :add_script_name, :url, or :url_only is used, the path method will also create a
66
+ # Note that if :add_script_name, :relative, :url, or :url_only is used, the path method will also create a
64
67
  # <tt>_*_path</tt> private method.
65
68
  module Path
66
69
  DEFAULT_PORTS = {'http' => 80, 'https' => 443}.freeze
@@ -121,16 +124,31 @@ class Roda
121
124
 
122
125
  meth = opts[:name] || "#{name}_path"
123
126
  url = opts[:url]
127
+ url_only = opts[:url_only]
128
+ relative = opts[:relative]
124
129
  add_script_name = opts.fetch(:add_script_name, self.opts[:add_script_name])
125
130
 
126
- if add_script_name || url || opts[:url_only]
131
+ if relative
132
+ if (url || url_only)
133
+ raise RodaError, "cannot provide :url or :url_only option if using :relative option"
134
+ end
135
+ add_script_name = true
136
+ plugin :relative_path
137
+ end
138
+
139
+ if add_script_name || url || url_only || relative
127
140
  _meth = "_#{meth}"
128
141
  define_method(_meth, &block)
129
142
  private _meth
130
143
  end
131
144
 
132
- unless opts[:url_only]
133
- if add_script_name
145
+ unless url_only
146
+ if relative
147
+ define_method(meth) do |*a, &blk|
148
+ # Allow calling private _method to get path
149
+ relative_path(request.script_name.to_s + send(_meth, *a, &blk))
150
+ end
151
+ elsif add_script_name
134
152
  define_method(meth) do |*a, &blk|
135
153
  # Allow calling private _method to get path
136
154
  request.script_name.to_s + send(_meth, *a, &blk)
@@ -140,7 +158,7 @@ class Roda
140
158
  end
141
159
  end
142
160
 
143
- if url || opts[:url_only]
161
+ if url || url_only
144
162
  url_meth = if url.is_a?(String) || url.is_a?(Symbol)
145
163
  url
146
164
  else
@@ -148,14 +166,8 @@ class Roda
148
166
  end
149
167
 
150
168
  url_block = lambda do |*a, &blk|
151
- r = request
152
- scheme = r.scheme
153
- port = r.port
154
- uri = ["#{scheme}://#{r.host}#{":#{port}" unless DEFAULT_PORTS[scheme] == port}"]
155
- uri << request.script_name.to_s if add_script_name
156
169
  # Allow calling private _method to get path
157
- uri << send(_meth, *a, &blk)
158
- File.join(uri)
170
+ "#{_base_url}#{request.script_name if add_script_name}#{send(_meth, *a, &blk)}"
159
171
  end
160
172
 
161
173
  define_method(url_meth, &url_block)
@@ -190,6 +202,21 @@ class Roda
190
202
  path = request.script_name.to_s + path if opts[:add_script_name]
191
203
  path
192
204
  end
205
+
206
+ # Similar to #path, but returns a complete URL.
207
+ def url(*args, &block)
208
+ "#{_base_url}#{path(*args, &block)}"
209
+ end
210
+
211
+ private
212
+
213
+ # The string to prepend to the path to make the path a URL.
214
+ def _base_url
215
+ r = @_request
216
+ scheme = r.scheme
217
+ port = r.port
218
+ "#{scheme}://#{r.host}#{":#{port}" unless DEFAULT_PORTS[scheme] == port}"
219
+ end
193
220
  end
194
221
  end
195
222
 
@@ -27,6 +27,8 @@ class Roda
27
27
  end
28
28
 
29
29
  module RequestMethods
30
+ private
31
+
30
32
  def _match_string(str)
31
33
  if str.index(":")
32
34
  consume(self.class.cached_matcher(str){Regexp.escape(str).gsub(/:(\w+)/){|m| _match_symbol_regexp($1)}})
@@ -42,6 +42,8 @@ class Roda
42
42
  # :default_mime :: The default mime type to use if the mime type is not recognized.
43
43
  # :gzip :: Whether to serve already gzipped files with a .gz extension for clients
44
44
  # supporting gzipped transfer encoding.
45
+ # :brotli :: Whether to serve already brotli-compressed files with a .br extension
46
+ # for clients supporting brotli transfer encoding.
45
47
  # :headers :: A hash of headers to use for statically served files
46
48
  # :root :: Use this option for the root of the public directory (default: "public")
47
49
  def self.configure(app, opts={})
@@ -52,6 +54,7 @@ class Roda
52
54
  end
53
55
  app.opts[:public_server] = ::Rack::File.new(app.opts[:public_root], opts[:headers]||{}, opts[:default_mime] || 'text/plain')
54
56
  app.opts[:public_gzip] = opts[:gzip]
57
+ app.opts[:public_brotli] = opts[:brotli]
55
58
  end
56
59
 
57
60
  module RequestMethods
@@ -65,23 +68,8 @@ class Roda
65
68
  server = roda_opts[:public_server]
66
69
  path = ::File.join(server.root, *public_path_segments(path))
67
70
 
68
- if roda_opts[:public_gzip] && env['HTTP_ACCEPT_ENCODING'] =~ /\bgzip\b/
69
- gzip_path = path + '.gz'
70
-
71
- if public_file_readable?(gzip_path)
72
- res = public_serve(server, gzip_path)
73
- headers = res[1]
74
-
75
- unless res[0] == 304
76
- if mime_type = ::Rack::Mime.mime_type(::File.extname(path), 'text/plain')
77
- headers['Content-Type'] = mime_type
78
- end
79
- headers['Content-Encoding'] = 'gzip'
80
- end
81
-
82
- halt res
83
- end
84
- end
71
+ public_serve_compressed(server, path, '.br', 'br') if roda_opts[:public_brotli]
72
+ public_serve_compressed(server, path, '.gz', 'gzip') if roda_opts[:public_gzip]
85
73
 
86
74
  if public_file_readable?(path)
87
75
  halt public_serve(server, path)
@@ -113,6 +101,24 @@ class Roda
113
101
  # :nocov:
114
102
  end
115
103
 
104
+ def public_serve_compressed(server, path, suffix, encoding)
105
+ if env['HTTP_ACCEPT_ENCODING'] =~ /\b#{encoding}\b/
106
+ compressed_path = path + suffix
107
+
108
+ if public_file_readable?(compressed_path)
109
+ res = public_serve(server, compressed_path)
110
+ headers = res[1]
111
+
112
+ unless res[0] == 304
113
+ headers['Content-Type'] = ::Rack::Mime.mime_type(::File.extname(path), 'text/plain')
114
+ headers['Content-Encoding'] = encoding
115
+ end
116
+
117
+ halt res
118
+ end
119
+ end
120
+ end
121
+
116
122
  if ::Rack.release > '2'
117
123
  # Serve the given path using the given Rack::File server.
118
124
  def public_serve(server, path)
@@ -0,0 +1,73 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The relative_path plugin adds a relative_path method that accepts
7
+ # an absolute path and returns a path relative to the current request
8
+ # by adding an appropriate prefix:
9
+ #
10
+ # plugin :relative_path
11
+ # route do |r|
12
+ # relative_path("/foo")
13
+ # end
14
+ #
15
+ # # GET /
16
+ # "./foo"
17
+ #
18
+ # # GET /bar
19
+ # "./foo"
20
+ #
21
+ # # GET /bar/
22
+ # "../foo"
23
+ #
24
+ # # GET /bar/baz/quux
25
+ # "../../foo"
26
+ #
27
+ # It also offers a relative_prefix method that returns a string that can
28
+ # be prepended to an absolute path. This can be more efficient if you
29
+ # need to convert multiple paths.
30
+ #
31
+ # This plugin is mostly designed for applications using Roda as a static
32
+ # site generator, where the generated site can be hosted at any subpath.
33
+ module RelativePath
34
+ module InstanceMethods
35
+ # Return a relative path for the absolute path based on the current path
36
+ # of the request by adding the appropriate prefix.
37
+ def relative_path(absolute_path)
38
+ relative_prefix + absolute_path
39
+ end
40
+
41
+ # Return a relative prefix to append to an absolute path to a relative path
42
+ # based on the current path of the request.
43
+ def relative_prefix
44
+ env = @_request.env
45
+ script_name = env["SCRIPT_NAME"]
46
+ path_info = env["PATH_INFO"]
47
+
48
+ # Check path begins with slash. All valid paths should, but in case this
49
+ # request is bad, just skip using a relative prefix.
50
+ case script_name.getbyte(0)
51
+ when nil # SCRIPT_NAME empty
52
+ unless path_info.getbyte(0) == 47 # PATH_INFO starts with /
53
+ return ''
54
+ end
55
+ when 47 # SCRIPT_NAME starts with /
56
+ # nothing
57
+ else
58
+ return ''
59
+ end
60
+
61
+ slash_count = script_name.count('/') + path_info.count('/')
62
+ if slash_count > 1
63
+ ("../" * (slash_count - 2)) << ".."
64
+ else
65
+ "."
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ register_plugin(:relative_path, RelativePath)
72
+ end
73
+ end
@@ -241,12 +241,13 @@ class Roda
241
241
  # template file has been modified. This is an internal class and
242
242
  # the API is subject to change at any time.
243
243
  class TemplateMtimeWrapper
244
- def initialize(template_class, path, *template_args)
244
+ def initialize(template_class, path, dependencies, *template_args)
245
245
  @template_class = template_class
246
246
  @path = path
247
247
  @template_args = template_args
248
+ @dependencies = ([path] + Array(dependencies)) if dependencies
248
249
 
249
- @mtime = (File.mtime(path) if File.file?(path))
250
+ @mtime = template_last_modified if File.file?(path)
250
251
  @template = template_class.new(path, *template_args)
251
252
  end
252
253
 
@@ -257,17 +258,28 @@ class Roda
257
258
  @template.render(*args, &block)
258
259
  end
259
260
 
261
+ # Return when the template was last modified. If the template depends on any
262
+ # other files, check the modification times of all dependencies and
263
+ # return the maximum.
264
+ def template_last_modified
265
+ if deps = @dependencies
266
+ deps.map{|f| File.mtime(f)}.max
267
+ else
268
+ File.mtime(@path)
269
+ end
270
+ end
271
+
260
272
  # If the template file has been updated, return true and update
261
273
  # the template object and the modification time. Other return false.
262
274
  def modified?
263
275
  begin
264
- mtime = File.mtime(path = @path)
276
+ mtime = template_last_modified
265
277
  rescue
266
278
  # ignore errors
267
279
  else
268
280
  if mtime != @mtime
269
281
  @mtime = mtime
270
- @template = @template_class.new(path, *@template_args)
282
+ @template = @template_class.new(@path, *@template_args)
271
283
  return true
272
284
  end
273
285
  end
@@ -275,7 +287,9 @@ class Roda
275
287
  false
276
288
  end
277
289
 
290
+ # :nocov:
278
291
  if COMPILED_METHOD_SUPPORT
292
+ # :nocov:
279
293
  # Compile a method in the given module with the given name that will
280
294
  # call the compiled template method, updating the compiled template method
281
295
  def define_compiled_method(roda_class, method_name, locals_keys=EMPTY_ARRAY)
@@ -325,7 +339,9 @@ class Roda
325
339
  def inherited(subclass)
326
340
  super
327
341
  opts = subclass.opts[:render] = subclass.opts[:render].dup
342
+ # :nocov:
328
343
  if COMPILED_METHOD_SUPPORT
344
+ # :nocov:
329
345
  opts[:template_method_cache] = (opts[:cache_class] || RodaCache).new
330
346
  end
331
347
  opts[:cache] = opts[:cache].dup
@@ -576,7 +592,7 @@ class Roda
576
592
  !opts[:inline]
577
593
 
578
594
  if render_opts[:check_template_mtime] && !opts[:template_block] && !cache
579
- template = TemplateMtimeWrapper.new(opts[:template_class], opts[:path], 1, template_opts)
595
+ template = TemplateMtimeWrapper.new(opts[:template_class], opts[:path], opts[:dependencies], 1, template_opts)
580
596
 
581
597
  if define_compiled_method
582
598
  method_name = :"_roda_template_#{self.class.object_id}_#{method_cache_key}"
@@ -26,7 +26,9 @@ class Roda
26
26
  #
27
27
  # Will render the +foo+ template, but the local variable used inside
28
28
  # the template will be +bar+. You can use <tt>local: nil</tt> to
29
- # not set a local variable inside the template.
29
+ # not set a local variable inside the template. By default, the
30
+ # local variable name is based on the template name, with any
31
+ # directories and file extensions removed.
30
32
  module RenderEach
31
33
  # Load the render plugin before this plugin, since this plugin
32
34
  # calls the render method.
@@ -45,11 +47,11 @@ class Roda
45
47
  # set a local variable. If not set, uses the template name.
46
48
  def render_each(enum, template, opts=(no_opts = true; optimized_template = _cached_render_each_template_method(template); OPTS))
47
49
  if optimized_template
48
- return _optimized_render_each(enum, optimized_template, template.to_s.to_sym, {})
50
+ return _optimized_render_each(enum, optimized_template, render_each_default_local(template), {})
49
51
  elsif opts.has_key?(:local)
50
52
  as = opts[:local]
51
53
  else
52
- as = template.to_s.to_sym
54
+ as = render_each_default_local(template)
53
55
  if no_opts && optimized_template.nil? && (optimized_template = _optimized_render_method_for_locals(template, (locals = {as=>nil})))
54
56
  return _optimized_render_each(enum, optimized_template, as, locals)
55
57
  end
@@ -77,6 +79,12 @@ class Roda
77
79
 
78
80
  private
79
81
 
82
+ # The default local variable name to use for the template, if the :local option
83
+ # is not used when calling render_each.
84
+ def render_each_default_local(template)
85
+ File.basename(template.to_s).sub(/\..+\z/, '').to_sym
86
+ end
87
+
80
88
  if Render::COMPILED_METHOD_SUPPORT
81
89
  # If compiled method support is enabled in the render plugin, return the
82
90
  # method name to call to render the template. Return false if not given
@@ -43,7 +43,9 @@ class Roda
43
43
  module InstanceMethods
44
44
  private
45
45
 
46
+ # :nocov:
46
47
  if Render::COMPILED_METHOD_SUPPORT
48
+ # :nocov:
47
49
  # Disable use of cached templates, since it assumes a render/view call with no
48
50
  # options will have no locals.
49
51
  def _cached_template_method(template)
@@ -33,7 +33,7 @@ class Roda
33
33
  # does not contain a trailing slash, a trailing slash is appended to the
34
34
  # path internally, or a redirect is issued when configured with
35
35
  # <tt>use_redirects: true</tt>.
36
- def run(app)
36
+ def run(*)
37
37
  if remaining_path.empty?
38
38
  if scope.opts[:run_append_slash_redirect]
39
39
  redirect("#{path}/")
@@ -374,7 +374,7 @@ class Roda
374
374
 
375
375
  module ResponseMethods
376
376
  # Set or retrieve the response status code.
377
- def status(value = (return @status; nil))
377
+ def status(value = nil || (return @status))
378
378
  @status = value
379
379
  end
380
380
 
@@ -401,7 +401,7 @@ class Roda
401
401
 
402
402
  # Set multiple response headers with Hash, or return the headers if no
403
403
  # argument is given.
404
- def headers(hash = (return @headers; nil))
404
+ def headers(hash = nil || (return @headers))
405
405
  @headers.merge!(hash)
406
406
  end
407
407
 
@@ -412,7 +412,7 @@ class Roda
412
412
 
413
413
  # Set the Content-Type of the response body given a media type or file
414
414
  # extension. See plugin documentation for options.
415
- def content_type(type = (return @headers["Content-Type"]; nil), opts = OPTS)
415
+ def content_type(type = nil || (return @headers["Content-Type"]), opts = OPTS)
416
416
  unless (mime_type = mime_type(type) || opts[:default])
417
417
  raise RodaError, "Unknown media type: #{type}"
418
418
  end
@@ -478,7 +478,7 @@ class Roda
478
478
  # If a type and value are given, set the value in Rack's MIME registry.
479
479
  # If only a type is given, lookup the type in Rack's MIME registry and
480
480
  # return it.
481
- def mime_type(type=(return; nil), value = nil)
481
+ def mime_type(type=nil || (return), value = nil)
482
482
  return type.to_s if type.to_s.include?('/')
483
483
  type = ".#{type}" unless type.to_s[0] == ?.
484
484
  if value
@@ -6,7 +6,7 @@ class Roda
6
6
  # The symbol_matchers plugin allows you do define custom regexps to use
7
7
  # for specific symbols. For example, if you have a route such as:
8
8
  #
9
- # r.on :username do
9
+ # r.on :username do |username|
10
10
  # # ...
11
11
  # end
12
12
  #
@@ -28,7 +28,7 @@ class Roda
28
28
  # If the placeholder_string_matchers plugin is loaded, this feature also applies to
29
29
  # placeholders in strings, so the following:
30
30
  #
31
- # r.on "users/:username" do
31
+ # r.on "users/:username" do |username|
32
32
  # # ...
33
33
  # end
34
34
  #
@@ -742,9 +742,7 @@ class Roda
742
742
  return
743
743
  end
744
744
 
745
- if v = self[key]
746
- v.subkey(keys, do_raise)
747
- end
745
+ self[key].subkey(keys, do_raise)
748
746
  rescue => e
749
747
  handle_error(key, reason, e)
750
748
  end
@@ -808,10 +806,10 @@ class Roda
808
806
  @nested_params = nil
809
807
 
810
808
  if capturing_started
811
- # Unset capturing if capturing was not already started.
809
+ # Unset capturing if capturing was already started.
812
810
  @capture = nil
813
811
  else
814
- # If capturing was already started, update cached nested params
812
+ # If capturing was not already started, update cached nested params
815
813
  # before resetting symbolize setting.
816
814
  @nested_params = nested_params
817
815
  end
@@ -878,7 +876,7 @@ class Roda
878
876
  def handle_error(key, reason, e, do_raise=false)
879
877
  case e
880
878
  when String
881
- handle_error(key, reason, Error.new(e), do_raise=false)
879
+ handle_error(key, reason, Error.new(e), do_raise)
882
880
  when Error, ArgumentError
883
881
  if @capture && (le = @capture.last) && le == e
884
882
  raise e if do_raise
@@ -126,7 +126,9 @@ class Roda
126
126
 
127
127
  private
128
128
 
129
+ # :nocov:
129
130
  if Render::COMPILED_METHOD_SUPPORT
131
+ # :nocov:
130
132
  # Return nil if using custom view or layout options.
131
133
  # If using a view subdir, prefix the template key with the subdir.
132
134
  def _cached_template_method_key(template)
@@ -466,10 +466,8 @@ class Roda
466
466
  rp = @remaining_path
467
467
  if rp.getbyte(0) == 47
468
468
  if last = rp.index('/', 1)
469
- if last > 1
470
- @captures << rp[1, last-1]
471
- @remaining_path = rp[last, rp.length]
472
- end
469
+ @captures << rp[1, last-1]
470
+ @remaining_path = rp[last, rp.length]
473
471
  elsif rp.length > 1
474
472
  @captures << rp[1,rp.length]
475
473
  @remaining_path = ""
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 3
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 29
7
+ RodaMinorVersion = 34
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
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: 3.29.0
4
+ version: 3.34.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: 2020-02-14 00:00:00.000000000 Z
11
+ date: 2020-07-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -205,6 +205,11 @@ extra_rdoc_files:
205
205
  - doc/release_notes/3.27.0.txt
206
206
  - doc/release_notes/3.28.0.txt
207
207
  - doc/release_notes/3.29.0.txt
208
+ - doc/release_notes/3.30.0.txt
209
+ - doc/release_notes/3.31.0.txt
210
+ - doc/release_notes/3.32.0.txt
211
+ - doc/release_notes/3.33.0.txt
212
+ - doc/release_notes/3.34.0.txt
208
213
  files:
209
214
  - CHANGELOG
210
215
  - MIT-LICENSE
@@ -235,6 +240,11 @@ files:
235
240
  - doc/release_notes/3.28.0.txt
236
241
  - doc/release_notes/3.29.0.txt
237
242
  - doc/release_notes/3.3.0.txt
243
+ - doc/release_notes/3.30.0.txt
244
+ - doc/release_notes/3.31.0.txt
245
+ - doc/release_notes/3.32.0.txt
246
+ - doc/release_notes/3.33.0.txt
247
+ - doc/release_notes/3.34.0.txt
238
248
  - doc/release_notes/3.4.0.txt
239
249
  - doc/release_notes/3.5.0.txt
240
250
  - doc/release_notes/3.6.0.txt
@@ -314,6 +324,7 @@ files:
314
324
  - lib/roda/plugins/placeholder_string_matchers.rb
315
325
  - lib/roda/plugins/precompile_templates.rb
316
326
  - lib/roda/plugins/public.rb
327
+ - lib/roda/plugins/relative_path.rb
317
328
  - lib/roda/plugins/render.rb
318
329
  - lib/roda/plugins/render_each.rb
319
330
  - lib/roda/plugins/render_locals.rb