Skip to content
This repository
Newer
Older
100644 454 lines (369 sloc) 15.053 kb
7f001d9f »
2012-04-08 Use literal_eval for option arguments.
1 import sys
2 import re
9b2cd0a0 »
2012-04-07 Initial commit.
3
4
df336702 »
2012-06-12 Rename: UsageMessageError -> DocoptLanguageError.
5 class DocoptLanguageError(Exception):
a29db2f1 »
2012-05-06 Add DocoptError.
6
7 """Error in construction of usage-message by developer."""
8
9
87ce4ddd »
2012-04-22 Introduce DocoptExit for better testability.
10 class DocoptExit(SystemExit):
95ea0876 »
2012-04-22 Print usage on error.
11
32c73fe5 »
2012-05-29 minor changes
12 """Exit in case user invoked program with incorrect arguments."""
a29db2f1 »
2012-05-06 Add DocoptError.
13
95ea0876 »
2012-04-22 Print usage on error.
14 usage = ''
15
0e6f623e »
2012-05-06 Get rid of usage(), parse_doc_usage().
16 def __init__(self, message=''):
28b9c319 »
2012-05-12 Update options_example.py to use pattern-matching.
17 SystemExit.__init__(self, (message + '\n' + self.usage).strip())
87ce4ddd »
2012-04-22 Introduce DocoptExit for better testability.
18
19
5e69ae2e »
2012-04-16 Extract common class Pattern.
20 class Pattern(object):
c1115db2 »
2012-04-13 Branch for development of arguments pattern-matching.
21
22 def __eq__(self, other):
23 return repr(self) == repr(other)
24
448cd1ac »
2012-05-12 Working Pattern.fix_list_arguments.
25 def __hash__(self):
26 return hash(repr(self))
27
a461ff80 »
2012-05-12 Make Argument.match accumulate values if necessary.
28 def fix(self):
29 self.fix_identities()
30 self.fix_list_arguments()
31 return self
32
e77f8bda »
2012-05-12 Pattern.fix_identities
33 def fix_identities(self, uniq=None):
34 """Make pattern-tree tips point to same object if they are equal."""
35 if not hasattr(self, 'children'):
36 return self
37 uniq = list(set(self.flat)) if uniq == None else uniq
38 for i, c in enumerate(self.children):
39 if not hasattr(c, 'children'):
40 assert c in uniq
41 self.children[i] = uniq[uniq.index(c)]
42 else:
43 c.fix_identities(uniq)
44
e96217bf »
2012-05-12 Start Pattern.fix_list_arguments.
45 def fix_list_arguments(self):
46 """Find arguments that should accumulate values and fix them."""
448cd1ac »
2012-05-12 Working Pattern.fix_list_arguments.
47 either = [list(c.children) for c in self.either.children]
48 for case in either:
3cacaca4 »
2012-07-29 Count multiple commands, accumulate multiple options.
49 for e in [c for c in case if case.count(c) > 1]:
50 if type(e) is Argument or type(e) is Option and e.argcount:
51 e.value = []
52 if type(e) is Command or type(e) is Option and e.argcount == 0:
53 e.value = 0
e96217bf »
2012-05-12 Start Pattern.fix_list_arguments.
54 return self
55
d6850ae9 »
2012-05-12 Start making Pattern.either to expand any pattern to Either.
56 @property
57 def either(self):
98bb1954 »
2012-05-12 Clarify goal of Pattern.either.
58 """Transform pattern into an equivalent, with only top-level Either."""
59 # Currently the pattern will not be equivalent, but more "narrow",
60 # although good enough to reason about list arguments.
6f43b600 »
2012-07-29 Experimental: split Pattern into {Child,Parrent,}Pattern
61 ret = []
62 groups = [[self]]
63 while groups:
64 children = groups.pop(0)
65 types = [type(c) for c in children]
66 if Either in types:
67 either = [c for c in children if type(c) is Either][0]
68 children.pop(children.index(either))
69 for c in either.children:
70 groups.append([c] + children)
71 elif Required in types:
72 required = [c for c in children if type(c) is Required][0]
73 children.pop(children.index(required))
74 groups.append(list(required.children) + children)
75 elif Optional in types:
76 optional = [c for c in children if type(c) is Optional][0]
77 children.pop(children.index(optional))
78 groups.append(list(optional.children) + children)
79 elif OneOrMore in types:
80 oneormore = [c for c in children if type(c) is OneOrMore][0]
81 children.pop(children.index(oneormore))
82 groups.append(list(oneormore.children) * 2 + children)
83 else:
84 ret.append(children)
85 return Either(*[Required(*e) for e in ret])
86
87
88 class ChildPattern(Pattern):
89
90 def __init__(self, name, value=None):
91 self.name = name
92 self.value = value
93
94 def __repr__(self):
95 return '%s(%r, %r)' % (self.__class__.__name__, self.name, self.value)
96
97 @property
98 def flat(self):
99 return [self]
d6850ae9 »
2012-05-12 Start making Pattern.either to expand any pattern to Either.
100
3cacaca4 »
2012-07-29 Count multiple commands, accumulate multiple options.
101 def match(self, left, collected=None):
102 collected = [] if collected is None else collected
103 pos, match = self.single_match(left)
104 if match is None:
105 return False, left, collected
106 left_ = left[:pos] + left[pos+1:]
107 same_name = [a for a in collected if a.name == self.name]
35ed0e29 »
2012-07-29 Refactor ChildPattern.match.
108 if type(self.value) in (int, list):
109 increment = 1 if type(self.value) is int else [match.value]
3cacaca4 »
2012-07-29 Count multiple commands, accumulate multiple options.
110 if not same_name:
35ed0e29 »
2012-07-29 Refactor ChildPattern.match.
111 match.value = increment
3cacaca4 »
2012-07-29 Count multiple commands, accumulate multiple options.
112 return True, left_, collected + [match]
35ed0e29 »
2012-07-29 Refactor ChildPattern.match.
113 same_name[0].value += increment
3cacaca4 »
2012-07-29 Count multiple commands, accumulate multiple options.
114 return True, left_, collected
115 return True, left_, collected + [match]
116
d6850ae9 »
2012-05-12 Start making Pattern.either to expand any pattern to Either.
117
6f43b600 »
2012-07-29 Experimental: split Pattern into {Child,Parrent,}Pattern
118 class ParrentPattern(Pattern):
5e69ae2e »
2012-04-16 Extract common class Pattern.
119
6f43b600 »
2012-07-29 Experimental: split Pattern into {Child,Parrent,}Pattern
120 def __init__(self, *children):
121 self.children = list(children)
122
123 def __repr__(self):
124 return '%s(%s)' % (self.__class__.__name__,
125 ', '.join(repr(a) for a in self.children))
126
127 @property
128 def flat(self):
129 return sum([c.flat for c in self.children], [])
130
131
132 class Argument(ChildPattern):
fdb48d70 »
2012-04-16 Experimental refactoring of Argument.
133
3cacaca4 »
2012-07-29 Count multiple commands, accumulate multiple options.
134 def single_match(self, left):
135 for n, p in enumerate(left):
136 if type(p) is Argument:
137 return n, Argument(self.name, p.value)
138 return None, None
b5eb385b »
2012-04-14 Basic pattern-matching working.
139
c1115db2 »
2012-04-13 Branch for development of arguments pattern-matching.
140
3cacaca4 »
2012-07-29 Count multiple commands, accumulate multiple options.
141 class Command(Argument):
d55e10b9 »
2012-06-02 Initial support for commands.
142
143 def __init__(self, name, value=False):
144 self.name = name
145 self.value = value
146
3cacaca4 »
2012-07-29 Count multiple commands, accumulate multiple options.
147 def single_match(self, left):
148 for n, p in enumerate(left):
149 if type(p) is Argument:
150 if p.value == self.name:
151 return n, Command(self.name, True)
152 else:
153 break
154 return None, None
d55e10b9 »
2012-06-02 Initial support for commands.
155
156
6f43b600 »
2012-07-29 Experimental: split Pattern into {Child,Parrent,}Pattern
157 class Option(ChildPattern):
9b2cd0a0 »
2012-04-07 Initial commit.
158
de37f27a »
2012-06-02 Refactor Option.
159 def __init__(self, short=None, long=None, argcount=0, value=False):
160 assert argcount in (0, 1)
161 self.short, self.long = short, long
162 self.argcount, self.value = argcount, value
e4f9bf1e »
2012-06-06 Fix bugs found with language-agnostic tester.
163 self.value = None if value == False and argcount else value # HACK
9b2cd0a0 »
2012-04-07 Initial commit.
164
9943f4c3 »
2012-06-03 Move option into Option.parse.
165 @classmethod
166 def parse(class_, option_description):
167 short, long, argcount, value = None, None, 0, False
168 options, _, description = option_description.strip().partition(' ')
169 options = options.replace(',', ' ').replace('=', ' ')
170 for s in options.split():
171 if s.startswith('--'):
172 long = s
173 elif s.startswith('-'):
174 short = s
175 else:
176 argcount = 1
177 if argcount:
178 matched = re.findall('\[default: (.*)\]', description, flags=re.I)
e4f9bf1e »
2012-06-06 Fix bugs found with language-agnostic tester.
179 value = matched[0] if matched else None
9943f4c3 »
2012-06-03 Move option into Option.parse.
180 return class_(short, long, argcount, value)
181
3cacaca4 »
2012-07-29 Count multiple commands, accumulate multiple options.
182 def single_match(self, left):
183 for n, p in enumerate(left):
184 if self.name == p.name:
185 return n, p
186 return None, None
bede5ce5 »
2012-04-14 Dead end?
187
9b2cd0a0 »
2012-04-07 Initial commit.
188 @property
189 def name(self):
de37f27a »
2012-06-02 Refactor Option.
190 return self.long or self.short
9b2cd0a0 »
2012-04-07 Initial commit.
191
192 def __repr__(self):
de37f27a »
2012-06-02 Refactor Option.
193 return 'Option(%r, %r, %r, %r)' % (self.short, self.long,
194 self.argcount, self.value)
9b2cd0a0 »
2012-04-07 Initial commit.
195
6c1d59f8 »
2012-06-02 Minor renaming.
196
6f43b600 »
2012-07-29 Experimental: split Pattern into {Child,Parrent,}Pattern
197 class Required(ParrentPattern):
5e69ae2e »
2012-04-16 Extract common class Pattern.
198
689c7615 »
2012-04-17 Start experiment with collecting arguments while matching.
199 def match(self, left, collected=None):
200 collected = [] if collected is None else collected
8ae0ace4 »
2012-06-20 remove dependence on copy
201 l = left
202 c = collected
baf96d66 »
2012-04-17 Rename args -> children for patterns.
203 for p in self.children:
18685298 »
2012-05-17 Fix bug: arguments not accumulating into same list.
204 matched, l, c = p.match(l, c)
205 if not matched:
206 return False, left, collected
207 return True, l, c
5e69ae2e »
2012-04-16 Extract common class Pattern.
208
209
6f43b600 »
2012-07-29 Experimental: split Pattern into {Child,Parrent,}Pattern
210 class Optional(ParrentPattern):
5e69ae2e »
2012-04-16 Extract common class Pattern.
211
689c7615 »
2012-04-17 Start experiment with collecting arguments while matching.
212 def match(self, left, collected=None):
213 collected = [] if collected is None else collected
baf96d66 »
2012-04-17 Rename args -> children for patterns.
214 for p in self.children:
b62099ba »
2012-04-17 Arguments are collected while matching.
215 m, left, collected = p.match(left, collected)
689c7615 »
2012-04-17 Start experiment with collecting arguments while matching.
216 return True, left, collected
5e69ae2e »
2012-04-16 Extract common class Pattern.
217
218
6f43b600 »
2012-07-29 Experimental: split Pattern into {Child,Parrent,}Pattern
219 class OneOrMore(ParrentPattern):
5e69ae2e »
2012-04-16 Extract common class Pattern.
220
689c7615 »
2012-04-17 Start experiment with collecting arguments while matching.
221 def match(self, left, collected=None):
7dfd3726 »
2012-05-13 Fix infinite loop in case OneOrMore(Optional(...)) match.
222 assert len(self.children) == 1
689c7615 »
2012-04-17 Start experiment with collecting arguments while matching.
223 collected = [] if collected is None else collected
8ae0ace4 »
2012-06-20 remove dependence on copy
224 l = left
225 c = collected
7dfd3726 »
2012-05-13 Fix infinite loop in case OneOrMore(Optional(...)) match.
226 l_ = None
5e69ae2e »
2012-04-16 Extract common class Pattern.
227 matched = True
fd37b68f »
2012-05-11 Slightly better OneOrMore.match.
228 times = 0
5e69ae2e »
2012-04-16 Extract common class Pattern.
229 while matched:
7dfd3726 »
2012-05-13 Fix infinite loop in case OneOrMore(Optional(...)) match.
230 # could it be that something didn't match but changed l or c?
231 matched, l, c = self.children[0].match(l, c)
fd37b68f »
2012-05-11 Slightly better OneOrMore.match.
232 times += 1 if matched else 0
7dfd3726 »
2012-05-13 Fix infinite loop in case OneOrMore(Optional(...)) match.
233 if l_ == l:
234 break
8ae0ace4 »
2012-06-20 remove dependence on copy
235 l_ = l
18685298 »
2012-05-17 Fix bug: arguments not accumulating into same list.
236 if times >= 1:
237 return True, l, c
238 return False, left, collected
9b2cd0a0 »
2012-04-07 Initial commit.
239
240
6f43b600 »
2012-07-29 Experimental: split Pattern into {Child,Parrent,}Pattern
241 class Either(ParrentPattern):
11d534ff »
2012-04-21 Add Either.match(...).
242
243 def match(self, left, collected=None):
244 collected = [] if collected is None else collected
4d6068e8 »
2012-05-29 make Either truly greedy and eliminate GreedyEither
245 outcomes = []
246 for p in self.children:
8ae0ace4 »
2012-06-20 remove dependence on copy
247 matched, _, _ = outcome = p.match(left, collected)
4d6068e8 »
2012-05-29 make Either truly greedy and eliminate GreedyEither
248 if matched:
249 outcomes.append(outcome)
250 if outcomes:
318f8725 »
2012-05-31 Partial support for python3.2.
251 return min(outcomes, key=lambda outcome: len(outcome[1]))
11d534ff »
2012-04-21 Add Either.match(...).
252 return False, left, collected
b0d91be6 »
2012-04-21 Dirty Either.
253
254
bcf7f82b »
2012-06-08 Refactoring of TokenStream by Andrew Kassen (sonwell).
255 class TokenStream(list):
d0512e27 »
2012-05-30 Minor stylistic changes.
256
cc727c58 »
2012-06-03 Unify signature and error handling in parse_long, parse_shorts.
257 def __init__(self, source, error):
283ee5f7 »
2012-07-29 Refactoring: hasattr instead of isinstance.
258 self += source.split() if hasattr(source, 'split') else source
cc727c58 »
2012-06-03 Unify signature and error handling in parse_long, parse_shorts.
259 self.error = error
5faf1be4 »
2012-05-30 recursive descent parser (in progress...)
260
bcf7f82b »
2012-06-08 Refactoring of TokenStream by Andrew Kassen (sonwell).
261 def move(self):
262 return self.pop(0) if len(self) else None
5faf1be4 »
2012-05-30 recursive descent parser (in progress...)
263
bcf7f82b »
2012-06-08 Refactoring of TokenStream by Andrew Kassen (sonwell).
264 def current(self):
265 return self[0] if len(self) else None
5faf1be4 »
2012-05-30 recursive descent parser (in progress...)
266
267
cc727c58 »
2012-06-03 Unify signature and error handling in parse_long, parse_shorts.
268 def parse_long(tokens, options):
a84249ef »
2012-06-03 Get rid of TokenStream.unmove.
269 raw, eq, value = tokens.move().partition('=')
270 value = None if eq == value == '' else value
e5d5b5ab »
2012-07-29 Fix issue 40, #40
271 opt = [o for o in options if o.long and o.long == raw]
272 if tokens.error is DocoptExit and opt == []:
273 opt = [o for o in options if o.long and o.long.startswith(raw)]
619b7c00 »
2012-04-16 Rewrite docopt() without getopt module.
274 if len(opt) < 1:
e8a91f40 »
2012-06-12 Make option descriptions optional, if not ambiguous.
275 if tokens.error is DocoptExit:
276 raise tokens.error('%s is not recognized' % raw)
277 else:
278 o = Option(None, raw, (1 if eq == '=' else 0))
279 options.append(o)
280 return [o]
619b7c00 »
2012-04-16 Rewrite docopt() without getopt module.
281 if len(opt) > 1:
cc727c58 »
2012-06-03 Unify signature and error handling in parse_long, parse_shorts.
282 raise tokens.error('%s is not a unique prefix: %s?' %
b2739805 »
2012-06-02 Minor refactoring.
283 (raw, ', '.join('%s' % o.long for o in opt)))
8ae0ace4 »
2012-06-20 remove dependence on copy
284 o = opt[0]
285 opt = Option(o.short, o.long, o.argcount, o.value)
de37f27a »
2012-06-02 Refactor Option.
286 if opt.argcount == 1:
7c631516 »
2012-04-13 First working version of custom parser.
287 if value is None:
0029d004 »
2012-06-01 Rename token.pop, token.peek - token.current, token.move
288 if tokens.current() is None:
cc727c58 »
2012-06-03 Unify signature and error handling in parse_long, parse_shorts.
289 raise tokens.error('%s requires argument' % opt.name)
0029d004 »
2012-06-01 Rename token.pop, token.peek - token.current, token.move
290 value = tokens.move()
7c631516 »
2012-04-13 First working version of custom parser.
291 elif value is not None:
cc727c58 »
2012-06-03 Unify signature and error handling in parse_long, parse_shorts.
292 raise tokens.error('%s must not have an argument' % opt.name)
8e8cc151 »
2012-07-29 Experimental refactoring of docopt function.
293 if tokens.error is DocoptExit:
294 opt.value = value or True
295 else:
296 opt.value = None if value else False
d5098995 »
2012-06-03 Move towards unified signature for parse_ function.
297 return [opt]
7c631516 »
2012-04-13 First working version of custom parser.
298
299
cc727c58 »
2012-06-03 Unify signature and error handling in parse_long, parse_shorts.
300 def parse_shorts(tokens, options):
d5098995 »
2012-06-03 Move towards unified signature for parse_ function.
301 raw = tokens.move()[1:]
5faf1be4 »
2012-05-30 recursive descent parser (in progress...)
302 parsed = []
7c631516 »
2012-04-13 First working version of custom parser.
303 while raw != '':
de37f27a »
2012-06-02 Refactor Option.
304 opt = [o for o in options
305 if o.short and o.short.lstrip('-').startswith(raw[0])]
1aece2fd »
2012-05-13 Improve error-handling for user- and develper-errors.
306 if len(opt) > 1:
cc727c58 »
2012-06-03 Unify signature and error handling in parse_long, parse_shorts.
307 raise tokens.error('-%s is specified ambiguously %d times' %
1aece2fd »
2012-05-13 Improve error-handling for user- and develper-errors.
308 (raw[0], len(opt)))
309 if len(opt) < 1:
e8a91f40 »
2012-06-12 Make option descriptions optional, if not ambiguous.
310 if tokens.error is DocoptExit:
311 raise tokens.error('-%s is not recognized' % raw[0])
312 else:
313 o = Option('-' + raw[0], None)
314 options.append(o)
315 parsed.append(o)
b282b43e »
2012-06-12 Fix bug with incorrect creation of option with no description.
316 raw = raw[1:]
e8a91f40 »
2012-06-12 Make option descriptions optional, if not ambiguous.
317 continue
8ae0ace4 »
2012-06-20 remove dependence on copy
318 o = opt[0]
319 opt = Option(o.short, o.long, o.argcount, o.value)
7c631516 »
2012-04-13 First working version of custom parser.
320 raw = raw[1:]
de37f27a »
2012-06-02 Refactor Option.
321 if opt.argcount == 0:
8e8cc151 »
2012-07-29 Experimental refactoring of docopt function.
322 value = True if tokens.error is DocoptExit else False
7c631516 »
2012-04-13 First working version of custom parser.
323 else:
324 if raw == '':
0029d004 »
2012-06-01 Rename token.pop, token.peek - token.current, token.move
325 if tokens.current() is None:
cc727c58 »
2012-06-03 Unify signature and error handling in parse_long, parse_shorts.
326 raise tokens.error('-%s requires argument' % opt.short[0])
0029d004 »
2012-06-01 Rename token.pop, token.peek - token.current, token.move
327 raw = tokens.move()
7c631516 »
2012-04-13 First working version of custom parser.
328 value, raw = raw, ''
8ce79dfc »
2012-07-31 Fix bug that captured value from pattern.
329 if tokens.error is DocoptExit:
330 opt.value = value
331 else:
332 opt.value = None if value else False
e8a91f40 »
2012-06-12 Make option descriptions optional, if not ambiguous.
333 parsed.append(opt)
5faf1be4 »
2012-05-30 recursive descent parser (in progress...)
334 return parsed
7c631516 »
2012-04-13 First working version of custom parser.
335
a66bede7 »
2012-06-02 Minor refactoring of parsers.
336
5faf1be4 »
2012-05-30 recursive descent parser (in progress...)
337 def parse_pattern(source, options):
cc727c58 »
2012-06-03 Unify signature and error handling in parse_long, parse_shorts.
338 tokens = TokenStream(re.sub(r'([\[\]\(\)\|]|\.\.\.)', r' \1 ', source),
df336702 »
2012-06-12 Rename: UsageMessageError -> DocoptLanguageError.
339 DocoptLanguageError)
5faf1be4 »
2012-05-30 recursive descent parser (in progress...)
340 result = parse_expr(tokens, options)
2e1a0b44 »
2012-06-03 Better error handling.
341 if tokens.current() is not None:
126ea48a »
2012-06-04 Micro-refactoring.
342 raise tokens.error('unexpected ending: %r' % ' '.join(tokens))
0271da8d »
2012-05-30 working recursive descent parser (kind of)
343 return Required(*result)
344
5faf1be4 »
2012-05-30 recursive descent parser (in progress...)
345
346 def parse_expr(tokens, options):
9f814fd2 »
2012-06-17 Micro-refactoring of parse_atom.
347 """expr ::= seq ( '|' seq )* ;"""
0271da8d »
2012-05-30 working recursive descent parser (kind of)
348 seq = parse_seq(tokens, options)
0029d004 »
2012-06-01 Rename token.pop, token.peek - token.current, token.move
349 if tokens.current() != '|':
0271da8d »
2012-05-30 working recursive descent parser (kind of)
350 return seq
a84249ef »
2012-06-03 Get rid of TokenStream.unmove.
351 result = [Required(*seq)] if len(seq) > 1 else seq
a66bede7 »
2012-06-02 Minor refactoring of parsers.
352 while tokens.current() == '|':
0029d004 »
2012-06-01 Rename token.pop, token.peek - token.current, token.move
353 tokens.move()
0271da8d »
2012-05-30 working recursive descent parser (kind of)
354 seq = parse_seq(tokens, options)
d0512e27 »
2012-05-30 Minor stylistic changes.
355 result += [Required(*seq)] if len(seq) > 1 else seq
a84249ef »
2012-06-03 Get rid of TokenStream.unmove.
356 return [Either(*result)] if len(result) > 1 else result
5faf1be4 »
2012-05-30 recursive descent parser (in progress...)
357
358
359 def parse_seq(tokens, options):
a66bede7 »
2012-06-02 Minor refactoring of parsers.
360 """seq ::= ( atom [ '...' ] )* ;"""
5faf1be4 »
2012-05-30 recursive descent parser (in progress...)
361 result = []
a66bede7 »
2012-06-02 Minor refactoring of parsers.
362 while tokens.current() not in [None, ']', ')', '|']:
5faf1be4 »
2012-05-30 recursive descent parser (in progress...)
363 atom = parse_atom(tokens, options)
0029d004 »
2012-06-01 Rename token.pop, token.peek - token.current, token.move
364 if tokens.current() == '...':
a66bede7 »
2012-06-02 Minor refactoring of parsers.
365 atom = [OneOrMore(*atom)]
0029d004 »
2012-06-01 Rename token.pop, token.peek - token.current, token.move
366 tokens.move()
5faf1be4 »
2012-05-30 recursive descent parser (in progress...)
367 result += atom
0271da8d »
2012-05-30 working recursive descent parser (kind of)
368 return result
5faf1be4 »
2012-05-30 recursive descent parser (in progress...)
369
370
371 def parse_atom(tokens, options):
9f814fd2 »
2012-06-17 Micro-refactoring of parse_atom.
372 """atom ::= '(' expr ')' | '[' expr ']' | 'options'
987bf25a »
2012-06-03 Minor changes.
373 | long | shorts | argument | command ;
09e26630 »
2012-06-01 port AnyOptions shortcut and travis-ci setup from master
374 """
a84249ef »
2012-06-03 Get rid of TokenStream.unmove.
375 token = tokens.current()
5faf1be4 »
2012-05-30 recursive descent parser (in progress...)
376 result = []
52ce4e4e »
2012-07-29 Questionable refactoring of parse_atom.
377 if token in '([':
a84249ef »
2012-06-03 Get rid of TokenStream.unmove.
378 tokens.move()
52ce4e4e »
2012-07-29 Questionable refactoring of parse_atom.
379 matching, pattern = {'(': [')', Required], '[': [']', Optional]}[token]
380 result = pattern(*parse_expr(tokens, options))
381 if tokens.move() != matching:
382 raise tokens.error("unmatched '%s'" % token)
383 return [result]
9f814fd2 »
2012-06-17 Micro-refactoring of parse_atom.
384 elif token == 'options':
385 tokens.move()
a6e1d166 »
2012-07-29 Refactoring: Instead of AnyOptions use options themselves.
386 return options
be6e7970 »
2012-06-12 Make '--' a special command, not syntax.
387 elif token.startswith('--') and token != '--':
a84249ef »
2012-06-03 Get rid of TokenStream.unmove.
388 return parse_long(tokens, options)
be6e7970 »
2012-06-12 Make '--' a special command, not syntax.
389 elif token.startswith('-') and token not in ('-', '--'):
a84249ef »
2012-06-03 Get rid of TokenStream.unmove.
390 return parse_shorts(tokens, options)
d55e10b9 »
2012-06-02 Initial support for commands.
391 elif token.startswith('<') and token.endswith('>') or token.isupper():
a84249ef »
2012-06-03 Get rid of TokenStream.unmove.
392 return [Argument(tokens.move())]
d55e10b9 »
2012-06-02 Initial support for commands.
393 else:
a84249ef »
2012-06-03 Get rid of TokenStream.unmove.
394 return [Command(tokens.move())]
5faf1be4 »
2012-05-30 recursive descent parser (in progress...)
395
396
4ab517b2 »
2012-07-29 Refactoring: collect options same way as arguments.
397 def parse_argv(source, options):
cc727c58 »
2012-06-03 Unify signature and error handling in parse_long, parse_shorts.
398 tokens = TokenStream(source, DocoptExit)
769d84dc »
2012-05-11 Small refactoring of pattern/parser.
399 parsed = []
0029d004 »
2012-06-01 Rename token.pop, token.peek - token.current, token.move
400 while tokens.current() is not None:
a84249ef »
2012-06-03 Get rid of TokenStream.unmove.
401 if tokens.current() == '--':
be6e7970 »
2012-06-12 Make '--' a special command, not syntax.
402 return parsed + [Argument(None, v) for v in tokens]
a84249ef »
2012-06-03 Get rid of TokenStream.unmove.
403 elif tokens.current().startswith('--'):
404 parsed += parse_long(tokens, options)
e9b3acff »
2012-06-12 Allow '-' to be a normal command.
405 elif tokens.current().startswith('-') and tokens.current() != '-':
a84249ef »
2012-06-03 Get rid of TokenStream.unmove.
406 parsed += parse_shorts(tokens, options)
f293e390 »
2012-04-14 Refactor Pattern into parse().
407 else:
a84249ef »
2012-06-03 Get rid of TokenStream.unmove.
408 parsed.append(Argument(None, tokens.move()))
f293e390 »
2012-04-14 Refactor Pattern into parse().
409 return parsed
545d46b3 »
2012-04-16 Reorganize source structure.
410
411
412 def parse_doc_options(doc):
9943f4c3 »
2012-06-03 Move option into Option.parse.
413 return [Option.parse('-' + s) for s in re.split('^ *-|\n *-', doc)[1:]]
545d46b3 »
2012-04-16 Reorganize source structure.
414
415
f62b2369 »
2012-05-06 Move towards having single pattern instead of several.
416 def printable_usage(doc):
4786f475 »
2012-06-12 Raise exception if problems with finding 'usage:'.
417 usage_split = re.split(r'([Uu][Ss][Aa][Gg][Ee]:)', doc)
418 if len(usage_split) < 3:
419 raise DocoptLanguageError('"usage:" (case-insensitive) not found.')
420 if len(usage_split) > 3:
421 raise DocoptLanguageError('More than one "usage:" (case-insensitive).')
422 return re.split(r'\n\s*\n', ''.join(usage_split[1:]))[0].strip()
95ea0876 »
2012-04-22 Print usage on error.
423
424
f62b2369 »
2012-05-06 Move towards having single pattern instead of several.
425 def formal_usage(printable_usage):
426 pu = printable_usage.split()[1:] # split and drop "usage:"
fa484c3e »
2012-06-22 Fix edge case of a leading or trailing pipe.
427 return '( ' + ' '.join(') | (' if s == pu[0] else s for s in pu[1:]) + ' )'
f62b2369 »
2012-05-06 Move towards having single pattern instead of several.
428
429
19f2cb6f »
2012-04-22 Fix extras.
430 def extras(help, version, options, doc):
ddb7347a »
2012-06-02 Minor refactoring.
431 if help and any((o.name in ('-h', '--help')) and o.value for o in options):
95ea0876 »
2012-04-22 Print usage on error.
432 print(doc.strip())
433 exit()
e5e4a898 »
2012-06-12 Fix bug when user error was shown in case of developer error.
434 if version and any(o.name == '--version' and o.value for o in options):
95ea0876 »
2012-04-22 Print usage on error.
435 print(version)
436 exit()
b62099ba »
2012-04-17 Arguments are collected while matching.
437
a078f207 »
2012-04-20 Minor refactoring: for better or for worse.
438
9586e9a8 »
2012-05-31 Make docopt return dict.
439 class Dict(dict):
440 def __repr__(self):
441 return '{%s}' % ',\n '.join('%r: %r' % i for i in sorted(self.items()))
442
443
a0eab75b »
2012-05-17 Document API for future 0.2 release with pattern-matching.
444 def docopt(doc, argv=sys.argv[1:], help=True, version=None):
a6e1d166 »
2012-07-29 Refactoring: Instead of AnyOptions use options themselves.
445 DocoptExit.usage = printable_usage(doc)
8e8cc151 »
2012-07-29 Experimental refactoring of docopt function.
446 options = parse_doc_options(doc)
a6e1d166 »
2012-07-29 Refactoring: Instead of AnyOptions use options themselves.
447 pattern = parse_pattern(formal_usage(DocoptExit.usage), options)
8e8cc151 »
2012-07-29 Experimental refactoring of docopt function.
448 argv = parse_argv(argv, options)
e5e4a898 »
2012-06-12 Fix bug when user error was shown in case of developer error.
449 extras(help, version, argv, doc)
4ab517b2 »
2012-07-29 Refactoring: collect options same way as arguments.
450 matched, left, collected = pattern.fix().match(argv)
451 if matched and left == []: # better error message if left?
8ce79dfc »
2012-07-31 Fix bug that captured value from pattern.
452 return Dict((a.name, a.value) for a in (pattern.flat + options + collected))
0e6f623e »
2012-05-06 Get rid of usage(), parse_doc_usage().
453 raise DocoptExit()
Something went wrong with that request. Please try again.