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,56 @@ +#!/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 main(argv): + if not argv: + print('Usage: codemod_nestedwith.py FILES') + + for i, path in enumerate(argv): + print('(%d/%d) scanning %s' % (i + 1, len(argv), path)) + changed = False + red = redbaron.RedBaron(readpath(path)) + processed = set() + for node in red.find_all('with'): + if node in processed or node.type != 'with': + continue + top = node + child = top[0] + while True: + if len(top) > 1 or child.type != 'with': + break + # estimate line length after merging two "with"s + new = '%swith %s:' % (top.indentation, top.contexts.dumps()) + new += ', %s' % child.contexts.dumps() + # only do the rewrite if the end result is within 80 chars + if len(new) > 80: + break + processed.add(child) + top.contexts.extend(child.contexts) + top.value = child.value + top.value.decrease_indentation(4) + child = child[0] + changed = True + if changed: + print('updating %s' % path) + writepath(path, red.dumps()) + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/tests/hghave.py b/tests/hghave.py --- a/tests/hghave.py +++ b/tests/hghave.py @@ -448,6 +448,15 @@ except ImportError: return False +@check("redbaron", "RedBaron python refactoring library") +def has_redbaron(): + try: + import redbaron + redbaron.RedBaron('1+1') # silence unused import warning + return True + except ImportError: + return False + @check("outer-repo", "outer repo") def has_outer_repo(): # failing for other reasons than 'no repo' imply that there is a repo diff --git a/tests/test-codemod-nestedwith.t b/tests/test-codemod-nestedwith.t new file mode 100644 --- /dev/null +++ b/tests/test-codemod-nestedwith.t @@ -0,0 +1,59 @@ +#require redbaron + + $ cat > a.py < with a() as b, c as d: + > with e.f, g.h.i[3].j() as k: + > with l: + > with m(): + > with superlongmethodsocannotbeinasamelinewith80charlimit: + > # do something + > result = foo.bar() + > if result: + > return 0 + > raise RuntimeError('unexpected') + > + > with a: + > with b: + > with c, d: + > with e, f: + > with g: + > foo(2) + > with h: + > with i: + > foo(3) + > bar(1) + > + > with supersupersupersupersuperlongmethod() as supersupersuperlongx: + > with a as somethinglong: + > with b as something: + > with supersupersuperlongmethod() as supersupersuperlongy: + > with c: + > with d: + > baz(3) + > EOF + + $ $TESTDIR/../contrib/codemod/codemod_nestedwith.py a.py + (1/1) scanning a.py + updating a.py + + $ cat a.py + with a() as b, c as d, e.f, g.h.i[3].j() as k, l, m(): + with superlongmethodsocannotbeinasamelinewith80charlimit: + # do something + result = foo.bar() + if result: + return 0 + raise RuntimeError('unexpected') + + with a, b: + with c, d, e, f: + with g: + foo(2) + with h, i: + foo(3) + bar(1) + + with supersupersupersupersuperlongmethod() as supersupersuperlongx: + with a as somethinglong, b as something: + with supersupersuperlongmethod() as supersupersuperlongy, c, d: + baz(3)