├── COPYING ├── NEWS ├── README ├── Rakefile ├── lib └── evil.rb ├── setup.rb └── test ├── tc_all.rb ├── tc_inline.rb └── test-extract.rb /COPYING: -------------------------------------------------------------------------------- 1 | evil-ruby is copyrighted free software by Florian Gross . 2 | You can redistribute it and/or modify it under either the terms of the GPL 3 | (see http://www.gnu.org/licenses/gpl.html), or the conditions below: 4 | 5 | 1. You may make and give away verbatim copies of the source form of the 6 | software without restriction, provided that you duplicate all of the 7 | original copyright notices and associated disclaimers. 8 | 9 | 2. You may modify your copy of the software in any way, provided that 10 | you do at least ONE of the following: 11 | 12 | a) place your modifications in the Public Domain or otherwise 13 | make them Freely Available, such as by posting said 14 | modifications to Usenet or an equivalent medium, or by allowing 15 | the author to include your modifications in the software. 16 | 17 | b) use the modified software only within your corporation or 18 | organization. 19 | 20 | c) give non-standard binaries non-standard names, with 21 | instructions on where to get the original software distribution. 22 | 23 | d) make other distribution arrangements with the author. 24 | 25 | 3. You may distribute the software in object code or binary form, 26 | provided that you do at least ONE of the following: 27 | 28 | a) distribute the binaries and library files of the software, 29 | together with instructions (in the manual page or equivalent) 30 | on where to get the original distribution. 31 | 32 | b) accompany the distribution with the machine-readable source of 33 | the software. 34 | 35 | c) give non-standard binaries non-standard names, with 36 | instructions on where to get the original software distribution. 37 | 38 | d) make other distribution arrangements with the author. 39 | 40 | 4. You may modify and include the part of the software into any other 41 | software (possibly commercial). But some files in the distribution 42 | are not written by the author, so that they are not under these terms. 43 | 44 | For the list of those files and their copying conditions, see the 45 | file LEGAL. 46 | 47 | 5. The scripts and library files supplied as input to or produced as 48 | output from the software do not automatically fall under the 49 | copyright of the software, but belong to whomever generated them, 50 | and may be sold commercially, and may be aggregated with this 51 | software. 52 | 53 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR 54 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 55 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 56 | PURPOSE. 57 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | = NEWS 2 | 3 | This file sums up important changes that happened between releases. 4 | 5 | == evil-ruby 0.1.0 6 | * Fixed to work in Ruby 1.9 as well as 1.8 7 | * Removed features that caused crashs 8 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | = README for evil-ruby 2 | 3 | Extends Ruby's semantics by accessing its internals from pure Ruby code. 4 | 5 | 6 | == Installation 7 | 8 | De-compress archive and enter its top directory. Then type: 9 | 10 | ($ su) 11 | # ruby setup.rb 12 | 13 | This simple step installs this program under the default location of Ruby 14 | libraries. You can also install files into your favorite directory by supplying 15 | setup.rb with some options. Try "ruby setup.rb --help". 16 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'rake/packagetask' 3 | require 'rake/rdoctask' 4 | require 'find' 5 | 6 | readme = File.read("README").gsub("\r\n", "\n") 7 | author_line = readme[/^\*\s*Author:.+$/].split(/\s+/, 2)[1] rescue nil 8 | 9 | # Manual globals 10 | 11 | PKG_AUTOREQUIRE = nil 12 | PKG_RUBY_VERSION = '>= 1.8.4' 13 | PKG_GEM_DEPENDENCIES = {} 14 | PKG_RDOC_FILES = ['README', 'NEWS'] 15 | PKG_RDOC_OPTIONS = %w(--all --main README --title #{PKG_NAME}) 16 | PKG_FILES = PKG_RDOC_FILES + ['COPYING', 'setup.rb', 'Rakefile'] 17 | 18 | # Automatic globals 19 | 20 | PKG_NAME, PKG_VERSION = *File.read("NEWS")[/^==.+$/].split(/\s+/)[1..2] 21 | PKG_DESCRIPTION = readme.split(/\n{3,}/)[0].sub(/^=.+$\s*/, "") rescue nil 22 | PKG_SUMMARY = readme[/^=.+$/].split(/--/)[1].strip rescue PKG_DESCRIPTION 23 | PKG_HOMEPAGE = readme[/^\*\s*Homepage:.+$/].split(/\s+/, 2)[1] rescue nil 24 | PKG_EMAIL = author_line[/<(.+)>/, 1] rescue nil 25 | PKG_AUTHOR = author_line.sub(PKG_EMAIL, "").sub("<>", "").strip rescue nil 26 | 27 | Find.find('lib/', 'test/', 'bin/') do |file| 28 | if FileTest.directory?(file) and file[/\.svn/i] then 29 | Find.prune 30 | elsif !file[/\.DS_Store/i] then 31 | PKG_FILES << file 32 | end 33 | end 34 | 35 | PKG_FILES.reject! { |file| !File.file?(file) } 36 | 37 | PKG_EXE_FILES, PKG_LIB_FILES = *%w(bin/ lib/).map do |dir| 38 | PKG_FILES.grep(/#{dir}/i).reject { |f| File.directory?(f) } 39 | end 40 | 41 | PKG_EXE_FILES.map! { |exe| exe.sub(%r(^bin/), "") } 42 | 43 | # Tasks 44 | 45 | task :default => :test 46 | 47 | # Test task 48 | if File.exist?("test/") then 49 | require 'rake/testtask' 50 | 51 | Rake::TestTask.new do |test| 52 | test.test_files = ['test/tc_all.rb'] 53 | end 54 | else 55 | task :test do 56 | puts "No tests to run" 57 | end 58 | end 59 | 60 | # Doc task 61 | Rake::RDocTask.new do |rd| 62 | rd.rdoc_files.include(PKG_LIB_FILES, PKG_RDOC_FILES) 63 | rd.options += PKG_RDOC_OPTIONS 64 | end 65 | 66 | # Tar task 67 | Rake::PackageTask.new(PKG_NAME, PKG_VERSION) do |pkg| 68 | pkg.need_tar = true 69 | pkg.package_files = PKG_FILES 70 | end 71 | 72 | # Gem task 73 | begin 74 | require 'rake/gempackagetask' 75 | 76 | spec = Gem::Specification.new do |spec| 77 | spec.name = PKG_NAME 78 | spec.version = PKG_VERSION 79 | spec.summary = PKG_SUMMARY 80 | spec.description = PKG_DESCRIPTION 81 | 82 | spec.homepage = PKG_HOMEPAGE 83 | spec.email = PKG_EMAIL 84 | spec.author = PKG_AUTHOR 85 | 86 | spec.has_rdoc = true 87 | spec.extra_rdoc_files = PKG_RDOC_FILES 88 | spec.rdoc_options += PKG_RDOC_OPTIONS 89 | 90 | if File.exist?("test/") then 91 | spec.test_files = ['test/tc_all.rb'] 92 | end 93 | 94 | spec.required_ruby_version = PKG_RUBY_VERSION 95 | (PKG_GEM_DEPENDENCIES || {}).each do |name, version| 96 | spec.add_dependency(name, version) 97 | end 98 | 99 | spec.files = PKG_FILES 100 | spec.executables = PKG_EXE_FILES 101 | spec.autorequire = PKG_AUTOREQUIRE 102 | end 103 | 104 | Rake::GemPackageTask.new(spec) do |pkg| 105 | pkg.need_zip = true 106 | pkg.need_tar = true 107 | end 108 | rescue LoadError 109 | end 110 | -------------------------------------------------------------------------------- /lib/evil.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yugui/evil-ruby/6ef54293d43b957d3540c13dde63ef06eaa0c701/lib/evil.rb -------------------------------------------------------------------------------- /setup.rb: -------------------------------------------------------------------------------- 1 | # 2 | # setup.rb 3 | # 4 | # Copyright (c) 2000-2004 Minero Aoki 5 | # 6 | # This program is free software. 7 | # You can distribute/modify this program under the terms of 8 | # the GNU LGPL, Lesser General Public License version 2.1. 9 | # 10 | 11 | unless Enumerable.method_defined?(:map) # Ruby 1.4.6 12 | module Enumerable 13 | alias map collect 14 | end 15 | end 16 | 17 | unless File.respond_to?(:read) # Ruby 1.6 18 | def File.read(fname) 19 | open(fname) {|f| 20 | return f.read 21 | } 22 | end 23 | end 24 | 25 | def File.binread(fname) 26 | open(fname, 'rb') {|f| 27 | return f.read 28 | } 29 | end 30 | 31 | # for corrupted windows stat(2) 32 | def File.dir?(path) 33 | File.directory?((path[-1,1] == '/') ? path : path + '/') 34 | end 35 | 36 | 37 | class SetupError < StandardError; end 38 | 39 | def setup_rb_error(msg) 40 | raise SetupError, msg 41 | end 42 | 43 | # 44 | # Config 45 | # 46 | 47 | if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } 48 | ARGV.delete(arg) 49 | require arg.split(/=/, 2)[1] 50 | $".push 'rbconfig.rb' 51 | else 52 | require 'rbconfig' 53 | end 54 | 55 | def multipackage_install? 56 | FileTest.directory?(File.dirname($0) + '/packages') 57 | end 58 | 59 | 60 | class ConfigItem 61 | def initialize(name, template, default, desc) 62 | @name = name.freeze 63 | @template = template 64 | @value = default 65 | @default = default.dup.freeze 66 | @description = desc 67 | end 68 | 69 | attr_reader :name 70 | attr_reader :description 71 | 72 | attr_accessor :default 73 | alias help_default default 74 | 75 | def help_opt 76 | "--#{@name}=#{@template}" 77 | end 78 | 79 | def value 80 | @value 81 | end 82 | 83 | def eval(table) 84 | @value.gsub(%r<\$([^/]+)>) { table[$1] } 85 | end 86 | 87 | def set(val) 88 | @value = check(val) 89 | end 90 | 91 | private 92 | 93 | def check(val) 94 | setup_rb_error "config: --#{name} requires argument" unless val 95 | val 96 | end 97 | end 98 | 99 | class BoolItem < ConfigItem 100 | def config_type 101 | 'bool' 102 | end 103 | 104 | def help_opt 105 | "--#{@name}" 106 | end 107 | 108 | private 109 | 110 | def check(val) 111 | return 'yes' unless val 112 | unless /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i =~ val 113 | setup_rb_error "config: --#{@name} accepts only yes/no for argument" 114 | end 115 | (/\Ay(es)?|\At(rue)/i =~ value) ? 'yes' : 'no' 116 | end 117 | end 118 | 119 | class PathItem < ConfigItem 120 | def config_type 121 | 'path' 122 | end 123 | 124 | private 125 | 126 | def check(path) 127 | setup_rb_error "config: --#{@name} requires argument" unless path 128 | path[0,1] == '$' ? path : File.expand_path(path) 129 | end 130 | end 131 | 132 | class ProgramItem < ConfigItem 133 | def config_type 134 | 'program' 135 | end 136 | end 137 | 138 | class SelectItem < ConfigItem 139 | def initialize(name, template, default, desc) 140 | super 141 | @ok = template.split('/') 142 | end 143 | 144 | def config_type 145 | 'select' 146 | end 147 | 148 | private 149 | 150 | def check(val) 151 | unless @ok.include?(val.strip) 152 | setup_rb_error "config: use --#{@name}=#{@template} (#{val})" 153 | end 154 | val.strip 155 | end 156 | end 157 | 158 | class PackageSelectionItem < ConfigItem 159 | def initialize(name, template, default, help_default, desc) 160 | super name, template, default, desc 161 | @help_default = help_default 162 | end 163 | 164 | attr_reader :help_default 165 | 166 | def config_type 167 | 'package' 168 | end 169 | 170 | private 171 | 172 | def check(val) 173 | unless File.dir?("packages/#{val}") 174 | setup_rb_error "config: no such package: #{val}" 175 | end 176 | val 177 | end 178 | end 179 | 180 | class ConfigTable_class 181 | 182 | def initialize(items) 183 | @items = items 184 | @table = {} 185 | items.each do |i| 186 | @table[i.name] = i 187 | end 188 | ALIASES.each do |ali, name| 189 | @table[ali] = @table[name] 190 | end 191 | end 192 | 193 | include Enumerable 194 | 195 | def each(&block) 196 | @items.each(&block) 197 | end 198 | 199 | def key?(name) 200 | @table.key?(name) 201 | end 202 | 203 | def lookup(name) 204 | @table[name] or raise ArgumentError, "no such config item: #{name}" 205 | end 206 | 207 | def add(item) 208 | @items.push item 209 | @table[item.name] = item 210 | end 211 | 212 | def remove(name) 213 | item = lookup(name) 214 | @items.delete_if {|i| i.name == name } 215 | @table.delete_if {|name, i| i.name == name } 216 | item 217 | end 218 | 219 | def new 220 | dup() 221 | end 222 | 223 | def savefile 224 | '.config' 225 | end 226 | 227 | def load 228 | begin 229 | t = dup() 230 | File.foreach(savefile()) do |line| 231 | k, v = *line.split(/=/, 2) 232 | t[k] = v.strip 233 | end 234 | t 235 | rescue Errno::ENOENT 236 | setup_rb_error $!.message + "#{File.basename($0)} config first" 237 | end 238 | end 239 | 240 | def save 241 | @items.each {|i| i.value } 242 | File.open(savefile(), 'w') {|f| 243 | @items.each do |i| 244 | f.printf "%s=%s\n", i.name, i.value if i.value 245 | end 246 | } 247 | end 248 | 249 | def [](key) 250 | lookup(key).eval(self) 251 | end 252 | 253 | def []=(key, val) 254 | lookup(key).set val 255 | end 256 | 257 | end 258 | 259 | c = ::Config::CONFIG 260 | 261 | rubypath = c['bindir'] + '/' + c['ruby_install_name'] 262 | 263 | major = c['MAJOR'].to_i 264 | minor = c['MINOR'].to_i 265 | teeny = c['TEENY'].to_i 266 | version = "#{major}.#{minor}" 267 | 268 | # ruby ver. >= 1.4.4? 269 | newpath_p = ((major >= 2) or 270 | ((major == 1) and 271 | ((minor >= 5) or 272 | ((minor == 4) and (teeny >= 4))))) 273 | 274 | if c['rubylibdir'] 275 | # V < 1.6.3 276 | _stdruby = c['rubylibdir'] 277 | _siteruby = c['sitedir'] 278 | _siterubyver = c['sitelibdir'] 279 | _siterubyverarch = c['sitearchdir'] 280 | elsif newpath_p 281 | # 1.4.4 <= V <= 1.6.3 282 | _stdruby = "$prefix/lib/ruby/#{version}" 283 | _siteruby = c['sitedir'] 284 | _siterubyver = "$siteruby/#{version}" 285 | _siterubyverarch = "$siterubyver/#{c['arch']}" 286 | else 287 | # V < 1.4.4 288 | _stdruby = "$prefix/lib/ruby/#{version}" 289 | _siteruby = "$prefix/lib/ruby/#{version}/site_ruby" 290 | _siterubyver = _siteruby 291 | _siterubyverarch = "$siterubyver/#{c['arch']}" 292 | end 293 | libdir = '-* dummy libdir *-' 294 | stdruby = '-* dummy rubylibdir *-' 295 | siteruby = '-* dummy site_ruby *-' 296 | siterubyver = '-* dummy site_ruby version *-' 297 | parameterize = lambda {|path| 298 | path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')\ 299 | .sub(/\A#{Regexp.quote(libdir)}/, '$libdir')\ 300 | .sub(/\A#{Regexp.quote(stdruby)}/, '$stdruby')\ 301 | .sub(/\A#{Regexp.quote(siteruby)}/, '$siteruby')\ 302 | .sub(/\A#{Regexp.quote(siterubyver)}/, '$siterubyver') 303 | } 304 | libdir = parameterize.call(c['libdir']) 305 | stdruby = parameterize.call(_stdruby) 306 | siteruby = parameterize.call(_siteruby) 307 | siterubyver = parameterize.call(_siterubyver) 308 | siterubyverarch = parameterize.call(_siterubyverarch) 309 | 310 | if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } 311 | makeprog = arg.sub(/'/, '').split(/=/, 2)[1] 312 | else 313 | makeprog = 'make' 314 | end 315 | 316 | common_conf = [ 317 | PathItem.new('prefix', 'path', c['prefix'], 318 | 'path prefix of target environment'), 319 | PathItem.new('bindir', 'path', parameterize.call(c['bindir']), 320 | 'the directory for commands'), 321 | PathItem.new('libdir', 'path', libdir, 322 | 'the directory for libraries'), 323 | PathItem.new('datadir', 'path', parameterize.call(c['datadir']), 324 | 'the directory for shared data'), 325 | PathItem.new('mandir', 'path', parameterize.call(c['mandir']), 326 | 'the directory for man pages'), 327 | PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']), 328 | 'the directory for man pages'), 329 | PathItem.new('stdruby', 'path', stdruby, 330 | 'the directory for standard ruby libraries'), 331 | PathItem.new('siteruby', 'path', siteruby, 332 | 'the directory for version-independent aux ruby libraries'), 333 | PathItem.new('siterubyver', 'path', siterubyver, 334 | 'the directory for aux ruby libraries'), 335 | PathItem.new('siterubyverarch', 'path', siterubyverarch, 336 | 'the directory for aux ruby binaries'), 337 | PathItem.new('rbdir', 'path', '$siterubyver', 338 | 'the directory for ruby scripts'), 339 | PathItem.new('sodir', 'path', '$siterubyverarch', 340 | 'the directory for ruby extentions'), 341 | PathItem.new('rubypath', 'path', rubypath, 342 | 'the path to set to #! line'), 343 | ProgramItem.new('rubyprog', 'name', rubypath, 344 | 'the ruby program using for installation'), 345 | ProgramItem.new('makeprog', 'name', makeprog, 346 | 'the make program to compile ruby extentions'), 347 | SelectItem.new('shebang', 'all/ruby/never', 'ruby', 348 | 'shebang line (#!) editing mode'), 349 | BoolItem.new('without-ext', 'yes/no', 'no', 350 | 'does not compile/install ruby extentions') 351 | ] 352 | class ConfigTable_class # open again 353 | ALIASES = { 354 | 'std-ruby' => 'stdruby', 355 | 'site-ruby-common' => 'siteruby', # For backward compatibility 356 | 'site-ruby' => 'siterubyver', # For backward compatibility 357 | 'bin-dir' => 'bindir', 358 | 'bin-dir' => 'bindir', 359 | 'rb-dir' => 'rbdir', 360 | 'so-dir' => 'sodir', 361 | 'data-dir' => 'datadir', 362 | 'ruby-path' => 'rubypath', 363 | 'ruby-prog' => 'rubyprog', 364 | 'ruby' => 'rubyprog', 365 | 'make-prog' => 'makeprog', 366 | 'make' => 'makeprog' 367 | } 368 | end 369 | multipackage_conf = [ 370 | PackageSelectionItem.new('with', 'name,name...', '', 'ALL', 371 | 'package names that you want to install'), 372 | PackageSelectionItem.new('without', 'name,name...', '', 'NONE', 373 | 'package names that you do not want to install') 374 | ] 375 | if multipackage_install? 376 | ConfigTable = ConfigTable_class.new(common_conf + multipackage_conf) 377 | else 378 | ConfigTable = ConfigTable_class.new(common_conf) 379 | end 380 | 381 | 382 | module MetaConfigAPI 383 | 384 | def eval_file_ifexist(fname) 385 | instance_eval File.read(fname), fname, 1 if File.file?(fname) 386 | end 387 | 388 | def config_names 389 | ConfigTable.map {|i| i.name } 390 | end 391 | 392 | def config?(name) 393 | ConfigTable.key?(name) 394 | end 395 | 396 | def bool_config?(name) 397 | ConfigTable.lookup(name).config_type == 'bool' 398 | end 399 | 400 | def path_config?(name) 401 | ConfigTable.lookup(name).config_type == 'path' 402 | end 403 | 404 | def value_config?(name) 405 | case ConfigTable.lookup(name).config_type 406 | when 'bool', 'path' 407 | true 408 | else 409 | false 410 | end 411 | end 412 | 413 | def add_config(item) 414 | ConfigTable.add item 415 | end 416 | 417 | def add_bool_config(name, default, desc) 418 | ConfigTable.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc) 419 | end 420 | 421 | def add_path_config(name, default, desc) 422 | ConfigTable.add PathItem.new(name, 'path', default, desc) 423 | end 424 | 425 | def set_config_default(name, default) 426 | ConfigTable.lookup(name).default = default 427 | end 428 | 429 | def remove_config(name) 430 | ConfigTable.remove(name) 431 | end 432 | 433 | end 434 | 435 | 436 | # 437 | # File Operations 438 | # 439 | 440 | module FileOperations 441 | 442 | def mkdir_p(dirname, prefix = nil) 443 | dirname = prefix + File.expand_path(dirname) if prefix 444 | $stderr.puts "mkdir -p #{dirname}" if verbose? 445 | return if no_harm? 446 | 447 | # does not check '/'... it's too abnormal case 448 | dirs = File.expand_path(dirname).split(%r<(?=/)>) 449 | if /\A[a-z]:\z/i =~ dirs[0] 450 | disk = dirs.shift 451 | dirs[0] = disk + dirs[0] 452 | end 453 | dirs.each_index do |idx| 454 | path = dirs[0..idx].join('') 455 | Dir.mkdir path unless File.dir?(path) 456 | end 457 | end 458 | 459 | def rm_f(fname) 460 | $stderr.puts "rm -f #{fname}" if verbose? 461 | return if no_harm? 462 | 463 | if File.exist?(fname) or File.symlink?(fname) 464 | File.chmod 0777, fname 465 | File.unlink fname 466 | end 467 | end 468 | 469 | def rm_rf(dn) 470 | $stderr.puts "rm -rf #{dn}" if verbose? 471 | return if no_harm? 472 | 473 | Dir.chdir dn 474 | Dir.foreach('.') do |fn| 475 | next if fn == '.' 476 | next if fn == '..' 477 | if File.dir?(fn) 478 | verbose_off { 479 | rm_rf fn 480 | } 481 | else 482 | verbose_off { 483 | rm_f fn 484 | } 485 | end 486 | end 487 | Dir.chdir '..' 488 | Dir.rmdir dn 489 | end 490 | 491 | def move_file(src, dest) 492 | File.unlink dest if File.exist?(dest) 493 | begin 494 | File.rename src, dest 495 | rescue 496 | File.open(dest, 'wb') {|f| f.write File.binread(src) } 497 | File.chmod File.stat(src).mode, dest 498 | File.unlink src 499 | end 500 | end 501 | 502 | def install(from, dest, mode, prefix = nil) 503 | $stderr.puts "install #{from} #{dest}" if verbose? 504 | return if no_harm? 505 | 506 | realdest = prefix ? prefix + File.expand_path(dest) : dest 507 | realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest) 508 | str = File.binread(from) 509 | if diff?(str, realdest) 510 | verbose_off { 511 | rm_f realdest if File.exist?(realdest) 512 | } 513 | File.open(realdest, 'wb') {|f| 514 | f.write str 515 | } 516 | File.chmod mode, realdest 517 | 518 | File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| 519 | if prefix 520 | f.puts realdest.sub(prefix, '') 521 | else 522 | f.puts realdest 523 | end 524 | } 525 | end 526 | end 527 | 528 | def diff?(new_content, path) 529 | return true unless File.exist?(path) 530 | new_content != File.binread(path) 531 | end 532 | 533 | def command(str) 534 | $stderr.puts str if verbose? 535 | system str or raise RuntimeError, "'system #{str}' failed" 536 | end 537 | 538 | def ruby(str) 539 | command config('rubyprog') + ' ' + str 540 | end 541 | 542 | def make(task = '') 543 | command config('makeprog') + ' ' + task 544 | end 545 | 546 | def extdir?(dir) 547 | File.exist?(dir + '/MANIFEST') 548 | end 549 | 550 | def all_files_in(dirname) 551 | Dir.open(dirname) {|d| 552 | return d.select {|ent| File.file?("#{dirname}/#{ent}") } 553 | } 554 | end 555 | 556 | REJECT_DIRS = %w( 557 | CVS SCCS RCS CVS.adm .svn 558 | ) 559 | 560 | def all_dirs_in(dirname) 561 | Dir.open(dirname) {|d| 562 | return d.select {|n| File.dir?("#{dirname}/#{n}") } - %w(. ..) - REJECT_DIRS 563 | } 564 | end 565 | 566 | end 567 | 568 | 569 | # 570 | # Main Installer 571 | # 572 | 573 | module HookUtils 574 | 575 | def run_hook(name) 576 | try_run_hook "#{curr_srcdir()}/#{name}" or 577 | try_run_hook "#{curr_srcdir()}/#{name}.rb" 578 | end 579 | 580 | def try_run_hook(fname) 581 | return false unless File.file?(fname) 582 | begin 583 | instance_eval File.read(fname), fname, 1 584 | rescue 585 | setup_rb_error "hook #{fname} failed:\n" + $!.message 586 | end 587 | true 588 | end 589 | 590 | end 591 | 592 | 593 | module HookScriptAPI 594 | 595 | def get_config(key) 596 | @config[key] 597 | end 598 | 599 | alias config get_config 600 | 601 | def set_config(key, val) 602 | @config[key] = val 603 | end 604 | 605 | # 606 | # srcdir/objdir (works only in the package directory) 607 | # 608 | 609 | #abstract srcdir_root 610 | #abstract objdir_root 611 | #abstract relpath 612 | 613 | def curr_srcdir 614 | "#{srcdir_root()}/#{relpath()}" 615 | end 616 | 617 | def curr_objdir 618 | "#{objdir_root()}/#{relpath()}" 619 | end 620 | 621 | def srcfile(path) 622 | "#{curr_srcdir()}/#{path}" 623 | end 624 | 625 | def srcexist?(path) 626 | File.exist?(srcfile(path)) 627 | end 628 | 629 | def srcdirectory?(path) 630 | File.dir?(srcfile(path)) 631 | end 632 | 633 | def srcfile?(path) 634 | File.file? srcfile(path) 635 | end 636 | 637 | def srcentries(path = '.') 638 | Dir.open("#{curr_srcdir()}/#{path}") {|d| 639 | return d.to_a - %w(. ..) 640 | } 641 | end 642 | 643 | def srcfiles(path = '.') 644 | srcentries(path).select {|fname| 645 | File.file?(File.join(curr_srcdir(), path, fname)) 646 | } 647 | end 648 | 649 | def srcdirectories(path = '.') 650 | srcentries(path).select {|fname| 651 | File.dir?(File.join(curr_srcdir(), path, fname)) 652 | } 653 | end 654 | 655 | end 656 | 657 | 658 | class ToplevelInstaller 659 | 660 | Version = '3.3.1' 661 | Copyright = 'Copyright (c) 2000-2004 Minero Aoki' 662 | 663 | TASKS = [ 664 | [ 'all', 'do config, setup, then install' ], 665 | [ 'config', 'saves your configurations' ], 666 | [ 'show', 'shows current configuration' ], 667 | [ 'setup', 'compiles ruby extentions and others' ], 668 | [ 'install', 'installs files' ], 669 | [ 'clean', "does `make clean' for each extention" ], 670 | [ 'distclean',"does `make distclean' for each extention" ] 671 | ] 672 | 673 | def ToplevelInstaller.invoke 674 | instance().invoke 675 | end 676 | 677 | @singleton = nil 678 | 679 | def ToplevelInstaller.instance 680 | @singleton ||= new(File.dirname($0)) 681 | @singleton 682 | end 683 | 684 | include MetaConfigAPI 685 | 686 | def initialize(ardir_root) 687 | @config = nil 688 | @options = { 'verbose' => true } 689 | @ardir = File.expand_path(ardir_root) 690 | end 691 | 692 | def inspect 693 | "#<#{self.class} #{__id__()}>" 694 | end 695 | 696 | def invoke 697 | run_metaconfigs 698 | case task = parsearg_global() 699 | when nil, 'all' 700 | @config = load_config('config') 701 | parsearg_config 702 | init_installers 703 | exec_config 704 | exec_setup 705 | exec_install 706 | else 707 | @config = load_config(task) 708 | __send__ "parsearg_#{task}" 709 | init_installers 710 | __send__ "exec_#{task}" 711 | end 712 | end 713 | 714 | def run_metaconfigs 715 | eval_file_ifexist "#{@ardir}/metaconfig" 716 | end 717 | 718 | def load_config(task) 719 | case task 720 | when 'config' 721 | ConfigTable.new 722 | when 'clean', 'distclean' 723 | if File.exist?(ConfigTable.savefile) 724 | then ConfigTable.load 725 | else ConfigTable.new 726 | end 727 | else 728 | ConfigTable.load 729 | end 730 | end 731 | 732 | def init_installers 733 | @installer = Installer.new(@config, @options, @ardir, File.expand_path('.')) 734 | end 735 | 736 | # 737 | # Hook Script API bases 738 | # 739 | 740 | def srcdir_root 741 | @ardir 742 | end 743 | 744 | def objdir_root 745 | '.' 746 | end 747 | 748 | def relpath 749 | '.' 750 | end 751 | 752 | # 753 | # Option Parsing 754 | # 755 | 756 | def parsearg_global 757 | valid_task = /\A(?:#{TASKS.map {|task,desc| task }.join '|'})\z/ 758 | 759 | while arg = ARGV.shift 760 | case arg 761 | when /\A\w+\z/ 762 | setup_rb_error "invalid task: #{arg}" unless valid_task =~ arg 763 | return arg 764 | 765 | when '-q', '--quiet' 766 | @options['verbose'] = false 767 | 768 | when '--verbose' 769 | @options['verbose'] = true 770 | 771 | when '-h', '--help' 772 | print_usage $stdout 773 | exit 0 774 | 775 | when '-v', '--version' 776 | puts "#{File.basename($0)} version #{Version}" 777 | exit 0 778 | 779 | when '--copyright' 780 | puts Copyright 781 | exit 0 782 | 783 | else 784 | setup_rb_error "unknown global option '#{arg}'" 785 | end 786 | end 787 | 788 | nil 789 | end 790 | 791 | 792 | def parsearg_no_options 793 | unless ARGV.empty? 794 | setup_rb_error "#{task}: unknown options: #{ARGV.join ' '}" 795 | end 796 | end 797 | 798 | alias parsearg_show parsearg_no_options 799 | alias parsearg_setup parsearg_no_options 800 | alias parsearg_clean parsearg_no_options 801 | alias parsearg_distclean parsearg_no_options 802 | 803 | def parsearg_config 804 | re = /\A--(#{ConfigTable.map {|i| i.name }.join('|')})(?:=(.*))?\z/ 805 | @options['config-opt'] = [] 806 | 807 | while i = ARGV.shift 808 | if /\A--?\z/ =~ i 809 | @options['config-opt'] = ARGV.dup 810 | break 811 | end 812 | m = re.match(i) or setup_rb_error "config: unknown option #{i}" 813 | name, value = *m.to_a[1,2] 814 | @config[name] = value 815 | end 816 | end 817 | 818 | def parsearg_install 819 | @options['no-harm'] = false 820 | @options['install-prefix'] = '' 821 | while a = ARGV.shift 822 | case a 823 | when /\A--no-harm\z/ 824 | @options['no-harm'] = true 825 | when /\A--prefix=(.*)\z/ 826 | path = $1 827 | path = File.expand_path(path) unless path[0,1] == '/' 828 | @options['install-prefix'] = path 829 | else 830 | setup_rb_error "install: unknown option #{a}" 831 | end 832 | end 833 | end 834 | 835 | def print_usage(out) 836 | out.puts 'Typical Installation Procedure:' 837 | out.puts " $ ruby #{File.basename $0} config" 838 | out.puts " $ ruby #{File.basename $0} setup" 839 | out.puts " # ruby #{File.basename $0} install (may require root privilege)" 840 | out.puts 841 | out.puts 'Detailed Usage:' 842 | out.puts " ruby #{File.basename $0} " 843 | out.puts " ruby #{File.basename $0} [] []" 844 | 845 | fmt = " %-24s %s\n" 846 | out.puts 847 | out.puts 'Global options:' 848 | out.printf fmt, '-q,--quiet', 'suppress message outputs' 849 | out.printf fmt, ' --verbose', 'output messages verbosely' 850 | out.printf fmt, '-h,--help', 'print this message' 851 | out.printf fmt, '-v,--version', 'print version and quit' 852 | out.printf fmt, ' --copyright', 'print copyright and quit' 853 | out.puts 854 | out.puts 'Tasks:' 855 | TASKS.each do |name, desc| 856 | out.printf fmt, name, desc 857 | end 858 | 859 | fmt = " %-24s %s [%s]\n" 860 | out.puts 861 | out.puts 'Options for CONFIG or ALL:' 862 | ConfigTable.each do |item| 863 | out.printf fmt, item.help_opt, item.description, item.help_default 864 | end 865 | out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's" 866 | out.puts 867 | out.puts 'Options for INSTALL:' 868 | out.printf fmt, '--no-harm', 'only display what to do if given', 'off' 869 | out.printf fmt, '--prefix=path', 'install path prefix', '$prefix' 870 | out.puts 871 | end 872 | 873 | # 874 | # Task Handlers 875 | # 876 | 877 | def exec_config 878 | @installer.exec_config 879 | @config.save # must be final 880 | end 881 | 882 | def exec_setup 883 | @installer.exec_setup 884 | end 885 | 886 | def exec_install 887 | @installer.exec_install 888 | end 889 | 890 | def exec_show 891 | ConfigTable.each do |i| 892 | printf "%-20s %s\n", i.name, i.value 893 | end 894 | end 895 | 896 | def exec_clean 897 | @installer.exec_clean 898 | end 899 | 900 | def exec_distclean 901 | @installer.exec_distclean 902 | end 903 | 904 | end 905 | 906 | 907 | class ToplevelInstallerMulti < ToplevelInstaller 908 | 909 | include HookUtils 910 | include HookScriptAPI 911 | include FileOperations 912 | 913 | def initialize(ardir) 914 | super 915 | @packages = all_dirs_in("#{@ardir}/packages") 916 | raise 'no package exists' if @packages.empty? 917 | end 918 | 919 | def run_metaconfigs 920 | eval_file_ifexist "#{@ardir}/metaconfig" 921 | @packages.each do |name| 922 | eval_file_ifexist "#{@ardir}/packages/#{name}/metaconfig" 923 | end 924 | end 925 | 926 | def init_installers 927 | @installers = {} 928 | @packages.each do |pack| 929 | @installers[pack] = Installer.new(@config, @options, 930 | "#{@ardir}/packages/#{pack}", 931 | "packages/#{pack}") 932 | end 933 | 934 | with = extract_selection(config('with')) 935 | without = extract_selection(config('without')) 936 | @selected = @installers.keys.select {|name| 937 | (with.empty? or with.include?(name)) \ 938 | and not without.include?(name) 939 | } 940 | end 941 | 942 | def extract_selection(list) 943 | a = list.split(/,/) 944 | a.each do |name| 945 | setup_rb_error "no such package: #{name}" unless @installers.key?(name) 946 | end 947 | a 948 | end 949 | 950 | def print_usage(f) 951 | super 952 | f.puts 'Inluded packages:' 953 | f.puts ' ' + @packages.sort.join(' ') 954 | f.puts 955 | end 956 | 957 | # 958 | # multi-package metaconfig API 959 | # 960 | 961 | attr_reader :packages 962 | 963 | def declare_packages(list) 964 | raise 'package list is empty' if list.empty? 965 | list.each do |name| 966 | raise "directory packages/#{name} does not exist"\ 967 | unless File.dir?("#{@ardir}/packages/#{name}") 968 | end 969 | @packages = list 970 | end 971 | 972 | # 973 | # Task Handlers 974 | # 975 | 976 | def exec_config 977 | run_hook 'pre-config' 978 | each_selected_installers {|inst| inst.exec_config } 979 | run_hook 'post-config' 980 | @config.save # must be final 981 | end 982 | 983 | def exec_setup 984 | run_hook 'pre-setup' 985 | each_selected_installers {|inst| inst.exec_setup } 986 | run_hook 'post-setup' 987 | end 988 | 989 | def exec_install 990 | run_hook 'pre-install' 991 | each_selected_installers {|inst| inst.exec_install } 992 | run_hook 'post-install' 993 | end 994 | 995 | def exec_clean 996 | rm_f ConfigTable.savefile 997 | run_hook 'pre-clean' 998 | each_selected_installers {|inst| inst.exec_clean } 999 | run_hook 'post-clean' 1000 | end 1001 | 1002 | def exec_distclean 1003 | rm_f ConfigTable.savefile 1004 | run_hook 'pre-distclean' 1005 | each_selected_installers {|inst| inst.exec_distclean } 1006 | run_hook 'post-distclean' 1007 | end 1008 | 1009 | # 1010 | # lib 1011 | # 1012 | 1013 | def each_selected_installers 1014 | Dir.mkdir 'packages' unless File.dir?('packages') 1015 | @selected.each do |pack| 1016 | $stderr.puts "Processing the package `#{pack}' ..." if @options['verbose'] 1017 | Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") 1018 | Dir.chdir "packages/#{pack}" 1019 | yield @installers[pack] 1020 | Dir.chdir '../..' 1021 | end 1022 | end 1023 | 1024 | def verbose? 1025 | @options['verbose'] 1026 | end 1027 | 1028 | def no_harm? 1029 | @options['no-harm'] 1030 | end 1031 | 1032 | end 1033 | 1034 | 1035 | class Installer 1036 | 1037 | FILETYPES = %w( bin lib ext data ) 1038 | 1039 | include HookScriptAPI 1040 | include HookUtils 1041 | include FileOperations 1042 | 1043 | def initialize(config, opt, srcroot, objroot) 1044 | @config = config 1045 | @options = opt 1046 | @srcdir = File.expand_path(srcroot) 1047 | @objdir = File.expand_path(objroot) 1048 | @currdir = '.' 1049 | end 1050 | 1051 | def inspect 1052 | "#<#{self.class} #{File.basename(@srcdir)}>" 1053 | end 1054 | 1055 | # 1056 | # Hook Script API base methods 1057 | # 1058 | 1059 | def srcdir_root 1060 | @srcdir 1061 | end 1062 | 1063 | def objdir_root 1064 | @objdir 1065 | end 1066 | 1067 | def relpath 1068 | @currdir 1069 | end 1070 | 1071 | # 1072 | # configs/options 1073 | # 1074 | 1075 | def no_harm? 1076 | @options['no-harm'] 1077 | end 1078 | 1079 | def verbose? 1080 | @options['verbose'] 1081 | end 1082 | 1083 | def verbose_off 1084 | begin 1085 | save, @options['verbose'] = @options['verbose'], false 1086 | yield 1087 | ensure 1088 | @options['verbose'] = save 1089 | end 1090 | end 1091 | 1092 | # 1093 | # TASK config 1094 | # 1095 | 1096 | def exec_config 1097 | exec_task_traverse 'config' 1098 | end 1099 | 1100 | def config_dir_bin(rel) 1101 | end 1102 | 1103 | def config_dir_lib(rel) 1104 | end 1105 | 1106 | def config_dir_ext(rel) 1107 | extconf if extdir?(curr_srcdir()) 1108 | end 1109 | 1110 | def extconf 1111 | opt = @options['config-opt'].join(' ') 1112 | command "#{config('rubyprog')} #{curr_srcdir()}/extconf.rb #{opt}" 1113 | end 1114 | 1115 | def config_dir_data(rel) 1116 | end 1117 | 1118 | # 1119 | # TASK setup 1120 | # 1121 | 1122 | def exec_setup 1123 | exec_task_traverse 'setup' 1124 | end 1125 | 1126 | def setup_dir_bin(rel) 1127 | all_files_in(curr_srcdir()).each do |fname| 1128 | adjust_shebang "#{curr_srcdir()}/#{fname}" 1129 | end 1130 | end 1131 | 1132 | def adjust_shebang(path) 1133 | return if no_harm? 1134 | tmpfile = File.basename(path) + '.tmp' 1135 | begin 1136 | File.open(path, 'rb') {|r| 1137 | first = r.gets 1138 | return unless File.basename(config('rubypath')) == 'ruby' 1139 | return unless File.basename(first.sub(/\A\#!/, '').split[0]) == 'ruby' 1140 | $stderr.puts "adjusting shebang: #{File.basename(path)}" if verbose? 1141 | File.open(tmpfile, 'wb') {|w| 1142 | w.print first.sub(/\A\#!\s*\S+/, '#! ' + config('rubypath')) 1143 | w.write r.read 1144 | } 1145 | move_file tmpfile, File.basename(path) 1146 | } 1147 | ensure 1148 | File.unlink tmpfile if File.exist?(tmpfile) 1149 | end 1150 | end 1151 | 1152 | def setup_dir_lib(rel) 1153 | end 1154 | 1155 | def setup_dir_ext(rel) 1156 | make if extdir?(curr_srcdir()) 1157 | end 1158 | 1159 | def setup_dir_data(rel) 1160 | end 1161 | 1162 | # 1163 | # TASK install 1164 | # 1165 | 1166 | def exec_install 1167 | rm_f 'InstalledFiles' 1168 | exec_task_traverse 'install' 1169 | end 1170 | 1171 | def install_dir_bin(rel) 1172 | install_files collect_filenames_auto(), "#{config('bindir')}/#{rel}", 0755 1173 | end 1174 | 1175 | def install_dir_lib(rel) 1176 | install_files ruby_scripts(), "#{config('rbdir')}/#{rel}", 0644 1177 | end 1178 | 1179 | def install_dir_ext(rel) 1180 | return unless extdir?(curr_srcdir()) 1181 | install_files ruby_extentions('.'), 1182 | "#{config('sodir')}/#{File.dirname(rel)}", 1183 | 0555 1184 | end 1185 | 1186 | def install_dir_data(rel) 1187 | install_files collect_filenames_auto(), "#{config('datadir')}/#{rel}", 0644 1188 | end 1189 | 1190 | def install_files(list, dest, mode) 1191 | mkdir_p dest, @options['install-prefix'] 1192 | list.each do |fname| 1193 | install fname, dest, mode, @options['install-prefix'] 1194 | end 1195 | end 1196 | 1197 | def ruby_scripts 1198 | collect_filenames_auto().select {|n| /\.rb\z/ =~ n } 1199 | end 1200 | 1201 | # picked up many entries from cvs-1.11.1/src/ignore.c 1202 | reject_patterns = %w( 1203 | core RCSLOG tags TAGS .make.state 1204 | .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb 1205 | *~ *.old *.bak *.BAK *.orig *.rej _$* *$ 1206 | 1207 | *.org *.in .* 1208 | ) 1209 | mapping = { 1210 | '.' => '\.', 1211 | '$' => '\$', 1212 | '#' => '\#', 1213 | '*' => '.*' 1214 | } 1215 | REJECT_PATTERNS = Regexp.new('\A(?:' + 1216 | reject_patterns.map {|pat| 1217 | pat.gsub(/[\.\$\#\*]/) {|ch| mapping[ch] } 1218 | }.join('|') + 1219 | ')\z') 1220 | 1221 | def collect_filenames_auto 1222 | mapdir((existfiles() - hookfiles()).reject {|fname| 1223 | REJECT_PATTERNS =~ fname 1224 | }) 1225 | end 1226 | 1227 | def existfiles 1228 | all_files_in(curr_srcdir()) | all_files_in('.') 1229 | end 1230 | 1231 | def hookfiles 1232 | %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| 1233 | %w( config setup install clean ).map {|t| sprintf(fmt, t) } 1234 | }.flatten 1235 | end 1236 | 1237 | def mapdir(filelist) 1238 | filelist.map {|fname| 1239 | if File.exist?(fname) # objdir 1240 | fname 1241 | else # srcdir 1242 | File.join(curr_srcdir(), fname) 1243 | end 1244 | } 1245 | end 1246 | 1247 | def ruby_extentions(dir) 1248 | Dir.open(dir) {|d| 1249 | ents = d.select {|fname| /\.#{::Config::CONFIG['DLEXT']}\z/ =~ fname } 1250 | if ents.empty? 1251 | setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first" 1252 | end 1253 | return ents 1254 | } 1255 | end 1256 | 1257 | # 1258 | # TASK clean 1259 | # 1260 | 1261 | def exec_clean 1262 | exec_task_traverse 'clean' 1263 | rm_f ConfigTable.savefile 1264 | rm_f 'InstalledFiles' 1265 | end 1266 | 1267 | def clean_dir_bin(rel) 1268 | end 1269 | 1270 | def clean_dir_lib(rel) 1271 | end 1272 | 1273 | def clean_dir_ext(rel) 1274 | return unless extdir?(curr_srcdir()) 1275 | make 'clean' if File.file?('Makefile') 1276 | end 1277 | 1278 | def clean_dir_data(rel) 1279 | end 1280 | 1281 | # 1282 | # TASK distclean 1283 | # 1284 | 1285 | def exec_distclean 1286 | exec_task_traverse 'distclean' 1287 | rm_f ConfigTable.savefile 1288 | rm_f 'InstalledFiles' 1289 | end 1290 | 1291 | def distclean_dir_bin(rel) 1292 | end 1293 | 1294 | def distclean_dir_lib(rel) 1295 | end 1296 | 1297 | def distclean_dir_ext(rel) 1298 | return unless extdir?(curr_srcdir()) 1299 | make 'distclean' if File.file?('Makefile') 1300 | end 1301 | 1302 | # 1303 | # lib 1304 | # 1305 | 1306 | def exec_task_traverse(task) 1307 | run_hook "pre-#{task}" 1308 | FILETYPES.each do |type| 1309 | if config('without-ext') == 'yes' and type == 'ext' 1310 | $stderr.puts 'skipping ext/* by user option' if verbose? 1311 | next 1312 | end 1313 | traverse task, type, "#{task}_dir_#{type}" 1314 | end 1315 | run_hook "post-#{task}" 1316 | end 1317 | 1318 | def traverse(task, rel, mid) 1319 | dive_into(rel) { 1320 | run_hook "pre-#{task}" 1321 | __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') 1322 | all_dirs_in(curr_srcdir()).each do |d| 1323 | traverse task, "#{rel}/#{d}", mid 1324 | end 1325 | run_hook "post-#{task}" 1326 | } 1327 | end 1328 | 1329 | def dive_into(rel) 1330 | return unless File.dir?("#{@srcdir}/#{rel}") 1331 | 1332 | dir = File.basename(rel) 1333 | Dir.mkdir dir unless File.dir?(dir) 1334 | prevdir = Dir.pwd 1335 | Dir.chdir dir 1336 | $stderr.puts '---> ' + rel if verbose? 1337 | @currdir = rel 1338 | yield 1339 | Dir.chdir prevdir 1340 | $stderr.puts '<--- ' + rel if verbose? 1341 | @currdir = File.dirname(rel) 1342 | end 1343 | 1344 | end 1345 | 1346 | 1347 | if $0 == __FILE__ 1348 | begin 1349 | if multipackage_install? 1350 | ToplevelInstallerMulti.invoke 1351 | else 1352 | ToplevelInstaller.invoke 1353 | end 1354 | rescue SetupError 1355 | raise if $DEBUG 1356 | $stderr.puts $!.message 1357 | $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." 1358 | exit 1 1359 | end 1360 | end 1361 | -------------------------------------------------------------------------------- /test/tc_all.rb: -------------------------------------------------------------------------------- 1 | # Runs all tests. 2 | 3 | $LOAD_PATH.unshift "lib" 4 | 5 | test_dir = File.split(Dir.pwd).last == "test" ? "." : "test" 6 | 7 | tests = Dir["#{test_dir}/**/*.rb"].reject { |file| file == "tc_all.rb" } 8 | tests.each { |test| require test } 9 | -------------------------------------------------------------------------------- /test/tc_inline.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.join(File.dirname(__FILE__), "../lib") 2 | $LOAD_PATH.unshift File.dirname(__FILE__) 3 | 4 | require "test-extract" 5 | require "evil" 6 | 7 | glob = File.join(File.dirname(__FILE__), "../lib/*") 8 | Dir[glob].each do |file| 9 | next unless File.file?(file) 10 | Extracter.process(file) 11 | end 12 | -------------------------------------------------------------------------------- /test/test-extract.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit/testcase' 2 | require 'test/unit/ui/console/testrunner' 3 | 4 | class Extracter 5 | def self.process(fn) 6 | new(File.read(fn)) 7 | end 8 | 9 | def initialize(content) 10 | comment_block_re = /((?:^\s*?(?:#.*?)?\n)+)/m 11 | component_re = /\s*(?:class|def|module|alias)\s+:?([^\s()]+)?/ 12 | blocks = content.scan(/#{comment_block_re}#{component_re}/) 13 | 14 | test_suite = Class.new(Test::Unit::TestCase) 15 | 16 | has_test = false 17 | blocks.each do |(comment, component)| 18 | code_in_doc_re = /^(\s*# +(?:.*?)$)/ 19 | tests = comment.scan(code_in_doc_re) 20 | body = tests.map do |test| 21 | test.map do |raw_line| 22 | line = raw_line.sub(/^\s*#\s{0,3}/, "") 23 | if md = /(.*?)#\s*(=>|~>|raises?)\s*(.*?)$/.match(line) 24 | new_line, type, result = *md.captures 25 | new_line.strip! 26 | case type 27 | when "=>" 28 | ["begin", 29 | " assert_equal(#{result}, #{new_line})", 30 | "rescue => err", 31 | " assert_equal(#{result.inspect}, (#{new_line}).inspect)", 32 | "end"].join("\n") 33 | when "~>", "raise", "raises" 34 | "assert_raises(Object.const_get(#{result.inspect})) { #{new_line} }" 35 | end 36 | else 37 | line 38 | end 39 | end.join("\n") 40 | end.join("\n") 41 | 42 | unless component 43 | if $DEBUG 44 | STDERR.puts "Can't get name for this code:", 45 | body.gsub(/(?:\r?\n){2}/, "\n") 46 | end 47 | component = test.hash.abs 48 | end 49 | 50 | if body and not body.empty? 51 | has_test = true 52 | begin 53 | test_suite.class_eval %{ 54 | def #{test_method_name(component)} 55 | #{body} 56 | end 57 | } 58 | rescue Object => err 59 | STDERR.puts "Error in #{test_method_name(component)}: ", 60 | err, "", "Code is: ", body, "" if $DEBUG 61 | end 62 | end 63 | end 64 | 65 | if not has_test 66 | test_suite.class_eval do 67 | def test_main; end 68 | end 69 | end 70 | 71 | Test::Unit::UI::Console::TestRunner.new(test_suite).start 72 | end 73 | 74 | def test_method_name(component) 75 | result = "test_#{component}" 76 | { 77 | "+" => "op_plus", 78 | "-" => "op_minus", 79 | "+@" => "op_plus_self", 80 | "-@" => "op_minus_self", 81 | "*" => "op_mul", 82 | "**" => "op_pow", 83 | "/" => "op_div", 84 | "%" => "op_mod", 85 | "<<" => "op_lshift", 86 | ">>" => "op_rshift", 87 | "~" => "op_tilde", 88 | "<=>" => "op_cmp", 89 | "<" => "op_lt", 90 | ">" => "op_gt", 91 | "==" => "op_equal", 92 | "<=" => "op_lt_eq", 93 | ">=" => "op_gt_eq", 94 | "===" => "op_case_eq", 95 | "=~" => "op_apply", 96 | "|" => "op_or", 97 | "&" => "op_and", 98 | "^" => "op_xor", 99 | "[]" => "op_fetch", 100 | "[]=" => "op_store" 101 | }.each do |(what, by)| 102 | result.gsub!(what, by) 103 | end 104 | return result 105 | end 106 | end 107 | 108 | if __FILE__ == $0 109 | file = ARGV.shift 110 | load(file) 111 | Extracter.process(file) 112 | end 113 | --------------------------------------------------------------------------------