lib/nanoc3/base/compilation/compiler.rb in nanoc3-3.2.0a3 vs lib/nanoc3/base/compilation/compiler.rb in nanoc3-3.2.0a4
- old
+ new
@@ -38,10 +38,12 @@
#
# * `processing_ended` — indicates that the compiler has finished processing
# the specified object.
class Compiler
+ extend Nanoc3::Memoization
+
# @group Accessors
# @return [Nanoc3::Site] The site this compiler belongs to
attr_reader :site
@@ -50,41 +52,19 @@
# rep or layout, it will be removed from the stack.
#
# @return [Array] The compilation stack
attr_reader :stack
- # @return [Array<Nanoc3::Rule>] The list of item compilation rules that
- # will be used to compile items.
- attr_reader :item_compilation_rules
-
- # @return [Array<Nanoc3::Rule>] The list of routing rules that will be
- # used to give all items a path.
- attr_reader :item_routing_rules
-
- # The hash containing layout-to-filter mapping rules. This hash is
- # ordered: iterating over the hash will happen in insertion order.
- #
- # @return [Hash] The layout-to-filter mapping rules
- attr_reader :layout_filter_mapping
-
- # @return [Proc] The code block that will be executed after all data is
- # loaded but before the site is compiled
- attr_accessor :preprocessor
-
# @group Public instance methods
# Creates a new compiler fo the given site
#
# @param [Nanoc3::Site] site The site this compiler belongs to
def initialize(site)
@site = site
@stack = []
-
- @item_compilation_rules = []
- @item_routing_rules = []
- @layout_filter_mapping = OrderedHash.new
end
# Compiles the site and writes out the compiled item representations.
#
# Previous versions of nanoc (< 3.2) allowed passing items to compile, and
@@ -110,201 +90,131 @@
FileUtils.rm_rf(Nanoc3::Filter::TMP_BINARY_ITEMS_DIR)
end
# @group Private instance methods
+ # @return [Nanoc3::RulesCollection] The collection of rules to be used
+ # for compiling this site
+ def rules_collection
+ Nanoc3::RulesCollection.new(self)
+ end
+ memoize :rules_collection
+
# Load the helper data that is used for compiling the site.
#
# @api private
#
# @return [void]
def load
- return if @loaded
- @loaded = true
+ return if @loaded || @loading
+ @loading = true
# Load site if necessary
- @site.load
+ site.load
# Preprocess
- load_rules
+ rules_collection.load
preprocess
site.setup_child_parent_links
build_reps
route_reps
# Load auxiliary stores
stores.each { |s| s.load }
# Determine which reps need to be recompiled
- dependency_tracker.propagate_outdatedness
forget_dependencies_if_outdated(items)
+
+ @loaded = true
+ rescue => e
+ unload
+ raise e
+ ensure
+ @loading = false
end
- # Store the modified helper data used for compiling the site.
+ # Undoes the effects of {#load}. Used when {#load} raises an exception.
#
# @api private
#
# @return [void]
- def store
- stores.each { |s| s.store }
- end
+ def unload
+ return if @unloading
+ @unloading = true
- # Returns the dependency tracker for this site, creating it first if it
- # does not yet exist.
- #
- # @api private
- #
- # @return [Nanoc3::DependencyTracker] The dependency tracker for this site
- def dependency_tracker
- @dependency_tracker ||= begin
- dt = Nanoc3::DependencyTracker.new(@site.items + @site.layouts)
- dt.compiler = self
- dt
- end
- end
+ stores.each { |s| s.unload }
- # Finds the first matching compilation rule for the given item
- # representation.
- #
- # @api private
- #
- # @param [Nanoc3::ItemRep] rep The item rep for which to fetch the rule
- #
- # @return [Nanoc3::Rule, nil] The compilation rule for the given item rep,
- # or nil if no rules have been found
- def compilation_rule_for(rep)
- @item_compilation_rules.find do |rule|
- rule.applicable_to?(rep.item) && rule.rep_name == rep.name
- end
- end
+ @stack = []
- # Finds the first matching routing rule for the given item representation.
- #
- # @api private
- #
- # @param [Nanoc3::ItemRep] rep The item rep for which to fetch the rule
- #
- # @return [Nanoc3::Rule, nil] The routing rule for the given item rep, or
- # nil if no rules have been found
- def routing_rule_for(rep)
- @item_routing_rules.find do |rule|
- rule.applicable_to?(rep.item) && rule.rep_name == rep.name
- end
- end
+ items.each { |item| item.reps.clear }
+ site.teardown_child_parent_links
+ rules_collection.unload
- # Returns the list of routing rules that can be applied to the given item
- # representation. For each snapshot, the first matching rule will be
- # returned. The result is a hash containing the corresponding rule for
- # each snapshot.
- #
- # @api private
- #
- # @return [Hash<Symbol, Nanoc3::Rule>] The routing rules for the given rep
- def routing_rules_for(rep)
- rules = {}
- @item_routing_rules.each do |rule|
- next if !rule.applicable_to?(rep.item)
- next if rule.rep_name != rep.name
- next if rules.has_key?(rule.snapshot_name)
+ site.unload
- rules[rule.snapshot_name] = rule
- end
- rules
+ @loaded = false
+ @unloading = false
end
- # Finds the filter name and arguments to use for the given layout.
+ # Store the modified helper data used for compiling the site.
#
# @api private
#
- # @param [Nanoc3::Layout] layout The layout for which to fetch the filter.
- #
- # @return [Array, nil] A tuple containing the filter name and the filter
- # arguments for the given layout.
- def filter_for_layout(layout)
- @layout_filter_mapping.each_pair do |layout_identifier, filter_name_and_args|
- return filter_name_and_args if layout.identifier =~ layout_identifier
+ # @return [void]
+ def store
+ # Calculate rule memory
+ (reps + layouts).each do |obj|
+ rule_memory_store[obj] = rule_memory_calculator[obj]
end
- nil
- end
- # @api private
- #
- # @return [Boolean] true if the object is outdated, false otherwise
- def outdated?(obj)
- outdatedness_checker.outdated?(obj)
- end
+ # Calculate checksums
+ self.objects.each do |obj|
+ checksum_store[obj] = obj.checksum
+ end
- # Returns the reason why the given object is outdated.
- #
- # @see Nanoc3::OutdatednessChecker#outdatedness_reason_for
- #
- # @api private
- def outdatedness_reason_for(obj)
- outdatedness_checker.outdatedness_reason_for(obj)
+ # Store
+ stores.each { |s| s.store }
end
- # Returns the Nanoc3::CompilerDSL that should be used for this site.
+ # Returns the dependency tracker for this site, creating it first if it
+ # does not yet exist.
#
# @api private
- def dsl
- @dsl ||= Nanoc3::CompilerDSL.new(self)
- end
-
- # Loads this site’s rules.
#
- # @api private
- def load_rules
- # Find rules file
- rules_filename = [ 'Rules', 'rules', 'Rules.rb', 'rules.rb' ].find { |f| File.file?(f) }
- raise Nanoc3::Errors::NoRulesFileFound.new if rules_filename.nil?
-
- # Get rule data
- @rules = File.read(rules_filename)
-
- # Load DSL
- dsl.instance_eval(@rules, "./#{rules_filename}")
+ # @return [Nanoc3::DependencyTracker] The dependency tracker for this site
+ def dependency_tracker
+ dt = Nanoc3::DependencyTracker.new(@site.items + @site.layouts)
+ dt.compiler = self
+ dt
end
+ memoize :dependency_tracker
# Runs the preprocessor.
#
# @api private
def preprocess
- preprocessor_context.instance_eval(&preprocessor) if preprocessor
+ return if rules_collection.preprocessor.nil?
+ preprocessor_context.instance_eval(&rules_collection.preprocessor)
end
# Returns all objects managed by the site (items, layouts, code snippets,
# site configuration and the rules).
#
# @api private
def objects
- # FIXME remove reference to rules
- site.items + site.layouts + site.code_snippets + [ site.config, self.rules_with_reference ]
+ site.items + site.layouts + site.code_snippets +
+ [ site.config, rules_collection ]
end
- # Returns the rules along with an unique reference (`:rules`) so that the
- # outdatedness checker can use them.
- #
- # @api private
- def rules_with_reference
- rules = @rules
- @rules_pseudo ||= begin
- pseudo = Object.new
- pseudo.instance_eval { @data = rules }
- def pseudo.reference ; :rules ; end
- def pseudo.data ; @data.inspect ; end
- pseudo
- end
- end
-
# Creates the representations of all items as defined by the compilation
# rules.
#
# @api private
def build_reps
- @site.items.each do |item|
+ items.each do |item|
# Find matching rules
- matching_rules = item_compilation_rules.select { |r| r.applicable_to?(item) }
+ matching_rules = rules_collection.item_compilation_rules_for(item)
raise Nanoc3::Errors::NoMatchingCompilationRuleFound.new(item) if matching_rules.empty?
# Create reps
rep_names = matching_rules.map { |r| r.rep_name }.uniq
rep_names.each do |rep_name|
@@ -315,14 +225,13 @@
# Determines the paths of all item representations.
#
# @api private
def route_reps
- reps = @site.items.map { |i| i.reps }.flatten
reps.each do |rep|
# Find matching rules
- rules = routing_rules_for(rep)
+ rules = rules_collection.routing_rules_for(rep)
raise Nanoc3::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, :compiler => self)
@@ -369,37 +278,46 @@
:config => site.config,
:site => site
})
end
+ # @return [Nanoc3::OutdatednessChecker] The outdatedness checker
+ def outdatedness_checker
+ Nanoc3::OutdatednessChecker.new(
+ :site => @site,
+ :checksum_store => checksum_store,
+ :dependency_tracker => dependency_tracker)
+ end
+ memoize :outdatedness_checker
+
private
+ # @return [Array<Nanoc3::Item>] The site’s items
def items
- @items ||= @site.items
+ @site.items
end
+ memoize :items
+ # @return [Array<Nanoc3::ItemRep>] The site’s item representations
def reps
- @reps ||= items.map { |i| i.reps }.flatten
+ items.map { |i| i.reps }.flatten
end
+ memoize :reps
+ # @return [Array<Nanoc3::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)
- require 'set'
-
- # Partition in outdated and non-outdated
- outdated_reps = Set.new
- skipped_reps = Set.new
- reps.each do |rep|
- target = (outdated?(rep) || dependency_tracker.outdated_due_to_dependencies?(rep.item)) ? outdated_reps : skipped_reps
- target.add(rep)
- end
-
- # Build graph for outdated reps
+ outdated_reps = Set.new(reps.select { |rep| outdatedness_checker.outdated?(rep) })
content_dependency_graph = Nanoc3::DirectedGraph.new(outdated_reps)
# Listen to processing start/stop
Nanoc3::NotificationCenter.on(:processing_started, self) { |obj| @stack.push(obj) }
Nanoc3::NotificationCenter.on(:processing_ended, self) { |obj| @stack.pop }
@@ -415,11 +333,10 @@
compile_rep(rep)
content_dependency_graph.delete_vertex(rep)
rescue Nanoc3::Errors::UnmetDependency => e
content_dependency_graph.add_edge(e.rep, rep)
unless content_dependency_graph.vertices.include?(e.rep)
- skipped_reps.delete(e.rep)
content_dependency_graph.add_vertex(e.rep)
end
end
end
@@ -444,17 +361,22 @@
def compile_rep(rep)
Nanoc3::NotificationCenter.post(:compilation_started, rep)
Nanoc3::NotificationCenter.post(:processing_started, rep)
Nanoc3::NotificationCenter.post(:visit_started, rep.item)
- if !outdated?(rep) && !dependency_tracker.outdated_due_to_dependencies?(rep.item) && compiled_content_cache[rep]
+ # Calculate rule memory if we haven’t yet done do
+ rules_collection.new_rule_memory_for_rep(rep)
+
+ if !outdatedness_checker.outdated?(rep) && compiled_content_cache[rep]
+ # Reuse content
Nanoc3::NotificationCenter.post(:cached_content_used, rep)
rep.content = compiled_content_cache[rep]
else
+ # Recalculate content
rep.snapshot(:raw)
rep.snapshot(:pre, :final => false)
- compilation_rule_for(rep).apply_to(rep, :compiler => self)
+ rules_collection.compilation_rule_for(rep).apply_to(rep, :compiler => self)
rep.snapshot(:post) if rep.has_snapshot?(:post)
rep.snapshot(:last)
end
rep.compiled = true
@@ -475,44 +397,59 @@
# the dependencies
#
# @return [void]
def forget_dependencies_if_outdated(items)
items.each do |i|
- if i.reps.any? { |r| outdated?(r) } || dependency_tracker.outdated_due_to_dependencies?(i)
+ if i.reps.any? { |r| outdatedness_checker.outdated?(r) }
dependency_tracker.forget_dependencies_for(i)
end
end
end
# Returns a preprocessor context, creating one if none exists yet.
def preprocessor_context
- @preprocessor_context ||= Nanoc3::Context.new({
+ Nanoc3::Context.new({
:site => @site,
:config => @site.config,
:items => @site.items,
:layouts => @site.layouts
})
end
+ memoize :preprocessor_context
# @return [CompiledContentCache] The compiled content cache
def compiled_content_cache
- @compiled_content_cache ||= Nanoc3::CompiledContentCache.new
+ Nanoc3::CompiledContentCache.new
end
+ memoize :compiled_content_cache
# @return [ChecksumStore] The checksum store
def checksum_store
- @checksum_store ||= Nanoc3::ChecksumStore.new(:site => @site)
+ Nanoc3::ChecksumStore.new(:site => @site)
end
+ memoize :checksum_store
- # @return [Nanoc3::OutdatednessChecker] The outdatedness checker
- def outdatedness_checker
- @outdatedness_checker ||= Nanoc3::OutdatednessChecker.new(:site => @site, :checksum_store => checksum_store)
+ # @return [RuleMemoryStore] The rule memory store
+ def rule_memory_store
+ Nanoc3::RuleMemoryStore.new(:site => @site)
end
+ memoize :rule_memory_store
+ # @return [RuleMemoryCalculator] The rule memory calculator
+ def rule_memory_calculator
+ Nanoc3::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
- [ compiled_content_cache, checksum_store, dependency_tracker ]
+ [
+ checksum_store,
+ compiled_content_cache,
+ dependency_tracker,
+ rule_memory_store
+ ]
end
end
end