rmail-sup 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +7 -0
  2. data/NEWS +323 -0
  3. data/NOTES +14 -0
  4. data/README +83 -0
  5. data/Rakefile +184 -0
  6. data/THANKS +25 -0
  7. data/TODO +115 -0
  8. data/guide/Intro.txt +122 -0
  9. data/guide/MIME.txt +6 -0
  10. data/guide/TableOfContents.txt +13 -0
  11. data/install.rb +1023 -0
  12. data/lib/rmail.rb +50 -0
  13. data/lib/rmail/address.rb +841 -0
  14. data/lib/rmail/header.rb +981 -0
  15. data/lib/rmail/mailbox.rb +62 -0
  16. data/lib/rmail/mailbox/mboxreader.rb +182 -0
  17. data/lib/rmail/message.rb +201 -0
  18. data/lib/rmail/parser.rb +412 -0
  19. data/lib/rmail/parser/multipart.rb +217 -0
  20. data/lib/rmail/parser/pushbackreader.rb +173 -0
  21. data/lib/rmail/serialize.rb +190 -0
  22. data/lib/rmail/utils.rb +59 -0
  23. data/test/addrgrammar.txt +113 -0
  24. data/test/data/mbox.odd +4 -0
  25. data/test/data/mbox.simple +8 -0
  26. data/test/data/multipart/data.1 +5 -0
  27. data/test/data/multipart/data.10 +1 -0
  28. data/test/data/multipart/data.11 +9 -0
  29. data/test/data/multipart/data.12 +9 -0
  30. data/test/data/multipart/data.13 +3 -0
  31. data/test/data/multipart/data.14 +3 -0
  32. data/test/data/multipart/data.15 +3 -0
  33. data/test/data/multipart/data.16 +3 -0
  34. data/test/data/multipart/data.17 +0 -0
  35. data/test/data/multipart/data.2 +5 -0
  36. data/test/data/multipart/data.3 +2 -0
  37. data/test/data/multipart/data.4 +3 -0
  38. data/test/data/multipart/data.5 +1 -0
  39. data/test/data/multipart/data.6 +2 -0
  40. data/test/data/multipart/data.7 +3 -0
  41. data/test/data/multipart/data.8 +5 -0
  42. data/test/data/multipart/data.9 +4 -0
  43. data/test/data/parser.badmime1 +4 -0
  44. data/test/data/parser.badmime2 +6 -0
  45. data/test/data/parser.nested-multipart +75 -0
  46. data/test/data/parser.nested-simple +12 -0
  47. data/test/data/parser.nested-simple2 +16 -0
  48. data/test/data/parser.nested-simple3 +21 -0
  49. data/test/data/parser.rfc822 +65 -0
  50. data/test/data/parser.simple-mime +24 -0
  51. data/test/data/parser/multipart.1 +8 -0
  52. data/test/data/parser/multipart.10 +4 -0
  53. data/test/data/parser/multipart.11 +12 -0
  54. data/test/data/parser/multipart.12 +12 -0
  55. data/test/data/parser/multipart.13 +6 -0
  56. data/test/data/parser/multipart.14 +6 -0
  57. data/test/data/parser/multipart.15 +6 -0
  58. data/test/data/parser/multipart.16 +6 -0
  59. data/test/data/parser/multipart.2 +8 -0
  60. data/test/data/parser/multipart.3 +5 -0
  61. data/test/data/parser/multipart.4 +6 -0
  62. data/test/data/parser/multipart.5 +4 -0
  63. data/test/data/parser/multipart.6 +5 -0
  64. data/test/data/parser/multipart.7 +6 -0
  65. data/test/data/parser/multipart.8 +8 -0
  66. data/test/data/parser/multipart.9 +7 -0
  67. data/test/data/transparency/absolute.1 +5 -0
  68. data/test/data/transparency/absolute.2 +1 -0
  69. data/test/data/transparency/absolute.3 +2 -0
  70. data/test/data/transparency/absolute.4 +3 -0
  71. data/test/data/transparency/absolute.5 +4 -0
  72. data/test/data/transparency/absolute.6 +49 -0
  73. data/test/data/transparency/message.1 +73 -0
  74. data/test/data/transparency/message.2 +34 -0
  75. data/test/data/transparency/message.3 +63 -0
  76. data/test/data/transparency/message.4 +5 -0
  77. data/test/data/transparency/message.5 +15 -0
  78. data/test/data/transparency/message.6 +1185 -0
  79. data/test/runtests.rb +35 -0
  80. data/test/testaddress.rb +1204 -0
  81. data/test/testbase.rb +204 -0
  82. data/test/testheader.rb +1225 -0
  83. data/test/testmailbox.rb +47 -0
  84. data/test/testmboxreader.rb +161 -0
  85. data/test/testmessage.rb +257 -0
  86. data/test/testparser.rb +634 -0
  87. data/test/testparsermultipart.rb +205 -0
  88. data/test/testpushbackreader.rb +40 -0
  89. data/test/testserialize.rb +264 -0
  90. data/test/testtestbase.rb +116 -0
  91. data/test/testtranspparency.rb +105 -0
  92. data/version +1 -0
  93. metadata +149 -0
data/THANKS ADDED
@@ -0,0 +1,25 @@
1
+ = Thanks to Python's email module for:
2
+
3
+ - The idea of separating parsing and serialization from the mail
4
+ classes themselves.
5
+ - A lot of the MIME multipart API.
6
+
7
+ = Thanks to Perl's MimeTools for:
8
+
9
+ - Showing me this can all be done in the scripting language (no C
10
+ extensions necessary).
11
+ - Accessing MIME parts via a File like API (thereby allowing them to
12
+ be stored on disk).
13
+
14
+ = People:
15
+
16
+ "Yoshinori K. Okuji" <okuji-at-enbug-dot-org>
17
+
18
+ - Code to parse messages from strings (though since then I did it a
19
+ much simpler way).
20
+ - Numerous other API suggestions and improvements.
21
+
22
+ Booker Bense <bbense-at-SLAC-dot-Stanford-dot-EDU>
23
+
24
+ - For prodding me to implement mbox mailbox parsing (and an initial
25
+ implementation). It took me months, but I did it!
data/TODO ADDED
@@ -0,0 +1,115 @@
1
+ In rough priority order...but please let me know if something you need
2
+ is missing.
3
+
4
+ = FIXME
5
+
6
+ - StreamHandler should get preamble_begin and preamble_end callbacks.
7
+ Same for epilogue_begin, epilogue_end.
8
+
9
+ - Add a Rakefile rule to run the tests in the super anal way (against
10
+ multiple versions of Ruby).
11
+
12
+ = STOLEN
13
+
14
+ - Header continuation issue:
15
+ https://sourceforge.net/tracker/?func=detail&atid=105470&aid=504152&group_id=5470
16
+ - Subject: =?ISO-8859-1?Q?=24=A3_Kills_IRB?=
17
+
18
+ = Documentation
19
+
20
+ - Make it clear that all strings the library works with are the ASCII
21
+ strings that are part of the RFC2822 message -- not strings in any
22
+ other charset.
23
+
24
+ - Update Intro.txt, write MIME.txt and other Guides.
25
+
26
+ = Features
27
+
28
+ - RFC2231
29
+ http://mail.python.org/pipermail/email-sig/2003-November/000032.html
30
+
31
+ - Smart handling of charset issues for text/ types.
32
+
33
+ [Goal] allow setting the body part to a given piece of data, and
34
+ RMail handles setting the appropriate MIME headers. This
35
+ requires the data to have a known charset.
36
+
37
+ Maybe this plays into the encoding solution below.
38
+
39
+ - Base64 and quoted-printable encoding
40
+
41
+ [Goal] allow setting the body part to a given piece of data, and
42
+ RMail handles setting the appropriate MIME headers and
43
+ possibly quoted-printable or base64 transcoding it if
44
+ appropriate.
45
+
46
+ This may be best done by introducing an RMail::TransferEncodedString
47
+ class that knows whether it is transcoded as binary,
48
+ quoted-printable, base64, etc. You would set RMail::Message#body to
49
+ a TransferEncodedString and RMail::Message would set the appropriate
50
+ MIME headers. If at serialization time the message body were set to
51
+ a plain String, then RMail::Message would transcode it into base64.
52
+
53
+ - An RMail::Message#attach method that takes care of setting
54
+ content-disposition, etc.
55
+
56
+ - Header folding in RMail::Header.
57
+
58
+ - A small wrapper around Net::SMTP#send_mail that takes care of:
59
+ - parsing recipients out of the various message headers
60
+ - deleting the Bcc: header
61
+ - sending the message
62
+
63
+ - Provide a way to parse just the message headers, leaving the rest in
64
+ the input stream (for use by RubyFilter). This also implies a
65
+ feature that parses a message body given a RMail::Header object and
66
+ an input stream. This should be easy -- read line by line until you
67
+ get an empty line or EOF, then pass that off to parse_header as a
68
+ string to be parsed. Parsing the body can just use a
69
+ PushbackReader.
70
+
71
+ - Handle different end of line terminators everywhere (for both
72
+ parsing and generating).
73
+
74
+ - Provide some way when parsing a message to start spooling to disk
75
+ for really large messages. This would be easy to do depending on
76
+ how message body transparency is implemented.
77
+
78
+ - MIME charset support in header fields and message bodies. This
79
+ requires m18n support in ruby, so it won't be coming soon.
80
+
81
+ - Maildir parsing.
82
+
83
+ - Simplify the Parser such that the class used to hold the parse
84
+ result can be easily changed and so the memory used can be
85
+ minimized.
86
+
87
+ The idea is to support "bogofilter" style parsing where the data of
88
+ the parse is immediately processed and thrown away but the structure
89
+ and content of the parsed message is important.
90
+
91
+ This may require extensions to header parsing, where things like the
92
+ important MIME header fields can be parsed without creating a whole
93
+ RMail::Header object.
94
+
95
+ = Minor Features
96
+
97
+ - Untainting of email addresses. See Perl's CGI::Untaint::email.
98
+
99
+ - Parser for Received: headers. See Perl's Mail::Field::Received.
100
+
101
+ = Documentation Tasks
102
+
103
+ - A "howto" like documentation.
104
+
105
+ - How to write out a base64 encoded MIME part to a file.
106
+ - How to parse a unix MBOX file.
107
+ - MIME operations (see RFC2047)
108
+ - Character sets in message bodies
109
+ - Non-textual message bodies
110
+ - Multi-part message bodies
111
+ - Textual header information in character sets other than
112
+ US-ASCII.
113
+
114
+ - Go through every class and make sure the methods are sorted in a
115
+ sensible order so the RDoc output is nice.
@@ -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