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