slop 1.4.1 → 1.5.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/README.md CHANGED
@@ -315,6 +315,44 @@ yields:
315
315
  -n, --name Your name
316
316
  -h, --help Print this help message
317
317
 
318
+ Commands
319
+ --------
320
+
321
+ Slop allows you to nest more instances of Slop inside of `commands`. These
322
+ instances will then be used to parse arguments if they're called upon.
323
+
324
+ Slop will use the first argument in the list of items passed to `parse` to
325
+ check if it is a `command`.
326
+
327
+ Slop.parse ['foo', '--bar', 'baz']
328
+
329
+ Slop will look to see if the `foo` command exists, and if it does, it'll pass
330
+ the options `['--bar', 'baz']` to the instance of Slop that belongs to `foo`.
331
+ Here's how commands might look:
332
+
333
+ opts = Slop.new do
334
+ command :foo do
335
+ on :b, :bar, 'something', true
336
+ end
337
+
338
+ command :clean do
339
+ on :v, :verbose, do
340
+ puts 'Enabled verbose mode for clean'
341
+ end
342
+ end
343
+
344
+ # non-command specific options
345
+ on :v, :version do
346
+ puts 'version 1'
347
+ end
348
+ end
349
+
350
+ * Run with `run.rb -v`
351
+ * Output: `version 1`
352
+
353
+ * Run with: `run.rb clean -v`
354
+ * Output: `Enabled verbose mode for clean`
355
+
318
356
  Woah woah, why you hating on OptionParser?
319
357
  ------------------------------------------
320
358
 
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.4.1'
19
+ VERSION = '1.5.0'
20
20
 
21
21
  # Parses the items from a CLI format into a friendly object.
22
22
  #
@@ -44,6 +44,9 @@ class Slop
44
44
  # @return [Options]
45
45
  attr_reader :options
46
46
 
47
+ # @return [Hash]
48
+ attr_reader :commands
49
+
47
50
  attr_writer :banner
48
51
  attr_accessor :longest_flag
49
52
 
@@ -54,6 +57,9 @@ class Slop
54
57
  # @option opts [Boolean] :multiple_switches Allows `-abc` to be processed
55
58
  # as the options 'a', 'b', 'c' and will force their argument values to
56
59
  # true. By default Slop with parse this as 'a' with the argument 'bc'
60
+ # @option opts [String] :banner The banner text used for the help
61
+ # @option opts [Proc, #call] :on_empty Any object that respondes to `call`
62
+ # which is executed when Slop has no items to parse
57
63
  def initialize(*opts, &block)
58
64
  sloptions = {}
59
65
  sloptions.merge! opts.pop if opts.last.is_a? Hash
@@ -61,10 +67,16 @@ class Slop
61
67
  opts.each { |o| sloptions[o] = true }
62
68
 
63
69
  @options = Options.new
70
+ @commands = {}
71
+
64
72
  @longest_flag = 0
65
- @strict = sloptions[:strict]
66
73
  @invalid_options = []
74
+
75
+ @banner ||= sloptions[:banner]
76
+ @strict = sloptions[:strict]
67
77
  @multiple_switches = sloptions[:multiple_switches]
78
+ @on_empty = sloptions[:on_empty]
79
+ @sloptions = sloptions
68
80
 
69
81
  if block_given?
70
82
  block.arity == 1 ? yield(self) : instance_eval(&block)
@@ -106,9 +118,8 @@ class Slop
106
118
  end
107
119
 
108
120
  # Enumerable interface
109
- def each
110
- return enum_for(:each) unless block_given?
111
- @options.each { |option| yield option }
121
+ def each(&block)
122
+ @options.each(&block)
112
123
  end
113
124
 
114
125
  # Return the value of an option via the subscript operator.
@@ -119,7 +130,7 @@ class Slop
119
130
  # @return [Object] Returns the value associated with that option.
120
131
  def [](key)
121
132
  option = @options[key]
122
- option.argument_value if option
133
+ option ? option.argument_value : @commands[key]
123
134
  end
124
135
 
125
136
  # Specify an option with a short or long version, description and type.
@@ -155,6 +166,50 @@ class Slop
155
166
  alias :opt :option
156
167
  alias :on :option
157
168
 
169
+
170
+ # Namespace options depending on what command is executed
171
+ #
172
+ # @param [Symbol, String] label
173
+ # @param [Hash] options
174
+ # @example
175
+ # opts = Slop.new do
176
+ # command :create do
177
+ # on :v, :verbose
178
+ # end
179
+ # end
180
+ #
181
+ # # ARGV is `create -v`
182
+ # opts.commands[:create].verbose? #=> true
183
+ # @return [Slop] a new instance of Slop namespaced to +label+
184
+ def command(label, options={}, &block)
185
+ if @commands[label]
186
+ raise ArgumentError, "command `#{label}` already exists"
187
+ end
188
+
189
+ options = @sloptions.merge(options)
190
+ slop = Slop.new(options)
191
+ @commands[label] = slop
192
+
193
+ if block_given?
194
+ block.arity == 1 ? yield(slop) : slop.instance_eval(&block)
195
+ end
196
+
197
+ slop
198
+ end
199
+
200
+ # Add an object to be called when Slop has no values to parse
201
+ #
202
+ # @param [Object, nil] proc The object (which can be anything
203
+ # responding to :call)
204
+ # @example
205
+ # Slop.parse do
206
+ # on_empty { puts 'No argument given!' }
207
+ # end
208
+ def on_empty(obj=nil, &block)
209
+ @on_empty ||= (obj || block)
210
+ end
211
+ alias :on_empty= :on_empty
212
+
158
213
  # Returns the parsed list into a option/value hash.
159
214
  #
160
215
  # @example
@@ -197,21 +252,32 @@ class Slop
197
252
 
198
253
  private
199
254
 
200
- def self.initialize_and_parse(items, delete, options, &block)
201
- if items.is_a?(Hash) && options.empty?
202
- options = items
203
- items = ARGV
204
- end
255
+ class << self
256
+ private
205
257
 
206
- slop = new(options, &block)
207
- delete ? slop.parse!(items) : slop.parse(items)
208
- slop
258
+ def initialize_and_parse(items, delete, options, &block)
259
+ if items.is_a?(Hash) && options.empty?
260
+ options = items
261
+ items = ARGV
262
+ end
263
+
264
+ slop = new(options, &block)
265
+ delete ? slop.parse!(items) : slop.parse(items)
266
+ slop
267
+ end
209
268
  end
210
269
 
211
270
  def parse_items(items, delete=false, &block)
271
+ if items.empty? && @on_empty.respond_to?(:call)
272
+ @on_empty.call self
273
+ return
274
+ end
275
+
276
+ return if execute_command(items, delete)
277
+
212
278
  trash = []
213
279
 
214
- items.each do |item|
280
+ items.each_with_index do |item, index|
215
281
  item = item.to_s
216
282
  flag = item.sub(/^--?/, '')
217
283
  option = @options[flag]
@@ -230,7 +296,8 @@ class Slop
230
296
  option = @options[$1]
231
297
  argument = $2
232
298
  when /\A--no-(.+)\z/
233
- option.force_argument_value(false) if option = @options[$1]
299
+ option = @options[$1]
300
+ option.force_argument_value(false) if option
234
301
  end
235
302
  end
236
303
 
@@ -240,47 +307,47 @@ class Slop
240
307
  option.argument_value = true
241
308
 
242
309
  if option.expects_argument? || option.accepts_optional_argument?
243
- argument ||= items.at(items.index(item) + 1)
244
- check_valid_argument(option, argument)
310
+ argument ||= items.at(index + 1)
311
+ check_valid_argument!(option, argument)
245
312
  trash << argument
246
313
 
247
314
  if argument
248
- check_matching_argument(option, argument)
315
+ check_matching_argument!(option, argument)
249
316
  option.argument_value = argument
250
317
  option.callback.call option.argument_value if option.callback
251
318
  else
252
319
  option.argument_value = nil
253
- check_optional_argument(option, flag)
320
+ check_optional_argument!(option, flag)
254
321
  end
255
322
  elsif option.callback
256
323
  option.callback.call nil
257
324
  end
258
325
  else
259
- check_invalid_option(item, flag)
326
+ check_invalid_option!(item, flag)
260
327
  block.call(item) if block_given? && !trash.include?(item)
261
328
  end
262
329
  end
263
330
 
264
331
  items.delete_if { |item| trash.include? item } if delete
265
- raise_if_invalid_options
332
+ raise_if_invalid_options!
266
333
  items
267
334
  end
268
335
 
269
- def check_valid_argument(option, argument)
336
+ def check_valid_argument!(option, argument)
270
337
  if !option.accepts_optional_argument? && argument =~ /\A--?.+\z/
271
338
  raise MissingArgumentError,
272
339
  "'#{option.key}' expects an argument, none given"
273
340
  end
274
341
  end
275
342
 
276
- def check_matching_argument(option, argument)
343
+ def check_matching_argument!(option, argument)
277
344
  if option.match && !argument.match(option.match)
278
345
  raise InvalidArgumentError,
279
346
  "'#{argument}' does not match #{option.match.inspect}"
280
347
  end
281
348
  end
282
349
 
283
- def check_optional_argument(option, flag)
350
+ def check_optional_argument!(option, flag)
284
351
  if option.accepts_optional_argument?
285
352
  option.callback.call nil if option.callback
286
353
  else
@@ -289,10 +356,18 @@ class Slop
289
356
  end
290
357
  end
291
358
 
292
- def check_invalid_option(item, flag)
359
+ def check_invalid_option!(item, flag)
293
360
  @invalid_options << flag if item[/\A--?/] && @strict
294
361
  end
295
362
 
363
+ def raise_if_invalid_options!
364
+ return if !@strict || @invalid_options.empty?
365
+ message = "Unknown option"
366
+ message << 's' if @invalid_options.size > 1
367
+ message << ' -- ' << @invalid_options.map { |o| "'#{o}'" }.join(', ')
368
+ raise InvalidOptionError, message
369
+ end
370
+
296
371
  def enable_multiple_switches(item)
297
372
  item[1..-1].split('').each do |switch|
298
373
  if option = @options[switch]
@@ -310,6 +385,17 @@ class Slop
310
385
  end
311
386
  end
312
387
 
388
+ def execute_command(items, delete)
389
+ command = items[0]
390
+ command = @commands.keys.find { |cmd| cmd.to_s == command.to_s }
391
+ if @commands.key?(command)
392
+ items.shift
393
+ opts = @commands[command]
394
+ delete ? opts.parse!(items) : opts.parse(items)
395
+ true
396
+ end
397
+ end
398
+
313
399
  def clean_options(args)
314
400
  options = []
315
401
 
@@ -332,12 +418,4 @@ class Slop
332
418
  options.push args.first.respond_to?(:to_sym) ? args.shift : nil
333
419
  options.push args.shift ? true : false # force true/false
334
420
  end
335
-
336
- def raise_if_invalid_options
337
- return if !@strict || @invalid_options.empty?
338
- message = "Unknown option"
339
- message << 's' if @invalid_options.size > 1
340
- message << ' -- ' << @invalid_options.map { |o| "'#{o}'" }.join(', ')
341
- raise InvalidOptionError, message
342
- end
343
421
  end
data/lib/slop/option.rb CHANGED
@@ -152,6 +152,8 @@ class Slop
152
152
  Integer($1) .. Integer($2)
153
153
  when /\A(\d+?)\.\.\.(\d+)\z/
154
154
  Integer($1) ... Integer($2)
155
+ when /\A\d+\z/
156
+ Integer(value)
155
157
  else
156
158
  value
157
159
  end
@@ -0,0 +1,72 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class CommandsTest < TestCase
4
+ test 'creating commands' do
5
+ slop = Slop.new do
6
+ command :foo do on :f, :foo, 'foo option' end
7
+ command :bar do on :f, :foo; on :b, :bar, true end
8
+ end
9
+
10
+ slop.commands.each_value do |command|
11
+ assert_kind_of Slop, command
12
+ end
13
+
14
+ assert 'foo option', slop.commands[:foo].options[:foo].description
15
+
16
+ slop.parse %w/bar --bar baz/
17
+ assert 'baz', slop.commands[:bar][:bar]
18
+ assert_nil slop.commands['bar']
19
+ end
20
+
21
+ test 'repeating existing commands' do
22
+ slop = Slop.new
23
+ assert slop.command :foo
24
+ assert_raises(ArgumentError) { slop.command :foo }
25
+ end
26
+
27
+ test 'commands inheriting options' do
28
+ slop = Slop.new :strict do
29
+ command :foo do end
30
+ end
31
+ assert slop.commands[:foo].instance_variable_get(:@strict)
32
+ end
33
+
34
+ test 'commands setting options' do
35
+ slop = Slop.new :strict => false do
36
+ command :foo, :strict => true do end
37
+ end
38
+ assert slop.commands[:foo].instance_variable_get(:@strict)
39
+ end
40
+
41
+ test 'inception' do
42
+ slop = Slop.new do
43
+ command(:foo) { command(:bar) { command(:baz) { on :f, 'D:' } } }
44
+ end
45
+ desc = slop.commands[:foo].commands[:bar].commands[:baz].options[:f].description
46
+ assert_equal 'D:', desc
47
+ end
48
+
49
+ test 'commands with banners' do
50
+ slop = Slop.new do
51
+ command(:foo, :banner => 'bar') { }
52
+ command(:bar) { banner 'bar' }
53
+ end
54
+ assert_equal 'bar', slop.commands[:foo].banner
55
+ assert_equal 'bar', slop.commands[:bar].banner
56
+ end
57
+
58
+ test 'executing on_empty on separate commands' do
59
+ incmd = inslop = false
60
+ slop = Slop.new do
61
+ command(:foo) { on(:bar) {}; on_empty { incmd = true }}
62
+ on_empty { inslop = true }
63
+ end
64
+ slop.parse %w//
65
+ assert inslop
66
+ refute incmd
67
+ inslop = false
68
+ slop.parse %w/foo/
69
+ assert incmd
70
+ refute inslop
71
+ end
72
+ end
data/test/option_test.rb CHANGED
@@ -74,8 +74,10 @@ class OptionTest < TestCase
74
74
  assert_equal (1...10), option_value(%w/-r 1...10/, :r, true, :as => Range)
75
75
 
76
76
  # default back to the string unless a regex is successful
77
+ # return value.to_i if the value is /\A\d+\z/
77
78
  # maybe this should raise is Slop#strict?
78
79
  assert_equal "1abc10", option_value(%w/-r 1abc10/, :r, true, :as => Range)
80
+ assert_equal 1, option_value(%w/-r 1/, :r, true, :as => Range)
79
81
  end
80
82
 
81
83
  test 'printing options' do
data/test/slop_test.rb CHANGED
@@ -1,5 +1,3 @@
1
- require File.dirname(__FILE__) + '/helper'
2
-
3
1
  class SlopTest < TestCase
4
2
  def clean_options(*args)
5
3
  Slop.new.send(:clean_options, args)
@@ -19,7 +17,6 @@ class SlopTest < TestCase
19
17
 
20
18
  test 'new accepts a hash or array of symbols' do
21
19
  slop = Slop.new :strict, :multiple_switches => true
22
-
23
20
  [ :@multiple_switches, :@strict ].each do |var|
24
21
  assert slop.instance_variable_get var
25
22
  end
@@ -44,6 +41,14 @@ class SlopTest < TestCase
44
41
  end
45
42
  end
46
43
 
44
+ test 'callback when option array is empty' do
45
+ item1 = item2 = nil
46
+ Slop.new { on_empty { item1 = 'foo' } }.parse
47
+
48
+ assert_equal 'foo', item1
49
+ assert_nil item2
50
+ end
51
+
47
52
  test 'multiple switches with the :multiple_switches flag' do
48
53
  slop = Slop.new :multiple_switches => true, :strict => true
49
54
  %w/a b c/.each { |f| slop.on f }
@@ -86,10 +91,8 @@ class SlopTest < TestCase
86
91
 
87
92
  test 'preserving order when yielding non-options' do
88
93
  items = []
89
-
90
94
  slop = Slop.new { on(:name, true) { |name| items << name } }
91
95
  slop.parse(%w/foo --name bar baz/) { |value| items << value }
92
-
93
96
  assert_equal %w/foo bar baz/, items
94
97
  end
95
98
 
@@ -105,6 +108,9 @@ class SlopTest < TestCase
105
108
 
106
109
  slop = Slop.new "foo bar"
107
110
  assert_equal "foo bar", slop.banner
111
+
112
+ slop = Slop.new :banner => "foo bar"
113
+ assert_equal "foo bar", slop.banner
108
114
  end
109
115
 
110
116
  test 'storing long option lengths' do
@@ -120,7 +126,6 @@ class SlopTest < TestCase
120
126
  opts = Slop.new do
121
127
  on :name, true
122
128
  end
123
-
124
129
  assert_equal %w/a/, opts.parse!(%w/--name lee a/)
125
130
  assert_equal %w/--name lee a/, opts.parse(%w/--name lee a/)
126
131
  end
@@ -173,13 +178,21 @@ class SlopTest < TestCase
173
178
  assert_equal(['c', nil, nil, false], clean_options(:c, false))
174
179
  end
175
180
 
176
- test '[] returns an options argument value or nil' do
181
+ test '[] returns an options argument value or a command or nil (in that order)' do
177
182
  slop = Slop.new
178
183
  slop.opt :n, :name, true
179
- slop.parse %w/--name lee/
184
+ slop.opt :foo
185
+ slop.command(:foo) { }
186
+ slop.command(:bar) { }
187
+ slop.parse %w/--name lee --foo/
180
188
 
181
189
  assert_equal 'lee', slop[:name]
182
190
  assert_equal 'lee', slop[:n]
191
+
192
+ assert_equal true, slop[:foo]
193
+ assert_kind_of Slop, slop[:bar]
194
+
195
+ assert_nil slop[:baz]
183
196
  end
184
197
 
185
198
  test 'arguments ending ? test for option existance' do
@@ -238,7 +251,6 @@ class SlopTest < TestCase
238
251
  slop = Slop.new
239
252
  slop.banner = 'Usage: foo [options]'
240
253
  slop.parse
241
-
242
254
  assert slop.to_s =~ /^Usage: foo/
243
255
  end
244
256
 
@@ -299,7 +311,6 @@ class SlopTest < TestCase
299
311
 
300
312
  test 'parsing options with options as arguments' do
301
313
  slop = Slop.new { on :f, :foo, true }
302
-
303
314
  assert_raises(Slop::MissingArgumentError) { slop.parse %w/-f --bar/ }
304
315
  end
305
316
  end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: slop
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 1.4.1
5
+ version: 1.5.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Lee Jarvis
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-04-14 00:00:00 +01:00
13
+ date: 2011-04-16 00:00:00 +01:00
14
14
  default_executable:
15
15
  dependencies: []
16
16
 
@@ -33,6 +33,7 @@ files:
33
33
  - lib/slop/option.rb
34
34
  - lib/slop/options.rb
35
35
  - slop.gemspec
36
+ - test/commands_test.rb
36
37
  - test/helper.rb
37
38
  - test/option_test.rb
38
39
  - test/slop_test.rb
@@ -65,6 +66,7 @@ signing_key:
65
66
  specification_version: 3
66
67
  summary: Option gathering made easy
67
68
  test_files:
69
+ - test/commands_test.rb
68
70
  - test/helper.rb
69
71
  - test/option_test.rb
70
72
  - test/slop_test.rb