#!/usr/bin/env ruby
#
# Setup.rb, 2010-02-07 08:33:15
#
# This is a stand-alone bundle of the setup.rb application.
# You can place it in your projects script/ directory, or
# rename it to 'setup.rb' and place it in your project's
# root directory (just like old times).
#
module Setup
  VERSION   = '5.0.1'
end
class << File #:nodoc: all
  unless respond_to?(:read)   # Ruby 1.6 and less
    def read(fname)
      open(fname){ |f| return f.read }
    end
  end
  def dir?(path)
    directory?((path[-1,1] == '/') ? path : path + '/')
  end
end
unless Errno.const_defined?(:ENOTEMPTY)   # Windows?
  module Errno  #:nodoc:
    class ENOTEMPTY  #:nodoc:
    end
  end
end
module Setup
  META_EXTENSION_DIR = '.setup'
  FILETYPES = %w( bin lib ext data etc man doc )
  INSTALL_RECORD = 'SetupReceipt'  #'.cache/setup/installedfiles'
end
module Setup
  class Project
    ROOT_MARKER = '{setup.rb,script/setup,meta/,MANIFEST,lib/}'
    def rootdir
      @rootdir ||= (
        root = Dir[File.join(Dir.pwd, ROOT_MARKER)].first
        if !root
          raise Error, "not a project directory"
        else
          Dir.pwd
        end
      )
    end
    def name
      @name = (
        if file = Dir["{script/setup,meta,.meta}/name"].first
          File.read(file).strip
        else
          nil
        end
      )
    end
    def loadpath
      @loadpath ||= (
        if file = Dir.glob('{script/setup,meta,.meta}/loadpath').first
          raw = File.read(file).strip.chomp(']')
          raw.split(/[\n,]/).map do |e|
            e.strip.sub(/^[\[-]\s*/,'')
          end
        else
          nil
        end
      )
    end
    def extconfs
      @extconfs ||= Dir['ext/**/extconf.rb']
    end
    def extensions
      @extensions ||= extconfs.collect{ |f| File.dirname(f) }
    end
    def compiles?
      !extensions.empty?
    end
  end
end
module Setup
  class Session
    attr :options
    def initialize(options={})
      @options = options
      self.io ||= StringIO.new  # log instead ?
    end
    def io
      @options[:io]
    end
    def io=(anyio)
      @options[:io] = anyio
    end
    def trace?; @options[:trace]; end
    def trace=(val)
      @options[:trace] = val
    end
    def trial?; @options[:trial]; end
    def trial=(val)
      @options[:trial] = val
    end
    def quiet?; @options[:quiet]; end
    def quiet=(val)
      @options[:quiet] = val
    end
    def force?; @options[:force]; end
    def force=(val)
      @options[:force] = val
    end
    def compile?
      configuration.compile? && project.compiles?
    end
    def all
      config
      if compile?
        make
      end
      if configuration.test?
        ok = test
        exit 1 unless ok
      end
      install
      if configuration.ri?
        document
      end
    end
    def config
      log_header('Configure')
      if configuration.save_config
        io.puts "Configuration saved." unless quiet?
      else
        io.puts "Configuration current." unless quiet?
      end
      puts configuration if trace? && !quiet?
      compiler.configure if compile? #compiler.compiles?
    end
    def make
      abort "must run 'setup config' first" unless configuration.exist?
      log_header('Compile')
      compiler.compile
    end
    alias_method :setup, :make
    def install
      abort "must run 'setup config' first" unless configuration.exist?
      log_header('Install')
      installer.install
    end
    def test
      return true unless tester.testable?
      log_header('Test')
      tester.test
    end
    def document
      log_header('Document')
      documentor.document
    end
    def clean
      log_header('Clean')
      compiler.clean
    end
    def distclean
      log_header('Distclean')
      compiler.distclean
    end
    def uninstall
      if !File.exist?(INSTALL_RECORD)
        io.puts "Nothing is installed."
        return
      end
      log_header('Uninstall')
      uninstaller.uninstall
      io.puts('Ok.')
    end
    def show
      puts configuration
    end
    def project
      @project ||= Project.new
    end
    def configuration
      @configuration ||= Configuration.new
    end
    def compiler
      @compiler ||= Compiler.new(project, configuration, options)
    end
    def installer
      @installer ||= Installer.new(project, configuration, options)
    end
    def tester
      @tester ||= Tester.new(project, configuration, options)
    end
    def documentor
      @documentor ||= Documentor.new(project, configuration, options)
    end
    def uninstaller
      @uninstaller ||= Uninstaller.new(project, configuration, options)
    end
    def log_header(phase)
      return if quiet?
      if trial?
        str = "#{phase.upcase} (trail run)"
      else
        str = "#{phase.upcase}"
      end
      line = "- " * 35
      line[0..str.size+3] = str
      io.puts("\n- - #{line}\n\n")
    end
  end
end
module Setup
  class Base
    attr :project
    attr :config
    attr_accessor :trial
    attr_accessor :trace
    attr_accessor :quiet
    attr_accessor :force
    attr_accessor :io
    def initialize(project, configuration, options={})
      @project = project
      @config  = configuration
      initialize_hooks
      options.each do |k,v|
        __send__("#{k}=", v) if respond_to?("#{k}=")
      end
    end
    def initialize_hooks
      file = META_EXTENSION_DIR + "/#{self.class.name.downcase}.rb"
      if File.exist?(file)
        script = File.read(file)
        (class << self; self; end).class_eval(script)
      end
    end
    def trial? ; @trial ; end
    def trace? ; @trace ; end
    def quiet? ; @quiet ; end
    def force? ; @force ; end
    def rootdir
      project.rootdir
    end
    def bash(*args)
      $stderr.puts args.join(' ') if trace?
      system(*args) or raise RuntimeError, "system(#{args.map{|a| a.inspect }.join(' ')}) failed"
    end
    alias_method :command, :bash
    def ruby(*args)
      bash(config.rubyprog, *args)
    end
    def trace_off #:yield:
      begin
        save, @trace = trace?, false
        yield
      ensure
        @trace = save
      end
    end
    def rm_f(path)
      io.puts "rm -f #{path}" if trace? or trial?
      return if trial?
      force_remove_file(path)
    end
    def force_remove_file(path)
      begin
        remove_file(path)
      rescue
      end
    end
    def remove_file(path)
      File.chmod 0777, path
      File.unlink(path)
    end
    def rmdir(path)
      io.puts "rmdir #{path}" if trace? or trial?
      return if trial?
      Dir.rmdir(path)
    end
  end
  class Error < StandardError
  end
end
module Setup
  class Compiler < Base
    def compiles?
      !extdirs.empty?
    end
    def configure
      extdirs.each do |dir|
        Dir.chdir(dir) do
          if File.exist?('extconf.rb') && !FileUtils.uptodate?('Makefile', ['extconf.rb'])
            ruby("extconf.rb")
          end
        end
      end
    end
    def compile
      extdirs.each do |dir|
        Dir.chdir(dir) do
          make
        end
      end
    end
    def clean
      extdirs.each do |dir|
        Dir.chdir(dir) do
          make('clean')
        end
      end
    end
    def distclean
      extdirs.each do |dir|
        Dir.chdir(dir) do
          make('distclean')
        end
      end
    end
    def extdirs
      Dir['ext/**/*/{MANIFEST,extconf.rb}'].map do |f|
        File.dirname(f)
      end.uniq
    end
    def make(task=nil)
      return unless File.exist?('Makefile')
      bash(*[config.makeprog, task].compact)
    end
  end
end
require 'rbconfig'
require 'fileutils'
require 'erb'
require 'yaml'
require 'shellwords'
module Setup
  class Configuration
    RBCONFIG  = ::Config::CONFIG
    CONFIG_FILE = 'SetupConfig'  # '.cache/setup/config'
    META_CONFIG_FILE = META_EXTENSION_DIR + '/configuration.rb'
    def self.options
      @@options ||= []
    end
    def self.option(name, *args) #type, description)
      options << [name.to_s, *args] #type, description]
      attr_accessor(name)
    end
    option :prefix          , :path, 'path prefix of target environment'
    option :bindir          , :path, 'directory for commands'
    option :libdir          , :path, 'directory for libraries'
    option :datadir         , :path, 'directory for shared data'
    option :mandir          , :path, 'directory for man pages'
    option :docdir          , :path, 'directory for documentation'
    option :rbdir           , :path, 'directory for ruby scripts'
    option :sodir           , :path, 'directory for ruby extentions'
    option :sysconfdir      , :path, 'directory for system configuration files'
    option :localstatedir   , :path, 'directory for local state data'
    option :libruby         , :path, 'directory for ruby libraries'
    option :librubyver      , :path, 'directory for standard ruby libraries'
    option :librubyverarch  , :path, 'directory for standard ruby extensions'
    option :siteruby        , :path, 'directory for version-independent aux ruby libraries'
    option :siterubyver     , :path, 'directory for aux ruby libraries'
    option :siterubyverarch , :path, 'directory for aux ruby binaries'
    option :rubypath        , :prog, 'path to set to #! line'
    option :rubyprog        , :prog, 'ruby program used for installation'
    option :makeprog        , :prog, 'make program to compile ruby extentions'
    option :extconfopt      , :opts, 'options to pass-thru to extconf.rb'
    option :shebang         , :pick, 'shebang line (#!) editing mode (all,ruby,never)'
    option :no_test, :t     , :bool, 'run pre-installation tests'
    option :no_ri,   :d     , :bool, 'generate ri documentation'
    option :no_doc          , :bool, 'install doc/ directory'
    option :no_ext          , :bool, 'compile/install ruby extentions'
    option :install_prefix  , :path, 'install to alternate root location'
    option :root            , :path, 'install to alternate root location'
    option :installdirs     , :pick, 'install location mode (site,std,home)'  #, local)
    option :type            , :pick, 'install location mode (site,std,home)'
    ::Config::CONFIG.each do |key,val|
      next if key == "configure_args"
      name = key.to_s.downcase
      define_method(name){ val }
    end
    config_args = Shellwords.shellwords(::Config::CONFIG["configure_args"])
    config_args.each do |ent|
      if ent.index("=")
        key, val = *ent.split("=")
      else
        key, val = ent, true
      end
      name = key.downcase
      name = name.sub(/^--/,'')
      name = name.gsub(/-/,'_')
      define_method(name){ val }
    end
    def options
      self.class.options
    end
    def initialize(values={})
      initialize_metaconfig
      initialize_defaults
      initialize_environment
      initialize_configfile
      values.each{ |k,v| __send__("#{k}=", v) }
      yeild(self) if block_given?
    end
    def initialize_metaconfig
      if File.exist?(META_CONFIG_FILE)
        script = File.read(META_CONFIG_FILE)
        (class << self; self; end).class_eval(script)
      end
    end
    def initialize_defaults
      self.type    = 'site'
      self.no_ri   = true
      self.no_test = true
      self.no_doc  = false
      self.no_ext  = false
    end
    def initialize_environment
      options.each do |name, *args|
        if value = ENV["RUBYSETUP_#{name.to_s.upcase}"]
          __send__("#{name}=", value)
        end
      end
    end
    def initialize_configfile
      if File.exist?(CONFIG_FILE)
        erb = ERB.new(File.read(CONFIG_FILE))
        txt = erb.result(binding)
        dat = YAML.load(txt)
        dat.each do |k, v|
          next if 'type' == k
          next if 'installdirs' == k
          k = k.gsub('-','_')
          __send__("#{k}=", v)
        end
        if dat['type']
          self.type = dat['type']
        end
        if dat['installdirs']
          self.installdirs = dat['installdirs']
        end
      end
    end
    def base_bindir
      @base_bindir ||= subprefix('bindir')
    end
    def base_libdir
      @base_libdir ||= subprefix('libdir')
    end
    def base_datadir
      @base_datadir ||= subprefix('datadir')
    end
    def base_mandir
      @base_mandir ||= subprefix('mandir')
    end
    def base_docdir
      @base_docdir || File.dirname(subprefix('docdir'))
    end
    def base_rubylibdir
      @rubylibdir ||= subprefix('rubylibdir')
    end
    def base_rubyarchdir
      @base_rubyarchdir ||= subprefix('archdir')
    end
    def base_sysconfdir
      @base_sysconfdir ||= subprefix('sysconfdir')
    end
    def base_localstatedir
      @base_localstatedir ||= subprefix('localstatedir')
    end
    def type
      @type ||= 'site'
    end
    def type=(val)
      @type = val
      case val.to_s
      when 'std', 'ruby'
        @rbdir = librubyver       #'$librubyver'
        @sodir = librubyverarch   #'$librubyverarch'
      when 'site'
        @rbdir = siterubyver      #'$siterubyver'
        @sodir = siterubyverarch  #'$siterubyverarch'
      when 'home'
        self.prefix = File.join(home, '.local')  # TODO: Use XDG
        @rbdir = nil #'$libdir/ruby'
        @sodir = nil #'$libdir/ruby'
      else
        raise Error, "bad config: use type=(std|site|home) [#{val}]"
      end
    end
    alias_method :installdirs, :type
    alias_method :installdirs=, :type=
    alias_method :install_prefix, :root
    alias_method :install_prefix=, :root=
    def prefix
      @prefix ||= RBCONFIG['prefix']
    end
    def prefix=(path)
      @prefix = pathname(path)
    end
    def libruby
      @libruby ||= RBCONFIG['prefix'] + "/lib/ruby"
    end
    def libruby=(path)
      path = pathname(path)
      @librubyver = librubyver.sub(libruby, path)
      @librubyverarch = librubyverarch.sub(libruby, path)
      @libruby = path
    end
    def librubyver
      @librubyver ||= RBCONFIG['rubylibdir']
    end
    def librubyver=(path)
      @librubyver = pathname(path)
    end
    def librubyverarch
      @librubyverarch ||= RBCONFIG['archdir']
    end
    def librubyverarch=(path)
      @librubyverarch = pathname(path)
    end
    def siteruby
      @siteruby ||= RBCONFIG['sitedir']
    end
    def siteruby=(path)
      path = pathname(path)
      @siterubyver = siterubyver.sub(siteruby, path)
      @siterubyverarch = siterubyverarch.sub(siteruby, path)
      @siteruby = path
    end
    def siterubyver
      @siterubyver ||= RBCONFIG['sitelibdir']
    end
    def siterubyver=(path)
      @siterubyver = pathname(path)
    end
    def siterubyverarch
      @siterubyverarch ||= RBCONFIG['sitearchdir']
    end
    def siterubyverarch=(path)
      @siterubyverarch = pathname(path)
    end
    def bindir
      @bindir || File.join(prefix, base_bindir)
    end
    def bindir=(path)
      @bindir = pathname(path)
    end
    def libdir
      @libdir || File.join(prefix, base_libdir)
    end
    def libdir=(path)
      @libdir = pathname(path)
    end
    def datadir
      @datadir || File.join(prefix, base_datadir)
    end
    def datadir=(path)
      @datadir = pathname(path)
    end
    def mandir
      @mandir || File.join(prefix,  base_mandir)
    end
    def mandir=(path)
      @mandir = pathname(path)
    end
    def docdir
      @docdir || File.join(prefix, base_docdir)
    end
    def docdir=(path)
      @docdir = pathname(path)
    end
    def rbdir
      @rbdir || File.join(prefix, base_rubylibdir)
    end
    def sodir
      @sodir || File.join(prefix, base_rubyarchdir)
    end
    def sysconfdir
      @sysconfdir ||= base_sysconfdir
    end
    def sysconfdir=(path)
      @sysconfdir = pathname(path)
    end
    def localstatedir
      @localstatedir ||= base_localstatedir
    end
    def localstatedir=(path)
      @localstatedir = pathname(path)
    end
    def rubypath
      @rubypath ||= File.join(RBCONFIG['bindir'], RBCONFIG['ruby_install_name'] + RBCONFIG['EXEEXT'])
    end
    def rubypath=(path)
      @rubypath = pathname(path)
    end
    def rubyprog
      @rubyprog || rubypath
    end
    def rubyprog=(command)
      @rubyprog = command
    end
    def makeprog
      @makeprog ||= (
        if arg = RBCONFIG['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
          arg.sub(/'/, '').split(/=/, 2)[1]
        else
          'make'
        end
      )
    end
    def makeprog=(command)
      @makeprog = command
    end
    def extconfopt
      @extconfopt ||= ''
    end
    def extconfopt=(string)
      @extconfopt = string
    end
    def shebang
      @shebang ||= 'ruby'
    end
    def shebang=(val)
      if %w(all ruby never).include?(val)
        @shebang = val
      else
        raise Error, "bad config: use SHEBANG=(all|ruby|never) [#{val}]"
      end
    end
    def no_ext
      @no_ext
    end
    def no_ext=(val)
      @no_ext = boolean(val)
    end
    def no_test
      @no_test
    end
    def no_test=(val)
      @no_test = boolean(val)
    end
    def no_doc
      @no_doc
    end
    def no_doc=(val)
      @no_doc = boolean(val)
    end
    def no_ri
      @no_ri
    end
    def no_ri=(val)
      @no_ri = boolean(val)
    end
    def compile?
      !no_ext
    end
    def test?
      !no_test
    end
    def ri?
      !no_ri
    end
    def doc?
      !no_doc
    end
    def to_h
      h = {}
      options.each do |name, *args|
        h[name.to_s] = __send__(name)
      end
      h
    end
    def to_s
      to_yaml.sub(/\A---\s*\n/,'')
    end
    def to_yaml(*args)
      to_h.to_yaml(*args)
    end
    def save_config
      out = to_yaml
      if not File.exist?(File.dirname(CONFIG_FILE))
        FileUtils.mkdir_p(File.dirname(CONFIG_FILE))
      end
      if File.exist?(CONFIG_FILE)
        txt = File.read(CONFIG_FILE)
        return nil if txt == out
      end          
      File.open(CONFIG_FILE, 'w'){ |f| f << out }
      true
    end
    def exist?
      File.exist?(CONFIG_FILE)
    end
  private
    def pathname(path)
      path.gsub(%r<\\$([^/]+)>){ self[$1] }
    end
    def boolean(val, name=nil)
      case val
      when true, false, nil
        val
      else
        case val.to_s.downcase
        when 'y', 'yes', 't', 'true'
           true
        when 'n', 'no', 'f', 'false'
           false
        else
          raise Error, "bad config: use --#{name}=(yes|no) [\#{val}]"
        end
      end
    end
    def subprefix(path, with='')
      val = RBCONFIG[path]
      raise "Unknown path -- #{path}" if val.nil?
      prefix = Regexp.quote(RBCONFIG['prefix'])
      val.sub(/\A#{prefix}/, with)
    end
    def home
      ENV['HOME'] || raise(Error, 'HOME is not set.')
    end
  end #class ConfigTable
end #module Setup
=begin
    def inintialize_metaconfig
      path = Dir.glob(METACONFIG_FILE).first
      if path && File.file?(path)
        MetaConfigEnvironment.new(self).instance_eval(File.read(path), path)
      end
    end
    class MetaConfigEnvironment
      def initialize(config) #, installer)
        @config    = config
      end
      def config_names
        @config.descriptions.collect{ |n, t, d| n.to_s }
      end
      def config?(name)
        @config.descriptions.find do |sym, type, desc|
          sym.to_s == name.to_s
        end
      end
      def bool_config?(name)
        @config.descriptions.find do |sym, type, desc|
          sym.to_s == name.to_s && type == :bool
        end
      end
      def path_config?(name)
        @config.descriptions.find do |sym, type, desc|
          sym.to_s == name.to_s && type == :path
        end
      end
      def value_config?(name)
        @config.descriptions.find do |sym, type, desc|
          sym.to_s == name.to_s && type != :prog
        end
      end
      def add_config(name, default, desc)
        @config.descriptions << [name.to_sym, nil, desc]
      end
      def add_bool_config(name, default, desc)
        @config.descriptions << [name.to_sym, :bool, desc]
      end
      def add_path_config(name, default, desc)
        @config.descriptions << [name.to_sym, :path, desc]
      end
      def set_config_default(name, default)
        @config[name] = default
      end
      def remove_config(name)
        item = @config.descriptions.find do |sym, type, desc|
          sym.to_s == name.to_s
        end
        index = @config.descriptions.index(item)
        @config.descriptions.delete(index)
      end
    end
=end
module Setup
  class Documentor < Base
    def document
      return if config.no_doc
      exec_ri
    end
    def exec_ri
      case config.type #installdirs
      when 'std', 'ruby'
        output  = "--ri-site"
      when 'site'
        output = "--ri-site"
      when 'home'
        output = "--ri"
      else
        abort "bad config: should not be possible -- type=#{config.type}"
      end
      if File.exist?('.document')
        files = File.read('.document').split("\n")
        files.reject!{ |l| l =~ /^\s*[#]/ || l !~ /\S/ }
        files.collect!{ |f| f.strip }
      else
        files = []
        files << 'lib' if File.directory?('lib')
        files << 'ext' if File.directory?('ext')
      end
      opt = []
      opt << "-U"
      opt << "-q" #if quiet?
      opt << output
      opt << files
      opt = opt.flatten
      cmd = "rdoc " + opt.join(' ')
      if trial?
        puts cmd
      else
        begin
          success = system(cmd)
          raise unless success
          io.puts "Ok ri." #unless quiet?
        rescue Exception
          $stderr.puts "ri generation failed"
          $stderr.puts "command was: '#{cmd}'"
        end
      end
    end
    def exec_rdoc
      main = Dir.glob("README{,.*}", File::FNM_CASEFOLD).first
      if File.exist?('.document')
        files = File.read('.document').split("\n")
        files.reject!{ |l| l =~ /^\s*[#]/ || l !~ /\S/ }
        files.collect!{ |f| f.strip }
      else
        files = []
        files << main  if main
        files << 'lib' if File.directory?('lib')
        files << 'ext' if File.directory?('ext')
      end
      checkfiles = (files + files.map{ |f| Dir[File.join(f,'*','**')] }).flatten.uniq
      if FileUtils.uptodate?('doc/rdoc', checkfiles)
        puts "RDocs look current."
        return
      end
      output    = 'doc/rdoc'
      title     = (PACKAGE.capitalize + " API").strip if PACKAGE
      template  = config.doctemplate || 'html'
      opt = []
      opt << "-U"
      opt << "-q" #if quiet?
      opt << "--op=#{output}"
      opt << "--title=#{title}"
      opt << "--main=#{main}"     if main
      opt << files
      opt = opt.flatten
      cmd = "rdoc " + opt.join(' ')
      if trial?
        puts cmd 
      else
        begin
          system(cmd)
          puts "Ok rdoc." unless quiet?
        rescue Exception
          puts "Fail rdoc."
          puts "Command was: '#{cmd}'"
          puts "Proceeding with install anyway."
        end
      end
    end
  end
end
module Setup
  class Installer < Base
    def install_prefix
      config.install_prefix
    end
    def install
      Dir.chdir(rootdir) do
        install_bin
        install_ext
        install_lib
        install_data
        install_man
        install_doc
        install_etc
        prune_install_record
      end
    end
    def install_bin
      return unless directory?('bin')
      report_transfer('bin', config.bindir)
      files = files('bin')
      install_files('bin', files, config.bindir, 0755)
    end
    def install_ext
      return unless directory?('ext')
      report_transfer('ext', config.sodir)
      files = files('ext')
      files = select_dllext(files)
      files.each do |file|
        name = File.join(File.dirname(File.dirname(file)), File.basename(file))
        dest = destination(config.sodir, name)
        install_file('ext', file, dest, 0555, install_prefix)
      end
    end
    def install_lib
      return unless directory?('lib')
      report_transfer('lib', config.rbdir)
      files = files('lib')
      install_files('lib', files, config.rbdir, 0644)
    end
    def install_data
      return unless directory?('data')
      report_transfer('data', config.datadir)
      files = files('data')
      install_files('data', files, config.datadir, 0644)
    end
    def install_etc
      return unless directory?('etc')
      report_transfer('etc', config.sysconfdir)
      files = files('etc')
      install_files('etc', files, config.sysconfdir, 0644)
    end
    def install_man
      return unless directory?('man')
      report_transfer('man', config.mandir)
      files = files('man')
      install_files('man', files, config.mandir, 0644)
    end
    def install_doc
      return unless config.doc?
      return unless directory?('doc')
      return unless project.name
      dir = File.join(config.docdir, "ruby-{project.name}")
      report_transfer('doc', dir)
      files = files('doc')
      install_files('doc', files, dir, 0644)
    end
  private
    def report_transfer(source, directory)
      unless quiet?
        if install_prefix
          out = File.join(install_prefix, directory)
        else
          out = directory
        end
        io.puts "* #{source} -> #{out}"
      end
    end
    def directory?(path)
      File.directory?(path)
    end
    def files(dir)
      files = Dir["#{dir}/**/*"]
      files = files.select{ |f| File.file?(f) }
      files = files.map{ |f| f.sub("#{dir}/", '') }
      files
    end
    def select_dllext(files)
      ents = files.select do |file| 
        File.extname(file) == ".#{dllext}"
      end
      if ents.empty? && !files.empty?
        raise Error, "ruby extention not compiled: 'setup.rb make' first"
      end
      ents
    end
    def dllext
      config.dlext
    end
    def install_files(dir, list, dest, mode)
      list.each do |fname|
        rdest = destination(dest, fname)
        install_file(dir, fname, rdest, mode, install_prefix)
      end
    end
    def install_file(dir, from, dest, mode, prefix=nil)
      mkdir_p(File.dirname(dest))
      if trace? or trial?
        io.puts "install #{dir}/#{from} #{dest}"
      end
      return if trial?
      str = binread(File.join(dir, from))
      if diff?(str, dest)
        trace_off {
          rm_f(dest) if File.exist?(dest)
        }
        File.open(dest, 'wb'){ |f| f.write(str) }
        File.chmod(mode, dest)
      end
      record_installation(dest) # record file as installed
    end
    def mkdir_p(dirname) #, prefix=nil)
      return if File.directory?(dirname)
      io.puts "mkdir -p #{dirname}" if trace? or trial?
      return if trial?
      dirs = File.expand_path(dirname).split(%r<(?=/)>)
      if /\A[a-z]:\z/i =~ dirs[0]
        disk = dirs.shift
        dirs[0] = disk + dirs[0]
      end
      dirs.each_index do |idx|
        path = dirs[0..idx].join('')
        unless File.dir?(path)
          Dir.mkdir(path)
        end
        record_installation(path)  # record directories made
      end
    end
    def record_installation(path)
      File.open(install_record, 'a') do |f|
        f.puts(path)
      end
    end
    def prune_install_record
      entries = File.read(install_record).split("\n")
      entries.uniq!
      File.open(install_record, 'w') do |f|
        f << entries.join("\n")
        f << "\n"
      end
    end
    def install_record
      @install_record ||= (
        file = INSTALL_RECORD
        dir  = File.dirname(file)
        unless File.directory?(dir)
          FileUtils.mkdir_p(dir)
        end
        file
      )
    end
    def destination(dir, file)
      dest = install_prefix ? File.join(install_prefix, File.expand_path(dir)) : dir
      dest = File.join(dest, file) #if File.dir?(dest)
      dest = File.expand_path(dest)
      dest
    end
    def diff?(new_content, path)
      return true unless File.exist?(path)
      new_content != binread(path)
    end
    def binread(fname)
      File.open(fname, 'rb') do |f|
        return f.read
      end
    end
    def install_shebang(files, dir)
      files.each do |file|
        path = File.join(dir, File.basename(file))
        update_shebang_line(path)
      end
    end
    def update_shebang_line(path)
      return if trial?
      return if config.shebang == 'never'
      old = Shebang.load(path)
      if old
        if old.args.size > 1
          $stderr.puts "warning: #{path}"
          $stderr.puts "Shebang line has too many args."
          $stderr.puts "It is not portable and your program may not work."
        end
        new = new_shebang(old)
        return if new.to_s == old.to_s
      else
        return unless config.shebang == 'all'
        new = Shebang.new(config.rubypath)
      end
      $stderr.puts "updating shebang: #{File.basename(path)}" if trace?
      open_atomic_writer(path) do |output|
        File.open(path, 'rb') do |f|
          f.gets if old   # discard
          output.puts new.to_s
          output.print f.read
        end
      end
    end
    def new_shebang(old)
      if /\Aruby/ =~ File.basename(old.cmd)
        Shebang.new(config.rubypath, old.args)
      elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby'
        Shebang.new(config.rubypath, old.args[1..-1])
      else
        return old unless config.shebang == 'all'
        Shebang.new(config.rubypath)
      end
    end
    def open_atomic_writer(path, &block)
      tmpfile = File.basename(path) + '.tmp'
      begin
        File.open(tmpfile, 'wb', &block)
        File.rename tmpfile, File.basename(path)
      ensure
        File.unlink tmpfile if File.exist?(tmpfile)
      end
    end
    class Shebang
      def Shebang.load(path)
        line = nil
        File.open(path) {|f|
          line = f.gets
        }
        return nil unless /\A#!/ =~ line
        parse(line)
      end
      def Shebang.parse(line)
        cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ')
        new(cmd, args)
      end
      def initialize(cmd, args = [])
        @cmd = cmd
        @args = args
      end
      attr_reader :cmd
      attr_reader :args
      def to_s
        "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}")
      end
    end
  end
end
module Setup
  class Tester < Base
    RUBYSCRIPT  = META_EXTENSION_DIR + '/testrc.rb'
    SHELLSCRIPT = 'script/test'
    def testable?
      return false if config.no_test
      return true  if File.exist?(RUBYSCRIPT)
      return true  if File.exist?(SHELLSCRIPT)
      false
    end
    def test
      return true if !testable?
      if File.exist?(RUBYSCRIPT)
        test_rubyscript
      elsif File.exist?(SHELLSCRIPT)
        test_shellscript
      end
    end
    def test_shellscript
      bash(SHELLSCRIPT)
    end
    def test_rubyscript
      ruby(RUBYSCRIPT)
    end
  end
end
module Setup
  class Uninstaller < Base
    def uninstall
      return unless File.exist?(INSTALL_RECORD)
      files = []
      dirs  = []
      paths.each do |path|
        dirs  << path if File.dir?(path)
        files << path if File.file?(path)
      end
      if dirs.empty? && files.empty?
        io.outs "Nothing to remove."
        return
      end
      files.sort!{ |a,b| b.size <=> a.size }
      dirs.sort!{ |a,b| b.size <=> a.size }
      if !force? && !trial?
        puts (files + dirs).collect{ |f| "#{f}" }.join("\n")
        puts
        puts "Must use --force option to remove these files and directories that become empty."
        return
      end
      files.each do |file|
        rm_f(file)
      end
      dirs.each do |dir|
        entries = Dir.entries(dir)
        entries.delete('.')
        entries.delete('..')
          rmdir(dir) if entries.empty?
      end
      rm_f(INSTALL_RECORD)
    end
  private
    def paths
      @paths ||= (
        lines = File.read(INSTALL_RECORD).split(/\s*\n/)
        lines = lines.map{ |line| line.strip }
        lines = lines.uniq
        lines = lines.reject{ |line| line.empty? }       # skip blank lines
        lines = lines.reject{ |line| line[0,1] == '#' }  # skip blank lines
        lines
      )
    end
  end
end
require 'optparse'
module Setup
  class Command
    def self.run(*argv)
      new.run(*argv)
    end
    def self.tasks
      @tasks ||= {}
    end
    def self.order
      @order ||= []
    end
    def self.task(name, description)
      tasks[name] = description
      order << name
    end
    task 'all'      , "config, setup, test, install"
    task 'config'   , "saves your configuration"
    task 'show'     , "show current configuration"
    task 'make'     , "compile ruby extentions"
    task 'test'     , "run test suite"
    task 'doc'      , "generate ri documentation"
    task 'install'  , "install project files"
    task 'uninstall', "uninstall previously installed files"
    task 'clean'    , "does `make clean' for each extention"
    task 'distclean', "does `make distclean' for each extention"
    def run(*argv)
      ARGV.replace(argv) unless argv.empty?
      task = ARGV.find{ |a| a !~ /^[-]/ }
      task = 'all' unless task
      unless task_names.include?(task)
        $stderr.puts "Not a valid task -- #{task}"
        exit 1
      end
      parser  = OptionParser.new
      options = {}
      parser.banner = "Usage: #{File.basename($0)} [TASK] [OPTIONS]"
      optparse_header(parser, options)
      case task
      when 'all'
        optparse_all(parser, options)
      when 'config'
        optparse_config(parser, options)
      when 'install'
        optparse_install(parser, options)
      end
      optparse_common(parser, options)
      begin
        parser.parse!(ARGV)
      rescue OptionParser::InvalidOption
        $stderr.puts $!.to_s.capitalize
        exit 1
      end
      rootdir = session.project.rootdir
      print_header
      begin
        session.__send__(task)
      rescue Error => err
        raise err if $DEBUG
        $stderr.puts $!.message
        $stderr.puts "Try 'setup.rb --help' for detailed usage."
        abort $!.message #exit 1
      end
      puts unless session.quiet?
    end
    def session
      @session ||= Session.new(:io=>$stdout)
    end
    def configuration
      @configuration ||= session.configuration
    end
    def optparse_header(parser, options)
      parser.banner = "USAGE: #{File.basename($0)} [command] [options]"
    end
    def optparse_all(parser, options)
      optparse_config(parser, options)
    end
    def optparse_config(parser, options)
      parser.separator ""
      parser.separator "Configuration options:"
      configuration.options.each do |args|
        args = args.dup
        desc = args.pop
        type = args.pop
        name, shortcut = *args
        optname = name.to_s.gsub('_', '-')
        case type
        when :bool
          if optname.index('no-') == 0
            optname = "[no-]" + optname.sub(/^no-/, '')
            opts = shortcut ? ["-#{shortcut}", "--#{optname}", desc] : ["--#{optname}", desc]
            parser.on(*opts) do |val|
              configuration.__send__("#{name}=", !val)
            end
          else
            optname = "[no-]" + optname.sub(/^no-/, '')
            opts = shortcut ? ["-#{shortcut}", "--#{optname}", desc] : ["--#{optname}", desc]
            parser.on(*opts) do |val|
              configuration.__send__("#{name}=", val)
            end
          end
        else
          opts = shortcut ? ["-#{shortcut}", "--#{optname} #{type.to_s.upcase}", desc] : ["--#{optname} #{type.to_s.upcase}", desc]
          parser.on(*opts) do |val|
            configuration.__send__("#{name}=", val)
          end
        end
      end
    end
    def optparse_install(parser, options)
      parser.separator ""
      parser.separator "Install options:"
      parser.on("--prefix PATH", "Installation prefix") do |val|
        configuration.install_prefix = val
      end
    end
    def optparse_common(parser, options)
      parser.separator ""
      parser.separator "General options:"
      parser.on("-q", "--quiet", "Suppress output") do
        session.quiet = true
      end
      parser.on("-f", "--force", "Force operation") do
        session.force = true
      end
      parser.on("--trace", "--verbose", "Watch execution") do |val|
        session.trace = true
      end
      parser.on("--trial", "--no-harm", "Do not write to disk") do |val|
        session.trial = true
      end
      parser.on("--debug", "Turn on debug mode") do |val|
        $DEBUG = true
      end
      parser.separator ""
      parser.separator "Inform options:"
      parser.on_tail("-h", "--help", "display this help information") do
        puts parser
        exit
      end
      parser.on_tail("--version", "-v", "Show version") do
        puts File.basename($0) + ' v' + Setup::VERSION #Version.join('.')
        exit
      end
      parser.on_tail("--copyright", "Show copyright") do
        puts Setup::COPYRIGHT #opyright
        exit
      end
    end
    def task_names
      self.class.tasks.keys
    end
    def print_header
    end
  end
end
Setup::Command.run