swissmedic_diff 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,45 @@
1
+ #! /usr/bin/ruby18
2
+
3
+ require 'swissmedic-diff'
4
+
5
+ def usage
6
+ puts <<-EOS
7
+ Usage: #$0 [-gnr] [-i ignorelist] <file1> <file2> [<output>]
8
+
9
+ -g --group sort by news, deletions and updates
10
+ -n --name sort by name
11
+ -r --registration sort by registration
12
+ -i --ignore ignore differences in the following comma-separated keys
13
+ EOS
14
+ end
15
+
16
+ out = nil
17
+ sort = :group
18
+
19
+ ignore = []
20
+ if(/^-/.match ARGV.first)
21
+ sort = case ARGV.shift
22
+ when /^-{1,2}i/
23
+ ignore.concat ARGV.shift.split(',').collect { |key| key.to_sym }
24
+ when /^-{1,2}n/
25
+ :name
26
+ when /^-{1,2}r/
27
+ :registration
28
+ else
29
+ :group
30
+ end
31
+ end
32
+
33
+ case ARGV.size
34
+ when 2
35
+ out = $stdout
36
+ when 3
37
+ out = File.open(ARGV[2], 'w')
38
+ else
39
+ usage
40
+ exit 1
41
+ end
42
+
43
+ plug = SwissmedicDiff.new
44
+ diff = plug.diff(ARGV[1], ARGV[0], ignore)
45
+ out.puts plug.to_s(sort)
@@ -0,0 +1,286 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+ # SwissmedicDiff -- swissmedic-diff -- 27.03.2008 -- hwyss@ywesee.com
4
+
5
+ require 'ostruct'
6
+ require 'spreadsheet'
7
+
8
+ #= diff command (compare two xls fles) for swissmedic xls file.
9
+ #
10
+ #Compares two Excel Documents provided by Swissmedic and displays the
11
+ #salient differences. Also: Find out what Products have changed on the
12
+ #swiss healthcare market.
13
+ #
14
+ #Authors:: Hannes Wyss (hwyss@ywesee.com), Masaomi Hatakeyama (mhatakeyama@ywesee.com)
15
+ #Version:: 0.1.2 2010-07-26 commit c30af5c15f6b8101f8f84cb482dfd09ab20729d6
16
+ #Copyright:: Copyright (C) ywesee GmbH, 2010. All rights reserved.
17
+ #License:: GPLv2.0 Compliance
18
+ #Source:: http://scm.ywesee.com/?p=swissmedic-diff/.git;a=summary
19
+ class SwissmedicDiff
20
+ VERSION = '0.1.3'
21
+ module Diff
22
+ COLUMNS = [ :iksnr, :seqnr, :name_base, :company,
23
+ :index_therapeuticus, :atc_class, :production_science,
24
+ :registration_date, :sequence_date, :expiry_date, :ikscd,
25
+ :size, :unit, :ikscat, :substances, :composition,
26
+ :indication_registration, :indication_sequence ]
27
+ FLAGS = {
28
+ :new => 'Neues Produkt',
29
+ :name_base => 'Namensänderung',
30
+ :ikscat => 'Abgabekategorie',
31
+ :index_therapeuticus => 'Index Therapeuticus',
32
+ :indication_registration => 'Anwendungsgebiet Präparate',
33
+ :indication_sequence => 'Anwendungsgebiet Sequenz',
34
+ :company => 'Zulassungsinhaber',
35
+ :composition => 'Zusammensetzung',
36
+ :sequence => 'Packungen',
37
+ :size => 'Packungsgrösse',
38
+ :expiry_date => 'Ablaufdatum der Zulassung',
39
+ :registration_date => 'Erstzulassungsdatum',
40
+ :sequence_date => 'Zulassungsdatum Sequenz',
41
+ :delete => 'Das Produkt wurde gelöscht',
42
+ :replaced_package => 'Packungs-Nummer',
43
+ :substances => 'Wirkstoffe',
44
+ :production_science => 'Heilmittelcode',
45
+ :atc_class => 'ATC-Code',
46
+ }
47
+ GALFORM_P = %r{excipiens\s+(ad|pro)\s+(?<galform>((?!\bpro\b)[^.])+)}
48
+
49
+ def capitalize(string)
50
+ string.split(/\s+/).collect { |word| word.capitalize }.join(' ')
51
+ end
52
+ def cell(row, pos)
53
+ if(cell = row[pos])
54
+ cell.to_s
55
+ end
56
+ end
57
+ def column(key)
58
+ COLUMNS.index(key)
59
+ end
60
+ def describe(diff, iksnr)
61
+ sprintf("%s: %s", iksnr, name(diff, iksnr))
62
+ end
63
+ def describe_flag(diff, iksnr, flag)
64
+ txt = FLAGS.fetch(flag, flag)
65
+ case flag
66
+ when :sequence
67
+ when :replaced_package
68
+ pairs = diff.newest_rows[iksnr].collect { |rep, row|
69
+ if(old = diff.replacements[row])
70
+ [old, rep].join(' -> ')
71
+ end
72
+ }.compact
73
+ sprintf "%s (%s)", txt, pairs.join(',')
74
+ when :registration_date, :expiry_date
75
+ row = diff.newest_rows[iksnr].sort.first.last
76
+ sprintf "%s (%s)", txt, row[column(flag)].strftime('%d.%m.%Y')
77
+ else
78
+ row = diff.newest_rows[iksnr].sort.first.last
79
+ sprintf "%s (%s)", txt, cell(row, column(flag))
80
+ end
81
+ end
82
+
83
+ #=== Comparison two Excel files
84
+ #
85
+ #_target_:: new file path (String)
86
+ #_latest_:: old file path (String)
87
+ #_ignore_:: columns not to be comared (Symbol)
88
+ #
89
+ #return :: differences (OpenStruct class)
90
+ def diff(target, latest, ignore = [])
91
+ replacements = {}
92
+ known_regs, known_seqs, known_pacs, newest_rows = known_data(latest)
93
+ @diff = OpenStruct.new
94
+ @diff.news = news = []
95
+ @diff.updates = updates = []
96
+ @diff.changes = changes = {}
97
+ @diff.newest_rows = newest_rows
98
+ Spreadsheet.client_encoding = 'UTF-8'
99
+ tbook = Spreadsheet.open(target)
100
+ sheet = tbook.worksheet(0)
101
+ if new_column = cell(sheet.row(2), COLUMNS.size)
102
+ raise "New column #{COLUMNS.size} (#{new_column})"
103
+ end
104
+ idx, prr, prp = nil
105
+ multiples = {}
106
+ tbook.worksheet(0).each(3) { |row|
107
+ if row.size < COLUMNS.size/2 || row.select{|val| val==nil}.size > COLUMNS.size/2
108
+ raise "Data missing in " + target + "\n(line " + (row.idx+1).to_s + "): " + row.join(", ").to_s + "\n"
109
+ end
110
+ group = cell(row, column(:production_science))
111
+ if(group != 'Tierarzneimittel')
112
+ iksnr = cell(row, column(:iksnr))
113
+ seqnr = "%02i" % cell(row, column(:seqnr)).to_i
114
+ pacnr = cell(row, column(:ikscd))
115
+ (multiples[iksnr] ||= {})
116
+ if prr == iksnr && prp == pacnr
117
+ idx += 1
118
+ elsif previous = multiples[iksnr][pacnr]
119
+ prr = iksnr
120
+ prp = pacnr
121
+ idx = previous[COLUMNS.size].to_i + 1
122
+ else
123
+ prr = iksnr
124
+ prp = pacnr
125
+ idx = 0
126
+ end
127
+ row[COLUMNS.size] = idx
128
+ (newest_rows[iksnr] ||= {})[pacnr] = row
129
+ multiples[iksnr][pacnr] = row
130
+ if(other = known_regs.delete([iksnr]))
131
+ changes[iksnr] ||= []
132
+ else
133
+ changes[iksnr] ||= [:new]
134
+ end
135
+ known_seqs.delete([iksnr, seqnr])
136
+ if(other = known_pacs.delete([iksnr, pacnr, idx]))
137
+ flags = rows_diff(row, other, ignore)
138
+ (changes[iksnr].concat flags).uniq!
139
+ updates.push row unless flags.empty?
140
+ else
141
+ replacements.store [ iksnr, seqnr, cell(row, column(:size)),
142
+ cell(row, column(:unit)) ], row
143
+ flags = changes[iksnr]
144
+ flags.push(:sequence).uniq! unless(flags.include? :new)
145
+ news.push row
146
+ end
147
+ end
148
+ }
149
+ @diff.replacements = reps = {}
150
+ known_pacs.each { |(iksnr, pacnr, idx), row|
151
+ key = [iksnr, '%02i' % cell(row, column(:seqnr)).to_i,
152
+ cell(row, column(:size)), cell(row, column(:unit))]
153
+ if(rep = replacements[key])
154
+ changes[iksnr].push :replaced_package
155
+ reps.store rep, pacnr
156
+ end
157
+ }
158
+ known_regs.each_key { |(iksnr,_)| changes[iksnr] = [:delete] }
159
+ changes.delete_if { |iksnr, flags| flags.empty? }
160
+ @diff.package_deletions = known_pacs.collect { |key, row|
161
+ ## the keys in known_pacs don't include the sequence number (which
162
+ # would prevent us from properly recognizing multi-sequence-Packages),
163
+ # so we need complete the path to the package now
164
+ key[1,0] = '%02i' % cell(row, column(:seqnr)).to_i
165
+ key
166
+ }
167
+ @diff.sequence_deletions = known_seqs.keys
168
+ @diff.registration_deletions = known_regs.keys
169
+ @diff
170
+ end
171
+ def format_flags(flags)
172
+ flags.delete(:revision)
173
+ flags.collect { |flag|
174
+ "- %s\n" % FLAGS.fetch(flag, "Unbekannt (#{flag})")
175
+ }.compact.join
176
+ end
177
+ def known_data(latest)
178
+ known_regs = {}
179
+ known_seqs = {}
180
+ known_pacs = {}
181
+ newest_rows = {}
182
+ _known_data latest, known_regs, known_seqs, known_pacs, newest_rows
183
+ [known_regs, known_seqs, known_pacs, newest_rows]
184
+ end
185
+ def _known_data(latest, known_regs, known_seqs, known_pacs, newest_rows)
186
+ lbook = Spreadsheet.open(latest)
187
+ idx, prr, prp = nil
188
+ multiples = {}
189
+ lbook.worksheet(0).each(3) { |row|
190
+ group = cell(row, column(:production_science))
191
+ if(group != 'Tierarzneimittel')
192
+ iksnr = cell(row, column(:iksnr))
193
+ seqnr = "%02i" % cell(row, column(:seqnr)).to_i
194
+ pacnr = cell(row, column(:ikscd))
195
+ multiples[iksnr] ||= {}
196
+ if prr == iksnr && prp == pacnr
197
+ idx += 1
198
+ elsif previous = multiples[iksnr][pacnr]
199
+ prr = iksnr
200
+ prp = pacnr
201
+ idx = previous[COLUMNS.size].to_i + 1
202
+ else
203
+ prr = iksnr
204
+ prp = pacnr
205
+ idx = 0
206
+ end
207
+ multiples[iksnr][pacnr] = row
208
+ row[COLUMNS.size] = idx
209
+ known_regs.store [iksnr], row
210
+ known_seqs.store [iksnr, seqnr], row
211
+ known_pacs.store [iksnr, pacnr, idx], row
212
+ (newest_rows[iksnr] ||= {})[pacnr] = row
213
+ end
214
+ }
215
+ end
216
+ def name(diff, iksnr)
217
+ rows = diff.newest_rows[iksnr]
218
+ row = rows.sort.first.last
219
+ cell(row, column(:name_base))
220
+ end
221
+ def rows_diff(row, other, ignore = [])
222
+ flags = []
223
+ COLUMNS.each_with_index { |key, idx|
224
+ if(!ignore.include?(key) \
225
+ && _comparable(key, row, idx) != _comparable(key, other, idx))
226
+ flags.push key
227
+ end
228
+ }
229
+ flags
230
+ end
231
+
232
+ #=== Output the differencies with String
233
+ #
234
+ # This should be called after diff method.
235
+ #
236
+ #_sort_ :: sort key (:group | :name | :registration)
237
+ #
238
+ #return :: difference (String)
239
+ def to_s(sort=:group)
240
+ return '' unless @diff
241
+ @diff.changes.sort_by { |iksnr, flags|
242
+ _sort_by(sort, iksnr, flags)
243
+ }.collect { |iksnr, flags|
244
+ if(flags.include? :new)
245
+ "+ " << describe(@diff, iksnr)
246
+ elsif(flags.include? :delete)
247
+ "- " << describe(@diff, iksnr)
248
+ else
249
+ "> " << describe(@diff, iksnr) << "; " \
250
+ << flags.collect { |flag| describe_flag(@diff, iksnr, flag)
251
+ }.compact.join(", ")
252
+ end
253
+ }.join("\n")
254
+ end
255
+ def _sort_by(sort, iksnr, flags)
256
+ case sort
257
+ when :name
258
+ [name(@diff, iksnr), iksnr]
259
+ when :registration
260
+ iksnr
261
+ else
262
+ weight = if(flags.include? :new)
263
+ 0
264
+ elsif(flags.include? :delete)
265
+ 1
266
+ else
267
+ 2
268
+ end
269
+ [weight, iksnr]
270
+ end
271
+ end
272
+ def _comparable(key, row, idx)
273
+ if cell = row[idx]
274
+ case key
275
+ when :registration_date, :expiry_date
276
+ row[idx]
277
+ when :seqnr
278
+ sprintf "%02i", cell.to_i
279
+ else
280
+ cell(row, idx).downcase.gsub(/\s+/, "")
281
+ end
282
+ end
283
+ end
284
+ end
285
+ include Diff
286
+ end
@@ -0,0 +1,1345 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Setup.rb, 2010-01-26 21:50:03
4
+ # http://proutils.github.com/setup/
5
+ #
6
+ # This is a stand-alone bundle of the setup.rb application.
7
+ # You can place it in your projects script/ directory, or
8
+ # rename it to 'setup.rb' and place it in your project's
9
+ # root directory (just like old times).
10
+ #
11
+ module Setup
12
+ VERSION = '5.0.0'
13
+ end
14
+ class << File #:nodoc: all
15
+ unless respond_to?(:read) # Ruby 1.6 and less
16
+ def read(fname)
17
+ open(fname){ |f| return f.read }
18
+ end
19
+ end
20
+ def dir?(path)
21
+ directory?((path[-1,1] == '/') ? path : path + '/')
22
+ end
23
+ end
24
+ unless Errno.const_defined?(:ENOTEMPTY) # Windows?
25
+ module Errno #:nodoc:
26
+ class ENOTEMPTY #:nodoc:
27
+ end
28
+ end
29
+ end
30
+ module Setup
31
+ META_EXTENSION_DIR = '.setup'
32
+ FILETYPES = %w( bin lib ext data etc man doc )
33
+ INSTALL_RECORD = 'InstalledFiles' #'.cache/setup/installedfiles'
34
+ end
35
+ module Setup
36
+ class Project
37
+ ROOT_MARKER = '{setup.rb,script/setup,meta/,MANIFEST,lib/}'
38
+ def rootdir
39
+ @rootdir ||= (
40
+ root = Dir[File.join(Dir.pwd, ROOT_MARKER)].first
41
+ if !root
42
+ raise Error, "not a project directory"
43
+ else
44
+ Dir.pwd
45
+ end
46
+ )
47
+ end
48
+ def name
49
+ @name = (
50
+ if file = Dir["{script/setup,meta,.meta}/name"].first
51
+ File.read(file).strip
52
+ else
53
+ nil
54
+ end
55
+ )
56
+ end
57
+ def loadpath
58
+ @loadpath ||= (
59
+ if file = Dir.glob('{script/setup,meta,.meta}/loadpath').first
60
+ raw = File.read(file).strip.chomp(']')
61
+ raw.split(/[\n,]/).map do |e|
62
+ e.strip.sub(/^[\[-]\s*/,'')
63
+ end
64
+ else
65
+ nil
66
+ end
67
+ )
68
+ end
69
+ def extconfs
70
+ @extconfs ||= Dir['ext/**/extconf.rb']
71
+ end
72
+ def extensions
73
+ @extensions ||= extconfs.collect{ |f| File.dirname(f) }
74
+ end
75
+ def compiles?
76
+ !extensions.empty?
77
+ end
78
+ end
79
+ end
80
+ module Setup
81
+ class Session
82
+ attr :options
83
+ def initialize(options={})
84
+ @options = options
85
+ self.io ||= StringIO.new # log instead ?
86
+ end
87
+ def io
88
+ @options[:io]
89
+ end
90
+ def io=(anyio)
91
+ @options[:io] = anyio
92
+ end
93
+ def trace?; @options[:trace]; end
94
+ def trace=(val)
95
+ @options[:trace] = val
96
+ end
97
+ def trial?; @options[:trial]; end
98
+ def trial=(val)
99
+ @options[:trial] = val
100
+ end
101
+ def quiet?; @options[:quiet]; end
102
+ def quiet=(val)
103
+ @options[:quiet] = val
104
+ end
105
+ def force?; @options[:force]; end
106
+ def force=(val)
107
+ @options[:force] = val
108
+ end
109
+ def compile?
110
+ configuration.compile? && project.compiles?
111
+ end
112
+ def all
113
+ config
114
+ if compile?
115
+ make
116
+ end
117
+ if configuration.test?
118
+ ok = test
119
+ exit 1 unless ok
120
+ end
121
+ install
122
+ if configuration.ri?
123
+ document
124
+ end
125
+ end
126
+ def config
127
+ log_header('Configure')
128
+ if configuration.save_config
129
+ io.puts "Configuration saved." unless quiet?
130
+ else
131
+ io.puts "Configuration current." unless quiet?
132
+ end
133
+ puts configuration if trace? && !quiet?
134
+ compiler.configure if compile? #compiler.compiles?
135
+ end
136
+ def make
137
+ abort "must setup config first" unless configuration.exist?
138
+ log_header('Compile')
139
+ compiler.compile
140
+ end
141
+ alias_method :setup, :make
142
+ def install
143
+ abort "must setup config first" unless configuration.exist?
144
+ log_header('Install')
145
+ installer.install
146
+ end
147
+ def test
148
+ return true unless tester.testable?
149
+ log_header('Test')
150
+ tester.test
151
+ end
152
+ def document
153
+ log_header('Document')
154
+ documentor.document
155
+ end
156
+ def clean
157
+ log_header('Clean')
158
+ compiler.clean
159
+ end
160
+ def distclean
161
+ log_header('Distclean')
162
+ compiler.distclean
163
+ end
164
+ def uninstall
165
+ if !File.exist?(INSTALL_RECORD)
166
+ io.puts "Nothing is installed."
167
+ return
168
+ end
169
+ log_header('Uninstall')
170
+ uninstaller.uninstall
171
+ io.puts('Ok.')
172
+ end
173
+ def show
174
+ puts configuration
175
+ end
176
+ def project
177
+ @project ||= Project.new
178
+ end
179
+ def configuration
180
+ @configuration ||= Configuration.new
181
+ end
182
+ def compiler
183
+ @compiler ||= Compiler.new(project, configuration, options)
184
+ end
185
+ def installer
186
+ @installer ||= Installer.new(project, configuration, options)
187
+ end
188
+ def tester
189
+ @tester ||= Tester.new(project, configuration, options)
190
+ end
191
+ def documentor
192
+ @documentor ||= Documentor.new(project, configuration, options)
193
+ end
194
+ def uninstaller
195
+ @uninstaller ||= Uninstaller.new(project, configuration, options)
196
+ end
197
+ def log_header(phase)
198
+ return if quiet?
199
+ if trial?
200
+ str = "#{phase.upcase} (trail run)"
201
+ else
202
+ str = "#{phase.upcase}"
203
+ end
204
+ line = "- " * 35
205
+ line[0..str.size+3] = str
206
+ io.puts("\n- - #{line}\n\n")
207
+ end
208
+ end
209
+ end
210
+ module Setup
211
+ class Base
212
+ attr :project
213
+ attr :config
214
+ attr_accessor :trial
215
+ attr_accessor :trace
216
+ attr_accessor :quiet
217
+ attr_accessor :force
218
+ attr_accessor :io
219
+ def initialize(project, configuration, options={})
220
+ @project = project
221
+ @config = configuration
222
+ initialize_hooks
223
+ options.each do |k,v|
224
+ __send__("#{k}=", v) if respond_to?("#{k}=")
225
+ end
226
+ end
227
+ def initialize_hooks
228
+ file = META_EXTENSION_DIR + "/#{self.class.name.downcase}.rb"
229
+ if File.exist?(file)
230
+ script = File.read(file)
231
+ (class << self; self; end).class_eval(script)
232
+ end
233
+ end
234
+ def trial? ; @trial ; end
235
+ def trace? ; @trace ; end
236
+ def quiet? ; @quiet ; end
237
+ def force? ; @force ; end
238
+ def rootdir
239
+ project.rootdir
240
+ end
241
+ def bash(*args)
242
+ $stderr.puts args.join(' ') if trace?
243
+ system(*args) or raise RuntimeError, "system(#{args.map{|a| a.inspect }.join(' ')}) failed"
244
+ end
245
+ alias_method :command, :bash
246
+ def ruby(*args)
247
+ bash(config.rubyprog, *args)
248
+ end
249
+ def trace_off #:yield:
250
+ begin
251
+ save, @trace = trace?, false
252
+ yield
253
+ ensure
254
+ @trace = save
255
+ end
256
+ end
257
+ def rm_f(path)
258
+ io.puts "rm -f #{path}" if trace? or trial?
259
+ return if trial?
260
+ force_remove_file(path)
261
+ end
262
+ def force_remove_file(path)
263
+ begin
264
+ remove_file(path)
265
+ rescue
266
+ end
267
+ end
268
+ def remove_file(path)
269
+ File.chmod 0777, path
270
+ File.unlink(path)
271
+ end
272
+ def rmdir(path)
273
+ io.puts "rmdir #{path}" if trace? or trial?
274
+ return if trial?
275
+ Dir.rmdir(path)
276
+ end
277
+ end
278
+ class Error < StandardError
279
+ end
280
+ end
281
+ module Setup
282
+ class Compiler < Base
283
+ def compiles?
284
+ !extdirs.empty?
285
+ end
286
+ def configure
287
+ extdirs.each do |dir|
288
+ Dir.chdir(dir) do
289
+ if File.exist?('extconf.rb') && !FileUtils.uptodate?('Makefile', ['extconf.rb'])
290
+ ruby("extconf.rb")
291
+ end
292
+ end
293
+ end
294
+ end
295
+ def compile
296
+ extdirs.each do |dir|
297
+ Dir.chdir(dir) do
298
+ make
299
+ end
300
+ end
301
+ end
302
+ def clean
303
+ extdirs.each do |dir|
304
+ Dir.chdir(dir) do
305
+ make('clean')
306
+ end
307
+ end
308
+ end
309
+ def distclean
310
+ extdirs.each do |dir|
311
+ Dir.chdir(dir) do
312
+ make('distclean')
313
+ end
314
+ end
315
+ end
316
+ def extdirs
317
+ Dir['ext/**/*/{MANIFEST,extconf.rb}'].map do |f|
318
+ File.dirname(f)
319
+ end.uniq
320
+ end
321
+ def make(task=nil)
322
+ return unless File.exist?('Makefile')
323
+ bash(*[config.makeprog, task].compact)
324
+ end
325
+ end
326
+ end
327
+ require 'rbconfig'
328
+ require 'fileutils'
329
+ require 'erb'
330
+ require 'yaml'
331
+ require 'shellwords'
332
+ module Setup
333
+ class Configuration
334
+ RBCONFIG = ::Config::CONFIG
335
+ CONFIG_FILE = 'SetupConfig' # '.cache/setup/config'
336
+ META_CONFIG_FILE = META_EXTENSION_DIR + '/configuration.rb'
337
+ def self.options
338
+ @@options ||= []
339
+ end
340
+ def self.option(name, *args) #type, description)
341
+ options << [name.to_s, *args] #type, description]
342
+ attr_accessor(name)
343
+ end
344
+ option :prefix , :path, 'path prefix of target environment'
345
+ option :bindir , :path, 'directory for commands'
346
+ option :libdir , :path, 'directory for libraries'
347
+ option :datadir , :path, 'directory for shared data'
348
+ option :mandir , :path, 'directory for man pages'
349
+ option :docdir , :path, 'directory for documentation'
350
+ option :rbdir , :path, 'directory for ruby scripts'
351
+ option :sodir , :path, 'directory for ruby extentions'
352
+ option :sysconfdir , :path, 'directory for system configuration files'
353
+ option :localstatedir , :path, 'directory for local state data'
354
+ option :libruby , :path, 'directory for ruby libraries'
355
+ option :librubyver , :path, 'directory for standard ruby libraries'
356
+ option :librubyverarch , :path, 'directory for standard ruby extensions'
357
+ option :siteruby , :path, 'directory for version-independent aux ruby libraries'
358
+ option :siterubyver , :path, 'directory for aux ruby libraries'
359
+ option :siterubyverarch , :path, 'directory for aux ruby binaries'
360
+ option :rubypath , :prog, 'path to set to #! line'
361
+ option :rubyprog , :prog, 'ruby program used for installation'
362
+ option :makeprog , :prog, 'make program to compile ruby extentions'
363
+ option :extconfopt , :opts, 'options to pass-thru to extconf.rb'
364
+ option :shebang , :pick, 'shebang line (#!) editing mode (all,ruby,never)'
365
+ option :no_test, :t , :bool, 'run pre-installation tests'
366
+ option :no_ri, :d , :bool, 'generate ri documentation'
367
+ option :no_doc , :bool, 'install doc/ directory'
368
+ option :no_ext , :bool, 'compile/install ruby extentions'
369
+ option :install_prefix , :path, 'install to alternate root location'
370
+ option :root , :path, 'install to alternate root location'
371
+ option :installdirs , :pick, 'install location mode (site,std,home)' #, local)
372
+ option :type , :pick, 'install location mode (site,std,home)'
373
+ ::Config::CONFIG.each do |key,val|
374
+ next if key == "configure_args"
375
+ name = key.to_s.downcase
376
+ define_method(name){ val }
377
+ end
378
+ config_args = Shellwords.shellwords(::Config::CONFIG["configure_args"])
379
+ config_args.each do |ent|
380
+ if ent.index("=")
381
+ key, val = *ent.split("=")
382
+ else
383
+ key, val = ent, true
384
+ end
385
+ name = key.downcase
386
+ name = name.sub(/^--/,'')
387
+ name = name.gsub(/-/,'_')
388
+ define_method(name){ val }
389
+ end
390
+ def options
391
+ self.class.options
392
+ end
393
+ def initialize(values={})
394
+ initialize_metaconfig
395
+ initialize_defaults
396
+ initialize_environment
397
+ initialize_configfile
398
+ values.each{ |k,v| __send__("#{k}=", v) }
399
+ yeild(self) if block_given?
400
+ end
401
+ def initialize_metaconfig
402
+ if File.exist?(META_CONFIG_FILE)
403
+ script = File.read(META_CONFIG_FILE)
404
+ (class << self; self; end).class_eval(script)
405
+ end
406
+ end
407
+ def initialize_defaults
408
+ self.type = 'site'
409
+ self.no_ri = true
410
+ self.no_test = true
411
+ self.no_doc = false
412
+ self.no_ext = false
413
+ end
414
+ def initialize_environment
415
+ options.each do |name, *args|
416
+ if value = ENV["RUBYSETUP_#{name.to_s.upcase}"]
417
+ __send__("#{name}=", value)
418
+ end
419
+ end
420
+ end
421
+ def initialize_configfile
422
+ if File.exist?(CONFIG_FILE)
423
+ erb = ERB.new(File.read(CONFIG_FILE))
424
+ txt = erb.result(binding)
425
+ dat = YAML.load(txt)
426
+ dat.each do |k, v|
427
+ next if 'type' == k
428
+ next if 'installdirs' == k
429
+ k = k.gsub('-','_')
430
+ __send__("#{k}=", v)
431
+ end
432
+ if dat['type']
433
+ self.type = dat['type']
434
+ end
435
+ if dat['installdirs']
436
+ self.installdirs = dat['installdirs']
437
+ end
438
+ end
439
+ end
440
+ def base_bindir
441
+ @base_bindir ||= subprefix('bindir')
442
+ end
443
+ def base_libdir
444
+ @base_libdir ||= subprefix('libdir')
445
+ end
446
+ def base_datadir
447
+ @base_datadir ||= subprefix('datadir')
448
+ end
449
+ def base_mandir
450
+ @base_mandir ||= subprefix('mandir')
451
+ end
452
+ def base_docdir
453
+ @base_docdir || File.dirname(subprefix('docdir'))
454
+ end
455
+ def base_rubylibdir
456
+ @rubylibdir ||= subprefix('rubylibdir')
457
+ end
458
+ def base_rubyarchdir
459
+ @base_rubyarchdir ||= subprefix('archdir')
460
+ end
461
+ def base_sysconfdir
462
+ @base_sysconfdir ||= subprefix('sysconfdir')
463
+ end
464
+ def base_localstatedir
465
+ @base_localstatedir ||= subprefix('localstatedir')
466
+ end
467
+ def type
468
+ @type ||= 'site'
469
+ end
470
+ def type=(val)
471
+ @type = val
472
+ case val.to_s
473
+ when 'std', 'ruby'
474
+ @rbdir = librubyver #'$librubyver'
475
+ @sodir = librubyverarch #'$librubyverarch'
476
+ when 'site'
477
+ @rbdir = siterubyver #'$siterubyver'
478
+ @sodir = siterubyverarch #'$siterubyverarch'
479
+ when 'home'
480
+ self.prefix = File.join(home, '.local') # TODO: Use XDG
481
+ @rbdir = nil #'$libdir/ruby'
482
+ @sodir = nil #'$libdir/ruby'
483
+ else
484
+ raise Error, "bad config: use type=(std|site|home) [#{val}]"
485
+ end
486
+ end
487
+ alias_method :installdirs, :type
488
+ alias_method :installdirs=, :type=
489
+ alias_method :install_prefix, :root
490
+ alias_method :install_prefix=, :root=
491
+ def prefix
492
+ @prefix ||= RBCONFIG['prefix']
493
+ end
494
+ def prefix=(path)
495
+ @prefix = pathname(path)
496
+ end
497
+ def libruby
498
+ @libruby ||= RBCONFIG['prefix'] + "/lib/ruby"
499
+ end
500
+ def libruby=(path)
501
+ path = pathname(path)
502
+ @librubyver = librubyver.sub(libruby, path)
503
+ @librubyverarch = librubyverarch.sub(libruby, path)
504
+ @libruby = path
505
+ end
506
+ def librubyver
507
+ @librubyver ||= RBCONFIG['rubylibdir']
508
+ end
509
+ def librubyver=(path)
510
+ @librubyver = pathname(path)
511
+ end
512
+ def librubyverarch
513
+ @librubyverarch ||= RBCONFIG['archdir']
514
+ end
515
+ def librubyverarch=(path)
516
+ @librubyverarch = pathname(path)
517
+ end
518
+ def siteruby
519
+ @siteruby ||= RBCONFIG['sitedir']
520
+ end
521
+ def siteruby=(path)
522
+ path = pathname(path)
523
+ @siterubyver = siterubyver.sub(siteruby, path)
524
+ @siterubyverarch = siterubyverarch.sub(siteruby, path)
525
+ @siteruby = path
526
+ end
527
+ def siterubyver
528
+ @siterubyver ||= RBCONFIG['sitelibdir']
529
+ end
530
+ def siterubyver=(path)
531
+ @siterubyver = pathname(path)
532
+ end
533
+ def siterubyverarch
534
+ @siterubyverarch ||= RBCONFIG['sitearchdir']
535
+ end
536
+ def siterubyverarch=(path)
537
+ @siterubyverarch = pathname(path)
538
+ end
539
+ def bindir
540
+ @bindir || File.join(prefix, base_bindir)
541
+ end
542
+ def bindir=(path)
543
+ @bindir = pathname(path)
544
+ end
545
+ def libdir
546
+ @libdir || File.join(prefix, base_libdir)
547
+ end
548
+ def libdir=(path)
549
+ @libdir = pathname(path)
550
+ end
551
+ def datadir
552
+ @datadir || File.join(prefix, base_datadir)
553
+ end
554
+ def datadir=(path)
555
+ @datadir = pathname(path)
556
+ end
557
+ def mandir
558
+ @mandir || File.join(prefix, base_mandir)
559
+ end
560
+ def mandir=(path)
561
+ @mandir = pathname(path)
562
+ end
563
+ def docdir
564
+ @docdir || File.join(prefix, base_docdir)
565
+ end
566
+ def docdir=(path)
567
+ @docdir = pathname(path)
568
+ end
569
+ def rbdir
570
+ @rbdir || File.join(prefix, base_rubylibdir)
571
+ end
572
+ def sodir
573
+ @sodir || File.join(prefix, base_rubyarchdir)
574
+ end
575
+ def sysconfdir
576
+ @sysconfdir ||= base_sysconfdir
577
+ end
578
+ def sysconfdir=(path)
579
+ @sysconfdir = pathname(path)
580
+ end
581
+ def localstatedir
582
+ @localstatedir ||= base_localstatedir
583
+ end
584
+ def localstatedir=(path)
585
+ @localstatedir = pathname(path)
586
+ end
587
+ def rubypath
588
+ @rubypath ||= File.join(RBCONFIG['bindir'], RBCONFIG['ruby_install_name'] + RBCONFIG['EXEEXT'])
589
+ end
590
+ def rubypath=(path)
591
+ @rubypath = pathname(path)
592
+ end
593
+ def rubyprog
594
+ @rubyprog || rubypath
595
+ end
596
+ def rubyprog=(command)
597
+ @rubyprog = command
598
+ end
599
+ def makeprog
600
+ @makeprog ||= (
601
+ if arg = RBCONFIG['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
602
+ arg.sub(/'/, '').split(/=/, 2)[1]
603
+ else
604
+ 'make'
605
+ end
606
+ )
607
+ end
608
+ def makeprog=(command)
609
+ @makeprog = command
610
+ end
611
+ def extconfopt
612
+ @extconfopt ||= ''
613
+ end
614
+ def extconfopt=(string)
615
+ @extconfopt = string
616
+ end
617
+ def shebang
618
+ @shebang ||= 'ruby'
619
+ end
620
+ def shebang=(val)
621
+ if %w(all ruby never).include?(val)
622
+ @shebang = val
623
+ else
624
+ raise Error, "bad config: use SHEBANG=(all|ruby|never) [#{val}]"
625
+ end
626
+ end
627
+ def no_ext
628
+ @no_ext
629
+ end
630
+ def no_ext=(val)
631
+ @no_ext = boolean(val)
632
+ end
633
+ def no_test
634
+ @no_test
635
+ end
636
+ def no_test=(val)
637
+ @no_test = boolean(val)
638
+ end
639
+ def no_doc
640
+ @no_doc
641
+ end
642
+ def no_doc=(val)
643
+ @no_doc = boolean(val)
644
+ end
645
+ def no_ri
646
+ @no_ri
647
+ end
648
+ def no_ri=(val)
649
+ @no_ri = boolean(val)
650
+ end
651
+ def compile?
652
+ !no_ext
653
+ end
654
+ def test?
655
+ !no_test
656
+ end
657
+ def ri?
658
+ !no_ri
659
+ end
660
+ def doc?
661
+ !no_doc
662
+ end
663
+ def to_h
664
+ h = {}
665
+ options.each do |name, *args|
666
+ h[name.to_s] = __send__(name)
667
+ end
668
+ h
669
+ end
670
+ def to_s
671
+ to_yaml.sub(/\A---\s*\n/,'')
672
+ end
673
+ def to_yaml(*args)
674
+ to_h.to_yaml(*args)
675
+ end
676
+ def save_config
677
+ out = to_yaml
678
+ if not File.exist?(File.dirname(CONFIG_FILE))
679
+ FileUtils.mkdir_p(File.dirname(CONFIG_FILE))
680
+ end
681
+ if File.exist?(CONFIG_FILE)
682
+ txt = File.read(CONFIG_FILE)
683
+ return nil if txt == out
684
+ end
685
+ File.open(CONFIG_FILE, 'w'){ |f| f << out }
686
+ true
687
+ end
688
+ def exist?
689
+ File.exist?(CONFIG_FILE)
690
+ end
691
+ private
692
+ def pathname(path)
693
+ path.gsub(%r<\\$([^/]+)>){ self[$1] }
694
+ end
695
+ def boolean(val, name=nil)
696
+ case val
697
+ when true, false, nil
698
+ val
699
+ else
700
+ case val.to_s.downcase
701
+ when 'y', 'yes', 't', 'true'
702
+ true
703
+ when 'n', 'no', 'f', 'false'
704
+ false
705
+ else
706
+ raise Error, "bad config: use --#{name}=(yes|no) [\#{val}]"
707
+ end
708
+ end
709
+ end
710
+ def subprefix(path, with='')
711
+ val = RBCONFIG[path]
712
+ raise "Unknown path -- #{path}" if val.nil?
713
+ prefix = Regexp.quote(RBCONFIG['prefix'])
714
+ val.sub(/\A#{prefix}/, with)
715
+ end
716
+ def home
717
+ ENV['HOME'] || raise(Error, 'HOME is not set.')
718
+ end
719
+ end #class ConfigTable
720
+ end #module Setup
721
+ =begin
722
+ def inintialize_metaconfig
723
+ path = Dir.glob(METACONFIG_FILE).first
724
+ if path && File.file?(path)
725
+ MetaConfigEnvironment.new(self).instance_eval(File.read(path), path)
726
+ end
727
+ end
728
+ class MetaConfigEnvironment
729
+ def initialize(config) #, installer)
730
+ @config = config
731
+ end
732
+ def config_names
733
+ @config.descriptions.collect{ |n, t, d| n.to_s }
734
+ end
735
+ def config?(name)
736
+ @config.descriptions.find do |sym, type, desc|
737
+ sym.to_s == name.to_s
738
+ end
739
+ end
740
+ def bool_config?(name)
741
+ @config.descriptions.find do |sym, type, desc|
742
+ sym.to_s == name.to_s && type == :bool
743
+ end
744
+ end
745
+ def path_config?(name)
746
+ @config.descriptions.find do |sym, type, desc|
747
+ sym.to_s == name.to_s && type == :path
748
+ end
749
+ end
750
+ def value_config?(name)
751
+ @config.descriptions.find do |sym, type, desc|
752
+ sym.to_s == name.to_s && type != :prog
753
+ end
754
+ end
755
+ def add_config(name, default, desc)
756
+ @config.descriptions << [name.to_sym, nil, desc]
757
+ end
758
+ def add_bool_config(name, default, desc)
759
+ @config.descriptions << [name.to_sym, :bool, desc]
760
+ end
761
+ def add_path_config(name, default, desc)
762
+ @config.descriptions << [name.to_sym, :path, desc]
763
+ end
764
+ def set_config_default(name, default)
765
+ @config[name] = default
766
+ end
767
+ def remove_config(name)
768
+ item = @config.descriptions.find do |sym, type, desc|
769
+ sym.to_s == name.to_s
770
+ end
771
+ index = @config.descriptions.index(item)
772
+ @config.descriptions.delete(index)
773
+ end
774
+ end
775
+ =end
776
+ module Setup
777
+ class Documentor < Base
778
+ def document
779
+ return if config.no_doc
780
+ exec_ri
781
+ end
782
+ def exec_ri
783
+ case config.type #installdirs
784
+ when 'std', 'ruby'
785
+ output = "--ri-site"
786
+ when 'site'
787
+ output = "--ri-site"
788
+ when 'home'
789
+ output = "--ri"
790
+ else
791
+ abort "bad config: should not be possible -- type=#{config.type}"
792
+ end
793
+ if File.exist?('.document')
794
+ files = File.read('.document').split("\n")
795
+ files.reject!{ |l| l =~ /^\s*[#]/ || l !~ /\S/ }
796
+ files.collect!{ |f| f.strip }
797
+ else
798
+ files = []
799
+ files << 'lib' if File.directory?('lib')
800
+ files << 'ext' if File.directory?('ext')
801
+ end
802
+ opt = []
803
+ opt << "-U"
804
+ opt << "-q" #if quiet?
805
+ opt << output
806
+ opt << files
807
+ opt = opt.flatten
808
+ cmd = "rdoc " + opt.join(' ')
809
+ if trial?
810
+ puts cmd
811
+ else
812
+ begin
813
+ success = system(cmd)
814
+ raise unless success
815
+ io.puts "Ok ri." #unless quiet?
816
+ rescue Exception
817
+ $stderr.puts "ri generation failed"
818
+ $stderr.puts "command was: '#{cmd}'"
819
+ end
820
+ end
821
+ end
822
+ def exec_rdoc
823
+ main = Dir.glob("README{,.*}", File::FNM_CASEFOLD).first
824
+ if File.exist?('.document')
825
+ files = File.read('.document').split("\n")
826
+ files.reject!{ |l| l =~ /^\s*[#]/ || l !~ /\S/ }
827
+ files.collect!{ |f| f.strip }
828
+ else
829
+ files = []
830
+ files << main if main
831
+ files << 'lib' if File.directory?('lib')
832
+ files << 'ext' if File.directory?('ext')
833
+ end
834
+ checkfiles = (files + files.map{ |f| Dir[File.join(f,'*','**')] }).flatten.uniq
835
+ if FileUtils.uptodate?('doc/rdoc', checkfiles)
836
+ puts "RDocs look current."
837
+ return
838
+ end
839
+ output = 'doc/rdoc'
840
+ title = (PACKAGE.capitalize + " API").strip if PACKAGE
841
+ template = config.doctemplate || 'html'
842
+ opt = []
843
+ opt << "-U"
844
+ opt << "-q" #if quiet?
845
+ opt << "--op=#{output}"
846
+ opt << "--title=#{title}"
847
+ opt << "--main=#{main}" if main
848
+ opt << files
849
+ opt = opt.flatten
850
+ cmd = "rdoc " + opt.join(' ')
851
+ if trial?
852
+ puts cmd
853
+ else
854
+ begin
855
+ system(cmd)
856
+ puts "Ok rdoc." unless quiet?
857
+ rescue Exception
858
+ puts "Fail rdoc."
859
+ puts "Command was: '#{cmd}'"
860
+ puts "Proceeding with install anyway."
861
+ end
862
+ end
863
+ end
864
+ end
865
+ end
866
+ module Setup
867
+ class Installer < Base
868
+ def install_prefix
869
+ config.install_prefix
870
+ end
871
+ def install
872
+ Dir.chdir(rootdir) do
873
+ install_bin
874
+ install_ext
875
+ install_lib
876
+ install_data
877
+ install_man
878
+ install_doc
879
+ install_etc
880
+ prune_install_record
881
+ end
882
+ end
883
+ def install_bin
884
+ return unless directory?('bin')
885
+ report_transfer('bin', config.bindir)
886
+ files = files('bin')
887
+ install_files('bin', files, config.bindir, 0755)
888
+ end
889
+ def install_ext
890
+ return unless directory?('ext')
891
+ report_transfer('ext', config.sodir)
892
+ files = files('ext')
893
+ files = select_dllext(files)
894
+ files.each do |file|
895
+ name = File.join(File.dirname(File.dirname(file)), File.basename(file))
896
+ dest = destination(config.sodir, name)
897
+ install_file('ext', file, dest, 0555, install_prefix)
898
+ end
899
+ end
900
+ def install_lib
901
+ return unless directory?('lib')
902
+ report_transfer('lib', config.rbdir)
903
+ files = files('lib')
904
+ install_files('lib', files, config.rbdir, 0644)
905
+ end
906
+ def install_data
907
+ return unless directory?('data')
908
+ report_transfer('data', config.datadir)
909
+ files = files('data')
910
+ install_files('data', files, config.datadir, 0644)
911
+ end
912
+ def install_etc
913
+ return unless directory?('etc')
914
+ report_transfer('etc', config.sysconfdir)
915
+ files = files('etc')
916
+ install_files('etc', files, config.sysconfdir, 0644)
917
+ end
918
+ def install_man
919
+ return unless directory?('man')
920
+ report_transfer('man', config.mandir)
921
+ files = files('man')
922
+ install_files('man', files, config.mandir, 0644)
923
+ end
924
+ def install_doc
925
+ return unless config.doc?
926
+ return unless directory?('doc')
927
+ return unless project.name
928
+ dir = File.join(config.docdir, "ruby-{project.name}")
929
+ report_transfer('doc', dir)
930
+ files = files('doc')
931
+ install_files('doc', files, dir, 0644)
932
+ end
933
+ private
934
+ def report_transfer(source, directory)
935
+ unless quiet?
936
+ if install_prefix
937
+ out = File.join(install_prefix, directory)
938
+ else
939
+ out = directory
940
+ end
941
+ io.puts "* #{source} -> #{out}"
942
+ end
943
+ end
944
+ def directory?(path)
945
+ File.directory?(path)
946
+ end
947
+ def files(dir)
948
+ files = Dir["#{dir}/**/*"]
949
+ files = files.select{ |f| File.file?(f) }
950
+ files = files.map{ |f| f.sub("#{dir}/", '') }
951
+ files
952
+ end
953
+ def select_dllext(files)
954
+ ents = files.select do |file|
955
+ File.extname(file) == ".#{dllext}"
956
+ end
957
+ if ents.empty? && !files.empty?
958
+ raise Error, "ruby extention not compiled: 'setup.rb setup' first"
959
+ end
960
+ ents
961
+ end
962
+ def dllext
963
+ config.dlext
964
+ end
965
+ def install_files(dir, list, dest, mode)
966
+ list.each do |fname|
967
+ rdest = destination(dest, fname)
968
+ install_file(dir, fname, rdest, mode, install_prefix)
969
+ end
970
+ end
971
+ def install_file(dir, from, dest, mode, prefix=nil)
972
+ mkdir_p(File.dirname(dest))
973
+ if trace? or trial?
974
+ io.puts "install #{dir}/#{from} #{dest}"
975
+ end
976
+ return if trial?
977
+ str = binread(File.join(dir, from))
978
+ if diff?(str, dest)
979
+ trace_off {
980
+ rm_f(dest) if File.exist?(dest)
981
+ }
982
+ File.open(dest, 'wb'){ |f| f.write(str) }
983
+ File.chmod(mode, dest)
984
+ end
985
+ record_installation(dest) # record file as installed
986
+ end
987
+ def mkdir_p(dirname) #, prefix=nil)
988
+ return if File.directory?(dirname)
989
+ io.puts "mkdir -p #{dirname}" if trace? or trial?
990
+ return if trial?
991
+ dirs = File.expand_path(dirname).split(%r<(?=/)>)
992
+ if /\A[a-z]:\z/i =~ dirs[0]
993
+ disk = dirs.shift
994
+ dirs[0] = disk + dirs[0]
995
+ end
996
+ dirs.each_index do |idx|
997
+ path = dirs[0..idx].join('')
998
+ unless File.dir?(path)
999
+ Dir.mkdir(path)
1000
+ end
1001
+ record_installation(path) # record directories made
1002
+ end
1003
+ end
1004
+ def record_installation(path)
1005
+ File.open(install_record, 'a') do |f|
1006
+ f.puts(path)
1007
+ end
1008
+ end
1009
+ def prune_install_record
1010
+ entries = File.read(install_record).split("\n")
1011
+ entries.uniq!
1012
+ File.open(install_record, 'w') do |f|
1013
+ f << entries.join("\n")
1014
+ f << "\n"
1015
+ end
1016
+ end
1017
+ def install_record
1018
+ @install_record ||= (
1019
+ file = INSTALL_RECORD
1020
+ dir = File.dirname(file)
1021
+ unless File.directory?(dir)
1022
+ FileUtils.mkdir_p(dir)
1023
+ end
1024
+ file
1025
+ )
1026
+ end
1027
+ def destination(dir, file)
1028
+ dest = install_prefix ? File.join(install_prefix, File.expand_path(dir)) : dir
1029
+ dest = File.join(dest, file) #if File.dir?(dest)
1030
+ dest = File.expand_path(dest)
1031
+ dest
1032
+ end
1033
+ def diff?(new_content, path)
1034
+ return true unless File.exist?(path)
1035
+ new_content != binread(path)
1036
+ end
1037
+ def binread(fname)
1038
+ File.open(fname, 'rb') do |f|
1039
+ return f.read
1040
+ end
1041
+ end
1042
+ def install_shebang(files, dir)
1043
+ files.each do |file|
1044
+ path = File.join(dir, File.basename(file))
1045
+ update_shebang_line(path)
1046
+ end
1047
+ end
1048
+ def update_shebang_line(path)
1049
+ return if trial?
1050
+ return if config.shebang == 'never'
1051
+ old = Shebang.load(path)
1052
+ if old
1053
+ if old.args.size > 1
1054
+ $stderr.puts "warning: #{path}"
1055
+ $stderr.puts "Shebang line has too many args."
1056
+ $stderr.puts "It is not portable and your program may not work."
1057
+ end
1058
+ new = new_shebang(old)
1059
+ return if new.to_s == old.to_s
1060
+ else
1061
+ return unless config.shebang == 'all'
1062
+ new = Shebang.new(config.rubypath)
1063
+ end
1064
+ $stderr.puts "updating shebang: #{File.basename(path)}" if trace?
1065
+ open_atomic_writer(path) do |output|
1066
+ File.open(path, 'rb') do |f|
1067
+ f.gets if old # discard
1068
+ output.puts new.to_s
1069
+ output.print f.read
1070
+ end
1071
+ end
1072
+ end
1073
+ def new_shebang(old)
1074
+ if /\Aruby/ =~ File.basename(old.cmd)
1075
+ Shebang.new(config.rubypath, old.args)
1076
+ elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby'
1077
+ Shebang.new(config.rubypath, old.args[1..-1])
1078
+ else
1079
+ return old unless config.shebang == 'all'
1080
+ Shebang.new(config.rubypath)
1081
+ end
1082
+ end
1083
+ def open_atomic_writer(path, &block)
1084
+ tmpfile = File.basename(path) + '.tmp'
1085
+ begin
1086
+ File.open(tmpfile, 'wb', &block)
1087
+ File.rename tmpfile, File.basename(path)
1088
+ ensure
1089
+ File.unlink tmpfile if File.exist?(tmpfile)
1090
+ end
1091
+ end
1092
+ class Shebang
1093
+ def Shebang.load(path)
1094
+ line = nil
1095
+ File.open(path) {|f|
1096
+ line = f.gets
1097
+ }
1098
+ return nil unless /\A#!/ =~ line
1099
+ parse(line)
1100
+ end
1101
+ def Shebang.parse(line)
1102
+ cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ')
1103
+ new(cmd, args)
1104
+ end
1105
+ def initialize(cmd, args = [])
1106
+ @cmd = cmd
1107
+ @args = args
1108
+ end
1109
+ attr_reader :cmd
1110
+ attr_reader :args
1111
+ def to_s
1112
+ "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}")
1113
+ end
1114
+ end
1115
+ end
1116
+ end
1117
+ module Setup
1118
+ class Tester < Base
1119
+ RUBYSCRIPT = META_EXTENSION_DIR + '/testrc.rb'
1120
+ SHELLSCRIPT = 'script/test'
1121
+ def testable?
1122
+ return false if config.no_test
1123
+ return true if File.exist?(RUBYSCRIPT)
1124
+ return true if File.exist?(SHELLSCRIPT)
1125
+ false
1126
+ end
1127
+ def test
1128
+ return true if !testable?
1129
+ if File.exist?(RUBYSCRIPT)
1130
+ test_rubyscript
1131
+ elsif File.exist?(SHELLSCRIPT)
1132
+ test_shellscript
1133
+ end
1134
+ end
1135
+ def test_shellscript
1136
+ bash(SHELLSCRIPT)
1137
+ end
1138
+ def test_rubyscript
1139
+ ruby(RUBYSCRIPT)
1140
+ end
1141
+ end
1142
+ end
1143
+ module Setup
1144
+ class Uninstaller < Base
1145
+ def uninstall
1146
+ return unless File.exist?(INSTALL_RECORD)
1147
+ files = []
1148
+ dirs = []
1149
+ paths.each do |path|
1150
+ dirs << path if File.dir?(path)
1151
+ files << path if File.file?(path)
1152
+ end
1153
+ if dirs.empty? && files.empty?
1154
+ io.outs "Nothing to remove."
1155
+ return
1156
+ end
1157
+ files.sort!{ |a,b| b.size <=> a.size }
1158
+ dirs.sort!{ |a,b| b.size <=> a.size }
1159
+ if !force? && !trial?
1160
+ puts (files + dirs).collect{ |f| "#{f}" }.join("\n")
1161
+ puts
1162
+ puts "Must use --force option to remove these files and directories that become empty."
1163
+ return
1164
+ end
1165
+ files.each do |file|
1166
+ rm_f(file)
1167
+ end
1168
+ dirs.each do |dir|
1169
+ entries = Dir.entries(dir)
1170
+ entries.delete('.')
1171
+ entries.delete('..')
1172
+ rmdir(dir) if entries.empty?
1173
+ end
1174
+ rm_f(INSTALL_RECORD)
1175
+ end
1176
+ private
1177
+ def paths
1178
+ @paths ||= (
1179
+ lines = File.read(INSTALL_RECORD).split(/\s*\n/)
1180
+ lines = lines.map{ |line| line.strip }
1181
+ lines = lines.uniq
1182
+ lines = lines.reject{ |line| line.empty? } # skip blank lines
1183
+ lines = lines.reject{ |line| line[0,1] == '#' } # skip blank lines
1184
+ lines
1185
+ )
1186
+ end
1187
+ end
1188
+ end
1189
+ require 'optparse'
1190
+ module Setup
1191
+ class Command
1192
+ def self.run(*argv)
1193
+ new.run(*argv)
1194
+ end
1195
+ def self.tasks
1196
+ @tasks ||= {}
1197
+ end
1198
+ def self.order
1199
+ @order ||= []
1200
+ end
1201
+ def self.task(name, description)
1202
+ tasks[name] = description
1203
+ order << name
1204
+ end
1205
+ task 'all' , "config, setup, test, install"
1206
+ task 'config' , "saves your configuration"
1207
+ task 'show' , "show current configuration"
1208
+ task 'make' , "compile ruby extentions"
1209
+ task 'test' , "run test suite"
1210
+ task 'doc' , "generate ri documentation"
1211
+ task 'install' , "install project files"
1212
+ task 'uninstall', "uninstall previously installed files"
1213
+ task 'clean' , "does `make clean' for each extention"
1214
+ task 'distclean', "does `make distclean' for each extention"
1215
+ def run(*argv)
1216
+ ARGV.replace(argv) unless argv.empty?
1217
+ task = ARGV.find{ |a| a !~ /^[-]/ }
1218
+ task = 'all' unless task
1219
+ unless task_names.include?(task)
1220
+ $stderr.puts "Not a valid task -- #{task}"
1221
+ exit 1
1222
+ end
1223
+ parser = OptionParser.new
1224
+ options = {}
1225
+ parser.banner = "Usage: #{File.basename($0)} [TASK] [OPTIONS]"
1226
+ optparse_header(parser, options)
1227
+ case task
1228
+ when 'all'
1229
+ optparse_all(parser, options)
1230
+ when 'config'
1231
+ optparse_config(parser, options)
1232
+ when 'install'
1233
+ optparse_install(parser, options)
1234
+ end
1235
+ optparse_common(parser, options)
1236
+ begin
1237
+ parser.parse!(ARGV)
1238
+ rescue OptionParser::InvalidOption
1239
+ $stderr.puts $!.to_s.capitalize
1240
+ exit 1
1241
+ end
1242
+ rootdir = session.project.rootdir
1243
+ print_header
1244
+ begin
1245
+ session.__send__(task)
1246
+ rescue Error
1247
+ raise if $DEBUG
1248
+ $stderr.puts $!.message
1249
+ $stderr.puts "Try 'setup.rb --help' for detailed usage."
1250
+ exit 1
1251
+ end
1252
+ puts unless session.quiet?
1253
+ end
1254
+ def session
1255
+ @session ||= Session.new(:io=>$stdout)
1256
+ end
1257
+ def configuration
1258
+ @configuration ||= session.configuration
1259
+ end
1260
+ def optparse_header(parser, options)
1261
+ parser.banner = "USAGE: #{File.basename($0)} [command] [options]"
1262
+ end
1263
+ def optparse_all(parser, options)
1264
+ optparse_config(parser, options)
1265
+ end
1266
+ def optparse_config(parser, options)
1267
+ parser.separator ""
1268
+ parser.separator "Configuration options:"
1269
+ configuration.options.each do |args|
1270
+ args = args.dup
1271
+ desc = args.pop
1272
+ type = args.pop
1273
+ name, shortcut = *args
1274
+ optname = name.to_s.gsub('_', '-')
1275
+ case type
1276
+ when :bool
1277
+ if optname.index('no-') == 0
1278
+ optname = "[no-]" + optname.sub(/^no-/, '')
1279
+ opts = shortcut ? ["-#{shortcut}", "--#{optname}", desc] : ["--#{optname}", desc]
1280
+ parser.on(*opts) do |val|
1281
+ configuration.__send__("#{name}=", !val)
1282
+ end
1283
+ else
1284
+ optname = "[no-]" + optname.sub(/^no-/, '')
1285
+ opts = shortcut ? ["-#{shortcut}", "--#{optname}", desc] : ["--#{optname}", desc]
1286
+ parser.on(*opts) do |val|
1287
+ configuration.__send__("#{name}=", val)
1288
+ end
1289
+ end
1290
+ else
1291
+ opts = shortcut ? ["-#{shortcut}", "--#{optname} #{type.to_s.upcase}", desc] : ["--#{optname} #{type.to_s.upcase}", desc]
1292
+ parser.on(*opts) do |val|
1293
+ configuration.__send__("#{name}=", val)
1294
+ end
1295
+ end
1296
+ end
1297
+ end
1298
+ def optparse_install(parser, options)
1299
+ parser.separator ""
1300
+ parser.separator "Install options:"
1301
+ parser.on("--prefix PATH", "Installation prefix") do |val|
1302
+ configuration.install_prefix = val
1303
+ end
1304
+ end
1305
+ def optparse_common(parser, options)
1306
+ parser.separator ""
1307
+ parser.separator "General options:"
1308
+ parser.on("-q", "--quiet", "Suppress output") do
1309
+ session.quiet = true
1310
+ end
1311
+ parser.on("-f", "--force", "Force operation") do
1312
+ session.force = true
1313
+ end
1314
+ parser.on("--trace", "--verbose", "Watch execution") do |val|
1315
+ session.trace = true
1316
+ end
1317
+ parser.on("--trial", "--no-harm", "Do not write to disk") do |val|
1318
+ session.trial = true
1319
+ end
1320
+ parser.on("--debug", "Turn on debug mode") do |val|
1321
+ $DEBUG = true
1322
+ end
1323
+ parser.separator ""
1324
+ parser.separator "Inform options:"
1325
+ parser.on_tail("-h", "--help", "display this help information") do
1326
+ puts parser
1327
+ exit
1328
+ end
1329
+ parser.on_tail("--version", "-v", "Show version") do
1330
+ puts File.basename($0) + ' v' + Setup::VERSION #Version.join('.')
1331
+ exit
1332
+ end
1333
+ parser.on_tail("--copyright", "Show copyright") do
1334
+ puts Setup::COPYRIGHT #opyright
1335
+ exit
1336
+ end
1337
+ end
1338
+ def task_names
1339
+ self.class.tasks.keys
1340
+ end
1341
+ def print_header
1342
+ end
1343
+ end
1344
+ end
1345
+ Setup::Command.run