main 2.8.4 → 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 +60 -77
- data/README.erb +784 -0
- data/Rakefile +241 -0
- data/lib/main.rb +1 -1
- data/lib/main/base.rb +5 -5
- data/lib/main/parameter.rb +1 -1
- data/lib/main/usage.rb +0 -3
- data/main.gemspec +28 -0
- data/test/main.rb +11 -0
- metadata +9 -28
- data/gemspec.rb +0 -36
- data/gen_readme.rb +0 -42
- data/install.rb +0 -214
data/README
CHANGED
@@ -206,20 +206,6 @@ SAMPLES
|
|
206
206
|
true
|
207
207
|
42
|
208
208
|
|
209
|
-
~ > ruby samples/a.rb --help
|
210
|
-
|
211
|
-
NAME
|
212
|
-
a.rb
|
213
|
-
|
214
|
-
SYNOPSIS
|
215
|
-
a.rb foo [options]+
|
216
|
-
|
217
|
-
PARAMETERS
|
218
|
-
foo (1 -> int(foo))
|
219
|
-
the foo param
|
220
|
-
--help, -h
|
221
|
-
|
222
|
-
|
223
209
|
|
224
210
|
<========< samples/b.rb >========>
|
225
211
|
|
@@ -248,20 +234,6 @@ SAMPLES
|
|
248
234
|
true
|
249
235
|
[40, 1, 1]
|
250
236
|
|
251
|
-
~ > ruby samples/b.rb --help
|
252
|
-
|
253
|
-
NAME
|
254
|
-
b.rb
|
255
|
-
|
256
|
-
SYNOPSIS
|
257
|
-
b.rb foo foo foo [options]+
|
258
|
-
|
259
|
-
PARAMETERS
|
260
|
-
foo (3 -> int(foo))
|
261
|
-
the foo param
|
262
|
-
--help, -h
|
263
|
-
|
264
|
-
|
265
237
|
|
266
238
|
<========< samples/c.rb >========>
|
267
239
|
|
@@ -296,20 +268,6 @@ SAMPLES
|
|
296
268
|
true
|
297
269
|
false
|
298
270
|
|
299
|
-
~ > ruby samples/c.rb --help
|
300
|
-
|
301
|
-
NAME
|
302
|
-
c.rb
|
303
|
-
|
304
|
-
SYNOPSIS
|
305
|
-
c.rb foo=foo [bar=bar] [options]+
|
306
|
-
|
307
|
-
PARAMETERS
|
308
|
-
foo=foo (2 -> float(foo))
|
309
|
-
bar=bar (1 ~> bool(bar))
|
310
|
-
--help, -h
|
311
|
-
|
312
|
-
|
313
271
|
|
314
272
|
<========< samples/d.rb >========>
|
315
273
|
|
@@ -349,19 +307,70 @@ SAMPLES
|
|
349
307
|
nil
|
350
308
|
false
|
351
309
|
|
352
|
-
~ > ruby samples/d.rb --help
|
353
310
|
|
354
|
-
|
355
|
-
|
311
|
+
<========< samples/e.rb >========>
|
312
|
+
|
313
|
+
~ > cat samples/e.rb
|
314
|
+
|
315
|
+
require 'main'
|
356
316
|
|
357
|
-
|
358
|
-
|
317
|
+
Main {
|
318
|
+
argument 'global-argument'
|
319
|
+
option 'global-option'
|
359
320
|
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
321
|
+
def run() puts 'global-run' end
|
322
|
+
|
323
|
+
mode 'a' do
|
324
|
+
option 'a-option'
|
325
|
+
end
|
326
|
+
|
327
|
+
mode 'b' do
|
328
|
+
option 'b-option'
|
329
|
+
|
330
|
+
def run() puts 'b-run' end
|
331
|
+
end
|
332
|
+
}
|
333
|
+
|
334
|
+
~ > ruby samples/e.rb
|
335
|
+
|
336
|
+
argument(global-argument)) 0/1
|
337
|
+
|
338
|
+
|
339
|
+
<========< samples/f.rb >========>
|
340
|
+
|
341
|
+
~ > cat samples/f.rb
|
342
|
+
|
343
|
+
require 'main'
|
344
|
+
|
345
|
+
Main {
|
346
|
+
argument('directory'){ description 'the directory to operate on' }
|
347
|
+
|
348
|
+
option('force'){ description 'use a bigger hammer' }
|
349
|
+
|
350
|
+
def run
|
351
|
+
puts 'this is how we run when no mode is specified'
|
352
|
+
end
|
353
|
+
|
354
|
+
mode 'compress' do
|
355
|
+
option('bzip'){ description 'use bzip compression' }
|
356
|
+
|
357
|
+
def run
|
358
|
+
puts 'this is how we run in compress mode'
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
mode 'uncompress' do
|
363
|
+
option('delete-after'){ description 'delete orginal file after uncompressing' }
|
364
364
|
|
365
|
+
def run
|
366
|
+
puts 'this is how we run in un-compress mode'
|
367
|
+
end
|
368
|
+
end
|
369
|
+
}
|
370
|
+
|
371
|
+
~ > ruby samples/f.rb
|
372
|
+
|
373
|
+
argument(directory)) 0/1
|
365
374
|
|
366
375
|
|
367
376
|
<========< samples/g.rb >========>
|
@@ -383,20 +392,6 @@ SAMPLES
|
|
383
392
|
|
384
393
|
This is what to_options produces: {"help"=>nil, "foo"=>"42", "bar"=>nil}
|
385
394
|
|
386
|
-
~ > ruby samples/g.rb --help
|
387
|
-
|
388
|
-
NAME
|
389
|
-
g.rb
|
390
|
-
|
391
|
-
SYNOPSIS
|
392
|
-
g.rb foo [options]+
|
393
|
-
|
394
|
-
PARAMETERS
|
395
|
-
foo (1 -> foo)
|
396
|
-
--bar
|
397
|
-
--help, -h
|
398
|
-
|
399
|
-
|
400
395
|
|
401
396
|
<========< samples/h.rb >========>
|
402
397
|
|
@@ -443,18 +438,6 @@ SAMPLES
|
|
443
438
|
|
444
439
|
"forty-two"
|
445
440
|
|
446
|
-
~ > ruby samples/h.rb --help
|
447
|
-
|
448
|
-
NAME
|
449
|
-
h.rb
|
450
|
-
|
451
|
-
SYNOPSIS
|
452
|
-
h.rb (a|b) [options]+
|
453
|
-
|
454
|
-
PARAMETERS
|
455
|
-
--foobar
|
456
|
-
--help, -h
|
457
|
-
|
458
441
|
|
459
442
|
|
460
443
|
DOCS
|
data/README.erb
ADDED
@@ -0,0 +1,784 @@
|
|
1
|
+
NAME
|
2
|
+
main.rb
|
3
|
+
|
4
|
+
SYNOPSIS
|
5
|
+
a class factory and dsl for generating command line programs real quick
|
6
|
+
|
7
|
+
URI
|
8
|
+
http://codeforpeople.com/lib/ruby/
|
9
|
+
http://rubyforge.org/projects/codeforpeople/
|
10
|
+
http://codeforpeople.rubyforge.org/svn/
|
11
|
+
|
12
|
+
INSTALL
|
13
|
+
gem install main
|
14
|
+
|
15
|
+
DESCRIPTION
|
16
|
+
main.rb features the following:
|
17
|
+
|
18
|
+
- unification of option, argument, keyword, and environment parameter
|
19
|
+
parsing
|
20
|
+
- auto generation of usage and help messages
|
21
|
+
- support for mode/sub-commands
|
22
|
+
- io redirection support
|
23
|
+
- logging hooks using ruby's built-in logging mechanism
|
24
|
+
- intelligent error handling and exit codes
|
25
|
+
- use as dsl or library for building Main objects
|
26
|
+
- parsing user defined ARGV and ENV
|
27
|
+
- zero requirements for understanding the obtuse apis of *any* command
|
28
|
+
line option parsers
|
29
|
+
- leather pants
|
30
|
+
|
31
|
+
in short main.rb aims to drastically lower the barrier to writing uniform
|
32
|
+
command line applications.
|
33
|
+
|
34
|
+
for instance, this program
|
35
|
+
|
36
|
+
require 'main'
|
37
|
+
|
38
|
+
Main {
|
39
|
+
argument 'foo'
|
40
|
+
option 'bar'
|
41
|
+
|
42
|
+
def run
|
43
|
+
p params['foo']
|
44
|
+
p params['bar']
|
45
|
+
exit_success!
|
46
|
+
end
|
47
|
+
}
|
48
|
+
|
49
|
+
sets up a program which requires one argument, 'bar', and which may accept one
|
50
|
+
command line switch, '--foo' in addition to the single option/mode which is always
|
51
|
+
accepted and handled appropriately: 'help', '--help', '-h'. for the most
|
52
|
+
part main.rb stays out of your command line namespace but insists that your
|
53
|
+
application has at least a help mode/option.
|
54
|
+
|
55
|
+
main.rb supports sub-commands in a very simple way
|
56
|
+
|
57
|
+
require 'main'
|
58
|
+
|
59
|
+
Main {
|
60
|
+
mode 'install' do
|
61
|
+
def run() puts 'installing...' end
|
62
|
+
end
|
63
|
+
|
64
|
+
mode 'uninstall' do
|
65
|
+
def run() puts 'uninstalling...' end
|
66
|
+
end
|
67
|
+
}
|
68
|
+
|
69
|
+
which allows a program, called 'a.rb', to be invoked as
|
70
|
+
|
71
|
+
ruby a.rb install
|
72
|
+
|
73
|
+
and
|
74
|
+
|
75
|
+
ruby a.rb uninstall
|
76
|
+
|
77
|
+
for simple programs main.rb is a real time saver but it's for more complex
|
78
|
+
applications where main.rb's unification of parameter parsing, class
|
79
|
+
configuration dsl, and auto-generation of usage messages can really streamline
|
80
|
+
command line application development. for example the following 'a.rb'
|
81
|
+
program:
|
82
|
+
|
83
|
+
require 'main'
|
84
|
+
|
85
|
+
Main {
|
86
|
+
argument('foo'){
|
87
|
+
cast :int
|
88
|
+
}
|
89
|
+
keyword('bar'){
|
90
|
+
arity 2
|
91
|
+
cast :float
|
92
|
+
defaults 0.0, 1.0
|
93
|
+
}
|
94
|
+
option('foobar'){
|
95
|
+
argument :optional
|
96
|
+
description 'the foobar option is very handy'
|
97
|
+
}
|
98
|
+
environment('BARFOO'){
|
99
|
+
cast :list_of_bool
|
100
|
+
synopsis 'export barfoo=value'
|
101
|
+
}
|
102
|
+
|
103
|
+
def run
|
104
|
+
p params['foo'].value
|
105
|
+
p params['bar'].values
|
106
|
+
p params['foobar'].value
|
107
|
+
p params['BARFOO'].value
|
108
|
+
end
|
109
|
+
}
|
110
|
+
|
111
|
+
when run with a command line of
|
112
|
+
|
113
|
+
BARFOO=true,false,false ruby a.rb 42 bar=40 bar=2 --foobar=a
|
114
|
+
|
115
|
+
will produce
|
116
|
+
|
117
|
+
42
|
118
|
+
[40.0, 2.0]
|
119
|
+
"a"
|
120
|
+
[true, false, false]
|
121
|
+
|
122
|
+
while a command line of
|
123
|
+
|
124
|
+
ruby a.rb --help
|
125
|
+
|
126
|
+
will produce
|
127
|
+
|
128
|
+
NAME
|
129
|
+
a.rb
|
130
|
+
|
131
|
+
SYNOPSIS
|
132
|
+
a.rb foo [bar=bar] [options]+
|
133
|
+
|
134
|
+
PARAMETERS
|
135
|
+
* foo [ 1 -> int(foo) ]
|
136
|
+
|
137
|
+
* bar=bar [ 2 ~> float(bar=0.0,1.0) ]
|
138
|
+
|
139
|
+
* --foobar=[foobar] [ 1 ~> foobar ]
|
140
|
+
the foobar option is very handy
|
141
|
+
|
142
|
+
* --help, -h
|
143
|
+
|
144
|
+
* export barfoo=value
|
145
|
+
|
146
|
+
and this shows how all of argument, keyword, option, and environment parsing
|
147
|
+
can be declartively dealt with in a unified fashion - the dsl for all
|
148
|
+
parameter types is the same - and how auto synopsis and usage generation saves
|
149
|
+
keystrokes. the parameter synopsis is compact and can be read as
|
150
|
+
|
151
|
+
* foo [ 1 -> int(foo) ]
|
152
|
+
|
153
|
+
'one argument will get processed via int(argument_name)'
|
154
|
+
|
155
|
+
1 : one argument
|
156
|
+
-> : will get processed (the argument is required)
|
157
|
+
int(foo) : the cast is int, the arg name is foo
|
158
|
+
|
159
|
+
* bar=bar [ 2 ~> float(bar=0.0,1.0) ]
|
160
|
+
|
161
|
+
'two keyword arguments might be processed via float(bar=0.0,1.0)'
|
162
|
+
|
163
|
+
2 : two arguments
|
164
|
+
~> : might be processed (the argument is optional)
|
165
|
+
float(bar=0.0,1.0) : the cast will be float, the default values are
|
166
|
+
0.0 and 1.0
|
167
|
+
|
168
|
+
* --foobar=[foobar] [ 1 ~> foobar ]
|
169
|
+
|
170
|
+
'one option with optional argument may be given directly'
|
171
|
+
|
172
|
+
* --help, -h
|
173
|
+
|
174
|
+
no synopsis, simple switch takes no args and is not required
|
175
|
+
|
176
|
+
* export barfoo=value
|
177
|
+
|
178
|
+
a user defined synopsis
|
179
|
+
|
180
|
+
SAMPLES
|
181
|
+
<%= samples %>
|
182
|
+
|
183
|
+
DOCS
|
184
|
+
test/main.rb
|
185
|
+
|
186
|
+
vim -p lib/main.rb lib/main/*rb
|
187
|
+
|
188
|
+
API section below
|
189
|
+
|
190
|
+
HISTORY
|
191
|
+
2.8.3
|
192
|
+
- support for block defaults
|
193
|
+
|
194
|
+
|
195
|
+
2.8.2
|
196
|
+
- fixes and tests for negative arity/attr arguments, options, eg
|
197
|
+
|
198
|
+
argument(:foo){
|
199
|
+
arity -1
|
200
|
+
}
|
201
|
+
|
202
|
+
def run # ARGV == %w( a b c )
|
203
|
+
p foo #=> %w( a b c )
|
204
|
+
end
|
205
|
+
|
206
|
+
thanks nathan
|
207
|
+
|
208
|
+
2.8.1
|
209
|
+
- move from attributes.rb to fattr.rb
|
210
|
+
|
211
|
+
2.8.0
|
212
|
+
- added 'to_options' method for Parameter::Table. this allows you to convert
|
213
|
+
all the parameters to a simple hash.
|
214
|
+
for example
|
215
|
+
|
216
|
+
Main {
|
217
|
+
option 'foo'
|
218
|
+
argument 'baz'
|
219
|
+
|
220
|
+
run { puts params.to_options.inspect }
|
221
|
+
|
222
|
+
}
|
223
|
+
|
224
|
+
2.7.0
|
225
|
+
- removed bundled arrayfields and attributes. these are now dependancies
|
226
|
+
mananged by rubygems. a.k.a. you must have rubygems installed for main
|
227
|
+
to work.
|
228
|
+
|
229
|
+
2.6.0
|
230
|
+
- added 'mixin' feaature for storing, and later evaluating a block of
|
231
|
+
code. the purpose of this is for use with modes where you want to keep
|
232
|
+
your code dry, but may not want to define something in the base class
|
233
|
+
for all to inherit. 'mixin' allows you to define the code to inherit
|
234
|
+
once and the selectively drop it in child classes (modes) on demand.
|
235
|
+
for example
|
236
|
+
|
237
|
+
Main {
|
238
|
+
mixin :foobar do
|
239
|
+
option 'foo'
|
240
|
+
option 'bar'
|
241
|
+
end
|
242
|
+
|
243
|
+
mode :install do
|
244
|
+
mixin :foobar
|
245
|
+
end
|
246
|
+
|
247
|
+
mode :uninstall do
|
248
|
+
mixin :foobar
|
249
|
+
end
|
250
|
+
|
251
|
+
mode :clean do
|
252
|
+
end
|
253
|
+
}
|
254
|
+
|
255
|
+
- mode definitions are now deferred to the end of the Main block, so you
|
256
|
+
can do this
|
257
|
+
|
258
|
+
Main {
|
259
|
+
mode 'a' do
|
260
|
+
mixin :foo
|
261
|
+
end
|
262
|
+
|
263
|
+
mode 'b' do
|
264
|
+
mixin :foo
|
265
|
+
end
|
266
|
+
|
267
|
+
def inherited_method
|
268
|
+
42
|
269
|
+
end
|
270
|
+
|
271
|
+
mixin 'foo' do
|
272
|
+
def another_inherited_method
|
273
|
+
'forty-two'
|
274
|
+
end
|
275
|
+
end
|
276
|
+
}
|
277
|
+
|
278
|
+
- added sanity check at end of paramter contruction
|
279
|
+
|
280
|
+
- improved auto usage generation when arity is used with arguments
|
281
|
+
|
282
|
+
- removed 'p' shortcut in paramerter dsl because it collided with
|
283
|
+
Kernel.p. it's now called 'param'. this method is availble *inside* a
|
284
|
+
parameter definition
|
285
|
+
|
286
|
+
option('foo', 'f'){
|
287
|
+
synopsis "arity = #{ param.arity }"
|
288
|
+
}
|
289
|
+
|
290
|
+
- fixed bug where '--' did not signal the end of parameter parsing in a
|
291
|
+
getoptlong compliant way
|
292
|
+
|
293
|
+
- added (before/after)_parse_parameters, (before/after)_initialize, and
|
294
|
+
(before/after)_run hooks
|
295
|
+
|
296
|
+
- fixed bug where adding to usage via
|
297
|
+
|
298
|
+
usage['my_section'] = 'custom message'
|
299
|
+
|
300
|
+
totally horked the default auto generated usage message
|
301
|
+
|
302
|
+
- updated dependancies in gemspec.rb for attributes (~> 5.0.0) and
|
303
|
+
arrayfields (~> 4.3.0)
|
304
|
+
|
305
|
+
- check that client code defined run, iff not wrap_run! is called. this is
|
306
|
+
so mains with a mode, but no run defined, still function correctly when
|
307
|
+
passed a mode
|
308
|
+
|
309
|
+
- added new shortcut for creating accessors for parameters. for example
|
310
|
+
|
311
|
+
option('foo'){
|
312
|
+
argument :required
|
313
|
+
cast :int
|
314
|
+
attr
|
315
|
+
}
|
316
|
+
|
317
|
+
def run
|
318
|
+
p foo ### this attr will return the parameter's *value*
|
319
|
+
end
|
320
|
+
|
321
|
+
a block can be passed to specify how to extract the value from the
|
322
|
+
parameter
|
323
|
+
|
324
|
+
argument('foo'){
|
325
|
+
optional
|
326
|
+
default 21
|
327
|
+
cast :int
|
328
|
+
attr{|param| param.value * 2}
|
329
|
+
}
|
330
|
+
|
331
|
+
def run
|
332
|
+
p foo #=> 42
|
333
|
+
end
|
334
|
+
|
335
|
+
- fixed bug where 'abort("message")' would print "message" twice on exit
|
336
|
+
if running under a nested mode (yes again - the fix in 2.4.0 wasn't
|
337
|
+
complete)
|
338
|
+
|
339
|
+
- added a time cast, which uses Time.parse
|
340
|
+
|
341
|
+
argument('login_time'){ cast :time }
|
342
|
+
|
343
|
+
- added a date cast, which uses Date.parse
|
344
|
+
|
345
|
+
argument('login_date'){ cast :date }
|
346
|
+
|
347
|
+
|
348
|
+
2.5.0
|
349
|
+
- added 'examples', 'samples', and 'api' kewords to main dsl. each
|
350
|
+
keyword takes a list of strings which will be included in the help
|
351
|
+
message
|
352
|
+
|
353
|
+
Main {
|
354
|
+
examples "foobar example", "barfoo example"
|
355
|
+
|
356
|
+
samples <<-txt
|
357
|
+
do this
|
358
|
+
|
359
|
+
don't do that
|
360
|
+
txt
|
361
|
+
|
362
|
+
api %(
|
363
|
+
foobar string, hash
|
364
|
+
|
365
|
+
barfoo hash, string
|
366
|
+
)
|
367
|
+
}
|
368
|
+
|
369
|
+
results in a usage message with sections like
|
370
|
+
|
371
|
+
...
|
372
|
+
|
373
|
+
EXAMPLES
|
374
|
+
foobar example
|
375
|
+
barfoo example
|
376
|
+
|
377
|
+
SAMPLES
|
378
|
+
do this
|
379
|
+
|
380
|
+
don't do that
|
381
|
+
|
382
|
+
API
|
383
|
+
foobar string, hash
|
384
|
+
|
385
|
+
barfoo hash, string
|
386
|
+
|
387
|
+
...
|
388
|
+
|
389
|
+
2.4.0
|
390
|
+
- fixed bug where 'abort("message")' would print "message" twice on exit
|
391
|
+
if running under a nested mode.
|
392
|
+
|
393
|
+
- allowed parameters to be overridden completely in subclasses (modes)
|
394
|
+
|
395
|
+
2.3.0
|
396
|
+
- re-worked Main.new such that client code may define an #initialize
|
397
|
+
methods and the class will continue to work. that is to say it's fine
|
398
|
+
to do this
|
399
|
+
|
400
|
+
Main {
|
401
|
+
def initialize
|
402
|
+
@a = 42
|
403
|
+
end
|
404
|
+
|
405
|
+
def run
|
406
|
+
p @a
|
407
|
+
end
|
408
|
+
|
409
|
+
mode 'foo' do
|
410
|
+
def run
|
411
|
+
p @a
|
412
|
+
end
|
413
|
+
end
|
414
|
+
}
|
415
|
+
|
416
|
+
the client #initialize will be called *after* main has done it's normal
|
417
|
+
initialization so things like @argv, @env, and @stdin will all be there
|
418
|
+
in initialize. of course you could have done this before but you'd have
|
419
|
+
to both call super and call it with the correct arguments - now you can
|
420
|
+
simply ignore it.
|
421
|
+
|
422
|
+
2.2.0
|
423
|
+
- added ability for parameter dsl error handlers to accept an argument,
|
424
|
+
this will be passed the current error. for example
|
425
|
+
|
426
|
+
argument(:x) do
|
427
|
+
arity 42
|
428
|
+
|
429
|
+
error do |e|
|
430
|
+
case e
|
431
|
+
when Parameter::Arity
|
432
|
+
...
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
- refined the mode parsing a bit: modes can now be abbreviated to uniqness
|
437
|
+
and, when the mode is ambiuous, a nice error message is printed, for
|
438
|
+
example:
|
439
|
+
|
440
|
+
ambiguous mode: in = (inflate or install)?
|
441
|
+
|
442
|
+
2.1.0
|
443
|
+
- added custom error handling dsl for parameters, this includes the ability
|
444
|
+
to prepend, append, or replace the standard error handlers:
|
445
|
+
|
446
|
+
require 'main'
|
447
|
+
|
448
|
+
Main {
|
449
|
+
argument 'x' do
|
450
|
+
error :before do
|
451
|
+
puts 'this fires *before* normal error handling using #instance_eval...'
|
452
|
+
end
|
453
|
+
|
454
|
+
error do
|
455
|
+
puts 'this fires *instead of* normal error handling using #instance_eval...'
|
456
|
+
end
|
457
|
+
|
458
|
+
error :after do
|
459
|
+
puts 'this fires *after* normal error handling using #instance_eval...'
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
run(){ p param['x'].given? }
|
464
|
+
}
|
465
|
+
|
466
|
+
- added ability to exit at any time bypassing *all* error handling using
|
467
|
+
'throw :exit, 42' where 42 is the desired exit status. throw without a
|
468
|
+
status simply exits with 0.
|
469
|
+
|
470
|
+
- added 'help!' method which simply dumps out usage and exits
|
471
|
+
|
472
|
+
2.0.0
|
473
|
+
- removed need for proxy.rb via Main::Base.wrap_run!
|
474
|
+
- added error handling hooks for parameter parsing
|
475
|
+
- bundled arrayfields, attributes, and pervasives although gems are tried
|
476
|
+
first
|
477
|
+
- softened error messages for parameter parsing errors: certain classes of
|
478
|
+
errors are now 'softspoken' and print only the message, not the entire
|
479
|
+
stacktrace, to stderr. much nicer for users. this is configurable.
|
480
|
+
- added subcommand/mode support
|
481
|
+
- added support for user defined exception handling on top level
|
482
|
+
exceptions/exits
|
483
|
+
- added support for negative arity. this users ruby's own arity
|
484
|
+
semantics, for example:
|
485
|
+
|
486
|
+
lambda{|*a|}.arity == -1
|
487
|
+
lambda{|a,*b|}.arity == -2
|
488
|
+
lambda{|a,b,*c|}.arity == -3
|
489
|
+
...
|
490
|
+
|
491
|
+
in otherwords parameters now support 'zero or more', 'one or more' ...
|
492
|
+
'n or more' argument semantics
|
493
|
+
|
494
|
+
1.0.0
|
495
|
+
- some improved usage messages from jeremy hinegardner
|
496
|
+
|
497
|
+
0.0.2
|
498
|
+
- removed dependancy on attributes/arrayfields. main now has zero gem
|
499
|
+
dependancies.
|
500
|
+
|
501
|
+
- added support for io redirection. redirection of stdin, stdout, and
|
502
|
+
stderr can be done to any io like object or object that can be
|
503
|
+
inerpreted as a pathname (object.to_s)
|
504
|
+
|
505
|
+
- main objects can now easily be created and run on demand, which makes
|
506
|
+
testing a breeze
|
507
|
+
|
508
|
+
def test_unit_goodness!
|
509
|
+
main =
|
510
|
+
Main.new{
|
511
|
+
stdout StringIO.new
|
512
|
+
stderr '/dev/null'
|
513
|
+
|
514
|
+
def run
|
515
|
+
puts 42
|
516
|
+
end
|
517
|
+
}
|
518
|
+
|
519
|
+
main.run
|
520
|
+
main.stdout.rewind
|
521
|
+
|
522
|
+
assert main.stdout.read == "42\n"
|
523
|
+
end
|
524
|
+
|
525
|
+
- added API section to readme and called it 'docs'
|
526
|
+
|
527
|
+
- wrote a bunch more tests. there are now 42 of them.
|
528
|
+
|
529
|
+
0.0.1
|
530
|
+
|
531
|
+
initial version. this version extracts much of the functionality of alib's
|
532
|
+
(gen install alib) Alib.script main program generator and also some of jim's
|
533
|
+
freeze's excellent CommandLine::Aplication into what i hope is a simpler and
|
534
|
+
more unified interface
|
535
|
+
|
536
|
+
API
|
537
|
+
|
538
|
+
Main {
|
539
|
+
|
540
|
+
###########################################################################
|
541
|
+
# CLASS LEVEL API #
|
542
|
+
###########################################################################
|
543
|
+
#
|
544
|
+
# the name of the program, auto-set and used in usage
|
545
|
+
#
|
546
|
+
program 'foo.rb'
|
547
|
+
#
|
548
|
+
# a short description of program functionality, auto-set and used in usage
|
549
|
+
#
|
550
|
+
synopsis "foo.rb arg [options]+"
|
551
|
+
#
|
552
|
+
# long description of program functionality, used in usage iff set
|
553
|
+
#
|
554
|
+
description <<-hdoc
|
555
|
+
this text will automatically be indented to the right level.
|
556
|
+
|
557
|
+
it should describe how the program works in detail
|
558
|
+
hdoc
|
559
|
+
#
|
560
|
+
# used in usage iff set
|
561
|
+
#
|
562
|
+
author 'ara.t.howard@gmail.com'
|
563
|
+
#
|
564
|
+
# used in usage
|
565
|
+
#
|
566
|
+
version '0.0.42'
|
567
|
+
#
|
568
|
+
# stdin/out/err can be anthing which responds to read/write or a string
|
569
|
+
# which will be opened as in the appropriate mode
|
570
|
+
#
|
571
|
+
stdin '/dev/null'
|
572
|
+
stdout '/dev/null'
|
573
|
+
stderr open('/dev/null', 'w')
|
574
|
+
#
|
575
|
+
# the logger should be a Logger object, something 'write'-able, or a string
|
576
|
+
# which will be used to open the logger. the logger_level specifies the
|
577
|
+
# initalize verbosity setting, the default is Logger::INFO
|
578
|
+
#
|
579
|
+
logger(( program + '.log' ))
|
580
|
+
logger_level Logger::DEBUG
|
581
|
+
#
|
582
|
+
# you can configure exit codes. the defaults are shown
|
583
|
+
#
|
584
|
+
exit_success # 0
|
585
|
+
exit_failure # 1
|
586
|
+
exit_warn # 42
|
587
|
+
#
|
588
|
+
# the usage object is rather complex. by default it's an object which can
|
589
|
+
# be built up in sections using the
|
590
|
+
#
|
591
|
+
# usage["BUGS"] = "something about bugs'
|
592
|
+
#
|
593
|
+
# syntax to append sections onto the already pre-built usage message which
|
594
|
+
# contains program, synopsis, parameter descriptions and the like
|
595
|
+
#
|
596
|
+
# however, you always replace the usage object wholesale with one of your
|
597
|
+
# chosing like so
|
598
|
+
#
|
599
|
+
usage <<-txt
|
600
|
+
my own usage message
|
601
|
+
txt
|
602
|
+
|
603
|
+
###########################################################################
|
604
|
+
# MODE API #
|
605
|
+
###########################################################################
|
606
|
+
#
|
607
|
+
# modes are class factories that inherit from their parent class. they can
|
608
|
+
# be nested *arbitrarily* deep. usage messages are tailored for each mode.
|
609
|
+
# modes are, for the most part, independant classes but parameters are
|
610
|
+
# always a superset of the parent class - a mode accepts all of it's parents
|
611
|
+
# paramters *plus* and additional ones
|
612
|
+
#
|
613
|
+
option 'inherited-option'
|
614
|
+
argument 'inherited-argument'
|
615
|
+
|
616
|
+
mode 'install' do
|
617
|
+
option 'force' do
|
618
|
+
description 'clobber existing installation'
|
619
|
+
end
|
620
|
+
|
621
|
+
def run
|
622
|
+
inherited_method()
|
623
|
+
puts 'installing...'
|
624
|
+
end
|
625
|
+
|
626
|
+
mode 'docs' do
|
627
|
+
description 'installs the docs'
|
628
|
+
|
629
|
+
def run
|
630
|
+
puts 'installing docs...'
|
631
|
+
end
|
632
|
+
end
|
633
|
+
end
|
634
|
+
|
635
|
+
mode 'un-install' do
|
636
|
+
option 'force' do
|
637
|
+
description 'remove even if dependancies exist'
|
638
|
+
end
|
639
|
+
|
640
|
+
def run
|
641
|
+
inherited_method()
|
642
|
+
puts 'un-installing...'
|
643
|
+
end
|
644
|
+
end
|
645
|
+
|
646
|
+
def run
|
647
|
+
puts 'no mode yo?'
|
648
|
+
end
|
649
|
+
|
650
|
+
def inherited_method
|
651
|
+
puts 'superclass_method...'
|
652
|
+
end
|
653
|
+
|
654
|
+
|
655
|
+
###########################################################################
|
656
|
+
# PARAMETER API #
|
657
|
+
###########################################################################
|
658
|
+
#
|
659
|
+
# all the parameter types of argument|keyword|option|environment share this
|
660
|
+
# api. you must specify the type when the parameter method is used.
|
661
|
+
# alternatively used one of the shortcut methods
|
662
|
+
# argument|keyword|option|environment. in otherwords
|
663
|
+
#
|
664
|
+
# parameter('foo'){ type :option }
|
665
|
+
#
|
666
|
+
# is synonymous with
|
667
|
+
#
|
668
|
+
# option('foo'){ }
|
669
|
+
#
|
670
|
+
option 'foo' {
|
671
|
+
#
|
672
|
+
# required - whether this paramter must by supplied on the command line.
|
673
|
+
# note that you can create 'required' options with this keyword
|
674
|
+
#
|
675
|
+
required # or required true
|
676
|
+
#
|
677
|
+
# argument_required - applies only to options.
|
678
|
+
#
|
679
|
+
argument_required # argument :required
|
680
|
+
#
|
681
|
+
# argument_optional - applies only to options.
|
682
|
+
#
|
683
|
+
argument_optional # argument :optional
|
684
|
+
#
|
685
|
+
# cast - should be either a lambda taking one argument, or a symbol
|
686
|
+
# designation one of the built in casts defined in Main::Cast. supported
|
687
|
+
# types are :boolean|:integer|:float|:numeric|:string|:uri. built-in
|
688
|
+
# casts can be abbreviated
|
689
|
+
#
|
690
|
+
cast :int
|
691
|
+
#
|
692
|
+
# validate - should be a lambda taking one argument and returning
|
693
|
+
# true|false
|
694
|
+
#
|
695
|
+
validate{|int| int == 42}
|
696
|
+
#
|
697
|
+
# synopsis - should be a concise characterization of the paramter. a
|
698
|
+
# default synopsis is built automatically from the parameter. this
|
699
|
+
# information is displayed in the usage message
|
700
|
+
#
|
701
|
+
synopsis '--foo'
|
702
|
+
#
|
703
|
+
# description - a longer description of the paramter. it appears in the
|
704
|
+
# usage also.
|
705
|
+
#
|
706
|
+
description 'a long description of foo'
|
707
|
+
#
|
708
|
+
# arity - indicates how many times the parameter should appear on the
|
709
|
+
# command line. the default is one. negative arities are supported and
|
710
|
+
# follow the same rules as ruby methods/procs.
|
711
|
+
#
|
712
|
+
arity 2
|
713
|
+
#
|
714
|
+
# default - you can provide a default value in case none is given. the
|
715
|
+
# alias 'defaults' reads a bit nicer when you are giving a list of
|
716
|
+
# defaults for paramters of > 1 arity
|
717
|
+
#
|
718
|
+
defaults 40, 2
|
719
|
+
#
|
720
|
+
# you can add custom per-parameter error handlers using the following
|
721
|
+
#
|
722
|
+
error :before do
|
723
|
+
puts 'this fires *before* normal error handling using #instance_eval...'
|
724
|
+
end
|
725
|
+
|
726
|
+
error do
|
727
|
+
puts 'this fires *instead of* normal error handling using #instance_eval...'
|
728
|
+
end
|
729
|
+
|
730
|
+
error :after do
|
731
|
+
puts 'this fires *after* normal error handling using #instance_eval...'
|
732
|
+
end
|
733
|
+
}
|
734
|
+
|
735
|
+
###########################################################################
|
736
|
+
# INSTANCE LEVEL API #
|
737
|
+
###########################################################################
|
738
|
+
#
|
739
|
+
# you must define a run method. it is the only method you must define.
|
740
|
+
#
|
741
|
+
def run
|
742
|
+
#
|
743
|
+
# all parameters are available in the 'params' hash and via the alias
|
744
|
+
# 'param'. it can be indexed via string or symbol. the values are all
|
745
|
+
# Main::Parameter objects
|
746
|
+
#
|
747
|
+
foo = params['foo']
|
748
|
+
#
|
749
|
+
# the given? method indicates whether or not the parameter was given on
|
750
|
+
# the commandline/environment, etc. in particular this will not be true
|
751
|
+
# when a default value was specified but no parameter was given
|
752
|
+
#
|
753
|
+
foo.given?
|
754
|
+
#
|
755
|
+
# the list of all values can be retrieved via 'values'. note that this
|
756
|
+
# is always an array.
|
757
|
+
#
|
758
|
+
p foo.values
|
759
|
+
#
|
760
|
+
# the __first__ value can be retrieved via 'value'. note that this
|
761
|
+
# never an array.
|
762
|
+
#
|
763
|
+
p foo.value
|
764
|
+
#
|
765
|
+
# the methods debug|info|warn|error|fatal are delegated to the logger
|
766
|
+
# object
|
767
|
+
#
|
768
|
+
info{ "this goes to the log" }
|
769
|
+
#
|
770
|
+
# you can set the exit_status at anytime. this status is used when
|
771
|
+
# exiting the program. exceptions cause this to be ext_failure if, and
|
772
|
+
# only if, the current value was exit_success. in otherwords an
|
773
|
+
# un-caught exception always results in a failing exit_status
|
774
|
+
#
|
775
|
+
exit_status exit_failure
|
776
|
+
#
|
777
|
+
# a few shortcuts both set the exit_status and exit the program.
|
778
|
+
#
|
779
|
+
exit_success!
|
780
|
+
exit_failure!
|
781
|
+
exit_warn!
|
782
|
+
end
|
783
|
+
|
784
|
+
}
|