main 2.9.3 → 3.0.1

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