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.
- checksums.yaml +4 -4
- data/features/asset_hash.feature +30 -32
- data/features/asset_host.feature +2 -0
- data/features/gzip.feature +1 -1
- data/features/import_files.feature +0 -2
- data/features/nested_layouts.feature +20 -17
- data/fixtures/asset-host-app/source/javascripts/asset_host.js +2 -0
- data/fixtures/frontmatter-neighbor-app/config.rb +1 -1
- data/fixtures/frontmatter-settings-neighbor-app/config.rb +1 -1
- data/fixtures/nested-layout-app/source/layouts/inner.erb +5 -2
- data/fixtures/nested-layout-app/source/layouts/inner_haml.haml +6 -2
- data/fixtures/nested-layout-app/source/layouts/inner_slim.slim +6 -2
- data/fixtures/nested-layout-app/source/layouts/master.erb +7 -1
- data/fixtures/nested-layout-app/source/layouts/master_haml.haml +5 -1
- data/fixtures/nested-layout-app/source/layouts/master_slim.slim +5 -1
- data/fixtures/nested-layout-app/source/layouts/outer.erb +6 -2
- data/fixtures/nested-layout-app/source/layouts/outer_haml.haml +5 -1
- data/fixtures/nested-layout-app/source/layouts/outer_slim.slim +5 -1
- data/lib/middleman-core.rb +0 -3
- data/lib/middleman-core/application.rb +7 -9
- data/lib/middleman-core/builder.rb +88 -44
- data/lib/middleman-core/contracts.rb +102 -13
- data/lib/middleman-core/core_extensions/data.rb +15 -10
- data/lib/middleman-core/core_extensions/default_helpers.rb +15 -6
- data/lib/middleman-core/core_extensions/file_watcher.rb +2 -2
- data/lib/middleman-core/core_extensions/front_matter.rb +11 -3
- data/lib/middleman-core/core_extensions/i18n.rb +1 -1
- data/lib/middleman-core/core_extensions/inline_url_rewriter.rb +2 -2
- data/lib/middleman-core/extension.rb +1 -1
- data/lib/middleman-core/extensions.rb +1 -1
- data/lib/middleman-core/extensions/asset_hash.rb +1 -1
- data/lib/middleman-core/extensions/asset_host.rb +1 -1
- data/lib/middleman-core/extensions/automatic_image_sizes.rb +1 -1
- data/lib/middleman-core/extensions/cache_buster.rb +1 -1
- data/lib/middleman-core/extensions/external_pipeline.rb +2 -1
- data/lib/middleman-core/extensions/gzip.rb +2 -2
- data/lib/middleman-core/extensions/minify_css.rb +1 -1
- data/lib/middleman-core/extensions/minify_javascript.rb +1 -1
- data/lib/middleman-core/extensions/relative_assets.rb +1 -1
- data/lib/middleman-core/file_renderer.rb +12 -9
- data/lib/middleman-core/logger.rb +1 -0
- data/lib/middleman-core/preview_server.rb +14 -14
- data/lib/middleman-core/renderers/haml.rb +3 -1
- data/lib/middleman-core/renderers/less.rb +1 -1
- data/lib/middleman-core/renderers/liquid.rb +1 -1
- data/lib/middleman-core/renderers/sass.rb +7 -2
- data/lib/middleman-core/sitemap/extensions/ignores.rb +2 -2
- data/lib/middleman-core/sitemap/extensions/import.rb +3 -1
- data/lib/middleman-core/sitemap/resource.rb +7 -6
- data/lib/middleman-core/sources.rb +30 -13
- data/lib/middleman-core/sources/source_watcher.rb +50 -12
- data/lib/middleman-core/step_definitions/middleman_steps.rb +2 -2
- data/lib/middleman-core/template_context.rb +1 -1
- data/lib/middleman-core/template_renderer.rb +13 -4
- data/lib/middleman-core/util.rb +6 -606
- data/lib/middleman-core/util/binary.rb +79 -0
- data/lib/middleman-core/util/data.rb +37 -8
- data/lib/middleman-core/util/files.rb +134 -0
- data/lib/middleman-core/util/paths.rb +251 -0
- data/lib/middleman-core/util/rack.rb +52 -0
- data/lib/middleman-core/util/uri_templates.rb +97 -0
- data/lib/middleman-core/version.rb +1 -1
- data/middleman-core.gemspec +1 -0
- 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.
|
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.
|
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
|
-
|
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 =
|
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
|
-
|
140
|
-
|
141
|
-
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
|
data/lib/middleman-core/util.rb
CHANGED
@@ -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
|
-
|
18
|
-
require '
|
19
|
-
|
20
|
-
|
21
|
-
require '
|
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
|