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 +5 -0
- data/.rspec +1 -0
- data/.yardopts +11 -0
- data/LICENSE.txt +20 -0
- data/README.md +57 -0
- data/Rakefile +42 -0
- data/VERSION +1 -0
- data/lib/optiongrouper.rb +449 -0
- data/spec/optiongrouper_spec.rb +497 -0
- data/spec/spec_helper.rb +13 -0
- metadata +147 -0
data/.document
ADDED
data/.rspec
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--color
|
data/.yardopts
ADDED
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
|
data/spec/spec_helper.rb
ADDED
|
@@ -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
|