Module: Middleman::Util

Extended by:
Memoist
Includes:
Contracts
Defined in:
middleman-core/lib/middleman-core/util.rb,
middleman-core/lib/middleman-core/util/data.rb,
middleman-core/lib/middleman-core/util/rack.rb,
middleman-core/lib/middleman-core/util/files.rb,
middleman-core/lib/middleman-core/util/paths.rb,
middleman-core/lib/middleman-core/util/binary.rb,
middleman-core/lib/middleman-core/util/uri_templates.rb

Defined Under Namespace

Modules: Data, UriTemplates Classes: BlogTemplateProcessor, EnhancedHash

Constant Summary

IGNORE_DESCRIPTOR =
Or[Regexp, RespondTo[:call], String]

Constants included from Contracts

Contracts::PATH_MATCHER, Contracts::ResourceList, Contracts::VectorOf

Class Method Summary collapse

Methods included from Contracts

#Contract

Class Method Details

.all_files_under(path, &ignore) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'middleman-core/lib/middleman-core/util/files.rb', line 17

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

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

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



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'middleman-core/lib/middleman-core/util/paths.rb', line 75

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



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'middleman-core/lib/middleman-core/util/paths.rb', line 106

def asset_url(app, path, prefix='', options={})
  # Don't touch assets which already have a full path
  return path if path.include?('//') || path.start_with?('data:')

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

  uri = ::Middleman::Util.parse_uri(path)
  path = uri.path

  # Ensure the url we pass into find_resource_by_destination_path is not a
  # relative path, since it only takes absolute url paths.
  dest_path = url_for(app, path, options.merge(relative: false))

  result = if resource = app.sitemap.find_resource_by_path(dest_path)
    resource.url
  elsif resource = app.sitemap.find_resource_by_destination_path(dest_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

  final_result = ::Addressable::URI.encode(
    relative_path_from_resource(
      options[:current_resource],
      result,
      options[:relative]
    )
  )

  result_uri = ::Middleman::Util.parse_uri(final_result)
  result_uri.query = uri.query
  result_uri.fragment = uri.fragment
  result_uri.to_s
end

.binary?(filename) ⇒ Boolean

Returns:

  • (Boolean)


18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'middleman-core/lib/middleman-core/util/binary.rb', line 18

def binary?(filename)
  @binary_cache ||= {}

  return @binary_cache[filename] if @binary_cache.key?(filename)

  @binary_cache[filename] = begin
    path = Pathname(filename)
    ext = path.extname

    # We hardcode detecting of gzipped SVG files
    if ext == '.svgz'
      true
    elsif ::Tilt.registered?(ext.sub('.', ''))
      false
    else
      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
  end
end

.collect_extensions(path) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'middleman-core/lib/middleman-core/util/files.rb', line 86

def collect_extensions(path)
  @@extensions_cache ||= {}

  base_name = ::File.basename(path)
  @@extensions_cache[base_name] ||= begin
    result = []

    unless base_name.start_with?('.')
      step_through_extensions(base_name) { |e| result << e }
    end

    result
  end
end

.current_directoryArray<String>

Get the PWD and try to keep path encoding consistent.

Parameters:

  • path (String)

    The glob path.

Returns:



49
50
51
52
53
54
55
# File 'middleman-core/lib/middleman-core/util/files.rb', line 49

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.



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

Contract RespondTo[:each] => String

.extract_response_text(response) ⇒ Object



14
15
16
17
18
19
20
21
# File 'middleman-core/lib/middleman-core/util/rack.rb', line 14

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)


68
69
70
71
72
73
74
75
76
# File 'middleman-core/lib/middleman-core/util/binary.rb', line 68

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


108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'middleman-core/lib/middleman-core/util/files.rb', line 108

def find_related_files(app, files)
  return [] if files.empty?

  file_set = ::Set.new(files)

  all_extensions = files.flat_map { |f| collect_extensions(f.to_s) }
  sass_type_aliasing = ['.scss', '.sass']
  erb_type_aliasing = ['.erb', '.haml', '.slim']

  all_extensions |= sass_type_aliasing unless (all_extensions & sass_type_aliasing).empty?
  all_extensions |= erb_type_aliasing unless (all_extensions & erb_type_aliasing).empty?

  all_extensions.uniq!

  app.sitemap.resources.select { |r|
    if r.file_descriptor
      local_extensions = collect_extensions(r.file_descriptor[:full_path].to_s)
      local_extensions |= sass_type_aliasing unless (local_extensions & sass_type_aliasing).empty?
      local_extensions |= erb_type_aliasing unless (local_extensions & erb_type_aliasing).empty?

      local_extensions.uniq!

      !(all_extensions & local_extensions).empty? && !file_set.include?(r.file_descriptor[:full_path])
    else
      false
    end
  }.map(&:file_descriptor)
end

.full_path(path, app) ⇒ Object



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'middleman-core/lib/middleman-core/util/paths.rb', line 243

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:



37
38
39
40
41
42
43
# File 'middleman-core/lib/middleman-core/util/files.rb', line 37

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



19
20
21
22
# File 'middleman-core/lib/middleman-core/util.rb', line 19

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)


49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'middleman-core/lib/middleman-core/util/binary.rb', line 49

def nonbinary_mime?(mime)
  if mime.start_with?('text/')
    true
  elsif mime.include?('xml') && !mime.include?('officedocument')
    true
  elsif mime.include?('json')
    true
  elsif mime.include?('javascript')
    true
  else
    false
  end
end

.normalize_path(path) ⇒ Object



34
35
36
37
# File 'middleman-core/lib/middleman-core/util/paths.rb', line 34

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

.parse_uri(uri) ⇒ Object



19
20
21
# File 'middleman-core/lib/middleman-core/util/paths.rb', line 19

def parse_uri(uri)
  ::Addressable::URI.parse(uri)
end

.path_match(matcher, path) ⇒ Object



295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'middleman-core/lib/middleman-core/util/paths.rb', line 295

def path_match(matcher, path)
  if matcher.is_a?(String)
    if matcher.include? '*'
      ::File.fnmatch(matcher, path)
    else
      path == matcher
    end
  elsif matcher.respond_to?(:match)
    !!(path =~ matcher)
  elsif 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



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

Contract PATH_MATCHER, String => Bool

.recursively_enhance(obj) ⇒ Object



29
30
31
32
33
34
35
36
37
# File 'middleman-core/lib/middleman-core/util/data.rb', line 29

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

.relative_path_from_resource(curr_resource, resource_url, relative) ⇒ Object



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'middleman-core/lib/middleman-core/util/paths.rb', line 266

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



78
79
80
# File 'middleman-core/lib/middleman-core/util/files.rb', line 78

def remove_templating_extensions(path)
  step_through_extensions(path)
end

.rewrite_paths(body, path, exts, app, &_block) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'middleman-core/lib/middleman-core/util/rack.rb', line 24

def rewrite_paths(body, path, exts, app, &_block)
  matcher = /([\'\"\(,]\s*|# sourceMappingURL=)([^\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

    current_resource = app.sitemap.find_resource_by_destination_path(path)

    begin
      uri = ::Middleman::Util.parse_uri(asset_path)

      if uri.relative? && uri.host.nil? && !(asset_path =~ /^[^\/].*[a-z]+\.[a-z]+\/.*/)
        dest_path = ::Middleman::Util.url_for(app, asset_path, relative: false, current_resource: current_resource)

        resource = app.sitemap.find_resource_by_destination_path(dest_path)

        if resource && (result = yield(asset_path))
          "#{opening_character}#{result}"
        else
          match
        end
      else
        match
      end
    rescue ::Addressable::URI::InvalidURIError
      match
    end
  end
end

.should_ignore?(validator, value) ⇒ Boolean

Returns:

  • (Boolean)


50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'middleman-core/lib/middleman-core/util/paths.rb', line 50

def should_ignore?(validator, value)
  if validator.is_a? Regexp
    # Treat as Regexp
    !!(value =~ validator)
  elsif validator.respond_to? :call
    # Treat as proc
    validator.call(value)
  elsif validator.is_a? String
    # Treat as glob
    File.fnmatch(value, validator)
  else
    # If some unknown thing, don't ignore
    false
  end
end

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

Yields:

  • (::File.extname(path))


58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'middleman-core/lib/middleman-core/util/files.rb', line 58

def step_through_extensions(path)
  while ::Middleman::Util.tilt_class(path)
    ext = ::File.extname(path)
    break if ext.empty?

    yield ext if block_given?

    # Strip templating extensions as long as Tilt knows them
    path = path[0..-(ext.length + 1)]
  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.



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

Contract String, ::Middleman::Application => String

.strip_leading_slash(path) ⇒ Object



43
44
45
# File 'middleman-core/lib/middleman-core/util/paths.rb', line 43

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

.tilt_class(path) ⇒ Object



25
26
27
# File 'middleman-core/lib/middleman-core/util/paths.rb', line 25

def tilt_class(path)
  ::Tilt[path]
end

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



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
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
230
231
232
233
234
235
# File 'middleman-core/lib/middleman-core/util/paths.rb', line 152

def url_for(app, path_or_resource, options={})
  if path_or_resource.is_a?(String) || path_or_resource.is_a?(Symbol)
    r = app.sitemap.find_resource_by_page_id(path_or_resource)

    path_or_resource = r ? r : path_or_resource.to_s
  end

  # 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 = ::Middleman::Util.parse_uri(url)
  rescue ::Addressable::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
      ::Addressable::URI.encode(
        relative_path_from_resource(
          this_resource,
          resource_url,
          effective_relative
        )
      )
    else
      resource_url
    end
  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