nanoc-core 4.11.12 → 4.11.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/lib/nanoc/core.rb +37 -0
  3. data/lib/nanoc/core/basic_item_rep_collection_view.rb +88 -0
  4. data/lib/nanoc/core/basic_item_rep_view.rb +83 -0
  5. data/lib/nanoc/core/basic_item_view.rb +54 -0
  6. data/lib/nanoc/core/checksummer.rb +2 -0
  7. data/lib/nanoc/core/compilation_item_rep_collection_view.rb +12 -0
  8. data/lib/nanoc/core/compilation_item_rep_view.rb +51 -0
  9. data/lib/nanoc/core/compilation_item_view.rb +47 -0
  10. data/lib/nanoc/core/compilation_phases/abstract.rb +48 -0
  11. data/lib/nanoc/core/compilation_phases/cache.rb +43 -0
  12. data/lib/nanoc/core/compilation_phases/mark_done.rb +23 -0
  13. data/lib/nanoc/core/compilation_phases/notify.rb +19 -0
  14. data/lib/nanoc/core/compilation_phases/recalculate.rb +49 -0
  15. data/lib/nanoc/core/compilation_phases/resume.rb +52 -0
  16. data/lib/nanoc/core/compilation_phases/write.rb +84 -0
  17. data/lib/nanoc/core/compilation_stages/build_reps.rb +36 -0
  18. data/lib/nanoc/core/compilation_stages/calculate_checksums.rb +42 -0
  19. data/lib/nanoc/core/compilation_stages/cleanup.rb +43 -0
  20. data/lib/nanoc/core/compilation_stages/compile_reps.rb +96 -0
  21. data/lib/nanoc/core/compilation_stages/determine_outdatedness.rb +49 -0
  22. data/lib/nanoc/core/compilation_stages/forget_outdated_dependencies.rb +20 -0
  23. data/lib/nanoc/core/compilation_stages/load_stores.rb +35 -0
  24. data/lib/nanoc/core/compilation_stages/postprocess.rb +21 -0
  25. data/lib/nanoc/core/compilation_stages/preprocess.rb +32 -0
  26. data/lib/nanoc/core/compilation_stages/prune.rb +30 -0
  27. data/lib/nanoc/core/compilation_stages/store_post_compilation_state.rb +20 -0
  28. data/lib/nanoc/core/compilation_stages/store_pre_compilation_state.rb +32 -0
  29. data/lib/nanoc/core/compiler.rb +214 -0
  30. data/lib/nanoc/core/compiler_loader.rb +48 -0
  31. data/lib/nanoc/core/config_loader.rb +95 -0
  32. data/lib/nanoc/core/config_view.rb +67 -0
  33. data/lib/nanoc/core/configuration.rb +2 -4
  34. data/lib/nanoc/core/document_view_mixin.rb +87 -0
  35. data/lib/nanoc/core/errors.rb +97 -0
  36. data/lib/nanoc/core/executor.rb +134 -0
  37. data/lib/nanoc/core/feature.rb +92 -0
  38. data/lib/nanoc/core/filter.rb +269 -0
  39. data/lib/nanoc/core/identifiable_collection_view.rb +111 -0
  40. data/lib/nanoc/core/item_collection_with_reps_view.rb +12 -0
  41. data/lib/nanoc/core/item_collection_without_reps_view.rb +12 -0
  42. data/lib/nanoc/core/item_rep_builder.rb +54 -0
  43. data/lib/nanoc/core/item_rep_selector.rb +67 -0
  44. data/lib/nanoc/core/item_rep_writer.rb +85 -0
  45. data/lib/nanoc/core/layout_collection_view.rb +12 -0
  46. data/lib/nanoc/core/layout_view.rb +9 -0
  47. data/lib/nanoc/core/mutable_config_view.rb +16 -0
  48. data/lib/nanoc/core/mutable_document_view_mixin.rb +60 -0
  49. data/lib/nanoc/core/mutable_identifiable_collection_view.rb +19 -0
  50. data/lib/nanoc/core/mutable_item_collection_view.rb +34 -0
  51. data/lib/nanoc/core/mutable_item_view.rb +9 -0
  52. data/lib/nanoc/core/mutable_layout_collection_view.rb +26 -0
  53. data/lib/nanoc/core/mutable_layout_view.rb +9 -0
  54. data/lib/nanoc/core/outdatedness_checker.rb +222 -0
  55. data/lib/nanoc/core/outdatedness_rules/attributes_modified.rb +41 -0
  56. data/lib/nanoc/core/outdatedness_rules/code_snippets_modified.rb +31 -0
  57. data/lib/nanoc/core/outdatedness_rules/content_modified.rb +21 -0
  58. data/lib/nanoc/core/outdatedness_rules/item_collection_extended.rb +20 -0
  59. data/lib/nanoc/core/outdatedness_rules/layout_collection_extended.rb +20 -0
  60. data/lib/nanoc/core/outdatedness_rules/not_written.rb +17 -0
  61. data/lib/nanoc/core/outdatedness_rules/rules_modified.rb +45 -0
  62. data/lib/nanoc/core/outdatedness_rules/uses_always_outdated_filter.rb +26 -0
  63. data/lib/nanoc/core/post_compile_item_collection_view.rb +12 -0
  64. data/lib/nanoc/core/post_compile_item_rep_collection_view.rb +12 -0
  65. data/lib/nanoc/core/post_compile_item_rep_view.rb +33 -0
  66. data/lib/nanoc/core/post_compile_item_view.rb +20 -0
  67. data/lib/nanoc/core/pruner.rb +119 -0
  68. data/lib/nanoc/core/site_loader.rb +102 -0
  69. data/lib/nanoc/core/trivial_error.rb +10 -0
  70. data/lib/nanoc/core/version.rb +1 -1
  71. data/lib/nanoc/core/view.rb +43 -0
  72. data/lib/nanoc/core/view_context_for_compilation.rb +6 -6
  73. metadata +95 -2
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class ItemCollectionWithRepsView < ::Nanoc::Core::IdentifiableCollectionView
6
+ # @api private
7
+ def view_class
8
+ Nanoc::Core::CompilationItemView
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class ItemCollectionWithoutRepsView < ::Nanoc::Core::IdentifiableCollectionView
6
+ # @api private
7
+ def view_class
8
+ Nanoc::Core::BasicItemView
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ # @api private
6
+ class ItemRepBuilder
7
+ include Nanoc::Core::ContractsSupport
8
+
9
+ attr_reader :reps
10
+
11
+ contract Nanoc::Core::Site, Nanoc::Core::ActionProvider, Nanoc::Core::ItemRepRepo => C::Any
12
+ def initialize(site, action_provider, reps)
13
+ @site = site
14
+ @action_provider = action_provider
15
+ @reps = reps
16
+ end
17
+
18
+ def run
19
+ @site.items.each do |item|
20
+ @action_provider.rep_names_for(item).each do |rep_name|
21
+ @reps << Nanoc::Core::ItemRep.new(item, rep_name)
22
+ end
23
+ end
24
+
25
+ action_sequences = Nanoc::Core::ItemRepRouter.new(@reps, @action_provider, @site).run
26
+
27
+ @reps.each do |rep|
28
+ rep.snapshot_defs = self.class.snapshot_defs_for(action_sequences[rep])
29
+ end
30
+
31
+ action_sequences
32
+ end
33
+
34
+ contract Nanoc::Core::ActionSequence => C::ArrayOf[Nanoc::Core::SnapshotDef]
35
+ def self.snapshot_defs_for(action_sequence)
36
+ is_binary = action_sequence.item_rep.item.content.binary?
37
+ snapshot_defs = []
38
+
39
+ action_sequence.each do |action|
40
+ case action
41
+ when Nanoc::Core::ProcessingActions::Snapshot
42
+ action.snapshot_names.each do |snapshot_name|
43
+ snapshot_defs << Nanoc::Core::SnapshotDef.new(snapshot_name, binary: is_binary)
44
+ end
45
+ when Nanoc::Core::ProcessingActions::Filter
46
+ is_binary = Nanoc::Core::Filter.named!(action.filter_name).to_binary?
47
+ end
48
+ end
49
+
50
+ snapshot_defs
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ # Yields item reps to compile.
6
+ class ItemRepSelector
7
+ def initialize(reps)
8
+ @reps = reps
9
+ end
10
+
11
+ class MicroGraph
12
+ def initialize(reps)
13
+ @reps = Set.new(reps)
14
+ @stack = []
15
+ end
16
+
17
+ def next
18
+ if @stack.any?
19
+ @stack.last
20
+ elsif @reps.any?
21
+ @reps.each { |rep| break rep }.tap do |rep|
22
+ @reps.delete(rep)
23
+ @stack.push(rep)
24
+ end
25
+ else
26
+ nil
27
+ end
28
+ end
29
+
30
+ def mark_ok
31
+ @stack.pop
32
+ end
33
+
34
+ def mark_failed(dep)
35
+ if @stack.include?(dep)
36
+ raise Nanoc::Core::Errors::DependencyCycle.new(@stack + [dep])
37
+ end
38
+
39
+ @reps.delete(dep)
40
+ @stack.push(dep)
41
+ end
42
+ end
43
+
44
+ def each
45
+ mg = MicroGraph.new(@reps)
46
+
47
+ loop do
48
+ rep = mg.next
49
+ break if rep.nil?
50
+
51
+ begin
52
+ yield(rep)
53
+ mg.mark_ok
54
+ rescue => e
55
+ actual_error = e.is_a?(Nanoc::Core::Errors::CompilationError) ? e.unwrap : e
56
+
57
+ if actual_error.is_a?(Nanoc::Core::Errors::UnmetDependency)
58
+ mg.mark_failed(actual_error.rep)
59
+ else
60
+ raise(e)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class ItemRepWriter
6
+ include Nanoc::Core::ContractsSupport
7
+ include Nanoc::Core::Assertions::Mixin
8
+
9
+ TMP_TEXT_ITEMS_DIR = 'text_items'
10
+
11
+ def write_all(item_rep, compiled_content_store)
12
+ written_paths = Set.new
13
+
14
+ item_rep.snapshot_defs.map(&:name).each do |snapshot_name|
15
+ write(item_rep, compiled_content_store, snapshot_name, written_paths)
16
+ end
17
+ end
18
+
19
+ def write(item_rep, compiled_content_store, snapshot_name, written_paths)
20
+ item_rep.raw_paths.fetch(snapshot_name, []).each do |raw_path|
21
+ write_single(item_rep, compiled_content_store, snapshot_name, raw_path, written_paths)
22
+ end
23
+ end
24
+
25
+ def write_single(item_rep, compiled_content_store, snapshot_name, raw_path, written_paths)
26
+ assert Nanoc::Core::Assertions::PathIsAbsolute.new(path: raw_path)
27
+
28
+ # Don’t write twice
29
+ # TODO: test written_paths behavior
30
+ return if written_paths.include?(raw_path)
31
+
32
+ written_paths << raw_path
33
+
34
+ # Create parent directory
35
+ FileUtils.mkdir_p(File.dirname(raw_path))
36
+
37
+ # Check if file will be created
38
+ is_created = !File.file?(raw_path)
39
+
40
+ # Notify
41
+ Nanoc::Core::NotificationCenter.post(
42
+ :rep_write_started, item_rep, raw_path
43
+ )
44
+
45
+ content = compiled_content_store.get(item_rep, snapshot_name)
46
+ if content.binary?
47
+ temp_path = content.filename
48
+ else
49
+ temp_path = temp_filename
50
+ File.write(temp_path, content.string)
51
+ end
52
+
53
+ # Check whether content was modified
54
+ is_modified = is_created || !FileUtils.identical?(raw_path, temp_path)
55
+
56
+ # Notify ready for diff generation
57
+ if !is_created && is_modified && !content.binary?
58
+ Nanoc::Core::NotificationCenter.post(
59
+ :rep_ready_for_diff, raw_path, File.read(raw_path, encoding: 'UTF-8'), content.string
60
+ )
61
+ end
62
+
63
+ # Write
64
+ if is_modified
65
+ begin
66
+ FileUtils.ln(temp_path, raw_path, force: true)
67
+ rescue Errno::EXDEV, Errno::EACCES
68
+ FileUtils.cp(temp_path, raw_path)
69
+ end
70
+ end
71
+
72
+ item_rep.modified = is_modified
73
+
74
+ # Notify
75
+ Nanoc::Core::NotificationCenter.post(
76
+ :rep_write_ended, item_rep, content.binary?, raw_path, is_created, is_modified
77
+ )
78
+ end
79
+
80
+ def temp_filename
81
+ Nanoc::Core::TempFilenameFactory.instance.create(TMP_TEXT_ITEMS_DIR)
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class LayoutCollectionView < ::Nanoc::Core::IdentifiableCollectionView
6
+ # @api private
7
+ def view_class
8
+ Nanoc::Core::LayoutView
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class LayoutView < ::Nanoc::Core::View
6
+ include Nanoc::Core::DocumentViewMixin
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class MutableConfigView < Nanoc::Core::ConfigView
6
+ # Sets the value for the given attribute.
7
+ #
8
+ # @param [Symbol] key
9
+ #
10
+ # @see Hash#[]=
11
+ def []=(key, value)
12
+ @config[key] = value
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ module MutableDocumentViewMixin
6
+ # @api private
7
+ class DisallowedAttributeValueError < Nanoc::Core::Error
8
+ attr_reader :value
9
+
10
+ def initialize(value)
11
+ @value = value
12
+ end
13
+
14
+ def message
15
+ "The #{value.class} cannot be stored inside an attribute. Store its identifier instead."
16
+ end
17
+ end
18
+
19
+ def raw_content=(arg)
20
+ _unwrap.content = Nanoc::Core::Content.create(arg)
21
+ end
22
+
23
+ # Sets the value for the given attribute.
24
+ #
25
+ # @param [Symbol] key
26
+ #
27
+ # @see Hash#[]=
28
+ def []=(key, value)
29
+ disallowed_value_classes = Set.new([
30
+ Nanoc::Core::Item,
31
+ Nanoc::Core::Layout,
32
+ Nanoc::Core::CompilationItemView,
33
+ Nanoc::Core::LayoutView,
34
+ ])
35
+ if disallowed_value_classes.include?(value.class)
36
+ raise DisallowedAttributeValueError.new(value)
37
+ end
38
+
39
+ _unwrap.set_attribute(key, value)
40
+ end
41
+
42
+ # Sets the identifier to the given argument.
43
+ #
44
+ # @param [String, Nanoc::Core::Identifier] arg
45
+ def identifier=(arg)
46
+ _unwrap.identifier = Nanoc::Core::Identifier.from(arg)
47
+ end
48
+
49
+ # Updates the attributes based on the given hash.
50
+ #
51
+ # @param [Hash] hash
52
+ #
53
+ # @return [self]
54
+ def update_attributes(hash)
55
+ hash.each { |k, v| _unwrap.set_attribute(k, v) }
56
+ self
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class MutableIdentifiableCollectionView < Nanoc::Core::IdentifiableCollectionView
6
+ # Deletes every object for which the block evaluates to true.
7
+ #
8
+ # @yieldparam [#identifier] object
9
+ #
10
+ # @yieldreturn [Boolean]
11
+ #
12
+ # @return [self]
13
+ def delete_if(&_block)
14
+ @objects = @objects.reject { |o| yield(view_class.new(o, @context)) }
15
+ self
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class MutableItemCollectionView < Nanoc::Core::MutableIdentifiableCollectionView
6
+ # @api private
7
+ def view_class
8
+ Nanoc::Core::MutableItemView
9
+ end
10
+
11
+ # Creates a new item and adds it to the site’s collection of items.
12
+ #
13
+ # @param [String] content The uncompiled item content (if it is a textual
14
+ # item) or the path to the filename containing the content (if it is a
15
+ # binary item).
16
+ #
17
+ # @param [Hash] attributes A hash containing this item's attributes.
18
+ #
19
+ # @param [Nanoc::Core::Identifier, String] identifier This item's identifier.
20
+ #
21
+ # @param [Boolean] binary Whether or not this item is binary
22
+ #
23
+ # @param [String] filename Absolute path to the file
24
+ # containing this content (if any)
25
+ #
26
+ # @return [self]
27
+ def create(content, attributes, identifier, binary: false, filename: nil)
28
+ content = Nanoc::Core::Content.create(content, binary: binary, filename: filename)
29
+ @objects = @objects.add(Nanoc::Core::Item.new(content, attributes, identifier))
30
+ self
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class MutableItemView < Nanoc::Core::BasicItemView
6
+ include Nanoc::Core::MutableDocumentViewMixin
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class MutableLayoutCollectionView < Nanoc::Core::MutableIdentifiableCollectionView
6
+ # @api private
7
+ def view_class
8
+ Nanoc::Core::MutableLayoutView
9
+ end
10
+
11
+ # Creates a new layout and adds it to the site’s collection of layouts.
12
+ #
13
+ # @param [String] content The layout content.
14
+ #
15
+ # @param [Hash] attributes A hash containing this layout's attributes.
16
+ #
17
+ # @param [Nanoc::Core::Identifier, String] identifier This layout's identifier.
18
+ #
19
+ # @return [self]
20
+ def create(content, attributes, identifier)
21
+ @objects = @objects.add(Nanoc::Core::Layout.new(content, attributes, identifier))
22
+ self
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class MutableLayoutView < Nanoc::Core::LayoutView
6
+ include Nanoc::Core::MutableDocumentViewMixin
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,222 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ # Responsible for determining whether an item or a layout is outdated.
6
+ #
7
+ # @api private
8
+ class OutdatednessChecker
9
+ class Basic
10
+ DDMemoize.activate(self)
11
+
12
+ include Nanoc::Core::ContractsSupport
13
+
14
+ Rules = Nanoc::Core::OutdatednessRules
15
+
16
+ RULES_FOR_ITEM_REP =
17
+ [
18
+ Rules::RulesModified,
19
+ Rules::ContentModified,
20
+ Rules::AttributesModified,
21
+ Rules::NotWritten,
22
+ Rules::CodeSnippetsModified,
23
+ Rules::UsesAlwaysOutdatedFilter,
24
+ ].freeze
25
+
26
+ RULES_FOR_LAYOUT =
27
+ [
28
+ Rules::RulesModified,
29
+ Rules::ContentModified,
30
+ Rules::AttributesModified,
31
+ Rules::UsesAlwaysOutdatedFilter,
32
+ ].freeze
33
+
34
+ RULES_FOR_CONFIG =
35
+ [
36
+ Rules::AttributesModified,
37
+ ].freeze
38
+
39
+ RULES_FOR_ITEM_COLLECTION =
40
+ [
41
+ Rules::ItemCollectionExtended,
42
+ ].freeze
43
+
44
+ RULES_FOR_LAYOUT_COLLECTION =
45
+ [
46
+ Rules::LayoutCollectionExtended,
47
+ ].freeze
48
+
49
+ C_OBJ_MAYBE_REP = C::Or[Nanoc::Core::Item, Nanoc::Core::ItemRep, Nanoc::Core::Configuration, Nanoc::Core::Layout, Nanoc::Core::ItemCollection, Nanoc::Core::LayoutCollection]
50
+
51
+ contract C::KeywordArgs[outdatedness_checker: OutdatednessChecker, reps: Nanoc::Core::ItemRepRepo] => C::Any
52
+ def initialize(outdatedness_checker:, reps:)
53
+ @outdatedness_checker = outdatedness_checker
54
+ @reps = reps
55
+ end
56
+
57
+ contract C_OBJ_MAYBE_REP => C::Maybe[Nanoc::Core::OutdatednessStatus]
58
+ memoized def outdatedness_status_for(obj)
59
+ case obj
60
+ when Nanoc::Core::ItemRep
61
+ apply_rules(RULES_FOR_ITEM_REP, obj)
62
+ when Nanoc::Core::Item
63
+ apply_rules_multi(RULES_FOR_ITEM_REP, @reps[obj])
64
+ when Nanoc::Core::Layout
65
+ apply_rules(RULES_FOR_LAYOUT, obj)
66
+ when Nanoc::Core::Configuration
67
+ apply_rules(RULES_FOR_CONFIG, obj)
68
+ when Nanoc::Core::ItemCollection
69
+ apply_rules(RULES_FOR_ITEM_COLLECTION, obj)
70
+ when Nanoc::Core::LayoutCollection
71
+ apply_rules(RULES_FOR_LAYOUT_COLLECTION, obj)
72
+ else
73
+ raise Nanoc::Core::Errors::InternalInconsistency, "do not know how to check outdatedness of #{obj.inspect}"
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ contract C::ArrayOf[Class], C_OBJ_MAYBE_REP, Nanoc::Core::OutdatednessStatus => C::Maybe[Nanoc::Core::OutdatednessStatus]
80
+ def apply_rules(rules, obj, status = Nanoc::Core::OutdatednessStatus.new)
81
+ rules.inject(status) do |acc, rule|
82
+ if !acc.useful_to_apply?(rule)
83
+ acc
84
+ else
85
+ reason = rule.instance.call(obj, @outdatedness_checker)
86
+ if reason
87
+ acc.update(reason)
88
+ else
89
+ acc
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ contract C::ArrayOf[Class], C::ArrayOf[C_OBJ_MAYBE_REP] => C::Maybe[Nanoc::Core::OutdatednessStatus]
96
+ def apply_rules_multi(rules, objs)
97
+ objs.inject(Nanoc::Core::OutdatednessStatus.new) { |acc, elem| apply_rules(rules, elem, acc) }
98
+ end
99
+ end
100
+
101
+ DDMemoize.activate(self)
102
+
103
+ include Nanoc::Core::ContractsSupport
104
+
105
+ attr_reader :checksum_store
106
+ attr_reader :checksums
107
+ attr_reader :dependency_store
108
+ attr_reader :action_sequence_store
109
+ attr_reader :action_sequences
110
+ attr_reader :site
111
+
112
+ Reasons = Nanoc::Core::OutdatednessReasons
113
+
114
+ C_OBJ = C::Or[Nanoc::Core::Item, Nanoc::Core::ItemRep, Nanoc::Core::Configuration, Nanoc::Core::Layout, Nanoc::Core::ItemCollection]
115
+ C_ITEM_OR_REP = C::Or[Nanoc::Core::Item, Nanoc::Core::ItemRep]
116
+ C_ACTION_SEQUENCES = C::HashOf[C_OBJ => Nanoc::Core::ActionSequence]
117
+
118
+ contract C::KeywordArgs[site: Nanoc::Core::Site, checksum_store: Nanoc::Core::ChecksumStore, checksums: Nanoc::Core::ChecksumCollection, dependency_store: Nanoc::Core::DependencyStore, action_sequence_store: Nanoc::Core::ActionSequenceStore, action_sequences: C_ACTION_SEQUENCES, reps: Nanoc::Core::ItemRepRepo] => C::Any
119
+ def initialize(site:, checksum_store:, checksums:, dependency_store:, action_sequence_store:, action_sequences:, reps:)
120
+ @site = site
121
+ @checksum_store = checksum_store
122
+ @checksums = checksums
123
+ @dependency_store = dependency_store
124
+ @action_sequence_store = action_sequence_store
125
+ @action_sequences = action_sequences
126
+ @reps = reps
127
+
128
+ @objects_outdated_due_to_dependencies = {}
129
+ end
130
+
131
+ def action_sequence_for(rep)
132
+ @action_sequences.fetch(rep)
133
+ end
134
+
135
+ contract C_OBJ => C::Bool
136
+ def outdated?(obj)
137
+ outdatedness_reasons_for(obj).any?
138
+ end
139
+
140
+ contract C_OBJ => C::IterOf[Reasons::Generic]
141
+ def outdatedness_reasons_for(obj)
142
+ reasons = basic.outdatedness_status_for(obj).reasons
143
+ if reasons.any?
144
+ reasons
145
+ elsif outdated_due_to_dependencies?(obj)
146
+ [Reasons::DependenciesOutdated]
147
+ else
148
+ []
149
+ end
150
+ end
151
+
152
+ private
153
+
154
+ contract C::None => Basic
155
+ def basic
156
+ @_basic ||= Basic.new(outdatedness_checker: self, reps: @reps)
157
+ end
158
+
159
+ contract C_OBJ, Hamster::Set => C::Bool
160
+ def outdated_due_to_dependencies?(obj, processed = Hamster::Set.new)
161
+ # Convert from rep to item if necessary
162
+ obj = obj.item if obj.is_a?(Nanoc::Core::ItemRep)
163
+
164
+ # Only items can have dependencies
165
+ return false unless obj.is_a?(Nanoc::Core::Item)
166
+
167
+ # Get from cache
168
+ if @objects_outdated_due_to_dependencies.key?(obj)
169
+ return @objects_outdated_due_to_dependencies[obj]
170
+ end
171
+
172
+ # Check processed
173
+ # Don’t return true; the false will be or’ed into a true if there
174
+ # really is a dependency that is causing outdatedness.
175
+ return false if processed.include?(obj)
176
+
177
+ # Calculate
178
+ is_outdated = dependency_store.dependencies_causing_outdatedness_of(obj).any? do |dep|
179
+ dependency_causes_outdatedness?(dep) ||
180
+ (dep.props.compiled_content? &&
181
+ outdated_due_to_dependencies?(dep.from, processed.merge([obj])))
182
+ end
183
+
184
+ # Cache
185
+ @objects_outdated_due_to_dependencies[obj] = is_outdated
186
+
187
+ # Done
188
+ is_outdated
189
+ end
190
+
191
+ contract Nanoc::Core::Dependency => C::Bool
192
+ def dependency_causes_outdatedness?(dependency)
193
+ return true if dependency.from.nil?
194
+
195
+ status = basic.outdatedness_status_for(dependency.from)
196
+
197
+ active = status.props.active & dependency.props.active
198
+ active.delete(:attributes) if attributes_unaffected?(status, dependency)
199
+ active.delete(:raw_content) if raw_content_unaffected?(status, dependency)
200
+
201
+ active.any?
202
+ end
203
+
204
+ def attributes_unaffected?(status, dependency)
205
+ reason = status.reasons.find { |r| r.is_a?(Nanoc::Core::OutdatednessReasons::AttributesModified) }
206
+ reason && dependency.props.attributes.is_a?(Enumerable) && (dependency.props.attributes & reason.attributes).empty?
207
+ end
208
+
209
+ def raw_content_unaffected?(status, dependency)
210
+ reason = status.reasons.find { |r| r.is_a?(Nanoc::Core::OutdatednessReasons::DocumentCollectionExtended) }
211
+ if reason.nil?
212
+ false
213
+ elsif !dependency.props.raw_content.is_a?(Enumerable)
214
+ false
215
+ else
216
+ patterns = dependency.props.raw_content.map { |r| Nanoc::Core::Pattern.from(r) }
217
+ patterns.none? { |pat| reason.objects.any? { |obj| pat.match?(obj.identifier) } }
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end