
# dependencies.rb - C::Dependencies generator for Rant.
#
# Copyright (C) 2005 Stefan Lang <langstefan@gmx.at>

require 'rant/rantlib'
require 'rant/c/include'

module Rant::Generators::C end
class Rant::Generators::C::Dependencies
    def self.rant_gen(rac, ch, args, &block)
	c_files, out_fn, include_pathes, opts = nil
	# args validation
	if block
	    rac.warn_msg "C::Dependencies: ignoring block"
	end
	case args.size
	when 0 # noop
	when 1
	    farg = args.first
	    Hash === farg ? (opts = farg) : (out_fn = farg)
	when 2
	    out_fn = args.first
	    opts = args[1]
	else
	    rac.abort_at(ch,
		"C::Dependencies takes one or two arguments.")
	end
        correct_case = false
	if opts
	    if opts.respond_to? :to_hash
		opts = opts.to_hash
	    else
		rac.abort_at(ch,
		    "C::Dependencies: second argument has to be a hash.")
	    end
	    opts.each { |k, v|
		case k
		when :sources
		    c_files = v
		when :search, :search_pathes, :include_pathes
		    include_pathes = 
		    if v.respond_to? :to_str
			[v.to_str]
		    else
			v
		    end
                when :correct_case
                    correct_case = !!v
		else
		    rac.abort_at(ch,
			"C::Dependencies: no such option -- #{k}")
		end
	    }
	end
	out_fn ||= "c_dependencies"
	c_files ||= rac.cx.sys["**/*.{c,cpp,cc,h,hpp}"]
	include_pathes ||= ["."]
	if out_fn.respond_to? :to_str
	    out_fn = out_fn.to_str
	else
	    rac.abort_at(ch, "filename has to be a string")
	end
	unless ::Rant::FileList === c_files
	    if c_files.respond_to? :to_ary
		c_files = c_files.to_ary
	    else
		rac.abort_at(ch, "sources has to be a list of files")
	    end
	end
	unless ::Rant::FileList === include_pathes
	    if include_pathes.respond_to? :to_ary
		include_pathes = include_pathes.to_ary
	    else
		rac.abort_at(ch,
		    "search has to be a list of directories")
	    end
	end
	# define file task
	rac.cx.file({:__caller__ => ch, out_fn => c_files}) do |t|
	    tmp_rac = ::Rant::RantApp.new
	    depfile_ts = Time.at(0)
	    if File.exist? t.name
		tmp_rac.source(t.name)
		depfile_ts = File.mtime(t.name)
	    end
	    rf_str = ""
	    c_files.each { |cf|
		f_task = nil
		unless test(?f, cf)
		    rac.warn_msg "#{t.name}: no such file -- #{cf}"
		    next
		end
		f_task = tmp_rac.tasks[cf.to_str]
		deps = f_task ? f_task.prerequisites : nil
		if !deps or File.mtime(cf) > depfile_ts
		    rac.cmd_msg "scanning #{cf}"
		    std_includes, local_includes = 
			::Rant::C::Include.parse_includes(File.read(cf))
		    deps = []
		    (std_includes + local_includes).each { |fn|
			path = existing_file(
                            include_pathes, fn, correct_case)
			deps << path if path
		    }
		end
		rf_str << file_deps(cf, deps) << "\n"
	    }
	    rac.vmsg 1, "writing C source dependencies to #{t.name}"
	    open(t.name, "w") { |f|
		f.puts
		f.puts "# #{t.name}"
		f.puts "# C source dependencies generated by Rant #{Rant::VERSION}"
		f.puts "# WARNING: Modifications to this file will get lost!"
		f.puts
		f.write rf_str
	    }
	end
    end
    def self.existing_file(dirs, fn, correct_case)
	dirs.each { |dir|
	    path = dir == "." ? fn : File.join(dir, fn)
	    if test ?f, path
                return path unless correct_case
                found_file = File.basename(fn)
                found_in_dir = File.dirname(File.join(dir, fn))
                Dir.entries(found_in_dir).each { |dentry|
                    return File.join(found_in_dir, dentry) if dentry.downcase == found_file.downcase
                }
            end
	}
	nil
    end
    def self.file_deps(target, deps)
	s = "gen SourceNode, #{target.to_str.dump} => "
	s << "[#{ deps.map{ |fn| fn.to_str.dump }.join(', ')}]"
    end
end # class Rant::Generators::C::Dependencies
