slop 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|