roda 3.28.0 → 3.33.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.
Files changed (208) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +40 -0
  3. data/README.rdoc +15 -1
  4. data/doc/conventions.rdoc +17 -8
  5. data/doc/release_notes/3.29.0.txt +15 -0
  6. data/doc/release_notes/3.30.0.txt +14 -0
  7. data/doc/release_notes/3.31.0.txt +11 -0
  8. data/doc/release_notes/3.32.0.txt +42 -0
  9. data/doc/release_notes/3.33.0.txt +8 -0
  10. data/lib/roda.rb +3 -0
  11. data/lib/roda/plugins/_symbol_regexp_matchers.rb +2 -0
  12. data/lib/roda/plugins/assets.rb +26 -5
  13. data/lib/roda/plugins/caching.rb +2 -0
  14. data/lib/roda/plugins/common_logger.rb +1 -1
  15. data/lib/roda/plugins/content_security_policy.rb +2 -0
  16. data/lib/roda/plugins/default_headers.rb +2 -0
  17. data/lib/roda/plugins/exception_page.rb +9 -5
  18. data/lib/roda/plugins/hash_matcher.rb +1 -1
  19. data/lib/roda/plugins/header_matchers.rb +5 -1
  20. data/lib/roda/plugins/indifferent_params.rb +4 -0
  21. data/lib/roda/plugins/partials.rb +33 -6
  22. data/lib/roda/plugins/path.rb +42 -15
  23. data/lib/roda/plugins/placeholder_string_matchers.rb +2 -0
  24. data/lib/roda/plugins/public.rb +25 -17
  25. data/lib/roda/plugins/relative_path.rb +73 -0
  26. data/lib/roda/plugins/render.rb +17 -5
  27. data/lib/roda/plugins/render_each.rb +11 -3
  28. data/lib/roda/plugins/run_append_slash.rb +1 -1
  29. data/lib/roda/plugins/symbol_matchers.rb +2 -2
  30. data/lib/roda/version.rb +1 -1
  31. metadata +13 -214
  32. data/Rakefile +0 -108
  33. data/doc/release_notes/1.0.0.txt +0 -329
  34. data/doc/release_notes/1.1.0.txt +0 -226
  35. data/doc/release_notes/1.2.0.txt +0 -406
  36. data/doc/release_notes/1.3.0.txt +0 -109
  37. data/doc/release_notes/2.0.0.txt +0 -75
  38. data/doc/release_notes/2.1.0.txt +0 -124
  39. data/doc/release_notes/2.10.0.txt +0 -27
  40. data/doc/release_notes/2.11.0.txt +0 -70
  41. data/doc/release_notes/2.12.0.txt +0 -40
  42. data/doc/release_notes/2.13.0.txt +0 -10
  43. data/doc/release_notes/2.14.0.txt +0 -44
  44. data/doc/release_notes/2.15.0.txt +0 -53
  45. data/doc/release_notes/2.16.0.txt +0 -48
  46. data/doc/release_notes/2.17.0.txt +0 -62
  47. data/doc/release_notes/2.18.0.txt +0 -69
  48. data/doc/release_notes/2.19.0.txt +0 -30
  49. data/doc/release_notes/2.2.0.txt +0 -97
  50. data/doc/release_notes/2.20.0.txt +0 -5
  51. data/doc/release_notes/2.21.0.txt +0 -17
  52. data/doc/release_notes/2.22.0.txt +0 -41
  53. data/doc/release_notes/2.23.0.txt +0 -29
  54. data/doc/release_notes/2.24.0.txt +0 -65
  55. data/doc/release_notes/2.25.0.txt +0 -14
  56. data/doc/release_notes/2.26.0.txt +0 -13
  57. data/doc/release_notes/2.27.0.txt +0 -56
  58. data/doc/release_notes/2.28.0.txt +0 -17
  59. data/doc/release_notes/2.29.0.txt +0 -156
  60. data/doc/release_notes/2.3.0.txt +0 -109
  61. data/doc/release_notes/2.4.0.txt +0 -55
  62. data/doc/release_notes/2.5.0.txt +0 -23
  63. data/doc/release_notes/2.5.1.txt +0 -4
  64. data/doc/release_notes/2.6.0.txt +0 -21
  65. data/doc/release_notes/2.7.0.txt +0 -75
  66. data/doc/release_notes/2.8.0.txt +0 -44
  67. data/doc/release_notes/2.9.0.txt +0 -6
  68. data/spec/all.rb +0 -1
  69. data/spec/assets/css/app.scss +0 -1
  70. data/spec/assets/css/no_access.css +0 -1
  71. data/spec/assets/css/raw.css +0 -1
  72. data/spec/assets/js/head/app.js +0 -1
  73. data/spec/composition_spec.rb +0 -31
  74. data/spec/define_roda_method_spec.rb +0 -274
  75. data/spec/env_spec.rb +0 -11
  76. data/spec/freeze_spec.rb +0 -37
  77. data/spec/integration_spec.rb +0 -209
  78. data/spec/matchers_spec.rb +0 -832
  79. data/spec/opts_spec.rb +0 -42
  80. data/spec/plugin/_after_hook_spec.rb +0 -19
  81. data/spec/plugin/all_verbs_spec.rb +0 -29
  82. data/spec/plugin/assets_preloading_spec.rb +0 -98
  83. data/spec/plugin/assets_spec.rb +0 -745
  84. data/spec/plugin/backtracking_array_spec.rb +0 -42
  85. data/spec/plugin/branch_locals_spec.rb +0 -106
  86. data/spec/plugin/caching_spec.rb +0 -337
  87. data/spec/plugin/chunked_spec.rb +0 -201
  88. data/spec/plugin/class_level_routing_spec.rb +0 -164
  89. data/spec/plugin/class_matchers_spec.rb +0 -40
  90. data/spec/plugin/common_logger_spec.rb +0 -85
  91. data/spec/plugin/content_for_spec.rb +0 -162
  92. data/spec/plugin/content_security_policy_spec.rb +0 -175
  93. data/spec/plugin/cookies_spec.rb +0 -51
  94. data/spec/plugin/csrf_spec.rb +0 -111
  95. data/spec/plugin/default_headers_spec.rb +0 -82
  96. data/spec/plugin/default_status_spec.rb +0 -95
  97. data/spec/plugin/delay_build_spec.rb +0 -23
  98. data/spec/plugin/delegate_spec.rb +0 -23
  99. data/spec/plugin/delete_empty_headers_spec.rb +0 -27
  100. data/spec/plugin/direct_call_spec.rb +0 -28
  101. data/spec/plugin/disallow_file_uploads_spec.rb +0 -25
  102. data/spec/plugin/drop_body_spec.rb +0 -24
  103. data/spec/plugin/early_hints_spec.rb +0 -19
  104. data/spec/plugin/empty_root_spec.rb +0 -14
  105. data/spec/plugin/environments_spec.rb +0 -42
  106. data/spec/plugin/error_email_spec.rb +0 -97
  107. data/spec/plugin/error_handler_spec.rb +0 -216
  108. data/spec/plugin/error_mail_spec.rb +0 -93
  109. data/spec/plugin/exception_page_spec.rb +0 -168
  110. data/spec/plugin/flash_spec.rb +0 -121
  111. data/spec/plugin/h_spec.rb +0 -11
  112. data/spec/plugin/halt_spec.rb +0 -119
  113. data/spec/plugin/hash_matcher_spec.rb +0 -27
  114. data/spec/plugin/hash_routes_spec.rb +0 -535
  115. data/spec/plugin/head_spec.rb +0 -52
  116. data/spec/plugin/header_matchers_spec.rb +0 -98
  117. data/spec/plugin/heartbeat_spec.rb +0 -74
  118. data/spec/plugin/hooks_spec.rb +0 -152
  119. data/spec/plugin/indifferent_params_spec.rb +0 -14
  120. data/spec/plugin/json_parser_spec.rb +0 -141
  121. data/spec/plugin/json_spec.rb +0 -83
  122. data/spec/plugin/mail_processor_spec.rb +0 -451
  123. data/spec/plugin/mailer_spec.rb +0 -282
  124. data/spec/plugin/match_affix_spec.rb +0 -43
  125. data/spec/plugin/match_hook_spec.rb +0 -79
  126. data/spec/plugin/middleware_spec.rb +0 -237
  127. data/spec/plugin/middleware_stack_spec.rb +0 -81
  128. data/spec/plugin/module_include_spec.rb +0 -48
  129. data/spec/plugin/multi_route_spec.rb +0 -268
  130. data/spec/plugin/multi_run_spec.rb +0 -87
  131. data/spec/plugin/multi_view_spec.rb +0 -50
  132. data/spec/plugin/multibyte_string_matcher_spec.rb +0 -44
  133. data/spec/plugin/named_templates_spec.rb +0 -96
  134. data/spec/plugin/not_allowed_spec.rb +0 -69
  135. data/spec/plugin/not_found_spec.rb +0 -128
  136. data/spec/plugin/optimized_string_matchers_spec.rb +0 -43
  137. data/spec/plugin/padrino_render_spec.rb +0 -34
  138. data/spec/plugin/param_matchers_spec.rb +0 -69
  139. data/spec/plugin/params_capturing_spec.rb +0 -33
  140. data/spec/plugin/partials_spec.rb +0 -43
  141. data/spec/plugin/pass_spec.rb +0 -29
  142. data/spec/plugin/path_matchers_spec.rb +0 -42
  143. data/spec/plugin/path_rewriter_spec.rb +0 -45
  144. data/spec/plugin/path_spec.rb +0 -222
  145. data/spec/plugin/placeholder_string_matchers_spec.rb +0 -126
  146. data/spec/plugin/precompile_templates_spec.rb +0 -61
  147. data/spec/plugin/public_spec.rb +0 -85
  148. data/spec/plugin/render_each_spec.rb +0 -82
  149. data/spec/plugin/render_locals_spec.rb +0 -114
  150. data/spec/plugin/render_spec.rb +0 -912
  151. data/spec/plugin/request_aref_spec.rb +0 -51
  152. data/spec/plugin/request_headers_spec.rb +0 -39
  153. data/spec/plugin/response_request_spec.rb +0 -43
  154. data/spec/plugin/route_block_args_spec.rb +0 -86
  155. data/spec/plugin/route_csrf_spec.rb +0 -305
  156. data/spec/plugin/run_append_slash_spec.rb +0 -77
  157. data/spec/plugin/run_handler_spec.rb +0 -53
  158. data/spec/plugin/sessions_spec.rb +0 -452
  159. data/spec/plugin/shared_vars_spec.rb +0 -45
  160. data/spec/plugin/sinatra_helpers_spec.rb +0 -537
  161. data/spec/plugin/slash_path_empty_spec.rb +0 -22
  162. data/spec/plugin/static_routing_spec.rb +0 -192
  163. data/spec/plugin/static_spec.rb +0 -30
  164. data/spec/plugin/status_303_spec.rb +0 -28
  165. data/spec/plugin/status_handler_spec.rb +0 -158
  166. data/spec/plugin/streaming_spec.rb +0 -246
  167. data/spec/plugin/strip_path_prefix_spec.rb +0 -24
  168. data/spec/plugin/symbol_matchers_spec.rb +0 -51
  169. data/spec/plugin/symbol_status_spec.rb +0 -25
  170. data/spec/plugin/symbol_views_spec.rb +0 -32
  171. data/spec/plugin/timestamp_public_spec.rb +0 -85
  172. data/spec/plugin/type_routing_spec.rb +0 -348
  173. data/spec/plugin/typecast_params_spec.rb +0 -1370
  174. data/spec/plugin/unescape_path_spec.rb +0 -22
  175. data/spec/plugin/view_options_spec.rb +0 -170
  176. data/spec/plugin_spec.rb +0 -71
  177. data/spec/redirect_spec.rb +0 -41
  178. data/spec/request_spec.rb +0 -97
  179. data/spec/response_spec.rb +0 -199
  180. data/spec/route_spec.rb +0 -39
  181. data/spec/session_middleware_spec.rb +0 -129
  182. data/spec/session_spec.rb +0 -37
  183. data/spec/spec_helper.rb +0 -137
  184. data/spec/version_spec.rb +0 -14
  185. data/spec/views/_test.erb +0 -1
  186. data/spec/views/a.erb +0 -1
  187. data/spec/views/a.rdoc +0 -2
  188. data/spec/views/about.erb +0 -1
  189. data/spec/views/about.str +0 -1
  190. data/spec/views/about/_test.css.gz +0 -0
  191. data/spec/views/about/_test.erb +0 -1
  192. data/spec/views/about/_test.erb.gz +0 -0
  193. data/spec/views/about/comp_test.erb +0 -1
  194. data/spec/views/b.erb +0 -1
  195. data/spec/views/c.erb +0 -1
  196. data/spec/views/comp_layout.erb +0 -1
  197. data/spec/views/comp_test.erb +0 -1
  198. data/spec/views/content-yield.erb +0 -1
  199. data/spec/views/each.str +0 -1
  200. data/spec/views/home.erb +0 -2
  201. data/spec/views/home.str +0 -2
  202. data/spec/views/iv.erb +0 -1
  203. data/spec/views/layout-alternative.erb +0 -2
  204. data/spec/views/layout-yield.erb +0 -3
  205. data/spec/views/layout.erb +0 -2
  206. data/spec/views/layout.str +0 -2
  207. data/spec/views/multiple-layout.erb +0 -1
  208. data/spec/views/multiple.erb +0 -1
@@ -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
@@ -42,6 +42,8 @@ class Roda
42
42
  INDIFFERENT_PROC = lambda{|h,k| h[k.to_s] if k.is_a?(Symbol)}
43
43
 
44
44
  if Rack.release > '2'
45
+ require 'rack/query_parser'
46
+
45
47
  class QueryParser < Rack::QueryParser
46
48
  # Work around for invalid optimization in rack
47
49
  def parse_nested_query(qs, d=nil)
@@ -62,6 +64,8 @@ class Roda
62
64
  module RequestMethods
63
65
  QUERY_PARSER = Rack::Utils.default_query_parser = QueryParser.new(QueryParser::Params, 65536, 100)
64
66
 
67
+ private
68
+
65
69
  def query_parser
66
70
  QUERY_PARSER
67
71
  end
@@ -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,26 @@ 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
+ if mime_type = ::Rack::Mime.mime_type(::File.extname(path), 'text/plain')
114
+ headers['Content-Type'] = mime_type
115
+ end
116
+ headers['Content-Encoding'] = encoding
117
+ end
118
+
119
+ halt res
120
+ end
121
+ end
122
+ end
123
+
116
124
  if ::Rack.release > '2'
117
125
  # Serve the given path using the given Rack::File server.
118
126
  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
@@ -576,7 +588,7 @@ class Roda
576
588
  !opts[:inline]
577
589
 
578
590
  if render_opts[:check_template_mtime] && !opts[:template_block] && !cache
579
- template = TemplateMtimeWrapper.new(opts[:template_class], opts[:path], 1, template_opts)
591
+ template = TemplateMtimeWrapper.new(opts[:template_class], opts[:path], opts[:dependencies], 1, template_opts)
580
592
 
581
593
  if define_compiled_method
582
594
  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