nanoc-core 4.12.7 → 4.12.9

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: 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