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.
- data/README +1044 -0
- data/README.erb +784 -0
- data/Rakefile +241 -0
- data/TODO +20 -0
- data/lib/main.rb +60 -0
- data/lib/main/base.rb +515 -0
- data/lib/main/cast.rb +100 -0
- data/lib/main/factories.rb +20 -0
- data/lib/main/getoptlong.rb +470 -0
- data/lib/main/logger.rb +51 -0
- data/lib/main/mode.rb +42 -0
- data/lib/main/parameter.rb +699 -0
- data/lib/main/softspoken.rb +12 -0
- data/lib/main/stdext.rb +38 -0
- data/lib/main/usage.rb +208 -0
- data/lib/main/util.rb +91 -0
- data/main.gemspec +28 -0
- data/samples/a.rb +17 -0
- data/samples/b.rb +17 -0
- data/samples/c.rb +21 -0
- data/samples/d.rb +26 -0
- data/samples/e.rb +18 -0
- data/samples/f.rb +27 -0
- data/samples/g.rb +10 -0
- data/samples/h.rb +36 -0
- data/test/main.rb +861 -0
- metadata +83 -0
data/Rakefile
ADDED
|
@@ -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
|
data/lib/main.rb
ADDED
|
@@ -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
|
data/lib/main/base.rb
ADDED
|
@@ -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
|