lib/nanoc3/base/item_rep.rb in nanoc3-3.1.0a2 vs lib/nanoc3/base/item_rep.rb in nanoc3-3.1.0a3
- old
+ new
@@ -25,63 +25,69 @@
# @return [Symbol] The representation's unique name
attr_reader :name
# @return [Boolean] true if this rep is forced to be dirty (e.g. because
- # of the `--force` commandline option); false otherwise
+ # of the `--force` commandline option); false otherwise
attr_accessor :force_outdated
# @return [Boolean] true if this rep’s output file has changed since the
- # last time it was compiled; false otherwise
+ # last time it was compiled; false otherwise
attr_accessor :modified
alias_method :modified?, :modified
# @return [Boolean] true if this rep’s output file was created during the
- # current or last compilation session; false otherwise
+ # current or last compilation session; false otherwise
attr_accessor :created
alias_method :created?, :created
- # @return [Boolean] true if this representation has already been
- # compiled during the current or last compilation session; false
- # otherwise
+ # @return [Boolean] true if this representation has already been compiled
+ # during the current or last compilation session; false otherwise
attr_accessor :compiled
alias_method :compiled?, :compiled
# @return [Boolean] true if this representation’s compiled content has
- # been written during the current or last compilation session; false
- # otherwise
+ # been written during the current or last compilation session; false
+ # otherwise
attr_reader :written
alias_method :written?, :written
# @return [String] The item rep's path, as used when being linked to. It
- # starts with a slash and it is relative to the output directory. It
- # does not include the path to the output directory. It will not include
- # the filename if the filename is an index filename.
+ # starts with a slash and it is relative to the output directory. It does
+ # not include the path to the output directory. It will not include the
+ # filename if the filename is an index filename.
attr_accessor :path
# @return [String] The item rep's raw path. It is relative to the current
- # working directory and includes the path to the output directory. It
- # also includes the filename, even if it is an index filename.
+ # working directory and includes the path to the output directory. It also
+ # includes the filename, even if it is an index filename.
attr_accessor :raw_path
# Creates a new item representation for the given item.
#
# @param [Nanoc3::Item] item The item to which the new representation will
- # belong.
+ # belong.
#
# @param [Symbol] name The unique name for the new item representation.
def initialize(item, name)
# Set primary attributes
@item = item
@name = name
- # Initialize content
- @content = {
- :raw => @item.raw_content,
- :last => @item.raw_content,
- :pre => @item.raw_content
- }
+ # Initialize content and filenames
+ if @item.binary?
+ @filenames = {
+ :raw => @item.raw_filename,
+ :last => @item.raw_filename
+ }
+ else
+ @content = {
+ :raw => @item.raw_content,
+ :last => @item.raw_content,
+ :pre => @item.raw_content
+ }
+ end
@old_content = nil
# Reset flags
@compiled = false
@modified = false
@@ -89,11 +95,11 @@
@written = false
@force_outdated = false
end
# @return [Boolean] true if this item rep's output file is outdated and
- # must be regenerated, false otherwise
+ # must be regenerated, false otherwise
def outdated?
# Outdated if we don't know
return true if @item.mtime.nil?
# Outdated if the dependency tracker says so
@@ -129,30 +135,44 @@
return false
end
# @return [Hash] The assignments that should be available when compiling
- # the content.
+ # the content.
def assigns
- {
- :content => @content[:last],
+ if item.binary?
+ content_or_filename_assigns = { :filename => @filenames[:last] }
+ else
+ content_or_filename_assigns = { :content => @content[:last] }
+ end
+
+ content_or_filename_assigns.merge({
:item => self.item,
:item_rep => self,
:items => self.item.site.items,
:layouts => self.item.site.layouts,
:config => self.item.site.config,
:site => self.item.site
- }
+ })
end
# Returns the compiled content from a given snapshot.
#
- # @option params [String] :snapshot The name of the snapshot from
- # which to fetch the compiled content. By default, the returned compiled
- # content will be the content compiled right before the first layout
- # call (if any).
+ # @option params [String] :snapshot The name of the snapshot from which to
+ # fetch the compiled content. By default, the returned compiled content
+ # will be the content compiled right before the first layout call (if
+ # any).
+ #
+ # @return [String] The compiled content at the given snapshot (or the
+ # default snapshot if no snapshot is specified)
def compiled_content(params={})
+ # Check whether content can be fetched
+ # TODO get proper exception
+ if @item.binary?
+ raise RuntimeError, "attempted to fetch compiled content from a binary item"
+ end
+
# Notify
Nanoc3::NotificationCenter.post(:visit_started, self.item)
Nanoc3::NotificationCenter.post(:visit_ended, self.item)
# Debug
@@ -184,43 +204,58 @@
#
# This method is supposed to be called only in a compilation rule block
# (see {Nanoc3::CompilerDSL#compile}).
#
# @param [Symbol] filter_name The name of the filter to run the item
- # representations' content through
+ # representations' content through
#
# @param [Hash] filter_args The filter arguments that should be passed to
- # the filter's #run method
+ # the filter's #run method
#
# @return [void]
def filter(filter_name, filter_args={})
# Create filter
klass = Nanoc3::Filter.named(filter_name)
raise Nanoc3::Errors::UnknownFilter.new(filter_name) if klass.nil?
filter = klass.new(assigns)
+ # Check whether filter can be applied
+ if klass.binary? && !item.binary?
+ raise Nanoc3::Errors::CannotUseBinaryFilter.new(self, klass)
+ elsif !klass.binary? && item.binary?
+ raise Nanoc3::Errors::CannotUseTextualFilter.new(self, klass)
+ end
+
# Run filter
Nanoc3::NotificationCenter.post(:filtering_started, self, filter_name)
- @content[:last] = filter.run(@content[:last], filter_args)
+ if item.binary?
+ filter.run(@filenames[:last], filter_args)
+ @filenames[:last] = filter.output_filename
+ else
+ @content[:last] = filter.run(@content[:last], filter_args)
+ end
Nanoc3::NotificationCenter.post(:filtering_ended, self, filter_name)
# Create snapshot
- snapshot(@content[:post] ? :post : :pre)
+ snapshot(@content[:post] ? :post : :pre) unless item.binary?
end
# Lays out the item using the given layout. This method will replace the
# content of the `:last` snapshot with the laid out content of the last
# snapshot.
#
# This method is supposed to be called only in a compilation rule block
# (see {Nanoc3::CompilerDSL#compile}).
#
- # @param [String] layout_identifier The identifier of the layout the ite
- # should be laid out with
+ # @param [String] layout_identifier The identifier of the layout the item
+ # should be laid out with
#
# @return [void]
def layout(layout_identifier)
+ # Check whether item can be laid out
+ raise Nanoc3::Errors::CannotLayoutBinaryItem.new(self) if item.binary?
+
# Create "pre" snapshot
snapshot(:pre) unless @content[:pre]
# Create filter
layout = layout_with_identifier(layout_identifier)
@@ -241,11 +276,12 @@
#
# @param [Symbol] snapshot_name The name of the snapshot to create
#
# @return [void]
def snapshot(snapshot_name)
- @content[snapshot_name] = @content[:last]
+ target = @item.binary? ? @filenames : @content
+ target[snapshot_name] = target[:last]
end
# Writes the item rep's compiled content to the rep's output file.
#
# This method should not be called directly, even in a compilation block;
@@ -257,41 +293,62 @@
FileUtils.mkdir_p(File.dirname(self.raw_path))
# Check if file will be created
@created = !File.file?(self.raw_path)
- # Remember old content
- if File.file?(self.raw_path)
- @old_content = File.read(self.raw_path)
- end
+ if @item.binary?
+ # Calculate hash of old content
+ if File.file?(self.raw_path)
+ hash_old = hash(self.raw_path)
+ size_old = File.size(self.raw_path)
+ end
- # Write
- File.open(self.raw_path, 'w') { |io| io.write(@content[:last]) }
- @written = true
+ # Copy
+ FileUtils.cp(@filenames[:last], self.raw_path)
+ @written = true
- # Check if file was modified
- @modified = File.read(self.raw_path) != @old_content
+ # Check if file was modified
+ size_new = File.size(self.raw_path)
+ hash_new = hash(self.raw_path) if size_old == size_new
+ @modified = (size_old != size_new || hash_old != hash_new)
+ else
+ # Remember old content
+ if File.file?(self.raw_path)
+ @old_content = File.read(self.raw_path)
+ end
+
+ # Write
+ File.open(self.raw_path, 'w') { |io| io.write(@content[:last]) }
+ @written = true
+
+ # Check if file was modified
+ @modified = File.read(self.raw_path) != @old_content
+ end
end
# Creates and returns a diff between the compiled content before the
# current compilation session and the content compiled in the current
# compilation session.
#
# @return [String, nil] The difference between the old and new compiled
- # content in `diff(1)` format, or nil if there is no previous compiled
- # content
+ # content in `diff(1)` format, or nil if there is no previous compiled
+ # content
def diff
+ # Check if content can be diffed
+ # TODO allow binary diffs
+ return nil if @item.binary?
+
# Check if old content exists
if @old_content.nil? or self.raw_path.nil?
nil
else
diff_strings(@old_content, @content[:last])
end
end
def inspect
- "<#{self.class}:0x#{self.object_id.to_s(16)} name=#{self.name} item.identifier=#{self.item.identifier}>"
+ "<#{self.class}:0x#{self.object_id.to_s(16)} name=#{self.name} item.identifier=#{self.item.identifier} item.binary?=#{@item.binary?}>"
end
private
def layout_with_identifier(layout_identifier)
@@ -338,9 +395,21 @@
result == '' ? nil : result
rescue Errno::ENOENT
warn 'Failed to run `diff`, so no diff with the previously compiled ' \
'content will be available.'
nil
+ end
+
+ # Returns a hash of the given filename
+ def hash(filename)
+ digest = Digest::SHA1.new
+ File.open(filename, 'r') do |io|
+ until io.eof
+ data = io.readpartial(2**10)
+ digest.update(data)
+ end
+ end
+ digest.hexdigest
end
end
end