#!/usr/bin/env ruby

# sisu-install - Monolithic rant script, autogenerated by rant-import 0.5.8.
#
# Copyright (C) 2005 Stefan Lang <langstefan@gmx.at>
#
# This program is free software.
# You can distribute/modify this program under the terms of
# the GNU LGPL, Lesser General Public License version 2.1.


require 'getoptlong'


require 'rbconfig'

unless Process::Status.method_defined?(:success?) # new in 1.8.2
    class Process::Status
        def success?; exitstatus == 0; end
    end
end
unless Regexp.respond_to? :union # new in 1.8.1
    def Regexp.union(*patterns)
        return /(?!)/ if patterns.empty?
        Regexp.new(patterns.join("|"))
    end
end
if RUBY_VERSION < "1.8.2"
    class Array
        undef_method :flatten, :flatten!
        def flatten
            cp = self.dup
            cp.flatten!
            cp
        end
        def flatten!
            res = []
            flattened = false
            self.each { |e|
                if e.respond_to? :to_ary
                    res.concat(e.to_ary)
                    flattened = true
                else
                    res << e
                end
            }
            if flattened
                replace(res)
                flatten!
                self
            end
        end
    end
end

class String
    def _rant_sub_ext(ext, new_ext = nil)
        if new_ext
            self.sub(/#{Regexp.escape ext}$/, new_ext)
        else
            self.sub(/(\.[^.]*$)|$/, ".#{ext}")
        end
    end
end

module Rant
    VERSION = '0.5.8'

    @__rant_no_value__ = Object.new.freeze
    def self.__rant_no_value__
	@__rant_no_value__
    end

    module Env
        OS = ::Config::CONFIG['target']
        RUBY = ::Config::CONFIG['ruby_install_name']
        RUBY_BINDIR = ::Config::CONFIG['bindir']
        RUBY_EXE = File.join(RUBY_BINDIR, RUBY + ::Config::CONFIG["EXEEXT"])

        @@zip_bin = false
        @@tar_bin = false

        if OS =~ /mswin/i
            def on_windows?; true; end
        else
            def on_windows?; false; end
        end

        def have_zip?
            if @@zip_bin == false
                @@zip_bin = find_bin "zip"
            end
            !@@zip_bin.nil?
        end
        def have_tar?
            if @@tar_bin == false
                @@tar_bin = find_bin "tar"
            end
            !@@tar_bin.nil?
        end
        def pathes
            path = ENV[on_windows? ? "Path" : "PATH"]
            return [] unless path
            path.split(on_windows? ? ";" : ":")
        end
        def find_bin bin_name
            if on_windows?
                bin_name_exe = nil
                if bin_name !~ /\.[^\.]{1,3}$/i
                    bin_name_exe = bin_name + ".exe"
                end
                pathes.each { |dir|
                    file = File.join(dir, bin_name)
                    return file if test(?f, file)
                    if bin_name_exe
                        file = File.join(dir, bin_name_exe)
                        return file if test(?f, file)
                    end
                }
            else
                pathes.each { |dir|
                    file = File.join(dir, bin_name)
                    return file if test(?x, file)
                }
            end
            nil
        end
        def shell_path path
            if on_windows?
                path = path.tr("/", "\\")
                if path.include? ' '
                    '"' + path + '"'
                else
                    path
                end
            else
                if path.include? ' '
                    "'" + path + "'"
                else
                    path
                end
            end
        end
        extend self
    end # module Env

    module Sys
	def sp(arg)
            if arg.respond_to? :to_ary
                arg.to_ary.map{ |e| sp e }.join(' ')
            else
                _escaped_path arg
            end
	end
        def escape(arg)
            if arg.respond_to? :to_ary
                arg.to_ary.map{ |e| escape e }.join(' ')
            else
                _escaped arg
            end
        end
        if Env.on_windows?
            def _escaped_path(path)
		_escaped(path.to_s.tr("/", "\\"))
            end
            def _escaped(arg)
		sarg = arg.to_s
		return sarg unless sarg.include?(" ")
		sarg << "\\" if sarg[-1].chr == "\\"
                "\"#{sarg}\""
            end
            def regular_filename(fn)
                fn.to_str.tr("\\", "/").gsub(%r{/{2,}}, "/")
            end
        else
            def _escaped_path(path)
                path.to_s.gsub(/(?=\s)/, "\\")
            end
            alias _escaped _escaped_path
            def regular_filename(fn)
                fn.to_str.gsub(%r{/{2,}}, "/")
            end
        end
        private :_escaped_path
        private :_escaped
	def split_all(path)
            names = regular_filename(path).split(%r{/})
            names[0] = "/" if names[0] && names[0].empty?
            names
	end
        extend self
    end # module Sys


    ROOT_RANTFILE = "root.rant"
    SUB_RANTFILE = "sub.rant"
    RANTFILES = [ "Rantfile", "rantfile", ROOT_RANTFILE ]
    
    CODE_IMPORTS = []
    
    class RantAbortException < StandardError
    end

    class RantDoneException < StandardError
    end

    class Error < StandardError
    end

    module Generators
    end

    module RantVar

	class Error < Rant::Error
	end

	class ConstraintError < Error

	    attr_reader :constraint, :val

	    def initialize(constraint, val, msg = nil)
		@msg = msg
		@constraint = constraint
		@val = val
	    end

	    def message
		val_desc = @val.inspect
		val_desc[7..-1] = "..." if val_desc.length > 10
		"#{val_desc} doesn't match constraint: #@constraint"
	    end
	end

	class NotAConstraintFactoryError < Error
	    attr_reader :obj
	    def initialize(obj, msg = nil)
		@msg = msg
		@obj = obj
	    end
	    def message
		obj_desc = @obj.inspect
		obj_desc[7..-1] = "..." if obj_desc.length > 10
		"#{obj_desc} is not a valid constraint factory"
	    end
	end

	class InvalidVidError < Error
	    def initialize(vid, msg = nil)
		@msg = msg
		@vid = vid
	    end
	    def message
		vid_desc = @vid.inspect
		vid_desc[7..-1] = "..." if vid_desc.length > 10
		"#{vid_desc} is not a valid var identifier"
	    end
	end

	class InvalidConstraintError < Error
	end

	class QueryError < Error
	end

	class Space

	    @@env_ref = Object.new

	    def initialize
		@store = {}
		@constraints = {}
	    end

	    def query(*args, &block)
		case args.size
		when 0
		    raise QueryError, "no arguments", caller
		when 1
		    arg = args.first
		    if Hash === arg
			if arg.size == 1
			    arg.each { |k,v|
				self[k] = v if self[k].nil?
			    }
			    self
			else
			    init_all arg
			end
		    else
			self[arg]
		    end
		when 2, 3
		    vid, cf, val = *args
                    constrain vid,
                        get_factory(cf).rant_constraint
		    self[vid] = val if val
		else
		    raise QueryError, "too many arguments"
		end
	    end

	    def restrict vid, ct, *ct_args
		if vid.respond_to? :to_ary
		    vid.to_ary.each { |v| restrict(v, ct, *ct_args) }
		else
		    constrain vid,
			get_factory(ct).rant_constraint(*ct_args)
		end
		self
	    end

	    def get_factory id
		if String === id || Symbol === id
                    id = Constraints.const_get(id) rescue nil
		end
		unless id.respond_to? :rant_constraint
                    raise NotAConstraintFactoryError.new(id), caller
		end
		id
	    end
	    private :get_factory

	    def [](vid)
		vid = RantVar.valid_vid vid
		val = @store[vid]
		val.equal?(@@env_ref) ? ENV[vid] : val
	    end

	    def []=(vid, val)
		vid = RantVar.valid_vid(vid)
		c = @constraints[vid]
		if @store[vid] == @@env_ref
		    ENV[vid] = c ? c.filter(val) : val
		else
		    @store[vid] = c ? c.filter(val) : val
		end
	    end

	    def env(*vars)
		vars.flatten.each { |var|
		    vid = RantVar.valid_vid(var)
		    cur_val = @store[vid]
		    next if cur_val == @@env_ref
		    ENV[vid] = cur_val unless cur_val.nil?
		    @store[vid] = @@env_ref
		}
		nil
	    end

	    def set_all hash
		unless Hash === hash
		    raise QueryError,
			"set_all argument has to be a hash"
		end
		hash.each_pair { |k, v|
		    self[k] = v
		}
	    end

	    def init_all hash
		unless Hash === hash
		    raise QueryError,
			"init_all argument has to be a hash"
		end
		hash.each_pair { |k, v|
		    self[k] = v if self[k].nil?
		}
	    end

	    def constrain vid, constraint
		vid = RantVar.valid_vid(vid)
		unless RantVar.valid_constraint? constraint
		    raise InvalidConstraintError, constraint
		end
		@constraints[vid] = constraint
		if @store.member? vid
		    begin
			val = @store[vid]
			@store[vid] = constraint.filter(@store[vid])
		    rescue
			@store[vid] = constraint.default
			raise ConstraintError.new(constraint, val)
		    end
		else
		    @store[vid] = constraint.default
		end
	    end

	    def has_var?(vid)
		!self[vid].nil?
	    end

            def _set(vid, val) #:nodoc:
                @store[vid] = val
            end

            def _get(vid) #:nodoc:
                @store[vid]
            end

            def _init(vid, val) #:nodoc:
                @store[vid] ||= val
            end

	end	# class Space

	module Constraint
	    def matches? val
		filter val
		true
	    rescue
		return false
	    end
	end

	def valid_vid(obj)
	    case obj
	    when String; obj
	    when Symbol; obj.to_s
	    else
		if obj.respond_to? :to_str
		    obj.to_str
		else
		    raise InvalidVidError.new(obj)
		end
	    end
	end
	
	def valid_constraint?(obj)
	    obj.respond_to?(:filter) &&
		obj.respond_to?(:matches?) &&
		obj.respond_to?(:default)
	end

	module_function :valid_constraint?, :valid_vid

	module Constraints
	    class AutoList
		include Constraint
		class << self
		    alias rant_constraint new
		end
		def filter(val)
		    if val.respond_to? :to_ary
			val.to_ary
		    elsif val.nil?
			raise ConstraintError.new(self, val)
		    else
			[val]
		    end
		end
		def default
		    []
		end
		def to_s
		    "list or single, non-nil value"
		end
	    end
        end # module Constraints
    end # module RantVar
end # module Rant


require 'fileutils'


module Rant
    def FileList(arg)
        if arg.respond_to?(:to_rant_filelist)
            arg.to_rant_filelist
        elsif arg.respond_to?(:to_ary)
            FileList.new(arg.to_ary)
        else
            raise TypeError,
                "cannot convert #{arg.class} into Rant::FileList"
        end
    end
    module_function :FileList
    class FileList
        include Enumerable

        ESC_SEPARATOR = Regexp.escape(File::SEPARATOR)
        ESC_ALT_SEPARATOR = File::ALT_SEPARATOR ?
            Regexp.escape(File::ALT_SEPARATOR) : nil

        class << self
            def [](*patterns)
                new.hide_dotfiles.include(*patterns)
            end
            def glob(*patterns)
                fl = new.hide_dotfiles.ignore(".", "..").include(*patterns)
                if block_given? then yield fl else fl end
            end
            def glob_all(*patterns)
                fl = new.ignore(".", "..").include(*patterns)
                if block_given? then yield fl else fl end
            end
        end

        def initialize(store = [])
            @pending = false
            @def_glob_dotfiles = true
            @items = store
            @ignore_rx = nil
            @keep = {}
            @actions = []
        end
        alias _object_dup dup
        private :_object_dup
        def dup
            c = _object_dup
            c.items = @items.dup
            c.actions = @actions.dup
            c.ignore_rx = @ignore_rx.dup if @ignore_rx
            c.instance_variable_set(:@keep, @keep.dup)
            c
        end
        def copy
            c = _object_dup
            c.items = @items.map { |entry| entry.dup }
            c.actions = @actions.dup
            c.ignore_rx = @ignore_rx.dup if @ignore_rx
            h_keep = {}
            @keep.each_key { |entry| h_keep[entry] = true }
            c.instance_variable_set(:@keep, h_keep)
            c
        end
        def glob_dotfiles?
            @def_glob_dotfiles
        end
        def glob_dotfiles=(flag)
            @def_glob_dotfiles = flag ? true : false
        end
        def hide_dotfiles
            @def_glob_dotfiles = false
            self
        end
        def glob_dotfiles
            @def_glob_dotfiles = true
            self
        end

        protected
        attr_accessor :actions, :items
        attr_accessor :pending
        attr_accessor :ignore_rx

        public
        def each(&block)
            resolve if @pending
            @items.each(&block)
            self
        end
        def to_ary
            resolve if @pending
            @items
        end
        alias to_a to_ary
        alias entries to_ary    # entries: defined in Enumerable
        def to_rant_filelist
            self
        end
        def +(other)
            if other.respond_to? :to_rant_filelist
                c = other.to_rant_filelist.dup
                c.actions.concat(@actions)
                c.items.concat(@items)
                c.pending = !c.actions.empty?
                c
            elsif other.respond_to? :to_ary
                c = dup
                c.actions <<
                    [:apply_ary_method_1, :concat, other.to_ary.dup]
                c.pending = true
                c
            else
                raise TypeError,
                    "cannot add #{other.class} to Rant::FileList"
            end
        end
        def <<(file)
            @actions << [:apply_ary_method_1, :push, file]
            @keep[file] = true
            @pending = true
            self
        end
        def keep(entry)
            @keep[entry] = true
            @items << entry
            self
        end
        def concat(ary)
            if @pending
                ary = ary.to_ary.dup
                @actions << [:apply_ary_method_1, :concat, ary]
            else
                ix = ignore_rx and ary = ary.to_ary.reject { |f| f =~ ix }
                @items.concat(ary)
            end
            self
        end
        def size
            resolve if @pending
            @items.size
        end
        alias length size
        def empty?
            resolve if @pending
            @items.empty?
        end
        def join(sep = ' ')
            resolve if @pending
            @items.join(sep)
        end
        def pop
            resolve if @pending
            @items.pop
        end
        def push(entry)
            resolve if @pending
            @items.push(entry) if entry !~ ignore_rx
            self
        end
        def shift
            resolve if @pending
            @items.shift
        end
        def unshift(entry)
            resolve if @pending
            @items.unshift(entry) if entry !~ ignore_rx
            self
        end
if Object.method_defined?(:fcall) || Object.method_defined?(:funcall) # in Ruby 1.9 like __send__
        @@__send_private__ = Object.method_defined?(:fcall) ? :fcall : :funcall
        def resolve
            @pending = false
            @actions.each{ |action| self.__send__(@@__send_private__, *action) }.clear
            ix = ignore_rx
            if ix
                @items.reject! { |f| f =~ ix && !@keep[f] }
            end
            self
        end
else
        def resolve
            @pending = false
            @actions.each{ |action| self.__send__(*action) }.clear
            ix = ignore_rx
            if ix
                @items.reject! { |f| f =~ ix && !@keep[f] }
            end
            self
        end
end
        def include(*pats)
            @def_glob_dotfiles ? glob_all(*pats) : glob_unix(*pats)
        end
        alias glob include
        def glob_unix(*patterns)
            patterns.flatten.each { |pat|
                @actions << [:apply_glob_unix, pat]
            }
            @pending = true
            self
        end
        def glob_all(*patterns)
            patterns.flatten.each { |pat|
                @actions << [:apply_glob_all, pat]
            }
            @pending = true
            self
        end
        if RUBY_VERSION < "1.8.2"
            FN_DOTFILE_RX_ = ESC_ALT_SEPARATOR ?
                /(^|(#{ESC_SEPARATOR}|#{ESC_ALT_SEPARATOR})+)\..*
                    ((#{ESC_SEPARATOR}|#{ESC_ALT_SEPARATOR})+|$)/x :
                /(^|#{ESC_SEPARATOR}+)\..* (#{ESC_SEPARATOR}+|$)/x
            def apply_glob_unix(pattern)
                inc_files = Dir.glob(pattern)
                unless pattern =~ /(^|\/)\./
                    inc_files.reject! { |fn| fn =~ FN_DOTFILE_RX_ }
                end
                @items.concat(inc_files)
            end
        else
            def apply_glob_unix(pattern)
                @items.concat(Dir.glob(pattern))
            end
        end
        private :apply_glob_unix
        def apply_glob_all(pattern)
            @items.concat(Dir.glob(pattern, File::FNM_DOTMATCH))
        end
        private :apply_glob_all
        def exclude(*patterns)
            patterns.each { |pat|
                if Regexp === pat
                    @actions << [:apply_exclude_rx, pat]
                else
                    @actions << [:apply_exclude, pat]
                end
            }
            @pending = true
            self
        end
        def ignore(*patterns)
            patterns.each { |pat|
                add_ignore_rx(Regexp === pat ? pat : mk_all_rx(pat))
            }
            @pending = true
            self
        end
        def add_ignore_rx(rx)
            @ignore_rx =
            if @ignore_rx
                Regexp.union(@ignore_rx, rx)
            else
                rx
            end
        end
        private :add_ignore_rx
        def apply_exclude(pattern)
            @items.reject! { |elem|
                File.fnmatch?(pattern, elem, File::FNM_DOTMATCH) && !@keep[elem]
            }
        end
        private :apply_exclude
        def apply_exclude_rx(rx)
            @items.reject! { |elem|
                elem =~ rx && !@keep[elem]
            }
        end
        private :apply_exclude_rx
        def exclude_name(*names)
            names.each { |name|
                @actions << [:apply_exclude_rx, mk_all_rx(name)]
            }
            @pending = true
            self
        end
        alias shun exclude_name
        if File::ALT_SEPARATOR
            def mk_all_rx(file)
                /(^|(#{ESC_SEPARATOR}|#{ESC_ALT_SEPARATOR})+)#{Regexp.escape(file)}
                    ((#{ESC_SEPARATOR}|#{ESC_ALT_SEPARATOR})+|$)/x
            end
        else
            def mk_all_rx(file)
                /(^|#{ESC_SEPARATOR}+)#{Regexp.escape(file)}
                    (#{ESC_SEPARATOR}+|$)/x
            end
        end
        private :mk_all_rx
        def exclude_path(*patterns)
            patterns.each { |pat|
                @actions << [:apply_exclude_path, pat]
            }
            @pending = true
            self
        end
        def apply_exclude_path(pattern)
            flags = File::FNM_DOTMATCH|File::FNM_PATHNAME
            @items.reject! { |elem|
                File.fnmatch?(pattern, elem, flags) && !@keep[elem]
            }
        end
        private :apply_exclude
        def select(&block)
            d = dup
            d.actions << [:apply_select, block]
            d.pending = true
            d
        end
        alias find_all select
        def apply_select blk
            @items = @items.select(&blk)
        end
        private :apply_select
        def map(&block)
            d = dup
            d.actions << [:apply_ary_method, :map!, block]
            d.pending = true
            d
        end
        alias collect map
        def sub_ext(ext, new_ext=nil)
            map { |f| f._rant_sub_ext ext, new_ext }
        end
        def ext(ext_str)
            sub_ext(ext_str)
        end
        def arglist
            Rant::Sys.sp to_ary
        end
        alias to_s arglist
        alias object_inspect inspect
        def uniq!
            @actions << [:apply_ary_method, :uniq!]
            @pending = true
            self
        end
        def sort!
            @actions << [:apply_ary_method, :sort!]
            @pending = true
            self
        end
        def map!(&block)
            @actions << [:apply_ary_method, :map!, block]
            @pending = true
            self
        end
        def reject!(&block)
            @actions << [:apply_ary_method, :reject!, block]
            @pending = true
            self
        end
        private
        def apply_ary_method(meth, block=nil)
            @items.send meth, &block
        end
        def apply_ary_method_1(meth, arg1, block=nil)
            @items.send meth, arg1, &block
        end
    end # class FileList
end # module Rant

if RUBY_VERSION == "1.8.3"
    module FileUtils
        METHODS = singleton_methods - %w(private_module_function
            commands options have_option? options_of collect_method)
        module Verbose
            class << self
                public(*::FileUtils::METHODS)
            end
            public(*::FileUtils::METHODS)
        end
    end
end

if RUBY_VERSION < "1.8.1"
    module FileUtils
        undef_method :fu_list
        def fu_list(arg)
            arg.respond_to?(:to_ary) ? arg.to_ary : [arg]
        end
    end
end

module Rant
    class RacFileList < FileList

	attr_reader :subdir
	attr_reader :basedir

	def initialize(rac, store = [])
	    super(store)
	    @rac = rac
	    @subdir = @rac.current_subdir
	    @basedir = Dir.pwd
	    @ignore_hash = nil
            @add_ignore_args = []
	    update_ignore_rx
	end
        def dup
            c = super
            c.instance_variable_set(
                :@add_ignore_args, @add_ignore_args.dup)
            c
        end
        def copy
            c = super
            c.instance_variable_set(
                :@add_ignore_args, @add_ignore_args.map { |e| e.dup })
            c
        end
        alias filelist_ignore ignore
        def ignore(*patterns)
            @add_ignore_args.concat patterns
            self
        end
	def ignore_rx
	    update_ignore_rx
	    @ignore_rx
	end
	alias filelist_resolve resolve
	def resolve
	    Sys.cd(@basedir) { filelist_resolve }
	end
	def each_cd(&block)
	    old_pwd = Dir.pwd
	    Sys.cd(@basedir)
	    filelist_resolve if @pending
	    @items.each(&block)
	ensure
	    Sys.cd(old_pwd)
	end
	private
	def update_ignore_rx
	    ri = @rac.var[:ignore]
            ri = ri ? (ri + @add_ignore_args) : @add_ignore_args
	    rh = ri.hash
	    unless rh == @ignore_hash
		@ignore_rx = nil
		filelist_ignore(*ri)
		@ignore_hash = rh
	    end
	end
    end	# class RacFileList

    class MultiFileList

	attr_reader :cur_list
	
	def initialize(rac)
	    @rac = rac
	    @cur_list = RacFileList.new(@rac)
	    @lists = [@cur_list]
	end

	def each_entry(&block)
	    @lists.each { |list|
		list.each_cd(&block)
	    }
	end

	def add(filelist)
	    @cur_list = filelist
	    @lists << filelist
	    self
	end

	def method_missing(sym, *args, &block)
	    if @cur_list && @cur_list.respond_to?(sym)
		if @cur_list.subdir == @rac.current_subdir
		    @cur_list.send(sym, *args, &block)
		else
		    add(RacFileList.new(@rac))
		    @cur_list.send(sym, *args, &block)
		end
	    else
		super
	    end
	end
    end	# class MultiFileList

    class CommandError < StandardError
	attr_reader :cmd
	attr_reader :status
	def initialize(cmd, status=nil, msg=nil)
	    @msg = msg
	    @cmd = cmd
	    @status = status
	end
	def message
	    if !@msg && cmd
		if status
		    "Command failed with status #{status.exitstatus}:\n" +
		    "[#{cmd}]"
		else
		    "Command failed:\n[#{cmd}]"
		end
	    else
		@msg
	    end
	end
    end

    module Sys
	include ::FileUtils::Verbose

	@symlink_supported = true
	class << self
	    attr_accessor :symlink_supported
	end

	def fu_output_message(msg)	#:nodoc:
	end
        private :fu_output_message

        def fu_each_src_dest(src, *rest)
            src = src.to_ary if src.respond_to? :to_ary
            super(src, *rest)
        end
        private :fu_each_src_dest

	def sh(*cmd_args, &block)
	    cmd_args.flatten!
	    cmd = cmd_args.join(" ")
	    fu_output_message cmd
            success = system(*cmd_args)
	    if block_given?
                block[$?]
            elsif !success
		raise CommandError.new(cmd, $?)
	    end
	end

	def ruby(*args, &block)
            if args.empty?
                sh(Env::RUBY_EXE, '', &block)
            else
                sh(args.unshift(Env::RUBY_EXE), &block)
            end
	end
        def cd(dir, &block)
            fu_output_message "cd #{dir}"
            orig_pwd = Dir.pwd
            Dir.chdir dir
            if block
                begin
                    block.arity == 0 ? block.call : block.call(Dir.pwd)
                ensure
                    fu_output_message "cd -"
                    Dir.chdir orig_pwd
                end
            else
                self
            end
        end

	def safe_ln(src, dest)
            dest = dest.to_str
            src = src.respond_to?(:to_ary) ? src.to_ary : src.to_str
	    unless Sys.symlink_supported
		cp(src, dest)
	    else
		begin
		    ln(src, dest)
		rescue Exception # SystemCallError # Errno::EOPNOTSUPP
		    Sys.symlink_supported = false
		    cp(src, dest)
		end
	    end
	end

        def ln_f(src, dest)
            ln(src, dest, :force => true)
        end

        def split_path(str)
            str.split(Env.on_windows? ? ";" : ":")
        end

        if Env.on_windows?
            def root_dir?(path)
                path == "/" || path == "\\" ||
                    path =~ %r{\A[a-zA-Z]+:(\\|/)\Z}
            end
            def absolute_path?(path)
                path =~ %r{\A([a-zA-Z]+:)?(/|\\)}
            end
        else
            def root_dir?(path)
                path == "/"
            end
            def absolute_path?(path)
                path =~ %r{\A/}
            end
        end

        extend self

        if RUBY_VERSION >= "1.8.4"  # needed by 1.9.0, too
            class << self
                public(*::FileUtils::METHODS)
            end
            public(*::FileUtils::METHODS)
        end

    end	# module Sys

    class SysObject
	include Sys
	def initialize(rant)
	    @rant = rant or
		raise ArgumentError, "rant application required"
	end
        def ignore(*patterns)
            @rant.var[:ignore].concat(patterns)
            nil
        end
        def filelist(arg = Rant.__rant_no_value__)
            if Rant.__rant_no_value__.equal?(arg)
                RacFileList.new(@rant)
            elsif arg.respond_to?(:to_rant_filelist)
                arg.to_rant_filelist
            elsif arg.respond_to?(:to_ary)
                RacFileList.new(@rant, arg.to_ary)
            else
                raise TypeError,
                    "cannot convert #{arg.class} into Rant::FileList"
            end
        end
	def [](*patterns)
	    RacFileList.new(@rant).hide_dotfiles.include(*patterns)
	end
	def glob(*patterns, &block)
	    fl = RacFileList.new(@rant).hide_dotfiles.include(*patterns)
            fl.ignore(".", "..")
            if block_given? then yield fl else fl end
	end
        def glob_all(*patterns, &block)
	    fl = RacFileList.new(@rant).include(*patterns)
            fl.ignore(".", "..") # use case: "*.*" as pattern
            if block_given? then yield fl else fl end
        end
        def expand_path(path)
            File.expand_path(@rant.project_to_fs_path(path))
        end
	private
	def fu_output_message(cmd)
	    @rant.cmd_msg cmd
	end
    end


    class TaskFail < StandardError
	def initialize(task, orig, msg)
	    @task = task
	    @orig = orig
            @msg = msg
	end
        def exception
            self
        end
	def task
	    @task
	end
	def tname
	    @task ? @task.name : nil
	end
	def orig
	    @orig
	end
        def msg
            @msg
        end
    end

    class Rantfile
	attr_reader :tasks, :path
	attr_accessor :project_subdir
	def initialize(path)
	    @path = path or raise ArgumentError, "path required"
	    @tasks = []
	    @project_subdir = nil
	end
        alias to_s path
        alias to_str path
    end	# class Rantfile

    module Node

	INVOKE_OPT = {}.freeze

	T0 = Time.at(0).freeze

	attr_reader :name
	attr_reader :rac
	attr_accessor :description
	attr_accessor :rantfile
	attr_accessor :line_number
        attr_accessor :project_subdir
	
	def initialize
	    @description = nil
	    @rantfile = nil
	    @line_number = nil
	    @run = false
            @project_subdir = ""
	    @success = nil
	end

        def reference_name
            sd = rac.current_subdir
            case sd
            when ""; full_name
            when project_subdir; name
            else "@#{full_name}".sub(/^@#{Regexp.escape sd}\//, '')
            end
        end

        alias to_s reference_name
        alias to_rant_target name

	def full_name
	    sd = project_subdir
	    sd.empty? ? name : File.join(sd, name)
	end

        def ch
            {:file => rantfile.to_str, :ln => line_number}
        end

	def goto_task_home
	    @rac.goto_project_dir project_subdir
	end

        def file_target?
            false
        end

	def done?
	    @success
	end

	def needed?
            invoke(:needed? => true)
	end

	def run?
	    @run
	end

	def invoke(opt = INVOKE_OPT)
	    return circular_dep if run?
	    @run = true
	    begin
		return !done? if opt[:needed?]
		self.run if !done?
                @success = true
	    ensure
		@run = false
	    end
	end

	def fail msg = nil, orig = nil
            raise TaskFail.new(self, orig, msg)
	end

	def each_target
	end

        def has_actions?
            defined? @block and @block
        end

        def dry_run
            text = "Executing #{name.dump}"
            text << " [NOOP]" unless has_actions?
            @rac.cmd_msg text
            action_descs.each { |ad|
                @rac.cmd_print "  - "
                @rac.cmd_msg ad.sub(/\n$/, '').gsub(/\n/, "\n    ")
            }
        end

        private
	def run
	    goto_task_home
            return if @rac.running_task(self)
	    return unless has_actions?
            @receiver.pre_run(self) if defined? @receiver and @receiver
	    @block.arity == 0 ? @block.call : @block[self] if @block
	end

        def action_descs
            descs = []
            if defined? @receiver and @receiver
                descs.concat(@receiver.pre_action_descs)
            end
            @block ? descs << action_block_desc : descs
        end

        def action_block_desc
            @block.inspect =~ /^#<Proc:[\da-z]+@(.+):(\d+)>$/i
            fn, ln = $1, $2
            "Ruby Proc at #{fn.sub(/^#{Regexp.escape @rac.rootdir}\//, '')}:#{ln}"
        end

	def circular_dep
	    rac.warn_msg "Circular dependency on task `#{full_name}'."
	    false
	end
    end	# module Node


    def self.init_import_nodes__default(rac, *rest)
        rac.node_factory = DefaultNodeFactory.new
    end

    class DefaultNodeFactory
        def new_task(rac, name, pre, blk)
            Task.new(rac, name, pre, &blk)
        end
        def new_file(rac, name, pre, blk)
            FileTask.new(rac, name, pre, &blk)
        end
        def new_dir(rac, name, pre, blk)
            DirTask.new(rac, name, pre, &blk)
        end
        def new_source(rac, name, pre, blk)
            SourceNode.new(rac, name, pre, &blk)
        end
        def new_custom(rac, name, pre, blk)
            UserTask.new(rac, name, pre, &blk)
        end
        def new_auto_subfile(rac, name, pre, blk)
            AutoSubFileTask.new(rac, name, pre, &blk)
        end
    end

    class Task
	include Node

        attr_accessor :receiver

	def initialize(rac, name, prerequisites = [], &block)
	    super()
	    @rac = rac or raise ArgumentError, "rac not given"
            @name = name or raise ArgumentError, "name not given"
	    @pre = prerequisites || []
	    @pre_resolved = false
	    @block = block
	    @run = false
            @receiver = nil
	end

	def prerequisites
	    @pre.collect { |pre| pre.to_s }
	end
	alias deps prerequisites

	def source
	    @pre.first.to_s
	end

	def has_actions?
            @block or @receiver && @receiver.has_pre_action?
	end

	def <<(pre)
	    @pre_resolved = false
	    @pre << pre
	end

	def invoked?
	    !@success.nil?
	end

	def fail?
	    @success == false
	end

	def enhance(deps = nil, &blk)
	    if deps
		@pre_resolved = false
		@pre.concat deps
	    end
	    if @block
		if blk
		    first_block = @block
		    @block = lambda { |t|
			first_block[t]
			blk[t]
		    }
		end
	    else
		@block = blk
	    end
	end

	def invoke(opt = INVOKE_OPT)
	    return circular_dep if @run
	    @run = true
	    begin
		return if done?
		internal_invoke opt
	    ensure
		@run = false
	    end
	end

	def internal_invoke(opt, ud_init = true)
	    goto_task_home
	    update = ud_init || opt[:force]
	    dep = nil
	    uf = false
	    each_dep { |dep|
		if dep.respond_to? :timestamp
		    handle_timestamped(dep, opt) && update = true
		elsif Node === dep
		    handle_node(dep, opt) && update = true
		else
		    dep, uf = handle_non_node(dep, opt)
		    uf && update = true
		    dep
		end
	    }
            if @receiver
                goto_task_home
                update = true if @receiver.update?(self)
            end
	    return update if opt[:needed?]
            run if update
	    @success = true
	    update
	rescue StandardError => e
	    @success = false
	    self.fail(nil, e)
	end
	private :internal_invoke

	def handle_node(dep, opt)
	    dep.invoke opt
	end

	def handle_timestamped(dep, opt)
	    dep.invoke opt
	end

	def handle_non_node(dep, opt)
	    @rac.err_msg "Unknown task `#{dep}',",
		"referenced in `#{rantfile.path}', line #{@line_number}!"
	    self.fail
	end

	def each_dep
	    t = nil
	    if @pre_resolved
		return @pre.each { |t| yield(t) }
	    end
	    my_full_name = full_name
	    my_project_subdir = project_subdir
	    @pre.map! { |t|
		if Node === t
		    if t.full_name == my_full_name
			nil
		    else
			yield(t)
			t
		    end
		else
		    t = t.to_s if Symbol === t
		    if t == my_full_name #TODO
			nil
		    else
			selection = @rac.resolve t,
					my_project_subdir
			if selection.empty?
			    yield(t)
			else
			    selection.each { |st| yield(st) }
			    selection
			end
		    end
		end
	    }
            if @pre.kind_of? Rant::FileList
                @pre.resolve
            else
                @pre.flatten!
                @pre.compact!
            end
	    @pre_resolved = true
	end
    end	# class Task

    class UserTask < Task

	def initialize(*args)
	    super
	    @block = nil
	    @needed = nil
            @target_files = nil
	    yield self if block_given?
	end

	def act(&block)
	    @block = block
	end

	def needed(&block)
	    @needed = block
	end

        def file_target?
            @target_files and @target_files.include? @name
        end

        def each_target(&block)
            goto_task_home
            @target_files.each(&block) if @target_files
        end
        
        def file_target(*args)
            args.flatten!
            args << @name if args.empty?
            if @target_files
                @target_files.concat(args)
            else
                @target_files = args
            end
        end
	
	def invoke(opt = INVOKE_OPT)
	    return circular_dep if @run
	    @run = true
	    begin
		return if done?
		internal_invoke(opt, ud_init_by_needed)
	    ensure
		@run = false
	    end
	end

	private
	def ud_init_by_needed
	    if @needed
		goto_task_home
		@needed.arity == 0 ? @needed.call : @needed[self]
	    end
	end
    end	# class UserTask

    class FileTask < Task

	def initialize(*args)
	    super
	    @ts = T0
	end

        def file_target?
            true
        end

	def invoke(opt = INVOKE_OPT)
	    return circular_dep if @run
	    @run = true
	    begin
		return if done?
		goto_task_home
		if File.exist? @name
		    @ts = File.mtime @name
		    internal_invoke opt, false
		else
		    @ts = T0
		    internal_invoke opt, true
		end
	    ensure
		@run = false
	    end
	end

	def timestamp(opt = INVOKE_OPT)
	    File.exist?(@name) ? File.mtime(@name) : T0
	end

        def handle_node(dep, opt)
            return true if dep.file_target? && dep.invoke(opt)
	    if File.exist? dep.name
                File.mtime(dep.name) > @ts
            elsif !dep.file_target?
		@rac.err_msg @rac.pos_text(rantfile.path, line_number),
		    "in prerequisites: no such file: `#{dep.full_name}'"
		self.fail
	    end
        end

	def handle_timestamped(dep, opt)
	    return true if dep.invoke opt
	    dep.timestamp(opt) > @ts
	end

	def handle_non_node(dep, opt)
            goto_task_home # !!??
	    unless File.exist? dep
		@rac.err_msg @rac.pos_text(rantfile.path, line_number),
		    "in prerequisites: no such file or task: `#{dep}'"
		self.fail
	    end
	    [dep, File.mtime(dep) > @ts]
	end

	def each_target
	    goto_task_home
	    yield name
	end
    end	# class FileTask

    module AutoInvokeDirNode
	private
	def run
            goto_task_home
            return if @rac.running_task(self)
	    dir = File.dirname(name)
            @rac.build dir unless dir == "." || dir == "/"
            return unless @block
            @block.arity == 0 ? @block.call : @block[self]
	end
    end

    class AutoSubFileTask < FileTask
        include AutoInvokeDirNode
    end

    class DirTask < Task

	def initialize(*args)
	    super
	    @ts = T0
	    @isdir = nil
	end

	def invoke(opt = INVOKE_OPT)
	    return circular_dep if @run
	    @run = true
	    begin
		return if done?
		goto_task_home
		@isdir = test(?d, @name)
		if @isdir
		    @ts = @block ? test(?M, @name) : Time.now
		    internal_invoke opt, false
		else
		    @ts = T0
		    internal_invoke opt, true
		end
	    ensure
		@run = false
	    end
	end

        def file_target?
            true
        end

        def handle_node(dep, opt)
            return true if dep.file_target? && dep.invoke(opt)
	    if File.exist? dep.name
                File.mtime(dep.name) > @ts
            elsif !dep.file_target?
		@rac.err_msg @rac.pos_text(rantfile.path, line_number),
		    "in prerequisites: no such file: `#{dep.full_name}'"
		self.fail
	    end
        end

	def handle_timestamped(dep, opt)
	    return @block if dep.invoke opt
	    @block && dep.timestamp(opt) > @ts
	end

	def handle_non_node(dep, opt)
            goto_task_home
	    unless File.exist? dep
		@rac.err_msg @rac.pos_text(rantfile.path, line_number),
		    "in prerequisites: no such file or task: `#{dep}'"
		self.fail
	    end
	    [dep, @block && File.mtime(dep) > @ts]
	end

	def run
            return if @rac.running_task(self)
	    @rac.sys.mkdir @name unless @isdir
	    if @block
		@block.arity == 0 ? @block.call : @block[self]
		goto_task_home
		@rac.sys.touch @name
	    end
	end

	def each_target
	    goto_task_home
	    yield name
	end
    end	# class DirTask

    class SourceNode
	include Node
	def initialize(rac, name, prerequisites = [])
	    super()
	    @rac = rac
	    @name = name or raise ArgumentError, "name not given"
	    @pre = prerequisites
	    @run = false
	    @ts = nil
	end
	def prerequisites
	    @pre
	end
	def timestamp(opt = INVOKE_OPT)
	    return @ts if @ts
	    goto_task_home
	    if File.exist?(@name)
		@ts = File.mtime @name
	    else
		rac.abort_at(ch, "SourceNode: no such file -- #@name")
	    end
	    sd = project_subdir
	    @pre.each { |f|
		nodes = rac.resolve f, sd
		if nodes.empty?
		    if File.exist? f
			mtime = File.mtime f
			@ts = mtime if mtime > @ts
		    else
			rac.abort_at(ch, 
			    "SourceNode: no such file -- #{f}")
		    end
		else
		    nodes.each { |node|
                        node.invoke(opt)
			if node.respond_to? :timestamp
			    node_ts = node.timestamp(opt)
                            goto_task_home
			    @ts = node_ts if node_ts > @ts
			else
			    rac.abort_at(ch, 
				"SourceNode can't depend on #{node.name}")
			end
		    }
		end
	    }
	    @ts
	end
	def invoke(opt = INVOKE_OPT)
	    false
	end
        def related_sources
            @pre
        end
    end # class SourceNode

    module Generators
        class Task
	    def self.rant_gen(rac, ch, args, &block)
		unless args.size == 1
		    rac.abort("Task takes only one argument " +
			"which has to be like one given to the " +
			"`task' function")
		end
		rac.prepare_task(args.first, nil, ch) { |name,pre,blk|
		    rac.node_factory.new_custom(rac, name, pre, block)
		}
	    end
        end
        class Directory
	    def self.rant_gen(rac, ch, args, &block)
		case args.size
		when 1
		    name, pre = rac.normalize_task_arg(args.first, ch)
		    self.task(rac, ch, name, pre, &block)
		when 2
		    basedir = args.shift
		    if basedir.respond_to? :to_str
			basedir = basedir.to_str
		    else
			rac.abort_at(ch,
			    "Directory: basedir argument has to be a string.")
		    end
		    name, pre = rac.normalize_task_arg(args.first, ch)
		    self.task(rac, ch, name, pre, basedir, &block)
		else
		    rac.abort_at(ch, "Directory takes one argument, " +
			"which should be like one given to the `task' command.")
		end
	    end

	    def self.task(rac, ch, name, prerequisites=[], basedir=nil, &block)
		dirs = ::Rant::Sys.split_all(name)
		if dirs.empty?
		    rac.abort_at(ch,
			"Not a valid directory name: `#{name}'")
		end
		path = basedir
		last_task = nil
		task_block = nil
		desc_for_last = rac.pop_desc
		dirs.each { |dir|
                    pre = [path]
                    pre.compact!
		    if dir.equal?(dirs.last)
			rac.cx.desc desc_for_last

                        dp = prerequisites.dup
                        pre.each { |elem| dp << elem }
                        pre = dp

			task_block = block
		    end
		    path = path.nil? ? dir : File.join(path, dir)
		    last_task = rac.prepare_task({:__caller__ => ch,
			    path => pre}, task_block) { |name,pre,blk|
			rac.node_factory.new_dir(rac, name, pre, blk)
		    }
		}
		last_task
	    end
        end # class Directory
        class SourceNode
            def self.rant_gen(rac, ch, args)
                unless args.size == 1
                    rac.abort_at(ch, "SourceNode takes one argument.")
                end
                if block_given?
                    rac.abort_at(ch, "SourceNode doesn't take a block.")
                end
                rac.prepare_task(args.first, nil, ch) { |name, pre, blk|
                    rac.node_factory.new_source(rac, name, pre, blk)
                }
            end
        end
	class Rule
	    def self.rant_gen(rac, ch, args, &block)
		unless args.size == 1
		    rac.abort_at(ch, "Rule takes only one argument.")
		end
                rac.abort_at(ch, "Rule: block required.") unless block
		arg = args.first
		target = nil
		src_arg = nil
		if Symbol === arg
		    target = ".#{arg}"
		elsif arg.respond_to? :to_str
		    target = arg.to_str
		elsif Regexp === arg
		    target = arg
		elsif Hash === arg && arg.size == 1
		    arg.each_pair { |target, src_arg| }
		    src_arg = src_arg.to_str if src_arg.respond_to? :to_str
		    target = target.to_str if target.respond_to? :to_str
		    src_arg = ".#{src_arg}" if Symbol === src_arg
		    target = ".#{target}" if Symbol === target
		else
		    rac.abort_at(ch, "Rule argument " +
			"has to be a hash with one key-value pair.")
		end
		esc_target = nil
		target_rx = case target
                    when String
                        esc_target = Regexp.escape(target)
                        /#{esc_target}$/
                    when Regexp
                        target
                    else
		    rac.abort_at(ch, "rule target has " +
			"to be a string or regular expression")
		end
		src_proc = case src_arg
                    when String, Array
                        unless String === target
                            rac.abort(ch, "rule target has to be " +
                                  "a string if source is a string")
                        end
                        if src_arg.kind_of? String
                            lambda { |name|
                                name.sub(/#{esc_target}$/, src_arg)
                            }
                        else
                            lambda { |name|
                                src_arg.collect { |s_src|
                                    s_src = ".#{s_src}" if Symbol === s_src
                                    name.sub(/#{esc_target}$/, s_src)
                                }
                            }
                        end
                    when Proc; src_arg
                    when nil; lambda { |name| [] }
                    else
                        rac.abort_at(ch, "rule source has to be a " +
                            "String, Array or Proc")
                    end
                rac.resolve_hooks <<
                    (block.arity == 2 ? Hook : FileHook).new(
                           rac, ch, target_rx, src_proc, block)
		nil
	    end
            class Hook
                attr_accessor :target_rx
                def initialize(rant, ch, target_rx, src_proc, block)
                    @rant = rant
                    @ch = ch
                    @target_rx = target_rx
                    @src_proc = src_proc
                    @block = block
                end
                def call(target, rel_project_dir)
		    if @target_rx =~ target
                        have_src = true
                        src = @src_proc[target]
                        if src.respond_to? :to_ary
                            have_src = src.to_ary.all? { |s|
                                have_src?(rel_project_dir, s)
                            }
                        else
                            have_src = have_src?(rel_project_dir, src)
                        end
                        if have_src
                            create_nodes(rel_project_dir, target, src)
                        end
                    end
                end
                alias [] call
                private
                def have_src?(rel_project_dir, name)
                    return true unless
                        @rant.rec_save_resolve(name, self, rel_project_dir).empty?
                    test(?e, @rant.abs_path(rel_project_dir, name))
                end
                def create_nodes(rel_project_dir, target, deps)
                    @rant.goto_project_dir rel_project_dir
                    case nodes = @block[target, deps]
                    when Array; nodes
                    when Node; [nodes]
                    else
                        @rant.abort_at(@ch, "Block has to " +
                            "return Node or array of Nodes.")
                    end
                end
            end
            class FileHook < Hook
                private
                def have_src?(rel_project_dir, name)
                    test(?e, @rant.abs_path(rel_project_dir, name)) or
                        @rant.rec_save_resolve(name, self, rel_project_dir
                            ).any? { |t| t.file_target? }
                end
                def create_nodes(rel_project_dir, target, deps)
                    @rant.goto_project_dir rel_project_dir
                    t = @rant.file(:__caller__ => @ch,
                            target => deps, &@block)
                    [t]
                end
            end
	end # class Rule
	class Action
	    def self.rant_gen(rac, ch, args, &block)
                case args.size
                when 0
                    unless (rac[:tasks] || rac[:stop_after_load])
                        yield
                    end
                when 1
                    rx = args.first
                    unless rx.kind_of? Regexp
                        rac.abort_at(ch, "Action: argument has " +
                            "to be a regular expression.")
                    end
                    rac.resolve_hooks << self.new(rac, block, rx)
                    nil
                else
                    rac.abort_at(ch, "Action: too many arguments.")
                end
	    end
            def initialize(rant, block, rx)
                @rant = rant
                @subdir = @rant.current_subdir
                @block = block
                @rx = rx
            end
            def call(target, rel_project_dir)
                if target =~ @rx
                    @rant.resolve_hooks.delete(self)
                    @rant.goto_project_dir @subdir
                    @block.call
                    @rant.resolve(target, rel_project_dir)
                end
            end
            alias [] call
	end
    end	# module Generators
end # module Rant

Rant::MAIN_OBJECT = self

class String
    alias sub_ext _rant_sub_ext
    def to_rant_target
        self
    end
end

module Rant::Lib
    def parse_caller_elem(elem)
        return { :file => "", :ln => 0 } unless elem
        if elem =~ /^(.+):(\d+)(?::|$)/
            { :file => $1, :ln => $2.to_i }
        else
            $stderr.puts "parse_caller_elem: #{elem.inspect}"
            { :file => elem, :ln => 0 }
        end
        
    end
    module_function :parse_caller_elem
end # module Lib

module Rant::Console
    RANT_PREFIX		= "rant: "
    ERROR_PREFIX	= "[ERROR] "
    WARN_PREFIX		= "[WARNING] "
    def msg_prefix
	if defined? @msg_prefix and @msg_prefix
	    @msg_prefix
	else
	    RANT_PREFIX
	end
    end
    def msg(*text)
        pre = msg_prefix
        $stderr.puts "#{pre}#{text.join("\n" + ' ' * pre.length)}"
    end
    def vmsg(importance, *text)
        msg(*text) if verbose >= importance
    end
    def err_msg(*text)
        pre = msg_prefix + ERROR_PREFIX
        $stderr.puts "#{pre}#{text.join("\n" + ' ' * pre.length)}"
    end
    def warn_msg(*text)
        pre = msg_prefix + WARN_PREFIX
        $stderr.puts "#{pre}#{text.join("\n" + ' ' * pre.length)}"
    end
    def ask_yes_no text
        $stderr.print msg_prefix + text + " [y|n] "
        case $stdin.readline
        when /y|yes/i; true
        when /n|no/i; false
        else
            $stderr.puts(' ' * msg_prefix.length +
                "Please answer with `yes' or `no'")
            ask_yes_no text
        end
    end
    def prompt text
        $stderr.print msg_prefix + text
        input = $stdin.readline
	input ? input.chomp : input
    end
    def option_listing opts
	rs = ""
	opts.each { |lopt, *opt_a|
	    if opt_a.size == 2
		mode, desc = opt_a
	    else
		sopt, mode, desc = opt_a
	    end
	    next unless desc	# "private" option
	    optstr = ""
	    arg = nil
	    if mode != GetoptLong::NO_ARGUMENT
		if desc =~ /(\b[A-Z_]{2,}\b)/
		    arg = $1
		end
	    end
	    if lopt
		optstr << lopt
		if arg
		    optstr << " " << arg
		end
		optstr = optstr.ljust(30)
	    end
	    if sopt
		optstr << "   " unless optstr.empty?
		optstr << sopt
		if arg
		    optstr << " " << arg
		end
	    end
	    rs << "  #{optstr}\n"
	    rs << "      #{desc.split("\n").join("\n      ")}\n"
	}
	rs
    end
    extend self
end # module Rant::Console

module RantContext
    include Rant::Generators

    Env = Rant::Env
    FileList = Rant::FileList

    def task(targ, &block)
	rant.task(targ, &block)
    end

    def file(targ, &block)
	rant.file(targ, &block)
    end

    def enhance(targ, &block)
	rant.enhance(targ, &block)
    end

    def desc(*args)
	rant.desc(*args)
    end

    def gen(*args, &block)
	rant.gen(*args, &block)
    end

    def import(*args, &block)
	rant.import(*args, &block)
    end

    def plugin(*args, &block)
	rant.plugin(*args, &block)
    end

    def subdirs(*args)
	rant.subdirs(*args)
    end

    def source(opt, rantfile = nil)
	rant.source(opt, rantfile)
    end

    def sys(*args, &block)
	rant.sys(*args, &block)
    end

    def var(*args, &block)
	rant.var(*args, &block)
    end

    def make(*args, &block)
        rant.make(*args, &block)
    end

end	# module RantContext

class RantAppContext
    include RantContext

    def initialize(app)
	@__rant__ = app
    end

    def rant
        @__rant__
    end

    def method_missing(sym, *args)
	Rant::MAIN_OBJECT.send(sym, *args)
    rescue NoMethodError
	raise NameError, "NameError: undefined local " +
	    "variable or method `#{sym}' for main:Object", caller
    end
end

module Rant

    @__rant__ = nil
    class << self

	def run(first_arg=nil, *other_args)
	    other_args = other_args.flatten
	    args = first_arg.nil? ? ARGV.dup : ([first_arg] + other_args)
	    if rant && !rant.run?
		rant.run(args.flatten)
	    else
                @__rant__ = Rant::RantApp.new
		rant.run(args)
	    end
	end

	def rant
	    @__rant__
	end
    end

end # module Rant

class Rant::RantApp
    include Rant::Console

    class AutoLoadNodeFactory
        def initialize(rant)
            @rant = rant
        end
        def method_missing(sym, *args, &block)
            @rant.import "nodes/default"
            @rant.node_factory.send(sym, *args, &block)
        end
    end



    OPTIONS	= [
	[ "--help",	"-h",	GetoptLong::NO_ARGUMENT,
	    "Print this help and exit."				],
	[ "--version",	"-V",	GetoptLong::NO_ARGUMENT,
	    "Print version of Rant and exit."			],
	[ "--verbose",	"-v",	GetoptLong::NO_ARGUMENT,
	    "Print more messages to stderr."			],
	[ "--quiet",	"-q",	GetoptLong::NO_ARGUMENT,
	    "Don't print commands."			        ],
	[ "--err-commands",	GetoptLong::NO_ARGUMENT,
	    "Print failed commands and their exit status."	],
	[ "--directory","-C",	GetoptLong::REQUIRED_ARGUMENT,
	    "Run rant in DIRECTORY."				],
        [ "--cd-parent","-c",   GetoptLong::NO_ARGUMENT,
            "Run rant in parent directory with Rantfile."       ],
        [ "--look-up",  "-u",   GetoptLong::NO_ARGUMENT,
            "Look in parent directories for root Rantfile."     ],
	[ "--rantfile",	"-f",	GetoptLong::REQUIRED_ARGUMENT,
	    "Process RANTFILE instead of standard rantfiles.\n" +
	    "Multiple files may be specified with this option." ],
	[ "--force-run","-a",	GetoptLong::REQUIRED_ARGUMENT,
	    "Force rebuild of TARGET and all dependencies."     ],
        [ "--dry-run",  "-n",   GetoptLong::NO_ARGUMENT,
            "Print info instead of actually executing actions."  ],
	[ "--tasks",	"-T",	GetoptLong::NO_ARGUMENT,
	    "Show a list of all described tasks and exit."	],
	

        [ "--import",   "-i",   GetoptLong::REQUIRED_ARGUMENT, nil ],
	[ "--stop-after-load",	GetoptLong::NO_ARGUMENT, nil	],
	[ "--trace-abort",	GetoptLong::NO_ARGUMENT, nil	],
    ]

    ROOT_DIR_ID = "@"
    ESCAPE_ID = "\\"

    attr_reader :args
    attr_reader :rantfiles
    attr_reader :force_targets
    attr_reader :plugins
    attr_reader :context
    alias cx context
    attr_reader :tasks
    attr_reader :imports
    attr_reader :current_subdir
    attr_reader :resolve_hooks
    attr_reader :rootdir

    attr_accessor :node_factory

    def initialize
	@args = []
	@context = RantAppContext.new(self)
	@sys = ::Rant::SysObject.new(self)
	@rantfiles = []
	@tasks = {}
	@opts = {
	    :verbose        => 0,
	    :quiet          => false,
	}
        @rootdir = Dir.pwd      # root directory of project
	@arg_rantfiles = []	# rantfiles given in args
	@arg_targets = []	# targets given in args
	@force_targets = []     # targets given with -a option
	@run = false            # run method was called at least once
	@done = false           # run method was successful
	@plugins = []
	@var = Rant::RantVar::Space.new
	@var.query :ignore, :AutoList, []
	@imports = []

	@task_desc = nil
        @last_build_subdir = ""

	@current_subdir = ""
	@resolve_hooks = []

        @node_factory = AutoLoadNodeFactory.new(self)
    end

    def [](opt)
	@opts[opt]
    end

    def []=(opt, val)
        @opts[opt] = val
    end

    def expand_path(subdir, path)
	case path
	when nil;	subdir.dup
	when "";	subdir.dup
	when /^@/;	path.sub(/^@/, '')
	else
            path = path.sub(/^\\(?=@)/, '')
	    if subdir.empty?
		path
	    else
		File.join(subdir, path)
	    end
	end
    end
    def resolve_root_ref(path)
        return File.join(@rootdir, path[1..-1]) if path =~ /^@/
        path.sub(/^\\(?=@)/, '')
    end
    def project_to_fs_path(path)
	sub = expand_path(@current_subdir, path)
	sub.empty? ? @rootdir : File.join(@rootdir, sub)
    end
    def abs_path(subdir, fn)
        return fn if Rant::Sys.absolute_path?(fn)
        path = File.join(@rootdir, subdir, fn)
        path.gsub!(%r{/+}, "/")
        path.sub!(%r{/$}, "") if path.length > 1
        path
    end
    def goto(dir)
        goto_project_dir(expand_path(@current_subdir, dir))
    end
    def goto_project_dir(dir='')
        @current_subdir = dir
        abs_path = @current_subdir.empty? ?
            @rootdir : File.join(@rootdir, @current_subdir)
	unless Dir.pwd == abs_path
	    Dir.chdir abs_path
	    vmsg 1, "in #{abs_path}"
	end
    end

    def run?
	@run
    end

    def done?
	@done
    end

    def run(*args)
	@run = true
	@args.concat(args.flatten)
	orig_pwd = @rootdir = Dir.pwd
	process_args
        Dir.chdir(@rootdir) rescue abort $!.message
	load_rantfiles

	raise Rant::RantDoneException if @opts[:stop_after_load]

	@plugins.each { |plugin| plugin.rant_start }
	if @opts[:tasks]
	    show_descriptions
	    raise Rant::RantDoneException
	end
	run_tasks
	raise Rant::RantDoneException
    rescue Rant::RantDoneException
	@done = true
	@plugins.each { |plugin| plugin.rant_done }
	return 0
    rescue Rant::RantAbortException
	$stderr.puts "rant aborted!"
	return 1
    rescue Exception => e
	ch = get_ch_from_backtrace(e.backtrace)
	if ch && !@opts[:trace_abort]
	    err_msg(pos_text(ch[:file], ch[:ln]), e.message)
	else
	    err_msg e.message, e.backtrace[0..4]
	end
	$stderr.puts "rant aborted!"
	return 1
    ensure
        Dir.chdir @rootdir if test ?d, @rootdir
        hooks = var._get("__at_return__")
        hooks.each { |hook| hook.call } if hooks
	@plugins.each { |plugin| plugin.rant_plugin_stop }
	@plugins.each { |plugin| plugin.rant_quit }
        Dir.chdir orig_pwd
    end


    def desc(*args)
	if args.empty? || (args.size == 1 && args.first.nil?)
	    @task_desc = nil
	else
	    @task_desc = args.join("\n")
	end
    end

    def task(targ, &block)
	prepare_task(targ, block) { |name,pre,blk|
            @node_factory.new_task(self, name, pre, blk)
	}
    end

    def file(targ, &block)
	prepare_task(targ, block) { |name,pre,blk|
            @node_factory.new_file(self, name, pre, blk)
	}
    end

    def gen(*args, &block)
	ch = Rant::Lib::parse_caller_elem(caller[1])
	generator = args.shift
	unless generator.respond_to? :rant_gen
	    abort_at(ch,
		"gen: First argument has to be a task-generator.")
	end
	generator.rant_gen(self, ch, args, &block)
    end

    def import(*args, &block)
	ch = Rant::Lib::parse_caller_elem(caller[1])
	if block
	    warn_msg pos_text(ch[:file], ch[:ln]),
		"import: ignoring block"
	end
	args.flatten.each { |arg|
	    unless String === arg
                abort_at(ch, "import: only strings allowed as arguments")
	    end
	    unless @imports.include? arg
		unless Rant::CODE_IMPORTS.include? arg
		    begin
			vmsg 2, "import #{arg}"
			require "rant/import/#{arg}"
		    rescue LoadError => e
			abort_at(ch, "No such import - #{arg}")
		    end
		    Rant::CODE_IMPORTS << arg.dup
		end
                init_msg = "init_import_#{arg.gsub(/[^\w]/, '__')}"
                Rant.send init_msg, self if Rant.respond_to? init_msg
		@imports << arg.dup
	    end
	}
    end

    def plugin(*args, &block)
	clr = caller[1]
	ch = Rant::Lib::parse_caller_elem(clr)
	name = nil
	pre = []
	ln = ch[:ln] || 0
	file = ch[:file]

	pl_name = args.shift
	pl_name = pl_name.to_str if pl_name.respond_to? :to_str
	pl_name = pl_name.to_s if pl_name.is_a? Symbol
	unless pl_name.is_a? String
	    abort(pos_text(file, ln),
		"Plugin name has to be a string or symbol.")
	end
	lc_pl_name = pl_name.downcase
	import_name = "plugin/#{lc_pl_name}"
	unless Rant::CODE_IMPORTS.include? import_name
	    begin
		require "rant/plugin/#{lc_pl_name}"
		Rant::CODE_IMPORTS << import_name
	    rescue LoadError
		abort(pos_text(file, ln),
		    "no such plugin library -- #{lc_pl_name}")
	    end
	end
	pl_class = nil
	begin
	    pl_class = ::Rant::Plugin.const_get(pl_name)
	rescue NameError, ArgumentError
	    abort(pos_text(file, ln),
		"no such plugin -- #{pl_name}")
	end

	plugin = pl_class.rant_plugin_new(self, ch, *args, &block)
	@plugins << plugin
	vmsg 2, "Plugin `#{plugin.rant_plugin_name}' registered."
	plugin.rant_plugin_init
	plugin
    end

    def enhance(targ, &block)
	prepare_task(targ, block) { |name,pre,blk|
	    t = resolve(name).last
	    if t
		unless t.respond_to? :enhance
		    abort("Can't enhance task `#{name}'")
		end
		t.enhance(pre, &blk)
		return t
	    end
	    warn_msg "enhance \"#{name}\": no such task",
		"Generating a new file task with the given name."
            @node_factory.new_file(self, name, pre, blk)
	}
    end

    def source(opt, rantfile = nil)
	unless rantfile
	    rantfile = opt
	    opt = nil
	end
	make_rf = opt != :n && opt != :now
	rf, is_new = rantfile_for_path(rantfile)
	return false unless is_new
	make rantfile if make_rf
	unless File.exist? rf.path
	    abort("source: No such file -- #{rantfile}")
	end

	load_file rf
    end

    def subdirs(*args)
	args.flatten!
	ch = Rant::Lib::parse_caller_elem(caller[1])
	args.each { |arg|
	    if arg.respond_to? :to_str
		arg = arg.to_str
	    else
		abort_at(ch, "subdirs: arguments must be strings")
	    end
	    loaded = false
	    prev_subdir = @current_subdir
	    begin
		goto arg
                if test(?f, Rant::SUB_RANTFILE)
                    path = Rant::SUB_RANTFILE
                else
                    path = rantfile_in_dir
                end
                if path
                    if defined? @initial_subdir and
                            @initial_subdir == @current_subdir
                        rf, is_new = rantfile_for_path(path, false)
                        @rantfiles.unshift rf if is_new
                    else
                        rf, is_new = rantfile_for_path(path)
                    end
		    load_file rf if is_new
                elsif !@opts[:no_warn_subdir]
                    warn_msg(pos_text(ch[:file], ch[:ln]),
                        "subdirs: No Rantfile in subdir `#{arg}'.")
                end
	    ensure
		goto_project_dir prev_subdir
	    end
	}
    rescue SystemCallError => e
	abort_at(ch, "subdirs: " + e.message)
    end

    def sys(*args, &block)
	args.empty? ? @sys : @sys.sh(*args, &block)
    end

    def var(*args, &block)
	args.empty? ? @var : @var.query(*args, &block)
    end

    def pop_desc
	td = @task_desc
	@task_desc = nil
	td
    end

    def abort(*msg)
	err_msg(msg) unless msg.empty?
	$stderr.puts caller if @opts[:trace_abort]
	raise Rant::RantAbortException
    end

    def abort_at(ch, *msg)
	err_msg(pos_text(ch[:file], ch[:ln]), msg)
	$stderr.puts caller if @opts[:trace_abort]
	raise Rant::RantAbortException
    end

    def show_help
	puts "rant [-f Rantfile] [Options] [targets]"
	puts
	puts "Options are:"
	print option_listing(OPTIONS)
    end

    def show_descriptions
	tlist = select_tasks { |t| t.description }
	def_target = target_list.first
	if tlist.empty?
	    puts "rant         # => " + list_task_names(
		resolve(def_target)).join(', ')
	    msg "No described tasks."
	    return
	end
	prefix = "rant "
	infix = "  # "
        name_length = (tlist.map{ |t| t.to_s.length } << 7).max
	cmd_length = prefix.length + name_length
	unless tlist.first.to_s == def_target
	    defaults = list_task_names(
		resolve(def_target)).join(', ')
	    puts "#{prefix}#{' ' * name_length}#{infix}=> #{defaults}"
	end
	tlist.each { |t|
	    print(prefix + t.to_s.ljust(name_length) + infix)
	    dt = t.description.sub(/\s+$/, "")
	    puts dt.gsub(/\n/, "\n" + ' ' * cmd_length + infix + "  ")
	}
	true
    end

    def list_task_names(*tasks)
	rsl = []
	tasks.flatten.each { |t|
	    if t.respond_to?(:has_actions?) && t.has_actions?
		rsl << t
	    elsif t.respond_to? :prerequisites
		if t.prerequisites.empty?
		    rsl << t
		else
                    t.prerequisites.each { |pre|
                        rsl.concat(list_task_names(
                            resolve(pre, t.project_subdir)))
                    }
		end
	    else
		rsl << t
	    end
	}
	rsl
    end
    private :list_task_names

    def verbose
	@opts[:verbose]
    end

    def quiet?
	@opts[:quiet]
    end

    def pos_text(file, ln)
	t = "in file `#{file}'"
        t << ", line #{ln}" if ln && ln > 0
	t << ": "
    end

    def cmd_msg(cmd)
	puts cmd unless quiet?
    end

    def cmd_print(text)
        print text unless quiet?
        $stdout.flush
    end

    def cmd_targets
	@force_targets + @arg_targets
    end

    def running_task(task)
        if @current_subdir != @last_build_subdir
            cmd_msg "(in #{@current_subdir.empty? ?
                @rootdir : @current_subdir})"
            @last_build_subdir = @current_subdir
        end
        if @opts[:dry_run]
            task.dry_run
            true
        end
    end

    private
    def have_any_task?
        !@tasks.empty?
    end

    def target_list
	if !have_any_task? && @resolve_hooks.empty?
	    abort("No tasks defined for this rant application!")
	end

	target_list = @force_targets + @arg_targets
	if target_list.empty?
	    def_tasks = resolve "default"
	    unless def_tasks.empty?
		target_list << "default"
	    else
		@rantfiles.each { |f|
                    first = f.tasks.first
		    if first
                        target_list << first.reference_name
			break
		    end
		}
	    end
	end
	target_list
    end

    def run_tasks
	target_list.each { |target|
	    if build(target) == 0
		abort("Don't know how to make `#{target}'.")
	    end
        }
    end

    def make(target, *args, &block)
        ch = nil
        if target.respond_to? :to_hash
            targ = target.to_hash
            ch = Rant::Lib.parse_caller_elem(caller[1])
            abort_at(ch, "make: too many arguments") unless args.empty?
            tn = nil
            prepare_task(targ, block, ch) { |name,pre,blk|
                tn = name
                @node_factory.new_file(self, name, pre, blk)
            }
            build(tn)
        elsif target.respond_to? :to_rant_target
            rt = target.to_rant_target
            opt = args.shift
            unless args.empty?
                ch ||= Rant::Lib.parse_caller_elem(caller[1])
                abort_at(ch, "make: too many arguments")
            end
            if block
                ch ||= Rant::Lib.parse_caller_elem(caller[1])
                prepare_task(rt, block, ch) { |name,pre,blk|
                    @node_factory.new_file(self, name, pre, blk)
                }
                build(rt)
            else
                build(rt, opt||{})
            end
        elsif target.respond_to? :rant_gen
            ch = Rant::Lib.parse_caller_elem(caller[1])
            rv = target.rant_gen(self, ch, args, &block)
            unless rv.respond_to? :to_rant_target
                abort_at(ch, "make: invalid generator return value")
            end
            build(rv.to_rant_target)
            rv
        else
            ch = Rant::Lib.parse_caller_elem(caller[1])
            abort_at(ch,
                "make: generator or target as first argument required.")
        end
    end
    public :make

    def build(target, opt = {})
	opt[:force] = true if @force_targets.delete(target)
        opt[:dry_run] = @opts[:dry_run]
	matching_tasks = 0
	old_subdir = @current_subdir
	old_pwd = Dir.pwd
	resolve(target).each { |t|
            unless opt[:type] == :file && !t.file_target?
                matching_tasks += 1
                begin
                    t.invoke(opt) 
                rescue Rant::TaskFail => e
                    err_task_fail(e)
                    abort
                end
            end
	}
	@current_subdir = old_subdir
	Dir.chdir old_pwd
	matching_tasks
    end
    public :build

    def resolve(task_name, rel_project_dir = @current_subdir)
	s = @tasks[expand_path(rel_project_dir, task_name)]
	case s
	when nil
	    @resolve_hooks.each { |s|
		s = s[task_name, rel_project_dir]
		return s if s
	    }
	    []
	when Rant::Node; [s]
	else # assuming list of tasks
	    s
	end
    end
    public :resolve

    def rec_save_resolve(task_name, excl_hook, rel_project_dir = @current_subdir)
	s = @tasks[expand_path(rel_project_dir, task_name)]
	case s
	when nil
	    @resolve_hooks.each { |s|
                next if s == excl_hook
		s = s[task_name, rel_project_dir]
		return s if s
	    }
	    []
	when Rant::Node; [s]
	else
	    s
	end
    end
    public :rec_save_resolve

    def at_resolve(&block)
	@resolve_hooks << block if block
    end
    public :at_resolve

    def at_return(&block)
        hooks = var._get("__at_return__")
        if hooks
            hooks << block
        else
            var._set("__at_return__", [block])
        end
    end
    public :at_return

    def select_tasks
	selection = []
	@rantfiles.each { |rf|
	    rf.tasks.each { |t|
		selection << t if yield t
	    }
	}
	selection
    end
    public :select_tasks

    def load_rantfiles
        unless @arg_rantfiles.empty?
            @arg_rantfiles.each { |fn|
                if test(?f, fn)
                    rf, is_new = rantfile_for_path(fn)
                    load_file rf if is_new
                else
                    abort "No such file -- #{fn}"
                end
            }
            return
        end
        return if have_any_task?
        fn = rantfile_in_dir
        if @opts[:cd_parent]
            old_root = @rootdir
            until fn or @rootdir == "/"
                @rootdir = File.dirname(@rootdir)
                fn = rantfile_in_dir(@rootdir)
            end
            if @rootdir != old_root and fn
                Dir.chdir @rootdir
                cmd_msg "(in #@rootdir)" 
            end
        end
        if fn
            rf, is_new = rantfile_for_path(fn)
            load_file rf if is_new
            return
        end
        have_sub_rantfile = test(?f, Rant::SUB_RANTFILE)
        if have_sub_rantfile || @opts[:look_up]
            cur_dir = Dir.pwd
            until cur_dir == "/"
                cur_dir = File.dirname(cur_dir)
                Dir.chdir cur_dir
                fn = rantfile_in_dir
                if fn
                    @initial_subdir = @rootdir.sub(
                        /^#{Regexp.escape cur_dir}\//, '')
                    @rootdir = cur_dir
                    cmd_msg "(root is #@rootdir, in #@initial_subdir)"
                    @last_build_subdir = @initial_subdir
                    rf, is_new = rantfile_for_path(fn)
                    load_file rf if is_new
                    goto_project_dir @initial_subdir
                    if have_sub_rantfile
                        rf, is_new = rantfile_for_path(
                                Rant::SUB_RANTFILE, false)
                        if is_new
                            @rantfiles.unshift rf
                            load_file rf
                        end
                    end
                    break
                end
            end
        end
        if @rantfiles.empty?
            abort("No Rantfile found, looking for:",
                Rant::RANTFILES.join(", "))
        end
    end

    def load_file(rantfile)
	vmsg 1, "source #{rantfile}"
	@context.instance_eval(File.read(rantfile), rantfile)
    end
    private :load_file

    def rantfile_in_dir(dir=nil)
	::Rant::RANTFILES.each { |rfn|
	    path = dir ? File.join(dir, rfn) : rfn
            return path if test ?f, path
	}
        nil
    end

    def process_args
	old_argv = ARGV.dup
	ARGV.replace(@args.dup)
	cmd_opts = GetoptLong.new(*OPTIONS.collect { |lst| lst[0..-2] })
	cmd_opts.quiet = true
	cmd_opts.each { |opt, value|
	    case opt
	    when "--verbose"; @opts[:verbose] += 1
	    when "--version"
		puts "rant #{Rant::VERSION}"
		raise Rant::RantDoneException
	    when "--help"
		show_help
		raise Rant::RantDoneException
	    when "--directory"
                @rootdir = File.expand_path(value)
	    when "--rantfile"
		@arg_rantfiles << value
	    when "--force-run"
		@force_targets << value
            when "--import"
                import value
	    else
		@opts[opt.sub(/^--/, '').tr('-', "_").to_sym] = true
	    end
	}
    rescue GetoptLong::Error => e
	abort(e.message)
    ensure
	rem_args = ARGV.dup
	ARGV.replace(old_argv)
	rem_args.each { |ra|
	    if ra =~ /(^[^=]+)=([^=]+)$/
		vmsg 2, "var: #$1=#$2"
		@var[$1] = $2
	    else
		@arg_targets << ra
	    end
	}
    end

    def prepare_task(targ, block, clr = caller[2])

	if targ.is_a? Hash
	    targ.reject! { |k, v| clr = v if k == :__caller__ }
	end
	ch = Hash === clr ? clr : Rant::Lib::parse_caller_elem(clr)

	name, pre = normalize_task_arg(targ, ch)

	file, is_new = rantfile_for_path(ch[:file])
	nt = yield(name, pre, block)
	nt.rantfile = file
        nt.project_subdir = @current_subdir
	nt.line_number = ch[:ln]
	nt.description = @task_desc
	@task_desc = nil
	file.tasks << nt
	hash_task nt
	nt
    end
    public :prepare_task

    def hash_task(task)
	n = task.full_name
	et = @tasks[n]
	case et
	when nil
	    @tasks[n] = task
	when Rant::Node
	    mt = [et, task]
	    @tasks[n] = mt
	else # assuming list of tasks
	    et << task
	end
    end

    def normalize_task_arg(targ, ch)
	name = nil
	pre = []
	
	if targ.is_a? Hash
	    if targ.empty?
		abort_at(ch, "Empty hash as task argument, " +
		    "task name required.")
	    end
	    if targ.size > 1
		abort_at(ch, "Too many hash elements, " +
		    "should only be one.")
	    end
	    targ.each_pair { |k,v|
		name = normalize_task_name(k, ch)
		pre = v
	    }
	    unless ::Rant::FileList === pre
		if pre.respond_to? :to_ary
		    pre = pre.to_ary.dup
		    pre.map! { |elem|
			normalize_task_name(elem, ch)
		    }
		else
		    pre = [normalize_task_name(pre, ch)]
		end
	    end
	else
	    name = normalize_task_name(targ, ch)
	end

	[name, pre]
    end
    public :normalize_task_arg

    def normalize_task_name(arg, ch)
	return arg if arg.is_a? String
	if Symbol === arg
	    arg.to_s
	elsif arg.respond_to? :to_str
	    arg.to_str
	else
	    abort_at(ch, "Task name has to be a string or symbol.")
	end
    end

    def rantfile_for_path(path, register=true)
	abs_path = File.expand_path(path)
        file = @rantfiles.find { |rf| rf.path == abs_path }
	if file
	    [file, false]
	else
	    file = Rant::Rantfile.new abs_path
	    file.project_subdir = @current_subdir
	    @rantfiles << file if register
	    [file, true]
	end
    end

    def get_ch_from_backtrace(backtrace)
	backtrace.each { |clr|
	    ch = ::Rant::Lib.parse_caller_elem(clr)
	    if ::Rant::Env.on_windows?
		return ch if @rantfiles.any? { |rf|
		    rf.path.tr("\\", "/").sub(/^\w\:/, '') ==
			ch[:file].tr("\\", "/").sub(/^\w\:/, '')
		}
	    else
		return ch if @rantfiles.any? { |rf|
		    rf.path == ch[:file]
		}
	    end
	}
	nil
    end

    def err_task_fail(e)
	msg = []
	t_msg = ["Task `#{e.tname}' fail."]
	orig = e
	loop { orig = orig.orig; break unless Rant::TaskFail === orig }
	if orig && orig != e && !(Rant::RantAbortException === orig)
            ch = get_ch_from_backtrace(orig.backtrace)
            msg << pos_text(ch[:file], ch[:ln]) if ch
            unless Rant::CommandError === orig && !@opts[:err_commands]
                msg << orig.message
                msg << orig.backtrace[0..4] unless ch
            end
	end
        if e.msg && !e.msg.empty?
            ch = get_ch_from_backtrace(e.backtrace)
            t_msg.unshift(e.msg)
            t_msg.unshift(pos_text(ch[:file], ch[:ln])) if ch
        end
	err_msg msg unless msg.empty?
	err_msg t_msg
    end
end	# class Rant::RantApp

$".concat(['rant/rantlib.rb', 'rant/init.rb', 'rant/rantvar.rb', 'rant/rantsys.rb', 'rant/import/filelist/core.rb', 'rant/node.rb', 'rant/import/nodes/default.rb', 'rant/coregen.rb'])
Rant::CODE_IMPORTS.concat %w(nodes/default
    )

# Catch a `require "rant"', sad...
alias require_backup_by_rant require
def require libf
    if libf == "rant"
        # TODO: needs rework! look at lib/rant.rb
	self.class.instance_eval { include Rant }
    else
	begin
	    require_backup_by_rant libf
	rescue
	    raise $!, caller
	end
    end
end

exit Rant.run