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 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
+ }