ultra-lite-hub 0.0.1

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 (162) hide show
  1. checksums.yaml +7 -0
  2. data/roda-3.105.0/MIT-LICENSE +21 -0
  3. data/roda-3.105.0/lib/roda/cache.rb +42 -0
  4. data/roda-3.105.0/lib/roda/plugins/Integer_matcher_max.rb +55 -0
  5. data/roda-3.105.0/lib/roda/plugins/_after_hook.rb +11 -0
  6. data/roda-3.105.0/lib/roda/plugins/_base64.rb +34 -0
  7. data/roda-3.105.0/lib/roda/plugins/_before_hook.rb +41 -0
  8. data/roda-3.105.0/lib/roda/plugins/_optimized_matching.rb +227 -0
  9. data/roda-3.105.0/lib/roda/plugins/_symbol_class_matchers.rb +108 -0
  10. data/roda-3.105.0/lib/roda/plugins/_symbol_regexp_matchers.rb +24 -0
  11. data/roda-3.105.0/lib/roda/plugins/additional_render_engines.rb +61 -0
  12. data/roda-3.105.0/lib/roda/plugins/additional_view_directories.rb +66 -0
  13. data/roda-3.105.0/lib/roda/plugins/all_verbs.rb +50 -0
  14. data/roda-3.105.0/lib/roda/plugins/assets.rb +902 -0
  15. data/roda-3.105.0/lib/roda/plugins/assets_preloading.rb +89 -0
  16. data/roda-3.105.0/lib/roda/plugins/assume_ssl.rb +28 -0
  17. data/roda-3.105.0/lib/roda/plugins/autoload_hash_branches.rb +79 -0
  18. data/roda-3.105.0/lib/roda/plugins/autoload_named_routes.rb +65 -0
  19. data/roda-3.105.0/lib/roda/plugins/backtracking_array.rb +88 -0
  20. data/roda-3.105.0/lib/roda/plugins/bearer_token.rb +28 -0
  21. data/roda-3.105.0/lib/roda/plugins/branch_locals.rb +74 -0
  22. data/roda-3.105.0/lib/roda/plugins/break.rb +43 -0
  23. data/roda-3.105.0/lib/roda/plugins/caching.rb +206 -0
  24. data/roda-3.105.0/lib/roda/plugins/capture_erb.rb +94 -0
  25. data/roda-3.105.0/lib/roda/plugins/chunked.rb +331 -0
  26. data/roda-3.105.0/lib/roda/plugins/class_level_routing.rb +111 -0
  27. data/roda-3.105.0/lib/roda/plugins/class_matchers.rb +155 -0
  28. data/roda-3.105.0/lib/roda/plugins/common_logger.rb +80 -0
  29. data/roda-3.105.0/lib/roda/plugins/conditional_sessions.rb +67 -0
  30. data/roda-3.105.0/lib/roda/plugins/content_for.rb +103 -0
  31. data/roda-3.105.0/lib/roda/plugins/content_security_policy.rb +329 -0
  32. data/roda-3.105.0/lib/roda/plugins/cookie_flags.rb +157 -0
  33. data/roda-3.105.0/lib/roda/plugins/cookies.rb +51 -0
  34. data/roda-3.105.0/lib/roda/plugins/csrf.rb +81 -0
  35. data/roda-3.105.0/lib/roda/plugins/custom_block_results.rb +85 -0
  36. data/roda-3.105.0/lib/roda/plugins/custom_matchers.rb +89 -0
  37. data/roda-3.105.0/lib/roda/plugins/default_headers.rb +64 -0
  38. data/roda-3.105.0/lib/roda/plugins/default_status.rb +35 -0
  39. data/roda-3.105.0/lib/roda/plugins/delay_build.rb +18 -0
  40. data/roda-3.105.0/lib/roda/plugins/delegate.rb +77 -0
  41. data/roda-3.105.0/lib/roda/plugins/delete_empty_headers.rb +36 -0
  42. data/roda-3.105.0/lib/roda/plugins/direct_call.rb +38 -0
  43. data/roda-3.105.0/lib/roda/plugins/disallow_file_uploads.rb +38 -0
  44. data/roda-3.105.0/lib/roda/plugins/drop_body.rb +43 -0
  45. data/roda-3.105.0/lib/roda/plugins/each_part.rb +87 -0
  46. data/roda-3.105.0/lib/roda/plugins/early_hints.rb +25 -0
  47. data/roda-3.105.0/lib/roda/plugins/empty_root.rb +49 -0
  48. data/roda-3.105.0/lib/roda/plugins/environments.rb +79 -0
  49. data/roda-3.105.0/lib/roda/plugins/erb_h.rb +43 -0
  50. data/roda-3.105.0/lib/roda/plugins/error_email.rb +152 -0
  51. data/roda-3.105.0/lib/roda/plugins/error_handler.rb +133 -0
  52. data/roda-3.105.0/lib/roda/plugins/error_mail.rb +149 -0
  53. data/roda-3.105.0/lib/roda/plugins/exception_page.rb +445 -0
  54. data/roda-3.105.0/lib/roda/plugins/filter_common_logger.rb +46 -0
  55. data/roda-3.105.0/lib/roda/plugins/flash.rb +117 -0
  56. data/roda-3.105.0/lib/roda/plugins/h.rb +59 -0
  57. data/roda-3.105.0/lib/roda/plugins/halt.rb +108 -0
  58. data/roda-3.105.0/lib/roda/plugins/hash_branch_view_subdir.rb +76 -0
  59. data/roda-3.105.0/lib/roda/plugins/hash_branches.rb +145 -0
  60. data/roda-3.105.0/lib/roda/plugins/hash_matcher.rb +39 -0
  61. data/roda-3.105.0/lib/roda/plugins/hash_paths.rb +128 -0
  62. data/roda-3.105.0/lib/roda/plugins/hash_public.rb +125 -0
  63. data/roda-3.105.0/lib/roda/plugins/hash_public_cache.rb +91 -0
  64. data/roda-3.105.0/lib/roda/plugins/hash_routes.rb +304 -0
  65. data/roda-3.105.0/lib/roda/plugins/head.rb +99 -0
  66. data/roda-3.105.0/lib/roda/plugins/header_matchers.rb +88 -0
  67. data/roda-3.105.0/lib/roda/plugins/heartbeat.rb +41 -0
  68. data/roda-3.105.0/lib/roda/plugins/hmac_paths.rb +413 -0
  69. data/roda-3.105.0/lib/roda/plugins/hooks.rb +92 -0
  70. data/roda-3.105.0/lib/roda/plugins/host_authorization.rb +155 -0
  71. data/roda-3.105.0/lib/roda/plugins/host_routing.rb +190 -0
  72. data/roda-3.105.0/lib/roda/plugins/hsts.rb +35 -0
  73. data/roda-3.105.0/lib/roda/plugins/indifferent_params.rb +124 -0
  74. data/roda-3.105.0/lib/roda/plugins/inject_erb.rb +33 -0
  75. data/roda-3.105.0/lib/roda/plugins/invalid_request_body.rb +107 -0
  76. data/roda-3.105.0/lib/roda/plugins/ip_from_header.rb +34 -0
  77. data/roda-3.105.0/lib/roda/plugins/json.rb +119 -0
  78. data/roda-3.105.0/lib/roda/plugins/json_parser.rb +107 -0
  79. data/roda-3.105.0/lib/roda/plugins/link_to.rb +83 -0
  80. data/roda-3.105.0/lib/roda/plugins/mail_processor.rb +629 -0
  81. data/roda-3.105.0/lib/roda/plugins/mailer.rb +283 -0
  82. data/roda-3.105.0/lib/roda/plugins/map_matcher.rb +48 -0
  83. data/roda-3.105.0/lib/roda/plugins/match_affix.rb +64 -0
  84. data/roda-3.105.0/lib/roda/plugins/match_hook.rb +34 -0
  85. data/roda-3.105.0/lib/roda/plugins/match_hook_args.rb +93 -0
  86. data/roda-3.105.0/lib/roda/plugins/middleware.rb +225 -0
  87. data/roda-3.105.0/lib/roda/plugins/middleware_stack.rb +101 -0
  88. data/roda-3.105.0/lib/roda/plugins/module_include.rb +96 -0
  89. data/roda-3.105.0/lib/roda/plugins/multi_public.rb +95 -0
  90. data/roda-3.105.0/lib/roda/plugins/multi_route.rb +156 -0
  91. data/roda-3.105.0/lib/roda/plugins/multi_run.rb +144 -0
  92. data/roda-3.105.0/lib/roda/plugins/multi_view.rb +83 -0
  93. data/roda-3.105.0/lib/roda/plugins/multibyte_string_matcher.rb +57 -0
  94. data/roda-3.105.0/lib/roda/plugins/named_routes.rb +167 -0
  95. data/roda-3.105.0/lib/roda/plugins/named_templates.rb +102 -0
  96. data/roda-3.105.0/lib/roda/plugins/not_allowed.rb +149 -0
  97. data/roda-3.105.0/lib/roda/plugins/not_found.rb +55 -0
  98. data/roda-3.105.0/lib/roda/plugins/optimized_segment_matchers.rb +53 -0
  99. data/roda-3.105.0/lib/roda/plugins/optimized_string_matchers.rb +50 -0
  100. data/roda-3.105.0/lib/roda/plugins/padrino_render.rb +42 -0
  101. data/roda-3.105.0/lib/roda/plugins/param_matchers.rb +83 -0
  102. data/roda-3.105.0/lib/roda/plugins/params_capturing.rb +116 -0
  103. data/roda-3.105.0/lib/roda/plugins/part.rb +76 -0
  104. data/roda-3.105.0/lib/roda/plugins/partials.rb +74 -0
  105. data/roda-3.105.0/lib/roda/plugins/pass.rb +47 -0
  106. data/roda-3.105.0/lib/roda/plugins/path.rb +255 -0
  107. data/roda-3.105.0/lib/roda/plugins/path_matchers.rb +54 -0
  108. data/roda-3.105.0/lib/roda/plugins/path_rewriter.rb +111 -0
  109. data/roda-3.105.0/lib/roda/plugins/permissions_policy.rb +336 -0
  110. data/roda-3.105.0/lib/roda/plugins/placeholder_string_matchers.rb +48 -0
  111. data/roda-3.105.0/lib/roda/plugins/plain_hash_response_headers.rb +32 -0
  112. data/roda-3.105.0/lib/roda/plugins/precompile_templates.rb +156 -0
  113. data/roda-3.105.0/lib/roda/plugins/public.rb +182 -0
  114. data/roda-3.105.0/lib/roda/plugins/r.rb +35 -0
  115. data/roda-3.105.0/lib/roda/plugins/recheck_precompiled_assets.rb +107 -0
  116. data/roda-3.105.0/lib/roda/plugins/redirect_http_to_https.rb +99 -0
  117. data/roda-3.105.0/lib/roda/plugins/redirect_path.rb +59 -0
  118. data/roda-3.105.0/lib/roda/plugins/relative_path.rb +74 -0
  119. data/roda-3.105.0/lib/roda/plugins/render.rb +998 -0
  120. data/roda-3.105.0/lib/roda/plugins/render_coverage.rb +105 -0
  121. data/roda-3.105.0/lib/roda/plugins/render_each.rb +224 -0
  122. data/roda-3.105.0/lib/roda/plugins/render_locals.rb +102 -0
  123. data/roda-3.105.0/lib/roda/plugins/request_aref.rb +77 -0
  124. data/roda-3.105.0/lib/roda/plugins/request_headers.rb +81 -0
  125. data/roda-3.105.0/lib/roda/plugins/response_attachment.rb +94 -0
  126. data/roda-3.105.0/lib/roda/plugins/response_content_type.rb +79 -0
  127. data/roda-3.105.0/lib/roda/plugins/response_request.rb +29 -0
  128. data/roda-3.105.0/lib/roda/plugins/route_block_args.rb +50 -0
  129. data/roda-3.105.0/lib/roda/plugins/route_csrf.rb +388 -0
  130. data/roda-3.105.0/lib/roda/plugins/run_append_slash.rb +51 -0
  131. data/roda-3.105.0/lib/roda/plugins/run_handler.rb +51 -0
  132. data/roda-3.105.0/lib/roda/plugins/run_require_slash.rb +46 -0
  133. data/roda-3.105.0/lib/roda/plugins/sec_fetch_site_csrf.rb +131 -0
  134. data/roda-3.105.0/lib/roda/plugins/send_file.rb +127 -0
  135. data/roda-3.105.0/lib/roda/plugins/sessions.rb +537 -0
  136. data/roda-3.105.0/lib/roda/plugins/shared_vars.rb +86 -0
  137. data/roda-3.105.0/lib/roda/plugins/sinatra_helpers.rb +443 -0
  138. data/roda-3.105.0/lib/roda/plugins/slash_path_empty.rb +26 -0
  139. data/roda-3.105.0/lib/roda/plugins/static.rb +42 -0
  140. data/roda-3.105.0/lib/roda/plugins/static_routing.rb +89 -0
  141. data/roda-3.105.0/lib/roda/plugins/status_303.rb +34 -0
  142. data/roda-3.105.0/lib/roda/plugins/status_handler.rb +86 -0
  143. data/roda-3.105.0/lib/roda/plugins/streaming.rb +170 -0
  144. data/roda-3.105.0/lib/roda/plugins/strip_path_prefix.rb +33 -0
  145. data/roda-3.105.0/lib/roda/plugins/symbol_matchers.rb +188 -0
  146. data/roda-3.105.0/lib/roda/plugins/symbol_status.rb +32 -0
  147. data/roda-3.105.0/lib/roda/plugins/symbol_views.rb +37 -0
  148. data/roda-3.105.0/lib/roda/plugins/timestamp_public.rb +78 -0
  149. data/roda-3.105.0/lib/roda/plugins/type_routing.rb +215 -0
  150. data/roda-3.105.0/lib/roda/plugins/typecast_params.rb +1183 -0
  151. data/roda-3.105.0/lib/roda/plugins/typecast_params_sized_integers.rb +108 -0
  152. data/roda-3.105.0/lib/roda/plugins/unescape_path.rb +41 -0
  153. data/roda-3.105.0/lib/roda/plugins/view_options.rb +201 -0
  154. data/roda-3.105.0/lib/roda/plugins/view_subdir_leading_slash.rb +49 -0
  155. data/roda-3.105.0/lib/roda/plugins.rb +68 -0
  156. data/roda-3.105.0/lib/roda/request.rb +722 -0
  157. data/roda-3.105.0/lib/roda/response.rb +221 -0
  158. data/roda-3.105.0/lib/roda/session_middleware.rb +183 -0
  159. data/roda-3.105.0/lib/roda/version.rb +18 -0
  160. data/roda-3.105.0/lib/roda.rb +586 -0
  161. data/ultra-lite-hub.gemspec +12 -0
  162. metadata +201 -0
@@ -0,0 +1,902 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The assets plugin adds support for rendering your CSS and javascript
7
+ # asset files on the fly in development, and compiling them
8
+ # to a single, compressed file in production.
9
+ #
10
+ # This uses the render plugin for rendering the assets, and the render
11
+ # plugin uses tilt internally, so you can use any template engine
12
+ # supported by tilt for your assets. Tilt ships with support for
13
+ # the following asset template engines, assuming the necessary libraries
14
+ # are installed:
15
+ #
16
+ # css :: Less, Sass, Scss
17
+ # js :: CoffeeScript
18
+ #
19
+ # You can also use opal as a javascript template engine, assuming it is
20
+ # installed.
21
+ #
22
+ # == Usage
23
+ #
24
+ # When loading the plugin, use the :css and :js options
25
+ # to set the source file(s) to use for CSS and javascript assets:
26
+ #
27
+ # plugin :assets, css: 'some_file.scss', js: 'some_file.coffee'
28
+ #
29
+ # This will look for the following files:
30
+ #
31
+ # assets/css/some_file.scss
32
+ # assets/js/some_file.coffee
33
+ #
34
+ # The values for the :css and :js options can be arrays to load multiple
35
+ # files. If you want to change the paths where asset files are stored, see the
36
+ # Options section below.
37
+ #
38
+ # === Serving
39
+ #
40
+ # In your routes, call the +r.assets+ method to add a route to your assets,
41
+ # which will make your app serve the rendered assets:
42
+ #
43
+ # route do |r|
44
+ # r.assets
45
+ # end
46
+ #
47
+ # You should generally call +r.assets+ inside the route block itself, and not
48
+ # under any branches of the routing tree.
49
+ #
50
+ # === Views
51
+ #
52
+ # In your layout view, use the assets method to add links to your CSS and
53
+ # javascript assets:
54
+ #
55
+ # <%= assets(:css) %>
56
+ # <%= assets(:js) %>
57
+ #
58
+ # You can add attributes to the tags by using an options hash:
59
+ #
60
+ # <%= assets(:css, media: 'print') %>
61
+ #
62
+ # The assets method will respect the application's +:add_script_name+ option,
63
+ # if it is set it will automatically prefix the path with the +SCRIPT_NAME+ for
64
+ # the request.
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
+ #
81
+ # == Asset Groups
82
+ #
83
+ # The asset plugin supports groups for the cases where you have different
84
+ # css/js files for your front end and back end. To use asset groups, you
85
+ # pass a hash for the :css and/or :js options:
86
+ #
87
+ # plugin :assets, css: {frontend: 'some_frontend_file.scss',
88
+ # backend: 'some_backend_file.scss'}
89
+ #
90
+ # This expects the following directory structure for your assets:
91
+ #
92
+ # assets/css/frontend/some_frontend_file.scss
93
+ # assets/css/backend/some_backend_file.scss
94
+ #
95
+ # If you do not want to force that directory structure when using
96
+ # asset groups, you can use the <tt>group_subdirs: false</tt> option.
97
+ #
98
+ # In your view code use an array argument in your call to assets:
99
+ #
100
+ # <%= assets([:css, :frontend]) %>
101
+ #
102
+ # === Nesting
103
+ #
104
+ # Asset groups also support nesting, though that should only be needed
105
+ # in fairly large applications. You can use a nested hash when loading
106
+ # the plugin:
107
+ #
108
+ # plugin :assets,
109
+ # css: {frontend: {dashboard: 'some_frontend_file.scss'}}
110
+ #
111
+ # and an extra entry per nesting level when creating the tags.
112
+ #
113
+ # <%= assets([:css, :frontend, :dashboard]) %>
114
+ #
115
+ # == Caching
116
+ #
117
+ # The assets plugin uses the caching plugin internally, and will set the
118
+ # Last-Modified header to the modified timestamp of the asset source file
119
+ # when rendering the asset.
120
+ #
121
+ # If you have assets that include other asset files, such as using @import
122
+ # in a sass file, you need to specify the dependencies for your assets so
123
+ # that the assets plugin will correctly pick up changes. You can do this
124
+ # using the :dependencies option to the plugin, which takes a hash where
125
+ # the keys are paths to asset files, and values are arrays of paths to
126
+ # dependencies of those asset files:
127
+ #
128
+ # app.plugin :assets,
129
+ # dependencies: {'assets/css/bootstrap.scss'=>Dir['assets/css/bootstrap/' '**/*.scss']}
130
+ #
131
+ # == Asset Compilation
132
+ #
133
+ # In production, you are generally going to want to compile your assets
134
+ # into a single file, with you can do by calling compile_assets after
135
+ # loading the plugin:
136
+ #
137
+ # plugin :assets, css: 'some_file.scss', js: 'some_file.coffee'
138
+ # compile_assets
139
+ #
140
+ # After calling compile_assets, calls to assets in your views will default
141
+ # to a using a single link each to your CSS and javascript compiled asset
142
+ # files. By default the compiled files are written to the public directory,
143
+ # so that they can be served by the webserver.
144
+ #
145
+ # === Asset Compression
146
+ #
147
+ # If you have the yuicompressor gem installed and working, it will be used
148
+ # automatically to compress your javascript and css assets. For javascript
149
+ # assets, if yuicompressor is not available, the plugin will check for
150
+ # closure-compiler, uglifier, and minjs and use the first one that works.
151
+ # If no compressors are available, the assets will just be concatenated
152
+ # together and not compressed during compilation. You can use the
153
+ # :css_compressor and :js_compressor options to specify the compressor to use.
154
+ #
155
+ # It is also possible to use the built-in compression options in the CSS or JS
156
+ # compiler, assuming the compiler supports such options. For example, with
157
+ # sass/sassc, you can use:
158
+ #
159
+ # plugin :assets,
160
+ # css_opts: {style: :compressed}
161
+ #
162
+ # === Source Maps (CSS)
163
+ #
164
+ # The assets plugin does not have direct support for source maps, so it is
165
+ # recommended you use embedded source maps if supported by the CSS compiler.
166
+ # For sass/sassc, you can use:
167
+ #
168
+ # plugin :assets,
169
+ # css_opts: {:source_map_embed=>true, source_map_contents: true, source_map_file: "."}
170
+ #
171
+ # === With Asset Groups
172
+ #
173
+ # When using asset groups, a separate compiled file will be produced per
174
+ # asset group.
175
+ #
176
+ # === Unique Asset Names
177
+ #
178
+ # When compiling assets, a unique name is given to each asset file, using the
179
+ # a SHA1 hash of the content of the file. This is done so that clients do
180
+ # not attempt to use cached versions of the assets if the asset has changed.
181
+ #
182
+ # === Serving
183
+ #
184
+ # When compiling assets, +r.assets+ will serve the compiled asset
185
+ # files. However, it is recommended to have the main webserver (e.g. nginx)
186
+ # serve the compiled files, instead of relying on the application.
187
+ #
188
+ # Assuming you are using compiled assets in production mode that are served
189
+ # by the webserver, you can remove the serving of them by the application:
190
+ #
191
+ # route do |r|
192
+ # r.assets unless ENV['RACK_ENV'] == 'production'
193
+ # end
194
+ #
195
+ # If you do have the application serve the compiled assets, it will use the
196
+ # Last-Modified header to make sure that clients do not redownload compiled
197
+ # assets that haven't changed.
198
+ #
199
+ # === Asset Precompilation
200
+ #
201
+ # If you want to precompile your assets, so they do not need to be compiled
202
+ # every time you boot the application, you can provide a :precompiled option
203
+ # when loading the plugin. The value of this option should be the filename
204
+ # where the compiled asset metadata is stored.
205
+ #
206
+ # If the compiled asset metadata file does not exist when the assets plugin
207
+ # is loaded, the plugin will run in non-compiled mode. However, when you call
208
+ # compile_assets, it will write the compiled asset metadata file after
209
+ # compiling the assets.
210
+ #
211
+ # If the compiled asset metadata file already exists when the assets plugin
212
+ # is loaded, the plugin will read the file to get the compiled asset metadata,
213
+ # and it will run in compiled mode, assuming that the compiled asset files
214
+ # already exist.
215
+ #
216
+ # ==== On Heroku
217
+ #
218
+ # Heroku supports precompiling the assets when using Roda. You just need to
219
+ # add an assets:precompile task, similar to this:
220
+ #
221
+ # namespace :assets do
222
+ # desc "Precompile the assets"
223
+ # task :precompile do
224
+ # require './app'
225
+ # App.compile_assets
226
+ # end
227
+ # end
228
+ #
229
+ # == Postprocessing
230
+ #
231
+ # If you pass a callable object to the :postprocessor option, it will be called
232
+ # before an asset is served.
233
+ # If the assets are to be compiled, the object will be called at compilation time.
234
+ #
235
+ # It is passed three arguments; the name of the asset file, the type of the
236
+ # asset file (which is a symbol, either :css or :js), and the asset contents.
237
+ #
238
+ # It should return the new content for the asset.
239
+ #
240
+ # You can use this to call Autoprefixer on your CSS:
241
+ #
242
+ # plugin :assets, {
243
+ # css: [ 'style.scss' ],
244
+ # postprocessor: lambda do |file, type, content|
245
+ # type == :css ? AutoprefixerRails.process(content).css : content
246
+ # end
247
+ # }
248
+ #
249
+ # == External Assets/Assets from Gems
250
+ #
251
+ # The assets plugin only supports loading assets files underneath the assets
252
+ # path. You cannot pass an absolute path to an asset file and have it
253
+ # work. If you would like to reference asset files that are outside the assets
254
+ # path, you have the following options:
255
+ #
256
+ # * Copy, hard link, or symlink the external assets files into the assets path.
257
+ # * Use tilt-indirect or another method of indirection (such as an erb template that loads
258
+ # the external asset file) so that a file inside the assets path can reference files
259
+ # outside the assets path.
260
+ #
261
+ # == Plugin Options
262
+ #
263
+ # :add_suffix :: Whether to append a .css or .js extension to asset routes in non-compiled mode
264
+ # (default: false)
265
+ # :compiled_asset_host :: The asset host to use for compiled assets. Should include the protocol
266
+ # as well as the host (e.g. "https://cdn.example.com", "//cdn.example.com")
267
+ # :compiled_css_dir :: Directory name in which to store the compiled css file,
268
+ # inside :compiled_path (default: nil)
269
+ # :compiled_css_route :: Route under :prefix for compiled css assets (default: :compiled_css_dir)
270
+ # :compiled_js_dir :: Directory name in which to store the compiled javascript file,
271
+ # inside :compiled_path (default: nil)
272
+ # :compiled_js_route :: Route under :prefix for compiled javscript assets (default: :compiled_js_dir)
273
+ # :compiled_name :: Compiled file name prefix (default: 'app')
274
+ # :compiled_path:: Path inside public folder in which compiled files are stored (default: :prefix)
275
+ # :concat_only :: Whether to just concatenate instead of concatenating
276
+ # and compressing files (default: false)
277
+ # :css_compressor :: Compressor to use for compressing CSS, either :yui, :none, or nil (the default, which will try
278
+ # :yui if available, but not fail if it is not available)
279
+ # :css_dir :: Directory name containing your css source, inside :path (default: 'css')
280
+ # :css_headers :: A hash of additional headers for your rendered css files
281
+ # :css_opts :: Template options to pass to the render plugin (via :template_opts) when rendering css assets
282
+ # :css_route :: Route under :prefix for css assets (default: :css_dir)
283
+ # :dependencies :: A hash of dependencies for your asset files. Keys should be paths to asset files,
284
+ # values should be arrays of paths your asset files depends on. This is used to
285
+ # detect changes in your asset files.
286
+ # :early_hints :: Automatically send early hints for all assets. Requires the early_hints plugin.
287
+ # :group_subdirs :: Whether a hash used in :css and :js options requires the assets for the
288
+ # related group are contained in a subdirectory with the same name (default: true)
289
+ # :gzip :: Store gzipped compiled assets files, and serve those to clients who accept gzip encoding.
290
+ # :headers :: A hash of additional headers for both js and css rendered files
291
+ # :js_compressor :: Compressor to use for compressing javascript, either :yui, :closure, :uglifier, :minjs,
292
+ # :none, or nil (the default, which will try :yui, :closure, :uglifier, then :minjs, but
293
+ # not fail if any of them is not available)
294
+ # :js_dir :: Directory name containing your javascript source, inside :path (default: 'js')
295
+ # :js_headers :: A hash of additional headers for your rendered javascript files
296
+ # :js_opts :: Template options to pass to the render plugin (via :template_opts) when rendering javascript assets
297
+ # :js_route :: Route under :prefix for javascript assets (default: :js_dir)
298
+ # :path :: Path to your asset source directory (default: 'assets'). Relative
299
+ # paths will be considered relative to the application's :root option.
300
+ # :postprocessor :: A block which should accept three arguments (asset name, asset type,
301
+ # content). This block can be used to hook into the asset system and
302
+ # make your own modifications before the asset is served. If the asset
303
+ # is to be compiled, the block is called at compile time.
304
+ # :prefix :: Prefix for assets path in your URL/routes (default: 'assets')
305
+ # :precompiled :: Path to the compiled asset metadata file. If the file exists, will use compiled
306
+ # mode using the metadata in the file. If the file does not exist, will use
307
+ # non-compiled mode, but will write the metadata to the file if compile_assets is called.
308
+ # :public :: Path to your public folder, in which compiled files are placed (default: 'public'). Relative
309
+ # paths will be considered relative to the application's :root option.
310
+ # :relative_paths :: Use relative paths instead of absolute paths when setting up link and script tags for
311
+ # assets.
312
+ # :sri :: Enables subresource integrity when setting up references to compiled assets. The value should be
313
+ # :sha256, :sha384, or :sha512 depending on which hash algorithm you want to use. This changes the
314
+ # hash algorithm that Roda will use when naming compiled asset files. The default is :sha256, you
315
+ # can use nil to disable subresource integrity.
316
+ # :timestamp_paths :: Include the timestamp of assets in asset paths in non-compiled mode. Doing this can
317
+ # slow down development requests due to additional requests to get last modified times,
318
+ # but it will make sure the paths change in development when there are modifications,
319
+ # which can fix issues when using a caching proxy in non-compiled mode. This can also
320
+ # be specified as a string to use that string to separate the timestamp from the asset.
321
+ # By default, <tt>/</tt> is used as the separator if timestamp paths are enabled.
322
+ module Assets
323
+ DEFAULTS = {
324
+ :compiled_name => 'app'.freeze,
325
+ :js_dir => 'js'.freeze,
326
+ :css_dir => 'css'.freeze,
327
+ :prefix => 'assets'.freeze,
328
+ :concat_only => false,
329
+ :compiled => false,
330
+ :add_suffix => false,
331
+ :early_hints => false,
332
+ :timestamp_paths => false,
333
+ :group_subdirs => true,
334
+ :compiled_css_dir => nil,
335
+ :compiled_js_dir => nil,
336
+ :sri => :sha256
337
+ }.freeze
338
+
339
+ # Internal exception raised when a compressor cannot be found
340
+ CompressorNotFound = Class.new(RodaError)
341
+
342
+ # Load the render, caching, and h plugins, since the assets plugin
343
+ # depends on them.
344
+ def self.load_dependencies(app, opts = OPTS)
345
+ app.plugin :render
346
+ app.plugin :caching
347
+ app.plugin :h
348
+
349
+ if opts[:relative_paths]
350
+ app.plugin :relative_path
351
+ end
352
+ end
353
+
354
+ # Setup the options for the plugin. See the Assets module RDoc
355
+ # for a description of the supported options.
356
+ def self.configure(app, opts = {})
357
+ if app.assets_opts
358
+ prev_opts = app.assets_opts[:orig_opts]
359
+ orig_opts = app.assets_opts[:orig_opts].merge(opts)
360
+ [:headers, :css_headers, :js_headers, :css_opts, :js_opts, :dependencies].each do |s|
361
+ if prev_opts[s]
362
+ if opts[s]
363
+ orig_opts[s] = prev_opts[s].merge(opts[s])
364
+ else
365
+ orig_opts[s] = prev_opts[s].dup
366
+ end
367
+ end
368
+ end
369
+ app.opts[:assets] = orig_opts.dup
370
+ app.opts[:assets][:orig_opts] = orig_opts
371
+ else
372
+ app.opts[:assets] = opts.dup
373
+ app.opts[:assets][:orig_opts] = opts
374
+ end
375
+ opts = app.opts[:assets]
376
+ opts[:path] = app.expand_path(opts[:path]||"assets").freeze
377
+ opts[:public] = app.expand_path(opts[:public]||"public").freeze
378
+
379
+ # Combine multiple values into a path, ignoring trailing slashes
380
+ j = lambda do |*v|
381
+ opts.values_at(*v).
382
+ reject{|s| s.to_s.empty?}.
383
+ map{|s| s.chomp('/')}.
384
+ join('/').freeze
385
+ end
386
+
387
+ # Same as j, but add a trailing slash if not empty
388
+ sj = lambda do |*v|
389
+ s = j.call(*v)
390
+ s.empty? ? s : (s + '/').freeze
391
+ end
392
+
393
+ if opts[:precompiled] && !opts[:compiled] && ::File.exist?(opts[:precompiled])
394
+ require 'json'
395
+ opts[:compiled] = app.send(:_precompiled_asset_metadata, opts[:precompiled])
396
+ end
397
+
398
+ if opts[:early_hints]
399
+ app.plugin :early_hints
400
+ end
401
+
402
+ if opts[:timestamp_paths] && !opts[:timestamp_paths].is_a?(String)
403
+ opts[:timestamp_paths] = '/'
404
+ end
405
+
406
+ DEFAULTS.each do |k, v|
407
+ opts[k] = v unless opts.has_key?(k)
408
+ end
409
+
410
+ [
411
+ [:compiled_path, :prefix],
412
+ [:js_route, :js_dir],
413
+ [:css_route, :css_dir],
414
+ [:compiled_js_route, :compiled_js_dir],
415
+ [:compiled_css_route, :compiled_css_dir]
416
+ ].each do |k, v|
417
+ opts[k] = opts[v] unless opts.has_key?(k)
418
+ end
419
+
420
+ [:css_headers, :js_headers, :css_opts, :js_opts, :dependencies].each do |s|
421
+ opts[s] ||= {}
422
+ end
423
+
424
+ expanded_deps = opts[:expanded_dependencies] = {}
425
+ opts[:dependencies].each do |file, deps|
426
+ expanded_deps[File.expand_path(file)] = Array(deps)
427
+ end
428
+
429
+ if headers = opts[:headers]
430
+ opts[:css_headers] = headers.merge(opts[:css_headers])
431
+ opts[:js_headers] = headers.merge(opts[:js_headers])
432
+ end
433
+ opts[:css_headers][RodaResponseHeaders::CONTENT_TYPE] ||= "text/css; charset=UTF-8".freeze
434
+ opts[:js_headers][RodaResponseHeaders::CONTENT_TYPE] ||= "application/javascript; charset=UTF-8".freeze
435
+
436
+ [:css_headers, :js_headers, :css_opts, :js_opts, :dependencies, :expanded_dependencies].each do |s|
437
+ opts[s].freeze
438
+ end
439
+ [:headers, :css, :js].each do |s|
440
+ opts[s].freeze if opts[s]
441
+ end
442
+
443
+ # Used for reading/writing files
444
+ opts[:js_path] = sj.call(:path, :js_dir)
445
+ opts[:css_path] = sj.call(:path, :css_dir)
446
+ opts[:compiled_js_path] = j.call(:public, :compiled_path, :compiled_js_dir, :compiled_name)
447
+ opts[:compiled_css_path] = j.call(:public, :compiled_path, :compiled_css_dir, :compiled_name)
448
+
449
+ # Used for URLs/routes
450
+ opts[:js_prefix] = sj.call(:prefix, :js_route)
451
+ opts[:css_prefix] = sj.call(:prefix, :css_route)
452
+ opts[:compiled_js_prefix] = j.call(:prefix, :compiled_js_route, :compiled_name)
453
+ opts[:compiled_css_prefix] = j.call(:prefix, :compiled_css_route, :compiled_name)
454
+ opts[:js_suffix] = (opts[:add_suffix] ? '.js' : '').freeze
455
+ opts[:css_suffix] = (opts[:add_suffix] ? '.css' : '').freeze
456
+
457
+ opts.freeze
458
+ end
459
+
460
+ module ClassMethods
461
+ # Return the assets options for this class.
462
+ def assets_opts
463
+ opts[:assets]
464
+ end
465
+
466
+ # Compile options for the given asset type. If no asset_type
467
+ # is given, compile both the :css and :js asset types. You
468
+ # can specify an array of types (e.g. [:css, :frontend]) to
469
+ # compile assets for the given asset group.
470
+ def compile_assets(type=nil)
471
+ require 'fileutils'
472
+
473
+ unless assets_opts[:compiled]
474
+ opts[:assets] = assets_opts.merge(:compiled => _compiled_assets_initial_hash).freeze
475
+ end
476
+
477
+ if type == nil
478
+ _compile_assets(:css)
479
+ _compile_assets(:js)
480
+ else
481
+ _compile_assets(type)
482
+ end
483
+
484
+ if precompile_file = assets_opts[:precompiled]
485
+ require 'json'
486
+ ::FileUtils.mkdir_p(File.dirname(precompile_file))
487
+ tmp_file = "#{precompile_file}.tmp"
488
+ ::File.open(tmp_file, 'wb'){|f| f.write((opts[:json_serializer] || :to_json.to_proc).call(assets_opts[:compiled]))}
489
+ ::File.rename(tmp_file, precompile_file)
490
+ end
491
+
492
+ assets_opts[:compiled]
493
+ end
494
+
495
+ private
496
+
497
+ # The initial hash to use to store compiled asset metadata.
498
+ def _compiled_assets_initial_hash
499
+ {}
500
+ end
501
+
502
+ # Internals of compile_assets, handling recursive calls for loading
503
+ # all asset groups under the given type.
504
+ def _compile_assets(type)
505
+ type, *dirs = type if type.is_a?(Array)
506
+ dirs ||= []
507
+ files = assets_opts[type]
508
+ dirs.each{|d| files = files[d]}
509
+
510
+ case files
511
+ when Hash
512
+ files.each_key{|dir| _compile_assets([type] + dirs + [dir])}
513
+ else
514
+ files = Array(files)
515
+ compile_assets_files(files, type, dirs) unless files.empty?
516
+ end
517
+ end
518
+
519
+ # The precompiled asset metadata stored in the given file
520
+ def _precompiled_asset_metadata(file)
521
+ (opts[:json_parser] || ::JSON.method(:parse)).call(::File.read(file))
522
+ end
523
+
524
+ # Compile each array of files for the given type into a single
525
+ # file. Dirs should be an array of asset group names, if these
526
+ # are files in an asset group.
527
+ def compile_assets_files(files, type, dirs)
528
+ dirs = nil if dirs && dirs.empty?
529
+ o = assets_opts
530
+ app = allocate
531
+
532
+ content = files.map do |file|
533
+ file = "#{dirs.join('/')}/#{file}" if dirs && o[:group_subdirs]
534
+ file = "#{o[:"#{type}_path"]}#{file}"
535
+ app.read_asset_file(file, type)
536
+ end.join("\n")
537
+
538
+ unless o[:concat_only]
539
+ content = compress_asset(content, type)
540
+ end
541
+
542
+ suffix = ".#{dirs.join('.')}" if dirs
543
+ key = "#{type}#{suffix}"
544
+ unique_id = o[:compiled][key] = asset_digest(content)
545
+ path = "#{o[:"compiled_#{type}_path"]}#{suffix}.#{unique_id}.#{type}"
546
+ ::FileUtils.mkdir_p(File.dirname(path))
547
+ ::File.open(path, 'wb'){|f| f.write(content)}
548
+
549
+ if o[:gzip]
550
+ require 'zlib'
551
+ Zlib::GzipWriter.open("#{path}.gz") do |gz|
552
+ gz.write(content)
553
+ end
554
+ end
555
+
556
+ nil
557
+ end
558
+
559
+ # Compress the given content for the given type by using the
560
+ # configured compressor, or trying the supported compressors.
561
+ def compress_asset(content, type)
562
+ case compressor = assets_opts[:"#{type}_compressor"]
563
+ when :none
564
+ return content
565
+ when nil
566
+ # default, try different compressors
567
+ else
568
+ # Allow calling private compress methods
569
+ return send("compress_#{type}_#{compressor}", content)
570
+ end
571
+
572
+ compressors = if type == :js
573
+ [:yui, :closure, :uglifier, :minjs]
574
+ else
575
+ [:yui]
576
+ end
577
+
578
+ compressors.each do |comp|
579
+ begin
580
+ # Allow calling private compress methods
581
+ if c = send("compress_#{type}_#{comp}", content)
582
+ return c
583
+ end
584
+ rescue LoadError, CompressorNotFound
585
+ end
586
+ end
587
+
588
+ content
589
+ end
590
+
591
+ # Compress the CSS using YUI Compressor, requires java runtime
592
+ def compress_css_yui(content)
593
+ compress_yui(content, :compress_css)
594
+ end
595
+
596
+ # Compress the JS using Google Closure Compiler, requires java runtime
597
+ def compress_js_closure(content)
598
+ require 'closure-compiler'
599
+
600
+ begin
601
+ ::Closure::Compiler.new.compile(content)
602
+ rescue ::Closure::Error => e
603
+ raise CompressorNotFound, "#{e.class}: #{e.message}", e.backtrace
604
+ end
605
+ end
606
+
607
+ # Compress the JS using MinJS, a pure ruby compressor
608
+ def compress_js_minjs(content)
609
+ require 'minjs'
610
+ Minjs::Compressor::Compressor.new(:debug => false).compress(content).to_js
611
+ end
612
+
613
+ # Compress the JS using Uglifier, requires javascript runtime
614
+ def compress_js_uglifier(content)
615
+ begin
616
+ require 'uglifier'
617
+ rescue => e
618
+ # :nocov:
619
+ raise CompressorNotFound, "#{e.class}: #{e.message}", e.backtrace
620
+ # :nocov:
621
+ end
622
+
623
+ Uglifier.compile(content)
624
+ end
625
+
626
+ # Compress the CSS using YUI Compressor, requires java runtime
627
+ def compress_js_yui(content)
628
+ compress_yui(content, :compress_js)
629
+ end
630
+
631
+ # Compress the CSS/JS using YUI Compressor, requires java runtime
632
+ def compress_yui(content, meth)
633
+ require 'yuicompressor'
634
+ ::YUICompressor.public_send(meth, content, :munge => true)
635
+ rescue ::Errno::ENOENT => e
636
+ raise CompressorNotFound, "#{e.class}: #{e.message}", e.backtrace
637
+ end
638
+
639
+ # Return a unique id for the given content. By default, uses the
640
+ # SHA256 hash of the content. This method can be overridden to use
641
+ # a different digest type or to return a static string if you don't
642
+ # want to use a unique value.
643
+ def asset_digest(content)
644
+ algo = assets_opts[:sri] || :sha256
645
+ digest = begin
646
+ require 'openssl'
647
+ ::OpenSSL::Digest
648
+ # :nocov:
649
+ rescue LoadError
650
+ require 'digest/sha2'
651
+ ::Digest
652
+ # :nocov:
653
+ end
654
+ digest.const_get(algo.to_s.upcase).hexdigest(content)
655
+ end
656
+ end
657
+
658
+ module InstanceMethods
659
+ # Return an array of paths for the given asset type and optionally
660
+ # asset group. See the assets function documentation for details.
661
+ def assets_paths(type)
662
+ o = self.class.assets_opts
663
+ if type.is_a?(Array)
664
+ ltype, *dirs = type
665
+ else
666
+ ltype = type
667
+ end
668
+ stype = ltype.to_s
669
+
670
+ url_prefix = request.script_name if self.class.opts[:add_script_name]
671
+ relative_paths = o[:relative_paths]
672
+
673
+ paths = if o[:compiled]
674
+ relative_paths = false if o[:compiled_asset_host]
675
+ if ukey = _compiled_assets_hash(type, true)
676
+ ["#{o[:compiled_asset_host]}#{url_prefix}/#{o[:"compiled_#{stype}_prefix"]}.#{ukey}.#{stype}"]
677
+ else
678
+ []
679
+ end
680
+ else
681
+ asset_dir = o[ltype]
682
+ if dirs && !dirs.empty?
683
+ dirs.each{|f| asset_dir = asset_dir[f]}
684
+ prefix = "#{dirs.join('/')}/" if o[:group_subdirs]
685
+ end
686
+ Array(asset_dir).map do |f|
687
+ if ts = o[:timestamp_paths]
688
+ mtime = asset_last_modified(File.join(o[:"#{stype}_path"], *[prefix, f].compact))
689
+ mtime = "#{sprintf("%i%06i", mtime.to_i, mtime.usec)}#{ts}"
690
+ end
691
+ "#{url_prefix}/#{o[:"#{stype}_prefix"]}#{mtime}#{prefix}#{f}#{o[:"#{stype}_suffix"]}"
692
+ end
693
+ end
694
+
695
+ if relative_paths
696
+ paths.map! do |path|
697
+ "#{relative_prefix}#{path}"
698
+ end
699
+ end
700
+
701
+ paths
702
+ end
703
+
704
+ # Return a string containing html tags for the given asset type.
705
+ # This will use a script tag for the :js type and a link tag for
706
+ # the :css type.
707
+ #
708
+ # To return the tags for a specific asset group, use an array for
709
+ # the type, such as [:css, :frontend].
710
+ #
711
+ # You can specify custom attributes for the tag by passing a hash
712
+ # as the attrs argument.
713
+ #
714
+ # When the assets are not compiled, this will result in a separate
715
+ # tag for each asset file. When the assets are compiled, this will
716
+ # result in a single tag to the compiled asset file.
717
+ def assets(type, attrs = OPTS)
718
+ ltype = type.is_a?(Array) ? type[0] : type
719
+
720
+ o = self.class.assets_opts
721
+ if o[:compiled] && (algo = o[:sri]) && (hash = _compiled_assets_hash(type))
722
+ attrs = Hash[attrs]
723
+ attrs[:integrity] = "#{algo}-#{h([[hash].pack('H*')].pack('m').tr("\n", ''))}"
724
+ end
725
+
726
+ attributes = attrs.map{|k,v| "#{k}=\"#{h(v)}\""}.join(' ')
727
+
728
+ if ltype == :js
729
+ tag_start = "<script#{' type="text/javascript"' unless attrs[:type]} #{attributes} src=\""
730
+ tag_end = "\"></script>"
731
+ else
732
+ tag_start = "<link rel=\"stylesheet\" #{attributes} href=\""
733
+ tag_end = "\" />"
734
+ end
735
+
736
+ paths = assets_paths(type)
737
+ if o[:early_hints]
738
+ early_hint_as = ltype == :js ? 'script' : 'style'
739
+ early_hints = paths.map{|p| "<#{p}>; rel=preload; as=#{early_hint_as}"}
740
+ early_hints = early_hints.join("\n") if Rack.release < '3'
741
+ send_early_hints(RodaResponseHeaders::LINK=>early_hints)
742
+ end
743
+ paths.map{|p| "#{tag_start}#{h(p)}#{tag_end}"}.join("\n")
744
+ end
745
+
746
+ # Render the asset with the given filename. When assets are compiled,
747
+ # or when the file is already of the given type (no rendering necessary),
748
+ # this returns the contents of the compiled file.
749
+ # When assets are not compiled and the file is not already in the same format,
750
+ # this will render the asset using the render plugin.
751
+ # In both cases, if the file has not been modified since the last request,
752
+ # this will return a 304 response.
753
+ def render_asset(file, type)
754
+ o = self.class.assets_opts
755
+ if o[:compiled]
756
+ file = "#{o[:"compiled_#{type}_path"]}#{file}"
757
+
758
+ if o[:gzip] && env['HTTP_ACCEPT_ENCODING'] =~ /\bgzip\b/
759
+ @_response[RodaResponseHeaders::CONTENT_ENCODING] = 'gzip'
760
+ file += '.gz'
761
+ end
762
+
763
+ check_asset_request(file, type, ::File.stat(file).mtime)
764
+ ::File.read(file)
765
+ else
766
+ file = "#{o[:"#{type}_path"]}#{file}"
767
+ check_asset_request(file, type, asset_last_modified(file))
768
+ read_asset_file(file, type)
769
+ end
770
+ end
771
+
772
+ # Return the content of the file if it is already of the correct type.
773
+ # Otherwise, render the file using the render plugin. +file+ should be
774
+ # the relative path to the file from the current directory.
775
+ def read_asset_file(file, type)
776
+ o = self.class.assets_opts
777
+
778
+ content = if file.end_with?(".#{type}")
779
+ ::File.read(file)
780
+ else
781
+ render_asset_file(file, :template_opts=>o[:"#{type}_opts"], :dependencies=>o[:expanded_dependencies][file])
782
+ end
783
+
784
+ o[:postprocessor] ? o[:postprocessor].call(file, type, content) : content
785
+ end
786
+
787
+ private
788
+
789
+ def _compiled_assets_hash(type, return_ukey=false)
790
+ compiled = self.class.assets_opts[:compiled]
791
+ type, *dirs = type if type.is_a?(Array)
792
+ stype = type.to_s
793
+
794
+ if dirs && !dirs.empty?
795
+ key = dirs.join('.')
796
+ ckey = "#{stype}.#{key}"
797
+ if hash = ukey = compiled[ckey]
798
+ ukey = "#{key}.#{ukey}"
799
+ end
800
+ else
801
+ hash = ukey = compiled[stype]
802
+ end
803
+
804
+ return_ukey ? ukey : hash
805
+ end
806
+
807
+ # Return when the file was last modified. If the file depends on any
808
+ # other files, check the modification times of all dependencies and
809
+ # return the maximum.
810
+ def asset_last_modified(file)
811
+ if deps = self.class.assets_opts[:expanded_dependencies][file]
812
+ ([file] + Array(deps)).map{|f| ::File.stat(f).mtime}.max
813
+ else
814
+ ::File.stat(file).mtime
815
+ end
816
+ end
817
+
818
+ # If the asset hasn't been modified since the last request, return
819
+ # a 304 response immediately. Otherwise, add the appropriate
820
+ # type-specific headers.
821
+ def check_asset_request(file, type, mtime)
822
+ @_request.last_modified(mtime)
823
+ @_response.headers.merge!(self.class.assets_opts[:"#{type}_headers"])
824
+ end
825
+
826
+ # Render the given asset file using the render plugin, with the given options.
827
+ # +file+ should be the relative path to the file from the current directory.
828
+ def render_asset_file(file, options)
829
+ render_template({:path => file}, options)
830
+ end
831
+ end
832
+
833
+ module RequestClassMethods
834
+ # An array of asset type strings and regexps for that type, for all asset types
835
+ # handled.
836
+ def assets_matchers
837
+ @assets_matchers ||= [:css, :js].map do |t|
838
+ if regexp = assets_regexp(t)
839
+ [t, regexp].freeze
840
+ end
841
+ end.compact.freeze
842
+ end
843
+
844
+ private
845
+
846
+ # A string for the asset filename for the asset type, key, and digest.
847
+ def _asset_regexp(type, key, digest)
848
+ "#{key.sub(/\A#{type}/, '')}.#{digest}.#{type}"
849
+ end
850
+
851
+ # The regexp matcher to use for the given type. This handles any asset groups
852
+ # for the asset types.
853
+ def assets_regexp(type)
854
+ o = roda_class.assets_opts
855
+ if compiled = o[:compiled]
856
+ assets = compiled.
857
+ select{|k,_| k =~ /\A#{type}/}.
858
+ map{|k, md| _asset_regexp(type, k, md)}
859
+ return if assets.empty?
860
+ /#{o[:"compiled_#{type}_prefix"]}(#{Regexp.union(assets)})/
861
+ else
862
+ return unless assets = o[type]
863
+ assets = unnest_assets_hash(assets)
864
+ ts = o[:timestamp_paths]
865
+ /#{o[:"#{type}_prefix"]}#{"\\d+#{ts}" if ts}(#{Regexp.union(assets.uniq)})#{o[:"#{type}_suffix"]}/
866
+ end
867
+ end
868
+
869
+ # Recursively unnested the given assets hash, returning a single array of asset
870
+ # files for the given.
871
+ def unnest_assets_hash(h)
872
+ case h
873
+ when Hash
874
+ h.flat_map do |k,v|
875
+ assets = unnest_assets_hash(v)
876
+ assets = assets.map{|x| "#{k}/#{x}"} if roda_class.assets_opts[:group_subdirs]
877
+ assets
878
+ end
879
+ else
880
+ Array(h)
881
+ end
882
+ end
883
+ end
884
+
885
+ module RequestMethods
886
+ # Render the matching asset if this is a GET request for a supported asset.
887
+ def assets
888
+ if is_get?
889
+ self.class.assets_matchers.each do |type, matcher|
890
+ is matcher do |file|
891
+ scope.render_asset(file, type)
892
+ end
893
+ end
894
+ nil
895
+ end
896
+ end
897
+ end
898
+ end
899
+
900
+ register_plugin(:assets, Assets)
901
+ end
902
+ end