xls2odat 1.1.5

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ # Hoe.plugin :compiler
7
+ # Hoe.plugin :cucumberfeatures
8
+ # Hoe.plugin :gem_prelude_sucks
9
+ # Hoe.plugin :inline
10
+ # Hoe.plugin :inline
11
+ # Hoe.plugin :manifest
12
+ # Hoe.plugin :newgem
13
+ # Hoe.plugin :racc
14
+ # Hoe.plugin :rubyforge
15
+ # Hoe.plugin :rubyforge
16
+ # Hoe.plugin :website
17
+
18
+ Hoe.spec 'xls2odat' do
19
+ # HEY! If you fill these out in ~/.hoe_template/Rakefile.erb then
20
+ # you'll never have to touch them again!
21
+ # (delete this comment too, of course)
22
+
23
+ developer('Masaomi Hatakeyama, Zeno R.R. Davatz', 'mhatakeyama@ywesee.com, zdavatz@ywesee.com')
24
+
25
+ # self.rubyforge_name = 'xls2odatx' # if different than 'xls2odat'
26
+ end
27
+
28
+ # vim: syntax=ruby
data/SetupConfig ADDED
@@ -0,0 +1,30 @@
1
+ ---
2
+ install_prefix:
3
+ localstatedir: /var/lib
4
+ prefix: /usr
5
+ rubyprog: /usr/bin/ruby18
6
+ rubypath: /usr/bin/ruby18
7
+ siterubyverarch: /usr/lib/ruby/site_ruby/1.8/i686-linux
8
+ no_ext: false
9
+ no_ri: true
10
+ extconfopt: ""
11
+ type: site
12
+ no_doc: false
13
+ librubyverarch: /usr/lib/ruby/1.8/i686-linux
14
+ installdirs: site
15
+ no_test: true
16
+ siteruby: /usr/lib/ruby/site_ruby
17
+ root:
18
+ siterubyver: /usr/lib/ruby/site_ruby/1.8
19
+ sysconfdir: /etc
20
+ rbdir: /usr/lib/ruby/site_ruby/1.8
21
+ datadir: /usr/share
22
+ shebang: ruby
23
+ makeprog: make
24
+ librubyver: /usr/lib/ruby/1.8
25
+ mandir: /usr/share/man
26
+ libdir: /usr/lib
27
+ libruby: /usr/lib/ruby
28
+ sodir: /usr/lib/ruby/site_ruby/1.8/i686-linux
29
+ docdir: /usr/share/doc
30
+ bindir: /usr/bin
data/bin/xls2odat ADDED
@@ -0,0 +1,23 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'xls2odat'
4
+
5
+ def usage
6
+ puts <<-EOS
7
+ #$0 ver.#{Xls2odat::VERSION}
8
+ Usage: #$0 <config file (.xls)> <data files (.xls)>...
9
+
10
+ EOS
11
+ end
12
+
13
+ if ARGV.length < 2
14
+ usage
15
+ exit 1
16
+ end
17
+
18
+ converter = Xls2odat.new
19
+ converter.read_config(ARGV[0])
20
+ ARGV.slice(1..-1).each_with_index do |datafile, i|
21
+ converter.parse_data(datafile, i)
22
+ end
23
+ converter.output
data/lib/xls2odat.rb ADDED
@@ -0,0 +1,290 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+ # Xls2odat -- 22.10.2010 -- mhatakeyama@ywesee.com
4
+
5
+ require 'spreadsheet'
6
+
7
+ #
8
+ #= Convert command from oddb mutations xls file to text file.
9
+ #
10
+ # xls2odat converts one or more XLS files to flat files so
11
+ # they can be imported to a drugstore software or a pharmacy software
12
+ # in Switzerland.
13
+ #
14
+ #Authors:: Masaomi Hatakeyama (mhatakeyama@ywesee.com)
15
+ #Version:: 1.1.7 2010-10-22 commit
16
+ #Copyright:: Copyright (C) ywesee GmbH, 2010. All rights reserved.
17
+ #License:: GPLv2.0 Compliance
18
+ #Source:: http://scm.ywesee.com/?p=xls2odat/.git;a=summary
19
+ class Xls2odat
20
+ VERSION = '1.1.5'
21
+ VERSION = File.readlines(__FILE__).grep(/Version/)[0].match(/Version::\s+([0-9.]+)/)[1]
22
+ COLUMNS = [ :filenr, :seqnr, :field_name, :T, :L, :description, :bemerkung ]
23
+ FIELDS = [0.0, 0.0, "Field name", "T", "L", "Description", "Bemerkung"]
24
+
25
+ def initialize
26
+ @conf = [] # @conf[config No][file index, column A][index, column B] == config value, column H
27
+ @out_filename = [] # output file name list
28
+ @out_data = {} # key: @out_filename, output data list
29
+ @pharmacode = 0 # pharmacode (be incremented)
30
+ @pharmacode_increment = true # flag when @pharmacode is increment
31
+ @iterate_count = 0 # count variable for iteration
32
+ @max_reclen = {} # key: @out_filename, value: length of logest record (bytes)
33
+ @odat_version = 0 # odat version: related to output file names and log file
34
+ end
35
+
36
+ #=== Reading a configuration file
37
+ #
38
+ # This method reads a configuratin file (.xls) and
39
+ # sets the information in @conf variable
40
+ #
41
+ #_target_:: config file path (String)
42
+ #
43
+ #return :: none
44
+ def read_config(target)
45
+ # read file
46
+ Spreadsheet.client_encoding = 'UTF-8'
47
+ tbook = Spreadsheet.open(target)
48
+ fields = tbook.worksheet(0).row(0).to_a
49
+ new_columns = fields[FIELDS.size..fields.size-1].delete_if{|x| x == nil}
50
+
51
+ # check file
52
+ if fields[0..6] != FIELDS
53
+ raise "Config file format is wrong (#{fields.join(", ").to_s})"
54
+ elsif new_columns.size == 0
55
+ raise "No config information in #{target}"
56
+ end
57
+
58
+ # output file name list
59
+ outfilelist = tbook.worksheet(0).column(0).to_a.uniq.delete_if{|x| x == nil}.map{|x| "%02d" % x.to_i}
60
+ outfilelist.shift # delete the first value corresponding to the first line date in xls file
61
+ # check odat version
62
+ if outfilelist.include?("06") or outfilelist.include?("07")
63
+ @out_filename = outfilelist.map{|x| "s" + x}
64
+ @odat_version = 3
65
+ else
66
+ @out_filename = outfilelist.map{|x| "H" + x}
67
+ @odat_version = 2
68
+ end
69
+ # initialize output data list
70
+ @out_filename.each do |filename|
71
+ @out_data[filename] = Array.new
72
+ end
73
+
74
+ # initialize @conf format
75
+ @conf.fill(0, new_columns.length){[]}
76
+
77
+ # import data and set it in @conf
78
+ tbook.worksheet(0).select{|r| r[0] != nil}.each do |row|
79
+ filenr = row[column_s(:filenr)].to_i
80
+ seqnr = row[column_s(:seqnr)].to_i
81
+
82
+ new_columns.length.times do |i|
83
+ @conf[i][filenr] = [] if @conf[i][filenr] == nil
84
+ value = row[FIELDS.size+i]
85
+ @conf[i][filenr][seqnr] = if value.class == Float
86
+ value.to_i
87
+ else
88
+ value
89
+ end
90
+ end
91
+ end
92
+
93
+ # there is no judge statement for checking the number of iterations.
94
+ # in the case that there is more than two informations of iteration,
95
+ # it should be checked here
96
+ # if the numbers of iterations (numbers of ",") are same or not.
97
+ end
98
+
99
+ #=== Parsing data
100
+ #
101
+ # Parsing the data files and the data converted are
102
+ # set in @out_data list
103
+ #
104
+ #_target_:: data file path (String)
105
+ #_confnr_:: configuration number (Integer)
106
+ #
107
+ #return :: none
108
+ def parse_data(target, confnr=0)
109
+ # read_config must be run before this method
110
+ if @conf.length == 0
111
+ raise "Config file has not been read yet."
112
+ elsif @conf[confnr] == nil
113
+ raise "No config information for Config. No.#{confnr}. Check #{target}."
114
+ end
115
+
116
+ # open data file
117
+ Spreadsheet.client_encoding = 'UTF-8'
118
+ tbook = Spreadsheet.open(target)
119
+
120
+ # initialize
121
+ @phamacode = 0
122
+
123
+ # parse data
124
+ tbook.worksheet(0).each_with_index do |row,ri|
125
+ # save prefix
126
+ @prefix = cell("A", row).strip if cell("A", row) =~ /^\s+.+/ and row.to_a.delete_if{|x| x == nil}.length == 1
127
+ if ri > 0 and cell("A", row) != "" and row.to_a.delete_if{|x| x == nil}.length > 5 # skip the title line in xls file
128
+ @pharmacode_increment = false # this will be changed in analyzer method
129
+ # output separately in each output file
130
+ @out_filename.each do |filename|
131
+ filenr = filename.delete("H").delete("s").to_i
132
+ config = @conf[confnr][filenr]
133
+ iterate_length = 1
134
+
135
+ # check iteration
136
+ iterate_config = config.grep(/,/)
137
+ if iterate_config.length > 0
138
+ iterate_flag = true
139
+ iterate_length = iterate_config[0].split(",").length
140
+ end
141
+
142
+ iterate_length.times do |i|
143
+ @iterate_count = i # @iterate_count would be used in analyzer method
144
+
145
+ # buffer one line
146
+ line_buffer = []
147
+ config.slice(1..-1).each do |conf|
148
+ line_buffer << analyzer(conf, row, target)
149
+ end
150
+
151
+ # output
152
+ @out_data[filename] << line_buffer
153
+ end # iterate_length.times
154
+ end # @out_filename.each
155
+
156
+ @pharmacode += 1 if @pharmacode_increment == true
157
+ end # if ri > 0
158
+ end # tbook.worksheet(0).each_with_index
159
+ end # def parse_data
160
+
161
+ #=== Output data
162
+ #
163
+ # output @out_data list into @out_filename files
164
+ #
165
+ #return :: none
166
+ def output
167
+
168
+ @out_filename.each do |filename|
169
+ open(filename, "w") do |out|
170
+ max_reclen = 0
171
+ @out_data[filename].each do |line|
172
+ line_str = line.join("|").to_s + "\n"
173
+ # search the longest record
174
+ max_reclen = line_str.size if line_str.size > max_reclen
175
+ out.print line_str
176
+ end
177
+ @max_reclen[filename] = max_reclen
178
+ end
179
+ end
180
+
181
+ if @odat_version == 2
182
+ output_log
183
+ end
184
+ end
185
+
186
+ private
187
+ # output @out_data list into @out_filename files
188
+ def output_log
189
+
190
+ table_name = ["", "AC", "ACMED", "ACNAM", "ACWHS", "ACACT", "ACBARCODE", "ACPRICEALG", "ACTXT", "ACLIM", "LIMITTXT", "CODES", "COMP", "WHSPRICE", "ACINS", "ACALERT", "LIMITATION", "KK", "KKINS", "ACCOMP"]
191
+ version = "02.10"
192
+ actual_date = Date.today.strftime("%Y%m%d")
193
+ reference_date = actual_date
194
+ data_source = "11"
195
+ remarks = "generated by xls2odat ywesee gmbh zürich"
196
+
197
+ # open output file
198
+ @out_filename.each do |filename|
199
+ i = filename.delete("H").delete("s").to_i
200
+ open(filename + ".LOG", "w") do |out|
201
+ out.print filename + ".txt" + "|" # file id
202
+ out.print table_name[i] + "|" # table name
203
+ out.print version + "|" # version
204
+ out.print actual_date + "|" # actual date
205
+ out.print reference_date + "|" # reference date
206
+ out.print data_source + "|" # data source
207
+ out.print @out_data[filename].length.to_s + "|" # number of records
208
+ out.print File.size(filename).to_s + "|" # file size (byte)
209
+ out.print @max_reclen[filename].to_s + "|" # length of logest record (byte)
210
+ out.print remarks + "|" # remarks
211
+ end
212
+ end
213
+
214
+ end
215
+
216
+ # symbol to column number
217
+ def column_s(key)
218
+ return COLUMNS.index(key)
219
+ end
220
+
221
+ # alphabet to column number
222
+ def column_a(alphabet)
223
+ a = "A"[0]
224
+ n = -1
225
+ alphabet.reverse.split(//).each_with_index do |x, i|
226
+ n += (x.capitalize[0] - a + 1) * (26 ** i)
227
+ end
228
+ return n
229
+ end
230
+
231
+ # convert an alphabet to cell data
232
+ def cell(alphabet, row)
233
+ return row[column_a(alphabet)].to_s
234
+ end
235
+
236
+ # config analyzer
237
+ def analyzer(str, row, target)
238
+ case str.to_s
239
+ when / or /
240
+ result = ""
241
+ str.to_s.split("or").map{|x| x.strip}.each do |v|
242
+ value = analyzer(v, row, target)
243
+ if value != "" and value != "0"
244
+ result = value
245
+ break
246
+ end
247
+ end
248
+ result
249
+ when "timestamp"
250
+ Date.today.to_s.split("-").join.to_s
251
+ when "Datum der Tabelle"
252
+ File::mtime(target).strftime("%Y%m%d")
253
+ when /\+/
254
+ str.split("+").map{|x| analyzer(x.strip, row, target)}.join.to_s
255
+ when /if/ # if condition
256
+ result = ""
257
+ str.split("if").map{|x| x.split("then")}.slice(1..-1).each do |ifthen|
258
+ if ifthen[0] =~ /([A-Z]+)(\W+)([0-9.-]+)/ || ifthen[0] =~ /([A-Z]+)(\W+)\"(.+)\"/
259
+ condition = [$0.strip, $1.strip, $2.strip, $3.strip]
260
+ end
261
+ value = ifthen[1]
262
+ s = 'if "' + cell(condition[1], row) + '"' + condition[2] + '"' + condition[3] + '" then ' + value + ' end'
263
+ result = eval(s)
264
+ break if result != nil
265
+ end
266
+ result.to_s
267
+ when /,/ # iteration
268
+ a = str.to_s.split(",").map{|x| x.strip}[@iterate_count]
269
+ analyzer(a, row, target)
270
+ when /"(.+)"/ # constant string
271
+ $1
272
+ when /(\d+ )+/ # pharmacode (increment)
273
+ @pharmacode_increment = true
274
+ (str.to_s.delete(" ").to_i + @pharmacode).to_s
275
+ when /[A-Z]/ # refer to columns
276
+ value = cell(str.to_s, row)
277
+ if value =~ /^0/ # keep the top '0'
278
+ value = value.to_s
279
+ elsif value =~ /\d{5}/ # for EAN code and pharmacode (long digit)
280
+ value = value.to_i
281
+ end
282
+ if str.to_s =~ /A/ and @prefix and @prefix != "" # add prefix
283
+ value = @prefix.to_s + " " + value.to_s
284
+ end
285
+ return value.to_s
286
+ else
287
+ str.to_s
288
+ end
289
+ end
290
+ end
data/setup.rb ADDED
@@ -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