main 2.5.0 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -5,8 +5,9 @@ SYNOPSIS
5
5
  a class factory and dsl for generating command line programs real quick
6
6
 
7
7
  URI
8
- http://rubyforge.org/projects/codeforpeople/
9
8
  http://codeforpeople.com/lib/ruby/
9
+ http://rubyforge.org/projects/codeforpeople/
10
+ http://codeforpeople.rubyforge.org/svn/
10
11
 
11
12
  INSTALL
12
13
  gem install main
@@ -25,6 +26,7 @@ DESCRIPTION
25
26
  - parsing user defined ARGV and ENV
26
27
  - zero requirements for understanding the obtuse apis of *any* command
27
28
  line option parsers
29
+ - leather pants
28
30
 
29
31
  in short main.rb aims to drastically lower the barrier to writing uniform
30
32
  command line applications.
@@ -64,7 +66,7 @@ DESCRIPTION
64
66
  end
65
67
  }
66
68
 
67
- which allows you a program called 'a.rb' to be invoked as
69
+ which allows a program, called 'a.rb', to be invoked as
68
70
 
69
71
  ruby a.rb install
70
72
 
@@ -252,7 +254,7 @@ SAMPLES
252
254
  b.rb
253
255
 
254
256
  SYNOPSIS
255
- b.rb foo [options]+
257
+ b.rb foo foo foo [options]+
256
258
 
257
259
  PARAMETERS
258
260
  foo (3 -> int(foo))
@@ -357,7 +359,7 @@ SAMPLES
357
359
 
358
360
  PARAMETERS
359
361
  --foo=foo, -f (2 -> float(foo))
360
- --bar=[bar], -b (1 ~> bool(bar=false))
362
+ --bar=[bar], -b (0 ~> bool(bar=false))
361
363
  --help, -h
362
364
 
363
365
 
@@ -365,11 +367,130 @@ SAMPLES
365
367
  DOCS
366
368
  test/main.rb
367
369
 
368
- vim -o lib/main.rb lib/main/*
370
+ find ./lib |xargs vim -o
369
371
 
370
372
  API section below
371
373
 
372
374
  HISTORY
375
+ 2.6.0
376
+ - added 'mixin' feaature for storing, and later evaluating a block of
377
+ code. the purpose of this is for use with modes where you want to keep
378
+ your code dry, but may not want to define something in the base class
379
+ for all to inherit. 'mixin' allows you to define the code to inherit
380
+ once and the selectively drop it in child classes (modes) on demand.
381
+ for example
382
+
383
+ Main {
384
+ mixin :foobar do
385
+ option 'foo'
386
+ option 'bar'
387
+ end
388
+
389
+ mode :install do
390
+ mixin :foobar
391
+ end
392
+
393
+ mode :uninstall do
394
+ mixin :foobar
395
+ end
396
+
397
+ mode :clean do
398
+ end
399
+ }
400
+
401
+ - mode definitions are now deferred to the end of the Main block, so you
402
+ can do this
403
+
404
+ Main {
405
+ mode 'a' do
406
+ mixin :foo
407
+ end
408
+
409
+ mode 'b' do
410
+ mixin :foo
411
+ end
412
+
413
+ def inherited_method
414
+ 42
415
+ end
416
+
417
+ mixin 'foo' do
418
+ def another_inherited_method
419
+ 'forty-two'
420
+ end
421
+ end
422
+ }
423
+
424
+ - added sanity check at end of paramter contruction
425
+
426
+ - improved auto usage generation when arity is used with arguments
427
+
428
+ - removed 'p' shortcut in paramerter dsl because it collided with
429
+ Kernel.p. it's now called 'param'. this method is availble *inside* a
430
+ parameter definition
431
+
432
+ option('foo', 'f'){
433
+ synopsis "arity = #{ param.arity }"
434
+ }
435
+
436
+ - fixed bug where '--' did not signal the end of parameter parsing in a
437
+ getoptlong compliant way
438
+
439
+ - added (before/after)_parse_parameters, (before/after)_initialize, and
440
+ (before/after)_run hooks
441
+
442
+ - fixed bug where adding to usage via
443
+
444
+ usage['my_section'] = 'custom message'
445
+
446
+ totally horked the default auto generated usage message
447
+
448
+ - updated dependancies in gemspec.rb for attributes (~> 5.0.0) and
449
+ arrayfields (~> 4.3.0)
450
+
451
+ - check that client code defined run, iff not wrap_run! is called. this is
452
+ so mains with a mode, but no run defined, still function correctly when
453
+ passed a mode
454
+
455
+ - added new shortcut for creating accessors for parameters. for example
456
+
457
+ option('foo'){
458
+ argument :required
459
+ cast :int
460
+ attr
461
+ }
462
+
463
+ def run
464
+ p foo ### this attr will return the parameter's *value*
465
+ end
466
+
467
+ a block can be passed to specify how to extract the value from the
468
+ parameter
469
+
470
+ argument('foo'){
471
+ optional
472
+ default 21
473
+ cast :int
474
+ attr{|param| param.value * 2}
475
+ }
476
+
477
+ def run
478
+ p foo #=> 42
479
+ end
480
+
481
+ - fixed bug where 'abort("message")' would print "message" twice on exit
482
+ if running under a nested mode (yes again - the fix in 2.4.0 wasn't
483
+ complete)
484
+
485
+ - added a time cast, which uses Time.parse
486
+
487
+ argument('login_time'){ cast :time }
488
+
489
+ - added a date cast, which uses Date.parse
490
+
491
+ argument('login_date'){ cast :date }
492
+
493
+
373
494
  2.5.0
374
495
  - added 'examples', 'samples', and 'api' kewords to main dsl. each
375
496
  keyword takes a list of strings which will be included in the help
data/TODO ADDED
@@ -0,0 +1,10 @@
1
+
2
+ * clean up warnings under 'ruby -d'
3
+ * error reporting weird for some errors
4
+
5
+ ===============================================================================
6
+ X calls to abort sometimes lead to STDERR prining twice ;-(
7
+ X main's' with modes, but no run do not operate properly - add default run w/wrap_run!
8
+ X usage fubar when extra chunks are set
9
+ X usage of arguments is fubar when negative arities are used
10
+ X add sanity checks at parameter contruction completion
data/a.rb CHANGED
@@ -1,24 +1,7 @@
1
1
  require 'main'
2
2
 
3
3
  Main {
4
- option 'bar'
5
-
6
- examples 'a', 'b'
7
-
8
- api <<-txt
9
- foobar
10
- barfoo
11
- txt
12
-
13
- mode 'foo' do
14
- option('bar'){ required }
15
-
16
- def run
17
- p params
18
- end
19
- end
20
-
21
- def run
22
- p params
23
- end
4
+ option('foo'){
5
+ examples
6
+ }
24
7
  }
data/gemspec.rb CHANGED
@@ -25,7 +25,8 @@ Gem::Specification::new do |spec|
25
25
 
26
26
  spec.has_rdoc = File::exist? "doc"
27
27
  spec.test_suite_file = "test/#{ lib }.rb" if File::directory? "test"
28
- #spec.add_dependency 'lib', '>= version'
28
+ spec.add_dependency 'attributes', '>= 5.0.0'
29
+ spec.add_dependency 'arrayfields', '>= 4.3.0'
29
30
 
30
31
  spec.extensions << "extconf.rb" if File::exists? "extconf.rb"
31
32
 
data/install.rb CHANGED
@@ -36,9 +36,11 @@ def install_rb(srcdir=nil, destdir=nil, mode=nil, bin=nil)
36
36
  next unless FileTest.file?(f)
37
37
  next if (f = f[srcdir.length+1..-1]) == nil
38
38
  next if (/CVS$/ =~ File.dirname(f))
39
+ next if (/\.svn/ =~ File.dirname(f))
39
40
  next if f =~ %r/\.lnk/
40
41
  next if f =~ %r/\.svn/
41
42
  next if f =~ %r/\.swp/
43
+ next if f =~ %r/\.svn/
42
44
  path.push f
43
45
  dir |= [File.dirname(f)]
44
46
  end
@@ -2,7 +2,7 @@ module Main
2
2
  #
3
3
  # top level constants
4
4
  #
5
- Main::VERSION = '2.5.0' unless
5
+ Main::VERSION = '2.6.0' unless
6
6
  defined? Main::VERSION
7
7
  def self.version() Main::VERSION end
8
8
 
@@ -28,13 +28,13 @@ module Main
28
28
  42
29
29
  end
30
30
  begin
31
- gem 'attributes', '~> 4.1.0'
31
+ gem 'attributes', '~> 5.0.0'
32
32
  require 'attributes'
33
33
  rescue Exception
34
34
  require libdir + 'attributes'
35
35
  end
36
36
  begin
37
- gem 'attributes', '~> 4.3.0'
37
+ gem 'arrayfields', '~> 4.3.0'
38
38
  require 'arrayfields'
39
39
  rescue Exception
40
40
  require libdir + 'arrayfields'
@@ -45,6 +45,7 @@ module Main
45
45
  require libdir + 'stdext'
46
46
  require libdir + 'softspoken'
47
47
  require libdir + 'util'
48
+ require libdir + 'logger'
48
49
  require libdir + 'usage'
49
50
  require libdir + 'cast'
50
51
  require libdir + 'parameter'
@@ -21,7 +21,9 @@ module Main
21
21
  end
22
22
 
23
23
  pre_run
24
+ before_run
24
25
  self.class.const_get(:RUN).bind(self).call(*a, &b)
26
+ after_run
25
27
  post_run
26
28
 
27
29
  finalize
@@ -66,13 +68,14 @@ module Main
66
68
  }
67
69
  end
68
70
 
71
+ # attributes
69
72
  attribute( 'name' ){ File.basename $0 }
70
73
  attribute( 'synopsis' ){ Usage.default_synopsis(self) }
71
74
  attribute( 'description' )
72
75
  attribute( 'usage' ){ Usage.default_usage self }
73
76
  attribute( 'modes' ){ Mode.list }
77
+ attribute( 'mode_definitions' ){ Array.new }
74
78
  attribute( 'mode_name' ){ 'main' }
75
- def mode_name=(value) @mode_name = Mode.new value end
76
79
  attribute( 'parent' ){ nil }
77
80
  attribute( 'children' ){ Set.new }
78
81
 
@@ -89,6 +92,21 @@ module Main
89
92
  attribute( 'exit_failure' ){ EXIT_FAILURE }
90
93
  attribute( 'exit_warn' ){ EXIT_WARN }
91
94
  inheritable_attribute( 'parameters' ){ Parameter::List[] }
95
+ inheritable_attribute( 'can_has_hash' ){ Hash.new }
96
+ inheritable_attribute( 'mixin_table' ){ Hash.new }
97
+
98
+ # override a few attributes
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
92
110
 
93
111
  def create parent = Base, *a, &b
94
112
  Class.new parent do |child|
@@ -97,6 +115,15 @@ module Main
97
115
  child.context do
98
116
  child.class_eval &b if b
99
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
100
127
  end
101
128
  end
102
129
  end
@@ -115,6 +142,14 @@ module Main
115
142
  end
116
143
  end
117
144
 
145
+ module ::Main
146
+ singleton_class{
147
+ def current
148
+ ::Main::Base.context
149
+ end
150
+ }
151
+ end
152
+
118
153
  def fully_qualified_mode
119
154
  list = []
120
155
  ancestors.each do |ancestor|
@@ -129,8 +164,10 @@ module Main
129
164
  def new(*a, &b)
130
165
  allocate.instance_eval do
131
166
  pre_initialize
167
+ before_initialize
132
168
  main_initialize *a, &b
133
169
  initialize
170
+ after_initialize
134
171
  post_initialize
135
172
  self
136
173
  end
@@ -167,14 +204,51 @@ module Main
167
204
  end
168
205
  alias_method 'env', 'environment'
169
206
 
207
+ =begin
170
208
  def mode name, &b
171
209
  klass =
172
210
  create context do
173
- mode_name name
211
+ mode_name name.to_s
174
212
  module_eval &b if b
175
213
  end
176
214
  modes.add klass
177
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!
178
252
 
179
253
  %w[ examples samples api ].each do |chunkname|
180
254
  module_eval <<-code
@@ -184,6 +258,8 @@ module Main
184
258
  end
185
259
  code
186
260
  end
261
+ alias_method 'example', 'examples'
262
+ alias_method 'sample', 'samples'
187
263
  end
188
264
  extend DSL
189
265
 
@@ -219,6 +295,7 @@ module Main
219
295
  =begin
220
296
  =end
221
297
  def pre_initialize() :hook end
298
+ def before_initialize() :hook end
222
299
  def main_initialize argv = ARGV, env = ENV, opts = {}
223
300
  @argv, @env, @opts = argv, env, opts
224
301
  setup_finalizers
@@ -227,6 +304,7 @@ module Main
227
304
  setup_logging
228
305
  end
229
306
  def initialize() :hook end
307
+ def after_initialize() :hook end
230
308
  def post_initialize() :hook end
231
309
 
232
310
  def setup_finalizers
@@ -253,7 +331,7 @@ module Main
253
331
  def logger= log
254
332
  unless(defined?(@logger) and @logger == log)
255
333
  case log
256
- when Logger
334
+ when ::Logger, Logger
257
335
  @logger = log
258
336
  when IO, StringIO
259
337
  @logger = Logger.new log
@@ -320,6 +398,7 @@ module Main
320
398
  end
321
399
 
322
400
  def pre_parse_parameters() :hook end
401
+ def before_parse_parameters() :hook end
323
402
  def parse_parameters
324
403
  pre_parse_parameters
325
404
 
@@ -329,12 +408,15 @@ module Main
329
408
 
330
409
  post_parse_parameters
331
410
  end
411
+ def after_parse_parameters() :hook end
332
412
  def post_parse_parameters() :hook end
333
413
 
334
414
  def pre_run() :hook end
415
+ def before_run() :hook end
335
416
  def run
336
417
  raise NotImplementedError, 'run not defined'
337
418
  end
419
+ def after_run() :hook end
338
420
  def post_run() :hook end
339
421
 
340
422
  def mode_given?
@@ -380,7 +462,9 @@ module Main
380
462
  end
381
463
 
382
464
  if Softspoken === e or SystemExit === e
383
- stderr.puts e.message unless(SystemExit === e and e.message.to_s == 'exit')
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
384
468
  else
385
469
  fatal{ e }
386
470
  end