docopt 0.5.0 → 0.6.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4547ac54342e6534a9fa8abb147aa8499c4f6fa3
4
+ data.tar.gz: 0cf00312ed6b06b89ee8d98b8f35da4da7468b27
5
+ SHA512:
6
+ metadata.gz: f4531743745caedcbe2acbb46122f9282bf6eb8fe3eabafd44a206df8b63e4167e4a4ca21d41d8794d05ced6edb9a94a70286b0e4c8871b112d233e0530c255b
7
+ data.tar.gz: 6b77409ae7e165a37ec30d909fd105b171d1461084fc9acc4e15d29bccf12be7999d2f8d1895729f7ae7c2acf8e1ecd570e650b2607932d0da1e5596b2e57f8a
@@ -13,8 +13,8 @@ Gem::Specification.new do |s|
13
13
  ## If your rubyforge_project name is different, then edit it and comment out
14
14
  ## the sub! line in the Rakefile
15
15
  s.name = 'docopt'
16
- s.version = '0.5.0'
17
- s.date = '2012-09-01'
16
+ s.version = '0.6.0'
17
+ s.date = '2016-10-03'
18
18
  # s.rubyforge_project = 'docopt'
19
19
 
20
20
  ## Make sure your summary is short. The description may be as long
@@ -51,7 +51,7 @@ Gem::Specification.new do |s|
51
51
 
52
52
  ## List your development dependencies here. Development dependencies are
53
53
  ## those that are only needed during development
54
- s.add_development_dependency('json', "~> 1.6.5")
54
+ s.add_development_dependency('json', '~> 1.6', '>= 1.6.5')
55
55
 
56
56
  ## Leave this section as-is. It will be automatically generated from the
57
57
  ## contents of your Git repository via the gemspec task. DO NOT REMOVE
@@ -72,7 +72,9 @@ Gem::Specification.new do |s|
72
72
  examples/odd_even_example.rb
73
73
  examples/quick_example.rb
74
74
  lib/docopt.rb
75
+ test/language_agnostic_tester.py
75
76
  test/test_docopt.rb
77
+ test/testcases.docopt
76
78
  test/testee.rb
77
79
  ]
78
80
  # = MANIFEST =
@@ -1,5 +1,5 @@
1
1
  module Docopt
2
- VERSION = '0.5.0'
2
+ VERSION = '0.6.0'
3
3
  end
4
4
  module Docopt
5
5
  class DocoptLanguageError < SyntaxError
@@ -19,7 +19,7 @@ module Docopt
19
19
  end
20
20
 
21
21
  def initialize(message='')
22
- @@message = ((message && message != '' ? (message + "\n") : '') + @@usage)
22
+ @@message = (message + "\n" + @@usage).strip
23
23
  end
24
24
  end
25
25
 
@@ -40,7 +40,7 @@ module Docopt
40
40
 
41
41
  def fix
42
42
  fix_identities
43
- fix_list_arguments
43
+ fix_repeating_arguments
44
44
  return self
45
45
  end
46
46
 
@@ -62,11 +62,15 @@ module Docopt
62
62
  end
63
63
  end
64
64
 
65
- def fix_list_arguments
65
+ def fix_repeating_arguments
66
66
  either.children.map { |c| c.children }.each do |case_|
67
67
  case_.select { |c| case_.count(c) > 1 }.each do |e|
68
68
  if e.class == Argument or (e.class == Option and e.argcount > 0)
69
- e.value = []
69
+ if e.value == nil
70
+ e.value = []
71
+ elsif e.value.class != Array
72
+ e.value = e.value.split
73
+ end
70
74
  end
71
75
  if e.class == Command or (e.class == Option and e.argcount == 0)
72
76
  e.value = 0
@@ -100,6 +104,11 @@ module Docopt
100
104
  children.slice!(children.index(optional))
101
105
  groups << optional.children + children
102
106
 
107
+ elsif types.include?(AnyOptions)
108
+ anyoptions = children.select { |c| c.class == AnyOptions }[0]
109
+ children.slice!(children.index(anyoptions))
110
+ groups << anyoptions.children + children
111
+
103
112
  elsif types.include?(OneOrMore)
104
113
  oneormore = children.select { |c| c.class == OneOrMore }[0]
105
114
  children.slice!(children.index(oneormore))
@@ -128,8 +137,12 @@ module Docopt
128
137
  "#{self.class.name}(#{self.name}, #{self.value})"
129
138
  end
130
139
 
131
- def flat
132
- [self]
140
+ def flat(*types)
141
+ if types.empty? or types.include?(self.class)
142
+ [self]
143
+ else
144
+ []
145
+ end
133
146
  end
134
147
 
135
148
 
@@ -145,7 +158,11 @@ module Docopt
145
158
 
146
159
  same_name = collected.select { |a| a.name == self.name }
147
160
  if @value.is_a? Array or @value.is_a? Integer
148
- increment = @value.is_a?(Integer) ? 1 : [match.value]
161
+ if @value.is_a? Integer
162
+ increment = 1
163
+ else
164
+ increment = match.value.is_a?(String) ? [match.value] : match.value
165
+ end
149
166
  if same_name.count == 0
150
167
  match.value = increment
151
168
  return [true, left_, collected + [match]]
@@ -169,17 +186,17 @@ module Docopt
169
186
  return "#{self.class.name}(#{childstr.join(", ")})"
170
187
  end
171
188
 
172
- def flat
173
- self.children.map { |c| c.flat }.flatten
189
+ def flat(*types)
190
+ if types.include?(self.class)
191
+ [self]
192
+ else
193
+ self.children.map { |c| c.flat(*types) }.flatten
194
+ end
174
195
  end
175
196
  end
176
197
 
177
198
  class Argument < ChildPattern
178
199
 
179
- # def initialize(*args)
180
- # super(*args)
181
- # end
182
-
183
200
  def single_match(left)
184
201
  left.each_with_index do |p, n|
185
202
  if p.class == Argument
@@ -188,6 +205,12 @@ module Docopt
188
205
  end
189
206
  return [nil, nil]
190
207
  end
208
+
209
+ def self.parse(class_, source)
210
+ name = /(<\S*?>)/.match(source)[0]
211
+ value = /\[default: (.*)\]/i.match(source)
212
+ class_.new(name, (value ? value[0] : nil))
213
+ end
191
214
  end
192
215
 
193
216
 
@@ -235,8 +258,7 @@ module Docopt
235
258
  short, long, argcount, value = nil, nil, 0, false
236
259
  options, _, description = option_description.strip.partition(' ')
237
260
 
238
- options.gsub!(",", " ")
239
- options.gsub!("=", " ")
261
+ options = options.gsub(',', ' ').gsub('=', ' ')
240
262
 
241
263
  for s in options.split
242
264
  if s.start_with?('--')
@@ -251,8 +273,7 @@ module Docopt
251
273
  matched = description.scan(/\[default: (.*)\]/i)
252
274
  value = matched[0][0] if matched.count > 0
253
275
  end
254
- ret = self.new(short, long, argcount, value)
255
- return ret
276
+ new(short, long, argcount, value)
256
277
  end
257
278
 
258
279
  def single_match(left)
@@ -299,6 +320,9 @@ module Docopt
299
320
  end
300
321
  end
301
322
 
323
+ class AnyOptions < Optional
324
+ end
325
+
302
326
  class OneOrMore < ParentPattern
303
327
  def match(left, collected=nil)
304
328
  if self.children.count != 1
@@ -339,10 +363,9 @@ module Docopt
339
363
  end
340
364
 
341
365
  if outcomes.count > 0
342
- ret = outcomes.min_by do |outcome|
366
+ return outcomes.min_by do |outcome|
343
367
  outcome[1] == nil ? 0 : outcome[1].count
344
368
  end
345
- return ret
346
369
  end
347
370
  return [false, left, collected]
348
371
  end
@@ -372,93 +395,88 @@ module Docopt
372
395
 
373
396
  class << self
374
397
  def parse_long(tokens, options)
375
- raw, eq, value = tokens.move().partition('=')
398
+ long, eq, value = tokens.move().partition('=')
399
+ unless long.start_with?('--')
400
+ raise RuntimeError
401
+ end
376
402
  value = (eq == value and eq == '') ? nil : value
377
403
 
378
- opt = options.select { |o| o.long and o.long == raw }
404
+ similar = options.select { |o| o.long and o.long == long }
379
405
 
380
- if tokens.error == Exit and opt == []
381
- opt = options.select { |o| o.long and o.long.start_with?(raw) }
406
+ if tokens.error == Exit and similar == []
407
+ similar = options.select { |o| o.long and o.long.start_with?(long) }
382
408
  end
383
409
 
384
- if opt.count < 1
410
+ if similar.count > 1
411
+ ostr = similar.map { |o| o.long }.join(', ')
412
+ raise tokens.error, "#{long} is not a unique prefix: #{ostr}?"
413
+ elsif similar.count < 1
414
+ argcount = (eq == '=' ? 1 : 0)
415
+ o = Option.new(nil, long, argcount)
416
+ options << o
385
417
  if tokens.error == Exit
386
- raise tokens.error, "#{raw} is not recognized"
387
- else
388
- o = Option.new(nil, raw, eq == '=' ? 1 : 0)
389
- options << o
390
- return [o]
418
+ o = Option.new(nil, long, argcount, (argcount == 1 ? value : true))
391
419
  end
392
- end
393
- if opt.count > 1
394
- ostr = opt.map { |o| o.long }.join(', ')
395
- raise tokens.error, "#{raw} is not a unique prefix: #{ostr}?"
396
- end
397
- o = opt[0]
398
- opt = Option.new(o.short, o.long, o.argcount, o.value)
399
- if opt.argcount == 1
400
- if value == nil
401
- if tokens.current() == nil
402
- raise tokens.error, "#{opt.name} requires argument"
420
+ else
421
+ s0 = similar[0]
422
+ o = Option.new(s0.short, s0.long, s0.argcount, s0.value)
423
+ if o.argcount == 0
424
+ if !value.nil?
425
+ raise tokens.error, "#{o.long} must not have an argument"
426
+ end
427
+ else
428
+ if value.nil?
429
+ if tokens.current().nil?
430
+ raise tokens.error, "#{o.long} requires argument"
431
+ end
432
+ value = tokens.move()
403
433
  end
404
- value = tokens.move()
405
434
  end
406
- elsif value != nil
407
- raise tokens.error, "#{opt.name} must not have an argument"
408
- end
409
-
410
- if tokens.error == Exit
411
- opt.value = value ? value : true
412
- else
413
- opt.value = value ? nil : false
435
+ if tokens.error == Exit
436
+ o.value = (!value.nil? ? value : true)
437
+ end
414
438
  end
415
- return [opt]
439
+ return [o]
416
440
  end
417
441
 
418
442
  def parse_shorts(tokens, options)
419
- raw = tokens.move()[1..-1]
443
+ token = tokens.move()
444
+ unless token.start_with?('-') && !token.start_with?('--')
445
+ raise RuntimeError
446
+ end
447
+ left = token[1..-1]
420
448
  parsed = []
421
- while raw != ''
422
- first = raw.slice(0, 1)
423
- opt = options.select { |o| o.short and o.short.sub(/^-+/, '').start_with?(first) }
424
-
425
- if opt.count > 1
426
- raise tokens.error, "-#{first} is specified ambiguously #{opt.count} times"
427
- end
428
-
429
- if opt.count < 1
449
+ while left != ''
450
+ short, left = '-' + left[0], left[1..-1]
451
+ similar = options.select { |o| o.short == short }
452
+ if similar.count > 1
453
+ raise tokens.error, "#{short} is specified ambiguously #{similar.count} times"
454
+ elsif similar.count < 1
455
+ o = Option.new(short, nil, 0)
456
+ options << o
430
457
  if tokens.error == Exit
431
- raise tokens.error, "-#{first} is not recognized"
432
- else
433
- o = Option.new('-' + first, nil)
434
- options << o
435
- parsed << o
436
- raw = raw[1..-1]
437
- next
458
+ o = Option.new(short, nil, 0, true)
438
459
  end
439
- end
440
-
441
- o = opt[0]
442
- opt = Option.new(o.short, o.long, o.argcount, o.value)
443
- raw = raw[1..-1]
444
- if opt.argcount == 0
445
- value = tokens.error == Exit ? true : false
446
460
  else
447
- if raw == ''
448
- if tokens.current() == nil
449
- raise tokens.error, "-#{opt.short.slice(0, 1)} requires argument"
461
+ s0 = similar[0]
462
+ o = Option.new(short, s0.long, s0.argcount, s0.value)
463
+ value = nil
464
+ if o.argcount != 0
465
+ if left == ''
466
+ if tokens.current().nil?
467
+ raise tokens.error, "#{short} requires argument"
468
+ end
469
+ value = tokens.move()
470
+ else
471
+ value = left
472
+ left = ''
450
473
  end
451
- raw = tokens.move()
452
474
  end
453
- value, raw = raw, ''
454
- end
455
-
456
- if tokens.error == Exit
457
- opt.value = value
458
- else
459
- opt.value = value ? nil : false
475
+ if tokens.error == Exit
476
+ o.value = (!value.nil? ? value : true)
477
+ end
460
478
  end
461
- parsed << opt
479
+ parsed << o
462
480
  end
463
481
  return parsed
464
482
  end
@@ -524,21 +542,19 @@ module Docopt
524
542
  return [result]
525
543
  elsif token == 'options'
526
544
  tokens.move()
527
- return options
545
+ return [AnyOptions.new]
528
546
  elsif token.start_with?('--') and token != '--'
529
547
  return parse_long(tokens, options)
530
548
  elsif token.start_with?('-') and not ['-', '--'].include? token
531
549
  return parse_shorts(tokens, options)
532
-
533
- elsif token.start_with?('<') and token.end_with?('>') or token.upcase == token
550
+ elsif token.start_with?('<') and token.end_with?('>') or (token.upcase == token && token.match(/[A-Z]/))
534
551
  return [Argument.new(tokens.move())]
535
552
  else
536
553
  return [Command.new(tokens.move())]
537
554
  end
538
555
  end
539
556
 
540
- def parse_argv(source, options)
541
- tokens = TokenStream.new(source, Exit)
557
+ def parse_argv(tokens, options, options_first=false)
542
558
  parsed = []
543
559
  while tokens.current() != nil
544
560
  if tokens.current() == '--'
@@ -547,6 +563,8 @@ module Docopt
547
563
  parsed += parse_long(tokens, options)
548
564
  elsif tokens.current().start_with?('-') and tokens.current() != '-'
549
565
  parsed += parse_shorts(tokens, options)
566
+ elsif options_first
567
+ return parsed + tokens.map { |v| Argument.new(nil, v) }
550
568
  else
551
569
  parsed << Argument.new(nil, tokens.move())
552
570
  end
@@ -554,8 +572,10 @@ module Docopt
554
572
  return parsed
555
573
  end
556
574
 
557
- def parse_doc_options(doc)
558
- return doc.split(/^ *-|\n *-/)[1..-1].map { |s| Option.parse('-' + s) }
575
+ def parse_defaults(doc)
576
+ split = doc.split(/^ *(<\S+?>|-\S+?)/).drop(1)
577
+ split = split.each_slice(2).reject { |pair| pair.count != 2 }.map { |s1, s2| s1 + s2 }
578
+ split.select { |s| s.start_with?('-') }.map { |s| Option.parse(s) }
559
579
  end
560
580
 
561
581
  def printable_usage(doc)
@@ -566,14 +586,14 @@ module Docopt
566
586
  if usage_split.count > 3
567
587
  raise DocoptLanguageError, 'More than one "usage:" (case-insensitive).'
568
588
  end
569
- return usage_split[1..-1].join().split(/\n\s*\n/)[0].strip
589
+ return usage_split.drop(1).join().split(/\n\s*\n/)[0].strip
570
590
  end
571
591
 
572
592
  def formal_usage(printable_usage)
573
- pu = printable_usage.split()[1..-1] # split and drop "usage:"
593
+ pu = printable_usage.split().drop(1) # split and drop "usage:"
574
594
 
575
595
  ret = []
576
- for s in pu[1..-1]
596
+ for s in pu.drop(1)
577
597
  if s == pu[0]
578
598
  ret << ') | ('
579
599
  else
@@ -612,50 +632,37 @@ module Docopt
612
632
  end
613
633
 
614
634
  def extras(help, version, options, doc)
615
- ofound = false
616
- vfound = false
617
- for o in options
618
- if o.value and (o.name == '-h' or o.name == '--help')
619
- ofound = true
620
- end
621
- if o.value and (o.name == '--version')
622
- vfound = true
623
- end
624
- end
625
-
626
- if help and ofound
635
+ if help and options.any? { |o| ['-h', '--help'].include?(o.name) && o.value }
627
636
  Exit.set_usage(nil)
628
637
  raise Exit, doc.strip
629
638
  end
630
- if version and vfound
639
+ if version and options.any? { |o| o.name == '--version' && o.value }
631
640
  Exit.set_usage(nil)
632
641
  raise Exit, version
633
642
  end
634
643
  end
635
644
 
636
645
  def docopt(doc, params={})
637
- default = {:version => nil, :argv => nil, :help => true}
646
+ default = {:version => nil, :argv => nil, :help => true, :options_first => false}
638
647
  params = default.merge(params)
639
648
  params[:argv] = ARGV if !params[:argv]
640
649
 
641
650
  Exit.set_usage(printable_usage(doc))
642
- options = parse_doc_options(doc)
651
+ options = parse_defaults(doc)
643
652
  pattern = parse_pattern(formal_usage(Exit.usage), options)
644
- argv = parse_argv(params[:argv], options)
653
+ argv = parse_argv(TokenStream.new(params[:argv], Exit), options, params[:options_first])
654
+ pattern_options = pattern.flat(Option).uniq
655
+ pattern.flat(AnyOptions).each do |ao|
656
+ doc_options = parse_defaults(doc)
657
+ ao.children = doc_options.reject { |o| pattern_options.include?(o) }.uniq
658
+ end
645
659
  extras(params[:help], params[:version], argv, doc)
646
660
 
647
661
  matched, left, collected = pattern.fix().match(argv)
648
662
  collected ||= []
649
663
 
650
- if matched and (!left or left.count == 0)
651
- ret = {}
652
- for a in pattern.flat + options + collected
653
- name = a.name
654
- if name and name != ''
655
- ret[name] = a.value
656
- end
657
- end
658
- return ret
664
+ if matched and (left.count == 0)
665
+ return Hash[(pattern.flat + collected).map { |a| [a.name, a.value] }]
659
666
  end
660
667
  raise Exit
661
668
  end