middleman-core 4.1.0.rc.2 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/features/asset_hash.feature +30 -32
  3. data/features/asset_host.feature +2 -0
  4. data/features/gzip.feature +1 -1
  5. data/features/import_files.feature +0 -2
  6. data/features/nested_layouts.feature +20 -17
  7. data/fixtures/asset-host-app/source/javascripts/asset_host.js +2 -0
  8. data/fixtures/frontmatter-neighbor-app/config.rb +1 -1
  9. data/fixtures/frontmatter-settings-neighbor-app/config.rb +1 -1
  10. data/fixtures/nested-layout-app/source/layouts/inner.erb +5 -2
  11. data/fixtures/nested-layout-app/source/layouts/inner_haml.haml +6 -2
  12. data/fixtures/nested-layout-app/source/layouts/inner_slim.slim +6 -2
  13. data/fixtures/nested-layout-app/source/layouts/master.erb +7 -1
  14. data/fixtures/nested-layout-app/source/layouts/master_haml.haml +5 -1
  15. data/fixtures/nested-layout-app/source/layouts/master_slim.slim +5 -1
  16. data/fixtures/nested-layout-app/source/layouts/outer.erb +6 -2
  17. data/fixtures/nested-layout-app/source/layouts/outer_haml.haml +5 -1
  18. data/fixtures/nested-layout-app/source/layouts/outer_slim.slim +5 -1
  19. data/lib/middleman-core.rb +0 -3
  20. data/lib/middleman-core/application.rb +7 -9
  21. data/lib/middleman-core/builder.rb +88 -44
  22. data/lib/middleman-core/contracts.rb +102 -13
  23. data/lib/middleman-core/core_extensions/data.rb +15 -10
  24. data/lib/middleman-core/core_extensions/default_helpers.rb +15 -6
  25. data/lib/middleman-core/core_extensions/file_watcher.rb +2 -2
  26. data/lib/middleman-core/core_extensions/front_matter.rb +11 -3
  27. data/lib/middleman-core/core_extensions/i18n.rb +1 -1
  28. data/lib/middleman-core/core_extensions/inline_url_rewriter.rb +2 -2
  29. data/lib/middleman-core/extension.rb +1 -1
  30. data/lib/middleman-core/extensions.rb +1 -1
  31. data/lib/middleman-core/extensions/asset_hash.rb +1 -1
  32. data/lib/middleman-core/extensions/asset_host.rb +1 -1
  33. data/lib/middleman-core/extensions/automatic_image_sizes.rb +1 -1
  34. data/lib/middleman-core/extensions/cache_buster.rb +1 -1
  35. data/lib/middleman-core/extensions/external_pipeline.rb +2 -1
  36. data/lib/middleman-core/extensions/gzip.rb +2 -2
  37. data/lib/middleman-core/extensions/minify_css.rb +1 -1
  38. data/lib/middleman-core/extensions/minify_javascript.rb +1 -1
  39. data/lib/middleman-core/extensions/relative_assets.rb +1 -1
  40. data/lib/middleman-core/file_renderer.rb +12 -9
  41. data/lib/middleman-core/logger.rb +1 -0
  42. data/lib/middleman-core/preview_server.rb +14 -14
  43. data/lib/middleman-core/renderers/haml.rb +3 -1
  44. data/lib/middleman-core/renderers/less.rb +1 -1
  45. data/lib/middleman-core/renderers/liquid.rb +1 -1
  46. data/lib/middleman-core/renderers/sass.rb +7 -2
  47. data/lib/middleman-core/sitemap/extensions/ignores.rb +2 -2
  48. data/lib/middleman-core/sitemap/extensions/import.rb +3 -1
  49. data/lib/middleman-core/sitemap/resource.rb +7 -6
  50. data/lib/middleman-core/sources.rb +30 -13
  51. data/lib/middleman-core/sources/source_watcher.rb +50 -12
  52. data/lib/middleman-core/step_definitions/middleman_steps.rb +2 -2
  53. data/lib/middleman-core/template_context.rb +1 -1
  54. data/lib/middleman-core/template_renderer.rb +13 -4
  55. data/lib/middleman-core/util.rb +6 -606
  56. data/lib/middleman-core/util/binary.rb +79 -0
  57. data/lib/middleman-core/util/data.rb +37 -8
  58. data/lib/middleman-core/util/files.rb +134 -0
  59. data/lib/middleman-core/util/paths.rb +251 -0
  60. data/lib/middleman-core/util/rack.rb +52 -0
  61. data/lib/middleman-core/util/uri_templates.rb +97 -0
  62. data/lib/middleman-core/version.rb +1 -1
  63. data/middleman-core.gemspec +1 -0
  64. metadata +25 -4
@@ -31,13 +31,13 @@ end
31
31
  Then /^the file "([^\"]*)" has the contents$/ do |path, contents|
32
32
  write_file(path, contents)
33
33
 
34
- @server_inst.files.find_new_files!
34
+ @server_inst.files.poll_once!
35
35
  end
36
36
 
37
37
  Then /^the file "([^\"]*)" is removed$/ do |path|
38
38
  step %Q{I remove the file "#{path}"}
39
39
 
40
- @server_inst.files.find_new_files!
40
+ @server_inst.files.poll_once!
41
41
  end
42
42
 
43
43
  Given /^a modification time for a file named "([^\"]*)"$/ do |file|
@@ -110,7 +110,7 @@ module Middleman
110
110
  r = sitemap.find_resource_by_path(source_path)
111
111
 
112
112
  if (r && !r.template?) || (Tilt[partial_file[:full_path]].nil? && partial_file[:full_path].exist?)
113
- File.read(partial_file[:full_path])
113
+ partial_file.read
114
114
  else
115
115
  opts = options.dup
116
116
  locs = opts.delete(:locals)
@@ -133,12 +133,21 @@ module Middleman
133
133
  # Add extension helpers to context.
134
134
  @app.extensions.add_exposed_to_context(context)
135
135
 
136
- content = _render_with_all_renderers(path, locs, context, opts, &block)
136
+ content = ::Middleman::Util.instrument 'builder.output.resource.render-template', path: File.basename(path) do
137
+ _render_with_all_renderers(path, locs, context, opts, &block)
138
+ end
137
139
 
138
140
  # If we need a layout and have a layout, use it
139
- if layout_file = fetch_layout(engine, options)
140
- layout_renderer = ::Middleman::FileRenderer.new(@app, layout_file[:relative_path].to_s)
141
- content = layout_renderer.render(locals, options, context) { content }
141
+ layout_file = fetch_layout(engine, options)
142
+ if layout_file
143
+ content = ::Middleman::Util.instrument 'builder.output.resource.render-layout', path: File.basename(layout_file[:relative_path].to_s) do
144
+ if layout_file = fetch_layout(engine, options)
145
+ layout_renderer = ::Middleman::FileRenderer.new(@app, layout_file[:relative_path].to_s)
146
+ layout_renderer.render(locals, options, context) { content }
147
+ else
148
+ content
149
+ end
150
+ end
142
151
  end
143
152
 
144
153
  # Return result
@@ -1,624 +1,24 @@
1
1
  # For instrumenting
2
2
  require 'active_support/notifications'
3
3
 
4
- # Core Pathname library used for traversal
5
- require 'pathname'
6
-
7
- # Template and Mime detection
8
- require 'tilt'
9
- require 'rack/mime'
10
-
11
- # DbC
12
- require 'middleman-core/contracts'
13
4
  require 'middleman-core/application'
14
5
  require 'middleman-core/sources'
15
6
  require 'middleman-core/sitemap/resource'
16
-
17
- # Indifferent Access
18
- require 'hashie'
19
-
20
- # For URI templating
21
- require 'addressable/uri'
22
- require 'addressable/template'
23
- require 'active_support/inflector'
24
- require 'active_support/inflector/transliterate'
7
+ require 'middleman-core/util/binary'
8
+ require 'middleman-core/util/data'
9
+ require 'middleman-core/util/files'
10
+ require 'middleman-core/util/paths'
11
+ require 'middleman-core/util/rack'
12
+ require 'middleman-core/util/uri_templates'
25
13
 
26
14
  module Middleman
27
15
  module Util
28
- include Contracts
29
-
30
16
  module_function
31
17
 
32
- # Whether the source file is binary.
33
- #
34
- # @param [String] filename The file to check.
35
- # @return [Boolean]
36
- Contract Or[String, Pathname] => Bool
37
- def binary?(filename)
38
- path = Pathname(filename)
39
- ext = path.extname
40
-
41
- # We hardcode detecting of gzipped SVG files
42
- return true if ext == '.svgz'
43
-
44
- return false if Tilt.registered?(ext.sub('.', ''))
45
-
46
- dot_ext = (ext.to_s[0] == '.') ? ext.dup : ".#{ext}"
47
-
48
- if mime = ::Rack::Mime.mime_type(dot_ext, nil)
49
- !nonbinary_mime?(mime)
50
- else
51
- file_contents_include_binary_bytes?(path.to_s)
52
- end
53
- end
54
-
55
- # Takes a matcher, which can be a literal string
56
- # or a string containing glob expressions, or a
57
- # regexp, or a proc, or anything else that responds
58
- # to #match or #call, and returns whether or not the
59
- # given path matches that matcher.
60
- #
61
- # @param [String, #match, #call] matcher A matcher String, RegExp, Proc, etc.
62
- # @param [String] path A path as a string
63
- # @return [Boolean] Whether the path matches the matcher
64
- Contract PATH_MATCHER, String => Bool
65
- def path_match(matcher, path)
66
- case
67
- when matcher.is_a?(String)
68
- if matcher.include? '*'
69
- File.fnmatch(matcher, path)
70
- else
71
- path == matcher
72
- end
73
- when matcher.respond_to?(:match)
74
- !matcher.match(path).nil?
75
- when matcher.respond_to?(:call)
76
- matcher.call(path)
77
- else
78
- File.fnmatch(matcher.to_s, path)
79
- end
80
- end
81
-
82
- class EnhancedHash < ::Hashie::Mash
83
- # include ::Hashie::Extensions::MergeInitializer
84
- # include ::Hashie::Extensions::MethodReader
85
- # include ::Hashie::Extensions::IndifferentAccess
86
- end
87
-
88
- # Recursively convert a normal Hash into a EnhancedHash
89
- #
90
- # @private
91
- # @param [Hash] data Normal hash
92
- # @return [Hash]
93
- Contract Maybe[Hash] => Maybe[Or[Array, EnhancedHash]]
94
- def recursively_enhance(obj)
95
- if obj.is_a? ::Array
96
- obj.map { |e| recursively_enhance(e) }
97
- elsif obj.is_a? ::Hash
98
- ::Hashie::Mash.new(obj)
99
- else
100
- obj
101
- end
102
- end
103
-
104
- # Normalize a path to not include a leading slash
105
- # @param [String] path
106
- # @return [String]
107
- Contract String => String
108
- def normalize_path(path)
109
- # The tr call works around a bug in Ruby's Unicode handling
110
- ::URI.decode(path).sub(%r{^/}, '').tr('', '')
111
- end
112
-
113
- # This is a separate method from normalize_path in case we
114
- # change how we normalize paths
115
- Contract String => String
116
- def strip_leading_slash(path)
117
- path.sub(%r{^/}, '')
118
- end
119
-
120
18
  # Facade for ActiveSupport/Notification
121
19
  def instrument(name, payload={}, &block)
122
20
  suffixed_name = (name =~ /\.middleman$/) ? name.dup : "#{name}.middleman"
123
21
  ::ActiveSupport::Notifications.instrument(suffixed_name, payload, &block)
124
22
  end
125
-
126
- # Extract the text of a Rack response as a string.
127
- # Useful for extensions implemented as Rack middleware.
128
- # @param response The response from #call
129
- # @return [String] The whole response as a string.
130
- Contract RespondTo[:each] => String
131
- def extract_response_text(response)
132
- # The rack spec states all response bodies must respond to each
133
- result = ''
134
- response.each do |part, _|
135
- result << part
136
- end
137
- result
138
- end
139
-
140
- # Get a recusive list of files inside a path.
141
- # Works with symlinks.
142
- #
143
- # @param path Some path string or Pathname
144
- # @param ignore A proc/block that returns true if a given path should be ignored - if a path
145
- # is ignored, nothing below it will be searched either.
146
- # @return [Array<Pathname>] An array of Pathnames for each file (no directories)
147
- Contract Or[String, Pathname], Proc => ArrayOf[Pathname]
148
- def all_files_under(path, &ignore)
149
- path = Pathname(path)
150
-
151
- if path.directory?
152
- path.children.flat_map do |child|
153
- all_files_under(child, &ignore)
154
- end.compact
155
- elsif path.file?
156
- if block_given? && yield(path)
157
- []
158
- else
159
- [path]
160
- end
161
- else
162
- []
163
- end
164
- end
165
-
166
- # Get the path of a file of a given type
167
- #
168
- # @param [Middleman::Application] app The app.
169
- # @param [Symbol] kind The type of file
170
- # @param [String, Symbol] source The path to the file
171
- # @param [Hash] options Data to pass through.
172
- # @return [String]
173
- Contract ::Middleman::Application, Symbol, Or[String, Symbol], Hash => String
174
- def asset_path(app, kind, source, options={})
175
- return source if source.to_s.include?('//') || source.to_s.start_with?('data:')
176
-
177
- asset_folder = case kind
178
- when :css
179
- app.config[:css_dir]
180
- when :js
181
- app.config[:js_dir]
182
- when :images
183
- app.config[:images_dir]
184
- when :fonts
185
- app.config[:fonts_dir]
186
- else
187
- kind.to_s
188
- end
189
-
190
- source = source.to_s.tr(' ', '')
191
- ignore_extension = (kind == :images || kind == :fonts) # don't append extension
192
- source << ".#{kind}" unless ignore_extension || source.end_with?(".#{kind}")
193
- asset_folder = '' if source.start_with?('/') # absolute path
194
-
195
- asset_url(app, source, asset_folder, options)
196
- end
197
-
198
- # Get the URL of an asset given a type/prefix
199
- #
200
- # @param [String] path The path (such as "photo.jpg")
201
- # @param [String] prefix The type prefix (such as "images")
202
- # @param [Hash] options Data to pass through.
203
- # @return [String] The fully qualified asset url
204
- Contract ::Middleman::Application, String, String, Hash => String
205
- def asset_url(app, path, prefix='', options={})
206
- # Don't touch assets which already have a full path
207
- return path if path.include?('//') || path.start_with?('data:')
208
-
209
- if options[:relative] && !options[:current_resource]
210
- raise ArgumentError, '#asset_url must be run in a context with current_resource if relative: true'
211
- end
212
-
213
- uri = URI(path)
214
- path = uri.path
215
-
216
- result = if resource = app.sitemap.find_resource_by_destination_path(url_for(app, path, options))
217
- resource.url
218
- else
219
- path = File.join(prefix, path)
220
- if resource = app.sitemap.find_resource_by_path(path)
221
- resource.url
222
- else
223
- File.join(app.config[:http_prefix], path)
224
- end
225
- end
226
-
227
- final_result = ::URI.encode(relative_path_from_resource(options[:current_resource], result, options[:relative]))
228
-
229
- result_uri = URI(final_result)
230
- result_uri.query = uri.query
231
- result_uri.fragment = uri.fragment
232
- result_uri.to_s
233
- end
234
-
235
- # Given a source path (referenced either absolutely or relatively)
236
- # or a Resource, this will produce the nice URL configured for that
237
- # path, respecting :relative_links, directory indexes, etc.
238
- Contract ::Middleman::Application, Or[String, ::Middleman::Sitemap::Resource], Hash => String
239
- def url_for(app, path_or_resource, options={})
240
- # Handle Resources and other things which define their own url method
241
- url = if path_or_resource.respond_to?(:url)
242
- path_or_resource.url
243
- else
244
- path_or_resource.dup
245
- end
246
-
247
- # Try to parse URL
248
- begin
249
- uri = URI(url)
250
- rescue ::URI::InvalidURIError
251
- # Nothing we can do with it, it's not really a URI
252
- return url
253
- end
254
-
255
- relative = options[:relative]
256
- raise "Can't use the relative option with an external URL" if relative && uri.host
257
-
258
- # Allow people to turn on relative paths for all links with
259
- # set :relative_links, true
260
- # but still override on a case by case basis with the :relative parameter.
261
- effective_relative = relative || false
262
- effective_relative = true if relative.nil? && app.config[:relative_links]
263
-
264
- # Try to find a sitemap resource corresponding to the desired path
265
- this_resource = options[:current_resource]
266
-
267
- if path_or_resource.is_a?(::Middleman::Sitemap::Resource)
268
- resource = path_or_resource
269
- resource_url = url
270
- elsif this_resource && uri.path && !uri.host
271
- # Handle relative urls
272
- url_path = Pathname(uri.path)
273
- current_source_dir = Pathname('/' + this_resource.path).dirname
274
- url_path = current_source_dir.join(url_path) if url_path.relative?
275
- resource = app.sitemap.find_resource_by_path(url_path.to_s)
276
- if resource
277
- resource_url = resource.url
278
- else
279
- # Try to find a resource relative to destination paths
280
- url_path = Pathname(uri.path)
281
- current_source_dir = Pathname('/' + this_resource.destination_path).dirname
282
- url_path = current_source_dir.join(url_path) if url_path.relative?
283
- resource = app.sitemap.find_resource_by_destination_path(url_path.to_s)
284
- resource_url = resource.url if resource
285
- end
286
- elsif options[:find_resource] && uri.path && !uri.host
287
- resource = app.sitemap.find_resource_by_path(uri.path)
288
- resource_url = resource.url if resource
289
- end
290
-
291
- if resource
292
- uri.path = if this_resource
293
- ::URI.encode(relative_path_from_resource(this_resource, resource_url, effective_relative))
294
- else
295
- resource_url
296
- end
297
- end
298
-
299
- # Support a :query option that can be a string or hash
300
- if query = options[:query]
301
- uri.query = query.respond_to?(:to_param) ? query.to_param : query.to_s
302
- end
303
-
304
- # Support a :fragment or :anchor option just like Padrino
305
- fragment = options[:anchor] || options[:fragment]
306
- uri.fragment = fragment.to_s if fragment
307
-
308
- # Finally make the URL back into a string
309
- uri.to_s
310
- end
311
-
312
- # Expand a path to include the index file if it's a directory
313
- #
314
- # @param [String] path Request path/
315
- # @param [Middleman::Application] app The requesting app.
316
- # @return [String] Path with index file if necessary.
317
- Contract String, ::Middleman::Application => String
318
- def full_path(path, app)
319
- resource = app.sitemap.find_resource_by_destination_path(path)
320
-
321
- unless resource
322
- # Try it with /index.html at the end
323
- indexed_path = File.join(path.sub(%r{/$}, ''), app.config[:index_file])
324
- resource = app.sitemap.find_resource_by_destination_path(indexed_path)
325
- end
326
-
327
- if resource
328
- '/' + resource.destination_path
329
- else
330
- '/' + normalize_path(path)
331
- end
332
- end
333
-
334
- Contract String, String, ArrayOf[String], Proc => String
335
- def rewrite_paths(body, _path, exts, &_block)
336
- matcher = /([=\'\"\(,]\s*)([^\s\'\"\)>]+(#{Regexp.union(exts)}))/
337
-
338
- url_fn_prefix = 'url('
339
-
340
- body.dup.gsub(matcher) do |match|
341
- opening_character = $1
342
- asset_path = $2
343
-
344
- if asset_path.start_with?(url_fn_prefix)
345
- opening_character << url_fn_prefix
346
- asset_path = asset_path[url_fn_prefix.length..-1]
347
- end
348
-
349
- begin
350
- uri = ::Addressable::URI.parse(asset_path)
351
-
352
- if uri.relative? && uri.host.nil? && !asset_path.match(/^[^\/].*[a-z]+\.[a-z]+\/.*/) && (result = yield(asset_path))
353
- "#{opening_character}#{result}"
354
- else
355
- match
356
- end
357
- rescue ::Addressable::URI::InvalidURIError
358
- match
359
- end
360
- end
361
- end
362
-
363
- # Is mime type known to be non-binary?
364
- #
365
- # @param [String] mime The mimetype to check.
366
- # @return [Boolean]
367
- Contract String => Bool
368
- def nonbinary_mime?(mime)
369
- case
370
- when mime.start_with?('text/')
371
- true
372
- when mime.include?('xml') && !mime.include?('officedocument')
373
- true
374
- when mime.include?('json')
375
- true
376
- when mime.include?('javascript')
377
- true
378
- else
379
- false
380
- end
381
- end
382
-
383
- # Read a few bytes from the file and see if they are binary.
384
- #
385
- # @param [String] filename The file to check.
386
- # @return [Boolean]
387
- Contract String => Bool
388
- def file_contents_include_binary_bytes?(filename)
389
- binary_bytes = [0, 1, 2, 3, 4, 5, 6, 11, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 28, 29, 30, 31]
390
- s = File.read(filename, 4096) || ''
391
- s.each_byte do |c|
392
- return true if binary_bytes.include?(c)
393
- end
394
-
395
- false
396
- end
397
-
398
- # Glob a directory and try to keep path encoding consistent.
399
- #
400
- # @param [String] path The glob path.
401
- # @return [Array<String>]
402
- def glob_directory(path)
403
- results = ::Dir[path]
404
-
405
- return results unless RUBY_PLATFORM =~ /darwin/
406
-
407
- results.map { |r| r.encode('UTF-8', 'UTF-8-MAC') }
408
- end
409
-
410
- # Get the PWD and try to keep path encoding consistent.
411
- #
412
- # @param [String] path The glob path.
413
- # @return [Array<String>]
414
- def current_directory
415
- result = ::Dir.pwd
416
-
417
- return result unless RUBY_PLATFORM =~ /darwin/
418
-
419
- result.encode('UTF-8', 'UTF-8-MAC')
420
- end
421
-
422
- # Get a relative path to a resource.
423
- #
424
- # @param [Middleman::Sitemap::Resource] curr_resource The resource.
425
- # @param [String] resource_url The target url.
426
- # @param [Boolean] relative If the path should be relative.
427
- # @return [String]
428
- Contract ::Middleman::Sitemap::Resource, String, Bool => String
429
- def relative_path_from_resource(curr_resource, resource_url, relative)
430
- # Switch to the relative path between resource and the given resource
431
- # if we've been asked to.
432
- if relative
433
- # Output urls relative to the destination path, not the source path
434
- current_dir = Pathname('/' + curr_resource.destination_path).dirname
435
- relative_path = Pathname(resource_url).relative_path_from(current_dir).to_s
436
-
437
- # Put back the trailing slash to avoid unnecessary Apache redirects
438
- if resource_url.end_with?('/') && !relative_path.end_with?('/')
439
- relative_path << '/'
440
- end
441
-
442
- relative_path
443
- else
444
- resource_url
445
- end
446
- end
447
-
448
- Contract String => String
449
- def step_through_extensions(path)
450
- while ::Tilt[path]
451
- yield File.extname(path) if block_given?
452
-
453
- # Strip templating extensions as long as Tilt knows them
454
- path = path.sub(/#{::Regexp.escape(File.extname(path))}$/, '')
455
- end
456
-
457
- yield File.extname(path) if block_given?
458
-
459
- path
460
- end
461
-
462
- # Removes the templating extensions, while keeping the others
463
- # @param [String] path
464
- # @return [String]
465
- Contract String => String
466
- def remove_templating_extensions(path)
467
- step_through_extensions(path)
468
- end
469
-
470
- # Removes the templating extensions, while keeping the others
471
- # @param [String] path
472
- # @return [String]
473
- Contract String => ArrayOf[String]
474
- def collect_extensions(path)
475
- result = []
476
-
477
- step_through_extensions(path) { |e| result << e }
478
-
479
- result
480
- end
481
-
482
- # Convert a path to a file resprentation.
483
- #
484
- # @param [Pathname] path The path.
485
- # @return [Middleman::SourceFile]
486
- Contract Pathname, Pathname, Symbol, Bool => ::Middleman::SourceFile
487
- def path_to_source_file(path, directory, type, destination_dir)
488
- types = Set.new([type])
489
-
490
- relative_path = path.relative_path_from(directory)
491
- relative_path = File.join(destination_dir, relative_path) if destination_dir
492
-
493
- ::Middleman::SourceFile.new(Pathname(relative_path), path, directory, types)
494
- end
495
-
496
- # Finds files which should also be considered to be dirty when
497
- # the given file(s) are touched.
498
- #
499
- # @param [Middleman::Application] app The app.
500
- # @param [Pathname] files The original touched file paths.
501
- # @return [Middleman::SourceFile] All related file paths, not including the source file paths.
502
- Contract ::Middleman::Application, ArrayOf[Pathname] => ArrayOf[::Middleman::SourceFile]
503
- def find_related_files(app, files)
504
- all_extensions = files.flat_map { |f| collect_extensions(f.to_s) }
505
-
506
- sass_type_aliasing = ['.scss', '.sass']
507
- erb_type_aliasing = ['.erb', '.haml', '.slim']
508
-
509
- if (all_extensions & sass_type_aliasing).length > 0
510
- all_extensions |= sass_type_aliasing
511
- end
512
-
513
- if (all_extensions & erb_type_aliasing).length > 0
514
- all_extensions |= erb_type_aliasing
515
- end
516
-
517
- all_extensions.uniq!
518
-
519
- app.sitemap.resources.select(&:file_descriptor).select { |r|
520
- local_extensions = collect_extensions(r.file_descriptor[:full_path].to_s)
521
-
522
- if (local_extensions & sass_type_aliasing).length > 0
523
- local_extensions |= sass_type_aliasing
524
- end
525
-
526
- if (local_extensions & erb_type_aliasing).length > 0
527
- local_extensions |= erb_type_aliasing
528
- end
529
-
530
- local_extensions.uniq!
531
-
532
- ((all_extensions & local_extensions).length > 0) && files.none? { |f| f == r.file_descriptor[:full_path] }
533
- }.map(&:file_descriptor)
534
- end
535
-
536
- # Handy methods for dealing with URI templates. Mix into whatever class.
537
- module UriTemplates
538
- module_function
539
-
540
- # Given a URI template string, make an Addressable::Template
541
- # This supports the legacy middleman-blog/Sinatra style :colon
542
- # URI templates as well as RFC6570 templates.
543
- #
544
- # @param [String] tmpl_src URI template source
545
- # @return [Addressable::Template] a URI template
546
- def uri_template(tmpl_src)
547
- # Support the RFC6470 templates directly if people use them
548
- if tmpl_src.include?(':')
549
- tmpl_src = tmpl_src.gsub(/:([A-Za-z0-9]+)/, '{\1}')
550
- end
551
-
552
- ::Addressable::Template.new ::Middleman::Util.normalize_path(tmpl_src)
553
- end
554
-
555
- # Apply a URI template with the given data, producing a normalized
556
- # Middleman path.
557
- #
558
- # @param [Addressable::Template] template
559
- # @param [Hash] data
560
- # @return [String] normalized path
561
- def apply_uri_template(template, data)
562
- ::Middleman::Util.normalize_path ::Addressable::URI.unencode(template.expand(data)).to_s
563
- end
564
-
565
- # Use a template to extract parameters from a path, and validate some special (date)
566
- # keys. Returns nil if the special keys don't match.
567
- #
568
- # @param [Addressable::Template] template
569
- # @param [String] path
570
- def extract_params(template, path)
571
- template.extract(path, BlogTemplateProcessor)
572
- end
573
-
574
- # Parameterize a string preserving any multibyte characters
575
- def safe_parameterize(str)
576
- sep = '-'
577
-
578
- # Reimplementation of http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-parameterize that preserves un-transliterate-able multibyte chars.
579
- parameterized_string = ActiveSupport::Inflector.transliterate(str.to_s).downcase
580
- parameterized_string.gsub!(/[^a-z0-9\-_\?]+/, sep)
581
-
582
- parameterized_string.chars.to_a.each_with_index do |char, i|
583
- next unless char == '?' && str[i].bytes.count != 1
584
- parameterized_string[i] = str[i]
585
- end
586
-
587
- re_sep = Regexp.escape(sep)
588
- # No more than one of the separator in a row.
589
- parameterized_string.gsub!(/#{re_sep}{2,}/, sep)
590
- # Remove leading/trailing separator.
591
- parameterized_string.gsub!(/^#{re_sep}|#{re_sep}$/, '')
592
-
593
- parameterized_string
594
- end
595
-
596
- # Convert a date into a hash of components to strings
597
- # suitable for using in a URL template.
598
- # @param [DateTime] date
599
- # @return [Hash] parameters
600
- def date_to_params(date)
601
- {
602
- year: date.year.to_s,
603
- month: date.month.to_s.rjust(2, '0'),
604
- day: date.day.to_s.rjust(2, '0')
605
- }
606
- end
607
- end
608
-
609
- # A special template processor that validates date fields
610
- # and has an extra-permissive default regex.
611
- #
612
- # See https://github.com/sporkmonger/addressable/blob/master/lib/addressable/template.rb#L279
613
- class BlogTemplateProcessor
614
- def self.match(name)
615
- case name
616
- when 'year' then '\d{4}'
617
- when 'month' then '\d{2}'
618
- when 'day' then '\d{2}'
619
- else '.*?'
620
- end
621
- end
622
- end
623
23
  end
624
24
  end