diff --git a/.rubocop.yml b/.rubocop.yml index 30e6b9ee..8346729e 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -61,3 +61,5 @@ Style/ParallelAssignment: Enabled: false Style/BlockDelimiters: Enabled: false +Style/MultilineBlockChain: + Enabled: false diff --git a/Gemfile b/Gemfile index 90e9fd5b..295d773e 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,7 @@ gem 'yard', '~> 0.8', require: false # Test tools gem 'pry', '~> 0.10', group: :development, require: false gem 'pry-byebug' +gem 'pry-stack_explorer' gem 'aruba', '~> 0.7.4', require: false gem 'rspec', '~> 3.0', require: false gem 'cucumber', '~> 2.0', require: false diff --git a/middleman-core/fixtures/related-files-app/config.rb b/middleman-core/fixtures/related-files-app/config.rb new file mode 100644 index 00000000..e69de29b diff --git a/middleman-core/fixtures/related-files-app/source/index.html.erb b/middleman-core/fixtures/related-files-app/source/index.html.erb new file mode 100644 index 00000000..e69de29b diff --git a/middleman-core/fixtures/related-files-app/source/partials/_test.erb b/middleman-core/fixtures/related-files-app/source/partials/_test.erb new file mode 100644 index 00000000..e69de29b diff --git a/middleman-core/fixtures/related-files-app/source/partials/_test2.haml b/middleman-core/fixtures/related-files-app/source/partials/_test2.haml new file mode 100644 index 00000000..e69de29b diff --git a/middleman-core/fixtures/related-files-app/source/stylesheets/_include3.sass b/middleman-core/fixtures/related-files-app/source/stylesheets/_include3.sass new file mode 100644 index 00000000..e69de29b diff --git a/middleman-core/fixtures/related-files-app/source/stylesheets/_include4.scss b/middleman-core/fixtures/related-files-app/source/stylesheets/_include4.scss new file mode 100644 index 00000000..e69de29b diff --git a/middleman-core/fixtures/related-files-app/source/stylesheets/include1.css b/middleman-core/fixtures/related-files-app/source/stylesheets/include1.css new file mode 100644 index 00000000..e69de29b diff --git a/middleman-core/fixtures/related-files-app/source/stylesheets/include2.css.scss b/middleman-core/fixtures/related-files-app/source/stylesheets/include2.css.scss new file mode 100644 index 00000000..e69de29b diff --git a/middleman-core/fixtures/related-files-app/source/stylesheets/site.css.scss b/middleman-core/fixtures/related-files-app/source/stylesheets/site.css.scss new file mode 100644 index 00000000..e69de29b diff --git a/middleman-core/lib/middleman-core/sitemap/store.rb b/middleman-core/lib/middleman-core/sitemap/store.rb index 2d1e052d..7a0a8736 100644 --- a/middleman-core/lib/middleman-core/sitemap/store.rb +++ b/middleman-core/lib/middleman-core/sitemap/store.rb @@ -180,7 +180,7 @@ module Middleman Contract String => String def extensionless_path(file) path = file.dup - remove_templating_extensions(path) + ::Middleman::Util.remove_templating_extensions(path) end # Actually update the resource list, assuming anything has called @@ -224,16 +224,6 @@ module Middleman end end - # Removes the templating extensions, while keeping the others - # @param [String] path - # @return [String] - Contract String => String - def remove_templating_extensions(path) - # Strip templating extensions as long as Tilt knows them - path = path.sub(/#{::Regexp.escape(File.extname(path))}$/, '') while ::Tilt[path] - path - end - # Remove the locale token from the end of the path # @param [String] path # @return [String] diff --git a/middleman-core/lib/middleman-core/sources/source_watcher.rb b/middleman-core/lib/middleman-core/sources/source_watcher.rb index ff18c6f9..85234816 100644 --- a/middleman-core/lib/middleman-core/sources/source_watcher.rb +++ b/middleman-core/lib/middleman-core/sources/source_watcher.rb @@ -225,15 +225,23 @@ module Middleman # @return [void] Contract ArrayOf[Pathname], ArrayOf[Pathname] => Any def update(updated_paths, removed_paths) - valid_updates = (updated_paths | find_related_files(updated_paths + removed_paths)) - .lazy - .map(&method(:path_to_source_file)) + valid_updates = updated_paths + .map { |p| ::Middleman::Util.path_to_source_file(p, @directory, @type) } .select(&method(:valid?)) - .to_a - .each do |f| - add_file_to_cache(f) - logger.debug "== Change (#{f[:types].inspect}): #{f[:relative_path]}" - end + + valid_updates.each do |f| + add_file_to_cache(f) + logger.debug "== Change (#{f[:types].inspect}): #{f[:relative_path]}" + end + + related_updates = ::Middleman::Util.find_related_files(app, (updated_paths + removed_paths)).select(&method(:valid?)) + + related_updates.each do |f| + add_file_to_cache(f) + logger.debug "== Possible Change (#{f[:types].inspect}): #{f[:relative_path]}" + end + + valid_updates |= related_updates valid_removes = removed_paths .lazy @@ -283,35 +291,5 @@ module Middleman @only.any? { |reg| reg.match(file[:relative_path].to_s) } end end - - # Convert a path to a file resprentation. - # - # @param [Pathname] path The path. - # @return [Middleman::SourceFile] - Contract Pathname => IsA['Middleman::SourceFile'] - def path_to_source_file(path) - types = Set.new([@type]) - - relative_path = path.relative_path_from(@directory) - destination_dir = @options.fetch(:destination_dir, false) - relative_path = File.join(destination_dir, relative_path) if destination_dir - - ::Middleman::SourceFile.new(Pathname(relative_path), path, @directory, types) - end - - # Finds files which should also be considered to be dirty when - # the given file(s) are touched. - # - # @param [Pathname] files The original touched file paths. - # @return [Pathname] All related file paths, not including the source file paths. - Contract ArrayOf[Pathname] => ArrayOf[Pathname] - def find_related_files(files) - files.flat_map do |file| - # If any partial file changes, reload all non-partials - if File.basename(file).start_with?('_') - Dir[File.join(@directory, app.config[:source], "**/[^_]*.#{File.extname(file)}")].map { |p| Pathname(p) } - end - end.compact.uniq - files - end end end diff --git a/middleman-core/lib/middleman-core/util.rb b/middleman-core/lib/middleman-core/util.rb index 139bca08..15964b17 100644 --- a/middleman-core/lib/middleman-core/util.rb +++ b/middleman-core/lib/middleman-core/util.rb @@ -430,6 +430,95 @@ module Middleman end end + Contract String => String + def step_through_extensions(path) + while ::Tilt[path] + yield File.extname(path) if block_given? + + # Strip templating extensions as long as Tilt knows them + path = path.sub(/#{::Regexp.escape(File.extname(path))}$/, '') + end + + yield File.extname(path) if block_given? + + path + end + + # Removes the templating extensions, while keeping the others + # @param [String] path + # @return [String] + Contract String => String + def remove_templating_extensions(path) + step_through_extensions(path) + end + + # Removes the templating extensions, while keeping the others + # @param [String] path + # @return [String] + Contract String => ArrayOf[String] + def collect_extensions(path) + result = [] + + step_through_extensions(path) { |e| result << e } + + result + end + + # Convert a path to a file resprentation. + # + # @param [Pathname] path The path. + # @return [Middleman::SourceFile] + Contract Pathname, Pathname, Symbol => IsA['Middleman::SourceFile'] + def path_to_source_file(path, directory, type) + types = Set.new([type]) + + relative_path = path.relative_path_from(directory) + # destination_dir = @options.fetch(:destination_dir, false) + # relative_path = File.join(destination_dir, relative_path) if destination_dir + + ::Middleman::SourceFile.new(Pathname(relative_path), path, directory, types) + end + + # Finds files which should also be considered to be dirty when + # the given file(s) are touched. + # + # @param [Middleman::Application] app The app. + # @param [Pathname] files The original touched file paths. + # @return [Middleman::SourceFile] All related file paths, not including the source file paths. + Contract IsA['Middleman::Application'], ArrayOf[Pathname] => ArrayOf[IsA['Middleman::SourceFile']] + def find_related_files(app, files) + all_extensions = files.flat_map { |f| collect_extensions(f.to_s) } + + sass_type_aliasing = ['.scss', '.sass'] + erb_type_aliasing = ['.erb', '.haml', '.slim'] + + if (all_extensions & sass_type_aliasing).length > 0 + all_extensions |= sass_type_aliasing + end + + if (all_extensions & erb_type_aliasing).length > 0 + all_extensions |= erb_type_aliasing + end + + all_extensions.uniq! + + app.sitemap.resources.select { |r| + local_extensions = collect_extensions(r.file_descriptor[:relative_path].to_s) + + if (local_extensions & sass_type_aliasing).length > 0 + local_extensions |= sass_type_aliasing + end + + if (local_extensions & erb_type_aliasing).length > 0 + local_extensions |= erb_type_aliasing + end + + local_extensions.uniq! + + ((all_extensions & local_extensions).length > 0) && files.none? { |f| f == r.file_descriptor[:full_path] } + }.map(&:file_descriptor) + end + # Handy methods for dealing with URI templates. Mix into whatever class. module UriTemplates module_function diff --git a/middleman-core/spec/middleman-core/util_spec.rb b/middleman-core/spec/middleman-core/util_spec.rb index f50d7555..ccac1885 100644 --- a/middleman-core/spec/middleman-core/util_spec.rb +++ b/middleman-core/spec/middleman-core/util_spec.rb @@ -141,4 +141,44 @@ describe Middleman::Util do end + describe "::find_related_files" do + after(:each) do + Given.cleanup! + end + + before(:each) do + Given.fixture 'related-files-app' + @mm = Middleman::Application.new + end + + def source_file(path) + Pathname(File.expand_path("source/#{path}")) + end + + it "Finds partials possibly related to ERb files" do + related = Middleman::Util.find_related_files(@mm, [source_file('partials/_test.erb')]).map { |f| f[:full_path].to_s } + expect(related).to include File.expand_path("source/index.html.erb") + + related = Middleman::Util.find_related_files(@mm, [source_file('partials/_test2.haml')]).map { |f| f[:full_path].to_s } + expect(related).to include File.expand_path("source/index.html.erb") + end + + it "Finds partials possible related to Scss files" do + related = Middleman::Util.find_related_files(@mm, [source_file('stylesheets/_include4.scss')]).map { |f| f[:full_path].to_s } + expect(related).to include File.expand_path("source/stylesheets/site.css.scss") + expect(related).to include File.expand_path("source/stylesheets/include2.css.scss") + + related = Middleman::Util.find_related_files(@mm, [source_file('stylesheets/include2.css.scss')]).map { |f| f[:full_path].to_s } + expect(related).to include File.expand_path("source/stylesheets/site.css.scss") + expect(related).not_to include File.expand_path("source/stylesheets/include2.css.scss") + + related = Middleman::Util.find_related_files(@mm, [source_file('stylesheets/include1.css')]).map { |f| f[:full_path].to_s } + expect(related).to include File.expand_path("source/stylesheets/site.css.scss") + expect(related).to include File.expand_path("source/stylesheets/include2.css.scss") + + related = Middleman::Util.find_related_files(@mm, [source_file('stylesheets/_include3.sass')]).map { |f| f[:full_path].to_s } + expect(related).to include File.expand_path("source/stylesheets/site.css.scss") + expect(related).to include File.expand_path("source/stylesheets/include2.css.scss") + end + end end