slop 1.1.0 → 1.2.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 +21 -1
- data/lib/slop.rb +82 -30
- data/lib/slop/option.rb +51 -4
- data/lib/slop/version.rb +1 -1
- data/test/option_test.rb +15 -0
- data/test/slop_test.rb +45 -0
- metadata +2 -13
data/README.md
CHANGED
@@ -37,8 +37,28 @@ You can also return your options as a Hash
|
|
37
37
|
|
38
38
|
opts.to_hash #=> {'name' => 'Lee Jarvis', 'verbose' => true, 'age' => nil, 'sex' => 'male'}
|
39
39
|
|
40
|
+
# Symbols
|
41
|
+
opts.to_hash(true) #=> {:name => 'Lee Jarvis', :verbose => true, :age => nil, :sex => 'male'}
|
42
|
+
|
43
|
+
If you pass a block to `Slop#parse`, Slop will yield non-options as
|
44
|
+
they're found, just like
|
45
|
+
[OptionParser](http://rubydoc.info/stdlib/optparse/1.9.2/OptionParser:order)
|
46
|
+
does it.
|
47
|
+
|
48
|
+
opts = Slop.new do
|
49
|
+
on :n, :name, :optional => false
|
50
|
+
end
|
51
|
+
|
52
|
+
opts.parse do |arg|
|
53
|
+
puts arg
|
54
|
+
end
|
55
|
+
|
56
|
+
# if ARGV is `foo --name Lee bar`
|
57
|
+
foo
|
58
|
+
bar
|
59
|
+
|
40
60
|
If you don't like the method `on` (because it sounds like the option **expects**
|
41
|
-
a
|
61
|
+
a block), you can use the `opt` or `option` alternatives.
|
42
62
|
|
43
63
|
on :v, :verbose
|
44
64
|
opt :v, :verbose
|
data/lib/slop.rb
CHANGED
@@ -5,6 +5,8 @@ class Slop
|
|
5
5
|
include Enumerable
|
6
6
|
|
7
7
|
class MissingArgumentError < ArgumentError; end
|
8
|
+
class InvalidArgumentError < ArgumentError; end
|
9
|
+
class InvalidOptionError < ArgumentError; end
|
8
10
|
|
9
11
|
# Parses the items from a CLI format into a friendly object.
|
10
12
|
#
|
@@ -21,25 +23,42 @@ class Slop
|
|
21
23
|
# program.rb --verbose -n 'Emily' -a 25
|
22
24
|
# @see Slop#banner
|
23
25
|
# @see Slop#option
|
24
|
-
def self.parse(items=ARGV, &block)
|
25
|
-
|
26
|
-
|
27
|
-
|
26
|
+
def self.parse(items=ARGV, options={}, &block)
|
27
|
+
initialize_and_parse(items, false, options, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Identical to {Slop.parse}, but removes parsed options from the original Array.
|
31
|
+
#
|
32
|
+
# @yield Specify available CLI arguments using Slop# methods such as Slop#banner and Slop#option
|
33
|
+
# @return [Slop] Returns an instance of Slop.
|
34
|
+
def self.parse!(items=ARGV, options={}, &block)
|
35
|
+
initialize_and_parse(items, true, options, &block)
|
28
36
|
end
|
29
37
|
|
30
38
|
attr_reader :options
|
31
39
|
attr_writer :banner
|
32
40
|
attr_accessor :longest_flag
|
33
41
|
|
34
|
-
|
42
|
+
# @param [Hash] options
|
43
|
+
# @option options [Boolean] :help Automatically add the `help` option
|
44
|
+
# @option options [Boolean] :strict Strict mode raises when a non listed
|
45
|
+
# option is found, false by default
|
46
|
+
def initialize(options={}, &block)
|
35
47
|
@options = Options.new
|
36
48
|
@banner = nil
|
37
49
|
@longest_flag = 0
|
38
|
-
@
|
50
|
+
@strict = options[:strict]
|
39
51
|
|
40
52
|
if block_given?
|
41
53
|
block.arity == 1 ? yield(self) : instance_eval(&block)
|
42
54
|
end
|
55
|
+
|
56
|
+
if options[:help]
|
57
|
+
on :h, :help, 'Print this help message', :tail => true do
|
58
|
+
puts help
|
59
|
+
exit
|
60
|
+
end
|
61
|
+
end
|
43
62
|
end
|
44
63
|
|
45
64
|
# Set or return banner text.
|
@@ -59,21 +78,15 @@ class Slop
|
|
59
78
|
# Parse a list of options, leaving the original Array unchanged.
|
60
79
|
#
|
61
80
|
# @param items
|
62
|
-
def parse(items=ARGV)
|
63
|
-
|
64
|
-
|
65
|
-
if block_given?
|
66
|
-
@items.each { |item| yield item }
|
67
|
-
end
|
68
|
-
|
69
|
-
@items
|
81
|
+
def parse(items=ARGV, &block)
|
82
|
+
parse_items items, &block
|
70
83
|
end
|
71
84
|
|
72
85
|
# Parse a list of options, removing parsed options from the original Array.
|
73
86
|
#
|
74
|
-
# @
|
75
|
-
def parse!(items=ARGV)
|
76
|
-
parse_items items, true
|
87
|
+
# @param items
|
88
|
+
def parse!(items=ARGV, &block)
|
89
|
+
parse_items items, true, &block
|
77
90
|
end
|
78
91
|
|
79
92
|
# Enumerable interface
|
@@ -169,46 +182,85 @@ class Slop
|
|
169
182
|
# @see Slop#banner
|
170
183
|
def to_s
|
171
184
|
banner = "#{@banner}\n" if @banner
|
172
|
-
(banner || '') + options.
|
185
|
+
(banner || '') + options.to_help
|
173
186
|
end
|
174
187
|
alias :help :to_s
|
175
188
|
|
176
189
|
private
|
177
190
|
|
178
|
-
def
|
191
|
+
def self.initialize_and_parse(items, delete, options, &block)
|
192
|
+
if items.is_a?(Hash) && options.empty?
|
193
|
+
options = items
|
194
|
+
items = ARGV
|
195
|
+
end
|
196
|
+
|
197
|
+
slop = new(options, &block)
|
198
|
+
delete ? slop.parse!(items) : slop.parse(items)
|
199
|
+
slop
|
200
|
+
end
|
201
|
+
|
202
|
+
def parse_items(items, delete=false, &block)
|
179
203
|
trash = []
|
180
204
|
|
181
205
|
items.each do |item|
|
182
|
-
|
206
|
+
item = item.to_s
|
207
|
+
flag = item.sub(/^--?/, '')
|
183
208
|
option = @options[flag]
|
184
209
|
|
210
|
+
if !option && item =~ /^-[^-]/
|
211
|
+
flag, argument = flag.split('', 2)
|
212
|
+
option = @options[flag]
|
213
|
+
end
|
214
|
+
|
185
215
|
if option
|
186
|
-
trash << item
|
216
|
+
trash << item
|
187
217
|
option.argument_value = true
|
188
218
|
|
189
219
|
if option.expects_argument? || option.accepts_optional_argument?
|
190
|
-
argument
|
191
|
-
trash << argument
|
220
|
+
argument ||= items.at(items.index(item) + 1)
|
221
|
+
trash << argument
|
192
222
|
|
193
223
|
if argument
|
224
|
+
check_matching_argument(option, argument)
|
194
225
|
option.argument_value = argument
|
195
226
|
option.callback.call option.argument_value if option.callback
|
196
227
|
else
|
197
228
|
option.argument_value = nil
|
198
|
-
|
199
|
-
option.callback.call nil if option.callback
|
200
|
-
else
|
201
|
-
raise MissingArgumentError,
|
202
|
-
"'#{flag}' expects an argument, none given"
|
203
|
-
end
|
229
|
+
check_optional_argument(option, flag)
|
204
230
|
end
|
205
231
|
elsif option.callback
|
206
232
|
option.callback.call nil
|
207
233
|
end
|
234
|
+
else
|
235
|
+
check_invalid_option(item, flag)
|
236
|
+
block.call(item) if block_given? && !trash.include?(item)
|
208
237
|
end
|
209
238
|
end
|
210
239
|
|
211
|
-
items.delete_if { |item| trash.include? item }
|
240
|
+
items.delete_if { |item| trash.include? item } if delete
|
241
|
+
items
|
242
|
+
end
|
243
|
+
|
244
|
+
def check_matching_argument(option, argument)
|
245
|
+
if option.match && !argument.match(option.match)
|
246
|
+
raise InvalidArgumentError,
|
247
|
+
"'#{argument}' does not match #{option.match.inspect}"
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def check_optional_argument(option, flag)
|
252
|
+
if option.accepts_optional_argument?
|
253
|
+
option.callback.call nil if option.callback
|
254
|
+
else
|
255
|
+
raise MissingArgumentError,
|
256
|
+
"'#{flag}' expects an argument, none given"
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def check_invalid_option(item, flag)
|
261
|
+
if item[/^--?/] && @strict
|
262
|
+
raise InvalidOptionError, "Unknown option -- '#{flag}'"
|
263
|
+
end
|
212
264
|
end
|
213
265
|
|
214
266
|
def clean_options(args)
|
data/lib/slop/option.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
class Slop
|
2
2
|
class Options < Array
|
3
|
+
|
4
|
+
# @param [Boolean] symbols true to cast hash keys to symbols
|
5
|
+
# @return [Hash]
|
3
6
|
def to_hash(symbols)
|
4
7
|
out = {}
|
5
8
|
each do |option|
|
@@ -10,8 +13,10 @@ class Slop
|
|
10
13
|
out
|
11
14
|
end
|
12
15
|
|
13
|
-
|
14
|
-
|
16
|
+
# @param [Object] flag
|
17
|
+
# @return [Option] the option assoiated with this flag
|
18
|
+
def [](flag)
|
19
|
+
item = flag.to_s
|
15
20
|
if item =~ /^\d+$/
|
16
21
|
slice item.to_i
|
17
22
|
else
|
@@ -20,16 +25,51 @@ class Slop
|
|
20
25
|
end
|
21
26
|
end
|
22
27
|
end
|
28
|
+
|
29
|
+
# @return [String]
|
30
|
+
def to_help
|
31
|
+
heads = select {|x| !x.tail }
|
32
|
+
tails = select {|x| x.tail }
|
33
|
+
(heads + tails).map(&:to_s).join("\n")
|
34
|
+
end
|
23
35
|
end
|
24
36
|
|
25
37
|
class Option
|
26
38
|
|
39
|
+
# @return [String, #to_s]
|
27
40
|
attr_reader :short_flag
|
41
|
+
|
42
|
+
# @return [String, #to_s]
|
28
43
|
attr_reader :long_flag
|
44
|
+
|
45
|
+
# @return [String]
|
29
46
|
attr_reader :description
|
47
|
+
|
48
|
+
# @return [Proc, #call]
|
30
49
|
attr_reader :callback
|
50
|
+
|
51
|
+
# @return [Boolean]
|
52
|
+
attr_reader :tail
|
53
|
+
|
54
|
+
# @return [Regex]
|
55
|
+
attr_reader :match
|
56
|
+
|
31
57
|
attr_writer :argument_value
|
32
58
|
|
59
|
+
# @param [Slop] slop
|
60
|
+
# @param [String, #to_s] short
|
61
|
+
# @param [String, #to_s] long
|
62
|
+
# @param [String] description
|
63
|
+
# @param [Boolean] argument
|
64
|
+
# @param [Hash] options
|
65
|
+
# @option options [Boolean] :optional
|
66
|
+
# @option options [Boolean] :argument
|
67
|
+
# @option options [Object] :default
|
68
|
+
# @option options [Proc, #call] :callback
|
69
|
+
# @option options [String, #to_s] :delimiter
|
70
|
+
# @option options [Integer] :limit
|
71
|
+
# @option options [Boolean] :tail
|
72
|
+
# @option options [Regexp] :match
|
33
73
|
def initialize(slop, short, long, description, argument, options={}, &blk)
|
34
74
|
@slop = slop
|
35
75
|
@short_flag = short
|
@@ -38,6 +78,8 @@ class Slop
|
|
38
78
|
@options = options
|
39
79
|
@expects_argument = argument
|
40
80
|
@expects_argument = true if options[:optional] == false
|
81
|
+
@tail = options[:tail]
|
82
|
+
@match = options[:match]
|
41
83
|
|
42
84
|
if @long_flag && @long_flag.size > @slop.longest_flag
|
43
85
|
@slop.longest_flag = @long_flag.size
|
@@ -48,22 +90,28 @@ class Slop
|
|
48
90
|
@argument_value = nil
|
49
91
|
end
|
50
92
|
|
93
|
+
# @return [Boolean] true if this option expects an argument
|
51
94
|
def expects_argument?
|
52
95
|
@expects_argument || @options[:argument]
|
53
96
|
end
|
54
97
|
|
98
|
+
# @return [Boolean] true if this option expects an optional argument
|
55
99
|
def accepts_optional_argument?
|
56
100
|
@options[:optional]
|
57
101
|
end
|
58
102
|
|
103
|
+
# @return [String] either the long or short flag for this option
|
59
104
|
def key
|
60
105
|
@long_flag || @short_flag
|
61
106
|
end
|
62
107
|
|
108
|
+
# @return [Object]
|
63
109
|
def default
|
64
110
|
@options[:default]
|
65
111
|
end
|
66
112
|
|
113
|
+
# @return [Object] the argument value after it's been case
|
114
|
+
# according to the `:as` option
|
67
115
|
def argument_value
|
68
116
|
value = @argument_value || default
|
69
117
|
return if value.nil?
|
@@ -103,5 +151,4 @@ class Slop
|
|
103
151
|
"description=#{@description.inspect}>"
|
104
152
|
end
|
105
153
|
end
|
106
|
-
|
107
|
-
end
|
154
|
+
end
|
data/lib/slop/version.rb
CHANGED
data/test/option_test.rb
CHANGED
@@ -88,4 +88,19 @@ class OptionTest < TestCase
|
|
88
88
|
assert_equal 'foo', option(:f, :foo).key
|
89
89
|
assert_equal 'b', option(:b).key
|
90
90
|
end
|
91
|
+
|
92
|
+
test 'tail to append items to the options list when printing help' do
|
93
|
+
slop = Slop.new
|
94
|
+
slop.on :f, :foo, :tail => true
|
95
|
+
slop.on :b, :bar
|
96
|
+
assert slop.to_s.strip =~ /foo$/
|
97
|
+
end
|
98
|
+
|
99
|
+
test 'argument matching' do
|
100
|
+
slop = Slop.new
|
101
|
+
slop.on :f, :foo, true, :match => /^h/
|
102
|
+
|
103
|
+
assert_raises(Slop::InvalidArgumentError, /world/) { slop.parse %w/--foo world/ }
|
104
|
+
assert slop.parse %w/--foo hello/
|
105
|
+
end
|
91
106
|
end
|
data/test/slop_test.rb
CHANGED
@@ -33,6 +33,15 @@ class SlopTest < TestCase
|
|
33
33
|
assert_kind_of Slop, slop
|
34
34
|
end
|
35
35
|
|
36
|
+
test 'automatically adding the help option' do
|
37
|
+
slop = Slop.new
|
38
|
+
assert_empty slop.options
|
39
|
+
|
40
|
+
slop = Slop.new :help => true
|
41
|
+
refute_empty slop.options
|
42
|
+
assert_equal 'Print this help message', slop.options[:help].description
|
43
|
+
end
|
44
|
+
|
36
45
|
test 'yielding non-options when a block is passed to "parse"' do
|
37
46
|
opts = Slop.new do
|
38
47
|
on :name, true
|
@@ -42,6 +51,15 @@ class SlopTest < TestCase
|
|
42
51
|
end
|
43
52
|
end
|
44
53
|
|
54
|
+
test 'preserving order when yielding non-options' do
|
55
|
+
items = []
|
56
|
+
|
57
|
+
slop = Slop.new { on(:name, true) { |name| items << name } }
|
58
|
+
slop.parse(%w/foo --name bar baz/) { |value| items << value }
|
59
|
+
|
60
|
+
assert_equal %w/foo bar baz/, items
|
61
|
+
end
|
62
|
+
|
45
63
|
test 'setting the banner' do
|
46
64
|
slop = Slop.new
|
47
65
|
slop.banner = "foo bar"
|
@@ -62,6 +80,15 @@ class SlopTest < TestCase
|
|
62
80
|
assert_equal 8, slop.longest_flag
|
63
81
|
end
|
64
82
|
|
83
|
+
test 'parse returning the list of arguments left after parsing' do
|
84
|
+
opts = Slop.new do
|
85
|
+
on :name, true
|
86
|
+
end
|
87
|
+
|
88
|
+
assert_equal %w/a/, opts.parse!(%w/--name lee a/)
|
89
|
+
assert_equal %w/--name lee a/, opts.parse(%w/--name lee a/)
|
90
|
+
end
|
91
|
+
|
65
92
|
test '#parse does not remove parsed items' do
|
66
93
|
items = %w/--foo/
|
67
94
|
Slop.new { |opt| opt.on :foo }.parse(items)
|
@@ -180,4 +207,22 @@ class SlopTest < TestCase
|
|
180
207
|
opts.parse %w/--name lee/
|
181
208
|
assert_equal 'lee', name
|
182
209
|
end
|
210
|
+
|
211
|
+
test 'strict mode' do
|
212
|
+
strict = Slop.new :strict => true
|
213
|
+
totallynotstrict = Slop.new
|
214
|
+
|
215
|
+
assert_raises(Slop::InvalidOptionError, /--foo/) { strict.parse %w/--foo/ }
|
216
|
+
assert totallynotstrict.parse %w/--foo/
|
217
|
+
end
|
218
|
+
|
219
|
+
test 'short option flag with no space between flag and argument' do
|
220
|
+
slop = Slop.new
|
221
|
+
slop.opt :p, :password, true
|
222
|
+
slop.opt :s, :shortpass, true
|
223
|
+
slop.parse %w/-p foo -sbar/
|
224
|
+
|
225
|
+
assert_equal 'foo', slop[:password]
|
226
|
+
assert_equal 'bar', slop[:shortpass]
|
227
|
+
end
|
183
228
|
end
|
metadata
CHANGED
@@ -1,13 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: slop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: 19
|
5
4
|
prerelease:
|
6
|
-
|
7
|
-
- 1
|
8
|
-
- 1
|
9
|
-
- 0
|
10
|
-
version: 1.1.0
|
5
|
+
version: 1.2.0
|
11
6
|
platform: ruby
|
12
7
|
authors:
|
13
8
|
- Lee Jarvis
|
@@ -15,7 +10,7 @@ autorequire:
|
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
12
|
|
18
|
-
date: 2011-03-
|
13
|
+
date: 2011-03-21 00:00:00 +00:00
|
19
14
|
default_executable:
|
20
15
|
dependencies: []
|
21
16
|
|
@@ -55,18 +50,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
55
50
|
requirements:
|
56
51
|
- - ">="
|
57
52
|
- !ruby/object:Gem::Version
|
58
|
-
hash: 3
|
59
|
-
segments:
|
60
|
-
- 0
|
61
53
|
version: "0"
|
62
54
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
55
|
none: false
|
64
56
|
requirements:
|
65
57
|
- - ">="
|
66
58
|
- !ruby/object:Gem::Version
|
67
|
-
hash: 3
|
68
|
-
segments:
|
69
|
-
- 0
|
70
59
|
version: "0"
|
71
60
|
requirements: []
|
72
61
|
|