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 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