diff --git a/lib/jekyll.rb b/lib/jekyll.rb index 3f01e4d0..dd464503 100644 --- a/lib/jekyll.rb +++ b/lib/jekyll.rb @@ -51,11 +51,11 @@ 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' autoload :Publisher, 'jekyll/publisher' + autoload :Regenerator, 'jekyll/regenerator' autoload :RelatedPosts, 'jekyll/related_posts' autoload :Renderer, 'jekyll/renderer' autoload :Site, 'jekyll/site' diff --git a/lib/jekyll/cleaner.rb b/lib/jekyll/cleaner.rb index bc5bd155..0295fb92 100644 --- a/lib/jekyll/cleaner.rb +++ b/lib/jekyll/cleaner.rb @@ -29,7 +29,7 @@ module Jekyll # # Returns an Array with the metdata file as the only item def metadata_file - [site.metadata.metadata_file] + [site.regenerator.metadata_file] end # Private: The list of existing files, apart from those included in keep_files and hidden files. diff --git a/lib/jekyll/convertible.rb b/lib/jekyll/convertible.rb index eb96dd35..d587f8f3 100644 --- a/lib/jekyll/convertible.rb +++ b/lib/jekyll/convertible.rb @@ -168,15 +168,6 @@ module Jekyll true end - # Determine whether to regenerate the file based on metadata. - # - # Returns true if file needs to be regenerated - def regenerate? - asset_file? || - data['regenerate'] || - site.metadata.regenerate?(site.in_source_dir(relative_path)) - end - # Determine whether the file should be placed into layouts. # # Returns false if the document is an asset file. @@ -217,7 +208,7 @@ module Jekyll File.join(site.config['layouts'], layout.name)) # Add layout to dependency tree - site.metadata.add_dependency( + site.regenerator.add_dependency( site.in_source_dir(path), site.in_source_dir(layout.path) ) diff --git a/lib/jekyll/document.rb b/lib/jekyll/document.rb index c272741f..ec9ee742 100644 --- a/lib/jekyll/document.rb +++ b/lib/jekyll/document.rb @@ -107,13 +107,6 @@ module Jekyll !(coffeescript_file? || yaml_file?) end - # Determine whether the document should be regenerated based on metadata. - # - # Returns true if the document needs to be regenerated. - def regenerate? - data['regenerate'] || site.metadata.regenerate?(path, write?) - end - # Determine whether the file should be placed into layouts. # # Returns false if the document is either an asset file or a yaml file, diff --git a/lib/jekyll/metadata.rb b/lib/jekyll/regenerator.rb similarity index 76% rename from lib/jekyll/metadata.rb rename to lib/jekyll/regenerator.rb index 953dd7c0..d03c0ced 100644 --- a/lib/jekyll/metadata.rb +++ b/lib/jekyll/regenerator.rb @@ -1,5 +1,5 @@ module Jekyll - class Metadata + class Regenerator attr_reader :site, :metadata, :cache def initialize(site) @@ -12,6 +12,25 @@ module Jekyll @cache = {} end + # Checks if a renderable object needs to be regenerated + # + # Returns a boolean. + def regenerate?(document) + case document + when Post, Page + document.asset_file? || document.data['regenerate'] || + modified?(site.in_source_dir(document.relative_path)) + when Document + !document.write? || document.data['regenerate'] || modified?(document.path) + else + if document.respond_to?(:path) + modified?(document.path) + else + true + end + end + end + # Add a path to the metadata # # Returns true, also on failure. @@ -40,10 +59,11 @@ module Jekyll @cache = {} end - # Checks if a path should be regenerated + # Checks if a path's (or one of its dependencies) + # mtime has changed # # Returns a boolean. - def regenerate?(path, add = true) + def modified?(path) return true if disabled? # Check for path in cache @@ -55,19 +75,19 @@ module Jekyll data = metadata[path] if data data["deps"].each do |dependency| - if regenerate?(dependency) + if modified?(dependency) return cache[dependency] = cache[path] = true end end if data["mtime"].eql? File.mtime(path) return cache[path] = false else - return !add || add(path) + return add(path) end end # Path does not exist in metadata, add it - return !add || add(path) + return add(path) end # Add a dependency of a path diff --git a/lib/jekyll/renderer.rb b/lib/jekyll/renderer.rb index e0701fd5..f25ca343 100644 --- a/lib/jekyll/renderer.rb +++ b/lib/jekyll/renderer.rb @@ -140,7 +140,7 @@ module Jekyll ) # Add layout to dependency tree - site.metadata.add_dependency( + site.regenerator.add_dependency( site.in_source_dir(document.path), site.in_source_dir(layout.path) ) if document.write? diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb index b6ab7039..8f9c902d 100644 --- a/lib/jekyll/site.rb +++ b/lib/jekyll/site.rb @@ -11,7 +11,7 @@ module Jekyll :gems, :plugin_manager attr_accessor :converters, :generators - attr_reader :metadata + attr_reader :regenerator # Public: Initialize a new Site. # @@ -28,8 +28,8 @@ module Jekyll @source = File.expand_path(config['source']).freeze @dest = File.expand_path(config['destination']).freeze - # Build metadata - @metadata = Metadata.new(self) + # Initialize incremental regenerator + @regenerator = Regenerator.new(self) self.plugin_manager = Jekyll::PluginManager.new(self) self.plugins = plugin_manager.plugins_path @@ -295,13 +295,17 @@ module Jekyll payload = site_payload collections.each do |label, collection| collection.docs.each do |document| - document.output = Jekyll::Renderer.new(self, document, payload).run if document.regenerate? + if regenerator.regenerate?(document) + document.output = Jekyll::Renderer.new(self, document, payload).run + end end end payload = site_payload [posts, pages].flatten.each do |page_or_post| - page_or_post.render(layouts, payload) if page_or_post.regenerate? + if regenerator.regenerate?(page_or_post) + page_or_post.render(layouts, payload) + end end rescue Errno::ENOENT # ignore missing layout dir @@ -319,9 +323,9 @@ module Jekyll # Returns nothing. def write each_site_file { |item| - item.write(dest) if item.regenerate? + item.write(dest) if regenerator.regenerate?(item) } - metadata.write unless full_rebuild? + regenerator.write unless full_rebuild? end # Construct a Hash of Posts indexed by the specified Post attribute. @@ -487,7 +491,7 @@ module Jekyll @frontmatter_defaults ||= FrontmatterDefaults.new(self) end - # Whether to perform a full rebuild without metadata + # Whether to perform a full rebuild without incremental regeneration # # Returns a Boolean: true for a full rebuild, false for normal build def full_rebuild?(override = {}) diff --git a/lib/jekyll/static_file.rb b/lib/jekyll/static_file.rb index a8b09afc..b83f4fae 100644 --- a/lib/jekyll/static_file.rb +++ b/lib/jekyll/static_file.rb @@ -65,8 +65,6 @@ module Jekyll true end - alias_method :regenerate?, :write? - # Write the static file to the destination directory (if modified). # # dest - The String path to the destination dir. diff --git a/lib/jekyll/tags/include.rb b/lib/jekyll/tags/include.rb index 16da6f49..c09460ff 100644 --- a/lib/jekyll/tags/include.rb +++ b/lib/jekyll/tags/include.rb @@ -116,7 +116,7 @@ eos # Add include to dependency tree if context.registers[:page] and context.registers[:page].has_key? "path" - site.metadata.add_dependency( + site.regenerator.add_dependency( site.in_source_dir(context.registers[:page]["path"]), path ) diff --git a/test/test_metadata.rb b/test/test_metadata.rb deleted file mode 100644 index 858b166a..00000000 --- a/test/test_metadata.rb +++ /dev/null @@ -1,140 +0,0 @@ -require 'helper' - -class TestMetadata < Test::Unit::TestCase - context "The site metadata" do - setup do - FileUtils.rm_rf(source_dir(".jekyll-metadata")) - - @site = Site.new(Jekyll.configuration({ - "source" => source_dir, - "destination" => dest_dir - })) - - @site.process - @path = @site.in_source_dir(@site.pages.first.path) - @metadata = @site.metadata - end - - should "store modification times" do - assert_equal File.mtime(@path), @metadata.metadata[@path]["mtime"] - end - - should "cache processed entries" do - assert @metadata.cache[@path] - end - - should "write to the metadata file" do - @metadata.clear - @metadata.add(@path) - @metadata.write - assert File.file?(source_dir(".jekyll-metadata")) - end - - should "read from the metadata file" do - @metadata = Metadata.new(@site) - assert_equal File.mtime(@path), @metadata.metadata[@path]["mtime"] - end - - # Methods - - should "be able to add a path to the metadata" do - @metadata.clear - @metadata.add(@path) - assert_equal File.mtime(@path), @metadata.metadata[@path]["mtime"] - assert_equal [], @metadata.metadata[@path]["deps"] - assert @metadata.cache[@path] - end - - should "return true on nonexistent path" do - @metadata.clear - assert @metadata.add("/bogus/path.md") - assert @metadata.regenerate?("/bogus/path.md") - end - - should "be able to force a path to regenerate" do - @metadata.clear - @metadata.force(@path) - assert @metadata.cache[@path] - assert @metadata.regenerate?(@path) - end - - should "be able to clear metadata and cache" do - @metadata.clear - @metadata.add(@path) - assert_equal 1, @metadata.metadata.length - assert_equal 1, @metadata.cache.length - @metadata.clear - assert_equal 0, @metadata.metadata.length - assert_equal 0, @metadata.cache.length - end - - should "not regenerate a path if it is not modified" do - @metadata.clear - @metadata.add(@path) - @metadata.write - @metadata = Metadata.new(@site) - - assert !@metadata.regenerate?(@path) - end - - should "not regenerate if path in cache is false" do - @metadata.clear - @metadata.add(@path) - @metadata.write - @metadata = Metadata.new(@site) - - assert !@metadata.regenerate?(@path) - assert !@metadata.cache[@path] - assert !@metadata.regenerate?(@path) - end - - should "regenerate if path in not in metadata" do - @metadata.clear - @metadata.add(@path) - - assert @metadata.regenerate?(@path) - end - - should "regenerate if path in cache is true" do - @metadata.clear - @metadata.add(@path) - - assert @metadata.regenerate?(@path) - assert @metadata.cache[@path] - assert @metadata.regenerate?(@path) - end - - should "regenerate if file is modified" do - @metadata.clear - @metadata.add(@path) - @metadata.metadata[@path]["mtime"] = Time.at(0) - @metadata.write - @metadata = Metadata.new(@site) - - assert_not_same File.mtime(@path), @metadata.metadata[@path]["mtime"] - assert @metadata.regenerate?(@path) - end - - should "regenerate if dependency is modified" do - @metadata.clear - @metadata.add(@path) - @metadata.write - @metadata = Metadata.new(@site) - - @metadata.add_dependency(@path, "new.dependency") - assert_equal ["new.dependency"], @metadata.metadata[@path]["deps"] - assert @metadata.regenerate?("new.dependency") - assert @metadata.regenerate?(@path) - end - - should "regenerate everything if metadata is disabled" do - @site.config["full_rebuild"] = true - @metadata.clear - @metadata.add(@path) - @metadata.write - @metadata = Metadata.new(@site) - - assert @metadata.regenerate?(@path) - end - end -end diff --git a/test/test_regenerator.rb b/test/test_regenerator.rb new file mode 100644 index 00000000..74a6bb4f --- /dev/null +++ b/test/test_regenerator.rb @@ -0,0 +1,193 @@ +require 'helper' + +class TestRegenerator < Test::Unit::TestCase + context "The site regenerator" do + setup do + FileUtils.rm_rf(source_dir(".jekyll-metadata")) + + @site = Site.new(Jekyll.configuration({ + "source" => source_dir, + "destination" => dest_dir, + "collections" => { + "methods" => { + "output" => true + } + } + })) + + @site.read + @page = @site.pages.first + @post = @site.posts.first + @document = @site.docs_to_write.first + @asset_file = @site.pages.find(&:asset_file?) + @regenerator = @site.regenerator + end + + should "regenerate documents and assets if changed or not in metadata" do + assert @regenerator.regenerate?(@page) + assert @regenerator.regenerate?(@post) + assert @regenerator.regenerate?(@document) + assert @regenerator.regenerate?(@asset_file) + end + + should "not regenerate if not changed" do + # Process files + @regenerator.regenerate?(@page) + @regenerator.regenerate?(@post) + @regenerator.regenerate?(@document) + @regenerator.regenerate?(@asset_file) + + @regenerator.write + @regenerator = Regenerator.new(@site) + + assert !@regenerator.regenerate?(@page) + assert !@regenerator.regenerate?(@post) + assert !@regenerator.regenerate?(@document) + end + + should "always regenerate asset files" do + assert @regenerator.regenerate?(@asset_file) + end + + should "always regenerate objects that don't respond to :path" do + assert @regenerator.regenerate?(Object.new) + end + end + + context "The site metadata" do + setup do + FileUtils.rm_rf(source_dir(".jekyll-metadata")) + + @site = Site.new(Jekyll.configuration({ + "source" => source_dir, + "destination" => dest_dir + })) + + @site.process + @path = @site.in_source_dir(@site.pages.first.path) + @regenerator = @site.regenerator + end + + should "store modification times" do + assert_equal File.mtime(@path), @regenerator.metadata[@path]["mtime"] + end + + should "cache processed entries" do + assert @regenerator.cache[@path] + end + + should "write to the metadata file" do + @regenerator.clear + @regenerator.add(@path) + @regenerator.write + assert File.file?(source_dir(".jekyll-metadata")) + end + + should "read from the metadata file" do + @regenerator = Regenerator.new(@site) + assert_equal File.mtime(@path), @regenerator.metadata[@path]["mtime"] + end + + # Methods + + should "be able to add a path to the metadata" do + @regenerator.clear + @regenerator.add(@path) + assert_equal File.mtime(@path), @regenerator.metadata[@path]["mtime"] + assert_equal [], @regenerator.metadata[@path]["deps"] + assert @regenerator.cache[@path] + end + + should "return true on nonexistent path" do + @regenerator.clear + assert @regenerator.add("/bogus/path.md") + assert @regenerator.modified?("/bogus/path.md") + end + + should "be able to force a path to regenerate" do + @regenerator.clear + @regenerator.force(@path) + assert @regenerator.cache[@path] + assert @regenerator.modified?(@path) + end + + should "be able to clear metadata and cache" do + @regenerator.clear + @regenerator.add(@path) + assert_equal 1, @regenerator.metadata.length + assert_equal 1, @regenerator.cache.length + @regenerator.clear + assert_equal 0, @regenerator.metadata.length + assert_equal 0, @regenerator.cache.length + end + + should "not regenerate a path if it is not modified" do + @regenerator.clear + @regenerator.add(@path) + @regenerator.write + @regenerator = Regenerator.new(@site) + + assert !@regenerator.modified?(@path) + end + + should "not regenerate if path in cache is false" do + @regenerator.clear + @regenerator.add(@path) + @regenerator.write + @regenerator = Regenerator.new(@site) + + assert !@regenerator.modified?(@path) + assert !@regenerator.cache[@path] + assert !@regenerator.modified?(@path) + end + + should "regenerate if path in not in metadata" do + @regenerator.clear + @regenerator.add(@path) + + assert @regenerator.modified?(@path) + end + + should "regenerate if path in cache is true" do + @regenerator.clear + @regenerator.add(@path) + + assert @regenerator.modified?(@path) + assert @regenerator.cache[@path] + assert @regenerator.modified?(@path) + end + + should "regenerate if file is modified" do + @regenerator.clear + @regenerator.add(@path) + @regenerator.metadata[@path]["mtime"] = Time.at(0) + @regenerator.write + @regenerator = Regenerator.new(@site) + + assert_not_same File.mtime(@path), @regenerator.metadata[@path]["mtime"] + assert @regenerator.modified?(@path) + end + + should "regenerate if dependency is modified" do + @regenerator.clear + @regenerator.add(@path) + @regenerator.write + @regenerator = Regenerator.new(@site) + + @regenerator.add_dependency(@path, "new.dependency") + assert_equal ["new.dependency"], @regenerator.metadata[@path]["deps"] + assert @regenerator.modified?("new.dependency") + assert @regenerator.modified?(@path) + end + + should "regenerate everything if metadata is disabled" do + @site.config["full_rebuild"] = true + @regenerator.clear + @regenerator.add(@path) + @regenerator.write + @regenerator = Regenerator.new(@site) + + assert @regenerator.modified?(@path) + end + end +end diff --git a/test/test_site.rb b/test/test_site.rb index cf5ee9bb..1fe86b76 100644 --- a/test/test_site.rb +++ b/test/test_site.rb @@ -99,7 +99,7 @@ class TestSite < Test::Unit::TestCase should "write only modified static files" do clear_dest StaticFile.reset_cache - @site.metadata.clear + @site.regenerator.clear @site.process some_static_file = @site.static_files[0].path @@ -129,7 +129,7 @@ class TestSite < Test::Unit::TestCase should "write static files if not modified but missing in destination" do clear_dest StaticFile.reset_cache - @site.metadata.clear + @site.regenerator.clear @site.process dest = File.expand_path(@site.static_files[0].destination(@site.dest)) @@ -243,7 +243,7 @@ class TestSite < Test::Unit::TestCase context 'with orphaned files in destination' do setup do clear_dest - @site.metadata.clear + @site.regenerator.clear @site.process # generate some orphaned files: # single file