Module: Middleman::Util

Includes:
Contracts
Defined in:
lib/middleman-core/util.rb,
lib/middleman-core/util/data.rb,
lib/middleman-core/util/rack.rb,
lib/middleman-core/util/files.rb,
lib/middleman-core/util/paths.rb,
lib/middleman-core/util/binary.rb,
lib/middleman-core/util/uri_templates.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



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File '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



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/middleman-core/util/paths.rb', line 38

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



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/middleman-core/util/paths.rb', line 69

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 = 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_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 = ::URI.encode(relative_path_from_resource(options[:current_resource], result, options[:relative]))

  result_uri = 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 '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



90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/middleman-core/util/files.rb', line 90

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 '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 '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 '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)


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

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


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
# File 'lib/middleman-core/util/files.rb', line 112

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



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/middleman-core/util/paths.rb', line 191

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 '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 '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
62
# File 'lib/middleman-core/util/binary.rb', line 49

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

.normalize_path(path) ⇒ Object



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

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



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

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)
    !!(path =~ matcher)
  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



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

Contract PATH_MATCHER, String => Bool

.recursively_enhance(obj) ⇒ Object



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

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



214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/middleman-core/util/paths.rb', line 214

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



82
83
84
# File 'lib/middleman-core/util/files.rb', line 82

def remove_templating_extensions(path)
  step_through_extensions(path)
end

.rewrite_paths(body, _path, exts, &_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
# File 'lib/middleman-core/util/rack.rb', line 24

def rewrite_paths(body, _path, exts, &_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

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

      if uri.relative? && uri.host.nil? && !(asset_path =~ /^[^\/].*[a-z]+\.[a-z]+\/.*/) && (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))


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

def step_through_extensions(path)
  while tilt_recognizes?(path)
    ext = ::File.extname(path)
    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.



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

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

.strip_leading_slash(path) ⇒ Object



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

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

.tilt_recognizes?(path) ⇒ Boolean

Returns:

  • (Boolean)


58
59
60
61
# File 'lib/middleman-core/util/files.rb', line 58

def tilt_recognizes?(path)
  @@tilt_lookup_cache ||= {}
  @@tilt_lookup_cache[path] ||= ::Tilt[path]
end

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



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
147
148
149
150
151
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
# File 'lib/middleman-core/util/paths.rb', line 107

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 if r
  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 = 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
  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