nanoc-core 4.12.8 → 4.12.10

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a29a2064140d04c44df1122dc537ca800a3ed7c20bfb740c4009f3b976067ebb
4
- data.tar.gz: 6fc3869373babe4c16671b15747d0d3da4b291e4f6434cf1af6e5fbe05c99799
3
+ metadata.gz: 3365fa09f4ff20a16df20f260562fca8c64342098ba70b4547dc38580204ea2e
4
+ data.tar.gz: 132a480e5593d19bb6c1802f6f740e3ed1d081eb88f0178fbde311d9a4e52cc0
5
5
  SHA512:
6
- metadata.gz: bd1fd013b69ae47c40b8a1c5558729314603ceadabdadf3eb7b117b810988a7f7d777d5f3d6dba9f3e4f899763064c073ddd1e808f7d1e0f8aae8d52dc0c1e43
7
- data.tar.gz: a11a2202ed08c519e151f097aee47ee16e189a969169fb09ca29162acb99436531007c8c943b6dea892be702454e518376a16253dc9f181069625305e1f09554
6
+ metadata.gz: 5c0190dbbad1225882d5b76891e80f45cf5e02238c6f6418071d9e8968e82cdd0263099eb5a71963b179238fb57a86f53ef7f7677592299c61cc6197be6b5dd9
7
+ data.tar.gz: 13424fbb1cc81c95e471a8ba99f172d24b6f33b1e9220a50773ed32554f5577622db7d63825734b04254e33c624b2d382b16ced1628952f5cee7e6429d96e57a
@@ -9,6 +9,8 @@ module Nanoc
9
9
 
10
10
  def initialize(checksums)
11
11
  @checksums = checksums
12
+
13
+ @_attribute_checksums = {}
12
14
  end
13
15
 
14
16
  contract c_obj => C::Maybe[String]
@@ -23,7 +25,7 @@ module Nanoc
23
25
 
24
26
  contract c_obj => C::Maybe[C::HashOf[Symbol, String]]
25
27
  def attributes_checksum_for(obj)
26
- @checksums[[obj.reference, :each_attribute]]
28
+ @_attribute_checksums[obj] ||= @checksums[[obj.reference, :each_attribute]]
27
29
  end
28
30
 
29
31
  def to_h
@@ -21,6 +21,8 @@ module Nanoc
21
21
  @objects = objects
22
22
 
23
23
  @checksums = {}
24
+
25
+ invalidate_memoization
24
26
  end
25
27
 
26
28
  contract c_obj => C::Maybe[String]
@@ -50,7 +52,7 @@ module Nanoc
50
52
 
51
53
  contract c_obj => C::Maybe[C::HashOf[Symbol, String]]
52
54
  def attributes_checksum_for(obj)
53
- @checksums[[obj.reference, :each_attribute]]
55
+ @_attribute_checksums[obj] ||= @checksums[[obj.reference, :each_attribute]]
54
56
  end
55
57
 
56
58
  protected
@@ -60,6 +62,8 @@ module Nanoc
60
62
  end
61
63
 
62
64
  def data=(new_data)
65
+ invalidate_memoization
66
+
63
67
  references = Set.new(@objects.map(&:reference))
64
68
 
65
69
  @checksums = {}
@@ -69,6 +73,12 @@ module Nanoc
69
73
  end
70
74
  end
71
75
  end
76
+
77
+ private
78
+
79
+ def invalidate_memoization
80
+ @_attribute_checksums = {}
81
+ end
72
82
  end
73
83
  end
74
84
  end
@@ -81,7 +81,8 @@ module Nanoc
81
81
  # NOTE: Other behaviors are registered elsewhere
82
82
  # (search for `define_behavior`).
83
83
 
84
- define_behavior(Array, ArrayUpdateBehavior)
84
+ define_behavior(Array, CollectionUpdateBehavior)
85
+ define_behavior(Set, SetUpdateBehavior)
85
86
  define_behavior(FalseClass, NoUpdateBehavior)
86
87
  define_behavior(Hash, HashUpdateBehavior)
87
88
  define_behavior(NilClass, NoUpdateBehavior)
@@ -96,7 +97,7 @@ module Nanoc
96
97
  define_behavior(Nanoc::Core::Configuration, HashUpdateBehavior)
97
98
  define_behavior(Nanoc::Core::Context, ContextUpdateBehavior)
98
99
  define_behavior(Nanoc::Core::CodeSnippet, DataUpdateBehavior)
99
- define_behavior(Nanoc::Core::IdentifiableCollection, ArrayUpdateBehavior)
100
+ define_behavior(Nanoc::Core::IdentifiableCollection, CollectionUpdateBehavior)
100
101
  define_behavior(Nanoc::Core::Identifier, ToSUpdateBehavior)
101
102
  define_behavior(Nanoc::Core::Item, DocumentUpdateBehavior)
102
103
  define_behavior(Nanoc::Core::ItemRep, ItemRepUpdateBehavior)
@@ -194,7 +195,7 @@ module Nanoc
194
195
  end
195
196
  end
196
197
 
197
- class ArrayUpdateBehavior < UpdateBehavior
198
+ class CollectionUpdateBehavior < UpdateBehavior
198
199
  def self.update(obj, digest)
199
200
  obj.each do |el|
200
201
  yield(el)
@@ -203,6 +204,13 @@ module Nanoc
203
204
  end
204
205
  end
205
206
 
207
+ class SetUpdateBehavior < CollectionUpdateBehavior
208
+ def self.update(obj, digest)
209
+ # Similar to CollectionUpdateBehavior, but sorted for consistency.
210
+ super(obj.sort { |a, b| (a <=> b) || 0 }, digest)
211
+ end
212
+ end
213
+
206
214
  class HashUpdateBehavior < UpdateBehavior
207
215
  def self.update(obj, digest)
208
216
  obj.each do |key, value|
@@ -25,6 +25,7 @@ module Nanoc
25
25
  Maybe = Ignorer.instance
26
26
  None = Ignorer.instance
27
27
  ArrayOf = Ignorer.instance
28
+ SetOf = Ignorer.instance
28
29
  Or = Ignorer.instance
29
30
  Func = Ignorer.instance
30
31
  RespondTo = Ignorer.instance
@@ -96,8 +97,7 @@ module Nanoc
96
97
  false
97
98
  end
98
99
 
99
- # FIXME: Do something better with contracts on Ruby 3.x
100
- @_contracts_support__should_enable = contracts_loadable && !RUBY_VERSION.start_with?('3') && !ENV.key?('DISABLE_CONTRACTS')
100
+ @_contracts_support__should_enable = contracts_loadable && !ENV.key?('DISABLE_CONTRACTS')
101
101
 
102
102
  if @_contracts_support__should_enable
103
103
  # FIXME: ugly
@@ -10,9 +10,39 @@ module Nanoc
10
10
  attr_reader :raw_content
11
11
 
12
12
  # TODO: Split raw_content for documents and collections
13
- C_RAW_CONTENT = C::Or[C::IterOf[C::Or[String, Regexp]], C::Bool]
14
- C_ATTRS = C::Or[C::IterOf[Symbol], C::Bool]
15
- contract C::KeywordArgs[raw_content: C::Optional[C_RAW_CONTENT], attributes: C::Optional[C_ATTRS], compiled_content: C::Optional[C::Bool], path: C::Optional[C::Bool]] => C::Any
13
+ C_RAW_CONTENT =
14
+ C::Or[
15
+ C::SetOf[C::Or[String, Regexp]],
16
+ C::ArrayOf[C::Or[String, Regexp]],
17
+ C::Bool
18
+ ]
19
+
20
+ C_ATTR =
21
+ C::Or[
22
+ C::SetOf[
23
+ C::Or[
24
+ Symbol, # any value
25
+ [Symbol, C::Any] # pair (specific value)
26
+ ],
27
+ ],
28
+ C::ArrayOf[
29
+ C::Or[
30
+ Symbol, # any value
31
+ [Symbol, C::Any] # pair (specific value)
32
+ ],
33
+ ],
34
+ C::Bool
35
+ ]
36
+
37
+ C_ARGS =
38
+ C::KeywordArgs[
39
+ raw_content: C::Optional[C_RAW_CONTENT],
40
+ attributes: C::Optional[C_ATTR],
41
+ compiled_content: C::Optional[C::Bool],
42
+ path: C::Optional[C::Bool]
43
+ ]
44
+
45
+ contract C_ARGS => C::Any
16
46
  def initialize(raw_content: false, attributes: false, compiled_content: false, path: false)
17
47
  @compiled_content = compiled_content
18
48
  @path = path
@@ -21,7 +51,7 @@ module Nanoc
21
51
  case attributes
22
52
  when Set
23
53
  attributes
24
- when Enumerable
54
+ when Array
25
55
  Set.new(attributes)
26
56
  else
27
57
  attributes
@@ -31,7 +61,7 @@ module Nanoc
31
61
  case raw_content
32
62
  when Set
33
63
  raw_content
34
- when Enumerable
64
+ when Array
35
65
  Set.new(raw_content)
36
66
  else
37
67
  raw_content
@@ -46,6 +76,23 @@ module Nanoc
46
76
  s << (attributes? ? 'a' : '_')
47
77
  s << (compiled_content? ? 'c' : '_')
48
78
  s << (path? ? 'p' : '_')
79
+
80
+ if @raw_content.is_a?(Set)
81
+ @raw_content.each do |elem|
82
+ s << '; raw_content('
83
+ s << elem.inspect
84
+ s << ')'
85
+ end
86
+ end
87
+
88
+ if @attributes.is_a?(Set)
89
+ @attributes.each do |elem|
90
+ s << '; attr('
91
+ s << elem.inspect
92
+ s << ')'
93
+ end
94
+ end
95
+
49
96
  s << ')'
50
97
  end
51
98
  end
@@ -63,8 +110,8 @@ module Nanoc
63
110
  contract C::None => C::Bool
64
111
  def raw_content?
65
112
  case @raw_content
66
- when Enumerable
67
- @raw_content.any?
113
+ when Set
114
+ !@raw_content.empty?
68
115
  else
69
116
  @raw_content
70
117
  end
@@ -73,8 +120,8 @@ module Nanoc
73
120
  contract C::None => C::Bool
74
121
  def attributes?
75
122
  case @attributes
76
- when Enumerable
77
- @attributes.any?
123
+ when Set
124
+ !@attributes.empty?
78
125
  else
79
126
  @attributes
80
127
  end
@@ -136,6 +183,15 @@ module Nanoc
136
183
  end
137
184
  end
138
185
 
186
+ def attribute_keys
187
+ case @attributes
188
+ when Enumerable
189
+ @attributes.map { |a| a.is_a?(Array) ? a.first : a }
190
+ else
191
+ []
192
+ end
193
+ end
194
+
139
195
  contract C::None => Hash
140
196
  def to_h
141
197
  {
@@ -6,11 +6,36 @@ module Nanoc
6
6
  class DependencyStore < ::Nanoc::Core::Store
7
7
  include Nanoc::Core::ContractsSupport
8
8
 
9
- C_ATTR = C::Or[C::IterOf[Symbol], C::Bool]
10
- C_RAW_CONTENT = C::Or[C::IterOf[C::Or[String, Regexp]], C::Bool]
11
- C_KEYWORD_PROPS = C::KeywordArgs[raw_content: C::Optional[C_RAW_CONTENT], attributes: C::Optional[C_ATTR], compiled_content: C::Optional[C::Bool], path: C::Optional[C::Bool]]
9
+ C_RAW_CONTENT =
10
+ C::Or[
11
+ C::ArrayOf[C::Or[String, Regexp]],
12
+ C::Bool
13
+ ]
14
+
15
+ C_ATTR =
16
+ C::Or[
17
+ C::ArrayOf[Symbol],
18
+ C::HashOf[Symbol => C::Any],
19
+ C::Bool
20
+ ]
21
+
22
+ C_KEYWORD_PROPS =
23
+ C::KeywordArgs[
24
+ raw_content: C::Optional[C_RAW_CONTENT],
25
+ attributes: C::Optional[C_ATTR],
26
+ compiled_content: C::Optional[C::Bool],
27
+ path: C::Optional[C::Bool]
28
+ ]
29
+
12
30
  C_OBJ_SRC = Nanoc::Core::Item
13
- C_OBJ_DST = C::Or[Nanoc::Core::Item, Nanoc::Core::Layout, Nanoc::Core::Configuration, Nanoc::Core::IdentifiableCollection]
31
+
32
+ C_OBJ_DST =
33
+ C::Or[
34
+ Nanoc::Core::Item,
35
+ Nanoc::Core::Layout,
36
+ Nanoc::Core::Configuration,
37
+ Nanoc::Core::IdentifiableCollection
38
+ ]
14
39
 
15
40
  attr_reader :items
16
41
  attr_reader :layouts
@@ -46,12 +71,7 @@ module Nanoc
46
71
  Nanoc::Core::Dependency.new(
47
72
  other_object,
48
73
  object,
49
- Nanoc::Core::DependencyProps.new(
50
- raw_content: props.fetch(:raw_content, false),
51
- attributes: props.fetch(:attributes, false),
52
- compiled_content: props.fetch(:compiled_content, false),
53
- path: props.fetch(:path, false),
54
- ),
74
+ props,
55
75
  )
56
76
  end
57
77
  end
@@ -116,11 +136,16 @@ module Nanoc
116
136
  src_ref = obj2ref(src)
117
137
  dst_ref = obj2ref(dst)
118
138
 
119
- existing_props = Nanoc::Core::DependencyProps.new(**(@graph.props_for(dst_ref, src_ref) || {}))
139
+ # Convert attributes into key-value pairs, if necessary
140
+ if attributes.is_a?(Hash)
141
+ attributes = attributes.to_a
142
+ end
143
+
144
+ existing_props = @graph.props_for(dst_ref, src_ref)
120
145
  new_props = Nanoc::Core::DependencyProps.new(raw_content: raw_content, attributes: attributes, compiled_content: compiled_content, path: path)
121
- props = existing_props.merge(new_props)
146
+ props = existing_props ? existing_props.merge(new_props) : new_props
122
147
 
123
- @graph.add_edge(dst_ref, src_ref, props: props.to_h)
148
+ @graph.add_edge(dst_ref, src_ref, props: props)
124
149
  end
125
150
 
126
151
  def add_vertex_for(obj)
@@ -163,18 +188,26 @@ module Nanoc
163
188
  end
164
189
 
165
190
  def props_for(from, to)
166
- props = @graph.props_for(obj2ref(from), obj2ref(to)) || {}
167
-
168
- if props.values.any? { |v| v }
169
- props
170
- else
171
- { raw_content: true, attributes: true, compiled_content: true, path: true }
172
- end
191
+ props = @graph.props_for(obj2ref(from), obj2ref(to))
192
+ return props if props
193
+
194
+ # This is for backwards compatibility, in case there are no dependency
195
+ # props available yet. Pretend everything is set to `true`; it’ll be
196
+ # recompiled and correct props will be available in the next run.
197
+ #
198
+ # NOTE: This isn’t covered by tests, yet. (Not trivial to test because
199
+ # it’s not a condition that can arise in the current Nanoc version.)
200
+ Nanoc::Core::DependencyProps.new(
201
+ raw_content: true,
202
+ attributes: true,
203
+ compiled_content: true,
204
+ path: true,
205
+ )
173
206
  end
174
207
 
175
208
  def data
176
209
  {
177
- edges: @graph.edges,
210
+ edges: @graph.edges.map { |arr| [arr[0], arr[1], arr[2].to_h] },
178
211
  vertices: @graph.vertices,
179
212
  }
180
213
  end
@@ -193,8 +226,11 @@ module Nanoc
193
226
  # Load edges
194
227
  new_data[:edges].each do |edge|
195
228
  from_index, to_index, props = *edge
196
- from = from_index && previous_refs[from_index]
197
- to = to_index && previous_refs[to_index]
229
+
230
+ from = from_index && previous_refs[from_index]
231
+ to = to_index && previous_refs[to_index]
232
+ props = Nanoc::Core::DependencyProps.new(**props)
233
+
198
234
  @graph.add_edge(from, to, props: props)
199
235
  end
200
236
 
@@ -16,13 +16,14 @@ module Nanoc
16
16
 
17
17
  C_RAW_CONTENT =
18
18
  C::Or[
19
- C::IterOf[C::Or[String, Regexp]],
19
+ C::ArrayOf[C::Or[String, Regexp]],
20
20
  C::Bool
21
21
  ]
22
22
 
23
23
  C_ATTR =
24
24
  C::Or[
25
- C::IterOf[Symbol],
25
+ C::ArrayOf[Symbol],
26
+ C::HashOf[Symbol => C::Any],
26
27
  C::Bool
27
28
  ]
28
29
 
@@ -49,6 +49,10 @@ module Nanoc
49
49
  @checksum_data = checksum_data
50
50
  @content_checksum_data = content_checksum_data
51
51
  @attributes_checksum_data = attributes_checksum_data
52
+
53
+ # Precalculate for performance
54
+ @hash = [self.class, identifier].hash
55
+ reference
52
56
  end
53
57
 
54
58
  # @return [Hash]
@@ -107,7 +111,7 @@ module Nanoc
107
111
 
108
112
  contract C::None => C::Num
109
113
  def hash
110
- [self.class, identifier].hash
114
+ @hash
111
115
  end
112
116
 
113
117
  contract C::Any => C::Bool
@@ -90,3 +90,5 @@ module Nanoc
90
90
  end
91
91
  end
92
92
  end
93
+
94
+ Nanoc::Core::Feature.define('where', version: '4.12')
@@ -69,6 +69,34 @@ module Nanoc
69
69
  @objects.find_all(arg).map { |i| view_class.new(i, @context) }
70
70
  end
71
71
 
72
+ # Finds all objects that have the given attribute key/value pair.
73
+ #
74
+ # @example
75
+ #
76
+ # @items.where(kind: 'article')
77
+ # @items.where(kind: 'article', year: 2020)
78
+ #
79
+ # @return [Enumerable]
80
+ def where(**hash)
81
+ unless Nanoc::Core::Feature.enabled?(Nanoc::Core::Feature::WHERE)
82
+ raise(
83
+ Nanoc::Core::TrivialError,
84
+ '#where is experimental, and not yet available unless the corresponding feature flag is turned on. Set the `NANOC_FEATURES` environment variable to `where` to enable its usage. (Alternatively, set the environment variable to `all` to turn on all feature flags.)',
85
+ )
86
+ end
87
+
88
+ @context.dependency_tracker.bounce(_unwrap, attributes: hash)
89
+
90
+ # IDEA: Nanoc could remember (from the previous compilation) how many
91
+ # times #where is called with a given attribute key, and memoize the
92
+ # key-to-identifiers list.
93
+ found_objects = @objects.select do |i|
94
+ hash.all? { |k, v| i.attributes[k] == v }
95
+ end
96
+
97
+ found_objects.map { |i| view_class.new(i, @context) }
98
+ end
99
+
72
100
  # @overload [](string)
73
101
  #
74
102
  # Finds the object whose identifier matches the given string.
@@ -4,7 +4,7 @@ module Nanoc
4
4
  module Core
5
5
  class Item < ::Nanoc::Core::Document
6
6
  def reference
7
- "item:#{identifier}"
7
+ @_reference ||= "item:#{identifier}"
8
8
  end
9
9
  end
10
10
  end
@@ -4,7 +4,7 @@ module Nanoc
4
4
  module Core
5
5
  class Layout < ::Nanoc::Core::Document
6
6
  def reference
7
- "layout:#{identifier}"
7
+ @_reference ||= "layout:#{identifier}"
8
8
  end
9
9
  end
10
10
  end
@@ -26,13 +26,7 @@ module Nanoc
26
26
  #
27
27
  # @see Hash#[]=
28
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)
29
+ if disallowed_value_class?(value.class)
36
30
  raise DisallowedAttributeValueError.new(value)
37
31
  end
38
32
 
@@ -55,6 +49,21 @@ module Nanoc
55
49
  hash.each { |k, v| _unwrap.set_attribute(k, v) }
56
50
  self
57
51
  end
52
+
53
+ private
54
+
55
+ def disallowed_value_class?(klass)
56
+ # NOTE: We’re explicitly disabling Style/MultipleComparison, because
57
+ # the suggested alternative (Array#include?) carries a measurable
58
+ # performance penatly.
59
+ #
60
+ # rubocop:disable Style/MultipleComparison
61
+ klass == Nanoc::Core::Item ||
62
+ klass == Nanoc::Core::Layout ||
63
+ klass == Nanoc::Core::CompilationItemView ||
64
+ klass == Nanoc::Core::LayoutView
65
+ # rubocop:enable Style/MultipleComparison
66
+ end
58
67
  end
59
68
  end
60
69
  end
@@ -13,6 +13,7 @@ module Nanoc
13
13
 
14
14
  RULES_FOR_ITEM_REP =
15
15
  [
16
+ Rules::ItemAdded,
16
17
  Rules::RulesModified,
17
18
  Rules::ContentModified,
18
19
  Rules::AttributesModified,
@@ -23,6 +24,7 @@ module Nanoc
23
24
 
24
25
  RULES_FOR_LAYOUT =
25
26
  [
27
+ Rules::LayoutAdded,
26
28
  Rules::RulesModified,
27
29
  Rules::ContentModified,
28
30
  Rules::AttributesModified,
@@ -34,16 +36,6 @@ module Nanoc
34
36
  Rules::AttributesModified,
35
37
  ].freeze
36
38
 
37
- RULES_FOR_ITEM_COLLECTION =
38
- [
39
- Rules::ItemCollectionExtended,
40
- ].freeze
41
-
42
- RULES_FOR_LAYOUT_COLLECTION =
43
- [
44
- Rules::LayoutCollectionExtended,
45
- ].freeze
46
-
47
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]
48
40
 
49
41
  contract C::KeywordArgs[outdatedness_checker: OutdatednessChecker, reps: Nanoc::Core::ItemRepRepo] => C::Any
@@ -67,10 +59,10 @@ module Nanoc
67
59
  apply_rules(RULES_FOR_LAYOUT, obj)
68
60
  when Nanoc::Core::Configuration
69
61
  apply_rules(RULES_FOR_CONFIG, obj)
70
- when Nanoc::Core::ItemCollection
71
- apply_rules(RULES_FOR_ITEM_COLLECTION, obj)
72
- when Nanoc::Core::LayoutCollection
73
- apply_rules(RULES_FOR_LAYOUT_COLLECTION, 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)
74
66
  else
75
67
  raise Nanoc::Core::Errors::InternalInconsistency, "do not know how to check outdatedness of #{obj.inspect}"
76
68
  end
@@ -190,31 +182,107 @@ module Nanoc
190
182
 
191
183
  contract Nanoc::Core::Dependency => C::Bool
192
184
  def dependency_causes_outdatedness?(dependency)
193
- return true if dependency.from.nil?
194
-
195
- status = basic.outdatedness_status_for(dependency.from)
185
+ case dependency.from
186
+ when nil
187
+ true
188
+ when Nanoc::Core::ItemCollection, Nanoc::Core::LayoutCollection
189
+ all_objects = dependency.from
190
+
191
+ raw_content_prop_causes_outdatedness?(all_objects, dependency.props.raw_content) ||
192
+ attributes_prop_causes_outdatedness?(all_objects, dependency.props.attributes)
193
+ else
194
+ status = basic.outdatedness_status_for(dependency.from)
196
195
 
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)
196
+ active = status.props.active & dependency.props.active
197
+ active.delete(:attributes) if attributes_unaffected?(status, dependency)
200
198
 
201
- active.any?
199
+ !active.empty?
200
+ end
202
201
  end
203
202
 
204
203
  def attributes_unaffected?(status, dependency)
205
204
  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?
205
+ reason && dependency.props.attribute_keys.any? && (dependency.props.attribute_keys & reason.attributes).empty?
207
206
  end
208
207
 
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) } }
208
+ def raw_content_prop_causes_outdatedness?(objects, raw_content_prop)
209
+ return false unless raw_content_prop
210
+
211
+ matching_objects =
212
+ case raw_content_prop
213
+ when true
214
+ # If the `raw_content` dependency prop is `true`, then this is a
215
+ # dependency on all *objects* (items or layouts).
216
+ objects
217
+ when Enumerable
218
+ # If the `raw_content` dependency prop is a collection, then this
219
+ # 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) } }
222
+ else
223
+ raise(
224
+ Nanoc::Core::Errors::InternalInconsistency,
225
+ "Unexpected type of raw_content: #{raw_content_prop.inspect}",
226
+ )
227
+ end
228
+
229
+ # For all objects matching the `raw_content` dependency prop:
230
+ # If the object is outdated because it is newly added,
231
+ # then this dependency causes outdatedness.
232
+ #
233
+ # Note that these objects might be modified but *not* newly added,
234
+ # in which case this dependency will *not* cause outdatedness.
235
+ # However, when the object is used later (e.g. attributes are
236
+ # accessed), then another dependency will exist that will cause
237
+ # outdatedness.
238
+ matching_objects.any? do |obj|
239
+ status = basic.outdatedness_status_for(obj)
240
+ status.reasons.any? { |r| Nanoc::Core::OutdatednessReasons::DocumentAdded == r }
241
+ end
242
+ end
243
+
244
+ def attributes_prop_causes_outdatedness?(objects, attributes_prop)
245
+ return false unless attributes_prop
246
+
247
+ unless attributes_prop.is_a?(Set)
248
+ raise(
249
+ Nanoc::Core::Errors::InternalInconsistency,
250
+ 'expected attributes_prop to be a Set',
251
+ )
252
+ end
253
+
254
+ pairs = attributes_prop.select { |a| a.is_a?(Array) }.to_h
255
+
256
+ unless pairs.any?
257
+ raise(
258
+ Nanoc::Core::Errors::InternalInconsistency,
259
+ 'expected attributes_prop not to be empty',
260
+ )
261
+ end
262
+
263
+ dep_checksums = pairs.transform_values { |value| Nanoc::Core::Checksummer.calc(value) }
264
+
265
+ objects.any? do |object|
266
+ # Find old and new attribute checksums for the object
267
+ old_object_checksums = checksum_store.attributes_checksum_for(object)
268
+ next false unless old_object_checksums
269
+
270
+ new_object_checksums = checksums.attributes_checksum_for(object)
271
+
272
+ 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
285
+ end
218
286
  end
219
287
  end
220
288
  end
@@ -18,6 +18,10 @@ module Nanoc
18
18
  # @param [String] message The descriptive message for this outdatedness
19
19
  # reason
20
20
  def initialize(message, props = Nanoc::Core::DependencyProps.new)
21
+ # TODO: Replace `DependencyProps` with its own `OutdatednessProps`
22
+ # type. For `OutdatednessProps`, the only values are true/false;
23
+ # giving a collection for `raw_content` makes no sense (anymore).
24
+
21
25
  @message = message
22
26
  @props = props
23
27
  end
@@ -42,30 +46,16 @@ module Nanoc
42
46
  Nanoc::Core::DependencyProps.new(compiled_content: true, path: true),
43
47
  )
44
48
 
49
+ DocumentAdded = Generic.new(
50
+ 'The item or layout is newly added to the site.',
51
+ Nanoc::Core::DependencyProps.new, # NOTE: empty props, because they’re not relevant
52
+ )
53
+
45
54
  ContentModified = Generic.new(
46
55
  'The content of this item has been modified since the last time the site was compiled.',
47
56
  Nanoc::Core::DependencyProps.new(raw_content: true, compiled_content: true),
48
57
  )
49
58
 
50
- class DocumentCollectionExtended < Generic
51
- attr_reader :objects
52
-
53
- def initialize(objects)
54
- super(
55
- 'New items/layouts have been added to the site.',
56
- Nanoc::Core::DependencyProps.new(raw_content: true),
57
- )
58
-
59
- @objects = objects
60
- end
61
- end
62
-
63
- class ItemCollectionExtended < DocumentCollectionExtended
64
- end
65
-
66
- class LayoutCollectionExtended < DocumentCollectionExtended
67
- end
68
-
69
59
  class AttributesModified < Generic
70
60
  attr_reader :attributes
71
61
 
@@ -23,11 +23,39 @@ module Nanoc
23
23
  end
24
24
 
25
25
  def self.affects_props(*names)
26
- @affected_props = Set.new(names)
26
+ @affects_raw_content = false
27
+ @affects_attributes = false
28
+ @affects_compiled_content = false
29
+ @affects_path = false
30
+
31
+ names.each do |name|
32
+ case name
33
+ when :raw_content
34
+ @affects_raw_content = true
35
+ when :attributes
36
+ @affects_attributes = true
37
+ when :compiled_content
38
+ @affects_compiled_content = true
39
+ when :path
40
+ @affects_path = true
41
+ end
42
+ end
43
+ end
44
+
45
+ def self.affects_raw_content?
46
+ @affects_raw_content
47
+ end
48
+
49
+ def self.affects_attributes?
50
+ @affects_attributes
51
+ end
52
+
53
+ def self.affects_compiled_content?
54
+ @affects_compiled_content
27
55
  end
28
56
 
29
- def self.affected_props
30
- @affected_props
57
+ def self.affects_path?
58
+ @affects_path
31
59
  end
32
60
  end
33
61
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ module OutdatednessRules
6
+ class ItemAdded < Nanoc::Core::OutdatednessRule
7
+ affects_props :raw_content
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)
12
+ Nanoc::Core::OutdatednessReasons::DocumentAdded
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ module OutdatednessRules
6
+ class LayoutAdded < Nanoc::Core::OutdatednessRule
7
+ affects_props :raw_content
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)
12
+ Nanoc::Core::OutdatednessReasons::DocumentAdded
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -13,7 +13,12 @@ module Nanoc
13
13
  end
14
14
 
15
15
  def useful_to_apply?(rule)
16
- (rule.affected_props - @props.active).any?
16
+ return true if rule.affects_raw_content? && !@props.raw_content?
17
+ return true if rule.affects_attributes? && !@props.attributes?
18
+ return true if rule.affects_compiled_content? && !@props.compiled_content?
19
+ return true if rule.affects_path? && !@props.path?
20
+
21
+ false
17
22
  end
18
23
 
19
24
  def update(reason)
@@ -22,6 +27,10 @@ module Nanoc
22
27
  props: @props.merge(reason.props),
23
28
  )
24
29
  end
30
+
31
+ def inspect
32
+ "<#{self.class} reasons=#{@reasons.inspect} props=#{@props.inspect}>"
33
+ end
25
34
  end
26
35
  end
27
36
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Nanoc
4
4
  module Core
5
- VERSION = '4.12.8'
5
+ VERSION = '4.12.10'
6
6
  end
7
7
  end
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.8
4
+ version: 4.12.10
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-03 00:00:00.000000000 Z
11
+ date: 2022-10-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -261,8 +261,8 @@ files:
261
261
  - lib/nanoc/core/outdatedness_rules/attributes_modified.rb
262
262
  - lib/nanoc/core/outdatedness_rules/code_snippets_modified.rb
263
263
  - lib/nanoc/core/outdatedness_rules/content_modified.rb
264
- - lib/nanoc/core/outdatedness_rules/item_collection_extended.rb
265
- - lib/nanoc/core/outdatedness_rules/layout_collection_extended.rb
264
+ - lib/nanoc/core/outdatedness_rules/item_added.rb
265
+ - lib/nanoc/core/outdatedness_rules/layout_added.rb
266
266
  - lib/nanoc/core/outdatedness_rules/not_written.rb
267
267
  - lib/nanoc/core/outdatedness_rules/rules_modified.rb
268
268
  - lib/nanoc/core/outdatedness_rules/uses_always_outdated_filter.rb
@@ -315,7 +315,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
315
315
  - !ruby/object:Gem::Version
316
316
  version: '0'
317
317
  requirements: []
318
- rubygems_version: 3.3.22
318
+ rubygems_version: 3.3.24
319
319
  signing_key:
320
320
  specification_version: 4
321
321
  summary: Core of Nanoc
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Nanoc
4
- module Core
5
- module OutdatednessRules
6
- class ItemCollectionExtended < Nanoc::Core::OutdatednessRule
7
- affects_props :raw_content
8
-
9
- contract Nanoc::Core::ItemCollection, C::Named['Nanoc::Core::OutdatednessChecker'] => C::Maybe[Nanoc::Core::OutdatednessReasons::Generic]
10
- def apply(_obj, outdatedness_checker)
11
- new_items = outdatedness_checker.dependency_store.new_items
12
-
13
- if new_items.any?
14
- Nanoc::Core::OutdatednessReasons::ItemCollectionExtended.new(new_items)
15
- end
16
- end
17
- end
18
- end
19
- end
20
- end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Nanoc
4
- module Core
5
- module OutdatednessRules
6
- class LayoutCollectionExtended < Nanoc::Core::OutdatednessRule
7
- affects_props :raw_content
8
-
9
- contract Nanoc::Core::LayoutCollection, C::Named['Nanoc::Core::OutdatednessChecker'] => C::Maybe[Nanoc::Core::OutdatednessReasons::Generic]
10
- def apply(_obj, outdatedness_checker)
11
- new_layouts = outdatedness_checker.dependency_store.new_layouts
12
-
13
- if new_layouts.any?
14
- Nanoc::Core::OutdatednessReasons::LayoutCollectionExtended.new(new_layouts)
15
- end
16
- end
17
- end
18
- end
19
- end
20
- end