module Juicer class DependencyResolver include Enumerable attr_reader :files # Constructor def initialize(options = {}) @files = [] @options = options end # # Resolve dependencies. # This method accepts an optional block. The block will receive each # file in succession. The file is included in the returned collection # if the block is true for the given file. Without a block every found # file is returned. # def resolve(file, &block) @files = [] _resolve(file, &block) end # # Yield files recursively. Resolve dependencies first, then call each, or # any other enumerable methods. # def each(&block) @files.each(&block) end # # Resolves a path relative to another. If the path is absolute (ie it # starts with a protocol or /) the :document_root options has to be # set as well. # def resolve_path(path, reference) # Absolute URL if path =~ %r{^(/|[a-z]+:)} if @options[:document_root].nil? msg = "Cannot resolve absolute path '#{path}' without document root option" raise ArgumentError.new(msg) end path.sub!(%r{^[a-z]+://[^/]+/}, '') return File.expand_path(File.join(@options[:document_root], path)) end File.expand_path(File.join(File.dirname(reference), path)) end private def parse(line) raise NotImplementedError.new end def extension raise NotImplementedError.new end def encoded_line(line) if String.method_defined?(:encode) line.encode!('UTF-8', 'UTF-8', :invalid => :replace) else line end end # # Carries out the actual work of resolve. resolve resets the internal # file list and yields control to _resolve for rebuilding the file list. # def _resolve(file) imported_path = nil IO.foreach(file) do |line| # Implementing subclasses may throw :done from the parse method when # the file is exhausted for dependency declaration possibilities. catch(:done) do imported_path = parse(line, imported_path) # If a dependency declaration was found if imported_path # Resolves a path relative to the file that imported it imported_path = resolve_path(imported_path, file) if File.directory?(imported_path) imported_files = Dir.glob(File.join(imported_path, "**", "*#{extension}")) else imported_files = [imported_path] end imported_files.each do |imported_file| # Only keep processing file if it's not already included. # Yield to block to allow caller to ignore file if !@files.include?(imported_file) && (!block_given? || yield(imported_file)) # Check this file for imports before adding it to get order right _resolve(imported_file) { |f| f != File.expand_path(file) } end end end end end file = File.expand_path(file) @files << file if !@files.include?(file) && (!block_given? || yield(file)) end end end