nanoc 1.6.2 → 2.0

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 (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