module Nanoc::Int # Creates checksums for given objects. # # A checksum is a string, such as “mL+TaqNsEeiPkWloPgCtAofT1yg=”, that is used # to determine whether a piece of data has changed. # # @api private class Checksummer class VerboseDigest def initialize @str = '' end def update(str) @str << str end def to_s @str end end class CompactDigest def initialize @digest = Digest::SHA1.new end def update(str) @digest.update(str) end def to_s @digest.base64digest end end class << self # @param obj The object to create a checksum for # # @return [String] The digest def calc(obj, digest_class = CompactDigest) digest = digest_class.new update(obj, digest) digest.to_s end private def update(obj, digest, visited = Hamster::Set.new) digest.update(obj.class.to_s) if visited.include?(obj) digest.update('') else digest.update('<') behavior_for(obj).update(obj, digest) { |o| update(o, digest, visited.add(obj)) } digest.update('>') end end def behavior_for(obj) case obj when String, Symbol, Numeric RawUpdateBehavior when Pathname PathnameUpdateBehavior when Nanoc::Int::BinaryContent BinaryContentUpdateBehavior when Array, Nanoc::Int::IdentifiableCollection ArrayUpdateBehavior when Hash, Nanoc::Int::Configuration HashUpdateBehavior when Nanoc::Int::Item, Nanoc::Int::Layout DocumentUpdateBehavior when NilClass, TrueClass, FalseClass NoUpdateBehavior when Time ToIToSUpdateBehavior when Nanoc::Identifier ToSUpdateBehavior when Nanoc::RuleDSL::RulesCollection, Nanoc::Int::CodeSnippet DataUpdateBehavior when Nanoc::Int::TextualContent StringUpdateBehavior when Nanoc::View UnwrapUpdateBehavior else RescueUpdateBehavior end end end class UpdateBehavior def self.update(_obj, _digest) raise NotImpementedError end end class RawUpdateBehavior < UpdateBehavior def self.update(obj, digest) digest.update(obj.to_s) end end class ToSUpdateBehavior < UpdateBehavior def self.update(obj, _digest) yield(obj.to_s) end end class ToIToSUpdateBehavior < UpdateBehavior def self.update(obj, digest) digest.update(obj.to_i.to_s) end end class StringUpdateBehavior < UpdateBehavior def self.update(obj, _digest) yield(obj.string) end end class DataUpdateBehavior < UpdateBehavior def self.update(obj, _digest) yield(obj.data) end end class NoUpdateBehavior < UpdateBehavior def self.update(_obj, _digest) end end class UnwrapUpdateBehavior < UpdateBehavior def self.update(obj, _digest) yield(obj.unwrap) end end class ArrayUpdateBehavior < UpdateBehavior def self.update(obj, digest) obj.each do |el| yield(el) digest.update(',') end end end class HashUpdateBehavior < UpdateBehavior def self.update(obj, digest) obj.each do |key, value| yield(key) digest.update('=') yield(value) digest.update(',') end end end class DocumentUpdateBehavior < UpdateBehavior def self.update(obj, digest) if obj.checksum_data digest.update('checksum_data=' + obj.checksum_data) else digest.update('content=') yield(obj.content) digest.update(',attributes=') yield(obj.attributes) digest.update(',identifier=') yield(obj.identifier) end end end class PathnameUpdateBehavior < UpdateBehavior def self.update(obj, digest) filename = obj.to_s if File.exist?(filename) stat = File.stat(filename) digest.update(stat.size.to_s + '-' + stat.mtime.to_i.to_s) else digest.update('???') end end end class BinaryContentUpdateBehavior < UpdateBehavior def self.update(obj, _digest) yield(Pathname.new(obj.filename)) end end class RescueUpdateBehavior < UpdateBehavior def self.update(obj, digest) if obj.class.to_s == 'Sass::Importers::Filesystem' digest.update('root=') digest.update(obj.root) return end data = begin Marshal.dump(obj) rescue obj.inspect end digest.update(data) end end end end