lib/nanoc3/base/site.rb in nanoc3-3.1.9 vs lib/nanoc3/base/site.rb in nanoc3-3.2.0a1
- old
+ new
@@ -37,17 +37,20 @@
# The default configuration for a site. A site's configuration overrides
# these options: when a {Nanoc3::Site} is created with a configuration
# that lacks some options, the default value will be taken from
# `DEFAULT_CONFIG`.
DEFAULT_CONFIG = {
- :text_extensions => %w( css erb haml htm html js less markdown md php rb sass scss txt xhtml xml ),
+ :text_extensions => %w( css erb haml htm html js less markdown md php rb sass txt xml ),
:output_dir => 'output',
:data_sources => [ {} ],
:index_filenames => [ 'index.html' ],
:enable_output_diff => false
}
+ # The name of the file where checksums will be stored.
+ CHECKSUMS_FILE_NAME = 'tmp/checksums'
+
# The site configuration. The configuration has the following keys:
#
# * `text_extensions` ({Array<String>}) - A list of file extensions that
# will cause nanoc to threat the file as textual instead of binary. When
# the data source finds a content file with an extension that is
@@ -87,27 +90,37 @@
# would need the username of the account from which to fetch tweets.
#
# @return [Hash] The site configuration
attr_reader :config
- # @return [Time] The timestamp when the site configuration was last
- # modified
- attr_reader :config_mtime
+ # @return [String] The checksum of the site configuration that was in
+ # effect during the previous site compilation
+ attr_accessor :old_config_checksum
- # @return [Time] The timestamp when the rules were last modified
- attr_reader :rules_mtime
+ # @return [String] The current, up-to-date checksum of the site
+ # configuration
+ attr_reader :new_config_checksum
+ # @return [String] The checksum of the rules file that was in effect
+ # during the previous site compilation
+ attr_accessor :old_rules_checksum
+
+ # @return [String] The current, up-to-date checksum of the rules file
+ attr_reader :new_rules_checksum
+
# @return [Proc] The code block that will be executed after all data is
- # loaded but before the site is compiled
+ # loaded but before the site is compiled
attr_accessor :preprocessor
# Creates a site object for the site specified by the given
# `dir_or_config_hash` argument.
#
# @param [Hash, String] dir_or_config_hash If a string, contains the path
- # to the site directory; if a hash, contains the site configuration.
+ # to the site directory; if a hash, contains the site configuration.
def initialize(dir_or_config_hash)
+ @new_checksums = {}
+
build_config(dir_or_config_hash)
@code_snippets_loaded = false
@items_loaded = false
@layouts_loaded = false
@@ -123,14 +136,14 @@
# Returns the data sources for this site. Will create a new data source if
# none exists yet.
#
# @return [Array<Nanoc3::DataSource>] The list of data sources for this
- # site
+ # site
#
# @raise [Nanoc3::Errors::UnknownDataSource] if the site configuration
- # specifies an unknown data source
+ # specifies an unknown data source
def data_sources
@data_sources ||= begin
@config[:data_sources].map do |data_source_hash|
# Get data source class
data_source_class = Nanoc3::DataSource.named(data_source_hash[:type])
@@ -160,11 +173,11 @@
# with the site and fetch all site data. The site data is cached, so
# calling this method will not have any effect the second time, unless
# the `force` parameter is true.
#
# @param [Boolean] force If true, will force load the site data even if it
- # has been loaded before, to circumvent caching issues
+ # has been loaded before, to circumvent caching issues
#
# @return [void]
def load_data(force=false)
# Don't load data twice
return if instance_variable_defined?(:@data_loaded) && @data_loaded && !force
@@ -190,41 +203,65 @@
end
# Returns this site’s code snippets.
#
# @return [Array<Nanoc3::CodeSnippet>] The list of code snippets in this
- # site
+ # site
#
# @raise [Nanoc3::Errors::DataNotYetAvailable] if the site data hasn’t
- # been loaded yet (call {#load_data} to load the site data)
+ # been loaded yet (call {#load_data} to load the site data)
def code_snippets
raise Nanoc3::Errors::DataNotYetAvailable.new('Code snippets', false) unless @code_snippets_loaded
@code_snippets
end
# Returns this site’s items.
#
# @return [Array<Nanoc3::Item>] The list of items in this site
#
# @raise [Nanoc3::Errors::DataNotYetAvailable] if the site data hasn’t
- # been loaded yet (call {#load_data} to load the site data)
+ # been loaded yet (call {#load_data} to load the site data)
def items
raise Nanoc3::Errors::DataNotYetAvailable.new('Items', true) unless @items_loaded
@items
end
# Returns this site’s layouts.
#
# @return [Array<Nanoc3::Layouts>] The list of layout in this site
#
# @raise [Nanoc3::Errors::DataNotYetAvailable] if the site data hasn’t
- # been loaded yet (call {#load_data} to load the site data)
+ # been loaded yet (call {#load_data} to load the site data)
def layouts
raise Nanoc3::Errors::DataNotYetAvailable.new('Layouts', true) unless @layouts_loaded
@layouts
end
+ # Stores the checksums into the checksums file.
+ #
+ # @return [void]
+ def store_checksums
+ # Store
+ FileUtils.mkdir_p(File.dirname(CHECKSUMS_FILE_NAME))
+ store = PStore.new(CHECKSUMS_FILE_NAME)
+ store.transaction do
+ store[:checksums] = @new_checksums || {}
+ end
+ end
+
+ # @return [Boolean] true if the site configuration was modified since the
+ # site was last compiled, false otherwise
+ def config_outdated?
+ !self.old_config_checksum || !self.new_config_checksum || self.old_config_checksum != self.new_config_checksum
+ end
+
+ # @return [Boolean] true if the rules were modified since the site was
+ # last compiled, false otherwise
+ def rules_outdated?
+ !self.old_rules_checksum || !self.new_rules_checksum || self.old_rules_checksum != self.new_rules_checksum
+ end
+
private
# Returns the Nanoc3::CompilerDSL that should be used for this site.
def dsl
@dsl ||= Nanoc3::CompilerDSL.new(self)
@@ -238,14 +275,20 @@
# Get code snippets
@code_snippets = Dir['lib/**/*.rb'].sort.map do |filename|
Nanoc3::CodeSnippet.new(
File.read(filename),
filename,
- File.stat(filename).mtime
+ :checksum => Nanoc3::Checksummer.checksum_for(filename)
)
end
+ # Set checksums
+ @code_snippets.each do |cs|
+ cs.old_checksum = old_checksum_for(:code_snippet, cs.filename)
+ @new_checksums[ [ :code_snippet, cs.filename ] ] = cs.new_checksum
+ end
+
# Execute code snippets
@code_snippets.each { |cs| cs.load }
@code_snippets_loaded = true
end
@@ -255,12 +298,14 @@
# Find rules file
rules_filename = [ 'Rules', 'rules', 'Rules.rb', 'rules.rb' ].find { |f| File.file?(f) }
raise Nanoc3::Errors::NoRulesFileFound.new if rules_filename.nil?
# Get rule data
- @rules = File.read(rules_filename)
- @rules_mtime = File.stat(rules_filename).mtime
+ @rules = File.read(rules_filename)
+ @new_rules_checksum = Nanoc3::Checksummer.checksum_for(rules_filename)
+ @old_rules_checksum = old_checksum_for(:misc, 'Rules')
+ @new_checksums[ [ :misc, 'Rules' ] ] = @new_rules_checksum
# Load DSL
dsl.instance_eval(@rules, "./#{rules_filename}")
end
@@ -272,10 +317,16 @@
items_in_ds = ds.items
items_in_ds.each { |i| i.identifier = File.join(ds.items_root, i.identifier) }
@items.concat(items_in_ds)
end
+ # Set checksums
+ @items.each do |i|
+ i.old_checksum = old_checksum_for(:item, i.identifier)
+ @new_checksums[ [ :item, i.identifier ] ] = i.new_checksum
+ end
+
@items_loaded = true
end
# Loads this site’s layouts.
def load_layouts
@@ -284,10 +335,16 @@
layouts_in_ds = ds.layouts
layouts_in_ds.each { |i| i.identifier = File.join(ds.layouts_root, i.identifier) }
@layouts.concat(layouts_in_ds)
end
+ # Set checksums
+ @layouts.each do |l|
+ l.old_checksum = old_checksum_for(:layout, l.identifier)
+ @new_checksums[ [ :layout, l.identifier ] ] = l.new_checksum
+ end
+
@layouts_loaded = true
end
# Links items, layouts and code snippets to the site.
def link_everything_to_site
@@ -335,56 +392,55 @@
# Determines the paths of all item representations.
def route_reps
reps = @items.map { |i| i.reps }.flatten
reps.each do |rep|
- # Find matching rule
- rule = self.compiler.routing_rule_for(rep)
- raise Nanoc3::Errors::NoMatchingRoutingRuleFound.new(rep) if rule.nil?
+ # Find matching rules
+ rules = self.compiler.routing_rules_for(rep)
+ raise Nanoc3::Errors::NoMatchingRoutingRuleFound.new(rep) if rules[:last].nil?
- # Get basic path by applying matching rule
- basic_path = rule.apply_to(rep)
- next if basic_path.nil?
- if basic_path !~ %r{^/}
- raise RuntimeError, "The path returned for the #{rep.inspect} item representation, “#{basic_path}”, does not start with a slash. Please ensure that all routing rules return a path that starts with a slash.".make_compatible_with_env
- end
+ rules.each_pair do |snapshot, rule|
+ # Get basic path by applying matching rule
+ basic_path = rule.apply_to(rep)
+ next if basic_path.nil?
- # Get raw path by prepending output directory
- rep.raw_path = self.config[:output_dir] + basic_path
+ # Get raw path by prepending output directory
+ rep.raw_paths[snapshot] = self.config[:output_dir] + basic_path
- # Get normal path by stripping index filename
- rep.path = basic_path
- self.config[:index_filenames].each do |index_filename|
- if rep.path[-index_filename.length..-1] == index_filename
- # Strip and stop
- rep.path = rep.path[0..-index_filename.length-1]
- break
+ # Get normal path by stripping index filename
+ rep.paths[snapshot] = basic_path
+ self.config[:index_filenames].each do |index_filename|
+ if rep.paths[snapshot][-index_filename.length..-1] == index_filename
+ # Strip and stop
+ rep.paths[snapshot] = rep.paths[snapshot][0..-index_filename.length-1]
+ break
+ end
end
end
end
end
# Builds the configuration hash based on the given argument. Also see
- # #initialize for details.
+ # {#initialize} for details.
def build_config(dir_or_config_hash)
if dir_or_config_hash.is_a? String
- # Check whether it is supported
- if dir_or_config_hash != '.'
- warn 'WARNING: Calling Nanoc3::Site.new with a directory that is not the current working directory is not supported. It is recommended to change the directory before calling Nanoc3::Site.new. For example, instead of Nanoc3::Site.new(\'abc\'), use Dir.chdir(\'abc\') { Nanoc3::Site.new(\'.\') }.'
- end
-
# Read config from config.yaml in given dir
config_path = File.join(dir_or_config_hash, 'config.yaml')
@config = DEFAULT_CONFIG.merge(YAML.load_file(config_path).symbolize_keys)
@config[:data_sources].map! { |ds| ds.symbolize_keys }
- @config_mtime = File.stat(config_path).mtime
+
+ @new_config_checksum = Nanoc3::Checksummer.checksum_for('config.yaml')
+ @new_checksums[ [ :misc, 'config.yaml' ] ] = @new_config_checksum
else
# Use passed config hash
@config = DEFAULT_CONFIG.merge(dir_or_config_hash)
- @config_mtime = nil
+ @new_config_checksum = nil
end
+ # Build checksum
+ @old_config_checksum = old_checksum_for(:misc, 'config.yaml')
+
# Merge data sources with default data source config
@config[:data_sources].map! { |ds| DEFAULT_DATA_SOURCE_CONFIG.merge(ds) }
end
# Returns a preprocessor context, creating one if none exists yet.
@@ -393,9 +449,41 @@
:site => self,
:config => self.config,
:items => self.items,
:layouts => self.layouts
})
+ end
+
+ # Returns the checksums, loads the checksums from the cached checksums
+ # file first if necessary. The checksums returned is a hash in th following
+ # format:
+ #
+ # {
+ # [ :layout, '/identifier/' ] => checksum,
+ # [ :item, '/identifier/' ] => checksum,
+ # [ :code_snippet, 'lib/filename.rb' ] => checksum,
+ # }
+ def checksums
+ return @checksums if @checksums_loaded
+
+ if !File.file?(CHECKSUMS_FILE_NAME)
+ @checksums = {}
+ else
+ require 'pstore'
+ store = PStore.new(CHECKSUMS_FILE_NAME)
+ store.transaction do
+ @checksums = store[:checksums] || {}
+ end
+ end
+
+ @checksums_loaded = true
+ @checksums
+ end
+
+ # Returns the old checksum for the given object.
+ def old_checksum_for(type, identifier)
+ key = [ type, identifier ]
+ checksums[key]
end
end
end