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