matlab-ruby 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/matlab.rb ADDED
@@ -0,0 +1,22 @@
1
+ # Copyright (c) 2007 Jonathan Younger <jonathan.younger@lipomics.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'matlab/matrix'
22
+ require 'matlab/engine'
@@ -0,0 +1,200 @@
1
+ require 'matlab_api'
2
+ require 'matlab/matrix'
3
+
4
+ class String
5
+ # Converts the string to a MATLAB mxArray
6
+ def to_matlab
7
+ Matlab::Driver::Native::API.mxCreateString(self)
8
+ end
9
+ end
10
+
11
+ class TrueClass
12
+ # Converts true to a MATLAB logical scalar
13
+ def to_matlab
14
+ Matlab::Driver::Native::API.mxCreateLogicalScalar(self)
15
+ end
16
+ end
17
+
18
+ class FalseClass
19
+ # Converts false to a MATLAB logical scalar
20
+ def to_matlab
21
+ Matlab::Driver::Native::API.mxCreateLogicalScalar(self)
22
+ end
23
+ end
24
+
25
+ class NilClass
26
+ # Converts nil to MATLAB NaN
27
+ def to_matlab
28
+ Matlab::Driver::Native::API.mxGetNaN
29
+ end
30
+ end
31
+
32
+ class Float
33
+ # Converts the value to nil if it is a MATLAB NaN
34
+ def to_ruby
35
+ to_s == nil.to_matlab.to_s ? nil : self
36
+ end
37
+ end
38
+
39
+ class Numeric
40
+ # Converts the value to a MATLAB double
41
+ def to_matlab
42
+ Matlab::Driver::Native::API.mxCreateDoubleScalar(self)
43
+ end
44
+ end
45
+
46
+ class Array
47
+ # Flattens and converts the array to a 1 Dimensional Matlab::CellMatrix
48
+ def to_cell_matrix
49
+ values = flatten
50
+ cell_matrix = Matlab::CellMatrix.new(values.size, 1)
51
+
52
+ values.each_with_index do |value, index|
53
+ cell_matrix[index, 0] = value
54
+ end
55
+ cell_matrix
56
+ end
57
+ end
58
+
59
+ module Matlab
60
+ class Matrix
61
+ # Converts the matrix into a MATLAB numeric matrix
62
+ def to_matlab
63
+ matrix = Matlab::Driver::Native::API.mxCreateDoubleMatrix(m, n, Matlab::Driver::Native::API::MxREAL)
64
+ double_array = Matlab::Driver::Native::API::DoubleArray.new(m * n)
65
+
66
+ index = 0
67
+ n.times do |column_index|
68
+ m.times do |row_index|
69
+ double_array[index] = (@cells[row_index][column_index] ? @cells[row_index][column_index].to_f : nil.to_matlab)
70
+ index += 1
71
+ end
72
+ end
73
+
74
+ Matlab::Driver::Native::API.mxSetPr(matrix, double_array)
75
+ matrix
76
+ end
77
+
78
+ # Creates a Matlab::Matrix from a MATLAB numeric matrix
79
+ def self.from_matlab(matrix)
80
+ m = Matlab::Driver::Native::API.mxGetM(matrix)
81
+ n = Matlab::Driver::Native::API.mxGetN(matrix)
82
+
83
+ matlab_matrix = self.new(m, n)
84
+ double_array = Matlab::Driver::Native::API::DoubleArray.frompointer(Matlab::Driver::Native::API.mxGetPr(matrix))
85
+
86
+ index = 0
87
+ n.times do |column_index|
88
+ m.times do |row_index|
89
+ matlab_matrix[row_index, column_index] = (Matlab::Driver::Native::API.mxIsNaN(double_array[index]) ? nil : double_array[index])
90
+ index += 1
91
+ end
92
+ end
93
+
94
+ matlab_matrix
95
+ end
96
+ end
97
+
98
+ class CellMatrix
99
+ # Converts the matrix into a MATLAB cell matrix
100
+ def to_matlab
101
+ matrix = Matlab::Driver::Native::API.mxCreateCellMatrix(m, n)
102
+
103
+ index = 0
104
+ n.times do |column_index|
105
+ m.times do |row_index|
106
+ value = (@cells[row_index][column_index].nil? ? Matlab::Driver::Native::API.mxCreateDoubleScalar(nil.to_matlab) : @cells[row_index][column_index].to_matlab)
107
+ Matlab::Driver::Native::API.mxSetCell(matrix, index, value)
108
+ index += 1
109
+ end
110
+ end
111
+
112
+ matrix
113
+ end
114
+
115
+ # Creates a Matlab::CellMatrix from a MATLAB cell matrix
116
+ def self.from_matlab(matrix)
117
+ m = Matlab::Driver::Native::API.mxGetM(matrix)
118
+ n = Matlab::Driver::Native::API.mxGetN(matrix)
119
+
120
+ cell_matrix = self.new(m, n)
121
+
122
+ index = 0
123
+ n.times do |column_index|
124
+ m.times do |row_index|
125
+ value = Matlab::Driver::Native::API.mxGetCell(matrix, index).to_ruby
126
+ cell_matrix[row_index, column_index] = (value.to_s == nil.to_matlab.to_s ? nil : value)
127
+ index += 1
128
+ end
129
+ end
130
+
131
+ cell_matrix
132
+ end
133
+ end
134
+
135
+ class StructMatrix
136
+ # Converts the matrix into a MATLAB struct matrix
137
+ def to_matlab
138
+ matrix = Matlab::Driver::Native::API.mxCreateStructMatrix(m, n, 0, nil)
139
+ names.each { |name| Matlab::Driver::Native::API.mxAddField(matrix, name) }
140
+
141
+ index = 0
142
+ m.times do |row_index|
143
+ n.times do |column_index|
144
+ names.each do |name|
145
+ value = (@cells[row_index][column_index][name].nil? ? Matlab::Driver::Native::API.mxCreateDoubleScalar(nil.to_matlab) : @cells[row_index][column_index][name].to_matlab)
146
+ Matlab::Driver::Native::API.mxSetField(matrix, index, name, value)
147
+ end
148
+ index += 1
149
+ end
150
+ end
151
+
152
+ matrix
153
+ end
154
+
155
+ # Creates a Matlab::StructMatrix from a MATLAB struct matrix
156
+ def self.from_matlab(matrix)
157
+ m = Matlab::Driver::Native::API.mxGetM(matrix)
158
+ n = Matlab::Driver::Native::API.mxGetN(matrix)
159
+ names = (0...Matlab::Driver::Native::API.mxGetNumberOfFields(matrix)).collect { |i| Matlab::Driver::Native::API.mxGetFieldNameByNumber(matrix, i) }
160
+
161
+ struct_matrix = self.new(m, n, *names)
162
+
163
+ index = 0
164
+ m.times do |row_index|
165
+ n.times do |column_index|
166
+ names.each do |name|
167
+ value = Matlab::Driver::Native::API.mxGetField(matrix, index, name)
168
+ struct_matrix[row_index, column_index][name] = (Matlab::Driver::Native::API.mxIsEmpty(value) || value.to_ruby.to_s == nil.to_matlab.to_s ? nil : value.to_ruby)
169
+ end
170
+ index += 1
171
+ end
172
+ end
173
+
174
+ struct_matrix
175
+ end
176
+ end
177
+ end
178
+
179
+ class SWIG::TYPE_p_mxArray_tag
180
+ include Matlab::Driver::Native::API
181
+
182
+ def to_ruby
183
+ case
184
+ when mxIsStruct(self)
185
+ Matlab::StructMatrix.from_matlab(self)
186
+ when mxIsCell(self)
187
+ Matlab::CellMatrix.from_matlab(self)
188
+ when mxIsChar(self)
189
+ mxArrayToString(self)
190
+ when mxIsLogical(self)
191
+ mxIsLogicalScalarTrue(self)
192
+ when (mxGetM(self) > 1 || mxGetN(self) > 1)
193
+ Matlab::Matrix.from_matlab(self)
194
+ when mxIsDouble(self)
195
+ mxGetScalar(self)
196
+ when mxIsEmpty(self)
197
+ nil
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,26 @@
1
+ require 'matlab_api'
2
+ require 'matlab/driver/native/conversions'
3
+
4
+ module Matlab ; module Driver ; module Native
5
+ class Driver
6
+ def open(command)
7
+ API.engOpen(command)
8
+ end
9
+
10
+ def close(engine)
11
+ API.engClose(engine)
12
+ end
13
+
14
+ def eval_string(engine, string)
15
+ API.engEvalString(engine, string)
16
+ end
17
+
18
+ def get_variable(engine, name)
19
+ API.engGetVariable(engine, name).to_ruby
20
+ end
21
+
22
+ def put_variable(engine, name, value)
23
+ API.engPutVariable(engine, name, value.to_matlab)
24
+ end
25
+ end
26
+ end ; end ; end
@@ -0,0 +1,81 @@
1
+ module Matlab
2
+
3
+ # The Engine class encapsulates a single connection to a MATLAB instance.
4
+ # Usage:
5
+ #
6
+ # require 'matlab'
7
+ #
8
+ # engine = Matlab::Engine.new
9
+ #
10
+ # engine.x = 123.456
11
+ # engine.y = 789.101112
12
+ # engine.eval_string "z = x * y"
13
+ # p engine.z
14
+ #
15
+ # engine.close
16
+ #
17
+ # Values are sent to and from MATLAB by calling a method on the
18
+ # engine with the variable name of interest.
19
+ class Engine
20
+ # The low-level opaque engine handle that this object wraps.
21
+ attr_reader :handle
22
+
23
+ # A reference to the underlying MATLAB driver used by this engine.
24
+ attr_reader :driver
25
+
26
+ # Create a new Engine object that connects to MATLAB via the given command
27
+ def initialize(command = "matlab -nodesktop -nosplash", options = {})
28
+ load_driver(options[:driver])
29
+
30
+ @handle = @driver.open(command)
31
+ end
32
+
33
+ # Sends the given string to MATLAB to be evaluated
34
+ def eval_string(string)
35
+ @driver.eval_string(@handle, string)
36
+ end
37
+
38
+ # Puts or Gets a value to MATLAB via a given name
39
+ def method_missing(method_id, *args)
40
+ method_name = method_id.id2name
41
+
42
+ if method_name[(-1)..-1] == "="
43
+ @driver.put_variable(@handle, method_name.chop, args.first)
44
+ else
45
+ @driver.get_variable(@handle, method_name)
46
+ end
47
+ end
48
+
49
+ # Closes this engine
50
+ def close
51
+ @driver.close(@handle)
52
+ end
53
+
54
+ private
55
+ # Loads the corresponding driver, or if it is nil, attempts to locate a
56
+ # suitable driver.
57
+ def load_driver(driver)
58
+ case driver
59
+ when Class
60
+ # do nothing--use what was given
61
+ when Symbol, String
62
+ require "matlab/driver/#{driver.to_s.downcase}/driver"
63
+ driver = Matlab::Driver.const_get(driver)::Driver
64
+ else
65
+ [ "Native" ].each do |d|
66
+ begin
67
+ require "matlab/driver/#{d.downcase}/driver"
68
+ driver = Matlab::Driver.const_get(d)::Driver
69
+ break
70
+ rescue SyntaxError
71
+ raise
72
+ rescue ScriptError, Exception, NameError
73
+ end
74
+ end
75
+ raise "no driver for matlab found" unless driver
76
+ end
77
+
78
+ @driver = driver.new
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,84 @@
1
+ module Matlab
2
+
3
+ # The Matrix class is used for storing values that are sent to
4
+ # or received from MATLAB numeric matrices
5
+ # Usage:
6
+ #
7
+ # require 'matlab'
8
+ #
9
+ # matrix = Matlab::Matrix.new(20, 400)
10
+ # 20.times { |m| 400.times { |n| matrix[m, n] = rand } }
11
+ #
12
+ class Matrix
13
+ include Comparable
14
+
15
+ attr_reader :m
16
+ attr_reader :n
17
+ attr_reader :cells
18
+
19
+ # Creates a new Matrix with the given dimensions for row and column size
20
+ def initialize(m, n)
21
+ @m, @n = m, n
22
+ @cells = Array.new(m) { Array.new(n) }
23
+ end
24
+
25
+ # Gets the value at the given row and column position
26
+ def [](m, n)
27
+ @cells[m][n]
28
+ end
29
+
30
+ # Sets the given value at the row and column position
31
+ def []=(m, n, value)
32
+ @cells[m][n] = value
33
+ end
34
+
35
+ # Enables comparisons of matrices to each other
36
+ def ==(other)
37
+ @cells == other.cells
38
+ end
39
+ end
40
+
41
+ # The CellMatrix class is used for storing values that are sent to
42
+ # or received from MATLAB cell matrices.
43
+ # Usage:
44
+ #
45
+ # require 'matlab'
46
+ #
47
+ # cell_matrix = Matlab::CellMatrix.new(20, 400)
48
+ # 20.times { |m| 400.times { |n| cell_matrix[m, n] = rand.to_s } }
49
+ #
50
+ class CellMatrix < Matrix; end
51
+
52
+ # The StructMatrix class is used for storing hash values that are sent to
53
+ # or received from MATLAB struct matrices.
54
+ # Usage:
55
+ #
56
+ # require 'matlab'
57
+ #
58
+ # struct_matrix = Matlab::StructMatrix.new(17, 1, "name", "age", "married"
59
+ # 17.times do |m|
60
+ # struct_matrix[m, 0]["name"] = "Bob #{m}"
61
+ # struct_matrix[m, 0]["age"] = (rand * 100).to_i
62
+ # struct_matrix[m, 0]["married"] = (rand > 0.5)
63
+ # end
64
+ #
65
+ # p struct_matrix[16, 0]
66
+ #
67
+ class StructMatrix < Matrix
68
+ attr_reader :names
69
+
70
+ # Creates a new StructMatrix with the given dimensions for row and column size
71
+ # and the names of the attributes
72
+ def initialize(m, n, *names)
73
+ super(m, n)
74
+ @names = names
75
+
76
+ # Populate the matrix with a hash using the names provided
77
+ m.times do |row_index|
78
+ n.times do |column_index|
79
+ @cells[row_index][column_index] = names.inject({}) { |s,e| s.merge( { e.to_s => nil } ) }
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,14 @@
1
+ module Matlab
2
+
3
+ module Version
4
+
5
+ MAJOR = 1
6
+ MINOR = 0
7
+ TINY = 0
8
+
9
+ STRING = [ MAJOR, MINOR, TINY ].join( "." )
10
+ #:beta-tag:
11
+
12
+ end
13
+
14
+ end
data/setup.rb ADDED
@@ -0,0 +1,1333 @@
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
+ #
12
+ # For backward compatibility
13
+ #
14
+
15
+ unless Enumerable.method_defined?(:map)
16
+ module Enumerable
17
+ alias map collect
18
+ end
19
+ end
20
+
21
+ unless Enumerable.method_defined?(:detect)
22
+ module Enumerable
23
+ alias detect find
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
+ select {|i| not yield(i) }
37
+ end
38
+ end
39
+ end
40
+
41
+ unless Enumerable.method_defined?(:inject)
42
+ module Enumerable
43
+ def inject(result)
44
+ each do |i|
45
+ result = yield(result, i)
46
+ end
47
+ result
48
+ end
49
+ end
50
+ end
51
+
52
+ unless Enumerable.method_defined?(:any?)
53
+ module Enumerable
54
+ def any?
55
+ each do |i|
56
+ return true if yield(i)
57
+ end
58
+ false
59
+ end
60
+ end
61
+ end
62
+
63
+ unless File.respond_to?(:read)
64
+ def File.read(fname)
65
+ open(fname) {|f|
66
+ return f.read
67
+ }
68
+ end
69
+ end
70
+
71
+ #
72
+ # Application independent utilities
73
+ #
74
+
75
+ def File.binread(fname)
76
+ open(fname, 'rb') {|f|
77
+ return f.read
78
+ }
79
+ end
80
+
81
+ # for corrupted windows stat(2)
82
+ def File.dir?(path)
83
+ File.directory?((path[-1,1] == '/') ? path : path + '/')
84
+ end
85
+
86
+ #
87
+ # Config
88
+ #
89
+
90
+ if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg }
91
+ ARGV.delete(arg)
92
+ require arg.split(/=/, 2)[1]
93
+ $".push 'rbconfig.rb'
94
+ else
95
+ require 'rbconfig'
96
+ end
97
+
98
+ def multipackage_install?
99
+ FileTest.directory?(File.dirname($0) + '/packages')
100
+ end
101
+
102
+
103
+ class ConfigTable
104
+
105
+ c = ::Config::CONFIG
106
+
107
+ rubypath = c['bindir'] + '/' + c['ruby_install_name']
108
+
109
+ major = c['MAJOR'].to_i
110
+ minor = c['MINOR'].to_i
111
+ teeny = c['TEENY'].to_i
112
+ version = "#{major}.#{minor}"
113
+
114
+ # ruby ver. >= 1.4.4?
115
+ newpath_p = ((major >= 2) or
116
+ ((major == 1) and
117
+ ((minor >= 5) or
118
+ ((minor == 4) and (teeny >= 4)))))
119
+
120
+ subprefix = lambda {|path|
121
+ path.sub(/\A#{Regexp.quote(c['prefix'])}/o, '$prefix')
122
+ }
123
+
124
+ if c['rubylibdir']
125
+ # V < 1.6.3
126
+ stdruby = subprefix.call(c['rubylibdir'])
127
+ siteruby = subprefix.call(c['sitedir'])
128
+ versite = subprefix.call(c['sitelibdir'])
129
+ sodir = subprefix.call(c['sitearchdir'])
130
+ elsif newpath_p
131
+ # 1.4.4 <= V <= 1.6.3
132
+ stdruby = "$prefix/lib/ruby/#{version}"
133
+ siteruby = subprefix.call(c['sitedir'])
134
+ versite = siteruby + '/' + version
135
+ sodir = "$site-ruby/#{c['arch']}"
136
+ else
137
+ # V < 1.4.4
138
+ stdruby = "$prefix/lib/ruby/#{version}"
139
+ siteruby = "$prefix/lib/ruby/#{version}/site_ruby"
140
+ versite = siteruby
141
+ sodir = "$site-ruby/#{c['arch']}"
142
+ end
143
+
144
+ if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
145
+ makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
146
+ else
147
+ makeprog = 'make'
148
+ end
149
+
150
+ common_descripters = [
151
+ [ 'prefix', [ c['prefix'],
152
+ 'path',
153
+ 'path prefix of target environment' ] ],
154
+ [ 'std-ruby', [ stdruby,
155
+ 'path',
156
+ 'the directory for standard ruby libraries' ] ],
157
+ [ 'site-ruby-common', [ siteruby,
158
+ 'path',
159
+ 'the directory for version-independent non-standard ruby libraries' ] ],
160
+ [ 'site-ruby', [ versite,
161
+ 'path',
162
+ 'the directory for non-standard ruby libraries' ] ],
163
+ [ 'bin-dir', [ '$prefix/bin',
164
+ 'path',
165
+ 'the directory for commands' ] ],
166
+ [ 'rb-dir', [ '$site-ruby',
167
+ 'path',
168
+ 'the directory for ruby scripts' ] ],
169
+ [ 'so-dir', [ sodir,
170
+ 'path',
171
+ 'the directory for ruby extentions' ] ],
172
+ [ 'data-dir', [ '$prefix/share',
173
+ 'path',
174
+ 'the directory for shared data' ] ],
175
+ [ 'ruby-path', [ rubypath,
176
+ 'path',
177
+ 'path to set to #! line' ] ],
178
+ [ 'ruby-prog', [ rubypath,
179
+ 'name',
180
+ 'the ruby program using for installation' ] ],
181
+ [ 'make-prog', [ makeprog,
182
+ 'name',
183
+ 'the make program to compile ruby extentions' ] ],
184
+ [ 'without-ext', [ 'no',
185
+ 'yes/no',
186
+ 'does not compile/install ruby extentions' ] ]
187
+ ]
188
+ multipackage_descripters = [
189
+ [ 'with', [ '',
190
+ 'name,name...',
191
+ 'package names that you want to install',
192
+ 'ALL' ] ],
193
+ [ 'without', [ '',
194
+ 'name,name...',
195
+ 'package names that you do not want to install',
196
+ 'NONE' ] ]
197
+ ]
198
+ if multipackage_install?
199
+ DESCRIPTER = common_descripters + multipackage_descripters
200
+ else
201
+ DESCRIPTER = common_descripters
202
+ end
203
+
204
+ SAVE_FILE = '.config'
205
+
206
+ def ConfigTable.each_name(&block)
207
+ keys().each(&block)
208
+ end
209
+
210
+ def ConfigTable.keys
211
+ DESCRIPTER.map {|name, *dummy| name }
212
+ end
213
+
214
+ def ConfigTable.each_definition(&block)
215
+ DESCRIPTER.each(&block)
216
+ end
217
+
218
+ def ConfigTable.get_entry(name)
219
+ name, ent = DESCRIPTER.assoc(name)
220
+ ent
221
+ end
222
+
223
+ def ConfigTable.get_entry!(name)
224
+ get_entry(name) or raise ArgumentError, "no such config: #{name}"
225
+ end
226
+
227
+ def ConfigTable.add_entry(name, vals)
228
+ ConfigTable::DESCRIPTER.push [name,vals]
229
+ end
230
+
231
+ def ConfigTable.remove_entry(name)
232
+ get_entry(name) or raise ArgumentError, "no such config: #{name}"
233
+ DESCRIPTER.delete_if {|n, arr| n == name }
234
+ end
235
+
236
+ def ConfigTable.config_key?(name)
237
+ get_entry(name) ? true : false
238
+ end
239
+
240
+ def ConfigTable.bool_config?(name)
241
+ ent = get_entry(name) or return false
242
+ ent[1] == 'yes/no'
243
+ end
244
+
245
+ def ConfigTable.value_config?(name)
246
+ ent = get_entry(name) or return false
247
+ ent[1] != 'yes/no'
248
+ end
249
+
250
+ def ConfigTable.path_config?(name)
251
+ ent = get_entry(name) or return false
252
+ ent[1] == 'path'
253
+ end
254
+
255
+
256
+ class << self
257
+ alias newobj new
258
+ end
259
+
260
+ def ConfigTable.new
261
+ c = newobj()
262
+ c.initialize_from_table
263
+ c
264
+ end
265
+
266
+ def ConfigTable.load
267
+ c = newobj()
268
+ c.initialize_from_file
269
+ c
270
+ end
271
+
272
+ def initialize_from_table
273
+ @table = {}
274
+ DESCRIPTER.each do |k, (default, vname, desc, default2)|
275
+ @table[k] = default
276
+ end
277
+ end
278
+
279
+ def initialize_from_file
280
+ raise InstallError, "#{File.basename $0} config first"\
281
+ unless File.file?(SAVE_FILE)
282
+ @table = {}
283
+ File.foreach(SAVE_FILE) do |line|
284
+ k, v = line.split(/=/, 2)
285
+ @table[k] = v.strip
286
+ end
287
+ end
288
+
289
+ def save
290
+ File.open(SAVE_FILE, 'w') {|f|
291
+ @table.each do |k, v|
292
+ f.printf "%s=%s\n", k, v if v
293
+ end
294
+ }
295
+ end
296
+
297
+ def []=(k, v)
298
+ raise InstallError, "unknown config option #{k}"\
299
+ unless ConfigTable.config_key?(k)
300
+ @table[k] = v
301
+ end
302
+
303
+ def [](key)
304
+ return nil unless @table[key]
305
+ @table[key].gsub(%r<\$([^/]+)>) { self[$1] }
306
+ end
307
+
308
+ def set_raw(key, val)
309
+ @table[key] = val
310
+ end
311
+
312
+ def get_raw(key)
313
+ @table[key]
314
+ end
315
+
316
+ end
317
+
318
+
319
+ module MetaConfigAPI
320
+
321
+ def eval_file_ifexist(fname)
322
+ instance_eval File.read(fname), fname, 1 if File.file?(fname)
323
+ end
324
+
325
+ def config_names
326
+ ConfigTable.keys
327
+ end
328
+
329
+ def config?(name)
330
+ ConfigTable.config_key?(name)
331
+ end
332
+
333
+ def bool_config?(name)
334
+ ConfigTable.bool_config?(name)
335
+ end
336
+
337
+ def value_config?(name)
338
+ ConfigTable.value_config?(name)
339
+ end
340
+
341
+ def path_config?(name)
342
+ ConfigTable.path_config?(name)
343
+ end
344
+
345
+ def add_config(name, argname, default, desc)
346
+ ConfigTable.add_entry name,[default,argname,desc]
347
+ end
348
+
349
+ def add_path_config(name, default, desc)
350
+ add_config name, 'path', default, desc
351
+ end
352
+
353
+ def add_bool_config(name, default, desc)
354
+ add_config name, 'yes/no', default ? 'yes' : 'no', desc
355
+ end
356
+
357
+ def set_config_default(name, default)
358
+ if bool_config?(name)
359
+ ConfigTable.get_entry!(name)[0] = (default ? 'yes' : 'no')
360
+ else
361
+ ConfigTable.get_entry!(name)[0] = default
362
+ end
363
+ end
364
+
365
+ def remove_config(name)
366
+ ent = ConfigTable.get_entry(name)
367
+ ConfigTable.remove_entry name
368
+ ent
369
+ end
370
+
371
+ end
372
+
373
+ #
374
+ # File Operations
375
+ #
376
+
377
+ module FileOperations
378
+
379
+ def mkdir_p(dirname, prefix = nil)
380
+ dirname = prefix + dirname if prefix
381
+ $stderr.puts "mkdir -p #{dirname}" if verbose?
382
+ return if no_harm?
383
+
384
+ # does not check '/'... it's too abnormal case
385
+ dirs = dirname.split(%r<(?=/)>)
386
+ if /\A[a-z]:\z/i =~ dirs[0]
387
+ disk = dirs.shift
388
+ dirs[0] = disk + dirs[0]
389
+ end
390
+ dirs.each_index do |idx|
391
+ path = dirs[0..idx].join('')
392
+ Dir.mkdir path unless File.dir?(path)
393
+ end
394
+ end
395
+
396
+ def rm_f(fname)
397
+ $stderr.puts "rm -f #{fname}" if verbose?
398
+ return if no_harm?
399
+
400
+ if File.exist?(fname) or File.symlink?(fname)
401
+ File.chmod 0777, fname
402
+ File.unlink fname
403
+ end
404
+ end
405
+
406
+ def rm_rf(dn)
407
+ $stderr.puts "rm -rf #{dn}" if verbose?
408
+ return if no_harm?
409
+
410
+ Dir.chdir dn
411
+ Dir.foreach('.') do |fn|
412
+ next if fn == '.'
413
+ next if fn == '..'
414
+ if File.dir?(fn)
415
+ verbose_off {
416
+ rm_rf fn
417
+ }
418
+ else
419
+ verbose_off {
420
+ rm_f fn
421
+ }
422
+ end
423
+ end
424
+ Dir.chdir '..'
425
+ Dir.rmdir dn
426
+ end
427
+
428
+ def move_file(src, dest)
429
+ File.unlink dest if File.exist?(dest)
430
+ begin
431
+ File.rename src, dest
432
+ rescue
433
+ File.open(dest, 'wb') {|f| f.write File.binread(src) }
434
+ File.chmod File.stat(src).mode, dest
435
+ File.unlink src
436
+ end
437
+ end
438
+
439
+ def install(from, dest, mode, prefix = nil)
440
+ $stderr.puts "install #{from} #{dest}" if verbose?
441
+ return if no_harm?
442
+
443
+ realdest = prefix ? prefix + dest : dest
444
+ realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
445
+ str = File.binread(from)
446
+ if diff?(str, realdest)
447
+ verbose_off {
448
+ rm_f realdest if File.exist?(realdest)
449
+ }
450
+ File.open(realdest, 'wb') {|f|
451
+ f.write str
452
+ }
453
+ File.chmod mode, realdest
454
+
455
+ File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
456
+ if prefix
457
+ f.puts realdest.sub(prefix, '')
458
+ else
459
+ f.puts realdest
460
+ end
461
+ }
462
+ end
463
+ end
464
+
465
+ def diff?(new_content, path)
466
+ return true unless File.exist?(path)
467
+ new_content != File.binread(path)
468
+ end
469
+
470
+ def command(str)
471
+ $stderr.puts str if verbose?
472
+ system str or raise RuntimeError, "'system #{str}' failed"
473
+ end
474
+
475
+ def ruby(str)
476
+ command config('ruby-prog') + ' ' + str
477
+ end
478
+
479
+ def make(task = '')
480
+ command config('make-prog') + ' ' + task
481
+ end
482
+
483
+ def extdir?(dir)
484
+ File.exist?(dir + '/MANIFEST')
485
+ end
486
+
487
+ def all_files_in(dirname)
488
+ Dir.open(dirname) {|d|
489
+ return d.select {|ent| File.file?("#{dirname}/#{ent}") }
490
+ }
491
+ end
492
+
493
+ REJECT_DIRS = %w(
494
+ CVS SCCS RCS CVS.adm .svn
495
+ )
496
+
497
+ def all_dirs_in(dirname)
498
+ Dir.open(dirname) {|d|
499
+ return d.select {|n| File.dir?("#{dirname}/#{n}") } - %w(. ..) - REJECT_DIRS
500
+ }
501
+ end
502
+
503
+ end
504
+
505
+ #
506
+ # Main Installer
507
+ #
508
+
509
+ class InstallError < StandardError; end
510
+
511
+
512
+ module HookUtils
513
+
514
+ def run_hook(name)
515
+ try_run_hook "#{curr_srcdir()}/#{name}" or
516
+ try_run_hook "#{curr_srcdir()}/#{name}.rb"
517
+ end
518
+
519
+ def try_run_hook(fname)
520
+ return false unless File.file?(fname)
521
+ begin
522
+ instance_eval File.read(fname), fname, 1
523
+ rescue
524
+ raise InstallError, "hook #{fname} failed:\n" + $!.message
525
+ end
526
+ true
527
+ end
528
+
529
+ end
530
+
531
+
532
+ module HookScriptAPI
533
+
534
+ def get_config(key)
535
+ @config[key]
536
+ end
537
+
538
+ alias config get_config
539
+
540
+ def set_config(key, val)
541
+ @config[key] = val
542
+ end
543
+
544
+ #
545
+ # srcdir/objdir (works only in the package directory)
546
+ #
547
+
548
+ #abstract srcdir_root
549
+ #abstract objdir_root
550
+ #abstract relpath
551
+
552
+ def curr_srcdir
553
+ "#{srcdir_root()}/#{relpath()}"
554
+ end
555
+
556
+ def curr_objdir
557
+ "#{objdir_root()}/#{relpath()}"
558
+ end
559
+
560
+ def srcfile(path)
561
+ "#{curr_srcdir()}/#{path}"
562
+ end
563
+
564
+ def srcexist?(path)
565
+ File.exist?(srcfile(path))
566
+ end
567
+
568
+ def srcdirectory?(path)
569
+ File.dir?(srcfile(path))
570
+ end
571
+
572
+ def srcfile?(path)
573
+ File.file? srcfile(path)
574
+ end
575
+
576
+ def srcentries(path = '.')
577
+ Dir.open("#{curr_srcdir()}/#{path}") {|d|
578
+ return d.to_a - %w(. ..)
579
+ }
580
+ end
581
+
582
+ def srcfiles(path = '.')
583
+ srcentries(path).select {|fname|
584
+ File.file?(File.join(curr_srcdir(), path, fname))
585
+ }
586
+ end
587
+
588
+ def srcdirectories(path = '.')
589
+ srcentries(path).select {|fname|
590
+ File.dir?(File.join(curr_srcdir(), path, fname))
591
+ }
592
+ end
593
+
594
+ end
595
+
596
+
597
+ class ToplevelInstaller
598
+
599
+ Version = '3.3.0'
600
+ Copyright = 'Copyright (c) 2000-2004 Minero Aoki'
601
+
602
+ TASKS = [
603
+ [ 'all', 'do config, setup, then install' ],
604
+ [ 'config', 'saves your configurations' ],
605
+ [ 'show', 'shows current configuration' ],
606
+ [ 'setup', 'compiles ruby extentions and others' ],
607
+ [ 'install', 'installs files' ],
608
+ [ 'clean', "does `make clean' for each extention" ],
609
+ [ 'distclean',"does `make distclean' for each extention" ]
610
+ ]
611
+
612
+ def ToplevelInstaller.invoke
613
+ instance().invoke
614
+ end
615
+
616
+ @singleton = nil
617
+
618
+ def ToplevelInstaller.instance
619
+ @singleton ||= new(File.dirname($0))
620
+ @singleton
621
+ end
622
+
623
+ include MetaConfigAPI
624
+
625
+ def initialize(ardir_root)
626
+ @config = nil
627
+ @options = { 'verbose' => true }
628
+ @ardir = File.expand_path(ardir_root)
629
+ end
630
+
631
+ def inspect
632
+ "#<#{self.class} #{__id__()}>"
633
+ end
634
+
635
+ def invoke
636
+ run_metaconfigs
637
+ case task = parsearg_global()
638
+ when nil, 'all'
639
+ @config = load_config('config')
640
+ parsearg_config
641
+ init_installers
642
+ exec_config
643
+ exec_setup
644
+ exec_install
645
+ else
646
+ @config = load_config(task)
647
+ __send__ "parsearg_#{task}"
648
+ init_installers
649
+ __send__ "exec_#{task}"
650
+ end
651
+ end
652
+
653
+ def run_metaconfigs
654
+ eval_file_ifexist "#{@ardir}/metaconfig"
655
+ end
656
+
657
+ def load_config(task)
658
+ case task
659
+ when 'config'
660
+ ConfigTable.new
661
+ when 'clean', 'distclean'
662
+ if File.exist?(ConfigTable::SAVE_FILE)
663
+ then ConfigTable.load
664
+ else ConfigTable.new
665
+ end
666
+ else
667
+ ConfigTable.load
668
+ end
669
+ end
670
+
671
+ def init_installers
672
+ @installer = Installer.new(@config, @options, @ardir, File.expand_path('.'))
673
+ end
674
+
675
+ #
676
+ # Hook Script API bases
677
+ #
678
+
679
+ def srcdir_root
680
+ @ardir
681
+ end
682
+
683
+ def objdir_root
684
+ '.'
685
+ end
686
+
687
+ def relpath
688
+ '.'
689
+ end
690
+
691
+ #
692
+ # Option Parsing
693
+ #
694
+
695
+ def parsearg_global
696
+ valid_task = /\A(?:#{TASKS.map {|task,desc| task }.join '|'})\z/
697
+
698
+ while arg = ARGV.shift
699
+ case arg
700
+ when /\A\w+\z/
701
+ raise InstallError, "invalid task: #{arg}" unless valid_task =~ arg
702
+ return arg
703
+
704
+ when '-q', '--quiet'
705
+ @options['verbose'] = false
706
+
707
+ when '--verbose'
708
+ @options['verbose'] = true
709
+
710
+ when '-h', '--help'
711
+ print_usage $stdout
712
+ exit 0
713
+
714
+ when '-v', '--version'
715
+ puts "#{File.basename($0)} version #{Version}"
716
+ exit 0
717
+
718
+ when '--copyright'
719
+ puts Copyright
720
+ exit 0
721
+
722
+ else
723
+ raise InstallError, "unknown global option '#{arg}'"
724
+ end
725
+ end
726
+
727
+ nil
728
+ end
729
+
730
+
731
+ def parsearg_no_options
732
+ raise InstallError, "#{task}: unknown options: #{ARGV.join ' '}"\
733
+ unless ARGV.empty?
734
+ end
735
+
736
+ alias parsearg_show parsearg_no_options
737
+ alias parsearg_setup parsearg_no_options
738
+ alias parsearg_clean parsearg_no_options
739
+ alias parsearg_distclean parsearg_no_options
740
+
741
+ def parsearg_config
742
+ re = /\A--(#{ConfigTable.keys.join '|'})(?:=(.*))?\z/
743
+ @options['config-opt'] = []
744
+
745
+ while i = ARGV.shift
746
+ if /\A--?\z/ =~ i
747
+ @options['config-opt'] = ARGV.dup
748
+ break
749
+ end
750
+ m = re.match(i) or raise InstallError, "config: unknown option #{i}"
751
+ name, value = m.to_a[1,2]
752
+ if value
753
+ if ConfigTable.bool_config?(name)
754
+ raise InstallError, "config: --#{name} allows only yes/no for argument"\
755
+ unless /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i =~ value
756
+ value = (/\Ay(es)?|\At(rue)/i =~ value) ? 'yes' : 'no'
757
+ end
758
+ else
759
+ raise InstallError, "config: --#{name} requires argument"\
760
+ unless ConfigTable.bool_config?(name)
761
+ value = 'yes'
762
+ end
763
+ @config[name] = value
764
+ end
765
+ end
766
+
767
+ def parsearg_install
768
+ @options['no-harm'] = false
769
+ @options['install-prefix'] = ''
770
+ while a = ARGV.shift
771
+ case a
772
+ when /\A--no-harm\z/
773
+ @options['no-harm'] = true
774
+ when /\A--prefix=(.*)\z/
775
+ path = $1
776
+ path = File.expand_path(path) unless path[0,1] == '/'
777
+ @options['install-prefix'] = path
778
+ else
779
+ raise InstallError, "install: unknown option #{a}"
780
+ end
781
+ end
782
+ end
783
+
784
+ def print_usage(out)
785
+ out.puts 'Typical Installation Procedure:'
786
+ out.puts " $ ruby #{File.basename $0} config"
787
+ out.puts " $ ruby #{File.basename $0} setup"
788
+ out.puts " # ruby #{File.basename $0} install (may require root privilege)"
789
+ out.puts
790
+ out.puts 'Detailed Usage:'
791
+ out.puts " ruby #{File.basename $0} <global option>"
792
+ out.puts " ruby #{File.basename $0} [<global options>] <task> [<task options>]"
793
+
794
+ fmt = " %-20s %s\n"
795
+ out.puts
796
+ out.puts 'Global options:'
797
+ out.printf fmt, '-q,--quiet', 'suppress message outputs'
798
+ out.printf fmt, ' --verbose', 'output messages verbosely'
799
+ out.printf fmt, '-h,--help', 'print this message'
800
+ out.printf fmt, '-v,--version', 'print version and quit'
801
+ out.printf fmt, ' --copyright', 'print copyright and quit'
802
+
803
+ out.puts
804
+ out.puts 'Tasks:'
805
+ TASKS.each do |name, desc|
806
+ out.printf " %-10s %s\n", name, desc
807
+ end
808
+
809
+ out.puts
810
+ out.puts 'Options for CONFIG or ALL:'
811
+ ConfigTable.each_definition do |name, (default, arg, desc, default2)|
812
+ out.printf " %-20s %s [%s]\n",
813
+ '--'+ name + (ConfigTable.bool_config?(name) ? '' : '='+arg),
814
+ desc,
815
+ default2 || default
816
+ end
817
+ out.printf " %-20s %s [%s]\n",
818
+ '--rbconfig=path', 'your rbconfig.rb to load', "running ruby's"
819
+
820
+ out.puts
821
+ out.puts 'Options for INSTALL:'
822
+ out.printf " %-20s %s [%s]\n",
823
+ '--no-harm', 'only display what to do if given', 'off'
824
+ out.printf " %-20s %s [%s]\n",
825
+ '--prefix', 'install path prefix', '$prefix'
826
+
827
+ out.puts
828
+ end
829
+
830
+ #
831
+ # Task Handlers
832
+ #
833
+
834
+ def exec_config
835
+ @installer.exec_config
836
+ @config.save # must be final
837
+ end
838
+
839
+ def exec_setup
840
+ @installer.exec_setup
841
+ end
842
+
843
+ def exec_install
844
+ @installer.exec_install
845
+ end
846
+
847
+ def exec_show
848
+ ConfigTable.each_name do |k|
849
+ v = @config.get_raw(k)
850
+ if not v or v.empty?
851
+ v = '(not specified)'
852
+ end
853
+ printf "%-10s %s\n", k, v
854
+ end
855
+ end
856
+
857
+ def exec_clean
858
+ @installer.exec_clean
859
+ end
860
+
861
+ def exec_distclean
862
+ @installer.exec_distclean
863
+ end
864
+
865
+ end
866
+
867
+
868
+ class ToplevelInstallerMulti < ToplevelInstaller
869
+
870
+ include HookUtils
871
+ include HookScriptAPI
872
+ include FileOperations
873
+
874
+ def initialize(ardir)
875
+ super
876
+ @packages = all_dirs_in("#{@ardir}/packages")
877
+ raise 'no package exists' if @packages.empty?
878
+ end
879
+
880
+ def run_metaconfigs
881
+ eval_file_ifexist "#{@ardir}/metaconfig"
882
+ @packages.each do |name|
883
+ eval_file_ifexist "#{@ardir}/packages/#{name}/metaconfig"
884
+ end
885
+ end
886
+
887
+ def init_installers
888
+ @installers = {}
889
+ @packages.each do |pack|
890
+ @installers[pack] = Installer.new(@config, @options,
891
+ "#{@ardir}/packages/#{pack}",
892
+ "packages/#{pack}")
893
+ end
894
+
895
+ with = extract_selection(config('with'))
896
+ without = extract_selection(config('without'))
897
+ @selected = @installers.keys.select {|name|
898
+ (with.empty? or with.include?(name)) \
899
+ and not without.include?(name)
900
+ }
901
+ end
902
+
903
+ def extract_selection(list)
904
+ a = list.split(/,/)
905
+ a.each do |name|
906
+ raise InstallError, "no such package: #{name}" \
907
+ unless @installers.key?(name)
908
+ end
909
+ a
910
+ end
911
+
912
+ def print_usage(f)
913
+ super
914
+ f.puts 'Inluded packages:'
915
+ f.puts ' ' + @packages.sort.join(' ')
916
+ f.puts
917
+ end
918
+
919
+ #
920
+ # multi-package metaconfig API
921
+ #
922
+
923
+ attr_reader :packages
924
+
925
+ def declare_packages(list)
926
+ raise 'package list is empty' if list.empty?
927
+ list.each do |name|
928
+ raise "directory packages/#{name} does not exist"\
929
+ unless File.dir?("#{@ardir}/packages/#{name}")
930
+ end
931
+ @packages = list
932
+ end
933
+
934
+ #
935
+ # Task Handlers
936
+ #
937
+
938
+ def exec_config
939
+ run_hook 'pre-config'
940
+ each_selected_installers {|inst| inst.exec_config }
941
+ run_hook 'post-config'
942
+ @config.save # must be final
943
+ end
944
+
945
+ def exec_setup
946
+ run_hook 'pre-setup'
947
+ each_selected_installers {|inst| inst.exec_setup }
948
+ run_hook 'post-setup'
949
+ end
950
+
951
+ def exec_install
952
+ run_hook 'pre-install'
953
+ each_selected_installers {|inst| inst.exec_install }
954
+ run_hook 'post-install'
955
+ end
956
+
957
+ def exec_clean
958
+ rm_f ConfigTable::SAVE_FILE
959
+ run_hook 'pre-clean'
960
+ each_selected_installers {|inst| inst.exec_clean }
961
+ run_hook 'post-clean'
962
+ end
963
+
964
+ def exec_distclean
965
+ rm_f ConfigTable::SAVE_FILE
966
+ run_hook 'pre-distclean'
967
+ each_selected_installers {|inst| inst.exec_distclean }
968
+ run_hook 'post-distclean'
969
+ end
970
+
971
+ #
972
+ # lib
973
+ #
974
+
975
+ def each_selected_installers
976
+ Dir.mkdir 'packages' unless File.dir?('packages')
977
+ @selected.each do |pack|
978
+ $stderr.puts "Processing the package `#{pack}' ..." if @options['verbose']
979
+ Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
980
+ Dir.chdir "packages/#{pack}"
981
+ yield @installers[pack]
982
+ Dir.chdir '../..'
983
+ end
984
+ end
985
+
986
+ def verbose?
987
+ @options['verbose']
988
+ end
989
+
990
+ def no_harm?
991
+ @options['no-harm']
992
+ end
993
+
994
+ end
995
+
996
+
997
+ class Installer
998
+
999
+ FILETYPES = %w( bin lib ext data )
1000
+
1001
+ include HookScriptAPI
1002
+ include HookUtils
1003
+ include FileOperations
1004
+
1005
+ def initialize(config, opt, srcroot, objroot)
1006
+ @config = config
1007
+ @options = opt
1008
+ @srcdir = File.expand_path(srcroot)
1009
+ @objdir = File.expand_path(objroot)
1010
+ @currdir = '.'
1011
+ end
1012
+
1013
+ def inspect
1014
+ "#<#{self.class} #{File.basename(@srcdir)}>"
1015
+ end
1016
+
1017
+ #
1018
+ # Hook Script API bases
1019
+ #
1020
+
1021
+ def srcdir_root
1022
+ @srcdir
1023
+ end
1024
+
1025
+ def objdir_root
1026
+ @objdir
1027
+ end
1028
+
1029
+ def relpath
1030
+ @currdir
1031
+ end
1032
+
1033
+ #
1034
+ # configs/options
1035
+ #
1036
+
1037
+ def no_harm?
1038
+ @options['no-harm']
1039
+ end
1040
+
1041
+ def verbose?
1042
+ @options['verbose']
1043
+ end
1044
+
1045
+ def verbose_off
1046
+ begin
1047
+ save, @options['verbose'] = @options['verbose'], false
1048
+ yield
1049
+ ensure
1050
+ @options['verbose'] = save
1051
+ end
1052
+ end
1053
+
1054
+ #
1055
+ # TASK config
1056
+ #
1057
+
1058
+ def exec_config
1059
+ exec_task_traverse 'config'
1060
+ end
1061
+
1062
+ def config_dir_bin(rel)
1063
+ end
1064
+
1065
+ def config_dir_lib(rel)
1066
+ end
1067
+
1068
+ def config_dir_ext(rel)
1069
+ extconf if extdir?(curr_srcdir())
1070
+ end
1071
+
1072
+ def extconf
1073
+ opt = @options['config-opt'].join(' ')
1074
+ command "#{config('ruby-prog')} #{curr_srcdir()}/extconf.rb #{opt}"
1075
+ end
1076
+
1077
+ def config_dir_data(rel)
1078
+ end
1079
+
1080
+ #
1081
+ # TASK setup
1082
+ #
1083
+
1084
+ def exec_setup
1085
+ exec_task_traverse 'setup'
1086
+ end
1087
+
1088
+ def setup_dir_bin(rel)
1089
+ all_files_in(curr_srcdir()).each do |fname|
1090
+ adjust_shebang "#{curr_srcdir()}/#{fname}"
1091
+ end
1092
+ end
1093
+
1094
+ def adjust_shebang(path)
1095
+ return if no_harm?
1096
+ tmpfile = File.basename(path) + '.tmp'
1097
+ begin
1098
+ File.open(path, 'rb') {|r|
1099
+ File.open(tmpfile, 'wb') {|w|
1100
+ first = r.gets
1101
+ return unless should_modify_shebang?(first)
1102
+ $stderr.puts "adjusting shebang: #{File.basename(path)}" if verbose?
1103
+ w.print first.sub(SHEBANG_RE, '#!' + config('ruby-path'))
1104
+ w.write r.read
1105
+ }
1106
+ }
1107
+ move_file tmpfile, File.basename(path)
1108
+ ensure
1109
+ File.unlink tmpfile if File.exist?(tmpfile)
1110
+ end
1111
+ end
1112
+
1113
+ def should_modify_shebang?(line)
1114
+ File.basename(config('ruby-path')) == 'ruby' or
1115
+ shebang_command(line) == 'ruby'
1116
+ end
1117
+
1118
+ def shebang_command(line)
1119
+ cmd, arg = *line.sub(/\A\#!/, '').strip.split(/\s+/, 2)
1120
+ cmd
1121
+ end
1122
+
1123
+ def setup_dir_lib(rel)
1124
+ end
1125
+
1126
+ def setup_dir_ext(rel)
1127
+ make if extdir?(curr_srcdir())
1128
+ end
1129
+
1130
+ def setup_dir_data(rel)
1131
+ end
1132
+
1133
+ #
1134
+ # TASK install
1135
+ #
1136
+
1137
+ def exec_install
1138
+ exec_task_traverse 'install'
1139
+ end
1140
+
1141
+ def install_dir_bin(rel)
1142
+ install_files collect_filenames_auto(), "#{config('bin-dir')}/#{rel}", 0755
1143
+ end
1144
+
1145
+ def install_dir_lib(rel)
1146
+ install_files ruby_scripts(), "#{config('rb-dir')}/#{rel}", 0644
1147
+ end
1148
+
1149
+ def install_dir_ext(rel)
1150
+ return unless extdir?(curr_srcdir())
1151
+ install_files ruby_extentions('.'),
1152
+ "#{config('so-dir')}/#{File.dirname(rel)}",
1153
+ 0555
1154
+ end
1155
+
1156
+ def install_dir_data(rel)
1157
+ install_files collect_filenames_auto(), "#{config('data-dir')}/#{rel}", 0644
1158
+ end
1159
+
1160
+ def install_files(list, dest, mode)
1161
+ mkdir_p dest, @options['install-prefix']
1162
+ list.each do |fname|
1163
+ install fname, dest, mode, @options['install-prefix']
1164
+ end
1165
+ end
1166
+
1167
+ def ruby_scripts
1168
+ collect_filenames_auto().select {|n| /\.rb\z/ =~ n }
1169
+ end
1170
+
1171
+ # picked up many entries from cvs-1.11.1/src/ignore.c
1172
+ reject_patterns = %w(
1173
+ core RCSLOG tags TAGS .make.state
1174
+ .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
1175
+ *~ *.old *.bak *.BAK *.orig *.rej _$* *$
1176
+
1177
+ *.org *.in .*
1178
+ )
1179
+ mapping = {
1180
+ '.' => '\.',
1181
+ '$' => '\$',
1182
+ '#' => '\#',
1183
+ '*' => '.*'
1184
+ }
1185
+ REJECT_PATTERNS = Regexp.new('\A(?:' +
1186
+ reject_patterns.map {|pat|
1187
+ pat.gsub(/[\.\$\#\*]/) {|ch| mapping[ch] }
1188
+ }.join('|') +
1189
+ ')\z')
1190
+
1191
+ def collect_filenames_auto
1192
+ mapdir((existfiles() - hookfiles()).reject {|fname|
1193
+ REJECT_PATTERNS =~ fname
1194
+ })
1195
+ end
1196
+
1197
+ def existfiles
1198
+ all_files_in(curr_srcdir()) | all_files_in('.')
1199
+ end
1200
+
1201
+ def hookfiles
1202
+ %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
1203
+ %w( config setup install clean ).map {|t| sprintf(fmt, t) }
1204
+ }.flatten
1205
+ end
1206
+
1207
+ def mapdir(filelist)
1208
+ filelist.map {|fname|
1209
+ if File.exist?(fname) # objdir
1210
+ fname
1211
+ else # srcdir
1212
+ File.join(curr_srcdir(), fname)
1213
+ end
1214
+ }
1215
+ end
1216
+
1217
+ def ruby_extentions(dir)
1218
+ _ruby_extentions(dir) or
1219
+ raise InstallError, "no ruby extention exists: 'ruby #{$0} setup' first"
1220
+ end
1221
+
1222
+ DLEXT = /\.#{ ::Config::CONFIG['DLEXT'] }\z/
1223
+
1224
+ def _ruby_extentions(dir)
1225
+ Dir.open(dir) {|d|
1226
+ return d.select {|fname| DLEXT =~ fname }
1227
+ }
1228
+ end
1229
+
1230
+ #
1231
+ # TASK clean
1232
+ #
1233
+
1234
+ def exec_clean
1235
+ exec_task_traverse 'clean'
1236
+ rm_f ConfigTable::SAVE_FILE
1237
+ rm_f 'InstalledFiles'
1238
+ end
1239
+
1240
+ def clean_dir_bin(rel)
1241
+ end
1242
+
1243
+ def clean_dir_lib(rel)
1244
+ end
1245
+
1246
+ def clean_dir_ext(rel)
1247
+ return unless extdir?(curr_srcdir())
1248
+ make 'clean' if File.file?('Makefile')
1249
+ end
1250
+
1251
+ def clean_dir_data(rel)
1252
+ end
1253
+
1254
+ #
1255
+ # TASK distclean
1256
+ #
1257
+
1258
+ def exec_distclean
1259
+ exec_task_traverse 'distclean'
1260
+ rm_f ConfigTable::SAVE_FILE
1261
+ rm_f 'InstalledFiles'
1262
+ end
1263
+
1264
+ def distclean_dir_bin(rel)
1265
+ end
1266
+
1267
+ def distclean_dir_lib(rel)
1268
+ end
1269
+
1270
+ def distclean_dir_ext(rel)
1271
+ return unless extdir?(curr_srcdir())
1272
+ make 'distclean' if File.file?('Makefile')
1273
+ end
1274
+
1275
+ #
1276
+ # lib
1277
+ #
1278
+
1279
+ def exec_task_traverse(task)
1280
+ run_hook "pre-#{task}"
1281
+ FILETYPES.each do |type|
1282
+ if config('without-ext') == 'yes' and type == 'ext'
1283
+ $stderr.puts 'skipping ext/* by user option' if verbose?
1284
+ next
1285
+ end
1286
+ traverse task, type, "#{task}_dir_#{type}"
1287
+ end
1288
+ run_hook "post-#{task}"
1289
+ end
1290
+
1291
+ def traverse(task, rel, mid)
1292
+ dive_into(rel) {
1293
+ run_hook "pre-#{task}"
1294
+ __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
1295
+ all_dirs_in(curr_srcdir()).each do |d|
1296
+ traverse task, "#{rel}/#{d}", mid
1297
+ end
1298
+ run_hook "post-#{task}"
1299
+ }
1300
+ end
1301
+
1302
+ def dive_into(rel)
1303
+ return unless File.dir?("#{@srcdir}/#{rel}")
1304
+
1305
+ dir = File.basename(rel)
1306
+ Dir.mkdir dir unless File.dir?(dir)
1307
+ prevdir = Dir.pwd
1308
+ Dir.chdir dir
1309
+ $stderr.puts '---> ' + rel if verbose?
1310
+ @currdir = rel
1311
+ yield
1312
+ Dir.chdir prevdir
1313
+ $stderr.puts '<--- ' + rel if verbose?
1314
+ @currdir = File.dirname(rel)
1315
+ end
1316
+
1317
+ end
1318
+
1319
+
1320
+ if $0 == __FILE__
1321
+ begin
1322
+ if multipackage_install?
1323
+ ToplevelInstallerMulti.invoke
1324
+ else
1325
+ ToplevelInstaller.invoke
1326
+ end
1327
+ rescue
1328
+ raise if $DEBUG
1329
+ $stderr.puts $!.message
1330
+ $stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
1331
+ exit 1
1332
+ end
1333
+ end