tmail 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. data/LICENSE +21 -0
  2. data/README +157 -0
  3. data/bat/changelog +19 -0
  4. data/bat/clobber/package +10 -0
  5. data/bat/compile +42 -0
  6. data/bat/config.yaml +8 -0
  7. data/bat/prepare +8 -0
  8. data/bat/publish +51 -0
  9. data/bat/rdoc +42 -0
  10. data/bat/release +12 -0
  11. data/bat/setup +1616 -0
  12. data/bat/stats +138 -0
  13. data/bat/tag +25 -0
  14. data/bat/test +25 -0
  15. data/ext/tmail/Makefile +25 -0
  16. data/ext/tmail/base64/MANIFEST +4 -0
  17. data/ext/tmail/base64/base64.c +264 -0
  18. data/ext/tmail/base64/depend +1 -0
  19. data/ext/tmail/base64/extconf.rb +38 -0
  20. data/ext/tmail/scanner_c/MANIFEST +4 -0
  21. data/ext/tmail/scanner_c/depend +1 -0
  22. data/ext/tmail/scanner_c/extconf.rb +38 -0
  23. data/ext/tmail/scanner_c/scanner_c.c +582 -0
  24. data/lib/tmail.rb +4 -0
  25. data/lib/tmail/Makefile +19 -0
  26. data/lib/tmail/address.rb +245 -0
  27. data/lib/tmail/attachments.rb +47 -0
  28. data/lib/tmail/base64.rb +75 -0
  29. data/lib/tmail/compat.rb +39 -0
  30. data/lib/tmail/config.rb +71 -0
  31. data/lib/tmail/core_extensions.rb +67 -0
  32. data/lib/tmail/encode.rb +524 -0
  33. data/lib/tmail/header.rb +931 -0
  34. data/lib/tmail/index.rb +8 -0
  35. data/lib/tmail/interface.rb +540 -0
  36. data/lib/tmail/loader.rb +1 -0
  37. data/lib/tmail/mail.rb +507 -0
  38. data/lib/tmail/mailbox.rb +435 -0
  39. data/lib/tmail/mbox.rb +1 -0
  40. data/lib/tmail/net.rb +282 -0
  41. data/lib/tmail/obsolete.rb +137 -0
  42. data/lib/tmail/parser.rb +1475 -0
  43. data/lib/tmail/parser.y +381 -0
  44. data/lib/tmail/port.rb +379 -0
  45. data/lib/tmail/quoting.rb +142 -0
  46. data/lib/tmail/require_arch.rb +56 -0
  47. data/lib/tmail/scanner.rb +44 -0
  48. data/lib/tmail/scanner_r.rb +263 -0
  49. data/lib/tmail/stringio.rb +279 -0
  50. data/lib/tmail/tmail.rb +1 -0
  51. data/lib/tmail/utils.rb +281 -0
  52. data/lib/tmail/version.rb +38 -0
  53. data/meta/icli.yaml +16 -0
  54. data/meta/tmail-1.1.1.roll +24 -0
  55. data/sample/data/multipart +23 -0
  56. data/sample/data/normal +29 -0
  57. data/sample/data/sendtest +5 -0
  58. data/sample/data/simple +14 -0
  59. data/sample/data/test +27 -0
  60. data/sample/extract-attachements.rb +33 -0
  61. data/sample/from-check.rb +26 -0
  62. data/sample/multipart.rb +26 -0
  63. data/sample/parse-bench.rb +68 -0
  64. data/sample/parse-test.rb +19 -0
  65. data/sample/sendmail.rb +94 -0
  66. data/test/extctrl.rb +6 -0
  67. data/test/fixtures/raw_base64_decoded_string +0 -0
  68. data/test/fixtures/raw_base64_email +83 -0
  69. data/test/fixtures/raw_base64_encoded_string +1 -0
  70. data/test/fixtures/raw_email +14 -0
  71. data/test/fixtures/raw_email10 +20 -0
  72. data/test/fixtures/raw_email11 +34 -0
  73. data/test/fixtures/raw_email12 +32 -0
  74. data/test/fixtures/raw_email13 +29 -0
  75. data/test/fixtures/raw_email2 +114 -0
  76. data/test/fixtures/raw_email3 +70 -0
  77. data/test/fixtures/raw_email4 +59 -0
  78. data/test/fixtures/raw_email5 +19 -0
  79. data/test/fixtures/raw_email6 +20 -0
  80. data/test/fixtures/raw_email7 +66 -0
  81. data/test/fixtures/raw_email8 +47 -0
  82. data/test/fixtures/raw_email9 +28 -0
  83. data/test/fixtures/raw_email_quoted_with_0d0a +14 -0
  84. data/test/fixtures/raw_email_simple +11 -0
  85. data/test/fixtures/raw_email_with_illegal_boundary +58 -0
  86. data/test/fixtures/raw_email_with_multipart_mixed_quoted_boundary +50 -0
  87. data/test/fixtures/raw_email_with_nested_attachment +100 -0
  88. data/test/fixtures/raw_email_with_partially_quoted_subject +14 -0
  89. data/test/fixtures/raw_email_with_quoted_illegal_boundary +58 -0
  90. data/test/kcode.rb +14 -0
  91. data/test/test_address.rb +1128 -0
  92. data/test/test_attachments.rb +35 -0
  93. data/test/test_base64.rb +63 -0
  94. data/test/test_encode.rb +77 -0
  95. data/test/test_header.rb +885 -0
  96. data/test/test_helper.rb +2 -0
  97. data/test/test_mail.rb +623 -0
  98. data/test/test_mbox.rb +126 -0
  99. data/test/test_port.rb +430 -0
  100. data/test/test_scanner.rb +209 -0
  101. data/test/test_utils.rb +37 -0
  102. metadata +205 -0
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2001-2007 Minero Aoki
2
+ Changes Copyright (c) 2007 Mikel Lindsaar
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,157 @@
1
+ = TMail
2
+
3
+ http://tmail.rubyforge.org/
4
+
5
+ Mikel Lindsaar senior developer
6
+ Trans assitant developer
7
+ Minero Aoki original developer
8
+
9
+ == DESCRIPTION:
10
+
11
+ TMail is a mail handling library for Ruby. It abstracts a mail message into a usable object allowing you to read, set, add and delete headers and the mail body.
12
+
13
+ TMail is used by the Ruby on Rails web framework as the Email abstraction layer for their ActionMailer module. It is also used by the Nitro framework and many other applications on and off the web.
14
+
15
+ The goal of the TMail handling library is to be able to parse and handle raw Email sources and produce RFC compliant Emails as a result. If you find something that TMail does that violates an RFC, we want to know and we'll get it fixed fast.
16
+
17
+ == FEATURES/PROBLEMS:
18
+
19
+ TMail is about 95% or so RFC compliant on the handling of emails.
20
+
21
+ There is a few edge case problems (such as if the person has an unquoted @ symbol in the To: field) that are not handled yet. But we are working on these.
22
+
23
+ There are also some problems in the header handling, but for 99.9% of email, you will be fine. Usually, the problems revolve around parsing incomming emails and making sense of them.
24
+
25
+ I really welcome any examples of Emails that "didn't work" with TMail so I can use them as test cases.
26
+
27
+ == SYNOPSIS:
28
+
29
+ TMail is very easy to use. You simply require the library and then pass a raw email text message into the TMail::Mail.parse method. This returns a TMail::Mail object which you can now query and run methods against to modify, inspect or add to the Email.
30
+
31
+ === Short Version:
32
+
33
+ irb(main):001:0> require 'tmail'
34
+ irb(main):002:0> raw_email = File.open("my_raw_email", 'r') { |f| @mail = f.read }
35
+ irb(main):003:0> email = TMail::Mail.parse(raw_email)
36
+ irb(main):004:0> puts email['to']
37
+ mikel@example.com
38
+ => nil
39
+ irb(main):005:0> email['to'] = 'mikel@somewhere.else.com'
40
+ => "mikel@somewhere.else.com"
41
+ irb(main):006:0> puts email['to']
42
+ mikel@somewhere.else.com
43
+ => nil
44
+
45
+ === Longer Version:
46
+
47
+ Assuming you have a single raw email in the variable my_message, you can do the following:
48
+
49
+ require 'tmail'
50
+ email = TMail::Mail.parse(my_message)
51
+
52
+ This will give you a TMail::Mail class containing your parsed message. There are other methods of opening emails through Ports.
53
+
54
+ You can view this email by a simple puts:
55
+
56
+ puts email
57
+
58
+ Return-Path: <mikel@nowhere.com>
59
+ Date: Sun, 21 Oct 2007 19:38:13 +1000
60
+ From: Mikel Lindsaar <mikel@nowhere.com>
61
+ To: mikel@somewhere.com
62
+ Message-Id: <009601c813c6$19df3510$0437d30a@mikel091a>
63
+ Subject: Testing Email
64
+
65
+ Hello Mikel
66
+
67
+ Easy right?
68
+
69
+ === Adding a header to the EMail:
70
+
71
+ Say now that you have opened your message, you want to put in a Reply-To field. You do this like so:
72
+
73
+ email['reply-to'] = "My Email Address <my_address@anotherplace.com>"
74
+
75
+ Is it really there? Well, find out with a puts:
76
+
77
+ puts email
78
+
79
+ Return-Path: <mikel@nowhere.com>
80
+ Date: Sun, 21 Oct 2007 19:38:13 +1000
81
+ From: Mikel Lindsaar <mikel@nowhere.com>
82
+ Reply-To: My Email Address <my_address@anotherplace.com>
83
+ To: mikel@somewhere.com
84
+ Message-Id: <009601c813c6$19df3510$0437d30a@mikel091a>
85
+ Subject: Testing Email
86
+
87
+ Hello Mikel
88
+
89
+ Yup looks good.
90
+
91
+ === Inspecting a header:
92
+
93
+ You can then inspect your added header by doing:
94
+
95
+ email['reply-to'] # => #<TMail::AddressHeader "My Email Address <my_address@anotherplace.com>">
96
+
97
+ If you just want to the actual value, not the AddressHeader object, pass to_s to this.
98
+
99
+ email['reply-to'].to_s # => "My Email Address <my_address@anotherplace.com>"
100
+
101
+ === Deleting a header:
102
+
103
+ One way of deleting a header from an Email is just assigning it nil like so:
104
+
105
+ email['reply-to'] = nil # => nil
106
+
107
+ If you now puts the email again, it will not be included:
108
+
109
+ puts email
110
+
111
+ Return-Path: <mikel@nowhere.com>
112
+ Date: Sun, 21 Oct 2007 19:38:13 +1000
113
+ From: Mikel Lindsaar <mikel@nowhere.com>
114
+ To: mikel@somewhere.com
115
+ Message-Id: <009601c813c6$19df3510$0437d30a@mikel091a>
116
+ Subject: Testing Email
117
+
118
+ Hello Mikel
119
+
120
+ === Writing out an Email:
121
+
122
+ You can just call to_s on any email to have it serialized out as a single string with the right number of line breaks and encodings.
123
+
124
+ == REQUIREMENTS:
125
+
126
+ * Ruby 1.6 or later
127
+ * C compiler if you want the Ruby extensions for Scanner and Base64
128
+ * Ruby 1.8 or later
129
+
130
+ == Installation
131
+
132
+ * sudo gem install tmail
133
+
134
+ == LICENSE:
135
+
136
+ (The MIT License)
137
+
138
+ Copyright (c) 2007 FIX
139
+
140
+ Permission is hereby granted, free of charge, to any person obtaining
141
+ a copy of this software and associated documentation files (the
142
+ 'Software'), to deal in the Software without restriction, including
143
+ without limitation the rights to use, copy, modify, merge, publish,
144
+ distribute, sublicense, and/or sell copies of the Software, and to
145
+ permit persons to whom the Software is furnished to do so, subject to
146
+ the following conditions:
147
+
148
+ The above copyright notice and this permission notice shall be
149
+ included in all copies or substantial portions of the Software.
150
+
151
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
152
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
153
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
154
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
155
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
156
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
157
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ratch
2
+
3
+ # generate changelogs
4
+
5
+ file = 'log/History.txt'
6
+ out = 'log/Release.txt'
7
+
8
+ name, version = *File.basename(glob("meta/*.roll").first).chomp('.roll').split('-')
9
+
10
+ main :changelog => [:recent] do
11
+ mkdir_p "doc/log"
12
+ svn "log --xml > doc/log/changelog.xml"
13
+ end
14
+
15
+ task :recent do
16
+ changes = /^===\s*#{version}(.*?)\n===/m.match(File.read(file))[0]
17
+ changes = changes.chomp('===').strip.sub(/^===\s+/, 'TAG ')
18
+ File.open(out, 'w'){ |f| f << changes }
19
+ end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ratch
2
+
3
+ # Remove package products.
4
+
5
+ main :clobber_packaging do
6
+ glob("pkg/*").each do |f|
7
+ rm_r(f)
8
+ end
9
+ end
10
+
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ratch
2
+
3
+ # compile extensions
4
+
5
+ main :compile => [:compile_base64, :compile_scanner_c] do
6
+ end
7
+
8
+ task :compile_base64 do
9
+ file = nil
10
+ cd('ext/tmail/base64') do
11
+ ruby "extconf.rb"
12
+ make
13
+ file = glob("base64.#{dlext}").first
14
+ file = File.expand_path(file) if file
15
+ end
16
+ if file
17
+ mkdir_p("lib/tmail/#{arch}")
18
+ mv(file, "lib/tmail/#{arch}/")
19
+ end
20
+ end
21
+
22
+ task :compile_scanner_c do
23
+ file = nil
24
+ cd("ext/tmail/scanner_c") do
25
+ ruby "extconf.rb"
26
+ make
27
+ file = glob("scanner_c.#{dlext}").first
28
+ file = File.expand_path(file) if file
29
+ end
30
+ if file
31
+ mkdir_p("lib/tmail/#{arch}")
32
+ mv(file, "lib/tmail/#{arch}/")
33
+ end
34
+ end
35
+
36
+ def dlext
37
+ Config::CONFIG['DLEXT']
38
+ end
39
+
40
+ def arch
41
+ Config::CONFIG['arch']
42
+ end
@@ -0,0 +1,8 @@
1
+ publish:
2
+ project: tmail
3
+ source: www
4
+
5
+ rdoc:
6
+ output: www/rdoc
7
+ main: README.txt
8
+
@@ -0,0 +1,8 @@
1
+ # prepare for release
2
+ #
3
+ # This updates the manifest file
4
+ # and generates the packages.
5
+
6
+ manifest -u MANIFEST
7
+ box
8
+
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ratch
2
+
3
+ # Publish website to rubyforge
4
+ #
5
+ # This task publishes the source dir (deafult 'doc')
6
+ # to a rubyforge website.
7
+
8
+ main :publish do
9
+ config = configuration['publish']
10
+
11
+ project = config['project']
12
+ subdir = config['subdir']
13
+ source = config['source'] || "doc"
14
+ username = config['username'] || ENV['RUBYFORGE_USERNAME']
15
+ protect = %w{usage statcvs statsvn robot.txt wiki}
16
+ exclude = %w{.svn}
17
+
18
+ abort "no project" unless project
19
+ abort "no username" unless username
20
+
21
+ if subdir
22
+ destination = File.join(project, subdir)
23
+ else
24
+ destination = project
25
+ end
26
+
27
+ dir = source.chomp('/') + '/'
28
+ url = "#{username}@rubyforge.org:/var/www/gforge-projects/#{destination}"
29
+
30
+ op = ['-rLvz', '--delete'] # maybe -p ?
31
+
32
+ # add filter options. The commandline version didn't seem
33
+ # to work, so I opted for creating an .rsync_filter file for
34
+ # all cases.
35
+
36
+ filter_file = File.join(source,'.rsync-filter')
37
+
38
+ unless file?(filter_file)
39
+ File.open(filter_file, 'w') do |f|
40
+ exclude.map{|e| f << "- #{e}\n"}
41
+ protect.map{|e| f << "P #{e}\n"}
42
+ end
43
+ end
44
+
45
+ op << "--filter='dir-merge #{filter_file}'"
46
+
47
+ args = op + [dir, url]
48
+
49
+ rsync(*args.to_params)
50
+ end
51
+
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ratch
2
+
3
+ # generate rdocs
4
+ #
5
+ # Generate Rdoc documentation. Settings are
6
+ # the same as the rdoc command's options.
7
+
8
+ main :rdoc do
9
+ # Load rdoc configuration.
10
+
11
+ config = configuration['rdoc']
12
+
13
+ config['op'] = config.delete('output') if config['output']
14
+
15
+ config = {
16
+ 'template' => 'html',
17
+ 'op' => 'doc/rdoc',
18
+ 'merge' => true,
19
+ 'inline-source' => true,
20
+ 'exclude' => %w{MANIFEST, Manifest.txt},
21
+ 'include' => %w{[A-Z]* lib ext}
22
+ }.update(config)
23
+
24
+ output = config['op']
25
+
26
+ # Check for parent directory.
27
+ # (Helps to ensure we're in the right place.)
28
+
29
+ dir!(File.dirname(output))
30
+
31
+ # Prepare command arguments.
32
+
33
+ vector = config.command_vector('include')
34
+
35
+ # Remove old rdocs, if any.
36
+
37
+ rm_r(output) if File.exist?(output)
38
+
39
+ # Document.
40
+
41
+ rdoc(*vector)
42
+ end
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ratch
2
+
3
+ # release packages
4
+
5
+ main :release do
6
+ fname = Dir.glob('meta/*.roll').first
7
+ pname = File.basename(fname).chomp('.roll')
8
+ name, version = *pname.split('-')
9
+
10
+ icli "rubyforge release --version=#{version}"
11
+ end
12
+
@@ -0,0 +1,1616 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Setup and install.
4
+ #
5
+ # setup.rb
6
+ #
7
+ # Copyright (c) 2000-2005 Minero Aoki
8
+ #
9
+ # This program is free software.
10
+ # You can distribute/modify this program under the terms of
11
+ # the GNU LGPL, Lesser General Public License version 2.1.
12
+
13
+ #
14
+ # Was hardcoded as 'InstalledFiles'
15
+ #
16
+
17
+ INSTALLED_MANIFEST = '.installed'
18
+
19
+ #
20
+ # Was hardcoded as 'packages'.
21
+ #
22
+
23
+ PACKAGES_DIRECTORY = 'src'
24
+
25
+ #
26
+ # Locate and move to the root dir of the project/package.
27
+ # This is setup to put setup in a utilities subdir.
28
+ #
29
+ # TODO Make more robust.
30
+ #
31
+
32
+ ROOT_DIRECTORY = File.dirname(File.dirname(File.expand_path($0)))
33
+
34
+ Dir.chdir(ROOT_DIRECTORY)
35
+
36
+ #
37
+ # Deal with Ruby version issues.
38
+ #
39
+
40
+ unless Enumerable.method_defined?(:map) # Ruby 1.4.6
41
+ module Enumerable
42
+ alias map collect
43
+ end
44
+ end
45
+
46
+ unless File.respond_to?(:read) # Ruby 1.6
47
+ def File.read(fname)
48
+ open(fname) {|f|
49
+ return f.read
50
+ }
51
+ end
52
+ end
53
+
54
+ unless Errno.const_defined?(:ENOTEMPTY) # Windows?
55
+ module Errno
56
+ class ENOTEMPTY
57
+ # We do not raise this exception, implementation is not needed.
58
+ end
59
+ end
60
+ end
61
+
62
+ def File.binread(fname)
63
+ open(fname, 'rb') {|f|
64
+ return f.read
65
+ }
66
+ end
67
+
68
+ # for corrupted Windows' stat(2)
69
+ def File.dir?(path)
70
+ File.directory?((path[-1,1] == '/') ? path : path + '/')
71
+ end
72
+
73
+
74
+ class ConfigTable
75
+
76
+ include Enumerable
77
+
78
+ def initialize(rbconfig)
79
+ @rbconfig = rbconfig
80
+ @items = []
81
+ @table = {}
82
+ # options
83
+ @install_prefix = nil
84
+ @config_opt = nil
85
+ @verbose = true
86
+ @no_harm = false
87
+ end
88
+
89
+ attr_accessor :install_prefix
90
+ attr_accessor :config_opt
91
+
92
+ attr_writer :verbose
93
+
94
+ def verbose?
95
+ @verbose
96
+ end
97
+
98
+ attr_writer :no_harm
99
+
100
+ def no_harm?
101
+ @no_harm
102
+ end
103
+
104
+ def [](key)
105
+ lookup(key).resolve(self)
106
+ end
107
+
108
+ def []=(key, val)
109
+ lookup(key).set val
110
+ end
111
+
112
+ def names
113
+ @items.map {|i| i.name }
114
+ end
115
+
116
+ def each(&block)
117
+ @items.each(&block)
118
+ end
119
+
120
+ def key?(name)
121
+ @table.key?(name)
122
+ end
123
+
124
+ def lookup(name)
125
+ @table[name] or setup_rb_error "no such config item: #{name}"
126
+ end
127
+
128
+ def add(item)
129
+ @items.push item
130
+ @table[item.name] = item
131
+ end
132
+
133
+ def remove(name)
134
+ item = lookup(name)
135
+ @items.delete_if {|i| i.name == name }
136
+ @table.delete_if {|name, i| i.name == name }
137
+ item
138
+ end
139
+
140
+ def load_script(path, inst = nil)
141
+ if File.file?(path)
142
+ MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path
143
+ end
144
+ end
145
+
146
+ def savefile
147
+ '.config'
148
+ end
149
+
150
+ def load_savefile
151
+ begin
152
+ File.foreach(savefile()) do |line|
153
+ k, v = *line.split(/=/, 2)
154
+ self[k] = v.strip
155
+ end
156
+ rescue Errno::ENOENT
157
+ setup_rb_error $!.message + "\n#{File.basename($0)} config first"
158
+ end
159
+ end
160
+
161
+ def save
162
+ @items.each {|i| i.value }
163
+ File.open(savefile(), 'w') {|f|
164
+ @items.each do |i|
165
+ f.printf "%s=%s\n", i.name, i.value if i.value? and i.value
166
+ end
167
+ }
168
+ end
169
+
170
+ def load_standard_entries
171
+ standard_entries(@rbconfig).each do |ent|
172
+ add ent
173
+ end
174
+ end
175
+
176
+ def standard_entries(rbconfig)
177
+ c = rbconfig
178
+
179
+ rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT'])
180
+
181
+ major = c['MAJOR'].to_i
182
+ minor = c['MINOR'].to_i
183
+ teeny = c['TEENY'].to_i
184
+ version = "#{major}.#{minor}"
185
+
186
+ # ruby ver. >= 1.4.4?
187
+ newpath_p = ((major >= 2) or
188
+ ((major == 1) and
189
+ ((minor >= 5) or
190
+ ((minor == 4) and (teeny >= 4)))))
191
+
192
+ if c['rubylibdir']
193
+ # V > 1.6.3
194
+ libruby = "#{c['prefix']}/lib/ruby"
195
+ librubyver = c['rubylibdir']
196
+ librubyverarch = c['archdir']
197
+ siteruby = c['sitedir']
198
+ siterubyver = c['sitelibdir']
199
+ siterubyverarch = c['sitearchdir']
200
+ elsif newpath_p
201
+ # 1.4.4 <= V <= 1.6.3
202
+ libruby = "#{c['prefix']}/lib/ruby"
203
+ librubyver = "#{c['prefix']}/lib/ruby/#{version}"
204
+ librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
205
+ siteruby = c['sitedir']
206
+ siterubyver = "$siteruby/#{version}"
207
+ siterubyverarch = "$siterubyver/#{c['arch']}"
208
+ else
209
+ # V < 1.4.4
210
+ libruby = "#{c['prefix']}/lib/ruby"
211
+ librubyver = "#{c['prefix']}/lib/ruby/#{version}"
212
+ librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
213
+ siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby"
214
+ siterubyver = siteruby
215
+ siterubyverarch = "$siterubyver/#{c['arch']}"
216
+ end
217
+ parameterize = lambda {|path|
218
+ path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')
219
+ }
220
+
221
+ if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
222
+ makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
223
+ else
224
+ makeprog = 'make'
225
+ end
226
+
227
+ [
228
+ ExecItem.new('installdirs', 'std/site/home',
229
+ 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\
230
+ {|val, table|
231
+ case val
232
+ when 'std'
233
+ table['rbdir'] = '$librubyver'
234
+ table['sodir'] = '$librubyverarch'
235
+ when 'site'
236
+ table['rbdir'] = '$siterubyver'
237
+ table['sodir'] = '$siterubyverarch'
238
+ when 'home'
239
+ setup_rb_error '$HOME was not set' unless ENV['HOME']
240
+ table['prefix'] = ENV['HOME']
241
+ table['rbdir'] = '$libdir/ruby'
242
+ table['sodir'] = '$libdir/ruby'
243
+ end
244
+ },
245
+ PathItem.new('prefix', 'path', c['prefix'],
246
+ 'path prefix of target environment'),
247
+ PathItem.new('bindir', 'path', parameterize.call(c['bindir']),
248
+ 'the directory for commands'),
249
+ PathItem.new('libdir', 'path', parameterize.call(c['libdir']),
250
+ 'the directory for libraries'),
251
+ PathItem.new('datadir', 'path', parameterize.call(c['datadir']),
252
+ 'the directory for shared data'),
253
+ PathItem.new('mandir', 'path', parameterize.call(c['mandir']),
254
+ 'the directory for man pages'),
255
+ PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']),
256
+ 'the directory for system configuration files'),
257
+ PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']),
258
+ 'the directory for local state data'),
259
+ PathItem.new('libruby', 'path', libruby,
260
+ 'the directory for ruby libraries'),
261
+ PathItem.new('librubyver', 'path', librubyver,
262
+ 'the directory for standard ruby libraries'),
263
+ PathItem.new('librubyverarch', 'path', librubyverarch,
264
+ 'the directory for standard ruby extensions'),
265
+ PathItem.new('siteruby', 'path', siteruby,
266
+ 'the directory for version-independent aux ruby libraries'),
267
+ PathItem.new('siterubyver', 'path', siterubyver,
268
+ 'the directory for aux ruby libraries'),
269
+ PathItem.new('siterubyverarch', 'path', siterubyverarch,
270
+ 'the directory for aux ruby binaries'),
271
+ PathItem.new('rbdir', 'path', '$siterubyver',
272
+ 'the directory for ruby scripts'),
273
+ PathItem.new('sodir', 'path', '$siterubyverarch',
274
+ 'the directory for ruby extentions'),
275
+ PathItem.new('rubypath', 'path', rubypath,
276
+ 'the path to set to #! line'),
277
+ ProgramItem.new('rubyprog', 'name', rubypath,
278
+ 'the ruby program using for installation'),
279
+ ProgramItem.new('makeprog', 'name', makeprog,
280
+ 'the make program to compile ruby extentions'),
281
+ SelectItem.new('shebang', 'all/ruby/never', 'ruby',
282
+ 'shebang line (#!) editing mode'),
283
+ BoolItem.new('without-ext', 'yes/no', 'no',
284
+ 'does not compile/install ruby extentions')
285
+ ]
286
+ end
287
+ private :standard_entries
288
+
289
+ def load_multipackage_entries
290
+ multipackage_entries().each do |ent|
291
+ add ent
292
+ end
293
+ end
294
+
295
+ def multipackage_entries
296
+ [
297
+ PackageSelectionItem.new('with', 'name,name...', '', 'ALL',
298
+ 'package names that you want to install'),
299
+ PackageSelectionItem.new('without', 'name,name...', '', 'NONE',
300
+ 'package names that you do not want to install')
301
+ ]
302
+ end
303
+ private :multipackage_entries
304
+
305
+ ALIASES = {
306
+ 'std-ruby' => 'librubyver',
307
+ 'stdruby' => 'librubyver',
308
+ 'rubylibdir' => 'librubyver',
309
+ 'archdir' => 'librubyverarch',
310
+ 'site-ruby-common' => 'siteruby', # For backward compatibility
311
+ 'site-ruby' => 'siterubyver', # For backward compatibility
312
+ 'bin-dir' => 'bindir',
313
+ 'bin-dir' => 'bindir',
314
+ 'rb-dir' => 'rbdir',
315
+ 'so-dir' => 'sodir',
316
+ 'data-dir' => 'datadir',
317
+ 'ruby-path' => 'rubypath',
318
+ 'ruby-prog' => 'rubyprog',
319
+ 'ruby' => 'rubyprog',
320
+ 'make-prog' => 'makeprog',
321
+ 'make' => 'makeprog'
322
+ }
323
+
324
+ def fixup
325
+ ALIASES.each do |ali, name|
326
+ @table[ali] = @table[name]
327
+ end
328
+ @items.freeze
329
+ @table.freeze
330
+ @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/
331
+ end
332
+
333
+ def parse_opt(opt)
334
+ m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}"
335
+ m.to_a[1,2]
336
+ end
337
+
338
+ def dllext
339
+ @rbconfig['DLEXT']
340
+ end
341
+
342
+ def value_config?(name)
343
+ lookup(name).value?
344
+ end
345
+
346
+ class Item
347
+ def initialize(name, template, default, desc)
348
+ @name = name.freeze
349
+ @template = template
350
+ @value = default
351
+ @default = default
352
+ @description = desc
353
+ end
354
+
355
+ attr_reader :name
356
+ attr_reader :description
357
+
358
+ attr_accessor :default
359
+ alias help_default default
360
+
361
+ def help_opt
362
+ "--#{@name}=#{@template}"
363
+ end
364
+
365
+ def value?
366
+ true
367
+ end
368
+
369
+ def value
370
+ @value
371
+ end
372
+
373
+ def resolve(table)
374
+ @value.gsub(%r<\$([^/]+)>) { table[$1] }
375
+ end
376
+
377
+ def set(val)
378
+ @value = check(val)
379
+ end
380
+
381
+ private
382
+
383
+ def check(val)
384
+ setup_rb_error "config: --#{name} requires argument" unless val
385
+ val
386
+ end
387
+ end
388
+
389
+ class BoolItem < Item
390
+ def config_type
391
+ 'bool'
392
+ end
393
+
394
+ def help_opt
395
+ "--#{@name}"
396
+ end
397
+
398
+ private
399
+
400
+ def check(val)
401
+ return 'yes' unless val
402
+ case val
403
+ when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes'
404
+ when /\An(o)?\z/i, /\Af(alse)\z/i then 'no'
405
+ else
406
+ setup_rb_error "config: --#{@name} accepts only yes/no for argument"
407
+ end
408
+ end
409
+ end
410
+
411
+ class PathItem < Item
412
+ def config_type
413
+ 'path'
414
+ end
415
+
416
+ private
417
+
418
+ def check(path)
419
+ setup_rb_error "config: --#{@name} requires argument" unless path
420
+ path[0,1] == '$' ? path : File.expand_path(path)
421
+ end
422
+ end
423
+
424
+ class ProgramItem < Item
425
+ def config_type
426
+ 'program'
427
+ end
428
+ end
429
+
430
+ class SelectItem < Item
431
+ def initialize(name, selection, default, desc)
432
+ super
433
+ @ok = selection.split('/')
434
+ end
435
+
436
+ def config_type
437
+ 'select'
438
+ end
439
+
440
+ private
441
+
442
+ def check(val)
443
+ unless @ok.include?(val.strip)
444
+ setup_rb_error "config: use --#{@name}=#{@template} (#{val})"
445
+ end
446
+ val.strip
447
+ end
448
+ end
449
+
450
+ class ExecItem < Item
451
+ def initialize(name, selection, desc, &block)
452
+ super name, selection, nil, desc
453
+ @ok = selection.split('/')
454
+ @action = block
455
+ end
456
+
457
+ def config_type
458
+ 'exec'
459
+ end
460
+
461
+ def value?
462
+ false
463
+ end
464
+
465
+ def resolve(table)
466
+ setup_rb_error "$#{name()} wrongly used as option value"
467
+ end
468
+
469
+ undef set
470
+
471
+ def evaluate(val, table)
472
+ v = val.strip.downcase
473
+ unless @ok.include?(v)
474
+ setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})"
475
+ end
476
+ @action.call v, table
477
+ end
478
+ end
479
+
480
+ class PackageSelectionItem < Item
481
+ def initialize(name, template, default, help_default, desc)
482
+ super name, template, default, desc
483
+ @help_default = help_default
484
+ end
485
+
486
+ attr_reader :help_default
487
+
488
+ def config_type
489
+ 'package'
490
+ end
491
+
492
+ private
493
+
494
+ def check(val)
495
+ unless File.dir?("#{PACKAGES_DIRECTORY}/#{val}")
496
+ setup_rb_error "config: no such package: #{val}"
497
+ end
498
+ val
499
+ end
500
+ end
501
+
502
+ class MetaConfigEnvironment
503
+ def initialize(config, installer)
504
+ @config = config
505
+ @installer = installer
506
+ end
507
+
508
+ def config_names
509
+ @config.names
510
+ end
511
+
512
+ def config?(name)
513
+ @config.key?(name)
514
+ end
515
+
516
+ def bool_config?(name)
517
+ @config.lookup(name).config_type == 'bool'
518
+ end
519
+
520
+ def path_config?(name)
521
+ @config.lookup(name).config_type == 'path'
522
+ end
523
+
524
+ def value_config?(name)
525
+ @config.lookup(name).config_type != 'exec'
526
+ end
527
+
528
+ def add_config(item)
529
+ @config.add item
530
+ end
531
+
532
+ def add_bool_config(name, default, desc)
533
+ @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc)
534
+ end
535
+
536
+ def add_path_config(name, default, desc)
537
+ @config.add PathItem.new(name, 'path', default, desc)
538
+ end
539
+
540
+ def set_config_default(name, default)
541
+ @config.lookup(name).default = default
542
+ end
543
+
544
+ def remove_config(name)
545
+ @config.remove(name)
546
+ end
547
+
548
+ # For only multipackage
549
+ def packages
550
+ raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer
551
+ @installer.packages
552
+ end
553
+
554
+ # For only multipackage
555
+ def declare_packages(list)
556
+ raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer
557
+ @installer.packages = list
558
+ end
559
+ end
560
+
561
+ end # class ConfigTable
562
+
563
+
564
+ # This module requires: #verbose?, #no_harm?
565
+ module FileOperations
566
+
567
+ def mkdir_p(dirname, prefix = nil)
568
+ dirname = prefix + File.expand_path(dirname) if prefix
569
+ $stderr.puts "mkdir -p #{dirname}" if verbose?
570
+ return if no_harm?
571
+
572
+ # Does not check '/', it's too abnormal.
573
+ dirs = File.expand_path(dirname).split(%r<(?=/)>)
574
+ if /\A[a-z]:\z/i =~ dirs[0]
575
+ disk = dirs.shift
576
+ dirs[0] = disk + dirs[0]
577
+ end
578
+ dirs.each_index do |idx|
579
+ path = dirs[0..idx].join('')
580
+ Dir.mkdir path unless File.dir?(path)
581
+ end
582
+ end
583
+
584
+ def rm_f(path)
585
+ $stderr.puts "rm -f #{path}" if verbose?
586
+ return if no_harm?
587
+ force_remove_file path
588
+ end
589
+
590
+ def rm_rf(path)
591
+ $stderr.puts "rm -rf #{path}" if verbose?
592
+ return if no_harm?
593
+ remove_tree path
594
+ end
595
+
596
+ def remove_tree(path)
597
+ if File.symlink?(path)
598
+ remove_file path
599
+ elsif File.dir?(path)
600
+ remove_tree0 path
601
+ else
602
+ force_remove_file path
603
+ end
604
+ end
605
+
606
+ def remove_tree0(path)
607
+ Dir.foreach(path) do |ent|
608
+ next if ent == '.'
609
+ next if ent == '..'
610
+ entpath = "#{path}/#{ent}"
611
+ if File.symlink?(entpath)
612
+ remove_file entpath
613
+ elsif File.dir?(entpath)
614
+ remove_tree0 entpath
615
+ else
616
+ force_remove_file entpath
617
+ end
618
+ end
619
+ begin
620
+ Dir.rmdir path
621
+ rescue Errno::ENOTEMPTY
622
+ # directory may not be empty
623
+ end
624
+ end
625
+
626
+ def move_file(src, dest)
627
+ force_remove_file dest
628
+ begin
629
+ File.rename src, dest
630
+ rescue
631
+ File.open(dest, 'wb') {|f|
632
+ f.write File.binread(src)
633
+ }
634
+ File.chmod File.stat(src).mode, dest
635
+ File.unlink src
636
+ end
637
+ end
638
+
639
+ def force_remove_file(path)
640
+ begin
641
+ remove_file path
642
+ rescue
643
+ end
644
+ end
645
+
646
+ def remove_file(path)
647
+ File.chmod 0777, path
648
+ File.unlink path
649
+ end
650
+
651
+ def install(from, dest, mode, prefix = nil)
652
+ $stderr.puts "install #{from} #{dest}" if verbose?
653
+ return if no_harm?
654
+
655
+ realdest = prefix ? prefix + File.expand_path(dest) : dest
656
+ realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
657
+ str = File.binread(from)
658
+ if diff?(str, realdest)
659
+ verbose_off {
660
+ rm_f realdest if File.exist?(realdest)
661
+ }
662
+ File.open(realdest, 'wb') {|f|
663
+ f.write str
664
+ }
665
+ File.chmod mode, realdest
666
+
667
+ File.open("#{objdir_root()}/#{INSTALLED_MANIFEST}", 'a') {|f|
668
+ if prefix
669
+ f.puts realdest.sub(prefix, '')
670
+ else
671
+ f.puts realdest
672
+ end
673
+ }
674
+ end
675
+ end
676
+
677
+ def diff?(new_content, path)
678
+ return true unless File.exist?(path)
679
+ new_content != File.binread(path)
680
+ end
681
+
682
+ def command(*args)
683
+ $stderr.puts args.join(' ') if verbose?
684
+ system(*args) or raise RuntimeError,
685
+ "system(#{args.map{|a| a.inspect }.join(' ')}) failed"
686
+ end
687
+
688
+ def ruby(*args)
689
+ command config('rubyprog'), *args
690
+ end
691
+
692
+ def make(task = nil)
693
+ command(*[config('makeprog'), task].compact)
694
+ end
695
+
696
+ def extdir?(dir)
697
+ File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb")
698
+ end
699
+
700
+ def files_of(dir)
701
+ Dir.open(dir) {|d|
702
+ return d.select {|ent| File.file?("#{dir}/#{ent}") }
703
+ }
704
+ end
705
+
706
+ DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn )
707
+
708
+ def directories_of(dir)
709
+ Dir.open(dir) {|d|
710
+ return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT
711
+ }
712
+ end
713
+
714
+ end
715
+
716
+
717
+ # This module requires: #srcdir_root, #objdir_root, #relpath
718
+ module HookScriptAPI
719
+
720
+ def get_config(key)
721
+ @config[key]
722
+ end
723
+
724
+ alias config get_config
725
+
726
+ # obsolete: use metaconfig to change configuration
727
+ def set_config(key, val)
728
+ @config[key] = val
729
+ end
730
+
731
+ #
732
+ # srcdir/objdir (works only in the package directory)
733
+ #
734
+
735
+ def curr_srcdir
736
+ "#{srcdir_root()}/#{relpath()}"
737
+ end
738
+
739
+ def curr_objdir
740
+ "#{objdir_root()}/#{relpath()}"
741
+ end
742
+
743
+ def srcfile(path)
744
+ "#{curr_srcdir()}/#{path}"
745
+ end
746
+
747
+ def srcexist?(path)
748
+ File.exist?(srcfile(path))
749
+ end
750
+
751
+ def srcdirectory?(path)
752
+ File.dir?(srcfile(path))
753
+ end
754
+
755
+ def srcfile?(path)
756
+ File.file?(srcfile(path))
757
+ end
758
+
759
+ def srcentries(path = '.')
760
+ Dir.open("#{curr_srcdir()}/#{path}") {|d|
761
+ return d.to_a - %w(. ..)
762
+ }
763
+ end
764
+
765
+ def srcfiles(path = '.')
766
+ srcentries(path).select {|fname|
767
+ File.file?(File.join(curr_srcdir(), path, fname))
768
+ }
769
+ end
770
+
771
+ def srcdirectories(path = '.')
772
+ srcentries(path).select {|fname|
773
+ File.dir?(File.join(curr_srcdir(), path, fname))
774
+ }
775
+ end
776
+
777
+ end
778
+
779
+
780
+ class ToplevelInstaller
781
+
782
+ Version = '3.4.1'
783
+ Copyright = 'Copyright (c) 2000-2005 Minero Aoki'
784
+
785
+ TASKS = [
786
+ [ 'all', 'do config, setup, then install' ],
787
+ [ 'config', 'saves your configurations' ],
788
+ [ 'show', 'shows current configuration' ],
789
+ [ 'setup', 'compiles ruby extentions and others' ],
790
+ [ 'install', 'installs files' ],
791
+ [ 'test', 'run all tests in test/' ],
792
+ [ 'clean', "does `make clean' for each extention" ],
793
+ [ 'distclean',"does `make distclean' for each extention" ]
794
+ ]
795
+
796
+ def ToplevelInstaller.invoke
797
+ config = ConfigTable.new(load_rbconfig())
798
+ config.load_standard_entries
799
+ config.load_multipackage_entries if multipackage?
800
+ config.fixup
801
+ klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller)
802
+ #klass.new(File.dirname($0), config).invoke
803
+ klass.new(ROOT_DIRECTORY, config).invoke
804
+ end
805
+
806
+ def ToplevelInstaller.multipackage?
807
+ #File.dir?(File.dirname($0) + '/#{PACKAGES_DIRECTORY}')
808
+ File.dir?(ROOT_DIRECTORY + "/#{PACKAGES_DIRECTORY}")
809
+ end
810
+
811
+ def ToplevelInstaller.load_rbconfig
812
+ if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg }
813
+ ARGV.delete(arg)
814
+ load File.expand_path(arg.split(/=/, 2)[1])
815
+ $".push 'rbconfig.rb'
816
+ else
817
+ require 'rbconfig'
818
+ end
819
+ ::Config::CONFIG
820
+ end
821
+
822
+ def initialize(ardir_root, config)
823
+ @ardir = File.expand_path(ardir_root)
824
+ @config = config
825
+ # cache
826
+ @valid_task_re = nil
827
+ end
828
+
829
+ def config(key)
830
+ @config[key]
831
+ end
832
+
833
+ def inspect
834
+ "#<#{self.class} #{__id__()}>"
835
+ end
836
+
837
+ def invoke
838
+ run_metaconfigs
839
+ case task = parsearg_global()
840
+ when nil, 'all'
841
+ parsearg_config
842
+ init_installers
843
+ exec_config
844
+ exec_setup
845
+ exec_install
846
+ else
847
+ case task
848
+ when 'config', 'test'
849
+ ;
850
+ when 'clean', 'distclean'
851
+ @config.load_savefile if File.exist?(@config.savefile)
852
+ else
853
+ @config.load_savefile
854
+ end
855
+ __send__ "parsearg_#{task}"
856
+ init_installers
857
+ __send__ "exec_#{task}"
858
+ end
859
+ end
860
+
861
+ def run_metaconfigs
862
+ @config.load_script "#{@ardir}/metaconfig"
863
+ end
864
+
865
+ def init_installers
866
+ @installer = Installer.new(@config, @ardir, File.expand_path('.'))
867
+ end
868
+
869
+ #
870
+ # Hook Script API bases
871
+ #
872
+
873
+ def srcdir_root
874
+ @ardir
875
+ end
876
+
877
+ def objdir_root
878
+ '.'
879
+ end
880
+
881
+ def relpath
882
+ '.'
883
+ end
884
+
885
+ #
886
+ # Option Parsing
887
+ #
888
+
889
+ def parsearg_global
890
+ while arg = ARGV.shift
891
+ case arg
892
+ when /\A\w+\z/
893
+ setup_rb_error "invalid task: #{arg}" unless valid_task?(arg)
894
+ return arg
895
+ when '-q', '--quiet'
896
+ @config.verbose = false
897
+ when '--verbose'
898
+ @config.verbose = true
899
+ when '--help'
900
+ print_usage $stdout
901
+ exit 0
902
+ when '--version'
903
+ puts "#{File.basename($0)} version #{Version}"
904
+ exit 0
905
+ when '--copyright'
906
+ puts Copyright
907
+ exit 0
908
+ else
909
+ setup_rb_error "unknown global option '#{arg}'"
910
+ end
911
+ end
912
+ nil
913
+ end
914
+
915
+ def valid_task?(t)
916
+ valid_task_re() =~ t
917
+ end
918
+
919
+ def valid_task_re
920
+ @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/
921
+ end
922
+
923
+ def parsearg_no_options
924
+ unless ARGV.empty?
925
+ task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1)
926
+ setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}"
927
+ end
928
+ end
929
+
930
+ alias parsearg_show parsearg_no_options
931
+ alias parsearg_setup parsearg_no_options
932
+ alias parsearg_test parsearg_no_options
933
+ alias parsearg_clean parsearg_no_options
934
+ alias parsearg_distclean parsearg_no_options
935
+
936
+ def parsearg_config
937
+ evalopt = []
938
+ set = []
939
+ @config.config_opt = []
940
+ while i = ARGV.shift
941
+ if /\A--?\z/ =~ i
942
+ @config.config_opt = ARGV.dup
943
+ break
944
+ end
945
+ name, value = *@config.parse_opt(i)
946
+ if @config.value_config?(name)
947
+ @config[name] = value
948
+ else
949
+ evalopt.push [name, value]
950
+ end
951
+ set.push name
952
+ end
953
+ evalopt.each do |name, value|
954
+ @config.lookup(name).evaluate value, @config
955
+ end
956
+ # Check if configuration is valid
957
+ set.each do |n|
958
+ @config[n] if @config.value_config?(n)
959
+ end
960
+ end
961
+
962
+ def parsearg_install
963
+ @config.no_harm = false
964
+ @config.install_prefix = ''
965
+ while a = ARGV.shift
966
+ case a
967
+ when '--no-harm'
968
+ @config.no_harm = true
969
+ when /\A--prefix=/
970
+ path = a.split(/=/, 2)[1]
971
+ path = File.expand_path(path) unless path[0,1] == '/'
972
+ @config.install_prefix = path
973
+ else
974
+ setup_rb_error "install: unknown option #{a}"
975
+ end
976
+ end
977
+ end
978
+
979
+ def print_usage(out)
980
+ out.puts 'Typical Installation Procedure:'
981
+ out.puts " $ ruby #{File.basename $0} config"
982
+ out.puts " $ ruby #{File.basename $0} setup"
983
+ out.puts " # ruby #{File.basename $0} install (may require root privilege)"
984
+ out.puts
985
+ out.puts 'Detailed Usage:'
986
+ out.puts " ruby #{File.basename $0} <global option>"
987
+ out.puts " ruby #{File.basename $0} [<global options>] <task> [<task options>]"
988
+
989
+ fmt = " %-24s %s\n"
990
+ out.puts
991
+ out.puts 'Global options:'
992
+ out.printf fmt, '-q,--quiet', 'suppress message outputs'
993
+ out.printf fmt, ' --verbose', 'output messages verbosely'
994
+ out.printf fmt, ' --help', 'print this message'
995
+ out.printf fmt, ' --version', 'print version and quit'
996
+ out.printf fmt, ' --copyright', 'print copyright and quit'
997
+ out.puts
998
+ out.puts 'Tasks:'
999
+ TASKS.each do |name, desc|
1000
+ out.printf fmt, name, desc
1001
+ end
1002
+
1003
+ fmt = " %-24s %s [%s]\n"
1004
+ out.puts
1005
+ out.puts 'Options for CONFIG or ALL:'
1006
+ @config.each do |item|
1007
+ out.printf fmt, item.help_opt, item.description, item.help_default
1008
+ end
1009
+ out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's"
1010
+ out.puts
1011
+ out.puts 'Options for INSTALL:'
1012
+ out.printf fmt, '--no-harm', 'only display what to do if given', 'off'
1013
+ out.printf fmt, '--prefix=path', 'install path prefix', ''
1014
+ out.puts
1015
+ end
1016
+
1017
+ #
1018
+ # Task Handlers
1019
+ #
1020
+
1021
+ def exec_config
1022
+ @installer.exec_config
1023
+ @config.save # must be final
1024
+ end
1025
+
1026
+ def exec_setup
1027
+ @installer.exec_setup
1028
+ end
1029
+
1030
+ def exec_install
1031
+ @installer.exec_install
1032
+ end
1033
+
1034
+ def exec_test
1035
+ @installer.exec_test
1036
+ end
1037
+
1038
+ def exec_show
1039
+ @config.each do |i|
1040
+ printf "%-20s %s\n", i.name, i.value if i.value?
1041
+ end
1042
+ end
1043
+
1044
+ def exec_clean
1045
+ @installer.exec_clean
1046
+ end
1047
+
1048
+ def exec_distclean
1049
+ @installer.exec_distclean
1050
+ end
1051
+
1052
+ end # class ToplevelInstaller
1053
+
1054
+
1055
+ class ToplevelInstallerMulti < ToplevelInstaller
1056
+
1057
+ include FileOperations
1058
+
1059
+ def initialize(ardir_root, config)
1060
+ super
1061
+ @packages = directories_of("#{@ardir}/#{PACKAGES_DIRECTORY}")
1062
+ raise 'no package exists' if @packages.empty?
1063
+ @root_installer = Installer.new(@config, @ardir, File.expand_path('.'))
1064
+ end
1065
+
1066
+ def run_metaconfigs
1067
+ @config.load_script "#{@ardir}/metaconfig", self
1068
+ @packages.each do |name|
1069
+ @config.load_script "#{@ardir}/#{PACKAGES_DIRECTORY}/#{name}/metaconfig"
1070
+ end
1071
+ end
1072
+
1073
+ attr_reader :packages
1074
+
1075
+ def packages=(list)
1076
+ raise 'package list is empty' if list.empty?
1077
+ list.each do |name|
1078
+ raise "directory #{PACKAGES_DIRECTORY}/#{name} does not exist"\
1079
+ unless File.dir?("#{@ardir}/#{PACKAGES_DIRECTORY}/#{name}")
1080
+ end
1081
+ @packages = list
1082
+ end
1083
+
1084
+ def init_installers
1085
+ @installers = {}
1086
+ @packages.each do |pack|
1087
+ @installers[pack] = Installer.new(@config,
1088
+ "#{@ardir}/#{PACKAGES_DIRECTORY}/#{pack}",
1089
+ "#{PACKAGES_DIRECTORY}/#{pack}")
1090
+ end
1091
+ with = extract_selection(config('with'))
1092
+ without = extract_selection(config('without'))
1093
+ @selected = @installers.keys.select {|name|
1094
+ (with.empty? or with.include?(name)) \
1095
+ and not without.include?(name)
1096
+ }
1097
+ end
1098
+
1099
+ def extract_selection(list)
1100
+ a = list.split(/,/)
1101
+ a.each do |name|
1102
+ setup_rb_error "no such package: #{name}" unless @installers.key?(name)
1103
+ end
1104
+ a
1105
+ end
1106
+
1107
+ def print_usage(f)
1108
+ super
1109
+ f.puts 'Inluded packages:'
1110
+ f.puts ' ' + @packages.sort.join(' ')
1111
+ f.puts
1112
+ end
1113
+
1114
+ #
1115
+ # Task Handlers
1116
+ #
1117
+
1118
+ def exec_config
1119
+ run_hook 'pre-config'
1120
+ each_selected_installers {|inst| inst.exec_config }
1121
+ run_hook 'post-config'
1122
+ @config.save # must be final
1123
+ end
1124
+
1125
+ def exec_setup
1126
+ run_hook 'pre-setup'
1127
+ each_selected_installers {|inst| inst.exec_setup }
1128
+ run_hook 'post-setup'
1129
+ end
1130
+
1131
+ def exec_install
1132
+ run_hook 'pre-install'
1133
+ each_selected_installers {|inst| inst.exec_install }
1134
+ run_hook 'post-install'
1135
+ end
1136
+
1137
+ def exec_test
1138
+ run_hook 'pre-test'
1139
+ each_selected_installers {|inst| inst.exec_test }
1140
+ run_hook 'post-test'
1141
+ end
1142
+
1143
+ def exec_clean
1144
+ rm_f @config.savefile
1145
+ run_hook 'pre-clean'
1146
+ each_selected_installers {|inst| inst.exec_clean }
1147
+ run_hook 'post-clean'
1148
+ end
1149
+
1150
+ def exec_distclean
1151
+ rm_f @config.savefile
1152
+ run_hook 'pre-distclean'
1153
+ each_selected_installers {|inst| inst.exec_distclean }
1154
+ run_hook 'post-distclean'
1155
+ end
1156
+
1157
+ #
1158
+ # lib
1159
+ #
1160
+
1161
+ def each_selected_installers
1162
+ Dir.mkdir PACKAGES_DIRECTORY unless File.dir?(PACKAGES_DIRECTORY)
1163
+ @selected.each do |pack|
1164
+ $stderr.puts "Processing the package `#{pack}' ..." if verbose?
1165
+ Dir.mkdir "#{PACKAGES_DIRECTORY}/#{pack}" unless File.dir?("#{PACKAGES_DIRECTORY}/#{pack}")
1166
+ Dir.chdir "#{PACKAGES_DIRECTORY}/#{pack}"
1167
+ yield @installers[pack]
1168
+ Dir.chdir '../..'
1169
+ end
1170
+ end
1171
+
1172
+ def run_hook(id)
1173
+ @root_installer.run_hook id
1174
+ end
1175
+
1176
+ # module FileOperations requires this
1177
+ def verbose?
1178
+ @config.verbose?
1179
+ end
1180
+
1181
+ # module FileOperations requires this
1182
+ def no_harm?
1183
+ @config.no_harm?
1184
+ end
1185
+
1186
+ end # class ToplevelInstallerMulti
1187
+
1188
+
1189
+ class Installer
1190
+
1191
+ FILETYPES = %w( bin lib ext data conf man )
1192
+
1193
+ include FileOperations
1194
+ include HookScriptAPI
1195
+
1196
+ def initialize(config, srcroot, objroot)
1197
+ @config = config
1198
+ @srcdir = File.expand_path(srcroot)
1199
+ @objdir = File.expand_path(objroot)
1200
+ @currdir = '.'
1201
+ end
1202
+
1203
+ def inspect
1204
+ "#<#{self.class} #{File.basename(@srcdir)}>"
1205
+ end
1206
+
1207
+ def noop(rel)
1208
+ end
1209
+
1210
+ #
1211
+ # Hook Script API base methods
1212
+ #
1213
+
1214
+ def srcdir_root
1215
+ @srcdir
1216
+ end
1217
+
1218
+ def objdir_root
1219
+ @objdir
1220
+ end
1221
+
1222
+ def relpath
1223
+ @currdir
1224
+ end
1225
+
1226
+ #
1227
+ # Config Access
1228
+ #
1229
+
1230
+ # module FileOperations requires this
1231
+ def verbose?
1232
+ @config.verbose?
1233
+ end
1234
+
1235
+ # module FileOperations requires this
1236
+ def no_harm?
1237
+ @config.no_harm?
1238
+ end
1239
+
1240
+ def verbose_off
1241
+ begin
1242
+ save, @config.verbose = @config.verbose?, false
1243
+ yield
1244
+ ensure
1245
+ @config.verbose = save
1246
+ end
1247
+ end
1248
+
1249
+ #
1250
+ # TASK config
1251
+ #
1252
+
1253
+ def exec_config
1254
+ exec_task_traverse 'config'
1255
+ end
1256
+
1257
+ alias config_dir_bin noop
1258
+ alias config_dir_lib noop
1259
+
1260
+ def config_dir_ext(rel)
1261
+ extconf if extdir?(curr_srcdir())
1262
+ end
1263
+
1264
+ alias config_dir_data noop
1265
+ alias config_dir_conf noop
1266
+ alias config_dir_man noop
1267
+
1268
+ def extconf
1269
+ ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt
1270
+ end
1271
+
1272
+ #
1273
+ # TASK setup
1274
+ #
1275
+
1276
+ def exec_setup
1277
+ exec_task_traverse 'setup'
1278
+ end
1279
+
1280
+ def setup_dir_bin(rel)
1281
+ files_of(curr_srcdir()).each do |fname|
1282
+ update_shebang_line "#{curr_srcdir()}/#{fname}"
1283
+ end
1284
+ end
1285
+
1286
+ alias setup_dir_lib noop
1287
+
1288
+ def setup_dir_ext(rel)
1289
+ make if extdir?(curr_srcdir())
1290
+ end
1291
+
1292
+ alias setup_dir_data noop
1293
+ alias setup_dir_conf noop
1294
+ alias setup_dir_man noop
1295
+
1296
+ def update_shebang_line(path)
1297
+ return if no_harm?
1298
+ return if config('shebang') == 'never'
1299
+ old = Shebang.load(path)
1300
+ if old
1301
+ $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
1302
+ new = new_shebang(old)
1303
+ return if new.to_s == old.to_s
1304
+ else
1305
+ return unless config('shebang') == 'all'
1306
+ new = Shebang.new(config('rubypath'))
1307
+ end
1308
+ $stderr.puts "updating shebang: #{File.basename(path)}" if verbose?
1309
+ open_atomic_writer(path) {|output|
1310
+ File.open(path, 'rb') {|f|
1311
+ f.gets if old # discard
1312
+ output.puts new.to_s
1313
+ output.print f.read
1314
+ }
1315
+ }
1316
+ end
1317
+
1318
+ def new_shebang(old)
1319
+ if /\Aruby/ =~ File.basename(old.cmd)
1320
+ Shebang.new(config('rubypath'), old.args)
1321
+ elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby'
1322
+ Shebang.new(config('rubypath'), old.args[1..-1])
1323
+ else
1324
+ return old unless config('shebang') == 'all'
1325
+ Shebang.new(config('rubypath'))
1326
+ end
1327
+ end
1328
+
1329
+ def open_atomic_writer(path, &block)
1330
+ tmpfile = File.basename(path) + '.tmp'
1331
+ begin
1332
+ File.open(tmpfile, 'wb', &block)
1333
+ File.rename tmpfile, File.basename(path)
1334
+ ensure
1335
+ File.unlink tmpfile if File.exist?(tmpfile)
1336
+ end
1337
+ end
1338
+
1339
+ class Shebang
1340
+ def Shebang.load(path)
1341
+ line = nil
1342
+ File.open(path) {|f|
1343
+ line = f.gets
1344
+ }
1345
+ return nil unless /\A#!/ =~ line
1346
+ parse(line)
1347
+ end
1348
+
1349
+ def Shebang.parse(line)
1350
+ cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ')
1351
+ new(cmd, args)
1352
+ end
1353
+
1354
+ def initialize(cmd, args = [])
1355
+ @cmd = cmd
1356
+ @args = args
1357
+ end
1358
+
1359
+ attr_reader :cmd
1360
+ attr_reader :args
1361
+
1362
+ def to_s
1363
+ "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}")
1364
+ end
1365
+ end
1366
+
1367
+ #
1368
+ # TASK install
1369
+ #
1370
+
1371
+ def exec_install
1372
+ rm_f "#{INSTALLED_MANIFEST}"
1373
+ exec_task_traverse 'install'
1374
+ end
1375
+
1376
+ def install_dir_bin(rel)
1377
+ install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755
1378
+ end
1379
+
1380
+ def install_dir_lib(rel)
1381
+ install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644
1382
+ end
1383
+
1384
+ def install_dir_ext(rel)
1385
+ return unless extdir?(curr_srcdir())
1386
+ install_files rubyextentions('.'),
1387
+ "#{config('sodir')}/#{File.dirname(rel)}",
1388
+ 0555
1389
+ end
1390
+
1391
+ def install_dir_data(rel)
1392
+ install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644
1393
+ end
1394
+
1395
+ def install_dir_conf(rel)
1396
+ # FIXME: should not remove current config files
1397
+ # (rename previous file to .old/.org)
1398
+ install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644
1399
+ end
1400
+
1401
+ def install_dir_man(rel)
1402
+ install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644
1403
+ end
1404
+
1405
+ def install_files(list, dest, mode)
1406
+ mkdir_p dest, @config.install_prefix
1407
+ list.each do |fname|
1408
+ install fname, dest, mode, @config.install_prefix
1409
+ end
1410
+ end
1411
+
1412
+ def libfiles
1413
+ glob_reject(%w(*.y *.output), targetfiles())
1414
+ end
1415
+
1416
+ def rubyextentions(dir)
1417
+ ents = glob_select("*.#{@config.dllext}", targetfiles())
1418
+ if ents.empty?
1419
+ setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first"
1420
+ end
1421
+ ents
1422
+ end
1423
+
1424
+ def targetfiles
1425
+ mapdir(existfiles() - hookfiles())
1426
+ end
1427
+
1428
+ def mapdir(ents)
1429
+ ents.map {|ent|
1430
+ if File.exist?(ent)
1431
+ then ent # objdir
1432
+ else "#{curr_srcdir()}/#{ent}" # srcdir
1433
+ end
1434
+ }
1435
+ end
1436
+
1437
+ # picked up many entries from cvs-1.11.1/src/ignore.c
1438
+ JUNK_FILES = %w(
1439
+ core RCSLOG tags TAGS .make.state
1440
+ .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
1441
+ *~ *.old *.bak *.BAK *.orig *.rej _$* *$
1442
+
1443
+ *.org *.in .*
1444
+ )
1445
+
1446
+ def existfiles
1447
+ glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.')))
1448
+ end
1449
+
1450
+ def hookfiles
1451
+ %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
1452
+ %w( config setup install clean ).map {|t| sprintf(fmt, t) }
1453
+ }.flatten
1454
+ end
1455
+
1456
+ def glob_select(pat, ents)
1457
+ re = globs2re([pat])
1458
+ ents.select {|ent| re =~ ent }
1459
+ end
1460
+
1461
+ def glob_reject(pats, ents)
1462
+ re = globs2re(pats)
1463
+ ents.reject {|ent| re =~ ent }
1464
+ end
1465
+
1466
+ GLOB2REGEX = {
1467
+ '.' => '\.',
1468
+ '$' => '\$',
1469
+ '#' => '\#',
1470
+ '*' => '.*'
1471
+ }
1472
+
1473
+ def globs2re(pats)
1474
+ /\A(?:#{
1475
+ pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|')
1476
+ })\z/
1477
+ end
1478
+
1479
+ #
1480
+ # TASK test
1481
+ #
1482
+
1483
+ TESTDIR = 'test'
1484
+
1485
+ def exec_test
1486
+ unless File.directory?('test')
1487
+ $stderr.puts 'no test in this package' if verbose?
1488
+ return
1489
+ end
1490
+ $stderr.puts 'Running tests...' if verbose?
1491
+ begin
1492
+ require 'test/unit'
1493
+ rescue LoadError
1494
+ setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.'
1495
+ end
1496
+ runner = Test::Unit::AutoRunner.new(true)
1497
+ runner.to_run << TESTDIR
1498
+ runner.run
1499
+ end
1500
+
1501
+ #
1502
+ # TASK clean
1503
+ #
1504
+
1505
+ def exec_clean
1506
+ exec_task_traverse 'clean'
1507
+ rm_f @config.savefile
1508
+ rm_f "#{INSTALLED_MANIFEST}"
1509
+ end
1510
+
1511
+ alias clean_dir_bin noop
1512
+ alias clean_dir_lib noop
1513
+ alias clean_dir_data noop
1514
+ alias clean_dir_conf noop
1515
+ alias clean_dir_man noop
1516
+
1517
+ def clean_dir_ext(rel)
1518
+ return unless extdir?(curr_srcdir())
1519
+ make 'clean' if File.file?('Makefile')
1520
+ end
1521
+
1522
+ #
1523
+ # TASK distclean
1524
+ #
1525
+
1526
+ def exec_distclean
1527
+ exec_task_traverse 'distclean'
1528
+ rm_f @config.savefile
1529
+ rm_f "#{INSTALLED_MANIFEST}"
1530
+ end
1531
+
1532
+ alias distclean_dir_bin noop
1533
+ alias distclean_dir_lib noop
1534
+
1535
+ def distclean_dir_ext(rel)
1536
+ return unless extdir?(curr_srcdir())
1537
+ make 'distclean' if File.file?('Makefile')
1538
+ end
1539
+
1540
+ alias distclean_dir_data noop
1541
+ alias distclean_dir_conf noop
1542
+ alias distclean_dir_man noop
1543
+
1544
+ #
1545
+ # Traversing
1546
+ #
1547
+
1548
+ def exec_task_traverse(task)
1549
+ run_hook "pre-#{task}"
1550
+ FILETYPES.each do |type|
1551
+ if type == 'ext' and config('without-ext') == 'yes'
1552
+ $stderr.puts 'skipping ext/* by user option' if verbose?
1553
+ next
1554
+ end
1555
+ traverse task, type, "#{task}_dir_#{type}"
1556
+ end
1557
+ run_hook "post-#{task}"
1558
+ end
1559
+
1560
+ def traverse(task, rel, mid)
1561
+ dive_into(rel) {
1562
+ run_hook "pre-#{task}"
1563
+ __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
1564
+ directories_of(curr_srcdir()).each do |d|
1565
+ traverse task, "#{rel}/#{d}", mid
1566
+ end
1567
+ run_hook "post-#{task}"
1568
+ }
1569
+ end
1570
+
1571
+ def dive_into(rel)
1572
+ return unless File.dir?("#{@srcdir}/#{rel}")
1573
+
1574
+ dir = File.basename(rel)
1575
+ Dir.mkdir dir unless File.dir?(dir)
1576
+ prevdir = Dir.pwd
1577
+ Dir.chdir dir
1578
+ $stderr.puts '---> ' + rel if verbose?
1579
+ @currdir = rel
1580
+ yield
1581
+ Dir.chdir prevdir
1582
+ $stderr.puts '<--- ' + rel if verbose?
1583
+ @currdir = File.dirname(rel)
1584
+ end
1585
+
1586
+ def run_hook(id)
1587
+ path = [ "#{curr_srcdir()}/#{id}",
1588
+ "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) }
1589
+ return unless path
1590
+ begin
1591
+ instance_eval File.read(path), path, 1
1592
+ rescue
1593
+ raise if $DEBUG
1594
+ setup_rb_error "hook #{path} failed:\n" + $!.message
1595
+ end
1596
+ end
1597
+
1598
+ end # class Installer
1599
+
1600
+
1601
+ class SetupError < StandardError; end
1602
+
1603
+ def setup_rb_error(msg)
1604
+ raise SetupError, msg
1605
+ end
1606
+
1607
+ if $0 == __FILE__
1608
+ begin
1609
+ ToplevelInstaller.invoke
1610
+ rescue SetupError
1611
+ raise if $DEBUG
1612
+ $stderr.puts $!.message
1613
+ $stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
1614
+ exit 1
1615
+ end
1616
+ end