nanoc-core 4.12.8 → 4.12.10

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