lib/nanoc/base/compilation/outdatedness_checker.rb in nanoc-4.4.3 vs lib/nanoc/base/compilation/outdatedness_checker.rb in nanoc-4.4.4

- old
+ new

@@ -1,17 +1,85 @@ module Nanoc::Int # Responsible for determining whether an item or a layout is outdated. # # @api private class OutdatednessChecker + class Basic + extend Nanoc::Int::Memoization + + include Nanoc::Int::ContractsSupport + + Rules = Nanoc::Int::OutdatednessRules + + RULES_FOR_ITEM_REP = + [ + Rules::RulesModified, + Rules::PathsModified, + Rules::ContentModified, + Rules::AttributesModified, + Rules::NotWritten, + Rules::CodeSnippetsModified, + Rules::ConfigurationModified, + ].freeze + + RULES_FOR_LAYOUT = + [ + Rules::RulesModified, + Rules::ContentModified, + Rules::AttributesModified, + ].freeze + + contract C::KeywordArgs[outdatedness_checker: OutdatednessChecker, reps: Nanoc::Int::ItemRepRepo] => C::Any + def initialize(outdatedness_checker:, reps:) + @outdatedness_checker = outdatedness_checker + @reps = reps + end + + contract C::Or[Nanoc::Int::Item, Nanoc::Int::ItemRep, Nanoc::Int::Layout] => C::Maybe[OutdatednessStatus] + def outdatedness_status_for(obj) + case obj + when Nanoc::Int::ItemRep + apply_rules(RULES_FOR_ITEM_REP, obj) + when Nanoc::Int::Item + apply_rules_multi(RULES_FOR_ITEM_REP, @reps[obj]) + when Nanoc::Int::Layout + apply_rules(RULES_FOR_LAYOUT, obj) + else + raise "do not know how to check outdatedness of #{obj.inspect}" + end + end + memoize :outdatedness_status_for + + private + + contract C::ArrayOf[Class], C::Or[Nanoc::Int::Item, Nanoc::Int::ItemRep, Nanoc::Int::Layout], OutdatednessStatus => C::Maybe[OutdatednessStatus] + def apply_rules(rules, obj, status = OutdatednessStatus.new) + rules.inject(status) do |acc, rule| + if !acc.useful_to_apply?(rule) + acc + elsif rule.instance.apply(obj, @outdatedness_checker) + acc.update(rule.instance.reason) + else + acc + end + end + end + + contract C::ArrayOf[Class], C::ArrayOf[C::Or[Nanoc::Int::Item, Nanoc::Int::ItemRep, Nanoc::Int::Layout]] => C::Maybe[OutdatednessStatus] + def apply_rules_multi(rules, objs) + objs.inject(OutdatednessStatus.new) { |acc, elem| apply_rules(rules, elem, acc) } + end + end + extend Nanoc::Int::Memoization include Nanoc::Int::ContractsSupport attr_reader :checksum_store attr_reader :dependency_store attr_reader :rule_memory_store + attr_reader :action_provider attr_reader :site Reasons = Nanoc::Int::OutdatednessReasons # @param [Nanoc::Int::Site] site @@ -26,12 +94,10 @@ @dependency_store = dependency_store @rule_memory_store = rule_memory_store @action_provider = action_provider @reps = reps - @basic_outdatedness_reasons = {} - @outdatedness_reasons = {} @objects_outdated_due_to_dependencies = {} end contract C::Or[Nanoc::Int::Item, Nanoc::Int::ItemRep, Nanoc::Int::Layout] => C::Bool # Checks whether the given object is outdated and therefore needs to be @@ -62,91 +128,22 @@ end memoize :outdatedness_reason_for private - contract C::Or[Nanoc::Int::Item, Nanoc::Int::ItemRep, Nanoc::Int::Layout] => C::Bool - # Checks whether the given object is outdated and therefore needs to be - # recompiled. This method does not take dependencies into account; use - # {#outdated?} if you want to include dependencies in the outdatedness - # check. - # - # @param [Nanoc::Int::Item, Nanoc::Int::ItemRep, Nanoc::Int::Layout] obj The object - # whose outdatedness should be checked. - # - # @return [Boolean] true if the object is outdated, false otherwise - def basic_outdated?(obj) - !basic_outdatedness_reason_for(obj).nil? + contract C::None => Basic + def basic + @_basic ||= Basic.new(outdatedness_checker: self, reps: @reps) end contract C::Or[Nanoc::Int::Item, Nanoc::Int::ItemRep, Nanoc::Int::Layout] => C::Maybe[Reasons::Generic] - # Calculates the reason why the given object is outdated. This method does - # not take dependencies into account; use {#outdatedness_reason_for?} if - # you want to include dependencies in the outdatedness check. - # - # @param [Nanoc::Int::Item, Nanoc::Int::ItemRep, Nanoc::Int::Layout] obj The object - # whose outdatedness reason should be calculated. - # - # @return [Reasons::Generic, nil] The reason why the - # given object is outdated, or nil if the object is not outdated. def basic_outdatedness_reason_for(obj) - case obj - when Nanoc::Int::ItemRep - # Outdated if rules outdated - return Reasons::RulesModified if - rule_memory_differs_for(obj) - - # Outdated if checksums are missing or different - return Reasons::NotEnoughData unless checksums_available?(obj.item) - return Reasons::ContentModified unless content_checksums_identical?(obj.item) - return Reasons::AttributesModified unless attributes_checksums_identical?(obj.item) - - # Outdated if compiled file doesn't exist (yet) - return Reasons::NotWritten if obj.raw_path && !File.file?(obj.raw_path) - - # Outdated if code snippets outdated - return Reasons::CodeSnippetsModified if site.code_snippets.any? do |cs| - object_modified?(cs) - end - - # Outdated if configuration outdated - return Reasons::ConfigurationModified if object_modified?(site.config) - - # Not outdated - nil - when Nanoc::Int::Item - @reps[obj].lazy.map { |rep| basic_outdatedness_reason_for(rep) }.find { |s| s } - when Nanoc::Int::Layout - # Outdated if rules outdated - return Reasons::RulesModified if - rule_memory_differs_for(obj) - - # Outdated if checksums are missing or different - return Reasons::NotEnoughData unless checksums_available?(obj) - return Reasons::ContentModified unless content_checksums_identical?(obj) - return Reasons::AttributesModified unless attributes_checksums_identical?(obj) - - # Not outdated - nil - else - raise "do not know how to check outdatedness of #{obj.inspect}" - end + # FIXME: Stop using this; it is no longer accurate, as there can be >1 reasons + basic.outdatedness_status_for(obj).reasons.first end - memoize :basic_outdatedness_reason_for contract C::Or[Nanoc::Int::Item, Nanoc::Int::ItemRep, Nanoc::Int::Layout], Hamster::Set => C::Bool - # Checks whether the given object is outdated due to dependencies. - # - # @param [Nanoc::Int::Item, Nanoc::Int::ItemRep, Nanoc::Int::Layout] obj The object - # whose outdatedness should be checked. - # - # @param [Set] processed The collection of items that has been visited - # during this outdatedness check. This is used to prevent checks for - # items that (indirectly) depend on their own from looping - # indefinitely. It should not be necessary to pass this a custom value. - # - # @return [Boolean] true if the object is outdated, false otherwise def outdated_due_to_dependencies?(obj, processed = Hamster::Set.new) # Convert from rep to item if necessary obj = obj.item if obj.is_a?(Nanoc::Int::ItemRep) # Get from cache @@ -158,79 +155,27 @@ # Don’t return true; the false will be or’ed into a true if there # really is a dependency that is causing outdatedness. return false if processed.include?(obj) # Calculate - is_outdated = dependency_store.objects_causing_outdatedness_of(obj).any? do |other| - other.nil? || basic_outdated?(other) || outdated_due_to_dependencies?(other, processed.merge([obj])) + is_outdated = dependency_store.dependencies_causing_outdatedness_of(obj).any? do |dep| + dependency_causes_outdatedness?(dep) || + (dep.props.compiled_content? && + outdated_due_to_dependencies?(dep.from, processed.merge([obj]))) end # Cache @objects_outdated_due_to_dependencies[obj] = is_outdated # Done is_outdated end - contract C::Or[Nanoc::Int::Item, Nanoc::Int::ItemRep, Nanoc::Int::Layout] => C::Bool - # @param [Nanoc::Int::ItemRep, Nanoc::Int::Layout] obj The layout or item - # representation to check the rule memory for - # - # @return [Boolean] true if the rule memory for the given item - # represenation has changed, false otherwise - def rule_memory_differs_for(obj) - !rule_memory_store[obj].eql?(@action_provider.memory_for(obj).serialize) - end - memoize :rule_memory_differs_for + contract Nanoc::Int::Dependency => C::Bool + def dependency_causes_outdatedness?(dependency) + return true if dependency.from.nil? - contract C::Any => String - # @param obj The object to create a checksum for - # - # @return [String] The digest - def calc_checksum(obj) - Nanoc::Int::Checksummer.calc(obj) + status = basic.outdatedness_status_for(dependency.from) + (status.props.active & dependency.props.active).any? end - memoize :calc_checksum - - contract C::Any => C::Bool - # @param obj - # - # @return [Boolean] false if either the new or the old checksum for the - # given object is not available, true if both checksums are available - def checksums_available?(obj) - checksum_store[obj] && calc_checksum(obj) ? true : false - end - memoize :checksums_available? - - contract C::Any => C::Bool - # @param obj - # - # @return [Boolean] false if the old and new checksums for the given - # object differ, true if they are identical - def checksums_identical?(obj) - checksum_store[obj] == calc_checksum(obj) - end - memoize :checksums_identical? - - contract C::Or[Nanoc::Int::Item, Nanoc::Int::Layout] => C::Bool - def content_checksums_identical?(obj) - checksum_store.content_checksum_for(obj) == Nanoc::Int::Checksummer.calc_for_content_of(obj) - end - memoize :content_checksums_identical? - - contract C::Or[Nanoc::Int::Item, Nanoc::Int::Layout] => C::Bool - def attributes_checksums_identical?(obj) - checksum_store.attributes_checksum_for(obj) == Nanoc::Int::Checksummer.calc_for_attributes_of(obj) - end - memoize :attributes_checksums_identical? - - contract C::Any => C::Bool - # @param obj - # - # @return [Boolean] true if the old and new checksums for the given object - # are available and identical, false otherwise - def object_modified?(obj) - !checksums_available?(obj) || !checksums_identical?(obj) - end - memoize :object_modified? end end