slop 3.3.3 → 3.4.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,11 @@
1
+ 3.4.0 (2013-01-12)
2
+ ------------------
3
+
4
+ * Implement new command system (#95)
5
+ * Deprecate Slop::Commands
6
+ * Ensure 'no-foo' options are not inverted when parsing '--no-foo' (#86)
7
+ * Code refactoring and simplification (Kenichi Kamiya, #84, #85)
8
+
1
9
  3.3.3 (2012-08-29)
2
10
  ------------------
3
11
 
data/README.md CHANGED
@@ -2,61 +2,33 @@ Slop
2
2
  ====
3
3
 
4
4
  Slop is a simple option parser with an easy to remember syntax and friendly API.
5
-
6
- This README is targeted at Slop v3.
5
+ API Documentation is available [here](http://injekt.github.com/rdoc/slop/).
7
6
 
8
7
  [![Build Status](https://secure.travis-ci.org/injekt/slop.png)](http://travis-ci.org/injekt/slop)
9
8
 
10
- Installation
11
- ------------
12
-
13
- ### Rubygems
14
-
15
- gem install slop
16
-
17
- ### GitHub
18
-
19
- git clone git://github.com/injekt/slop.git
20
- gem build slop.gemspec
21
- gem install slop-<version>.gem
22
-
23
9
  Usage
24
10
  -----
25
11
 
26
12
  ```ruby
27
- # parse assumes ARGV, otherwise you can pass it your own Array
28
13
  opts = Slop.parse do
29
- banner "ruby foo.rb [options]\n"
30
- on :name=, 'Your name'
31
- on :p, :password, 'Your password', :argument => :optional
32
- on :v, :verbose, 'Enable verbose mode'
14
+ banner 'Usage: foo.rb [options]'
15
+
16
+ on 'name=', 'Your name'
17
+ on 'p', 'password', 'An optional password', argument: :optional
18
+ on 'v', 'verbose', 'Enable verbose mode'
33
19
  end
34
20
 
35
21
  # if ARGV is `--name Lee -v`
36
22
  opts.verbose? #=> true
37
23
  opts.password? #=> false
38
24
  opts[:name] #=> 'lee'
25
+ opts.to_hash #=> {:name=>"Lee", :password=>nil, :verbose=>true}
39
26
  ```
40
27
 
41
- Slop supports several methods of writing options:
42
-
43
- ```ruby
44
- # These options all do the same thing
45
- on '-n', '--name', 'Your name', :argument => true
46
- on 'n', :name=, 'Your name'
47
- on :n, '--name=', 'Your name'
48
-
49
- # As do these
50
- on 'p', '--password', 'Your password', :argument => :optional
51
- on :p, :password, 'Your password', :optional_argument => true
52
- on '-p', 'password=?', 'Your password'
53
- ```
54
-
55
- You can also return your options as a Hash:
28
+ Installation
29
+ ------------
56
30
 
57
- ```ruby
58
- opts.to_hash #=> { :name => 'lee', :verbose => true, :password => nil }
59
- ```
31
+ gem install slop
60
32
 
61
33
  Printing Help
62
34
  -------------
@@ -84,13 +56,88 @@ All of these options can be sent to `Slop.new` or `Slop.parse` in Hash form.
84
56
  * `longest_flag` - The longest string flag, used to aid configuring help
85
57
  text. **default:** *0*.
86
58
 
87
- Features
59
+ Lists
60
+ -----
61
+
62
+ ```ruby
63
+ opts = Slop.parse do
64
+ on :list=, as: Array
65
+ end
66
+ # ruby run.rb --list one,two
67
+ opts[:list] #=> ["one", "two"]
68
+ # ruby run.rb --list one,two --list three
69
+ opts[:list] #=> ["one", "two", "three"]
70
+ ```
71
+
72
+ You can also specify a delimiter and limit.
73
+
74
+ ```ruby
75
+ opts = Slop.parse do
76
+ on :list=, as: Array, delimiter: ':', limit: 2
77
+ end
78
+ # ruby run.rb --list one:two:three
79
+ opts[:list] #=> ["one", "two:three"]
80
+ ```
81
+
82
+ Ranges
83
+ ------
84
+
85
+ ```ruby
86
+ opts = Slop.parse do
87
+ on :range=, as: Range
88
+ end
89
+ # ruby run.rb --range 1..10
90
+ opts[:range] #=> 1..10
91
+ # ruby run.rb --range 1...10
92
+ opts[:range] #=> 1...10
93
+ # ruby run.rb --range 1-10
94
+ opts[:range] #=> 1..10
95
+ # ruby run.rb --range 1,10
96
+ opts[:range] #=> 1..10
97
+ ```
98
+
99
+ Autocreate
100
+ ----------
101
+
102
+ Slop has an 'autocreate' feature. This feature is intended to create
103
+ options on the fly, without having to specify them yourself. In some case,
104
+ uses this code could be all you need in your application:
105
+
106
+ ```ruby
107
+ # ruby run.rb --foo bar --baz --name lee
108
+ opts = Slop.parse(autocreate: true)
109
+ opts.to_hash #=> {:foo=>"bar", :baz=>true, :name=>"lee"}
110
+ opts.fetch_option(:name).expects_argument? #=> true
111
+ ```
112
+
113
+ Commands
88
114
  --------
89
115
 
90
- Check out the following wiki pages for more features:
116
+ Slop supports git style sub-commands, like so:
117
+
118
+ ```ruby
119
+ opts = Slop.parse do
120
+ on '-v', 'Print the version' do
121
+ puts "Version 1.0"
122
+ end
91
123
 
92
- * [Ranges](https://github.com/injekt/slop/wiki/Ranges)
93
- * [Auto Create](https://github.com/injekt/slop/wiki/Auto-Create)
124
+ command 'add' do
125
+ on :v, :verbose, 'Enable verbose mode'
126
+ on :name, 'Your name'
127
+
128
+ run do |opts, args|
129
+ puts "You ran 'add' with options #{opts.to_hash} and args: #{args.inspect}"
130
+ end
131
+ end
132
+ end
133
+
134
+ # ruby run.rb -v
135
+ #=> Version 1.0
136
+ # ruby run.rb add -v foo --name Lee
137
+ #=> You ran 'add' with options {:verbose=>true,:name=>"Lee"} and args ["foo"]
138
+ opts.to_hash(true) # Pass true to tell Slop to merge sub-command option values.
139
+ # => { :v => nil, :add => { :v => true, :name => "Lee" } }
140
+ ```
94
141
 
95
142
  Woah woah, why you hating on OptionParser?
96
143
  ------------------------------------------
@@ -132,4 +179,4 @@ opts = Slop.parse do
132
179
  end
133
180
 
134
181
  opts.to_hash #=> { :name => 'lee', :age => 105 }
135
- ```
182
+ ```
data/Rakefile CHANGED
@@ -10,4 +10,20 @@ Rake::TestTask.new do |t|
10
10
  t.test_files = Dir['test/*_test.rb']
11
11
  end
12
12
 
13
+ begin
14
+ require 'rdoc/task'
15
+
16
+ # requires sdoc and horo gems
17
+ RDoc::Task.new do |rdoc|
18
+ rdoc.title = 'Slop API Documentation'
19
+ rdoc.rdoc_dir = 'doc'
20
+ rdoc.options << '-f' << 'sdoc'
21
+ rdoc.options << '-T' << 'rails'
22
+ rdoc.options << '-e' << 'UTF-8'
23
+ rdoc.options << '-g'
24
+ rdoc.rdoc_files.include('lib/**/*.rb')
25
+ end
26
+ rescue LoadError
27
+ end
28
+
13
29
  task :default => :test
@@ -4,7 +4,7 @@ require 'slop/commands'
4
4
  class Slop
5
5
  include Enumerable
6
6
 
7
- VERSION = '3.3.3'
7
+ VERSION = '3.4.0'
8
8
 
9
9
  # The main Error class, all Exception classes inherit from this class.
10
10
  class Error < StandardError; end
@@ -51,7 +51,7 @@ class Slop
51
51
  #
52
52
  # Returns a new instance of Slop.
53
53
  def parse(items = ARGV, config = {}, &block)
54
- init_and_parse(items, false, config, &block)
54
+ parse! items.dup, config, &block
55
55
  end
56
56
 
57
57
  # items - The Array of items to extract options from (default: ARGV).
@@ -60,7 +60,10 @@ class Slop
60
60
  #
61
61
  # Returns a new instance of Slop.
62
62
  def parse!(items = ARGV, config = {}, &block)
63
- init_and_parse(items, true, config, &block)
63
+ config, items = items, ARGV if items.is_a?(Hash) && config.empty?
64
+ slop = Slop.new config, &block
65
+ slop.parse! items
66
+ slop
64
67
  end
65
68
 
66
69
  # Build a Slop object from a option specification.
@@ -106,22 +109,6 @@ class Slop
106
109
  opts
107
110
  end
108
111
 
109
- private
110
-
111
- # Convenience method used by ::parse and ::parse!.
112
- #
113
- # items - The Array of items to parse.
114
- # delete - When true, executes #parse! over #parse.
115
- # config - The Hash of configuration options to pass to Slop.new.
116
- # block - The optional block to pass to Slop.new
117
- #
118
- # Returns a newly created instance of Slop.
119
- def init_and_parse(items, delete, config, &block)
120
- config, items = items, ARGV if items.is_a?(Hash) && config.empty?
121
- slop = Slop.new(config, &block)
122
- delete ? slop.parse!(items) : slop.parse(items)
123
- slop
124
- end
125
112
  end
126
113
 
127
114
  # The Hash of configuration options for this Slop instance.
@@ -137,11 +124,13 @@ class Slop
137
124
  def initialize(config = {}, &block)
138
125
  @config = DEFAULT_OPTIONS.merge(config)
139
126
  @options = []
127
+ @commands = {}
140
128
  @trash = []
141
129
  @triggered_options = []
142
130
  @unknown_options = []
143
131
  @callbacks = {}
144
132
  @separators = {}
133
+ @runner = nil
145
134
 
146
135
  if block_given?
147
136
  block.arity == 1 ? yield(self) : instance_eval(&block)
@@ -164,8 +153,6 @@ class Slop
164
153
  # Set the banner.
165
154
  #
166
155
  # banner - The String to set the banner.
167
- #
168
- # Returns nothing.
169
156
  def banner=(banner)
170
157
  config[:banner] = banner
171
158
  end
@@ -180,6 +167,33 @@ class Slop
180
167
  config[:banner]
181
168
  end
182
169
 
170
+ # Set the description (used for commands).
171
+ #
172
+ # desc - The String to set the description.
173
+ def description=(desc)
174
+ config[:description] = desc
175
+ end
176
+
177
+ # Get or set the description (used for commands).
178
+ #
179
+ # desc - The String to set the description.
180
+ #
181
+ # Returns the description String.
182
+ def description(desc = nil)
183
+ config[:description] = desc if desc
184
+ config[:description]
185
+ end
186
+
187
+ # Add a new command.
188
+ #
189
+ # command - The Symbol or String used to identify this command.
190
+ # options - A Hash of configuration options (see Slop::new)
191
+ #
192
+ # Returns a new instance of Slop mapped to this command.
193
+ def command(command, options = {}, &block)
194
+ @commands[command.to_s] = Slop.new(options, &block)
195
+ end
196
+
183
197
  # Parse a list of items, executing and gathering options along the way.
184
198
  #
185
199
  # items - The Array of items to extract options from (default: ARGV).
@@ -187,7 +201,8 @@ class Slop
187
201
  #
188
202
  # Returns an Array of original items.
189
203
  def parse(items = ARGV, &block)
190
- parse_items(items, false, &block)
204
+ parse! items.dup, &block
205
+ items
191
206
  end
192
207
 
193
208
  # Parse a list of items, executing and gathering options along the way.
@@ -199,7 +214,39 @@ class Slop
199
214
  #
200
215
  # Returns an Array of original items with options removed.
201
216
  def parse!(items = ARGV, &block)
202
- parse_items(items, true, &block)
217
+ if items.empty? && @callbacks[:empty]
218
+ @callbacks[:empty].each { |cb| cb.call(self) }
219
+ return items
220
+ end
221
+
222
+ if cmd = @commands[items[0]]
223
+ return cmd.parse! items[1..-1]
224
+ end
225
+
226
+ items.each_with_index do |item, index|
227
+ @trash << index && break if item == '--'
228
+ autocreate(items, index) if config[:autocreate]
229
+ process_item(items, index, &block) unless @trash.include?(index)
230
+ end
231
+ items.reject!.with_index { |item, index| @trash.include?(index) }
232
+
233
+ missing_options = options.select { |opt| opt.required? && opt.count < 1 }
234
+ if missing_options.any?
235
+ raise MissingOptionError,
236
+ "Missing required option(s): #{missing_options.map(&:key).join(', ')}"
237
+ end
238
+
239
+ if @unknown_options.any?
240
+ raise InvalidOptionError, "Unknown options #{@unknown_options.join(', ')}"
241
+ end
242
+
243
+ if @triggered_options.empty? && @callbacks[:no_options]
244
+ @callbacks[:no_options].each { |cb| cb.call(self) }
245
+ end
246
+
247
+ @runner.call(self, items) if @runner.respond_to?(:call)
248
+
249
+ items
203
250
  end
204
251
 
205
252
  # Add an Option.
@@ -232,8 +279,14 @@ class Slop
232
279
  alias get []
233
280
 
234
281
  # Returns a new Hash with option flags as keys and option values as values.
235
- def to_hash
236
- Hash[options.map { |opt| [opt.key.to_sym, opt.value] }]
282
+ #
283
+ # include_commands - If true, merge options from all sub-commands.
284
+ def to_hash(include_commands = false)
285
+ hash = Hash[options.map { |opt| [opt.key.to_sym, opt.value] }]
286
+ if include_commands
287
+ @commands.each { |cmd, opts| hash.merge!(cmd.to_sym => opts.to_hash) }
288
+ end
289
+ hash
237
290
  end
238
291
  alias to_h to_hash
239
292
 
@@ -242,6 +295,29 @@ class Slop
242
295
  options.each(&block)
243
296
  end
244
297
 
298
+ # Specify code to be executed when these options are parsed.
299
+ #
300
+ # callable - An object responding to a call method.
301
+ #
302
+ # yields - The instance of Slop parsing these options
303
+ # An Array of unparsed arguments
304
+ #
305
+ # Example:
306
+ #
307
+ # Slop.parse do
308
+ # on :v, :verbose
309
+ #
310
+ # run do |opts, args|
311
+ # puts "Arguments: #{args.inspect}" if opts.verbose?
312
+ # end
313
+ # end
314
+ def run(callable = nil, &block)
315
+ @runner = callable || block
316
+ unless @runner.respond_to?(:call)
317
+ raise ArgumentError, "You must specify a callable object or a block to #run"
318
+ end
319
+ end
320
+
245
321
  # Check for an options presence.
246
322
  #
247
323
  # Examples:
@@ -255,35 +331,11 @@ class Slop
255
331
  keys.all? { |key| (opt = fetch_option(key)) && opt.count > 0 }
256
332
  end
257
333
 
258
- # Convenience method for present?(:option).
259
- #
260
- # Examples:
261
- #
262
- # opts.parse %( --verbose )
263
- # opts.verbose? #=> true
264
- # opts.other? #=> false
265
- #
266
- # Returns true if this option is present. If this method does not end
267
- # with a ? character it will instead call super().
268
- def method_missing(method, *args, &block)
269
- meth = method.to_s
270
- if meth.end_with?('?')
271
- present?(meth.chop)
272
- else
273
- super
274
- end
275
- end
276
-
277
334
  # Override this method so we can check if an option? method exists.
278
335
  #
279
336
  # Returns true if this option key exists in our list of options.
280
- def respond_to?(method)
281
- method = method.to_s
282
- if method.end_with?('?') && options.any? { |o| o.key == method.chop }
283
- true
284
- else
285
- super
286
- end
337
+ def respond_to_missing?(method_name, include_private = false)
338
+ options.any? { |o| o.key == method_name.to_s.chop } || super
287
339
  end
288
340
 
289
341
  # Fetch a list of options which were missing from the parsed list.
@@ -319,6 +371,22 @@ class Slop
319
371
  options.find { |option| [option.long, option.short].include?(clean(key)) }
320
372
  end
321
373
 
374
+ # Fetch a Slop object associated with this command.
375
+ #
376
+ # command - The String or Symbol name of the command.
377
+ #
378
+ # Examples:
379
+ #
380
+ # opts.command :foo do
381
+ # on :v, :verbose, 'Enable verbose mode'
382
+ # end
383
+ #
384
+ # # ruby run.rb foo -v
385
+ # opts.fetch_command(:foo).verbose? #=> true
386
+ def fetch_command(command)
387
+ @commands[command.to_s]
388
+ end
389
+
322
390
  # Add a callback.
323
391
  #
324
392
  # label - The Symbol identifier to attach this callback.
@@ -350,59 +418,43 @@ class Slop
350
418
  (str = @separators[i + 1]) ? [o, str].join("\n") : o
351
419
  }.join("\n")
352
420
 
353
- if config[:banner]
354
- config[:banner] << "\n"
355
- config[:banner] << "#{@separators[0]}\n" if @separators[0]
356
- config[:banner] + optstr
421
+ if @commands.any?
422
+ optstr << "\n" if !optstr.empty?
423
+ optstr << "\nAvailable commands:\n\n"
424
+ optstr << commands_to_help
425
+ optstr << "\n\nSee `<command> --help` for more information on a specific command."
426
+ end
427
+
428
+ banner = config[:banner]
429
+ banner = "Usage: #{File.basename($0, '.*')}#{' [command]' if @commands.any?} [options]" if banner.nil?
430
+ if banner
431
+ "#{banner}\n#{@separators[0] ? "#{@separators[0]}\n" : ''}#{optstr}"
357
432
  else
358
433
  optstr
359
434
  end
360
435
  end
361
436
  alias help to_s
362
437
 
363
- # Returns the String inspection text.
364
- def inspect
365
- "#<Slop #{config.inspect} #{options.map(&:inspect)}>"
366
- end
367
-
368
438
  private
369
439
 
370
- # Parse a list of items and process their values.
440
+ # Convenience method for present?(:option).
371
441
  #
372
- # items - The Array of items to process.
373
- # delete - True to remove any triggered options and arguments from the
374
- # original list of items.
375
- # block - An optional block which when passed will yields non-options.
442
+ # Examples:
376
443
  #
377
- # Returns the original Array of items.
378
- def parse_items(items, delete, &block)
379
- if items.empty? && @callbacks[:empty]
380
- @callbacks[:empty].each { |cb| cb.call(self) }
381
- return items
382
- end
383
-
384
- items.each_with_index do |item, index|
385
- @trash << index && break if item == '--'
386
- autocreate(items, index) if config[:autocreate]
387
- process_item(items, index, &block) unless @trash.include?(index)
388
- end
389
- items.reject!.with_index { |item, index| @trash.include?(index) } if delete
390
-
391
- missing_options = options.select { |opt| opt.required? && opt.count < 1 }
392
- if missing_options.any?
393
- raise MissingOptionError,
394
- "Missing required option(s): #{missing_options.map(&:key).join(', ')}"
395
- end
396
-
397
- if @unknown_options.any?
398
- raise InvalidOptionError, "Unknown options #{@unknown_options.join(', ')}"
399
- end
400
-
401
- if @triggered_options.empty? && @callbacks[:no_options]
402
- @callbacks[:no_options].each { |cb| cb.call(self) }
444
+ # opts.parse %( --verbose )
445
+ # opts.verbose? #=> true
446
+ # opts.other? #=> false
447
+ #
448
+ # Returns true if this option is present. If this method does not end
449
+ # with a ? character it will instead call super().
450
+ def method_missing(method, *args, &block)
451
+ meth = method.to_s
452
+ if meth.end_with?('?')
453
+ meth.chop!
454
+ present?(meth) || present?(meth.gsub('_', '-'))
455
+ else
456
+ super
403
457
  end
404
-
405
- items
406
458
  end
407
459
 
408
460
  # Process a list item, figure out if it's an option, execute any
@@ -419,6 +471,7 @@ class Slop
419
471
 
420
472
  if option
421
473
  option.count += 1 unless item.start_with?('--no-')
474
+ option.count += 1 if option.key[0, 3] == "no-"
422
475
  @trash << index
423
476
  @triggered_options << option
424
477
 
@@ -548,7 +601,7 @@ class Slop
548
601
  config[:optional_argument] = true if @config[:optional_arguments]
549
602
 
550
603
  if objects.last.is_a?(Hash)
551
- config = config.merge!(objects.last)
604
+ config.merge!(objects.last)
552
605
  objects.pop
553
606
  end
554
607
  short = extract_short_flag(objects, config)
@@ -599,4 +652,12 @@ class Slop
599
652
  object.to_s.sub(/\A--?/, '')
600
653
  end
601
654
 
655
+ def commands_to_help
656
+ padding = 0
657
+ @commands.each { |c, _| padding = c.size if c.size > padding }
658
+ @commands.map do |cmd, opts|
659
+ " #{cmd}#{' ' * (padding - cmd.size)} #{opts.description}"
660
+ end.join("\n")
661
+ end
662
+
602
663
  end
@@ -39,6 +39,10 @@ class Slop
39
39
  @banner = nil
40
40
  @triggered_command = nil
41
41
 
42
+ warn "[DEPRECATED] Slop::Commands is deprecated and will be removed in "\
43
+ "Slop version 4. Check out http://injekt.github.com/slop/#commands for "\
44
+ "a new implementation of commands."
45
+
42
46
  if block_given?
43
47
  block.arity == 1 ? yield(self) : instance_eval(&block)
44
48
  end
@@ -108,18 +112,19 @@ class Slop
108
112
  key.to_s == @triggered_command
109
113
  end
110
114
 
115
+ # Enumerable interface.
116
+ def each(&block)
117
+ @commands.each(&block)
118
+ end
119
+
111
120
  # Parse a list of items.
112
121
  #
113
122
  # items - The Array of items to parse.
114
123
  #
115
124
  # Returns the original Array of items.
116
125
  def parse(items = ARGV)
117
- parse_items(items)
118
- end
119
-
120
- # Enumerable interface.
121
- def each(&block)
122
- @commands.each(&block)
126
+ parse! items.dup
127
+ items
123
128
  end
124
129
 
125
130
  # Parse a list of items, removing any options or option arguments found.
@@ -128,7 +133,22 @@ class Slop
128
133
  #
129
134
  # Returns the original Array of items with options removed.
130
135
  def parse!(items = ARGV)
131
- parse_items(items, true)
136
+ if opts = commands[items[0].to_s]
137
+ @triggered_command = items.shift
138
+ execute_arguments! items
139
+ opts.parse! items
140
+ execute_global_opts! items
141
+ else
142
+ if opts = commands['default']
143
+ opts.parse! items
144
+ else
145
+ if config[:strict] && items[0]
146
+ raise InvalidCommandError, "Unknown command `#{items[0]}`"
147
+ end
148
+ end
149
+ execute_global_opts! items
150
+ end
151
+ items
132
152
  end
133
153
 
134
154
  # Returns a nested Hash with Slop options and values. See Slop#to_hash.
@@ -141,8 +161,12 @@ class Slop
141
161
  defaults = commands.delete('default')
142
162
  globals = commands.delete('global')
143
163
  helps = commands.reject { |_, v| v.options.none? }
144
- helps.merge!('Global options' => globals.to_s) if globals
145
- helps.merge!('Other options' => defaults.to_s) if defaults
164
+ if globals && globals.options.any?
165
+ helps.merge!('Global options' => globals.to_s)
166
+ end
167
+ if defaults && defaults.options.any?
168
+ helps.merge!('Other options' => defaults.to_s)
169
+ end
146
170
  banner = @banner ? "#{@banner}\n" : ""
147
171
  banner + helps.map { |key, opts| " #{key}\n#{opts}" }.join("\n\n")
148
172
  end
@@ -155,43 +179,18 @@ class Slop
155
179
 
156
180
  private
157
181
 
158
- # Parse a list of items.
159
- #
160
- # items - The Array of items to parse.
161
- # bang - When true, #parse! will be called instead of #parse.
162
- #
163
- # Returns the Array of items (with options removed if bang == true).
164
- def parse_items(items, bang = false)
165
- if opts = commands[items[0].to_s]
166
- @triggered_command = items.shift
167
- execute_arguments(items, bang)
168
- bang ? opts.parse!(items) : opts.parse(items)
169
- execute_global_opts(items, bang)
170
- else
171
- if opts = commands['default']
172
- bang ? opts.parse!(items) : opts.parse(items)
173
- else
174
- if config[:strict] && items[0]
175
- raise InvalidCommandError, "Unknown command `#{items[0]}`"
176
- end
177
- end
178
- execute_global_opts(items, bang)
179
- end
180
- items
181
- end
182
-
183
182
  # Returns nothing.
184
- def execute_arguments(items, bang)
183
+ def execute_arguments!(items)
185
184
  @arguments = items.take_while { |arg| !arg.start_with?('-') }
186
- items.shift(@arguments.size) if bang
185
+ items.shift @arguments.size
187
186
  end
188
187
 
189
188
  # Returns nothing.
190
- def execute_global_opts(items, bang)
189
+ def execute_global_opts!(items)
191
190
  if global_opts = commands['global']
192
- bang ? global_opts.parse!(items) : global_opts.parse(items)
191
+ global_opts.parse! items
193
192
  end
194
193
  end
195
194
 
196
195
  end
197
- end
196
+ end
@@ -56,7 +56,7 @@ class Slop
56
56
  @config.each_key do |key|
57
57
  predicate = :"#{key}?"
58
58
  unless self.class.method_defined? predicate
59
- self.class.send(:define_method, predicate) { !!@config[key] }
59
+ self.class.__send__(:define_method, predicate) { !!@config[key] }
60
60
  end
61
61
  end
62
62
  end
@@ -126,16 +126,15 @@ class Slop
126
126
  def to_s
127
127
  return config[:help] if config[:help].respond_to?(:to_str)
128
128
 
129
- out = " "
130
- out += short ? "-#{short}, " : ' ' * 4
129
+ out = " #{short ? "-#{short}, " : ' ' * 4}"
131
130
 
132
131
  if long
133
- out += "--#{long}"
132
+ out << "--#{long}"
134
133
  size = long.size
135
134
  diff = @slop.config[:longest_flag] - size
136
- out += " " * (diff + 6)
135
+ out << (' ' * (diff + 6))
137
136
  else
138
- out += " " * (@slop.config[:longest_flag] + 8)
137
+ out << (' ' * (@slop.config[:longest_flag] + 8))
139
138
  end
140
139
 
141
140
  "#{out}#{description}"
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'slop'
3
- s.version = '3.3.3'
3
+ s.version = '3.4.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'
@@ -9,6 +9,8 @@ Gem::Specification.new do |s|
9
9
  s.files = `git ls-files`.split("\n")
10
10
  s.test_files = `git ls-files -- test/*`.split("\n")
11
11
 
12
+ s.required_ruby_version = '>= 1.8.7'
13
+
12
14
  s.add_development_dependency 'rake'
13
15
  s.add_development_dependency 'minitest'
14
16
  end
@@ -13,6 +13,16 @@ class CommandsTest < TestCase
13
13
  add_callback(:empty) { 'version 1' }
14
14
  end
15
15
  end
16
+
17
+ @empty_commands = Slop::Commands.new do
18
+ default do
19
+ end
20
+
21
+ global do
22
+ end
23
+
24
+ on 'verbose'
25
+ end
16
26
  end
17
27
 
18
28
  test "it nests instances of Slop" do
@@ -75,6 +85,10 @@ class CommandsTest < TestCase
75
85
  assert_kind_of Slop, @commands.global
76
86
  end
77
87
 
88
+ test "empty default/global blocks don't add their titles in the help output" do
89
+ assert_empty @empty_commands.to_s
90
+ end
91
+
78
92
  test "parse does nothing when there's nothing to parse" do
79
93
  assert @commands.parse []
80
94
  end
@@ -99,4 +113,26 @@ class CommandsTest < TestCase
99
113
  assert_equal %w( file1 file2 ), @commands.arguments
100
114
  end
101
115
 
116
+ test "context and return value of constructor block" do
117
+ peep = nil
118
+ ret = Slop::Commands.new { peep = self }
119
+ assert_same ret, peep
120
+ assert !equal?(peep)
121
+
122
+ peep = nil
123
+ ret = Slop::Commands.new { |a| peep = self }
124
+ assert !peep.equal?(ret)
125
+ assert_same peep, self
126
+
127
+ peep = nil
128
+ ret = Slop::Commands.new { |a, b| peep = self }
129
+ assert_same ret, peep
130
+ assert !equal?(peep)
131
+
132
+ peep = nil
133
+ ret = Slop::Commands.new { |a, *rest| peep = self }
134
+ assert_same ret, peep
135
+ assert !equal?(peep)
136
+ end
137
+
102
138
  end
@@ -314,7 +314,7 @@ class SlopTest < TestCase
314
314
  end
315
315
 
316
316
  test "separators" do
317
- opts = Slop.new do
317
+ opts = Slop.new(:banner => false) do
318
318
  on :foo
319
319
  separator "hello"
320
320
  separator "world"
@@ -331,10 +331,24 @@ class SlopTest < TestCase
331
331
 
332
332
  test "printing help with :help => true" do
333
333
  temp_stderr do |string|
334
- opts = Slop.new(:help => true)
334
+ opts = Slop.new(:help => true, :banner => false)
335
335
  opts.parse %w( --help )
336
336
  assert_equal " -h, --help Display this help message.\n", string
337
337
  end
338
+
339
+ temp_stderr do |string|
340
+ opts = Slop.new(:help => true)
341
+ opts.parse %w( --help )
342
+ assert_equal "Usage: rake_test_loader [options]\n -h, --help Display this help message.\n", string
343
+ end
344
+ end
345
+
346
+ test "fallback to substituting - for _ when using <option>?" do
347
+ opts = Slop.new do
348
+ on 'foo-bar'
349
+ end
350
+ opts.parse %w( --foo-bar )
351
+ assert opts.foo_bar?
338
352
  end
339
353
 
340
354
  test "option=value syntax does NOT consume following argument" do
@@ -344,4 +358,94 @@ class SlopTest < TestCase
344
358
  assert_equal %w' baz hello ', args
345
359
  end
346
360
 
361
+ test "context and return value of constructor block" do
362
+ peep = nil
363
+ ret = Slop.new { peep = self }
364
+ assert_same ret, peep
365
+ assert !equal?(peep)
366
+
367
+ peep = nil
368
+ ret = Slop.new { |a| peep = self }
369
+ assert !peep.equal?(ret)
370
+ assert_same peep, self
371
+
372
+ peep = nil
373
+ ret = Slop.new { |a, b| peep = self }
374
+ assert_same ret, peep
375
+ assert !equal?(peep)
376
+
377
+ peep = nil
378
+ ret = Slop.new { |a, *rest| peep = self }
379
+ assert_same ret, peep
380
+ assert !equal?(peep)
381
+
382
+ peep = nil
383
+ ret = Slop.parse([]) { peep = self }
384
+ assert_same ret, peep
385
+ assert !equal?(peep)
386
+
387
+ peep = nil
388
+ ret = Slop.parse([]) { |a| peep = self }
389
+ assert !peep.equal?(ret)
390
+ assert_same peep, self
391
+
392
+ peep = nil
393
+ ret = Slop.parse([]) { |a, b| peep = self }
394
+ assert_same ret, peep
395
+ assert !equal?(peep)
396
+
397
+ peep = nil
398
+ ret = Slop.parse([]) { |a, *rest| peep = self }
399
+ assert_same ret, peep
400
+ assert !equal?(peep)
401
+ end
402
+
403
+ test "to_s do not break self" do
404
+ slop = Slop.new do
405
+ banner "foo"
406
+ end
407
+
408
+ assert_equal "foo", slop.banner
409
+ slop.to_s
410
+ assert_equal "foo", slop.banner
411
+ end
412
+
413
+ test "options with prefixed --no should not default to inverted behaviour unless intended" do
414
+ opts = Slop.new { on :bar }
415
+ opts.parse %w'--no-bar'
416
+ assert_equal false, opts[:bar]
417
+
418
+ opts = Slop.new { on 'no-bar' }
419
+ opts.parse %w'--no-bar'
420
+ assert_equal true, opts['no-bar']
421
+ end
422
+
423
+ test "method missing() is a private method" do
424
+ assert Slop.new.private_methods.map(&:to_sym).include?(:method_missing)
425
+ end
426
+
427
+ test "respond_to?() arity checker is similar of other objects" do
428
+ slop = Slop.new
429
+ obj = Object.new
430
+
431
+ assert_same obj.respond_to?(:__id__), slop.respond_to?(:__id__)
432
+ assert_same obj.respond_to?(:__id__, false), slop.respond_to?(:__id__, false)
433
+ assert_same obj.respond_to?(:__id__, true), slop.respond_to?(:__id__, true)
434
+
435
+ assert_raises ArgumentError do
436
+ slop.respond_to? :__id__, false, :INVALID_ARGUMENT
437
+ end
438
+ end
439
+
440
+ test "adding a runner" do
441
+ orun = proc { |r| assert_instance_of Slop, r }
442
+ arun = proc { |r| assert_equal ['foo', 'bar'], r }
443
+
444
+ Slop.parse(%w'foo --foo bar -v bar') do
445
+ on :v
446
+ on :foo=
447
+ run { |o, a| orun[o]; arun[a] }
448
+ end
449
+ end
450
+
347
451
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slop
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.3
4
+ version: 3.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-08-29 00:00:00.000000000 Z
12
+ date: 2013-01-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -75,19 +75,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
75
75
  requirements:
76
76
  - - ! '>='
77
77
  - !ruby/object:Gem::Version
78
- version: '0'
79
- segments:
80
- - 0
81
- hash: 3713237527878732625
78
+ version: 1.8.7
82
79
  required_rubygems_version: !ruby/object:Gem::Requirement
83
80
  none: false
84
81
  requirements:
85
82
  - - ! '>='
86
83
  - !ruby/object:Gem::Version
87
84
  version: '0'
88
- segments:
89
- - 0
90
- hash: 3713237527878732625
91
85
  requirements: []
92
86
  rubyforge_project:
93
87
  rubygems_version: 1.8.23
@@ -99,3 +93,4 @@ test_files:
99
93
  - test/helper.rb
100
94
  - test/option_test.rb
101
95
  - test/slop_test.rb
96
+ has_rdoc: