optiongrouper 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.yardopts ADDED
@@ -0,0 +1,11 @@
1
+ --title
2
+ OptionGrouper
3
+ -m
4
+ markdown
5
+ -M
6
+ maruku
7
+ --readme
8
+ README.md
9
+ --no-private
10
+
11
+ lib/**/*.rb
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Kővágó, Zoltán
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,57 @@
1
+ OptionGrouper
2
+ =========
3
+
4
+ Command line option parsing, inspired by trollop, in an unusual way.
5
+
6
+ ## Quick intro
7
+
8
+ Options are grouped into groups, you can define the same option in more than one
9
+ group. In this case, you can use a `--group:option` syntax on the command line.
10
+
11
+ The default group (`:default`, also used when you do not specify a name) is a
12
+ bit special, as you do not need to prefix commands in this group.
13
+
14
+ require 'optiongrouper'
15
+
16
+ opts = OptionGrouper.new do
17
+ version "My Program 1.0"
18
+ header "\nUsage:\n my_program [options]\n\n"
19
+
20
+ group do
21
+ opt :debug, "Enable debugging"
22
+ end
23
+
24
+ group :debug do
25
+ header "Debugging options"
26
+ opt :level, "Set debug level", :value => :integer, :default => 0
27
+ opt :kill, "Kill the process <PID> with signal <SIGNAL>", :value =>
28
+ [[:integer, "PID"], [:integer, "SIGNAL"]]
29
+ end
30
+ end.parse
31
+
32
+ # opts[:default][:debug] is true if --debug was passed on command line
33
+ # opts[:debug][:kill] contains an array with [PID, SIGNAL]
34
+ # etc
35
+
36
+ Hope you get the idea now.
37
+
38
+ ## Contributing to optiongrouper
39
+
40
+ * Check out the latest master to make sure the feature hasn't been implemented
41
+ or the bug hasn't been fixed yet
42
+ * Check out the issue tracker to make sure someone already hasn't requested it
43
+ and/or contributed it
44
+ * Fork the project
45
+ * Start a feature/bugfix branch
46
+ * Commit and push until you are happy with your contribution
47
+ * Make sure to add tests for it. This is important so I don't break it in a
48
+ future version unintentionally.
49
+ * Please try not to mess with the Rakefile, version, or history. If you want to
50
+ have your own version, or is otherwise necessary, that is fine, but please
51
+ isolate to its own commit so I can cherry-pick around it.
52
+
53
+ ## Copyright
54
+
55
+ Copyright © 2010 Kővágó, Zoltán. See [LICENSE.txt](LICENSE.txt) for further
56
+ details.
57
+
data/Rakefile ADDED
@@ -0,0 +1,42 @@
1
+ # -*- mode: ruby; coding: utf-8 -*-
2
+
3
+ require 'rubygems'
4
+ require 'rake'
5
+
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gem|
8
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
9
+ gem.name = "optiongrouper"
10
+ gem.homepage = "http://github.com/DirtYiCE/optiongrouper"
11
+ gem.license = "MIT"
12
+ gem.summary = "Command line option parsing library"
13
+ gem.description = "Command line option parsing library with some fancy features"
14
+ gem.email = "DirtY.iCE.hu@gmail.com"
15
+ gem.authors = ["Kővágó, Zoltán"]
16
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
17
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
18
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
19
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
20
+ gem.add_runtime_dependency "blockenspiel", "~> 0.4.0"
21
+ gem.add_development_dependency "rspec", "~> 2.1.0"
22
+ gem.add_development_dependency "yard", "~> 0.6.0"
23
+ gem.add_development_dependency "jeweler", "~> 1.5.1"
24
+ gem.add_development_dependency "rcov", ">= 0"
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ require 'yard'
42
+ YARD::Rake::YardocTask.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
@@ -0,0 +1,449 @@
1
+ require 'blockenspiel'
2
+
3
+ # A basic command line option parser with group support.
4
+ class OptionGrouper
5
+ include Blockenspiel::DSL
6
+
7
+ VERSION = File.read(File.join(File.dirname(__FILE__), '..', 'VERSION')).strip
8
+
9
+ # A Group contains some parameters
10
+ class Group
11
+ include Blockenspiel::DSL
12
+
13
+ # Initialize a new group
14
+ # @param name [Symbol] name of the group
15
+ def initialize name
16
+ @name = name
17
+ @long = name.to_s.gsub(/_/, '-')
18
+ @opts = {}
19
+ end
20
+
21
+ # Invoke Group in a block to configure
22
+ def invoke &blk
23
+ Blockenspiel.invoke blk, self
24
+ end
25
+
26
+ # Define a new option
27
+ # @param name [Symbol] name of the parameter. Underscores will be converted
28
+ # to hypens in long argument, and a short one will be generated unless you
29
+ # define one or you declare not to generate one.
30
+ # @param desc [#to_s] description of the argument.
31
+ # @param opts [Hash] additional options.
32
+ # @option opts [String] :long use this as a long option.
33
+ # @option opts [String] :short use this as a short option (must be 1
34
+ # character long to work)
35
+ # @option opts [Boolean] :no_short do not automatically generate a short
36
+ # option if set to true
37
+ # @option opts [Proc] :on invoke block after parsing argument
38
+ # @option opts [Symbol, Array<Symbol>, Array<Array<Symbol, String>>]
39
+ # :value describe additional values to the argument. Symbols declare the
40
+ # type of the value, and also the string displayed on help page using
41
+ # `[[Symbol, String], ...]` syntax.
42
+ # @option opts :default (nil) default value of the option if not specified
43
+ # on command line.
44
+ # @option opts :set (true) set valueless commands to this if specified.
45
+ # @return [void]
46
+ def opt name, desc, opts = {}
47
+ opts[:desc] = desc
48
+ opts[:long] ||= name.to_s.gsub(/_/, '-')
49
+ opts[:set] ||= true
50
+ @opts[name] = opts
51
+ end
52
+
53
+ # Defines the name used in command line. By default it's the normal name
54
+ # with underscores replaced with hyphens. It's not advised to change it.
55
+ # @overload long()
56
+ # Gets current command line name.
57
+ # @return [String] current name
58
+ # @overload long str
59
+ # Sets current command line name.
60
+ # @return [String] the newly set name
61
+ def long *args
62
+ if args.size == 0
63
+ @long
64
+ else
65
+ @long = args[0]
66
+ end
67
+ end
68
+
69
+ # @overload header
70
+ # Gets current header message.
71
+ # @return [String] header message
72
+ # @overload header str
73
+ # Sets a new header message
74
+ # @param str [String] new header message
75
+ # @return [String] header message
76
+ def header *args
77
+ if args.size == 0
78
+ if @header
79
+ "#{@header} (#{@long}):"
80
+ else
81
+ "#{@long}:"
82
+ end
83
+ else
84
+ @header = args[0]
85
+ end
86
+ end
87
+
88
+ # @private
89
+ attr_reader :opts
90
+ end
91
+
92
+ # Initialize an OptionGrouper. You can configure it using the yielded block.
93
+ def initialize &blk
94
+ @groups = {}
95
+ @on_version = :exit
96
+ @on_help = :exit
97
+ @on_invalid_parameter = :exit
98
+ @on_ambigous_parameter = :exit
99
+ @on_not_argument = :exit
100
+
101
+ run = Proc.new { show_help }
102
+ group do
103
+ header 'General options'
104
+ opt :help, 'Show this message', :short => 'h', :on => run
105
+ end
106
+
107
+ invoke &blk if blk
108
+ end
109
+
110
+ # Yield the configurator block again.
111
+ def invoke &blk
112
+ Blockenspiel.invoke blk, self
113
+ end
114
+
115
+ # What to do after printing version.
116
+ # * `:exit`: call exit to quit the program.
117
+ # * `:continue`: continue processing
118
+ # * `:stop`: stop processing
119
+ # * a Proc: call it
120
+ # @param res [Symbol, Proc] do this.
121
+ # @return [void]
122
+ def on_version res
123
+ @on_version = res
124
+ end
125
+
126
+ # What to do after printing help. Takes same options as \{#on_version}.
127
+ def on_help res
128
+ @on_help = res
129
+ end
130
+
131
+ # What to do after an invalid parameter. Takes the same options as
132
+ # \{#on_version}, except you can also use `:raise` to raise an exception.
133
+ def on_invalid_parameter to
134
+ @on_invalid_parameter = to
135
+ end
136
+
137
+ # What to do after an ambigous parameter. Takes same options as
138
+ # \{#on_invalid_parameter}.
139
+ def on_ambigous_parameter to
140
+ @on_ambigous_parameter = to
141
+ end
142
+
143
+ # What to do with a non-option string. Takes same options as
144
+ # \{#on_invalid_parameter}.
145
+ def on_not_argument to
146
+ @on_not_argument = to
147
+ end
148
+
149
+ # Sets the version of the program
150
+ # @param version [#to_s] program name and version.
151
+ # @return [void]
152
+ def version version
153
+ @version = version
154
+ run = Proc.new { show_version }
155
+ group :default do
156
+ opt :version, 'Show version', :on => run
157
+ end
158
+ end
159
+
160
+ # Sets header printed when help is requested.
161
+ # @param str [#to_s] header message
162
+ # @return [void]
163
+ def header str
164
+ @header = str
165
+ end
166
+
167
+ # Define a new group, if needed, then invoke it.
168
+ # @param name [Symbol] name of the group
169
+ # @yield a configuration block
170
+ # @return [Group] the defined group
171
+ def group name = :default, &blk
172
+ @groups[name] ||= Group.new(name)
173
+ @groups[name].invoke &blk
174
+ @groups[name]
175
+ end
176
+
177
+ # Parse command line arguments
178
+ # @param args [Array<String>] list of command line arguments.
179
+ # @return [Hash] \{#result}
180
+ def parse args = ARGV
181
+ init_parse
182
+
183
+ catch(:stop) do
184
+ while a = args.shift
185
+ if a == '--'
186
+ break
187
+ elsif a =~ /^--[^-]/
188
+ handle_long args, a
189
+ elsif a =~ /^-[^-]/
190
+ handle_short args, a
191
+ else
192
+ @ignored << a
193
+ on_error @on_not_argument, "`#{a}' is not an argument."
194
+ end
195
+ end
196
+ end
197
+ @result
198
+ ensure
199
+ args.unshift *@ignored
200
+ end
201
+
202
+ # Hash of parsed results.
203
+ # Hash keys are the group names, values are another hashes, where key is the
204
+ # option name and value is the parsed value.
205
+ # @return [Hash{Symbol => Hash{Symbol => Object}}]
206
+ attr_reader :result
207
+
208
+ # Stop argument processing (call it in callbacks)
209
+ def stop
210
+ throw :stop
211
+ end
212
+
213
+ private
214
+ # @private
215
+ # Marker for ambigous arguments without group
216
+ AMBIGOUS = -1
217
+
218
+ # Initialize parsing structures
219
+ def init_parse
220
+ # list of long arguments without group
221
+ @long_nogrp = {}
222
+ # long arguments in group:name format
223
+ @long_grp = {}
224
+ # short arguments
225
+ @shorts = {}
226
+ @result = {}
227
+ @group_names = {}
228
+ # list of ignored parameters
229
+ @ignored = []
230
+
231
+ @groups.each do |gname, grp|
232
+ @group_names[grp.long] = gname
233
+
234
+ @result[gname] = {}
235
+ grp.opts.each do |oname, opt|
236
+ long = opt[:long].to_s
237
+ if @long_nogrp[long] # if this group is already defined
238
+ unless @long_nogrp[long][0] == :default
239
+ # only mark as ambigous if the other item is not in the default group
240
+ ot = @long_nogrp[long][@long_nogrp[0] == AMBIGOUS ? 1..-1 : 0]
241
+ @long_nogrp[long] = [AMBIGOUS, ot, gname].flatten
242
+ end
243
+ else
244
+ @long_nogrp[long] = [gname, oname]
245
+ end
246
+ @long_grp["#{grp.long}:#{long}"] = [gname, oname]
247
+ short_coll gname, oname if @shorts[opt[:short]]
248
+ @shorts[opt[:short]] = [gname, oname] if opt[:short]
249
+
250
+ @result[gname][oname] = opt[:default]
251
+ end
252
+ end
253
+ # after gone through the list once, fill in unset short parameters
254
+ # automatically
255
+ @groups.each do |gname, grp|
256
+ grp.opts.each do |oname, opt|
257
+ next if opt[:short] || opt[:no_short]
258
+ opt[:long].each_char do |c|
259
+ next if c == "-"
260
+ if !@shorts[c]
261
+ @shorts[c] = [gname, oname]
262
+ break
263
+ elsif !@shorts[c.capitalize]
264
+ @shorts[c.capitalize] = [gname, oname]
265
+ break
266
+ end
267
+ end
268
+ end
269
+ end
270
+ end
271
+
272
+ # Called when two options try to set the same short argument.
273
+ def short_coll g, o
274
+ s = @groups[g].opts[o][:short]
275
+ a = @shorts[s]
276
+ raise RuntimeError, "`--#{a.join ':'}` and `--#{g}:#{o}' both tried to set short option `-#{s}'."
277
+ end
278
+
279
+ # Handle long parameters (`--xx`)
280
+ def handle_long args, a
281
+ arg = a[2..-1].split '=', 2
282
+
283
+ # Check first if we have group defined
284
+ grparg = arg[0].split ':', 2
285
+ if grparg.size == 2
286
+ unless @group_names[grparg[0]]
287
+ c = @group_names.select {|k,v| k.start_with? grparg[0] }
288
+ return invalid_parameter a if c.size == 0
289
+ return ambigous_parameter a, c.map {|k,v| "#{k}:..." } if c.size > 1
290
+ grparg[0] = c.keys.first
291
+ end
292
+ find = "#{grparg[0]}:#{grparg[1]}"
293
+ long = @long_grp
294
+ else
295
+ find = arg[0]
296
+ long = @long_nogrp
297
+ end
298
+
299
+ x = long[find]
300
+ unless x # handle abbreviated options
301
+ c = long.select {|k,v| k.start_with? find }
302
+ return invalid_parameter a if c.size == 0
303
+ return ambigous_parameter a, c.keys if c.size > 1
304
+ x = c.values.first
305
+ end
306
+ return ambigous_parameter a, x[1..-1].map {|m| "#{m}:#{arg[0]}"} if x[0] == AMBIGOUS
307
+ opt = @groups[x[0]].opts[x[1]]
308
+
309
+ handle_general args, x, opt, arg[1]
310
+ end
311
+
312
+ # Handle short options
313
+ def handle_short args, arg, a = arg[1..-1]
314
+ x = @shorts[a[0]]
315
+ return invalid_parameter "-#{a}" unless x
316
+ opt = @groups[x[0]].opts[x[1]]
317
+
318
+ # If it has a value, we take anything left in the argument as value
319
+ if opt[:value] && a.size > 1
320
+ handle_general args, x, opt, a[1..-1]
321
+ else
322
+ handle_general args, x, opt
323
+ handle_short args, arg, a[1..-1] if a.size > 1
324
+ end
325
+ end
326
+
327
+ def handle_general args, x, opt, value = nil
328
+ if opt[:value]
329
+ if opt[:value].is_a? Array
330
+ @result[x[0]][x[1]] = opt[:value].map do |v|
331
+ z = parse_param(value || args.shift, v)
332
+ value = nil
333
+ z
334
+ end
335
+ else
336
+ @result[x[0]][x[1]] = parse_param(value || args.shift, opt[:value])
337
+ end
338
+ else
339
+ @result[x[0]][x[1]] = opt[:set]
340
+ end
341
+ opt[:on].call @result[x[0]][x[1]] if opt[:on]
342
+ end
343
+
344
+ def invalid_parameter par
345
+ @ignored << par
346
+ on_error @on_invalid_parameter, "Unknown argument `#{par}'."
347
+ end
348
+
349
+ def ambigous_parameter par, cand
350
+ @ignored << par
351
+ msg = "Ambigous parameter `#{par}'.\nCandidates:\n"
352
+ msg << cand.map {|m| " --#{m}" }.join("\n")
353
+ on_error @on_ambigous_parameter, msg
354
+ end
355
+
356
+ def show_version
357
+ puts @version
358
+ on_general @on_version
359
+ end
360
+
361
+ def show_help
362
+ puts @version if @version
363
+ puts @header if @header
364
+
365
+ prompts = {}
366
+ max = 1
367
+ @groups.each do |gname, grp|
368
+ prompts[gname] = {}
369
+ grp.opts.each do |oname, opt|
370
+ short = @shorts.key [gname, oname]
371
+ msg = short ? " -#{short}, " : " "
372
+ if @long_nogrp[opt[:long]][0] != gname
373
+ msg << "--#{grp.long}:#{opt[:long]}"
374
+ else
375
+ msg << "--#{opt[:long]}"
376
+ end
377
+ if opt[:value].is_a? Array
378
+ opt[:value].each {|v| msg << param_display(v) }
379
+ elsif opt[:value]
380
+ msg << param_display(opt[:value])
381
+ end
382
+ prompts[gname][oname] = msg
383
+ max = msg.size if msg.size > max
384
+ end
385
+ end
386
+
387
+ @groups.each do |gname, grp|
388
+ puts grp.header
389
+ grp.opts.each do |oname, opt|
390
+ printf "%#{max}s: %s\n", prompts[gname][oname], opt[:desc]
391
+ end
392
+ puts
393
+ end
394
+
395
+ on_general @on_help
396
+ end
397
+
398
+ def on_general on
399
+ case on
400
+ when :exit
401
+ exit
402
+ when :continue
403
+ # nop
404
+ when :stop
405
+ stop
406
+ else
407
+ on.call
408
+ end
409
+ end
410
+
411
+ def on_error on, msg
412
+ case on
413
+ when :exit
414
+ $stderr.puts msg
415
+ $stderr.puts "\nRun with `--help' to get help."
416
+ exit
417
+ when :raise
418
+ raise RuntimeError, msg
419
+ when :continue
420
+ # nop
421
+ when :stop
422
+ stop
423
+ else
424
+ on.call msg
425
+ end
426
+ end
427
+
428
+ def parse_param param, type
429
+ type = type[0] if type.is_a? Array
430
+ if type.is_a? Proc
431
+ type.call param
432
+ elsif Kernel.respond_to? type.to_s.capitalize
433
+ Kernel.send type.to_s.capitalize, param
434
+ else
435
+ raise RuntimeError, "Unknown type #{type.inspect} specified"
436
+ end
437
+ end
438
+
439
+ def param_display type
440
+ str = if type.is_a? Array
441
+ type[1]
442
+ elsif type.is_a? Proc
443
+ "PARAM"
444
+ else
445
+ type.to_s.upcase
446
+ end
447
+ " <#{str}>"
448
+ end
449
+ end
@@ -0,0 +1,497 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe OptionGrouper do
4
+ before(:each) { $stdout = StringIO.new; $stderr = StringIO.new }
5
+ after(:each) { $stdout = STDOUT; $stderr = STDERR }
6
+ let(:out) { $stdout.string }
7
+ let(:err) { $stderr.string }
8
+
9
+ describe 'built-in commands' do
10
+ it 'should print a version number' do
11
+ OptionGrouper.new do
12
+ on_version :continue
13
+ version '0.0.0'
14
+ end.parse ['--version']
15
+ out.should == "0.0.0\n"
16
+ end
17
+
18
+ it 'should print a version number and exit' do
19
+ o = OptionGrouper.new do
20
+ version 'x.x.x'
21
+ end
22
+ o.parse []
23
+ lambda { o.parse ['--version'] }.should raise_error SystemExit
24
+ out.should == "x.x.x\n"
25
+
26
+ o.on_version :exit
27
+ lambda { o.parse ['--version'] }.should raise_error SystemExit
28
+ end
29
+
30
+ it 'should stop processing if told so' do
31
+ o = OptionGrouper.new do
32
+ on_version :stop
33
+ version '0.0.0'
34
+ group do
35
+ opt :foo, "Foo bar"
36
+ end
37
+ end
38
+ o.parse([])[:default][:foo].should be_nil
39
+ o.parse(['--foo'])[:default][:foo].should be_true
40
+ out.should be_empty
41
+ o.parse(%w(--version --foo))[:default][:foo].should be_nil
42
+ out.should == "0.0.0\n"
43
+ end
44
+
45
+ it 'should print a help message' do
46
+ o = OptionGrouper.new do
47
+ header "FooBar\n\n"
48
+ group do
49
+ opt :foo, "Do foo foo"
50
+ end
51
+ group :bar do
52
+ header "Bing"
53
+ opt :foo, "Foo collission"
54
+ opt :asd, "Asd asd", :value => :integer
55
+ opt :zee, "Zee", :value => [:string, :integer]
56
+ opt :fee, "Fee", :value => [[:integer, 'INT'], [:string, 'STR']]
57
+ end
58
+ group :asd do
59
+ long :foobar
60
+ opt :foo, "Foo n+1", :no_short => true
61
+ end
62
+ end
63
+ lambda { o.parse ['--help'] }.should raise_error SystemExit
64
+ out.should == <<EOS
65
+ FooBar
66
+
67
+ General options (default):
68
+ -h, --help: Show this message
69
+ -f, --foo: Do foo foo
70
+
71
+ Bing (bar):
72
+ -F, --bar:foo: Foo collission
73
+ -a, --asd <INTEGER>: Asd asd
74
+ -z, --zee <STRING> <INTEGER>: Zee
75
+ -e, --fee <INT> <STR>: Fee
76
+
77
+ foobar:
78
+ --foobar:foo: Foo n+1
79
+
80
+ EOS
81
+ end
82
+ end
83
+
84
+ describe 'single-group long parameters' do
85
+ it 'should parse more valueless parameters' do
86
+ res = OptionGrouper.new do
87
+ group do
88
+ opt :foo, "Foo"
89
+ opt :bar, "Bar", :default => 3, :set => 5
90
+ opt :asd, "Asd"
91
+ opt :def, "Def", :set => :def
92
+ end
93
+ end.parse(%w(--asd --def))[:default]
94
+ res[:foo].should be_nil
95
+ res[:bar].should == 3
96
+ res[:asd].should be_true
97
+ res[:def].should == :def
98
+ end
99
+
100
+ it 'should parse string arguments' do
101
+ res = OptionGrouper.new do
102
+ group do
103
+ opt :foo, "Foo", :value => :string
104
+ opt :bar, "Bar", :value => :string, :default => "bar"
105
+ opt :asd, "Asd", :value => :string, :default => "asd"
106
+ end
107
+ end.parse(%w(--foo foo --asd gher))[:default]
108
+ res[:foo].should == "foo"
109
+ res[:bar].should == "bar"
110
+ res[:asd].should == "gher"
111
+ end
112
+
113
+ it 'should parse string arguments using =' do
114
+ res = OptionGrouper.new do
115
+ group do
116
+ opt :foo, "Foo", :value => :string
117
+ opt :bar, "Bar", :value => :string, :default => "bar"
118
+ opt :asd, "Asd", :value => :string, :default => "asd"
119
+ end
120
+ end.parse(%w(--foo=foo --asd=gher))[:default]
121
+ res[:foo].should == "foo"
122
+ res[:bar].should == "bar"
123
+ res[:asd].should == "gher"
124
+ end
125
+
126
+ it 'should parse integer, float arguments' do
127
+ res = OptionGrouper.new do
128
+ group do
129
+ opt :int1, "int", :value => :integer
130
+ opt :int2, "int", :value => :integer, :default => 3
131
+ opt :flt1, "float", :value => :float, :default => 4.3
132
+ opt :flt2, "float", :value => :float, :default => 2.2
133
+ end
134
+ end.parse(%w(--int2=99 --flt1 9.8))[:default]
135
+ res[:int1].should be_nil
136
+ res[:int2].should == 99
137
+ res[:flt1].should be_within(0.0001).of(9.8)
138
+ res[:flt2].should be_within(0.0001).of(2.2)
139
+ end
140
+
141
+ it 'should parse using lambdas' do
142
+ res = OptionGrouper.new do
143
+ group do
144
+ opt :a, "a", :value => lambda {|s| Integer(s) + 5 }
145
+ opt :b, "b", :value => lambda {|s| Float(s) / 2 }
146
+ end
147
+ end.parse(%w(--a 1 --b=5))[:default]
148
+ res[:a].should == 6
149
+ res[:b].should be_within(0.0001).of(2.5)
150
+ end
151
+
152
+ it 'should parse multi value arguments' do
153
+ res = OptionGrouper.new do
154
+ group do
155
+ opt :a, "a", :value => [:string, :integer], :default => ["a", 3]
156
+ opt :b, "b", :value => [[:integer, "INT"], :string], :default => [7, "b"]
157
+ opt :c, "c", :value => [:integer, :integer, :string]
158
+ end
159
+ end.parse(%w(--b=2 foo --c 2 5 foo))[:default]
160
+ res[:a].should == ["a", 3]
161
+ res[:b].should == [2, "foo"]
162
+ res[:c].should == [2, 5, 'foo']
163
+ end
164
+ end
165
+
166
+ describe 'single-group short options' do
167
+ it 'should use supplied single arguments' do
168
+ res = OptionGrouper.new do
169
+ group do
170
+ opt :foo, "foo", :short => 'f'
171
+ opt :bar, "bar", :short => 'b'
172
+ opt :asd, "asd", :short => 'a'
173
+ end
174
+ end.parse(%w(-f -ba))[:default]
175
+ res[:foo].should be_true
176
+ res[:bar].should be_true
177
+ res[:asd].should be_true
178
+ end
179
+
180
+ it 'should automatically generate single arguments' do
181
+ res = OptionGrouper.new do
182
+ group do
183
+ opt :foo, "foo"
184
+ opt :bar, "bar"
185
+ end
186
+ end.parse(%w(-b))[:default]
187
+ res[:foo].should be_nil
188
+ res[:bar].should be_true
189
+ end
190
+
191
+ it 'should work with multiple options starting the same' do
192
+ res = OptionGrouper.new do
193
+ group do
194
+ opt :foo1, "foo1"
195
+ opt :foo2, "foo2"
196
+ opt :foo3, "foo3"
197
+ opt :foo4, "foo4"
198
+ opt :foo5, "foo5"
199
+ end
200
+ end.parse(['-Fo5'])[:default]
201
+ res[:foo1].should be_nil
202
+ res[:foo2].should be_true
203
+ res[:foo3].should be_true
204
+ res[:foo4].should be_nil
205
+ res[:foo5].should be_true
206
+ end
207
+
208
+ it 'should parse values' do
209
+ res = OptionGrouper.new do
210
+ group do
211
+ opt :foo, "foo", :value => :integer
212
+ opt :bar, "bar", :value => :string
213
+ opt :multi, "multi", :value => [:integer, :string]
214
+ end
215
+ end.parse(%w(-f 6 -bzizi -m3 bar))[:default]
216
+ res[:foo].should == 6
217
+ res[:bar].should == "zizi"
218
+ res[:multi].should == [3, "bar"]
219
+ end
220
+ end
221
+
222
+ describe 'single-group error handling' do
223
+ it 'should handle invalid commands' do
224
+ lambda { OptionGrouper.new.parse(['--foo']) }.should raise_error SystemExit
225
+ err.should == "Unknown argument `--foo'.\n\nRun with `--help' to get help.\n"
226
+ end
227
+
228
+ it 'should handle invalid short commands' do
229
+ lambda { OptionGrouper.new.parse(['-f']) }.should raise_error SystemExit
230
+ err.should == "Unknown argument `-f'.\n\nRun with `--help' to get help.\n"
231
+ end
232
+
233
+ it 'should ignore invalid commands' do
234
+ args = %w(--xd -x -f7 --bar)
235
+ res = OptionGrouper.new do
236
+ on_invalid_parameter :continue
237
+ group do
238
+ opt :foo, "foo", :value => :integer
239
+ end
240
+ end.parse(args)[:default]
241
+ res[:foo].should == 7
242
+ args.should == %w(--xd -x --bar)
243
+ end
244
+
245
+ it 'should stop processing at an invalid command' do
246
+ args = %w(--foo --xd --bar)
247
+ res = OptionGrouper.new do
248
+ on_invalid_parameter :stop
249
+ group do
250
+ opt :foo, "foo"
251
+ opt :bar, "bar"
252
+ end
253
+ end.parse(args)[:default]
254
+ res[:foo].should be_true
255
+ res[:bar].should be_nil
256
+ args.should == %w(--xd --bar)
257
+ end
258
+
259
+ it 'should raise an error on invalid commands' do
260
+ o = OptionGrouper.new { on_invalid_parameter :raise }
261
+ lambda { o.parse(['--foo']) }.should raise_error RuntimeError, "Unknown argument `--foo'."
262
+ end
263
+
264
+ it 'should call lambda on invalid command' do
265
+ check = 0
266
+ OptionGrouper.new do
267
+ on_invalid_parameter lambda {|msg| check += 1 }
268
+ end.parse ['--foo']
269
+ check.should == 1
270
+ end
271
+ end
272
+
273
+ describe 'single-group abbreviation' do
274
+ it 'should acceppt non-ambigous abbrevations' do
275
+ res = OptionGrouper.new do
276
+ group do
277
+ opt :foo_bar, "FooBar"
278
+ opt :asd, "Asd"
279
+ end
280
+ end.parse(['--foo'])[:default]
281
+ res[:foo_bar].should be_true
282
+ res[:asd].should be_nil
283
+ end
284
+
285
+ it 'should bail out on ambigous parameters' do
286
+ o = OptionGrouper.new do
287
+ group do
288
+ opt :foo_bar, "FooBar"
289
+ opt :foo_baz, "FooBaz"
290
+ end
291
+ end
292
+ l = lambda { o.parse ['--foo'] }
293
+ l.should raise_error SystemExit
294
+ msg = <<EOS
295
+ Ambigous parameter `--foo'.
296
+ Candidates:
297
+ --foo-bar
298
+ --foo-baz
299
+ EOS
300
+ err.should == msg + "\nRun with `--help' to get help.\n"
301
+
302
+ o.on_ambigous_parameter :raise
303
+ l.should raise_error RuntimeError, msg.strip
304
+ end
305
+
306
+ it 'should ignore ambigous parameters (why?)' do
307
+ res = OptionGrouper.new do
308
+ on_ambigous_parameter :continue
309
+ group do
310
+ opt :foo_bar, "FooBar"
311
+ opt :foo_baz, "FooBaz"
312
+ opt :bar, "bar"
313
+ end
314
+ end.parse(%w(--foo --bar))[:default]
315
+ res[:foo_bar].should be_nil
316
+ res[:foo_baz].should be_nil
317
+ res[:bar].should be_true
318
+ end
319
+
320
+ it 'should stop on ambigous parameters' do
321
+ res = OptionGrouper.new do
322
+ on_ambigous_parameter :stop
323
+ group do
324
+ opt :foo_bar, "FooBar"
325
+ opt :foo_baz, "FooBaz"
326
+ opt :bar, "bar"
327
+ end
328
+ end.parse(%w(--foo --bar))[:default]
329
+ res[:foo_bar].should be_nil
330
+ res[:foo_baz].should be_nil
331
+ res[:bar].should be_nil
332
+ end
333
+ end
334
+
335
+ describe 'multi group' do
336
+ it 'should work with no collisions' do
337
+ res = OptionGrouper.new do
338
+ group :a do
339
+ opt :a, "a"
340
+ opt :a_b, "a b"
341
+ end
342
+ group :b do
343
+ opt :x, "x"
344
+ opt :yize, "y"
345
+ end
346
+ end.parse %w(--a-b -x --yize)
347
+ res[:a][:a].should be_nil
348
+ res[:a][:a_b].should be_true
349
+ res[:b][:x].should be_true
350
+ res[:b][:yize].should be_true
351
+ end
352
+
353
+ it 'should work with no collision full name qualifying' do
354
+ res = OptionGrouper.new do
355
+ group :aaa do
356
+ opt :a, "a"
357
+ opt :a_b, "a b"
358
+ end
359
+ group :bbb do
360
+ long "ccc"
361
+ opt :x, "x"
362
+ opt :yize, "y"
363
+ end
364
+ end.parse %w(--aaa:a-b --ccc:yize)
365
+ res[:aaa][:a].should be_nil
366
+ res[:aaa][:a_b].should be_true
367
+ res[:bbb][:x].should be_nil
368
+ res[:bbb][:yize].should be_true
369
+ end
370
+
371
+ it 'should work with collissions also' do
372
+ res = OptionGrouper.new do
373
+ group :abc do
374
+ opt :foo, "foo"
375
+ opt :bar, "bar"
376
+ end
377
+ group :def do
378
+ opt :foo, "foo"
379
+ opt :baz, "baz"
380
+ end
381
+ end.parse %w(--abc:foo --bar --baz)
382
+ res[:abc][:foo].should be_true
383
+ res[:abc][:bar].should be_true
384
+ res[:def][:foo].should be_nil
385
+ res[:def][:baz].should be_true
386
+ end
387
+
388
+ it 'should handle ambigous parameters' do
389
+ o = OptionGrouper.new do
390
+ group :abc do
391
+ opt :foo, "foo"
392
+ opt :bar, "bar"
393
+ end
394
+ group :def do
395
+ opt :foo, "foo"
396
+ opt :baz, "baz"
397
+ end
398
+ end
399
+ lambda { o.parse ['--foo'] }.should raise_error SystemExit
400
+ msg = <<EOS
401
+ Ambigous parameter `--foo'.
402
+ Candidates:
403
+ --abc:foo
404
+ --def:foo
405
+ EOS
406
+ err.should == msg + "\nRun with `--help' to get help.\n"
407
+
408
+ o.on_ambigous_parameter :raise
409
+ lambda { o.parse ['--foo']}.should raise_error RuntimeError, msg.strip
410
+
411
+ o.on_ambigous_parameter :continue
412
+ res = o.parse %w(--foo --bar)
413
+ res[:abc][:foo].should be_nil
414
+ res[:abc][:bar].should be_true
415
+ res[:def][:foo].should be_nil
416
+ res[:def][:baz].should be_nil
417
+
418
+ o.on_ambigous_parameter :stop
419
+ res = o.parse %w(--foo --bar)
420
+ res[:abc][:foo].should be_nil
421
+ res[:abc][:bar].should be_nil
422
+ res[:def][:foo].should be_nil
423
+ res[:def][:baz].should be_nil
424
+ end
425
+
426
+ it 'should allow abbreviated groups' do
427
+ o = OptionGrouper.new do
428
+ group :abcd do
429
+ long "abgh"
430
+ opt :opt1, "opt1"
431
+ end
432
+ group :abef do
433
+ opt :opt2, "opt2"
434
+ end
435
+ end
436
+ res = o.parse %w(--abg:opt1)
437
+ res[:abcd][:opt1].should be_true
438
+ res[:abef][:opt2].should be_nil
439
+
440
+ lambda { o.parse %w(--ab:opt1) }.should raise_error SystemExit
441
+ err.should == <<EOS
442
+ Ambigous parameter `--ab:opt1'.
443
+ Candidates:
444
+ --abgh:...
445
+ --abef:...
446
+
447
+ Run with `--help' to get help.
448
+ EOS
449
+ end
450
+ end
451
+
452
+ describe 'not an option' do
453
+ it 'should bail out on not an option' do
454
+ lambda { OptionGrouper.new.parse %w(foo --bar) }.should raise_error SystemExit
455
+ err.should == "`foo' is not an argument.\n\nRun with `--help' to get help.\n"
456
+ end
457
+
458
+ it 'should throw an exception and stop' do
459
+ o = OptionGrouper.new do
460
+ on_not_argument :raise
461
+ group do
462
+ opt :foo, "foo"
463
+ opt :bar, "bar"
464
+ end
465
+ end
466
+ args = %w(--foo foo --bar)
467
+
468
+ lambda { o.parse args }.should raise_error RuntimeError, "`foo' is not an argument."
469
+ res = o.result[:default]
470
+ res[:foo].should be_true
471
+ res[:bar].should be_nil
472
+ args.should == %w(foo --bar)
473
+ end
474
+
475
+ it 'should ignore or stop' do
476
+ o = OptionGrouper.new do
477
+ on_not_argument :continue
478
+ group do
479
+ opt :foo, "foo"
480
+ opt :bar, "bar"
481
+ end
482
+ end
483
+ args = %w(--bar foo --foo)
484
+ res = o.parse(args)[:default]
485
+ res[:foo].should be_true
486
+ res[:bar].should be_true
487
+ args.should == ['foo']
488
+
489
+ o.on_not_argument :stop
490
+ args = %w(--bar foo --foo)
491
+ res = o.parse(args)[:default]
492
+ res[:foo].should be_nil
493
+ res[:bar].should be_true
494
+ args.should == %w(foo --foo)
495
+ end
496
+ end
497
+ end
@@ -0,0 +1,13 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'optiongrouper'
5
+ require 'stringio'
6
+
7
+ # Requires supporting files with custom matchers and macros, etc,
8
+ # in ./support/ and its subdirectories.
9
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
10
+
11
+ RSpec.configure do |config|
12
+
13
+ end
metadata ADDED
@@ -0,0 +1,147 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: optiongrouper
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 0
9
+ version: 0.0.0
10
+ platform: ruby
11
+ authors:
12
+ - "K\xC5\x91v\xC3\xA1g\xC3\xB3, Zolt\xC3\xA1n"
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-12-21 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: blockenspiel
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ - 4
31
+ - 0
32
+ version: 0.4.0
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: rspec
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 2
45
+ - 1
46
+ - 0
47
+ version: 2.1.0
48
+ type: :development
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: yard
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ~>
57
+ - !ruby/object:Gem::Version
58
+ segments:
59
+ - 0
60
+ - 6
61
+ - 0
62
+ version: 0.6.0
63
+ type: :development
64
+ version_requirements: *id003
65
+ - !ruby/object:Gem::Dependency
66
+ name: jeweler
67
+ prerelease: false
68
+ requirement: &id004 !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ~>
72
+ - !ruby/object:Gem::Version
73
+ segments:
74
+ - 1
75
+ - 5
76
+ - 1
77
+ version: 1.5.1
78
+ type: :development
79
+ version_requirements: *id004
80
+ - !ruby/object:Gem::Dependency
81
+ name: rcov
82
+ prerelease: false
83
+ requirement: &id005 !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ segments:
89
+ - 0
90
+ version: "0"
91
+ type: :development
92
+ version_requirements: *id005
93
+ description: Command line option parsing library with some fancy features
94
+ email: DirtY.iCE.hu@gmail.com
95
+ executables: []
96
+
97
+ extensions: []
98
+
99
+ extra_rdoc_files:
100
+ - LICENSE.txt
101
+ - README.md
102
+ files:
103
+ - .document
104
+ - .rspec
105
+ - .yardopts
106
+ - LICENSE.txt
107
+ - README.md
108
+ - Rakefile
109
+ - VERSION
110
+ - lib/optiongrouper.rb
111
+ - spec/optiongrouper_spec.rb
112
+ - spec/spec_helper.rb
113
+ has_rdoc: true
114
+ homepage: http://github.com/DirtYiCE/optiongrouper
115
+ licenses:
116
+ - MIT
117
+ post_install_message:
118
+ rdoc_options: []
119
+
120
+ require_paths:
121
+ - lib
122
+ required_ruby_version: !ruby/object:Gem::Requirement
123
+ none: false
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ segments:
128
+ - 0
129
+ version: "0"
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ none: false
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ segments:
136
+ - 0
137
+ version: "0"
138
+ requirements: []
139
+
140
+ rubyforge_project:
141
+ rubygems_version: 1.3.7
142
+ signing_key:
143
+ specification_version: 3
144
+ summary: Command line option parsing library
145
+ test_files:
146
+ - spec/optiongrouper_spec.rb
147
+ - spec/spec_helper.rb