Mercurial > repos > shellac > guppy_basecaller
comparison env/lib/python3.7/site-packages/libfuturize/fixer_util.py @ 5:9b1c78e6ba9c draft default tip
"planemo upload commit 6c0a8142489327ece472c84e558c47da711a9142"
| author | shellac |
|---|---|
| date | Mon, 01 Jun 2020 08:59:25 -0400 |
| parents | 79f47841a781 |
| children |
comparison
equal
deleted
inserted
replaced
| 4:79f47841a781 | 5:9b1c78e6ba9c |
|---|---|
| 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) |
