| | |
| | """ |
| | Part of the astor library for Python AST manipulation. |
| | |
| | License: 3-clause BSD |
| | |
| | Copyright (c) 2015 Patrick Maupin |
| | |
| | Pretty-print source -- post-process for the decompiler |
| | |
| | The goals of the initial cut of this engine are: |
| | |
| | 1) Do a passable, if not PEP8, job of line-wrapping. |
| | |
| | 2) Serve as an example of an interface to the decompiler |
| | for anybody who wants to do a better job. :) |
| | """ |
| |
|
| |
|
| | def pretty_source(source): |
| | """ Prettify the source. |
| | """ |
| |
|
| | return ''.join(split_lines(source)) |
| |
|
| |
|
| | def split_lines(source, maxline=79): |
| | """Split inputs according to lines. |
| | If a line is short enough, just yield it. |
| | Otherwise, fix it. |
| | """ |
| | result = [] |
| | extend = result.extend |
| | append = result.append |
| | line = [] |
| | multiline = False |
| | count = 0 |
| | for item in source: |
| | newline = type(item)('\n') |
| | index = item.find(newline) |
| | if index: |
| | line.append(item) |
| | multiline = index > 0 |
| | count += len(item) |
| | else: |
| | if line: |
| | if count <= maxline or multiline: |
| | extend(line) |
| | else: |
| | wrap_line(line, maxline, result) |
| | count = 0 |
| | multiline = False |
| | line = [] |
| | append(item) |
| | return result |
| |
|
| |
|
| | def count(group, slen=str.__len__): |
| | return sum([slen(x) for x in group]) |
| |
|
| |
|
| | def wrap_line(line, maxline=79, result=[], count=count): |
| | """ We have a line that is too long, |
| | so we're going to try to wrap it. |
| | """ |
| |
|
| | |
| |
|
| | append = result.append |
| | extend = result.extend |
| |
|
| | indentation = line[0] |
| | lenfirst = len(indentation) |
| | indent = lenfirst - len(indentation.lstrip()) |
| | assert indent in (0, lenfirst) |
| | indentation = line.pop(0) if indent else '' |
| |
|
| | |
| |
|
| | dgroups = list(delimiter_groups(line)) |
| | unsplittable = dgroups[::2] |
| | splittable = dgroups[1::2] |
| |
|
| | |
| | |
| |
|
| | if max(count(x) for x in unsplittable) > maxline - indent: |
| | line = add_parens(line, maxline, indent) |
| | dgroups = list(delimiter_groups(line)) |
| | unsplittable = dgroups[::2] |
| | splittable = dgroups[1::2] |
| |
|
| | |
| | |
| |
|
| | first = unsplittable[0] |
| | append(indentation) |
| | extend(first) |
| | if not splittable: |
| | return result |
| | pos = indent + count(first) |
| | indentation += ' ' |
| | indent += 4 |
| | if indent >= maxline / 2: |
| | maxline = maxline / 2 + indent |
| |
|
| | for sg, nsg in zip(splittable, unsplittable[1:]): |
| |
|
| | if sg: |
| | |
| | |
| | if pos > indent and pos + len(sg[0]) > maxline: |
| | append('\n') |
| | append(indentation) |
| | pos = indent |
| |
|
| | |
| | |
| | csg = count(sg) |
| | while pos + csg > maxline: |
| | ready, sg = split_group(sg, pos, maxline) |
| | if ready[-1].endswith(' '): |
| | ready[-1] = ready[-1][:-1] |
| | extend(ready) |
| | append('\n') |
| | append(indentation) |
| | pos = indent |
| | csg = count(sg) |
| |
|
| | |
| | if sg: |
| | extend(sg) |
| | pos += csg |
| |
|
| | |
| | |
| | cnsg = count(nsg) |
| | if pos > indent and pos + cnsg > maxline: |
| | append('\n') |
| | append(indentation) |
| | pos = indent |
| | extend(nsg) |
| | pos += cnsg |
| |
|
| |
|
| | def split_group(source, pos, maxline): |
| | """ Split a group into two subgroups. The |
| | first will be appended to the current |
| | line, the second will start the new line. |
| | |
| | Note that the first group must always |
| | contain at least one item. |
| | |
| | The original group may be destroyed. |
| | """ |
| | first = [] |
| | source.reverse() |
| | while source: |
| | tok = source.pop() |
| | first.append(tok) |
| | pos += len(tok) |
| | if source: |
| | tok = source[-1] |
| | allowed = (maxline + 1) if tok.endswith(' ') else (maxline - 4) |
| | if pos + len(tok) > allowed: |
| | break |
| |
|
| | source.reverse() |
| | return first, source |
| |
|
| |
|
| | begin_delim = set('([{') |
| | end_delim = set(')]}') |
| | end_delim.add('):') |
| |
|
| |
|
| | def delimiter_groups(line, begin_delim=begin_delim, |
| | end_delim=end_delim): |
| | """Split a line into alternating groups. |
| | The first group cannot have a line feed inserted, |
| | the next one can, etc. |
| | """ |
| | text = [] |
| | line = iter(line) |
| | while True: |
| | |
| | for item in line: |
| | text.append(item) |
| | if item in begin_delim: |
| | break |
| | if not text: |
| | break |
| | yield text |
| |
|
| | |
| | level = 0 |
| | text = [] |
| | for item in line: |
| | if item in begin_delim: |
| | level += 1 |
| | elif item in end_delim: |
| | level -= 1 |
| | if level < 0: |
| | yield text |
| | text = [item] |
| | break |
| | text.append(item) |
| | else: |
| | assert not text, text |
| | break |
| |
|
| |
|
| | statements = set(['del ', 'return', 'yield ', 'if ', 'while ']) |
| |
|
| |
|
| | def add_parens(line, maxline, indent, statements=statements, count=count): |
| | """Attempt to add parentheses around the line |
| | in order to make it splittable. |
| | """ |
| |
|
| | if line[0] in statements: |
| | index = 1 |
| | if not line[0].endswith(' '): |
| | index = 2 |
| | assert line[1] == ' ' |
| | line.insert(index, '(') |
| | if line[-1] == ':': |
| | line.insert(-1, ')') |
| | else: |
| | line.append(')') |
| |
|
| | |
| | groups = list(get_assign_groups(line)) |
| | if len(groups) == 1: |
| | |
| | return line |
| |
|
| | counts = list(count(x) for x in groups) |
| | didwrap = False |
| |
|
| | |
| | if sum(counts[:-1]) >= maxline - indent - 4: |
| | for group in groups[:-1]: |
| | didwrap = False |
| | if len(group) > 1: |
| | group.insert(0, '(') |
| | group.insert(-1, ')') |
| | didwrap = True |
| |
|
| | |
| | if not didwrap or counts[-1] > maxline - indent - 10: |
| | groups[-1].insert(0, '(') |
| | groups[-1].append(')') |
| |
|
| | return [item for group in groups for item in group] |
| |
|
| |
|
| | |
| | ops = list('|^&+-*/%@~') + '<< >> // **'.split() + [''] |
| | ops = set(' %s= ' % x for x in ops) |
| |
|
| |
|
| | def get_assign_groups(line, ops=ops): |
| | """ Split a line into groups by assignment (including |
| | augmented assignment) |
| | """ |
| | group = [] |
| | for item in line: |
| | group.append(item) |
| | if item in ops: |
| | yield group |
| | group = [] |
| | yield group |
| |
|