docopt 0.0.4 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,83 @@
1
+ ## This is the rakegem gemspec template. Make sure you read and understand
2
+ ## all of the comments. Some sections require modification, and others can
3
+ ## be deleted if you don't need them. Once you understand the contents of
4
+ ## this file, feel free to delete any comments that begin with two hash marks.
5
+ ## You can find comprehensive Gem::Specification documentation, at
6
+ ## http://docs.rubygems.org/read/chapter/20
7
+ Gem::Specification.new do |s|
8
+ s.specification_version = 2 if s.respond_to? :specification_version=
9
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
10
+ s.rubygems_version = '1.3.5'
11
+ s.required_ruby_version = '>= 1.8.7'
12
+ ## Leave these as is they will be modified for you by the rake gemspec task.
13
+ ## If your rubyforge_project name is different, then edit it and comment out
14
+ ## the sub! line in the Rakefile
15
+ s.name = 'docopt'
16
+ s.version = '0.5.0'
17
+ s.date = '2012-09-01'
18
+ # s.rubyforge_project = 'docopt'
19
+
20
+ ## Make sure your summary is short. The description may be as long
21
+ ## as you like.
22
+ s.summary = "A command line option parser, that will make you smile."
23
+ s.description = "Isn't it awesome how `optparse` and other option parsers generate help and usage-messages based on your code?! Hell no!\nYou know what's awesome? It's when the option parser *is* generated based on the help and usage-message that you write in a docstring! That's what docopt does!"
24
+
25
+ ## List the primary authors. If there are a bunch of authors, it's probably
26
+ ## better to set the email to an email list or something. If you don't have
27
+ ## a custom homepage, consider using your GitHub URL or the like.
28
+ s.authors = ["Blake Williams", "Vladimir Keleshev", "Alex Speller", "Nima Johari"]
29
+ s.email = "code@shabbyrobe.org"
30
+ s.homepage = "http://github.com/docopt/docopt.rb"
31
+ s.license = 'MIT'
32
+ ## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
33
+ ## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
34
+ s.require_paths = %w[lib]
35
+
36
+ ## This sections is only necessary if you have C extensions.
37
+ # s.require_paths << 'ext'
38
+ # s.extensions = %w[ext/extconf.rb]
39
+
40
+ ## If your gem includes any executables, list them here.
41
+ # s.executables = ["name"]
42
+
43
+ ## Specify any RDoc options here. You'll want to add your README and
44
+ ## LICENSE files to the extra_rdoc_files list.
45
+ s.rdoc_options = ["--charset=UTF-8"]
46
+ s.extra_rdoc_files = %w[README.md LICENSE]
47
+
48
+ ## List your runtime dependencies here. Runtime dependencies are those
49
+ ## that are needed for an end user to actually USE your code.
50
+ # s.add_dependency('DEPNAME', [">= 1.1.0", "< 2.0.0"])
51
+
52
+ ## List your development dependencies here. Development dependencies are
53
+ ## those that are only needed during development
54
+ s.add_development_dependency('json', "~> 1.6.5")
55
+
56
+ ## Leave this section as-is. It will be automatically generated from the
57
+ ## contents of your Git repository via the gemspec task. DO NOT REMOVE
58
+ ## THE MANIFEST COMMENTS, they are used as delimiters by the task.
59
+ # = MANIFEST =
60
+ s.files = %w[
61
+ Gemfile
62
+ LICENSE
63
+ README.md
64
+ Rakefile
65
+ docopt.gemspec
66
+ examples/any_options_example.rb
67
+ examples/calculator.rb
68
+ examples/counted_example.rb
69
+ examples/example_options.rb
70
+ examples/git_example.rb
71
+ examples/naval_fate.rb
72
+ examples/odd_even_example.rb
73
+ examples/quick_example.rb
74
+ lib/docopt.rb
75
+ test/test_docopt.rb
76
+ test/testee.rb
77
+ ]
78
+ # = MANIFEST =
79
+
80
+ ## Test files will be grabbed from the file list. Make sure the path glob
81
+ ## matches what you actually use.
82
+ s.test_files = s.files.select { |path| path =~ /^test\/test_.*\.rb/ }
83
+ end
@@ -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
@@ -1,112 +1,663 @@
1
- require 'getoptlong'
2
-
3
- class Docopt
4
- attr_reader :docopts
5
-
6
- class UnknownOptionError < StandardError; end
7
-
8
- class Option
9
- attr_reader :short, :long, :argcount, :value
10
-
11
- def initialize parse
12
- @argcount = 0
13
- options, _, description = parse.strip.partition(' ')
14
- options = options.sub(',', ' ').sub('=', ' ')
1
+ module Docopt
2
+ VERSION = '0.5.0'
3
+ end
4
+ module Docopt
5
+ class DocoptLanguageError < SyntaxError
6
+ end
15
7
 
16
- for s in options.split
17
- if s.start_with? '--'
18
- @long = s
19
- elsif s.start_with? '-'
20
- @short = s
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 && message != '' ? (message + "\n") : '') + @@usage)
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_list_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)]
21
59
  else
22
- @argcount = 1
60
+ c.fix_identities(uniq)
23
61
  end
24
62
  end
25
-
26
- if @argcount == 1
27
- matched = description.scan(/\[default: (.*)\]/)[0]
28
- @value = matched ? matched[0] : nil
63
+ end
64
+
65
+ def fix_list_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
+ e.value = []
70
+ end
71
+ if e.class == Command or (e.class == Option and e.argcount == 0)
72
+ e.value = 0
73
+ end
74
+ end
29
75
  end
76
+
77
+ return self
30
78
  end
31
-
32
- def set_value val
33
- if argcount.zero?
34
- @value = true
35
- else
36
- @value = val
79
+
80
+ def either
81
+ ret = []
82
+ groups = [[self]]
83
+ while groups.count > 0
84
+ children = groups.shift
85
+ types = children.map { |c| c.class }
86
+
87
+ if types.include?(Either)
88
+ either = children.select { |c| c.class == Either }[0]
89
+ children.slice!(children.index(either))
90
+ for c in either.children
91
+ groups << [c] + children
92
+ end
93
+ elsif types.include?(Required)
94
+ required = children.select { |c| c.class == Required }[0]
95
+ children.slice!(children.index(required))
96
+ groups << required.children + children
97
+
98
+ elsif types.include?(Optional)
99
+ optional = children.select { |c| c.class == Optional }[0]
100
+ children.slice!(children.index(optional))
101
+ groups << optional.children + children
102
+
103
+ elsif types.include?(OneOrMore)
104
+ oneormore = children.select { |c| c.class == OneOrMore }[0]
105
+ children.slice!(children.index(oneormore))
106
+ groups << (oneormore.children * 2) + children
107
+
108
+ else
109
+ ret << children
110
+ end
37
111
  end
112
+
113
+ args = ret.map { |e| Required.new(*e) }
114
+ return Either.new(*args)
38
115
  end
39
-
40
- def synonyms
41
- ([short, long] + symbols).compact
116
+ end
117
+
118
+
119
+ class ChildPattern < Pattern
120
+ attr_accessor :name, :value
121
+
122
+ def initialize(name, value=nil)
123
+ @name = name
124
+ @value = value
42
125
  end
43
-
44
- def symbols
45
- [short, long].compact.map do |name|
46
- name.gsub(/^-+/, '').to_sym
126
+
127
+ def inspect()
128
+ "#{self.class.name}(#{self.name}, #{self.value})"
129
+ end
130
+
131
+ def flat
132
+ [self]
133
+ end
134
+
135
+
136
+ def match(left, collected=nil)
137
+ collected ||= []
138
+ pos, match = self.single_match(left)
139
+ if match == nil
140
+ return [false, left, collected]
47
141
  end
142
+
143
+ left_ = left.dup
144
+ left_.slice!(pos)
145
+
146
+ same_name = collected.select { |a| a.name == self.name }
147
+ if @value.is_a? Array or @value.is_a? Integer
148
+ increment = @value.is_a?(Integer) ? 1 : [match.value]
149
+ if same_name.count == 0
150
+ match.value = increment
151
+ return [true, left_, collected + [match]]
152
+ end
153
+ same_name[0].value += increment
154
+ return [true, left_, collected]
155
+ end
156
+ return [true, left_, collected + [match]]
48
157
  end
158
+ end
49
159
 
50
- def getopt
51
- [long, short, argcount].compact
160
+ class ParentPattern < Pattern
161
+ attr_accessor :children
162
+
163
+ def initialize(*children)
164
+ @children = children
52
165
  end
53
166
 
54
167
  def inspect
55
- "#<Docopt::Option short: #{short}, long: #{long}, argcount: #{argcount}, value: #{value}>"
168
+ childstr = self.children.map { |a| a.inspect }
169
+ return "#{self.class.name}(#{childstr.join(", ")})"
170
+ end
171
+
172
+ def flat
173
+ self.children.map { |c| c.flat }.flatten
56
174
  end
175
+ end
176
+
177
+ class Argument < ChildPattern
178
+
179
+ # def initialize(*args)
180
+ # super(*args)
181
+ # end
182
+
183
+ def single_match(left)
184
+ left.each_with_index do |p, n|
185
+ if p.class == Argument
186
+ return [n, Argument.new(self.name, p.value)]
187
+ end
188
+ end
189
+ return [nil, nil]
190
+ end
191
+ end
192
+
57
193
 
58
- def == other
59
- self.inspect == other.inspect
194
+ class Command < Argument
195
+ def initialize(name, value=false)
196
+ @name = name
197
+ @value = value
198
+ end
199
+
200
+ def single_match(left)
201
+ left.each_with_index do |p, n|
202
+ if p.class == Argument
203
+ if p.value == self.name
204
+ return n, Command.new(self.name, true)
205
+ else
206
+ break
207
+ end
208
+ end
209
+ end
210
+ return [nil, nil]
60
211
  end
61
212
  end
62
-
63
-
64
- def initialize(doc, version=nil, help=true)
65
- @docopts = doc.split(/^ *-|\n *-/)[1..-1].map do |line|
66
- Option.new('-' + line)
67
- end
68
-
69
- GetoptLong.new(*docopts.map(&:getopt)).each do |opt, arg|
70
- docopt_option = option(opt)
71
- if help and (opt == '--help' or opt == '-h')
72
- puts doc.strip
73
- exit
74
- elsif version and opt == '--version'
75
- puts version
76
- exit
213
+
214
+
215
+ class Option < ChildPattern
216
+ attr_reader :short, :long
217
+ attr_accessor :argcount
218
+
219
+ def initialize(short=nil, long=nil, argcount=0, value=false)
220
+ unless [0, 1].include? argcount
221
+ raise RuntimeError
222
+ end
223
+
224
+ @short, @long = short, long
225
+ @argcount, @value = argcount, value
226
+
227
+ if value == false and argcount > 0
228
+ @value = nil
77
229
  else
78
- docopt_option.set_value arg
230
+ @value = value
231
+ end
232
+ end
233
+
234
+ def self.parse(option_description)
235
+ short, long, argcount, value = nil, nil, 0, false
236
+ options, _, description = option_description.strip.partition(' ')
237
+
238
+ options.gsub!(",", " ")
239
+ options.gsub!("=", " ")
240
+
241
+ for s in options.split
242
+ if s.start_with?('--')
243
+ long = s
244
+ elsif s.start_with?('-')
245
+ short = s
246
+ else
247
+ argcount = 1
248
+ end
79
249
  end
250
+ if argcount > 0
251
+ matched = description.scan(/\[default: (.*)\]/i)
252
+ value = matched[0][0] if matched.count > 0
253
+ end
254
+ ret = self.new(short, long, argcount, value)
255
+ return ret
256
+ end
257
+
258
+ def single_match(left)
259
+ left.each_with_index do |p, n|
260
+ if self.name == p.name
261
+ return [n, p]
262
+ end
263
+ end
264
+ return [nil, nil]
265
+ end
266
+
267
+ def name
268
+ return self.long ? self.long : self.short
269
+ end
270
+
271
+ def inspect
272
+ return "Option(#{self.short}, #{self.long}, #{self.argcount}, #{self.value})"
80
273
  end
81
274
  end
82
-
83
- def option name
84
- option = @docopts.detect do |docopt|
85
- docopt.synonyms.include?(name)
275
+
276
+ class Required < ParentPattern
277
+ def match(left, collected=nil)
278
+ collected ||= []
279
+ l = left
280
+ c = collected
281
+
282
+ for p in self.children
283
+ matched, l, c = p.match(l, c)
284
+ if not matched
285
+ return [false, left, collected]
286
+ end
287
+ end
288
+ return [true, l, c]
86
289
  end
87
- raise UnknownOptionError.new("#{name} option not found") unless option
88
- option
89
290
  end
90
291
 
292
+ class Optional < ParentPattern
293
+ def match(left, collected=nil)
294
+ collected ||= []
295
+ for p in self.children
296
+ m, left, collected = p.match(left, collected)
297
+ end
298
+ return [true, left, collected]
299
+ end
300
+ end
91
301
 
92
-
93
- def value name
94
- option(name).value
302
+ class OneOrMore < ParentPattern
303
+ def match(left, collected=nil)
304
+ if self.children.count != 1
305
+ raise RuntimeError
306
+ end
307
+
308
+ collected ||= []
309
+ l = left
310
+ c = collected
311
+ l_ = nil
312
+ matched = true
313
+ times = 0
314
+ while matched
315
+ # could it be that something didn't match but changed l or c?
316
+ matched, l, c = self.children[0].match(l, c)
317
+ times += (matched ? 1 : 0)
318
+ if l_ == l
319
+ break
320
+ end
321
+ l_ = l
322
+ end
323
+ if times >= 1
324
+ return [true, l, c]
325
+ end
326
+ return [false, left, collected]
327
+ end
95
328
  end
96
- alias_method :[], :value
97
-
98
- def size
99
- @docopts.size
329
+
330
+ class Either < ParentPattern
331
+ def match(left, collected=nil)
332
+ collected ||= []
333
+ outcomes = []
334
+ for p in self.children
335
+ matched, _, _ = outcome = p.match(left, collected)
336
+ if matched
337
+ outcomes << outcome
338
+ end
339
+ end
340
+
341
+ if outcomes.count > 0
342
+ ret = outcomes.min_by do |outcome|
343
+ outcome[1] == nil ? 0 : outcome[1].count
344
+ end
345
+ return ret
346
+ end
347
+ return [false, left, collected]
348
+ end
100
349
  end
101
-
102
- def inspect
103
- @docopts.map do |option|
104
- "#{option.short} #{option.long}=#{option.value.inspect}".strip
105
- end.join("\n")
350
+
351
+ class TokenStream < Array
352
+ attr_reader :error
353
+
354
+ def initialize(source, error)
355
+ if !source
356
+ source = []
357
+ elsif source.class != ::Array
358
+ source = source.split
359
+ end
360
+ super(source)
361
+ @error = error
362
+ end
363
+
364
+ def move
365
+ return self.shift
366
+ end
367
+
368
+ def current
369
+ return self[0]
370
+ end
106
371
  end
107
- end
108
372
 
109
- # Convenience method for Docopt.parse
110
- def Docopt *args
111
- Docopt.new *args
112
- end
373
+ class << self
374
+ def parse_long(tokens, options)
375
+ raw, eq, value = tokens.move().partition('=')
376
+ value = (eq == value and eq == '') ? nil : value
377
+
378
+ opt = options.select { |o| o.long and o.long == raw }
379
+
380
+ if tokens.error == Exit and opt == []
381
+ opt = options.select { |o| o.long and o.long.start_with?(raw) }
382
+ end
383
+
384
+ if opt.count < 1
385
+ 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]
391
+ 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"
403
+ end
404
+ value = tokens.move()
405
+ 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
414
+ end
415
+ return [opt]
416
+ end
417
+
418
+ def parse_shorts(tokens, options)
419
+ raw = tokens.move()[1..-1]
420
+ 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
430
+ 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
438
+ 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
+ else
447
+ if raw == ''
448
+ if tokens.current() == nil
449
+ raise tokens.error, "-#{opt.short.slice(0, 1)} requires argument"
450
+ end
451
+ raw = tokens.move()
452
+ 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
460
+ end
461
+ parsed << opt
462
+ end
463
+ return parsed
464
+ end
465
+
466
+
467
+ def parse_pattern(source, options)
468
+ tokens = TokenStream.new(source.gsub(/([\[\]\(\)\|]|\.\.\.)/, ' \1 '), DocoptLanguageError)
469
+
470
+ result = parse_expr(tokens, options)
471
+ if tokens.current() != nil
472
+ raise tokens.error, "unexpected ending: #{tokens.join(" ")}"
473
+ end
474
+ return Required.new(*result)
475
+ end
476
+
477
+
478
+ def parse_expr(tokens, options)
479
+ seq = parse_seq(tokens, options)
480
+ if tokens.current() != '|'
481
+ return seq
482
+ end
483
+ result = seq.count > 1 ? [Required.new(*seq)] : seq
484
+
485
+ while tokens.current() == '|'
486
+ tokens.move()
487
+ seq = parse_seq(tokens, options)
488
+ result += seq.count > 1 ? [Required.new(*seq)] : seq
489
+ end
490
+ return result.count > 1 ? [Either.new(*result)] : result
491
+ end
492
+
493
+ def parse_seq(tokens, options)
494
+ result = []
495
+ stop = [nil, ']', ')', '|']
496
+ while !stop.include?(tokens.current)
497
+ atom = parse_atom(tokens, options)
498
+ if tokens.current() == '...'
499
+ atom = [OneOrMore.new(*atom)]
500
+ tokens.move()
501
+ end
502
+ result += atom
503
+ end
504
+ return result
505
+ end
506
+
507
+ def parse_atom(tokens, options)
508
+ token = tokens.current()
509
+ result = []
510
+
511
+ if ['(' , '['].include? token
512
+ tokens.move()
513
+ if token == '('
514
+ matching = ')'
515
+ pattern = Required
516
+ else
517
+ matching = ']'
518
+ pattern = Optional
519
+ end
520
+ result = pattern.new(*parse_expr(tokens, options))
521
+ if tokens.move() != matching
522
+ raise tokens.error, "unmatched '#{token}'"
523
+ end
524
+ return [result]
525
+ elsif token == 'options'
526
+ tokens.move()
527
+ return options
528
+ elsif token.start_with?('--') and token != '--'
529
+ return parse_long(tokens, options)
530
+ elsif token.start_with?('-') and not ['-', '--'].include? token
531
+ return parse_shorts(tokens, options)
532
+
533
+ elsif token.start_with?('<') and token.end_with?('>') or token.upcase == token
534
+ return [Argument.new(tokens.move())]
535
+ else
536
+ return [Command.new(tokens.move())]
537
+ end
538
+ end
539
+
540
+ def parse_argv(source, options)
541
+ tokens = TokenStream.new(source, Exit)
542
+ parsed = []
543
+ while tokens.current() != nil
544
+ if tokens.current() == '--'
545
+ return parsed + tokens.map { |v| Argument.new(nil, v) }
546
+ elsif tokens.current().start_with?('--')
547
+ parsed += parse_long(tokens, options)
548
+ elsif tokens.current().start_with?('-') and tokens.current() != '-'
549
+ parsed += parse_shorts(tokens, options)
550
+ else
551
+ parsed << Argument.new(nil, tokens.move())
552
+ end
553
+ end
554
+ return parsed
555
+ end
556
+
557
+ def parse_doc_options(doc)
558
+ return doc.split(/^ *-|\n *-/)[1..-1].map { |s| Option.parse('-' + s) }
559
+ end
560
+
561
+ def printable_usage(doc)
562
+ usage_split = doc.split(/([Uu][Ss][Aa][Gg][Ee]:)/)
563
+ if usage_split.count < 3
564
+ raise DocoptLanguageError, '"usage:" (case-insensitive) not found.'
565
+ end
566
+ if usage_split.count > 3
567
+ raise DocoptLanguageError, 'More than one "usage:" (case-insensitive).'
568
+ end
569
+ return usage_split[1..-1].join().split(/\n\s*\n/)[0].strip
570
+ end
571
+
572
+ def formal_usage(printable_usage)
573
+ pu = printable_usage.split()[1..-1] # split and drop "usage:"
574
+
575
+ ret = []
576
+ for s in pu[1..-1]
577
+ if s == pu[0]
578
+ ret << ') | ('
579
+ else
580
+ ret << s
581
+ end
582
+ end
583
+
584
+ return '( ' + ret.join(' ') + ' )'
585
+ end
586
+
587
+ def dump_patterns(pattern, indent=0)
588
+ ws = " " * 4 * indent
589
+ out = ""
590
+ if pattern.class == Array
591
+ if pattern.count > 0
592
+ out << ws << "[\n"
593
+ for p in pattern
594
+ out << dump_patterns(p, indent+1).rstrip << "\n"
595
+ end
596
+ out << ws << "]\n"
597
+ else
598
+ out << ws << "[]\n"
599
+ end
600
+
601
+ elsif pattern.class.ancestors.include?(ParentPattern)
602
+ out << ws << pattern.class.name << "(\n"
603
+ for p in pattern.children
604
+ out << dump_patterns(p, indent+1).rstrip << "\n"
605
+ end
606
+ out << ws << ")\n"
607
+
608
+ else
609
+ out << ws << pattern.inspect
610
+ end
611
+ return out
612
+ end
613
+
614
+ 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
627
+ Exit.set_usage(nil)
628
+ raise Exit, doc.strip
629
+ end
630
+ if version and vfound
631
+ Exit.set_usage(nil)
632
+ raise Exit, version
633
+ end
634
+ end
635
+
636
+ def docopt(doc, params={})
637
+ default = {:version => nil, :argv => nil, :help => true}
638
+ params = default.merge(params)
639
+ params[:argv] = ARGV if !params[:argv]
640
+
641
+ Exit.set_usage(printable_usage(doc))
642
+ options = parse_doc_options(doc)
643
+ pattern = parse_pattern(formal_usage(Exit.usage), options)
644
+ argv = parse_argv(params[:argv], options)
645
+ extras(params[:help], params[:version], argv, doc)
646
+
647
+ matched, left, collected = pattern.fix().match(argv)
648
+ collected ||= []
649
+
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
659
+ end
660
+ raise Exit
661
+ end
662
+ end
663
+ end