Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(40)

Side by Side Diff: third_party/google-endpoints/libfuturize/fixer_util.py

Issue 2666783008: Add google-endpoints to third_party/. (Closed)
Patch Set: Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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
OLDNEW
« no previous file with comments | « third_party/google-endpoints/libfuturize/__init__.py ('k') | third_party/google-endpoints/libfuturize/fixes/__init__.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698