OLD | NEW |
(Empty) | |
| 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.INDEN
T: |
| 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/wit
h 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 docs
tring |
| 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 node = node.children[3] |
| 447 # now node is the import_as_name[s] |
| 448 # print(python_grammar.number2symbol[node.type]) # breaks sometimes |
| 449 if node.type == syms.import_as_names: |
| 450 result = set() |
| 451 for n in node.children: |
| 452 if n.type == token.NAME: |
| 453 result.add(n.value) |
| 454 elif n.type == syms.import_as_name: |
| 455 n = n.children[0] |
| 456 assert n.type == token.NAME |
| 457 result.add(n.value) |
| 458 return result |
| 459 elif node.type == syms.import_as_name: |
| 460 node = node.children[0] |
| 461 assert node.type == token.NAME |
| 462 return set([node.value]) |
| 463 elif node.type == token.NAME: |
| 464 return set([node.value]) |
| 465 else: |
| 466 # TODO: handle brackets like this: |
| 467 # from __future__ import (absolute_import, division) |
| 468 assert False, "strange import: %s" % savenode |
| 469 |
| 470 |
| 471 SHEBANG_REGEX = r'^#!.*python' |
| 472 ENCODING_REGEX = r"^#.*coding[:=]\s*([-\w.]+)" |
| 473 |
| 474 |
| 475 def is_shebang_comment(node): |
| 476 """ |
| 477 Comments are prefixes for Leaf nodes. Returns whether the given node has a |
| 478 prefix that looks like a shebang line or an encoding line: |
| 479 |
| 480 #!/usr/bin/env python |
| 481 #!/usr/bin/python3 |
| 482 """ |
| 483 return bool(re.match(SHEBANG_REGEX, node.prefix)) |
| 484 |
| 485 |
| 486 def is_encoding_comment(node): |
| 487 """ |
| 488 Comments are prefixes for Leaf nodes. Returns whether the given node has a |
| 489 prefix that looks like an encoding line: |
| 490 |
| 491 # coding: utf-8 |
| 492 # encoding: utf-8 |
| 493 # -*- coding: <encoding name> -*- |
| 494 # vim: set fileencoding=<encoding name> : |
| 495 """ |
| 496 return bool(re.match(ENCODING_REGEX, node.prefix)) |
| 497 |
| 498 |
| 499 def wrap_in_fn_call(fn_name, args, prefix=None): |
| 500 """ |
| 501 Example: |
| 502 >>> wrap_in_fn_call("oldstr", (arg,)) |
| 503 oldstr(arg) |
| 504 |
| 505 >>> wrap_in_fn_call("olddiv", (arg1, arg2)) |
| 506 olddiv(arg1, arg2) |
| 507 """ |
| 508 assert len(args) > 0 |
| 509 if len(args) == 1: |
| 510 newargs = args |
| 511 elif len(args) == 2: |
| 512 expr1, expr2 = args |
| 513 newargs = [expr1, Comma(), expr2] |
| 514 else: |
| 515 assert NotImplementedError('write me') |
| 516 return Call(Name(fn_name), newargs, prefix=prefix) |
| 517 |
| 518 |
OLD | NEW |