slop 1.8.0 → 1.9.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/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
|