ydbi 0.5.6 → 0.5.7

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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +35 -0
  3. data/.gitignore +8 -0
  4. data/.travis.yml +15 -0
  5. data/ChangeLog +321 -314
  6. data/Gemfile +5 -0
  7. data/Rakefile +10 -0
  8. data/TODO +44 -0
  9. data/bench/bench.rb +79 -0
  10. data/build/rake_task_lib.rb +186 -0
  11. data/doc/DBD_SPEC.rdoc +88 -0
  12. data/doc/DBI_SPEC.rdoc +157 -0
  13. data/doc/homepage/contact.html +62 -0
  14. data/doc/homepage/development.html +124 -0
  15. data/doc/homepage/index.html +83 -0
  16. data/doc/homepage/ruby-dbi.css +91 -0
  17. data/lib/dbd/Mysql.rb +137 -0
  18. data/lib/dbd/ODBC.rb +89 -0
  19. data/lib/dbd/Pg.rb +188 -0
  20. data/lib/dbd/SQLite.rb +97 -0
  21. data/lib/dbd/SQLite3.rb +124 -0
  22. data/lib/dbd/mysql/database.rb +405 -0
  23. data/lib/dbd/mysql/driver.rb +125 -0
  24. data/lib/dbd/mysql/statement.rb +188 -0
  25. data/lib/dbd/odbc/database.rb +128 -0
  26. data/lib/dbd/odbc/driver.rb +38 -0
  27. data/lib/dbd/odbc/statement.rb +137 -0
  28. data/lib/dbd/pg/database.rb +504 -0
  29. data/lib/dbd/pg/exec.rb +47 -0
  30. data/lib/dbd/pg/statement.rb +160 -0
  31. data/lib/dbd/pg/tuples.rb +121 -0
  32. data/lib/dbd/pg/type.rb +209 -0
  33. data/lib/dbd/sqlite/database.rb +151 -0
  34. data/lib/dbd/sqlite/statement.rb +125 -0
  35. data/lib/dbd/sqlite3/database.rb +201 -0
  36. data/lib/dbd/sqlite3/statement.rb +78 -0
  37. data/lib/dbi.rb +13 -17
  38. data/lib/dbi/utils/date.rb +7 -3
  39. data/lib/dbi/version.rb +1 -1
  40. data/prototypes/types2.rb +237 -0
  41. data/setup.rb +1585 -0
  42. data/test/DBD_TESTS +50 -0
  43. data/test/TESTING +16 -0
  44. data/test/dbd/general/test_database.rb +206 -0
  45. data/test/dbd/general/test_statement.rb +326 -0
  46. data/test/dbd/general/test_types.rb +296 -0
  47. data/test/dbd/mysql/base.rb +26 -0
  48. data/test/dbd/mysql/down.sql +19 -0
  49. data/test/dbd/mysql/test_blob.rb +18 -0
  50. data/test/dbd/mysql/test_new_methods.rb +7 -0
  51. data/test/dbd/mysql/test_patches.rb +111 -0
  52. data/test/dbd/mysql/up.sql +28 -0
  53. data/test/dbd/odbc/base.rb +30 -0
  54. data/test/dbd/odbc/down.sql +19 -0
  55. data/test/dbd/odbc/test_new_methods.rb +12 -0
  56. data/test/dbd/odbc/test_ping.rb +10 -0
  57. data/test/dbd/odbc/test_statement.rb +44 -0
  58. data/test/dbd/odbc/test_transactions.rb +58 -0
  59. data/test/dbd/odbc/up.sql +33 -0
  60. data/test/dbd/postgresql/base.rb +31 -0
  61. data/test/dbd/postgresql/down.sql +31 -0
  62. data/test/dbd/postgresql/test_arrays.rb +179 -0
  63. data/test/dbd/postgresql/test_async.rb +121 -0
  64. data/test/dbd/postgresql/test_blob.rb +36 -0
  65. data/test/dbd/postgresql/test_bytea.rb +87 -0
  66. data/test/dbd/postgresql/test_ping.rb +10 -0
  67. data/test/dbd/postgresql/test_timestamp.rb +77 -0
  68. data/test/dbd/postgresql/test_transactions.rb +58 -0
  69. data/test/dbd/postgresql/testdbipg.rb +307 -0
  70. data/test/dbd/postgresql/up.sql +60 -0
  71. data/test/dbd/sqlite/base.rb +32 -0
  72. data/test/dbd/sqlite/test_database.rb +30 -0
  73. data/test/dbd/sqlite/test_driver.rb +68 -0
  74. data/test/dbd/sqlite/test_statement.rb +112 -0
  75. data/test/dbd/sqlite/up.sql +25 -0
  76. data/test/dbd/sqlite3/base.rb +32 -0
  77. data/test/dbd/sqlite3/test_database.rb +77 -0
  78. data/test/dbd/sqlite3/test_driver.rb +67 -0
  79. data/test/dbd/sqlite3/test_statement.rb +88 -0
  80. data/test/dbd/sqlite3/up.sql +33 -0
  81. data/test/dbi/tc_columninfo.rb +4 -9
  82. data/test/dbi/tc_date.rb +2 -9
  83. data/test/dbi/tc_dbi.rb +3 -9
  84. data/test/dbi/tc_row.rb +17 -23
  85. data/test/dbi/tc_sqlbind.rb +6 -7
  86. data/test/dbi/tc_statementhandle.rb +3 -4
  87. data/test/dbi/tc_time.rb +2 -8
  88. data/test/dbi/tc_timestamp.rb +2 -16
  89. data/test/dbi/tc_types.rb +5 -11
  90. data/test/ts_dbd.rb +131 -0
  91. data/ydbi.gemspec +23 -0
  92. metadata +128 -10
@@ -0,0 +1,78 @@
1
+ #
2
+ # See DBI::BaseStatement.
3
+ #
4
+ class DBI::DBD::SQLite3::Statement < DBI::BaseStatement
5
+ def initialize(sql, db)
6
+ sql.gsub!(/\\\\/) { '\\' } # sqlite underneath does this for us automatically, and it's causing trouble with the rest of the system.
7
+ @sql = sql
8
+ @db = db
9
+ @stmt = db.prepare(sql)
10
+ @result = nil
11
+ rescue ::SQLite3::Exception, RuntimeError => err
12
+ raise DBI::ProgrammingError.new(err.message)
13
+ end
14
+
15
+ #
16
+ # See DBI::BaseStatement#bind_param. This method will also raise
17
+ # DBI::InterfaceError if +param+ is not a Fixnum, to prevent incorrect
18
+ # binding.
19
+ #
20
+ def bind_param(param, value, attribs=nil)
21
+ raise DBI::InterfaceError, "Bound parameter must be an integer" unless param.kind_of? Fixnum
22
+ @stmt.bind_param(param, value)
23
+ end
24
+
25
+ def execute()
26
+ @result = @stmt.execute
27
+ @rows = DBI::SQL.query?(@sql) ? 0 : @db.changes
28
+ end
29
+
30
+ def finish()
31
+ @stmt.close rescue nil
32
+ @result = nil
33
+ end
34
+
35
+ def fetch()
36
+ ret = @result.next
37
+ return ret unless ret
38
+ [ret].flatten
39
+ end
40
+
41
+ def column_info()
42
+ @stmt.columns.zip(@stmt.types).map{|name, type_name|
43
+ m = DBI::DBD::SQLite3.parse_type(type_name)
44
+ h = {
45
+ 'name' => name,
46
+ 'type_name' => m[1],
47
+ 'sql_type' =>
48
+ begin
49
+ DBI.const_get('SQL_'+m[1].upcase)
50
+ rescue NameError
51
+ DBI::SQL_OTHER
52
+ end,
53
+ }
54
+ h['precision'] = m[3].to_i if m[3]
55
+ h['scale'] = m[5].to_i if m[5]
56
+
57
+ case h['type_name']
58
+ when 'double'
59
+ h['dbi_type'] = DBI::Type::Float
60
+ end
61
+
62
+ h
63
+ }
64
+ end
65
+
66
+ def rows()
67
+ @rows
68
+ end
69
+
70
+ def bind_params(*bindvars)
71
+ @stmt.bind_params(bindvars)
72
+ end
73
+
74
+ def cancel()
75
+ @result = nil
76
+ @index = 0
77
+ end
78
+ end
data/lib/dbi.rb CHANGED
@@ -9,16 +9,16 @@ module DBI; end
9
9
  #
10
10
  # Copyright (c) 2001, 2002, 2003 Michael Neumann <mneumann@ntecs.de>
11
11
  # Copyright (c) 2008 Erik Hollensbe <erik@hollensbe.org>
12
- #
12
+ #
13
13
  # All rights reserved.
14
14
  #
15
- # Redistribution and use in source and binary forms, with or without
16
- # modification, are permitted provided that the following conditions
15
+ # Redistribution and use in source and binary forms, with or without
16
+ # modification, are permitted provided that the following conditions
17
17
  # are met:
18
- # 1. Redistributions of source code must retain the above copyright
18
+ # 1. Redistributions of source code must retain the above copyright
19
19
  # notice, this list of conditions and the following disclaimer.
20
- # 2. Redistributions in binary form must reproduce the above copyright
21
- # notice, this list of conditions and the following disclaimer in the
20
+ # 2. Redistributions in binary form must reproduce the above copyright
21
+ # notice, this list of conditions and the following disclaimer in the
22
22
  # documentation and/or other materials provided with the distribution.
23
23
  # 3. The name of the author may not be used to endorse or promote products
24
24
  # derived from this software without specific prior written permission.
@@ -128,7 +128,7 @@ module DBI
128
128
 
129
129
  class << self
130
130
 
131
- # Establish a database connection.
131
+ # Establish a database connection.
132
132
  #
133
133
  # Format goes as such: "dbi:Driver:database_conn_args"
134
134
  #
@@ -179,7 +179,7 @@ module DBI
179
179
  # Return a list (of String) of the available drivers.
180
180
  #
181
181
  # NOTE:: This is non-functional for gem installations, due to the
182
- # nature of how it currently works. A better solution for
182
+ # nature of how it currently works. A better solution for
183
183
  # this will be provided in DBI 0.6.0.
184
184
  def collect_drivers
185
185
  drivers = { }
@@ -204,7 +204,7 @@ module DBI
204
204
  drivers = []
205
205
  collect_drivers.each do |key, value|
206
206
  drivers.push("dbi:#{key}:")
207
- end
207
+ end
208
208
  return drivers
209
209
  end
210
210
 
@@ -252,7 +252,7 @@ module DBI
252
252
  rescue LoadError => e1
253
253
  # see if you can find it in the path
254
254
  unless @@caseless_driver_name_map
255
- @@caseless_driver_name_map = { }
255
+ @@caseless_driver_name_map = { }
256
256
  collect_drivers.each do |key, value|
257
257
  @@caseless_driver_name_map[key.downcase] = value
258
258
  end
@@ -306,17 +306,13 @@ module DBI
306
306
  # FIXME trace
307
307
  # drh.trace(@@trace_mode, @@trace_output)
308
308
  @@driver_map[driver_name] = [drh, dbd_dr]
309
- return driver_name
309
+ return driver_name
310
310
  else
311
311
  return driver_name
312
312
  end
313
313
  end
314
314
  rescue LoadError, NameError
315
- if $SAFE >= 1
316
- raise InterfaceError, "Could not load driver (#{$!.message}). Note that in SAFE mode >= 1, driver URLs have to be case sensitive!"
317
- else
318
- raise InterfaceError, "Could not load driver (#{$!.message})"
319
- end
315
+ raise InterfaceError, "Could not load driver (#{$!.message})"
320
316
  end
321
317
 
322
318
  # Splits a DBI URL into two components - the database driver name
@@ -327,7 +323,7 @@ module DBI
327
323
  # the proper format for the URL. If it isn't correct, an Interface
328
324
  # error is raised.
329
325
  def parse_url(driver_url)
330
- if driver_url =~ /^(DBI|dbi):([^:]+)(:(.*))$/
326
+ if driver_url =~ /^(DBI|dbi):([^:]+)(:(.*))$/
331
327
  [$2, $4]
332
328
  else
333
329
  raise InterfaceError, "Invalid Data Source Name"
@@ -1,3 +1,7 @@
1
+ require 'deprecated'
2
+ require 'dbi/types'
3
+ require 'dbi/binary'
4
+ require 'dbi/typeutil'
1
5
  module DBI
2
6
  #
3
7
  # Represents a Date.
@@ -30,7 +34,7 @@ module DBI
30
34
  sprintf("%04d-%02d-%02d", @year, @month, @day)
31
35
  end
32
36
 
33
- private
37
+ private
34
38
 
35
39
  # DBI::Date.new(year = 0, month = 0, day = 0)
36
40
  # DBI::Date.new(Date)
@@ -42,10 +46,10 @@ module DBI
42
46
  def initialize(year=0, month=0, day=0)
43
47
  case year
44
48
  when ::Date
45
- @year, @month, @day = year.year, year.month, year.day
49
+ @year, @month, @day = year.year, year.month, year.day
46
50
  @original_date = year
47
51
  when ::Time
48
- @year, @month, @day = year.year, year.month, year.day
52
+ @year, @month, @day = year.year, year.month, year.day
49
53
  @original_time = year
50
54
  else
51
55
  @year, @month, @day = year, month, day
@@ -1,3 +1,3 @@
1
1
  module DBI
2
- VERSION = "0.5.6"
2
+ VERSION = "0.5.7"
3
3
  end
@@ -0,0 +1,237 @@
1
+ require 'date'
2
+ require 'time'
3
+
4
+ #
5
+ # General example, these would be types that would exist in DBI proper
6
+ #
7
+
8
+ module DBI
9
+ class DBI::Type
10
+ def self.parse(obj, type=nil, dbh=nil)
11
+ if type
12
+ sym = ( "to_" + type.to_s ).to_sym
13
+ begin
14
+ return self.__send__(sym, obj)
15
+ rescue ::NoMethodError
16
+ self.to_type(obj)
17
+ end
18
+ else
19
+ return self.to_type(obj)
20
+ end
21
+ end
22
+
23
+ def self.coerce(obj, type=nil, dbh=nil)
24
+ if type
25
+ sym = ( "from_" + type.to_s ).to_sym
26
+ begin
27
+ return self.__send__(sym, obj)
28
+ rescue ::NoMethodError
29
+ self.from_type(obj)
30
+ end
31
+ else
32
+ return self.from_type(obj)
33
+ end
34
+ end
35
+
36
+ def self.from_type(obj)
37
+ obj.to_s rescue obj.to_str rescue obj
38
+ end
39
+
40
+ def self.to_type(obj)
41
+ obj
42
+ end
43
+ end
44
+
45
+ class DBI::Type::Null < DBI::Type
46
+ def self.to_type(obj)
47
+ return obj unless obj
48
+ return nil if obj.to_s.match(/^null$/i)
49
+ return obj
50
+ end
51
+
52
+ def self.from_type(obj)
53
+ obj
54
+ end
55
+ end
56
+
57
+ class DBI::Type::Integer < DBI::Type::Null
58
+ def self.parse(obj)
59
+ obj = super
60
+ return obj unless obj
61
+ return obj.to_i if obj.respond_to? :to_i
62
+ return obj
63
+ end
64
+ end
65
+
66
+ class DBI::Type::Timestamp < DBI::Type::Null
67
+ def self.to_type(obj)
68
+ obj = super
69
+ return obj unless obj
70
+
71
+ case obj
72
+ when ::DateTime
73
+ return obj
74
+ when ::Date
75
+ return ::DateTime.strptime(obj.to_s, "%Y-%m-%d")
76
+ when ::Time
77
+ return ::DateTime.parse(obj.to_s)
78
+ when ::Integer
79
+ return ::DateTime.parse(::Time.at(obj).to_s)
80
+ else
81
+ return ::DateTime.parse(obj.to_s) if obj.respond_to? :to_s
82
+ return ::DateTime.parse(obj.to_str) if obj.respond_to? :to_str
83
+ return obj
84
+ end
85
+ end
86
+
87
+ def self.from_type(obj)
88
+ obj = super
89
+ return obj unless obj
90
+
91
+ case obj
92
+ when ::DateTime
93
+ return obj.to_s # produces ISO8601
94
+ when ::Time
95
+ return obj.iso8601
96
+ when ::Integer
97
+ return ::Time.at(obj).iso8601
98
+ else
99
+ return obj
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ module DBI::DBD
106
+ class Pg
107
+
108
+ #
109
+ # during connect time, after DatabaseHandle initialization, the hash
110
+ # that DatabaseHandle#type_map provides would be tweaked to take
111
+ # advantage of the available date formats.
112
+ #
113
+ # See 'PgDatabaseHandle' below for a mock.
114
+ #
115
+
116
+ class Type
117
+ class Timestamp < DBI::Type::Timestamp
118
+ def self.from_dmy(obj)
119
+ return obj if DBI::Type::Null.parse(obj).nil?
120
+
121
+ case obj
122
+ when ::DateTime, ::Time
123
+ obj.strftime("%d/%m/%Y %H:%M:%S")
124
+ when ::Integer
125
+ ::Time.at(obj).strftime("%d/%m/%Y %H:%M:%S")
126
+ else
127
+ # punt... this will actually try the baseline
128
+ # conversion at this point
129
+ raise "Crap!"
130
+ end
131
+ end
132
+
133
+ def self.to_dmy(obj)
134
+ return obj if DBI::Type::Null.parse(obj).nil?
135
+
136
+ # realistically all there needs to be is a check for the
137
+ # type ruby-pg typically returns and string, but to be
138
+ # complete I'm showing how it could be done if the type was
139
+ # less clear.
140
+
141
+ case obj
142
+ when ::DateTime
143
+ return obj
144
+ when ::Time
145
+ return ::DateTime.parse(obj.to_s)
146
+ else
147
+ return ::DateTime.strptime(obj, "%d/%m/%Y %H:%M:%S")
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ #
156
+ # this is just used to emulate the methods a DatabaseHandle would have to
157
+ # faciliate this.. certainly not a full (or correct) mirroring of the DBI API.
158
+ #
159
+ class DatabaseHandle
160
+
161
+ attr_accessor :columns
162
+
163
+ def outbound_type_map
164
+ {
165
+ 'timestamp' => [DBI::Type::Timestamp]
166
+ }
167
+ end
168
+
169
+ def inbound_type_map
170
+ {
171
+ ::DateTime => [DBI::Type::Timestamp],
172
+ ::Time => [DBI::Type::Timestamp]
173
+ }
174
+ end
175
+
176
+ # humor me while I completely break DBI for the sake of brevity..
177
+ def execute(*bindvars)
178
+ bindvars.collect do |var|
179
+ type_info = inbound_type_map[var.class]
180
+ type_info[0].coerce(var, type_info[1], self)
181
+ end
182
+ end
183
+
184
+ def fetch(*bindvars)
185
+ ret = []
186
+
187
+ bindvars.each_with_index do |var, i|
188
+ type_info = outbound_type_map[columns[i]]
189
+ ret.push type_info[0].parse(var, type_info[1], self)
190
+ end
191
+
192
+ return ret
193
+ end
194
+ end
195
+
196
+ class PgDatabaseHandle < DatabaseHandle
197
+ def outbound_type_map
198
+ {
199
+ 'timestamp' => [DBI::DBD::Pg::Type::Timestamp, :dmy]
200
+ }
201
+ end
202
+
203
+ def inbound_type_map
204
+ {
205
+ ::DateTime => [DBI::DBD::Pg::Type::Timestamp, :dmy],
206
+ ::Time => [DBI::DBD::Pg::Type::Timestamp, :dmy]
207
+ }
208
+ end
209
+ end
210
+
211
+ # ok! now for the functional example:
212
+
213
+ if __FILE__ == $0
214
+
215
+ dbh = DatabaseHandle.new
216
+ dbh.columns = %w(timestamp timestamp)
217
+ # this would go TO the database..
218
+ p dbh.execute(DateTime.now, Time.now)
219
+ # this would come FROM the database...
220
+ p dbh.fetch(Time.now.iso8601, DateTime.now.to_s)
221
+
222
+ # now the Pg example:
223
+ dbh = PgDatabaseHandle.new
224
+ dbh.columns = %w(timestamp timestamp)
225
+
226
+ # this would go TO the database..
227
+ p dbh.execute(DateTime.now, Time.now)
228
+ # this would come FROM the database...
229
+ p dbh.fetch(Time.now.strftime("%d/%m/%Y %H:%M:%S"), DateTime.now.strftime("%d/%m/%Y %H:%M:%S"))
230
+
231
+ # this should fail appropriately
232
+ begin
233
+ dbh.fetch(Time.now.iso8601, DateTime.now.to_s)
234
+ rescue Exception
235
+ puts "this failed like it should"
236
+ end
237
+ end
@@ -0,0 +1,1585 @@
1
+ #
2
+ # setup.rb
3
+ #
4
+ # Copyright (c) 2000-2005 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
+ unless Errno.const_defined?(:ENOTEMPTY) # Windows?
26
+ module Errno
27
+ class ENOTEMPTY
28
+ # We do not raise this exception, implementation is not needed.
29
+ end
30
+ end
31
+ end
32
+
33
+ def File.binread(fname)
34
+ open(fname, 'rb') {|f|
35
+ return f.read
36
+ }
37
+ end
38
+
39
+ # for corrupted Windows' stat(2)
40
+ def File.dir?(path)
41
+ File.directory?((path[-1,1] == '/') ? path : path + '/')
42
+ end
43
+
44
+
45
+ class ConfigTable
46
+
47
+ include Enumerable
48
+
49
+ def initialize(rbconfig)
50
+ @rbconfig = rbconfig
51
+ @items = []
52
+ @table = {}
53
+ # options
54
+ @install_prefix = nil
55
+ @config_opt = nil
56
+ @verbose = true
57
+ @no_harm = false
58
+ end
59
+
60
+ attr_accessor :install_prefix
61
+ attr_accessor :config_opt
62
+
63
+ attr_writer :verbose
64
+
65
+ def verbose?
66
+ @verbose
67
+ end
68
+
69
+ attr_writer :no_harm
70
+
71
+ def no_harm?
72
+ @no_harm
73
+ end
74
+
75
+ def [](key)
76
+ lookup(key).resolve(self)
77
+ end
78
+
79
+ def []=(key, val)
80
+ lookup(key).set val
81
+ end
82
+
83
+ def names
84
+ @items.map {|i| i.name }
85
+ end
86
+
87
+ def each(&block)
88
+ @items.each(&block)
89
+ end
90
+
91
+ def key?(name)
92
+ @table.key?(name)
93
+ end
94
+
95
+ def lookup(name)
96
+ @table[name] or setup_rb_error "no such config item: #{name}"
97
+ end
98
+
99
+ def add(item)
100
+ @items.push item
101
+ @table[item.name] = item
102
+ end
103
+
104
+ def remove(name)
105
+ item = lookup(name)
106
+ @items.delete_if {|i| i.name == name }
107
+ @table.delete_if {|name, i| i.name == name }
108
+ item
109
+ end
110
+
111
+ def load_script(path, inst = nil)
112
+ if File.file?(path)
113
+ MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path
114
+ end
115
+ end
116
+
117
+ def savefile
118
+ '.config'
119
+ end
120
+
121
+ def load_savefile
122
+ begin
123
+ File.foreach(savefile()) do |line|
124
+ k, v = *line.split(/=/, 2)
125
+ self[k] = v.strip
126
+ end
127
+ rescue Errno::ENOENT
128
+ setup_rb_error $!.message + "\n#{File.basename($0)} config first"
129
+ end
130
+ end
131
+
132
+ def save
133
+ @items.each {|i| i.value }
134
+ File.open(savefile(), 'w') {|f|
135
+ @items.each do |i|
136
+ f.printf "%s=%s\n", i.name, i.value if i.value? and i.value
137
+ end
138
+ }
139
+ end
140
+
141
+ def load_standard_entries
142
+ standard_entries(@rbconfig).each do |ent|
143
+ add ent
144
+ end
145
+ end
146
+
147
+ def standard_entries(rbconfig)
148
+ c = rbconfig
149
+
150
+ rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT'])
151
+
152
+ major = c['MAJOR'].to_i
153
+ minor = c['MINOR'].to_i
154
+ teeny = c['TEENY'].to_i
155
+ version = "#{major}.#{minor}"
156
+
157
+ # ruby ver. >= 1.4.4?
158
+ newpath_p = ((major >= 2) or
159
+ ((major == 1) and
160
+ ((minor >= 5) or
161
+ ((minor == 4) and (teeny >= 4)))))
162
+
163
+ if c['rubylibdir']
164
+ # V > 1.6.3
165
+ libruby = "#{c['prefix']}/lib/ruby"
166
+ librubyver = c['rubylibdir']
167
+ librubyverarch = c['archdir']
168
+ siteruby = c['sitedir']
169
+ siterubyver = c['sitelibdir']
170
+ siterubyverarch = c['sitearchdir']
171
+ elsif newpath_p
172
+ # 1.4.4 <= V <= 1.6.3
173
+ libruby = "#{c['prefix']}/lib/ruby"
174
+ librubyver = "#{c['prefix']}/lib/ruby/#{version}"
175
+ librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
176
+ siteruby = c['sitedir']
177
+ siterubyver = "$siteruby/#{version}"
178
+ siterubyverarch = "$siterubyver/#{c['arch']}"
179
+ else
180
+ # V < 1.4.4
181
+ libruby = "#{c['prefix']}/lib/ruby"
182
+ librubyver = "#{c['prefix']}/lib/ruby/#{version}"
183
+ librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
184
+ siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby"
185
+ siterubyver = siteruby
186
+ siterubyverarch = "$siterubyver/#{c['arch']}"
187
+ end
188
+ parameterize = lambda {|path|
189
+ path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')
190
+ }
191
+
192
+ if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
193
+ makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
194
+ else
195
+ makeprog = 'make'
196
+ end
197
+
198
+ [
199
+ ExecItem.new('installdirs', 'std/site/home',
200
+ 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\
201
+ {|val, table|
202
+ case val
203
+ when 'std'
204
+ table['rbdir'] = '$librubyver'
205
+ table['sodir'] = '$librubyverarch'
206
+ when 'site'
207
+ table['rbdir'] = '$siterubyver'
208
+ table['sodir'] = '$siterubyverarch'
209
+ when 'home'
210
+ setup_rb_error '$HOME was not set' unless ENV['HOME']
211
+ table['prefix'] = ENV['HOME']
212
+ table['rbdir'] = '$libdir/ruby'
213
+ table['sodir'] = '$libdir/ruby'
214
+ end
215
+ },
216
+ PathItem.new('prefix', 'path', c['prefix'],
217
+ 'path prefix of target environment'),
218
+ PathItem.new('bindir', 'path', parameterize.call(c['bindir']),
219
+ 'the directory for commands'),
220
+ PathItem.new('libdir', 'path', parameterize.call(c['libdir']),
221
+ 'the directory for libraries'),
222
+ PathItem.new('datadir', 'path', parameterize.call(c['datadir']),
223
+ 'the directory for shared data'),
224
+ PathItem.new('mandir', 'path', parameterize.call(c['mandir']),
225
+ 'the directory for man pages'),
226
+ PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']),
227
+ 'the directory for system configuration files'),
228
+ PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']),
229
+ 'the directory for local state data'),
230
+ PathItem.new('libruby', 'path', libruby,
231
+ 'the directory for ruby libraries'),
232
+ PathItem.new('librubyver', 'path', librubyver,
233
+ 'the directory for standard ruby libraries'),
234
+ PathItem.new('librubyverarch', 'path', librubyverarch,
235
+ 'the directory for standard ruby extensions'),
236
+ PathItem.new('siteruby', 'path', siteruby,
237
+ 'the directory for version-independent aux ruby libraries'),
238
+ PathItem.new('siterubyver', 'path', siterubyver,
239
+ 'the directory for aux ruby libraries'),
240
+ PathItem.new('siterubyverarch', 'path', siterubyverarch,
241
+ 'the directory for aux ruby binaries'),
242
+ PathItem.new('rbdir', 'path', '$siterubyver',
243
+ 'the directory for ruby scripts'),
244
+ PathItem.new('sodir', 'path', '$siterubyverarch',
245
+ 'the directory for ruby extentions'),
246
+ PathItem.new('rubypath', 'path', rubypath,
247
+ 'the path to set to #! line'),
248
+ ProgramItem.new('rubyprog', 'name', rubypath,
249
+ 'the ruby program using for installation'),
250
+ ProgramItem.new('makeprog', 'name', makeprog,
251
+ 'the make program to compile ruby extentions'),
252
+ SelectItem.new('shebang', 'all/ruby/never', 'ruby',
253
+ 'shebang line (#!) editing mode'),
254
+ BoolItem.new('without-ext', 'yes/no', 'no',
255
+ 'does not compile/install ruby extentions')
256
+ ]
257
+ end
258
+ private :standard_entries
259
+
260
+ def load_multipackage_entries
261
+ multipackage_entries().each do |ent|
262
+ add ent
263
+ end
264
+ end
265
+
266
+ def multipackage_entries
267
+ [
268
+ PackageSelectionItem.new('with', 'name,name...', '', 'ALL',
269
+ 'package names that you want to install'),
270
+ PackageSelectionItem.new('without', 'name,name...', '', 'NONE',
271
+ 'package names that you do not want to install')
272
+ ]
273
+ end
274
+ private :multipackage_entries
275
+
276
+ ALIASES = {
277
+ 'std-ruby' => 'librubyver',
278
+ 'stdruby' => 'librubyver',
279
+ 'rubylibdir' => 'librubyver',
280
+ 'archdir' => 'librubyverarch',
281
+ 'site-ruby-common' => 'siteruby', # For backward compatibility
282
+ 'site-ruby' => 'siterubyver', # For backward compatibility
283
+ 'bin-dir' => 'bindir',
284
+ 'bin-dir' => 'bindir',
285
+ 'rb-dir' => 'rbdir',
286
+ 'so-dir' => 'sodir',
287
+ 'data-dir' => 'datadir',
288
+ 'ruby-path' => 'rubypath',
289
+ 'ruby-prog' => 'rubyprog',
290
+ 'ruby' => 'rubyprog',
291
+ 'make-prog' => 'makeprog',
292
+ 'make' => 'makeprog'
293
+ }
294
+
295
+ def fixup
296
+ ALIASES.each do |ali, name|
297
+ @table[ali] = @table[name]
298
+ end
299
+ @items.freeze
300
+ @table.freeze
301
+ @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/
302
+ end
303
+
304
+ def parse_opt(opt)
305
+ m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}"
306
+ m.to_a[1,2]
307
+ end
308
+
309
+ def dllext
310
+ @rbconfig['DLEXT']
311
+ end
312
+
313
+ def value_config?(name)
314
+ lookup(name).value?
315
+ end
316
+
317
+ class Item
318
+ def initialize(name, template, default, desc)
319
+ @name = name.freeze
320
+ @template = template
321
+ @value = default
322
+ @default = default
323
+ @description = desc
324
+ end
325
+
326
+ attr_reader :name
327
+ attr_reader :description
328
+
329
+ attr_accessor :default
330
+ alias help_default default
331
+
332
+ def help_opt
333
+ "--#{@name}=#{@template}"
334
+ end
335
+
336
+ def value?
337
+ true
338
+ end
339
+
340
+ def value
341
+ @value
342
+ end
343
+
344
+ def resolve(table)
345
+ @value.gsub(%r<\$([^/]+)>) { table[$1] }
346
+ end
347
+
348
+ def set(val)
349
+ @value = check(val)
350
+ end
351
+
352
+ private
353
+
354
+ def check(val)
355
+ setup_rb_error "config: --#{name} requires argument" unless val
356
+ val
357
+ end
358
+ end
359
+
360
+ class BoolItem < Item
361
+ def config_type
362
+ 'bool'
363
+ end
364
+
365
+ def help_opt
366
+ "--#{@name}"
367
+ end
368
+
369
+ private
370
+
371
+ def check(val)
372
+ return 'yes' unless val
373
+ case val
374
+ when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes'
375
+ when /\An(o)?\z/i, /\Af(alse)\z/i then 'no'
376
+ else
377
+ setup_rb_error "config: --#{@name} accepts only yes/no for argument"
378
+ end
379
+ end
380
+ end
381
+
382
+ class PathItem < Item
383
+ def config_type
384
+ 'path'
385
+ end
386
+
387
+ private
388
+
389
+ def check(path)
390
+ setup_rb_error "config: --#{@name} requires argument" unless path
391
+ path[0,1] == '$' ? path : File.expand_path(path)
392
+ end
393
+ end
394
+
395
+ class ProgramItem < Item
396
+ def config_type
397
+ 'program'
398
+ end
399
+ end
400
+
401
+ class SelectItem < Item
402
+ def initialize(name, selection, default, desc)
403
+ super
404
+ @ok = selection.split('/')
405
+ end
406
+
407
+ def config_type
408
+ 'select'
409
+ end
410
+
411
+ private
412
+
413
+ def check(val)
414
+ unless @ok.include?(val.strip)
415
+ setup_rb_error "config: use --#{@name}=#{@template} (#{val})"
416
+ end
417
+ val.strip
418
+ end
419
+ end
420
+
421
+ class ExecItem < Item
422
+ def initialize(name, selection, desc, &block)
423
+ super name, selection, nil, desc
424
+ @ok = selection.split('/')
425
+ @action = block
426
+ end
427
+
428
+ def config_type
429
+ 'exec'
430
+ end
431
+
432
+ def value?
433
+ false
434
+ end
435
+
436
+ def resolve(table)
437
+ setup_rb_error "$#{name()} wrongly used as option value"
438
+ end
439
+
440
+ undef set
441
+
442
+ def evaluate(val, table)
443
+ v = val.strip.downcase
444
+ unless @ok.include?(v)
445
+ setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})"
446
+ end
447
+ @action.call v, table
448
+ end
449
+ end
450
+
451
+ class PackageSelectionItem < Item
452
+ def initialize(name, template, default, help_default, desc)
453
+ super name, template, default, desc
454
+ @help_default = help_default
455
+ end
456
+
457
+ attr_reader :help_default
458
+
459
+ def config_type
460
+ 'package'
461
+ end
462
+
463
+ private
464
+
465
+ def check(val)
466
+ unless File.dir?("packages/#{val}")
467
+ setup_rb_error "config: no such package: #{val}"
468
+ end
469
+ val
470
+ end
471
+ end
472
+
473
+ class MetaConfigEnvironment
474
+ def initialize(config, installer)
475
+ @config = config
476
+ @installer = installer
477
+ end
478
+
479
+ def config_names
480
+ @config.names
481
+ end
482
+
483
+ def config?(name)
484
+ @config.key?(name)
485
+ end
486
+
487
+ def bool_config?(name)
488
+ @config.lookup(name).config_type == 'bool'
489
+ end
490
+
491
+ def path_config?(name)
492
+ @config.lookup(name).config_type == 'path'
493
+ end
494
+
495
+ def value_config?(name)
496
+ @config.lookup(name).config_type != 'exec'
497
+ end
498
+
499
+ def add_config(item)
500
+ @config.add item
501
+ end
502
+
503
+ def add_bool_config(name, default, desc)
504
+ @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc)
505
+ end
506
+
507
+ def add_path_config(name, default, desc)
508
+ @config.add PathItem.new(name, 'path', default, desc)
509
+ end
510
+
511
+ def set_config_default(name, default)
512
+ @config.lookup(name).default = default
513
+ end
514
+
515
+ def remove_config(name)
516
+ @config.remove(name)
517
+ end
518
+
519
+ # For only multipackage
520
+ def packages
521
+ raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer
522
+ @installer.packages
523
+ end
524
+
525
+ # For only multipackage
526
+ def declare_packages(list)
527
+ raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer
528
+ @installer.packages = list
529
+ end
530
+ end
531
+
532
+ end # class ConfigTable
533
+
534
+
535
+ # This module requires: #verbose?, #no_harm?
536
+ module FileOperations
537
+
538
+ def mkdir_p(dirname, prefix = nil)
539
+ dirname = prefix + File.expand_path(dirname) if prefix
540
+ $stderr.puts "mkdir -p #{dirname}" if verbose?
541
+ return if no_harm?
542
+
543
+ # Does not check '/', it's too abnormal.
544
+ dirs = File.expand_path(dirname).split(%r<(?=/)>)
545
+ if /\A[a-z]:\z/i =~ dirs[0]
546
+ disk = dirs.shift
547
+ dirs[0] = disk + dirs[0]
548
+ end
549
+ dirs.each_index do |idx|
550
+ path = dirs[0..idx].join('')
551
+ Dir.mkdir path unless File.dir?(path)
552
+ end
553
+ end
554
+
555
+ def rm_f(path)
556
+ $stderr.puts "rm -f #{path}" if verbose?
557
+ return if no_harm?
558
+ force_remove_file path
559
+ end
560
+
561
+ def rm_rf(path)
562
+ $stderr.puts "rm -rf #{path}" if verbose?
563
+ return if no_harm?
564
+ remove_tree path
565
+ end
566
+
567
+ def remove_tree(path)
568
+ if File.symlink?(path)
569
+ remove_file path
570
+ elsif File.dir?(path)
571
+ remove_tree0 path
572
+ else
573
+ force_remove_file path
574
+ end
575
+ end
576
+
577
+ def remove_tree0(path)
578
+ Dir.foreach(path) do |ent|
579
+ next if ent == '.'
580
+ next if ent == '..'
581
+ entpath = "#{path}/#{ent}"
582
+ if File.symlink?(entpath)
583
+ remove_file entpath
584
+ elsif File.dir?(entpath)
585
+ remove_tree0 entpath
586
+ else
587
+ force_remove_file entpath
588
+ end
589
+ end
590
+ begin
591
+ Dir.rmdir path
592
+ rescue Errno::ENOTEMPTY
593
+ # directory may not be empty
594
+ end
595
+ end
596
+
597
+ def move_file(src, dest)
598
+ force_remove_file dest
599
+ begin
600
+ File.rename src, dest
601
+ rescue
602
+ File.open(dest, 'wb') {|f|
603
+ f.write File.binread(src)
604
+ }
605
+ File.chmod File.stat(src).mode, dest
606
+ File.unlink src
607
+ end
608
+ end
609
+
610
+ def force_remove_file(path)
611
+ begin
612
+ remove_file path
613
+ rescue
614
+ end
615
+ end
616
+
617
+ def remove_file(path)
618
+ File.chmod 0777, path
619
+ File.unlink path
620
+ end
621
+
622
+ def install(from, dest, mode, prefix = nil)
623
+ $stderr.puts "install #{from} #{dest}" if verbose?
624
+ return if no_harm?
625
+
626
+ realdest = prefix ? prefix + File.expand_path(dest) : dest
627
+ realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
628
+ str = File.binread(from)
629
+ if diff?(str, realdest)
630
+ verbose_off {
631
+ rm_f realdest if File.exist?(realdest)
632
+ }
633
+ File.open(realdest, 'wb') {|f|
634
+ f.write str
635
+ }
636
+ File.chmod mode, realdest
637
+
638
+ File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
639
+ if prefix
640
+ f.puts realdest.sub(prefix, '')
641
+ else
642
+ f.puts realdest
643
+ end
644
+ }
645
+ end
646
+ end
647
+
648
+ def diff?(new_content, path)
649
+ return true unless File.exist?(path)
650
+ new_content != File.binread(path)
651
+ end
652
+
653
+ def command(*args)
654
+ $stderr.puts args.join(' ') if verbose?
655
+ system(*args) or raise RuntimeError,
656
+ "system(#{args.map{|a| a.inspect }.join(' ')}) failed"
657
+ end
658
+
659
+ def ruby(*args)
660
+ command config('rubyprog'), *args
661
+ end
662
+
663
+ def make(task = nil)
664
+ command(*[config('makeprog'), task].compact)
665
+ end
666
+
667
+ def extdir?(dir)
668
+ File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb")
669
+ end
670
+
671
+ def files_of(dir)
672
+ Dir.open(dir) {|d|
673
+ return d.select {|ent| File.file?("#{dir}/#{ent}") }
674
+ }
675
+ end
676
+
677
+ DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn )
678
+
679
+ def directories_of(dir)
680
+ Dir.open(dir) {|d|
681
+ return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT
682
+ }
683
+ end
684
+
685
+ end
686
+
687
+
688
+ # This module requires: #srcdir_root, #objdir_root, #relpath
689
+ module HookScriptAPI
690
+
691
+ def get_config(key)
692
+ @config[key]
693
+ end
694
+
695
+ alias config get_config
696
+
697
+ # obsolete: use metaconfig to change configuration
698
+ def set_config(key, val)
699
+ @config[key] = val
700
+ end
701
+
702
+ #
703
+ # srcdir/objdir (works only in the package directory)
704
+ #
705
+
706
+ def curr_srcdir
707
+ "#{srcdir_root()}/#{relpath()}"
708
+ end
709
+
710
+ def curr_objdir
711
+ "#{objdir_root()}/#{relpath()}"
712
+ end
713
+
714
+ def srcfile(path)
715
+ "#{curr_srcdir()}/#{path}"
716
+ end
717
+
718
+ def srcexist?(path)
719
+ File.exist?(srcfile(path))
720
+ end
721
+
722
+ def srcdirectory?(path)
723
+ File.dir?(srcfile(path))
724
+ end
725
+
726
+ def srcfile?(path)
727
+ File.file?(srcfile(path))
728
+ end
729
+
730
+ def srcentries(path = '.')
731
+ Dir.open("#{curr_srcdir()}/#{path}") {|d|
732
+ return d.to_a - %w(. ..)
733
+ }
734
+ end
735
+
736
+ def srcfiles(path = '.')
737
+ srcentries(path).select {|fname|
738
+ File.file?(File.join(curr_srcdir(), path, fname))
739
+ }
740
+ end
741
+
742
+ def srcdirectories(path = '.')
743
+ srcentries(path).select {|fname|
744
+ File.dir?(File.join(curr_srcdir(), path, fname))
745
+ }
746
+ end
747
+
748
+ end
749
+
750
+
751
+ class ToplevelInstaller
752
+
753
+ Version = '3.4.1'
754
+ Copyright = 'Copyright (c) 2000-2005 Minero Aoki'
755
+
756
+ TASKS = [
757
+ [ 'all', 'do config, setup, then install' ],
758
+ [ 'config', 'saves your configurations' ],
759
+ [ 'show', 'shows current configuration' ],
760
+ [ 'setup', 'compiles ruby extentions and others' ],
761
+ [ 'install', 'installs files' ],
762
+ [ 'test', 'run all tests in test/' ],
763
+ [ 'clean', "does `make clean' for each extention" ],
764
+ [ 'distclean',"does `make distclean' for each extention" ]
765
+ ]
766
+
767
+ def ToplevelInstaller.invoke
768
+ config = ConfigTable.new(load_rbconfig())
769
+ config.load_standard_entries
770
+ config.load_multipackage_entries if multipackage?
771
+ config.fixup
772
+ klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller)
773
+ klass.new(File.dirname($0), config).invoke
774
+ end
775
+
776
+ def ToplevelInstaller.multipackage?
777
+ File.dir?(File.dirname($0) + '/packages')
778
+ end
779
+
780
+ def ToplevelInstaller.load_rbconfig
781
+ if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg }
782
+ ARGV.delete(arg)
783
+ load File.expand_path(arg.split(/=/, 2)[1])
784
+ $".push 'rbconfig.rb'
785
+ else
786
+ require 'rbconfig'
787
+ end
788
+ ::Config::CONFIG
789
+ end
790
+
791
+ def initialize(ardir_root, config)
792
+ @ardir = File.expand_path(ardir_root)
793
+ @config = config
794
+ # cache
795
+ @valid_task_re = nil
796
+ end
797
+
798
+ def config(key)
799
+ @config[key]
800
+ end
801
+
802
+ def inspect
803
+ "#<#{self.class} #{__id__()}>"
804
+ end
805
+
806
+ def invoke
807
+ run_metaconfigs
808
+ case task = parsearg_global()
809
+ when nil, 'all'
810
+ parsearg_config
811
+ init_installers
812
+ exec_config
813
+ exec_setup
814
+ exec_install
815
+ else
816
+ case task
817
+ when 'config', 'test'
818
+ ;
819
+ when 'clean', 'distclean'
820
+ @config.load_savefile if File.exist?(@config.savefile)
821
+ else
822
+ @config.load_savefile
823
+ end
824
+ __send__ "parsearg_#{task}"
825
+ init_installers
826
+ __send__ "exec_#{task}"
827
+ end
828
+ end
829
+
830
+ def run_metaconfigs
831
+ @config.load_script "#{@ardir}/metaconfig"
832
+ end
833
+
834
+ def init_installers
835
+ @installer = Installer.new(@config, @ardir, File.expand_path('.'))
836
+ end
837
+
838
+ #
839
+ # Hook Script API bases
840
+ #
841
+
842
+ def srcdir_root
843
+ @ardir
844
+ end
845
+
846
+ def objdir_root
847
+ '.'
848
+ end
849
+
850
+ def relpath
851
+ '.'
852
+ end
853
+
854
+ #
855
+ # Option Parsing
856
+ #
857
+
858
+ def parsearg_global
859
+ while arg = ARGV.shift
860
+ case arg
861
+ when /\A\w+\z/
862
+ setup_rb_error "invalid task: #{arg}" unless valid_task?(arg)
863
+ return arg
864
+ when '-q', '--quiet'
865
+ @config.verbose = false
866
+ when '--verbose'
867
+ @config.verbose = true
868
+ when '--help'
869
+ print_usage $stdout
870
+ exit 0
871
+ when '--version'
872
+ puts "#{File.basename($0)} version #{Version}"
873
+ exit 0
874
+ when '--copyright'
875
+ puts Copyright
876
+ exit 0
877
+ else
878
+ setup_rb_error "unknown global option '#{arg}'"
879
+ end
880
+ end
881
+ nil
882
+ end
883
+
884
+ def valid_task?(t)
885
+ valid_task_re() =~ t
886
+ end
887
+
888
+ def valid_task_re
889
+ @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/
890
+ end
891
+
892
+ def parsearg_no_options
893
+ unless ARGV.empty?
894
+ task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1)
895
+ setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}"
896
+ end
897
+ end
898
+
899
+ alias parsearg_show parsearg_no_options
900
+ alias parsearg_setup parsearg_no_options
901
+ alias parsearg_test parsearg_no_options
902
+ alias parsearg_clean parsearg_no_options
903
+ alias parsearg_distclean parsearg_no_options
904
+
905
+ def parsearg_config
906
+ evalopt = []
907
+ set = []
908
+ @config.config_opt = []
909
+ while i = ARGV.shift
910
+ if /\A--?\z/ =~ i
911
+ @config.config_opt = ARGV.dup
912
+ break
913
+ end
914
+ name, value = *@config.parse_opt(i)
915
+ if @config.value_config?(name)
916
+ @config[name] = value
917
+ else
918
+ evalopt.push [name, value]
919
+ end
920
+ set.push name
921
+ end
922
+ evalopt.each do |name, value|
923
+ @config.lookup(name).evaluate value, @config
924
+ end
925
+ # Check if configuration is valid
926
+ set.each do |n|
927
+ @config[n] if @config.value_config?(n)
928
+ end
929
+ end
930
+
931
+ def parsearg_install
932
+ @config.no_harm = false
933
+ @config.install_prefix = ''
934
+ while a = ARGV.shift
935
+ case a
936
+ when '--no-harm'
937
+ @config.no_harm = true
938
+ when /\A--prefix=/
939
+ path = a.split(/=/, 2)[1]
940
+ path = File.expand_path(path) unless path[0,1] == '/'
941
+ @config.install_prefix = path
942
+ else
943
+ setup_rb_error "install: unknown option #{a}"
944
+ end
945
+ end
946
+ end
947
+
948
+ def print_usage(out)
949
+ out.puts 'Typical Installation Procedure:'
950
+ out.puts " $ ruby #{File.basename $0} config"
951
+ out.puts " $ ruby #{File.basename $0} setup"
952
+ out.puts " # ruby #{File.basename $0} install (may require root privilege)"
953
+ out.puts
954
+ out.puts 'Detailed Usage:'
955
+ out.puts " ruby #{File.basename $0} <global option>"
956
+ out.puts " ruby #{File.basename $0} [<global options>] <task> [<task options>]"
957
+
958
+ fmt = " %-24s %s\n"
959
+ out.puts
960
+ out.puts 'Global options:'
961
+ out.printf fmt, '-q,--quiet', 'suppress message outputs'
962
+ out.printf fmt, ' --verbose', 'output messages verbosely'
963
+ out.printf fmt, ' --help', 'print this message'
964
+ out.printf fmt, ' --version', 'print version and quit'
965
+ out.printf fmt, ' --copyright', 'print copyright and quit'
966
+ out.puts
967
+ out.puts 'Tasks:'
968
+ TASKS.each do |name, desc|
969
+ out.printf fmt, name, desc
970
+ end
971
+
972
+ fmt = " %-24s %s [%s]\n"
973
+ out.puts
974
+ out.puts 'Options for CONFIG or ALL:'
975
+ @config.each do |item|
976
+ out.printf fmt, item.help_opt, item.description, item.help_default
977
+ end
978
+ out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's"
979
+ out.puts
980
+ out.puts 'Options for INSTALL:'
981
+ out.printf fmt, '--no-harm', 'only display what to do if given', 'off'
982
+ out.printf fmt, '--prefix=path', 'install path prefix', ''
983
+ out.puts
984
+ end
985
+
986
+ #
987
+ # Task Handlers
988
+ #
989
+
990
+ def exec_config
991
+ @installer.exec_config
992
+ @config.save # must be final
993
+ end
994
+
995
+ def exec_setup
996
+ @installer.exec_setup
997
+ end
998
+
999
+ def exec_install
1000
+ @installer.exec_install
1001
+ end
1002
+
1003
+ def exec_test
1004
+ @installer.exec_test
1005
+ end
1006
+
1007
+ def exec_show
1008
+ @config.each do |i|
1009
+ printf "%-20s %s\n", i.name, i.value if i.value?
1010
+ end
1011
+ end
1012
+
1013
+ def exec_clean
1014
+ @installer.exec_clean
1015
+ end
1016
+
1017
+ def exec_distclean
1018
+ @installer.exec_distclean
1019
+ end
1020
+
1021
+ end # class ToplevelInstaller
1022
+
1023
+
1024
+ class ToplevelInstallerMulti < ToplevelInstaller
1025
+
1026
+ include FileOperations
1027
+
1028
+ def initialize(ardir_root, config)
1029
+ super
1030
+ @packages = directories_of("#{@ardir}/packages")
1031
+ raise 'no package exists' if @packages.empty?
1032
+ @root_installer = Installer.new(@config, @ardir, File.expand_path('.'))
1033
+ end
1034
+
1035
+ def run_metaconfigs
1036
+ @config.load_script "#{@ardir}/metaconfig", self
1037
+ @packages.each do |name|
1038
+ @config.load_script "#{@ardir}/packages/#{name}/metaconfig"
1039
+ end
1040
+ end
1041
+
1042
+ attr_reader :packages
1043
+
1044
+ def packages=(list)
1045
+ raise 'package list is empty' if list.empty?
1046
+ list.each do |name|
1047
+ raise "directory packages/#{name} does not exist"\
1048
+ unless File.dir?("#{@ardir}/packages/#{name}")
1049
+ end
1050
+ @packages = list
1051
+ end
1052
+
1053
+ def init_installers
1054
+ @installers = {}
1055
+ @packages.each do |pack|
1056
+ @installers[pack] = Installer.new(@config,
1057
+ "#{@ardir}/packages/#{pack}",
1058
+ "packages/#{pack}")
1059
+ end
1060
+ with = extract_selection(config('with'))
1061
+ without = extract_selection(config('without'))
1062
+ @selected = @installers.keys.select {|name|
1063
+ (with.empty? or with.include?(name)) \
1064
+ and not without.include?(name)
1065
+ }
1066
+ end
1067
+
1068
+ def extract_selection(list)
1069
+ a = list.split(/,/)
1070
+ a.each do |name|
1071
+ setup_rb_error "no such package: #{name}" unless @installers.key?(name)
1072
+ end
1073
+ a
1074
+ end
1075
+
1076
+ def print_usage(f)
1077
+ super
1078
+ f.puts 'Inluded packages:'
1079
+ f.puts ' ' + @packages.sort.join(' ')
1080
+ f.puts
1081
+ end
1082
+
1083
+ #
1084
+ # Task Handlers
1085
+ #
1086
+
1087
+ def exec_config
1088
+ run_hook 'pre-config'
1089
+ each_selected_installers {|inst| inst.exec_config }
1090
+ run_hook 'post-config'
1091
+ @config.save # must be final
1092
+ end
1093
+
1094
+ def exec_setup
1095
+ run_hook 'pre-setup'
1096
+ each_selected_installers {|inst| inst.exec_setup }
1097
+ run_hook 'post-setup'
1098
+ end
1099
+
1100
+ def exec_install
1101
+ run_hook 'pre-install'
1102
+ each_selected_installers {|inst| inst.exec_install }
1103
+ run_hook 'post-install'
1104
+ end
1105
+
1106
+ def exec_test
1107
+ run_hook 'pre-test'
1108
+ each_selected_installers {|inst| inst.exec_test }
1109
+ run_hook 'post-test'
1110
+ end
1111
+
1112
+ def exec_clean
1113
+ rm_f @config.savefile
1114
+ run_hook 'pre-clean'
1115
+ each_selected_installers {|inst| inst.exec_clean }
1116
+ run_hook 'post-clean'
1117
+ end
1118
+
1119
+ def exec_distclean
1120
+ rm_f @config.savefile
1121
+ run_hook 'pre-distclean'
1122
+ each_selected_installers {|inst| inst.exec_distclean }
1123
+ run_hook 'post-distclean'
1124
+ end
1125
+
1126
+ #
1127
+ # lib
1128
+ #
1129
+
1130
+ def each_selected_installers
1131
+ Dir.mkdir 'packages' unless File.dir?('packages')
1132
+ @selected.each do |pack|
1133
+ $stderr.puts "Processing the package `#{pack}' ..." if verbose?
1134
+ Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
1135
+ Dir.chdir "packages/#{pack}"
1136
+ yield @installers[pack]
1137
+ Dir.chdir '../..'
1138
+ end
1139
+ end
1140
+
1141
+ def run_hook(id)
1142
+ @root_installer.run_hook id
1143
+ end
1144
+
1145
+ # module FileOperations requires this
1146
+ def verbose?
1147
+ @config.verbose?
1148
+ end
1149
+
1150
+ # module FileOperations requires this
1151
+ def no_harm?
1152
+ @config.no_harm?
1153
+ end
1154
+
1155
+ end # class ToplevelInstallerMulti
1156
+
1157
+
1158
+ class Installer
1159
+
1160
+ FILETYPES = %w( bin lib ext data conf man )
1161
+
1162
+ include FileOperations
1163
+ include HookScriptAPI
1164
+
1165
+ def initialize(config, srcroot, objroot)
1166
+ @config = config
1167
+ @srcdir = File.expand_path(srcroot)
1168
+ @objdir = File.expand_path(objroot)
1169
+ @currdir = '.'
1170
+ end
1171
+
1172
+ def inspect
1173
+ "#<#{self.class} #{File.basename(@srcdir)}>"
1174
+ end
1175
+
1176
+ def noop(rel)
1177
+ end
1178
+
1179
+ #
1180
+ # Hook Script API base methods
1181
+ #
1182
+
1183
+ def srcdir_root
1184
+ @srcdir
1185
+ end
1186
+
1187
+ def objdir_root
1188
+ @objdir
1189
+ end
1190
+
1191
+ def relpath
1192
+ @currdir
1193
+ end
1194
+
1195
+ #
1196
+ # Config Access
1197
+ #
1198
+
1199
+ # module FileOperations requires this
1200
+ def verbose?
1201
+ @config.verbose?
1202
+ end
1203
+
1204
+ # module FileOperations requires this
1205
+ def no_harm?
1206
+ @config.no_harm?
1207
+ end
1208
+
1209
+ def verbose_off
1210
+ begin
1211
+ save, @config.verbose = @config.verbose?, false
1212
+ yield
1213
+ ensure
1214
+ @config.verbose = save
1215
+ end
1216
+ end
1217
+
1218
+ #
1219
+ # TASK config
1220
+ #
1221
+
1222
+ def exec_config
1223
+ exec_task_traverse 'config'
1224
+ end
1225
+
1226
+ alias config_dir_bin noop
1227
+ alias config_dir_lib noop
1228
+
1229
+ def config_dir_ext(rel)
1230
+ extconf if extdir?(curr_srcdir())
1231
+ end
1232
+
1233
+ alias config_dir_data noop
1234
+ alias config_dir_conf noop
1235
+ alias config_dir_man noop
1236
+
1237
+ def extconf
1238
+ ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt
1239
+ end
1240
+
1241
+ #
1242
+ # TASK setup
1243
+ #
1244
+
1245
+ def exec_setup
1246
+ exec_task_traverse 'setup'
1247
+ end
1248
+
1249
+ def setup_dir_bin(rel)
1250
+ files_of(curr_srcdir()).each do |fname|
1251
+ update_shebang_line "#{curr_srcdir()}/#{fname}"
1252
+ end
1253
+ end
1254
+
1255
+ alias setup_dir_lib noop
1256
+
1257
+ def setup_dir_ext(rel)
1258
+ make if extdir?(curr_srcdir())
1259
+ end
1260
+
1261
+ alias setup_dir_data noop
1262
+ alias setup_dir_conf noop
1263
+ alias setup_dir_man noop
1264
+
1265
+ def update_shebang_line(path)
1266
+ return if no_harm?
1267
+ return if config('shebang') == 'never'
1268
+ old = Shebang.load(path)
1269
+ if old
1270
+ $stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1
1271
+ new = new_shebang(old)
1272
+ return if new.to_s == old.to_s
1273
+ else
1274
+ return unless config('shebang') == 'all'
1275
+ new = Shebang.new(config('rubypath'))
1276
+ end
1277
+ $stderr.puts "updating shebang: #{File.basename(path)}" if verbose?
1278
+ open_atomic_writer(path) {|output|
1279
+ File.open(path, 'rb') {|f|
1280
+ f.gets if old # discard
1281
+ output.puts new.to_s
1282
+ output.print f.read
1283
+ }
1284
+ }
1285
+ end
1286
+
1287
+ def new_shebang(old)
1288
+ if /\Aruby/ =~ File.basename(old.cmd)
1289
+ Shebang.new(config('rubypath'), old.args)
1290
+ elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby'
1291
+ Shebang.new(config('rubypath'), old.args[1..-1])
1292
+ else
1293
+ return old unless config('shebang') == 'all'
1294
+ Shebang.new(config('rubypath'))
1295
+ end
1296
+ end
1297
+
1298
+ def open_atomic_writer(path, &block)
1299
+ tmpfile = File.basename(path) + '.tmp'
1300
+ begin
1301
+ File.open(tmpfile, 'wb', &block)
1302
+ File.rename tmpfile, File.basename(path)
1303
+ ensure
1304
+ File.unlink tmpfile if File.exist?(tmpfile)
1305
+ end
1306
+ end
1307
+
1308
+ class Shebang
1309
+ def Shebang.load(path)
1310
+ line = nil
1311
+ File.open(path) {|f|
1312
+ line = f.gets
1313
+ }
1314
+ return nil unless /\A#!/ =~ line
1315
+ parse(line)
1316
+ end
1317
+
1318
+ def Shebang.parse(line)
1319
+ cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ')
1320
+ new(cmd, args)
1321
+ end
1322
+
1323
+ def initialize(cmd, args = [])
1324
+ @cmd = cmd
1325
+ @args = args
1326
+ end
1327
+
1328
+ attr_reader :cmd
1329
+ attr_reader :args
1330
+
1331
+ def to_s
1332
+ "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}")
1333
+ end
1334
+ end
1335
+
1336
+ #
1337
+ # TASK install
1338
+ #
1339
+
1340
+ def exec_install
1341
+ rm_f 'InstalledFiles'
1342
+ exec_task_traverse 'install'
1343
+ end
1344
+
1345
+ def install_dir_bin(rel)
1346
+ install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755
1347
+ end
1348
+
1349
+ def install_dir_lib(rel)
1350
+ install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644
1351
+ end
1352
+
1353
+ def install_dir_ext(rel)
1354
+ return unless extdir?(curr_srcdir())
1355
+ install_files rubyextentions('.'),
1356
+ "#{config('sodir')}/#{File.dirname(rel)}",
1357
+ 0555
1358
+ end
1359
+
1360
+ def install_dir_data(rel)
1361
+ install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644
1362
+ end
1363
+
1364
+ def install_dir_conf(rel)
1365
+ # FIXME: should not remove current config files
1366
+ # (rename previous file to .old/.org)
1367
+ install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644
1368
+ end
1369
+
1370
+ def install_dir_man(rel)
1371
+ install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644
1372
+ end
1373
+
1374
+ def install_files(list, dest, mode)
1375
+ mkdir_p dest, @config.install_prefix
1376
+ list.each do |fname|
1377
+ install fname, dest, mode, @config.install_prefix
1378
+ end
1379
+ end
1380
+
1381
+ def libfiles
1382
+ glob_reject(%w(*.y *.output), targetfiles())
1383
+ end
1384
+
1385
+ def rubyextentions(dir)
1386
+ ents = glob_select("*.#{@config.dllext}", targetfiles())
1387
+ if ents.empty?
1388
+ setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first"
1389
+ end
1390
+ ents
1391
+ end
1392
+
1393
+ def targetfiles
1394
+ mapdir(existfiles() - hookfiles())
1395
+ end
1396
+
1397
+ def mapdir(ents)
1398
+ ents.map {|ent|
1399
+ if File.exist?(ent)
1400
+ then ent # objdir
1401
+ else "#{curr_srcdir()}/#{ent}" # srcdir
1402
+ end
1403
+ }
1404
+ end
1405
+
1406
+ # picked up many entries from cvs-1.11.1/src/ignore.c
1407
+ JUNK_FILES = %w(
1408
+ core RCSLOG tags TAGS .make.state
1409
+ .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
1410
+ *~ *.old *.bak *.BAK *.orig *.rej _$* *$
1411
+
1412
+ *.org *.in .*
1413
+ )
1414
+
1415
+ def existfiles
1416
+ glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.')))
1417
+ end
1418
+
1419
+ def hookfiles
1420
+ %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
1421
+ %w( config setup install clean ).map {|t| sprintf(fmt, t) }
1422
+ }.flatten
1423
+ end
1424
+
1425
+ def glob_select(pat, ents)
1426
+ re = globs2re([pat])
1427
+ ents.select {|ent| re =~ ent }
1428
+ end
1429
+
1430
+ def glob_reject(pats, ents)
1431
+ re = globs2re(pats)
1432
+ ents.reject {|ent| re =~ ent }
1433
+ end
1434
+
1435
+ GLOB2REGEX = {
1436
+ '.' => '\.',
1437
+ '$' => '\$',
1438
+ '#' => '\#',
1439
+ '*' => '.*'
1440
+ }
1441
+
1442
+ def globs2re(pats)
1443
+ /\A(?:#{
1444
+ pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|')
1445
+ })\z/
1446
+ end
1447
+
1448
+ #
1449
+ # TASK test
1450
+ #
1451
+
1452
+ TESTDIR = 'test'
1453
+
1454
+ def exec_test
1455
+ unless File.directory?('test')
1456
+ $stderr.puts 'no test in this package' if verbose?
1457
+ return
1458
+ end
1459
+ $stderr.puts 'Running tests...' if verbose?
1460
+ begin
1461
+ require 'test/unit'
1462
+ rescue LoadError
1463
+ setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.'
1464
+ end
1465
+ runner = Test::Unit::AutoRunner.new(true)
1466
+ runner.to_run << TESTDIR
1467
+ runner.run
1468
+ end
1469
+
1470
+ #
1471
+ # TASK clean
1472
+ #
1473
+
1474
+ def exec_clean
1475
+ exec_task_traverse 'clean'
1476
+ rm_f @config.savefile
1477
+ rm_f 'InstalledFiles'
1478
+ end
1479
+
1480
+ alias clean_dir_bin noop
1481
+ alias clean_dir_lib noop
1482
+ alias clean_dir_data noop
1483
+ alias clean_dir_conf noop
1484
+ alias clean_dir_man noop
1485
+
1486
+ def clean_dir_ext(rel)
1487
+ return unless extdir?(curr_srcdir())
1488
+ make 'clean' if File.file?('Makefile')
1489
+ end
1490
+
1491
+ #
1492
+ # TASK distclean
1493
+ #
1494
+
1495
+ def exec_distclean
1496
+ exec_task_traverse 'distclean'
1497
+ rm_f @config.savefile
1498
+ rm_f 'InstalledFiles'
1499
+ end
1500
+
1501
+ alias distclean_dir_bin noop
1502
+ alias distclean_dir_lib noop
1503
+
1504
+ def distclean_dir_ext(rel)
1505
+ return unless extdir?(curr_srcdir())
1506
+ make 'distclean' if File.file?('Makefile')
1507
+ end
1508
+
1509
+ alias distclean_dir_data noop
1510
+ alias distclean_dir_conf noop
1511
+ alias distclean_dir_man noop
1512
+
1513
+ #
1514
+ # Traversing
1515
+ #
1516
+
1517
+ def exec_task_traverse(task)
1518
+ run_hook "pre-#{task}"
1519
+ FILETYPES.each do |type|
1520
+ if type == 'ext' and config('without-ext') == 'yes'
1521
+ $stderr.puts 'skipping ext/* by user option' if verbose?
1522
+ next
1523
+ end
1524
+ traverse task, type, "#{task}_dir_#{type}"
1525
+ end
1526
+ run_hook "post-#{task}"
1527
+ end
1528
+
1529
+ def traverse(task, rel, mid)
1530
+ dive_into(rel) {
1531
+ run_hook "pre-#{task}"
1532
+ __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
1533
+ directories_of(curr_srcdir()).each do |d|
1534
+ traverse task, "#{rel}/#{d}", mid
1535
+ end
1536
+ run_hook "post-#{task}"
1537
+ }
1538
+ end
1539
+
1540
+ def dive_into(rel)
1541
+ return unless File.dir?("#{@srcdir}/#{rel}")
1542
+
1543
+ dir = File.basename(rel)
1544
+ Dir.mkdir dir unless File.dir?(dir)
1545
+ prevdir = Dir.pwd
1546
+ Dir.chdir dir
1547
+ $stderr.puts '---> ' + rel if verbose?
1548
+ @currdir = rel
1549
+ yield
1550
+ Dir.chdir prevdir
1551
+ $stderr.puts '<--- ' + rel if verbose?
1552
+ @currdir = File.dirname(rel)
1553
+ end
1554
+
1555
+ def run_hook(id)
1556
+ path = [ "#{curr_srcdir()}/#{id}",
1557
+ "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) }
1558
+ return unless path
1559
+ begin
1560
+ instance_eval File.read(path), path, 1
1561
+ rescue
1562
+ raise if $DEBUG
1563
+ setup_rb_error "hook #{path} failed:\n" + $!.message
1564
+ end
1565
+ end
1566
+
1567
+ end # class Installer
1568
+
1569
+
1570
+ class SetupError < StandardError; end
1571
+
1572
+ def setup_rb_error(msg)
1573
+ raise SetupError, msg
1574
+ end
1575
+
1576
+ if $0 == __FILE__
1577
+ begin
1578
+ ToplevelInstaller.invoke
1579
+ rescue SetupError
1580
+ raise if $DEBUG
1581
+ $stderr.puts $!.message
1582
+ $stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
1583
+ exit 1
1584
+ end
1585
+ end