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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/features/asset_hash.feature +30 -32
  3. data/features/asset_host.feature +2 -0
  4. data/features/gzip.feature +1 -1
  5. data/features/import_files.feature +0 -2
  6. data/features/nested_layouts.feature +20 -17
  7. data/fixtures/asset-host-app/source/javascripts/asset_host.js +2 -0
  8. data/fixtures/frontmatter-neighbor-app/config.rb +1 -1
  9. data/fixtures/frontmatter-settings-neighbor-app/config.rb +1 -1
  10. data/fixtures/nested-layout-app/source/layouts/inner.erb +5 -2
  11. data/fixtures/nested-layout-app/source/layouts/inner_haml.haml +6 -2
  12. data/fixtures/nested-layout-app/source/layouts/inner_slim.slim +6 -2
  13. data/fixtures/nested-layout-app/source/layouts/master.erb +7 -1
  14. data/fixtures/nested-layout-app/source/layouts/master_haml.haml +5 -1
  15. data/fixtures/nested-layout-app/source/layouts/master_slim.slim +5 -1
  16. data/fixtures/nested-layout-app/source/layouts/outer.erb +6 -2
  17. data/fixtures/nested-layout-app/source/layouts/outer_haml.haml +5 -1
  18. data/fixtures/nested-layout-app/source/layouts/outer_slim.slim +5 -1
  19. data/lib/middleman-core.rb +0 -3
  20. data/lib/middleman-core/application.rb +7 -9
  21. data/lib/middleman-core/builder.rb +88 -44
  22. data/lib/middleman-core/contracts.rb +102 -13
  23. data/lib/middleman-core/core_extensions/data.rb +15 -10
  24. data/lib/middleman-core/core_extensions/default_helpers.rb +15 -6
  25. data/lib/middleman-core/core_extensions/file_watcher.rb +2 -2
  26. data/lib/middleman-core/core_extensions/front_matter.rb +11 -3
  27. data/lib/middleman-core/core_extensions/i18n.rb +1 -1
  28. data/lib/middleman-core/core_extensions/inline_url_rewriter.rb +2 -2
  29. data/lib/middleman-core/extension.rb +1 -1
  30. data/lib/middleman-core/extensions.rb +1 -1
  31. data/lib/middleman-core/extensions/asset_hash.rb +1 -1
  32. data/lib/middleman-core/extensions/asset_host.rb +1 -1
  33. data/lib/middleman-core/extensions/automatic_image_sizes.rb +1 -1
  34. data/lib/middleman-core/extensions/cache_buster.rb +1 -1
  35. data/lib/middleman-core/extensions/external_pipeline.rb +2 -1
  36. data/lib/middleman-core/extensions/gzip.rb +2 -2
  37. data/lib/middleman-core/extensions/minify_css.rb +1 -1
  38. data/lib/middleman-core/extensions/minify_javascript.rb +1 -1
  39. data/lib/middleman-core/extensions/relative_assets.rb +1 -1
  40. data/lib/middleman-core/file_renderer.rb +12 -9
  41. data/lib/middleman-core/logger.rb +1 -0
  42. data/lib/middleman-core/preview_server.rb +14 -14
  43. data/lib/middleman-core/renderers/haml.rb +3 -1
  44. data/lib/middleman-core/renderers/less.rb +1 -1
  45. data/lib/middleman-core/renderers/liquid.rb +1 -1
  46. data/lib/middleman-core/renderers/sass.rb +7 -2
  47. data/lib/middleman-core/sitemap/extensions/ignores.rb +2 -2
  48. data/lib/middleman-core/sitemap/extensions/import.rb +3 -1
  49. data/lib/middleman-core/sitemap/resource.rb +7 -6
  50. data/lib/middleman-core/sources.rb +30 -13
  51. data/lib/middleman-core/sources/source_watcher.rb +50 -12
  52. data/lib/middleman-core/step_definitions/middleman_steps.rb +2 -2
  53. data/lib/middleman-core/template_context.rb +1 -1
  54. data/lib/middleman-core/template_renderer.rb +13 -4
  55. data/lib/middleman-core/util.rb +6 -606
  56. data/lib/middleman-core/util/binary.rb +79 -0
  57. data/lib/middleman-core/util/data.rb +37 -8
  58. data/lib/middleman-core/util/files.rb +134 -0
  59. data/lib/middleman-core/util/paths.rb +251 -0
  60. data/lib/middleman-core/util/rack.rb +52 -0
  61. data/lib/middleman-core/util/uri_templates.rb +97 -0
  62. data/lib/middleman-core/version.rb +1 -1
  63. data/middleman-core.gemspec +1 -0
  64. metadata +25 -4
@@ -112,7 +112,7 @@ class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
112
112
 
113
113
  # Only compress script tags that contain JavaScript (as opposed to
114
114
  # something like jQuery templates, identified with a "text/html" type).
115
- if first.include?('<script>') || first.include?('text/javascript')
115
+ if !first.include?('type=') || first.include?('text/javascript')
116
116
  first + minify(inline_content) + last
117
117
  else
118
118
  match
@@ -3,7 +3,7 @@ require 'addressable/uri'
3
3
  # Relative Assets extension
4
4
  class Middleman::Extensions::RelativeAssets < ::Middleman::Extension
5
5
  option :exts, %w(.css .png .jpg .jpeg .webp .svg .svgz .js .gif .ttf .otf .woff .woff2 .eot), 'List of extensions that get cache busters strings appended to them.'
6
- option :sources, %w(.htm .html .css), 'List of extensions that are searched for relative assets.'
6
+ option :sources, %w(.css .htm .html .xhtml), 'List of extensions that are searched for relative assets.'
7
7
  option :ignore, [], 'Regexes of filenames to skip adding query strings to'
8
8
  option :rewrite_ignore, [], 'Regexes of filenames to skip processing for path rewrites'
9
9
 
@@ -51,7 +51,7 @@ module Middleman
51
51
 
52
52
  # Merge per-extension options from config
53
53
  extension = File.extname(path)
54
- options = opts.merge(options_for_ext(extension))
54
+ options = {}.merge!(opts).merge!(options_for_ext(extension))
55
55
  options[:outvar] ||= '@_out_buf'
56
56
  options[:context] = context
57
57
  options.delete(:layout)
@@ -73,9 +73,10 @@ module Middleman
73
73
  # end
74
74
 
75
75
  # Render using Tilt
76
- content = ::Middleman::Util.instrument 'render.tilt', path: path do
77
- template.render(context, locs, &block)
78
- end
76
+ # content = ::Middleman::Util.instrument 'render.tilt', path: path do
77
+ # template.render(context, locs, &block)
78
+ # end
79
+ content = template.render(context, locs, &block)
79
80
 
80
81
  # Allow hooks to manipulate the result after render
81
82
  content = @app.callbacks_for(:after_render).reduce(content) do |sum, callback|
@@ -96,12 +97,14 @@ module Middleman
96
97
  # @return [String]
97
98
  Contract String
98
99
  def template_data_for_file
99
- if @app.extensions[:front_matter]
100
- @app.extensions[:front_matter].template_data_for_file(@path) || File.read(@path)
101
- else
102
- file = @app.files.find(:source, @path)
103
- file.read if file
100
+ file = @app.files.find(:source, @path)
101
+
102
+ if @app.extensions[:front_matter] || (file && !file[:types].include?(:no_frontmatter))
103
+ result = @app.extensions[:front_matter].template_data_for_file(@path)
104
+ return result unless result.nil?
104
105
  end
106
+
107
+ file ? file.read : File.read(@path)
105
108
  end
106
109
 
107
110
  protected
@@ -40,6 +40,7 @@ module Middleman
40
40
  return if @instrumenting.is_a?(String) && @instrumenting != 'instrument' && !message.include?(@instrumenting)
41
41
 
42
42
  evt = ::ActiveSupport::Notifications::Event.new(message, *args)
43
+ return unless evt.duration > 30
43
44
  info "== Instrument (#{evt.name.sub(/.middleman$/, '')}): #{evt.duration}ms\n#{args.last}"
44
45
  end
45
46
  end
@@ -151,19 +151,19 @@ module Middleman
151
151
  config[:ssl_private_key] = opts[:ssl_private_key] if opts[:ssl_private_key]
152
152
 
153
153
  ready do
154
- match_against = [
155
- %r{^config\.rb$},
156
- %r{^environments/[^\.](.*)\.rb$},
157
- %r{^lib/[^\.](.*)\.rb$},
158
- %r{^#{config[:helpers_dir]}/[^\.](.*)\.rb$}
159
- ]
160
-
161
- # config.rb
162
- watcher = files.watch :reload,
163
- path: root,
164
- only: match_against
165
-
166
154
  unless config[:watcher_disable]
155
+ match_against = [
156
+ %r{^config\.rb$},
157
+ %r{^environments/[^\.](.*)\.rb$},
158
+ %r{^lib/[^\.](.*)\.rb$},
159
+ %r{^#{config[:helpers_dir]}/[^\.](.*)\.rb$}
160
+ ]
161
+
162
+ # config.rb
163
+ watcher = files.watch :reload,
164
+ path: root,
165
+ only: match_against
166
+
167
167
  # Hack around node_modules in root.
168
168
  watcher.listener.ignore(/^node_modules/)
169
169
 
@@ -235,8 +235,8 @@ module Middleman
235
235
 
236
236
  if ssl_certificate || ssl_private_key
237
237
  raise 'You must provide both :ssl_certificate and :ssl_private_key' unless ssl_private_key && ssl_certificate
238
- http_opts[:SSLCertificate] = OpenSSL::X509::Certificate.new File.read ssl_certificate
239
- http_opts[:SSLPrivateKey] = OpenSSL::PKey::RSA.new File.read ssl_private_key
238
+ http_opts[:SSLCertificate] = OpenSSL::X509::Certificate.new ::File.read ssl_certificate
239
+ http_opts[:SSLPrivateKey] = OpenSSL::PKey::RSA.new ::File.read ssl_private_key
240
240
  else
241
241
  # use a generated self-signed cert
242
242
  http_opts[:SSLCertName] = [
@@ -30,7 +30,7 @@ module Middleman
30
30
  end
31
31
 
32
32
  def evaluate(scope, locals, &block)
33
- options = @options.merge(filename: eval_file, line: line, context: @context || scope)
33
+ options = {}.merge!(@options).merge!(filename: eval_file, line: line, context: @context || scope)
34
34
  @engine = ::Haml::Engine.new(data, options)
35
35
  output = @engine.render(scope, locals, &block)
36
36
 
@@ -46,6 +46,7 @@ module Middleman
46
46
  ::Haml::Options.defaults[:context] = nil
47
47
  ::Haml::Options.send :attr_accessor, :context
48
48
 
49
+ # rubocop:disable NestedMethodDefinition
49
50
  [::Haml::Filters::Sass, ::Haml::Filters::Scss, ::Haml::Filters::Markdown].each do |f|
50
51
  f.class_exec do
51
52
  def self.render_with_options(text, compiler_options)
@@ -57,6 +58,7 @@ module Middleman
57
58
  end
58
59
  end
59
60
  end
61
+ # rubocop:enable NestedMethodDefinition
60
62
 
61
63
  ::Tilt.prefer(::Middleman::Renderers::HamlTemplate, :haml)
62
64
 
@@ -26,7 +26,7 @@ module Middleman
26
26
  if ::Less.const_defined? :Engine
27
27
  @engine = ::Less::Engine.new(data)
28
28
  else
29
- parser = ::Less::Parser.new(options.merge(filename: eval_file, line: line, paths: ['.', File.dirname(eval_file)]))
29
+ parser = ::Less::Parser.new({}.merge!(options).merge!(filename: eval_file, line: line, paths: ['.', File.dirname(eval_file)]))
30
30
  @engine = parser.parse(data)
31
31
  end
32
32
  end
@@ -14,7 +14,7 @@ module Middleman
14
14
  def read_template_file(template_path, _)
15
15
  file = app.files.find(:source, "_#{template_path}.liquid")
16
16
  raise ::Liquid::FileSystemError, "No such template '#{template_path}'" unless file
17
- File.read(file[:full_path])
17
+ file.read
18
18
  end
19
19
 
20
20
  # @return Array<Middleman::Sitemap::Resource>
@@ -1,5 +1,10 @@
1
1
  require 'sass'
2
2
 
3
+ begin
4
+ require 'sassc'
5
+ rescue LoadError
6
+ end
7
+
3
8
  module Middleman
4
9
  module Renderers
5
10
  # Sass renderer
@@ -76,7 +81,7 @@ module Middleman
76
81
  filename: eval_file,
77
82
  line: line,
78
83
  syntax: syntax,
79
- custom: (options[:custom] || {}).merge(
84
+ custom: {}.merge!(options[:custom] || {}).merge!(
80
85
  middleman_context: ctx.app,
81
86
  current_resource: ctx.current_resource
82
87
  )
@@ -92,7 +97,7 @@ module Middleman
92
97
  more_opts[:css_filename] = file.sub(/\.s[ac]ss$/, '')
93
98
  end
94
99
 
95
- options.merge(more_opts)
100
+ {}.merge!(options).merge!(more_opts)
96
101
  end
97
102
  end
98
103
 
@@ -11,7 +11,7 @@ module Middleman
11
11
  # Ignore a path or add an ignore callback
12
12
  # @param [String, Regexp] path Path glob expression, or path regex
13
13
  # @return [IgnoreDescriptor]
14
- Contract Maybe[Or[String, Regexp]], Maybe[Proc] => RespondTo[:execute_descriptor]
14
+ Contract Or[String, Regexp, Proc] => RespondTo[:execute_descriptor]
15
15
  def ignore(path=nil, &block)
16
16
  @app.sitemap.invalidate_resources_not_ignored_cache!
17
17
  IgnoreDescriptor.new(path, block)
@@ -49,7 +49,7 @@ module Middleman
49
49
  else
50
50
  match_path == path_clean
51
51
  end
52
- elsif block_given?
52
+ elsif block
53
53
  block.call(match_path)
54
54
  end
55
55
  end
@@ -12,8 +12,10 @@ module Middleman
12
12
 
13
13
  ImportFileDescriptor = Struct.new(:from, :to) do
14
14
  def execute_descriptor(app, resources)
15
+ source = ::Middleman::SourceFile.new(Pathname(from).relative_path_from(app.source_dir), Pathname(from), app.source_dir, Set.new([:source, :binary]), 0)
16
+
15
17
  resources + [
16
- ::Middleman::Sitemap::Resource.new(app.sitemap, to, from)
18
+ ::Middleman::Sitemap::Resource.new(app.sitemap, to, source)
17
19
  ]
18
20
  end
19
21
  end
@@ -54,7 +54,7 @@ module Middleman
54
54
  source = Pathname(source) if source && source.is_a?(String)
55
55
 
56
56
  @file_descriptor = if source && source.is_a?(Pathname)
57
- ::Middleman::SourceFile.new(source.relative_path_from(@app.source_dir), source, @app.source_dir, Set.new([:source]))
57
+ ::Middleman::SourceFile.new(source.relative_path_from(@app.source_dir), source, @app.source_dir, Set.new([:source]), 0)
58
58
  else
59
59
  source
60
60
  end
@@ -66,6 +66,8 @@ module Middleman
66
66
  # Page are data that is exposed through this resource's data member.
67
67
  # Note: It is named 'page' for backwards compatibility with older MM.
68
68
  @metadata = { options: {}, locals: {}, page: {} }
69
+
70
+ @page_data = nil
69
71
  end
70
72
 
71
73
  # Whether this resource has a template file
@@ -91,6 +93,7 @@ module Middleman
91
93
  # Note: It is named 'page' for backwards compatibility with older MM.
92
94
  Contract METADATA_HASH => METADATA_HASH
93
95
  def add_metadata(meta={})
96
+ @page_data = nil
94
97
  @metadata.deep_merge!(meta)
95
98
  end
96
99
 
@@ -98,7 +101,7 @@ module Middleman
98
101
  # @return [Hash]
99
102
  Contract RespondTo[:indifferent_access?]
100
103
  def data
101
- ::Middleman::Util.recursively_enhance(metadata[:page])
104
+ @page_data ||= ::Middleman::Util.recursively_enhance(metadata[:page])
102
105
  end
103
106
 
104
107
  # Options about how this resource is rendered, such as its :layout,
@@ -129,18 +132,16 @@ module Middleman
129
132
  def render(opts={}, locs={})
130
133
  return ::Middleman::FileRenderer.new(@app, file_descriptor[:full_path].to_s).template_data_for_file unless template?
131
134
 
132
- # ::Middleman::Util.instrument 'render.resource', path: file_descriptor[:full_path].to_s, destination_path: destination_path do
133
135
  md = metadata
134
136
  opts = md[:options].deep_merge(opts)
135
137
  locs = md[:locals].deep_merge(locs)
136
138
  locs[:current_path] ||= destination_path
137
139
 
138
140
  # Certain output file types don't use layouts
139
- opts[:layout] = false if !opts.key?(:layout) && ext != '.html'
141
+ opts[:layout] = false if !opts.key?(:layout) && !@app.config.extensions_with_layout.include?(ext)
140
142
 
141
143
  renderer = ::Middleman::TemplateRenderer.new(@app, file_descriptor[:full_path].to_s)
142
144
  renderer.render(locs, opts)
143
- # end
144
145
  end
145
146
 
146
147
  # A path without the directory index - so foo/index.html becomes
@@ -161,7 +162,7 @@ module Middleman
161
162
  # @return [Boolean]
162
163
  Contract Bool
163
164
  def binary?
164
- !file_descriptor.nil? && ::Middleman::Util.binary?(file_descriptor[:full_path].to_s)
165
+ !file_descriptor.nil? && (file_descriptor[:types].include?(:binary) || ::Middleman::Util.binary?(file_descriptor[:full_path].to_s))
165
166
  end
166
167
 
167
168
  # Ignore a resource directly, without going through the whole
@@ -3,7 +3,12 @@ require 'middleman-core/contracts'
3
3
 
4
4
  module Middleman
5
5
  # The standard "record" that contains information about a file on disk.
6
- SourceFile = Struct.new :relative_path, :full_path, :directory, :types
6
+ SourceFile = Struct.new(:relative_path, :full_path, :directory, :types, :version) do
7
+ def read
8
+ ::Middleman::Sources.file_cache[full_path] ||= {}
9
+ ::Middleman::Sources.file_cache[full_path][version] ||= ::File.read(full_path)
10
+ end
11
+ end
7
12
 
8
13
  # Sources handle multiple on-disk collections of files which make up
9
14
  # a Middleman project. They are separated by `type` which can then be
@@ -36,6 +41,8 @@ module Middleman
36
41
  # Reference to the global logger.
37
42
  def_delegator :@app, :logger
38
43
 
44
+ cattr_accessor :file_cache
45
+
39
46
  # Built-in types
40
47
  # :source, :data, :locales, :reload
41
48
 
@@ -50,6 +57,8 @@ module Middleman
50
57
  @watchers = watchers
51
58
  @sorted_watchers = @watchers.dup.freeze
52
59
 
60
+ ::Middleman::Sources.file_cache = {}
61
+
53
62
  @options = options
54
63
 
55
64
  # Set of procs wanting to be notified of changes
@@ -82,7 +91,7 @@ module Middleman
82
91
  validator: (block_given? ? block : regex))
83
92
 
84
93
  bump_count
85
- find_new_files! if @running
94
+ poll_once! if @running
86
95
  end
87
96
 
88
97
  # Whether this path is ignored.
@@ -175,11 +184,13 @@ module Middleman
175
184
  # @param [String] path The file path.
176
185
  # @param [Boolean] glob If the path contains wildcard or glob characters.
177
186
  # @return [Middleman::SourceFile, nil]
178
- Contract Or[Symbol, ArrayOf[Symbol], SetOf[Symbol]], String, Maybe[Bool] => Maybe[SourceFile]
187
+ Contract Or[Symbol, ArrayOf[Symbol], SetOf[Symbol]], Or[Pathname, String], Maybe[Bool] => Maybe[SourceFile]
179
188
  def find(types, path, glob=false)
189
+ array_of_types = Array(types)
190
+
180
191
  watchers
181
192
  .lazy
182
- .select { |d| Array(types).include?(d.type) }
193
+ .select { |d| array_of_types.include?(d.type) }
183
194
  .map { |d| d.find(path, glob) }
184
195
  .reject(&:nil?)
185
196
  .first
@@ -192,10 +203,7 @@ module Middleman
192
203
  # @return [Boolean]
193
204
  Contract Or[Symbol, ArrayOf[Symbol], SetOf[Symbol]], String => Bool
194
205
  def exists?(types, path)
195
- watchers
196
- .lazy
197
- .select { |d| Array(types).include?(d.type) }
198
- .any? { |d| d.exists?(path) }
206
+ watchers.any? { |d| Array(types).include?(d.type) && d.exists?(path) }
199
207
  end
200
208
 
201
209
  # Check if a file for a given type exists.
@@ -205,18 +213,27 @@ module Middleman
205
213
  # @return [Boolean]
206
214
  Contract Or[Symbol, ArrayOf[Symbol], SetOf[Symbol]], String => Maybe[HANDLER]
207
215
  def watcher_for_path(types, path)
208
- watchers
209
- .select { |d| Array(types).include?(d.type) }
210
- .find { |d| d.exists?(path) }
216
+ watchers.detect { |d| Array(types).include?(d.type) && d.exists?(path) }
211
217
  end
212
218
 
213
- # Manually poll all watchers for new content.
219
+ # Manually check for new files
214
220
  #
215
221
  # @return [void]
216
222
  Contract ArrayOf[Pathname]
217
223
  def find_new_files!
218
224
  return [] unless @update_count != @last_update_count
219
225
 
226
+ @last_update_count = @update_count
227
+ watchers.reduce([]) { |sum, w| sum + w.find_new_files! }
228
+ end
229
+
230
+ # Manually poll all watchers for new content.
231
+ #
232
+ # @return [void]
233
+ Contract ArrayOf[Pathname]
234
+ def poll_once!
235
+ return [] unless @update_count != @last_update_count
236
+
220
237
  @last_update_count = @update_count
221
238
  watchers.reduce([]) { |sum, w| sum + w.poll_once! }
222
239
  end
@@ -297,7 +314,7 @@ module Middleman
297
314
  def matches?(validator, file)
298
315
  path = file[:relative_path]
299
316
  if validator.is_a? Regexp
300
- !!validator.match(path.to_s)
317
+ !!(path.to_s =~ validator)
301
318
  else
302
319
  !!validator.call(path, @app)
303
320
  end
@@ -1,6 +1,7 @@
1
1
  # Watcher Library
2
2
  require 'listen'
3
3
  require 'middleman-core/contracts'
4
+ require 'digest'
4
5
 
5
6
  # Monkey patch Listen silencer so `only` works on directories too
6
7
  module Listen
@@ -48,6 +49,8 @@ module Middleman
48
49
  # Reference to lower level listener
49
50
  attr_reader :listener
50
51
 
52
+ IGNORED_DIRECTORIES = %w(.git node_modules .sass-cache).freeze
53
+
51
54
  # Construct a new SourceWatcher
52
55
  #
53
56
  # @param [Middleman::Sources] parent The parent collection.
@@ -65,6 +68,8 @@ module Middleman
65
68
  @files = {}
66
69
  @extensionless_files = {}
67
70
 
71
+ @frontmatter = options.fetch(:frontmatter, true)
72
+ @binary = options.fetch(:binary, false)
68
73
  @validator = options.fetch(:validator, proc { true })
69
74
  @ignored = options.fetch(:ignored, proc { false })
70
75
  @only = Array(options.fetch(:only, []))
@@ -177,22 +182,30 @@ module Middleman
177
182
  @listener = nil
178
183
  end
179
184
 
185
+ Contract ArrayOf[Pathname]
186
+ def find_new_files!
187
+ new_files = ::Middleman::Util.all_files_under(@directory.to_s, &method(:should_not_recurse?))
188
+ .reject { |p| @files.key?(p) }
189
+
190
+ update(new_files, []).flatten.map { |s| s[:full_path] }
191
+ end
192
+
180
193
  # Manually trigger update events.
181
194
  #
182
195
  # @return [void]
183
196
  Contract ArrayOf[Pathname]
184
197
  def poll_once!
185
- updated = ::Middleman::Util.all_files_under(@directory.to_s)
198
+ updated = ::Middleman::Util.all_files_under(@directory.to_s, &method(:should_not_recurse?))
186
199
  removed = @files.keys.reject { |p| updated.include?(p) }
187
200
 
188
- update(updated, removed)
201
+ result = update(updated, removed)
189
202
 
190
203
  if @waiting_for_existence && @directory.exist?
191
204
  @waiting_for_existence = false
192
205
  listen!
193
206
  end
194
207
 
195
- updated + removed
208
+ result.flatten.map { |s| s[:full_path] }
196
209
  end
197
210
 
198
211
  # Work around this bug: http://bugs.ruby-lang.org/issues/4521
@@ -206,6 +219,11 @@ module Middleman
206
219
 
207
220
  protected
208
221
 
222
+ Contract Pathname => Bool
223
+ def should_not_recurse?(p)
224
+ IGNORED_DIRECTORIES.include?(p.basename.to_s)
225
+ end
226
+
209
227
  # The `listen` gem callback.
210
228
  #
211
229
  # @param [Array] modified List of modified files.
@@ -225,14 +243,14 @@ module Middleman
225
243
  #
226
244
  # @param [String, Pathname] path The updated file path.
227
245
  # @return [void]
228
- Contract ArrayOf[Pathname], ArrayOf[Pathname] => Any
246
+ Contract ArrayOf[Pathname], ArrayOf[Pathname] => ArrayOf[ArrayOf[IsA['Middleman::SourceFile']]]
229
247
  def update(updated_paths, removed_paths)
230
248
  valid_updates = updated_paths
231
- .map { |p| ::Middleman::Util.path_to_source_file(p, @directory, @type, @options.fetch(:destination_dir, false)) }
249
+ .map { |p| @files[p] || path_to_source_file(p, @directory, @type, @options[:destination_dir]) }
232
250
  .select(&method(:valid?))
233
251
 
234
252
  valid_updates.each do |f|
235
- add_file_to_cache(f)
253
+ record_file_change(f)
236
254
  logger.debug "== Change (#{f[:types].inspect}): #{f[:relative_path]}"
237
255
  end
238
256
 
@@ -245,11 +263,9 @@ module Middleman
245
263
  valid_updates |= related_updates
246
264
 
247
265
  valid_removes = removed_paths
248
- .lazy
249
266
  .select(&@files.method(:key?))
250
267
  .map(&@files.method(:[]))
251
268
  .select(&method(:valid?))
252
- .to_a
253
269
  .each do |f|
254
270
  remove_file_from_cache(f)
255
271
  logger.debug "== Deletion (#{f[:types].inspect}): #{f[:relative_path]}"
@@ -260,12 +276,34 @@ module Middleman
260
276
  valid_removes,
261
277
  self
262
278
  ]) unless valid_updates.empty? && valid_removes.empty?
279
+
280
+ [valid_updates, valid_removes]
281
+ end
282
+
283
+ # Convert a path to a file resprentation.
284
+ #
285
+ # @param [Pathname] path The path.
286
+ # @return [Middleman::SourceFile]
287
+ Contract Pathname, Pathname, Symbol, Maybe[String] => ::Middleman::SourceFile
288
+ def path_to_source_file(path, directory, type, destination_dir)
289
+ types = Set.new([type])
290
+ types << :no_frontmatter unless @frontmatter
291
+ types << :binary if @binary
292
+
293
+ relative_path = path.relative_path_from(directory)
294
+ relative_path = File.join(destination_dir, relative_path) if destination_dir
295
+
296
+ ::Middleman::SourceFile.new(Pathname(relative_path), path, directory, types, 0)
263
297
  end
264
298
 
265
299
  Contract IsA['Middleman::SourceFile'] => Any
266
- def add_file_to_cache(f)
267
- @files[f[:full_path]] = f
268
- @extensionless_files[strip_extensions(f[:full_path])] = f
300
+ def record_file_change(f)
301
+ if @files[f[:full_path]]
302
+ @files[f[:full_path]][:version] += 1
303
+ else
304
+ @files[f[:full_path]] = f
305
+ @extensionless_files[strip_extensions(f[:full_path])] = f
306
+ end
269
307
  end
270
308
 
271
309
  Contract IsA['Middleman::SourceFile'] => Any
@@ -291,7 +329,7 @@ module Middleman
291
329
  if @only.empty?
292
330
  !@ignored.call(file)
293
331
  else
294
- @only.any? { |reg| reg.match(file[:relative_path].to_s) }
332
+ @only.any? { |reg| file[:relative_path].to_s =~ reg }
295
333
  end
296
334
  end
297
335
  end