nanoc3 3.2.0a3 → 3.2.0a4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. data/.gemtest +0 -0
  2. data/LICENSE +1 -1
  3. data/NEWS.md +23 -4
  4. data/README.md +7 -0
  5. data/lib/nanoc3/base/compilation/checksum_store.rb +17 -90
  6. data/lib/nanoc3/base/compilation/compiled_content_cache.rb +5 -0
  7. data/lib/nanoc3/base/compilation/compiler.rb +112 -175
  8. data/lib/nanoc3/base/compilation/compiler_dsl.rb +54 -11
  9. data/lib/nanoc3/base/compilation/dependency_tracker.rb +32 -65
  10. data/lib/nanoc3/base/compilation/filter.rb +4 -3
  11. data/lib/nanoc3/base/compilation/item_rep_proxy.rb +19 -4
  12. data/lib/nanoc3/base/compilation/item_rep_recorder_proxy.rb +90 -0
  13. data/lib/nanoc3/base/compilation/outdatedness_checker.rb +152 -15
  14. data/lib/nanoc3/base/compilation/outdatedness_reasons.rb +12 -9
  15. data/lib/nanoc3/base/compilation/rule.rb +3 -1
  16. data/lib/nanoc3/base/compilation/rule_memory_calculator.rb +42 -0
  17. data/lib/nanoc3/base/compilation/rule_memory_store.rb +53 -0
  18. data/lib/nanoc3/base/compilation/rules_collection.rb +205 -0
  19. data/lib/nanoc3/base/core_ext/array.rb +20 -0
  20. data/lib/nanoc3/base/core_ext/hash.rb +30 -0
  21. data/lib/nanoc3/base/core_ext/pathname.rb +26 -0
  22. data/lib/nanoc3/base/core_ext/string.rb +12 -0
  23. data/lib/nanoc3/base/core_ext.rb +1 -0
  24. data/lib/nanoc3/base/directed_graph.rb +11 -3
  25. data/lib/nanoc3/base/errors.rb +0 -4
  26. data/lib/nanoc3/base/memoization.rb +72 -0
  27. data/lib/nanoc3/base/result_data/item_rep.rb +64 -25
  28. data/lib/nanoc3/base/source_data/code_snippet.rb +9 -0
  29. data/lib/nanoc3/base/source_data/configuration.rb +20 -0
  30. data/lib/nanoc3/base/source_data/item.rb +29 -4
  31. data/lib/nanoc3/base/source_data/layout.rb +20 -1
  32. data/lib/nanoc3/base/source_data/site.rb +49 -26
  33. data/lib/nanoc3/base/store.rb +10 -1
  34. data/lib/nanoc3/base.rb +6 -1
  35. data/lib/nanoc3/cli/base.rb +20 -7
  36. data/lib/nanoc3/cli/commands/compile.rb +0 -2
  37. data/lib/nanoc3/cli/commands/create_site.rb +16 -7
  38. data/lib/nanoc3/cli/commands/debug.rb +3 -3
  39. data/lib/nanoc3/cli/commands/view.rb +1 -0
  40. data/lib/nanoc3/cli/commands/watch.rb +2 -1
  41. data/lib/nanoc3/data_sources/deprecated/delicious.rb +0 -2
  42. data/lib/nanoc3/data_sources/deprecated/last_fm.rb +0 -2
  43. data/lib/nanoc3/data_sources/deprecated/twitter.rb +0 -2
  44. data/lib/nanoc3/data_sources/filesystem.rb +17 -3
  45. data/lib/nanoc3/data_sources/filesystem_unified.rb +17 -17
  46. data/lib/nanoc3/extra/auto_compiler.rb +5 -1
  47. data/lib/nanoc3/extra/core_ext/time.rb +1 -1
  48. data/lib/nanoc3/extra/file_proxy.rb +11 -1
  49. data/lib/nanoc3/extra/validators/links.rb +1 -1
  50. data/lib/nanoc3/filters/asciidoc.rb +3 -3
  51. data/lib/nanoc3/filters/colorize_syntax.rb +106 -27
  52. data/lib/nanoc3/filters/erb.rb +16 -6
  53. data/lib/nanoc3/filters/erubis.rb +5 -1
  54. data/lib/nanoc3/filters/haml.rb +2 -1
  55. data/lib/nanoc3/filters/less.rb +3 -6
  56. data/lib/nanoc3/filters/mustache.rb +3 -0
  57. data/lib/nanoc3/filters/redcarpet.rb +27 -0
  58. data/lib/nanoc3/filters/sass.rb +1 -5
  59. data/lib/nanoc3/filters/slim.rb +25 -0
  60. data/lib/nanoc3/filters/typogruby.rb +23 -0
  61. data/lib/nanoc3/filters.rb +6 -0
  62. data/lib/nanoc3/helpers/blogging.rb +22 -26
  63. data/lib/nanoc3/helpers/rendering.rb +1 -1
  64. data/lib/nanoc3/helpers/xml_sitemap.rb +11 -2
  65. data/lib/nanoc3.rb +24 -3
  66. data/nanoc3.gemspec +4 -3
  67. data/tasks/clean.rake +11 -0
  68. data/tasks/doc.rake +14 -0
  69. data/tasks/test.rake +38 -0
  70. data/test/base/core_ext/array_spec.rb +55 -0
  71. data/test/base/core_ext/hash_spec.rb +82 -0
  72. data/test/base/core_ext/pathname_spec.rb +29 -0
  73. data/test/base/core_ext/string_spec.rb +39 -0
  74. data/test/base/test_checksum_store.rb +37 -0
  75. data/test/base/test_code_snippet.rb +33 -0
  76. data/test/base/test_compiler.rb +303 -0
  77. data/test/base/test_compiler_dsl.rb +156 -0
  78. data/test/base/test_context.rb +33 -0
  79. data/test/base/test_data_source.rb +48 -0
  80. data/test/base/test_dependency_tracker.rb +264 -0
  81. data/test/base/test_directed_graph.rb +285 -0
  82. data/test/base/test_filter.rb +85 -0
  83. data/test/base/test_item.rb +164 -0
  84. data/test/base/test_item_rep.rb +555 -0
  85. data/test/base/test_layout.rb +44 -0
  86. data/test/base/test_memoization.rb +53 -0
  87. data/test/base/test_notification_center.rb +36 -0
  88. data/test/base/test_outdatedness_checker.rb +365 -0
  89. data/test/base/test_plugin.rb +32 -0
  90. data/test/base/test_rule.rb +21 -0
  91. data/test/base/test_rule_context.rb +67 -0
  92. data/test/base/test_site.rb +144 -0
  93. data/test/cli/commands/test_compile.rb +12 -0
  94. data/test/cli/commands/test_create_item.rb +12 -0
  95. data/test/cli/commands/test_create_layout.rb +28 -0
  96. data/test/cli/commands/test_create_site.rb +24 -0
  97. data/test/cli/commands/test_help.rb +12 -0
  98. data/test/cli/commands/test_info.rb +12 -0
  99. data/test/cli/commands/test_update.rb +12 -0
  100. data/test/cli/test_logger.rb +12 -0
  101. data/test/data_sources/test_filesystem.rb +420 -0
  102. data/test/data_sources/test_filesystem_unified.rb +562 -0
  103. data/test/data_sources/test_filesystem_verbose.rb +359 -0
  104. data/test/extra/core_ext/test_enumerable.rb +32 -0
  105. data/test/extra/core_ext/test_time.rb +17 -0
  106. data/test/extra/deployers/test_rsync.rb +234 -0
  107. data/test/extra/test_auto_compiler.rb +417 -0
  108. data/test/extra/test_file_proxy.rb +21 -0
  109. data/test/extra/test_vcs.rb +24 -0
  110. data/test/extra/validators/test_links.rb +53 -0
  111. data/test/extra/validators/test_w3c.rb +49 -0
  112. data/test/filters/test_asciidoc.rb +22 -0
  113. data/test/filters/test_bluecloth.rb +20 -0
  114. data/test/filters/test_coderay.rb +46 -0
  115. data/test/filters/test_colorize_syntax.rb +149 -0
  116. data/test/filters/test_erb.rb +101 -0
  117. data/test/filters/test_erubis.rb +72 -0
  118. data/test/filters/test_haml.rb +98 -0
  119. data/test/filters/test_kramdown.rb +20 -0
  120. data/test/filters/test_less.rb +59 -0
  121. data/test/filters/test_markaby.rb +26 -0
  122. data/test/filters/test_maruku.rb +20 -0
  123. data/test/filters/test_mustache.rb +27 -0
  124. data/test/filters/test_rainpress.rb +31 -0
  125. data/test/filters/test_rdiscount.rb +33 -0
  126. data/test/filters/test_rdoc.rb +18 -0
  127. data/test/filters/test_redcarpet.rb +63 -0
  128. data/test/filters/test_redcloth.rb +35 -0
  129. data/test/filters/test_relativize_paths.rb +231 -0
  130. data/test/filters/test_rubypants.rb +20 -0
  131. data/test/filters/test_sass.rb +103 -0
  132. data/test/filters/test_slim.rb +37 -0
  133. data/test/filters/test_typogruby.rb +23 -0
  134. data/test/helper.rb +161 -0
  135. data/test/helpers/test_blogging.rb +756 -0
  136. data/test/helpers/test_breadcrumbs.rb +83 -0
  137. data/test/helpers/test_capturing.rb +43 -0
  138. data/test/helpers/test_filtering.rb +108 -0
  139. data/test/helpers/test_html_escape.rb +34 -0
  140. data/test/helpers/test_link_to.rb +251 -0
  141. data/test/helpers/test_rendering.rb +90 -0
  142. data/test/helpers/test_tagging.rb +89 -0
  143. data/test/helpers/test_text.rb +26 -0
  144. data/test/helpers/test_xml_sitemap.rb +105 -0
  145. data/test/tasks/test_clean.rb +69 -0
  146. metadata +96 -27
  147. data/lib/nanoc3/base/compilation/checksummer.rb +0 -68
@@ -5,16 +5,14 @@ module Nanoc3
5
5
  # Contains methods that will be executed by the site’s `Rules` file.
6
6
  class CompilerDSL
7
7
 
8
- # @return [Nanoc3::Compiler] The compiler where this DSL belongs to.
9
- attr_reader :compiler
10
-
11
- # Creates a new compiler DSL for the given compiler.
8
+ # Creates a new compiler DSL for the given collection of rules.
12
9
  #
13
10
  # @api private
14
11
  #
15
- # @param [Nanoc3::Site] site The site this DSL belongs to
16
- def initialize(compiler)
17
- @compiler = compiler
12
+ # @param [Nanoc3::RulesCollection] rules_collection The collection of rules
13
+ # to modify when loading this DSL
14
+ def initialize(rules_collection)
15
+ @rules_collection = rules_collection
18
16
  end
19
17
 
20
18
  # Creates a preprocessor block that will be executed after all data is
@@ -24,7 +22,7 @@ module Nanoc3
24
22
  #
25
23
  # @return [void]
26
24
  def preprocess(&block)
27
- compiler.preprocessor = block
25
+ @rules_collection.preprocessor = block
28
26
  end
29
27
 
30
28
  # Creates a compilation rule for all items whose identifier match the
@@ -68,7 +66,7 @@ module Nanoc3
68
66
 
69
67
  # Create rule
70
68
  rule = Rule.new(identifier_to_regex(identifier), rep_name, block)
71
- compiler.item_compilation_rules << rule
69
+ @rules_collection.add_item_compilation_rule(rule)
72
70
  end
73
71
 
74
72
  # Creates a routing rule for all items whose identifier match the
@@ -113,7 +111,7 @@ module Nanoc3
113
111
 
114
112
  # Create rule
115
113
  rule = Rule.new(identifier_to_regex(identifier), rep_name, block, :snapshot_name => snapshot_name)
116
- compiler.item_routing_rules << rule
114
+ @rules_collection.add_item_routing_rule(rule)
117
115
  end
118
116
 
119
117
  # Creates a layout rule for all layouts whose identifier match the given
@@ -141,7 +139,52 @@ module Nanoc3
141
139
  #
142
140
  # layout '/custom/', :haml, :format => :html5
143
141
  def layout(identifier, filter_name, params={})
144
- compiler.layout_filter_mapping[identifier_to_regex(identifier)] = [ filter_name, params ]
142
+ @rules_collection.layout_filter_mapping[identifier_to_regex(identifier)] = [ filter_name, params ]
143
+ end
144
+
145
+ # Creates a pair of compilation and routing rules that indicate that the
146
+ # specified item(s) should be copied to the output folder as-is. The items
147
+ # are selected using an identifier, which may either be a string
148
+ # containing the `*` wildcard, or a regular expression.
149
+ #
150
+ # This meta-rule will be applicable to reps with a name equal to
151
+ # `:default`; this can be changed by giving an explicit `:rep` parameter.
152
+ #
153
+ # @param [String] identifier A pattern matching identifiers of items that
154
+ # should be processed using this meta-rule
155
+ #
156
+ # @option params [Symbol] :rep (:default) The name of the representation
157
+ # that should be routed using this rule
158
+ #
159
+ # @return [void]
160
+ #
161
+ # @since 3.2.0
162
+ #
163
+ # @example Copying the `/foo/` item as-is
164
+ #
165
+ # passthrough '/foo/'
166
+ #
167
+ # @example Copying the `:raw` rep of the `/bar/` item as-is
168
+ #
169
+ # passthrough '/bar/', :rep => :raw
170
+ def passthrough(identifier, params={})
171
+ # Require no block
172
+ raise ArgumentError.new("#passthrough does not require a block") if block_given?
173
+
174
+ # Get rep name
175
+ rep_name = params[:rep] || :default
176
+
177
+ # Create compilation rule
178
+ compilation_block = proc { }
179
+ compilation_rule = Rule.new(identifier_to_regex(identifier), rep_name, compilation_block)
180
+ @rules_collection.add_item_compilation_rule(compilation_rule, :before)
181
+
182
+ # Create routing rule
183
+ routing_block = proc do
184
+ item.identifier.chop + '.' + item[:extension]
185
+ end
186
+ routing_rule = Rule.new(identifier_to_regex(identifier), rep_name, routing_block)
187
+ @rules_collection.add_item_routing_rule(routing_rule, :before)
145
188
  end
146
189
 
147
190
  private
@@ -1,7 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'pstore'
4
-
5
3
  module Nanoc3
6
4
 
7
5
  # Responsible for remembering dependencies between items and layouts. It is
@@ -18,9 +16,9 @@ module Nanoc3
18
16
  # attribute of item B and vice versa without problems).
19
17
  #
20
18
  # The dependency tracker remembers the dependency information between runs.
21
- # Dependency information is stored in the `tmp/dependencies` file. This file
22
- # also contains a version number; when a dependencies file with an
23
- # incompatible version is found, it is ignored.
19
+ # Dependency information is stored in the `tmp/dependencies` file.
20
+ #
21
+ # @api private
24
22
  class DependencyTracker < ::Nanoc3::Store
25
23
 
26
24
  # @return [Array<Nanoc3::Item, Nanoc3::Layout>] The list of items and
@@ -41,8 +39,6 @@ module Nanoc3
41
39
  @objects = objects
42
40
 
43
41
  @graph = Nanoc3::DirectedGraph.new([ nil ] + @objects)
44
- @previous_objects = []
45
- @objects_outdated_due_to_dependencies = Set.new
46
42
  end
47
43
 
48
44
  # Starts listening for dependency messages (`:visit_started` and
@@ -76,33 +72,25 @@ module Nanoc3
76
72
  Nanoc3::NotificationCenter.remove(:visit_ended, self)
77
73
  end
78
74
 
79
- # Checks whether the given object is outdated due to dependencies, i.e.
80
- # check whether there are other objects that are outdated that cause this
81
- # object to be outdated.
82
- #
83
- # @param [Nanoc3::Item, Nanoc3::Layout] obj The object to check
84
- #
85
- # @return [Boolean] true if the given object is outdated due to
86
- # dependencies, false if not.
87
- def outdated_due_to_dependencies?(obj)
88
- @objects_outdated_due_to_dependencies.include?(obj)
89
- end
90
-
91
75
  # Returns the direct dependencies for the given object.
92
76
  #
93
- # The direct dependencies of the given object include the items
94
- # and layouts that, when outdated will cause the given object to be marked
95
- # as outdated. Indirect dependencies will not be returned (e.g. if A
96
- # depends on B which depends on C, then the direct dependencies of A do
97
- # not include C).
77
+ # The direct dependencies of the given object include the items and
78
+ # layouts that, when outdated will cause the given object to be marked as
79
+ # outdated. Indirect dependencies will not be returned (e.g. if A depends
80
+ # on B which depends on C, then the direct dependencies of A do not
81
+ # include C).
82
+ #
83
+ # The direct predecessors can include nil, which indicates an item that is
84
+ # no longer present in the site.
98
85
  #
99
86
  # @param [Nanoc3::Item, Nanoc3::Layout] object The object for
100
87
  # which to fetch the direct predecessors
101
88
  #
102
- # @return [Array<Nanoc3::Item, Nanoc3::Layout>] The direct predecessors of
89
+ # @return [Array<Nanoc3::Item, Nanoc3::Layout, nil>] The direct
90
+ # predecessors of
103
91
  # the given object
104
- def direct_predecessors_of(object)
105
- @graph.direct_predecessors_of(object).compact
92
+ def objects_causing_outdatedness_of(object)
93
+ @graph.direct_predecessors_of(object)
106
94
  end
107
95
 
108
96
  # Returns the direct inverse dependencies for the given object.
@@ -118,7 +106,7 @@ module Nanoc3
118
106
  #
119
107
  # @return [Array<Nanoc3::Item, Nanoc3::Layout>] The direct successors of
120
108
  # the given object
121
- def direct_successors_of(object)
109
+ def objects_outdated_due_to(object)
122
110
  @graph.direct_successors_of(object).compact
123
111
  end
124
112
 
@@ -138,35 +126,6 @@ module Nanoc3
138
126
  @graph.add_edge(dst, src) unless src == dst
139
127
  end
140
128
 
141
- # Traverses the dependency graph and marks all objects that (directly or
142
- # indirectly) depend on an outdated object as outdated.
143
- #
144
- # @return [void]
145
- def propagate_outdatedness
146
- # Unmark everything
147
- @objects_outdated_due_to_dependencies.clear
148
-
149
- # Mark new objects as outdated
150
- added_objects = @objects - @previous_objects
151
- @objects_outdated_due_to_dependencies.merge(added_objects)
152
-
153
- # Mark successors of outdated objects as outdated
154
- require 'set'
155
- unprocessed = [ nil ] + @objects.select { |o| compiler.outdated?(o) }
156
- seen = Set.new(unprocessed)
157
- until unprocessed.empty?
158
- obj = unprocessed.shift
159
-
160
- self.direct_successors_of(obj).each do |successor|
161
- next if seen.include?(successor)
162
- seen << successor
163
-
164
- @objects_outdated_due_to_dependencies << successor
165
- unprocessed << successor
166
- end
167
- end
168
- end
169
-
170
129
  # Empties the list of dependencies for the given object. This is necessary
171
130
  # before recompiling the given object, because otherwise old dependencies
172
131
  # will stick around and new dependencies will appear twice. This function
@@ -182,11 +141,6 @@ module Nanoc3
182
141
  @graph.delete_edges_to(object)
183
142
  end
184
143
 
185
- # @deprecated Use {#propagate_outdatedness} instead.
186
- def mark_outdated_items
187
- propagate_outdatedness
188
- end
189
-
190
144
  # @deprecated Use {#store} instead
191
145
  def store_graph
192
146
  self.store
@@ -197,6 +151,11 @@ module Nanoc3
197
151
  self.load
198
152
  end
199
153
 
154
+ # @see Nanoc3::Store#unload
155
+ def unload
156
+ @graph = Nanoc3::DirectedGraph.new([ nil ] + @objects)
157
+ end
158
+
200
159
  protected
201
160
 
202
161
  def data
@@ -211,17 +170,25 @@ module Nanoc3
211
170
  @graph = Nanoc3::DirectedGraph.new([ nil ] + @objects)
212
171
 
213
172
  # Load vertices
214
- @previous_objects = new_data[:vertices].map do |reference|
173
+ previous_objects = new_data[:vertices].map do |reference|
215
174
  @objects.find { |obj| reference == obj.reference }
216
175
  end
217
176
 
218
177
  # Load edges
219
178
  new_data[:edges].each do |edge|
220
179
  from_index, to_index = *edge
221
- from = from_index && @previous_objects[from_index]
222
- to = to_index && @previous_objects[to_index]
180
+ from = from_index && previous_objects[from_index]
181
+ to = to_index && previous_objects[to_index]
223
182
  @graph.add_edge(from, to)
224
183
  end
184
+
185
+ # Record dependency from all items on new items
186
+ new_objects = (@objects - previous_objects)
187
+ new_objects.each do |new_obj|
188
+ @objects.each do |obj|
189
+ @graph.add_edge(new_obj, obj)
190
+ end
191
+ end
225
192
  end
226
193
 
227
194
  end
@@ -110,17 +110,18 @@ module Nanoc3
110
110
  # used on binary items. When running a binary filter on a file, the
111
111
  # resulting file must end up in the location returned by this method.
112
112
  #
113
+ # The returned filename will be absolute, so it is safe to change to
114
+ # another directory inside the filter.
115
+ #
113
116
  # @return [String] The output filename
114
117
  def output_filename
115
118
  @output_filename ||= begin
116
- require 'tempfile'
117
-
118
119
  FileUtils.mkdir_p(TMP_BINARY_ITEMS_DIR)
119
120
  tempfile = Tempfile.new(filename.gsub(/[^a-z]/, '-'), TMP_BINARY_ITEMS_DIR)
120
121
  new_filename = tempfile.path
121
122
  tempfile.close!
122
123
 
123
- new_filename
124
+ File.expand_path(new_filename)
124
125
  end
125
126
  end
126
127
 
@@ -1,7 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'forwardable'
4
-
5
3
  module Nanoc3
6
4
 
7
5
  # Represents an item representation, but provides an interface that is
@@ -65,19 +63,36 @@ module Nanoc3
65
63
  set_assigns
66
64
 
67
65
  layout = layout_with_identifier(layout_identifier)
68
- filter_name, filter_args = @compiler.filter_for_layout(layout)
66
+ filter_name, filter_args = @compiler.rules_collection.filter_for_layout(layout)
69
67
 
70
68
  @item_rep.layout(layout, filter_name, filter_args)
71
69
  end
72
70
 
71
+ # Returns true because this item is already a proxy, and therefore doesn’t
72
+ # need to be wrapped anymore.
73
+ #
74
+ # @api private
75
+ #
76
+ # @return [true]
77
+ #
78
+ # @see Nanoc3::ItemRep#is_proxy?
79
+ # @see Nanoc3::ItemRepRecorderProxy#is_proxy?
80
+ def is_proxy?
81
+ true
82
+ end
83
+
73
84
  private
74
85
 
75
86
  def set_assigns
76
87
  @item_rep.assigns = @compiler.assigns_for(@item_rep)
77
88
  end
78
89
 
90
+ def layouts
91
+ @compiler.site.layouts
92
+ end
93
+
79
94
  def layout_with_identifier(layout_identifier)
80
- layout ||= @compiler.site.layouts.find { |l| l.identifier == layout_identifier.cleaned_identifier }
95
+ layout ||= layouts.find { |l| l.identifier == layout_identifier.cleaned_identifier }
81
96
  raise Nanoc3::Errors::UnknownLayout.new(layout_identifier) if layout.nil?
82
97
  layout
83
98
  end
@@ -0,0 +1,90 @@
1
+ # encoding: utf-8
2
+
3
+ module Nanoc3
4
+
5
+ # Represents a fake iem representation that does not actually perform any
6
+ # actual filtering, layouting or snapshotting, but instead keeps track of
7
+ # what would happen if a real item representation would have been used
8
+ # instead. It therefore “records” the actions that happens upon it.
9
+ #
10
+ # The list of recorded actions is used during compilation to determine
11
+ # whether an item representation needs to be recompiled: if the list of
12
+ # actions is different from the list of actions from the previous
13
+ # compilation run, the item needs to be recompiled; if it is the same, it
14
+ # may not need to be recompiled.
15
+ #
16
+ # @api private
17
+ class ItemRepRecorderProxy
18
+
19
+ extend Forwardable
20
+
21
+ def_delegators :@item_rep, :item, :name, :binary, :binary?, :compiled_content, :has_snapshot?, :raw_path, :path, :assigns, :assigns=
22
+
23
+ # @return The list of recorded actions (“rule memory”)
24
+ #
25
+ # @example The compilation rule and the corresponding rule memory
26
+ #
27
+ # # rule
28
+ # compile '/foo/' do
29
+ # filter :erb
30
+ # filter :myfilter, :arg1 => 'stuff'
31
+ # layout 'meh'
32
+ # end
33
+ #
34
+ # # memory
35
+ # [
36
+ # [ :filter, :erb, {} ],
37
+ # [ :filter, :myfilter, { :arg1 => 'stuff' } ],
38
+ # [ :layout, 'meh' ]
39
+ # ]
40
+ #
41
+ # @return [Array] The rule memory
42
+ attr_reader :rule_memory
43
+
44
+ # @param [Nanoc3::ItemRep] item_rep The item representation that this
45
+ # proxy should behave like
46
+ def initialize(item_rep)
47
+ @item_rep = item_rep
48
+ @rule_memory = []
49
+ end
50
+
51
+ # @return [void]
52
+ #
53
+ # @see Nanoc3::ItemRepProxy#filter, Nanoc3::ItemRep#filter
54
+ def filter(name, args={})
55
+ @rule_memory << [ :filter, name, args ]
56
+ end
57
+
58
+ # @return [void]
59
+ #
60
+ # @see Nanoc3::ItemRepProxy#layout, Nanoc3::ItemRep#layout
61
+ def layout(layout_identifier)
62
+ @rule_memory << [ :layout, layout_identifier ]
63
+ end
64
+
65
+ # @return [void]
66
+ #
67
+ # @see Nanoc3::ItemRep#snapshot
68
+ def snapshot(snapshot_name, params={})
69
+ @rule_memory << [ :snapshot, snapshot_name, params ]
70
+ end
71
+
72
+ # @return [{}]
73
+ def content
74
+ {}
75
+ end
76
+
77
+ # Returns true because this item is already a proxy, and therefore doesn’t
78
+ # need to be wrapped anymore.
79
+ #
80
+ # @return [true]
81
+ #
82
+ # @see Nanoc3::ItemRep#is_proxy?
83
+ # @see Nanoc3::ItemRepProxy#is_proxy?
84
+ def is_proxy?
85
+ true
86
+ end
87
+
88
+ end
89
+
90
+ end
@@ -7,16 +7,27 @@ module Nanoc3
7
7
  # @api private
8
8
  class OutdatednessChecker
9
9
 
10
+ extend Nanoc3::Memoization
11
+
10
12
  # @option params [Nanoc3::Site] :site (nil) The site this outdatedness
11
13
  # checker belongs to.
12
14
  #
13
15
  # @option params [Nanoc3::ChecksumStore] :checksum_store (nil) The
14
16
  # checksum store where checksums of items, layouts, … are stored.
17
+ #
18
+ # @option params [Nanoc3::DependencyTracker] :dependency_tracker (nil) The
19
+ # dependency tracker for the given site.
15
20
  def initialize(params={})
16
- @site = params[:site] if params.has_key?(:site)
17
- @checksum_store = params[:checksum_store] if params.has_key?(:checksum_store)
18
-
21
+ @site = params[:site] or raise ArgumentError,
22
+ 'Nanoc3::OutdatednessChecker#initialize needs a :site parameter'
23
+ @checksum_store = params[:checksum_store] or raise ArgumentError,
24
+ 'Nanoc3::OutdatednessChecker#initialize needs a :checksum_store parameter'
25
+ @dependency_tracker = params[:dependency_tracker] or raise ArgumentError,
26
+ 'Nanoc3::OutdatednessChecker#initialize needs a :dependency_tracker parameter'
27
+
28
+ @basic_outdatedness_reasons = {}
19
29
  @outdatedness_reasons = {}
30
+ @objects_outdated_due_to_dependencies = {}
20
31
  end
21
32
 
22
33
  # Checks whether the given object is outdated and therefore needs to be
@@ -38,34 +49,72 @@ module Nanoc3
38
49
  # @return [Nanoc3::OutdatednessReasons::Generic, nil] The reason why the
39
50
  # given object is outdated, or nil if the object is not outdated.
40
51
  def outdatedness_reason_for(obj)
52
+ reason = basic_outdatedness_reason_for(obj)
53
+ if reason.nil? && outdated_due_to_dependencies?(obj)
54
+ reason = Nanoc3::OutdatednessReasons::DependenciesOutdated
55
+ end
56
+ reason
57
+ end
58
+ memoize :outdatedness_reason_for
59
+
60
+ private
61
+
62
+ # Checks whether the given object is outdated and therefore needs to be
63
+ # recompiled. This method does not take dependencies into account; use
64
+ # {#outdated?} if you want to include dependencies in the outdatedness
65
+ # check.
66
+ #
67
+ # @param [Nanoc3::Item, Nanoc3::ItemRep, Nanoc3::Layout] obj The object
68
+ # whose outdatedness should be checked.
69
+ #
70
+ # @return [Boolean] true if the object is outdated, false otherwise
71
+ def basic_outdated?(obj)
72
+ !basic_outdatedness_reason_for(obj).nil?
73
+ end
74
+
75
+ # Calculates the reason why the given object is outdated. This method does
76
+ # not take dependencies into account; use {#outdatedness_reason_for?} if
77
+ # you want to include dependencies in the outdatedness check.
78
+ #
79
+ # @param [Nanoc3::Item, Nanoc3::ItemRep, Nanoc3::Layout] obj The object
80
+ # whose outdatedness reason should be calculated.
81
+ #
82
+ # @return [Nanoc3::OutdatednessReasons::Generic, nil] The reason why the
83
+ # given object is outdated, or nil if the object is not outdated.
84
+ def basic_outdatedness_reason_for(obj)
41
85
  case obj.type
42
86
  when :item_rep
87
+ # Outdated if rules outdated
88
+ return Nanoc3::OutdatednessReasons::RulesModified if
89
+ rule_memory_differs_for(obj)
90
+
43
91
  # Outdated if checksums are missing or different
44
- return Nanoc3::OutdatednessReasons::NotEnoughData if !checksum_store.checksums_available?(obj.item)
45
- return Nanoc3::OutdatednessReasons::SourceModified if !checksum_store.checksums_identical?(obj.item)
92
+ return Nanoc3::OutdatednessReasons::NotEnoughData if !checksums_available?(obj.item)
93
+ return Nanoc3::OutdatednessReasons::SourceModified if !checksums_identical?(obj.item)
46
94
 
47
95
  # Outdated if compiled file doesn't exist (yet)
48
96
  return Nanoc3::OutdatednessReasons::NotWritten if obj.raw_path && !File.file?(obj.raw_path)
49
97
 
50
98
  # Outdated if code snippets outdated
51
- return Nanoc3::OutdatednessReasons::CodeSnippetsModified if @site.code_snippets.any? do |cs|
52
- checksum_store.object_modified?(cs)
99
+ return Nanoc3::OutdatednessReasons::CodeSnippetsModified if site.code_snippets.any? do |cs|
100
+ object_modified?(cs)
53
101
  end
54
102
 
55
103
  # Outdated if configuration outdated
56
- return Nanoc3::OutdatednessReasons::ConfigurationModified if checksum_store.object_modified?(@site.config)
57
-
58
- # Outdated if rules outdated
59
- return Nanoc3::OutdatednessReasons::RulesModified if checksum_store.object_modified?(@site.compiler.rules_with_reference)
104
+ return Nanoc3::OutdatednessReasons::ConfigurationModified if object_modified?(site.config)
60
105
 
61
106
  # Not outdated
62
107
  return nil
63
108
  when :item
64
- obj.reps.find { |rep| outdatedness_reason_for(rep) }
109
+ obj.reps.find { |rep| basic_outdatedness_reason_for(rep) }
65
110
  when :layout
111
+ # Outdated if rules outdated
112
+ return Nanoc3::OutdatednessReasons::RulesModified if
113
+ rule_memory_differs_for(obj)
114
+
66
115
  # Outdated if checksums are missing or different
67
- return Nanoc3::OutdatednessReasons::NotEnoughData if !checksum_store.checksums_available?(obj)
68
- return Nanoc3::OutdatednessReasons::SourceModified if !checksum_store.checksums_identical?(obj)
116
+ return Nanoc3::OutdatednessReasons::NotEnoughData if !checksums_available?(obj)
117
+ return Nanoc3::OutdatednessReasons::SourceModified if !checksums_identical?(obj)
69
118
 
70
119
  # Not outdated
71
120
  return nil
@@ -73,14 +122,102 @@ module Nanoc3
73
122
  raise RuntimeError, "do not know how to check outdatedness of #{obj.inspect}"
74
123
  end
75
124
  end
125
+ memoize :basic_outdatedness_reason_for
76
126
 
77
- private
127
+ # Checks whether the given object is outdated due to dependencies.
128
+ #
129
+ # @param [Nanoc3::Item, Nanoc3::ItemRep, Nanoc3::Layout] obj The object
130
+ # whose outdatedness should be checked.
131
+ #
132
+ # @param [Set] processed The collection of items that has been visited
133
+ # during this outdatedness check. This is used to prevent checks for
134
+ # items that (indirectly) depend on their own from looping
135
+ # indefinitely. It should not be necessary to pass this a custom value.
136
+ #
137
+ # @return [Boolean] true if the object is outdated, false otherwise
138
+ def outdated_due_to_dependencies?(obj, processed=Set.new)
139
+ # Convert from rep to item if necessary
140
+ obj = obj.item if obj.type == :item_rep
141
+
142
+ # Get from cache
143
+ if @objects_outdated_due_to_dependencies.has_key?(obj)
144
+ return @objects_outdated_due_to_dependencies[obj]
145
+ end
146
+
147
+ # Check processed
148
+ # Don’t return true; the false will be or’ed into a true if there
149
+ # really is a dependency that is causing outdatedness.
150
+ return false if processed.include?(obj)
151
+
152
+ # Calculate
153
+ is_outdated = dependency_tracker.objects_causing_outdatedness_of(obj).any? do |other|
154
+ other.nil? || basic_outdated?(other) || outdated_due_to_dependencies?(other, processed.merge([obj]))
155
+ end
156
+
157
+ # Cache
158
+ @objects_outdated_due_to_dependencies[obj] = is_outdated
159
+
160
+ # Done
161
+ is_outdated
162
+ end
163
+
164
+ # @param [Nanoc3::ItemRep, Nanoc3::Layout] obj The layout or item
165
+ # representation to check the rule memory for
166
+ #
167
+ # @return [Boolean] true if the rule memory for the given item
168
+ # represenation has changed, false otherwise
169
+ def rule_memory_differs_for(obj)
170
+ rules_collection.rule_memory_differs_for(obj)
171
+ end
172
+ memoize :rule_memory_differs_for
173
+
174
+ # @param obj
175
+ #
176
+ # @return [Boolean] false if either the new or the old checksum for the
177
+ # given object is not available, true if both checksums are available
178
+ def checksums_available?(obj)
179
+ !!checksum_store[obj] && obj.checksum
180
+ end
181
+ memoize :checksums_available?
182
+
183
+ # @param obj
184
+ #
185
+ # @return [Boolean] false if the old and new checksums for the given
186
+ # object differ, true if they are identical
187
+ def checksums_identical?(obj)
188
+ checksum_store[obj] == obj.checksum
189
+ end
190
+ memoize :checksums_identical?
191
+
192
+ # @param obj
193
+ #
194
+ # @return [Boolean] true if the old and new checksums for the given object
195
+ # are available and identical, false otherwise
196
+ def object_modified?(obj)
197
+ !checksums_available?(obj) || !checksums_identical?(obj)
198
+ end
199
+ memoize :object_modified?
78
200
 
79
201
  # @return [Nanoc3::ChecksumStore] The checksum store
80
202
  def checksum_store
81
203
  @checksum_store
82
204
  end
83
205
 
206
+ # TODO document
207
+ def rules_collection
208
+ site.compiler.rules_collection
209
+ end
210
+
211
+ # @return [Nanoc3::DependencyTracker] The dependency tracker
212
+ def dependency_tracker
213
+ @dependency_tracker
214
+ end
215
+
216
+ # @return [Nanoc3::Site] The site
217
+ def site
218
+ @site
219
+ end
220
+
84
221
  end
85
222
 
86
223
  end
@@ -20,24 +20,27 @@ module Nanoc3
20
20
 
21
21
  end
22
22
 
23
- NotEnoughData = Generic.new(
24
- 'Not enough data is present to correctly determine whether the item is outdated.')
25
-
26
- NotWritten = Generic.new(
27
- 'This item representation has not yet been written to the output directory (but it does have a path).')
28
-
29
- SourceModified = Generic.new(
30
- 'The source file of this item has been modified since the last time the site was compiled.')
31
-
32
23
  CodeSnippetsModified = Generic.new(
33
24
  'The code snippets have been modified since the last time the site was compiled.')
34
25
 
35
26
  ConfigurationModified = Generic.new(
36
27
  'The site configuration has been modified since the last time the site was compiled.')
37
28
 
29
+ DependenciesOutdated = Generic.new(
30
+ 'This item uses content or attributes that have changed since the last time the site was compiled.')
31
+
32
+ NotEnoughData = Generic.new(
33
+ 'Not enough data is present to correctly determine whether the item is outdated.')
34
+
35
+ NotWritten = Generic.new(
36
+ 'This item representation has not yet been written to the output directory (but it does have a path).')
37
+
38
38
  RulesModified = Generic.new(
39
39
  'The rules file has been modified since the last time the site was compiled.')
40
40
 
41
+ SourceModified = Generic.new(
42
+ 'The source file of this item has been modified since the last time the site was compiled.')
43
+
41
44
  end
42
45
 
43
46
  end