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 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.8.0'
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
- (sloptions[:io] || $stderr).puts help
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 [Object, #call] obj The object to be triggered when this command
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(obj=nil, &block)
281
- if obj || block_given?
282
- @execution_block = obj || 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
- banner = "#{@banner}\n" if @banner
339
- (banner || '') + options.to_help
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
- next if @multiple_switches
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
- command = @commands.keys.find { |cmd| cmd.to_s == items[0].to_s }
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] = args.find {|c| c.is_a? Class }
614
+ extras[:as] =args.find {|c| c.is_a? Class }
497
615
  args.delete(extras[:as])
498
- extras.delete(:as) unless extras[: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
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'slop'
3
- s.version = '1.8.0'
3
+ s.version = '1.9.0'
4
4
  s.summary = 'Option gathering made easy'
5
5
  s.description = 'A simple DSL for gathering options and parsing the command line'
6
6
  s.author = 'Lee Jarvis'
@@ -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
- test_name = "test_#{name.gsub(/\s+/, '_')}".to_sym
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
- version: 1.8.0
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-12 00:00:00 Z
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.8.5
76
+ rubygems_version: 1.6.2
64
77
  signing_key:
65
78
  specification_version: 3
66
79
  summary: Option gathering made easy