main 2.9.3 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -7,7 +7,7 @@ SYNOPSIS
7
7
  URI
8
8
  http://codeforpeople.com/lib/ruby/
9
9
  http://rubyforge.org/projects/codeforpeople/
10
- http://codeforpeople.rubyforge.org/svn/
10
+ http://github.com/ahoward/main
11
11
 
12
12
  INSTALL
13
13
  gem install main
@@ -314,26 +314,35 @@ SAMPLES
314
314
 
315
315
  require 'main'
316
316
 
317
+ ARGV.replace %w( x y argument )
318
+
317
319
  Main {
318
- argument 'global-argument'
319
- option 'global-option'
320
+ argument 'argument'
321
+ option 'option'
320
322
 
321
- def run() puts 'global-run' end
323
+ def run() puts 'run' end
322
324
 
323
325
  mode 'a' do
324
326
  option 'a-option'
327
+ def run() puts 'a-run' end
325
328
  end
326
329
 
327
- mode 'b' do
328
- option 'b-option'
330
+ mode 'x' do
331
+ option 'x-option'
332
+
333
+ def run() puts 'x-run' end
334
+
335
+ mode 'y' do
336
+ option 'y-option'
329
337
 
330
- def run() puts 'b-run' end
338
+ def run() puts 'y-run' end
339
+ end
331
340
  end
332
341
  }
333
342
 
334
343
  ~ > ruby samples/e.rb
335
344
 
336
- argument(global-argument)) 0/1
345
+ y-run
337
346
 
338
347
 
339
348
  <========< samples/f.rb >========>
@@ -342,6 +351,8 @@ SAMPLES
342
351
 
343
352
  require 'main'
344
353
 
354
+ ARGV.replace %W( compress /data )
355
+
345
356
  Main {
346
357
  argument('directory'){ description 'the directory to operate on' }
347
358
 
@@ -370,7 +381,7 @@ SAMPLES
370
381
 
371
382
  ~ > ruby samples/f.rb
372
383
 
373
- argument(directory)) 0/1
384
+ this is how we run in compress mode
374
385
 
375
386
 
376
387
  <========< samples/g.rb >========>
@@ -442,12 +453,17 @@ SAMPLES
442
453
 
443
454
  DOCS
444
455
  test/main.rb
445
-
446
456
  vim -p lib/main.rb lib/main/*rb
447
-
448
457
  API section below
449
458
 
450
459
  HISTORY
460
+ 3.0.0
461
+ - major refactor to support modes via module/extend vs. subclassing.
462
+ MIGHT NOT be backward compatible, though no known issues thus far.
463
+
464
+ 2.9.0
465
+ - support ruby 1.9
466
+
451
467
  2.8.3
452
468
  - support for block defaults
453
469
 
data/README.erb CHANGED
@@ -7,7 +7,7 @@ SYNOPSIS
7
7
  URI
8
8
  http://codeforpeople.com/lib/ruby/
9
9
  http://rubyforge.org/projects/codeforpeople/
10
- http://codeforpeople.rubyforge.org/svn/
10
+ http://github.com/ahoward/main
11
11
 
12
12
  INSTALL
13
13
  gem install main
@@ -182,12 +182,14 @@ SAMPLES
182
182
 
183
183
  DOCS
184
184
  test/main.rb
185
-
186
185
  vim -p lib/main.rb lib/main/*rb
187
-
188
186
  API section below
189
187
 
190
188
  HISTORY
189
+ 3.0.0
190
+ - major refactor to support modes via module/extend vs. subclassing.
191
+ MIGHT NOT be backward compatible, though no known issues thus far.
192
+
191
193
  2.9.0
192
194
  - support ruby 1.9
193
195
 
data/Rakefile CHANGED
@@ -64,6 +64,7 @@ task :gemspec do
64
64
 
65
65
  Gem::Specification::new do |spec|
66
66
  spec.name = #{ lib.inspect }
67
+ spec.description = 'a class factory and dsl for generating command line programs real quick'
67
68
  spec.version = #{ version.inspect }
68
69
  spec.platform = Gem::Platform::RUBY
69
70
  spec.summary = #{ lib.inspect }
@@ -77,8 +78,8 @@ task :gemspec do
77
78
 
78
79
  spec.has_rdoc = #{ has_rdoc.inspect }
79
80
  spec.test_files = #{ test_files.inspect }
80
- #spec.add_dependency 'lib', '>= version'
81
- #spec.add_dependency 'fattr'
81
+ spec.add_dependency 'fattr', '>= 1.0.3'
82
+ spec.add_dependency 'arrayfields', '>= 4.5.0'
82
83
 
83
84
  spec.extensions.push(*#{ extensions.inspect })
84
85
 
data/TODO CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  * figure out how to support '-' ??
11
11
 
12
- * clean up warnings under 'ruby -d'
12
+ * clean up warnings under 'ruby -w'
13
13
  * error reporting weird for some errors
14
14
 
15
15
  ===============================================================================
data/a.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'main'
2
+
3
+ Main {
4
+ def run() p 42 end
5
+ }
@@ -2,7 +2,7 @@ module Main
2
2
  #
3
3
  # top level constants
4
4
  #
5
- Main::VERSION = '2.9.3' unless
5
+ Main::VERSION = '3.0.1' unless
6
6
  defined? Main::VERSION
7
7
  def self.version() Main::VERSION end
8
8
 
@@ -29,20 +29,7 @@ module Main
29
29
  end
30
30
 
31
31
  require 'fattr'
32
- begin
33
- version = Fattr.version
34
- raise unless version[%r/^1\./]
35
- rescue
36
- abort "main requires fattrs >= 1.0.3 - gem install fattr"
37
- end
38
-
39
32
  require 'arrayfields'
40
- begin
41
- version = Arrayfields.version
42
- raise unless version[%r/^4\./]
43
- rescue
44
- abort "main requires arrayfields >= 4.5.0 - gem install arrayfields"
45
- end
46
33
  #
47
34
  # main's own libs
48
35
  #
@@ -55,6 +42,6 @@ module Main
55
42
  require libdir + 'parameter'
56
43
  require libdir + 'getoptlong'
57
44
  require libdir + 'mode'
58
- require libdir + 'base'
45
+ require libdir + 'program'
59
46
  require libdir + 'factories'
60
47
  end
@@ -0,0 +1,77 @@
1
+ def parameter(*a, &b)
2
+ (parameters << Parameter.create(:parameter, self, *a, &b)).last
3
+ end
4
+
5
+ def argument(*a, &b)
6
+ (parameters << Parameter.create(:argument, self, *a, &b)).last
7
+ end
8
+ alias_method 'arg', 'argument'
9
+
10
+ def option(*a, &b)
11
+ (parameters << Parameter.create(:option, self, *a, &b)).last
12
+ end
13
+ alias_method 'opt', 'option'
14
+ alias_method 'switch', 'option'
15
+
16
+ def keyword(*a, &b)
17
+ (parameters << Parameter.create(:keyword, self, *a, &b)).last
18
+ end
19
+ alias_method 'kw', 'keyword'
20
+
21
+ def environment(*a, &b)
22
+ (parameters << Parameter.create(:environment, self, *a, &b)).last
23
+ end
24
+ alias_method 'env', 'environment'
25
+
26
+ def default_options!
27
+ option 'help', 'h' unless parameters.has_option?('help', 'h')
28
+ end
29
+
30
+ def mode(name, &block)
31
+ name = name.to_s
32
+ modes[name] = block
33
+ block.fattr(:name => name)
34
+ block
35
+ end
36
+
37
+ def can_has(ptype, *a, &b)
38
+ key = a.map{|s| s.to_s}.sort_by{|s| -s.size }.first
39
+ can_has_hash.update key => [ptype, a, b]
40
+ key
41
+ end
42
+
43
+ def has(key, *keys)
44
+ keys = [key, *keys].flatten.compact.map{|k| k.to_s}
45
+ keys.map do |key|
46
+ ptype, a, b = can_has_hash[key]
47
+ abort "yo - can *not* has #{ key.inspect }!?" unless(ptype and a and b)
48
+ send ptype, *a, &b
49
+ key
50
+ end
51
+ end
52
+
53
+ def mixin(name, *names, &block)
54
+ names = [name, *names].flatten.compact.map{|name| name.to_s}
55
+ if block
56
+ names.each do |name|
57
+ mixin_table[name] = block
58
+ end
59
+ else
60
+ names.each do |name|
61
+ module_eval(&mixin_table[name])
62
+ end
63
+ end
64
+ end
65
+
66
+ ## TODO - for some reason these hork the usage!
67
+
68
+ %w[ examples samples api ].each do |chunkname|
69
+ module_eval <<-code
70
+ def #{ chunkname } *a, &b
71
+ txt = b ? b.call : a.join("\\n")
72
+ usage['#{ chunkname }'] = txt
73
+ end
74
+ code
75
+ end
76
+ alias_method 'example', 'examples'
77
+ alias_method 'sample', 'samples'
@@ -1,20 +1,26 @@
1
1
  module Main
2
- def Main.create *a, &b
3
- ::Main::Base.create(::Main::Base, *a, &b)
2
+ def Main.factory(&block)
3
+ Program.factory(&block)
4
4
  end
5
5
 
6
- def Main.new *a, &b
7
- create(::Main::Base, &b).new *a
6
+ def Main.create(&block)
7
+ factory(&block)
8
8
  end
9
9
 
10
- def Main.run argv = ARGV, env = ENV, opts = {}, &block
11
- Base.create(&block).new(argv, env, opts).run
10
+ def Main.new(*args, &block)
11
+ factory(&block).build(*args).new()
12
12
  end
13
13
 
14
- module ::Kernel
15
- def Main argv = ARGV, env = ENV, opts = {}, &block
16
- ::Main.run argv, env, opts, &block
17
- end
18
- alias_method 'main', 'Main'
14
+ def Main.run(*args, &block)
15
+ new(*args, &block).run()
19
16
  end
20
17
  end
18
+
19
+ module Kernel
20
+ private
21
+ def Main(*args, &block)
22
+ Main.run(*args, &block)
23
+ end
24
+
25
+ alias_method 'main', 'Main'
26
+ end
@@ -12,11 +12,13 @@ module Main
12
12
  ensure
13
13
  self.fields = []
14
14
  end
15
+
15
16
  def add klass
16
17
  mode_name = Mode.new klass.mode_name
17
18
  raise Duplicate, mode_name if has_key? mode_name
18
19
  self[mode_name] = klass
19
20
  end
21
+
20
22
  def find_by_mode m, options = {}
21
23
  quiet = options['quiet'] || options[:quiet]
22
24
  each_pair do |mode, klass|
@@ -37,8 +39,8 @@ module Main
37
39
  end
38
40
  end
39
41
 
40
- def self.list *a, &b
41
- List.new *a, &b
42
+ def self.list(*a, &b)
43
+ List.new(*a, &b)
42
44
  end
43
45
  end
44
46
  end
@@ -38,22 +38,24 @@ module Main
38
38
  @sym ||= name.split(%r/::/).last.downcase.to_sym
39
39
  end
40
40
 
41
- def class_for type
41
+ def class_for(type)
42
42
  sym = type.to_s.downcase.to_sym
43
43
  c = Types.detect{|t| t.sym == sym}
44
44
  raise ArgumentError, type.inspect unless c
45
45
  c
46
46
  end
47
47
 
48
- def create type, *a, &b
49
- c = class_for type
48
+ def create(type, main, *a, &b)
49
+ c = class_for(type)
50
50
  obj = c.allocate
51
51
  obj.type = c.sym
52
- obj.instance_eval{ initialize *a, &b }
52
+ obj.main = main
53
+ obj.instance_eval{ initialize(*a, &b) }
53
54
  obj
54
55
  end
55
56
  end
56
57
 
58
+ fattr 'main'
57
59
  fattr 'type'
58
60
  fattr 'names'
59
61
  fattr 'abbreviations'
@@ -74,8 +76,9 @@ module Main
74
76
  fattr 'error_handler_instead'
75
77
  fattr 'error_handler_after'
76
78
 
77
- def initialize name, *names, &block
78
- @names = Cast.list_of_string name, *names
79
+ def initialize(name, *names, &block)
80
+ @names = Cast.list_of_string(name, *names)
81
+
79
82
  @names.map! do |name|
80
83
  if name =~ %r/^-+/
81
84
  name.gsub! %r/^-+/, ''
@@ -330,7 +333,7 @@ puts
330
333
  defaults!
331
334
  validate!
332
335
 
333
- argv.push *ignore[1 .. -1] unless ignore.empty?
336
+ argv.push(*ignore[1..-1]) unless ignore.empty?
334
337
 
335
338
  return self
336
339
  ensure
@@ -520,8 +523,8 @@ puts
520
523
  replace keep
521
524
  end
522
525
 
523
- def << *a
524
- delete *a
526
+ def <<(*a)
527
+ delete(*a)
525
528
  super
526
529
  end
527
530
  end
@@ -541,7 +544,7 @@ puts
541
544
  name = param.name
542
545
  a ||= name
543
546
  b = fattr_block_for name, &block
544
- Main.current.module_eval{ fattr *a, &b }
547
+ @param.main.module_eval{ fattr(*a, &b) }
545
548
  end
546
549
  alias_method 'attribute', 'fattr'
547
550
 
@@ -550,8 +553,8 @@ puts
550
553
  lambda{ block.call self.param[name] }
551
554
  end
552
555
 
553
- def attr *a, &b
554
- fattr *a, &b
556
+ def attr(*a, &b)
557
+ fattr(*a, &b)
555
558
  end
556
559
 
557
560
  def example *list
@@ -643,7 +646,7 @@ puts
643
646
  raise ArgumentError, 'no default'
644
647
  end
645
648
  unless values.empty?
646
- param.defaults.push *values
649
+ param.defaults.push(*values)
647
650
  end
648
651
  unless block.nil?
649
652
  param.defaults.push block
@@ -0,0 +1,6 @@
1
+ module Main
2
+ class Program
3
+ require 'lib/main/program/class_methods.rb'
4
+ require 'lib/main/program/instance_methods.rb'
5
+ end
6
+ end
@@ -0,0 +1,260 @@
1
+ module Main
2
+ class Program
3
+ module ClassMethods
4
+ fattr('name'){ File.basename($0) }
5
+ fattr('program'){ File.basename($0) }
6
+ fattr('synopsis'){ Main::Usage.default_synopsis(self) }
7
+ fattr('description')
8
+ fattr('usage'){ Main::Usage.default_usage(self) }
9
+ fattr('modes'){ Main::Mode.list }
10
+
11
+ fattr('author')
12
+ fattr('version')
13
+ fattr('stdin'){ $stdin }
14
+ fattr('stdout'){ $stdout }
15
+ fattr('stderr'){ $stderr }
16
+ fattr('logger'){ Logger.new(stderr) }
17
+ fattr('logger_level'){ Logger::INFO }
18
+ fattr('exit_status'){ nil }
19
+ fattr('exit_success'){ Main::EXIT_SUCCESS }
20
+ fattr('exit_failure'){ Main::EXIT_FAILURE }
21
+ fattr('exit_warn'){ Main::EXIT_WARN }
22
+ fattr('parameters'){ Main::Parameter::List[] }
23
+ fattr('can_has_hash'){ Hash.new }
24
+ fattr('mixin_table'){ Hash.new }
25
+
26
+ fattr('factory')
27
+ fattr('argv')
28
+ fattr('env')
29
+ fattr('opts')
30
+
31
+ def factory(&block)
32
+ Factory.new(&block)
33
+ end
34
+ alias_method 'create', 'factory'
35
+
36
+ class Factory
37
+ def initialize(&block)
38
+ @block = block || lambda{}
39
+ end
40
+
41
+ def to_proc
42
+ @block
43
+ end
44
+
45
+ def build(*args, &block)
46
+ argv = (args.shift || ARGV).map{|arg| arg.dup}
47
+ env = (args.shift || ENV).to_hash.dup
48
+ opts = (args.shift || {}).to_hash.dup
49
+
50
+ factory = self
51
+ program = Class.new(Program)
52
+ program.evaluate(&factory)
53
+
54
+ program.module_eval do
55
+ program.factory = factory
56
+ program.argv = argv
57
+ program.env = env
58
+ program.opts = opts
59
+
60
+ dynamically_extend_via_commandline_modes!
61
+ program.set_default_options!
62
+
63
+ define_method(:run, &block) if block
64
+
65
+ wrap_run!
66
+ end
67
+ program
68
+ end
69
+ end
70
+
71
+ def new()
72
+ instance = allocate
73
+ instance.instance_eval do
74
+ pre_initialize()
75
+ before_initialize()
76
+ main_initialize()
77
+ initialize()
78
+ after_initialize()
79
+ post_initialize()
80
+ end
81
+ instance
82
+ end
83
+
84
+ def evaluate(&block)
85
+ module_eval(&block)
86
+ end
87
+
88
+ def set_default_options!
89
+ option('help', 'h') unless parameters.has_option?('help', 'h')
90
+ end
91
+
92
+ # TODO - ambiguous modes
93
+
94
+ # extend the class based on modules given in argv
95
+ #
96
+ def dynamically_extend_via_commandline_modes!
97
+ size = modes.size
98
+ depth_first_modes = Array.fields
99
+
100
+ loop do
101
+ modes.each do |mode|
102
+ arg = argv.first && %r/^#{ argv.first }/
103
+ if arg and mode.name =~ arg
104
+ argv.shift
105
+ modes.clear
106
+ evaluate(&mode)
107
+ depth_first_modes[mode.name] = mode
108
+ break
109
+ end
110
+ end
111
+
112
+ arg = argv.first && %r/^#{ argv.first }/
113
+ more_modes = (
114
+ !modes.empty? and modes.any?{|mode| arg && mode.name =~ arg}
115
+ )
116
+
117
+ break unless more_modes
118
+ end
119
+
120
+ self.modes = depth_first_modes
121
+ end
122
+
123
+ # wrap up users run method to handle errors, etc
124
+ #
125
+ def wrap_run!
126
+ evaluate do
127
+ alias_method 'run!', 'run'
128
+
129
+ def run()
130
+ exit_status =
131
+ catch :exit do
132
+ begin
133
+ parse_parameters
134
+
135
+ if help?
136
+ puts(usage)
137
+ exit
138
+ end
139
+
140
+ pre_run
141
+ before_run
142
+ run!
143
+ after_run
144
+ post_run
145
+
146
+ finalize
147
+ rescue Object => exception
148
+ self.exit_status ||= exception.status if exception.respond_to?(:status)
149
+ handle_exception(exception)
150
+ end
151
+ nil
152
+ end
153
+
154
+ self.exit_status ||= (exit_status || exit_success)
155
+ handle_exit(self.exit_status)
156
+ end
157
+ end
158
+ end
159
+
160
+ # TODO
161
+ def fully_qualified_mode
162
+ modes.map{|mode| mode.name}.join(' ')
163
+ end
164
+
165
+ def mode_name
166
+ return 'main' if modes.empty?
167
+ fully_qualified_mode
168
+ end
169
+
170
+ undef_method 'usage'
171
+ def usage(*args, &block)
172
+ usage! unless defined? @usage
173
+ return @usage if args.empty? and block.nil?
174
+ key, value, *ignored = args
175
+ value = block.call if block
176
+ @usage[key.to_s] = value.to_s
177
+ end
178
+
179
+ def parameter(*a, &b)
180
+ (parameters << Parameter.create(:parameter, self, *a, &b)).last
181
+ end
182
+
183
+ def argument(*a, &b)
184
+ (parameters << Parameter.create(:argument, self, *a, &b)).last
185
+ end
186
+
187
+ def option(*a, &b)
188
+ (parameters << Parameter.create(:option, self, *a, &b)).last
189
+ end
190
+
191
+ def keyword(*a, &b)
192
+ (parameters << Parameter.create(:keyword, self, *a, &b)).last
193
+ end
194
+
195
+ def environment(*a, &b)
196
+ (parameters << Parameter.create(:environment, self, *a, &b)).last
197
+ end
198
+
199
+ def default_options!
200
+ option 'help', 'h' unless parameters.has_option?('help', 'h')
201
+ end
202
+
203
+ def mode(name, &block)
204
+ name = name.to_s
205
+ modes[name] = block
206
+ block.fattr(:name => name)
207
+ block
208
+ end
209
+
210
+ def can_has(ptype, *a, &b)
211
+ key = a.map{|s| s.to_s}.sort_by{|s| -s.size }.first
212
+ can_has_hash.update key => [ptype, a, b]
213
+ key
214
+ end
215
+
216
+ def has(key, *keys)
217
+ keys = [key, *keys].flatten.compact.map{|k| k.to_s}
218
+ keys.map do |key|
219
+ ptype, a, b = can_has_hash[key]
220
+ abort "yo - can *not* has #{ key.inspect }!?" unless(ptype and a and b)
221
+ send ptype, *a, &b
222
+ key
223
+ end
224
+ end
225
+
226
+ def mixin(name, *names, &block)
227
+ names = [name, *names].flatten.compact.map{|name| name.to_s}
228
+ if block
229
+ names.each do |name|
230
+ mixin_table[name] = block
231
+ end
232
+ else
233
+ names.each do |name|
234
+ module_eval(&mixin_table[name])
235
+ end
236
+ end
237
+ end
238
+
239
+ ## TODO - for some reason these hork the usage!
240
+
241
+ %w[ examples samples api ].each do |chunkname|
242
+ module_eval <<-code
243
+ def #{ chunkname } *a, &b
244
+ txt = b ? b.call : a.join("\\n")
245
+ usage['#{ chunkname }'] = txt
246
+ end
247
+ code
248
+ end
249
+ alias_method 'example', 'examples'
250
+ alias_method 'sample', 'samples'
251
+
252
+ def run(&block)
253
+ block ||= lambda{}
254
+ define_method(:run, &block) if block
255
+ end
256
+ end
257
+
258
+ extend ClassMethods
259
+ end
260
+ end