diff --git a/lib/jekyll.rb b/lib/jekyll.rb index 03724100..0a7c2b69 100644 --- a/lib/jekyll.rb +++ b/lib/jekyll.rb @@ -48,6 +48,7 @@ module Jekyll autoload :Layout, 'jekyll/layout' autoload :LayoutReader, 'jekyll/layout_reader' autoload :LogAdapter, 'jekyll/log_adapter' + autoload :Metadata, 'jekyll/metadata' autoload :Page, 'jekyll/page' autoload :PluginManager, 'jekyll/plugin_manager' autoload :Post, 'jekyll/post' diff --git a/lib/jekyll/cleaner.rb b/lib/jekyll/cleaner.rb index 6dd59ea0..79f97f47 100644 --- a/lib/jekyll/cleaner.rb +++ b/lib/jekyll/cleaner.rb @@ -21,7 +21,14 @@ module Jekyll # # Returns an Array of the file and directory paths def obsolete_files - (existing_files - new_files - new_dirs + replaced_files).to_a + (existing_files - new_files - new_dirs + replaced_files + metadata_file).to_a + end + + # Private: The metadata file storing dependency tree and build history + # + # Returns an Array with the metdata file as the only item + def metadata_file + [site.metadata.metadata_file] end # Private: The list of existing files, apart from those included in keep_files and hidden files. diff --git a/lib/jekyll/command.rb b/lib/jekyll/command.rb index a1cc8c01..d463d235 100644 --- a/lib/jekyll/command.rb +++ b/lib/jekyll/command.rb @@ -58,6 +58,7 @@ module Jekyll c.option 'unpublished', '--unpublished', 'Render posts that were marked as unpublished' c.option 'quiet', '-q', '--quiet', 'Silence output.' c.option 'verbose', '-V', '--verbose', 'Print verbose output.' + c.option 'clean', '-c', '--clean', 'Clean the site before rebuilding.' end end diff --git a/lib/jekyll/convertible.rb b/lib/jekyll/convertible.rb index 4b21a66b..4b1f3761 100644 --- a/lib/jekyll/convertible.rb +++ b/lib/jekyll/convertible.rb @@ -207,6 +207,12 @@ module Jekyll info, File.join(site.config['layouts'], layout.name)) + # Add layout to dependency tree + site.metadata.add_dependency( + Jekyll.sanitized_path(site.source, path), + Jekyll.sanitized_path(site.source, layout.path) + ) + if layout = layouts[layout.data["layout"]] if used.include?(layout) layout = nil # avoid recursive chain diff --git a/lib/jekyll/layout.rb b/lib/jekyll/layout.rb index 4dde59b6..f973019f 100644 --- a/lib/jekyll/layout.rb +++ b/lib/jekyll/layout.rb @@ -8,6 +8,9 @@ module Jekyll # Gets the name of this layout. attr_reader :name + # Gets the path to this layout. + attr_reader :path + # Gets/Sets the extension of this layout. attr_accessor :ext @@ -26,6 +29,7 @@ module Jekyll @site = site @base = base @name = name + @path = Jekyll.sanitized_path(site.source, File.join(base, name)) self.data = {} diff --git a/lib/jekyll/metadata.rb b/lib/jekyll/metadata.rb new file mode 100644 index 00000000..abda35e5 --- /dev/null +++ b/lib/jekyll/metadata.rb @@ -0,0 +1,87 @@ +require 'set' + +module Jekyll + class Metadata + attr_reader :site, :metadata + + def initialize(site) + @site = site + + # Initialize metadata store by reading YAML file, + # or an empty hash if file does not exist + @metadata = (File.file?(metadata_file) && !(site.config['clean'])) ? SafeYAML.load(File.read(metadata_file)) : {} + + # Initialize cache to an empty hash + @cache = {} + end + + # Add a path to the metadata + # + # Returns true. + def add(path) + @metadata[path] = { + "mtime" => File.mtime(path), + "deps" => [] + } + @cache[path] = true + end + + # Force a path to regenerate + # + # Returns true. + def force(path) + @cache[path] = true + end + + # Checks if a path should be regenerated + # + # Returns a boolean. + def regenerate?(path) + # Check for path in cache + if @cache.has_key? path + return @cache[path] + end + + # Check path that exists in metadata + if (data = @metadata[path]) + data["deps"].each do |dependency| + if regenerate?(dependency) + return @cache[dependency] = @cache[path] = true + end + end + if data["mtime"] == File.mtime(path) + return @cache[path] = false + else + return add(path) + end + end + + # Path does not exist in metadata, add it + return add(path) + end + + # Add a dependency of a path + # + # Returns true. + def add_dependency(path, dependency) + @metadata[path]["deps"] << dependency unless @metadata[path]["deps"].include? dependency + add(dependency) + end + + # Write the metadata to disk + # + # Returns nothing. + def write + File.open(metadata_file, 'w') do |f| + f.write(@metadata.to_yaml) + end + end + + # Produce the absolute path of the metadata file + # + # Returns the String path of the file. + def metadata_file + Jekyll.sanitized_path(site.source, '.jekyll-metadata') + end + end +end diff --git a/lib/jekyll/renderer.rb b/lib/jekyll/renderer.rb index f88a4187..61839353 100644 --- a/lib/jekyll/renderer.rb +++ b/lib/jekyll/renderer.rb @@ -138,6 +138,12 @@ module Jekyll File.join(site.config['layouts'], layout.name) ) + # Add layout to dependency tree + site.metadata.add_dependency( + Jekyll.sanitized_path(site.source, document.path), + Jekyll.sanitized_path(site.source, layout.path) + ) + if layout = site.layouts[layout.data["layout"]] if used.include?(layout) layout = nil # avoid recursive chain diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb index 115b4133..38c61661 100644 --- a/lib/jekyll/site.rb +++ b/lib/jekyll/site.rb @@ -11,6 +11,7 @@ module Jekyll :gems, :plugin_manager attr_accessor :converters, :generators + attr_accessor :metadata # Public: Initialize a new Site. # @@ -27,6 +28,9 @@ module Jekyll @source = File.expand_path(config['source']).freeze @dest = File.expand_path(config['destination']).freeze + # Build metadata + @metadata = Metadata.new(self) + self.plugin_manager = Jekyll::PluginManager.new(self) self.plugins = plugin_manager.plugins_path @@ -49,7 +53,7 @@ module Jekyll read generate render - cleanup + cleanup if config['clean'] write end @@ -289,13 +293,17 @@ module Jekyll collections.each do |label, collection| collection.docs.each do |document| - document.output = Jekyll::Renderer.new(self, document).run + if @metadata.regenerate?(document.path) + document.output = Jekyll::Renderer.new(self, document).run + end end end payload = site_payload [posts, pages].flatten.each do |page_or_post| - page_or_post.render(layouts, payload) + if @metadata.regenerate?(Jekyll.sanitized_path(source, page_or_post.relative_path)) + page_or_post.render(layouts, payload) + end end rescue Errno::ENOENT => e # ignore missing layout dir @@ -312,7 +320,12 @@ module Jekyll # # Returns nothing. def write - each_site_file { |item| item.write(dest) } + each_site_file { |item| + if @metadata.regenerate? Jekyll.sanitized_path(source, item.path) + item.write(dest) + end + } + @metadata.write end # Construct a Hash of Posts indexed by the specified Post attribute. diff --git a/lib/jekyll/tags/include.rb b/lib/jekyll/tags/include.rb index 3eb4d7c0..8f7da2ca 100644 --- a/lib/jekyll/tags/include.rb +++ b/lib/jekyll/tags/include.rb @@ -105,13 +105,20 @@ eos end def render(context) + site = context.registers[:site] dir = resolved_includes_dir(context) file = render_variable(context) || @file validate_file_name(file) path = File.join(dir, file) - validate_path(path, dir, context.registers[:site].safe) + validate_path(path, dir, site.safe) + + # Add include to dependency tree + site.metadata.add_dependency( + Jekyll.sanitized_path(site.source, context.registers[:page]["path"]), + path + ) begin partial = Liquid::Template.parse(source(path, context)) diff --git a/site/.gitignore b/site/.gitignore index 79bd74f7..c1bd964c 100644 --- a/site/.gitignore +++ b/site/.gitignore @@ -2,3 +2,4 @@ _site/ *.swp pkg/ test/ +.jekyll-metadata