docopt 0.5.0 → 0.6.0

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