nanoc3 3.1.9 → 3.2.0a1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. data/LICENSE +1 -1
  2. data/NEWS.md +0 -50
  3. data/README.md +3 -15
  4. data/bin/nanoc3 +2 -0
  5. data/lib/nanoc3/base/checksummer.rb +40 -0
  6. data/lib/nanoc3/base/code_snippet.rb +30 -12
  7. data/lib/nanoc3/base/compiled_content_cache.rb +86 -0
  8. data/lib/nanoc3/base/compiler.rb +134 -95
  9. data/lib/nanoc3/base/compiler_dsl.rb +12 -11
  10. data/lib/nanoc3/base/core_ext/string.rb +2 -2
  11. data/lib/nanoc3/base/data_source.rb +17 -16
  12. data/lib/nanoc3/base/dependency_tracker.rb +102 -121
  13. data/lib/nanoc3/base/directed_graph.rb +65 -3
  14. data/lib/nanoc3/base/errors.rb +20 -16
  15. data/lib/nanoc3/base/item.rb +58 -50
  16. data/lib/nanoc3/base/item_rep.rb +177 -150
  17. data/lib/nanoc3/base/layout.rb +51 -18
  18. data/lib/nanoc3/base/notification_center.rb +8 -8
  19. data/lib/nanoc3/base/plugin_registry.rb +9 -9
  20. data/lib/nanoc3/base/rule.rb +18 -9
  21. data/lib/nanoc3/base/rule_context.rb +5 -5
  22. data/lib/nanoc3/base/site.rb +135 -47
  23. data/lib/nanoc3/base.rb +21 -19
  24. data/lib/nanoc3/cli/base.rb +51 -74
  25. data/lib/nanoc3/cli/commands/autocompile.rb +3 -0
  26. data/lib/nanoc3/cli/commands/compile.rb +35 -74
  27. data/lib/nanoc3/cli/commands/create_site.rb +17 -5
  28. data/lib/nanoc3/cli/commands/debug.rb +11 -4
  29. data/lib/nanoc3/cli/commands/view.rb +0 -1
  30. data/lib/nanoc3/cli/commands/watch.rb +148 -0
  31. data/lib/nanoc3/cli/commands.rb +1 -0
  32. data/lib/nanoc3/cli/logger.rb +15 -21
  33. data/lib/nanoc3/data_sources/deprecated/twitter.rb +0 -1
  34. data/lib/nanoc3/data_sources/filesystem.rb +11 -40
  35. data/lib/nanoc3/data_sources/filesystem_unified.rb +22 -22
  36. data/lib/nanoc3/extra/auto_compiler.rb +1 -1
  37. data/lib/nanoc3/extra/chick.rb +8 -8
  38. data/lib/nanoc3/extra/deployers/rsync.rb +2 -3
  39. data/lib/nanoc3/extra/validators/links.rb +32 -51
  40. data/lib/nanoc3/extra/validators/w3c.rb +2 -2
  41. data/lib/nanoc3/extra/vcs.rb +1 -1
  42. data/lib/nanoc3/filters/colorize_syntax.rb +15 -19
  43. data/lib/nanoc3/filters/erb.rb +1 -5
  44. data/lib/nanoc3/filters/erubis.rb +1 -5
  45. data/lib/nanoc3/filters/haml.rb +1 -2
  46. data/lib/nanoc3/filters/less.rb +2 -51
  47. data/lib/nanoc3/filters/mustache.rb +21 -0
  48. data/lib/nanoc3/filters/rdiscount.rb +1 -2
  49. data/lib/nanoc3/filters/relativize_paths.rb +3 -2
  50. data/lib/nanoc3/filters/sass.rb +50 -56
  51. data/lib/nanoc3/filters.rb +2 -0
  52. data/lib/nanoc3/helpers/blogging.rb +22 -29
  53. data/lib/nanoc3/helpers/breadcrumbs.rb +1 -1
  54. data/lib/nanoc3/helpers/capturing.rb +1 -1
  55. data/lib/nanoc3/helpers/filtering.rb +1 -1
  56. data/lib/nanoc3/helpers/link_to.rb +10 -21
  57. data/lib/nanoc3/helpers/rendering.rb +5 -24
  58. data/lib/nanoc3/helpers/tagging.rb +6 -6
  59. data/lib/nanoc3/helpers/text.rb +2 -2
  60. data/lib/nanoc3.rb +1 -1
  61. metadata +35 -93
  62. data/.gemtest +0 -0
  63. data/doc/yardoc_templates/default/layout/html/footer.erb +0 -10
  64. data/nanoc3.gemspec +0 -41
  65. data/tasks/clean.rake +0 -11
  66. data/tasks/doc.rake +0 -14
  67. data/tasks/gem.rake +0 -13
  68. data/tasks/test.rake +0 -38
  69. data/test/base/core_ext/array_spec.rb +0 -23
  70. data/test/base/core_ext/hash_spec.rb +0 -41
  71. data/test/base/core_ext/string_spec.rb +0 -27
  72. data/test/base/test_code_snippet.rb +0 -33
  73. data/test/base/test_compiler.rb +0 -410
  74. data/test/base/test_compiler_dsl.rb +0 -121
  75. data/test/base/test_context.rb +0 -33
  76. data/test/base/test_data_source.rb +0 -48
  77. data/test/base/test_dependency_tracker.rb +0 -510
  78. data/test/base/test_directed_graph.rb +0 -91
  79. data/test/base/test_filter.rb +0 -85
  80. data/test/base/test_item.rb +0 -141
  81. data/test/base/test_item_rep.rb +0 -953
  82. data/test/base/test_layout.rb +0 -44
  83. data/test/base/test_notification_center.rb +0 -36
  84. data/test/base/test_plugin.rb +0 -32
  85. data/test/base/test_rule.rb +0 -21
  86. data/test/base/test_rule_context.rb +0 -63
  87. data/test/base/test_site.rb +0 -366
  88. data/test/cli/commands/test_compile.rb +0 -12
  89. data/test/cli/commands/test_create_item.rb +0 -12
  90. data/test/cli/commands/test_create_layout.rb +0 -28
  91. data/test/cli/commands/test_create_site.rb +0 -24
  92. data/test/cli/commands/test_help.rb +0 -12
  93. data/test/cli/commands/test_info.rb +0 -12
  94. data/test/cli/commands/test_update.rb +0 -12
  95. data/test/cli/test_logger.rb +0 -12
  96. data/test/data_sources/test_filesystem.rb +0 -420
  97. data/test/data_sources/test_filesystem_unified.rb +0 -538
  98. data/test/data_sources/test_filesystem_verbose.rb +0 -359
  99. data/test/extra/core_ext/test_enumerable.rb +0 -32
  100. data/test/extra/core_ext/test_time.rb +0 -17
  101. data/test/extra/deployers/test_rsync.rb +0 -234
  102. data/test/extra/test_auto_compiler.rb +0 -482
  103. data/test/extra/test_file_proxy.rb +0 -21
  104. data/test/extra/test_vcs.rb +0 -24
  105. data/test/extra/validators/test_links.rb +0 -53
  106. data/test/extra/validators/test_w3c.rb +0 -49
  107. data/test/filters/test_bluecloth.rb +0 -20
  108. data/test/filters/test_coderay.rb +0 -46
  109. data/test/filters/test_colorize_syntax.rb +0 -84
  110. data/test/filters/test_erb.rb +0 -72
  111. data/test/filters/test_erubis.rb +0 -72
  112. data/test/filters/test_haml.rb +0 -98
  113. data/test/filters/test_kramdown.rb +0 -20
  114. data/test/filters/test_less.rb +0 -118
  115. data/test/filters/test_markaby.rb +0 -26
  116. data/test/filters/test_maruku.rb +0 -20
  117. data/test/filters/test_rainpress.rb +0 -31
  118. data/test/filters/test_rdiscount.rb +0 -33
  119. data/test/filters/test_rdoc.rb +0 -18
  120. data/test/filters/test_redcloth.rb +0 -20
  121. data/test/filters/test_relativize_paths.rb +0 -231
  122. data/test/filters/test_rubypants.rb +0 -20
  123. data/test/filters/test_sass.rb +0 -235
  124. data/test/gem_loader.rb +0 -11
  125. data/test/helper.rb +0 -99
  126. data/test/helpers/test_blogging.rb +0 -808
  127. data/test/helpers/test_breadcrumbs.rb +0 -83
  128. data/test/helpers/test_capturing.rb +0 -42
  129. data/test/helpers/test_filtering.rb +0 -108
  130. data/test/helpers/test_html_escape.rb +0 -18
  131. data/test/helpers/test_link_to.rb +0 -251
  132. data/test/helpers/test_rendering.rb +0 -109
  133. data/test/helpers/test_tagging.rb +0 -89
  134. data/test/helpers/test_text.rb +0 -26
  135. data/test/helpers/test_xml_sitemap.rb +0 -69
  136. data/test/tasks/test_clean.rb +0 -71
@@ -6,26 +6,13 @@ module Nanoc3
6
6
  # have multiple representations. A representation has its own output file.
7
7
  # A single item can therefore have multiple output files, each run through
8
8
  # a different set of filters with a different layout.
9
- #
10
- # An item representation is observable. The following events will be
11
- # notified:
12
- #
13
- # * `:compilation_started`
14
- # * `:compilation_ended`
15
- # * `:filtering_started`
16
- # * `:filtering_ended`
17
- #
18
- # The compilation-related events have one parameters (the item
19
- # representation); the filtering-related events have two (the item
20
- # representation, and a symbol containing the filter class name).
21
9
  class ItemRep
22
10
 
23
11
  # The descriptive strings for each outdatedness reason. This hash is used
24
12
  # by the {#outdatedness_reason} method.
25
13
  OUTDATEDNESS_REASON_DESCRIPTIONS = {
26
- :no_mtime => 'No file modification time is available.',
27
- :forced => 'All pages are recompiled because of a `--force` flag given to the compilation command.',
28
- :no_raw_path => 'The routing rules do not specify a path where this item should be written to, i.e. the item representation will never be written to the output directory.',
14
+ :not_enough_data => 'Not enough data is present to correctly determine whether the item is outdated.',
15
+ :forced => 'All items are recompiled because of a `--force` flag given to the compilation command.',
29
16
  :not_written => 'This item representation has not yet been written to the output directory (but it does have a path).',
30
17
  :source_modified => 'The source file of this item has been modified since the last time this item representation was compiled.',
31
18
  :layouts_outdated => 'The source of one or more layouts has been modified since the last time this item representation was compiled.',
@@ -41,49 +28,38 @@ module Nanoc3
41
28
  attr_reader :name
42
29
 
43
30
  # @return [Boolean] true if this rep is forced to be dirty (e.g. because
44
- # of the `--force` commandline option); false otherwise
31
+ # of the `--force` commandline option); false otherwise
45
32
  attr_accessor :force_outdated
46
33
 
47
34
  # @return [Boolean] true if this rep is currently binary; false otherwise
48
35
  attr_reader :binary
49
36
  alias_method :binary?, :binary
50
37
 
51
- # @return [Boolean] true if this rep’s output file has changed since the
52
- # last time it was compiled; false otherwise
53
- attr_accessor :modified
54
- alias_method :modified?, :modified
55
-
56
- # @return [Boolean] true if this rep’s output file was created during the
57
- # current or last compilation session; false otherwise
58
- attr_accessor :created
59
- alias_method :created?, :created
60
-
61
38
  # @return [Boolean] true if this representation has already been compiled
62
- # during the current or last compilation session; false otherwise
39
+ # during the current or last compilation session; false otherwise
63
40
  attr_accessor :compiled
64
41
  alias_method :compiled?, :compiled
65
42
 
66
- # @return [Boolean] true if this representation’s compiled content has
67
- # been written during the current or last compilation session; false
68
- # otherwise
69
- attr_reader :written
70
- alias_method :written?, :written
43
+ # @return [Hash<Symbol,String>] A hash containing the raw paths (paths
44
+ # including the path to the output directory and the filename) for all
45
+ # snapshots. The keys correspond with the snapshot names, and the values
46
+ # with the path.
47
+ attr_accessor :raw_paths
71
48
 
72
- # @return [String] The item rep's path, as used when being linked to. It
73
- # starts with a slash and it is relative to the output directory. It does
74
- # not include the path to the output directory. It will not include the
75
- # filename if the filename is an index filename.
76
- attr_accessor :path
49
+ # @return [Hash<Symbol,String>] A hash containing the paths for all
50
+ # snapshots. The keys correspond with the snapshot names, and the values
51
+ # with the path.
52
+ attr_accessor :paths
77
53
 
78
- # @return [String] The item rep's raw path. It is relative to the current
79
- # working directory and includes the path to the output directory. It also
80
- # includes the filename, even if it is an index filename.
81
- attr_accessor :raw_path
54
+ # @return [Hash<Symbol,String>] A hash containing the content at all
55
+ # snapshots. The keys correspond with the snapshot names, and the
56
+ # values with the content.
57
+ attr_accessor :content
82
58
 
83
59
  # Creates a new item representation for the given item.
84
60
  #
85
61
  # @param [Nanoc3::Item] item The item to which the new representation will
86
- # belong.
62
+ # belong.
87
63
  #
88
64
  # @param [Symbol] name The unique name for the new item representation.
89
65
  def initialize(item, name)
@@ -94,15 +70,14 @@ module Nanoc3
94
70
  # Set binary
95
71
  @binary = @item.binary?
96
72
 
97
- # Initialize content and filenames
98
- initialize_content
73
+ # Initialize content and filenames and paths
74
+ @raw_paths = {}
75
+ @paths = {}
99
76
  @old_content = nil
77
+ initialize_content
100
78
 
101
79
  # Reset flags
102
80
  @compiled = false
103
- @modified = false
104
- @created = false
105
- @written = false
106
81
  @force_outdated = false
107
82
  end
108
83
 
@@ -120,39 +95,26 @@ module Nanoc3
120
95
  def outdatedness_reason
121
96
  # Get reason symbol
122
97
  reason = lambda do
123
- # Outdated if we don't know
124
- return :no_mtime if @item.mtime.nil?
125
-
126
- # Outdated if the dependency tracker says so
98
+ # Outdated if we’re compiling with --force
127
99
  return :forced if @force_outdated
128
100
 
129
- # Outdated if compiled file doesn't exist (yet)
130
- return :no_raw_path if self.raw_path.nil?
131
- return :not_written if !File.file?(self.raw_path)
101
+ # Outdated if checksums are missing
102
+ if !@item.old_checksum || !@item.new_checksum
103
+ return :not_enough_data
104
+ end
132
105
 
133
- # Get compiled mtime
134
- compiled_mtime = File.stat(self.raw_path).mtime
106
+ # Outdated if compiled file doesn't exist (yet)
107
+ return :not_written if self.raw_path && !File.file?(self.raw_path)
135
108
 
136
109
  # Outdated if file too old
137
- return :source_modified if @item.mtime > compiled_mtime
138
-
139
- # Outdated if layouts outdated
140
- return :layouts_outdated if @item.site.layouts.any? do |l|
141
- l.mtime.nil? || l.mtime > compiled_mtime
110
+ if @item.old_checksum != @item.new_checksum
111
+ return :source_modified
142
112
  end
143
113
 
144
- # Outdated if code outdated
145
- return :code_outdated if @item.site.code_snippets.any? do |cs|
146
- cs.mtime.nil? || cs.mtime > compiled_mtime
147
- end
148
-
149
- # Outdated if config outdated
150
- return :config_outdated if @item.site.config_mtime.nil?
151
- return :config_outdated if @item.site.config_mtime > compiled_mtime
152
-
153
- # Outdated if rules outdated
154
- return :rules_outdated if @item.site.rules_mtime.nil?
155
- return :rules_outdated if @item.site.rules_mtime > compiled_mtime
114
+ # Outdated if other site parts outdated
115
+ return :code_outdated if @item.site.code_snippets.any? { |cs| cs.outdated? }
116
+ return :config_outdated if @item.site.config_outdated?
117
+ return :rules_outdated if @item.site.rules_outdated?
156
118
 
157
119
  return nil
158
120
  end[]
@@ -169,13 +131,13 @@ module Nanoc3
169
131
  end
170
132
 
171
133
  # @return [Boolean] true if this item rep's output file is outdated and
172
- # must be regenerated, false otherwise
134
+ # must be regenerated, false otherwise
173
135
  def outdated?
174
136
  !outdatedness_reason.nil?
175
137
  end
176
138
 
177
139
  # @return [Hash] The assignments that should be available when compiling
178
- # the content.
140
+ # the content.
179
141
  def assigns
180
142
  if self.binary?
181
143
  content_or_filename_assigns = { :filename => @filenames[:last] }
@@ -196,22 +158,19 @@ module Nanoc3
196
158
  # Returns the compiled content from a given snapshot.
197
159
  #
198
160
  # @option params [String] :snapshot The name of the snapshot from which to
199
- # fetch the compiled content. By default, the returned compiled content
200
- # will be the content compiled right before the first layout call (if
201
- # any).
161
+ # fetch the compiled content. By default, the returned compiled content
162
+ # will be the content compiled right before the first layout call (if
163
+ # any).
202
164
  #
203
165
  # @return [String] The compiled content at the given snapshot (or the
204
- # default snapshot if no snapshot is specified)
166
+ # default snapshot if no snapshot is specified)
205
167
  def compiled_content(params={})
206
168
  # Notify
207
169
  Nanoc3::NotificationCenter.post(:visit_started, self.item)
208
170
  Nanoc3::NotificationCenter.post(:visit_ended, self.item)
209
171
 
210
- # Debug
211
- puts "*** Attempting to fetch content for #{self.inspect}" if $DEBUG
212
-
213
172
  # Require compilation
214
- raise Nanoc3::Errors::UnmetDependency.new(self) unless compiled?
173
+ raise Nanoc3::Errors::UnmetDependency.new(self) if !compiled? && !params[:force]
215
174
 
216
175
  # Get name of last pre-layout snapshot
217
176
  snapshot_name = params[:snapshot]
@@ -223,13 +182,57 @@ module Nanoc3
223
182
 
224
183
  # Check presence of snapshot
225
184
  if @content[snapshot_name].nil?
226
- warn "WARNING: The “#{self.item.identifier}” item (rep “#{self.name}”) does not have the requested snapshot named #{snapshot_name.inspect}.\n\n* Make sure that you are requesting the correct snapshot.\n* It is not possible to request the compiled content of a binary item representation; if this item is marked as binary even though you believe it should be textual, you may need to add the extension of this item to the site configuration’s `text_extensions` array.".make_compatible_with_env
185
+ warn(('-' * 78 + "\nWARNING: The “#{self.item.identifier}” item (rep “#{self.name}”) does not have the requested snapshot named #{snapshot_name.inspect}.\n\n* Make sure that you are requesting the correct snapshot.\n* It is not possible to request the compiled content of a binary item representation; if this item is marked as binary even though you believe it should be textual, you may need to add the extension of this item to the site configuration’s `text_extensions` array.\n" + '-' * 78).make_compatible_with_env)
227
186
  end
228
187
 
229
188
  # Get content
230
189
  @content[snapshot_name]
231
190
  end
232
191
 
192
+ # Checks whether content exists at a given snapshot.
193
+ #
194
+ # @return [Boolean] True if content exists for the snapshot with the
195
+ # given name, false otherwise
196
+ def has_snapshot?(snapshot_name)
197
+ !@content[snapshot_name].nil?
198
+ end
199
+
200
+ # Returns the item rep’s raw path. It includes the path to the output
201
+ # directory and the full filename.
202
+ #
203
+ # @option params [Symbol] :snapshot (:last) The snapshot for which the
204
+ # path should be returned
205
+ #
206
+ # @return [String] The item rep’s path
207
+ def raw_path(params={})
208
+ snapshot_name = params[:snapshot] || :last
209
+ @raw_paths[snapshot_name]
210
+ end
211
+
212
+ # Returns the item rep’s path, as used when being linked to. It starts
213
+ # with a slash and it is relative to the output directory. It does not
214
+ # include the path to the output directory. It will not include the
215
+ # filename if the filename is an index filename.
216
+ #
217
+ # @option params [Symbol] :snapshot (:last) The snapshot for which the
218
+ # path should be returned
219
+ #
220
+ # @return [String] The item rep’s path
221
+ def path(params={})
222
+ snapshot_name = params[:snapshot] || :last
223
+ @paths[snapshot_name]
224
+ end
225
+
226
+ # @deprecated Modify the {#raw_paths} attribute instead
227
+ def raw_path=(raw_path)
228
+ raw_paths[:last] = raw_path
229
+ end
230
+
231
+ # @deprecated Modify the {#paths} attribute instead
232
+ def path=(path)
233
+ paths[:last] = path
234
+ end
235
+
233
236
  # @deprecated Use {Nanoc3::ItemRep#compiled_content} instead.
234
237
  def content_at_snapshot(snapshot=:pre)
235
238
  compiled_content(:snapshot => snapshot)
@@ -252,10 +255,10 @@ module Nanoc3
252
255
  # (see {Nanoc3::CompilerDSL#compile}).
253
256
  #
254
257
  # @param [Symbol] filter_name The name of the filter to run the item
255
- # representations' content through
258
+ # representations' content through
256
259
  #
257
260
  # @param [Hash] filter_args The filter arguments that should be passed to
258
- # the filter's #run method
261
+ # the filter's #run method
259
262
  #
260
263
  # @return [void]
261
264
  def filter(filter_name, filter_args={})
@@ -292,7 +295,7 @@ module Nanoc3
292
295
  end
293
296
 
294
297
  # Create snapshot
295
- snapshot(@content[:post] ? :post : :pre) unless self.binary?
298
+ snapshot(@content[:post] ? :post : :pre, :final => false) unless self.binary?
296
299
  end
297
300
 
298
301
  # Lays out the item using the given layout. This method will replace the
@@ -303,7 +306,7 @@ module Nanoc3
303
306
  # (see {Nanoc3::CompilerDSL#compile}).
304
307
  #
305
308
  # @param [String] layout_identifier The identifier of the layout the item
306
- # should be laid out with
309
+ # should be laid out with
307
310
  #
308
311
  # @return [void]
309
312
  def layout(layout_identifier)
@@ -311,12 +314,18 @@ module Nanoc3
311
314
  raise Nanoc3::Errors::CannotLayoutBinaryItem.new(self) if self.binary?
312
315
 
313
316
  # Create "pre" snapshot
314
- snapshot(:pre) unless @content[:pre]
317
+ if @content[:post].nil?
318
+ snapshot(:pre, :final => true)
319
+ end
315
320
 
316
321
  # Create filter
317
322
  layout = layout_with_identifier(layout_identifier)
318
323
  filter, filter_name, filter_args = filter_for_layout(layout)
319
324
 
325
+ # Visit
326
+ Nanoc3::NotificationCenter.post(:visit_started, layout)
327
+ Nanoc3::NotificationCenter.post(:visit_ended, layout)
328
+
320
329
  # Layout
321
330
  @item.site.compiler.stack.push(layout)
322
331
  Nanoc3::NotificationCenter.post(:filtering_started, self, filter_name)
@@ -325,17 +334,27 @@ module Nanoc3
325
334
  @item.site.compiler.stack.pop
326
335
 
327
336
  # Create "post" snapshot
328
- snapshot(:post)
337
+ snapshot(:post, :final => false)
329
338
  end
330
339
 
331
340
  # Creates a snapshot of the current compiled item content.
332
341
  #
333
342
  # @param [Symbol] snapshot_name The name of the snapshot to create
334
343
  #
344
+ # @option params [Boolean] :final (true) True if this is the final time
345
+ # the snapshot will be updated; false if it is a non-final moving
346
+ # snapshot (such as `:pre`, `:post` or `:last`)
347
+ #
335
348
  # @return [void]
336
- def snapshot(snapshot_name)
337
- target = self.binary? ? @filenames : @content
338
- target[snapshot_name] = target[:last]
349
+ def snapshot(snapshot_name, params={})
350
+ # Parse params
351
+ params[:final] = true if !params.has_key?(:final)
352
+
353
+ # Create snapshot
354
+ @content[snapshot_name] = @content[:last] unless self.binary?
355
+
356
+ # Write
357
+ write(snapshot_name) if params[:final]
339
358
  end
340
359
 
341
360
  # Writes the item rep's compiled content to the rep's output file.
@@ -343,50 +362,48 @@ module Nanoc3
343
362
  # This method should not be called directly, even in a compilation block;
344
363
  # the compiler is responsible for calling this method.
345
364
  #
365
+ # @param [String, nil] raw_path The raw path to write the compiled rep to.
366
+ # If nil, the default raw path will be used.
367
+ #
346
368
  # @return [void]
347
- def write
369
+ def write(snapshot=:last)
370
+ # Get raw path
371
+ raw_path = self.raw_path(:snapshot => snapshot)
372
+ return if raw_path.nil?
373
+
348
374
  # Create parent directory
349
- FileUtils.mkdir_p(File.dirname(self.raw_path))
375
+ FileUtils.mkdir_p(File.dirname(raw_path))
350
376
 
351
377
  # Check if file will be created
352
- @created = !File.file?(self.raw_path)
378
+ is_created = !File.file?(raw_path)
353
379
 
354
- if self.binary?
355
- # Calculate hash of old content
356
- if File.file?(self.raw_path)
357
- hash_old = hash_for_file(self.raw_path)
358
- size_old = File.size(self.raw_path)
359
- end
360
- size_new = File.size(@filenames[:last])
361
- hash_new = hash_for_file(@filenames[:last]) if size_old == size_new
362
-
363
- # Check if file was modified
364
- @modified = (size_old != size_new || hash_old != hash_new)
380
+ # Calculate characteristics of old content
381
+ if File.file?(raw_path)
382
+ hash_old = Nanoc3::Checksummer.checksum_for(raw_path)
383
+ size_old = File.size(raw_path)
384
+ end
365
385
 
386
+ if self.binary?
366
387
  # Copy
367
- if @modified
368
- FileUtils.cp(@filenames[:last], self.raw_path)
369
- end
370
- @written = true
388
+ FileUtils.cp(@filenames[:last], raw_path)
371
389
  else
372
- # Remember old content
373
- if File.file?(self.raw_path)
374
- @old_content = File.read(self.raw_path)
375
- end
376
-
377
390
  # Write
378
- new_content = @content[:last]
379
- if @old_content != new_content
380
- File.open(self.raw_path, 'w') { |io| io.write(new_content) }
381
- end
382
- @written = true
391
+ File.open(raw_path, 'w') { |io| io.write(@content[:last]) }
383
392
 
384
393
  # Generate diff
385
394
  generate_diff
386
-
387
- # Check if file was modified
388
- @modified = File.read(self.raw_path) != @old_content
389
395
  end
396
+
397
+ # Check if file was modified
398
+ size_new = File.size(raw_path)
399
+ hash_new = Nanoc3::Checksummer.checksum_for(raw_path) if size_old == size_new
400
+ is_modified = (size_old != size_new || hash_old != hash_new)
401
+
402
+ # Notify
403
+ Nanoc3::NotificationCenter.post(
404
+ :rep_written,
405
+ self, raw_path, is_created, is_modified
406
+ )
390
407
  end
391
408
 
392
409
  # Creates and returns a diff between the compiled content before the
@@ -394,8 +411,8 @@ module Nanoc3
394
411
  # compilation session.
395
412
  #
396
413
  # @return [String, nil] The difference between the old and new compiled
397
- # content in `diff(1)` format, or nil if there is no previous compiled
398
- # content
414
+ # content in `diff(1)` format, or nil if there is no previous compiled
415
+ # content
399
416
  def diff
400
417
  if self.binary?
401
418
  nil
@@ -405,6 +422,36 @@ module Nanoc3
405
422
  end
406
423
  end
407
424
 
425
+ # @deprecated
426
+ def created
427
+ raise NotImplementedError, "Nanoc3::ItemRep#created is no longer implemented"
428
+ end
429
+
430
+ # @deprecated
431
+ def created?
432
+ raise NotImplementedError, "Nanoc3::ItemRep#created? is no longer implemented"
433
+ end
434
+
435
+ # @deprecated
436
+ def modified
437
+ raise NotImplementedError, "Nanoc3::ItemRep#modified is no longer implemented"
438
+ end
439
+
440
+ # @deprecated
441
+ def modified?
442
+ raise NotImplementedError, "Nanoc3::ItemRep#modified? is no longer implemented"
443
+ end
444
+
445
+ # @deprecated
446
+ def written
447
+ raise NotImplementedError, "Nanoc3::ItemRep#written is no longer implemented"
448
+ end
449
+
450
+ # @deprecated
451
+ def written?
452
+ raise NotImplementedError, "Nanoc3::ItemRep#written? is no longer implemented"
453
+ end
454
+
408
455
  def inspect
409
456
  "<#{self.class}:0x#{self.object_id.to_s(16)} name=#{self.name} binary=#{self.binary?} raw_path=#{self.raw_path} item.identifier=#{self.item.identifier}>"
410
457
  end
@@ -414,17 +461,10 @@ module Nanoc3
414
461
  def initialize_content
415
462
  # Initialize content and filenames
416
463
  if self.binary?
417
- @filenames = {
418
- :raw => @item.raw_filename,
419
- :last => @item.raw_filename
420
- }
421
- @content = {}
464
+ @filenames = { :last => @item.raw_filename }
465
+ @content = {}
422
466
  else
423
- @content = {
424
- :raw => @item.raw_content,
425
- :last => @item.raw_content,
426
- :pre => @item.raw_content
427
- }
467
+ @content = { :last => @item.raw_content }
428
468
  @filenames = {}
429
469
  end
430
470
  end
@@ -442,7 +482,7 @@ module Nanoc3
442
482
  def filter_for_layout(layout)
443
483
  # Get filter name and args
444
484
  filter_name, filter_args = @item.site.compiler.filter_for_layout(layout)
445
- raise Nanoc3::Errors::CannotDetermineFilter.new(layout.identifier) if filter_name.nil?
485
+ raise Nanoc3::Errors::CannotDetermineFilter.new(layout_identifier) if filter_name.nil?
446
486
 
447
487
  # Get filter class
448
488
  filter_class = Nanoc3::Filter.named(filter_name)
@@ -459,7 +499,6 @@ module Nanoc3
459
499
  if @old_content.nil? || self.raw_path.nil? || !@item.site.config[:enable_output_diff]
460
500
  @diff = nil
461
501
  else
462
- require 'thread'
463
502
  @diff_thread = Thread.new do
464
503
  @diff = diff_strings(@old_content, @content[:last])
465
504
  sleep 2
@@ -493,18 +532,6 @@ module Nanoc3
493
532
  nil
494
533
  end
495
534
 
496
- # Returns a hash of the given filename
497
- def hash_for_file(filename)
498
- digest = Digest::SHA1.new
499
- File.open(filename, 'r') do |io|
500
- until io.eof
501
- data = io.readpartial(2**10)
502
- digest.update(data)
503
- end
504
- end
505
- digest.hexdigest
506
- end
507
-
508
535
  end
509
536
 
510
537
  end
@@ -16,11 +16,23 @@ module Nanoc3
16
16
  attr_reader :attributes
17
17
 
18
18
  # @return [String] This layout's identifier, starting and ending with a
19
- # slash
19
+ # slash
20
20
  attr_accessor :identifier
21
21
 
22
- # @return [Time] The time when this layout was last modified
23
- attr_reader :mtime
22
+ # @return [Time] The time where this layout was last modified
23
+ attr_reader :mtime
24
+
25
+ # @return [String] The checksum of this layout that was in effect during
26
+ # the previous site compilation
27
+ attr_accessor :old_checksum
28
+
29
+ # @return [String] The current, up-to-date checksum of this layout
30
+ attr_reader :new_checksum
31
+
32
+ # @return [Boolean] Whether or not this layout is outdated because of its
33
+ # dependencies are outdated
34
+ attr_accessor :outdated_due_to_dependencies
35
+ alias_method :outdated_due_to_dependencies?, :outdated_due_to_dependencies
24
36
 
25
37
  # Creates a new layout.
26
38
  #
@@ -30,25 +42,25 @@ module Nanoc3
30
42
  #
31
43
  # @param [String] identifier This layout's identifier.
32
44
  #
33
- # @param [Time, Hash, nil] params_or_mtime Extra parameters for the
34
- # layout, or the time when this layout was last modified (deprecated).
45
+ # @param [Time, Hash] params Extra parameters. For backwards
46
+ # compatibility, this can be a Time instance indicating the time when
47
+ # this layout was last modified (mtime).
35
48
  #
36
- # @option params_or_mtime [Time, nil] :mtime (nil) The time when this
37
- # layout was last modified
38
- def initialize(raw_content, attributes, identifier, params_or_mtime=nil)
39
- # Get params and mtime
40
- # TODO [in nanoc 4.0] clean this up
41
- if params_or_mtime.nil? || params_or_mtime.is_a?(Time)
42
- params = {}
43
- @mtime = params_or_mtime
44
- elsif params_or_mtime.is_a?(Hash)
45
- params = params_or_mtime
46
- @mtime = params[:mtime]
47
- end
49
+ # @option params [Time, nil] :mtime (nil) The time when this layout was
50
+ # last modified
51
+ #
52
+ # @option params [String, nil] :checksum (nil) The current, up-to-date
53
+ # checksum of this layout
54
+ def initialize(raw_content, attributes, identifier, params=nil)
55
+ # Get mtime and checksum
56
+ params ||= {}
57
+ params = { :mtime => params } if params.is_a?(Time)
58
+ @new_checksum = params[:checksum]
59
+ @mtime = params[:mtime]
48
60
 
49
61
  @raw_content = raw_content
50
62
  @attributes = attributes.symbolize_keys
51
- @identifier = identifier.cleaned_identifier.freeze
63
+ @identifier = identifier.cleaned_identifier
52
64
  end
53
65
 
54
66
  # Requests the attribute with the given key.
@@ -60,6 +72,27 @@ module Nanoc3
60
72
  @attributes[key]
61
73
  end
62
74
 
75
+ # @return [Boolean] true if the layout was modified since the site was
76
+ # last compiled, false otherwise
77
+ def outdated?
78
+ !self.old_checksum || !self.new_checksum || self.new_checksum != self.old_checksum
79
+ end
80
+
81
+ # Returns the type of this object. Will always return `:layout`, because
82
+ # this is a layout. For items, this method returns `:item`.
83
+ #
84
+ # @return [Symbol] :layout
85
+ def type
86
+ :layout
87
+ end
88
+
89
+ # Returns an object that can be used for uniquely identifying objects.
90
+ #
91
+ # @return [Object] An unique reference to this object
92
+ def reference
93
+ [ type, self.identifier ]
94
+ end
95
+
63
96
  def inspect
64
97
  "<#{self.class}:0x#{self.object_id.to_s(16)} identifier=#{self.identifier}>"
65
98
  end
@@ -17,12 +17,12 @@ module Nanoc3
17
17
  # the notification with the given name is received.
18
18
  #
19
19
  # @param [String, Symbol] name The name of the notification that will
20
- # cause the given block to be called.
20
+ # cause the given block to be called.
21
21
  #
22
22
  # @param [String, Symbol, nil] id An identifier for the block. This is
23
- # only used to be able to remove the block (using the remove method)
24
- # later. Can be nil, but this is not recommended because it prevents
25
- # the given notification block from being unregistered.
23
+ # only used to be able to remove the block (using the remove method)
24
+ # later. Can be nil, but this is not recommended because it prevents
25
+ # the given notification block from being unregistered.
26
26
  #
27
27
  # @yield [*args] Will be executed with the arguments passed to {.post}
28
28
  #
@@ -37,10 +37,10 @@ module Nanoc3
37
37
  # Posts a notification with the given name and the given arguments.
38
38
  #
39
39
  # @param [String, Symbol] name The name of the notification that should
40
- # be posted.
40
+ # be posted.
41
41
  #
42
42
  # @param args Arguments that wil be passed to the blocks handling the
43
- # notification.
43
+ # notification.
44
44
  #
45
45
  # @return [void]
46
46
  def post(name, *args)
@@ -57,10 +57,10 @@ module Nanoc3
57
57
  # posted.
58
58
  #
59
59
  # @param [String, Symbol] name The name of the notification that should
60
- # no longer be registered.
60
+ # no longer be registered.
61
61
  #
62
62
  # @param [String, Symbol] id The identifier of the block that should be
63
- # removed.
63
+ # removed.
64
64
  #
65
65
  # @return [void]
66
66
  def remove(name, id)