slop 1.8.0 → 1.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.md +15 -0
- data/lib/slop.rb +133 -15
- data/slop.gemspec +1 -1
- data/test/commands_test.rb +64 -2
- data/test/helper.rb +1 -10
- data/test/slop_test.rb +61 -0
- metadata +16 -3
data/CHANGES.md
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
TBA
|
2
|
+
---
|
3
|
+
|
4
|
+
* Add command completion and support for an error message when ambiguous
|
5
|
+
commands are used
|
6
|
+
* Add command aliases
|
7
|
+
* Fix: Ensure parsed elements are removed from original arguments when using
|
8
|
+
`:multiple_switches`
|
9
|
+
* Ensure anything after `--` is parsed as an argument and not option even
|
10
|
+
if prefixed with `/--?/`
|
11
|
+
* Performance improvements when making many calls to `Slop#option?` for
|
12
|
+
checking an options presence (Rob Gleeson)
|
13
|
+
* Ensure `execute` passes command arguments to the block
|
14
|
+
* Support for summary and description (Denis Defreyne)
|
15
|
+
|
1
16
|
1.8.0 (2011-06-12)
|
2
17
|
------------------
|
3
18
|
|
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.9.0'
|
20
20
|
|
21
21
|
# Parses the items from a CLI format into a friendly object
|
22
22
|
#
|
@@ -51,9 +51,22 @@ class Slop
|
|
51
51
|
# @param [String] string The text to set the banner to
|
52
52
|
attr_writer :banner
|
53
53
|
|
54
|
+
# @overload summary=(string)
|
55
|
+
# Set the summary
|
56
|
+
# @param [String] string The text to set the summary to
|
57
|
+
attr_writer :summary
|
58
|
+
|
59
|
+
# @overload description=(string)
|
60
|
+
# Set the description
|
61
|
+
# @param [String] string The text to set the description to
|
62
|
+
attr_writer :description
|
63
|
+
|
54
64
|
# @return [Integer] The length of the longest flag slop knows of
|
55
65
|
attr_accessor :longest_flag
|
56
66
|
|
67
|
+
# @return [Array] A list of aliases this command uses
|
68
|
+
attr_reader :aliases
|
69
|
+
|
57
70
|
# @option opts [Boolean] :help
|
58
71
|
# * Automatically add the `help` option
|
59
72
|
#
|
@@ -91,6 +104,13 @@ class Slop
|
|
91
104
|
# @option opts [Boolean] :arguments (false)
|
92
105
|
# * Set to true to enable all specified options to accept arguments
|
93
106
|
# by default
|
107
|
+
#
|
108
|
+
# @option opts [Array] :aliases ([])
|
109
|
+
# * Primary uses by commands to implement command aliases
|
110
|
+
|
111
|
+
# @option opts [Boolean] :completion (true)
|
112
|
+
# * When true, commands will be auto completed. Ie `foobar` will be
|
113
|
+
# executed simply when `foo` `fo` or `foob` are used
|
94
114
|
def initialize(*opts, &block)
|
95
115
|
sloptions = opts.last.is_a?(Hash) ? opts.pop : {}
|
96
116
|
sloptions[:banner] = opts.shift if opts[0].respond_to? :to_str
|
@@ -103,13 +123,16 @@ class Slop
|
|
103
123
|
@longest_flag = 0
|
104
124
|
@invalid_options = []
|
105
125
|
|
126
|
+
@aliases = Array(sloptions[:aliases] || sloptions[:alias])
|
106
127
|
@banner = sloptions[:banner]
|
107
128
|
@strict = sloptions[:strict]
|
108
129
|
@ignore_case = sloptions[:ignore_case]
|
109
130
|
@multiple_switches = sloptions[:multiple_switches]
|
110
131
|
@autocreate = sloptions[:autocreate]
|
132
|
+
@completion = sloptions.fetch(:completion, true)
|
111
133
|
@arguments = sloptions[:arguments]
|
112
134
|
@on_empty = sloptions[:on_empty]
|
135
|
+
@io = sloptions.fetch(:io, $stderr)
|
113
136
|
@on_noopts = sloptions[:on_noopts] || sloptions[:on_optionless]
|
114
137
|
@sloptions = sloptions
|
115
138
|
|
@@ -119,7 +142,7 @@ class Slop
|
|
119
142
|
|
120
143
|
if sloptions[:help]
|
121
144
|
on :h, :help, 'Print this help message', :tail => true do
|
122
|
-
|
145
|
+
@io.puts help
|
123
146
|
exit unless sloptions[:exit_on_help] == false
|
124
147
|
end
|
125
148
|
end
|
@@ -138,6 +161,32 @@ class Slop
|
|
138
161
|
@banner
|
139
162
|
end
|
140
163
|
|
164
|
+
# Set or return the summary
|
165
|
+
#
|
166
|
+
# @param [String] text Displayed summary text
|
167
|
+
# @example
|
168
|
+
# opts = Slop.parse do
|
169
|
+
# summary "do stuff with more stuff"
|
170
|
+
# end
|
171
|
+
# @return [String] The current summary
|
172
|
+
def summary(text=nil)
|
173
|
+
@summary = text if text
|
174
|
+
@summary
|
175
|
+
end
|
176
|
+
|
177
|
+
# Set or return the description
|
178
|
+
#
|
179
|
+
# @param [String] text Displayed description text
|
180
|
+
# @example
|
181
|
+
# opts = Slop.parse do
|
182
|
+
# description "This command does a lot of stuff with other stuff."
|
183
|
+
# end
|
184
|
+
# @return [String] The current description
|
185
|
+
def description(text=nil)
|
186
|
+
@description = text if text
|
187
|
+
@description
|
188
|
+
end
|
189
|
+
|
141
190
|
# Parse a list of options, leaving the original Array unchanged
|
142
191
|
#
|
143
192
|
# @param [Array] items A list of items to parse
|
@@ -226,6 +275,10 @@ class Slop
|
|
226
275
|
slop = Slop.new @sloptions.merge options
|
227
276
|
@commands[label] = slop
|
228
277
|
|
278
|
+
Array(options[:aliases] || options[:alias]).each do |a|
|
279
|
+
@commands[a] = @commands[label]
|
280
|
+
end
|
281
|
+
|
229
282
|
if block_given?
|
230
283
|
block.arity == 1 ? yield(slop) : slop.instance_eval(&block)
|
231
284
|
end
|
@@ -273,15 +326,15 @@ class Slop
|
|
273
326
|
# end
|
274
327
|
# opts.parse %w[foo --verbose] #=> true
|
275
328
|
#
|
276
|
-
# @param [
|
329
|
+
# @param [Array] args The list of arguments to send to this command
|
277
330
|
# is invoked
|
278
331
|
# @since 1.8.0
|
279
332
|
# @yields [Slop] an instance of Slop for this command
|
280
|
-
def execute(
|
281
|
-
if
|
282
|
-
@execution_block =
|
333
|
+
def execute(args=[], &block)
|
334
|
+
if block_given?
|
335
|
+
@execution_block = block
|
283
336
|
elsif @execution_block.respond_to?(:call)
|
284
|
-
@execution_block.call(self)
|
337
|
+
@execution_block.call(self, args)
|
285
338
|
end
|
286
339
|
end
|
287
340
|
|
@@ -310,7 +363,13 @@ class Slop
|
|
310
363
|
# @return [Boolean] true if this option is present, false otherwise
|
311
364
|
def method_missing(meth, *args, &block)
|
312
365
|
super unless meth.to_s[-1, 1] == '?'
|
313
|
-
present? meth.to_s.chomp '?'
|
366
|
+
present = present? meth.to_s.chomp '?'
|
367
|
+
|
368
|
+
(class << self; self; end).instance_eval do
|
369
|
+
define_method(meth) { present }
|
370
|
+
end
|
371
|
+
|
372
|
+
present
|
314
373
|
end
|
315
374
|
|
316
375
|
# Check if an option is specified in the parsed list
|
@@ -335,8 +394,15 @@ class Slop
|
|
335
394
|
# puts opts
|
336
395
|
# @return [String] Help text.
|
337
396
|
def to_s
|
338
|
-
|
339
|
-
|
397
|
+
parts = []
|
398
|
+
|
399
|
+
parts << banner if banner
|
400
|
+
parts << summary if summary
|
401
|
+
parts << wrap_and_indent(description, 80, 4) if description
|
402
|
+
parts << "options:" if options.size > 0
|
403
|
+
parts << options.to_help if options.size > 0
|
404
|
+
|
405
|
+
parts.join("\n\n")
|
340
406
|
end
|
341
407
|
alias :help :to_s
|
342
408
|
|
@@ -374,13 +440,24 @@ class Slop
|
|
374
440
|
end
|
375
441
|
|
376
442
|
trash = []
|
443
|
+
ignore_all = false
|
377
444
|
|
378
445
|
items.each_with_index do |item, index|
|
379
446
|
item = item.to_s
|
380
447
|
flag = item.sub(/\A--?/, '')
|
448
|
+
|
449
|
+
if item == '--'
|
450
|
+
trash << index
|
451
|
+
ignore_all = true
|
452
|
+
end
|
453
|
+
|
454
|
+
next if ignore_all
|
381
455
|
autocreate(flag, index, items) if @autocreate
|
382
456
|
option, argument = extract_option(item, flag)
|
383
|
-
|
457
|
+
if @multiple_switches && !option
|
458
|
+
trash << index if item[/\A-[^-]/]
|
459
|
+
next
|
460
|
+
end
|
384
461
|
|
385
462
|
if option
|
386
463
|
option.count += 1
|
@@ -449,6 +526,31 @@ class Slop
|
|
449
526
|
end
|
450
527
|
end
|
451
528
|
|
529
|
+
def wrap_and_indent(string, width, indentation)
|
530
|
+
# Wrap and indent each paragraph
|
531
|
+
string.lines.map do |paragraph|
|
532
|
+
# Initialize
|
533
|
+
lines = []
|
534
|
+
line = ''
|
535
|
+
|
536
|
+
# Split into words
|
537
|
+
paragraph.split(/\s/).each do |word|
|
538
|
+
# Begin new line if it's too long
|
539
|
+
if (line + ' ' + word).length >= width
|
540
|
+
lines << line
|
541
|
+
line = ''
|
542
|
+
end
|
543
|
+
|
544
|
+
# Add word to line
|
545
|
+
line << (line == '' ? '' : ' ' ) + word
|
546
|
+
end
|
547
|
+
lines << line
|
548
|
+
|
549
|
+
# Join lines
|
550
|
+
lines.map { |l| ' '*indentation + l }.join("\n")
|
551
|
+
end.join("\n")
|
552
|
+
end
|
553
|
+
|
452
554
|
def extract_option(item, flag)
|
453
555
|
if item[0, 1] == '-'
|
454
556
|
option = @options[flag]
|
@@ -474,12 +576,28 @@ class Slop
|
|
474
576
|
end
|
475
577
|
|
476
578
|
def execute_command(items, delete)
|
477
|
-
|
579
|
+
str = items[0]
|
580
|
+
|
581
|
+
if str
|
582
|
+
command = @commands.keys.find { |c| c.to_s == str.to_s }
|
583
|
+
|
584
|
+
if !command && @completion
|
585
|
+
cmds = @commands.keys.select { |c| c.to_s[0, str.length] == str }
|
586
|
+
|
587
|
+
if cmds.size > 1
|
588
|
+
@io.puts "Command '#{str}' is ambiguous:"
|
589
|
+
@io.puts " " + cmds.map(&:to_s).sort.join(', ')
|
590
|
+
else
|
591
|
+
command = cmds.shift
|
592
|
+
end
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
478
596
|
if command
|
479
597
|
items.shift
|
480
598
|
opts = @commands[command]
|
481
599
|
delete ? opts.parse!(items) : opts.parse(items)
|
482
|
-
opts.execute
|
600
|
+
opts.execute(items.reject { |i| i == '--' })
|
483
601
|
end
|
484
602
|
end
|
485
603
|
|
@@ -493,9 +611,9 @@ class Slop
|
|
493
611
|
def clean_options(args)
|
494
612
|
options = []
|
495
613
|
extras = {}
|
496
|
-
extras[:as] =
|
614
|
+
extras[:as] =args.find {|c| c.is_a? Class }
|
497
615
|
args.delete(extras[:as])
|
498
|
-
extras.delete(:as)
|
616
|
+
extras.delete(:as) if extras[:as].nil?
|
499
617
|
|
500
618
|
short = args.first.to_s.sub(/\A--?/, '')
|
501
619
|
if short.size == 1
|
data/slop.gemspec
CHANGED
data/test/commands_test.rb
CHANGED
@@ -75,15 +75,77 @@ class CommandsTest < TestCase
|
|
75
75
|
slop = Slop.new
|
76
76
|
slop.command :foo do
|
77
77
|
on :v, :verbose
|
78
|
-
execute { |o| foo = o.verbose? }
|
78
|
+
execute { |o, a| foo = o.verbose? }
|
79
79
|
end
|
80
80
|
slop.command :bar do
|
81
81
|
on :v, :verbose
|
82
|
-
execute { |o| bar = o.verbose? }
|
82
|
+
execute { |o, a| bar = o.verbose? }
|
83
83
|
end
|
84
84
|
slop.parse %w[ foo --verbose ]
|
85
85
|
|
86
86
|
assert foo
|
87
87
|
refute bar
|
88
88
|
end
|
89
|
+
|
90
|
+
test 'executing blocks and command arguments' do
|
91
|
+
opts = args = nil
|
92
|
+
slop = Slop.new
|
93
|
+
slop.command :foo do
|
94
|
+
execute do |o, a|
|
95
|
+
opts = o
|
96
|
+
args = a
|
97
|
+
end
|
98
|
+
end
|
99
|
+
slop.parse %w[ foo bar baz ]
|
100
|
+
|
101
|
+
assert_equal %w[ bar baz ], args
|
102
|
+
assert_kind_of Slop, opts
|
103
|
+
end
|
104
|
+
|
105
|
+
test 'executing nested commands' do
|
106
|
+
args = nil
|
107
|
+
slop = Slop.new
|
108
|
+
slop.command :foo do
|
109
|
+
command :bar do
|
110
|
+
execute { |o, a| args = a }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
slop.parse %w[ foo bar baz ]
|
114
|
+
|
115
|
+
assert_equal %w[ baz ], args
|
116
|
+
end
|
117
|
+
|
118
|
+
test 'aliases' do
|
119
|
+
slop = Slop.new
|
120
|
+
slop.command :foo, :alias => :bar
|
121
|
+
slop.command :baz, :aliases => [:lorem, :ipsum]
|
122
|
+
|
123
|
+
assert_equal [:bar], slop.commands[:foo].aliases
|
124
|
+
assert_equal [:lorem, :ipsum], slop.commands[:baz].aliases
|
125
|
+
|
126
|
+
assert_raises(ArgumentError) { slop.command :lorem }
|
127
|
+
end
|
128
|
+
|
129
|
+
test 'command completion' do
|
130
|
+
foo = bar = bara = nil
|
131
|
+
slop = Slop.new
|
132
|
+
slop.command(:foo) { execute { foo = true } }
|
133
|
+
slop.parse %w[ fo ]
|
134
|
+
assert foo
|
135
|
+
|
136
|
+
slop.command(:bar) { execute { bar = true } }
|
137
|
+
slop.command(:bara) { execute { bara = true } }
|
138
|
+
slop.parse %w[ bar ]
|
139
|
+
assert bar
|
140
|
+
refute bara
|
141
|
+
end
|
142
|
+
|
143
|
+
test 'ambiguous command completion' do
|
144
|
+
io = StringIO.new
|
145
|
+
slop = Slop.new(:io => io)
|
146
|
+
slop.command :bar
|
147
|
+
slop.command :baz
|
148
|
+
slop.parse %w[ ba ]
|
149
|
+
assert_equal "Command 'ba' is ambiguous:\n bar, baz\n", io.string
|
150
|
+
end
|
89
151
|
end
|
data/test/helper.rb
CHANGED
@@ -8,15 +8,6 @@ require 'stringio'
|
|
8
8
|
|
9
9
|
class TestCase < MiniTest::Unit::TestCase
|
10
10
|
def self.test(name, &block)
|
11
|
-
|
12
|
-
defined = instance_method(test_name) rescue false
|
13
|
-
raise "#{test_name} is already defined in #{self}" if defined
|
14
|
-
if block_given?
|
15
|
-
define_method(test_name, &block)
|
16
|
-
else
|
17
|
-
define_method(test_name) do
|
18
|
-
flunk "No implementation provided for #{name}"
|
19
|
-
end
|
20
|
-
end
|
11
|
+
define_method("test_#{name.gsub(/\W/, '_')}", &block) if block
|
21
12
|
end
|
22
13
|
end
|
data/test/slop_test.rb
CHANGED
@@ -84,6 +84,16 @@ class SlopTest < TestCase
|
|
84
84
|
|
85
85
|
assert_raises(Slop::InvalidOptionError, /d/) { slop.parse %w/-abcd/ }
|
86
86
|
assert_raises(Slop::MissingArgumentError, /z/) { slop.parse %w/-abcz/ }
|
87
|
+
|
88
|
+
slop = Slop.new(:multiple_switches)
|
89
|
+
slop.on :a
|
90
|
+
slop.on :f, true
|
91
|
+
args = %w[-abc -f foo bar]
|
92
|
+
slop.parse! args
|
93
|
+
|
94
|
+
assert_equal %w[ bar ], args
|
95
|
+
assert_equal 'foo', slop[:f]
|
96
|
+
assert slop[:a]
|
87
97
|
end
|
88
98
|
|
89
99
|
test 'passing a block' do
|
@@ -140,6 +150,31 @@ class SlopTest < TestCase
|
|
140
150
|
assert_equal "foo bar", slop.banner
|
141
151
|
end
|
142
152
|
|
153
|
+
test 'setting the summary' do
|
154
|
+
slop = Slop.new
|
155
|
+
slop.banner = "foo bar"
|
156
|
+
slop.summary = "does stuff"
|
157
|
+
|
158
|
+
assert_equal "foo bar\n\ndoes stuff", slop.to_s
|
159
|
+
end
|
160
|
+
|
161
|
+
test 'setting the description' do
|
162
|
+
slop = Slop.new
|
163
|
+
slop.banner = "foo bar"
|
164
|
+
slop.summary = "does stuff"
|
165
|
+
slop.description = "This does stuff."
|
166
|
+
|
167
|
+
assert_equal "foo bar\n\ndoes stuff\n\n This does stuff.", slop.to_s
|
168
|
+
end
|
169
|
+
|
170
|
+
test 'setting the description without matching summary' do
|
171
|
+
slop = Slop.new
|
172
|
+
slop.banner = "foo bar"
|
173
|
+
slop.description = "This does stuff."
|
174
|
+
|
175
|
+
assert_equal "foo bar\n\n This does stuff.", slop.to_s
|
176
|
+
end
|
177
|
+
|
143
178
|
test 'storing long option lengths' do
|
144
179
|
slop = Slop.new
|
145
180
|
assert_equal 0, slop.longest_flag
|
@@ -440,6 +475,15 @@ class SlopTest < TestCase
|
|
440
475
|
assert_equal ' --bar [HELLO] ', opts.options[:bar].to_s
|
441
476
|
end
|
442
477
|
|
478
|
+
test 'not parsing options if after --' do
|
479
|
+
args = %w[ foo bar -- --foo bar ]
|
480
|
+
opts = Slop.parse!(args) do
|
481
|
+
on :foo, true
|
482
|
+
end
|
483
|
+
|
484
|
+
assert_equal %w[ foo bar --foo bar ], args
|
485
|
+
end
|
486
|
+
|
443
487
|
test 'inline classes' do
|
444
488
|
opts = Slop.new do
|
445
489
|
on :foo, Array, true
|
@@ -451,4 +495,21 @@ class SlopTest < TestCase
|
|
451
495
|
assert_equal :hello, opts[:bar]
|
452
496
|
end
|
453
497
|
|
498
|
+
test 'wrap and indent' do
|
499
|
+
slop = Slop.new
|
500
|
+
|
501
|
+
assert_equal(
|
502
|
+
"Lorem ipsum dolor sit amet, consectetur\n" +
|
503
|
+
"adipisicing elit, sed do eiusmod tempor\n" +
|
504
|
+
"incididunt ut labore et dolore magna\n" +
|
505
|
+
"aliqua.",
|
506
|
+
slop.send(:wrap_and_indent, "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", 40, 0))
|
507
|
+
|
508
|
+
assert_equal(
|
509
|
+
" Lorem ipsum dolor sit amet,\n" +
|
510
|
+
" consectetur adipisicing elit, sed\n" +
|
511
|
+
" do eiusmod tempor incididunt ut\n" +
|
512
|
+
" labore et dolore magna aliqua.",
|
513
|
+
slop.send(:wrap_and_indent, "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", 36, 4))
|
514
|
+
end
|
454
515
|
end
|
metadata
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: slop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 51
|
4
5
|
prerelease:
|
5
|
-
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 9
|
9
|
+
- 0
|
10
|
+
version: 1.9.0
|
6
11
|
platform: ruby
|
7
12
|
authors:
|
8
13
|
- Lee Jarvis
|
@@ -10,7 +15,8 @@ autorequire:
|
|
10
15
|
bindir: bin
|
11
16
|
cert_chain: []
|
12
17
|
|
13
|
-
date: 2011-06-
|
18
|
+
date: 2011-06-15 00:00:00 +01:00
|
19
|
+
default_executable:
|
14
20
|
dependencies: []
|
15
21
|
|
16
22
|
description: A simple DSL for gathering options and parsing the command line
|
@@ -37,6 +43,7 @@ files:
|
|
37
43
|
- test/helper.rb
|
38
44
|
- test/option_test.rb
|
39
45
|
- test/slop_test.rb
|
46
|
+
has_rdoc: true
|
40
47
|
homepage: http://github.com/injekt/slop
|
41
48
|
licenses: []
|
42
49
|
|
@@ -50,17 +57,23 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
50
57
|
requirements:
|
51
58
|
- - ">="
|
52
59
|
- !ruby/object:Gem::Version
|
60
|
+
hash: 3
|
61
|
+
segments:
|
62
|
+
- 0
|
53
63
|
version: "0"
|
54
64
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
65
|
none: false
|
56
66
|
requirements:
|
57
67
|
- - ">="
|
58
68
|
- !ruby/object:Gem::Version
|
69
|
+
hash: 3
|
70
|
+
segments:
|
71
|
+
- 0
|
59
72
|
version: "0"
|
60
73
|
requirements: []
|
61
74
|
|
62
75
|
rubyforge_project:
|
63
|
-
rubygems_version: 1.
|
76
|
+
rubygems_version: 1.6.2
|
64
77
|
signing_key:
|
65
78
|
specification_version: 3
|
66
79
|
summary: Option gathering made easy
|