[DRE-commits] [nanoc] 17/21: New upstream version 4.4.7

Cédric Boutillier boutil at moszumanska.debian.org
Thu Jan 5 14:04:57 UTC 2017


This is an automated email from the git hooks/post-receive script.

boutil pushed a commit to branch master
in repository nanoc.

commit a3b192782948722f70ecea9d451d7033cd1dac95
Author: Cédric Boutillier <boutil at debian.org>
Date:   Thu Jan 5 14:16:51 2017 +0100

    New upstream version 4.4.7
---
 Gemfile                                            |   1 -
 Gemfile.lock                                       |  24 +-
 LICENSE                                            |   2 +-
 NEWS.md                                            |  11 +
 README.md                                          |   2 +-
 lib/nanoc.rb                                       |   2 +-
 lib/nanoc/base/repos.rb                            |   1 +
 lib/nanoc/base/repos/outdatedness_store.rb         |  49 +++
 lib/nanoc/base/services/compiler.rb                | 438 ++++++++++++++-------
 lib/nanoc/base/services/compiler_loader.rb         |   4 +
 lib/nanoc/base/services/outdatedness_checker.rb    |   8 +-
 lib/nanoc/checking/checks/external_links.rb        |   2 -
 lib/nanoc/data_sources/filesystem.rb               |  17 +-
 lib/nanoc/version.rb                               |   2 +-
 spec/nanoc/base/compiler_spec.rb                   |  68 +++-
 spec/nanoc/base/repos/outdatedness_store_spec.rb   | 100 +++++
 .../base/services/outdatedness_checker_spec.rb     |  21 +-
 .../nanoc/base/services/outdatedness_rules_spec.rb |  16 +-
 spec/nanoc/cli/commands/view_spec.rb               |   2 +
 spec/nanoc/data_sources/filesystem_spec.rb         |  14 +-
 spec/nanoc/extra/parallel_collection_spec.rb       |   4 +-
 .../integration/outdatedness_integration_spec.rb   |   8 +
 .../integration/partial_recompilation_spec.rb      |  48 +++
 spec/nanoc/regressions/gh_1045_spec.rb             |  48 +++
 spec/nanoc/regressions/gh_1047_spec.rb             |  28 ++
 test/base/test_compiler.rb                         |   1 +
 26 files changed, 712 insertions(+), 209 deletions(-)

diff --git a/Gemfile b/Gemfile
index 2c9a378..fbe4745 100644
--- a/Gemfile
+++ b/Gemfile
@@ -47,7 +47,6 @@ group :plugins do
   gem 'mustache', '~> 1.0'
   gem 'nokogiri', '~> 1.6'
   gem 'pandoc-ruby'
-  gem 'parallel'
   gem 'pygments.rb', github: 'tmm1/pygments.rb', platforms: [:ruby, :mswin]
   gem 'rack'
   gem 'rainpress'
diff --git a/Gemfile.lock b/Gemfile.lock
index f6fcbe7..d2f0bf9 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,6 +1,6 @@
 GIT
   remote: git://github.com/bbatsov/rubocop.git
-  revision: 186b16e10514eaf304990d614184b980abc0ad2b
+  revision: 7c95534e1906539e53341cd53265fadfc0aba0e2
   specs:
     rubocop (0.46.0)
       parser (>= 2.3.3.1, < 3.0)
@@ -11,23 +11,23 @@ GIT
 
 GIT
   remote: git://github.com/cowboyd/therubyracer.git
-  revision: 2f1127bed45f03b91f3c92469be26399d3c1bcd8
+  revision: a635aee79b2057c396f8af92417cadf93415c275
   specs:
-    therubyracer (0.12.2)
+    therubyracer (0.12.3)
       libv8 (~> 3.16.14.15)
       ref
 
 GIT
   remote: git://github.com/tmm1/pygments.rb.git
-  revision: a988d127d3270d21685d2ac2e586060bf43709f3
+  revision: cf50cbeda48e8a242ef9cba9467e092935730f66
   specs:
-    pygments.rb (1.1.0)
+    pygments.rb (1.1.1)
       multi_json (>= 1.0.0)
 
 PATH
   remote: .
   specs:
-    nanoc (4.4.6)
+    nanoc (4.4.7)
       cri (~> 2.3)
       hamster (~> 3.0)
       ref (~> 2.0)
@@ -204,7 +204,7 @@ GEM
     fog-voxel (0.1.0)
       fog-core
       fog-xml
-    fog-vsphere (1.5.2)
+    fog-vsphere (1.6.0)
       fog-core
       rbvmomi (~> 1.9)
     fog-xenserver (0.2.3)
@@ -268,13 +268,12 @@ GEM
     multi_json (1.12.1)
     mustache (1.0.3)
     nenv (0.3.0)
-    nokogiri (1.7.0)
+    nokogiri (1.7.0.1)
       mini_portile2 (~> 2.1.0)
     notiffany (0.1.1)
       nenv (~> 0.1)
       shellany (~> 0.0)
     pandoc-ruby (2.0.1)
-    parallel (1.10.0)
     parser (2.3.3.1)
       ast (~> 2.2)
     powerpack (0.1.1)
@@ -282,9 +281,9 @@ GEM
       coderay (~> 1.1.0)
       method_source (~> 0.8.1)
       slop (~> 3.4)
-    public_suffix (2.0.4)
+    public_suffix (2.0.5)
     rack (2.0.1)
-    rainbow (2.2.0)
+    rainbow (2.2.1)
     rainpress (1.0)
     rake (12.0.0)
     rb-fsevent (0.9.8)
@@ -348,7 +347,7 @@ GEM
     w3c_validators (1.3.1)
       json (~> 2.0)
       nokogiri (~> 1.6)
-    webmock (2.3.1)
+    webmock (2.3.2)
       addressable (>= 2.3.6)
       crack (>= 0.3.2)
       hashdiff
@@ -394,7 +393,6 @@ DEPENDENCIES
   nanoc!
   nokogiri (~> 1.6)
   pandoc-ruby
-  parallel
   pry
   pygments.rb!
   rack
diff --git a/LICENSE b/LICENSE
index d7eb287..0462f21 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2007-2016 Denis Defreyne and contributors
+Copyright (c) 2007-2017 Denis Defreyne and contributors
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/NEWS.md b/NEWS.md
index a608770..227761e 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,5 +1,16 @@
 # Nanoc news
 
+## 4.4.7 (2017-01-05)
+
+Fixes:
+
+* Fixed issue that caused an item not to be considered outdated when only the mtime has changed (#1046)
+* Removed stray `require 'parallel'` which could break the `external_links` check (#1051) [Cédric Boutillier]
+
+Enhancements:
+
+* Made Nanoc not recompile compiled items after an exception occurs (#1044)
+
 ## 4.4.6 (2016-12-28)
 
 Fixes:
diff --git a/README.md b/README.md
index 4440185..00af0a1 100644
--- a/README.md
+++ b/README.md
@@ -19,4 +19,4 @@ Contributions are greatly appreciated! Consult the [Development guidelines](http
 
 Many thanks to everyone who has contributed to Nanoc in one way or another:
 
-Ale Muñoz, Alexander Mankuta, Andy Drop, Arnau Siches, Ben Armston, Bil Bas, Brian Candler, Bruno Dufour, Chris Chapman, Chris Eppstein, Christian Plessl, Colin Barrett, Colin Seymour, Croath Liu, Damien Pollet, Dan Callahan, Daniel Hofstetter, Daniel Mendler, Daniel Wollschlaeger, David Alexander, David Everitt, Denis Defreyne, Dennis Sutch, Devon Luke Buchanan, Dmitry Bilunov, Eric Sunshine, Erik Hollensbe, Fabian Buch, Felix Hanley, Garen Torikian, Go Maeda, Grégory Karékinian, Gregor [...]
+Ale Muñoz, Alexander Mankuta, Andy Drop, Arnau Siches, Ben Armston, Bil Bas, Brian Candler, Bruno Dufour, Cédric Boutillier, Chris Chapman, Chris Eppstein, Christian Plessl, Colin Barrett, Colin Seymour, Croath Liu, Damien Pollet, Dan Callahan, Daniel Hofstetter, Daniel Mendler, Daniel Wollschlaeger, David Alexander, David Everitt, Denis Defreyne, Dennis Sutch, Devon Luke Buchanan, Dmitry Bilunov, Eric Sunshine, Erik Hollensbe, Fabian Buch, Felix Hanley, Garen Torikian, Go Maeda, Grégory [...]
diff --git a/lib/nanoc.rb b/lib/nanoc.rb
index accd1cb..562eaa2 100644
--- a/lib/nanoc.rb
+++ b/lib/nanoc.rb
@@ -7,7 +7,7 @@ module Nanoc
     gem_info = defined?(Gem) ? "with RubyGems #{Gem::VERSION}" : 'without RubyGems'
     engine   = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'
     res = ''
-    res << "Nanoc #{Nanoc::VERSION} © 2007-2016 Denis Defreyne.\n"
+    res << "Nanoc #{Nanoc::VERSION} © 2007-2017 Denis Defreyne.\n"
     res << "Running #{engine} #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) on #{RUBY_PLATFORM} #{gem_info}.\n"
     res
   end
diff --git a/lib/nanoc/base/repos.rb b/lib/nanoc/base/repos.rb
index 8d1b299..d8a0779 100644
--- a/lib/nanoc/base/repos.rb
+++ b/lib/nanoc/base/repos.rb
@@ -6,5 +6,6 @@ require_relative 'repos/config_loader'
 require_relative 'repos/data_source'
 require_relative 'repos/dependency_store'
 require_relative 'repos/item_rep_repo'
+require_relative 'repos/outdatedness_store'
 require_relative 'repos/rule_memory_store'
 require_relative 'repos/site_loader'
diff --git a/lib/nanoc/base/repos/outdatedness_store.rb b/lib/nanoc/base/repos/outdatedness_store.rb
new file mode 100644
index 0000000..4e00dc4
--- /dev/null
+++ b/lib/nanoc/base/repos/outdatedness_store.rb
@@ -0,0 +1,49 @@
+module Nanoc::Int
+  # @api private
+  class OutdatednessStore < ::Nanoc::Int::Store
+    include Nanoc::Int::ContractsSupport
+
+    contract C::KeywordArgs[site: C::Maybe[Nanoc::Int::Site], reps: Nanoc::Int::ItemRepRepo] => C::Any
+    def initialize(site: nil, reps:)
+      super(Nanoc::Int::Store.tmp_path_for(env_name: (site.config.env_name if site), store_name: 'outdatedness'), 1)
+
+      @outdated_reps = Set.new
+      @all_reps = reps
+    end
+
+    contract Nanoc::Int::ItemRep => C::Bool
+    def include?(obj)
+      @outdated_reps.include?(obj)
+    end
+
+    contract Nanoc::Int::ItemRep => self
+    def add(obj)
+      @outdated_reps << obj
+      self
+    end
+
+    contract Nanoc::Int::ItemRep => self
+    def remove(obj)
+      @outdated_reps.delete(obj)
+      self
+    end
+
+    contract C::None => C::ArrayOf[Nanoc::Int::ItemRep]
+    def to_a
+      @outdated_reps.to_a
+    end
+
+    protected
+
+    def data
+      @outdated_reps.map(&:reference)
+    end
+
+    def data=(new_data)
+      outdated_refs = Set.new(new_data)
+      all_reps = Set.new(@all_reps)
+
+      @outdated_reps = Set.new(all_reps.select { |rep| outdated_refs.include?(rep.reference) })
+    end
+  end
+end
diff --git a/lib/nanoc/base/services/compiler.rb b/lib/nanoc/base/services/compiler.rb
index c5df878..e2ebcc0 100644
--- a/lib/nanoc/base/services/compiler.rb
+++ b/lib/nanoc/base/services/compiler.rb
@@ -73,128 +73,272 @@ module Nanoc::Int
       end
     end
 
-    # Provides functionality for (re)calculating the content of an item rep, without caching or
-    # outdatedness checking.
-    class RecalculatePhase
-      include Nanoc::Int::ContractsSupport
+    # All phases for the compilation of a single item rep. Phases will be repeated for every rep.
+    module Phases
+      # Provides functionality for (re)calculating the content of an item rep, without caching or
+      # outdatedness checking.
+      class Recalculate
+        include Nanoc::Int::ContractsSupport
+
+        def initialize(action_provider:, dependency_store:, compilation_context:)
+          @action_provider = action_provider
+          @dependency_store = dependency_store
+          @compilation_context = compilation_context
+        end
 
-      def initialize(action_provider:, dependency_store:, compilation_context:)
-        @action_provider = action_provider
-        @dependency_store = dependency_store
-        @compilation_context = compilation_context
+        contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => C::Any
+        def run(rep, is_outdated:) # rubocop:disable Lint/UnusedMethodArgument
+          dependency_tracker = Nanoc::Int::DependencyTracker.new(@dependency_store)
+          dependency_tracker.enter(rep.item)
+
+          executor = Nanoc::Int::Executor.new(rep, @compilation_context, dependency_tracker)
+
+          @action_provider.memory_for(rep).each do |action|
+            case action
+            when Nanoc::Int::ProcessingActions::Filter
+              executor.filter(action.filter_name, action.params)
+            when Nanoc::Int::ProcessingActions::Layout
+              executor.layout(action.layout_identifier, action.params)
+            when Nanoc::Int::ProcessingActions::Snapshot
+              executor.snapshot(action.snapshot_name)
+            else
+              raise Nanoc::Int::Errors::InternalInconsistency, "unknown action #{action.inspect}"
+            end
+          end
+        ensure
+          dependency_tracker.exit
+        end
       end
 
-      contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => C::Any
-      def run(rep, is_outdated:) # rubocop:disable Lint/UnusedMethodArgument
-        dependency_tracker = Nanoc::Int::DependencyTracker.new(@dependency_store)
-        dependency_tracker.enter(rep.item)
-
-        executor = Nanoc::Int::Executor.new(rep, @compilation_context, dependency_tracker)
-
-        @action_provider.memory_for(rep).each do |action|
-          case action
-          when Nanoc::Int::ProcessingActions::Filter
-            executor.filter(action.filter_name, action.params)
-          when Nanoc::Int::ProcessingActions::Layout
-            executor.layout(action.layout_identifier, action.params)
-          when Nanoc::Int::ProcessingActions::Snapshot
-            executor.snapshot(action.snapshot_name)
+      # Provides functionality for (re)calculating the content of an item rep, with caching or
+      # outdatedness checking. Delegates to s::Recalculate if outdated or no cache available.
+      class Cache
+        include Nanoc::Int::ContractsSupport
+
+        def initialize(compiled_content_cache:, wrapped:)
+          @compiled_content_cache = compiled_content_cache
+          @wrapped = wrapped
+        end
+
+        contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => C::Any
+        def run(rep, is_outdated:)
+          if can_reuse_content_for_rep?(rep, is_outdated: is_outdated)
+            Nanoc::Int::NotificationCenter.post(:cached_content_used, rep)
+            rep.snapshot_contents = @compiled_content_cache[rep]
           else
-            raise Nanoc::Int::Errors::InternalInconsistency, "unknown action #{action.inspect}"
+            @wrapped.run(rep, is_outdated: is_outdated)
           end
+
+          rep.compiled = true
+          @compiled_content_cache[rep] = rep.snapshot_contents
+        end
+
+        contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => C::Bool
+        def can_reuse_content_for_rep?(rep, is_outdated:)
+          !is_outdated && !@compiled_content_cache[rep].nil?
         end
-      ensure
-        dependency_tracker.exit
       end
-    end
 
-    # Provides functionality for (re)calculating the content of an item rep, with caching or
-    # outdatedness checking. Delegates to RecalculatePhase if outdated or no cache available.
-    class CachePhase
-      include Nanoc::Int::ContractsSupport
+      # Provides functionality for suspending and resuming item rep compilation (using fibers).
+      class Resume
+        include Nanoc::Int::ContractsSupport
 
-      def initialize(compiled_content_cache:, wrapped:)
-        @compiled_content_cache = compiled_content_cache
-        @wrapped = wrapped
+        def initialize(wrapped:)
+          @wrapped = wrapped
+        end
+
+        contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => C::Any
+        def run(rep, is_outdated:)
+          fiber = fiber_for(rep, is_outdated: is_outdated)
+          while fiber.alive?
+            Nanoc::Int::NotificationCenter.post(:compilation_started, rep)
+            res = fiber.resume
+
+            case res
+            when Nanoc::Int::Errors::UnmetDependency
+              Nanoc::Int::NotificationCenter.post(:compilation_suspended, rep, res)
+              raise(res)
+            when Proc
+              fiber.resume(res.call)
+            else
+              # TODO: raise
+            end
+          end
+
+          Nanoc::Int::NotificationCenter.post(:compilation_ended, rep)
+        end
+
+        private
+
+        contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => Fiber
+        def fiber_for(rep, is_outdated:)
+          @fibers ||= {}
+
+          @fibers[rep] ||=
+            Fiber.new do
+              @wrapped.run(rep, is_outdated: is_outdated)
+              @fibers.delete(rep)
+            end
+
+          @fibers[rep]
+        end
       end
 
-      contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => C::Any
-      def run(rep, is_outdated:)
-        if can_reuse_content_for_rep?(rep, is_outdated: is_outdated)
-          Nanoc::Int::NotificationCenter.post(:cached_content_used, rep)
-          rep.snapshot_contents = @compiled_content_cache[rep]
-        else
-          @wrapped.run(rep, is_outdated: is_outdated)
+      class Write
+        include Nanoc::Int::ContractsSupport
+
+        def initialize(wrapped:)
+          @wrapped = wrapped
         end
 
-        rep.compiled = true
-        @compiled_content_cache[rep] = rep.snapshot_contents
+        contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => C::Any
+        def run(rep, is_outdated:)
+          @wrapped.run(rep, is_outdated: is_outdated)
+
+          rep.snapshot_defs.each do |sdef|
+            ItemRepWriter.new.write(rep, sdef.name)
+          end
+        end
       end
 
-      contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => C::Bool
-      def can_reuse_content_for_rep?(rep, is_outdated:)
-        !is_outdated && !@compiled_content_cache[rep].nil?
+      class MarkDone
+        include Nanoc::Int::ContractsSupport
+
+        def initialize(wrapped:, outdatedness_store:)
+          @wrapped = wrapped
+          @outdatedness_store = outdatedness_store
+        end
+
+        contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => C::Any
+        def run(rep, is_outdated:)
+          @wrapped.run(rep, is_outdated: is_outdated)
+          @outdatedness_store.remove(rep)
+        end
       end
     end
 
-    # Provides functionality for suspending and resuming item rep compilation (using fibers).
-    class ResumePhase
-      include Nanoc::Int::ContractsSupport
+    module Stages
+      class Preprocess
+        def initialize(action_provider:, site:, dependency_store:, checksum_store:)
+          @action_provider = action_provider
+          @site = site
+          @dependency_store = dependency_store
+          @checksum_store = checksum_store
+        end
+
+        def run
+          @action_provider.preprocess(@site)
 
-      def initialize(wrapped:)
-        @wrapped = wrapped
+          @dependency_store.objects = @site.items.to_a + @site.layouts.to_a
+          @checksum_store.objects = @site.items.to_a + @site.layouts.to_a + @site.code_snippets + [@site.config]
+
+          @site.freeze
+        end
       end
 
-      contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => C::Any
-      def run(rep, is_outdated:)
-        fiber = fiber_for(rep, is_outdated: is_outdated)
-        while fiber.alive?
-          Nanoc::Int::NotificationCenter.post(:compilation_started, rep)
-          res = fiber.resume
-
-          case res
-          when Nanoc::Int::Errors::UnmetDependency
-            Nanoc::Int::NotificationCenter.post(:compilation_suspended, rep, res)
-            raise(res)
-          when Proc
-            fiber.resume(res.call)
-          else
-            # TODO: raise
+      class Prune
+        def initialize(config:, reps:)
+          @config = config
+          @reps = reps
+        end
+
+        def run
+          if @config[:prune][:auto_prune]
+            Nanoc::Pruner.new(@config, @reps, exclude: prune_config_exclude).run
           end
         end
 
-        Nanoc::Int::NotificationCenter.post(:compilation_ended, rep)
-      end
+        private
 
-      private
+        def prune_config
+          @config[:prune] || {}
+        end
 
-      contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => Fiber
-      def fiber_for(rep, is_outdated:)
-        @fibers ||= {}
+        def prune_config_exclude
+          prune_config[:exclude] || {}
+        end
+      end
 
-        @fibers[rep] ||=
-          Fiber.new do
-            @wrapped.run(rep, is_outdated: is_outdated)
-            @fibers.delete(rep)
+      class DetermineOutdatedness
+        def initialize(reps:, outdatedness_checker:, outdatedness_store:)
+          @reps = reps
+          @outdatedness_checker = outdatedness_checker
+          @outdatedness_store = outdatedness_store
+        end
+
+        def run
+          outdated_reps_tmp = @reps.select do |r|
+            @outdatedness_store.include?(r) || @outdatedness_checker.outdated?(r)
           end
 
-        @fibers[rep]
-      end
-    end
+          outdated_items = outdated_reps_tmp.map(&:item).uniq
+          outdated_reps = Set.new(outdated_items.flat_map { |i| @reps[i] })
 
-    class WritePhase
-      include Nanoc::Int::ContractsSupport
+          outdated_reps.each { |r| @outdatedness_store.add(r) }
 
-      def initialize(wrapped:)
-        @wrapped = wrapped
+          yield(outdated_items)
+        end
       end
 
-      contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => C::Any
-      def run(rep, is_outdated:)
-        @wrapped.run(rep, is_outdated: is_outdated)
+      class CompileReps
+        def initialize(outdatedness_store:, dependency_store:, action_provider:, compilation_context:, compiled_content_cache:)
+          @outdatedness_store = outdatedness_store
+          @dependency_store = dependency_store
+          @action_provider = action_provider
+          @compilation_context = compilation_context
+          @compiled_content_cache = compiled_content_cache
+        end
+
+        def run
+          selector = Nanoc::Int::ItemRepSelector.new(@outdatedness_store.to_a)
+          selector.each do |rep|
+            handle_errors_while(rep) { compile_rep(rep, is_outdated: @outdatedness_store.include?(rep)) }
+          end
+        ensure
+          @outdatedness_store.store
+          @compiled_content_cache.store
+        end
+
+        private
+
+        def handle_errors_while(item_rep)
+          yield
+        rescue => e
+          raise Nanoc::Int::Errors::CompilationError.new(e, item_rep)
+        end
+
+        def compile_rep(rep, is_outdated:)
+          item_rep_compiler.run(rep, is_outdated: is_outdated)
+        end
 
-        rep.snapshot_defs.each do |sdef|
-          ItemRepWriter.new.write(rep, sdef.name)
+        def item_rep_compiler
+          @_item_rep_compiler ||= begin
+            recalculate_phase = Phases::Recalculate.new(
+              action_provider: @action_provider,
+              dependency_store: @dependency_store,
+              compilation_context: @compilation_context,
+            )
+
+            cache_phase = Phases::Cache.new(
+              compiled_content_cache: @compiled_content_cache,
+              wrapped: recalculate_phase,
+            )
+
+            resume_phase = Phases::Resume.new(
+              wrapped: cache_phase,
+            )
+
+            write_phase = Phases::Write.new(
+              wrapped: resume_phase,
+            )
+
+            mark_done_phase = Phases::MarkDone.new(
+              wrapped: write_phase,
+              outdatedness_store: @outdatedness_store,
+            )
+
+            mark_done_phase
+          end
         end
       end
     end
@@ -225,7 +369,10 @@ module Nanoc::Int
     # @api private
     attr_reader :reps
 
-    def initialize(site, compiled_content_cache:, checksum_store:, rule_memory_store:, action_provider:, dependency_store:, outdatedness_checker:, reps:)
+    # @api private
+    attr_reader :outdatedness_store
+
+    def initialize(site, compiled_content_cache:, checksum_store:, rule_memory_store:, action_provider:, dependency_store:, outdatedness_checker:, reps:, outdatedness_store:)
       @site = site
 
       @compiled_content_cache = compiled_content_cache
@@ -235,22 +382,20 @@ module Nanoc::Int
       @outdatedness_checker   = outdatedness_checker
       @reps                   = reps
       @action_provider        = action_provider
+      @outdatedness_store     = outdatedness_store
     end
 
     def run_all
-      @action_provider.preprocess(@site)
+      preprocess_stage.run
       build_reps
-      prune
-      run
-      @action_provider.postprocess(@site, @reps)
-    end
-
-    def run
+      prune_stage.run
       load_stores
-      @site.freeze
-
-      compile_reps
+      determine_outdatedness
+      forget_dependencies_if_needed
       store
+      compile_reps
+      store_output_state
+      @action_provider.postprocess(@site, @reps)
     ensure
       Nanoc::Int::TempFilenameFactory.instance.cleanup(
         Nanoc::Filter::TMP_BINARY_ITEMS_DIR,
@@ -261,17 +406,10 @@ module Nanoc::Int
     end
 
     def load_stores
-      # FIXME: icky hack to update the dependency/checksum store’s list of objects
-      # (does not include preprocessed objects otherwise)
-      dependency_store.objects = site.items.to_a + site.layouts.to_a
-      checksum_store.objects = site.items.to_a + site.layouts.to_a + site.code_snippets + [site.config]
-
       stores.each(&:load)
     end
 
-    # Store the modified helper data used for compiling the site.
-    #
-    # @return [void]
+    # TODO: rename to store_preprocessed_state
     def store
       # Calculate rule memory
       (@reps.to_a + @site.layouts.to_a).each do |obj|
@@ -284,7 +422,12 @@ module Nanoc::Int
       objects_to_checksum.each { |obj| checksum_store.add(obj) }
 
       # Store
-      stores.each(&:store)
+      checksum_store.store
+      rule_memory_store.store
+    end
+
+    def store_output_state
+      @dependency_store.store
     end
 
     def build_reps
@@ -305,62 +448,52 @@ module Nanoc::Int
 
     private
 
-    def prune
-      if site.config[:prune][:auto_prune]
-        Nanoc::Pruner.new(site.config, reps, exclude: prune_config_exclude).run
-      end
+    def preprocess_stage
+      @_preprocess_stage ||= Stages::Preprocess.new(
+        action_provider: action_provider,
+        site: site,
+        dependency_store: dependency_store,
+        checksum_store: checksum_store,
+      )
     end
 
-    def prune_config
-      site.config[:prune] || {}
+    def prune_stage
+      @_prune_stage ||= Stages::Prune.new(
+        config: site.config,
+        reps: reps,
+      )
     end
 
-    def prune_config_exclude
-      prune_config[:exclude] || {}
+    def determine_outdatedness_stage
+      @_determine_outdatedness_stage ||= Stages::DetermineOutdatedness.new(
+        reps: reps,
+        outdatedness_checker: outdatedness_checker,
+        outdatedness_store: outdatedness_store,
+      )
     end
 
-    def compile_reps
-      outdated_items = @reps.select { |r| outdatedness_checker.outdated?(r) }.map(&:item).uniq
-      outdated_items.each { |i| @dependency_store.forget_dependencies_for(i) }
-
-      reps_to_recompile = Set.new(outdated_items.flat_map { |i| @reps[i] })
-      selector = Nanoc::Int::ItemRepSelector.new(reps_to_recompile)
-      selector.each do |rep|
-        handle_errors_while(rep) { compile_rep(rep, is_outdated: reps_to_recompile.include?(rep)) }
-      end
+    def compile_reps_stage
+      @_compile_reps_stage ||= Stages::CompileReps.new(
+        outdatedness_store: @outdatedness_store,
+        dependency_store: @dependency_store,
+        action_provider: action_provider,
+        compilation_context: compilation_context,
+        compiled_content_cache: compiled_content_cache,
+      )
     end
 
-    def handle_errors_while(item_rep)
-      yield
-    rescue => e
-      raise Nanoc::Int::Errors::CompilationError.new(e, item_rep)
+    def determine_outdatedness
+      determine_outdatedness_stage.run do |outdated_items|
+        @outdated_items = outdated_items
+      end
     end
 
-    def compile_rep(rep, is_outdated:)
-      item_rep_compiler.run(rep, is_outdated: is_outdated)
+    def forget_dependencies_if_needed
+      @outdated_items.each { |i| @dependency_store.forget_dependencies_for(i) }
     end
 
-    def item_rep_compiler
-      @_item_rep_compiler ||= begin
-        recalculate_phase = RecalculatePhase.new(
-          action_provider: action_provider,
-          dependency_store: @dependency_store,
-          compilation_context: compilation_context,
-        )
-
-        cache_phase = CachePhase.new(
-          compiled_content_cache: compiled_content_cache,
-          wrapped: recalculate_phase,
-        )
-
-        resume_phase = ResumePhase.new(
-          wrapped: cache_phase,
-        )
-
-        WritePhase.new(
-          wrapped: resume_phase,
-        )
-      end
+    def compile_reps
+      compile_reps_stage.run
     end
 
     # Returns all stores that can load/store data that can be used for
@@ -371,6 +504,7 @@ module Nanoc::Int
         compiled_content_cache,
         @dependency_store,
         rule_memory_store,
+        @outdatedness_store,
       ]
     end
   end
diff --git a/lib/nanoc/base/services/compiler_loader.rb b/lib/nanoc/base/services/compiler_loader.rb
index 77fde3a..b4d3141 100644
--- a/lib/nanoc/base/services/compiler_loader.rb
+++ b/lib/nanoc/base/services/compiler_loader.rb
@@ -16,6 +16,9 @@ module Nanoc::Int
 
       action_provider ||= Nanoc::Int::ActionProvider.named(:rule_dsl).for(site)
 
+      outdatedness_store =
+        Nanoc::Int::OutdatednessStore.new(site: site, reps: item_rep_repo)
+
       outdatedness_checker =
         Nanoc::Int::OutdatednessChecker.new(
           site: site,
@@ -40,6 +43,7 @@ module Nanoc::Int
         outdatedness_checker: outdatedness_checker,
         reps: item_rep_repo,
         action_provider: action_provider,
+        outdatedness_store: outdatedness_store,
       }
 
       Nanoc::Int::Compiler.new(site, params)
diff --git a/lib/nanoc/base/services/outdatedness_checker.rb b/lib/nanoc/base/services/outdatedness_checker.rb
index eb8ad19..2b06aea 100644
--- a/lib/nanoc/base/services/outdatedness_checker.rb
+++ b/lib/nanoc/base/services/outdatedness_checker.rb
@@ -82,12 +82,8 @@ module Nanoc::Int
 
     Reasons = Nanoc::Int::OutdatednessReasons
 
-    # @param [Nanoc::Int::Site] site
-    # @param [Nanoc::Int::ChecksumStore] checksum_store
-    # @param [Nanoc::Int::DependencyStore] dependency_store
-    # @param [Nanoc::Int::RuleMemoryStore] rule_memory_store
-    # @param [Nanoc::Int::ActionProvider] action_provider
-    # @param [Nanoc::Int::ItemRepRepo] reps
+    # FIXME: Replace C::Any with proper types
+    contract C::KeywordArgs[site: Nanoc::Int::Site, checksum_store: Nanoc::Int::ChecksumStore, dependency_store: Nanoc::Int::DependencyStore, rule_memory_store: Nanoc::Int::RuleMemoryStore, action_provider: C::Any, reps: Nanoc::Int::ItemRepRepo] => C::Any
     def initialize(site:, checksum_store:, dependency_store:, rule_memory_store:, action_provider:, reps:)
       @site = site
       @checksum_store = checksum_store
diff --git a/lib/nanoc/checking/checks/external_links.rb b/lib/nanoc/checking/checks/external_links.rb
index e8d92e7..6a0fc99 100644
--- a/lib/nanoc/checking/checks/external_links.rb
+++ b/lib/nanoc/checking/checks/external_links.rb
@@ -12,8 +12,6 @@ module ::Nanoc::Checking::Checks
     identifiers :external_links, :elinks
 
     def run
-      require 'parallel'
-
       # Find all broken external hrefs
       # TODO: de-duplicate this (duplicated in internal links check)
       filenames = output_filenames.select { |f| File.extname(f) == '.html' && !excluded_file?(f) }
diff --git a/lib/nanoc/data_sources/filesystem.rb b/lib/nanoc/data_sources/filesystem.rb
index 5fdce86..40f6cd1 100644
--- a/lib/nanoc/data_sources/filesystem.rb
+++ b/lib/nanoc/data_sources/filesystem.rb
@@ -163,7 +163,7 @@ module Nanoc::DataSources
             attributes,
             identifier,
             content_checksum_data: proto_doc.content_checksum_data,
-            attributes_checksum_data: proto_doc.attributes_checksum_data,
+            attributes_checksum_data: attributes_checksum_data_for(proto_doc, content_filename, meta_filename),
           )
         end
       end
@@ -171,16 +171,25 @@ module Nanoc::DataSources
       res
     end
 
-    def attributes_for(proto_doc, content_filename, meta_filename)
-      extra_attributes = {
+    def attributes_checksum_data_for(proto_doc, content_filename, meta_filename)
+      YAML.dump(
+        attributes: proto_doc.attributes_checksum_data,
+        extra_attributes: extra_attributes_for(content_filename, meta_filename),
+      )
+    end
+
+    def extra_attributes_for(content_filename, meta_filename)
+      {
         filename: content_filename,
         content_filename: content_filename,
         meta_filename: meta_filename,
         extension: content_filename ? ext_of(content_filename)[1..-1] : nil,
         mtime: mtime_of(content_filename, meta_filename),
       }
+    end
 
-      extra_attributes.merge(proto_doc.attributes)
+    def attributes_for(proto_doc, content_filename, meta_filename)
+      extra_attributes_for(content_filename, meta_filename).merge(proto_doc.attributes)
     end
 
     def identifier_for(content_filename, meta_filename, dir_name)
diff --git a/lib/nanoc/version.rb b/lib/nanoc/version.rb
index f850667..1507c31 100644
--- a/lib/nanoc/version.rb
+++ b/lib/nanoc/version.rb
@@ -1,4 +1,4 @@
 module Nanoc
   # The current Nanoc version.
-  VERSION = '4.4.6'.freeze
+  VERSION = '4.4.7'.freeze
 end
diff --git a/spec/nanoc/base/compiler_spec.rb b/spec/nanoc/base/compiler_spec.rb
index abfff16..5e0c54f 100644
--- a/spec/nanoc/base/compiler_spec.rb
+++ b/spec/nanoc/base/compiler_spec.rb
@@ -9,16 +9,18 @@ describe Nanoc::Int::Compiler do
       dependency_store: dependency_store,
       outdatedness_checker: outdatedness_checker,
       reps: reps,
+      outdatedness_store: outdatedness_store,
     )
   end
 
-  let(:checksum_store)         { :__irrelevant_checksum_store }
-  let(:rule_memory_store)      { :__irrelevant_rule_memory_store }
+  let(:checksum_store)    { Nanoc::Int::ChecksumStore.new(objects: items) }
+  let(:rule_memory_store) { Nanoc::Int::RuleMemoryStore.new }
 
   let(:dependency_store) { Nanoc::Int::DependencyStore.new(items.to_a) }
   let(:reps) { Nanoc::Int::ItemRepRepo.new }
 
   let(:outdatedness_checker) { double(:outdatedness_checker) }
+  let(:outdatedness_store) { Nanoc::Int::OutdatednessStore.new(site: site, reps: reps) }
   let(:action_provider) { double(:action_provider) }
 
   let(:compiled_content_cache) { Nanoc::Int::CompiledContentCache.new(items: items) }
@@ -50,10 +52,13 @@ describe Nanoc::Int::Compiler do
   end
 
   let(:memory) do
-    [
-      Nanoc::Int::ProcessingActions::Filter.new(:erb, {}),
-      Nanoc::Int::ProcessingActions::Snapshot.new(:last, nil),
-    ]
+    actions =
+      [
+        Nanoc::Int::ProcessingActions::Filter.new(:erb, {}),
+        Nanoc::Int::ProcessingActions::Snapshot.new(:last, nil),
+      ]
+
+    Nanoc::Int::RuleMemory.new(nil, actions: actions)
   end
 
   before do
@@ -72,7 +77,10 @@ describe Nanoc::Int::Compiler do
   end
 
   describe '#compile_reps' do
-    subject { compiler.send(:compile_reps) }
+    subject do
+      compiler.send(:determine_outdatedness)
+      compiler.send(:compile_reps)
+    end
 
     before do
       allow(action_provider).to receive(:snapshots_defs_for).with(rep).and_return(snapshot_defs_for_rep)
@@ -93,6 +101,21 @@ describe Nanoc::Int::Compiler do
         .to('3')
     end
 
+    it 'removes the item rep from the outdatedness store' do
+      expect(compiler.outdatedness_store.include?(rep)).not_to be
+      expect { subject }.not_to change { compiler.outdatedness_store.include?(rep) }
+    end
+
+    context 'rep in outdatedness store' do
+      before do
+        compiler.outdatedness_store.add(rep)
+      end
+
+      it 'removes the item rep from the outdatedness store' do
+        expect { subject }.to change { compiler.outdatedness_store.include?(rep) }.from(true).to(false)
+      end
+    end
+
     context 'exception' do
       let(:item) { Nanoc::Int::Item.new('<%= raise "lol" %>', {}, '/hi.md') }
 
@@ -112,11 +135,28 @@ describe Nanoc::Int::Compiler do
           expect(err.unwrap.message).to eq('lol')
         end
       end
+
+      it 'adds the item rep to the outdatedness store' do
+        expect { subject rescue nil }.to change { compiler.outdatedness_store.include?(rep) }.from(false).to(true)
+      end
+
+      context 'rep in outdatedness store' do
+        before do
+          compiler.outdatedness_store.add(rep)
+        end
+
+        it 'keeps the item rep in the outdatedness store' do
+          expect(compiler.outdatedness_store.include?(rep)).to be
+          expect { subject rescue nil }.not_to change { compiler.outdatedness_store.include?(rep) }
+        end
+      end
     end
   end
 
   describe '#compile_rep' do
-    subject { compiler.send(:compile_rep, rep, is_outdated: is_outdated) }
+    let(:stage) { compiler.send(:compile_reps_stage) }
+
+    subject { stage.send(:compile_rep, rep, is_outdated: is_outdated) }
 
     let(:is_outdated) { true }
 
@@ -145,10 +185,10 @@ describe Nanoc::Int::Compiler do
       it 'generates expected output' do
         expect(rep.snapshot_contents[:last].string).to eql(item.content.string)
 
-        expect { compiler.send(:compile_rep, rep, is_outdated: true) }
+        expect { stage.send(:compile_rep, rep, is_outdated: true) }
           .to raise_error(Nanoc::Int::Errors::UnmetDependency)
-        compiler.send(:compile_rep, other_rep, is_outdated: true)
-        compiler.send(:compile_rep, rep, is_outdated: true)
+        stage.send(:compile_rep, other_rep, is_outdated: true)
+        stage.send(:compile_rep, rep, is_outdated: true)
 
         expect(rep.snapshot_contents[:last].string).to eql('other=other content')
       end
@@ -171,10 +211,10 @@ describe Nanoc::Int::Compiler do
         expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:filtering_ended, rep, :erb).ordered
         expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:compilation_ended, rep).ordered
 
-        expect { compiler.send(:compile_rep, rep, is_outdated: true) }
+        expect { stage.send(:compile_rep, rep, is_outdated: true) }
           .to raise_error(Nanoc::Int::Errors::UnmetDependency)
-        compiler.send(:compile_rep, other_rep, is_outdated: true)
-        compiler.send(:compile_rep, rep, is_outdated: true)
+        stage.send(:compile_rep, other_rep, is_outdated: true)
+        stage.send(:compile_rep, rep, is_outdated: true)
       end
     end
   end
diff --git a/spec/nanoc/base/repos/outdatedness_store_spec.rb b/spec/nanoc/base/repos/outdatedness_store_spec.rb
new file mode 100644
index 0000000..20750f9
--- /dev/null
+++ b/spec/nanoc/base/repos/outdatedness_store_spec.rb
@@ -0,0 +1,100 @@
+describe Nanoc::Int::OutdatednessStore do
+  subject(:store) { described_class.new(site: site, reps: reps) }
+
+  let(:site) { double(:site) }
+  let(:reps) { Nanoc::Int::ItemRepRepo.new }
+
+  let(:item) { Nanoc::Int::Item.new('foo', {}, '/foo.md') }
+  let(:rep) { Nanoc::Int::ItemRep.new(item, :foo) }
+
+  let(:site) do
+    Nanoc::Int::Site.new(
+      config: config,
+      code_snippets: code_snippets,
+      items: items,
+      layouts: layouts,
+    )
+  end
+
+  let(:config) { Nanoc::Int::Configuration.new.with_defaults }
+  let(:items) { [] }
+  let(:layouts) { [] }
+  let(:code_snippets) { [] }
+
+  describe '#include?, #add and #remove' do
+    subject { store.include?(rep) }
+
+    context 'nothing added' do
+      it { is_expected.not_to be }
+    end
+
+    context 'rep added' do
+      before { store.add(rep) }
+      it { is_expected.to be }
+    end
+
+    context 'rep added and removed' do
+      before do
+        store.add(rep)
+        store.remove(rep)
+      end
+
+      it { is_expected.not_to be }
+    end
+
+    context 'rep added, removed, and added again' do
+      before do
+        store.add(rep)
+        store.remove(rep)
+        store.add(rep)
+      end
+
+      it { is_expected.to be }
+    end
+  end
+
+  describe '#to_a' do
+    subject { store.to_a }
+
+    context 'nothing added' do
+      it { is_expected.to be_empty }
+    end
+
+    context 'one rep added' do
+      before { store.add(rep) }
+      it { is_expected.to eql([rep]) }
+    end
+  end
+
+  describe 'reloading' do
+    subject do
+      store.store
+      store.load
+      store.include?(rep)
+    end
+
+    context 'not added' do
+      context 'rep part of new reps' do
+        before { reps << rep }
+        it { is_expected.not_to be }
+      end
+
+      context 'rep not part of new reps' do
+        it { is_expected.not_to be }
+      end
+    end
+
+    context 'added' do
+      before { store.add(rep) }
+
+      context 'rep part of new reps' do
+        before { reps << rep }
+        it { is_expected.to be }
+      end
+
+      context 'rep not part of new reps' do
+        it { is_expected.not_to be }
+      end
+    end
+  end
+end
diff --git a/spec/nanoc/base/services/outdatedness_checker_spec.rb b/spec/nanoc/base/services/outdatedness_checker_spec.rb
index 6eeb7ad..f857016 100644
--- a/spec/nanoc/base/services/outdatedness_checker_spec.rb
+++ b/spec/nanoc/base/services/outdatedness_checker_spec.rb
@@ -10,9 +10,22 @@ describe Nanoc::Int::OutdatednessChecker do
     )
   end
 
-  let(:site) { double(:site) }
   let(:checksum_store) { double(:checksum_store) }
-  let(:dependency_store) { double(:dependency_store) }
+
+  let(:dependency_store) do
+    Nanoc::Int::DependencyStore.new(objects)
+  end
+
+  let(:objects) { [item] }
+
+  let(:site) do
+    Nanoc::Int::Site.new(
+      config: config,
+      items: [],
+      layouts: [],
+      code_snippets: [],
+    )
+  end
 
   let(:rule_memory_store) do
     Nanoc::Int::RuleMemoryStore.new
@@ -102,10 +115,6 @@ describe Nanoc::Int::OutdatednessChecker do
   describe '#outdated_due_to_dependencies?' do
     subject { outdatedness_checker.send(:outdated_due_to_dependencies?, item) }
 
-    let(:dependency_store) do
-      Nanoc::Int::DependencyStore.new(objects)
-    end
-
     let(:checksum_store) { Nanoc::Int::ChecksumStore.new(objects: objects) }
 
     let(:other_item) { Nanoc::Int::Item.new('other stuff', {}, '/other.md') }
diff --git a/spec/nanoc/base/services/outdatedness_rules_spec.rb b/spec/nanoc/base/services/outdatedness_rules_spec.rb
index 1888cfd..4c2accd 100644
--- a/spec/nanoc/base/services/outdatedness_rules_spec.rb
+++ b/spec/nanoc/base/services/outdatedness_rules_spec.rb
@@ -18,11 +18,19 @@ describe Nanoc::Int::OutdatednessRules do
     let(:item_rep) { Nanoc::Int::ItemRep.new(item, :default) }
     let(:item) { Nanoc::Int::Item.new('stuff', {}, '/foo.md') }
 
-    let(:site) { double(:site) }
     let(:config) { Nanoc::Int::Configuration.new }
     let(:code_snippets) { [] }
     let(:objects) { [config] + code_snippets + [item] }
 
+    let(:site) do
+      Nanoc::Int::Site.new(
+        config: config,
+        items: [],
+        layouts: [],
+        code_snippets: code_snippets,
+      )
+    end
+
     let(:action_provider) { double(:action_provider) }
     let(:reps) { Nanoc::Int::ItemRepRepo.new }
     let(:dependency_store) { Nanoc::Int::DependencyStore.new(dependency_store_objects) }
@@ -67,15 +75,15 @@ describe Nanoc::Int::OutdatednessRules do
     context 'ConfigurationModified' do
       let(:rule_class) { Nanoc::Int::OutdatednessRules::ConfigurationModified }
 
-      context 'only non-outdated snippets' do
-        let(:config) { Nanoc::Int::CodeSnippet.new('asdf', 'lib/foo.md') }
+      context 'non-outdated' do
+        let(:config) { Nanoc::Int::Configuration.new }
 
         before { checksum_store.add(config) }
 
         it { is_expected.not_to be }
       end
 
-      context 'only non-outdated snippets' do
+      context 'outdated' do
         let(:config) { Nanoc::Int::Configuration.new }
         let(:config_old) { Nanoc::Int::Configuration.new(hash: { foo: 125 }) }
 
diff --git a/spec/nanoc/cli/commands/view_spec.rb b/spec/nanoc/cli/commands/view_spec.rb
index 2b64b19..2c36912 100644
--- a/spec/nanoc/cli/commands/view_spec.rb
+++ b/spec/nanoc/cli/commands/view_spec.rb
@@ -1,3 +1,5 @@
+require 'net/http'
+
 describe Nanoc::CLI::Commands::View, site: true, stdio: true do
   describe '#run' do
     def run_nanoc_cmd(cmd)
diff --git a/spec/nanoc/data_sources/filesystem_spec.rb b/spec/nanoc/data_sources/filesystem_spec.rb
index 90c02e6..4d831f8 100644
--- a/spec/nanoc/data_sources/filesystem_spec.rb
+++ b/spec/nanoc/data_sources/filesystem_spec.rb
@@ -48,7 +48,19 @@ describe Nanoc::DataSources::Filesystem do
           expect(subject[0].identifier).to eq(Nanoc::Identifier.new('/bar/'))
           expect(subject[0].checksum_data).to be_nil
           expect(subject[0].content_checksum_data).to eq('test 1')
-          expect(subject[0].attributes_checksum_data).to eq("num: 1\n")
+        end
+
+        it 'has the right attributes checksum data' do
+          cs = YAML.load(subject[0].attributes_checksum_data)
+
+          expect(cs[:attributes]).to eq("num: 1\n")
+          expect(cs[:extra_attributes]).to eq(
+            filename: 'foo/bar.html',
+            content_filename: 'foo/bar.html',
+            meta_filename: nil,
+            extension: 'html',
+            mtime: now,
+          )
         end
       end
     end
diff --git a/spec/nanoc/extra/parallel_collection_spec.rb b/spec/nanoc/extra/parallel_collection_spec.rb
index 4674093..7fd8194 100644
--- a/spec/nanoc/extra/parallel_collection_spec.rb
+++ b/spec/nanoc/extra/parallel_collection_spec.rb
@@ -13,7 +13,7 @@ describe Nanoc::Extra::ParallelCollection do
     let!(:out) { [] }
 
     it 'is fast' do
-      expect { subject }.to finish_in_under(0.2).seconds
+      expect { subject }.to finish_in_under(0.25).seconds
     end
 
     it 'is correct' do
@@ -66,7 +66,7 @@ describe Nanoc::Extra::ParallelCollection do
     end
 
     it 'is fast' do
-      expect { subject }.to finish_in_under(0.2).seconds
+      expect { subject }.to finish_in_under(0.25).seconds
     end
 
     it 'does not leave threads lingering' do
diff --git a/spec/nanoc/integration/outdatedness_integration_spec.rb b/spec/nanoc/integration/outdatedness_integration_spec.rb
index df1aa50..6dabb31 100644
--- a/spec/nanoc/integration/outdatedness_integration_spec.rb
+++ b/spec/nanoc/integration/outdatedness_integration_spec.rb
@@ -1,9 +1,14 @@
 describe 'Outdatedness integration', site: true, stdio: true do
   context 'only attribute dependency' do
+    let(:time) { Time.now }
+
     before do
       File.write('content/foo.md', "---\ntitle: hello\n---\n\nfoo")
       File.write('content/bar.md', '<%= @items["/foo.*"][:title] %>')
 
+      FileUtils.touch('content/foo.md', mtime: time)
+      FileUtils.touch('content/bar.md', mtime: time)
+
       File.write('Rules', <<EOS)
 compile '/foo.*' do
   write '/foo.html'
@@ -29,6 +34,7 @@ EOS
 
     it 'shows file as outdated after modification' do
       File.write('content/bar.md', 'JUST BAR!')
+      FileUtils.touch('content/bar.md', mtime: time)
 
       expect { Nanoc::CLI.run(%w(show-data --no-color)) }.to(
         output(/^item \/foo\.md, rep default:\n  is not outdated/).to_stdout,
@@ -40,6 +46,7 @@ EOS
 
     it 'shows file and dependencies as not outdated after content modification' do
       File.write('content/foo.md', "---\ntitle: hello\n---\n\nfoooOoooOOoooOooo")
+      FileUtils.touch('content/foo.md', mtime: time)
 
       expect { Nanoc::CLI.run(%w(show-data --no-color)) }.to(
         output(/^item \/foo\.md, rep default:\n  is outdated: /).to_stdout,
@@ -51,6 +58,7 @@ EOS
 
     it 'shows file and dependencies as outdated after title modification' do
       File.write('content/foo.md', "---\ntitle: bye\n---\n\nfoo")
+      FileUtils.touch('content/foo.md', mtime: time)
 
       expect { Nanoc::CLI.run(%w(show-data --no-color)) }.to(
         output(/^item \/foo\.md, rep default:\n  is outdated: /).to_stdout,
diff --git a/spec/nanoc/integration/partial_recompilation_spec.rb b/spec/nanoc/integration/partial_recompilation_spec.rb
new file mode 100644
index 0000000..bae353a
--- /dev/null
+++ b/spec/nanoc/integration/partial_recompilation_spec.rb
@@ -0,0 +1,48 @@
+describe 'Partial recompilation', site: true, stdio: true do
+  before do
+    File.write('content/foo.md', "---\ntitle: hello\n---\n\nfoo")
+    File.write('content/bar.md', '<%= @items["/foo.*"].compiled_content %><% raise "boom" %>')
+
+    File.write('Rules', <<EOS)
+compile '/foo.*' do
+  write '/foo.html'
+end
+
+compile '/bar.*' do
+  filter :erb
+  write '/bar.html'
+end
+EOS
+  end
+
+  example do
+    expect(File.file?('output/foo.html')).not_to be
+    expect(File.file?('output/bar.html')).not_to be
+
+    expect { Nanoc::CLI.run(%w(show-data --no-color)) }
+      .to(output(/^item \/foo\.md, rep default:\n  is outdated: /).to_stdout)
+    expect { Nanoc::CLI.run(%w(show-data --no-color)) }
+      .to(output(/^item \/bar\.md, rep default:\n  is outdated: /).to_stdout)
+
+    expect { Nanoc::CLI.run(%w(compile --verbose)) rescue nil }
+      .to output(/create.*output\/foo\.html/).to_stdout
+
+    expect { Nanoc::CLI.run(%w(show-data --no-color)) }
+      .to(output(/^item \/foo\.md, rep default:\n  is not outdated/).to_stdout)
+    expect { Nanoc::CLI.run(%w(show-data --no-color)) }
+      .to(output(/^item \/bar\.md, rep default:\n  is outdated: /).to_stdout)
+
+    expect(File.file?('output/foo.html')).to be
+    expect(File.file?('output/bar.html')).not_to be
+
+    File.write('content/bar.md', '<% raise "boom" %>')
+
+    expect { Nanoc::CLI.run(%w(compile --verbose --debug)) rescue nil }
+      .to output(/skip.*output\/foo\.html/).to_stdout
+
+    expect { Nanoc::CLI.run(%w(show-data --no-color)) }
+      .to(output(/^item \/foo\.md, rep default:\n  is not outdated/).to_stdout)
+    expect { Nanoc::CLI.run(%w(show-data --no-color)) }
+      .to(output(/^item \/bar\.md, rep default:\n  is outdated: /).to_stdout)
+  end
+end
diff --git a/spec/nanoc/regressions/gh_1045_spec.rb b/spec/nanoc/regressions/gh_1045_spec.rb
new file mode 100644
index 0000000..11e8891
--- /dev/null
+++ b/spec/nanoc/regressions/gh_1045_spec.rb
@@ -0,0 +1,48 @@
+describe 'GH-1045', site: true, stdio: true do
+  before do
+    File.write('content/foo.txt', 'foo')
+    FileUtils.touch('content/foo.txt', mtime: Time.parse('2015-03-02 10:00:00Z'))
+
+    File.write('content/sitemap.erb', '<%= xml_sitemap(items: items.select { |i| i.path.end_with?(\'/\') }) %>')
+
+    File.write('nanoc.yaml', <<EOS)
+base_url: 'http://example.com'
+EOS
+
+    File.write('lib/default.rb', <<EOS)
+include Nanoc::Helpers::XMLSitemap
+EOS
+
+    File.write('Rules', <<EOS)
+compile '/*.txt' do
+  write item.identifier.without_ext + '/index.html'
+end
+
+compile '/sitemap.erb' do
+  filter :erb
+  write item.identifier.without_ext + '.xml'
+end
+EOS
+  end
+
+  it 'creates the sitemap' do
+    Nanoc::CLI.run(%w(compile))
+
+    expect(File.file?('output/sitemap.xml')).to be
+    contents = File.read('output/sitemap.xml')
+    expect(contents).to match(%r{<loc>http://example.com/foo/</loc>})
+    expect(contents).to match(%r{<lastmod>2015-03-02</lastmod>})
+  end
+
+  it 'updates the sitemap' do
+    Nanoc::CLI.run(%w(compile))
+    File.write('content/foo.txt', 'foo 2')
+    FileUtils.touch('content/foo.txt', mtime: Time.parse('2016-04-03 10:00:00Z'))
+    Nanoc::CLI.run(%w(compile))
+
+    expect(File.file?('output/sitemap.xml')).to be
+    contents = File.read('output/sitemap.xml')
+    expect(contents).to match(%r{<loc>http://example.com/foo/</loc>})
+    expect(contents).to match(%r{<lastmod>2016-04-03</lastmod>})
+  end
+end
diff --git a/spec/nanoc/regressions/gh_1047_spec.rb b/spec/nanoc/regressions/gh_1047_spec.rb
new file mode 100644
index 0000000..aef1d61
--- /dev/null
+++ b/spec/nanoc/regressions/gh_1047_spec.rb
@@ -0,0 +1,28 @@
+describe 'GH-1047', site: true, stdio: true do
+  before do
+    File.write('Rules', <<EOS)
+  compile '/*' do
+    filter :erb
+    write item.identifier
+  end
+EOS
+  end
+
+  it 'does not reuse old content' do
+    File.write('content/foo.md', 'I am old foo!')
+    File.write('content/bar.md', 'I am old bar!')
+    Nanoc::CLI.run(%w(compile))
+    expect(File.read('output/foo.md')).to eql('I am old foo!')
+    expect(File.read('output/bar.md')).to eql('I am old bar!')
+
+    File.write('content/foo.md', 'I am foo!')
+    File.write('content/bar.md', '<%= @items["/foo.*"].compiled_content %><%= raise "boom" %>')
+    expect { Nanoc::CLI.run(%w(compile)) }.to raise_error(Nanoc::Int::Errors::CompilationError)
+    expect(File.read('output/foo.md')).to eql('I am foo!')
+
+    File.write('content/bar.md', '[<%= @items["/foo.*"].compiled_content %>]')
+    Nanoc::CLI.run(%w(compile))
+    expect(File.read('output/foo.md')).to eql('I am foo!')
+    expect(File.read('output/bar.md')).to eql('[I am foo!]')
+  end
+end
diff --git a/test/base/test_compiler.rb b/test/base/test_compiler.rb
index 17613bb..af15aa5 100644
--- a/test/base/test_compiler.rb
+++ b/test/base/test_compiler.rb
@@ -24,6 +24,7 @@ class Nanoc::Int::CompilerTest < Nanoc::TestCase
       ),
       action_provider: action_provider,
       reps: reps,
+      outdatedness_store: Nanoc::Int::OutdatednessStore.new(site: site, reps: reps),
     }
 
     params[:outdatedness_checker] =

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/nanoc.git



More information about the Pkg-ruby-extras-commits mailing list