nanoc2 2.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. data/ChangeLog +3 -0
  2. data/LICENSE +19 -0
  3. data/README +75 -0
  4. data/Rakefile +76 -0
  5. data/bin/nanoc2 +26 -0
  6. data/lib/nanoc2.rb +73 -0
  7. data/lib/nanoc2/base.rb +26 -0
  8. data/lib/nanoc2/base/asset.rb +117 -0
  9. data/lib/nanoc2/base/asset_defaults.rb +21 -0
  10. data/lib/nanoc2/base/asset_rep.rb +282 -0
  11. data/lib/nanoc2/base/binary_filter.rb +44 -0
  12. data/lib/nanoc2/base/code.rb +41 -0
  13. data/lib/nanoc2/base/compiler.rb +67 -0
  14. data/lib/nanoc2/base/core_ext.rb +2 -0
  15. data/lib/nanoc2/base/core_ext/hash.rb +78 -0
  16. data/lib/nanoc2/base/core_ext/string.rb +8 -0
  17. data/lib/nanoc2/base/data_source.rb +286 -0
  18. data/lib/nanoc2/base/defaults.rb +30 -0
  19. data/lib/nanoc2/base/filter.rb +93 -0
  20. data/lib/nanoc2/base/layout.rb +91 -0
  21. data/lib/nanoc2/base/notification_center.rb +66 -0
  22. data/lib/nanoc2/base/page.rb +132 -0
  23. data/lib/nanoc2/base/page_defaults.rb +20 -0
  24. data/lib/nanoc2/base/page_rep.rb +324 -0
  25. data/lib/nanoc2/base/plugin.rb +71 -0
  26. data/lib/nanoc2/base/proxies.rb +5 -0
  27. data/lib/nanoc2/base/proxies/asset_proxy.rb +29 -0
  28. data/lib/nanoc2/base/proxies/asset_rep_proxy.rb +26 -0
  29. data/lib/nanoc2/base/proxies/layout_proxy.rb +25 -0
  30. data/lib/nanoc2/base/proxies/page_proxy.rb +35 -0
  31. data/lib/nanoc2/base/proxies/page_rep_proxy.rb +28 -0
  32. data/lib/nanoc2/base/proxy.rb +37 -0
  33. data/lib/nanoc2/base/router.rb +72 -0
  34. data/lib/nanoc2/base/site.rb +274 -0
  35. data/lib/nanoc2/base/template.rb +64 -0
  36. data/lib/nanoc2/binary_filters.rb +1 -0
  37. data/lib/nanoc2/binary_filters/image_science_thumbnail.rb +28 -0
  38. data/lib/nanoc2/cli.rb +9 -0
  39. data/lib/nanoc2/cli/base.rb +132 -0
  40. data/lib/nanoc2/cli/commands.rb +10 -0
  41. data/lib/nanoc2/cli/commands/autocompile.rb +80 -0
  42. data/lib/nanoc2/cli/commands/compile.rb +312 -0
  43. data/lib/nanoc2/cli/commands/create_layout.rb +85 -0
  44. data/lib/nanoc2/cli/commands/create_page.rb +85 -0
  45. data/lib/nanoc2/cli/commands/create_site.rb +323 -0
  46. data/lib/nanoc2/cli/commands/create_template.rb +76 -0
  47. data/lib/nanoc2/cli/commands/help.rb +69 -0
  48. data/lib/nanoc2/cli/commands/info.rb +125 -0
  49. data/lib/nanoc2/cli/commands/switch.rb +141 -0
  50. data/lib/nanoc2/cli/commands/update.rb +91 -0
  51. data/lib/nanoc2/cli/logger.rb +72 -0
  52. data/lib/nanoc2/data_sources.rb +2 -0
  53. data/lib/nanoc2/data_sources/filesystem.rb +707 -0
  54. data/lib/nanoc2/data_sources/filesystem_combined.rb +495 -0
  55. data/lib/nanoc2/extra.rb +6 -0
  56. data/lib/nanoc2/extra/auto_compiler.rb +285 -0
  57. data/lib/nanoc2/extra/context.rb +22 -0
  58. data/lib/nanoc2/extra/core_ext.rb +2 -0
  59. data/lib/nanoc2/extra/core_ext/hash.rb +54 -0
  60. data/lib/nanoc2/extra/core_ext/time.rb +13 -0
  61. data/lib/nanoc2/extra/file_proxy.rb +29 -0
  62. data/lib/nanoc2/extra/vcs.rb +48 -0
  63. data/lib/nanoc2/extra/vcses.rb +5 -0
  64. data/lib/nanoc2/extra/vcses/bazaar.rb +21 -0
  65. data/lib/nanoc2/extra/vcses/dummy.rb +20 -0
  66. data/lib/nanoc2/extra/vcses/git.rb +21 -0
  67. data/lib/nanoc2/extra/vcses/mercurial.rb +21 -0
  68. data/lib/nanoc2/extra/vcses/subversion.rb +21 -0
  69. data/lib/nanoc2/filters.rb +16 -0
  70. data/lib/nanoc2/filters/bluecloth.rb +13 -0
  71. data/lib/nanoc2/filters/erb.rb +19 -0
  72. data/lib/nanoc2/filters/erubis.rb +14 -0
  73. data/lib/nanoc2/filters/haml.rb +21 -0
  74. data/lib/nanoc2/filters/markaby.rb +14 -0
  75. data/lib/nanoc2/filters/maruku.rb +14 -0
  76. data/lib/nanoc2/filters/old.rb +19 -0
  77. data/lib/nanoc2/filters/rainpress.rb +13 -0
  78. data/lib/nanoc2/filters/rdiscount.rb +13 -0
  79. data/lib/nanoc2/filters/rdoc.rb +23 -0
  80. data/lib/nanoc2/filters/redcloth.rb +14 -0
  81. data/lib/nanoc2/filters/relativize_paths.rb +16 -0
  82. data/lib/nanoc2/filters/relativize_paths_in_css.rb +16 -0
  83. data/lib/nanoc2/filters/relativize_paths_in_html.rb +16 -0
  84. data/lib/nanoc2/filters/rubypants.rb +14 -0
  85. data/lib/nanoc2/filters/sass.rb +18 -0
  86. data/lib/nanoc2/helpers.rb +9 -0
  87. data/lib/nanoc2/helpers/blogging.rb +217 -0
  88. data/lib/nanoc2/helpers/capturing.rb +63 -0
  89. data/lib/nanoc2/helpers/filtering.rb +54 -0
  90. data/lib/nanoc2/helpers/html_escape.rb +25 -0
  91. data/lib/nanoc2/helpers/link_to.rb +113 -0
  92. data/lib/nanoc2/helpers/render.rb +49 -0
  93. data/lib/nanoc2/helpers/tagging.rb +56 -0
  94. data/lib/nanoc2/helpers/text.rb +38 -0
  95. data/lib/nanoc2/helpers/xml_sitemap.rb +63 -0
  96. data/lib/nanoc2/routers.rb +3 -0
  97. data/lib/nanoc2/routers/default.rb +54 -0
  98. data/lib/nanoc2/routers/no_dirs.rb +66 -0
  99. data/lib/nanoc2/routers/versioned.rb +79 -0
  100. metadata +185 -0
@@ -0,0 +1,72 @@
1
+ require 'singleton'
2
+
3
+ module Nanoc2::CLI
4
+
5
+ # Nanoc2::CLI::Logger is a singleton class responsible for generating
6
+ # feedback in the terminal.
7
+ class Logger
8
+
9
+ ACTION_COLORS = {
10
+ :create => "\e[1m" + "\e[32m", # bold + green
11
+ :update => "\e[1m" + "\e[33m", # bold + yellow
12
+ :identical => "\e[1m", # bold
13
+ :skip => "\e[1m", # bold
14
+ :'not written' => "\e[1m" # bold
15
+ }
16
+
17
+ include Singleton
18
+
19
+ # The log level, which can be :high, :low or :off (which will log all
20
+ # messages, only high-priority messages, or no messages at all,
21
+ # respectively).
22
+ attr_accessor :level
23
+
24
+ # Whether to use color in log messages or not
25
+ attr_accessor :color
26
+ alias_method :color?, :color
27
+
28
+ def initialize # :nodoc:
29
+ @level = :high
30
+ @color = true
31
+ end
32
+
33
+ # Logs a file-related action.
34
+ #
35
+ # +level+:: The importance of this action. Can be :high or :low.
36
+ #
37
+ # +action+:: The kind of file action. Can be :create, :update or
38
+ # :identical.
39
+ #
40
+ # +path+:: The path to the file the action was performed on.
41
+ def file(level, action, path, duration=nil)
42
+ log(
43
+ level,
44
+ '%s%12s%s %s%s' % [
45
+ color? ? ACTION_COLORS[action.to_sym] : '',
46
+ action,
47
+ color? ? "\e[0m" : '',
48
+ duration.nil? ? '' : "[%2.2fs] " % [ duration ],
49
+ path
50
+ ]
51
+ )
52
+ end
53
+
54
+ # Logs a message.
55
+ #
56
+ # +level+:: The importance of this message. Can be :high or :low.
57
+ #
58
+ # +s+:: The message to be logged.
59
+ #
60
+ # +io+:: The IO instance to which the message will be written. Defaults to
61
+ # standard output.
62
+ def log(level, s, io=$stdout)
63
+ # Don't log when logging is disabled
64
+ return if @level == :off
65
+
66
+ # Log when level permits it
67
+ io.puts(s) if (@level == :low or @level == level)
68
+ end
69
+
70
+ end
71
+
72
+ end
@@ -0,0 +1,2 @@
1
+ require 'nanoc2/data_sources/filesystem'
2
+ require 'nanoc2/data_sources/filesystem_combined'
@@ -0,0 +1,707 @@
1
+ module Nanoc2::DataSources
2
+
3
+ # The filesystem data source is the default data source for a new nanoc
4
+ # site. It stores all data as files on the hard disk.
5
+ #
6
+ # None of the methods are documented in this file. See Nanoc2::DataSource for
7
+ # documentation on the overridden methods instead.
8
+ #
9
+ # = Pages
10
+ #
11
+ # The filesystem data source stores its pages in nested directories. Each
12
+ # directory represents a single page. The root directory is the 'content'
13
+ # directory.
14
+ #
15
+ # Every directory has a content file and a meta file. The content file
16
+ # contains the actual page content, while the meta file contains the page's
17
+ # metadata, formatted as YAML.
18
+ #
19
+ # Both content files and meta files are named after its parent directory
20
+ # (i.e. page). For example, a page named 'foo' will have a directorynamed
21
+ # 'foo', with e.g. a 'foo.markdown' content file and a 'foo.yaml' meta file.
22
+ #
23
+ # Content file extensions are not used for determining the filter that
24
+ # should be run; the meta file defines the list of filters. The meta file
25
+ # extension must always be 'yaml', though.
26
+ #
27
+ # Content files can also have the 'index' basename. Similarly, meta files
28
+ # can have the 'meta' basename. For example, a parent directory named 'foo'
29
+ # can have an 'index.txt' content file and a 'meta.yaml' meta file. This is
30
+ # to preserve backward compatibility.
31
+ #
32
+ # = Page defaults
33
+ #
34
+ # The page defaults are loaded from a YAML-formatted file named
35
+ # 'page_defaults.yaml' at the top level of the nanoc site directory. For
36
+ # backward compatibility, the file can also be named 'meta.yaml'.
37
+ #
38
+ # = Assets
39
+ #
40
+ # Assets are stored in the 'assets' directory (surprise!). The structure is
41
+ # very similar to the structure of the 'content' directory, so see the Pages
42
+ # section for details on how this directory is structured.
43
+ #
44
+ # = Asset defaults
45
+ #
46
+ # The asset defaults are stored similar to the way page defaults are stored,
47
+ # except that the asset defaults file is named 'asset_defaults.yaml'
48
+ # instead.
49
+ #
50
+ # = Layouts
51
+ #
52
+ # Layouts are stored as directories in the 'layouts' directory. Each layout
53
+ # contains a content file and a meta file. The content file contain the
54
+ # actual layout, and the meta file describes how the page should be handled
55
+ # (contains the filter that should be used).
56
+ #
57
+ # For backward compatibility, a layout can also be a single file in the
58
+ # 'layouts' directory. Such a layout cannot have any metadata; the filter
59
+ # used for this layout is determined from the file extension.
60
+ #
61
+ # = Templates
62
+ #
63
+ # Templates are located in the 'templates' directroy. Every template is a
64
+ # directory consisting of a content file and a meta file, both named after
65
+ # the template. This is very similar to the way pages are stored, except
66
+ # that templates cannot be nested.
67
+ #
68
+ # = Code
69
+ #
70
+ # Code is stored in '.rb' files in the 'lib' directory. Code can reside in
71
+ # sub-directories.
72
+ class Filesystem < Nanoc2::DataSource
73
+
74
+ PAGE_DEFAULTS_FILENAME = 'page_defaults.yaml'
75
+ PAGE_DEFAULTS_FILENAME_OLD = 'meta.yaml'
76
+ ASSET_DEFAULTS_FILENAME = 'asset_defaults.yaml'
77
+
78
+ ########## Attributes ##########
79
+
80
+ identifier :filesystem
81
+
82
+ ########## VCSes ##########
83
+
84
+ attr_accessor :vcs
85
+
86
+ def vcs
87
+ @vcs ||= Nanoc2::Extra::VCSes::Dummy.new
88
+ end
89
+
90
+ ########## Preparation ##########
91
+
92
+ def up # :nodoc:
93
+ end
94
+
95
+ def down # :nodoc:
96
+ end
97
+
98
+ def setup # :nodoc:
99
+ # Create directories
100
+ %w( assets content templates layouts lib ).each do |dir|
101
+ FileUtils.mkdir_p(dir)
102
+ vcs.add(dir)
103
+ end
104
+ end
105
+
106
+ def destroy # :nodoc:
107
+ # Remove files
108
+ vcs.remove(ASSET_DEFAULTS_FILENAME) if File.file?(ASSET_DEFAULTS_FILENAME)
109
+ vcs.remove(PAGE_DEFAULTS_FILENAME) if File.file?(PAGE_DEFAULTS_FILENAME)
110
+ vcs.remove(PAGE_DEFAULTS_FILENAME_OLD) if File.file?(PAGE_DEFAULTS_FILENAME_OLD)
111
+
112
+ # Remove directories
113
+ vcs.remove('assets')
114
+ vcs.remove('content')
115
+ vcs.remove('templates')
116
+ vcs.remove('layouts')
117
+ vcs.remove('lib')
118
+ end
119
+
120
+ def update # :nodoc:
121
+ update_page_defaults
122
+ update_pages
123
+ update_templates
124
+ end
125
+
126
+ ########## Pages ##########
127
+
128
+ def pages # :nodoc:
129
+ meta_filenames('content').map do |meta_filename|
130
+ # Read metadata
131
+ meta = YAML.load_file(meta_filename) || {}
132
+
133
+ if meta['is_draft']
134
+ # Skip drafts
135
+ nil
136
+ else
137
+ # Get content
138
+ content_filename = content_filename_for_dir(File.dirname(meta_filename))
139
+ content = File.read(content_filename)
140
+
141
+ # Get attributes
142
+ attributes = meta.merge(:file => Nanoc2::Extra::FileProxy.new(content_filename))
143
+
144
+ # Get path
145
+ path = meta_filename.sub(/^content/, '').sub(/[^\/]+\.yaml$/, '')
146
+
147
+ # Get modification times
148
+ meta_mtime = File.stat(meta_filename).mtime
149
+ content_mtime = File.stat(content_filename).mtime
150
+ mtime = meta_mtime > content_mtime ? meta_mtime : content_mtime
151
+
152
+ # Create page object
153
+ Nanoc2::Page.new(content, attributes, path, mtime)
154
+ end
155
+ end.compact
156
+ end
157
+
158
+ def save_page(page) # :nodoc:
159
+ # Determine possible meta file paths
160
+ last_component = page.path.split('/')[-1]
161
+ meta_filename_worst = 'content' + page.path + 'index.yaml'
162
+ meta_filename_best = 'content' + page.path + (last_component || 'content') + '.yaml'
163
+
164
+ # Get existing path
165
+ existing_path = nil
166
+ existing_path = meta_filename_best if File.file?(meta_filename_best)
167
+ existing_path = meta_filename_worst if File.file?(meta_filename_worst)
168
+
169
+ if existing_path.nil?
170
+ # Get filenames
171
+ dir_path = 'content' + page.path
172
+ meta_filename = meta_filename_best
173
+ content_filename = 'content' + page.path + (last_component || 'content') + '.html'
174
+
175
+ # Notify
176
+ Nanoc2::NotificationCenter.post(:file_created, meta_filename)
177
+ Nanoc2::NotificationCenter.post(:file_created, content_filename)
178
+
179
+ # Create directories if necessary
180
+ FileUtils.mkdir_p(dir_path)
181
+ else
182
+ # Get filenames
183
+ meta_filename = existing_path
184
+ content_filename = content_filename_for_dir(File.dirname(existing_path))
185
+
186
+ # Notify
187
+ Nanoc2::NotificationCenter.post(:file_updated, meta_filename)
188
+ Nanoc2::NotificationCenter.post(:file_updated, content_filename)
189
+ end
190
+
191
+ # Write files
192
+ File.open(meta_filename, 'w') { |io| io.write(page.attributes.to_split_yaml) }
193
+ File.open(content_filename, 'w') { |io| io.write(page.content) }
194
+
195
+ # Add to working copy if possible
196
+ if existing_path.nil?
197
+ vcs.add(meta_filename)
198
+ vcs.add(content_filename)
199
+ end
200
+ end
201
+
202
+ def move_page(page, new_path) # :nodoc:
203
+ # TODO implement
204
+ end
205
+
206
+ def delete_page(page) # :nodoc:
207
+ # TODO implement
208
+ end
209
+
210
+ ########## Assets ##########
211
+
212
+ def assets # :nodoc:
213
+ meta_filenames('assets').map do |meta_filename|
214
+ # Read metadata
215
+ meta = YAML.load_file(meta_filename) || {}
216
+
217
+ # Get content file
218
+ content_filename = content_filename_for_dir(File.dirname(meta_filename))
219
+ content_file = Nanoc2::Extra::FileProxy.new(content_filename)
220
+
221
+ # Get attributes
222
+ attributes = { 'extension' => File.extname(content_filename)[1..-1] }.merge(meta)
223
+
224
+ # Get path
225
+ path = meta_filename.sub(/^assets/, '').sub(/[^\/]+\.yaml$/, '')
226
+
227
+ # Get modification times
228
+ meta_mtime = File.stat(meta_filename).mtime
229
+ content_mtime = File.stat(content_filename).mtime
230
+ mtime = meta_mtime > content_mtime ? meta_mtime : content_mtime
231
+
232
+ # Create asset object
233
+ Nanoc2::Asset.new(content_file, attributes, path, mtime)
234
+ end
235
+ end
236
+
237
+ def save_asset(asset) # :nodoc:
238
+ # Determine meta file path
239
+ last_component = asset.path.split('/')[-1]
240
+ meta_filename = 'assets' + asset.path + last_component + '.yaml'
241
+
242
+ # Get existing path
243
+ existing_path = nil
244
+ existing_path = meta_filename_best if File.file?(meta_filename_best)
245
+ existing_path = meta_filename_worst if File.file?(meta_filename_worst)
246
+
247
+ if meta_filename.nil?
248
+ # Get filenames
249
+ dir_path = 'assets' + asset.path
250
+ content_filename = 'assets' + asset.path + last_component + '.dat'
251
+
252
+ # Notify
253
+ Nanoc2::NotificationCenter.post(:file_created, meta_filename)
254
+ Nanoc2::NotificationCenter.post(:file_created, content_filename)
255
+
256
+ # Create directories if necessary
257
+ FileUtils.mkdir_p(dir_path)
258
+ else
259
+ # Get filenames
260
+ content_filename = content_filename_for_dir(File.dirname(meta_filename))
261
+
262
+ # Notify
263
+ Nanoc2::NotificationCenter.post(:file_updated, meta_filename)
264
+ Nanoc2::NotificationCenter.post(:file_updated, content_filename)
265
+ end
266
+
267
+ # Write files
268
+ File.open(meta_filename, 'w') { |io| io.write(asset.attributes.to_split_yaml) }
269
+ File.open(content_filename, 'w') { }
270
+
271
+ # Add to working copy if possible
272
+ if meta_filename.nil?
273
+ vcs.add(meta_filename)
274
+ vcs.add(content_filename)
275
+ end
276
+ end
277
+
278
+ def move_asset(asset, new_path) # :nodoc:
279
+ # TODO implement
280
+ end
281
+
282
+ def delete_asset(asset) # :nodoc:
283
+ # TODO implement
284
+ end
285
+
286
+ ########## Page Defaults ##########
287
+
288
+ def page_defaults # :nodoc:
289
+ # Get attributes
290
+ filename = File.file?(PAGE_DEFAULTS_FILENAME) ? PAGE_DEFAULTS_FILENAME : PAGE_DEFAULTS_FILENAME_OLD
291
+ attributes = YAML.load_file(filename) || {}
292
+
293
+ # Get mtime
294
+ mtime = File.stat(filename).mtime
295
+
296
+ # Build page defaults
297
+ Nanoc2::PageDefaults.new(attributes, mtime)
298
+ end
299
+
300
+ def save_page_defaults(page_defaults) # :nodoc:
301
+ # Notify
302
+ if File.file?(PAGE_DEFAULTS_FILENAME)
303
+ filename = PAGE_DEFAULTS_FILENAME
304
+ created = false
305
+ Nanoc2::NotificationCenter.post(:file_updated, filename)
306
+ elsif File.file?(PAGE_DEFAULTS_FILENAME_OLD)
307
+ filename = PAGE_DEFAULTS_FILENAME_OLD
308
+ created = false
309
+ Nanoc2::NotificationCenter.post(:file_updated, filename)
310
+ else
311
+ filename = PAGE_DEFAULTS_FILENAME
312
+ created = true
313
+ Nanoc2::NotificationCenter.post(:file_created, filename)
314
+ end
315
+
316
+ # Write
317
+ File.open(filename, 'w') do |io|
318
+ io.write(page_defaults.attributes.to_split_yaml)
319
+ end
320
+
321
+ # Add to working copy if possible
322
+ vcs.add(filename) if created
323
+ end
324
+
325
+ ########## Asset Defaults ##########
326
+
327
+ def asset_defaults # :nodoc:
328
+ if File.file?(ASSET_DEFAULTS_FILENAME)
329
+ # Get attributes
330
+ attributes = YAML.load_file(ASSET_DEFAULTS_FILENAME) || {}
331
+
332
+ # Get mtime
333
+ mtime = File.stat(ASSET_DEFAULTS_FILENAME).mtime
334
+
335
+ # Build asset defaults
336
+ Nanoc2::AssetDefaults.new(attributes, mtime)
337
+ else
338
+ Nanoc2::AssetDefaults.new({})
339
+ end
340
+ end
341
+
342
+ def save_asset_defaults(asset_defaults) # :nodoc:
343
+ # Notify
344
+ if File.file?(ASSET_DEFAULTS_FILENAME)
345
+ Nanoc2::NotificationCenter.post(:file_updated, ASSET_DEFAULTS_FILENAME)
346
+ created = false
347
+ else
348
+ Nanoc2::NotificationCenter.post(:file_created, ASSET_DEFAULTS_FILENAME)
349
+ created = true
350
+ end
351
+
352
+ # Write
353
+ File.open(ASSET_DEFAULTS_FILENAME, 'w') do |io|
354
+ io.write(asset_defaults.attributes.to_split_yaml)
355
+ end
356
+
357
+ # Add to working copy if possible
358
+ vcs.add(ASSET_DEFAULTS_FILENAME) if created
359
+ end
360
+
361
+ ########## Layouts ##########
362
+
363
+ def layouts # :nodoc:
364
+ # Determine what layout directory structure is being used
365
+ is_old_school = (Dir['layouts/*'].select { |f| File.file?(f) }.size > 0)
366
+
367
+ if is_old_school
368
+ # Warn about deprecation
369
+ warn(
370
+ 'DEPRECATION WARNING: nanoc 2.1 changes the way layouts are ' +
371
+ 'stored. Future versions will not support these outdated sites. ' +
372
+ 'To update your site, issue \'nanoc update\'.'
373
+ )
374
+
375
+ Dir[File.join('layouts', '*')].reject { |f| f =~ /~$/ }.map do |filename|
376
+ # Get content
377
+ content = File.read(filename)
378
+
379
+ # Get attributes
380
+ attributes = { :extension => File.extname(filename)}
381
+
382
+ # Get path
383
+ path = File.basename(filename, attributes[:extension])
384
+
385
+ # Get modification time
386
+ mtime = File.stat(filename).mtime
387
+
388
+ # Create layout object
389
+ Nanoc2::Layout.new(content, attributes, path, mtime)
390
+ end
391
+ else
392
+ meta_filenames('layouts').map do |meta_filename|
393
+ # Get content
394
+ content_filename = content_filename_for_dir(File.dirname(meta_filename))
395
+ content = File.read(content_filename)
396
+
397
+ # Get attributes
398
+ attributes = YAML.load_file(meta_filename) || {}
399
+
400
+ # Get path
401
+ path = meta_filename.sub(/^layouts\//, '').sub(/\/[^\/]+\.yaml$/, '')
402
+
403
+ # Get modification times
404
+ meta_mtime = File.stat(meta_filename).mtime
405
+ content_mtime = File.stat(content_filename).mtime
406
+ mtime = meta_mtime > content_mtime ? meta_mtime : content_mtime
407
+
408
+ # Create layout object
409
+ Nanoc2::Layout.new(content, attributes, path, mtime)
410
+ end
411
+ end
412
+ end
413
+
414
+ def save_layout(layout) # :nodoc:
415
+ # Determine what layout directory structure is being used
416
+ is_old_school = (Dir['layouts/*'].select { |f| File.file?(f) }.size > 0)
417
+ error_outdated if is_old_school
418
+
419
+ # Get paths
420
+ last_component = layout.path.split('/')[-1]
421
+ dir_path = 'layouts' + layout.path
422
+ meta_filename = dir_path + last_component + '.yaml'
423
+ content_filename = Dir[dir_path + last_component + '.*'][0]
424
+
425
+ if File.file?(meta_filename)
426
+ created = false
427
+
428
+ # Notify
429
+ Nanoc2::NotificationCenter.post(:file_updated, meta_filename)
430
+ Nanoc2::NotificationCenter.post(:file_updated, content_filename)
431
+ else
432
+ created = true
433
+
434
+ # Create dir
435
+ FileUtils.mkdir_p(dir_path)
436
+
437
+ # Get content filename
438
+ content_filename = dir_path + last_component + '.html'
439
+
440
+ # Notify
441
+ Nanoc2::NotificationCenter.post(:file_created, meta_filename)
442
+ Nanoc2::NotificationCenter.post(:file_created, content_filename)
443
+ end
444
+
445
+ # Write files
446
+ File.open(meta_filename, 'w') { |io| io.write(layout.attributes.to_split_yaml) }
447
+ File.open(content_filename, 'w') { |io| io.write(layout.content) }
448
+
449
+ # Add to working copy if possible
450
+ if created
451
+ vcs.add(meta_filename)
452
+ vcs.add(content_filename)
453
+ end
454
+ end
455
+
456
+ def move_layout(layout, new_path) # :nodoc:
457
+ # TODO implement
458
+ end
459
+
460
+ def delete_layout(layout) # :nodoc:
461
+ # TODO implement
462
+ end
463
+
464
+ ########## Templates ##########
465
+
466
+ def templates # :nodoc:
467
+ meta_filenames('templates').map do |meta_filename|
468
+ # Get name
469
+ name = meta_filename.sub(/^templates\/(.*)\/[^\/]+\.yaml$/, '\1')
470
+
471
+ # Get content
472
+ content_filename = content_filename_for_dir(File.dirname(meta_filename))
473
+ content = File.read(content_filename)
474
+
475
+ # Get attributes
476
+ attributes = YAML.load_file(meta_filename) || {}
477
+
478
+ # Build template
479
+ Nanoc2::Template.new(content, attributes, name)
480
+ end
481
+ end
482
+
483
+ def save_template(template) # :nodoc:
484
+ # Determine possible meta file paths
485
+ meta_filename_worst = 'templates/' + template.name + '/index.yaml'
486
+ meta_filename_best = 'templates/' + template.name + '/' + template.name + '.yaml'
487
+
488
+ # Get existing path
489
+ existing_path = nil
490
+ existing_path = meta_filename_best if File.file?(meta_filename_best)
491
+ existing_path = meta_filename_worst if File.file?(meta_filename_worst)
492
+
493
+ if existing_path.nil?
494
+ # Get filenames
495
+ dir_path = 'templates/' + template.name
496
+ meta_filename = meta_filename_best
497
+ content_filename = 'templates/' + template.name + '/' + template.name + '.html'
498
+
499
+ # Notify
500
+ Nanoc2::NotificationCenter.post(:file_created, meta_filename)
501
+ Nanoc2::NotificationCenter.post(:file_created, content_filename)
502
+
503
+ # Create directories if necessary
504
+ FileUtils.mkdir_p(dir_path)
505
+ else
506
+ # Get filenames
507
+ meta_filename = existing_path
508
+ content_filename = content_filename_for_dir(File.dirname(existing_path))
509
+
510
+ # Notify
511
+ Nanoc2::NotificationCenter.post(:file_updated, meta_filename)
512
+ Nanoc2::NotificationCenter.post(:file_updated, content_filename)
513
+ end
514
+
515
+ # Write files
516
+ File.open(meta_filename, 'w') { |io| io.write(template.page_attributes.to_split_yaml) }
517
+ File.open(content_filename, 'w') { |io| io.write(template.page_content) }
518
+
519
+ # Add to working copy if possible
520
+ if existing_path.nil?
521
+ vcs.add(meta_filename)
522
+ vcs.add(content_filename)
523
+ end
524
+ end
525
+
526
+ def move_template(template, new_name) # :nodoc:
527
+ # TODO implement
528
+ end
529
+
530
+ def delete_template(template) # :nodoc:
531
+ # TODO implement
532
+ end
533
+
534
+ ########## Code ##########
535
+
536
+ def code # :nodoc:
537
+ # Get files
538
+ filenames = Dir['lib/**/*.rb'].sort
539
+
540
+ # Get data
541
+ data = filenames.map { |filename| File.read(filename) + "\n" }.join('')
542
+
543
+ # Get modification time
544
+ mtimes = filenames.map { |filename| File.stat(filename).mtime }
545
+ mtime = mtimes.inject { |memo, mtime| memo > mtime ? mtime : memo }
546
+
547
+ # Build code
548
+ Nanoc2::Code.new(data, mtime)
549
+ end
550
+
551
+ def save_code(code) # :nodoc:
552
+ # Check whether code existed
553
+ existed = File.file?('lib/default.rb')
554
+
555
+ # Remove all existing code files
556
+ Dir['lib/**/*.rb'].each do |file|
557
+ vcs.remove(file) unless file == 'lib/default.rb'
558
+ end
559
+
560
+ # Notify
561
+ if existed
562
+ Nanoc2::NotificationCenter.post(:file_updated, 'lib/default.rb')
563
+ else
564
+ Nanoc2::NotificationCenter.post(:file_created, 'lib/default.rb')
565
+ end
566
+
567
+ # Write new code
568
+ File.open('lib/default.rb', 'w') do |io|
569
+ io.write(code.data)
570
+ end
571
+
572
+ # Add to working copy if possible
573
+ vcs.add('lib/default.rb') unless existed
574
+ end
575
+
576
+ private
577
+
578
+ ########## Custom functions ##########
579
+
580
+ # Returns the list of all meta files in the given base directory as well
581
+ # as its subdirectories.
582
+ def meta_filenames(base)
583
+ # Find all possible meta file names
584
+ filenames = Dir[base + '/**/*.yaml']
585
+
586
+ # Filter out invalid meta files
587
+ good_filenames = []
588
+ bad_filenames = []
589
+ filenames.each do |filename|
590
+ if filename =~ /meta\.yaml$/ or filename =~ /([^\/]+)\/\1\.yaml$/
591
+ good_filenames << filename
592
+ else
593
+ bad_filenames << filename
594
+ end
595
+ end
596
+
597
+ # Warn about bad filenames
598
+ unless bad_filenames.empty?
599
+ raise RuntimeError.new(
600
+ "The following files appear to be meta files, " +
601
+ "but have an invalid name:\n - " +
602
+ bad_filenames.join("\n - ")
603
+ )
604
+ end
605
+
606
+ good_filenames
607
+ end
608
+
609
+ # Returns the filename of the content file in the given directory,
610
+ # ignoring any unwanted files (files that end with '~', '.orig', '.rej' or
611
+ # '.bak')
612
+ def content_filename_for_dir(dir)
613
+ # Find all files
614
+ filename_glob_1 = dir.sub(/([^\/]+)$/, '\1/\1.*')
615
+ filename_glob_2 = dir.sub(/([^\/]+)$/, '\1/index.*')
616
+ filenames = (Dir[filename_glob_1] + Dir[filename_glob_2]).uniq
617
+
618
+ # Reject meta files
619
+ filenames.reject! { |f| f =~ /\.yaml$/ }
620
+
621
+ # Reject backups
622
+ filenames.reject! { |f| f =~ /(~|\.orig|\.rej|\.bak)$/ }
623
+
624
+ # Make sure there is only one content file
625
+ if filenames.size != 1
626
+ raise RuntimeError.new(
627
+ "Expected 1 content file in #{dir} but found #{filenames.size}"
628
+ )
629
+ end
630
+
631
+ # Return content filename
632
+ filenames.first
633
+ end
634
+
635
+ # Raises an "outdated data format" error.
636
+ def error_outdated
637
+ raise RuntimeError.new(
638
+ 'This site\'s data is stored in an old format and must be updated. ' +
639
+ 'To do so, issue the \'nanoc update\' command. For help on ' +
640
+ 'updating a site\'s data, issue \'nanoc help update\'.'
641
+ )
642
+ end
643
+
644
+ # Updated outdated page defaults (renames page defaults file)
645
+ def update_page_defaults
646
+ return unless File.file?(PAGE_DEFAULTS_FILENAME_OLD)
647
+
648
+ vcs.move(PAGE_DEFAULTS_FILENAME_OLD, PAGE_DEFAULTS_FILENAME)
649
+ end
650
+
651
+ # Updates outdated pages (both content and meta file names).
652
+ def update_pages
653
+ # Update content files
654
+ # content/foo/bar/baz/index.ext -> content/foo/bar/baz/baz.ext
655
+ Dir['content/**/index.*'].select { |f| File.file?(f) }.each do |old_filename|
656
+ # Determine new name
657
+ if old_filename =~ /^content\/index\./
658
+ new_filename = old_filename.sub(/^content\/index\./, 'content/content.')
659
+ else
660
+ new_filename = old_filename.sub(/([^\/]+)\/index\.([^\/]+)$/, '\1/\1.\2')
661
+ end
662
+
663
+ # Move
664
+ vcs.move(old_filename, new_filename)
665
+ end
666
+
667
+ # Update meta files
668
+ # content/foo/bar/baz/meta.yaml -> content/foo/bar/baz/baz.yaml
669
+ Dir['content/**/meta.yaml'].select { |f| File.file?(f) }.each do |old_filename|
670
+ # Determine new name
671
+ if old_filename == 'content/meta.yaml'
672
+ new_filename = 'content/content.yaml'
673
+ else
674
+ new_filename = old_filename.sub(/([^\/]+)\/meta.yaml$/, '\1/\1.yaml')
675
+ end
676
+
677
+ # Move
678
+ vcs.move(old_filename, new_filename)
679
+ end
680
+ end
681
+
682
+ # Updates outdated templates (both content and meta file names).
683
+ def update_templates
684
+ # Update content files
685
+ # templates/foo/index.ext -> templates/foo/foo.ext
686
+ Dir['templates/**/index.*'].select { |f| File.file?(f) }.each do |old_filename|
687
+ # Determine new name
688
+ new_filename = old_filename.sub(/([^\/]+)\/index\.([^\/]+)$/, '\1/\1.\2')
689
+
690
+ # Move
691
+ vcs.move(old_filename, new_filename)
692
+ end
693
+
694
+ # Update meta files
695
+ # templates/foo/meta.yaml -> templates/foo/foo.yaml
696
+ Dir['templates/**/meta.yaml'].select { |f| File.file?(f) }.each do |old_filename|
697
+ # Determine new name
698
+ new_filename = old_filename.sub(/([^\/]+)\/meta.yaml$/, '\1/\1.yaml')
699
+
700
+ # Move
701
+ vcs.move(old_filename, new_filename)
702
+ end
703
+ end
704
+
705
+ end
706
+
707
+ end