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