lib/nanoc/base/compilation/compiler.rb in nanoc-4.0.2 vs lib/nanoc/base/compilation/compiler.rb in nanoc-4.1.0a1
- old
+ new
@@ -36,131 +36,112 @@
# * `processing_ended` — indicates that the compiler has finished processing
# the specified object.
#
# @api private
class Compiler
- extend Nanoc::Int::Memoization
-
- class IdenticalRoutesError < ::Nanoc::Error
- def initialize(output_path, rep_a, rep_b)
- super("The item representations #{rep_a.inspect} and #{rep_b.inspect} are both routed to #{output_path}.")
- end
- end
-
- # @group Accessors
-
- # @return [Nanoc::Int::Site] The site this compiler belongs to
+ # @api private
attr_reader :site
# The compilation stack. When the compiler begins compiling a rep or a
# layout, it will be placed on the stack; when it is done compiling the
# rep or layout, it will be removed from the stack.
#
# @return [Array] The compilation stack
attr_reader :stack
- # @group Public instance methods
+ # @api private
+ attr_reader :rules_collection
- # Creates a new compiler fo the given site
- #
- # @param [Nanoc::Int::Site] site The site this compiler belongs to
- def initialize(site)
- @site = site
+ # @api private
+ attr_reader :compiled_content_cache
- @stack = []
- end
+ # @api private
+ attr_reader :checksum_store
- # Compiles the site and writes out the compiled item representations.
- #
- # Previous versions of Nanoc (< 3.2) allowed passing items to compile, and
- # had a “force” option to make the compiler recompile all pages, even
- # when not outdated. These arguments and options are, as of Nanoc 3.2, no
- # longer used, and will simply be ignored when passed to {#run}.
- #
- # @overload run
- # @return [void]
- def run(*_args)
- # Create output directory if necessary
- FileUtils.mkdir_p(@site.config[:output_dir])
+ # @api private
+ attr_reader :rule_memory_store
- # Compile reps
- load
- @site.freeze
+ # @api private
+ attr_reader :rule_memory_calculator
- # Determine which reps need to be recompiled
- forget_dependencies_if_outdated(items)
+ # @api private
+ attr_reader :dependency_store
- dependency_tracker.start
- compile_reps(reps)
- dependency_tracker.stop
- store
- ensure
- Nanoc::Int::TempFilenameFactory.instance.cleanup(
- Nanoc::Filter::TMP_BINARY_ITEMS_DIR)
- Nanoc::Int::TempFilenameFactory.instance.cleanup(
- Nanoc::Int::ItemRepWriter::TMP_TEXT_ITEMS_DIR)
- end
+ # @api private
+ attr_reader :outdatedness_checker
- # @group Private instance methods
+ # @api private
+ attr_reader :reps
- # @return [Nanoc::Int::RulesCollection] The collection of rules to be used
- # for compiling this site
- def rules_collection
- Nanoc::Int::RulesCollection.new(self)
+ def initialize(site, rules_collection, compiled_content_cache:, checksum_store:, rule_memory_store:, rule_memory_calculator:, dependency_store:, outdatedness_checker:, reps:)
+ @site = site
+ @rules_collection = rules_collection
+
+ @compiled_content_cache = compiled_content_cache
+ @checksum_store = checksum_store
+ @rule_memory_store = rule_memory_store
+ @rule_memory_calculator = rule_memory_calculator
+ @dependency_store = dependency_store
+ @outdatedness_checker = outdatedness_checker
+ @reps = reps
+
+ @stack = []
end
- memoize :rules_collection
- # Load the helper data that is used for compiling the site.
- #
- # @return [void]
- def load
- return if @loaded || @loading
- @loading = true
+ # 1. Load site
+ # 2. Load rules
+ # 3. Preprocess
+ # 4. Build item reps
+ # 5. Compile
+ # 6. Postprocess
+ # TODO: move elsewhere
+ def run_all
# Preprocess
- rules_collection.load
- preprocess
- Nanoc::Int::SiteLoader.new.setup_child_parent_links(site.items)
+ Nanoc::Int::Preprocessor.new(site: @site, rules_collection: @rules_collection).run
+
+ # Build reps
build_reps
- route_reps
- # Load auxiliary stores
- stores.each(&:load)
+ # Compile
+ run
- @loaded = true
- rescue => e
- unload
- raise e
- ensure
- @loading = false
+ # Postprocess
+ Nanoc::Int::Postprocessor.new(create_view_context, site: @site, rules_collection: @rules_collection).run
end
- # Undoes the effects of {#load}. Used when {#load} raises an exception.
- #
- # @return [void]
- def unload
- return if @unloading
- @unloading = true
+ def run
+ load_stores
+ @site.freeze
- stores.each(&:unload)
+ # Determine which reps need to be recompiled
+ forget_dependencies_if_outdated
@stack = []
+ dependency_tracker = Nanoc::Int::DependencyTracker.new(@dependency_store)
+ dependency_tracker.run do
+ compile_reps
+ end
+ store
+ ensure
+ Nanoc::Int::TempFilenameFactory.instance.cleanup(
+ Nanoc::Filter::TMP_BINARY_ITEMS_DIR)
+ Nanoc::Int::TempFilenameFactory.instance.cleanup(
+ Nanoc::Int::ItemRepWriter::TMP_TEXT_ITEMS_DIR)
+ end
- items.each { |item| item.reps.clear }
- rules_collection.unload
-
- @loaded = false
- @unloading = false
+ def load_stores
+ stores.each(&:load)
end
# Store the modified helper data used for compiling the site.
#
# @return [void]
def store
# Calculate rule memory
- (reps + layouts.to_a).each do |obj|
- rule_memory_store[obj] = rule_memory_calculator[obj]
+ (@reps.to_a + @site.layouts.to_a).each do |obj|
+ rule_memory_store[obj] = rule_memory_calculator[obj].serialize
end
# Calculate checksums
objects.each do |obj|
checksum_store[obj] = Nanoc::Int::Checksummer.calc(obj)
@@ -168,100 +149,25 @@
# Store
stores.each(&:store)
end
- # Returns the dependency tracker for this site, creating it first if it
- # does not yet exist.
- #
- # @return [Nanoc::Int::DependencyTracker] The dependency tracker for this site
- def dependency_tracker
- dt = Nanoc::Int::DependencyTracker.new(@site.items.to_a + @site.layouts.to_a)
- dt.compiler = self
- dt
- end
- memoize :dependency_tracker
-
- # Runs the preprocessors.
- #
- # @api private
- def preprocess
- rules_collection.preprocessors.each_value do |preprocessor|
- preprocessor_context.instance_eval(&preprocessor)
- end
- end
-
# Returns all objects managed by the site (items, layouts, code snippets,
# site configuration and the rules).
#
# @api private
def objects
site.items.to_a + site.layouts.to_a + site.code_snippets +
[site.config, rules_collection]
end
- # Creates the representations of all items as defined by the compilation
- # rules.
- #
- # @api private
def build_reps
- items.each do |item|
- # Find matching rules
- matching_rules = rules_collection.item_compilation_rules_for(item)
- raise Nanoc::Int::Errors::NoMatchingCompilationRuleFound.new(item) if matching_rules.empty?
-
- # Create reps
- rep_names = matching_rules.map(&:rep_name).uniq
- rep_names.each do |rep_name|
- item.reps << Nanoc::Int::ItemRep.new(item, rep_name)
- end
- end
+ builder = Nanoc::Int::ItemRepBuilder.new(
+ site, rules_collection, rule_memory_calculator, @reps)
+ builder.run
end
- # Determines the paths of all item representations.
- #
- # @api private
- def route_reps
- paths_to_reps = {}
-
- reps.each do |rep|
- # Find matching rules
- rules = rules_collection.routing_rules_for(rep)
- raise Nanoc::Int::Errors::NoMatchingRoutingRuleFound.new(rep) if rules[:last].nil?
-
- rules.each_pair do |snapshot, rule|
- # Get basic path by applying matching rule
- basic_path = rule.apply_to(rep, executor: nil, compiler: self)
- next if basic_path.nil?
- if basic_path !~ %r{^/}
- raise "The path returned for the #{rep.inspect} item representation, “#{basic_path}”, does not start with a slash. Please ensure that all routing rules return a path that starts with a slash."
- end
-
- # Check for duplicate paths
- if paths_to_reps.key?(basic_path)
- raise IdenticalRoutesError.new(basic_path, paths_to_reps[basic_path], rep)
- else
- paths_to_reps[basic_path] = rep
- end
-
- # Get raw path by prepending output directory
- rep.raw_paths[snapshot] = @site.config[:output_dir] + basic_path
-
- # Get normal path by stripping index filename
- rep.paths[snapshot] = basic_path
- @site.config[:index_filenames].each do |index_filename|
- rep_path_ending = rep.paths[snapshot][-index_filename.length..-1]
- next unless rep_path_ending == index_filename
-
- # Strip and stop
- rep.paths[snapshot] = rep.paths[snapshot][0..-index_filename.length - 1]
- break
- end
- end
- end
- end
-
# @param [Nanoc::Int::ItemRep] rep The item representation for which the
# assigns should be fetched
#
# @return [Hash] The assigns that should be used in the next filter/layout
# operation
@@ -272,92 +178,46 @@
content_or_filename_assigns = { filename: rep.snapshot_contents[:last].filename }
else
content_or_filename_assigns = { content: rep.snapshot_contents[:last].string }
end
+ view_context = create_view_context
+
# TODO: Do not expose @site (necessary for captures store though…)
content_or_filename_assigns.merge(
- item: Nanoc::ItemView.new(rep.item),
- rep: Nanoc::ItemRepView.new(rep),
- item_rep: Nanoc::ItemRepView.new(rep),
- items: Nanoc::ItemCollectionView.new(site.items),
- layouts: Nanoc::LayoutCollectionView.new(site.layouts),
- config: Nanoc::ConfigView.new(site.config),
- site: Nanoc::SiteView.new(site),
+ item: Nanoc::ItemView.new(rep.item, view_context),
+ rep: Nanoc::ItemRepView.new(rep, view_context),
+ item_rep: Nanoc::ItemRepView.new(rep, view_context),
+ items: Nanoc::ItemCollectionView.new(site.items, view_context),
+ layouts: Nanoc::LayoutCollectionView.new(site.layouts, view_context),
+ config: Nanoc::ConfigView.new(site.config, view_context),
+ site: Nanoc::SiteView.new(site, view_context),
)
end
- # @return [Nanoc::Int::OutdatednessChecker] The outdatedness checker
- def outdatedness_checker
- Nanoc::Int::OutdatednessChecker.new(
- site: @site,
- checksum_store: checksum_store,
- dependency_tracker: dependency_tracker,
- )
+ def create_view_context
+ Nanoc::ViewContext.new(reps: @reps, items: @site.items)
end
- memoize :outdatedness_checker
private
- # @return [Array<Nanoc::Int::Item>] The site’s items
- def items
- @site.items
- end
- memoize :items
-
- # @return [Array<Nanoc::Int::ItemRep>] The site’s item representations
- def reps
- items.map(&:reps).flatten
- end
- memoize :reps
-
- # @return [Array<Nanoc::Int::Layout>] The site’s layouts
- def layouts
- @site.layouts
- end
- memoize :layouts
-
- # Compiles the given representations.
- #
- # @param [Array] reps The item representations to compile.
- #
- # @return [void]
- def compile_reps(reps)
- content_dependency_graph = Nanoc::Int::DirectedGraph.new(reps)
-
+ def compile_reps
# Listen to processing start/stop
Nanoc::Int::NotificationCenter.on(:processing_started, self) { |obj| @stack.push(obj) }
- Nanoc::Int::NotificationCenter.on(:processing_ended, self) { |_obj| @stack.pop }
+ Nanoc::Int::NotificationCenter.on(:processing_ended, self) { |_obj| @stack.pop }
# Assign snapshots
- reps.each do |rep|
- rep.snapshot_defs = rules_collection.snapshots_defs_for(rep)
+ @reps.each do |rep|
+ rep.snapshot_defs = rule_memory_calculator.snapshots_defs_for(rep)
end
- # Attempt to compile all active reps
- loop do
- # Find rep to compile
- break if content_dependency_graph.roots.empty?
- rep = content_dependency_graph.roots.each { |e| break e }
+ # Find item reps to compile and compile them
+ selector = Nanoc::Int::ItemRepSelector.new(@reps)
+ selector.each do |rep|
@stack = []
-
- begin
- compile_rep(rep)
- content_dependency_graph.delete_vertex(rep)
- rescue Nanoc::Int::Errors::UnmetDependency => e
- other_rep = e.rep.unwrap rescue e.rep
- content_dependency_graph.add_edge(other_rep, rep)
- unless content_dependency_graph.vertices.include?(other_rep)
- content_dependency_graph.add_vertex(other_rep)
- end
- end
+ compile_rep(rep)
end
-
- # Check whether everything was compiled
- unless content_dependency_graph.vertices.empty?
- raise Nanoc::Int::Errors::RecursiveCompilation.new(content_dependency_graph.vertices)
- end
ensure
Nanoc::Int::NotificationCenter.remove(:processing_started, self)
Nanoc::Int::NotificationCenter.remove(:processing_ended, self)
end
@@ -369,30 +229,19 @@
#
# @param [Nanoc::Int::ItemRep] rep The rep that is to be compiled
#
# @return [void]
def compile_rep(rep)
- executor = Nanoc::Int::Executor.new(self)
-
Nanoc::Int::NotificationCenter.post(:compilation_started, rep)
Nanoc::Int::NotificationCenter.post(:processing_started, rep)
Nanoc::Int::NotificationCenter.post(:visit_started, rep.item)
- # Calculate rule memory if we haven’t yet done do
- rules_collection.new_rule_memory_for_rep(rep)
-
- if !rep.item.forced_outdated? && !outdatedness_checker.outdated?(rep) && compiled_content_cache[rep]
- # Reuse content
+ if can_reuse_content_for_rep?(rep)
Nanoc::Int::NotificationCenter.post(:cached_content_used, rep)
rep.snapshot_contents = compiled_content_cache[rep]
else
- # Recalculate content
- executor.snapshot(rep, :raw)
- executor.snapshot(rep, :pre, final: false)
- rules_collection.compilation_rule_for(rep).apply_to(rep, executor: executor, compiler: self)
- executor.snapshot(rep, :post) if rep.has_snapshot?(:post)
- executor.snapshot(rep, :last)
+ recalculate_content_for_rep(rep)
end
rep.compiled = true
compiled_content_cache[rep] = rep.snapshot_contents
@@ -404,64 +253,44 @@
raise e
ensure
Nanoc::Int::NotificationCenter.post(:visit_ended, rep.item)
end
+ # @return [Boolean]
+ def can_reuse_content_for_rep?(rep)
+ !rep.item.forced_outdated? && !outdatedness_checker.outdated?(rep) && compiled_content_cache[rep]
+ end
+
+ # @return [void]
+ def recalculate_content_for_rep(rep)
+ executor = Nanoc::Int::Executor.new(self)
+
+ executor.snapshot(rep, :raw)
+ executor.snapshot(rep, :pre, final: false)
+ rules_collection.compilation_rule_for(rep)
+ .apply_to(rep, executor: executor, site: @site, view_context: create_view_context)
+ executor.snapshot(rep, :post) if rep.has_snapshot?(:post)
+ executor.snapshot(rep, :last)
+ end
+
# Clears the list of dependencies for items that will be recompiled.
#
- # @param [Array<Nanoc::Int::Item>] items The list of items for which to forget
- # the dependencies
- #
# @return [void]
- def forget_dependencies_if_outdated(items)
- items.each do |i|
- if i.reps.any? { |r| outdatedness_checker.outdated?(r) }
- dependency_tracker.forget_dependencies_for(i)
+ def forget_dependencies_if_outdated
+ @site.items.each do |i|
+ if @reps[i].any? { |r| outdatedness_checker.outdated?(r) }
+ @dependency_store.forget_dependencies_for(i)
end
end
end
- # Returns a preprocessor context, creating one if none exists yet.
- def preprocessor_context
- Nanoc::Int::Context.new(
- config: Nanoc::MutableConfigView.new(@site.config),
- items: Nanoc::MutableItemCollectionView.new(@site.items),
- layouts: Nanoc::MutableLayoutCollectionView.new(@site.layouts),
- )
- end
- memoize :preprocessor_context
-
- # @return [Nanoc:Int::CompiledContentCache] The compiled content cache
- def compiled_content_cache
- Nanoc::Int::CompiledContentCache.new
- end
- memoize :compiled_content_cache
-
- # @return [ChecksumStore] The checksum store
- def checksum_store
- Nanoc::Int::ChecksumStore.new(site: @site)
- end
- memoize :checksum_store
-
- # @return [RuleMemoryStore] The rule memory store
- def rule_memory_store
- Nanoc::Int::RuleMemoryStore.new(site: @site)
- end
- memoize :rule_memory_store
-
- # @return [RuleMemoryCalculator] The rule memory calculator
- def rule_memory_calculator
- Nanoc::Int::RuleMemoryCalculator.new(rules_collection: rules_collection)
- end
- memoize :rule_memory_calculator
-
# Returns all stores that can load/store data that can be used for
# compilation.
def stores
[
checksum_store,
compiled_content_cache,
- dependency_tracker,
+ @dependency_store,
rule_memory_store,
]
end
end
end