rmail 0.17

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 (91) hide show
  1. data/NEWS +309 -0
  2. data/NOTES +14 -0
  3. data/README +83 -0
  4. data/THANKS +25 -0
  5. data/TODO +112 -0
  6. data/guide/Intro.txt +122 -0
  7. data/guide/MIME.txt +6 -0
  8. data/guide/TableOfContents.txt +13 -0
  9. data/install.rb +1023 -0
  10. data/lib/rmail.rb +50 -0
  11. data/lib/rmail/address.rb +829 -0
  12. data/lib/rmail/header.rb +987 -0
  13. data/lib/rmail/mailbox.rb +62 -0
  14. data/lib/rmail/mailbox/mboxreader.rb +182 -0
  15. data/lib/rmail/message.rb +201 -0
  16. data/lib/rmail/parser.rb +412 -0
  17. data/lib/rmail/parser/multipart.rb +217 -0
  18. data/lib/rmail/parser/pushbackreader.rb +173 -0
  19. data/lib/rmail/serialize.rb +190 -0
  20. data/lib/rmail/utils.rb +59 -0
  21. data/rmail.gemspec +17 -0
  22. data/tests/addrgrammar.txt +113 -0
  23. data/tests/data/mbox.odd +4 -0
  24. data/tests/data/mbox.simple +8 -0
  25. data/tests/data/multipart/data.1 +5 -0
  26. data/tests/data/multipart/data.10 +1 -0
  27. data/tests/data/multipart/data.11 +9 -0
  28. data/tests/data/multipart/data.12 +9 -0
  29. data/tests/data/multipart/data.13 +3 -0
  30. data/tests/data/multipart/data.14 +3 -0
  31. data/tests/data/multipart/data.15 +3 -0
  32. data/tests/data/multipart/data.16 +3 -0
  33. data/tests/data/multipart/data.17 +0 -0
  34. data/tests/data/multipart/data.2 +5 -0
  35. data/tests/data/multipart/data.3 +2 -0
  36. data/tests/data/multipart/data.4 +3 -0
  37. data/tests/data/multipart/data.5 +1 -0
  38. data/tests/data/multipart/data.6 +2 -0
  39. data/tests/data/multipart/data.7 +3 -0
  40. data/tests/data/multipart/data.8 +5 -0
  41. data/tests/data/multipart/data.9 +4 -0
  42. data/tests/data/parser.badmime1 +4 -0
  43. data/tests/data/parser.badmime2 +6 -0
  44. data/tests/data/parser.nested-multipart +75 -0
  45. data/tests/data/parser.nested-simple +12 -0
  46. data/tests/data/parser.nested-simple2 +16 -0
  47. data/tests/data/parser.nested-simple3 +21 -0
  48. data/tests/data/parser.rfc822 +65 -0
  49. data/tests/data/parser.simple-mime +24 -0
  50. data/tests/data/parser/multipart.1 +8 -0
  51. data/tests/data/parser/multipart.10 +4 -0
  52. data/tests/data/parser/multipart.11 +12 -0
  53. data/tests/data/parser/multipart.12 +12 -0
  54. data/tests/data/parser/multipart.13 +6 -0
  55. data/tests/data/parser/multipart.14 +6 -0
  56. data/tests/data/parser/multipart.15 +6 -0
  57. data/tests/data/parser/multipart.16 +6 -0
  58. data/tests/data/parser/multipart.2 +8 -0
  59. data/tests/data/parser/multipart.3 +5 -0
  60. data/tests/data/parser/multipart.4 +6 -0
  61. data/tests/data/parser/multipart.5 +4 -0
  62. data/tests/data/parser/multipart.6 +5 -0
  63. data/tests/data/parser/multipart.7 +6 -0
  64. data/tests/data/parser/multipart.8 +8 -0
  65. data/tests/data/parser/multipart.9 +7 -0
  66. data/tests/data/transparency/absolute.1 +5 -0
  67. data/tests/data/transparency/absolute.2 +1 -0
  68. data/tests/data/transparency/absolute.3 +2 -0
  69. data/tests/data/transparency/absolute.4 +3 -0
  70. data/tests/data/transparency/absolute.5 +4 -0
  71. data/tests/data/transparency/absolute.6 +49 -0
  72. data/tests/data/transparency/message.1 +73 -0
  73. data/tests/data/transparency/message.2 +34 -0
  74. data/tests/data/transparency/message.3 +63 -0
  75. data/tests/data/transparency/message.4 +5 -0
  76. data/tests/data/transparency/message.5 +15 -0
  77. data/tests/data/transparency/message.6 +1185 -0
  78. data/tests/runtests.rb +35 -0
  79. data/tests/testaddress.rb +1192 -0
  80. data/tests/testbase.rb +207 -0
  81. data/tests/testheader.rb +1207 -0
  82. data/tests/testmailbox.rb +47 -0
  83. data/tests/testmboxreader.rb +161 -0
  84. data/tests/testmessage.rb +257 -0
  85. data/tests/testparser.rb +634 -0
  86. data/tests/testparsermultipart.rb +205 -0
  87. data/tests/testpushbackreader.rb +40 -0
  88. data/tests/testserialize.rb +264 -0
  89. data/tests/testtestbase.rb +112 -0
  90. data/tests/testtranspparency.rb +105 -0
  91. metadata +143 -0
@@ -0,0 +1,122 @@
1
+ = Introduction to RubyMail
2
+
3
+ This will get you started with the basics of using RubyMail.
4
+
5
+ == Turning Text into a Message Object
6
+
7
+ message = RMail::Parser.read(input)
8
+
9
+ The input can be a normal Ruby IO object or a string. The result is a
10
+ RMail::Message object. See RMail::Parser for more information.
11
+
12
+ == Turning a Message Object into Text
13
+
14
+ File.open('my-message', 'w') { |f|
15
+ RMail::Serialize.write(f, message)
16
+ }
17
+
18
+ or
19
+
20
+ string = RMail::Serialize.write('', message)
21
+
22
+ See RMail::Serialize for more information.
23
+
24
+ A convenient shortcut when you want the message as a string is
25
+ RMail::Message#to_s.
26
+
27
+ string = message.to_s
28
+
29
+ You can also use RMail::Message#each to process each line of the
30
+ serialized message in turn.
31
+
32
+ message.each { |line|
33
+ puts line
34
+ }
35
+
36
+ == Manipulating a Message
37
+
38
+ You use the methods of the RMail::Message and RMail::Header classes to
39
+ manipulate the message.
40
+
41
+ === Retrieve the Body
42
+
43
+ You can retrieve the text of a single part message with
44
+ RMail::Message#body.
45
+
46
+ body = message.body
47
+
48
+ But beware that if the message is a MIME multipart message, +body+
49
+ will be an Array of RMail::Message objects. If you know you have a
50
+ MIME multipart message (easily tested with RMail::Message#multipart?),
51
+ then you can retrieve them individually with RMail::Message#part and
52
+ RMail::Message#each_part.
53
+
54
+ first_part = message.part(0)
55
+
56
+ message.each_part { |part|
57
+ # do something with part
58
+ }
59
+
60
+ See guide/MIME.txt for more tips on dealing with MIME messages.
61
+
62
+ === Manipulate the Message Headers
63
+
64
+ The RMail::Message#header method retrieves the RMail::Header object
65
+ that every message contains. You can then use RMail::Header methods
66
+ to manipulate the message's header.
67
+
68
+ People often confuse a "message header" with "header fields." In
69
+ RubyMail a "header" always means the entire header of the message,
70
+ while "field" means an individual header field (e.g. "Subject").
71
+
72
+ To append new fields, simply access assign to them like an array:
73
+
74
+ message.header['To'] = 'bob@example.net'
75
+ message.header['From'] = 'sally@example.net'
76
+
77
+ Note that the above will <em>always</em> append a new header, so you
78
+ must delete existing headers if you want only one.
79
+
80
+ To retrieve fields, you can access the header like an array. This
81
+ will return the field value.
82
+
83
+ subject = message.header['Subject']
84
+
85
+ Of course, a header may have several fields with the same name. To
86
+ retrieve all the values you can use RMail::Header.match.
87
+
88
+ destinations = message.header.match(/^(to|cc|bcc)$/, nil)
89
+
90
+ See the RMail::Header documentation for many other useful functions.
91
+
92
+ == Dealing with Email Addresses
93
+
94
+ The address syntax for Internet email messages (as specified in
95
+ RFC2822) is complex. RubyMail contains the RMail::Address class that
96
+ can be used to parse and generate these addresses in a robust way.
97
+ For example, to retrieve all destination addresses from a
98
+ RMail::Header object:
99
+
100
+ recipients = RMail::Address.parse(header.match(/^(to|cc)/, nil))
101
+
102
+ Then print out just the address portion:
103
+
104
+ recipients.each { |r|
105
+ r.address
106
+ }
107
+
108
+ When creating an address from scratch, you typically do this:
109
+
110
+ address = RMail::Address.new("Bob Smith <bob@example.net>")
111
+
112
+ Then to get the text form of the address form back:
113
+
114
+ address.format
115
+
116
+ TODO: addresses can be keys of a hash, sorted, etc...
117
+
118
+ == More Topics
119
+
120
+ This is just the beginning. See guide/TableOfContents.txt for a list
121
+ of other things covered in this guide.
122
+
@@ -0,0 +1,6 @@
1
+ = MIME Support in RubyMail
2
+
3
+ FIXME: write this.
4
+ TODO: show how to add, delete, decode and encode parts.
5
+
6
+ http://rwiki.moonwolf.com/rw-cgi.cgi?cmd=view;name=RubyMail
@@ -0,0 +1,13 @@
1
+ = RubyMail Guide Table of Contents
2
+
3
+ [guide/Intro.txt] Brief introduction to RubyMail.
4
+
5
+ [guide/MIME.txt] How to perform common operations on MIME messages.
6
+
7
+ [guide/SMTP.txt] How to send a RubyMail message out using the standard
8
+ Ruby net/smtp.rb package.
9
+
10
+ [guide/POP.txt] How to parse a RubyMail message directly from a POP
11
+ server using the standard net/pop.rb package.
12
+
13
+ [guide/Mailbox.txt] How to read mailboxes of various formats.
@@ -0,0 +1,1023 @@
1
+ #
2
+ # This file is automatically generated. DO NOT MODIFY!
3
+ #
4
+ # install.rb
5
+ #
6
+ # Copyright (c) 2000-2002 Minero Aoki <aamine@loveruby.net>
7
+ #
8
+ # This program is free software.
9
+ # You can distribute/modify this program under the terms of
10
+ # the GNU Lesser General Public License version 2.
11
+ #
12
+
13
+ ### begin compat.rb
14
+
15
+ unless Enumerable.instance_methods.include? 'inject'
16
+ module Enumerable
17
+ def inject( result )
18
+ each do |i|
19
+ result = yield(result, i)
20
+ end
21
+ result
22
+ end
23
+ end
24
+ end
25
+
26
+ def File.read_all( fname )
27
+ File.open(fname, 'rb') {|f| return f.read }
28
+ end
29
+
30
+ def File.write( fname, str )
31
+ File.open(fname, 'wb') {|f| f.write str }
32
+ end
33
+
34
+ ### end compat.rb
35
+ ### begin config.rb
36
+
37
+ if i = ARGV.index(/\A--rbconfig=/)
38
+ file = $'
39
+ ARGV.delete_at(i)
40
+ require file
41
+ else
42
+ require 'rbconfig'
43
+ end
44
+
45
+
46
+ class ConfigTable
47
+
48
+ c = ::Config::CONFIG
49
+
50
+ rubypath = c['bindir'] + '/' + c['ruby_install_name']
51
+
52
+ major = c['MAJOR'].to_i
53
+ minor = c['MINOR'].to_i
54
+ teeny = c['TEENY'].to_i
55
+ version = "#{major}.#{minor}"
56
+
57
+ # ruby ver. >= 1.4.4?
58
+ newpath_p = ((major >= 2) or
59
+ ((major == 1) and
60
+ ((minor >= 5) or
61
+ ((minor == 4) and (teeny >= 4)))))
62
+
63
+ re = Regexp.new('\A' + Regexp.quote(c['prefix']))
64
+ subprefix = lambda {|path|
65
+ re === path and path.sub(re, '$prefix')
66
+ }
67
+
68
+ if c['rubylibdir']
69
+ # V < 1.6.3
70
+ stdruby = subprefix.call(c['rubylibdir'])
71
+ siteruby = subprefix.call(c['sitedir'])
72
+ versite = subprefix.call(c['sitelibdir'])
73
+ sodir = subprefix.call(c['sitearchdir'])
74
+ elsif newpath_p
75
+ # 1.4.4 <= V <= 1.6.3
76
+ stdruby = "$prefix/lib/ruby/#{version}"
77
+ siteruby = subprefix.call(c['sitedir'])
78
+ versite = siteruby + '/' + version
79
+ sodir = "$site-ruby/#{c['arch']}"
80
+ else
81
+ # V < 1.4.4
82
+ stdruby = "$prefix/lib/ruby/#{version}"
83
+ siteruby = "$prefix/lib/ruby/#{version}/site_ruby"
84
+ versite = siteruby
85
+ sodir = "$site-ruby/#{c['arch']}"
86
+ end
87
+
88
+ DESCRIPTER = [
89
+ [ 'prefix', [ c['prefix'],
90
+ 'path',
91
+ 'path prefix of target environment' ] ],
92
+ [ 'std-ruby', [ stdruby,
93
+ 'path',
94
+ 'the directory for standard ruby libraries' ] ],
95
+ [ 'site-ruby-common', [ siteruby,
96
+ 'path',
97
+ 'the directory for version-independent non-standard ruby libraries' ] ],
98
+ [ 'site-ruby', [ versite,
99
+ 'path',
100
+ 'the directory for non-standard ruby libraries' ] ],
101
+ [ 'bin-dir', [ '$prefix/bin',
102
+ 'path',
103
+ 'the directory for commands' ] ],
104
+ [ 'rb-dir', [ '$site-ruby',
105
+ 'path',
106
+ 'the directory for ruby scripts' ] ],
107
+ [ 'so-dir', [ sodir,
108
+ 'path',
109
+ 'the directory for ruby extentions' ] ],
110
+ [ 'data-dir', [ '$prefix/share',
111
+ 'path',
112
+ 'the directory for shared data' ] ],
113
+ [ 'ruby-path', [ rubypath,
114
+ 'path',
115
+ 'path to set to #! line' ] ],
116
+ [ 'ruby-prog', [ rubypath,
117
+ 'name',
118
+ 'the ruby program using for installation' ] ],
119
+ [ 'make-prog', [ 'make',
120
+ 'name',
121
+ 'the make program to compile ruby extentions' ] ],
122
+ [ 'without-ext', [ 'no',
123
+ 'yes/no',
124
+ 'does not compile/install ruby extentions' ] ]
125
+ ]
126
+
127
+ SAVE_FILE = 'config.save'
128
+
129
+ def ConfigTable.each_name( &block )
130
+ keys().each(&block)
131
+ end
132
+
133
+ def ConfigTable.keys
134
+ DESCRIPTER.collect {|k,*dummy| k }
135
+ end
136
+
137
+ def ConfigTable.each_definition( &block )
138
+ DESCRIPTER.each(&block)
139
+ end
140
+
141
+ def ConfigTable.get_entry( name )
142
+ name, ent = DESCRIPTER.assoc(name)
143
+ ent
144
+ end
145
+
146
+ def ConfigTable.get_entry!( name )
147
+ get_entry(name) or raise ArgumentError, "no such config: #{name}"
148
+ end
149
+
150
+ def ConfigTable.add_entry( name, vals )
151
+ ConfigTable::DESCRIPTER.push [name,vals]
152
+ end
153
+
154
+ def ConfigTable.remove_entry( name )
155
+ get_entry name or raise ArgumentError, "no such config: #{name}"
156
+ DESCRIPTER.delete_if {|n,arr| n == name }
157
+ end
158
+
159
+ def ConfigTable.config_key?( name )
160
+ get_entry(name) ? true : false
161
+ end
162
+
163
+ def ConfigTable.bool_config?( name )
164
+ ent = get_entry(name) or return false
165
+ ent[1] == 'yes/no'
166
+ end
167
+
168
+ def ConfigTable.value_config?( name )
169
+ ent = get_entry(name) or return false
170
+ ent[1] != 'yes/no'
171
+ end
172
+
173
+ def ConfigTable.path_config?( name )
174
+ ent = get_entry(name) or return false
175
+ ent[1] == 'path'
176
+ end
177
+
178
+
179
+ class << self
180
+
181
+ alias newobj new
182
+
183
+ def new
184
+ c = newobj()
185
+ c.__send__ :init
186
+ c
187
+ end
188
+
189
+ def load
190
+ c = newobj()
191
+ raise InstallError, "#{File.basename $0} config first"\
192
+ unless File.file? SAVE_FILE
193
+ File.foreach(SAVE_FILE) do |line|
194
+ k, v = line.split(/=/, 2)
195
+ c.instance_eval {
196
+ @table[k] = v.strip
197
+ }
198
+ end
199
+ c
200
+ end
201
+
202
+ end
203
+
204
+ def initialize
205
+ @table = {}
206
+ end
207
+
208
+ def init
209
+ DESCRIPTER.each do |k, (default, vname, desc, default2)|
210
+ @table[k] = default
211
+ end
212
+ end
213
+ private :init
214
+
215
+ def save
216
+ File.open(SAVE_FILE, 'w') {|f|
217
+ @table.each do |k, v|
218
+ f.printf "%s=%s\n", k, v if v
219
+ end
220
+ }
221
+ end
222
+
223
+ def []=( k, v )
224
+ ConfigTable.config_key? k or raise InstallError, "unknown config option #{k}"
225
+ if ConfigTable.path_config? k
226
+ @table[k] = (v[0,1] != '$') ? File.expand_path(v) : v
227
+ else
228
+ @table[k] = v
229
+ end
230
+ end
231
+
232
+ def []( key )
233
+ @table[key] or return nil
234
+ @table[key].gsub(%r<\$([^/]+)>) { self[$1] }
235
+ end
236
+
237
+ def set_raw( key, val )
238
+ @table[key] = val
239
+ end
240
+
241
+ def get_raw( key )
242
+ @table[key]
243
+ end
244
+
245
+ end
246
+
247
+
248
+ class MetaConfigEnvironment
249
+
250
+ def self.eval_file( file )
251
+ return unless File.file? file
252
+ new.instance_eval File.read_all(file), file, 1
253
+ end
254
+
255
+ private
256
+
257
+ def config_names
258
+ ConfigTable.keys
259
+ end
260
+
261
+ def config?( name )
262
+ ConfigTable.config_key? name
263
+ end
264
+
265
+ def bool_config?( name )
266
+ ConfigTable.bool_config? name
267
+ end
268
+
269
+ def value_config?( name )
270
+ ConfigTable.value_config? name
271
+ end
272
+
273
+ def path_config?( name )
274
+ ConfigTable.path_config? name
275
+ end
276
+
277
+ def add_config( name, argname, default, desc )
278
+ ConfigTable.add_entry name,[default,argname,desc]
279
+ end
280
+
281
+ def add_path_config( name, default, desc )
282
+ add_config name, 'path', default, desc
283
+ end
284
+
285
+ def add_bool_config( name, default, desc )
286
+ add_config name, 'yes/no', default ? 'yes' : 'no', desc
287
+ end
288
+
289
+ def set_config_default( name, default )
290
+ if bool_config? name
291
+ ConfigTable.get_entry!(name)[0] = default ? 'yes' : 'no'
292
+ else
293
+ ConfigTable.get_entry!(name)[0] = default
294
+ end
295
+ end
296
+
297
+ def remove_config( name )
298
+ ent = ConfigTable.get_entry(name)
299
+ ConfigTable.remove_entry name
300
+ ent
301
+ end
302
+
303
+ end
304
+
305
+ ### end config.rb
306
+ ### begin fileop.rb
307
+
308
+ module FileOperations
309
+
310
+ def mkdir_p( dname, prefix = nil )
311
+ dname = prefix + dname if prefix
312
+ $stderr.puts "mkdir -p #{dname}" if verbose?
313
+ return if no_harm?
314
+
315
+ # does not check '/'... it's too abnormal case
316
+ dirs = dname.split(%r<(?=/)>)
317
+ if /\A[a-z]:\z/i === dirs[0]
318
+ disk = dirs.shift
319
+ dirs[0] = disk + dirs[0]
320
+ end
321
+ dirs.each_index do |idx|
322
+ path = dirs[0..idx].join('')
323
+ Dir.mkdir path unless dir? path
324
+ end
325
+ end
326
+
327
+ def rm_f( fname )
328
+ $stderr.puts "rm -f #{fname}" if verbose?
329
+ return if no_harm?
330
+
331
+ if File.exist? fname or File.symlink? fname
332
+ File.chmod 0777, fname
333
+ File.unlink fname
334
+ end
335
+ end
336
+
337
+ def rm_rf( dn )
338
+ $stderr.puts "rm -rf #{dn}" if verbose?
339
+ return if no_harm?
340
+
341
+ Dir.chdir dn
342
+ Dir.foreach('.') do |fn|
343
+ next if fn == '.'
344
+ next if fn == '..'
345
+ if dir? fn
346
+ verbose_off {
347
+ rm_rf fn
348
+ }
349
+ else
350
+ verbose_off {
351
+ rm_f fn
352
+ }
353
+ end
354
+ end
355
+ Dir.chdir '..'
356
+ Dir.rmdir dn
357
+ end
358
+
359
+ def mv( src, dest )
360
+ rm_f dest
361
+ begin
362
+ File.link src, dest
363
+ rescue
364
+ File.write dest, File.read_all(src)
365
+ File.chmod File.stat(src).mode, dest
366
+ end
367
+ rm_f src
368
+ end
369
+
370
+ def install( from, dest, mode, prefix = nil )
371
+ $stderr.puts "install #{from} #{dest}" if verbose?
372
+ return if no_harm?
373
+
374
+ realdest = prefix + dest if prefix
375
+ if dir? realdest
376
+ realdest += '/' + File.basename(from)
377
+ end
378
+ str = File.read_all(from)
379
+ if diff? str, realdest
380
+ verbose_off {
381
+ rm_f realdest if File.exist? realdest
382
+ }
383
+ File.write realdest, str
384
+ File.chmod mode, realdest
385
+
386
+ File.open(objdir + '/InstalledFiles', 'a') {|f| f.puts realdest }
387
+ end
388
+ end
389
+
390
+ def diff?( orig, targ )
391
+ return true unless File.exist? targ
392
+ orig != File.read_all(targ)
393
+ end
394
+
395
+ def command( str )
396
+ $stderr.puts str if verbose?
397
+ system str or raise RuntimeError, "'system #{str}' failed"
398
+ end
399
+
400
+ def ruby( str )
401
+ command config('ruby-prog') + ' ' + str
402
+ end
403
+
404
+ def dir?( dname )
405
+ # for corrupted windows stat()
406
+ File.directory?((dname[-1,1] == '/') ? dname : dname + '/')
407
+ end
408
+
409
+ def all_files( dname )
410
+ Dir.open(dname) {|d|
411
+ return d.find_all {|n| File.file? "#{dname}/#{n}" }
412
+ }
413
+ end
414
+
415
+ def all_dirs( dname )
416
+ Dir.open(dname) {|d|
417
+ return d.find_all {|n| dir? "#{dname}/#{n}" } - %w(. ..)
418
+ }
419
+ end
420
+
421
+ end
422
+
423
+ ### end fileop.rb
424
+ ### begin base.rb
425
+
426
+ class InstallError < StandardError; end
427
+
428
+
429
+ class Installer
430
+
431
+ Version = '3.1.3'
432
+ Copyright = 'Copyright (c) 2000-2002 Minero Aoki'
433
+
434
+
435
+ @toplevel = nil
436
+
437
+ def self.declear_toplevel_installer( inst )
438
+ @toplevel and
439
+ raise ArgumentError, 'more than one toplevel installer decleared'
440
+ @toplevel = inst
441
+ end
442
+
443
+ def self.toplevel_installer
444
+ @toplevel
445
+ end
446
+
447
+
448
+ FILETYPES = %w( bin lib ext data )
449
+
450
+ include FileOperations
451
+
452
+ def initialize( config, opt, srcroot, objroot )
453
+ @config = config
454
+ @options = opt
455
+ @srcdir = File.expand_path(srcroot)
456
+ @objdir = File.expand_path(objroot)
457
+ @currdir = '.'
458
+ end
459
+
460
+ def inspect
461
+ "#<#{self.class} #{__id__}>"
462
+ end
463
+
464
+ #
465
+ # configs/options
466
+ #
467
+
468
+ def get_config( key )
469
+ @config[key]
470
+ end
471
+
472
+ alias config get_config
473
+
474
+ def set_config( key, val )
475
+ @config[key] = val
476
+ end
477
+
478
+ def no_harm?
479
+ @options['no-harm']
480
+ end
481
+
482
+ def verbose?
483
+ @options['verbose']
484
+ end
485
+
486
+ def verbose_off
487
+ save, @options['verbose'] = @options['verbose'], false
488
+ yield
489
+ @options['verbose'] = save
490
+ end
491
+
492
+ #
493
+ # srcdir/objdir
494
+ #
495
+
496
+ attr_reader :srcdir
497
+ alias srcdir_root srcdir
498
+ alias package_root srcdir
499
+
500
+ def curr_srcdir
501
+ "#{@srcdir}/#{@currdir}"
502
+ end
503
+
504
+ attr_reader :objdir
505
+ alias objdir_root objdir
506
+
507
+ def curr_objdir
508
+ "#{@objdir}/#{@currdir}"
509
+ end
510
+
511
+ def srcfile( path )
512
+ curr_srcdir + '/' + path
513
+ end
514
+
515
+ def srcexist?( path )
516
+ File.exist? srcfile(path)
517
+ end
518
+
519
+ def srcdirectory?( path )
520
+ dir? srcfile(path)
521
+ end
522
+
523
+ def srcfile?( path )
524
+ File.file? srcfile(path)
525
+ end
526
+
527
+ def srcentries( path = '.' )
528
+ Dir.open(curr_srcdir + '/' + path) {|d|
529
+ return d.to_a - %w(. ..) - hookfilenames
530
+ }
531
+ end
532
+
533
+ def srcfiles( path = '.' )
534
+ srcentries(path).find_all {|fname|
535
+ File.file? File.join(curr_srcdir, path, fname)
536
+ }
537
+ end
538
+
539
+ def srcdirectories( path = '.' )
540
+ srcentries(path).find_all {|fname|
541
+ dir? File.join(curr_srcdir, path, fname)
542
+ }
543
+ end
544
+
545
+ def dive_into( rel )
546
+ return unless dir? "#{@srcdir}/#{rel}"
547
+ return if File.basename(rel) == "SCCS"
548
+
549
+ dir = File.basename(rel)
550
+ Dir.mkdir dir unless dir? dir
551
+ save = Dir.pwd
552
+ Dir.chdir dir
553
+ $stderr.puts '---> ' + rel if verbose?
554
+ @currdir = rel
555
+ yield
556
+ Dir.chdir save
557
+ $stderr.puts '<--- ' + rel if verbose?
558
+ @currdir = File.dirname(rel)
559
+ end
560
+
561
+ #
562
+ # config
563
+ #
564
+
565
+ def exec_config
566
+ exec_task_traverse 'config'
567
+ end
568
+
569
+ def config_dir_bin( rel )
570
+ end
571
+
572
+ def config_dir_lib( rel )
573
+ end
574
+
575
+ def config_dir_ext( rel )
576
+ extconf if extdir? curr_srcdir
577
+ end
578
+
579
+ def extconf
580
+ opt = @options['config-opt'].join(' ')
581
+ command "#{config('ruby-prog')} #{curr_srcdir}/extconf.rb #{opt}"
582
+ end
583
+
584
+ def config_dir_data( rel )
585
+ end
586
+
587
+ #
588
+ # setup
589
+ #
590
+
591
+ def exec_setup
592
+ exec_task_traverse 'setup'
593
+ end
594
+
595
+ def setup_dir_bin( relpath )
596
+ all_files(curr_srcdir).each do |fname|
597
+ add_rubypath "#{curr_srcdir}/#{fname}"
598
+ end
599
+ end
600
+
601
+ SHEBANG_RE = /\A\#!\s*\S*ruby\S*/
602
+
603
+ def add_rubypath( path )
604
+ $stderr.puts %Q<set #! line to "\#!#{config('ruby-path')}" for #{path} ...> if verbose?
605
+ return if no_harm?
606
+
607
+ tmpfile = File.basename(path) + '.tmp'
608
+ begin
609
+ File.open(path) {|r|
610
+ File.open(tmpfile, 'w') {|w|
611
+ first = r.gets
612
+ return unless SHEBANG_RE === first # reject '/usr/bin/env ruby'
613
+
614
+ w.print first.sub(SHEBANG_RE, '#!' + config('ruby-path'))
615
+ w.write r.read
616
+ } }
617
+ mv tmpfile, File.basename(path)
618
+ ensure
619
+ rm_f tmpfile if File.exist? tmpfile
620
+ end
621
+ end
622
+
623
+ def setup_dir_lib( relpath )
624
+ end
625
+
626
+ def setup_dir_ext( relpath )
627
+ make if extdir?(curr_srcdir)
628
+ end
629
+
630
+ def make
631
+ command config('make-prog')
632
+ end
633
+
634
+ def setup_dir_data( relpath )
635
+ end
636
+
637
+ #
638
+ # install
639
+ #
640
+
641
+ def exec_install
642
+ exec_task_traverse 'install'
643
+ end
644
+
645
+ def install_dir_bin( rel )
646
+ install_files targfiles, config('bin-dir') + '/' + rel, 0755
647
+ end
648
+
649
+ def install_dir_lib( rel )
650
+ install_files targfiles, config('rb-dir') + '/' + rel, 0644
651
+ end
652
+
653
+ def install_dir_ext( rel )
654
+ install_dir_ext_main File.dirname(rel) if extdir?(curr_srcdir)
655
+ end
656
+
657
+ def install_dir_ext_main( rel )
658
+ install_files allext('.'), config('so-dir') + '/' + rel, 0555
659
+ end
660
+
661
+ def install_dir_data( rel )
662
+ install_files targfiles, config('data-dir') + '/' + rel, 0644
663
+ end
664
+
665
+ def install_files( list, dest, mode )
666
+ mkdir_p dest, @options['install-prefix']
667
+ list.each do |fname|
668
+ install fname, dest, mode, @options['install-prefix']
669
+ end
670
+ end
671
+
672
+ def targfiles
673
+ (targfilenames() - hookfilenames()).collect {|fname|
674
+ File.exist?(fname) ? fname : File.join(curr_srcdir(), fname)
675
+ }
676
+ end
677
+
678
+ def targfilenames
679
+ [ curr_srcdir(), '.' ].inject([]) {|ret, dir|
680
+ ret | all_files(dir)
681
+ }
682
+ end
683
+
684
+ def hookfilenames
685
+ %w( pre-%s post-%s pre-%s.rb post-%s.rb ).collect {|fmt|
686
+ %w( config setup install clean ).collect {|t| sprintf fmt, t }
687
+ }.flatten
688
+ end
689
+
690
+ def allext( dir )
691
+ _allext(dir) or raise InstallError,
692
+ "no extention exists: Have you done 'ruby #{$0} setup' ?"
693
+ end
694
+
695
+ DLEXT = /\.#{ ::Config::CONFIG['DLEXT'] }\z/
696
+
697
+ def _allext( dir )
698
+ Dir.open(dir) {|d|
699
+ return d.find_all {|fname| DLEXT === fname }
700
+ }
701
+ end
702
+
703
+ #
704
+ # clean
705
+ #
706
+
707
+ def exec_clean
708
+ exec_task_traverse 'clean'
709
+ rm_f 'config.save'
710
+ rm_f 'InstalledFiles'
711
+ end
712
+
713
+ def clean_dir_bin( rel )
714
+ end
715
+
716
+ def clean_dir_lib( rel )
717
+ end
718
+
719
+ def clean_dir_ext( rel )
720
+ clean
721
+ end
722
+
723
+ def clean
724
+ command config('make-prog') + ' clean' if File.file? 'Makefile'
725
+ end
726
+
727
+ def clean_dir_data( rel )
728
+ end
729
+
730
+ #
731
+ # lib
732
+ #
733
+
734
+ def exec_task_traverse( task )
735
+ run_hook 'pre-' + task
736
+ FILETYPES.each do |type|
737
+ if config('without-ext') == 'yes' and type == 'ext'
738
+ $stderr.puts 'skipping ext/* by user option' if verbose?
739
+ next
740
+ end
741
+ traverse task, type, task + '_dir_' + type
742
+ end
743
+ run_hook 'post-' + task
744
+ end
745
+
746
+ def traverse( task, rel, mid )
747
+ dive_into(rel) {
748
+ run_hook 'pre-' + task
749
+ __send__ mid, rel.sub(%r_\A.*?(?:/|\z)_, '')
750
+ all_dirs(curr_srcdir).each do |d|
751
+ traverse task, rel + '/' + d, mid
752
+ end
753
+ run_hook 'post-' + task
754
+ }
755
+ end
756
+
757
+ def run_hook( name )
758
+ try_run_hook curr_srcdir + '/' + name or
759
+ try_run_hook curr_srcdir + '/' + name + '.rb'
760
+ end
761
+
762
+ def try_run_hook( fname )
763
+ return false unless File.file? fname
764
+
765
+ env = self.dup
766
+ begin
767
+ env.instance_eval File.read_all(fname), fname, 1
768
+ rescue
769
+ raise InstallError, "hook #{fname} failed:\n" + $!.message
770
+ end
771
+ true
772
+ end
773
+
774
+ def extdir?( dir )
775
+ File.exist? dir + '/MANIFEST'
776
+ end
777
+
778
+ end
779
+
780
+ ### end base.rb
781
+ ### begin toplevel.rb
782
+
783
+ class ToplevelInstaller < Installer
784
+
785
+ TASKS = [
786
+ [ 'config', 'saves your configurations' ],
787
+ [ 'show', 'shows current configuration' ],
788
+ [ 'setup', 'compiles extention or else' ],
789
+ [ 'install', 'installs files' ],
790
+ [ 'clean', "does `make clean' for each extention" ]
791
+ ]
792
+
793
+
794
+ def initialize( root )
795
+ super nil, {'verbose' => true}, root, '.'
796
+ Installer.declear_toplevel_installer self
797
+ end
798
+
799
+
800
+ def execute
801
+ run_metaconfigs
802
+
803
+ case task = parsearg_global()
804
+ when 'config'
805
+ @config = ConfigTable.new
806
+ else
807
+ @config = ConfigTable.load
808
+ end
809
+ parsearg_TASK task
810
+
811
+ exectask task
812
+ end
813
+
814
+
815
+ def run_metaconfigs
816
+ MetaConfigEnvironment.eval_file "#{srcdir_root()}/#{metaconfig()}"
817
+ end
818
+
819
+ def metaconfig
820
+ 'metaconfig'
821
+ end
822
+
823
+
824
+ def exectask( task )
825
+ if task == 'show'
826
+ exec_show
827
+ else
828
+ try task
829
+ end
830
+ end
831
+
832
+ def try( task )
833
+ $stderr.printf "#{File.basename $0}: entering %s phase...\n", task if verbose?
834
+ begin
835
+ __send__ 'exec_' + task
836
+ rescue
837
+ $stderr.printf "%s failed\n", task
838
+ raise
839
+ end
840
+ $stderr.printf "#{File.basename $0}: %s done.\n", task if verbose?
841
+ end
842
+
843
+ #
844
+ # processing arguments
845
+ #
846
+
847
+ def parsearg_global
848
+ task_re = /\A(?:#{TASKS.collect {|i| i[0] }.join '|'})\z/
849
+
850
+ while arg = ARGV.shift
851
+ case arg
852
+ when /\A\w+\z/
853
+ task_re === arg or raise InstallError, "wrong task: #{arg}"
854
+ return arg
855
+
856
+ when '-q', '--quiet'
857
+ @options['verbose'] = false
858
+
859
+ when '--verbose'
860
+ @options['verbose'] = true
861
+
862
+ when '-h', '--help'
863
+ print_usage $stdout
864
+ exit 0
865
+
866
+ when '-v', '--version'
867
+ puts "#{File.basename $0} version #{Version}"
868
+ exit 0
869
+
870
+ when '--copyright'
871
+ puts Copyright
872
+ exit 0
873
+
874
+ else
875
+ raise InstallError, "unknown global option '#{arg}'"
876
+ end
877
+ end
878
+
879
+ raise InstallError, "No task or global option given.
880
+ Typical installation procedure is:
881
+ $ ruby #{File.basename $0} config
882
+ $ ruby #{File.basename $0} setup
883
+ # ruby #{File.basename $0} install (may require root privilege)
884
+ "
885
+ end
886
+
887
+
888
+ def parsearg_TASK( task )
889
+ mid = "parsearg_#{task}"
890
+ if respond_to? mid, true
891
+ __send__ mid
892
+ else
893
+ ARGV.empty? or
894
+ raise InstallError, "#{task}: unknown options: #{ARGV.join ' '}"
895
+ end
896
+ end
897
+
898
+ def parsearg_config
899
+ re = /\A--(#{ConfigTable.keys.join '|'})(?:=(.*))?\z/
900
+ @options['config-opt'] = []
901
+
902
+ while i = ARGV.shift
903
+ if /\A--?\z/ === i
904
+ @options['config-opt'] = ARGV.dup
905
+ break
906
+ end
907
+ m = re.match(i) or raise InstallError, "config: unknown option #{i}"
908
+ name, value = m.to_a[1,2]
909
+ if value
910
+ if ConfigTable.bool_config?(name)
911
+ /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i === value or raise InstallError, "config: --#{name} allows only yes/no for argument"
912
+ value = (/\Ay(es)?|\At(rue)/i === value) ? 'yes' : 'no'
913
+ end
914
+ else
915
+ ConfigTable.bool_config?(name) or raise InstallError, "config: --#{name} requires argument"
916
+ value = 'yes'
917
+ end
918
+ @config[name] = value
919
+ end
920
+ end
921
+
922
+ def parsearg_install
923
+ @options['no-harm'] = false
924
+ @options['install-prefix'] = ''
925
+ while a = ARGV.shift
926
+ case a
927
+ when /\A--no-harm\z/
928
+ @options['no-harm'] = true
929
+ when /\A--prefix=(.*)\z/
930
+ path = $1
931
+ path = File.expand_path(path) unless path[0,1] == '/'
932
+ @options['install-prefix'] = path
933
+ else
934
+ raise InstallError, "install: unknown option #{a}"
935
+ end
936
+ end
937
+ end
938
+
939
+
940
+ def print_usage( out )
941
+ out.puts 'Typical Installation Procedure:'
942
+ out.puts " $ ruby #{File.basename $0} config"
943
+ out.puts " $ ruby #{File.basename $0} setup"
944
+ out.puts " # ruby #{File.basename $0} install (may require root privilege)"
945
+ out.puts
946
+ out.puts 'Detailed Usage:'
947
+ out.puts " ruby #{File.basename $0} <global option>"
948
+ out.puts " ruby #{File.basename $0} [<global options>] <task> [<task options>]"
949
+
950
+ fmt = " %-20s %s\n"
951
+ out.puts
952
+ out.puts 'Global options:'
953
+ out.printf fmt, '-q,--quiet', 'suppress message outputs'
954
+ out.printf fmt, ' --verbose', 'output messages verbosely'
955
+ out.printf fmt, '-h,--help', 'print this message'
956
+ out.printf fmt, '-v,--version', 'print version and quit'
957
+ out.printf fmt, ' --copyright', 'print copyright and quit'
958
+
959
+ out.puts
960
+ out.puts 'Tasks:'
961
+ TASKS.each do |name, desc|
962
+ out.printf " %-10s %s\n", name, desc
963
+ end
964
+
965
+ out.puts
966
+ out.puts 'Options for config:'
967
+ ConfigTable.each_definition do |name, (default, arg, desc, default2)|
968
+ out.printf " %-20s %s [%s]\n",
969
+ '--'+ name + (ConfigTable.bool_config?(name) ? '' : '='+arg),
970
+ desc,
971
+ default2 || default
972
+ end
973
+ out.printf " %-20s %s [%s]\n",
974
+ '--rbconfig=path', 'your rbconfig.rb to load', "running ruby's"
975
+
976
+ out.puts
977
+ out.puts 'Options for install:'
978
+ out.printf " %-20s %s [%s]\n",
979
+ '--no-harm', 'only display what to do if given', 'off'
980
+ out.printf " %-20s %s [%s]\n",
981
+ '--prefix', 'install path prefix', '$prefix'
982
+
983
+ out.puts
984
+ end
985
+
986
+ #
987
+ # config
988
+ #
989
+
990
+ def exec_config
991
+ super
992
+ @config.save
993
+ end
994
+
995
+ #
996
+ # show
997
+ #
998
+
999
+ def exec_show
1000
+ ConfigTable.each_name do |k|
1001
+ v = @config.get_raw(k)
1002
+ if not v or v.empty?
1003
+ v = '(not specified)'
1004
+ end
1005
+ printf "%-10s %s\n", k, v
1006
+ end
1007
+ end
1008
+
1009
+ end
1010
+
1011
+ ### end toplevel.rb
1012
+
1013
+ if $0 == __FILE__
1014
+ begin
1015
+ installer = ToplevelInstaller.new(File.dirname($0))
1016
+ installer.execute
1017
+ rescue
1018
+ raise if $DEBUG
1019
+ $stderr.puts $!.message
1020
+ $stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
1021
+ exit 1
1022
+ end
1023
+ end