diff --git a/lib/jekyll/static_file.rb b/lib/jekyll/static_file.rb index 7dfd6b97..01f8ee62 100644 --- a/lib/jekyll/static_file.rb +++ b/lib/jekyll/static_file.rb @@ -1,6 +1,8 @@ module Jekyll class StaticFile + @@mtimes = Hash.new # the cache of last modification times [path] -> mtime + # Initialize a new StaticFile. # +site+ is the Site # +base+ is the String path to the @@ -15,13 +17,59 @@ module Jekyll @name = name end - # Write the static file to the destination directory. + # Obtains source file path. + # + # Returns source file path. + def path + File.join(@base, @dir, @name) + end + + # Obtain destination path. # +dest+ is the String path to the destination dir # - # Returns nothing + # Returns destination file path. + def destination(dest) + File.join(dest, @dir, @name) + end + + # Obtain mtime of the source path. + # + # Returns last modifiaction time for this file. + def mtime + File.stat(path).mtime.to_i + end + + # Is source path modified? + # + # Returns true if modified since last write. + def modified? + @@mtimes[path] != mtime + end + + # Write the static file to the destination directory (if modified). + # +dest+ is the String path to the destination dir + # + # Returns false if the file was not modified since last time (no-op). def write(dest) - FileUtils.mkdir_p(File.join(dest, @dir)) - FileUtils.cp(File.join(@base, @dir, @name), File.join(dest, @dir, @name)) + dest_path = destination(dest) + dest_dir = File.join(dest, @dir) + + return false if File.exist? dest_path and !modified? + @@mtimes[path] = mtime + + FileUtils.mkdir_p(dest_dir) + FileUtils.cp(path, dest_path) + + true + end + + # Reset the mtimes cache (for testing purposes). + # + # Returns nothing. + def self.reset_cache + @@mtimes = Hash.new + + nil end end diff --git a/test/test_site.rb b/test/test_site.rb index 705d0d09..09654ab9 100644 --- a/test/test_site.rb +++ b/test/test_site.rb @@ -34,6 +34,64 @@ class TestSite < Test::Unit::TestCase assert before_time <= @site.time end + should "write only modified static files" do + clear_dest + StaticFile.reset_cache + + @site.process + some_static_file = @site.static_files[0].path + dest = File.expand_path(@site.static_files[0].destination(@site.dest)) + mtime1 = File.stat(dest).mtime.to_i # first run must generate dest file + + # need to sleep because filesystem timestamps have best resolution in seconds + sleep 1 + @site.process + mtime2 = File.stat(dest).mtime.to_i + assert_equal mtime1, mtime2 + + # simulate file modification by user + FileUtils.touch some_static_file + + sleep 1 + @site.process + mtime3 = File.stat(dest).mtime.to_i + assert_not_equal mtime2, mtime3 # must be regenerated! + + sleep 1 + @site.process + mtime4 = File.stat(dest).mtime.to_i + assert_equal mtime3, mtime4 # no modifications, so must be the same + end + + should "write static files if not modified but missing in destination" do + clear_dest + StaticFile.reset_cache + + @site.process + some_static_file = @site.static_files[0].path + dest = File.expand_path(@site.static_files[0].destination(@site.dest)) + mtime1 = File.stat(dest).mtime.to_i # first run must generate dest file + + # need to sleep because filesystem timestamps have best resolution in seconds + sleep 1 + @site.process + mtime2 = File.stat(dest).mtime.to_i + assert_equal mtime1, mtime2 + + # simulate destination file deletion + File.unlink dest + + sleep 1 + @site.process + mtime3 = File.stat(dest).mtime.to_i + assert_not_equal mtime2, mtime3 # must be regenerated and differ! + + sleep 1 + @site.process + mtime4 = File.stat(dest).mtime.to_i + assert_equal mtime3, mtime4 # no modifications, so must be the same + end + should "read layouts" do @site.read_layouts assert_equal ["default", "simple"].sort, @site.layouts.keys.sort