optiongrouper 0.0.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/.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