nanoc3 3.0.9 → 3.1.0a1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +1 -1
- data/NEWS.md +360 -0
- data/README.md +85 -0
- data/Rakefile +2 -2
- data/bin/nanoc3 +0 -4
- data/lib/nanoc3/base/code_snippet.rb +14 -6
- data/lib/nanoc3/base/compiler.rb +68 -49
- data/lib/nanoc3/base/compiler_dsl.rb +70 -29
- data/lib/nanoc3/base/context.rb +47 -0
- data/lib/nanoc3/base/core_ext/array.rb +4 -0
- data/lib/nanoc3/base/core_ext/hash.rb +5 -1
- data/lib/nanoc3/base/core_ext/string.rb +2 -0
- data/lib/nanoc3/base/data_source.rb +132 -96
- data/lib/nanoc3/base/dependency_tracker.rb +160 -185
- data/lib/nanoc3/base/directed_graph.rb +252 -0
- data/lib/nanoc3/base/errors.rb +52 -4
- data/lib/nanoc3/base/filter.rb +43 -28
- data/lib/nanoc3/base/item.rb +93 -25
- data/lib/nanoc3/base/item_rep.rb +166 -55
- data/lib/nanoc3/base/layout.rb +16 -13
- data/lib/nanoc3/base/notification_center.rb +28 -12
- data/lib/nanoc3/base/plugin_registry.rb +158 -0
- data/lib/nanoc3/base/rule.rb +27 -8
- data/lib/nanoc3/base/rule_context.rb +59 -46
- data/lib/nanoc3/base/site.rb +124 -77
- data/lib/nanoc3/base.rb +7 -2
- data/lib/nanoc3/cli/base.rb +4 -1
- data/lib/nanoc3/cli/commands/autocompile.rb +5 -4
- data/lib/nanoc3/cli/commands/compile.rb +28 -7
- data/lib/nanoc3/cli/commands/create_item.rb +1 -1
- data/lib/nanoc3/cli/commands/create_layout.rb +1 -1
- data/lib/nanoc3/cli/commands/create_site.rb +46 -22
- data/lib/nanoc3/cli/commands/debug.rb +100 -0
- data/lib/nanoc3/cli/commands/help.rb +1 -1
- data/lib/nanoc3/cli/commands/info.rb +1 -1
- data/lib/nanoc3/cli/commands/view.rb +85 -0
- data/lib/nanoc3/cli/commands.rb +2 -0
- data/lib/nanoc3/cli/logger.rb +7 -0
- data/lib/nanoc3/cli.rb +0 -3
- data/lib/nanoc3/data_sources/{delicious.rb → deprecated/delicious.rb} +1 -24
- data/lib/nanoc3/data_sources/{last_fm.rb → deprecated/last_fm.rb} +1 -27
- data/lib/nanoc3/data_sources/{twitter.rb → deprecated/twitter.rb} +1 -14
- data/lib/nanoc3/data_sources/filesystem.rb +188 -176
- data/lib/nanoc3/data_sources/filesystem_unified.rb +107 -0
- data/lib/nanoc3/data_sources/filesystem_verbose.rb +80 -0
- data/lib/nanoc3/data_sources.rb +18 -9
- data/lib/nanoc3/extra/core_ext/enumerable.rb +39 -0
- data/lib/nanoc3/extra/core_ext/time.rb +2 -2
- data/lib/nanoc3/extra/core_ext.rb +1 -0
- data/lib/nanoc3/extra/deployers/rsync.rb +49 -3
- data/lib/nanoc3/extra/file_proxy.rb +7 -0
- data/lib/nanoc3/extra/vcs.rb +25 -24
- data/lib/nanoc3/extra/vcses/bazaar.rb +4 -0
- data/lib/nanoc3/extra/vcses/dummy.rb +4 -0
- data/lib/nanoc3/extra/vcses/git.rb +4 -0
- data/lib/nanoc3/extra/vcses/mercurial.rb +4 -0
- data/lib/nanoc3/extra/vcses/subversion.rb +4 -0
- data/lib/nanoc3/extra.rb +4 -1
- data/lib/nanoc3/filters/erb.rb +1 -1
- data/lib/nanoc3/filters/erubis.rb +1 -1
- data/lib/nanoc3/filters/haml.rb +1 -1
- data/lib/nanoc3/filters/kramdown.rb +14 -0
- data/lib/nanoc3/filters/maruku.rb +1 -1
- data/lib/nanoc3/filters/rainpress.rb +1 -1
- data/lib/nanoc3/filters/rdiscount.rb +3 -1
- data/lib/nanoc3/filters.rb +2 -0
- data/lib/nanoc3/helpers/blogging.rb +91 -75
- data/lib/nanoc3/helpers/breadcrumbs.rb +18 -10
- data/lib/nanoc3/helpers/capturing.rb +24 -29
- data/lib/nanoc3/helpers/filtering.rb +20 -17
- data/lib/nanoc3/helpers/html_escape.rb +7 -4
- data/lib/nanoc3/helpers/link_to.rb +51 -41
- data/lib/nanoc3/helpers/rendering.rb +15 -8
- data/lib/nanoc3/helpers/tagging.rb +27 -21
- data/lib/nanoc3/helpers/text.rb +12 -8
- data/lib/nanoc3/helpers/xml_sitemap.rb +13 -15
- data/lib/nanoc3/tasks/deploy/rsync.rake +4 -1
- data/lib/nanoc3/tasks.rb +2 -1
- data/lib/nanoc3.rb +24 -1
- metadata +43 -87
- data/NEWS.rdoc +0 -328
- data/README.rdoc +0 -83
- data/lib/nanoc3/base/plugin.rb +0 -88
- data/lib/nanoc3/base/preprocessor_context.rb +0 -37
- data/lib/nanoc3/data_sources/filesystem_combined.rb +0 -214
- data/lib/nanoc3/data_sources/filesystem_common.rb +0 -22
- data/lib/nanoc3/data_sources/filesystem_compact.rb +0 -256
- data/lib/nanoc3/extra/context.rb +0 -24
- data/lib/nanoc3/package.rb +0 -107
- data/vendor/cri/ChangeLog +0 -0
- data/vendor/cri/LICENSE +0 -19
- data/vendor/cri/NEWS +0 -0
- data/vendor/cri/README +0 -4
- data/vendor/cri/Rakefile +0 -25
- data/vendor/cri/lib/cri/base.rb +0 -153
- data/vendor/cri/lib/cri/command.rb +0 -105
- data/vendor/cri/lib/cri/core_ext/string.rb +0 -41
- data/vendor/cri/lib/cri/core_ext.rb +0 -8
- data/vendor/cri/lib/cri/option_parser.rb +0 -186
- data/vendor/cri/lib/cri.rb +0 -12
- data/vendor/cri/test/test_base.rb +0 -6
- data/vendor/cri/test/test_command.rb +0 -6
- data/vendor/cri/test/test_core_ext.rb +0 -21
- data/vendor/cri/test/test_option_parser.rb +0 -279
@@ -2,77 +2,28 @@
|
|
2
2
|
|
3
3
|
module Nanoc3::DataSources
|
4
4
|
|
5
|
-
#
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
# The filesystem data source stores its items in nested directories. Each
|
14
|
-
# directory represents a single item. The root directory is the 'content'
|
15
|
-
# directory.
|
16
|
-
#
|
17
|
-
# Every directory has a content file and a meta file. The content file
|
18
|
-
# contains the actual item content, while the meta file contains the item's
|
19
|
-
# metadata, formatted as YAML.
|
20
|
-
#
|
21
|
-
# Both content files and meta files are named after its parent directory
|
22
|
-
# (i.e. item). For example, a item named 'foo' will have a directory named
|
23
|
-
# 'foo', with e.g. a 'foo.markdown' content file and a 'foo.yaml' meta file.
|
24
|
-
#
|
25
|
-
# Content file extensions are not used for determining the filter that
|
26
|
-
# should be run; the meta file defines the list of filters. The meta file
|
27
|
-
# extension must always be 'yaml', though.
|
28
|
-
#
|
29
|
-
# Content files can also have the 'index' basename. Similarly, meta files
|
30
|
-
# can have the 'meta' basename. For example, a parent directory named 'foo'
|
31
|
-
# can have an 'index.txt' content file and a 'meta.yaml' meta file. This is
|
32
|
-
# to preserve backward compatibility.
|
33
|
-
#
|
34
|
-
# The identifier is calculated by stripping the extension; if there is more
|
35
|
-
# than one extension, only the last extension is stripped and the previous
|
36
|
-
# extensions will be part of the identifier.
|
37
|
-
#
|
38
|
-
# = Layouts
|
39
|
-
#
|
40
|
-
# Layouts are stored as directories in the 'layouts' directory. Each layout
|
41
|
-
# contains a content file and a meta file. The content file contain the
|
42
|
-
# actual layout, and the meta file describes how the item should be handled
|
43
|
-
# (contains the filter that should be used).
|
44
|
-
#
|
45
|
-
# For backward compatibility, a layout can also be a single file in the
|
46
|
-
# 'layouts' directory. Such a layout cannot have any metadata; the filter
|
47
|
-
# used for this layout is determined from the file extension.
|
48
|
-
#
|
49
|
-
# The identifier for layouts is generated the same way as identifiers for
|
50
|
-
# items (see above for details).
|
51
|
-
#
|
52
|
-
# = Code Snippets
|
53
|
-
#
|
54
|
-
# Code snippets are stored in '.rb' files in the 'lib' directory. Code
|
55
|
-
# snippets can reside in sub-directories.
|
56
|
-
class Filesystem < Nanoc3::DataSource
|
57
|
-
|
58
|
-
include Nanoc3::DataSources::FilesystemCommon
|
59
|
-
|
60
|
-
########## VCSes ##########
|
61
|
-
|
62
|
-
attr_accessor :vcs
|
63
|
-
|
5
|
+
# Provides functionality common across all filesystem data sources.
|
6
|
+
module Filesystem
|
7
|
+
|
8
|
+
# The VCS that will be called when adding, deleting and moving files. If
|
9
|
+
# no VCS has been set, or if the VCS has been set to `nil`, a dummy VCS
|
10
|
+
# will be returned.
|
11
|
+
#
|
12
|
+
# @return [Nanoc3::Extra::VCS, nil] The VCS that will be used.
|
64
13
|
def vcs
|
65
14
|
@vcs ||= Nanoc3::Extra::VCSes::Dummy.new
|
66
15
|
end
|
16
|
+
attr_writer :vcs
|
67
17
|
|
68
|
-
|
69
|
-
|
18
|
+
# See {Nanoc3::DataSource#up}.
|
70
19
|
def up
|
71
20
|
end
|
72
21
|
|
22
|
+
# See {Nanoc3::DataSource#down}.
|
73
23
|
def down
|
74
24
|
end
|
75
25
|
|
26
|
+
# See {Nanoc3::DataSource#setup}.
|
76
27
|
def setup
|
77
28
|
# Create directories
|
78
29
|
%w( content layouts lib ).each do |dir|
|
@@ -81,160 +32,221 @@ module Nanoc3::DataSources
|
|
81
32
|
end
|
82
33
|
end
|
83
34
|
|
84
|
-
|
85
|
-
|
35
|
+
# See {Nanoc3::DataSource#items}.
|
86
36
|
def items
|
87
|
-
|
88
|
-
|
89
|
-
meta = YAML.load_file(meta_filename) || {}
|
37
|
+
load_objects('content', 'item', Nanoc3::Item)
|
38
|
+
end
|
90
39
|
|
91
|
-
|
92
|
-
|
93
|
-
|
40
|
+
# See {Nanoc3::DataSource#layouts}.
|
41
|
+
def layouts
|
42
|
+
load_objects('layouts', 'layout', Nanoc3::Layout)
|
43
|
+
end
|
94
44
|
|
95
|
-
|
96
|
-
|
45
|
+
# See {Nanoc3::DataSource#create_item}.
|
46
|
+
def create_item(content, attributes, identifier, params={})
|
47
|
+
create_object('content', content, attributes, identifier, params)
|
48
|
+
end
|
97
49
|
|
98
|
-
|
99
|
-
|
50
|
+
# See {Nanoc3::DataSource#create_layout}.
|
51
|
+
def create_layout(content, attributes, identifier, params={})
|
52
|
+
create_object('layouts', content, attributes, identifier, params)
|
53
|
+
end
|
100
54
|
|
101
|
-
|
102
|
-
meta_mtime = File.stat(meta_filename).mtime
|
103
|
-
content_mtime = File.stat(content_filename).mtime
|
104
|
-
mtime = meta_mtime > content_mtime ? meta_mtime : content_mtime
|
55
|
+
private
|
105
56
|
|
106
|
-
|
107
|
-
|
108
|
-
|
57
|
+
# Creates a new object (item or layout) on disk in dir_name according to
|
58
|
+
# the given identifier. The file will have its attributes taken from the
|
59
|
+
# attributes hash argument and its content from the content argument.
|
60
|
+
def create_object(dir_name, content, attributes, identifier, params={})
|
61
|
+
raise NotImplementedError.new(
|
62
|
+
"#{self.class} does not implement ##{name}"
|
63
|
+
)
|
109
64
|
end
|
110
65
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
66
|
+
# Creates instances of klass corresponding to the files in dir_name. The
|
67
|
+
# kind attribute indicates the kind of object that is being loaded and is
|
68
|
+
# used solely for debugging purposes.
|
69
|
+
#
|
70
|
+
# This particular implementation loads objects from a filesystem-based
|
71
|
+
# data source where content and attributes can be spread over two separate
|
72
|
+
# files. The content and meta-file are optional (but at least one of them
|
73
|
+
# needs to be present, obviously) and the content file can start with a
|
74
|
+
# metadata section.
|
75
|
+
#
|
76
|
+
# @see Nanoc3::DataSources::Filesystem#load_objects
|
77
|
+
def load_objects(dir_name, kind, klass)
|
78
|
+
all_split_files_in(dir_name).map do |base_filename, (meta_ext, content_ext)|
|
79
|
+
# Get filenames
|
80
|
+
meta_filename = filename_for(base_filename, meta_ext)
|
81
|
+
content_filename = filename_for(base_filename, content_ext)
|
82
|
+
|
83
|
+
# Read content and metadata
|
84
|
+
meta, content = parse(content_filename, meta_filename, kind)
|
116
85
|
|
117
86
|
# Get attributes
|
118
|
-
attributes =
|
87
|
+
attributes = {
|
88
|
+
:filename => content_filename,
|
89
|
+
:content_filename => content_filename,
|
90
|
+
:meta_filename => meta_filename,
|
91
|
+
:extension => content_filename ? ext_of(content_filename)[1..-1] : nil,
|
92
|
+
# WARNING :file is deprecated; please create a File object manually
|
93
|
+
# using the :content_filename or :meta_filename attributes.
|
94
|
+
# TODO [in nanoc 4.0] remove me
|
95
|
+
:file => content_filename ? Nanoc3::Extra::FileProxy.new(content_filename) : nil
|
96
|
+
}.merge(meta)
|
119
97
|
|
120
98
|
# Get identifier
|
121
|
-
|
99
|
+
if meta_filename
|
100
|
+
identifier = identifier_for_filename(meta_filename[(dir_name.length+1)..-1])
|
101
|
+
elsif content_filename
|
102
|
+
identifier = identifier_for_filename(content_filename[(dir_name.length+1)..-1])
|
103
|
+
else
|
104
|
+
raise RuntimeError, "meta_filename and content_filename are both nil"
|
105
|
+
end
|
122
106
|
|
123
107
|
# Get modification times
|
124
|
-
meta_mtime = File.stat(meta_filename).mtime
|
125
|
-
content_mtime = File.stat(content_filename).mtime
|
126
|
-
|
108
|
+
meta_mtime = meta_filename ? File.stat(meta_filename).mtime : nil
|
109
|
+
content_mtime = content_filename ? File.stat(content_filename).mtime : nil
|
110
|
+
if meta_mtime && content_mtime
|
111
|
+
mtime = meta_mtime > content_mtime ? meta_mtime : content_mtime
|
112
|
+
elsif meta_mtime
|
113
|
+
mtime = meta_mtime
|
114
|
+
elsif content_mtime
|
115
|
+
mtime = content_mtime
|
116
|
+
else
|
117
|
+
raise RuntimeError, "meta_mtime and content_mtime are both nil"
|
118
|
+
end
|
127
119
|
|
128
120
|
# Create layout object
|
129
|
-
|
121
|
+
klass.new(content, attributes, identifier, mtime)
|
130
122
|
end
|
131
123
|
end
|
132
124
|
|
133
|
-
|
134
|
-
|
135
|
-
#
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
125
|
+
# Finds all items/layouts/... in the given base directory. Returns a hash
|
126
|
+
# in which the keys are the file's dirname + basenames, and the values a
|
127
|
+
# pair consisting of the metafile extension and the content file
|
128
|
+
# extension. The meta file extension or the content file extension can be
|
129
|
+
# nil, but not both. Backup files are ignored. For example:
|
130
|
+
#
|
131
|
+
# {
|
132
|
+
# 'content/foo' => [ 'yaml', 'html' ],
|
133
|
+
# 'content/bar' => [ 'yaml', nil ],
|
134
|
+
# 'content/qux' => [ nil, 'html' ]
|
135
|
+
# }
|
136
|
+
def all_split_files_in(dir_name)
|
137
|
+
# Get all good file names
|
138
|
+
filenames = Dir[dir_name + '/**/*'].select { |i| File.file?(i) }
|
139
|
+
filenames.reject! { |fn| fn =~ /(~|\.orig|\.rej|\.bak)$/ }
|
140
|
+
|
141
|
+
# Group by identifier
|
142
|
+
grouped_filenames = filenames.group_by { |fn| basename_of(fn) }
|
143
|
+
|
144
|
+
# Convert values into metafile/content file extension tuple
|
145
|
+
grouped_filenames.each_pair do |key, filenames|
|
146
|
+
# Divide
|
147
|
+
meta_filenames = filenames.select { |fn| ext_of(fn) == '.yaml' }
|
148
|
+
content_filenames = filenames.select { |fn| ext_of(fn) != '.yaml' }
|
149
|
+
|
150
|
+
# Check number of files per type
|
151
|
+
if ![ 0, 1 ].include?(meta_filenames.size)
|
152
|
+
raise RuntimeError, "Found #{meta_filenames.size} meta files for #{key}; expected 0 or 1"
|
153
|
+
end
|
154
|
+
if ![ 0, 1 ].include?(content_filenames.size)
|
155
|
+
raise RuntimeError, "Found #{content_filenames.size} content files for #{key}; expected 0 or 1"
|
156
|
+
end
|
140
157
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
# Notify
|
147
|
-
Nanoc3::NotificationCenter.post(:file_created, meta_filename)
|
148
|
-
Nanoc3::NotificationCenter.post(:file_created, content_filename)
|
158
|
+
# Reorder elements and convert to extnames
|
159
|
+
filenames[0] = meta_filenames[0] ? ext_of(meta_filenames[0])[1..-1] : nil
|
160
|
+
filenames[1] = content_filenames[0] ? ext_of(content_filenames[0])[1..-1] : nil
|
161
|
+
end
|
149
162
|
|
150
|
-
#
|
151
|
-
|
152
|
-
File.open(meta_filename, 'w') { |io| io.write(YAML.dump(attributes.stringify_keys)) }
|
153
|
-
File.open(content_filename, 'w') { |io| io.write(content) }
|
163
|
+
# Done
|
164
|
+
grouped_filenames
|
154
165
|
end
|
155
166
|
|
156
|
-
#
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
# Create files
|
172
|
-
FileUtils.mkdir_p(dir_path)
|
173
|
-
File.open(meta_filename, 'w') { |io| io.write(YAML.dump(attributes.stringify_keys)) }
|
174
|
-
File.open(content_filename, 'w') { |io| io.write(content) }
|
167
|
+
# Returns the filename for the given base filename and the extension.
|
168
|
+
#
|
169
|
+
# If the extension is nil, this function should return nil as well.
|
170
|
+
#
|
171
|
+
# A simple implementation would simply concatenate the base filename, a
|
172
|
+
# period and an extension (which is what the
|
173
|
+
# {Nanoc3::DataSources::FilesystemCompact} data source does), but other
|
174
|
+
# data sources may prefer to implement this differently (for example,
|
175
|
+
# {Nanoc3::DataSources::FilesystemVerbose} doubles the last part of the
|
176
|
+
# basename before concatenating it with a period and the extension).
|
177
|
+
def filename_for(base_filename, ext)
|
178
|
+
raise NotImplementedError.new(
|
179
|
+
"#{self.class} does not implement #filename_for"
|
180
|
+
)
|
175
181
|
end
|
176
182
|
|
177
|
-
|
178
|
-
|
179
|
-
|
183
|
+
# Returns the identifier that corresponds with the given filename, which
|
184
|
+
# can be the content filename or the meta filename.
|
185
|
+
def identifier_for_filename(filename)
|
186
|
+
raise NotImplementedError.new(
|
187
|
+
"#{self.class} does not implement #identifier_for_filename"
|
188
|
+
)
|
189
|
+
end
|
180
190
|
|
181
|
-
# Returns the
|
182
|
-
#
|
183
|
-
|
184
|
-
|
185
|
-
|
191
|
+
# Returns the base name of filename, i.e. filename with the first or all
|
192
|
+
# extensions stripped off. By default, all extensions are stripped off,
|
193
|
+
# but when allow_periods_in_identifiers is set to true in the site
|
194
|
+
# configuration, only the last extension will be stripped .
|
195
|
+
def basename_of(filename)
|
196
|
+
filename.sub(extension_regex, '')
|
197
|
+
end
|
186
198
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
good_filenames << filename
|
193
|
-
else
|
194
|
-
bad_filenames << filename
|
195
|
-
end
|
196
|
-
end
|
199
|
+
# Returns the extension(s) of filename. Supports multiple extensions.
|
200
|
+
# Includes the leading period.
|
201
|
+
def ext_of(filename)
|
202
|
+
filename =~ extension_regex ? $1 : ''
|
203
|
+
end
|
197
204
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
+
# Returns a regex that is used for determining the extension of a file
|
206
|
+
# name. The first match group will be the entire extension, including the
|
207
|
+
# leading period.
|
208
|
+
def extension_regex
|
209
|
+
if @config && @config[:allow_periods_in_identifiers]
|
210
|
+
/(\.[^\/\.]+$)/
|
211
|
+
else
|
212
|
+
/(\.[^\/]+$)/
|
205
213
|
end
|
206
|
-
|
207
|
-
good_filenames
|
208
214
|
end
|
209
215
|
|
210
|
-
#
|
211
|
-
#
|
212
|
-
#
|
213
|
-
def
|
214
|
-
#
|
215
|
-
|
216
|
-
|
217
|
-
|
216
|
+
# Parses the file named `filename` and returns an array with its first
|
217
|
+
# element a hash with the file's metadata, and with its second element the
|
218
|
+
# file content itself.
|
219
|
+
def parse(content_filename, meta_filename, kind)
|
220
|
+
# Read content and metadata from separate files
|
221
|
+
if meta_filename
|
222
|
+
content = content_filename ? File.read(content_filename) : ''
|
223
|
+
meta = YAML.load_file(meta_filename) || {}
|
218
224
|
|
219
|
-
|
220
|
-
|
225
|
+
return [ meta, content ]
|
226
|
+
end
|
221
227
|
|
222
|
-
#
|
223
|
-
|
228
|
+
# Read data
|
229
|
+
data = File.read(content_filename)
|
224
230
|
|
225
|
-
#
|
226
|
-
if
|
231
|
+
# Check presence of metadata section
|
232
|
+
if data !~ /^(-{5}|-{3})/
|
233
|
+
return [ {}, data ]
|
234
|
+
end
|
235
|
+
|
236
|
+
# Split data
|
237
|
+
pieces = data.split(/^(-{5}|-{3})/)
|
238
|
+
if pieces.size < 4
|
227
239
|
raise RuntimeError.new(
|
228
|
-
"
|
240
|
+
"The file '#{content_filename}' does not seem to be a nanoc #{kind}"
|
229
241
|
)
|
230
242
|
end
|
231
243
|
|
232
|
-
#
|
233
|
-
|
234
|
-
|
244
|
+
# Parse
|
245
|
+
meta = YAML.load(pieces[2]) || {}
|
246
|
+
content = pieces[4..-1].join.strip
|
235
247
|
|
236
|
-
|
237
|
-
|
248
|
+
# Done
|
249
|
+
[ meta, content ]
|
238
250
|
end
|
239
251
|
|
240
252
|
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Nanoc3::DataSources
|
4
|
+
|
5
|
+
# The filesystem_unified data source stores its items and layouts in nested
|
6
|
+
# directories. Items and layouts are represented by one or two files; if it
|
7
|
+
# is represented using one file, the metadata can be contained in this file.
|
8
|
+
# The root directory for items is the `content` directory; for layouts, this
|
9
|
+
# is the `layouts` directory.
|
10
|
+
#
|
11
|
+
# The metadata for items and layouts can be stored in a separate file with
|
12
|
+
# the same base name but with the `.yaml` extension. If such a file is
|
13
|
+
# found, metadata is read from that file. Alternatively, the content file
|
14
|
+
# itself can start with a metadata section: it can be stored at the top of
|
15
|
+
# the file, between `---` (three dashes) separators. For example:
|
16
|
+
#
|
17
|
+
# ---
|
18
|
+
# title: "Moo!"
|
19
|
+
# ---
|
20
|
+
# h1. Hello!
|
21
|
+
#
|
22
|
+
# The metadata section can be omitted. If the file does not start with
|
23
|
+
# three or five dashes, the entire file will be considered as content.
|
24
|
+
#
|
25
|
+
# The identifier of items and layouts is determined as follows. A file with
|
26
|
+
# an `index.*` filename, such as `index.txt`, will have the filesystem path
|
27
|
+
# with the `index.*` part stripped as a identifier. For example:
|
28
|
+
#
|
29
|
+
# foo/bar/index.html → /foo/bar/
|
30
|
+
#
|
31
|
+
# In other cases, the identifier is calculated by stripping the extension.
|
32
|
+
# If the `allow_periods_in_identifiers` attribute in the configuration is
|
33
|
+
# true, only the last extension will be stripped if the file has multiple
|
34
|
+
# extensions; if it is false or unset, all extensions will be stripped.
|
35
|
+
# For example:
|
36
|
+
#
|
37
|
+
# (`allow_periods_in_identifiers` set to true)
|
38
|
+
# foo.entry.html → /foo.entry/
|
39
|
+
#
|
40
|
+
# (`allow_periods_in_identifiers` set to false)
|
41
|
+
# foo.html.erb → /foo/
|
42
|
+
#
|
43
|
+
# Note that it is possible for two different, separate files to have the
|
44
|
+
# same identifier. It is recommended to avoid such situations.
|
45
|
+
#
|
46
|
+
# Some more examples:
|
47
|
+
#
|
48
|
+
# content/index.html → /
|
49
|
+
# content/foo.html → /foo/
|
50
|
+
# content/foo/index.html → /foo/
|
51
|
+
# content/foo/bar.html → /foo/bar/
|
52
|
+
# content/foo/bar.baz.html → /foo/bar/ OR /foo/bar.baz/
|
53
|
+
# content/foo/bar/index.html → /foo/bar/
|
54
|
+
# content/foo.bar/index.html → /foo.bar/
|
55
|
+
#
|
56
|
+
# The file extension does not determine the filters to run on items; the
|
57
|
+
# Rules file is used to specify processing instructors for each item.
|
58
|
+
class FilesystemUnified < Nanoc3::DataSource
|
59
|
+
|
60
|
+
include Nanoc3::DataSources::Filesystem
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# See {Nanoc3::DataSources::Filesystem#create_object}.
|
65
|
+
def create_object(dir_name, content, attributes, identifier, params={})
|
66
|
+
# Check for periods
|
67
|
+
if (@config.nil? || !@config[:allow_periods_in_identifiers]) && identifier.include?('.')
|
68
|
+
raise RuntimeError,
|
69
|
+
"Attempted to create an object in #{dir_name} with identifier #{identifier} containing a period, but allow_periods_in_identifiers is not enabled in the site configuration. (Enabling allow_periods_in_identifiers may cause the site to break, though.)"
|
70
|
+
end
|
71
|
+
|
72
|
+
# Determine path
|
73
|
+
ext = params[:extension] || '.html'
|
74
|
+
path = dir_name + (identifier == '/' ? '/index.html' : identifier[0..-2] + ext)
|
75
|
+
parent_path = File.dirname(path)
|
76
|
+
|
77
|
+
# Notify
|
78
|
+
Nanoc3::NotificationCenter.post(:file_created, path)
|
79
|
+
|
80
|
+
# Write item
|
81
|
+
FileUtils.mkdir_p(parent_path)
|
82
|
+
File.open(path, 'w') do |io|
|
83
|
+
io.write(YAML.dump(attributes.stringify_keys) + "\n")
|
84
|
+
io.write("---\n")
|
85
|
+
io.write(content)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# See {Nanoc3::DataSources::Filesystem#filename_for}.
|
90
|
+
def filename_for(base_filename, ext)
|
91
|
+
ext ? base_filename + '.' + ext : nil
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns the identifier derived from the given filename, first stripping
|
95
|
+
# the given directory name off the filename.
|
96
|
+
def identifier_for_filename(filename)
|
97
|
+
if filename =~ /index\.[^\/]+$/
|
98
|
+
regex = ((@config && @config[:allow_periods_in_identifiers]) ? /index\.[^\/\.]+$/ : /index\.[^\/]+$/)
|
99
|
+
else
|
100
|
+
regex = ((@config && @config[:allow_periods_in_identifiers]) ? /\.[^\/\.]+$/ : /\.[^\/]+$/)
|
101
|
+
end
|
102
|
+
filename.sub(regex, '').cleaned_identifier
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Nanoc3::DataSources
|
4
|
+
|
5
|
+
# The filesystem_verbose data source is the old data source for a new nanoc
|
6
|
+
# site. It stores all data as files on the hard disk.
|
7
|
+
#
|
8
|
+
# None of the methods are documented in this file. See {Nanoc3::DataSource}
|
9
|
+
# for documentation on the overridden methods instead.
|
10
|
+
#
|
11
|
+
# The filesystem_verbose data source stores its items and layouts in nested
|
12
|
+
# directories. Each directory represents a single item or layout. The root
|
13
|
+
# directory for items is the `content` directory; for layouts it is the
|
14
|
+
# `layouts` directory.
|
15
|
+
#
|
16
|
+
# Every directory has a content file and a meta file. The content file
|
17
|
+
# contains the actual item content, while the meta file contains the item’s
|
18
|
+
# or the layout’s metadata, formatted as YAML.
|
19
|
+
#
|
20
|
+
# Both content files and meta files are named after its parent directory
|
21
|
+
# (i.e. item). For example, an item/layout named `foo` will have a directory
|
22
|
+
# named `foo`, with e.g. a `foo.markdown` content file and a `foo.yaml` meta
|
23
|
+
# file.
|
24
|
+
#
|
25
|
+
# Content file extensions are not used for determining the filter that
|
26
|
+
# should be run; the meta file defines the list of filters. The meta file
|
27
|
+
# extension must always be `.yaml`, though.
|
28
|
+
#
|
29
|
+
# For backwards compatibility, content files can also have the `index`
|
30
|
+
# basename. Similarly, meta files can have the `meta` basename. For example,
|
31
|
+
# a parent directory named `foo` can have an `index.txt` content file and a
|
32
|
+
# `meta.yaml` meta file.
|
33
|
+
#
|
34
|
+
# The identifier is calculated by stripping the extension; if there is more
|
35
|
+
# than one extension, only the last extension is stripped and the previous
|
36
|
+
# extensions will be part of the identifier.
|
37
|
+
class FilesystemVerbose < Nanoc3::DataSource
|
38
|
+
|
39
|
+
include Nanoc3::DataSources::Filesystem
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# See {Nanoc3::DataSources::Filesystem#create_object}.
|
44
|
+
def create_object(dir_name, content, attributes, identifier, params={})
|
45
|
+
# Determine base path
|
46
|
+
last_component = identifier.split('/')[-1] || dir_name
|
47
|
+
base_path = dir_name + identifier + last_component
|
48
|
+
|
49
|
+
# Get filenames
|
50
|
+
ext = params[:extension] || '.html'
|
51
|
+
dir_path = dir_name + identifier
|
52
|
+
meta_filename = dir_name + identifier + last_component + '.yaml'
|
53
|
+
content_filename = dir_name + identifier + last_component + ext
|
54
|
+
|
55
|
+
# Notify
|
56
|
+
Nanoc3::NotificationCenter.post(:file_created, meta_filename)
|
57
|
+
Nanoc3::NotificationCenter.post(:file_created, content_filename)
|
58
|
+
|
59
|
+
# Create files
|
60
|
+
FileUtils.mkdir_p(dir_path)
|
61
|
+
File.open(meta_filename, 'w') { |io| io.write(YAML.dump(attributes.stringify_keys)) }
|
62
|
+
File.open(content_filename, 'w') { |io| io.write(content) }
|
63
|
+
end
|
64
|
+
|
65
|
+
# See {Nanoc3::DataSources::Filesystem#filename_for}.
|
66
|
+
def filename_for(base_filename, ext)
|
67
|
+
last_part = base_filename.split('/')[-1]
|
68
|
+
base_glob = base_filename.split('/')[0..-2].join('/') + "/{index,#{last_part}}."
|
69
|
+
|
70
|
+
ext ? Dir[base_glob + ext][0] : nil
|
71
|
+
end
|
72
|
+
|
73
|
+
# See {Nanoc3::DataSources::Filesystem#identifier_for_filename}.
|
74
|
+
def identifier_for_filename(filename)
|
75
|
+
filename.sub(/[^\/]+\.yaml$/, '')
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
data/lib/nanoc3/data_sources.rb
CHANGED
@@ -2,19 +2,28 @@
|
|
2
2
|
|
3
3
|
module Nanoc3::DataSources
|
4
4
|
|
5
|
-
autoload 'Delicious', 'nanoc3/data_sources/delicious'
|
6
5
|
autoload 'Filesystem', 'nanoc3/data_sources/filesystem'
|
7
|
-
autoload '
|
8
|
-
autoload '
|
9
|
-
autoload 'FilesystemCompact', 'nanoc3/data_sources/filesystem_compact'
|
10
|
-
autoload 'LastFM', 'nanoc3/data_sources/last_fm'
|
11
|
-
autoload 'Twitter', 'nanoc3/data_sources/twitter'
|
6
|
+
autoload 'FilesystemUnified', 'nanoc3/data_sources/filesystem_unified'
|
7
|
+
autoload 'FilesystemVerbose', 'nanoc3/data_sources/filesystem_verbose'
|
12
8
|
|
9
|
+
Nanoc3::DataSource.register '::Nanoc3::DataSources::FilesystemVerbose', :filesystem_verbose
|
10
|
+
Nanoc3::DataSource.register '::Nanoc3::DataSources::FilesystemUnified', :filesystem_unified
|
11
|
+
|
12
|
+
# Deprecated; fetch data from online data sources manually instead
|
13
|
+
# TODO [in nanoc 4.0] remove me
|
14
|
+
autoload 'Delicious', 'nanoc3/data_sources/deprecated/delicious'
|
15
|
+
autoload 'LastFM', 'nanoc3/data_sources/deprecated/last_fm'
|
16
|
+
autoload 'Twitter', 'nanoc3/data_sources/deprecated/twitter'
|
13
17
|
Nanoc3::DataSource.register '::Nanoc3::DataSources::Delicious', :delicious
|
14
|
-
Nanoc3::DataSource.register '::Nanoc3::DataSources::Filesystem', :filesystem
|
15
|
-
Nanoc3::DataSource.register '::Nanoc3::DataSources::FilesystemCombined', :filesystem_combined
|
16
|
-
Nanoc3::DataSource.register '::Nanoc3::DataSources::FilesystemCompact', :filesystem_compact
|
17
18
|
Nanoc3::DataSource.register '::Nanoc3::DataSources::LastFM', :last_fm
|
18
19
|
Nanoc3::DataSource.register '::Nanoc3::DataSources::Twitter', :twitter
|
19
20
|
|
21
|
+
# Deprecated; use `filesystem_verbose` or `filesystem_unified` instead
|
22
|
+
# TODO [in nanoc 4.0] remove me
|
23
|
+
Nanoc3::DataSource.register '::Nanoc3::DataSources::FilesystemVerbose', :filesystem
|
24
|
+
Nanoc3::DataSource.register '::Nanoc3::DataSources::FilesystemUnified', :filesystem_combined
|
25
|
+
Nanoc3::DataSource.register '::Nanoc3::DataSources::FilesystemUnified', :filesystem_compact
|
26
|
+
FilesystemCombined = FilesystemUnified
|
27
|
+
FilesystemCompact = FilesystemUnified
|
28
|
+
|
20
29
|
end
|