docopt 0.0.4 → 0.5.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.
@@ -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