add setup.rb and _irbrc, update README
authorpabs@pablotron.org
Sat May 30 05:34:21 2009 -0400 (2 years ago)
changeset 18ed196cef9f6e
parent 17 bfa244dbf6e7
child 19 9c92680efe75
add setup.rb and _irbrc, update README
README
_irbrc
setup.rb
     1.1 --- a/README	Sat May 30 05:20:36 2009 -0400
     1.2 +++ b/README	Sat May 30 05:34:21 2009 -0400
     1.3 @@ -54,8 +54,14 @@
     1.4      # install wirble via RubyGems
     1.5      sudo gem install wirble
     1.6  
     1.7 -If you don't have RubyGems, you can also install the Wirble library
     1.8 -this:
     1.9 +If you don't have RubyGems, you can also install the Wirble using
    1.10 +setup.rb: 
    1.11 +
    1.12 +    # install wirble using setup.rb
    1.13 +    ruby ./setup.rb config && ruby ./setup.rb setup
    1.14 +    sudo ruby ./setup.rb install
    1.15 +
    1.16 +Or, install Wirble by hand using sudo:
    1.17  
    1.18      # install wirble to library directory
    1.19      sudo cp -v wirble.rb $(ruby -e 'puts $LOAD_PATH[0]')
    1.20 @@ -80,19 +86,25 @@
    1.21  
    1.22  Using Wirble
    1.23  ============
    1.24 +A sample ~/.irbrc is available in the file "_irbrc".  If you just want
    1.25 +to get Wirble up quickly, copy that to your home directory and be done
    1.26 +with it.  Otherwise. read on:
    1.27 +
    1.28  Using Wirble is easy: just add the following lines to your ~/.irbrc
    1.29  file:
    1.30  
    1.31 -    # load rubygems and wirble
    1.32 -    require 'rubygems' rescue nil
    1.33 -    require 'wirble'
    1.34 +    begin
    1.35 +      require 'wirble'
    1.36  
    1.37 -    # load wirble
    1.38 -    Wirble.init
    1.39 +      # init wirble
    1.40 +      Wirble.init
    1.41 +    rescue LoadError => err
    1.42 +      $stderr.puts "Couldn't load Wirble: #{err}"
    1.43 +    end
    1.44  
    1.45  A lot of people really don't like colors, so I've kept the color
    1.46  disabled by default.  To enable it, add the following bit to your
    1.47 -~/.irbrc file after "Wirble.init":
    1.48 +~/.irbrc file immediately after the call to "Wirble.init":
    1.49  
    1.50      # enable color
    1.51      Wirble.colorize
    1.52 @@ -100,8 +112,15 @@
    1.53  If you want to terrify your grandmother and impress your buddies, your
    1.54  entire ~/.irbrc can also be written like so:
    1.55  
    1.56 -    %w{rubygems wirble}.each { |lib| require lib rescue nil }
    1.57 -    %w{init colorize}.each { |str| Wirble.send(str.intern) }
    1.58 +    %w{rubygems wirble}.each do |lib| 
    1.59 +      begin 
    1.60 +        require lib 
    1.61 +      rescue LoadError => err
    1.62 +        $stderr.puts "Couldn't load #{lib}: #{err}"
    1.63 +      end
    1.64 +    end
    1.65 +
    1.66 +    %w{init colorize}.each { |str| Wirble.send(str) }
    1.67  
    1.68  Configuring Wirble
    1.69  ==================
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/_irbrc	Sat May 30 05:34:21 2009 -0400
     2.3 @@ -0,0 +1,27 @@
     2.4 +######################################################################
     2.5 +# _irbrc - Sample .irbrc file to enable Wirble.                      #
     2.6 +######################################################################
     2.7 +
     2.8 +#
     2.9 +# Uncomment the block below if you want to load RubyGems in Irb.
    2.10 +#
    2.11 +
    2.12 +# begin 
    2.13 +#   require 'rubygems'
    2.14 +# rescue LoadError => err
    2.15 +#   warn "Couldn't load RubyGems: #{err}"
    2.16 +# end
    2.17 +
    2.18 +begin 
    2.19 +  # load and initialize wirble
    2.20 +  require 'wirble'
    2.21 +  Wirble.init
    2.22 +  
    2.23 +  #
    2.24 +  # Uncomment the line below to enable Wirble colors.
    2.25 +  # 
    2.26 +
    2.27 +  # Wirble.colorize
    2.28 +rescue LoadError => err
    2.29 +  warn "Couldn't load Wirble: #{err}"
    2.30 +end
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/setup.rb	Sat May 30 05:34:21 2009 -0400
     3.3 @@ -0,0 +1,1596 @@
     3.4 +#
     3.5 +# setup.rb
     3.6 +#
     3.7 +# Copyright (c) 2000-2006 Minero Aoki
     3.8 +#
     3.9 +# This program is free software.
    3.10 +# You can distribute/modify this program under the terms of
    3.11 +# the GNU LGPL, Lesser General Public License version 2.1.
    3.12 +#
    3.13 +
    3.14 +unless Enumerable.method_defined?(:map)   # Ruby 1.4.6
    3.15 +  module Enumerable
    3.16 +    alias map collect
    3.17 +  end
    3.18 +end
    3.19 +
    3.20 +unless File.respond_to?(:read)   # Ruby 1.6
    3.21 +  def File.read(fname)
    3.22 +    open(fname) {|f|
    3.23 +      return f.read
    3.24 +    }
    3.25 +  end
    3.26 +end
    3.27 +
    3.28 +unless Errno.const_defined?(:ENOTEMPTY)   # Windows?
    3.29 +  module Errno
    3.30 +    class ENOTEMPTY
    3.31 +      # We do not raise this exception, implementation is not needed.
    3.32 +    end
    3.33 +  end
    3.34 +end
    3.35 +
    3.36 +def File.binread(fname)
    3.37 +  open(fname, 'rb') {|f|
    3.38 +    return f.read
    3.39 +  }
    3.40 +end
    3.41 +
    3.42 +# for corrupted Windows' stat(2)
    3.43 +def File.dir?(path)
    3.44 +  File.directory?((path[-1,1] == '/') ? path : path + '/')
    3.45 +end
    3.46 +
    3.47 +
    3.48 +class ConfigTable
    3.49 +
    3.50 +  include Enumerable
    3.51 +
    3.52 +  def initialize(rbconfig)
    3.53 +    @rbconfig = rbconfig
    3.54 +    @items = []
    3.55 +    @table = {}
    3.56 +    # options
    3.57 +    @install_prefix = nil
    3.58 +    @config_opt = nil
    3.59 +    @verbose = true
    3.60 +    @no_harm = false
    3.61 +  end
    3.62 +
    3.63 +  attr_accessor :install_prefix
    3.64 +  attr_accessor :config_opt
    3.65 +
    3.66 +  attr_writer :verbose
    3.67 +
    3.68 +  def verbose?
    3.69 +    @verbose
    3.70 +  end
    3.71 +
    3.72 +  attr_writer :no_harm
    3.73 +
    3.74 +  def no_harm?
    3.75 +    @no_harm
    3.76 +  end
    3.77 +
    3.78 +  def [](key)
    3.79 +    lookup(key).resolve(self)
    3.80 +  end
    3.81 +
    3.82 +  def []=(key, val)
    3.83 +    lookup(key).set val
    3.84 +  end
    3.85 +
    3.86 +  def names
    3.87 +    @items.map {|i| i.name }
    3.88 +  end
    3.89 +
    3.90 +  def each(&block)
    3.91 +    @items.each(&block)
    3.92 +  end
    3.93 +
    3.94 +  def key?(name)
    3.95 +    @table.key?(name)
    3.96 +  end
    3.97 +
    3.98 +  def lookup(name)
    3.99 +    @table[name] or setup_rb_error "no such config item: #{name}"
   3.100 +  end
   3.101 +
   3.102 +  def add(item)
   3.103 +    @items.push item
   3.104 +    @table[item.name] = item
   3.105 +  end
   3.106 +
   3.107 +  def remove(name)
   3.108 +    item = lookup(name)
   3.109 +    @items.delete_if {|i| i.name == name }
   3.110 +    @table.delete_if {|name, i| i.name == name }
   3.111 +    item
   3.112 +  end
   3.113 +
   3.114 +  def load_script(path, inst = nil)
   3.115 +    if File.file?(path)
   3.116 +      MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path
   3.117 +    end
   3.118 +  end
   3.119 +
   3.120 +  def savefile
   3.121 +    '.config'
   3.122 +  end
   3.123 +
   3.124 +  def load_savefile
   3.125 +    begin
   3.126 +      File.foreach(savefile()) do |line|
   3.127 +        k, v = *line.split(/=/, 2)
   3.128 +        self[k] = v.strip
   3.129 +      end
   3.130 +    rescue Errno::ENOENT
   3.131 +      setup_rb_error $!.message + "\n#{File.basename($0)} config first"
   3.132 +    end
   3.133 +  end
   3.134 +
   3.135 +  def save
   3.136 +    @items.each {|i| i.value }
   3.137 +    File.open(savefile(), 'w') {|f|
   3.138 +      @items.each do |i|
   3.139 +        f.printf "%s=%s\n", i.name, i.value if i.value? and i.value
   3.140 +      end
   3.141 +    }
   3.142 +  end
   3.143 +
   3.144 +  def load_standard_entries
   3.145 +    standard_entries(@rbconfig).each do |ent|
   3.146 +      add ent
   3.147 +    end
   3.148 +  end
   3.149 +
   3.150 +  def standard_entries(rbconfig)
   3.151 +    c = rbconfig
   3.152 +
   3.153 +    rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT'])
   3.154 +
   3.155 +    major = c['MAJOR'].to_i
   3.156 +    minor = c['MINOR'].to_i
   3.157 +    teeny = c['TEENY'].to_i
   3.158 +    version = "#{major}.#{minor}"
   3.159 +
   3.160 +    # ruby ver. >= 1.4.4?
   3.161 +    newpath_p = ((major >= 2) or
   3.162 +                 ((major == 1) and
   3.163 +                  ((minor >= 5) or
   3.164 +                   ((minor == 4) and (teeny >= 4)))))
   3.165 +
   3.166 +    if c['rubylibdir']
   3.167 +      # V > 1.6.3
   3.168 +      libruby         = "#{c['prefix']}/lib/ruby"
   3.169 +      librubyver      = c['rubylibdir']
   3.170 +      librubyverarch  = c['archdir']
   3.171 +      siteruby        = c['sitedir']
   3.172 +      siterubyver     = c['sitelibdir']
   3.173 +      siterubyverarch = c['sitearchdir']
   3.174 +    elsif newpath_p
   3.175 +      # 1.4.4 <= V <= 1.6.3
   3.176 +      libruby         = "#{c['prefix']}/lib/ruby"
   3.177 +      librubyver      = "#{c['prefix']}/lib/ruby/#{version}"
   3.178 +      librubyverarch  = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
   3.179 +      siteruby        = c['sitedir']
   3.180 +      siterubyver     = "$siteruby/#{version}"
   3.181 +      siterubyverarch = "$siterubyver/#{c['arch']}"
   3.182 +    else
   3.183 +      # V < 1.4.4
   3.184 +      libruby         = "#{c['prefix']}/lib/ruby"
   3.185 +      librubyver      = "#{c['prefix']}/lib/ruby/#{version}"
   3.186 +      librubyverarch  = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
   3.187 +      siteruby        = "#{c['prefix']}/lib/ruby/#{version}/site_ruby"
   3.188 +      siterubyver     = siteruby
   3.189 +      siterubyverarch = "$siterubyver/#{c['arch']}"
   3.190 +    end
   3.191 +    parameterize = lambda {|path|
   3.192 +      path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')
   3.193 +    }
   3.194 +
   3.195 +    if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
   3.196 +      makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
   3.197 +    else
   3.198 +      makeprog = 'make'
   3.199 +    end
   3.200 +
   3.201 +    [
   3.202 +      ExecItem.new('installdirs', 'std/site/home',
   3.203 +                   'std: install under libruby; site: install under site_ruby; home: install under $HOME')\
   3.204 +          {|val, table|
   3.205 +            case val
   3.206 +            when 'std'
   3.207 +              table['rbdir'] = '$librubyver'
   3.208 +              table['sodir'] = '$librubyverarch'
   3.209 +            when 'site'
   3.210 +              table['rbdir'] = '$siterubyver'
   3.211 +              table['sodir'] = '$siterubyverarch'
   3.212 +            when 'home'
   3.213 +              setup_rb_error '$HOME was not set' unless ENV['HOME']
   3.214 +              table['prefix'] = ENV['HOME']
   3.215 +              table['rbdir'] = '$libdir/ruby'
   3.216 +              table['sodir'] = '$libdir/ruby'
   3.217 +            end
   3.218 +          },
   3.219 +      PathItem.new('prefix', 'path', c['prefix'],
   3.220 +                   'path prefix of target environment'),
   3.221 +      PathItem.new('bindir', 'path', parameterize.call(c['bindir']),
   3.222 +                   'the directory for commands'),
   3.223 +      PathItem.new('libdir', 'path', parameterize.call(c['libdir']),
   3.224 +                   'the directory for libraries'),
   3.225 +      PathItem.new('datadir', 'path', parameterize.call(c['datadir']),
   3.226 +                   'the directory for shared data'),
   3.227 +      PathItem.new('mandir', 'path', parameterize.call(c['mandir']),
   3.228 +                   'the directory for man pages'),
   3.229 +      PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']),
   3.230 +                   'the directory for system configuration files'),
   3.231 +      PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']),
   3.232 +                   'the directory for local state data'),
   3.233 +      PathItem.new('libruby', 'path', libruby,
   3.234 +                   'the directory for ruby libraries'),
   3.235 +      PathItem.new('librubyver', 'path', librubyver,
   3.236 +                   'the directory for standard ruby libraries'),
   3.237 +      PathItem.new('librubyverarch', 'path', librubyverarch,
   3.238 +                   'the directory for standard ruby extensions'),
   3.239 +      PathItem.new('siteruby', 'path', siteruby,
   3.240 +          'the directory for version-independent aux ruby libraries'),
   3.241 +      PathItem.new('siterubyver', 'path', siterubyver,
   3.242 +                   'the directory for aux ruby libraries'),
   3.243 +      PathItem.new('siterubyverarch', 'path', siterubyverarch,
   3.244 +                   'the directory for aux ruby binaries'),
   3.245 +      PathItem.new('rbdir', 'path', '$siterubyver',
   3.246 +                   'the directory for ruby scripts'),
   3.247 +      PathItem.new('sodir', 'path', '$siterubyverarch',
   3.248 +                   'the directory for ruby extentions'),
   3.249 +      PathItem.new('rubypath', 'path', rubypath,
   3.250 +                   'the path to set to #! line'),
   3.251 +      ProgramItem.new('rubyprog', 'name', rubypath,
   3.252 +                      'the ruby program using for installation'),
   3.253 +      ProgramItem.new('makeprog', 'name', makeprog,
   3.254 +                      'the make program to compile ruby extentions'),
   3.255 +      SelectItem.new('shebang', 'all/ruby/never', 'ruby',
   3.256 +                     'shebang line (#!) editing mode'),
   3.257 +      BoolItem.new('without-ext', 'yes/no', 'no',
   3.258 +                   'does not compile/install ruby extentions')
   3.259 +    ]
   3.260 +  end
   3.261 +  private :standard_entries
   3.262 +
   3.263 +  def load_multipackage_entries
   3.264 +    multipackage_entries().each do |ent|
   3.265 +      add ent
   3.266 +    end
   3.267 +  end
   3.268 +
   3.269 +  def multipackage_entries
   3.270 +    [
   3.271 +      PackageSelectionItem.new('with', 'name,name...', '', 'ALL',
   3.272 +                               'package names that you want to install'),
   3.273 +      PackageSelectionItem.new('without', 'name,name...', '', 'NONE',
   3.274 +                               'package names that you do not want to install')
   3.275 +    ]
   3.276 +  end
   3.277 +  private :multipackage_entries
   3.278 +
   3.279 +  ALIASES = {
   3.280 +    'std-ruby'         => 'librubyver',
   3.281 +    'stdruby'          => 'librubyver',
   3.282 +    'rubylibdir'       => 'librubyver',
   3.283 +    'archdir'          => 'librubyverarch',
   3.284 +    'site-ruby-common' => 'siteruby',     # For backward compatibility
   3.285 +    'site-ruby'        => 'siterubyver',  # For backward compatibility
   3.286 +    'bin-dir'          => 'bindir',
   3.287 +    'bin-dir'          => 'bindir',
   3.288 +    'rb-dir'           => 'rbdir',
   3.289 +    'so-dir'           => 'sodir',
   3.290 +    'data-dir'         => 'datadir',
   3.291 +    'ruby-path'        => 'rubypath',
   3.292 +    'ruby-prog'        => 'rubyprog',
   3.293 +    'ruby'             => 'rubyprog',
   3.294 +    'make-prog'        => 'makeprog',
   3.295 +    'make'             => 'makeprog'
   3.296 +  }
   3.297 +
   3.298 +  def fixup
   3.299 +    ALIASES.each do |ali, name|
   3.300 +      @table[ali] = @table[name]
   3.301 +    end
   3.302 +  end
   3.303 +
   3.304 +  def options_re
   3.305 +    /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/
   3.306 +  end
   3.307 +
   3.308 +  def parse_opt(opt)
   3.309 +    m = options_re().match(opt) or setup_rb_error "config: unknown option #{opt}"
   3.310 +    m.to_a[1,2]
   3.311 +  end
   3.312 +
   3.313 +  def dllext
   3.314 +    @rbconfig['DLEXT']
   3.315 +  end
   3.316 +
   3.317 +  def value_config?(name)
   3.318 +    lookup(name).value?
   3.319 +  end
   3.320 +
   3.321 +  class Item
   3.322 +    def initialize(name, template, default, desc)
   3.323 +      @name = name.freeze
   3.324 +      @template = template
   3.325 +      @value = default
   3.326 +      @default = default
   3.327 +      @description = desc
   3.328 +    end
   3.329 +
   3.330 +    attr_reader :name
   3.331 +    attr_reader :description
   3.332 +
   3.333 +    attr_accessor :default
   3.334 +    alias help_default default
   3.335 +
   3.336 +    def help_opt
   3.337 +      "--#{@name}=#{@template}"
   3.338 +    end
   3.339 +
   3.340 +    def value?
   3.341 +      true
   3.342 +    end
   3.343 +
   3.344 +    def value
   3.345 +      @value
   3.346 +    end
   3.347 +
   3.348 +    def resolve(table)
   3.349 +      @value.gsub(%r<\$([^/]+)>) { table[$1] }
   3.350 +    end
   3.351 +
   3.352 +    def set(val)
   3.353 +      @value = check(val)
   3.354 +    end
   3.355 +
   3.356 +    private
   3.357 +
   3.358 +    def check(val)
   3.359 +      setup_rb_error "config: --#{name} requires argument" unless val
   3.360 +      val
   3.361 +    end
   3.362 +  end
   3.363 +
   3.364 +  class BoolItem < Item
   3.365 +    def config_type
   3.366 +      'bool'
   3.367 +    end
   3.368 +
   3.369 +    def help_opt
   3.370 +      "--#{@name}"
   3.371 +    end
   3.372 +
   3.373 +    private
   3.374 +
   3.375 +    def check(val)
   3.376 +      return 'yes' unless val
   3.377 +      case val
   3.378 +      when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes'
   3.379 +      when /\An(o)?\z/i, /\Af(alse)\z/i  then 'no'
   3.380 +      else
   3.381 +        setup_rb_error "config: --#{@name} accepts only yes/no for argument"
   3.382 +      end
   3.383 +    end
   3.384 +  end
   3.385 +
   3.386 +  class PathItem < Item
   3.387 +    def config_type
   3.388 +      'path'
   3.389 +    end
   3.390 +
   3.391 +    private
   3.392 +
   3.393 +    def check(path)
   3.394 +      setup_rb_error "config: --#{@name} requires argument"  unless path
   3.395 +      path[0,1] == '$' ? path : File.expand_path(path)
   3.396 +    end
   3.397 +  end
   3.398 +
   3.399 +  class ProgramItem < Item
   3.400 +    def config_type
   3.401 +      'program'
   3.402 +    end
   3.403 +  end
   3.404 +
   3.405 +  class SelectItem < Item
   3.406 +    def initialize(name, selection, default, desc)
   3.407 +      super
   3.408 +      @ok = selection.split('/')
   3.409 +    end
   3.410 +
   3.411 +    def config_type
   3.412 +      'select'
   3.413 +    end
   3.414 +
   3.415 +    private
   3.416 +
   3.417 +    def check(val)
   3.418 +      unless @ok.include?(val.strip)
   3.419 +        setup_rb_error "config: use --#{@name}=#{@template} (#{val})"
   3.420 +      end
   3.421 +      val.strip
   3.422 +    end
   3.423 +  end
   3.424 +
   3.425 +  class ExecItem < Item
   3.426 +    def initialize(name, selection, desc, &block)
   3.427 +      super name, selection, nil, desc
   3.428 +      @ok = selection.split('/')
   3.429 +      @action = block
   3.430 +    end
   3.431 +
   3.432 +    def config_type
   3.433 +      'exec'
   3.434 +    end
   3.435 +
   3.436 +    def value?
   3.437 +      false
   3.438 +    end
   3.439 +
   3.440 +    def resolve(table)
   3.441 +      setup_rb_error "$#{name()} wrongly used as option value"
   3.442 +    end
   3.443 +
   3.444 +    undef set
   3.445 +
   3.446 +    def evaluate(val, table)
   3.447 +      v = val.strip.downcase
   3.448 +      unless @ok.include?(v)
   3.449 +        setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})"
   3.450 +      end
   3.451 +      @action.call v, table
   3.452 +    end
   3.453 +  end
   3.454 +
   3.455 +  class PackageSelectionItem < Item
   3.456 +    def initialize(name, template, default, help_default, desc)
   3.457 +      super name, template, default, desc
   3.458 +      @help_default = help_default
   3.459 +    end
   3.460 +
   3.461 +    attr_reader :help_default
   3.462 +
   3.463 +    def config_type
   3.464 +      'package'
   3.465 +    end
   3.466 +
   3.467 +    private
   3.468 +
   3.469 +    def check(val)
   3.470 +      unless File.dir?("packages/#{val}")
   3.471 +        setup_rb_error "config: no such package: #{val}"
   3.472 +      end
   3.473 +      val
   3.474 +    end
   3.475 +  end
   3.476 +
   3.477 +  class MetaConfigEnvironment
   3.478 +    def initialize(config, installer)
   3.479 +      @config = config
   3.480 +      @installer = installer
   3.481 +    end
   3.482 +
   3.483 +    def config_names
   3.484 +      @config.names
   3.485 +    end
   3.486 +
   3.487 +    def config?(name)
   3.488 +      @config.key?(name)
   3.489 +    end
   3.490 +
   3.491 +    def bool_config?(name)
   3.492 +      @config.lookup(name).config_type == 'bool'
   3.493 +    end
   3.494 +
   3.495 +    def path_config?(name)
   3.496 +      @config.lookup(name).config_type == 'path'
   3.497 +    end
   3.498 +
   3.499 +    def value_config?(name)
   3.500 +      @config.lookup(name).config_type != 'exec'
   3.501 +    end
   3.502 +
   3.503 +    def add_config(item)
   3.504 +      @config.add item
   3.505 +    end
   3.506 +
   3.507 +    def add_bool_config(name, default, desc)
   3.508 +      @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc)
   3.509 +    end
   3.510 +
   3.511 +    def add_path_config(name, default, desc)
   3.512 +      @config.add PathItem.new(name, 'path', default, desc)
   3.513 +    end
   3.514 +
   3.515 +    def set_config_default(name, default)
   3.516 +      @config.lookup(name).default = default
   3.517 +    end
   3.518 +
   3.519 +    def remove_config(name)
   3.520 +      @config.remove(name)
   3.521 +    end
   3.522 +
   3.523 +    # For only multipackage
   3.524 +    def packages
   3.525 +      raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer
   3.526 +      @installer.packages
   3.527 +    end
   3.528 +
   3.529 +    # For only multipackage
   3.530 +    def declare_packages(list)
   3.531 +      raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer
   3.532 +      @installer.packages = list
   3.533 +    end
   3.534 +  end
   3.535 +
   3.536 +end   # class ConfigTable
   3.537 +
   3.538 +
   3.539 +# This module requires: #verbose?, #no_harm?
   3.540 +module FileOperations
   3.541 +
   3.542 +  def mkdir_p(dirname, prefix = nil)
   3.543 +    dirname = prefix + File.expand_path(dirname) if prefix
   3.544 +    $stderr.puts "mkdir -p #{dirname}" if verbose?
   3.545 +    return if no_harm?
   3.546 +
   3.547 +    # Does not check '/', it's too abnormal.
   3.548 +    dirs = File.expand_path(dirname).split(%r<(?=/)>)
   3.549 +    if /\A[a-z]:\z/i =~ dirs[0]
   3.550 +      disk = dirs.shift
   3.551 +      dirs[0] = disk + dirs[0]
   3.552 +    end
   3.553 +    dirs.each_index do |idx|
   3.554 +      path = dirs[0..idx].join('')
   3.555 +      Dir.mkdir path unless File.dir?(path)
   3.556 +    end
   3.557 +  end
   3.558 +
   3.559 +  def rm_f(path)
   3.560 +    $stderr.puts "rm -f #{path}" if verbose?
   3.561 +    return if no_harm?
   3.562 +    force_remove_file path
   3.563 +  end
   3.564 +
   3.565 +  def rm_rf(path)
   3.566 +    $stderr.puts "rm -rf #{path}" if verbose?
   3.567 +    return if no_harm?
   3.568 +    remove_tree path
   3.569 +  end
   3.570 +
   3.571 +  def remove_tree(path)
   3.572 +    if File.symlink?(path)
   3.573 +      remove_file path
   3.574 +    elsif File.dir?(path)
   3.575 +      remove_tree0 path
   3.576 +    else
   3.577 +      force_remove_file path
   3.578 +    end
   3.579 +  end
   3.580 +
   3.581 +  def remove_tree0(path)
   3.582 +    Dir.foreach(path) do |ent|
   3.583 +      next if ent == '.'
   3.584 +      next if ent == '..'
   3.585 +      entpath = "#{path}/#{ent}"
   3.586 +      if File.symlink?(entpath)
   3.587 +        remove_file entpath
   3.588 +      elsif File.dir?(entpath)
   3.589 +        remove_tree0 entpath
   3.590 +      else
   3.591 +        force_remove_file entpath
   3.592 +      end
   3.593 +    end
   3.594 +    begin
   3.595 +      Dir.rmdir path
   3.596 +    rescue Errno::ENOTEMPTY
   3.597 +      # directory may not be empty
   3.598 +    end
   3.599 +  end
   3.600 +
   3.601 +  def move_file(src, dest)
   3.602 +    force_remove_file dest
   3.603 +    begin
   3.604 +      File.rename src, dest
   3.605 +    rescue
   3.606 +      File.open(dest, 'wb') {|f|
   3.607 +        f.write File.binread(src)
   3.608 +      }
   3.609 +      File.chmod File.stat(src).mode, dest
   3.610 +      File.unlink src
   3.611 +    end
   3.612 +  end
   3.613 +
   3.614 +  def force_remove_file(path)
   3.615 +    begin
   3.616 +      remove_file path
   3.617 +    rescue
   3.618 +    end
   3.619 +  end
   3.620 +
   3.621 +  def remove_file(path)
   3.622 +    File.chmod 0777, path
   3.623 +    File.unlink path
   3.624 +  end
   3.625 +
   3.626 +  def install(from, dest, mode, prefix = nil)
   3.627 +    $stderr.puts "install #{from} #{dest}" if verbose?
   3.628 +    return if no_harm?
   3.629 +
   3.630 +    realdest = prefix ? prefix + File.expand_path(dest) : dest
   3.631 +    realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
   3.632 +    str = File.binread(from)
   3.633 +    if diff?(str, realdest)
   3.634 +      verbose_off {
   3.635 +        rm_f realdest if File.exist?(realdest)
   3.636 +      }
   3.637 +      File.open(realdest, 'wb') {|f|
   3.638 +        f.write str
   3.639 +      }
   3.640 +      File.chmod mode, realdest
   3.641 +
   3.642 +      File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
   3.643 +        if prefix
   3.644 +          f.puts realdest.sub(prefix, '')
   3.645 +        else
   3.646 +          f.puts realdest
   3.647 +        end
   3.648 +      }
   3.649 +    end
   3.650 +  end
   3.651 +
   3.652 +  def diff?(new_content, path)
   3.653 +    return true unless File.exist?(path)
   3.654 +    new_content != File.binread(path)
   3.655 +  end
   3.656 +
   3.657 +  def command(*args)
   3.658 +    $stderr.puts args.join(' ') if verbose?
   3.659 +    system(*args) or raise RuntimeError,
   3.660 +        "system(#{args.map{|a| a.inspect }.join(' ')}) failed"
   3.661 +  end
   3.662 +
   3.663 +  def ruby(*args)
   3.664 +    command config('rubyprog'), *args
   3.665 +  end
   3.666 +  
   3.667 +  def make(task = nil)
   3.668 +    command(*[config('makeprog'), task].compact)
   3.669 +  end
   3.670 +
   3.671 +  def extdir?(dir)
   3.672 +    File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb")
   3.673 +  end
   3.674 +
   3.675 +  def files_of(dir)
   3.676 +    Dir.open(dir) {|d|
   3.677 +      return d.select {|ent| File.file?("#{dir}/#{ent}") }
   3.678 +    }
   3.679 +  end
   3.680 +
   3.681 +  DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn )
   3.682 +
   3.683 +  def directories_of(dir)
   3.684 +    Dir.open(dir) {|d|
   3.685 +      return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT
   3.686 +    }
   3.687 +  end
   3.688 +
   3.689 +end
   3.690 +
   3.691 +
   3.692 +# This module requires: #srcdir_root, #objdir_root, #relpath
   3.693 +module HookScriptAPI
   3.694 +
   3.695 +  def get_config(key)
   3.696 +    @config[key]
   3.697 +  end
   3.698 +
   3.699 +  alias config get_config
   3.700 +
   3.701 +  # obsolete: use metaconfig to change configuration
   3.702 +  def set_config(key, val)
   3.703 +    @config[key] = val
   3.704 +  end
   3.705 +
   3.706 +  #
   3.707 +  # srcdir/objdir (works only in the package directory)
   3.708 +  #
   3.709 +
   3.710 +  def curr_srcdir
   3.711 +    "#{srcdir_root()}/#{relpath()}"
   3.712 +  end
   3.713 +
   3.714 +  def curr_objdir
   3.715 +    "#{objdir_root()}/#{relpath()}"
   3.716 +  end
   3.717 +
   3.718 +  def srcfile(path)
   3.719 +    "#{curr_srcdir()}/#{path}"
   3.720 +  end
   3.721 +
   3.722 +  def srcexist?(path)
   3.723 +    File.exist?(srcfile(path))
   3.724 +  end
   3.725 +
   3.726 +  def srcdirectory?(path)
   3.727 +    File.dir?(srcfile(path))
   3.728 +  end
   3.729 +  
   3.730 +  def srcfile?(path)
   3.731 +    File.file?(srcfile(path))
   3.732 +  end
   3.733 +
   3.734 +  def srcentries(path = '.')
   3.735 +    Dir.open("#{curr_srcdir()}/#{path}") {|d|
   3.736 +      return d.to_a - %w(. ..)
   3.737 +    }
   3.738 +  end
   3.739 +
   3.740 +  def srcfiles(path = '.')
   3.741 +    srcentries(path).select {|fname|
   3.742 +      File.file?(File.join(curr_srcdir(), path, fname))
   3.743 +    }
   3.744 +  end
   3.745 +
   3.746 +  def srcdirectories(path = '.')
   3.747 +    srcentries(path).select {|fname|
   3.748 +      File.dir?(File.join(curr_srcdir(), path, fname))
   3.749 +    }
   3.750 +  end
   3.751 +
   3.752 +end
   3.753 +
   3.754 +
   3.755 +class ToplevelInstaller
   3.756 +
   3.757 +  Version   = '3.4.1'
   3.758 +  Copyright = 'Copyright (c) 2000-2006 Minero Aoki'
   3.759 +
   3.760 +  TASKS = [
   3.761 +    [ 'all',      'do config, setup, then install' ],
   3.762 +    [ 'config',   'saves your configurations' ],
   3.763 +    [ 'show',     'shows current configuration' ],
   3.764 +    [ 'setup',    'compiles ruby extentions and others' ],
   3.765 +    [ 'install',  'installs files' ],
   3.766 +    [ 'test',     'run all tests in test/' ],
   3.767 +    [ 'clean',    "does `make clean' for each extention" ],
   3.768 +    [ 'distclean',"does `make distclean' for each extention" ]
   3.769 +  ]
   3.770 +
   3.771 +  def ToplevelInstaller.invoke
   3.772 +    config = ConfigTable.new(load_rbconfig())
   3.773 +    config.load_standard_entries
   3.774 +    config.load_multipackage_entries if multipackage?
   3.775 +    config.fixup
   3.776 +    klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller)
   3.777 +    klass.new(File.dirname($0), config).invoke
   3.778 +  end
   3.779 +
   3.780 +  def ToplevelInstaller.multipackage?
   3.781 +    File.dir?(File.dirname($0) + '/packages')
   3.782 +  end
   3.783 +
   3.784 +  def ToplevelInstaller.load_rbconfig
   3.785 +    if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg }
   3.786 +      ARGV.delete(arg)
   3.787 +      load File.expand_path(arg.split(/=/, 2)[1])
   3.788 +      $".push 'rbconfig.rb'
   3.789 +    else
   3.790 +      require 'rbconfig'
   3.791 +    end
   3.792 +    ::Config::CONFIG
   3.793 +  end
   3.794 +
   3.795 +  def initialize(ardir_root, config)
   3.796 +    @ardir = File.expand_path(ardir_root)
   3.797 +    @config = config
   3.798 +    # cache
   3.799 +    @valid_task_re = nil
   3.800 +  end
   3.801 +
   3.802 +  def config(key)
   3.803 +    @config[key]
   3.804 +  end
   3.805 +
   3.806 +  def inspect
   3.807 +    "#<#{self.class} #{__id__()}>"
   3.808 +  end
   3.809 +
   3.810 +  def invoke
   3.811 +    run_metaconfigs
   3.812 +    case task = parsearg_global()
   3.813 +    when nil, 'all'
   3.814 +      parsearg_config
   3.815 +      init_installers
   3.816 +      exec_config
   3.817 +      exec_setup
   3.818 +      exec_install
   3.819 +    else
   3.820 +      case task
   3.821 +      when 'config', 'test'
   3.822 +        ;
   3.823 +      when 'clean', 'distclean'
   3.824 +        @config.load_savefile if File.exist?(@config.savefile)
   3.825 +      else
   3.826 +        @config.load_savefile
   3.827 +      end
   3.828 +      __send__ "parsearg_#{task}"
   3.829 +      init_installers
   3.830 +      __send__ "exec_#{task}"
   3.831 +    end
   3.832 +  end
   3.833 +  
   3.834 +  def run_metaconfigs
   3.835 +    @config.load_script "#{@ardir}/metaconfig"
   3.836 +  end
   3.837 +
   3.838 +  def init_installers
   3.839 +    @installer = Installer.new(@config, @ardir, File.expand_path('.'))
   3.840 +  end
   3.841 +
   3.842 +  #
   3.843 +  # Hook Script API bases
   3.844 +  #
   3.845 +
   3.846 +  def srcdir_root
   3.847 +    @ardir
   3.848 +  end
   3.849 +
   3.850 +  def objdir_root
   3.851 +    '.'
   3.852 +  end
   3.853 +
   3.854 +  def relpath
   3.855 +    '.'
   3.856 +  end
   3.857 +
   3.858 +  #
   3.859 +  # Option Parsing
   3.860 +  #
   3.861 +
   3.862 +  def parsearg_global
   3.863 +    while arg = ARGV.shift
   3.864 +      case arg
   3.865 +      when /\A\w+\z/
   3.866 +        setup_rb_error "invalid task: #{arg}" unless valid_task?(arg)
   3.867 +        return arg
   3.868 +      when '-q', '--quiet'
   3.869 +        @config.verbose = false
   3.870 +      when '--verbose'
   3.871 +        @config.verbose = true
   3.872 +      when '--help'
   3.873 +        print_usage $stdout
   3.874 +        exit 0
   3.875 +      when '--version'
   3.876 +        puts "#{File.basename($0)} version #{Version}"
   3.877 +        exit 0
   3.878 +      when '--copyright'
   3.879 +        puts Copyright
   3.880 +        exit 0
   3.881 +      else
   3.882 +        setup_rb_error "unknown global option '#{arg}'"
   3.883 +      end
   3.884 +    end
   3.885 +    nil
   3.886 +  end
   3.887 +
   3.888 +  def valid_task?(t)
   3.889 +    valid_task_re() =~ t
   3.890 +  end
   3.891 +
   3.892 +  def valid_task_re
   3.893 +    @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/
   3.894 +  end
   3.895 +
   3.896 +  def parsearg_no_options
   3.897 +    unless ARGV.empty?
   3.898 +      task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1)
   3.899 +      setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}"
   3.900 +    end
   3.901 +  end
   3.902 +
   3.903 +  alias parsearg_show       parsearg_no_options
   3.904 +  alias parsearg_setup      parsearg_no_options
   3.905 +  alias parsearg_test       parsearg_no_options
   3.906 +  alias parsearg_clean      parsearg_no_options
   3.907 +  alias parsearg_distclean  parsearg_no_options
   3.908 +
   3.909 +  def parsearg_config
   3.910 +    evalopt = []
   3.911 +    set = []
   3.912 +    @config.config_opt = []
   3.913 +    while i = ARGV.shift
   3.914 +      if /\A--?\z/ =~ i
   3.915 +        @config.config_opt = ARGV.dup
   3.916 +        break
   3.917 +      end
   3.918 +      name, value = *@config.parse_opt(i)
   3.919 +      if @config.value_config?(name)
   3.920 +        @config[name] = value
   3.921 +      else
   3.922 +        evalopt.push [name, value]
   3.923 +      end
   3.924 +      set.push name
   3.925 +    end
   3.926 +    evalopt.each do |name, value|
   3.927 +      @config.lookup(name).evaluate value, @config
   3.928 +    end
   3.929 +    # Check if configuration is valid
   3.930 +    set.each do |n|
   3.931 +      @config[n] if @config.value_config?(n)
   3.932 +    end
   3.933 +  end
   3.934 +
   3.935 +  def parsearg_install
   3.936 +    @config.no_harm = false
   3.937 +    @config.install_prefix = ''
   3.938 +    while a = ARGV.shift
   3.939 +      case a
   3.940 +      when '--no-harm'
   3.941 +        @config.no_harm = true
   3.942 +      when /\A--prefix=/
   3.943 +        path = a.split(/=/, 2)[1]
   3.944 +        path = File.expand_path(path) unless path[0,1] == '/'
   3.945 +        @config.install_prefix = path
   3.946 +      else
   3.947 +        setup_rb_error "install: unknown option #{a}"
   3.948 +      end
   3.949 +    end
   3.950 +  end
   3.951 +
   3.952 +  def print_usage(out)
   3.953 +    out.puts 'Typical Installation Procedure:'
   3.954 +    out.puts "  $ ruby #{File.basename $0} config"
   3.955 +    out.puts "  $ ruby #{File.basename $0} setup"
   3.956 +    out.puts "  # ruby #{File.basename $0} install (may require root privilege)"
   3.957 +    out.puts
   3.958 +    out.puts 'Detailed Usage:'
   3.959 +    out.puts "  ruby #{File.basename $0} <global option>"
   3.960 +    out.puts "  ruby #{File.basename $0} [<global options>] <task> [<task options>]"
   3.961 +
   3.962 +    fmt = "  %-24s %s\n"
   3.963 +    out.puts
   3.964 +    out.puts 'Global options:'
   3.965 +    out.printf fmt, '-q,--quiet',   'suppress message outputs'
   3.966 +    out.printf fmt, '   --verbose', 'output messages verbosely'
   3.967 +    out.printf fmt, '   --help',    'print this message'
   3.968 +    out.printf fmt, '   --version', 'print version and quit'
   3.969 +    out.printf fmt, '   --copyright',  'print copyright and quit'
   3.970 +    out.puts
   3.971 +    out.puts 'Tasks:'
   3.972 +    TASKS.each do |name, desc|
   3.973 +      out.printf fmt, name, desc
   3.974 +    end
   3.975 +
   3.976 +    fmt = "  %-24s %s [%s]\n"
   3.977 +    out.puts
   3.978 +    out.puts 'Options for CONFIG or ALL:'
   3.979 +    @config.each do |item|
   3.980 +      out.printf fmt, item.help_opt, item.description, item.help_default
   3.981 +    end
   3.982 +    out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's"
   3.983 +    out.puts
   3.984 +    out.puts 'Options for INSTALL:'
   3.985 +    out.printf fmt, '--no-harm', 'only display what to do if given', 'off'
   3.986 +    out.printf fmt, '--prefix=path',  'install path prefix', ''
   3.987 +    out.puts
   3.988 +  end
   3.989 +
   3.990 +  #
   3.991 +  # Task Handlers
   3.992 +  #
   3.993 +
   3.994 +  def exec_config
   3.995 +    @installer.exec_config
   3.996 +    @config.save   # must be final
   3.997 +  end
   3.998 +
   3.999 +  def exec_setup
  3.1000 +    @installer.exec_setup
  3.1001 +  end
  3.1002 +
  3.1003 +  def exec_install
  3.1004 +    @installer.exec_install
  3.1005 +  end
  3.1006 +
  3.1007 +  def exec_test
  3.1008 +    @installer.exec_test
  3.1009 +  end
  3.1010 +
  3.1011 +  def exec_show
  3.1012 +    @config.each do |i|
  3.1013 +      printf "%-20s %s\n", i.name, i.value if i.value?
  3.1014 +    end
  3.1015 +  end
  3.1016 +
  3.1017 +  def exec_clean
  3.1018 +    @installer.exec_clean
  3.1019 +  end
  3.1020 +
  3.1021 +  def exec_distclean
  3.1022 +    @installer.exec_distclean
  3.1023 +  end
  3.1024 +
  3.1025 +end   # class ToplevelInstaller
  3.1026 +
  3.1027 +
  3.1028 +class ToplevelInstallerMulti < ToplevelInstaller
  3.1029 +
  3.1030 +  include FileOperations
  3.1031 +
  3.1032 +  def initialize(ardir_root, config)
  3.1033 +    super
  3.1034 +    @packages = directories_of("#{@ardir}/packages")
  3.1035 +    raise 'no package exists' if @packages.empty?
  3.1036 +    @root_installer = Installer.new(@config, @ardir, File.expand_path('.'))
  3.1037 +  end
  3.1038 +
  3.1039 +  def run_metaconfigs
  3.1040 +    @config.load_script "#{@ardir}/metaconfig", self
  3.1041 +    @packages.each do |name|
  3.1042 +      @config.load_script "#{@ardir}/packages/#{name}/metaconfig"
  3.1043 +    end
  3.1044 +  end
  3.1045 +
  3.1046 +  attr_reader :packages
  3.1047 +
  3.1048 +  def packages=(list)
  3.1049 +    raise 'package list is empty' if list.empty?
  3.1050 +    list.each do |name|
  3.1051 +      raise "directory packages/#{name} does not exist"\
  3.1052 +              unless File.dir?("#{@ardir}/packages/#{name}")
  3.1053 +    end
  3.1054 +    @packages = list
  3.1055 +  end
  3.1056 +
  3.1057 +  def init_installers
  3.1058 +    @installers = {}
  3.1059 +    @packages.each do |pack|
  3.1060 +      @installers[pack] = Installer.new(@config,
  3.1061 +                                       "#{@ardir}/packages/#{pack}",
  3.1062 +                                       "packages/#{pack}")
  3.1063 +    end
  3.1064 +    with    = extract_selection(config('with'))
  3.1065 +    without = extract_selection(config('without'))
  3.1066 +    @selected = @installers.keys.select {|name|
  3.1067 +                  (with.empty? or with.include?(name)) \
  3.1068 +                      and not without.include?(name)
  3.1069 +                }
  3.1070 +  end
  3.1071 +
  3.1072 +  def extract_selection(list)
  3.1073 +    a = list.split(/,/)
  3.1074 +    a.each do |name|
  3.1075 +      setup_rb_error "no such package: #{name}"  unless @installers.key?(name)
  3.1076 +    end
  3.1077 +    a
  3.1078 +  end
  3.1079 +
  3.1080 +  def print_usage(f)
  3.1081 +    super
  3.1082 +    f.puts 'Inluded packages:'
  3.1083 +    f.puts '  ' + @packages.sort.join(' ')
  3.1084 +    f.puts
  3.1085 +  end
  3.1086 +
  3.1087 +  #
  3.1088 +  # Task Handlers
  3.1089 +  #
  3.1090 +
  3.1091 +  def exec_config
  3.1092 +    run_hook 'pre-config'
  3.1093 +    each_selected_installers {|inst| inst.exec_config }
  3.1094 +    run_hook 'post-config'
  3.1095 +    @config.save   # must be final
  3.1096 +  end
  3.1097 +
  3.1098 +  def exec_setup
  3.1099 +    run_hook 'pre-setup'
  3.1100 +    each_selected_installers {|inst| inst.exec_setup }
  3.1101 +    run_hook 'post-setup'
  3.1102 +  end
  3.1103 +
  3.1104 +  def exec_install
  3.1105 +    run_hook 'pre-install'
  3.1106 +    each_selected_installers {|inst| inst.exec_install }
  3.1107 +    run_hook 'post-install'
  3.1108 +  end
  3.1109 +
  3.1110 +  def exec_test
  3.1111 +    run_hook 'pre-test'
  3.1112 +    each_selected_installers {|inst| inst.exec_test }
  3.1113 +    run_hook 'post-test'
  3.1114 +  end
  3.1115 +
  3.1116 +  def exec_clean
  3.1117 +    rm_f @config.savefile
  3.1118 +    run_hook 'pre-clean'
  3.1119 +    each_selected_installers {|inst| inst.exec_clean }
  3.1120 +    run_hook 'post-clean'
  3.1121 +  end
  3.1122 +
  3.1123 +  def exec_distclean
  3.1124 +    rm_f @config.savefile
  3.1125 +    run_hook 'pre-distclean'
  3.1126 +    each_selected_installers {|inst| inst.exec_distclean }
  3.1127 +    run_hook 'post-distclean'
  3.1128 +  end
  3.1129 +
  3.1130 +  #
  3.1131 +  # lib
  3.1132 +  #
  3.1133 +
  3.1134 +  def each_selected_installers
  3.1135 +    Dir.mkdir 'packages' unless File.dir?('packages')
  3.1136 +    @selected.each do |pack|
  3.1137 +      $stderr.puts "Processing the package `#{pack}' ..." if verbose?
  3.1138 +      Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
  3.1139 +      Dir.chdir "packages/#{pack}"
  3.1140 +      yield @installers[pack]
  3.1141 +      Dir.chdir '../..'
  3.1142 +    end
  3.1143 +  end
  3.1144 +
  3.1145 +  def run_hook(id)
  3.1146 +    @root_installer.run_hook id
  3.1147 +  end
  3.1148 +
  3.1149 +  # module FileOperations requires this
  3.1150 +  def verbose?
  3.1151 +    @config.verbose?
  3.1152 +  end
  3.1153 +
  3.1154 +  # module FileOperations requires this
  3.1155 +  def no_harm?
  3.1156 +    @config.no_harm?
  3.1157 +  end
  3.1158 +
  3.1159 +end   # class ToplevelInstallerMulti
  3.1160 +
  3.1161 +
  3.1162 +class Installer
  3.1163 +
  3.1164 +  FILETYPES = %w( bin lib ext data conf man )
  3.1165 +
  3.1166 +  include FileOperations
  3.1167 +  include HookScriptAPI
  3.1168 +
  3.1169 +  def initialize(config, srcroot, objroot)
  3.1170 +    @config = config
  3.1171 +    @srcdir = File.expand_path(srcroot)
  3.1172 +    @objdir = File.expand_path(objroot)
  3.1173 +    @currdir = '.'
  3.1174 +  end
  3.1175 +
  3.1176 +  def inspect
  3.1177 +    "#<#{self.class} #{File.basename(@srcdir)}>"
  3.1178 +  end
  3.1179 +
  3.1180 +  def noop(rel)
  3.1181 +  end
  3.1182 +
  3.1183 +  #
  3.1184 +  # Hook Script API base methods
  3.1185 +  #
  3.1186 +
  3.1187 +  def srcdir_root
  3.1188 +    @srcdir
  3.1189 +  end
  3.1190 +
  3.1191 +  def objdir_root
  3.1192 +    @objdir
  3.1193 +  end
  3.1194 +
  3.1195 +  def relpath
  3.1196 +    @currdir
  3.1197 +  end
  3.1198 +
  3.1199 +  #
  3.1200 +  # Config Access
  3.1201 +  #
  3.1202 +
  3.1203 +  # module FileOperations requires this
  3.1204 +  def verbose?
  3.1205 +    @config.verbose?
  3.1206 +  end
  3.1207 +
  3.1208 +  # module FileOperations requires this
  3.1209 +  def no_harm?
  3.1210 +    @config.no_harm?
  3.1211 +  end
  3.1212 +
  3.1213 +  def verbose_off
  3.1214 +    begin
  3.1215 +      save, @config.verbose = @config.verbose?, false
  3.1216 +      yield
  3.1217 +    ensure
  3.1218 +      @config.verbose = save
  3.1219 +    end
  3.1220 +  end
  3.1221 +
  3.1222 +  #
  3.1223 +  # TASK config
  3.1224 +  #
  3.1225 +
  3.1226 +  def exec_config
  3.1227 +    exec_task_traverse 'config'
  3.1228 +  end
  3.1229 +
  3.1230 +  alias config_dir_bin noop
  3.1231 +  alias config_dir_lib noop
  3.1232 +
  3.1233 +  def config_dir_ext(rel)
  3.1234 +    extconf if extdir?(curr_srcdir())
  3.1235 +  end
  3.1236 +
  3.1237 +  alias config_dir_data noop
  3.1238 +  alias config_dir_conf noop
  3.1239 +  alias config_dir_man noop
  3.1240 +
  3.1241 +  def extconf
  3.1242 +    ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt
  3.1243 +  end
  3.1244 +
  3.1245 +  #
  3.1246 +  # TASK setup
  3.1247 +  #
  3.1248 +
  3.1249 +  def exec_setup
  3.1250 +    exec_task_traverse 'setup'
  3.1251 +  end
  3.1252 +
  3.1253 +  def setup_dir_bin(rel)
  3.1254 +    files_of(curr_srcdir()).each do |fname|
  3.1255 +      update_shebang_line "#{curr_srcdir()}/#{fname}"
  3.1256 +    end
  3.1257 +  end
  3.1258 +
  3.1259 +  alias setup_dir_lib noop
  3.1260 +
  3.1261 +  def setup_dir_ext(rel)
  3.1262 +    make if extdir?(curr_srcdir())
  3.1263 +  end
  3.1264 +
  3.1265 +  alias setup_dir_data noop
  3.1266 +  alias setup_dir_conf noop
  3.1267 +  alias setup_dir_man noop
  3.1268 +
  3.1269 +  def update_shebang_line(path)
  3.1270 +    return if no_harm?
  3.1271 +    return if config('shebang') == 'never'
  3.1272 +    old = Shebang.load(path)
  3.1273 +    if old
  3.1274 +      $stderr.puts "warning: #{path}: Shebang line includes too many args.  It is not portable and your program may not work." if old.args.size > 1
  3.1275 +      new = new_shebang(old)
  3.1276 +      return if new.to_s == old.to_s
  3.1277 +    else
  3.1278 +      return unless config('shebang') == 'all'
  3.1279 +      new = Shebang.new(config('rubypath'))
  3.1280 +    end
  3.1281 +    $stderr.puts "updating shebang: #{File.basename(path)}" if verbose?
  3.1282 +    open_atomic_writer(path) {|output|
  3.1283 +      File.open(path, 'rb') {|f|
  3.1284 +        f.gets if old   # discard
  3.1285 +        output.puts new.to_s
  3.1286 +        output.print f.read
  3.1287 +      }
  3.1288 +    }
  3.1289 +  end
  3.1290 +
  3.1291 +  def new_shebang(old)
  3.1292 +    if /\Aruby/ =~ File.basename(old.cmd)
  3.1293 +      Shebang.new(config('rubypath'), old.args)
  3.1294 +    elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby'
  3.1295 +      Shebang.new(config('rubypath'), old.args[1..-1])
  3.1296 +    else
  3.1297 +      return old unless config('shebang') == 'all'
  3.1298 +      Shebang.new(config('rubypath'))
  3.1299 +    end
  3.1300 +  end
  3.1301 +
  3.1302 +  def open_atomic_writer(path, &block)
  3.1303 +    tmpfile = File.basename(path) + '.tmp'
  3.1304 +    begin
  3.1305 +      File.open(tmpfile, 'wb', &block)
  3.1306 +      File.rename tmpfile, File.basename(path)
  3.1307 +    ensure
  3.1308 +      File.unlink tmpfile if File.exist?(tmpfile)
  3.1309 +    end
  3.1310 +  end
  3.1311 +
  3.1312 +  class Shebang
  3.1313 +    def Shebang.load(path)
  3.1314 +      line = nil
  3.1315 +      File.open(path) {|f|
  3.1316 +        line = f.gets
  3.1317 +      }
  3.1318 +      return nil unless /\A#!/ =~ line
  3.1319 +      parse(line)
  3.1320 +    end
  3.1321 +
  3.1322 +    def Shebang.parse(line)
  3.1323 +      cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ')
  3.1324 +      new(cmd, args)
  3.1325 +    end
  3.1326 +
  3.1327 +    def initialize(cmd, args = [])
  3.1328 +      @cmd = cmd
  3.1329 +      @args = args
  3.1330 +    end
  3.1331 +
  3.1332 +    attr_reader :cmd
  3.1333 +    attr_reader :args
  3.1334 +
  3.1335 +    def to_s
  3.1336 +      "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}")
  3.1337 +    end
  3.1338 +  end
  3.1339 +
  3.1340 +  #
  3.1341 +  # TASK install
  3.1342 +  #
  3.1343 +
  3.1344 +  def exec_install
  3.1345 +    rm_f 'InstalledFiles'
  3.1346 +    exec_task_traverse 'install'
  3.1347 +  end
  3.1348 +
  3.1349 +  def install_dir_bin(rel)
  3.1350 +    install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755, strip_ext?
  3.1351 +  end
  3.1352 +
  3.1353 +  def strip_ext?
  3.1354 +    /mswin|mingw/ !~ RUBY_PLATFORM
  3.1355 +  end
  3.1356 +
  3.1357 +  def install_dir_lib(rel)
  3.1358 +    install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644
  3.1359 +  end
  3.1360 +
  3.1361 +  def install_dir_ext(rel)
  3.1362 +    return unless extdir?(curr_srcdir())
  3.1363 +    install_files rubyextentions('.'),
  3.1364 +                  "#{config('sodir')}/#{File.dirname(rel)}",
  3.1365 +                  0555
  3.1366 +  end
  3.1367 +
  3.1368 +  def install_dir_data(rel)
  3.1369 +    install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644
  3.1370 +  end
  3.1371 +
  3.1372 +  def install_dir_conf(rel)
  3.1373 +    # FIXME: should not remove current config files
  3.1374 +    # (rename previous file to .old/.org)
  3.1375 +    install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644
  3.1376 +  end
  3.1377 +
  3.1378 +  def install_dir_man(rel)
  3.1379 +    install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644
  3.1380 +  end
  3.1381 +
  3.1382 +  def install_files(list, dest, mode, stripext = false)
  3.1383 +    mkdir_p dest, @config.install_prefix
  3.1384 +    list.each do |fname|
  3.1385 +      if stripext
  3.1386 +        install fname, "#{dest}/#{File.basename(fname, '.*')}",
  3.1387 +                mode, @config.install_prefix
  3.1388 +      else
  3.1389 +        install fname, dest, mode, @config.install_prefix
  3.1390 +      end
  3.1391 +    end
  3.1392 +  end
  3.1393 +
  3.1394 +  def libfiles
  3.1395 +    glob_reject(%w(*.y *.output), targetfiles())
  3.1396 +  end
  3.1397 +
  3.1398 +  def rubyextentions(dir)
  3.1399 +    ents = glob_select("*.#{@config.dllext}", targetfiles())
  3.1400 +    if ents.empty?
  3.1401 +      setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first"
  3.1402 +    end
  3.1403 +    ents
  3.1404 +  end
  3.1405 +
  3.1406 +  def targetfiles
  3.1407 +    mapdir(existfiles() - hookfiles())
  3.1408 +  end
  3.1409 +
  3.1410 +  def mapdir(ents)
  3.1411 +    ents.map {|ent|
  3.1412 +      if File.exist?(ent)
  3.1413 +      then ent                         # objdir
  3.1414 +      else "#{curr_srcdir()}/#{ent}"   # srcdir
  3.1415 +      end
  3.1416 +    }
  3.1417 +  end
  3.1418 +
  3.1419 +  # picked up many entries from cvs-1.11.1/src/ignore.c
  3.1420 +  JUNK_FILES = %w( 
  3.1421 +    core RCSLOG tags TAGS .make.state
  3.1422 +    .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
  3.1423 +    *~ *.old *.bak *.BAK *.orig *.rej _$* *$
  3.1424 +
  3.1425 +    *.org *.in .*
  3.1426 +  )
  3.1427 +
  3.1428 +  def existfiles
  3.1429 +    glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.')))
  3.1430 +  end
  3.1431 +
  3.1432 +  def hookfiles
  3.1433 +    %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
  3.1434 +      %w( config setup install clean distclean ).map {|t| sprintf(fmt, t) }
  3.1435 +    }.flatten
  3.1436 +  end
  3.1437 +
  3.1438 +  def glob_select(pat, ents)
  3.1439 +    re = globs2re([pat])
  3.1440 +    ents.select {|ent| re =~ ent }
  3.1441 +  end
  3.1442 +
  3.1443 +  def glob_reject(pats, ents)
  3.1444 +    re = globs2re(pats)
  3.1445 +    ents.reject {|ent| re =~ ent }
  3.1446 +  end
  3.1447 +
  3.1448 +  GLOB2REGEX = {
  3.1449 +    '.' => '\.',
  3.1450 +    '$' => '\$',
  3.1451 +    '#' => '\#',
  3.1452 +    '*' => '.*'
  3.1453 +  }
  3.1454 +
  3.1455 +  def globs2re(pats)
  3.1456 +    /\A(?:#{
  3.1457 +      pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|')
  3.1458 +    })\z/
  3.1459 +  end
  3.1460 +
  3.1461 +  #
  3.1462 +  # TASK test
  3.1463 +  #
  3.1464 +
  3.1465 +  TESTDIR = 'test'
  3.1466 +
  3.1467 +  def exec_test
  3.1468 +    unless File.directory?('test')
  3.1469 +      $stderr.puts 'no test in this package' if verbose?
  3.1470 +      return
  3.1471 +    end
  3.1472 +    $stderr.puts 'Running tests...' if verbose?
  3.1473 +    begin
  3.1474 +      require 'test/unit'
  3.1475 +    rescue LoadError
  3.1476 +      setup_rb_error 'test/unit cannot loaded.  You need Ruby 1.8 or later to invoke this task.'
  3.1477 +    end
  3.1478 +    runner = Test::Unit::AutoRunner.new(true)
  3.1479 +    runner.to_run << TESTDIR
  3.1480 +    runner.run
  3.1481 +  end
  3.1482 +
  3.1483 +  #
  3.1484 +  # TASK clean
  3.1485 +  #
  3.1486 +
  3.1487 +  def exec_clean
  3.1488 +    exec_task_traverse 'clean'
  3.1489 +    rm_f @config.savefile
  3.1490 +    rm_f 'InstalledFiles'
  3.1491 +  end
  3.1492 +
  3.1493 +  alias clean_dir_bin noop
  3.1494 +  alias clean_dir_lib noop
  3.1495 +  alias clean_dir_data noop
  3.1496 +  alias clean_dir_conf noop
  3.1497 +  alias clean_dir_man noop
  3.1498 +
  3.1499 +  def clean_dir_ext(rel)
  3.1500 +    return unless extdir?(curr_srcdir())
  3.1501 +    make 'clean' if File.file?('Makefile')
  3.1502 +  end
  3.1503 +
  3.1504 +  #
  3.1505 +  # TASK distclean
  3.1506 +  #
  3.1507 +
  3.1508 +  def exec_distclean
  3.1509 +    exec_task_traverse 'distclean'
  3.1510 +    rm_f @config.savefile
  3.1511 +    rm_f 'InstalledFiles'
  3.1512 +  end
  3.1513 +
  3.1514 +  alias distclean_dir_bin noop
  3.1515 +  alias distclean_dir_lib noop
  3.1516 +
  3.1517 +  def distclean_dir_ext(rel)
  3.1518 +    return unless extdir?(curr_srcdir())
  3.1519 +    make 'distclean' if File.file?('Makefile')
  3.1520 +  end
  3.1521 +
  3.1522 +  alias distclean_dir_data noop
  3.1523 +  alias distclean_dir_conf noop
  3.1524 +  alias distclean_dir_man noop
  3.1525 +
  3.1526 +  #
  3.1527 +  # Traversing
  3.1528 +  #
  3.1529 +
  3.1530 +  def exec_task_traverse(task)
  3.1531 +    run_hook "pre-#{task}"
  3.1532 +    FILETYPES.each do |type|
  3.1533 +      if type == 'ext' and config('without-ext') == 'yes'
  3.1534 +        $stderr.puts 'skipping ext/* by user option' if verbose?
  3.1535 +        next
  3.1536 +      end
  3.1537 +      traverse task, type, "#{task}_dir_#{type}"
  3.1538 +    end
  3.1539 +    run_hook "post-#{task}"
  3.1540 +  end
  3.1541 +
  3.1542 +  def traverse(task, rel, mid)
  3.1543 +    dive_into(rel) {
  3.1544 +      run_hook "pre-#{task}"
  3.1545 +      __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
  3.1546 +      directories_of(curr_srcdir()).each do |d|
  3.1547 +        traverse task, "#{rel}/#{d}", mid
  3.1548 +      end
  3.1549 +      run_hook "post-#{task}"
  3.1550 +    }
  3.1551 +  end
  3.1552 +
  3.1553 +  def dive_into(rel)
  3.1554 +    return unless File.dir?("#{@srcdir}/#{rel}")
  3.1555 +
  3.1556 +    dir = File.basename(rel)
  3.1557 +    Dir.mkdir dir unless File.dir?(dir)
  3.1558 +    prevdir = Dir.pwd
  3.1559 +    Dir.chdir dir
  3.1560 +    $stderr.puts '---> ' + rel if verbose?
  3.1561 +    @currdir = rel
  3.1562 +    yield
  3.1563 +    Dir.chdir prevdir
  3.1564 +    $stderr.puts '<--- ' + rel if verbose?
  3.1565 +    @currdir = File.dirname(rel)
  3.1566 +  end
  3.1567 +
  3.1568 +  def run_hook(id)
  3.1569 +    path = [ "#{curr_srcdir()}/#{id}",
  3.1570 +             "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) }
  3.1571 +    return unless path
  3.1572 +    $stderr.puts "invoking hook script #{path}" if verbose?
  3.1573 +    begin
  3.1574 +      instance_eval File.read(path), path, 1
  3.1575 +    rescue
  3.1576 +      raise if $DEBUG
  3.1577 +      setup_rb_error "hook #{path} failed:\n" + $!.message
  3.1578 +    end
  3.1579 +  end
  3.1580 +
  3.1581 +end   # class Installer
  3.1582 +
  3.1583 +
  3.1584 +class SetupError < StandardError; end
  3.1585 +
  3.1586 +def setup_rb_error(msg)
  3.1587 +  raise SetupError, msg
  3.1588 +end
  3.1589 +
  3.1590 +if $0 == __FILE__
  3.1591 +  begin
  3.1592 +    ToplevelInstaller.invoke
  3.1593 +  rescue SetupError
  3.1594 +    raise if $DEBUG
  3.1595 +    $stderr.puts $!.message
  3.1596 +    $stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
  3.1597 +    exit 1
  3.1598 +  end
  3.1599 +end