trollop 1.4 → 1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/FAQ.txt +6 -4
  2. data/History.txt +7 -0
  3. data/lib/trollop.rb +59 -26
  4. data/test/test_trollop.rb +73 -7
  5. metadata +2 -2
data/FAQ.txt CHANGED
@@ -20,12 +20,14 @@ A: Because it's ambiguous whether these are arguments or negative
20
20
 
21
21
  I could be very clever and detect when there are no arguments
22
22
  that require floating-point parameters, and allow such short option
23
- names in those cases, but I'd rather be simple and consistent.
23
+ names in those cases, but opted for simplicity and consistency.
24
24
 
25
25
  Q: Why does Trollop disallow options appearing multiple times, despite
26
26
  the POSIX standard allowing it?
27
27
  A: Because basically I think it's confusing, and more often than
28
28
  not, a symptom of you making a mistake (e.g. getting lost in a long
29
- command line and accidentally setting the same thing twice.) Given
30
- that, I really don't see that much advantage to "-vvvvv" over "-v
31
- 5", so Trollop will produce an error if it happens.
29
+ command line and accidentally setting the same thing twice.)
30
+ I also don't see that much advantage to "-vvvvv" over "-v 5", so
31
+ Trollop will produce an error if you try to use the same argument
32
+ multiple times.
33
+
@@ -1,3 +1,10 @@
1
+ == 1.5 / 2007-03-31
2
+ * --help and --version do the right thing even if the rest of the
3
+ command line is incorrect.
4
+ * Added #conflicts and #depends to model dependencies and exclusivity
5
+ between arguments.
6
+ * Minor bugfixes.
7
+
1
8
  == 1.4 / 2007-03-26
2
9
  * Disable short options with :short => :none.
3
10
  * Minor bugfixes and error message improvements.
@@ -5,7 +5,7 @@
5
5
 
6
6
  module Trollop
7
7
 
8
- VERSION = "1.4"
8
+ VERSION = "1.5"
9
9
 
10
10
  ## Thrown by Parser in the event of a commandline error. Not needed if
11
11
  ## you're using the Trollop::options entry.
@@ -47,6 +47,7 @@ class Parser
47
47
  @long = {}
48
48
  @short = {}
49
49
  @order = []
50
+ @constraints = []
50
51
 
51
52
  #instance_eval(&b) if b # can't take arguments
52
53
  cloaker(&b).bind(self).call(*a) if b
@@ -170,6 +171,20 @@ class Parser
170
171
  def banner s; @order << [:text, s] end
171
172
  alias :text :banner
172
173
 
174
+ ## Marks two (or more!) options as requiring each other. Only
175
+ ## handles undirected dependcies. Directed dependencies are better
176
+ ## modeled with #die.
177
+ def depends *syms
178
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
179
+ @constraints << [:depends, syms]
180
+ end
181
+
182
+ ## Marks two (or more!) options as conflicting.
183
+ def conflicts *syms
184
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
185
+ @constraints << [:conflicts, syms]
186
+ end
187
+
173
188
  ## yield successive arg, parameter pairs
174
189
  def each_arg args # :nodoc:
175
190
  remains = []
@@ -210,7 +225,7 @@ class Parser
210
225
  remains
211
226
  end
212
227
 
213
- def parse args #:nodoc:
228
+ def parse cmdline #:nodoc:
214
229
  vals = {}
215
230
  required = {}
216
231
  found = {}
@@ -218,13 +233,15 @@ class Parser
218
233
  opt :version, "Print version and exit" if @version unless @specs[:version] || @long["version"]
219
234
  opt :help, "Show this message" unless @specs[:help] || @long["help"]
220
235
 
221
- @specs.each do |name, opts|
222
- required[name] = true if opts[:required]
223
- vals[name] = opts[:default]
236
+ @specs.each do |sym, opts|
237
+ required[sym] = true if opts[:required]
238
+ vals[sym] = opts[:default]
224
239
  end
225
240
 
226
- @leftovers = each_arg args do |arg, param|
227
- name =
241
+ ## resolve symbols
242
+ args = []
243
+ @leftovers = each_arg cmdline do |arg, param|
244
+ sym =
228
245
  case arg
229
246
  when /^-([^-])$/
230
247
  @short[$1]
@@ -233,37 +250,53 @@ class Parser
233
250
  else
234
251
  raise CommandlineError, "invalid argument syntax: '#{arg}'"
235
252
  end
236
- raise CommandlineError, "unknown argument '#{arg}'" unless name
237
- raise CommandlineError, "option '#{arg}' specified multiple times" if found[name]
253
+ raise CommandlineError, "unknown argument '#{arg}'" unless sym
254
+ raise CommandlineError, "option '#{arg}' specified multiple times" if found[sym]
255
+ args << [sym, arg, param]
256
+ found[sym] = true
257
+
258
+ @specs[sym][:type] != :flag # take params on all except flags
259
+ end
260
+
261
+ ## check for version and help args
262
+ raise VersionNeeded if args.any? { |sym, *a| sym == :version }
263
+ raise HelpNeeded if args.any? { |sym, *a| sym == :help }
264
+
265
+ ## check constraint satisfaction
266
+ @constraints.each do |type, syms|
267
+ constraint_sym = syms.find { |sym| found[sym] }
268
+ next unless constraint_sym
269
+
270
+ case type
271
+ when :depends
272
+ syms.each { |sym| raise CommandlineError, "--#{@long[constraint_sym]} requires --#{@long[sym]}" unless found[sym] }
273
+ when :conflicts
274
+ syms.each { |sym| raise CommandlineError, "--#{@long[constraint_sym]} conflicts with --#{@long[sym]}" if found[sym] && sym != constraint_sym }
275
+ end
276
+ end
277
+
278
+ raise CommandlineError, "option '#{required.keys.first}' must be specified" if required.any? { |sym, x| !found[sym] }
238
279
 
239
- raise VersionNeeded if name == :version
240
- raise HelpNeeded if name == :help
280
+ ## parse parameters
281
+ args.each do |sym, arg, param|
282
+ opts = @specs[sym]
241
283
 
242
- found[name] = true
243
- opts = @specs[name]
284
+ raise CommandlineError, "option '#{arg}' needs a parameter" unless param || opts[:type] == :flag
244
285
 
245
286
  case opts[:type]
246
287
  when :flag
247
- vals[name] = !opts[:default]
248
- false
288
+ vals[sym] = !opts[:default]
249
289
  when :int
250
- raise CommandlineError, "option '#{arg}' needs a parameter" unless param
251
290
  raise CommandlineError, "option '#{arg}' needs an integer" unless param =~ /^\d+$/
252
- vals[name] = param.to_i
253
- true
291
+ vals[sym] = param.to_i
254
292
  when :float
255
- raise CommandlineError, "option '#{arg}' needs a parameter" unless param
256
293
  raise CommandlineError, "option '#{arg}' needs a floating-point number" unless param =~ FLOAT_RE
257
- vals[name] = param.to_f
258
- true
294
+ vals[sym] = param.to_f
259
295
  when :string
260
- raise CommandlineError, "option '#{arg}' needs a parameter" unless param
261
- vals[name] = param
262
- true
296
+ vals[sym] = param.to_s
263
297
  end
264
298
  end
265
299
 
266
- raise CommandlineError, "option '#{required.keys.first}' must be specified" if required.any? { |name, x| !found[name] }
267
300
  vals
268
301
  end
269
302
 
@@ -422,7 +455,7 @@ end
422
455
  ## Trollop::die "need at least one filename" if ARGV.empty?
423
456
  def die arg, msg=nil
424
457
  if msg
425
- $stderr.puts "Error: parameter for option '--#{@p.specs[arg][:long]}' or '-#{@p.specs[arg][:short]}' #{msg}."
458
+ $stderr.puts "Error: argument --#{@p.specs[arg][:long]} #{msg}."
426
459
  else
427
460
  $stderr.puts "Error: #{arg}."
428
461
  end
@@ -17,13 +17,13 @@ class Trollop < ::Test::Unit::TestCase
17
17
 
18
18
  def test_unknown_arguments
19
19
  assert_raise(CommandlineError) { @p.parse(%w(--arg)) }
20
- @p.opt "arg", "desc"
20
+ @p.opt "arg"
21
21
  assert_nothing_raised { @p.parse(%w(--arg)) }
22
22
  assert_raise(CommandlineError) { @p.parse(%w(--arg2)) }
23
23
  end
24
24
 
25
25
  def test_syntax_check
26
- @p.opt "arg", "desc"
26
+ @p.opt "arg"
27
27
 
28
28
  assert_nothing_raised { @p.parse(%w(--arg)) }
29
29
  assert_nothing_raised { @p.parse(%w(arg)) }
@@ -54,8 +54,8 @@ class Trollop < ::Test::Unit::TestCase
54
54
 
55
55
  ## flags that don't take arguments ignore them
56
56
  def test_arglessflags_refuse_args
57
- @p.opt "goodarg", "desc"
58
- @p.opt "goodarg2", "desc"
57
+ @p.opt "goodarg"
58
+ @p.opt "goodarg2"
59
59
  assert_nothing_raised { @p.parse(%w(--goodarg)) }
60
60
  assert_nothing_raised { @p.parse(%w(--goodarg --goodarg2)) }
61
61
  opts = @p.parse %w(--goodarg a)
@@ -135,8 +135,8 @@ class Trollop < ::Test::Unit::TestCase
135
135
 
136
136
  ## two args can't have the same name
137
137
  def test_conflicting_names_are_detected
138
- assert_nothing_raised { @p.opt "goodarg", "desc" }
139
- assert_raise(ArgumentError) { @p.opt "goodarg", "desc" }
138
+ assert_nothing_raised { @p.opt "goodarg" }
139
+ assert_raise(ArgumentError) { @p.opt "goodarg" }
140
140
  end
141
141
 
142
142
  ## two args can't have the same :long
@@ -195,7 +195,7 @@ class Trollop < ::Test::Unit::TestCase
195
195
  end
196
196
 
197
197
  def test_version_only_appears_if_set
198
- @p.opt "arg", "desc"
198
+ @p.opt "arg"
199
199
  assert_raise(CommandlineError) { @p.parse %w(-v) }
200
200
  @p.version "trollop 1.2.3.4"
201
201
  assert_raise(VersionNeeded) { @p.parse %w(-v) }
@@ -376,6 +376,72 @@ EOM
376
376
  assert_nothing_raised { @p.parse %w(-v) }
377
377
  end
378
378
 
379
+ def test_version_and_help_override_errors
380
+ @p.opt :asdf, "desc", :type => String
381
+ @p.version "version"
382
+ assert_nothing_raised { @p.parse %w(--asdf goat) }
383
+ assert_raises(CommandlineError) { @p.parse %w(--asdf) }
384
+ assert_raises(HelpNeeded) { @p.parse %w(--asdf --help) }
385
+ assert_raises(VersionNeeded) { @p.parse %w(--asdf --version) }
386
+ end
387
+
388
+ def test_conflicts
389
+ @p.opt :one
390
+ assert_raises(ArgumentError) { @p.conflicts :one, :two }
391
+ @p.opt :two
392
+ assert_nothing_raised { @p.conflicts :one, :two }
393
+ assert_nothing_raised { @p.parse %w(--one) }
394
+ assert_nothing_raised { @p.parse %w(--two) }
395
+ assert_raises(CommandlineError) { opts = @p.parse %w(--one --two) }
396
+
397
+ @p.opt :hello
398
+ @p.opt :yellow
399
+ @p.opt :mellow
400
+ @p.opt :jello
401
+ @p.conflicts :hello, :yellow, :mellow, :jello
402
+ assert_raises(CommandlineError) { opts = @p.parse %w(--hello --yellow --mellow --jello) }
403
+ assert_raises(CommandlineError) { opts = @p.parse %w(--hello --mellow --jello) }
404
+ assert_raises(CommandlineError) { opts = @p.parse %w(--hello --jello) }
405
+
406
+ assert_nothing_raised { opts = @p.parse %w(--hello) }
407
+ assert_nothing_raised { opts = @p.parse %w(--jello) }
408
+ assert_nothing_raised { opts = @p.parse %w(--yellow) }
409
+ assert_nothing_raised { opts = @p.parse %w(--mellow) }
410
+
411
+ assert_nothing_raised { opts = @p.parse %w(--mellow --one) }
412
+ assert_nothing_raised { opts = @p.parse %w(--mellow --two) }
413
+
414
+ assert_raises(CommandlineError) { opts = @p.parse %w(--mellow --two --jello) }
415
+ assert_raises(CommandlineError) { opts = @p.parse %w(--one --mellow --two --jello) }
416
+ end
417
+
418
+ def test_depends
419
+ @p.opt :one
420
+ assert_raises(ArgumentError) { @p.depends :one, :two }
421
+ @p.opt :two
422
+ assert_nothing_raised { @p.depends :one, :two }
423
+ assert_nothing_raised { opts = @p.parse %w(--one --two) }
424
+ assert_raises(CommandlineError) { @p.parse %w(--one) }
425
+ assert_raises(CommandlineError) { @p.parse %w(--two) }
426
+
427
+ @p.opt :hello
428
+ @p.opt :yellow
429
+ @p.opt :mellow
430
+ @p.opt :jello
431
+ @p.depends :hello, :yellow, :mellow, :jello
432
+ assert_nothing_raised { opts = @p.parse %w(--hello --yellow --mellow --jello) }
433
+ assert_raises(CommandlineError) { opts = @p.parse %w(--hello --mellow --jello) }
434
+ assert_raises(CommandlineError) { opts = @p.parse %w(--hello --jello) }
435
+
436
+ assert_raises(CommandlineError) { opts = @p.parse %w(--hello) }
437
+ assert_raises(CommandlineError) { opts = @p.parse %w(--mellow) }
438
+
439
+ assert_nothing_raised { opts = @p.parse %w(--hello --yellow --mellow --jello --one --two) }
440
+ assert_nothing_raised { opts = @p.parse %w(--hello --yellow --mellow --jello --one --two a b c) }
441
+
442
+ assert_raises(CommandlineError) { opts = @p.parse %w(--mellow --two --jello --one) }
443
+ end
444
+
379
445
  end
380
446
 
381
447
  end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: trollop
5
5
  version: !ruby/object:Gem::Version
6
- version: "1.4"
7
- date: 2007-03-26 00:00:00 -07:00
6
+ version: "1.5"
7
+ date: 2007-03-31 00:00:00 -07:00
8
8
  summary: Trollop is YAFCLAP --- yet another fine commandline argument processing library for Ruby. Trollop is designed to provide the maximal amount of GNU-style argument processing in the minimum number of lines of code (for you, the programmer).
9
9
  require_paths:
10
10
  - lib