analogger 0.5.0

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.
data/INSTALL ADDED
@@ -0,0 +1,60 @@
1
+ Swiftcore Analogger 0.5
2
+
3
+ Homepage:: http://analogger.swiftcore.org
4
+ Copyright:: (C) 2007 by Kirk Haines. All Rights Reserved.
5
+ Email:: wyhaines@gmail.com
6
+
7
+
8
+ Installation
9
+ ------------
10
+
11
+ To install analogger:
12
+
13
+ ruby setup.rb
14
+
15
+ The analogger executable will be installed into the ruby installation's
16
+ bindir, along with server and client libraries into the site_lib. The
17
+ rdoc documentation will also be generated.
18
+
19
+ ruby setup.rb --help
20
+
21
+ to se a full list of options.
22
+
23
+
24
+ Quickstart
25
+ ----------
26
+
27
+ To start an Analogger instance, first create a configuration file:
28
+
29
+ port: 6766
30
+ host: 127.0.0.1
31
+ default_log: /var/log/weblogs/default
32
+ daemonize: true
33
+ syncinterval: 60
34
+ logs:
35
+ - service: bigapp
36
+ logfile: /var/log/bigapp
37
+ cull: true
38
+ - service:
39
+ - smallapp1
40
+ - smallapp2
41
+ logfile: /var/log/smallapps
42
+ cull: true
43
+ - service: newsletter_sender
44
+ logfile: /var/log/newsletter.log
45
+ cull: false
46
+
47
+
48
+ Then start the analogger:
49
+
50
+ /usr/bin/env analogger -c config_file
51
+
52
+
53
+ To use the client library to connect to an Analogger instance and send
54
+ logging messages to it:
55
+
56
+ require 'swiftcore/Analogger/Client'
57
+
58
+ logger = Swiftcdore::Analogger::Client.new('smallapp1','127.0.0.1','6766')
59
+
60
+ logger.log('info','This is a log message.')
data/README ADDED
@@ -0,0 +1,19 @@
1
+ Swiftcore Analogger 0.5
2
+
3
+ Homepage:: http://analogger.swiftcore.org
4
+ Copyright:: (C) 2007 by Kirk Haines. All Rights Reserved.
5
+ Email:: wyhaines@gmail.com
6
+ License:: Ruby's License
7
+
8
+ The Swiftcore Analogger implements a fast asynchronous logging system
9
+ for Ruby programs as well as client library for sending logging messages
10
+ to the Analogger process.
11
+
12
+ Analogger will accept logs from multiple sources and can have multiple
13
+ logging destinations. Currently, logging to a file, to STDOUT, or to
14
+ STDERR is supported. A future revision may support logging to a
15
+ database destination, as well.
16
+
17
+ Analogger depends on EventMachine (http://rubyforge.org/projects/eventmachine)
18
+ to provide the framework for the network communications, though EM is
19
+ not used for the client library.
@@ -0,0 +1,41 @@
1
+ #####
2
+ # Swiftcore Analogger
3
+ # http://analogger.swiftcore.org
4
+ # Copyright 2007 Kirk Haines
5
+ #
6
+ # Licensed under the Ruby License. See the README for details.
7
+ #
8
+ #####
9
+
10
+ spec = Gem::Specification.new do |s|
11
+ s.name = 'analogger'
12
+ s.author = %q(Kirk Haines)
13
+ s.email = %q(wyhaines@gmail.com)
14
+ s.version = '0.5.0'
15
+ s.summary = %q(A fast asynchronous logging service and client for Ruby.)
16
+ s.platform = Gem::Platform::RUBY
17
+
18
+ s.has_rdoc = true
19
+ s.rdoc_options = %w(--title Swiftcore::Analogger --main README --line-numbers)
20
+ s.extra_rdoc_files = %w(README)
21
+
22
+ s.files = Dir['**/*']
23
+ s.executables = %w(analogger)
24
+ s.require_paths = %w(src)
25
+
26
+ s.requirements << "Eventmachine 0.7.0 or higher."
27
+ s.add_dependency('eventmachine')
28
+ s.test_files = ['test/TC_Analogger.rb']
29
+
30
+ s.rubyforge_project = %q(analogger)
31
+ s.homepage = %q(http://analogger.swiftcore.org/)
32
+ description = []
33
+ File.open("README") do |file|
34
+ file.each do |line|
35
+ line.chomp!
36
+ break if line.empty?
37
+ description << "#{line.gsub(/\[\d\]/, '')}"
38
+ end
39
+ end
40
+ s.description = description[1..-1].join(" ")
41
+ end
@@ -0,0 +1,94 @@
1
+ #!ruby
2
+
3
+ require 'yaml'
4
+ require 'optparse'
5
+ begin
6
+ load_attempted ||= false
7
+ require 'swiftcore/Analogger'
8
+ rescue LoadError => e
9
+ unless load_attempted
10
+ load_attempted = true
11
+ require 'rubygems'
12
+ retry
13
+ end
14
+ raise e
15
+ end
16
+
17
+ #####
18
+ #
19
+ # Swiftcore Analogger
20
+ #
21
+ # The Swiftcore Analogger is an asyncronous logging service intended to
22
+ # provide a fast, flexible, centralized logging service for client
23
+ # applications.
24
+ #
25
+ # Clients connect using and instance of the Swiftcore::Analogger::Client
26
+ # class, and can then deliver logging messages to the service. The
27
+ # Analogger is configured using a file to define mappings of service
28
+ # labels to logging destinations, accepted severity levels, and
29
+ # and whether to cull repeated messages or not.
30
+ #
31
+ #####
32
+
33
+ module Swiftcore
34
+ class AnaloggerExec
35
+ def self.parse_options(config = {})
36
+ # Config file to read
37
+ #
38
+ # port: 12345
39
+ # secret: abcdef
40
+ # interval: 1
41
+ # default_log: /var/log/emlogger
42
+ # logs:
43
+ # - service: client1
44
+ # levels: info
45
+ # logfile: /foo/bar.txt
46
+ # cull: true
47
+ #
48
+ OptionParser.new do |opts|
49
+ opts.banner = 'Usage: analogger.rb [options]'
50
+ opts.separator ''
51
+ opts.on('-c','--config CONFFILE',"The configuration file to read.") do |conf|
52
+ config = YAML.load(File.read(conf))
53
+ end
54
+ opts.on('-p','--port [PORT]',Integer,"The port to receive connections on.") do |port|
55
+ config[Swiftcore::Analogger::Cport] = port
56
+ end
57
+ opts.on('-h','--host [HOST]',String,"The host to bind the connection to.") do |host|
58
+ config[Swiftcore::Analogger::Chost] = host
59
+ end
60
+ # opts.on('-r','--controlkey [KEY]',String,"The secret key that authenticates a control session.") do |secret|
61
+ # config[Swiftcore::Analogger::Csecret] = secret
62
+ # end
63
+ opts.on('-k','--key [KEY]',String,"The secret key that authenticates a valid client session.") do |secret|
64
+ config[Swiftcore::Analogger::Ckey] = secret
65
+ end
66
+ opts.on('-i','--interval [INTERVAL]',String,"The interval between queue writes. Defaults to 1 second.") do |interval|
67
+ config[Swiftcore::Analogger::Cinterval] = interval
68
+ end
69
+ opts.on('-s','--syncinterval [INTERVAL]',String,"The interval between queue syncs. Defaults to 60 seconds.") do |interval|
70
+ config[Swiftcore::Analogger::Csyncinterval] = interval
71
+ end
72
+ opts.on('-d','--default [PATH]',String,"The default log destination. Defaults to stdout.") do |default|
73
+ config[Swiftcore::Analogger::Cdefault_log] = default
74
+ end
75
+ opts.on('-x','--daemonize',"Tell the Analogger to daemonize itself.") do
76
+ config[Swiftcore::Analogger::Cdaemonize] = true
77
+ end
78
+ opts.on('-w','--writepid [FILENAME]',"The filename to write a PID file to.") do |pidfile|
79
+ config[Swiftcore::Analogger::Cpidfile] = pidfile || 'analogger.pid'
80
+ end
81
+ end.parse!
82
+ config
83
+ end
84
+
85
+ def self.run
86
+ Swiftcore::Analogger.start(parse_options)
87
+ end
88
+
89
+ end
90
+ end
91
+
92
+ loop do
93
+ catch(:hup) {Swiftcore::AnaloggerExec.run}
94
+ end
@@ -0,0 +1,672 @@
1
+ require 'rbconfig'
2
+ require 'fileutils'
3
+ require 'optparse'
4
+ require 'yaml'
5
+
6
+ class String
7
+ # This patch for win32 paths contributed by James Britt.
8
+ def w32
9
+ if self =~ /^\w:\/\w:/i
10
+ self.gsub(/^\w:\//i, '')
11
+ else
12
+ self
13
+ end
14
+ end
15
+ end
16
+
17
+ module Package
18
+
19
+ class SpecificationError < StandardError; end
20
+ class PackageSpecification_1_0; end
21
+
22
+ SEMANTICS = { "1.0" => PackageSpecification_1_0 }
23
+
24
+ KINDS = [
25
+ :bin, :lib, :ext, :data, :conf, :doc
26
+ ]
27
+
28
+ mapping = { '.' => '\.', '$' => '\$', '#' => '\#', '*' => '.*' }
29
+ ignore_files = %w[core RCSLOG tags TAGS .make.state .nse_depinfo .hg
30
+ #* .#* cvslog.* ,* .del-* *.olb *~ *.old *.bak *.BAK *.orig *.rej _$* *$
31
+ *.org *.in .* ]
32
+
33
+ IGNORE_FILES = ignore_files.map do |x|
34
+ Regexp.new('\A' + x.gsub(/[\.\$\#\*]/){|c| mapping[c]} + '\z')
35
+ end
36
+
37
+ def self.config(name)
38
+ # XXX use pathname
39
+ prefix = Regexp.quote(Config::CONFIG["prefix"])
40
+ exec_prefix = Regexp.quote(Config::CONFIG["exec_prefix"])
41
+ Config::CONFIG[name].gsub(/\A\/?(#{prefix}|#{exec_prefix})\/?/, '')
42
+ end
43
+
44
+ SITE_DIRS = {
45
+ :bin => config("bindir"),
46
+ :lib => config("sitelibdir"),
47
+ :ext => config("sitearchdir"),
48
+ :data => config("datadir"),
49
+ :conf => config("sysconfdir"),
50
+ :doc => File.join(config("datadir"), "doc"),
51
+ }
52
+
53
+ VENDOR_DIRS = {
54
+ :bin => config("bindir"),
55
+ :lib => config("rubylibdir"),
56
+ :ext => config("archdir"),
57
+ :data => config("datadir"),
58
+ :conf => config("sysconfdir"),
59
+ :doc => File.join(config("datadir"), "doc"),
60
+ }
61
+
62
+ MODES = {
63
+ :bin => 0755,
64
+ :lib => 0644,
65
+ :ext => 0755, # was: 0555,
66
+ :data => 0644,
67
+ :conf => 0644,
68
+ :doc => 0644,
69
+ }
70
+
71
+
72
+ SETUP_OPTIONS = {:parse_cmdline => true, :load_conf => true, :run_tasks => true}
73
+ RUBY_BIN = File.join(::Config::CONFIG['bindir'],::Config::CONFIG['ruby_install_name']) << ::Config::CONFIG['EXEEXT']
74
+
75
+ def self.setup(version, options = {}, &instructions)
76
+ prefixes = dirs = nil
77
+ options = SETUP_OPTIONS.dup.update(options)
78
+
79
+ if options[:load_conf] && File.exist?("config.save")
80
+ config = YAML.load_file "config.save"
81
+ prefixes = config[:prefixes]
82
+ dirs = config[:dirs]
83
+ end
84
+
85
+ pkg = package_specification_with_semantics(version).new(prefixes, dirs)
86
+ pkg.parse_command_line if options[:parse_cmdline]
87
+ pkg.instance_eval(&instructions)
88
+
89
+ pkg.run_tasks if options[:run_tasks]
90
+
91
+ # pkg.install
92
+ pkg
93
+ end
94
+
95
+ def self.package_specification_with_semantics(version)
96
+ #XXX: implement the full x.y(.z)? semantics
97
+ r = SEMANTICS[version]
98
+ raise SpecificationError, "Unknown version #{version}." unless r
99
+ r
100
+ end
101
+
102
+
103
+ module Actions
104
+
105
+ class InstallFile
106
+
107
+ attr_reader :source, :destination, :mode
108
+
109
+ def initialize(source, destination, mode, options)
110
+ @source = source
111
+ @destination = destination
112
+ @mode = mode
113
+ @options = options
114
+ end
115
+
116
+ def install
117
+ dirs = [@options.destdir.to_s, @destination].select {|x| x}
118
+ d = File.expand_path(File.join(dirs)).w32
119
+ FileUtils.install @source, d,
120
+ {:verbose => @options.verbose,
121
+ :noop => @options.noop, :mode => @mode }
122
+ end
123
+
124
+ def hash
125
+ [@source.hash, @destination.hash].hash
126
+ end
127
+
128
+ def eql?(other)
129
+ self.class == other.class &&
130
+ @source == other.source &&
131
+ @destination == other.destination &&
132
+ @mode == other.mode
133
+ end
134
+
135
+ def <=>(other)
136
+ FULL_ORDER[self, other] || self.destination <=> other.destination
137
+ end
138
+ end
139
+
140
+ class MkDir
141
+
142
+ attr_reader :directory
143
+
144
+ def initialize(directory, options)
145
+ @directory = directory
146
+ @options = options
147
+ end
148
+
149
+ def install
150
+ dirs = [@options.destdir.to_s, @directory].select {|x| x}
151
+ d = File.expand_path(File.join(dirs)).w32
152
+ FileUtils.mkdir_p d,
153
+ {:verbose => @options.verbose,
154
+ :noop => @options.noop }
155
+ end
156
+
157
+ def <=>(other)
158
+ FULL_ORDER[self, other] || self.directory <=> other.directory
159
+ end
160
+ end
161
+
162
+ class FixShebang
163
+
164
+ attr_reader :destination
165
+
166
+ def initialize(destination, options)
167
+ @options = options
168
+ @destination = destination
169
+ end
170
+
171
+ def install
172
+ dirs = [@options.destdir.to_s, @destination].select {|x| x}
173
+ d = File.expand_path(File.join(dirs)).w32
174
+ path = d
175
+ fix_shebang(path)
176
+ end
177
+
178
+ # taken from rpa-base, originally based on setup.rb's
179
+ # modify: #!/usr/bin/ruby
180
+ # modify: #! /usr/bin/ruby
181
+ # modify: #!ruby
182
+ # not modify: #!/usr/bin/env ruby
183
+ SHEBANG_RE = /\A\#!\s*\S*ruby\S*/
184
+
185
+ #TODO allow the ruby-prog to be placed in the shebang line to be passed as
186
+ # an option
187
+ def fix_shebang(path)
188
+ tmpfile = path + '.tmp'
189
+ begin
190
+ #XXX: needed at all?
191
+ # it seems that FileUtils doesn't expose its default output
192
+ # @fileutils_output = $stderr
193
+ # we might want to allow this to be redirected.
194
+ $stderr.puts "shebang:open #{tmpfile}" if @options.verbose
195
+ unless @options.noop
196
+ File.open(path) do |r|
197
+ File.open(tmpfile, 'w', 0755) do |w|
198
+ first = r.gets
199
+ return unless SHEBANG_RE =~ first
200
+ ruby = File.join(::Config::CONFIG['bindir'],::Config::CONFIG['ruby_install_name'])
201
+ ruby << ::Config::CONFIG['EXEEXT']
202
+ #w.print first.sub(SHEBANG_RE, '#!' + Config::CONFIG['ruby-prog'])
203
+ w.print first.sub(SHEBANG_RE, '#!' + ruby)
204
+ w.write r.read
205
+ end
206
+ end
207
+ end
208
+ FileUtils.mv(tmpfile, path, :verbose => @options.verbose,
209
+ :noop => @options.noop)
210
+ ensure
211
+ FileUtils.rm_f(tmpfile, :verbose => @options.verbose,
212
+ :noop => @options.noop)
213
+ end
214
+ end
215
+
216
+ def <=>(other)
217
+ FULL_ORDER[self, other] || self.destination <=> other.destination
218
+ end
219
+
220
+ def hash
221
+ @destination.hash
222
+ end
223
+
224
+ def eql?(other)
225
+ self.class == other.class && self.destination == other.destination
226
+ end
227
+ end
228
+
229
+ order = [MkDir, InstallFile, FixShebang]
230
+ FULL_ORDER = lambda do |me, other|
231
+ a, b = order.index(me.class), order.index(other.class)
232
+ if a && b
233
+ (r = a - b) == 0 ? nil : r
234
+ else
235
+ -1
236
+ end
237
+ end
238
+
239
+ class ActionList < Array
240
+
241
+ def directories!(options)
242
+ dirnames = []
243
+ map! do |d|
244
+ if d.kind_of?(InstallFile) && !dirnames.include?(File.dirname(d.destination))
245
+ dirnames << File.dirname(d.destination)
246
+ [MkDir.new(File.dirname(d.destination), options), d]
247
+ else
248
+ d
249
+ end
250
+ end
251
+ flatten!
252
+ end
253
+
254
+ def run(task)
255
+ each { |action| action.__send__ task }
256
+ end
257
+ end
258
+
259
+ end # module Actions
260
+
261
+ Options = Struct.new(:noop, :verbose, :destdir, :nodoc)
262
+
263
+ class PackageSpecification_1_0
264
+
265
+ TASKS = %w[config setup install test show]
266
+ # default options for translate(foo => bar)
267
+ TRANSLATE_DEFAULT_OPTIONS = { :inherit => true }
268
+
269
+ def self.declare_file_type(args, &handle_arg)
270
+ str_arr_p = lambda{|x| Array === x && x.all?{|y| String === y}}
271
+
272
+ # strict type checking --- we don't want this to be extended arbitrarily
273
+ unless args.size == 1 && Hash === args.first &&
274
+ args.first.all?{|f,r| [Proc, String, NilClass].include?(r.class) &&
275
+ (String === f || str_arr_p[f])} or
276
+ args.all?{|x| String === x || str_arr_p[x]}
277
+ raise SpecificationError,
278
+ "Unspecified semantics for the given arguments: #{args.inspect}"
279
+ end
280
+
281
+ if args.size == 1 && Hash === args.first
282
+ args.first.to_a.each do |file, rename_info|
283
+ if Array === file
284
+ # ignoring boring files
285
+ handle_arg.call(file, true, rename_info)
286
+ else
287
+ # we do want "boring" files given explicitly
288
+ handle_arg.call([file], false, rename_info)
289
+ end
290
+ end
291
+ else
292
+ args.each do |a|
293
+ if Array === a
294
+ a.each{|file| handle_arg.call(file, true, nil)}
295
+ else
296
+ handle_arg.call(a, false, nil)
297
+ end
298
+ end
299
+ end
300
+ end
301
+
302
+ #{{{ define the file tagging methods
303
+ KINDS.each do |kind|
304
+ define_method(kind) do |*args| # if this were 1.9 we could also take a block
305
+ bin_callback = lambda do |kind_, type, dest, options|
306
+ next if kind_ != :bin || type == :dir
307
+ @actions << Actions::FixShebang.new(dest, options)
308
+ end
309
+ #TODO: refactor
310
+ self.class.declare_file_type(args) do |files, ignore_p, opt_rename_info|
311
+ files.each do |file|
312
+ next if ignore_p && IGNORE_FILES.any?{|re| re.match(file)}
313
+ add_file(kind, file, opt_rename_info, &bin_callback)
314
+ end
315
+ end
316
+ end
317
+ end
318
+
319
+ def unit_test(*files)
320
+ if ARGV.length > 0
321
+ files.each do |x|
322
+ @unit_tests.concat([x]) if ARGV.include?(File.basename(x).sub(/\.\w+$/,''))
323
+ end
324
+ else
325
+ @unit_tests.concat files.flatten
326
+ end
327
+ end
328
+
329
+ def ri(*files)
330
+ @ri_files.concat files.flatten
331
+ end
332
+
333
+ def build_ext(path,opts = {:mkmf => true}, &blk)
334
+ @ext_targets << [path,opts,blk]
335
+ end
336
+
337
+ attr_accessor :actions, :options
338
+
339
+ def self.metadata(name)
340
+ define_method(name) do |*args|
341
+ if args.size == 1
342
+ @metadata[name] = args.first
343
+ end
344
+ @metadata[name]
345
+ end
346
+ end
347
+
348
+ metadata :name
349
+ metadata :version
350
+ metadata :author
351
+
352
+ def translate_dir(kind, dir)
353
+ replaced_dir_parts = dir.split(%r{/})
354
+ kept_dir_parts = []
355
+ loop do
356
+ replaced_path = replaced_dir_parts.join("/")
357
+ target, options = @translate[kind][replaced_path]
358
+ options ||= TRANSLATE_DEFAULT_OPTIONS
359
+ if target && (replaced_path == dir || options[:inherit])
360
+ dir = (target != '' ? File.join(target, *kept_dir_parts) :
361
+ File.join(*kept_dir_parts))
362
+ break
363
+ end
364
+ break if replaced_dir_parts.empty?
365
+ kept_dir_parts.unshift replaced_dir_parts.pop
366
+ end
367
+ dir
368
+ end
369
+
370
+ def add_file(kind, filename, new_filename_info, &callback)
371
+ #TODO: refactor!!!
372
+ if File.directory? filename #XXX setup.rb and rpa-base defined File.dir?
373
+ dir = filename.sub(/\A\.\//, "").sub(/\/\z/, "")
374
+ dest = File.join(@prefixes[kind], @dirs[kind], translate_dir(kind, dir))
375
+ @actions << Actions::MkDir.new(dest, @options)
376
+ callback.call(kind, :dir, dest, @options) if block_given?
377
+ else
378
+ if new_filename_info
379
+ case new_filename_info
380
+ when Proc
381
+ dest_name = new_filename_info.call(filename.dup)
382
+ else
383
+ dest_name = new_filename_info.dup
384
+ end
385
+ else
386
+ dest_name = filename.dup
387
+ end
388
+
389
+ dirname = File.dirname(dest_name)
390
+ dirname = "" if dirname == "."
391
+ dest_name = File.join(translate_dir(kind, dirname), File.basename(dest_name))
392
+
393
+ dest = File.join(@prefixes[kind], @dirs[kind], dest_name)
394
+ @actions << Actions::InstallFile.new(filename, dest, MODES[kind], @options)
395
+ callback.call(kind, :file, dest, @options) if block_given?
396
+ end
397
+ end
398
+
399
+ def initialize(prefixes = nil, dirs = nil)
400
+ @prefix = Config::CONFIG["prefix"].gsub(/\A\//, '')
401
+ @translate = {}
402
+ @prefixes = (prefixes || {}).dup
403
+ KINDS.each do |kind|
404
+ @prefixes[kind] = @prefix unless prefixes
405
+ @translate[kind] = {}
406
+ end
407
+
408
+ @dirs = (dirs || {}).dup
409
+ @dirs.update SITE_DIRS unless dirs
410
+
411
+ @actions = Actions::ActionList.new
412
+
413
+ @metadata = {}
414
+ @unit_tests = []
415
+ @ri_files = []
416
+ @ext_targets = []
417
+
418
+ @options = Options.new
419
+ @options.verbose = true
420
+ @options.noop = false # XXX for testing
421
+ @options.destdir = ''
422
+
423
+ @tasks = []
424
+ end
425
+
426
+ def aoki
427
+ (KINDS - [:ext]).each do |kind|
428
+ translate(kind, kind.to_s => "", :inherit => true)
429
+ __send__ kind, Dir["#{kind}/**/*"]
430
+ end
431
+ translate(:ext, "ext/*" => "", :inherit => true)
432
+ ext Dir["ext/**/*.#{Config::CONFIG['DLEXT']}"]
433
+ end
434
+
435
+ # Builds any needed extensions.
436
+
437
+ def setup
438
+ @ext_targets.each do |et|
439
+ path, options, block = et
440
+ path = expand_ext_path(path)
441
+ case
442
+ when options[:mkmf]
443
+ setup_mkmf(path, options, block)
444
+ end
445
+ end
446
+ end
447
+
448
+ def expand_ext_path(path)
449
+ if path =~ /#{File::SEPARATOR}/
450
+ extpath = File.expand_path(path)
451
+ else
452
+ extpath = File.expand_path(File.join('ext',path,'extconf.rb'))
453
+ end
454
+ unless extpath =~ /\.rb$/
455
+ extpath = File.join(extpath,'extconf.rb')
456
+ end
457
+ extpath
458
+ end
459
+
460
+ def setup_mkmf(execpath, options, block)
461
+ $stderr.puts "Building extension in #{File.dirname(execpath)} with #{options.inspect}."
462
+ cwd = Dir.pwd
463
+ begin
464
+ if block
465
+ block.call(options)
466
+ else
467
+ Dir.chdir(File.dirname(execpath))
468
+ system('make','clean') if FileTest.exist? "Makefile"
469
+ system(RUBY_BIN,execpath)
470
+ system('make')
471
+ end
472
+ ensure
473
+ Dir.chdir(cwd)
474
+ end
475
+ end
476
+
477
+ def install
478
+ $stderr.puts "Installing #{name || "unknown package"} #{version}..." if options.verbose
479
+
480
+ actions.uniq!
481
+ actions.sort!
482
+ actions.directories!(options)
483
+
484
+ actions.run :install
485
+
486
+ unless @ri_files.empty? or @options.nodoc
487
+ $stderr.puts "Generating ri documentation from #{@ri_files.join(',')}"
488
+ require 'rdoc/rdoc'
489
+ unless options.noop
490
+ begin
491
+ RDoc::RDoc.new.document(["--ri-site"].concat(@ri_files.flatten))
492
+ rescue Exception => e
493
+ $stderr.write("Installation of ri documentation failed: #{e.to_s} #{e.backtrace.join("\n")}")
494
+ end
495
+ end
496
+ end
497
+ end
498
+
499
+ def test
500
+ unless @unit_tests.empty?
501
+ puts "Testing #{name || "unknown package"} #{version}..." if options.verbose
502
+ require 'test/unit'
503
+ unless options.noop
504
+ t = Test::Unit::AutoRunner.new(true)
505
+ t.process_args(@unit_tests)
506
+ t.run
507
+ end
508
+ end
509
+ end
510
+
511
+ def config
512
+ File.open("config.save", "w") do |f|
513
+ YAML.dump({:prefixes => @prefixes, :dirs => @dirs}, f)
514
+ end
515
+ end
516
+
517
+ def show
518
+ KINDS.each do |kind|
519
+ puts "#{kind}\t#{File.join(options.destdir, @prefixes[kind], @dirs[kind])}"
520
+ end
521
+ end
522
+
523
+ def translate(kind, additional_translations)
524
+ default_opts = TRANSLATE_DEFAULT_OPTIONS.dup
525
+ key_val_pairs = additional_translations.to_a
526
+ option_pairs = key_val_pairs.select{|(k,v)| Symbol === k}
527
+ default_opts.update(Hash[*option_pairs.flatten])
528
+
529
+ (key_val_pairs - option_pairs).each do |key, val|
530
+ add_translation(kind, key, val, default_opts)
531
+ end
532
+ end
533
+
534
+ def add_translation(kind, src, dest, options)
535
+ if is_glob?(src)
536
+ dirs = expand_dir_glob(src)
537
+ else
538
+ dirs = [src]
539
+ end
540
+ dirs.each do |dirname|
541
+ dirname = dirname.sub(%r{\A\./}, "").sub(%r{/\z}, "")
542
+ @translate[kind].update({dirname => [dest, options]})
543
+ end
544
+ end
545
+
546
+ def is_glob?(x)
547
+ /(^|[^\\])[*?{\[]/.match(x)
548
+ end
549
+
550
+ def expand_dir_glob(src)
551
+ Dir[src].select{|x| File.directory?(x)}
552
+ end
553
+
554
+ def clean_path(path)
555
+ path.gsub(/\A\//, '').gsub(/\/+\Z/, '').squeeze("/")
556
+ end
557
+
558
+ def parse_command_line
559
+ opts = OptionParser.new(nil, 24, ' ') do |opts|
560
+ opts.banner = "Usage: setup.rb [options] [task]"
561
+
562
+ opts.separator ""
563
+ opts.separator "Tasks:"
564
+ opts.separator " config configures paths"
565
+ opts.separator " show shows paths"
566
+ opts.separator " setup compiles ruby extentions and others XXX"
567
+ opts.separator " install installs files"
568
+ opts.separator " test runs unit tests"
569
+
570
+
571
+ opts.separator ""
572
+ opts.separator "Specific options:"
573
+
574
+ opts.on "--prefix=PREFIX",
575
+ "path prefix of target environment [#@prefix]" do |prefix|
576
+ @prefix.replace clean_path(prefix) # Shared!
577
+ end
578
+
579
+ opts.separator ""
580
+
581
+ KINDS.each do |kind|
582
+ opts.on "--#{kind}prefix=PREFIX",
583
+ "path prefix for #{kind} files [#{@prefixes[kind]}]" do |prefix|
584
+ @prefixes[kind] = clean_path(prefix)
585
+ end
586
+ end
587
+
588
+ opts.separator ""
589
+
590
+ KINDS.each do |kind|
591
+ opts.on "--#{kind}dir=PREFIX",
592
+ "directory for #{kind} files [#{@dirs[kind]}]" do |prefix|
593
+ @dirs[kind] = clean_path(prefix)
594
+ end
595
+ end
596
+
597
+ opts.separator ""
598
+
599
+ KINDS.each do |kind|
600
+ opts.on "--#{kind}=PREFIX",
601
+ "absolute directory for #{kind} files [#{File.join(@prefixes[kind], @dirs[kind])}]" do |prefix|
602
+ @prefixes[kind] = clean_path(prefix)
603
+ end
604
+ end
605
+
606
+ opts.separator ""
607
+ opts.separator "Predefined path configurations:"
608
+ opts.on "--site", "install into site-local directories (default)" do
609
+ @dirs.update SITE_DIRS
610
+ end
611
+
612
+ opts.on "--vendor", "install into distribution directories (for packagers)" do
613
+ @dirs.update VENDOR_DIRS
614
+ end
615
+
616
+ opts.separator ""
617
+ opts.separator "General options:"
618
+
619
+ opts.on "--destdir=DESTDIR",
620
+ "install all files relative to DESTDIR (/)" do |destdir|
621
+ @options.destdir = destdir
622
+ end
623
+
624
+ opts.on "--dry-run", "only display what to do if given [#{@options.noop}]" do
625
+ @options.noop = true
626
+ end
627
+
628
+ opts.on "--no-harm", "only display what to do if given" do
629
+ @options.noop = true
630
+ end
631
+
632
+ opts.on "--no-doc","don't generate documentation" do
633
+ @options.nodoc = true
634
+ end
635
+
636
+ opts.on "--[no-]verbose", "output messages verbosely [#{@options.verbose}]" do |verbose|
637
+ @options.verbose = verbose
638
+ end
639
+
640
+ opts.on_tail("-h", "--help", "Show this message") do
641
+ puts opts
642
+ exit
643
+ end
644
+ end
645
+
646
+ opts.parse! ARGV
647
+
648
+ #if (ARGV - TASKS).empty? # Only existing tasks?
649
+ @tasks = ARGV.dup.select do |t|
650
+ if TASKS.include?(t)
651
+ ARGV.delete(t)
652
+ true
653
+ end
654
+ end
655
+
656
+ @tasks = ["setup","install"] if @tasks.empty?
657
+ #else
658
+ # abort "Unknown task(s) #{(ARGV-TASKS).join ", "}."
659
+ #end
660
+ end
661
+
662
+ def run_tasks
663
+ @tasks.each { |task| __send__ task }
664
+ end
665
+ end
666
+
667
+ end # module Package
668
+
669
+ require 'rbconfig'
670
+ def config(x)
671
+ Config::CONFIG[x]
672
+ end