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