nanoc-core 4.12.10 → 4.12.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) 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/executor.rb +2 -1
  10. data/lib/nanoc/core/identifiable_collection.rb +16 -33
  11. data/lib/nanoc/core/identifiable_collection_view.rb +54 -10
  12. data/lib/nanoc/core/item_collection.rb +0 -6
  13. data/lib/nanoc/core/item_rep.rb +18 -0
  14. data/lib/nanoc/core/item_rep_builder.rb +4 -4
  15. data/lib/nanoc/core/item_rep_selector.rb +69 -17
  16. data/lib/nanoc/core/layout_collection.rb +0 -6
  17. data/lib/nanoc/core/outdatedness_checker.rb +57 -119
  18. data/lib/nanoc/core/outdatedness_rules/attributes_modified.rb +6 -6
  19. data/lib/nanoc/core/outdatedness_rules/code_snippets_modified.rb +6 -6
  20. data/lib/nanoc/core/outdatedness_rules/content_modified.rb +3 -3
  21. data/lib/nanoc/core/outdatedness_rules/item_added.rb +3 -3
  22. data/lib/nanoc/core/outdatedness_rules/layout_added.rb +3 -3
  23. data/lib/nanoc/core/outdatedness_rules/not_written.rb +9 -9
  24. data/lib/nanoc/core/outdatedness_rules/rules_modified.rb +12 -10
  25. data/lib/nanoc/core/outdatedness_rules/uses_always_outdated_filter.rb +2 -2
  26. data/lib/nanoc/core/store.rb +56 -23
  27. data/lib/nanoc/core/version.rb +1 -1
  28. data/lib/nanoc/core.rb +8 -0
  29. 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: d7fc026255728ddb8de8c0756afdfbdc7548e47938d9c4fb464023c0811ae4e2
4
+ data.tar.gz: a8531e9c3920f81c9caa88996b1f195eedec21ef392cda04cd2fcf76c0f83106
5
5
  SHA512:
6
- metadata.gz: 5c0190dbbad1225882d5b76891e80f45cf5e02238c6f6418071d9e8968e82cdd0263099eb5a71963b179238fb57a86f53ef7f7677592299c61cc6197be6b5dd9
7
- data.tar.gz: 13424fbb1cc81c95e471a8ba99f172d24b6f33b1e9220a50773ed32554f5577622db7d63825734b04254e33c624b2d382b16ced1628952f5cee7e6429d96e57a
6
+ metadata.gz: f819fe95a0d25831e51ba4ce360ca76921c01517402bf67d3407d369b1fc8c7f05a7262fe0df1dbfd03e3c3f12d72281d818db19746f294f135e8b017a21a07d
7
+ data.tar.gz: c0de0aef8f83c0a7723d755bf804a25f3ca574e308b54b36cac6e0a91ac5b0b2c4658635cd899d18fdc830d51250a151cbb23d69dfdd4b4abd1cde5b12c96a2c
@@ -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)
@@ -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,23 +8,62 @@ 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)
29
+ # Prio A: most important; prio C: least important.
30
+ @prio_a = []
31
+ @prio_b = ItemRepIgnorableIterator.new(reps)
32
+ @prio_c = []
33
+
34
+ # Stack of things that depend on each other. This is used for
35
+ # detecting and reporting circular dependencies.
14
36
  @stack = []
37
+
38
+ # List of reps that we’ve already seen. Reps from `reps` will end up
39
+ # in here. Reps can end up in here even *before* they come from
40
+ # `reps`, when they are part of a dependency.
41
+ @seen = Set.new
15
42
  end
16
43
 
17
44
  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
45
+ # Read prio A
46
+ @this = @prio_a.pop
47
+ if @this
48
+ @stack.push(@this)
49
+ return @this
27
50
  end
51
+
52
+ # Read prio B
53
+ @this = @prio_b.next_ignoring(@seen)
54
+ if @this
55
+ @stack.push(@this)
56
+ return @this
57
+ end
58
+
59
+ # Read prio C
60
+ @this = @prio_c.pop
61
+ if @this
62
+ @stack.push(@this)
63
+ return @this
64
+ end
65
+
66
+ nil
28
67
  end
29
68
 
30
69
  def mark_ok
@@ -36,26 +75,39 @@ module Nanoc
36
75
  raise Nanoc::Core::Errors::DependencyCycle.new(@stack + [dep])
37
76
  end
38
77
 
39
- @reps.delete(dep)
40
- @stack.push(dep)
78
+ # `@this` depends on `dep`, so `dep` has to be compiled first. Thus,
79
+ # move `@this` into priority C, and `dep` into priority A.
80
+
81
+ # Put `@this` (item rep that needs `dep` to be compiled first) into
82
+ # priority C (lowest prio).
83
+ @prio_c.push(@this)
84
+
85
+ # Put `dep` (item rep that needs to be compiled first, before
86
+ # `@this`) into priority A (highest prio).
87
+ @prio_a.push(dep)
88
+
89
+ # Remember that we’ve prioritised `dep`. This particular element will
90
+ # come from @prio_b at some point in the future, so we’ll have to skip
91
+ # it then.
92
+ @seen << dep
41
93
  end
42
94
  end
43
95
 
44
96
  def each
45
- mg = MicroGraph.new(@reps)
97
+ pq = ItemRepPriorityQueue.new(@reps)
46
98
 
47
99
  loop do
48
- rep = mg.next
100
+ rep = pq.next
49
101
  break if rep.nil?
50
102
 
51
103
  begin
52
104
  yield(rep)
53
- mg.mark_ok
105
+ pq.mark_ok
54
106
  rescue => e
55
107
  actual_error = e.is_a?(Nanoc::Core::Errors::CompilationError) ? e.unwrap : e
56
108
 
57
109
  if actual_error.is_a?(Nanoc::Core::Errors::UnmetDependency)
58
- mg.mark_failed(actual_error.rep)
110
+ pq.mark_failed(actual_error.rep)
59
111
  else
60
112
  raise(e)
61
113
  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.11'
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.11
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-12 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