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.
- checksums.yaml +7 -0
- data/docopt.gemspec +5 -3
- data/lib/docopt.rb +129 -122
- data/test/language_agnostic_tester.py +48 -0
- data/test/test_docopt.rb +7 -44
- data/test/testcases.docopt +909 -0
- data/test/testee.rb +0 -0
- metadata +25 -18
checksums.yaml
ADDED
@@ -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
|
data/docopt.gemspec
CHANGED
@@ -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.
|
17
|
-
s.date = '
|
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',
|
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 =
|
data/lib/docopt.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Docopt
|
2
|
-
VERSION = '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 = (
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
404
|
+
similar = options.select { |o| o.long and o.long == long }
|
379
405
|
|
380
|
-
if tokens.error == Exit and
|
381
|
-
|
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
|
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
|
-
|
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
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
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
|
-
|
407
|
-
|
408
|
-
|
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 [
|
439
|
+
return [o]
|
416
440
|
end
|
417
441
|
|
418
442
|
def parse_shorts(tokens, options)
|
419
|
-
|
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
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
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
|
-
|
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
|
-
|
448
|
-
|
449
|
-
|
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
|
-
|
454
|
-
|
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 <<
|
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
|
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(
|
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
|
558
|
-
|
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
|
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()
|
593
|
+
pu = printable_usage.split().drop(1) # split and drop "usage:"
|
574
594
|
|
575
595
|
ret = []
|
576
|
-
for s in pu
|
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
|
-
|
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
|
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 =
|
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 (
|
651
|
-
|
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
|