nanoc 1.6.2 → 2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/ChangeLog +27 -0
  2. data/Rakefile +34 -27
  3. data/bin/nanoc +153 -49
  4. data/lib/nanoc.rb +15 -33
  5. data/lib/nanoc/base/auto_compiler.rb +124 -0
  6. data/lib/nanoc/base/compiler.rb +55 -0
  7. data/lib/nanoc/base/core_ext/hash.rb +34 -0
  8. data/lib/nanoc/base/data_source.rb +53 -0
  9. data/lib/nanoc/base/enhancements.rb +89 -0
  10. data/lib/nanoc/base/filter.rb +16 -0
  11. data/lib/nanoc/base/layout_processor.rb +33 -0
  12. data/lib/nanoc/base/page.rb +155 -0
  13. data/lib/nanoc/base/page_proxy.rb +31 -0
  14. data/lib/nanoc/base/plugin.rb +19 -0
  15. data/lib/nanoc/base/plugin_manager.rb +33 -0
  16. data/lib/nanoc/base/site.rb +143 -0
  17. data/lib/nanoc/data_sources/database.rb +259 -0
  18. data/lib/nanoc/data_sources/filesystem.rb +308 -0
  19. data/lib/nanoc/data_sources/trivial.rb +145 -0
  20. data/lib/nanoc/filters/erb.rb +34 -0
  21. data/lib/nanoc/filters/haml.rb +16 -0
  22. data/lib/nanoc/filters/markaby.rb +15 -0
  23. data/lib/nanoc/filters/markdown.rb +13 -0
  24. data/lib/nanoc/filters/rdoc.rb +14 -0
  25. data/lib/nanoc/filters/smartypants.rb +13 -0
  26. data/lib/nanoc/filters/textile.rb +13 -0
  27. data/lib/nanoc/layout_processors/erb.rb +35 -0
  28. data/lib/nanoc/layout_processors/haml.rb +18 -0
  29. data/lib/nanoc/layout_processors/markaby.rb +16 -0
  30. metadata +37 -30
  31. data/lib/nanoc/compiler.rb +0 -145
  32. data/lib/nanoc/core_ext.rb +0 -1
  33. data/lib/nanoc/core_ext/array.rb +0 -17
  34. data/lib/nanoc/core_ext/hash.rb +0 -43
  35. data/lib/nanoc/core_ext/string.rb +0 -13
  36. data/lib/nanoc/core_ext/yaml.rb +0 -10
  37. data/lib/nanoc/creator.rb +0 -180
  38. data/lib/nanoc/enhancements.rb +0 -101
  39. data/lib/nanoc/filters.rb +0 -7
  40. data/lib/nanoc/filters/eruby_filter.rb +0 -39
  41. data/lib/nanoc/filters/haml_filter.rb +0 -18
  42. data/lib/nanoc/filters/liquid_filter.rb +0 -47
  43. data/lib/nanoc/filters/markaby_filter.rb +0 -15
  44. data/lib/nanoc/filters/markdown_filter.rb +0 -13
  45. data/lib/nanoc/filters/rdoc_filter.rb +0 -15
  46. data/lib/nanoc/filters/sass_filter.rb +0 -13
  47. data/lib/nanoc/filters/smartypants_filter.rb +0 -13
  48. data/lib/nanoc/filters/textile_filter.rb +0 -13
  49. data/lib/nanoc/page.rb +0 -171
  50. data/lib/nanoc/page_drop.rb +0 -18
  51. data/lib/nanoc/page_proxy.rb +0 -30
@@ -0,0 +1,308 @@
1
+ module Nanoc::DataSource::Filesystem
2
+
3
+ class FilesystemDataSource < Nanoc::DataSource
4
+
5
+ ########## Attributes ##########
6
+
7
+ identifier :filesystem
8
+
9
+ ########## Preparation ##########
10
+
11
+ def up
12
+ end
13
+
14
+ def down
15
+ end
16
+
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"
27
+ end
28
+
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"
45
+ end
46
+
47
+ # Create template
48
+ FileManager.create_file 'templates/default/default.txt' do
49
+ "Hi, I'm a new page!\n"
50
+ 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"
56
+ end
57
+
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"
68
+ end
69
+
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"
79
+ end
80
+
81
+ end
82
+
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
110
+
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
127
+ end
128
+ end
129
+
130
+ # The page defaults are loaded from a 'meta.yaml' file
131
+ def page_defaults
132
+ (YAML.load_file('meta.yaml') || {}).clean
133
+ end
134
+
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)
146
+
147
+ # Build hash for layout
148
+ { :name => name, :content => content, :extension => extension }
149
+ end
150
+ end
151
+
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)
173
+ 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
+ end
184
+ end
185
+
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" }
190
+ end
191
+
192
+ ########## Creating data ##########
193
+
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(/^\/+|\/+$/, '')
200
+
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"
234
+ end
235
+ end
236
+
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'
245
+
246
+ # Make sure the template doesn't exist yet
247
+ error "A template named '#{name}' already exists." if File.exist?(meta_path)
248
+
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" }
252
+ end
253
+
254
+ private
255
+
256
+ ########## Custom functions ##########
257
+
258
+ # Returns the list of meta files in the given (optional) base directory.
259
+ def meta_filenames(base='content')
260
+ # Find all possible meta file names
261
+ filenames = Dir[base + '/**/*.yaml']
262
+
263
+ # Filter out invalid meta files
264
+ good_filenames = []
265
+ bad_filenames = []
266
+ filenames.each do |filename|
267
+ if filename =~ /meta\.yaml$/ or filename =~ /([^\/]+)\/\1\.yaml$/
268
+ good_filenames << filename
269
+ else
270
+ bad_filenames << filename
271
+ end
272
+ end
273
+
274
+ # Warn about bad filenames
275
+ 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 - ")
279
+ end
280
+
281
+ good_filenames
282
+ end
283
+
284
+ # Returns a File object for the content file in the given directory
285
+ def content_file_for_dir(dir)
286
+ # Find all files
287
+ filename_glob_1 = dir.sub(/([^\/]+)$/, '\1/\1.*')
288
+ filename_glob_2 = dir.sub(/([^\/]+)$/, '\1/index.*')
289
+ filenames = Dir[filename_glob_1] + Dir[filename_glob_2]
290
+
291
+ # Reject meta files
292
+ filenames.reject! { |f| f =~ /\.yaml$/ }
293
+
294
+ # Reject backups
295
+ filenames.reject! { |f| f =~ /~$/ }
296
+
297
+ # Make sure there is only one content file
298
+ if filenames.size != 1
299
+ error "Expected 1 content file in #{dir} but found #{filenames.size}"
300
+ end
301
+
302
+ # Read content file
303
+ File.new(filenames.first)
304
+ end
305
+
306
+ end
307
+
308
+ end
@@ -0,0 +1,145 @@
1
+ # This is the module where the trivial data source will live in. It's not
2
+ # really _necessary_ to create a separate module/namespace for each data
3
+ # source, but it can be useful, especially when the data source needs extra
4
+ # classes (for example, the database data source defines uses ActiveRecord, so
5
+ # it needs classes for each table).
6
+ module Nanoc::DataSource::Trivial
7
+
8
+ # This is the implementation of a trivial data source. It doesn't do much
9
+ # except return bogus data. It is meant to be a very simple example of a
10
+ # data source, and it should be quite useful for those who want to write
11
+ # their own data sources.
12
+ class TrivialDataSource < Nanoc::DataSource
13
+
14
+ ########## Attributes ##########
15
+
16
+ # DataSource.identifier defines the name for this data source. The first
17
+ # and only argument is the data source name as a symbol.
18
+ identifier :trivial
19
+
20
+ ########## Preparation ##########
21
+
22
+ # DataSource#up is run before compiling. This is the place where you
23
+ # should initialize the data source, if necessary. You don't need to
24
+ # implement it; you can leave it out if you don't need initialization.
25
+ # This is the ideal place to connect to the database, for example.
26
+ # If your data source requires any special libraries, require them here
27
+ # using 'nanoc_require'.
28
+ def up
29
+ end
30
+
31
+ # DataSource#down is run after compiling. This is where you should clean
32
+ # up any resources you used during the site compilation. You don't need to
33
+ # implement it; you can leave it out if there's nothing to clean up. For
34
+ # example, this is a good place to close the connection to the database,
35
+ # if you have one.
36
+ def down
37
+ end
38
+
39
+ # DataSource#setup is run when the site is created. This is the place
40
+ # where you should create the data source for the first time. You don't
41
+ # need to implement it; you can leave it out if there's nothing to set up.
42
+ # For example, if you're using a database, this is where you should create
43
+ # the necessary tables for the data source to function properly.
44
+ def setup
45
+ error "Sorry. The trivial data source isn't competent enough."
46
+ end
47
+
48
+ ########## Loading data ##########
49
+
50
+ # DataSource#pages returns an array of hashes that represent pages. Each
51
+ # hash must have at least the :uncompiled_content and :path keys. You can
52
+ # include other metadata in this hash, though.
53
+ def pages
54
+ [
55
+ { :uncompiled_content => 'Hi!', :path => '/' },
56
+ { :uncompiled_content => 'Hello there.', :path => '/about/' }
57
+ ]
58
+ end
59
+
60
+ # Datasource#page_defaults returns a hash with default values for page
61
+ # metadata. This hash can be anything, even an empty hash if you wish.
62
+ def page_defaults
63
+ { :layout => 'quux' }
64
+ end
65
+
66
+ # DataSource#layouts returns an array of hashes that represent layouts.
67
+ # Each hash must have the :name, :content and :extension keys. The
68
+ # :extension key determines the layout processor that will be used (they
69
+ # are defined in layout_processors/*.rb).
70
+ def layouts
71
+ [
72
+ {
73
+ :name => 'quux',
74
+ :content => "<html>\n" +
75
+ " <head>\n" +
76
+ " <title><%= @page.title %></title>\n" +
77
+ " </head>\n" +
78
+ " <body>\n" +
79
+ "<%= @page.content %>\n" +
80
+ " </body>\n" +
81
+ "</html>",
82
+ :extension => '.erb'
83
+ }
84
+ ]
85
+ end
86
+
87
+ # DataSource#templates returns an array of hashes that represent page
88
+ # templates. These page templates are used used by DataSource#create_page
89
+ # to create pages using a template. Each hash must have the :name key for
90
+ # identifying the template. Apart from that, you can structure the hash
91
+ # like you desire. I recommend having :content (for the page content) and
92
+ # :meta (for the page metadata) keys. Note that in this example, the value
93
+ # corresponding to the :meta key is a hash, but it could just as well have
94
+ # been a YAML-formatted string. Just make sure that what
95
+ # DataSource#templates serves is what DataSource#create_page expects.
96
+ def templates
97
+ [
98
+ {
99
+ :name => 'default',
100
+ :content => 'Hi, I am a new page. Please edit me!',
101
+ :meta => { :title => 'A New Page' }
102
+ }
103
+ ]
104
+ end
105
+
106
+ # DataSource#code returns a string containing custom code which will be
107
+ # loaded before the site is compiled. This can be code for custom filters
108
+ # and layout processors, but pretty much any code can be put in there
109
+ # (global helper functions are very useful). It is possible to override
110
+ # methods of built-in nanoc classes, but doing so will likely cause
111
+ # massive breakage, so doing so is not recommended.
112
+ def code
113
+ "def foo ; 'bar' ; end"
114
+ end
115
+
116
+ ########## Creating data ##########
117
+
118
+ # DataSource#create_page is run when a page is created. This function
119
+ # should create a new page with the given name and using the given
120
+ # template. The template is a hash taken the array of hashes returned by
121
+ # DataSource#templates, so make sure that what DataSource#templates
122
+ # returns is what DataSource#create_page expects. This trivial data source
123
+ # doesn't have a permanent storage, so it can't create any pages.
124
+ def create_page(path, template)
125
+ error "Sorry. The trivial data source isn't competent enough."
126
+ end
127
+
128
+ # DataSource#create_layout is run when a layout is created. This function
129
+ # should create a new layout with the given name. This trivial data source
130
+ # doesn't have a permanent storage, so it can't create any layouts.
131
+ def create_layout(name)
132
+ error "Sorry. The trivial data source isn't competent enough."
133
+ end
134
+
135
+ # DataSource#create_template is run when a template is created. This
136
+ # function should create a new template with the given name. This trivial
137
+ # data source doesn't have a permanent storage, so it can't create any
138
+ # templates.
139
+ def create_template(name)
140
+ error "Sorry. The trivial data source isn't competent enough."
141
+ end
142
+
143
+ end
144
+
145
+ end