nanoc 2.0 → 2.1

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