middleman-core 4.1.0.rc.2 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|