| | |
| | |
| | """ |
| | Part of the astor library for Python AST manipulation. |
| | |
| | License: 3-clause BSD |
| | |
| | Copyright (c) 2015 Patrick Maupin |
| | """ |
| |
|
| | import sys |
| | import os |
| | import ast |
| | import shutil |
| | import logging |
| |
|
| | from astor.code_gen import to_source |
| | from astor.file_util import code_to_ast |
| | from astor.node_util import (allow_ast_comparison, dump_tree, |
| | strip_tree, fast_compare) |
| |
|
| |
|
| | dsttree = 'tmp_rtrip' |
| |
|
| | |
| |
|
| |
|
| | def out_prep(s, pre_encoded=(sys.version_info[0] == 2)): |
| | return s if pre_encoded else s.encode('utf-8') |
| |
|
| |
|
| | def convert(srctree, dsttree=dsttree, readonly=False, dumpall=False, |
| | ignore_exceptions=False, fullcomp=False): |
| | """Walk the srctree, and convert/copy all python files |
| | into the dsttree |
| | |
| | """ |
| |
|
| | if fullcomp: |
| | allow_ast_comparison() |
| |
|
| | parse_file = code_to_ast.parse_file |
| | find_py_files = code_to_ast.find_py_files |
| | srctree = os.path.normpath(srctree) |
| |
|
| | if not readonly: |
| | dsttree = os.path.normpath(dsttree) |
| | logging.info('') |
| | logging.info('Trashing ' + dsttree) |
| | shutil.rmtree(dsttree, True) |
| |
|
| | unknown_src_nodes = set() |
| | unknown_dst_nodes = set() |
| | badfiles = set() |
| | broken = [] |
| |
|
| | oldpath = None |
| |
|
| | allfiles = find_py_files(srctree, None if readonly else dsttree) |
| | for srcpath, fname in allfiles: |
| | |
| | if not readonly and srcpath != oldpath: |
| | oldpath = srcpath |
| | if srcpath >= srctree: |
| | dstpath = srcpath.replace(srctree, dsttree, 1) |
| | if not dstpath.startswith(dsttree): |
| | raise ValueError("%s not a subdirectory of %s" % |
| | (dstpath, dsttree)) |
| | else: |
| | assert srctree.startswith(srcpath) |
| | dstpath = dsttree |
| | os.makedirs(dstpath) |
| |
|
| | srcfname = os.path.join(srcpath, fname) |
| | logging.info('Converting %s' % srcfname) |
| | try: |
| | srcast = parse_file(srcfname) |
| | except SyntaxError: |
| | badfiles.add(srcfname) |
| | continue |
| |
|
| | try: |
| | dsttxt = to_source(srcast) |
| | except Exception: |
| | if not ignore_exceptions: |
| | raise |
| | dsttxt = '' |
| |
|
| | if not readonly: |
| | dstfname = os.path.join(dstpath, fname) |
| | try: |
| | with open(dstfname, 'wb') as f: |
| | f.write(out_prep(dsttxt)) |
| | except UnicodeEncodeError: |
| | badfiles.add(dstfname) |
| |
|
| | |
| | |
| | try: |
| | dstast = ast.parse(dsttxt) if readonly else parse_file(dstfname) |
| | except SyntaxError: |
| | dstast = [] |
| | if fullcomp: |
| | unknown_src_nodes.update(strip_tree(srcast)) |
| | unknown_dst_nodes.update(strip_tree(dstast)) |
| | bad = srcast != dstast |
| | else: |
| | bad = not fast_compare(srcast, dstast) |
| | if dumpall or bad: |
| | srcdump = dump_tree(srcast) |
| | dstdump = dump_tree(dstast) |
| | logging.warning(' calculating dump -- %s' % |
| | ('bad' if bad else 'OK')) |
| | if bad: |
| | broken.append(srcfname) |
| | if dumpall or bad: |
| | if not readonly: |
| | try: |
| | with open(dstfname[:-3] + '.srcdmp', 'wb') as f: |
| | f.write(out_prep(srcdump)) |
| | except UnicodeEncodeError: |
| | badfiles.add(dstfname[:-3] + '.srcdmp') |
| | try: |
| | with open(dstfname[:-3] + '.dstdmp', 'wb') as f: |
| | f.write(out_prep(dstdump)) |
| | except UnicodeEncodeError: |
| | badfiles.add(dstfname[:-3] + '.dstdmp') |
| | elif dumpall: |
| | sys.stdout.write('\n\nAST:\n\n ') |
| | sys.stdout.write(srcdump.replace('\n', '\n ')) |
| | sys.stdout.write('\n\nDecompile:\n\n ') |
| | sys.stdout.write(dsttxt.replace('\n', '\n ')) |
| | sys.stdout.write('\n\nNew AST:\n\n ') |
| | sys.stdout.write('(same as old)' if dstdump == srcdump |
| | else dstdump.replace('\n', '\n ')) |
| | sys.stdout.write('\n') |
| |
|
| | if badfiles: |
| | logging.warning('\nFiles not processed due to syntax errors:') |
| | for fname in sorted(badfiles): |
| | logging.warning(' %s' % fname) |
| | if broken: |
| | logging.warning('\nFiles failed to round-trip to AST:') |
| | for srcfname in broken: |
| | logging.warning(' %s' % srcfname) |
| |
|
| | ok_to_strip = 'col_offset _precedence _use_parens lineno _p_op _pp' |
| | ok_to_strip = set(ok_to_strip.split()) |
| | bad_nodes = (unknown_dst_nodes | unknown_src_nodes) - ok_to_strip |
| | if bad_nodes: |
| | logging.error('\nERROR -- UNKNOWN NODES STRIPPED: %s' % bad_nodes) |
| | logging.info('\n') |
| | return broken |
| |
|
| |
|
| | def usage(msg): |
| | raise SystemExit(textwrap.dedent(""" |
| | |
| | Error: %s |
| | |
| | Usage: |
| | |
| | python -m astor.rtrip [readonly] [<source>] |
| | |
| | |
| | This utility tests round-tripping of Python source to AST |
| | and back to source. |
| | |
| | If readonly is specified, then the source will be tested, |
| | but no files will be written. |
| | |
| | if the source is specified to be "stdin" (without quotes) |
| | then any source entered at the command line will be compiled |
| | into an AST, converted back to text, and then compiled to |
| | an AST again, and the results will be displayed to stdout. |
| | |
| | If neither readonly nor stdin is specified, then rtrip |
| | will create a mirror directory named tmp_rtrip and will |
| | recursively round-trip all the Python source from the source |
| | into the tmp_rtrip dir, after compiling it and then reconstituting |
| | it through code_gen.to_source. |
| | |
| | If the source is not specified, the entire Python library will be used. |
| | |
| | """) % msg) |
| |
|
| |
|
| | if __name__ == '__main__': |
| | import textwrap |
| |
|
| | args = sys.argv[1:] |
| |
|
| | readonly = 'readonly' in args |
| | if readonly: |
| | args.remove('readonly') |
| |
|
| | if not args: |
| | args = [os.path.dirname(textwrap.__file__)] |
| |
|
| | if len(args) > 1: |
| | usage("Too many arguments") |
| |
|
| | fname, = args |
| | dumpall = False |
| | if not os.path.exists(fname): |
| | dumpall = fname == 'stdin' or usage("Cannot find directory %s" % fname) |
| |
|
| | logging.basicConfig(format='%(msg)s', level=logging.INFO) |
| | convert(fname, readonly=readonly or dumpall, dumpall=dumpall) |
| |
|