ahoward-main 2.9.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.
@@ -0,0 +1,241 @@
1
+ This.author = "Ara T. Howard"
2
+ This.email = "ara.t.howard@gmail.com"
3
+ This.homepage = "http://github.com/ahoward/#{ This.lib }/tree/master"
4
+ This.rubyforge_project = 'codeforpeople'
5
+
6
+ task :default do
7
+ puts(Rake::Task.tasks.map{|task| task.name} - ['default'])
8
+ end
9
+
10
+ task :spec do
11
+ require 'spec/rake/spectask'
12
+ Spec::Rake::SpecTask.new do |t|
13
+ t.spec_files = FileList['spec/*_spec.rb']
14
+ end
15
+ end
16
+
17
+ task :gemspec do
18
+ ignore_extensions = 'git', 'svn', 'tmp', /sw./, 'bak', 'gem'
19
+ ignore_directories = 'pkg'
20
+ ignore_files = 'test/log'
21
+
22
+ shiteless =
23
+ lambda do |list|
24
+ list.delete_if do |entry|
25
+ next unless test(?e, entry)
26
+ extension = File.basename(entry).split(%r/[.]/).last
27
+ ignore_extensions.any?{|ext| ext === extension}
28
+ end
29
+ list.delete_if do |entry|
30
+ next unless test(?d, entry)
31
+ dirname = File.expand_path(entry)
32
+ ignore_directories.any?{|dir| File.expand_path(dir) == dirname}
33
+ end
34
+ list.delete_if do |entry|
35
+ next unless test(?f, entry)
36
+ filename = File.expand_path(entry)
37
+ ignore_files.any?{|file| File.expand_path(file) == filename}
38
+ end
39
+ end
40
+
41
+ lib = This.lib
42
+ version = This.version
43
+ files = shiteless[Dir::glob("**/**")]
44
+ executables = shiteless[Dir::glob("bin/*")].map{|exe| File.basename(exe)}
45
+ has_rdoc = true #File.exist?('doc')
46
+ test_files = "test/#{ lib }.rb" if File.file?("test/#{ lib }.rb")
47
+
48
+ extensions = This.extensions
49
+ if extensions.nil?
50
+ %w( Makefile configure extconf.rb ).each do |ext|
51
+ extensions << ext if File.exists?(ext)
52
+ end
53
+ end
54
+ extensions = [extensions].flatten.compact
55
+
56
+ template =
57
+ if test(?e, 'gemspec.erb')
58
+ Template{ IO.read('gemspec.erb') }
59
+ else
60
+ Template {
61
+ <<-__
62
+ ## #{ lib }.gemspec
63
+ #
64
+
65
+ Gem::Specification::new do |spec|
66
+ spec.name = #{ lib.inspect }
67
+ spec.version = #{ version.inspect }
68
+ spec.platform = Gem::Platform::RUBY
69
+ spec.summary = #{ lib.inspect }
70
+
71
+ spec.files = #{ files.inspect }
72
+ spec.executables = #{ executables.inspect }
73
+
74
+ <% if test(?d, 'lib') %>
75
+ spec.require_path = "lib"
76
+ <% end %>
77
+
78
+ spec.has_rdoc = #{ has_rdoc.inspect }
79
+ spec.test_files = #{ test_files.inspect }
80
+ #spec.add_dependency 'lib', '>= version'
81
+ #spec.add_dependency 'fattr'
82
+
83
+ spec.extensions.push(*#{ extensions.inspect })
84
+
85
+ spec.rubyforge_project = #{ This.rubyforge_project.inspect }
86
+ spec.author = #{ This.author.inspect }
87
+ spec.email = #{ This.email.inspect }
88
+ spec.homepage = #{ This.homepage.inspect }
89
+ end
90
+ __
91
+ }
92
+ end
93
+
94
+ open("#{ lib }.gemspec", "w"){|fd| fd.puts template}
95
+ This.gemspec = "#{ lib }.gemspec"
96
+ end
97
+
98
+ task :gem => [:clean, :gemspec] do
99
+ Fu.mkdir_p This.pkgdir
100
+ before = Dir['*.gem']
101
+ cmd = "gem build #{ This.gemspec }"
102
+ `#{ cmd }`
103
+ after = Dir['*.gem']
104
+ gem = ((after - before).first || after.first) or abort('no gem!')
105
+ Fu.mv gem, This.pkgdir
106
+ This.gem = File.basename(gem)
107
+ end
108
+
109
+ task :readme do
110
+ samples = ''
111
+ prompt = '~ > '
112
+ lib = This.lib
113
+ version = This.version
114
+
115
+ Dir['sample*/*'].sort.each do |sample|
116
+ samples << "\n" << " <========< #{ sample } >========>" << "\n\n"
117
+
118
+ cmd = "cat #{ sample }"
119
+ samples << Util.indent(prompt + cmd, 2) << "\n\n"
120
+ samples << Util.indent(`#{ cmd }`, 4) << "\n"
121
+
122
+ cmd = "ruby #{ sample }"
123
+ samples << Util.indent(prompt + cmd, 2) << "\n\n"
124
+
125
+ cmd = "ruby -e'STDOUT.sync=true; exec %(ruby -Ilib #{ sample })'"
126
+ samples << Util.indent(`#{ cmd } 2>&1`, 4) << "\n"
127
+ end
128
+
129
+ template =
130
+ if test(?e, 'readme.erb')
131
+ Template{ IO.read('readme.erb') }
132
+ else
133
+ Template {
134
+ <<-__
135
+ NAME
136
+ #{ lib }
137
+
138
+ DESCRIPTION
139
+
140
+ INSTALL
141
+ gem install #{ lib }
142
+
143
+ SAMPLES
144
+ #{ samples }
145
+ __
146
+ }
147
+ end
148
+
149
+ open("README", "w"){|fd| fd.puts template}
150
+ end
151
+
152
+
153
+ task :clean do
154
+ Dir[File.join(This.pkgdir, '**/**')].each{|entry| Fu.rm_rf(entry)}
155
+ end
156
+
157
+
158
+ task :release => [:clean, :gemspec, :gem] do
159
+ gems = Dir[File.join(This.pkgdir, '*.gem')].flatten
160
+ raise "which one? : #{ gems.inspect }" if gems.size > 1
161
+ raise "no gems?" if gems.size < 1
162
+ cmd = "rubyforge login && rubyforge add_release #{ This.rubyforge_project } #{ This.lib } #{ This.version } #{ This.pkgdir }/#{ This.gem }"
163
+ puts cmd
164
+ system cmd
165
+ end
166
+
167
+
168
+
169
+
170
+
171
+ BEGIN {
172
+ $VERBOSE = nil
173
+
174
+ require 'ostruct'
175
+ require 'erb'
176
+ require 'fileutils'
177
+
178
+ Fu = FileUtils
179
+
180
+ This = OpenStruct.new
181
+
182
+ This.file = File.expand_path(__FILE__)
183
+ This.dir = File.dirname(This.file)
184
+ This.pkgdir = File.join(This.dir, 'pkg')
185
+
186
+ lib = ENV['LIB']
187
+ unless lib
188
+ lib = File.basename(Dir.pwd)
189
+ end
190
+ This.lib = lib
191
+
192
+ version = ENV['VERSION']
193
+ unless version
194
+ name = lib.capitalize
195
+ library = "./lib/#{ lib }.rb"
196
+ program = "./bin/#{ lib }"
197
+ if test(?e, library)
198
+ require library
199
+ version = eval(name).send(:version)
200
+ elsif test(?e, program)
201
+ version = `#{ program } --version`.strip
202
+ end
203
+ abort('no version') if(version.nil? or version.empty?)
204
+ end
205
+ This.version = version
206
+
207
+ abort('no lib') unless This.lib
208
+ abort('no version') unless This.version
209
+
210
+ module Util
211
+ def indent(s, n = 2)
212
+ s = unindent(s)
213
+ ws = ' ' * n
214
+ s.gsub(%r/^/, ws)
215
+ end
216
+
217
+ def unindent(s)
218
+ indent = nil
219
+ s.each do |line|
220
+ next if line =~ %r/^\s*$/
221
+ indent = line[%r/^\s*/] and break
222
+ end
223
+ indent ? s.gsub(%r/^#{ indent }/, "") : s
224
+ end
225
+ extend self
226
+ end
227
+
228
+ class Template
229
+ def initialize(&block)
230
+ @block = block
231
+ @template = block.call.to_s
232
+ end
233
+ def expand(b=nil)
234
+ ERB.new(Util.unindent(@template)).result(b||@block)
235
+ end
236
+ alias_method 'to_s', 'expand'
237
+ end
238
+ def Template(*args, &block) Template.new(*args, &block) end
239
+
240
+ Dir.chdir(This.dir)
241
+ }
data/TODO ADDED
@@ -0,0 +1,20 @@
1
+
2
+ * support pathname casts
3
+ * support uri casts
4
+ * support filename casts
5
+ * support directory costs
6
+ * support io cast '-' (:input, :output)
7
+
8
+ * fix weird super bug jeremy found
9
+
10
+ * figure out how to support '-' ??
11
+
12
+ * clean up warnings under 'ruby -d'
13
+ * error reporting weird for some errors
14
+
15
+ ===============================================================================
16
+ X calls to abort sometimes lead to STDERR prining twice ;-(
17
+ X main's' with modes, but no run do not operate properly - add default run w/wrap_run!
18
+ X usage fubar when extra chunks are set
19
+ X usage of arguments is fubar when negative arities are used
20
+ X add sanity checks at parameter contruction completion
@@ -0,0 +1,60 @@
1
+ module Main
2
+ #
3
+ # top level constants
4
+ #
5
+ Main::VERSION = '2.9.0' unless
6
+ defined? Main::VERSION
7
+ def self.version() Main::VERSION end
8
+
9
+ Main::LIBDIR = File.join(File.dirname(File.expand_path(__FILE__)), self.name.downcase, '') unless
10
+ defined? Main::LIBDIR
11
+ def self.libdir() Main::LIBDIR end
12
+
13
+ Main::EXIT_SUCCESS = 0 unless defined? Main::EXIT_SUCCESS
14
+ Main::EXIT_FAILURE = 1 unless defined? Main::EXIT_FAILURE
15
+ Main::EXIT_WARN = 42 unless defined? Main::EXIT_WARN
16
+ #
17
+ # built-in
18
+ #
19
+ require 'logger'
20
+ require 'enumerator'
21
+ require 'set'
22
+ #
23
+ # use gems to pick up dependancies
24
+ #
25
+ begin
26
+ require 'rubygems'
27
+ rescue LoadError
28
+ 42
29
+ end
30
+
31
+ require 'fattr'
32
+ begin
33
+ version = Fattr.version
34
+ raise unless version[%r/^1\./]
35
+ rescue
36
+ abort "main requires fattrs >= 1.0.3 - gem install fattr"
37
+ end
38
+
39
+ require 'arrayfields'
40
+ begin
41
+ version = Arrayfields.version
42
+ raise unless version[%r/^4\./]
43
+ rescue
44
+ abort "main requires arrayfields >= 4.5.0 - gem install arrayfields"
45
+ end
46
+ #
47
+ # main's own libs
48
+ #
49
+ require libdir + 'stdext'
50
+ require libdir + 'softspoken'
51
+ require libdir + 'util'
52
+ require libdir + 'logger'
53
+ require libdir + 'usage'
54
+ require libdir + 'cast'
55
+ require libdir + 'parameter'
56
+ require libdir + 'getoptlong'
57
+ require libdir + 'mode'
58
+ require libdir + 'base'
59
+ require libdir + 'factories'
60
+ end
@@ -0,0 +1,515 @@
1
+ module Main
2
+ class Base
3
+ class << self
4
+ def wrap_run!
5
+ const_set :RUN, instance_method(:run)
6
+
7
+ class_eval do
8
+ def run *a, &b
9
+ argv.push "--#{ argv.shift }" if argv.first == 'help'
10
+ return mode_run! if mode_given?
11
+
12
+ status =
13
+ catch :exit do
14
+ begin
15
+
16
+ parse_parameters
17
+
18
+ if params['help'] and params['help'].given?
19
+ print usage.to_s
20
+ exit
21
+ end
22
+
23
+ pre_run
24
+ before_run
25
+ self.class.const_get(:RUN).bind(self).call(*a, &b)
26
+ after_run
27
+ post_run
28
+
29
+ finalize
30
+ rescue Exception => e
31
+ handle_exception e
32
+ end
33
+ nil
34
+ end
35
+
36
+ handle_throw status
37
+ end
38
+ end
39
+ end
40
+
41
+ def method_added m
42
+ return if @in_method_added
43
+ super if defined? super
44
+ @in_method_added = true
45
+ begin
46
+ wrap_run! if m.to_s == 'run'
47
+ ensure
48
+ @in_method_added = false
49
+ end
50
+ end
51
+
52
+ def self.inheritable_fattr name, &block
53
+ block ||= lambda{}
54
+ fattr( name ){
55
+ catch :value do
56
+ if parent?
57
+ value = parent.send name
58
+ value =
59
+ begin
60
+ Util.mcp value
61
+ rescue
62
+ value.clone rescue value.dup
63
+ end
64
+ throw :value, value
65
+ end
66
+ instance_eval &block
67
+ end
68
+ }
69
+ end
70
+
71
+ # fattrs
72
+ fattr( 'name' ){ File.basename $0 }
73
+ fattr( 'synopsis' ){ Usage.default_synopsis(self) }
74
+ fattr( 'description' )
75
+ fattr( 'usage' ){ Usage.default_usage self }
76
+ fattr( 'modes' ){ Mode.list }
77
+ fattr( 'mode_definitions' ){ Array.new }
78
+ fattr( 'mode_name' ){ 'main' }
79
+ fattr( 'parent' ){ nil }
80
+ fattr( 'children' ){ Set.new }
81
+
82
+ fattr( 'program' ){ File.basename $0 }
83
+ fattr( 'author' )
84
+ fattr( 'version' )
85
+ fattr( 'stdin' ){ $stdin }
86
+ fattr( 'stdout' ){ $stdout }
87
+ fattr( 'stderr' ){ $stderr }
88
+ fattr( 'logger' ){ stderr }
89
+ fattr( 'logger_level' ){ Logger::INFO }
90
+ fattr( 'exit_status' ){ Main::EXIT_SUCCESS }
91
+ fattr( 'exit_success' ){ Main::EXIT_SUCCESS }
92
+ fattr( 'exit_failure' ){ Main::EXIT_FAILURE }
93
+ fattr( 'exit_warn' ){ Main::EXIT_WARN }
94
+ inheritable_fattr( 'parameters' ){ Main::Parameter::List[] }
95
+ inheritable_fattr( 'can_has_hash' ){ Hash.new }
96
+ inheritable_fattr( 'mixin_table' ){ Hash.new }
97
+
98
+ # override a few fattrs
99
+ def mode_name=(value)
100
+ @mode_name = Mode.new value
101
+ end
102
+
103
+ def usage *argv, &block
104
+ usage! unless defined? @usage
105
+ return @usage if argv.empty? and block.nil?
106
+ key, value, *ignored = argv
107
+ value = block.call if block
108
+ @usage[key.to_s] = value.to_s
109
+ end
110
+
111
+ def create parent = Base, *a, &b
112
+ Class.new parent do |child|
113
+ child.parent = parent unless parent == Base
114
+ parent.children.add child
115
+ child.context do
116
+ child.class_eval &b if b
117
+ child.default_options!
118
+ #child.wrap_run! unless child.const_defined?(:RUN)
119
+ mode_definitions.each do |name, block|
120
+ klass =
121
+ create context do
122
+ mode_name name.to_s
123
+ module_eval &block if block
124
+ end
125
+ modes.add klass
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ def context &block
132
+ @@context ||= []
133
+ unless block
134
+ @@context.last
135
+ else
136
+ begin
137
+ @@context.push self
138
+ block.call @@context.last
139
+ ensure
140
+ @@context.pop
141
+ end
142
+ end
143
+ end
144
+
145
+ module ::Main
146
+ singleton_class{
147
+ def current
148
+ ::Main::Base.context
149
+ end
150
+ }
151
+ end
152
+
153
+ def fully_qualified_mode
154
+ list = []
155
+ ancestors.each do |ancestor|
156
+ break unless ancestor < Base
157
+ list << ancestor.mode_name
158
+ end
159
+ list.reverse[1..-1]
160
+ end
161
+
162
+ def run(&b) define_method(:run, &b) end
163
+
164
+ def new(*a, &b)
165
+ allocate.instance_eval do
166
+ pre_initialize
167
+ before_initialize
168
+ main_initialize *a, &b
169
+ initialize
170
+ after_initialize
171
+ post_initialize
172
+ self
173
+ end
174
+ end
175
+ end
176
+
177
+ module DSL
178
+ def parameter *a, &b
179
+ (parameters << Parameter.create(:parameter, *a, &b)).last
180
+ end
181
+
182
+ def option *a, &b
183
+ (parameters << Parameter.create(:option, *a, &b)).last
184
+ end
185
+ alias_method 'opt', 'option'
186
+ alias_method 'switch', 'option'
187
+
188
+ def default_options!
189
+ option 'help', 'h' unless parameters.has_option?('help', 'h')
190
+ end
191
+
192
+ def argument *a, &b
193
+ (parameters << Parameter.create(:argument, *a, &b)).last
194
+ end
195
+ alias_method 'arg', 'argument'
196
+
197
+ def keyword *a, &b
198
+ (parameters << Parameter.create(:keyword, *a, &b)).last
199
+ end
200
+ alias_method 'kw', 'keyword'
201
+
202
+ def environment *a, &b
203
+ (parameters << Parameter.create(:environment, *a, &b)).last
204
+ end
205
+ alias_method 'env', 'environment'
206
+
207
+ =begin
208
+ def mode name, &b
209
+ klass =
210
+ create context do
211
+ mode_name name.to_s
212
+ module_eval &b if b
213
+ end
214
+ modes.add klass
215
+ end
216
+ =end
217
+
218
+ def mode name, &b
219
+ mode_definitions << [name, b]
220
+ end
221
+
222
+ def can_has ptype, *a, &b
223
+ key = a.map{|s| s.to_s}.sort_by{|s| -s.size }.first
224
+ can_has_hash.update key => [ptype, a, b]
225
+ key
226
+ end
227
+
228
+ def has key, *keys
229
+ keys = [key, *keys].flatten.compact.map{|k| k.to_s}
230
+ keys.map do |key|
231
+ ptype, a, b = can_has_hash[key]
232
+ abort "yo - can *not* has #{ key.inspect }!?" unless(ptype and a and b)
233
+ send ptype, *a, &b
234
+ key
235
+ end
236
+ end
237
+
238
+ def mixin name, *names, &block
239
+ names = [name, *names].flatten.compact.map{|name| name.to_s}
240
+ if block
241
+ names.each do |name|
242
+ mixin_table[name] = block
243
+ end
244
+ else
245
+ names.each do |name|
246
+ module_eval &mixin_table[name]
247
+ end
248
+ end
249
+ end
250
+
251
+ ## TODO - for some reason these hork the usage!
252
+
253
+ %w[ examples samples api ].each do |chunkname|
254
+ module_eval <<-code
255
+ def #{ chunkname } *a, &b
256
+ txt = b ? b.call : a.join("\\n")
257
+ usage['#{ chunkname }'] = txt
258
+ end
259
+ code
260
+ end
261
+ alias_method 'example', 'examples'
262
+ alias_method 'sample', 'samples'
263
+ end
264
+ extend DSL
265
+
266
+ fattr 'argv'
267
+ fattr 'env'
268
+ fattr 'params'
269
+ fattr 'logger'
270
+ fattr 'stdin'
271
+ fattr 'stdout'
272
+ fattr 'stderr'
273
+
274
+ %w(
275
+ program name synopsis description author version
276
+ exit_status exit_success exit_failure exit_warn
277
+ logger_level
278
+ usage
279
+ ).each{|a| fattr(a){ self.class.send a}}
280
+
281
+ %w( parameters param ).each do |dst|
282
+ alias_method "#{ dst }", "params"
283
+ alias_method "#{ dst }=", "params="
284
+ alias_method "#{ dst }?", "params?"
285
+ end
286
+
287
+ %w( debug info warn fatal error ).each do |m|
288
+ module_eval <<-code
289
+ def #{ m } *a, &b
290
+ logger.#{ m } *a, &b
291
+ end
292
+ code
293
+ end
294
+
295
+ =begin
296
+ =end
297
+ def pre_initialize() :hook end
298
+ def before_initialize() :hook end
299
+ def main_initialize argv = ARGV, env = ENV, opts = {}
300
+ @argv, @env, @opts = argv, env, opts
301
+ setup_finalizers
302
+ setup_io_restoration
303
+ setup_io_redirection
304
+ setup_logging
305
+ end
306
+ def initialize() :hook end
307
+ def after_initialize() :hook end
308
+ def post_initialize() :hook end
309
+
310
+ def setup_finalizers
311
+ @finalizers = finalizers = []
312
+ ObjectSpace.define_finalizer(self) do
313
+ while((f = finalizers.pop)); f.call; end
314
+ end
315
+ end
316
+
317
+ def finalize
318
+ while((f = @finalizers.pop)); f.call; end
319
+ end
320
+
321
+ def setup_io_redirection
322
+ self.stdin = @opts['stdin'] || @opts[:stdin] || self.class.stdin
323
+ self.stdout = @opts['stdout'] || @opts[:stdout] || self.class.stdout
324
+ self.stderr = @opts['stderr'] || @opts[:stderr] || self.class.stderr
325
+ end
326
+
327
+ def setup_logging
328
+ log = self.class.logger || stderr
329
+ self.logger = log
330
+ end
331
+ def logger= log
332
+ unless(defined?(@logger) and @logger == log)
333
+ case log
334
+ when ::Logger, Logger
335
+ @logger = log
336
+ when IO, StringIO
337
+ @logger = Logger.new log
338
+ @logger.level = logger_level
339
+ else
340
+ @logger = Logger.new *log
341
+ @logger.level = logger_level
342
+ end
343
+ end
344
+ @logger
345
+ end
346
+
347
+ def setup_io_restoration
348
+ [STDIN, STDOUT, STDERR].each do |io|
349
+ dup = io.dup and @finalizers.push lambda{ io.reopen dup rescue nil }
350
+ end
351
+ end
352
+
353
+ def stdin= io
354
+ unless(defined?(@stdin) and (@stdin == io))
355
+ @stdin =
356
+ if io.respond_to? 'read'
357
+ io
358
+ else
359
+ fd = open io.to_s, 'r+'
360
+ @finalizers.push lambda{ fd.close }
361
+ fd
362
+ end
363
+ begin
364
+ STDIN.reopen @stdin
365
+ rescue
366
+ $stdin = @stdin
367
+ ::Object.const_set 'STDIN', @stdin
368
+ end
369
+ end
370
+ end
371
+
372
+ def stdout= io
373
+ unless(defined?(@stdout) and (@stdout == io))
374
+ @stdout =
375
+ if io.respond_to? 'write'
376
+ io
377
+ else
378
+ fd = open io.to_s, 'w+'
379
+ @finalizers.push lambda{ fd.close }
380
+ fd
381
+ end
382
+ STDOUT.reopen @stdout rescue($stdout = @stdout)
383
+ end
384
+ end
385
+
386
+ def stderr= io
387
+ unless(defined?(@stderr) and (@stderr == io))
388
+ @stderr =
389
+ if io.respond_to? 'write'
390
+ io
391
+ else
392
+ fd = open io.to_s, 'w+'
393
+ @finalizers.push lambda{ fd.close }
394
+ fd
395
+ end
396
+ STDERR.reopen @stderr rescue($stderr = @stderr)
397
+ end
398
+ end
399
+
400
+ def pre_parse_parameters() :hook end
401
+ def before_parse_parameters() :hook end
402
+ def parse_parameters
403
+ pre_parse_parameters
404
+
405
+ self.class.parameters.parse self
406
+ @params = Parameter::Table.new
407
+ self.class.parameters.each{|p| @params[p.name.to_s] = p}
408
+
409
+ post_parse_parameters
410
+ end
411
+ def after_parse_parameters() :hook end
412
+ def post_parse_parameters() :hook end
413
+
414
+ def pre_run() :hook end
415
+ def before_run() :hook end
416
+ def run
417
+ raise NotImplementedError, 'run not defined'
418
+ end
419
+ def after_run() :hook end
420
+ def post_run() :hook end
421
+
422
+ def mode_given?
423
+ begin
424
+ modes.size > 0 and
425
+ argv.size > 0 and
426
+ modes.find_by_mode(argv.first)
427
+ rescue Mode::Ambiguous
428
+ true
429
+ end
430
+ end
431
+ def mode_run!
432
+ mode = modes.find_by_mode argv.shift
433
+ klass = modes[mode] or abort "bad mode <#{ mode }>"
434
+ main = klass.new @argv, @env, @opts
435
+ main.mode = mode
436
+ main.run
437
+ end
438
+ def modes
439
+ self.class.modes
440
+ end
441
+ fattr 'mode'
442
+
443
+ def help! status = 0
444
+ print usage.to_s
445
+ exit(status)
446
+ end
447
+
448
+ def abort message = 'exit'
449
+ raise SystemExit.new(message)
450
+ end
451
+
452
+ def handle_exception e
453
+ if e.respond_to?(:error_handler_before)
454
+ fcall(e, :error_handler_before, self)
455
+ end
456
+
457
+ if e.respond_to?(:error_handler_instead)
458
+ fcall(e, :error_handler_instead, self)
459
+ else
460
+ if e.respond_to? :status
461
+ exit_status(( e.status ))
462
+ end
463
+
464
+ if Softspoken === e or SystemExit === e
465
+ quiet = ((SystemExit === e and e.message.respond_to?('abort')) or # see main/stdext.rb
466
+ (SystemExit === e and e.message == 'exit'))
467
+ stderr.puts e.message unless quiet
468
+ else
469
+ fatal{ e }
470
+ end
471
+ end
472
+
473
+ if e.respond_to?(:error_handler_after)
474
+ fcall(e, :error_handler_after, self)
475
+ end
476
+
477
+ exit_status(( exit_failure )) if exit_status == exit_success
478
+ exit_status(( Integer(exit_status) rescue(exit_status ? 0 : 1) ))
479
+ exit exit_status
480
+ end
481
+
482
+ def fcall obj, m, *argv, &block
483
+ m = obj.method m
484
+ arity = m.arity
485
+ if arity >= 0
486
+ argv = argv[0, arity]
487
+ else
488
+ arity = arity.abs - 1
489
+ argv = argv[0, arity] + argv[arity .. -1]
490
+ end
491
+ m.call *argv, &block
492
+ end
493
+
494
+ def handle_throw status
495
+ exit(( Integer(status) rescue 0 ))
496
+ end
497
+
498
+ %w[ before instead after ].each do |which|
499
+ module_eval <<-code
500
+ def error_handler_#{ which } *argv, &block
501
+ block.call *argv
502
+ end
503
+ code
504
+ end
505
+
506
+ def instance_eval_block *argv, &block
507
+ sc =
508
+ class << self
509
+ self
510
+ end
511
+ sc.module_eval{ define_method '__instance_eval_block', &block }
512
+ fcall self, '__instance_eval_block', *argv, &block
513
+ end
514
+ end
515
+ end