nanoc-core 4.12.10 → 4.12.12

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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/lib/nanoc/core/action_sequence.rb +1 -4
  3. data/lib/nanoc/core/action_sequence_builder.rb +8 -10
  4. data/lib/nanoc/core/basic_item_view.rb +1 -1
  5. data/lib/nanoc/core/basic_outdatedness_checker.rb +135 -0
  6. data/lib/nanoc/core/compilation_stages/determine_outdatedness.rb +6 -1
  7. data/lib/nanoc/core/compilation_stages/load_stores.rb +5 -12
  8. data/lib/nanoc/core/configuration.rb +15 -0
  9. data/lib/nanoc/core/errors.rb +1 -4
  10. data/lib/nanoc/core/executor.rb +2 -1
  11. data/lib/nanoc/core/identifiable_collection.rb +16 -33
  12. data/lib/nanoc/core/identifiable_collection_view.rb +54 -10
  13. data/lib/nanoc/core/item_collection.rb +0 -6
  14. data/lib/nanoc/core/item_rep.rb +18 -0
  15. data/lib/nanoc/core/item_rep_builder.rb +4 -4
  16. data/lib/nanoc/core/item_rep_selector.rb +89 -22
  17. data/lib/nanoc/core/layout_collection.rb +0 -6
  18. data/lib/nanoc/core/outdatedness_checker.rb +57 -119
  19. data/lib/nanoc/core/outdatedness_rules/attributes_modified.rb +6 -6
  20. data/lib/nanoc/core/outdatedness_rules/code_snippets_modified.rb +6 -6
  21. data/lib/nanoc/core/outdatedness_rules/content_modified.rb +3 -3
  22. data/lib/nanoc/core/outdatedness_rules/item_added.rb +3 -3
  23. data/lib/nanoc/core/outdatedness_rules/layout_added.rb +3 -3
  24. data/lib/nanoc/core/outdatedness_rules/not_written.rb +9 -9
  25. data/lib/nanoc/core/outdatedness_rules/rules_modified.rb +12 -10
  26. data/lib/nanoc/core/outdatedness_rules/uses_always_outdated_filter.rb +2 -2
  27. data/lib/nanoc/core/store.rb +56 -23
  28. data/lib/nanoc/core/version.rb +1 -1
  29. data/lib/nanoc/core.rb +8 -0
  30. metadata +4 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3365fa09f4ff20a16df20f260562fca8c64342098ba70b4547dc38580204ea2e
4
- data.tar.gz: 132a480e5593d19bb6c1802f6f740e3ed1d081eb88f0178fbde311d9a4e52cc0
3
+ metadata.gz: 5f5cba2759d0a1a3fed3b3ecd60b4d48fe74e4e47d9855f585731a52436b407c
4
+ data.tar.gz: cbe6128977455e8109c3991059521a6f97053eda60eccaa4597637c680f89a00
5
5
  SHA512:
6
- metadata.gz: 5c0190dbbad1225882d5b76891e80f45cf5e02238c6f6418071d9e8968e82cdd0263099eb5a71963b179238fb57a86f53ef7f7677592299c61cc6197be6b5dd9
7
- data.tar.gz: 13424fbb1cc81c95e471a8ba99f172d24b6f33b1e9220a50773ed32554f5577622db7d63825734b04254e33c624b2d382b16ced1628952f5cee7e6429d96e57a
6
+ metadata.gz: 488a4b2b75e2a0a49f1a66f810003f5f11961e156013852cd97e1495bc1b66a0906b7efe1816a760127ed4759cd8926426d0dccc9776d12bb159ac15be3eabc6
7
+ data.tar.gz: 32cc8224e2aaf4abff07af2fb0401211f927bfa1e057c3641acdf043097df4554f4a70142cfcbac37f791f54dfefd196d10e0f48c7637d2692a94134b4850658
@@ -7,11 +7,9 @@ module Nanoc
7
7
  include Enumerable
8
8
  prepend MemoWise
9
9
 
10
- attr_reader :item_rep
11
10
  attr_reader :actions
12
11
 
13
- def initialize(item_rep, actions: [])
14
- @item_rep = item_rep
12
+ def initialize(actions: [])
15
13
  @actions = actions
16
14
  end
17
15
 
@@ -54,7 +52,6 @@ module Nanoc
54
52
  # contract C::Func[Nanoc::Core::ProcessingAction => C::Any] => self
55
53
  def map(&block)
56
54
  self.class.new(
57
- @item_rep,
58
55
  actions: @actions.map(&block),
59
56
  )
60
57
  end
@@ -15,14 +15,13 @@ module Nanoc
15
15
  end
16
16
  end
17
17
 
18
- def self.build(rep)
19
- builder = new(rep)
18
+ def self.build
19
+ builder = new
20
20
  yield(builder)
21
21
  builder.action_sequence
22
22
  end
23
23
 
24
- def initialize(item_rep)
25
- @item_rep = item_rep
24
+ def initialize
26
25
  @actions = []
27
26
  end
28
27
 
@@ -38,24 +37,23 @@ module Nanoc
38
37
  self
39
38
  end
40
39
 
41
- contract Symbol, C::Maybe[String] => self
42
- def add_snapshot(snapshot_name, path)
43
- will_add_snapshot(snapshot_name)
40
+ def add_snapshot(snapshot_name, path, rep)
41
+ will_add_snapshot(snapshot_name, rep)
44
42
  @actions << Nanoc::Core::ProcessingActions::Snapshot.new([snapshot_name], path ? [path] : [])
45
43
  self
46
44
  end
47
45
 
48
46
  contract C::None => Nanoc::Core::ActionSequence
49
47
  def action_sequence
50
- Nanoc::Core::ActionSequence.new(@item_rep, actions: @actions)
48
+ Nanoc::Core::ActionSequence.new(actions: @actions)
51
49
  end
52
50
 
53
51
  private
54
52
 
55
- def will_add_snapshot(name)
53
+ def will_add_snapshot(name, rep)
56
54
  @_snapshot_names ||= Set.new
57
55
  if @_snapshot_names.include?(name)
58
- raise CannotCreateMultipleSnapshotsWithSameNameError.new(@item_rep, name)
56
+ raise CannotCreateMultipleSnapshotsWithSameNameError.new(rep, name)
59
57
  else
60
58
  @_snapshot_names << name
61
59
  end
@@ -34,7 +34,7 @@ module Nanoc
34
34
  parent_identifier = '/' + _unwrap.identifier.components[0..-2].join('/') + '/'
35
35
  parent_identifier = '/' if parent_identifier == '//'
36
36
 
37
- parent = @context.items[parent_identifier]
37
+ parent = @context.items.object_with_identifier(parent_identifier)
38
38
 
39
39
  parent && self.class.new(parent, @context)
40
40
  end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class BasicOutdatednessChecker
6
+ include Nanoc::Core::ContractsSupport
7
+
8
+ attr_reader :site
9
+ attr_reader :checksum_store
10
+ attr_reader :checksums
11
+ attr_reader :dependency_store
12
+ attr_reader :action_sequence_store
13
+ attr_reader :action_sequences
14
+
15
+ Rules = Nanoc::Core::OutdatednessRules
16
+
17
+ RULES_FOR_ITEM_REP =
18
+ [
19
+ Rules::ItemAdded,
20
+ Rules::RulesModified,
21
+ Rules::ContentModified,
22
+ Rules::AttributesModified,
23
+ Rules::NotWritten,
24
+ Rules::CodeSnippetsModified,
25
+ Rules::UsesAlwaysOutdatedFilter,
26
+ ].freeze
27
+
28
+ RULES_FOR_LAYOUT =
29
+ [
30
+ Rules::LayoutAdded,
31
+ Rules::RulesModified,
32
+ Rules::ContentModified,
33
+ Rules::AttributesModified,
34
+ Rules::UsesAlwaysOutdatedFilter,
35
+ ].freeze
36
+
37
+ RULES_FOR_CONFIG =
38
+ [
39
+ Rules::AttributesModified,
40
+ ].freeze
41
+
42
+ C_OBJ = C::Or[
43
+ Nanoc::Core::Item,
44
+ Nanoc::Core::ItemRep,
45
+ Nanoc::Core::Configuration,
46
+ Nanoc::Core::Layout,
47
+ Nanoc::Core::ItemCollection,
48
+ ]
49
+
50
+ C_OBJ_MAYBE_REP = C::Or[
51
+ Nanoc::Core::Item,
52
+ Nanoc::Core::ItemRep,
53
+ Nanoc::Core::Configuration,
54
+ Nanoc::Core::Layout,
55
+ Nanoc::Core::ItemCollection,
56
+ Nanoc::Core::LayoutCollection,
57
+ ]
58
+
59
+ C_ACTION_SEQUENCES = C::HashOf[C_OBJ => Nanoc::Core::ActionSequence]
60
+
61
+ contract C::KeywordArgs[
62
+ site: Nanoc::Core::Site,
63
+ checksum_store: Nanoc::Core::ChecksumStore,
64
+ checksums: Nanoc::Core::ChecksumCollection,
65
+ dependency_store: Nanoc::Core::DependencyStore,
66
+ action_sequence_store: Nanoc::Core::ActionSequenceStore,
67
+ action_sequences: C_ACTION_SEQUENCES,
68
+ reps: Nanoc::Core::ItemRepRepo,
69
+ ] => C::Any
70
+ def initialize(site:, checksum_store:, checksums:, dependency_store:, action_sequence_store:, action_sequences:, reps:)
71
+ @reps = reps
72
+ @site = site
73
+ @checksum_store = checksum_store
74
+ @checksums = checksums
75
+ @dependency_store = dependency_store
76
+ @action_sequence_store = action_sequence_store
77
+ @action_sequences = action_sequences
78
+
79
+ # Memoize
80
+ @_outdatedness_status_for = {}
81
+ end
82
+
83
+ contract C_OBJ_MAYBE_REP => C::Maybe[Nanoc::Core::OutdatednessStatus]
84
+ def outdatedness_status_for(obj)
85
+ # TODO: remove memoization (no longer needed)
86
+ @_outdatedness_status_for[obj] ||=
87
+ case obj
88
+ when Nanoc::Core::ItemRep
89
+ apply_rules(RULES_FOR_ITEM_REP, obj)
90
+ when Nanoc::Core::Item
91
+ apply_rules_multi(RULES_FOR_ITEM_REP, @reps[obj])
92
+ when Nanoc::Core::Layout
93
+ apply_rules(RULES_FOR_LAYOUT, obj)
94
+ when Nanoc::Core::Configuration
95
+ apply_rules(RULES_FOR_CONFIG, obj)
96
+ when Nanoc::Core::ItemCollection, Nanoc::Core::LayoutCollection
97
+ # Collections are never outdated. Objects inside them might be,
98
+ # however.
99
+ apply_rules([], obj)
100
+ else
101
+ raise Nanoc::Core::Errors::InternalInconsistency, "do not know how to check outdatedness of #{obj.inspect}"
102
+ end
103
+ end
104
+
105
+ def action_sequence_for(rep)
106
+ @action_sequences.fetch(rep)
107
+ end
108
+
109
+ private
110
+
111
+ contract C::ArrayOf[Class], C_OBJ_MAYBE_REP, Nanoc::Core::OutdatednessStatus => C::Maybe[Nanoc::Core::OutdatednessStatus]
112
+ def apply_rules(rules, obj, status = Nanoc::Core::OutdatednessStatus.new)
113
+ rules.inject(status) do |acc, rule|
114
+ if acc.useful_to_apply?(rule)
115
+ reason = rule.instance.call(obj, self)
116
+ if reason
117
+ acc.update(reason)
118
+ else
119
+ acc
120
+ end
121
+ else
122
+ acc
123
+ end
124
+ end
125
+ end
126
+
127
+ contract C::ArrayOf[Class], C::ArrayOf[C_OBJ_MAYBE_REP] => C::Maybe[Nanoc::Core::OutdatednessStatus]
128
+ def apply_rules_multi(rules, objs)
129
+ objs.inject(Nanoc::Core::OutdatednessStatus.new) do |acc, elem|
130
+ apply_rules(rules, elem, acc)
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -41,7 +41,12 @@ module Nanoc
41
41
  end
42
42
 
43
43
  def outdated?(rep)
44
- @outdatedness_store.include?(rep) || @outdatedness_checker.outdated?(rep)
44
+ if @outdatedness_store.include?(rep)
45
+ # We determined previously that this rep is outdated.
46
+ true
47
+ else
48
+ !@outdatedness_checker.outdatedness_reasons_for(rep).empty?
49
+ end
45
50
  end
46
51
  end
47
52
  end
@@ -16,18 +16,11 @@ module Nanoc
16
16
 
17
17
  contract C::None => C::Any
18
18
  def run
19
- load_store(@checksum_store)
20
- load_store(@compiled_content_cache)
21
- load_store(@dependency_store)
22
- load_store(@action_sequence_store)
23
- load_store(@outdatedness_store)
24
- end
25
-
26
- contract Nanoc::Core::Store => C::Any
27
- def load_store(store)
28
- Nanoc::Core::Instrumentor.call(:store_loaded, store.class) do
29
- store.load
30
- end
19
+ @checksum_store.load
20
+ @compiled_content_cache.load
21
+ @dependency_store.load
22
+ @action_sequence_store.load
23
+ @outdatedness_store.load
31
24
  end
32
25
  end
33
26
  end
@@ -176,6 +176,21 @@ module Nanoc
176
176
  "<#{self.class}>"
177
177
  end
178
178
 
179
+ contract C::None => C::Num
180
+ def hash
181
+ [@dir, @env_name].hash
182
+ end
183
+
184
+ contract C::Any => C::Bool
185
+ def ==(other)
186
+ eql?(other)
187
+ end
188
+
189
+ contract C::Any => C::Bool
190
+ def eql?(other)
191
+ other.is_a?(self.class) && @dir == other.dir && @env_name == other.env_name
192
+ end
193
+
179
194
  private
180
195
 
181
196
  def make_absolute(path)
@@ -72,10 +72,7 @@ module Nanoc
72
72
  # Error that is raised during site compilation when an item (directly or
73
73
  # indirectly) includes its own item content, leading to endless recursion.
74
74
  class DependencyCycle < ::Nanoc::Core::Error
75
- def initialize(stack)
76
- start_idx = stack.index(stack.last)
77
- cycle = stack[start_idx..-2]
78
-
75
+ def initialize(cycle)
79
76
  msg_bits = []
80
77
  msg_bits << 'The site cannot be compiled because there is a dependency cycle:'
81
78
  msg_bits << ''
@@ -90,10 +90,11 @@ module Nanoc
90
90
 
91
91
  def find_layout(arg)
92
92
  req_id = arg.__nanoc_cleaned_identifier
93
- layout = layouts[req_id]
93
+ layout = layouts.object_with_identifier(req_id)
94
94
  return layout if layout
95
95
 
96
96
  if use_globs?
97
+ # TODO: use object_matching_glob instead
97
98
  pat = Nanoc::Core::Pattern.from(arg)
98
99
  layout = layouts.find { |l| pat.match?(l.identifier) }
99
100
  return layout if layout
@@ -37,15 +37,6 @@ module Nanoc
37
37
  super
38
38
  end
39
39
 
40
- # contract C::Any => C::Maybe[C::RespondTo[:identifier]]
41
- def [](arg)
42
- if frozen?
43
- get_memoized(arg)
44
- else
45
- get_unmemoized(arg)
46
- end
47
- end
48
-
49
40
  # contract C::Any => C::IterOf[C::RespondTo[:identifier]]
50
41
  def find_all(arg)
51
42
  if frozen?
@@ -75,7 +66,6 @@ module Nanoc
75
66
  self.class.new(@config, @objects.reject(&block))
76
67
  end
77
68
 
78
- # contract C::Any => C::Maybe[C::RespondTo[:identifier]]
79
69
  def object_with_identifier(identifier)
80
70
  if frozen?
81
71
  @mapping[identifier.to_s]
@@ -84,26 +74,26 @@ module Nanoc
84
74
  end
85
75
  end
86
76
 
87
- protected
88
-
89
- # contract C::Any => C::Maybe[C::RespondTo[:identifier]]
90
- def get_unmemoized(arg)
91
- case arg
92
- when Nanoc::Core::Identifier
93
- object_with_identifier(arg)
94
- when String
95
- object_with_identifier(arg) || object_matching_glob(arg)
96
- when Regexp
97
- find { |i| i.identifier.to_s =~ arg }
77
+ def object_matching_glob(glob)
78
+ if frozen?
79
+ object_matching_glob_memoized(glob)
98
80
  else
99
- raise ArgumentError, "don’t know how to fetch objects by #{arg.inspect}"
81
+ object_matching_glob_unmemoized(glob)
100
82
  end
101
83
  end
102
84
 
103
- # contract C::Any => C::Maybe[C::RespondTo[:identifier]]
104
- def get_memoized(_arg)
105
- # TODO: Figure out how to get memo_wise to work with subclasses
106
- raise 'implement in subclasses'
85
+ protected
86
+
87
+ def object_matching_glob_memoized(glob)
88
+ object_matching_glob_unmemoized(glob)
89
+ end
90
+ memo_wise :object_matching_glob_memoized
91
+
92
+ def object_matching_glob_unmemoized(glob)
93
+ if use_globs?
94
+ pat = Pattern.from(glob)
95
+ find { |i| pat.match?(i.identifier) }
96
+ end
107
97
  end
108
98
 
109
99
  # contract C::Any => C::IterOf[C::RespondTo[:identifier]]
@@ -118,13 +108,6 @@ module Nanoc
118
108
  end
119
109
  memo_wise :find_all_memoized
120
110
 
121
- def object_matching_glob(glob)
122
- if use_globs?
123
- pat = Pattern.from(glob)
124
- find { |i| pat.match?(i.identifier) }
125
- end
126
- end
127
-
128
111
  def build_mapping
129
112
  @mapping = {}
130
113
  each do |object|
@@ -120,19 +120,63 @@ module Nanoc
120
120
  #
121
121
  # @return [#identifier] if an object was found
122
122
  def [](arg)
123
- prop_attribute =
124
- case arg
125
- when String, Nanoc::Core::Identifier
126
- [arg.to_s]
127
- when Regexp
128
- [arg]
123
+ # The argument to #[] fall in two categories: exact matches, and
124
+ # patterns. An example of an exact match is '/about.md', while an
125
+ # example of a pattern is '/about.*'.
126
+ #
127
+ # If a Nanoc::Core::Identifier is given, we know it will need to be an
128
+ # exact match. If a String is given, it could be either. If a Regexp is
129
+ # given, we know it’s a pattern match.
130
+ #
131
+ # If we have a pattern match, create a dependency on the item
132
+ # collection, with a `raw_content` property that contains the pattern.
133
+ # If we have an exact match, do nothing -- there is no reason to create
134
+ # a dependency on the item itself, because accessing that item
135
+ # (attributes, compiled content, …) will create the dependency later.
136
+
137
+ object_from_exact_match = nil
138
+ object_from_pattern_match = nil
139
+
140
+ case arg
141
+ when Nanoc::Core::Identifier
142
+ # Can only be an exact match
143
+ object_from_exact_match = @objects.object_with_identifier(arg)
144
+ when String
145
+ # Can be an exact match, or a pattern match
146
+ tmp = @objects.object_with_identifier(arg)
147
+ if tmp
148
+ object_from_exact_match = tmp
129
149
  else
130
- true
150
+ object_from_pattern_match = @objects.object_matching_glob(arg)
131
151
  end
152
+ when Regexp
153
+ # Can only be a pattern match
154
+ object_from_pattern_match = @objects.find { |i| i.identifier.to_s =~ arg }
155
+ else
156
+ raise ArgumentError, "Unexpected argument #{arg.class} to []: Can only pass strings, identfiers, and regular expressions"
157
+ end
132
158
 
133
- @context.dependency_tracker.bounce(_unwrap, raw_content: prop_attribute)
134
- res = @objects[arg]
135
- res && view_class.new(res, @context)
159
+ unless object_from_exact_match
160
+ # We got this object from a pattern match. Create a dependency with
161
+ # this pattern, because if the objects matching this pattern change,
162
+ # then the result of #[] will change too.
163
+ #
164
+ # NOTE: object_from_exact_match can also be nil, but in that case
165
+ # we still need to create a dependency.
166
+
167
+ prop_attribute =
168
+ case arg
169
+ when Identifier
170
+ [arg.to_s]
171
+ when String, Regexp
172
+ [arg]
173
+ end
174
+
175
+ @context.dependency_tracker.bounce(_unwrap, raw_content: prop_attribute)
176
+ end
177
+
178
+ object = object_from_exact_match || object_from_pattern_match
179
+ object && view_class.new(object, @context)
136
180
  end
137
181
  end
138
182
  end
@@ -9,12 +9,6 @@ module Nanoc
9
9
  initialize_basic(config, objects, 'items')
10
10
  end
11
11
 
12
- # contract C::Any => C::Maybe[C::RespondTo[:identifier]]
13
- def get_memoized(arg)
14
- get_unmemoized(arg)
15
- end
16
- memo_wise :get_memoized
17
-
18
12
  def reference
19
13
  'items'
20
14
  end
@@ -42,6 +42,9 @@ module Nanoc
42
42
  # Reset flags
43
43
  @compiled = false
44
44
  @modified = false
45
+
46
+ # Precalculate for performance
47
+ @hash = [self.class, item, name].hash
45
48
  end
46
49
 
47
50
  contract C::HashOf[Symbol => C::IterOf[C::AbsolutePathString]] => C::HashOf[Symbol => C::IterOf[C::AbsolutePathString]]
@@ -75,6 +78,21 @@ module Nanoc
75
78
  @paths.fetch(snapshot, []).first
76
79
  end
77
80
 
81
+ contract C::None => C::Num
82
+ def hash
83
+ @hash
84
+ end
85
+
86
+ contract C::Any => C::Bool
87
+ def ==(other)
88
+ eql?(other)
89
+ end
90
+
91
+ contract C::Any => C::Bool
92
+ def eql?(other)
93
+ other.is_a?(self.class) && item == other.item && name == other.name
94
+ end
95
+
78
96
  # Returns an object that can be used for uniquely identifying objects.
79
97
  def reference
80
98
  "item_rep:#{item.identifier}:#{name}"
@@ -25,15 +25,15 @@ module Nanoc
25
25
  action_sequences = Nanoc::Core::ItemRepRouter.new(@reps, @action_provider, @site).run
26
26
 
27
27
  @reps.each do |rep|
28
- rep.snapshot_defs = self.class.snapshot_defs_for(action_sequences[rep])
28
+ rep.snapshot_defs = self.class.snapshot_defs_for(action_sequences[rep], rep)
29
29
  end
30
30
 
31
31
  action_sequences
32
32
  end
33
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?
34
+ contract Nanoc::Core::ActionSequence, Nanoc::Core::ItemRep => C::ArrayOf[Nanoc::Core::SnapshotDef]
35
+ def self.snapshot_defs_for(action_sequence, rep)
36
+ is_binary = rep.item.content.binary?
37
37
  snapshot_defs = []
38
38
 
39
39
  action_sequence.each do |action|
@@ -8,54 +8,121 @@ module Nanoc
8
8
  @reps = reps
9
9
  end
10
10
 
11
- class MicroGraph
11
+ # An iterator (FIFO) over an array, with ability to ignore certain
12
+ # elements.
13
+ class ItemRepIgnorableIterator
14
+ def initialize(array)
15
+ @array = array.dup
16
+ end
17
+
18
+ def next_ignoring(ignored)
19
+ elem = @array.shift
20
+ elem = @array.shift while ignored.include?(elem)
21
+ elem
22
+ end
23
+ end
24
+
25
+ # A priority queue that tracks dependencies and can detect circular
26
+ # dependencies.
27
+ class ItemRepPriorityQueue
12
28
  def initialize(reps)
13
- @reps = Set.new(reps)
14
- @stack = []
29
+ # Prio A: most important; prio C: least important.
30
+ @prio_a = []
31
+ @prio_b = ItemRepIgnorableIterator.new(reps)
32
+ @prio_c = []
33
+
34
+ # List of reps that we’ve already seen. Reps from `reps` will end up
35
+ # in here. Reps can end up in here even *before* they come from
36
+ # `reps`, when they are part of a dependency.
37
+ @seen = Set.new
38
+
39
+ # Record (hard) dependencies. Used for detecting cycles.
40
+ @dependencies = Hash.new { |hash, key| hash[key] = Set.new }
15
41
  end
16
42
 
17
43
  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
44
+ # Read prio A
45
+ @this = @prio_a.pop
46
+ if @this
47
+ return @this
48
+ end
49
+
50
+ # Read prio B
51
+ @this = @prio_b.next_ignoring(@seen)
52
+ if @this
53
+ return @this
27
54
  end
55
+
56
+ # Read prio C
57
+ @this = @prio_c.pop
58
+ if @this
59
+ return @this
60
+ end
61
+
62
+ nil
28
63
  end
29
64
 
30
65
  def mark_ok
31
- @stack.pop
66
+ # Nothing to do
32
67
  end
33
68
 
34
69
  def mark_failed(dep)
35
- if @stack.include?(dep)
36
- raise Nanoc::Core::Errors::DependencyCycle.new(@stack + [dep])
37
- end
70
+ record_dependency(dep)
71
+
72
+ # `@this` depends on `dep`, so `dep` has to be compiled first. Thus,
73
+ # move `@this` into priority C, and `dep` into priority A.
74
+
75
+ # Put `@this` (item rep that needs `dep` to be compiled first) into
76
+ # priority C (lowest prio).
77
+ @prio_c.push(@this) unless @prio_c.include?(@this)
78
+
79
+ # Put `dep` (item rep that needs to be compiled first, before
80
+ # `@this`) into priority A (highest prio).
81
+ @prio_a.push(dep)
82
+
83
+ # Remember that we’ve prioritised `dep`. This particular element will
84
+ # come from @prio_b at some point in the future, so we’ll have to skip
85
+ # it then.
86
+ @seen << dep
87
+ end
88
+
89
+ private
38
90
 
39
- @reps.delete(dep)
40
- @stack.push(dep)
91
+ def record_dependency(dep)
92
+ @dependencies[@this] << dep
93
+
94
+ find_cycle(@this, [@this])
95
+ end
96
+
97
+ def find_cycle(dep, path)
98
+ @dependencies[dep].each do |dep1|
99
+ # Check whether this dependency path ends in `@this` again. If it
100
+ # does, we have a cycle (because we started from `@this`).
101
+ if dep1 == @this
102
+ raise Nanoc::Core::Errors::DependencyCycle.new(path)
103
+ end
104
+
105
+ # Continue checking, starting from `dep1` this time.
106
+ find_cycle(dep1, [*path, dep1])
107
+ end
41
108
  end
42
109
  end
43
110
 
44
111
  def each
45
- mg = MicroGraph.new(@reps)
112
+ pq = ItemRepPriorityQueue.new(@reps)
46
113
 
47
114
  loop do
48
- rep = mg.next
115
+ rep = pq.next
49
116
  break if rep.nil?
50
117
 
51
118
  begin
52
119
  yield(rep)
53
- mg.mark_ok
120
+ pq.mark_ok
54
121
  rescue => e
55
122
  actual_error = e.is_a?(Nanoc::Core::Errors::CompilationError) ? e.unwrap : e
56
123
 
57
124
  if actual_error.is_a?(Nanoc::Core::Errors::UnmetDependency)
58
- mg.mark_failed(actual_error.rep)
125
+ pq.mark_failed(actual_error.rep)
59
126
  else
60
127
  raise(e)
61
128
  end
@@ -9,12 +9,6 @@ module Nanoc
9
9
  initialize_basic(config, objects, 'layouts')
10
10
  end
11
11
 
12
- # contract C::Any => C::Maybe[C::RespondTo[:identifier]]
13
- def get_memoized(arg)
14
- get_unmemoized(arg)
15
- end
16
- memo_wise :get_memoized
17
-
18
12
  def reference
19
13
  'layouts'
20
14
  end
@@ -6,92 +6,6 @@ module Nanoc
6
6
  #
7
7
  # @api private
8
8
  class OutdatednessChecker
9
- class Basic
10
- include Nanoc::Core::ContractsSupport
11
-
12
- Rules = Nanoc::Core::OutdatednessRules
13
-
14
- RULES_FOR_ITEM_REP =
15
- [
16
- Rules::ItemAdded,
17
- Rules::RulesModified,
18
- Rules::ContentModified,
19
- Rules::AttributesModified,
20
- Rules::NotWritten,
21
- Rules::CodeSnippetsModified,
22
- Rules::UsesAlwaysOutdatedFilter,
23
- ].freeze
24
-
25
- RULES_FOR_LAYOUT =
26
- [
27
- Rules::LayoutAdded,
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
- C_OBJ_MAYBE_REP = C::Or[Nanoc::Core::Item, Nanoc::Core::ItemRep, Nanoc::Core::Configuration, Nanoc::Core::Layout, Nanoc::Core::ItemCollection, Nanoc::Core::LayoutCollection]
40
-
41
- contract C::KeywordArgs[outdatedness_checker: OutdatednessChecker, reps: Nanoc::Core::ItemRepRepo] => C::Any
42
- def initialize(outdatedness_checker:, reps:)
43
- @outdatedness_checker = outdatedness_checker
44
- @reps = reps
45
-
46
- # Memoize
47
- @_outdatedness_status_for = {}
48
- end
49
-
50
- contract C_OBJ_MAYBE_REP => C::Maybe[Nanoc::Core::OutdatednessStatus]
51
- def outdatedness_status_for(obj)
52
- @_outdatedness_status_for[obj] ||=
53
- case obj
54
- when Nanoc::Core::ItemRep
55
- apply_rules(RULES_FOR_ITEM_REP, obj)
56
- when Nanoc::Core::Item
57
- apply_rules_multi(RULES_FOR_ITEM_REP, @reps[obj])
58
- when Nanoc::Core::Layout
59
- apply_rules(RULES_FOR_LAYOUT, obj)
60
- when Nanoc::Core::Configuration
61
- apply_rules(RULES_FOR_CONFIG, obj)
62
- when Nanoc::Core::ItemCollection, Nanoc::Core::LayoutCollection
63
- # Collections are never outdated. Objects inside them might be,
64
- # however.
65
- apply_rules([], obj)
66
- else
67
- raise Nanoc::Core::Errors::InternalInconsistency, "do not know how to check outdatedness of #{obj.inspect}"
68
- end
69
- end
70
-
71
- private
72
-
73
- contract C::ArrayOf[Class], C_OBJ_MAYBE_REP, Nanoc::Core::OutdatednessStatus => C::Maybe[Nanoc::Core::OutdatednessStatus]
74
- def apply_rules(rules, obj, status = Nanoc::Core::OutdatednessStatus.new)
75
- rules.inject(status) do |acc, rule|
76
- if acc.useful_to_apply?(rule)
77
- reason = rule.instance.call(obj, @outdatedness_checker)
78
- if reason
79
- acc.update(reason)
80
- else
81
- acc
82
- end
83
- else
84
- acc
85
- end
86
- end
87
- end
88
-
89
- contract C::ArrayOf[Class], C::ArrayOf[C_OBJ_MAYBE_REP] => C::Maybe[Nanoc::Core::OutdatednessStatus]
90
- def apply_rules_multi(rules, objs)
91
- objs.inject(Nanoc::Core::OutdatednessStatus.new) { |acc, elem| apply_rules(rules, elem, acc) }
92
- end
93
- end
94
-
95
9
  include Nanoc::Core::ContractsSupport
96
10
 
97
11
  attr_reader :checksum_store
@@ -107,7 +21,15 @@ module Nanoc
107
21
  C_ITEM_OR_REP = C::Or[Nanoc::Core::Item, Nanoc::Core::ItemRep]
108
22
  C_ACTION_SEQUENCES = C::HashOf[C_OBJ => Nanoc::Core::ActionSequence]
109
23
 
110
- 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
24
+ contract C::KeywordArgs[
25
+ site: Nanoc::Core::Site,
26
+ checksum_store: Nanoc::Core::ChecksumStore,
27
+ checksums: Nanoc::Core::ChecksumCollection,
28
+ dependency_store: Nanoc::Core::DependencyStore,
29
+ action_sequence_store: Nanoc::Core::ActionSequenceStore,
30
+ action_sequences: C_ACTION_SEQUENCES,
31
+ reps: Nanoc::Core::ItemRepRepo
32
+ ] => C::Any
111
33
  def initialize(site:, checksum_store:, checksums:, dependency_store:, action_sequence_store:, action_sequences:, reps:)
112
34
  @site = site
113
35
  @checksum_store = checksum_store
@@ -120,20 +42,11 @@ module Nanoc
120
42
  @objects_outdated_due_to_dependencies = {}
121
43
  end
122
44
 
123
- def action_sequence_for(rep)
124
- @action_sequences.fetch(rep)
125
- end
126
-
127
- contract C_OBJ => C::Bool
128
- def outdated?(obj)
129
- outdatedness_reasons_for(obj).any?
130
- end
131
-
132
45
  contract C_OBJ => C::IterOf[Reasons::Generic]
133
46
  def outdatedness_reasons_for(obj)
134
- reasons = basic.outdatedness_status_for(obj).reasons
135
- if reasons.any?
136
- reasons
47
+ basic_reasons = basic_outdatedness_statuses.fetch(obj).reasons
48
+ if basic_reasons.any?
49
+ basic_reasons
137
50
  elsif outdated_due_to_dependencies?(obj)
138
51
  [Reasons::DependenciesOutdated]
139
52
  else
@@ -143,9 +56,28 @@ module Nanoc
143
56
 
144
57
  private
145
58
 
146
- contract C::None => Basic
59
+ def basic_outdatedness_statuses
60
+ @_basic_outdatedness_statuses ||= {}.tap do |tmp|
61
+ collections = [[@site.config], @site.layouts, @site.items, @reps]
62
+ collections.each do |collection|
63
+ collection.each do |obj|
64
+ tmp[obj] = basic.outdatedness_status_for(obj)
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ contract C::None => BasicOutdatednessChecker
147
71
  def basic
148
- @_basic ||= Basic.new(outdatedness_checker: self, reps: @reps)
72
+ @_basic ||= BasicOutdatednessChecker.new(
73
+ site: @site,
74
+ checksum_store: @checksum_store,
75
+ checksums: @checksums,
76
+ dependency_store: @dependency_store,
77
+ action_sequence_store: @action_sequence_store,
78
+ action_sequences: @action_sequences,
79
+ reps: @reps,
80
+ )
149
81
  end
150
82
 
151
83
  contract C_OBJ, Hamster::Set => C::Bool
@@ -191,7 +123,7 @@ module Nanoc
191
123
  raw_content_prop_causes_outdatedness?(all_objects, dependency.props.raw_content) ||
192
124
  attributes_prop_causes_outdatedness?(all_objects, dependency.props.attributes)
193
125
  else
194
- status = basic.outdatedness_status_for(dependency.from)
126
+ status = basic_outdatedness_statuses.fetch(dependency.from)
195
127
 
196
128
  active = status.props.active & dependency.props.active
197
129
  active.delete(:attributes) if attributes_unaffected?(status, dependency)
@@ -217,8 +149,7 @@ module Nanoc
217
149
  when Enumerable
218
150
  # If the `raw_content` dependency prop is a collection, then this
219
151
  # is a dependency on specific objects, given by the patterns.
220
- patterns = raw_content_prop.map { |r| Nanoc::Core::Pattern.from(r) }
221
- patterns.flat_map { |pat| objects.select { |obj| pat.match?(obj.identifier) } }
152
+ raw_content_prop.flat_map { |pat| objects.find_all(pat) }
222
153
  else
223
154
  raise(
224
155
  Nanoc::Core::Errors::InternalInconsistency,
@@ -236,7 +167,7 @@ module Nanoc
236
167
  # accessed), then another dependency will exist that will cause
237
168
  # outdatedness.
238
169
  matching_objects.any? do |obj|
239
- status = basic.outdatedness_status_for(obj)
170
+ status = basic_outdatedness_statuses.fetch(obj)
240
171
  status.reasons.any? { |r| Nanoc::Core::OutdatednessReasons::DocumentAdded == r }
241
172
  end
242
173
  end
@@ -265,23 +196,30 @@ module Nanoc
265
196
  objects.any? do |object|
266
197
  # Find old and new attribute checksums for the object
267
198
  old_object_checksums = checksum_store.attributes_checksum_for(object)
268
- next false unless old_object_checksums
269
-
270
199
  new_object_checksums = checksums.attributes_checksum_for(object)
271
200
 
272
201
  dep_checksums.any? do |key, dep_value|
273
- # Get old and new checksum for this particular attribute
274
- old_value = old_object_checksums[key]
275
- new_value = new_object_checksums[key]
276
-
277
- # If the old and new checksums are identical, then the attribute is
278
- # unchanged and can’t cause outdatedness.
279
- next false unless old_value != new_value
280
-
281
- # We already know that the old value and new value are different.
282
- # This attribute will cause outdatedness if either of those
283
- # checksums is identical to the one in the prop.
284
- old_value == dep_value || new_value == dep_value
202
+ if old_object_checksums
203
+ # Get old and new checksum for this particular attribute
204
+ old_value = old_object_checksums[key]
205
+ new_value = new_object_checksums[key]
206
+
207
+ # If the old and new checksums are identical, then the attribute
208
+ # is unchanged and can’t cause outdatedness.
209
+ next false unless old_value != new_value
210
+
211
+ # We already know that the old value and new value are different.
212
+ # This attribute will cause outdatedness if either of those
213
+ # checksums is identical to the one in the prop.
214
+ old_value == dep_value || new_value == dep_value
215
+ else
216
+ # We don’t have the previous checksums, which means this item is
217
+ # newly added. In this case, we can compare the value in the
218
+ # dependency with the new checksum.
219
+
220
+ new_value = new_object_checksums[key]
221
+ new_value == dep_value
222
+ end
285
223
  end
286
224
  end
287
225
  end
@@ -8,22 +8,22 @@ module Nanoc
8
8
 
9
9
  affects_props :attributes, :compiled_content
10
10
 
11
- contract C::Or[Nanoc::Core::ItemRep, Nanoc::Core::Item, Nanoc::Core::Configuration, Nanoc::Core::Layout], C::Named['Nanoc::Core::OutdatednessChecker'] => C::Maybe[Nanoc::Core::OutdatednessReasons::Generic]
12
- def apply(obj, outdatedness_checker)
11
+ contract C::Or[Nanoc::Core::ItemRep, Nanoc::Core::Item, Nanoc::Core::Configuration, Nanoc::Core::Layout], C::Named['Nanoc::Core::BasicOutdatednessChecker'] => C::Maybe[Nanoc::Core::OutdatednessReasons::Generic]
12
+ def apply(obj, basic_outdatedness_checker)
13
13
  case obj
14
14
  when Nanoc::Core::ItemRep
15
- apply(obj.item, outdatedness_checker)
15
+ apply(obj.item, basic_outdatedness_checker)
16
16
  when Nanoc::Core::Item, Nanoc::Core::Layout, Nanoc::Core::Configuration
17
- if outdatedness_checker.checksum_store[obj] == outdatedness_checker.checksums.checksum_for(obj)
17
+ if basic_outdatedness_checker.checksum_store[obj] == basic_outdatedness_checker.checksums.checksum_for(obj)
18
18
  return nil
19
19
  end
20
20
 
21
- old_checksums = outdatedness_checker.checksum_store.attributes_checksum_for(obj)
21
+ old_checksums = basic_outdatedness_checker.checksum_store.attributes_checksum_for(obj)
22
22
  unless old_checksums
23
23
  return Nanoc::Core::OutdatednessReasons::AttributesModified.new(true)
24
24
  end
25
25
 
26
- new_checksums = outdatedness_checker.checksums.attributes_checksum_for(obj)
26
+ new_checksums = basic_outdatedness_checker.checksums.attributes_checksum_for(obj)
27
27
 
28
28
  attributes = Set.new(old_checksums.keys) + Set.new(new_checksums.keys)
29
29
  changed_attributes = attributes.reject { |a| old_checksums[a] == new_checksums[a] }
@@ -10,18 +10,18 @@ module Nanoc
10
10
 
11
11
  affects_props :raw_content, :attributes, :compiled_content, :path
12
12
 
13
- def apply(_obj, outdatedness_checker)
14
- if any_snippets_modified?(outdatedness_checker)
13
+ def apply(_obj, basic_outdatedness_checker)
14
+ if any_snippets_modified?(basic_outdatedness_checker)
15
15
  Nanoc::Core::OutdatednessReasons::CodeSnippetsModified
16
16
  end
17
17
  end
18
18
 
19
19
  private
20
20
 
21
- def any_snippets_modified?(outdatedness_checker)
22
- outdatedness_checker.site.code_snippets.any? do |cs|
23
- ch_old = outdatedness_checker.checksum_store[cs]
24
- ch_new = outdatedness_checker.checksums.checksum_for(cs)
21
+ def any_snippets_modified?(basic_outdatedness_checker)
22
+ basic_outdatedness_checker.site.code_snippets.any? do |cs|
23
+ ch_old = basic_outdatedness_checker.checksum_store[cs]
24
+ ch_new = basic_outdatedness_checker.checksums.checksum_for(cs)
25
25
  ch_old != ch_new
26
26
  end
27
27
  end
@@ -6,11 +6,11 @@ module Nanoc
6
6
  class ContentModified < Nanoc::Core::OutdatednessRule
7
7
  affects_props :raw_content, :compiled_content
8
8
 
9
- def apply(obj, outdatedness_checker)
9
+ def apply(obj, basic_outdatedness_checker)
10
10
  obj = obj.item if obj.is_a?(Nanoc::Core::ItemRep)
11
11
 
12
- ch_old = outdatedness_checker.checksum_store.content_checksum_for(obj)
13
- ch_new = outdatedness_checker.checksums.content_checksum_for(obj)
12
+ ch_old = basic_outdatedness_checker.checksum_store.content_checksum_for(obj)
13
+ ch_new = basic_outdatedness_checker.checksums.content_checksum_for(obj)
14
14
  if ch_old != ch_new
15
15
  Nanoc::Core::OutdatednessReasons::ContentModified
16
16
  end
@@ -6,9 +6,9 @@ module Nanoc
6
6
  class ItemAdded < Nanoc::Core::OutdatednessRule
7
7
  affects_props :raw_content
8
8
 
9
- contract Nanoc::Core::ItemRep, C::Named['Nanoc::Core::OutdatednessChecker'] => C::Maybe[Nanoc::Core::OutdatednessReasons::Generic]
10
- def apply(obj, outdatedness_checker)
11
- if outdatedness_checker.dependency_store.new_items.include?(obj.item)
9
+ contract Nanoc::Core::ItemRep, C::Named['Nanoc::Core::BasicOutdatednessChecker'] => C::Maybe[Nanoc::Core::OutdatednessReasons::Generic]
10
+ def apply(obj, basic_outdatedness_checker)
11
+ if basic_outdatedness_checker.dependency_store.new_items.include?(obj.item)
12
12
  Nanoc::Core::OutdatednessReasons::DocumentAdded
13
13
  end
14
14
  end
@@ -6,9 +6,9 @@ module Nanoc
6
6
  class LayoutAdded < Nanoc::Core::OutdatednessRule
7
7
  affects_props :raw_content
8
8
 
9
- contract Nanoc::Core::Layout, C::Named['Nanoc::Core::OutdatednessChecker'] => C::Maybe[Nanoc::Core::OutdatednessReasons::Generic]
10
- def apply(obj, outdatedness_checker)
11
- if outdatedness_checker.dependency_store.new_layouts.include?(obj)
9
+ contract Nanoc::Core::Layout, C::Named['Nanoc::Core::BasicOutdatednessChecker'] => C::Maybe[Nanoc::Core::OutdatednessReasons::Generic]
10
+ def apply(obj, basic_outdatedness_checker)
11
+ if basic_outdatedness_checker.dependency_store.new_layouts.include?(obj)
12
12
  Nanoc::Core::OutdatednessReasons::DocumentAdded
13
13
  end
14
14
  end
@@ -6,30 +6,30 @@ module Nanoc
6
6
  class NotWritten < Nanoc::Core::OutdatednessRule
7
7
  affects_props :raw_content, :attributes, :compiled_content, :path
8
8
 
9
- def apply(obj, outdatedness_checker)
10
- if obj.raw_paths.values.flatten.compact.any? { |fn| !exist?(fn, outdatedness_checker) }
9
+ def apply(obj, basic_outdatedness_checker)
10
+ if obj.raw_paths.values.flatten.compact.any? { |fn| !exist?(fn, basic_outdatedness_checker) }
11
11
  Nanoc::Core::OutdatednessReasons::NotWritten
12
12
  end
13
13
  end
14
14
 
15
15
  private
16
16
 
17
- def exist?(fn, outdatedness_checker)
18
- all(outdatedness_checker).include?(fn)
17
+ def exist?(fn, basic_outdatedness_checker)
18
+ all(basic_outdatedness_checker).include?(fn)
19
19
  end
20
20
 
21
- def all(outdatedness_checker)
21
+ def all(basic_outdatedness_checker)
22
22
  # NOTE: Cached per outdatedness checker, so that unrelated invocations
23
23
  # later on don’t reuse an old cache.
24
24
 
25
25
  @all ||= {}
26
- @all[outdatedness_checker] ||= Set.new(
27
- Dir.glob("#{site_root(outdatedness_checker)}/**/*", File::FNM_DOTMATCH),
26
+ @all[basic_outdatedness_checker] ||= Set.new(
27
+ Dir.glob("#{site_root(basic_outdatedness_checker)}/**/*", File::FNM_DOTMATCH),
28
28
  )
29
29
  end
30
30
 
31
- def site_root(outdatedness_checker)
32
- outdatedness_checker.site.config.output_dir
31
+ def site_root(basic_outdatedness_checker)
32
+ basic_outdatedness_checker.site.config.output_dir
33
33
  end
34
34
  end
35
35
  end
@@ -6,15 +6,15 @@ module Nanoc
6
6
  class RulesModified < Nanoc::Core::OutdatednessRule
7
7
  affects_props :compiled_content, :path
8
8
 
9
- def apply(obj, outdatedness_checker)
9
+ def apply(obj, basic_outdatedness_checker)
10
10
  # Check rules of obj itself
11
- if rules_modified?(obj, outdatedness_checker)
11
+ if rules_modified?(obj, basic_outdatedness_checker)
12
12
  return Nanoc::Core::OutdatednessReasons::RulesModified
13
13
  end
14
14
 
15
15
  # Check rules of layouts used by obj
16
- layouts = layouts_touched_by(obj, outdatedness_checker)
17
- if layouts.any? { |layout| rules_modified?(layout, outdatedness_checker) }
16
+ layouts = layouts_touched_by(obj, basic_outdatedness_checker)
17
+ if layouts.any? { |layout| rules_modified?(layout, basic_outdatedness_checker) }
18
18
  return Nanoc::Core::OutdatednessReasons::RulesModified
19
19
  end
20
20
 
@@ -23,20 +23,22 @@ module Nanoc
23
23
 
24
24
  private
25
25
 
26
- def rules_modified?(obj, outdatedness_checker)
27
- seq_old = outdatedness_checker.action_sequence_store[obj]
28
- seq_new = outdatedness_checker.action_sequence_for(obj).serialize
26
+ def rules_modified?(obj, basic_outdatedness_checker)
27
+ seq_old = basic_outdatedness_checker.action_sequence_store[obj]
28
+ seq_new = basic_outdatedness_checker.action_sequence_for(obj).serialize
29
29
 
30
30
  !seq_old.eql?(seq_new)
31
31
  end
32
32
 
33
- def layouts_touched_by(obj, outdatedness_checker)
34
- actions = outdatedness_checker.action_sequence_store[obj]
33
+ def layouts_touched_by(obj, basic_outdatedness_checker)
34
+ actions = basic_outdatedness_checker.action_sequence_store[obj]
35
35
  layout_actions = actions.select { |a| a.first == :layout }
36
36
 
37
+ layouts = basic_outdatedness_checker.site.layouts
38
+
37
39
  layout_actions.map do |layout_action|
38
40
  layout_pattern = layout_action[1]
39
- outdatedness_checker.site.layouts[layout_pattern]
41
+ layouts.object_with_identifier(layout_pattern) || layouts.object_matching_glob(layout_pattern)
40
42
  end.compact
41
43
  end
42
44
  end
@@ -6,8 +6,8 @@ module Nanoc
6
6
  class UsesAlwaysOutdatedFilter < Nanoc::Core::OutdatednessRule
7
7
  affects_props :raw_content, :attributes, :path
8
8
 
9
- def apply(obj, outdatedness_checker)
10
- seq = outdatedness_checker.action_sequence_for(obj)
9
+ def apply(obj, basic_outdatedness_checker)
10
+ seq = basic_outdatedness_checker.action_sequence_for(obj)
11
11
  if any_always_outdated?(seq)
12
12
  Nanoc::Core::OutdatednessReasons::UsesAlwaysOutdatedFilter
13
13
  end
@@ -7,11 +7,7 @@ module Nanoc
7
7
  # graphs.
8
8
  #
9
9
  # Each store has a version number. When attempting to load data from a store
10
- # that has an incompatible version number, no data will be loaded, but
11
- # {#version_mismatch_detected} will be called.
12
- #
13
- # @abstract Subclasses must implement {#data} and {#data=}, and may
14
- # implement {#no_data_found} and {#version_mismatch_detected}.
10
+ # that has an incompatible version number, no data will be loaded.
15
11
  #
16
12
  # @api private
17
13
  class Store
@@ -78,17 +74,8 @@ module Nanoc
78
74
  #
79
75
  # @return [void]
80
76
  def load
81
- return unless File.file?(filename)
82
-
83
- begin
84
- pstore.transaction(true) do
85
- return if pstore[:version] != version
86
-
87
- self.data = pstore[:data]
88
- end
89
- rescue
90
- FileUtils.rm_f(filename)
91
- load
77
+ Nanoc::Core::Instrumentor.call(:store_loaded, self.class) do
78
+ load_uninstrumented
92
79
  end
93
80
  end
94
81
 
@@ -97,18 +84,64 @@ module Nanoc
97
84
  #
98
85
  # @return [void]
99
86
  def store
100
- FileUtils.mkdir_p(File.dirname(filename))
101
-
102
- pstore.transaction do
103
- pstore[:data] = data
104
- pstore[:version] = version
87
+ # NOTE: Yes, the “store stored” name is a little silly. Maybe stores
88
+ # need to be renamed to databases or so.
89
+ Nanoc::Core::Instrumentor.call(:store_stored, self.class) do
90
+ store_uninstrumented
105
91
  end
106
92
  end
107
93
 
108
94
  private
109
95
 
110
- def pstore
111
- @pstore ||= PStore.new(filename)
96
+ def load_uninstrumented
97
+ unsafe_load_uninstrumented
98
+ rescue
99
+ # An error occurred! Remove the database and try again
100
+ FileUtils.rm_f(version_filename)
101
+ FileUtils.rm_f(data_filename)
102
+
103
+ # Try again
104
+ unsafe_load_uninstrumented
105
+ end
106
+
107
+ def store_uninstrumented
108
+ FileUtils.mkdir_p(File.dirname(filename))
109
+
110
+ write_obj_to_file(version_filename, version)
111
+ write_obj_to_file(data_filename, data)
112
+
113
+ # Remove old file (back from the PStore days), if there are any.
114
+ FileUtils.rm_f(filename)
115
+ end
116
+
117
+ # Unsafe, because it can throw exceptions.
118
+ def unsafe_load_uninstrumented
119
+ # If there is no database, no point in loading anything
120
+ return unless File.file?(version_filename)
121
+
122
+ # Check if store version is the expected version. If it is not, don’t
123
+ # load.
124
+ read_version = read_obj_from_file(version_filename)
125
+ return if read_version != version
126
+
127
+ # Load data
128
+ self.data = read_obj_from_file(data_filename)
129
+ end
130
+
131
+ def write_obj_to_file(fn, obj)
132
+ File.binwrite(fn, Marshal.dump(obj))
133
+ end
134
+
135
+ def read_obj_from_file(fn)
136
+ Marshal.load(File.binread(fn))
137
+ end
138
+
139
+ def version_filename
140
+ "#{filename}.version.db"
141
+ end
142
+
143
+ def data_filename
144
+ "#{filename}.data.db"
112
145
  end
113
146
  end
114
147
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Nanoc
4
4
  module Core
5
- VERSION = '4.12.10'
5
+ VERSION = '4.12.12'
6
6
  end
7
7
  end
data/lib/nanoc/core.rb CHANGED
@@ -33,6 +33,14 @@ module Nanoc
33
33
  # thus cannot be used to mean the presence of nothing.
34
34
  UNDEFINED = Object.new
35
35
 
36
+ def UNDEFINED.inspect
37
+ '<UNDEFINED>'
38
+ end
39
+
40
+ def UNDEFINED.to_s
41
+ inspect
42
+ end
43
+
36
44
  # @return [String] A string containing information about this Nanoc version
37
45
  # and its environment (Ruby engine and version, Rubygems version if any).
38
46
  #
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nanoc-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.12.10
4
+ version: 4.12.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Denis Defreyne
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-10-18 00:00:00.000000000 Z
11
+ date: 2022-11-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -169,6 +169,7 @@ files:
169
169
  - lib/nanoc/core/basic_item_rep_collection_view.rb
170
170
  - lib/nanoc/core/basic_item_rep_view.rb
171
171
  - lib/nanoc/core/basic_item_view.rb
172
+ - lib/nanoc/core/basic_outdatedness_checker.rb
172
173
  - lib/nanoc/core/binary_compiled_content_cache.rb
173
174
  - lib/nanoc/core/binary_content.rb
174
175
  - lib/nanoc/core/changes_stream.rb
@@ -315,7 +316,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
315
316
  - !ruby/object:Gem::Version
316
317
  version: '0'
317
318
  requirements: []
318
- rubygems_version: 3.3.24
319
+ rubygems_version: 3.3.25
319
320
  signing_key:
320
321
  specification_version: 4
321
322
  summary: Core of Nanoc