From 52f8b9c0b1e1606a4260ef2e0df4d525497691b1 Mon Sep 17 00:00:00 2001 From: Ralph Amissah Date: Sat, 7 Jul 2007 09:21:47 +0100 Subject: cgi-sample search form; texinfo fix; xml scaffold; help, man pages etc. visited; screen output, color set to true; docbook entries removed * cgi generated sample search form * order results on files of the same title, in multiple files (with different filenames) * postgresql, character case sensitivity, control, on/off * tail decoration, gplv3 & sisu info * texinfo/info (pinfo) module starts to do something vaguely useful again [not a much used module, testing required] * print XML rendition of document structure to screen -T * sisurc.yml default, color set to true [apologies if this causes anyone any inconvenience, it is configurable in sisurc.yml] * help, man pages, README (man(8) related and env, 'sisu -V') * docbook entries removed for the present time * sisu-install (install ruby rant script renamed) and permissions set to executable --- sisu-install | 3088 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3088 insertions(+) create mode 100755 sisu-install (limited to 'sisu-install') diff --git a/sisu-install b/sisu-install new file mode 100755 index 00000000..3d78c6c1 --- /dev/null +++ b/sisu-install @@ -0,0 +1,3088 @@ +#!/usr/bin/env ruby + +# sisu-install - Monolithic rant script, autogenerated by rant-import 0.5.8. +# +# Copyright (C) 2005 Stefan Lang +# +# 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 =~ /^#$/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 -- cgit v1.2.3