lib/nanoc/data_sources/filesystem.rb in nanoc-2.0.4 vs lib/nanoc/data_sources/filesystem.rb in nanoc-2.1

- old
+ new

@@ -1,278 +1,588 @@ -module Nanoc::DataSource::Filesystem +module Nanoc::DataSources - class FileProxy + # The filesystem data source is the default data source for a new nanoc + # site. It stores all data as files on the hard disk. + # + # None of the methods are documented in this file. See Nanoc::DataSource for + # documentation on the overridden methods instead. + # + # = Pages + # + # The filesystem data source stores its pages in nested directories. Each + # directory represents a single page. The root directory is the 'content' + # directory. + # + # Every directory has a content file and a meta file. The content file + # contains the actual page content, while the meta file contains the page's + # metadata, formatted as YAML. + # + # Both content files and meta files are named after its parent directory + # (i.e. page). For example, a page named 'foo' will have a directorynamed + # 'foo', with e.g. a 'foo.markdown' content file and a 'foo.yaml' meta file. + # + # Content file extensions are not used for determining the filter that + # should be run; the meta file defines the list of filters. The meta file + # extension must always be 'yaml', though. + # + # Content files can also have the 'index' basename. Similarly, meta files + # can have the 'meta' basename. For example, a parent directory named 'foo' + # can have an 'index.txt' content file and a 'meta.yaml' meta file. This is + # to preserve backward compatibility. + # + # = Page defaults + # + # The page defaults are loaded from a YAML-formatted file named + # 'page_defaults.yaml' at the top level of the nanoc site directory. For + # backward compatibility, the file can also be named 'meta.yaml'. + # + # = Assets + # + # Assets are stored in the 'assets' directory (surprise!). The structure is + # very similar to the structure of the 'content' directory, so see the Pages + # section for details on how this directory is structured. + # + # = Asset defaults + # + # The asset defaults are stored similar to the way page defaults are stored, + # except that the asset defaults file is named 'asset_defaults.yaml' + # instead. + # + # = Layouts + # + # Layouts are stored as directories in the 'layouts' directory. Each layout + # contains a content file and a meta file. The content file contain the + # actual layout, and the meta file describes how the page should be handled + # (contains the filter that should be used). + # + # For backward compatibility, a layout can also be a single file in the + # 'layouts' directory. Such a layout cannot have any metadata; the filter + # used for this layout is determined from the file extension. + # + # = Templates + # + # Templates are located in the 'templates' directroy. Every template is a + # directory consisting of a content file and a meta file, both named after + # the template. This is very similar to the way pages are stored, except + # that templates cannot be nested. + # + # = Code + # + # Code is stored in '.rb' files in the 'lib' directory. Code can reside in + # sub-directories. + class Filesystem < Nanoc::DataSource - instance_methods.each { |m| undef_method m unless m =~ /^__/ } + PAGE_DEFAULTS_FILENAME = 'page_defaults.yaml' + PAGE_DEFAULTS_FILENAME_OLD = 'meta.yaml' + ASSET_DEFAULTS_FILENAME = 'asset_defaults.yaml' - def initialize(path) - @path = path - end + ########## Attributes ########## - def method_missing(sym, *args, &block) - File.new(@path).__send__(sym, *args, &block) + identifier :filesystem + + ########## VCSes ########## + + attr_accessor :vcs + + def vcs + @vcs ||= Nanoc::Extra::VCSes::Dummy.new end - end + ########## Preparation ########## - class FilesystemDataSource < Nanoc::DataSource + def up # :nodoc: + end - ########## Attributes ########## + def down # :nodoc: + end - identifier :filesystem + def setup # :nodoc: + # Create directories + %w( assets content templates layouts lib ).each do |dir| + FileUtils.mkdir_p(dir) + vcs.add(dir) + end + end - ########## Preparation ########## + def destroy # :nodoc: + # Remove files + vcs.remove(ASSET_DEFAULTS_FILENAME) if File.file?(ASSET_DEFAULTS_FILENAME) + vcs.remove(PAGE_DEFAULTS_FILENAME) if File.file?(PAGE_DEFAULTS_FILENAME) + vcs.remove(PAGE_DEFAULTS_FILENAME_OLD) if File.file?(PAGE_DEFAULTS_FILENAME_OLD) - def up + # Remove directories + vcs.remove('assets') + vcs.remove('content') + vcs.remove('templates') + vcs.remove('layouts') + vcs.remove('lib') end - def down + def update # :nodoc: + update_page_defaults + update_pages + update_layouts + update_templates end - def setup - # Create page - FileManager.create_file 'content/content.txt' do - "I'm a brand new root page. Please edit me!\n" + ########## Pages ########## + + def pages # :nodoc: + meta_filenames('content').map do |meta_filename| + # Read metadata + meta = YAML.load_file(meta_filename) || {} + + if meta['is_draft'] + # Skip drafts + nil + else + # Get content + content_filename = content_filename_for_dir(File.dirname(meta_filename)) + content = File.read(content_filename) + + # Get attributes + attributes = meta.merge(:file => Nanoc::Extra::FileProxy.new(content_filename)) + + # Get path + path = meta_filename.sub(/^content/, '').sub(/[^\/]+\.yaml$/, '') + + # Get modification times + meta_mtime = File.stat(meta_filename).mtime + content_mtime = File.stat(content_filename).mtime + mtime = meta_mtime > content_mtime ? meta_mtime : content_mtime + + # Create page object + Nanoc::Page.new(content, attributes, path, mtime) + end + end.compact + end + + def save_page(page) # :nodoc: + # Determine possible meta file paths + last_component = page.path.split('/')[-1] + meta_filename_worst = 'content' + page.path + 'index.yaml' + meta_filename_best = 'content' + page.path + (last_component || 'content') + '.yaml' + + # Get existing path + existing_path = nil + existing_path = meta_filename_best if File.file?(meta_filename_best) + existing_path = meta_filename_worst if File.file?(meta_filename_worst) + + if existing_path.nil? + # Get filenames + dir_path = 'content' + page.path + meta_filename = meta_filename_best + content_filename = 'content' + page.path + (last_component || 'content') + '.html' + + # Notify + Nanoc::NotificationCenter.post(:file_created, meta_filename) + Nanoc::NotificationCenter.post(:file_created, content_filename) + + # Create directories if necessary + FileUtils.mkdir_p(dir_path) + else + # Get filenames + meta_filename = existing_path + content_filename = content_filename_for_dir(File.dirname(existing_path)) + + # Notify + Nanoc::NotificationCenter.post(:file_updated, meta_filename) + Nanoc::NotificationCenter.post(:file_updated, content_filename) end - FileManager.create_file 'content/content.yaml' do - "# Built-in\n" + - "\n" + - "# Custom\n" + - "title: \"A New Root Page\"\n" + + # Write files + File.open(meta_filename, 'w') { |io| io.write(page.attributes.to_split_yaml) } + File.open(content_filename, 'w') { |io| io.write(page.content) } + + # Add to working copy if possible + if existing_path.nil? + vcs.add(meta_filename) + vcs.add(content_filename) end + end - # Create page defaults - FileManager.create_file 'meta.yaml' do - "# This file contains the default values for all metafiles.\n" + - "# Other metafiles can override the contents of this one.\n" + - "\n" + - "# Built-in\n" + - "custom_path: none\n" + - "extension: \"html\"\n" + - "filename: \"index\"\n" + - "filters_post: []\n" + - "filters_pre: []\n" + - "is_draft: false\n" + - "layout: \"default\"\n" + - "skip_output: false\n" + - "\n" + - "# Custom\n" + def move_page(page, new_path) # :nodoc: + # TODO implement + end + + def delete_page(page) # :nodoc: + # TODO implement + end + + ########## Assets ########## + + def assets # :nodoc: + meta_filenames('assets').map do |meta_filename| + # Read metadata + meta = YAML.load_file(meta_filename) || {} + + # Get content file + content_filename = content_filename_for_dir(File.dirname(meta_filename)) + content_file = File.new(content_filename) + + # Get attributes + attributes = meta.merge(:extension => File.extname(content_filename)[1..-1]) + + # Get path + path = meta_filename.sub(/^assets/, '').sub(/[^\/]+\.yaml$/, '') + + # Get modification times + meta_mtime = File.stat(meta_filename).mtime + content_mtime = File.stat(content_filename).mtime + mtime = meta_mtime > content_mtime ? meta_mtime : content_mtime + + # Create asset object + Nanoc::Asset.new(content_file, attributes, path, mtime) end + end - # Create template - FileManager.create_file 'templates/default/default.txt' do - "Hi, I'm a new page!\n" + def save_asset(asset) # :nodoc: + # Determine meta file path + last_component = asset.path.split('/')[-1] + meta_filename = 'assets' + asset.path + last_component + '.yaml' + + # Get existing path + existing_path = nil + existing_path = meta_filename_best if File.file?(meta_filename_best) + existing_path = meta_filename_worst if File.file?(meta_filename_worst) + + if meta_filename.nil? + # Get filenames + dir_path = 'assets' + asset.path + content_filename = 'assets' + asset.path + last_component + '.dat' + + # Notify + Nanoc::NotificationCenter.post(:file_created, meta_filename) + Nanoc::NotificationCenter.post(:file_created, content_filename) + + # Create directories if necessary + FileUtils.mkdir_p(dir_path) + else + # Get filenames + content_filename = content_filename_for_dir(File.dirname(meta_filename)) + + # Notify + Nanoc::NotificationCenter.post(:file_updated, meta_filename) + Nanoc::NotificationCenter.post(:file_updated, content_filename) end - FileManager.create_file 'templates/default/default.yaml' do - "# Built-in\n" + - "\n" + - "# Custom\n" + - "title: \"A New Page\"\n" + + # Write files + File.open(meta_filename, 'w') { |io| io.write(asset.attributes.to_split_yaml) } + File.open(content_filename, 'w') { } + + # Add to working copy if possible + if meta_filename.nil? + vcs.add(meta_filename) + vcs.add(content_filename) end + end - # Create layout - FileManager.create_file 'layouts/default.erb' do - "<html>\n" + - " <head>\n" + - " <title><%= @page.title %></title>\n" + - " </head>\n" + - " <body>\n" + - "<%= @page.content %>\n" + - " </body>\n" + - "</html>\n" + def move_asset(asset, new_path) # :nodoc: + # TODO implement + end + + def delete_asset(asset) # :nodoc: + # TODO implement + end + + ########## Page Defaults ########## + + def page_defaults # :nodoc: + # Get attributes + filename = File.file?(PAGE_DEFAULTS_FILENAME) ? PAGE_DEFAULTS_FILENAME : PAGE_DEFAULTS_FILENAME_OLD + attributes = YAML.load_file(filename) || {} + + # Get mtime + mtime = File.stat(filename).mtime + + # Build page defaults + Nanoc::PageDefaults.new(attributes, mtime) + end + + def save_page_defaults(page_defaults) # :nodoc: + # Notify + if File.file?(PAGE_DEFAULTS_FILENAME) + filename = PAGE_DEFAULTS_FILENAME + created = false + Nanoc::NotificationCenter.post(:file_updated, filename) + elsif File.file?(PAGE_DEFAULTS_FILENAME_OLD) + filename = PAGE_DEFAULTS_FILENAME_OLD + created = false + Nanoc::NotificationCenter.post(:file_updated, filename) + else + filename = PAGE_DEFAULTS_FILENAME + created = true + Nanoc::NotificationCenter.post(:file_created, filename) end - # Create code - FileManager.create_file 'lib/default.rb' do - "\# All files in the 'lib' directory will be loaded\n" + - "\# before nanoc starts compiling.\n" + - "\n" + - "def html_escape(str)\n" + - " str.gsub('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;').gsub('\"', '&quot;')\n" + - "end\n" + - "alias h html_escape\n" + # Write + File.open(filename, 'w') do |io| + io.write(page_defaults.attributes.to_split_yaml) end + # Add to working copy if possible + vcs.add(filename) if created end - ########## Loading data ########## + ########## Asset Defaults ########## - # The filesystem data source stores its pages in nested directories. Each - # directory represents a single page. The root directory is the 'content' - # directory. - # - # Every directory has a content file and a meta file. The content file - # contains the actual page content, while the meta file contains the - # page's metadata. - # - # Both content files and meta files are named after its parent directory - # (i.e. page). For example, a page named 'foo' will have a directory named - # 'foo', with e.g. a 'foo.markdown' content file and a 'foo.yaml' meta - # file. - # - # Content file extensions are ignored by nanoc. The content file extension - # does not determine the filters to run on it; the meta file defines the - # list of filters. The meta file extension must always be 'yaml', though. - # - # Content files can also have the 'index' basename. Similarly, meta files - # can have the 'meta' basename. For example, a parent directory named - # 'foo' can have an 'index.txt' content file and a 'meta.yaml' meta file. - # This is to preserve backward compatibility. - def pages - meta_filenames.inject([]) do |pages, filename| - # Read metadata - meta = (YAML.load_file(filename) || {}).clean + def asset_defaults # :nodoc: + if File.file?(ASSET_DEFAULTS_FILENAME) + # Get attributes + attributes = YAML.load_file(ASSET_DEFAULTS_FILENAME) || {} - if meta[:is_draft] - # Skip drafts - pages - else - # Get extra info - path = filename.sub(/^content/, '').sub(/[^\/]+\.yaml$/, '') - file = content_file_for_dir(File.dirname(filename)) - extras = { - :path => path, - :file => FileProxy.new(file.path), - :uncompiled_content => file.read - } + # Get mtime + mtime = File.stat(ASSET_DEFAULTS_FILENAME).mtime - # Add to list of pages - pages + [ meta.merge(extras) ] - end + # Build asset defaults + Nanoc::AssetDefaults.new(attributes, mtime) + else + Nanoc::AssetDefaults.new({}) end end - # The page defaults are loaded from a 'meta.yaml' file - def page_defaults - (YAML.load_file('meta.yaml') || {}).clean + def save_asset_defaults(asset_defaults) # :nodoc: + # Notify + if File.file?(ASSET_DEFAULTS_FILENAME) + Nanoc::NotificationCenter.post(:file_updated, ASSET_DEFAULTS_FILENAME) + created = false + else + Nanoc::NotificationCenter.post(:file_created, ASSET_DEFAULTS_FILENAME) + created = true + end + + # Write + File.open(ASSET_DEFAULTS_FILENAME, 'w') do |io| + io.write(asset_defaults.attributes.to_split_yaml) + end + + # Add to working copy if possible + vcs.add(ASSET_DEFAULTS_FILENAME) if created end - # Layouts are stored as files in the 'layouts' directory. Each layout has - # a basename (the part before the extension) and an extension. Unlike page - # content files, the extension _is_ used for determining the layout - # processor; which extension maps to which layout processor is defined in - # the layout processors. - def layouts - Dir["layouts/*"].reject { |f| f =~ /~$/ }.map do |filename| - # Get layout details - extension = File.extname(filename) - name = File.basename(filename, extension) - content = File.read(filename) + ########## Layouts ########## - # Build hash for layout - { :name => name, :content => content, :extension => extension } + def layouts # :nodoc: + # Determine what layout directory structure is being used + dir_count = Dir[File.join('layouts', '*')].select { |f| File.directory?(f) }.size + is_old_school = (dir_count == 0) + + if is_old_school + # Warn about deprecation + warn( + 'nanoc 2.1 changes the way layouts are stored. Future versions will not support these outdated sites. To update your site, issue \'nanoc update\'.', + 'DEPRECATION WARNING' + ) + + Dir[File.join('layouts', '*')].reject { |f| f =~ /~$/ }.map do |filename| + # Get content + content = File.read(filename) + + # Get attributes + attributes = { :extension => File.extname(filename)} + + # Get path + path = File.basename(filename, attributes[:extension]) + + # Get modification time + mtime = File.stat(filename).mtime + + # Create layout object + Nanoc::Layout.new(content, attributes, path, mtime) + end + else + meta_filenames('layouts').map do |meta_filename| + # Get content + content_filename = content_filename_for_dir(File.dirname(meta_filename)) + content = File.read(content_filename) + + # Get attributes + attributes = YAML.load_file(meta_filename) || {} + + # Get path + path = meta_filename.sub(/^layouts\//, '').sub(/\/[^\/]+\.yaml$/, '') + + # Get modification times + meta_mtime = File.stat(meta_filename).mtime + content_mtime = File.stat(content_filename).mtime + mtime = meta_mtime > content_mtime ? meta_mtime : content_mtime + + # Create layout object + Nanoc::Layout.new(content, attributes, path, mtime) + end end end - # Templates are located in the 'templates' directroy. Every template is a - # directory consisting of a content file and a meta file, both named after - # the template. This is very similar to the way pages are stored, except - # that templates cannot be nested. - def templates - meta_filenames('templates').inject([]) do |templates, filename| - # Get template name - name = filename.sub(/^templates\/(.*)\/[^\/]+\.yaml$/, '\1') + def save_layout(layout) # :nodoc: + # Determine what layout directory structure is being used + layout_file_count = Dir[File.join('layouts', '*')].select { |f| File.file?(f) }.size + error_outdated if layout_file_count > 0 - # Get file names - meta_filename = filename - content_filenames = Dir['templates/' + name + '/' + name + '.*'] + - Dir['templates/' + name + '/index.*'] - - Dir['templates/' + name + '/*.yaml' ] + # Get paths + last_component = layout.path.split('/')[-1] + dir_path = 'layouts' + layout.path + meta_filename = dir_path + last_component + '.yaml' + content_filename = Dir[dir_path + last_component + '.*'][0] - # Read files - extension = nil - content = nil - content_filenames.each do |content_filename| - content = File.read(content_filename) - extension = File.extname(content_filename) - end - meta = File.read(meta_filename) + if File.file?(meta_filename) + created = false - # Add it to the list of templates - templates + [{ - :name => name, - :extension => extension, - :content => content, - :meta => meta - }] + # Notify + Nanoc::NotificationCenter.post(:file_updated, meta_filename) + Nanoc::NotificationCenter.post(:file_updated, content_filename) + else + created = true + + # Create dir + FileUtils.mkdir_p(dir_path) + + # Get content filename + content_filename = dir_path + last_component + '.html' + + # Notify + Nanoc::NotificationCenter.post(:file_created, meta_filename) + Nanoc::NotificationCenter.post(:file_created, content_filename) end + + # Write files + File.open(meta_filename, 'w') { |io| io.write(layout.attributes.to_split_yaml) } + File.open(content_filename, 'w') { |io| io.write(layout.content) } + + # Add to working copy if possible + if created + vcs.add(meta_filename) + vcs.add(content_filename) + end end - # Code is stored in '.rb' files in the 'lib' directory. Code can reside - # in sub-directories. - def code - Dir['lib/**/*.rb'].sort.inject('') { |m, f| m + File.read(f) + "\n" } + def move_layout(layout, new_path) # :nodoc: + # TODO implement end - ########## Creating data ########## + def delete_layout(layout) # :nodoc: + # TODO implement + end - # Creating a page creates a page directory with the name of the page in - # the 'content' directory, as well as a content file named xxx.txt and a - # meta file named xxx.yaml (with xxx being the name of the page). - def create_page(path, template) - # Make sure path does not start or end with a slash - sanitized_path = path.gsub(/^\/+|\/+$/, '') + ########## Templates ########## - # Get paths - dir_path = 'content/' + sanitized_path - name = sanitized_path.sub(/.*\/([^\/]+)$/, '\1') - meta_path = dir_path + '/' + name + '.yaml' - content_path = dir_path + '/' + name + template[:extension] + def templates # :nodoc: + meta_filenames('templates').map do |meta_filename| + # Get name + name = meta_filename.sub(/^templates\/(.*)\/[^\/]+\.yaml$/, '\1') - # Make sure the page doesn't exist yet - error "A page named '#{path}' already exists." if File.exist?(meta_path) + # Get content + content_filename = content_filename_for_dir(File.dirname(meta_filename)) + content = File.read(content_filename) - # Create index and meta file - FileManager.create_file(meta_path) { template[:meta] } - FileManager.create_file(content_path) { template[:content] } + # Get attributes + attributes = YAML.load_file(meta_filename) || {} + + # Build template + Nanoc::Template.new(content, attributes, name) + end end - # Creating a layout creates a single file in the 'layouts' directory, - # named xxx.erb (with xxx being the name of the layout). - def create_layout(name) - # Get details - path = 'layouts/' + name + '.erb' + def save_template(template) # :nodoc: + # Determine possible meta file paths + meta_filename_worst = 'templates/' + template.name + '/index.yaml' + meta_filename_best = 'templates/' + template.name + '/' + template.name + '.yaml' - # Make sure the layout doesn't exist yet - error "A layout named '#{name}' already exists." if File.exist?(path) + # Get existing path + existing_path = nil + existing_path = meta_filename_best if File.file?(meta_filename_best) + existing_path = meta_filename_worst if File.file?(meta_filename_worst) - # Create layout file - FileManager.create_file(path) do - "<html>\n" + - " <head>\n" + - " <title><%= @page.title %></title>\n" + - " </head>\n" + - " <body>\n" + - "<%= @page.content %>\n" + - " </body>\n" + - "</html>\n" + if existing_path.nil? + # Get filenames + dir_path = 'templates/' + template.name + meta_filename = meta_filename_best + content_filename = 'templates/' + template.name + '/' + template.name + '.html' + + # Notify + Nanoc::NotificationCenter.post(:file_created, meta_filename) + Nanoc::NotificationCenter.post(:file_created, content_filename) + + # Create directories if necessary + FileUtils.mkdir_p(dir_path) + else + # Get filenames + meta_filename = existing_path + content_filename = content_filename_for_dir(File.dirname(existing_path)) + + # Notify + Nanoc::NotificationCenter.post(:file_updated, meta_filename) + Nanoc::NotificationCenter.post(:file_updated, content_filename) end + + # Write files + File.open(meta_filename, 'w') { |io| io.write(template.page_attributes.to_split_yaml) } + File.open(content_filename, 'w') { |io| io.write(template.page_content) } + + # Add to working copy if possible + if existing_path.nil? + vcs.add(meta_filename) + vcs.add(content_filename) + end end - # Creating a template creates a template directory with the name of the - # template in the 'templates' directory, as well as a content file named - # xxx.txt and a meta file named xxx.yaml (with xxx being the name of the - # template). - def create_template(name) - # Get paths - meta_path = 'templates/' + name + '/' + name + '.yaml' - content_path = 'templates/' + name + '/' + name + '.txt' + def move_template(template, new_name) # :nodoc: + # TODO implement + end - # Make sure the template doesn't exist yet - error "A template named '#{name}' already exists." if File.exist?(meta_path) + def delete_template(template) # :nodoc: + # TODO implement + end - # Create index and meta file - FileManager.create_file(meta_path) { "# Built-in\n\n# Custom\ntitle: A New Page\n" } - FileManager.create_file(content_path) { "Hi, I'm new here!\n" } + ########## Code ########## + + def code # :nodoc: + # Get files + filenames = Dir['lib/**/*.rb'].sort + + # Get data + data = filenames.map { |filename| File.read(filename) + "\n" }.join('') + + # Get modification time + mtimes = filenames.map { |filename| File.stat(filename).mtime } + mtime = mtimes.inject { |memo, mtime| memo > mtime ? mtime : memo } + + # Build code + Nanoc::Code.new(data, mtime) end + def save_code(code) # :nodoc: + # Check whether code existed + existed = File.file?('lib/default.rb') + + # Remove all existing code files + Dir['lib/**/*.rb'].each do |file| + vcs.remove(file) unless file == 'lib/default.rb' + end + + # Notify + if existed + Nanoc::NotificationCenter.post(:file_updated, 'lib/default.rb') + else + Nanoc::NotificationCenter.post(:file_created, 'lib/default.rb') + end + + # Write new code + File.open('lib/default.rb', 'w') do |io| + io.write(code.data) + end + + # Add to working copy if possible + vcs.add('lib/default.rb') unless existed + end + private ########## Custom functions ########## - # Returns the list of meta files in the given (optional) base directory. - def meta_filenames(base='content') + # Returns the list of all meta files in the given base directory as well + # as its subdirectories. + def meta_filenames(base) # Find all possible meta file names filenames = Dir[base + '/**/*.yaml'] # Filter out invalid meta files good_filenames = [] @@ -285,37 +595,148 @@ end end # Warn about bad filenames unless bad_filenames.empty? - error "The following files appear to be meta files, " + - "but have an invalid name:\n - " + - bad_filenames.join("\n - ") + raise RuntimeError.new( + "The following files appear to be meta files, " + + "but have an invalid name:\n - " + + bad_filenames.join("\n - ") + ) end good_filenames end - # Returns a File object for the content file in the given directory - def content_file_for_dir(dir) + # Returns the filename of the content file in the given directory, + # ignoring any unwanted files (files that end with '~', '.orig', '.rej' or + # '.bak') + def content_filename_for_dir(dir) # Find all files filename_glob_1 = dir.sub(/([^\/]+)$/, '\1/\1.*') filename_glob_2 = dir.sub(/([^\/]+)$/, '\1/index.*') filenames = Dir[filename_glob_1] + Dir[filename_glob_2] # Reject meta files filenames.reject! { |f| f =~ /\.yaml$/ } # Reject backups - filenames.reject! { |f| f =~ /~$/ } + filenames.reject! { |f| f =~ /(~|\.orig|\.rej|\.bak)$/ } # Make sure there is only one content file if filenames.size != 1 - error "Expected 1 content file in #{dir} but found #{filenames.size}" + raise RuntimeError.new( + "Expected 1 content file in #{dir} but found #{filenames.size}" + ) end - # Read content file - File.new(filenames.first) + # Return content filename + filenames.first + end + + # Raises an "outdated data format" error. + def error_outdated + raise RuntimeError.new( + 'This site\'s data is stored in an old format and must be updated. ' + + 'To do so, issue the \'nanoc update\' command. For help on ' + + 'updating a site\'s data, issue \'nanoc help update\'.' + ) + end + + # Updated outdated page defaults (renames page defaults file) + def update_page_defaults + return unless File.file?(PAGE_DEFAULTS_FILENAME_OLD) + + vcs.move(PAGE_DEFAULTS_FILENAME_OLD, PAGE_DEFAULTS_FILENAME) + end + + # Updates outdated pages (both content and meta file names). + def update_pages + # Update content files + # content/foo/bar/baz/index.ext -> content/foo/bar/baz/baz.ext + Dir['content/**/index.*'].select { |f| File.file?(f) }.each do |old_filename| + # Determine new name + if old_filename =~ /^content\/index\./ + new_filename = old_filename.sub(/^content\/index\./, 'content/content.') + else + new_filename = old_filename.sub(/([^\/]+)\/index\.([^\/]+)$/, '\1/\1.\2') + end + + # Move + vcs.move(old_filename, new_filename) + end + + # Update meta files + # content/foo/bar/baz/meta.yaml -> content/foo/bar/baz/baz.yaml + Dir['content/**/meta.yaml'].select { |f| File.file?(f) }.each do |old_filename| + # Determine new name + if old_filename == 'content/meta.yaml' + new_filename = 'content/content.yaml' + else + new_filename = old_filename.sub(/([^\/]+)\/meta.yaml$/, '\1/\1.yaml') + end + + # Move + vcs.move(old_filename, new_filename) + end + end + + # Updates outdated layouts. + def update_layouts + # layouts/abc.ext -> layouts/abc/abc.{html,yaml} + Dir[File.join('layouts', '*')].select { |f| File.file?(f) }.each do |filename| + # Get filter class + filter_class = Nanoc::Filter.with_extension(File.extname(filename)) + + # Get data + content = File.read(filename) + attributes = { :filter => filter_class.identifier.to_s } + path = File.basename(filename, File.extname(filename)) + + # Get layout + tmp_layout = Nanoc::Layout.new(content, attributes, path) + + # Get filenames + last_component = tmp_layout.path.split('/')[-1] + dir_path = 'layouts' + tmp_layout.path + meta_filename = dir_path + last_component + '.yaml' + content_filename = dir_path + last_component + File.extname(filename) + + # Create new files + FileUtils.mkdir_p(dir_path) + File.open(meta_filename, 'w') { |io| io.write(tmp_layout.attributes.to_split_yaml) } + File.open(content_filename, 'w') { |io| io.write(tmp_layout.content) } + + # Add + vcs.add(meta_filename) + vcs.add(content_filename) + + # Delete old files + vcs.remove(filename) + end + end + + # Updates outdated templates (both content and meta file names). + def update_templates + # Update content files + # templates/foo/index.ext -> templates/foo/foo.ext + Dir['templates/**/index.*'].select { |f| File.file?(f) }.each do |old_filename| + # Determine new name + new_filename = old_filename.sub(/([^\/]+)\/index\.([^\/]+)$/, '\1/\1.\2') + + # Move + vcs.move(old_filename, new_filename) + end + + # Update meta files + # templates/foo/meta.yaml -> templates/foo/foo.yaml + Dir['templates/**/meta.yaml'].select { |f| File.file?(f) }.each do |old_filename| + # Determine new name + new_filename = old_filename.sub(/([^\/]+)\/meta.yaml$/, '\1/\1.yaml') + + # Move + vcs.move(old_filename, new_filename) + end end end end