slop 1.4.1 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
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