middleman-core 4.1.0.rc.2 → 4.1.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 (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