nanoc3 3.0.9 → 3.1.0a1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. data/LICENSE +1 -1
  2. data/NEWS.md +360 -0
  3. data/README.md +85 -0
  4. data/Rakefile +2 -2
  5. data/bin/nanoc3 +0 -4
  6. data/lib/nanoc3/base/code_snippet.rb +14 -6
  7. data/lib/nanoc3/base/compiler.rb +68 -49
  8. data/lib/nanoc3/base/compiler_dsl.rb +70 -29
  9. data/lib/nanoc3/base/context.rb +47 -0
  10. data/lib/nanoc3/base/core_ext/array.rb +4 -0
  11. data/lib/nanoc3/base/core_ext/hash.rb +5 -1
  12. data/lib/nanoc3/base/core_ext/string.rb +2 -0
  13. data/lib/nanoc3/base/data_source.rb +132 -96
  14. data/lib/nanoc3/base/dependency_tracker.rb +160 -185
  15. data/lib/nanoc3/base/directed_graph.rb +252 -0
  16. data/lib/nanoc3/base/errors.rb +52 -4
  17. data/lib/nanoc3/base/filter.rb +43 -28
  18. data/lib/nanoc3/base/item.rb +93 -25
  19. data/lib/nanoc3/base/item_rep.rb +166 -55
  20. data/lib/nanoc3/base/layout.rb +16 -13
  21. data/lib/nanoc3/base/notification_center.rb +28 -12
  22. data/lib/nanoc3/base/plugin_registry.rb +158 -0
  23. data/lib/nanoc3/base/rule.rb +27 -8
  24. data/lib/nanoc3/base/rule_context.rb +59 -46
  25. data/lib/nanoc3/base/site.rb +124 -77
  26. data/lib/nanoc3/base.rb +7 -2
  27. data/lib/nanoc3/cli/base.rb +4 -1
  28. data/lib/nanoc3/cli/commands/autocompile.rb +5 -4
  29. data/lib/nanoc3/cli/commands/compile.rb +28 -7
  30. data/lib/nanoc3/cli/commands/create_item.rb +1 -1
  31. data/lib/nanoc3/cli/commands/create_layout.rb +1 -1
  32. data/lib/nanoc3/cli/commands/create_site.rb +46 -22
  33. data/lib/nanoc3/cli/commands/debug.rb +100 -0
  34. data/lib/nanoc3/cli/commands/help.rb +1 -1
  35. data/lib/nanoc3/cli/commands/info.rb +1 -1
  36. data/lib/nanoc3/cli/commands/view.rb +85 -0
  37. data/lib/nanoc3/cli/commands.rb +2 -0
  38. data/lib/nanoc3/cli/logger.rb +7 -0
  39. data/lib/nanoc3/cli.rb +0 -3
  40. data/lib/nanoc3/data_sources/{delicious.rb → deprecated/delicious.rb} +1 -24
  41. data/lib/nanoc3/data_sources/{last_fm.rb → deprecated/last_fm.rb} +1 -27
  42. data/lib/nanoc3/data_sources/{twitter.rb → deprecated/twitter.rb} +1 -14
  43. data/lib/nanoc3/data_sources/filesystem.rb +188 -176
  44. data/lib/nanoc3/data_sources/filesystem_unified.rb +107 -0
  45. data/lib/nanoc3/data_sources/filesystem_verbose.rb +80 -0
  46. data/lib/nanoc3/data_sources.rb +18 -9
  47. data/lib/nanoc3/extra/core_ext/enumerable.rb +39 -0
  48. data/lib/nanoc3/extra/core_ext/time.rb +2 -2
  49. data/lib/nanoc3/extra/core_ext.rb +1 -0
  50. data/lib/nanoc3/extra/deployers/rsync.rb +49 -3
  51. data/lib/nanoc3/extra/file_proxy.rb +7 -0
  52. data/lib/nanoc3/extra/vcs.rb +25 -24
  53. data/lib/nanoc3/extra/vcses/bazaar.rb +4 -0
  54. data/lib/nanoc3/extra/vcses/dummy.rb +4 -0
  55. data/lib/nanoc3/extra/vcses/git.rb +4 -0
  56. data/lib/nanoc3/extra/vcses/mercurial.rb +4 -0
  57. data/lib/nanoc3/extra/vcses/subversion.rb +4 -0
  58. data/lib/nanoc3/extra.rb +4 -1
  59. data/lib/nanoc3/filters/erb.rb +1 -1
  60. data/lib/nanoc3/filters/erubis.rb +1 -1
  61. data/lib/nanoc3/filters/haml.rb +1 -1
  62. data/lib/nanoc3/filters/kramdown.rb +14 -0
  63. data/lib/nanoc3/filters/maruku.rb +1 -1
  64. data/lib/nanoc3/filters/rainpress.rb +1 -1
  65. data/lib/nanoc3/filters/rdiscount.rb +3 -1
  66. data/lib/nanoc3/filters.rb +2 -0
  67. data/lib/nanoc3/helpers/blogging.rb +91 -75
  68. data/lib/nanoc3/helpers/breadcrumbs.rb +18 -10
  69. data/lib/nanoc3/helpers/capturing.rb +24 -29
  70. data/lib/nanoc3/helpers/filtering.rb +20 -17
  71. data/lib/nanoc3/helpers/html_escape.rb +7 -4
  72. data/lib/nanoc3/helpers/link_to.rb +51 -41
  73. data/lib/nanoc3/helpers/rendering.rb +15 -8
  74. data/lib/nanoc3/helpers/tagging.rb +27 -21
  75. data/lib/nanoc3/helpers/text.rb +12 -8
  76. data/lib/nanoc3/helpers/xml_sitemap.rb +13 -15
  77. data/lib/nanoc3/tasks/deploy/rsync.rake +4 -1
  78. data/lib/nanoc3/tasks.rb +2 -1
  79. data/lib/nanoc3.rb +24 -1
  80. metadata +43 -87
  81. data/NEWS.rdoc +0 -328
  82. data/README.rdoc +0 -83
  83. data/lib/nanoc3/base/plugin.rb +0 -88
  84. data/lib/nanoc3/base/preprocessor_context.rb +0 -37
  85. data/lib/nanoc3/data_sources/filesystem_combined.rb +0 -214
  86. data/lib/nanoc3/data_sources/filesystem_common.rb +0 -22
  87. data/lib/nanoc3/data_sources/filesystem_compact.rb +0 -256
  88. data/lib/nanoc3/extra/context.rb +0 -24
  89. data/lib/nanoc3/package.rb +0 -107
  90. data/vendor/cri/ChangeLog +0 -0
  91. data/vendor/cri/LICENSE +0 -19
  92. data/vendor/cri/NEWS +0 -0
  93. data/vendor/cri/README +0 -4
  94. data/vendor/cri/Rakefile +0 -25
  95. data/vendor/cri/lib/cri/base.rb +0 -153
  96. data/vendor/cri/lib/cri/command.rb +0 -105
  97. data/vendor/cri/lib/cri/core_ext/string.rb +0 -41
  98. data/vendor/cri/lib/cri/core_ext.rb +0 -8
  99. data/vendor/cri/lib/cri/option_parser.rb +0 -186
  100. data/vendor/cri/lib/cri.rb +0 -12
  101. data/vendor/cri/test/test_base.rb +0 -6
  102. data/vendor/cri/test/test_command.rb +0 -6
  103. data/vendor/cri/test/test_core_ext.rb +0 -21
  104. data/vendor/cri/test/test_option_parser.rb +0 -279
@@ -2,77 +2,28 @@
2
2
 
3
3
  module Nanoc3::DataSources
4
4
 
5
- # The filesystem data source is the default 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
- # = Items
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
- ########## Preparation ##########
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
- ########## Loading data ##########
85
-
35
+ # See {Nanoc3::DataSource#items}.
86
36
  def items
87
- meta_filenames('content').map do |meta_filename|
88
- # Read metadata
89
- meta = YAML.load_file(meta_filename) || {}
37
+ load_objects('content', 'item', Nanoc3::Item)
38
+ end
90
39
 
91
- # Get content
92
- content_filename = content_filename_for_dir(File.dirname(meta_filename))
93
- content = File.read(content_filename)
40
+ # See {Nanoc3::DataSource#layouts}.
41
+ def layouts
42
+ load_objects('layouts', 'layout', Nanoc3::Layout)
43
+ end
94
44
 
95
- # Get attributes
96
- attributes = meta.merge(:file => Nanoc3::Extra::FileProxy.new(content_filename))
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
- # Get identifier
99
- identifier = meta_filename_to_identifier(meta_filename, /^content/)
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
- # Get modification times
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
- # Create item object
107
- Nanoc3::Item.new(content, attributes, identifier, mtime)
108
- end
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
- def layouts
112
- meta_filenames('layouts').map do |meta_filename|
113
- # Get content
114
- content_filename = content_filename_for_dir(File.dirname(meta_filename))
115
- content = File.read(content_filename)
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 = YAML.load_file(meta_filename) || {}
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
- identifier = meta_filename_to_identifier(meta_filename, /^layouts/)
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
- mtime = meta_mtime > content_mtime ? meta_mtime : content_mtime
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
- Nanoc3::Layout.new(content, attributes, identifier, mtime)
121
+ klass.new(content, attributes, identifier, mtime)
130
122
  end
131
123
  end
132
124
 
133
- ########## Creating data ##########
134
-
135
- # Creates a new item with the given content, attributes and identifier.
136
- def create_item(content, attributes, identifier)
137
- # Determine base path
138
- last_component = identifier.split('/')[-1] || 'content'
139
- base_path = 'content' + identifier + last_component
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
- # Get filenames
142
- dir_path = 'content' + identifier
143
- meta_filename = 'content' + identifier + last_component + '.yaml'
144
- content_filename = 'content' + identifier + last_component + '.html'
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
- # Create files
151
- FileUtils.mkdir_p(dir_path)
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
- # Creates a new layout with the given content, attributes and identifier.
157
- def create_layout(content, attributes, identifier)
158
- # Determine base path
159
- last_component = identifier.split('/')[-1]
160
- base_path = 'layouts' + identifier + last_component
161
-
162
- # Get filenames
163
- dir_path = 'layouts' + identifier
164
- meta_filename = 'layouts' + identifier + last_component + '.yaml'
165
- content_filename = 'layouts' + identifier + last_component + '.html'
166
-
167
- # Notify
168
- Nanoc3::NotificationCenter.post(:file_created, meta_filename)
169
- Nanoc3::NotificationCenter.post(:file_created, content_filename)
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
- private
178
-
179
- ########## Custom functions ##########
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 list of all meta files in the given base directory as well
182
- # as its subdirectories.
183
- def meta_filenames(base)
184
- # Find all possible meta file names
185
- filenames = Dir[base + '/**/*.yaml']
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
- # Filter out invalid meta files
188
- good_filenames = []
189
- bad_filenames = []
190
- filenames.each do |filename|
191
- if filename =~ /meta\.yaml$/ or filename =~ /([^\/]+)\/\1\.yaml$/
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
- # Warn about bad filenames
199
- unless bad_filenames.empty?
200
- raise RuntimeError.new(
201
- "The following files appear to be meta files, " +
202
- "but have an invalid name:\n - " +
203
- bad_filenames.join("\n - ")
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
- # Returns the filename of the content file in the given directory,
211
- # ignoring any unwanted files (files that end with '~', '.orig', '.rej' or
212
- # '.bak')
213
- def content_filename_for_dir(dir)
214
- # Find all files
215
- filename_glob_1 = dir.sub(/([^\/]+)$/, '\1/\1.*')
216
- filename_glob_2 = dir.sub(/([^\/]+)$/, '\1/index.*')
217
- filenames = (Dir[filename_glob_1] + Dir[filename_glob_2]).uniq
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
- # Reject meta files
220
- filenames.reject! { |f| f =~ /\.yaml$/ }
225
+ return [ meta, content ]
226
+ end
221
227
 
222
- # Reject backups
223
- filenames.reject! { |f| f =~ /(~|\.orig|\.rej|\.bak)$/ }
228
+ # Read data
229
+ data = File.read(content_filename)
224
230
 
225
- # Make sure there is only one content file
226
- if filenames.size != 1
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
- "Expected 1 content file in #{dir} but found #{filenames.size}"
240
+ "The file '#{content_filename}' does not seem to be a nanoc #{kind}"
229
241
  )
230
242
  end
231
243
 
232
- # Return content filename
233
- filenames.first
234
- end
244
+ # Parse
245
+ meta = YAML.load(pieces[2]) || {}
246
+ content = pieces[4..-1].join.strip
235
247
 
236
- def meta_filename_to_identifier(meta_filename, regex)
237
- meta_filename.sub(regex, '').sub(/[^\/]+\.yaml$/, '')
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
@@ -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 'FilesystemCombined', 'nanoc3/data_sources/filesystem_combined'
8
- autoload 'FilesystemCommon', 'nanoc3/data_sources/filesystem_common'
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