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