diff --git a/contrib/codemod/codemod_nestedwith.py b/contrib/codemod/codemod_nestedwith.py new file mode 100755 --- /dev/null +++ b/contrib/codemod/codemod_nestedwith.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python +# codemod_nestedwith.py - codemod tool to rewrite nested with +# +# Copyright 2017 Facebook, Inc. +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. +from __future__ import absolute_import, print_function + +import sys + +import redbaron + +def readpath(path): + with open(path) as f: + return f.read() + +def writepath(path, content): + with open(path, 'w') as f: + f.write(content) + +def extractstring(rnode): + """get the string from a RedBaron string or call_argument node""" + while rnode.type != 'string': + rnode = rnode.value + return rnode.value[1:-1] # unquote, "'str'" -> "str" + +def nestedwithitems(red): + """match nested withs, yield (node, innernode, level)""" + visited = set() + for node in red.find_all('with'): + if node in visited: + continue + entry = None + level = 0 + cur = node + try: + while True: + if cur.type == 'with': + visited.add(cur) + level += 1 + cur = cur[0] + else: + break + if level > 1: + entry = (node, level) + except Exception: + raise + #pass + else: + if entry: + yield entry + +def main(argv): + if not argv: + print('Usage: codemod_nestedwith.py FILES\n') + + # first loop: scan all files before taking any action + for i, path in enumerate(argv): + print('(%d/%d) scanning %s' % (i + 1, len(argv), path)) + changed = False + red = redbaron.RedBaron(readpath(path)) + + for node, level in nestedwithitems(red): + while level > 1: + # estimate line length after merging two "with"s + new = '%swith %s:' % (node.indentation, node.contexts.dumps()) + new += ', %s' % node[0].contexts.dumps() + # only do the rewrite if the end result is within 80 chars + if len(new) > 80: + break + node.contexts.append(node[0].contexts) + node.value = node[0].value + node.value.decrease_indentation(4) + level -= 1 + changed = True + if changed: + print('updating %s' % path) + writepath(path, red.dumps()) + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:]))