slop 1.4.1 → 1.5.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/README.md +38 -0
- data/lib/slop.rb +112 -34
- data/lib/slop/option.rb +2 -0
- data/test/commands_test.rb +72 -0
- data/test/option_test.rb +2 -0
- data/test/slop_test.rb +21 -10
- metadata +4 -2
data/README.md
CHANGED
@@ -315,6 +315,44 @@ yields:
|
|
315
315
|
-n, --name Your name
|
316
316
|
-h, --help Print this help message
|
317
317
|
|
318
|
+
Commands
|
319
|
+
--------
|
320
|
+
|
321
|
+
Slop allows you to nest more instances of Slop inside of `commands`. These
|
322
|
+
instances will then be used to parse arguments if they're called upon.
|
323
|
+
|
324
|
+
Slop will use the first argument in the list of items passed to `parse` to
|
325
|
+
check if it is a `command`.
|
326
|
+
|
327
|
+
Slop.parse ['foo', '--bar', 'baz']
|
328
|
+
|
329
|
+
Slop will look to see if the `foo` command exists, and if it does, it'll pass
|
330
|
+
the options `['--bar', 'baz']` to the instance of Slop that belongs to `foo`.
|
331
|
+
Here's how commands might look:
|
332
|
+
|
333
|
+
opts = Slop.new do
|
334
|
+
command :foo do
|
335
|
+
on :b, :bar, 'something', true
|
336
|
+
end
|
337
|
+
|
338
|
+
command :clean do
|
339
|
+
on :v, :verbose, do
|
340
|
+
puts 'Enabled verbose mode for clean'
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
# non-command specific options
|
345
|
+
on :v, :version do
|
346
|
+
puts 'version 1'
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
* Run with `run.rb -v`
|
351
|
+
* Output: `version 1`
|
352
|
+
|
353
|
+
* Run with: `run.rb clean -v`
|
354
|
+
* Output: `Enabled verbose mode for clean`
|
355
|
+
|
318
356
|
Woah woah, why you hating on OptionParser?
|
319
357
|
------------------------------------------
|
320
358
|
|
data/lib/slop.rb
CHANGED
@@ -16,7 +16,7 @@ class Slop
|
|
16
16
|
class InvalidOptionError < RuntimeError; end
|
17
17
|
|
18
18
|
# @return [String] The current version string
|
19
|
-
VERSION = '1.
|
19
|
+
VERSION = '1.5.0'
|
20
20
|
|
21
21
|
# Parses the items from a CLI format into a friendly object.
|
22
22
|
#
|
@@ -44,6 +44,9 @@ class Slop
|
|
44
44
|
# @return [Options]
|
45
45
|
attr_reader :options
|
46
46
|
|
47
|
+
# @return [Hash]
|
48
|
+
attr_reader :commands
|
49
|
+
|
47
50
|
attr_writer :banner
|
48
51
|
attr_accessor :longest_flag
|
49
52
|
|
@@ -54,6 +57,9 @@ class Slop
|
|
54
57
|
# @option opts [Boolean] :multiple_switches Allows `-abc` to be processed
|
55
58
|
# as the options 'a', 'b', 'c' and will force their argument values to
|
56
59
|
# true. By default Slop with parse this as 'a' with the argument 'bc'
|
60
|
+
# @option opts [String] :banner The banner text used for the help
|
61
|
+
# @option opts [Proc, #call] :on_empty Any object that respondes to `call`
|
62
|
+
# which is executed when Slop has no items to parse
|
57
63
|
def initialize(*opts, &block)
|
58
64
|
sloptions = {}
|
59
65
|
sloptions.merge! opts.pop if opts.last.is_a? Hash
|
@@ -61,10 +67,16 @@ class Slop
|
|
61
67
|
opts.each { |o| sloptions[o] = true }
|
62
68
|
|
63
69
|
@options = Options.new
|
70
|
+
@commands = {}
|
71
|
+
|
64
72
|
@longest_flag = 0
|
65
|
-
@strict = sloptions[:strict]
|
66
73
|
@invalid_options = []
|
74
|
+
|
75
|
+
@banner ||= sloptions[:banner]
|
76
|
+
@strict = sloptions[:strict]
|
67
77
|
@multiple_switches = sloptions[:multiple_switches]
|
78
|
+
@on_empty = sloptions[:on_empty]
|
79
|
+
@sloptions = sloptions
|
68
80
|
|
69
81
|
if block_given?
|
70
82
|
block.arity == 1 ? yield(self) : instance_eval(&block)
|
@@ -106,9 +118,8 @@ class Slop
|
|
106
118
|
end
|
107
119
|
|
108
120
|
# Enumerable interface
|
109
|
-
def each
|
110
|
-
|
111
|
-
@options.each { |option| yield option }
|
121
|
+
def each(&block)
|
122
|
+
@options.each(&block)
|
112
123
|
end
|
113
124
|
|
114
125
|
# Return the value of an option via the subscript operator.
|
@@ -119,7 +130,7 @@ class Slop
|
|
119
130
|
# @return [Object] Returns the value associated with that option.
|
120
131
|
def [](key)
|
121
132
|
option = @options[key]
|
122
|
-
option.argument_value
|
133
|
+
option ? option.argument_value : @commands[key]
|
123
134
|
end
|
124
135
|
|
125
136
|
# Specify an option with a short or long version, description and type.
|
@@ -155,6 +166,50 @@ class Slop
|
|
155
166
|
alias :opt :option
|
156
167
|
alias :on :option
|
157
168
|
|
169
|
+
|
170
|
+
# Namespace options depending on what command is executed
|
171
|
+
#
|
172
|
+
# @param [Symbol, String] label
|
173
|
+
# @param [Hash] options
|
174
|
+
# @example
|
175
|
+
# opts = Slop.new do
|
176
|
+
# command :create do
|
177
|
+
# on :v, :verbose
|
178
|
+
# end
|
179
|
+
# end
|
180
|
+
#
|
181
|
+
# # ARGV is `create -v`
|
182
|
+
# opts.commands[:create].verbose? #=> true
|
183
|
+
# @return [Slop] a new instance of Slop namespaced to +label+
|
184
|
+
def command(label, options={}, &block)
|
185
|
+
if @commands[label]
|
186
|
+
raise ArgumentError, "command `#{label}` already exists"
|
187
|
+
end
|
188
|
+
|
189
|
+
options = @sloptions.merge(options)
|
190
|
+
slop = Slop.new(options)
|
191
|
+
@commands[label] = slop
|
192
|
+
|
193
|
+
if block_given?
|
194
|
+
block.arity == 1 ? yield(slop) : slop.instance_eval(&block)
|
195
|
+
end
|
196
|
+
|
197
|
+
slop
|
198
|
+
end
|
199
|
+
|
200
|
+
# Add an object to be called when Slop has no values to parse
|
201
|
+
#
|
202
|
+
# @param [Object, nil] proc The object (which can be anything
|
203
|
+
# responding to :call)
|
204
|
+
# @example
|
205
|
+
# Slop.parse do
|
206
|
+
# on_empty { puts 'No argument given!' }
|
207
|
+
# end
|
208
|
+
def on_empty(obj=nil, &block)
|
209
|
+
@on_empty ||= (obj || block)
|
210
|
+
end
|
211
|
+
alias :on_empty= :on_empty
|
212
|
+
|
158
213
|
# Returns the parsed list into a option/value hash.
|
159
214
|
#
|
160
215
|
# @example
|
@@ -197,21 +252,32 @@ class Slop
|
|
197
252
|
|
198
253
|
private
|
199
254
|
|
200
|
-
|
201
|
-
|
202
|
-
options = items
|
203
|
-
items = ARGV
|
204
|
-
end
|
255
|
+
class << self
|
256
|
+
private
|
205
257
|
|
206
|
-
|
207
|
-
|
208
|
-
|
258
|
+
def initialize_and_parse(items, delete, options, &block)
|
259
|
+
if items.is_a?(Hash) && options.empty?
|
260
|
+
options = items
|
261
|
+
items = ARGV
|
262
|
+
end
|
263
|
+
|
264
|
+
slop = new(options, &block)
|
265
|
+
delete ? slop.parse!(items) : slop.parse(items)
|
266
|
+
slop
|
267
|
+
end
|
209
268
|
end
|
210
269
|
|
211
270
|
def parse_items(items, delete=false, &block)
|
271
|
+
if items.empty? && @on_empty.respond_to?(:call)
|
272
|
+
@on_empty.call self
|
273
|
+
return
|
274
|
+
end
|
275
|
+
|
276
|
+
return if execute_command(items, delete)
|
277
|
+
|
212
278
|
trash = []
|
213
279
|
|
214
|
-
items.
|
280
|
+
items.each_with_index do |item, index|
|
215
281
|
item = item.to_s
|
216
282
|
flag = item.sub(/^--?/, '')
|
217
283
|
option = @options[flag]
|
@@ -230,7 +296,8 @@ class Slop
|
|
230
296
|
option = @options[$1]
|
231
297
|
argument = $2
|
232
298
|
when /\A--no-(.+)\z/
|
233
|
-
option
|
299
|
+
option = @options[$1]
|
300
|
+
option.force_argument_value(false) if option
|
234
301
|
end
|
235
302
|
end
|
236
303
|
|
@@ -240,47 +307,47 @@ class Slop
|
|
240
307
|
option.argument_value = true
|
241
308
|
|
242
309
|
if option.expects_argument? || option.accepts_optional_argument?
|
243
|
-
argument ||= items.at(
|
244
|
-
check_valid_argument(option, argument)
|
310
|
+
argument ||= items.at(index + 1)
|
311
|
+
check_valid_argument!(option, argument)
|
245
312
|
trash << argument
|
246
313
|
|
247
314
|
if argument
|
248
|
-
check_matching_argument(option, argument)
|
315
|
+
check_matching_argument!(option, argument)
|
249
316
|
option.argument_value = argument
|
250
317
|
option.callback.call option.argument_value if option.callback
|
251
318
|
else
|
252
319
|
option.argument_value = nil
|
253
|
-
check_optional_argument(option, flag)
|
320
|
+
check_optional_argument!(option, flag)
|
254
321
|
end
|
255
322
|
elsif option.callback
|
256
323
|
option.callback.call nil
|
257
324
|
end
|
258
325
|
else
|
259
|
-
check_invalid_option(item, flag)
|
326
|
+
check_invalid_option!(item, flag)
|
260
327
|
block.call(item) if block_given? && !trash.include?(item)
|
261
328
|
end
|
262
329
|
end
|
263
330
|
|
264
331
|
items.delete_if { |item| trash.include? item } if delete
|
265
|
-
raise_if_invalid_options
|
332
|
+
raise_if_invalid_options!
|
266
333
|
items
|
267
334
|
end
|
268
335
|
|
269
|
-
def check_valid_argument(option, argument)
|
336
|
+
def check_valid_argument!(option, argument)
|
270
337
|
if !option.accepts_optional_argument? && argument =~ /\A--?.+\z/
|
271
338
|
raise MissingArgumentError,
|
272
339
|
"'#{option.key}' expects an argument, none given"
|
273
340
|
end
|
274
341
|
end
|
275
342
|
|
276
|
-
def check_matching_argument(option, argument)
|
343
|
+
def check_matching_argument!(option, argument)
|
277
344
|
if option.match && !argument.match(option.match)
|
278
345
|
raise InvalidArgumentError,
|
279
346
|
"'#{argument}' does not match #{option.match.inspect}"
|
280
347
|
end
|
281
348
|
end
|
282
349
|
|
283
|
-
def check_optional_argument(option, flag)
|
350
|
+
def check_optional_argument!(option, flag)
|
284
351
|
if option.accepts_optional_argument?
|
285
352
|
option.callback.call nil if option.callback
|
286
353
|
else
|
@@ -289,10 +356,18 @@ class Slop
|
|
289
356
|
end
|
290
357
|
end
|
291
358
|
|
292
|
-
def check_invalid_option(item, flag)
|
359
|
+
def check_invalid_option!(item, flag)
|
293
360
|
@invalid_options << flag if item[/\A--?/] && @strict
|
294
361
|
end
|
295
362
|
|
363
|
+
def raise_if_invalid_options!
|
364
|
+
return if !@strict || @invalid_options.empty?
|
365
|
+
message = "Unknown option"
|
366
|
+
message << 's' if @invalid_options.size > 1
|
367
|
+
message << ' -- ' << @invalid_options.map { |o| "'#{o}'" }.join(', ')
|
368
|
+
raise InvalidOptionError, message
|
369
|
+
end
|
370
|
+
|
296
371
|
def enable_multiple_switches(item)
|
297
372
|
item[1..-1].split('').each do |switch|
|
298
373
|
if option = @options[switch]
|
@@ -310,6 +385,17 @@ class Slop
|
|
310
385
|
end
|
311
386
|
end
|
312
387
|
|
388
|
+
def execute_command(items, delete)
|
389
|
+
command = items[0]
|
390
|
+
command = @commands.keys.find { |cmd| cmd.to_s == command.to_s }
|
391
|
+
if @commands.key?(command)
|
392
|
+
items.shift
|
393
|
+
opts = @commands[command]
|
394
|
+
delete ? opts.parse!(items) : opts.parse(items)
|
395
|
+
true
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
313
399
|
def clean_options(args)
|
314
400
|
options = []
|
315
401
|
|
@@ -332,12 +418,4 @@ class Slop
|
|
332
418
|
options.push args.first.respond_to?(:to_sym) ? args.shift : nil
|
333
419
|
options.push args.shift ? true : false # force true/false
|
334
420
|
end
|
335
|
-
|
336
|
-
def raise_if_invalid_options
|
337
|
-
return if !@strict || @invalid_options.empty?
|
338
|
-
message = "Unknown option"
|
339
|
-
message << 's' if @invalid_options.size > 1
|
340
|
-
message << ' -- ' << @invalid_options.map { |o| "'#{o}'" }.join(', ')
|
341
|
-
raise InvalidOptionError, message
|
342
|
-
end
|
343
421
|
end
|
data/lib/slop/option.rb
CHANGED
@@ -0,0 +1,72 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
class CommandsTest < TestCase
|
4
|
+
test 'creating commands' do
|
5
|
+
slop = Slop.new do
|
6
|
+
command :foo do on :f, :foo, 'foo option' end
|
7
|
+
command :bar do on :f, :foo; on :b, :bar, true end
|
8
|
+
end
|
9
|
+
|
10
|
+
slop.commands.each_value do |command|
|
11
|
+
assert_kind_of Slop, command
|
12
|
+
end
|
13
|
+
|
14
|
+
assert 'foo option', slop.commands[:foo].options[:foo].description
|
15
|
+
|
16
|
+
slop.parse %w/bar --bar baz/
|
17
|
+
assert 'baz', slop.commands[:bar][:bar]
|
18
|
+
assert_nil slop.commands['bar']
|
19
|
+
end
|
20
|
+
|
21
|
+
test 'repeating existing commands' do
|
22
|
+
slop = Slop.new
|
23
|
+
assert slop.command :foo
|
24
|
+
assert_raises(ArgumentError) { slop.command :foo }
|
25
|
+
end
|
26
|
+
|
27
|
+
test 'commands inheriting options' do
|
28
|
+
slop = Slop.new :strict do
|
29
|
+
command :foo do end
|
30
|
+
end
|
31
|
+
assert slop.commands[:foo].instance_variable_get(:@strict)
|
32
|
+
end
|
33
|
+
|
34
|
+
test 'commands setting options' do
|
35
|
+
slop = Slop.new :strict => false do
|
36
|
+
command :foo, :strict => true do end
|
37
|
+
end
|
38
|
+
assert slop.commands[:foo].instance_variable_get(:@strict)
|
39
|
+
end
|
40
|
+
|
41
|
+
test 'inception' do
|
42
|
+
slop = Slop.new do
|
43
|
+
command(:foo) { command(:bar) { command(:baz) { on :f, 'D:' } } }
|
44
|
+
end
|
45
|
+
desc = slop.commands[:foo].commands[:bar].commands[:baz].options[:f].description
|
46
|
+
assert_equal 'D:', desc
|
47
|
+
end
|
48
|
+
|
49
|
+
test 'commands with banners' do
|
50
|
+
slop = Slop.new do
|
51
|
+
command(:foo, :banner => 'bar') { }
|
52
|
+
command(:bar) { banner 'bar' }
|
53
|
+
end
|
54
|
+
assert_equal 'bar', slop.commands[:foo].banner
|
55
|
+
assert_equal 'bar', slop.commands[:bar].banner
|
56
|
+
end
|
57
|
+
|
58
|
+
test 'executing on_empty on separate commands' do
|
59
|
+
incmd = inslop = false
|
60
|
+
slop = Slop.new do
|
61
|
+
command(:foo) { on(:bar) {}; on_empty { incmd = true }}
|
62
|
+
on_empty { inslop = true }
|
63
|
+
end
|
64
|
+
slop.parse %w//
|
65
|
+
assert inslop
|
66
|
+
refute incmd
|
67
|
+
inslop = false
|
68
|
+
slop.parse %w/foo/
|
69
|
+
assert incmd
|
70
|
+
refute inslop
|
71
|
+
end
|
72
|
+
end
|
data/test/option_test.rb
CHANGED
@@ -74,8 +74,10 @@ class OptionTest < TestCase
|
|
74
74
|
assert_equal (1...10), option_value(%w/-r 1...10/, :r, true, :as => Range)
|
75
75
|
|
76
76
|
# default back to the string unless a regex is successful
|
77
|
+
# return value.to_i if the value is /\A\d+\z/
|
77
78
|
# maybe this should raise is Slop#strict?
|
78
79
|
assert_equal "1abc10", option_value(%w/-r 1abc10/, :r, true, :as => Range)
|
80
|
+
assert_equal 1, option_value(%w/-r 1/, :r, true, :as => Range)
|
79
81
|
end
|
80
82
|
|
81
83
|
test 'printing options' do
|
data/test/slop_test.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/helper'
|
2
|
-
|
3
1
|
class SlopTest < TestCase
|
4
2
|
def clean_options(*args)
|
5
3
|
Slop.new.send(:clean_options, args)
|
@@ -19,7 +17,6 @@ class SlopTest < TestCase
|
|
19
17
|
|
20
18
|
test 'new accepts a hash or array of symbols' do
|
21
19
|
slop = Slop.new :strict, :multiple_switches => true
|
22
|
-
|
23
20
|
[ :@multiple_switches, :@strict ].each do |var|
|
24
21
|
assert slop.instance_variable_get var
|
25
22
|
end
|
@@ -44,6 +41,14 @@ class SlopTest < TestCase
|
|
44
41
|
end
|
45
42
|
end
|
46
43
|
|
44
|
+
test 'callback when option array is empty' do
|
45
|
+
item1 = item2 = nil
|
46
|
+
Slop.new { on_empty { item1 = 'foo' } }.parse
|
47
|
+
|
48
|
+
assert_equal 'foo', item1
|
49
|
+
assert_nil item2
|
50
|
+
end
|
51
|
+
|
47
52
|
test 'multiple switches with the :multiple_switches flag' do
|
48
53
|
slop = Slop.new :multiple_switches => true, :strict => true
|
49
54
|
%w/a b c/.each { |f| slop.on f }
|
@@ -86,10 +91,8 @@ class SlopTest < TestCase
|
|
86
91
|
|
87
92
|
test 'preserving order when yielding non-options' do
|
88
93
|
items = []
|
89
|
-
|
90
94
|
slop = Slop.new { on(:name, true) { |name| items << name } }
|
91
95
|
slop.parse(%w/foo --name bar baz/) { |value| items << value }
|
92
|
-
|
93
96
|
assert_equal %w/foo bar baz/, items
|
94
97
|
end
|
95
98
|
|
@@ -105,6 +108,9 @@ class SlopTest < TestCase
|
|
105
108
|
|
106
109
|
slop = Slop.new "foo bar"
|
107
110
|
assert_equal "foo bar", slop.banner
|
111
|
+
|
112
|
+
slop = Slop.new :banner => "foo bar"
|
113
|
+
assert_equal "foo bar", slop.banner
|
108
114
|
end
|
109
115
|
|
110
116
|
test 'storing long option lengths' do
|
@@ -120,7 +126,6 @@ class SlopTest < TestCase
|
|
120
126
|
opts = Slop.new do
|
121
127
|
on :name, true
|
122
128
|
end
|
123
|
-
|
124
129
|
assert_equal %w/a/, opts.parse!(%w/--name lee a/)
|
125
130
|
assert_equal %w/--name lee a/, opts.parse(%w/--name lee a/)
|
126
131
|
end
|
@@ -173,13 +178,21 @@ class SlopTest < TestCase
|
|
173
178
|
assert_equal(['c', nil, nil, false], clean_options(:c, false))
|
174
179
|
end
|
175
180
|
|
176
|
-
test '[] returns an options argument value or nil' do
|
181
|
+
test '[] returns an options argument value or a command or nil (in that order)' do
|
177
182
|
slop = Slop.new
|
178
183
|
slop.opt :n, :name, true
|
179
|
-
slop.
|
184
|
+
slop.opt :foo
|
185
|
+
slop.command(:foo) { }
|
186
|
+
slop.command(:bar) { }
|
187
|
+
slop.parse %w/--name lee --foo/
|
180
188
|
|
181
189
|
assert_equal 'lee', slop[:name]
|
182
190
|
assert_equal 'lee', slop[:n]
|
191
|
+
|
192
|
+
assert_equal true, slop[:foo]
|
193
|
+
assert_kind_of Slop, slop[:bar]
|
194
|
+
|
195
|
+
assert_nil slop[:baz]
|
183
196
|
end
|
184
197
|
|
185
198
|
test 'arguments ending ? test for option existance' do
|
@@ -238,7 +251,6 @@ class SlopTest < TestCase
|
|
238
251
|
slop = Slop.new
|
239
252
|
slop.banner = 'Usage: foo [options]'
|
240
253
|
slop.parse
|
241
|
-
|
242
254
|
assert slop.to_s =~ /^Usage: foo/
|
243
255
|
end
|
244
256
|
|
@@ -299,7 +311,6 @@ class SlopTest < TestCase
|
|
299
311
|
|
300
312
|
test 'parsing options with options as arguments' do
|
301
313
|
slop = Slop.new { on :f, :foo, true }
|
302
|
-
|
303
314
|
assert_raises(Slop::MissingArgumentError) { slop.parse %w/-f --bar/ }
|
304
315
|
end
|
305
316
|
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: slop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 1.
|
5
|
+
version: 1.5.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Lee Jarvis
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-04-
|
13
|
+
date: 2011-04-16 00:00:00 +01:00
|
14
14
|
default_executable:
|
15
15
|
dependencies: []
|
16
16
|
|
@@ -33,6 +33,7 @@ files:
|
|
33
33
|
- lib/slop/option.rb
|
34
34
|
- lib/slop/options.rb
|
35
35
|
- slop.gemspec
|
36
|
+
- test/commands_test.rb
|
36
37
|
- test/helper.rb
|
37
38
|
- test/option_test.rb
|
38
39
|
- test/slop_test.rb
|
@@ -65,6 +66,7 @@ signing_key:
|
|
65
66
|
specification_version: 3
|
66
67
|
summary: Option gathering made easy
|
67
68
|
test_files:
|
69
|
+
- test/commands_test.rb
|
68
70
|
- test/helper.rb
|
69
71
|
- test/option_test.rb
|
70
72
|
- test/slop_test.rb
|