ahoward-main 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,1044 @@
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
+
182
+ <========< samples/a.rb >========>
183
+
184
+ ~ > cat samples/a.rb
185
+
186
+ require 'main'
187
+
188
+ ARGV.replace %w( 42 ) if ARGV.empty?
189
+
190
+ Main {
191
+ argument('foo'){
192
+ required # this is the default
193
+ cast :int # value cast to Fixnum
194
+ validate{|foo| foo == 42} # raises error in failure case
195
+ description 'the foo param' # shown in --help
196
+ }
197
+
198
+ def run
199
+ p params['foo'].given?
200
+ p params['foo'].value
201
+ end
202
+ }
203
+
204
+ ~ > ruby samples/a.rb
205
+
206
+ true
207
+ 42
208
+
209
+
210
+ <========< samples/b.rb >========>
211
+
212
+ ~ > cat samples/b.rb
213
+
214
+ require 'main'
215
+
216
+ ARGV.replace %w( 40 1 1 ) if ARGV.empty?
217
+
218
+ Main {
219
+ argument('foo'){
220
+ arity 3 # foo will given three times
221
+ cast :int # value cast to Fixnum
222
+ validate{|foo| [40,1].include? foo} # raises error in failure case
223
+ description 'the foo param' # shown in --help
224
+ }
225
+
226
+ def run
227
+ p params['foo'].given?
228
+ p params['foo'].values
229
+ end
230
+ }
231
+
232
+ ~ > ruby samples/b.rb
233
+
234
+ true
235
+ [40, 1, 1]
236
+
237
+
238
+ <========< samples/c.rb >========>
239
+
240
+ ~ > cat samples/c.rb
241
+
242
+ require 'main'
243
+
244
+ ARGV.replace %w( foo=40 foo=2 bar=false ) if ARGV.empty?
245
+
246
+ Main {
247
+ keyword('foo'){
248
+ required # by default keywords are not required
249
+ arity 2
250
+ cast :float
251
+ }
252
+ keyword('bar'){
253
+ cast :bool
254
+ }
255
+
256
+ def run
257
+ p params['foo'].given?
258
+ p params['foo'].values
259
+ p params['bar'].given?
260
+ p params['bar'].value
261
+ end
262
+ }
263
+
264
+ ~ > ruby samples/c.rb
265
+
266
+ true
267
+ [40.0, 2.0]
268
+ true
269
+ false
270
+
271
+
272
+ <========< samples/d.rb >========>
273
+
274
+ ~ > cat samples/d.rb
275
+
276
+ require 'main'
277
+
278
+ ARGV.replace %w( --foo=40 -f2 ) if ARGV.empty?
279
+
280
+ Main {
281
+ option('foo', 'f'){
282
+ required # by default options are not required, we could use 'foo=foo'
283
+ # above as a shortcut
284
+ argument_required
285
+ arity 2
286
+ cast :float
287
+ }
288
+
289
+ option('bar=[bar]', 'b'){ # note shortcut syntax for optional args
290
+ # argument_optional # we could also use this method
291
+ cast :bool
292
+ default false
293
+ }
294
+
295
+ def run
296
+ p params['foo'].given?
297
+ p params['foo'].values
298
+ p params['bar'].given?
299
+ p params['bar'].value
300
+ end
301
+ }
302
+
303
+ ~ > ruby samples/d.rb
304
+
305
+ true
306
+ [40.0, 2.0]
307
+ nil
308
+ false
309
+
310
+
311
+ <========< samples/e.rb >========>
312
+
313
+ ~ > cat samples/e.rb
314
+
315
+ require 'main'
316
+
317
+ Main {
318
+ argument 'global-argument'
319
+ option 'global-option'
320
+
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
+
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
374
+
375
+
376
+ <========< samples/g.rb >========>
377
+
378
+ ~ > cat samples/g.rb
379
+
380
+ require 'main'
381
+
382
+ ARGV.replace %w( 42 ) if ARGV.empty?
383
+
384
+ Main {
385
+ argument( 'foo' )
386
+ option( 'bar' )
387
+
388
+ run { puts "This is what to_options produces: #{params.to_options.inspect}" }
389
+ }
390
+
391
+ ~ > ruby samples/g.rb
392
+
393
+ This is what to_options produces: {"help"=>nil, "foo"=>"42", "bar"=>nil}
394
+
395
+
396
+ <========< samples/h.rb >========>
397
+
398
+ ~ > cat samples/h.rb
399
+
400
+ require 'main'
401
+
402
+ # block-defaults are instance_eval'd in the main instance and be combined with
403
+ # mixins
404
+ #
405
+ # ./h.rb #=> forty-two
406
+ # ./h.rb a #=> 42
407
+ # ./h.rb b #=> 42.0
408
+ #
409
+
410
+ Main {
411
+ fattr :default_for_foobar => 'forty-two'
412
+
413
+ option(:foobar) do
414
+ default{ default_for_foobar }
415
+ end
416
+
417
+ mixin :foo do
418
+ fattr :default_for_foobar => 42
419
+ end
420
+
421
+ mixin :bar do
422
+ fattr :default_for_foobar => 42.0
423
+ end
424
+
425
+
426
+ run{ p params[:foobar].value }
427
+
428
+ mode :a do
429
+ mixin :foo
430
+ end
431
+
432
+ mode :b do
433
+ mixin :bar
434
+ end
435
+ }
436
+
437
+ ~ > ruby samples/h.rb
438
+
439
+ "forty-two"
440
+
441
+
442
+
443
+ DOCS
444
+ test/main.rb
445
+
446
+ vim -p lib/main.rb lib/main/*rb
447
+
448
+ API section below
449
+
450
+ HISTORY
451
+ 2.8.3
452
+ - support for block defaults
453
+
454
+
455
+ 2.8.2
456
+ - fixes and tests for negative arity/attr arguments, options, eg
457
+
458
+ argument(:foo){
459
+ arity -1
460
+ }
461
+
462
+ def run # ARGV == %w( a b c )
463
+ p foo #=> %w( a b c )
464
+ end
465
+
466
+ thanks nathan
467
+
468
+ 2.8.1
469
+ - move from attributes.rb to fattr.rb
470
+
471
+ 2.8.0
472
+ - added 'to_options' method for Parameter::Table. this allows you to convert
473
+ all the parameters to a simple hash.
474
+ for example
475
+
476
+ Main {
477
+ option 'foo'
478
+ argument 'baz'
479
+
480
+ run { puts params.to_options.inspect }
481
+
482
+ }
483
+
484
+ 2.7.0
485
+ - removed bundled arrayfields and attributes. these are now dependancies
486
+ mananged by rubygems. a.k.a. you must have rubygems installed for main
487
+ to work.
488
+
489
+ 2.6.0
490
+ - added 'mixin' feaature for storing, and later evaluating a block of
491
+ code. the purpose of this is for use with modes where you want to keep
492
+ your code dry, but may not want to define something in the base class
493
+ for all to inherit. 'mixin' allows you to define the code to inherit
494
+ once and the selectively drop it in child classes (modes) on demand.
495
+ for example
496
+
497
+ Main {
498
+ mixin :foobar do
499
+ option 'foo'
500
+ option 'bar'
501
+ end
502
+
503
+ mode :install do
504
+ mixin :foobar
505
+ end
506
+
507
+ mode :uninstall do
508
+ mixin :foobar
509
+ end
510
+
511
+ mode :clean do
512
+ end
513
+ }
514
+
515
+ - mode definitions are now deferred to the end of the Main block, so you
516
+ can do this
517
+
518
+ Main {
519
+ mode 'a' do
520
+ mixin :foo
521
+ end
522
+
523
+ mode 'b' do
524
+ mixin :foo
525
+ end
526
+
527
+ def inherited_method
528
+ 42
529
+ end
530
+
531
+ mixin 'foo' do
532
+ def another_inherited_method
533
+ 'forty-two'
534
+ end
535
+ end
536
+ }
537
+
538
+ - added sanity check at end of paramter contruction
539
+
540
+ - improved auto usage generation when arity is used with arguments
541
+
542
+ - removed 'p' shortcut in paramerter dsl because it collided with
543
+ Kernel.p. it's now called 'param'. this method is availble *inside* a
544
+ parameter definition
545
+
546
+ option('foo', 'f'){
547
+ synopsis "arity = #{ param.arity }"
548
+ }
549
+
550
+ - fixed bug where '--' did not signal the end of parameter parsing in a
551
+ getoptlong compliant way
552
+
553
+ - added (before/after)_parse_parameters, (before/after)_initialize, and
554
+ (before/after)_run hooks
555
+
556
+ - fixed bug where adding to usage via
557
+
558
+ usage['my_section'] = 'custom message'
559
+
560
+ totally horked the default auto generated usage message
561
+
562
+ - updated dependancies in gemspec.rb for attributes (~> 5.0.0) and
563
+ arrayfields (~> 4.3.0)
564
+
565
+ - check that client code defined run, iff not wrap_run! is called. this is
566
+ so mains with a mode, but no run defined, still function correctly when
567
+ passed a mode
568
+
569
+ - added new shortcut for creating accessors for parameters. for example
570
+
571
+ option('foo'){
572
+ argument :required
573
+ cast :int
574
+ attr
575
+ }
576
+
577
+ def run
578
+ p foo ### this attr will return the parameter's *value*
579
+ end
580
+
581
+ a block can be passed to specify how to extract the value from the
582
+ parameter
583
+
584
+ argument('foo'){
585
+ optional
586
+ default 21
587
+ cast :int
588
+ attr{|param| param.value * 2}
589
+ }
590
+
591
+ def run
592
+ p foo #=> 42
593
+ end
594
+
595
+ - fixed bug where 'abort("message")' would print "message" twice on exit
596
+ if running under a nested mode (yes again - the fix in 2.4.0 wasn't
597
+ complete)
598
+
599
+ - added a time cast, which uses Time.parse
600
+
601
+ argument('login_time'){ cast :time }
602
+
603
+ - added a date cast, which uses Date.parse
604
+
605
+ argument('login_date'){ cast :date }
606
+
607
+
608
+ 2.5.0
609
+ - added 'examples', 'samples', and 'api' kewords to main dsl. each
610
+ keyword takes a list of strings which will be included in the help
611
+ message
612
+
613
+ Main {
614
+ examples "foobar example", "barfoo example"
615
+
616
+ samples <<-txt
617
+ do this
618
+
619
+ don't do that
620
+ txt
621
+
622
+ api %(
623
+ foobar string, hash
624
+
625
+ barfoo hash, string
626
+ )
627
+ }
628
+
629
+ results in a usage message with sections like
630
+
631
+ ...
632
+
633
+ EXAMPLES
634
+ foobar example
635
+ barfoo example
636
+
637
+ SAMPLES
638
+ do this
639
+
640
+ don't do that
641
+
642
+ API
643
+ foobar string, hash
644
+
645
+ barfoo hash, string
646
+
647
+ ...
648
+
649
+ 2.4.0
650
+ - fixed bug where 'abort("message")' would print "message" twice on exit
651
+ if running under a nested mode.
652
+
653
+ - allowed parameters to be overridden completely in subclasses (modes)
654
+
655
+ 2.3.0
656
+ - re-worked Main.new such that client code may define an #initialize
657
+ methods and the class will continue to work. that is to say it's fine
658
+ to do this
659
+
660
+ Main {
661
+ def initialize
662
+ @a = 42
663
+ end
664
+
665
+ def run
666
+ p @a
667
+ end
668
+
669
+ mode 'foo' do
670
+ def run
671
+ p @a
672
+ end
673
+ end
674
+ }
675
+
676
+ the client #initialize will be called *after* main has done it's normal
677
+ initialization so things like @argv, @env, and @stdin will all be there
678
+ in initialize. of course you could have done this before but you'd have
679
+ to both call super and call it with the correct arguments - now you can
680
+ simply ignore it.
681
+
682
+ 2.2.0
683
+ - added ability for parameter dsl error handlers to accept an argument,
684
+ this will be passed the current error. for example
685
+
686
+ argument(:x) do
687
+ arity 42
688
+
689
+ error do |e|
690
+ case e
691
+ when Parameter::Arity
692
+ ...
693
+ end
694
+ end
695
+
696
+ - refined the mode parsing a bit: modes can now be abbreviated to uniqness
697
+ and, when the mode is ambiuous, a nice error message is printed, for
698
+ example:
699
+
700
+ ambiguous mode: in = (inflate or install)?
701
+
702
+ 2.1.0
703
+ - added custom error handling dsl for parameters, this includes the ability
704
+ to prepend, append, or replace the standard error handlers:
705
+
706
+ require 'main'
707
+
708
+ Main {
709
+ argument 'x' do
710
+ error :before do
711
+ puts 'this fires *before* normal error handling using #instance_eval...'
712
+ end
713
+
714
+ error do
715
+ puts 'this fires *instead of* normal error handling using #instance_eval...'
716
+ end
717
+
718
+ error :after do
719
+ puts 'this fires *after* normal error handling using #instance_eval...'
720
+ end
721
+ end
722
+
723
+ run(){ p param['x'].given? }
724
+ }
725
+
726
+ - added ability to exit at any time bypassing *all* error handling using
727
+ 'throw :exit, 42' where 42 is the desired exit status. throw without a
728
+ status simply exits with 0.
729
+
730
+ - added 'help!' method which simply dumps out usage and exits
731
+
732
+ 2.0.0
733
+ - removed need for proxy.rb via Main::Base.wrap_run!
734
+ - added error handling hooks for parameter parsing
735
+ - bundled arrayfields, attributes, and pervasives although gems are tried
736
+ first
737
+ - softened error messages for parameter parsing errors: certain classes of
738
+ errors are now 'softspoken' and print only the message, not the entire
739
+ stacktrace, to stderr. much nicer for users. this is configurable.
740
+ - added subcommand/mode support
741
+ - added support for user defined exception handling on top level
742
+ exceptions/exits
743
+ - added support for negative arity. this users ruby's own arity
744
+ semantics, for example:
745
+
746
+ lambda{|*a|}.arity == -1
747
+ lambda{|a,*b|}.arity == -2
748
+ lambda{|a,b,*c|}.arity == -3
749
+ ...
750
+
751
+ in otherwords parameters now support 'zero or more', 'one or more' ...
752
+ 'n or more' argument semantics
753
+
754
+ 1.0.0
755
+ - some improved usage messages from jeremy hinegardner
756
+
757
+ 0.0.2
758
+ - removed dependancy on attributes/arrayfields. main now has zero gem
759
+ dependancies.
760
+
761
+ - added support for io redirection. redirection of stdin, stdout, and
762
+ stderr can be done to any io like object or object that can be
763
+ inerpreted as a pathname (object.to_s)
764
+
765
+ - main objects can now easily be created and run on demand, which makes
766
+ testing a breeze
767
+
768
+ def test_unit_goodness!
769
+ main =
770
+ Main.new{
771
+ stdout StringIO.new
772
+ stderr '/dev/null'
773
+
774
+ def run
775
+ puts 42
776
+ end
777
+ }
778
+
779
+ main.run
780
+ main.stdout.rewind
781
+
782
+ assert main.stdout.read == "42\n"
783
+ end
784
+
785
+ - added API section to readme and called it 'docs'
786
+
787
+ - wrote a bunch more tests. there are now 42 of them.
788
+
789
+ 0.0.1
790
+
791
+ initial version. this version extracts much of the functionality of alib's
792
+ (gen install alib) Alib.script main program generator and also some of jim's
793
+ freeze's excellent CommandLine::Aplication into what i hope is a simpler and
794
+ more unified interface
795
+
796
+ API
797
+
798
+ Main {
799
+
800
+ ###########################################################################
801
+ # CLASS LEVEL API #
802
+ ###########################################################################
803
+ #
804
+ # the name of the program, auto-set and used in usage
805
+ #
806
+ program 'foo.rb'
807
+ #
808
+ # a short description of program functionality, auto-set and used in usage
809
+ #
810
+ synopsis "foo.rb arg [options]+"
811
+ #
812
+ # long description of program functionality, used in usage iff set
813
+ #
814
+ description <<-hdoc
815
+ this text will automatically be indented to the right level.
816
+
817
+ it should describe how the program works in detail
818
+ hdoc
819
+ #
820
+ # used in usage iff set
821
+ #
822
+ author 'ara.t.howard@gmail.com'
823
+ #
824
+ # used in usage
825
+ #
826
+ version '0.0.42'
827
+ #
828
+ # stdin/out/err can be anthing which responds to read/write or a string
829
+ # which will be opened as in the appropriate mode
830
+ #
831
+ stdin '/dev/null'
832
+ stdout '/dev/null'
833
+ stderr open('/dev/null', 'w')
834
+ #
835
+ # the logger should be a Logger object, something 'write'-able, or a string
836
+ # which will be used to open the logger. the logger_level specifies the
837
+ # initalize verbosity setting, the default is Logger::INFO
838
+ #
839
+ logger(( program + '.log' ))
840
+ logger_level Logger::DEBUG
841
+ #
842
+ # you can configure exit codes. the defaults are shown
843
+ #
844
+ exit_success # 0
845
+ exit_failure # 1
846
+ exit_warn # 42
847
+ #
848
+ # the usage object is rather complex. by default it's an object which can
849
+ # be built up in sections using the
850
+ #
851
+ # usage["BUGS"] = "something about bugs'
852
+ #
853
+ # syntax to append sections onto the already pre-built usage message which
854
+ # contains program, synopsis, parameter descriptions and the like
855
+ #
856
+ # however, you always replace the usage object wholesale with one of your
857
+ # chosing like so
858
+ #
859
+ usage <<-txt
860
+ my own usage message
861
+ txt
862
+
863
+ ###########################################################################
864
+ # MODE API #
865
+ ###########################################################################
866
+ #
867
+ # modes are class factories that inherit from their parent class. they can
868
+ # be nested *arbitrarily* deep. usage messages are tailored for each mode.
869
+ # modes are, for the most part, independant classes but parameters are
870
+ # always a superset of the parent class - a mode accepts all of it's parents
871
+ # paramters *plus* and additional ones
872
+ #
873
+ option 'inherited-option'
874
+ argument 'inherited-argument'
875
+
876
+ mode 'install' do
877
+ option 'force' do
878
+ description 'clobber existing installation'
879
+ end
880
+
881
+ def run
882
+ inherited_method()
883
+ puts 'installing...'
884
+ end
885
+
886
+ mode 'docs' do
887
+ description 'installs the docs'
888
+
889
+ def run
890
+ puts 'installing docs...'
891
+ end
892
+ end
893
+ end
894
+
895
+ mode 'un-install' do
896
+ option 'force' do
897
+ description 'remove even if dependancies exist'
898
+ end
899
+
900
+ def run
901
+ inherited_method()
902
+ puts 'un-installing...'
903
+ end
904
+ end
905
+
906
+ def run
907
+ puts 'no mode yo?'
908
+ end
909
+
910
+ def inherited_method
911
+ puts 'superclass_method...'
912
+ end
913
+
914
+
915
+ ###########################################################################
916
+ # PARAMETER API #
917
+ ###########################################################################
918
+ #
919
+ # all the parameter types of argument|keyword|option|environment share this
920
+ # api. you must specify the type when the parameter method is used.
921
+ # alternatively used one of the shortcut methods
922
+ # argument|keyword|option|environment. in otherwords
923
+ #
924
+ # parameter('foo'){ type :option }
925
+ #
926
+ # is synonymous with
927
+ #
928
+ # option('foo'){ }
929
+ #
930
+ option 'foo' {
931
+ #
932
+ # required - whether this paramter must by supplied on the command line.
933
+ # note that you can create 'required' options with this keyword
934
+ #
935
+ required # or required true
936
+ #
937
+ # argument_required - applies only to options.
938
+ #
939
+ argument_required # argument :required
940
+ #
941
+ # argument_optional - applies only to options.
942
+ #
943
+ argument_optional # argument :optional
944
+ #
945
+ # cast - should be either a lambda taking one argument, or a symbol
946
+ # designation one of the built in casts defined in Main::Cast. supported
947
+ # types are :boolean|:integer|:float|:numeric|:string|:uri. built-in
948
+ # casts can be abbreviated
949
+ #
950
+ cast :int
951
+ #
952
+ # validate - should be a lambda taking one argument and returning
953
+ # true|false
954
+ #
955
+ validate{|int| int == 42}
956
+ #
957
+ # synopsis - should be a concise characterization of the paramter. a
958
+ # default synopsis is built automatically from the parameter. this
959
+ # information is displayed in the usage message
960
+ #
961
+ synopsis '--foo'
962
+ #
963
+ # description - a longer description of the paramter. it appears in the
964
+ # usage also.
965
+ #
966
+ description 'a long description of foo'
967
+ #
968
+ # arity - indicates how many times the parameter should appear on the
969
+ # command line. the default is one. negative arities are supported and
970
+ # follow the same rules as ruby methods/procs.
971
+ #
972
+ arity 2
973
+ #
974
+ # default - you can provide a default value in case none is given. the
975
+ # alias 'defaults' reads a bit nicer when you are giving a list of
976
+ # defaults for paramters of > 1 arity
977
+ #
978
+ defaults 40, 2
979
+ #
980
+ # you can add custom per-parameter error handlers using the following
981
+ #
982
+ error :before do
983
+ puts 'this fires *before* normal error handling using #instance_eval...'
984
+ end
985
+
986
+ error do
987
+ puts 'this fires *instead of* normal error handling using #instance_eval...'
988
+ end
989
+
990
+ error :after do
991
+ puts 'this fires *after* normal error handling using #instance_eval...'
992
+ end
993
+ }
994
+
995
+ ###########################################################################
996
+ # INSTANCE LEVEL API #
997
+ ###########################################################################
998
+ #
999
+ # you must define a run method. it is the only method you must define.
1000
+ #
1001
+ def run
1002
+ #
1003
+ # all parameters are available in the 'params' hash and via the alias
1004
+ # 'param'. it can be indexed via string or symbol. the values are all
1005
+ # Main::Parameter objects
1006
+ #
1007
+ foo = params['foo']
1008
+ #
1009
+ # the given? method indicates whether or not the parameter was given on
1010
+ # the commandline/environment, etc. in particular this will not be true
1011
+ # when a default value was specified but no parameter was given
1012
+ #
1013
+ foo.given?
1014
+ #
1015
+ # the list of all values can be retrieved via 'values'. note that this
1016
+ # is always an array.
1017
+ #
1018
+ p foo.values
1019
+ #
1020
+ # the __first__ value can be retrieved via 'value'. note that this
1021
+ # never an array.
1022
+ #
1023
+ p foo.value
1024
+ #
1025
+ # the methods debug|info|warn|error|fatal are delegated to the logger
1026
+ # object
1027
+ #
1028
+ info{ "this goes to the log" }
1029
+ #
1030
+ # you can set the exit_status at anytime. this status is used when
1031
+ # exiting the program. exceptions cause this to be ext_failure if, and
1032
+ # only if, the current value was exit_success. in otherwords an
1033
+ # un-caught exception always results in a failing exit_status
1034
+ #
1035
+ exit_status exit_failure
1036
+ #
1037
+ # a few shortcuts both set the exit_status and exit the program.
1038
+ #
1039
+ exit_success!
1040
+ exit_failure!
1041
+ exit_warn!
1042
+ end
1043
+
1044
+ }