wooga_docopt 0.6.0.1
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/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +188 -0
- data/Rakefile +150 -0
- data/docopt.gemspec +85 -0
- data/examples/any_options_example.rb +24 -0
- data/examples/calculator.rb +16 -0
- data/examples/counted_example.rb +22 -0
- data/examples/example_options.rb +44 -0
- data/examples/git_example.rb +44 -0
- data/examples/naval_fate.rb +30 -0
- data/examples/odd_even_example.rb +19 -0
- data/examples/quick_example.rb +16 -0
- data/lib/docopt.rb +670 -0
- data/test/language_agnostic_tester.py +48 -0
- data/test/test_docopt.rb +12 -0
- data/test/testcases.docopt +909 -0
- data/test/testee.rb +12 -0
- metadata +112 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.expand_path("../../lib/docopt.rb", __FILE__)
|
2
|
+
|
3
|
+
doc = <<DOCOPT
|
4
|
+
Example of program which uses [options] shortcut in pattern.
|
5
|
+
|
6
|
+
Usage:
|
7
|
+
#{__FILE__} [options] <port>
|
8
|
+
|
9
|
+
Options:
|
10
|
+
-h --help show this help message and exit
|
11
|
+
--version show version and exit
|
12
|
+
-n, --number N use N as a number
|
13
|
+
-t, --timeout TIMEOUT set timeout TIMEOUT seconds
|
14
|
+
--apply apply changes to database
|
15
|
+
-q operate in quiet mode
|
16
|
+
|
17
|
+
DOCOPT
|
18
|
+
|
19
|
+
|
20
|
+
begin
|
21
|
+
puts Docopt::docopt(doc, version: '1.0.0rc2').to_s
|
22
|
+
rescue Docopt::Exit => e
|
23
|
+
puts e.message
|
24
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.expand_path("../../lib/docopt.rb", __FILE__)
|
2
|
+
|
3
|
+
doc = <<DOCOPT
|
4
|
+
Usage:
|
5
|
+
#{__FILE__} tcp <host> <port> [--timeout=<seconds>]
|
6
|
+
#{__FILE__} serial <port> [--baud=9600] [--timeout=<seconds>]
|
7
|
+
#{__FILE__} -h | --help | --version
|
8
|
+
|
9
|
+
DOCOPT
|
10
|
+
|
11
|
+
begin
|
12
|
+
require "pp"
|
13
|
+
pp Docopt::docopt(doc)
|
14
|
+
rescue Docopt::Exit => e
|
15
|
+
puts e.message
|
16
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.expand_path("../../lib/docopt.rb", __FILE__)
|
2
|
+
|
3
|
+
doc = <<DOCOPT
|
4
|
+
Usage: #{__FILE__} --help
|
5
|
+
#{__FILE__} -v...
|
6
|
+
#{__FILE__} go [go]
|
7
|
+
#{__FILE__} (--path=<path>)...
|
8
|
+
#{__FILE__} <file> <file>
|
9
|
+
|
10
|
+
Try: #{__FILE__} -vvvvvvvvvv
|
11
|
+
#{__FILE__} go go
|
12
|
+
#{__FILE__} --path ./here --path ./there
|
13
|
+
#{__FILE__} this.txt that.txt
|
14
|
+
|
15
|
+
DOCOPT
|
16
|
+
|
17
|
+
begin
|
18
|
+
require "pp"
|
19
|
+
pp Docopt::docopt(doc)
|
20
|
+
rescue Docopt::Exit => e
|
21
|
+
puts e.message
|
22
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require File.expand_path("../../lib/docopt.rb", __FILE__)
|
2
|
+
|
3
|
+
doc = <<DOCOPT
|
4
|
+
Example of program with many options using docopt.
|
5
|
+
|
6
|
+
Usage:
|
7
|
+
#{__FILE__} [-hvqrf NAME] [--exclude=PATTERNS]
|
8
|
+
[--select=ERRORS | --ignore=ERRORS] [--show-source]
|
9
|
+
[--statistics] [--count] [--benchmark] PATH...
|
10
|
+
#{__FILE__} (--doctest | --testsuite=DIR)
|
11
|
+
#{__FILE__} --version
|
12
|
+
|
13
|
+
Arguments:
|
14
|
+
PATH destination path
|
15
|
+
|
16
|
+
Options:
|
17
|
+
-h --help show this help message and exit
|
18
|
+
--version show version and exit
|
19
|
+
-v --verbose print status messages
|
20
|
+
-q --quiet report only file names
|
21
|
+
-r --repeat show all occurrences of the same error
|
22
|
+
--exclude=PATTERNS exclude files or directories which match these comma
|
23
|
+
separated patterns [default: .svn,CVS,.bzr,.hg,.git]
|
24
|
+
-f NAME --file=NAME when parsing directories, only check filenames matching
|
25
|
+
these comma separated patterns [default: *#{__FILE__}]
|
26
|
+
--select=ERRORS select errors and warnings (e.g. E,W6)
|
27
|
+
--ignore=ERRORS skip errors and warnings (e.g. E4,W)
|
28
|
+
--show-source show source code for each error
|
29
|
+
--statistics count errors and warnings
|
30
|
+
--count print total number of errors and warnings to standard
|
31
|
+
error and set exit code to 1 if total is not null
|
32
|
+
--benchmark measure processing speed
|
33
|
+
--testsuite=DIR run regression tests from dir
|
34
|
+
--doctest run doctest on myself
|
35
|
+
|
36
|
+
|
37
|
+
DOCOPT
|
38
|
+
|
39
|
+
begin
|
40
|
+
require "pp"
|
41
|
+
pp Docopt::docopt(doc)
|
42
|
+
rescue Docopt::Exit => e
|
43
|
+
puts e.message
|
44
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require File.expand_path("../../lib/docopt.rb", __FILE__)
|
2
|
+
|
3
|
+
doc = <<DOCOPT
|
4
|
+
Usage:
|
5
|
+
#{__FILE__} remote [-v | --verbose]
|
6
|
+
#{__FILE__} remote add [-t <branch>] [-m <master>] [-f]
|
7
|
+
[--tags|--no-tags] [--mirror] <name> <url>
|
8
|
+
#{__FILE__} remote rename <old> <new>
|
9
|
+
#{__FILE__} remote rm <name>
|
10
|
+
#{__FILE__} remote set-head <name> (-a | -d | <branch>)
|
11
|
+
#{__FILE__} remote set-branches <name> [--add] <branch>...
|
12
|
+
#{__FILE__} remote set-url [--push] <name> <newurl> [<oldurl>]
|
13
|
+
#{__FILE__} remote set-url --add [--push] <name> <newurl>
|
14
|
+
#{__FILE__} remote set-url --delete [--push] <name> <url>
|
15
|
+
#{__FILE__} remote [-v | --verbose] show [-n] <name>
|
16
|
+
#{__FILE__} remote prune [-n | --dry-run] <name>
|
17
|
+
#{__FILE__} remote [-v | --verbose] update [-p | --prune]
|
18
|
+
[(<group> | <remote>)...]
|
19
|
+
|
20
|
+
Options:
|
21
|
+
-v, --verbose
|
22
|
+
-t <branch>
|
23
|
+
-m <master>
|
24
|
+
-f
|
25
|
+
--tags
|
26
|
+
--no-tags
|
27
|
+
--mittor
|
28
|
+
-a
|
29
|
+
-d
|
30
|
+
-n, --dry-run
|
31
|
+
-p, --prune
|
32
|
+
--add
|
33
|
+
--delete
|
34
|
+
--push
|
35
|
+
--mirror
|
36
|
+
|
37
|
+
DOCOPT
|
38
|
+
|
39
|
+
begin
|
40
|
+
require "pp"
|
41
|
+
pp Docopt::docopt(doc)
|
42
|
+
rescue Docopt::Exit => e
|
43
|
+
puts e.message
|
44
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.expand_path("../../lib/docopt.rb", __FILE__)
|
2
|
+
|
3
|
+
#The *popular* naval fate example
|
4
|
+
|
5
|
+
doc = <<DOCOPT
|
6
|
+
Naval Fate.
|
7
|
+
|
8
|
+
Usage:
|
9
|
+
#{__FILE__} ship new <name>...
|
10
|
+
#{__FILE__} ship <name> move <x> <y> [--speed=<kn>]
|
11
|
+
#{__FILE__} ship shoot <x> <y>
|
12
|
+
#{__FILE__} mine (set|remove) <x> <y> [--moored|--drifting]
|
13
|
+
#{__FILE__} -h | --help
|
14
|
+
#{__FILE__} --version
|
15
|
+
|
16
|
+
Options:
|
17
|
+
-h --help Show this screen.
|
18
|
+
--version Show version.
|
19
|
+
--speed=<kn> Speed in knots [default: 10].
|
20
|
+
--moored Moored (anchored) mine.
|
21
|
+
--drifting Drifting mine.
|
22
|
+
|
23
|
+
DOCOPT
|
24
|
+
|
25
|
+
begin
|
26
|
+
require "pp"
|
27
|
+
pp Docopt::docopt(doc)
|
28
|
+
rescue Docopt::Exit => e
|
29
|
+
puts e.message
|
30
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.expand_path("../../lib/docopt.rb", __FILE__)
|
2
|
+
|
3
|
+
doc = <<DOCOPT
|
4
|
+
Usage: #{__FILE__} [-h | --help] (ODD EVEN)...
|
5
|
+
|
6
|
+
Example, try:
|
7
|
+
#{__FILE__} 1 2 3 4
|
8
|
+
|
9
|
+
Options:
|
10
|
+
-h, --help
|
11
|
+
|
12
|
+
DOCOPT
|
13
|
+
|
14
|
+
begin
|
15
|
+
require "pp"
|
16
|
+
pp Docopt::docopt(doc)
|
17
|
+
rescue Docopt::Exit => e
|
18
|
+
puts e.message
|
19
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.expand_path("../../lib/docopt.rb", __FILE__)
|
2
|
+
|
3
|
+
doc = <<DOCOPT
|
4
|
+
Usage:
|
5
|
+
#{__FILE__} tcp <host> <port> [--timeout=<seconds>]
|
6
|
+
#{__FILE__} serial <port> [--baud=9600] [--timeout=<seconds>]
|
7
|
+
#{__FILE__} -h | --help | --version
|
8
|
+
|
9
|
+
DOCOPT
|
10
|
+
|
11
|
+
begin
|
12
|
+
require "pp"
|
13
|
+
pp Docopt::docopt(doc)
|
14
|
+
rescue Docopt::Exit => e
|
15
|
+
puts e.message
|
16
|
+
end
|
data/lib/docopt.rb
ADDED
@@ -0,0 +1,670 @@
|
|
1
|
+
module Docopt
|
2
|
+
VERSION = '0.6.0'
|
3
|
+
end
|
4
|
+
module Docopt
|
5
|
+
class DocoptLanguageError < SyntaxError
|
6
|
+
end
|
7
|
+
|
8
|
+
class Exit < RuntimeError
|
9
|
+
def self.usage
|
10
|
+
@@usage
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.set_usage(usage)
|
14
|
+
@@usage = usage ? usage : ''
|
15
|
+
end
|
16
|
+
|
17
|
+
def message
|
18
|
+
@@message
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(message='')
|
22
|
+
@@message = (message + "\n" + @@usage).strip
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Pattern
|
27
|
+
attr_accessor :children
|
28
|
+
|
29
|
+
def ==(other)
|
30
|
+
return self.inspect == other.inspect
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_str
|
34
|
+
return self.inspect
|
35
|
+
end
|
36
|
+
|
37
|
+
def dump
|
38
|
+
puts ::Docopt::dump_patterns(self)
|
39
|
+
end
|
40
|
+
|
41
|
+
def fix
|
42
|
+
fix_identities
|
43
|
+
fix_repeating_arguments
|
44
|
+
return self
|
45
|
+
end
|
46
|
+
|
47
|
+
def fix_identities(uniq=nil)
|
48
|
+
if not instance_variable_defined?(:@children)
|
49
|
+
return self
|
50
|
+
end
|
51
|
+
uniq ||= flat.uniq
|
52
|
+
|
53
|
+
@children.each_with_index do |c, i|
|
54
|
+
if not c.instance_variable_defined?(:@children)
|
55
|
+
if !uniq.include?(c)
|
56
|
+
raise RuntimeError
|
57
|
+
end
|
58
|
+
@children[i] = uniq[uniq.index(c)]
|
59
|
+
else
|
60
|
+
c.fix_identities(uniq)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def fix_repeating_arguments
|
66
|
+
either.children.map { |c| c.children }.each do |case_|
|
67
|
+
case_.select { |c| case_.count(c) > 1 }.each do |e|
|
68
|
+
if e.class == Argument or (e.class == Option and e.argcount > 0)
|
69
|
+
if e.value == nil
|
70
|
+
e.value = []
|
71
|
+
elsif e.value.class != Array
|
72
|
+
e.value = e.value.split
|
73
|
+
end
|
74
|
+
end
|
75
|
+
if e.class == Command or (e.class == Option and e.argcount == 0)
|
76
|
+
e.value = 0
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
return self
|
82
|
+
end
|
83
|
+
|
84
|
+
def either
|
85
|
+
ret = []
|
86
|
+
groups = [[self]]
|
87
|
+
while groups.count > 0
|
88
|
+
children = groups.shift
|
89
|
+
types = children.map { |c| c.class }
|
90
|
+
|
91
|
+
if types.include?(Either)
|
92
|
+
either = children.select { |c| c.class == Either }[0]
|
93
|
+
children.slice!(children.index(either))
|
94
|
+
for c in either.children
|
95
|
+
groups << [c] + children
|
96
|
+
end
|
97
|
+
elsif types.include?(Required)
|
98
|
+
required = children.select { |c| c.class == Required }[0]
|
99
|
+
children.slice!(children.index(required))
|
100
|
+
groups << required.children + children
|
101
|
+
|
102
|
+
elsif types.include?(Optional)
|
103
|
+
optional = children.select { |c| c.class == Optional }[0]
|
104
|
+
children.slice!(children.index(optional))
|
105
|
+
groups << optional.children + children
|
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
|
+
|
112
|
+
elsif types.include?(OneOrMore)
|
113
|
+
oneormore = children.select { |c| c.class == OneOrMore }[0]
|
114
|
+
children.slice!(children.index(oneormore))
|
115
|
+
groups << (oneormore.children * 2) + children
|
116
|
+
|
117
|
+
else
|
118
|
+
ret << children
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
args = ret.map { |e| Required.new(*e) }
|
123
|
+
return Either.new(*args)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
class ChildPattern < Pattern
|
129
|
+
attr_accessor :name, :value
|
130
|
+
|
131
|
+
def initialize(name, value=nil)
|
132
|
+
@name = name
|
133
|
+
@value = value
|
134
|
+
end
|
135
|
+
|
136
|
+
def inspect()
|
137
|
+
"#{self.class.name}(#{self.name}, #{self.value})"
|
138
|
+
end
|
139
|
+
|
140
|
+
def flat(*types)
|
141
|
+
if types.empty? or types.include?(self.class)
|
142
|
+
[self]
|
143
|
+
else
|
144
|
+
[]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
def match(left, collected=nil)
|
150
|
+
collected ||= []
|
151
|
+
pos, match = self.single_match(left)
|
152
|
+
if match == nil
|
153
|
+
return [false, left, collected]
|
154
|
+
end
|
155
|
+
|
156
|
+
left_ = left.dup
|
157
|
+
left_.slice!(pos)
|
158
|
+
|
159
|
+
same_name = collected.select { |a| a.name == self.name }
|
160
|
+
if @value.is_a? Array or @value.is_a? Integer
|
161
|
+
if @value.is_a? Integer
|
162
|
+
increment = 1
|
163
|
+
else
|
164
|
+
increment = match.value.is_a?(String) ? [match.value] : match.value
|
165
|
+
end
|
166
|
+
if same_name.count == 0
|
167
|
+
match.value = increment
|
168
|
+
return [true, left_, collected + [match]]
|
169
|
+
end
|
170
|
+
same_name[0].value += increment
|
171
|
+
return [true, left_, collected]
|
172
|
+
end
|
173
|
+
return [true, left_, collected + [match]]
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
class ParentPattern < Pattern
|
178
|
+
attr_accessor :children
|
179
|
+
|
180
|
+
def initialize(*children)
|
181
|
+
@children = children
|
182
|
+
end
|
183
|
+
|
184
|
+
def inspect
|
185
|
+
childstr = self.children.map { |a| a.inspect }
|
186
|
+
return "#{self.class.name}(#{childstr.join(", ")})"
|
187
|
+
end
|
188
|
+
|
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
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
class Argument < ChildPattern
|
199
|
+
|
200
|
+
def single_match(left)
|
201
|
+
left.each_with_index do |p, n|
|
202
|
+
if p.class == Argument
|
203
|
+
return [n, Argument.new(self.name, p.value)]
|
204
|
+
end
|
205
|
+
end
|
206
|
+
return [nil, nil]
|
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
|
214
|
+
end
|
215
|
+
|
216
|
+
|
217
|
+
class Command < Argument
|
218
|
+
def initialize(name, value=false)
|
219
|
+
@name = name
|
220
|
+
@value = value
|
221
|
+
end
|
222
|
+
|
223
|
+
def single_match(left)
|
224
|
+
left.each_with_index do |p, n|
|
225
|
+
if p.class == Argument
|
226
|
+
if p.value == self.name
|
227
|
+
return n, Command.new(self.name, true)
|
228
|
+
else
|
229
|
+
break
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
return [nil, nil]
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
|
238
|
+
class Option < ChildPattern
|
239
|
+
attr_reader :short, :long
|
240
|
+
attr_accessor :argcount
|
241
|
+
|
242
|
+
def initialize(short=nil, long=nil, argcount=0, value=false)
|
243
|
+
unless [0, 1].include? argcount
|
244
|
+
raise RuntimeError
|
245
|
+
end
|
246
|
+
|
247
|
+
@short, @long = short, long
|
248
|
+
@argcount, @value = argcount, value
|
249
|
+
|
250
|
+
if value == false and argcount > 0
|
251
|
+
@value = nil
|
252
|
+
else
|
253
|
+
@value = value
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def self.parse(option_description)
|
258
|
+
short, long, argcount, value = nil, nil, 0, false
|
259
|
+
options, _, description = option_description.strip.partition(' ')
|
260
|
+
|
261
|
+
options = options.gsub(',', ' ').gsub('=', ' ')
|
262
|
+
|
263
|
+
for s in options.split
|
264
|
+
if s.start_with?('--')
|
265
|
+
long = s
|
266
|
+
elsif s.start_with?('-')
|
267
|
+
short = s
|
268
|
+
else
|
269
|
+
argcount = 1
|
270
|
+
end
|
271
|
+
end
|
272
|
+
if argcount > 0
|
273
|
+
matched = description.scan(/\[default: (.*)\]/i)
|
274
|
+
value = matched[0][0] if matched.count > 0
|
275
|
+
end
|
276
|
+
new(short, long, argcount, value)
|
277
|
+
end
|
278
|
+
|
279
|
+
def single_match(left)
|
280
|
+
left.each_with_index do |p, n|
|
281
|
+
if self.name == p.name
|
282
|
+
return [n, p]
|
283
|
+
end
|
284
|
+
end
|
285
|
+
return [nil, nil]
|
286
|
+
end
|
287
|
+
|
288
|
+
def name
|
289
|
+
return self.long ? self.long : self.short
|
290
|
+
end
|
291
|
+
|
292
|
+
def inspect
|
293
|
+
return "Option(#{self.short}, #{self.long}, #{self.argcount}, #{self.value})"
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
class Required < ParentPattern
|
298
|
+
def match(left, collected=nil)
|
299
|
+
collected ||= []
|
300
|
+
l = left
|
301
|
+
c = collected
|
302
|
+
|
303
|
+
for p in self.children
|
304
|
+
matched, l, c = p.match(l, c)
|
305
|
+
if not matched
|
306
|
+
return [false, left, collected]
|
307
|
+
end
|
308
|
+
end
|
309
|
+
return [true, l, c]
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
class Optional < ParentPattern
|
314
|
+
def match(left, collected=nil)
|
315
|
+
collected ||= []
|
316
|
+
for p in self.children
|
317
|
+
m, left, collected = p.match(left, collected)
|
318
|
+
end
|
319
|
+
return [true, left, collected]
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
class AnyOptions < Optional
|
324
|
+
end
|
325
|
+
|
326
|
+
class OneOrMore < ParentPattern
|
327
|
+
def match(left, collected=nil)
|
328
|
+
if self.children.count != 1
|
329
|
+
raise RuntimeError
|
330
|
+
end
|
331
|
+
|
332
|
+
collected ||= []
|
333
|
+
l = left
|
334
|
+
c = collected
|
335
|
+
l_ = nil
|
336
|
+
matched = true
|
337
|
+
times = 0
|
338
|
+
while matched
|
339
|
+
# could it be that something didn't match but changed l or c?
|
340
|
+
matched, l, c = self.children[0].match(l, c)
|
341
|
+
times += (matched ? 1 : 0)
|
342
|
+
if l_ == l
|
343
|
+
break
|
344
|
+
end
|
345
|
+
l_ = l
|
346
|
+
end
|
347
|
+
if times >= 1
|
348
|
+
return [true, l, c]
|
349
|
+
end
|
350
|
+
return [false, left, collected]
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
class Either < ParentPattern
|
355
|
+
def match(left, collected=nil)
|
356
|
+
collected ||= []
|
357
|
+
outcomes = []
|
358
|
+
for p in self.children
|
359
|
+
matched, _, _ = outcome = p.match(left, collected)
|
360
|
+
if matched
|
361
|
+
outcomes << outcome
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
if outcomes.count > 0
|
366
|
+
return outcomes.min_by do |outcome|
|
367
|
+
outcome[1] == nil ? 0 : outcome[1].count
|
368
|
+
end
|
369
|
+
end
|
370
|
+
return [false, left, collected]
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
class TokenStream < Array
|
375
|
+
attr_reader :error
|
376
|
+
|
377
|
+
def initialize(source, error)
|
378
|
+
if !source
|
379
|
+
source = []
|
380
|
+
elsif source.class != ::Array
|
381
|
+
source = source.split
|
382
|
+
end
|
383
|
+
super(source)
|
384
|
+
@error = error
|
385
|
+
end
|
386
|
+
|
387
|
+
def move
|
388
|
+
return self.shift
|
389
|
+
end
|
390
|
+
|
391
|
+
def current
|
392
|
+
return self[0]
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
class << self
|
397
|
+
def parse_long(tokens, options)
|
398
|
+
long, eq, value = tokens.move().partition('=')
|
399
|
+
unless long.start_with?('--')
|
400
|
+
raise RuntimeError
|
401
|
+
end
|
402
|
+
value = (eq == value and eq == '') ? nil : value
|
403
|
+
|
404
|
+
similar = options.select { |o| o.long and o.long == long }
|
405
|
+
|
406
|
+
if tokens.error == Exit and similar == []
|
407
|
+
similar = options.select { |o| o.long and o.long.start_with?(long) }
|
408
|
+
end
|
409
|
+
|
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
|
417
|
+
if tokens.error == Exit
|
418
|
+
o = Option.new(nil, long, argcount, (argcount == 1 ? value : true))
|
419
|
+
end
|
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()
|
433
|
+
end
|
434
|
+
end
|
435
|
+
if tokens.error == Exit
|
436
|
+
o.value = (!value.nil? ? value : true)
|
437
|
+
end
|
438
|
+
end
|
439
|
+
return [o]
|
440
|
+
end
|
441
|
+
|
442
|
+
def parse_shorts(tokens, options)
|
443
|
+
token = tokens.move()
|
444
|
+
unless token.start_with?('-') && !token.start_with?('--')
|
445
|
+
raise RuntimeError
|
446
|
+
end
|
447
|
+
left = token[1..-1]
|
448
|
+
parsed = []
|
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
|
457
|
+
if tokens.error == Exit
|
458
|
+
o = Option.new(short, nil, 0, true)
|
459
|
+
end
|
460
|
+
else
|
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 = ''
|
473
|
+
end
|
474
|
+
end
|
475
|
+
if tokens.error == Exit
|
476
|
+
o.value = (!value.nil? ? value : true)
|
477
|
+
end
|
478
|
+
end
|
479
|
+
parsed << o
|
480
|
+
end
|
481
|
+
return parsed
|
482
|
+
end
|
483
|
+
|
484
|
+
|
485
|
+
def parse_pattern(source, options)
|
486
|
+
tokens = TokenStream.new(source.gsub(/([\[\]\(\)\|]|\.\.\.)/, ' \1 '), DocoptLanguageError)
|
487
|
+
|
488
|
+
result = parse_expr(tokens, options)
|
489
|
+
if tokens.current() != nil
|
490
|
+
raise tokens.error, "unexpected ending: #{tokens.join(" ")}"
|
491
|
+
end
|
492
|
+
return Required.new(*result)
|
493
|
+
end
|
494
|
+
|
495
|
+
|
496
|
+
def parse_expr(tokens, options)
|
497
|
+
seq = parse_seq(tokens, options)
|
498
|
+
if tokens.current() != '|'
|
499
|
+
return seq
|
500
|
+
end
|
501
|
+
result = seq.count > 1 ? [Required.new(*seq)] : seq
|
502
|
+
|
503
|
+
while tokens.current() == '|'
|
504
|
+
tokens.move()
|
505
|
+
seq = parse_seq(tokens, options)
|
506
|
+
result += seq.count > 1 ? [Required.new(*seq)] : seq
|
507
|
+
end
|
508
|
+
return result.count > 1 ? [Either.new(*result)] : result
|
509
|
+
end
|
510
|
+
|
511
|
+
def parse_seq(tokens, options)
|
512
|
+
result = []
|
513
|
+
stop = [nil, ']', ')', '|']
|
514
|
+
while !stop.include?(tokens.current)
|
515
|
+
atom = parse_atom(tokens, options)
|
516
|
+
if tokens.current() == '...'
|
517
|
+
atom = [OneOrMore.new(*atom)]
|
518
|
+
tokens.move()
|
519
|
+
end
|
520
|
+
result += atom
|
521
|
+
end
|
522
|
+
return result
|
523
|
+
end
|
524
|
+
|
525
|
+
def parse_atom(tokens, options)
|
526
|
+
token = tokens.current()
|
527
|
+
result = []
|
528
|
+
|
529
|
+
if ['(' , '['].include? token
|
530
|
+
tokens.move()
|
531
|
+
if token == '('
|
532
|
+
matching = ')'
|
533
|
+
pattern = Required
|
534
|
+
else
|
535
|
+
matching = ']'
|
536
|
+
pattern = Optional
|
537
|
+
end
|
538
|
+
result = pattern.new(*parse_expr(tokens, options))
|
539
|
+
if tokens.move() != matching
|
540
|
+
raise tokens.error, "unmatched '#{token}'"
|
541
|
+
end
|
542
|
+
return [result]
|
543
|
+
elsif token == 'options'
|
544
|
+
tokens.move()
|
545
|
+
return [AnyOptions.new]
|
546
|
+
elsif token.start_with?('--') and token != '--'
|
547
|
+
return parse_long(tokens, options)
|
548
|
+
elsif token.start_with?('-') and not ['-', '--'].include? token
|
549
|
+
return parse_shorts(tokens, options)
|
550
|
+
elsif token.start_with?('<') and token.end_with?('>') or (token.upcase == token && token.match(/[A-Z]/))
|
551
|
+
return [Argument.new(tokens.move())]
|
552
|
+
else
|
553
|
+
return [Command.new(tokens.move())]
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
def parse_argv(tokens, options, options_first=false)
|
558
|
+
parsed = []
|
559
|
+
while tokens.current() != nil
|
560
|
+
if tokens.current() == '--'
|
561
|
+
return parsed + tokens.map { |v| Argument.new(nil, v) }
|
562
|
+
elsif tokens.current().start_with?('--')
|
563
|
+
parsed += parse_long(tokens, options)
|
564
|
+
elsif tokens.current().start_with?('-') and tokens.current() != '-'
|
565
|
+
parsed += parse_shorts(tokens, options)
|
566
|
+
elsif options_first
|
567
|
+
return parsed + tokens.map { |v| Argument.new(nil, v) }
|
568
|
+
else
|
569
|
+
parsed << Argument.new(nil, tokens.move())
|
570
|
+
end
|
571
|
+
end
|
572
|
+
return parsed
|
573
|
+
end
|
574
|
+
|
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) }
|
579
|
+
end
|
580
|
+
|
581
|
+
def printable_usage(doc)
|
582
|
+
usage_split = doc.split(/([Uu][Ss][Aa][Gg][Ee]:)/)
|
583
|
+
if usage_split.count < 3
|
584
|
+
raise DocoptLanguageError, '"usage:" (case-insensitive) not found.'
|
585
|
+
end
|
586
|
+
if usage_split.count > 3
|
587
|
+
raise DocoptLanguageError, 'More than one "usage:" (case-insensitive).'
|
588
|
+
end
|
589
|
+
return usage_split.drop(1).join().split(/\n\s*\n/)[0].strip
|
590
|
+
end
|
591
|
+
|
592
|
+
def formal_usage(printable_usage)
|
593
|
+
pu = printable_usage.split().drop(1) # split and drop "usage:"
|
594
|
+
|
595
|
+
ret = []
|
596
|
+
for s in pu.drop(1)
|
597
|
+
if s == pu[0]
|
598
|
+
ret << ') | ('
|
599
|
+
else
|
600
|
+
ret << s
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
604
|
+
return '( ' + ret.join(' ') + ' )'
|
605
|
+
end
|
606
|
+
|
607
|
+
def dump_patterns(pattern, indent=0)
|
608
|
+
ws = " " * 4 * indent
|
609
|
+
out = ""
|
610
|
+
if pattern.class == Array
|
611
|
+
if pattern.count > 0
|
612
|
+
out << ws << "[\n"
|
613
|
+
for p in pattern
|
614
|
+
out << dump_patterns(p, indent+1).rstrip << "\n"
|
615
|
+
end
|
616
|
+
out << ws << "]\n"
|
617
|
+
else
|
618
|
+
out << ws << "[]\n"
|
619
|
+
end
|
620
|
+
|
621
|
+
elsif pattern.class.ancestors.include?(ParentPattern)
|
622
|
+
out << ws << pattern.class.name << "(\n"
|
623
|
+
for p in pattern.children
|
624
|
+
out << dump_patterns(p, indent+1).rstrip << "\n"
|
625
|
+
end
|
626
|
+
out << ws << ")\n"
|
627
|
+
|
628
|
+
else
|
629
|
+
out << ws << pattern.inspect
|
630
|
+
end
|
631
|
+
return out
|
632
|
+
end
|
633
|
+
|
634
|
+
def extras(help, version, options, doc)
|
635
|
+
if help and options.any? { |o| ['-h', '--help'].include?(o.name) && o.value }
|
636
|
+
Exit.set_usage(nil)
|
637
|
+
raise Exit, doc.strip
|
638
|
+
end
|
639
|
+
if version and options.any? { |o| o.name == '--version' && o.value }
|
640
|
+
Exit.set_usage(nil)
|
641
|
+
raise Exit, version
|
642
|
+
end
|
643
|
+
end
|
644
|
+
|
645
|
+
def docopt(doc, params={})
|
646
|
+
default = {:version => nil, :argv => nil, :help => true, :options_first => false}
|
647
|
+
params = default.merge(params)
|
648
|
+
params[:argv] = ARGV if !params[:argv]
|
649
|
+
|
650
|
+
Exit.set_usage(printable_usage(doc))
|
651
|
+
options = parse_defaults(doc)
|
652
|
+
pattern = parse_pattern(formal_usage(Exit.usage), 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
|
659
|
+
extras(params[:help], params[:version], argv, doc)
|
660
|
+
|
661
|
+
matched, left, collected = pattern.fix().match(argv)
|
662
|
+
collected ||= []
|
663
|
+
|
664
|
+
if matched and (left.count == 0)
|
665
|
+
return Hash[(pattern.flat + collected).map { |a| [a.name, a.value] }]
|
666
|
+
end
|
667
|
+
raise Exit
|
668
|
+
end
|
669
|
+
end
|
670
|
+
end
|