Mercurial > repos > guerler > springsuite
comparison planemo/lib/python3.7/site-packages/libfuturize/fixer_util.py @ 1:56ad4e20f292 draft
"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author | guerler |
---|---|
date | Fri, 31 Jul 2020 00:32:28 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
0:d30785e31577 | 1:56ad4e20f292 |
---|---|
1 """ | |
2 Utility functions from 2to3, 3to2 and python-modernize (and some home-grown | |
3 ones). | |
4 | |
5 Licences: | |
6 2to3: PSF License v2 | |
7 3to2: Apache Software License (from 3to2/setup.py) | |
8 python-modernize licence: BSD (from python-modernize/LICENSE) | |
9 """ | |
10 | |
11 from lib2to3.fixer_util import (FromImport, Newline, is_import, | |
12 find_root, does_tree_import, Comma) | |
13 from lib2to3.pytree import Leaf, Node | |
14 from lib2to3.pygram import python_symbols as syms, python_grammar | |
15 from lib2to3.pygram import token | |
16 from lib2to3.fixer_util import (Node, Call, Name, syms, Comma, Number) | |
17 import re | |
18 | |
19 | |
20 def canonical_fix_name(fix, avail_fixes): | |
21 """ | |
22 Examples: | |
23 >>> canonical_fix_name('fix_wrap_text_literals') | |
24 'libfuturize.fixes.fix_wrap_text_literals' | |
25 >>> canonical_fix_name('wrap_text_literals') | |
26 'libfuturize.fixes.fix_wrap_text_literals' | |
27 >>> canonical_fix_name('wrap_te') | |
28 ValueError("unknown fixer name") | |
29 >>> canonical_fix_name('wrap') | |
30 ValueError("ambiguous fixer name") | |
31 """ | |
32 if ".fix_" in fix: | |
33 return fix | |
34 else: | |
35 if fix.startswith('fix_'): | |
36 fix = fix[4:] | |
37 # Infer the full module name for the fixer. | |
38 # First ensure that no names clash (e.g. | |
39 # lib2to3.fixes.fix_blah and libfuturize.fixes.fix_blah): | |
40 found = [f for f in avail_fixes | |
41 if f.endswith('fix_{0}'.format(fix))] | |
42 if len(found) > 1: | |
43 raise ValueError("Ambiguous fixer name. Choose a fully qualified " | |
44 "module name instead from these:\n" + | |
45 "\n".join(" " + myf for myf in found)) | |
46 elif len(found) == 0: | |
47 raise ValueError("Unknown fixer. Use --list-fixes or -l for a list.") | |
48 return found[0] | |
49 | |
50 | |
51 | |
52 ## These functions are from 3to2 by Joe Amenta: | |
53 | |
54 def Star(prefix=None): | |
55 return Leaf(token.STAR, u'*', prefix=prefix) | |
56 | |
57 def DoubleStar(prefix=None): | |
58 return Leaf(token.DOUBLESTAR, u'**', prefix=prefix) | |
59 | |
60 def Minus(prefix=None): | |
61 return Leaf(token.MINUS, u'-', prefix=prefix) | |
62 | |
63 def commatize(leafs): | |
64 """ | |
65 Accepts/turns: (Name, Name, ..., Name, Name) | |
66 Returns/into: (Name, Comma, Name, Comma, ..., Name, Comma, Name) | |
67 """ | |
68 new_leafs = [] | |
69 for leaf in leafs: | |
70 new_leafs.append(leaf) | |
71 new_leafs.append(Comma()) | |
72 del new_leafs[-1] | |
73 return new_leafs | |
74 | |
75 def indentation(node): | |
76 """ | |
77 Returns the indentation for this node | |
78 Iff a node is in a suite, then it has indentation. | |
79 """ | |
80 while node.parent is not None and node.parent.type != syms.suite: | |
81 node = node.parent | |
82 if node.parent is None: | |
83 return u"" | |
84 # The first three children of a suite are NEWLINE, INDENT, (some other node) | |
85 # INDENT.value contains the indentation for this suite | |
86 # anything after (some other node) has the indentation as its prefix. | |
87 if node.type == token.INDENT: | |
88 return node.value | |
89 elif node.prev_sibling is not None and node.prev_sibling.type == token.INDENT: | |
90 return node.prev_sibling.value | |
91 elif node.prev_sibling is None: | |
92 return u"" | |
93 else: | |
94 return node.prefix | |
95 | |
96 def indentation_step(node): | |
97 """ | |
98 Dirty little trick to get the difference between each indentation level | |
99 Implemented by finding the shortest indentation string | |
100 (technically, the "least" of all of the indentation strings, but | |
101 tabs and spaces mixed won't get this far, so those are synonymous.) | |
102 """ | |
103 r = find_root(node) | |
104 # Collect all indentations into one set. | |
105 all_indents = set(i.value for i in r.pre_order() if i.type == token.INDENT) | |
106 if not all_indents: | |
107 # nothing is indented anywhere, so we get to pick what we want | |
108 return u" " # four spaces is a popular convention | |
109 else: | |
110 return min(all_indents) | |
111 | |
112 def suitify(parent): | |
113 """ | |
114 Turn the stuff after the first colon in parent's children | |
115 into a suite, if it wasn't already | |
116 """ | |
117 for node in parent.children: | |
118 if node.type == syms.suite: | |
119 # already in the prefered format, do nothing | |
120 return | |
121 | |
122 # One-liners have no suite node, we have to fake one up | |
123 for i, node in enumerate(parent.children): | |
124 if node.type == token.COLON: | |
125 break | |
126 else: | |
127 raise ValueError(u"No class suite and no ':'!") | |
128 # Move everything into a suite node | |
129 suite = Node(syms.suite, [Newline(), Leaf(token.INDENT, indentation(node) + indentation_step(node))]) | |
130 one_node = parent.children[i+1] | |
131 one_node.remove() | |
132 one_node.prefix = u'' | |
133 suite.append_child(one_node) | |
134 parent.append_child(suite) | |
135 | |
136 def NameImport(package, as_name=None, prefix=None): | |
137 """ | |
138 Accepts a package (Name node), name to import it as (string), and | |
139 optional prefix and returns a node: | |
140 import <package> [as <as_name>] | |
141 """ | |
142 if prefix is None: | |
143 prefix = u"" | |
144 children = [Name(u"import", prefix=prefix), package] | |
145 if as_name is not None: | |
146 children.extend([Name(u"as", prefix=u" "), | |
147 Name(as_name, prefix=u" ")]) | |
148 return Node(syms.import_name, children) | |
149 | |
150 _compound_stmts = (syms.if_stmt, syms.while_stmt, syms.for_stmt, syms.try_stmt, syms.with_stmt) | |
151 _import_stmts = (syms.import_name, syms.import_from) | |
152 | |
153 def import_binding_scope(node): | |
154 """ | |
155 Generator yields all nodes for which a node (an import_stmt) has scope | |
156 The purpose of this is for a call to _find() on each of them | |
157 """ | |
158 # import_name / import_from are small_stmts | |
159 assert node.type in _import_stmts | |
160 test = node.next_sibling | |
161 # A small_stmt can only be followed by a SEMI or a NEWLINE. | |
162 while test.type == token.SEMI: | |
163 nxt = test.next_sibling | |
164 # A SEMI can only be followed by a small_stmt or a NEWLINE | |
165 if nxt.type == token.NEWLINE: | |
166 break | |
167 else: | |
168 yield nxt | |
169 # A small_stmt can only be followed by either a SEMI or a NEWLINE | |
170 test = nxt.next_sibling | |
171 # Covered all subsequent small_stmts after the import_stmt | |
172 # Now to cover all subsequent stmts after the parent simple_stmt | |
173 parent = node.parent | |
174 assert parent.type == syms.simple_stmt | |
175 test = parent.next_sibling | |
176 while test is not None: | |
177 # Yes, this will yield NEWLINE and DEDENT. Deal with it. | |
178 yield test | |
179 test = test.next_sibling | |
180 | |
181 context = parent.parent | |
182 # Recursively yield nodes following imports inside of a if/while/for/try/with statement | |
183 if context.type in _compound_stmts: | |
184 # import is in a one-liner | |
185 c = context | |
186 while c.next_sibling is not None: | |
187 yield c.next_sibling | |
188 c = c.next_sibling | |
189 context = context.parent | |
190 | |
191 # Can't chain one-liners on one line, so that takes care of that. | |
192 | |
193 p = context.parent | |
194 if p is None: | |
195 return | |
196 | |
197 # in a multi-line suite | |
198 | |
199 while p.type in _compound_stmts: | |
200 | |
201 if context.type == syms.suite: | |
202 yield context | |
203 | |
204 context = context.next_sibling | |
205 | |
206 if context is None: | |
207 context = p.parent | |
208 p = context.parent | |
209 if p is None: | |
210 break | |
211 | |
212 def ImportAsName(name, as_name, prefix=None): | |
213 new_name = Name(name) | |
214 new_as = Name(u"as", prefix=u" ") | |
215 new_as_name = Name(as_name, prefix=u" ") | |
216 new_node = Node(syms.import_as_name, [new_name, new_as, new_as_name]) | |
217 if prefix is not None: | |
218 new_node.prefix = prefix | |
219 return new_node | |
220 | |
221 | |
222 def is_docstring(node): | |
223 """ | |
224 Returns True if the node appears to be a docstring | |
225 """ | |
226 return (node.type == syms.simple_stmt and | |
227 len(node.children) > 0 and node.children[0].type == token.STRING) | |
228 | |
229 | |
230 def future_import(feature, node): | |
231 """ | |
232 This seems to work | |
233 """ | |
234 root = find_root(node) | |
235 | |
236 if does_tree_import(u"__future__", feature, node): | |
237 return | |
238 | |
239 # Look for a shebang or encoding line | |
240 shebang_encoding_idx = None | |
241 | |
242 for idx, node in enumerate(root.children): | |
243 # Is it a shebang or encoding line? | |
244 if is_shebang_comment(node) or is_encoding_comment(node): | |
245 shebang_encoding_idx = idx | |
246 if is_docstring(node): | |
247 # skip over docstring | |
248 continue | |
249 names = check_future_import(node) | |
250 if not names: | |
251 # not a future statement; need to insert before this | |
252 break | |
253 if feature in names: | |
254 # already imported | |
255 return | |
256 | |
257 import_ = FromImport(u'__future__', [Leaf(token.NAME, feature, prefix=" ")]) | |
258 if shebang_encoding_idx == 0 and idx == 0: | |
259 # If this __future__ import would go on the first line, | |
260 # detach the shebang / encoding prefix from the current first line. | |
261 # and attach it to our new __future__ import node. | |
262 import_.prefix = root.children[0].prefix | |
263 root.children[0].prefix = u'' | |
264 # End the __future__ import line with a newline and add a blank line | |
265 # afterwards: | |
266 children = [import_ , Newline()] | |
267 root.insert_child(idx, Node(syms.simple_stmt, children)) | |
268 | |
269 | |
270 def future_import2(feature, node): | |
271 """ | |
272 An alternative to future_import() which might not work ... | |
273 """ | |
274 root = find_root(node) | |
275 | |
276 if does_tree_import(u"__future__", feature, node): | |
277 return | |
278 | |
279 insert_pos = 0 | |
280 for idx, node in enumerate(root.children): | |
281 if node.type == syms.simple_stmt and node.children and \ | |
282 node.children[0].type == token.STRING: | |
283 insert_pos = idx + 1 | |
284 break | |
285 | |
286 for thing_after in root.children[insert_pos:]: | |
287 if thing_after.type == token.NEWLINE: | |
288 insert_pos += 1 | |
289 continue | |
290 | |
291 prefix = thing_after.prefix | |
292 thing_after.prefix = u"" | |
293 break | |
294 else: | |
295 prefix = u"" | |
296 | |
297 import_ = FromImport(u"__future__", [Leaf(token.NAME, feature, prefix=u" ")]) | |
298 | |
299 children = [import_, Newline()] | |
300 root.insert_child(insert_pos, Node(syms.simple_stmt, children, prefix=prefix)) | |
301 | |
302 def parse_args(arglist, scheme): | |
303 u""" | |
304 Parse a list of arguments into a dict | |
305 """ | |
306 arglist = [i for i in arglist if i.type != token.COMMA] | |
307 | |
308 ret_mapping = dict([(k, None) for k in scheme]) | |
309 | |
310 for i, arg in enumerate(arglist): | |
311 if arg.type == syms.argument and arg.children[1].type == token.EQUAL: | |
312 # argument < NAME '=' any > | |
313 slot = arg.children[0].value | |
314 ret_mapping[slot] = arg.children[2] | |
315 else: | |
316 slot = scheme[i] | |
317 ret_mapping[slot] = arg | |
318 | |
319 return ret_mapping | |
320 | |
321 | |
322 # def is_import_from(node): | |
323 # """Returns true if the node is a statement "from ... import ..." | |
324 # """ | |
325 # return node.type == syms.import_from | |
326 | |
327 | |
328 def is_import_stmt(node): | |
329 return (node.type == syms.simple_stmt and node.children and | |
330 is_import(node.children[0])) | |
331 | |
332 | |
333 def touch_import_top(package, name_to_import, node): | |
334 """Works like `does_tree_import` but adds an import statement at the | |
335 top if it was not imported (but below any __future__ imports) and below any | |
336 comments such as shebang lines). | |
337 | |
338 Based on lib2to3.fixer_util.touch_import() | |
339 | |
340 Calling this multiple times adds the imports in reverse order. | |
341 | |
342 Also adds "standard_library.install_aliases()" after "from future import | |
343 standard_library". This should probably be factored into another function. | |
344 """ | |
345 | |
346 root = find_root(node) | |
347 | |
348 if does_tree_import(package, name_to_import, root): | |
349 return | |
350 | |
351 # Ideally, we would look for whether futurize --all-imports has been run, | |
352 # as indicated by the presence of ``from builtins import (ascii, ..., | |
353 # zip)`` -- and, if it has, we wouldn't import the name again. | |
354 | |
355 # Look for __future__ imports and insert below them | |
356 found = False | |
357 for name in ['absolute_import', 'division', 'print_function', | |
358 'unicode_literals']: | |
359 if does_tree_import('__future__', name, root): | |
360 found = True | |
361 break | |
362 if found: | |
363 # At least one __future__ import. We want to loop until we've seen them | |
364 # all. | |
365 start, end = None, None | |
366 for idx, node in enumerate(root.children): | |
367 if check_future_import(node): | |
368 start = idx | |
369 # Start looping | |
370 idx2 = start | |
371 while node: | |
372 node = node.next_sibling | |
373 idx2 += 1 | |
374 if not check_future_import(node): | |
375 end = idx2 | |
376 break | |
377 break | |
378 assert start is not None | |
379 assert end is not None | |
380 insert_pos = end | |
381 else: | |
382 # No __future__ imports. | |
383 # We look for a docstring and insert the new node below that. If no docstring | |
384 # exists, just insert the node at the top. | |
385 for idx, node in enumerate(root.children): | |
386 if node.type != syms.simple_stmt: | |
387 break | |
388 if not is_docstring(node): | |
389 # This is the usual case. | |
390 break | |
391 insert_pos = idx | |
392 | |
393 if package is None: | |
394 import_ = Node(syms.import_name, [ | |
395 Leaf(token.NAME, u"import"), | |
396 Leaf(token.NAME, name_to_import, prefix=u" ") | |
397 ]) | |
398 else: | |
399 import_ = FromImport(package, [Leaf(token.NAME, name_to_import, prefix=u" ")]) | |
400 if name_to_import == u'standard_library': | |
401 # Add: | |
402 # standard_library.install_aliases() | |
403 # after: | |
404 # from future import standard_library | |
405 install_hooks = Node(syms.simple_stmt, | |
406 [Node(syms.power, | |
407 [Leaf(token.NAME, u'standard_library'), | |
408 Node(syms.trailer, [Leaf(token.DOT, u'.'), | |
409 Leaf(token.NAME, u'install_aliases')]), | |
410 Node(syms.trailer, [Leaf(token.LPAR, u'('), | |
411 Leaf(token.RPAR, u')')]) | |
412 ]) | |
413 ] | |
414 ) | |
415 children_hooks = [install_hooks, Newline()] | |
416 else: | |
417 children_hooks = [] | |
418 | |
419 # FromImport(package, [Leaf(token.NAME, name_to_import, prefix=u" ")]) | |
420 | |
421 children_import = [import_, Newline()] | |
422 old_prefix = root.children[insert_pos].prefix | |
423 root.children[insert_pos].prefix = u'' | |
424 root.insert_child(insert_pos, Node(syms.simple_stmt, children_import, prefix=old_prefix)) | |
425 if len(children_hooks) > 0: | |
426 root.insert_child(insert_pos + 1, Node(syms.simple_stmt, children_hooks)) | |
427 | |
428 | |
429 ## The following functions are from python-modernize by Armin Ronacher: | |
430 # (a little edited). | |
431 | |
432 def check_future_import(node): | |
433 """If this is a future import, return set of symbols that are imported, | |
434 else return None.""" | |
435 # node should be the import statement here | |
436 savenode = node | |
437 if not (node.type == syms.simple_stmt and node.children): | |
438 return set() | |
439 node = node.children[0] | |
440 # now node is the import_from node | |
441 if not (node.type == syms.import_from and | |
442 # node.type == token.NAME and # seems to break it | |
443 hasattr(node.children[1], 'value') and | |
444 node.children[1].value == u'__future__'): | |
445 return set() | |
446 if node.children[3].type == token.LPAR: | |
447 node = node.children[4] | |
448 else: | |
449 node = node.children[3] | |
450 # now node is the import_as_name[s] | |
451 # print(python_grammar.number2symbol[node.type]) # breaks sometimes | |
452 if node.type == syms.import_as_names: | |
453 result = set() | |
454 for n in node.children: | |
455 if n.type == token.NAME: | |
456 result.add(n.value) | |
457 elif n.type == syms.import_as_name: | |
458 n = n.children[0] | |
459 assert n.type == token.NAME | |
460 result.add(n.value) | |
461 return result | |
462 elif node.type == syms.import_as_name: | |
463 node = node.children[0] | |
464 assert node.type == token.NAME | |
465 return set([node.value]) | |
466 elif node.type == token.NAME: | |
467 return set([node.value]) | |
468 else: | |
469 # TODO: handle brackets like this: | |
470 # from __future__ import (absolute_import, division) | |
471 assert False, "strange import: %s" % savenode | |
472 | |
473 | |
474 SHEBANG_REGEX = r'^#!.*python' | |
475 ENCODING_REGEX = r"^#.*coding[:=]\s*([-\w.]+)" | |
476 | |
477 | |
478 def is_shebang_comment(node): | |
479 """ | |
480 Comments are prefixes for Leaf nodes. Returns whether the given node has a | |
481 prefix that looks like a shebang line or an encoding line: | |
482 | |
483 #!/usr/bin/env python | |
484 #!/usr/bin/python3 | |
485 """ | |
486 return bool(re.match(SHEBANG_REGEX, node.prefix)) | |
487 | |
488 | |
489 def is_encoding_comment(node): | |
490 """ | |
491 Comments are prefixes for Leaf nodes. Returns whether the given node has a | |
492 prefix that looks like an encoding line: | |
493 | |
494 # coding: utf-8 | |
495 # encoding: utf-8 | |
496 # -*- coding: <encoding name> -*- | |
497 # vim: set fileencoding=<encoding name> : | |
498 """ | |
499 return bool(re.match(ENCODING_REGEX, node.prefix)) | |
500 | |
501 | |
502 def wrap_in_fn_call(fn_name, args, prefix=None): | |
503 """ | |
504 Example: | |
505 >>> wrap_in_fn_call("oldstr", (arg,)) | |
506 oldstr(arg) | |
507 | |
508 >>> wrap_in_fn_call("olddiv", (arg1, arg2)) | |
509 olddiv(arg1, arg2) | |
510 | |
511 >>> wrap_in_fn_call("olddiv", [arg1, comma, arg2, comma, arg3]) | |
512 olddiv(arg1, arg2, arg3) | |
513 """ | |
514 assert len(args) > 0 | |
515 if len(args) == 2: | |
516 expr1, expr2 = args | |
517 newargs = [expr1, Comma(), expr2] | |
518 else: | |
519 newargs = args | |
520 return Call(Name(fn_name), newargs, prefix=prefix) |