Mercurial > minori
comparison dep/fmt/support/docopt.py @ 343:1faa72660932
*: transfer back to cmake from autotools
autotools just made lots of things more complicated than
they should have and many things broke (i.e. translations)
| author | Paper <paper@paper.us.eu.org> |
|---|---|
| date | Thu, 20 Jun 2024 05:56:06 -0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 342:adb79bdde329 | 343:1faa72660932 |
|---|---|
| 1 """Pythonic command-line interface parser that will make you smile. | |
| 2 | |
| 3 * http://docopt.org | |
| 4 * Repository and issue-tracker: https://github.com/docopt/docopt | |
| 5 * Licensed under terms of MIT license (see LICENSE-MIT) | |
| 6 * Copyright (c) 2013 Vladimir Keleshev, vladimir@keleshev.com | |
| 7 | |
| 8 """ | |
| 9 import sys | |
| 10 import re | |
| 11 | |
| 12 | |
| 13 __all__ = ['docopt'] | |
| 14 __version__ = '0.6.1' | |
| 15 | |
| 16 | |
| 17 class DocoptLanguageError(Exception): | |
| 18 | |
| 19 """Error in construction of usage-message by developer.""" | |
| 20 | |
| 21 | |
| 22 class DocoptExit(SystemExit): | |
| 23 | |
| 24 """Exit in case user invoked program with incorrect arguments.""" | |
| 25 | |
| 26 usage = '' | |
| 27 | |
| 28 def __init__(self, message=''): | |
| 29 SystemExit.__init__(self, (message + '\n' + self.usage).strip()) | |
| 30 | |
| 31 | |
| 32 class Pattern(object): | |
| 33 | |
| 34 def __eq__(self, other): | |
| 35 return repr(self) == repr(other) | |
| 36 | |
| 37 def __hash__(self): | |
| 38 return hash(repr(self)) | |
| 39 | |
| 40 def fix(self): | |
| 41 self.fix_identities() | |
| 42 self.fix_repeating_arguments() | |
| 43 return self | |
| 44 | |
| 45 def fix_identities(self, uniq=None): | |
| 46 """Make pattern-tree tips point to same object if they are equal.""" | |
| 47 if not hasattr(self, 'children'): | |
| 48 return self | |
| 49 uniq = list(set(self.flat())) if uniq is None else uniq | |
| 50 for i, child in enumerate(self.children): | |
| 51 if not hasattr(child, 'children'): | |
| 52 assert child in uniq | |
| 53 self.children[i] = uniq[uniq.index(child)] | |
| 54 else: | |
| 55 child.fix_identities(uniq) | |
| 56 | |
| 57 def fix_repeating_arguments(self): | |
| 58 """Fix elements that should accumulate/increment values.""" | |
| 59 either = [list(child.children) for child in transform(self).children] | |
| 60 for case in either: | |
| 61 for e in [child for child in case if case.count(child) > 1]: | |
| 62 if type(e) is Argument or type(e) is Option and e.argcount: | |
| 63 if e.value is None: | |
| 64 e.value = [] | |
| 65 elif type(e.value) is not list: | |
| 66 e.value = e.value.split() | |
| 67 if type(e) is Command or type(e) is Option and e.argcount == 0: | |
| 68 e.value = 0 | |
| 69 return self | |
| 70 | |
| 71 | |
| 72 def transform(pattern): | |
| 73 """Expand pattern into an (almost) equivalent one, but with single Either. | |
| 74 | |
| 75 Example: ((-a | -b) (-c | -d)) => (-a -c | -a -d | -b -c | -b -d) | |
| 76 Quirks: [-a] => (-a), (-a...) => (-a -a) | |
| 77 | |
| 78 """ | |
| 79 result = [] | |
| 80 groups = [[pattern]] | |
| 81 while groups: | |
| 82 children = groups.pop(0) | |
| 83 parents = [Required, Optional, OptionsShortcut, Either, OneOrMore] | |
| 84 if any(t in map(type, children) for t in parents): | |
| 85 child = [c for c in children if type(c) in parents][0] | |
| 86 children.remove(child) | |
| 87 if type(child) is Either: | |
| 88 for c in child.children: | |
| 89 groups.append([c] + children) | |
| 90 elif type(child) is OneOrMore: | |
| 91 groups.append(child.children * 2 + children) | |
| 92 else: | |
| 93 groups.append(child.children + children) | |
| 94 else: | |
| 95 result.append(children) | |
| 96 return Either(*[Required(*e) for e in result]) | |
| 97 | |
| 98 | |
| 99 class LeafPattern(Pattern): | |
| 100 | |
| 101 """Leaf/terminal node of a pattern tree.""" | |
| 102 | |
| 103 def __init__(self, name, value=None): | |
| 104 self.name, self.value = name, value | |
| 105 | |
| 106 def __repr__(self): | |
| 107 return '%s(%r, %r)' % (self.__class__.__name__, self.name, self.value) | |
| 108 | |
| 109 def flat(self, *types): | |
| 110 return [self] if not types or type(self) in types else [] | |
| 111 | |
| 112 def match(self, left, collected=None): | |
| 113 collected = [] if collected is None else collected | |
| 114 pos, match = self.single_match(left) | |
| 115 if match is None: | |
| 116 return False, left, collected | |
| 117 left_ = left[:pos] + left[pos + 1:] | |
| 118 same_name = [a for a in collected if a.name == self.name] | |
| 119 if type(self.value) in (int, list): | |
| 120 if type(self.value) is int: | |
| 121 increment = 1 | |
| 122 else: | |
| 123 increment = ([match.value] if type(match.value) is str | |
| 124 else match.value) | |
| 125 if not same_name: | |
| 126 match.value = increment | |
| 127 return True, left_, collected + [match] | |
| 128 same_name[0].value += increment | |
| 129 return True, left_, collected | |
| 130 return True, left_, collected + [match] | |
| 131 | |
| 132 | |
| 133 class BranchPattern(Pattern): | |
| 134 | |
| 135 """Branch/inner node of a pattern tree.""" | |
| 136 | |
| 137 def __init__(self, *children): | |
| 138 self.children = list(children) | |
| 139 | |
| 140 def __repr__(self): | |
| 141 return '%s(%s)' % (self.__class__.__name__, | |
| 142 ', '.join(repr(a) for a in self.children)) | |
| 143 | |
| 144 def flat(self, *types): | |
| 145 if type(self) in types: | |
| 146 return [self] | |
| 147 return sum([child.flat(*types) for child in self.children], []) | |
| 148 | |
| 149 | |
| 150 class Argument(LeafPattern): | |
| 151 | |
| 152 def single_match(self, left): | |
| 153 for n, pattern in enumerate(left): | |
| 154 if type(pattern) is Argument: | |
| 155 return n, Argument(self.name, pattern.value) | |
| 156 return None, None | |
| 157 | |
| 158 @classmethod | |
| 159 def parse(class_, source): | |
| 160 name = re.findall('(<\S*?>)', source)[0] | |
| 161 value = re.findall('\[default: (.*)\]', source, flags=re.I) | |
| 162 return class_(name, value[0] if value else None) | |
| 163 | |
| 164 | |
| 165 class Command(Argument): | |
| 166 | |
| 167 def __init__(self, name, value=False): | |
| 168 self.name, self.value = name, value | |
| 169 | |
| 170 def single_match(self, left): | |
| 171 for n, pattern in enumerate(left): | |
| 172 if type(pattern) is Argument: | |
| 173 if pattern.value == self.name: | |
| 174 return n, Command(self.name, True) | |
| 175 else: | |
| 176 break | |
| 177 return None, None | |
| 178 | |
| 179 | |
| 180 class Option(LeafPattern): | |
| 181 | |
| 182 def __init__(self, short=None, long=None, argcount=0, value=False): | |
| 183 assert argcount in (0, 1) | |
| 184 self.short, self.long, self.argcount = short, long, argcount | |
| 185 self.value = None if value is False and argcount else value | |
| 186 | |
| 187 @classmethod | |
| 188 def parse(class_, option_description): | |
| 189 short, long, argcount, value = None, None, 0, False | |
| 190 options, _, description = option_description.strip().partition(' ') | |
| 191 options = options.replace(',', ' ').replace('=', ' ') | |
| 192 for s in options.split(): | |
| 193 if s.startswith('--'): | |
| 194 long = s | |
| 195 elif s.startswith('-'): | |
| 196 short = s | |
| 197 else: | |
| 198 argcount = 1 | |
| 199 if argcount: | |
| 200 matched = re.findall('\[default: (.*)\]', description, flags=re.I) | |
| 201 value = matched[0] if matched else None | |
| 202 return class_(short, long, argcount, value) | |
| 203 | |
| 204 def single_match(self, left): | |
| 205 for n, pattern in enumerate(left): | |
| 206 if self.name == pattern.name: | |
| 207 return n, pattern | |
| 208 return None, None | |
| 209 | |
| 210 @property | |
| 211 def name(self): | |
| 212 return self.long or self.short | |
| 213 | |
| 214 def __repr__(self): | |
| 215 return 'Option(%r, %r, %r, %r)' % (self.short, self.long, | |
| 216 self.argcount, self.value) | |
| 217 | |
| 218 | |
| 219 class Required(BranchPattern): | |
| 220 | |
| 221 def match(self, left, collected=None): | |
| 222 collected = [] if collected is None else collected | |
| 223 l = left | |
| 224 c = collected | |
| 225 for pattern in self.children: | |
| 226 matched, l, c = pattern.match(l, c) | |
| 227 if not matched: | |
| 228 return False, left, collected | |
| 229 return True, l, c | |
| 230 | |
| 231 | |
| 232 class Optional(BranchPattern): | |
| 233 | |
| 234 def match(self, left, collected=None): | |
| 235 collected = [] if collected is None else collected | |
| 236 for pattern in self.children: | |
| 237 m, left, collected = pattern.match(left, collected) | |
| 238 return True, left, collected | |
| 239 | |
| 240 | |
| 241 class OptionsShortcut(Optional): | |
| 242 | |
| 243 """Marker/placeholder for [options] shortcut.""" | |
| 244 | |
| 245 | |
| 246 class OneOrMore(BranchPattern): | |
| 247 | |
| 248 def match(self, left, collected=None): | |
| 249 assert len(self.children) == 1 | |
| 250 collected = [] if collected is None else collected | |
| 251 l = left | |
| 252 c = collected | |
| 253 l_ = None | |
| 254 matched = True | |
| 255 times = 0 | |
| 256 while matched: | |
| 257 # could it be that something didn't match but changed l or c? | |
| 258 matched, l, c = self.children[0].match(l, c) | |
| 259 times += 1 if matched else 0 | |
| 260 if l_ == l: | |
| 261 break | |
| 262 l_ = l | |
| 263 if times >= 1: | |
| 264 return True, l, c | |
| 265 return False, left, collected | |
| 266 | |
| 267 | |
| 268 class Either(BranchPattern): | |
| 269 | |
| 270 def match(self, left, collected=None): | |
| 271 collected = [] if collected is None else collected | |
| 272 outcomes = [] | |
| 273 for pattern in self.children: | |
| 274 matched, _, _ = outcome = pattern.match(left, collected) | |
| 275 if matched: | |
| 276 outcomes.append(outcome) | |
| 277 if outcomes: | |
| 278 return min(outcomes, key=lambda outcome: len(outcome[1])) | |
| 279 return False, left, collected | |
| 280 | |
| 281 | |
| 282 class Tokens(list): | |
| 283 | |
| 284 def __init__(self, source, error=DocoptExit): | |
| 285 self += source.split() if hasattr(source, 'split') else source | |
| 286 self.error = error | |
| 287 | |
| 288 @staticmethod | |
| 289 def from_pattern(source): | |
| 290 source = re.sub(r'([\[\]\(\)\|]|\.\.\.)', r' \1 ', source) | |
| 291 source = [s for s in re.split('\s+|(\S*<.*?>)', source) if s] | |
| 292 return Tokens(source, error=DocoptLanguageError) | |
| 293 | |
| 294 def move(self): | |
| 295 return self.pop(0) if len(self) else None | |
| 296 | |
| 297 def current(self): | |
| 298 return self[0] if len(self) else None | |
| 299 | |
| 300 | |
| 301 def parse_long(tokens, options): | |
| 302 """long ::= '--' chars [ ( ' ' | '=' ) chars ] ;""" | |
| 303 long, eq, value = tokens.move().partition('=') | |
| 304 assert long.startswith('--') | |
| 305 value = None if eq == value == '' else value | |
| 306 similar = [o for o in options if o.long == long] | |
| 307 if tokens.error is DocoptExit and similar == []: # if no exact match | |
| 308 similar = [o for o in options if o.long and o.long.startswith(long)] | |
| 309 if len(similar) > 1: # might be simply specified ambiguously 2+ times? | |
| 310 raise tokens.error('%s is not a unique prefix: %s?' % | |
| 311 (long, ', '.join(o.long for o in similar))) | |
| 312 elif len(similar) < 1: | |
| 313 argcount = 1 if eq == '=' else 0 | |
| 314 o = Option(None, long, argcount) | |
| 315 options.append(o) | |
| 316 if tokens.error is DocoptExit: | |
| 317 o = Option(None, long, argcount, value if argcount else True) | |
| 318 else: | |
| 319 o = Option(similar[0].short, similar[0].long, | |
| 320 similar[0].argcount, similar[0].value) | |
| 321 if o.argcount == 0: | |
| 322 if value is not None: | |
| 323 raise tokens.error('%s must not have an argument' % o.long) | |
| 324 else: | |
| 325 if value is None: | |
| 326 if tokens.current() in [None, '--']: | |
| 327 raise tokens.error('%s requires argument' % o.long) | |
| 328 value = tokens.move() | |
| 329 if tokens.error is DocoptExit: | |
| 330 o.value = value if value is not None else True | |
| 331 return [o] | |
| 332 | |
| 333 | |
| 334 def parse_shorts(tokens, options): | |
| 335 """shorts ::= '-' ( chars )* [ [ ' ' ] chars ] ;""" | |
| 336 token = tokens.move() | |
| 337 assert token.startswith('-') and not token.startswith('--') | |
| 338 left = token.lstrip('-') | |
| 339 parsed = [] | |
| 340 while left != '': | |
| 341 short, left = '-' + left[0], left[1:] | |
| 342 similar = [o for o in options if o.short == short] | |
| 343 if len(similar) > 1: | |
| 344 raise tokens.error('%s is specified ambiguously %d times' % | |
| 345 (short, len(similar))) | |
| 346 elif len(similar) < 1: | |
| 347 o = Option(short, None, 0) | |
| 348 options.append(o) | |
| 349 if tokens.error is DocoptExit: | |
| 350 o = Option(short, None, 0, True) | |
| 351 else: # why copying is necessary here? | |
| 352 o = Option(short, similar[0].long, | |
| 353 similar[0].argcount, similar[0].value) | |
| 354 value = None | |
| 355 if o.argcount != 0: | |
| 356 if left == '': | |
| 357 if tokens.current() in [None, '--']: | |
| 358 raise tokens.error('%s requires argument' % short) | |
| 359 value = tokens.move() | |
| 360 else: | |
| 361 value = left | |
| 362 left = '' | |
| 363 if tokens.error is DocoptExit: | |
| 364 o.value = value if value is not None else True | |
| 365 parsed.append(o) | |
| 366 return parsed | |
| 367 | |
| 368 | |
| 369 def parse_pattern(source, options): | |
| 370 tokens = Tokens.from_pattern(source) | |
| 371 result = parse_expr(tokens, options) | |
| 372 if tokens.current() is not None: | |
| 373 raise tokens.error('unexpected ending: %r' % ' '.join(tokens)) | |
| 374 return Required(*result) | |
| 375 | |
| 376 | |
| 377 def parse_expr(tokens, options): | |
| 378 """expr ::= seq ( '|' seq )* ;""" | |
| 379 seq = parse_seq(tokens, options) | |
| 380 if tokens.current() != '|': | |
| 381 return seq | |
| 382 result = [Required(*seq)] if len(seq) > 1 else seq | |
| 383 while tokens.current() == '|': | |
| 384 tokens.move() | |
| 385 seq = parse_seq(tokens, options) | |
| 386 result += [Required(*seq)] if len(seq) > 1 else seq | |
| 387 return [Either(*result)] if len(result) > 1 else result | |
| 388 | |
| 389 | |
| 390 def parse_seq(tokens, options): | |
| 391 """seq ::= ( atom [ '...' ] )* ;""" | |
| 392 result = [] | |
| 393 while tokens.current() not in [None, ']', ')', '|']: | |
| 394 atom = parse_atom(tokens, options) | |
| 395 if tokens.current() == '...': | |
| 396 atom = [OneOrMore(*atom)] | |
| 397 tokens.move() | |
| 398 result += atom | |
| 399 return result | |
| 400 | |
| 401 | |
| 402 def parse_atom(tokens, options): | |
| 403 """atom ::= '(' expr ')' | '[' expr ']' | 'options' | |
| 404 | long | shorts | argument | command ; | |
| 405 """ | |
| 406 token = tokens.current() | |
| 407 result = [] | |
| 408 if token in '([': | |
| 409 tokens.move() | |
| 410 matching, pattern = {'(': [')', Required], '[': [']', Optional]}[token] | |
| 411 result = pattern(*parse_expr(tokens, options)) | |
| 412 if tokens.move() != matching: | |
| 413 raise tokens.error("unmatched '%s'" % token) | |
| 414 return [result] | |
| 415 elif token == 'options': | |
| 416 tokens.move() | |
| 417 return [OptionsShortcut()] | |
| 418 elif token.startswith('--') and token != '--': | |
| 419 return parse_long(tokens, options) | |
| 420 elif token.startswith('-') and token not in ('-', '--'): | |
| 421 return parse_shorts(tokens, options) | |
| 422 elif token.startswith('<') and token.endswith('>') or token.isupper(): | |
| 423 return [Argument(tokens.move())] | |
| 424 else: | |
| 425 return [Command(tokens.move())] | |
| 426 | |
| 427 | |
| 428 def parse_argv(tokens, options, options_first=False): | |
| 429 """Parse command-line argument vector. | |
| 430 | |
| 431 If options_first: | |
| 432 argv ::= [ long | shorts ]* [ argument ]* [ '--' [ argument ]* ] ; | |
| 433 else: | |
| 434 argv ::= [ long | shorts | argument ]* [ '--' [ argument ]* ] ; | |
| 435 | |
| 436 """ | |
| 437 parsed = [] | |
| 438 while tokens.current() is not None: | |
| 439 if tokens.current() == '--': | |
| 440 return parsed + [Argument(None, v) for v in tokens] | |
| 441 elif tokens.current().startswith('--'): | |
| 442 parsed += parse_long(tokens, options) | |
| 443 elif tokens.current().startswith('-') and tokens.current() != '-': | |
| 444 parsed += parse_shorts(tokens, options) | |
| 445 elif options_first: | |
| 446 return parsed + [Argument(None, v) for v in tokens] | |
| 447 else: | |
| 448 parsed.append(Argument(None, tokens.move())) | |
| 449 return parsed | |
| 450 | |
| 451 | |
| 452 def parse_defaults(doc): | |
| 453 defaults = [] | |
| 454 for s in parse_section('options:', doc): | |
| 455 # FIXME corner case "bla: options: --foo" | |
| 456 _, _, s = s.partition(':') # get rid of "options:" | |
| 457 split = re.split('\n[ \t]*(-\S+?)', '\n' + s)[1:] | |
| 458 split = [s1 + s2 for s1, s2 in zip(split[::2], split[1::2])] | |
| 459 options = [Option.parse(s) for s in split if s.startswith('-')] | |
| 460 defaults += options | |
| 461 return defaults | |
| 462 | |
| 463 | |
| 464 def parse_section(name, source): | |
| 465 pattern = re.compile('^([^\n]*' + name + '[^\n]*\n?(?:[ \t].*?(?:\n|$))*)', | |
| 466 re.IGNORECASE | re.MULTILINE) | |
| 467 return [s.strip() for s in pattern.findall(source)] | |
| 468 | |
| 469 | |
| 470 def formal_usage(section): | |
| 471 _, _, section = section.partition(':') # drop "usage:" | |
| 472 pu = section.split() | |
| 473 return '( ' + ' '.join(') | (' if s == pu[0] else s for s in pu[1:]) + ' )' | |
| 474 | |
| 475 | |
| 476 def extras(help, version, options, doc): | |
| 477 if help and any((o.name in ('-h', '--help')) and o.value for o in options): | |
| 478 print(doc.strip("\n")) | |
| 479 sys.exit() | |
| 480 if version and any(o.name == '--version' and o.value for o in options): | |
| 481 print(version) | |
| 482 sys.exit() | |
| 483 | |
| 484 | |
| 485 class Dict(dict): | |
| 486 def __repr__(self): | |
| 487 return '{%s}' % ',\n '.join('%r: %r' % i for i in sorted(self.items())) | |
| 488 | |
| 489 | |
| 490 def docopt(doc, argv=None, help=True, version=None, options_first=False): | |
| 491 """Parse `argv` based on command-line interface described in `doc`. | |
| 492 | |
| 493 `docopt` creates your command-line interface based on its | |
| 494 description that you pass as `doc`. Such description can contain | |
| 495 --options, <positional-argument>, commands, which could be | |
| 496 [optional], (required), (mutually | exclusive) or repeated... | |
| 497 | |
| 498 Parameters | |
| 499 ---------- | |
| 500 doc : str | |
| 501 Description of your command-line interface. | |
| 502 argv : list of str, optional | |
| 503 Argument vector to be parsed. sys.argv[1:] is used if not | |
| 504 provided. | |
| 505 help : bool (default: True) | |
| 506 Set to False to disable automatic help on -h or --help | |
| 507 options. | |
| 508 version : any object | |
| 509 If passed, the object will be printed if --version is in | |
| 510 `argv`. | |
| 511 options_first : bool (default: False) | |
| 512 Set to True to require options precede positional arguments, | |
| 513 i.e. to forbid options and positional arguments intermix. | |
| 514 | |
| 515 Returns | |
| 516 ------- | |
| 517 args : dict | |
| 518 A dictionary, where keys are names of command-line elements | |
| 519 such as e.g. "--verbose" and "<path>", and values are the | |
| 520 parsed values of those elements. | |
| 521 | |
| 522 Example | |
| 523 ------- | |
| 524 >>> from docopt import docopt | |
| 525 >>> doc = ''' | |
| 526 ... Usage: | |
| 527 ... my_program tcp <host> <port> [--timeout=<seconds>] | |
| 528 ... my_program serial <port> [--baud=<n>] [--timeout=<seconds>] | |
| 529 ... my_program (-h | --help | --version) | |
| 530 ... | |
| 531 ... Options: | |
| 532 ... -h, --help Show this screen and exit. | |
| 533 ... --baud=<n> Baudrate [default: 9600] | |
| 534 ... ''' | |
| 535 >>> argv = ['tcp', '127.0.0.1', '80', '--timeout', '30'] | |
| 536 >>> docopt(doc, argv) | |
| 537 {'--baud': '9600', | |
| 538 '--help': False, | |
| 539 '--timeout': '30', | |
| 540 '--version': False, | |
| 541 '<host>': '127.0.0.1', | |
| 542 '<port>': '80', | |
| 543 'serial': False, | |
| 544 'tcp': True} | |
| 545 | |
| 546 See also | |
| 547 -------- | |
| 548 * For video introduction see http://docopt.org | |
| 549 * Full documentation is available in README.rst as well as online | |
| 550 at https://github.com/docopt/docopt#readme | |
| 551 | |
| 552 """ | |
| 553 argv = sys.argv[1:] if argv is None else argv | |
| 554 | |
| 555 usage_sections = parse_section('usage:', doc) | |
| 556 if len(usage_sections) == 0: | |
| 557 raise DocoptLanguageError('"usage:" (case-insensitive) not found.') | |
| 558 if len(usage_sections) > 1: | |
| 559 raise DocoptLanguageError('More than one "usage:" (case-insensitive).') | |
| 560 DocoptExit.usage = usage_sections[0] | |
| 561 | |
| 562 options = parse_defaults(doc) | |
| 563 pattern = parse_pattern(formal_usage(DocoptExit.usage), options) | |
| 564 # [default] syntax for argument is disabled | |
| 565 #for a in pattern.flat(Argument): | |
| 566 # same_name = [d for d in arguments if d.name == a.name] | |
| 567 # if same_name: | |
| 568 # a.value = same_name[0].value | |
| 569 argv = parse_argv(Tokens(argv), list(options), options_first) | |
| 570 pattern_options = set(pattern.flat(Option)) | |
| 571 for options_shortcut in pattern.flat(OptionsShortcut): | |
| 572 doc_options = parse_defaults(doc) | |
| 573 options_shortcut.children = list(set(doc_options) - pattern_options) | |
| 574 #if any_options: | |
| 575 # options_shortcut.children += [Option(o.short, o.long, o.argcount) | |
| 576 # for o in argv if type(o) is Option] | |
| 577 extras(help, version, argv, doc) | |
| 578 matched, left, collected = pattern.fix().match(argv) | |
| 579 if matched and left == []: # better error message if left? | |
| 580 return Dict((a.name, a.value) for a in (pattern.flat() + collected)) | |
| 581 raise DocoptExit() |
