nanoc 1.6.2 → 2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +27 -0
- data/Rakefile +34 -27
- data/bin/nanoc +153 -49
- data/lib/nanoc.rb +15 -33
- data/lib/nanoc/base/auto_compiler.rb +124 -0
- data/lib/nanoc/base/compiler.rb +55 -0
- data/lib/nanoc/base/core_ext/hash.rb +34 -0
- data/lib/nanoc/base/data_source.rb +53 -0
- data/lib/nanoc/base/enhancements.rb +89 -0
- data/lib/nanoc/base/filter.rb +16 -0
- data/lib/nanoc/base/layout_processor.rb +33 -0
- data/lib/nanoc/base/page.rb +155 -0
- data/lib/nanoc/base/page_proxy.rb +31 -0
- data/lib/nanoc/base/plugin.rb +19 -0
- data/lib/nanoc/base/plugin_manager.rb +33 -0
- data/lib/nanoc/base/site.rb +143 -0
- data/lib/nanoc/data_sources/database.rb +259 -0
- data/lib/nanoc/data_sources/filesystem.rb +308 -0
- data/lib/nanoc/data_sources/trivial.rb +145 -0
- data/lib/nanoc/filters/erb.rb +34 -0
- data/lib/nanoc/filters/haml.rb +16 -0
- data/lib/nanoc/filters/markaby.rb +15 -0
- data/lib/nanoc/filters/markdown.rb +13 -0
- data/lib/nanoc/filters/rdoc.rb +14 -0
- data/lib/nanoc/filters/smartypants.rb +13 -0
- data/lib/nanoc/filters/textile.rb +13 -0
- data/lib/nanoc/layout_processors/erb.rb +35 -0
- data/lib/nanoc/layout_processors/haml.rb +18 -0
- data/lib/nanoc/layout_processors/markaby.rb +16 -0
- metadata +37 -30
- data/lib/nanoc/compiler.rb +0 -145
- data/lib/nanoc/core_ext.rb +0 -1
- data/lib/nanoc/core_ext/array.rb +0 -17
- data/lib/nanoc/core_ext/hash.rb +0 -43
- data/lib/nanoc/core_ext/string.rb +0 -13
- data/lib/nanoc/core_ext/yaml.rb +0 -10
- data/lib/nanoc/creator.rb +0 -180
- data/lib/nanoc/enhancements.rb +0 -101
- data/lib/nanoc/filters.rb +0 -7
- data/lib/nanoc/filters/eruby_filter.rb +0 -39
- data/lib/nanoc/filters/haml_filter.rb +0 -18
- data/lib/nanoc/filters/liquid_filter.rb +0 -47
- data/lib/nanoc/filters/markaby_filter.rb +0 -15
- data/lib/nanoc/filters/markdown_filter.rb +0 -13
- data/lib/nanoc/filters/rdoc_filter.rb +0 -15
- data/lib/nanoc/filters/sass_filter.rb +0 -13
- data/lib/nanoc/filters/smartypants_filter.rb +0 -13
- data/lib/nanoc/filters/textile_filter.rb +0 -13
- data/lib/nanoc/page.rb +0 -171
- data/lib/nanoc/page_drop.rb +0 -18
- 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('&', '&').str('<', '<').str('>', '>').str('\"', '"')\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
|