Module: Middleman::Util

Includes:
Contracts
Defined in:
lib/middleman-core/util.rb

Defined Under Namespace

Modules: Data, UriTemplates Classes: BlogTemplateProcessor, EnhancedHash

Constant Summary

Constants included from Contracts

Contracts::PATH_MATCHER

Class Method Summary collapse

Methods included from Contracts

#Contract

Class Method Details

.all_files_under(path, &ignore) ⇒ Object



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/middleman-core/util.rb', line 145

def all_files_under(path, &ignore)
  path = Pathname(path)

  if path.directory?
    path.children.flat_map do |child|
      all_files_under(child, &ignore)
    end.compact
  elsif path.file?
    if block_given? && ignore.call(path)
      []
    else
      [path]
    end
  else
    []
  end
end

.asset_path(app, kind, source, options = {}) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/middleman-core/util.rb', line 171

def asset_path(app, kind, source, options={})
  return source if source.to_s.include?('//') || source.to_s.start_with?('data:')

  asset_folder = case kind
  when :css
    app.config[:css_dir]
  when :js
    app.config[:js_dir]
  when :images
    app.config[:images_dir]
  when :fonts
    app.config[:fonts_dir]
  else
    kind.to_s
  end

  source = source.to_s.tr(' ', '')
  ignore_extension = (kind == :images || kind == :fonts) # don't append extension
  source << ".#{kind}" unless ignore_extension || source.end_with?(".#{kind}")
  asset_folder = '' if source.start_with?('/') # absolute path

  asset_url(app, source, asset_folder, options)
end

.asset_url(app, path, prefix = '', options = {}) ⇒ Object



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/middleman-core/util.rb', line 202

def asset_url(app, path, prefix='', options={})
  # Don't touch assets which already have a full path
  if path.include?('//') || path.start_with?('data:')
    path
  else # rewrite paths to use their destination path
    result = if resource = app.sitemap.find_resource_by_destination_path(url_for(app, path))
      resource.url
    else
      path = File.join(prefix, path)
      if resource = app.sitemap.find_resource_by_path(path)
        resource.url
      else
        File.join(app.config[:http_prefix], path)
      end
    end

    if options[:relative] != true
      result
    else
      unless options[:current_resource]
        raise ArgumentError, '#asset_url must be run in a context with current_resource if relative: true'
      end

      current_dir = Pathname('/' + options[:current_resource].destination_path)
      Pathname(result).relative_path_from(current_dir.dirname).to_s
    end
  end
end

.binary?(filename) ⇒ Boolean

Returns:

  • (Boolean)


34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/middleman-core/util.rb', line 34

def binary?(filename)
  path = Pathname(filename)
  ext = path.extname

  # We hardcode detecting of gzipped SVG files
  return true if ext == '.svgz'

  return false if Tilt.registered?(ext.sub('.', ''))

  dot_ext = (ext.to_s[0] == '.') ? ext.dup : ".#{ext}"

  if mime = ::Rack::Mime.mime_type(dot_ext, nil)
    !nonbinary_mime?(mime)
  else
    file_contents_include_binary_bytes?(path.to_s)
  end
end

.collect_extensions(path) ⇒ Object



473
474
475
476
477
478
479
# File 'lib/middleman-core/util.rb', line 473

def collect_extensions(path)
  result = []

  step_through_extensions(path) { |e| result << e }

  result
end

.current_directoryArray<String>

Get the PWD and try to keep path encoding consistent.

Parameters:

  • path (String)

    The glob path.

Returns:



413
414
415
416
417
418
419
# File 'lib/middleman-core/util.rb', line 413

def current_directory
  result = ::Dir.pwd

  return result unless RUBY_PLATFORM =~ /darwin/

  result.encode('UTF-8', 'UTF-8-MAC')
end

.eachString

Extract the text of a Rack response as a string. Useful for extensions implemented as Rack middleware.

Parameters:

  • response

    The response from #call

Returns:

  • (String)

    The whole response as a string.



127
# File 'lib/middleman-core/util.rb', line 127

Contract RespondTo[:each] => String

.extract_response_text(response) ⇒ Object



128
129
130
131
132
133
134
135
# File 'lib/middleman-core/util.rb', line 128

def extract_response_text(response)
  # The rack spec states all response bodies must respond to each
  result = ''
  response.each do |part, _|
    result << part
  end
  result
end

.file_contents_include_binary_bytes?(filename) ⇒ Boolean

Returns:

  • (Boolean)


387
388
389
390
391
392
393
394
395
# File 'lib/middleman-core/util.rb', line 387

def file_contents_include_binary_bytes?(filename)
  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]
  s = File.read(filename, 4096) || ''
  s.each_byte do |c|
    return true if binary_bytes.include?(c)
  end

  false
end


502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
# File 'lib/middleman-core/util.rb', line 502

def find_related_files(app, files)
  all_extensions = files.flat_map { |f| collect_extensions(f.to_s) }

  sass_type_aliasing = ['.scss', '.sass']
  erb_type_aliasing = ['.erb', '.haml', '.slim']

  if (all_extensions & sass_type_aliasing).length > 0
    all_extensions |= sass_type_aliasing
  end

  if (all_extensions & erb_type_aliasing).length > 0
    all_extensions |= erb_type_aliasing
  end

  all_extensions.uniq!

  app.sitemap.resources.select(&:file_descriptor).select { |r|
    local_extensions = collect_extensions(r.file_descriptor[:full_path].to_s)

    if (local_extensions & sass_type_aliasing).length > 0
      local_extensions |= sass_type_aliasing
    end

    if (local_extensions & erb_type_aliasing).length > 0
      local_extensions |= erb_type_aliasing
    end

    local_extensions.uniq!

    ((all_extensions & local_extensions).length > 0) && files.none? { |f| f == r.file_descriptor[:full_path] }
  }.map(&:file_descriptor)
end

.full_path(path, app) ⇒ Object



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/middleman-core/util.rb', line 317

def full_path(path, app)
  resource = app.sitemap.find_resource_by_destination_path(path)

  unless resource
    # Try it with /index.html at the end
    indexed_path = File.join(path.sub(%r{/$}, ''), app.config[:index_file])
    resource = app.sitemap.find_resource_by_destination_path(indexed_path)
  end

  if resource
    '/' + resource.destination_path
  else
    '/' + normalize_path(path)
  end
end

.glob_directory(path) ⇒ Array<String>

Glob a directory and try to keep path encoding consistent.

Parameters:

  • path (String)

    The glob path.

Returns:



401
402
403
404
405
406
407
# File 'lib/middleman-core/util.rb', line 401

def glob_directory(path)
  results = ::Dir[path]

  return results unless RUBY_PLATFORM =~ /darwin/

  results.map { |r| r.encode('UTF-8', 'UTF-8-MAC') }
end

.instrument(name, payload = {}, &block) ⇒ Object

Facade for ActiveSupport/Notification



118
119
120
121
# File 'lib/middleman-core/util.rb', line 118

def instrument(name, payload={}, &block)
  suffixed_name = (name =~ /\.middleman$/) ? name.dup : "#{name}.middleman"
  ::ActiveSupport::Notifications.instrument(suffixed_name, payload, &block)
end

.nonbinary_mime?(mime) ⇒ Boolean

Returns:

  • (Boolean)


367
368
369
370
371
372
373
374
375
376
377
378
379
380
# File 'lib/middleman-core/util.rb', line 367

def nonbinary_mime?(mime)
  case
  when mime.start_with?('text/')
    true
  when mime.include?('xml')
    true
  when mime.include?('json')
    true
  when mime.include?('javascript')
    true
  else
    false
  end
end

.normalize_path(path) ⇒ Object



105
106
107
108
# File 'lib/middleman-core/util.rb', line 105

def normalize_path(path)
  # The tr call works around a bug in Ruby's Unicode handling
  URI.decode(path).sub(%r{^/}, '').tr('', '')
end

.path_match(matcher, path) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/middleman-core/util.rb', line 62

def path_match(matcher, path)
  case
  when matcher.is_a?(String)
    if matcher.include? '*'
      File.fnmatch(matcher, path)
    else
      path == matcher
    end
  when matcher.respond_to?(:match)
    !matcher.match(path).nil?
  when matcher.respond_to?(:call)
    matcher.call(path)
  else
    File.fnmatch(matcher.to_s, path)
  end
end

.PATH_MATCHERBoolean

Takes a matcher, which can be a literal string or a string containing glob expressions, or a regexp, or a proc, or anything else that responds to #match or #call, and returns whether or not the given path matches that matcher.

Parameters:

  • matcher (String, #match, #call)

    A matcher String, RegExp, Proc, etc.

  • path (String)

    A path as a string

Returns:

  • (Boolean)

    Whether the path matches the matcher



61
# File 'lib/middleman-core/util.rb', line 61

Contract PATH_MATCHER, String => Bool

.path_to_source_file(path, directory, type, destination_dir) ⇒ Object



486
487
488
489
490
491
492
493
# File 'lib/middleman-core/util.rb', line 486

def path_to_source_file(path, directory, type, destination_dir)
  types = Set.new([type])

  relative_path = path.relative_path_from(directory)
  relative_path   = File.join(destination_dir, relative_path) if destination_dir

  ::Middleman::SourceFile.new(Pathname(relative_path), path, directory, types)
end

.PathnameMiddleman::SourceFile

Convert a path to a file resprentation.

Parameters:

Returns:



# File 'lib/middleman-core/util.rb', line 485

.recursively_enhance(obj) ⇒ Object



91
92
93
94
95
96
97
98
99
# File 'lib/middleman-core/util.rb', line 91

def recursively_enhance(obj)
  if obj.is_a? ::Array
    obj.map { |e| recursively_enhance(e) }
  elsif obj.is_a? ::Hash
    ::Hashie::Mash.new(obj)
  else
    obj
  end
end

.relative_path_from_resource(curr_resource, resource_url, relative) ⇒ Object



428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
# File 'lib/middleman-core/util.rb', line 428

def relative_path_from_resource(curr_resource, resource_url, relative)
  # Switch to the relative path between resource and the given resource
  # if we've been asked to.
  if relative
    # Output urls relative to the destination path, not the source path
    current_dir = Pathname('/' + curr_resource.destination_path).dirname
    relative_path = Pathname(resource_url).relative_path_from(current_dir).to_s

    # Put back the trailing slash to avoid unnecessary Apache redirects
    if resource_url.end_with?('/') && !relative_path.end_with?('/')
      relative_path << '/'
    end

    relative_path
  else
    resource_url
  end
end

.remove_templating_extensions(path) ⇒ Object



465
466
467
# File 'lib/middleman-core/util.rb', line 465

def remove_templating_extensions(path)
  step_through_extensions(path)
end

.rewrite_paths(body, _path, exts, &_block) ⇒ Object



334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
# File 'lib/middleman-core/util.rb', line 334

def rewrite_paths(body, _path, exts, &_block)
  matcher = /([=\'\"\(,]\s*)([^\s\'\"\)>]+(#{Regexp.union(exts)}))/

  url_fn_prefix = 'url('

  body.dup.gsub(matcher) do |match|
    opening_character = $1
    asset_path = $2

    if asset_path.start_with?(url_fn_prefix)
      opening_character << url_fn_prefix
      asset_path = asset_path[url_fn_prefix.length..-1]
    end

    begin
      uri = ::Addressable::URI.parse(asset_path)

      if uri.relative? && uri.host.nil? && (result = yield(asset_path))
        "#{opening_character}#{result}"
      else
        match
      end
    rescue ::Addressable::URI::InvalidURIError
      match
    end
  end
end

.step_through_extensions(path) {|File.extname(path)| ... } ⇒ Object

Yields:

  • (File.extname(path))


448
449
450
451
452
453
454
455
456
457
458
459
# File 'lib/middleman-core/util.rb', line 448

def step_through_extensions(path)
  while ::Tilt[path]
    yield File.extname(path) if block_given?

    # Strip templating extensions as long as Tilt knows them
    path = path.sub(/#{::Regexp.escape(File.extname(path))}$/, '')
  end

  yield File.extname(path) if block_given?

  path
end

.StringString

Expand a path to include the index file if it's a directory

Parameters:

Returns:

  • (String)

    Path with index file if necessary.



316
# File 'lib/middleman-core/util.rb', line 316

Contract String, IsA['Middleman::Application'] => String

.strip_leading_slash(path) ⇒ Object



113
114
115
# File 'lib/middleman-core/util.rb', line 113

def strip_leading_slash(path)
  path.sub(%r{^/}, '')
end

.url_for(app, path_or_resource, options = {}) ⇒ Object



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/middleman-core/util.rb', line 235

def url_for(app, path_or_resource, options={})
  # Handle Resources and other things which define their own url method
  url = if path_or_resource.respond_to?(:url)
    path_or_resource.url
  else
    path_or_resource.dup
  end

  # Try to parse URL
  begin
    uri = URI(url)
  rescue URI::InvalidURIError
    # Nothing we can do with it, it's not really a URI
    return url
  end

  relative = options[:relative]
  raise "Can't use the relative option with an external URL" if relative && uri.host

  # Allow people to turn on relative paths for all links with
  # set :relative_links, true
  # but still override on a case by case basis with the :relative parameter.
  effective_relative = relative || false
  effective_relative = true if relative.nil? && app.config[:relative_links]

  # Try to find a sitemap resource corresponding to the desired path
  this_resource = options[:current_resource]

  if path_or_resource.is_a?(::Middleman::Sitemap::Resource)
    resource = path_or_resource
    resource_url = url
  elsif this_resource && uri.path && !uri.host
    # Handle relative urls
    url_path = Pathname(uri.path)
    current_source_dir = Pathname('/' + this_resource.path).dirname
    url_path = current_source_dir.join(url_path) if url_path.relative?
    resource = app.sitemap.find_resource_by_path(url_path.to_s)
    if resource
      resource_url = resource.url
    else
      # Try to find a resource relative to destination paths
      url_path = Pathname(uri.path)
      current_source_dir = Pathname('/' + this_resource.destination_path).dirname
      url_path = current_source_dir.join(url_path) if url_path.relative?
      resource = app.sitemap.find_resource_by_destination_path(url_path.to_s)
      resource_url = resource.url if resource
    end
  elsif options[:find_resource] && uri.path && !uri.host
    resource = app.sitemap.find_resource_by_path(uri.path)
    resource_url = resource.url if resource
  end

  if resource
    uri.path = if this_resource
      URI.encode(relative_path_from_resource(this_resource, resource_url, effective_relative))
    else
      resource_url
    end
  else
    # If they explicitly asked for relative links but we can't find a resource...
    raise "No resource exists at #{url}" if relative
  end

  # Support a :query option that can be a string or hash
  if query = options[:query]
    uri.query = query.respond_to?(:to_param) ? query.to_param : query.to_s
  end

  # Support a :fragment or :anchor option just like Padrino
  fragment = options[:anchor] || options[:fragment]
  uri.fragment = fragment.to_s if fragment

  # Finally make the URL back into a string
  uri.to_s
end