nanoc-core 4.11.12 → 4.11.13

Sign up to get free protection for your applications and to get access to all the features.
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