revent 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. data/INSTALL +10 -0
  2. data/README +42 -0
  3. data/lib/revent.rb +166 -0
  4. data/sample/client.rb +34 -0
  5. data/sample/server.rb +33 -0
  6. data/setup.rb +1306 -0
  7. metadata +61 -0
data/INSTALL ADDED
@@ -0,0 +1,10 @@
1
+ From the root of the package, run:
2
+
3
+ ruby setup.rb config
4
+ ruby setup.rb setup
5
+ ruby setup.rb install
6
+
7
+ Further install options are available using:
8
+
9
+ ruby setup.rb --help
10
+
data/README ADDED
@@ -0,0 +1,42 @@
1
+ = Revent - RPC based on EventMachine
2
+
3
+ Tired of DRuby? This library allows mutual RPC between server and clients:
4
+ clients can call methods on server and server can call methods on clients.
5
+
6
+ EventMachine is used for network communication and Marshal is used for
7
+ serializing/deserializing.
8
+
9
+
10
+ == Usage
11
+
12
+ See http://revent.rubyforge.org
13
+
14
+
15
+ == Installation
16
+
17
+ See INSTALL or
18
+ gem install revent
19
+
20
+
21
+ == Feedback
22
+
23
+ Feedback of any kind is welcome; please post to the forum at
24
+ http://rubyforge.org/projects/revent
25
+
26
+
27
+ == Copyright
28
+
29
+ Copyright (c) 2008 Ngoc DAO Thanh. All Rights Reserved.
30
+
31
+
32
+ == License
33
+
34
+ Revent is free software distributed under the Ruby license. See the
35
+ COPYING file in the standard Ruby distribution for details.
36
+
37
+
38
+ == Warranty
39
+
40
+ This software is provided "as is" and without any express or implied
41
+ warranties, including, without limitation, the implied warranties of
42
+ merchantibility and fitness for a particular purpose.
data/lib/revent.rb ADDED
@@ -0,0 +1,166 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+ require 'stringio'
4
+
5
+ # Included by both ReventServer and ReventClient.
6
+ module ReventCon
7
+ attr_writer :me
8
+ attr_accessor :property # Something that you want to associate with this connection
9
+ attr_writer :cons # Only used for ReventServer
10
+
11
+ def post_init
12
+ @connected = true
13
+ @data = ''
14
+ end
15
+
16
+ def receive_data(data)
17
+ @data << data
18
+
19
+ while true
20
+ io = StringIO.new(@data)
21
+ o = Marshal.load(io)
22
+ @data.slice!(0, io.pos)
23
+ process(o)
24
+ end
25
+ rescue
26
+ end
27
+
28
+ def process(o)
29
+ case o[:type]
30
+ when :call
31
+ result = @cons.nil? ? @me.on_call(o[:cmd], o[:arg]) : @me.on_call(self, o[:cmd], o[:arg])
32
+ o = {:type => :result, :cmd => o[:cmd], :result => result}
33
+ send_data(Marshal.dump(o))
34
+ when :result
35
+ @cons.nil? ? @me.on_result(o[:cmd], o[:result]) : @me.on_result(self, o[:cmd], o[:result])
36
+ when :error
37
+ @cons.nil? ? @me.on_error(o[:cmd], o[:error]) : @me.on_error(self, o[:cmd], o[:error])
38
+ end
39
+ rescue => e
40
+ o = {:type => :error, :cmd => o[:cmd], :error => e}
41
+ send_data(Marshal.dump(o))
42
+ end
43
+
44
+ def unbind
45
+ @connected = false
46
+ if @cons.nil?
47
+ @me.on_close
48
+ else
49
+ @me.on_close(self)
50
+ @cons.delete(self)
51
+ end
52
+ end
53
+
54
+ # ----------------------------------------------------------------------------
55
+
56
+ def connected?
57
+ @connected
58
+ end
59
+
60
+ # IP of the container
61
+ def remote_ip
62
+ a = get_peername[2,6].unpack("nC4")
63
+ a.delete_at(0)
64
+ a.join('.')
65
+ end
66
+
67
+ def call(cmd, arg)
68
+ o = {:type => :call, :cmd => cmd, :arg => arg}
69
+ send_data(Marshal.dump(o))
70
+ end
71
+
72
+ def close
73
+ close_connection_after_writing
74
+ end
75
+ end
76
+
77
+ # To make your class a server, just include ReventServer in it.
78
+ module ReventServer
79
+ def start_server(host, port)
80
+ @revent_cons = []
81
+ EventMachine::start_server(host, port, ReventCon) do |con|
82
+ @revent_cons << con
83
+ con.me = self
84
+ con.cons = @revent_cons
85
+ on_connect(con)
86
+ end
87
+ end
88
+
89
+ # Utilities. You can call remote_ip, call, close directly on each "client", as
90
+ # if it is an instance of ReventClient.
91
+ # ----------------------------------------------------------------------------
92
+
93
+ def clients
94
+ @revent_cons
95
+ end
96
+
97
+ def on_connect(client)
98
+ end
99
+
100
+ # ----------------------------------------------------------------------------
101
+
102
+ def on_connect(client)
103
+ end
104
+
105
+ def on_close(client)
106
+ end
107
+
108
+ def on_call(client, cmd, arg)
109
+ end
110
+
111
+ def on_result(client, cmd, result)
112
+ end
113
+
114
+ def on_error(client, cmd, error)
115
+ end
116
+ end
117
+
118
+ # To make your class a client, just include ReventClient in it.
119
+ module ReventClient
120
+ def connect(host, port)
121
+ EventMachine::connect(host, port, ReventCon) do |con|
122
+ @revent_con = con
123
+ con.me = self
124
+ on_connect
125
+ end
126
+ end
127
+
128
+ # Utilities ------------------------------------------------------------------
129
+
130
+ def property
131
+ @revent_con.property
132
+ end
133
+
134
+ def property=(value)
135
+ @revent_con.property = value
136
+ end
137
+
138
+ def remote_ip
139
+ @revent_con.remote_ip
140
+ end
141
+
142
+ def call(cmd, arg)
143
+ @revent_con.call(cmd, arg)
144
+ end
145
+
146
+ def close
147
+ @revent_con.close_connection_after_writing
148
+ end
149
+
150
+ # ----------------------------------------------------------------------------
151
+
152
+ def on_connect
153
+ end
154
+
155
+ def on_close
156
+ end
157
+
158
+ def on_call(cmd, arg)
159
+ end
160
+
161
+ def on_result(cmd, result)
162
+ end
163
+
164
+ def on_error(cmd, error)
165
+ end
166
+ end
data/sample/client.rb ADDED
@@ -0,0 +1,34 @@
1
+ require 'revent'
2
+
3
+ class Client
4
+ include ReventClient
5
+
6
+ def initialize
7
+ connect('localhost', 1943)
8
+ end
9
+
10
+ N = 10**5
11
+
12
+ def on_connect
13
+ @now = Time.now
14
+ @x = 0
15
+ N.times do |i|
16
+ call('hello', i)
17
+ end
18
+ end
19
+
20
+ def on_result(cmd, arg)
21
+ @x += 1
22
+ #puts @x
23
+ if @x == N
24
+ dt = Time.now - @now
25
+ puts N
26
+ puts dt
27
+ puts N/dt
28
+ end
29
+ end
30
+ end
31
+
32
+ EventMachine::run do
33
+ Client.new
34
+ end
data/sample/server.rb ADDED
@@ -0,0 +1,33 @@
1
+ require 'revent'
2
+
3
+ class Server
4
+ include ReventServer
5
+
6
+ def initialize
7
+ start_server('localhost', 1943)
8
+ end
9
+
10
+ N = 10**5
11
+
12
+ def on_connect(con)
13
+ @x = 0
14
+ @now = Time.now
15
+ N.times do |i|
16
+ con.call('hello', i)
17
+ end
18
+ end
19
+
20
+ def on_result(con, cmd, arg)
21
+ @x += 1
22
+ if @x == N
23
+ dt = Time.now - @now
24
+ puts N
25
+ puts dt
26
+ puts N/dt
27
+ end
28
+ end
29
+ end
30
+
31
+ EventMachine::run do
32
+ Server.new
33
+ end
data/setup.rb ADDED
@@ -0,0 +1,1306 @@
1
+ #
2
+ # This file is automatically generated. DO NOT MODIFY!
3
+ #
4
+ # setup.rb
5
+ #
6
+ # Copyright (c) 2000-2003 Minero Aoki <aamine@loveruby.net>
7
+ #
8
+ # This program is free software.
9
+ # You can distribute/modify this program under the terms of
10
+ # the GNU Lesser General Public License version 2.
11
+ #
12
+
13
+ def multipackage_install?
14
+ FileTest.directory?(File.dirname($0) + '/packages')
15
+ end
16
+
17
+ #
18
+ # compat.rb
19
+ #
20
+
21
+ unless Enumerable.method_defined?(:map)
22
+ module Enumerable
23
+ alias map collect
24
+ end
25
+ end
26
+
27
+ unless Enumerable.method_defined?(:select)
28
+ module Enumerable
29
+ alias select find_all
30
+ end
31
+ end
32
+
33
+ unless Enumerable.method_defined?(:reject)
34
+ module Enumerable
35
+ def reject
36
+ result = []
37
+ each do |i|
38
+ result.push i unless yield(i)
39
+ end
40
+ result
41
+ end
42
+ end
43
+ end
44
+
45
+ unless Enumerable.method_defined?(:inject)
46
+ module Enumerable
47
+ def inject(result)
48
+ each do |i|
49
+ result = yield(result, i)
50
+ end
51
+ result
52
+ end
53
+ end
54
+ end
55
+
56
+ unless Enumerable.method_defined?(:any?)
57
+ module Enumerable
58
+ def any?
59
+ each do |i|
60
+ return true if yield(i)
61
+ end
62
+ false
63
+ end
64
+ end
65
+ end
66
+
67
+ unless File.respond_to?(:read)
68
+ def File.read(fname)
69
+ File.open(fname) {|f|
70
+ return f.read
71
+ }
72
+ end
73
+ end
74
+ #
75
+ # fileop.rb
76
+ #
77
+
78
+ module FileOperations
79
+
80
+ def mkdir_p(dirname, prefix = nil)
81
+ dirname = prefix + dirname if prefix
82
+ $stderr.puts "mkdir -p #{dirname}" if verbose?
83
+ return if no_harm?
84
+
85
+ # does not check '/'... it's too abnormal case
86
+ dirs = dirname.split(%r<(?=/)>)
87
+ if /\A[a-z]:\z/i =~ dirs[0]
88
+ disk = dirs.shift
89
+ dirs[0] = disk + dirs[0]
90
+ end
91
+ dirs.each_index do |idx|
92
+ path = dirs[0..idx].join('')
93
+ Dir.mkdir path unless File.dir?(path)
94
+ end
95
+ end
96
+
97
+ def rm_f(fname)
98
+ $stderr.puts "rm -f #{fname}" if verbose?
99
+ return if no_harm?
100
+
101
+ if File.exist?(fname) or File.symlink?(fname)
102
+ File.chmod 0777, fname
103
+ File.unlink fname
104
+ end
105
+ end
106
+
107
+ def rm_rf(dn)
108
+ $stderr.puts "rm -rf #{dn}" if verbose?
109
+ return if no_harm?
110
+
111
+ Dir.chdir dn
112
+ Dir.foreach('.') do |fn|
113
+ next if fn == '.'
114
+ next if fn == '..'
115
+ if File.dir?(fn)
116
+ verbose_off {
117
+ rm_rf fn
118
+ }
119
+ else
120
+ verbose_off {
121
+ rm_f fn
122
+ }
123
+ end
124
+ end
125
+ Dir.chdir '..'
126
+ Dir.rmdir dn
127
+ end
128
+
129
+ def move_file(src, dest)
130
+ File.unlink dest if File.exist?(dest)
131
+ begin
132
+ File.rename src, dest
133
+ rescue
134
+ File.open(dest, 'wb') {|f| f.write File.read(src) }
135
+ File.chmod File.stat(src).mode, dest
136
+ File.unlink src
137
+ end
138
+ end
139
+
140
+ def install(from, dest, mode, prefix = nil)
141
+ $stderr.puts "install #{from} #{dest}" if verbose?
142
+ return if no_harm?
143
+
144
+ realdest = prefix + dest if prefix
145
+ realdest += '/' + File.basename(from) if File.dir?(realdest)
146
+ str = File.read(from)
147
+ if diff?(str, realdest)
148
+ verbose_off {
149
+ rm_f realdest if File.exist?(realdest)
150
+ }
151
+ File.open(realdest, 'wb') {|f| f.write str }
152
+ File.chmod mode, realdest
153
+
154
+ File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| f.puts dest }
155
+ end
156
+ end
157
+
158
+ def diff?(orig, targ)
159
+ return true unless File.exist?(targ)
160
+ orig != File.read(targ)
161
+ end
162
+
163
+ def command(str)
164
+ $stderr.puts str if verbose?
165
+ system str or raise RuntimeError, "'system #{str}' failed"
166
+ end
167
+
168
+ def ruby(str)
169
+ command config('ruby-prog') + ' ' + str
170
+ end
171
+
172
+ def make(task = '')
173
+ command config('make-prog') + ' ' + task
174
+ end
175
+
176
+ def extdir?(dir)
177
+ File.exist?(dir + '/MANIFEST')
178
+ end
179
+
180
+ def all_files_in(dirname)
181
+ Dir.open(dirname) {|d|
182
+ return d.select {|ent| File.file?("#{dirname}/#{ent}") }
183
+ }
184
+ end
185
+
186
+ REJECT_DIRS = %w(
187
+ CVS SCCS RCS CVS.adm
188
+ )
189
+
190
+ def all_dirs_in(dirname)
191
+ Dir.open(dirname) {|d|
192
+ return d.select {|n| File.dir?("#{dirname}/#{n}") } - %w(. ..) - REJECT_DIRS
193
+ }
194
+ end
195
+
196
+ end
197
+
198
+
199
+ class File
200
+
201
+ def File.dir?(path)
202
+ # for corrupted windows stat()
203
+ File.directory?((path[-1,1] == '/') ? path : path + '/')
204
+ end
205
+
206
+ end
207
+ #
208
+ # config.rb
209
+ #
210
+
211
+ if idx = ARGV.index(/\A--rbconfig=/)
212
+ require ARGV.delete_at(idx).split(/=/, 2)[1]
213
+ else
214
+ require 'rbconfig'
215
+ end
216
+
217
+ class ConfigTable
218
+
219
+ c = ::Config::CONFIG
220
+
221
+ rubypath = c['bindir'] + '/' + c['ruby_install_name']
222
+
223
+ major = c['MAJOR'].to_i
224
+ minor = c['MINOR'].to_i
225
+ teeny = c['TEENY'].to_i
226
+ version = "#{major}.#{minor}"
227
+
228
+ # ruby ver. >= 1.4.4?
229
+ newpath_p = ((major >= 2) or
230
+ ((major == 1) and
231
+ ((minor >= 5) or
232
+ ((minor == 4) and (teeny >= 4)))))
233
+
234
+ subprefix = lambda {|path|
235
+ path.sub(/\A#{Regexp.quote(c['prefix'])}/o, '$prefix')
236
+ }
237
+
238
+ if c['rubylibdir']
239
+ # V < 1.6.3
240
+ stdruby = subprefix.call(c['rubylibdir'])
241
+ siteruby = subprefix.call(c['sitedir'])
242
+ versite = subprefix.call(c['sitelibdir'])
243
+ sodir = subprefix.call(c['sitearchdir'])
244
+ elsif newpath_p
245
+ # 1.4.4 <= V <= 1.6.3
246
+ stdruby = "$prefix/lib/ruby/#{version}"
247
+ siteruby = subprefix.call(c['sitedir'])
248
+ versite = siteruby + '/' + version
249
+ sodir = "$site-ruby/#{c['arch']}"
250
+ else
251
+ # V < 1.4.4
252
+ stdruby = "$prefix/lib/ruby/#{version}"
253
+ siteruby = "$prefix/lib/ruby/#{version}/site_ruby"
254
+ versite = siteruby
255
+ sodir = "$site-ruby/#{c['arch']}"
256
+ end
257
+
258
+ common_descripters = [
259
+ [ 'prefix', [ c['prefix'],
260
+ 'path',
261
+ 'path prefix of target environment' ] ],
262
+ [ 'std-ruby', [ stdruby,
263
+ 'path',
264
+ 'the directory for standard ruby libraries' ] ],
265
+ [ 'site-ruby-common', [ siteruby,
266
+ 'path',
267
+ 'the directory for version-independent non-standard ruby libraries' ] ],
268
+ [ 'site-ruby', [ versite,
269
+ 'path',
270
+ 'the directory for non-standard ruby libraries' ] ],
271
+ [ 'bin-dir', [ '$prefix/bin',
272
+ 'path',
273
+ 'the directory for commands' ] ],
274
+ [ 'rb-dir', [ '$site-ruby',
275
+ 'path',
276
+ 'the directory for ruby scripts' ] ],
277
+ [ 'so-dir', [ sodir,
278
+ 'path',
279
+ 'the directory for ruby extentions' ] ],
280
+ [ 'data-dir', [ '$prefix/share',
281
+ 'path',
282
+ 'the directory for shared data' ] ],
283
+ [ 'ruby-path', [ rubypath,
284
+ 'path',
285
+ 'path to set to #! line' ] ],
286
+ [ 'ruby-prog', [ rubypath,
287
+ 'name',
288
+ 'the ruby program using for installation' ] ],
289
+ [ 'make-prog', [ 'make',
290
+ 'name',
291
+ 'the make program to compile ruby extentions' ] ],
292
+ [ 'without-ext', [ 'no',
293
+ 'yes/no',
294
+ 'does not compile/install ruby extentions' ] ]
295
+ ]
296
+ multipackage_descripters = [
297
+ [ 'with', [ '',
298
+ 'name,name...',
299
+ 'package names that you want to install',
300
+ 'ALL' ] ],
301
+ [ 'without', [ '',
302
+ 'name,name...',
303
+ 'package names that you do not want to install',
304
+ 'NONE' ] ]
305
+ ]
306
+ if multipackage_install?
307
+ DESCRIPTER = common_descripters + multipackage_descripters
308
+ else
309
+ DESCRIPTER = common_descripters
310
+ end
311
+
312
+ SAVE_FILE = 'config.save'
313
+
314
+ def ConfigTable.each_name(&block)
315
+ keys().each(&block)
316
+ end
317
+
318
+ def ConfigTable.keys
319
+ DESCRIPTER.map {|name, *dummy| name }
320
+ end
321
+
322
+ def ConfigTable.each_definition(&block)
323
+ DESCRIPTER.each(&block)
324
+ end
325
+
326
+ def ConfigTable.get_entry(name)
327
+ name, ent = DESCRIPTER.assoc(name)
328
+ ent
329
+ end
330
+
331
+ def ConfigTable.get_entry!(name)
332
+ get_entry(name) or raise ArgumentError, "no such config: #{name}"
333
+ end
334
+
335
+ def ConfigTable.add_entry(name, vals)
336
+ ConfigTable::DESCRIPTER.push [name,vals]
337
+ end
338
+
339
+ def ConfigTable.remove_entry(name)
340
+ get_entry(name) or raise ArgumentError, "no such config: #{name}"
341
+ DESCRIPTER.delete_if {|n, arr| n == name }
342
+ end
343
+
344
+ def ConfigTable.config_key?(name)
345
+ get_entry(name) ? true : false
346
+ end
347
+
348
+ def ConfigTable.bool_config?(name)
349
+ ent = get_entry(name) or return false
350
+ ent[1] == 'yes/no'
351
+ end
352
+
353
+ def ConfigTable.value_config?(name)
354
+ ent = get_entry(name) or return false
355
+ ent[1] != 'yes/no'
356
+ end
357
+
358
+ def ConfigTable.path_config?(name)
359
+ ent = get_entry(name) or return false
360
+ ent[1] == 'path'
361
+ end
362
+
363
+
364
+ class << self
365
+ alias newobj new
366
+ end
367
+
368
+ def ConfigTable.new
369
+ c = newobj()
370
+ c.initialize_from_table
371
+ c
372
+ end
373
+
374
+ def ConfigTable.load
375
+ c = newobj()
376
+ c.initialize_from_file
377
+ c
378
+ end
379
+
380
+ def initialize_from_table
381
+ @table = {}
382
+ DESCRIPTER.each do |k, (default, vname, desc, default2)|
383
+ @table[k] = default
384
+ end
385
+ end
386
+
387
+ def initialize_from_file
388
+ raise InstallError, "#{File.basename $0} config first"\
389
+ unless File.file?(SAVE_FILE)
390
+ @table = {}
391
+ File.foreach(SAVE_FILE) do |line|
392
+ k, v = line.split(/=/, 2)
393
+ @table[k] = v.strip
394
+ end
395
+ end
396
+
397
+ def save
398
+ File.open(SAVE_FILE, 'w') {|f|
399
+ @table.each do |k, v|
400
+ f.printf "%s=%s\n", k, v if v
401
+ end
402
+ }
403
+ end
404
+
405
+ def []=(k, v)
406
+ raise InstallError, "unknown config option #{k}"\
407
+ unless ConfigTable.config_key?(k)
408
+ @table[k] = v
409
+ end
410
+
411
+ def [](key)
412
+ return nil unless @table[key]
413
+ @table[key].gsub(%r<\$([^/]+)>) { self[$1] }
414
+ end
415
+
416
+ def set_raw(key, val)
417
+ @table[key] = val
418
+ end
419
+
420
+ def get_raw(key)
421
+ @table[key]
422
+ end
423
+
424
+ end
425
+
426
+
427
+ module MetaConfigAPI
428
+
429
+ def eval_file_ifexist(fname)
430
+ instance_eval File.read(fname), fname, 1 if File.file?(fname)
431
+ end
432
+
433
+ def config_names
434
+ ConfigTable.keys
435
+ end
436
+
437
+ def config?(name)
438
+ ConfigTable.config_key?(name)
439
+ end
440
+
441
+ def bool_config?(name)
442
+ ConfigTable.bool_config?(name)
443
+ end
444
+
445
+ def value_config?(name)
446
+ ConfigTable.value_config?(name)
447
+ end
448
+
449
+ def path_config?(name)
450
+ ConfigTable.path_config?(name)
451
+ end
452
+
453
+ def add_config(name, argname, default, desc)
454
+ ConfigTable.add_entry name,[default,argname,desc]
455
+ end
456
+
457
+ def add_path_config(name, default, desc)
458
+ add_config name, 'path', default, desc
459
+ end
460
+
461
+ def add_bool_config(name, default, desc)
462
+ add_config name, 'yes/no', default ? 'yes' : 'no', desc
463
+ end
464
+
465
+ def set_config_default(name, default)
466
+ if bool_config?(name)
467
+ ConfigTable.get_entry!(name)[0] = (default ? 'yes' : 'no')
468
+ else
469
+ ConfigTable.get_entry!(name)[0] = default
470
+ end
471
+ end
472
+
473
+ def remove_config(name)
474
+ ent = ConfigTable.get_entry(name)
475
+ ConfigTable.remove_entry name
476
+ ent
477
+ end
478
+
479
+ end
480
+ #
481
+ # base.rb
482
+ #
483
+
484
+ require 'rbconfig'
485
+
486
+
487
+ class InstallError < StandardError; end
488
+
489
+
490
+ module HookUtils
491
+
492
+ def run_hook(name)
493
+ try_run_hook "#{curr_srcdir()}/#{name}" or
494
+ try_run_hook "#{curr_srcdir()}/#{name}.rb"
495
+ end
496
+
497
+ def try_run_hook(fname)
498
+ return false unless File.file?(fname)
499
+ begin
500
+ instance_eval File.read(fname), fname, 1
501
+ rescue
502
+ raise InstallError, "hook #{fname} failed:\n" + $!.message
503
+ end
504
+ true
505
+ end
506
+
507
+ end
508
+
509
+
510
+ module HookScriptAPI
511
+
512
+ def get_config(key)
513
+ @config[key]
514
+ end
515
+
516
+ alias config get_config
517
+
518
+ def set_config(key, val)
519
+ @config[key] = val
520
+ end
521
+
522
+ #
523
+ # srcdir/objdir (works only in the package directory)
524
+ #
525
+
526
+ #abstract srcdir_root
527
+ #abstract objdir_root
528
+ #abstract relpath
529
+
530
+ def curr_srcdir
531
+ "#{srcdir_root()}/#{relpath()}"
532
+ end
533
+
534
+ def curr_objdir
535
+ "#{objdir_root()}/#{relpath()}"
536
+ end
537
+
538
+ def srcfile(path)
539
+ "#{curr_srcdir()}/#{path}"
540
+ end
541
+
542
+ def srcexist?(path)
543
+ File.exist?(srcfile(path))
544
+ end
545
+
546
+ def srcdirectory?(path)
547
+ File.dir?(srcfile(path))
548
+ end
549
+
550
+ def srcfile?(path)
551
+ File.file? srcfile(path)
552
+ end
553
+
554
+ def srcentries(path = '.')
555
+ Dir.open("#{curr_srcdir()}/#{path}") {|d|
556
+ return d.to_a - %w(. ..)
557
+ }
558
+ end
559
+
560
+ def srcfiles(path = '.')
561
+ srcentries(path).select {|fname|
562
+ File.file?(File.join(curr_srcdir(), path, fname))
563
+ }
564
+ end
565
+
566
+ def srcdirectories(path = '.')
567
+ srcentries(path).select {|fname|
568
+ File.dir?(File.join(curr_srcdir(), path, fname))
569
+ }
570
+ end
571
+
572
+ end
573
+
574
+
575
+ class Installer
576
+
577
+ FILETYPES = %w( bin lib ext data )
578
+
579
+ include HookScriptAPI
580
+ include HookUtils
581
+ include FileOperations
582
+
583
+ def initialize(config, opt, srcroot, objroot)
584
+ @config = config
585
+ @options = opt
586
+ @srcdir = File.expand_path(srcroot)
587
+ @objdir = File.expand_path(objroot)
588
+ @currdir = '.'
589
+ end
590
+
591
+ def inspect
592
+ "#<#{self.class} #{File.basename(@srcdir)}>"
593
+ end
594
+
595
+ #
596
+ # Hook Script API bases
597
+ #
598
+
599
+ def srcdir_root
600
+ @srcdir
601
+ end
602
+
603
+ def objdir_root
604
+ @objdir
605
+ end
606
+
607
+ def relpath
608
+ @currdir
609
+ end
610
+
611
+ #
612
+ # configs/options
613
+ #
614
+
615
+ def no_harm?
616
+ @options['no-harm']
617
+ end
618
+
619
+ def verbose?
620
+ @options['verbose']
621
+ end
622
+
623
+ def verbose_off
624
+ begin
625
+ save, @options['verbose'] = @options['verbose'], false
626
+ yield
627
+ ensure
628
+ @options['verbose'] = save
629
+ end
630
+ end
631
+
632
+ #
633
+ # TASK config
634
+ #
635
+
636
+ def exec_config
637
+ exec_task_traverse 'config'
638
+ end
639
+
640
+ def config_dir_bin(rel)
641
+ end
642
+
643
+ def config_dir_lib(rel)
644
+ end
645
+
646
+ def config_dir_ext(rel)
647
+ extconf if extdir?(curr_srcdir())
648
+ end
649
+
650
+ def extconf
651
+ opt = @options['config-opt'].join(' ')
652
+ command "#{config('ruby-prog')} #{curr_srcdir()}/extconf.rb #{opt}"
653
+ end
654
+
655
+ def config_dir_data(rel)
656
+ end
657
+
658
+ #
659
+ # TASK setup
660
+ #
661
+
662
+ def exec_setup
663
+ exec_task_traverse 'setup'
664
+ end
665
+
666
+ def setup_dir_bin(rel)
667
+ all_files_in(curr_srcdir()).each do |fname|
668
+ adjust_shebang "#{curr_srcdir()}/#{fname}"
669
+ end
670
+ end
671
+
672
+ # modify: #!/usr/bin/ruby
673
+ # modify: #! /usr/bin/ruby
674
+ # modify: #!ruby
675
+ # not modify: #!/usr/bin/env ruby
676
+ SHEBANG_RE = /\A\#!\s*\S*ruby\S*/
677
+
678
+ def adjust_shebang(path)
679
+ return if no_harm?
680
+
681
+ tmpfile = File.basename(path) + '.tmp'
682
+ begin
683
+ File.open(path) {|r|
684
+ File.open(tmpfile, 'w') {|w|
685
+ first = r.gets
686
+ return unless SHEBANG_RE =~ first
687
+
688
+ $stderr.puts "adjusting shebang: #{File.basename path}" if verbose?
689
+ w.print first.sub(SHEBANG_RE, '#!' + config('ruby-path'))
690
+ w.write r.read
691
+ }
692
+ }
693
+ move_file tmpfile, File.basename(path)
694
+ ensure
695
+ File.unlink tmpfile if File.exist?(tmpfile)
696
+ end
697
+ end
698
+
699
+ def setup_dir_lib(rel)
700
+ end
701
+
702
+ def setup_dir_ext(rel)
703
+ make if extdir?(curr_srcdir())
704
+ end
705
+
706
+ def setup_dir_data(rel)
707
+ end
708
+
709
+ #
710
+ # TASK install
711
+ #
712
+
713
+ def exec_install
714
+ exec_task_traverse 'install'
715
+ end
716
+
717
+ def install_dir_bin(rel)
718
+ install_files collect_filenames_auto(), config('bin-dir') + '/' + rel, 0755
719
+ end
720
+
721
+ def install_dir_lib(rel)
722
+ install_files ruby_scripts(), config('rb-dir') + '/' + rel, 0644
723
+ end
724
+
725
+ def install_dir_ext(rel)
726
+ return unless extdir?(curr_srcdir())
727
+ install_files ruby_extentions('.'),
728
+ config('so-dir') + '/' + File.dirname(rel),
729
+ 0555
730
+ end
731
+
732
+ def install_dir_data(rel)
733
+ install_files collect_filenames_auto(), config('data-dir') + '/' + rel, 0644
734
+ end
735
+
736
+ def install_files(list, dest, mode)
737
+ mkdir_p dest, @options['install-prefix']
738
+ list.each do |fname|
739
+ install fname, dest, mode, @options['install-prefix']
740
+ end
741
+ end
742
+
743
+ def ruby_scripts
744
+ collect_filenames_auto().select {|n| /\.rb\z/ =~ n }
745
+ end
746
+
747
+ # picked up many entries from cvs-1.11.1/src/ignore.c
748
+ reject_patterns = %w(
749
+ core RCSLOG tags TAGS .make.state
750
+ .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
751
+ *~ *.old *.bak *.BAK *.orig *.rej _$* *$
752
+
753
+ *.org *.in .*
754
+ )
755
+ mapping = {
756
+ '.' => '\.',
757
+ '$' => '\$',
758
+ '#' => '\#',
759
+ '*' => '.*'
760
+ }
761
+ REJECT_PATTERNS = Regexp.new('\A(?:' +
762
+ reject_patterns.map {|pat|
763
+ pat.gsub(/[\.\$\#\*]/) {|ch| mapping[ch] }
764
+ }.join('|') +
765
+ ')\z')
766
+
767
+ def collect_filenames_auto
768
+ mapdir((existfiles() - hookfiles()).reject {|fname|
769
+ REJECT_PATTERNS =~ fname
770
+ })
771
+ end
772
+
773
+ def existfiles
774
+ all_files_in(curr_srcdir()) | all_files_in('.')
775
+ end
776
+
777
+ def hookfiles
778
+ %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
779
+ %w( config setup install clean ).map {|t| sprintf(fmt, t) }
780
+ }.flatten
781
+ end
782
+
783
+ def mapdir(filelist)
784
+ filelist.map {|fname|
785
+ if File.exist?(fname) # objdir
786
+ fname
787
+ else # srcdir
788
+ File.join(curr_srcdir(), fname)
789
+ end
790
+ }
791
+ end
792
+
793
+ def ruby_extentions(dir)
794
+ _ruby_extentions(dir) or
795
+ raise InstallError, "no ruby extention exists: 'ruby #{$0} setup' first"
796
+ end
797
+
798
+ DLEXT = /\.#{ ::Config::CONFIG['DLEXT'] }\z/
799
+
800
+ def _ruby_extentions(dir)
801
+ Dir.open(dir) {|d|
802
+ return d.select {|fname| DLEXT =~ fname }
803
+ }
804
+ end
805
+
806
+ #
807
+ # TASK clean
808
+ #
809
+
810
+ def exec_clean
811
+ exec_task_traverse 'clean'
812
+ rm_f 'config.save'
813
+ rm_f 'InstalledFiles'
814
+ end
815
+
816
+ def clean_dir_bin(rel)
817
+ end
818
+
819
+ def clean_dir_lib(rel)
820
+ end
821
+
822
+ def clean_dir_ext(rel)
823
+ return unless extdir?(curr_srcdir())
824
+ make 'clean' if File.file?('Makefile')
825
+ end
826
+
827
+ def clean_dir_data(rel)
828
+ end
829
+
830
+ #
831
+ # TASK distclean
832
+ #
833
+
834
+ def exec_distclean
835
+ exec_task_traverse 'distclean'
836
+ rm_f 'config.save'
837
+ rm_f 'InstalledFiles'
838
+ end
839
+
840
+ def distclean_dir_bin(rel)
841
+ end
842
+
843
+ def distclean_dir_lib(rel)
844
+ end
845
+
846
+ def distclean_dir_ext(rel)
847
+ return unless extdir?(curr_srcdir())
848
+ make 'distclean' if File.file?('Makefile')
849
+ end
850
+
851
+ #
852
+ # lib
853
+ #
854
+
855
+ def exec_task_traverse(task)
856
+ run_hook "pre-#{task}"
857
+ FILETYPES.each do |type|
858
+ if config('without-ext') == 'yes' and type == 'ext'
859
+ $stderr.puts 'skipping ext/* by user option' if verbose?
860
+ next
861
+ end
862
+ traverse task, type, "#{task}_dir_#{type}"
863
+ end
864
+ run_hook "post-#{task}"
865
+ end
866
+
867
+ def traverse(task, rel, mid)
868
+ dive_into(rel) {
869
+ run_hook "pre-#{task}"
870
+ __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
871
+ all_dirs_in(curr_srcdir()).each do |d|
872
+ traverse task, "#{rel}/#{d}", mid
873
+ end
874
+ run_hook "post-#{task}"
875
+ }
876
+ end
877
+
878
+ def dive_into(rel)
879
+ return unless File.dir?("#{@srcdir}/#{rel}")
880
+
881
+ dir = File.basename(rel)
882
+ Dir.mkdir dir unless File.dir?(dir)
883
+ prevdir = Dir.pwd
884
+ Dir.chdir dir
885
+ $stderr.puts '---> ' + rel if verbose?
886
+ @currdir = rel
887
+ yield
888
+ Dir.chdir prevdir
889
+ $stderr.puts '<--- ' + rel if verbose?
890
+ @currdir = File.dirname(rel)
891
+ end
892
+
893
+ end
894
+ #
895
+ # toplevel.rb
896
+ #
897
+
898
+ class ToplevelInstaller
899
+
900
+ Version = '3.2.2'
901
+ Copyright = 'Copyright (c) 2000-2003 Minero Aoki'
902
+
903
+ TASKS = [
904
+ [ 'config', 'saves your configurations' ],
905
+ [ 'show', 'shows current configuration' ],
906
+ [ 'setup', 'compiles ruby extentions and others' ],
907
+ [ 'install', 'installs files' ],
908
+ [ 'clean', "does `make clean' for each extention" ],
909
+ [ 'distclean',"does `make distclean' for each extention" ]
910
+ ]
911
+
912
+ def ToplevelInstaller.invoke
913
+ instance().invoke
914
+ end
915
+
916
+ @singleton = nil
917
+
918
+ def ToplevelInstaller.instance
919
+ @singleton ||= new(File.dirname($0))
920
+ @singleton
921
+ end
922
+
923
+ include MetaConfigAPI
924
+
925
+ def initialize(ardir_root)
926
+ @config = nil
927
+ @options = { 'verbose' => true }
928
+ @ardir = File.expand_path(ardir_root)
929
+ end
930
+
931
+ def inspect
932
+ "#<#{self.class} #{__id__()}>"
933
+ end
934
+
935
+ def invoke
936
+ run_metaconfigs
937
+ task = parsearg_global()
938
+ @config = load_config(task)
939
+ __send__ "parsearg_#{task}"
940
+ init_installers
941
+ __send__ "exec_#{task}"
942
+ end
943
+
944
+ def run_metaconfigs
945
+ eval_file_ifexist "#{@ardir}/metaconfig"
946
+ end
947
+
948
+ def load_config(task)
949
+ case task
950
+ when 'config'
951
+ ConfigTable.new
952
+ when 'clean', 'distclean'
953
+ if File.exist?('config.save')
954
+ then ConfigTable.load
955
+ else ConfigTable.new
956
+ end
957
+ else
958
+ ConfigTable.load
959
+ end
960
+ end
961
+
962
+ def init_installers
963
+ @installer = Installer.new(@config, @options, @ardir, File.expand_path('.'))
964
+ end
965
+
966
+ #
967
+ # Hook Script API bases
968
+ #
969
+
970
+ def srcdir_root
971
+ @ardir
972
+ end
973
+
974
+ def objdir_root
975
+ '.'
976
+ end
977
+
978
+ def relpath
979
+ '.'
980
+ end
981
+
982
+ #
983
+ # Option Parsing
984
+ #
985
+
986
+ def parsearg_global
987
+ valid_task = /\A(?:#{TASKS.map {|task,desc| task }.join '|'})\z/
988
+
989
+ while arg = ARGV.shift
990
+ case arg
991
+ when /\A\w+\z/
992
+ raise InstallError, "invalid task: #{arg}" unless valid_task =~ arg
993
+ return arg
994
+
995
+ when '-q', '--quiet'
996
+ @options['verbose'] = false
997
+
998
+ when '--verbose'
999
+ @options['verbose'] = true
1000
+
1001
+ when '-h', '--help'
1002
+ print_usage $stdout
1003
+ exit 0
1004
+
1005
+ when '-v', '--version'
1006
+ puts "#{File.basename($0)} version #{Version}"
1007
+ exit 0
1008
+
1009
+ when '--copyright'
1010
+ puts Copyright
1011
+ exit 0
1012
+
1013
+ else
1014
+ raise InstallError, "unknown global option '#{arg}'"
1015
+ end
1016
+ end
1017
+
1018
+ raise InstallError, <<EOS
1019
+ No task or global option given.
1020
+ Typical installation procedure is:
1021
+ $ ruby #{File.basename($0)} config
1022
+ $ ruby #{File.basename($0)} setup
1023
+ # ruby #{File.basename($0)} install (may require root privilege)
1024
+ EOS
1025
+ end
1026
+
1027
+
1028
+ def parsearg_no_options
1029
+ raise InstallError, "#{task}: unknown options: #{ARGV.join ' '}"\
1030
+ unless ARGV.empty?
1031
+ end
1032
+
1033
+ alias parsearg_show parsearg_no_options
1034
+ alias parsearg_setup parsearg_no_options
1035
+ alias parsearg_clean parsearg_no_options
1036
+ alias parsearg_distclean parsearg_no_options
1037
+
1038
+ def parsearg_config
1039
+ re = /\A--(#{ConfigTable.keys.join '|'})(?:=(.*))?\z/
1040
+ @options['config-opt'] = []
1041
+
1042
+ while i = ARGV.shift
1043
+ if /\A--?\z/ =~ i
1044
+ @options['config-opt'] = ARGV.dup
1045
+ break
1046
+ end
1047
+ m = re.match(i) or raise InstallError, "config: unknown option #{i}"
1048
+ name, value = m.to_a[1,2]
1049
+ if value
1050
+ if ConfigTable.bool_config?(name)
1051
+ raise InstallError, "config: --#{name} allows only yes/no for argument"\
1052
+ unless /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i =~ value
1053
+ value = (/\Ay(es)?|\At(rue)/i =~ value) ? 'yes' : 'no'
1054
+ end
1055
+ else
1056
+ raise InstallError, "config: --#{name} requires argument"\
1057
+ unless ConfigTable.bool_config?(name)
1058
+ value = 'yes'
1059
+ end
1060
+ @config[name] = value
1061
+ end
1062
+ end
1063
+
1064
+ def parsearg_install
1065
+ @options['no-harm'] = false
1066
+ @options['install-prefix'] = ''
1067
+ while a = ARGV.shift
1068
+ case a
1069
+ when /\A--no-harm\z/
1070
+ @options['no-harm'] = true
1071
+ when /\A--prefix=(.*)\z/
1072
+ path = $1
1073
+ path = File.expand_path(path) unless path[0,1] == '/'
1074
+ @options['install-prefix'] = path
1075
+ else
1076
+ raise InstallError, "install: unknown option #{a}"
1077
+ end
1078
+ end
1079
+ end
1080
+
1081
+ def print_usage(out)
1082
+ out.puts 'Typical Installation Procedure:'
1083
+ out.puts " $ ruby #{File.basename $0} config"
1084
+ out.puts " $ ruby #{File.basename $0} setup"
1085
+ out.puts " # ruby #{File.basename $0} install (may require root privilege)"
1086
+ out.puts
1087
+ out.puts 'Detailed Usage:'
1088
+ out.puts " ruby #{File.basename $0} <global option>"
1089
+ out.puts " ruby #{File.basename $0} [<global options>] <task> [<task options>]"
1090
+
1091
+ fmt = " %-20s %s\n"
1092
+ out.puts
1093
+ out.puts 'Global options:'
1094
+ out.printf fmt, '-q,--quiet', 'suppress message outputs'
1095
+ out.printf fmt, ' --verbose', 'output messages verbosely'
1096
+ out.printf fmt, '-h,--help', 'print this message'
1097
+ out.printf fmt, '-v,--version', 'print version and quit'
1098
+ out.printf fmt, ' --copyright', 'print copyright and quit'
1099
+
1100
+ out.puts
1101
+ out.puts 'Tasks:'
1102
+ TASKS.each do |name, desc|
1103
+ out.printf " %-10s %s\n", name, desc
1104
+ end
1105
+
1106
+ out.puts
1107
+ out.puts 'Options for config:'
1108
+ ConfigTable.each_definition do |name, (default, arg, desc, default2)|
1109
+ out.printf " %-20s %s [%s]\n",
1110
+ '--'+ name + (ConfigTable.bool_config?(name) ? '' : '='+arg),
1111
+ desc,
1112
+ default2 || default
1113
+ end
1114
+ out.printf " %-20s %s [%s]\n",
1115
+ '--rbconfig=path', 'your rbconfig.rb to load', "running ruby's"
1116
+
1117
+ out.puts
1118
+ out.puts 'Options for install:'
1119
+ out.printf " %-20s %s [%s]\n",
1120
+ '--no-harm', 'only display what to do if given', 'off'
1121
+ out.printf " %-20s %s [%s]\n",
1122
+ '--prefix', 'install path prefix', '$prefix'
1123
+
1124
+ out.puts
1125
+ end
1126
+
1127
+ #
1128
+ # Task Handlers
1129
+ #
1130
+
1131
+ def exec_config
1132
+ @installer.exec_config
1133
+ @config.save # must be final
1134
+ end
1135
+
1136
+ def exec_setup
1137
+ @installer.exec_setup
1138
+ end
1139
+
1140
+ def exec_install
1141
+ @installer.exec_install
1142
+ end
1143
+
1144
+ def exec_show
1145
+ ConfigTable.each_name do |k|
1146
+ v = @config.get_raw(k)
1147
+ if not v or v.empty?
1148
+ v = '(not specified)'
1149
+ end
1150
+ printf "%-10s %s\n", k, v
1151
+ end
1152
+ end
1153
+
1154
+ def exec_clean
1155
+ @installer.exec_clean
1156
+ end
1157
+
1158
+ def exec_distclean
1159
+ @installer.exec_distclean
1160
+ end
1161
+
1162
+ end
1163
+
1164
+
1165
+ class ToplevelInstallerMulti < ToplevelInstaller
1166
+
1167
+ include HookUtils
1168
+ include HookScriptAPI
1169
+ include FileOperations
1170
+
1171
+ def initialize(ardir)
1172
+ super
1173
+ @packages = all_dirs_in("#{@ardir}/packages")
1174
+ raise 'no package exists' if @packages.empty?
1175
+ end
1176
+
1177
+ def run_metaconfigs
1178
+ eval_file_ifexist "#{@ardir}/metaconfig"
1179
+ @packages.each do |name|
1180
+ eval_file_ifexist "#{@ardir}/packages/#{name}/metaconfig"
1181
+ end
1182
+ end
1183
+
1184
+ def init_installers
1185
+ @installers = {}
1186
+ @packages.each do |pack|
1187
+ @installers[pack] = Installer.new(@config, @options,
1188
+ "#{@ardir}/packages/#{pack}",
1189
+ "packages/#{pack}")
1190
+ end
1191
+
1192
+ with = extract_selection(config('with'))
1193
+ without = extract_selection(config('without'))
1194
+ @selected = @installers.keys.select {|name|
1195
+ (with.empty? or with.include?(name)) \
1196
+ and not without.include?(name)
1197
+ }
1198
+ end
1199
+
1200
+ def extract_selection(list)
1201
+ a = list.split(/,/)
1202
+ a.each do |name|
1203
+ raise InstallError, "no such package: #{name}" \
1204
+ unless @installers.key?(name)
1205
+ end
1206
+ a
1207
+ end
1208
+
1209
+ def print_usage(f)
1210
+ super
1211
+ f.puts 'Inluded packages:'
1212
+ f.puts ' ' + @packages.sort.join(' ')
1213
+ f.puts
1214
+ end
1215
+
1216
+ #
1217
+ # multi-package metaconfig API
1218
+ #
1219
+
1220
+ attr_reader :packages
1221
+
1222
+ def declare_packages(list)
1223
+ raise 'package list is empty' if list.empty?
1224
+ list.each do |name|
1225
+ raise "directory packages/#{name} does not exist"\
1226
+ unless File.dir?("#{@ardir}/packages/#{name}")
1227
+ end
1228
+ @packages = list
1229
+ end
1230
+
1231
+ #
1232
+ # Task Handlers
1233
+ #
1234
+
1235
+ def exec_config
1236
+ run_hook 'pre-config'
1237
+ each_selected_installers {|inst| inst.exec_config }
1238
+ run_hook 'post-config'
1239
+ @config.save # must be final
1240
+ end
1241
+
1242
+ def exec_setup
1243
+ run_hook 'pre-setup'
1244
+ each_selected_installers {|inst| inst.exec_setup }
1245
+ run_hook 'post-setup'
1246
+ end
1247
+
1248
+ def exec_install
1249
+ run_hook 'pre-install'
1250
+ each_selected_installers {|inst| inst.exec_install }
1251
+ run_hook 'post-install'
1252
+ end
1253
+
1254
+ def exec_clean
1255
+ rm_f 'config.save'
1256
+ run_hook 'pre-clean'
1257
+ each_selected_installers {|inst| inst.exec_clean }
1258
+ run_hook 'post-clean'
1259
+ end
1260
+
1261
+ def exec_distclean
1262
+ rm_f 'config.save'
1263
+ run_hook 'pre-distclean'
1264
+ each_selected_installers {|inst| inst.exec_distclean }
1265
+ run_hook 'post-distclean'
1266
+ end
1267
+
1268
+ #
1269
+ # lib
1270
+ #
1271
+
1272
+ def each_selected_installers
1273
+ Dir.mkdir 'packages' unless File.dir?('packages')
1274
+ @selected.each do |pack|
1275
+ $stderr.puts "Processing the package `#{pack}' ..." if @options['verbose']
1276
+ Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
1277
+ Dir.chdir "packages/#{pack}"
1278
+ yield @installers[pack]
1279
+ Dir.chdir '../..'
1280
+ end
1281
+ end
1282
+
1283
+ def verbose?
1284
+ @options['verbose']
1285
+ end
1286
+
1287
+ def no_harm?
1288
+ @options['no-harm']
1289
+ end
1290
+
1291
+ end
1292
+
1293
+ if $0 == __FILE__
1294
+ begin
1295
+ if multipackage_install?
1296
+ ToplevelInstallerMulti.invoke
1297
+ else
1298
+ ToplevelInstaller.invoke
1299
+ end
1300
+ rescue
1301
+ raise if $DEBUG
1302
+ $stderr.puts $!.message
1303
+ $stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
1304
+ exit 1
1305
+ end
1306
+ end