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)