nanoc3 3.2.0a1 → 3.2.0a2

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.
Files changed (58) hide show
  1. data/ChangeLog +2 -2
  2. data/NEWS.md +14 -0
  3. data/README.md +20 -12
  4. data/doc/yardoc_templates/default/layout/html/footer.erb +10 -0
  5. data/lib/nanoc3/base/compilation/checksum_store.rb +130 -0
  6. data/lib/nanoc3/base/compilation/checksummer.rb +68 -0
  7. data/lib/nanoc3/base/compilation/compiled_content_cache.rb +57 -0
  8. data/lib/nanoc3/base/{compiler.rb → compilation/compiler.rb} +255 -55
  9. data/lib/nanoc3/base/{compiler_dsl.rb → compilation/compiler_dsl.rb} +11 -6
  10. data/lib/nanoc3/base/{dependency_tracker.rb → compilation/dependency_tracker.rb} +62 -92
  11. data/lib/nanoc3/base/{filter.rb → compilation/filter.rb} +0 -0
  12. data/lib/nanoc3/base/compilation/item_rep_proxy.rb +87 -0
  13. data/lib/nanoc3/base/compilation/outdatedness_checker.rb +86 -0
  14. data/lib/nanoc3/base/compilation/outdatedness_reasons.rb +43 -0
  15. data/lib/nanoc3/base/{rule.rb → compilation/rule.rb} +8 -2
  16. data/lib/nanoc3/base/{rule_context.rb → compilation/rule_context.rb} +17 -14
  17. data/lib/nanoc3/base/directed_graph.rb +8 -0
  18. data/lib/nanoc3/base/errors.rb +9 -17
  19. data/lib/nanoc3/base/plugin_registry.rb +1 -1
  20. data/lib/nanoc3/base/result_data/item_rep.rb +447 -0
  21. data/lib/nanoc3/base/{code_snippet.rb → source_data/code_snippet.rb} +7 -22
  22. data/lib/nanoc3/base/{data_source.rb → source_data/data_source.rb} +0 -0
  23. data/lib/nanoc3/base/{item.rb → source_data/item.rb} +20 -30
  24. data/lib/nanoc3/base/{layout.rb → source_data/layout.rb} +7 -26
  25. data/lib/nanoc3/base/source_data/site.rb +314 -0
  26. data/lib/nanoc3/base/store.rb +126 -0
  27. data/lib/nanoc3/base.rb +26 -15
  28. data/lib/nanoc3/cli/base.rb +2 -1
  29. data/lib/nanoc3/cli/commands/compile.rb +116 -48
  30. data/lib/nanoc3/cli/commands/create_item.rb +0 -1
  31. data/lib/nanoc3/cli/commands/create_layout.rb +0 -1
  32. data/lib/nanoc3/cli/commands/create_site.rb +11 -1
  33. data/lib/nanoc3/cli/commands/debug.rb +11 -6
  34. data/lib/nanoc3/cli/commands/info.rb +1 -2
  35. data/lib/nanoc3/cli/commands/update.rb +0 -1
  36. data/lib/nanoc3/cli/commands/watch.rb +27 -32
  37. data/lib/nanoc3/cli/logger.rb +2 -2
  38. data/lib/nanoc3/data_sources/filesystem.rb +1 -6
  39. data/lib/nanoc3/extra/auto_compiler.rb +2 -3
  40. data/lib/nanoc3/extra/deployers/rsync.rb +1 -0
  41. data/lib/nanoc3/extra/validators/links.rb +45 -26
  42. data/lib/nanoc3/filters/asciidoc.rb +58 -0
  43. data/lib/nanoc3/filters/colorize_syntax.rb +47 -9
  44. data/lib/nanoc3/filters/less.rb +6 -0
  45. data/lib/nanoc3/filters/sass.rb +8 -5
  46. data/lib/nanoc3/filters.rb +2 -0
  47. data/lib/nanoc3/helpers/blogging.rb +8 -0
  48. data/lib/nanoc3/helpers/html_escape.rb +37 -7
  49. data/lib/nanoc3/helpers/link_to.rb +15 -4
  50. data/lib/nanoc3/helpers/rendering.rb +6 -2
  51. data/lib/nanoc3/tasks/clean.rb +0 -4
  52. data/lib/nanoc3.rb +1 -1
  53. data/nanoc3.gemspec +42 -0
  54. metadata +35 -19
  55. data/lib/nanoc3/base/checksummer.rb +0 -40
  56. data/lib/nanoc3/base/compiled_content_cache.rb +0 -86
  57. data/lib/nanoc3/base/item_rep.rb +0 -537
  58. data/lib/nanoc3/base/site.rb +0 -490
@@ -1,86 +0,0 @@
1
- # encoding: utf-8
2
-
3
- module Nanoc3
4
-
5
- # Represents a cache than can be used to store already compiled content,
6
- # to prevent it from being needlessly recompiled.
7
- #
8
- # This class is intended for internal use only. Do not rely on its
9
- # presence; future versions of nanoc, even in the 3.x branch, may no
10
- # longer contain this class.
11
- class CompiledContentCache
12
-
13
- # @return [String] The filename where the cache will be loaded from
14
- # and stored to
15
- attr_reader :filename
16
-
17
- # Creates a new cache for the given filename.
18
- #
19
- # @param [String] filename The filename where the cache will be loaded
20
- # from and stored to
21
- def initialize(filename)
22
- require 'pstore'
23
-
24
- @filename = filename
25
- end
26
-
27
- # Loads the cache from the filesystem into memory.
28
- #
29
- # @return [void]
30
- def load
31
- cache = nil
32
- return if !File.file?(filename)
33
- pstore.transaction { cache = pstore[:compiled_content] }
34
- end
35
-
36
- # Stores the content of the (probably modified) in-memory cache to the
37
- # filesystem.
38
- #
39
- # @return [void]
40
- def store
41
- FileUtils.mkdir_p(File.dirname(filename))
42
- pstore.transaction { pstore[:compiled_content] = cache }
43
- end
44
-
45
- # Returns the cached compiled content for the given item
46
- # representation. This cached compiled content is a hash where the keys
47
- # are the snapshot names and the values the compiled content at the
48
- # given snapshot.
49
- #
50
- # @param [Nanoc3::ItemRep] rep The item rep to fetch the content for
51
- #
52
- # @return [Hash<Symbol,String>] A hash containing the cached compiled
53
- # content for the given item representation
54
- def [](rep)
55
- item_cache = cache[rep.item.identifier] || {}
56
- item_cache[rep.name]
57
- end
58
-
59
- # Sets the compiled content for the given representation.
60
- #
61
- # @param [Nanoc3::ItemRep] rep The item representation for which to set
62
- # the compiled content
63
- #
64
- # @param [Hash<Symbol,String>] content A hash containing the compiled
65
- # content of the given representation
66
- #
67
- # @return [void]
68
- def []=(rep, content)
69
- cache[rep.item.identifier] ||= {}
70
- cache[rep.item.identifier][rep.name] = content
71
- end
72
-
73
- private
74
-
75
- def cache
76
- @cache ||= {}
77
- end
78
-
79
- def pstore
80
- require 'pstore'
81
- @store ||= PStore.new(@filename)
82
- end
83
-
84
- end
85
-
86
- end
@@ -1,537 +0,0 @@
1
- # encoding: utf-8
2
-
3
- module Nanoc3
4
-
5
- # A single representation (rep) of an item ({Nanoc3::Item}). An item can
6
- # have multiple representations. A representation has its own output file.
7
- # A single item can therefore have multiple output files, each run through
8
- # a different set of filters with a different layout.
9
- class ItemRep
10
-
11
- # The descriptive strings for each outdatedness reason. This hash is used
12
- # by the {#outdatedness_reason} method.
13
- OUTDATEDNESS_REASON_DESCRIPTIONS = {
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.',
16
- :not_written => 'This item representation has not yet been written to the output directory (but it does have a path).',
17
- :source_modified => 'The source file of this item has been modified since the last time this item representation was compiled.',
18
- :layouts_outdated => 'The source of one or more layouts has been modified since the last time this item representation was compiled.',
19
- :code_outdated => 'The code snippets in the `lib/` directory have been modified since the last time this item representation was compiled.',
20
- :config_outdated => 'The site configuration has been modified since the last time this item representation was compiled.',
21
- :rules_outdated => 'The rules file has been modified since the last time this item representation was compiled.',
22
- }
23
-
24
- # @return [Nanoc3::Item] The item to which this rep belongs
25
- attr_reader :item
26
-
27
- # @return [Symbol] The representation's unique name
28
- attr_reader :name
29
-
30
- # @return [Boolean] true if this rep is forced to be dirty (e.g. because
31
- # of the `--force` commandline option); false otherwise
32
- attr_accessor :force_outdated
33
-
34
- # @return [Boolean] true if this rep is currently binary; false otherwise
35
- attr_reader :binary
36
- alias_method :binary?, :binary
37
-
38
- # @return [Boolean] true if this representation has already been compiled
39
- # during the current or last compilation session; false otherwise
40
- attr_accessor :compiled
41
- alias_method :compiled?, :compiled
42
-
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
48
-
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
53
-
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
58
-
59
- # Creates a new item representation for the given item.
60
- #
61
- # @param [Nanoc3::Item] item The item to which the new representation will
62
- # belong.
63
- #
64
- # @param [Symbol] name The unique name for the new item representation.
65
- def initialize(item, name)
66
- # Set primary attributes
67
- @item = item
68
- @name = name
69
-
70
- # Set binary
71
- @binary = @item.binary?
72
-
73
- # Initialize content and filenames and paths
74
- @raw_paths = {}
75
- @paths = {}
76
- @old_content = nil
77
- initialize_content
78
-
79
- # Reset flags
80
- @compiled = false
81
- @force_outdated = false
82
- end
83
-
84
- # Calculates the reason why this item representation is outdated. The
85
- # output will be a hash with a `:type` key, containing the reason why the
86
- # item is outdated in the form of a symbol, and a `:description` key,
87
- # containing a descriptive string that can be printed if necessary.
88
- #
89
- # For documentation on the types that this method can return, check the
90
- # {OUTDATEDNESS_REASON_DESCRIPTIONS} hash in this class.
91
- #
92
- # @return [Hash, nil] A hash containing the reason why this item rep is
93
- # outdated, both in the form of a symbol and as a descriptive string, or
94
- # nil if the item representation is not outdated.
95
- def outdatedness_reason
96
- # Get reason symbol
97
- reason = lambda do
98
- # Outdated if we’re compiling with --force
99
- return :forced if @force_outdated
100
-
101
- # Outdated if checksums are missing
102
- if !@item.old_checksum || !@item.new_checksum
103
- return :not_enough_data
104
- end
105
-
106
- # Outdated if compiled file doesn't exist (yet)
107
- return :not_written if self.raw_path && !File.file?(self.raw_path)
108
-
109
- # Outdated if file too old
110
- if @item.old_checksum != @item.new_checksum
111
- return :source_modified
112
- end
113
-
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?
118
-
119
- return nil
120
- end[]
121
-
122
- # Build reason symbol and description
123
- if reason.nil?
124
- nil
125
- else
126
- {
127
- :type => reason,
128
- :description => OUTDATEDNESS_REASON_DESCRIPTIONS[reason]
129
- }
130
- end
131
- end
132
-
133
- # @return [Boolean] true if this item rep's output file is outdated and
134
- # must be regenerated, false otherwise
135
- def outdated?
136
- !outdatedness_reason.nil?
137
- end
138
-
139
- # @return [Hash] The assignments that should be available when compiling
140
- # the content.
141
- def assigns
142
- if self.binary?
143
- content_or_filename_assigns = { :filename => @filenames[:last] }
144
- else
145
- content_or_filename_assigns = { :content => @content[:last] }
146
- end
147
-
148
- content_or_filename_assigns.merge({
149
- :item => self.item,
150
- :item_rep => self,
151
- :items => self.item.site.items,
152
- :layouts => self.item.site.layouts,
153
- :config => self.item.site.config,
154
- :site => self.item.site
155
- })
156
- end
157
-
158
- # Returns the compiled content from a given snapshot.
159
- #
160
- # @option params [String] :snapshot The name of the snapshot from which to
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).
164
- #
165
- # @return [String] The compiled content at the given snapshot (or the
166
- # default snapshot if no snapshot is specified)
167
- def compiled_content(params={})
168
- # Notify
169
- Nanoc3::NotificationCenter.post(:visit_started, self.item)
170
- Nanoc3::NotificationCenter.post(:visit_ended, self.item)
171
-
172
- # Require compilation
173
- raise Nanoc3::Errors::UnmetDependency.new(self) if !compiled? && !params[:force]
174
-
175
- # Get name of last pre-layout snapshot
176
- snapshot_name = params[:snapshot]
177
- if @content[:pre]
178
- snapshot_name ||= :pre
179
- else
180
- snapshot_name ||= :last
181
- end
182
-
183
- # Check presence of snapshot
184
- if @content[snapshot_name].nil?
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)
186
- end
187
-
188
- # Get content
189
- @content[snapshot_name]
190
- end
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
-
236
- # @deprecated Use {Nanoc3::ItemRep#compiled_content} instead.
237
- def content_at_snapshot(snapshot=:pre)
238
- compiled_content(:snapshot => snapshot)
239
- end
240
-
241
- # Resets the compilation progress for this item representation. This is
242
- # necessary when an unmet dependency is detected during compilation.
243
- # This method should probably not be called directly.
244
- #
245
- # @return [void]
246
- def forget_progress
247
- initialize_content
248
- end
249
-
250
- # Runs the item content through the given filter with the given arguments.
251
- # This method will replace the content of the `:last` snapshot with the
252
- # filtered content of the last snapshot.
253
- #
254
- # This method is supposed to be called only in a compilation rule block
255
- # (see {Nanoc3::CompilerDSL#compile}).
256
- #
257
- # @param [Symbol] filter_name The name of the filter to run the item
258
- # representations' content through
259
- #
260
- # @param [Hash] filter_args The filter arguments that should be passed to
261
- # the filter's #run method
262
- #
263
- # @return [void]
264
- def filter(filter_name, filter_args={})
265
- # Get filter class
266
- klass = filter_named(filter_name)
267
- raise Nanoc3::Errors::UnknownFilter.new(filter_name) if klass.nil?
268
-
269
- # Check whether filter can be applied
270
- if klass.from_binary? && !self.binary?
271
- raise Nanoc3::Errors::CannotUseBinaryFilter.new(self, klass)
272
- elsif !klass.from_binary? && self.binary?
273
- raise Nanoc3::Errors::CannotUseTextualFilter.new(self, klass)
274
- end
275
-
276
- # Create filter
277
- filter = klass.new(assigns)
278
-
279
- # Run filter
280
- Nanoc3::NotificationCenter.post(:filtering_started, self, filter_name)
281
- source = self.binary? ? @filenames[:last] : @content[:last]
282
- result = filter.run(source, filter_args)
283
- if klass.to_binary?
284
- @filenames[:last] = filter.output_filename
285
- else
286
- @content[:last] = result
287
- end
288
- @binary = klass.to_binary?
289
- Nanoc3::NotificationCenter.post(:filtering_ended, self, filter_name)
290
-
291
- # Check whether file was written
292
- if self.binary? && !File.file?(filter.output_filename)
293
- raise RuntimeError,
294
- "The #{filter_name.inspect} filter did not write anything to the required output file, #{filter.output_filename}."
295
- end
296
-
297
- # Create snapshot
298
- snapshot(@content[:post] ? :post : :pre, :final => false) unless self.binary?
299
- end
300
-
301
- # Lays out the item using the given layout. This method will replace the
302
- # content of the `:last` snapshot with the laid out content of the last
303
- # snapshot.
304
- #
305
- # This method is supposed to be called only in a compilation rule block
306
- # (see {Nanoc3::CompilerDSL#compile}).
307
- #
308
- # @param [String] layout_identifier The identifier of the layout the item
309
- # should be laid out with
310
- #
311
- # @return [void]
312
- def layout(layout_identifier)
313
- # Check whether item can be laid out
314
- raise Nanoc3::Errors::CannotLayoutBinaryItem.new(self) if self.binary?
315
-
316
- # Create "pre" snapshot
317
- if @content[:post].nil?
318
- snapshot(:pre, :final => true)
319
- end
320
-
321
- # Create filter
322
- layout = layout_with_identifier(layout_identifier)
323
- filter, filter_name, filter_args = filter_for_layout(layout)
324
-
325
- # Visit
326
- Nanoc3::NotificationCenter.post(:visit_started, layout)
327
- Nanoc3::NotificationCenter.post(:visit_ended, layout)
328
-
329
- # Layout
330
- @item.site.compiler.stack.push(layout)
331
- Nanoc3::NotificationCenter.post(:filtering_started, self, filter_name)
332
- @content[:last] = filter.run(layout.raw_content, filter_args)
333
- Nanoc3::NotificationCenter.post(:filtering_ended, self, filter_name)
334
- @item.site.compiler.stack.pop
335
-
336
- # Create "post" snapshot
337
- snapshot(:post, :final => false)
338
- end
339
-
340
- # Creates a snapshot of the current compiled item content.
341
- #
342
- # @param [Symbol] snapshot_name The name of the snapshot to create
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
- #
348
- # @return [void]
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]
358
- end
359
-
360
- # Writes the item rep's compiled content to the rep's output file.
361
- #
362
- # This method should not be called directly, even in a compilation block;
363
- # the compiler is responsible for calling this method.
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
- #
368
- # @return [void]
369
- def write(snapshot=:last)
370
- # Get raw path
371
- raw_path = self.raw_path(:snapshot => snapshot)
372
- return if raw_path.nil?
373
-
374
- # Create parent directory
375
- FileUtils.mkdir_p(File.dirname(raw_path))
376
-
377
- # Check if file will be created
378
- is_created = !File.file?(raw_path)
379
-
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
385
-
386
- if self.binary?
387
- # Copy
388
- FileUtils.cp(@filenames[:last], raw_path)
389
- else
390
- # Write
391
- File.open(raw_path, 'w') { |io| io.write(@content[:last]) }
392
-
393
- # Generate diff
394
- generate_diff
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
- )
407
- end
408
-
409
- # Creates and returns a diff between the compiled content before the
410
- # current compilation session and the content compiled in the current
411
- # compilation session.
412
- #
413
- # @return [String, nil] The difference between the old and new compiled
414
- # content in `diff(1)` format, or nil if there is no previous compiled
415
- # content
416
- def diff
417
- if self.binary?
418
- nil
419
- else
420
- @diff_thread.join if @diff_thread
421
- @diff
422
- end
423
- end
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
-
455
- def inspect
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}>"
457
- end
458
-
459
- private
460
-
461
- def initialize_content
462
- # Initialize content and filenames
463
- if self.binary?
464
- @filenames = { :last => @item.raw_filename }
465
- @content = {}
466
- else
467
- @content = { :last => @item.raw_content }
468
- @filenames = {}
469
- end
470
- end
471
-
472
- def filter_named(name)
473
- Nanoc3::Filter.named(name)
474
- end
475
-
476
- def layout_with_identifier(layout_identifier)
477
- layout ||= @item.site.layouts.find { |l| l.identifier == layout_identifier.cleaned_identifier }
478
- raise Nanoc3::Errors::UnknownLayout.new(layout_identifier) if layout.nil?
479
- layout
480
- end
481
-
482
- def filter_for_layout(layout)
483
- # Get filter name and args
484
- filter_name, filter_args = @item.site.compiler.filter_for_layout(layout)
485
- raise Nanoc3::Errors::CannotDetermineFilter.new(layout_identifier) if filter_name.nil?
486
-
487
- # Get filter class
488
- filter_class = Nanoc3::Filter.named(filter_name)
489
- raise Nanoc3::Errors::UnknownFilter.new(filter_name) if filter_class.nil?
490
-
491
- # Create filter
492
- filter = filter_class.new(assigns.merge({ :layout => layout }))
493
-
494
- # Done
495
- [ filter, filter_name, filter_args ]
496
- end
497
-
498
- def generate_diff
499
- if @old_content.nil? || self.raw_path.nil? || !@item.site.config[:enable_output_diff]
500
- @diff = nil
501
- else
502
- @diff_thread = Thread.new do
503
- @diff = diff_strings(@old_content, @content[:last])
504
- sleep 2
505
- @diff_thread = nil
506
- end
507
- end
508
- end
509
-
510
- def diff_strings(a, b)
511
- require 'tempfile'
512
- require 'open3'
513
-
514
- # Create files
515
- Tempfile.open('old') do |old_file|
516
- Tempfile.open('new') do |new_file|
517
- # Write files
518
- old_file.write(a)
519
- new_file.write(b)
520
-
521
- # Diff
522
- cmd = [ 'diff', '-u', old_file.path, new_file.path ]
523
- Open3.popen3(*cmd) do |stdin, stdout, stderr|
524
- result = stdout.read
525
- return (result == '' ? nil : result)
526
- end
527
- end
528
- end
529
- rescue Errno::ENOENT
530
- warn 'Failed to run `diff`, so no diff with the previously compiled ' \
531
- 'content will be available.'
532
- nil
533
- end
534
-
535
- end
536
-
537
- end