nanoc-core 4.12.7 → 4.12.9

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: 1fb5a347a2a973a7c42b1f85746b5267b43c588bf627c4f813c09c3a23b8e7eb
4
- data.tar.gz: f03ba79aea51348eca0a83c80e3bef8a5853da59981205939b7d417588dd3678
3
+ metadata.gz: 835f6e4a74f07183143caa0c2d27f304fa5a712c3231dddc2dd28974f714b117
4
+ data.tar.gz: 71331c08284b2f0da1b6410c8a5c17230efa1606640c1ca7e8724fb97e037951
5
5
  SHA512:
6
- metadata.gz: '0211852d5d7069d0dec63609db20a2cf533ba167e71ab08e195e333abea2cb17ecce34b05c4c65cd0be84c1726389caae8f02313188641a980e1aa6622f47619'
7
- data.tar.gz: d3f7d023ce5855b2e2b67c2e49132b60aaec1f8cc71bf7163b2d1a2492304fccf932a2252e0ea1546c4694a83168395195dae444c530ad641c37d5f45e0a9673
6
+ metadata.gz: 1c3b6430105aab73a5e9efff550b3519bd3e19cf99cb9b70f7a3b42916fd48becc091ea7325740673936373650e0d2950f986e507128aa1a7156f0e95bbb1520
7
+ data.tar.gz: 24ef14012f2ed47f9dd77f885601c395b58fb4d50fb61afa2196fedeecac2571d3702ad87f0e7d80d8806a07b6712364774cf6012650595f4a0cae072e85353e
@@ -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,7 +110,7 @@ module Nanoc
63
110
  contract C::None => C::Bool
64
111
  def raw_content?
65
112
  case @raw_content
66
- when Enumerable
113
+ when Set
67
114
  @raw_content.any?
68
115
  else
69
116
  @raw_content
@@ -73,7 +120,7 @@ module Nanoc
73
120
  contract C::None => C::Bool
74
121
  def attributes?
75
122
  case @attributes
76
- when Enumerable
123
+ when Set
77
124
  @attributes.any?
78
125
  else
79
126
  @attributes
@@ -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
@@ -116,11 +141,16 @@ module Nanoc
116
141
  src_ref = obj2ref(src)
117
142
  dst_ref = obj2ref(dst)
118
143
 
119
- existing_props = Nanoc::Core::DependencyProps.new(**(@graph.props_for(dst_ref, src_ref) || {}))
144
+ # Convert attributes into key-value pairs, if necessary
145
+ if attributes.is_a?(Hash)
146
+ attributes = attributes.to_a
147
+ end
148
+
149
+ existing_props = @graph.props_for(dst_ref, src_ref)
120
150
  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)
151
+ props = existing_props ? existing_props.merge(new_props) : new_props
122
152
 
123
- @graph.add_edge(dst_ref, src_ref, props: props.to_h)
153
+ @graph.add_edge(dst_ref, src_ref, props: props)
124
154
  end
125
155
 
126
156
  def add_vertex_for(obj)
@@ -162,11 +192,12 @@ module Nanoc
162
192
  refs.map { |r| ref2obj(r) }
163
193
  end
164
194
 
195
+ # TODO: Return not a Hash, but a DependencyProps instead
165
196
  def props_for(from, to)
166
- props = @graph.props_for(obj2ref(from), obj2ref(to)) || {}
197
+ props = @graph.props_for(obj2ref(from), obj2ref(to))
167
198
 
168
- if props.values.any? { |v| v }
169
- props
199
+ if props&.active&.any?
200
+ props.to_h
170
201
  else
171
202
  { raw_content: true, attributes: true, compiled_content: true, path: true }
172
203
  end
@@ -174,7 +205,7 @@ module Nanoc
174
205
 
175
206
  def data
176
207
  {
177
- edges: @graph.edges,
208
+ edges: @graph.edges.map { |arr| [arr[0], arr[1], arr[2].to_h] },
178
209
  vertices: @graph.vertices,
179
210
  }
180
211
  end
@@ -193,8 +224,11 @@ module Nanoc
193
224
  # Load edges
194
225
  new_data[:edges].each do |edge|
195
226
  from_index, to_index, props = *edge
196
- from = from_index && previous_refs[from_index]
197
- to = to_index && previous_refs[to_index]
227
+
228
+ from = from_index && previous_refs[from_index]
229
+ to = to_index && previous_refs[to_index]
230
+ props = Nanoc::Core::DependencyProps.new(**props)
231
+
198
232
  @graph.add_edge(from, to, props: props)
199
233
  end
200
234
 
@@ -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
 
@@ -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,13 +4,36 @@ module Nanoc
4
4
  module Core
5
5
  # @api private
6
6
  class Instrumentor
7
+ @enabled = false
8
+
9
+ def self.enable
10
+ if block_given?
11
+ begin
12
+ enable
13
+ yield
14
+ ensure
15
+ disable
16
+ end
17
+ else
18
+ @enabled = true
19
+ end
20
+ end
21
+
22
+ def self.disable
23
+ @enabled = false
24
+ end
25
+
7
26
  def self.call(key, *args)
8
- stopwatch = DDMetrics::Stopwatch.new
9
- stopwatch.start
10
- yield
11
- ensure
12
- stopwatch.stop
13
- Nanoc::Core::NotificationCenter.post(key, stopwatch.duration, *args)
27
+ return yield unless @enabled
28
+
29
+ begin
30
+ stopwatch = DDMetrics::Stopwatch.new
31
+ stopwatch.start
32
+ yield
33
+ ensure
34
+ stopwatch.stop
35
+ Nanoc::Core::NotificationCenter.post(key, stopwatch.duration, *args)
36
+ end
14
37
  end
15
38
  end
16
39
  end
@@ -7,14 +7,13 @@ module Nanoc
7
7
  # @api private
8
8
  class OutdatednessChecker
9
9
  class Basic
10
- prepend MemoWise
11
-
12
- # include Nanoc::Core::ContractsSupport
10
+ include Nanoc::Core::ContractsSupport
13
11
 
14
12
  Rules = Nanoc::Core::OutdatednessRules
15
13
 
16
14
  RULES_FOR_ITEM_REP =
17
15
  [
16
+ Rules::ItemAdded,
18
17
  Rules::RulesModified,
19
18
  Rules::ContentModified,
20
19
  Rules::AttributesModified,
@@ -25,6 +24,7 @@ module Nanoc
25
24
 
26
25
  RULES_FOR_LAYOUT =
27
26
  [
27
+ Rules::LayoutAdded,
28
28
  Rules::RulesModified,
29
29
  Rules::ContentModified,
30
30
  Rules::AttributesModified,
@@ -36,48 +36,41 @@ module Nanoc
36
36
  Rules::AttributesModified,
37
37
  ].freeze
38
38
 
39
- RULES_FOR_ITEM_COLLECTION =
40
- [
41
- Rules::ItemCollectionExtended,
42
- ].freeze
43
-
44
- RULES_FOR_LAYOUT_COLLECTION =
45
- [
46
- Rules::LayoutCollectionExtended,
47
- ].freeze
48
-
49
- # C_OBJ_MAYBE_REP = C::Or[Nanoc::Core::Item, Nanoc::Core::ItemRep, Nanoc::Core::Configuration, Nanoc::Core::Layout, Nanoc::Core::ItemCollection, Nanoc::Core::LayoutCollection]
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]
50
40
 
51
- # contract C::KeywordArgs[outdatedness_checker: OutdatednessChecker, reps: Nanoc::Core::ItemRepRepo] => C::Any
41
+ contract C::KeywordArgs[outdatedness_checker: OutdatednessChecker, reps: Nanoc::Core::ItemRepRepo] => C::Any
52
42
  def initialize(outdatedness_checker:, reps:)
53
43
  @outdatedness_checker = outdatedness_checker
54
44
  @reps = reps
45
+
46
+ # Memoize
47
+ @_outdatedness_status_for = {}
55
48
  end
56
49
 
57
- # contract C_OBJ_MAYBE_REP => C::Maybe[Nanoc::Core::OutdatednessStatus]
50
+ contract C_OBJ_MAYBE_REP => C::Maybe[Nanoc::Core::OutdatednessStatus]
58
51
  def outdatedness_status_for(obj)
59
- case obj
60
- when Nanoc::Core::ItemRep
61
- apply_rules(RULES_FOR_ITEM_REP, obj)
62
- when Nanoc::Core::Item
63
- apply_rules_multi(RULES_FOR_ITEM_REP, @reps[obj])
64
- when Nanoc::Core::Layout
65
- apply_rules(RULES_FOR_LAYOUT, obj)
66
- when Nanoc::Core::Configuration
67
- apply_rules(RULES_FOR_CONFIG, obj)
68
- when Nanoc::Core::ItemCollection
69
- apply_rules(RULES_FOR_ITEM_COLLECTION, obj)
70
- when Nanoc::Core::LayoutCollection
71
- apply_rules(RULES_FOR_LAYOUT_COLLECTION, obj)
72
- else
73
- raise Nanoc::Core::Errors::InternalInconsistency, "do not know how to check outdatedness of #{obj.inspect}"
74
- end
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
75
69
  end
76
- memo_wise :outdatedness_status_for
77
70
 
78
71
  private
79
72
 
80
- # contract C::ArrayOf[Class], C_OBJ_MAYBE_REP, Nanoc::Core::OutdatednessStatus => C::Maybe[Nanoc::Core::OutdatednessStatus]
73
+ contract C::ArrayOf[Class], C_OBJ_MAYBE_REP, Nanoc::Core::OutdatednessStatus => C::Maybe[Nanoc::Core::OutdatednessStatus]
81
74
  def apply_rules(rules, obj, status = Nanoc::Core::OutdatednessStatus.new)
82
75
  rules.inject(status) do |acc, rule|
83
76
  if acc.useful_to_apply?(rule)
@@ -93,15 +86,13 @@ module Nanoc
93
86
  end
94
87
  end
95
88
 
96
- # contract C::ArrayOf[Class], C::ArrayOf[C_OBJ_MAYBE_REP] => C::Maybe[Nanoc::Core::OutdatednessStatus]
89
+ contract C::ArrayOf[Class], C::ArrayOf[C_OBJ_MAYBE_REP] => C::Maybe[Nanoc::Core::OutdatednessStatus]
97
90
  def apply_rules_multi(rules, objs)
98
91
  objs.inject(Nanoc::Core::OutdatednessStatus.new) { |acc, elem| apply_rules(rules, elem, acc) }
99
92
  end
100
93
  end
101
94
 
102
- prepend MemoWise
103
-
104
- # include Nanoc::Core::ContractsSupport
95
+ include Nanoc::Core::ContractsSupport
105
96
 
106
97
  attr_reader :checksum_store
107
98
  attr_reader :checksums
@@ -112,11 +103,11 @@ module Nanoc
112
103
 
113
104
  Reasons = Nanoc::Core::OutdatednessReasons
114
105
 
115
- # C_OBJ = C::Or[Nanoc::Core::Item, Nanoc::Core::ItemRep, Nanoc::Core::Configuration, Nanoc::Core::Layout, Nanoc::Core::ItemCollection]
116
- # C_ITEM_OR_REP = C::Or[Nanoc::Core::Item, Nanoc::Core::ItemRep]
117
- # C_ACTION_SEQUENCES = C::HashOf[C_OBJ => Nanoc::Core::ActionSequence]
106
+ C_OBJ = C::Or[Nanoc::Core::Item, Nanoc::Core::ItemRep, Nanoc::Core::Configuration, Nanoc::Core::Layout, Nanoc::Core::ItemCollection]
107
+ C_ITEM_OR_REP = C::Or[Nanoc::Core::Item, Nanoc::Core::ItemRep]
108
+ C_ACTION_SEQUENCES = C::HashOf[C_OBJ => Nanoc::Core::ActionSequence]
118
109
 
119
- # 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
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
120
111
  def initialize(site:, checksum_store:, checksums:, dependency_store:, action_sequence_store:, action_sequences:, reps:)
121
112
  @site = site
122
113
  @checksum_store = checksum_store
@@ -133,12 +124,12 @@ module Nanoc
133
124
  @action_sequences.fetch(rep)
134
125
  end
135
126
 
136
- # contract C_OBJ => C::Bool
127
+ contract C_OBJ => C::Bool
137
128
  def outdated?(obj)
138
129
  outdatedness_reasons_for(obj).any?
139
130
  end
140
131
 
141
- # contract C_OBJ => C::IterOf[Reasons::Generic]
132
+ contract C_OBJ => C::IterOf[Reasons::Generic]
142
133
  def outdatedness_reasons_for(obj)
143
134
  reasons = basic.outdatedness_status_for(obj).reasons
144
135
  if reasons.any?
@@ -152,12 +143,12 @@ module Nanoc
152
143
 
153
144
  private
154
145
 
155
- # contract C::None => Basic
146
+ contract C::None => Basic
156
147
  def basic
157
148
  @_basic ||= Basic.new(outdatedness_checker: self, reps: @reps)
158
149
  end
159
150
 
160
- # contract C_OBJ, Hamster::Set => C::Bool
151
+ contract C_OBJ, Hamster::Set => C::Bool
161
152
  def outdated_due_to_dependencies?(obj, processed = Hamster::Set.new)
162
153
  # Convert from rep to item if necessary
163
154
  obj = obj.item if obj.is_a?(Nanoc::Core::ItemRep)
@@ -189,33 +180,113 @@ module Nanoc
189
180
  is_outdated
190
181
  end
191
182
 
192
- # contract Nanoc::Core::Dependency => C::Bool
183
+ contract Nanoc::Core::Dependency => C::Bool
193
184
  def dependency_causes_outdatedness?(dependency)
194
- return true if dependency.from.nil?
195
-
196
- 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)
197
195
 
198
- active = status.props.active & dependency.props.active
199
- active.delete(:attributes) if attributes_unaffected?(status, dependency)
200
- 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)
201
198
 
202
- active.any?
199
+ active.any?
200
+ end
203
201
  end
204
202
 
205
203
  def attributes_unaffected?(status, dependency)
206
204
  reason = status.reasons.find { |r| r.is_a?(Nanoc::Core::OutdatednessReasons::AttributesModified) }
207
- 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?
208
206
  end
209
207
 
210
- def raw_content_unaffected?(status, dependency)
211
- reason = status.reasons.find { |r| r.is_a?(Nanoc::Core::OutdatednessReasons::DocumentCollectionExtended) }
212
- if reason.nil?
213
- false
214
- elsif !dependency.props.raw_content.is_a?(Enumerable)
215
- false
216
- else
217
- patterns = dependency.props.raw_content.map { |r| Nanoc::Core::Pattern.from(r) }
218
- 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
+ # Ignore any attribute not mentioned in the dependency
273
+ old_object_checksums = old_object_checksums.select { |k, _v| dep_checksums.key?(k) }
274
+ new_object_checksums = new_object_checksums.select { |k, _v| dep_checksums.key?(k) }
275
+
276
+ dep_checksums.any? do |key, dep_value|
277
+ # Get old and new checksum for this particular attribute
278
+ old_value = old_object_checksums[key]
279
+ new_value = new_object_checksums[key]
280
+
281
+ # If either the old or new vale match the value in the dependency,
282
+ # then a potential change is relevant to us, and can cause
283
+ # outdatedness.
284
+ is_match = [old_value, new_value].include?(dep_value)
285
+
286
+ is_changed = old_value != new_value
287
+
288
+ is_match && is_changed
289
+ end
219
290
  end
220
291
  end
221
292
  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
 
@@ -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
@@ -6,11 +6,31 @@ 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| !File.file?(fn) }
9
+ def apply(obj, outdatedness_checker)
10
+ if obj.raw_paths.values.flatten.compact.any? { |fn| !exist?(fn, outdatedness_checker) }
11
11
  Nanoc::Core::OutdatednessReasons::NotWritten
12
12
  end
13
13
  end
14
+
15
+ private
16
+
17
+ def exist?(fn, outdatedness_checker)
18
+ all(outdatedness_checker).include?(fn)
19
+ end
20
+
21
+ def all(outdatedness_checker)
22
+ # NOTE: Cached per outdatedness checker, so that unrelated invocations
23
+ # later on don’t reuse an old cache.
24
+
25
+ @all ||= {}
26
+ @all[outdatedness_checker] ||= Set.new(
27
+ Dir.glob("#{site_root(outdatedness_checker)}/**/*", File::FNM_DOTMATCH),
28
+ )
29
+ end
30
+
31
+ def site_root(outdatedness_checker)
32
+ outdatedness_checker.site.config.output_dir
33
+ end
14
34
  end
15
35
  end
16
36
  end
@@ -22,6 +22,10 @@ module Nanoc
22
22
  props: @props.merge(reason.props),
23
23
  )
24
24
  end
25
+
26
+ def inspect
27
+ "<#{self.class} reasons=#{@reasons.inspect} props=#{@props.inspect}>"
28
+ end
25
29
  end
26
30
  end
27
31
  end
@@ -81,7 +81,7 @@ module Nanoc
81
81
  return unless File.file?(filename)
82
82
 
83
83
  begin
84
- pstore.transaction do
84
+ pstore.transaction(true) do
85
85
  return if pstore[:version] != version
86
86
 
87
87
  self.data = pstore[:data]
@@ -42,9 +42,8 @@ module Nanoc
42
42
  # @return [void]
43
43
  def cleanup(prefix)
44
44
  path = File.join(@root_dir, prefix)
45
- if File.exist?(path)
46
- FileUtils.rm_rf(path)
47
- end
45
+
46
+ FileUtils.rm_rf(path)
48
47
 
49
48
  @counts.delete(prefix)
50
49
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Nanoc
4
4
  module Core
5
- VERSION = '4.12.7'
5
+ VERSION = '4.12.9'
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.7
4
+ version: 4.12.9
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-06-06 00:00:00.000000000 Z
11
+ date: 2022-10-08 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.15
318
+ rubygems_version: 3.3.22
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